@amiable-dev/docusaurus-plugin-stentorosaur 0.20.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/options.d.ts.map +1 -1
- package/lib/options.js +1 -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 +9 -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
|
@@ -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/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,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;
|
|
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
|
@@ -257,6 +257,7 @@ const pluginOptionsSchema = utils_validation_1.Joi.object({
|
|
|
257
257
|
defaultSLO: utils_validation_1.Joi.number().min(0).max(100).default(exports.DEFAULT_OPTIONS.defaultSLO),
|
|
258
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),
|
|
259
259
|
statusView: utils_validation_1.Joi.string().valid('default', 'upptime').default('default'),
|
|
260
|
+
statusCardLayout: utils_validation_1.Joi.string().valid('minimal', 'detailed').default('minimal'),
|
|
260
261
|
uptimeConfig: utils_validation_1.Joi.object({
|
|
261
262
|
sections: utils_validation_1.Joi.array().items(utils_validation_1.Joi.object({
|
|
262
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;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADR-004: StatusBadge Styles
|
|
3
|
+
*
|
|
4
|
+
* Status indicator badge with status text and icon.
|
|
5
|
+
* Uses CSS variables for theming.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/* CSS Variables for status colors (can be overridden by theme) */
|
|
9
|
+
:root {
|
|
10
|
+
--status-operational: #22c55e;
|
|
11
|
+
--status-degraded: #eab308;
|
|
12
|
+
--status-outage: #ef4444;
|
|
13
|
+
--status-maintenance: #3b82f6;
|
|
14
|
+
--status-no-data: #9ca3af;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* Base badge styles */
|
|
18
|
+
.statusBadge {
|
|
19
|
+
display: inline-flex;
|
|
20
|
+
align-items: center;
|
|
21
|
+
gap: 0.375rem;
|
|
22
|
+
font-weight: 500;
|
|
23
|
+
border-radius: 9999px;
|
|
24
|
+
white-space: nowrap;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* Status icon (colored dot) */
|
|
28
|
+
.statusIcon {
|
|
29
|
+
display: inline-flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
justify-content: center;
|
|
32
|
+
border-radius: 50%;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* Status label */
|
|
36
|
+
.statusLabel {
|
|
37
|
+
font-family: inherit;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* Size variants */
|
|
41
|
+
.sizeSm {
|
|
42
|
+
font-size: 0.75rem;
|
|
43
|
+
padding: 0.125rem 0.5rem;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.sizeSm .statusIcon {
|
|
47
|
+
font-size: 0.5rem;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.sizeMd {
|
|
51
|
+
font-size: 0.875rem;
|
|
52
|
+
padding: 0.25rem 0.75rem;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.sizeMd .statusIcon {
|
|
56
|
+
font-size: 0.625rem;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.sizeLg {
|
|
60
|
+
font-size: 1rem;
|
|
61
|
+
padding: 0.375rem 1rem;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.sizeLg .statusIcon {
|
|
65
|
+
font-size: 0.75rem;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* Status colors */
|
|
69
|
+
.statusUp {
|
|
70
|
+
color: var(--status-operational);
|
|
71
|
+
background-color: rgba(34, 197, 94, 0.1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.statusUp .statusIcon {
|
|
75
|
+
color: var(--status-operational);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.statusDegraded {
|
|
79
|
+
color: var(--status-degraded);
|
|
80
|
+
background-color: rgba(234, 179, 8, 0.1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.statusDegraded .statusIcon {
|
|
84
|
+
color: var(--status-degraded);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.statusDown {
|
|
88
|
+
color: var(--status-outage);
|
|
89
|
+
background-color: rgba(239, 68, 68, 0.1);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.statusDown .statusIcon {
|
|
93
|
+
color: var(--status-outage);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.statusMaintenance {
|
|
97
|
+
color: var(--status-maintenance);
|
|
98
|
+
background-color: rgba(59, 130, 246, 0.1);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.statusMaintenance .statusIcon {
|
|
102
|
+
color: var(--status-maintenance);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* Dark mode support */
|
|
106
|
+
[data-theme='dark'] .statusUp {
|
|
107
|
+
background-color: rgba(34, 197, 94, 0.15);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
[data-theme='dark'] .statusDegraded {
|
|
111
|
+
background-color: rgba(234, 179, 8, 0.15);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
[data-theme='dark'] .statusDown {
|
|
115
|
+
background-color: rgba(239, 68, 68, 0.15);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
[data-theme='dark'] .statusMaintenance {
|
|
119
|
+
background-color: rgba(59, 130, 246, 0.15);
|
|
120
|
+
}
|