Доработан js и минимизация json
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
using SQLLinter.Common;
|
||||
using SQLLinter.Infrastructure.Diagram;
|
||||
using SQLLinter.Infrastructure.Rules.RuleViolations;
|
||||
using System.Data;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
@@ -66,38 +68,76 @@ public class HtmlReportFormatter : IReportFormatter
|
||||
|
||||
foreach (var fileGroup in groupedByFile)
|
||||
{
|
||||
var fileData = new FileReportData
|
||||
var fileData = new FileReport
|
||||
{
|
||||
FileName = fileGroup.Key
|
||||
Name = fileGroup.Key,
|
||||
};
|
||||
|
||||
|
||||
// Группировка по severity
|
||||
var severityGroups = fileGroup
|
||||
.GroupBy(v => v.Severity)
|
||||
.OrderByDescending(g => g.Key)
|
||||
.ToList();
|
||||
|
||||
foreach (var severityGroup in severityGroups)
|
||||
foreach (var violation in fileGroup.Select(t => t).OrderBy(v => v.Line).ThenBy(v => v.Column))
|
||||
{
|
||||
var violationsList = severityGroup
|
||||
.OrderBy(v => v.Line)
|
||||
.ThenBy(v => v.Column)
|
||||
.Select(v => new ViolationData
|
||||
{
|
||||
Line = v.Line,
|
||||
Column = v.Column,
|
||||
RuleName = v.RuleName,
|
||||
Text = EscapeHtml(v.Text),
|
||||
Index = severityGroup.ToList().IndexOf(v) + 1
|
||||
})
|
||||
.ToList();
|
||||
int ruleId;
|
||||
List<string> args = new();
|
||||
|
||||
if (severityGroup.Key == RuleViolationSeverity.Critical)
|
||||
fileData.CriticalViolations = violationsList;
|
||||
else if (severityGroup.Key == RuleViolationSeverity.Warning)
|
||||
fileData.WarningViolations = violationsList;
|
||||
else if (severityGroup.Key == RuleViolationSeverity.Info)
|
||||
fileData.InfoViolations = violationsList;
|
||||
if (violation is RuleTemplateViolation templateRule)
|
||||
{
|
||||
args = templateRule.Params.Select(p => EscapeHtml(p)).ToList();
|
||||
|
||||
if (reportData.Rules.Any(t => t.Value.Name == templateRule.RuleName))
|
||||
{
|
||||
ruleId = reportData.Rules.First(t => t.Value.Name == templateRule.RuleName).Key;
|
||||
}
|
||||
else
|
||||
{
|
||||
ruleId = reportData.Rules.Count + 1;
|
||||
reportData.Rules.Add(ruleId, new Rule
|
||||
{
|
||||
Name = templateRule.RuleName,
|
||||
Template = templateRule.RuleTemplate
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ruleId = reportData.Rules.Count + 1;
|
||||
reportData.Rules.Add(ruleId, new Rule
|
||||
{
|
||||
Name = violation.RuleName,
|
||||
Template = violation.Text,
|
||||
});
|
||||
}
|
||||
|
||||
var v = new Violation()
|
||||
{
|
||||
RuleId = ruleId,
|
||||
Args = args,
|
||||
Column = violation.Column,
|
||||
Line = violation.Line,
|
||||
};
|
||||
|
||||
if (violation.Severity == RuleViolationSeverity.Critical)
|
||||
{
|
||||
v.Index = fileData.Violations.Critical.Count + 1;
|
||||
fileData.Violations.Critical.Add(v);
|
||||
}
|
||||
|
||||
else if (violation.Severity == RuleViolationSeverity.Warning)
|
||||
{
|
||||
v.Index = fileData.Violations.Warning.Count + 1;
|
||||
fileData.Violations.Warning.Add(v);
|
||||
}
|
||||
|
||||
else if (violation.Severity == RuleViolationSeverity.Info)
|
||||
{
|
||||
v.Index = fileData.Violations.Info.Count + 1;
|
||||
fileData.Violations.Info.Add(v);
|
||||
}
|
||||
}
|
||||
|
||||
reportData.Files.Add(fileData);
|
||||
@@ -106,9 +146,9 @@ public class HtmlReportFormatter : IReportFormatter
|
||||
// Добавление диаграммы, если есть
|
||||
if (diagram != null)
|
||||
{
|
||||
reportData.Diagram = new DiagramData
|
||||
reportData.Diagram = new Diagram
|
||||
{
|
||||
MermaidContent = MermaidRenderer.ToMermaidContent(diagram),
|
||||
Content = MermaidRenderer.ToMermaidContent(diagram),
|
||||
HasDiagram = true
|
||||
};
|
||||
}
|
||||
@@ -173,36 +213,70 @@ public class HtmlReportFormatter : IReportFormatter
|
||||
// Классы для сериализации
|
||||
private class ReportData
|
||||
{
|
||||
public List<FileReportData> Files { get; set; } = new();
|
||||
public DiagramData? Diagram { get; set; }
|
||||
[JsonPropertyName("f")] // files
|
||||
public List<FileReport> Files { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("r")] // rules
|
||||
public Dictionary<int, Rule> Rules { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("d")] // diagram
|
||||
public Diagram Diagram { get; set; } = new();
|
||||
}
|
||||
|
||||
private class FileReportData
|
||||
private class FileReport
|
||||
{
|
||||
public string FileName { get; set; } = string.Empty;
|
||||
public List<ViolationData>? CriticalViolations { get; set; }
|
||||
public List<ViolationData>? WarningViolations { get; set; }
|
||||
public List<ViolationData>? InfoViolations { get; set; }
|
||||
[JsonPropertyName("n")] // name
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[JsonIgnore]
|
||||
public int TotalViolations =>
|
||||
(CriticalViolations?.Count ?? 0) +
|
||||
(WarningViolations?.Count ?? 0) +
|
||||
(InfoViolations?.Count ?? 0);
|
||||
[JsonPropertyName("v")] // violations
|
||||
public Violations Violations { get; set; } = new();
|
||||
}
|
||||
|
||||
private class ViolationData
|
||||
private class Violations
|
||||
{
|
||||
[JsonPropertyName("c")] // critical
|
||||
public List<Violation> Critical { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("w")] // warning
|
||||
public List<Violation> Warning { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("i")] // info
|
||||
public List<Violation> Info { get; set; } = new();
|
||||
}
|
||||
|
||||
private class Violation
|
||||
{
|
||||
[JsonPropertyName("i")] // index
|
||||
public int Index { get; set; }
|
||||
|
||||
[JsonPropertyName("l")] // line
|
||||
public int Line { get; set; }
|
||||
|
||||
[JsonPropertyName("c")] // column
|
||||
public int Column { get; set; }
|
||||
public string RuleName { get; set; } = string.Empty;
|
||||
public string Text { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("r")] // ruleId
|
||||
public int RuleId { get; set; }
|
||||
|
||||
[JsonPropertyName("a")] // args (optional)
|
||||
public List<string>? Args { get; set; }
|
||||
}
|
||||
|
||||
private class DiagramData
|
||||
private class Rule
|
||||
{
|
||||
public string MermaidContent { get; set; } = string.Empty;
|
||||
[JsonPropertyName("n")] // name
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("t")] // template
|
||||
public string Template { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
private class Diagram
|
||||
{
|
||||
[JsonPropertyName("c")] // content
|
||||
public string Content { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("h")] // hasDiagram
|
||||
public bool HasDiagram { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -393,7 +393,7 @@ td.index {
|
||||
td.rule {
|
||||
font-weight: 600;
|
||||
color: var(--color-text-primary);
|
||||
min-width: 200px;
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
td.description {
|
||||
|
||||
@@ -7,8 +7,9 @@ import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.mi
|
||||
class ReportRenderer {
|
||||
constructor(data) {
|
||||
this.data = data;
|
||||
this.files = data.files || [];
|
||||
this.diagram = data.diagram;
|
||||
this.files = data.files || data.f || [];
|
||||
this.rules = data.rules || data.r || {};
|
||||
this.diagram = data.diagram || data.d || {};
|
||||
this.currentFileIndex = 0;
|
||||
|
||||
this.reportsContainer = document.getElementById('reports-container');
|
||||
@@ -24,12 +25,13 @@ class ReportRenderer {
|
||||
this.renderTabs();
|
||||
this.renderFileReports();
|
||||
this.setupTabNavigation();
|
||||
|
||||
// Активируем первый таб
|
||||
this.activateTab(0);
|
||||
}
|
||||
|
||||
renderTabs() {
|
||||
const tabsList = document.getElementById('tabs-list');
|
||||
if (!tabsList) return;
|
||||
|
||||
this.tabsList.innerHTML = '';
|
||||
|
||||
// Табы для файлов
|
||||
@@ -39,25 +41,30 @@ class ReportRenderer {
|
||||
tab.dataset.target = `file_${index}`;
|
||||
tab.dataset.index = index;
|
||||
|
||||
const total = (file.v.c?.length || 0) + (file.v.w?.length || 0) + (file.v.i?.length || 0);
|
||||
|
||||
tab.innerHTML = `
|
||||
${this.escapeHtml(file.fileName)}
|
||||
<span class="tab-badge">${file.totalViolations || this.calculateTotalViolations(file)}</span>
|
||||
${this.escapeHtml(file.n)}
|
||||
<span class="tab-badge">${total}</span>
|
||||
`;
|
||||
|
||||
this.tabsList.appendChild(tab);
|
||||
});
|
||||
|
||||
// Таб для диаграммы (если есть)
|
||||
if (this.diagram?.hasDiagram) {
|
||||
if (this.diagram.h || this.diagram.hasDiagram) {
|
||||
const diagramTab = document.createElement('button');
|
||||
diagramTab.className = 'tab';
|
||||
diagramTab.dataset.target = 'mermaid';
|
||||
diagramTab.textContent = 'Диаграмма';
|
||||
this.tabsList.appendChild(diagramTab);
|
||||
tabsList.appendChild(diagramTab);
|
||||
}
|
||||
}
|
||||
|
||||
renderFileReports() {
|
||||
const container = document.getElementById('reports-container');
|
||||
if (!container) return;
|
||||
|
||||
this.reportsContainer.innerHTML = '';
|
||||
|
||||
// Рендеринг отчетов по файлам
|
||||
@@ -67,6 +74,9 @@ class ReportRenderer {
|
||||
reportDiv.className = 'file-report';
|
||||
reportDiv.style.display = 'none';
|
||||
|
||||
const fileName = file.n || file.fileName || '';
|
||||
const violations = file.v || file.violations || {};
|
||||
|
||||
// Заголовок файла
|
||||
reportDiv.innerHTML = `
|
||||
<div class="file-title-container">
|
||||
@@ -75,27 +85,28 @@ class ReportRenderer {
|
||||
<path d="M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.9 22 6 22H18C19.1 22 20 21.1 20 20V8L14 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M14 2V8H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<span class="file-name">${this.escapeHtml(file.fileName)}</span>
|
||||
<span class="file-name">${this.escapeHtml(fileName)}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Секции по severity
|
||||
if (file.criticalViolations?.length > 0) {
|
||||
reportDiv.appendChild(this.createSeveritySection('critical', file.criticalViolations));
|
||||
// Добавляем секции нарушений
|
||||
if (file.v.c?.length > 0) {
|
||||
reportDiv.appendChild(this.createViolationSection('critical', file.v.c));
|
||||
}
|
||||
if (file.warningViolations?.length > 0) {
|
||||
reportDiv.appendChild(this.createSeveritySection('warning', file.warningViolations));
|
||||
if (file.v.w?.length > 0) {
|
||||
reportDiv.appendChild(this.createViolationSection('warning', file.v.w));
|
||||
}
|
||||
if (file.infoViolations?.length > 0) {
|
||||
reportDiv.appendChild(this.createSeveritySection('info', file.infoViolations));
|
||||
if (file.v.i?.length > 0) {
|
||||
reportDiv.appendChild(this.createViolationSection('info', file.v.i));
|
||||
}
|
||||
|
||||
this.reportsContainer.appendChild(reportDiv);
|
||||
});
|
||||
|
||||
// Контейнер для диаграммы (если есть)
|
||||
if (this.diagram?.hasDiagram) {
|
||||
if (this.diagram.h || this.diagram.hasDiagram) {
|
||||
const diagramContent = this.diagram.c || this.diagram.Content || '';
|
||||
const diagramDiv = document.createElement('div');
|
||||
diagramDiv.id = 'mermaid';
|
||||
diagramDiv.className = 'file-report';
|
||||
@@ -104,7 +115,7 @@ class ReportRenderer {
|
||||
diagramDiv.innerHTML = `
|
||||
<div class="diagram-toolbar">
|
||||
<div class="toolbar-search">
|
||||
<input type="text" id="diagramSearch" placeholder="Поиск по узлам (нажмите Enter)" />
|
||||
<input type="text" id="diagramSearch" placeholder="Поиск по узлам" />
|
||||
<button class="search-clear" type="button">✕</button>
|
||||
</div>
|
||||
<div class="toolbar-actions">
|
||||
@@ -131,7 +142,7 @@ class ReportRenderer {
|
||||
<div id="minimap"></div>
|
||||
</div>
|
||||
<div id="mermaidSource" style="display:none">
|
||||
${this.escapeHtml(this.diagram.mermaidContent)}
|
||||
${this.escapeHtml(diagramContent)}
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -139,7 +150,7 @@ class ReportRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
createSeveritySection(severity, violations) {
|
||||
createViolationSection(severity, violations) {
|
||||
const severityTitle = {
|
||||
critical: 'Critical',
|
||||
warning: 'Warning',
|
||||
@@ -149,15 +160,31 @@ class ReportRenderer {
|
||||
const section = document.createElement('div');
|
||||
section.className = `severity-section ${severity}`;
|
||||
|
||||
const tableRows = violations.map(v => `
|
||||
<tr>
|
||||
<td class="index">${v.index}</td>
|
||||
<td class="line">${v.line}</td>
|
||||
<td class="column">${v.column}</td>
|
||||
<td class="rule">${this.escapeHtml(v.ruleName)}</td>
|
||||
<td class="description">${this.escapeHtml(v.text)}</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
const tableRows = violations.map(violation => {
|
||||
// Получаем правило по ID
|
||||
const ruleId = violation.r || violation.ruleId;
|
||||
const rule = this.rules[ruleId] || { n: 'Unknown', t: 'Описание отсутствует' };
|
||||
|
||||
// Формируем текст с подстановкой параметров
|
||||
let text = rule.t || '';
|
||||
const args = violation.a || violation.args || [];
|
||||
|
||||
if (args.length > 0 && text.includes('{')) {
|
||||
args.forEach((arg, index) => {
|
||||
text = text.replace(new RegExp(`\\{${index}\\}`, 'g'), arg);
|
||||
});
|
||||
}
|
||||
|
||||
return `
|
||||
<tr>
|
||||
<td class="index">${violation.i || violation.index}</td>
|
||||
<td class="line">${violation.l || violation.line}</td>
|
||||
<td class="column">${violation.c || violation.column}</td>
|
||||
<td class="rule">${this.escapeHtml(rule.n || rule.name)}</td>
|
||||
<td class="description">${this.escapeHtml(text)}</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
section.innerHTML = `
|
||||
<div class="severity-header">
|
||||
|
||||
Reference in New Issue
Block a user