diff --git a/web/static/css/style.css b/web/static/css/style.css index f433429b..1c77d1d4 100644 --- a/web/static/css/style.css +++ b/web/static/css/style.css @@ -7938,177 +7938,162 @@ header { color: var(--text-primary); } -/* 仪表盘页面样式 */ +/* 仪表盘页面样式(最佳实践布局 + 视觉增强) */ .dashboard-page { height: 100%; display: flex; flex-direction: column; overflow: auto; - background: linear-gradient(180deg, #f0f4ff 0%, #f8fafc 24%, var(--bg-secondary) 100%); + background: linear-gradient(180deg, #eef2f7 0%, #f1f5f9 50%, #e2e8f0 100%); } .dashboard-page .page-header { flex-shrink: 0; + padding: 18px 24px; + border-bottom: 1px solid rgba(0,0,0,0.06); + background: rgba(255,255,255,0.95); + backdrop-filter: blur(10px); + box-shadow: 0 1px 0 rgba(255,255,255,0.8) inset; +} + +.dashboard-page .page-header h2 { + font-size: 1.25rem; + font-weight: 700; + letter-spacing: -0.02em; + margin: 0; } .dashboard-content { flex: 1; - padding: 20px 24px 32px; + padding: 24px; overflow: auto; + width: 100%; + box-sizing: border-box; } -.dashboard-cards { +/* 第一行:核心 KPI(视觉层次 + 色块强调) */ +.dashboard-kpi-row { display: grid; - grid-template-columns: repeat(3, 1fr); + grid-template-columns: repeat(4, 1fr); gap: 20px; - margin-bottom: 28px; + margin-bottom: 24px; } @media (max-width: 900px) { - .dashboard-cards { grid-template-columns: 1fr; } + .dashboard-kpi-row { grid-template-columns: repeat(2, 1fr); } } -.dashboard-card { - position: relative; - background: var(--bg-primary); - border-radius: 16px; - padding: 24px; - display: flex; - align-items: center; - gap: 20px; - cursor: pointer; - transition: transform 0.25s ease, box-shadow 0.25s ease; - box-shadow: 0 4px 14px rgba(0,0,0,0.06), 0 1px 3px rgba(0,0,0,0.04); - border: 1px solid rgba(0,0,0,0.06); - overflow: hidden; +@media (max-width: 480px) { + .dashboard-kpi-row { grid-template-columns: 1fr; } } -.dashboard-card:hover { - transform: translateY(-4px); - box-shadow: 0 12px 28px rgba(0,0,0,0.1), 0 4px 12px rgba(0,0,0,0.06); -} - -.dashboard-card-glow { - position: absolute; - top: -40%; - right: -20%; - width: 60%; - height: 100%; - border-radius: 50%; - opacity: 0.08; - pointer-events: none; -} - -.dashboard-card-tasks .dashboard-card-glow { background: radial-gradient(circle, #0066ff 0%, transparent 70%); } -.dashboard-card-vulns .dashboard-card-glow { background: radial-gradient(circle, #dc3545 0%, transparent 70%); } -.dashboard-card-chat .dashboard-card-glow { background: radial-gradient(circle, #0d9488 0%, transparent 70%); } - -.dashboard-card-icon { - width: 56px; - height: 56px; +.dashboard-kpi-card { + background: #fff; border-radius: 14px; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - transition: transform 0.25s ease; + padding: 22px; + cursor: pointer; + transition: transform 0.2s ease, box-shadow 0.25s ease; + 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); + position: relative; } -.dashboard-card:hover .dashboard-card-icon { - transform: scale(1.08); +.dashboard-kpi-card:nth-child(1) { background: linear-gradient(145deg, #fff 0%, #f0f9ff 100%); } +.dashboard-kpi-card:nth-child(2) { background: linear-gradient(145deg, #fff 0%, #fef2f2 100%); } +.dashboard-kpi-card:nth-child(3) { background: linear-gradient(145deg, #fff 0%, #f0fdf4 100%); } +.dashboard-kpi-card:nth-child(4) { background: linear-gradient(145deg, #fff 0%, #f0fdfa 100%); } + +.dashboard-kpi-card:hover { + transform: translateY(-3px); + box-shadow: 0 12px 24px rgba(0,0,0,0.08), 0 4px 12px rgba(0,102,255,0.08); + border-color: rgba(0, 102, 255, 0.2); } -.dashboard-card-tasks .dashboard-card-icon { - background: linear-gradient(145deg, #e0edff 0%, #cce0ff 100%); - color: #0066ff; -} - -.dashboard-card-vulns .dashboard-card-icon { - background: linear-gradient(145deg, #ffebee 0%, #ffcdd2 100%); - color: #c62828; -} - -.dashboard-card-chat .dashboard-card-icon { - background: linear-gradient(145deg, #e0f2f1 0%, #b2dfdb 100%); - color: #0d9488; -} - -.dashboard-card-body { - min-width: 0; -} - -.dashboard-card-value { - font-size: 2rem; +.dashboard-kpi-value { + font-size: 1.875rem; font-weight: 800; color: var(--text-primary); - line-height: 1.1; + line-height: 1.2; letter-spacing: -0.03em; } -.dashboard-card-label { - font-size: 0.9rem; +.dashboard-kpi-label { + font-size: 0.8125rem; color: var(--text-secondary); - margin-top: 4px; + margin-top: 8px; font-weight: 500; } -.dashboard-card-desc { - font-size: 0.8rem; - color: var(--text-muted); - margin-top: 2px; +/* 两列主内容网格 */ +.dashboard-grid { + display: grid; + grid-template-columns: 1fr 380px; + gap: 24px; + align-items: start; } -.dashboard-card-cta { - font-size: 1.15rem; +@media (max-width: 1100px) { + .dashboard-grid { + grid-template-columns: 1fr; + } +} + +.dashboard-main { + display: flex; + flex-direction: column; + gap: 24px; +} + +.dashboard-side { + display: flex; + flex-direction: column; + gap: 24px; +} + +.dashboard-grid .dashboard-section { + margin-bottom: 0; + background: #fff; + border-radius: 14px; + padding: 22px; + box-shadow: 0 2px 12px rgba(0,0,0,0.06), 0 1px 4px rgba(0,0,0,0.04); + border: 1px solid rgba(0,0,0,0.05); + transition: box-shadow 0.25s ease; +} + +.dashboard-grid .dashboard-section:hover { + box-shadow: 0 8px 24px rgba(0,0,0,0.07), 0 2px 8px rgba(0,0,0,0.04); +} + +.dashboard-section-title { + display: block; + font-size: 0.9375rem; font-weight: 700; color: var(--text-primary); + margin: 0 0 16px 0; + padding-bottom: 10px; + border-bottom: 2px solid #f1f5f9; + letter-spacing: -0.01em; } -.dashboard-card-hint { - font-size: 0.75rem; - color: var(--text-muted); - margin-top: 6px; - opacity: 0.9; -} - -.dashboard-section { - margin-bottom: 28px; -} - -.dashboard-section-chart { - background: var(--bg-primary); - border-radius: 16px; - padding: 22px 24px; - box-shadow: 0 2px 12px rgba(0,0,0,0.04); - border: 1px solid rgba(0,0,0,0.05); -} - -.dashboard-section-overview { - background: var(--bg-primary); - border-radius: 16px; - padding: 22px 24px; - box-shadow: 0 2px 12px rgba(0,0,0,0.04); - border: 1px solid rgba(0,0,0,0.05); -} - -.dashboard-overview-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); - gap: 16px; +.dashboard-overview-list { + display: flex; + flex-direction: column; + gap: 12px; } .dashboard-overview-item { - padding: 14px 18px; - background: var(--bg-secondary); - border-radius: 12px; - border: 1px solid var(--border-color); + padding: 14px 16px; + background: linear-gradient(135deg, #fafbfc 0%, #f8fafc 100%); + border-radius: 10px; + border: 1px solid rgba(0,0,0,0.05); cursor: pointer; - transition: border-color 0.2s, background 0.2s; + transition: border-color 0.2s, background 0.2s, transform 0.2s; } .dashboard-overview-item:hover { - border-color: var(--accent-color); - background: rgba(0, 102, 255, 0.04); + border-color: rgba(0, 102, 255, 0.25); + background: linear-gradient(135deg, rgba(0,102,255,0.04) 0%, rgba(0,102,255,0.06) 100%); + transform: translateX(4px); } .dashboard-overview-label { @@ -8124,28 +8109,10 @@ header { color: var(--text-primary); } -.dashboard-section-title { - display: flex; - align-items: center; - gap: 10px; - font-size: 1rem; - font-weight: 600; - color: var(--text-primary); - margin: 0 0 16px 0; -} - -.dashboard-section-dot { - width: 6px; - height: 6px; - border-radius: 50%; - background: var(--accent-color); - flex-shrink: 0; -} - .dashboard-chart-wrap { display: flex; flex-direction: column; - gap: 16px; + gap: 20px; } .dashboard-stacked-bar { @@ -8154,26 +8121,27 @@ header { height: 28px; border-radius: 14px; overflow: hidden; - background: var(--bg-tertiary); - box-shadow: inset 0 1px 2px rgba(0,0,0,0.06); + background: #f1f5f9; } .dashboard-bar-seg { height: 100%; min-width: 2px; - transition: width 0.4s ease; + transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1); + opacity: 0.92; } -.dashboard-bar-seg.seg-critical { background: linear-gradient(90deg, #b91c1c, #dc2626); } -.dashboard-bar-seg.seg-high { background: linear-gradient(90deg, #ea580c, #f97316); } -.dashboard-bar-seg.seg-medium { background: linear-gradient(90deg, #ca8a04, #eab308); } -.dashboard-bar-seg.seg-low { background: linear-gradient(90deg, #059669, #10b981); } -.dashboard-bar-seg.seg-info { background: linear-gradient(90deg, #4b5563, #6b7280); } +.dashboard-bar-seg.seg-critical { background: linear-gradient(90deg, #fca5a5, #fecaca); } +.dashboard-bar-seg.seg-high { background: linear-gradient(90deg, #fdba74, #fed7aa); } +.dashboard-bar-seg.seg-medium { background: linear-gradient(90deg, #fde047, #fef08a); } +.dashboard-bar-seg.seg-low { background: linear-gradient(90deg, #6ee7b7, #a7f3d0); } +.dashboard-bar-seg.seg-info { background: linear-gradient(90deg, #cbd5e1, #e2e8f0); } .dashboard-legend { display: flex; flex-wrap: wrap; - gap: 20px 28px; + gap: 16px 32px; + align-items: center; } .dashboard-legend-item { @@ -8181,6 +8149,14 @@ header { align-items: center; gap: 8px; font-size: 0.875rem; + padding: 6px 12px; + background: #f8fafc; + border-radius: 20px; + transition: background 0.2s; +} + +.dashboard-legend-item:hover { + background: #f1f5f9; } .dashboard-legend-dot { @@ -8188,6 +8164,7 @@ header { height: 10px; border-radius: 50%; flex-shrink: 0; + box-shadow: 0 0 0 2px rgba(255,255,255,0.8); } .dashboard-legend-dot.critical { background: #dc2626; } @@ -8207,85 +8184,207 @@ header { } .dashboard-quick-links { + display: grid; + grid-template-columns: 1fr; + gap: 10px; +} + +/* 主内容区:快捷入口横向排列,按钮保持自然宽度不拉伸 */ +.dashboard-quick-links-row { display: flex; flex-wrap: wrap; gap: 12px; } +.dashboard-quick-inline .dashboard-quick-link { + flex: 0 0 auto; + width: auto; + min-width: unset; + padding: 8px 14px; +} + +/* 主内容区快捷入口:去掉图标外层的“涂层”,降低按钮高度 */ +.dashboard-quick-inline .dashboard-quick-icon { + width: auto; + height: auto; + min-width: unset; + padding: 0; + border-radius: 0; + background: transparent; + box-shadow: none; +} + +.dashboard-quick-inline .dashboard-quick-link:hover .dashboard-quick-icon { + background: transparent; + box-shadow: none; +} + +@media (max-width: 1000px) { + .dashboard-quick-links:not(.dashboard-quick-links-row) { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (max-width: 480px) { + .dashboard-quick-links:not(.dashboard-quick-links-row) { + grid-template-columns: 1fr; + } +} + .dashboard-quick-link { - display: inline-flex; + display: flex; align-items: center; gap: 10px; - padding: 12px 20px; - background: var(--bg-primary); - border: 1px solid var(--border-color); - border-radius: 12px; + padding: 10px 14px; + background: linear-gradient(135deg, #fafbfc 0%, #f8fafc 100%); + border: 1px solid rgba(0,0,0,0.06); + border-radius: 10px; color: var(--text-primary); - font-size: 0.9rem; + font-size: 0.875rem; font-weight: 500; text-decoration: none; cursor: pointer; - transition: all 0.2s ease; - box-shadow: 0 1px 4px rgba(0,0,0,0.04); + transition: all 0.25s ease; } .dashboard-quick-link:hover { - border-color: var(--accent-color); - background: rgba(0, 102, 255, 0.06); - color: var(--accent-color); - box-shadow: 0 4px 12px rgba(0, 102, 255, 0.12); + border-color: rgba(0, 102, 255, 0.3); + background: linear-gradient(135deg, rgba(0,102,255,0.06) 0%, rgba(0,102,255,0.04) 100%); + color: var(--text-primary); + transform: translateX(4px); + box-shadow: 0 2px 8px rgba(0,102,255,0.08); } .dashboard-quick-icon { display: flex; align-items: center; justify-content: center; + width: 36px; + height: 36px; + border-radius: 10px; + background: #fff; color: var(--text-secondary); + box-shadow: 0 1px 3px rgba(0,0,0,0.06); + transition: color 0.2s, background 0.2s, box-shadow 0.2s; } .dashboard-quick-link:hover .dashboard-quick-icon { - color: var(--accent-color); + color: #0369a1; + background: rgba(3, 105, 161, 0.1); + box-shadow: 0 2px 8px rgba(3, 105, 161, 0.15); +} + +/* 工具执行次数(仅柱状图) */ +.dashboard-section-tools .dashboard-section-title { + margin-bottom: 12px; +} + +.dashboard-tools-chart-wrap { + min-width: 0; + flex: 1; +} + +.dashboard-tools-chart-placeholder { + display: flex; + align-items: center; + justify-content: center; + min-height: 120px; + font-size: 0.8125rem; + color: var(--text-muted); + background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%); + border-radius: 10px; + border: 1px dashed #cbd5e1; +} + +.dashboard-tools-bar-chart { + display: flex; + flex-direction: column; + gap: 10px; +} + +.dashboard-tools-bar-item { + display: grid; + grid-template-columns: 82px 1fr 36px; + gap: 12px; + align-items: center; + font-size: 0.8125rem; +} + +.dashboard-tools-bar-label { + color: var(--text-secondary); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.dashboard-tools-bar-track { + height: 18px; + background: #f1f5f9; + border-radius: 9px; + overflow: hidden; +} + +.dashboard-tools-bar-fill { + height: 100%; + border-radius: 9px; + min-width: 6px; + transform-origin: left; + animation: dashboard-bar-fill-in 0.8s cubic-bezier(0.25, 0.1, 0.25, 1) forwards; + opacity: 0.92; +} + +@keyframes dashboard-bar-fill-in { + from { transform: scaleX(0); } + to { transform: scaleX(1); } +} + +.dashboard-tools-bar-value { + font-weight: 600; + color: var(--text-secondary); + font-size: 0.8125rem; + text-align: right; + font-variant-numeric: tabular-nums; } .dashboard-cta-block { - margin-top: 8px; - padding: 24px; - background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%); - border: 1px solid rgba(14, 165, 233, 0.2); - border-radius: 16px; -} - -.dashboard-cta-inner { + margin-top: 24px; + padding: 20px 24px; + background: linear-gradient(135deg, #fff 0%, #f8fafc 100%); + border: 1px solid rgba(0,0,0,0.06); + border-radius: 14px; display: flex; - flex-wrap: wrap; align-items: center; justify-content: space-between; - gap: 16px; + gap: 20px; + flex-wrap: wrap; + box-shadow: 0 4px 16px rgba(0,0,0,0.06), 0 1px 4px rgba(0,0,0,0.04); } .dashboard-cta-text { font-size: 1rem; - font-weight: 600; + font-weight: 700; color: var(--text-primary); margin: 0; + letter-spacing: -0.01em; } .dashboard-cta-btn { padding: 12px 24px; - background: linear-gradient(135deg, #0284c7 0%, #0369a1 100%); + background: linear-gradient(135deg, #0369a1 0%, #0284c7 50%, #0ea5e9 100%); border: none; - border-radius: 12px; + border-radius: 10px; color: #fff; - font-size: 0.95rem; + font-size: 0.9375rem; font-weight: 600; cursor: pointer; - transition: transform 0.15s, box-shadow 0.2s; - box-shadow: 0 4px 14px rgba(2, 132, 199, 0.35); + transition: transform 0.2s ease, box-shadow 0.25s ease, filter 0.2s; + box-shadow: 0 4px 14px rgba(2, 132, 199, 0.4); } .dashboard-cta-btn:hover { transform: translateY(-2px); - box-shadow: 0 6px 20px rgba(2, 132, 199, 0.4); + box-shadow: 0 8px 24px rgba(2, 132, 199, 0.45); + filter: brightness(1.05); } .vulnerability-controls { diff --git a/web/static/js/dashboard.js b/web/static/js/dashboard.js index d842264e..1d1f3d59 100644 --- a/web/static/js/dashboard.js +++ b/web/static/js/dashboard.js @@ -14,6 +14,12 @@ async function refreshDashboard() { if (barEl) barEl.style.width = '0%'; }); setDashboardOverviewPlaceholder('…'); + setEl('dashboard-kpi-tools-calls', '…'); + setEl('dashboard-kpi-success-rate', '…'); + var chartPlaceholder = document.getElementById('dashboard-tools-pie-placeholder'); + if (chartPlaceholder) { chartPlaceholder.style.display = 'block'; chartPlaceholder.textContent = '加载中…'; } + var barChartEl = document.getElementById('dashboard-tools-bar-chart'); + if (barChartEl) { barChartEl.style.display = 'none'; barChartEl.innerHTML = ''; } if (typeof apiFetch === 'undefined') { if (runningEl) runningEl.textContent = '-'; @@ -75,20 +81,31 @@ async function refreshDashboard() { setEl('dashboard-batch-done', '-'); } - // 工具调用:monitor/stats 为 { toolName: { TotalCalls, ... } } + // 工具调用:monitor/stats 为 { toolName: { totalCalls, successCalls, failedCalls, ... } } if (monitorRes && typeof monitorRes === 'object') { const names = Object.keys(monitorRes); - let totalCalls = 0; + let totalCalls = 0, totalSuccess = 0, totalFailed = 0; names.forEach(k => { const v = monitorRes[k]; const n = v && (v.totalCalls ?? v.TotalCalls); if (typeof n === 'number') totalCalls += n; + const s = v && (v.successCalls ?? v.SuccessCalls); + if (typeof s === 'number') totalSuccess += s; + const f = v && (v.failedCalls ?? v.FailedCalls); + if (typeof f === 'number') totalFailed += f; }); setEl('dashboard-tools-count', String(names.length)); setEl('dashboard-tools-calls', String(totalCalls)); + setEl('dashboard-kpi-tools-calls', String(totalCalls)); + var rateStr = totalCalls > 0 ? ((totalSuccess / totalCalls) * 100).toFixed(1) + '%' : '-'; + setEl('dashboard-kpi-success-rate', rateStr); + renderDashboardToolsBar(monitorRes); } else { setEl('dashboard-tools-count', '-'); setEl('dashboard-tools-calls', '-'); + setEl('dashboard-kpi-tools-calls', '-'); + setEl('dashboard-kpi-success-rate', '-'); + renderDashboardToolsBar(null); } // Skills:{ total_skills, total_calls, ... } @@ -104,6 +121,11 @@ async function refreshDashboard() { if (runningEl) runningEl.textContent = '-'; if (vulnTotalEl) vulnTotalEl.textContent = '-'; setDashboardOverviewPlaceholder('-'); + setEl('dashboard-kpi-success-rate', '-'); + setEl('dashboard-kpi-tools-calls', '-'); + renderDashboardToolsBar(null); + var ph = document.getElementById('dashboard-tools-pie-placeholder'); + if (ph) { ph.style.display = 'block'; ph.textContent = '暂无调用数据'; } } } @@ -116,3 +138,59 @@ function setDashboardOverviewPlaceholder(t) { ['dashboard-batch-pending', 'dashboard-batch-running', 'dashboard-batch-done', 'dashboard-tools-count', 'dashboard-tools-calls', 'dashboard-skills-count', 'dashboard-skills-calls'].forEach(id => setEl(id, t)); } + +// Top 30 工具执行次数柱状图颜色(柔和、低饱和度) +var DASHBOARD_BAR_COLORS = [ + '#93c5fd', '#a78bfa', '#6ee7b7', '#fde047', '#fda4af', + '#7dd3fc', '#a5b4fc', '#5eead4', '#fdba74', '#e9d5ff' +]; + +function esc(s) { + if (typeof s !== 'string') return ''; + return s.replace(/&/g, '&').replace(/ 0; }) + .sort(function (a, b) { return b.totalCalls - a.totalCalls; }) + .slice(0, 30); + + if (entries.length === 0) { + placeholder.style.display = 'block'; + barChartEl.style.display = 'none'; + barChartEl.innerHTML = ''; + return; + } + + placeholder.style.display = 'none'; + barChartEl.style.display = 'block'; + + const maxCalls = Math.max.apply(null, entries.map(function (e) { return e.totalCalls; })); + var html = ''; + entries.forEach(function (e, i) { + var pct = maxCalls > 0 ? (e.totalCalls / maxCalls) * 100 : 0; + var label = e.name.length > 12 ? e.name.slice(0, 10) + '…' : e.name; + var color = DASHBOARD_BAR_COLORS[i % DASHBOARD_BAR_COLORS.length]; + html += '
'; + }); + barChartEl.innerHTML = html; +} diff --git a/web/templates/index.html b/web/templates/index.html index 77e4bc7a..dc480c28 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -213,107 +213,88 @@准备好开始安全测试?
- -准备好开始安全测试?
+