frontend-guardian-core 3.5.0 → 3.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/fg-core.js +114 -1
- package/bin/fg-server.js +102 -0
- package/dist/engine/rule-engine.d.ts +2 -0
- package/dist/engine/rule-engine.d.ts.map +1 -1
- package/dist/engine/rule-engine.js +6 -0
- package/dist/engine/rule-engine.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -1
- package/dist/index.js.map +1 -1
- package/dist/rules/registry.d.ts +8 -1
- package/dist/rules/registry.d.ts.map +1 -1
- package/dist/rules/registry.js +32 -0
- package/dist/rules/registry.js.map +1 -1
- package/dist/server/dashboard-html.d.ts +10 -0
- package/dist/server/dashboard-html.d.ts.map +1 -0
- package/dist/server/dashboard-html.js +351 -0
- package/dist/server/dashboard-html.js.map +1 -0
- package/dist/server/dashboard-server.d.ts +105 -0
- package/dist/server/dashboard-server.d.ts.map +1 -0
- package/dist/server/dashboard-server.js +344 -0
- package/dist/server/dashboard-server.js.map +1 -0
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/compliance.d.ts +58 -0
- package/dist/utils/compliance.d.ts.map +1 -0
- package/dist/utils/compliance.js +266 -0
- package/dist/utils/compliance.js.map +1 -0
- package/dist/utils/dashboard-client.d.ts +58 -0
- package/dist/utils/dashboard-client.d.ts.map +1 -0
- package/dist/utils/dashboard-client.js +72 -0
- package/dist/utils/dashboard-client.js.map +1 -0
- package/package.json +3 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/rules/registry.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;;;;
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/rules/registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dashboard HTML Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates a self-contained SPA that fetches data from the
|
|
5
|
+
* dashboard server APIs and renders charts using Canvas.
|
|
6
|
+
*
|
|
7
|
+
* Zero external dependencies: pure native JS + Canvas.
|
|
8
|
+
*/
|
|
9
|
+
export declare function generateDashboardHtml(): string;
|
|
10
|
+
//# sourceMappingURL=dashboard-html.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboard-html.d.ts","sourceRoot":"","sources":["../../src/server/dashboard-html.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,wBAAgB,qBAAqB,IAAI,MAAM,CAkV9C"}
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Dashboard HTML Generator
|
|
4
|
+
*
|
|
5
|
+
* Generates a self-contained SPA that fetches data from the
|
|
6
|
+
* dashboard server APIs and renders charts using Canvas.
|
|
7
|
+
*
|
|
8
|
+
* Zero external dependencies: pure native JS + Canvas.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.generateDashboardHtml = generateDashboardHtml;
|
|
12
|
+
function generateDashboardHtml() {
|
|
13
|
+
return `<!DOCTYPE html>
|
|
14
|
+
<html lang="en">
|
|
15
|
+
<head>
|
|
16
|
+
<meta charset="UTF-8">
|
|
17
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
18
|
+
<title>Frontend Guardian Dashboard</title>
|
|
19
|
+
<style>
|
|
20
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
21
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; background: #f5f7fa; color: #333; line-height: 1.6; }
|
|
22
|
+
.container { max-width: 1200px; margin: 0 auto; padding: 32px 24px; }
|
|
23
|
+
h1 { font-size: 28px; font-weight: 600; margin-bottom: 8px; color: #1a1a2e; }
|
|
24
|
+
.subtitle { color: #888; font-size: 14px; margin-bottom: 24px; }
|
|
25
|
+
.project-select { margin-bottom: 24px; }
|
|
26
|
+
.project-select select { padding: 10px 16px; font-size: 15px; border: 1px solid #ddd; border-radius: 8px; background: #fff; min-width: 300px; cursor: pointer; }
|
|
27
|
+
.project-select label { font-size: 14px; color: #666; margin-right: 8px; }
|
|
28
|
+
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 20px; margin-bottom: 32px; }
|
|
29
|
+
.card { background: #fff; border-radius: 12px; padding: 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.06); }
|
|
30
|
+
.card h3 { font-size: 13px; color: #888; margin-bottom: 12px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px; }
|
|
31
|
+
.big-number { font-size: 42px; font-weight: 700; color: #1a1a2e; }
|
|
32
|
+
.big-number.critical { color: #e74c3c; }
|
|
33
|
+
.big-number.warning { color: #f39c12; }
|
|
34
|
+
.big-number.suggestion { color: #3498db; }
|
|
35
|
+
.chart-container { background: #fff; border-radius: 12px; padding: 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.06); margin-bottom: 20px; }
|
|
36
|
+
.chart-container h3 { font-size: 16px; margin-bottom: 16px; color: #1a1a2e; }
|
|
37
|
+
canvas { width: 100%; height: 300px; }
|
|
38
|
+
table { width: 100%; border-collapse: collapse; font-size: 14px; }
|
|
39
|
+
th, td { padding: 10px 12px; text-align: left; border-bottom: 1px solid #eee; }
|
|
40
|
+
th { font-weight: 600; color: #666; font-size: 12px; text-transform: uppercase; }
|
|
41
|
+
tr:hover { background: #f9fafb; }
|
|
42
|
+
.severity-critical { color: #e74c3c; font-weight: 600; }
|
|
43
|
+
.severity-warning { color: #f39c12; font-weight: 600; }
|
|
44
|
+
.severity-suggestion { color: #3498db; font-weight: 600; }
|
|
45
|
+
.footer { text-align: center; color: #aaa; font-size: 12px; margin-top: 40px; padding-bottom: 24px; }
|
|
46
|
+
.empty { text-align: center; padding: 60px 20px; color: #999; }
|
|
47
|
+
.empty-icon { font-size: 48px; margin-bottom: 16px; }
|
|
48
|
+
.spinner { display: inline-block; width: 20px; height: 20px; border: 2px solid #ddd; border-top-color: #3498db; border-radius: 50%; animation: spin 1s linear infinite; }
|
|
49
|
+
@keyframes spin { to { transform: rotate(360deg); } }
|
|
50
|
+
.no-data { color: #999; font-style: italic; padding: 20px; text-align: center; }
|
|
51
|
+
</style>
|
|
52
|
+
</head>
|
|
53
|
+
<body>
|
|
54
|
+
<div class="container">
|
|
55
|
+
<h1>Frontend Guardian Dashboard</h1>
|
|
56
|
+
<p class="subtitle">Multi-project governance trends and scan history</p>
|
|
57
|
+
|
|
58
|
+
<div class="project-select">
|
|
59
|
+
<label for="project-select">Project:</label>
|
|
60
|
+
<select id="project-select">
|
|
61
|
+
<option value="">Loading projects...</option>
|
|
62
|
+
</select>
|
|
63
|
+
<span id="loading" style="margin-left:12px;display:none;"><span class="spinner"></span></span>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<div id="dashboard-content">
|
|
67
|
+
<div class="empty">
|
|
68
|
+
<div class="empty-icon">📊</div>
|
|
69
|
+
<p>Select a project to view dashboard</p>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<div class="footer">
|
|
74
|
+
Frontend Guardian v3.5.2
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<script>
|
|
79
|
+
// ── Dashboard SPA ─────────────────────────────────────────────────────
|
|
80
|
+
const projectSelect = document.getElementById('project-select');
|
|
81
|
+
const dashboardContent = document.getElementById('dashboard-content');
|
|
82
|
+
const loading = document.getElementById('loading');
|
|
83
|
+
|
|
84
|
+
let currentProjectId = null;
|
|
85
|
+
|
|
86
|
+
// Load projects on page load
|
|
87
|
+
async function loadProjects() {
|
|
88
|
+
try {
|
|
89
|
+
const res = await fetch('/api/projects');
|
|
90
|
+
const data = await res.json();
|
|
91
|
+
const projects = data.projects || [];
|
|
92
|
+
|
|
93
|
+
projectSelect.innerHTML = '';
|
|
94
|
+
if (projects.length === 0) {
|
|
95
|
+
projectSelect.innerHTML = '<option value="">No projects yet</option>';
|
|
96
|
+
dashboardContent.innerHTML = '<div class="empty"><div class="empty-icon">📦</div><p>No scan reports received yet.</p><p style="font-size:13px;margin-top:8px;">Run: fg-core ./project --scan --server http://' + location.host + '</p></div>';
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const placeholder = document.createElement('option');
|
|
101
|
+
placeholder.value = '';
|
|
102
|
+
placeholder.textContent = 'Select a project...';
|
|
103
|
+
projectSelect.appendChild(placeholder);
|
|
104
|
+
|
|
105
|
+
for (const p of projects) {
|
|
106
|
+
const opt = document.createElement('option');
|
|
107
|
+
opt.value = p.id;
|
|
108
|
+
opt.textContent = p.name + ' (' + p.reportCount + ' reports)';
|
|
109
|
+
projectSelect.appendChild(opt);
|
|
110
|
+
}
|
|
111
|
+
} catch (err) {
|
|
112
|
+
projectSelect.innerHTML = '<option value="">Failed to load projects</option>';
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
projectSelect.addEventListener('change', async () => {
|
|
117
|
+
const id = projectSelect.value;
|
|
118
|
+
if (!id) {
|
|
119
|
+
dashboardContent.innerHTML = '<div class="empty"><div class="empty-icon">📊</div><p>Select a project to view dashboard</p></div>';
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
currentProjectId = id;
|
|
123
|
+
loading.style.display = 'inline-block';
|
|
124
|
+
await loadDashboard(id);
|
|
125
|
+
loading.style.display = 'none';
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
async function loadDashboard(projectId) {
|
|
129
|
+
try {
|
|
130
|
+
const [trendsRes, latestRes, reportsRes] = await Promise.all([
|
|
131
|
+
fetch('/api/projects/' + projectId + '/trends'),
|
|
132
|
+
fetch('/api/projects/' + projectId + '/latest'),
|
|
133
|
+
fetch('/api/projects/' + projectId + '/reports?limit=20'),
|
|
134
|
+
]);
|
|
135
|
+
|
|
136
|
+
const trendsData = await trendsRes.json();
|
|
137
|
+
const latestData = await latestRes.json();
|
|
138
|
+
const reportsData = await reportsRes.json();
|
|
139
|
+
|
|
140
|
+
renderDashboard(trendsData.trends || [], latestData.report || null, reportsData.reports || []);
|
|
141
|
+
} catch (err) {
|
|
142
|
+
dashboardContent.innerHTML = '<div class="empty"><div class="empty-icon">⚠</div><p>Failed to load dashboard data</p><p style="font-size:13px;">' + String(err) + '</p></div>';
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function renderDashboard(trends, latest, reports) {
|
|
147
|
+
const hasData = trends.length > 0;
|
|
148
|
+
const latestCounts = latest ? {
|
|
149
|
+
critical: latest.result.issues.critical.length,
|
|
150
|
+
warning: latest.result.issues.warning.length,
|
|
151
|
+
suggestion: latest.result.issues.suggestion.length,
|
|
152
|
+
} : { critical: 0, warning: 0, suggestion: 0 };
|
|
153
|
+
|
|
154
|
+
// Fix rate: compare first and last trend point
|
|
155
|
+
let fixRate = 0;
|
|
156
|
+
let trendDir = '-';
|
|
157
|
+
if (trends.length >= 2) {
|
|
158
|
+
const first = trends[0];
|
|
159
|
+
const last = trends[trends.length - 1];
|
|
160
|
+
const firstTotal = first.critical + first.warning + first.suggestion;
|
|
161
|
+
const lastTotal = last.critical + last.warning + last.suggestion;
|
|
162
|
+
fixRate = firstTotal > 0 ? Math.round(((firstTotal - lastTotal) / firstTotal) * 100) : 0;
|
|
163
|
+
trendDir = fixRate >= 0 ? 'Improving' : 'Worsening';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
let html = '<div class="grid">';
|
|
167
|
+
html += '<div class="card"><h3>Critical</h3><div class="big-number critical">' + latestCounts.critical + '</div></div>';
|
|
168
|
+
html += '<div class="card"><h3>Warning</h3><div class="big-number warning">' + latestCounts.warning + '</div></div>';
|
|
169
|
+
html += '<div class="card"><h3>Suggestion</h3><div class="big-number suggestion">' + latestCounts.suggestion + '</div></div>';
|
|
170
|
+
html += '<div class="card"><h3>Trend</h3><div class="big-number">' + Math.abs(fixRate) + '%</div><span style="font-size:13px;color:#666;">' + trendDir + '</span></div>';
|
|
171
|
+
html += '</div>';
|
|
172
|
+
|
|
173
|
+
if (hasData) {
|
|
174
|
+
html += '<div class="chart-container"><h3>Issue Trends</h3><canvas id="trendChart"></canvas></div>';
|
|
175
|
+
|
|
176
|
+
html += '<div class="grid">';
|
|
177
|
+
html += '<div class="chart-container"><h3>Severity Distribution</h3><canvas id="severityChart"></canvas></div>';
|
|
178
|
+
html += '<div class="chart-container"><h3>Module Distribution</h3><canvas id="moduleChart"></canvas></div>';
|
|
179
|
+
html += '</div>';
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Scan history table
|
|
183
|
+
html += '<div class="chart-container"><h3>Scan History</h3>';
|
|
184
|
+
if (reports.length === 0) {
|
|
185
|
+
html += '<p class="no-data">No scan history</p>';
|
|
186
|
+
} else {
|
|
187
|
+
html += '<table><thead><tr><th>Time</th><th>Module</th><th>Critical</th><th>Warning</th><th>Suggestion</th></tr></thead><tbody>';
|
|
188
|
+
for (const r of reports) {
|
|
189
|
+
const d = new Date(r.timestamp);
|
|
190
|
+
const time = d.getFullYear() + '-' + String(d.getMonth()+1).padStart(2,'0') + '-' + String(d.getDate()).padStart(2,'0') + ' ' + String(d.getHours()).padStart(2,'0') + ':' + String(d.getMinutes()).padStart(2,'0');
|
|
191
|
+
html += '<tr>';
|
|
192
|
+
html += '<td>' + time + '</td>';
|
|
193
|
+
html += '<td>' + r.module + '</td>';
|
|
194
|
+
html += '<td class="severity-critical">' + r.counts.critical + '</td>';
|
|
195
|
+
html += '<td class="severity-warning">' + r.counts.warning + '</td>';
|
|
196
|
+
html += '<td class="severity-suggestion">' + r.counts.suggestion + '</td>';
|
|
197
|
+
html += '</tr>';
|
|
198
|
+
}
|
|
199
|
+
html += '</tbody></table>';
|
|
200
|
+
}
|
|
201
|
+
html += '</div>';
|
|
202
|
+
|
|
203
|
+
dashboardContent.innerHTML = html;
|
|
204
|
+
|
|
205
|
+
if (hasData) {
|
|
206
|
+
drawTrendChart(trends);
|
|
207
|
+
drawSeverityChart(latestCounts);
|
|
208
|
+
drawModuleChart(latest ? latest.module : 'all');
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function drawTrendChart(trends) {
|
|
213
|
+
const canvas = document.getElementById('trendChart');
|
|
214
|
+
if (!canvas) return;
|
|
215
|
+
const ctx = canvas.getContext('2d');
|
|
216
|
+
const dpr = window.devicePixelRatio || 1;
|
|
217
|
+
const rect = canvas.getBoundingClientRect();
|
|
218
|
+
canvas.width = rect.width * dpr;
|
|
219
|
+
canvas.height = rect.height * dpr;
|
|
220
|
+
ctx.scale(dpr, dpr);
|
|
221
|
+
|
|
222
|
+
const W = rect.width, H = rect.height;
|
|
223
|
+
const pad = { t: 30, r: 30, b: 50, l: 50 };
|
|
224
|
+
const gw = W - pad.l - pad.r, gh = H - pad.t - pad.b;
|
|
225
|
+
|
|
226
|
+
const maxVal = Math.max(...trends.map(d => d.total), 1);
|
|
227
|
+
const getX = i => pad.l + (i / (trends.length - 1 || 1)) * gw;
|
|
228
|
+
const getY = v => pad.t + gh - (v / maxVal) * gh;
|
|
229
|
+
|
|
230
|
+
// Grid
|
|
231
|
+
ctx.strokeStyle = '#eee'; ctx.lineWidth = 1;
|
|
232
|
+
for (let i = 0; i <= 4; i++) {
|
|
233
|
+
const y = pad.t + (i / 4) * gh;
|
|
234
|
+
ctx.beginPath(); ctx.moveTo(pad.l, y); ctx.lineTo(W - pad.r, y); ctx.stroke();
|
|
235
|
+
ctx.fillStyle = '#999'; ctx.font = '12px sans-serif';
|
|
236
|
+
ctx.fillText(Math.round(maxVal * (1 - i / 4)), 5, y + 4);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Axes
|
|
240
|
+
ctx.strokeStyle = '#ddd';
|
|
241
|
+
ctx.beginPath(); ctx.moveTo(pad.l, pad.t); ctx.lineTo(pad.l, H - pad.b); ctx.stroke();
|
|
242
|
+
ctx.beginPath(); ctx.moveTo(pad.l, H - pad.b); ctx.lineTo(W - pad.r, H - pad.b); ctx.stroke();
|
|
243
|
+
|
|
244
|
+
// Lines
|
|
245
|
+
const colors = { critical: '#e74c3c', warning: '#f39c12', suggestion: '#3498db' };
|
|
246
|
+
['critical', 'warning', 'suggestion'].forEach(key => {
|
|
247
|
+
ctx.strokeStyle = colors[key]; ctx.lineWidth = 2;
|
|
248
|
+
ctx.beginPath();
|
|
249
|
+
trends.forEach((d, i) => {
|
|
250
|
+
const x = getX(i), y = getY(d[key]);
|
|
251
|
+
if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
|
|
252
|
+
});
|
|
253
|
+
ctx.stroke();
|
|
254
|
+
ctx.fillStyle = colors[key];
|
|
255
|
+
trends.forEach((d, i) => {
|
|
256
|
+
const x = getX(i), y = getY(d[key]);
|
|
257
|
+
ctx.beginPath(); ctx.arc(x, y, 3, 0, Math.PI * 2); ctx.fill();
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// X labels
|
|
262
|
+
ctx.fillStyle = '#999'; ctx.font = '11px sans-serif'; ctx.textAlign = 'center';
|
|
263
|
+
trends.forEach((d, i) => {
|
|
264
|
+
if (trends.length <= 10 || i % Math.ceil(trends.length / 10) === 0) {
|
|
265
|
+
const date = new Date(d.timestamp);
|
|
266
|
+
const label = String(date.getMonth()+1).padStart(2,'0') + '-' + String(date.getDate()).padStart(2,'0');
|
|
267
|
+
ctx.fillText(label, getX(i), H - pad.b + 20);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// Legend
|
|
272
|
+
const legend = [{c:'#e74c3c',l:'Critical'},{c:'#f39c12',l:'Warning'},{c:'#3498db',l:'Suggestion'}];
|
|
273
|
+
legend.forEach((item, i) => {
|
|
274
|
+
const lx = W - pad.r - 200 + i * 70;
|
|
275
|
+
ctx.fillStyle = item.c; ctx.fillRect(lx, 10, 12, 12);
|
|
276
|
+
ctx.fillStyle = '#666'; ctx.font = '12px sans-serif'; ctx.textAlign = 'left';
|
|
277
|
+
ctx.fillText(item.l, lx + 16, 21);
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function drawSeverityChart(counts) {
|
|
282
|
+
const canvas = document.getElementById('severityChart');
|
|
283
|
+
if (!canvas) return;
|
|
284
|
+
const ctx = canvas.getContext('2d');
|
|
285
|
+
const dpr = window.devicePixelRatio || 1;
|
|
286
|
+
const rect = canvas.getBoundingClientRect();
|
|
287
|
+
canvas.width = rect.width * dpr; canvas.height = rect.height * dpr;
|
|
288
|
+
ctx.scale(dpr, dpr);
|
|
289
|
+
const W = rect.width, H = rect.height;
|
|
290
|
+
const pad = { t: 30, r: 30, b: 40, l: 50 };
|
|
291
|
+
const gw = W - pad.l - pad.r, gh = H - pad.t - pad.b;
|
|
292
|
+
|
|
293
|
+
const bars = [
|
|
294
|
+
{ label: 'Critical', value: counts.critical, color: '#e74c3c' },
|
|
295
|
+
{ label: 'Warning', value: counts.warning, color: '#f39c12' },
|
|
296
|
+
{ label: 'Suggestion', value: counts.suggestion, color: '#3498db' },
|
|
297
|
+
];
|
|
298
|
+
const maxVal = Math.max(...bars.map(b => b.value), 1);
|
|
299
|
+
|
|
300
|
+
bars.forEach((bar, i) => {
|
|
301
|
+
const bw = gw / bars.length * 0.5;
|
|
302
|
+
const bx = pad.l + (i + 0.5) * (gw / bars.length) - bw / 2;
|
|
303
|
+
const bh = (bar.value / maxVal) * gh;
|
|
304
|
+
const by = pad.t + gh - bh;
|
|
305
|
+
ctx.fillStyle = bar.color;
|
|
306
|
+
ctx.fillRect(bx, by, bw, bh);
|
|
307
|
+
ctx.fillStyle = '#555'; ctx.font = '13px sans-serif'; ctx.textAlign = 'center';
|
|
308
|
+
ctx.fillText(bar.label, bx + bw / 2, H - pad.b + 20);
|
|
309
|
+
ctx.fillStyle = '#333'; ctx.font = 'bold 14px sans-serif';
|
|
310
|
+
ctx.fillText(bar.value, bx + bw / 2, by - 8);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
ctx.strokeStyle = '#eee'; ctx.lineWidth = 1;
|
|
314
|
+
for (let i = 0; i <= 4; i++) {
|
|
315
|
+
const y = pad.t + (i / 4) * gh;
|
|
316
|
+
ctx.beginPath(); ctx.moveTo(pad.l, y); ctx.lineTo(W - pad.r, y); ctx.stroke();
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function drawModuleChart(moduleName) {
|
|
321
|
+
const canvas = document.getElementById('moduleChart');
|
|
322
|
+
if (!canvas) return;
|
|
323
|
+
const ctx = canvas.getContext('2d');
|
|
324
|
+
const dpr = window.devicePixelRatio || 1;
|
|
325
|
+
const rect = canvas.getBoundingClientRect();
|
|
326
|
+
canvas.width = rect.width * dpr; canvas.height = rect.height * dpr;
|
|
327
|
+
ctx.scale(dpr, dpr);
|
|
328
|
+
const W = rect.width, H = rect.height;
|
|
329
|
+
|
|
330
|
+
// Simple pie showing just the module name since we aggregate per-module
|
|
331
|
+
const cx = W / 2, cy = H / 2, r = Math.min(W, H) / 2 - 40;
|
|
332
|
+
ctx.fillStyle = '#3498db';
|
|
333
|
+
ctx.beginPath(); ctx.arc(cx, cy, r, 0, Math.PI * 2); ctx.fill();
|
|
334
|
+
ctx.fillStyle = '#fff'; ctx.font = 'bold 16px sans-serif'; ctx.textAlign = 'center';
|
|
335
|
+
ctx.fillText(moduleName, cx, cy + 6);
|
|
336
|
+
ctx.fillStyle = '#999'; ctx.font = '12px sans-serif';
|
|
337
|
+
ctx.fillText('Module', cx, cy + 22);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Auto-refresh every 30 seconds
|
|
341
|
+
setInterval(() => {
|
|
342
|
+
if (currentProjectId) loadDashboard(currentProjectId);
|
|
343
|
+
}, 30000);
|
|
344
|
+
|
|
345
|
+
// Initial load
|
|
346
|
+
loadProjects();
|
|
347
|
+
</script>
|
|
348
|
+
</body>
|
|
349
|
+
</html>`;
|
|
350
|
+
}
|
|
351
|
+
//# sourceMappingURL=dashboard-html.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboard-html.js","sourceRoot":"","sources":["../../src/server/dashboard-html.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;AAEH,sDAkVC;AAlVD,SAAgB,qBAAqB;IACjC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAgVH,CAAC;AACT,CAAC"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Governance Dashboard Server
|
|
3
|
+
*
|
|
4
|
+
* v3.5.2: Central HTTP server for collecting and displaying
|
|
5
|
+
* multi-project scan results.
|
|
6
|
+
*
|
|
7
|
+
* Zero external dependencies: uses node:http and node:fs only.
|
|
8
|
+
*/
|
|
9
|
+
import { type Server } from "node:http";
|
|
10
|
+
import type { ScanResult, Issue } from "../types.js";
|
|
11
|
+
/** Project metadata stored on the server */
|
|
12
|
+
export interface DashboardProject {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
path: string;
|
|
16
|
+
createdAt: number;
|
|
17
|
+
lastScanAt: number;
|
|
18
|
+
reportCount: number;
|
|
19
|
+
}
|
|
20
|
+
/** A single scan report stored on the server */
|
|
21
|
+
export interface DashboardReport {
|
|
22
|
+
id: string;
|
|
23
|
+
projectId: string;
|
|
24
|
+
timestamp: number;
|
|
25
|
+
module: string;
|
|
26
|
+
result: ScanResult;
|
|
27
|
+
issues: Issue[];
|
|
28
|
+
git?: {
|
|
29
|
+
commit?: string;
|
|
30
|
+
branch?: string;
|
|
31
|
+
};
|
|
32
|
+
meta?: {
|
|
33
|
+
strategy?: string;
|
|
34
|
+
duration?: number;
|
|
35
|
+
filesScanned?: number;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** Server options */
|
|
39
|
+
export interface DashboardServerOptions {
|
|
40
|
+
/** Data directory (default: ~/.frontend-guardian-server) */
|
|
41
|
+
dataDir?: string;
|
|
42
|
+
/** CORS origin (default: no CORS headers) */
|
|
43
|
+
cors?: string;
|
|
44
|
+
/** Optional auth token for POST /api/reports */
|
|
45
|
+
authToken?: string;
|
|
46
|
+
}
|
|
47
|
+
/** POST /api/reports request body */
|
|
48
|
+
export interface ReportPayload {
|
|
49
|
+
projectName: string;
|
|
50
|
+
projectPath: string;
|
|
51
|
+
module: string;
|
|
52
|
+
result: ScanResult;
|
|
53
|
+
issues: Issue[];
|
|
54
|
+
git?: {
|
|
55
|
+
commit?: string;
|
|
56
|
+
branch?: string;
|
|
57
|
+
};
|
|
58
|
+
meta?: {
|
|
59
|
+
strategy?: string;
|
|
60
|
+
duration?: number;
|
|
61
|
+
filesScanned?: number;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** Trend data point */
|
|
65
|
+
export interface TrendPoint {
|
|
66
|
+
timestamp: number;
|
|
67
|
+
critical: number;
|
|
68
|
+
warning: number;
|
|
69
|
+
suggestion: number;
|
|
70
|
+
total: number;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Governance Dashboard HTTP Server
|
|
74
|
+
*
|
|
75
|
+
* Collects scan reports from multiple projects and serves
|
|
76
|
+
* a web dashboard for trend visualization.
|
|
77
|
+
*/
|
|
78
|
+
export declare class DashboardServer {
|
|
79
|
+
private dataDir;
|
|
80
|
+
private options;
|
|
81
|
+
private server;
|
|
82
|
+
private projectsFile;
|
|
83
|
+
constructor(options?: DashboardServerOptions);
|
|
84
|
+
/** Start the HTTP server */
|
|
85
|
+
start(port?: number): Promise<void>;
|
|
86
|
+
/** Stop the HTTP server */
|
|
87
|
+
stop(): Promise<void>;
|
|
88
|
+
/** Get the underlying http.Server instance (for testing) */
|
|
89
|
+
getServer(): Server | null;
|
|
90
|
+
/** Get the data directory path */
|
|
91
|
+
getDataDir(): string;
|
|
92
|
+
private handleRequest;
|
|
93
|
+
private handlePostReport;
|
|
94
|
+
private handleGetProjects;
|
|
95
|
+
private handleGetProject;
|
|
96
|
+
private handleGetReports;
|
|
97
|
+
private handleGetTrends;
|
|
98
|
+
private handleGetLatest;
|
|
99
|
+
private handleGetDashboard;
|
|
100
|
+
private ensureDir;
|
|
101
|
+
private loadProjects;
|
|
102
|
+
private saveProjects;
|
|
103
|
+
private countReports;
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=dashboard-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboard-server.d.ts","sourceRoot":"","sources":["../../src/server/dashboard-server.ts"],"names":[],"mappings":"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"}
|