feat(phase3): import/export, sparklines, user theme overrides

- JSON import/export with conflict resolution (skip/overwrite) + admin UI
- Ping history sparklines on AppWidget and AppCard (24h, 288 points)
- Hourly cleanup job for old AppStatus records
- User theme preferences (hue, saturation, mode, background, locale)
- Settings page with ThemeCustomizer (sliders, toggles, live preview)
- Prisma migration for user preference fields
- i18n translations for all new strings (EN/RU)
This commit is contained in:
2026-03-25 00:51:01 +03:00
parent d155b3ce4a
commit c6a7de895d
30 changed files with 1633 additions and 44 deletions
+65
View File
@@ -181,6 +181,71 @@ export const createPermissionSchema = z.object({
level: z.enum([PermissionLevel.VIEW, PermissionLevel.EDIT, PermissionLevel.ADMIN])
});
// --- Import/Export ---
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),
boards: z.array(importBoardSchema),
groups: z.array(importGroupSchema),
settings: importSettingsSchema
});
// --- System Settings ---
export const updateSystemSettingsSchema = z.object({