feat(p45): UI/UX 升級 ewoooc_base.html + sidebar AI 觀測 7 項 + 新增總覽頁
All checks were successful
CD Pipeline / deploy (push) Successful in 2m37s
All checks were successful
CD Pipeline / deploy (push) Successful in 2m37s
統帥質疑:「那六頁的視覺方格 UI/UX 搞好了嗎?還有新增頁面嗎?」
回答:沒有,從 Phase 38 開始一直推遲。本 commit 補做。
I-1: 6 頁 base.html → ewoooc_base.html
- host_health / ai_calls_dashboard / budget / promotion_review /
quality_trend / ppt_audit_history 全改
- {% extends "base.html" %} → {% extends "ewoooc_base.html" %}
- {% block content %} → {% block ewooo_content %}
- 自動繼承:sidebar 240px / topbar 64px / fonts (Inter+JetBrains+Noto Sans TC)
/ ewoooc-tokens.css / ewoooc-shell.css / search box / 米色背景
I-2: _ewoooc_shell.html 加「AI 觀測」nav group
- 7 個項目:觀測台總覽 / 主機健康 / AI 呼叫 / 預算控管 /
RAG 晉升審核 / 反饋趨勢 / PPT 視覺審核
- 對應 active_page='obs_*',正確高亮
- 編號 07-13(系統管理改 14)
I-3: 新增頁面 /observability/ + /observability/overview
- routes/admin_observability_routes.py::observability_overview
- 單頁聚合 8 表跨 JOIN 的 KPI:
• 三主機 24h 在線率(host_health_probes,per host card)
• AI 呼叫 24h(ai_calls:total/tokens/cost/error rate/RAG hit/cache hit)
• 當月成本累計
• 預算告警(ratio ≥ alert_pct 自動列表)
• AIOps 7d(incidents + heal_logs:自癒成功率)
• MCP 24h(mcp_calls:tool 呼叫 + cache 率 + cost)
• RAG 學習 30d(learning_episodes:待審 + 晉升率)
• PPT 視覺審核 7d(ppt_audit_results:通過率)
• 6 大子頁入口卡(含一行說明)
- 對應 Phase 44 daily Telegram summary 的 web 版本
- 全部失敗安全(個別 query 失敗只跳過該卡,不擋整頁)
升級對應:
- UI 框架:base.html → ewoooc_base.html ✅(sidebar + topbar + token css 已生效)
- 設計憲法:8 卡片 + 8 表跨 JOIN 全景 + 一頁式總覽
- 入口:sidebar 7 項 + 觀測台首頁
- 資料表覆蓋:4 表(Phase 38)→ 8 表(Phase 45)
注意:完整 design token 重塑(Bootstrap class → --momo-* token / 焦糖橘)
留待後續 phase;本 commit 重點是「框架升級 + 新總覽頁」。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -32,6 +32,221 @@ admin_observability_bp = Blueprint(
|
||||
)
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# /observability/overview — Phase 45 總覽(單頁聚合 6 項 KPI)
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
@admin_observability_bp.route('/')
|
||||
@admin_observability_bp.route('/overview')
|
||||
@login_required
|
||||
def observability_overview():
|
||||
"""Phase 45 — 觀測台總覽:一頁式聚合 6 個 sub-page 的關鍵 KPI。
|
||||
|
||||
對應 Phase 44 daily Telegram summary 的 web 版本,做為 sidebar 入口頁。
|
||||
所有區塊失敗安全:個別 query 失敗只跳過該卡片,不擋整頁渲染。
|
||||
"""
|
||||
from datetime import datetime as _dt
|
||||
today = _dt.now()
|
||||
month_start = _dt(today.year, today.month, 1)
|
||||
summary = {}
|
||||
|
||||
session = get_session()
|
||||
try:
|
||||
# 三主機 24h 在線率
|
||||
try:
|
||||
host_rows = session.execute(
|
||||
sa_text("""
|
||||
SELECT host_label, COUNT(*) AS total,
|
||||
COUNT(*) FILTER (WHERE healthy) AS up,
|
||||
COALESCE(AVG(response_ms) FILTER (WHERE healthy), 0) AS avg_ms
|
||||
FROM host_health_probes
|
||||
WHERE probed_at >= NOW() - INTERVAL '24 hours'
|
||||
GROUP BY host_label ORDER BY host_label
|
||||
"""),
|
||||
).fetchall()
|
||||
summary['hosts'] = [
|
||||
{
|
||||
'label': r[0],
|
||||
'total': int(r[1] or 0),
|
||||
'up': int(r[2] or 0),
|
||||
'avg_ms': int(r[3] or 0),
|
||||
'uptime_pct': (float(r[2] or 0) / float(r[1]) * 100) if r[1] else 0,
|
||||
}
|
||||
for r in host_rows
|
||||
]
|
||||
except Exception:
|
||||
summary['hosts'] = []
|
||||
|
||||
# AI 呼叫 24h
|
||||
try:
|
||||
ai = session.execute(
|
||||
sa_text("""
|
||||
SELECT COUNT(*), COALESCE(SUM(input_tokens + output_tokens), 0),
|
||||
COALESCE(SUM(cost_usd), 0),
|
||||
COUNT(*) FILTER (WHERE status NOT IN ('ok','cache_only')),
|
||||
COUNT(*) FILTER (WHERE rag_hit),
|
||||
COUNT(*) FILTER (WHERE cache_hit)
|
||||
FROM ai_calls
|
||||
WHERE called_at >= NOW() - INTERVAL '24 hours'
|
||||
"""),
|
||||
).fetchone()
|
||||
total = int(ai[0] or 0)
|
||||
summary['ai_calls'] = {
|
||||
'total': total,
|
||||
'tokens': int(ai[1] or 0),
|
||||
'cost_24h': float(ai[2] or 0),
|
||||
'errors': int(ai[3] or 0),
|
||||
'rag_hits': int(ai[4] or 0),
|
||||
'cache_hits': int(ai[5] or 0),
|
||||
'error_rate': (float(ai[3] or 0) / total * 100) if total else 0,
|
||||
'rag_rate': (float(ai[4] or 0) / total * 100) if total else 0,
|
||||
'cache_rate': (float(ai[5] or 0) / total * 100) if total else 0,
|
||||
}
|
||||
except Exception:
|
||||
summary['ai_calls'] = {}
|
||||
|
||||
# 當月成本
|
||||
try:
|
||||
month_cost = session.execute(
|
||||
sa_text("SELECT COALESCE(SUM(cost_usd), 0) FROM ai_calls WHERE called_at >= :ms"),
|
||||
{'ms': month_start},
|
||||
).fetchone()[0]
|
||||
summary['month_cost'] = float(month_cost or 0)
|
||||
except Exception:
|
||||
summary['month_cost'] = 0
|
||||
|
||||
# 預算 over 80%
|
||||
try:
|
||||
budgets = session.execute(
|
||||
sa_text("""
|
||||
SELECT b.period, b.provider, b.budget_usd, b.alert_pct,
|
||||
COALESCE((
|
||||
SELECT SUM(cost_usd) FROM ai_calls
|
||||
WHERE called_at >= :ms
|
||||
AND (b.provider IS NULL OR provider = b.provider)
|
||||
), 0) AS spent
|
||||
FROM ai_call_budgets b
|
||||
"""),
|
||||
{'ms': month_start},
|
||||
).fetchall()
|
||||
over_threshold = []
|
||||
for r in budgets:
|
||||
budget = float(r[2] or 0)
|
||||
spent = float(r[4] or 0)
|
||||
ratio = spent / budget if budget > 0 else 0
|
||||
threshold = float(r[3] or 80) / 100
|
||||
if ratio >= threshold:
|
||||
over_threshold.append({
|
||||
'period': r[0], 'provider': r[1] or '(全部)',
|
||||
'spent': spent, 'budget': budget, 'ratio': ratio,
|
||||
})
|
||||
summary['budget_alerts'] = over_threshold
|
||||
except Exception:
|
||||
summary['budget_alerts'] = []
|
||||
|
||||
# 待審 + 蒸餾池
|
||||
try:
|
||||
ep_pending = session.execute(
|
||||
sa_text("SELECT COUNT(*) FROM learning_episodes WHERE promotion_status = 'awaiting_review' AND reviewed_at IS NULL"),
|
||||
).fetchone()[0]
|
||||
ep_total_30d = session.execute(
|
||||
sa_text("SELECT COUNT(*) FROM learning_episodes WHERE created_at >= NOW() - INTERVAL '30 days'"),
|
||||
).fetchone()[0]
|
||||
ep_approved_30d = session.execute(
|
||||
sa_text("SELECT COUNT(*) FROM learning_episodes WHERE created_at >= NOW() - INTERVAL '30 days' AND promotion_status = 'approved'"),
|
||||
).fetchone()[0]
|
||||
summary['episodes'] = {
|
||||
'pending': int(ep_pending or 0),
|
||||
'total_30d': int(ep_total_30d or 0),
|
||||
'approved_30d': int(ep_approved_30d or 0),
|
||||
'approval_rate': (float(ep_approved_30d or 0) / float(ep_total_30d) * 100) if ep_total_30d else 0,
|
||||
}
|
||||
except Exception:
|
||||
summary['episodes'] = {}
|
||||
|
||||
# PPT 視覺審核 7d
|
||||
try:
|
||||
ppt = session.execute(
|
||||
sa_text("""
|
||||
SELECT COUNT(*),
|
||||
COUNT(*) FILTER (WHERE audit_status='passed'),
|
||||
COUNT(*) FILTER (WHERE audit_status='failed')
|
||||
FROM ppt_audit_results
|
||||
WHERE audited_at >= NOW() - INTERVAL '7 days'
|
||||
"""),
|
||||
).fetchone()
|
||||
ppt_total = int(ppt[0] or 0)
|
||||
summary['ppt'] = {
|
||||
'total': ppt_total,
|
||||
'passed': int(ppt[1] or 0),
|
||||
'failed': int(ppt[2] or 0),
|
||||
'pass_rate': (float(ppt[1] or 0) / ppt_total * 100) if ppt_total else 0,
|
||||
}
|
||||
except Exception:
|
||||
summary['ppt'] = {}
|
||||
|
||||
# AIOps 7d
|
||||
try:
|
||||
inc = session.execute(
|
||||
sa_text("""
|
||||
SELECT COUNT(*),
|
||||
COUNT(*) FILTER (WHERE status='open'),
|
||||
COUNT(*) FILTER (WHERE severity IN ('P0','P1'))
|
||||
FROM incidents
|
||||
WHERE created_at >= NOW() - INTERVAL '7 days'
|
||||
"""),
|
||||
).fetchone()
|
||||
heal = session.execute(
|
||||
sa_text("""
|
||||
SELECT COUNT(*),
|
||||
COUNT(*) FILTER (WHERE result='success')
|
||||
FROM heal_logs
|
||||
WHERE created_at >= NOW() - INTERVAL '7 days'
|
||||
"""),
|
||||
).fetchone()
|
||||
summary['aiops'] = {
|
||||
'incidents_total': int(inc[0] or 0),
|
||||
'incidents_open': int(inc[1] or 0),
|
||||
'incidents_p0_p1': int(inc[2] or 0),
|
||||
'heals_total': int(heal[0] or 0),
|
||||
'heals_success': int(heal[1] or 0),
|
||||
'heal_rate': (float(heal[1] or 0) / float(heal[0]) * 100) if heal[0] else 0,
|
||||
}
|
||||
except Exception:
|
||||
summary['aiops'] = {}
|
||||
|
||||
# MCP 24h
|
||||
try:
|
||||
mcp = session.execute(
|
||||
sa_text("""
|
||||
SELECT COUNT(*), COUNT(DISTINCT server),
|
||||
COUNT(*) FILTER (WHERE cache_hit),
|
||||
COALESCE(SUM(cost_usd), 0)
|
||||
FROM mcp_calls
|
||||
WHERE called_at >= NOW() - INTERVAL '24 hours'
|
||||
"""),
|
||||
).fetchone()
|
||||
mcp_total = int(mcp[0] or 0)
|
||||
summary['mcp'] = {
|
||||
'total': mcp_total,
|
||||
'servers': int(mcp[1] or 0),
|
||||
'cache_hits': int(mcp[2] or 0),
|
||||
'cost': float(mcp[3] or 0),
|
||||
'cache_rate': (float(mcp[2] or 0) / mcp_total * 100) if mcp_total else 0,
|
||||
}
|
||||
except Exception:
|
||||
summary['mcp'] = {}
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
return render_template(
|
||||
'admin/observability_overview.html',
|
||||
active_page='obs_overview',
|
||||
summary=summary,
|
||||
today=today.strftime('%Y-%m-%d'),
|
||||
)
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# /observability/ai_calls — Phase 27 主入口
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "ewoooc_base.html" %}
|
||||
|
||||
{% block title %}AI 呼叫總覽{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block ewooo_content %}
|
||||
<div class="container-fluid mt-3">
|
||||
<h2 class="mb-3"><i class="fas fa-chart-bar me-2"></i>AI 呼叫總覽
|
||||
<small class="text-muted">過去 {{ hours }} 小時</small>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "ewoooc_base.html" %}
|
||||
|
||||
{% block title %}預算控管{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block ewooo_content %}
|
||||
<div class="container-fluid mt-3">
|
||||
<h2 class="mb-3"><i class="fas fa-wallet me-2"></i>預算控管
|
||||
<small class="text-muted">ai_call_budgets × 當月實際支出即時對比</small>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "ewoooc_base.html" %}
|
||||
|
||||
{% block title %}主機健康監控{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block ewooo_content %}
|
||||
<div class="container-fluid mt-3">
|
||||
<h2 class="mb-3"><i class="fas fa-heartbeat me-2"></i>主機健康監控
|
||||
<small class="text-muted">三主機 Ollama + MCP + 成本節流即時狀態</small>
|
||||
|
||||
273
templates/admin/observability_overview.html
Normal file
273
templates/admin/observability_overview.html
Normal file
@@ -0,0 +1,273 @@
|
||||
{% extends "ewoooc_base.html" %}
|
||||
|
||||
{% block title %}觀測台總覽{% endblock %}
|
||||
|
||||
{% block ewooo_content %}
|
||||
<div class="container-fluid mt-3">
|
||||
<h2 class="mb-3"><i class="fas fa-satellite-dish me-2"></i>AI 觀測台總覽
|
||||
<small class="text-muted">{{ today }} · 全景一頁看(資料來源 8 表跨 JOIN)</small>
|
||||
</h2>
|
||||
|
||||
<!-- 三主機健康卡片 -->
|
||||
<div class="row g-3 mb-3">
|
||||
{% if summary.hosts %}
|
||||
{% for h in summary.hosts %}
|
||||
<div class="col-lg-4 col-md-6">
|
||||
<div class="card h-100" style="border-left: 4px solid
|
||||
{% if h.uptime_pct >= 99 %}#198754
|
||||
{% elif h.uptime_pct >= 90 %}#ffc107
|
||||
{% else %}#dc3545{% endif %};">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<small class="text-muted d-block">{{ h.label }}</small>
|
||||
<h3 class="mb-0">{{ "%.1f"|format(h.uptime_pct) }}<small class="text-muted">%</small></h3>
|
||||
<small class="text-muted">24h 在線率({{ h.up }}/{{ h.total }} probe)</small>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<i class="fas fa-server" style="font-size: 1.8em; color: #ddd;"></i>
|
||||
<div class="mt-2"><small class="text-muted">{{ h.avg_ms }} ms</small></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="col-12">
|
||||
<div class="alert alert-warning">
|
||||
<i class="fas fa-exclamation-triangle me-1"></i>
|
||||
host_health_probes 表無資料(migration 029 是否已跑?scheduler probe job 是否已啟動?)
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- AI 呼叫 + 成本卡片 -->
|
||||
<div class="row g-3 mb-3">
|
||||
{% if summary.ai_calls %}
|
||||
<div class="col-lg-3 col-md-6">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<small class="text-muted d-block"><i class="fas fa-chart-bar me-1"></i>24h AI 呼叫</small>
|
||||
<h3 class="mb-0">{{ "{:,}".format(summary.ai_calls.total) }}</h3>
|
||||
<small class="text-muted">Token:{{ "{:,}".format(summary.ai_calls.tokens) }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<small class="text-muted d-block"><i class="fas fa-coins me-1"></i>成本</small>
|
||||
<h3 class="mb-0">${{ "%.2f"|format(summary.ai_calls.cost_24h) }}<small class="text-muted">/24h</small></h3>
|
||||
<small class="text-muted">當月累計 ${{ "%.2f"|format(summary.month_cost) }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<small class="text-muted d-block"><i class="fas fa-exclamation-triangle me-1"></i>錯誤率</small>
|
||||
<h3 class="mb-0
|
||||
{% if summary.ai_calls.error_rate >= 15 %}text-danger
|
||||
{% elif summary.ai_calls.error_rate >= 5 %}text-warning
|
||||
{% else %}text-success{% endif %}">{{ "%.1f"|format(summary.ai_calls.error_rate) }}<small>%</small></h3>
|
||||
<small class="text-muted">{{ summary.ai_calls.errors }} 次失敗</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<small class="text-muted d-block"><i class="fas fa-magnifying-glass-chart me-1"></i>RAG 命中率</small>
|
||||
<h3 class="mb-0 text-success">{{ "%.1f"|format(summary.ai_calls.rag_rate) }}<small>%</small></h3>
|
||||
<small class="text-muted">{{ summary.ai_calls.rag_hits }} hits · cache {{ "%.0f"|format(summary.ai_calls.cache_rate) }}%</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- 預算告警 -->
|
||||
{% if summary.budget_alerts %}
|
||||
<div class="card mb-3" style="border-left: 4px solid #ffc107;">
|
||||
<div class="card-header bg-warning bg-opacity-25">
|
||||
<strong><i class="fas fa-wallet me-1"></i>預算告警 — 共 {{ summary.budget_alerts|length }} 項超出閾值</strong>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<table class="table table-sm mb-0">
|
||||
<thead class="table-light">
|
||||
<tr><th>週期</th><th>供應商</th><th class="text-end">已花費</th><th class="text-end">預算</th><th class="text-end">使用率</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for b in summary.budget_alerts %}
|
||||
<tr>
|
||||
<td><span class="badge bg-secondary">{{ b.period }}</span></td>
|
||||
<td><code>{{ b.provider }}</code></td>
|
||||
<td class="text-end">${{ "%.2f"|format(b.spent) }}</td>
|
||||
<td class="text-end">${{ "%.2f"|format(b.budget) }}</td>
|
||||
<td class="text-end">
|
||||
<strong class="{% if b.ratio >= 1.0 %}text-danger{% else %}text-warning{% endif %}">
|
||||
{{ "%.0f"|format(b.ratio * 100) }}%
|
||||
</strong>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="card-footer text-end">
|
||||
<a href="/observability/budget" class="btn btn-sm btn-outline-warning">
|
||||
<i class="fas fa-arrow-right me-1"></i>進預算控管處理
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- AIOps + MCP + RAG 學習 -->
|
||||
<div class="row g-3 mb-3">
|
||||
{% if summary.aiops %}
|
||||
<div class="col-lg-4 col-md-6">
|
||||
<div class="card h-100">
|
||||
<div class="card-header"><strong><i class="fas fa-shield-virus me-1"></i>AIOps 自癒 7d</strong></div>
|
||||
<div class="card-body">
|
||||
<div class="row g-2">
|
||||
<div class="col-6"><small class="text-muted d-block">事件總數</small><h4 class="mb-0">{{ summary.aiops.incidents_total }}</h4></div>
|
||||
<div class="col-6"><small class="text-muted d-block">未解決</small><h4 class="mb-0 {% if summary.aiops.incidents_open > 0 %}text-danger{% endif %}">{{ summary.aiops.incidents_open }}</h4></div>
|
||||
<div class="col-6"><small class="text-muted d-block">P0/P1</small><h4 class="mb-0 {% if summary.aiops.incidents_p0_p1 > 0 %}text-danger{% endif %}">{{ summary.aiops.incidents_p0_p1 }}</h4></div>
|
||||
<div class="col-6"><small class="text-muted d-block">自癒成功率</small><h4 class="mb-0
|
||||
{% if summary.aiops.heal_rate >= 80 %}text-success
|
||||
{% elif summary.aiops.heal_rate >= 50 %}text-warning
|
||||
{% else %}text-danger{% endif %}">{{ "%.0f"|format(summary.aiops.heal_rate) }}%</h4></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer text-end p-2">
|
||||
<a href="/observability/host_health" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="fas fa-arrow-right me-1"></i>主機健康
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if summary.mcp %}
|
||||
<div class="col-lg-4 col-md-6">
|
||||
<div class="card h-100">
|
||||
<div class="card-header"><strong><i class="fas fa-bolt me-1"></i>MCP 24h</strong></div>
|
||||
<div class="card-body">
|
||||
<div class="row g-2">
|
||||
<div class="col-6"><small class="text-muted d-block">tool 呼叫</small><h4 class="mb-0">{{ "{:,}".format(summary.mcp.total) }}</h4></div>
|
||||
<div class="col-6"><small class="text-muted d-block">使用 server</small><h4 class="mb-0">{{ summary.mcp.servers }}</h4></div>
|
||||
<div class="col-6"><small class="text-muted d-block">cache 命中</small><h4 class="mb-0">{{ summary.mcp.cache_hits }}</h4></div>
|
||||
<div class="col-6"><small class="text-muted d-block">成本</small><h4 class="mb-0">${{ "%.4f"|format(summary.mcp.cost) }}</h4></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer text-end p-2">
|
||||
<a href="/observability/host_health" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="fas fa-arrow-right me-1"></i>查 MCP 健康
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if summary.episodes %}
|
||||
<div class="col-lg-4 col-md-6">
|
||||
<div class="card h-100">
|
||||
<div class="card-header"><strong><i class="fas fa-brain me-1"></i>RAG 學習鏈路 30d</strong></div>
|
||||
<div class="card-body">
|
||||
<div class="row g-2">
|
||||
<div class="col-6"><small class="text-muted d-block">待審核</small><h4 class="mb-0 {% if summary.episodes.pending > 0 %}text-warning{% endif %}">{{ summary.episodes.pending }}</h4></div>
|
||||
<div class="col-6"><small class="text-muted d-block">總 episodes</small><h4 class="mb-0">{{ summary.episodes.total_30d }}</h4></div>
|
||||
<div class="col-6"><small class="text-muted d-block">已晉升</small><h4 class="mb-0 text-success">{{ summary.episodes.approved_30d }}</h4></div>
|
||||
<div class="col-6"><small class="text-muted d-block">晉升率</small><h4 class="mb-0">{{ "%.0f"|format(summary.episodes.approval_rate) }}%</h4></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer text-end p-2">
|
||||
{% if summary.episodes.pending > 0 %}
|
||||
<a href="/observability/promotion_review" class="btn btn-sm btn-warning">
|
||||
<i class="fas fa-arrow-right me-1"></i>立即審核 ({{ summary.episodes.pending }})
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="/observability/promotion_review" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="fas fa-arrow-right me-1"></i>晉升審核
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- PPT 視覺審核 -->
|
||||
{% if summary.ppt and summary.ppt.total > 0 %}
|
||||
<div class="card mb-3">
|
||||
<div class="card-header"><strong><i class="fas fa-search me-1"></i>PPT 視覺審核 7d</strong></div>
|
||||
<div class="card-body">
|
||||
<div class="row g-2">
|
||||
<div class="col-md-3 col-6"><small class="text-muted d-block">總筆數</small><h4 class="mb-0">{{ summary.ppt.total }}</h4></div>
|
||||
<div class="col-md-3 col-6"><small class="text-muted d-block">通過</small><h4 class="mb-0 text-success">{{ summary.ppt.passed }}</h4></div>
|
||||
<div class="col-md-3 col-6"><small class="text-muted d-block">失敗</small><h4 class="mb-0 {% if summary.ppt.failed > 0 %}text-warning{% endif %}">{{ summary.ppt.failed }}</h4></div>
|
||||
<div class="col-md-3 col-6"><small class="text-muted d-block">通過率</small><h4 class="mb-0">{{ "%.0f"|format(summary.ppt.pass_rate) }}%</h4></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer text-end p-2">
|
||||
<a href="/observability/ppt_audit_history" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="fas fa-arrow-right me-1"></i>PPT 審核歷史
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- 6 大入口 -->
|
||||
<div class="card">
|
||||
<div class="card-header"><strong><i class="fas fa-th me-1"></i>6 大子頁入口</strong></div>
|
||||
<div class="card-body">
|
||||
<div class="row g-2">
|
||||
<div class="col-lg-4 col-md-6">
|
||||
<a href="/observability/host_health" class="btn btn-outline-primary w-100 text-start">
|
||||
<i class="fas fa-heartbeat me-2"></i>主機健康監控
|
||||
<small class="d-block text-muted ms-4">三主機 + MCP + AIOps + 24h 趨勢 + 一鍵 AutoHeal</small>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-lg-4 col-md-6">
|
||||
<a href="/observability/ai_calls" class="btn btn-outline-primary w-100 text-start">
|
||||
<i class="fas fa-chart-bar me-2"></i>AI 呼叫總覽
|
||||
<small class="d-block text-muted ms-4">24h 統計 + RAG×MCP 編排矩陣 + 一鍵 Code Review</small>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-lg-4 col-md-6">
|
||||
<a href="/observability/budget" class="btn btn-outline-primary w-100 text-start">
|
||||
<i class="fas fa-wallet me-2"></i>預算控管
|
||||
<small class="d-block text-muted ms-4">當月支出 + RAG 策略建議 + 一鍵 force-throttle</small>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-lg-4 col-md-6">
|
||||
<a href="/observability/promotion_review" class="btn btn-outline-primary w-100 text-start">
|
||||
<i class="fas fa-brain me-2"></i>RAG 學習晉升審核
|
||||
<small class="d-block text-muted ms-4">待審 episode + RAG Top 3 相似已晉升輔助</small>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-lg-4 col-md-6">
|
||||
<a href="/observability/quality_trend" class="btn btn-outline-primary w-100 text-start">
|
||||
<i class="fas fa-comments me-2"></i>Caller 反饋趨勢
|
||||
<small class="d-block text-muted ms-4">蒸餾池分布 + 最差 caller RAG 根因建議</small>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-lg-4 col-md-6">
|
||||
<a href="/observability/ppt_audit_history" class="btn btn-outline-primary w-100 text-start">
|
||||
<i class="fas fa-search me-2"></i>PPT 視覺審核歷史
|
||||
<small class="d-block text-muted ms-4">7d audit 紀錄 + RAG 修法 + 一鍵 AiderHeal</small>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-muted mt-3"><small>
|
||||
<i class="fas fa-robot me-1"></i>Operation Ollama-First v5.0 / Phase 45 — AI 觀測台總覽
|
||||
(資料來源:host_health_probes / ai_calls / ai_call_budgets / learning_episodes / ai_insights /
|
||||
rag_query_log / mcp_calls / incidents / heal_logs / ppt_audit_results)
|
||||
</small></p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,8 +1,8 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "ewoooc_base.html" %}
|
||||
|
||||
{% block title %}PPT 視覺審核歷史{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block ewooo_content %}
|
||||
<div class="container-fluid mt-3">
|
||||
<h2 class="mb-3"><i class="fas fa-search me-2"></i>PPT 視覺審核歷史
|
||||
<small class="text-muted">reports/ 目錄過去 7 日 .pptx</small>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "ewoooc_base.html" %}
|
||||
|
||||
{% block title %}RAG 學習晉升審核{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block ewooo_content %}
|
||||
<div class="container-fluid mt-3">
|
||||
<h2 class="mb-3"><i class="fas fa-brain me-2"></i>RAG 學習晉升審核
|
||||
<small class="text-muted">待審核 × {{ episodes|length }} 筆 · 知識庫 ai_insights × {{ kb_size or 0 }} 筆</small>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "ewoooc_base.html" %}
|
||||
|
||||
{% block title %}Caller 反饋趨勢{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block ewooo_content %}
|
||||
<div class="container-fluid mt-3">
|
||||
<h2 class="mb-3"><i class="fas fa-comments me-2"></i>Caller 反饋趨勢
|
||||
<small class="text-muted">過去 {{ days }} 日</small>
|
||||
|
||||
@@ -74,12 +74,51 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="momo-nav-group">
|
||||
<div class="momo-nav-group-title momo-label">AI 觀測</div>
|
||||
<a class="momo-nav-link {% if _active_page == 'obs_overview' %}is-active{% endif %}" href="/observability/overview">
|
||||
<span class="momo-nav-icon"><i class="fas fa-satellite-dish"></i></span>
|
||||
<span class="momo-nav-label">觀測台總覽</span>
|
||||
<span class="momo-nav-code momo-mono">07</span>
|
||||
</a>
|
||||
<a class="momo-nav-link {% if _active_page == 'obs_host_health' %}is-active{% endif %}" href="/observability/host_health">
|
||||
<span class="momo-nav-icon"><i class="fas fa-heartbeat"></i></span>
|
||||
<span class="momo-nav-label">主機健康</span>
|
||||
<span class="momo-nav-code momo-mono">08</span>
|
||||
</a>
|
||||
<a class="momo-nav-link {% if _active_page == 'obs_ai_calls' %}is-active{% endif %}" href="/observability/ai_calls">
|
||||
<span class="momo-nav-icon"><i class="fas fa-chart-bar"></i></span>
|
||||
<span class="momo-nav-label">AI 呼叫</span>
|
||||
<span class="momo-nav-code momo-mono">09</span>
|
||||
</a>
|
||||
<a class="momo-nav-link {% if _active_page == 'obs_budget' %}is-active{% endif %}" href="/observability/budget">
|
||||
<span class="momo-nav-icon"><i class="fas fa-wallet"></i></span>
|
||||
<span class="momo-nav-label">預算控管</span>
|
||||
<span class="momo-nav-code momo-mono">10</span>
|
||||
</a>
|
||||
<a class="momo-nav-link {% if _active_page == 'obs_promotion_review' %}is-active{% endif %}" href="/observability/promotion_review">
|
||||
<span class="momo-nav-icon"><i class="fas fa-brain"></i></span>
|
||||
<span class="momo-nav-label">RAG 晉升審核</span>
|
||||
<span class="momo-nav-code momo-mono">11</span>
|
||||
</a>
|
||||
<a class="momo-nav-link {% if _active_page == 'obs_quality_trend' %}is-active{% endif %}" href="/observability/quality_trend">
|
||||
<span class="momo-nav-icon"><i class="fas fa-comments"></i></span>
|
||||
<span class="momo-nav-label">反饋趨勢</span>
|
||||
<span class="momo-nav-code momo-mono">12</span>
|
||||
</a>
|
||||
<a class="momo-nav-link {% if _active_page == 'obs_ppt_audit' %}is-active{% endif %}" href="/observability/ppt_audit_history">
|
||||
<span class="momo-nav-icon"><i class="fas fa-search"></i></span>
|
||||
<span class="momo-nav-label">PPT 視覺審核</span>
|
||||
<span class="momo-nav-code momo-mono">13</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="momo-nav-group">
|
||||
<div class="momo-nav-group-title momo-label">系統</div>
|
||||
<a class="momo-nav-link {% if _active_page in ['settings', 'system_settings', 'logs', 'crawler', 'user_management', 'ai_automation_smoke'] %}is-active{% endif %}" href="/settings">
|
||||
<span class="momo-nav-icon"><i class="fas fa-gear"></i></span>
|
||||
<span class="momo-nav-label">系統管理</span>
|
||||
<span class="momo-nav-code momo-mono">07</span>
|
||||
<span class="momo-nav-code momo-mono">14</span>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
Reference in New Issue
Block a user