@checkstack/catalog-frontend 0.0.4 → 0.1.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 CHANGED
@@ -1,5 +1,88 @@
1
1
  # @checkstack/catalog-frontend
2
2
 
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 8e43507: # Teams and Resource-Level Access Control
8
+
9
+ This release introduces a comprehensive Teams system for organizing users and controlling access to resources at a granular level.
10
+
11
+ ## Features
12
+
13
+ ### Team Management
14
+
15
+ - Create, update, and delete teams with name and description
16
+ - Add/remove users from teams
17
+ - Designate team managers with elevated privileges
18
+ - View team membership and manager status
19
+
20
+ ### Resource-Level Access Control
21
+
22
+ - Grant teams access to specific resources (systems, health checks, incidents, maintenances)
23
+ - Configure read-only or manage permissions per team
24
+ - Resource-level "Team Only" mode that restricts access exclusively to team members
25
+ - Separate `resourceAccessSettings` table for resource-level settings (not per-grant)
26
+ - Automatic cleanup of grants when teams are deleted (database cascade)
27
+
28
+ ### Middleware Integration
29
+
30
+ - Extended `autoAuthMiddleware` to support resource access checks
31
+ - Single-resource pre-handler validation for detail endpoints
32
+ - Automatic list filtering for collection endpoints
33
+ - S2S endpoints for access verification
34
+
35
+ ### Frontend Components
36
+
37
+ - `TeamsTab` component for managing teams in Auth Settings
38
+ - `TeamAccessEditor` component for assigning team access to resources
39
+ - Resource-level "Team Only" toggle in `TeamAccessEditor`
40
+ - Integration into System, Health Check, Incident, and Maintenance editors
41
+
42
+ ## Breaking Changes
43
+
44
+ ### API Response Format Changes
45
+
46
+ List endpoints now return objects with named keys instead of arrays directly:
47
+
48
+ ```typescript
49
+ // Before
50
+ const systems = await catalogApi.getSystems();
51
+
52
+ // After
53
+ const { systems } = await catalogApi.getSystems();
54
+ ```
55
+
56
+ Affected endpoints:
57
+
58
+ - `catalog.getSystems` → `{ systems: [...] }`
59
+ - `healthcheck.getConfigurations` → `{ configurations: [...] }`
60
+ - `incident.listIncidents` → `{ incidents: [...] }`
61
+ - `maintenance.listMaintenances` → `{ maintenances: [...] }`
62
+
63
+ ### User Identity Enrichment
64
+
65
+ `RealUser` and `ApplicationUser` types now include `teamIds: string[]` field with team memberships.
66
+
67
+ ## Documentation
68
+
69
+ See `docs/backend/teams.md` for complete API reference and integration guide.
70
+
71
+ ### Patch Changes
72
+
73
+ - 97c5a6b: Fix Radix UI accessibility warning in dialog components by adding visually hidden DialogDescription components
74
+ - Updated dependencies [8e43507]
75
+ - Updated dependencies [97c5a6b]
76
+ - Updated dependencies [97c5a6b]
77
+ - Updated dependencies [8e43507]
78
+ - Updated dependencies [8e43507]
79
+ - @checkstack/ui@0.1.0
80
+ - @checkstack/auth-frontend@0.1.0
81
+ - @checkstack/catalog-common@1.0.0
82
+ - @checkstack/common@0.1.0
83
+ - @checkstack/frontend-api@0.0.4
84
+ - @checkstack/notification-common@0.0.4
85
+
3
86
  ## 0.0.4
4
87
 
5
88
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checkstack/catalog-frontend",
3
- "version": "0.0.4",
3
+ "version": "0.1.0",
4
4
  "type": "module",
5
5
  "main": "src/index.tsx",
6
6
  "scripts": {
@@ -22,7 +22,7 @@ import {
22
22
  ConfirmationModal,
23
23
  useToast,
24
24
  } from "@checkstack/ui";
25
- import { Plus, Trash2, LayoutGrid, Server, Settings } from "lucide-react";
25
+ import { Plus, Trash2, LayoutGrid, Server, Settings, Edit } from "lucide-react";
26
26
  import { SystemEditor } from "./SystemEditor";
27
27
  import { GroupEditor } from "./GroupEditor";
28
28
 
@@ -40,6 +40,7 @@ export const CatalogConfigPage = () => {
40
40
 
41
41
  // Dialog state
42
42
  const [isSystemEditorOpen, setIsSystemEditorOpen] = useState(false);
43
+ const [editingSystem, setEditingSystem] = useState<System | undefined>();
43
44
  const [isGroupEditorOpen, setIsGroupEditorOpen] = useState(false);
44
45
 
45
46
  const [selectedGroupId, setSelectedGroupId] = useState("");
@@ -61,7 +62,7 @@ export const CatalogConfigPage = () => {
61
62
  const loadData = async () => {
62
63
  setLoading(true);
63
64
  try {
64
- const [s, g] = await Promise.all([
65
+ const [{ systems: s }, g] = await Promise.all([
65
66
  catalogApi.getSystems(),
66
67
  catalogApi.getGroups(),
67
68
  ]);
@@ -94,12 +95,25 @@ export const CatalogConfigPage = () => {
94
95
  }
95
96
  }, [searchParams, canManage, setSearchParams]);
96
97
 
97
- const handleCreateSystem = async (data: {
98
+ // Unified save handler for both create and edit
99
+ const handleSaveSystem = async (data: {
98
100
  name: string;
99
101
  description?: string;
100
102
  }) => {
101
- await catalogApi.createSystem(data);
102
- toast.success("System created successfully");
103
+ if (editingSystem) {
104
+ // Update existing system
105
+ await catalogApi.updateSystem({
106
+ id: editingSystem.id,
107
+ data,
108
+ });
109
+ toast.success("System updated successfully");
110
+ setEditingSystem(undefined);
111
+ } else {
112
+ // Create new system
113
+ await catalogApi.createSystem(data);
114
+ toast.success("System created successfully");
115
+ }
116
+ setIsSystemEditorOpen(false);
103
117
  await loadData();
104
118
  };
105
119
 
@@ -191,42 +205,6 @@ export const CatalogConfigPage = () => {
191
205
  }
192
206
  };
193
207
 
194
- const handleUpdateSystemName = async (id: string, newName: string) => {
195
- try {
196
- await catalogApi.updateSystem({ id, data: { name: newName } });
197
- toast.success("System name updated successfully");
198
- loadData();
199
- } catch (error) {
200
- const message =
201
- error instanceof Error ? error.message : "Failed to update system name";
202
- toast.error(message);
203
- console.error("Failed to update system name:", error);
204
- throw error;
205
- }
206
- };
207
-
208
- const handleUpdateSystemDescription = async (
209
- id: string,
210
- newDescription: string
211
- ) => {
212
- try {
213
- await catalogApi.updateSystem({
214
- id,
215
- data: { description: newDescription },
216
- });
217
- toast.success("System description updated successfully");
218
- loadData();
219
- } catch (error) {
220
- const message =
221
- error instanceof Error
222
- ? error.message
223
- : "Failed to update system description";
224
- toast.error(message);
225
- console.error("Failed to update system description:", error);
226
- throw error;
227
- }
228
- };
229
-
230
208
  const handleUpdateGroupName = async (id: string, newName: string) => {
231
209
  try {
232
210
  await catalogApi.updateGroup({ id, data: { name: newName } });
@@ -285,13 +263,9 @@ export const CatalogConfigPage = () => {
285
263
  >
286
264
  <div className="flex-1 space-y-1">
287
265
  <div className="flex items-center justify-between">
288
- <EditableText
289
- value={system.name}
290
- onSave={(newName) =>
291
- handleUpdateSystemName(system.id, newName)
292
- }
293
- className="font-medium text-foreground"
294
- />
266
+ <span className="font-medium text-foreground">
267
+ {system.name}
268
+ </span>
295
269
  <ExtensionSlot
296
270
  slot={CatalogSystemActionsSlot}
297
271
  context={{
@@ -300,25 +274,30 @@ export const CatalogConfigPage = () => {
300
274
  }}
301
275
  />
302
276
  </div>
303
- <EditableText
304
- value={system.description || "No description"}
305
- onSave={(newDescription) =>
306
- handleUpdateSystemDescription(
307
- system.id,
308
- newDescription
309
- )
310
- }
311
- className="text-xs text-muted-foreground font-mono"
312
- placeholder="Add description..."
313
- />
277
+ <p className="text-xs text-muted-foreground">
278
+ {system.description || "No description"}
279
+ </p>
280
+ </div>
281
+ <div className="flex gap-1">
282
+ <Button
283
+ variant="ghost"
284
+ size="sm"
285
+ className="h-8 w-8 p-0"
286
+ onClick={() => {
287
+ setEditingSystem(system);
288
+ setIsSystemEditorOpen(true);
289
+ }}
290
+ >
291
+ <Edit className="w-4 h-4" />
292
+ </Button>
293
+ <Button
294
+ variant="ghost"
295
+ className="text-destructive hover:text-destructive/90 hover:bg-destructive/10 h-8 w-8 p-0"
296
+ onClick={() => handleDeleteSystem(system.id)}
297
+ >
298
+ <Trash2 className="w-4 h-4" />
299
+ </Button>
314
300
  </div>
315
- <Button
316
- variant="ghost"
317
- className="text-destructive hover:text-destructive/90 hover:bg-destructive/10 h-8 w-8 p-0"
318
- onClick={() => handleDeleteSystem(system.id)}
319
- >
320
- <Trash2 className="w-4 h-4" />
321
- </Button>
322
301
  </div>
323
302
  ))}
324
303
  </div>
@@ -466,8 +445,20 @@ export const CatalogConfigPage = () => {
466
445
  {/* Dialogs */}
467
446
  <SystemEditor
468
447
  open={isSystemEditorOpen}
469
- onClose={() => setIsSystemEditorOpen(false)}
470
- onSave={handleCreateSystem}
448
+ onClose={() => {
449
+ setIsSystemEditorOpen(false);
450
+ setEditingSystem(undefined);
451
+ }}
452
+ onSave={handleSaveSystem}
453
+ initialData={
454
+ editingSystem
455
+ ? {
456
+ id: editingSystem.id,
457
+ name: editingSystem.name,
458
+ description: editingSystem.description ?? undefined,
459
+ }
460
+ : undefined
461
+ }
471
462
  />
472
463
 
473
464
  <GroupEditor
@@ -5,6 +5,7 @@ import {
5
5
  Label,
6
6
  Dialog,
7
7
  DialogContent,
8
+ DialogDescription,
8
9
  DialogHeader,
9
10
  DialogTitle,
10
11
  DialogFooter,
@@ -60,6 +61,11 @@ export const GroupEditor: React.FC<GroupEditorProps> = ({
60
61
  <DialogTitle>
61
62
  {initialData ? "Edit Group" : "Create Group"}
62
63
  </DialogTitle>
64
+ <DialogDescription className="sr-only">
65
+ {initialData
66
+ ? "Modify the settings for this group"
67
+ : "Create a new group to organize your systems"}
68
+ </DialogDescription>
63
69
  </DialogHeader>
64
70
 
65
71
  <div className="space-y-4 py-4">
@@ -57,7 +57,7 @@ export const SystemDetailPage: React.FC = () => {
57
57
  }
58
58
 
59
59
  Promise.all([catalogApi.getSystems(), catalogApi.getGroups()])
60
- .then(([systems, allGroups]) => {
60
+ .then(([{ systems }, allGroups]) => {
61
61
  const foundSystem = systems.find((s) => s.id === systemId);
62
62
 
63
63
  if (!foundSystem) {
@@ -5,17 +5,19 @@ import {
5
5
  Label,
6
6
  Dialog,
7
7
  DialogContent,
8
+ DialogDescription,
8
9
  DialogHeader,
9
10
  DialogTitle,
10
11
  DialogFooter,
11
12
  useToast,
12
13
  } from "@checkstack/ui";
14
+ import { TeamAccessEditor } from "@checkstack/auth-frontend";
13
15
 
14
16
  interface SystemEditorProps {
15
17
  open: boolean;
16
18
  onClose: () => void;
17
19
  onSave: (data: { name: string; description?: string }) => Promise<void>;
18
- initialData?: { name: string; description?: string };
20
+ initialData?: { id: string; name: string; description?: string };
19
21
  }
20
22
 
21
23
  export const SystemEditor: React.FC<SystemEditorProps> = ({
@@ -67,6 +69,11 @@ export const SystemEditor: React.FC<SystemEditorProps> = ({
67
69
  <DialogTitle>
68
70
  {initialData ? "Edit System" : "Create System"}
69
71
  </DialogTitle>
72
+ <DialogDescription className="sr-only">
73
+ {initialData
74
+ ? "Modify the settings for this system"
75
+ : "Create a new system to monitor"}
76
+ </DialogDescription>
70
77
  </DialogHeader>
71
78
 
72
79
  <div className="space-y-4 py-4">
@@ -92,6 +99,16 @@ export const SystemEditor: React.FC<SystemEditorProps> = ({
92
99
  rows={3}
93
100
  />
94
101
  </div>
102
+
103
+ {/* Team Access Editor - only shown for existing systems */}
104
+ {initialData?.id && (
105
+ <TeamAccessEditor
106
+ resourceType="catalog.system"
107
+ resourceId={initialData.id}
108
+ compact
109
+ expanded
110
+ />
111
+ )}
95
112
  </div>
96
113
 
97
114
  <DialogFooter>