@eventcatalog/core 5.9.0 → 5.10.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/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/{chunk-WN6ZFSXF.js → chunk-5QOG7UYZ.js} +1 -1
- package/dist/{chunk-C4L6B5KR.js → chunk-EETSYUKN.js} +1 -1
- package/dist/{chunk-UIJUSHOT.js → chunk-EGOTM5ZG.js} +3 -2
- package/dist/{chunk-26C26LFE.js → chunk-EXBUNFIM.js} +1 -1
- package/dist/{chunk-SZU7I2PD.js → chunk-ZE2RPUNY.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/docs/development/00-why-eventcatalog.md +3 -0
- package/dist/docs/development/ask-your-architecture/01-intro.md +17 -4
- package/dist/docs/development/ask-your-architecture/06-sync-docs-with-your-code.md +8 -1
- package/dist/docs/development/ask-your-architecture/07-catch-breaking-changes.md +8 -1
- package/dist/docs/development/ask-your-architecture/08-check-code-against-adrs.md +169 -0
- package/dist/docs/development/guides/resources/adrs/01-introduction.md +1 -0
- package/dist/docs/development/guides/resources/entities/05-entity-maps.md +5 -0
- package/dist/docs/development/guides/resources/entities/06-global-entity-map.md +101 -0
- package/dist/docs/development/guides/resources/entities/{06-reference.md → 07-reference.md} +2 -1
- package/dist/eventcatalog.cjs +6 -5
- package/dist/eventcatalog.js +5 -5
- package/dist/generate.cjs +3 -2
- package/dist/generate.js +3 -3
- package/dist/utils/cli-logger.cjs +1 -1
- package/dist/utils/cli-logger.js +2 -2
- package/eventcatalog/auth.config.ts +7 -9
- package/eventcatalog/src/components/MDX/EntityMap/CatalogEntityMap.tsx +301 -0
- package/eventcatalog/src/features/ai/chat-api.ts +2 -4
- package/eventcatalog/src/features/auth/login.astro +2 -4
- package/eventcatalog/src/features/auth/middleware/middleware.ts +2 -3
- package/eventcatalog/src/features/mcp/apps/generated/canvas.html +61 -61
- package/eventcatalog/src/features/mcp/apps/generated/viewer.html +72 -72
- package/eventcatalog/src/features/mcp/mcp-server.ts +2 -1
- package/eventcatalog/src/features/studio/components/ConnectAgentDialog.tsx +1 -1
- package/eventcatalog/src/features/studio/hooks/use-canvas-webmcp.ts +2 -2
- package/eventcatalog/src/features/studio/pages/webmcp-relay.ts +1 -1
- package/eventcatalog/src/features/studio/webmcp-relay/README.md +1 -1
- package/eventcatalog/src/features/studio/webmcp-relay/embed.js +1 -2
- package/eventcatalog/src/features/studio/webmcp-relay/widget.html +2 -1
- package/eventcatalog/src/features/studio/webmcp-relay/widget.js +2 -1
- package/eventcatalog/src/pages/visualiser/entity-map/index.astro +47 -0
- package/eventcatalog/src/pages/visualiser/graph/index.astro +2 -2
- package/eventcatalog/src/stores/sidebar-store/state.ts +10 -1
- package/eventcatalog/src/utils/catalog-modules.ts +16 -0
- package/eventcatalog/src/utils/node-graphs/catalog-entity-map-view.ts +82 -0
- package/eventcatalog/src/utils/node-graphs/catalog-entity-map.ts +113 -0
- package/eventcatalog/src/utils/node-graphs/domain-entity-map.ts +17 -61
- package/eventcatalog/src/utils/node-graphs/entity-layout.ts +53 -0
- package/eventcatalog/src/utils/node-graphs/flow-lanes.ts +2 -2
- package/package.json +6 -6
package/dist/generate.cjs
CHANGED
|
@@ -34,6 +34,7 @@ __export(generate_exports, {
|
|
|
34
34
|
});
|
|
35
35
|
module.exports = __toCommonJS(generate_exports);
|
|
36
36
|
var import_node_path3 = __toESM(require("path"), 1);
|
|
37
|
+
var import_node_url2 = require("url");
|
|
37
38
|
|
|
38
39
|
// src/eventcatalog-config-file-utils.js
|
|
39
40
|
var import_promises2 = require("fs/promises");
|
|
@@ -105,7 +106,7 @@ var getEventCatalogConfigFile = async (projectDirectory) => {
|
|
|
105
106
|
var import_picocolors = __toESM(require("picocolors"), 1);
|
|
106
107
|
|
|
107
108
|
// package.json
|
|
108
|
-
var version = "5.
|
|
109
|
+
var version = "5.10.0";
|
|
109
110
|
|
|
110
111
|
// src/constants.ts
|
|
111
112
|
var VERSION = version;
|
|
@@ -204,7 +205,7 @@ var generate = async (PROJECT_DIRECTORY) => {
|
|
|
204
205
|
plugin = plugin.replace("<rootDir>", PROJECT_DIRECTORY);
|
|
205
206
|
}
|
|
206
207
|
try {
|
|
207
|
-
const importedGenerator = await import(plugin);
|
|
208
|
+
const importedGenerator = await (import_node_path3.default.isAbsolute(plugin) ? import((0, import_node_url2.pathToFileURL)(plugin).href) : import(plugin));
|
|
208
209
|
const generator2 = getDefaultExport(importedGenerator);
|
|
209
210
|
await generator2({ eventCatalogConfig: {} }, pluginConfig);
|
|
210
211
|
} catch (error) {
|
package/dist/generate.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
generate
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
5
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-EGOTM5ZG.js";
|
|
4
|
+
import "./chunk-ZE2RPUNY.js";
|
|
5
|
+
import "./chunk-EETSYUKN.js";
|
|
6
6
|
import "./chunk-4G7FSRPN.js";
|
|
7
7
|
export {
|
|
8
8
|
generate
|
package/dist/utils/cli-logger.js
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
import { defineConfig } from '@utils/auth-astro/config';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
2
|
import GitHub from '@auth/core/providers/github';
|
|
4
3
|
import Okta from '@auth/core/providers/okta';
|
|
5
4
|
import type { Account, Profile, User, Session } from '@auth/core/types';
|
|
6
5
|
import { isAuthEnabled, isSSR } from '@utils/feature';
|
|
6
|
+
import { importCatalogModule } from '@utils/catalog-modules';
|
|
7
7
|
import Google from '@auth/core/providers/google';
|
|
8
8
|
import Auth0 from '@auth/core/providers/auth0';
|
|
9
9
|
import Entra from '@auth/core/providers/microsoft-entra-id';
|
|
10
10
|
import jwt from 'jsonwebtoken';
|
|
11
11
|
|
|
12
12
|
// Need to try and read the eventcatalog.auth.js file and get the auth providers from there
|
|
13
|
-
const catalogDirectory = process.env.PROJECT_DIR || process.cwd();
|
|
14
|
-
|
|
15
13
|
const getAuthProviders = async () => {
|
|
16
14
|
try {
|
|
17
|
-
const config = await
|
|
15
|
+
const config = await importCatalogModule('eventcatalog.auth.js');
|
|
18
16
|
const authConfig = config.default;
|
|
19
17
|
|
|
20
18
|
const providers = [];
|
|
@@ -98,6 +96,7 @@ const getAuthProviders = async () => {
|
|
|
98
96
|
|
|
99
97
|
return providers;
|
|
100
98
|
} catch (error) {
|
|
99
|
+
console.error('❌ Failed to load the auth providers from eventcatalog.auth.js:', error);
|
|
101
100
|
return [];
|
|
102
101
|
}
|
|
103
102
|
};
|
|
@@ -110,13 +109,13 @@ const getAuthConfig = async () => {
|
|
|
110
109
|
};
|
|
111
110
|
}
|
|
112
111
|
try {
|
|
113
|
-
const config = await
|
|
112
|
+
const config = await importCatalogModule('eventcatalog.auth.js');
|
|
114
113
|
const authConfig = config.default;
|
|
115
114
|
|
|
116
115
|
// If custom auth config is specified (Enterprise feature)
|
|
117
116
|
if (authConfig?.customAuthConfig) {
|
|
118
117
|
try {
|
|
119
|
-
const customConfig = await
|
|
118
|
+
const customConfig = await importCatalogModule(authConfig.customAuthConfig);
|
|
120
119
|
return customConfig.default;
|
|
121
120
|
} catch (error) {
|
|
122
121
|
console.error('❌ Failed to load custom auth config:', error);
|
|
@@ -212,9 +211,8 @@ const getAuthConfig = async () => {
|
|
|
212
211
|
debug: authConfig?.debug || false,
|
|
213
212
|
};
|
|
214
213
|
} catch (error) {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
);
|
|
214
|
+
// Auth is enabled and eventcatalog.auth.js exists (isAuthEnabled checks both), so the file failed to load
|
|
215
|
+
console.error('❌ Failed to load eventcatalog.auth.js, auth disabled:', error);
|
|
218
216
|
return {
|
|
219
217
|
providers: [],
|
|
220
218
|
pages: {
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The entity map of the whole catalog: every entity and the references between them, beside a list of
|
|
3
|
+
* the domains they belong to, to show or hide each one. Only references that cross domains can be
|
|
4
|
+
* shown too. What's hidden is kept in the URL (`?hide=`), so a view can be shared.
|
|
5
|
+
*
|
|
6
|
+
* Grouping (by domain, system or team) is the visualiser's own "Group by" picker, kept in the URL too,
|
|
7
|
+
* so it carries over when the graph is drawn again with other domains hidden, a reload or a shared link
|
|
8
|
+
* (grouping by nothing included). The page starts grouped by domain only when the URL says nothing.
|
|
9
|
+
*/
|
|
10
|
+
import { useEffect, useMemo, useState, type ComponentProps } from 'react';
|
|
11
|
+
import { createPortal } from 'react-dom';
|
|
12
|
+
import type { Edge, Node } from '@xyflow/react';
|
|
13
|
+
import { Boxes, Eye, EyeOff, PanelLeftClose, PanelLeftOpen, Search, Shuffle } from 'lucide-react';
|
|
14
|
+
import AstroNodeGraph from '@components/MDX/NodeGraph/AstroNodeGraph';
|
|
15
|
+
import { getCrossDomainEdges, getDomainSummaries, getShownGraph } from '@utils/node-graphs/catalog-entity-map-view';
|
|
16
|
+
|
|
17
|
+
type Props = ComponentProps<typeof AstroNodeGraph> & { sharedLaneId: string };
|
|
18
|
+
|
|
19
|
+
const HIDE_PARAM = 'hide';
|
|
20
|
+
const CROSS_DOMAIN_PARAM = 'crossDomain';
|
|
21
|
+
// Whether the filters are collapsed, remembered in this browser (storage can be unavailable)
|
|
22
|
+
const COLLAPSED_KEY = 'eventcatalog:entity-map:filters-collapsed';
|
|
23
|
+
const PANEL_WIDTH = 288;
|
|
24
|
+
const RAIL_WIDTH = 44;
|
|
25
|
+
|
|
26
|
+
const readCollapsed = () => {
|
|
27
|
+
try {
|
|
28
|
+
return window.localStorage.getItem(COLLAPSED_KEY) === 'true';
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
const writeCollapsed = (collapsed: boolean) => {
|
|
34
|
+
try {
|
|
35
|
+
window.localStorage.setItem(COLLAPSED_KEY, String(collapsed));
|
|
36
|
+
} catch {
|
|
37
|
+
// Not remembered, then
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
const isTyping = (target: EventTarget | null) =>
|
|
41
|
+
target instanceof HTMLElement && (target.isContentEditable || ['INPUT', 'TEXTAREA', 'SELECT'].includes(target.tagName));
|
|
42
|
+
|
|
43
|
+
const readUrl = () => {
|
|
44
|
+
const params = new URLSearchParams(window.location.search);
|
|
45
|
+
return {
|
|
46
|
+
hidden: new Set((params.get(HIDE_PARAM) || '').split(',').filter(Boolean)),
|
|
47
|
+
crossDomainOnly: params.get(CROSS_DOMAIN_PARAM) === 'true',
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const writeUrl = (hidden: Set<string>, crossDomainOnly: boolean) => {
|
|
52
|
+
const url = new URL(window.location.href);
|
|
53
|
+
if (hidden.size > 0) url.searchParams.set(HIDE_PARAM, [...hidden].join(','));
|
|
54
|
+
else url.searchParams.delete(HIDE_PARAM);
|
|
55
|
+
if (crossDomainOnly) url.searchParams.set(CROSS_DOMAIN_PARAM, 'true');
|
|
56
|
+
else url.searchParams.delete(CROSS_DOMAIN_PARAM);
|
|
57
|
+
window.history.replaceState(window.history.state, '', url);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export default function CatalogEntityMap({ nodes, edges, sharedLaneId, ...graphProps }: Props) {
|
|
61
|
+
const [{ hidden, crossDomainOnly }, setView] = useState(readUrl);
|
|
62
|
+
const [query, setQuery] = useState('');
|
|
63
|
+
const [collapsed, setCollapsed] = useState(readCollapsed);
|
|
64
|
+
// The graph laid out again for a filtered view, with the view it's for (`key`), so a layout is never
|
|
65
|
+
// shown for another view while that one's is being made
|
|
66
|
+
const [laidOut, setLaidOut] = useState<{ key: string; nodes: Node[]; edges: Edge[] } | null>(null);
|
|
67
|
+
|
|
68
|
+
const crossDomainEdges = useMemo(() => getCrossDomainEdges(nodes, edges, sharedLaneId), [nodes, edges, sharedLaneId]);
|
|
69
|
+
const domains = useMemo(
|
|
70
|
+
() => getDomainSummaries(nodes, crossDomainEdges, sharedLaneId),
|
|
71
|
+
[nodes, crossDomainEdges, sharedLaneId]
|
|
72
|
+
);
|
|
73
|
+
const shown = useMemo(
|
|
74
|
+
() => getShownGraph({ nodes, edges, crossDomainEdges, hidden, crossDomainOnly }),
|
|
75
|
+
[nodes, edges, crossDomainEdges, hidden, crossDomainOnly]
|
|
76
|
+
);
|
|
77
|
+
// A new graph each time what's shown changes: the visualiser takes its nodes when it mounts
|
|
78
|
+
const graphKey = [...hidden].sort().join(',') + (crossDomainOnly ? '|cross' : '');
|
|
79
|
+
|
|
80
|
+
// Entities hidden leave gaps in the layout the catalog gave the whole map, so what's shown is laid out
|
|
81
|
+
// again (when grouped, the visualiser lays the groups out itself anyway)
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
writeUrl(hidden, crossDomainOnly);
|
|
84
|
+
if (shown.isEverything) return;
|
|
85
|
+
let cancelled = false;
|
|
86
|
+
// The same force layout the catalog gave the whole map. Should it fail, the entities keep their
|
|
87
|
+
// places in the whole map's layout rather than nothing being drawn
|
|
88
|
+
import('@utils/node-graphs/entity-layout')
|
|
89
|
+
.then(({ layoutEntityNodes }) => layoutEntityNodes(shown.nodes, shown.edges))
|
|
90
|
+
.catch(() => shown.nodes)
|
|
91
|
+
.then((laidOutNodes) => {
|
|
92
|
+
if (!cancelled) setLaidOut({ key: graphKey, nodes: laidOutNodes, edges: shown.edges });
|
|
93
|
+
});
|
|
94
|
+
return () => {
|
|
95
|
+
cancelled = true;
|
|
96
|
+
};
|
|
97
|
+
}, [shown, graphKey, hidden, crossDomainOnly]);
|
|
98
|
+
|
|
99
|
+
const toggle = (id: string) =>
|
|
100
|
+
setView((view) => {
|
|
101
|
+
const next = new Set(view.hidden);
|
|
102
|
+
if (next.has(id)) next.delete(id);
|
|
103
|
+
else next.add(id);
|
|
104
|
+
return { ...view, hidden: next };
|
|
105
|
+
});
|
|
106
|
+
// Shows only this domain (or everything, when it's the only one shown)
|
|
107
|
+
const solo = (id: string) =>
|
|
108
|
+
setView((view) => {
|
|
109
|
+
const others = domains.filter((domain) => domain.lane.id !== id).map((domain) => domain.lane.id);
|
|
110
|
+
const isSolo = !view.hidden.has(id) && others.every((other) => view.hidden.has(other));
|
|
111
|
+
return { ...view, hidden: isSolo ? new Set() : new Set(others) };
|
|
112
|
+
});
|
|
113
|
+
const setAll = (visible: boolean) =>
|
|
114
|
+
setView((view) => ({ ...view, hidden: visible ? new Set() : new Set(domains.map((domain) => domain.lane.id)) }));
|
|
115
|
+
|
|
116
|
+
const filteredDomains = domains.filter((domain) => domain.lane.name.toLowerCase().includes(query.toLowerCase()));
|
|
117
|
+
const graph = shown.isEverything ? { nodes, edges } : laidOut?.key === graphKey ? laidOut : null;
|
|
118
|
+
// Shown on the collapsed rail, so filters left on aren't forgotten
|
|
119
|
+
const activeFilters = hidden.size + (crossDomainOnly ? 1 : 0);
|
|
120
|
+
|
|
121
|
+
useEffect(() => writeCollapsed(collapsed), [collapsed]);
|
|
122
|
+
// [ collapses and opens the filters
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
const onKeyDown = (event: KeyboardEvent) => {
|
|
125
|
+
if (event.key !== '[' || event.metaKey || event.ctrlKey || event.altKey || isTyping(event.target)) return;
|
|
126
|
+
event.preventDefault();
|
|
127
|
+
setCollapsed((isCollapsed) => !isCollapsed);
|
|
128
|
+
};
|
|
129
|
+
window.addEventListener('keydown', onKeyDown);
|
|
130
|
+
return () => window.removeEventListener('keydown', onKeyDown);
|
|
131
|
+
}, []);
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<>
|
|
135
|
+
<aside
|
|
136
|
+
style={{ width: collapsed ? RAIL_WIDTH : PANEL_WIDTH }}
|
|
137
|
+
className="relative shrink-0 border border-[rgb(var(--ec-page-border))] bg-[rgb(var(--ec-card-bg))] rounded-md overflow-hidden transition-[width] duration-300 ease-in-out motion-reduce:transition-none"
|
|
138
|
+
>
|
|
139
|
+
{/* Collapsed: a rail to open the filters again */}
|
|
140
|
+
<div
|
|
141
|
+
aria-hidden={!collapsed}
|
|
142
|
+
className={`absolute inset-0 flex flex-col items-center gap-3 py-3 transition-[opacity,visibility] duration-200 ${
|
|
143
|
+
collapsed ? 'opacity-100 visible delay-150' : 'opacity-0 invisible'
|
|
144
|
+
}`}
|
|
145
|
+
>
|
|
146
|
+
<button
|
|
147
|
+
type="button"
|
|
148
|
+
onClick={() => setCollapsed(false)}
|
|
149
|
+
title="Show filters ([)"
|
|
150
|
+
aria-label="Show filters"
|
|
151
|
+
className="p-1.5 rounded-md text-[rgb(var(--ec-icon-color))] hover:text-[rgb(var(--ec-icon-hover))] hover:bg-[rgb(var(--ec-accent-subtle)/0.5)]"
|
|
152
|
+
>
|
|
153
|
+
<PanelLeftOpen className="h-4 w-4" />
|
|
154
|
+
</button>
|
|
155
|
+
{activeFilters > 0 && (
|
|
156
|
+
<span
|
|
157
|
+
title={`${activeFilters} filter${activeFilters === 1 ? '' : 's'} on`}
|
|
158
|
+
className="min-w-[1.25rem] h-5 px-1 flex items-center justify-center rounded-full text-[10px] font-semibold bg-[rgb(var(--ec-accent))] text-[rgb(var(--ec-accent-text))]"
|
|
159
|
+
>
|
|
160
|
+
{activeFilters}
|
|
161
|
+
</span>
|
|
162
|
+
)}
|
|
163
|
+
<button
|
|
164
|
+
type="button"
|
|
165
|
+
onClick={() => setCollapsed(false)}
|
|
166
|
+
className="[writing-mode:vertical-rl] rotate-180 text-xs text-[rgb(var(--ec-page-text-muted))] hover:text-[rgb(var(--ec-page-text))] whitespace-nowrap"
|
|
167
|
+
>
|
|
168
|
+
{shown.nodes.length} of {nodes.length} entities
|
|
169
|
+
</button>
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
{/* Open: the filters, at their full width so nothing reflows while the panel slides */}
|
|
173
|
+
<div
|
|
174
|
+
aria-hidden={collapsed}
|
|
175
|
+
style={{ width: PANEL_WIDTH - 2 }}
|
|
176
|
+
className={`h-full flex flex-col transition-[opacity,visibility] duration-200 ${
|
|
177
|
+
collapsed ? 'opacity-0 invisible' : 'opacity-100 visible delay-100'
|
|
178
|
+
}`}
|
|
179
|
+
>
|
|
180
|
+
<div className="px-4 pt-4 pb-3 border-b border-[rgb(var(--ec-page-border))]">
|
|
181
|
+
<div className="flex items-start justify-between gap-2">
|
|
182
|
+
<h1 className="text-sm font-semibold text-[rgb(var(--ec-page-text))]">Global Entity Map</h1>
|
|
183
|
+
<button
|
|
184
|
+
type="button"
|
|
185
|
+
onClick={() => setCollapsed(true)}
|
|
186
|
+
title="Hide filters ([)"
|
|
187
|
+
aria-label="Hide filters"
|
|
188
|
+
className="-mt-1 -mr-2 p-1.5 rounded-md text-[rgb(var(--ec-icon-color))] hover:text-[rgb(var(--ec-icon-hover))] hover:bg-[rgb(var(--ec-accent-subtle)/0.5)]"
|
|
189
|
+
>
|
|
190
|
+
<PanelLeftClose className="h-4 w-4" />
|
|
191
|
+
</button>
|
|
192
|
+
</div>
|
|
193
|
+
<p className="mt-1 text-xs text-[rgb(var(--ec-page-text-muted))]">
|
|
194
|
+
{shown.nodes.length} of {nodes.length} entities · {crossDomainEdges.length} references across domains
|
|
195
|
+
</p>
|
|
196
|
+
</div>
|
|
197
|
+
|
|
198
|
+
<label className="flex items-center gap-2.5 px-4 py-3 border-b border-[rgb(var(--ec-page-border))] cursor-pointer text-xs text-[rgb(var(--ec-page-text))]">
|
|
199
|
+
<input
|
|
200
|
+
type="checkbox"
|
|
201
|
+
checked={crossDomainOnly}
|
|
202
|
+
onChange={(event) => setView((view) => ({ ...view, crossDomainOnly: event.target.checked }))}
|
|
203
|
+
className="accent-[rgb(var(--ec-accent))]"
|
|
204
|
+
/>
|
|
205
|
+
<Shuffle className="h-3.5 w-3.5 text-[rgb(var(--ec-icon-color))]" />
|
|
206
|
+
<span className="flex-1">Only references across domains</span>
|
|
207
|
+
</label>
|
|
208
|
+
|
|
209
|
+
<div className="px-4 pt-3 pb-2 flex items-center justify-between">
|
|
210
|
+
<span className="text-[10px] font-semibold uppercase tracking-wider text-[rgb(var(--ec-page-text-muted))]">
|
|
211
|
+
Domains
|
|
212
|
+
</span>
|
|
213
|
+
<span className="flex gap-2 text-[11px]">
|
|
214
|
+
<button type="button" onClick={() => setAll(true)} className="text-[rgb(var(--ec-accent))] hover:underline">
|
|
215
|
+
Show all
|
|
216
|
+
</button>
|
|
217
|
+
<button type="button" onClick={() => setAll(false)} className="text-[rgb(var(--ec-accent))] hover:underline">
|
|
218
|
+
Hide all
|
|
219
|
+
</button>
|
|
220
|
+
</span>
|
|
221
|
+
</div>
|
|
222
|
+
<div className="px-4 pb-2">
|
|
223
|
+
<div className="flex items-center gap-2 px-2 h-8 rounded-md border border-[rgb(var(--ec-input-border))] bg-[rgb(var(--ec-input-bg))]">
|
|
224
|
+
<Search className="h-3.5 w-3.5 text-[rgb(var(--ec-icon-color))]" />
|
|
225
|
+
<input
|
|
226
|
+
value={query}
|
|
227
|
+
onChange={(event) => setQuery(event.target.value)}
|
|
228
|
+
placeholder="Filter domains"
|
|
229
|
+
className="flex-1 min-w-0 bg-transparent text-xs outline-none text-[rgb(var(--ec-input-text))]"
|
|
230
|
+
/>
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
|
|
234
|
+
<ul className="flex-1 overflow-y-auto px-2 pb-3">
|
|
235
|
+
{filteredDomains.map(({ lane, count, crossDomain }) => {
|
|
236
|
+
const visible = !hidden.has(lane.id);
|
|
237
|
+
return (
|
|
238
|
+
<li
|
|
239
|
+
key={lane.id}
|
|
240
|
+
className="group flex items-center gap-1 rounded-md hover:bg-[rgb(var(--ec-accent-subtle)/0.5)]"
|
|
241
|
+
>
|
|
242
|
+
<button
|
|
243
|
+
type="button"
|
|
244
|
+
onClick={() => toggle(lane.id)}
|
|
245
|
+
aria-pressed={visible}
|
|
246
|
+
className={`flex flex-1 min-w-0 items-center gap-2.5 px-2 py-1.5 text-left text-xs ${
|
|
247
|
+
visible ? 'text-[rgb(var(--ec-page-text))]' : 'text-[rgb(var(--ec-page-text-muted))] opacity-60'
|
|
248
|
+
}`}
|
|
249
|
+
>
|
|
250
|
+
{visible ? (
|
|
251
|
+
<Eye className="h-3.5 w-3.5 shrink-0 text-[rgb(var(--ec-icon-color))]" />
|
|
252
|
+
) : (
|
|
253
|
+
<EyeOff className="h-3.5 w-3.5 shrink-0" />
|
|
254
|
+
)}
|
|
255
|
+
<span className={`flex-1 truncate ${lane.id.startsWith('__') ? 'italic' : 'font-medium'}`}>{lane.name}</span>
|
|
256
|
+
{crossDomain > 0 && (
|
|
257
|
+
<span
|
|
258
|
+
title={`${crossDomain} references across domains`}
|
|
259
|
+
className="flex items-center gap-0.5 text-[10px] text-[rgb(var(--ec-page-text-muted))]"
|
|
260
|
+
>
|
|
261
|
+
<Shuffle className="h-3 w-3" />
|
|
262
|
+
{crossDomain}
|
|
263
|
+
</span>
|
|
264
|
+
)}
|
|
265
|
+
<span className="tabular-nums text-[11px] text-[rgb(var(--ec-page-text-muted))]">{count}</span>
|
|
266
|
+
</button>
|
|
267
|
+
<button
|
|
268
|
+
type="button"
|
|
269
|
+
onClick={() => solo(lane.id)}
|
|
270
|
+
title={`Only show ${lane.name}`}
|
|
271
|
+
className="mr-1 p-1 rounded opacity-0 group-hover:opacity-100 text-[rgb(var(--ec-icon-color))] hover:text-[rgb(var(--ec-icon-hover))]"
|
|
272
|
+
>
|
|
273
|
+
<Boxes className="h-3.5 w-3.5" />
|
|
274
|
+
</button>
|
|
275
|
+
</li>
|
|
276
|
+
);
|
|
277
|
+
})}
|
|
278
|
+
</ul>
|
|
279
|
+
</div>
|
|
280
|
+
</aside>
|
|
281
|
+
|
|
282
|
+
{graph && graph.nodes.length > 0 && (
|
|
283
|
+
<AstroNodeGraph key={graphKey} {...graphProps} nodes={graph.nodes} edges={graph.edges} />
|
|
284
|
+
)}
|
|
285
|
+
{graph && graph.nodes.length === 0 && <EmptyState portalId={graphProps.portalId || `${graphProps.id}-portal`} />}
|
|
286
|
+
</>
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const EmptyState = ({ portalId }: { portalId: string }) => {
|
|
291
|
+
const [container, setContainer] = useState<HTMLElement | null>(null);
|
|
292
|
+
useEffect(() => setContainer(document.getElementById(portalId)), [portalId]);
|
|
293
|
+
if (!container) return null;
|
|
294
|
+
// Drawn in the graph's box, where the graph would be
|
|
295
|
+
return createPortal(
|
|
296
|
+
<div className="flex h-full items-center justify-center text-sm text-[rgb(var(--ec-page-text-muted))]">
|
|
297
|
+
No entities to show. Pick a domain on the left.
|
|
298
|
+
</div>,
|
|
299
|
+
container
|
|
300
|
+
);
|
|
301
|
+
};
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
type LanguageModel,
|
|
10
10
|
type UIMessage,
|
|
11
11
|
} from 'ai';
|
|
12
|
-
import {
|
|
12
|
+
import { importCatalogModule } from '@utils/catalog-modules';
|
|
13
13
|
import { isCanvasEnabled, isDevMode } from '@utils/feature';
|
|
14
14
|
import { getCollection, getEntry } from 'astro:content';
|
|
15
15
|
import { z } from 'astro/zod';
|
|
@@ -33,8 +33,6 @@ import {
|
|
|
33
33
|
toolDescriptions,
|
|
34
34
|
} from '@features/tools/catalog-tools';
|
|
35
35
|
|
|
36
|
-
const catalogDirectory = process.env.PROJECT_DIR || process.cwd();
|
|
37
|
-
|
|
38
36
|
// How many model calls one answer may take. Questions across several domains need many tool calls, so the last
|
|
39
37
|
// step has tools turned off: the model has to answer with what it found, rather than stop without a reply.
|
|
40
38
|
const MAX_STEPS = 10;
|
|
@@ -58,7 +56,7 @@ let modelConfiguration: any;
|
|
|
58
56
|
let extendedTools: any;
|
|
59
57
|
|
|
60
58
|
try {
|
|
61
|
-
const providerConfiguration = await
|
|
59
|
+
const providerConfiguration = await importCatalogModule('eventcatalog.chat.js');
|
|
62
60
|
model = await providerConfiguration.default();
|
|
63
61
|
modelConfiguration = providerConfiguration.configuration || defaultConfiguration;
|
|
64
62
|
hasChatConfiguration = true;
|
|
@@ -3,18 +3,16 @@ import '../../styles/tailwind.css';
|
|
|
3
3
|
import config from '@config';
|
|
4
4
|
import { formatPageTitle } from '@utils/page-title';
|
|
5
5
|
const { title, logo } = config;
|
|
6
|
-
import { join } from 'node:path';
|
|
7
6
|
import { isAuthEnabled, isSSR } from '@utils/feature';
|
|
8
7
|
import { buildUrl } from '@utils/url-builder';
|
|
9
8
|
import { getSession } from '@utils/auth-astro/server';
|
|
10
|
-
|
|
11
|
-
const catalogDirectory = process.env.PROJECT_DIR || process.cwd();
|
|
9
|
+
import { importCatalogModule } from '@utils/catalog-modules';
|
|
12
10
|
|
|
13
11
|
let hasAuthConfigurationFile = false;
|
|
14
12
|
let providers: string[] = [];
|
|
15
13
|
|
|
16
14
|
try {
|
|
17
|
-
const authConfig = await
|
|
15
|
+
const authConfig = await importCatalogModule('eventcatalog.auth.js');
|
|
18
16
|
providers = Object.keys(authConfig.default.providers);
|
|
19
17
|
hasAuthConfigurationFile = true;
|
|
20
18
|
} catch (error) {
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import type { MiddlewareHandler } from 'astro';
|
|
2
2
|
import { authMiddleware } from './middleware-auth.ts';
|
|
3
3
|
import { sequence } from 'astro:middleware';
|
|
4
|
-
import {
|
|
4
|
+
import { importCatalogModule } from '@utils/catalog-modules';
|
|
5
5
|
import { isSSR } from '@utils/feature';
|
|
6
6
|
|
|
7
7
|
// Try to load customer's custom RBAC middleware
|
|
8
8
|
let customerRbacMiddleware: MiddlewareHandler | null = null;
|
|
9
9
|
|
|
10
10
|
try {
|
|
11
|
-
const
|
|
12
|
-
const customerMiddleware = await import(/* @vite-ignore */ join(catalogDirectory, 'middleware.ts'));
|
|
11
|
+
const customerMiddleware = await importCatalogModule('middleware.ts');
|
|
13
12
|
customerRbacMiddleware = customerMiddleware.rbacMiddleware;
|
|
14
13
|
|
|
15
14
|
if (!isSSR()) {
|