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 =
`