mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-07-02 18:55:52 +02:00
Add files via upload
This commit is contained in:
+88
-1
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||
|
||||
@@ -39,6 +39,15 @@
|
||||
"version": "当前版本",
|
||||
"toggleSidebar": "折叠/展开侧边栏"
|
||||
},
|
||||
"theme": {
|
||||
"system": "跟随系统",
|
||||
"light": "浅色",
|
||||
"dark": "暗色",
|
||||
"titleSystem": "当前:跟随系统主题。点击切换为浅色。",
|
||||
"titleLight": "当前:浅色主题。点击切换为暗色。",
|
||||
"titleDark": "当前:暗色主题。点击切换为跟随系统。",
|
||||
"toggle": "切换主题"
|
||||
},
|
||||
"notifications": {
|
||||
"title": "事件通知",
|
||||
"empty": "暂无新事件",
|
||||
|
||||
+29
-2
@@ -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-image(data 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
@@ -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) {
|
||||
|
||||
@@ -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 */ }
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user