@checkstack/healthcheck-frontend 0.12.1 → 0.13.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/CHANGELOG.md CHANGED
@@ -1,5 +1,68 @@
1
1
  # @checkstack/healthcheck-frontend
2
2
 
3
+ ## 0.13.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 26d8bae: Distributed satellite health checks and Assignment IDE page
8
+
9
+ **Satellite System**
10
+
11
+ - New `satellite-backend`, `satellite-common`, `satellite-frontend`, and `satellite` agent packages for distributed health check execution
12
+ - WebSocket-based satellite connectivity with authentication, heartbeats, and live configuration push
13
+ - Satellite management UI with create dialog, status badges, and list page
14
+
15
+ **Live Configuration Updates**
16
+
17
+ - Added `assignmentChanged` hook to `healthcheck-backend` for cross-plugin communication
18
+ - `satellite-backend` subscribes to assignment changes and pushes config updates to connected satellites in real-time
19
+
20
+ **Assignment IDE Page**
21
+
22
+ - Replaced the 1028-line modal-based `SystemHealthCheckAssignment` component with a full-page IDE layout
23
+ - New modular components: `AssignmentTree`, `GeneralPanel`, `ThresholdsPanel`, `RetentionPanel`, `ExecutionPanel`
24
+ - Added unassign capability and sorted assignment lists for stable ordering
25
+
26
+ **Shared IDE Primitives**
27
+
28
+ - Extracted `IDETreeNode`, `IDETreeSection`, `IDEStatusBar`, `IDELayout` to `@checkstack/ui` for cross-plugin reuse
29
+ - Migrated existing health check IDE editor to use shared primitives
30
+
31
+ **Infrastructure**
32
+
33
+ - Added `Dockerfile.satellite` for containerized satellite deployment
34
+ - WebSocket route registry in `@checkstack/backend` and `@checkstack/backend-api`
35
+
36
+ - 26d8bae: Source attribution and filtering for satellite health checks
37
+
38
+ **Source Attribution**
39
+
40
+ - Fixed satellite result attribution: runs from satellites now correctly display their source instead of defaulting to "Local"
41
+ - Added `sourceId` and `sourceLabel` to both public and detailed history API responses
42
+
43
+ **Source Filtering**
44
+
45
+ - Added `sourceFilter` parameter to `getHistory`, `getDetailedHistory`, and `getDetailedAggregatedHistory` RPC endpoints
46
+ - Source filter supports "local" (core-only), specific satellite UUID, or all sources
47
+ - Filter applies to all three aggregation tiers (raw, hourly, daily)
48
+
49
+ **Frontend**
50
+
51
+ - System detail accordion shows source filter buttons (All / Local / per-satellite) next to date range filter
52
+ - Filter applies to both charts and recent runs table
53
+ - Source column added to the recent runs table with Local/Remote badges
54
+ - Health check history detail page includes per-satellite source filter buttons
55
+
56
+ ### Patch Changes
57
+
58
+ - Updated dependencies [26d8bae]
59
+ - Updated dependencies [26d8bae]
60
+ - @checkstack/ui@1.3.0
61
+ - @checkstack/healthcheck-common@0.11.0
62
+ - @checkstack/satellite-common@0.2.0
63
+ - @checkstack/auth-frontend@0.5.19
64
+ - @checkstack/dashboard-frontend@0.3.27
65
+
3
66
  ## 0.12.1
4
67
 
5
68
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checkstack/healthcheck-frontend",
3
- "version": "0.12.1",
3
+ "version": "0.13.0",
4
4
  "type": "module",
5
5
  "main": "src/index.tsx",
6
6
  "checkstack": {
@@ -12,14 +12,14 @@
12
12
  "lint:code": "eslint . --max-warnings 0"
13
13
  },
14
14
  "dependencies": {
15
- "@checkstack/auth-frontend": "0.5.17",
16
- "@checkstack/catalog-common": "1.3.0",
17
- "@checkstack/common": "0.6.4",
18
- "@checkstack/dashboard-frontend": "0.3.25",
19
- "@checkstack/frontend-api": "0.3.8",
20
- "@checkstack/healthcheck-common": "0.10.0",
21
- "@checkstack/signal-frontend": "0.0.14",
22
- "@checkstack/ui": "1.2.0",
15
+ "@checkstack/auth-frontend": "0.5.18",
16
+ "@checkstack/catalog-common": "1.3.1",
17
+ "@checkstack/common": "0.6.5",
18
+ "@checkstack/dashboard-frontend": "0.3.26",
19
+ "@checkstack/frontend-api": "0.3.9",
20
+ "@checkstack/healthcheck-common": "0.10.1",
21
+ "@checkstack/signal-frontend": "0.0.15",
22
+ "@checkstack/ui": "1.2.1",
23
23
  "ajv": "^8.18.0",
24
24
  "ajv-formats": "^3.0.1",
25
25
  "date-fns": "^4.1.0",
@@ -28,7 +28,8 @@
28
28
  "react-router-dom": "^6.20.0",
29
29
  "recharts": "^3.6.0",
30
30
  "uuid": "^13.0.0",
31
- "zod": "^4.2.1"
31
+ "zod": "^4.2.1",
32
+ "@checkstack/satellite-common": "0.1.0"
32
33
  },
33
34
  "devDependencies": {
34
35
  "@checkstack/scripts": "0.1.2",
@@ -10,7 +10,7 @@ import {
10
10
  Pagination,
11
11
  } from "@checkstack/ui";
12
12
  import { formatDistanceToNow, format } from "date-fns";
13
- import { ExternalLink, Loader2 } from "lucide-react";
13
+ import { ExternalLink, Loader2, Satellite, Server } from "lucide-react";
14
14
  import { useNavigate } from "react-router-dom";
15
15
  import { healthcheckRoutes } from "@checkstack/healthcheck-common";
16
16
  import { resolveRoute } from "@checkstack/common";
@@ -22,6 +22,10 @@ export interface HealthCheckRunDetailed {
22
22
  status: "healthy" | "unhealthy" | "degraded";
23
23
  result: Record<string, unknown>;
24
24
  timestamp: Date;
25
+ /** Source ID for result attribution (undefined = local core, UUID = satellite) */
26
+ sourceId?: string;
27
+ /** Human-readable source label (e.g. "Local" or "EU West (eu-west-1)") */
28
+ sourceLabel?: string;
25
29
  }
26
30
 
27
31
  export interface HealthCheckRunsTableProps {
@@ -93,6 +97,7 @@ export const HealthCheckRunsTable: React.FC<HealthCheckRunsTableProps> = ({
93
97
  </>
94
98
  )}
95
99
  <TableHead>Timestamp</TableHead>
100
+ <TableHead>Source</TableHead>
96
101
  {showFilterColumns && <TableHead className="w-16"></TableHead>}
97
102
  </TableRow>
98
103
  </TableHeader>
@@ -123,6 +128,19 @@ export const HealthCheckRunsTable: React.FC<HealthCheckRunsTableProps> = ({
123
128
  })}
124
129
  </span>
125
130
  </TableCell>
131
+ <TableCell>
132
+ {run.sourceId ? (
133
+ <span className="inline-flex items-center gap-1 text-xs px-1.5 py-0.5 rounded-full bg-orange-500/10 text-orange-600">
134
+ <Satellite className="h-3 w-3" />
135
+ {run.sourceLabel ?? "Remote"}
136
+ </span>
137
+ ) : (
138
+ <span className="inline-flex items-center gap-1 text-xs px-1.5 py-0.5 rounded-full bg-muted text-muted-foreground">
139
+ <Server className="h-3 w-3" />
140
+ {run.sourceLabel ?? "Local"}
141
+ </span>
142
+ )}
143
+ </TableCell>
126
144
  {showFilterColumns && (
127
145
  <TableCell>
128
146
  <ExternalLink className="h-4 w-4 text-muted-foreground" />
@@ -15,6 +15,7 @@ import {
15
15
  healthCheckAccess,
16
16
  healthcheckRoutes,
17
17
  } from "@checkstack/healthcheck-common";
18
+ import { SatelliteApi, satelliteAccess } from "@checkstack/satellite-common";
18
19
  import { resolveRoute } from "@checkstack/common";
19
20
  import {
20
21
  HealthBadge,
@@ -36,10 +37,14 @@ import {
36
37
  CardContent,
37
38
  CardHeader,
38
39
  CardTitle,
39
-
40
40
  } from "@checkstack/ui";
41
41
  import { formatDistanceToNow } from "date-fns";
42
- import { ChevronDown, ChevronRight } from "lucide-react";
42
+ import {
43
+ ChevronDown,
44
+ ChevronRight,
45
+ Satellite as SatelliteIcon,
46
+ Server,
47
+ } from "lucide-react";
43
48
  import { useNavigate } from "react-router-dom";
44
49
  import { HealthCheckSparkline } from "./HealthCheckSparkline";
45
50
  import { HealthCheckLatencyChart } from "./HealthCheckLatencyChart";
@@ -71,14 +76,24 @@ interface ExpandedRowProps {
71
76
  systemId: string;
72
77
  }
73
78
 
74
-
75
79
  const ExpandedDetails: React.FC<ExpandedRowProps> = ({ item, systemId }) => {
76
80
  const healthCheckClient = usePluginClient(HealthCheckApi);
81
+ const satelliteClient = usePluginClient(SatelliteApi);
77
82
  const navigate = useNavigate();
78
83
  const accessApi = useApi(accessApiRef);
79
84
  const { allowed: canViewDetails } = accessApi.useAccess(
80
85
  healthCheckAccess.details,
81
86
  );
87
+ const { allowed: canReadSatellites } = accessApi.useAccess(
88
+ satelliteAccess.satellite.read,
89
+ );
90
+
91
+ // Fetch satellites for source filter (only if user has access)
92
+ const { data: satellitesData } = satelliteClient.listSatellites.useQuery(
93
+ {},
94
+ { enabled: canReadSatellites },
95
+ );
96
+ const satellites = satellitesData?.satellites ?? [];
82
97
 
83
98
  // Date range state for filtering - default to last 24 hours
84
99
  const [dateRange, setDateRange] = useState(() =>
@@ -86,6 +101,7 @@ const ExpandedDetails: React.FC<ExpandedRowProps> = ({ item, systemId }) => {
86
101
  );
87
102
  // Track if a rolling preset is active (vs custom range)
88
103
  const [isRollingPreset, setIsRollingPreset] = useState(true);
104
+ const [sourceFilter, setSourceFilter] = useState<string | undefined>();
89
105
 
90
106
  // Callback to handle date range changes from the filter
91
107
  const handleDateRangeChange = useCallback(
@@ -139,6 +155,7 @@ const ExpandedDetails: React.FC<ExpandedRowProps> = ({ item, systemId }) => {
139
155
  configurationId: item.configurationId,
140
156
  strategyId: item.strategyId,
141
157
  dateRange,
158
+ sourceFilter,
142
159
  isRollingPreset,
143
160
  // Update endDate to current time when new runs are detected (only for rolling presets)
144
161
  onDateRangeRefresh: (newEndDate) => {
@@ -161,6 +178,7 @@ const ExpandedDetails: React.FC<ExpandedRowProps> = ({ item, systemId }) => {
161
178
  offset: pagination.offset,
162
179
  startDate: dateRange.startDate,
163
180
  // Don't pass endDate - backend defaults to 'now' so new runs are included
181
+ sourceFilter,
164
182
  sortOrder: "desc",
165
183
  });
166
184
 
@@ -191,7 +209,6 @@ const ExpandedDetails: React.FC<ExpandedRowProps> = ({ item, systemId }) => {
191
209
  : `Window mode (${item.stateThresholds.windowSize} runs): Degraded at ${item.stateThresholds.degraded.minFailureCount}+ failures, Unhealthy at ${item.stateThresholds.unhealthy.minFailureCount}+ failures`
192
210
  : "Using default thresholds";
193
211
 
194
-
195
212
  // Render charts - charts handle data transformation internally
196
213
  const renderCharts = () => {
197
214
  if (chartLoading) {
@@ -266,8 +283,6 @@ const ExpandedDetails: React.FC<ExpandedRowProps> = ({ item, systemId }) => {
266
283
  </div>
267
284
  </div>
268
285
 
269
-
270
-
271
286
  {/* Date Range Filter with Loading Spinner */}
272
287
  <div className="flex items-center gap-3 flex-wrap">
273
288
  <DateRangeFilter
@@ -292,6 +307,49 @@ const ExpandedDetails: React.FC<ExpandedRowProps> = ({ item, systemId }) => {
292
307
  <Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
293
308
  )}
294
309
  </div>
310
+ {/* Source filter (visible when satellites exist and user has read access) */}
311
+ {canReadSatellites && satellites.length > 0 && (
312
+ <div className="flex items-center gap-2">
313
+ <span className="text-xs text-muted-foreground">Source:</span>
314
+ <div className="flex items-center gap-1">
315
+ <button
316
+ onClick={() => setSourceFilter(undefined)}
317
+ className={`inline-flex items-center gap-1 text-xs px-2 py-1 rounded-full transition-colors ${
318
+ sourceFilter === undefined
319
+ ? "bg-primary text-primary-foreground"
320
+ : "bg-muted text-muted-foreground hover:bg-muted/80"
321
+ }`}
322
+ >
323
+ All
324
+ </button>
325
+ <button
326
+ onClick={() => setSourceFilter("local")}
327
+ className={`inline-flex items-center gap-1 text-xs px-2 py-1 rounded-full transition-colors ${
328
+ sourceFilter === "local"
329
+ ? "bg-primary text-primary-foreground"
330
+ : "bg-muted text-muted-foreground hover:bg-muted/80"
331
+ }`}
332
+ >
333
+ <Server className="h-3 w-3" />
334
+ Local
335
+ </button>
336
+ {satellites.map((sat) => (
337
+ <button
338
+ key={sat.id}
339
+ onClick={() => setSourceFilter(sat.id)}
340
+ className={`inline-flex items-center gap-1 text-xs px-2 py-1 rounded-full transition-colors ${
341
+ sourceFilter === sat.id
342
+ ? "bg-orange-500 text-white"
343
+ : "bg-orange-500/10 text-orange-600 hover:bg-orange-500/20"
344
+ }`}
345
+ >
346
+ <SatelliteIcon className="h-3 w-3" />
347
+ {sat.name}
348
+ </button>
349
+ ))}
350
+ </div>
351
+ </div>
352
+ )}
295
353
 
296
354
  {/* Charts Section */}
297
355
  {renderCharts()}
@@ -309,12 +367,14 @@ const ExpandedDetails: React.FC<ExpandedRowProps> = ({ item, systemId }) => {
309
367
  </span>
310
368
  <div className="flex-1 h-px bg-border" />
311
369
  </div>
370
+
312
371
  <div className="rounded-md border">
313
372
  <Table>
314
373
  <TableHeader>
315
374
  <TableRow>
316
375
  <TableHead className="w-24">Status</TableHead>
317
376
  <TableHead>Time</TableHead>
377
+ <TableHead>Source</TableHead>
318
378
  </TableRow>
319
379
  </TableHeader>
320
380
  <TableBody>
@@ -348,6 +408,19 @@ const ExpandedDetails: React.FC<ExpandedRowProps> = ({ item, systemId }) => {
348
408
  addSuffix: true,
349
409
  })}
350
410
  </TableCell>
411
+ <TableCell>
412
+ {run.sourceId ? (
413
+ <span className="inline-flex items-center gap-1 text-xs px-1.5 py-0.5 rounded-full bg-orange-500/10 text-orange-600">
414
+ <SatelliteIcon className="h-3 w-3" />
415
+ {run.sourceLabel ?? "Remote"}
416
+ </span>
417
+ ) : (
418
+ <span className="inline-flex items-center gap-1 text-xs px-1.5 py-0.5 rounded-full bg-muted text-muted-foreground">
419
+ <Server className="h-3 w-3" />
420
+ {run.sourceLabel ?? "Local"}
421
+ </span>
422
+ )}
423
+ </TableCell>
351
424
  </TableRow>
352
425
  ))}
353
426
  </TableBody>