Compare commits

...

2 Commits

Author SHA1 Message Date
Nicolas Meienberger
0dfe000148 feat: rename volumes & repositories 2025-11-28 20:47:27 +01:00
Nicolas Meienberger
7d9d3d5d3d fix: wrong compression modes used 2025-11-28 20:28:47 +01:00
10 changed files with 872 additions and 71 deletions

View File

@@ -709,7 +709,7 @@ export type ListRepositoriesResponses = {
* List of repositories
*/
200: Array<{
compressionMode: 'auto' | 'better' | 'fastest' | 'max' | 'off' | null;
compressionMode: 'auto' | 'max' | 'off' | null;
config: {
accessKeyId: string;
backend: 'r2';
@@ -849,7 +849,7 @@ export type CreateRepositoryData = {
isExistingRepository?: boolean;
};
name: string;
compressionMode?: 'auto' | 'better' | 'fastest' | 'max' | 'off';
compressionMode?: 'auto' | 'max' | 'off';
};
path?: never;
query?: never;
@@ -924,7 +924,7 @@ export type GetRepositoryResponses = {
* Repository details
*/
200: {
compressionMode: 'auto' | 'better' | 'fastest' | 'max' | 'off' | null;
compressionMode: 'auto' | 'max' | 'off' | null;
config: {
accessKeyId: string;
backend: 'r2';
@@ -1002,7 +1002,7 @@ export type GetRepositoryResponse = GetRepositoryResponses[keyof GetRepositoryRe
export type UpdateRepositoryData = {
body?: {
compressionMode?: 'auto' | 'better' | 'fastest' | 'max' | 'off';
compressionMode?: 'auto' | 'max' | 'off';
name?: string;
};
path: {
@@ -1028,7 +1028,7 @@ export type UpdateRepositoryResponses = {
* Repository updated successfully
*/
200: {
compressionMode: 'auto' | 'better' | 'fastest' | 'max' | 'off' | null;
compressionMode: 'auto' | 'max' | 'off' | null;
config: {
accessKeyId: string;
backend: 'r2';
@@ -1295,7 +1295,7 @@ export type ListBackupSchedulesResponses = {
lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null;
nextBackupAt: number | null;
repository: {
compressionMode: 'auto' | 'better' | 'fastest' | 'max' | 'off' | null;
compressionMode: 'auto' | 'max' | 'off' | null;
config: {
accessKeyId: string;
backend: 'r2';
@@ -1528,7 +1528,7 @@ export type GetBackupScheduleResponses = {
lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null;
nextBackupAt: number | null;
repository: {
compressionMode: 'auto' | 'better' | 'fastest' | 'max' | 'off' | null;
compressionMode: 'auto' | 'max' | 'off' | null;
config: {
accessKeyId: string;
backend: 'r2';
@@ -1742,7 +1742,7 @@ export type GetBackupScheduleForVolumeResponses = {
lastBackupStatus: 'error' | 'in_progress' | 'success' | 'warning' | null;
nextBackupAt: number | null;
repository: {
compressionMode: 'auto' | 'better' | 'fastest' | 'max' | 'off' | null;
compressionMode: 'auto' | 'max' | 'off' | null;
config: {
accessKeyId: string;
backend: 'r2';

View File

@@ -115,8 +115,6 @@ export const CreateRepositoryForm = ({
onChange={(e) => field.onChange(slugify(e.target.value))}
max={32}
min={2}
disabled={mode === "update"}
className={mode === "update" ? "bg-gray-50" : ""}
/>
</FormControl>
<FormDescription>Unique identifier for the repository.</FormDescription>
@@ -176,10 +174,8 @@ export const CreateRepositoryForm = ({
</FormControl>
<SelectContent>
<SelectItem value="off">Off</SelectItem>
<SelectItem value="auto">Auto</SelectItem>
<SelectItem value="fastest">Fastest</SelectItem>
<SelectItem value="better">Better</SelectItem>
<SelectItem value="max">Max</SelectItem>
<SelectItem value="auto">Auto (fast)</SelectItem>
<SelectItem value="max">Max (slower, better compression)</SelectItem>
</SelectContent>
</Select>
<FormDescription>Compression mode for backups stored in this repository.</FormDescription>

View File

@@ -104,8 +104,6 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for
onChange={(e) => field.onChange(slugify(e.target.value))}
max={32}
min={1}
disabled={mode === "update"}
className={mode === "update" ? "bg-gray-50" : ""}
/>
</FormControl>
<FormDescription>Unique identifier for the volume.</FormDescription>

View File

@@ -114,8 +114,6 @@ export const CreateNotificationForm = ({ onSubmit, mode = "create", initialValue
onChange={(e) => field.onChange(slugify(e.target.value))}
max={32}
min={2}
disabled={mode === "update"}
className={mode === "update" ? "bg-gray-50" : ""}
/>
</FormControl>
<FormDescription>Unique identifier for this notification destination.</FormDescription>

View File

@@ -1,63 +1,170 @@
import { useMutation } from "@tanstack/react-query";
import { useState } from "react";
import { toast } from "sonner";
import { useNavigate } from "react-router";
import { Card } from "~/client/components/ui/card";
import { Button } from "~/client/components/ui/button";
import { Input } from "~/client/components/ui/input";
import { Label } from "~/client/components/ui/label";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/client/components/ui/select";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "~/client/components/ui/alert-dialog";
import type { Repository } from "~/client/lib/types";
import { slugify } from "~/client/lib/utils";
import { updateRepositoryMutation } from "~/client/api-client/@tanstack/react-query.gen";
import type { UpdateRepositoryResponse } from "~/client/api-client/types.gen";
type CompressionMode = "off" | "auto" | "fastest" | "better" | "max";
type Props = {
repository: Repository;
};
export const RepositoryInfoTabContent = ({ repository }: Props) => {
return (
<Card className="p-6">
<div className="space-y-6">
<div>
<h3 className="text-lg font-semibold mb-4">Repository Information</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<div className="text-sm font-medium text-muted-foreground">Name</div>
<p className="mt-1 text-sm">{repository.name}</p>
</div>
<div>
<div className="text-sm font-medium text-muted-foreground">Backend</div>
<p className="mt-1 text-sm">{repository.type}</p>
</div>
<div>
<div className="text-sm font-medium text-muted-foreground">Compression Mode</div>
<p className="mt-1 text-sm">{repository.compressionMode || "off"}</p>
</div>
<div>
<div className="text-sm font-medium text-muted-foreground">Status</div>
<p className="mt-1 text-sm">{repository.status || "unknown"}</p>
</div>
<div>
<div className="text-sm font-medium text-muted-foreground">Created At</div>
<p className="mt-1 text-sm">{new Date(repository.createdAt).toLocaleString()}</p>
</div>
<div>
<div className="text-sm font-medium text-muted-foreground">Last Checked</div>
<p className="mt-1 text-sm">
{repository.lastChecked ? new Date(repository.lastChecked).toLocaleString() : "Never"}
</p>
</div>
</div>
</div>
{repository.lastError && (
<div>
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-red-500">Last Error</h3>
</div>
const navigate = useNavigate();
const [name, setName] = useState(repository.name);
const [compressionMode, setCompressionMode] = useState<CompressionMode>(
(repository.compressionMode as CompressionMode) || "off",
);
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
<div className="bg-red-500/10 border border-red-500/20 rounded-md p-4">
<p className="text-sm text-red-500">{repository.lastError}</p>
const updateMutation = useMutation({
...updateRepositoryMutation(),
onSuccess: (data: UpdateRepositoryResponse) => {
toast.success("Repository updated successfully");
setShowConfirmDialog(false);
if (data.name !== repository.name) {
navigate(`/repositories/${data.name}`);
}
},
onError: (error) => {
toast.error("Failed to update repository", { description: error.message, richColors: true });
setShowConfirmDialog(false);
},
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
setShowConfirmDialog(true);
};
const confirmUpdate = () => {
updateMutation.mutate({
path: { name: repository.name },
body: { name, compressionMode },
});
};
const hasChanges =
name !== repository.name || compressionMode !== ((repository.compressionMode as CompressionMode) || "off");
return (
<>
<Card className="p-6">
<form onSubmit={handleSubmit} className="space-y-6">
<div>
<h3 className="text-lg font-semibold mb-4">Repository Settings</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="name">Name</Label>
<Input
id="name"
value={name}
onChange={(e) => setName(slugify(e.target.value))}
placeholder="Repository name"
maxLength={32}
minLength={2}
/>
<p className="text-sm text-muted-foreground">Unique identifier for the repository.</p>
</div>
<div className="space-y-2">
<Label htmlFor="compressionMode">Compression Mode</Label>
<Select value={compressionMode} onValueChange={(val) => setCompressionMode(val as CompressionMode)}>
<SelectTrigger id="compressionMode">
<SelectValue placeholder="Select compression mode" />
</SelectTrigger>
<SelectContent>
<SelectItem value="off">Off</SelectItem>
<SelectItem value="auto">Auto</SelectItem>
<SelectItem value="max">Max</SelectItem>
</SelectContent>
</Select>
<p className="text-sm text-muted-foreground">Compression level for new data.</p>
</div>
</div>
</div>
)}
<div>
<h3 className="text-lg font-semibold mb-4">Configuration</h3>
<div className="bg-muted/50 rounded-md p-4">
<pre className="text-sm overflow-auto">{JSON.stringify(repository.config, null, 2)}</pre>
<div>
<h3 className="text-lg font-semibold mb-4">Repository Information</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<div className="text-sm font-medium text-muted-foreground">Backend</div>
<p className="mt-1 text-sm">{repository.type}</p>
</div>
<div>
<div className="text-sm font-medium text-muted-foreground">Status</div>
<p className="mt-1 text-sm">{repository.status || "unknown"}</p>
</div>
<div>
<div className="text-sm font-medium text-muted-foreground">Created At</div>
<p className="mt-1 text-sm">{new Date(repository.createdAt * 1000).toLocaleString()}</p>
</div>
<div>
<div className="text-sm font-medium text-muted-foreground">Last Checked</div>
<p className="mt-1 text-sm">
{repository.lastChecked ? new Date(repository.lastChecked).toLocaleString() : "Never"}
</p>
</div>
</div>
</div>
</div>
</div>
</Card>
{repository.lastError && (
<div>
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-red-500">Last Error</h3>
</div>
<div className="bg-red-500/10 border border-red-500/20 rounded-md p-4">
<p className="text-sm text-red-500">{repository.lastError}</p>
</div>
</div>
)}
<div>
<h3 className="text-lg font-semibold mb-4">Configuration</h3>
<div className="bg-muted/50 rounded-md p-4">
<pre className="text-sm overflow-auto">{JSON.stringify(repository.config, null, 2)}</pre>
</div>
</div>
<div className="flex justify-end pt-4 border-t">
<Button type="submit" disabled={!hasChanges || updateMutation.isPending} loading={updateMutation.isPending}>
Save Changes
</Button>
</div>
</form>
</Card>
<AlertDialog open={showConfirmDialog} onOpenChange={setShowConfirmDialog}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Update Repository</AlertDialogTitle>
<AlertDialogDescription>Are you sure you want to update the repository settings?</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={confirmUpdate}>Update</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</>
);
};

View File

@@ -1,5 +1,6 @@
import { useMutation } from "@tanstack/react-query";
import { useState } from "react";
import { useNavigate } from "react-router";
import { toast } from "sonner";
import { CreateVolumeForm, type FormValues } from "~/client/components/create-volume-form";
import {
@@ -17,6 +18,7 @@ import type { StatFs, Volume } from "~/client/lib/types";
import { HealthchecksCard } from "../components/healthchecks-card";
import { StorageChart } from "../components/storage-chart";
import { updateVolumeMutation } from "~/client/api-client/@tanstack/react-query.gen";
import type { UpdateVolumeResponse } from "~/client/api-client/types.gen";
type Props = {
volume: Volume;
@@ -24,12 +26,18 @@ type Props = {
};
export const VolumeInfoTabContent = ({ volume, statfs }: Props) => {
const navigate = useNavigate();
const updateMutation = useMutation({
...updateVolumeMutation(),
onSuccess: (_) => {
onSuccess: (data: UpdateVolumeResponse) => {
toast.success("Volume updated successfully");
setOpen(false);
setPendingValues(null);
if (data.name !== volume.name) {
navigate(`/volumes/${data.name}`);
}
},
onError: (error) => {
toast.error("Failed to update volume", { description: error.message });
@@ -50,7 +58,7 @@ export const VolumeInfoTabContent = ({ volume, statfs }: Props) => {
if (pendingValues) {
updateMutation.mutate({
path: { name: volume.name },
body: { config: pendingValues },
body: { name: pendingValues.name, config: pendingValues },
});
}
};

View File

@@ -0,0 +1 @@
UPDATE `repositories_table` SET `compression_mode` = 'auto' WHERE `compression_mode` IN ('fastest', 'better');

View File

@@ -0,0 +1,688 @@
{
"id": "d0bfd316-b8f5-459b-ab17-0ce679479321",
"prevId": "e50ff0fb-4111-4d20-b550-9407ee397517",
"version": "6",
"dialect": "sqlite",
"tables": {
"app_metadata": {
"name": "app_metadata",
"columns": {
"key": {
"name": "key",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"value": {
"name": "value",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch())"
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch())"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"backup_schedule_notifications_table": {
"name": "backup_schedule_notifications_table",
"columns": {
"schedule_id": {
"name": "schedule_id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"destination_id": {
"name": "destination_id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"notify_on_start": {
"name": "notify_on_start",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": false
},
"notify_on_success": {
"name": "notify_on_success",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": false
},
"notify_on_failure": {
"name": "notify_on_failure",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": true
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch())"
}
},
"indexes": {},
"foreignKeys": {
"backup_schedule_notifications_table_schedule_id_backup_schedules_table_id_fk": {
"name": "backup_schedule_notifications_table_schedule_id_backup_schedules_table_id_fk",
"tableFrom": "backup_schedule_notifications_table",
"columnsFrom": [
"schedule_id"
],
"tableTo": "backup_schedules_table",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "cascade"
},
"backup_schedule_notifications_table_destination_id_notification_destinations_table_id_fk": {
"name": "backup_schedule_notifications_table_destination_id_notification_destinations_table_id_fk",
"tableFrom": "backup_schedule_notifications_table",
"columnsFrom": [
"destination_id"
],
"tableTo": "notification_destinations_table",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "cascade"
}
},
"compositePrimaryKeys": {
"backup_schedule_notifications_table_schedule_id_destination_id_pk": {
"columns": [
"schedule_id",
"destination_id"
],
"name": "backup_schedule_notifications_table_schedule_id_destination_id_pk"
}
},
"uniqueConstraints": {},
"checkConstraints": {}
},
"backup_schedules_table": {
"name": "backup_schedules_table",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"volume_id": {
"name": "volume_id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"repository_id": {
"name": "repository_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"enabled": {
"name": "enabled",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": true
},
"cron_expression": {
"name": "cron_expression",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"retention_policy": {
"name": "retention_policy",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"exclude_patterns": {
"name": "exclude_patterns",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false,
"default": "'[]'"
},
"include_patterns": {
"name": "include_patterns",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false,
"default": "'[]'"
},
"last_backup_at": {
"name": "last_backup_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"last_backup_status": {
"name": "last_backup_status",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"last_backup_error": {
"name": "last_backup_error",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"next_backup_at": {
"name": "next_backup_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch())"
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch())"
}
},
"indexes": {},
"foreignKeys": {
"backup_schedules_table_volume_id_volumes_table_id_fk": {
"name": "backup_schedules_table_volume_id_volumes_table_id_fk",
"tableFrom": "backup_schedules_table",
"columnsFrom": [
"volume_id"
],
"tableTo": "volumes_table",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "cascade"
},
"backup_schedules_table_repository_id_repositories_table_id_fk": {
"name": "backup_schedules_table_repository_id_repositories_table_id_fk",
"tableFrom": "backup_schedules_table",
"columnsFrom": [
"repository_id"
],
"tableTo": "repositories_table",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "cascade"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"notification_destinations_table": {
"name": "notification_destinations_table",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"enabled": {
"name": "enabled",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": true
},
"type": {
"name": "type",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"config": {
"name": "config",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch())"
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch())"
}
},
"indexes": {
"notification_destinations_table_name_unique": {
"name": "notification_destinations_table_name_unique",
"columns": [
"name"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"repositories_table": {
"name": "repositories_table",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"short_id": {
"name": "short_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"type": {
"name": "type",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"config": {
"name": "config",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"compression_mode": {
"name": "compression_mode",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false,
"default": "'auto'"
},
"status": {
"name": "status",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false,
"default": "'unknown'"
},
"last_checked": {
"name": "last_checked",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"last_error": {
"name": "last_error",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch())"
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch())"
}
},
"indexes": {
"repositories_table_short_id_unique": {
"name": "repositories_table_short_id_unique",
"columns": [
"short_id"
],
"isUnique": true
},
"repositories_table_name_unique": {
"name": "repositories_table_name_unique",
"columns": [
"name"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"sessions_table": {
"name": "sessions_table",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"expires_at": {
"name": "expires_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch())"
}
},
"indexes": {},
"foreignKeys": {
"sessions_table_user_id_users_table_id_fk": {
"name": "sessions_table_user_id_users_table_id_fk",
"tableFrom": "sessions_table",
"columnsFrom": [
"user_id"
],
"tableTo": "users_table",
"columnsTo": [
"id"
],
"onUpdate": "no action",
"onDelete": "cascade"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"users_table": {
"name": "users_table",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"username": {
"name": "username",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"password_hash": {
"name": "password_hash",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"has_downloaded_restic_password": {
"name": "has_downloaded_restic_password",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch())"
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch())"
}
},
"indexes": {
"users_table_username_unique": {
"name": "users_table_username_unique",
"columns": [
"username"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"volumes_table": {
"name": "volumes_table",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"short_id": {
"name": "short_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"type": {
"name": "type",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"status": {
"name": "status",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'unmounted'"
},
"last_error": {
"name": "last_error",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"last_health_check": {
"name": "last_health_check",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch())"
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch())"
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch())"
},
"config": {
"name": "config",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"auto_remount": {
"name": "auto_remount",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": true
}
},
"indexes": {
"volumes_table_short_id_unique": {
"name": "volumes_table_short_id_unique",
"columns": [
"short_id"
],
"isUnique": true
},
"volumes_table_name_unique": {
"name": "volumes_table_name_unique",
"columns": [
"name"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
}
},
"views": {},
"enums": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
},
"internal": {
"indexes": {}
}
}

View File

@@ -120,6 +120,13 @@
"when": 1764194697035,
"tag": "0016_fix-timestamps-to-ms",
"breakpoints": true
},
{
"idx": 17,
"version": "6",
"when": 1764357897219,
"tag": "0017_fix-compression-modes",
"breakpoints": true
}
]
}

View File

@@ -93,8 +93,6 @@ export type RepositoryConfig = typeof repositoryConfigSchema.infer;
export const COMPRESSION_MODES = {
off: "off",
auto: "auto",
fastest: "fastest",
better: "better",
max: "max",
} as const;