Files
SMANAGE/TempLate/Org/assets/js/write.js
T
2026-06-16 11:55:14 +08:00

673 lines
27 KiB
JavaScript

// 2. 多语言核心功能
let currentLang = 'en'; // 默认语言
// 切换语言
function switchLanguage(lang) {
if (lang === currentLang) return;
currentLang = lang;
const resources = i18nResources[lang];
// 替换普通文本(data-i18n 属性)
document.querySelectorAll('[data-i18n]').forEach(el => {
const key = el.getAttribute('data-i18n');
if (resources[key]) el.textContent = resources[key];
});
// 替换占位符(data-i18n-placeholder 属性)
document.querySelectorAll('[data-i18n-placeholder]').forEach(el => {
const key = el.getAttribute('data-i18n-placeholder');
if (resources[key]) el.placeholder = resources[key];
});
// 替换标题属性(data-i18n-title 属性)
document.querySelectorAll('[data-i18n-title]').forEach(el => {
const key = el.getAttribute('data-i18n-title');
if (resources[key]) el.title = resources[key];
});
// 替换下拉选项文本(data-i18n 属性)
document.querySelectorAll('option[data-i18n]').forEach(el => {
const key = el.getAttribute('data-i18n');
if (resources[key]) el.textContent = resources[key];
});
// 更新语言切换按钮状态(桌面端)
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;
// 重新渲染Markdown预览(语言切换后保持预览状态)
updateAllMarkdownPreviews();
}
// 3. Markdown编辑器功能
function initMarkdownEditors() {
// 为所有编辑器标签绑定切换事件
document.querySelectorAll('.editor-tab').forEach(tab => {
tab.addEventListener('click', function() {
const editor = this.closest('.markdown-editor');
const tabType = this.getAttribute('data-tab');
const textarea = editor.querySelector('textarea');
const preview = editor.querySelector('.preview-content');
const tabs = editor.querySelectorAll('.editor-tab');
// 更新标签状态
tabs.forEach(t => {
t.classList.remove('active', 'text-neutral');
t.classList.add(t.getAttribute('data-tab') === tabType ? 'active' : 'text-neutral');
});
// 切换编辑/预览模式
if (tabType === 'edit') {
textarea.classList.remove('hidden');
preview.classList.add('hidden');
} else {
textarea.classList.add('hidden');
preview.classList.remove('hidden');
// 渲染Markdown
preview.innerHTML = marked.parse(textarea.value);
// 代码高亮
Prism.highlightAllUnder(preview);
}
});
});
// 为所有Markdown文本域绑定输入事件(实时预览)
document.querySelectorAll('.editor-content').forEach(textarea => {
textarea.addEventListener('input', function() {
const editor = this.closest('.markdown-editor');
const preview = editor.querySelector('.preview-content');
const activeTab = editor.querySelector('.editor-tab.active');
// 如果当前是预览模式,实时更新预览
if (activeTab.getAttribute('data-tab') === 'preview') {
preview.innerHTML = marked.parse(this.value);
Prism.highlightAllUnder(preview);
}
});
});
}
// 更新所有Markdown预览
function updateAllMarkdownPreviews() {
document.querySelectorAll('.markdown-editor').forEach(editor => {
const textarea = editor.querySelector('textarea');
const preview = editor.querySelector('.preview-content');
const activeTab = editor.querySelector('.editor-tab.active');
if (activeTab.getAttribute('data-tab') === 'preview') {
preview.innerHTML = marked.parse(textarea.value);
Prism.highlightAllUnder(preview);
}
});
}
// 4. CVSS评分计算功能
function initCvssCalculator() {
// CVSS v3.1 评分权重映射
const cvssWeights = {
AV: { N: 0.85, A: 0.62, L: 0.55, P: 0.2 },
AC: { L: 0.77, H: 0.44 },
PR: { N: 0.85, L: 0.62, H: 0.27 },
UI: { N: 0.85, R: 0.62 },
S: { U: 6.42, C: 7.52 },
C: { N: 0, L: 0.22, H: 0.56 },
I: { N: 0, L: 0.22, H: 0.56 },
A: { N: 0, L: 0.22, H: 0.56 }
};
// 计算CVSS基础评分
function calculateCvssScore() {
// 获取所有CVSS指标值
const AV = document.querySelector('select[name="cvssAV"]').value;
const AC = document.querySelector('select[name="cvssAC"]').value;
const PR = document.querySelector('select[name="cvssPR"]').value;
const UI = document.querySelector('select[name="cvssUI"]').value;
const S = document.querySelector('select[name="cvssS"]').value;
const C = document.querySelector('select[name="cvssC"]').value;
const I = document.querySelector('select[name="cvssI"]').value;
const A = document.querySelector('select[name="cvssA"]').value;
// 计算利用向量评分 (Exploitability Score)
const exploitabilityScore = 8.22 * cvssWeights.AV[AV] * cvssWeights.AC[AC] * cvssWeights.PR[PR] * cvssWeights.UI[UI];
// 计算影响评分 (Impact Score)
const impactSubScore = 1 - ((1 - cvssWeights.C[C]) * (1 - cvssWeights.I[I]) * (1 - cvssWeights.A[A]));
const impactScore = cvssWeights.S[S] * impactSubScore;
// 计算基础评分 (Base Score)
let baseScore;
if (impactScore <= 0) {
baseScore = 0;
} else if (S === 'U') {
baseScore = Math.min(impactScore + exploitabilityScore, 10);
} else {
baseScore = Math.min(1.08 * (impactScore + exploitabilityScore), 10);
}
// 四舍五入到一位小数
baseScore = Math.round(baseScore * 10) / 10;
// 确定严重级别
let severity, severityColor;
if (baseScore >= 9.0) {
severity = 'CRITICAL';
severityColor = 'bg-danger/10 text-danger';
} else if (baseScore >= 7.0) {
severity = 'HIGH';
severityColor = 'bg-danger/10 text-danger';
} else if (baseScore >= 4.0) {
severity = 'MEDIUM';
severityColor = 'bg-warning/10 text-warning';
} else if (baseScore >= 0.1) {
severity = 'LOW';
severityColor = 'bg-success/10 text-success';
} else {
severity = 'NONE';
severityColor = 'bg-neutral/10 text-neutral';
}
// 更新UI显示
document.getElementById('cvssScore').textContent = baseScore;
document.getElementById('cvssSeverity').textContent = severity;
document.getElementById('cvssSeverity').className = `px-2 py-1 rounded-full ${severityColor} text-xs font-medium`;
// 更新CVSS向量字符串
const vectorString = `CVSS:3.1/AV:${AV}/AC:${AC}/PR:${PR}/UI:${UI}/S:${S}/C:${C}/I:${I}/A:${A}`;
document.getElementById('cvssVector').textContent = vectorString;
return {
score: baseScore,
severity: severity,
vector: vectorString
};
}
// 初始化时计算一次
calculateCvssScore();
// 绑定指标变更事件
document.querySelectorAll('.cvss-metric').forEach(select => {
select.addEventListener('change', calculateCvssScore);
});
// 暴露计算函数供其他功能使用
window.calculateCvssScore = calculateCvssScore;
}
// 5. 动态表单功能(添加/删除条目)
function initDynamicForms() {
// 产品条目:添加
document.getElementById('addProductBtn').addEventListener('click', function() {
const productsList = document.getElementById('productsList');
const productItem = productsList.querySelector('.product-item').cloneNode(true);
// 清空输入值
productItem.querySelectorAll('input').forEach(input => {
input.value = '';
});
// 添加删除事件
productItem.querySelector('.removeProductBtn').addEventListener('click', function() {
if (productsList.querySelectorAll('.product-item').length > 1) {
this.closest('.product-item').remove();
} else {
// 至少保留一个条目,清空值
productItem.querySelectorAll('input').forEach(input => {
input.value = '';
});
}
});
productsList.appendChild(productItem);
});
// 产品条目:删除
document.addEventListener('click', function(e) {
if (e.target.closest('.removeProductBtn')) {
const productItem = e.target.closest('.product-item');
const productsList = document.getElementById('productsList');
if (productsList.querySelectorAll('.product-item').length > 1) {
productItem.remove();
} else {
// 至少保留一个条目,清空值
productItem.querySelectorAll('input').forEach(input => {
input.value = '';
});
}
}
});
// 参考资料:添加
document.getElementById('addReferenceBtn').addEventListener('click', function() {
const referencesList = document.getElementById('referencesList');
const referenceItem = referencesList.querySelector('.reference-item').cloneNode(true);
// 清空输入值
referenceItem.querySelectorAll('input').forEach(input => {
input.value = '';
});
// 添加删除事件
referenceItem.querySelector('.removeReferenceBtn').addEventListener('click', function() {
if (referencesList.querySelectorAll('.reference-item').length > 1) {
this.closest('.reference-item').remove();
} else {
// 至少保留一个条目,清空值
referenceItem.querySelectorAll('input').forEach(input => {
input.value = '';
});
}
});
referencesList.appendChild(referenceItem);
});
// 参考资料:删除
document.addEventListener('click', function(e) {
if (e.target.closest('.removeReferenceBtn')) {
const referenceItem = e.target.closest('.reference-item');
const referencesList = document.getElementById('referencesList');
if (referencesList.querySelectorAll('.reference-item').length > 1) {
referenceItem.remove();
} else {
// 至少保留一个条目,清空值
referenceItem.querySelectorAll('input').forEach(input => {
input.value = '';
});
}
}
});
// 相关SVE:添加
document.getElementById('addRelatedSveBtn').addEventListener('click', function() {
const relatedSveList = document.getElementById('relatedSveList');
const relatedSveItem = document.createElement('div');
relatedSveItem.className = 'related-sve-item flex items-center gap-3 p-3 border border-gray-200 rounded-lg';
relatedSveItem.innerHTML = `
<input type="text" name="relatedSveId[]" placeholder="SVE-YYYY-XXXXXX" class="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-secondary/50 focus:border-secondary" data-i18n-placeholder="related_sve_id_placeholder">
<select name="relatedSveRelation[]" class="w-40 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-secondary/50 focus:border-secondary">
<option value="same_product" data-i18n="related_same_product">Same Product</option>
<option value="similar_vuln" data-i18n="related_similar_vuln">Similar Vulnerability</option>
<option value="same_vendor" data-i18n="related_same_vendor">Same Vendor</option>
<option value="other" data-i18n="related_other">Other Relationship</option>
</select>
<button type="button" class="removeRelatedSveBtn text-danger hover:text-danger/80 p-2" data-i18n-title="remove_related_sve">
<i class="fa fa-times"></i>
</button>
`;
// 添加删除事件
relatedSveItem.querySelector('.removeRelatedSveBtn').addEventListener('click', function() {
relatedSveItem.remove();
});
relatedSveList.appendChild(relatedSveItem);
});
// 相关SVE:删除
document.addEventListener('click', function(e) {
if (e.target.closest('.removeRelatedSveBtn')) {
const relatedSveItem = e.target.closest('.related-sve-item');
// 可以删除所有相关SVE条目,没有数量限制
relatedSveItem.remove();
}
});
}
// 6. JSON导入导出功能
function initJsonImportExport() {
// 导出JSON
document.getElementById('exportJson').addEventListener('click', function() {
// 收集表单数据
const sveData = collectFormData();
// 转换为JSON字符串
const jsonString = JSON.stringify(sveData, null, 2);
// 创建下载链接
const blob = new Blob([jsonString], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${sveData.sveId || 'new-sve'}.json`;
document.body.appendChild(a);
a.click();
// 清理
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(url);
}, 0);
});
// 导入JSON
document.getElementById('importJson').addEventListener('change', function(e) {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(event) {
try {
const sveData = JSON.parse(event.target.result);
populateFormData(sveData);
// 重置文件输入,允许重复选择同一文件
e.target.value = '';
} catch (error) {
alert('Failed to import JSON: Invalid format');
console.error('JSON import error:', error);
}
};
reader.readAsText(file);
});
}
// 收集表单数据
function collectFormData() {
// 基础信息
const sveYear = document.getElementById('sveYear').value;
const sveNumber = document.getElementById('sveNumber').value;
const sveId = `SVE-${sveYear}-${sveNumber}`;
// CVSS评分
const cvss = window.calculateCvssScore();
// 受影响产品
const products = [];
document.querySelectorAll('.product-item').forEach(item => {
products.push({
name: item.querySelector('input[name="productName[]"]').value,
version: item.querySelector('input[name="productVersion[]"]').value,
fixedVersion: item.querySelector('input[name="productFixedVersion[]"]').value
});
});
// 参考资料
const references = [];
document.querySelectorAll('.reference-item').forEach(item => {
references.push({
type: item.querySelector('select[name="referenceType[]"]').value,
title: item.querySelector('input[name="referenceTitle[]"]').value,
url: item.querySelector('input[name="referenceUrl[]"]').value
});
});
// 相关SVE
const relatedSves = [];
document.querySelectorAll('.related-sve-item').forEach(item => {
const sveId = item.querySelector('input[name="relatedSveId[]"]').value;
if (sveId) { // 只收集有值的相关SVE
relatedSves.push({
sveId: sveId,
relation: item.querySelector('select[name="relatedSveRelation[]"]').value
});
}
});
return {
sveId: sveId,
sveYear: sveYear,
sveNumber: sveNumber,
publishDate: document.getElementById('publishDate').value,
vulnTitle: document.getElementById('vulnTitle').value,
vulnStatus: document.getElementById('vulnStatus').value,
vulnDescription: document.getElementById('vulnDescription').value,
products: products,
cvss: {
av: document.querySelector('select[name="cvssAV"]').value,
ac: document.querySelector('select[name="cvssAC"]').value,
pr: document.querySelector('select[name="cvssPR"]').value,
ui: document.querySelector('select[name="cvssUI"]').value,
s: document.querySelector('select[name="cvssS"]').value,
c: document.querySelector('select[name="cvssC"]').value,
i: document.querySelector('select[name="cvssI"]').value,
a: document.querySelector('select[name="cvssA"]').value,
score: cvss.score,
severity: cvss.severity,
vector: cvss.vector
},
solutions: document.getElementById('solutions').value,
references: references,
relatedSves: relatedSves,
createdAt: new Date().toISOString()
};
}
// 填充表单数据
function populateFormData(sveData) {
if (!sveData) return;
// 基础信息
if (sveData.sveYear) document.getElementById('sveYear').value = sveData.sveYear;
if (sveData.sveNumber) document.getElementById('sveNumber').value = sveData.sveNumber;
if (sveData.publishDate) document.getElementById('publishDate').value = sveData.publishDate;
if (sveData.vulnTitle) document.getElementById('vulnTitle').value = sveData.vulnTitle;
if (sveData.vulnStatus) document.getElementById('vulnStatus').value = sveData.vulnStatus;
// 漏洞描述
if (sveData.vulnDescription) {
document.getElementById('vulnDescription').value = sveData.vulnDescription;
// 更新预览
const preview = document.getElementById('vulnDescriptionPreview');
if (!preview.classList.contains('hidden')) {
preview.innerHTML = marked.parse(sveData.vulnDescription);
Prism.highlightAllUnder(preview);
}
}
// 受影响产品
const productsList = document.getElementById('productsList');
// 清空现有条目(保留一个)
const productItems = productsList.querySelectorAll('.product-item');
for (let i = 1; i < productItems.length; i++) {
productItems[i].remove();
}
// 填充数据
if (sveData.products && sveData.products.length > 0) {
sveData.products.forEach((product, index) => {
let productItem;
if (index === 0) {
productItem = productsList.querySelector('.product-item');
} else {
// 添加新条目
document.getElementById('addProductBtn').click();
productItem = productsList.querySelectorAll('.product-item')[index];
}
productItem.querySelector('input[name="productName[]"]').value = product.name || '';
productItem.querySelector('input[name="productVersion[]"]').value = product.version || '';
productItem.querySelector('input[name="productFixedVersion[]"]').value = product.fixedVersion || '';
});
}
// CVSS指标
if (sveData.cvss) {
if (sveData.cvss.av) document.querySelector('select[name="cvssAV"]').value = sveData.cvss.av;
if (sveData.cvss.ac) document.querySelector('select[name="cvssAC"]').value = sveData.cvss.ac;
if (sveData.cvss.pr) document.querySelector('select[name="cvssPR"]').value = sveData.cvss.pr;
if (sveData.cvss.ui) document.querySelector('select[name="cvssUI"]').value = sveData.cvss.ui;
if (sveData.cvss.s) document.querySelector('select[name="cvssS"]').value = sveData.cvss.s;
if (sveData.cvss.c) document.querySelector('select[name="cvssC"]').value = sveData.cvss.c;
if (sveData.cvss.i) document.querySelector('select[name="cvssI"]').value = sveData.cvss.i;
if (sveData.cvss.a) document.querySelector('select[name="cvssA"]').value = sveData.cvss.a;
// 重新计算评分
window.calculateCvssScore();
}
// 解决方案
if (sveData.solutions) {
document.getElementById('solutions').value = sveData.solutions;
// 更新预览
const preview = document.getElementById('solutionsPreview');
if (!preview.classList.contains('hidden')) {
preview.innerHTML = marked.parse(sveData.solutions);
Prism.highlightAllUnder(preview);
}
}
// 参考资料
const referencesList = document.getElementById('referencesList');
// 清空现有条目(保留一个)
const referenceItems = referencesList.querySelectorAll('.reference-item');
for (let i = 1; i < referenceItems.length; i++) {
referenceItems[i].remove();
}
// 填充数据
if (sveData.references && sveData.references.length > 0) {
sveData.references.forEach((reference, index) => {
let referenceItem;
if (index === 0) {
referenceItem = referencesList.querySelector('.reference-item');
} else {
// 添加新条目
document.getElementById('addReferenceBtn').click();
referenceItem = referencesList.querySelectorAll('.reference-item')[index];
}
if (reference.type) referenceItem.querySelector('select[name="referenceType[]"]').value = reference.type;
referenceItem.querySelector('input[name="referenceTitle[]"]').value = reference.title || '';
referenceItem.querySelector('input[name="referenceUrl[]"]').value = reference.url || '';
});
}
// 相关SVE
const relatedSveList = document.getElementById('relatedSveList');
// 清空现有条目
relatedSveList.innerHTML = '';
// 填充数据
if (sveData.relatedSves && sveData.relatedSves.length > 0) {
sveData.relatedSves.forEach((relatedSve, index) => {
// 添加新条目
document.getElementById('addRelatedSveBtn').click();
const relatedSveItem = relatedSveList.querySelectorAll('.related-sve-item')[index];
relatedSveItem.querySelector('input[name="relatedSveId[]"]').value = relatedSve.sveId || '';
if (relatedSve.relation) relatedSveItem.querySelector('select[name="relatedSveRelation[]"]').value = relatedSve.relation;
});
} else {
// 至少保留一个空条目
document.getElementById('addRelatedSveBtn').click();
}
}
// 7. 表单提交处理
function initFormSubmit() {
document.getElementById('sveForm').addEventListener('submit', function(e) {
e.preventDefault();
// 收集表单数据
const sveData = collectFormData();
console.log('SVE Data to submit:', sveData);
fetch('/API/Write.api', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(sveData),
}).then(response => response.json()).then(data => {
if (!data.code) {
alert(data.msg);
} else {
alert(data.error);
}
console.log('Success:', data);
}).catch((error) => {
alert(data.error);
console.error('Error:', error);
});
// 重置表单(可选)
// this.reset();
});
// 保存草稿
document.querySelector('button[data-i18n="save_draft"]').addEventListener('click', function() {
// 收集表单数据
const sveData = collectFormData();
// 在实际应用中,这里会保存草稿到服务器或本地存储
console.log('SVE Draft saved:', sveData);
// 显示成功消息
alert('Draft saved successfully!');
});
}
// 8. 移动端菜单切换
function initMobileMenu() {
document.getElementById('mobile-menu-btn').addEventListener('click', function() {
const mobileMenu = document.getElementById('mobile-menu');
const mobileLangSwitch = document.getElementById('mobile-lang-switch');
// 切换菜单显示/隐藏,并同步显示语言切换按钮
mobileMenu.classList.toggle('hidden');
mobileLangSwitch.classList.toggle('hidden');
});
}
// 9. 导航栏滚动效果
function initScrollEffects() {
window.addEventListener('scroll', function() {
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');
}
});
}
// 10. 初始化所有功能
document.addEventListener('DOMContentLoaded', function() {
// 设置当前年份为默认年份
const currentYear = new Date().getFullYear().toString();
document.getElementById('sveYear').value = currentYear;
// 绑定语言切换事件(桌面端)
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'));
// 初始化其他功能
initMarkdownEditors();
initCvssCalculator();
initDynamicForms();
initJsonImportExport();
initFormSubmit();
initMobileMenu();
initScrollEffects();
SVEID = window.location.search.split("SVEID=")[1];
if (SVEID) {
fetch(`/API/GetInformation.api?SVEID=${SVEID}`)
.then(response => response.json())
.then(data => {
if (!data.code) {
populateFormData(data);
} else {
alert("获取SVE详情失败");
}
})
.catch(error => {
console.error("网络错误:", error);
alert("网络原因获取SVE详情失败");
});
}
});