From a3979223618bc9e19be44cb1692ecdc75d1c3f30 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=85=AC=E6=98=8E?=
<83812544+Ed1s0nZ@users.noreply.github.com>
Date: Thu, 4 Jun 2026 17:57:12 +0800
Subject: [PATCH] Add files via upload
---
web/static/css/style.css | 1819 ++++++++++++++++++++++--------------
web/static/i18n/en-US.json | 25 +-
web/static/i18n/zh-CN.json | 25 +-
web/static/js/monitor.js | 1008 ++++++++++++++++----
web/templates/index.html | 20 +-
5 files changed, 1971 insertions(+), 926 deletions(-)
diff --git a/web/static/css/style.css b/web/static/css/style.css
index e5195903..919996aa 100644
--- a/web/static/css/style.css
+++ b/web/static/css/style.css
@@ -5984,18 +5984,58 @@ header {
.monitor-sections {
display: grid;
- gap: 24px;
+ gap: 12px;
width: 100%;
box-sizing: border-box;
min-width: 0;
}
+/* ── MCP 监控页:页面级布局 ── */
+.mcp-monitor-page .page-content {
+ padding: 16px 20px;
+ background: #f1f5f9;
+}
+
+.mcp-monitor-page .page-header {
+ padding: 16px 24px;
+ background: #fff;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.06);
+ box-shadow: 0 1px 0 rgba(255, 255, 255, 0.8) inset;
+}
+
+.mcp-monitor-page .page-header-main {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ min-width: 0;
+}
+
+.mcp-monitor-page .page-header h2 {
+ font-size: 1.25rem;
+ font-weight: 700;
+ letter-spacing: -0.02em;
+}
+
+.monitor-page-subtitle {
+ margin: 0;
+ font-size: 0.8125rem;
+ color: var(--text-secondary);
+ font-weight: 500;
+ line-height: 1.4;
+}
+
+.mcp-monitor-page .btn-icon-text {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+}
+
.monitor-section {
background: var(--bg-primary);
- border: 1px solid var(--border-color);
- border-radius: 14px;
- padding: 20px;
- box-shadow: var(--shadow-sm);
+ border: 1px solid rgba(0, 0, 0, 0.06);
+ border-radius: 16px;
+ padding: 20px 22px;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04), 0 4px 16px rgba(0, 0, 0, 0.03);
display: flex;
flex-direction: column;
gap: 16px;
@@ -6004,6 +6044,14 @@ header {
box-sizing: border-box;
}
+.monitor-section.monitor-overview {
+ background: transparent;
+ border: none;
+ box-shadow: none;
+ padding: 0;
+ gap: 0;
+}
+
.monitor-section .section-header {
display: flex;
align-items: center;
@@ -6015,7 +6063,9 @@ header {
.monitor-section .section-header h3 {
margin: 0;
- font-size: 1.1rem;
+ font-size: 1rem;
+ font-weight: 700;
+ letter-spacing: -0.01em;
color: var(--text-primary);
flex-shrink: 0;
min-width: 0;
@@ -6089,22 +6139,7 @@ header {
color: var(--text-muted);
}
-/* MCP 执行统计:概览 KPI + 工具排行 */
-.monitor-stats-section-header {
- align-items: flex-start;
-}
-
-.monitor-stats-header-text h3 {
- margin: 0;
-}
-
-.monitor-stats-subtitle {
- margin: 4px 0 0;
- font-size: 0.75rem;
- color: var(--text-muted);
- font-weight: 500;
-}
-
+/* MCP 执行统计 — 最终布局(metrics bar + panel 表格/饼图) */
.mcp-exec-stats-root {
width: 100%;
min-width: 0;
@@ -6113,122 +6148,114 @@ header {
.mcp-exec-stats {
display: flex;
flex-direction: column;
- gap: 20px;
+ gap: 12px;
width: 100%;
}
-.mcp-stats-kpi-row {
- display: grid;
- grid-template-columns: repeat(3, minmax(0, 1fr));
- gap: 16px;
+/* ── KPI 概览条 ── */
+.mcp-stats-kpi {
+ display: flex;
+ flex-direction: row;
+ align-items: stretch;
+ background: #fff;
+ border: 1px solid rgba(0, 0, 0, 0.07);
+ border-radius: 12px;
+ box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04);
+ overflow: hidden;
}
-@media (max-width: 900px) {
- .mcp-stats-kpi-row {
- grid-template-columns: 1fr;
+@media (max-width: 768px) {
+ .mcp-stats-kpi {
+ flex-direction: column;
}
}
-.mcp-stats-kpi-card {
- background: #fff;
- border-radius: 14px;
- padding: 18px 20px;
- border: 1px solid rgba(0, 0, 0, 0.06);
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04);
+.mcp-stats-kpi__item {
+ flex: 1;
+ display: flex;
+ align-items: stretch;
+ gap: 12px;
+ padding: 14px 20px;
+ min-width: 0;
+ position: relative;
+}
+
+.mcp-stats-kpi__item:not(:last-child)::after {
+ content: '';
+ position: absolute;
+ right: 0;
+ top: 18%;
+ height: 64%;
+ width: 1px;
+ background: rgba(0, 0, 0, 0.06);
+}
+
+@media (max-width: 768px) {
+ .mcp-stats-kpi__item:not(:last-child)::after {
+ right: 16px;
+ left: 16px;
+ top: auto;
+ bottom: 0;
+ width: auto;
+ height: 1px;
+ }
+}
+
+.mcp-stats-kpi__accent {
+ flex: 0 0 3px;
+ border-radius: 3px;
+ align-self: stretch;
+}
+
+.mcp-stats-kpi__item--calls .mcp-stats-kpi__accent { background: linear-gradient(180deg, #60a5fa, #2563eb); }
+.mcp-stats-kpi__item--rate .mcp-stats-kpi__accent { background: linear-gradient(180deg, #2dd4bf, #0d9488); }
+.mcp-stats-kpi__item--time .mcp-stats-kpi__accent { background: linear-gradient(180deg, #a78bfa, #7c3aed); }
+
+.mcp-stats-kpi__content {
display: flex;
flex-direction: column;
- gap: 10px;
- min-height: 118px;
- min-width: 0;
- transition: transform 0.2s ease, box-shadow 0.25s ease, border-color 0.2s ease;
-}
-
-.mcp-stats-kpi-card--calls {
- background: linear-gradient(145deg, #fff 0%, #f0f9ff 100%);
-}
-
-.mcp-stats-kpi-card--rate {
- background: linear-gradient(145deg, #fff 0%, #f0fdfa 100%);
-}
-
-.mcp-stats-kpi-card--time {
- background: linear-gradient(145deg, #fff 0%, #faf5ff 100%);
-}
-
-.mcp-stats-kpi-head {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 8px;
-}
-
-.mcp-stats-kpi-label {
- font-size: 0.8125rem;
- color: var(--text-secondary);
- font-weight: 500;
-}
-
-.mcp-stats-kpi-icon {
- width: 28px;
- height: 28px;
- border-radius: 8px;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
-}
-
-.mcp-stats-kpi-icon--calls { background: rgba(59, 130, 246, 0.1); color: #3b82f6; }
-.mcp-stats-kpi-icon--rate { background: rgba(20, 184, 166, 0.1); color: #14b8a6; }
-.mcp-stats-kpi-icon--time { background: rgba(139, 92, 246, 0.1); color: #8b5cf6; }
-
-.mcp-stats-kpi-body {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 12px;
+ gap: 4px;
min-width: 0;
}
-.mcp-stats-kpi-value {
- font-size: 1.875rem;
- font-weight: 800;
- color: var(--text-primary);
- line-height: 1.1;
- letter-spacing: -0.03em;
- font-variant-numeric: tabular-nums;
+.mcp-stats-kpi__label {
+ font-size: 0.6875rem;
+ font-weight: 600;
+ letter-spacing: 0.03em;
+ text-transform: uppercase;
+ color: var(--text-muted);
}
-.mcp-stats-kpi-value--time {
- font-size: 1.05rem;
+.mcp-stats-kpi__value {
+ font-size: 1.625rem;
font-weight: 700;
+ color: var(--text-primary);
+ font-variant-numeric: tabular-nums;
+ line-height: 1.05;
+ letter-spacing: -0.03em;
+}
+
+.mcp-stats-kpi__value--rate.is-success { color: #15803d; }
+.mcp-stats-kpi__value--rate.is-warning { color: #ca8a04; }
+.mcp-stats-kpi__value--rate.is-danger { color: #dc2626; }
+
+.mcp-stats-kpi__value--time {
+ font-size: 0.875rem;
+ font-weight: 600;
letter-spacing: -0.01em;
line-height: 1.35;
- word-break: break-word;
}
-.mcp-stats-kpi-sub {
+.mcp-stats-kpi__meta {
display: flex;
- align-items: center;
flex-wrap: wrap;
- gap: 8px;
- min-height: 22px;
+ gap: 6px;
+ margin-top: 2px;
}
-.mcp-stats-kpi-sub-text {
- font-size: 0.75rem;
- color: var(--text-secondary);
- font-weight: 500;
-}
-
-.mcp-stats-kpi-sub-text.is-success { color: #15803d; }
-.mcp-stats-kpi-sub-text.is-warning { color: #ca8a04; }
-.mcp-stats-kpi-sub-text.is-danger { color: #dc2626; }
-
-.mcp-stats-pill {
+.mcp-stats-kpi__chip {
display: inline-flex;
align-items: center;
- gap: 4px;
padding: 2px 8px;
border-radius: 999px;
font-size: 0.6875rem;
@@ -6236,187 +6263,968 @@ header {
font-variant-numeric: tabular-nums;
}
-.mcp-stats-pill--success {
+.mcp-stats-kpi__chip.is-ok {
+ color: #166534;
background: rgba(34, 197, 94, 0.12);
- color: #15803d;
}
-.mcp-stats-pill--fail {
+.mcp-stats-kpi__chip.is-fail {
+ color: #991b1b;
background: rgba(239, 68, 68, 0.1);
+}
+
+.mcp-stats-kpi__status {
+ font-size: 0.75rem;
+ font-weight: 500;
+}
+
+.mcp-stats-kpi__status.is-success { color: #15803d; }
+.mcp-stats-kpi__status.is-warning { color: #ca8a04; }
+.mcp-stats-kpi__status.is-danger { color: #dc2626; }
+
+/* ── 工具统计 + 调用趋势(合并面板) ── */
+.mcp-stats-combined {
+ background: #fff;
+ border: 1px solid rgba(0, 0, 0, 0.07);
+ border-radius: 10px;
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
+ overflow: hidden;
+}
+
+.mcp-stats-combined__head {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ flex-wrap: wrap;
+ padding: 12px 16px;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.06);
+ background: #fafbfc;
+}
+
+.mcp-stats-combined__title {
+ margin: 0;
+ font-size: 0.875rem;
+ font-weight: 600;
+ color: var(--text-primary);
+}
+
+.mcp-stats-combined__meta-row {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 8px 10px;
+ margin-top: 6px;
+}
+
+.mcp-stats-combined__meta {
+ margin: 0;
+ font-size: 0.6875rem;
+ color: var(--text-muted);
+ line-height: 1.4;
+}
+
+.mcp-stats-combined__scopes {
+ display: inline-flex;
+ flex-wrap: wrap;
+ gap: 6px;
+}
+
+.mcp-stats-scope-badge {
+ display: inline-flex;
+ align-items: center;
+ padding: 2px 8px;
+ border-radius: 999px;
+ font-size: 0.625rem;
+ font-weight: 600;
+ letter-spacing: 0.02em;
+ line-height: 1.3;
+}
+
+.mcp-stats-scope-badge--cumulative {
+ color: #1e40af;
+ background: rgba(59, 130, 246, 0.1);
+}
+
+.mcp-stats-scope-badge--timeline {
+ color: #0f766e;
+ background: rgba(20, 184, 166, 0.12);
+}
+
+.mcp-stats-scope-badge--inline {
+ margin-right: 6px;
+ vertical-align: middle;
+}
+
+.mcp-stats-filter-chip {
+ display: inline-flex;
+ align-items: center;
+ gap: 2px;
+ max-width: min(280px, 100%);
+ padding: 4px 8px 4px 10px;
+ border-radius: 999px;
+ background: rgba(0, 102, 255, 0.08);
+ border: 1px solid rgba(0, 102, 255, 0.22);
+}
+
+.mcp-stats-filter-chip__label {
+ font-size: 0.75rem;
+ font-weight: 500;
+ color: var(--accent-color);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.mcp-stats-filter-chip__clear,
+.mcp-stats-filter-chip .mcp-stats-clear-filter {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: auto;
+ min-width: 18px;
+ height: auto;
+ margin: 0;
+ padding: 0 2px 0 4px;
+ border: none;
+ border-radius: 0;
+ background: transparent;
+ box-shadow: none;
+ color: var(--accent-color);
+ font-size: 1.125rem;
+ font-weight: 400;
+ line-height: 1;
+ cursor: pointer;
+ flex-shrink: 0;
+}
+
+.mcp-stats-filter-chip__clear:hover,
+.mcp-stats-filter-chip .mcp-stats-clear-filter:hover {
+ background: transparent;
+ color: #1d4ed8;
+ opacity: 0.85;
+}
+
+.mcp-stats-combined__actions {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ flex-shrink: 0;
+ flex-wrap: wrap;
+}
+
+/* 左侧工具统计 : 右侧调用趋势 = 1 : 1 */
+.mcp-stats-combined__body--full {
+ display: flex;
+ flex-direction: row;
+ align-items: stretch;
+ min-width: 0;
+ min-height: 188px;
+}
+
+.mcp-stats-combined__body--tools {
+ display: flex;
+ flex-direction: row;
+ align-items: stretch;
+ min-width: 0;
+}
+
+.mcp-stats-combined__body--timeline {
+ display: block;
+}
+
+.mcp-stats-combined__main {
+ flex: 1 1 50%;
+ max-width: 50%;
+ min-width: 0;
+ padding: 10px 14px 12px;
+}
+
+.mcp-stats-combined__body--full .mcp-stats-combined__timeline {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ flex: 1 1 50%;
+ max-width: 50%;
+ min-width: 0;
+ padding: 8px 12px 10px;
+ background: #fff;
+ border-left: 1px solid rgba(0, 0, 0, 0.06);
+}
+
+.mcp-stats-combined__body--tools .mcp-stats-tool-table {
+ width: 100%;
+}
+
+@media (max-width: 900px) {
+ .mcp-stats-combined__body--full {
+ min-height: 160px;
+ }
+}
+
+@media (max-width: 720px) {
+ .mcp-stats-combined__body--full,
+ .mcp-stats-combined__body--tools {
+ flex-direction: column;
+ min-height: 0;
+ }
+ .mcp-stats-combined__main {
+ border-bottom: 1px solid rgba(0, 0, 0, 0.06);
+ }
+ .mcp-stats-tool-item__metrics,
+ .mcp-stats-tool-item__metrics-head {
+ grid-template-columns: 3rem 2.5rem 4rem;
+ column-gap: 0.875rem;
+ padding-left: 12px;
+ }
+ .mcp-stats-tool-item__track {
+ width: min(100%, 180px);
+ }
+ .mcp-stats-combined__timeline {
+ flex: 1 1 auto;
+ width: 100%;
+ max-width: none;
+ min-width: 0;
+ border-left: none;
+ }
+}
+
+.mcp-stats-combined__col-label {
+ margin: 0;
+ font-size: 0.6875rem;
+ font-weight: 600;
+ letter-spacing: 0.04em;
+ text-transform: uppercase;
+ color: var(--text-muted);
+}
+
+.mcp-stats-combined__timeline-inner {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ min-height: 0;
+ min-width: 0;
+}
+
+/* 左侧工具统计:堆叠条 + 排行列表 */
+.mcp-stats-tools-panel {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ min-width: 0;
+}
+
+.mcp-stats-tools-panel__hero {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+}
+
+.mcp-stats-proportion-bar {
+ display: flex;
+ align-items: stretch;
+ height: 10px;
+ border-radius: 999px;
+ overflow: hidden;
+ background: rgba(148, 163, 184, 0.2);
+}
+
+.mcp-stats-proportion-seg {
+ min-width: 3px;
+ border: none;
+ padding: 0;
+ cursor: pointer;
+ transition: filter 0.15s ease, opacity 0.15s ease;
+}
+
+.mcp-stats-proportion-seg.is-others {
+ cursor: default;
+}
+
+.mcp-stats-proportion-seg.is-highlighted,
+.mcp-stats-proportion-seg.is-active {
+ filter: brightness(1.08);
+ z-index: 1;
+}
+
+.mcp-stats-proportion-seg.is-dimmed {
+ opacity: 0.35;
+}
+
+.mcp-stats-proportion-seg:focus-visible {
+ outline: 2px solid rgba(0, 102, 255, 0.5);
+ outline-offset: 1px;
+}
+
+.mcp-stats-tools-panel__caption {
+ margin: 0;
+ font-size: 0.6875rem;
+ color: var(--text-muted);
+ line-height: 1.35;
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 4px;
+}
+
+.mcp-stats-timeline__sparse-hint {
+ margin: 0;
+ padding: 0 2px;
+ font-size: 0.625rem;
+ color: var(--text-muted);
+ line-height: 1.35;
+}
+
+.mcp-stats-tools-panel__list-head {
+ display: flex;
+ align-items: flex-end;
+ gap: 8px;
+ padding: 0 10px 4px;
+ font-size: 0.6875rem;
+ color: var(--text-muted);
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.04em;
+}
+
+.mcp-stats-tools-panel__list-head > span:nth-child(1) {
+ flex: 0 0 26px;
+ text-align: center;
+}
+
+.mcp-stats-tools-panel__list-head > span:nth-child(2) {
+ flex: 0 0 10px;
+}
+
+.mcp-stats-tools-panel__list-head > span:nth-child(3) {
+ flex: 1 1 auto;
+ min-width: 0;
+}
+
+.mcp-stats-tools-panel__list-head .mcp-stats-tool-item__metrics-head {
+ margin-left: auto;
+}
+
+.mcp-stats-tools-panel__list {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+}
+
+.mcp-stats-tool-item {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 10px 12px;
+ border-radius: 8px;
+ cursor: pointer;
+ transition: background 0.12s ease;
+ font-size: 0.8125rem;
+}
+
+.mcp-stats-tool-item__rank {
+ flex: 0 0 26px;
+}
+
+.mcp-stats-tool-item__dot {
+ flex: 0 0 10px;
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ flex-shrink: 0;
+}
+
+.mcp-stats-tool-item__body {
+ flex: 1 1 auto;
+ min-width: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 5px;
+}
+
+.mcp-stats-tool-item__metrics {
+ margin-left: auto;
+}
+
+.mcp-stats-tool-item__metrics,
+.mcp-stats-tool-item__metrics-head {
+ display: grid;
+ grid-template-columns: 3.5rem 3rem 4.75rem;
+ column-gap: 1.375rem;
+ row-gap: 3px;
+ align-items: center;
+ justify-items: end;
+ flex: 0 0 auto;
+ padding: 4px 8px 4px 20px;
+ border-left: 1px solid rgba(0, 0, 0, 0.06);
+ white-space: nowrap;
+}
+
+.mcp-stats-tool-item__metrics-head {
+ border-left: none;
+ padding-left: 18px;
+ line-height: 1.2;
+}
+
+.mcp-stats-tool-item__metrics-head > span {
+ text-align: right;
+}
+
+.mcp-stats-tool-item:hover,
+.mcp-stats-tool-item.is-highlighted {
+ background: rgba(0, 102, 255, 0.05);
+}
+
+.mcp-stats-tool-item.is-active {
+ background: rgba(0, 102, 255, 0.09);
+}
+
+.mcp-stats-tool-item.is-dimmed {
+ opacity: 0.42;
+}
+
+.mcp-stats-tool-item:focus-visible {
+ outline: 2px solid rgba(0, 102, 255, 0.45);
+ outline-offset: -2px;
+}
+
+.mcp-stats-tool-item__name {
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
+ font-size: 0.75rem;
+ font-weight: 500;
+ line-height: 1.3;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ min-width: 0;
+ flex: 1 1 auto;
+}
+
+.mcp-stats-tool-item__share {
+ font-size: 0.75rem;
+ font-weight: 500;
+ font-variant-numeric: tabular-nums;
+ color: var(--text-muted);
+ line-height: 1.2;
+}
+
+.mcp-stats-tool-item__track {
+ display: block;
+ width: min(100%, 240px);
+ max-width: 100%;
+ height: 4px;
+ border-radius: 999px;
+ background: rgba(0, 0, 0, 0.05);
+ overflow: hidden;
+}
+
+.mcp-stats-tool-item__fill {
+ display: block;
+ height: 100%;
+ border-radius: 999px;
+ min-width: 2px;
+ transition: width 0.2s ease;
+}
+
+.mcp-stats-tool-item__calls {
+ font-size: 0.875rem;
+ font-weight: 700;
+ font-variant-numeric: tabular-nums;
+ color: var(--text-primary);
+ line-height: 1.25;
+ min-width: 2ch;
+}
+
+.mcp-stats-tool-item__rate {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ justify-content: center;
+ gap: 2px;
+ font-size: 0.75rem;
+ font-weight: 600;
+ font-variant-numeric: tabular-nums;
+ white-space: nowrap;
+ line-height: 1.25;
+ min-width: 3.5rem;
+}
+
+.mcp-stats-tool-item__rate.is-success { color: #15803d; }
+.mcp-stats-tool-item__rate.is-warning { color: #ca8a04; }
+.mcp-stats-tool-item__rate.is-danger { color: #dc2626; }
+
+.mcp-stats-tool-item__fail {
+ display: block;
+ font-size: 0.625rem;
+ font-weight: 500;
+ color: #b91c1c;
+ line-height: 1.2;
+}
+
+.mcp-stats-tool-item .mcp-stats-rank {
+ width: 22px;
+ height: 22px;
+ font-size: 0.6875rem;
+}
+
+.mcp-stats-combined .mcp-stats-timeline__legend {
+ margin-top: 4px;
+ gap: 8px;
+}
+
+/* ── 调用趋势折线图(内嵌于合并面板) ── */
+.mcp-stats-timeline__inline-meta {
+ margin: 0;
+ font-size: 0.625rem;
+ color: var(--text-muted);
+ line-height: 1.35;
+}
+
+.mcp-stats-timeline__ranges {
+ display: inline-flex;
+ gap: 4px;
+ padding: 2px;
+ background: rgba(0, 0, 0, 0.04);
+ border-radius: 8px;
+}
+
+.mcp-stats-timeline__range {
+ padding: 4px 10px;
+ font-size: 0.75rem;
+ font-weight: 500;
+ border: none;
+ border-radius: 6px;
+ background: transparent;
+ color: var(--text-secondary);
+ cursor: pointer;
+ font: inherit;
+ line-height: 1.2;
+}
+
+.mcp-stats-timeline__range:hover {
+ color: var(--text-primary);
+ background: rgba(255, 255, 255, 0.7);
+}
+
+.mcp-stats-timeline__range.is-active {
+ background: #fff;
+ color: var(--accent-color);
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
+}
+
+.mcp-stats-timeline__chart-wrap {
+ flex: 1;
+ min-height: 120px;
+ min-width: 0;
+ width: 100%;
+ padding: 2px 0;
+}
+
+.mcp-stats-combined__timeline .mcp-stats-timeline__chart-wrap {
+ flex: 1;
+ min-height: 100px;
+ height: auto;
+}
+
+.mcp-stats-timeline__chart {
+ width: 100%;
+ height: 100%;
+ display: block;
+}
+
+.mcp-stats-combined__timeline .mcp-stats-timeline__chart {
+ min-height: 100px;
+ height: 100%;
+}
+
+.mcp-stats-timeline__legend {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 12px;
+ margin-top: 6px;
+ padding: 0 4px;
+ font-size: 0.6875rem;
+ color: var(--text-muted);
+}
+
+.mcp-stats-timeline__legend-item {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+}
+
+.mcp-stats-timeline__legend-item::before {
+ content: '';
+ width: 16px;
+ height: 3px;
+ border-radius: 999px;
+ background: linear-gradient(90deg, #60a5fa, #2563eb);
+}
+
+.mcp-stats-timeline__legend-item--fail::before {
+ background: transparent;
+ border-top: 2px dashed #ef4444;
+ height: 0;
+ width: 14px;
+}
+
+.mcp-stats-timeline-line {
+ fill: none;
+ stroke: #3b82f6;
+ stroke-width: 1.75;
+ stroke-linecap: round;
+ stroke-linejoin: round;
+ vector-effect: non-scaling-stroke;
+}
+
+.mcp-stats-timeline-area {
+ stroke: none;
+}
+
+.mcp-stats-timeline-line--fail {
+ stroke: #f87171;
+ stroke-width: 1.75;
+ stroke-dasharray: 5 4;
+ opacity: 0.9;
+ filter: none;
+}
+
+.mcp-stats-timeline-grid {
+ stroke: rgba(148, 163, 184, 0.35);
+ stroke-width: 1;
+ stroke-dasharray: 3 4;
+}
+
+.mcp-stats-timeline-grid--base {
+ stroke: rgba(148, 163, 184, 0.55);
+ stroke-dasharray: none;
+}
+
+.mcp-stats-timeline-axis {
+ font-size: 9px;
+ fill: var(--text-muted);
+}
+
+.mcp-stats-timeline-y {
+ font-size: 9px;
+ fill: var(--text-muted);
+ text-anchor: end;
+}
+
+.mcp-stats-timeline-peak-glow {
+ fill: rgba(59, 130, 246, 0.08);
+ stroke: none;
+ pointer-events: none;
+}
+
+.mcp-stats-timeline-dot {
+ fill: #fff;
+ stroke: #3b82f6;
+ stroke-width: 1.5;
+ cursor: crosshair;
+ opacity: 0;
+ transition: opacity 0.12s ease;
+}
+
+.mcp-stats-timeline-dot--peak {
+ opacity: 0.7;
+}
+
+.mcp-stats-timeline__chart-wrap:hover .mcp-stats-timeline-dot,
+.mcp-stats-timeline-dot.is-active {
+ opacity: 1;
+}
+
+.mcp-stats-timeline-dot.is-active {
+ fill: #2563eb;
+ stroke: #fff;
+ stroke-width: 2;
+}
+
+.mcp-stats-timeline-empty,
+.mcp-stats-timeline-error {
+ margin: 0;
+ padding: 20px 8px;
+ text-align: center;
+ font-size: 0.75rem;
+ color: var(--text-muted);
+}
+
+.mcp-stats-timeline-error {
color: #b91c1c;
}
-.mcp-stats-stacked-bar {
- display: flex;
- height: 6px;
- border-radius: 999px;
+.mcp-stats-timeline-tooltip {
+ position: fixed;
+ z-index: 10000;
+ pointer-events: none;
+ padding: 6px 10px;
+ font-size: 0.75rem;
+ line-height: 1.35;
+ color: #fff;
+ background: rgba(15, 23, 42, 0.92);
+ border-radius: 6px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ white-space: nowrap;
+ transform: translate(-50%, -100%);
+ margin-top: -8px;
+ display: none;
+}
+
+/* ── 工具统计面板:表格 + 饼图 ── */
+.mcp-stats-panel {
+ background: #fff;
+ border: 1px solid rgba(0, 0, 0, 0.07);
+ border-radius: 10px;
overflow: hidden;
- background: rgba(0, 0, 0, 0.06);
- gap: 1px;
- margin-top: 2px;
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
}
-.mcp-stats-stacked-bar-seg {
- min-width: 0;
- transition: flex-grow 0.35s ease;
+.mcp-stats-panel__head {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ padding: 12px 16px;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.06);
+ background: #fafbfc;
}
-.mcp-stats-stacked-bar-seg--success {
- background: linear-gradient(90deg, #22c55e, #16a34a);
+.mcp-stats-panel__title {
+ margin: 0;
+ font-size: 0.875rem;
+ font-weight: 600;
+ color: var(--text-primary);
}
-.mcp-stats-stacked-bar-seg--fail {
- background: linear-gradient(90deg, #f87171, #dc2626);
+.mcp-stats-panel__meta {
+ margin: 2px 0 0;
+ font-size: 0.6875rem;
+ color: var(--text-muted);
}
-.mcp-stats-ring-wrap {
- position: relative;
- width: 56px;
- height: 56px;
- flex-shrink: 0;
-}
-
-.mcp-stats-ring-svg {
- width: 56px;
- height: 56px;
- transform: rotate(-90deg);
-}
-
-.mcp-stats-ring-track {
- stroke: rgba(0, 0, 0, 0.08);
-}
-
-.mcp-stats-ring-fill {
- stroke: #14b8a6;
- stroke-linecap: round;
- transition: stroke-dashoffset 0.5s ease, stroke 0.2s ease;
-}
-
-.mcp-stats-ring-fill.is-warning { stroke: #eab308; }
-.mcp-stats-ring-fill.is-danger { stroke: #ef4444; }
-
-.mcp-stats-split {
+.mcp-stats-panel__body {
display: grid;
- grid-template-columns: minmax(0, 1.15fr) minmax(260px, 0.85fr);
- gap: 16px;
+ grid-template-columns: minmax(0, 1fr) 272px;
align-items: stretch;
}
-@media (max-width: 1024px) {
- .mcp-stats-split {
+@media (max-width: 900px) {
+ .mcp-stats-panel__body {
grid-template-columns: 1fr;
}
-
- .mcp-stats-dist-body--stacked {
- grid-template-columns: 1fr;
- grid-template-rows: auto minmax(0, 1fr);
- }
-
- .mcp-stats-dist-chart-stage {
- width: 100%;
- min-width: 0;
- }
-
- .mcp-stats-dist-chart-wrap {
- width: min(228px, 100%);
- height: auto;
- aspect-ratio: 1;
- }
-
- .mcp-stats-dist-legend--grid {
- justify-content: flex-start;
- gap: 5px;
- }
-
- .mcp-stats-dist-legend-item-wrap,
- .mcp-stats-dist-legend--grid > .mcp-stats-dist-legend-item {
- flex: 0 0 auto;
- }
-
- .mcp-stats-tool-list {
- justify-content: flex-start;
- }
}
-.mcp-stats-split-left,
-.mcp-stats-split-right {
+.mcp-stats-panel__table-wrap {
min-width: 0;
- display: flex;
- flex-direction: column;
+ overflow-x: auto;
}
-.mcp-stats-insight-panel {
- display: flex;
- flex-direction: column;
- flex: 1;
- min-height: 100%;
-}
-
-.mcp-stats-insight-block {
- background: var(--bg-secondary);
- border: 1px solid var(--border-color);
- border-radius: 14px;
- padding: 14px 16px;
-}
-
-/* 调用分布:左圆图 + 右图例,与左侧 Top6 同高;固定正方形尺寸,避免 cqh 失效导致饼图消失 */
-.mcp-stats-split-right {
- align-items: stretch;
-}
-
-.mcp-stats-dist-panel.mcp-stats-tools-panel {
- gap: 8px;
- padding: 12px 14px;
- flex: 1;
- min-height: 100%;
+.mcp-stats-tool-table {
width: 100%;
+ border-collapse: collapse;
+ font-size: 0.8125rem;
}
-.mcp-stats-dist-panel .mcp-stats-tools-header {
- flex-shrink: 0;
- gap: 8px;
+.mcp-stats-tool-table thead {
+ background: #f8fafc;
}
-.mcp-stats-dist-panel .mcp-stats-tools-legend {
- display: -webkit-box;
- -webkit-line-clamp: 2;
- line-clamp: 2;
- -webkit-box-orient: vertical;
+.mcp-stats-tool-table th {
+ padding: 8px 14px;
+ font-size: 0.6875rem;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.04em;
+ color: var(--text-muted);
+ text-align: left;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.06);
+ white-space: nowrap;
+}
+
+.mcp-stats-tool-table td {
+ padding: 9px 14px;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.04);
+ vertical-align: middle;
+}
+
+.mcp-stats-tool-table tbody tr:last-child td {
+ border-bottom: none;
+}
+
+.mcp-stats-tool-table .col-rank {
+ width: 44px;
+ text-align: center;
+}
+
+.mcp-stats-tool-table .col-tool {
+ min-width: 140px;
+ max-width: 360px;
+}
+
+.mcp-stats-tool-table .col-num,
+.mcp-stats-tool-table .col-share {
+ width: 64px;
+ text-align: right;
+ font-variant-numeric: tabular-nums;
+ white-space: nowrap;
+}
+
+.mcp-stats-tool-table .col-num {
+ font-weight: 600;
+ color: var(--text-primary);
+}
+
+.mcp-stats-tool-table .col-share {
+ color: var(--text-secondary);
+ font-weight: 500;
+}
+
+.mcp-stats-tool-table .col-rate {
+ width: 108px;
+ text-align: right;
+ white-space: nowrap;
+}
+
+.mcp-stats-tool-table th.col-num,
+.mcp-stats-tool-table th.col-share,
+.mcp-stats-tool-table th.col-rate {
+ text-align: right;
+}
+
+.mcp-stats-tool-dot {
+ display: inline-block;
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ margin-right: 8px;
+ vertical-align: middle;
+}
+
+.mcp-stats-tool-label {
+ vertical-align: middle;
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
+ font-size: 0.8125rem;
+ font-weight: 500;
+}
+
+.mcp-stats-tool-table .col-tool {
overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
-.mcp-stats-dist-body--stacked {
- flex: 1 1 auto;
- display: grid;
- grid-template-columns: minmax(168px, 40%) minmax(0, 1fr);
- gap: 10px;
- align-items: stretch;
- min-height: 0;
- width: 100%;
+.mcp-stats-rank {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 22px;
+ height: 22px;
+ border-radius: 6px;
+ background: rgba(0, 0, 0, 0.05);
+ color: var(--text-secondary);
+ font-size: 0.75rem;
+ font-weight: 700;
}
-.mcp-stats-dist-chart-stage {
+.mcp-stats-rank.rank-1 { background: rgba(234, 179, 8, 0.18); color: #a16207; }
+.mcp-stats-rank.rank-2 { background: rgba(148, 163, 184, 0.22); color: #64748b; }
+.mcp-stats-rank.rank-3 { background: rgba(180, 83, 9, 0.12); color: #b45309; }
+
+.mcp-stats-rate { font-weight: 600; font-variant-numeric: tabular-nums; }
+.mcp-stats-rate.is-success { color: #15803d; }
+.mcp-stats-rate.is-warning { color: #ca8a04; }
+.mcp-stats-rate.is-danger { color: #dc2626; }
+
+.mcp-stats-fail-note {
+ margin-left: 6px;
+ font-size: 0.6875rem;
+ color: #b91c1c;
+ font-weight: 500;
+}
+
+tr.mcp-stats-tool-row[data-tool-name] {
+ cursor: pointer;
+ transition: background 0.12s ease;
+}
+
+tr.mcp-stats-tool-row[data-tool-name]:hover,
+tr.mcp-stats-tool-row[data-tool-name].is-highlighted {
+ background: rgba(0, 102, 255, 0.04);
+}
+
+tr.mcp-stats-tool-row[data-tool-name].is-active {
+ background: rgba(0, 102, 255, 0.07);
+}
+
+tr.mcp-stats-tool-row[data-tool-name].is-dimmed {
+ opacity: 0.4;
+}
+
+tr.mcp-stats-tool-row[data-tool-name]:focus-visible {
+ outline: 2px solid rgba(0, 102, 255, 0.45);
+ outline-offset: -2px;
+}
+
+/* 饼图侧栏 */
+.mcp-stats-panel__aside {
display: flex;
align-items: center;
justify-content: center;
- min-width: 0;
- min-height: 0;
+ padding: 16px;
+ border-left: 1px solid rgba(0, 0, 0, 0.06);
+ background: linear-gradient(180deg, #f8fafc 0%, #fff 100%);
}
-.mcp-stats-dist-chart-wrap {
+@media (max-width: 900px) {
+ .mcp-stats-panel__aside {
+ border-left: none;
+ border-top: 1px solid rgba(0, 0, 0, 0.06);
+ padding: 20px 16px 24px;
+ }
+}
+
+.mcp-stats-dist-panel--compact {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 10px;
+ width: 100%;
+ max-width: 220px;
+ padding: 0;
+}
+
+.mcp-stats-panel__aside-title {
+ margin: 0;
+ font-size: 0.75rem;
+ font-weight: 600;
+ color: var(--text-secondary);
+ text-align: center;
+}
+
+.mcp-stats-panel__chart {
position: relative;
width: 100%;
- max-width: 212px;
+ max-width: 196px;
aspect-ratio: 1;
- height: auto;
- flex-shrink: 0;
+}
+
+.mcp-stats-panel__aside-hint {
+ margin: 0;
+ font-size: 0.6875rem;
+ color: var(--text-muted);
+ text-align: center;
+ line-height: 1.35;
}
.mcp-stats-dist-svg {
display: block;
width: 100%;
height: 100%;
- filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.08));
}
.mcp-stats-dist-segment {
@@ -6425,24 +7233,10 @@ header {
outline: none;
}
-.mcp-stats-dist-segment.is-others {
- cursor: default;
-}
-
-.mcp-stats-dist-segment.is-dimmed {
- opacity: 0.45;
-}
-
-.mcp-stats-dist-segment.is-highlighted {
- opacity: 1;
- filter: brightness(1.06);
-}
-
-.mcp-stats-dist-segment.is-active {
- filter: brightness(1.08);
- stroke: rgba(0, 102, 255, 0.55);
- stroke-width: 0.6;
-}
+.mcp-stats-dist-segment.is-others { cursor: default; }
+.mcp-stats-dist-segment.is-dimmed { opacity: 0.35; }
+.mcp-stats-dist-segment.is-highlighted,
+.mcp-stats-dist-segment.is-active { filter: brightness(1.06); }
.mcp-stats-dist-segment:focus-visible {
outline: 2px solid rgba(0, 102, 255, 0.45);
@@ -6451,509 +7245,76 @@ header {
.mcp-stats-dist-donut-hole {
position: absolute;
- inset: 22%;
+ inset: 27%;
border-radius: 50%;
- background: var(--bg-primary);
+ background: #fff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
- gap: 2px;
- box-shadow: inset 0 0 0 1px var(--border-color);
+ gap: 0;
+ box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.05);
pointer-events: none;
}
.mcp-stats-dist-donut-label {
- font-size: 0.6875rem;
+ font-size: 0.5625rem;
font-weight: 700;
text-transform: uppercase;
- letter-spacing: 0.04em;
+ letter-spacing: 0.05em;
color: var(--text-muted);
- max-width: 92%;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
text-align: center;
}
.mcp-stats-dist-donut-label:not(.is-default) {
text-transform: none;
- font-size: 0.625rem;
- font-weight: 600;
+ font-size: 0.5rem;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
+ max-width: 90%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
.mcp-stats-dist-donut-value {
display: flex;
align-items: baseline;
- justify-content: center;
gap: 1px;
- font-size: 1.625rem;
+ font-size: 1.125rem;
font-weight: 800;
color: var(--text-primary);
font-variant-numeric: tabular-nums;
line-height: 1;
- letter-spacing: -0.04em;
}
.mcp-stats-dist-donut-unit {
- font-size: 0.875rem;
+ font-size: 0.625rem;
font-weight: 700;
color: var(--text-secondary);
}
-.mcp-stats-dist-legend--grid {
- list-style: none;
- margin: 0;
- padding: 0;
- display: flex;
- flex-direction: column;
- gap: 4px;
- min-width: 0;
- min-height: 0;
- height: 100%;
- justify-content: space-between;
-}
-
-.mcp-stats-dist-legend-item-wrap {
- list-style: none;
- margin: 0;
- padding: 0;
- width: 100%;
- flex: 1 1 0;
- min-height: 0;
- display: flex;
-}
-
-.mcp-stats-dist-legend--grid > .mcp-stats-dist-legend-item {
- flex: 1 1 0;
- min-height: 0;
-}
-
-.mcp-stats-dist-legend-item {
- display: grid;
- grid-template-columns: 4px minmax(0, 1fr) auto;
- grid-template-rows: auto;
- gap: 0 6px;
- align-items: center;
- align-content: center;
- width: 100%;
- height: 100%;
- min-height: 28px;
- padding: 4px 6px;
- border-radius: 8px;
- background: var(--bg-primary);
- border: 1px solid var(--border-color);
- transition: background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
- box-sizing: border-box;
- font: inherit;
- color: inherit;
- text-align: left;
- cursor: pointer;
-}
-
-.mcp-stats-dist-legend-item:hover,
-.mcp-stats-dist-legend-item.is-highlighted {
- border-color: rgba(0, 102, 255, 0.22);
- background: rgba(0, 102, 255, 0.03);
-}
-
-.mcp-stats-dist-legend-item.is-active {
- border-color: rgba(0, 102, 255, 0.35);
- background: rgba(0, 102, 255, 0.05);
- box-shadow: 0 0 0 2px rgba(0, 102, 255, 0.1);
-}
-
-.mcp-stats-dist-legend-item.is-dimmed {
- opacity: 0.5;
-}
-
-.mcp-stats-dist-legend-item:focus-visible {
- outline: 2px solid rgba(0, 102, 255, 0.45);
- outline-offset: 2px;
-}
-
-.mcp-stats-dist-legend-item.is-others {
- cursor: default;
- opacity: 0.72;
-}
-
-.mcp-stats-dist-legend-item.is-others:hover {
- border-color: var(--border-color);
- background: var(--bg-primary);
-}
-
-.mcp-stats-dist-swatch {
- grid-row: 1;
- grid-column: 1;
- align-self: stretch;
- width: 4px;
- min-height: 22px;
- border-radius: 2px;
- background: var(--swatch-color, #94a3b8);
-}
-
-.mcp-stats-dist-legend-name {
- grid-column: 2;
- grid-row: 1;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- color: var(--text-primary);
- font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
- font-size: 0.625rem;
- font-weight: 600;
- line-height: 1.25;
- min-width: 0;
-}
-
-.mcp-stats-dist-legend-meta {
- grid-column: 3;
- grid-row: 1;
- display: flex;
- align-items: center;
- gap: 5px;
- font-size: 0.625rem;
- font-variant-numeric: tabular-nums;
- white-space: nowrap;
- flex-shrink: 0;
-}
-
-.mcp-stats-dist-legend-meta em {
- font-style: normal;
- font-weight: 700;
- color: var(--text-primary);
-}
-
-.mcp-stats-dist-legend-meta span {
- color: var(--text-muted);
- font-weight: 500;
-}
-
-.mcp-stats-risk-list {
- list-style: none;
- margin: 0;
- padding: 0;
- display: flex;
- flex-direction: column;
- gap: 6px;
-}
-
-.mcp-stats-risk-btn {
- width: 100%;
- text-align: left;
- padding: 8px 10px;
- border-radius: 8px;
- border: 1px solid rgba(239, 68, 68, 0.2);
- background: rgba(254, 242, 242, 0.6);
- color: #b91c1c;
- font-size: 0.75rem;
- font-weight: 500;
- cursor: pointer;
- transition: background 0.15s ease, border-color 0.15s ease;
- font: inherit;
-}
-
-.mcp-stats-risk-btn:hover {
- background: rgba(254, 242, 242, 1);
- border-color: rgba(239, 68, 68, 0.35);
-}
-
-.mcp-stats-risk-empty,
-.mcp-stats-selected-empty {
- margin: 0;
- font-size: 0.75rem;
- color: var(--text-muted);
- line-height: 1.5;
-}
-
-.mcp-stats-selected-card {
- display: flex;
- flex-direction: column;
- gap: 8px;
-}
-
-.mcp-stats-selected-name {
- font-size: 0.8125rem;
- font-weight: 600;
- font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
- color: var(--text-primary);
- word-break: break-all;
-}
-
-.mcp-stats-selected-meta {
- margin: 0;
- font-size: 0.75rem;
- color: var(--text-secondary);
-}
-
-.mcp-stats-tools-panel {
- background: var(--bg-secondary);
- border: 1px solid var(--border-color);
- border-radius: 14px;
- padding: 16px 18px;
- display: flex;
- flex-direction: column;
- gap: 14px;
- flex: 1;
- min-height: 100%;
- box-sizing: border-box;
-}
-
-.mcp-stats-tools-header {
- display: flex;
- align-items: flex-start;
- justify-content: space-between;
- gap: 12px;
- flex-wrap: wrap;
-}
-
-.mcp-stats-tools-heading {
- display: flex;
- flex-direction: column;
- gap: 4px;
- min-width: 0;
-}
-
-.mcp-stats-tools-title {
- margin: 0;
- font-size: 0.875rem;
- font-weight: 600;
- color: var(--text-primary);
-}
-
-.mcp-stats-tools-legend {
- font-size: 0.6875rem;
- color: var(--text-muted);
- font-weight: 500;
- line-height: 1.4;
-}
-
-.mcp-stats-tools-hint {
- font-size: 0.75rem;
- color: var(--text-muted);
- font-weight: 500;
- white-space: nowrap;
- padding-top: 2px;
-}
-
-.mcp-stats-tool-list {
- list-style: none;
- margin: 0;
- padding: 0;
- display: flex;
- flex-direction: column;
- gap: 8px;
- flex: 1;
- justify-content: space-between;
-}
-
-.mcp-stats-tool-item {
- margin: 0;
- padding: 0;
-}
-
-.mcp-stats-tool-row {
- display: flex;
- align-items: center;
- gap: 12px;
- padding: 10px 12px;
- border-radius: 10px;
- border: 1px solid transparent;
- background: var(--bg-primary);
- cursor: pointer;
- transition: background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
- text-align: left;
- width: 100%;
- box-sizing: border-box;
- font: inherit;
- color: var(--text-primary);
-}
-
-.mcp-stats-tool-row:hover {
- border-color: rgba(0, 102, 255, 0.22);
- box-shadow: 0 2px 10px rgba(0, 102, 255, 0.1);
- transform: translateX(2px);
-}
-
-.mcp-stats-tool-row:hover .mcp-stats-tool-chevron {
- color: var(--accent-color);
- opacity: 1;
-}
-
-.mcp-stats-tool-row:focus-visible {
- outline: 2px solid rgba(0, 102, 255, 0.45);
- outline-offset: 2px;
-}
-
-.mcp-stats-tool-row.is-active {
- border-color: rgba(0, 102, 255, 0.35);
- background: rgba(0, 102, 255, 0.05);
- box-shadow: 0 0 0 2px rgba(0, 102, 255, 0.1);
-}
-
-.mcp-stats-tool-row.is-active .mcp-stats-tool-chevron {
- color: var(--accent-color);
- opacity: 1;
-}
-
-.mcp-stats-tool-rank {
- flex-shrink: 0;
- width: 24px;
- height: 24px;
- border-radius: 6px;
- background: rgba(0, 102, 255, 0.08);
- color: #2563eb;
- font-size: 0.75rem;
- font-weight: 700;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- font-variant-numeric: tabular-nums;
-}
-
-.mcp-stats-tool-item:first-child .mcp-stats-tool-rank {
- background: rgba(234, 179, 8, 0.15);
- color: #a16207;
-}
-
-.mcp-stats-tool-main {
- flex: 1;
- display: flex;
- flex-direction: column;
- gap: 8px;
- min-width: 0;
-}
-
-.mcp-stats-tool-top {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 10px;
- min-width: 0;
-}
-
-.mcp-stats-tool-name {
- font-size: 0.8125rem;
- font-weight: 600;
- color: #1a1a1a;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- min-width: 4rem;
- flex: 1 1 auto;
- font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
-}
-
-.mcp-stats-tool-metrics {
- display: flex;
- align-items: center;
- gap: 6px;
- font-size: 0.75rem;
- color: var(--text-secondary);
- font-variant-numeric: tabular-nums;
- white-space: nowrap;
- flex-shrink: 0;
-}
-
-.mcp-stats-tool-count {
- font-weight: 600;
- color: var(--text-primary);
-}
-
-.mcp-stats-tool-rate.is-success { color: #15803d; font-weight: 600; }
-.mcp-stats-tool-rate.is-warning { color: #ca8a04; font-weight: 600; }
-.mcp-stats-tool-rate.is-danger { color: #dc2626; font-weight: 600; }
-
-.mcp-stats-tool-fail-badge {
- display: inline-flex;
- padding: 1px 6px;
- border-radius: 999px;
- font-size: 0.625rem;
- font-weight: 600;
- background: rgba(239, 68, 68, 0.1);
- color: #b91c1c;
-}
-
-.mcp-stats-tool-chevron {
- flex-shrink: 0;
- color: var(--text-muted);
- opacity: 0.45;
- transition: color 0.15s ease, opacity 0.15s ease, transform 0.15s ease;
-}
-
-.mcp-stats-tool-row:hover .mcp-stats-tool-chevron {
- transform: translateX(2px);
-}
-
-.mcp-stats-tool-bar-track {
- width: 100%;
- height: 8px;
- border-radius: 999px;
- background: rgba(0, 0, 0, 0.06);
- overflow: hidden;
-}
-
-.mcp-stats-tool-bar-fill {
- height: 100%;
- min-width: 3px;
- border-radius: 999px;
- overflow: hidden;
- transition: width 0.4s ease;
-}
-
-.mcp-stats-tool-bar-inner {
- display: flex;
- width: 100%;
- height: 100%;
- gap: 1px;
-}
-
-.mcp-stats-tool-bar-seg {
- height: 100%;
- min-width: 0;
- transition: width 0.35s ease;
-}
-
-.mcp-stats-tool-bar-seg--success {
- background: linear-gradient(90deg, #22c55e, #16a34a);
-}
-
-.mcp-stats-tool-bar-seg--fail {
- background: linear-gradient(90deg, #f87171, #dc2626);
-}
-
.mcp-stats-clear-filter {
- align-self: flex-start;
- padding: 4px 12px;
+ padding: 5px 12px;
font-size: 0.75rem;
- border-radius: 999px;
- border: 1px solid var(--border-color);
- background: var(--bg-primary);
+ border-radius: 6px;
+ border: 1px solid rgba(0, 102, 255, 0.25);
+ background: #fff;
color: var(--accent-color);
cursor: pointer;
font-weight: 500;
- transition: background 0.15s ease, border-color 0.15s ease;
+ font: inherit;
+ flex-shrink: 0;
}
.mcp-stats-clear-filter:hover {
background: rgba(0, 102, 255, 0.06);
- border-color: rgba(0, 102, 255, 0.25);
}
@media (prefers-reduced-motion: reduce) {
- .mcp-stats-tool-row,
- .mcp-stats-stacked-bar-seg,
- .mcp-stats-tool-bar-seg,
- .mcp-stats-tool-bar-fill,
- .mcp-stats-tool-chevron,
- .mcp-stats-ring-fill {
+ tr.mcp-stats-tool-row[data-tool-name],
+ .mcp-stats-dist-segment {
transition: none;
}
-
- .mcp-stats-tool-row:hover {
- transform: none;
- }
}
/* 兼容 Skills 监控等复用 monitor-stats-grid 的页面 */
@@ -7017,6 +7378,36 @@ header {
overflow-x: auto;
}
+.mcp-monitor-page .monitor-executions {
+ padding: 14px 16px;
+}
+
+.mcp-monitor-page .monitor-executions .section-header {
+ margin-bottom: 10px;
+}
+
+.mcp-monitor-page .monitor-table th,
+.mcp-monitor-page .monitor-table td {
+ padding: 8px 12px;
+}
+
+.mcp-monitor-page .monitor-table-container {
+ border: 1px solid rgba(0, 0, 0, 0.06);
+ border-radius: 12px;
+ box-shadow: none;
+}
+
+.mcp-monitor-page .monitor-table thead {
+ background: rgba(0, 0, 0, 0.02);
+}
+
+.mcp-monitor-page .monitor-empty {
+ padding: 48px 24px;
+ text-align: center;
+ color: var(--text-muted);
+ font-size: 0.875rem;
+}
+
.monitor-table {
width: 100%;
border-collapse: collapse;
diff --git a/web/static/i18n/en-US.json b/web/static/i18n/en-US.json
index 1ab0e671..b4230512 100644
--- a/web/static/i18n/en-US.json
+++ b/web/static/i18n/en-US.json
@@ -1514,6 +1514,17 @@
"unknownTool": "Unknown tool",
"successFailedRate": "Success {{success}} / Failed {{failed}} · {{rate}}% success rate",
"topToolsTitle": "Top {{n}} tools by calls",
+ "toolRankingTitle": "Tool call ranking",
+ "toolStatsTitle": "Tool statistics",
+ "toolStatsHint": "Click a bar segment or row to filter records below; hover to highlight",
+ "scopeCumulative": "All time",
+ "scopeTimeline": "Trend period",
+ "filterActive": "Filtered: {{tool}}",
+ "kpiScopeNote": "Lifetime totals",
+ "columnCalls": "Calls",
+ "columnShare": "Share",
+ "columnSuccessRate": "Success rate",
+ "rankingSummary": "Top {{n}} {{pct}}% · {{total}} total calls",
"barVolumeLegend": "Bar length: relative call volume; green/red: success vs failure share",
"clickToFilterTool": "Click a row to filter records below",
"toolRowAriaLabel": "{{name}}, {{total}} calls, {{rate}}% success rate. Click to filter records.",
@@ -1526,9 +1537,21 @@
"rateWarning": "Some failures detected",
"rateCritical": "High failure rate",
"statsSubtitle": "Refreshed {{time}} · {{count}} tools",
+ "timelineTitle": "Call trend",
+ "timelineHint": "All tools combined (not split by tool)",
+ "timelineRange24h": "24h",
+ "timelineRange7d": "7d",
+ "timelineRange30d": "30d",
+ "timelineSummary": "{{total}} calls in range · peak {{peak}}",
+ "timelineSparseHint": "Most buckets are empty; peak {{peak}} calls at {{peakTime}}",
+ "timelineNoData": "No calls in this period",
+ "timelineLoadError": "Failed to load call trend",
+ "timelineTotalLegend": "Total calls",
+ "timelineFailedLegend": "Failed",
+ "timelineTooltip": "{{time}}: {{total}} calls ({{failed}} failed)",
"distTitle": "Call distribution",
"distLegend": "Slice area shows share of all calls",
- "distClickHint": "Click legend or slice to filter records",
+ "distClickHint": "Click a bar segment to filter records",
"distHeaderHint": "{{n}} total calls",
"distSegmentAria": "{{name}}, {{pct}}% of calls, {{calls}} times",
"distOthersNoFilter": "Other tools cannot be filtered individually",
diff --git a/web/static/i18n/zh-CN.json b/web/static/i18n/zh-CN.json
index 0af6f76b..ab653a16 100644
--- a/web/static/i18n/zh-CN.json
+++ b/web/static/i18n/zh-CN.json
@@ -1503,6 +1503,17 @@
"unknownTool": "未知工具",
"successFailedRate": "成功 {{success}} / 失败 {{failed}} · 成功率 {{rate}}%",
"topToolsTitle": "工具调用 Top {{n}}",
+ "toolRankingTitle": "工具调用排行",
+ "toolStatsTitle": "工具统计",
+ "toolStatsHint": "点击色条或列表行筛选下方执行记录;悬停联动高亮",
+ "scopeCumulative": "累计",
+ "scopeTimeline": "趋势时段",
+ "filterActive": "已筛选:{{tool}}",
+ "kpiScopeNote": "累计统计(全时段)",
+ "columnCalls": "调用",
+ "columnShare": "占比",
+ "columnSuccessRate": "成功率",
+ "rankingSummary": "Top {{n}} 占 {{pct}}% · 共 {{total}} 次调用",
"barVolumeLegend": "条长表示相对调用量,条内绿/红为成功/失败占比",
"clickToFilterTool": "点击行筛选下方执行记录",
"toolRowAriaLabel": "{{name}},{{total}} 次调用,成功率 {{rate}}%,点击查看执行记录",
@@ -1515,9 +1526,21 @@
"rateWarning": "存在失败调用",
"rateCritical": "失败率偏高",
"statsSubtitle": "最后刷新 {{time}} · 共 {{count}} 个工具",
+ "timelineTitle": "调用趋势",
+ "timelineHint": "全部工具合计,不按工具拆分",
+ "timelineRange24h": "24 小时",
+ "timelineRange7d": "7 天",
+ "timelineRange30d": "30 天",
+ "timelineSummary": "区间内 {{total}} 次 · 峰值 {{peak}}",
+ "timelineSparseHint": "该时段多数时间为 0,峰值 {{peak}} 次出现在 {{peakTime}}",
+ "timelineNoData": "该时段暂无调用",
+ "timelineLoadError": "无法加载调用趋势",
+ "timelineTotalLegend": "总调用",
+ "timelineFailedLegend": "失败",
+ "timelineTooltip": "{{time}}:{{total}} 次(失败 {{failed}})",
"distTitle": "调用分布",
"distLegend": "扇区面积为占全部调用比例",
- "distClickHint": "点击图例或扇区筛选执行记录",
+ "distClickHint": "点击色条筛选执行记录",
"distHeaderHint": "共 {{n}} 次调用",
"distSegmentAria": "{{name}},占 {{pct}}%,{{calls}} 次",
"distOthersNoFilter": "其他工具无法单独筛选",
diff --git a/web/static/js/monitor.js b/web/static/js/monitor.js
index a52ea6cf..a3d65ef8 100644
--- a/web/static/js/monitor.js
+++ b/web/static/js/monitor.js
@@ -3329,6 +3329,9 @@ let monitorPanelFetchSeq = 0;
const monitorState = {
executions: [],
stats: {},
+ timeline: null,
+ timelineRange: null,
+ timelineError: null,
lastFetchedAt: null,
pagination: {
page: 1,
@@ -3415,17 +3418,15 @@ async function refreshMonitorPanel(page = null) {
url += `&tool=${encodeURIComponent(currentToolFilter)}`;
}
- const response = await apiFetch(url, { method: 'GET' });
- const result = await response.json().catch(() => ({}));
- if (!response.ok) {
- throw new Error(result.error || '获取监控数据失败');
- }
+ const { result, timeline, timelineError } = await fetchMonitorAndTimeline(url);
if (mySeq !== monitorPanelFetchSeq) {
return;
}
monitorState.executions = Array.isArray(result.executions) ? result.executions : [];
monitorState.stats = result.stats || {};
+ monitorState.timeline = timeline;
+ monitorState.timelineError = timelineError;
monitorState.lastFetchedAt = new Date();
// 更新分页信息
@@ -3499,17 +3500,15 @@ async function refreshMonitorPanelWithFilter(statusFilter = 'all', toolFilter =
url += `&tool=${encodeURIComponent(toolFilter)}`;
}
- const response = await apiFetch(url, { method: 'GET' });
- const result = await response.json().catch(() => ({}));
- if (!response.ok) {
- throw new Error(result.error || '获取监控数据失败');
- }
+ const { result, timeline, timelineError } = await fetchMonitorAndTimeline(url);
if (mySeq !== monitorPanelFetchSeq) {
return;
}
monitorState.executions = Array.isArray(result.executions) ? result.executions : [];
monitorState.stats = result.stats || {};
+ monitorState.timeline = timeline;
+ monitorState.timelineError = timelineError;
monitorState.lastFetchedAt = new Date();
// 更新分页信息
@@ -3541,6 +3540,399 @@ async function refreshMonitorPanelWithFilter(statusFilter = 'all', toolFilter =
const MCP_STATS_TOP_N = 6;
+const MCP_TIMELINE_RANGES = ['24h', '7d', '30d'];
+
+function getMcpMonitorTimelineRange() {
+ if (monitorState.timelineRange && MCP_TIMELINE_RANGES.includes(monitorState.timelineRange)) {
+ return monitorState.timelineRange;
+ }
+ const saved = localStorage.getItem('mcpMonitorTimelineRange');
+ const range = MCP_TIMELINE_RANGES.includes(saved) ? saved : '7d';
+ monitorState.timelineRange = range;
+ return range;
+}
+
+async function fetchMonitorAndTimeline(monitorUrl) {
+ const range = getMcpMonitorTimelineRange();
+ const [monitorResp, timelineResp] = await Promise.all([
+ apiFetch(monitorUrl, { method: 'GET' }),
+ apiFetch(`/api/monitor/calls-timeline?range=${encodeURIComponent(range)}`, { method: 'GET' })
+ ]);
+ const result = await monitorResp.json().catch(() => ({}));
+ if (!monitorResp.ok) {
+ throw new Error(result.error || '获取监控数据失败');
+ }
+ let timeline = null;
+ let timelineError = null;
+ try {
+ const timelineJson = await timelineResp.json().catch(() => ({}));
+ if (timelineResp.ok) {
+ timeline = timelineJson;
+ } else {
+ timelineError = timelineJson.error || 'timeline failed';
+ }
+ } catch (err) {
+ timelineError = err && err.message ? err.message : 'timeline failed';
+ }
+ return { result, timeline, timelineError };
+}
+
+function formatMcpTimelineLabel(isoOrDate, rangeKey, locale) {
+ const d = isoOrDate instanceof Date ? isoOrDate : new Date(isoOrDate);
+ if (Number.isNaN(d.getTime())) return '';
+ if (rangeKey === '24h') {
+ return d.toLocaleTimeString(locale, { hour: '2-digit', minute: '2-digit' });
+ }
+ if (rangeKey === '30d') {
+ return d.toLocaleDateString(locale, { month: 'numeric', day: 'numeric' });
+ }
+ return d.toLocaleString(locale, { month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit' });
+}
+
+function buildMcpTimelineSvg(points, rangeKey) {
+ if (!Array.isArray(points) || points.length === 0) return '';
+ const W = 400;
+ const H = 140;
+ const padL = 32;
+ const padR = 8;
+ const padT = 12;
+ const padB = 24;
+ const plotW = W - padL - padR;
+ const plotH = H - padT - padB;
+ const maxVal = Math.max(1, ...points.map((p) => p.total || 0));
+ const hasFailed = points.some((p) => (p.failed || 0) > 0);
+ const locale = (typeof window.__locale === 'string' && window.__locale.startsWith('zh')) ? 'zh-CN' : 'en-US';
+
+ const coords = points.map((p, i) => {
+ const x = padL + (points.length <= 1 ? plotW / 2 : (i / (points.length - 1)) * plotW);
+ const y = padT + plotH - ((p.total || 0) / maxVal) * plotH;
+ return { x, y, p, i };
+ });
+
+ const linePath = coords.map((c, i) => `${i === 0 ? 'M' : 'L'} ${c.x.toFixed(2)} ${c.y.toFixed(2)}`).join(' ');
+ const baseY = padT + plotH;
+ const areaPath = `${linePath} L ${coords[coords.length - 1].x.toFixed(2)} ${baseY} L ${coords[0].x.toFixed(2)} ${baseY} Z`;
+
+ let failPath = '';
+ if (hasFailed) {
+ failPath = coords.map((c, i) => {
+ const fy = padT + plotH - ((c.p.failed || 0) / maxVal) * plotH;
+ return `${i === 0 ? 'M' : 'L'} ${c.x.toFixed(2)} ${fy.toFixed(2)}`;
+ }).join(' ');
+ }
+
+ let peakIdx = 0;
+ points.forEach((p, i) => {
+ if ((p.total || 0) >= (points[peakIdx].total || 0)) peakIdx = i;
+ });
+
+ const yTicks = [0, Math.ceil(maxVal / 2), maxVal];
+ const yLines = yTicks.map((v) => {
+ const y = padT + plotH - (v / maxVal) * plotH;
+ const isBase = v === 0;
+ return `
${escapeHtml(errText)}:${escapeHtml(timelineError)}
`; + } + + const points = timeline && Array.isArray(timeline.points) ? timeline.points : []; + const summaryTotal = timeline && timeline.summary ? (timeline.summary.totalCalls || 0) : 0; + const peak = timeline && timeline.summary ? (timeline.summary.peak || 0) : 0; + const summaryText = mcpMonitorT('timelineSummary', { total: summaryTotal, peak }) + || `区间内 ${summaryTotal} 次 · 峰值 ${peak}`; + + if (points.length === 0 || summaryTotal === 0) { + const noData = mcpMonitorT('timelineNoData') || '该时段暂无调用'; + return `${escapeHtml(noData)}
`; + } + + const rangeKey = timeline.range || getMcpMonitorTimelineRange(); + const chartSvg = buildMcpTimelineSvg(points, rangeKey); + const totalLegend = mcpMonitorT('timelineTotalLegend') || '总调用'; + const failLegend = mcpMonitorT('timelineFailedLegend') || '失败'; + const hasFailed = points.some((p) => (p.failed || 0) > 0); + const sparseHint = buildTimelineSparseHint(points, timeline); + const sparseHtml = sparseHint + ? `${escapeHtml(sparseHint)}
` + : ''; + + return ` + +${escapeHtml(timelineTitle)}
+| # | +${escapeHtml(colTool)} | +${escapeHtml(colCalls)} | +${escapeHtml(colShare)} | +${escapeHtml(colRate)} | +
|---|
+ ${escapeHtml(mcpMonitorT('scopeCumulative') || '累计')} + ${escapeHtml(caption)} +
+${escapeHtml(distTitle)}
+${escapeHtml(distClickHint)}
+