Files
2026-06-16 11:55:14 +08:00

695 lines
27 KiB
JavaScript

function renderPageMSC() {
// 渲染数学公式
document.querySelectorAll('pre code.language-math, pre code.language-katex, pre code.language-latex').forEach((block) => {
const parentPre = block.parentNode;
const displayMode = true;
const mathDiv = document.createElement('div');
mathDiv.className = 'math';
mathDiv.textContent = block.textContent;
katex.render(mathDiv.textContent, mathDiv, {
throwOnError: false,
displayMode
});
parentPre.parentNode.replaceChild(mathDiv, parentPre);
});
// 渲染流程图和序列图
document.querySelectorAll('pre code.language-flow, pre code.language-seq').forEach((block) => {
const parentPre = block.parentNode;
const diagram = document.createElement('div');
diagram.className = 'mermaid';
diagram.textContent = block.textContent;
parentPre.parentNode.replaceChild(diagram, parentPre);
});
mermaid.init();
// 为普通代码块添加行号、标题、复制按钮和演示按钮
document.querySelectorAll('pre code').forEach((block) => {
const lang = block.getAttribute('class')?.replace('hljs', '').trim();
const supportedLanguages = ['javascript', 'html', 'plaintext']; // 定义支持高亮的语言
// 只对支持的语言进行高亮处理
if (supportedLanguages.includes(lang)) {
hljs.highlightElement(block);
}
// 跳过数学公式和图表代码块
if (['math', 'katex', 'latex', 'flow', 'seq'].includes(lang)) {
return;
}
// 添加行号
if (!block.classList.contains('nohighlight')) {
const lines = block.textContent.split('\n').length;
let lineNumbersHTML = '';
for (let i = 0; i < lines; i++) {
lineNumbersHTML += '<span></span>';
}
const lineNumbersWrapper = document.createElement('div');
lineNumbersWrapper.className = 'line-numbers';
lineNumbersWrapper.innerHTML = lineNumbersHTML;
block.className += ' line-numbers-rows';
block.parentNode.insertBefore(lineNumbersWrapper, block);
}
// 添加代码块标题
const codeTitle = document.createElement('div');
codeTitle.className = 'code-title';
codeTitle.textContent = lang || 'plaintext';
// 添加复制按钮
const copyButton = document.createElement('button');
copyButton.className = 'copy-btn';
copyButton.innerHTML = '<i class="fa fa-copy"></i>';
copyButton.addEventListener('click', () => {
const text = block.textContent;
navigator.clipboard.writeText(text).then(() => {
copyButton.innerHTML = '<i class="fa fa-check"></i>';
setTimeout(() => {
copyButton.innerHTML = '<i class="fa fa-copy"></i>';
}, 1000);
}).catch((err) => {
console.error('复制失败: ', err);
});
});
codeTitle.appendChild(copyButton);
// 添加演示按钮
if (lang === 'javascript' || lang === 'html') {
const demoButton = document.createElement('button');
demoButton.className = 'demo-btn';
demoButton.innerHTML = '<i class="fa fa-external-link"></i>';
demoButton.addEventListener('click', () => {
const code = block.textContent;
const newWindow = window.open('', '_blank');
if (lang === 'javascript') {
newWindow.document.write(`<html><body><script>${code}<\/script></body></html>`);
} else if (lang === 'html') {
newWindow.document.write(code);
}
});
codeTitle.appendChild(demoButton);
}
block.parentNode.insertBefore(codeTitle, block);
});
renderMathInElement(document.body);
}
// 3. 多语言核心功能
let currentLang = 'en'; // 默认语言
// 抽离:更新所有data-i18n元素的语言
function updateI18nElements(resources) {
// 1. 替换普通文本(data-i18n 属性)
document.querySelectorAll('[data-i18n]').forEach(el => {
const key = el.getAttribute('data-i18n');
if (resources[key]) el.textContent = resources[key];
});
// 2. 替换占位符(data-i18n-placeholder 属性)
document.querySelectorAll('[data-i18n-placeholder]').forEach(el => {
const key = el.getAttribute('data-i18n-placeholder');
if (resources[key]) el.placeholder = resources[key];
});
// 3. 替换标题属性(data-i18n-title 属性)
document.querySelectorAll('[data-i18n-title]').forEach(el => {
const key = el.getAttribute('data-i18n-title');
if (resources[key]) el.title = resources[key];
});
}
// 切换语言(调用抽离的updateI18nElements,支持动态元素)
function switchLanguage(lang) {
if (lang === currentLang) return;
currentLang = lang;
const resources = i18nResources[lang];
// 第一步:更新所有data-i18n元素(包括动态生成的)
updateI18nElements(resources);
// 第二步:更新语言切换按钮状态
document.getElementById('lang-en').className = `lang-switch-btn ${lang === 'en' ? 'active' : 'inactive'}`;
document.getElementById('lang-zh').className = `lang-switch-btn ${lang === 'zh' ? 'active' : 'inactive'}`;
document.getElementById('mobile-lang-en').className = `lang-switch-btn ${lang === 'en' ? 'active' : 'inactive'} text-xs`;
document.getElementById('mobile-lang-zh').className = `lang-switch-btn ${lang === 'zh' ? 'active' : 'inactive'} text-xs`;
// 第三步:更新HTML文档语言属性
document.documentElement.lang = lang;
// 第四步:重新渲染依赖语言的动态内容(渲染后会生成新的data-i18n元素)
renderVulnDescription();
renderAffectedProducts();
renderVulnDetailsCard(); // 动态生成漏洞详情的data-i18n元素
renderRelatedSves();
updateCvssSeverityStyle();
renderCvssChart();
// 关键:重新更新一次data-i18n元素(处理动态渲染新增的元素)
updateI18nElements(resources);
}
// 4. 动态渲染核心函数
// 4.1 基础信息渲染(无需语言依赖,初始化时渲染一次)
function renderBasicInfo() {
// 页面标题
document.getElementById('pageTitle').textContent = `${sveData.sveId} - ${sveData.vulnTitle} - SVE Database`;
// 面包屑导航
document.getElementById('breadCrumbSveId').textContent = sveData.sveId;
// SVE ID和标题
document.getElementById('sveIdDisplay').textContent = sveData.sveId;
document.getElementById('vulnTitle').textContent = sveData.vulnTitle;
// 日期
document.getElementById('publishDate').textContent = sveData.publishDate;
const lastModifiedDate = sveData.lastModified;
document.getElementById('lastModified').textContent = lastModifiedDate;
// CVSS基础信息(评分、向量)
document.getElementById('cvssScore').textContent = sveData.cvss.score;
document.getElementById('cvssSeverity').textContent = i18nResources[currentLang][sveData.cvss.severity.toLowerCase()] || sveData.cvss.severity;
document.getElementById('cvssVector').textContent = sveData.cvss.vector;
// 解决方案(Markdown格式)
const solutionsContainer = document.getElementById('solutionsContent');
solutionsContainer.innerHTML = marked.parse(sveData.solutions);
// 参考资料
renderReferences();
// 更新CVSS严重级别样式
updateCvssSeverityStyle();
}
// 4.2 漏洞描述渲染(依赖语言,多语言切换时需重新渲染)
function renderVulnDescription() {
const descContainer = document.getElementById('vulnDescription');
const html = marked.parse(sveData.vulnDescription);
descContainer.innerHTML = html;
}
// 4.3 受影响产品渲染(依赖语言,多语言切换时需重新渲染)
function renderAffectedProducts() {
const productsList = document.getElementById('affectedProductsList');
productsList.innerHTML = '';
sveData.products.forEach(product => {
const li = document.createElement('li');
li.className = 'flex items-start';
// 危险图标(有漏洞)
li.innerHTML = `
<i class="fa fa-check-circle text-danger mt-1 mr-2"></i>
<div>
<span class="font-medium text-dark">${product.name}</span> ${product.version}
<div class="text-xs text-neutral mt-1">
<span data-i18n="fixed_version">Fixed Version:</span>
<span class="font-medium">${product.fixedVersion || i18nResources[currentLang].no_fixed_version}</span>
</div>
</div>
`;
productsList.appendChild(li);
});
}
// 4.4 参考资料渲染(无需语言依赖,初始化时渲染一次)
function renderReferences() {
const referencesList = document.getElementById('referencesList');
referencesList.innerHTML = '';
sveData.references.forEach(ref => {
const li = document.createElement('li');
li.className = 'flex items-start';
// 参考资料类型映射(多语言)
const refTypeMap = {
'vendor': i18nResources[currentLang].vendor_advisory,
'tech_doc': i18nResources[currentLang].technical_documentation || 'Technical Documentation:',
'sve_guide': i18nResources[currentLang].sve_guide || 'SVE Guidance:',
'research': i18nResources[currentLang].research_paper || 'Research Paper:',
'other': i18nResources[currentLang].other || 'Other:'
};
const refTypeText = refTypeMap[ref.type] || ref.type + ':';
li.innerHTML = `
<span class="font-medium text-dark w-24">${refTypeText}</span>
<a href="${ref.url}" target="_blank" class="text-secondary hover:underline flex-1">
${ref.title || ref.url}
</a>
`;
referencesList.appendChild(li);
});
}
// 4.5 漏洞详情卡片渲染(依赖语言,多语言切换时需重新渲染)
function renderVulnDetailsCard() {
const detailsCard = document.getElementById('vulnDetailsCard');
detailsCard.innerHTML = '';
// CVSS指标映射(用于显示完整文本)
const cvssMappings = {
av: {
'N': 'av_network',
'A': 'av_adjacent',
'L': 'av_local',
'P': 'av_physical'
},
ac: {
'L': 'ac_low',
'H': 'ac_high'
},
pr: {
'N': 'pr_none',
'L': 'pr_low',
'H': 'pr_high'
},
ui: {
'N': 'ui_none',
'R': 'ui_required'
},
s: {
'U': 's_unchanged',
'C': 's_changed'
},
c: {
'N': 'c_none',
'L': 'c_low',
'H': 'c_high'
},
i: {
'N': 'i_none',
'L': 'i_low',
'H': 'i_high'
},
a: {
'N': 'a_none',
'L': 'a_low',
'H': 'a_high'
}
};
// 状态(原代码中span的文本写死,改为通过data-i18n动态获取)
const statusItem = document.createElement('div');
statusItem.innerHTML = `
<div class="text-neutral mb-1" data-i18n="status">Status</div>
<div class="flex items-center">
<span class="px-2 py-1 rounded-full bg-success/10 text-success text-xs font-medium" data-i18n="publicly_disclosed">
${i18nResources[currentLang].publicly_disclosed}
</span>
</div>
`;
detailsCard.appendChild(statusItem);
// 攻击向量
const avItem = document.createElement('div');
const avText = i18nResources[currentLang][cvssMappings.av[sveData.cvss.av]];
avItem.innerHTML = `
<div class="text-neutral mb-1" data-i18n="attack_vector">Attack Vector</div>
<div class="text-dark font-medium">${avText}</div>
`;
detailsCard.appendChild(avItem);
// 攻击复杂度
const acItem = document.createElement('div');
const acText = i18nResources[currentLang][cvssMappings.ac[sveData.cvss.ac]];
acItem.innerHTML = `
<div class="text-neutral mb-1" data-i18n="attack_complexity">Attack Complexity</div>
<div class="text-dark font-medium">${acText}</div>
`;
detailsCard.appendChild(acItem);
// 所需权限
const prItem = document.createElement('div');
const prText = i18nResources[currentLang][cvssMappings.pr[sveData.cvss.pr]];
prItem.innerHTML = `
<div class="text-neutral mb-1" data-i18n="privileges_required">Privileges Required</div>
<div class="text-dark font-medium">${prText}</div>
`;
detailsCard.appendChild(prItem);
// 用户交互
const uiItem = document.createElement('div');
const uiText = i18nResources[currentLang][cvssMappings.ui[sveData.cvss.ui]];
uiItem.innerHTML = `
<div class="text-neutral mb-1" data-i18n="user_interaction">User Interaction</div>
<div class="text-dark font-medium">${uiText}</div>
`;
detailsCard.appendChild(uiItem);
// 影响范围
const sItem = document.createElement('div');
const sText = i18nResources[currentLang][cvssMappings.s[sveData.cvss.s]];
sItem.innerHTML = `
<div class="text-neutral mb-1" data-i18n="scope">Scope</div>
<div class="text-dark font-medium">${sText}</div>
`;
detailsCard.appendChild(sItem);
// 机密性影响
const cItem = document.createElement('div');
const cText = i18nResources[currentLang][cvssMappings.c[sveData.cvss.c]];
cItem.innerHTML = `
<div class="text-neutral mb-1" data-i18n="confidentiality_impact">Confidentiality Impact</div>
<div class="text-dark font-medium">${cText}</div>
`;
detailsCard.appendChild(cItem);
// 完整性影响
const iItem = document.createElement('div');
const iText = i18nResources[currentLang][cvssMappings.i[sveData.cvss.i]];
iItem.innerHTML = `
<div class="text-neutral mb-1" data-i18n="integrity_impact">Integrity Impact</div>
<div class="text-dark font-medium">${iText}</div>
`;
detailsCard.appendChild(iItem);
// 可用性影响
const aItem = document.createElement('div');
const aText = i18nResources[currentLang][cvssMappings.a[sveData.cvss.a]];
aItem.innerHTML = `
<div class="text-neutral mb-1" data-i18n="availability_impact">Availability Impact</div>
<div class="text-dark font-medium">${aText}</div>
`;
detailsCard.appendChild(aItem);
}
// 4.6 相关SVE渲染(依赖语言,多语言切换时需重新渲染)
function renderRelatedSves() {
const relatedSvesList = document.getElementById('relatedSvesList');
const viewAllBtn = document.getElementById('viewAllRelatedBtn');
relatedSvesList.innerHTML = '';
if (sveData.relatedSves && sveData.relatedSves.length > 0) {
// 有相关SVE数据
sveData.relatedSves.forEach(relatedSve => {
const li = document.createElement('li');
li.innerHTML = `
<a href="#" class="flex items-center justify-between group">
<span class="text-secondary group-hover:underline">${relatedSve.sveId}</span>
<span class="text-xs px-2 py-0.5 rounded-full ${getSeverityClass(relatedSve.severity)}" data-i18n="${relatedSve.severity.toLowerCase()}">
${i18nResources[currentLang][relatedSve.severity.toLowerCase()] || relatedSve.severity}
</span>
</a>
`;
relatedSvesList.appendChild(li);
});
// 显示"查看全部"按钮(如果数据超过4条)
if (sveData.relatedSves.length > 4) {
viewAllBtn.classList.remove('hidden');
} else {
viewAllBtn.classList.add('hidden');
}
} else {
// 无相关SVE数据
const noDataDiv = document.createElement('div');
noDataDiv.className = 'text-neutral text-sm italic';
noDataDiv.setAttribute('data-i18n', 'no_related_sves');
noDataDiv.textContent = i18nResources[currentLang].no_related_sves;
relatedSvesList.appendChild(noDataDiv);
// 隐藏"查看全部"按钮
viewAllBtn.classList.add('hidden');
}
}
// 1. 全局存储雷达图实例(用于切换语言时销毁旧图表)
let cvssChartInstance = null;
// 2. 抽离:根据当前语言动态生成雷达图配置
function getCvssChartConfig() {
// CVSS指标值映射(用于图表数据,与原逻辑一致)
const cvssValueMap = {
'N': 2, 'A': 1, 'L': 0.5, 'P': 0, // AV
'L': 0, 'H': 1, // AC
'N': 0, 'L': 1, 'H': 2, // PR
'N': 0, 'R': 1, // UI
'U': 0, 'C': 1, // S
'N': 0, 'L': 1, 'H': 2, // C/I/A
};
// 3. 动态获取当前语言的雷达图轴标签(核心:多语言适配)
const cvssLabels = [
i18nResources[currentLang].attack_vector, // 攻击向量
i18nResources[currentLang].attack_complexity, // 攻击复杂度
i18nResources[currentLang].privileges_required,// 所需权限
i18nResources[currentLang].user_interaction, // 用户交互
i18nResources[currentLang].scope, // 影响范围
i18nResources[currentLang].confidentiality_impact, // 机密性影响
i18nResources[currentLang].integrity_impact, // 完整性影响
i18nResources[currentLang].availability_impact // 可用性影响
];
// 4. 动态获取当前语言的提示框(Tooltip)文本(核心:多语言适配)
const cvssMappingsI18n = {
av: {
'N': i18nResources[currentLang].av_network,
'A': i18nResources[currentLang].av_adjacent,
'L': i18nResources[currentLang].av_local,
'P': i18nResources[currentLang].av_physical
},
ac: {
'L': i18nResources[currentLang].ac_low,
'H': i18nResources[currentLang].ac_high
},
pr: {
'N': i18nResources[currentLang].pr_none,
'L': i18nResources[currentLang].pr_low,
'H': i18nResources[currentLang].pr_high
},
ui: {
'N': i18nResources[currentLang].ui_none,
'R': i18nResources[currentLang].ui_required
},
s: {
'U': i18nResources[currentLang].s_unchanged,
'C': i18nResources[currentLang].s_changed
},
c: {
'N': i18nResources[currentLang].c_none,
'L': i18nResources[currentLang].c_low,
'H': i18nResources[currentLang].c_high
},
i: {
'N': i18nResources[currentLang].i_none,
'L': i18nResources[currentLang].i_low,
'H': i18nResources[currentLang].i_high
},
a: {
'N': i18nResources[currentLang].a_none,
'L': i18nResources[currentLang].a_low,
'H': i18nResources[currentLang].a_high
}
};
// 提取CVSS指标数据(与原逻辑一致)
const cvssValues = [
cvssValueMap[sveData.cvss.av], // AV
cvssValueMap[sveData.cvss.ac], // AC
cvssValueMap[sveData.cvss.pr], // PR
cvssValueMap[sveData.cvss.ui], // UI
cvssValueMap[sveData.cvss.s], // S
cvssValueMap[sveData.cvss.c], // C
cvssValueMap[sveData.cvss.i], // I
cvssValueMap[sveData.cvss.a] // A
];
// 返回适配当前语言的图表配置
return {
type: 'radar',
data: {
labels: cvssLabels, // 动态语言标签
datasets: [{
label: 'CVSS v3.1',
data: cvssValues,
backgroundColor: getSeverityColor(sveData.cvss.severity, 0.2),
borderColor: getSeverityColor(sveData.cvss.severity, 1),
pointBackgroundColor: getSeverityColor(sveData.cvss.severity, 1),
pointBorderColor: '#fff',
pointHoverBackgroundColor: '#fff',
pointHoverBorderColor: getSeverityColor(sveData.cvss.severity, 1)
}]
},
options: {
scales: {
r: {
angleLines: { display: true },
suggestedMin: 0,
suggestedMax: 2,
ticks: { stepSize: 0.5 }
}
},
plugins: {
legend: { display: false },
tooltip: {
callbacks: {
label: function(context) {
// 动态语言提示框文本(使用当前语言的映射)
const index = context.dataIndex;
const metricKey = ['av', 'ac', 'pr', 'ui', 's', 'c', 'i', 'a'][index];
const metricValue = sveData.cvss[metricKey];
const metricText = cvssMappingsI18n[metricKey][metricValue];
return `${cvssLabels[index]}: ${metricText}`; // 标签+值均为当前语言
}
}
}
}
}
};
}
// 重构:雷达图渲染函数(支持销毁旧图表 + 用当前语言重建)
function renderCvssChart() {
const ctx = document.getElementById('cvssChart').getContext('2d');
// 1. 先销毁旧图表(避免canvas叠加)
if (cvssChartInstance) {
cvssChartInstance.destroy();
}
// 2. 用当前语言的配置创建新图表
cvssChartInstance = new Chart(ctx, getCvssChartConfig());
}
// 5. 辅助工具函数
// 5.1 根据严重级别获取颜色
function getSeverityColor(severity, alpha = 1) {
const severityLower = severity.toLowerCase();
switch (severityLower) {
case 'critical':
return `rgba(217, 48, 37, ${alpha})`; // 红色
case 'high':
return `rgba(217, 48, 37, ${alpha})`; // 红色
case 'medium':
return `rgba(244, 180, 0, ${alpha})`; // 黄色
case 'low':
return `rgba(30, 142, 62, ${alpha})`; // 绿色
default:
return `rgba(95, 99, 104, ${alpha})`; // 灰色
}
}
// 5.2 根据严重级别获取样式类
function getSeverityClass(severity) {
const severityLower = severity.toLowerCase();
switch (severityLower) {
case 'critical':
return 'bg-danger text-white';
case 'high':
return 'bg-danger text-white';
case 'medium':
return 'bg-warning text-white';
case 'low':
return 'bg-success text-white';
default:
return 'bg-neutral text-white';
}
}
// 5.3 更新CVSS严重级别样式
function updateCvssSeverityStyle() {
const cvssScoreEl = document.getElementById('cvssScore');
const cvssSeverityEl = document.getElementById('cvssSeverity');
const severity = sveData.cvss.severity.toLowerCase();
// 设置颜色
cvssScoreEl.className = `text-3xl font-bold ${getSeverityClass(severity).replace('/10', '')}`;
cvssSeverityEl.className = `${getSeverityClass(severity)}`;
}
// 6. 原有功能保留
// 6.1 复制SVE ID
function initCopyBtn() {
document.getElementById('copyBtn').addEventListener('click', function() {
const sveId = sveData.sveId;
navigator.clipboard.writeText(sveId).then(() => {
const message = document.getElementById('copyMessage');
message.classList.remove('hidden');
setTimeout(() => {
message.classList.add('hidden');
}, 2000);
});
});
}
// 6.2 移动端导航菜单
function initMobileMenu() {
document.getElementById('mobile-menu-btn').addEventListener('click', () => {
const mobileMenu = document.getElementById('mobile-menu');
const mobileLangSwitch = document.getElementById('mobile-lang-switch');
mobileMenu.classList.toggle('hidden');
mobileLangSwitch.classList.toggle('hidden');
});
}
// 6.3 导航栏滚动效果
function initScrollEffect() {
window.addEventListener('scroll', () => {
const header = document.querySelector('header');
if (window.scrollY > 10) {
header.classList.add('shadow');
header.classList.remove('shadow-sm');
} else {
header.classList.remove('shadow');
header.classList.add('shadow-sm');
}
});
}
// 6.4 绑定语言切换事件
function initLanguageSwitch() {
document.getElementById('lang-en').addEventListener('click', () => switchLanguage('en'));
document.getElementById('lang-zh').addEventListener('click', () => switchLanguage('zh'));
document.getElementById('mobile-lang-en').addEventListener('click', () => switchLanguage('en'));
document.getElementById('mobile-lang-zh').addEventListener('click', () => switchLanguage('zh'));
}
// 7. 初始化所有功能
document.addEventListener('DOMContentLoaded', () => {
// 初始化Marked.js(用于Markdown渲染)
marked.setOptions({
breaks: true,
highlight: (code, lang) => {
if (lang && Prism.languages[lang]) {
return Prism.highlight(code, Prism.languages[lang], lang);
}
return Prism.highlight(code, Prism.languages.markup, 'markup');
}
});
SVEID = window.location.pathname.split("/")[2];
fetch(`/API/GetInformation.api?SVEID=${SVEID}`)
.then(response => response.json())
.then(data => {
if (!data.code) {
sveData = data;
renderBasicInfo();
renderVulnDescription();
renderAffectedProducts();
renderVulnDetailsCard();
renderRelatedSves();
renderCvssChart();
renderPageMSC();
} else {
alert("获取SVE详情失败");
}
})
.catch(error => {
console.error("网络错误:", error);
alert("网络原因获取SVE详情失败,或渲染失败");
});
// 初始化原有功能
initCopyBtn();
initMobileMenu();
initScrollEffect();
initLanguageSwitch();
});