@checkstack/healthcheck-frontend 0.3.0 → 0.4.1
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 +78 -0
- package/package.json +2 -1
- package/src/api.ts +2 -10
- package/src/auto-charts/useStrategySchemas.ts +39 -48
- package/src/components/HealthCheckHistory.tsx +10 -14
- package/src/components/HealthCheckSystemOverview.tsx +66 -123
- package/src/components/SystemHealthBadge.tsx +36 -27
- package/src/components/SystemHealthCheckAssignment.tsx +191 -210
- package/src/hooks/useCollectors.ts +21 -34
- package/src/hooks/useHealthCheckData.ts +88 -137
- package/src/index.tsx +2 -16
- package/src/pages/HealthCheckConfigPage.tsx +64 -40
- package/src/pages/HealthCheckHistoryDetailPage.tsx +25 -24
- package/src/pages/HealthCheckHistoryPage.tsx +24 -20
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import {
|
|
3
|
+
usePluginClient,
|
|
4
|
+
accessApiRef,
|
|
5
|
+
useApi,
|
|
6
|
+
} from "@checkstack/frontend-api";
|
|
7
|
+
import { HealthCheckApi } from "../api";
|
|
4
8
|
import {
|
|
5
9
|
healthCheckAccess,
|
|
6
10
|
DEFAULT_RETENTION_CONFIG,
|
|
@@ -49,22 +53,6 @@ interface UseHealthCheckDataResult {
|
|
|
49
53
|
* - The configured rawRetentionDays for the assignment
|
|
50
54
|
*
|
|
51
55
|
* Returns a ready-to-use context for HealthCheckDiagramSlot.
|
|
52
|
-
*
|
|
53
|
-
* @example
|
|
54
|
-
* ```tsx
|
|
55
|
-
* const { context, loading, hasAccess } = useHealthCheckData({
|
|
56
|
-
* systemId,
|
|
57
|
-
* configurationId,
|
|
58
|
-
* strategyId,
|
|
59
|
-
* dateRange: { startDate, endDate },
|
|
60
|
-
* });
|
|
61
|
-
*
|
|
62
|
-
* if (!hasAccess) return <NoAccessMessage />;
|
|
63
|
-
* if (loading) return <LoadingSpinner />;
|
|
64
|
-
* if (!context) return null;
|
|
65
|
-
*
|
|
66
|
-
* return <ExtensionSlot slot={HealthCheckDiagramSlot} context={context} />;
|
|
67
|
-
* ```
|
|
68
56
|
*/
|
|
69
57
|
export function useHealthCheckData({
|
|
70
58
|
systemId,
|
|
@@ -74,30 +62,13 @@ export function useHealthCheckData({
|
|
|
74
62
|
limit = 100,
|
|
75
63
|
offset = 0,
|
|
76
64
|
}: UseHealthCheckDataProps): UseHealthCheckDataResult {
|
|
77
|
-
const
|
|
65
|
+
const healthCheckClient = usePluginClient(HealthCheckApi);
|
|
78
66
|
const accessApi = useApi(accessApiRef);
|
|
79
67
|
|
|
80
68
|
// Access state
|
|
81
|
-
const { allowed: hasAccess, loading: accessLoading } =
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
// Retention config state
|
|
85
|
-
const [retentionConfig, setRetentionConfig] = useState<RetentionConfig>(
|
|
86
|
-
DEFAULT_RETENTION_CONFIG
|
|
69
|
+
const { allowed: hasAccess, loading: accessLoading } = accessApi.useAccess(
|
|
70
|
+
healthCheckAccess.details
|
|
87
71
|
);
|
|
88
|
-
const [retentionLoading, setRetentionLoading] = useState(true);
|
|
89
|
-
|
|
90
|
-
// Raw data state
|
|
91
|
-
const [rawRuns, setRawRuns] = useState<
|
|
92
|
-
TypedHealthCheckRun<Record<string, unknown>>[]
|
|
93
|
-
>([]);
|
|
94
|
-
const [rawLoading, setRawLoading] = useState(false);
|
|
95
|
-
|
|
96
|
-
// Aggregated data state
|
|
97
|
-
const [aggregatedBuckets, setAggregatedBuckets] = useState<
|
|
98
|
-
TypedAggregatedBucket<Record<string, unknown>>[]
|
|
99
|
-
>([]);
|
|
100
|
-
const [aggregatedLoading, setAggregatedLoading] = useState(false);
|
|
101
72
|
|
|
102
73
|
// Calculate date range in days
|
|
103
74
|
const dateRangeDays = useMemo(() => {
|
|
@@ -107,69 +78,64 @@ export function useHealthCheckData({
|
|
|
107
78
|
);
|
|
108
79
|
}, [dateRange.startDate, dateRange.endDate]);
|
|
109
80
|
|
|
81
|
+
// Query: Fetch retention config
|
|
82
|
+
const { data: retentionData, isLoading: retentionLoading } =
|
|
83
|
+
healthCheckClient.getRetentionConfig.useQuery(
|
|
84
|
+
{ systemId, configurationId },
|
|
85
|
+
{ enabled: !!systemId && !!configurationId && hasAccess }
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const retentionConfig =
|
|
89
|
+
retentionData?.retentionConfig ?? DEFAULT_RETENTION_CONFIG;
|
|
90
|
+
|
|
110
91
|
// Determine if we should use aggregated data
|
|
111
92
|
const isAggregated = dateRangeDays > retentionConfig.rawRetentionDays;
|
|
112
93
|
|
|
113
|
-
// Fetch
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
.finally(() => setRetentionLoading(false));
|
|
126
|
-
}, [api, systemId, configurationId]);
|
|
127
|
-
|
|
128
|
-
// Fetch raw data function - extracted for reuse by signal handler
|
|
129
|
-
const fetchRawData = useCallback(
|
|
130
|
-
(showLoading = true) => {
|
|
131
|
-
if (showLoading) {
|
|
132
|
-
setRawLoading(true);
|
|
133
|
-
}
|
|
134
|
-
api
|
|
135
|
-
.getDetailedHistory({
|
|
136
|
-
systemId,
|
|
137
|
-
configurationId,
|
|
138
|
-
startDate: dateRange.startDate,
|
|
139
|
-
// Don't pass endDate for live updates - backend defaults to 'now'
|
|
140
|
-
limit,
|
|
141
|
-
offset,
|
|
142
|
-
})
|
|
143
|
-
.then((response) => {
|
|
144
|
-
setRawRuns(
|
|
145
|
-
response.runs.map((r) => ({
|
|
146
|
-
id: r.id,
|
|
147
|
-
configurationId,
|
|
148
|
-
systemId,
|
|
149
|
-
status: r.status,
|
|
150
|
-
timestamp: r.timestamp,
|
|
151
|
-
latencyMs: r.latencyMs,
|
|
152
|
-
result: r.result,
|
|
153
|
-
}))
|
|
154
|
-
);
|
|
155
|
-
})
|
|
156
|
-
.finally(() => setRawLoading(false));
|
|
94
|
+
// Query: Fetch raw data (when in raw mode)
|
|
95
|
+
const {
|
|
96
|
+
data: rawData,
|
|
97
|
+
isLoading: rawLoading,
|
|
98
|
+
refetch: refetchRawData,
|
|
99
|
+
} = healthCheckClient.getDetailedHistory.useQuery(
|
|
100
|
+
{
|
|
101
|
+
systemId,
|
|
102
|
+
configurationId,
|
|
103
|
+
startDate: dateRange.startDate,
|
|
104
|
+
limit,
|
|
105
|
+
offset,
|
|
157
106
|
},
|
|
158
|
-
|
|
107
|
+
{
|
|
108
|
+
enabled:
|
|
109
|
+
!!systemId &&
|
|
110
|
+
!!configurationId &&
|
|
111
|
+
hasAccess &&
|
|
112
|
+
!accessLoading &&
|
|
113
|
+
!retentionLoading &&
|
|
114
|
+
!isAggregated,
|
|
115
|
+
}
|
|
159
116
|
);
|
|
160
117
|
|
|
161
|
-
// Fetch
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
118
|
+
// Query: Fetch aggregated data (when in aggregated mode)
|
|
119
|
+
const bucketSize = dateRangeDays > 30 ? "daily" : "hourly";
|
|
120
|
+
const { data: aggregatedData, isLoading: aggregatedLoading } =
|
|
121
|
+
healthCheckClient.getDetailedAggregatedHistory.useQuery(
|
|
122
|
+
{
|
|
123
|
+
systemId,
|
|
124
|
+
configurationId,
|
|
125
|
+
startDate: dateRange.startDate,
|
|
126
|
+
endDate: dateRange.endDate,
|
|
127
|
+
bucketSize,
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
enabled:
|
|
131
|
+
!!systemId &&
|
|
132
|
+
!!configurationId &&
|
|
133
|
+
hasAccess &&
|
|
134
|
+
!accessLoading &&
|
|
135
|
+
!retentionLoading &&
|
|
136
|
+
isAggregated,
|
|
137
|
+
}
|
|
138
|
+
);
|
|
173
139
|
|
|
174
140
|
// Listen for realtime health check updates to refresh data silently
|
|
175
141
|
useSignal(HEALTH_CHECK_RUN_COMPLETED, ({ systemId: changedId }) => {
|
|
@@ -181,50 +147,35 @@ export function useHealthCheckData({
|
|
|
181
147
|
!retentionLoading &&
|
|
182
148
|
!isAggregated
|
|
183
149
|
) {
|
|
184
|
-
|
|
150
|
+
void refetchRawData();
|
|
185
151
|
}
|
|
186
152
|
});
|
|
187
153
|
|
|
188
|
-
//
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
);
|
|
214
|
-
})
|
|
215
|
-
.finally(() => setAggregatedLoading(false));
|
|
216
|
-
}, [
|
|
217
|
-
api,
|
|
218
|
-
systemId,
|
|
219
|
-
configurationId,
|
|
220
|
-
hasAccess,
|
|
221
|
-
accessLoading,
|
|
222
|
-
retentionLoading,
|
|
223
|
-
isAggregated,
|
|
224
|
-
dateRangeDays,
|
|
225
|
-
dateRange.startDate,
|
|
226
|
-
dateRange.endDate,
|
|
227
|
-
]);
|
|
154
|
+
// Transform raw runs to typed format
|
|
155
|
+
const rawRuns = useMemo((): TypedHealthCheckRun<
|
|
156
|
+
Record<string, unknown>
|
|
157
|
+
>[] => {
|
|
158
|
+
if (!rawData?.runs) return [];
|
|
159
|
+
return rawData.runs.map((r) => ({
|
|
160
|
+
id: r.id,
|
|
161
|
+
configurationId,
|
|
162
|
+
systemId,
|
|
163
|
+
status: r.status,
|
|
164
|
+
timestamp: r.timestamp,
|
|
165
|
+
latencyMs: r.latencyMs,
|
|
166
|
+
result: r.result as Record<string, unknown>,
|
|
167
|
+
}));
|
|
168
|
+
}, [rawData, configurationId, systemId]);
|
|
169
|
+
|
|
170
|
+
// Transform aggregated buckets
|
|
171
|
+
const aggregatedBuckets = useMemo((): TypedAggregatedBucket<
|
|
172
|
+
Record<string, unknown>
|
|
173
|
+
>[] => {
|
|
174
|
+
if (!aggregatedData?.buckets) return [];
|
|
175
|
+
return aggregatedData.buckets as TypedAggregatedBucket<
|
|
176
|
+
Record<string, unknown>
|
|
177
|
+
>[];
|
|
178
|
+
}, [aggregatedData]);
|
|
228
179
|
|
|
229
180
|
const context = useMemo((): HealthCheckDiagramSlotContext | undefined => {
|
|
230
181
|
if (!hasAccess || accessLoading || retentionLoading) {
|
package/src/index.tsx
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createFrontendPlugin,
|
|
3
3
|
createSlotExtension,
|
|
4
|
-
rpcApiRef,
|
|
5
|
-
type ApiRef,
|
|
6
4
|
UserMenuItemsSlot,
|
|
7
5
|
} from "@checkstack/frontend-api";
|
|
8
|
-
import { healthCheckApiRef, type HealthCheckApiClient } from "./api";
|
|
9
6
|
import { HealthCheckConfigPage } from "./pages/HealthCheckConfigPage";
|
|
10
7
|
import { HealthCheckHistoryPage } from "./pages/HealthCheckHistoryPage";
|
|
11
8
|
import { HealthCheckHistoryDetailPage } from "./pages/HealthCheckHistoryDetailPage";
|
|
@@ -23,7 +20,6 @@ import {
|
|
|
23
20
|
} from "@checkstack/catalog-common";
|
|
24
21
|
import {
|
|
25
22
|
healthcheckRoutes,
|
|
26
|
-
HealthCheckApi,
|
|
27
23
|
pluginMetadata,
|
|
28
24
|
} from "@checkstack/healthcheck-common";
|
|
29
25
|
|
|
@@ -64,18 +60,8 @@ export default createFrontendPlugin({
|
|
|
64
60
|
accessRule: healthCheckAccess.details,
|
|
65
61
|
},
|
|
66
62
|
],
|
|
67
|
-
|
|
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
|
-
],
|
|
63
|
+
// No APIs needed - components use usePluginClient() directly
|
|
64
|
+
apis: [],
|
|
79
65
|
extensions: [
|
|
80
66
|
createSlotExtension(UserMenuItemsSlot, {
|
|
81
67
|
id: "healthcheck.user-menu.items",
|
|
@@ -1,39 +1,42 @@
|
|
|
1
1
|
import { useEffect, useState } from "react";
|
|
2
2
|
import { useSearchParams } from "react-router-dom";
|
|
3
3
|
import {
|
|
4
|
-
|
|
4
|
+
usePluginClient,
|
|
5
5
|
wrapInSuspense,
|
|
6
6
|
accessApiRef,
|
|
7
|
+
useApi,
|
|
7
8
|
} from "@checkstack/frontend-api";
|
|
8
|
-
import {
|
|
9
|
+
import { HealthCheckApi } from "../api";
|
|
9
10
|
import {
|
|
10
11
|
HealthCheckConfiguration,
|
|
11
|
-
HealthCheckStrategyDto,
|
|
12
12
|
CreateHealthCheckConfiguration,
|
|
13
13
|
healthcheckRoutes,
|
|
14
14
|
healthCheckAccess,
|
|
15
15
|
} from "@checkstack/healthcheck-common";
|
|
16
16
|
import { HealthCheckList } from "../components/HealthCheckList";
|
|
17
17
|
import { HealthCheckEditor } from "../components/HealthCheckEditor";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
Button,
|
|
20
|
+
ConfirmationModal,
|
|
21
|
+
PageLayout,
|
|
22
|
+
useToast,
|
|
23
|
+
} from "@checkstack/ui";
|
|
19
24
|
import { Plus, History } from "lucide-react";
|
|
20
25
|
import { Link } from "react-router-dom";
|
|
21
26
|
import { resolveRoute } from "@checkstack/common";
|
|
22
27
|
|
|
23
28
|
const HealthCheckConfigPageContent = () => {
|
|
24
|
-
const
|
|
29
|
+
const healthCheckClient = usePluginClient(HealthCheckApi);
|
|
25
30
|
const accessApi = useApi(accessApiRef);
|
|
31
|
+
const toast = useToast();
|
|
26
32
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
27
|
-
const { allowed: canRead, loading: accessLoading } =
|
|
28
|
-
|
|
33
|
+
const { allowed: canRead, loading: accessLoading } = accessApi.useAccess(
|
|
34
|
+
healthCheckAccess.configuration.read
|
|
35
|
+
);
|
|
29
36
|
const { allowed: canManage } = accessApi.useAccess(
|
|
30
37
|
healthCheckAccess.configuration.manage
|
|
31
38
|
);
|
|
32
39
|
|
|
33
|
-
const [configurations, setConfigurations] = useState<
|
|
34
|
-
HealthCheckConfiguration[]
|
|
35
|
-
>([]);
|
|
36
|
-
const [strategies, setStrategies] = useState<HealthCheckStrategyDto[]>([]);
|
|
37
40
|
const [isEditorOpen, setIsEditorOpen] = useState(false);
|
|
38
41
|
const [editingConfig, setEditingConfig] = useState<
|
|
39
42
|
HealthCheckConfiguration | undefined
|
|
@@ -42,20 +45,17 @@ const HealthCheckConfigPageContent = () => {
|
|
|
42
45
|
// Delete modal state
|
|
43
46
|
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
|
44
47
|
const [idToDelete, setIdToDelete] = useState<string | undefined>();
|
|
45
|
-
const [isDeleting, setIsDeleting] = useState(false);
|
|
46
|
-
|
|
47
|
-
const fetchData = async () => {
|
|
48
|
-
const [{ configurations: configs }, strats] = await Promise.all([
|
|
49
|
-
api.getConfigurations(),
|
|
50
|
-
api.getStrategies(),
|
|
51
|
-
]);
|
|
52
|
-
setConfigurations(configs);
|
|
53
|
-
setStrategies(strats);
|
|
54
|
-
};
|
|
55
48
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
49
|
+
// Fetch configurations with useQuery
|
|
50
|
+
const { data: configurationsData, refetch: refetchConfigurations } =
|
|
51
|
+
healthCheckClient.getConfigurations.useQuery({});
|
|
52
|
+
|
|
53
|
+
// Fetch strategies with useQuery
|
|
54
|
+
const { data: strategies = [] } = healthCheckClient.getStrategies.useQuery(
|
|
55
|
+
{}
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const configurations = configurationsData?.configurations ?? [];
|
|
59
59
|
|
|
60
60
|
// Handle ?action=create URL parameter (from command palette)
|
|
61
61
|
useEffect(() => {
|
|
@@ -68,6 +68,38 @@ const HealthCheckConfigPageContent = () => {
|
|
|
68
68
|
}
|
|
69
69
|
}, [searchParams, canManage, setSearchParams]);
|
|
70
70
|
|
|
71
|
+
// Mutations
|
|
72
|
+
const createMutation = healthCheckClient.createConfiguration.useMutation({
|
|
73
|
+
onSuccess: () => {
|
|
74
|
+
setIsEditorOpen(false);
|
|
75
|
+
void refetchConfigurations();
|
|
76
|
+
},
|
|
77
|
+
onError: (error) => {
|
|
78
|
+
toast.error(error instanceof Error ? error.message : "Failed to create");
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const updateMutation = healthCheckClient.updateConfiguration.useMutation({
|
|
83
|
+
onSuccess: () => {
|
|
84
|
+
setIsEditorOpen(false);
|
|
85
|
+
void refetchConfigurations();
|
|
86
|
+
},
|
|
87
|
+
onError: (error) => {
|
|
88
|
+
toast.error(error instanceof Error ? error.message : "Failed to update");
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const deleteMutation = healthCheckClient.deleteConfiguration.useMutation({
|
|
93
|
+
onSuccess: () => {
|
|
94
|
+
setIsDeleteModalOpen(false);
|
|
95
|
+
setIdToDelete(undefined);
|
|
96
|
+
void refetchConfigurations();
|
|
97
|
+
},
|
|
98
|
+
onError: (error) => {
|
|
99
|
+
toast.error(error instanceof Error ? error.message : "Failed to delete");
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
|
|
71
103
|
const handleCreate = () => {
|
|
72
104
|
setEditingConfig(undefined);
|
|
73
105
|
setIsEditorOpen(true);
|
|
@@ -83,25 +115,17 @@ const HealthCheckConfigPageContent = () => {
|
|
|
83
115
|
setIsDeleteModalOpen(true);
|
|
84
116
|
};
|
|
85
117
|
|
|
86
|
-
const confirmDelete =
|
|
118
|
+
const confirmDelete = () => {
|
|
87
119
|
if (!idToDelete) return;
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
await api.deleteConfiguration(idToDelete);
|
|
91
|
-
await fetchData();
|
|
92
|
-
} finally {
|
|
93
|
-
setIsDeleting(false);
|
|
94
|
-
setIsDeleteModalOpen(false);
|
|
95
|
-
setIdToDelete(undefined);
|
|
96
|
-
}
|
|
120
|
+
deleteMutation.mutate(idToDelete);
|
|
97
121
|
};
|
|
98
122
|
|
|
99
123
|
const handleSave = async (data: CreateHealthCheckConfiguration) => {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
124
|
+
if (editingConfig) {
|
|
125
|
+
updateMutation.mutate({ id: editingConfig.id, body: data });
|
|
126
|
+
} else {
|
|
127
|
+
createMutation.mutate(data);
|
|
128
|
+
}
|
|
105
129
|
};
|
|
106
130
|
|
|
107
131
|
const handleEditorClose = () => {
|
|
@@ -153,7 +177,7 @@ const HealthCheckConfigPageContent = () => {
|
|
|
153
177
|
message="Are you sure you want to delete this health check configuration? This action cannot be undone."
|
|
154
178
|
confirmText="Delete"
|
|
155
179
|
variant="danger"
|
|
156
|
-
isLoading={
|
|
180
|
+
isLoading={deleteMutation.isPending}
|
|
157
181
|
/>
|
|
158
182
|
</PageLayout>
|
|
159
183
|
);
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
2
|
import {
|
|
3
|
-
useApi,
|
|
4
3
|
wrapInSuspense,
|
|
5
4
|
accessApiRef,
|
|
5
|
+
useApi,
|
|
6
|
+
usePluginClient,
|
|
6
7
|
} from "@checkstack/frontend-api";
|
|
7
|
-
import { healthCheckApiRef } from "../api";
|
|
8
8
|
import {
|
|
9
9
|
healthcheckRoutes,
|
|
10
10
|
healthCheckAccess,
|
|
11
|
+
HealthCheckApi,
|
|
11
12
|
} from "@checkstack/healthcheck-common";
|
|
12
13
|
import { resolveRoute } from "@checkstack/common";
|
|
13
14
|
import {
|
|
14
15
|
PageLayout,
|
|
15
16
|
usePagination,
|
|
17
|
+
usePaginationSync,
|
|
16
18
|
Card,
|
|
17
19
|
CardHeader,
|
|
18
20
|
CardTitle,
|
|
@@ -34,33 +36,32 @@ const HealthCheckHistoryDetailPageContent = () => {
|
|
|
34
36
|
configurationId: string;
|
|
35
37
|
}>();
|
|
36
38
|
|
|
37
|
-
const
|
|
39
|
+
const healthCheckClient = usePluginClient(HealthCheckApi);
|
|
38
40
|
const accessApi = useApi(accessApiRef);
|
|
39
|
-
const { allowed: canManage, loading: accessLoading } =
|
|
40
|
-
|
|
41
|
+
const { allowed: canManage, loading: accessLoading } = accessApi.useAccess(
|
|
42
|
+
healthCheckAccess.configuration.manage
|
|
43
|
+
);
|
|
41
44
|
|
|
42
45
|
const [dateRange, setDateRange] = useState<DateRange>(getDefaultDateRange);
|
|
43
46
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
} =
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
limit: params.limit,
|
|
56
|
-
offset: params.offset,
|
|
57
|
-
}),
|
|
58
|
-
getItems: (response) => response.runs as HealthCheckRunDetailed[],
|
|
59
|
-
getTotal: (response) => response.total,
|
|
60
|
-
defaultLimit: 20,
|
|
61
|
-
extraParams: { startDate: dateRange.startDate, endDate: dateRange.endDate },
|
|
47
|
+
// Pagination state
|
|
48
|
+
const pagination = usePagination({ defaultLimit: 20 });
|
|
49
|
+
|
|
50
|
+
// Fetch data with useQuery
|
|
51
|
+
const { data, isLoading } = healthCheckClient.getDetailedHistory.useQuery({
|
|
52
|
+
systemId,
|
|
53
|
+
configurationId,
|
|
54
|
+
startDate: dateRange.startDate,
|
|
55
|
+
endDate: dateRange.endDate,
|
|
56
|
+
limit: pagination.limit,
|
|
57
|
+
offset: pagination.offset,
|
|
62
58
|
});
|
|
63
59
|
|
|
60
|
+
// Sync total from response
|
|
61
|
+
usePaginationSync(pagination, data?.total);
|
|
62
|
+
|
|
63
|
+
const runs = (data?.runs ?? []) as HealthCheckRunDetailed[];
|
|
64
|
+
|
|
64
65
|
return (
|
|
65
66
|
<PageLayout
|
|
66
67
|
title="Health Check Run History"
|
|
@@ -88,7 +89,7 @@ const HealthCheckHistoryDetailPageContent = () => {
|
|
|
88
89
|
/>
|
|
89
90
|
<HealthCheckRunsTable
|
|
90
91
|
runs={runs}
|
|
91
|
-
loading={
|
|
92
|
+
loading={isLoading}
|
|
92
93
|
emptyMessage="No health check runs found for this configuration."
|
|
93
94
|
pagination={pagination}
|
|
94
95
|
/>
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
-
useApi,
|
|
3
2
|
wrapInSuspense,
|
|
4
3
|
accessApiRef,
|
|
4
|
+
useApi,
|
|
5
|
+
usePluginClient,
|
|
5
6
|
} from "@checkstack/frontend-api";
|
|
6
|
-
import { healthCheckApiRef } from "../api";
|
|
7
7
|
import {
|
|
8
8
|
PageLayout,
|
|
9
9
|
usePagination,
|
|
10
|
+
usePaginationSync,
|
|
10
11
|
Card,
|
|
11
12
|
CardHeader,
|
|
12
13
|
CardTitle,
|
|
@@ -16,29 +17,32 @@ import {
|
|
|
16
17
|
HealthCheckRunsTable,
|
|
17
18
|
type HealthCheckRunDetailed,
|
|
18
19
|
} from "../components/HealthCheckRunsTable";
|
|
19
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
healthCheckAccess,
|
|
22
|
+
HealthCheckApi,
|
|
23
|
+
} from "@checkstack/healthcheck-common";
|
|
20
24
|
|
|
21
25
|
const HealthCheckHistoryPageContent = () => {
|
|
22
|
-
const
|
|
26
|
+
const healthCheckClient = usePluginClient(HealthCheckApi);
|
|
23
27
|
const accessApi = useApi(accessApiRef);
|
|
24
|
-
const { allowed: canManage, loading: accessLoading } =
|
|
25
|
-
|
|
28
|
+
const { allowed: canManage, loading: accessLoading } = accessApi.useAccess(
|
|
29
|
+
healthCheckAccess.configuration.manage
|
|
30
|
+
);
|
|
26
31
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
} =
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
limit: params.limit,
|
|
35
|
-
offset: params.offset,
|
|
36
|
-
}),
|
|
37
|
-
getItems: (response) => response.runs as HealthCheckRunDetailed[],
|
|
38
|
-
getTotal: (response) => response.total,
|
|
39
|
-
defaultLimit: 20,
|
|
32
|
+
// Pagination state
|
|
33
|
+
const pagination = usePagination({ defaultLimit: 20 });
|
|
34
|
+
|
|
35
|
+
// Fetch data with useQuery
|
|
36
|
+
const { data, isLoading } = healthCheckClient.getDetailedHistory.useQuery({
|
|
37
|
+
limit: pagination.limit,
|
|
38
|
+
offset: pagination.offset,
|
|
40
39
|
});
|
|
41
40
|
|
|
41
|
+
// Sync total from response
|
|
42
|
+
usePaginationSync(pagination, data?.total);
|
|
43
|
+
|
|
44
|
+
const runs = (data?.runs ?? []) as HealthCheckRunDetailed[];
|
|
45
|
+
|
|
42
46
|
return (
|
|
43
47
|
<PageLayout
|
|
44
48
|
title="Health Check History"
|
|
@@ -53,7 +57,7 @@ const HealthCheckHistoryPageContent = () => {
|
|
|
53
57
|
<CardContent>
|
|
54
58
|
<HealthCheckRunsTable
|
|
55
59
|
runs={runs}
|
|
56
|
-
loading={
|
|
60
|
+
loading={isLoading}
|
|
57
61
|
showFilterColumns
|
|
58
62
|
pagination={pagination}
|
|
59
63
|
/>
|