feat(backup): replace JSON import/export with SQLite database backup system

Replace the JSON-based import/export with a proper backup system that copies
the SQLite database file directly. Supports manual on-demand backups, periodic
scheduled backups via node-cron, configurable retention, file download, and
full database restore.

- Add backupService with VACUUM INTO for safe DB copies
- Add backupScheduler following healthcheckScheduler pattern
- Add 6 admin API endpoints (create, list, download, restore, delete, schedule)
- Add BackupPanel UI with backup table, confirmation dialogs, schedule config
- Add backup fields to SystemSettings schema
- Remove old ImportExportPanel, exportService, importService, and related code
This commit is contained in:
2026-04-02 23:16:18 +03:00
parent d479726fe3
commit b0439e39c4
24 changed files with 1079 additions and 1183 deletions
+9 -63
View File
@@ -220,69 +220,12 @@ export const createPermissionSchema = z.object({
level: z.enum([PermissionLevel.VIEW, PermissionLevel.EDIT, PermissionLevel.ADMIN])
});
// --- Import/Export ---
// --- Backup Schedule ---
const importAppSchema = z.object({
name: z.string().min(1).max(200),
url: z.string().url(),
icon: z.string().max(500).nullable(),
iconType: z.string().max(50),
description: z.string().max(1000).nullable(),
category: z.string().max(100).nullable(),
tags: z.string().max(500),
healthcheckEnabled: z.boolean(),
healthcheckInterval: z.number().int().min(30).max(86400),
healthcheckMethod: z.string(),
healthcheckExpectedStatus: z.number().int().min(100).max(599),
healthcheckTimeout: z.number().int().min(1000).max(30000)
});
const importWidgetSchema = z.object({
type: z.string().min(1),
order: z.number().int().min(0),
config: z.string(),
appName: z.string().nullable()
});
const importSectionSchema = z.object({
title: z.string().min(1).max(200),
icon: z.string().max(500).nullable(),
order: z.number().int().min(0),
isExpandedByDefault: z.boolean(),
widgets: z.array(importWidgetSchema)
});
const importBoardSchema = z.object({
name: z.string().min(1).max(200),
icon: z.string().max(500).nullable(),
description: z.string().max(1000).nullable(),
isDefault: z.boolean(),
isGuestAccessible: z.boolean(),
backgroundConfig: z.string().nullable(),
sections: z.array(importSectionSchema)
});
const importGroupSchema = z.object({
name: z.string().min(1).max(100),
description: z.string().max(500).nullable(),
isDefault: z.boolean()
});
const importSettingsSchema = z.object({
authMode: z.string().optional(),
registrationEnabled: z.boolean().optional(),
defaultTheme: z.string().optional(),
defaultPrimaryColor: z.string().optional(),
healthcheckDefaults: z.string().optional()
});
export const importDataSchema = z.object({
version: z.string(),
exportedAt: z.string(),
apps: z.array(importAppSchema).max(1000),
boards: z.array(importBoardSchema).max(100),
groups: z.array(importGroupSchema).max(100),
settings: importSettingsSchema
export const updateBackupScheduleSchema = z.object({
backupEnabled: z.boolean().optional(),
backupCronExpression: z.string().min(1).max(100).optional(),
backupMaxCount: z.number().int().min(1).max(100).optional()
});
// --- System Settings ---
@@ -468,7 +411,10 @@ export const auditLogQuerySchema = z.object({
AuditAction.APP_DELETED,
AuditAction.SETTINGS_UPDATED,
AuditAction.IMPORT,
AuditAction.EXPORT
AuditAction.EXPORT,
AuditAction.BACKUP_CREATED,
AuditAction.BACKUP_RESTORED,
AuditAction.BACKUP_DELETED
])
.optional(),
entityType: z.string().max(50).optional(),