@eventcatalog/core 3.48.4 → 4.0.0-beta.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/dist/analytics/analytics.cjs +1 -1
- package/dist/analytics/analytics.js +2 -2
- package/dist/analytics/log-build.cjs +1 -1
- package/dist/analytics/log-build.js +3 -3
- package/dist/catalog-to-astro-content-directory.cjs +1 -1
- package/dist/chunk-BWN544LB.js +73 -0
- package/dist/{chunk-XNVSSL4W.js → chunk-JTZT2SDF.js} +1 -1
- package/dist/{chunk-JY4H7A4S.js → chunk-M2ZRT7KQ.js} +1 -1
- package/dist/{chunk-SY4XL6OQ.js → chunk-MBVTS76Q.js} +1 -1
- package/dist/{chunk-KDLSBQRP.js → chunk-TZTPGQGL.js} +1 -1
- package/dist/{chunk-FNDSHOV2.js → chunk-U5OTNDVQ.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/core-node-modules.cjs +108 -0
- package/dist/core-node-modules.d.cts +7 -0
- package/dist/core-node-modules.d.ts +7 -0
- package/dist/core-node-modules.js +8 -0
- package/dist/docs/development/components/components/15-prompt.md +5 -1
- package/dist/eventcatalog.cjs +124 -48
- package/dist/eventcatalog.js +26 -15
- package/dist/generate.cjs +1 -1
- package/dist/generate.js +3 -3
- package/dist/utils/cli-logger.cjs +1 -1
- package/dist/utils/cli-logger.js +2 -2
- package/eventcatalog/astro.config.mjs +27 -23
- package/eventcatalog/public/icons/languages/java.svg +1 -0
- package/eventcatalog/public/icons/payments/stripe.svg +1 -0
- package/eventcatalog/src/components/Badge.astro +10 -10
- package/eventcatalog/src/components/CopyAsMarkdown.tsx +154 -0
- package/eventcatalog/src/components/Grids/DomainGrid.tsx +207 -44
- package/eventcatalog/src/components/Grids/MessageGrid.tsx +0 -20
- package/eventcatalog/src/components/Grids/SystemGrid.tsx +185 -0
- package/eventcatalog/src/components/MDX/ADRTable/ADRTable.astro +99 -0
- package/eventcatalog/src/components/MDX/ADRTable/ADRTable.client.tsx +400 -0
- package/eventcatalog/src/components/MDX/ContextDiagram/ContextDiagramPortal.tsx +21 -0
- package/eventcatalog/src/components/MDX/NodeGraph/AstroNodeGraph.tsx +1 -0
- package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.astro +42 -6
- package/eventcatalog/src/components/MDX/Prompt/Prompt.astro +62 -9
- package/eventcatalog/src/components/MDX/ResourceRef/ResourceRef.astro +8 -1
- package/eventcatalog/src/components/MDX/SystemContextMap/SystemContextMapPortal.tsx +22 -0
- package/eventcatalog/src/components/MDX/components.tsx +7 -0
- package/eventcatalog/src/components/SchemaExplorer/SchemaExplorer.tsx +1 -1
- package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +67 -37
- package/eventcatalog/src/components/Tables/Discover/DiscoverTable.tsx +47 -0
- package/eventcatalog/src/components/Tables/Discover/FilterComponents.tsx +10 -2
- package/eventcatalog/src/components/Tables/Discover/columns.tsx +115 -27
- package/eventcatalog/src/components/Tables/Discover/index.ts +7 -1
- package/eventcatalog/src/components/Tables/SystemResources/SystemResourcesTable.tsx +267 -0
- package/eventcatalog/src/content.config.ts +96 -8
- package/eventcatalog/src/enterprise/collections/resource-docs-utils.ts +4 -1
- package/eventcatalog/src/enterprise/custom-documentation/pages/docs/custom/index.astro +12 -13
- package/eventcatalog/src/enterprise/custom-documentation/utils/custom-docs.ts +6 -0
- package/eventcatalog/src/enterprise/feature.ts +3 -1
- package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +96 -140
- package/eventcatalog/src/layouts/VisualiserLayout.astro +66 -0
- package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/_index.data.ts +4 -2
- package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/index.astro +2 -0
- package/eventcatalog/src/pages/diagrams/[id]/[version]/index.astro +15 -15
- package/eventcatalog/src/pages/discover/[type]/_index.data.ts +1 -0
- package/eventcatalog/src/pages/discover/[type]/index.astro +47 -2
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/[docVersion]/index.astro +15 -14
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/index.astro +13 -14
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/_index.data.ts +1 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/_index.data.ts +11 -1
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +81 -26
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/_index.data.ts +89 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/index.astro +85 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version].md.ts +2 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version].mdx.ts +2 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/_index.data.ts +1 -0
- package/eventcatalog/src/pages/docs/teams/[id]/index.astro +24 -1
- package/eventcatalog/src/pages/docs/users/[id]/index.astro +24 -1
- package/eventcatalog/src/pages/index.astro +30 -7
- package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/_index.data.ts +1 -0
- package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/index.astro +1 -1
- package/eventcatalog/src/pages/visualiser/[type]/[id]/[version].mermaid.ts +3 -0
- package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/systems-context/_index.data.ts +94 -0
- package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/systems-context/index.astro +51 -0
- package/eventcatalog/src/pages/visualiser/system-context-map/index.astro +41 -0
- package/eventcatalog/src/pages/visualiser/systems/[id]/[version]/context/_index.data.ts +91 -0
- package/eventcatalog/src/pages/visualiser/systems/[id]/[version]/context/index.astro +51 -0
- package/eventcatalog/src/stores/sidebar-store/builders/adr.ts +3 -1
- package/eventcatalog/src/stores/sidebar-store/builders/agent.ts +3 -1
- package/eventcatalog/src/stores/sidebar-store/builders/container.ts +1 -1
- package/eventcatalog/src/stores/sidebar-store/builders/data-product.ts +1 -1
- package/eventcatalog/src/stores/sidebar-store/builders/domain.ts +127 -49
- package/eventcatalog/src/stores/sidebar-store/builders/message.ts +1 -1
- package/eventcatalog/src/stores/sidebar-store/builders/service.ts +4 -2
- package/eventcatalog/src/stores/sidebar-store/builders/shared.ts +2 -1
- package/eventcatalog/src/stores/sidebar-store/builders/system.ts +166 -0
- package/eventcatalog/src/stores/sidebar-store/state.ts +99 -6
- package/eventcatalog/src/types/index.ts +2 -0
- package/eventcatalog/src/utils/collection-colors.ts +6 -1
- package/eventcatalog/src/utils/collections/domains.ts +35 -0
- package/eventcatalog/src/utils/collections/icons.ts +4 -5
- package/eventcatalog/src/utils/collections/systems.ts +110 -0
- package/eventcatalog/src/utils/collections/teams.ts +15 -10
- package/eventcatalog/src/utils/collections/users.ts +7 -4
- package/eventcatalog/src/utils/collections/util.ts +2 -0
- package/eventcatalog/src/utils/icon-map.ts +20 -0
- package/eventcatalog/src/utils/icon.ts +11 -0
- package/eventcatalog/src/utils/node-graphs/container-node-graph.ts +12 -2
- package/eventcatalog/src/utils/node-graphs/domains-node-graph.ts +0 -170
- package/eventcatalog/src/utils/node-graphs/system-context-node-graph.ts +400 -0
- package/eventcatalog/src/utils/node-graphs/systems-node-graph.ts +208 -0
- package/eventcatalog/src/utils/node-graphs/utils/utils.ts +12 -0
- package/eventcatalog/src/utils/page-loaders/page-data-loader.ts +2 -0
- package/package.json +17 -14
- package/eventcatalog/src/pages/visualiser/context-map/index.astro +0 -30
|
@@ -30,6 +30,7 @@ export type CollectionType =
|
|
|
30
30
|
| 'services'
|
|
31
31
|
| 'external-systems'
|
|
32
32
|
| 'domains'
|
|
33
|
+
| 'systems'
|
|
33
34
|
| 'flows'
|
|
34
35
|
| 'containers'
|
|
35
36
|
| 'data-products';
|
|
@@ -49,6 +50,9 @@ export interface DiscoverTableData {
|
|
|
49
50
|
hasDecisionMakers?: boolean;
|
|
50
51
|
hasInputs?: boolean;
|
|
51
52
|
hasOutputs?: boolean;
|
|
53
|
+
hasServices?: boolean;
|
|
54
|
+
hasFlows?: boolean;
|
|
55
|
+
isSubdomain?: boolean;
|
|
52
56
|
data: {
|
|
53
57
|
id: string;
|
|
54
58
|
name: string;
|
|
@@ -79,6 +83,7 @@ export interface DiscoverTableData {
|
|
|
79
83
|
model?: any;
|
|
80
84
|
agents?: Array<any>;
|
|
81
85
|
services?: Array<any>;
|
|
86
|
+
flows?: Array<any>;
|
|
82
87
|
servicesThatWriteToContainer?: Array<any>;
|
|
83
88
|
servicesThatReadFromContainer?: Array<any>;
|
|
84
89
|
inputs?: Array<any>;
|
|
@@ -91,11 +96,19 @@ export interface PropertyOption {
|
|
|
91
96
|
label: string;
|
|
92
97
|
}
|
|
93
98
|
|
|
99
|
+
export interface CollectionKindOption {
|
|
100
|
+
id: CollectionType;
|
|
101
|
+
label: string;
|
|
102
|
+
href: string;
|
|
103
|
+
count: number;
|
|
104
|
+
}
|
|
105
|
+
|
|
94
106
|
export interface DiscoverTableProps<T extends DiscoverTableData> {
|
|
95
107
|
data: T[];
|
|
96
108
|
collectionType: CollectionType;
|
|
97
109
|
collectionLabel: string;
|
|
98
110
|
collectionDescription?: string;
|
|
111
|
+
collectionKinds?: CollectionKindOption[];
|
|
99
112
|
domains?: Array<{ id: string; name: string; version: string }>;
|
|
100
113
|
owners?: Array<{ id: string; name: string; type?: 'user' | 'team' }>;
|
|
101
114
|
producers?: Array<{ id: string; name: string }>;
|
|
@@ -153,6 +166,7 @@ export function DiscoverTable<T extends DiscoverTableData>({
|
|
|
153
166
|
collectionType,
|
|
154
167
|
collectionLabel,
|
|
155
168
|
collectionDescription,
|
|
169
|
+
collectionKinds = [],
|
|
156
170
|
domains = [],
|
|
157
171
|
owners = [],
|
|
158
172
|
producers = [],
|
|
@@ -197,6 +211,7 @@ export function DiscoverTable<T extends DiscoverTableData>({
|
|
|
197
211
|
const [selectedBadges, setSelectedBadges] = useState<string[]>([]);
|
|
198
212
|
const [selectedProperties, setSelectedProperties] = useState<string[]>([]);
|
|
199
213
|
const [selectedStatuses, setSelectedStatuses] = useState<string[]>([]);
|
|
214
|
+
const selectedKind = collectionKinds.find((kind) => kind.id === collectionType);
|
|
200
215
|
|
|
201
216
|
// Collect unique badges from all items
|
|
202
217
|
const allBadges = useMemo(() => {
|
|
@@ -341,6 +356,10 @@ export function DiscoverTable<T extends DiscoverTableData>({
|
|
|
341
356
|
const services = row.data.services || [];
|
|
342
357
|
if (services.length === 0) return false;
|
|
343
358
|
}
|
|
359
|
+
if (prop === 'hasFlows') {
|
|
360
|
+
const flows = row.data.flows || [];
|
|
361
|
+
if (flows.length === 0) return false;
|
|
362
|
+
}
|
|
344
363
|
|
|
345
364
|
// Container-specific checks
|
|
346
365
|
if (prop === 'hasWriters') {
|
|
@@ -554,6 +573,12 @@ export function DiscoverTable<T extends DiscoverTableData>({
|
|
|
554
573
|
setSelectedProperties((prev) => (prev.includes(propertyId) ? prev.filter((p) => p !== propertyId) : [...prev, propertyId]));
|
|
555
574
|
};
|
|
556
575
|
|
|
576
|
+
const navigateToKind = (kindId: CollectionType) => {
|
|
577
|
+
const selectedKind = collectionKinds.find((kind) => kind.id === kindId);
|
|
578
|
+
if (!selectedKind || selectedKind.id === collectionType) return;
|
|
579
|
+
window.location.href = selectedKind.href;
|
|
580
|
+
};
|
|
581
|
+
|
|
557
582
|
const clearAllFilters = () => {
|
|
558
583
|
setSelectedDomains([]);
|
|
559
584
|
setSelectedOwners([]);
|
|
@@ -629,6 +654,28 @@ export function DiscoverTable<T extends DiscoverTableData>({
|
|
|
629
654
|
|
|
630
655
|
{/* Filter sections */}
|
|
631
656
|
<div className="flex-1 overflow-y-auto px-4 pt-4 pb-4 space-y-6">
|
|
657
|
+
{collectionKinds.length > 1 && (
|
|
658
|
+
<div>
|
|
659
|
+
<label className="block text-xs font-medium text-[rgb(var(--ec-page-text)/0.8)] mb-1.5">Kind</label>
|
|
660
|
+
<FilterDropdown
|
|
661
|
+
label="Select kind..."
|
|
662
|
+
selectedItems={selectedKind ? [selectedKind.label] : []}
|
|
663
|
+
onClear={() => {}}
|
|
664
|
+
clearable={false}
|
|
665
|
+
>
|
|
666
|
+
{collectionKinds.map((kind) => (
|
|
667
|
+
<CheckboxItem
|
|
668
|
+
key={kind.id}
|
|
669
|
+
label={kind.label}
|
|
670
|
+
checked={kind.id === collectionType}
|
|
671
|
+
onChange={() => navigateToKind(kind.id)}
|
|
672
|
+
count={kind.count}
|
|
673
|
+
/>
|
|
674
|
+
))}
|
|
675
|
+
</FilterDropdown>
|
|
676
|
+
</div>
|
|
677
|
+
)}
|
|
678
|
+
|
|
632
679
|
{/* Message Filters Section */}
|
|
633
680
|
{(showProducersFilter || showConsumersFilter) && (filteredProducers.length > 0 || filteredConsumers.length > 0) && (
|
|
634
681
|
<div className="space-y-3">
|
|
@@ -8,10 +8,18 @@ export interface FilterDropdownProps {
|
|
|
8
8
|
selectedItems: string[];
|
|
9
9
|
onClear: () => void;
|
|
10
10
|
onRemoveItem?: (item: string) => void;
|
|
11
|
+
clearable?: boolean;
|
|
11
12
|
children: React.ReactNode;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
export const FilterDropdown = ({
|
|
15
|
+
export const FilterDropdown = ({
|
|
16
|
+
label,
|
|
17
|
+
selectedItems,
|
|
18
|
+
onClear,
|
|
19
|
+
onRemoveItem,
|
|
20
|
+
clearable = true,
|
|
21
|
+
children,
|
|
22
|
+
}: FilterDropdownProps) => {
|
|
15
23
|
const [isOpen, setIsOpen] = useState(false);
|
|
16
24
|
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
17
25
|
const hasSelection = selectedItems.length > 0;
|
|
@@ -88,7 +96,7 @@ export const FilterDropdown = ({ label, selectedItems, onClear, onRemoveItem, ch
|
|
|
88
96
|
)}
|
|
89
97
|
</div>
|
|
90
98
|
<div className="flex items-center gap-1 flex-shrink-0 ml-2">
|
|
91
|
-
{hasSelection && (
|
|
99
|
+
{hasSelection && clearable && (
|
|
92
100
|
<span
|
|
93
101
|
role="button"
|
|
94
102
|
tabIndex={0}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createColumnHelper } from '@tanstack/react-table';
|
|
2
|
-
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { createPortal } from 'react-dom';
|
|
3
4
|
import { DocumentTextIcon, MapIcon } from '@heroicons/react/24/solid';
|
|
4
5
|
import {
|
|
5
6
|
ArrowDownIcon,
|
|
@@ -139,9 +140,14 @@ const ResourceNameCell = ({ item }: { item: DiscoverTableData }) => {
|
|
|
139
140
|
);
|
|
140
141
|
};
|
|
141
142
|
|
|
143
|
+
const MENU_WIDTH = 280;
|
|
144
|
+
const MENU_GAP = 6;
|
|
145
|
+
|
|
142
146
|
const RowActionsMenu = ({ item, collectionType }: { item: DiscoverTableData; collectionType: CollectionType }) => {
|
|
143
147
|
const [isOpen, setIsOpen] = useState(false);
|
|
148
|
+
const buttonRef = useRef<HTMLButtonElement>(null);
|
|
144
149
|
const menuRef = useRef<HTMLDivElement>(null);
|
|
150
|
+
const [menuPosition, setMenuPosition] = useState<{ top: number; left: number } | null>(null);
|
|
145
151
|
const favorites = useStore(favoritesStore);
|
|
146
152
|
const href = buildUrl(`/docs/${item.collection}/${item.data.id}/${item.data.version}`);
|
|
147
153
|
const visualiserHref = buildUrl(`/visualiser/${item.collection}/${item.data.id}/${item.data.version}`);
|
|
@@ -153,9 +159,35 @@ const RowActionsMenu = ({ item, collectionType }: { item: DiscoverTableData; col
|
|
|
153
159
|
: collectionType.charAt(0).toUpperCase() + collectionType.slice(1, -1);
|
|
154
160
|
const isFavorite = favorites.some((fav) => fav.nodeKey === nodeKey);
|
|
155
161
|
|
|
162
|
+
// The table lives inside scrollable, overflow-hidden containers, so the menu is
|
|
163
|
+
// rendered in a portal with fixed positioning to avoid being clipped.
|
|
164
|
+
const updatePosition = () => {
|
|
165
|
+
const button = buttonRef.current;
|
|
166
|
+
if (!button) return;
|
|
167
|
+
const rect = button.getBoundingClientRect();
|
|
168
|
+
const menuHeight = menuRef.current?.offsetHeight ?? 0;
|
|
169
|
+
const spaceBelow = window.innerHeight - rect.bottom;
|
|
170
|
+
|
|
171
|
+
// Flip above the trigger when there isn't enough room below it.
|
|
172
|
+
const openUpwards = menuHeight > 0 && spaceBelow < menuHeight + MENU_GAP && rect.top > spaceBelow;
|
|
173
|
+
const top = openUpwards ? rect.top - menuHeight - MENU_GAP : rect.bottom + MENU_GAP;
|
|
174
|
+
const left = Math.max(MENU_GAP, rect.right - MENU_WIDTH);
|
|
175
|
+
|
|
176
|
+
setMenuPosition({ top, left });
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
useLayoutEffect(() => {
|
|
180
|
+
if (!isOpen) return;
|
|
181
|
+
updatePosition();
|
|
182
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
183
|
+
}, [isOpen]);
|
|
184
|
+
|
|
156
185
|
useEffect(() => {
|
|
186
|
+
if (!isOpen) return;
|
|
187
|
+
|
|
157
188
|
const handlePointerDown = (event: MouseEvent) => {
|
|
158
|
-
|
|
189
|
+
const target = event.target as Node;
|
|
190
|
+
if (!menuRef.current?.contains(target) && !buttonRef.current?.contains(target)) {
|
|
159
191
|
setIsOpen(false);
|
|
160
192
|
}
|
|
161
193
|
};
|
|
@@ -166,14 +198,20 @@ const RowActionsMenu = ({ item, collectionType }: { item: DiscoverTableData; col
|
|
|
166
198
|
}
|
|
167
199
|
};
|
|
168
200
|
|
|
201
|
+
const handleReposition = () => updatePosition();
|
|
202
|
+
|
|
169
203
|
document.addEventListener('mousedown', handlePointerDown);
|
|
170
204
|
document.addEventListener('keydown', handleEscape);
|
|
205
|
+
window.addEventListener('resize', handleReposition);
|
|
206
|
+
window.addEventListener('scroll', handleReposition, true);
|
|
171
207
|
|
|
172
208
|
return () => {
|
|
173
209
|
document.removeEventListener('mousedown', handlePointerDown);
|
|
174
210
|
document.removeEventListener('keydown', handleEscape);
|
|
211
|
+
window.removeEventListener('resize', handleReposition);
|
|
212
|
+
window.removeEventListener('scroll', handleReposition, true);
|
|
175
213
|
};
|
|
176
|
-
}, []);
|
|
214
|
+
}, [isOpen]);
|
|
177
215
|
|
|
178
216
|
const handleToggleFavorite = () => {
|
|
179
217
|
const favoriteItem: FavoriteItem = {
|
|
@@ -188,8 +226,9 @@ const RowActionsMenu = ({ item, collectionType }: { item: DiscoverTableData; col
|
|
|
188
226
|
};
|
|
189
227
|
|
|
190
228
|
return (
|
|
191
|
-
<div className="relative flex justify-end"
|
|
229
|
+
<div className="relative flex justify-end">
|
|
192
230
|
<button
|
|
231
|
+
ref={buttonRef}
|
|
193
232
|
type="button"
|
|
194
233
|
aria-haspopup="menu"
|
|
195
234
|
aria-expanded={isOpen}
|
|
@@ -199,34 +238,48 @@ const RowActionsMenu = ({ item, collectionType }: { item: DiscoverTableData; col
|
|
|
199
238
|
<EllipsisVerticalIcon className="h-4 w-4" />
|
|
200
239
|
</button>
|
|
201
240
|
|
|
202
|
-
{isOpen &&
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
241
|
+
{isOpen &&
|
|
242
|
+
typeof document !== 'undefined' &&
|
|
243
|
+
createPortal(
|
|
244
|
+
<div
|
|
245
|
+
ref={menuRef}
|
|
246
|
+
role="menu"
|
|
247
|
+
style={{
|
|
248
|
+
position: 'fixed',
|
|
249
|
+
top: menuPosition?.top ?? -9999,
|
|
250
|
+
left: menuPosition?.left ?? -9999,
|
|
251
|
+
width: MENU_WIDTH,
|
|
252
|
+
visibility: menuPosition ? 'visible' : 'hidden',
|
|
253
|
+
}}
|
|
254
|
+
className="z-50 overflow-hidden rounded-xl border border-[rgb(var(--ec-page-border))] bg-[rgb(var(--ec-dropdown-bg))] shadow-xl"
|
|
207
255
|
>
|
|
208
|
-
<DocumentTextIcon className="h-3.5 w-3.5 text-[rgb(var(--ec-page-text-muted))]" />
|
|
209
|
-
View documentation
|
|
210
|
-
</a>
|
|
211
|
-
{hasVisualiser && (
|
|
212
256
|
<a
|
|
213
|
-
href={
|
|
257
|
+
href={href}
|
|
214
258
|
className="flex items-center gap-2.5 px-3 py-2.5 text-xs font-medium text-[rgb(var(--ec-page-text))] transition-colors hover:bg-[rgb(var(--ec-content-hover))] hover:text-[rgb(var(--ec-accent))]"
|
|
215
259
|
>
|
|
216
|
-
<
|
|
217
|
-
View
|
|
260
|
+
<DocumentTextIcon className="h-3.5 w-3.5 text-[rgb(var(--ec-page-text-muted))]" />
|
|
261
|
+
View documentation
|
|
218
262
|
</a>
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
263
|
+
{hasVisualiser && (
|
|
264
|
+
<a
|
|
265
|
+
href={visualiserHref}
|
|
266
|
+
className="flex items-center gap-2.5 px-3 py-2.5 text-xs font-medium text-[rgb(var(--ec-page-text))] transition-colors hover:bg-[rgb(var(--ec-content-hover))] hover:text-[rgb(var(--ec-accent))]"
|
|
267
|
+
>
|
|
268
|
+
<MapIcon className="h-3.5 w-3.5 text-[rgb(var(--ec-page-text-muted))]" />
|
|
269
|
+
View in visualiser
|
|
270
|
+
</a>
|
|
271
|
+
)}
|
|
272
|
+
<button
|
|
273
|
+
type="button"
|
|
274
|
+
onClick={handleToggleFavorite}
|
|
275
|
+
className="flex w-full items-center gap-2.5 px-3 py-2.5 text-left text-xs font-medium text-[rgb(var(--ec-page-text))] transition-colors hover:bg-[rgb(var(--ec-content-hover))] hover:text-[rgb(var(--ec-accent))]"
|
|
276
|
+
>
|
|
277
|
+
<StarIcon className="h-3.5 w-3.5 text-[rgb(var(--ec-page-text-muted))]" />
|
|
278
|
+
{isFavorite ? 'Remove from favorites' : 'Add to favorites'}
|
|
279
|
+
</button>
|
|
280
|
+
</div>,
|
|
281
|
+
document.body
|
|
282
|
+
)}
|
|
230
283
|
</div>
|
|
231
284
|
);
|
|
232
285
|
};
|
|
@@ -617,6 +670,39 @@ export const getDomainColumns = (tableConfiguration: TableConfiguration) => [
|
|
|
617
670
|
createActionsColumn('domains', tableConfiguration),
|
|
618
671
|
];
|
|
619
672
|
|
|
673
|
+
// ============================================================================
|
|
674
|
+
// SYSTEM COLUMNS
|
|
675
|
+
// ============================================================================
|
|
676
|
+
export const getSystemColumns = (tableConfiguration: TableConfiguration) => [
|
|
677
|
+
columnHelper.accessor('data.name', {
|
|
678
|
+
id: 'name',
|
|
679
|
+
header: () => <span>{tableConfiguration?.columns?.name?.label || 'System'}</span>,
|
|
680
|
+
cell: (info) => <ResourceNameCell item={info.row.original} />,
|
|
681
|
+
meta: {
|
|
682
|
+
filterVariant: 'name',
|
|
683
|
+
},
|
|
684
|
+
}),
|
|
685
|
+
createSummaryColumn(tableConfiguration),
|
|
686
|
+
columnHelper.accessor('data.services', {
|
|
687
|
+
id: 'services',
|
|
688
|
+
header: () => <span>Services</span>,
|
|
689
|
+
cell: (info) => <CollectionListCell items={info.getValue()} />,
|
|
690
|
+
meta: {
|
|
691
|
+
showFilter: false,
|
|
692
|
+
},
|
|
693
|
+
}),
|
|
694
|
+
columnHelper.accessor('data.flows', {
|
|
695
|
+
id: 'flows',
|
|
696
|
+
header: () => <span>Flows</span>,
|
|
697
|
+
cell: (info) => <CollectionListCell items={info.getValue()} />,
|
|
698
|
+
meta: {
|
|
699
|
+
showFilter: false,
|
|
700
|
+
},
|
|
701
|
+
}),
|
|
702
|
+
createBadgesColumn(tableConfiguration),
|
|
703
|
+
createActionsColumn('systems', tableConfiguration),
|
|
704
|
+
];
|
|
705
|
+
|
|
620
706
|
// ============================================================================
|
|
621
707
|
// FLOW COLUMNS
|
|
622
708
|
// ============================================================================
|
|
@@ -740,6 +826,8 @@ export const getDiscoverColumns = (collectionType: CollectionType, tableConfigur
|
|
|
740
826
|
return getServiceColumns(tableConfiguration);
|
|
741
827
|
case 'domains':
|
|
742
828
|
return getDomainColumns(tableConfiguration);
|
|
829
|
+
case 'systems':
|
|
830
|
+
return getSystemColumns(tableConfiguration);
|
|
743
831
|
case 'flows':
|
|
744
832
|
return getFlowColumns(tableConfiguration);
|
|
745
833
|
case 'containers':
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
export { DiscoverTable } from './DiscoverTable';
|
|
2
|
-
export type {
|
|
2
|
+
export type {
|
|
3
|
+
DiscoverTableData,
|
|
4
|
+
DiscoverTableProps,
|
|
5
|
+
CollectionType,
|
|
6
|
+
CollectionKindOption,
|
|
7
|
+
PropertyOption,
|
|
8
|
+
} from './DiscoverTable';
|
|
3
9
|
export { FilterDropdown, CheckboxItem } from './FilterComponents';
|
|
4
10
|
export type { FilterDropdownProps, CheckboxItemProps } from './FilterComponents';
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { useMemo, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
createColumnHelper,
|
|
4
|
+
flexRender,
|
|
5
|
+
getCoreRowModel,
|
|
6
|
+
getSortedRowModel,
|
|
7
|
+
useReactTable,
|
|
8
|
+
type SortingState,
|
|
9
|
+
} from '@tanstack/react-table';
|
|
10
|
+
import { ArrowDown, ArrowUp, ArrowUpDown, Search, SearchX } from 'lucide-react';
|
|
11
|
+
import { getColorAndIconForCollection } from '@utils/collections/icons';
|
|
12
|
+
import { getCollectionTextColorClass } from '@utils/collection-colors';
|
|
13
|
+
import { isIconPath, resolveIconUrl } from '@utils/icon';
|
|
14
|
+
|
|
15
|
+
export type SystemResourceCollection = 'services' | 'flows' | 'entities' | 'containers' | 'events' | 'commands' | 'queries';
|
|
16
|
+
|
|
17
|
+
export interface SystemResourceItem {
|
|
18
|
+
collection: SystemResourceCollection;
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
version: string;
|
|
22
|
+
summary?: string;
|
|
23
|
+
icon?: string;
|
|
24
|
+
href: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface SystemResourcesTableProps {
|
|
28
|
+
resources: SystemResourceItem[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const TYPE_LABELS: Record<SystemResourceCollection, string> = {
|
|
32
|
+
services: 'Services',
|
|
33
|
+
flows: 'Flows',
|
|
34
|
+
entities: 'Entities',
|
|
35
|
+
containers: 'Data Stores',
|
|
36
|
+
events: 'Events',
|
|
37
|
+
commands: 'Commands',
|
|
38
|
+
queries: 'Queries',
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Order the type filter pills follow on the page.
|
|
42
|
+
const TYPE_ORDER: SystemResourceCollection[] = ['services', 'flows', 'entities', 'containers', 'events', 'commands', 'queries'];
|
|
43
|
+
|
|
44
|
+
// Small icon for a collection type (used in filter pills and the type column).
|
|
45
|
+
const CollectionTypeIcon = ({ collection, className }: { collection: SystemResourceCollection; className?: string }) => {
|
|
46
|
+
const { color, Icon } = getColorAndIconForCollection(collection);
|
|
47
|
+
const colorClass = getCollectionTextColorClass(color, 'text-[rgb(var(--ec-icon-color))]');
|
|
48
|
+
return <Icon className={`${className ?? 'h-3.5 w-3.5'} ${colorClass}`} aria-hidden="true" />;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const ResourceIcon = ({ item }: { item: SystemResourceItem }) => {
|
|
52
|
+
if (item.icon && isIconPath(item.icon)) {
|
|
53
|
+
return <img src={resolveIconUrl(item.icon)} alt="" className="h-5 w-5" loading="lazy" />;
|
|
54
|
+
}
|
|
55
|
+
return <CollectionTypeIcon collection={item.collection} className="h-5 w-5" />;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const columnHelper = createColumnHelper<SystemResourceItem>();
|
|
59
|
+
|
|
60
|
+
const SortIcon = ({ direction }: { direction: false | 'asc' | 'desc' }) => {
|
|
61
|
+
if (direction === 'asc') return <ArrowUp className="h-3.5 w-3.5" aria-hidden="true" />;
|
|
62
|
+
if (direction === 'desc') return <ArrowDown className="h-3.5 w-3.5" aria-hidden="true" />;
|
|
63
|
+
return <ArrowUpDown className="h-3.5 w-3.5 opacity-40" aria-hidden="true" />;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export function SystemResourcesTable({ resources }: SystemResourcesTableProps) {
|
|
67
|
+
const [search, setSearch] = useState('');
|
|
68
|
+
const [selectedTypes, setSelectedTypes] = useState<SystemResourceCollection[]>([]);
|
|
69
|
+
const [sorting, setSorting] = useState<SortingState>([{ id: 'name', desc: false }]);
|
|
70
|
+
|
|
71
|
+
// Only show type filters that actually have resources in this system.
|
|
72
|
+
const availableTypes = useMemo(
|
|
73
|
+
() => TYPE_ORDER.filter((type) => resources.some((resource) => resource.collection === type)),
|
|
74
|
+
[resources]
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const filtered = useMemo(() => {
|
|
78
|
+
const query = search.trim().toLowerCase();
|
|
79
|
+
return resources.filter((resource) => {
|
|
80
|
+
if (selectedTypes.length > 0 && !selectedTypes.includes(resource.collection)) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
if (query) {
|
|
84
|
+
const haystack = `${resource.name} ${resource.id} ${resource.summary ?? ''}`.toLowerCase();
|
|
85
|
+
if (!haystack.includes(query)) return false;
|
|
86
|
+
}
|
|
87
|
+
return true;
|
|
88
|
+
});
|
|
89
|
+
}, [resources, search, selectedTypes]);
|
|
90
|
+
|
|
91
|
+
const columns = useMemo(
|
|
92
|
+
() => [
|
|
93
|
+
columnHelper.accessor('name', {
|
|
94
|
+
header: 'Name',
|
|
95
|
+
cell: (info) => {
|
|
96
|
+
const resource = info.row.original;
|
|
97
|
+
return (
|
|
98
|
+
<a href={resource.href} className="group flex items-center gap-3">
|
|
99
|
+
<span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-md border border-[rgb(var(--ec-page-border))] bg-[rgb(var(--ec-card-bg))]">
|
|
100
|
+
<ResourceIcon item={resource} />
|
|
101
|
+
</span>
|
|
102
|
+
<span className="text-sm font-medium text-[rgb(var(--ec-page-text))] group-hover:text-[rgb(var(--ec-accent))]">
|
|
103
|
+
{resource.name}
|
|
104
|
+
</span>
|
|
105
|
+
</a>
|
|
106
|
+
);
|
|
107
|
+
},
|
|
108
|
+
}),
|
|
109
|
+
columnHelper.accessor((row) => TYPE_LABELS[row.collection], {
|
|
110
|
+
id: 'type',
|
|
111
|
+
header: 'Type',
|
|
112
|
+
cell: (info) => {
|
|
113
|
+
const { collection } = info.row.original;
|
|
114
|
+
return (
|
|
115
|
+
<span className="inline-flex items-center gap-1.5 text-sm text-[rgb(var(--ec-page-text-muted))]">
|
|
116
|
+
<CollectionTypeIcon collection={collection} />
|
|
117
|
+
{TYPE_LABELS[collection]}
|
|
118
|
+
</span>
|
|
119
|
+
);
|
|
120
|
+
},
|
|
121
|
+
}),
|
|
122
|
+
columnHelper.accessor('version', {
|
|
123
|
+
header: 'Version',
|
|
124
|
+
cell: (info) => (
|
|
125
|
+
<span className="inline-flex items-center rounded-md border border-[rgb(var(--ec-page-border))] px-2 py-0.5 text-xs text-[rgb(var(--ec-page-text-muted))]">
|
|
126
|
+
v{info.getValue()}
|
|
127
|
+
</span>
|
|
128
|
+
),
|
|
129
|
+
}),
|
|
130
|
+
columnHelper.accessor((row) => row.summary ?? '', {
|
|
131
|
+
id: 'summary',
|
|
132
|
+
header: 'Summary',
|
|
133
|
+
enableSorting: false,
|
|
134
|
+
cell: (info) => (
|
|
135
|
+
<span className="line-clamp-2 text-sm text-[rgb(var(--ec-page-text-muted))]">{info.getValue() || '-'}</span>
|
|
136
|
+
),
|
|
137
|
+
}),
|
|
138
|
+
],
|
|
139
|
+
[]
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
const table = useReactTable({
|
|
143
|
+
data: filtered,
|
|
144
|
+
columns,
|
|
145
|
+
state: { sorting },
|
|
146
|
+
onSortingChange: setSorting,
|
|
147
|
+
getCoreRowModel: getCoreRowModel(),
|
|
148
|
+
getSortedRowModel: getSortedRowModel(),
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const toggleType = (type: SystemResourceCollection) => {
|
|
152
|
+
setSelectedTypes((prev) => (prev.includes(type) ? prev.filter((t) => t !== type) : [...prev, type]));
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<div className="flex flex-col gap-4">
|
|
157
|
+
{/* Controls: search + type filters */}
|
|
158
|
+
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
159
|
+
<div className="relative w-full sm:max-w-xs">
|
|
160
|
+
<Search
|
|
161
|
+
className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-[rgb(var(--ec-icon-color))]"
|
|
162
|
+
aria-hidden="true"
|
|
163
|
+
/>
|
|
164
|
+
<input
|
|
165
|
+
type="text"
|
|
166
|
+
value={search}
|
|
167
|
+
onChange={(event) => setSearch(event.target.value)}
|
|
168
|
+
placeholder="Search resources..."
|
|
169
|
+
className="w-full rounded-md border border-[rgb(var(--ec-input-border))] bg-[rgb(var(--ec-input-bg))] py-2 pl-9 pr-3 text-sm text-[rgb(var(--ec-input-text))] placeholder:text-[rgb(var(--ec-page-text-muted))] focus:outline-none focus:ring-2 focus:ring-[rgb(var(--ec-accent)/0.4)]"
|
|
170
|
+
/>
|
|
171
|
+
</div>
|
|
172
|
+
|
|
173
|
+
{availableTypes.length > 1 && (
|
|
174
|
+
<div className="flex flex-wrap gap-2">
|
|
175
|
+
{availableTypes.map((type) => {
|
|
176
|
+
const active = selectedTypes.includes(type);
|
|
177
|
+
return (
|
|
178
|
+
<button
|
|
179
|
+
key={type}
|
|
180
|
+
type="button"
|
|
181
|
+
onClick={() => toggleType(type)}
|
|
182
|
+
className={[
|
|
183
|
+
'inline-flex items-center gap-1.5 rounded-full border px-3 py-1 text-xs font-medium transition-colors',
|
|
184
|
+
active
|
|
185
|
+
? 'border-[rgb(var(--ec-accent))] bg-[rgb(var(--ec-accent-subtle))] text-[rgb(var(--ec-accent))]'
|
|
186
|
+
: 'border-[rgb(var(--ec-page-border))] text-[rgb(var(--ec-page-text-muted))] hover:text-[rgb(var(--ec-page-text))]',
|
|
187
|
+
].join(' ')}
|
|
188
|
+
>
|
|
189
|
+
<CollectionTypeIcon collection={type} />
|
|
190
|
+
{TYPE_LABELS[type]}
|
|
191
|
+
</button>
|
|
192
|
+
);
|
|
193
|
+
})}
|
|
194
|
+
</div>
|
|
195
|
+
)}
|
|
196
|
+
</div>
|
|
197
|
+
|
|
198
|
+
{/* Table */}
|
|
199
|
+
<div className="overflow-hidden rounded-lg border border-[rgb(var(--ec-page-border))]">
|
|
200
|
+
<table className="min-w-full divide-y divide-[rgb(var(--ec-page-border))]">
|
|
201
|
+
<thead className="bg-[rgb(var(--ec-card-bg))]">
|
|
202
|
+
{table.getHeaderGroups().map((headerGroup) => (
|
|
203
|
+
<tr key={headerGroup.id}>
|
|
204
|
+
{headerGroup.headers.map((header) => {
|
|
205
|
+
const canSort = header.column.getCanSort();
|
|
206
|
+
const isSummary = header.column.id === 'summary';
|
|
207
|
+
return (
|
|
208
|
+
<th
|
|
209
|
+
key={header.id}
|
|
210
|
+
className={[
|
|
211
|
+
'px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider text-[rgb(var(--ec-page-text-muted))]',
|
|
212
|
+
isSummary ? 'hidden md:table-cell' : '',
|
|
213
|
+
].join(' ')}
|
|
214
|
+
>
|
|
215
|
+
{canSort ? (
|
|
216
|
+
<button
|
|
217
|
+
type="button"
|
|
218
|
+
onClick={header.column.getToggleSortingHandler()}
|
|
219
|
+
className="inline-flex items-center gap-1.5 uppercase tracking-wider hover:text-[rgb(var(--ec-page-text))]"
|
|
220
|
+
>
|
|
221
|
+
{flexRender(header.column.columnDef.header, header.getContext())}
|
|
222
|
+
<SortIcon direction={header.column.getIsSorted()} />
|
|
223
|
+
</button>
|
|
224
|
+
) : (
|
|
225
|
+
flexRender(header.column.columnDef.header, header.getContext())
|
|
226
|
+
)}
|
|
227
|
+
</th>
|
|
228
|
+
);
|
|
229
|
+
})}
|
|
230
|
+
</tr>
|
|
231
|
+
))}
|
|
232
|
+
</thead>
|
|
233
|
+
<tbody className="divide-y divide-[rgb(var(--ec-page-border))] bg-[rgb(var(--ec-page-bg))]">
|
|
234
|
+
{table.getRowModel().rows.length === 0 && (
|
|
235
|
+
<tr>
|
|
236
|
+
<td colSpan={columns.length} className="px-4 py-12">
|
|
237
|
+
<div className="flex flex-col items-center justify-center gap-2 text-center text-[rgb(var(--ec-page-text-muted))]">
|
|
238
|
+
<SearchX className="h-6 w-6" aria-hidden="true" />
|
|
239
|
+
<p className="text-sm">No resources found.</p>
|
|
240
|
+
</div>
|
|
241
|
+
</td>
|
|
242
|
+
</tr>
|
|
243
|
+
)}
|
|
244
|
+
{table.getRowModel().rows.map((row) => (
|
|
245
|
+
<tr key={row.id} className="transition-colors hover:bg-[rgb(var(--ec-card-bg))]">
|
|
246
|
+
{row.getVisibleCells().map((cell) => {
|
|
247
|
+
const isSummary = cell.column.id === 'summary';
|
|
248
|
+
return (
|
|
249
|
+
<td key={cell.id} className={['px-4 py-3', isSummary ? 'hidden max-w-md md:table-cell' : ''].join(' ')}>
|
|
250
|
+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
251
|
+
</td>
|
|
252
|
+
);
|
|
253
|
+
})}
|
|
254
|
+
</tr>
|
|
255
|
+
))}
|
|
256
|
+
</tbody>
|
|
257
|
+
</table>
|
|
258
|
+
</div>
|
|
259
|
+
|
|
260
|
+
<p className="text-xs text-[rgb(var(--ec-page-text-muted))]">
|
|
261
|
+
Showing {filtered.length} of {resources.length} resources
|
|
262
|
+
</p>
|
|
263
|
+
</div>
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export default SystemResourcesTable;
|