riksdagsmonitor 0.8.5
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/LICENSE +201 -0
- package/README.md +999 -0
- package/SECURITY.md +154 -0
- package/dist/lib/chart-factory.d.ts +45 -0
- package/dist/lib/chart-factory.d.ts.map +1 -0
- package/dist/lib/chart-factory.js +220 -0
- package/dist/lib/chart-factory.js.map +1 -0
- package/dist/lib/data-loader.d.ts +44 -0
- package/dist/lib/data-loader.d.ts.map +1 -0
- package/dist/lib/data-loader.js +159 -0
- package/dist/lib/data-loader.js.map +1 -0
- package/dist/lib/dom-utils.d.ts +58 -0
- package/dist/lib/dom-utils.d.ts.map +1 -0
- package/dist/lib/dom-utils.js +153 -0
- package/dist/lib/dom-utils.js.map +1 -0
- package/dist/lib/error-boundary.d.ts +43 -0
- package/dist/lib/error-boundary.d.ts.map +1 -0
- package/dist/lib/error-boundary.js +101 -0
- package/dist/lib/error-boundary.js.map +1 -0
- package/dist/lib/fallback-ui.d.ts +28 -0
- package/dist/lib/fallback-ui.d.ts.map +1 -0
- package/dist/lib/fallback-ui.js +65 -0
- package/dist/lib/fallback-ui.js.map +1 -0
- package/dist/lib/index.d.ts +20 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +20 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/logger.d.ts +19 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +31 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/theme.d.ts +107 -0
- package/dist/lib/theme.d.ts.map +1 -0
- package/dist/lib/theme.js +207 -0
- package/dist/lib/theme.js.map +1 -0
- package/dist/lib/types.d.ts +95 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +14 -0
- package/dist/lib/types.js.map +1 -0
- package/package.json +140 -0
package/SECURITY.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://hack23.com/icon-192.png" alt="Hack23 Logo" width="192" height="192">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">🔐 Security Policy — Riksdagsmonitor</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>🛡️ Security Through Transparency and Vulnerability Management</strong><br>
|
|
9
|
+
<em>🎯 Defense-in-Depth Architecture for Democratic Intelligence</em>
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
<p align="center">
|
|
13
|
+
<a href="#"><img src="https://img.shields.io/badge/Owner-CEO-0A66C2?style=for-the-badge" alt="Owner"/></a>
|
|
14
|
+
<a href="#"><img src="https://img.shields.io/badge/Version-1.0-555?style=for-the-badge" alt="Version"/></a>
|
|
15
|
+
<a href="#"><img src="https://img.shields.io/badge/Effective-2026--02--20-success?style=for-the-badge" alt="Effective Date"/></a>
|
|
16
|
+
<a href="#"><img src="https://img.shields.io/badge/Review-Quarterly-orange?style=for-the-badge" alt="Review Cycle"/></a>
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
**📋 Document Owner:** CEO | **📄 Version:** 1.0 | **📅 Last Updated:** 2026-02-20 (UTC)
|
|
20
|
+
**🔄 Review Cycle:** Quarterly | **⏰ Next Review:** 2026-05-20
|
|
21
|
+
**🏢 Owner:** Hack23 AB (Org.nr 5595347807) | **🏷️ Classification:** Public
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 🎯 **Purpose Statement**
|
|
26
|
+
|
|
27
|
+
This security policy establishes vulnerability disclosure and incident response procedures for Riksdagsmonitor, implementing [Vulnerability Management](https://github.com/Hack23/ISMS-PUBLIC/blob/main/Vulnerability_Management.md) and [Incident Response Plan](https://github.com/Hack23/ISMS-PUBLIC/blob/main/Incident_Response_Plan.md) from Hack23 AB's ISMS framework.
|
|
28
|
+
|
|
29
|
+
Our security approach demonstrates our commitment to **transparency** and **operational excellence**, ensuring that vulnerabilities are managed systematically with documented response times and coordinated disclosure processes.
|
|
30
|
+
|
|
31
|
+
*— James Pether Sörling, CEO/Founder*
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Supported Versions
|
|
36
|
+
|
|
37
|
+
This project is under active development, and we provide security updates for the latest version only.
|
|
38
|
+
|
|
39
|
+
| Version | Supported | ISMS Policy |
|
|
40
|
+
| ------- | ------------------ | ----------- |
|
|
41
|
+
| latest | :white_check_mark: | [Vulnerability Management](https://github.com/Hack23/ISMS-PUBLIC/blob/main/Vulnerability_Management.md) |
|
|
42
|
+
|
|
43
|
+
## Security Posture
|
|
44
|
+
|
|
45
|
+
Riksdagsmonitor maintains strong security practices as documented in our [Security Architecture](SECURITY_ARCHITECTURE.md):
|
|
46
|
+
|
|
47
|
+
### Current Security Controls
|
|
48
|
+
|
|
49
|
+
- ✅ **Static Site Architecture** — No server-side code execution, no database vulnerabilities
|
|
50
|
+
- ✅ **HTTPS-Only** — TLS 1.3 via AWS CloudFront and GitHub Pages
|
|
51
|
+
- ✅ **Automated Security Scanning** — CodeQL, Dependabot, Secret Scanning
|
|
52
|
+
- ✅ **Supply Chain Security** — SHA-pinned GitHub Actions, step-security/harden-runner
|
|
53
|
+
- ✅ **Multi-Region Availability** — AWS CloudFront (us-east-1 primary, eu-west-1 replica) with GitHub Pages DR
|
|
54
|
+
- ✅ **SLSA Build Provenance** — Attestation for build integrity
|
|
55
|
+
- ✅ **Content Integrity** — Subresource Integrity (SRI) for CDN assets
|
|
56
|
+
- ✅ **Security Headers** — CSP, HSTS, X-Frame-Options, X-Content-Type-Options
|
|
57
|
+
|
|
58
|
+
**Evidence:**
|
|
59
|
+
- [](https://scorecard.dev/viewer/?uri=github.com/Hack23/riksdagsmonitor)
|
|
60
|
+
- [Security Overview](https://github.com/Hack23/riksdagsmonitor/security)
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Reporting a Vulnerability
|
|
65
|
+
|
|
66
|
+
We take the security of Riksdagsmonitor seriously. If you have found a potential security vulnerability, we kindly ask you to report it privately, so that we can assess and address the issue before it becomes publicly known.
|
|
67
|
+
|
|
68
|
+
### What Constitutes a Vulnerability
|
|
69
|
+
|
|
70
|
+
A vulnerability is a weakness or flaw in the project that can be exploited to compromise the security, integrity, or availability of the system or its data. Examples include, but are not limited to:
|
|
71
|
+
|
|
72
|
+
- Cross-site scripting (XSS) in generated content
|
|
73
|
+
- Insecure external resource loading
|
|
74
|
+
- Exposed secrets or credentials
|
|
75
|
+
- Supply chain vulnerabilities in dependencies
|
|
76
|
+
- Content injection through data pipelines
|
|
77
|
+
|
|
78
|
+
### How to Privately Report a Vulnerability using GitHub
|
|
79
|
+
|
|
80
|
+
1. On GitHub.com, navigate to the main page of the [riksdagsmonitor repository](https://github.com/Hack23/riksdagsmonitor).
|
|
81
|
+
2. Under the repository name, click **Security**.
|
|
82
|
+
3. In the left sidebar, under "Reporting", click **Advisories**.
|
|
83
|
+
4. Click **Report a vulnerability** to open the advisory form.
|
|
84
|
+
5. Fill in the advisory details form with as much information as possible.
|
|
85
|
+
6. At the bottom of the form, click **Submit report**.
|
|
86
|
+
|
|
87
|
+
### Disclosure Timeline
|
|
88
|
+
|
|
89
|
+
Upon receipt of a vulnerability report, our team will:
|
|
90
|
+
|
|
91
|
+
1. Acknowledge the report within **48 hours**
|
|
92
|
+
2. Validate the vulnerability within **7 days**
|
|
93
|
+
3. Develop and release a patch or mitigation within **30 days** (depending on complexity and severity)
|
|
94
|
+
4. Publish a security advisory with a detailed description of the vulnerability and the fix
|
|
95
|
+
|
|
96
|
+
### Recognition and Anonymity
|
|
97
|
+
|
|
98
|
+
We appreciate your effort in helping us maintain a secure project. If your report results in a confirmed security fix, we will recognize your contribution in the release notes, unless you request to remain anonymous.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 🔐 ISMS Framework Integration
|
|
103
|
+
|
|
104
|
+
Riksdagsmonitor's security practices are part of Hack23 AB's comprehensive Information Security Management System (ISMS):
|
|
105
|
+
|
|
106
|
+
### 📋 Related ISMS Policies
|
|
107
|
+
|
|
108
|
+
| 🛡️ **Policy** | 📊 **Application to Riksdagsmonitor** |
|
|
109
|
+
|--------------|--------------------------------------|
|
|
110
|
+
| [Vulnerability Management](https://github.com/Hack23/ISMS-PUBLIC/blob/main/Vulnerability_Management.md) | 48h response SLA, coordinated disclosure process |
|
|
111
|
+
| [Incident Response Plan](https://github.com/Hack23/ISMS-PUBLIC/blob/main/Incident_Response_Plan.md) | P1-P4 incident classification, escalation procedures |
|
|
112
|
+
| [Secure Development Policy](https://github.com/Hack23/ISMS-PUBLIC/blob/main/Secure_Development_Policy.md) | Security testing requirements, code review standards |
|
|
113
|
+
| [Information Security Policy](https://github.com/Hack23/ISMS-PUBLIC/blob/main/Information_Security_Policy.md) | Overall security governance framework |
|
|
114
|
+
| [Network Security Policy](https://github.com/Hack23/ISMS-PUBLIC/blob/main/Network_Security_Policy.md) | HTTPS-only, TLS 1.3, CDN security |
|
|
115
|
+
| [Cryptography Policy](https://github.com/Hack23/ISMS-PUBLIC/blob/main/Cryptography_Policy.md) | TLS configuration, SRI hashes |
|
|
116
|
+
|
|
117
|
+
### 🔍 Comprehensive Security Documentation
|
|
118
|
+
|
|
119
|
+
- **🛡️ Security Architecture:** [SECURITY_ARCHITECTURE.md](SECURITY_ARCHITECTURE.md) — Defense-in-depth controls
|
|
120
|
+
- **🎯 Threat Model:** [THREAT_MODEL.md](THREAT_MODEL.md) — STRIDE analysis, MITRE ATT&CK mapping
|
|
121
|
+
- **🔮 Future Security:** [FUTURE_SECURITY_ARCHITECTURE.md](FUTURE_SECURITY_ARCHITECTURE.md) — Security roadmap
|
|
122
|
+
- **🔧 CI/CD Security:** [WORKFLOWS.md](WORKFLOWS.md) — Pipeline security controls
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 📚 Related Documents
|
|
127
|
+
|
|
128
|
+
### 🔐 Security & Architecture
|
|
129
|
+
- [🛡️ Security Architecture](SECURITY_ARCHITECTURE.md) — System security design
|
|
130
|
+
- [🎯 Threat Model](THREAT_MODEL.md) — Comprehensive threat analysis
|
|
131
|
+
- [🔮 Future Security Architecture](FUTURE_SECURITY_ARCHITECTURE.md) — Security roadmap
|
|
132
|
+
- [🏗️ Architecture](ARCHITECTURE.md) — System architecture (C4 models)
|
|
133
|
+
- [🔧 Workflows](WORKFLOWS.md) — CI/CD pipeline documentation
|
|
134
|
+
|
|
135
|
+
### 📋 Project Governance
|
|
136
|
+
- [🤝 Contributing Guidelines](CONTRIBUTING.md) — Secure contribution process
|
|
137
|
+
- [📜 Code of Conduct](CODE_OF_CONDUCT.md) — Community standards
|
|
138
|
+
- [📋 README](README.md) — Project overview and classification
|
|
139
|
+
|
|
140
|
+
### 🛡️ ISMS Policies (Hack23 AB)
|
|
141
|
+
- [🔐 Information Security Policy](https://github.com/Hack23/ISMS-PUBLIC/blob/main/Information_Security_Policy.md)
|
|
142
|
+
- [🔍 Vulnerability Management](https://github.com/Hack23/ISMS-PUBLIC/blob/main/Vulnerability_Management.md)
|
|
143
|
+
- [🚨 Incident Response Plan](https://github.com/Hack23/ISMS-PUBLIC/blob/main/Incident_Response_Plan.md)
|
|
144
|
+
- [🛠️ Secure Development Policy](https://github.com/Hack23/ISMS-PUBLIC/blob/main/Secure_Development_Policy.md)
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
**📋 Document Control:**
|
|
149
|
+
**✅ Approved by:** James Pether Sörling, CEO
|
|
150
|
+
**📤 Distribution:** Public
|
|
151
|
+
**🏷️ Classification:** [](https://github.com/Hack23/ISMS-PUBLIC/blob/main/CLASSIFICATION.md#confidentiality-levels)
|
|
152
|
+
**📅 Effective Date:** 2026-02-20
|
|
153
|
+
**⏰ Next Review:** 2026-05-20
|
|
154
|
+
**🎯 Framework Compliance:** [](https://github.com/Hack23/ISMS-PUBLIC/blob/main/CLASSIFICATION.md) [](https://github.com/Hack23/ISMS-PUBLIC/blob/main/CLASSIFICATION.md) [](https://github.com/Hack23/ISMS-PUBLIC/blob/main/CLASSIFICATION.md)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Shared/ChartFactory
|
|
3
|
+
* @description Centralized Chart.js creation and configuration.
|
|
4
|
+
* Replaces 51+ independent `new Chart()` calls with consistent theming,
|
|
5
|
+
* responsive behavior, and accessibility features.
|
|
6
|
+
*
|
|
7
|
+
* @security No inline scripts — all Chart.js configuration is programmatic
|
|
8
|
+
|
|
9
|
+
*
|
|
10
|
+
* @intelligence Standardized intelligence visualization factory — ensures all analytical charts (risk heat maps, coalition networks, electoral forecasts) maintain consistent OSINT presentation standards with accessibility compliance for briefing-quality output.
|
|
11
|
+
*
|
|
12
|
+
* @business Development velocity multiplier — centralizing Chart.js configuration eliminates per-dashboard setup cost (reduced from 51+ `new Chart()` calls). Enables rapid prototyping of new intelligence products with predictable quality and performance.
|
|
13
|
+
*
|
|
14
|
+
* @marketing Visual quality assurance — every chart produced is screenshot-ready for social media, press releases, and reports. Consistent styling builds brand recognition. Responsive behavior ensures mobile-quality content for all distribution channels.
|
|
15
|
+
* */
|
|
16
|
+
import type { Chart as ChartType, ChartConfiguration, ChartTypeRegistry } from 'chart.js';
|
|
17
|
+
import { THEME_COLORS, CHART_PALETTE, BREAKPOINTS, getActiveThemeColors, getChartPalette } from './theme.js';
|
|
18
|
+
export { THEME_COLORS, CHART_PALETTE, BREAKPOINTS, getActiveThemeColors, getChartPalette };
|
|
19
|
+
/**
|
|
20
|
+
* Get responsive chart options based on current viewport.
|
|
21
|
+
*/
|
|
22
|
+
export declare function getResponsiveOptions(): Record<string, unknown>;
|
|
23
|
+
/**
|
|
24
|
+
* Create a Chart.js chart with consistent theming and responsive behavior.
|
|
25
|
+
*
|
|
26
|
+
* @param canvas - Canvas element or its ID
|
|
27
|
+
* @param config - Chart.js configuration
|
|
28
|
+
* @param containerId - Optional container ID for loading/error states
|
|
29
|
+
* @returns The Chart instance
|
|
30
|
+
*/
|
|
31
|
+
export declare function createChart<T extends keyof ChartTypeRegistry>(canvas: HTMLCanvasElement | string, config: ChartConfiguration<T>, containerId?: string): ChartType<T>;
|
|
32
|
+
/**
|
|
33
|
+
* Safely initialize a dashboard section.
|
|
34
|
+
* Shows loading state, runs the initializer, shows error state on failure.
|
|
35
|
+
*/
|
|
36
|
+
export declare function initDashboardSection(containerId: string, initializer: () => Promise<void>, loadingMessage?: string): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Add keyboard navigation to a Chart.js chart for accessibility.
|
|
39
|
+
*/
|
|
40
|
+
export declare function addChartKeyboardNav(chart: ChartType, canvas: HTMLCanvasElement): void;
|
|
41
|
+
/**
|
|
42
|
+
* Create a resize handler that re-creates charts on breakpoint changes.
|
|
43
|
+
*/
|
|
44
|
+
export declare function createResizeHandler(callback: () => void): () => void;
|
|
45
|
+
//# sourceMappingURL=chart-factory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chart-factory.d.ts","sourceRoot":"","sources":["../../src/browser/shared/chart-factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;KAcK;AAEL,OAAO,KAAK,EAAE,KAAK,IAAI,SAAS,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC1F,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAK7G,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,oBAAoB,EAAE,eAAe,EAAE,CAAC;AAY3F;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAkD9D;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,iBAAiB,EAC3D,MAAM,EAAE,iBAAiB,GAAG,MAAM,EAClC,MAAM,EAAE,kBAAkB,CAAC,CAAC,CAAC,EAC7B,WAAW,CAAC,EAAE,MAAM,GACnB,SAAS,CAAC,CAAC,CAAC,CAkCd;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EAChC,cAAc,SAAoB,GACjC,OAAO,CAAC,IAAI,CAAC,CAmBf;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,iBAAiB,GACxB,IAAI,CAwBN;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAiBpE"}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Shared/ChartFactory
|
|
3
|
+
* @description Centralized Chart.js creation and configuration.
|
|
4
|
+
* Replaces 51+ independent `new Chart()` calls with consistent theming,
|
|
5
|
+
* responsive behavior, and accessibility features.
|
|
6
|
+
*
|
|
7
|
+
* @security No inline scripts — all Chart.js configuration is programmatic
|
|
8
|
+
|
|
9
|
+
*
|
|
10
|
+
* @intelligence Standardized intelligence visualization factory — ensures all analytical charts (risk heat maps, coalition networks, electoral forecasts) maintain consistent OSINT presentation standards with accessibility compliance for briefing-quality output.
|
|
11
|
+
*
|
|
12
|
+
* @business Development velocity multiplier — centralizing Chart.js configuration eliminates per-dashboard setup cost (reduced from 51+ `new Chart()` calls). Enables rapid prototyping of new intelligence products with predictable quality and performance.
|
|
13
|
+
*
|
|
14
|
+
* @marketing Visual quality assurance — every chart produced is screenshot-ready for social media, press releases, and reports. Consistent styling builds brand recognition. Responsive behavior ensures mobile-quality content for all distribution channels.
|
|
15
|
+
* */
|
|
16
|
+
import { THEME_COLORS, CHART_PALETTE, BREAKPOINTS, getActiveThemeColors, getChartPalette } from './theme.js';
|
|
17
|
+
import { showLoadingState, showErrorState, hideStateOverlays } from './dom-utils.js';
|
|
18
|
+
import { logger } from './logger.js';
|
|
19
|
+
// Re-export for convenience
|
|
20
|
+
export { THEME_COLORS, CHART_PALETTE, BREAKPOINTS, getActiveThemeColors, getChartPalette };
|
|
21
|
+
/**
|
|
22
|
+
* Get the Chart constructor.
|
|
23
|
+
* Works with both global `Chart` (from script tag) and ES module import.
|
|
24
|
+
*/
|
|
25
|
+
function getChart() {
|
|
26
|
+
const g = globalThis;
|
|
27
|
+
if (g.Chart)
|
|
28
|
+
return g.Chart;
|
|
29
|
+
throw new Error('Chart.js not loaded');
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Get responsive chart options based on current viewport.
|
|
33
|
+
*/
|
|
34
|
+
export function getResponsiveOptions() {
|
|
35
|
+
const width = window.innerWidth;
|
|
36
|
+
const isMobile = width < BREAKPOINTS.tablet;
|
|
37
|
+
const isTablet = width >= BREAKPOINTS.tablet && width < BREAKPOINTS.desktop;
|
|
38
|
+
const theme = getActiveThemeColors();
|
|
39
|
+
return {
|
|
40
|
+
responsive: true,
|
|
41
|
+
maintainAspectRatio: false,
|
|
42
|
+
animation: { duration: isMobile ? 0 : 400 },
|
|
43
|
+
plugins: {
|
|
44
|
+
legend: {
|
|
45
|
+
position: isMobile ? 'bottom' : 'top',
|
|
46
|
+
labels: {
|
|
47
|
+
boxWidth: isMobile ? 8 : 12,
|
|
48
|
+
padding: isMobile ? 4 : 8,
|
|
49
|
+
font: { size: isMobile ? 10 : (isTablet ? 11 : 12) },
|
|
50
|
+
color: theme.bodyText,
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
tooltip: {
|
|
54
|
+
backgroundColor: theme.tooltipBg,
|
|
55
|
+
titleColor: theme.cyan,
|
|
56
|
+
bodyColor: theme.bodyText,
|
|
57
|
+
borderColor: theme.cyan,
|
|
58
|
+
borderWidth: 1,
|
|
59
|
+
padding: isMobile ? 6 : 10,
|
|
60
|
+
titleFont: { size: isMobile ? 11 : 13, weight: 'bold' },
|
|
61
|
+
bodyFont: { size: isMobile ? 10 : 12 },
|
|
62
|
+
cornerRadius: 4,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
scales: {
|
|
66
|
+
x: {
|
|
67
|
+
ticks: {
|
|
68
|
+
color: theme.tickColor,
|
|
69
|
+
font: { size: isMobile ? 9 : 11 },
|
|
70
|
+
maxRotation: isMobile ? 45 : 0,
|
|
71
|
+
},
|
|
72
|
+
grid: { color: theme.gridColor },
|
|
73
|
+
},
|
|
74
|
+
y: {
|
|
75
|
+
ticks: {
|
|
76
|
+
color: theme.tickColor,
|
|
77
|
+
font: { size: isMobile ? 9 : 11 },
|
|
78
|
+
},
|
|
79
|
+
grid: { color: theme.gridColor },
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Create a Chart.js chart with consistent theming and responsive behavior.
|
|
86
|
+
*
|
|
87
|
+
* @param canvas - Canvas element or its ID
|
|
88
|
+
* @param config - Chart.js configuration
|
|
89
|
+
* @param containerId - Optional container ID for loading/error states
|
|
90
|
+
* @returns The Chart instance
|
|
91
|
+
*/
|
|
92
|
+
export function createChart(canvas, config, containerId) {
|
|
93
|
+
const ChartCtor = getChart();
|
|
94
|
+
const canvasEl = typeof canvas === 'string'
|
|
95
|
+
? document.getElementById(canvas)
|
|
96
|
+
: canvas;
|
|
97
|
+
if (!canvasEl) {
|
|
98
|
+
throw new Error(`Canvas element not found: ${canvas}`);
|
|
99
|
+
}
|
|
100
|
+
// Merge responsive options with user config
|
|
101
|
+
const responsive = getResponsiveOptions();
|
|
102
|
+
const mergedConfig = {
|
|
103
|
+
...config,
|
|
104
|
+
options: deepMerge(responsive, config.options ?? {}),
|
|
105
|
+
};
|
|
106
|
+
const container = containerId
|
|
107
|
+
? document.getElementById(containerId)
|
|
108
|
+
: canvasEl.parentElement;
|
|
109
|
+
if (container) {
|
|
110
|
+
hideStateOverlays(container);
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
return new ChartCtor(canvasEl, mergedConfig);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
logger.error(`Failed to create chart:`, error);
|
|
117
|
+
if (container) {
|
|
118
|
+
showErrorState(container, 'Failed to render chart');
|
|
119
|
+
}
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Safely initialize a dashboard section.
|
|
125
|
+
* Shows loading state, runs the initializer, shows error state on failure.
|
|
126
|
+
*/
|
|
127
|
+
export async function initDashboardSection(containerId, initializer, loadingMessage = 'Loading data...') {
|
|
128
|
+
const container = document.getElementById(containerId);
|
|
129
|
+
if (!container) {
|
|
130
|
+
logger.debug(`Container #${containerId} not found — skipping`);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
showLoadingState(container, loadingMessage);
|
|
134
|
+
try {
|
|
135
|
+
await initializer();
|
|
136
|
+
hideStateOverlays(container);
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
logger.error(`Dashboard section ${containerId} failed:`, error);
|
|
140
|
+
showErrorState(container, `Failed to load ${containerId.replace(/-/g, ' ')}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Add keyboard navigation to a Chart.js chart for accessibility.
|
|
145
|
+
*/
|
|
146
|
+
export function addChartKeyboardNav(chart, canvas) {
|
|
147
|
+
canvas.setAttribute('tabindex', '0');
|
|
148
|
+
canvas.setAttribute('role', 'img');
|
|
149
|
+
canvas.addEventListener('keydown', (e) => {
|
|
150
|
+
const meta = chart.getActiveElements();
|
|
151
|
+
const currentIndex = meta.length > 0 ? meta[0].index : -1;
|
|
152
|
+
const datasetIndex = meta.length > 0 ? meta[0].datasetIndex : 0;
|
|
153
|
+
const maxIndex = (chart.data.datasets[datasetIndex]?.data?.length ?? 1) - 1;
|
|
154
|
+
let newIndex = currentIndex;
|
|
155
|
+
if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {
|
|
156
|
+
newIndex = Math.min(currentIndex + 1, maxIndex);
|
|
157
|
+
e.preventDefault();
|
|
158
|
+
}
|
|
159
|
+
else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
|
|
160
|
+
newIndex = Math.max(currentIndex - 1, 0);
|
|
161
|
+
e.preventDefault();
|
|
162
|
+
}
|
|
163
|
+
if (newIndex !== currentIndex && newIndex >= 0) {
|
|
164
|
+
chart.setActiveElements([{ datasetIndex, index: newIndex }]);
|
|
165
|
+
chart.update();
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Create a resize handler that re-creates charts on breakpoint changes.
|
|
171
|
+
*/
|
|
172
|
+
export function createResizeHandler(callback) {
|
|
173
|
+
let currentBreakpoint = getBreakpoint();
|
|
174
|
+
let resizeTimer;
|
|
175
|
+
const handler = () => {
|
|
176
|
+
clearTimeout(resizeTimer);
|
|
177
|
+
resizeTimer = setTimeout(() => {
|
|
178
|
+
const newBreakpoint = getBreakpoint();
|
|
179
|
+
if (newBreakpoint !== currentBreakpoint) {
|
|
180
|
+
currentBreakpoint = newBreakpoint;
|
|
181
|
+
callback();
|
|
182
|
+
}
|
|
183
|
+
}, 250);
|
|
184
|
+
};
|
|
185
|
+
window.addEventListener('resize', handler);
|
|
186
|
+
return () => window.removeEventListener('resize', handler);
|
|
187
|
+
}
|
|
188
|
+
function getBreakpoint() {
|
|
189
|
+
const w = window.innerWidth;
|
|
190
|
+
if (w < BREAKPOINTS.tablet)
|
|
191
|
+
return 'mobile';
|
|
192
|
+
if (w < BREAKPOINTS.desktop)
|
|
193
|
+
return 'tablet';
|
|
194
|
+
if (w < BREAKPOINTS.large)
|
|
195
|
+
return 'desktop';
|
|
196
|
+
return 'large';
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Deep merge two objects (simple version for chart configs).
|
|
200
|
+
*/
|
|
201
|
+
function deepMerge(target, source) {
|
|
202
|
+
const result = { ...target };
|
|
203
|
+
for (const key of Object.keys(source)) {
|
|
204
|
+
const sourceVal = source[key];
|
|
205
|
+
const targetVal = result[key];
|
|
206
|
+
if (sourceVal &&
|
|
207
|
+
typeof sourceVal === 'object' &&
|
|
208
|
+
!Array.isArray(sourceVal) &&
|
|
209
|
+
targetVal &&
|
|
210
|
+
typeof targetVal === 'object' &&
|
|
211
|
+
!Array.isArray(targetVal)) {
|
|
212
|
+
result[key] = deepMerge(targetVal, sourceVal);
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
result[key] = sourceVal;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return result;
|
|
219
|
+
}
|
|
220
|
+
//# sourceMappingURL=chart-factory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chart-factory.js","sourceRoot":"","sources":["../../src/browser/shared/chart-factory.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Shared/DataLoader
|
|
3
|
+
* @description Unified data fetching with fallback, caching, and retry logic.
|
|
4
|
+
* Replaces 6+ independent data loading implementations across dashboards.
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - Local-first with remote fallback
|
|
8
|
+
* - localStorage caching with TTL
|
|
9
|
+
* - Retry with exponential backoff
|
|
10
|
+
* - CSV parsing via PapaParse (CSP-compatible) with simple fallback
|
|
11
|
+
* - JSON and text response handling
|
|
12
|
+
|
|
13
|
+
*
|
|
14
|
+
* @intelligence Resilient OSINT data acquisition pipeline — multi-source intelligence data loading with local-first strategy, remote fallback, localStorage caching (TTL-based), retry with exponential backoff, and CSV/JSON parsing. Ensures continuous intelligence availability even during source outages.
|
|
15
|
+
*
|
|
16
|
+
* @business Platform reliability foundation — data loading resilience directly impacts user experience KPIs (page load time, error rate, Time to Interactive). Caching reduces infrastructure costs and enables offline-capable future PWA offering.
|
|
17
|
+
*
|
|
18
|
+
* @marketing Performance marketing enabler — fast, reliable data loading supports Core Web Vitals targets (LCP < 2.5s, FID < 100ms) critical for SEO ranking and user retention. Reliability metrics are a key selling point for B2G/enterprise prospects.
|
|
19
|
+
* */
|
|
20
|
+
import type { CSVRow, DataSource, LoadOptions } from './types.js';
|
|
21
|
+
/**
|
|
22
|
+
* Load text data from a data source with fallback and caching.
|
|
23
|
+
*/
|
|
24
|
+
export declare function loadText(source: DataSource, options?: LoadOptions): Promise<string>;
|
|
25
|
+
/**
|
|
26
|
+
* Load and parse CSV data from a data source.
|
|
27
|
+
* Uses PapaParse (CSP-compatible) with a simple CSV fallback parser.
|
|
28
|
+
*/
|
|
29
|
+
export declare function loadCSV(source: DataSource, options?: LoadOptions): Promise<CSVRow[]>;
|
|
30
|
+
/**
|
|
31
|
+
* Load and parse JSON data from a data source.
|
|
32
|
+
*/
|
|
33
|
+
export declare function loadJSON<T = unknown>(source: DataSource, options?: LoadOptions): Promise<T>;
|
|
34
|
+
/**
|
|
35
|
+
* Parse CSV text into rows.
|
|
36
|
+
* Uses PapaParse if available (CSP-compatible), falls back to a simple CSV parser.
|
|
37
|
+
* Does NOT use d3.csvParse as it requires unsafe-eval in CSP.
|
|
38
|
+
*/
|
|
39
|
+
export declare function parseCSV(text: string): CSVRow[];
|
|
40
|
+
/**
|
|
41
|
+
* Create a DataSource from local path with optional GitHub raw fallback.
|
|
42
|
+
*/
|
|
43
|
+
export declare function createDataSource(localPath: string, repoPath?: string): DataSource;
|
|
44
|
+
//# sourceMappingURL=data-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-loader.d.ts","sourceRoot":"","sources":["../../src/browser/shared/data-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;KAkBK;AAGL,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAgElE;;GAEG;AACH,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,UAAU,EAClB,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,MAAM,CAAC,CAmCjB;AAED;;;GAGG;AACH,wBAAsB,OAAO,CAC3B,MAAM,EAAE,UAAU,EAClB,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,MAAM,EAAE,CAAC,CAGnB;AAED;;GAEG;AACH,wBAAsB,QAAQ,CAAC,CAAC,GAAG,OAAO,EACxC,MAAM,EAAE,UAAU,EAClB,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,CAAC,CAAC,CAGZ;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAqB/C;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,EACjB,QAAQ,CAAC,EAAE,MAAM,GAChB,UAAU,CAQZ"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Shared/DataLoader
|
|
3
|
+
* @description Unified data fetching with fallback, caching, and retry logic.
|
|
4
|
+
* Replaces 6+ independent data loading implementations across dashboards.
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - Local-first with remote fallback
|
|
8
|
+
* - localStorage caching with TTL
|
|
9
|
+
* - Retry with exponential backoff
|
|
10
|
+
* - CSV parsing via PapaParse (CSP-compatible) with simple fallback
|
|
11
|
+
* - JSON and text response handling
|
|
12
|
+
|
|
13
|
+
*
|
|
14
|
+
* @intelligence Resilient OSINT data acquisition pipeline — multi-source intelligence data loading with local-first strategy, remote fallback, localStorage caching (TTL-based), retry with exponential backoff, and CSV/JSON parsing. Ensures continuous intelligence availability even during source outages.
|
|
15
|
+
*
|
|
16
|
+
* @business Platform reliability foundation — data loading resilience directly impacts user experience KPIs (page load time, error rate, Time to Interactive). Caching reduces infrastructure costs and enables offline-capable future PWA offering.
|
|
17
|
+
*
|
|
18
|
+
* @marketing Performance marketing enabler — fast, reliable data loading supports Core Web Vitals targets (LCP < 2.5s, FID < 100ms) critical for SEO ranking and user retention. Reliability metrics are a key selling point for B2G/enterprise prospects.
|
|
19
|
+
* */
|
|
20
|
+
import { logger } from './logger.js';
|
|
21
|
+
const DEFAULT_CACHE_TTL = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
22
|
+
const DEFAULT_RETRIES = 3;
|
|
23
|
+
const DEFAULT_RETRY_BACKOFF = 2000;
|
|
24
|
+
/**
|
|
25
|
+
* Fetch data from a URL with retry logic.
|
|
26
|
+
*/
|
|
27
|
+
async function fetchWithRetry(url, retries, backoff) {
|
|
28
|
+
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
29
|
+
try {
|
|
30
|
+
const response = await fetch(url);
|
|
31
|
+
if (response.ok)
|
|
32
|
+
return response;
|
|
33
|
+
logger.warn(`Fetch attempt ${attempt}/${retries} failed for ${url}: ${response.status}`);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
logger.warn(`Fetch attempt ${attempt}/${retries} error for ${url}:`, error);
|
|
37
|
+
}
|
|
38
|
+
if (attempt < retries) {
|
|
39
|
+
await new Promise((resolve) => setTimeout(resolve, backoff * attempt));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
throw new Error(`Failed to fetch ${url} after ${retries} attempts`);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Get data from localStorage cache if valid.
|
|
46
|
+
*/
|
|
47
|
+
function getFromCache(key, ttl) {
|
|
48
|
+
try {
|
|
49
|
+
const raw = localStorage.getItem(key);
|
|
50
|
+
if (!raw)
|
|
51
|
+
return null;
|
|
52
|
+
const entry = JSON.parse(raw);
|
|
53
|
+
if (Date.now() - entry.timestamp > ttl) {
|
|
54
|
+
localStorage.removeItem(key);
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
return entry.data;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Store data in localStorage cache.
|
|
65
|
+
*/
|
|
66
|
+
function setCache(key, data) {
|
|
67
|
+
try {
|
|
68
|
+
const entry = { data, timestamp: Date.now() };
|
|
69
|
+
localStorage.setItem(key, JSON.stringify(entry));
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
logger.warn('Failed to cache data — localStorage may be full');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Load text data from a data source with fallback and caching.
|
|
77
|
+
*/
|
|
78
|
+
export async function loadText(source, options = {}) {
|
|
79
|
+
const { cacheKey, cacheTTL = DEFAULT_CACHE_TTL, retries = DEFAULT_RETRIES, retryBackoff = DEFAULT_RETRY_BACKOFF, } = options;
|
|
80
|
+
// Check cache first
|
|
81
|
+
if (cacheKey) {
|
|
82
|
+
const cached = getFromCache(cacheKey, cacheTTL);
|
|
83
|
+
if (cached) {
|
|
84
|
+
logger.debug(`Cache hit for ${cacheKey}`);
|
|
85
|
+
return cached;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Try primary URL, then fallbacks
|
|
89
|
+
const urls = [source.primary, ...(source.fallbacks ?? [])];
|
|
90
|
+
let lastError = null;
|
|
91
|
+
for (const url of urls) {
|
|
92
|
+
try {
|
|
93
|
+
const response = await fetchWithRetry(url, retries, retryBackoff);
|
|
94
|
+
const text = await response.text();
|
|
95
|
+
if (cacheKey)
|
|
96
|
+
setCache(cacheKey, text);
|
|
97
|
+
logger.debug(`Loaded ${url} (${text.length} bytes)`);
|
|
98
|
+
return text;
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
102
|
+
logger.warn(`Failed to load from ${url}, trying next fallback...`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
throw lastError ?? new Error('No data sources provided');
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Load and parse CSV data from a data source.
|
|
109
|
+
* Uses PapaParse (CSP-compatible) with a simple CSV fallback parser.
|
|
110
|
+
*/
|
|
111
|
+
export async function loadCSV(source, options = {}) {
|
|
112
|
+
const text = await loadText(source, options);
|
|
113
|
+
return parseCSV(text);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Load and parse JSON data from a data source.
|
|
117
|
+
*/
|
|
118
|
+
export async function loadJSON(source, options = {}) {
|
|
119
|
+
const text = await loadText(source, options);
|
|
120
|
+
return JSON.parse(text);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Parse CSV text into rows.
|
|
124
|
+
* Uses PapaParse if available (CSP-compatible), falls back to a simple CSV parser.
|
|
125
|
+
* Does NOT use d3.csvParse as it requires unsafe-eval in CSP.
|
|
126
|
+
*/
|
|
127
|
+
export function parseCSV(text) {
|
|
128
|
+
// Use PapaParse if available (CSP-compatible, no unsafe-eval needed)
|
|
129
|
+
const PapaGlobal = globalThis.Papa;
|
|
130
|
+
if (PapaGlobal?.parse) {
|
|
131
|
+
return PapaGlobal.parse(text, { header: true, skipEmptyLines: true }).data;
|
|
132
|
+
}
|
|
133
|
+
// CSP-safe fallback: simple CSV parser
|
|
134
|
+
const lines = text.trim().split('\n');
|
|
135
|
+
if (lines.length < 2)
|
|
136
|
+
return [];
|
|
137
|
+
const headers = lines[0].split(',').map((h) => h.trim().replace(/^"|"$/g, ''));
|
|
138
|
+
return lines.slice(1).filter((l) => l.trim()).map((line) => {
|
|
139
|
+
const values = line.split(',').map((v) => v.trim().replace(/^"|"$/g, ''));
|
|
140
|
+
const row = {};
|
|
141
|
+
headers.forEach((header, i) => {
|
|
142
|
+
row[header] = values[i] ?? '';
|
|
143
|
+
});
|
|
144
|
+
return row;
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Create a DataSource from local path with optional GitHub raw fallback.
|
|
149
|
+
*/
|
|
150
|
+
export function createDataSource(localPath, repoPath) {
|
|
151
|
+
const source = { primary: localPath };
|
|
152
|
+
if (repoPath) {
|
|
153
|
+
source.fallbacks = [
|
|
154
|
+
`https://raw.githubusercontent.com/Hack23/cia/master/${repoPath}`,
|
|
155
|
+
];
|
|
156
|
+
}
|
|
157
|
+
return source;
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=data-loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-loader.js","sourceRoot":"","sources":["../../src/browser/shared/data-loader.ts"],"names":[],"mappings":"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"}
|