@checkstack/healthcheck-frontend 0.11.8 → 0.12.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 +64 -0
- package/package.json +5 -5
- package/src/auto-charts/schema-parser.ts +0 -7
- package/src/components/HealthCheckSystemOverview.tsx +2 -66
- package/src/components/SystemHealthCheckAssignment.tsx +4 -4
- package/src/components/editor/CollectorPicker.tsx +129 -0
- package/src/components/editor/CollectorSection.tsx +94 -0
- package/src/components/editor/EditorPanel.tsx +164 -0
- package/src/components/editor/EditorTree.tsx +185 -0
- package/src/components/editor/GeneralSection.tsx +98 -0
- package/src/components/editor/IDEStatusBar.tsx +58 -0
- package/src/hooks/useCollectors.ts +1 -1
- package/src/index.tsx +14 -0
- package/src/pages/HealthCheckConfigPage.tsx +16 -63
- package/src/pages/HealthCheckIDEPage.tsx +382 -0
- package/src/pages/StrategyPickerPage.tsx +157 -0
- package/src/components/HealthCheckEditor.tsx +0 -190
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import React, { useMemo } from "react";
|
|
2
|
+
import type {
|
|
3
|
+
CollectorConfigEntry,
|
|
4
|
+
CollectorDto,
|
|
5
|
+
} from "@checkstack/healthcheck-common";
|
|
6
|
+
import { Plus, Settings, Shield, ChevronRight } from "lucide-react";
|
|
7
|
+
import { isBuiltInCollector } from "../../hooks/useCollectors";
|
|
8
|
+
import type { ValidationIssue } from "./IDEStatusBar";
|
|
9
|
+
import { Badge } from "@checkstack/ui";
|
|
10
|
+
|
|
11
|
+
// =============================================================================
|
|
12
|
+
// TYPES
|
|
13
|
+
// =============================================================================
|
|
14
|
+
|
|
15
|
+
export type TreeNodeId =
|
|
16
|
+
| "general"
|
|
17
|
+
| "access"
|
|
18
|
+
| "collector-picker"
|
|
19
|
+
| `collector:${string}`;
|
|
20
|
+
|
|
21
|
+
interface EditorTreeProps {
|
|
22
|
+
collectors: CollectorConfigEntry[];
|
|
23
|
+
availableCollectors: CollectorDto[];
|
|
24
|
+
selectedNode: TreeNodeId;
|
|
25
|
+
onSelectNode: (nodeId: TreeNodeId) => void;
|
|
26
|
+
onAddCollector: (collectorId: string) => void;
|
|
27
|
+
validationIssues: ValidationIssue[];
|
|
28
|
+
strategyId: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// =============================================================================
|
|
32
|
+
// VALIDATION INDICATOR
|
|
33
|
+
// =============================================================================
|
|
34
|
+
|
|
35
|
+
function ValidationDot({ nodeId, issues }: { nodeId: string; issues: ValidationIssue[] }) {
|
|
36
|
+
const nodeIssues = issues.filter((i) => i.nodeId === nodeId);
|
|
37
|
+
if (nodeIssues.length === 0) return;
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<span className="ml-auto flex h-2 w-2 rounded-full bg-destructive shrink-0" />
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// =============================================================================
|
|
45
|
+
// TREE NODE
|
|
46
|
+
// =============================================================================
|
|
47
|
+
|
|
48
|
+
function TreeNode({
|
|
49
|
+
nodeId,
|
|
50
|
+
label,
|
|
51
|
+
icon: Icon,
|
|
52
|
+
selected,
|
|
53
|
+
onClick,
|
|
54
|
+
issues,
|
|
55
|
+
indent = false,
|
|
56
|
+
badge,
|
|
57
|
+
}: {
|
|
58
|
+
nodeId: string;
|
|
59
|
+
label: string;
|
|
60
|
+
icon: React.ElementType;
|
|
61
|
+
selected: boolean;
|
|
62
|
+
onClick: () => void;
|
|
63
|
+
issues: ValidationIssue[];
|
|
64
|
+
indent?: boolean;
|
|
65
|
+
badge?: string;
|
|
66
|
+
}) {
|
|
67
|
+
return (
|
|
68
|
+
<button
|
|
69
|
+
type="button"
|
|
70
|
+
onClick={onClick}
|
|
71
|
+
className={`flex items-center gap-2 w-full px-3 py-2 text-sm text-left transition-colors ${
|
|
72
|
+
indent ? "pl-7" : ""
|
|
73
|
+
} ${
|
|
74
|
+
selected
|
|
75
|
+
? "bg-primary/10 text-primary border-l-2 border-primary"
|
|
76
|
+
: "hover:bg-muted/50 border-l-2 border-transparent"
|
|
77
|
+
}`}
|
|
78
|
+
>
|
|
79
|
+
<Icon className="h-4 w-4 shrink-0 opacity-60" />
|
|
80
|
+
<span className="truncate flex-1">{label}</span>
|
|
81
|
+
{badge && (
|
|
82
|
+
<Badge variant="secondary" className="text-[10px] shrink-0">
|
|
83
|
+
{badge}
|
|
84
|
+
</Badge>
|
|
85
|
+
)}
|
|
86
|
+
<ValidationDot nodeId={nodeId} issues={issues} />
|
|
87
|
+
</button>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// =============================================================================
|
|
92
|
+
// EDITOR TREE
|
|
93
|
+
// =============================================================================
|
|
94
|
+
|
|
95
|
+
export const EditorTree: React.FC<EditorTreeProps> = ({
|
|
96
|
+
collectors,
|
|
97
|
+
availableCollectors,
|
|
98
|
+
selectedNode,
|
|
99
|
+
onSelectNode,
|
|
100
|
+
validationIssues,
|
|
101
|
+
strategyId,
|
|
102
|
+
}) => {
|
|
103
|
+
// Check if there are addable collectors remaining
|
|
104
|
+
const hasAddableCollectors = useMemo(() => {
|
|
105
|
+
const configuredIds = new Set(collectors.map((c) => c.collectorId));
|
|
106
|
+
return availableCollectors.some(
|
|
107
|
+
(c) => !configuredIds.has(c.id) || c.allowMultiple,
|
|
108
|
+
);
|
|
109
|
+
}, [collectors, availableCollectors]);
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<div className="py-2">
|
|
113
|
+
{/* General */}
|
|
114
|
+
<TreeNode
|
|
115
|
+
nodeId="general"
|
|
116
|
+
label="General"
|
|
117
|
+
icon={Settings}
|
|
118
|
+
selected={selectedNode === "general"}
|
|
119
|
+
onClick={() => onSelectNode("general")}
|
|
120
|
+
issues={validationIssues}
|
|
121
|
+
/>
|
|
122
|
+
|
|
123
|
+
{/* Collectors Section Header */}
|
|
124
|
+
<div className="px-3 pt-4 pb-1">
|
|
125
|
+
<span className="text-[11px] font-semibold text-muted-foreground uppercase tracking-wider">
|
|
126
|
+
Check Items
|
|
127
|
+
</span>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
{/* Configured Collectors */}
|
|
131
|
+
{collectors.map((entry) => {
|
|
132
|
+
const collector = availableCollectors.find(
|
|
133
|
+
(c) => c.id === entry.collectorId,
|
|
134
|
+
);
|
|
135
|
+
const builtIn = isBuiltInCollector(entry.collectorId, strategyId);
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
<TreeNode
|
|
139
|
+
key={entry.id}
|
|
140
|
+
nodeId={`collector:${entry.id}`}
|
|
141
|
+
label={collector?.displayName ?? entry.collectorId}
|
|
142
|
+
icon={ChevronRight}
|
|
143
|
+
selected={selectedNode === `collector:${entry.id}`}
|
|
144
|
+
onClick={() => onSelectNode(`collector:${entry.id}`)}
|
|
145
|
+
issues={validationIssues}
|
|
146
|
+
indent
|
|
147
|
+
badge={builtIn ? "Built-in" : undefined}
|
|
148
|
+
/>
|
|
149
|
+
);
|
|
150
|
+
})}
|
|
151
|
+
|
|
152
|
+
{/* Add Collector Button */}
|
|
153
|
+
{hasAddableCollectors && (
|
|
154
|
+
<button
|
|
155
|
+
type="button"
|
|
156
|
+
onClick={() => onSelectNode("collector-picker")}
|
|
157
|
+
className={`flex items-center gap-2 w-full px-3 py-2 pl-7 text-sm text-left transition-colors text-muted-foreground hover:text-foreground hover:bg-muted/50 border-l-2 ${
|
|
158
|
+
selectedNode === "collector-picker"
|
|
159
|
+
? "border-primary bg-primary/10 text-primary"
|
|
160
|
+
: "border-transparent"
|
|
161
|
+
}`}
|
|
162
|
+
>
|
|
163
|
+
<Plus className="h-4 w-4 shrink-0" />
|
|
164
|
+
<span className="truncate">Add check item...</span>
|
|
165
|
+
</button>
|
|
166
|
+
)}
|
|
167
|
+
|
|
168
|
+
{/* Access Control */}
|
|
169
|
+
<div className="px-3 pt-4 pb-1">
|
|
170
|
+
<span className="text-[11px] font-semibold text-muted-foreground uppercase tracking-wider">
|
|
171
|
+
Permissions
|
|
172
|
+
</span>
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
<TreeNode
|
|
176
|
+
nodeId="access"
|
|
177
|
+
label="Access Control"
|
|
178
|
+
icon={Shield}
|
|
179
|
+
selected={selectedNode === "access"}
|
|
180
|
+
onClick={() => onSelectNode("access")}
|
|
181
|
+
issues={validationIssues}
|
|
182
|
+
/>
|
|
183
|
+
</div>
|
|
184
|
+
);
|
|
185
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { HealthCheckStrategyDto } from "@checkstack/healthcheck-common";
|
|
3
|
+
import { Input, Label, DynamicForm } from "@checkstack/ui";
|
|
4
|
+
import { AlertTriangle } from "lucide-react";
|
|
5
|
+
|
|
6
|
+
interface GeneralSectionProps {
|
|
7
|
+
name: string;
|
|
8
|
+
intervalSeconds: number;
|
|
9
|
+
strategyConfig: Record<string, unknown>;
|
|
10
|
+
strategy: HealthCheckStrategyDto | undefined;
|
|
11
|
+
onNameChange: (name: string) => void;
|
|
12
|
+
onIntervalChange: (interval: number) => void;
|
|
13
|
+
onStrategyConfigChange: (config: Record<string, unknown>) => void;
|
|
14
|
+
onStrategyConfigValidChange: (isValid: boolean) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const GeneralSection: React.FC<GeneralSectionProps> = ({
|
|
18
|
+
name,
|
|
19
|
+
intervalSeconds,
|
|
20
|
+
strategyConfig,
|
|
21
|
+
strategy,
|
|
22
|
+
onNameChange,
|
|
23
|
+
onIntervalChange,
|
|
24
|
+
onStrategyConfigChange,
|
|
25
|
+
onStrategyConfigValidChange,
|
|
26
|
+
}) => {
|
|
27
|
+
return (
|
|
28
|
+
<div className="space-y-6">
|
|
29
|
+
<div>
|
|
30
|
+
<h2 className="text-lg font-semibold">General</h2>
|
|
31
|
+
<p className="text-sm text-muted-foreground">
|
|
32
|
+
Basic configuration for this health check.
|
|
33
|
+
</p>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
{/* Strategy Display */}
|
|
37
|
+
{strategy && (
|
|
38
|
+
<div className="flex items-center gap-2 rounded-md border border-border/50 bg-muted/30 px-3 py-2">
|
|
39
|
+
<span className="text-sm font-medium">{strategy.displayName}</span>
|
|
40
|
+
{strategy.description && (
|
|
41
|
+
<span className="text-xs text-muted-foreground">
|
|
42
|
+
— {strategy.description}
|
|
43
|
+
</span>
|
|
44
|
+
)}
|
|
45
|
+
</div>
|
|
46
|
+
)}
|
|
47
|
+
|
|
48
|
+
{/* Name */}
|
|
49
|
+
<div className="space-y-2">
|
|
50
|
+
<Label htmlFor="hc-name">Name</Label>
|
|
51
|
+
<Input
|
|
52
|
+
id="hc-name"
|
|
53
|
+
value={name}
|
|
54
|
+
onChange={(e) => onNameChange(e.target.value)}
|
|
55
|
+
placeholder="e.g. Production API Health"
|
|
56
|
+
/>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
{/* Interval */}
|
|
60
|
+
<div className="space-y-2">
|
|
61
|
+
<Label htmlFor="hc-interval">Interval (seconds)</Label>
|
|
62
|
+
<Input
|
|
63
|
+
id="hc-interval"
|
|
64
|
+
type="number"
|
|
65
|
+
min={1}
|
|
66
|
+
value={intervalSeconds}
|
|
67
|
+
onChange={(e) => onIntervalChange(Number(e.target.value))}
|
|
68
|
+
/>
|
|
69
|
+
{intervalSeconds > 0 && intervalSeconds < 60 && (
|
|
70
|
+
<div className="flex items-center gap-1.5 text-xs text-amber-500">
|
|
71
|
+
<AlertTriangle className="h-3 w-3" />
|
|
72
|
+
<span>
|
|
73
|
+
Sub-minute intervals may cause high load on monitored services.
|
|
74
|
+
</span>
|
|
75
|
+
</div>
|
|
76
|
+
)}
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
{/* Strategy Config */}
|
|
80
|
+
{strategy?.configSchema && (
|
|
81
|
+
<div className="space-y-3 pt-2 border-t">
|
|
82
|
+
<div>
|
|
83
|
+
<h3 className="text-sm font-semibold">Strategy Configuration</h3>
|
|
84
|
+
<p className="text-xs text-muted-foreground">
|
|
85
|
+
Settings specific to {strategy.displayName}.
|
|
86
|
+
</p>
|
|
87
|
+
</div>
|
|
88
|
+
<DynamicForm
|
|
89
|
+
schema={strategy.configSchema}
|
|
90
|
+
value={strategyConfig}
|
|
91
|
+
onChange={onStrategyConfigChange}
|
|
92
|
+
onValidChange={onStrategyConfigValidChange}
|
|
93
|
+
/>
|
|
94
|
+
</div>
|
|
95
|
+
)}
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { TreeNodeId } from "./EditorTree";
|
|
3
|
+
import { AlertCircle, CheckCircle2 } from "lucide-react";
|
|
4
|
+
|
|
5
|
+
// =============================================================================
|
|
6
|
+
// TYPES
|
|
7
|
+
// =============================================================================
|
|
8
|
+
|
|
9
|
+
export interface ValidationIssue {
|
|
10
|
+
nodeId: TreeNodeId;
|
|
11
|
+
message: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface IDEStatusBarProps {
|
|
15
|
+
issues: ValidationIssue[];
|
|
16
|
+
onIssueClick: (nodeId: TreeNodeId) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// =============================================================================
|
|
20
|
+
// STATUS BAR
|
|
21
|
+
// =============================================================================
|
|
22
|
+
|
|
23
|
+
export const IDEStatusBar: React.FC<IDEStatusBarProps> = ({
|
|
24
|
+
issues,
|
|
25
|
+
onIssueClick,
|
|
26
|
+
}) => {
|
|
27
|
+
if (issues.length === 0) {
|
|
28
|
+
return (
|
|
29
|
+
<div className="flex items-center gap-2 px-4 py-2 mt-2 rounded-md border bg-card text-xs text-muted-foreground">
|
|
30
|
+
<CheckCircle2 className="h-3.5 w-3.5 text-green-500" />
|
|
31
|
+
<span>No issues found</span>
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div className="flex items-center gap-3 px-4 py-2 mt-2 rounded-md border bg-card text-xs">
|
|
38
|
+
<div className="flex items-center gap-1.5 text-destructive shrink-0">
|
|
39
|
+
<AlertCircle className="h-3.5 w-3.5" />
|
|
40
|
+
<span className="font-medium">
|
|
41
|
+
{issues.length} {issues.length === 1 ? "issue" : "issues"}
|
|
42
|
+
</span>
|
|
43
|
+
</div>
|
|
44
|
+
<div className="flex items-center gap-2 overflow-x-auto">
|
|
45
|
+
{issues.map((issue, i) => (
|
|
46
|
+
<button
|
|
47
|
+
key={`${issue.nodeId}-${i}`}
|
|
48
|
+
type="button"
|
|
49
|
+
onClick={() => onIssueClick(issue.nodeId)}
|
|
50
|
+
className="text-muted-foreground hover:text-foreground transition-colors whitespace-nowrap underline-offset-2 hover:underline"
|
|
51
|
+
>
|
|
52
|
+
{issue.message}
|
|
53
|
+
</button>
|
|
54
|
+
))}
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
};
|
package/src/index.tsx
CHANGED
|
@@ -4,6 +4,8 @@ import {
|
|
|
4
4
|
UserMenuItemsSlot,
|
|
5
5
|
} from "@checkstack/frontend-api";
|
|
6
6
|
import { HealthCheckConfigPage } from "./pages/HealthCheckConfigPage";
|
|
7
|
+
import { StrategyPickerPage } from "./pages/StrategyPickerPage";
|
|
8
|
+
import { HealthCheckIDEPage } from "./pages/HealthCheckIDEPage";
|
|
7
9
|
import { HealthCheckHistoryPage } from "./pages/HealthCheckHistoryPage";
|
|
8
10
|
import { HealthCheckHistoryDetailPage } from "./pages/HealthCheckHistoryDetailPage";
|
|
9
11
|
import { HealthCheckMenuItems } from "./components/HealthCheckMenuItems";
|
|
@@ -43,6 +45,18 @@ export default createFrontendPlugin({
|
|
|
43
45
|
title: "Health Checks",
|
|
44
46
|
accessRule: healthCheckAccess.configuration.manage,
|
|
45
47
|
},
|
|
48
|
+
{
|
|
49
|
+
route: healthcheckRoutes.routes.create,
|
|
50
|
+
element: <StrategyPickerPage />,
|
|
51
|
+
title: "Create Health Check",
|
|
52
|
+
accessRule: healthCheckAccess.configuration.manage,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
route: healthcheckRoutes.routes.edit,
|
|
56
|
+
element: <HealthCheckIDEPage />,
|
|
57
|
+
title: "Edit Health Check",
|
|
58
|
+
accessRule: healthCheckAccess.configuration.manage,
|
|
59
|
+
},
|
|
46
60
|
{
|
|
47
61
|
route: healthcheckRoutes.routes.history,
|
|
48
62
|
element: <HealthCheckHistoryPage />,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useEffect
|
|
2
|
-
import { useSearchParams } from "react-router-dom";
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { useSearchParams, useNavigate } from "react-router-dom";
|
|
3
3
|
import {
|
|
4
4
|
usePluginClient,
|
|
5
5
|
wrapInSuspense,
|
|
@@ -8,13 +8,11 @@ import {
|
|
|
8
8
|
} from "@checkstack/frontend-api";
|
|
9
9
|
import { HealthCheckApi } from "../api";
|
|
10
10
|
import {
|
|
11
|
-
HealthCheckConfiguration,
|
|
12
|
-
CreateHealthCheckConfiguration,
|
|
11
|
+
type HealthCheckConfiguration,
|
|
13
12
|
healthcheckRoutes,
|
|
14
13
|
healthCheckAccess,
|
|
15
14
|
} from "@checkstack/healthcheck-common";
|
|
16
15
|
import { HealthCheckList } from "../components/HealthCheckList";
|
|
17
|
-
import { HealthCheckEditor } from "../components/HealthCheckEditor";
|
|
18
16
|
import {
|
|
19
17
|
Button,
|
|
20
18
|
ConfirmationModal,
|
|
@@ -23,12 +21,14 @@ import {
|
|
|
23
21
|
} from "@checkstack/ui";
|
|
24
22
|
import { Plus, History, Activity } from "lucide-react";
|
|
25
23
|
import { Link } from "react-router-dom";
|
|
26
|
-
import { resolveRoute } from "@checkstack/common";
|
|
24
|
+
import { resolveRoute, extractErrorMessage} from "@checkstack/common";
|
|
25
|
+
import { useState } from "react";
|
|
27
26
|
|
|
28
27
|
const HealthCheckConfigPageContent = () => {
|
|
29
28
|
const healthCheckClient = usePluginClient(HealthCheckApi);
|
|
30
29
|
const accessApi = useApi(accessApiRef);
|
|
31
30
|
const toast = useToast();
|
|
31
|
+
const navigate = useNavigate();
|
|
32
32
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
33
33
|
const { allowed: canRead, loading: accessLoading } = accessApi.useAccess(
|
|
34
34
|
healthCheckAccess.configuration.read,
|
|
@@ -37,11 +37,6 @@ const HealthCheckConfigPageContent = () => {
|
|
|
37
37
|
healthCheckAccess.configuration.manage,
|
|
38
38
|
);
|
|
39
39
|
|
|
40
|
-
const [isEditorOpen, setIsEditorOpen] = useState(false);
|
|
41
|
-
const [editingConfig, setEditingConfig] = useState<
|
|
42
|
-
HealthCheckConfiguration | undefined
|
|
43
|
-
>();
|
|
44
|
-
|
|
45
40
|
// Delete modal state
|
|
46
41
|
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
|
47
42
|
const [idToDelete, setIdToDelete] = useState<string | undefined>();
|
|
@@ -60,35 +55,14 @@ const HealthCheckConfigPageContent = () => {
|
|
|
60
55
|
// Handle ?action=create URL parameter (from command palette)
|
|
61
56
|
useEffect(() => {
|
|
62
57
|
if (searchParams.get("action") === "create" && canManage) {
|
|
63
|
-
|
|
64
|
-
setIsEditorOpen(true);
|
|
65
|
-
// Clear the URL param after opening
|
|
58
|
+
// Clear the URL param and navigate to the create flow
|
|
66
59
|
searchParams.delete("action");
|
|
67
60
|
setSearchParams(searchParams, { replace: true });
|
|
61
|
+
navigate(resolveRoute(healthcheckRoutes.routes.create));
|
|
68
62
|
}
|
|
69
|
-
}, [searchParams, canManage, setSearchParams]);
|
|
63
|
+
}, [searchParams, canManage, setSearchParams, navigate]);
|
|
70
64
|
|
|
71
65
|
// 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
66
|
const deleteMutation = healthCheckClient.deleteConfiguration.useMutation({
|
|
93
67
|
onSuccess: () => {
|
|
94
68
|
setIsDeleteModalOpen(false);
|
|
@@ -96,7 +70,7 @@ const HealthCheckConfigPageContent = () => {
|
|
|
96
70
|
void refetchConfigurations();
|
|
97
71
|
},
|
|
98
72
|
onError: (error) => {
|
|
99
|
-
toast.error(error
|
|
73
|
+
toast.error(extractErrorMessage(error, "Failed to delete"));
|
|
100
74
|
},
|
|
101
75
|
});
|
|
102
76
|
|
|
@@ -105,7 +79,7 @@ const HealthCheckConfigPageContent = () => {
|
|
|
105
79
|
void refetchConfigurations();
|
|
106
80
|
},
|
|
107
81
|
onError: (error) => {
|
|
108
|
-
toast.error(error
|
|
82
|
+
toast.error(extractErrorMessage(error, "Failed to pause"));
|
|
109
83
|
},
|
|
110
84
|
});
|
|
111
85
|
|
|
@@ -114,18 +88,18 @@ const HealthCheckConfigPageContent = () => {
|
|
|
114
88
|
void refetchConfigurations();
|
|
115
89
|
},
|
|
116
90
|
onError: (error) => {
|
|
117
|
-
toast.error(error
|
|
91
|
+
toast.error(extractErrorMessage(error, "Failed to resume"));
|
|
118
92
|
},
|
|
119
93
|
});
|
|
120
94
|
|
|
121
95
|
const handleCreate = () => {
|
|
122
|
-
|
|
123
|
-
setIsEditorOpen(true);
|
|
96
|
+
navigate(resolveRoute(healthcheckRoutes.routes.create));
|
|
124
97
|
};
|
|
125
98
|
|
|
126
99
|
const handleEdit = (config: HealthCheckConfiguration) => {
|
|
127
|
-
|
|
128
|
-
|
|
100
|
+
navigate(
|
|
101
|
+
resolveRoute(healthcheckRoutes.routes.edit, { configId: config.id }),
|
|
102
|
+
);
|
|
129
103
|
};
|
|
130
104
|
|
|
131
105
|
const handleDelete = (id: string) => {
|
|
@@ -138,19 +112,6 @@ const HealthCheckConfigPageContent = () => {
|
|
|
138
112
|
deleteMutation.mutate(idToDelete);
|
|
139
113
|
};
|
|
140
114
|
|
|
141
|
-
const handleSave = async (data: CreateHealthCheckConfiguration) => {
|
|
142
|
-
if (editingConfig) {
|
|
143
|
-
updateMutation.mutate({ id: editingConfig.id, body: data });
|
|
144
|
-
} else {
|
|
145
|
-
createMutation.mutate(data);
|
|
146
|
-
}
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
const handleEditorClose = () => {
|
|
150
|
-
setIsEditorOpen(false);
|
|
151
|
-
setEditingConfig(undefined);
|
|
152
|
-
};
|
|
153
|
-
|
|
154
115
|
return (
|
|
155
116
|
<PageLayout
|
|
156
117
|
title="Health Checks"
|
|
@@ -183,14 +144,6 @@ const HealthCheckConfigPageContent = () => {
|
|
|
183
144
|
canManage={canManage}
|
|
184
145
|
/>
|
|
185
146
|
|
|
186
|
-
<HealthCheckEditor
|
|
187
|
-
open={isEditorOpen}
|
|
188
|
-
strategies={strategies}
|
|
189
|
-
initialData={editingConfig}
|
|
190
|
-
onSave={handleSave}
|
|
191
|
-
onCancel={handleEditorClose}
|
|
192
|
-
/>
|
|
193
|
-
|
|
194
147
|
<ConfirmationModal
|
|
195
148
|
isOpen={isDeleteModalOpen}
|
|
196
149
|
onClose={() => setIsDeleteModalOpen(false)}
|