diff --git a/routes/admin_observability_routes.py b/routes/admin_observability_routes.py index 97a2bbc..c1f3b4c 100644 --- a/routes/admin_observability_routes.py +++ b/routes/admin_observability_routes.py @@ -5,10 +5,10 @@ routes/admin_observability_routes.py Operation Ollama-First v5.0 / Phase 27 — Admin Observability Dashboard 提供 admin 介面看戰役累積的觀測資料: - /admin/ai_calls — ai_calls 即時查詢(含篩選 / 圖表) - /admin/promotion_review — Phase 28 PromotionGate 待審核列表 - /admin/quality_trend — Phase 25 caller 反饋趨勢 - /admin/host_health — 三主機 Ollama + MCP 健康度 + /observability/ai_calls — ai_calls 即時查詢(含篩選 / 圖表) + /observability/promotion_review — Phase 28 PromotionGate 待審核列表 + /observability/quality_trend — Phase 25 caller 反饋趨勢 + /observability/host_health — 三主機 Ollama + MCP 健康度 設計原則: - 純讀(除了 promotion approve/reject 是 mutation) @@ -27,12 +27,12 @@ from database.manager import get_session admin_observability_bp = Blueprint( 'admin_observability', __name__, - url_prefix='/admin', + url_prefix='/observability', ) # ───────────────────────────────────────────────────────────────────────────── -# /admin/ai_calls — Phase 27 主入口 +# /observability/ai_calls — Phase 27 主入口 # ───────────────────────────────────────────────────────────────────────────── @admin_observability_bp.route('/ai_calls') @@ -154,7 +154,7 @@ def ai_calls_dashboard(): # ───────────────────────────────────────────────────────────────────────────── -# /admin/promotion_review — Phase 28 PromotionGate 待審核列表 +# /observability/promotion_review — Phase 28 PromotionGate 待審核列表 # ───────────────────────────────────────────────────────────────────────────── @admin_observability_bp.route('/promotion_review') @@ -226,7 +226,7 @@ def promotion_review_reject(episode_id: int): # ───────────────────────────────────────────────────────────────────────────── -# /admin/quality_trend — Phase 25 caller 反饋趨勢視覺化 +# /observability/quality_trend — Phase 25 caller 反饋趨勢視覺化 # ───────────────────────────────────────────────────────────────────────────── @admin_observability_bp.route('/quality_trend') @@ -262,7 +262,7 @@ def quality_trend_dashboard(): # ───────────────────────────────────────────────────────────────────────────── -# /admin/budget — Phase 29 預算管理 + 手動 throttle +# /observability/budget — Phase 29 預算管理 + 手動 throttle # ───────────────────────────────────────────────────────────────────────────── @admin_observability_bp.route('/budget') @@ -351,7 +351,7 @@ def budget_update(budget_id: int): # ───────────────────────────────────────────────────────────────────────────── -# /admin/ppt_audit_history — Phase 29 PPT 視覺審核歷史 +# /observability/ppt_audit_history — Phase 29 PPT 視覺審核歷史 # ───────────────────────────────────────────────────────────────────────────── @admin_observability_bp.route('/ppt_audit_history') @@ -402,7 +402,7 @@ def ppt_audit_history(): # ───────────────────────────────────────────────────────────────────────────── -# /admin/host_health — 三主機 + MCP 健康度 +# /observability/host_health — 三主機 + MCP 健康度 # ───────────────────────────────────────────────────────────────────────────── @admin_observability_bp.route('/host_health') diff --git a/templates/admin/ai_calls_dashboard.html b/templates/admin/ai_calls_dashboard.html index 5fc609c..434ab02 100644 --- a/templates/admin/ai_calls_dashboard.html +++ b/templates/admin/ai_calls_dashboard.html @@ -114,11 +114,11 @@
🤖 Operation Ollama-First v5.0 / Phase 29 — Admin Observability - | Promotion Review - | Quality Trend - | Host Health - | Budget - | PPT Audit + | Promotion Review + | Quality Trend + | Host Health + | Budget + | PPT Audit
{% endblock %} diff --git a/templates/admin/budget.html b/templates/admin/budget.html index 26590fa..8ab14dd 100644 --- a/templates/admin/budget.html +++ b/templates/admin/budget.html @@ -72,9 +72,9 @@🤖 Operation Ollama-First v5.0 / Phase 29 — Budget Manager - | AI Calls - | Host Health - | Promotion Review + | AI Calls + | Host Health + | Promotion Review
@@ -85,7 +85,7 @@ async function saveBudget(id) { const btn = document.querySelector(`.save-budget-btn[data-budget-id="${id}"]`); btn.disabled = true; btn.innerText = '⏳'; try { - const r = await fetch(`/admin/budget/update/${id}`, { + const r = await fetch(`/observability/budget/update/${id}`, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ diff --git a/templates/admin/host_health.html b/templates/admin/host_health.html index d5ce9bd..aafa1a6 100644 --- a/templates/admin/host_health.html +++ b/templates/admin/host_health.html @@ -114,11 +114,11 @@🤖 Operation Ollama-First v5.0 / Phase 29 — Host Health Dashboard - | AI Calls - | Promotion Review - | Quality Trend - | Budget - | PPT Audit + | AI Calls + | Promotion Review + | Quality Trend + | Budget + | PPT Audit
{% endblock %} diff --git a/templates/admin/ppt_audit_history.html b/templates/admin/ppt_audit_history.html index af0ffaa..502e8cd 100644 --- a/templates/admin/ppt_audit_history.html +++ b/templates/admin/ppt_audit_history.html @@ -50,9 +50,9 @@🤖 Operation Ollama-First v5.0 / Phase 29 — PPT Audit History - | AI Calls - | Host Health - | Budget + | AI Calls + | Host Health + | Budget
{% endblock %} diff --git a/templates/admin/promotion_review.html b/templates/admin/promotion_review.html index d4e04bd..ec5f882 100644 --- a/templates/admin/promotion_review.html +++ b/templates/admin/promotion_review.html @@ -54,11 +54,11 @@🤖 Operation Ollama-First v5.0 / Phase 29 — PromotionGate Web 審核頁 - | AI Calls - | Quality Trend - | Host Health - | Budget - | PPT Audit + | AI Calls + | Quality Trend + | Host Health + | Budget + | PPT Audit
@@ -66,7 +66,7 @@ async function approveEpisode(id, btn) { btn.disabled = true; btn.innerText = '⏳ 處理中...'; try { - const r = await fetch(`/admin/promotion_review/approve/${id}`, {method: 'POST'}); + const r = await fetch(`/observability/promotion_review/approve/${id}`, {method: 'POST'}); const d = await r.json(); if (d.ok) { const card = document.querySelector(`.episode-card[data-episode-id="${id}"]`); @@ -87,7 +87,7 @@ async function rejectEpisode(id, btn) { if (!confirm(`拒絕 Episode #${id}?此筆將永不晉升(保留在 learning_episodes 不刪除)`)) return; btn.disabled = true; btn.innerText = '⏳ 處理中...'; try { - const r = await fetch(`/admin/promotion_review/reject/${id}`, {method: 'POST'}); + const r = await fetch(`/observability/promotion_review/reject/${id}`, {method: 'POST'}); const d = await r.json(); if (d.ok) { const card = document.querySelector(`.episode-card[data-episode-id="${id}"]`); diff --git a/templates/admin/quality_trend.html b/templates/admin/quality_trend.html index a6af835..9a689af 100644 --- a/templates/admin/quality_trend.html +++ b/templates/admin/quality_trend.html @@ -94,11 +94,11 @@🤖 Operation Ollama-First v5.0 / Phase 29 — Caller Quality Trend - | AI Calls - | Promotion Review - | Host Health - | Budget - | PPT Audit + | AI Calls + | Promotion Review + | Host Health + | Budget + | PPT Audit
{% endblock %} diff --git a/tests/test_admin_observability_routes.py b/tests/test_admin_observability_routes.py index 0c4e012..25e29e3 100644 --- a/tests/test_admin_observability_routes.py +++ b/tests/test_admin_observability_routes.py @@ -60,13 +60,13 @@ def _fake_session(rows_per_query=None): # ────────────────────────────────────────────────────────────────────────── -# /admin/ai_calls +# /observability/ai_calls # ────────────────────────────────────────────────────────────────────────── def test_ai_calls_dashboard_200_empty(client, monkeypatch): from routes import admin_observability_routes as mod monkeypatch.setattr(mod, 'get_session', lambda: _fake_session([])) - r = client.get('/admin/ai_calls') + r = client.get('/observability/ai_calls') assert r.status_code == 200 assert b'AI Calls' in r.data or '\xe5\x88\x86\xe6\x9e\x90'.encode() in r.data or True # 中文標題可選 @@ -77,40 +77,40 @@ def test_ai_calls_dashboard_db_error_falls_back(client, monkeypatch): bad.execute.side_effect = RuntimeError('DB down') bad.close = MagicMock() monkeypatch.setattr(mod, 'get_session', lambda: bad) - r = client.get('/admin/ai_calls') + r = client.get('/observability/ai_calls') assert r.status_code == 200 # 失敗安全:仍 render,不 500 # ────────────────────────────────────────────────────────────────────────── -# /admin/promotion_review +# /observability/promotion_review # ────────────────────────────────────────────────────────────────────────── def test_promotion_review_200(client, monkeypatch): from routes import admin_observability_routes as mod monkeypatch.setattr(mod, 'get_session', lambda: _fake_session([])) - r = client.get('/admin/promotion_review') + r = client.get('/observability/promotion_review') assert r.status_code == 200 # ────────────────────────────────────────────────────────────────────────── -# /admin/quality_trend +# /observability/quality_trend # ────────────────────────────────────────────────────────────────────────── def test_quality_trend_200(client, monkeypatch): from routes import admin_observability_routes as mod monkeypatch.setattr(mod, 'get_session', lambda: _fake_session([])) - r = client.get('/admin/quality_trend') + r = client.get('/observability/quality_trend') assert r.status_code == 200 # ────────────────────────────────────────────────────────────────────────── -# /admin/budget +# /observability/budget # ────────────────────────────────────────────────────────────────────────── def test_budget_dashboard_200_empty(client, monkeypatch): from routes import admin_observability_routes as mod monkeypatch.setattr(mod, 'get_session', lambda: _fake_session([])) - r = client.get('/admin/budget') + r = client.get('/observability/budget') assert r.status_code == 200 @@ -118,7 +118,7 @@ def test_budget_update_rejects_invalid_budget(client, monkeypatch): from routes import admin_observability_routes as mod monkeypatch.setattr(mod, 'get_session', lambda: _fake_session([])) r = client.post( - '/admin/budget/update/1', + '/observability/budget/update/1', json={'budget_usd': -5, 'alert_pct': 80}, ) assert r.status_code == 400 @@ -129,7 +129,7 @@ def test_budget_update_rejects_invalid_alert(client, monkeypatch): from routes import admin_observability_routes as mod monkeypatch.setattr(mod, 'get_session', lambda: _fake_session([])) r = client.post( - '/admin/budget/update/1', + '/observability/budget/update/1', json={'budget_usd': 10, 'alert_pct': 999}, ) assert r.status_code == 400 @@ -139,7 +139,7 @@ def test_budget_update_accepts_valid(client, monkeypatch): from routes import admin_observability_routes as mod monkeypatch.setattr(mod, 'get_session', lambda: _fake_session([])) r = client.post( - '/admin/budget/update/1', + '/observability/budget/update/1', json={'budget_usd': 25.50, 'alert_pct': 80}, ) assert r.status_code == 200 @@ -147,17 +147,17 @@ def test_budget_update_accepts_valid(client, monkeypatch): # ────────────────────────────────────────────────────────────────────────── -# /admin/ppt_audit_history +# /observability/ppt_audit_history # ────────────────────────────────────────────────────────────────────────── def test_ppt_audit_history_200(client): """無 DB 依賴,純掃 reports/。""" - r = client.get('/admin/ppt_audit_history') + r = client.get('/observability/ppt_audit_history') assert r.status_code == 200 # ────────────────────────────────────────────────────────────────────────── -# /admin/host_health +# /observability/host_health # ────────────────────────────────────────────────────────────────────────── def test_host_health_200(client, monkeypatch): @@ -172,5 +172,5 @@ def test_host_health_200(client, monkeypatch): raise _r.exceptions.ConnectionError('mocked') monkeypatch.setattr(_r, 'get', fake_get) - r = client.get('/admin/host_health') + r = client.get('/observability/host_health') assert r.status_code == 200