feat(web): admin area with instance default theme, custom logo/favicon; .theme.json export

- Admin settings tab (admin-only) now holds user management plus a server-wide
  default theme editor (live preview + discard) and logo/favicon branding.
- New singleton InstanceSettings model + /api/instance router (public GET for
  the login screen; admin-gated theme PUT and logo/favicon upload/delete,
  reusing the avatar PIL/validation pattern).
- Instance default theme is the base users inherit and the target a per-colour
  "Reset" returns to (baseColor: instance default -> built-in).
- Custom favicon overrides the primary-colour tinting; custom logo replaces the
  top-left glyph+text, hard-capped so it only scales down and never breaks layout.
  Branding + defaults load before login (public endpoint).
- Theme export now uses the recognised .theme.json extension (old .theme still
  imports); import stays partial/unknown-key aware.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-07-20 14:19:15 +02:00
parent 6316ed3a6b
commit cea96660d9
13 changed files with 561 additions and 42 deletions

View File

@@ -92,6 +92,14 @@ export const SETTING_GROUPS = [
// Flat list of every syncable key the web manages (table order).
export const SYNCABLE_KEYS = SETTING_GROUPS.flatMap(g => g.rows.map(r => r.key));
// Instance-wide default theme set by an admin (from GET /api/instance/). It is
// the base a user inherits and the target that a per-colour "Reset" returns to.
// A user's own value always overrides it. See backend/routers/admin_router.py.
export let INSTANCE_DEFAULTS = {};
export function setInstanceDefaults(obj) { INSTANCE_DEFAULTS = obj || {}; }
// The effective default for a colour key: admin instance default → built-in.
export function baseColor(key) { return INSTANCE_DEFAULTS[key] || DEFAULT_COLORS[key]; }
const LOCAL_KEY = 'settingsLocal';
export function loadLocal() {