mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-07-19 10:28:09 +02:00
Add files via upload
This commit is contained in:
@@ -16,6 +16,8 @@
|
||||
--bg-primary: #ffffff;
|
||||
--bg-secondary: #f8f9fa;
|
||||
--bg-tertiary: #f1f3f5;
|
||||
--card-bg: var(--bg-primary);
|
||||
--input-bg: var(--bg-primary);
|
||||
--text-primary: #1a1a1a;
|
||||
--text-secondary: #6c757d;
|
||||
--text-muted: #adb5bd;
|
||||
@@ -48,6 +50,8 @@ html[data-theme="dark"] {
|
||||
--bg-primary: #111827;
|
||||
--bg-secondary: #0b1120;
|
||||
--bg-tertiary: #1f2937;
|
||||
--card-bg: #111827;
|
||||
--input-bg: #0b1120;
|
||||
--text-primary: #e5e7eb;
|
||||
--text-secondary: #a7b0c0;
|
||||
--text-muted: #6b7280;
|
||||
@@ -221,6 +225,36 @@ html[data-theme="dark"] .main-sidebar {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.nav-section-label {
|
||||
padding: 16px 19px 6px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
white-space: nowrap;
|
||||
opacity: 0.72;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.nav-section-label:first-child {
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.main-sidebar.collapsed .nav-section-label {
|
||||
height: 1px;
|
||||
margin: 8px 12px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
background: var(--border-color);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.main-sidebar.collapsed .nav-section-label span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
@@ -31348,6 +31382,7 @@ html[data-theme="dark"] .chat-files-toast.chat-toast--error {
|
||||
background: #fff;
|
||||
}
|
||||
#project-panel-facts,
|
||||
#project-panel-assets,
|
||||
#project-panel-conversations,
|
||||
#project-panel-vulns {
|
||||
display: flex;
|
||||
@@ -31357,10 +31392,12 @@ html[data-theme="dark"] .chat-files-toast.chat-toast--error {
|
||||
}
|
||||
#project-panel-facts .projects-fact-toolbar,
|
||||
#project-panel-vulns .projects-fact-toolbar,
|
||||
#project-panel-assets .projects-panel-toolbar,
|
||||
#project-panel-conversations .projects-panel-toolbar {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
#project-panel-facts .projects-table-wrap,
|
||||
#project-panel-assets .projects-table-wrap,
|
||||
#project-panel-conversations .projects-table-wrap,
|
||||
#project-panel-vulns .projects-table-wrap {
|
||||
flex: 1 1 auto;
|
||||
@@ -31370,6 +31407,7 @@ html[data-theme="dark"] .chat-files-toast.chat-toast--error {
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
#project-panel-conversations .projects-table-wrap,
|
||||
#project-panel-assets .projects-table-wrap,
|
||||
#project-panel-vulns .projects-table-wrap {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
@@ -31377,6 +31415,7 @@ html[data-theme="dark"] .chat-files-toast.chat-toast--error {
|
||||
overflow-x: auto;
|
||||
}
|
||||
#project-panel-facts .projects-table-wrap .data-table--projects thead th,
|
||||
#project-panel-assets .projects-table-wrap .data-table--projects thead th,
|
||||
#project-panel-conversations .projects-table-wrap .data-table--projects thead th,
|
||||
#project-panel-vulns .projects-table-wrap .data-table--projects thead th {
|
||||
position: sticky;
|
||||
@@ -31394,6 +31433,16 @@ html[data-theme="dark"] .chat-files-toast.chat-toast--error {
|
||||
.projects-panel-toolbar--hint .projects-fact-toolbar-hint {
|
||||
margin: 0;
|
||||
}
|
||||
.projects-panel-count { flex-shrink: 0; padding: 4px 9px; border-radius: 999px; color: #2563eb; background: #eff6ff; font-size: .75rem; font-weight: 600; }
|
||||
.projects-asset-target { display: block; max-width: 100%; padding: 0; border: 0; background: transparent; color: #2563eb; font: inherit; font-weight: 600; cursor: pointer; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.projects-asset-target:hover { text-decoration: underline; }
|
||||
#project-panel-assets .data-table--projects th:nth-child(1), #project-panel-assets .data-table--projects td:nth-child(1) { width: 24%; }
|
||||
#project-panel-assets .data-table--projects th:nth-child(2), #project-panel-assets .data-table--projects td:nth-child(2) { width: 12%; }
|
||||
#project-panel-assets .data-table--projects th:nth-child(3), #project-panel-assets .data-table--projects td:nth-child(3) { width: 22%; }
|
||||
#project-panel-assets .data-table--projects th:nth-child(4), #project-panel-assets .data-table--projects td:nth-child(4) { width: 10%; }
|
||||
#project-panel-assets .data-table--projects th:nth-child(5), #project-panel-assets .data-table--projects td:nth-child(5) { width: 16%; }
|
||||
#project-panel-assets .data-table--projects th:nth-child(6), #project-panel-assets .data-table--projects td:nth-child(6) { width: 8%; }
|
||||
#project-panel-assets .data-table--projects th:nth-child(7), #project-panel-assets .data-table--projects td:nth-child(7) { width: 8%; }
|
||||
#project-panel-facts .data-table--projects .cell-fact-key {
|
||||
overflow: hidden;
|
||||
max-width: 0;
|
||||
@@ -39899,3 +39948,407 @@ html[data-theme="dark"] .workflow-toolbar #workflow-connect-btn[aria-pressed="tr
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
}
|
||||
/* Asset management */
|
||||
.asset-page-subtitle { margin: 5px 0 0; color: var(--text-secondary, #6b7280); font-size: 13px; font-weight: 400; }
|
||||
.asset-content { display: flex; flex-direction: column; gap: 20px; }
|
||||
#page-asset-library .asset-content { overflow: hidden; }
|
||||
#page-asset-library .asset-toolbar { flex: 0 0 auto; }
|
||||
.asset-overview-summary { display: grid; grid-template-columns: minmax(0, 1.65fr) minmax(270px, .75fr); gap: 16px; }
|
||||
.asset-total-card,.asset-recent-card,.asset-panel { border: 1px solid var(--border-color, #e5e7eb); border-radius: 16px; background: var(--card-bg, #fff); box-shadow: 0 8px 24px rgba(15,23,42,.035); }
|
||||
.asset-total-card { min-width: 0; padding: 22px 24px 20px; }
|
||||
.asset-total-card__heading { display: flex; align-items: center; gap: 12px; }
|
||||
.asset-total-card__heading p { margin: 4px 0 0; color: var(--text-secondary, #6b7280); font-size: 12px; }
|
||||
.asset-overview-icon { display: grid; width: 40px; height: 40px; flex: 0 0 40px; place-items: center; border-radius: 11px; }
|
||||
.asset-overview-icon svg { width: 21px; height: 21px; }
|
||||
.asset-overview-icon--blue { background: rgba(37,99,235,.11); color: #3b82f6; }
|
||||
.asset-overview-icon--violet { background: rgba(99,102,241,.11); color: #6366f1; }
|
||||
.asset-eyebrow { color: var(--text-primary, #111827); font-size: 14px; font-weight: 650; }
|
||||
.asset-total-value { display: block; margin: 24px 0 22px; color: var(--text-primary, #111827); font-size: clamp(42px, 4vw, 58px); font-weight: 720; letter-spacing: -.045em; line-height: .9; }
|
||||
.asset-type-grid { display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); overflow: hidden; border: 1px solid var(--border-color, #e5e7eb); border-radius: 11px; background: var(--bg-secondary, #f8fafc); }
|
||||
.asset-type-item { display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 8px; min-width: 0; padding: 13px 14px; }
|
||||
.asset-type-item + .asset-type-item { border-left: 1px solid var(--border-color, #e5e7eb); }
|
||||
.asset-type-item > span:not(.asset-type-dot) { overflow: hidden; color: var(--text-secondary, #64748b); font-size: 12px; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.asset-type-item strong { color: var(--text-primary, #111827); font-size: 16px; }
|
||||
.asset-type-dot { width: 7px; height: 7px; border-radius: 50%; background: #3b82f6; box-shadow: 0 0 0 4px rgba(59,130,246,.10); }
|
||||
.asset-type-item--domain .asset-type-dot { background: #8b5cf6; box-shadow: 0 0 0 4px rgba(139,92,246,.10); }
|
||||
.asset-type-item--port .asset-type-dot { background: #14b8a6; box-shadow: 0 0 0 4px rgba(20,184,166,.10); }
|
||||
.asset-recent-card { display: flex; min-width: 0; flex-direction: column; overflow: hidden; padding: 22px; color: var(--text-primary,#111827); }
|
||||
.asset-recent-card__top { display: flex; align-items: center; justify-content: space-between; }
|
||||
.asset-recent-badge { padding: 5px 8px; border: 1px solid var(--border-color,#e5e7eb); border-radius: 999px; background: var(--bg-secondary,#f8fafc); color: var(--text-secondary,#64748b); font-size: 10px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; }
|
||||
.asset-recent-card__body { display: flex; margin-top: auto; flex-direction: column; gap: 7px; padding: 25px 0 18px; }
|
||||
.asset-recent-card__body span { color: var(--text-secondary,#64748b); font-size: 13px; }
|
||||
.asset-recent-card__body strong { font-size: clamp(38px,3.5vw,52px); font-weight: 720; letter-spacing: -.04em; line-height: 1; }
|
||||
.asset-recent-progress { height: 5px; overflow: hidden; border-radius: 999px; background: var(--bg-secondary,#eef2f7); }
|
||||
.asset-recent-progress i { display: block; width: 0; height: 100%; border-radius: inherit; background: linear-gradient(90deg,#3b82f6,#8b5cf6); transition: width .35s ease; }
|
||||
.asset-recent-caption { margin: 9px 0 0; color: var(--text-secondary,#64748b); font-size: 11px; }
|
||||
.asset-insight-heading { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 4px 2px 0; }
|
||||
.asset-insight-heading h3 { margin: 0; color: var(--text-primary,#111827); font-size: 17px; }
|
||||
.asset-insight-heading p { margin: 5px 0 0; color: var(--text-secondary,#64748b); font-size: 12px; }
|
||||
.asset-period-switch { display: inline-flex; padding: 3px; border: 1px solid var(--border-color,#e5e7eb); border-radius: 9px; background: var(--card-bg,#fff); }
|
||||
.asset-period-switch button { min-width: 54px; height: 29px; padding: 0 10px; border: 0; border-radius: 6px; background: transparent; color: var(--text-secondary,#64748b); cursor: pointer; font: inherit; font-size: 11px; }
|
||||
.asset-period-switch button:hover { color: var(--text-primary,#111827); }
|
||||
.asset-period-switch button.active { background: rgba(59,130,246,.12); color: #2563eb; font-weight: 650; }
|
||||
.asset-trend-grid { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); gap: 16px; }
|
||||
.asset-trend-panel { min-width: 0; }
|
||||
.asset-trend-panel .asset-panel-header { min-height: 42px; margin-bottom: 14px; }
|
||||
.asset-trend-header { display: grid; grid-template-columns: minmax(190px,1fr) auto; grid-template-areas: "heading legend" "heading summary"; align-items: center; column-gap: 24px; row-gap: 8px; }
|
||||
.asset-trend-heading { grid-area: heading; min-width: 0; }
|
||||
.asset-trend-header .asset-chart-legend { grid-area: legend; }
|
||||
.asset-trend-header .asset-chart-summary { grid-area: summary; justify-content: flex-end; }
|
||||
.asset-chart-legend { display: flex; align-items: center; flex-wrap: wrap; justify-content: flex-end; gap: 12px; }
|
||||
.asset-chart-legend span { display: inline-flex; align-items: center; gap: 5px; color: var(--text-secondary,#64748b); font-size: 10px; white-space: nowrap; }
|
||||
.asset-chart-legend span::before { width: 7px; height: 7px; border-radius: 50%; background: #3b82f6; content: ''; }
|
||||
.asset-chart-legend .asset-legend-inactive::before { background: #8b5cf6; }
|
||||
.asset-chart-legend .asset-legend-risk::before { background: #f59e0b; }
|
||||
.asset-chart-legend .asset-legend-high::before { background: #ef4444; }
|
||||
.asset-line-chart { min-height: 220px; }
|
||||
.asset-line-chart svg { display: block; width: 100%; height: 220px; overflow: visible; color: var(--text-muted,#94a3b8); }
|
||||
.asset-chart-grid line { stroke: var(--border-color,#e5e7eb); stroke-width: 1; stroke-dasharray: 3 4; }
|
||||
.asset-chart-grid text { fill: currentColor; font-size: 9px; }
|
||||
.asset-chart-summary { display: flex; align-items: center; gap: 18px; padding: 0; }
|
||||
.asset-chart-summary div { display: flex; align-items: baseline; gap: 6px; white-space: nowrap; }
|
||||
.asset-chart-summary span { color: var(--text-secondary,#64748b); font-size: 10px; }
|
||||
.asset-chart-summary strong { font-size: 17px; line-height: 1; }
|
||||
.asset-coverage-layout { display: grid; grid-template-columns: 180px minmax(0,1fr); align-items: center; gap: 28px; }
|
||||
.asset-coverage-gauge { --asset-coverage-value: 0%; display: grid; width: 142px; height: 142px; margin: 2px auto; place-items: center; border-radius: 50%; background: conic-gradient(#3b82f6 0 var(--asset-coverage-value),var(--bg-secondary,#eef2f7) var(--asset-coverage-value) 100%); }
|
||||
.asset-coverage-gauge::before { grid-area: 1/1; width: 104px; height: 104px; border-radius: 50%; background: var(--card-bg,#fff); content: ''; }
|
||||
.asset-coverage-gauge > div { grid-area: 1/1; z-index: 1; text-align: center; }
|
||||
.asset-coverage-gauge strong,.asset-coverage-gauge span { display: block; }
|
||||
.asset-coverage-gauge strong { color: var(--text-primary,#111827); font-size: 27px; letter-spacing: -.03em; }
|
||||
.asset-coverage-gauge span { margin-top: 5px; color: var(--text-secondary,#64748b); font-size: 10px; }
|
||||
.asset-coverage-metrics { display: grid; grid-template-columns: repeat(4,minmax(0,1fr)); overflow: hidden; border: 1px solid var(--border-color,#e5e7eb); border-radius: 12px; background: var(--bg-secondary,#f8fafc); }
|
||||
.asset-coverage-metrics > div { min-width: 0; padding: 17px 18px; }
|
||||
.asset-coverage-metrics > div + div { border-left: 1px solid var(--border-color,#e5e7eb); }
|
||||
.asset-coverage-metrics span,.asset-coverage-metrics strong,.asset-coverage-metrics small { display: block; }
|
||||
.asset-coverage-metrics span { color: var(--text-secondary,#64748b); font-size: 11px; }
|
||||
.asset-coverage-metrics strong { margin-top: 12px; color: var(--text-primary,#111827); font-size: 25px; letter-spacing: -.025em; }
|
||||
.asset-coverage-metrics small { margin-top: 7px; overflow: hidden; color: var(--text-muted,#94a3b8); font-size: 9px; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.asset-coverage-metrics .asset-coverage-gap strong { color: #f59e0b; }
|
||||
.asset-panel { padding: 22px 24px 24px; }
|
||||
.asset-panel-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 22px; }
|
||||
.asset-panel-header h3 { margin: 0; font-size: 16px; }
|
||||
.asset-panel-header p { margin: 6px 0 0; color: var(--text-secondary, #64748b); font-size: 12px; }
|
||||
.asset-panel-meta { flex: 0 0 auto; padding: 5px 9px; border-radius: 999px; background: var(--bg-secondary, #f1f5f9); color: var(--text-secondary, #64748b); font-size: 11px; }
|
||||
.asset-protocol-layout { display: grid; grid-template-columns: 300px minmax(0,1fr); gap: 24px; align-items: stretch; }
|
||||
.asset-protocol-summary { display: flex; min-height: 320px; flex-direction: column; padding: 20px; border: 1px solid var(--border-color, #e5e7eb); border-radius: 13px; background: var(--bg-secondary, #f8fafc); }
|
||||
.asset-protocol-summary[hidden] { display: none; }
|
||||
.asset-protocol-summary__label { color: var(--text-secondary,#64748b); font-size: 11px; font-weight: 600; }
|
||||
.asset-protocol-summary__main { display: flex; align-items: center; gap: 20px; padding: 24px 0; }
|
||||
.asset-protocol-donut { display: grid; width: 126px; height: 126px; flex: 0 0 126px; place-items: center; border-radius: 50%; background: conic-gradient(from -90deg,#6366f1 0 0%,var(--border-color,#e5e7eb) 0 100%); }
|
||||
.asset-protocol-donut::before { content: ''; grid-area: 1/1; width: 92px; height: 92px; border-radius: 50%; background: var(--card-bg,#fff); }
|
||||
.asset-protocol-donut > div { grid-area: 1/1; z-index: 1; text-align: center; }
|
||||
.asset-protocol-donut strong,.asset-protocol-donut span { display: block; }
|
||||
.asset-protocol-donut strong { color: var(--text-primary,#111827); font-size: 25px; }
|
||||
.asset-protocol-donut span { width: 74px; margin: 4px auto 0; color: var(--text-secondary,#64748b); font-size: 8px; }
|
||||
.asset-protocol-lead { min-width: 0; }
|
||||
.asset-protocol-lead span,.asset-protocol-lead strong,.asset-protocol-lead small { display: block; }
|
||||
.asset-protocol-lead span { color: var(--text-secondary,#64748b); font-size: 10px; }
|
||||
.asset-protocol-lead strong { margin-top: 8px; overflow: hidden; color: var(--text-primary,#111827); font-size: 20px; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.asset-protocol-lead small { margin-top: 5px; color: var(--text-secondary,#64748b); font-size: 11px; }
|
||||
.asset-protocol-top-list { display: flex; margin-top: auto; flex-direction: column; gap: 9px; padding-top: 16px; border-top: 1px solid var(--border-color,#e5e7eb); }
|
||||
.asset-protocol-top-list div { display: grid; grid-template-columns: 7px 1fr auto; align-items: center; gap: 8px; }
|
||||
.asset-protocol-top-list i { width: 7px; height: 7px; border-radius: 2px; }
|
||||
.asset-protocol-top-list span { overflow: hidden; color: var(--text-secondary,#64748b); font-size: 10px; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.asset-protocol-top-list strong { color: var(--text-primary,#111827); font-size: 10px; }
|
||||
.asset-protocol-ranking { min-width: 0; padding: 0 2px; }
|
||||
.asset-protocol-columns { display: grid; grid-template-columns: 26px 94px minmax(140px,1fr) 46px 42px; align-items: center; gap: 12px; margin-bottom: 7px; padding: 0 12px 9px; border-bottom: 1px solid var(--border-color,#e5e7eb); color: var(--text-muted,#94a3b8); font-size: 9px; }
|
||||
.asset-protocol-columns span:nth-last-child(-n+2) { text-align: right; }
|
||||
.asset-bar-chart { display: flex; min-width: 0; flex-direction: column; gap: 2px; }
|
||||
.asset-bar-row { display: grid; grid-template-columns: 26px 94px minmax(140px,1fr) 46px 42px; min-height: 35px; align-items: center; gap: 12px; padding: 3px 12px; border-radius: 7px; transition: background .15s; }
|
||||
.asset-bar-row:hover { background: var(--bg-secondary,#f8fafc); }
|
||||
.asset-bar-rank { color: var(--text-muted, #94a3b8); font: 10px ui-monospace,SFMono-Regular,Menlo,monospace; }
|
||||
.asset-bar-label { display: flex; align-items: center; gap: 7px; color: var(--text-primary,#111827); font-size: 11px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; }
|
||||
.asset-bar-label::before { width: 6px; height: 6px; flex: 0 0 6px; border-radius: 2px; background: var(--protocol-color,#3b82f6); content: ''; }
|
||||
.asset-bar-track { height: 6px; overflow: hidden; border-radius: 999px; background: var(--bg-secondary, #eef2f7); }
|
||||
.asset-bar-track i { display: block; height: 100%; border-radius: inherit; background: var(--protocol-color,#3b82f6); }
|
||||
.asset-bar-row strong,.asset-bar-row small { text-align: right; }
|
||||
.asset-bar-row strong { font-size: 11px; }
|
||||
.asset-bar-row small { color: var(--text-secondary, #6b7280); font-size: 11px; }
|
||||
.asset-toolbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 14px; border: 1px solid var(--border-color, #e5e7eb); border-radius: 12px; background: var(--card-bg, #fff); }
|
||||
.asset-toolbar input { min-width: 300px; flex: 1; }
|
||||
.asset-toolbar select { min-width: 130px; }
|
||||
.asset-toolbar input,.asset-toolbar select { height: 38px; padding: 0 12px; border: 1px solid var(--border-color, #d8dee8); border-radius: 8px; background: var(--input-bg, #fff); color: var(--text-primary, #111827); }
|
||||
.asset-custom-select { width: 100%; min-width: 0; font-weight: 400; }
|
||||
.asset-custom-select--filter { width: auto; min-width: 150px; flex: 0 0 auto; }
|
||||
.asset-custom-select .settings-custom-select-trigger { height: 42px; min-height: 42px; border-radius: 8px; background: var(--input-bg, #fff); font-size: 13px; font-weight: 400; }
|
||||
.asset-custom-select .settings-custom-select-caret { width: 16px; height: 16px; color: var(--text-secondary, #64748b); font-size: 0; }
|
||||
.asset-custom-select .settings-custom-select-caret::before { display: block; width: 7px; height: 7px; margin: 2px auto 0; border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor; content: ''; transform: rotate(45deg); }
|
||||
.asset-custom-select.open .settings-custom-select-caret { transform: rotate(180deg); }
|
||||
.asset-custom-select .settings-custom-select-menu { min-width: 100%; padding: 5px; border-radius: 9px; background: var(--card-bg, #fff); box-shadow: 0 12px 28px rgba(15,23,42,.16); }
|
||||
.asset-custom-select .settings-custom-select-option { min-height: 34px; border-radius: 6px; font-size: 13px; }
|
||||
.asset-custom-select--filter .settings-custom-select-trigger { height: 38px; min-height: 38px; padding: 7px 11px; }
|
||||
.asset-custom-select--pagination { width: 76px; flex: 0 0 76px; }
|
||||
.asset-custom-select--pagination .settings-custom-select-trigger { height: 32px; min-height: 32px; padding: 5px 9px; }
|
||||
.asset-custom-select--pagination .settings-custom-select-menu { top: auto; bottom: calc(100% + 6px); }
|
||||
.asset-batch-actions { display: flex; align-items: center; gap: 10px; padding: 10px 14px; border: 1px solid rgba(37,99,235,.32); border-radius: 10px; background: rgba(37,99,235,.07); }
|
||||
.asset-batch-actions[hidden] { display: none; }
|
||||
.asset-batch-actions > span { margin-right: auto; color: var(--text-primary, #111827); font-size: 13px; font-weight: 650; }
|
||||
.asset-list-meta { margin-left: auto; }
|
||||
.asset-list-card { overflow: hidden; border: 1px solid var(--border-color, #e5e7eb); border-radius: 12px; background: var(--card-bg, #fff); box-shadow: 0 1px 3px rgba(15,23,42,.03); }
|
||||
#page-asset-library .asset-list-card { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
|
||||
.asset-table-wrap { overflow: auto; border: 1px solid var(--border-color, #e5e7eb); border-radius: 12px; background: var(--card-bg, #fff); }
|
||||
.asset-list-card .asset-table-wrap { border: 0; border-radius: 0; }
|
||||
#page-asset-library .asset-list-card .asset-table-wrap { flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden; }
|
||||
.asset-list-card #asset-pagination.pagination-fixed { border: 0; border-top: 1px solid var(--border-color, #e5e7eb); border-radius: 0; box-shadow: none; }
|
||||
#page-asset-library .asset-list-card #asset-pagination { flex: 0 0 auto; }
|
||||
.asset-list-card #asset-pagination.pagination-fixed .pagination { border-radius: 0; }
|
||||
.asset-table { width: 100%; min-width: 0; table-layout: fixed; border-collapse: collapse; }
|
||||
.asset-table th,.asset-table td { padding: 13px 14px; border-bottom: 1px solid var(--border-color, #edf0f4); text-align: left; vertical-align: middle; }
|
||||
.asset-table th { position: sticky; top: 0; background: var(--card-bg, #fff); color: var(--text-secondary, #64748b); font-size: 12px; font-weight: 600; z-index: 1; }
|
||||
.asset-table td { font-size: 13px; }
|
||||
.asset-table td:nth-child(2) { width: 220px; max-width: 280px; }
|
||||
.asset-table th:nth-child(2),.asset-table td:nth-child(2) { width: auto; }
|
||||
.asset-table th:nth-child(3),.asset-table td:nth-child(3) { width: 112px; }
|
||||
.asset-table th:nth-child(4),.asset-table td:nth-child(4) { width: 140px; }
|
||||
.asset-table th:nth-child(5),.asset-table td:nth-child(5) { width: 180px; }
|
||||
.asset-table th:nth-child(6),.asset-table td:nth-child(6) { width: 96px; }
|
||||
.asset-table th:nth-child(7),.asset-table td:nth-child(7) { width: 84px; }
|
||||
.asset-table th:nth-child(8),.asset-table td:nth-child(8) { width: 84px; }
|
||||
.asset-table th:nth-child(9),.asset-table td:nth-child(9) { width: 176px; }
|
||||
.asset-check-cell { width: 42px; min-width: 42px; text-align: center !important; }
|
||||
.asset-check-cell input { width: 15px; height: 15px; cursor: pointer; accent-color: #2563eb; }
|
||||
.asset-table td strong,.asset-table td small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.asset-target-link { display: block; max-width: 100%; padding: 0; border: 0; background: transparent; color: #2563eb; font: inherit; font-weight: 650; text-align: left; cursor: pointer; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.asset-target-link:hover { text-decoration: underline; }
|
||||
.asset-table td small { margin-top: 4px; color: var(--text-secondary, #64748b); }
|
||||
.asset-tag { display: inline-block; margin: 5px 4px 0 0; padding: 2px 7px; border-radius: 999px; background: rgba(37,99,235,.1); color: #2563eb; font-size: 11px; }
|
||||
.asset-project-badge { display: inline-flex; max-width: 180px; padding: 3px 8px; border-radius: 6px; background: rgba(99,102,241,.1); color: #4f46e5; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.asset-service { display: block; max-width: 100%; overflow: hidden; color: inherit; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.asset-status { display: inline-flex; padding: 3px 9px; border-radius: 999px; font-size: 12px; background: rgba(16,185,129,.12); color: #059669; }
|
||||
.asset-status--inactive { background: rgba(100,116,139,.12); color: #64748b; }
|
||||
.asset-risk { display: inline-flex; justify-content: center; min-width: 46px; padding: 3px 8px; border-radius: 999px; font-size: 12px; font-weight: 650; background: rgba(100,116,139,.12); color: #64748b; }
|
||||
.asset-risk--critical { background: rgba(190,24,93,.16); color: #be185d; }
|
||||
.asset-risk--high { background: rgba(239,68,68,.15); color: #dc2626; }
|
||||
.asset-risk--medium { background: rgba(245,158,11,.16); color: #d97706; }
|
||||
.asset-risk--low { background: rgba(59,130,246,.14); color: #2563eb; }
|
||||
.asset-risk--info { background: rgba(99,102,241,.13); color: #4f46e5; }
|
||||
.asset-risk--normal { background: rgba(16,185,129,.13); color: #059669; }
|
||||
.asset-row-actions { white-space: nowrap; }
|
||||
.asset-row-actions .btn-link { margin-right: 8px; }
|
||||
.asset-row-actions .asset-delete { color: #dc2626; }
|
||||
.asset-vulnerability-link { min-width: 28px; padding: 3px 8px; border: 1px solid rgba(239,68,68,.28); border-radius: 999px; background: rgba(239,68,68,.10); color: #dc2626; font-weight: 700; cursor: pointer; }
|
||||
.asset-scan-content { width: min(700px, calc(100vw - 32px)); }
|
||||
.asset-project-content { width: min(500px, calc(100vw - 32px)); }
|
||||
.asset-project-content .form-hint { margin: 10px 0 0; color: var(--text-secondary, #64748b); font-size: 12px; line-height: 1.5; }
|
||||
.asset-project-content .settings-custom-select-trigger { height: 42px; border-radius: 8px; background: var(--input-bg, var(--bg-primary)); }
|
||||
.asset-project-content .settings-custom-select-menu { z-index: 2700; }
|
||||
.asset-project-content .settings-custom-select-option.is-disabled { display: none; }
|
||||
.asset-editor-field > .asset-custom-select { width: 100%; }
|
||||
.asset-editor-field > .asset-custom-select .settings-custom-select-trigger { width: 100%; }
|
||||
.asset-scan-targets { display: flex; flex-wrap: wrap; align-items: center; gap: 7px; margin-bottom: 18px; padding: 12px; border: 1px solid var(--border-color, #e5e7eb); border-radius: 10px; background: var(--bg-secondary, #f8fafc); }
|
||||
.asset-scan-target-chip { max-width: 220px; padding: 4px 9px; border-radius: 999px; background: rgba(37,99,235,.11); color: #2563eb; font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.asset-scan-prompt-label { display: block; margin-bottom: 8px; font-size: 13px; font-weight: 650; }
|
||||
.asset-scan-prompt { width: 100%; min-height: 150px; padding: 12px; resize: vertical; border: 1px solid var(--border-color, #d8dee8); border-radius: 9px; background: var(--input-bg, #fff); color: var(--text-primary, #111827); font: inherit; line-height: 1.6; }
|
||||
.asset-scan-modal .form-hint { display: block; margin: 8px 0 14px; color: var(--text-secondary, #64748b); }
|
||||
.asset-scan-modal #asset-scan-execute-wrap[hidden] { display: none !important; }
|
||||
.asset-pagination { display: flex; justify-content: flex-end; align-items: center; gap: 12px; }
|
||||
.asset-editor-content { width: min(840px, calc(100vw - 32px)); max-height: calc(100vh - 48px); margin: 24px auto; }
|
||||
.asset-editor-content .modal-header { padding: 20px 24px; }
|
||||
.asset-editor-content .modal-header > div { min-width: 0; }
|
||||
.asset-editor-content .modal-header h2 { font-size: 20px; }
|
||||
.asset-editor-subtitle { margin: 5px 0 0; color: var(--text-secondary, #64748b); font-size: 13px; font-weight: 400; }
|
||||
.asset-editor-body { padding: 22px 24px 24px; overscroll-behavior: contain; scrollbar-gutter: stable; }
|
||||
.asset-editor-content .modal-footer { position: relative; background: var(--bg-primary, #fff); box-shadow: 0 -8px 24px rgba(15,23,42,.04); }
|
||||
.asset-editor-primary { padding: 18px; border: 1px solid rgba(37,99,235,.22); border-radius: 12px; background: linear-gradient(135deg, rgba(37,99,235,.065), rgba(99,102,241,.025)); }
|
||||
.asset-editor-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
|
||||
.asset-editor-primary-grid { margin-top: 18px; }
|
||||
.asset-editor-field { display: flex; min-width: 0; flex-direction: column; gap: 7px; font-size: 13px; font-weight: 600; }
|
||||
.asset-editor-field > span { display: flex; align-items: center; gap: 4px; }
|
||||
.asset-editor-field > span b { color: #dc2626; font-size: 15px; }
|
||||
.asset-editor-field input,.asset-editor-field select { box-sizing: border-box; width: 100%; height: 42px; padding: 0 12px; border: 1px solid var(--border-color, #d8dee8); border-radius: 8px; outline: none; background: var(--input-bg, #fff); color: var(--text-primary, #111827); font: inherit; font-weight: 400; transition: border-color .15s, box-shadow .15s, background .15s; }
|
||||
.asset-editor-field input:focus,.asset-editor-field select:focus,.asset-tag-editor:focus-within { border-color: #2563eb; box-shadow: 0 0 0 3px rgba(37,99,235,.12); }
|
||||
.asset-editor-field input[aria-invalid="true"] { border-color: #dc2626; box-shadow: 0 0 0 3px rgba(220,38,38,.09); }
|
||||
.asset-editor-target-field input { height: 48px; font-size: 15px; }
|
||||
.asset-field-hint { color: var(--text-secondary, #64748b); font-size: 12px; font-weight: 400; line-height: 1.45; }
|
||||
.asset-field-error { color: #dc2626; font-size: 12px; font-weight: 500; line-height: 1.45; }
|
||||
.asset-form-error { margin-top: 14px; padding: 10px 12px; border: 1px solid rgba(220,38,38,.24); border-radius: 8px; background: rgba(220,38,38,.07); color: #b91c1c; font-size: 13px; line-height: 1.45; }
|
||||
.asset-tag-editor { display: flex; min-height: 42px; align-items: center; flex-wrap: wrap; gap: 6px; padding: 5px 8px; border: 1px solid var(--border-color, #d8dee8); border-radius: 8px; background: var(--input-bg, #fff); transition: border-color .15s, box-shadow .15s; }
|
||||
.asset-tag-chips { display: contents; }
|
||||
.asset-tag-chip { display: inline-flex; max-width: 160px; align-items: center; gap: 5px; padding: 3px 7px; border-radius: 999px; background: rgba(37,99,235,.11); color: #1d4ed8; font-size: 12px; font-weight: 500; }
|
||||
.asset-tag-chip span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.asset-tag-chip button { width: 16px; height: 16px; padding: 0; border: 0; border-radius: 50%; background: transparent; color: inherit; cursor: pointer; font-size: 14px; line-height: 14px; }
|
||||
.asset-tag-chip button:hover { background: rgba(37,99,235,.14); }
|
||||
.asset-tag-editor #asset-edit-tags { width: 130px; min-width: 90px; height: 30px; flex: 1; padding: 0 3px; border: 0; box-shadow: none; background: transparent; }
|
||||
.asset-editor-details { margin-top: 24px; }
|
||||
.asset-editor-section-group + .asset-editor-section-group { margin-top: 24px; padding-top: 24px; border-top: 1px solid var(--border-color, #e5e7eb); }
|
||||
.asset-editor-group-heading { display: flex; min-width: 0; align-items: baseline; gap: 10px; margin-bottom: 12px; }
|
||||
.asset-editor-group-heading::after { content: ''; height: 1px; min-width: 24px; flex: 1; background: var(--border-color, #e5e7eb); }
|
||||
.asset-editor-group-heading h3 { flex: 0 0 auto; margin: 0; color: var(--text-primary, #111827); font-size: 13px; font-weight: 650; line-height: 1.45; }
|
||||
.asset-editor-group-heading p { min-width: 0; margin: 0; color: var(--text-secondary, #64748b); font-size: 12px; font-weight: 400; line-height: 1.45; }
|
||||
.asset-editor-wide { grid-column: 1 / -1; }
|
||||
.asset-editor-submit-busy { pointer-events: none; opacity: .7; }
|
||||
.asset-detail-modal { overflow: hidden; }
|
||||
.asset-detail-modal .asset-detail-content {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: min(620px, 92vw);
|
||||
max-width: none;
|
||||
height: 100%;
|
||||
max-height: none;
|
||||
margin: 0;
|
||||
border-width: 0 0 0 1px;
|
||||
border-radius: 18px 0 0 18px;
|
||||
animation: assetDrawerIn .22s ease-out;
|
||||
}
|
||||
.asset-detail-modal .modal-header { border-radius: 18px 0 0 0; }
|
||||
.asset-detail-modal .modal-body { padding: 20px; }
|
||||
.asset-detail-modal .modal-footer { flex-shrink: 0; }
|
||||
@keyframes assetDrawerIn { from { opacity: .7; transform: translateX(100%); } to { opacity: 1; transform: translateX(0); } }
|
||||
.asset-detail-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1px; overflow: hidden; border: 1px solid var(--border-color, #e5e7eb); border-radius: 12px; background: var(--border-color, #e5e7eb); }
|
||||
.asset-detail-item { min-width: 0; padding: 15px 16px; background: var(--card-bg, #fff); }
|
||||
.asset-detail-item--wide { grid-column: 1 / -1; }
|
||||
.asset-detail-item > span { display: block; margin-bottom: 7px; color: var(--text-secondary, #64748b); font-size: 12px; }
|
||||
.asset-detail-item > div { min-height: 20px; color: var(--text-primary, #111827); overflow-wrap: anywhere; }
|
||||
.asset-detail-item code { display: block; padding: 9px; border-radius: 7px; background: var(--bg-secondary, #f4f6f8); white-space: pre-wrap; }
|
||||
|
||||
/* Asset management dark theme */
|
||||
html[data-theme="dark"] .asset-stat-card,
|
||||
html[data-theme="dark"] .asset-total-card,
|
||||
html[data-theme="dark"] .asset-recent-card,
|
||||
html[data-theme="dark"] .asset-panel,
|
||||
html[data-theme="dark"] .asset-toolbar,
|
||||
html[data-theme="dark"] .asset-list-card,
|
||||
html[data-theme="dark"] .asset-table-wrap,
|
||||
html[data-theme="dark"] .asset-table th,
|
||||
html[data-theme="dark"] .asset-scan-targets,
|
||||
html[data-theme="dark"] .asset-detail-item {
|
||||
background: var(--card-bg);
|
||||
border-color: var(--border-color);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .asset-overview-icon--violet {
|
||||
background: rgba(129, 140, 248, .14);
|
||||
color: #a5b4fc;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .asset-type-grid,
|
||||
html[data-theme="dark"] .asset-coverage-metrics,
|
||||
html[data-theme="dark"] .asset-protocol-summary {
|
||||
background: rgba(15, 23, 42, .38);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .asset-protocol-donut::before {
|
||||
background: var(--card-bg);
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .asset-coverage-gauge::before {
|
||||
background: var(--card-bg);
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .asset-period-switch button.active {
|
||||
background: rgba(96,165,250,.16);
|
||||
color: #93c5fd;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .asset-stat-card--accent {
|
||||
border-color: rgba(96, 165, 250, .38);
|
||||
background: linear-gradient(135deg, rgba(37, 99, 235, .20), rgba(124, 58, 237, .10));
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .asset-stat-card--accent strong,
|
||||
html[data-theme="dark"] .asset-target-link,
|
||||
html[data-theme="dark"] .asset-row-actions .btn-link {
|
||||
color: #60a5fa;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .asset-toolbar input,
|
||||
html[data-theme="dark"] .asset-toolbar select,
|
||||
html[data-theme="dark"] .asset-editor-field input,
|
||||
html[data-theme="dark"] .asset-editor-field select,
|
||||
html[data-theme="dark"] .asset-editor-grid input,
|
||||
html[data-theme="dark"] .asset-editor-grid select {
|
||||
background: var(--input-bg);
|
||||
border-color: var(--border-color);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .asset-toolbar input::placeholder,
|
||||
html[data-theme="dark"] .asset-editor-field input::placeholder,
|
||||
html[data-theme="dark"] .asset-editor-grid input::placeholder {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .asset-table tbody tr:hover td {
|
||||
background: rgba(96, 165, 250, .06);
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .asset-tag {
|
||||
background: rgba(96, 165, 250, .16);
|
||||
color: #93c5fd;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .asset-project-badge {
|
||||
background: rgba(167, 139, 250, .16);
|
||||
color: #c4b5fd;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .asset-status {
|
||||
background: rgba(52, 211, 153, .14);
|
||||
color: #6ee7b7;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .asset-status--inactive {
|
||||
background: rgba(148, 163, 184, .14);
|
||||
color: #cbd5e1;
|
||||
}
|
||||
html[data-theme="dark"] .asset-risk--critical { color: #f9a8d4; }
|
||||
html[data-theme="dark"] .asset-risk--high { color: #fca5a5; }
|
||||
html[data-theme="dark"] .asset-risk--medium { color: #fcd34d; }
|
||||
html[data-theme="dark"] .asset-risk--low { color: #93c5fd; }
|
||||
html[data-theme="dark"] .asset-risk--info { color: #c4b5fd; }
|
||||
html[data-theme="dark"] .asset-risk--normal { color: #6ee7b7; }
|
||||
|
||||
html[data-theme="dark"] .asset-row-actions .asset-delete {
|
||||
color: #f87171;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .asset-batch-actions { border-color: rgba(96,165,250,.36); background: rgba(37,99,235,.13); }
|
||||
html[data-theme="dark"] .asset-scan-prompt { background: var(--input-bg); border-color: var(--border-color); color: var(--text-primary); }
|
||||
html[data-theme="dark"] .asset-vulnerability-link { color: #fca5a5; border-color: rgba(248,113,113,.35); background: rgba(248,113,113,.12); }
|
||||
html[data-theme="dark"] .asset-custom-select .settings-custom-select-trigger,
|
||||
html[data-theme="dark"] .asset-custom-select .settings-custom-select-menu { background: var(--input-bg); border-color: var(--border-color); color: var(--text-primary); }
|
||||
html[data-theme="dark"] .asset-custom-select .settings-custom-select-menu { box-shadow: 0 14px 32px rgba(0,0,0,.34); }
|
||||
|
||||
html[data-theme="dark"] .asset-detail-grid {
|
||||
border-color: var(--border-color);
|
||||
background: var(--border-color);
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .asset-detail-item code {
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
html[data-theme="dark"] .asset-editor-primary { border-color: rgba(96,165,250,.32); background: linear-gradient(135deg, rgba(37,99,235,.18), rgba(99,102,241,.08)); }
|
||||
html[data-theme="dark"] .asset-tag-editor { border-color: var(--border-color); background: var(--card-bg); }
|
||||
html[data-theme="dark"] .asset-editor-content .modal-footer { background: var(--bg-primary); box-shadow: 0 -8px 24px rgba(0,0,0,.12); }
|
||||
html[data-theme="dark"] .asset-tag-chip { background: rgba(96,165,250,.16); color: #93c5fd; }
|
||||
html[data-theme="dark"] .asset-form-error { color: #fca5a5; }
|
||||
@media (max-width: 1180px) {
|
||||
.asset-overview-summary { grid-template-columns: minmax(0,1.45fr) minmax(240px,.75fr); }
|
||||
.asset-coverage-metrics { grid-template-columns: repeat(2,minmax(0,1fr)); }
|
||||
.asset-coverage-metrics > div:nth-child(3) { border-top: 1px solid var(--border-color,#e5e7eb); border-left: 0; }
|
||||
.asset-coverage-metrics > div:nth-child(4) { border-top: 1px solid var(--border-color,#e5e7eb); }
|
||||
.asset-protocol-layout { grid-template-columns: 1fr; }
|
||||
.asset-protocol-summary { width: auto; min-height: 0; }
|
||||
.asset-protocol-summary__main { justify-content: center; }
|
||||
.asset-protocol-top-list { display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); margin-top: 0; }
|
||||
}
|
||||
@media (max-width: 1050px) {
|
||||
.asset-table th:nth-child(4),.asset-table td:nth-child(4) { display: none; }
|
||||
}
|
||||
@media (max-width: 820px) {
|
||||
.asset-table th:nth-child(3),.asset-table td:nth-child(3) { display: none; }
|
||||
.asset-table th:nth-child(5),.asset-table td:nth-child(5) { width: 145px; }
|
||||
.asset-table th:nth-child(9),.asset-table td:nth-child(9) { width: 150px; }
|
||||
}
|
||||
@media (max-width: 760px) {
|
||||
.asset-overview-summary { grid-template-columns: 1fr; }
|
||||
.asset-trend-grid { grid-template-columns: 1fr; }
|
||||
.asset-insight-heading { align-items: flex-start; flex-direction: column; }
|
||||
.asset-coverage-layout { grid-template-columns: 1fr; }
|
||||
.asset-recent-card { min-height: 230px; }
|
||||
}
|
||||
@media (max-width: 640px) { .asset-editor-grid,.asset-detail-grid { grid-template-columns: 1fr; } .asset-total-card,.asset-recent-card,.asset-panel { padding: 18px; } .asset-type-grid,.asset-coverage-metrics { grid-template-columns: 1fr; } .asset-type-item + .asset-type-item,.asset-coverage-metrics > div + div,.asset-coverage-metrics > div:nth-child(4) { border-top: 1px solid var(--border-color,#e5e7eb); border-left: 0; } .asset-trend-header { grid-template-columns: 1fr auto; grid-template-areas: "heading summary"; column-gap: 12px; } .asset-chart-legend { display: none; } .asset-chart-summary { justify-content: flex-end; padding: 0; } .asset-chart-summary span { display: none; } .asset-protocol-summary { box-sizing: border-box; width: 100%; } .asset-protocol-summary__main { align-items: flex-start; flex-direction: column; } .asset-protocol-top-list { grid-template-columns: 1fr; } .asset-bar-row,.asset-protocol-columns { grid-template-columns: 20px 64px 1fr 36px; gap: 8px; padding-right: 4px; padding-left: 4px; } .asset-bar-row small,.asset-protocol-columns span:last-child { display: none; } .asset-editor-content { width: 100%; max-height: 100vh; min-height: 100vh; margin: 0; border-radius: 0; } .asset-editor-content .modal-header,.asset-editor-body,.asset-editor-content .modal-footer { padding-left: 16px; padding-right: 16px; } .asset-editor-subtitle { max-width: 270px; } .asset-editor-primary { padding: 14px; } .asset-editor-group-heading { align-items: flex-start; flex-wrap: wrap; gap: 3px 8px; } .asset-editor-group-heading p { width: 100%; } .asset-editor-section-group + .asset-editor-section-group { margin-top: 20px; padding-top: 20px; } .asset-editor-wide,.asset-detail-item--wide { grid-column: auto; } .asset-toolbar input { min-width: 100%; } }
|
||||
|
||||
@@ -78,6 +78,9 @@
|
||||
"nav": {
|
||||
"dashboard": "Dashboard",
|
||||
"chat": "Chat",
|
||||
"assets": "Asset Management",
|
||||
"assetOverview": "Asset Overview",
|
||||
"assetLibrary": "Asset Library",
|
||||
"infoCollect": "Recon",
|
||||
"tasks": "Tasks",
|
||||
"projects": "Projects",
|
||||
@@ -109,6 +112,12 @@
|
||||
"c2Profiles": "Traffic profiles",
|
||||
"platformRbac": "Platform Access"
|
||||
},
|
||||
"navGroups": {
|
||||
"workbench": "Workspace",
|
||||
"operations": "Security Operations",
|
||||
"capabilities": "Capabilities",
|
||||
"administration": "Administration"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Dashboard",
|
||||
"refresh": "Refresh",
|
||||
@@ -286,9 +295,13 @@
|
||||
"addFactCta": "+ Add fact",
|
||||
"tabFacts": "Fact board",
|
||||
"tabGraph": "Attack path",
|
||||
"tabAssets": "Asset library",
|
||||
"tabConversations": "Bound conversations",
|
||||
"tabVulns": "Related vulnerabilities",
|
||||
"tabSettings": "Settings",
|
||||
"boundAssetsHint": "Only assets bound to this project are shown; click a target for full details",
|
||||
"noBoundAssets": "No assets are bound to this project",
|
||||
"assetCount": "{{count}} assets",
|
||||
"factToolbarHint": "Index includes key and summary only (must include what + where + how to verify); put attack chain / POC in body, and reproduce via get_project_fact.",
|
||||
"graphToolbarHint": "Graph arrows match stored fact links (source → target). Nodes are layered target→infra→finding→exploit. Dashed edges are tentative.",
|
||||
"graphView": "View",
|
||||
@@ -579,6 +592,7 @@
|
||||
"liveTimelinePrunedRoundRange": "main-agent rounds {{from}}–{{to}}",
|
||||
"toolExecutionsCount": "{{n}} tool runs",
|
||||
"collapseToolExecutions": "Collapse tool runs",
|
||||
"toolExecutionDetailPending": "Tool execution details have not synced yet. Please try again shortly.",
|
||||
"noProcessDetail": "No process details (execution may be too fast or no detailed events)",
|
||||
"copyMessageTitle": "Copy message",
|
||||
"deleteTurnTitle": "Delete this turn",
|
||||
@@ -998,6 +1012,169 @@
|
||||
"parseModalApply": "Fill into query",
|
||||
"parseModalApplyRun": "Fill and query"
|
||||
},
|
||||
"assets": {
|
||||
"overviewTitle": "Asset Overview",
|
||||
"libraryTitle": "Asset Library",
|
||||
"viewLibrary": "View asset library",
|
||||
"totalAssets": "Total assets",
|
||||
"totalAssetsHint": "Assets currently under continuous monitoring",
|
||||
"ipCount": "IP addresses",
|
||||
"domainCount": "Domains",
|
||||
"portCount": "Ports",
|
||||
"recentCount": "Discovered in 7 days",
|
||||
"recentWindow": "7 days",
|
||||
"recentShare": "{{percent}}% of all current assets",
|
||||
"protocolDistribution": "Protocol distribution",
|
||||
"protocolDistributionHint": "Exposure composition by identified service",
|
||||
"protocolKinds": "{{count}} protocols",
|
||||
"topProtocol": "Top protocol",
|
||||
"topProtocolShare": "Top protocol share",
|
||||
"protocolComposition": "Protocol composition",
|
||||
"protocolRank": "Rank",
|
||||
"protocolName": "Protocol",
|
||||
"assetRatio": "Asset share",
|
||||
"assetAmount": "Count",
|
||||
"ratio": "Share",
|
||||
"assetCountUnit": "{{count}} assets",
|
||||
"postureChanges": "Posture changes",
|
||||
"postureChangesHint": "Track asset and risk changes over the selected period",
|
||||
"periodSelection": "Period selection",
|
||||
"days7": "7 days",
|
||||
"days30": "30 days",
|
||||
"days90": "90 days",
|
||||
"assetTrend": "Asset change trend",
|
||||
"assetTrendHint": "New discoveries and currently inactive assets",
|
||||
"addedAssets": "Added assets",
|
||||
"inactiveAssets": "Inactive assets",
|
||||
"riskTrend": "Risk discovery trend",
|
||||
"riskTrendHint": "New vulnerabilities and critical or high-risk findings",
|
||||
"discoveredRisks": "New vulnerabilities",
|
||||
"highRisks": "Critical & high",
|
||||
"scanCoverage": "Scan coverage",
|
||||
"scanCoverageHint": "Identify assets that are unscanned or overdue for review",
|
||||
"overallCoverage": "Overall coverage",
|
||||
"scannedAssets": "Scanned assets",
|
||||
"scannedAssetsHint": "Scanned at least once",
|
||||
"recentlyScanned": "Covered in 30 days",
|
||||
"neverScanned": "Never scanned",
|
||||
"neverScannedHint": "Prioritize for scanning",
|
||||
"staleScans": "Not scanned in 30+ days",
|
||||
"staleScansHint": "Scan results may be stale",
|
||||
"coverageOfTotal": "{{percent}}% of all assets",
|
||||
"coverageMeta": "{{scanned}} / {{total}} covered",
|
||||
"addAsset": "+ Add asset",
|
||||
"addAssetTitle": "Add asset",
|
||||
"editAssetTitle": "Edit asset",
|
||||
"editorSubtitle": "Enter one target; other details are parsed automatically or can be added later",
|
||||
"assetAddress": "Asset address",
|
||||
"assetAddressHint": "Any non-empty target is accepted; recognized URLs, domains, IPs, and ports are parsed automatically",
|
||||
"targetWhitespace": "The asset address cannot contain spaces",
|
||||
"targetInvalid": "Enter a valid URL, domain, or IP address",
|
||||
"hostInvalid": "The full URL is invalid or contains credentials",
|
||||
"ipInvalid": "Invalid IP address",
|
||||
"domainInvalid": "Invalid domain",
|
||||
"protocolInvalid": "Invalid protocol",
|
||||
"portInvalid": "Port must be between 1 and 65535",
|
||||
"connectionInfo": "Connection",
|
||||
"connectionInfoHint": "Parsed from the asset address automatically; adjust as needed",
|
||||
"fingerprintInfo": "Identification",
|
||||
"fingerprintInfoHint": "Add page and service fingerprints for easier discovery",
|
||||
"locationAndStatus": "Location and status",
|
||||
"locationAndStatusHint": "Record the asset location and current availability",
|
||||
"hostUrl": "Full URL / Host",
|
||||
"pageTitle": "Page title",
|
||||
"serverProduct": "Service / product",
|
||||
"tagsPlaceholder": "Press Enter or comma to add",
|
||||
"tagsHint": "Classify by environment, business, or ownership",
|
||||
"removeTag": "Remove tag {{tag}}",
|
||||
"addAssetAction": "Add asset",
|
||||
"discardChanges": "Discard unsaved changes?",
|
||||
"createdSuccessfully": "Asset added",
|
||||
"updatedSuccessfully": "Asset updated",
|
||||
"duplicateMerged": "Asset already existed; details were safely merged",
|
||||
"assetSkipped": "Asset was not saved; it may exist without update permission",
|
||||
"searchPlaceholder": "Search host, IP, domain, title, service, or tag",
|
||||
"allStatuses": "All statuses",
|
||||
"allProjects": "All projects",
|
||||
"unboundProject": "Not bound",
|
||||
"project": "Project",
|
||||
"archived": "Archived",
|
||||
"statusActive": "Active",
|
||||
"statusInactive": "Inactive",
|
||||
"target": "Target",
|
||||
"service": "Service",
|
||||
"title": "Title / fingerprint",
|
||||
"location": "Location",
|
||||
"country": "Country / region",
|
||||
"province": "Province / state",
|
||||
"city": "City",
|
||||
"source": "Source",
|
||||
"lastSeen": "Last seen",
|
||||
"status": "Status",
|
||||
"domain": "Domain",
|
||||
"port": "Port",
|
||||
"protocol": "Protocol",
|
||||
"server": "Server fingerprint",
|
||||
"tags": "Tags (comma separated)",
|
||||
"tagsLabel": "Tags",
|
||||
"detailTitle": "Asset details",
|
||||
"sourceQuery": "Source query",
|
||||
"firstSeen": "First seen",
|
||||
"totalMeta": "{{count}} assets",
|
||||
"targetRequired": "Enter an asset address",
|
||||
"loadFailed": "Failed to load assets",
|
||||
"saveFailed": "Failed to save asset",
|
||||
"saved": "Asset saved",
|
||||
"deleteConfirm": "Delete this asset?",
|
||||
"deleteFailed": "Failed to delete asset",
|
||||
"importSelected": "Import selected",
|
||||
"importOne": "Add to asset library",
|
||||
"selectFirst": "Select results to import first",
|
||||
"noValidImportTarget": "None of the selected results has a valid asset target",
|
||||
"importFailed": "Failed to import assets",
|
||||
"importDone": "Created {{created}}, updated {{updated}}",
|
||||
"importDoneWithInvalid": "Created {{created}}, updated {{updated}}, skipped {{invalid}} results without valid targets",
|
||||
"selectPage": "Select this page",
|
||||
"selectAsset": "Select asset",
|
||||
"selectedCount": "{{count}} selected",
|
||||
"clearSelection": "Clear selection",
|
||||
"bindProject": "Bind project",
|
||||
"bindProjectTitle": "Bind project",
|
||||
"chooseProject": "Choose a project",
|
||||
"selectProjectRequired": "Choose a project to bind",
|
||||
"bindProjectCount": "Update {{count}} assets",
|
||||
"bindProjectHint": "All selected assets will be bound to this project.",
|
||||
"confirmBinding": "Confirm",
|
||||
"bindProjectDone": "Bound {{count}} assets",
|
||||
"bindProjectFailed": "Failed to bind project",
|
||||
"sendToChat": "Send to chat",
|
||||
"sendToChatShort": "Scan",
|
||||
"createScanTask": "Create scan tasks",
|
||||
"scanTitle": "Scan assets",
|
||||
"scanAssetCount": "{{count}} assets",
|
||||
"userPrompt": "User prompt",
|
||||
"promptHint": "Use {{asset_id}}, {{target}}, {{host}}, {{ip}}, {{domain}}, or {{port}} placeholders. Task mode creates one task per asset.",
|
||||
"executeNow": "Run immediately after creation",
|
||||
"confirmSend": "Send",
|
||||
"confirmCreate": "Create tasks",
|
||||
"defaultScanPrompt": "Perform an authorized security scan of {{target}} (asset ID: {{asset_id}}). Save confirmed findings with record_vulnerability, then call complete_asset_scan(id={{asset_id}}) to update the last scan time and related vulnerability count.",
|
||||
"selectAssetsFirst": "Select assets first",
|
||||
"promptRequired": "Enter a user prompt",
|
||||
"scanSubmitFailed": "Failed to submit scan",
|
||||
"scanTaskLinkFailed": "Tasks were created, but asset linking failed",
|
||||
"scanConversationTitle": "Asset scan: {{targets}}",
|
||||
"scanQueueTitle": "Batch asset scan",
|
||||
"lastScan": "Last scan",
|
||||
"relatedVulnerabilities": "Related findings",
|
||||
"riskLevel": "Risk",
|
||||
"riskCritical": "Critical",
|
||||
"riskHigh": "High",
|
||||
"riskMedium": "Medium",
|
||||
"riskLow": "Low",
|
||||
"riskInfo": "Info",
|
||||
"riskNormal": "Clear",
|
||||
"riskUnassessed": "Unassessed"
|
||||
},
|
||||
"vulnerability": {
|
||||
"title": "Vulnerability Management",
|
||||
"addVuln": "Add vulnerability",
|
||||
@@ -2496,6 +2673,7 @@
|
||||
"knowledge": "Knowledge",
|
||||
"conversation": "Conversation",
|
||||
"vulnerability": "Vulnerability",
|
||||
"asset": "Asset",
|
||||
"externalMcp": "External MCP",
|
||||
"task": "Tasks",
|
||||
"tool": "Tools",
|
||||
|
||||
+183
-5
@@ -78,6 +78,9 @@
|
||||
"nav": {
|
||||
"dashboard": "仪表盘",
|
||||
"chat": "对话",
|
||||
"assets": "资产管理",
|
||||
"assetOverview": "资产概览",
|
||||
"assetLibrary": "资产库",
|
||||
"infoCollect": "信息收集",
|
||||
"tasks": "任务管理",
|
||||
"projects": "项目管理",
|
||||
@@ -90,11 +93,11 @@
|
||||
"knowledge": "知识",
|
||||
"knowledgeRetrievalLogs": "检索历史",
|
||||
"knowledgeManagement": "知识管理",
|
||||
"skills": "Skills",
|
||||
"skillsMonitor": "Skills状态监控",
|
||||
"skillsManagement": "Skills管理",
|
||||
"agents": "Agents",
|
||||
"agentsManagement": "Agent管理",
|
||||
"skills": "技能",
|
||||
"skillsMonitor": "技能状态",
|
||||
"skillsManagement": "技能管理",
|
||||
"agents": "智能体",
|
||||
"agentsManagement": "智能体管理",
|
||||
"roles": "角色",
|
||||
"rolesManagement": "角色管理",
|
||||
"workflows": "工作流",
|
||||
@@ -109,6 +112,12 @@
|
||||
"c2Profiles": "流量伪装",
|
||||
"platformRbac": "平台权限"
|
||||
},
|
||||
"navGroups": {
|
||||
"workbench": "工作台",
|
||||
"operations": "安全作业",
|
||||
"capabilities": "能力中心",
|
||||
"administration": "平台管理"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "仪表盘",
|
||||
"refresh": "刷新",
|
||||
@@ -274,9 +283,13 @@
|
||||
"addFactCta": "+ 添加事实",
|
||||
"tabFacts": "事实黑板",
|
||||
"tabGraph": "攻击路径",
|
||||
"tabAssets": "资产库",
|
||||
"tabConversations": "关联对话",
|
||||
"tabVulns": "关联漏洞",
|
||||
"tabSettings": "设置",
|
||||
"boundAssetsHint": "仅展示绑定到当前项目的资产;点击目标可查看完整详情",
|
||||
"noBoundAssets": "暂无绑定到此项目的资产",
|
||||
"assetCount": "{{count}} 个资产",
|
||||
"factToolbarHint": "索引仅含 key 与摘要(须含「什么 + 在哪 + 如何验证」);攻击链 / POC 写在 body,Agent 通过 get_project_fact 复现",
|
||||
"graphToolbarHint": "攻击路径图箭头与事实存储方向一致(source → target);节点按 target→infra→finding→exploit 分层排布。虚线边为待确认。",
|
||||
"graphView": "视图",
|
||||
@@ -567,6 +580,7 @@
|
||||
"liveTimelinePrunedRoundRange": "主代理第 {{from}}–{{to}} 轮",
|
||||
"toolExecutionsCount": "{{n}}次工具执行",
|
||||
"collapseToolExecutions": "收起工具执行",
|
||||
"toolExecutionDetailPending": "工具执行详情尚未同步,请稍后重试。",
|
||||
"noProcessDetail": "暂无过程详情(可能执行过快或未触发详细事件)",
|
||||
"copyMessageTitle": "复制消息内容",
|
||||
"deleteTurnTitle": "删除本轮对话",
|
||||
@@ -986,6 +1000,169 @@
|
||||
"parseModalApply": "填入查询框",
|
||||
"parseModalApplyRun": "填入并查询"
|
||||
},
|
||||
"assets": {
|
||||
"overviewTitle": "资产概览",
|
||||
"libraryTitle": "资产库",
|
||||
"viewLibrary": "查看资产库",
|
||||
"totalAssets": "资产总数",
|
||||
"totalAssetsHint": "当前纳入持续监测的资产",
|
||||
"ipCount": "IP 数量",
|
||||
"domainCount": "域名数量",
|
||||
"portCount": "端口数量",
|
||||
"recentCount": "近 7 天发现",
|
||||
"recentWindow": "7 天",
|
||||
"recentShare": "占当前资产总量的 {{percent}}%",
|
||||
"protocolDistribution": "协议分布",
|
||||
"protocolDistributionHint": "按已识别服务查看资产暴露构成",
|
||||
"protocolKinds": "{{count}} 种协议",
|
||||
"topProtocol": "主要协议",
|
||||
"topProtocolShare": "主要协议占比",
|
||||
"protocolComposition": "协议构成",
|
||||
"protocolRank": "排名",
|
||||
"protocolName": "协议",
|
||||
"assetRatio": "资产占比",
|
||||
"assetAmount": "数量",
|
||||
"ratio": "占比",
|
||||
"assetCountUnit": "{{count}} 个资产",
|
||||
"postureChanges": "态势变化",
|
||||
"postureChangesHint": "观察资产与风险在选定周期内的变化",
|
||||
"periodSelection": "周期选择",
|
||||
"days7": "7 天",
|
||||
"days30": "30 天",
|
||||
"days90": "90 天",
|
||||
"assetTrend": "资产增减趋势",
|
||||
"assetTrendHint": "新增发现与当前已停用资产",
|
||||
"addedAssets": "新增资产",
|
||||
"inactiveAssets": "停用资产",
|
||||
"riskTrend": "风险发现趋势",
|
||||
"riskTrendHint": "新增漏洞及其中的高危与严重风险",
|
||||
"discoveredRisks": "新增漏洞",
|
||||
"highRisks": "高危及严重",
|
||||
"scanCoverage": "扫描覆盖",
|
||||
"scanCoverageHint": "识别未扫描与长期未复查的资产缺口",
|
||||
"overallCoverage": "总体覆盖率",
|
||||
"scannedAssets": "已扫描资产",
|
||||
"scannedAssetsHint": "至少完成过一次扫描",
|
||||
"recentlyScanned": "近 30 天覆盖",
|
||||
"neverScanned": "从未扫描",
|
||||
"neverScannedHint": "建议优先纳入扫描",
|
||||
"staleScans": "超过 30 天未扫描",
|
||||
"staleScansHint": "扫描结果可能已过期",
|
||||
"coverageOfTotal": "占全部资产 {{percent}}%",
|
||||
"coverageMeta": "{{scanned}} / {{total}} 已覆盖",
|
||||
"addAsset": "+ 新增资产",
|
||||
"addAssetTitle": "新增资产",
|
||||
"editAssetTitle": "编辑资产",
|
||||
"editorSubtitle": "输入一个目标即可,其余信息可自动解析或稍后补充",
|
||||
"assetAddress": "资产地址",
|
||||
"assetAddressHint": "仅需填写非空目标;可识别的 URL、域名、IP 和端口会自动解析",
|
||||
"targetWhitespace": "资产地址不能包含空格",
|
||||
"targetInvalid": "请输入有效的 URL、域名或 IP 地址",
|
||||
"hostInvalid": "完整 URL 格式无效或包含凭据",
|
||||
"ipInvalid": "IP 地址格式无效",
|
||||
"domainInvalid": "域名格式无效",
|
||||
"protocolInvalid": "协议格式无效",
|
||||
"portInvalid": "端口必须在 1–65535 之间",
|
||||
"connectionInfo": "连接信息",
|
||||
"connectionInfoHint": "由资产地址自动解析,可按需修正",
|
||||
"fingerprintInfo": "识别信息",
|
||||
"fingerprintInfoHint": "补充页面与服务指纹,便于检索和识别",
|
||||
"locationAndStatus": "地理与状态",
|
||||
"locationAndStatusHint": "记录资产归属地区和当前启用状态",
|
||||
"hostUrl": "完整 URL / Host",
|
||||
"pageTitle": "页面标题",
|
||||
"serverProduct": "服务 / 产品",
|
||||
"tagsPlaceholder": "输入后按回车或逗号",
|
||||
"tagsHint": "用于环境、业务或责任范围分类",
|
||||
"removeTag": "移除标签 {{tag}}",
|
||||
"addAssetAction": "添加资产",
|
||||
"discardChanges": "放弃尚未保存的更改吗?",
|
||||
"createdSuccessfully": "资产已添加",
|
||||
"updatedSuccessfully": "资产已更新",
|
||||
"duplicateMerged": "资产已存在,信息已安全合并",
|
||||
"assetSkipped": "资产未保存,可能已存在且你没有更新权限",
|
||||
"searchPlaceholder": "搜索主机、IP、域名、标题、服务或标签",
|
||||
"allStatuses": "全部状态",
|
||||
"allProjects": "全部项目",
|
||||
"unboundProject": "暂不绑定",
|
||||
"project": "所属项目",
|
||||
"archived": "已归档",
|
||||
"statusActive": "活跃",
|
||||
"statusInactive": "停用",
|
||||
"target": "目标",
|
||||
"service": "服务",
|
||||
"title": "标题/指纹",
|
||||
"location": "地区",
|
||||
"country": "国家/地区",
|
||||
"province": "省份/州",
|
||||
"city": "城市",
|
||||
"source": "来源",
|
||||
"lastSeen": "最近发现",
|
||||
"status": "状态",
|
||||
"domain": "域名",
|
||||
"port": "端口",
|
||||
"protocol": "协议",
|
||||
"server": "服务指纹",
|
||||
"tags": "标签(逗号分隔)",
|
||||
"tagsLabel": "标签",
|
||||
"detailTitle": "资产详情",
|
||||
"sourceQuery": "来源查询",
|
||||
"firstSeen": "首次发现",
|
||||
"totalMeta": "共 {{count}} 条",
|
||||
"targetRequired": "请输入资产地址",
|
||||
"loadFailed": "加载资产失败",
|
||||
"saveFailed": "保存资产失败",
|
||||
"saved": "资产已保存",
|
||||
"deleteConfirm": "确定删除该资产吗?",
|
||||
"deleteFailed": "删除资产失败",
|
||||
"importSelected": "入库所选",
|
||||
"importOne": "加入资产库",
|
||||
"selectFirst": "请先选择需要入库的结果",
|
||||
"noValidImportTarget": "所选结果中没有可入库的有效资产目标",
|
||||
"importFailed": "资产入库失败",
|
||||
"importDone": "已新增 {{created}} 条,更新 {{updated}} 条",
|
||||
"importDoneWithInvalid": "已新增 {{created}} 条,更新 {{updated}} 条,跳过 {{invalid}} 条无有效目标的结果",
|
||||
"selectPage": "选择本页",
|
||||
"selectAsset": "选择资产",
|
||||
"selectedCount": "已选择 {{count}} 项",
|
||||
"clearSelection": "取消选择",
|
||||
"bindProject": "绑定项目",
|
||||
"bindProjectTitle": "绑定项目",
|
||||
"chooseProject": "请选择项目",
|
||||
"selectProjectRequired": "请选择要绑定的项目",
|
||||
"bindProjectCount": "将更新 {{count}} 个资产",
|
||||
"bindProjectHint": "所选资产将统一绑定到该项目。",
|
||||
"confirmBinding": "确认绑定",
|
||||
"bindProjectDone": "已绑定 {{count}} 个资产",
|
||||
"bindProjectFailed": "绑定项目失败",
|
||||
"sendToChat": "发送到对话",
|
||||
"sendToChatShort": "扫描",
|
||||
"createScanTask": "创建扫描任务",
|
||||
"scanTitle": "扫描资产",
|
||||
"scanAssetCount": "共 {{count}} 个资产",
|
||||
"userPrompt": "用户提示词",
|
||||
"promptHint": "可使用 {{asset_id}}、{{target}}、{{host}}、{{ip}}、{{domain}}、{{port}} 占位符;创建任务时会为每个资产生成一条任务。",
|
||||
"executeNow": "创建后立即执行",
|
||||
"confirmSend": "确认发送",
|
||||
"confirmCreate": "创建任务",
|
||||
"defaultScanPrompt": "请对资产 {{target}}(资产ID:{{asset_id}})进行授权安全扫描,优先检查暴露服务、已知漏洞、弱口令和常见 Web 风险;通过 record_vulnerability 保存确认的漏洞,完成后调用 complete_asset_scan(id={{asset_id}}) 回写上次扫描时间和相关漏洞。",
|
||||
"selectAssetsFirst": "请先选择资产",
|
||||
"promptRequired": "请输入用户提示词",
|
||||
"scanSubmitFailed": "提交扫描失败",
|
||||
"scanTaskLinkFailed": "任务已创建,但资产关联失败",
|
||||
"scanConversationTitle": "资产扫描:{{targets}}",
|
||||
"scanQueueTitle": "资产批量扫描",
|
||||
"lastScan": "上次扫描",
|
||||
"relatedVulnerabilities": "相关漏洞",
|
||||
"riskLevel": "风险等级",
|
||||
"riskCritical": "严重",
|
||||
"riskHigh": "高危",
|
||||
"riskMedium": "中危",
|
||||
"riskLow": "低危",
|
||||
"riskInfo": "提示",
|
||||
"riskNormal": "正常",
|
||||
"riskUnassessed": "未评估"
|
||||
},
|
||||
"vulnerability": {
|
||||
"title": "漏洞管理",
|
||||
"addVuln": "添加漏洞",
|
||||
@@ -2484,6 +2661,7 @@
|
||||
"knowledge": "知识库",
|
||||
"conversation": "对话",
|
||||
"vulnerability": "漏洞",
|
||||
"asset": "资产",
|
||||
"externalMcp": "外部 MCP",
|
||||
"task": "任务",
|
||||
"tool": "工具",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -360,6 +360,9 @@ const PAGE_PERMISSION_MAP = {
|
||||
chat: 'chat:read',
|
||||
hitl: 'hitl:read',
|
||||
'info-collect': 'fofa:execute',
|
||||
assets: 'asset:read',
|
||||
'asset-overview': 'asset:read',
|
||||
'asset-library': 'asset:read',
|
||||
tasks: 'tasks:read',
|
||||
workflows: 'workflow:read',
|
||||
projects: 'project:read',
|
||||
|
||||
+45
-13
@@ -3079,6 +3079,29 @@ function getCachedToolExecutionSummaries(messageElement) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 过程摘要中的早期/快速工具结果可能没有 executionId,但消息本身会按调用顺序保存 ID。
|
||||
* 合并两份数据,避免渲染摘要时丢失可用的弹窗详情入口。
|
||||
*/
|
||||
function mergeToolExecutionSummariesWithIds(summaries, executionIds) {
|
||||
const normalizedSummaries = Array.isArray(summaries)
|
||||
? summaries.map(normalizeToolExecutionSummaryForButton)
|
||||
: [];
|
||||
const normalizedIds = normalizeMcpExecutionIds(executionIds);
|
||||
const claimedIds = new Set(
|
||||
normalizedSummaries.map((item) => item.executionId).filter(Boolean)
|
||||
);
|
||||
const fallbackIds = normalizedIds.filter((id) => !claimedIds.has(id));
|
||||
let fallbackIndex = 0;
|
||||
return normalizedSummaries.map((item) => {
|
||||
if (item.executionId || fallbackIndex >= fallbackIds.length) return item;
|
||||
return {
|
||||
...item,
|
||||
executionId: fallbackIds[fallbackIndex++]
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function setPendingToolExecutionSummaries(messageElement, summaries) {
|
||||
if (!messageElement || !messageElement.dataset || !Array.isArray(summaries)) return;
|
||||
const normalized = cacheToolExecutionSummaries(messageElement, summaries);
|
||||
@@ -3337,9 +3360,28 @@ window.setMcpExecutionSummaryCount = setMcpExecutionSummaryCount;
|
||||
window.setPendingMcpExecutionIds = setPendingMcpExecutionIds;
|
||||
window.setPendingToolExecutionSummaries = setPendingToolExecutionSummaries;
|
||||
|
||||
async function openTaskToolExecutionDetail(messageElement, item, index) {
|
||||
let detailItem = item;
|
||||
if (!detailItem.executionId) {
|
||||
const refreshedItem = await resolveToolExecutionSummaryForFocus(messageElement, '', index);
|
||||
const mergedItems = mergeToolExecutionSummariesWithIds(
|
||||
getCachedToolExecutionSummaries(messageElement),
|
||||
getCachedMcpExecutionIds(messageElement)
|
||||
);
|
||||
detailItem = mergedItems[index] || refreshedItem || detailItem;
|
||||
}
|
||||
if (detailItem.executionId) {
|
||||
await showMCPDetail(detailItem.executionId);
|
||||
return;
|
||||
}
|
||||
alert(typeof window.t === 'function'
|
||||
? window.t('chat.toolExecutionDetailPending')
|
||||
: '工具执行详情尚未同步,请稍后重试。');
|
||||
}
|
||||
|
||||
/**
|
||||
* 声明式渲染工具调用列表。
|
||||
* 过程摘要是展示与定位的唯一模型;executionIds 仅在摘要尚未到达时提供占位。
|
||||
* 过程摘要是展示详情入口的唯一模型;executionIds 仅在摘要尚未到达时提供占位。
|
||||
* 每次更新整体替换列表,避免增量追加产生双重状态。
|
||||
*/
|
||||
function renderMcpCallButtons(messageElement) {
|
||||
@@ -3350,7 +3392,7 @@ function renderMcpCallButtons(messageElement) {
|
||||
const executionIds = getCachedMcpExecutionIds(messageElement);
|
||||
const summaries = getCachedToolExecutionSummaries(messageElement);
|
||||
const items = summaries.length > 0
|
||||
? summaries
|
||||
? mergeToolExecutionSummariesWithIds(summaries, executionIds)
|
||||
: executionIds.map((executionId) => normalizeToolExecutionSummaryForButton({ executionId }));
|
||||
|
||||
const renderVersion = String((parseInt(toolList.dataset.renderVersion, 10) || 0) + 1);
|
||||
@@ -3367,17 +3409,7 @@ function renderMcpCallButtons(messageElement) {
|
||||
if (item.toolCallId) {
|
||||
btn.dataset.toolCallId = item.toolCallId;
|
||||
}
|
||||
btn.onclick = async () => {
|
||||
let focusItem = item;
|
||||
if (!focusItem.processDetailId && !focusItem.toolCallId && focusItem.executionId) {
|
||||
focusItem = await resolveToolExecutionSummaryForFocus(
|
||||
messageElement,
|
||||
focusItem.executionId,
|
||||
index
|
||||
) || focusItem;
|
||||
}
|
||||
await focusToolExecutionInProcessDetails(messageElement, focusItem, index);
|
||||
};
|
||||
btn.onclick = () => openTaskToolExecutionDetail(messageElement, item, index);
|
||||
if (item.toolName) {
|
||||
renderToolExecutionButtonContent(btn, item.toolName, String(index + 1), item.status);
|
||||
} else {
|
||||
|
||||
@@ -66,7 +66,9 @@
|
||||
const skipText = el.getAttribute('data-i18n-skip-text') === 'true';
|
||||
const isFormControl = (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA');
|
||||
const attrList = el.getAttribute('data-i18n-attr');
|
||||
const text = i18next.t(key);
|
||||
const translated = i18next.t(key);
|
||||
// 缺键时保留模板中的后备文案,避免页面直接显示 assets.project 一类内部键名。
|
||||
const text = translated && translated !== key ? translated : '';
|
||||
// 仅当元素无子元素(仅文本或空)时才替换文本,避免覆盖卡片内的数字、子节点等;input/textarea 永不设置 textContent
|
||||
const hasNoElementChildren = !el.querySelector('*');
|
||||
if (!skipText && !isFormControl && hasNoElementChildren && text && typeof text === 'string') {
|
||||
@@ -80,7 +82,7 @@
|
||||
var val = text;
|
||||
if (attr === 'title' && titleKey) {
|
||||
var titleText = i18next.t(titleKey);
|
||||
if (titleText && typeof titleText === 'string') val = titleText;
|
||||
if (titleText && titleText !== titleKey && typeof titleText === 'string') val = titleText;
|
||||
}
|
||||
if (val && typeof val === 'string') {
|
||||
el.setAttribute(attr, val);
|
||||
@@ -233,4 +235,3 @@
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
|
||||
@@ -556,6 +556,9 @@ function renderFofaResults(payload) {
|
||||
<path d="M16 8h3a4 4 0 0 1 0 8h-3" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="btn-icon" data-require-permission="asset:write" onclick="importFofaRowAsset(${idx}); event.stopPropagation();" title="${escapeHtml(_t('assets.importOne'))}">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none"><path d="M12 3v12m0 0 4-4m-4 4-4-4M4 19h16" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -566,6 +569,7 @@ function renderFofaResults(payload) {
|
||||
|
||||
// 更新全选框状态
|
||||
syncSelectAllCheckbox();
|
||||
if (typeof applyRBACToUI === 'function') applyRBACToUI(els.tbody);
|
||||
}
|
||||
|
||||
function inferTargetFromRow(row, fields) {
|
||||
@@ -1147,4 +1151,3 @@ if (document.readyState === 'loading') {
|
||||
} else {
|
||||
updateSelectedMeta();
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ const PROJECTS_LIST_PAGE_SIZE_KEY = 'cyberstrike.projects_list_page_size';
|
||||
let currentProjectId = null;
|
||||
let currentProjectUpdatedAt = null;
|
||||
let currentProjectTab = 'facts';
|
||||
let currentProjectAssets = [];
|
||||
const projectNameById = {};
|
||||
let _projectsListReady = false;
|
||||
let _projectsFetchPromise = null;
|
||||
@@ -1016,8 +1017,9 @@ async function selectProject(id) {
|
||||
}
|
||||
|
||||
function switchProjectTab(tab) {
|
||||
if (tab === 'assets' && typeof hasPermission === 'function' && !hasPermission('asset:read')) tab = 'facts';
|
||||
currentProjectTab = tab;
|
||||
['facts', 'graph', 'conversations', 'vulns', 'settings'].forEach((t) => {
|
||||
['facts', 'graph', 'assets', 'conversations', 'vulns', 'settings'].forEach((t) => {
|
||||
const btn = document.getElementById(`project-tab-${t}`);
|
||||
const panel = document.getElementById(`project-panel-${t}`);
|
||||
if (btn) btn.classList.toggle('is-active', t === tab);
|
||||
@@ -1025,10 +1027,54 @@ function switchProjectTab(tab) {
|
||||
});
|
||||
if (tab === 'facts') loadProjectFacts();
|
||||
if (tab === 'graph') loadProjectFactGraph();
|
||||
if (tab === 'assets') loadProjectAssets();
|
||||
if (tab === 'conversations') loadProjectConversations();
|
||||
if (tab === 'vulns') loadProjectVulnerabilities();
|
||||
}
|
||||
|
||||
async function loadProjectAssets() {
|
||||
const tbody = document.getElementById('project-assets-tbody');
|
||||
const countEl = document.getElementById('project-assets-count');
|
||||
if (!tbody || !currentProjectId) return;
|
||||
tbody.innerHTML = `<tr class="is-empty-row"><td colspan="7">${escapeHtml(tpFmt('common.loading', '加载中...'))}</td></tr>`;
|
||||
const qs = new URLSearchParams({ project_id: currentProjectId, page: '1', page_size: '100' });
|
||||
const res = await apiFetch(`/api/assets?${qs.toString()}`);
|
||||
if (!res.ok) {
|
||||
currentProjectAssets = [];
|
||||
if (countEl) countEl.textContent = '0';
|
||||
tbody.innerHTML = `<tr class="is-empty-row"><td colspan="7">${escapeHtml(tpFmt('common.loadFailed', '加载失败'))}</td></tr>`;
|
||||
return;
|
||||
}
|
||||
const data = await res.json();
|
||||
currentProjectAssets = data.assets || [];
|
||||
if (countEl) countEl.textContent = tpFmt('projects.assetCount', `${data.total || 0} 个资产`, { count: data.total || 0 });
|
||||
if (!currentProjectAssets.length) {
|
||||
tbody.innerHTML = `<tr class="is-empty-row"><td colspan="7">${escapeHtml(tpFmt('projects.noBoundAssets', '暂无绑定到此项目的资产'))}</td></tr>`;
|
||||
return;
|
||||
}
|
||||
tbody.innerHTML = currentProjectAssets.map((asset, index) => {
|
||||
const target = asset.host || asset.domain || asset.ip || '-';
|
||||
const service = [asset.protocol, asset.port ? ':' + asset.port : ''].join('') || '-';
|
||||
const fingerprint = [asset.title, asset.server].filter(Boolean).join(' · ') || '-';
|
||||
const updated = asset.last_seen_at ? new Date(asset.last_seen_at).toLocaleString() : '-';
|
||||
const status = asset.status === 'inactive' ? tpFmt('assets.statusInactive', '停用') : tpFmt('assets.statusActive', '活跃');
|
||||
return `<tr>
|
||||
<td class="cell-summary"><button type="button" class="projects-asset-target" onclick="openProjectAssetDetail(${index})" title="${escapeHtml(target)}">${escapeHtml(target)}</button></td>
|
||||
<td><code>${escapeHtml(service)}</code></td>
|
||||
<td class="cell-summary" title="${escapeHtml(fingerprint)}">${escapeHtml(fingerprint)}</td>
|
||||
<td>${escapeHtml(asset.source || '-')}</td>
|
||||
<td>${escapeHtml(updated)}</td>
|
||||
<td><span class="asset-status asset-status--${escapeHtml(asset.status || 'active')}">${escapeHtml(status)}</span></td>
|
||||
<td class="col-actions"><div class="projects-table-actions"><button type="button" class="projects-action-btn projects-action-btn--view" onclick="openProjectAssetDetail(${index})">${escapeHtml(tpFmt('common.view', '查看'))}</button></div></td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function openProjectAssetDetail(index) {
|
||||
const asset = currentProjectAssets[Number(index)];
|
||||
if (asset && typeof window.openAssetDetailRecord === 'function') window.openAssetDetailRecord(asset);
|
||||
}
|
||||
|
||||
let _selectedGraphFactKey = null;
|
||||
let _selectedGraphEdgeId = null;
|
||||
let _currentGraphData = null;
|
||||
@@ -2759,6 +2805,8 @@ window.viewFactsForVulnerability = viewFactsForVulnerability;
|
||||
window.openProjectConversation = openProjectConversation;
|
||||
window.unbindConversationFromProject = unbindConversationFromProject;
|
||||
window.loadProjectConversations = loadProjectConversations;
|
||||
window.loadProjectAssets = loadProjectAssets;
|
||||
window.openProjectAssetDetail = openProjectAssetDetail;
|
||||
window.loadProjectFactGraph = loadProjectFactGraph;
|
||||
window.filterProjectFactGraph = filterProjectFactGraph;
|
||||
window.centerProjectFactGraph = centerProjectFactGraph;
|
||||
|
||||
@@ -113,6 +113,10 @@
|
||||
scanFofaRow: 'fofa:execute',
|
||||
batchScanSelectedFofaRows: 'fofa:execute',
|
||||
exportFofaResults: 'fofa:execute',
|
||||
importSelectedFofaAssets: 'asset:write',
|
||||
importFofaRowAsset: 'asset:write',
|
||||
saveAsset: 'asset:write',
|
||||
deleteAsset: 'asset:delete',
|
||||
|
||||
// 任务队列
|
||||
showBatchImportModal: 'tasks:write',
|
||||
|
||||
@@ -47,7 +47,7 @@ const rbacScopeMeta = {
|
||||
const rbacResourceLabels = {
|
||||
user: '平台用户',
|
||||
project: '项目', conversation: '对话', vulnerability: '漏洞', webshell: 'WebShell 连接',
|
||||
batch_task: '批量任务', c2_listener: 'C2 监听器',
|
||||
batch_task: '批量任务', c2_listener: 'C2 监听器', asset: '资产',
|
||||
};
|
||||
|
||||
function rbacText(value, fallback = '') {
|
||||
|
||||
+17
-3
@@ -81,7 +81,7 @@ function initRouter() {
|
||||
const hashParts = hash.split('?');
|
||||
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', 'platform-rbac', 'workflows', 'skills-monitor', 'skills-management', 'agents-management', 'settings', 'tasks', 'c2-listeners', 'c2-sessions', 'c2-tasks', 'c2-payloads', 'c2-events', 'c2-profiles'].includes(pageId)) {
|
||||
if (pageId && ['dashboard', 'chat', 'hitl', 'asset-overview', 'asset-library', 'info-collect', 'projects', 'vulnerabilities', 'webshell', 'chat-files', 'mcp-monitor', 'mcp-management', 'knowledge-management', 'knowledge-retrieval-logs', 'roles-management', 'platform-rbac', 'workflows', '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);
|
||||
@@ -141,7 +141,15 @@ function updateNavState(pageId) {
|
||||
});
|
||||
|
||||
// 设置活动状态
|
||||
if (pageId === 'mcp-monitor' || pageId === 'mcp-management') {
|
||||
if (pageId === 'asset-overview' || pageId === 'asset-library' || pageId === 'info-collect') {
|
||||
const assetItem = document.querySelector('.nav-item[data-page="assets"]');
|
||||
if (assetItem) {
|
||||
assetItem.classList.add('active');
|
||||
assetItem.classList.add('expanded');
|
||||
}
|
||||
const submenuItem = document.querySelector(`.nav-submenu-item[data-page="${pageId}"]`);
|
||||
if (submenuItem) submenuItem.classList.add('active');
|
||||
} else if (pageId === 'mcp-monitor' || pageId === 'mcp-management') {
|
||||
// MCP子菜单项
|
||||
const mcpItem = document.querySelector('.nav-item[data-page="mcp"]');
|
||||
if (mcpItem) {
|
||||
@@ -374,6 +382,12 @@ async function initPage(pageId) {
|
||||
initInfoCollectPage();
|
||||
}
|
||||
break;
|
||||
case 'asset-overview':
|
||||
if (typeof loadAssetOverview === 'function') loadAssetOverview();
|
||||
break;
|
||||
case 'asset-library':
|
||||
if (typeof loadAssets === 'function') loadAssets();
|
||||
break;
|
||||
case 'tasks':
|
||||
// 初始化任务管理页面
|
||||
if (typeof initTasksPage === 'function') {
|
||||
@@ -549,7 +563,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
let pageId = hashParts[0];
|
||||
|
||||
if (pageId === 'c2') pageId = 'c2-listeners';
|
||||
if (pageId && ['dashboard', 'chat', 'hitl', 'info-collect', 'projects', 'tasks', 'workflows', 'vulnerabilities', 'webshell', 'chat-files', 'mcp-monitor', 'mcp-management', 'knowledge-management', 'knowledge-retrieval-logs', 'roles-management', 'platform-rbac', 'skills-monitor', 'skills-management', 'agents-management', 'settings', 'c2-listeners', 'c2-sessions', 'c2-tasks', 'c2-payloads', 'c2-events', 'c2-profiles'].includes(pageId)) {
|
||||
if (pageId && ['dashboard', 'chat', 'hitl', 'asset-overview', 'asset-library', 'info-collect', 'projects', 'tasks', 'workflows', 'vulnerabilities', 'webshell', 'chat-files', 'mcp-monitor', 'mcp-management', 'knowledge-management', 'knowledge-retrieval-logs', 'roles-management', 'platform-rbac', '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);
|
||||
|
||||
+285
-40
@@ -23,7 +23,7 @@
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
<link rel="stylesheet" href="/static/css/style.css?v=20260716-9">
|
||||
<link rel="stylesheet" href="/static/css/c2.css">
|
||||
<link rel="stylesheet" href="/static/vendor/xterm.css">
|
||||
<script src="/static/js/router.js"></script>
|
||||
@@ -179,6 +179,9 @@
|
||||
</svg>
|
||||
</div>
|
||||
<nav class="main-sidebar-nav">
|
||||
<div class="nav-section-label" role="presentation">
|
||||
<span data-i18n="navGroups.workbench">工作台</span>
|
||||
</div>
|
||||
<div class="nav-item" data-page="dashboard">
|
||||
<div class="nav-item-content" data-title="仪表盘" onclick="switchPage('dashboard')" data-i18n="nav.dashboard" data-i18n-attr="data-title" data-i18n-skip-text="true">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
@@ -208,13 +211,40 @@
|
||||
<span data-i18n="nav.hitl">人机协同</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav-item" data-page="info-collect">
|
||||
<div class="nav-item-content" data-title="信息收集" onclick="switchPage('info-collect')" data-i18n="nav.infoCollect" data-i18n-attr="data-title" data-i18n-skip-text="true">
|
||||
<div class="nav-section-label" role="presentation">
|
||||
<span data-i18n="navGroups.operations">安全作业</span>
|
||||
</div>
|
||||
<div class="nav-item" data-page="projects">
|
||||
<div class="nav-item-content" data-title="项目管理" onclick="switchPage('projects')" data-i18n="nav.projects" data-i18n-attr="data-title" data-i18n-skip-text="true">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="11" cy="11" r="8"></circle>
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||
<path d="M8 6h11"></path>
|
||||
<path d="M5 12h10"></path>
|
||||
<path d="M10 18h9"></path>
|
||||
</svg>
|
||||
<span data-i18n="nav.infoCollect">信息收集</span>
|
||||
<span data-i18n="nav.projects">项目管理</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav-item nav-item-has-submenu" data-page="assets" data-require-permission-any="asset:read fofa:execute">
|
||||
<div class="nav-item-content" data-title="资产管理" onclick="window.toggleSubmenu('assets')" data-i18n="nav.assets" data-i18n-attr="data-title" data-i18n-skip-text="true">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M3 7l9-4 9 4-9 4-9-4z"></path><path d="M3 12l9 4 9-4"></path><path d="M3 17l9 4 9-4"></path>
|
||||
</svg>
|
||||
<span data-i18n="nav.assets">资产管理</span>
|
||||
<svg class="submenu-arrow" width="16" height="16" viewBox="0 0 24 24" fill="none"><path d="m9 18 6-6-6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</div>
|
||||
<div class="nav-submenu">
|
||||
<div class="nav-submenu-item" data-page="asset-overview" onclick="switchPage('asset-overview')" data-i18n="nav.assetOverview">资产概览</div>
|
||||
<div class="nav-submenu-item" data-page="asset-library" onclick="switchPage('asset-library')" data-i18n="nav.assetLibrary">资产库</div>
|
||||
<div class="nav-submenu-item" data-page="info-collect" onclick="switchPage('info-collect')" data-i18n="nav.infoCollect">信息收集</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav-item" data-page="vulnerabilities">
|
||||
<div class="nav-item-content" data-title="漏洞管理" onclick="switchPage('vulnerabilities')" data-i18n="nav.vulnerabilities" data-i18n-attr="data-title" data-i18n-skip-text="true">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>
|
||||
<path d="M9 12l2 2 4-4"></path>
|
||||
</svg>
|
||||
<span data-i18n="nav.vulnerabilities">漏洞管理</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav-item" data-page="tasks">
|
||||
@@ -236,33 +266,6 @@
|
||||
<span data-i18n="nav.workflows">工作流</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav-item" data-page="projects">
|
||||
<div class="nav-item-content" data-title="项目管理" onclick="switchPage('projects')" data-i18n="nav.projects" data-i18n-attr="data-title" data-i18n-skip-text="true">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polygon points="12 2 2 7 12 12 22 7 12 2"></polygon>
|
||||
<polyline points="2 17 12 22 22 17"></polyline>
|
||||
<polyline points="2 12 12 17 22 12"></polyline>
|
||||
</svg>
|
||||
<span data-i18n="nav.projects">项目管理</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav-item" data-page="vulnerabilities">
|
||||
<div class="nav-item-content" data-title="漏洞管理" onclick="switchPage('vulnerabilities')" data-i18n="nav.vulnerabilities" data-i18n-attr="data-title" data-i18n-skip-text="true">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>
|
||||
<path d="M9 12l2 2 4-4"></path>
|
||||
</svg>
|
||||
<span data-i18n="nav.vulnerabilities">漏洞管理</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav-item" data-page="chat-files">
|
||||
<div class="nav-item-content" data-title="文件管理" onclick="switchPage('chat-files')" data-i18n="nav.chatFiles" data-i18n-attr="data-title" data-i18n-skip-text="true">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>
|
||||
</svg>
|
||||
<span data-i18n="nav.chatFiles">文件管理</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav-item" data-page="webshell">
|
||||
<div class="nav-item-content" data-title="WebShell管理" onclick="switchPage('webshell')" data-i18n="nav.webshell" data-i18n-attr="data-title" data-i18n-skip-text="true">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
@@ -296,6 +299,17 @@
|
||||
<div class="nav-submenu-item" data-page="c2-profiles" onclick="switchPage('c2-profiles')" data-i18n="nav.c2Profiles">流量伪装</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav-item" data-page="chat-files">
|
||||
<div class="nav-item-content" data-title="文件管理" onclick="switchPage('chat-files')" data-i18n="nav.chatFiles" data-i18n-attr="data-title" data-i18n-skip-text="true">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>
|
||||
</svg>
|
||||
<span data-i18n="nav.chatFiles">文件管理</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav-section-label" role="presentation">
|
||||
<span data-i18n="navGroups.capabilities">能力中心</span>
|
||||
</div>
|
||||
<div class="nav-item nav-item-has-submenu" data-page="mcp">
|
||||
<div class="nav-item-content" data-title="MCP" onclick="window.toggleSubmenu('mcp')" data-i18n="nav.mcp" data-i18n-attr="data-title" data-i18n-skip-text="true">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
@@ -336,7 +350,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav-item nav-item-has-submenu" data-page="skills">
|
||||
<div class="nav-item-content" data-title="Skills" onclick="window.toggleSubmenu('skills')" data-i18n="nav.skills" data-i18n-attr="data-title" data-i18n-skip-text="true">
|
||||
<div class="nav-item-content" data-title="技能" onclick="window.toggleSubmenu('skills')" data-i18n="nav.skills" data-i18n-attr="data-title" data-i18n-skip-text="true">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"></path>
|
||||
<polyline points="14 2 14 8 20 8"></polyline>
|
||||
@@ -344,35 +358,35 @@
|
||||
<line x1="16" y1="17" x2="8" y2="17"></line>
|
||||
<polyline points="10 9 9 9 8 9"></polyline>
|
||||
</svg>
|
||||
<span data-i18n="nav.skills">Skills</span>
|
||||
<span data-i18n="nav.skills">技能</span>
|
||||
<svg class="submenu-arrow" width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 18l6-6-6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="nav-submenu">
|
||||
<div class="nav-submenu-item" data-page="skills-monitor" onclick="switchPage('skills-monitor')">
|
||||
<span data-i18n="nav.skillsMonitor">Skills状态监控</span>
|
||||
<span data-i18n="nav.skillsMonitor">技能状态</span>
|
||||
</div>
|
||||
<div class="nav-submenu-item" data-page="skills-management" onclick="switchPage('skills-management')">
|
||||
<span data-i18n="nav.skillsManagement">Skills管理</span>
|
||||
<span data-i18n="nav.skillsManagement">技能管理</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav-item nav-item-has-submenu" data-page="agents">
|
||||
<div class="nav-item-content" data-title="Agents" onclick="window.toggleSubmenu('agents')" data-i18n="nav.agents" data-i18n-attr="data-title" data-i18n-skip-text="true">
|
||||
<div class="nav-item-content" data-title="智能体" onclick="window.toggleSubmenu('agents')" data-i18n="nav.agents" data-i18n-attr="data-title" data-i18n-skip-text="true">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polygon points="12 2 2 7 12 12 22 7 12 2"></polygon>
|
||||
<polyline points="2 17 12 22 22 17"></polyline>
|
||||
<polyline points="2 12 12 17 22 12"></polyline>
|
||||
</svg>
|
||||
<span data-i18n="nav.agents">Agents</span>
|
||||
<span data-i18n="nav.agents">智能体</span>
|
||||
<svg class="submenu-arrow" width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 18l6-6-6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="nav-submenu">
|
||||
<div class="nav-submenu-item" data-page="agents-management" onclick="switchPage('agents-management')">
|
||||
<span data-i18n="nav.agentsManagement">Agent管理</span>
|
||||
<span data-i18n="nav.agentsManagement">智能体管理</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -395,6 +409,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav-section-label" role="presentation">
|
||||
<span data-i18n="navGroups.administration">平台管理</span>
|
||||
</div>
|
||||
<div class="nav-item" data-page="platform-rbac">
|
||||
<div class="nav-item-content" data-title="平台权限" onclick="switchPage('platform-rbac')" data-i18n="nav.platformRbac" data-i18n-attr="data-title" data-i18n-skip-text="true">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
@@ -1674,6 +1691,211 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 资产概览页面 -->
|
||||
<div id="page-asset-overview" class="page asset-page">
|
||||
<div class="page-header">
|
||||
<div><h2 data-i18n="assets.overviewTitle">资产概览</h2></div>
|
||||
<div class="page-header-actions">
|
||||
<button class="btn-secondary" type="button" onclick="loadAssetOverview()" data-i18n="common.refresh">刷新</button>
|
||||
<button class="btn-primary" type="button" onclick="switchPage('asset-library')" data-i18n="assets.viewLibrary">查看资产库</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-content asset-content">
|
||||
<div class="asset-overview-summary">
|
||||
<section class="asset-total-card">
|
||||
<div class="asset-total-card__heading">
|
||||
<div class="asset-overview-icon asset-overview-icon--blue" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="m12 2 8 4-8 4-8-4 8-4Z"/><path d="m4 10 8 4 8-4"/><path d="m4 14 8 4 8-4"/></svg></div>
|
||||
<div><span class="asset-eyebrow" data-i18n="assets.totalAssets">资产总数</span><p data-i18n="assets.totalAssetsHint">当前纳入持续监测的资产</p></div>
|
||||
</div>
|
||||
<strong id="asset-stat-total" class="asset-total-value">0</strong>
|
||||
<div class="asset-type-grid">
|
||||
<div class="asset-type-item asset-type-item--ip"><span class="asset-type-dot"></span><span data-i18n="assets.ipCount">IP 数量</span><strong id="asset-stat-ips">0</strong></div>
|
||||
<div class="asset-type-item asset-type-item--domain"><span class="asset-type-dot"></span><span data-i18n="assets.domainCount">域名数量</span><strong id="asset-stat-domains">0</strong></div>
|
||||
<div class="asset-type-item asset-type-item--port"><span class="asset-type-dot"></span><span data-i18n="assets.portCount">端口数量</span><strong id="asset-stat-ports">0</strong></div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="asset-recent-card">
|
||||
<div class="asset-recent-card__top">
|
||||
<div class="asset-overview-icon asset-overview-icon--violet" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M8 3v4M16 3v4M3 10h18M12 14v4M10 16h4"/></svg></div>
|
||||
<span class="asset-recent-badge" data-i18n="assets.recentWindow">7 DAYS</span>
|
||||
</div>
|
||||
<div class="asset-recent-card__body"><span data-i18n="assets.recentCount">近 7 天发现</span><strong id="asset-stat-recent">0</strong></div>
|
||||
<div class="asset-recent-progress"><i id="asset-recent-progress"></i></div>
|
||||
<p id="asset-recent-rate" class="asset-recent-caption">—</p>
|
||||
</section>
|
||||
</div>
|
||||
<div class="asset-insight-heading">
|
||||
<div><h3 data-i18n="assets.postureChanges">态势变化</h3><p data-i18n="assets.postureChangesHint">观察资产与风险在选定周期内的变化</p></div>
|
||||
<div class="asset-period-switch" role="group" data-i18n="assets.periodSelection" data-i18n-attr="aria-label" aria-label="周期选择">
|
||||
<button type="button" data-days="7" aria-pressed="false" onclick="setAssetOverviewPeriod(7)" data-i18n="assets.days7">7 天</button>
|
||||
<button type="button" class="active" data-days="30" aria-pressed="true" onclick="setAssetOverviewPeriod(30)" data-i18n="assets.days30">30 天</button>
|
||||
<button type="button" data-days="90" aria-pressed="false" onclick="setAssetOverviewPeriod(90)" data-i18n="assets.days90">90 天</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="asset-trend-grid">
|
||||
<section class="asset-panel asset-trend-panel">
|
||||
<div class="asset-panel-header asset-trend-header"><div class="asset-trend-heading"><h3 data-i18n="assets.assetTrend">资产增减趋势</h3><p data-i18n="assets.assetTrendHint">新增发现与当前已停用资产</p></div><div id="asset-growth-summary" class="asset-chart-summary"></div><div class="asset-chart-legend"><span class="asset-legend-added" data-i18n="assets.addedAssets">新增资产</span><span class="asset-legend-inactive" data-i18n="assets.inactiveAssets">停用资产</span></div></div>
|
||||
<div id="asset-growth-chart" class="asset-line-chart"><div class="muted" data-i18n="common.loading">加载中...</div></div>
|
||||
</section>
|
||||
<section class="asset-panel asset-trend-panel">
|
||||
<div class="asset-panel-header asset-trend-header"><div class="asset-trend-heading"><h3 data-i18n="assets.riskTrend">风险发现趋势</h3><p data-i18n="assets.riskTrendHint">新增漏洞及其中的高危与严重风险</p></div><div id="asset-risk-summary" class="asset-chart-summary"></div><div class="asset-chart-legend"><span class="asset-legend-risk" data-i18n="assets.discoveredRisks">新增漏洞</span><span class="asset-legend-high" data-i18n="assets.highRisks">高危及严重</span></div></div>
|
||||
<div id="asset-risk-chart" class="asset-line-chart"><div class="muted" data-i18n="common.loading">加载中...</div></div>
|
||||
</section>
|
||||
</div>
|
||||
<section class="asset-panel asset-coverage-panel">
|
||||
<div class="asset-panel-header"><div><h3 data-i18n="assets.scanCoverage">扫描覆盖</h3><p data-i18n="assets.scanCoverageHint">识别未扫描与长期未复查的资产缺口</p></div><span id="asset-coverage-status" class="asset-panel-meta">—</span></div>
|
||||
<div class="asset-coverage-layout">
|
||||
<div class="asset-coverage-gauge" id="asset-coverage-gauge"><div><strong id="asset-coverage-rate">0%</strong><span data-i18n="assets.overallCoverage">总体覆盖率</span></div></div>
|
||||
<div class="asset-coverage-metrics">
|
||||
<div><span data-i18n="assets.scannedAssets">已扫描资产</span><strong id="asset-coverage-scanned">0</strong><small data-i18n="assets.scannedAssetsHint">至少完成过一次扫描</small></div>
|
||||
<div><span data-i18n="assets.recentlyScanned">近 30 天覆盖</span><strong id="asset-coverage-recent">0</strong><small id="asset-coverage-recent-rate">0%</small></div>
|
||||
<div class="asset-coverage-gap"><span data-i18n="assets.neverScanned">从未扫描</span><strong id="asset-coverage-never">0</strong><small data-i18n="assets.neverScannedHint">建议优先纳入扫描</small></div>
|
||||
<div class="asset-coverage-gap"><span data-i18n="assets.staleScans">超过 30 天未扫描</span><strong id="asset-coverage-stale">0</strong><small data-i18n="assets.staleScansHint">扫描结果可能已过期</small></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="asset-panel asset-protocol-panel">
|
||||
<div class="asset-panel-header"><div><h3 data-i18n="assets.protocolDistribution">协议分布</h3><p data-i18n="assets.protocolDistributionHint">按已识别服务查看资产暴露构成</p></div><span class="asset-panel-meta" id="asset-protocol-meta">—</span></div>
|
||||
<div class="asset-protocol-layout">
|
||||
<div id="asset-protocol-summary" class="asset-protocol-summary" aria-hidden="true">
|
||||
<div class="asset-protocol-summary__label" data-i18n="assets.protocolComposition">协议构成</div>
|
||||
<div class="asset-protocol-summary__main">
|
||||
<div class="asset-protocol-donut"><div><strong>0%</strong><span data-i18n="assets.topProtocolShare">主要协议占比</span></div></div>
|
||||
<div class="asset-protocol-lead"><span data-i18n="assets.topProtocol">主要协议</span><strong>—</strong><small>—</small></div>
|
||||
</div>
|
||||
<div id="asset-protocol-top-list" class="asset-protocol-top-list"></div>
|
||||
</div>
|
||||
<div class="asset-protocol-ranking">
|
||||
<div class="asset-protocol-columns"><span data-i18n="assets.protocolRank">排名</span><span data-i18n="assets.protocolName">协议</span><span data-i18n="assets.assetRatio">资产占比</span><span data-i18n="assets.assetAmount">数量</span><span data-i18n="assets.ratio">占比</span></div>
|
||||
<div id="asset-protocol-chart" class="asset-bar-chart"><div class="muted" data-i18n="common.noData">暂无数据</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 资产库页面 -->
|
||||
<div id="page-asset-library" class="page asset-page">
|
||||
<div class="page-header">
|
||||
<div><h2 data-i18n="assets.libraryTitle">资产库</h2></div>
|
||||
<div class="page-header-actions">
|
||||
<button class="btn-secondary" type="button" onclick="loadAssets()" data-i18n="common.refresh">刷新</button>
|
||||
<button class="btn-primary" data-require-permission="asset:write" type="button" onclick="openAssetEditor()" data-i18n="assets.addAsset">+ 新增资产</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-content asset-content">
|
||||
<div class="asset-toolbar">
|
||||
<input id="asset-search" type="search" data-i18n="assets.searchPlaceholder" data-i18n-attr="placeholder" placeholder="搜索主机、IP、域名、标题、服务或标签" onkeydown="if(event.key==='Enter')loadAssets(1)">
|
||||
<select id="asset-status-filter" onchange="loadAssets(1)"><option value="" data-i18n="assets.allStatuses">全部状态</option><option value="active" data-i18n="assets.statusActive">活跃</option><option value="inactive" data-i18n="assets.statusInactive">停用</option></select>
|
||||
<select id="asset-project-filter" onchange="loadAssets(1)"><option value="" data-i18n="assets.allProjects">全部项目</option></select>
|
||||
<button class="btn-secondary" type="button" onclick="loadAssets(1)" data-i18n="common.search">搜索</button>
|
||||
<span id="asset-list-meta" class="muted"></span>
|
||||
</div>
|
||||
<div id="asset-batch-actions" class="asset-batch-actions" hidden>
|
||||
<span id="asset-selected-count"></span>
|
||||
<button class="btn-secondary" type="button" onclick="clearAssetSelection()" data-i18n="assets.clearSelection">取消选择</button>
|
||||
<button class="btn-secondary" data-require-permission="asset:write" type="button" onclick="openAssetProjectModal()" data-i18n="assets.bindProject">绑定项目</button>
|
||||
<button class="btn-secondary" type="button" onclick="openAssetScanModal('chat')" data-i18n="assets.sendToChat">发送到对话</button>
|
||||
<button class="btn-primary" type="button" onclick="openAssetScanModal('task')" data-i18n="assets.createScanTask">创建扫描任务</button>
|
||||
</div>
|
||||
<div class="asset-list-card">
|
||||
<div class="asset-table-wrap">
|
||||
<table class="asset-table">
|
||||
<thead><tr><th class="asset-check-cell"><input id="asset-select-page" type="checkbox" onchange="toggleAssetPageSelection(this.checked)" data-i18n="assets.selectPage" data-i18n-attr="aria-label" aria-label="选择本页"></th><th data-i18n="assets.target">目标</th><th data-i18n="assets.service">服务</th><th data-i18n="assets.project">所属项目</th><th data-i18n="assets.lastScan">上次扫描</th><th data-i18n="assets.relatedVulnerabilities">相关漏洞</th><th data-i18n="assets.riskLevel">风险等级</th><th data-i18n="assets.status">状态</th><th data-i18n="infoCollect.actions">操作</th></tr></thead>
|
||||
<tbody id="asset-table-body"><tr><td colspan="9" class="muted" data-i18n="common.noData">暂无数据</td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div id="asset-pagination" class="pagination-container pagination-fixed"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="asset-project-modal" class="modal asset-project-modal" style="display:none" role="dialog" aria-modal="true" aria-labelledby="asset-project-title" onclick="if(event.target===this)closeAssetProjectModal()">
|
||||
<form class="modal-content asset-project-content" onsubmit="event.preventDefault();submitAssetProjectBinding()" onclick="event.stopPropagation()">
|
||||
<div class="modal-header"><div><h2 id="asset-project-title" data-i18n="assets.bindProjectTitle">绑定项目</h2><p id="asset-project-subtitle" class="asset-page-subtitle"></p></div><button type="button" class="modal-close" onclick="closeAssetProjectModal()" data-i18n="common.close" data-i18n-attr="aria-label" aria-label="关闭"></button></div>
|
||||
<div class="modal-body">
|
||||
<label class="asset-editor-field" for="asset-batch-project"><span data-i18n="assets.project">所属项目</span><select id="asset-batch-project"><option value="" disabled hidden data-i18n="assets.chooseProject">请选择项目</option></select></label>
|
||||
<p class="form-hint" data-i18n="assets.bindProjectHint">所选资产将统一绑定到该项目。</p>
|
||||
</div>
|
||||
<div class="modal-footer"><button type="button" class="btn-secondary" onclick="closeAssetProjectModal()" data-i18n="common.cancel">取消</button><button id="asset-project-submit" type="submit" class="btn-primary" data-require-permission="asset:write" data-i18n="assets.confirmBinding">确认绑定</button></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="asset-scan-modal" class="modal asset-scan-modal" style="display:none" role="dialog" aria-modal="true" onclick="if(event.target===this)closeAssetScanModal()">
|
||||
<div class="modal-content asset-scan-content" onclick="event.stopPropagation()">
|
||||
<div class="modal-header"><div><h2 id="asset-scan-title" data-i18n="assets.scanTitle">扫描资产</h2><p id="asset-scan-subtitle" class="asset-page-subtitle"></p></div><span class="modal-close" onclick="closeAssetScanModal()">×</span></div>
|
||||
<div class="modal-body">
|
||||
<div id="asset-scan-targets" class="asset-scan-targets"></div>
|
||||
<label class="asset-scan-prompt-label" for="asset-scan-prompt" data-i18n="assets.userPrompt">用户提示词</label>
|
||||
<textarea id="asset-scan-prompt" class="asset-scan-prompt" rows="7"></textarea>
|
||||
<p id="asset-scan-hint" class="form-hint" data-i18n="assets.promptHint">可使用 {{asset_id}}、{{target}}、{{host}}、{{ip}}、{{domain}}、{{port}} 占位符;创建任务时会为每个资产生成一条任务。</p>
|
||||
<label id="asset-scan-execute-wrap" class="checkbox-label"><input id="asset-scan-execute-now" type="checkbox" checked><span data-i18n="assets.executeNow">创建后立即执行</span></label>
|
||||
</div>
|
||||
<div class="modal-footer"><button class="btn-secondary" onclick="closeAssetScanModal()" data-i18n="common.cancel">取消</button><button id="asset-scan-submit" class="btn-primary" onclick="submitAssetScan()" data-i18n="assets.confirmSend">确认发送</button></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="asset-editor-modal" class="modal asset-editor-modal" style="display:none" role="dialog" aria-modal="true" aria-labelledby="asset-editor-title" onclick="if(event.target===this)closeAssetEditor()">
|
||||
<form id="asset-editor-form" class="modal-content asset-editor-content" novalidate onsubmit="event.preventDefault();saveAsset()" onclick="event.stopPropagation()">
|
||||
<div class="modal-header"><div><h2 id="asset-editor-title" data-i18n="assets.addAssetTitle">新增资产</h2><p class="asset-editor-subtitle" data-i18n="assets.editorSubtitle">输入一个目标即可,其余信息可自动解析或稍后补充</p></div><button type="button" class="modal-close" onclick="closeAssetEditor()" data-i18n="common.close" data-i18n-attr="aria-label" aria-label="关闭"></button></div>
|
||||
<div class="modal-body asset-editor-body">
|
||||
<input type="hidden" id="asset-edit-id">
|
||||
<div class="asset-editor-primary">
|
||||
<label class="asset-editor-field asset-editor-target-field" for="asset-edit-target">
|
||||
<span><span data-i18n="assets.assetAddress">资产地址</span><b aria-hidden="true">*</b></span>
|
||||
<input id="asset-edit-target" type="text" autocomplete="off" spellcheck="false" placeholder="https://example.com:443、example.com 或 1.1.1.1:443" aria-describedby="asset-edit-target-hint asset-edit-target-error" aria-required="true">
|
||||
<small id="asset-edit-target-hint" class="asset-field-hint" data-i18n="assets.assetAddressHint">仅需填写非空目标;可识别的 URL、域名、IP 和端口会自动解析</small>
|
||||
<small id="asset-edit-target-error" class="asset-field-error" role="alert" hidden></small>
|
||||
</label>
|
||||
<div class="asset-editor-grid asset-editor-primary-grid">
|
||||
<label class="asset-editor-field"><span data-i18n="assets.project">所属项目</span><select id="asset-edit-project"><option value="" data-i18n="assets.unboundProject">暂不绑定</option></select></label>
|
||||
<label class="asset-editor-field"><span data-i18n="assets.tagsLabel">标签</span><div id="asset-tag-editor" class="asset-tag-editor"><div id="asset-tag-chips" class="asset-tag-chips"></div><input id="asset-edit-tags" type="text" autocomplete="off" data-i18n="assets.tagsPlaceholder" data-i18n-attr="placeholder" placeholder="输入后按回车或逗号"></div><small class="asset-field-hint" data-i18n="assets.tagsHint">用于环境、业务或责任范围分类</small></label>
|
||||
</div>
|
||||
</div>
|
||||
<section id="asset-editor-details" class="asset-editor-details" aria-labelledby="asset-editor-details-title">
|
||||
<div class="asset-editor-section-group">
|
||||
<div class="asset-editor-group-heading"><h3 id="asset-editor-details-title" data-i18n="assets.connectionInfo">连接信息</h3><p data-i18n="assets.connectionInfoHint">由资产地址自动解析,可按需修正</p></div>
|
||||
<div class="asset-editor-grid">
|
||||
<label class="asset-editor-field asset-editor-wide"><span data-i18n="assets.hostUrl">完整 URL / Host</span><input id="asset-edit-host" type="text" spellcheck="false" placeholder="https://example.com:443"><small id="asset-edit-host-error" class="asset-field-error" role="alert" hidden></small></label>
|
||||
<label class="asset-editor-field"><span>IP</span><input id="asset-edit-ip" type="text" spellcheck="false" placeholder="1.1.1.1"><small id="asset-edit-ip-error" class="asset-field-error" role="alert" hidden></small></label>
|
||||
<label class="asset-editor-field"><span data-i18n="assets.domain">域名</span><input id="asset-edit-domain" type="text" spellcheck="false" placeholder="example.com"><small id="asset-edit-domain-error" class="asset-field-error" role="alert" hidden></small></label>
|
||||
<label class="asset-editor-field"><span data-i18n="assets.port">端口</span><input id="asset-edit-port" type="number" inputmode="numeric" min="1" max="65535" placeholder="443"><small id="asset-edit-port-error" class="asset-field-error" role="alert" hidden></small></label>
|
||||
<label class="asset-editor-field"><span data-i18n="assets.protocol">协议</span><input id="asset-edit-protocol" type="text" list="asset-protocol-options" spellcheck="false" placeholder="https"><datalist id="asset-protocol-options"><option value="http"><option value="https"><option value="ssh"><option value="ftp"><option value="smtp"><option value="rdp"><option value="mysql"><option value="postgresql"><option value="redis"><option value="mongodb"></datalist><small id="asset-edit-protocol-error" class="asset-field-error" role="alert" hidden></small></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="asset-editor-section-group">
|
||||
<div class="asset-editor-group-heading"><h3 data-i18n="assets.fingerprintInfo">识别信息</h3><p data-i18n="assets.fingerprintInfoHint">补充页面与服务指纹,便于检索和识别</p></div>
|
||||
<div class="asset-editor-grid">
|
||||
<label class="asset-editor-field"><span data-i18n="assets.pageTitle">页面标题</span><input id="asset-edit-title-value" type="text" maxlength="500"></label>
|
||||
<label class="asset-editor-field"><span data-i18n="assets.serverProduct">服务 / 产品</span><input id="asset-edit-server" type="text" maxlength="255" placeholder="nginx"></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="asset-editor-section-group">
|
||||
<div class="asset-editor-group-heading"><h3 data-i18n="assets.locationAndStatus">地理与状态</h3><p data-i18n="assets.locationAndStatusHint">记录资产归属地区和当前启用状态</p></div>
|
||||
<div class="asset-editor-grid">
|
||||
<label class="asset-editor-field"><span data-i18n="assets.country">国家/地区</span><input id="asset-edit-country" type="text" maxlength="100" placeholder="CN"></label>
|
||||
<label class="asset-editor-field"><span data-i18n="assets.province">省份/州</span><input id="asset-edit-province" type="text" maxlength="100" placeholder="Beijing"></label>
|
||||
<label class="asset-editor-field"><span data-i18n="assets.city">城市</span><input id="asset-edit-city" type="text" maxlength="100" placeholder="Beijing"></label>
|
||||
<label class="asset-editor-field"><span data-i18n="assets.status">状态</span><select id="asset-edit-status"><option value="active" data-i18n="assets.statusActive">活跃</option><option value="inactive" data-i18n="assets.statusInactive">停用</option></select></label>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<div id="asset-editor-form-error" class="asset-form-error" role="alert" hidden></div>
|
||||
</div>
|
||||
<div class="modal-footer"><button type="button" class="btn-secondary" onclick="closeAssetEditor()" data-i18n="common.cancel">取消</button><button id="asset-editor-submit" type="submit" class="btn-primary" data-require-permission="asset:write" data-i18n="assets.addAssetAction">添加资产</button></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="asset-detail-modal" class="modal asset-detail-modal" style="display:none" role="dialog" aria-modal="true" onclick="if(event.target===this)closeAssetDetail()">
|
||||
<div class="modal-content asset-detail-content" onclick="event.stopPropagation()">
|
||||
<div class="modal-header"><div><h2 data-i18n="assets.detailTitle">资产详情</h2><p id="asset-detail-subtitle" class="asset-page-subtitle"></p></div><span class="modal-close" onclick="closeAssetDetail()">×</span></div>
|
||||
<div class="modal-body">
|
||||
<div id="asset-detail-grid" class="asset-detail-grid"></div>
|
||||
</div>
|
||||
<div class="modal-footer"><button class="btn-secondary" onclick="closeAssetDetail()" data-i18n="common.close">关闭</button><button id="asset-detail-edit" class="btn-primary" data-require-permission="asset:write" onclick="editAssetFromDetail()" data-i18n="common.edit">编辑</button></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 信息收集页面 -->
|
||||
<div id="page-info-collect" class="page">
|
||||
<div class="page-header">
|
||||
@@ -1747,6 +1969,7 @@
|
||||
<button class="btn-secondary btn-small" type="button" data-require-permission="fofa:execute" onclick="exportFofaResults('csv')" data-i18n="infoCollectPage.exportCsvTitle" data-i18n-attr="title" title="导出当前结果为 CSV(UTF-8,兼容中文)"><span data-i18n="infoCollectPage.exportCsv">导出 CSV</span></button>
|
||||
<button class="btn-secondary btn-small" type="button" data-require-permission="fofa:execute" onclick="exportFofaResults('json')" data-i18n="infoCollectPage.exportJsonTitle" data-i18n-attr="title" title="导出当前结果为 JSON"><span data-i18n="infoCollectPage.exportJson">导出 JSON</span></button>
|
||||
<button class="btn-secondary btn-small" type="button" data-require-permission="fofa:execute" onclick="exportFofaResults('xlsx')" data-i18n="infoCollectPage.exportXlsxTitle" data-i18n-attr="title" title="导出当前结果为 Excel"><span data-i18n="infoCollectPage.exportXlsx">导出 XLSX</span></button>
|
||||
<button class="btn-secondary btn-small" data-require-permission="asset:write" type="button" onclick="importSelectedFofaAssets()" data-i18n="assets.importSelected">入库所选</button>
|
||||
<button class="btn-primary btn-small" data-require-permission="fofa:execute" type="button" onclick="batchScanSelectedFofaRows()" data-i18n="infoCollectPage.batchScanTitle" data-i18n-attr="title" title="将所选行创建为批量任务队列"><span data-i18n="infoCollectPage.batchScan">批量扫描</span></button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1838,6 +2061,7 @@
|
||||
<nav class="projects-tabs" role="tablist">
|
||||
<button type="button" id="project-tab-facts" class="projects-tab is-active" role="tab" onclick="switchProjectTab('facts')" data-i18n="projects.tabFacts">事实黑板</button>
|
||||
<button type="button" id="project-tab-graph" class="projects-tab" role="tab" onclick="switchProjectTab('graph')" data-i18n="projects.tabGraph">攻击路径</button>
|
||||
<button type="button" id="project-tab-assets" class="projects-tab" role="tab" data-require-permission="asset:read" onclick="switchProjectTab('assets')" data-i18n="projects.tabAssets">资产库</button>
|
||||
<button type="button" id="project-tab-conversations" class="projects-tab" role="tab" onclick="switchProjectTab('conversations')" data-i18n="projects.tabConversations">关联对话</button>
|
||||
<button type="button" id="project-tab-vulns" class="projects-tab" role="tab" onclick="switchProjectTab('vulns')" data-i18n="projects.tabVulns">关联漏洞</button>
|
||||
<button type="button" id="project-tab-settings" class="projects-tab" role="tab" onclick="switchProjectTab('settings')" data-i18n="projects.tabSettings">设置</button>
|
||||
@@ -1988,6 +2212,23 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="project-panel-assets" class="projects-panel" role="tabpanel" hidden>
|
||||
<div class="projects-panel-toolbar projects-panel-toolbar--hint">
|
||||
<p class="projects-fact-toolbar-hint" role="note">
|
||||
<svg class="projects-fact-toolbar-hint-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="2"/><path d="M12 10v6M12 8h.01" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
<span data-i18n="projects.boundAssetsHint">仅展示绑定到当前项目的资产;点击目标可查看完整详情</span>
|
||||
</p>
|
||||
<span id="project-assets-count" class="projects-panel-count">0</span>
|
||||
</div>
|
||||
<div class="projects-table-wrap">
|
||||
<table class="data-table data-table--projects">
|
||||
<thead><tr><th data-i18n="assets.target">目标</th><th data-i18n="assets.service">服务</th><th data-i18n="assets.title">标题/指纹</th><th data-i18n="assets.source">来源</th><th data-i18n="assets.lastSeen">最近发现</th><th data-i18n="assets.status">状态</th><th class="col-actions" data-i18n="common.actions">操作</th></tr></thead>
|
||||
<tbody id="project-assets-tbody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div id="project-panel-conversations" class="projects-panel" role="tabpanel" hidden>
|
||||
<div class="projects-panel-toolbar projects-panel-toolbar--hint">
|
||||
<p class="projects-fact-toolbar-hint" role="note">
|
||||
@@ -4385,6 +4626,7 @@
|
||||
<option value="project" data-i18n="rbac.resourceTypes.project">项目</option>
|
||||
<option value="batch_task" data-i18n="rbac.resourceTypes.batch_task">批量任务</option>
|
||||
<option value="vulnerability" data-i18n="rbac.resourceTypes.vulnerability">漏洞</option>
|
||||
<option value="asset" data-i18n="rbac.resourceTypes.asset">资产</option>
|
||||
<option value="webshell" data-i18n="rbac.resourceTypes.webshell">WebShell 连接</option>
|
||||
<option value="c2_listener" data-i18n="rbac.resourceTypes.c2_listener">C2 监听器</option>
|
||||
</select>
|
||||
@@ -4413,6 +4655,7 @@
|
||||
<option value="project" data-i18n="rbac.resourceTypes.project">项目</option>
|
||||
<option value="batch_task" data-i18n="rbac.resourceTypes.batch_task">批量任务</option>
|
||||
<option value="vulnerability" data-i18n="rbac.resourceTypes.vulnerability">漏洞</option>
|
||||
<option value="asset" data-i18n="rbac.resourceTypes.asset">资产</option>
|
||||
<option value="webshell" data-i18n="rbac.resourceTypes.webshell">WebShell 连接</option>
|
||||
<option value="c2_listener" data-i18n="rbac.resourceTypes.c2_listener">C2 监听器</option>
|
||||
</select>
|
||||
@@ -4453,6 +4696,7 @@
|
||||
<option value="project" data-i18n="rbac.resourceTypes.project">项目</option>
|
||||
<option value="conversation" data-i18n="rbac.resourceTypes.conversation">对话</option>
|
||||
<option value="vulnerability" data-i18n="rbac.resourceTypes.vulnerability">漏洞</option>
|
||||
<option value="asset" data-i18n="rbac.resourceTypes.asset">资产</option>
|
||||
<option value="webshell" data-i18n="rbac.resourceTypes.webshell">WebShell 连接</option>
|
||||
<option value="batch_task" data-i18n="rbac.resourceTypes.batch_task">批量任务</option>
|
||||
<option value="c2_listener" data-i18n="rbac.resourceTypes.c2_listener">C2 监听器</option>
|
||||
@@ -6041,6 +6285,7 @@
|
||||
<script src="/static/js/modal.js"></script>
|
||||
<script src="/static/js/notifications.js"></script>
|
||||
<script src="/static/js/info-collect.js"></script>
|
||||
<script src="/static/js/assets.js?v=20260716-10"></script>
|
||||
<script src="/static/js/agents.js"></script>
|
||||
<script src="/static/js/dashboard.js"></script>
|
||||
<script src="/static/js/chat-scroll.js"></script>
|
||||
|
||||
Reference in New Issue
Block a user