From 1ca2241a97b1b9330dc3f81ba6c4e6e569284216 Mon Sep 17 00:00:00 2001 From: admin Date: Thu, 15 Oct 2026 23:52:17 +0800 Subject: [PATCH] =?UTF-8?q?feat(admin):=20=E5=A2=9E=E5=8A=A0=E4=B8=AD?= =?UTF-8?q?=E6=9E=A2=E8=AE=BE=E7=BD=AE(=E8=87=AA=E5=AE=9A=E4=B9=89=20ID=20?= =?UTF-8?q?=E4=B8=8E=E5=90=8D=E7=A7=B0,=E5=8F=B3=E4=B8=8A=E8=A7=92?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E5=85=A5=E5=8F=A3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin/src/App.vue | 25 ++++++++++-- admin/src/api/admin.js | 2 + admin/src/components/HubSettings.vue | 57 ++++++++++++++++++++++++++++ sdk/app/db.py | 5 +++ sdk/app/routers/admin.py | 16 ++++++++ sdk/app/services/settings.py | 40 +++++++++++++++++++ 6 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 admin/src/components/HubSettings.vue create mode 100644 sdk/app/services/settings.py diff --git a/admin/src/App.vue b/admin/src/App.vue index 8d703ce..27e7dbf 100644 --- a/admin/src/App.vue +++ b/admin/src/App.vue @@ -4,16 +4,28 @@ import { getToken, setToken, api } from './api/admin.js'; import Login from './views/Login.vue'; import Users from './views/Users.vue'; import Logs from './views/Logs.vue'; +import HubSettings from './components/HubSettings.vue'; const authed = ref(false); const page = ref('overview'); const user = ref(null); const stats = ref({ users: 0, total: 0, denied: 0, p95: null }); +const hub = ref({ hub_id: '', hub_name: '蜂群中枢' }); +const settingsOpen = ref(false); const PAGES = { overview: '概览', users: '用户管理', logs: '日志查看' }; function onUnauthorized() { setToken(''); authed.value = false; } +async function loadHub() { + try { + const r = await api.settings(); + hub.value = r.settings; + } catch (e) { + if (e.status === 401) onUnauthorized(); + } +} + async function loadStats() { try { const [u, l] = await Promise.all([api.users(), api.logs({ page: 1, page_size: 1 })]); @@ -32,6 +44,7 @@ async function init() { const r = await api.me(); user.value = r.user; authed.value = true; + loadHub(); loadStats(); } catch (e) { setToken(''); @@ -39,8 +52,9 @@ async function init() { } } -function onLogin(u) { user.value = u; authed.value = true; page.value = 'overview'; loadStats(); } +function onLogin(u) { user.value = u; authed.value = true; page.value = 'overview'; loadHub(); loadStats(); } async function onLogout() { try { await api.logout(); } catch {} setToken(''); authed.value = false; } +function onSettingsSaved(s) { hub.value = s; settingsOpen.value = false; } onMounted(init); @@ -51,7 +65,7 @@ onMounted(init);
-

{{ PAGES[page] }}

中枢
+
+

{{ PAGES[page] }}

+ {{ hub.hub_name }} + +
@@ -81,6 +99,7 @@ onMounted(init);
+ diff --git a/admin/src/api/admin.js b/admin/src/api/admin.js index 1bbd40c..16bfd28 100644 --- a/admin/src/api/admin.js +++ b/admin/src/api/admin.js @@ -43,4 +43,6 @@ export const api = { const qs = q.toString(); return request('/logs' + (qs ? '?' + qs : '')); }, + settings: () => request('/settings'), + updateSettings: (body) => request('/settings', { method: 'PUT', body }), }; diff --git a/admin/src/components/HubSettings.vue b/admin/src/components/HubSettings.vue new file mode 100644 index 0000000..69df7e9 --- /dev/null +++ b/admin/src/components/HubSettings.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/sdk/app/db.py b/sdk/app/db.py index 93327c1..4a894c6 100644 --- a/sdk/app/db.py +++ b/sdk/app/db.py @@ -146,6 +146,11 @@ CREATE TABLE IF NOT EXISTS auth_tokens ( ); CREATE INDEX IF NOT EXISTS idx_auth_tokens_user ON auth_tokens(user_id); + +CREATE TABLE IF NOT EXISTS settings ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL +); """ diff --git a/sdk/app/routers/admin.py b/sdk/app/routers/admin.py index 571562a..58ea218 100644 --- a/sdk/app/routers/admin.py +++ b/sdk/app/routers/admin.py @@ -21,6 +21,7 @@ from ..services.auth import ( delete_user, ) from ..services.logview import query_logs +from ..services.settings import get_hub_settings, set_hub_settings from ..upstream import envelope router = APIRouter(prefix="/api/admin", tags=["admin"]) @@ -44,6 +45,11 @@ class UserPatch(BaseModel): password: str | None = None +class HubSettingsBody(BaseModel): + hub_id: str | None = None + hub_name: str | None = None + + @router.post("/login") async def login(body: LoginBody, db: Database = Depends(get_db)): user = await verify_login(db, body.username, body.password) @@ -79,6 +85,16 @@ async def units(_user: dict = Depends(require_admin)): return {"ok": True, "units": list(AUTHORIZATION_UNITS)} +@router.get("/settings") +async def get_settings_api(db: Database = Depends(get_db), _user: dict = Depends(require_admin)): + return {"ok": True, "settings": await get_hub_settings(db)} + + +@router.put("/settings") +async def put_settings_api(body: HubSettingsBody, db: Database = Depends(get_db), _user: dict = Depends(require_admin)): + return {"ok": True, "settings": await set_hub_settings(db, body.hub_id, body.hub_name)} + + @router.get("/users") async def users(db: Database = Depends(get_db), _user: dict = Depends(require_admin)): return {"ok": True, "users": await list_users(db)} diff --git a/sdk/app/services/settings.py b/sdk/app/services/settings.py new file mode 100644 index 0000000..ba5952e --- /dev/null +++ b/sdk/app/services/settings.py @@ -0,0 +1,40 @@ +"""services/settings.py —— 中枢级键值设置(hub_id / hub_name)。""" +from __future__ import annotations + +import uuid + +from ..db import Database + +_HUB_ID_KEY = "hub_id" +_HUB_NAME_KEY = "hub_name" +DEFAULT_HUB_NAME = "蜂群中枢" + + +async def get_setting(db: Database, key: str, default: str | None = None) -> str | None: + row = await db.fetchone("SELECT value FROM settings WHERE key = ?", (key,)) + return row["value"] if row else default + + +async def set_setting(db: Database, key: str, value: str) -> None: + row = await db.fetchone("SELECT 1 FROM settings WHERE key = ?", (key,)) + if row is not None: + await db.execute("UPDATE settings SET value = ? WHERE key = ?", (value, key)) + else: + await db.execute("INSERT INTO settings (key, value) VALUES (?, ?)", (key, value)) + + +async def get_hub_settings(db: Database) -> dict: + hub_id = await get_setting(db, _HUB_ID_KEY) + if not hub_id: + hub_id = str(uuid.uuid4()) + await set_setting(db, _HUB_ID_KEY, hub_id) + hub_name = (await get_setting(db, _HUB_NAME_KEY, DEFAULT_HUB_NAME)) or DEFAULT_HUB_NAME + return {"hub_id": hub_id, "hub_name": hub_name} + + +async def set_hub_settings(db: Database, hub_id: str | None, hub_name: str | None) -> dict: + if hub_id and hub_id.strip(): + await set_setting(db, _HUB_ID_KEY, hub_id.strip()) + if hub_name and hub_name.strip(): + await set_setting(db, _HUB_NAME_KEY, hub_name.strip()) + return await get_hub_settings(db)