@checkstack/maintenance-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.
- package/CHANGELOG.md +96 -0
- package/package.json +29 -0
- package/src/api.ts +10 -0
- package/src/components/MaintenanceEditor.tsx +317 -0
- package/src/components/MaintenanceMenuItems.tsx +33 -0
- package/src/components/MaintenanceUpdateForm.tsx +123 -0
- package/src/components/SystemMaintenanceBadge.tsx +49 -0
- package/src/components/SystemMaintenancePanel.tsx +176 -0
- package/src/index.tsx +71 -0
- package/src/pages/MaintenanceConfigPage.tsx +347 -0
- package/src/pages/MaintenanceDetailPage.tsx +295 -0
- package/src/pages/SystemMaintenanceHistoryPage.tsx +198 -0
- package/src/utils/badges.tsx +29 -0
- package/tsconfig.json +6 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import React, { useEffect, useState, useMemo, useCallback } from "react";
|
|
2
|
+
import {
|
|
3
|
+
useParams,
|
|
4
|
+
Link,
|
|
5
|
+
useNavigate,
|
|
6
|
+
useSearchParams,
|
|
7
|
+
} from "react-router-dom";
|
|
8
|
+
import {
|
|
9
|
+
useApi,
|
|
10
|
+
rpcApiRef,
|
|
11
|
+
wrapInSuspense,
|
|
12
|
+
permissionApiRef,
|
|
13
|
+
} from "@checkstack/frontend-api";
|
|
14
|
+
import { resolveRoute } from "@checkstack/common";
|
|
15
|
+
import { maintenanceApiRef } from "../api";
|
|
16
|
+
import { maintenanceRoutes } from "@checkstack/maintenance-common";
|
|
17
|
+
import type { MaintenanceDetail } from "@checkstack/maintenance-common";
|
|
18
|
+
import {
|
|
19
|
+
catalogRoutes,
|
|
20
|
+
CatalogApi,
|
|
21
|
+
type System,
|
|
22
|
+
} from "@checkstack/catalog-common";
|
|
23
|
+
import {
|
|
24
|
+
Card,
|
|
25
|
+
CardHeader,
|
|
26
|
+
CardTitle,
|
|
27
|
+
CardContent,
|
|
28
|
+
Badge,
|
|
29
|
+
LoadingSpinner,
|
|
30
|
+
EmptyState,
|
|
31
|
+
PageLayout,
|
|
32
|
+
BackLink,
|
|
33
|
+
Button,
|
|
34
|
+
StatusUpdateTimeline,
|
|
35
|
+
useToast,
|
|
36
|
+
} from "@checkstack/ui";
|
|
37
|
+
import {
|
|
38
|
+
Calendar,
|
|
39
|
+
Clock,
|
|
40
|
+
Wrench,
|
|
41
|
+
Server,
|
|
42
|
+
Plus,
|
|
43
|
+
MessageSquare,
|
|
44
|
+
CheckCircle2,
|
|
45
|
+
} from "lucide-react";
|
|
46
|
+
import { format } from "date-fns";
|
|
47
|
+
import { MaintenanceUpdateForm } from "../components/MaintenanceUpdateForm";
|
|
48
|
+
import { getMaintenanceStatusBadge } from "../utils/badges";
|
|
49
|
+
|
|
50
|
+
const MaintenanceDetailPageContent: React.FC = () => {
|
|
51
|
+
const { maintenanceId } = useParams<{ maintenanceId: string }>();
|
|
52
|
+
const navigate = useNavigate();
|
|
53
|
+
const [searchParams] = useSearchParams();
|
|
54
|
+
const api = useApi(maintenanceApiRef);
|
|
55
|
+
const rpcApi = useApi(rpcApiRef);
|
|
56
|
+
const permissionApi = useApi(permissionApiRef);
|
|
57
|
+
const toast = useToast();
|
|
58
|
+
|
|
59
|
+
const catalogApi = useMemo(() => rpcApi.forPlugin(CatalogApi), [rpcApi]);
|
|
60
|
+
|
|
61
|
+
const { allowed: canManage } = permissionApi.useResourcePermission(
|
|
62
|
+
"maintenance",
|
|
63
|
+
"manage"
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const [maintenance, setMaintenance] = useState<MaintenanceDetail>();
|
|
67
|
+
const [systems, setSystems] = useState<System[]>([]);
|
|
68
|
+
const [loading, setLoading] = useState(true);
|
|
69
|
+
const [showUpdateForm, setShowUpdateForm] = useState(false);
|
|
70
|
+
|
|
71
|
+
const loadData = useCallback(async () => {
|
|
72
|
+
if (!maintenanceId) return;
|
|
73
|
+
|
|
74
|
+
setLoading(true);
|
|
75
|
+
try {
|
|
76
|
+
const [maintenanceData, systemList] = await Promise.all([
|
|
77
|
+
api.getMaintenance({ id: maintenanceId }),
|
|
78
|
+
catalogApi.getSystems(),
|
|
79
|
+
]);
|
|
80
|
+
setMaintenance(maintenanceData ?? undefined);
|
|
81
|
+
setSystems(systemList);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error("Failed to load maintenance details:", error);
|
|
84
|
+
} finally {
|
|
85
|
+
setLoading(false);
|
|
86
|
+
}
|
|
87
|
+
}, [maintenanceId, api, catalogApi]);
|
|
88
|
+
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
loadData();
|
|
91
|
+
}, [loadData]);
|
|
92
|
+
|
|
93
|
+
const handleUpdateSuccess = () => {
|
|
94
|
+
setShowUpdateForm(false);
|
|
95
|
+
loadData();
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const handleComplete = async () => {
|
|
99
|
+
if (!maintenanceId) return;
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
await api.closeMaintenance({ id: maintenanceId });
|
|
103
|
+
toast.success("Maintenance completed");
|
|
104
|
+
await loadData();
|
|
105
|
+
} catch (error) {
|
|
106
|
+
const message =
|
|
107
|
+
error instanceof Error ? error.message : "Failed to complete";
|
|
108
|
+
toast.error(message);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const getSystemName = (systemId: string): string => {
|
|
113
|
+
return systems.find((s) => s.id === systemId)?.name ?? systemId;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
if (!maintenanceId) {
|
|
117
|
+
return (
|
|
118
|
+
<EmptyState
|
|
119
|
+
title="Maintenance not found"
|
|
120
|
+
description="No maintenance ID was provided."
|
|
121
|
+
/>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (loading) {
|
|
126
|
+
return (
|
|
127
|
+
<div className="p-12 flex justify-center">
|
|
128
|
+
<LoadingSpinner />
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (!maintenance) {
|
|
134
|
+
return (
|
|
135
|
+
<EmptyState
|
|
136
|
+
title="Maintenance not found"
|
|
137
|
+
description="The requested maintenance could not be found."
|
|
138
|
+
/>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Use 'from' query param for back navigation, fallback to first affected system
|
|
143
|
+
const sourceSystemId = searchParams.get("from") ?? maintenance.systemIds[0];
|
|
144
|
+
const canComplete =
|
|
145
|
+
canManage &&
|
|
146
|
+
maintenance.status !== "completed" &&
|
|
147
|
+
maintenance.status !== "cancelled";
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<PageLayout
|
|
151
|
+
title={maintenance.title}
|
|
152
|
+
subtitle="Maintenance details and status history"
|
|
153
|
+
loading={false}
|
|
154
|
+
allowed={true}
|
|
155
|
+
actions={
|
|
156
|
+
sourceSystemId ? (
|
|
157
|
+
<BackLink
|
|
158
|
+
onClick={() =>
|
|
159
|
+
navigate(
|
|
160
|
+
resolveRoute(maintenanceRoutes.routes.systemHistory, {
|
|
161
|
+
systemId: sourceSystemId,
|
|
162
|
+
})
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
>
|
|
166
|
+
Back to History
|
|
167
|
+
</BackLink>
|
|
168
|
+
) : undefined
|
|
169
|
+
}
|
|
170
|
+
>
|
|
171
|
+
<div className="space-y-6">
|
|
172
|
+
{/* Maintenance Info Card */}
|
|
173
|
+
<Card>
|
|
174
|
+
<CardHeader className="border-b border-border">
|
|
175
|
+
<div className="flex items-center justify-between">
|
|
176
|
+
<div className="flex items-center gap-2">
|
|
177
|
+
<Wrench className="h-5 w-5 text-muted-foreground" />
|
|
178
|
+
<CardTitle>Maintenance Details</CardTitle>
|
|
179
|
+
</div>
|
|
180
|
+
<div className="flex items-center gap-2">
|
|
181
|
+
{getMaintenanceStatusBadge(maintenance.status)}
|
|
182
|
+
{canComplete && (
|
|
183
|
+
<Button variant="outline" size="sm" onClick={handleComplete}>
|
|
184
|
+
<CheckCircle2 className="h-4 w-4 mr-1" />
|
|
185
|
+
Complete
|
|
186
|
+
</Button>
|
|
187
|
+
)}
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
</CardHeader>
|
|
191
|
+
<CardContent className="p-6 space-y-4">
|
|
192
|
+
{maintenance.description && (
|
|
193
|
+
<div>
|
|
194
|
+
<h4 className="text-sm font-medium text-muted-foreground mb-1">
|
|
195
|
+
Description
|
|
196
|
+
</h4>
|
|
197
|
+
<p className="text-foreground">{maintenance.description}</p>
|
|
198
|
+
</div>
|
|
199
|
+
)}
|
|
200
|
+
|
|
201
|
+
<div className="grid grid-cols-2 gap-4">
|
|
202
|
+
<div>
|
|
203
|
+
<h4 className="text-sm font-medium text-muted-foreground mb-1">
|
|
204
|
+
Start Time
|
|
205
|
+
</h4>
|
|
206
|
+
<div className="flex items-center gap-2 text-foreground">
|
|
207
|
+
<Calendar className="h-4 w-4" />
|
|
208
|
+
<span>{format(new Date(maintenance.startAt), "PPpp")}</span>
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
<div>
|
|
212
|
+
<h4 className="text-sm font-medium text-muted-foreground mb-1">
|
|
213
|
+
End Time
|
|
214
|
+
</h4>
|
|
215
|
+
<div className="flex items-center gap-2 text-foreground">
|
|
216
|
+
<Clock className="h-4 w-4" />
|
|
217
|
+
<span>{format(new Date(maintenance.endAt), "PPpp")}</span>
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
221
|
+
|
|
222
|
+
<div>
|
|
223
|
+
<h4 className="text-sm font-medium text-muted-foreground mb-2">
|
|
224
|
+
Affected Systems
|
|
225
|
+
</h4>
|
|
226
|
+
<div className="flex flex-wrap gap-2">
|
|
227
|
+
{maintenance.systemIds.map((systemId) => (
|
|
228
|
+
<Link
|
|
229
|
+
key={systemId}
|
|
230
|
+
to={resolveRoute(catalogRoutes.routes.systemDetail, {
|
|
231
|
+
systemId,
|
|
232
|
+
})}
|
|
233
|
+
>
|
|
234
|
+
<Badge
|
|
235
|
+
variant="outline"
|
|
236
|
+
className="cursor-pointer hover:bg-muted"
|
|
237
|
+
>
|
|
238
|
+
<Server className="h-3 w-3 mr-1" />
|
|
239
|
+
{getSystemName(systemId)}
|
|
240
|
+
</Badge>
|
|
241
|
+
</Link>
|
|
242
|
+
))}
|
|
243
|
+
</div>
|
|
244
|
+
</div>
|
|
245
|
+
</CardContent>
|
|
246
|
+
</Card>
|
|
247
|
+
|
|
248
|
+
{/* Status Updates Timeline */}
|
|
249
|
+
<Card>
|
|
250
|
+
<CardHeader className="border-b border-border">
|
|
251
|
+
<div className="flex items-center justify-between">
|
|
252
|
+
<div className="flex items-center gap-2">
|
|
253
|
+
<MessageSquare className="h-5 w-5 text-muted-foreground" />
|
|
254
|
+
<CardTitle>Status Updates</CardTitle>
|
|
255
|
+
</div>
|
|
256
|
+
{canManage && !showUpdateForm && (
|
|
257
|
+
<Button
|
|
258
|
+
variant="outline"
|
|
259
|
+
size="sm"
|
|
260
|
+
onClick={() => setShowUpdateForm(true)}
|
|
261
|
+
>
|
|
262
|
+
<Plus className="h-4 w-4 mr-1" />
|
|
263
|
+
Add Update
|
|
264
|
+
</Button>
|
|
265
|
+
)}
|
|
266
|
+
</div>
|
|
267
|
+
</CardHeader>
|
|
268
|
+
<CardContent className="p-6">
|
|
269
|
+
{/* Add Update Form */}
|
|
270
|
+
{showUpdateForm && (
|
|
271
|
+
<div className="mb-6">
|
|
272
|
+
<MaintenanceUpdateForm
|
|
273
|
+
maintenanceId={maintenanceId}
|
|
274
|
+
onSuccess={handleUpdateSuccess}
|
|
275
|
+
onCancel={() => setShowUpdateForm(false)}
|
|
276
|
+
/>
|
|
277
|
+
</div>
|
|
278
|
+
)}
|
|
279
|
+
|
|
280
|
+
<StatusUpdateTimeline
|
|
281
|
+
updates={maintenance.updates}
|
|
282
|
+
renderStatusBadge={getMaintenanceStatusBadge}
|
|
283
|
+
emptyTitle="No status updates"
|
|
284
|
+
emptyDescription="No status updates have been posted for this maintenance."
|
|
285
|
+
/>
|
|
286
|
+
</CardContent>
|
|
287
|
+
</Card>
|
|
288
|
+
</div>
|
|
289
|
+
</PageLayout>
|
|
290
|
+
);
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
export const MaintenanceDetailPage = wrapInSuspense(
|
|
294
|
+
MaintenanceDetailPageContent
|
|
295
|
+
);
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import React, { useEffect, useState, useMemo } from "react";
|
|
2
|
+
import { useParams, useNavigate } from "react-router-dom";
|
|
3
|
+
import {
|
|
4
|
+
useApi,
|
|
5
|
+
rpcApiRef,
|
|
6
|
+
wrapInSuspense,
|
|
7
|
+
} from "@checkstack/frontend-api";
|
|
8
|
+
import { resolveRoute } from "@checkstack/common";
|
|
9
|
+
import { maintenanceApiRef } from "../api";
|
|
10
|
+
import { maintenanceRoutes } from "@checkstack/maintenance-common";
|
|
11
|
+
import type {
|
|
12
|
+
MaintenanceWithSystems,
|
|
13
|
+
MaintenanceStatus,
|
|
14
|
+
} from "@checkstack/maintenance-common";
|
|
15
|
+
import { catalogRoutes, CatalogApi } from "@checkstack/catalog-common";
|
|
16
|
+
import {
|
|
17
|
+
Card,
|
|
18
|
+
CardHeader,
|
|
19
|
+
CardTitle,
|
|
20
|
+
CardContent,
|
|
21
|
+
Badge,
|
|
22
|
+
LoadingSpinner,
|
|
23
|
+
EmptyState,
|
|
24
|
+
Table,
|
|
25
|
+
TableHeader,
|
|
26
|
+
TableRow,
|
|
27
|
+
TableHead,
|
|
28
|
+
TableBody,
|
|
29
|
+
TableCell,
|
|
30
|
+
PageLayout,
|
|
31
|
+
BackLink,
|
|
32
|
+
} from "@checkstack/ui";
|
|
33
|
+
import { Calendar, Clock, History } from "lucide-react";
|
|
34
|
+
import { format } from "date-fns";
|
|
35
|
+
|
|
36
|
+
const SystemMaintenanceHistoryPageContent: React.FC = () => {
|
|
37
|
+
const { systemId } = useParams<{ systemId: string }>();
|
|
38
|
+
const navigate = useNavigate();
|
|
39
|
+
const api = useApi(maintenanceApiRef);
|
|
40
|
+
const rpcApi = useApi(rpcApiRef);
|
|
41
|
+
|
|
42
|
+
const catalogApi = useMemo(() => rpcApi.forPlugin(CatalogApi), [rpcApi]);
|
|
43
|
+
|
|
44
|
+
const [maintenances, setMaintenances] = useState<MaintenanceWithSystems[]>(
|
|
45
|
+
[]
|
|
46
|
+
);
|
|
47
|
+
const [systemName, setSystemName] = useState<string>("");
|
|
48
|
+
const [loading, setLoading] = useState(true);
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (!systemId) return;
|
|
52
|
+
|
|
53
|
+
const loadData = async () => {
|
|
54
|
+
setLoading(true);
|
|
55
|
+
try {
|
|
56
|
+
const [maintenanceList, systemList] = await Promise.all([
|
|
57
|
+
api.listMaintenances({ systemId }),
|
|
58
|
+
catalogApi.getSystems(),
|
|
59
|
+
]);
|
|
60
|
+
setMaintenances(maintenanceList);
|
|
61
|
+
const system = systemList.find((s) => s.id === systemId);
|
|
62
|
+
setSystemName(system?.name ?? "Unknown System");
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error("Failed to load maintenance history:", error);
|
|
65
|
+
} finally {
|
|
66
|
+
setLoading(false);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
loadData();
|
|
71
|
+
}, [systemId, api, catalogApi]);
|
|
72
|
+
|
|
73
|
+
const getStatusBadge = (status: MaintenanceStatus) => {
|
|
74
|
+
switch (status) {
|
|
75
|
+
case "in_progress": {
|
|
76
|
+
return <Badge variant="warning">In Progress</Badge>;
|
|
77
|
+
}
|
|
78
|
+
case "scheduled": {
|
|
79
|
+
return <Badge variant="info">Scheduled</Badge>;
|
|
80
|
+
}
|
|
81
|
+
case "completed": {
|
|
82
|
+
return <Badge variant="success">Completed</Badge>;
|
|
83
|
+
}
|
|
84
|
+
case "cancelled": {
|
|
85
|
+
return <Badge variant="secondary">Cancelled</Badge>;
|
|
86
|
+
}
|
|
87
|
+
default: {
|
|
88
|
+
return <Badge>{status}</Badge>;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
if (!systemId) {
|
|
94
|
+
return (
|
|
95
|
+
<EmptyState
|
|
96
|
+
title="System not found"
|
|
97
|
+
description="No system ID was provided."
|
|
98
|
+
/>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<PageLayout
|
|
104
|
+
title={`Maintenance History: ${systemName}`}
|
|
105
|
+
subtitle="All past and scheduled maintenances for this system"
|
|
106
|
+
loading={loading}
|
|
107
|
+
allowed={true}
|
|
108
|
+
actions={
|
|
109
|
+
<BackLink
|
|
110
|
+
onClick={() =>
|
|
111
|
+
navigate(
|
|
112
|
+
resolveRoute(catalogRoutes.routes.systemDetail, { systemId })
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
>
|
|
116
|
+
Back to System
|
|
117
|
+
</BackLink>
|
|
118
|
+
}
|
|
119
|
+
>
|
|
120
|
+
<Card>
|
|
121
|
+
<CardHeader className="border-b border-border">
|
|
122
|
+
<div className="flex items-center gap-2">
|
|
123
|
+
<History className="h-5 w-5 text-muted-foreground" />
|
|
124
|
+
<CardTitle>Maintenance History</CardTitle>
|
|
125
|
+
</div>
|
|
126
|
+
</CardHeader>
|
|
127
|
+
<CardContent className="p-0">
|
|
128
|
+
{loading ? (
|
|
129
|
+
<div className="p-12 flex justify-center">
|
|
130
|
+
<LoadingSpinner />
|
|
131
|
+
</div>
|
|
132
|
+
) : maintenances.length === 0 ? (
|
|
133
|
+
<EmptyState
|
|
134
|
+
title="No maintenances found"
|
|
135
|
+
description="There are no recorded maintenances for this system."
|
|
136
|
+
/>
|
|
137
|
+
) : (
|
|
138
|
+
<Table>
|
|
139
|
+
<TableHeader>
|
|
140
|
+
<TableRow>
|
|
141
|
+
<TableHead>Title</TableHead>
|
|
142
|
+
<TableHead>Status</TableHead>
|
|
143
|
+
<TableHead>Start</TableHead>
|
|
144
|
+
<TableHead>End</TableHead>
|
|
145
|
+
</TableRow>
|
|
146
|
+
</TableHeader>
|
|
147
|
+
<TableBody>
|
|
148
|
+
{maintenances.map((m) => (
|
|
149
|
+
<TableRow
|
|
150
|
+
key={m.id}
|
|
151
|
+
className="cursor-pointer hover:bg-muted/50"
|
|
152
|
+
onClick={() =>
|
|
153
|
+
navigate(
|
|
154
|
+
`${resolveRoute(maintenanceRoutes.routes.detail, {
|
|
155
|
+
maintenanceId: m.id,
|
|
156
|
+
})}?from=${systemId}`
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
>
|
|
160
|
+
<TableCell>
|
|
161
|
+
<p className="font-medium text-foreground">{m.title}</p>
|
|
162
|
+
{m.description && (
|
|
163
|
+
<p className="text-sm text-muted-foreground truncate max-w-xs">
|
|
164
|
+
{m.description}
|
|
165
|
+
</p>
|
|
166
|
+
)}
|
|
167
|
+
</TableCell>
|
|
168
|
+
<TableCell>{getStatusBadge(m.status)}</TableCell>
|
|
169
|
+
<TableCell>
|
|
170
|
+
<div className="flex items-center gap-1 text-sm text-muted-foreground">
|
|
171
|
+
<Calendar className="h-3 w-3" />
|
|
172
|
+
<span>
|
|
173
|
+
{format(new Date(m.startAt), "MMM d, yyyy HH:mm")}
|
|
174
|
+
</span>
|
|
175
|
+
</div>
|
|
176
|
+
</TableCell>
|
|
177
|
+
<TableCell>
|
|
178
|
+
<div className="flex items-center gap-1 text-sm text-muted-foreground">
|
|
179
|
+
<Clock className="h-3 w-3" />
|
|
180
|
+
<span>
|
|
181
|
+
{format(new Date(m.endAt), "MMM d, yyyy HH:mm")}
|
|
182
|
+
</span>
|
|
183
|
+
</div>
|
|
184
|
+
</TableCell>
|
|
185
|
+
</TableRow>
|
|
186
|
+
))}
|
|
187
|
+
</TableBody>
|
|
188
|
+
</Table>
|
|
189
|
+
)}
|
|
190
|
+
</CardContent>
|
|
191
|
+
</Card>
|
|
192
|
+
</PageLayout>
|
|
193
|
+
);
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
export const SystemMaintenanceHistoryPage = wrapInSuspense(
|
|
197
|
+
SystemMaintenanceHistoryPageContent
|
|
198
|
+
);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Badge } from "@checkstack/ui";
|
|
3
|
+
import type { MaintenanceStatus } from "@checkstack/maintenance-common";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Returns a styled badge for the given maintenance status.
|
|
7
|
+
* Use this utility to ensure consistent status badge styling across the plugin.
|
|
8
|
+
*/
|
|
9
|
+
export function getMaintenanceStatusBadge(
|
|
10
|
+
status: MaintenanceStatus
|
|
11
|
+
): React.ReactNode {
|
|
12
|
+
switch (status) {
|
|
13
|
+
case "in_progress": {
|
|
14
|
+
return <Badge variant="warning">In Progress</Badge>;
|
|
15
|
+
}
|
|
16
|
+
case "scheduled": {
|
|
17
|
+
return <Badge variant="info">Scheduled</Badge>;
|
|
18
|
+
}
|
|
19
|
+
case "completed": {
|
|
20
|
+
return <Badge variant="success">Completed</Badge>;
|
|
21
|
+
}
|
|
22
|
+
case "cancelled": {
|
|
23
|
+
return <Badge variant="secondary">Cancelled</Badge>;
|
|
24
|
+
}
|
|
25
|
+
default: {
|
|
26
|
+
return <Badge>{status}</Badge>;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|