diff --git a/apps/client/app/lib/breadcrumbs.ts b/apps/client/app/lib/breadcrumbs.ts index d3b3e7d..ba46e3a 100644 --- a/apps/client/app/lib/breadcrumbs.ts +++ b/apps/client/app/lib/breadcrumbs.ts @@ -42,14 +42,14 @@ export function generateBreadcrumbs(pathname: string, params: Record void; loading?: boolean; summaryContent?: React.ReactNode; + formId: string; }; const backupScheduleToFormValues = (schedule?: BackupSchedule): BackupScheduleFormValues | undefined => { @@ -73,7 +73,7 @@ const backupScheduleToFormValues = (schedule?: BackupSchedule): BackupScheduleFo }; }; -export const CreateScheduleForm = ({ initialValues, onSubmit, volume, loading }: Props) => { +export const CreateScheduleForm = ({ initialValues, formId, onSubmit, volume }: Props) => { const form = useForm({ resolver: arktypeResolver(cleanSchema as unknown as typeof formSchema), defaultValues: backupScheduleToFormValues(initialValues), @@ -91,6 +91,7 @@ export const CreateScheduleForm = ({ initialValues, onSubmit, volume, loading }:
@@ -335,11 +336,6 @@ export const CreateScheduleForm = ({ initialValues, onSubmit, volume, loading }: )} /> - - -
diff --git a/apps/client/app/modules/backups/components/schedule-summary.tsx b/apps/client/app/modules/backups/components/schedule-summary.tsx index e264c55..ad41b49 100644 --- a/apps/client/app/modules/backups/components/schedule-summary.tsx +++ b/apps/client/app/modules/backups/components/schedule-summary.tsx @@ -58,7 +58,9 @@ export const ScheduleSummary = (props: Props) => {
Backup schedule - Automated backup configuration for {volume.name} + + Automated backup configuration for volume {volume.name} +
@@ -79,7 +81,7 @@ export const ScheduleSummary = (props: Props) => {

Repository

-

{summary.repositoryLabel}

+

{repository.name}

Last backup

diff --git a/apps/client/app/modules/backups/routes/backup-details.tsx b/apps/client/app/modules/backups/routes/backup-details.tsx index 02e19bb..182c7a0 100644 --- a/apps/client/app/modules/backups/routes/backup-details.tsx +++ b/apps/client/app/modules/backups/routes/backup-details.tsx @@ -1,5 +1,5 @@ -import { useState } from "react"; -import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { useId, useState } from "react"; +import { useQuery, useMutation } from "@tanstack/react-query"; import { Link, useParams } from "react-router"; import { toast } from "sonner"; import { Button } from "~/components/ui/button"; @@ -15,13 +15,13 @@ import { CreateScheduleForm, type BackupScheduleFormValues } from "../components import { ScheduleSummary } from "../components/schedule-summary"; export default function ScheduleDetailsPage() { - const { scheduleId } = useParams<{ scheduleId: string }>(); - const queryClient = useQueryClient(); + const { id } = useParams<{ id: string }>(); const [isEditMode, setIsEditMode] = useState(false); + const formId = useId(); const { data: schedule, isLoading: loadingSchedule } = useQuery({ ...getBackupScheduleOptions({ - path: { scheduleId: scheduleId || "" }, + path: { scheduleId: id || "" }, }), }); @@ -31,8 +31,6 @@ export default function ScheduleDetailsPage() { ...upsertBackupScheduleMutation(), onSuccess: () => { toast.success("Backup schedule saved successfully"); - queryClient.invalidateQueries({ queryKey: ["listBackupSchedules"] }); - queryClient.invalidateQueries({ queryKey: ["getBackupSchedule", scheduleId] }); setIsEditMode(false); }, onError: (error) => { @@ -46,7 +44,6 @@ export default function ScheduleDetailsPage() { ...runBackupNowMutation(), onSuccess: () => { toast.success("Backup started successfully"); - queryClient.invalidateQueries({ queryKey: ["getBackupSchedule", scheduleId] }); }, onError: (error) => { toast.error("Failed to start backup", { @@ -117,42 +114,41 @@ export default function ScheduleDetailsPage() { if (!schedule) { return ( -
- - -

Schedule not found

- -
-
-
+ + +

Not found

+ +
+
); } if (!isEditMode) { return ( -
- -
+ ); } return ( -
-
+
+ +
+
-
); } diff --git a/apps/client/app/modules/backups/routes/backups.tsx b/apps/client/app/modules/backups/routes/backups.tsx index c70d57a..71d83e7 100644 --- a/apps/client/app/modules/backups/routes/backups.tsx +++ b/apps/client/app/modules/backups/routes/backups.tsx @@ -63,13 +63,15 @@ export default function Backups({ loaderData }: Route.ComponentProps) {
{schedules.map((schedule) => ( - +
- Volume #{schedule.volumeId} + + Volume {schedule.volume.name} +
{schedule.enabled ? "Active" : "Paused"} @@ -77,7 +79,7 @@ export default function Backups({ loaderData }: Route.ComponentProps) {
- {schedule.repositoryId} + {schedule.repository.name}
@@ -102,7 +104,7 @@ export default function Backups({ loaderData }: Route.ComponentProps) {
Status {schedule.lastBackupStatus} diff --git a/apps/server/drizzle/0009_little_adam_warlock.sql b/apps/server/drizzle/0009_little_adam_warlock.sql new file mode 100644 index 0000000..7f2c0d6 --- /dev/null +++ b/apps/server/drizzle/0009_little_adam_warlock.sql @@ -0,0 +1 @@ +DROP INDEX `backup_schedules_table_volume_id_unique`; \ No newline at end of file diff --git a/apps/server/drizzle/meta/0009_snapshot.json b/apps/server/drizzle/meta/0009_snapshot.json new file mode 100644 index 0000000..69e8bb6 --- /dev/null +++ b/apps/server/drizzle/meta/0009_snapshot.json @@ -0,0 +1,451 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "6a326ac0-cb3a-4c63-8800-bc86d18e0c1d", + "prevId": "6e35f329-5431-47fd-8862-8fb06b0afe4b", + "tables": { + "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", + "tableTo": "volumes_table", + "columnsFrom": [ + "volume_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_schedules_table_repository_id_repositories_table_id_fk": { + "name": "backup_schedules_table_repository_id_repositories_table_id_fk", + "tableFrom": "backup_schedules_table", + "tableTo": "repositories_table", + "columnsFrom": [ + "repository_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "repositories_table": { + "name": "repositories_table", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "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_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", + "tableTo": "users_table", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "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 + }, + "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 + }, + "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_name_unique": { + "name": "volumes_table_name_unique", + "columns": [ + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/apps/server/drizzle/meta/_journal.json b/apps/server/drizzle/meta/_journal.json index f8840a7..333a110 100644 --- a/apps/server/drizzle/meta/_journal.json +++ b/apps/server/drizzle/meta/_journal.json @@ -64,6 +64,13 @@ "when": 1761414054481, "tag": "0008_silent_lady_bullseye", "breakpoints": true + }, + { + "idx": 9, + "version": "6", + "when": 1762095226041, + "tag": "0009_little_adam_warlock", + "breakpoints": true } ] } \ No newline at end of file diff --git a/apps/server/src/db/schema.ts b/apps/server/src/db/schema.ts index d3d0f4c..5829055 100644 --- a/apps/server/src/db/schema.ts +++ b/apps/server/src/db/schema.ts @@ -70,7 +70,6 @@ export const backupSchedulesTable = sqliteTable("backup_schedules_table", { id: int().primaryKey({ autoIncrement: true }), volumeId: int("volume_id") .notNull() - .unique() .references(() => volumesTable.id, { onDelete: "cascade" }), repositoryId: text("repository_id") .notNull() diff --git a/apps/server/src/modules/repositories/repositories.controller.ts b/apps/server/src/modules/repositories/repositories.controller.ts index 3ea74ba..225cd79 100644 --- a/apps/server/src/modules/repositories/repositories.controller.ts +++ b/apps/server/src/modules/repositories/repositories.controller.ts @@ -71,8 +71,6 @@ export const repositoriesController = new Hono() const response = { snapshots }; - c.header("Cache-Control", "max-age=30, stale-while-revalidate=300"); - return c.json(response, 200); }) .get(