diff --git a/backend/app/routers/admin.py b/backend/app/routers/admin.py index 794c682..b559daa 100644 --- a/backend/app/routers/admin.py +++ b/backend/app/routers/admin.py @@ -122,3 +122,35 @@ async def manual_purge( db = request.app.state.db result = await run_purge(db, config) return result + + +@router.put("/settings") +async def update_settings( + request: Request, + _admin: str = Depends(require_admin), +) -> dict: + """Update runtime settings (in-memory only — resets on restart). + + Accepts a JSON body with optional fields: + - welcome_message: str + """ + body = await request.json() + + if not hasattr(request.app.state, "settings_overrides"): + request.app.state.settings_overrides = {} + + updated = [] + if "welcome_message" in body: + msg = body["welcome_message"] + if not isinstance(msg, str): + from fastapi.responses import JSONResponse + + return JSONResponse( + status_code=422, + content={"detail": "welcome_message must be a string"}, + ) + request.app.state.settings_overrides["welcome_message"] = msg + updated.append("welcome_message") + logger.info("Admin updated welcome_message to: %s", msg[:80]) + + return {"updated": updated, "status": "ok"} diff --git a/backend/app/routers/system.py b/backend/app/routers/system.py index 8aa6004..d2db471 100644 --- a/backend/app/routers/system.py +++ b/backend/app/routers/system.py @@ -20,10 +20,16 @@ async def public_config(request: Request) -> dict: is fragile when new sensitive fields are added later. """ config = request.app.state.config + + # Runtime overrides (set via admin settings endpoint) take precedence + overrides = getattr(request.app.state, "settings_overrides", {}) + return { "session_mode": config.session.mode, "default_theme": config.ui.default_theme, - "welcome_message": config.ui.welcome_message, + "welcome_message": overrides.get( + "welcome_message", config.ui.welcome_message + ), "purge_enabled": config.purge.enabled, "max_concurrent_downloads": config.downloads.max_concurrent, } diff --git a/frontend/src/components/AdminPanel.vue b/frontend/src/components/AdminPanel.vue index b60cae9..5e9c9c1 100644 --- a/frontend/src/components/AdminPanel.vue +++ b/frontend/src/components/AdminPanel.vue @@ -1,10 +1,15 @@ @@ -32,7 +55,7 @@ async function switchTab(tab: typeof activeTab.value) {
+ + +
+
+ +

Displayed above the URL input on the main page. Leave empty to hide.

+ +
+
+ + ✓ Saved +
+

+ Changes are applied immediately but reset on server restart. +

+
@@ -239,4 +290,62 @@ h3 { color: var(--color-text-muted); text-align: center; } + +.settings-field { + display: flex; + flex-direction: column; + gap: var(--space-xs); +} + +.settings-field label { + font-weight: 600; + color: var(--color-text); +} + +.field-hint { + font-size: var(--font-size-sm); + color: var(--color-text-muted); + margin: 0; +} + +.settings-textarea { + width: 100%; + padding: var(--space-sm); + background: var(--color-bg); + color: var(--color-text); + border: 1px solid var(--color-border); + border-radius: var(--radius-sm); + font-family: var(--font-ui); + font-size: var(--font-size-base); + resize: vertical; +} + +.settings-textarea:focus { + outline: none; + border-color: var(--color-accent); +} + +.settings-actions { + display: flex; + align-items: center; + gap: var(--space-md); + margin-top: var(--space-md); +} + +.btn-save { + background: var(--color-accent); + color: var(--color-bg); + font-weight: 600; + padding: var(--space-sm) var(--space-lg); +} + +.btn-save:hover:not(:disabled) { + background: var(--color-accent-hover); +} + +.save-confirm { + color: var(--color-success); + font-weight: 500; + font-size: var(--font-size-sm); +} diff --git a/frontend/src/components/DownloadItem.vue b/frontend/src/components/DownloadItem.vue deleted file mode 100644 index 3ff9c0b..0000000 --- a/frontend/src/components/DownloadItem.vue +++ /dev/null @@ -1,179 +0,0 @@ - - - - - diff --git a/frontend/src/components/ThemePicker.vue b/frontend/src/components/ThemePicker.vue deleted file mode 100644 index 64778af..0000000 --- a/frontend/src/components/ThemePicker.vue +++ /dev/null @@ -1,105 +0,0 @@ - - - - - diff --git a/frontend/src/stores/admin.ts b/frontend/src/stores/admin.ts index 7ddbb30..d62cc75 100644 --- a/frontend/src/stores/admin.ts +++ b/frontend/src/stores/admin.ts @@ -110,6 +110,23 @@ export const useAdminStore = defineStore('admin', () => { } } + async function updateSettings(data: Record): Promise { + isLoading.value = true + try { + const res = await fetch('/api/admin/settings', { + method: 'PUT', + headers: { + ..._authHeaders(), + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }) + return res.ok + } finally { + isLoading.value = false + } + } + return { username, isAuthenticated, @@ -123,5 +140,6 @@ export const useAdminStore = defineStore('admin', () => { loadSessions, loadStorage, triggerPurge, + updateSettings, } })