From 9712ff931192ee7bfb21c84609d2a6bce911ee16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AC=E6=98=8E?= <83812544+Ed1s0nZ@users.noreply.github.com> Date: Tue, 7 Jul 2026 15:09:04 +0800 Subject: [PATCH] Add files via upload --- web/static/css/style.css | 9 +- web/static/js/fact-graph.js | 200 ++++++++++++++++++++++++++++++------ web/static/js/theme.js | 5 + 3 files changed, 179 insertions(+), 35 deletions(-) diff --git a/web/static/css/style.css b/web/static/css/style.css index abe8e3cc..ea84834d 100644 --- a/web/static/css/style.css +++ b/web/static/css/style.css @@ -27049,8 +27049,8 @@ html[data-theme="dark"] .chat-files-toast.chat-toast--error { z-index: 20; margin: 0; padding: 10px 0 12px; - background: #fff; - border-top: 1px solid #eef2f7; + background: var(--bg-primary); + border-top: 1px solid var(--border-color); } .projects-graph-toolbar-row { align-items: flex-end; @@ -31128,8 +31128,9 @@ html[data-theme="dark"] #page-projects .projects-graph-legend-divider { } html[data-theme="dark"] #page-projects .projects-graph-legend-item--node i { - background: #111827 !important; - box-shadow: none !important; + background: linear-gradient(135deg, #1e293b 0%, #172033 100%) !important; + border-color: var(--legend-color, #64748b) !important; + box-shadow: inset 0 1px 0 rgba(148, 163, 184, 0.06) !important; } html[data-theme="dark"] #page-projects .projects-settings-intro, diff --git a/web/static/js/fact-graph.js b/web/static/js/fact-graph.js index a787debe..001dd8d8 100644 --- a/web/static/js/fact-graph.js +++ b/web/static/js/fact-graph.js @@ -44,31 +44,115 @@ const CARD_KEY_FONT = 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace'; + const NODE_TYPE_STYLES = { + target: { + typeLabel: '目标', + typeEn: 'TARGET', + accent: { light: '#4F46E5', dark: '#818cf8' }, + bgEnd: { light: '#F5F3FF', dark: '#1e1b4b' }, + icon: 'target', + }, + finding: { + typeLabel: '发现', + typeEn: 'FINDING', + accent: { light: '#E11D48', dark: '#fb7185' }, + bgEnd: { light: '#FFF1F2', dark: '#3f1219' }, + icon: 'finding', + }, + exploit: { + typeLabel: '利用', + typeEn: 'EXPLOIT', + accent: { light: '#B45309', dark: '#fbbf24' }, + bgEnd: { light: '#FFFBEB', dark: '#3b2712' }, + icon: 'vulnerability', + }, + vulnerability: { + typeLabel: '漏洞', + typeEn: 'VULN', + accent: { light: '#9333EA', dark: '#c084fc' }, + bgEnd: { light: '#F5F3FF', dark: '#2e1065' }, + icon: 'vuln', + }, + auth: { + typeLabel: '认证', + typeEn: 'AUTH', + accent: { light: '#0D9488', dark: '#2dd4bf' }, + bgEnd: { light: '#F0FDFA', dark: '#0f2926' }, + icon: 'default', + }, + infra: { + typeLabel: '基础设施', + typeEn: 'INFRA', + accent: { light: '#64748B', dark: '#94a3b8' }, + bgEnd: { light: '#F8FAFC', dark: '#1e293b' }, + icon: 'default', + }, + chain: { + typeLabel: '攻击链', + typeEn: 'CHAIN', + accent: { light: '#7C3AED', dark: '#a78bfa' }, + bgEnd: { light: '#F5F3FF', dark: '#2e1065' }, + icon: 'vulnerability', + }, + poc: { + typeLabel: 'POC', + typeEn: 'POC', + accent: { light: '#C2410C', dark: '#fb923c' }, + bgEnd: { light: '#FFEDD5', dark: '#3b2010' }, + icon: 'vulnerability', + }, + business: { + typeLabel: '业务', + typeEn: 'BUSINESS', + accent: { light: '#0369A1', dark: '#38bdf8' }, + bgEnd: { light: '#F0F9FF', dark: '#0c2540' }, + icon: 'default', + }, + missing: { + typeLabel: '缺失', + typeEn: 'MISSING', + accent: { light: '#CBD5E1', dark: '#64748b' }, + bgEnd: { light: '#F1F5F9', dark: '#1e293b' }, + icon: 'default', + }, + note: { + typeLabel: '备注', + typeEn: 'NOTE', + accent: { light: '#94A3B8', dark: '#94a3b8' }, + bgEnd: { light: '#F8FAFC', dark: '#1e293b' }, + icon: 'default', + }, + }; + + function isDarkTheme() { + return document.documentElement.getAttribute('data-theme') === 'dark'; + } + + function cardPalette() { + const dark = isDarkTheme(); + return { + bgStart: dark ? '#1e293b' : '#FFFFFF', + keyColor: dark ? '#94a3b8' : '#64748b', + summaryColor: dark ? '#e5e7eb' : '#0f172a', + nodeFallbackBg: dark ? '#1e293b' : '#ffffff', + }; + } + function nodeTheme(type) { - switch (type) { - case 'target': - return { typeLabel: '目标', typeEn: 'TARGET', accent: '#4F46E5', bgEnd: '#F5F3FF', icon: 'target' }; - case 'finding': - return { typeLabel: '发现', typeEn: 'FINDING', accent: '#E11D48', bgEnd: '#FFF1F2', icon: 'finding', cardStyle: 'default' }; - case 'exploit': - return { typeLabel: '利用', typeEn: 'EXPLOIT', accent: '#B45309', bgEnd: '#FFFBEB', icon: 'vulnerability', cardStyle: 'default' }; - case 'vulnerability': - return { typeLabel: '漏洞', typeEn: 'VULN', accent: '#9333EA', bgEnd: '#F5F3FF', icon: 'vuln', cardStyle: 'default' }; - case 'auth': - return { typeLabel: '认证', typeEn: 'AUTH', accent: '#0D9488', bgEnd: '#F0FDFA', icon: 'default' }; - case 'infra': - return { typeLabel: '基础设施', typeEn: 'INFRA', accent: '#64748B', bgEnd: '#F8FAFC', icon: 'default' }; - case 'chain': - return { typeLabel: '攻击链', typeEn: 'CHAIN', accent: '#7C3AED', bgEnd: '#F5F3FF', icon: 'vulnerability' }; - case 'poc': - return { typeLabel: 'POC', typeEn: 'POC', accent: '#C2410C', bgEnd: '#FFEDD5', icon: 'vulnerability' }; - case 'business': - return { typeLabel: '业务', typeEn: 'BUSINESS', accent: '#0369A1', bgEnd: '#F0F9FF', icon: 'default' }; - case 'missing': - return { typeLabel: '缺失', typeEn: 'MISSING', accent: '#CBD5E1', bgEnd: '#F1F5F9', icon: 'default' }; - default: - return { typeLabel: '备注', typeEn: 'NOTE', accent: '#94A3B8', bgEnd: '#F8FAFC', icon: 'default' }; - } + const style = NODE_TYPE_STYLES[type] || NODE_TYPE_STYLES.note; + const dark = isDarkTheme(); + const palette = cardPalette(); + return { + typeLabel: style.typeLabel, + typeEn: style.typeEn, + accent: dark ? style.accent.dark : style.accent.light, + bgStart: palette.bgStart, + bgEnd: dark ? style.bgEnd.dark : style.bgEnd.light, + keyColor: palette.keyColor, + summaryColor: palette.summaryColor, + icon: style.icon, + cardStyle: style.cardStyle || 'default', + }; } function escapeXml(str) { @@ -228,7 +312,10 @@ function buildNodeCardSvgUrl(theme, layout, confidence) { const { width, height, headerLines, keyLines, summaryLines } = layout; const accent = theme.accent; + const bgStart = theme.bgStart; const bgEnd = theme.bgEnd; + const keyColor = theme.keyColor; + const summaryColor = theme.summaryColor; const conf = (confidence || '').toLowerCase(); const isTentative = conf === 'tentative'; const isDeprecated = conf === 'deprecated'; @@ -258,14 +345,14 @@ const keySvg = keyLines .map( (line, i) => - `${escapeXml(line)}`, + `${escapeXml(line)}`, ) .join(''); const summarySvg = summaryLines .map( (line, i) => - `${escapeXml(line)}`, + `${escapeXml(line)}`, ) .join(''); @@ -275,7 +362,7 @@ const svg = `` + `` + - `` + + `` + `` + `` + `` + @@ -290,6 +377,52 @@ } } + function applyNodePalette() { + if (!_cy) return; + const palette = cardPalette(); + _cy.style().selector('node').style('background-color', palette.nodeFallbackBg).update(); + } + + function rebuildNodeCards() { + if (!_cy) return; + applyNodePalette(); + _cy.nodes().forEach((nodeEle) => { + const d = nodeEle.data(); + const visualType = d.type || 'note'; + const theme = nodeTheme(visualType); + const statusBadge = d.statusBadge || buildStatusBadge(d.confidence); + const layout = computeNodeLayout( + visualType, + d.summary || '—', + statusBadge, + theme, + d.factKey, + ); + nodeEle.data({ + cardSvgUrl: buildNodeCardSvgUrl(theme, layout, d.confidence), + accentColor: theme.accent, + nodeWidth: layout.width, + nodeHeight: layout.height, + label: layout.searchLabel, + }); + }); + } + + function refreshTheme() { + rebuildNodeCards(); + if (!_cy) return; + const accent = isDarkTheme() ? '#60a5fa' : '#4F46E5'; + _cy.style() + .selector('edge:selected') + .style({ + 'line-color': accent, + 'target-arrow-color': accent, + }) + .selector('node:selected') + .style('border-color', accent) + .update(); + } + function destroy() { if (_resizeObs) { _resizeObs.disconnect(); @@ -487,6 +620,7 @@ accentColor: theme.accent, statusBadge: statusBadge, confidence: node.confidence || '', + summary: summary, nodeWidth: layout.width, nodeHeight: layout.height, cardSvgUrl: buildNodeCardSvgUrl(theme, layout, node.confidence), @@ -510,6 +644,7 @@ }); }); + const palette = cardPalette(); _cy = cytoscape({ container, elements, @@ -521,7 +656,7 @@ width: (ele) => ele.data('nodeWidth') || CARD_MIN_W, height: (ele) => ele.data('nodeHeight') || CARD_MIN_H, shape: 'round-rectangle', - 'background-color': '#ffffff', + 'background-color': palette.nodeFallbackBg, 'background-image': (ele) => ele.data('cardSvgUrl') || 'none', 'background-width': (ele) => (ele.data('nodeWidth') || CARD_MIN_W) + 'px', 'background-height': (ele) => (ele.data('nodeHeight') || CARD_MIN_H) + 'px', @@ -549,15 +684,15 @@ style: { width: 3.5, opacity: 1, - 'line-color': '#4F46E5', - 'target-arrow-color': '#4F46E5', + 'line-color': isDarkTheme() ? '#60a5fa' : '#4F46E5', + 'target-arrow-color': isDarkTheme() ? '#60a5fa' : '#4F46E5', }, }, { selector: 'node:selected', style: { 'border-width': 3, - 'border-color': '#4F46E5', + 'border-color': isDarkTheme() ? '#60a5fa' : '#4F46E5', 'border-opacity': 1, }, }, @@ -674,7 +809,10 @@ setConnectMode, selectEdge, clearEdgeSelection, + refreshTheme, nodeTheme, resolveGraphNodeType, }; + + document.addEventListener('cyberstrike-themechange', refreshTheme); })(typeof window !== 'undefined' ? window : globalThis); diff --git a/web/static/js/theme.js b/web/static/js/theme.js index 690b6cd9..ab5cd14d 100644 --- a/web/static/js/theme.js +++ b/web/static/js/theme.js @@ -83,6 +83,11 @@ root.setAttribute('data-theme', resolved); root.style.colorScheme = resolved; updateButton(normalized, resolved); + document.dispatchEvent( + new CustomEvent('cyberstrike-themechange', { + detail: { preference: normalized, resolved: resolved }, + }), + ); } function savePreference(preference) {