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();
}
})();
+139 -1
View File
@@ -6,6 +6,7 @@
<title data-i18n="apiDocs.pageTitle">API 文档 - CyberStrikeAI</title>
<link rel="icon" type="image/png" href="/static/logo.png">
<link rel="stylesheet" href="/static/css/style.css">
<script src="/static/js/theme.js"></script>
<style>
/* 覆盖主CSS的overflow限制,允许API文档页面滚动 */
body {
@@ -25,8 +26,23 @@
position: relative;
margin-bottom: 32px;
padding-bottom: 24px;
padding-right: 280px;
border-bottom: 2px solid var(--border-color);
}
.api-docs-header-actions {
position: absolute;
top: 0;
right: 0;
display: flex;
align-items: center;
gap: 10px;
}
.api-docs-header-actions .theme-toggle-btn,
.api-docs-header-actions .lang-switcher-btn {
min-height: 36px;
}
.api-docs-header h1 {
font-size: 2rem;
@@ -822,6 +838,114 @@
.empty-state p {
font-size: 0.875rem;
}
html[data-theme="dark"] body {
background: var(--bg-secondary);
}
html[data-theme="dark"] .api-docs-container {
background: var(--bg-secondary);
}
html[data-theme="dark"] .api-docs-sidebar,
html[data-theme="dark"] .auth-info-section,
html[data-theme="dark"] .api-endpoint,
html[data-theme="dark"] .api-endpoint-header,
html[data-theme="dark"] .api-test-form,
html[data-theme="dark"] .api-response-example,
html[data-theme="dark"] .api-description pre,
html[data-theme="dark"] .api-description-detail .code-block,
html[data-theme="dark"] .api-description-detail .inline-code,
html[data-theme="dark"] .api-description code {
background: #111827;
border-color: #263244;
}
html[data-theme="dark"] .api-endpoint:hover {
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.38);
}
html[data-theme="dark"] .api-group-link:hover {
background: rgba(96, 165, 250, 0.1);
color: var(--accent-hover);
}
html[data-theme="dark"] .api-group-link.active {
background: rgba(96, 165, 250, 0.16);
color: var(--accent-hover);
}
html[data-theme="dark"] .api-params-table th {
background: #0f172a;
}
html[data-theme="dark"] .api-test-input-group input,
html[data-theme="dark"] .api-test-input-group textarea {
background: #0f172a;
border-color: #2b374b;
color: var(--text-primary);
}
html[data-theme="dark"] .api-test-input-group input:focus,
html[data-theme="dark"] .api-test-input-group textarea:focus {
box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.16);
}
html[data-theme="dark"] .api-test-result.success {
background: rgba(52, 211, 153, 0.14);
color: #6ee7b7;
border-color: rgba(52, 211, 153, 0.28);
}
html[data-theme="dark"] .api-test-result.error {
background: rgba(248, 113, 113, 0.14);
color: #fca5a5;
border-color: rgba(248, 113, 113, 0.28);
}
html[data-theme="dark"] .api-test-btn.secondary {
background: #111827;
border-color: #2b374b;
color: var(--text-primary);
}
html[data-theme="dark"] .api-test-btn.secondary:hover {
background: #1f2937;
border-color: var(--accent-color);
}
html[data-theme="dark"] .api-docs-header-actions .theme-toggle-btn,
html[data-theme="dark"] .api-docs-header-actions .lang-switcher-btn {
background: #111827;
border-color: #2b374b;
color: var(--text-primary);
}
html[data-theme="dark"] .api-docs-header-actions .theme-toggle-btn:hover,
html[data-theme="dark"] .api-docs-header-actions .lang-switcher-btn:hover {
background: #1f2937;
border-color: var(--accent-color);
}
html[data-theme="dark"] #token-status {
background: rgba(96, 165, 250, 0.12) !important;
border-left-color: var(--accent-color) !important;
}
@media (max-width: 768px) {
.api-docs-header {
padding-right: 0;
padding-bottom: 72px;
}
.api-docs-header-actions {
top: auto;
bottom: 16px;
left: 0;
right: 0;
justify-content: flex-end;
}
}
</style>
</head>
<body>
@@ -837,7 +961,21 @@
<span data-i18n="apiDocs.title">API 文档</span>
</h1>
<p data-i18n="apiDocs.subtitle">CyberStrikeAI 平台 API 接口文档,支持在线测试</p>
<div class="api-docs-lang-switcher" style="position: absolute; top: 24px; right: 24px;">
<div class="api-docs-header-actions">
<button id="theme-toggle-btn" class="theme-toggle-btn btn-secondary" type="button" onclick="window.cycleThemePreference && window.cycleThemePreference()" data-i18n="theme.toggle" data-i18n-attr="title" title="切换主题" aria-label="切换主题">
<svg class="theme-toggle-icon theme-toggle-icon--system" width="16" height="16" viewBox="0 0 24 24" fill="none" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">
<rect x="3" y="4" width="18" height="13" rx="2" stroke="currentColor" stroke-width="2"/>
<path d="M8 21h8M12 17v4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
<svg class="theme-toggle-icon theme-toggle-icon--light" 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="4" stroke="currentColor" stroke-width="2"/>
<path d="M12 2v2M12 20v2M4 12H2M22 12h-2M5 5l1.5 1.5M17.5 17.5L19 19M19 5l-1.5 1.5M6.5 17.5L5 19" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
<svg class="theme-toggle-icon theme-toggle-icon--dark" width="16" height="16" viewBox="0 0 24 24" fill="none" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">
<path d="M21 14.5A8.5 8.5 0 0 1 9.5 3a7 7 0 1 0 11.5 11.5z" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/>
</svg>
<span id="theme-toggle-label">跟随系统</span>
</button>
<div class="lang-switcher">
<button type="button" class="btn-secondary lang-switcher-btn" onclick="typeof toggleLangDropdown === 'function' && toggleLangDropdown()" title="界面语言">
<span class="lang-switcher-icon">🌐</span>
+33 -2
View File
@@ -6,6 +6,23 @@
<title>CyberStrikeAI</title>
<link rel="icon" type="image/png" href="/static/logo.png">
<link rel="shortcut icon" type="image/png" href="/static/favicon.ico">
<script>
(function () {
try {
var stored = localStorage.getItem('cyberstrike-theme') || 'system';
if (stored !== 'system' && stored !== 'light' && stored !== 'dark') {
stored = 'system';
}
var prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
var resolved = stored === 'dark' || (stored !== 'light' && prefersDark) ? 'dark' : 'light';
document.documentElement.setAttribute('data-theme-preference', stored);
document.documentElement.setAttribute('data-theme', resolved);
document.documentElement.style.colorScheme = resolved;
} catch (e) {
document.documentElement.setAttribute('data-theme', 'light');
}
})();
</script>
<link rel="stylesheet" href="/static/css/style.css">
<link rel="stylesheet" href="/static/css/c2.css">
<link rel="stylesheet" href="/static/vendor/xterm.css">
@@ -57,12 +74,26 @@
</svg>
<span data-i18n="header.apiDocs">API 文档</span>
</button>
<button class="openapi-doc-btn" onclick="window.open('https://github.com/Ed1s0nZ/CyberStrikeAI', '_blank')" data-i18n="header.github" data-i18n-attr="title" data-i18n-skip-text="true" title="GitHub">
<button class="openapi-doc-btn" onclick="window.open('https://github.com/Ed1s0nZ/CyberStrikeAI', '_blank')" data-i18n="header.github" data-i18n-attr="title" data-i18n-skip-text="true" title="GitHub">
<svg width="16" height="16" viewBox="0 0 98 96" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z"/>
</svg>
<span data-i18n="header.github">GitHub</span>
</button>
<button id="theme-toggle-btn" class="theme-toggle-btn" type="button" onclick="window.cycleThemePreference && window.cycleThemePreference()" data-i18n="theme.toggle" data-i18n-attr="title" title="切换主题" aria-label="切换主题">
<svg class="theme-toggle-icon theme-toggle-icon--system" width="16" height="16" viewBox="0 0 24 24" fill="none" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">
<rect x="3" y="4" width="18" height="13" rx="2" stroke="currentColor" stroke-width="2"/>
<path d="M8 21h8M12 17v4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
<svg class="theme-toggle-icon theme-toggle-icon--light" 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="4" stroke="currentColor" stroke-width="2"/>
<path d="M12 2v2M12 20v2M4 12H2M22 12h-2M5 5l1.5 1.5M17.5 17.5L19 19M19 5l-1.5 1.5M6.5 17.5L5 19" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
<svg class="theme-toggle-icon theme-toggle-icon--dark" width="16" height="16" viewBox="0 0 24 24" fill="none" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">
<path d="M21 14.5A8.5 8.5 0 0 1 9.5 3a7 7 0 1 0 11.5 11.5z" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/>
</svg>
<span id="theme-toggle-label">跟随系统</span>
</button>
<div class="lang-switcher">
<button class="btn-secondary lang-switcher-btn" onclick="toggleLangDropdown()" data-i18n="header.language" data-i18n-attr="title" data-i18n-skip-text="true" title="界面语言">
<span class="lang-switcher-icon">🌐</span>
@@ -4706,6 +4737,7 @@
<script src="/static/vendor/i18next.min.js"></script>
<script src="/static/js/i18n.js"></script>
<script src="/static/js/theme.js"></script>
<script src="/static/js/builtin-tools.js"></script>
<script src="/static/js/auth.js"></script>
<script src="/static/js/modal.js"></script>
@@ -4736,4 +4768,3 @@
<script src="/static/js/c2.js"></script>
</body>
</html>