@checkstack/healthcheck-frontend 0.0.2

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.
@@ -0,0 +1 @@
1
+ export { useHealthCheckData } from "./useHealthCheckData";
@@ -0,0 +1,257 @@
1
+ import { useEffect, useState, useMemo } from "react";
2
+ import { useApi, permissionApiRef } from "@checkstack/frontend-api";
3
+ import { healthCheckApiRef } from "../api";
4
+ import {
5
+ permissions,
6
+ DEFAULT_RETENTION_CONFIG,
7
+ type RetentionConfig,
8
+ } from "@checkstack/healthcheck-common";
9
+ import type {
10
+ HealthCheckDiagramSlotContext,
11
+ TypedHealthCheckRun,
12
+ TypedAggregatedBucket,
13
+ } from "../slots";
14
+
15
+ interface UseHealthCheckDataProps {
16
+ systemId: string;
17
+ configurationId: string;
18
+ strategyId: string;
19
+ dateRange: {
20
+ startDate: Date;
21
+ endDate: Date;
22
+ };
23
+ /** Pagination for raw data mode */
24
+ limit?: number;
25
+ offset?: number;
26
+ }
27
+
28
+ interface UseHealthCheckDataResult {
29
+ /** The context to pass to HealthCheckDiagramSlot */
30
+ context: HealthCheckDiagramSlotContext | undefined;
31
+ /** Whether data is currently loading */
32
+ loading: boolean;
33
+ /** Whether aggregated data mode is active */
34
+ isAggregated: boolean;
35
+ /** The resolved retention config */
36
+ retentionConfig: RetentionConfig;
37
+ /** Whether user has permission to view detailed data */
38
+ hasPermission: boolean;
39
+ /** Whether permission is still loading */
40
+ permissionLoading: boolean;
41
+ }
42
+
43
+ /**
44
+ * Hook that handles fetching health check data for visualization.
45
+ * Automatically determines whether to use raw or aggregated data based on:
46
+ * - The selected date range
47
+ * - The configured rawRetentionDays for the assignment
48
+ *
49
+ * Returns a ready-to-use context for HealthCheckDiagramSlot.
50
+ *
51
+ * @example
52
+ * ```tsx
53
+ * const { context, loading, hasPermission } = useHealthCheckData({
54
+ * systemId,
55
+ * configurationId,
56
+ * strategyId,
57
+ * dateRange: { startDate, endDate },
58
+ * });
59
+ *
60
+ * if (!hasPermission) return <NoPermissionMessage />;
61
+ * if (loading) return <LoadingSpinner />;
62
+ * if (!context) return null;
63
+ *
64
+ * return <ExtensionSlot slot={HealthCheckDiagramSlot} context={context} />;
65
+ * ```
66
+ */
67
+ export function useHealthCheckData({
68
+ systemId,
69
+ configurationId,
70
+ strategyId,
71
+ dateRange,
72
+ limit = 100,
73
+ offset = 0,
74
+ }: UseHealthCheckDataProps): UseHealthCheckDataResult {
75
+ const api = useApi(healthCheckApiRef);
76
+ const permissionApi = useApi(permissionApiRef);
77
+
78
+ // Permission state
79
+ const { allowed: hasPermission, loading: permissionLoading } =
80
+ permissionApi.usePermission(permissions.healthCheckDetailsRead.id);
81
+
82
+ // Retention config state
83
+ const [retentionConfig, setRetentionConfig] = useState<RetentionConfig>(
84
+ DEFAULT_RETENTION_CONFIG
85
+ );
86
+ const [retentionLoading, setRetentionLoading] = useState(true);
87
+
88
+ // Raw data state
89
+ const [rawRuns, setRawRuns] = useState<
90
+ TypedHealthCheckRun<Record<string, unknown>>[]
91
+ >([]);
92
+ const [rawLoading, setRawLoading] = useState(false);
93
+
94
+ // Aggregated data state
95
+ const [aggregatedBuckets, setAggregatedBuckets] = useState<
96
+ TypedAggregatedBucket<Record<string, unknown>>[]
97
+ >([]);
98
+ const [aggregatedLoading, setAggregatedLoading] = useState(false);
99
+
100
+ // Calculate date range in days
101
+ const dateRangeDays = useMemo(() => {
102
+ return Math.ceil(
103
+ (dateRange.endDate.getTime() - dateRange.startDate.getTime()) /
104
+ (1000 * 60 * 60 * 24)
105
+ );
106
+ }, [dateRange.startDate, dateRange.endDate]);
107
+
108
+ // Determine if we should use aggregated data
109
+ const isAggregated = dateRangeDays > retentionConfig.rawRetentionDays;
110
+
111
+ // Fetch retention config on mount
112
+ useEffect(() => {
113
+ setRetentionLoading(true);
114
+ api
115
+ .getRetentionConfig({ systemId, configurationId })
116
+ .then((response) =>
117
+ setRetentionConfig(response.retentionConfig ?? DEFAULT_RETENTION_CONFIG)
118
+ )
119
+ .catch(() => {
120
+ // Fall back to default on error
121
+ setRetentionConfig(DEFAULT_RETENTION_CONFIG);
122
+ })
123
+ .finally(() => setRetentionLoading(false));
124
+ }, [api, systemId, configurationId]);
125
+
126
+ // Fetch raw data when in raw mode
127
+ useEffect(() => {
128
+ if (!hasPermission || permissionLoading || retentionLoading || isAggregated)
129
+ return;
130
+
131
+ setRawLoading(true);
132
+ api
133
+ .getDetailedHistory({
134
+ systemId,
135
+ configurationId,
136
+ startDate: dateRange.startDate,
137
+ endDate: dateRange.endDate,
138
+ limit,
139
+ offset,
140
+ })
141
+ .then((response) => {
142
+ setRawRuns(
143
+ response.runs.map((r) => ({
144
+ id: r.id,
145
+ configurationId,
146
+ systemId,
147
+ status: r.status,
148
+ timestamp: r.timestamp,
149
+ latencyMs: r.latencyMs,
150
+ result: r.result,
151
+ }))
152
+ );
153
+ })
154
+ .finally(() => setRawLoading(false));
155
+ }, [
156
+ api,
157
+ systemId,
158
+ configurationId,
159
+ hasPermission,
160
+ permissionLoading,
161
+ retentionLoading,
162
+ isAggregated,
163
+ dateRange.startDate,
164
+ dateRange.endDate,
165
+ limit,
166
+ offset,
167
+ ]);
168
+
169
+ // Fetch aggregated data when in aggregated mode
170
+ useEffect(() => {
171
+ if (
172
+ !hasPermission ||
173
+ permissionLoading ||
174
+ retentionLoading ||
175
+ !isAggregated
176
+ )
177
+ return;
178
+
179
+ setAggregatedLoading(true);
180
+ // Use daily buckets for ranges > 30 days, hourly otherwise
181
+ const bucketSize = dateRangeDays > 30 ? "daily" : "hourly";
182
+ // Use detailed endpoint to get aggregatedResult since we have permission
183
+ api
184
+ .getDetailedAggregatedHistory({
185
+ systemId,
186
+ configurationId,
187
+ startDate: dateRange.startDate,
188
+ endDate: dateRange.endDate,
189
+ bucketSize,
190
+ })
191
+ .then((response) => {
192
+ setAggregatedBuckets(
193
+ response.buckets as TypedAggregatedBucket<Record<string, unknown>>[]
194
+ );
195
+ })
196
+ .finally(() => setAggregatedLoading(false));
197
+ }, [
198
+ api,
199
+ systemId,
200
+ configurationId,
201
+ hasPermission,
202
+ permissionLoading,
203
+ retentionLoading,
204
+ isAggregated,
205
+ dateRangeDays,
206
+ dateRange.startDate,
207
+ dateRange.endDate,
208
+ ]);
209
+
210
+ const context = useMemo((): HealthCheckDiagramSlotContext | undefined => {
211
+ if (!hasPermission || permissionLoading || retentionLoading) {
212
+ return undefined;
213
+ }
214
+
215
+ if (isAggregated) {
216
+ return {
217
+ type: "aggregated",
218
+ systemId,
219
+ configurationId,
220
+ strategyId,
221
+ buckets: aggregatedBuckets,
222
+ };
223
+ }
224
+
225
+ return {
226
+ type: "raw",
227
+ systemId,
228
+ configurationId,
229
+ strategyId,
230
+ runs: rawRuns,
231
+ };
232
+ }, [
233
+ hasPermission,
234
+ permissionLoading,
235
+ retentionLoading,
236
+ isAggregated,
237
+ systemId,
238
+ configurationId,
239
+ strategyId,
240
+ rawRuns,
241
+ aggregatedBuckets,
242
+ ]);
243
+
244
+ const loading =
245
+ permissionLoading ||
246
+ retentionLoading ||
247
+ (isAggregated ? aggregatedLoading : rawLoading);
248
+
249
+ return {
250
+ context,
251
+ loading,
252
+ isAggregated,
253
+ retentionConfig,
254
+ hasPermission,
255
+ permissionLoading,
256
+ };
257
+ }
package/src/index.tsx ADDED
@@ -0,0 +1,99 @@
1
+ import {
2
+ createFrontendPlugin,
3
+ createSlotExtension,
4
+ rpcApiRef,
5
+ type ApiRef,
6
+ UserMenuItemsSlot,
7
+ } from "@checkstack/frontend-api";
8
+ import { healthCheckApiRef, type HealthCheckApiClient } from "./api";
9
+ import { HealthCheckConfigPage } from "./pages/HealthCheckConfigPage";
10
+ import { HealthCheckHistoryPage } from "./pages/HealthCheckHistoryPage";
11
+ import { HealthCheckHistoryDetailPage } from "./pages/HealthCheckHistoryDetailPage";
12
+ import { HealthCheckMenuItems } from "./components/HealthCheckMenuItems";
13
+ import { HealthCheckSystemOverview } from "./components/HealthCheckSystemOverview";
14
+ import { SystemHealthCheckAssignment } from "./components/SystemHealthCheckAssignment";
15
+ import { SystemHealthBadge } from "./components/SystemHealthBadge";
16
+ import { permissions } from "@checkstack/healthcheck-common";
17
+ import { autoChartExtension } from "./auto-charts";
18
+
19
+ import {
20
+ SystemDetailsSlot,
21
+ CatalogSystemActionsSlot,
22
+ SystemStateBadgesSlot,
23
+ } from "@checkstack/catalog-common";
24
+ import {
25
+ healthcheckRoutes,
26
+ HealthCheckApi,
27
+ pluginMetadata,
28
+ } from "@checkstack/healthcheck-common";
29
+
30
+ // Export slot definitions for other plugins to use
31
+ export {
32
+ HealthCheckDiagramSlot,
33
+ createStrategyDiagramExtension,
34
+ createDiagramExtensionFactory,
35
+ type HealthCheckDiagramSlotContext,
36
+ type RawDiagramContext,
37
+ type AggregatedDiagramContext,
38
+ type TypedHealthCheckRun,
39
+ type TypedAggregatedBucket,
40
+ } from "./slots";
41
+
42
+ // Export hooks for reusable data fetching
43
+ export { useHealthCheckData } from "./hooks";
44
+
45
+ export default createFrontendPlugin({
46
+ metadata: pluginMetadata,
47
+ routes: [
48
+ {
49
+ route: healthcheckRoutes.routes.config,
50
+ element: <HealthCheckConfigPage />,
51
+ title: "Health Checks",
52
+ permission: permissions.healthCheckManage,
53
+ },
54
+ {
55
+ route: healthcheckRoutes.routes.history,
56
+ element: <HealthCheckHistoryPage />,
57
+ title: "Health Check History",
58
+ permission: permissions.healthCheckRead,
59
+ },
60
+ {
61
+ route: healthcheckRoutes.routes.historyDetail,
62
+ element: <HealthCheckHistoryDetailPage />,
63
+ title: "Health Check Detail",
64
+ permission: permissions.healthCheckDetailsRead,
65
+ },
66
+ ],
67
+ apis: [
68
+ {
69
+ ref: healthCheckApiRef,
70
+ factory: (deps: {
71
+ get: <T>(ref: ApiRef<T>) => T;
72
+ }): HealthCheckApiClient => {
73
+ const rpcApi = deps.get(rpcApiRef);
74
+ // HealthCheckApiClient is just the RPC contract - return it directly
75
+ return rpcApi.forPlugin(HealthCheckApi);
76
+ },
77
+ },
78
+ ],
79
+ extensions: [
80
+ createSlotExtension(UserMenuItemsSlot, {
81
+ id: "healthcheck.user-menu.items",
82
+ component: HealthCheckMenuItems,
83
+ }),
84
+ createSlotExtension(SystemStateBadgesSlot, {
85
+ id: "healthcheck.system-health-badge",
86
+ component: SystemHealthBadge,
87
+ }),
88
+ createSlotExtension(SystemDetailsSlot, {
89
+ id: "healthcheck.system-details.overview",
90
+ component: HealthCheckSystemOverview,
91
+ }),
92
+ createSlotExtension(CatalogSystemActionsSlot, {
93
+ id: "healthcheck.catalog.system-actions",
94
+ component: SystemHealthCheckAssignment,
95
+ }),
96
+ // Auto-generated charts based on schema metadata
97
+ autoChartExtension,
98
+ ],
99
+ });
@@ -0,0 +1,164 @@
1
+ import { useEffect, useState } from "react";
2
+ import { useSearchParams } from "react-router-dom";
3
+ import {
4
+ useApi,
5
+ wrapInSuspense,
6
+ permissionApiRef,
7
+ } from "@checkstack/frontend-api";
8
+ import { healthCheckApiRef } from "../api";
9
+ import {
10
+ HealthCheckConfiguration,
11
+ HealthCheckStrategyDto,
12
+ CreateHealthCheckConfiguration,
13
+ healthcheckRoutes,
14
+ } from "@checkstack/healthcheck-common";
15
+ import { HealthCheckList } from "../components/HealthCheckList";
16
+ import { HealthCheckEditor } from "../components/HealthCheckEditor";
17
+ import { Button, ConfirmationModal, PageLayout } from "@checkstack/ui";
18
+ import { Plus, History } from "lucide-react";
19
+ import { Link } from "react-router-dom";
20
+ import { resolveRoute } from "@checkstack/common";
21
+
22
+ const HealthCheckConfigPageContent = () => {
23
+ const api = useApi(healthCheckApiRef);
24
+ const permissionApi = useApi(permissionApiRef);
25
+ const [searchParams, setSearchParams] = useSearchParams();
26
+ const { allowed: canRead, loading: permissionLoading } =
27
+ permissionApi.useResourcePermission("healthcheck", "read");
28
+ const { allowed: canManage } = permissionApi.useResourcePermission(
29
+ "healthcheck",
30
+ "manage"
31
+ );
32
+
33
+ const [configurations, setConfigurations] = useState<
34
+ HealthCheckConfiguration[]
35
+ >([]);
36
+ const [strategies, setStrategies] = useState<HealthCheckStrategyDto[]>([]);
37
+ const [isEditorOpen, setIsEditorOpen] = useState(false);
38
+ const [editingConfig, setEditingConfig] = useState<
39
+ HealthCheckConfiguration | undefined
40
+ >();
41
+
42
+ // Delete modal state
43
+ const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
44
+ const [idToDelete, setIdToDelete] = useState<string | undefined>();
45
+ const [isDeleting, setIsDeleting] = useState(false);
46
+
47
+ const fetchData = async () => {
48
+ const [configs, strats] = await Promise.all([
49
+ api.getConfigurations(),
50
+ api.getStrategies(),
51
+ ]);
52
+ setConfigurations(configs);
53
+ setStrategies(strats);
54
+ };
55
+
56
+ useEffect(() => {
57
+ fetchData();
58
+ }, [api]);
59
+
60
+ // Handle ?action=create URL parameter (from command palette)
61
+ useEffect(() => {
62
+ if (searchParams.get("action") === "create" && canManage) {
63
+ setEditingConfig(undefined);
64
+ setIsEditorOpen(true);
65
+ // Clear the URL param after opening
66
+ searchParams.delete("action");
67
+ setSearchParams(searchParams, { replace: true });
68
+ }
69
+ }, [searchParams, canManage, setSearchParams]);
70
+
71
+ const handleCreate = () => {
72
+ setEditingConfig(undefined);
73
+ setIsEditorOpen(true);
74
+ };
75
+
76
+ const handleEdit = (config: HealthCheckConfiguration) => {
77
+ setEditingConfig(config);
78
+ setIsEditorOpen(true);
79
+ };
80
+
81
+ const handleDelete = (id: string) => {
82
+ setIdToDelete(id);
83
+ setIsDeleteModalOpen(true);
84
+ };
85
+
86
+ const confirmDelete = async () => {
87
+ if (!idToDelete) return;
88
+ setIsDeleting(true);
89
+ try {
90
+ await api.deleteConfiguration(idToDelete);
91
+ await fetchData();
92
+ } finally {
93
+ setIsDeleting(false);
94
+ setIsDeleteModalOpen(false);
95
+ setIdToDelete(undefined);
96
+ }
97
+ };
98
+
99
+ const handleSave = async (data: CreateHealthCheckConfiguration) => {
100
+ await (editingConfig
101
+ ? api.updateConfiguration({ id: editingConfig.id, body: data })
102
+ : api.createConfiguration(data));
103
+ setIsEditorOpen(false);
104
+ await fetchData();
105
+ };
106
+
107
+ const handleEditorClose = () => {
108
+ setIsEditorOpen(false);
109
+ setEditingConfig(undefined);
110
+ };
111
+
112
+ return (
113
+ <PageLayout
114
+ title="Health Checks"
115
+ subtitle="Manage health check configurations"
116
+ loading={permissionLoading}
117
+ allowed={canRead}
118
+ actions={
119
+ <div className="flex gap-2">
120
+ {canManage && (
121
+ <Button variant="outline" asChild>
122
+ <Link to={resolveRoute(healthcheckRoutes.routes.history)}>
123
+ <History className="mr-2 h-4 w-4" /> View History
124
+ </Link>
125
+ </Button>
126
+ )}
127
+ <Button onClick={handleCreate}>
128
+ <Plus className="mr-2 h-4 w-4" /> Create Check
129
+ </Button>
130
+ </div>
131
+ }
132
+ >
133
+ <HealthCheckList
134
+ configurations={configurations}
135
+ strategies={strategies}
136
+ onEdit={handleEdit}
137
+ onDelete={handleDelete}
138
+ />
139
+
140
+ <HealthCheckEditor
141
+ open={isEditorOpen}
142
+ strategies={strategies}
143
+ initialData={editingConfig}
144
+ onSave={handleSave}
145
+ onCancel={handleEditorClose}
146
+ />
147
+
148
+ <ConfirmationModal
149
+ isOpen={isDeleteModalOpen}
150
+ onClose={() => setIsDeleteModalOpen(false)}
151
+ onConfirm={confirmDelete}
152
+ title="Delete Health Check"
153
+ message="Are you sure you want to delete this health check configuration? This action cannot be undone."
154
+ confirmText="Delete"
155
+ variant="danger"
156
+ isLoading={isDeleting}
157
+ />
158
+ </PageLayout>
159
+ );
160
+ };
161
+
162
+ export const HealthCheckConfigPage = wrapInSuspense(
163
+ HealthCheckConfigPageContent
164
+ );
@@ -0,0 +1,100 @@
1
+ import { useState } from "react";
2
+ import {
3
+ useApi,
4
+ wrapInSuspense,
5
+ permissionApiRef,
6
+ } from "@checkstack/frontend-api";
7
+ import { healthCheckApiRef } from "../api";
8
+ import { healthcheckRoutes } from "@checkstack/healthcheck-common";
9
+ import { resolveRoute } from "@checkstack/common";
10
+ import {
11
+ PageLayout,
12
+ usePagination,
13
+ Card,
14
+ CardHeader,
15
+ CardTitle,
16
+ CardContent,
17
+ BackLink,
18
+ DateRangeFilter,
19
+ getDefaultDateRange,
20
+ type DateRange,
21
+ } from "@checkstack/ui";
22
+ import { useParams } from "react-router-dom";
23
+ import {
24
+ HealthCheckRunsTable,
25
+ type HealthCheckRunDetailed,
26
+ } from "../components/HealthCheckRunsTable";
27
+
28
+ const HealthCheckHistoryDetailPageContent = () => {
29
+ const { systemId, configurationId } = useParams<{
30
+ systemId: string;
31
+ configurationId: string;
32
+ }>();
33
+
34
+ const api = useApi(healthCheckApiRef);
35
+ const permissionApi = useApi(permissionApiRef);
36
+ const { allowed: canManage, loading: permissionLoading } =
37
+ permissionApi.useResourcePermission("healthcheck", "manage");
38
+
39
+ const [dateRange, setDateRange] = useState<DateRange>(getDefaultDateRange);
40
+
41
+ const {
42
+ items: runs,
43
+ loading,
44
+ pagination,
45
+ } = usePagination({
46
+ fetchFn: (params) =>
47
+ api.getDetailedHistory({
48
+ systemId,
49
+ configurationId,
50
+ startDate: params.startDate,
51
+ endDate: params.endDate,
52
+ limit: params.limit,
53
+ offset: params.offset,
54
+ }),
55
+ getItems: (response) => response.runs as HealthCheckRunDetailed[],
56
+ getTotal: (response) => response.total,
57
+ defaultLimit: 20,
58
+ extraParams: { startDate: dateRange.startDate, endDate: dateRange.endDate },
59
+ });
60
+
61
+ return (
62
+ <PageLayout
63
+ title="Health Check Run History"
64
+ subtitle={`System: ${systemId} • Configuration: ${configurationId?.slice(
65
+ 0,
66
+ 8
67
+ )}...`}
68
+ loading={permissionLoading}
69
+ allowed={canManage}
70
+ actions={
71
+ <BackLink to={resolveRoute(healthcheckRoutes.routes.history)}>
72
+ Back to All History
73
+ </BackLink>
74
+ }
75
+ >
76
+ <Card>
77
+ <CardHeader>
78
+ <CardTitle>Run History</CardTitle>
79
+ </CardHeader>
80
+ <CardContent>
81
+ <DateRangeFilter
82
+ value={dateRange}
83
+ onChange={setDateRange}
84
+ className="mb-4"
85
+ />
86
+ <HealthCheckRunsTable
87
+ runs={runs}
88
+ loading={loading}
89
+ emptyMessage="No health check runs found for this configuration."
90
+ pagination={pagination}
91
+ />
92
+ </CardContent>
93
+ </Card>
94
+ </PageLayout>
95
+ );
96
+ };
97
+
98
+ export const HealthCheckHistoryDetailPage = wrapInSuspense(
99
+ HealthCheckHistoryDetailPageContent
100
+ );
@@ -0,0 +1,67 @@
1
+ import {
2
+ useApi,
3
+ wrapInSuspense,
4
+ permissionApiRef,
5
+ } from "@checkstack/frontend-api";
6
+ import { healthCheckApiRef } from "../api";
7
+ import {
8
+ PageLayout,
9
+ usePagination,
10
+ Card,
11
+ CardHeader,
12
+ CardTitle,
13
+ CardContent,
14
+ } from "@checkstack/ui";
15
+ import {
16
+ HealthCheckRunsTable,
17
+ type HealthCheckRunDetailed,
18
+ } from "../components/HealthCheckRunsTable";
19
+
20
+ const HealthCheckHistoryPageContent = () => {
21
+ const api = useApi(healthCheckApiRef);
22
+ const permissionApi = useApi(permissionApiRef);
23
+ const { allowed: canManage, loading: permissionLoading } =
24
+ permissionApi.useResourcePermission("healthcheck", "manage");
25
+
26
+ const {
27
+ items: runs,
28
+ loading,
29
+ pagination,
30
+ } = usePagination({
31
+ fetchFn: (params: { limit: number; offset: number }) =>
32
+ api.getDetailedHistory({
33
+ limit: params.limit,
34
+ offset: params.offset,
35
+ }),
36
+ getItems: (response) => response.runs as HealthCheckRunDetailed[],
37
+ getTotal: (response) => response.total,
38
+ defaultLimit: 20,
39
+ });
40
+
41
+ return (
42
+ <PageLayout
43
+ title="Health Check History"
44
+ subtitle="Detailed run history with full result data"
45
+ loading={permissionLoading}
46
+ allowed={canManage}
47
+ >
48
+ <Card>
49
+ <CardHeader>
50
+ <CardTitle>Run History</CardTitle>
51
+ </CardHeader>
52
+ <CardContent>
53
+ <HealthCheckRunsTable
54
+ runs={runs}
55
+ loading={loading}
56
+ showFilterColumns
57
+ pagination={pagination}
58
+ />
59
+ </CardContent>
60
+ </Card>
61
+ </PageLayout>
62
+ );
63
+ };
64
+
65
+ export const HealthCheckHistoryPage = wrapInSuspense(
66
+ HealthCheckHistoryPageContent
67
+ );