@greenarmor/ges-web-dashboard 1.3.0 → 1.4.1
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/dist/index.d.ts +18 -1
- package/dist/index.js +216 -1
- package/dist/template.js +244 -3
- package/package.json +6 -5
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as http from "node:http";
|
|
2
|
-
import type { ScoreFile, Control, FixHistoryEntry, ActivityLogEntry } from "@greenarmor/ges-core";
|
|
2
|
+
import type { ScoreFile, Control, FixHistoryEntry, ActivityLogEntry, GovernanceRecord, GovernanceVerificationResult } from "@greenarmor/ges-core";
|
|
3
3
|
import type { Finding } from "@greenarmor/ges-audit-engine";
|
|
4
4
|
export interface DashboardOptions {
|
|
5
5
|
port?: number;
|
|
@@ -78,9 +78,26 @@ export interface DashboardData {
|
|
|
78
78
|
packs: PackSummary[];
|
|
79
79
|
fixHistory: FixHistoryEntry[];
|
|
80
80
|
activityLog: ActivityLogEntry[];
|
|
81
|
+
governance: GovernanceData;
|
|
81
82
|
lastAudit: string;
|
|
82
83
|
}
|
|
84
|
+
export interface GovernanceData {
|
|
85
|
+
records: GovernanceRecord[];
|
|
86
|
+
verifications: GovernanceVerificationResult[];
|
|
87
|
+
summary: {
|
|
88
|
+
total: number;
|
|
89
|
+
approved: number;
|
|
90
|
+
pending: number;
|
|
91
|
+
rejected: number;
|
|
92
|
+
expired: number;
|
|
93
|
+
validWithIssues: number;
|
|
94
|
+
criticalRisk: number;
|
|
95
|
+
highRisk: number;
|
|
96
|
+
totalEvidence: number;
|
|
97
|
+
};
|
|
98
|
+
}
|
|
83
99
|
export declare function collectDashboardData(projectPath: string): DashboardData;
|
|
84
100
|
export declare function collectPackDetail(projectPath: string, packId: string): PackDetailReport | null;
|
|
85
101
|
export declare function collectControlDetail(projectPath: string, controlId: string): ControlDetail | null;
|
|
102
|
+
export declare function collectGovernanceData(projectPath: string): GovernanceData;
|
|
86
103
|
export declare function startDashboard(options: DashboardOptions): http.Server;
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,9 @@ import { runAudit, deduplicateFindings } from "@greenarmor/ges-audit-engine";
|
|
|
5
5
|
import { getAllPacks, getPack } from "@greenarmor/ges-policy-engine";
|
|
6
6
|
import { generateScoreFile } from "@greenarmor/ges-scoring-engine";
|
|
7
7
|
import { loadFixHistory, loadActivityLog, loadControlsFromDisk, loadControlOverrides, applyOverridesToControls } from "@greenarmor/ges-core";
|
|
8
|
+
import { loadGovernanceRecords, verifyGovernanceRecord, verifyAllGovernanceRecords } from "@greenarmor/ges-core";
|
|
8
9
|
import { getInstalledPackIds as getInstalledPackIdsFromDisk } from "@greenarmor/ges-core";
|
|
10
|
+
import { generateMarkdownReport, generateHtmlReport } from "@greenarmor/ges-report-generator";
|
|
9
11
|
import { renderDashboard } from "./template.js";
|
|
10
12
|
function loadConfig(projectPath) {
|
|
11
13
|
const configPath = path.join(projectPath, ".ges", "config.json");
|
|
@@ -176,6 +178,7 @@ export function collectDashboardData(projectPath) {
|
|
|
176
178
|
const packs = allPacks.map(p => buildPackSummary(p, controls, findings, installedPacks));
|
|
177
179
|
const fixHistory = loadFixHistory(projectPath);
|
|
178
180
|
const activityLog = loadActivityLog(projectPath);
|
|
181
|
+
const governance = collectGovernanceData(projectPath);
|
|
179
182
|
const metadataPath = path.join(projectPath, ".ges", "metadata.json");
|
|
180
183
|
let lastAudit = "";
|
|
181
184
|
try {
|
|
@@ -190,13 +193,14 @@ export function collectDashboardData(projectPath) {
|
|
|
190
193
|
projectName: config?.project_name || "Unknown Project",
|
|
191
194
|
projectType: config?.project_type || "unknown",
|
|
192
195
|
frameworks: allFrameworks,
|
|
193
|
-
gesfVersion: "1.
|
|
196
|
+
gesfVersion: "1.4.1",
|
|
194
197
|
score,
|
|
195
198
|
controls,
|
|
196
199
|
findings,
|
|
197
200
|
packs,
|
|
198
201
|
fixHistory,
|
|
199
202
|
activityLog,
|
|
203
|
+
governance,
|
|
200
204
|
lastAudit,
|
|
201
205
|
};
|
|
202
206
|
}
|
|
@@ -317,6 +321,22 @@ export function collectControlDetail(projectPath, controlId) {
|
|
|
317
321
|
packName: matchingPack?.name || "",
|
|
318
322
|
};
|
|
319
323
|
}
|
|
324
|
+
export function collectGovernanceData(projectPath) {
|
|
325
|
+
const records = loadGovernanceRecords(projectPath);
|
|
326
|
+
const verifications = verifyAllGovernanceRecords(projectPath);
|
|
327
|
+
const summary = {
|
|
328
|
+
total: records.length,
|
|
329
|
+
approved: records.filter(r => r.status === "approved").length,
|
|
330
|
+
pending: records.filter(r => r.status === "draft" || r.status === "pending-review").length,
|
|
331
|
+
rejected: records.filter(r => r.status === "rejected" || r.status === "revoked").length,
|
|
332
|
+
expired: records.filter(r => r.status === "expired").length,
|
|
333
|
+
validWithIssues: verifications.filter(v => !v.valid && v.completeness.has_approval).length,
|
|
334
|
+
criticalRisk: records.filter(r => r.risk_level === "critical").length,
|
|
335
|
+
highRisk: records.filter(r => r.risk_level === "high").length,
|
|
336
|
+
totalEvidence: records.reduce((sum, r) => sum + r.evidence.length, 0),
|
|
337
|
+
};
|
|
338
|
+
return { records, verifications, summary };
|
|
339
|
+
}
|
|
320
340
|
function jsonResponse(res, data, status = 200) {
|
|
321
341
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
322
342
|
res.end(JSON.stringify(data));
|
|
@@ -394,6 +414,33 @@ export function startDashboard(options) {
|
|
|
394
414
|
}
|
|
395
415
|
return;
|
|
396
416
|
}
|
|
417
|
+
if (pathname === "/api/governance") {
|
|
418
|
+
try {
|
|
419
|
+
const data = collectDashboardData(options.projectPath);
|
|
420
|
+
jsonResponse(res, data.governance);
|
|
421
|
+
}
|
|
422
|
+
catch (err) {
|
|
423
|
+
jsonError(res, err instanceof Error ? err.message : String(err));
|
|
424
|
+
}
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
const governanceMatch = pathname.match(/^\/api\/governance\/(.+)$/);
|
|
428
|
+
if (governanceMatch) {
|
|
429
|
+
try {
|
|
430
|
+
const govData = collectGovernanceData(options.projectPath);
|
|
431
|
+
const record = govData.records.find(r => r.id === governanceMatch[1] || r.system_name.toLowerCase() === decodeURIComponent(governanceMatch[1]).toLowerCase());
|
|
432
|
+
if (!record) {
|
|
433
|
+
jsonError(res, `Governance record not found: ${governanceMatch[1]}`, 404);
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
const verification = verifyGovernanceRecord(record);
|
|
437
|
+
jsonResponse(res, { record, verification });
|
|
438
|
+
}
|
|
439
|
+
catch (err) {
|
|
440
|
+
jsonError(res, err instanceof Error ? err.message : String(err));
|
|
441
|
+
}
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
397
444
|
const packMatch = pathname.match(/^\/api\/packs\/([a-z0-9-]+)$/);
|
|
398
445
|
if (packMatch) {
|
|
399
446
|
try {
|
|
@@ -454,6 +501,63 @@ export function startDashboard(options) {
|
|
|
454
501
|
}
|
|
455
502
|
return;
|
|
456
503
|
}
|
|
504
|
+
const governanceDetailMatch = pathname.match(/^\/api\/governance\/(.+)$/);
|
|
505
|
+
if (governanceDetailMatch) {
|
|
506
|
+
try {
|
|
507
|
+
const govData = collectGovernanceData(options.projectPath);
|
|
508
|
+
const record = govData.records.find(r => r.id === governanceDetailMatch[1] || r.system_name.toLowerCase() === decodeURIComponent(governanceDetailMatch[1]).toLowerCase());
|
|
509
|
+
if (!record) {
|
|
510
|
+
jsonError(res, `Governance record not found: ${governanceDetailMatch[1]}`, 404);
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
const verification = verifyGovernanceRecord(record);
|
|
514
|
+
jsonResponse(res, { record, verification });
|
|
515
|
+
}
|
|
516
|
+
catch (err) {
|
|
517
|
+
jsonError(res, err instanceof Error ? err.message : String(err));
|
|
518
|
+
}
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
if (pathname === "/api/report/compliance") {
|
|
522
|
+
try {
|
|
523
|
+
const data = collectDashboardData(options.projectPath);
|
|
524
|
+
const format = url.searchParams.get("format") || "markdown";
|
|
525
|
+
const reportOptions = {
|
|
526
|
+
format,
|
|
527
|
+
title: `Compliance Report - ${data.projectName}`,
|
|
528
|
+
include_executive_summary: true,
|
|
529
|
+
include_risk_assessment: true,
|
|
530
|
+
include_compliance: true,
|
|
531
|
+
include_security: true,
|
|
532
|
+
};
|
|
533
|
+
if (format === "html") {
|
|
534
|
+
const html = generateHtmlReport(reportOptions, data.score, data.controls, data.findings);
|
|
535
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Content-Disposition": `attachment; filename="compliance-report.html"` });
|
|
536
|
+
res.end(html);
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
const md = generateMarkdownReport(reportOptions, data.score, data.controls, data.findings);
|
|
540
|
+
res.writeHead(200, { "Content-Type": "text/markdown; charset=utf-8", "Content-Disposition": `attachment; filename="compliance-report.md"` });
|
|
541
|
+
res.end(md);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
catch (err) {
|
|
545
|
+
jsonError(res, err instanceof Error ? err.message : String(err));
|
|
546
|
+
}
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
if (pathname === "/api/report/governance") {
|
|
550
|
+
try {
|
|
551
|
+
const govData = collectGovernanceData(options.projectPath);
|
|
552
|
+
const md = generateGovernanceMarkdownReport(govData, collectDashboardData(options.projectPath).projectName);
|
|
553
|
+
res.writeHead(200, { "Content-Type": "text/markdown; charset=utf-8", "Content-Disposition": `attachment; filename="governance-provenance-report.md"` });
|
|
554
|
+
res.end(md);
|
|
555
|
+
}
|
|
556
|
+
catch (err) {
|
|
557
|
+
jsonError(res, err instanceof Error ? err.message : String(err));
|
|
558
|
+
}
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
457
561
|
if (pathname === "/health") {
|
|
458
562
|
jsonResponse(res, { status: "ok", timestamp: new Date().toISOString() });
|
|
459
563
|
return;
|
|
@@ -464,3 +568,114 @@ export function startDashboard(options) {
|
|
|
464
568
|
server.listen(port, host);
|
|
465
569
|
return server;
|
|
466
570
|
}
|
|
571
|
+
function generateGovernanceMarkdownReport(data, projectName) {
|
|
572
|
+
const lines = [];
|
|
573
|
+
lines.push(`# Governance Provenance Report`);
|
|
574
|
+
lines.push(`\n**Project**: ${projectName}`);
|
|
575
|
+
lines.push(`**Generated**: ${new Date().toISOString()}\n`);
|
|
576
|
+
const s = data.summary;
|
|
577
|
+
lines.push(`## Summary\n`);
|
|
578
|
+
lines.push(`| Metric | Value |`);
|
|
579
|
+
lines.push(`|--------|-------|`);
|
|
580
|
+
lines.push(`| Total Systems | ${s.total} |`);
|
|
581
|
+
lines.push(`| Approved | ${s.approved} |`);
|
|
582
|
+
lines.push(`| Pending | ${s.pending} |`);
|
|
583
|
+
lines.push(`| Expired / With Issues | ${s.expired + s.validWithIssues} |`);
|
|
584
|
+
lines.push(`| Critical Risk | ${s.criticalRisk} |`);
|
|
585
|
+
lines.push(`| High Risk | ${s.highRisk} |`);
|
|
586
|
+
lines.push(`| Total Evidence References | ${s.totalEvidence} |`);
|
|
587
|
+
if (data.records.length === 0) {
|
|
588
|
+
lines.push(`\n_No governance records found._`);
|
|
589
|
+
return lines.join("\n");
|
|
590
|
+
}
|
|
591
|
+
for (let i = 0; i < data.records.length; i++) {
|
|
592
|
+
const r = data.records[i];
|
|
593
|
+
const v = data.verifications[i] || data.verifications.find(vv => vv.record_id === r.id);
|
|
594
|
+
lines.push(`\n---\n`);
|
|
595
|
+
lines.push(`## ${r.system_name}\n`);
|
|
596
|
+
lines.push(`| Field | Value |`);
|
|
597
|
+
lines.push(`|-------|-------|`);
|
|
598
|
+
lines.push(`| ID | ${r.id} |`);
|
|
599
|
+
lines.push(`| Type | ${r.system_type} |`);
|
|
600
|
+
lines.push(`| Version | ${r.system_version || "(none)"} |`);
|
|
601
|
+
lines.push(`| Status | ${r.status} |`);
|
|
602
|
+
lines.push(`| Risk Level | ${r.risk_level} |`);
|
|
603
|
+
if (v) {
|
|
604
|
+
lines.push(`| Verification | ${v.valid ? "VALID" : "ISSUES"} |`);
|
|
605
|
+
lines.push(`| Approval Status | ${v.approval_status} |`);
|
|
606
|
+
if (v.days_until_expiry !== null) {
|
|
607
|
+
lines.push(`| Days Until Expiry | ${v.days_until_expiry} |`);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
if (r.approval) {
|
|
611
|
+
const a = r.approval;
|
|
612
|
+
lines.push(`\n### Approval Decision\n`);
|
|
613
|
+
lines.push(`- **Approver**: ${a.approver_name} (${a.approver_role})`);
|
|
614
|
+
lines.push(`- **Authority**: ${a.approval_authority}`);
|
|
615
|
+
lines.push(`- **Decision**: ${a.decision.toUpperCase()}`);
|
|
616
|
+
lines.push(`- **Date**: ${a.decision_date}`);
|
|
617
|
+
lines.push(`- **Validity**: ${a.valid_from} → ${a.valid_until || "indefinite"}`);
|
|
618
|
+
if (a.conditions.length > 0)
|
|
619
|
+
lines.push(`- **Conditions**: ${a.conditions.join("; ")}`);
|
|
620
|
+
if (a.rationale)
|
|
621
|
+
lines.push(`- **Rationale**: ${a.rationale}`);
|
|
622
|
+
}
|
|
623
|
+
if (r.risk_assessment) {
|
|
624
|
+
const ra = r.risk_assessment;
|
|
625
|
+
lines.push(`\n### Risk Assessment\n`);
|
|
626
|
+
lines.push(`- **Assessor**: ${ra.assessor}`);
|
|
627
|
+
lines.push(`- **Methodology**: ${ra.methodology}`);
|
|
628
|
+
lines.push(`- **Risk Score**: ${ra.risk_score}`);
|
|
629
|
+
lines.push(`- **Residual Risk**: ${ra.residual_risk}`);
|
|
630
|
+
lines.push(`- **Date**: ${ra.assessment_date}`);
|
|
631
|
+
if (ra.identified_risks.length > 0)
|
|
632
|
+
lines.push(`- **Identified Risks**: ${ra.identified_risks.join(", ")}`);
|
|
633
|
+
}
|
|
634
|
+
if (r.policy_basis) {
|
|
635
|
+
const pb = r.policy_basis;
|
|
636
|
+
lines.push(`\n### Policy Basis\n`);
|
|
637
|
+
lines.push(`- **Policy**: ${pb.policy_name} (${pb.policy_id} v${pb.version})`);
|
|
638
|
+
lines.push(`- **Standard**: ${pb.standard}`);
|
|
639
|
+
if (pb.clauses.length > 0)
|
|
640
|
+
lines.push(`- **Clauses**: ${pb.clauses.join(", ")}`);
|
|
641
|
+
}
|
|
642
|
+
lines.push(`\n### Evidence Chain (${r.evidence.length})\n`);
|
|
643
|
+
if (r.evidence.length === 0) {
|
|
644
|
+
lines.push(`_No evidence references._`);
|
|
645
|
+
}
|
|
646
|
+
else {
|
|
647
|
+
lines.push(`| # | Title | Type | Source | Reference |`);
|
|
648
|
+
lines.push(`|---|-------|------|--------|-----------|`);
|
|
649
|
+
r.evidence.forEach((e, j) => {
|
|
650
|
+
lines.push(`| ${j + 1} | ${e.title} | ${e.type} | ${e.source_system} | ${e.reference} |`);
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
if (r.review_cycle) {
|
|
654
|
+
const rc = r.review_cycle;
|
|
655
|
+
lines.push(`\n### Review Cycle\n`);
|
|
656
|
+
lines.push(`- **Frequency**: ${rc.frequency}`);
|
|
657
|
+
lines.push(`- **Last Review**: ${rc.last_review}`);
|
|
658
|
+
lines.push(`- **Next Review**: ${rc.next_review}`);
|
|
659
|
+
}
|
|
660
|
+
if (r.committee) {
|
|
661
|
+
const c = r.committee;
|
|
662
|
+
lines.push(`\n### Committee Approval\n`);
|
|
663
|
+
lines.push(`- **Committee**: ${c.committee_name}`);
|
|
664
|
+
lines.push(`- **Meeting**: ${c.meeting_date} (${c.meeting_reference})`);
|
|
665
|
+
if (c.attendees.length > 0)
|
|
666
|
+
lines.push(`- **Attendees**: ${c.attendees.join(", ")}`);
|
|
667
|
+
}
|
|
668
|
+
if (v && v.issues.length > 0) {
|
|
669
|
+
lines.push(`\n### Blocking Issues\n`);
|
|
670
|
+
for (const iss of v.issues)
|
|
671
|
+
lines.push(`- ${iss}`);
|
|
672
|
+
}
|
|
673
|
+
if (v && v.warnings.length > 0) {
|
|
674
|
+
lines.push(`\n### Warnings\n`);
|
|
675
|
+
for (const w of v.warnings)
|
|
676
|
+
lines.push(`- ${w}`);
|
|
677
|
+
}
|
|
678
|
+
lines.push(`\n_Created: ${r.created_at} by ${r.created_by} | Updated: ${r.updated_at} by ${r.updated_by} (v${r.record_version})_`);
|
|
679
|
+
}
|
|
680
|
+
return lines.join("\n");
|
|
681
|
+
}
|
package/dist/template.js
CHANGED
|
@@ -15,6 +15,8 @@ function matchPackForControl(controlId, packs) {
|
|
|
15
15
|
return p;
|
|
16
16
|
if (p.id === "government" && idUpper.startsWith("GOV-"))
|
|
17
17
|
return p;
|
|
18
|
+
if (p.id === "governance" && idUpper.startsWith("GOVP-"))
|
|
19
|
+
return p;
|
|
18
20
|
if (p.id === "iso27001" && idUpper.startsWith("ISO27K-"))
|
|
19
21
|
return p;
|
|
20
22
|
if (p.id === "iso27701" && idUpper.startsWith("ISO277-"))
|
|
@@ -71,6 +73,7 @@ export function renderDashboard(data) {
|
|
|
71
73
|
const findings = data.findings;
|
|
72
74
|
const controls = data.controls;
|
|
73
75
|
const packs = data.packs;
|
|
76
|
+
const governance = data.governance;
|
|
74
77
|
const findingsBySeverity = {
|
|
75
78
|
critical: findings.filter(f => f.severity === "critical").length,
|
|
76
79
|
high: findings.filter(f => f.severity === "high").length,
|
|
@@ -119,6 +122,8 @@ export function renderDashboard(data) {
|
|
|
119
122
|
return idUpper.startsWith("BC-");
|
|
120
123
|
if (packId === "government")
|
|
121
124
|
return idUpper.startsWith("GOV-");
|
|
125
|
+
if (packId === "governance")
|
|
126
|
+
return idUpper.startsWith("GOVP-");
|
|
122
127
|
if (packId === "iso27001")
|
|
123
128
|
return idUpper.startsWith("ISO27K-");
|
|
124
129
|
if (packId === "iso27701")
|
|
@@ -327,12 +332,18 @@ export function renderDashboard(data) {
|
|
|
327
332
|
<button class="nav-tab" onclick="showPage('findings', this)">Findings</button>
|
|
328
333
|
<button class="nav-tab" onclick="showPage('traceability', this)">Traceability</button>
|
|
329
334
|
<button class="nav-tab" onclick="showPage('activity', this)">Activity Log</button>
|
|
335
|
+
<button class="nav-tab" onclick="showPage('governance', this)">Governance</button>
|
|
330
336
|
</div>
|
|
331
337
|
</div>
|
|
332
338
|
|
|
333
339
|
<div class="container">
|
|
334
340
|
|
|
335
341
|
<div id="page-overview" class="page active">
|
|
342
|
+
<div style="display:flex;justify-content:flex-end;gap:8px;margin-bottom:12px;">
|
|
343
|
+
<a href="/api/report/compliance?format=markdown" class="report-btn" style="background:#0f766e;color:white;padding:6px 14px;border-radius:6px;text-decoration:none;font-size:12px;font-weight:600;">📄 Compliance Report (MD)</a>
|
|
344
|
+
<a href="/api/report/compliance?format=html" class="report-btn" style="background:#0f766e;color:white;padding:6px 14px;border-radius:6px;text-decoration:none;font-size:12px;font-weight:600;">📄 Compliance Report (HTML)</a>
|
|
345
|
+
<a href="/api/report/governance" class="report-btn" style="background:#6366f1;color:white;padding:6px 14px;border-radius:6px;text-decoration:none;font-size:12px;font-weight:600;">📋 Governance Report</a>
|
|
346
|
+
</div>
|
|
336
347
|
<div class="grid">
|
|
337
348
|
<div class="grid grid-3">
|
|
338
349
|
<div class="card stat">
|
|
@@ -637,12 +648,16 @@ export function renderDashboard(data) {
|
|
|
637
648
|
${renderActivityLogSection(data.activityLog || [])}
|
|
638
649
|
</div>
|
|
639
650
|
|
|
651
|
+
<div id="page-governance" class="page">
|
|
652
|
+
${renderGovernanceSection(governance)}
|
|
653
|
+
</div>
|
|
654
|
+
|
|
640
655
|
<div id="control-detail-modal" style="display:none;"></div>
|
|
641
656
|
|
|
642
657
|
</div>
|
|
643
658
|
|
|
644
659
|
<div class="footer">
|
|
645
|
-
Generated by GESF v${escapeHtml(data.gesfVersion)} | Last audit: ${escapeHtml(new Date(data.lastAudit).toLocaleString())} | <a href="/api/data">JSON API</a> | <a href="/api/packs">Packs API</a> | <a href="/api/fix-history">Fix History API</a> | <a href="/api/activity-log">Activity Log API</a>
|
|
660
|
+
Generated by GESF v${escapeHtml(data.gesfVersion)} | Last audit: ${escapeHtml(new Date(data.lastAudit).toLocaleString())} | <a href="/api/data">JSON API</a> | <a href="/api/packs">Packs API</a> | <a href="/api/fix-history">Fix History API</a> | <a href="/api/activity-log">Activity Log API</a> | <a href="/api/governance">Governance API</a>
|
|
646
661
|
</div>
|
|
647
662
|
|
|
648
663
|
<script>
|
|
@@ -663,7 +678,7 @@ export function renderDashboard(data) {
|
|
|
663
678
|
}
|
|
664
679
|
};
|
|
665
680
|
|
|
666
|
-
var navTabMap = { overview: 0, packs: 1, fixes: 2, findings: 3, traceability: 4, activity: 5 };
|
|
681
|
+
var navTabMap = { overview: 0, packs: 1, fixes: 2, findings: 3, traceability: 4, activity: 5, governance: 6 };
|
|
667
682
|
|
|
668
683
|
window.navigateToPage = function(page) {
|
|
669
684
|
var tabs = document.querySelectorAll('.nav-tab');
|
|
@@ -1065,6 +1080,8 @@ function renderDetailedFixesList(findings, controls, packs) {
|
|
|
1065
1080
|
return idUpper.startsWith("BC-");
|
|
1066
1081
|
if (p.id === "government")
|
|
1067
1082
|
return idUpper.startsWith("GOV-");
|
|
1083
|
+
if (p.id === "governance")
|
|
1084
|
+
return idUpper.startsWith("GOVP-");
|
|
1068
1085
|
if (p.id === "iso27001")
|
|
1069
1086
|
return idUpper.startsWith("ISO27K-");
|
|
1070
1087
|
if (p.id === "iso27701")
|
|
@@ -1288,6 +1305,9 @@ function renderFixHistorySection(entries, complianceIssues = []) {
|
|
|
1288
1305
|
html += `<tr><td style="font-weight:600;">Description</td><td>${escapeHtml(entry.fix.description)}</td></tr>`;
|
|
1289
1306
|
html += `<tr><td style="font-weight:600;">Status</td><td>${entry.fix.applied ? '<span style="color:#22c55e;font-weight:600;">Applied successfully</span>' : `<span style="color:#ef4444;font-weight:600;">Failed: ${escapeHtml(entry.fix.error || 'Unknown error')}</span>`}</td></tr>`;
|
|
1290
1307
|
html += `<tr><td style="font-weight:600;">Source</td><td>${entry.source === 'mcp' ? 'MCP auto_fix tool' : 'CLI ges fix command'}</td></tr>`;
|
|
1308
|
+
if (entry.actor_name) {
|
|
1309
|
+
html += `<tr><td style="font-weight:600;">Actor</td><td>${escapeHtml(entry.actor_name)}${entry.actor_role ? ' (' + escapeHtml(entry.actor_role) + ')' : ''}</td></tr>`;
|
|
1310
|
+
}
|
|
1291
1311
|
html += `<tr><td style="font-weight:600;">Timestamp</td><td>${new Date(entry.timestamp).toLocaleString()}</td></tr>`;
|
|
1292
1312
|
if (entry.dry_run) {
|
|
1293
1313
|
html += `<tr><td style="font-weight:600;">Mode</td><td><span class="badge" style="background:#eab308;color:white;">DRY RUN</span></td></tr>`;
|
|
@@ -1501,12 +1521,15 @@ function renderActivityLogSection(entries) {
|
|
|
1501
1521
|
html += `</div></div>`;
|
|
1502
1522
|
html += `</div>`;
|
|
1503
1523
|
html += `<div class="card"><div class="card-title">Timeline (newest first)</div>`;
|
|
1504
|
-
html += `<table><thead><tr><th>Time</th><th>Source</th><th>Action</th><th>Status</th><th>Description</th><th>Impact</th></tr></thead><tbody>`;
|
|
1524
|
+
html += `<table><thead><tr><th>Time</th><th>Source</th><th>Actor</th><th>Action</th><th>Status</th><th>Description</th><th>Impact</th></tr></thead><tbody>`;
|
|
1505
1525
|
for (const entry of sorted) {
|
|
1506
1526
|
const time = new Date(entry.timestamp).toLocaleString();
|
|
1507
1527
|
const sourceBadge = entry.source === "mcp"
|
|
1508
1528
|
? '<span class="badge" style="background:#7c3aed;font-size:10px;">MCP</span>'
|
|
1509
1529
|
: '<span class="badge" style="background:#0f766e;font-size:10px;">CLI</span>';
|
|
1530
|
+
const actorHtml = entry.actor_name
|
|
1531
|
+
? `<div style="font-size:12px;font-weight:600;">${escapeHtml(entry.actor_name)}</div>${entry.actor_role ? '<div style="font-size:11px;color:#6b7280;">' + escapeHtml(entry.actor_role) + '</div>' : ''}`
|
|
1532
|
+
: '<span style="color:#9ca3af;font-size:11px;">-</span>';
|
|
1510
1533
|
const actionLabel = actionLabels[entry.action] || entry.action;
|
|
1511
1534
|
const actionColor = actionColors[entry.action] || "#6b7280";
|
|
1512
1535
|
const actionBadge = `<span class="badge" style="background:${actionColor};font-size:10px;">${escapeHtml(actionLabel)}</span>`;
|
|
@@ -1532,6 +1555,7 @@ function renderActivityLogSection(entries) {
|
|
|
1532
1555
|
html += `<tr>
|
|
1533
1556
|
<td style="font-size:11px;white-space:nowrap;">${time}</td>
|
|
1534
1557
|
<td>${sourceBadge}</td>
|
|
1558
|
+
<td>${actorHtml}</td>
|
|
1535
1559
|
<td>${actionBadge}</td>
|
|
1536
1560
|
<td>${statusBadge}</td>
|
|
1537
1561
|
<td>
|
|
@@ -1544,6 +1568,223 @@ function renderActivityLogSection(entries) {
|
|
|
1544
1568
|
html += `</tbody></table></div>`;
|
|
1545
1569
|
return html;
|
|
1546
1570
|
}
|
|
1571
|
+
function govStatusColor(status) {
|
|
1572
|
+
const m = {
|
|
1573
|
+
approved: "#22c55e",
|
|
1574
|
+
conditional: "#eab308",
|
|
1575
|
+
"pending-review": "#3b82f6",
|
|
1576
|
+
draft: "#6b7280",
|
|
1577
|
+
rejected: "#ef4444",
|
|
1578
|
+
revoked: "#ef4444",
|
|
1579
|
+
expired: "#f97316",
|
|
1580
|
+
};
|
|
1581
|
+
return m[status] || "#6b7280";
|
|
1582
|
+
}
|
|
1583
|
+
function govRiskColor(level) {
|
|
1584
|
+
const m = {
|
|
1585
|
+
low: "#22c55e",
|
|
1586
|
+
medium: "#eab308",
|
|
1587
|
+
high: "#f97316",
|
|
1588
|
+
critical: "#ef4444",
|
|
1589
|
+
};
|
|
1590
|
+
return m[level] || "#6b7280";
|
|
1591
|
+
}
|
|
1592
|
+
function renderGovernanceSection(data) {
|
|
1593
|
+
const { records, verifications, summary } = data;
|
|
1594
|
+
if (records.length === 0) {
|
|
1595
|
+
return `<div class="card">
|
|
1596
|
+
<div class="card-title">Governance Provenance Chain</div>
|
|
1597
|
+
<div class="empty-state">
|
|
1598
|
+
<div class="icon">📋</div>
|
|
1599
|
+
<div class="msg">No governance records found</div>
|
|
1600
|
+
<div class="sub">Create records using <code>ges governance add</code> or the MCP <code>create_governance_record</code> tool</div>
|
|
1601
|
+
<div class="sub" style="margin-top:12px;">The governance tab provides end-to-end traceability for auditors:<br>
|
|
1602
|
+
System → Risk Assessment → Policy → Approval → Evidence → Review Cycle</div>
|
|
1603
|
+
</div>
|
|
1604
|
+
</div>`;
|
|
1605
|
+
}
|
|
1606
|
+
let html = "";
|
|
1607
|
+
html += `<div style="margin-bottom:20px;">`;
|
|
1608
|
+
html += `<h2 style="font-size:20px;font-weight:700;margin-bottom:4px;">Governance Provenance Chain</h2>`;
|
|
1609
|
+
html += `<p style="color:#6b7280;font-size:14px;margin-bottom:16px;">End-to-end approval traceability for auditors and regulators. Each record links: System → Risk Assessment → Policy → Approval → Evidence → Review Cycle.</p>`;
|
|
1610
|
+
html += `<div style="margin-bottom:12px;"><a href="/api/report/governance" style="background:#6366f1;color:white;padding:6px 14px;border-radius:6px;text-decoration:none;font-size:12px;font-weight:600;">📋 Export Provenance Report</a></div>`;
|
|
1611
|
+
html += `<div class="grid grid-4" style="margin-bottom:20px;">`;
|
|
1612
|
+
html += `<div class="card stat"><div class="num">${summary.total}</div><div class="label">Total Systems</div></div>`;
|
|
1613
|
+
html += `<div class="card stat"><div class="num" style="color:#22c55e;">${summary.approved}</div><div class="label">Approved</div></div>`;
|
|
1614
|
+
html += `<div class="card stat"><div class="num" style="color:#3b82f6;">${summary.pending}</div><div class="label">Pending</div></div>`;
|
|
1615
|
+
html += `<div class="card stat"><div class="num" style="color:#f97316;">${summary.expired + summary.validWithIssues}</div><div class="label">Expired / Issues</div></div>`;
|
|
1616
|
+
html += `</div>`;
|
|
1617
|
+
html += `</div>`;
|
|
1618
|
+
if (summary.criticalRisk > 0 || summary.highRisk > 0) {
|
|
1619
|
+
html += `<div class="card" style="margin-bottom:20px;border-left:4px solid #ef4444;">`;
|
|
1620
|
+
html += `<div class="card-title" style="color:#ef4444;">High-Risk Systems</div>`;
|
|
1621
|
+
html += `<div style="display:flex;gap:16px;font-size:14px;">`;
|
|
1622
|
+
html += `<span><span style="color:#ef4444;font-weight:700;">${summary.criticalRisk}</span> critical risk</span>`;
|
|
1623
|
+
html += `<span><span style="color:#f97316;font-weight:700;">${summary.highRisk}</span> high risk</span>`;
|
|
1624
|
+
html += `</div></div>`;
|
|
1625
|
+
}
|
|
1626
|
+
html += `<div id="governance-records-list">`;
|
|
1627
|
+
for (let i = 0; i < records.length; i++) {
|
|
1628
|
+
const r = records[i];
|
|
1629
|
+
const v = verifications[i] || verifications.find(vv => vv.record_id === r.id);
|
|
1630
|
+
const statusBg = govStatusColor(r.status);
|
|
1631
|
+
const riskBg = govRiskColor(r.risk_level);
|
|
1632
|
+
html += `<div class="fix-detail-card" style="margin-bottom:12px;">`;
|
|
1633
|
+
html += `<div class="fix-detail-header" onclick="toggleFix('gov-${i}')" style="cursor:pointer;">`;
|
|
1634
|
+
html += `<div class="fix-detail-info" style="flex:1;">`;
|
|
1635
|
+
html += `<div class="fix-detail-title">${escapeHtml(r.system_name)}</div>`;
|
|
1636
|
+
html += `<div class="fix-detail-meta">${escapeHtml(r.system_type)} | ${escapeHtml(r.id)}${r.system_version ? ' | v' + escapeHtml(r.system_version) : ''}</div>`;
|
|
1637
|
+
html += `</div>`;
|
|
1638
|
+
html += `<div class="fix-detail-badges">`;
|
|
1639
|
+
html += `<span class="badge badge-sev" style="background:${statusBg};font-size:10px;">${r.status.toUpperCase()}</span>`;
|
|
1640
|
+
html += `<span class="badge badge-sev" style="background:${riskBg};font-size:10px;">${r.risk_level.toUpperCase()} RISK</span>`;
|
|
1641
|
+
if (v) {
|
|
1642
|
+
html += `<span class="badge badge-sev" style="background:${v.valid ? '#22c55e' : '#ef4444'};font-size:10px;">${v.valid ? '✓ VALID' : '✗ ISSUES'}</span>`;
|
|
1643
|
+
}
|
|
1644
|
+
html += `<span class="fix-toggle" id="gov-${i}-toggle">Expand</span>`;
|
|
1645
|
+
html += `</div>`;
|
|
1646
|
+
html += `</div>`;
|
|
1647
|
+
html += `<div class="fix-detail-body" id="gov-${i}">`;
|
|
1648
|
+
if (v) {
|
|
1649
|
+
html += `<div class="fix-section"><div class="fix-section-title">Verification Checklist</div>`;
|
|
1650
|
+
html += `<ul class="check-list">`;
|
|
1651
|
+
const checks = [
|
|
1652
|
+
[v.completeness.has_approval, "Approval Decision"],
|
|
1653
|
+
[v.completeness.has_risk_assessment, "Risk Assessment"],
|
|
1654
|
+
[v.completeness.has_policy_basis, "Policy Basis"],
|
|
1655
|
+
[v.completeness.has_evidence, `Evidence Chain (${v.completeness.evidence_count} refs)`],
|
|
1656
|
+
[v.completeness.has_review_cycle, "Review Cycle"],
|
|
1657
|
+
[v.completeness.has_data_inventory, "Data Inventory"],
|
|
1658
|
+
[v.completeness.has_compliance_links, "Compliance Links"],
|
|
1659
|
+
[v.completeness.is_current, "Currently Valid"],
|
|
1660
|
+
];
|
|
1661
|
+
for (const [ok, label] of checks) {
|
|
1662
|
+
const icon = ok ? "✓" : "✗";
|
|
1663
|
+
const bg = ok ? "#22c55e" : "#ef4444";
|
|
1664
|
+
html += `<li><span class="check-icon" style="background:${bg};">${icon}</span> <span>${label}</span></li>`;
|
|
1665
|
+
}
|
|
1666
|
+
html += `</ul>`;
|
|
1667
|
+
if (v.approval_status !== "none") {
|
|
1668
|
+
html += `<div style="margin-top:8px;font-size:13px;">`;
|
|
1669
|
+
html += `<strong>Approval Status:</strong> `;
|
|
1670
|
+
const apColor = v.approval_status === "valid" ? "#22c55e" : v.approval_status === "expired" ? "#ef4444" : "#eab308";
|
|
1671
|
+
html += `<span style="color:${apColor};font-weight:600;">${v.approval_status.toUpperCase()}</span>`;
|
|
1672
|
+
if (v.days_until_expiry !== null) {
|
|
1673
|
+
const dayText = v.days_until_expiry < 0
|
|
1674
|
+
? `${Math.abs(v.days_until_expiry)} days ago (EXPIRED)`
|
|
1675
|
+
: `${v.days_until_expiry} days remaining`;
|
|
1676
|
+
html += ` — ${dayText}`;
|
|
1677
|
+
}
|
|
1678
|
+
html += `</div>`;
|
|
1679
|
+
}
|
|
1680
|
+
if (v.issues.length > 0) {
|
|
1681
|
+
html += `<div style="margin-top:8px;"><strong style="color:#ef4444;">Blocking Issues:</strong><ul style="margin-top:4px;">`;
|
|
1682
|
+
for (const iss of v.issues) {
|
|
1683
|
+
html += `<li style="font-size:13px;color:#ef4444;">${escapeHtml(iss)}</li>`;
|
|
1684
|
+
}
|
|
1685
|
+
html += `</ul></div>`;
|
|
1686
|
+
}
|
|
1687
|
+
if (v.warnings.length > 0) {
|
|
1688
|
+
html += `<div style="margin-top:4px;"><strong style="color:#eab308;">Warnings:</strong><ul style="margin-top:4px;">`;
|
|
1689
|
+
for (const w of v.warnings) {
|
|
1690
|
+
html += `<li style="font-size:13px;color:#92400e;">${escapeHtml(w)}</li>`;
|
|
1691
|
+
}
|
|
1692
|
+
html += `</ul></div>`;
|
|
1693
|
+
}
|
|
1694
|
+
html += `</div>`;
|
|
1695
|
+
}
|
|
1696
|
+
html += `<div class="fix-section"><div class="fix-section-title">Approval Decision</div>`;
|
|
1697
|
+
if (r.approval) {
|
|
1698
|
+
const a = r.approval;
|
|
1699
|
+
html += `<div style="font-size:13px;line-height:1.8;">`;
|
|
1700
|
+
html += `<div><strong>Approver:</strong> ${escapeHtml(a.approver_name)} (${escapeHtml(a.approver_role)})</div>`;
|
|
1701
|
+
html += `<div><strong>Authority:</strong> ${escapeHtml(a.approval_authority)}</div>`;
|
|
1702
|
+
html += `<div><strong>Decision:</strong> <span style="color:${a.decision === "approved" ? "#22c55e" : "#ef4444"};font-weight:600;">${a.decision.toUpperCase()}</span></div>`;
|
|
1703
|
+
html += `<div><strong>Date:</strong> ${escapeHtml(a.decision_date)}</div>`;
|
|
1704
|
+
html += `<div><strong>Validity:</strong> ${escapeHtml(a.valid_from)} → ${escapeHtml(a.valid_until || "indefinite")}</div>`;
|
|
1705
|
+
if (a.conditions.length > 0) {
|
|
1706
|
+
html += `<div><strong>Conditions:</strong> ${a.conditions.map(c => escapeHtml(c)).join("; ")}</div>`;
|
|
1707
|
+
}
|
|
1708
|
+
if (a.rationale) {
|
|
1709
|
+
html += `<div><strong>Rationale:</strong> ${escapeHtml(a.rationale)}</div>`;
|
|
1710
|
+
}
|
|
1711
|
+
html += `</div>`;
|
|
1712
|
+
}
|
|
1713
|
+
else {
|
|
1714
|
+
html += `<div style="color:#ef4444;font-size:13px;">⚠ NOT RECORDED</div>`;
|
|
1715
|
+
}
|
|
1716
|
+
html += `</div>`;
|
|
1717
|
+
if (r.risk_assessment) {
|
|
1718
|
+
const ra = r.risk_assessment;
|
|
1719
|
+
html += `<div class="fix-section"><div class="fix-section-title">Risk Assessment</div>`;
|
|
1720
|
+
html += `<div style="font-size:13px;line-height:1.8;">`;
|
|
1721
|
+
html += `<div><strong>Assessor:</strong> ${escapeHtml(ra.assessor)}</div>`;
|
|
1722
|
+
html += `<div><strong>Methodology:</strong> ${escapeHtml(ra.methodology)}</div>`;
|
|
1723
|
+
html += `<div><strong>Risk Score:</strong> ${escapeHtml(ra.risk_score)} — <strong>Residual:</strong> ${escapeHtml(ra.residual_risk)}</div>`;
|
|
1724
|
+
html += `<div><strong>Date:</strong> ${escapeHtml(ra.assessment_date)}</div>`;
|
|
1725
|
+
if (ra.identified_risks.length > 0) {
|
|
1726
|
+
html += `<div><strong>Identified Risks:</strong> ${ra.identified_risks.map(r => escapeHtml(r)).join(", ")}</div>`;
|
|
1727
|
+
}
|
|
1728
|
+
html += `</div>`;
|
|
1729
|
+
html += `</div>`;
|
|
1730
|
+
}
|
|
1731
|
+
if (r.policy_basis) {
|
|
1732
|
+
const pb = r.policy_basis;
|
|
1733
|
+
html += `<div class="fix-section"><div class="fix-section-title">Policy Basis</div>`;
|
|
1734
|
+
html += `<div style="font-size:13px;line-height:1.8;">`;
|
|
1735
|
+
html += `<div><strong>Policy:</strong> ${escapeHtml(pb.policy_name)} (${escapeHtml(pb.policy_id)} v${escapeHtml(pb.version)})</div>`;
|
|
1736
|
+
html += `<div><strong>Standard:</strong> ${escapeHtml(pb.standard)}</div>`;
|
|
1737
|
+
if (pb.clauses.length > 0) {
|
|
1738
|
+
html += `<div><strong>Clauses:</strong> ${pb.clauses.map(c => escapeHtml(c)).join(", ")}</div>`;
|
|
1739
|
+
}
|
|
1740
|
+
html += `</div>`;
|
|
1741
|
+
html += `</div>`;
|
|
1742
|
+
}
|
|
1743
|
+
html += `<div class="fix-section"><div class="fix-section-title">Evidence Chain (${r.evidence.length})</div>`;
|
|
1744
|
+
if (r.evidence.length === 0) {
|
|
1745
|
+
html += `<div style="color:#ef4444;font-size:13px;">⚠ NO EVIDENCE REFERENCES</div>`;
|
|
1746
|
+
}
|
|
1747
|
+
else {
|
|
1748
|
+
html += `<table><thead><tr><th>Title</th><th>Type</th><th>Source</th><th>Reference</th></tr></thead><tbody>`;
|
|
1749
|
+
for (const e of r.evidence) {
|
|
1750
|
+
html += `<tr>`;
|
|
1751
|
+
html += `<td>${escapeHtml(e.title)}</td>`;
|
|
1752
|
+
html += `<td style="font-size:12px;">${escapeHtml(e.type)}</td>`;
|
|
1753
|
+
html += `<td><span class="badge" style="background:#6b7280;color:white;font-size:10px;padding:2px 8px;">${escapeHtml(e.source_system)}</span></td>`;
|
|
1754
|
+
html += `<td style="font-family:monospace;font-size:11px;">${escapeHtml(e.reference)}</td>`;
|
|
1755
|
+
html += `</tr>`;
|
|
1756
|
+
}
|
|
1757
|
+
html += `</tbody></table>`;
|
|
1758
|
+
}
|
|
1759
|
+
html += `</div>`;
|
|
1760
|
+
if (r.review_cycle) {
|
|
1761
|
+
const rc = r.review_cycle;
|
|
1762
|
+
html += `<div class="fix-section"><div class="fix-section-title">Review Cycle</div>`;
|
|
1763
|
+
html += `<div style="font-size:13px;line-height:1.8;">`;
|
|
1764
|
+
html += `<div><strong>Frequency:</strong> ${escapeHtml(rc.frequency)}</div>`;
|
|
1765
|
+
html += `<div><strong>Last Review:</strong> ${escapeHtml(rc.last_review)} | <strong>Next:</strong> ${escapeHtml(rc.next_review)}</div>`;
|
|
1766
|
+
html += `</div>`;
|
|
1767
|
+
html += `</div>`;
|
|
1768
|
+
}
|
|
1769
|
+
if (r.committee) {
|
|
1770
|
+
const c = r.committee;
|
|
1771
|
+
html += `<div class="fix-section"><div class="fix-section-title">Committee Approval</div>`;
|
|
1772
|
+
html += `<div style="font-size:13px;line-height:1.8;">`;
|
|
1773
|
+
html += `<div><strong>Committee:</strong> ${escapeHtml(c.committee_name)}</div>`;
|
|
1774
|
+
html += `<div><strong>Meeting:</strong> ${escapeHtml(c.meeting_date)} (${escapeHtml(c.meeting_reference)})</div>`;
|
|
1775
|
+
if (c.attendees.length > 0) {
|
|
1776
|
+
html += `<div><strong>Attendees:</strong> ${c.attendees.map(a => escapeHtml(a)).join(", ")}</div>`;
|
|
1777
|
+
}
|
|
1778
|
+
html += `</div>`;
|
|
1779
|
+
html += `</div>`;
|
|
1780
|
+
}
|
|
1781
|
+
html += `<div style="margin-top:8px;font-size:11px;color:#9ca3af;">Created: ${escapeHtml(r.created_at)} by ${escapeHtml(r.created_by)} | Updated: ${escapeHtml(r.updated_at)} (v${r.record_version})</div>`;
|
|
1782
|
+
html += `</div>`;
|
|
1783
|
+
html += `</div>`;
|
|
1784
|
+
}
|
|
1785
|
+
html += `</div>`;
|
|
1786
|
+
return html;
|
|
1787
|
+
}
|
|
1547
1788
|
function escapeHtml(str) {
|
|
1548
1789
|
return str
|
|
1549
1790
|
.replace(/&/g, "&")
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"dependencies": {
|
|
3
|
-
"@greenarmor/ges-audit-engine": "1.
|
|
4
|
-
"@greenarmor/ges-core": "1.
|
|
5
|
-
"@greenarmor/ges-policy-engine": "1.
|
|
6
|
-
"@greenarmor/ges-
|
|
3
|
+
"@greenarmor/ges-audit-engine": "1.4.1",
|
|
4
|
+
"@greenarmor/ges-core": "1.4.1",
|
|
5
|
+
"@greenarmor/ges-policy-engine": "1.4.1",
|
|
6
|
+
"@greenarmor/ges-report-generator": "1.4.1",
|
|
7
|
+
"@greenarmor/ges-scoring-engine": "1.4.1"
|
|
7
8
|
},
|
|
8
9
|
"description": "GESF Web Dashboard - Visual compliance dashboard for teams",
|
|
9
10
|
"devDependencies": {
|
|
@@ -40,7 +41,7 @@
|
|
|
40
41
|
},
|
|
41
42
|
"type": "module",
|
|
42
43
|
"types": "./dist/index.d.ts",
|
|
43
|
-
"version": "1.
|
|
44
|
+
"version": "1.4.1",
|
|
44
45
|
"scripts": {
|
|
45
46
|
"build": "tsc",
|
|
46
47
|
"clean": "rm -rf dist tsconfig.tsbuildinfo",
|