mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-06-02 04:21:41 +02:00
Add files via upload
This commit is contained in:
+71
-23
@@ -21258,10 +21258,11 @@ button.chat-files-dropdown-item:hover:not(:disabled) {
|
||||
border-radius: 14px;
|
||||
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.06);
|
||||
overflow: hidden;
|
||||
min-height: 420px;
|
||||
min-height: 0;
|
||||
align-self: stretch;
|
||||
}
|
||||
.projects-detail-header {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
@@ -21349,6 +21350,7 @@ button.chat-files-dropdown-item:hover:not(:disabled) {
|
||||
padding: 12px 24px;
|
||||
background: #f8fafc;
|
||||
border-bottom: 1px solid #eef2f7;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.projects-tab {
|
||||
padding: 8px 16px;
|
||||
@@ -21594,40 +21596,86 @@ button.chat-files-dropdown-item:hover:not(:disabled) {
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
}
|
||||
#project-panel-facts {
|
||||
#project-panel-facts,
|
||||
#project-panel-conversations,
|
||||
#project-panel-vulns {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
}
|
||||
#project-panel-facts .projects-fact-toolbar {
|
||||
#project-panel-facts .projects-fact-toolbar,
|
||||
#project-panel-vulns .projects-fact-toolbar,
|
||||
#project-panel-conversations .projects-panel-toolbar {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
#project-panel-facts .projects-table-wrap {
|
||||
#project-panel-facts .projects-table-wrap,
|
||||
#project-panel-conversations .projects-table-wrap,
|
||||
#project-panel-vulns .projects-table-wrap {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
max-height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
#project-panel-facts .projects-table-wrap .data-table--projects {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
table-layout: fixed;
|
||||
border-spacing: 0;
|
||||
}
|
||||
#project-panel-facts .projects-table-wrap .data-table--projects thead,
|
||||
#project-panel-facts .projects-table-wrap .data-table--projects tbody tr {
|
||||
display: table;
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
}
|
||||
#project-panel-facts .projects-table-wrap .data-table--projects tbody {
|
||||
display: block;
|
||||
height: calc(100% - 42px);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
#project-panel-facts .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;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
box-shadow: 0 1px 0 var(--border-color, #e2e8f0);
|
||||
}
|
||||
.projects-panel-toolbar--hint {
|
||||
margin-bottom: 14px;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
.projects-panel-toolbar--hint .projects-fact-toolbar-hint {
|
||||
margin: 0;
|
||||
}
|
||||
#project-panel-conversations .data-table--projects th:nth-child(1),
|
||||
#project-panel-conversations .data-table--projects td:nth-child(1) {
|
||||
width: 48%;
|
||||
}
|
||||
#project-panel-conversations .data-table--projects th:nth-child(2),
|
||||
#project-panel-conversations .data-table--projects td:nth-child(2) {
|
||||
width: 22%;
|
||||
}
|
||||
#project-panel-conversations .data-table--projects th:nth-child(3),
|
||||
#project-panel-conversations .data-table--projects td:nth-child(3) {
|
||||
width: 30%;
|
||||
}
|
||||
.projects-vuln-toolbar-top {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.projects-vuln-toolbar-top .projects-fact-toolbar-hint {
|
||||
flex: 1 1 240px;
|
||||
margin: 0;
|
||||
}
|
||||
#project-panel-vulns .data-table--projects th:nth-child(1),
|
||||
#project-panel-vulns .data-table--projects td:nth-child(1) {
|
||||
width: 46%;
|
||||
}
|
||||
#project-panel-vulns .data-table--projects th:nth-child(2),
|
||||
#project-panel-vulns .data-table--projects td:nth-child(2) {
|
||||
width: 14%;
|
||||
}
|
||||
#project-panel-vulns .data-table--projects th:nth-child(3),
|
||||
#project-panel-vulns .data-table--projects td:nth-child(3) {
|
||||
width: 14%;
|
||||
}
|
||||
#project-panel-vulns .data-table--projects th:nth-child(4),
|
||||
#project-panel-vulns .data-table--projects td:nth-child(4) {
|
||||
width: 26%;
|
||||
}
|
||||
.projects-table-wrap .data-table--projects {
|
||||
min-width: 0;
|
||||
table-layout: fixed;
|
||||
|
||||
@@ -258,6 +258,9 @@
|
||||
"boundConversationsHint": "Conversations bound to this project; click to open",
|
||||
"titleLabel": "Title",
|
||||
"projectVulnSummaryHint": "Vulnerability summary under this project",
|
||||
"searchVulnsSr": "Search vulnerabilities",
|
||||
"searchVulnsPlaceholder": "Search title, description, type, target…",
|
||||
"noMatchingVulns": "No matching vulnerabilities, try adjusting filters",
|
||||
"viewInVulnerabilityManagement": "View in vulnerability management",
|
||||
"severity": "Severity",
|
||||
"status": "Status",
|
||||
|
||||
@@ -247,6 +247,9 @@
|
||||
"boundConversationsHint": "绑定到本项目的对话;点击可打开会话",
|
||||
"titleLabel": "标题",
|
||||
"projectVulnSummaryHint": "本项目下记录的漏洞汇总",
|
||||
"searchVulnsSr": "搜索漏洞",
|
||||
"searchVulnsPlaceholder": "搜索标题、描述、类型、目标…",
|
||||
"noMatchingVulns": "无匹配漏洞,请调整筛选条件",
|
||||
"viewInVulnerabilityManagement": "在漏洞管理中查看",
|
||||
"severity": "严重度",
|
||||
"status": "状态",
|
||||
|
||||
@@ -336,6 +336,50 @@ function formatSeverityBadge(severity) {
|
||||
return `<span class="projects-severity ${cls}">${escapeHtml(severity || '—')}</span>`;
|
||||
}
|
||||
|
||||
function formatVulnStatusBadge(status) {
|
||||
const s = (status || 'open').toLowerCase();
|
||||
const labelMap = {
|
||||
open: 'vulnerabilityPage.statusOpen',
|
||||
confirmed: 'vulnerabilityPage.statusConfirmed',
|
||||
fixed: 'vulnerabilityPage.statusFixed',
|
||||
false_positive: 'vulnerabilityPage.statusFalsePositive',
|
||||
};
|
||||
const label = labelMap[s] ? tp(labelMap[s]) : status || '—';
|
||||
const cls = ['open', 'confirmed', 'fixed', 'false_positive'].includes(s) ? s : 'open';
|
||||
return `<span class="status-badge status-${escapeHtml(cls)}">${escapeHtml(label)}</span>`;
|
||||
}
|
||||
|
||||
let _projectVulnsFilterDebounce = null;
|
||||
|
||||
function buildProjectVulnsQueryParams() {
|
||||
const params = new URLSearchParams();
|
||||
params.set('project_id', currentProjectId);
|
||||
params.set('limit', '200');
|
||||
const search = document.getElementById('project-vulns-search')?.value?.trim();
|
||||
const severity = document.getElementById('project-vulns-filter-severity')?.value?.trim();
|
||||
const status = document.getElementById('project-vulns-filter-status')?.value?.trim();
|
||||
if (search) params.set('q', search);
|
||||
if (severity) params.set('severity', severity);
|
||||
if (status) params.set('status', status);
|
||||
return params;
|
||||
}
|
||||
|
||||
function projectVulnsHasActiveFilter() {
|
||||
return !!(
|
||||
document.getElementById('project-vulns-search')?.value?.trim() ||
|
||||
document.getElementById('project-vulns-filter-severity')?.value ||
|
||||
document.getElementById('project-vulns-filter-status')?.value
|
||||
);
|
||||
}
|
||||
|
||||
function debouncedLoadProjectVulnerabilities() {
|
||||
if (_projectVulnsFilterDebounce) clearTimeout(_projectVulnsFilterDebounce);
|
||||
_projectVulnsFilterDebounce = setTimeout(() => {
|
||||
_projectVulnsFilterDebounce = null;
|
||||
loadProjectVulnerabilities();
|
||||
}, 280);
|
||||
}
|
||||
|
||||
function getProjectsListFilter() {
|
||||
return (document.getElementById('projects-list-search')?.value || '').trim().toLowerCase();
|
||||
}
|
||||
@@ -417,10 +461,16 @@ async function selectProject(id) {
|
||||
const catEl = document.getElementById('project-facts-filter-category');
|
||||
const confEl = document.getElementById('project-facts-filter-confidence');
|
||||
const sparseEl = document.getElementById('project-facts-filter-sparse');
|
||||
const vulnSearchEl = document.getElementById('project-vulns-search');
|
||||
const vulnSevEl = document.getElementById('project-vulns-filter-severity');
|
||||
const vulnStatusEl = document.getElementById('project-vulns-filter-status');
|
||||
if (searchEl) searchEl.value = '';
|
||||
if (catEl) catEl.value = '';
|
||||
if (confEl) confEl.value = '';
|
||||
if (sparseEl) sparseEl.checked = false;
|
||||
if (vulnSearchEl) vulnSearchEl.value = '';
|
||||
if (vulnSevEl) vulnSevEl.value = '';
|
||||
if (vulnStatusEl) vulnStatusEl.value = '';
|
||||
renderProjectsSidebar();
|
||||
updateProjectsDetailVisibility();
|
||||
try {
|
||||
@@ -846,15 +896,18 @@ async function loadProjectVulnerabilities() {
|
||||
const tbody = document.getElementById('project-vulns-tbody');
|
||||
if (!tbody || !currentProjectId) return;
|
||||
tbody.innerHTML = `<tr class="is-empty-row"><td colspan="4">${escapeHtml(tp('common.loading'))}</td></tr>`;
|
||||
const res = await apiFetch(`/api/vulnerabilities?project_id=${encodeURIComponent(currentProjectId)}&limit=100`);
|
||||
const qs = buildProjectVulnsQueryParams().toString();
|
||||
const res = await apiFetch(`/api/vulnerabilities?${qs}`);
|
||||
if (!res.ok) {
|
||||
tbody.innerHTML = `<tr class="is-empty-row"><td colspan="4">${escapeHtml(tp('common.loadFailed'))}</td></tr>`;
|
||||
return;
|
||||
}
|
||||
const data = await res.json();
|
||||
const items = data.Vulnerabilities || data.vulnerabilities || data.items || [];
|
||||
const items = data.Vulnerabilities || data.vulnerabilities || data.items || (Array.isArray(data) ? data : []);
|
||||
if (!items.length) {
|
||||
tbody.innerHTML = `<tr class="is-empty-row"><td colspan="4">${escapeHtml(tp('projects.noVulnerabilityRecords'))}</td></tr>`;
|
||||
tbody.innerHTML = `<tr class="is-empty-row"><td colspan="4">${
|
||||
projectVulnsHasActiveFilter() ? tp('projects.noMatchingVulns') : tp('projects.noVulnerabilityRecords')
|
||||
}</td></tr>`;
|
||||
refreshProjectHeaderStats();
|
||||
return;
|
||||
}
|
||||
@@ -863,7 +916,7 @@ async function loadProjectVulnerabilities() {
|
||||
return `<tr>
|
||||
<td class="cell-summary" title="${escapeHtml(v.title)}">${escapeHtml(v.title)}</td>
|
||||
<td>${formatSeverityBadge(v.severity)}</td>
|
||||
<td>${escapeHtml(v.status)}</td>
|
||||
<td>${formatVulnStatusBadge(v.status)}</td>
|
||||
<td class="col-actions">
|
||||
<div class="projects-table-actions">
|
||||
<button type="button" class="projects-action-btn projects-action-btn--view" data-vuln-id="${idEsc}" onclick="openVulnerabilityDetail(this.dataset.vulnId)">${escapeHtml(tp('common.view'))}</button>
|
||||
@@ -1542,6 +1595,8 @@ window.openVulnerabilitiesForProject = openVulnerabilitiesForProject;
|
||||
window.openVulnerabilityDetail = openVulnerabilityDetail;
|
||||
window.filterProjectsList = filterProjectsList;
|
||||
window.debouncedLoadProjectFacts = debouncedLoadProjectFacts;
|
||||
window.debouncedLoadProjectVulnerabilities = debouncedLoadProjectVulnerabilities;
|
||||
window.loadProjectVulnerabilities = loadProjectVulnerabilities;
|
||||
window.linkFactToExistingVulnerability = linkFactToExistingVulnerability;
|
||||
window.createVulnerabilityFromCurrentFact = createVulnerabilityFromCurrentFact;
|
||||
window.viewFactsForVulnerability = viewFactsForVulnerability;
|
||||
|
||||
@@ -1539,8 +1539,14 @@
|
||||
</div>
|
||||
</div>
|
||||
<div id="project-panel-conversations" class="projects-panel" role="tabpanel" hidden>
|
||||
<div class="projects-panel-toolbar">
|
||||
<span class="projects-panel-hint" data-i18n="projects.boundConversationsHint">绑定到本项目的对话;点击可打开会话</span>
|
||||
<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.boundConversationsHint">绑定到本项目的对话;点击可打开会话</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="projects-table-wrap">
|
||||
<table class="data-table data-table--projects">
|
||||
@@ -1550,9 +1556,48 @@
|
||||
</div>
|
||||
</div>
|
||||
<div id="project-panel-vulns" class="projects-panel" role="tabpanel" hidden>
|
||||
<div class="projects-panel-toolbar">
|
||||
<span class="projects-panel-hint" data-i18n="projects.projectVulnSummaryHint">本项目下记录的漏洞汇总</span>
|
||||
<button type="button" class="btn-secondary btn-small" onclick="openVulnerabilitiesForProject()" data-i18n="projects.viewInVulnerabilityManagement">在漏洞管理中查看</button>
|
||||
<div class="projects-fact-toolbar">
|
||||
<div class="projects-vuln-toolbar-top">
|
||||
<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.projectVulnSummaryHint">本项目下记录的漏洞汇总</span>
|
||||
</p>
|
||||
<button type="button" class="btn-secondary btn-small" onclick="openVulnerabilitiesForProject()" data-i18n="projects.viewInVulnerabilityManagement">在漏洞管理中查看</button>
|
||||
</div>
|
||||
<div class="projects-fact-toolbar-filters" role="search">
|
||||
<label class="projects-fact-filter-field projects-fact-filter-field--search">
|
||||
<span class="sr-only" data-i18n="projects.searchVulnsSr">搜索漏洞</span>
|
||||
<svg class="projects-fact-search-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="11" cy="11" r="7" stroke="currentColor" stroke-width="2"/>
|
||||
<path d="M20 20L16 16" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
<input type="search" id="project-vulns-search" placeholder="搜索标题、描述、类型、目标…" oninput="debouncedLoadProjectVulnerabilities()" autocomplete="off" data-i18n="projects.searchVulnsPlaceholder" data-i18n-attr="placeholder">
|
||||
</label>
|
||||
<label class="projects-fact-filter-field">
|
||||
<span class="projects-fact-filter-label" data-i18n="projects.severity">严重度</span>
|
||||
<select id="project-vulns-filter-severity" onchange="loadProjectVulnerabilities()">
|
||||
<option value="" data-i18n="projects.all">全部</option>
|
||||
<option value="critical">critical</option>
|
||||
<option value="high">high</option>
|
||||
<option value="medium">medium</option>
|
||||
<option value="low">low</option>
|
||||
<option value="info">info</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="projects-fact-filter-field">
|
||||
<span class="projects-fact-filter-label" data-i18n="projects.status">状态</span>
|
||||
<select id="project-vulns-filter-status" onchange="loadProjectVulnerabilities()">
|
||||
<option value="" data-i18n="projects.all">全部</option>
|
||||
<option value="open" data-i18n="vulnerabilityPage.statusOpen">待处理</option>
|
||||
<option value="confirmed" data-i18n="vulnerabilityPage.statusConfirmed">已确认</option>
|
||||
<option value="fixed" data-i18n="vulnerabilityPage.statusFixed">已修复</option>
|
||||
<option value="false_positive" data-i18n="vulnerabilityPage.statusFalsePositive">误报</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="projects-table-wrap">
|
||||
<table class="data-table data-table--projects">
|
||||
|
||||
Reference in New Issue
Block a user