From 87d460e243d9795f052ba11f41da7be3dd976d0f Mon Sep 17 00:00:00 2001 From: OoO Date: Mon, 4 May 2026 20:04:13 +0800 Subject: [PATCH] =?UTF-8?q?feat(p50):=20chart.js=20=E6=8A=98=E7=B7=9A?= =?UTF-8?q?=E5=9C=96=E8=A6=96=E8=A6=BA=E5=8C=96=20+=20Playbook=20=E4=B8=80?= =?UTF-8?q?=E9=8D=B5=E5=95=9F=E7=94=A8/=E5=81=9C=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 統帥要求「視覺方格 UI/UX」:raw 表格不夠,加 chart.js 雙圖 + L2 管理。 N-1: ai_calls hourly trend chart.js(雙軸混合) - 取代原 progress bar 表格 - 折線:呼叫數(藍)+ 錯誤次數(紅)→ 共用左軸 - 柱狀:成本 USD(黃)→ 右軸 - interaction mode index:滑鼠 hover 同時顯示三個指標 - chart.js 4.4.1 CDN 加在 {% block extra_js %} N-2: budget 30d cost trend stacked bar chart - 取代原 30d cost trend 表格(max-height 滾動 → 一目瞭然圖) - 8 個 provider 各自分色 本地 Ollama(綠系)vs 付費(橘/紫/青系) - stacked bar:每日總成本一柱,依 provider 堆疊 - tooltip 顯示每個 provider $X.XXXX N-3: Playbook 一鍵啟用/停用(L2 補強第 7 個) - 新 POST /observability/playbooks/toggle/ 翻轉 is_active + UPDATE updated_at - host_health.html playbook 排行表加「切換」欄 - 動態按鈕:啟用顯示「停用」、停用顯示「啟用」 - 對應觀測台直接管理 AutoHeal 庫,不需 SSH 改 DB L2 一鍵自動化從 6 個 → 7 個入口: - AutoHeal / AiderHeal / Code Review / Force Throttle(既有) - Telegram Heal / Throttle(既有) - Playbook Toggle(Phase 50 新增) Phase 38→50 累計 15 commits。 觀測台從 raw stats → AI 自動化專業舞台 → 視覺方格 UI 終局。 Co-Authored-By: Claude Opus 4.7 (1M context) --- routes/admin_observability_routes.py | 51 +++++++++++++++++--- templates/admin/ai_calls_dashboard.html | 63 +++++++++++++------------ templates/admin/budget.html | 58 ++++++++++++++++------- templates/admin/host_health.html | 24 +++++++++- 4 files changed, 141 insertions(+), 55 deletions(-) diff --git a/routes/admin_observability_routes.py b/routes/admin_observability_routes.py index aecd803..9f5979a 100644 --- a/routes/admin_observability_routes.py +++ b/routes/admin_observability_routes.py @@ -1511,6 +1511,42 @@ def ppt_audit_trigger_aider_heal(): return jsonify({'ok': False, 'error': f'{type(e).__name__}: {str(e)[:200]}'}), 500 +@admin_observability_bp.route('/playbooks/toggle/', methods=['POST']) +@login_required +def playbook_toggle(playbook_id: int): + """Phase 50 N-3:一鍵啟用/停用 playbook(is_active 翻轉)。 + + 用途:在 host_health 觀測台直接管理 AutoHeal playbook, + 不需 SSH 188 改 DB。 + """ + try: + session = get_session() + try: + row = session.execute( + sa_text("SELECT id, name, is_active FROM playbooks WHERE id = :id"), + {'id': playbook_id}, + ).fetchone() + if not row: + return jsonify({'ok': False, 'error': f'playbook #{playbook_id} 不存在'}), 404 + new_active = not bool(row[2]) + session.execute( + sa_text("UPDATE playbooks SET is_active = :a, updated_at = NOW() WHERE id = :id"), + {'a': new_active, 'id': playbook_id}, + ) + session.commit() + return jsonify({ + 'ok': True, + 'playbook_id': playbook_id, + 'name': row[1], + 'is_active': new_active, + 'message': f'Playbook 「{row[1]}」已{"啟用" if new_active else "停用"}', + }) + finally: + session.close() + except Exception as e: + return jsonify({'ok': False, 'error': f'{type(e).__name__}: {str(e)[:200]}'}), 500 + + @admin_observability_bp.route('/host_health/trigger_autoheal', methods=['POST']) @login_required def host_health_trigger_autoheal(): @@ -2050,7 +2086,7 @@ def host_health_dashboard(): # playbooks 庫排行(success_count + fail_count + 是否 active) pb_rows = s3.execute( sa_text(""" - SELECT name, error_type, action_type, severity_min, + SELECT id, name, error_type, action_type, severity_min, success_count, fail_count, is_active, cooldown_min FROM playbooks ORDER BY (success_count + fail_count) DESC, success_count DESC @@ -2059,13 +2095,14 @@ def host_health_dashboard(): ).fetchall() playbook_ranking = [ { - 'name': r[0], 'error_type': r[1], 'action_type': r[2], - 'severity': r[3], 'success': int(r[4] or 0), - 'fail': int(r[5] or 0), 'is_active': bool(r[6]), - 'cooldown_min': int(r[7] or 0), + 'id': int(r[0]), + 'name': r[1], 'error_type': r[2], 'action_type': r[3], + 'severity': r[4], 'success': int(r[5] or 0), + 'fail': int(r[6] or 0), 'is_active': bool(r[7]), + 'cooldown_min': int(r[8] or 0), 'success_rate': ( - float(r[4] or 0) / float((r[4] or 0) + (r[5] or 0)) * 100 - ) if ((r[4] or 0) + (r[5] or 0)) > 0 else 0, + float(r[5] or 0) / float((r[5] or 0) + (r[6] or 0)) * 100 + ) if ((r[5] or 0) + (r[6] or 0)) > 0 else 0, } for r in pb_rows ] diff --git a/templates/admin/ai_calls_dashboard.html b/templates/admin/ai_calls_dashboard.html index eac1e1e..c2f2136 100644 --- a/templates/admin/ai_calls_dashboard.html +++ b/templates/admin/ai_calls_dashboard.html @@ -124,40 +124,14 @@ {% endif %} - + {% if hourly_trend %}
過去 24h 每小時呼叫趨勢 - 每小時 bucket:呼叫數 · 成本 · 錯誤 + 折線:呼叫數 + 錯誤;柱狀:成本 USD
-
- - - - - - - - {% set max_calls = (hourly_trend | map(attribute='calls') | max) or 1 %} - {% for h in hourly_trend %} - - - - - - - - {% endfor %} - -
時段呼叫數成本 USD錯誤流量分布
{{ h.hour }}{{ "{:,}".format(h.calls) }}${{ "%.3f"|format(h.cost) }} - {% if h.errors > 0 %}{{ h.errors }} - {% else %}0{% endif %} - -
-
-
-
+
+
{% endif %} @@ -292,7 +266,36 @@

+