@checkstack/catalog-frontend 0.10.7 → 0.11.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +133 -0
  2. package/package.json +10 -9
  3. package/src/api.ts +6 -1
  4. package/src/components/CatalogConfigPage.tsx +337 -271
  5. package/src/components/CatalogPage.tsx +172 -11
  6. package/src/components/EnvironmentEditor.tsx +220 -0
  7. package/src/components/EnvironmentPreviewPicker.tsx +61 -0
  8. package/src/components/SystemDetailPage.tsx +47 -34
  9. package/src/components/SystemEditor.tsx +6 -0
  10. package/src/components/SystemEnvironmentsEditor.tsx +98 -0
  11. package/src/components/browse/CatalogBrowseHealth.tsx +36 -0
  12. package/src/components/browse/CatalogBrowseToolbar.tsx +173 -0
  13. package/src/components/browse/CatalogGroupSection.tsx +165 -0
  14. package/src/components/browse/CatalogSystemRow.tsx +63 -0
  15. package/src/components/browse/browseState.logic.test.ts +125 -0
  16. package/src/components/browse/browseState.logic.ts +158 -0
  17. package/src/components/browse/filterEntities.logic.test.ts +479 -0
  18. package/src/components/browse/filterEntities.logic.ts +360 -0
  19. package/src/components/browse/healthRollup.logic.test.ts +126 -0
  20. package/src/components/browse/healthRollup.logic.ts +120 -0
  21. package/src/components/browse/healthStatuses.logic.test.ts +39 -0
  22. package/src/components/browse/healthStatuses.logic.ts +29 -0
  23. package/src/components/environment-fields.logic.test.ts +111 -0
  24. package/src/components/environment-fields.logic.ts +98 -0
  25. package/src/components/environment-preview.logic.test.ts +76 -0
  26. package/src/components/environment-preview.logic.ts +61 -0
  27. package/src/components/manage/AssignMenu.tsx +78 -0
  28. package/src/components/manage/EnvironmentsTab.tsx +230 -0
  29. package/src/components/manage/GroupsTab.tsx +274 -0
  30. package/src/components/manage/SystemsTab.tsx +430 -0
  31. package/src/hooks/useCatalogBrowseState.ts +107 -0
  32. package/src/hooks/useDebouncedValue.ts +21 -0
  33. package/src/index.tsx +32 -20
  34. package/src/utils/formatDate.logic.test.ts +44 -0
  35. package/src/utils/formatDate.logic.ts +27 -0
  36. package/src/utils/normalizeMetadata.logic.test.ts +67 -0
  37. package/src/utils/normalizeMetadata.logic.ts +53 -0
  38. package/src/components/DraggableSystem.tsx +0 -200
  39. package/src/components/DroppableGroup.tsx +0 -174
  40. package/src/components/UserMenuItems.tsx +0 -31
@@ -1,174 +0,0 @@
1
- import { useDroppable } from "@dnd-kit/core";
2
- import { EditableText, Button, cn, usePerformance } from "@checkstack/ui";
3
- import { Trash2 } from "lucide-react";
4
- import {
5
- useProvenanceLock,
6
- useProvenanceLocks,
7
- GitOpsSourceBadge,
8
- } from "@checkstack/gitops-frontend";
9
- import type { Group, System } from "../api";
10
-
11
- interface DroppableGroupProps {
12
- group: Group;
13
- systems: System[];
14
- isOver: boolean;
15
- isDragging: boolean;
16
- draggingSystemAlreadyInGroup: boolean;
17
- newlyAddedSystemId?: string;
18
- onDeleteGroup: (id: string) => void;
19
- onUpdateGroupName: (id: string, name: string) => void;
20
- onRemoveSystem: (groupId: string, systemId: string) => void;
21
- }
22
-
23
- /**
24
- * A droppable group card for the Catalog Management page.
25
- *
26
- * When a system is being dragged over, the card highlights. If the system
27
- * is already assigned to this group, the drop zone shows a "Already in group"
28
- * indicator and the drop is blocked visually.
29
- */
30
- export const DroppableGroup = ({
31
- group,
32
- systems,
33
- isOver,
34
- isDragging,
35
- draggingSystemAlreadyInGroup,
36
- newlyAddedSystemId,
37
- onDeleteGroup,
38
- onUpdateGroupName,
39
- onRemoveSystem,
40
- }: DroppableGroupProps) => {
41
- const { isLowPower } = usePerformance();
42
- const { setNodeRef } = useDroppable({ id: group.id });
43
-
44
- const { isLocked, provenance } = useProvenanceLock({
45
- kind: "Group",
46
- entityId: group.id,
47
- });
48
-
49
- const { getLock } = useProvenanceLocks();
50
-
51
- const groupSystems = (group.systemIds ?? [])
52
- .map((sysId) => systems.find((s) => s.id === sysId))
53
- .filter((sys): sys is System => !!sys);
54
-
55
- const dropzoneActive = isDragging;
56
-
57
- return (
58
- <div
59
- ref={setNodeRef}
60
- className={cn(
61
- "p-3 rounded-lg border space-y-2",
62
- !isLowPower && "transition-all duration-150",
63
- isOver && !draggingSystemAlreadyInGroup
64
- ? "border-primary bg-primary/5 shadow-md shadow-primary/10"
65
- : isOver && draggingSystemAlreadyInGroup
66
- ? "border-muted-foreground/40 bg-muted/10"
67
- : dropzoneActive
68
- ? "border-border/60 bg-muted/20 border-dashed"
69
- : "border-border bg-muted/30",
70
- )}
71
- >
72
- {/* Group header */}
73
- <div className="flex items-center justify-between">
74
- <div className="flex items-center gap-2 flex-1">
75
- {isLocked && provenance && (
76
- <GitOpsSourceBadge provenance={provenance} />
77
- )}
78
- <div className="flex-1">
79
- <EditableText
80
- value={group.name}
81
- onSave={(newName) => onUpdateGroupName(group.id, newName)}
82
- className="font-medium text-foreground"
83
- disabled={isLocked}
84
- />
85
- <p className="text-xs text-muted-foreground font-mono">{group.id}</p>
86
- </div>
87
- </div>
88
- <Button
89
- variant="ghost"
90
- className="text-destructive hover:text-destructive/90 hover:bg-destructive/10 h-8 w-8 p-0"
91
- onClick={() => onDeleteGroup(group.id)}
92
- disabled={isLocked}
93
- title={isLocked ? "Managed by GitOps" : undefined}
94
- aria-label={`Delete group ${group.name}`}
95
- >
96
- <Trash2 className="w-4 h-4" />
97
- </Button>
98
- </div>
99
-
100
- {/* Drop zone label */}
101
- {isOver && (
102
- <div
103
- className={`text-xs text-center py-1 rounded transition-colors ${
104
- draggingSystemAlreadyInGroup
105
- ? "text-muted-foreground"
106
- : "text-primary font-medium"
107
- }`}
108
- >
109
- {draggingSystemAlreadyInGroup
110
- ? "Already in this group"
111
- : "Drop to add"}
112
- </div>
113
- )}
114
-
115
- {/* Systems in this group */}
116
- {groupSystems.length > 0 ? (
117
- <div className="pl-2 space-y-1">
118
- {groupSystems.map((sys) => {
119
- const isNew = newlyAddedSystemId === sys.id;
120
- const systemLock = getLock({ kind: "System", entityId: sys.id });
121
- const systemLocked = systemLock.isLocked;
122
- return (
123
- <div
124
- key={sys.id}
125
- className={cn(
126
- "flex items-center justify-between text-sm bg-background p-2 rounded border",
127
- !isLowPower && "transition-all duration-700",
128
- isNew
129
- ? "border-green-500/60 shadow-sm shadow-green-500/30"
130
- : "border-border shadow-none",
131
- )}
132
- >
133
- <span className="text-foreground truncate flex items-center gap-1.5">
134
- {systemLocked && systemLock.provenance ? (
135
- <GitOpsSourceBadge
136
- provenance={systemLock.provenance}
137
- iconClassName="w-3 h-3 text-primary"
138
- />
139
- ) : null}
140
- {sys.name}
141
- </span>
142
- <Button
143
- variant="ghost"
144
- className="text-destructive/60 hover:text-destructive h-6 w-6 p-0 flex-shrink-0"
145
- onClick={() => onRemoveSystem(group.id, sys.id)}
146
- disabled={systemLocked}
147
- title={
148
- systemLocked
149
- ? "Managed by GitOps"
150
- : `Remove ${sys.name} from ${group.name}`
151
- }
152
- aria-label={`Remove ${sys.name} from ${group.name}`}
153
- >
154
- <Trash2 className="w-3 h-3" />
155
- </Button>
156
- </div>
157
- );
158
- })}
159
- </div>
160
- ) : (
161
- /* Empty drop zone hint */
162
- <div
163
- className={`text-xs text-center py-3 rounded border border-dashed transition-colors ${
164
- dropzoneActive
165
- ? "border-primary/40 text-primary/60"
166
- : "border-border text-muted-foreground/60"
167
- }`}
168
- >
169
- {dropzoneActive ? "Drag a system here" : "No systems assigned"}
170
- </div>
171
- )}
172
- </div>
173
- );
174
- };
@@ -1,31 +0,0 @@
1
- import React from "react";
2
- import { Link } from "react-router-dom";
3
- import { Settings } from "lucide-react";
4
- import type { UserMenuItemsContext } from "@checkstack/frontend-api";
5
- import { DropdownMenuItem } from "@checkstack/ui";
6
- import { resolveRoute } from "@checkstack/common";
7
- import {
8
- catalogRoutes,
9
- catalogAccess,
10
- pluginMetadata,
11
- } from "@checkstack/catalog-common";
12
-
13
- export const CatalogUserMenuItems = ({
14
- accessRules: userPerms,
15
- }: UserMenuItemsContext) => {
16
- // Use the access rule's id directly
17
- const qualifiedId = `${pluginMetadata.pluginId}.${catalogAccess.system.manage.id}`;
18
- const canManage = userPerms.includes("*") || userPerms.includes(qualifiedId);
19
-
20
- if (!canManage) {
21
- return <React.Fragment />;
22
- }
23
-
24
- return (
25
- <Link to={resolveRoute(catalogRoutes.routes.config)}>
26
- <DropdownMenuItem icon={<Settings className="h-4 w-4" />}>
27
- Catalog Settings
28
- </DropdownMenuItem>
29
- </Link>
30
- );
31
- };