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:
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user