diff --git a/web/static/css/style.css b/web/static/css/style.css
index d59f0b5c..8c760600 100644
--- a/web/static/css/style.css
+++ b/web/static/css/style.css
@@ -16049,17 +16049,10 @@ tr.mcp-stats-tool-row[data-tool-name]:focus-visible {
gap: 8px;
}
-.dashboard-recent-facts-meta {
- font-size: 0.75rem;
- color: var(--text-secondary, #6b7280);
- padding: 2px 4px 8px;
- font-variant-numeric: tabular-nums;
-}
-
.dashboard-recent-fact-item {
display: grid;
- /* 置顶 / 分类 / 置信度 固定列宽,保证各行对齐 */
- grid-template-columns: 20px 64px 56px minmax(0, 1.4fr) minmax(0, 1fr) 9.5rem;
+ /* 置顶 / 分类 / 置信度 / 摘要 / fact_key / 项目 / 时间 */
+ grid-template-columns: 20px 64px 56px minmax(0, 1.2fr) minmax(0, 1fr) 72px 9.5rem;
align-items: center;
column-gap: 10px;
padding: 12px 10px;
@@ -16097,6 +16090,35 @@ tr.mcp-stats-tool-row[data-tool-name]:focus-visible {
justify-self: center;
}
+.dashboard-recent-fact-project {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 72px;
+ box-sizing: border-box;
+ padding: 3px 6px;
+ border-radius: 6px;
+ font-size: 0.6875rem;
+ font-weight: 600;
+ line-height: 1.2;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ justify-self: start;
+ background: #fff;
+ border: 1px dashed transparent;
+}
+
+/* 项目:虚线描边 + 浅底,与置信度实心药丸区分 */
+.dashboard-recent-fact-project.proj-tone-0 { background: rgba(139, 92, 246, 0.05); color: #6d28d9; border-color: rgba(109, 40, 217, 0.45); }
+.dashboard-recent-fact-project.proj-tone-1 { background: rgba(59, 130, 246, 0.05); color: #1d4ed8; border-color: rgba(29, 78, 216, 0.45); }
+.dashboard-recent-fact-project.proj-tone-2 { background: rgba(20, 184, 166, 0.05); color: #0f766e; border-color: rgba(15, 118, 110, 0.45); }
+.dashboard-recent-fact-project.proj-tone-3 { background: rgba(245, 158, 11, 0.06); color: #b45309; border-color: rgba(180, 83, 9, 0.45); }
+.dashboard-recent-fact-project.proj-tone-4 { background: rgba(244, 63, 94, 0.05); color: #be123c; border-color: rgba(190, 18, 60, 0.45); }
+.dashboard-recent-fact-project.proj-tone-5 { background: rgba(99, 102, 241, 0.05); color: #4338ca; border-color: rgba(67, 56, 202, 0.45); }
+.dashboard-recent-fact-project.proj-tone-6 { background: rgba(16, 185, 129, 0.05); color: #047857; border-color: rgba(4, 120, 87, 0.45); }
+.dashboard-recent-fact-project.proj-tone-7 { background: rgba(236, 72, 153, 0.05); color: #be185d; border-color: rgba(190, 24, 93, 0.45); }
+
.dashboard-recent-fact-cat,
.dashboard-recent-fact-conf {
display: inline-flex;
@@ -16165,7 +16187,7 @@ tr.mcp-stats-tool-row[data-tool-name]:focus-visible {
white-space: nowrap;
}
-.dashboard-recent-fact-meta {
+.dashboard-recent-fact-key {
color: var(--text-secondary);
font-size: 0.8125rem;
min-width: 0;
@@ -16188,21 +16210,21 @@ tr.mcp-stats-tool-row[data-tool-name]:focus-visible {
@media (max-width: 900px) {
.dashboard-recent-fact-item {
- grid-template-columns: 20px 64px minmax(0, 1fr) auto 8.25rem;
+ grid-template-columns: 20px 64px minmax(0, 1fr) minmax(0, 0.8fr) 72px 8.25rem;
}
.dashboard-recent-fact-conf { display: none; }
}
@media (max-width: 720px) {
.dashboard-recent-fact-item {
- grid-template-columns: 20px 64px minmax(0, 1fr) auto;
+ grid-template-columns: 20px 64px minmax(0, 1fr) 72px auto;
}
- .dashboard-recent-fact-meta { display: none; }
+ .dashboard-recent-fact-key { display: none; }
}
@media (max-width: 480px) {
.dashboard-recent-fact-item {
- grid-template-columns: 20px minmax(0, 1fr) auto;
+ grid-template-columns: 20px 72px minmax(0, 1fr) auto;
}
.dashboard-recent-fact-cat { display: none; }
.dashboard-recent-fact-time { display: none; }
diff --git a/web/static/js/dashboard.js b/web/static/js/dashboard.js
index 457bc7a7..8eb3c32d 100644
--- a/web/static/js/dashboard.js
+++ b/web/static/js/dashboard.js
@@ -116,7 +116,7 @@ async function refreshDashboard() {
fetchJson('/api/monitor/stats'),
fetchJson('/api/knowledge/stats'),
fetchJson('/api/skills/stats'),
- fetchJson('/api/vulnerabilities?limit=5&page=1'),
+ fetchJson('/api/vulnerabilities?limit=10&page=1'),
fetchJson('/api/roles'),
fetchJson('/api/multi-agent/markdown-agents'),
openVulnQuery('critical'),
@@ -139,7 +139,7 @@ async function refreshDashboard() {
fetchJson('/api/c2/listeners'),
fetchJson('/api/c2/sessions?limit=500'),
fetchJson('/api/c2/tasks?page=1&page_size=1'),
- fetchJson('/api/projects/dashboard-summary?fact_limit=5')
+ fetchJson('/api/projects/dashboard-summary?fact_limit=10')
]);
// 如果在 await 期间 controller 已被 abort,说明又有新刷新启动了,丢弃本次结果
@@ -1117,7 +1117,7 @@ function renderRecentVulns(res) {
empty.classList.remove('is-rich');
}
- list.slice(0, 5).forEach(function (v) {
+ list.slice(0, 10).forEach(function (v) {
const sev = (v.severity || 'info').toLowerCase();
const status = (v.status || 'open').toLowerCase();
const item = document.createElement('a');
@@ -1211,6 +1211,18 @@ function factCategoryShortLabel(category) {
return raw || 'note';
}
+// 按 project_id(回退 project_name)稳定映射 8 种配色,同一项目跨刷新颜色一致
+function projectFactProjectTone(projectId, projectName) {
+ var key = String(projectId || projectName || '').trim();
+ if (!key) return 0;
+ var hash = 0;
+ for (var i = 0; i < key.length; i++) {
+ hash = ((hash << 5) - hash) + key.charCodeAt(i);
+ hash |= 0;
+ }
+ return Math.abs(hash) % 8;
+}
+
function openProjectFactFromDashboard(projectId, factKey) {
if (!projectId) return;
if (typeof switchPage === 'function') {
@@ -1289,15 +1301,7 @@ function renderRecentFacts(res) {
empty.classList.remove('is-rich');
}
- if (activeProjects > 0 || totalFacts > 0) {
- var meta = document.createElement('div');
- meta.className = 'dashboard-recent-facts-meta';
- meta.textContent = dt('dashboard.factsAcrossProjects', { count: activeProjects, facts: totalFacts },
- activeProjects + ' 个活跃项目 · ' + totalFacts + ' 条事实');
- wrap.appendChild(meta);
- }
-
- list.slice(0, 5).forEach(function (f) {
+ list.slice(0, 10).forEach(function (f) {
if (!f) return;
var category = factCategoryShortLabel(f.category);
var confidence = String(f.confidence || 'tentative').toLowerCase();
@@ -1319,17 +1323,17 @@ function renderRecentFacts(res) {
var pinMark = '' + (f.pinned ? '📌' : '') + '';
+ var projectLabel = (f.project_name || '').trim() || dt('projects.defaultProjectName', null, '项目');
+ var factKeyLabel = (f.fact_key || '').trim() || '—';
+ var projectTone = projectFactProjectTone(pid, projectLabel);
+ var projectCol = '' + esc(projectLabel) + '';
var categoryBadge = '' + esc(category) + '';
var confBadge = '' + esc(factConfidenceShortLabel(confidence)) + '';
var summary = '' + esc(f.summary || dt('common.untitled', null, '无标题')) + '';
- // 勿用 i18n 插值拼接 fact_key:i18next 会把 / 转成 / 导致乱码
- var projectLabel = (f.project_name || '').trim() || dt('projects.defaultProjectName', null, '项目');
- var factKeyLabel = (f.fact_key || '').trim() || '—';
- var metaText = projectLabel + ' · ' + factKeyLabel;
- var metaLine = '' + esc(metaText) + '';
+ var factKeyCol = '' + esc(factKeyLabel) + '';
var time = '' + esc(timeAgoStr(f.updated_at)) + '';
- item.innerHTML = pinMark + categoryBadge + confBadge + summary + metaLine + time;
+ item.innerHTML = pinMark + categoryBadge + confBadge + summary + factKeyCol + projectCol + time;
wrap.appendChild(item);
});
}
@@ -1428,7 +1432,7 @@ function renderVulnStatusPanel(byStatus, total) {
//
// bySeverityOpen: { critical, high, medium, low }(只统计 status=open 的漏洞;info 不计入)
// totalOpen: 待处理漏洞总数(= critical + high + medium + low),仅用于"全无待处理 → safe"判断
-// recentVulnsRes: /api/vulnerabilities?limit=5 响应(用于"最近发现"时间,口径是全量,与处置状态无关)
+// recentVulnsRes: /api/vulnerabilities?limit=10 响应(用于"最近发现"时间,口径是全量,与处置状态无关)
function renderSeverityInsights(bySeverityOpen, totalOpen, recentVulnsRes) {
var riskBox = document.querySelector('.dashboard-severity-insight-risk');
var levelEl = document.getElementById('dashboard-severity-risk-level');