@amiable-dev/docusaurus-plugin-stentorosaur 0.19.0 → 0.21.0
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/lib/context/StatusDataProvider.d.ts +101 -0
- package/lib/context/StatusDataProvider.d.ts.map +1 -0
- package/lib/context/StatusDataProvider.js +266 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +128 -1
- package/lib/options.d.ts.map +1 -1
- package/lib/options.js +3 -0
- package/lib/theme/StatusBadge/index.d.ts +54 -0
- package/lib/theme/StatusBadge/index.d.ts.map +1 -0
- package/lib/theme/StatusBadge/index.js +63 -0
- package/lib/theme/StatusBadge/styles.module.css +120 -0
- package/lib/theme/SystemCard/index.d.ts +98 -0
- package/lib/theme/SystemCard/index.d.ts.map +1 -0
- package/lib/theme/SystemCard/index.js +125 -0
- package/lib/theme/SystemCard/styles.module.css +208 -0
- package/lib/theme/SystemCardGroup/index.d.ts +47 -0
- package/lib/theme/SystemCardGroup/index.d.ts.map +1 -0
- package/lib/theme/SystemCardGroup/index.js +113 -0
- package/lib/theme/SystemCardGroup/styles.module.css +180 -0
- package/lib/theme/UptimeBar/index.d.ts +69 -0
- package/lib/theme/UptimeBar/index.d.ts.map +1 -0
- package/lib/theme/UptimeBar/index.js +177 -0
- package/lib/theme/UptimeBar/styles.module.css +217 -0
- package/lib/types.d.ts +20 -0
- package/lib/types.d.ts.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +1 -1
- package/scripts/config.js +148 -13
- package/templates/Makefile.status +40 -11
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Your Organization
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* ADR-004: StatusDataProvider Context
|
|
9
|
+
*
|
|
10
|
+
* Centralized context provider for status data fetching.
|
|
11
|
+
* Fetches daily-summary.json and current.json once, provides merged data to children.
|
|
12
|
+
*
|
|
13
|
+
* @see docs/adrs/ADR-004-simplified-status-card-ux.md
|
|
14
|
+
*/
|
|
15
|
+
import React, { type ReactNode } from 'react';
|
|
16
|
+
import type { DailySummaryFile } from '../types';
|
|
17
|
+
/**
|
|
18
|
+
* Compact reading format from current.json
|
|
19
|
+
*/
|
|
20
|
+
interface CurrentReading {
|
|
21
|
+
t: number;
|
|
22
|
+
svc: string;
|
|
23
|
+
state: 'up' | 'down' | 'degraded' | 'maintenance';
|
|
24
|
+
code: number;
|
|
25
|
+
lat: number;
|
|
26
|
+
err?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Current status file structure
|
|
30
|
+
*/
|
|
31
|
+
interface CurrentStatus {
|
|
32
|
+
readings: CurrentReading[];
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Day status for UptimeBar visualization
|
|
36
|
+
*/
|
|
37
|
+
export interface DayStatus {
|
|
38
|
+
/** ISO date string (YYYY-MM-DD) */
|
|
39
|
+
date: string;
|
|
40
|
+
/** Uptime percentage (0-100) */
|
|
41
|
+
uptimePercent: number;
|
|
42
|
+
/** Number of incidents */
|
|
43
|
+
incidents: number;
|
|
44
|
+
/** Total checks performed */
|
|
45
|
+
checksTotal: number;
|
|
46
|
+
/** Successful checks */
|
|
47
|
+
checksPassed: number;
|
|
48
|
+
/** Computed status based on uptime thresholds */
|
|
49
|
+
status: 'operational' | 'degraded' | 'outage' | 'no-data';
|
|
50
|
+
/** Average latency in ms (optional) */
|
|
51
|
+
avgLatencyMs?: number | null;
|
|
52
|
+
/** P95 latency in ms (optional) */
|
|
53
|
+
p95LatencyMs?: number | null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Context value shape for StatusDataProvider
|
|
57
|
+
*/
|
|
58
|
+
export interface StatusDataContextValue {
|
|
59
|
+
/** Daily summary data (days 1-89) */
|
|
60
|
+
dailySummary: DailySummaryFile | null;
|
|
61
|
+
/** Current status data (today's readings) */
|
|
62
|
+
currentStatus: CurrentStatus | null;
|
|
63
|
+
/** Whether data is being fetched */
|
|
64
|
+
loading: boolean;
|
|
65
|
+
/** Error if data fetch failed */
|
|
66
|
+
error: Error | null;
|
|
67
|
+
/**
|
|
68
|
+
* Get merged 90-day data for a service.
|
|
69
|
+
* Combines today from current.json with history from daily-summary.json.
|
|
70
|
+
*/
|
|
71
|
+
getMerged90Days: (serviceName: string) => DayStatus[];
|
|
72
|
+
/** Refetch data */
|
|
73
|
+
refresh: () => Promise<void>;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Props for StatusDataProvider
|
|
77
|
+
*/
|
|
78
|
+
export interface StatusDataProviderProps {
|
|
79
|
+
/** Base URL for status data files (e.g., '/status-data') */
|
|
80
|
+
baseUrl: string;
|
|
81
|
+
/** Children to render */
|
|
82
|
+
children: ReactNode;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* StatusDataProvider component
|
|
86
|
+
*
|
|
87
|
+
* Provides centralized status data fetching and caching for child components.
|
|
88
|
+
* Implements the hybrid read pattern from ADR-002:
|
|
89
|
+
* - Fetches daily-summary.json for historical data (days 1-89)
|
|
90
|
+
* - Fetches current.json for today's live data
|
|
91
|
+
* - Merges them for complete 90-day view
|
|
92
|
+
*/
|
|
93
|
+
export declare function StatusDataProvider({ baseUrl, children, }: StatusDataProviderProps): React.ReactElement;
|
|
94
|
+
/**
|
|
95
|
+
* Hook to access status data context
|
|
96
|
+
*
|
|
97
|
+
* @throws Error if used outside StatusDataProvider
|
|
98
|
+
*/
|
|
99
|
+
export declare function useStatusData(): StatusDataContextValue;
|
|
100
|
+
export type { CurrentReading, CurrentStatus };
|
|
101
|
+
//# sourceMappingURL=StatusDataProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StatusDataProvider.d.ts","sourceRoot":"","sources":["../../src/context/StatusDataProvider.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,EAOZ,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAE,gBAAgB,EAAqB,MAAM,UAAU,CAAC;AAEpE;;GAEG;AACH,UAAU,cAAc;IACtB,CAAC,EAAE,MAAM,CAAC;IACV,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,IAAI,GAAG,MAAM,GAAG,UAAU,GAAG,aAAa,CAAC;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,UAAU,aAAa;IACrB,QAAQ,EAAE,cAAc,EAAE,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,aAAa,EAAE,MAAM,CAAC;IACtB,0BAA0B;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,6BAA6B;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,wBAAwB;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,iDAAiD;IACjD,MAAM,EAAE,aAAa,GAAG,UAAU,GAAG,QAAQ,GAAG,SAAS,CAAC;IAC1D,uCAAuC;IACvC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,mCAAmC;IACnC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,qCAAqC;IACrC,YAAY,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACtC,6CAA6C;IAC7C,aAAa,EAAE,aAAa,GAAG,IAAI,CAAC;IACpC,oCAAoC;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,iCAAiC;IACjC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB;;;OAGG;IACH,eAAe,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,SAAS,EAAE,CAAC;IACtD,mBAAmB;IACnB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,4DAA4D;IAC5D,OAAO,EAAE,MAAM,CAAC;IAChB,yBAAyB;IACzB,QAAQ,EAAE,SAAS,CAAC;CACrB;AAgID;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,OAAO,EACP,QAAQ,GACT,EAAE,uBAAuB,GAAG,KAAK,CAAC,YAAY,CAgK9C;AAED;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,sBAAsB,CAMtD;AAGD,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,CAAC"}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StatusDataProvider = StatusDataProvider;
|
|
4
|
+
exports.useStatusData = useStatusData;
|
|
5
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
|
+
/**
|
|
7
|
+
* Copyright (c) Your Organization
|
|
8
|
+
*
|
|
9
|
+
* This source code is licensed under the MIT license found in the
|
|
10
|
+
* LICENSE file in the root directory of this source tree.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* ADR-004: StatusDataProvider Context
|
|
14
|
+
*
|
|
15
|
+
* Centralized context provider for status data fetching.
|
|
16
|
+
* Fetches daily-summary.json and current.json once, provides merged data to children.
|
|
17
|
+
*
|
|
18
|
+
* @see docs/adrs/ADR-004-simplified-status-card-ux.md
|
|
19
|
+
*/
|
|
20
|
+
const react_1 = require("react");
|
|
21
|
+
// Create context with undefined default (will throw if used outside provider)
|
|
22
|
+
const StatusDataContext = (0, react_1.createContext)(undefined);
|
|
23
|
+
/**
|
|
24
|
+
* Uptime thresholds for status calculation (from ADR-004)
|
|
25
|
+
*/
|
|
26
|
+
const THRESHOLDS = {
|
|
27
|
+
OPERATIONAL: 99, // >= 99% = operational
|
|
28
|
+
DEGRADED: 95, // >= 95% = degraded, < 95% = outage
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Stale data threshold (24 hours in milliseconds)
|
|
32
|
+
*/
|
|
33
|
+
const STALE_THRESHOLD_MS = 24 * 60 * 60 * 1000;
|
|
34
|
+
/**
|
|
35
|
+
* Calculate status from uptime percentage
|
|
36
|
+
*/
|
|
37
|
+
function calculateStatus(uptimePercent, checksTotal) {
|
|
38
|
+
if (checksTotal === 0)
|
|
39
|
+
return 'no-data';
|
|
40
|
+
if (uptimePercent >= THRESHOLDS.OPERATIONAL)
|
|
41
|
+
return 'operational';
|
|
42
|
+
if (uptimePercent >= THRESHOLDS.DEGRADED)
|
|
43
|
+
return 'degraded';
|
|
44
|
+
return 'outage';
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Calculate p95 latency from an array of latency values
|
|
48
|
+
*/
|
|
49
|
+
function calculateP95(latencies) {
|
|
50
|
+
if (latencies.length === 0)
|
|
51
|
+
return null;
|
|
52
|
+
const sorted = [...latencies].sort((a, b) => a - b);
|
|
53
|
+
const index = Math.ceil(sorted.length * 0.95) - 1;
|
|
54
|
+
return sorted[Math.max(0, index)];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Group readings by date for a specific service
|
|
58
|
+
*/
|
|
59
|
+
function groupReadingsByDate(readings, serviceName) {
|
|
60
|
+
const groups = new Map();
|
|
61
|
+
const lowerServiceName = serviceName.toLowerCase();
|
|
62
|
+
for (const reading of readings) {
|
|
63
|
+
if (reading.svc.toLowerCase() !== lowerServiceName)
|
|
64
|
+
continue;
|
|
65
|
+
const date = new Date(reading.t).toISOString().split('T')[0];
|
|
66
|
+
if (!groups.has(date)) {
|
|
67
|
+
groups.set(date, []);
|
|
68
|
+
}
|
|
69
|
+
groups.get(date).push(reading);
|
|
70
|
+
}
|
|
71
|
+
return groups;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Aggregate readings for a specific day into a DayStatus
|
|
75
|
+
*/
|
|
76
|
+
function aggregateDayReadings(date, readings) {
|
|
77
|
+
const checksTotal = readings.length;
|
|
78
|
+
const checksPassed = readings.filter((r) => r.state === 'up' || r.state === 'maintenance').length;
|
|
79
|
+
const uptimePercent = checksTotal > 0 ? (checksPassed / checksTotal) * 100 : 0;
|
|
80
|
+
const latencies = readings.filter((r) => r.state === 'up').map((r) => r.lat);
|
|
81
|
+
const avgLatencyMs = latencies.length > 0
|
|
82
|
+
? Math.round(latencies.reduce((sum, lat) => sum + lat, 0) / latencies.length)
|
|
83
|
+
: null;
|
|
84
|
+
const p95LatencyMs = calculateP95(latencies);
|
|
85
|
+
// Count incidents (transitions from up to down)
|
|
86
|
+
let incidentCount = 0;
|
|
87
|
+
for (let i = 1; i < readings.length; i++) {
|
|
88
|
+
if (readings[i - 1].state === 'up' && readings[i].state === 'down') {
|
|
89
|
+
incidentCount++;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
date,
|
|
94
|
+
uptimePercent,
|
|
95
|
+
incidents: incidentCount,
|
|
96
|
+
checksTotal,
|
|
97
|
+
checksPassed,
|
|
98
|
+
status: calculateStatus(uptimePercent, checksTotal),
|
|
99
|
+
avgLatencyMs,
|
|
100
|
+
p95LatencyMs,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Convert DailySummaryEntry to DayStatus
|
|
105
|
+
*/
|
|
106
|
+
function summaryEntryToDayStatus(entry) {
|
|
107
|
+
const uptimePercent = entry.uptimePct * 100;
|
|
108
|
+
return {
|
|
109
|
+
date: entry.date,
|
|
110
|
+
uptimePercent,
|
|
111
|
+
incidents: entry.incidentCount,
|
|
112
|
+
checksTotal: entry.checksTotal,
|
|
113
|
+
checksPassed: entry.checksPassed,
|
|
114
|
+
status: calculateStatus(uptimePercent, entry.checksTotal),
|
|
115
|
+
avgLatencyMs: entry.avgLatencyMs,
|
|
116
|
+
p95LatencyMs: entry.p95LatencyMs,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* StatusDataProvider component
|
|
121
|
+
*
|
|
122
|
+
* Provides centralized status data fetching and caching for child components.
|
|
123
|
+
* Implements the hybrid read pattern from ADR-002:
|
|
124
|
+
* - Fetches daily-summary.json for historical data (days 1-89)
|
|
125
|
+
* - Fetches current.json for today's live data
|
|
126
|
+
* - Merges them for complete 90-day view
|
|
127
|
+
*/
|
|
128
|
+
function StatusDataProvider({ baseUrl, children, }) {
|
|
129
|
+
const [dailySummary, setDailySummary] = (0, react_1.useState)(null);
|
|
130
|
+
const [currentStatus, setCurrentStatus] = (0, react_1.useState)(null);
|
|
131
|
+
const [loading, setLoading] = (0, react_1.useState)(true);
|
|
132
|
+
const [error, setError] = (0, react_1.useState)(null);
|
|
133
|
+
const [summaryFailed, setSummaryFailed] = (0, react_1.useState)(false);
|
|
134
|
+
const [currentFailed, setCurrentFailed] = (0, react_1.useState)(false);
|
|
135
|
+
/**
|
|
136
|
+
* Fetch both data files in parallel
|
|
137
|
+
*/
|
|
138
|
+
const fetchData = (0, react_1.useCallback)(async () => {
|
|
139
|
+
setLoading(true);
|
|
140
|
+
setError(null);
|
|
141
|
+
setSummaryFailed(false);
|
|
142
|
+
setCurrentFailed(false);
|
|
143
|
+
let summaryOk = false;
|
|
144
|
+
let currentOk = false;
|
|
145
|
+
try {
|
|
146
|
+
// Fetch both files in parallel
|
|
147
|
+
const [summaryResponse, currentResponse] = await Promise.all([
|
|
148
|
+
fetch(`${baseUrl}/daily-summary.json`).catch(() => null),
|
|
149
|
+
fetch(`${baseUrl}/current.json`).catch(() => null),
|
|
150
|
+
]);
|
|
151
|
+
// Handle summary response
|
|
152
|
+
if (summaryResponse?.ok) {
|
|
153
|
+
try {
|
|
154
|
+
const data = await summaryResponse.json();
|
|
155
|
+
setDailySummary(data);
|
|
156
|
+
summaryOk = true;
|
|
157
|
+
// Check for stale data
|
|
158
|
+
if (data.lastUpdated) {
|
|
159
|
+
const lastUpdatedTime = new Date(data.lastUpdated).getTime();
|
|
160
|
+
const now = Date.now();
|
|
161
|
+
if (now - lastUpdatedTime > STALE_THRESHOLD_MS) {
|
|
162
|
+
console.warn(`[StatusDataProvider] Data is stale (last updated: ${data.lastUpdated})`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
setSummaryFailed(true);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
setSummaryFailed(true);
|
|
172
|
+
}
|
|
173
|
+
// Handle current response
|
|
174
|
+
if (currentResponse?.ok) {
|
|
175
|
+
try {
|
|
176
|
+
const data = await currentResponse.json();
|
|
177
|
+
// Handle both array and object with readings property
|
|
178
|
+
const readings = Array.isArray(data) ? data : data.readings || [];
|
|
179
|
+
setCurrentStatus({ readings });
|
|
180
|
+
currentOk = true;
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
setCurrentFailed(true);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
setCurrentFailed(true);
|
|
188
|
+
}
|
|
189
|
+
// Only set error if both files failed
|
|
190
|
+
if (!summaryOk && !currentOk) {
|
|
191
|
+
setError(new Error('No data available'));
|
|
192
|
+
}
|
|
193
|
+
setLoading(false);
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
setError(err instanceof Error ? err : new Error('Network error'));
|
|
197
|
+
setLoading(false);
|
|
198
|
+
}
|
|
199
|
+
}, [baseUrl]);
|
|
200
|
+
// Fetch data on mount
|
|
201
|
+
(0, react_1.useEffect)(() => {
|
|
202
|
+
fetchData();
|
|
203
|
+
}, [fetchData]);
|
|
204
|
+
/**
|
|
205
|
+
* Get merged 90-day data for a service
|
|
206
|
+
*/
|
|
207
|
+
const getMerged90Days = (0, react_1.useCallback)((serviceName) => {
|
|
208
|
+
// Return empty array if no data or unknown service
|
|
209
|
+
const lowerServiceName = serviceName.toLowerCase();
|
|
210
|
+
const entries = [];
|
|
211
|
+
const today = new Date().toISOString().split('T')[0];
|
|
212
|
+
// Get historical data from summary
|
|
213
|
+
const historicalEntries = dailySummary?.services?.[lowerServiceName] ||
|
|
214
|
+
dailySummary?.services?.[serviceName] ||
|
|
215
|
+
[];
|
|
216
|
+
// Get today's readings from current.json
|
|
217
|
+
const todayReadings = currentStatus?.readings
|
|
218
|
+
? groupReadingsByDate(currentStatus.readings, serviceName).get(today) ||
|
|
219
|
+
[]
|
|
220
|
+
: [];
|
|
221
|
+
// Add today's aggregated data if we have readings
|
|
222
|
+
if (todayReadings.length > 0) {
|
|
223
|
+
// Sort by timestamp for accurate incident counting
|
|
224
|
+
todayReadings.sort((a, b) => a.t - b.t);
|
|
225
|
+
entries.push(aggregateDayReadings(today, todayReadings));
|
|
226
|
+
}
|
|
227
|
+
// Add historical entries (filter out today if it exists in summary)
|
|
228
|
+
for (const entry of historicalEntries) {
|
|
229
|
+
if (entry.date !== today) {
|
|
230
|
+
entries.push(summaryEntryToDayStatus(entry));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// Sort by date descending (most recent first)
|
|
234
|
+
entries.sort((a, b) => b.date.localeCompare(a.date));
|
|
235
|
+
// Limit to 90 days
|
|
236
|
+
return entries.slice(0, 90);
|
|
237
|
+
}, [dailySummary, currentStatus]);
|
|
238
|
+
/**
|
|
239
|
+
* Refresh data (re-fetch)
|
|
240
|
+
*/
|
|
241
|
+
const refresh = (0, react_1.useCallback)(async () => {
|
|
242
|
+
await fetchData();
|
|
243
|
+
}, [fetchData]);
|
|
244
|
+
// Memoize context value
|
|
245
|
+
const value = (0, react_1.useMemo)(() => ({
|
|
246
|
+
dailySummary,
|
|
247
|
+
currentStatus,
|
|
248
|
+
loading,
|
|
249
|
+
error,
|
|
250
|
+
getMerged90Days,
|
|
251
|
+
refresh,
|
|
252
|
+
}), [dailySummary, currentStatus, loading, error, getMerged90Days, refresh]);
|
|
253
|
+
return ((0, jsx_runtime_1.jsx)(StatusDataContext.Provider, { value: value, children: children }));
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Hook to access status data context
|
|
257
|
+
*
|
|
258
|
+
* @throws Error if used outside StatusDataProvider
|
|
259
|
+
*/
|
|
260
|
+
function useStatusData() {
|
|
261
|
+
const context = (0, react_1.useContext)(StatusDataContext);
|
|
262
|
+
if (context === undefined) {
|
|
263
|
+
throw new Error('useStatusData must be used within a StatusDataProvider');
|
|
264
|
+
}
|
|
265
|
+
return context;
|
|
266
|
+
}
|
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAC,WAAW,EAAE,MAAM,EAAC,MAAM,mBAAmB,CAAC;AAC3D,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAC,WAAW,EAAE,MAAM,EAAC,MAAM,mBAAmB,CAAC;AAC3D,OAAO,KAAK,EAAC,aAAa,EAAE,UAAU,EAA6E,MAAM,SAAS,CAAC;AAInI,OAAO,EAAC,eAAe,EAAC,MAAM,WAAW,CAAC;AAqS1C;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,EAAE,CAalD;AAED,wBAA8B,YAAY,CACxC,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CA0iB7B;AAED,OAAO,EAAC,KAAK,aAAa,EAAC,MAAM,SAAS,CAAC"}
|
package/lib/index.js
CHANGED
|
@@ -19,6 +19,118 @@ const github_service_1 = require("./github-service");
|
|
|
19
19
|
const demo_data_1 = require("./demo-data");
|
|
20
20
|
var options_1 = require("./options");
|
|
21
21
|
Object.defineProperty(exports, "validateOptions", { enumerable: true, get: function () { return options_1.validateOptions; } });
|
|
22
|
+
/**
|
|
23
|
+
* Load entities from .monitorrc.json (ADR-003: Single Source of Truth)
|
|
24
|
+
*
|
|
25
|
+
* When entitiesSource is 'monitorrc' or 'hybrid', we auto-discover entities
|
|
26
|
+
* from the monitoring config rather than requiring manual docusaurus.config.js updates.
|
|
27
|
+
*
|
|
28
|
+
* @param siteDir - Docusaurus site directory
|
|
29
|
+
* @returns Array of Entity objects discovered from .monitorrc.json
|
|
30
|
+
*/
|
|
31
|
+
async function loadEntitiesFromMonitorRc(siteDir) {
|
|
32
|
+
const configPath = path_1.default.join(siteDir, '.monitorrc.json');
|
|
33
|
+
if (!(await fs_extra_1.default.pathExists(configPath))) {
|
|
34
|
+
console.log('[docusaurus-plugin-stentorosaur] No .monitorrc.json found, skipping auto-discovery');
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const config = await fs_extra_1.default.readJson(configPath);
|
|
39
|
+
if (!config.systems || !Array.isArray(config.systems)) {
|
|
40
|
+
console.warn('[docusaurus-plugin-stentorosaur] .monitorrc.json has no systems array');
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
// Filter out hidden systems and convert to Entity format
|
|
44
|
+
const entities = config.systems
|
|
45
|
+
.filter(sys => sys.display !== false) // Only include visible systems
|
|
46
|
+
.map(sys => ({
|
|
47
|
+
name: sys.system,
|
|
48
|
+
displayName: sys.displayName || sys.system,
|
|
49
|
+
type: 'system',
|
|
50
|
+
description: sys.description,
|
|
51
|
+
monitoring: {
|
|
52
|
+
enabled: true,
|
|
53
|
+
url: sys.url,
|
|
54
|
+
method: (sys.method || 'GET'),
|
|
55
|
+
timeout: sys.timeout,
|
|
56
|
+
expectedCodes: sys.expectedCodes,
|
|
57
|
+
maxResponseTime: sys.maxResponseTime,
|
|
58
|
+
},
|
|
59
|
+
}));
|
|
60
|
+
console.log(`[docusaurus-plugin-stentorosaur] Auto-discovered ${entities.length} entities from .monitorrc.json`);
|
|
61
|
+
// Log hidden systems for transparency
|
|
62
|
+
const hiddenCount = config.systems.filter(sys => sys.display === false).length;
|
|
63
|
+
if (hiddenCount > 0) {
|
|
64
|
+
console.log(`[docusaurus-plugin-stentorosaur] ${hiddenCount} hidden system(s) excluded from display`);
|
|
65
|
+
}
|
|
66
|
+
return entities;
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
console.warn('[docusaurus-plugin-stentorosaur] Failed to read .monitorrc.json:', error instanceof Error ? error.message : String(error));
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Merge entities from different sources based on entitiesSource setting
|
|
75
|
+
*
|
|
76
|
+
* @param configEntities - Entities from docusaurus.config.js
|
|
77
|
+
* @param monitorRcEntities - Entities auto-discovered from .monitorrc.json
|
|
78
|
+
* @param entitiesSource - Source preference: 'config' | 'monitorrc' | 'hybrid'
|
|
79
|
+
* @returns Merged entity list
|
|
80
|
+
*/
|
|
81
|
+
function mergeEntities(configEntities, monitorRcEntities, entitiesSource) {
|
|
82
|
+
switch (entitiesSource) {
|
|
83
|
+
case 'config':
|
|
84
|
+
// Traditional mode: only use docusaurus.config.js entities
|
|
85
|
+
return configEntities;
|
|
86
|
+
case 'monitorrc':
|
|
87
|
+
// Single source of truth: only use .monitorrc.json entities
|
|
88
|
+
return monitorRcEntities;
|
|
89
|
+
case 'hybrid':
|
|
90
|
+
// Merge both, with config taking precedence for duplicates
|
|
91
|
+
const mergedMap = new Map();
|
|
92
|
+
// Add monitorrc entities first
|
|
93
|
+
for (const entity of monitorRcEntities) {
|
|
94
|
+
mergedMap.set(entity.name, entity);
|
|
95
|
+
}
|
|
96
|
+
// Override with config entities (they take precedence)
|
|
97
|
+
for (const entity of configEntities) {
|
|
98
|
+
const existing = mergedMap.get(entity.name);
|
|
99
|
+
if (existing) {
|
|
100
|
+
// Deep merge: config overrides monitorrc
|
|
101
|
+
mergedMap.set(entity.name, { ...existing, ...entity });
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
mergedMap.set(entity.name, entity);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return Array.from(mergedMap.values());
|
|
108
|
+
default:
|
|
109
|
+
return configEntities;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Check for entity mismatches and warn (ADR-003)
|
|
114
|
+
*/
|
|
115
|
+
function checkEntityMismatches(configEntities, monitorRcEntities, entitiesSource) {
|
|
116
|
+
// Only warn in config mode when there are monitorrc entities not in config
|
|
117
|
+
if (entitiesSource === 'config' && monitorRcEntities.length > 0) {
|
|
118
|
+
const configNames = new Set(configEntities.map(e => e.name));
|
|
119
|
+
const missingInConfig = monitorRcEntities.filter(e => !configNames.has(e.name));
|
|
120
|
+
if (missingInConfig.length > 0) {
|
|
121
|
+
console.warn(`\n⚠️ [docusaurus-plugin-stentorosaur] Entity mismatch detected!`);
|
|
122
|
+
console.warn(` Found ${missingInConfig.length} system(s) in .monitorrc.json not in docusaurus.config.js:`);
|
|
123
|
+
for (const entity of missingInConfig) {
|
|
124
|
+
console.warn(` - ${entity.name}`);
|
|
125
|
+
}
|
|
126
|
+
console.warn(`\n These systems will be monitored but NOT displayed on the status page.`);
|
|
127
|
+
console.warn(` To fix, either:`);
|
|
128
|
+
console.warn(` 1. Add these to docusaurus.config.js entities array`);
|
|
129
|
+
console.warn(` 2. Use entitiesSource: 'monitorrc' for auto-discovery`);
|
|
130
|
+
console.warn(` 3. Add display: false to hide them intentionally\n`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
22
134
|
/**
|
|
23
135
|
* Read system status files and calculate metrics
|
|
24
136
|
*/
|
|
@@ -141,10 +253,25 @@ async function pluginStatus(context, options) {
|
|
|
141
253
|
throw new Error('docusaurus-plugin-stentorosaur requires either plugin options (owner, repo) or ' +
|
|
142
254
|
'site config (organizationName, projectName) to be set');
|
|
143
255
|
}
|
|
144
|
-
const { statusLabel = 'status', entities = [], labelScheme, token, dataPath = 'status-data', title = 'System Status', description = 'Current status of our systems and services', showResponseTimes = true, showUptime = true, useDemoData, showServices = true, showIncidents = true, showPerformanceMetrics = true, scheduledMaintenance = { enabled: true, label: 'maintenance' }, } = options;
|
|
256
|
+
const { statusLabel = 'status', entities: configEntities = [], entitiesSource = 'config', labelScheme, token, dataPath = 'status-data', title = 'System Status', description = 'Current status of our systems and services', showResponseTimes = true, showUptime = true, useDemoData, showServices = true, showIncidents = true, showPerformanceMetrics = true, scheduledMaintenance = { enabled: true, label: 'maintenance' }, } = options;
|
|
145
257
|
// Build maintenance labels array from config
|
|
146
258
|
const maintenanceLabels = scheduledMaintenance.labels ||
|
|
147
259
|
(scheduledMaintenance.label ? [scheduledMaintenance.label] : ['maintenance']);
|
|
260
|
+
// Load entities based on entitiesSource setting (ADR-003)
|
|
261
|
+
let entities = configEntities;
|
|
262
|
+
if (entitiesSource === 'monitorrc' || entitiesSource === 'hybrid') {
|
|
263
|
+
const monitorRcEntities = await loadEntitiesFromMonitorRc(siteDir);
|
|
264
|
+
entities = mergeEntities(configEntities, monitorRcEntities, entitiesSource);
|
|
265
|
+
// Check for mismatches and warn
|
|
266
|
+
checkEntityMismatches(configEntities, monitorRcEntities, entitiesSource);
|
|
267
|
+
}
|
|
268
|
+
else if (entitiesSource === 'config') {
|
|
269
|
+
// Even in config mode, check for mismatches to warn users
|
|
270
|
+
const monitorRcEntities = await loadEntitiesFromMonitorRc(siteDir);
|
|
271
|
+
if (monitorRcEntities.length > 0) {
|
|
272
|
+
checkEntityMismatches(configEntities, monitorRcEntities, entitiesSource);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
148
275
|
const statusDataDir = path_1.default.join(generatedFilesDir, 'docusaurus-plugin-stentorosaur');
|
|
149
276
|
const statusDataPath = path_1.default.join(statusDataDir, 'status.json');
|
|
150
277
|
return {
|
package/lib/options.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAC,GAAG,EAAC,MAAM,8BAA8B,CAAC;AACjD,OAAO,KAAK,EAAC,uBAAuB,EAAC,MAAM,mBAAmB,CAAC;AAC/D,OAAO,KAAK,EAAC,aAAa,EAAc,UAAU,EAAC,MAAM,SAAS,CAAC;AAEnE,eAAO,MAAM,eAAe,EAAE,OAAO,CAAC,aAAa,
|
|
1
|
+
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAC,GAAG,EAAC,MAAM,8BAA8B,CAAC;AACjD,OAAO,KAAK,EAAC,uBAAuB,EAAC,MAAM,mBAAmB,CAAC;AAC/D,OAAO,KAAK,EAAC,aAAa,EAAc,UAAU,EAAC,MAAM,SAAS,CAAC;AAEnE,eAAO,MAAM,eAAe,EAAE,OAAO,CAAC,aAAa,CAqBlD,CAAC;AAmIF;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,6BA2CzB,CAAC;AAEL;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE;IAC1C,UAAU,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,UAAU,CAoCb;AA+CD,wBAAgB,eAAe,CAAC,EAC9B,QAAQ,EACR,OAAO,GACR,EAAE,uBAAuB,CAAC,aAAa,EAAE,aAAa,CAAC,GAAG,aAAa,CAEvE"}
|
package/lib/options.js
CHANGED
|
@@ -13,6 +13,7 @@ const utils_validation_1 = require("@docusaurus/utils-validation");
|
|
|
13
13
|
exports.DEFAULT_OPTIONS = {
|
|
14
14
|
statusLabel: 'status',
|
|
15
15
|
entities: [],
|
|
16
|
+
entitiesSource: 'config',
|
|
16
17
|
labelScheme: {
|
|
17
18
|
separator: ':',
|
|
18
19
|
defaultType: 'system',
|
|
@@ -240,6 +241,7 @@ const pluginOptionsSchema = utils_validation_1.Joi.object({
|
|
|
240
241
|
repo: utils_validation_1.Joi.string(),
|
|
241
242
|
statusLabel: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.statusLabel),
|
|
242
243
|
entities: utils_validation_1.Joi.array().items(entitySchema).default(exports.DEFAULT_OPTIONS.entities),
|
|
244
|
+
entitiesSource: utils_validation_1.Joi.string().valid('config', 'monitorrc', 'hybrid').default('config'),
|
|
243
245
|
labelScheme: labelSchemeSchema.default(exports.DEFAULT_OPTIONS.labelScheme),
|
|
244
246
|
token: utils_validation_1.Joi.string(),
|
|
245
247
|
updateInterval: utils_validation_1.Joi.number().min(1).default(exports.DEFAULT_OPTIONS.updateInterval),
|
|
@@ -255,6 +257,7 @@ const pluginOptionsSchema = utils_validation_1.Joi.object({
|
|
|
255
257
|
defaultSLO: utils_validation_1.Joi.number().min(0).max(100).default(exports.DEFAULT_OPTIONS.defaultSLO),
|
|
256
258
|
systemSLOs: utils_validation_1.Joi.object().pattern(utils_validation_1.Joi.string(), utils_validation_1.Joi.number().min(0).max(100)).default(exports.DEFAULT_OPTIONS.systemSLOs),
|
|
257
259
|
statusView: utils_validation_1.Joi.string().valid('default', 'upptime').default('default'),
|
|
260
|
+
statusCardLayout: utils_validation_1.Joi.string().valid('minimal', 'detailed').default('minimal'),
|
|
258
261
|
uptimeConfig: utils_validation_1.Joi.object({
|
|
259
262
|
sections: utils_validation_1.Joi.array().items(utils_validation_1.Joi.object({
|
|
260
263
|
id: utils_validation_1.Joi.string().valid('active-incidents', 'live-status', 'charts', 'scheduled-maintenance', 'past-maintenance', 'past-incidents').required(),
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Your Organization
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* ADR-004: StatusBadge Component
|
|
9
|
+
*
|
|
10
|
+
* Status indicator badge with status text and icon.
|
|
11
|
+
* Supports i18n via customizable labels and size variants.
|
|
12
|
+
*
|
|
13
|
+
* @see docs/adrs/ADR-004-simplified-status-card-ux.md
|
|
14
|
+
*/
|
|
15
|
+
import React from 'react';
|
|
16
|
+
/**
|
|
17
|
+
* Status types supported by the badge
|
|
18
|
+
*/
|
|
19
|
+
export type StatusType = 'up' | 'degraded' | 'down' | 'maintenance';
|
|
20
|
+
/**
|
|
21
|
+
* Custom labels for i18n support
|
|
22
|
+
*/
|
|
23
|
+
export interface StatusLabels {
|
|
24
|
+
up?: string;
|
|
25
|
+
degraded?: string;
|
|
26
|
+
down?: string;
|
|
27
|
+
maintenance?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Size variants
|
|
31
|
+
*/
|
|
32
|
+
export type StatusBadgeSize = 'sm' | 'md' | 'lg';
|
|
33
|
+
/**
|
|
34
|
+
* Props for StatusBadge component
|
|
35
|
+
*/
|
|
36
|
+
export interface StatusBadgeProps {
|
|
37
|
+
/** Current status */
|
|
38
|
+
status: StatusType;
|
|
39
|
+
/** Custom labels for i18n */
|
|
40
|
+
labels?: StatusLabels;
|
|
41
|
+
/** Size variant (default: 'md') */
|
|
42
|
+
size?: StatusBadgeSize;
|
|
43
|
+
/** Optional additional className */
|
|
44
|
+
className?: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* StatusBadge Component
|
|
48
|
+
*
|
|
49
|
+
* Displays a status indicator with colored dot and label text.
|
|
50
|
+
* Designed for accessibility with ARIA role and visual icon.
|
|
51
|
+
*/
|
|
52
|
+
export declare function StatusBadge({ status, labels, size, className, }: StatusBadgeProps): React.ReactElement;
|
|
53
|
+
export default StatusBadge;
|
|
54
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/theme/StatusBadge/index.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;;GAOG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,UAAU,GAAG,MAAM,GAAG,aAAa,CAAC;AAEpE;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAEjD;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,qBAAqB;IACrB,MAAM,EAAE,UAAU,CAAC;IACnB,6BAA6B;IAC7B,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,mCAAmC;IACnC,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,oCAAoC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAyCD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,EAC1B,MAAM,EACN,MAAM,EACN,IAAW,EACX,SAAS,GACV,EAAE,gBAAgB,GAAG,KAAK,CAAC,YAAY,CAuBvC;AAGD,eAAe,WAAW,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.StatusBadge = StatusBadge;
|
|
7
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
8
|
+
const styles_module_css_1 = __importDefault(require("./styles.module.css"));
|
|
9
|
+
/**
|
|
10
|
+
* Default labels for each status
|
|
11
|
+
*/
|
|
12
|
+
const DEFAULT_LABELS = {
|
|
13
|
+
up: 'Operational',
|
|
14
|
+
degraded: 'Degraded',
|
|
15
|
+
down: 'Major Outage',
|
|
16
|
+
maintenance: 'Maintenance',
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Status icons for visual indication (accessible, works with or without color)
|
|
20
|
+
*/
|
|
21
|
+
const STATUS_ICONS = {
|
|
22
|
+
up: '●',
|
|
23
|
+
degraded: '●',
|
|
24
|
+
down: '●',
|
|
25
|
+
maintenance: '●',
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* CSS class mapping for status types
|
|
29
|
+
*/
|
|
30
|
+
const STATUS_CLASS_MAP = {
|
|
31
|
+
up: styles_module_css_1.default.statusUp,
|
|
32
|
+
degraded: styles_module_css_1.default.statusDegraded,
|
|
33
|
+
down: styles_module_css_1.default.statusDown,
|
|
34
|
+
maintenance: styles_module_css_1.default.statusMaintenance,
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* CSS class mapping for sizes
|
|
38
|
+
*/
|
|
39
|
+
const SIZE_CLASS_MAP = {
|
|
40
|
+
sm: styles_module_css_1.default.sizeSm,
|
|
41
|
+
md: styles_module_css_1.default.sizeMd,
|
|
42
|
+
lg: styles_module_css_1.default.sizeLg,
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* StatusBadge Component
|
|
46
|
+
*
|
|
47
|
+
* Displays a status indicator with colored dot and label text.
|
|
48
|
+
* Designed for accessibility with ARIA role and visual icon.
|
|
49
|
+
*/
|
|
50
|
+
function StatusBadge({ status, labels, size = 'md', className, }) {
|
|
51
|
+
// Merge custom labels with defaults
|
|
52
|
+
const resolvedLabels = {
|
|
53
|
+
...DEFAULT_LABELS,
|
|
54
|
+
...labels,
|
|
55
|
+
};
|
|
56
|
+
const label = resolvedLabels[status];
|
|
57
|
+
const icon = STATUS_ICONS[status];
|
|
58
|
+
const statusClass = STATUS_CLASS_MAP[status];
|
|
59
|
+
const sizeClass = SIZE_CLASS_MAP[size];
|
|
60
|
+
return ((0, jsx_runtime_1.jsxs)("span", { className: `${styles_module_css_1.default.statusBadge} ${statusClass} ${sizeClass} ${className || ''}`, role: "status", children: [(0, jsx_runtime_1.jsx)("span", { className: styles_module_css_1.default.statusIcon, "aria-hidden": "true", children: icon }), (0, jsx_runtime_1.jsx)("span", { className: styles_module_css_1.default.statusLabel, children: label })] }));
|
|
61
|
+
}
|
|
62
|
+
// Default export for easier imports
|
|
63
|
+
exports.default = StatusBadge;
|