@checkstack/catalog-frontend 0.6.2 → 0.8.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 +42 -0
- package/package.json +1 -1
- package/src/components/DraggableSystem.tsx +2 -2
- package/src/components/SystemDetailPage.tsx +158 -199
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# @checkstack/catalog-frontend
|
|
2
2
|
|
|
3
|
+
## 0.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 80cbc51: Enforce GitOps provenance lock on backend API endpoints to prevent manual configuration drift for synchronized resources.
|
|
8
|
+
|
|
9
|
+
## 0.7.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- bb1fea0: Redesign system detail page with hero banner, two-column layout, plugin metric tiles, and health check slide-over drawer.
|
|
14
|
+
|
|
15
|
+
### New Components
|
|
16
|
+
|
|
17
|
+
- **MetricTile** (`@checkstack/ui`): Compact stat tile with icon, label, value, variant coloring
|
|
18
|
+
- **Sheet** (`@checkstack/ui`): Slide-over drawer built on Radix Dialog primitives
|
|
19
|
+
|
|
20
|
+
### New Extension Slot
|
|
21
|
+
|
|
22
|
+
- **SystemOverviewMetricsSlot** (`@checkstack/catalog-common`): Plugin-contributed at-a-glance metric tiles in the system detail hero banner
|
|
23
|
+
|
|
24
|
+
### Layout Changes
|
|
25
|
+
|
|
26
|
+
- System detail page now uses a hero banner with breadcrumb, status badges, and metric tile strip
|
|
27
|
+
- Two-column layout: monitoring content (left) and system context (right)
|
|
28
|
+
- Health checks rendered as compact card rows instead of heavy accordions
|
|
29
|
+
- Clicking a health check opens a slide-over drawer with summary tiles, timeline charts, and recent runs
|
|
30
|
+
- Right column uses lightweight borderless sections with dividers instead of heavy Card wrappers
|
|
31
|
+
|
|
32
|
+
### Plugin Extensions
|
|
33
|
+
|
|
34
|
+
- Health check, SLO, Incident, and Maintenance plugins each contribute a metric tile to the hero banner
|
|
35
|
+
|
|
36
|
+
### Patch Changes
|
|
37
|
+
|
|
38
|
+
- Updated dependencies [bb1fea0]
|
|
39
|
+
- Updated dependencies [bb1fea0]
|
|
40
|
+
- @checkstack/ui@1.4.0
|
|
41
|
+
- @checkstack/catalog-common@1.4.0
|
|
42
|
+
- @checkstack/auth-frontend@0.5.26
|
|
43
|
+
- @checkstack/gitops-frontend@0.3.1
|
|
44
|
+
|
|
3
45
|
## 0.6.2
|
|
4
46
|
|
|
5
47
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -111,9 +111,9 @@ export const DraggableSystem = ({
|
|
|
111
111
|
variant="ghost"
|
|
112
112
|
size="sm"
|
|
113
113
|
className="h-7 w-7 p-0"
|
|
114
|
-
title={`Add ${system.name} to a group`}
|
|
114
|
+
title={isLocked ? "Managed by GitOps" : `Add ${system.name} to a group`}
|
|
115
115
|
onClick={() => setIsPickerOpen((v) => !v)}
|
|
116
|
-
disabled={availableGroups.length === 0}
|
|
116
|
+
disabled={availableGroups.length === 0 || isLocked}
|
|
117
117
|
aria-expanded={isPickerOpen}
|
|
118
118
|
aria-haspopup="listbox"
|
|
119
119
|
aria-label={`Add ${system.name} to group`}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useEffect, useState, useCallback } from "react";
|
|
2
|
-
import { useParams
|
|
2
|
+
import { useParams } from "react-router-dom";
|
|
3
3
|
import {
|
|
4
4
|
usePluginClient,
|
|
5
5
|
ExtensionSlot,
|
|
@@ -14,32 +14,24 @@ import {
|
|
|
14
14
|
import { NotificationApi } from "@checkstack/notification-common";
|
|
15
15
|
import {
|
|
16
16
|
Card,
|
|
17
|
-
CardHeader,
|
|
18
|
-
CardTitle,
|
|
19
17
|
CardContent,
|
|
18
|
+
Page,
|
|
19
|
+
PageContent,
|
|
20
20
|
PageLayout,
|
|
21
21
|
SubscribeButton,
|
|
22
22
|
useToast,
|
|
23
|
-
|
|
23
|
+
LoadingSpinner,
|
|
24
|
+
AccessDenied,
|
|
24
25
|
} from "@checkstack/ui";
|
|
25
26
|
import { authApiRef } from "@checkstack/auth-frontend/api";
|
|
26
27
|
|
|
27
|
-
import {
|
|
28
|
-
Activity,
|
|
29
|
-
Info,
|
|
30
|
-
Users,
|
|
31
|
-
FileJson,
|
|
32
|
-
Calendar,
|
|
33
|
-
Mail,
|
|
34
|
-
User,
|
|
35
|
-
} from "lucide-react";
|
|
28
|
+
import { Activity, Calendar, Mail, User } from "lucide-react";
|
|
36
29
|
import { extractErrorMessage } from "@checkstack/common";
|
|
37
30
|
|
|
38
31
|
const CATALOG_PLUGIN_ID = "catalog";
|
|
39
32
|
|
|
40
33
|
export const SystemDetailPage: React.FC = () => {
|
|
41
34
|
const { systemId } = useParams<{ systemId: string }>();
|
|
42
|
-
const navigate = useNavigate();
|
|
43
35
|
const catalogClient = usePluginClient(CatalogApi);
|
|
44
36
|
const notificationClient = usePluginClient(NotificationApi);
|
|
45
37
|
const toast = useToast();
|
|
@@ -88,9 +80,7 @@ export const SystemDetailPage: React.FC = () => {
|
|
|
88
80
|
void refetchSubscriptions();
|
|
89
81
|
},
|
|
90
82
|
onError: (error) => {
|
|
91
|
-
toast.error(
|
|
92
|
-
extractErrorMessage(error, "Failed to subscribe"),
|
|
93
|
-
);
|
|
83
|
+
toast.error(extractErrorMessage(error, "Failed to subscribe"));
|
|
94
84
|
},
|
|
95
85
|
});
|
|
96
86
|
|
|
@@ -101,9 +91,7 @@ export const SystemDetailPage: React.FC = () => {
|
|
|
101
91
|
void refetchSubscriptions();
|
|
102
92
|
},
|
|
103
93
|
onError: (error) => {
|
|
104
|
-
toast.error(
|
|
105
|
-
extractErrorMessage(error, "Failed to unsubscribe"),
|
|
106
|
-
);
|
|
94
|
+
toast.error(extractErrorMessage(error, "Failed to unsubscribe"));
|
|
107
95
|
},
|
|
108
96
|
});
|
|
109
97
|
|
|
@@ -144,9 +132,38 @@ export const SystemDetailPage: React.FC = () => {
|
|
|
144
132
|
unsubscribeMutation.mutate({ groupId: getSystemGroupId() });
|
|
145
133
|
};
|
|
146
134
|
|
|
147
|
-
|
|
135
|
+
if (loading) {
|
|
136
|
+
return (
|
|
137
|
+
<Page>
|
|
138
|
+
<PageContent>
|
|
139
|
+
<div className="flex justify-center py-12">
|
|
140
|
+
<LoadingSpinner />
|
|
141
|
+
</div>
|
|
142
|
+
</PageContent>
|
|
143
|
+
</Page>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (notFound) {
|
|
148
|
+
return (
|
|
149
|
+
<Page>
|
|
150
|
+
<PageContent>
|
|
151
|
+
<div className="max-w-3xl space-y-6">
|
|
152
|
+
<AccessDenied />
|
|
153
|
+
</div>
|
|
154
|
+
</PageContent>
|
|
155
|
+
</Page>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Guard for TypeScript
|
|
160
|
+
if (!system) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
148
164
|
const headerActions = (
|
|
149
|
-
<div className="flex items-center gap-
|
|
165
|
+
<div className="flex items-center gap-2">
|
|
166
|
+
<ExtensionSlot slot={SystemStateBadgesSlot} context={{ system }} />
|
|
150
167
|
{session && (
|
|
151
168
|
<SubscribeButton
|
|
152
169
|
isSubscribed={isSubscribed}
|
|
@@ -159,204 +176,146 @@ export const SystemDetailPage: React.FC = () => {
|
|
|
159
176
|
}
|
|
160
177
|
/>
|
|
161
178
|
)}
|
|
162
|
-
<BackLink onClick={() => navigate("/")}>Back to Dashboard</BackLink>
|
|
163
179
|
</div>
|
|
164
180
|
);
|
|
165
181
|
|
|
166
|
-
if (notFound) {
|
|
167
|
-
return (
|
|
168
|
-
<PageLayout
|
|
169
|
-
title="System Not Found"
|
|
170
|
-
icon={Activity}
|
|
171
|
-
actions={headerActions}
|
|
172
|
-
>
|
|
173
|
-
<Card className="border-destructive/30 bg-destructive/10">
|
|
174
|
-
<CardContent className="p-12 text-center">
|
|
175
|
-
<p className="text-destructive">
|
|
176
|
-
The system you're looking for doesn't exist or has been removed.
|
|
177
|
-
</p>
|
|
178
|
-
</CardContent>
|
|
179
|
-
</Card>
|
|
180
|
-
</PageLayout>
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Guard for TypeScript - PageLayout already handles loading state
|
|
185
|
-
if (!system) {
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
182
|
return (
|
|
190
183
|
<PageLayout
|
|
191
184
|
title={system.name}
|
|
192
185
|
icon={Activity}
|
|
193
|
-
loading={loading}
|
|
194
186
|
actions={headerActions}
|
|
187
|
+
loading={false}
|
|
195
188
|
maxWidth="full"
|
|
196
189
|
>
|
|
197
|
-
{/*
|
|
190
|
+
{/* Alert strip — incidents, maintenances, dependency alerts */}
|
|
198
191
|
<ExtensionSlot slot={SystemDetailsTopSlot} context={{ system }} />
|
|
199
192
|
|
|
200
|
-
{/*
|
|
201
|
-
<
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
System Status
|
|
207
|
-
</CardTitle>
|
|
208
|
-
</div>
|
|
209
|
-
</CardHeader>
|
|
210
|
-
<CardContent className="p-6">
|
|
211
|
-
<div className="flex flex-wrap items-center gap-2">
|
|
212
|
-
<ExtensionSlot slot={SystemStateBadgesSlot} context={{ system }} />
|
|
213
|
-
</div>
|
|
214
|
-
</CardContent>
|
|
215
|
-
</Card>
|
|
193
|
+
{/* Two-Column Layout */}
|
|
194
|
+
<div className="grid gap-6 lg:grid-cols-[1fr_340px]">
|
|
195
|
+
{/* Left Column — Monitoring */}
|
|
196
|
+
<div className="space-y-6 min-w-0">
|
|
197
|
+
<ExtensionSlot slot={SystemDetailsSlot} context={{ system }} />
|
|
198
|
+
</div>
|
|
216
199
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
<
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
<div className="flex items-center gap-2 text-muted-foreground">
|
|
249
|
-
<Calendar className="h-4 w-4" />
|
|
250
|
-
<span>
|
|
251
|
-
Updated:{" "}
|
|
252
|
-
{new Date(system.updatedAt).toLocaleDateString("en-US", {
|
|
253
|
-
year: "numeric",
|
|
254
|
-
month: "short",
|
|
255
|
-
day: "numeric",
|
|
256
|
-
})}
|
|
257
|
-
</span>
|
|
200
|
+
{/* Right Column — System Context */}
|
|
201
|
+
<Card className="h-fit">
|
|
202
|
+
<CardContent className="p-4 space-y-4">
|
|
203
|
+
{/* System Information */}
|
|
204
|
+
<div className="space-y-2">
|
|
205
|
+
<h3 className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
|
206
|
+
About
|
|
207
|
+
</h3>
|
|
208
|
+
<p className="text-sm text-foreground">
|
|
209
|
+
{system.description || "No description provided"}
|
|
210
|
+
</p>
|
|
211
|
+
<div className="flex flex-wrap gap-x-4 gap-y-1 text-xs text-muted-foreground">
|
|
212
|
+
<span className="flex items-center gap-1.5">
|
|
213
|
+
<Calendar className="h-3 w-3" />
|
|
214
|
+
Created{" "}
|
|
215
|
+
{new Date(system.createdAt).toLocaleDateString("en-US", {
|
|
216
|
+
year: "numeric",
|
|
217
|
+
month: "short",
|
|
218
|
+
day: "numeric",
|
|
219
|
+
})}
|
|
220
|
+
</span>
|
|
221
|
+
<span className="flex items-center gap-1.5">
|
|
222
|
+
<Calendar className="h-3 w-3" />
|
|
223
|
+
Updated{" "}
|
|
224
|
+
{new Date(system.updatedAt).toLocaleDateString("en-US", {
|
|
225
|
+
year: "numeric",
|
|
226
|
+
month: "short",
|
|
227
|
+
day: "numeric",
|
|
228
|
+
})}
|
|
229
|
+
</span>
|
|
230
|
+
</div>
|
|
258
231
|
</div>
|
|
259
|
-
</div>
|
|
260
|
-
</CardContent>
|
|
261
|
-
</Card>
|
|
262
232
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
<div className="flex items-center gap-2">
|
|
267
|
-
<Mail className="h-5 w-5 text-muted-foreground" />
|
|
268
|
-
<CardTitle className="text-lg font-semibold">Contacts</CardTitle>
|
|
269
|
-
</div>
|
|
270
|
-
</CardHeader>
|
|
271
|
-
<CardContent className="p-6">
|
|
272
|
-
{!contactsData || contactsData.length === 0 ? (
|
|
273
|
-
<p className="text-muted-foreground text-sm">
|
|
274
|
-
No contacts assigned to this system
|
|
275
|
-
</p>
|
|
276
|
-
) : (
|
|
233
|
+
<div className="h-px bg-border" />
|
|
234
|
+
|
|
235
|
+
{/* Contacts */}
|
|
277
236
|
<div className="space-y-2">
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
>
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
)
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
:
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
237
|
+
<h3 className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
|
238
|
+
Contacts
|
|
239
|
+
</h3>
|
|
240
|
+
{!contactsData || contactsData.length === 0 ? (
|
|
241
|
+
<p className="text-sm text-muted-foreground">
|
|
242
|
+
No contacts assigned
|
|
243
|
+
</p>
|
|
244
|
+
) : (
|
|
245
|
+
<div className="space-y-1.5">
|
|
246
|
+
{contactsData.map((contact) => (
|
|
247
|
+
<div
|
|
248
|
+
key={contact.id}
|
|
249
|
+
className="flex items-center gap-2 text-sm"
|
|
250
|
+
>
|
|
251
|
+
{contact.type === "user" ? (
|
|
252
|
+
<User className="h-3.5 w-3.5 text-muted-foreground" />
|
|
253
|
+
) : (
|
|
254
|
+
<Mail className="h-3.5 w-3.5 text-muted-foreground" />
|
|
255
|
+
)}
|
|
256
|
+
<a
|
|
257
|
+
href={`mailto:${contact.type === "user" ? contact.userEmail : contact.email}`}
|
|
258
|
+
className="text-primary hover:underline truncate"
|
|
259
|
+
>
|
|
260
|
+
{contact.type === "user"
|
|
261
|
+
? (contact.userName ?? contact.userId)
|
|
262
|
+
: contact.email}
|
|
263
|
+
</a>
|
|
264
|
+
{contact.label && (
|
|
265
|
+
<span className="text-muted-foreground text-xs">
|
|
266
|
+
({contact.label})
|
|
267
|
+
</span>
|
|
268
|
+
)}
|
|
269
|
+
</div>
|
|
270
|
+
))}
|
|
301
271
|
</div>
|
|
302
|
-
)
|
|
272
|
+
)}
|
|
303
273
|
</div>
|
|
304
|
-
)}
|
|
305
|
-
</CardContent>
|
|
306
|
-
</Card>
|
|
307
274
|
|
|
308
|
-
|
|
309
|
-
<Card className="border-border shadow-sm">
|
|
310
|
-
<CardHeader className="border-b border-border bg-muted/30">
|
|
311
|
-
<div className="flex items-center gap-2">
|
|
312
|
-
<Users className="h-5 w-5 text-muted-foreground" />
|
|
313
|
-
<CardTitle className="text-lg font-semibold">
|
|
314
|
-
Member of Groups
|
|
315
|
-
</CardTitle>
|
|
316
|
-
</div>
|
|
317
|
-
</CardHeader>
|
|
318
|
-
<CardContent className="p-6">
|
|
319
|
-
{groups.length === 0 ? (
|
|
320
|
-
<p className="text-muted-foreground text-sm">
|
|
321
|
-
This system is not part of any groups
|
|
322
|
-
</p>
|
|
323
|
-
) : (
|
|
324
|
-
<div className="flex flex-wrap gap-2">
|
|
325
|
-
{groups.map((group) => (
|
|
326
|
-
<span
|
|
327
|
-
key={group.id}
|
|
328
|
-
className="inline-flex items-center gap-1.5 rounded-full border border-primary/20 bg-primary/10 px-3 py-1 text-sm font-medium text-primary"
|
|
329
|
-
>
|
|
330
|
-
{group.name}
|
|
331
|
-
</span>
|
|
332
|
-
))}
|
|
333
|
-
</div>
|
|
334
|
-
)}
|
|
335
|
-
</CardContent>
|
|
336
|
-
</Card>
|
|
275
|
+
<div className="h-px bg-border" />
|
|
337
276
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
277
|
+
{/* Groups */}
|
|
278
|
+
<div className="space-y-2">
|
|
279
|
+
<h3 className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
|
280
|
+
Groups
|
|
281
|
+
</h3>
|
|
282
|
+
{groups.length === 0 ? (
|
|
283
|
+
<p className="text-sm text-muted-foreground">
|
|
284
|
+
Not part of any groups
|
|
285
|
+
</p>
|
|
286
|
+
) : (
|
|
287
|
+
<div className="flex flex-wrap gap-1.5">
|
|
288
|
+
{groups.map((group) => (
|
|
289
|
+
<span
|
|
290
|
+
key={group.id}
|
|
291
|
+
className="inline-flex items-center rounded-full border border-primary/20 bg-primary/10 px-2.5 py-0.5 text-xs font-medium text-primary"
|
|
292
|
+
>
|
|
293
|
+
{group.name}
|
|
294
|
+
</span>
|
|
295
|
+
))}
|
|
296
|
+
</div>
|
|
297
|
+
)}
|
|
298
|
+
</div>
|
|
358
299
|
|
|
359
|
-
|
|
300
|
+
{/* Metadata (conditional) */}
|
|
301
|
+
{system.metadata &&
|
|
302
|
+
typeof system.metadata === "object" &&
|
|
303
|
+
Object.keys(system.metadata).length > 0 && (
|
|
304
|
+
<>
|
|
305
|
+
<div className="h-px bg-border" />
|
|
306
|
+
<div className="space-y-2">
|
|
307
|
+
<h3 className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
|
308
|
+
Metadata
|
|
309
|
+
</h3>
|
|
310
|
+
<pre className="text-xs text-foreground bg-muted/30 p-3 rounded-md border border-border overflow-x-auto">
|
|
311
|
+
{JSON.stringify(system.metadata, undefined, 2)}
|
|
312
|
+
</pre>
|
|
313
|
+
</div>
|
|
314
|
+
</>
|
|
315
|
+
)}
|
|
316
|
+
</CardContent>
|
|
317
|
+
</Card>
|
|
318
|
+
</div>
|
|
360
319
|
</PageLayout>
|
|
361
320
|
);
|
|
362
321
|
};
|