mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-06-10 08:13:59 +02:00
Add files via upload
This commit is contained in:
@@ -37,7 +37,6 @@
|
||||
Form Controls (scoped to C2 pages)
|
||||
============================================================================ */
|
||||
|
||||
#page-c2 .form-control,
|
||||
#page-c2-listeners .form-control,
|
||||
#page-c2-sessions .form-control,
|
||||
#page-c2-tasks .form-control,
|
||||
@@ -61,7 +60,6 @@
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
#page-c2 .form-control:focus,
|
||||
#page-c2-listeners .form-control:focus,
|
||||
#page-c2-sessions .form-control:focus,
|
||||
#page-c2-tasks .form-control:focus,
|
||||
@@ -73,7 +71,6 @@
|
||||
box-shadow: 0 0 0 3px var(--c2-accent-dim);
|
||||
}
|
||||
|
||||
#page-c2 select.form-control,
|
||||
#page-c2-payloads select.form-control,
|
||||
.c2-modal select.form-control {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%2364748b' d='M2.5 4.5L6 8l3.5-3.5'/%3E%3C/svg%3E");
|
||||
@@ -85,7 +82,6 @@
|
||||
}
|
||||
|
||||
/* 原生下拉:避免 appearance:none 在部分浏览器中导致 select 无法正常展开 */
|
||||
#page-c2 select.form-control.c2-native-select,
|
||||
#page-c2-payloads select.form-control.c2-native-select,
|
||||
.c2-modal select.form-control.c2-native-select {
|
||||
appearance: auto;
|
||||
@@ -94,7 +90,6 @@
|
||||
padding-right: 14px;
|
||||
}
|
||||
|
||||
#page-c2 textarea.form-control,
|
||||
#page-c2-payloads textarea.form-control,
|
||||
.c2-modal textarea.form-control {
|
||||
resize: vertical;
|
||||
@@ -104,7 +99,6 @@
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
#page-c2 .form-control::placeholder,
|
||||
#page-c2-payloads .form-control::placeholder,
|
||||
.c2-modal .form-control::placeholder {
|
||||
color: var(--c2-text-muted);
|
||||
@@ -140,9 +134,6 @@
|
||||
Layout
|
||||
============================================================================ */
|
||||
|
||||
.c2-layout { display: flex; flex-direction: column; height: 100%; }
|
||||
.c2-main { flex: 1; overflow-y: auto; }
|
||||
|
||||
.c2-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -171,103 +162,6 @@
|
||||
margin: 12px;
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
Dashboard / Welcome
|
||||
============================================================================ */
|
||||
|
||||
.c2-welcome {
|
||||
text-align: center;
|
||||
padding: 100px 24px 80px;
|
||||
max-width: 860px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.c2-welcome-icon {
|
||||
margin-bottom: 16px;
|
||||
animation: c2-float 4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes c2-float {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-8px); }
|
||||
}
|
||||
|
||||
.c2-welcome h3 {
|
||||
font-size: 28px;
|
||||
margin-bottom: 12px;
|
||||
color: var(--c2-text);
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
|
||||
.c2-welcome p {
|
||||
color: var(--c2-text-dim);
|
||||
font-size: 15px;
|
||||
line-height: 1.7;
|
||||
margin-bottom: 48px;
|
||||
max-width: 520px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.c2-stats {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 48px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.c2-stat-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 28px 40px;
|
||||
background: var(--c2-surface);
|
||||
border-radius: var(--c2-radius);
|
||||
border: 1.5px solid var(--c2-border);
|
||||
min-width: 160px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.c2-stat-item:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: var(--c2-shadow-md);
|
||||
border-color: var(--c2-accent);
|
||||
}
|
||||
|
||||
.c2-stat-item:nth-child(1) .c2-stat-value { color: var(--c2-accent); }
|
||||
.c2-stat-item:nth-child(2) .c2-stat-value { color: var(--c2-green); }
|
||||
.c2-stat-item:nth-child(3) .c2-stat-value { color: var(--c2-amber); }
|
||||
|
||||
.c2-stat-value {
|
||||
font-size: 36px;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
|
||||
.c2-stat-label {
|
||||
font-size: 12px;
|
||||
color: var(--c2-text-dim);
|
||||
margin-top: 12px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
.c2-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
max-width: 420px;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.c2-actions > button {
|
||||
flex: 1;
|
||||
min-width: min(100%, 160px);
|
||||
}
|
||||
/* ============================================================================
|
||||
Listener Cards
|
||||
============================================================================ */
|
||||
@@ -1590,7 +1484,6 @@
|
||||
border-right: none;
|
||||
border-bottom: 1px solid var(--c2-border);
|
||||
}
|
||||
.c2-stats { flex-direction: column; gap: 12px; }
|
||||
.c2-payload-grid { grid-template-columns: 1fr; }
|
||||
.c2-listener-grid { grid-template-columns: 1fr; padding: 16px; }
|
||||
.c2-task-detail-grid { grid-template-columns: 1fr; }
|
||||
|
||||
@@ -15971,6 +15971,255 @@ tr.mcp-stats-tool-row[data-tool-name]:focus-visible {
|
||||
transform: translateX(2px);
|
||||
}
|
||||
|
||||
/* 最近漏洞 / 近期事实 Tab */
|
||||
.dashboard-section-header--tabs {
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.dashboard-feed-tabs {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 3px;
|
||||
background: #f1f5f9;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(15, 23, 42, 0.06);
|
||||
}
|
||||
|
||||
.dashboard-feed-tab {
|
||||
padding: 7px 14px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: #64748b;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
transition: background 0.15s, color 0.15s, box-shadow 0.15s;
|
||||
}
|
||||
|
||||
.dashboard-feed-tab:hover {
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.dashboard-feed-tab.is-active {
|
||||
color: #0066ff;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.08);
|
||||
}
|
||||
|
||||
.dashboard-feed-tab-badge {
|
||||
margin-left: 4px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: #64748b;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.dashboard-feed-tab.is-active .dashboard-feed-tab-badge {
|
||||
color: #0066ff;
|
||||
}
|
||||
|
||||
.dashboard-feed-tab:focus-visible {
|
||||
outline: 2px solid rgba(0, 102, 255, 0.45);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.dashboard-feed-panel[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.dashboard-recent-facts {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
min-height: 60px;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.dashboard-recent-facts-empty {
|
||||
text-align: center;
|
||||
color: var(--text-secondary);
|
||||
padding: 28px 12px;
|
||||
font-size: 0.875rem;
|
||||
background: #fafbfc;
|
||||
border-radius: 10px;
|
||||
border: 1px dashed rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.dashboard-recent-facts-empty.is-rich {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 28px 16px;
|
||||
text-align: center;
|
||||
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;
|
||||
align-items: center;
|
||||
column-gap: 10px;
|
||||
padding: 12px 10px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
border-bottom: 1px solid #f3f4f6;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.dashboard-recent-fact-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.dashboard-recent-fact-item:hover {
|
||||
background: rgba(0, 102, 255, 0.04);
|
||||
}
|
||||
|
||||
.dashboard-recent-fact-item:focus-visible {
|
||||
outline: 2px solid rgba(0, 102, 255, 0.5);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.dashboard-recent-fact-pin {
|
||||
width: 20px;
|
||||
flex-shrink: 0;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.dashboard-recent-fact-cat,
|
||||
.dashboard-recent-fact-conf {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3px 6px;
|
||||
border-radius: 6px;
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
justify-self: start;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.dashboard-recent-fact-cat {
|
||||
width: 64px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.dashboard-recent-fact-conf {
|
||||
width: 56px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.dashboard-recent-fact-cat {
|
||||
background: rgba(99, 102, 241, 0.1);
|
||||
color: #4338ca;
|
||||
}
|
||||
|
||||
.dashboard-recent-fact-cat.cat-finding,
|
||||
.dashboard-recent-fact-cat.cat-vuln,
|
||||
.dashboard-recent-fact-cat.cat-exploit,
|
||||
.dashboard-recent-fact-cat.cat-poc,
|
||||
.dashboard-recent-fact-cat.cat-chain {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
.dashboard-recent-fact-cat.cat-target,
|
||||
.dashboard-recent-fact-cat.cat-env,
|
||||
.dashboard-recent-fact-cat.cat-auth {
|
||||
background: rgba(14, 165, 233, 0.12);
|
||||
color: #0369a1;
|
||||
}
|
||||
|
||||
.dashboard-recent-fact-conf.conf-confirmed {
|
||||
background: rgba(34, 197, 94, 0.12);
|
||||
color: #15803d;
|
||||
}
|
||||
|
||||
.dashboard-recent-fact-conf.conf-tentative {
|
||||
background: rgba(245, 158, 11, 0.12);
|
||||
color: #b45309;
|
||||
}
|
||||
|
||||
.dashboard-recent-fact-summary {
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
font-size: 0.875rem;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dashboard-recent-fact-meta {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.8125rem;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
|
||||
}
|
||||
|
||||
.dashboard-recent-fact-time {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.75rem;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
font-variant-numeric: tabular-nums;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.dashboard-recent-fact-item {
|
||||
grid-template-columns: 20px 64px minmax(0, 1fr) auto 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;
|
||||
}
|
||||
.dashboard-recent-fact-meta { display: none; }
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.dashboard-recent-fact-item {
|
||||
grid-template-columns: 20px minmax(0, 1fr) auto;
|
||||
}
|
||||
.dashboard-recent-fact-cat { display: none; }
|
||||
.dashboard-recent-fact-time { display: none; }
|
||||
}
|
||||
|
||||
/* 最近漏洞列表 */
|
||||
.dashboard-recent-vulns {
|
||||
display: flex;
|
||||
|
||||
@@ -79,7 +79,6 @@
|
||||
"settings": "System settings",
|
||||
"hitl": "Human-in-the-loop",
|
||||
"c2": "C2",
|
||||
"c2Manage": "C2 management",
|
||||
"c2Listeners": "Listeners",
|
||||
"c2Sessions": "Sessions",
|
||||
"c2Tasks": "Tasks",
|
||||
@@ -153,7 +152,14 @@
|
||||
"lastUpdated": "Last updated",
|
||||
"viewAll": "View all →",
|
||||
"recentVulns": "Recent vulnerabilities",
|
||||
"recentFacts": "Recent facts",
|
||||
"noVulnYet": "No recent vulnerabilities",
|
||||
"noFactsYet": "No recent facts",
|
||||
"noFactsDesc": "In project-bound chats, the agent records targets, findings, and attack chains; new facts appear here",
|
||||
"createFirstProjectBtn": "Create first project",
|
||||
"factProjectMeta": "{{project}} · {{key}}",
|
||||
"factsAcrossProjects_one": "{{count}} active project · {{facts}} facts",
|
||||
"factsAcrossProjects_other": "{{count}} active projects · {{facts}} facts",
|
||||
"capabilities": "Capabilities",
|
||||
"mcpTools": "MCP tools",
|
||||
"rolesLabel": "Roles",
|
||||
@@ -377,6 +383,7 @@
|
||||
"settingsIntroTitle": "Project settings",
|
||||
"settingsIntroHint": "Configure project metadata and Agent authorization boundary; takes effect immediately for bound conversations after saving.",
|
||||
"pinProject": "Pin project (show first in list)",
|
||||
"pinFact": "Pin fact (prioritize in list and blackboard index)",
|
||||
"editDescriptionPlaceholder": "Targets, authorization scope, contacts, notes…",
|
||||
"scopeTitle": "Test scope",
|
||||
"scopeHint": "JSON format for Agent authorization boundary and target assets",
|
||||
@@ -2529,14 +2536,6 @@
|
||||
"checkboxLinkTitle": "Check to link this tool to this role"
|
||||
},
|
||||
"c2": {
|
||||
"title": "C2 Management",
|
||||
"welcomeTitle": "AI-Native C2 Framework",
|
||||
"welcomeDesc": "MCP-native design: let LLM call C2 like calling nmap to complete the full chain: initial access → control → tasks → lateral movement → cleanup",
|
||||
"statListeners": "Running Listeners",
|
||||
"statSessions": "Online Sessions",
|
||||
"statPending": "Pending Tasks",
|
||||
"goListeners": "Manage Listeners",
|
||||
"goSessions": "View Sessions",
|
||||
"clipboardCopied": "Copied to clipboard",
|
||||
"fmt": {
|
||||
"durationMs": "{{n}}ms",
|
||||
|
||||
@@ -79,7 +79,6 @@
|
||||
"settings": "系统设置",
|
||||
"hitl": "人机协同",
|
||||
"c2": "C2",
|
||||
"c2Manage": "C2 管理",
|
||||
"c2Listeners": "监听器",
|
||||
"c2Sessions": "会话",
|
||||
"c2Tasks": "任务",
|
||||
@@ -153,7 +152,13 @@
|
||||
"lastUpdated": "上次更新",
|
||||
"viewAll": "查看全部 →",
|
||||
"recentVulns": "最近漏洞",
|
||||
"recentFacts": "近期事实",
|
||||
"noVulnYet": "暂无最近漏洞",
|
||||
"noFactsYet": "暂无近期事实",
|
||||
"noFactsDesc": "在绑定项目的对话中,Agent 会自动记录目标、漏洞、攻击链等事实;新事实会出现在这里",
|
||||
"createFirstProjectBtn": "创建第一个项目",
|
||||
"factProjectMeta": "{{project}} · {{key}}",
|
||||
"factsAcrossProjects": "{{count}} 个活跃项目 · {{facts}} 条事实",
|
||||
"capabilities": "能力总览",
|
||||
"mcpTools": "MCP 工具",
|
||||
"rolesLabel": "角色",
|
||||
@@ -366,6 +371,7 @@
|
||||
"settingsIntroTitle": "项目设置",
|
||||
"settingsIntroHint": "配置项目元数据与 Agent 授权边界,保存后即时生效于绑定对话。",
|
||||
"pinProject": "置顶项目(列表优先显示)",
|
||||
"pinFact": "置顶事实(列表与黑板索引优先)",
|
||||
"editDescriptionPlaceholder": "测试目标、授权范围、联系人、注意事项…",
|
||||
"scopeTitle": "测试范围",
|
||||
"scopeHint": "JSON 格式,供 Agent 理解授权边界与目标资产",
|
||||
@@ -2518,14 +2524,6 @@
|
||||
"checkboxLinkTitle": "勾选表示本角色关联使用该工具"
|
||||
},
|
||||
"c2": {
|
||||
"title": "C2 管理",
|
||||
"welcomeTitle": "AI-Native C2 框架",
|
||||
"welcomeDesc": "以 MCP 工具为一等公民,让 LLM 可以像调用 nmap 一样调用 C2 完成「上线 → 控制 → 任务 → 横向 → 清场」全流程",
|
||||
"statListeners": "运行中监听器",
|
||||
"statSessions": "在线会话",
|
||||
"statPending": "待审任务",
|
||||
"goListeners": "管理监听器",
|
||||
"goSessions": "查看会话",
|
||||
"clipboardCopied": "已复制到剪贴板",
|
||||
"fmt": {
|
||||
"durationMs": "{{n}}ms",
|
||||
|
||||
@@ -321,7 +321,6 @@
|
||||
}
|
||||
|
||||
switch(pageId) {
|
||||
case 'c2':
|
||||
case 'c2-listeners':
|
||||
C2.loadListeners();
|
||||
break;
|
||||
@@ -370,7 +369,6 @@
|
||||
C2.profiles = pdata.profiles;
|
||||
}
|
||||
C2.renderListeners();
|
||||
C2.updateDashboardStats();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -736,7 +734,6 @@
|
||||
return apiRequest('GET', `${API_BASE}/sessions`).then(data => {
|
||||
C2.sessions = data.sessions || [];
|
||||
C2.renderSessions();
|
||||
C2.updateDashboardStats();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -2037,7 +2034,6 @@
|
||||
C2.renderTasks();
|
||||
C2.renderTasksPagination();
|
||||
C2.syncTasksToolbar();
|
||||
C2.updateDashboardStats();
|
||||
}).catch(err => {
|
||||
showToast(err.message || String(err), 'error');
|
||||
});
|
||||
@@ -2163,7 +2159,6 @@
|
||||
const tasks = data.tasks || [];
|
||||
if (typeof data.pending_queued_count === 'number') {
|
||||
C2.tasksPendingQueuedCount = data.pending_queued_count;
|
||||
C2.updateDashboardStats();
|
||||
}
|
||||
|
||||
if (!container) return;
|
||||
@@ -2819,7 +2814,6 @@
|
||||
showToast(`[${event.category}] ${event.message}`, event.level === 'critical' ? 'error' : 'info');
|
||||
}
|
||||
|
||||
C2.updateDashboardStats();
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
@@ -2953,26 +2947,6 @@
|
||||
});
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 仪表盘
|
||||
// ============================================================================
|
||||
|
||||
C2.updateDashboardStats = function() {
|
||||
const runningListeners = C2.listeners.filter(l => l.status === 'running').length;
|
||||
const activeSessions = C2.sessions.filter(s => s.status === 'active').length;
|
||||
const pendingTasks = typeof C2.tasksPendingQueuedCount === 'number'
|
||||
? C2.tasksPendingQueuedCount
|
||||
: C2.tasks.filter(t => t.status === 'queued' || t.status === 'pending').length;
|
||||
|
||||
const elListeners = document.getElementById('c2-stat-listeners');
|
||||
const elSessions = document.getElementById('c2-stat-sessions');
|
||||
const elPending = document.getElementById('c2-stat-pending');
|
||||
|
||||
if (elListeners) elListeners.textContent = runningListeners;
|
||||
if (elSessions) elSessions.textContent = activeSessions;
|
||||
if (elPending) elPending.textContent = pendingTasks;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 模态框
|
||||
// ============================================================================
|
||||
|
||||
+220
-2
@@ -21,6 +21,8 @@ var dashboardState = {
|
||||
lastUpdatedAt: 0, // 上次成功刷新的时间戳(ms)
|
||||
dismissedAlertKey: null, // 当前会话中被用户「×」掉的告警内容指纹(同样的 reasons 不再弹)
|
||||
lastResources: null, // 上一轮关键资源快照,用于判断是否首次有数据 / 智能 CTA
|
||||
recentFeedTab: 'vulns', // 最近漏洞 / 近期事实 Tab
|
||||
lastProjectSummary: null, // 最近一次项目仪表盘摘要(供 Tab 切换时重绘)
|
||||
};
|
||||
|
||||
async function refreshDashboard() {
|
||||
@@ -57,6 +59,7 @@ async function refreshDashboard() {
|
||||
hideEl('dashboard-kpi-vuln-critical-badge');
|
||||
hideEl('dashboard-alert-banner');
|
||||
setRecentVulnsLoading();
|
||||
setRecentFactsLoading();
|
||||
['tools', 'skills', 'knowledge', 'roles', 'agents', 'webshell'].forEach(function (k) {
|
||||
setEl('dashboard-resource-' + k, '…');
|
||||
});
|
||||
@@ -104,7 +107,8 @@ async function refreshDashboard() {
|
||||
openCriticalRes, openHighRes, openMediumRes, openLowRes, toolsConfigRes,
|
||||
hitlPendingRes, notificationsRes, externalMcpStatsRes,
|
||||
webshellRes,
|
||||
c2ListenersRes, c2SessionsRes, c2TasksRes
|
||||
c2ListenersRes, c2SessionsRes, c2TasksRes,
|
||||
projectSummaryRes
|
||||
] = await Promise.all([
|
||||
fetchJson('/api/agent-loop/tasks'),
|
||||
fetchJson('/api/vulnerabilities/stats'),
|
||||
@@ -134,7 +138,8 @@ async function refreshDashboard() {
|
||||
// C2 仪表盘条:监听器 / 会话 / 待处理任务(任务接口含 pending_queued_count)
|
||||
fetchJson('/api/c2/listeners'),
|
||||
fetchJson('/api/c2/sessions?limit=500'),
|
||||
fetchJson('/api/c2/tasks?page=1&page_size=1')
|
||||
fetchJson('/api/c2/tasks?page=1&page_size=1'),
|
||||
fetchJson('/api/projects/dashboard-summary?fact_limit=5')
|
||||
]);
|
||||
|
||||
// 如果在 await 期间 controller 已被 abort,说明又有新刷新启动了,丢弃本次结果
|
||||
@@ -387,6 +392,9 @@ async function refreshDashboard() {
|
||||
|
||||
// 最近漏洞列表
|
||||
renderRecentVulns(recentVulnsRes);
|
||||
dashboardState.lastProjectSummary = projectSummaryRes;
|
||||
renderRecentFacts(projectSummaryRes);
|
||||
updateDashboardFeedTabBadge(projectSummaryRes);
|
||||
|
||||
// External MCP 健康度(同时拿到 down 数喂给 alert banner / 推荐操作)
|
||||
var externalMcpDown = renderExternalMcpHealth(externalMcpStatsRes);
|
||||
@@ -454,6 +462,7 @@ async function refreshDashboard() {
|
||||
var c2secErr = document.getElementById('dashboard-section-c2');
|
||||
if (c2secErr) c2secErr.hidden = true;
|
||||
setRecentVulnsError();
|
||||
setRecentFactsError();
|
||||
renderDashboardToolsBar(null);
|
||||
var ph = document.getElementById('dashboard-tools-pie-placeholder');
|
||||
if (ph) { ph.style.removeProperty('display'); ph.textContent = (typeof window.t === 'function' ? window.t('dashboard.noCallData') : '暂无调用数据'); }
|
||||
@@ -1130,6 +1139,215 @@ function renderRecentVulns(res) {
|
||||
});
|
||||
}
|
||||
|
||||
// 最近漏洞 / 近期事实 Tab 切换(共用列表区域,查看全部链接随 Tab 变化)
|
||||
function switchDashboardFeedTab(tab) {
|
||||
tab = tab === 'facts' ? 'facts' : 'vulns';
|
||||
dashboardState.recentFeedTab = tab;
|
||||
|
||||
var tabVulns = document.getElementById('dashboard-feed-tab-vulns');
|
||||
var tabFacts = document.getElementById('dashboard-feed-tab-facts');
|
||||
var panelVulns = document.getElementById('dashboard-feed-panel-vulns');
|
||||
var panelFacts = document.getElementById('dashboard-feed-panel-facts');
|
||||
if (tabVulns) {
|
||||
tabVulns.classList.toggle('is-active', tab === 'vulns');
|
||||
tabVulns.setAttribute('aria-selected', tab === 'vulns' ? 'true' : 'false');
|
||||
}
|
||||
if (tabFacts) {
|
||||
tabFacts.classList.toggle('is-active', tab === 'facts');
|
||||
tabFacts.setAttribute('aria-selected', tab === 'facts' ? 'true' : 'false');
|
||||
}
|
||||
if (panelVulns) panelVulns.hidden = tab !== 'vulns';
|
||||
if (panelFacts) panelFacts.hidden = tab !== 'facts';
|
||||
updateDashboardFeedViewAll(tab);
|
||||
}
|
||||
|
||||
function updateDashboardFeedViewAll(tab) {
|
||||
var link = document.getElementById('dashboard-feed-view-all');
|
||||
if (!link) return;
|
||||
if (tab === 'facts') {
|
||||
link.onclick = function () { try { switchPage('projects'); } catch (_) {} };
|
||||
} else {
|
||||
link.onclick = function () { try { switchPage('vulnerabilities'); } catch (_) {} };
|
||||
}
|
||||
}
|
||||
|
||||
function updateDashboardFeedTabBadge(summaryRes) {
|
||||
var badge = document.getElementById('dashboard-feed-tab-facts-badge');
|
||||
if (!badge) return;
|
||||
var facts = (summaryRes && Array.isArray(summaryRes.recent_facts)) ? summaryRes.recent_facts.length : 0;
|
||||
if (facts > 0) {
|
||||
badge.hidden = false;
|
||||
badge.textContent = '(' + facts + ')';
|
||||
} else {
|
||||
badge.hidden = true;
|
||||
badge.textContent = '';
|
||||
}
|
||||
}
|
||||
|
||||
function setRecentFactsLoading() {
|
||||
var wrap = document.getElementById('dashboard-recent-facts');
|
||||
var empty = document.getElementById('dashboard-recent-facts-empty');
|
||||
if (!wrap) return;
|
||||
clearRecentFactsList(wrap);
|
||||
if (empty) {
|
||||
empty.hidden = false;
|
||||
empty.classList.remove('is-rich');
|
||||
empty.textContent = dt('common.loading', null, '加载中…');
|
||||
}
|
||||
}
|
||||
|
||||
function clearRecentFactsList(wrap) {
|
||||
if (!wrap) return;
|
||||
Array.from(wrap.querySelectorAll('.dashboard-recent-fact-item, .dashboard-recent-facts-meta')).forEach(function (n) { n.remove(); });
|
||||
}
|
||||
|
||||
function setRecentFactsError() {
|
||||
var wrap = document.getElementById('dashboard-recent-facts');
|
||||
var empty = document.getElementById('dashboard-recent-facts-empty');
|
||||
if (!wrap) return;
|
||||
clearRecentFactsList(wrap);
|
||||
if (empty) {
|
||||
empty.hidden = false;
|
||||
empty.classList.remove('is-rich');
|
||||
empty.textContent = dt('common.loadFailed', null, '加载失败');
|
||||
}
|
||||
}
|
||||
|
||||
function factConfidenceShortLabel(confidence) {
|
||||
var c = String(confidence || '').toLowerCase();
|
||||
if (c === 'confirmed') return dt('projects.confidenceConfirmed', null, '已确认');
|
||||
if (c === 'tentative') return dt('projects.confidenceTentative', null, '待确认');
|
||||
return c || '—';
|
||||
}
|
||||
|
||||
function factCategoryShortLabel(category) {
|
||||
var raw = String(category || '').trim();
|
||||
return raw || 'note';
|
||||
}
|
||||
|
||||
function openProjectFactFromDashboard(projectId, factKey) {
|
||||
if (!projectId) return;
|
||||
if (typeof switchPage === 'function') {
|
||||
switchPage('projects');
|
||||
}
|
||||
setTimeout(async function () {
|
||||
if (typeof window.initProjectsPage === 'function') {
|
||||
await window.initProjectsPage();
|
||||
}
|
||||
if (typeof window.selectProject === 'function') {
|
||||
await window.selectProject(projectId);
|
||||
}
|
||||
if (typeof window.switchProjectTab === 'function') {
|
||||
window.switchProjectTab('facts');
|
||||
}
|
||||
if (factKey && typeof window.viewProjectFactBody === 'function') {
|
||||
window.viewProjectFactBody(factKey);
|
||||
}
|
||||
}, 350);
|
||||
}
|
||||
|
||||
function renderRecentFacts(res) {
|
||||
var wrap = document.getElementById('dashboard-recent-facts');
|
||||
var empty = document.getElementById('dashboard-recent-facts-empty');
|
||||
if (!wrap) return;
|
||||
|
||||
clearRecentFactsList(wrap);
|
||||
|
||||
var list = (res && Array.isArray(res.recent_facts)) ? res.recent_facts : [];
|
||||
var totals = (res && res.totals) ? res.totals : {};
|
||||
var activeProjects = totals.active_projects || 0;
|
||||
var totalFacts = totals.total_facts || 0;
|
||||
|
||||
if (list.length === 0) {
|
||||
if (empty) {
|
||||
empty.hidden = false;
|
||||
empty.classList.add('is-rich');
|
||||
var desc = activeProjects > 0
|
||||
? dt('dashboard.noFactsDesc', null, '在绑定项目的对话中,Agent 会自动记录目标、漏洞、攻击链等事实;新事实会出现在这里')
|
||||
: dt('projects.selectOrCreateHint', null, '项目用于跨对话共享「事实黑板」:目标、环境、认证等信息会在绑定项目的对话中自动注入。');
|
||||
var ctaLabel = activeProjects > 0
|
||||
? dt('dashboard.goToChat', null, '前往对话')
|
||||
: dt('dashboard.createFirstProjectBtn', null, '创建第一个项目');
|
||||
var ctaAction = activeProjects > 0 ? 'chat' : 'project';
|
||||
empty.innerHTML = (
|
||||
'<span class="dashboard-empty-icon" aria-hidden="true">' +
|
||||
'<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/><line x1="12" y1="6" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>' +
|
||||
'</span>' +
|
||||
'<div class="dashboard-empty-title">' + esc(dt('dashboard.noFactsYet', null, '暂无近期事实')) + '</div>' +
|
||||
'<div class="dashboard-empty-desc">' + esc(desc) + '</div>' +
|
||||
'<button type="button" class="dashboard-empty-action" data-action="' + esc(ctaAction) + '">' +
|
||||
esc(ctaLabel) + ' →</button>'
|
||||
);
|
||||
var btn = empty.querySelector('[data-action]');
|
||||
if (btn) {
|
||||
btn.onclick = function () {
|
||||
var action = btn.getAttribute('data-action');
|
||||
if (action === 'project') {
|
||||
try { switchPage('projects'); } catch (_) {}
|
||||
setTimeout(function () {
|
||||
if (typeof window.showNewProjectModal === 'function') {
|
||||
window.showNewProjectModal();
|
||||
}
|
||||
}, 350);
|
||||
} else {
|
||||
try { switchPage('chat'); } catch (_) {}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty) {
|
||||
empty.hidden = true;
|
||||
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) {
|
||||
if (!f) return;
|
||||
var category = factCategoryShortLabel(f.category);
|
||||
var confidence = String(f.confidence || 'tentative').toLowerCase();
|
||||
var item = document.createElement('a');
|
||||
item.className = 'dashboard-recent-fact-item';
|
||||
item.setAttribute('role', 'button');
|
||||
item.tabIndex = 0;
|
||||
var pid = f.project_id || '';
|
||||
var fkey = f.fact_key || '';
|
||||
item.onclick = function () { openProjectFactFromDashboard(pid, fkey); };
|
||||
item.onkeydown = function (e) {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
item.click();
|
||||
}
|
||||
};
|
||||
|
||||
// 置顶列始终占位,避免有/无图钉时后续列错位
|
||||
var pinMark = '<span class="dashboard-recent-fact-pin' + (f.pinned ? ' is-pinned' : '') + '"' +
|
||||
(f.pinned ? (' title="' + esc(dt('projects.pinned', null, '置顶')) + '"') : '') +
|
||||
' aria-hidden="true">' + (f.pinned ? '📌' : '') + '</span>';
|
||||
var categoryBadge = '<span class="dashboard-recent-fact-cat cat-' + esc(category.toLowerCase().replace(/[^a-z0-9_-]/g, '')) + '">' + esc(category) + '</span>';
|
||||
var confBadge = '<span class="dashboard-recent-fact-conf conf-' + esc(confidence) + '">' + esc(factConfidenceShortLabel(confidence)) + '</span>';
|
||||
var summary = '<span class="dashboard-recent-fact-summary" title="' + esc(f.summary || '') + '">' + esc(f.summary || dt('common.untitled', null, '无标题')) + '</span>';
|
||||
// 勿用 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 = '<span class="dashboard-recent-fact-meta" title="' + esc(metaText) + '">' + esc(metaText) + '</span>';
|
||||
var time = '<span class="dashboard-recent-fact-time">' + esc(timeAgoStr(f.updated_at)) + '</span>';
|
||||
|
||||
item.innerHTML = pinMark + categoryBadge + confBadge + summary + metaLine + time;
|
||||
wrap.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
// 漏洞状态映射:把 status 字符串规整到 4 类(避免脏数据)
|
||||
function statusKey(s) {
|
||||
s = String(s || '').toLowerCase();
|
||||
|
||||
@@ -574,8 +574,11 @@ async function loadProjectFacts() {
|
||||
const vulnLink = f.related_vulnerability_id
|
||||
? `<span class="projects-fact-vuln-link" title="${escapeHtml(tp('projects.relatedVulnIdTitle'))}">${escapeHtml(f.related_vulnerability_id.slice(0, 8))}…</span>`
|
||||
: '';
|
||||
const pinBadge = f.pinned
|
||||
? `<span class="projects-list-item-badge" title="${escapeHtml(tp('projects.pinned'))}">${escapeHtml(tp('projects.pinned'))}</span>`
|
||||
: '';
|
||||
return `<tr>
|
||||
<td class="cell-fact-key"><code class="projects-fact-key-chip" title="${keyEsc}">${keyEsc}</code>${vulnLink}</td>
|
||||
<td class="cell-fact-key"><code class="projects-fact-key-chip" title="${keyEsc}">${keyEsc}</code>${pinBadge}${vulnLink}</td>
|
||||
<td class="cell-fact-category">${formatCategoryBadge(f.category)}</td>
|
||||
<td class="cell-summary" title="${escapeHtml(f.summary)}">${escapeHtml(f.summary)}</td>
|
||||
<td>${formatFactBodyBadge(f)}</td>
|
||||
@@ -1165,6 +1168,8 @@ function resetFactModalForm() {
|
||||
document.getElementById('fact-modal-summary').value = '';
|
||||
document.getElementById('fact-modal-body').value = '';
|
||||
document.getElementById('fact-modal-confidence').value = 'tentative';
|
||||
const pinEl = document.getElementById('fact-modal-pinned');
|
||||
if (pinEl) pinEl.checked = false;
|
||||
const rel = document.getElementById('fact-modal-related-vuln');
|
||||
if (rel) rel.value = '';
|
||||
updateFactFormHints();
|
||||
@@ -1198,6 +1203,8 @@ function fillFactModalForm(f) {
|
||||
}
|
||||
const rel = document.getElementById('fact-modal-related-vuln');
|
||||
if (rel) rel.value = f.related_vulnerability_id || '';
|
||||
const pinEl = document.getElementById('fact-modal-pinned');
|
||||
if (pinEl) pinEl.checked = !!f.pinned;
|
||||
updateFactFormHints();
|
||||
}
|
||||
|
||||
@@ -1242,6 +1249,7 @@ async function saveFactModal() {
|
||||
summary,
|
||||
body,
|
||||
confidence: document.getElementById('fact-modal-confidence').value,
|
||||
pinned: !!document.getElementById('fact-modal-pinned')?.checked,
|
||||
related_vulnerability_id: document.getElementById('fact-modal-related-vuln')?.value?.trim() || '',
|
||||
};
|
||||
const editId = window._factModalEditId;
|
||||
|
||||
@@ -56,8 +56,9 @@ function initRouter() {
|
||||
const hash = window.location.hash.slice(1);
|
||||
if (hash) {
|
||||
const hashParts = hash.split('?');
|
||||
const pageId = hashParts[0];
|
||||
if (pageId && ['dashboard', 'chat', 'hitl', 'info-collect', 'projects', 'vulnerabilities', 'webshell', 'chat-files', 'mcp-monitor', 'mcp-management', 'knowledge-management', 'knowledge-retrieval-logs', 'roles-management', 'skills-monitor', 'skills-management', 'agents-management', 'settings', 'tasks', 'c2', 'c2-listeners', 'c2-sessions', 'c2-tasks', 'c2-payloads', 'c2-events', 'c2-profiles'].includes(pageId)) {
|
||||
let pageId = hashParts[0];
|
||||
if (pageId === 'c2') pageId = 'c2-listeners';
|
||||
if (pageId && ['dashboard', 'chat', 'hitl', 'info-collect', 'projects', 'vulnerabilities', 'webshell', 'chat-files', 'mcp-monitor', 'mcp-management', 'knowledge-management', 'knowledge-retrieval-logs', 'roles-management', 'skills-monitor', 'skills-management', 'agents-management', 'settings', 'tasks', 'c2-listeners', 'c2-sessions', 'c2-tasks', 'c2-payloads', 'c2-events', 'c2-profiles'].includes(pageId)) {
|
||||
switchPage(pageId);
|
||||
if (pageId === 'chat') {
|
||||
scheduleChatConversationFromHash(500);
|
||||
@@ -464,7 +465,6 @@ async function initPage(pageId) {
|
||||
loadMarkdownAgents();
|
||||
}
|
||||
break;
|
||||
case 'c2':
|
||||
case 'c2-listeners':
|
||||
case 'c2-sessions':
|
||||
case 'c2-tasks':
|
||||
@@ -494,9 +494,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const hash = window.location.hash.slice(1);
|
||||
// 处理带参数的hash(如 chat?conversation=xxx)
|
||||
const hashParts = hash.split('?');
|
||||
const pageId = hashParts[0];
|
||||
let pageId = hashParts[0];
|
||||
|
||||
if (pageId && ['dashboard', 'chat', 'hitl', 'info-collect', 'tasks', 'vulnerabilities', 'webshell', 'chat-files', 'mcp-monitor', 'mcp-management', 'knowledge-management', 'knowledge-retrieval-logs', 'roles-management', 'skills-monitor', 'skills-management', 'agents-management', 'settings', 'c2', 'c2-listeners', 'c2-sessions', 'c2-tasks', 'c2-payloads', 'c2-events', 'c2-profiles'].includes(pageId)) {
|
||||
if (pageId === 'c2') pageId = 'c2-listeners';
|
||||
if (pageId && ['dashboard', 'chat', 'hitl', 'info-collect', 'tasks', 'vulnerabilities', 'webshell', 'chat-files', 'mcp-monitor', 'mcp-management', 'knowledge-management', 'knowledge-retrieval-logs', 'roles-management', 'skills-monitor', 'skills-management', 'agents-management', 'settings', 'c2-listeners', 'c2-sessions', 'c2-tasks', 'c2-payloads', 'c2-events', 'c2-profiles'].includes(pageId)) {
|
||||
switchPage(pageId);
|
||||
if (pageId === 'chat') {
|
||||
scheduleChatConversationFromHash(200);
|
||||
|
||||
Reference in New Issue
Block a user