|
|
|
|
@ -9,7 +9,7 @@ let currentProjectLastResults = null;
|
|
|
|
|
// API 基础URL - 自动适配当前域名
|
|
|
|
|
const API_BASE_URL = window.location.origin + '/api';
|
|
|
|
|
|
|
|
|
|
// DOM 元素
|
|
|
|
|
// DOM 元素(快捷检查相关元素已移除,保留变量定义以避免错误)
|
|
|
|
|
const uploadArea = document.getElementById('uploadArea');
|
|
|
|
|
const fileInput = document.getElementById('fileInput');
|
|
|
|
|
const progressContainer = document.getElementById('progressContainer');
|
|
|
|
|
@ -61,26 +61,28 @@ function showPage(pageId) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 文件上传处理
|
|
|
|
|
uploadArea.addEventListener('click', () => fileInput.click());
|
|
|
|
|
uploadArea.addEventListener('dragover', handleDragOver);
|
|
|
|
|
uploadArea.addEventListener('dragleave', handleDragLeave);
|
|
|
|
|
uploadArea.addEventListener('drop', handleDrop);
|
|
|
|
|
fileInput.addEventListener('change', handleFileSelect);
|
|
|
|
|
// 文件上传处理(快捷检查功能已移除,保留代码以防将来需要)
|
|
|
|
|
if (uploadArea && fileInput) {
|
|
|
|
|
uploadArea.addEventListener('click', () => fileInput.click());
|
|
|
|
|
uploadArea.addEventListener('dragover', handleDragOver);
|
|
|
|
|
uploadArea.addEventListener('dragleave', handleDragLeave);
|
|
|
|
|
uploadArea.addEventListener('drop', handleDrop);
|
|
|
|
|
fileInput.addEventListener('change', handleFileSelect);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleDragOver(e) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
uploadArea.classList.add('dragover');
|
|
|
|
|
if (uploadArea) uploadArea.classList.add('dragover');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleDragLeave(e) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
uploadArea.classList.remove('dragover');
|
|
|
|
|
if (uploadArea) uploadArea.classList.remove('dragover');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleDrop(e) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
uploadArea.classList.remove('dragover');
|
|
|
|
|
if (uploadArea) uploadArea.classList.remove('dragover');
|
|
|
|
|
const files = Array.from(e.dataTransfer.files);
|
|
|
|
|
processFiles(files);
|
|
|
|
|
}
|
|
|
|
|
@ -104,10 +106,11 @@ function processFiles(files) {
|
|
|
|
|
|
|
|
|
|
uploadedFiles = validFiles;
|
|
|
|
|
updateUploadArea();
|
|
|
|
|
startCheckBtn.style.display = 'inline-block';
|
|
|
|
|
if (startCheckBtn) startCheckBtn.style.display = 'inline-block';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function updateUploadArea() {
|
|
|
|
|
if (!uploadArea) return;
|
|
|
|
|
if (uploadedFiles.length > 0) {
|
|
|
|
|
uploadArea.innerHTML = `
|
|
|
|
|
<div class="upload-icon">
|
|
|
|
|
@ -119,9 +122,9 @@ function updateUploadArea() {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 开始检查
|
|
|
|
|
startCheckBtn.addEventListener('click', startCheck);
|
|
|
|
|
stopCheckBtn.addEventListener('click', stopCheck);
|
|
|
|
|
// 开始检查(快捷检查功能已移除)
|
|
|
|
|
if (startCheckBtn) startCheckBtn.addEventListener('click', startCheck);
|
|
|
|
|
if (stopCheckBtn) stopCheckBtn.addEventListener('click', stopCheck);
|
|
|
|
|
|
|
|
|
|
async function startCheck() {
|
|
|
|
|
if (uploadedFiles.length === 0) {
|
|
|
|
|
@ -130,10 +133,10 @@ async function startCheck() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
isChecking = true;
|
|
|
|
|
startCheckBtn.style.display = 'none';
|
|
|
|
|
stopCheckBtn.style.display = 'inline-block';
|
|
|
|
|
progressContainer.style.display = 'block';
|
|
|
|
|
resultsSection.style.display = 'none';
|
|
|
|
|
if (startCheckBtn) startCheckBtn.style.display = 'none';
|
|
|
|
|
if (stopCheckBtn) stopCheckBtn.style.display = 'inline-block';
|
|
|
|
|
if (progressContainer) progressContainer.style.display = 'block';
|
|
|
|
|
if (resultsSection) resultsSection.style.display = 'none';
|
|
|
|
|
|
|
|
|
|
// 更新 Agent 状态
|
|
|
|
|
updateAgentStatus('working');
|
|
|
|
|
@ -172,28 +175,32 @@ async function startCheck() {
|
|
|
|
|
|
|
|
|
|
function stopCheck() {
|
|
|
|
|
isChecking = false;
|
|
|
|
|
startCheckBtn.style.display = 'inline-block';
|
|
|
|
|
stopCheckBtn.style.display = 'none';
|
|
|
|
|
progressContainer.style.display = 'none';
|
|
|
|
|
|
|
|
|
|
if (startCheckBtn) startCheckBtn.style.display = 'inline-block';
|
|
|
|
|
if (stopCheckBtn) stopCheckBtn.style.display = 'none';
|
|
|
|
|
if (progressContainer) progressContainer.style.display = 'none';
|
|
|
|
|
|
|
|
|
|
// 更新 Agent 状态
|
|
|
|
|
updateAgentStatus('idle');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function updateAgentStatus(status) {
|
|
|
|
|
// Agent状态面板已移除,此函数保留以防将来需要
|
|
|
|
|
const agentItems = document.querySelectorAll('.agent-item');
|
|
|
|
|
if (agentItems.length === 0) return;
|
|
|
|
|
agentItems.forEach(item => {
|
|
|
|
|
const badge = item.querySelector('.agent-status-badge');
|
|
|
|
|
badge.className = `agent-status-badge status-${status}`;
|
|
|
|
|
badge.textContent = status === 'working' ? '工作中' :
|
|
|
|
|
status === 'completed' ? '完成' : '空闲';
|
|
|
|
|
if (badge) {
|
|
|
|
|
badge.className = `agent-status-badge status-${status}`;
|
|
|
|
|
badge.textContent = status === 'working' ? '工作中' :
|
|
|
|
|
status === 'completed' ? '完成' : '空闲';
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 更新进度条
|
|
|
|
|
function updateProgress(progress, text) {
|
|
|
|
|
progressFill.style.width = progress + '%';
|
|
|
|
|
progressText.textContent = text;
|
|
|
|
|
if (progressFill) progressFill.style.width = progress + '%';
|
|
|
|
|
if (progressText) progressText.textContent = text;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 上传文件到服务器
|
|
|
|
|
@ -335,7 +342,7 @@ function updateToolsStatus(toolsStatus) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 查看问题详情
|
|
|
|
|
function viewIssueDetail(rule, message) {
|
|
|
|
|
function viewIssueDetail(rule, message, suggestion = null) {
|
|
|
|
|
const detailModal = document.createElement('div');
|
|
|
|
|
detailModal.className = 'modal';
|
|
|
|
|
detailModal.style.display = 'block';
|
|
|
|
|
@ -356,7 +363,7 @@ function viewIssueDetail(rule, message) {
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label>建议操作:</label>
|
|
|
|
|
<p>请根据规则要求修改代码,确保符合军事代码合规性标准。</p>
|
|
|
|
|
<p>${suggestion || '请根据规则要求修改代码,确保符合军事代码合规性标准。'}</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="modal-footer">
|
|
|
|
|
@ -368,7 +375,7 @@ function viewIssueDetail(rule, message) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 显示修复建议
|
|
|
|
|
function showFixSuggestion(rule, message) {
|
|
|
|
|
function showFixSuggestion(rule, message, aiSuggestion = null) {
|
|
|
|
|
const suggestions = {
|
|
|
|
|
'B608': '使用参数化查询替代字符串拼接,例如使用 cursor.execute("SELECT * FROM table WHERE id = %s", (user_id,))',
|
|
|
|
|
'E501': '将长行拆分为多行,使用括号或反斜杠进行换行',
|
|
|
|
|
@ -377,7 +384,7 @@ function showFixSuggestion(rule, message) {
|
|
|
|
|
'default': '请参考相关代码规范文档,确保代码符合军事项目标准'
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const suggestion = suggestions[rule] || suggestions['default'];
|
|
|
|
|
const suggestion = aiSuggestion || suggestions[rule] || suggestions['default'];
|
|
|
|
|
|
|
|
|
|
const suggestionModal = document.createElement('div');
|
|
|
|
|
suggestionModal.className = 'modal';
|
|
|
|
|
@ -412,23 +419,24 @@ function showFixSuggestion(rule, message) {
|
|
|
|
|
|
|
|
|
|
function completeCheck() {
|
|
|
|
|
isChecking = false;
|
|
|
|
|
startCheckBtn.style.display = 'inline-block';
|
|
|
|
|
stopCheckBtn.style.display = 'none';
|
|
|
|
|
progressContainer.style.display = 'none';
|
|
|
|
|
|
|
|
|
|
if (startCheckBtn) startCheckBtn.style.display = 'inline-block';
|
|
|
|
|
if (stopCheckBtn) stopCheckBtn.style.display = 'none';
|
|
|
|
|
if (progressContainer) progressContainer.style.display = 'none';
|
|
|
|
|
|
|
|
|
|
// 更新 Agent 状态
|
|
|
|
|
updateAgentStatus('completed');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 显示结果区域
|
|
|
|
|
resultsSection.style.display = 'block';
|
|
|
|
|
if (resultsSection) resultsSection.style.display = 'block';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function showResults() {
|
|
|
|
|
resultsSection.style.display = 'block';
|
|
|
|
|
if (resultsSection) resultsSection.style.display = 'block';
|
|
|
|
|
// 结果现在通过 processCheckResults 函数处理
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function displayResults(results) {
|
|
|
|
|
if (!resultsContent) return;
|
|
|
|
|
resultsContent.innerHTML = '';
|
|
|
|
|
|
|
|
|
|
if (!results || results.length === 0) {
|
|
|
|
|
@ -463,38 +471,77 @@ function createResultItem(result) {
|
|
|
|
|
const iconClass = result.type === 'error' ? 'times' : result.type === 'warning' ? 'exclamation-triangle' : 'info-circle';
|
|
|
|
|
const displayPath = result.relative_path || result.file || '未知文件';
|
|
|
|
|
const location = `${displayPath}:${result.line}:${result.column || 0}`;
|
|
|
|
|
|
|
|
|
|
// 风险等级显示
|
|
|
|
|
const riskLevel = result.risk_level || 'low';
|
|
|
|
|
const riskColors = {
|
|
|
|
|
high: '#dc3545',
|
|
|
|
|
medium: '#ffc107',
|
|
|
|
|
low: '#28a745'
|
|
|
|
|
};
|
|
|
|
|
const riskLabels = {
|
|
|
|
|
high: '高风险',
|
|
|
|
|
medium: '中风险',
|
|
|
|
|
low: '低风险'
|
|
|
|
|
};
|
|
|
|
|
const riskBadge = result.risk_level ?
|
|
|
|
|
`<span style="background: ${riskColors[riskLevel]}; color: white; padding: 2px 8px; border-radius: 12px; font-size: 11px; margin-left: 8px;">${riskLabels[riskLevel]}</span>` : '';
|
|
|
|
|
|
|
|
|
|
// 检测工具显示
|
|
|
|
|
const detectedBy = result.detected_by && result.detected_by.length > 0
|
|
|
|
|
? result.detected_by.join(', ')
|
|
|
|
|
: (result.tool || '未知');
|
|
|
|
|
|
|
|
|
|
resultItem.innerHTML = `
|
|
|
|
|
<div class="result-icon ${result.type}"><i class="fas fa-${iconClass}"></i></div>
|
|
|
|
|
<div class="result-content">
|
|
|
|
|
<div class="result-title"></div>
|
|
|
|
|
<div class="result-content" style="flex: 1;">
|
|
|
|
|
<div class="result-title" style="display: flex; align-items: center; flex-wrap: wrap;">
|
|
|
|
|
<span>${result.message || '代码问题'}</span>
|
|
|
|
|
${riskBadge}
|
|
|
|
|
</div>
|
|
|
|
|
<div class="result-description"></div>
|
|
|
|
|
<div class="result-location">${location}</div>
|
|
|
|
|
${result.suggestion ? `<div class="result-suggestion" style="margin-top: 8px; padding: 8px; background: #f8f9fa; border-left: 3px solid #1e3c72; border-radius: 4px; font-size: 13px; color: #555;">
|
|
|
|
|
<strong style="color: #1e3c72;">💡 修改建议:</strong> ${result.suggestion}
|
|
|
|
|
</div>` : ''}
|
|
|
|
|
</div>
|
|
|
|
|
<div class="result-actions">
|
|
|
|
|
<button class="btn btn-primary">查看详情</button>
|
|
|
|
|
<button class="btn btn-secondary">修复建议</button>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
resultItem.querySelector('.result-title').textContent = result.message || '代码问题';
|
|
|
|
|
const desc = resultItem.querySelector('.result-description');
|
|
|
|
|
desc.innerHTML = `<strong>规则:</strong> ${result.rule || '未知规则'} | <strong>严重程度:</strong> ${result.severity || 'medium'}${result.confidence ? ` | <strong>置信度:</strong> ${result.confidence}` : ''}`;
|
|
|
|
|
desc.innerHTML = `<strong>规则:</strong> ${result.rule || '未知规则'} | <strong>严重程度:</strong> ${result.severity || 'medium'} | <strong>检测工具:</strong> ${detectedBy}${result.confidence ? ` | <strong>置信度:</strong> ${result.confidence}` : ''}${result.risk_score !== undefined ? ` | <strong>风险评分:</strong> ${result.risk_score}` : ''}`;
|
|
|
|
|
const [detailBtn, fixBtn] = resultItem.querySelectorAll('.result-actions .btn');
|
|
|
|
|
detailBtn.addEventListener('click', () => viewIssueDetail(result.rule || '', result.message || ''));
|
|
|
|
|
fixBtn.addEventListener('click', () => showFixSuggestion(result.rule || '', result.message || ''));
|
|
|
|
|
detailBtn.addEventListener('click', () => viewIssueDetail(result.rule || '', result.message || '', result.suggestion));
|
|
|
|
|
fixBtn.addEventListener('click', () => showFixSuggestion(result.rule || '', result.message || '', result.suggestion));
|
|
|
|
|
return resultItem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 创建结果摘要
|
|
|
|
|
function createResultsSummary(results) {
|
|
|
|
|
function createResultsSummary(results, resultData = {}) {
|
|
|
|
|
const errorCount = results.filter(r => r.type === 'error').length;
|
|
|
|
|
const warningCount = results.filter(r => r.type === 'warning').length;
|
|
|
|
|
const infoCount = results.filter(r => r.type === 'info').length;
|
|
|
|
|
const totalCount = results.length;
|
|
|
|
|
|
|
|
|
|
// AI分析统计
|
|
|
|
|
const highRiskCount = resultData.high_risk_count || results.filter(r => r.risk_level === 'high').length;
|
|
|
|
|
const mediumRiskCount = resultData.medium_risk_count || results.filter(r => r.risk_level === 'medium').length;
|
|
|
|
|
const lowRiskCount = resultData.low_risk_count || results.filter(r => r.risk_level === 'low').length;
|
|
|
|
|
const duplicatesRemoved = resultData.duplicates_removed || 0;
|
|
|
|
|
const analysisMethod = resultData.analysis_method || 'none';
|
|
|
|
|
const aiAnalyzed = resultData.ai_analyzed || false;
|
|
|
|
|
|
|
|
|
|
return `
|
|
|
|
|
<div style="background: #f8f9fa; padding: 20px; border-radius: 8px; margin-bottom: 20px; border-left: 4px solid #1e3c72;">
|
|
|
|
|
<h3 style="margin: 0 0 15px 0; color: #1e3c72;">检查结果摘要</h3>
|
|
|
|
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 15px;">
|
|
|
|
|
<h3 style="margin: 0 0 15px 0; color: #1e3c72; display: flex; align-items: center; gap: 8px;">
|
|
|
|
|
检查结果摘要
|
|
|
|
|
${aiAnalyzed ? `<span style="background: #1e3c72; color: white; padding: 4px 10px; border-radius: 12px; font-size: 11px; font-weight: normal;">
|
|
|
|
|
<i class="fas fa-robot"></i> AI分析: ${analysisMethod === 'ai-powered' ? 'AI增强' : '规则基础'}
|
|
|
|
|
</span>` : ''}
|
|
|
|
|
</h3>
|
|
|
|
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 15px; margin-bottom: 15px;">
|
|
|
|
|
<div style="text-align: center;">
|
|
|
|
|
<div style="font-size: 24px; font-weight: bold; color: #dc3545;">${errorCount}</div>
|
|
|
|
|
<div style="color: #666; font-size: 14px;">错误</div>
|
|
|
|
|
@ -512,6 +559,28 @@ function createResultsSummary(results) {
|
|
|
|
|
<div style="color: #666; font-size: 14px;">总计</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
${aiAnalyzed ? `
|
|
|
|
|
<div style="border-top: 1px solid #dee2e6; padding-top: 15px; margin-top: 15px;">
|
|
|
|
|
<h4 style="margin: 0 0 10px 0; color: #495057; font-size: 14px;">风险评估</h4>
|
|
|
|
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); gap: 10px;">
|
|
|
|
|
<div style="text-align: center;">
|
|
|
|
|
<div style="font-size: 20px; font-weight: bold; color: #dc3545;">${highRiskCount}</div>
|
|
|
|
|
<div style="color: #666; font-size: 12px;">高风险</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div style="text-align: center;">
|
|
|
|
|
<div style="font-size: 20px; font-weight: bold; color: #ffc107;">${mediumRiskCount}</div>
|
|
|
|
|
<div style="color: #666; font-size: 12px;">中风险</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div style="text-align: center;">
|
|
|
|
|
<div style="font-size: 20px; font-weight: bold; color: #28a745;">${lowRiskCount}</div>
|
|
|
|
|
<div style="color: #666; font-size: 12px;">低风险</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
${duplicatesRemoved > 0 ? `<div style="margin-top: 10px; padding: 8px; background: #e7f3ff; border-radius: 4px; font-size: 12px; color: #0066cc;">
|
|
|
|
|
<i class="fas fa-filter"></i> 已去重 ${duplicatesRemoved} 个重复问题
|
|
|
|
|
</div>` : ''}
|
|
|
|
|
</div>
|
|
|
|
|
` : ''}
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
@ -604,7 +673,7 @@ function displayProjects(projects) {
|
|
|
|
|
</div>
|
|
|
|
|
<div class="project-actions">
|
|
|
|
|
<button class="btn btn-primary" onclick="event.stopPropagation(); showProjectDetail(${JSON.stringify(project).replace(/"/g, '"')})">查看详情</button>
|
|
|
|
|
<button class="btn btn-secondary" onclick="event.stopPropagation(); runProjectCheck(${project.id})">运行检查</button>
|
|
|
|
|
<button class="btn btn-secondary" onclick="event.stopPropagation(); showRuleSetSelectDialog(${project.id})">运行检查</button>
|
|
|
|
|
<button class="btn btn-danger" onclick="event.stopPropagation(); deleteProject(${project.id}, '${project.name}')">删除</button>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
@ -907,24 +976,70 @@ function showProjectDetail(project) {
|
|
|
|
|
// 已移除检查历史渲染
|
|
|
|
|
|
|
|
|
|
// 运行项目检查
|
|
|
|
|
async function runProjectCheck(projectId) {
|
|
|
|
|
async function runProjectCheck(projectId, ruleSetName = null, tools = ['pylint', 'flake8', 'bandit'], useAiAnalysis = true) {
|
|
|
|
|
console.log('[运行检查] 开始执行检查,参数:', { projectId, ruleSetName, tools, useAiAnalysis });
|
|
|
|
|
|
|
|
|
|
if (!projectId) {
|
|
|
|
|
console.error('[运行检查] 项目ID为空');
|
|
|
|
|
showErrorMessage('项目ID无效');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
showLoadingOverlay('正在执行检查...');
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(`${API_BASE_URL}/projects/${projectId}/check`, { method: 'POST' });
|
|
|
|
|
const requestBody = {
|
|
|
|
|
tools: tools,
|
|
|
|
|
use_ai_analysis: useAiAnalysis
|
|
|
|
|
};
|
|
|
|
|
if (ruleSetName) {
|
|
|
|
|
requestBody.rule_set = ruleSetName;
|
|
|
|
|
console.log('[运行检查] 使用规则集:', ruleSetName);
|
|
|
|
|
} else {
|
|
|
|
|
console.log('[运行检查] 使用默认规则');
|
|
|
|
|
}
|
|
|
|
|
console.log('[运行检查] 选择的工具:', tools);
|
|
|
|
|
console.log('[运行检查] 启用AI分析:', useAiAnalysis);
|
|
|
|
|
|
|
|
|
|
const apiUrl = `${API_BASE_URL}/projects/${projectId}/check`;
|
|
|
|
|
console.log('[运行检查] 发送检查请求到:', apiUrl);
|
|
|
|
|
console.log('[运行检查] 请求体:', JSON.stringify(requestBody, null, 2));
|
|
|
|
|
|
|
|
|
|
const response = await fetch(apiUrl, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify(requestBody)
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
console.log('[运行检查] 响应状态:', response.status, response.statusText);
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
const errorText = await response.text();
|
|
|
|
|
console.error('[运行检查] 响应错误:', errorText);
|
|
|
|
|
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
console.log('[运行检查] 响应数据:', data);
|
|
|
|
|
|
|
|
|
|
if (data.success) {
|
|
|
|
|
currentProjectLastResults = data.data.all_issues || [];
|
|
|
|
|
console.log('检查完成,问题数量:', currentProjectLastResults.length);
|
|
|
|
|
showSuccessMessage('检查完成');
|
|
|
|
|
displayCheckResultsPanel(data.data);
|
|
|
|
|
if (currentProject && currentProject.id === projectId) {
|
|
|
|
|
displayCheckResultsPanel(data.data);
|
|
|
|
|
if (currentProject && currentProject.id === projectId) {
|
|
|
|
|
try {
|
|
|
|
|
const pr = await fetch(`${API_BASE_URL}/projects/${projectId}`);
|
|
|
|
|
const pd = await pr.json();
|
|
|
|
|
if (pd.success) currentProject = pd.data;
|
|
|
|
|
} catch {}
|
|
|
|
|
// 不再渲染检查历史
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error('更新项目信息失败:', e);
|
|
|
|
|
}
|
|
|
|
|
// 不再渲染检查历史
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
console.error('检查失败:', data.error);
|
|
|
|
|
showErrorMessage('检查失败:' + (data.error || ''));
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
@ -1083,7 +1198,7 @@ function showErrorMessage(message) {
|
|
|
|
|
function displayCheckResultsPanel(resultData) {
|
|
|
|
|
const panel = document.getElementById('checkResultsPanel');
|
|
|
|
|
if (!panel) return;
|
|
|
|
|
renderProjectResults(resultData.all_issues || []);
|
|
|
|
|
renderProjectResults(resultData.all_issues || [], resultData);
|
|
|
|
|
panel.style.display = 'block';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -1110,7 +1225,7 @@ function hideLoadingOverlay() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 在项目详情页的结果面板里渲染结果
|
|
|
|
|
function renderProjectResults(results) {
|
|
|
|
|
function renderProjectResults(results, resultData = {}) {
|
|
|
|
|
const container = document.getElementById('checkResultsContent');
|
|
|
|
|
if (!container) return;
|
|
|
|
|
container.innerHTML = '';
|
|
|
|
|
@ -1126,23 +1241,9 @@ function renderProjectResults(results) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const errorCount = results.filter(r => r.type === 'error').length;
|
|
|
|
|
const warningCount = results.filter(r => r.type === 'warning').length;
|
|
|
|
|
const infoCount = results.filter(r => r.type === 'info').length;
|
|
|
|
|
const totalCount = results.length;
|
|
|
|
|
|
|
|
|
|
// 使用 createResultsSummary 生成摘要
|
|
|
|
|
const summary = document.createElement('div');
|
|
|
|
|
summary.innerHTML = `
|
|
|
|
|
<div style="background: #f8f9fa; padding: 20px; border-radius: 8px; margin-bottom: 20px; border-left: 4px solid #1e3c72;">
|
|
|
|
|
<h3 style="margin: 0 0 15px 0; color: #1e3c72;">检查结果摘要</h3>
|
|
|
|
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 15px;">
|
|
|
|
|
<div style="text-align: center;"><div style="font-size: 24px; font-weight: bold; color: #dc3545;">${errorCount}</div><div style="color: #666; font-size: 14px;">错误</div></div>
|
|
|
|
|
<div style="text-align: center;"><div style="font-size: 24px; font-weight: bold; color: #ffc107;">${warningCount}</div><div style="color: #666; font-size: 14px;">警告</div></div>
|
|
|
|
|
<div style="text-align: center;"><div style="font-size: 24px; font-weight: bold; color: #17a2b8;">${infoCount}</div><div style="color: #666; font-size: 14px;">信息</div></div>
|
|
|
|
|
<div style="text-align: center;"><div style="font-size: 24px; font-weight: bold; color: #1e3c72;">${totalCount}</div><div style="color: #666; font-size: 14px;">总计</div></div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
summary.innerHTML = createResultsSummary(results, resultData);
|
|
|
|
|
container.appendChild(summary);
|
|
|
|
|
|
|
|
|
|
const list = document.createElement('div');
|
|
|
|
|
@ -1163,11 +1264,234 @@ const runCheckBtn = document.getElementById('runCheckBtn');
|
|
|
|
|
if (runCheckBtn) {
|
|
|
|
|
runCheckBtn.addEventListener('click', () => {
|
|
|
|
|
if (currentProject) {
|
|
|
|
|
runProjectCheck(currentProject.id);
|
|
|
|
|
showRuleSetSelectDialog(currentProject.id);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 显示规则集选择对话框
|
|
|
|
|
let pendingCheckProjectId = null;
|
|
|
|
|
async function showRuleSetSelectDialog(projectId) {
|
|
|
|
|
console.log('[检查配置] 显示对话框,项目ID:', projectId);
|
|
|
|
|
pendingCheckProjectId = projectId;
|
|
|
|
|
const modal = document.getElementById('ruleSetSelectModal');
|
|
|
|
|
const useCustomCheckbox = document.getElementById('useCustomRuleSet');
|
|
|
|
|
const ruleSetSelectGroup = document.getElementById('ruleSetSelectGroup');
|
|
|
|
|
const ruleSetSelect = document.getElementById('ruleSetSelect');
|
|
|
|
|
|
|
|
|
|
if (!modal) {
|
|
|
|
|
console.error('[检查配置] 模态框元素未找到');
|
|
|
|
|
showErrorMessage('检查配置对话框未找到');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log('[检查配置] 找到的元素:', {
|
|
|
|
|
modal: !!modal,
|
|
|
|
|
useCustomCheckbox: !!useCustomCheckbox,
|
|
|
|
|
ruleSetSelectGroup: !!ruleSetSelectGroup,
|
|
|
|
|
ruleSetSelect: !!ruleSetSelect
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 重置状态
|
|
|
|
|
if (useCustomCheckbox) {
|
|
|
|
|
useCustomCheckbox.checked = false;
|
|
|
|
|
}
|
|
|
|
|
if (ruleSetSelectGroup) {
|
|
|
|
|
ruleSetSelectGroup.style.display = 'none';
|
|
|
|
|
}
|
|
|
|
|
if (ruleSetSelect) {
|
|
|
|
|
ruleSetSelect.value = '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 重置工具选择(默认全选)
|
|
|
|
|
const toolPylint = document.getElementById('toolPylint');
|
|
|
|
|
const toolFlake8 = document.getElementById('toolFlake8');
|
|
|
|
|
const toolBandit = document.getElementById('toolBandit');
|
|
|
|
|
|
|
|
|
|
if (toolPylint) toolPylint.checked = true;
|
|
|
|
|
if (toolFlake8) toolFlake8.checked = true;
|
|
|
|
|
if (toolBandit) toolBandit.checked = true;
|
|
|
|
|
|
|
|
|
|
console.log('[检查配置] 工具选择已重置为默认全选');
|
|
|
|
|
|
|
|
|
|
// 加载规则集列表
|
|
|
|
|
try {
|
|
|
|
|
console.log('[检查配置] 开始加载规则集列表');
|
|
|
|
|
const response = await fetch(`${API_BASE_URL}/rules`);
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
console.log('[检查配置] 规则集列表响应:', data);
|
|
|
|
|
|
|
|
|
|
if (ruleSetSelect) {
|
|
|
|
|
ruleSetSelect.innerHTML = '<option value="">请选择规则集...</option>';
|
|
|
|
|
if (data.success && data.data && data.data.length > 0) {
|
|
|
|
|
data.data.forEach(rule => {
|
|
|
|
|
const option = document.createElement('option');
|
|
|
|
|
option.value = rule.name;
|
|
|
|
|
option.textContent = rule.name;
|
|
|
|
|
ruleSetSelect.appendChild(option);
|
|
|
|
|
});
|
|
|
|
|
console.log('[检查配置] 已加载', data.data.length, '个规则集');
|
|
|
|
|
} else {
|
|
|
|
|
const option = document.createElement('option');
|
|
|
|
|
option.value = '';
|
|
|
|
|
option.textContent = '暂无可用规则集';
|
|
|
|
|
option.disabled = true;
|
|
|
|
|
ruleSetSelect.appendChild(option);
|
|
|
|
|
console.log('[检查配置] 没有可用的规则集');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('[检查配置] 加载规则集列表失败:', error);
|
|
|
|
|
showErrorMessage('加载规则集列表失败');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
modal.style.display = 'block';
|
|
|
|
|
console.log('[检查配置] 对话框已显示');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 规则集选择对话框事件绑定
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
|
console.log('[检查配置] DOMContentLoaded - 开始绑定事件');
|
|
|
|
|
const useCustomCheckbox = document.getElementById('useCustomRuleSet');
|
|
|
|
|
const ruleSetSelectGroup = document.getElementById('ruleSetSelectGroup');
|
|
|
|
|
const closeRuleSetModal = document.getElementById('closeRuleSetModal');
|
|
|
|
|
const cancelRuleSetBtn = document.getElementById('cancelRuleSetBtn');
|
|
|
|
|
const confirmRuleSetBtn = document.getElementById('confirmRuleSetBtn');
|
|
|
|
|
|
|
|
|
|
console.log('[检查配置] 找到的元素:', {
|
|
|
|
|
useCustomCheckbox: !!useCustomCheckbox,
|
|
|
|
|
ruleSetSelectGroup: !!ruleSetSelectGroup,
|
|
|
|
|
closeRuleSetModal: !!closeRuleSetModal,
|
|
|
|
|
cancelRuleSetBtn: !!cancelRuleSetBtn,
|
|
|
|
|
confirmRuleSetBtn: !!confirmRuleSetBtn
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (useCustomCheckbox) {
|
|
|
|
|
useCustomCheckbox.addEventListener('change', (e) => {
|
|
|
|
|
console.log('[检查配置] 使用自定义规则集复选框状态改变:', e.target.checked);
|
|
|
|
|
if (ruleSetSelectGroup) {
|
|
|
|
|
ruleSetSelectGroup.style.display = e.target.checked ? 'block' : 'none';
|
|
|
|
|
}
|
|
|
|
|
if (!e.target.checked) {
|
|
|
|
|
const ruleSetSelect = document.getElementById('ruleSetSelect');
|
|
|
|
|
if (ruleSetSelect) ruleSetSelect.value = '';
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function closeRuleSetModalFunc() {
|
|
|
|
|
console.log('[检查配置] 关闭对话框');
|
|
|
|
|
const modal = document.getElementById('ruleSetSelectModal');
|
|
|
|
|
if (modal) modal.style.display = 'none';
|
|
|
|
|
pendingCheckProjectId = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (closeRuleSetModal) {
|
|
|
|
|
closeRuleSetModal.addEventListener('click', () => {
|
|
|
|
|
console.log('[检查配置] 点击关闭按钮');
|
|
|
|
|
closeRuleSetModalFunc();
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
console.warn('[检查配置] closeRuleSetModal 元素未找到');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (cancelRuleSetBtn) {
|
|
|
|
|
cancelRuleSetBtn.addEventListener('click', () => {
|
|
|
|
|
console.log('[检查配置] 点击取消按钮');
|
|
|
|
|
closeRuleSetModalFunc();
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
console.warn('[检查配置] cancelRuleSetBtn 元素未找到');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (confirmRuleSetBtn) {
|
|
|
|
|
confirmRuleSetBtn.addEventListener('click', () => {
|
|
|
|
|
console.log('[检查配置] 确认按钮被点击');
|
|
|
|
|
try {
|
|
|
|
|
// 获取选择的工具
|
|
|
|
|
const toolPylint = document.getElementById('toolPylint');
|
|
|
|
|
const toolFlake8 = document.getElementById('toolFlake8');
|
|
|
|
|
const toolBandit = document.getElementById('toolBandit');
|
|
|
|
|
|
|
|
|
|
console.log('[检查配置] 工具复选框状态:', {
|
|
|
|
|
pylint: toolPylint ? toolPylint.checked : '元素不存在',
|
|
|
|
|
flake8: toolFlake8 ? toolFlake8.checked : '元素不存在',
|
|
|
|
|
bandit: toolBandit ? toolBandit.checked : '元素不存在'
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const selectedTools = [];
|
|
|
|
|
if (toolPylint && toolPylint.checked) selectedTools.push('pylint');
|
|
|
|
|
if (toolFlake8 && toolFlake8.checked) selectedTools.push('flake8');
|
|
|
|
|
if (toolBandit && toolBandit.checked) selectedTools.push('bandit');
|
|
|
|
|
|
|
|
|
|
console.log('[检查配置] 选择的工具:', selectedTools);
|
|
|
|
|
|
|
|
|
|
// 验证至少选择一个工具
|
|
|
|
|
if (selectedTools.length === 0) {
|
|
|
|
|
console.warn('[检查配置] 未选择任何工具');
|
|
|
|
|
showErrorMessage('请至少选择一个检查工具');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取规则集选择
|
|
|
|
|
const useCustomCheckbox = document.getElementById('useCustomRuleSet');
|
|
|
|
|
const ruleSetSelect = document.getElementById('ruleSetSelect');
|
|
|
|
|
const useAiAnalysisCheckbox = document.getElementById('useAiAnalysis');
|
|
|
|
|
|
|
|
|
|
const useCustom = useCustomCheckbox ? useCustomCheckbox.checked : false;
|
|
|
|
|
const selectedRuleSet = ruleSetSelect ? ruleSetSelect.value : '';
|
|
|
|
|
const useAiAnalysis = useAiAnalysisCheckbox ? useAiAnalysisCheckbox.checked : true;
|
|
|
|
|
|
|
|
|
|
console.log('[检查配置] 规则集选择:', { useCustom, selectedRuleSet });
|
|
|
|
|
console.log('[检查配置] AI分析:', useAiAnalysis);
|
|
|
|
|
|
|
|
|
|
if (useCustom && !selectedRuleSet) {
|
|
|
|
|
console.warn('[检查配置] 选择了使用规则集但未选择具体规则集');
|
|
|
|
|
showErrorMessage('请选择一个规则集');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log('[检查配置] pendingCheckProjectId:', pendingCheckProjectId);
|
|
|
|
|
|
|
|
|
|
// 先保存项目ID,因为 closeRuleSetModalFunc 会清空它
|
|
|
|
|
const savedProjectId = pendingCheckProjectId;
|
|
|
|
|
|
|
|
|
|
// 关闭对话框
|
|
|
|
|
closeRuleSetModalFunc();
|
|
|
|
|
|
|
|
|
|
if (savedProjectId) {
|
|
|
|
|
const ruleSetName = useCustom ? selectedRuleSet : null;
|
|
|
|
|
console.log('[检查配置] 调用 runProjectCheck:', {
|
|
|
|
|
projectId: savedProjectId,
|
|
|
|
|
ruleSetName: ruleSetName,
|
|
|
|
|
tools: selectedTools,
|
|
|
|
|
useAiAnalysis: useAiAnalysis
|
|
|
|
|
});
|
|
|
|
|
runProjectCheck(savedProjectId, ruleSetName, selectedTools, useAiAnalysis);
|
|
|
|
|
} else {
|
|
|
|
|
console.error('[检查配置] savedProjectId 为空,无法执行检查');
|
|
|
|
|
showErrorMessage('项目ID丢失,请重新选择项目');
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('[检查配置] 处理确认按钮时出错:', error);
|
|
|
|
|
showErrorMessage('处理检查配置时出错: ' + error.message);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
console.error('[检查配置] confirmRuleSetBtn 元素未找到');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 点击模态框外部关闭
|
|
|
|
|
const ruleSetModal = document.getElementById('ruleSetSelectModal');
|
|
|
|
|
if (ruleSetModal) {
|
|
|
|
|
ruleSetModal.addEventListener('click', (e) => {
|
|
|
|
|
if (e.target === ruleSetModal) {
|
|
|
|
|
closeRuleSetModalFunc();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 删除项目按钮
|
|
|
|
|
const deleteProjectBtn = document.getElementById('deleteProjectBtn');
|
|
|
|
|
if (deleteProjectBtn) {
|
|
|
|
|
@ -1186,20 +1510,78 @@ async function loadDashboardData() {
|
|
|
|
|
if (!data.success) return;
|
|
|
|
|
const stats = data.data || {};
|
|
|
|
|
|
|
|
|
|
const totalProjectsElement = document.getElementById('totalProjects');
|
|
|
|
|
const totalFilesElement = document.getElementById('totalFiles');
|
|
|
|
|
const complianceRateElement = document.getElementById('complianceRate');
|
|
|
|
|
const pendingIssuesElement = document.getElementById('pendingIssues');
|
|
|
|
|
const highRiskIssuesElement = document.getElementById('highRiskIssues');
|
|
|
|
|
const checkedProjectsElement = document.getElementById('checkedProjects');
|
|
|
|
|
|
|
|
|
|
if (totalProjectsElement) totalProjectsElement.textContent = stats.num_projects ?? 0;
|
|
|
|
|
if (totalFilesElement) totalFilesElement.textContent = stats.total_files_checked ?? 0;
|
|
|
|
|
if (complianceRateElement) complianceRateElement.textContent = (stats.compliance_rate ?? 0) + '%';
|
|
|
|
|
if (pendingIssuesElement) pendingIssuesElement.textContent = stats.warning_count ?? 0;
|
|
|
|
|
if (highRiskIssuesElement) highRiskIssuesElement.textContent = stats.error_count ?? 0;
|
|
|
|
|
if (checkedProjectsElement) checkedProjectsElement.textContent = stats.checked_projects ?? 0;
|
|
|
|
|
|
|
|
|
|
// 加载最近项目列表
|
|
|
|
|
loadRecentProjects();
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error('加载仪表板数据失败:', e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 加载最近项目列表
|
|
|
|
|
async function loadRecentProjects() {
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(`${API_BASE_URL}/projects`);
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
const container = document.getElementById('recentProjectsList');
|
|
|
|
|
if (!container) return;
|
|
|
|
|
|
|
|
|
|
if (data.success && data.data && data.data.length > 0) {
|
|
|
|
|
const recentProjects = data.data
|
|
|
|
|
.sort((a, b) => new Date(b.updated_at || b.created_at) - new Date(a.updated_at || a.created_at))
|
|
|
|
|
.slice(0, 5);
|
|
|
|
|
|
|
|
|
|
container.innerHTML = '';
|
|
|
|
|
recentProjects.forEach(project => {
|
|
|
|
|
const item = document.createElement('div');
|
|
|
|
|
item.style.cssText = 'display:flex; align-items:center; justify-content:space-between; padding:12px; border-bottom:1px solid #e9ecef; cursor:pointer; transition:background 0.2s;';
|
|
|
|
|
item.onmouseenter = () => item.style.background = '#f8f9fa';
|
|
|
|
|
item.onmouseleave = () => item.style.background = 'transparent';
|
|
|
|
|
item.onclick = () => showProjectDetail(project);
|
|
|
|
|
|
|
|
|
|
const latestCheck = project.latest_check;
|
|
|
|
|
const totalIssues = latestCheck ? latestCheck.total_issues : 0;
|
|
|
|
|
const lastCheckDate = latestCheck && latestCheck.completed_at ?
|
|
|
|
|
new Date(latestCheck.completed_at).toLocaleString() : '从未检查';
|
|
|
|
|
|
|
|
|
|
item.innerHTML = `
|
|
|
|
|
<div style="flex:1;">
|
|
|
|
|
<div style="font-weight:600; color:#1e3c72; margin-bottom:4px;">${project.name}</div>
|
|
|
|
|
<div style="font-size:12px; color:#666;">
|
|
|
|
|
<span>问题数: ${totalIssues}</span>
|
|
|
|
|
<span style="margin-left:15px;">最后检查: ${lastCheckDate}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<i class="fas fa-chevron-right" style="color:#999;"></i>
|
|
|
|
|
`;
|
|
|
|
|
container.appendChild(item);
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
container.innerHTML = `
|
|
|
|
|
<div style="text-align: center; padding: 40px; color: #999;">
|
|
|
|
|
<i class="fas fa-folder-open" style="font-size: 48px; margin-bottom: 15px; opacity: 0.3;"></i>
|
|
|
|
|
<p>暂无项目,请先导入项目</p>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error('加载最近项目失败:', e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 文件浏览器相关变量
|
|
|
|
|
let currentFilePath = '';
|
|
|
|
|
let currentFileContent = '';
|
|
|
|
|
|