mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-05-26 17:27:54 +02:00
Add files via upload
This commit is contained in:
+134
-83
@@ -16990,8 +16990,8 @@ header {
|
||||
}
|
||||
|
||||
.vulnerability-controls {
|
||||
margin-bottom: 16px;
|
||||
padding: 10px 12px;
|
||||
margin-bottom: 12px;
|
||||
padding: 8px 10px;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 10px;
|
||||
@@ -17016,6 +17016,117 @@ header {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.vulnerability-filter-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.vulnerability-search-wrap {
|
||||
position: relative;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.vulnerability-search-wrap .vulnerability-search-icon {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: var(--text-secondary);
|
||||
pointer-events: none;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.vulnerability-search-wrap input[type="search"] {
|
||||
padding-left: 32px;
|
||||
}
|
||||
|
||||
.vulnerability-more-filters-anchor {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.vulnerability-more-filters-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
white-space: nowrap;
|
||||
padding: 8px 12px;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.vulnerability-more-filters-btn.is-active {
|
||||
border-color: #3b82f6;
|
||||
color: #2563eb;
|
||||
background: rgba(59, 130, 246, 0.06);
|
||||
}
|
||||
|
||||
.vulnerability-more-filters-popover {
|
||||
position: absolute;
|
||||
top: calc(100% + 6px);
|
||||
right: 0;
|
||||
z-index: 120;
|
||||
width: min(420px, calc(100vw - 32px));
|
||||
padding: 12px;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 10px;
|
||||
box-shadow: var(--shadow-md, 0 8px 24px rgba(15, 23, 42, 0.12));
|
||||
}
|
||||
|
||||
.vulnerability-more-filters-popover[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.vulnerability-more-filters-popover-title {
|
||||
margin: 0 0 10px;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.vulnerability-more-filters-popover-body {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px 12px;
|
||||
}
|
||||
|
||||
.vulnerability-more-filters-popover-body .vulnerability-filter-field--full {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.vulnerability-more-filters-popover-body .vulnerability-filter-field {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.vulnerability-more-filters-popover-body .vulnerability-filter-field > span {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.vulnerability-more-filters-popover-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
@media (max-width: 520px) {
|
||||
.vulnerability-more-filters-popover-body {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.vulnerability-more-filters-popover {
|
||||
right: auto;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.vulnerability-filter-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -17061,6 +17172,10 @@ header {
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.12);
|
||||
}
|
||||
|
||||
.vulnerability-filter-clear-btn[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.vulnerability-filter-clear-btn {
|
||||
flex-shrink: 0;
|
||||
padding: 8px 12px;
|
||||
@@ -17078,69 +17193,7 @@ header {
|
||||
background: rgba(59, 130, 246, 0.06);
|
||||
}
|
||||
|
||||
/* tasks-filters 的 display:flex 会覆盖 [hidden],必须显式隐藏 */
|
||||
#vulnerability-advanced-filters[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.vulnerability-filter-advanced-wrap {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.vulnerability-filter-advanced-wrap.is-expanded {
|
||||
margin-top: 10px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px dashed var(--border-color);
|
||||
}
|
||||
|
||||
.vulnerability-filter-advanced {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px 14px;
|
||||
}
|
||||
|
||||
.vulnerability-filter-advanced .vulnerability-filter-field {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.vulnerability-filter-advanced .vulnerability-filter-field > span {
|
||||
max-width: none;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.vulnerability-filter-advanced .vulnerability-filter-field input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.vulnerability-filter-advanced {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.vulnerability-filter-advanced-toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 8px;
|
||||
margin: 0 0 8px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.8125rem;
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
transition: color 0.15s ease, background 0.15s ease;
|
||||
}
|
||||
|
||||
.vulnerability-filter-advanced-toggle:hover {
|
||||
color: var(--text-primary);
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
/* 高级筛选生效数量:弱提示文字,避免实心蓝点过于抢眼 */
|
||||
/* 更多筛选生效数量:弱提示文字 */
|
||||
.vulnerability-filter-advanced-badge {
|
||||
display: inline;
|
||||
margin-left: 2px;
|
||||
@@ -17155,30 +17208,23 @@ header {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.vulnerability-filter-advanced-chevron {
|
||||
display: inline-block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
border-top: 5px solid currentColor;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.vulnerability-filter-advanced-toggle[aria-expanded="true"] .vulnerability-filter-advanced-chevron {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.vulnerability-filter-chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-top: 10px;
|
||||
padding-top: 10px;
|
||||
gap: 6px 8px;
|
||||
margin-top: 8px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.vulnerability-filter-chips-label {
|
||||
flex-shrink: 0;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.vulnerability-filter-chips-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -17222,8 +17268,13 @@ header {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
|
||||
.vulnerability-filter-actions {
|
||||
width: 100%;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.vulnerability-filter-clear-btn {
|
||||
align-self: flex-end;
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1545,6 +1545,10 @@
|
||||
"statClickAll": "View all (clear severity filter)",
|
||||
"statClickFilter": "Click to filter by this severity; click again to clear",
|
||||
"advancedFilters": "Advanced filters",
|
||||
"moreFilters": "More filters",
|
||||
"applyFilters": "Apply",
|
||||
"clearAdvanced": "Clear",
|
||||
"clearAll": "Reset all",
|
||||
"activeFilters": "Active filters",
|
||||
"chipRemove": "Remove",
|
||||
"filter": "Filter",
|
||||
@@ -1564,6 +1568,10 @@
|
||||
"statusFixed": "Fixed",
|
||||
"statusFalsePositive": "False positive",
|
||||
"searchVulnId": "Search vuln ID",
|
||||
"searchKeyword": "Search title, description, type, target…",
|
||||
"searchKeywordShort": "Keyword",
|
||||
"filterExactId": "Exact vuln ID",
|
||||
"filterEnterHint": "Press Enter to filter",
|
||||
"filterConversation": "Filter by conversation",
|
||||
"loading": "Loading...",
|
||||
"loadListFailed": "Failed to load",
|
||||
|
||||
@@ -1534,6 +1534,10 @@
|
||||
"statClickAll": "查看全部(清除严重度筛选)",
|
||||
"statClickFilter": "点击按此严重度筛选;再次点击清除",
|
||||
"advancedFilters": "高级筛选",
|
||||
"moreFilters": "更多筛选",
|
||||
"applyFilters": "应用",
|
||||
"clearAdvanced": "清空",
|
||||
"clearAll": "重置全部",
|
||||
"activeFilters": "已选条件",
|
||||
"chipRemove": "移除",
|
||||
"filter": "筛选",
|
||||
@@ -1552,7 +1556,11 @@
|
||||
"statusConfirmed": "已确认",
|
||||
"statusFixed": "已修复",
|
||||
"statusFalsePositive": "误报",
|
||||
"searchVulnId": "搜索漏洞ID",
|
||||
"searchVulnId": "搜索漏洞 ID",
|
||||
"searchKeyword": "搜索标题、描述、类型、目标…",
|
||||
"searchKeywordShort": "关键词",
|
||||
"filterExactId": "精确匹配漏洞 ID",
|
||||
"filterEnterHint": "回车筛选",
|
||||
"filterConversation": "筛选特定会话",
|
||||
"loading": "加载中...",
|
||||
"loadListFailed": "加载失败",
|
||||
|
||||
+152
-77
@@ -46,6 +46,7 @@ const getVulnerabilityPageSize = () => {
|
||||
|
||||
let currentVulnerabilityId = null;
|
||||
let vulnerabilityFilters = {
|
||||
q: '',
|
||||
id: '',
|
||||
conversation_id: '',
|
||||
task_id: '',
|
||||
@@ -65,11 +66,14 @@ const VULN_STAT_SEVERITIES = ['critical', 'high', 'medium', 'low', 'info'];
|
||||
let vulnerabilityStatCardsBound = false;
|
||||
let vulnerabilityFilterPanelBound = false;
|
||||
let vulnerabilityFilterOptionsCache = null;
|
||||
const VULNERABILITY_ADVANCED_OPEN_KEY = 'vulnerabilityAdvancedFiltersOpen';
|
||||
let vulnerabilityMoreFiltersPopoverOpen = false;
|
||||
let vulnerabilityFilterDebounceTimer = null;
|
||||
const VULNERABILITY_FILTER_DEBOUNCE_MS = 400;
|
||||
const VULNERABILITY_DATALIST_MAX = 8;
|
||||
const VULNERABILITY_DATALIST_MIN_QUERY = 2;
|
||||
|
||||
const VULN_FILTER_CHIP_FIELDS = [
|
||||
{ key: 'q', labelKey: 'vulnerabilityPage.searchKeywordShort' },
|
||||
{ key: 'id', labelKey: 'vulnerabilityPage.vulnId' },
|
||||
{ key: 'status', labelKey: null, format: 'status' },
|
||||
{ key: 'severity', labelKey: null, format: 'severity' },
|
||||
@@ -94,10 +98,12 @@ function syncVulnerabilityFiltersFromLocationHash() {
|
||||
const st = (params.get('status') || '').trim();
|
||||
const convTag = (params.get('conversation_tag') || '').trim();
|
||||
const taskTag = (params.get('task_tag') || '').trim();
|
||||
if (!vid && !cid && !tid && !sev && !st && !convTag && !taskTag) {
|
||||
const q = (params.get('q') || params.get('search') || '').trim();
|
||||
if (!vid && !cid && !tid && !sev && !st && !convTag && !taskTag && !q) {
|
||||
return;
|
||||
}
|
||||
|
||||
vulnerabilityFilters.q = '';
|
||||
vulnerabilityFilters.id = '';
|
||||
vulnerabilityFilters.conversation_id = '';
|
||||
vulnerabilityFilters.task_id = '';
|
||||
@@ -105,14 +111,16 @@ function syncVulnerabilityFiltersFromLocationHash() {
|
||||
vulnerabilityFilters.task_tag = '';
|
||||
vulnerabilityFilters.severity = '';
|
||||
vulnerabilityFilters.status = '';
|
||||
const idEl = document.getElementById('vulnerability-id-filter');
|
||||
const searchEl = document.getElementById('vulnerability-search-filter');
|
||||
const exactIdEl = document.getElementById('vulnerability-exact-id-filter');
|
||||
const convEl = document.getElementById('vulnerability-conversation-filter');
|
||||
const taskEl = document.getElementById('vulnerability-task-filter');
|
||||
const convTagEl = document.getElementById('vulnerability-conversation-tag-filter');
|
||||
const taskTagEl = document.getElementById('vulnerability-task-tag-filter');
|
||||
const sevEl = document.getElementById('vulnerability-severity-filter');
|
||||
const stEl = document.getElementById('vulnerability-status-filter');
|
||||
if (idEl) idEl.value = '';
|
||||
if (searchEl) searchEl.value = '';
|
||||
if (exactIdEl) exactIdEl.value = '';
|
||||
if (convEl) convEl.value = '';
|
||||
if (taskEl) taskEl.value = '';
|
||||
if (convTagEl) convTagEl.value = '';
|
||||
@@ -120,9 +128,13 @@ function syncVulnerabilityFiltersFromLocationHash() {
|
||||
if (sevEl) sevEl.value = '';
|
||||
if (stEl) stEl.value = '';
|
||||
|
||||
if (q) {
|
||||
vulnerabilityFilters.q = q;
|
||||
if (searchEl) searchEl.value = q;
|
||||
}
|
||||
if (vid) {
|
||||
vulnerabilityFilters.id = vid;
|
||||
if (idEl) idEl.value = vid;
|
||||
if (exactIdEl) exactIdEl.value = vid;
|
||||
}
|
||||
if (cid) {
|
||||
vulnerabilityFilters.conversation_id = cid;
|
||||
@@ -149,9 +161,6 @@ function syncVulnerabilityFiltersFromLocationHash() {
|
||||
if (stEl) stEl.value = st;
|
||||
}
|
||||
vulnerabilityPagination.currentPage = 1;
|
||||
if (hasVulnerabilityAdvancedFiltersActive()) {
|
||||
setVulnerabilityAdvancedFiltersOpen(true, false);
|
||||
}
|
||||
syncVulnerabilityStatCardActiveState();
|
||||
updateVulnerabilityFilterPanelState();
|
||||
renderVulnerabilityFilterChips();
|
||||
@@ -213,7 +222,8 @@ function applyVulnerabilitySeverityFilter(severity) {
|
||||
}
|
||||
|
||||
function readVulnerabilityFiltersFromForm() {
|
||||
vulnerabilityFilters.id = (document.getElementById('vulnerability-id-filter')?.value || '').trim();
|
||||
vulnerabilityFilters.q = (document.getElementById('vulnerability-search-filter')?.value || '').trim();
|
||||
vulnerabilityFilters.id = (document.getElementById('vulnerability-exact-id-filter')?.value || '').trim();
|
||||
vulnerabilityFilters.conversation_id = (document.getElementById('vulnerability-conversation-filter')?.value || '').trim();
|
||||
vulnerabilityFilters.task_id = (document.getElementById('vulnerability-task-filter')?.value || '').trim();
|
||||
vulnerabilityFilters.conversation_tag = (document.getElementById('vulnerability-conversation-tag-filter')?.value || '').trim();
|
||||
@@ -225,13 +235,13 @@ function readVulnerabilityFiltersFromForm() {
|
||||
|
||||
function hasVulnerabilityAdvancedFiltersActive() {
|
||||
const f = vulnerabilityFilters;
|
||||
return Boolean(f.conversation_id || f.task_id || f.conversation_tag || f.task_tag);
|
||||
return Boolean(f.id || f.conversation_id || f.task_id || f.conversation_tag || f.task_tag);
|
||||
}
|
||||
|
||||
function hasAnyVulnerabilityFilterActive() {
|
||||
const f = vulnerabilityFilters;
|
||||
return Boolean(
|
||||
f.id || f.conversation_id || f.task_id || f.conversation_tag || f.task_tag || f.severity || f.status
|
||||
f.q || f.id || f.conversation_id || f.task_id || f.conversation_tag || f.task_tag || f.severity || f.status
|
||||
);
|
||||
}
|
||||
|
||||
@@ -253,6 +263,7 @@ function updateVulnerabilityLocationHashFromFilters() {
|
||||
const params = new URLSearchParams(hashParts.length >= 2 ? hashParts.slice(1).join('?') : '');
|
||||
const f = vulnerabilityFilters;
|
||||
const pairs = [
|
||||
['q', f.q],
|
||||
['id', f.id],
|
||||
['conversation_id', f.conversation_id],
|
||||
['task_id', f.task_id],
|
||||
@@ -279,17 +290,82 @@ function updateVulnerabilityLocationHashFromFilters() {
|
||||
}
|
||||
}
|
||||
|
||||
function toggleVulnerabilityAdvancedFilters(ev) {
|
||||
function scheduleVulnerabilityFilterApply(immediate) {
|
||||
if (vulnerabilityFilterDebounceTimer) {
|
||||
clearTimeout(vulnerabilityFilterDebounceTimer);
|
||||
vulnerabilityFilterDebounceTimer = null;
|
||||
}
|
||||
if (immediate) {
|
||||
applyVulnerabilityFilters();
|
||||
return;
|
||||
}
|
||||
vulnerabilityFilterDebounceTimer = setTimeout(function () {
|
||||
vulnerabilityFilterDebounceTimer = null;
|
||||
applyVulnerabilityFilters();
|
||||
}, VULNERABILITY_FILTER_DEBOUNCE_MS);
|
||||
}
|
||||
|
||||
function syncVulnerabilityAdvancedFieldsFromFilters() {
|
||||
const map = {
|
||||
id: 'vulnerability-exact-id-filter',
|
||||
conversation_id: 'vulnerability-conversation-filter',
|
||||
task_id: 'vulnerability-task-filter',
|
||||
conversation_tag: 'vulnerability-conversation-tag-filter',
|
||||
task_tag: 'vulnerability-task-tag-filter'
|
||||
};
|
||||
Object.keys(map).forEach(function (key) {
|
||||
const el = document.getElementById(map[key]);
|
||||
if (el) el.value = vulnerabilityFilters[key] || '';
|
||||
});
|
||||
}
|
||||
|
||||
function setVulnerabilityMoreFiltersPopoverOpen(open) {
|
||||
const btn = document.getElementById('vulnerability-more-filters-btn');
|
||||
const popover = document.getElementById('vulnerability-more-filters-popover');
|
||||
if (!btn || !popover) return;
|
||||
vulnerabilityMoreFiltersPopoverOpen = open;
|
||||
btn.setAttribute('aria-expanded', open ? 'true' : 'false');
|
||||
btn.classList.toggle('is-active', open);
|
||||
popover.hidden = !open;
|
||||
}
|
||||
|
||||
function toggleVulnerabilityMoreFiltersPopover(ev) {
|
||||
if (ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
}
|
||||
const toggleBtn = document.getElementById('vulnerability-advanced-toggle');
|
||||
if (!toggleBtn) return;
|
||||
const expanded = toggleBtn.getAttribute('aria-expanded') === 'true';
|
||||
setVulnerabilityAdvancedFiltersOpen(!expanded, true);
|
||||
const opening = !vulnerabilityMoreFiltersPopoverOpen;
|
||||
if (opening) {
|
||||
readVulnerabilityFiltersFromForm();
|
||||
syncVulnerabilityAdvancedFieldsFromFilters();
|
||||
}
|
||||
setVulnerabilityMoreFiltersPopoverOpen(opening);
|
||||
}
|
||||
|
||||
function closeVulnerabilityMoreFiltersPopover(revertDraft) {
|
||||
if (revertDraft) syncVulnerabilityAdvancedFieldsFromFilters();
|
||||
setVulnerabilityMoreFiltersPopoverOpen(false);
|
||||
}
|
||||
|
||||
function clearVulnerabilityAdvancedFilterFields() {
|
||||
['vulnerability-exact-id-filter', 'vulnerability-conversation-filter', 'vulnerability-task-filter',
|
||||
'vulnerability-conversation-tag-filter', 'vulnerability-task-tag-filter'].forEach(function (id) {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.value = '';
|
||||
});
|
||||
}
|
||||
|
||||
function applyVulnerabilityMoreFiltersFromPopover() {
|
||||
closeVulnerabilityMoreFiltersPopover(false);
|
||||
scheduleVulnerabilityFilterApply(true);
|
||||
}
|
||||
|
||||
function onVulnerabilityFilterDocumentPointerDown(ev) {
|
||||
if (!vulnerabilityMoreFiltersPopoverOpen) return;
|
||||
const anchor = document.querySelector('.vulnerability-more-filters-anchor');
|
||||
if (anchor && anchor.contains(ev.target)) return;
|
||||
closeVulnerabilityMoreFiltersPopover(true);
|
||||
}
|
||||
window.toggleVulnerabilityAdvancedFilters = toggleVulnerabilityAdvancedFilters;
|
||||
|
||||
function initVulnerabilityFilterPanel() {
|
||||
const panel = document.getElementById('vulnerability-filter-panel');
|
||||
@@ -301,55 +377,69 @@ function initVulnerabilityFilterPanel() {
|
||||
}
|
||||
vulnerabilityFilterPanelBound = true;
|
||||
|
||||
let savedOpen = false;
|
||||
try {
|
||||
savedOpen = localStorage.getItem(VULNERABILITY_ADVANCED_OPEN_KEY) === 'true';
|
||||
} catch (e) { /* ignore */ }
|
||||
setVulnerabilityAdvancedFiltersOpen(savedOpen, false);
|
||||
|
||||
const stEl = document.getElementById('vulnerability-status-filter');
|
||||
if (stEl) stEl.addEventListener('change', applyVulnerabilityFilters);
|
||||
if (stEl) stEl.addEventListener('change', function () { scheduleVulnerabilityFilterApply(true); });
|
||||
|
||||
const textIds = [
|
||||
'vulnerability-id-filter',
|
||||
const searchEl = document.getElementById('vulnerability-search-filter');
|
||||
if (searchEl) {
|
||||
searchEl.addEventListener('input', function () { scheduleVulnerabilityFilterApply(false); });
|
||||
searchEl.addEventListener('keydown', function (ev) {
|
||||
if (ev.key === 'Enter') {
|
||||
ev.preventDefault();
|
||||
scheduleVulnerabilityFilterApply(true);
|
||||
}
|
||||
});
|
||||
searchEl.addEventListener('search', function () {
|
||||
if (searchEl.value === '') scheduleVulnerabilityFilterApply(true);
|
||||
});
|
||||
}
|
||||
|
||||
const moreBtn = document.getElementById('vulnerability-more-filters-btn');
|
||||
if (moreBtn) moreBtn.addEventListener('click', toggleVulnerabilityMoreFiltersPopover);
|
||||
|
||||
const applyBtn = document.getElementById('vulnerability-more-filters-apply');
|
||||
if (applyBtn) applyBtn.addEventListener('click', applyVulnerabilityMoreFiltersFromPopover);
|
||||
|
||||
const resetBtn = document.getElementById('vulnerability-more-filters-reset');
|
||||
if (resetBtn) {
|
||||
resetBtn.addEventListener('click', function () {
|
||||
clearVulnerabilityAdvancedFilterFields();
|
||||
applyVulnerabilityMoreFiltersFromPopover();
|
||||
});
|
||||
}
|
||||
|
||||
const advancedTextIds = [
|
||||
'vulnerability-exact-id-filter',
|
||||
'vulnerability-conversation-filter',
|
||||
'vulnerability-task-filter',
|
||||
'vulnerability-conversation-tag-filter',
|
||||
'vulnerability-task-tag-filter'
|
||||
];
|
||||
textIds.forEach(function (id) {
|
||||
advancedTextIds.forEach(function (id) {
|
||||
const el = document.getElementById(id);
|
||||
if (!el) return;
|
||||
el.addEventListener('keydown', function (ev) {
|
||||
if (ev.key === 'Enter') {
|
||||
ev.preventDefault();
|
||||
applyVulnerabilityFilters();
|
||||
applyVulnerabilityMoreFiltersFromPopover();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('mousedown', onVulnerabilityFilterDocumentPointerDown);
|
||||
document.addEventListener('keydown', function (ev) {
|
||||
if (ev.key === 'Escape' && vulnerabilityMoreFiltersPopoverOpen) {
|
||||
closeVulnerabilityMoreFiltersPopover(true);
|
||||
}
|
||||
});
|
||||
|
||||
bindVulnerabilityFilterTypeaheads();
|
||||
}
|
||||
|
||||
function setVulnerabilityAdvancedFiltersOpen(open, persist) {
|
||||
const toggleBtn = document.getElementById('vulnerability-advanced-toggle');
|
||||
const advanced = document.getElementById('vulnerability-advanced-filters');
|
||||
const wrap = document.querySelector('#vulnerability-filter-panel .vulnerability-filter-advanced-wrap');
|
||||
if (!toggleBtn || !advanced) return;
|
||||
toggleBtn.setAttribute('aria-expanded', open ? 'true' : 'false');
|
||||
advanced.hidden = !open;
|
||||
advanced.classList.toggle('is-open', open);
|
||||
if (wrap) wrap.classList.toggle('is-expanded', open);
|
||||
if (persist) {
|
||||
try {
|
||||
localStorage.setItem(VULNERABILITY_ADVANCED_OPEN_KEY, open ? 'true' : 'false');
|
||||
} catch (e) { /* ignore */ }
|
||||
}
|
||||
}
|
||||
|
||||
function countVulnerabilityAdvancedFiltersActive() {
|
||||
const f = vulnerabilityFilters;
|
||||
let n = 0;
|
||||
if (f.id) n++;
|
||||
if (f.conversation_id) n++;
|
||||
if (f.task_id) n++;
|
||||
if (f.conversation_tag) n++;
|
||||
@@ -379,6 +469,8 @@ function updateVulnerabilityFilterPanelState() {
|
||||
readVulnerabilityFiltersFromForm();
|
||||
panel.classList.toggle('is-filtered', hasAnyVulnerabilityFilterActive());
|
||||
updateVulnerabilityAdvancedBadge();
|
||||
const clearBtn = document.getElementById('vulnerability-filter-clear-btn');
|
||||
if (clearBtn) clearBtn.hidden = !hasAnyVulnerabilityFilterActive();
|
||||
}
|
||||
|
||||
function formatVulnerabilityFilterChipValue(key, value) {
|
||||
@@ -431,7 +523,8 @@ function renderVulnerabilityFilterChips() {
|
||||
|
||||
function removeVulnerabilityFilterByKey(key) {
|
||||
const map = {
|
||||
id: 'vulnerability-id-filter',
|
||||
q: 'vulnerability-search-filter',
|
||||
id: 'vulnerability-exact-id-filter',
|
||||
conversation_id: 'vulnerability-conversation-filter',
|
||||
task_id: 'vulnerability-task-filter',
|
||||
conversation_tag: 'vulnerability-conversation-tag-filter',
|
||||
@@ -609,13 +702,7 @@ async function loadVulnerabilityStats() {
|
||||
throw new Error('apiFetch未定义');
|
||||
}
|
||||
|
||||
const params = new URLSearchParams();
|
||||
if (vulnerabilityFilters.conversation_id) {
|
||||
params.append('conversation_id', vulnerabilityFilters.conversation_id);
|
||||
}
|
||||
if (vulnerabilityFilters.task_id) {
|
||||
params.append('task_id', vulnerabilityFilters.task_id);
|
||||
}
|
||||
const params = buildVulnerabilityFilterParams();
|
||||
|
||||
const response = await apiFetch(`/api/vulnerabilities/stats?${params.toString()}`);
|
||||
if (!response.ok) {
|
||||
@@ -689,31 +776,9 @@ async function loadVulnerabilities(page = null) {
|
||||
vulnerabilityPagination.currentPage = page;
|
||||
}
|
||||
|
||||
const params = new URLSearchParams();
|
||||
const params = buildVulnerabilityFilterParams();
|
||||
params.append('page', vulnerabilityPagination.currentPage.toString());
|
||||
params.append('limit', vulnerabilityPagination.pageSize.toString());
|
||||
|
||||
if (vulnerabilityFilters.id) {
|
||||
params.append('id', vulnerabilityFilters.id);
|
||||
}
|
||||
if (vulnerabilityFilters.conversation_id) {
|
||||
params.append('conversation_id', vulnerabilityFilters.conversation_id);
|
||||
}
|
||||
if (vulnerabilityFilters.task_id) {
|
||||
params.append('task_id', vulnerabilityFilters.task_id);
|
||||
}
|
||||
if (vulnerabilityFilters.conversation_tag) {
|
||||
params.append('conversation_tag', vulnerabilityFilters.conversation_tag);
|
||||
}
|
||||
if (vulnerabilityFilters.task_tag) {
|
||||
params.append('task_tag', vulnerabilityFilters.task_tag);
|
||||
}
|
||||
if (vulnerabilityFilters.severity) {
|
||||
params.append('severity', vulnerabilityFilters.severity);
|
||||
}
|
||||
if (vulnerabilityFilters.status) {
|
||||
params.append('status', vulnerabilityFilters.status);
|
||||
}
|
||||
|
||||
const response = await apiFetch(`/api/vulnerabilities?${params.toString()}`);
|
||||
if (!response.ok) {
|
||||
@@ -1090,8 +1155,14 @@ function filterVulnerabilities() {
|
||||
|
||||
// 清除筛选
|
||||
function clearVulnerabilityFilters() {
|
||||
closeVulnerabilityMoreFiltersPopover(false);
|
||||
if (vulnerabilityFilterDebounceTimer) {
|
||||
clearTimeout(vulnerabilityFilterDebounceTimer);
|
||||
vulnerabilityFilterDebounceTimer = null;
|
||||
}
|
||||
const fields = [
|
||||
'vulnerability-id-filter',
|
||||
'vulnerability-search-filter',
|
||||
'vulnerability-exact-id-filter',
|
||||
'vulnerability-conversation-filter',
|
||||
'vulnerability-task-filter',
|
||||
'vulnerability-conversation-tag-filter',
|
||||
@@ -1105,6 +1176,7 @@ function clearVulnerabilityFilters() {
|
||||
});
|
||||
|
||||
vulnerabilityFilters = {
|
||||
q: '',
|
||||
id: '',
|
||||
conversation_id: '',
|
||||
task_id: '',
|
||||
@@ -1277,8 +1349,11 @@ function formatVulnerabilityAsMarkdown(vuln) {
|
||||
|
||||
function buildVulnerabilityFilterParams() {
|
||||
const params = new URLSearchParams();
|
||||
if (vulnerabilityFilters.q) {
|
||||
params.append('q', vulnerabilityFilters.q);
|
||||
}
|
||||
const keys = ['id', 'conversation_id', 'task_id', 'conversation_tag', 'task_tag', 'severity', 'status'];
|
||||
keys.forEach((k) => {
|
||||
keys.forEach(function (k) {
|
||||
if (vulnerabilityFilters[k]) {
|
||||
params.append(k, vulnerabilityFilters[k]);
|
||||
}
|
||||
|
||||
+55
-32
@@ -1446,10 +1446,14 @@
|
||||
<div class="vulnerability-controls" id="vulnerability-filter-panel">
|
||||
<div class="vulnerability-filter-toolbar">
|
||||
<div class="vulnerability-filter-primary">
|
||||
<label class="vulnerability-filter-field vulnerability-filter-field--grow">
|
||||
<span class="sr-only" data-i18n="vulnerabilityPage.vulnId">漏洞ID</span>
|
||||
<input type="search" id="vulnerability-id-filter" autocomplete="off"
|
||||
data-i18n="vulnerabilityPage.searchVulnId" data-i18n-attr="placeholder" placeholder="搜索漏洞 ID,回车筛选" />
|
||||
<label class="vulnerability-filter-field vulnerability-filter-field--grow vulnerability-search-wrap">
|
||||
<span class="sr-only" data-i18n="vulnerabilityPage.searchKeyword">关键词搜索</span>
|
||||
<svg class="vulnerability-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="vulnerability-search-filter" autocomplete="off"
|
||||
data-i18n="vulnerabilityPage.searchKeyword" data-i18n-attr="placeholder" placeholder="搜索标题、描述、类型、目标…" />
|
||||
</label>
|
||||
<label class="vulnerability-filter-field vulnerability-filter-field--status">
|
||||
<span class="sr-only" data-i18n="vulnerabilityPage.status">状态</span>
|
||||
@@ -1461,7 +1465,52 @@
|
||||
<option value="false_positive" data-i18n="vulnerabilityPage.statusFalsePositive">误报</option>
|
||||
</select>
|
||||
</label>
|
||||
<button type="button" class="vulnerability-filter-clear-btn" onclick="clearVulnerabilityFilters()" data-i18n="vulnerabilityPage.clear">清除</button>
|
||||
<div class="vulnerability-filter-actions">
|
||||
<div class="vulnerability-more-filters-anchor">
|
||||
<button type="button" class="btn-secondary vulnerability-more-filters-btn" id="vulnerability-more-filters-btn"
|
||||
aria-expanded="false" aria-haspopup="dialog" aria-controls="vulnerability-more-filters-popover">
|
||||
<span data-i18n="vulnerabilityPage.moreFilters">更多筛选</span>
|
||||
<span class="vulnerability-filter-advanced-badge" id="vulnerability-advanced-badge" hidden></span>
|
||||
</button>
|
||||
<div class="vulnerability-more-filters-popover" id="vulnerability-more-filters-popover" hidden role="dialog"
|
||||
aria-labelledby="vulnerability-more-filters-title">
|
||||
<p class="vulnerability-more-filters-popover-title" id="vulnerability-more-filters-title" data-i18n="vulnerabilityPage.moreFilters">更多筛选</p>
|
||||
<div class="vulnerability-more-filters-popover-body">
|
||||
<label class="vulnerability-filter-field vulnerability-filter-field--full">
|
||||
<span data-i18n="vulnerabilityPage.vulnId">漏洞 ID</span>
|
||||
<input type="text" id="vulnerability-exact-id-filter"
|
||||
data-i18n="vulnerabilityPage.filterExactId" data-i18n-attr="placeholder" placeholder="精确匹配漏洞 ID" />
|
||||
</label>
|
||||
<label class="vulnerability-filter-field">
|
||||
<span data-i18n="vulnerabilityPage.conversationId">会话 ID</span>
|
||||
<input type="text" id="vulnerability-conversation-filter" list="vulnerability-conversation-suggestions"
|
||||
data-i18n="vulnerabilityPage.filterConversation" data-i18n-attr="placeholder" placeholder="筛选特定会话" />
|
||||
</label>
|
||||
<label class="vulnerability-filter-field">
|
||||
<span data-i18n="vulnerabilityPage.taskOrQueueId">任务 / 队列 ID</span>
|
||||
<input type="text" id="vulnerability-task-filter" list="vulnerability-task-suggestions"
|
||||
data-i18n="vulnerabilityPage.filterTaskOrQueue" data-i18n-attr="placeholder" placeholder="筛选任务或队列 ID" />
|
||||
</label>
|
||||
<label class="vulnerability-filter-field">
|
||||
<span data-i18n="vulnerabilityPage.conversationTag">对话标签</span>
|
||||
<input type="text" id="vulnerability-conversation-tag-filter" list="vulnerability-conversation-tag-suggestions"
|
||||
data-i18n="vulnerabilityPage.filterConversationTag" data-i18n-attr="placeholder" placeholder="筛选对话标签" />
|
||||
</label>
|
||||
<label class="vulnerability-filter-field">
|
||||
<span data-i18n="vulnerabilityPage.taskTag">任务标签</span>
|
||||
<input type="text" id="vulnerability-task-tag-filter" list="vulnerability-task-tag-suggestions"
|
||||
data-i18n="vulnerabilityPage.filterTaskTag" data-i18n-attr="placeholder" placeholder="筛选任务标签" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="vulnerability-more-filters-popover-footer">
|
||||
<button type="button" class="btn-secondary" id="vulnerability-more-filters-reset" data-i18n="vulnerabilityPage.clearAdvanced">清空</button>
|
||||
<button type="button" class="btn-primary" id="vulnerability-more-filters-apply" data-i18n="vulnerabilityPage.applyFilters">应用</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="vulnerability-filter-clear-btn" id="vulnerability-filter-clear-btn"
|
||||
onclick="clearVulnerabilityFilters()" hidden data-i18n="vulnerabilityPage.clearAll">重置全部</button>
|
||||
</div>
|
||||
</div>
|
||||
<select id="vulnerability-severity-filter" class="vulnerability-severity-sync" hidden aria-hidden="true" tabindex="-1">
|
||||
<option value=""></option>
|
||||
@@ -1472,34 +1521,8 @@
|
||||
<option value="info">info</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="vulnerability-filter-advanced-wrap">
|
||||
<button type="button" class="vulnerability-filter-advanced-toggle" id="vulnerability-advanced-toggle"
|
||||
aria-expanded="false" aria-controls="vulnerability-advanced-filters"
|
||||
onclick="toggleVulnerabilityAdvancedFilters(event)">
|
||||
<span class="vulnerability-filter-advanced-chevron" aria-hidden="true"></span>
|
||||
<span data-i18n="vulnerabilityPage.advancedFilters">高级筛选</span>
|
||||
<span class="vulnerability-filter-advanced-badge" id="vulnerability-advanced-badge" hidden></span>
|
||||
</button>
|
||||
<div class="vulnerability-filter-advanced" id="vulnerability-advanced-filters" hidden>
|
||||
<label class="vulnerability-filter-field">
|
||||
<span data-i18n="vulnerabilityPage.conversationId">会话 ID</span>
|
||||
<input type="text" id="vulnerability-conversation-filter" list="vulnerability-conversation-suggestions" placeholder="回车筛选" />
|
||||
</label>
|
||||
<label class="vulnerability-filter-field">
|
||||
<span data-i18n="vulnerabilityPage.taskOrQueueId">任务 / 队列 ID</span>
|
||||
<input type="text" id="vulnerability-task-filter" list="vulnerability-task-suggestions" placeholder="回车筛选" />
|
||||
</label>
|
||||
<label class="vulnerability-filter-field">
|
||||
<span data-i18n="vulnerabilityPage.conversationTag">对话标签</span>
|
||||
<input type="text" id="vulnerability-conversation-tag-filter" list="vulnerability-conversation-tag-suggestions" placeholder="回车筛选" />
|
||||
</label>
|
||||
<label class="vulnerability-filter-field">
|
||||
<span data-i18n="vulnerabilityPage.taskTag">任务标签</span>
|
||||
<input type="text" id="vulnerability-task-tag-filter" list="vulnerability-task-tag-suggestions" placeholder="回车筛选" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="vulnerability-filter-chips" id="vulnerability-filter-chips" hidden>
|
||||
<span class="vulnerability-filter-chips-label" data-i18n="vulnerabilityPage.activeFilters">已选条件</span>
|
||||
<div class="vulnerability-filter-chips-list" id="vulnerability-filter-chips-list" role="list"></div>
|
||||
</div>
|
||||
<datalist id="vulnerability-conversation-suggestions"></datalist>
|
||||
|
||||
Reference in New Issue
Block a user