Add theming system: custom palettes, per-user colour mode, admin appearance page
- 4 built-in themes (Default, Pastel, High Contrast, Ocean Blue) seeded as JSON files in /config/themes/ on startup; custom themes can be created, edited, and deleted via the new admin Appearance page - All theme tokens applied via JS inline CSS properties (no hardcoded CSS blocks) - New `color_mode` column on users table (migration dd6ad2f2c211); users can override the admin-set global default in Settings - Backend: GET/PATCH /settings/appearance, full CRUD on /settings/themes - Frontend: AdminAppearancePage with theme grid + colour pickers, SettingsPage replaces placeholder with mode selector, useTheme rewritten to fetch from API Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -88,7 +88,7 @@ docker compose up --build -d
|
||||
│ │ │ ├── config.py ← All settings via pydantic-settings (reads .env)
|
||||
│ │ │ ├── security.py ← JWT sign/verify (RS256), bcrypt hash/verify
|
||||
│ │ │ ├── sanitize.py ← Input sanitization helpers (see Security Standards)
|
||||
│ │ │ └── app_config.py ← Per-service config load/save to /config volume
|
||||
│ │ │ └── app_config.py ← Per-service config load/save to /config volume; theme files in /config/themes/
|
||||
│ │ ├── models/
|
||||
│ │ │ ├── __init__.py ← Imports all models (required for Alembic autogenerate)
|
||||
│ │ │ ├── user.py ← User model (see Database Models)
|
||||
@@ -100,11 +100,11 @@ docker compose up --build -d
|
||||
│ │ │ └── group.py ← GroupCreate/Update/Out/DetailOut, GroupMemberOut
|
||||
│ │ ├── routers/
|
||||
│ │ │ ├── auth.py ← POST /register, POST /login
|
||||
│ │ │ ├── users.py ← GET /me, GET+PATCH /me/preferences
|
||||
│ │ │ ├── users.py ← GET /me, GET+PATCH /me/preferences, PATCH /me/color-mode
|
||||
│ │ │ ├── profile.py ← GET+PUT /me (profile)
|
||||
│ │ │ ├── admin.py ← User admin CRUD (admin-only)
|
||||
│ │ │ ├── groups.py ← Group CRUD + member management (admin-only)
|
||||
│ │ │ ├── settings.py ← AI, doc limits, system prompts (admin-only)
|
||||
│ │ │ ├── settings.py ← AI, doc limits, system prompts, appearance, themes (admin-only)
|
||||
│ │ │ ├── services.py ← GET /services (health status)
|
||||
│ │ │ ├── categories_proxy.py ← Transparent proxy → doc-service /categories/*
|
||||
│ │ │ └── documents_proxy.py ← Transparent proxy → doc-service /documents/*
|
||||
@@ -227,6 +227,7 @@ Browser (:5173 dev / :80 prod)
|
||||
| `is_active` | Boolean | default=True | soft-delete flag |
|
||||
| `is_superuser` | Boolean | default=False | admin role; never exposed as-is (serialised as `is_admin`) |
|
||||
| `dashboard_app_ids` | JSON | NOT NULL, default=[] | list of pinned service IDs |
|
||||
| `color_mode` | String | nullable, default=NULL | user's preferred mode: "light" / "dark" / "system" / NULL (use admin default) |
|
||||
|
||||
Relationship: `profile` (one-to-one, cascade all+delete-orphan)
|
||||
|
||||
@@ -309,6 +310,7 @@ Unique constraint: `(group_id, user_id)`
|
||||
| `676084df61d1` | `add_profiles_table` |
|
||||
| `a3f9c2d14e87` | `add_groups_and_group_memberships` |
|
||||
| `c7e8f9a0b1d2` | `add_dashboard_app_ids_to_users` |
|
||||
| `dd6ad2f2c211` | `add_color_mode_to_users` |
|
||||
|
||||
**Doc-service**:
|
||||
|
||||
@@ -335,6 +337,7 @@ Unique constraint: `(group_id, user_id)`
|
||||
| GET | `/api/users/me` | user | Current user info → `UserOut` |
|
||||
| GET | `/api/users/me/preferences` | user | Dashboard pinned app IDs → `{app_ids}` |
|
||||
| PATCH | `/api/users/me/preferences` | user | Save pinned app IDs (max 50, slug-safe) |
|
||||
| PATCH | `/api/users/me/color-mode` | user | Save colour mode preference ("light"/"dark"/"system") |
|
||||
|
||||
### Profile (`/api/profile`) — authenticated
|
||||
|
||||
@@ -375,6 +378,12 @@ Unique constraint: `(group_id, user_id)`
|
||||
| PATCH | `/api/settings/documents/limits` | Update max PDF size |
|
||||
| GET | `/api/settings/system-prompts` | All editable system prompts |
|
||||
| PATCH | `/api/settings/system-prompts/{service_id}` | Update system prompt |
|
||||
| GET | `/api/settings/appearance` | Active theme + default mode (auth) |
|
||||
| PATCH | `/api/settings/appearance` | Update active theme + default mode (admin) |
|
||||
| GET | `/api/settings/themes` | List all themes — built-in + custom (auth) |
|
||||
| POST | `/api/settings/themes` | Create custom theme (admin) |
|
||||
| PATCH | `/api/settings/themes/{id}` | Update custom theme label/colours (admin) |
|
||||
| DELETE | `/api/settings/themes/{id}` | Delete custom theme (admin, 204) |
|
||||
|
||||
### Services (`/api/services`) — authenticated
|
||||
|
||||
@@ -436,6 +445,7 @@ Unique constraint: `(group_id, user_id)`
|
||||
| `/admin` | `AdminPage` (→ `/admin/users`) | AdminRoute |
|
||||
| `/admin/users` | `AdminUsersPage` | AdminRoute |
|
||||
| `/admin/groups` | `AdminGroupsPage` | AdminRoute |
|
||||
| `/admin/appearance` | `AdminAppearancePage` | AdminRoute |
|
||||
| `*` | redirect to `/` | — |
|
||||
|
||||
`PrivateRoute` — checks `token` from `useAuth`, redirects to `/login` if absent.
|
||||
@@ -669,6 +679,7 @@ Use `validation_alias` when the ORM field name differs from the JSON key (e.g.,
|
||||
| Token localStorage key | `"token"` | `useAuth.ts` |
|
||||
| Health check interval | 30 s | `service_health.py` |
|
||||
| Service poll (frontend) | 30 s | `AppsPage.tsx`, `DashboardPage.tsx` |
|
||||
| User `color_mode` default | NULL (falls back to admin default_mode, then system) | `models/user.py` |
|
||||
| Max dashboard pinned apps | 50 | `schemas/user.py` |
|
||||
| App ID max length | 64 chars | `schemas/user.py` |
|
||||
| App ID allowed chars | `[a-zA-Z0-9_\-]` | `schemas/user.py` |
|
||||
|
||||
Reference in New Issue
Block a user