mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-07-26 05:40:55 +02:00
Add files via upload
This commit is contained in:
@@ -37,7 +37,6 @@
|
|||||||
Form Controls (scoped to C2 pages)
|
Form Controls (scoped to C2 pages)
|
||||||
============================================================================ */
|
============================================================================ */
|
||||||
|
|
||||||
#page-c2 .form-control,
|
|
||||||
#page-c2-listeners .form-control,
|
#page-c2-listeners .form-control,
|
||||||
#page-c2-sessions .form-control,
|
#page-c2-sessions .form-control,
|
||||||
#page-c2-tasks .form-control,
|
#page-c2-tasks .form-control,
|
||||||
@@ -61,7 +60,6 @@
|
|||||||
appearance: none;
|
appearance: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#page-c2 .form-control:focus,
|
|
||||||
#page-c2-listeners .form-control:focus,
|
#page-c2-listeners .form-control:focus,
|
||||||
#page-c2-sessions .form-control:focus,
|
#page-c2-sessions .form-control:focus,
|
||||||
#page-c2-tasks .form-control:focus,
|
#page-c2-tasks .form-control:focus,
|
||||||
@@ -73,7 +71,6 @@
|
|||||||
box-shadow: 0 0 0 3px var(--c2-accent-dim);
|
box-shadow: 0 0 0 3px var(--c2-accent-dim);
|
||||||
}
|
}
|
||||||
|
|
||||||
#page-c2 select.form-control,
|
|
||||||
#page-c2-payloads select.form-control,
|
#page-c2-payloads select.form-control,
|
||||||
.c2-modal 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");
|
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 无法正常展开 */
|
/* 原生下拉:避免 appearance:none 在部分浏览器中导致 select 无法正常展开 */
|
||||||
#page-c2 select.form-control.c2-native-select,
|
|
||||||
#page-c2-payloads select.form-control.c2-native-select,
|
#page-c2-payloads select.form-control.c2-native-select,
|
||||||
.c2-modal select.form-control.c2-native-select {
|
.c2-modal select.form-control.c2-native-select {
|
||||||
appearance: auto;
|
appearance: auto;
|
||||||
@@ -94,7 +90,6 @@
|
|||||||
padding-right: 14px;
|
padding-right: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#page-c2 textarea.form-control,
|
|
||||||
#page-c2-payloads textarea.form-control,
|
#page-c2-payloads textarea.form-control,
|
||||||
.c2-modal textarea.form-control {
|
.c2-modal textarea.form-control {
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
@@ -104,7 +99,6 @@
|
|||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
#page-c2 .form-control::placeholder,
|
|
||||||
#page-c2-payloads .form-control::placeholder,
|
#page-c2-payloads .form-control::placeholder,
|
||||||
.c2-modal .form-control::placeholder {
|
.c2-modal .form-control::placeholder {
|
||||||
color: var(--c2-text-muted);
|
color: var(--c2-text-muted);
|
||||||
@@ -140,9 +134,6 @@
|
|||||||
Layout
|
Layout
|
||||||
============================================================================ */
|
============================================================================ */
|
||||||
|
|
||||||
.c2-layout { display: flex; flex-direction: column; height: 100%; }
|
|
||||||
.c2-main { flex: 1; overflow-y: auto; }
|
|
||||||
|
|
||||||
.c2-empty {
|
.c2-empty {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -171,103 +162,6 @@
|
|||||||
margin: 12px;
|
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
|
Listener Cards
|
||||||
============================================================================ */
|
============================================================================ */
|
||||||
@@ -1590,7 +1484,6 @@
|
|||||||
border-right: none;
|
border-right: none;
|
||||||
border-bottom: 1px solid var(--c2-border);
|
border-bottom: 1px solid var(--c2-border);
|
||||||
}
|
}
|
||||||
.c2-stats { flex-direction: column; gap: 12px; }
|
|
||||||
.c2-payload-grid { grid-template-columns: 1fr; }
|
.c2-payload-grid { grid-template-columns: 1fr; }
|
||||||
.c2-listener-grid { grid-template-columns: 1fr; padding: 16px; }
|
.c2-listener-grid { grid-template-columns: 1fr; padding: 16px; }
|
||||||
.c2-task-detail-grid { grid-template-columns: 1fr; }
|
.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);
|
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 {
|
.dashboard-recent-vulns {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -79,7 +79,6 @@
|
|||||||
"settings": "System settings",
|
"settings": "System settings",
|
||||||
"hitl": "Human-in-the-loop",
|
"hitl": "Human-in-the-loop",
|
||||||
"c2": "C2",
|
"c2": "C2",
|
||||||
"c2Manage": "C2 management",
|
|
||||||
"c2Listeners": "Listeners",
|
"c2Listeners": "Listeners",
|
||||||
"c2Sessions": "Sessions",
|
"c2Sessions": "Sessions",
|
||||||
"c2Tasks": "Tasks",
|
"c2Tasks": "Tasks",
|
||||||
@@ -153,7 +152,14 @@
|
|||||||
"lastUpdated": "Last updated",
|
"lastUpdated": "Last updated",
|
||||||
"viewAll": "View all →",
|
"viewAll": "View all →",
|
||||||
"recentVulns": "Recent vulnerabilities",
|
"recentVulns": "Recent vulnerabilities",
|
||||||
|
"recentFacts": "Recent facts",
|
||||||
"noVulnYet": "No recent vulnerabilities",
|
"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",
|
"capabilities": "Capabilities",
|
||||||
"mcpTools": "MCP tools",
|
"mcpTools": "MCP tools",
|
||||||
"rolesLabel": "Roles",
|
"rolesLabel": "Roles",
|
||||||
@@ -377,6 +383,7 @@
|
|||||||
"settingsIntroTitle": "Project settings",
|
"settingsIntroTitle": "Project settings",
|
||||||
"settingsIntroHint": "Configure project metadata and Agent authorization boundary; takes effect immediately for bound conversations after saving.",
|
"settingsIntroHint": "Configure project metadata and Agent authorization boundary; takes effect immediately for bound conversations after saving.",
|
||||||
"pinProject": "Pin project (show first in list)",
|
"pinProject": "Pin project (show first in list)",
|
||||||
|
"pinFact": "Pin fact (prioritize in list and blackboard index)",
|
||||||
"editDescriptionPlaceholder": "Targets, authorization scope, contacts, notes…",
|
"editDescriptionPlaceholder": "Targets, authorization scope, contacts, notes…",
|
||||||
"scopeTitle": "Test scope",
|
"scopeTitle": "Test scope",
|
||||||
"scopeHint": "JSON format for Agent authorization boundary and target assets",
|
"scopeHint": "JSON format for Agent authorization boundary and target assets",
|
||||||
@@ -2529,14 +2536,6 @@
|
|||||||
"checkboxLinkTitle": "Check to link this tool to this role"
|
"checkboxLinkTitle": "Check to link this tool to this role"
|
||||||
},
|
},
|
||||||
"c2": {
|
"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",
|
"clipboardCopied": "Copied to clipboard",
|
||||||
"fmt": {
|
"fmt": {
|
||||||
"durationMs": "{{n}}ms",
|
"durationMs": "{{n}}ms",
|
||||||
|
|||||||
@@ -79,7 +79,6 @@
|
|||||||
"settings": "系统设置",
|
"settings": "系统设置",
|
||||||
"hitl": "人机协同",
|
"hitl": "人机协同",
|
||||||
"c2": "C2",
|
"c2": "C2",
|
||||||
"c2Manage": "C2 管理",
|
|
||||||
"c2Listeners": "监听器",
|
"c2Listeners": "监听器",
|
||||||
"c2Sessions": "会话",
|
"c2Sessions": "会话",
|
||||||
"c2Tasks": "任务",
|
"c2Tasks": "任务",
|
||||||
@@ -153,7 +152,13 @@
|
|||||||
"lastUpdated": "上次更新",
|
"lastUpdated": "上次更新",
|
||||||
"viewAll": "查看全部 →",
|
"viewAll": "查看全部 →",
|
||||||
"recentVulns": "最近漏洞",
|
"recentVulns": "最近漏洞",
|
||||||
|
"recentFacts": "近期事实",
|
||||||
"noVulnYet": "暂无最近漏洞",
|
"noVulnYet": "暂无最近漏洞",
|
||||||
|
"noFactsYet": "暂无近期事实",
|
||||||
|
"noFactsDesc": "在绑定项目的对话中,Agent 会自动记录目标、漏洞、攻击链等事实;新事实会出现在这里",
|
||||||
|
"createFirstProjectBtn": "创建第一个项目",
|
||||||
|
"factProjectMeta": "{{project}} · {{key}}",
|
||||||
|
"factsAcrossProjects": "{{count}} 个活跃项目 · {{facts}} 条事实",
|
||||||
"capabilities": "能力总览",
|
"capabilities": "能力总览",
|
||||||
"mcpTools": "MCP 工具",
|
"mcpTools": "MCP 工具",
|
||||||
"rolesLabel": "角色",
|
"rolesLabel": "角色",
|
||||||
@@ -366,6 +371,7 @@
|
|||||||
"settingsIntroTitle": "项目设置",
|
"settingsIntroTitle": "项目设置",
|
||||||
"settingsIntroHint": "配置项目元数据与 Agent 授权边界,保存后即时生效于绑定对话。",
|
"settingsIntroHint": "配置项目元数据与 Agent 授权边界,保存后即时生效于绑定对话。",
|
||||||
"pinProject": "置顶项目(列表优先显示)",
|
"pinProject": "置顶项目(列表优先显示)",
|
||||||
|
"pinFact": "置顶事实(列表与黑板索引优先)",
|
||||||
"editDescriptionPlaceholder": "测试目标、授权范围、联系人、注意事项…",
|
"editDescriptionPlaceholder": "测试目标、授权范围、联系人、注意事项…",
|
||||||
"scopeTitle": "测试范围",
|
"scopeTitle": "测试范围",
|
||||||
"scopeHint": "JSON 格式,供 Agent 理解授权边界与目标资产",
|
"scopeHint": "JSON 格式,供 Agent 理解授权边界与目标资产",
|
||||||
@@ -2518,14 +2524,6 @@
|
|||||||
"checkboxLinkTitle": "勾选表示本角色关联使用该工具"
|
"checkboxLinkTitle": "勾选表示本角色关联使用该工具"
|
||||||
},
|
},
|
||||||
"c2": {
|
"c2": {
|
||||||
"title": "C2 管理",
|
|
||||||
"welcomeTitle": "AI-Native C2 框架",
|
|
||||||
"welcomeDesc": "以 MCP 工具为一等公民,让 LLM 可以像调用 nmap 一样调用 C2 完成「上线 → 控制 → 任务 → 横向 → 清场」全流程",
|
|
||||||
"statListeners": "运行中监听器",
|
|
||||||
"statSessions": "在线会话",
|
|
||||||
"statPending": "待审任务",
|
|
||||||
"goListeners": "管理监听器",
|
|
||||||
"goSessions": "查看会话",
|
|
||||||
"clipboardCopied": "已复制到剪贴板",
|
"clipboardCopied": "已复制到剪贴板",
|
||||||
"fmt": {
|
"fmt": {
|
||||||
"durationMs": "{{n}}ms",
|
"durationMs": "{{n}}ms",
|
||||||
|
|||||||
@@ -321,7 +321,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch(pageId) {
|
switch(pageId) {
|
||||||
case 'c2':
|
|
||||||
case 'c2-listeners':
|
case 'c2-listeners':
|
||||||
C2.loadListeners();
|
C2.loadListeners();
|
||||||
break;
|
break;
|
||||||
@@ -370,7 +369,6 @@
|
|||||||
C2.profiles = pdata.profiles;
|
C2.profiles = pdata.profiles;
|
||||||
}
|
}
|
||||||
C2.renderListeners();
|
C2.renderListeners();
|
||||||
C2.updateDashboardStats();
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -736,7 +734,6 @@
|
|||||||
return apiRequest('GET', `${API_BASE}/sessions`).then(data => {
|
return apiRequest('GET', `${API_BASE}/sessions`).then(data => {
|
||||||
C2.sessions = data.sessions || [];
|
C2.sessions = data.sessions || [];
|
||||||
C2.renderSessions();
|
C2.renderSessions();
|
||||||
C2.updateDashboardStats();
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2037,7 +2034,6 @@
|
|||||||
C2.renderTasks();
|
C2.renderTasks();
|
||||||
C2.renderTasksPagination();
|
C2.renderTasksPagination();
|
||||||
C2.syncTasksToolbar();
|
C2.syncTasksToolbar();
|
||||||
C2.updateDashboardStats();
|
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
showToast(err.message || String(err), 'error');
|
showToast(err.message || String(err), 'error');
|
||||||
});
|
});
|
||||||
@@ -2163,7 +2159,6 @@
|
|||||||
const tasks = data.tasks || [];
|
const tasks = data.tasks || [];
|
||||||
if (typeof data.pending_queued_count === 'number') {
|
if (typeof data.pending_queued_count === 'number') {
|
||||||
C2.tasksPendingQueuedCount = data.pending_queued_count;
|
C2.tasksPendingQueuedCount = data.pending_queued_count;
|
||||||
C2.updateDashboardStats();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
@@ -2819,7 +2814,6 @@
|
|||||||
showToast(`[${event.category}] ${event.message}`, event.level === 'critical' ? 'error' : 'info');
|
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)
|
lastUpdatedAt: 0, // 上次成功刷新的时间戳(ms)
|
||||||
dismissedAlertKey: null, // 当前会话中被用户「×」掉的告警内容指纹(同样的 reasons 不再弹)
|
dismissedAlertKey: null, // 当前会话中被用户「×」掉的告警内容指纹(同样的 reasons 不再弹)
|
||||||
lastResources: null, // 上一轮关键资源快照,用于判断是否首次有数据 / 智能 CTA
|
lastResources: null, // 上一轮关键资源快照,用于判断是否首次有数据 / 智能 CTA
|
||||||
|
recentFeedTab: 'vulns', // 最近漏洞 / 近期事实 Tab
|
||||||
|
lastProjectSummary: null, // 最近一次项目仪表盘摘要(供 Tab 切换时重绘)
|
||||||
};
|
};
|
||||||
|
|
||||||
async function refreshDashboard() {
|
async function refreshDashboard() {
|
||||||
@@ -57,6 +59,7 @@ async function refreshDashboard() {
|
|||||||
hideEl('dashboard-kpi-vuln-critical-badge');
|
hideEl('dashboard-kpi-vuln-critical-badge');
|
||||||
hideEl('dashboard-alert-banner');
|
hideEl('dashboard-alert-banner');
|
||||||
setRecentVulnsLoading();
|
setRecentVulnsLoading();
|
||||||
|
setRecentFactsLoading();
|
||||||
['tools', 'skills', 'knowledge', 'roles', 'agents', 'webshell'].forEach(function (k) {
|
['tools', 'skills', 'knowledge', 'roles', 'agents', 'webshell'].forEach(function (k) {
|
||||||
setEl('dashboard-resource-' + k, '…');
|
setEl('dashboard-resource-' + k, '…');
|
||||||
});
|
});
|
||||||
@@ -104,7 +107,8 @@ async function refreshDashboard() {
|
|||||||
openCriticalRes, openHighRes, openMediumRes, openLowRes, toolsConfigRes,
|
openCriticalRes, openHighRes, openMediumRes, openLowRes, toolsConfigRes,
|
||||||
hitlPendingRes, notificationsRes, externalMcpStatsRes,
|
hitlPendingRes, notificationsRes, externalMcpStatsRes,
|
||||||
webshellRes,
|
webshellRes,
|
||||||
c2ListenersRes, c2SessionsRes, c2TasksRes
|
c2ListenersRes, c2SessionsRes, c2TasksRes,
|
||||||
|
projectSummaryRes
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
fetchJson('/api/agent-loop/tasks'),
|
fetchJson('/api/agent-loop/tasks'),
|
||||||
fetchJson('/api/vulnerabilities/stats'),
|
fetchJson('/api/vulnerabilities/stats'),
|
||||||
@@ -134,7 +138,8 @@ async function refreshDashboard() {
|
|||||||
// C2 仪表盘条:监听器 / 会话 / 待处理任务(任务接口含 pending_queued_count)
|
// C2 仪表盘条:监听器 / 会话 / 待处理任务(任务接口含 pending_queued_count)
|
||||||
fetchJson('/api/c2/listeners'),
|
fetchJson('/api/c2/listeners'),
|
||||||
fetchJson('/api/c2/sessions?limit=500'),
|
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,说明又有新刷新启动了,丢弃本次结果
|
// 如果在 await 期间 controller 已被 abort,说明又有新刷新启动了,丢弃本次结果
|
||||||
@@ -387,6 +392,9 @@ async function refreshDashboard() {
|
|||||||
|
|
||||||
// 最近漏洞列表
|
// 最近漏洞列表
|
||||||
renderRecentVulns(recentVulnsRes);
|
renderRecentVulns(recentVulnsRes);
|
||||||
|
dashboardState.lastProjectSummary = projectSummaryRes;
|
||||||
|
renderRecentFacts(projectSummaryRes);
|
||||||
|
updateDashboardFeedTabBadge(projectSummaryRes);
|
||||||
|
|
||||||
// External MCP 健康度(同时拿到 down 数喂给 alert banner / 推荐操作)
|
// External MCP 健康度(同时拿到 down 数喂给 alert banner / 推荐操作)
|
||||||
var externalMcpDown = renderExternalMcpHealth(externalMcpStatsRes);
|
var externalMcpDown = renderExternalMcpHealth(externalMcpStatsRes);
|
||||||
@@ -454,6 +462,7 @@ async function refreshDashboard() {
|
|||||||
var c2secErr = document.getElementById('dashboard-section-c2');
|
var c2secErr = document.getElementById('dashboard-section-c2');
|
||||||
if (c2secErr) c2secErr.hidden = true;
|
if (c2secErr) c2secErr.hidden = true;
|
||||||
setRecentVulnsError();
|
setRecentVulnsError();
|
||||||
|
setRecentFactsError();
|
||||||
renderDashboardToolsBar(null);
|
renderDashboardToolsBar(null);
|
||||||
var ph = document.getElementById('dashboard-tools-pie-placeholder');
|
var ph = document.getElementById('dashboard-tools-pie-placeholder');
|
||||||
if (ph) { ph.style.removeProperty('display'); ph.textContent = (typeof window.t === 'function' ? window.t('dashboard.noCallData') : '暂无调用数据'); }
|
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 类(避免脏数据)
|
// 漏洞状态映射:把 status 字符串规整到 4 类(避免脏数据)
|
||||||
function statusKey(s) {
|
function statusKey(s) {
|
||||||
s = String(s || '').toLowerCase();
|
s = String(s || '').toLowerCase();
|
||||||
|
|||||||
@@ -574,8 +574,11 @@ async function loadProjectFacts() {
|
|||||||
const vulnLink = f.related_vulnerability_id
|
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>`
|
? `<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>
|
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-fact-category">${formatCategoryBadge(f.category)}</td>
|
||||||
<td class="cell-summary" title="${escapeHtml(f.summary)}">${escapeHtml(f.summary)}</td>
|
<td class="cell-summary" title="${escapeHtml(f.summary)}">${escapeHtml(f.summary)}</td>
|
||||||
<td>${formatFactBodyBadge(f)}</td>
|
<td>${formatFactBodyBadge(f)}</td>
|
||||||
@@ -1165,6 +1168,8 @@ function resetFactModalForm() {
|
|||||||
document.getElementById('fact-modal-summary').value = '';
|
document.getElementById('fact-modal-summary').value = '';
|
||||||
document.getElementById('fact-modal-body').value = '';
|
document.getElementById('fact-modal-body').value = '';
|
||||||
document.getElementById('fact-modal-confidence').value = 'tentative';
|
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');
|
const rel = document.getElementById('fact-modal-related-vuln');
|
||||||
if (rel) rel.value = '';
|
if (rel) rel.value = '';
|
||||||
updateFactFormHints();
|
updateFactFormHints();
|
||||||
@@ -1198,6 +1203,8 @@ function fillFactModalForm(f) {
|
|||||||
}
|
}
|
||||||
const rel = document.getElementById('fact-modal-related-vuln');
|
const rel = document.getElementById('fact-modal-related-vuln');
|
||||||
if (rel) rel.value = f.related_vulnerability_id || '';
|
if (rel) rel.value = f.related_vulnerability_id || '';
|
||||||
|
const pinEl = document.getElementById('fact-modal-pinned');
|
||||||
|
if (pinEl) pinEl.checked = !!f.pinned;
|
||||||
updateFactFormHints();
|
updateFactFormHints();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1242,6 +1249,7 @@ async function saveFactModal() {
|
|||||||
summary,
|
summary,
|
||||||
body,
|
body,
|
||||||
confidence: document.getElementById('fact-modal-confidence').value,
|
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() || '',
|
related_vulnerability_id: document.getElementById('fact-modal-related-vuln')?.value?.trim() || '',
|
||||||
};
|
};
|
||||||
const editId = window._factModalEditId;
|
const editId = window._factModalEditId;
|
||||||
|
|||||||
@@ -56,8 +56,9 @@ function initRouter() {
|
|||||||
const hash = window.location.hash.slice(1);
|
const hash = window.location.hash.slice(1);
|
||||||
if (hash) {
|
if (hash) {
|
||||||
const hashParts = hash.split('?');
|
const hashParts = hash.split('?');
|
||||||
const pageId = hashParts[0];
|
let 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)) {
|
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);
|
switchPage(pageId);
|
||||||
if (pageId === 'chat') {
|
if (pageId === 'chat') {
|
||||||
scheduleChatConversationFromHash(500);
|
scheduleChatConversationFromHash(500);
|
||||||
@@ -464,7 +465,6 @@ async function initPage(pageId) {
|
|||||||
loadMarkdownAgents();
|
loadMarkdownAgents();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'c2':
|
|
||||||
case 'c2-listeners':
|
case 'c2-listeners':
|
||||||
case 'c2-sessions':
|
case 'c2-sessions':
|
||||||
case 'c2-tasks':
|
case 'c2-tasks':
|
||||||
@@ -494,9 +494,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
const hash = window.location.hash.slice(1);
|
const hash = window.location.hash.slice(1);
|
||||||
// 处理带参数的hash(如 chat?conversation=xxx)
|
// 处理带参数的hash(如 chat?conversation=xxx)
|
||||||
const hashParts = hash.split('?');
|
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);
|
switchPage(pageId);
|
||||||
if (pageId === 'chat') {
|
if (pageId === 'chat') {
|
||||||
scheduleChatConversationFromHash(200);
|
scheduleChatConversationFromHash(200);
|
||||||
|
|||||||
+22
-53
@@ -221,7 +221,6 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-submenu" id="submenu-c2">
|
<div class="nav-submenu" id="submenu-c2">
|
||||||
<div class="nav-submenu-item" data-page="c2" onclick="switchPage('c2')" data-i18n="nav.c2Manage">C2 管理</div>
|
|
||||||
<div class="nav-submenu-item" data-page="c2-listeners" onclick="switchPage('c2-listeners')" data-i18n="nav.c2Listeners">监听器</div>
|
<div class="nav-submenu-item" data-page="c2-listeners" onclick="switchPage('c2-listeners')" data-i18n="nav.c2Listeners">监听器</div>
|
||||||
<div class="nav-submenu-item" data-page="c2-sessions" onclick="switchPage('c2-sessions')" data-i18n="nav.c2Sessions">会话</div>
|
<div class="nav-submenu-item" data-page="c2-sessions" onclick="switchPage('c2-sessions')" data-i18n="nav.c2Sessions">会话</div>
|
||||||
<div class="nav-submenu-item" data-page="c2-tasks" onclick="switchPage('c2-tasks')" data-i18n="nav.c2Tasks">任务</div>
|
<div class="nav-submenu-item" data-page="c2-tasks" onclick="switchPage('c2-tasks')" data-i18n="nav.c2Tasks">任务</div>
|
||||||
@@ -574,20 +573,30 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="dashboard-section dashboard-section-recent-vulns">
|
<section class="dashboard-section dashboard-section-recent-feed">
|
||||||
<div class="dashboard-section-header">
|
<div class="dashboard-section-header dashboard-section-header--tabs">
|
||||||
<h3 class="dashboard-section-title" data-i18n="dashboard.recentVulns">最近漏洞</h3>
|
<nav class="dashboard-feed-tabs" role="tablist" aria-label="最近漏洞与近期事实">
|
||||||
<a class="dashboard-section-link" onclick="switchPage('vulnerabilities')" data-i18n="dashboard.viewAll">查看全部 →</a>
|
<button type="button" class="dashboard-feed-tab is-active" role="tab" id="dashboard-feed-tab-vulns" aria-selected="true" aria-controls="dashboard-feed-panel-vulns" onclick="switchDashboardFeedTab('vulns')" data-i18n="dashboard.recentVulns">最近漏洞</button>
|
||||||
|
<button type="button" class="dashboard-feed-tab" role="tab" id="dashboard-feed-tab-facts" aria-selected="false" aria-controls="dashboard-feed-panel-facts" onclick="switchDashboardFeedTab('facts')"><span data-i18n="dashboard.recentFacts">近期事实</span><span class="dashboard-feed-tab-badge" id="dashboard-feed-tab-facts-badge" hidden></span></button>
|
||||||
|
</nav>
|
||||||
|
<a class="dashboard-section-link" id="dashboard-feed-view-all" onclick="switchPage('vulnerabilities')" data-i18n="dashboard.viewAll">查看全部 →</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="dashboard-recent-vulns" id="dashboard-recent-vulns">
|
<div class="dashboard-feed-panel" id="dashboard-feed-panel-vulns" role="tabpanel" aria-labelledby="dashboard-feed-tab-vulns">
|
||||||
<div class="dashboard-recent-vulns-empty" id="dashboard-recent-vulns-empty" data-i18n="dashboard.noVulnYet">暂无最近漏洞</div>
|
<div class="dashboard-recent-vulns" id="dashboard-recent-vulns">
|
||||||
|
<div class="dashboard-recent-vulns-empty" id="dashboard-recent-vulns-empty" data-i18n="dashboard.noVulnYet">暂无最近漏洞</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dashboard-feed-panel" id="dashboard-feed-panel-facts" role="tabpanel" aria-labelledby="dashboard-feed-tab-facts" hidden>
|
||||||
|
<div class="dashboard-recent-facts" id="dashboard-recent-facts">
|
||||||
|
<div class="dashboard-recent-facts-empty" id="dashboard-recent-facts-empty" data-i18n="dashboard.noFactsYet">暂无近期事实</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<!-- C2 概览:介于「最近漏洞」与「批量任务队列」之间 -->
|
<!-- C2 概览:介于「最近漏洞」与「批量任务队列」之间 -->
|
||||||
<section class="dashboard-section dashboard-section-c2" id="dashboard-section-c2" hidden>
|
<section class="dashboard-section dashboard-section-c2" id="dashboard-section-c2" hidden>
|
||||||
<div class="dashboard-section-header">
|
<div class="dashboard-section-header">
|
||||||
<h3 class="dashboard-section-title" data-i18n="dashboard.c2OverviewTitle">C2 概览</h3>
|
<h3 class="dashboard-section-title" data-i18n="dashboard.c2OverviewTitle">C2 概览</h3>
|
||||||
<a class="dashboard-section-link" onclick="switchPage('c2')" data-i18n="dashboard.c2GoManage">进入 C2 →</a>
|
<a class="dashboard-section-link" onclick="switchPage('c2-listeners')" data-i18n="dashboard.c2GoManage">进入 C2 →</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="dashboard-c2-strip">
|
<div class="dashboard-c2-strip">
|
||||||
<div class="dashboard-c2-stat" role="button" tabindex="0" onclick="switchPage('c2-listeners')" onkeydown="if(event.key==='Enter'||event.key===' ') { event.preventDefault(); switchPage('c2-listeners'); }" data-i18n="dashboard.c2ClickListeners" data-i18n-attr="title" title="查看监听器">
|
<div class="dashboard-c2-stat" role="button" tabindex="0" onclick="switchPage('c2-listeners')" onkeydown="if(event.key==='Enter'||event.key===' ') { event.preventDefault(); switchPage('c2-listeners'); }" data-i18n="dashboard.c2ClickListeners" data-i18n-attr="title" title="查看监听器">
|
||||||
@@ -2005,51 +2014,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- C2 管理页面容器(各子页面通过 JS 动态渲染) -->
|
|
||||||
<div id="page-c2" class="page">
|
|
||||||
<div class="page-header">
|
|
||||||
<h2 data-i18n="c2.title">C2 管理</h2>
|
|
||||||
</div>
|
|
||||||
<div class="page-content" id="c2-content">
|
|
||||||
<div class="c2-layout">
|
|
||||||
<div id="c2-main" class="c2-main">
|
|
||||||
<div class="c2-welcome">
|
|
||||||
<div class="c2-welcome-icon">
|
|
||||||
<svg width="72" height="72" viewBox="0 0 24 24" fill="none" stroke="url(#c2-grad)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
||||||
<defs><linearGradient id="c2-grad" x1="0" y1="0" x2="1" y2="1"><stop offset="0%" stop-color="#00d4ff"/><stop offset="100%" stop-color="#a855f7"/></linearGradient></defs>
|
|
||||||
<path d="M4.9 19.1C1 15.2 1 8.8 4.9 4.9"></path>
|
|
||||||
<path d="M7.8 16.2c-2.3-2.3-2.3-6.1 0-8.5"></path>
|
|
||||||
<circle cx="12" cy="12" r="2"></circle>
|
|
||||||
<path d="M16.2 7.8c2.3 2.3 2.3 6.1 0 8.5"></path>
|
|
||||||
<path d="M19.1 4.9C23 8.8 23 15.2 19.1 19"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<h3 data-i18n="c2.welcomeTitle">AI-Native C2 框架</h3>
|
|
||||||
<p data-i18n="c2.welcomeDesc">以 MCP 工具为一等公民,让 LLM 可以像调用 nmap 一样调用 C2 完成"上线 → 控制 → 任务 → 横向 → 清场"全流程</p>
|
|
||||||
<div class="c2-stats" id="c2-dashboard-stats">
|
|
||||||
<div class="c2-stat-item">
|
|
||||||
<span class="c2-stat-value" id="c2-stat-listeners">-</span>
|
|
||||||
<span class="c2-stat-label" data-i18n="c2.statListeners">运行中监听器</span>
|
|
||||||
</div>
|
|
||||||
<div class="c2-stat-item">
|
|
||||||
<span class="c2-stat-value" id="c2-stat-sessions">-</span>
|
|
||||||
<span class="c2-stat-label" data-i18n="c2.statSessions">在线会话</span>
|
|
||||||
</div>
|
|
||||||
<div class="c2-stat-item">
|
|
||||||
<span class="c2-stat-value" id="c2-stat-pending">-</span>
|
|
||||||
<span class="c2-stat-label" data-i18n="c2.statPending">待审任务</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="c2-actions">
|
|
||||||
<button class="btn-primary" onclick="switchPage('c2-listeners')" data-i18n="c2.goListeners">管理监听器</button>
|
|
||||||
<button class="btn-secondary" onclick="switchPage('c2-sessions')" data-i18n="c2.goSessions">查看会话</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- C2 监听器管理页面 -->
|
<!-- C2 监听器管理页面 -->
|
||||||
<div id="page-c2-listeners" class="page">
|
<div id="page-c2-listeners" class="page">
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
@@ -4260,6 +4224,11 @@
|
|||||||
<textarea id="fact-modal-body" class="form-input fact-modal-body-input" rows="14" placeholder="攻击链步骤、HTTP/命令 POC、响应现象、证据…" oninput="updateFactFormHints()"></textarea>
|
<textarea id="fact-modal-body" class="form-input fact-modal-body-input" rows="14" placeholder="攻击链步骤、HTTP/命令 POC、响应现象、证据…" oninput="updateFactFormHints()"></textarea>
|
||||||
<p id="fact-modal-body-hint" class="projects-field-hint" role="status"></p>
|
<p id="fact-modal-body-hint" class="projects-field-hint" role="status"></p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="projects-form-field">
|
||||||
|
<label class="projects-filter-check projects-pin-toggle">
|
||||||
|
<input type="checkbox" id="fact-modal-pinned"> <span data-i18n="projects.pinFact">置顶事实(列表与黑板索引优先)</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<div class="projects-form-field">
|
<div class="projects-form-field">
|
||||||
<label for="fact-modal-related-vuln" data-i18n="projects.relatedVulnIdLabel">关联漏洞 ID</label>
|
<label for="fact-modal-related-vuln" data-i18n="projects.relatedVulnIdLabel">关联漏洞 ID</label>
|
||||||
<input type="text" id="fact-modal-related-vuln" class="form-input" placeholder="可选" data-i18n="projects.optional" data-i18n-attr="placeholder">
|
<input type="text" id="fact-modal-related-vuln" class="form-input" placeholder="可选" data-i18n="projects.optional" data-i18n-attr="placeholder">
|
||||||
|
|||||||
Reference in New Issue
Block a user