diff --git a/scripts/check_observability_ui.py b/scripts/check_observability_ui.py index 781f306..55fdf30 100644 --- a/scripts/check_observability_ui.py +++ b/scripts/check_observability_ui.py @@ -65,7 +65,7 @@ TEMPLATE_RULES = [ Rule( "raw_error_copy", re.compile( - r"(查詢失敗:|ProgrammingError|UndefinedError|Traceback|Internal Server Error|relation\s+"|relation\s+\")" + r"(查詢失敗:|ProgrammingError|UndefinedError|Traceback|Internal Server Error|relation\s+"|relation\s+\"|alert\(['\"]Error[::]|載入錯誤:\$\{e\}|unknown)" ), "不得把 SQL/Jinja exception 或 raw failure 文案直接顯示給使用者。", ), diff --git a/templates/admin/ai_calls_dashboard.html b/templates/admin/ai_calls_dashboard.html index c0c63fb..10f6b88 100644 --- a/templates/admin/ai_calls_dashboard.html +++ b/templates/admin/ai_calls_dashboard.html @@ -143,6 +143,6 @@ const el = document.getElementById('hourlyTrendChart'); if (!el || !labels.length) return; new Chart(el, { data: { labels, datasets: [ { type: 'line', label: '呼叫數', data: calls, borderColor: '#c96442', backgroundColor: 'rgba(201,100,66,.12)', tension: .35, fill: true, yAxisID: 'y' }, { type: 'line', label: '錯誤', data: errors, borderColor: '#b94b45', backgroundColor: 'rgba(185,75,69,.1)', tension: .35, yAxisID: 'y' }, { type: 'bar', label: '成本 USD', data: costs, backgroundColor: 'rgba(184,121,47,.38)', borderColor: '#b8792f', yAxisID: 'y1' } ] }, options: { responsive: true, maintainAspectRatio: false, interaction: { mode: 'index', intersect: false }, scales: { y: { beginAtZero: true, title: { display: true, text: '次數' } }, y1: { position: 'right', beginAtZero: true, grid: { drawOnChartArea: false }, title: { display: true, text: 'USD' } } } } }); })(); -async function triggerCodeReview() { if (!confirm('觸發 Code Review Pipeline?\n\n會對最新 commit 跑 5 step 審查,背景執行。')) return; try { const r = await fetch('/observability/ai_calls/trigger_code_review', {method: 'POST'}); const d = await r.json(); if (d.ok) { alert(`✅ ${d.message}\n\nPipeline ID: ${d.pipeline_id}\nCommit: ${d.commit_sha}\n變更檔案: ${d.changed_files_count} 個`); } else { alert('❌ ' + (d.error || '觸發失敗')); } } catch (e) { alert('Error: ' + e); } } +async function triggerCodeReview() { if (!confirm('觸發 Code Review Pipeline?\n\n會對最新 commit 跑 5 step 審查,背景執行。')) return; try { const r = await fetch('/observability/ai_calls/trigger_code_review', {method: 'POST'}); const d = await r.json(); if (d.ok) { alert(`✅ ${d.message}\n\nPipeline ID: ${d.pipeline_id}\nCommit: ${d.commit_sha}\n變更檔案: ${d.changed_files_count} 個`); } else { alert('❌ ' + (d.error || '觸發失敗')); } } catch (e) { console.warn('code_review_trigger_failed', e); alert('操作暫時無法完成,請稍後再試或查看系統日誌。'); } } {% endblock %} diff --git a/templates/admin/budget.html b/templates/admin/budget.html index 52e7b5f..893f3c2 100644 --- a/templates/admin/budget.html +++ b/templates/admin/budget.html @@ -89,7 +89,7 @@ {% endblock %} diff --git a/templates/admin/business_intel.html b/templates/admin/business_intel.html index d2667cf..ca499a4 100644 --- a/templates/admin/business_intel.html +++ b/templates/admin/business_intel.html @@ -423,7 +423,7 @@ {% set ns.conf_total = ns.conf_total + ((r.avg_confidence or 0) * (r.count or 0)) %} {% endfor %} {% for v in verdict_stats %} - {% set label = v.verdict or 'unknown' %} + {% set label = v.verdict or '未分類' %} {% set ns.verdict_total = ns.verdict_total + (v.count or 0) %} {% if label == 'effective' or label == 'success' or label == 'positive' %} {% set ns.effective = ns.effective + (v.count or 0) %} @@ -517,7 +517,7 @@
{% for r in rec_by_strategy %}
-
{{ r.strategy or 'unknown' }}
+
{{ r.strategy or '未分類策略' }}
{{ r.count }}建議數
{{ '%.0f'|format((r.avg_confidence or 0) * 100) }}%信心
@@ -629,7 +629,7 @@ VerdictCountAvg Δ {% for v in verdict_stats %} - {{ v.verdict or 'unknown' }}{{ v.count }}{{ '%.1f'|format(v.avg_delta or 0) }}% + {{ v.verdict or '未分類' }}{{ v.count }}{{ '%.1f'|format(v.avg_delta or 0) }}% {% endfor %} @@ -712,7 +712,7 @@ new Chart(canvas, { type: 'doughnut', data: { - labels: verdictRows.map(row => row.verdict || 'unknown'), + labels: verdictRows.map(row => row.verdict || '未分類'), datasets: [{ data: verdictRows.map(row => row.count || 0), backgroundColor: ['#2f8f6b', '#c96442', '#f1b45a', '#6d4b3f', '#d9a06f'], diff --git a/templates/admin/host_health.html b/templates/admin/host_health.html index 1b5eaac..b649c41 100644 --- a/templates/admin/host_health.html +++ b/templates/admin/host_health.html @@ -178,7 +178,7 @@ if (!el || !data.length) return; new Chart(el, { type: 'line', data: { labels: data.map(d => d.date), datasets: [{ data: data.map(d => d.rate), borderColor: '#c96442', backgroundColor: 'rgba(201,100,66,.14)', borderWidth: 2, fill: true, tension: .35, pointRadius: 2 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { x: { display: false }, y: { min: 0, max: 100, ticks: { callback: v => v + '%' } } } } }); })(); -async function togglePlaybook(id, name) { if (!confirm(`切換 Playbook 「${name}」狀態?`)) return; try { const r = await fetch(`/observability/playbooks/toggle/${id}`, {method: 'POST'}); const d = await r.json(); if (d.ok) { alert(`✅ ${d.message}`); window.location.reload(); } else { alert('❌ ' + (d.error || '切換失敗')); } } catch (e) { alert('Error: ' + e); } } -async function triggerAutoHeal(hostLabel) { if (!confirm(`觸發 AutoHeal?\n\n主機:${hostLabel}`)) return; try { const r = await fetch('/observability/host_health/trigger_autoheal', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({host_label: hostLabel}) }); const d = await r.json(); if (d.ok) { alert(`✅ AutoHeal 已派出\n動作:${d.action || '—'}\n訊息:${d.message || ''}`); window.location.reload(); } else { alert('❌ ' + (d.error || d.message || '觸發失敗')); } } catch (e) { alert('Error: ' + e); } } +async function togglePlaybook(id, name) { if (!confirm(`切換 Playbook 「${name}」狀態?`)) return; try { const r = await fetch(`/observability/playbooks/toggle/${id}`, {method: 'POST'}); const d = await r.json(); if (d.ok) { alert(`✅ ${d.message}`); window.location.reload(); } else { alert('❌ ' + (d.error || '切換失敗')); } } catch (e) { console.warn('playbook_toggle_failed', e); alert('操作暫時無法完成,請稍後再試或查看系統日誌。'); } } +async function triggerAutoHeal(hostLabel) { if (!confirm(`觸發 AutoHeal?\n\n主機:${hostLabel}`)) return; try { const r = await fetch('/observability/host_health/trigger_autoheal', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({host_label: hostLabel}) }); const d = await r.json(); if (d.ok) { alert(`✅ AutoHeal 已派出\n動作:${d.action || '—'}\n訊息:${d.message || ''}`); window.location.reload(); } else { alert('❌ ' + (d.error || d.message || '觸發失敗')); } } catch (e) { console.warn('host_autoheal_failed', e); alert('操作暫時無法完成,請稍後再試或查看系統日誌。'); } } {% endblock %} diff --git a/templates/admin/ppt_audit_history.html b/templates/admin/ppt_audit_history.html index af2cc75..7b7316f 100644 --- a/templates/admin/ppt_audit_history.html +++ b/templates/admin/ppt_audit_history.html @@ -29,6 +29,6 @@
{% endblock %} diff --git a/templates/admin/promotion_review.html b/templates/admin/promotion_review.html index 5ea27dc..ad4f460 100644 --- a/templates/admin/promotion_review.html +++ b/templates/admin/promotion_review.html @@ -101,7 +101,7 @@ {% endblock %} diff --git a/templates/admin/rag_queries.html b/templates/admin/rag_queries.html index f1efb9a..18909ff 100644 --- a/templates/admin/rag_queries.html +++ b/templates/admin/rag_queries.html @@ -40,7 +40,7 @@ {% endblock %}