@greenarmor/ges-web-dashboard 1.4.1 → 1.4.3
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.js +189 -3
- package/dist/template.js +208 -3
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ 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
|
+
import { loadGovernanceRecords, verifyGovernanceRecord, verifyAllGovernanceRecords, createGovernanceRecord, addGovernanceRecord, findGovernanceRecord, setGovernanceApproval, addGovernanceEvidence, createEvidenceRef, deleteGovernanceRecord, setGovernanceRiskAssessment, setGovernancePolicyBasis, setGovernanceReviewCycle, setGovernanceDataInventory, setGovernanceComplianceLinks, setGovernanceCommittee, recordActivity, } from "@greenarmor/ges-core";
|
|
9
9
|
import { getInstalledPackIds as getInstalledPackIdsFromDisk } from "@greenarmor/ges-core";
|
|
10
10
|
import { generateMarkdownReport, generateHtmlReport } from "@greenarmor/ges-report-generator";
|
|
11
11
|
import { renderDashboard } from "./template.js";
|
|
@@ -193,7 +193,7 @@ export function collectDashboardData(projectPath) {
|
|
|
193
193
|
projectName: config?.project_name || "Unknown Project",
|
|
194
194
|
projectType: config?.project_type || "unknown",
|
|
195
195
|
frameworks: allFrameworks,
|
|
196
|
-
gesfVersion: "1.4.
|
|
196
|
+
gesfVersion: "1.4.3",
|
|
197
197
|
score,
|
|
198
198
|
controls,
|
|
199
199
|
findings,
|
|
@@ -345,11 +345,33 @@ function jsonError(res, message, status = 500) {
|
|
|
345
345
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
346
346
|
res.end(JSON.stringify({ error: message }));
|
|
347
347
|
}
|
|
348
|
+
function readBody(req) {
|
|
349
|
+
return new Promise((resolve, reject) => {
|
|
350
|
+
let body = "";
|
|
351
|
+
req.on("data", (chunk) => { body += chunk.toString(); });
|
|
352
|
+
req.on("end", () => {
|
|
353
|
+
try {
|
|
354
|
+
resolve(body ? JSON.parse(body) : {});
|
|
355
|
+
}
|
|
356
|
+
catch {
|
|
357
|
+
reject(new Error("Invalid JSON body"));
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
req.on("error", reject);
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
function parseList(val) {
|
|
364
|
+
if (Array.isArray(val))
|
|
365
|
+
return val.map(v => String(v).trim()).filter(Boolean);
|
|
366
|
+
if (typeof val === "string")
|
|
367
|
+
return val.split(",").map(s => s.trim()).filter(Boolean);
|
|
368
|
+
return [];
|
|
369
|
+
}
|
|
348
370
|
export function startDashboard(options) {
|
|
349
371
|
const port = options.port ?? 3001;
|
|
350
372
|
const host = options.host || "localhost";
|
|
351
373
|
const proto = ["http", "//"].join(":");
|
|
352
|
-
const server = http.createServer((req, res) => {
|
|
374
|
+
const server = http.createServer(async (req, res) => {
|
|
353
375
|
if (!req.url) {
|
|
354
376
|
res.writeHead(400);
|
|
355
377
|
res.end("Bad request");
|
|
@@ -357,6 +379,170 @@ export function startDashboard(options) {
|
|
|
357
379
|
}
|
|
358
380
|
const url = new URL(req.url, `${proto}${host}:${port}`);
|
|
359
381
|
const pathname = url.pathname;
|
|
382
|
+
if (req.method === "POST" && pathname === "/api/governance/create") {
|
|
383
|
+
try {
|
|
384
|
+
const body = await readBody(req);
|
|
385
|
+
const systemName = String(body.system_name || "").trim();
|
|
386
|
+
if (!systemName) {
|
|
387
|
+
jsonError(res, "system_name is required", 400);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
const record = createGovernanceRecord({
|
|
391
|
+
system_name: systemName,
|
|
392
|
+
system_description: String(body.system_description || ""),
|
|
393
|
+
system_type: (body.system_type || "ai-system"),
|
|
394
|
+
risk_level: (body.risk_level || "medium"),
|
|
395
|
+
});
|
|
396
|
+
addGovernanceRecord(options.projectPath, record);
|
|
397
|
+
recordActivity(options.projectPath, {
|
|
398
|
+
source: "cli",
|
|
399
|
+
action: "control_override",
|
|
400
|
+
title: `Governance record created: ${record.system_name}`,
|
|
401
|
+
description: `Created governance record for ${record.system_name} (${record.system_type}, risk: ${record.risk_level}). Record ID: ${record.id}`,
|
|
402
|
+
details: { governance_record_id: record.id },
|
|
403
|
+
actor_name: body.actor_name ? String(body.actor_name) : undefined,
|
|
404
|
+
actor_role: body.actor_role ? String(body.actor_role) : undefined,
|
|
405
|
+
});
|
|
406
|
+
jsonResponse(res, { success: true, record });
|
|
407
|
+
}
|
|
408
|
+
catch (err) {
|
|
409
|
+
jsonError(res, err instanceof Error ? err.message : String(err));
|
|
410
|
+
}
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
const govPostMatch = req.method === "POST" ? pathname.match(/^\/api\/governance\/([^/]+)\/(approve|evidence|risk-assessment|policy-basis|review-cycle|data-inventory|committee|compliance-links|delete)$/) : null;
|
|
414
|
+
if (govPostMatch) {
|
|
415
|
+
try {
|
|
416
|
+
const id = decodeURIComponent(govPostMatch[1]);
|
|
417
|
+
const action = govPostMatch[2];
|
|
418
|
+
const body = await readBody(req);
|
|
419
|
+
const pp = options.projectPath;
|
|
420
|
+
if (action === "delete") {
|
|
421
|
+
const ok = deleteGovernanceRecord(pp, id);
|
|
422
|
+
if (!ok) {
|
|
423
|
+
jsonError(res, "Record not found", 404);
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
recordActivity(pp, {
|
|
427
|
+
source: "cli",
|
|
428
|
+
action: "control_override",
|
|
429
|
+
title: `Governance record deleted: ${id}`,
|
|
430
|
+
description: `Deleted governance record ${id}.`,
|
|
431
|
+
details: { governance_record_id: id },
|
|
432
|
+
actor_name: body.actor_name ? String(body.actor_name) : undefined,
|
|
433
|
+
actor_role: body.actor_role ? String(body.actor_role) : undefined,
|
|
434
|
+
});
|
|
435
|
+
jsonResponse(res, { success: true });
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
const record = findGovernanceRecord(pp, id);
|
|
439
|
+
if (!record) {
|
|
440
|
+
jsonError(res, `Record not found: ${id}`, 404);
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
let updated = record;
|
|
444
|
+
if (action === "approve") {
|
|
445
|
+
const decision = body.decision || "approved";
|
|
446
|
+
updated = setGovernanceApproval(pp, record.id, {
|
|
447
|
+
approver_name: String(body.approver_name || ""),
|
|
448
|
+
approver_role: String(body.approver_role || ""),
|
|
449
|
+
approver_email: String(body.approver_email || ""),
|
|
450
|
+
approval_authority: String(body.approval_authority || ""),
|
|
451
|
+
decision: decision,
|
|
452
|
+
decision_date: new Date().toISOString(),
|
|
453
|
+
valid_from: String(body.valid_from || new Date().toISOString().split("T")[0]),
|
|
454
|
+
valid_until: body.valid_until ? String(body.valid_until) : null,
|
|
455
|
+
conditions: parseList(body.conditions),
|
|
456
|
+
rationale: String(body.rationale || ""),
|
|
457
|
+
}, "dashboard-user") || record;
|
|
458
|
+
}
|
|
459
|
+
else if (action === "evidence") {
|
|
460
|
+
const evType = body.type || "document";
|
|
461
|
+
const evSource = body.source_system || "other";
|
|
462
|
+
const evidence = createEvidenceRef({
|
|
463
|
+
type: evType,
|
|
464
|
+
title: String(body.title || ""),
|
|
465
|
+
source_system: evSource,
|
|
466
|
+
reference: String(body.reference || ""),
|
|
467
|
+
location_description: String(body.location_description || ""),
|
|
468
|
+
added_by: "dashboard-user",
|
|
469
|
+
});
|
|
470
|
+
updated = addGovernanceEvidence(pp, record.id, evidence, "dashboard-user") || record;
|
|
471
|
+
}
|
|
472
|
+
else if (action === "risk-assessment") {
|
|
473
|
+
updated = setGovernanceRiskAssessment(pp, record.id, {
|
|
474
|
+
id: `risk-${Date.now()}`,
|
|
475
|
+
assessor: String(body.assessor || ""),
|
|
476
|
+
assessment_date: new Date().toISOString(),
|
|
477
|
+
methodology: String(body.methodology || ""),
|
|
478
|
+
risk_score: String(body.risk_score || ""),
|
|
479
|
+
identified_risks: parseList(body.identified_risks),
|
|
480
|
+
residual_risk: String(body.residual_risk || ""),
|
|
481
|
+
mitigation_measures: parseList(body.mitigation_measures),
|
|
482
|
+
evidence: [],
|
|
483
|
+
}, "dashboard-user") || record;
|
|
484
|
+
}
|
|
485
|
+
else if (action === "policy-basis") {
|
|
486
|
+
updated = setGovernancePolicyBasis(pp, record.id, {
|
|
487
|
+
policy_id: String(body.policy_id || ""),
|
|
488
|
+
policy_name: String(body.policy_name || ""),
|
|
489
|
+
version: String(body.version || "1.0"),
|
|
490
|
+
clauses: parseList(body.clauses),
|
|
491
|
+
standard: String(body.standard || ""),
|
|
492
|
+
evidence: [],
|
|
493
|
+
}, "dashboard-user") || record;
|
|
494
|
+
}
|
|
495
|
+
else if (action === "review-cycle") {
|
|
496
|
+
const today = new Date().toISOString().split("T")[0];
|
|
497
|
+
updated = setGovernanceReviewCycle(pp, record.id, {
|
|
498
|
+
frequency: (body.frequency || "annual"),
|
|
499
|
+
last_review: today,
|
|
500
|
+
next_review: String(body.next_review || today),
|
|
501
|
+
review_history: [],
|
|
502
|
+
}, "dashboard-user") || record;
|
|
503
|
+
}
|
|
504
|
+
else if (action === "data-inventory") {
|
|
505
|
+
updated = setGovernanceDataInventory(pp, record.id, {
|
|
506
|
+
personal_data_categories: parseList(body.personal_data_categories),
|
|
507
|
+
processing_purposes: parseList(body.processing_purposes),
|
|
508
|
+
data_subjects: parseList(body.data_subjects),
|
|
509
|
+
cross_border_transfers: parseList(body.cross_border_transfers),
|
|
510
|
+
retention_period: String(body.retention_period || ""),
|
|
511
|
+
}, "dashboard-user") || record;
|
|
512
|
+
}
|
|
513
|
+
else if (action === "committee") {
|
|
514
|
+
updated = setGovernanceCommittee(pp, record.id, {
|
|
515
|
+
committee_name: String(body.committee_name || ""),
|
|
516
|
+
meeting_date: String(body.meeting_date || ""),
|
|
517
|
+
meeting_reference: String(body.meeting_reference || ""),
|
|
518
|
+
attendees: parseList(body.attendees),
|
|
519
|
+
decision_summary: String(body.decision_summary || ""),
|
|
520
|
+
evidence: [],
|
|
521
|
+
}, "dashboard-user") || record;
|
|
522
|
+
}
|
|
523
|
+
else if (action === "compliance-links") {
|
|
524
|
+
updated = setGovernanceComplianceLinks(pp, record.id, {
|
|
525
|
+
frameworks: parseList(body.frameworks),
|
|
526
|
+
controls_satisfied: parseList(body.controls_satisfied),
|
|
527
|
+
control_pack_ids: parseList(body.control_pack_ids),
|
|
528
|
+
}, "dashboard-user") || record;
|
|
529
|
+
}
|
|
530
|
+
recordActivity(pp, {
|
|
531
|
+
source: "cli",
|
|
532
|
+
action: "control_override",
|
|
533
|
+
title: `Governance ${action}: ${updated.system_name}`,
|
|
534
|
+
description: `Action "${action}" performed on governance record ${updated.system_name}.`,
|
|
535
|
+
details: { governance_record_id: updated.id, action },
|
|
536
|
+
actor_name: body.actor_name ? String(body.actor_name) : undefined,
|
|
537
|
+
actor_role: body.actor_role ? String(body.actor_role) : undefined,
|
|
538
|
+
});
|
|
539
|
+
jsonResponse(res, { success: true, record: updated });
|
|
540
|
+
}
|
|
541
|
+
catch (err) {
|
|
542
|
+
jsonError(res, err instanceof Error ? err.message : String(err));
|
|
543
|
+
}
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
360
546
|
if (req.method !== "GET") {
|
|
361
547
|
jsonError(res, "Method not allowed", 405);
|
|
362
548
|
return;
|
package/dist/template.js
CHANGED
|
@@ -316,6 +316,47 @@ export function renderDashboard(data) {
|
|
|
316
316
|
.container { padding: 16px; }
|
|
317
317
|
.nav-tabs { width: 100%; overflow-x: auto; }
|
|
318
318
|
}
|
|
319
|
+
|
|
320
|
+
/* Governance toolbar */
|
|
321
|
+
.gov-toolbar { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-bottom: 16px; }
|
|
322
|
+
.gov-btn { padding: 6px 14px; border-radius: 6px; border: none; cursor: pointer; font-size: 12px; font-weight: 600; text-decoration: none; display: inline-flex; align-items: center; gap: 4px; }
|
|
323
|
+
.gov-btn-primary { background: #6366f1; color: white; }
|
|
324
|
+
.gov-btn-primary:hover { background: #5558e9; }
|
|
325
|
+
.gov-btn-outline { background: white; color: #6366f1; border: 1px solid #c7d2fe; }
|
|
326
|
+
.gov-btn-outline:hover { background: #eef2ff; }
|
|
327
|
+
.gov-btn-danger { background: #fef2f2; color: #dc2626; border: 1px solid #fecaca; }
|
|
328
|
+
.gov-btn-danger:hover { background: #fee2e2; }
|
|
329
|
+
|
|
330
|
+
/* Per-record action buttons */
|
|
331
|
+
.gov-actions { display: flex; gap: 4px; flex-wrap: wrap; margin-top: 8px; padding-top: 8px; border-top: 1px solid #f3f4f6; }
|
|
332
|
+
.gov-action-btn { padding: 3px 8px; border-radius: 4px; border: 1px solid #e5e7eb; background: #f9fafb; color: #4b5563; font-size: 11px; cursor: pointer; }
|
|
333
|
+
.gov-action-btn:hover { background: #eef2ff; border-color: #c7d2fe; color: #4f46e5; }
|
|
334
|
+
|
|
335
|
+
/* Modal overlay */
|
|
336
|
+
.gov-modal-overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.4); z-index: 1000; justify-content: center; align-items: flex-start; padding-top: 60px; }
|
|
337
|
+
.gov-modal-overlay.active { display: flex; }
|
|
338
|
+
.gov-modal { background: white; border-radius: 10px; box-shadow: 0 20px 60px rgba(0,0,0,0.15); max-width: 520px; width: 90%; max-height: 80vh; overflow-y: auto; }
|
|
339
|
+
.gov-modal-header { padding: 16px 20px; border-bottom: 1px solid #e5e7eb; display: flex; justify-content: space-between; align-items: center; position: sticky; top: 0; background: white; border-radius: 10px 10px 0 0; z-index: 1; }
|
|
340
|
+
.gov-modal-title { font-size: 15px; font-weight: 700; color: #1f2937; }
|
|
341
|
+
.gov-modal-close { background: none; border: none; font-size: 20px; cursor: pointer; color: #9ca3af; padding: 0 4px; line-height: 1; }
|
|
342
|
+
.gov-modal-close:hover { color: #ef4444; }
|
|
343
|
+
.gov-modal-body { padding: 20px; }
|
|
344
|
+
.gov-modal-body label { display: block; font-size: 12px; font-weight: 600; color: #4b5563; margin-bottom: 4px; margin-top: 12px; }
|
|
345
|
+
.gov-modal-body label:first-child { margin-top: 0; }
|
|
346
|
+
.gov-modal-body input, .gov-modal-body select, .gov-modal-body textarea {
|
|
347
|
+
width: 100%; padding: 7px 10px; border: 1px solid #d1d5db; border-radius: 6px; font-size: 13px; box-sizing: border-box;
|
|
348
|
+
}
|
|
349
|
+
.gov-modal-body input:focus, .gov-modal-body select:focus, .gov-modal-body textarea:focus { outline: none; border-color: #6366f1; box-shadow: 0 0 0 3px rgba(99,102,241,0.1); }
|
|
350
|
+
.gov-modal-body textarea { resize: vertical; min-height: 60px; }
|
|
351
|
+
.gov-modal-footer { padding: 12px 20px; border-top: 1px solid #e5e7eb; display: flex; justify-content: flex-end; gap: 8px; position: sticky; bottom: 0; background: white; border-radius: 0 0 10px 10px; }
|
|
352
|
+
.gov-form-hint { font-size: 11px; color: #9ca3af; margin-top: 2px; }
|
|
353
|
+
|
|
354
|
+
/* Toast notifications */
|
|
355
|
+
.gov-toast-container { position: fixed; top: 20px; right: 20px; z-index: 2000; display: flex; flex-direction: column; gap: 8px; }
|
|
356
|
+
.gov-toast { padding: 12px 18px; border-radius: 8px; color: white; font-size: 13px; font-weight: 600; box-shadow: 0 4px 12px rgba(0,0,0,0.15); display: flex; align-items: center; gap: 8px; animation: govToastSlide 0.3s ease; max-width: 360px; }
|
|
357
|
+
.gov-toast.success { background: #22c55e; }
|
|
358
|
+
.gov-toast.error { background: #ef4444; }
|
|
359
|
+
@keyframes govToastSlide { from { opacity: 0; transform: translateX(40px); } to { opacity: 1; transform: translateX(0); } }
|
|
319
360
|
</style>
|
|
320
361
|
</head>
|
|
321
362
|
<body>
|
|
@@ -970,6 +1011,144 @@ export function renderDashboard(data) {
|
|
|
970
1011
|
d.textContent = str;
|
|
971
1012
|
return d.innerHTML;
|
|
972
1013
|
}
|
|
1014
|
+
|
|
1015
|
+
// --- Governance action toolbar ---
|
|
1016
|
+
var govAction = null;
|
|
1017
|
+
var govRecordId = null;
|
|
1018
|
+
var govTitles = {
|
|
1019
|
+
'create': 'Create Governance Record',
|
|
1020
|
+
'approve': 'Record Approval Decision',
|
|
1021
|
+
'evidence': 'Add Evidence Reference',
|
|
1022
|
+
'risk-assessment': 'Link Risk Assessment',
|
|
1023
|
+
'policy-basis': 'Document Policy Basis',
|
|
1024
|
+
'review-cycle': 'Set Review Cycle',
|
|
1025
|
+
'data-inventory': 'Document Data Inventory',
|
|
1026
|
+
'committee': 'Record Committee Approval',
|
|
1027
|
+
'compliance-links': 'Map Compliance Links'
|
|
1028
|
+
};
|
|
1029
|
+
function govF(name, label, inputHtml) {
|
|
1030
|
+
return '<label>' + label + '</label>' + inputHtml;
|
|
1031
|
+
}
|
|
1032
|
+
function govI(name, label, ph) {
|
|
1033
|
+
return govF(name, label, '<input type="text" name="' + name + '" placeholder="' + (ph||'') + '">');
|
|
1034
|
+
}
|
|
1035
|
+
function govS(name, label, opts) {
|
|
1036
|
+
var o = '';
|
|
1037
|
+
for (var i = 0; i < opts.length; i++) o += '<option value="' + opts[i][0] + '">' + opts[i][1] + '</option>';
|
|
1038
|
+
return govF(name, label, '<select name="' + name + '">' + o + '</select>');
|
|
1039
|
+
}
|
|
1040
|
+
function govT(name, label, ph) {
|
|
1041
|
+
return govF(name, label, '<textarea name="' + name + '" placeholder="' + (ph||'') + '"></textarea>');
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
window.openGovModal = function(action, recordId) {
|
|
1045
|
+
govAction = action;
|
|
1046
|
+
govRecordId = recordId || null;
|
|
1047
|
+
var m = document.getElementById('gov-modal');
|
|
1048
|
+
var h = '<div class="gov-modal-header"><div class="gov-modal-title">' + (govTitles[action]||action) + '</div><button class="gov-modal-close" onclick="closeGovModal()">×</button></div><div class="gov-modal-body">';
|
|
1049
|
+
if (action === 'create') {
|
|
1050
|
+
h += govI('system_name','System Name *','e.g., Payment API Gateway');
|
|
1051
|
+
h += govS('system_type','System Type',[['ai-system','AI System'],['application','Application'],['data-process','Data Process'],['api','API'],['model','Model'],['infrastructure','Infrastructure'],['third-party-service','Third-Party Service']]);
|
|
1052
|
+
h += govS('risk_level','Risk Level',[['low','Low'],['medium','Medium'],['high','High'],['critical','Critical']]);
|
|
1053
|
+
h += govT('system_description','Description','Brief description');
|
|
1054
|
+
} else if (action === 'approve') {
|
|
1055
|
+
h += govI('approver_name','Approver Name *','e.g., Jane Smith');
|
|
1056
|
+
h += govI('approver_role','Approver Role','e.g., CISO');
|
|
1057
|
+
h += govI('approver_email','Approver Email','e.g., jane@company.com');
|
|
1058
|
+
h += govI('approval_authority','Approval Authority','e.g., CISO Office');
|
|
1059
|
+
h += govS('decision','Decision',[['approved','Approved'],['conditional','Conditional'],['rejected','Rejected']]);
|
|
1060
|
+
h += govI('valid_from','Valid From','YYYY-MM-DD');
|
|
1061
|
+
h += govI('valid_until','Valid Until','YYYY-MM-DD (blank = indefinite)');
|
|
1062
|
+
h += govI('conditions','Conditions','comma-separated');
|
|
1063
|
+
h += govT('rationale','Rationale','Reason for decision');
|
|
1064
|
+
} else if (action === 'evidence') {
|
|
1065
|
+
h += govI('title','Title *','e.g., DPIA Report 2024');
|
|
1066
|
+
h += govS('type','Evidence Type',[['document','Document'],['ticket','Ticket'],['meeting-record','Meeting Record'],['report','Report'],['certificate','Certificate'],['contract','Contract'],['log','Log'],['dashboard','Dashboard'],['email','Email'],['other','Other']]);
|
|
1067
|
+
h += govS('source_system','Source System',[['jira','Jira'],['confluence','Confluence'],['servicenow','ServiceNow'],['sharepoint','SharePoint'],['grc-platform','GRC Platform'],['git','Git'],['file','File'],['url','URL'],['email','Email'],['other','Other']]);
|
|
1068
|
+
h += govI('reference','Reference *','Ticket ID, URL, doc name');
|
|
1069
|
+
h += govI('location_description','Location','Where to find it');
|
|
1070
|
+
} else if (action === 'risk-assessment') {
|
|
1071
|
+
h += govI('assessor','Assessor Name *','e.g., John Doe');
|
|
1072
|
+
h += govI('methodology','Methodology','e.g., NIST RMF');
|
|
1073
|
+
h += govI('risk_score','Risk Score','e.g., 7.5/10');
|
|
1074
|
+
h += govI('residual_risk','Residual Risk','low / medium / high / critical');
|
|
1075
|
+
h += govI('identified_risks','Identified Risks','comma-separated');
|
|
1076
|
+
h += govI('mitigation_measures','Mitigation Measures','comma-separated');
|
|
1077
|
+
} else if (action === 'policy-basis') {
|
|
1078
|
+
h += govI('policy_id','Policy ID','e.g., POL-001');
|
|
1079
|
+
h += govI('policy_name','Policy Name *','e.g., InfoSec Policy');
|
|
1080
|
+
h += govI('version','Version','e.g., 2.0');
|
|
1081
|
+
h += govI('standard','Standard','e.g., ISO 27001, GDPR');
|
|
1082
|
+
h += govI('clauses','Applicable Clauses','comma-separated');
|
|
1083
|
+
} else if (action === 'review-cycle') {
|
|
1084
|
+
h += govS('frequency','Review Frequency',[['quarterly','Quarterly'],['semi-annual','Semi-Annual'],['annual','Annual'],['biennial','Biennial']]);
|
|
1085
|
+
h += govI('next_review','Next Review Date','YYYY-MM-DD');
|
|
1086
|
+
} else if (action === 'data-inventory') {
|
|
1087
|
+
h += govI('personal_data_categories','Data Categories','comma-separated');
|
|
1088
|
+
h += govI('processing_purposes','Processing Purposes','comma-separated');
|
|
1089
|
+
h += govI('data_subjects','Data Subjects','comma-separated');
|
|
1090
|
+
h += govI('cross_border_transfers','Cross-Border Transfers','comma-separated');
|
|
1091
|
+
h += govI('retention_period','Retention Period','e.g., 7 years');
|
|
1092
|
+
} else if (action === 'committee') {
|
|
1093
|
+
h += govI('committee_name','Committee Name *','e.g., Data Governance Board');
|
|
1094
|
+
h += govI('meeting_date','Meeting Date','YYYY-MM-DD');
|
|
1095
|
+
h += govI('meeting_reference','Meeting Reference','e.g., MIN-2024-001');
|
|
1096
|
+
h += govI('attendees','Attendees','comma-separated');
|
|
1097
|
+
h += govT('decision_summary','Decision Summary','Summary of committee decision');
|
|
1098
|
+
} else if (action === 'compliance-links') {
|
|
1099
|
+
h += govI('frameworks','Frameworks','comma-separated (GDPR, OWASP...)');
|
|
1100
|
+
h += govI('controls_satisfied','Controls Satisfied','comma-separated control IDs');
|
|
1101
|
+
h += govI('control_pack_ids','Control Pack IDs','comma-separated pack IDs');
|
|
1102
|
+
}
|
|
1103
|
+
h += '<label>Your Name</label><input type="text" name="actor_name" placeholder="Optional"><div class="gov-form-hint">For activity log attribution</div>';
|
|
1104
|
+
h += '<label>Your Role</label><input type="text" name="actor_role" placeholder="Optional">';
|
|
1105
|
+
h += '</div>';
|
|
1106
|
+
h += '<div class="gov-modal-footer"><button class="gov-btn gov-btn-outline" onclick="closeGovModal()">Cancel</button><button class="gov-btn gov-btn-primary" onclick="submitGovForm()">Save</button></div>';
|
|
1107
|
+
m.innerHTML = h;
|
|
1108
|
+
document.getElementById('gov-modal-overlay').classList.add('active');
|
|
1109
|
+
};
|
|
1110
|
+
|
|
1111
|
+
window.closeGovModal = function() {
|
|
1112
|
+
document.getElementById('gov-modal-overlay').classList.remove('active');
|
|
1113
|
+
document.getElementById('gov-modal').innerHTML = '';
|
|
1114
|
+
};
|
|
1115
|
+
|
|
1116
|
+
window.submitGovForm = function() {
|
|
1117
|
+
var inputs = document.querySelectorAll('#gov-modal .gov-modal-body input[name], #gov-modal .gov-modal-body select[name], #gov-modal .gov-modal-body textarea[name]');
|
|
1118
|
+
var body = {};
|
|
1119
|
+
for (var i = 0; i < inputs.length; i++) body[inputs[i].name] = inputs[i].value;
|
|
1120
|
+
var url = govRecordId ? '/api/governance/' + encodeURIComponent(govRecordId) + '/' + govAction : '/api/governance/create';
|
|
1121
|
+
var btn = document.querySelector('#gov-modal .gov-modal-footer .gov-btn-primary');
|
|
1122
|
+
if (btn) { btn.textContent = 'Saving...'; btn.disabled = true; }
|
|
1123
|
+
fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) })
|
|
1124
|
+
.then(function(r) { return r.json(); })
|
|
1125
|
+
.then(function(d) {
|
|
1126
|
+
if (d.success) { closeGovModal(); showToast('Saved! Reloading...', 'success'); setTimeout(function() { location.reload(); }, 800); }
|
|
1127
|
+
else { showToast(d.error || 'Failed', 'error'); if (btn) { btn.textContent = 'Save'; btn.disabled = false; } }
|
|
1128
|
+
})
|
|
1129
|
+
.catch(function(e) { showToast('Error: ' + (e.message||'network'), 'error'); if (btn) { btn.textContent = 'Save'; btn.disabled = false; } });
|
|
1130
|
+
};
|
|
1131
|
+
|
|
1132
|
+
window.govDeleteRecord = function(recordId, systemName) {
|
|
1133
|
+
if (!confirm('Delete governance record "' + systemName + '"? This action cannot be undone.')) return;
|
|
1134
|
+
fetch('/api/governance/' + encodeURIComponent(recordId) + '/delete', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' })
|
|
1135
|
+
.then(function(r) { return r.json(); })
|
|
1136
|
+
.then(function(d) {
|
|
1137
|
+
if (d.success) { showToast('Deleted! Reloading...', 'success'); setTimeout(function() { location.reload(); }, 800); }
|
|
1138
|
+
else { showToast(d.error || 'Failed', 'error'); }
|
|
1139
|
+
})
|
|
1140
|
+
.catch(function(e) { showToast('Error: ' + (e.message||'network'), 'error'); });
|
|
1141
|
+
};
|
|
1142
|
+
|
|
1143
|
+
window.showToast = function(msg, type) {
|
|
1144
|
+
var c = document.getElementById('gov-toast-container');
|
|
1145
|
+
if (!c) return;
|
|
1146
|
+
var t = document.createElement('div');
|
|
1147
|
+
t.className = 'gov-toast ' + (type||'success');
|
|
1148
|
+
t.innerHTML = (type === 'error' ? '✗ ' : '✓ ') + msg;
|
|
1149
|
+
c.appendChild(t);
|
|
1150
|
+
setTimeout(function() { t.style.opacity = '0'; t.style.transition = 'opacity 0.3s'; setTimeout(function() { if(t.parentNode) t.remove(); }, 300); }, 3000);
|
|
1151
|
+
};
|
|
973
1152
|
})();
|
|
974
1153
|
</script>
|
|
975
1154
|
|
|
@@ -1597,17 +1776,22 @@ function renderGovernanceSection(data) {
|
|
|
1597
1776
|
<div class="empty-state">
|
|
1598
1777
|
<div class="icon">📋</div>
|
|
1599
1778
|
<div class="msg">No governance records found</div>
|
|
1600
|
-
<div class="sub">Create
|
|
1779
|
+
<div class="sub">Create your first governance record to start building the provenance chain</div>
|
|
1601
1780
|
<div class="sub" style="margin-top:12px;">The governance tab provides end-to-end traceability for auditors:<br>
|
|
1602
1781
|
System → Risk Assessment → Policy → Approval → Evidence → Review Cycle</div>
|
|
1782
|
+
<div style="margin-top:16px;">
|
|
1783
|
+
<button class="gov-btn gov-btn-primary" onclick="openGovModal('create')">+ Create First Record</button>
|
|
1784
|
+
</div>
|
|
1603
1785
|
</div>
|
|
1604
|
-
</div
|
|
1786
|
+
</div>
|
|
1787
|
+
${renderGovModals()}
|
|
1788
|
+
${renderGovToastContainer()}`;
|
|
1605
1789
|
}
|
|
1606
1790
|
let html = "";
|
|
1607
1791
|
html += `<div style="margin-bottom:20px;">`;
|
|
1608
1792
|
html += `<h2 style="font-size:20px;font-weight:700;margin-bottom:4px;">Governance Provenance Chain</h2>`;
|
|
1609
1793
|
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
|
|
1794
|
+
html += `<div class="gov-toolbar"><button class="gov-btn gov-btn-primary" onclick="openGovModal('create')">+ New Record</button><a href="/api/report/governance" class="gov-btn gov-btn-outline">📋 Export Report</a></div>`;
|
|
1611
1795
|
html += `<div class="grid grid-4" style="margin-bottom:20px;">`;
|
|
1612
1796
|
html += `<div class="card stat"><div class="num">${summary.total}</div><div class="label">Total Systems</div></div>`;
|
|
1613
1797
|
html += `<div class="card stat"><div class="num" style="color:#22c55e;">${summary.approved}</div><div class="label">Approved</div></div>`;
|
|
@@ -1778,11 +1962,24 @@ function renderGovernanceSection(data) {
|
|
|
1778
1962
|
html += `</div>`;
|
|
1779
1963
|
html += `</div>`;
|
|
1780
1964
|
}
|
|
1965
|
+
html += `<div class="gov-actions">`;
|
|
1966
|
+
html += `<button class="gov-action-btn" onclick="event.stopPropagation();openGovModal('approve','${escapeHtml(r.id)}')">Approve</button>`;
|
|
1967
|
+
html += `<button class="gov-action-btn" onclick="event.stopPropagation();openGovModal('evidence','${escapeHtml(r.id)}')">Evidence</button>`;
|
|
1968
|
+
html += `<button class="gov-action-btn" onclick="event.stopPropagation();openGovModal('risk-assessment','${escapeHtml(r.id)}')">Risk</button>`;
|
|
1969
|
+
html += `<button class="gov-action-btn" onclick="event.stopPropagation();openGovModal('policy-basis','${escapeHtml(r.id)}')">Policy</button>`;
|
|
1970
|
+
html += `<button class="gov-action-btn" onclick="event.stopPropagation();openGovModal('review-cycle','${escapeHtml(r.id)}')">Review</button>`;
|
|
1971
|
+
html += `<button class="gov-action-btn" onclick="event.stopPropagation();openGovModal('data-inventory','${escapeHtml(r.id)}')">Data Inv</button>`;
|
|
1972
|
+
html += `<button class="gov-action-btn" onclick="event.stopPropagation();openGovModal('committee','${escapeHtml(r.id)}')">Committee</button>`;
|
|
1973
|
+
html += `<button class="gov-action-btn" onclick="event.stopPropagation();openGovModal('compliance-links','${escapeHtml(r.id)}')">Compliance</button>`;
|
|
1974
|
+
html += `<button class="gov-action-btn" style="color:#dc2626;" onclick="event.stopPropagation();govDeleteRecord('${escapeHtml(r.id)}','${escapeHtml(r.system_name)}')">Delete</button>`;
|
|
1975
|
+
html += `</div>`;
|
|
1781
1976
|
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
1977
|
html += `</div>`;
|
|
1783
1978
|
html += `</div>`;
|
|
1784
1979
|
}
|
|
1785
1980
|
html += `</div>`;
|
|
1981
|
+
html += renderGovModals();
|
|
1982
|
+
html += renderGovToastContainer();
|
|
1786
1983
|
return html;
|
|
1787
1984
|
}
|
|
1788
1985
|
function escapeHtml(str) {
|
|
@@ -1793,3 +1990,11 @@ function escapeHtml(str) {
|
|
|
1793
1990
|
.replace(/"/g, """)
|
|
1794
1991
|
.replace(/'/g, "'");
|
|
1795
1992
|
}
|
|
1993
|
+
function renderGovModals() {
|
|
1994
|
+
return `<div class="gov-modal-overlay" id="gov-modal-overlay" onclick="if(event.target===this)closeGovModal()">
|
|
1995
|
+
<div class="gov-modal" id="gov-modal"></div>
|
|
1996
|
+
</div>`;
|
|
1997
|
+
}
|
|
1998
|
+
function renderGovToastContainer() {
|
|
1999
|
+
return `<div class="gov-toast-container" id="gov-toast-container"></div>`;
|
|
2000
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"dependencies": {
|
|
3
|
-
"@greenarmor/ges-audit-engine": "1.4.
|
|
4
|
-
"@greenarmor/ges-core": "1.4.
|
|
5
|
-
"@greenarmor/ges-policy-engine": "1.4.
|
|
6
|
-
"@greenarmor/ges-report-generator": "1.4.
|
|
7
|
-
"@greenarmor/ges-scoring-engine": "1.4.
|
|
3
|
+
"@greenarmor/ges-audit-engine": "1.4.3",
|
|
4
|
+
"@greenarmor/ges-core": "1.4.3",
|
|
5
|
+
"@greenarmor/ges-policy-engine": "1.4.3",
|
|
6
|
+
"@greenarmor/ges-report-generator": "1.4.3",
|
|
7
|
+
"@greenarmor/ges-scoring-engine": "1.4.3"
|
|
8
8
|
},
|
|
9
9
|
"description": "GESF Web Dashboard - Visual compliance dashboard for teams",
|
|
10
10
|
"devDependencies": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
"type": "module",
|
|
43
43
|
"types": "./dist/index.d.ts",
|
|
44
|
-
"version": "1.4.
|
|
44
|
+
"version": "1.4.3",
|
|
45
45
|
"scripts": {
|
|
46
46
|
"build": "tsc",
|
|
47
47
|
"clean": "rm -rf dist tsconfig.tsbuildinfo",
|