From 4e30eb3151e9f595970406ea93065e19a2bb732c Mon Sep 17 00:00:00 2001 From: CPAtoCybersecurity <150057814+CPAtoCybersecurity@users.noreply.github.com> Date: Sun, 2 Aug 2026 09:13:11 -0400 Subject: [PATCH 1/4] Regenerate comprehensive assessment data for catalog drift Pre-existing on main: PROC-evidence-preservation-guide.md was added to ASSESSMENT_CATALOG (8452eec, fd543f1) without re-running the generators, so the committed demo data lacked the artifact and carried a stale bankVersion. Mechanical output of generate-comprehensive-assessment.mjs; no hand edits. --- src/stores/comprehensiveAssessmentData.js | 370 +++++++++++----------- 1 file changed, 193 insertions(+), 177 deletions(-) diff --git a/src/stores/comprehensiveAssessmentData.js b/src/stores/comprehensiveAssessmentData.js index d9b496b..252ba92 100644 --- a/src/stores/comprehensiveAssessmentData.js +++ b/src/stores/comprehensiveAssessmentData.js @@ -151,9 +151,9 @@ export const COMPREHENSIVE_ARTIFACTS = [ { "id": 1009, "artifactId": "AR-110", - "name": "Incident Response Playbook (Excerpt)", - "description": "Catalog evidence: PROC-incident-response-playbook.md", - "link": "https://github.com/CPAtoCybersecurity/csf_profile/blob/main/ASSESSMENT_CATALOG/5_Artifacts/Procedures/PROC-incident-response-playbook.md", + "name": "Evidence Preservation Guide (Excerpt)", + "description": "Catalog evidence: PROC-evidence-preservation-guide.md", + "link": "https://github.com/CPAtoCybersecurity/csf_profile/blob/main/ASSESSMENT_CATALOG/5_Artifacts/Procedures/PROC-evidence-preservation-guide.md", "complianceRequirement": null, "controlId": null, "linkedSubcategoryIds": [], @@ -167,9 +167,9 @@ export const COMPREHENSIVE_ARTIFACTS = [ { "id": 1010, "artifactId": "AR-111", - "name": "Patch Management Procedure", - "description": "Catalog evidence: PROC-patch-management.md", - "link": "https://github.com/CPAtoCybersecurity/csf_profile/blob/main/ASSESSMENT_CATALOG/5_Artifacts/Procedures/PROC-patch-management.md", + "name": "Incident Response Playbook (Excerpt)", + "description": "Catalog evidence: PROC-incident-response-playbook.md", + "link": "https://github.com/CPAtoCybersecurity/csf_profile/blob/main/ASSESSMENT_CATALOG/5_Artifacts/Procedures/PROC-incident-response-playbook.md", "complianceRequirement": null, "controlId": null, "linkedSubcategoryIds": [], @@ -183,9 +183,9 @@ export const COMPREHENSIVE_ARTIFACTS = [ { "id": 1011, "artifactId": "AR-112", - "name": "Hardware Inventory Sampling Walkthrough", - "description": "Catalog evidence: PROC-sampling-walkthrough.md", - "link": "https://github.com/CPAtoCybersecurity/csf_profile/blob/main/ASSESSMENT_CATALOG/5_Artifacts/Procedures/PROC-sampling-walkthrough.md", + "name": "Patch Management Procedure", + "description": "Catalog evidence: PROC-patch-management.md", + "link": "https://github.com/CPAtoCybersecurity/csf_profile/blob/main/ASSESSMENT_CATALOG/5_Artifacts/Procedures/PROC-patch-management.md", "complianceRequirement": null, "controlId": null, "linkedSubcategoryIds": [], @@ -199,6 +199,22 @@ export const COMPREHENSIVE_ARTIFACTS = [ { "id": 1012, "artifactId": "AR-113", + "name": "Hardware Inventory Sampling Walkthrough", + "description": "Catalog evidence: PROC-sampling-walkthrough.md", + "link": "https://github.com/CPAtoCybersecurity/csf_profile/blob/main/ASSESSMENT_CATALOG/5_Artifacts/Procedures/PROC-sampling-walkthrough.md", + "complianceRequirement": null, + "controlId": null, + "linkedSubcategoryIds": [], + "linkedEvaluationIds": [], + "type": "Procedure", + "createdDate": "2026-04-30T00:00:00.000Z", + "lastModified": "2026-04-30T00:00:00.000Z", + "jiraKey": "AR-113", + "status": "ACTIVE" + }, + { + "id": 1013, + "artifactId": "AR-114", "name": "Q4 2025 Access Recertification Report", "description": "Catalog evidence: RPT-access-certification-q4.md", "link": "https://github.com/CPAtoCybersecurity/csf_profile/blob/main/ASSESSMENT_CATALOG/5_Artifacts/Reports/RPT-access-certification-q4.md", @@ -209,12 +225,12 @@ export const COMPREHENSIVE_ARTIFACTS = [ "type": "Report", "createdDate": "2026-04-30T00:00:00.000Z", "lastModified": "2026-04-30T00:00:00.000Z", - "jiraKey": "AR-113", + "jiraKey": "AR-114", "status": "ACTIVE" }, { - "id": 1013, - "artifactId": "AR-114", + "id": 1014, + "artifactId": "AR-115", "name": "AWS Tag Compliance Report", "description": "Catalog evidence: RPT-aws-tag-compliance.md", "link": "https://github.com/CPAtoCybersecurity/csf_profile/blob/main/ASSESSMENT_CATALOG/5_Artifacts/Reports/RPT-aws-tag-compliance.md", @@ -225,12 +241,12 @@ export const COMPREHENSIVE_ARTIFACTS = [ "type": "Report", "createdDate": "2026-04-30T00:00:00.000Z", "lastModified": "2026-04-30T00:00:00.000Z", - "jiraKey": "AR-114", + "jiraKey": "AR-115", "status": "ACTIVE" }, { - "id": 1014, - "artifactId": "AR-115", + "id": 1015, + "artifactId": "AR-116", "name": "Quarterly Backup Restore Test Report", "description": "Catalog evidence: RPT-backup-restore-test.md", "link": "https://github.com/CPAtoCybersecurity/csf_profile/blob/main/ASSESSMENT_CATALOG/5_Artifacts/Reports/RPT-backup-restore-test.md", @@ -241,12 +257,12 @@ export const COMPREHENSIVE_ARTIFACTS = [ "type": "Report", "createdDate": "2026-04-30T00:00:00.000Z", "lastModified": "2026-04-30T00:00:00.000Z", - "jiraKey": "AR-115", + "jiraKey": "AR-116", "status": "ACTIVE" }, { - "id": 1015, - "artifactId": "AR-116", + "id": 1016, + "artifactId": "AR-117", "name": "Network Discovery Scan Results", "description": "Catalog evidence: RPT-nmap-scan-results.md", "link": "https://github.com/CPAtoCybersecurity/csf_profile/blob/main/ASSESSMENT_CATALOG/5_Artifacts/Reports/RPT-nmap-scan-results.md", @@ -257,12 +273,12 @@ export const COMPREHENSIVE_ARTIFACTS = [ "type": "Report", "createdDate": "2026-04-30T00:00:00.000Z", "lastModified": "2026-04-30T00:00:00.000Z", - "jiraKey": "AR-116", + "jiraKey": "AR-117", "status": "ACTIVE" }, { - "id": 1016, - "artifactId": "AR-117", + "id": 1017, + "artifactId": "AR-118", "name": "Q1 2026 Phishing Simulation Report", "description": "Catalog evidence: RPT-phishing-simulation-q1.md", "link": "https://github.com/CPAtoCybersecurity/csf_profile/blob/main/ASSESSMENT_CATALOG/5_Artifacts/Reports/RPT-phishing-simulation-q1.md", @@ -273,12 +289,12 @@ export const COMPREHENSIVE_ARTIFACTS = [ "type": "Report", "createdDate": "2026-04-30T00:00:00.000Z", "lastModified": "2026-04-30T00:00:00.000Z", - "jiraKey": "AR-117", + "jiraKey": "AR-118", "status": "ACTIVE" }, { - "id": 1017, - "artifactId": "AR-118", + "id": 1018, + "artifactId": "AR-119", "name": "Weekly Vulnerability Scan Summary", "description": "Catalog evidence: RPT-vulnerability-scan-summary.md", "link": "https://github.com/CPAtoCybersecurity/csf_profile/blob/main/ASSESSMENT_CATALOG/5_Artifacts/Reports/RPT-vulnerability-scan-summary.md", @@ -289,12 +305,12 @@ export const COMPREHENSIVE_ARTIFACTS = [ "type": "Report", "createdDate": "2026-04-30T00:00:00.000Z", "lastModified": "2026-04-30T00:00:00.000Z", - "jiraKey": "AR-118", + "jiraKey": "AR-119", "status": "ACTIVE" }, { - "id": 1018, - "artifactId": "AR-119", + "id": 1019, + "artifactId": "AR-120", "name": "Alma Security Hardware Inventory", "description": "Catalog evidence: INV-hardware-inventory.md", "link": "https://github.com/CPAtoCybersecurity/csf_profile/blob/main/ASSESSMENT_CATALOG/5_Artifacts/Inventories/INV-hardware-inventory.md", @@ -305,12 +321,12 @@ export const COMPREHENSIVE_ARTIFACTS = [ "type": "Inventory", "createdDate": "2026-04-30T00:00:00.000Z", "lastModified": "2026-04-30T00:00:00.000Z", - "jiraKey": "AR-119", + "jiraKey": "AR-120", "status": "ACTIVE" }, { - "id": 1019, - "artifactId": "AR-120", + "id": 1020, + "artifactId": "AR-121", "name": "Service Account Inventory", "description": "Catalog evidence: INV-service-accounts.md", "link": "https://github.com/CPAtoCybersecurity/csf_profile/blob/main/ASSESSMENT_CATALOG/5_Artifacts/Inventories/INV-service-accounts.md", @@ -321,12 +337,12 @@ export const COMPREHENSIVE_ARTIFACTS = [ "type": "Inventory", "createdDate": "2026-04-30T00:00:00.000Z", "lastModified": "2026-04-30T00:00:00.000Z", - "jiraKey": "AR-120", + "jiraKey": "AR-121", "status": "ACTIVE" }, { - "id": 1020, - "artifactId": "AR-121", + "id": 1021, + "artifactId": "AR-122", "name": "Authorized Software Inventory", "description": "Catalog evidence: INV-software-inventory.md", "link": "https://github.com/CPAtoCybersecurity/csf_profile/blob/main/ASSESSMENT_CATALOG/5_Artifacts/Inventories/INV-software-inventory.md", @@ -337,12 +353,12 @@ export const COMPREHENSIVE_ARTIFACTS = [ "type": "Inventory", "createdDate": "2026-04-30T00:00:00.000Z", "lastModified": "2026-04-30T00:00:00.000Z", - "jiraKey": "AR-121", + "jiraKey": "AR-122", "status": "ACTIVE" }, { - "id": 1021, - "artifactId": "AR-122", + "id": 1022, + "artifactId": "AR-123", "name": "SOC Incident Ticket", "description": "Catalog evidence: TKT-SOC-1001-phishing.md", "link": "https://github.com/CPAtoCybersecurity/csf_profile/blob/main/ASSESSMENT_CATALOG/5_Artifacts/Tickets/TKT-SOC-1001-phishing.md", @@ -353,12 +369,12 @@ export const COMPREHENSIVE_ARTIFACTS = [ "type": "Ticket", "createdDate": "2026-04-30T00:00:00.000Z", "lastModified": "2026-04-30T00:00:00.000Z", - "jiraKey": "AR-122", + "jiraKey": "AR-123", "status": "ACTIVE" }, { - "id": 1022, - "artifactId": "AR-123", + "id": 1023, + "artifactId": "AR-124", "name": "SOC Incident Ticket", "description": "Catalog evidence: TKT-SOC-1004.md", "link": "https://github.com/CPAtoCybersecurity/csf_profile/blob/main/ASSESSMENT_CATALOG/5_Artifacts/Tickets/TKT-SOC-1004.md", @@ -369,12 +385,12 @@ export const COMPREHENSIVE_ARTIFACTS = [ "type": "Ticket", "createdDate": "2026-04-30T00:00:00.000Z", "lastModified": "2026-04-30T00:00:00.000Z", - "jiraKey": "AR-123", + "jiraKey": "AR-124", "status": "ACTIVE" }, { - "id": 1023, - "artifactId": "AR-124", + "id": 1024, + "artifactId": "AR-125", "name": "SOC Incident Ticket", "description": "Catalog evidence: TKT-SOC-1005.md", "link": "https://github.com/CPAtoCybersecurity/csf_profile/blob/main/ASSESSMENT_CATALOG/5_Artifacts/Tickets/TKT-SOC-1005.md", @@ -385,12 +401,12 @@ export const COMPREHENSIVE_ARTIFACTS = [ "type": "Ticket", "createdDate": "2026-04-30T00:00:00.000Z", "lastModified": "2026-04-30T00:00:00.000Z", - "jiraKey": "AR-124", + "jiraKey": "AR-125", "status": "ACTIVE" }, { - "id": 1024, - "artifactId": "AR-125", + "id": 1025, + "artifactId": "AR-126", "name": "GV.RM — Risk Management Strategy: Assessment Artifacts", "description": "Catalog evidence: EVD-GV.RM-02-assessment-artifacts.md", "link": "https://github.com/CPAtoCybersecurity/csf_profile/blob/main/ASSESSMENT_CATALOG/5_Artifacts/Evidence/EVD-GV.RM-02-assessment-artifacts.md", @@ -401,12 +417,12 @@ export const COMPREHENSIVE_ARTIFACTS = [ "type": "Evidence", "createdDate": "2026-04-30T00:00:00.000Z", "lastModified": "2026-04-30T00:00:00.000Z", - "jiraKey": "AR-125", + "jiraKey": "AR-126", "status": "ACTIVE" }, { - "id": 1025, - "artifactId": "AR-126", + "id": 1026, + "artifactId": "AR-127", "name": "PR.IP Assessment Artifacts", "description": "Catalog evidence: EVD-PR.IP-assessment-artifacts.md", "link": "https://github.com/CPAtoCybersecurity/csf_profile/blob/main/ASSESSMENT_CATALOG/5_Artifacts/Evidence/EVD-PR.IP-assessment-artifacts.md", @@ -417,12 +433,12 @@ export const COMPREHENSIVE_ARTIFACTS = [ "type": "Evidence", "createdDate": "2026-04-30T00:00:00.000Z", "lastModified": "2026-04-30T00:00:00.000Z", - "jiraKey": "AR-126", + "jiraKey": "AR-127", "status": "ACTIVE" }, { - "id": 1026, - "artifactId": "AR-127", + "id": 1027, + "artifactId": "AR-128", "name": "Alma Security Business Case", "description": "Catalog evidence: EVD-alma-business-case.md", "link": "https://github.com/CPAtoCybersecurity/csf_profile/blob/main/ASSESSMENT_CATALOG/5_Artifacts/Evidence/EVD-alma-business-case.md", @@ -433,12 +449,12 @@ export const COMPREHENSIVE_ARTIFACTS = [ "type": "Evidence", "createdDate": "2026-04-30T00:00:00.000Z", "lastModified": "2026-04-30T00:00:00.000Z", - "jiraKey": "AR-127", + "jiraKey": "AR-128", "status": "ACTIVE" }, { - "id": 1027, - "artifactId": "AR-128", + "id": 1028, + "artifactId": "AR-129", "name": "AWS Config Compliance Snapshot", "description": "Catalog evidence: EVD-aws-config-compliance.md", "link": "https://github.com/CPAtoCybersecurity/csf_profile/blob/main/ASSESSMENT_CATALOG/5_Artifacts/Evidence/EVD-aws-config-compliance.md", @@ -449,12 +465,12 @@ export const COMPREHENSIVE_ARTIFACTS = [ "type": "Evidence", "createdDate": "2026-04-30T00:00:00.000Z", "lastModified": "2026-04-30T00:00:00.000Z", - "jiraKey": "AR-128", + "jiraKey": "AR-129", "status": "ACTIVE" }, { - "id": 1028, - "artifactId": "AR-129", + "id": 1029, + "artifactId": "AR-130", "name": "�-k_�&D��_�\u0016T�s��R��&�l�A���nY�w\u0004�5?�J�I\u0010\u000e�T;P���8N��d�tz��^l9�0nm�z�d�5��\u000bb�c�'�hW�qDh�6�6$��GQK-VH�5Q��>l*{\u0017l*{\u0017 Date: Sun, 2 Aug 2026 10:26:39 -0400 Subject: [PATCH 2/4] Add Portfolio Overview to dashboard: status pie, owner bars, unassigned KPIs - Assessments-by-status pie with per-evaluation status table - Controls-by-owner horizontal bar chart sorted highest to lowest, Unassigned aggregated in neutral gray - Unassigned Controls / Unassigned Assessments KPI cards with N-of-M context - Workspace-wide rollups, independent of selected assessment/quarter --- src/pages/Dashboard.js | 231 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) diff --git a/src/pages/Dashboard.js b/src/pages/Dashboard.js index 0c93153..e4f1052 100644 --- a/src/pages/Dashboard.js +++ b/src/pages/Dashboard.js @@ -22,9 +22,11 @@ import { ReferenceLine, LineChart, Line, + LabelList, } from 'recharts'; import useAssessmentsStore from '../stores/assessmentsStore'; import useControlsStore from '../stores/controlsStore'; +import useUserStore from '../stores/userStore'; import useRequirementsStore from '../stores/requirementsStore'; import useUIStore from '../stores/uiStore'; import useFindingsStore from '../stores/findingsStore'; @@ -98,6 +100,8 @@ const STATUS_ORDER = ['Not Started', 'In Progress', 'Submitted', 'Complete', 'Bl const Dashboard = () => { const assessments = useAssessmentsStore((state) => state.assessments); const getControl = useControlsStore((state) => state.getControl); + const controls = useControlsStore((state) => state.controls); + const getUserById = useUserStore((state) => state.getUserById); const requirements = useRequirementsStore((state) => state.requirements); const darkMode = useUIStore((state) => state.darkMode); const findings = useFindingsStore((state) => state.findings); @@ -603,6 +607,69 @@ const Dashboard = () => { }, [selectedAssessment, dashboardData, selectedQuarter, artifacts, findings]); // ── End KPI Cards ────────────────────────────────────────────────────────── + // ── Portfolio Overview ───────────────────────────────────────────────────── + // Workspace-wide rollups across ALL assessments and ALL controls. These are + // deliberately independent of selectedAssessmentId / selectedQuarter — they + // answer "how is the whole program doing", not "how is this eval doing". + + // Assessments by status (assessment.status: Not Started | In Progress | Complete) + const portfolioStatusData = useMemo(() => { + const counts = assessments.reduce((acc, a) => { + const status = a.status || 'Not Started'; + acc[status] = (acc[status] || 0) + 1; + return acc; + }, {}); + + return Object.entries(counts) + .map(([name, value]) => ({ + name, + value, + color: STATUS_COLORS[name] || '#9ca3af', + })) + .sort((a, b) => { + const indexA = STATUS_ORDER.indexOf(a.name); + const indexB = STATUS_ORDER.indexOf(b.name); + if (indexA === -1 && indexB === -1) return a.name.localeCompare(b.name); + if (indexA === -1) return 1; + if (indexB === -1) return -1; + return indexA - indexB; + }); + }, [assessments]); + + const portfolioStatusTotal = useMemo(() => { + return portfolioStatusData.reduce((sum, item) => sum + item.value, 0); + }, [portfolioStatusData]); + + // Controls per owner, sorted highest count first. Null/missing ownerId + // aggregates into "Unassigned"; a deleted user's ID degrades to a stable + // fallback label rather than crashing the lookup. + const controlOwnerChartData = useMemo(() => { + const counts = new Map(); + controls.forEach(c => { + const key = c.ownerId || null; + counts.set(key, (counts.get(key) || 0) + 1); + }); + + return Array.from(counts.entries()) + .map(([ownerId, count]) => ({ + name: ownerId === null + ? 'Unassigned' + : (getUserById(ownerId)?.name || `User ${ownerId}`), + count, + unassigned: ownerId === null, + })) + .sort((a, b) => b.count - a.count || a.name.localeCompare(b.name)); + }, [controls, getUserById]); + + const unassignedControlsCount = useMemo(() => { + return controls.filter(c => !c.ownerId).length; + }, [controls]); + + const unassignedAssessmentsCount = useMemo(() => { + return assessments.filter(a => !a.users || a.users.length === 0).length; + }, [assessments]); + // ── End Portfolio Overview ───────────────────────────────────────────────── + if (assessments.length === 0) { return (
@@ -1219,6 +1286,170 @@ const Dashboard = () => { )} + {/* Portfolio Overview — workspace-wide, independent of the selected assessment */} +
+

Portfolio Overview

+ + {/* Assignment coverage KPI cards */} +
+ + + + +
+ + {/* Assessments by Status Pie Chart */} +
+
+

Assessment Status — All Evaluations

+
+ +
+ {/* Pie Chart */} +
+ {portfolioStatusData.length > 0 ? ( + + + ( + + {`${name} (${(percent * 100).toFixed(0)}%)`} + + )} + outerRadius={100} + fill="#8884d8" + dataKey="value" + > + {portfolioStatusData.map((entry, index) => ( + + ))} + + [`${value} assessments (${((value / portfolioStatusTotal) * 100).toFixed(1)}%)`, name]} + contentStyle={{ backgroundColor: chartColors.background, border: `1px solid ${chartColors.border}`, borderRadius: 8, color: chartColors.text }} + /> + + + ) : ( +
+ No assessments yet. +
+ )} +
+ + {/* Per-evaluation status table */} +
+

Status by Evaluation

+ + + + + + + + + {assessments.map((a, index) => { + const status = a.status || 'Not Started'; + return ( + + + + + ); + })} + {assessments.length > 0 && ( + + + + + )} + +
AssessmentStatus
{a.name} +
+
+ {status} +
+
Total{portfolioStatusTotal} assessments
+
+
+
+ + {/* Controls by Owner Bar Chart */} +
+
+

Controls by Owner

+
+ + {controlOwnerChartData.length > 0 ? ( + + + + + + [`${value} control${value === 1 ? '' : 's'}`, 'Controls']} + cursor={{ fill: 'rgba(148, 163, 184, 0.1)' }} + contentStyle={{ backgroundColor: chartColors.background, border: `1px solid ${chartColors.border}`, borderRadius: 6, fontSize: 12, color: chartColors.text }} + /> + + {controlOwnerChartData.map((entry, index) => ( + + ))} + + + + + ) : ( +
+ No controls in the library yet. +
+ )} +
+
+ {/* Audit Report Metadata Modal */} {showAuditModal && (
From 423b0df842e602ae123b4ee30c7849feb3dac05a Mon Sep 17 00:00:00 2001 From: CPAtoCybersecurity <150057814+CPAtoCybersecurity@users.noreply.github.com> Date: Sun, 2 Aug 2026 10:13:51 -0400 Subject: [PATCH 3/4] Make the web app an installable PWA with offline support - manifest.json: real 192/512 icons (manifest previously declared false sizes against one 365x383 logo), maskable variant, id './' preserving pre-manifest install identity, black background_color matching the terminal-theme shell - Replace stock React-logo icons with Simply Cyber shield icons (transparent 192/512, white-tile maskable + apple-touch-icon) - service-worker.js, three request classes (same-origin GET only, /api/ excluded): navigations network-first with ok-gated shell caching and offline fallback; content-hashed /static/ cache-first; everything else (CSV datasets) stale-while-revalidate so installed users keep getting dataset updates; redirected responses re-wrapped before caching (Chrome refuses redirected shells for navigations); SPA-fallback HTML never cached under asset URLs - stamp-service-worker.mjs appends a build fingerprint post-build so SW bytes change per deploy and the precache re-runs (idempotent) - Register the worker in production builds only; feature-checked so dev server, older browsers, and the Tauri webview are untouched - pwa.test.js: manifest fields + icon IHDR pixel-size probes, vm-driven service worker behavior tests (request classes, cache strategies, error-response gating, redirect normalization), production-path registration tests - INSTALL_THE_APP: document the browser-install path (no Node/Rust) Offline-boot verified: production build served, SW installed, server killed, cold reload renders the full app from SW cache. --- INSTALL_THE_APP/README.md | 6 + package.json | 2 +- public/apple-touch-icon.png | Bin 0 -> 6242 bytes public/index.html | 2 +- public/logo192.png | Bin 5347 -> 11861 bytes public/logo512.png | Bin 9664 -> 40032 bytes public/manifest.json | 32 +-- public/maskable-512.png | Bin 0 -> 21772 bytes public/service-worker.js | 167 ++++++++++++++++ scripts/stamp-service-worker.mjs | 14 ++ src/index.js | 4 + src/pwa.test.js | 329 +++++++++++++++++++++++++++++++ src/serviceWorkerRegistration.js | 32 +++ 13 files changed, 573 insertions(+), 15 deletions(-) create mode 100644 public/apple-touch-icon.png create mode 100644 public/maskable-512.png create mode 100644 public/service-worker.js create mode 100644 scripts/stamp-service-worker.mjs create mode 100644 src/pwa.test.js create mode 100644 src/serviceWorkerRegistration.js diff --git a/INSTALL_THE_APP/README.md b/INSTALL_THE_APP/README.md index 5003b68..693b435 100644 --- a/INSTALL_THE_APP/README.md +++ b/INSTALL_THE_APP/README.md @@ -10,6 +10,12 @@ This is one of three ways to use the CSF Profile Assessment Database: | `GET_THE_SPREADSHEETS/` | You just want CSV/Excel artifacts to work in spreadsheets. | | `GET_THE_NOTION_TEMPLATE/` | You prefer to assess inside Notion. Quick-start bundle and import guide. | +## Install from your browser (no toolchain) + +The web app is a Progressive Web App: open a **production copy** in Chrome or Edge — a hosted deployment, or a local build served with `npm run build` then `npx serve -s build` — and click the **install icon** in the address bar (or menu → *Install CSF Profile Assessment*). You get a standalone app window, a launcher/dock icon, and offline support: after the first visit the app opens and runs with no connection. (The `npm start` dev server deliberately skips the offline worker so hot reload never fights a cache.) Your data already lives entirely in your browser's local storage, so nothing about installing changes where data goes. + +This is the fastest path on machines where you can't install Node or Rust — nothing to build, nothing to run as admin. + ## Quick start See the main [README — Installation and Setup](../README.md#installation-and-setup) for the full walkthrough. Short version: diff --git a/package.json b/package.json index 5f05f2d..88e7bc0 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ }, "scripts": { "start": "react-scripts start", - "build": "react-scripts build", + "build": "react-scripts build && node scripts/stamp-service-worker.mjs", "test": "react-scripts test", "eject": "react-scripts eject", "tauri": "tauri", diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..6edf9ed7c8d6d8da93ce979f9fffa45a0c5176ed GIT binary patch literal 6242 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 literal 0 HcmV?d00001 diff --git a/public/index.html b/public/index.html index 40ce914..af6e4d8 100644 --- a/public/index.html +++ b/public/index.html @@ -9,7 +9,7 @@ name="description" content="Free open-source NIST CSF 2.0 maturity assessment tool — score all 106 subcategories, track quarterly progress, visualize gaps on a dashboard radar, and export audit-ready CSV workpapers." /> - +