Add files via upload

This commit is contained in:
公明
2026-07-02 17:50:09 +08:00
committed by GitHub
parent b804635fa8
commit 125685f08f
11 changed files with 2952 additions and 30 deletions
+88 -1
View File
@@ -33,6 +33,93 @@
--c2-mono: 'SF Mono', 'Fira Code', 'JetBrains Mono', 'Cascadia Code', monospace;
}
html[data-theme="dark"] {
--c2-accent: #60a5fa;
--c2-accent-hover: #93c5fd;
--c2-accent-dim: rgba(96, 165, 250, 0.14);
--c2-accent-glow: rgba(96, 165, 250, 0.28);
--c2-green: #34d399;
--c2-green-dim: rgba(52, 211, 153, 0.14);
--c2-red: #f87171;
--c2-red-dim: rgba(248, 113, 113, 0.14);
--c2-amber: #fbbf24;
--c2-amber-dim: rgba(251, 191, 36, 0.14);
--c2-purple: #a78bfa;
--c2-purple-dim: rgba(167, 139, 250, 0.14);
--c2-surface: #111827;
--c2-surface-alt: #0b1120;
--c2-border: #263244;
--c2-border-hover: #3b4a63;
--c2-text: #e5e7eb;
--c2-text-dim: #a7b0c0;
--c2-text-muted: #6b7280;
--c2-shadow-sm: 0 1px 3px rgba(0,0,0,0.34);
--c2-shadow-md: 0 8px 24px rgba(0,0,0,0.38);
--c2-shadow-lg: 0 18px 48px rgba(0,0,0,0.45);
}
html[data-theme="dark"] .c2-modal,
html[data-theme="dark"] .c2-modal-content,
html[data-theme="dark"] .c2-tab-panel--card,
html[data-theme="dark"] .c2-session-detail,
html[data-theme="dark"] .c2-payload-card,
html[data-theme="dark"] .c2-profile-card,
html[data-theme="dark"] .c2-event-card,
html[data-theme="dark"] .c2-task-row,
html[data-theme="dark"] .c2-listener-card {
background: var(--c2-surface);
color: var(--c2-text);
border-color: var(--c2-border);
}
html[data-theme="dark"] .c2-listener-card:hover,
html[data-theme="dark"] .c2-payload-card:hover,
html[data-theme="dark"] .c2-profile-card:hover {
border-color: var(--c2-border-hover);
}
html[data-theme="dark"] .c2-session-chip,
html[data-theme="dark"] .c2-listener-pill,
html[data-theme="dark"] .c2-task-type-badge,
html[data-theme="dark"] .c2-tab-btn {
background: var(--c2-surface-alt);
border-color: var(--c2-border);
}
html[data-theme="dark"] #page-c2-listeners,
html[data-theme="dark"] #page-c2-sessions,
html[data-theme="dark"] #page-c2-tasks,
html[data-theme="dark"] #page-c2-payloads,
html[data-theme="dark"] #page-c2-events,
html[data-theme="dark"] #page-c2-profiles,
html[data-theme="dark"] #page-c2-listeners .page-content,
html[data-theme="dark"] #page-c2-sessions .page-content,
html[data-theme="dark"] #page-c2-tasks .page-content,
html[data-theme="dark"] #page-c2-payloads .page-content,
html[data-theme="dark"] #page-c2-events .page-content,
html[data-theme="dark"] #page-c2-profiles .page-content,
html[data-theme="dark"] .c2-session-layout,
html[data-theme="dark"] .c2-session-main {
background: var(--c2-surface-alt) !important;
}
html[data-theme="dark"] .c2-session-sidebar-wrap,
html[data-theme="dark"] .c2-sessions-toolbar,
html[data-theme="dark"] .c2-session-sidebar {
background: #0b1120 !important;
border-color: var(--c2-border) !important;
}
html[data-theme="dark"] .c2-session-main-empty {
background: transparent !important;
color: var(--c2-text-dim) !important;
}
html[data-theme="dark"] .c2-session-main-empty__icon {
background: var(--c2-accent-dim) !important;
border-color: rgba(96, 165, 250, 0.35) !important;
}
/* ============================================================================
Form Controls (scoped to C2 pages)
============================================================================ */
@@ -533,7 +620,7 @@
min-height: 0;
overflow: hidden;
padding: 12px 16px 16px;
background: linear-gradient(180deg, #f8fafc 0%, #ffffff 180px);
background: linear-gradient(180deg, var(--c2-surface-alt) 0%, var(--c2-surface) 180px);
display: flex;
flex-direction: column;
}
+2445 -4
View File
File diff suppressed because it is too large Load Diff
+9
View File
@@ -39,6 +39,15 @@
"version": "Current version",
"toggleSidebar": "Collapse/expand sidebar"
},
"theme": {
"system": "System",
"light": "Light",
"dark": "Dark",
"titleSystem": "Current: system theme. Click to switch to light.",
"titleLight": "Current: light theme. Click to switch to dark.",
"titleDark": "Current: dark theme. Click to switch to system.",
"toggle": "Toggle theme"
},
"notifications": {
"title": "Notifications",
"empty": "No new events",
+9
View File
@@ -39,6 +39,15 @@
"version": "当前版本",
"toggleSidebar": "折叠/展开侧边栏"
},
"theme": {
"system": "跟随系统",
"light": "浅色",
"dark": "暗色",
"titleSystem": "当前:跟随系统主题。点击切换为浅色。",
"titleLight": "当前:浅色主题。点击切换为暗色。",
"titleDark": "当前:暗色主题。点击切换为跟随系统。",
"toggle": "切换主题"
},
"notifications": {
"title": "事件通知",
"empty": "暂无新事件",
+29 -2
View File
@@ -4210,6 +4210,7 @@ function renderAttackChain(chainData) {
const nodeCount = chainData.nodes.length;
const edgeCount = chainData.edges.length;
const isComplexGraph = nodeCount > 15 || edgeCount > 25;
const isDarkTheme = document.documentElement.getAttribute('data-theme') === 'dark';
// 优化节点标签:智能截断和换行
chainData.nodes.forEach(node => {
@@ -4313,6 +4314,29 @@ function renderAttackChain(chainData) {
iconType = 'vulnerability';
}
const labelTextColor = isDarkTheme ? '#E5E7EB' : '#0F172A';
if (isDarkTheme) {
typeColor = '#E5E7EB';
bgGradientStart = '#111827';
if (nodeType === 'target') {
bgGradientEnd = '#1E1B4B';
} else if (nodeType === 'action') {
bgGradientEnd = accentColor === '#10B981' ? '#052E2B' : '#172033';
} else if (nodeType === 'vulnerability') {
if (riskScore >= 80) {
bgGradientEnd = '#3F101C';
} else if (riskScore >= 60) {
bgGradientEnd = '#3B1D0D';
} else if (riskScore >= 40) {
bgGradientEnd = '#3A2A0A';
} else {
bgGradientEnd = '#063A36';
}
} else {
bgGradientEnd = '#172033';
}
}
// 为每个节点生成图标 background-imagedata URL
const iconSvg = _acBuildNodeIconDataUrl(iconType, accentColor, accentDark);
@@ -4345,6 +4369,7 @@ function renderAttackChain(chainData) {
accentDark: accentDark,
bgGradientStart: bgGradientStart,
bgGradientEnd: bgGradientEnd,
labelTextColor: labelTextColor,
iconDataUrl: iconSvg,
badgeText: badgeText,
riskScore: riskScore,
@@ -4444,7 +4469,9 @@ function renderAttackChain(chainData) {
},
'border-opacity': 0.5,
// 文字样式
'color': '#0f172a',
'color': function(ele) {
return ele.data('labelTextColor') || '#0f172a';
},
'font-size': function(ele) {
return isComplexGraph ? '13px' : '14px';
},
@@ -5048,7 +5075,7 @@ function showNodeDetails(nodeData) {
if (nodeData.metadata.ai_analysis) {
html += `
<div class="node-detail-item">
<strong>AI分析:</strong> <div style="margin-top: 5px; padding: 8px; background: #f5f5f5; border-radius: 4px;">${escapeHtml(nodeData.metadata.ai_analysis)}</div>
<strong>AI分析:</strong> <div class="node-detail-ai-analysis">${escapeHtml(nodeData.metadata.ai_analysis)}</div>
</div>
`;
}
+53 -19
View File
@@ -1707,7 +1707,7 @@ window.navigateToVulnerabilitiesWithFilter = navigateToVulnerabilitiesWithFilter
// 漏洞严重程度分布:半环形(donut)渲染
// 几何参数固定,便于配合 viewBox 0 0 560 320 的 SVG 容器
// 段间分隔由 CSS 的白色 stroke 完成,不使用 gapRad
// 段间分隔由 gapRad 几何间隙完成,不使用描边,避免浅色/暗色下白边或黑边过重
var SEVERITY_DONUT_CFG = {
// viewBox 0 0 480 260:整体保持紧凑,但环厚回到「黄金比例」附近,
// 让弧带本身有视觉分量,又不像最早那版那样占太多空间。
@@ -1717,7 +1717,7 @@ var SEVERITY_DONUT_CFG = {
rOuter: 165,
rInner: 115, // 环厚 = 50(介于原 90 和上一版 35 之间,自然且有质感)
labelOffset: 14,
gapRad: 0.012
gapRad: 0.022
};
// 三段渐变:[高光浅调, 中段饱和色, 深色边缘] —— 做出类似 3D 釉面的层次
@@ -1759,28 +1759,50 @@ function severityLabel(id) {
return SEVERITY_DEFAULT_LABELS[id] || id;
}
function isDashboardDarkTheme() {
return document.documentElement.getAttribute('data-theme') === 'dark';
}
function ensureSeverityDonutDefs() {
var defsEl = document.getElementById('dashboard-severity-donut-defs');
if (!defsEl || defsEl.hasChildNodes()) return;
if (!defsEl) return;
var dark = isDashboardDarkTheme();
var html = '';
html += '<linearGradient id="donut-track-face" x1="0%" y1="0%" x2="0%" y2="100%">';
html += '<stop offset="0%" stop-color="#f8fafc"/>';
html += '<stop offset="55%" stop-color="#e8eef5"/>';
html += '<stop offset="100%" stop-color="#dce5ef"/>';
if (dark) {
html += '<stop offset="0%" stop-color="#334155"/>';
html += '<stop offset="55%" stop-color="#1e293b"/>';
html += '<stop offset="100%" stop-color="#172033"/>';
} else {
html += '<stop offset="0%" stop-color="#f8fafc"/>';
html += '<stop offset="55%" stop-color="#e8eef5"/>';
html += '<stop offset="100%" stop-color="#dce5ef"/>';
}
html += '</linearGradient>';
html += '<radialGradient id="donut-track-vignette" cx="50%" cy="85%" r="75%" fx="50%" fy="85%">';
html += '<stop offset="0%" stop-color="#ffffff" stop-opacity="0.35"/>';
html += '<stop offset="70%" stop-color="#ffffff" stop-opacity="0"/>';
if (dark) {
html += '<stop offset="0%" stop-color="#0f172a" stop-opacity="0.55"/>';
html += '<stop offset="70%" stop-color="#0f172a" stop-opacity="0"/>';
} else {
html += '<stop offset="0%" stop-color="#ffffff" stop-opacity="0.35"/>';
html += '<stop offset="70%" stop-color="#ffffff" stop-opacity="0"/>';
}
html += '</radialGradient>';
html += '<radialGradient id="donut-inner-gloss" cx="35%" cy="75%" r="55%">';
html += '<stop offset="0%" stop-color="#ffffff" stop-opacity="0.45"/>';
html += '<stop offset="55%" stop-color="#ffffff" stop-opacity="0.08"/>';
html += '<stop offset="100%" stop-color="#ffffff" stop-opacity="0"/>';
if (dark) {
html += '<stop offset="0%" stop-color="#94a3b8" stop-opacity="0.10"/>';
html += '<stop offset="55%" stop-color="#94a3b8" stop-opacity="0.03"/>';
html += '<stop offset="100%" stop-color="#94a3b8" stop-opacity="0"/>';
} else {
html += '<stop offset="0%" stop-color="#ffffff" stop-opacity="0.45"/>';
html += '<stop offset="55%" stop-color="#ffffff" stop-opacity="0.08"/>';
html += '<stop offset="100%" stop-color="#ffffff" stop-opacity="0"/>';
}
html += '</radialGradient>';
html += '<filter id="donut-segment-soften" x="-18%" y="-18%" width="136%" height="136%" color-interpolation-filters="sRGB">';
html += '<feGaussianBlur in="SourceAlpha" stdDeviation="0.8" result="blur"/>';
html += '<feOffset dx="0" dy="1.5" in="blur" result="off"/>';
html += '<feFlood flood-color="#0f172a" flood-opacity="0.13" result="flood"/>';
html += '<feFlood flood-color="' + (dark ? '#000000' : '#0f172a') + '" flood-opacity="' + (dark ? '0.28' : '0.13') + '" result="flood"/>';
html += '<feComposite in="flood" in2="off" operator="in" result="shadow"/>';
html += '<feMerge><feMergeNode in="shadow"/><feMergeNode in="SourceGraphic"/></feMerge>';
html += '</filter>';
@@ -1812,13 +1834,11 @@ function renderSeverityDonut(bySeverity, total) {
ensureSeverityDonutDefs();
// 背景轨迹(完整半环):双层填充营造凹槽 + 高光
if (!trackEl.hasChildNodes()) {
var trackPath = halfRingPath(cfg.cx, cfg.cy, cfg.rOuter, cfg.rInner);
trackEl.innerHTML =
'<path class="donut-track-shadow" d="' + trackPath + '"/>' +
'<path class="donut-track" fill="url(#donut-track-face)" d="' + trackPath + '"/>' +
'<path class="donut-track-vignette" fill="url(#donut-track-vignette)" d="' + trackPath + '"/>';
}
var trackPath = halfRingPath(cfg.cx, cfg.cy, cfg.rOuter, cfg.rInner);
trackEl.innerHTML =
'<path class="donut-track-shadow" d="' + trackPath + '"/>' +
'<path class="donut-track" fill="url(#donut-track-face)" d="' + trackPath + '"/>' +
'<path class="donut-track-vignette" fill="url(#donut-track-vignette)" d="' + trackPath + '"/>';
var ids = ['critical', 'high', 'medium', 'low', 'info'];
var severities = ids.map(function (id) {
@@ -2105,6 +2125,20 @@ function attachSeverityDonutInteractivity() {
legend.addEventListener('click', severityLegendClick);
legend.addEventListener('keydown', severityLegendKeydown);
}
if (!severityDonutState.themeObserver) {
severityDonutState.themeObserver = new MutationObserver(function (mutations) {
for (var i = 0; i < mutations.length; i++) {
if (mutations[i].attributeName === 'data-theme') {
renderSeverityDonut(severityDonutState.bySeverity, severityDonutState.total);
break;
}
}
});
severityDonutState.themeObserver.observe(document.documentElement, {
attributes: true,
attributeFilter: ['data-theme']
});
}
}
legend && legend.querySelectorAll('.dashboard-severity-legend-item').forEach(function (item) {
+6
View File
@@ -154,6 +154,9 @@
}
applyTranslations(document);
updateLangLabel();
if (typeof window.refreshThemeToggleLabel === 'function') {
window.refreshThemeToggleLabel();
}
try {
window.__locale = lang;
} catch (e) { /* ignore */ }
@@ -180,6 +183,9 @@
await loadLanguageResources(initialLang);
applyTranslations(document);
updateLangLabel();
if (typeof window.refreshThemeToggleLabel === 'function') {
window.refreshThemeToggleLabel();
}
try {
window.__locale = i18next.language || initialLang;
} catch (e) { /* ignore */ }
+1 -1
View File
@@ -650,7 +650,7 @@ async function viewSkill(skillId) {
<div style="margin-bottom: 8px;"><strong>${escapeHtml(pathLabel)}</strong> ${escapeHtml(sumSkill.path || '')}</div>
<div style="margin-bottom: 16px;"><strong>${escapeHtml(modTimeLabel)}</strong> ${escapeHtml(sumSkill.mod_time || '')}</div>
<div style="margin-bottom: 8px;"><strong>${escapeHtml(contentLabel)}</strong> <span style="opacity:0.8;font-size:12px;">${escapeHtml(_t('skills.summaryHint'))}</span></div>
<pre id="skill-view-body" style="background: #f5f5f5; padding: 16px; border-radius: 4px; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;">${escapeHtml(sumSkill.content || '')}</pre>
<pre id="skill-view-body" style="padding: 16px; border-radius: 4px; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word; border: 1px solid var(--border-color);">${escapeHtml(sumSkill.content || '')}</pre>
</div>
<div class="modal-footer">
<button type="button" class="btn-secondary" data-skill-load-full>${escapeHtml(loadFullLabel)}</button>
+140
View File
@@ -0,0 +1,140 @@
(function () {
'use strict';
const STORAGE_KEY = 'cyberstrike-theme';
const THEMES = ['system', 'light', 'dark'];
const FALLBACK_LABELS = {
system: '跟随系统',
light: '浅色',
dark: '暗色'
};
const FALLBACK_TITLES = {
system: '当前:跟随系统主题。点击切换为浅色。',
light: '当前:浅色主题。点击切换为暗色。',
dark: '当前:暗色主题。点击切换为跟随系统。'
};
const TITLE_KEYS = {
system: 'titleSystem',
light: 'titleLight',
dark: 'titleDark'
};
const media = window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null;
function themeText(key, fallback) {
if (typeof window.t === 'function') {
const value = window.t('theme.' + key);
if (value && value !== 'theme.' + key) {
return value;
}
}
return fallback;
}
function getLabel(preference) {
return themeText(preference, FALLBACK_LABELS[preference] || FALLBACK_LABELS.system);
}
function getTitle(preference) {
const titleKey = TITLE_KEYS[preference] || TITLE_KEYS.system;
return themeText(titleKey, FALLBACK_TITLES[preference] || FALLBACK_TITLES.system);
}
function normalizePreference(value) {
return THEMES.includes(value) ? value : 'system';
}
function readPreference() {
try {
return normalizePreference(localStorage.getItem(STORAGE_KEY));
} catch (err) {
return 'system';
}
}
function resolveTheme(preference) {
if (preference === 'dark' || preference === 'light') {
return preference;
}
return media && media.matches ? 'dark' : 'light';
}
function updateButton(preference, resolved) {
const btn = document.getElementById('theme-toggle-btn');
const label = document.getElementById('theme-toggle-label');
if (!btn) {
return;
}
btn.dataset.themePreference = preference;
btn.dataset.theme = resolved;
const title = getTitle(preference);
btn.title = title;
btn.setAttribute('aria-label', title);
if (label) {
label.textContent = getLabel(preference);
}
}
function applyTheme(preference) {
const normalized = normalizePreference(preference);
const resolved = resolveTheme(normalized);
const root = document.documentElement;
root.setAttribute('data-theme-preference', normalized);
root.setAttribute('data-theme', resolved);
root.style.colorScheme = resolved;
updateButton(normalized, resolved);
}
function savePreference(preference) {
const normalized = normalizePreference(preference);
try {
localStorage.setItem(STORAGE_KEY, normalized);
} catch (err) {
// Ignore storage failures; the current page can still apply the theme.
}
applyTheme(normalized);
}
window.setThemePreference = savePreference;
window.getThemePreference = readPreference;
window.cycleThemePreference = function () {
const current = readPreference();
const next = THEMES[(THEMES.indexOf(current) + 1) % THEMES.length];
savePreference(next);
};
window.refreshThemeToggleLabel = function () {
applyTheme(readPreference());
};
if (media) {
const onSystemThemeChange = function () {
if (readPreference() === 'system') {
applyTheme('system');
}
};
if (typeof media.addEventListener === 'function') {
media.addEventListener('change', onSystemThemeChange);
} else if (typeof media.addListener === 'function') {
media.addListener(onSystemThemeChange);
}
}
document.addEventListener('languagechange', function () {
applyTheme(readPreference());
});
function initTheme() {
applyTheme(readPreference());
if (window.i18nReady && typeof window.i18nReady.then === 'function') {
window.i18nReady.then(function () {
applyTheme(readPreference());
});
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initTheme);
} else {
initTheme();
}
})();