Files
SQLLint/SQLLinter/Infrastructure/Reporters/HtmlReportFormatter.cs
2025-12-25 12:59:20 +03:00

243 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using SQLLinter.Common;
using SQLLinter.Infrastructure.Diagram;
using System.Text;
namespace SQLLinter.Infrastructure.Reporters;
public class HtmlReportFormatter : IReportFormatter
{
public string Format(List<IRuleViolation> violations)
{
if (violations.Count == 0)
{
return "<p><em>Нет нарушений</em></p>";
}
var groupedByFile = violations
.GroupBy(v => v.FileName)
.OrderBy(g => g.Key);
var sb = new StringBuilder();
GenerateBeginningHtml(sb);
int fileIndex = 0;
foreach (var fileGroup in groupedByFile)
{
string divId = $"file_{fileIndex}";
sb.AppendLine($"<div id=\"{divId}\" class=\"file-report{(fileIndex == 0 ? " active" : "")}\">");
sb.AppendLine($"<h1>Файл: {fileGroup.Key}</h1>");
var groupedBySeverity = fileGroup
.GroupBy(v => v.Severity)
.OrderByDescending(g => g.Key);
foreach (var severityGroup in groupedBySeverity)
{
string severityClass = severityGroup.Key switch
{
RuleViolationSeverity.Critical => "critical",
RuleViolationSeverity.Warning => "warning",
RuleViolationSeverity.Info => "info",
_ => ""
};
sb.AppendLine($"<div class=\"{severityClass}\">");
sb.AppendLine($"<h3>{severityGroup.Key}</h3>");
sb.AppendLine("<table>");
sb.AppendLine("<thead>");
sb.AppendLine("<tr><th>#</th><th>Строка</th><th>Колонка</th><th>Правило</th><th>Описание</th></tr>");
sb.AppendLine("</thead>");
sb.AppendLine("<tbody>");
int rowIndex = 1;
foreach (var v in severityGroup
.OrderBy(x => x.Line)
.ThenBy(x => x.Column))
{
sb.AppendLine($"<tr><td class=\"index\">{rowIndex}</td><td class=\"line\">{v.Line}</td><td class=\"column\">{v.Column}</td><td class=\"rule\">{v.RuleName}</td><td>{v.Text}</td></tr>");
rowIndex++;
}
sb.AppendLine("</tbody>");
sb.AppendLine("</table>");
sb.AppendLine("</div>");
}
sb.AppendLine("</div>");
fileIndex++;
}
// Табы снизу
sb.AppendLine("<div class=\"tabs\">");
fileIndex = 0;
foreach (var fileGroup in groupedByFile)
{
sb.AppendLine($"<div class=\"tab{(fileIndex == 0 ? " active" : "")}\" onclick=\"showReport('file_{fileIndex}', this)\">{fileGroup.Key}</div>");
fileIndex++;
}
sb.AppendLine("</div>");
GenerateEndingHtml(sb);
return sb.ToString();
}
public string Format(List<IRuleViolation> violations, BpmnDiagram diagram)
{
if (violations.Count == 0)
{
return "<p><em>Нет нарушений</em></p>";
}
var groupedByFile = violations
.GroupBy(v => v.FileName)
.OrderBy(g => g.Key);
var sb = new StringBuilder();
GenerateBeginningHtml(sb);
int fileIndex = 0;
foreach (var fileGroup in groupedByFile)
{
string divId = $"file_{fileIndex}";
sb.AppendLine($"<div id=\"{divId}\" class=\"file-report{(fileIndex == 0 ? " active" : "")}\">");
sb.AppendLine($"<h1>Файл: {fileGroup.Key}</h1>");
var groupedBySeverity = fileGroup
.GroupBy(v => v.Severity)
.OrderByDescending(g => g.Key);
foreach (var severityGroup in groupedBySeverity)
{
string severityClass = severityGroup.Key switch
{
RuleViolationSeverity.Critical => "critical",
RuleViolationSeverity.Warning => "warning",
RuleViolationSeverity.Info => "info",
_ => ""
};
sb.AppendLine($"<div class=\"{severityClass}\">");
sb.AppendLine($"<h3>{severityGroup.Key}</h3>");
sb.AppendLine("<table>");
sb.AppendLine("<thead>");
sb.AppendLine("<tr><th>#</th><th>Строка</th><th>Колонка</th><th>Правило</th><th>Описание</th></tr>");
sb.AppendLine("</thead>");
sb.AppendLine("<tbody>");
int rowIndex = 1;
foreach (var v in severityGroup
.OrderBy(x => x.Line)
.ThenBy(x => x.Column))
{
sb.AppendLine($"<tr><td class=\"index\">{rowIndex}</td><td class=\"line\">{v.Line}</td><td class=\"column\">{v.Column}</td><td class=\"rule\">{v.RuleName}</td><td>{v.Text}</td></tr>");
rowIndex++;
}
sb.AppendLine("</tbody>");
sb.AppendLine("</table>");
sb.AppendLine("</div>");
}
sb.AppendLine("</div>");
fileIndex++;
}
//mermaid диаграмма
sb.AppendLine($"<div id=\"mermaid\" class=\"file-report{(fileIndex == 0 ? " active" : "")}\">");
sb.AppendLine($"<h1>Диаграмма</h1>");
var mermaid = MermaidRenderer.RenderHtml(diagram);
sb.AppendLine(mermaid);
sb.AppendLine("</div>");
// Табы снизу
sb.AppendLine("<div class=\"tabs\">");
fileIndex = 0;
foreach (var fileGroup in groupedByFile)
{
sb.AppendLine($"<div class=\"tab{(fileIndex == 0 ? " active" : "")}\" onclick=\"showReport('file_{fileIndex}', this)\">{fileGroup.Key}</div>");
fileIndex++;
}
sb.AppendLine($"<div class=\"tab{(fileIndex == 0 ? " active" : "")}\" onclick=\"showReport('mermaid', this)\">Диграмма</div>");
sb.AppendLine("</div>");
GenerateEndingHtml(sb);
return sb.ToString();
}
private void GenerateBeginningHtml(StringBuilder sb)
{
sb.AppendLine("<!DOCTYPE html>");
sb.AppendLine("<html lang=\"ru\">");
sb.AppendLine("<head>");
sb.AppendLine("<meta charset=\"UTF-8\">");
sb.AppendLine("<title>Отчёт по SQLпроверкам</title>");
sb.AppendLine("<style>");
sb.AppendLine("body { font-family: 'Segoe UI', Arial, sans-serif; background-color: #f5f5f5; margin: 0; padding: 0; color: #000; }");
sb.AppendLine("h1 { padding: 20px; margin: 0; background-color: #0078d4; color: white; }");
sb.AppendLine("h2 { margin-top: 20px; color: #333; }");
sb.AppendLine("h3 { margin-top: 15px; color: #555; }");
sb.AppendLine("table { border-collapse: collapse; width: 100%; margin: 20px 0; box-shadow: 0 2px 6px rgba(0,0,0,0.1); background-color: white; border-radius: 4px; overflow: hidden; }");
sb.AppendLine("th, td { border: 1px solid #e0e0e0; padding: 4px 8px; text-align: left; line-height: 1.2; }");
sb.AppendLine("th { background-color: #fafafa; font-weight: 600; }");
sb.AppendLine("td.line, td.column { width: 60px; text-align: center; }");
sb.AppendLine("td.rule { width: 300px; text-align: left; }");
sb.AppendLine("td.index { width: 40px; text-align: center; }");
sb.AppendLine(".critical { border-left: 4px solid #d13438; padding: 10px; margin-bottom: 20px; background-color: #fde7e9; }");
sb.AppendLine(".warning { border-left: 4px solid #ffaa44; padding: 10px; margin-bottom: 20px; background-color: #fff4ce; }");
sb.AppendLine(".info { border-left: 4px solid #0078d4; padding: 10px; margin-bottom: 20px; background-color: #deecf9; }");
sb.AppendLine(".tabs { position: fixed; bottom: 0; left: 0; right: 0; background-color: #ffffff; border-top: 1px solid #ccc; padding: 10px; display: flex; overflow-x: auto; scrollbar-width: thin; justify-content: flex-start; box-shadow: 0 -2px 6px rgba(0,0,0,0.1); }");
sb.AppendLine(".tab { margin-right: 10px; padding: 8px 16px; border-radius: 4px; background-color: #f3f2f1; cursor: pointer; transition: background-color 0.2s; }");
sb.AppendLine(".tab:hover { background-color: #e1dfdd; }");
sb.AppendLine(".tab.active { background-color: #0078d4; color: white; }");
sb.AppendLine(".file-report { display: none; padding: 20px 0; }");
sb.AppendLine(".file-report.active { display: block; }");
// Тёмная тема
sb.AppendLine("@media (prefers-color-scheme: dark) {");
sb.AppendLine(" body { background-color: #1e1e1e; color: #ddd; }");
sb.AppendLine(" h1 { background-color: #005a9e; }");
sb.AppendLine(" h2, h3 { color: #ddd; }");
sb.AppendLine(" table { background-color: #2d2d2d; box-shadow: none; }");
sb.AppendLine(" th { background-color: #3c3c3c; color: #fff; }");
sb.AppendLine(" td { border: 1px solid #444; }");
sb.AppendLine(" .tabs { background-color: #2d2d2d; border-top: 1px solid #444; }");
sb.AppendLine(" .tab { background-color: #3c3c3c; color: #ddd; }");
sb.AppendLine(" .tab:hover { background-color: #555; }");
sb.AppendLine(" .tab.active { background-color: #0078d4; color: white; }");
sb.AppendLine(" .critical { background-color: #4d1f1f; border-left-color: #d13438; }");
sb.AppendLine(" .warning { background-color: #4d3b1f; border-left-color: #ffaa44; }");
sb.AppendLine(" .info { background-color: #1f3b4d; border-left-color: #0078d4; }");
sb.AppendLine("}");
sb.AppendLine("</style>");
sb.AppendLine("<script src=\"https://unpkg.com/mermaid@10/dist/mermaid.min.js\"></script>");
sb.AppendLine("<script>mermaid.initialize({ startOnLoad: true, securityLevel: 'loose' });</script>");
sb.AppendLine("</head>");
sb.AppendLine("<body>");
//sb.AppendLine("<h1>Отчёт по SQLпроверкам</h1>");
}
private void GenerateEndingHtml(StringBuilder sb)
{
// JS для переключения
sb.AppendLine("<script>");
sb.AppendLine("function showReport(id, tab) {");
sb.AppendLine(" document.querySelectorAll('.file-report').forEach(el => el.classList.remove('active'));");
sb.AppendLine(" document.getElementById(id).classList.add('active');");
sb.AppendLine(" document.querySelectorAll('.tab').forEach(el => el.classList.remove('active'));");
sb.AppendLine(" tab.classList.add('active');");
sb.AppendLine(" window.scrollTo({ top: 0, behavior: 'smooth' });");
sb.AppendLine("}");
sb.AppendLine("document.querySelector('.tabs').addEventListener('wheel', function(e) {");
sb.AppendLine(" e.preventDefault();");
sb.AppendLine(" this.scrollLeft += e.deltaY;");
sb.AppendLine("});");
sb.AppendLine("</script>");
sb.AppendLine("</body>");
sb.AppendLine("</html>");
}
}