@eventcatalog/core 3.0.0-beta.8 → 3.0.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/README.md +41 -98
- package/dist/__mocks__/astro-content.cjs +32 -0
- package/dist/__mocks__/astro-content.d.cts +13 -0
- package/dist/__mocks__/astro-content.d.ts +13 -0
- package/dist/__mocks__/astro-content.js +7 -0
- 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 +2 -19
- package/dist/catalog-to-astro-content-directory.d.cts +1 -2
- package/dist/catalog-to-astro-content-directory.d.ts +1 -2
- package/dist/catalog-to-astro-content-directory.js +3 -5
- package/dist/{chunk-R2BJ7MJG.js → chunk-6Z6ARMQS.js} +1 -17
- package/dist/{chunk-LQUXA3NB.js → chunk-BYP43AAT.js} +1 -1
- package/dist/{chunk-UTHNQFM7.js → chunk-E5Q7TZYT.js} +1 -1
- package/dist/{chunk-KEYJ3FB3.js → chunk-EKGR533N.js} +1 -1
- package/dist/{chunk-7MCE4J6I.js → chunk-KF5PARQK.js} +1 -1
- package/dist/{chunk-I3QUYHIK.js → chunk-VO5WYA44.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +20 -64
- package/dist/eventcatalog.config.d.cts +4 -0
- package/dist/eventcatalog.config.d.ts +4 -0
- package/dist/eventcatalog.js +26 -52
- 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 +4 -1
- package/eventcatalog/integrations/eventcatalog-features.ts +69 -0
- package/eventcatalog/public/icons/asyncapi-black.svg +2 -0
- package/eventcatalog/public/icons/graphql-black.svg +1 -0
- package/eventcatalog/public/icons/openapi-black.svg +1 -0
- package/eventcatalog/src/components/ChatPanel/ChatPanel.tsx +994 -0
- package/eventcatalog/src/components/ChatPanel/ChatPanelButton.tsx +24 -0
- package/eventcatalog/src/components/Grids/DomainGrid.tsx +310 -173
- package/eventcatalog/src/components/Grids/MessageGrid.tsx +299 -180
- package/eventcatalog/src/components/Grids/specification-utils.ts +106 -0
- package/eventcatalog/src/components/Header.astro +25 -5
- package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.tsx +14 -3
- package/eventcatalog/src/components/SchemaExplorer/ApiAccessSection.tsx +95 -90
- package/eventcatalog/src/components/SchemaExplorer/ApiContentViewer.tsx +144 -0
- package/eventcatalog/src/components/SchemaExplorer/Pagination.tsx +34 -8
- package/eventcatalog/src/components/SchemaExplorer/SchemaContentViewer.tsx +2 -2
- package/eventcatalog/src/components/SchemaExplorer/SchemaDetailsHeader.tsx +140 -109
- package/eventcatalog/src/components/SchemaExplorer/SchemaDetailsPanel.tsx +5 -14
- package/eventcatalog/src/components/SchemaExplorer/SchemaExplorer.tsx +247 -59
- package/eventcatalog/src/components/SchemaExplorer/SchemaFilters.tsx +64 -126
- package/eventcatalog/src/components/SchemaExplorer/SchemaListItem.tsx +41 -43
- package/eventcatalog/src/components/Search/Search.astro +2 -2
- package/eventcatalog/src/components/Search/SearchDataLoader.astro +25 -0
- package/eventcatalog/src/components/SideNav/NestedSideBar/SearchBar.tsx +6 -3
- package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +44 -16
- package/eventcatalog/src/components/SideNav/SideNav.astro +0 -15
- package/eventcatalog/src/components/Tables/Table.tsx +96 -77
- package/eventcatalog/src/components/Tables/columns/ContainersTableColumns.tsx +108 -74
- package/eventcatalog/src/components/Tables/columns/DomainTableColumns.tsx +74 -55
- package/eventcatalog/src/components/Tables/columns/FlowTableColumns.tsx +36 -36
- package/eventcatalog/src/components/Tables/columns/MessageTableColumns.tsx +110 -77
- package/eventcatalog/src/components/Tables/columns/ServiceTableColumns.tsx +105 -94
- package/eventcatalog/src/components/Tables/columns/SharedColumns.tsx +31 -26
- package/eventcatalog/src/components/Tables/columns/TeamsTableColumns.tsx +115 -215
- package/eventcatalog/src/components/Tables/columns/UserTableColumns.tsx +145 -243
- package/eventcatalog/src/content.config.ts +1 -13
- package/eventcatalog/src/enterprise/ai/chat-api.ts +360 -0
- package/eventcatalog/src/enterprise/auth/[...auth].ts +3 -0
- package/eventcatalog/src/enterprise/auth/login.astro +420 -0
- package/eventcatalog/src/enterprise/collections/index.ts +0 -1
- package/eventcatalog/src/layouts/Footer.astro +8 -5
- package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +133 -117
- package/eventcatalog/src/pages/_index.astro +243 -559
- package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/_index.data.ts +8 -2
- package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/index.astro +9 -5
- package/eventcatalog/src/pages/directory/[type]/index.astro +6 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/asyncapi/[filename].astro +19 -3
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/index.astro +7 -7
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/graphql/[filename].astro +1 -1
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +10 -7
- package/eventcatalog/src/pages/docs/[type]/[id]/language/index.astro +194 -121
- package/eventcatalog/src/pages/docs/teams/[id]/index.astro +94 -70
- package/eventcatalog/src/pages/docs/teams/[id].mdx.ts +36 -0
- package/eventcatalog/src/pages/docs/users/[id]/index.astro +56 -45
- package/eventcatalog/src/pages/docs/users/[id].mdx.ts +36 -0
- package/eventcatalog/src/pages/schemas/explorer/_index.data.ts +178 -0
- package/eventcatalog/src/pages/schemas/explorer/index.astro +7 -157
- package/eventcatalog/src/pages/studio.astro +124 -72
- package/eventcatalog/src/remark-plugins/directives.ts +30 -9
- package/eventcatalog/src/{components/SideNav/NestedSideBar → stores/sidebar-store}/builders/container.ts +10 -1
- package/eventcatalog/src/{components/SideNav/NestedSideBar → stores/sidebar-store}/builders/domain.ts +17 -7
- package/eventcatalog/src/{components/SideNav/NestedSideBar → stores/sidebar-store}/builders/message.ts +10 -1
- package/eventcatalog/src/{components/SideNav/NestedSideBar → stores/sidebar-store}/builders/service.ts +11 -4
- package/eventcatalog/src/{components/SideNav/NestedSideBar → stores/sidebar-store}/builders/shared.ts +14 -0
- package/eventcatalog/src/stores/{sidebar-store.ts → sidebar-store/index.ts} +1 -1
- package/eventcatalog/src/utils/collections/channels.ts +0 -2
- package/eventcatalog/src/utils/collections/commands.ts +0 -2
- package/eventcatalog/src/utils/collections/containers.ts +0 -2
- package/eventcatalog/src/utils/collections/domains.ts +0 -2
- package/eventcatalog/src/utils/collections/entities.ts +0 -2
- package/eventcatalog/src/utils/collections/events.ts +0 -2
- package/eventcatalog/src/utils/collections/flows.ts +0 -2
- package/eventcatalog/src/utils/collections/queries.ts +0 -2
- package/eventcatalog/src/utils/collections/schemas.ts +45 -7
- package/eventcatalog/src/utils/collections/services.ts +0 -2
- package/eventcatalog/src/utils/feature.ts +9 -5
- package/eventcatalog/src/utils/node-graphs/services-node-graph.ts +1 -1
- package/eventcatalog/src/utils/resource-files.ts +86 -0
- package/package.json +12 -15
- package/default-files-for-collections/changelogs.md +0 -5
- package/default-files-for-collections/channels.md +0 -8
- package/default-files-for-collections/commands.md +0 -8
- package/default-files-for-collections/domains.md +0 -8
- package/default-files-for-collections/events.md +0 -8
- package/default-files-for-collections/flows.md +0 -11
- package/default-files-for-collections/queries.md +0 -8
- package/default-files-for-collections/services.md +0 -8
- package/default-files-for-collections/ubiquitousLanguages.md +0 -7
- package/eventcatalog/src/enterprise/collections/chat-prompts.ts +0 -32
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/Chat.tsx +0 -60
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/ChatMessage.tsx +0 -414
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/ChatSidebar.tsx +0 -169
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/InputModal.tsx +0 -244
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/MentionInput.tsx +0 -211
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/WelcomePromptArea.tsx +0 -176
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/default-prompts.ts +0 -93
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/hooks/ChatProvider.tsx +0 -143
- package/eventcatalog/src/enterprise/eventcatalog-chat/components/windows/ChatWindow.server.tsx +0 -387
- package/eventcatalog/src/enterprise/eventcatalog-chat/pages/api/chat.ts +0 -59
- package/eventcatalog/src/enterprise/eventcatalog-chat/pages/chat/index.astro +0 -104
- package/eventcatalog/src/enterprise/eventcatalog-chat/providers/ai-provider.ts +0 -140
- package/eventcatalog/src/enterprise/eventcatalog-chat/providers/anthropic.ts +0 -28
- package/eventcatalog/src/enterprise/eventcatalog-chat/providers/google.ts +0 -41
- package/eventcatalog/src/enterprise/eventcatalog-chat/providers/index.ts +0 -26
- package/eventcatalog/src/enterprise/eventcatalog-chat/providers/openai.ts +0 -61
- package/eventcatalog/src/enterprise/eventcatalog-chat/utils/chat-prompts.ts +0 -50
- package/eventcatalog/src/pages/auth/login.astro +0 -280
- package/eventcatalog/src/pages/chat/feature.astro +0 -179
- package/eventcatalog/src/pages/chat/index.astro +0 -10
- package/eventcatalog/src/pages/docs/_default-docs.mdx +0 -25
- package/eventcatalog/src/pages/docs/index.astro +0 -33
- package/eventcatalog/src/pages/nav-index.json.ts +0 -30
- /package/eventcatalog/src/{pages → enterprise}/auth/error.astro +0 -0
- /package/eventcatalog/src/{middleware-auth.ts → enterprise/auth/middleware/middleware-auth.ts} +0 -0
- /package/eventcatalog/src/{middleware.ts → enterprise/auth/middleware/middleware.ts} +0 -0
- /package/eventcatalog/src/{pages/unauthorized/index.astro → enterprise/auth/unauthorized.astro} +0 -0
- /package/eventcatalog/src/{pages → enterprise}/plans/index.astro +0 -0
- /package/eventcatalog/src/{components/SideNav/NestedSideBar → stores/sidebar-store}/builders/flow.ts +0 -0
- /package/eventcatalog/src/{components/SideNav/NestedSideBar/sidebar-builder.ts → stores/sidebar-store/state.ts} +0 -0
|
@@ -8,10 +8,13 @@ import {
|
|
|
8
8
|
CircleStackIcon,
|
|
9
9
|
ChevronDownIcon,
|
|
10
10
|
ChevronUpIcon,
|
|
11
|
-
|
|
11
|
+
ArrowTopRightOnSquareIcon,
|
|
12
|
+
ArrowLongRightIcon,
|
|
13
|
+
ArrowLongLeftIcon,
|
|
12
14
|
} from '@heroicons/react/24/outline';
|
|
13
15
|
import { buildUrl } from '@utils/url-builder';
|
|
14
|
-
import { BoxIcon
|
|
16
|
+
import { BoxIcon } from 'lucide-react';
|
|
17
|
+
import { getSpecUrl, getSpecIcon, getSpecLabel, getServiceSpecifications } from './specification-utils';
|
|
15
18
|
|
|
16
19
|
// ============================================
|
|
17
20
|
// Types
|
|
@@ -32,14 +35,14 @@ const getMessageIcon = (collection: string) => {
|
|
|
32
35
|
case 'commands':
|
|
33
36
|
return { Icon: ChatBubbleLeftIcon, color: 'blue' };
|
|
34
37
|
case 'queries':
|
|
35
|
-
return { Icon: MagnifyingGlassIcon, color: '
|
|
38
|
+
return { Icon: MagnifyingGlassIcon, color: 'emerald' };
|
|
36
39
|
default:
|
|
37
40
|
return { Icon: BoltIcon, color: 'gray' };
|
|
38
41
|
}
|
|
39
42
|
};
|
|
40
43
|
|
|
41
44
|
// ============================================
|
|
42
|
-
//
|
|
45
|
+
// Sub-components
|
|
43
46
|
// ============================================
|
|
44
47
|
|
|
45
48
|
const EntityBadge = memo(({ entity }: { entity: any }) => {
|
|
@@ -49,177 +52,269 @@ const EntityBadge = memo(({ entity }: { entity: any }) => {
|
|
|
49
52
|
return (
|
|
50
53
|
<a
|
|
51
54
|
href={buildUrl(`/docs/entities/${id}`)}
|
|
52
|
-
className="inline-flex items-center gap-
|
|
55
|
+
className="inline-flex items-center gap-2 px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm font-medium text-gray-700 hover:bg-gray-50 hover:border-gray-300 transition-all shadow-sm"
|
|
53
56
|
>
|
|
54
|
-
<BoxIcon className="h-
|
|
55
|
-
<span
|
|
57
|
+
<BoxIcon className="h-4 w-4 text-purple-500" />
|
|
58
|
+
<span>{name}</span>
|
|
56
59
|
</a>
|
|
57
60
|
);
|
|
58
61
|
});
|
|
59
62
|
|
|
60
|
-
const
|
|
63
|
+
const MessageLink = memo(({ message }: { message: any }) => {
|
|
61
64
|
const data = message?.data || message;
|
|
62
65
|
const collection = message?.collection || 'events';
|
|
63
66
|
const { Icon, color } = getMessageIcon(collection);
|
|
64
67
|
const id = data?.id || message?.id;
|
|
65
68
|
const name = data?.name || data?.id || id;
|
|
66
|
-
const version = data?.version;
|
|
69
|
+
const version = data?.version || message?.data?.version || 'latest';
|
|
70
|
+
|
|
71
|
+
const iconStyles: Record<string, string> = {
|
|
72
|
+
orange: 'text-orange-500',
|
|
73
|
+
blue: 'text-blue-500',
|
|
74
|
+
emerald: 'text-emerald-500',
|
|
75
|
+
gray: 'text-gray-500',
|
|
76
|
+
};
|
|
67
77
|
|
|
68
78
|
return (
|
|
69
79
|
<a
|
|
70
80
|
href={buildUrl(`/docs/${collection}/${id}/${version}`)}
|
|
71
|
-
className="flex items-center gap-
|
|
81
|
+
className="flex items-center gap-2 py-1.5 text-sm text-gray-700 hover:text-gray-900 transition-colors group"
|
|
72
82
|
>
|
|
73
|
-
<Icon className={`h-
|
|
74
|
-
<span className="
|
|
83
|
+
<Icon className={`h-4 w-4 flex-shrink-0 ${iconStyles[color]}`} />
|
|
84
|
+
<span className="group-hover:underline">{name}</span>
|
|
85
|
+
<span className="text-xs text-gray-400">v{version}</span>
|
|
75
86
|
</a>
|
|
76
87
|
);
|
|
77
88
|
});
|
|
78
89
|
|
|
79
|
-
const
|
|
90
|
+
const SpecificationBadge = memo(
|
|
91
|
+
({ spec, serviceId, serviceVersion }: { spec: any; serviceId: string; serviceVersion: string }) => {
|
|
92
|
+
return (
|
|
93
|
+
<a
|
|
94
|
+
href={getSpecUrl(spec, serviceId, serviceVersion)}
|
|
95
|
+
className="inline-flex items-center gap-1.5 px-2 py-1.5 bg-white border border-gray-200 rounded-lg text-xs font-medium text-gray-700 hover:bg-gray-50 hover:border-gray-300 transition-all shadow-sm"
|
|
96
|
+
>
|
|
97
|
+
<img src={buildUrl(`/icons/${getSpecIcon(spec.type)}.svg`, true)} alt={`${spec.type} icon`} className="h-3.5 w-3.5" />
|
|
98
|
+
<span>{getSpecLabel(spec.type)}</span>
|
|
99
|
+
</a>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const ContainerLink = memo(({ container, type }: { container: any; type: 'reads' | 'writes' }) => {
|
|
80
105
|
const data = container?.data || container;
|
|
81
106
|
const id = data?.id || container?.id;
|
|
82
107
|
const name = data?.name || id;
|
|
83
|
-
const version = data?.version;
|
|
84
|
-
const colorClass = type === 'reads' ? 'orange' : 'purple';
|
|
108
|
+
const version = data?.version || 'latest';
|
|
85
109
|
|
|
86
110
|
return (
|
|
87
111
|
<a
|
|
88
112
|
href={buildUrl(`/docs/containers/${id}/${version}`)}
|
|
89
|
-
className=
|
|
113
|
+
className="flex items-center gap-2 py-1.5 text-sm text-gray-700 hover:text-gray-900 transition-colors group"
|
|
90
114
|
>
|
|
91
|
-
<CircleStackIcon className={`h-
|
|
92
|
-
<span className=
|
|
115
|
+
<CircleStackIcon className={`h-4 w-4 ${type === 'reads' ? 'text-amber-500' : 'text-violet-500'}`} />
|
|
116
|
+
<span className="group-hover:underline">{name}</span>
|
|
93
117
|
</a>
|
|
94
118
|
);
|
|
95
119
|
});
|
|
96
120
|
|
|
121
|
+
// Searchable scrollable box component
|
|
122
|
+
const SearchableBox = memo(
|
|
123
|
+
({
|
|
124
|
+
title,
|
|
125
|
+
icon: Icon,
|
|
126
|
+
iconColor,
|
|
127
|
+
items,
|
|
128
|
+
renderItem,
|
|
129
|
+
emptyText = '—',
|
|
130
|
+
}: {
|
|
131
|
+
title: string;
|
|
132
|
+
icon: any;
|
|
133
|
+
iconColor: string;
|
|
134
|
+
items: any[];
|
|
135
|
+
renderItem: (item: any, idx: number) => React.ReactNode;
|
|
136
|
+
emptyText?: string;
|
|
137
|
+
}) => {
|
|
138
|
+
const [search, setSearch] = useState('');
|
|
139
|
+
|
|
140
|
+
const filteredItems = items.filter((item) => {
|
|
141
|
+
if (!search) return true;
|
|
142
|
+
const data = item?.data || item;
|
|
143
|
+
const name = data?.name || data?.id || '';
|
|
144
|
+
return name.toLowerCase().includes(search.toLowerCase());
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
<div className="flex flex-col">
|
|
149
|
+
<div className="flex items-center gap-2 mb-2">
|
|
150
|
+
<div className="flex items-center gap-1.5 flex-shrink-0">
|
|
151
|
+
<Icon className={`h-4 w-4 ${iconColor}`} />
|
|
152
|
+
<h4 className="text-xs font-semibold text-gray-700 uppercase tracking-wide">{title}</h4>
|
|
153
|
+
<span className="text-[10px] text-gray-400 font-medium">({items.length})</span>
|
|
154
|
+
</div>
|
|
155
|
+
{items.length > 0 && (
|
|
156
|
+
<input
|
|
157
|
+
type="text"
|
|
158
|
+
placeholder="Search..."
|
|
159
|
+
value={search}
|
|
160
|
+
onChange={(e) => setSearch(e.target.value)}
|
|
161
|
+
className="flex-1 px-2 py-0.5 text-xs border border-gray-200 rounded focus:outline-none focus:border-gray-300"
|
|
162
|
+
onClick={(e) => e.stopPropagation()}
|
|
163
|
+
/>
|
|
164
|
+
)}
|
|
165
|
+
</div>
|
|
166
|
+
{items.length > 0 ? (
|
|
167
|
+
<div className="space-y-0.5 max-h-32 overflow-y-auto pr-1">
|
|
168
|
+
{filteredItems.length > 0 ? (
|
|
169
|
+
filteredItems.map((item, idx) => renderItem(item, idx))
|
|
170
|
+
) : (
|
|
171
|
+
<p className="text-xs text-gray-400 italic">No matches</p>
|
|
172
|
+
)}
|
|
173
|
+
</div>
|
|
174
|
+
) : (
|
|
175
|
+
<p className="text-xs text-gray-300">{emptyText}</p>
|
|
176
|
+
)}
|
|
177
|
+
</div>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
// Expanded content for service card
|
|
183
|
+
const ServiceExpandedContent = memo(
|
|
184
|
+
({ receives, sends, readsFrom, writesTo }: { receives: any[]; sends: any[]; readsFrom: any[]; writesTo: any[] }) => {
|
|
185
|
+
const hasMessages = receives.length > 0 || sends.length > 0;
|
|
186
|
+
const hasContainers = readsFrom.length > 0 || writesTo.length > 0;
|
|
187
|
+
|
|
188
|
+
return (
|
|
189
|
+
<div className="border-t border-gray-100 px-4 py-3 space-y-4">
|
|
190
|
+
{/* Messages Row */}
|
|
191
|
+
{hasMessages && (
|
|
192
|
+
<div className="grid grid-cols-2 gap-x-6">
|
|
193
|
+
<SearchableBox
|
|
194
|
+
title="Receives"
|
|
195
|
+
icon={ArrowLongRightIcon}
|
|
196
|
+
iconColor="text-blue-400"
|
|
197
|
+
items={receives}
|
|
198
|
+
renderItem={(msg, idx) => {
|
|
199
|
+
const msgId = msg?.data?.id || msg?.id;
|
|
200
|
+
return msgId ? <MessageLink key={`${msgId}-${idx}`} message={msg} /> : null;
|
|
201
|
+
}}
|
|
202
|
+
/>
|
|
203
|
+
<SearchableBox
|
|
204
|
+
title="Sends"
|
|
205
|
+
icon={ArrowLongLeftIcon}
|
|
206
|
+
iconColor="text-emerald-400 rotate-180"
|
|
207
|
+
items={sends}
|
|
208
|
+
renderItem={(msg, idx) => {
|
|
209
|
+
const msgId = msg?.data?.id || msg?.id;
|
|
210
|
+
return msgId ? <MessageLink key={`${msgId}-${idx}`} message={msg} /> : null;
|
|
211
|
+
}}
|
|
212
|
+
/>
|
|
213
|
+
</div>
|
|
214
|
+
)}
|
|
215
|
+
|
|
216
|
+
{/* Data Row */}
|
|
217
|
+
{hasContainers && (
|
|
218
|
+
<div className="grid grid-cols-2 gap-x-6 pt-3 border-t border-gray-100">
|
|
219
|
+
<SearchableBox
|
|
220
|
+
title="Reads"
|
|
221
|
+
icon={CircleStackIcon}
|
|
222
|
+
iconColor="text-amber-400"
|
|
223
|
+
items={readsFrom}
|
|
224
|
+
renderItem={(container, idx) => {
|
|
225
|
+
const containerId = container?.data?.id || container?.id;
|
|
226
|
+
return containerId ? <ContainerLink key={`${containerId}-${idx}`} container={container} type="reads" /> : null;
|
|
227
|
+
}}
|
|
228
|
+
/>
|
|
229
|
+
<SearchableBox
|
|
230
|
+
title="Writes"
|
|
231
|
+
icon={CircleStackIcon}
|
|
232
|
+
iconColor="text-violet-400"
|
|
233
|
+
items={writesTo}
|
|
234
|
+
renderItem={(container, idx) => {
|
|
235
|
+
const containerId = container?.data?.id || container?.id;
|
|
236
|
+
return containerId ? <ContainerLink key={`${containerId}-${idx}`} container={container} type="writes" /> : null;
|
|
237
|
+
}}
|
|
238
|
+
/>
|
|
239
|
+
</div>
|
|
240
|
+
)}
|
|
241
|
+
</div>
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
);
|
|
245
|
+
|
|
97
246
|
const ServiceCard = memo(({ service }: { service: any }) => {
|
|
98
247
|
const data = service?.data || service;
|
|
248
|
+
const [isCollapsed, setIsCollapsed] = useState(true);
|
|
249
|
+
|
|
99
250
|
if (!data?.id) return null;
|
|
100
251
|
|
|
101
252
|
const receives = data.receives || [];
|
|
102
253
|
const sends = data.sends || [];
|
|
103
254
|
const readsFrom = data.readsFrom || [];
|
|
104
255
|
const writesTo = data.writesTo || [];
|
|
256
|
+
const specifications = getServiceSpecifications(data);
|
|
105
257
|
const hasMessages = receives.length > 0 || sends.length > 0;
|
|
106
258
|
const hasContainers = readsFrom.length > 0 || writesTo.length > 0;
|
|
259
|
+
const hasSpecs = specifications.length > 0;
|
|
260
|
+
const hasContent = hasMessages || hasContainers;
|
|
107
261
|
|
|
108
262
|
return (
|
|
109
|
-
<div className="bg-white border
|
|
263
|
+
<div className="bg-white border border-gray-200 rounded-xl shadow-sm hover:shadow-md transition-shadow">
|
|
110
264
|
{/* Service Header */}
|
|
111
|
-
<div
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
>
|
|
116
|
-
<
|
|
117
|
-
|
|
118
|
-
<span className="text-xs text-gray-500">v{data.version}</span>
|
|
119
|
-
</a>
|
|
120
|
-
<a
|
|
121
|
-
href={buildUrl(`/architecture/services/${data.id}/${data.version}`)}
|
|
122
|
-
className="p-1 hover:bg-pink-100 rounded-md transition-colors duration-200"
|
|
123
|
-
title="Expand service architecture"
|
|
124
|
-
onClick={(e) => e.stopPropagation()}
|
|
125
|
-
>
|
|
126
|
-
<ArrowsPointingOutIcon className="h-4 w-4 text-gray-500 hover:text-pink-600" />
|
|
127
|
-
</a>
|
|
128
|
-
</div>
|
|
129
|
-
|
|
130
|
-
{data.summary && <p className="mt-2 text-sm text-gray-600 line-clamp-2">{data.summary}</p>}
|
|
131
|
-
|
|
132
|
-
{/* Message Flow Diagram */}
|
|
133
|
-
{hasMessages && (
|
|
134
|
-
<div className="mt-4 flex items-stretch gap-3">
|
|
135
|
-
{/* Receives (Inbound) */}
|
|
136
|
-
<div className="flex-1 bg-blue-50 border border-blue-200 rounded-lg p-3">
|
|
137
|
-
<div className="flex items-center gap-1.5 mb-2">
|
|
138
|
-
<ArrowRight className="h-3.5 w-3.5 text-blue-500" />
|
|
139
|
-
<span className="text-xs font-semibold text-blue-700 uppercase">Inbound Messages</span>
|
|
140
|
-
<span className="text-xs text-blue-500">({receives.length})</span>
|
|
141
|
-
</div>
|
|
142
|
-
{receives.length > 0 ? (
|
|
143
|
-
<div className="space-y-1.5">
|
|
144
|
-
{receives.slice(0, 4).map((msg: any, idx: number) => {
|
|
145
|
-
const msgId = msg?.data?.id || msg?.id;
|
|
146
|
-
return msgId ? <MessageBadge key={`${msgId}-${idx}`} message={msg} /> : null;
|
|
147
|
-
})}
|
|
148
|
-
{receives.length > 4 && <p className="text-[10px] text-gray-500 text-center">+{receives.length - 4} more</p>}
|
|
149
|
-
</div>
|
|
150
|
-
) : (
|
|
151
|
-
<p className="text-[10px] text-gray-400 italic">No incoming messages</p>
|
|
152
|
-
)}
|
|
265
|
+
<div
|
|
266
|
+
onClick={() => hasContent && setIsCollapsed(!isCollapsed)}
|
|
267
|
+
className={`flex items-center justify-between px-4 py-3 ${hasContent ? 'cursor-pointer hover:bg-gray-50' : ''} transition-colors`}
|
|
268
|
+
>
|
|
269
|
+
<div className="flex items-center gap-2.5">
|
|
270
|
+
<div className="flex items-center justify-center w-8 h-8 bg-pink-100 rounded-lg">
|
|
271
|
+
<ServerIcon className="h-4 w-4 text-pink-600" />
|
|
153
272
|
</div>
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
<ServerIcon className="h-6 w-6 text-pink-500" />
|
|
273
|
+
<div>
|
|
274
|
+
<div className="flex items-center gap-2">
|
|
275
|
+
<span className="font-semibold text-gray-900">{data.name || data.id}</span>
|
|
276
|
+
<span className="text-[11px] text-gray-500 font-medium bg-gray-100 px-1.5 py-0.5 rounded">v{data.version}</span>
|
|
159
277
|
</div>
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
{/* Sends (Outbound) */}
|
|
163
|
-
<div className="flex-1 bg-green-50 border border-green-200 rounded-lg p-3">
|
|
164
|
-
<div className="flex items-center gap-1.5 mb-2">
|
|
165
|
-
<ArrowLeft className="h-3.5 w-3.5 text-green-500" />
|
|
166
|
-
<span className="text-xs font-semibold text-green-700 uppercase">Outbound Messages</span>
|
|
167
|
-
<span className="text-xs text-green-500">({sends.length})</span>
|
|
168
|
-
</div>
|
|
169
|
-
{sends.length > 0 ? (
|
|
170
|
-
<div className="space-y-1.5">
|
|
171
|
-
{sends.slice(0, 4).map((msg: any, idx: number) => {
|
|
172
|
-
const msgId = msg?.data?.id || msg?.id;
|
|
173
|
-
return msgId ? <MessageBadge key={`${msgId}-${idx}`} message={msg} /> : null;
|
|
174
|
-
})}
|
|
175
|
-
{sends.length > 4 && <p className="text-[10px] text-gray-500 text-center">+{sends.length - 4} more</p>}
|
|
176
|
-
</div>
|
|
177
|
-
) : (
|
|
178
|
-
<p className="text-[10px] text-gray-400 italic">No outgoing messages</p>
|
|
179
|
-
)}
|
|
278
|
+
{data.summary && <p className="text-xs text-gray-500 line-clamp-1 mt-0.5 max-w-md">{data.summary}</p>}
|
|
180
279
|
</div>
|
|
181
280
|
</div>
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
})}
|
|
199
|
-
{readsFrom.length > 3 && <span className="text-[10px] text-gray-500">+{readsFrom.length - 3} more</span>}
|
|
200
|
-
</div>
|
|
281
|
+
<div className="flex items-center gap-2">
|
|
282
|
+
{/* Specs in header - always visible */}
|
|
283
|
+
{hasSpecs && (
|
|
284
|
+
<div className="flex items-center gap-1.5">
|
|
285
|
+
{specifications.map((spec: any, idx: number) => (
|
|
286
|
+
<a
|
|
287
|
+
key={`${spec.type}-${idx}`}
|
|
288
|
+
href={getSpecUrl(spec, data.id, data.version)}
|
|
289
|
+
onClick={(e) => e.stopPropagation()}
|
|
290
|
+
className="flex items-center gap-1 px-2 py-1 bg-gray-100 hover:bg-gray-200 rounded text-xs text-gray-600 hover:text-gray-900 transition-colors"
|
|
291
|
+
title={getSpecLabel(spec.type)}
|
|
292
|
+
>
|
|
293
|
+
<img src={buildUrl(`/icons/${getSpecIcon(spec.type)}.svg`, true)} alt="" className="h-3.5 w-3.5" />
|
|
294
|
+
<span className="hidden sm:inline">{getSpecLabel(spec.type)}</span>
|
|
295
|
+
</a>
|
|
296
|
+
))}
|
|
201
297
|
</div>
|
|
202
298
|
)}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
<div>
|
|
207
|
-
<div className="flex items-center gap-1.5 mb-2">
|
|
208
|
-
<CircleStackIcon className="h-3.5 w-3.5 text-purple-500" />
|
|
209
|
-
<span className="text-xs font-semibold text-gray-700">Writes to</span>
|
|
210
|
-
</div>
|
|
211
|
-
<div className="flex flex-wrap gap-1.5">
|
|
212
|
-
{writesTo.slice(0, 3).map((container: any, idx: number) => {
|
|
213
|
-
const containerId = container?.data?.id || container?.id;
|
|
214
|
-
return containerId ? (
|
|
215
|
-
<ContainerBadge key={`${containerId}-${idx}`} container={container} type="writes" />
|
|
216
|
-
) : null;
|
|
217
|
-
})}
|
|
218
|
-
{writesTo.length > 3 && <span className="text-[10px] text-gray-500">+{writesTo.length - 3} more</span>}
|
|
219
|
-
</div>
|
|
299
|
+
{hasContent && (
|
|
300
|
+
<div className="p-1.5 text-gray-400">
|
|
301
|
+
{isCollapsed ? <ChevronDownIcon className="h-4 w-4" /> : <ChevronUpIcon className="h-4 w-4" />}
|
|
220
302
|
</div>
|
|
221
303
|
)}
|
|
304
|
+
<a
|
|
305
|
+
href={buildUrl(`/architecture/services/${data.id}/${data.version}`)}
|
|
306
|
+
onClick={(e) => e.stopPropagation()}
|
|
307
|
+
className="p-1.5 text-gray-400 hover:text-pink-600 hover:bg-pink-50 rounded-lg transition-colors"
|
|
308
|
+
title="View service architecture"
|
|
309
|
+
>
|
|
310
|
+
<ArrowTopRightOnSquareIcon className="h-4 w-4" />
|
|
311
|
+
</a>
|
|
222
312
|
</div>
|
|
313
|
+
</div>
|
|
314
|
+
|
|
315
|
+
{/* Expanded Content - Compact Flow */}
|
|
316
|
+
{!isCollapsed && hasContent && (
|
|
317
|
+
<ServiceExpandedContent receives={receives} sends={sends} readsFrom={readsFrom} writesTo={writesTo} />
|
|
223
318
|
)}
|
|
224
319
|
</div>
|
|
225
320
|
);
|
|
@@ -227,7 +322,7 @@ const ServiceCard = memo(({ service }: { service: any }) => {
|
|
|
227
322
|
|
|
228
323
|
const SubdomainSection = memo(({ subdomain }: { subdomain: any }) => {
|
|
229
324
|
const data = subdomain?.data || subdomain;
|
|
230
|
-
const [isCollapsed, setIsCollapsed] = useState(
|
|
325
|
+
const [isCollapsed, setIsCollapsed] = useState(true);
|
|
231
326
|
|
|
232
327
|
if (!data?.id) return null;
|
|
233
328
|
|
|
@@ -235,39 +330,59 @@ const SubdomainSection = memo(({ subdomain }: { subdomain: any }) => {
|
|
|
235
330
|
const entities = data.entities || [];
|
|
236
331
|
|
|
237
332
|
return (
|
|
238
|
-
<div className="bg-
|
|
239
|
-
{/* Subdomain Header */}
|
|
240
|
-
<div
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
<
|
|
333
|
+
<div className="bg-white border border-gray-200 rounded-xl overflow-hidden shadow-sm">
|
|
334
|
+
{/* Subdomain Header - Clickable */}
|
|
335
|
+
<div
|
|
336
|
+
onClick={() => setIsCollapsed(!isCollapsed)}
|
|
337
|
+
className={`flex items-center justify-between px-5 py-4 cursor-pointer hover:bg-gray-50 transition-colors ${!isCollapsed ? 'border-b border-gray-200' : ''}`}
|
|
338
|
+
>
|
|
339
|
+
<div className="flex items-center gap-3">
|
|
340
|
+
<div className="flex items-center justify-center w-9 h-9 bg-orange-100 rounded-lg">
|
|
341
|
+
<RectangleGroupIcon className="h-5 w-5 text-orange-600" />
|
|
342
|
+
</div>
|
|
343
|
+
<div>
|
|
344
|
+
<div className="flex items-center gap-2">
|
|
345
|
+
<h3 className="text-base font-semibold text-gray-900">{data.name || data.id}</h3>
|
|
346
|
+
<span className="text-[11px] text-gray-500 font-medium bg-white px-1.5 py-0.5 rounded border border-gray-200">
|
|
347
|
+
v{data.version}
|
|
348
|
+
</span>
|
|
349
|
+
{/* Show counts when collapsed */}
|
|
350
|
+
{isCollapsed && (services.length > 0 || entities.length > 0) && (
|
|
351
|
+
<span className="text-[11px] text-gray-400 ml-1">
|
|
352
|
+
{services.length > 0 && `${services.length} service${services.length > 1 ? 's' : ''}`}
|
|
353
|
+
{services.length > 0 && entities.length > 0 && ', '}
|
|
354
|
+
{entities.length > 0 && `${entities.length} entit${entities.length > 1 ? 'ies' : 'y'}`}
|
|
355
|
+
</span>
|
|
356
|
+
)}
|
|
357
|
+
</div>
|
|
358
|
+
<span className="text-[11px] text-gray-500 font-medium">Subdomain</span>
|
|
359
|
+
</div>
|
|
246
360
|
</div>
|
|
247
|
-
<div className="flex gap-
|
|
248
|
-
<
|
|
249
|
-
onClick={() => setIsCollapsed(!isCollapsed)}
|
|
250
|
-
className="p-1 hover:bg-orange-200 rounded-md transition-colors cursor-pointer text-gray-500 hover:text-gray-700"
|
|
251
|
-
>
|
|
361
|
+
<div className="flex items-center gap-1">
|
|
362
|
+
<div className="p-2 text-gray-400">
|
|
252
363
|
{isCollapsed ? <ChevronDownIcon className="h-5 w-5" /> : <ChevronUpIcon className="h-5 w-5" />}
|
|
253
|
-
</
|
|
364
|
+
</div>
|
|
254
365
|
<a
|
|
255
366
|
href={buildUrl(`/architecture/domains/${data.id}/${data.version}`)}
|
|
256
|
-
className="p-1 hover:bg-orange-200 rounded-md transition-colors cursor-pointer text-gray-500 hover:text-gray-700"
|
|
257
|
-
title="Expand domain architecture"
|
|
258
367
|
onClick={(e) => e.stopPropagation()}
|
|
368
|
+
className="p-2 text-gray-400 hover:text-gray-600 hover:bg-white rounded-lg transition-colors"
|
|
369
|
+
title="View subdomain architecture"
|
|
259
370
|
>
|
|
260
|
-
<
|
|
371
|
+
<ArrowTopRightOnSquareIcon className="h-5 w-5" />
|
|
261
372
|
</a>
|
|
262
373
|
</div>
|
|
263
374
|
</div>
|
|
264
375
|
|
|
265
376
|
{!isCollapsed && (
|
|
266
|
-
|
|
377
|
+
<div className="p-5 space-y-5">
|
|
267
378
|
{/* Subdomain Entities */}
|
|
268
379
|
{entities.length > 0 && (
|
|
269
|
-
<div
|
|
270
|
-
<
|
|
380
|
+
<div>
|
|
381
|
+
<div className="flex items-center gap-2 mb-3">
|
|
382
|
+
<BoxIcon className="h-4 w-4 text-purple-600" />
|
|
383
|
+
<h4 className="text-sm font-semibold text-gray-700">Entities</h4>
|
|
384
|
+
<span className="text-xs text-gray-500 bg-gray-100 px-2 py-0.5 rounded-full font-medium">{entities.length}</span>
|
|
385
|
+
</div>
|
|
271
386
|
<div className="flex flex-wrap gap-2">
|
|
272
387
|
{entities.map((entity: any) => {
|
|
273
388
|
const entityId = entity?.data?.id || entity?.id;
|
|
@@ -280,11 +395,14 @@ const SubdomainSection = memo(({ subdomain }: { subdomain: any }) => {
|
|
|
280
395
|
{/* Subdomain Services */}
|
|
281
396
|
{services.length > 0 && (
|
|
282
397
|
<div>
|
|
283
|
-
<
|
|
284
|
-
|
|
398
|
+
<div className="flex items-center gap-2 mb-3">
|
|
399
|
+
<ServerIcon className="h-4 w-4 text-pink-600" />
|
|
400
|
+
<h4 className="text-sm font-semibold text-gray-700">Services</h4>
|
|
401
|
+
<span className="text-xs text-gray-500 bg-gray-100 px-2 py-0.5 rounded-full font-medium">{services.length}</span>
|
|
402
|
+
</div>
|
|
403
|
+
<div className="space-y-3">
|
|
285
404
|
{services.map((service: any) => {
|
|
286
405
|
const serviceId = service?.data?.id || service?.id;
|
|
287
|
-
// Ensure we pass the service down with its messages populated
|
|
288
406
|
return serviceId ? <ServiceCard key={serviceId} service={service} /> : null;
|
|
289
407
|
})}
|
|
290
408
|
</div>
|
|
@@ -292,9 +410,9 @@ const SubdomainSection = memo(({ subdomain }: { subdomain: any }) => {
|
|
|
292
410
|
)}
|
|
293
411
|
|
|
294
412
|
{entities.length === 0 && services.length === 0 && (
|
|
295
|
-
<p className="text-sm text-gray-
|
|
413
|
+
<p className="text-sm text-gray-400 italic text-center py-4">No entities or services in this subdomain</p>
|
|
296
414
|
)}
|
|
297
|
-
|
|
415
|
+
</div>
|
|
298
416
|
)}
|
|
299
417
|
</div>
|
|
300
418
|
);
|
|
@@ -306,7 +424,7 @@ const SubdomainSection = memo(({ subdomain }: { subdomain: any }) => {
|
|
|
306
424
|
|
|
307
425
|
export default function DomainGrid({ domain }: DomainGridProps) {
|
|
308
426
|
const data = domain?.data;
|
|
309
|
-
if (!data) return <div>No domain data</div>;
|
|
427
|
+
if (!data) return <div className="text-gray-500">No domain data</div>;
|
|
310
428
|
|
|
311
429
|
const subdomains = data.domains || [];
|
|
312
430
|
const entities = data.entities || [];
|
|
@@ -334,39 +452,43 @@ export default function DomainGrid({ domain }: DomainGridProps) {
|
|
|
334
452
|
);
|
|
335
453
|
|
|
336
454
|
return (
|
|
337
|
-
<div className="
|
|
338
|
-
{/* Domain
|
|
339
|
-
<div className="
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
<
|
|
344
|
-
<h1 className="text-2xl font-bold text-gray-900">{data.name || data.id}</h1>
|
|
345
|
-
<span className="px-2 py-0.5 bg-yellow-200 text-yellow-800 text-xs font-medium rounded">v{data.version}</span>
|
|
346
|
-
<span className="px-2 py-0.5 bg-yellow-300 text-yellow-900 text-xs font-medium rounded">Domain</span>
|
|
455
|
+
<div className="w-full">
|
|
456
|
+
{/* Domain Header - Doc style */}
|
|
457
|
+
<div className="border-b border-gray-200 md:pb-4">
|
|
458
|
+
<div className="flex items-start justify-between">
|
|
459
|
+
<div>
|
|
460
|
+
<h2 className="text-2xl md:text-4xl font-bold text-black">{data.name || data.id}</h2>
|
|
461
|
+
{data.summary && <p className="text-lg pt-2 text-gray-500 font-light">{data.summary}</p>}
|
|
347
462
|
</div>
|
|
348
|
-
<div className="flex gap-
|
|
463
|
+
<div className="flex items-center gap-2 flex-shrink-0">
|
|
349
464
|
<a
|
|
350
465
|
href={buildUrl(`/docs/domains/${data.id}/${data.version}`)}
|
|
351
|
-
className="
|
|
466
|
+
className="inline-flex items-center gap-1.5 px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-200 rounded-lg hover:bg-gray-50 hover:border-gray-300 transition-all"
|
|
352
467
|
>
|
|
353
468
|
View docs
|
|
469
|
+
<ArrowTopRightOnSquareIcon className="h-4 w-4 text-gray-400" />
|
|
354
470
|
</a>
|
|
355
471
|
<a
|
|
356
472
|
href={buildUrl(`/visualiser/domains/${data.id}/${data.version}`)}
|
|
357
|
-
className="
|
|
473
|
+
className="inline-flex items-center gap-1.5 px-3 py-2 text-sm font-medium text-white bg-gray-800 rounded-lg hover:bg-gray-900 transition-all"
|
|
358
474
|
>
|
|
359
475
|
Visualizer
|
|
476
|
+
<ArrowTopRightOnSquareIcon className="h-4 w-4 text-gray-400" />
|
|
360
477
|
</a>
|
|
361
478
|
</div>
|
|
362
479
|
</div>
|
|
480
|
+
</div>
|
|
363
481
|
|
|
364
|
-
|
|
365
|
-
|
|
482
|
+
{/* Domain Content */}
|
|
483
|
+
<div className="py-4 space-y-8">
|
|
366
484
|
{/* Domain Entities */}
|
|
367
485
|
{entities.length > 0 && (
|
|
368
|
-
<div
|
|
369
|
-
<
|
|
486
|
+
<div>
|
|
487
|
+
<div className="flex items-center gap-2 mb-4">
|
|
488
|
+
<BoxIcon className="h-5 w-5 text-purple-600" />
|
|
489
|
+
<h3 className="text-lg font-semibold text-gray-900">Entities</h3>
|
|
490
|
+
<span className="text-sm text-gray-500 bg-gray-100 px-2.5 py-0.5 rounded-full font-medium">{entities.length}</span>
|
|
491
|
+
</div>
|
|
370
492
|
<div className="flex flex-wrap gap-2">
|
|
371
493
|
{entities.map((entity: any) => {
|
|
372
494
|
const entityId = entity?.data?.id || entity?.id;
|
|
@@ -376,11 +498,17 @@ export default function DomainGrid({ domain }: DomainGridProps) {
|
|
|
376
498
|
</div>
|
|
377
499
|
)}
|
|
378
500
|
|
|
379
|
-
{/* Top-level Services
|
|
501
|
+
{/* Top-level Services */}
|
|
380
502
|
{topLevelServices.length > 0 && (
|
|
381
|
-
<div
|
|
382
|
-
<
|
|
383
|
-
|
|
503
|
+
<div>
|
|
504
|
+
<div className="flex items-center gap-2 mb-4">
|
|
505
|
+
<ServerIcon className="h-5 w-5 text-pink-600" />
|
|
506
|
+
<h3 className="text-lg font-semibold text-gray-900">Services</h3>
|
|
507
|
+
<span className="text-sm text-gray-500 bg-gray-100 px-2.5 py-0.5 rounded-full font-medium">
|
|
508
|
+
{topLevelServices.length}
|
|
509
|
+
</span>
|
|
510
|
+
</div>
|
|
511
|
+
<div className="space-y-3">
|
|
384
512
|
{topLevelServices.map((service: any) => {
|
|
385
513
|
const serviceId = service?.data?.id || service?.id;
|
|
386
514
|
return serviceId ? <ServiceCard key={serviceId} service={service} /> : null;
|
|
@@ -389,10 +517,16 @@ export default function DomainGrid({ domain }: DomainGridProps) {
|
|
|
389
517
|
</div>
|
|
390
518
|
)}
|
|
391
519
|
|
|
392
|
-
{/* Subdomains
|
|
520
|
+
{/* Subdomains */}
|
|
393
521
|
{subdomains.length > 0 && (
|
|
394
522
|
<div>
|
|
395
|
-
<
|
|
523
|
+
<div className="flex items-center gap-2 mb-4">
|
|
524
|
+
<RectangleGroupIcon className="h-5 w-5 text-orange-600" />
|
|
525
|
+
<h3 className="text-lg font-semibold text-gray-900">Subdomains</h3>
|
|
526
|
+
<span className="text-sm text-gray-500 bg-gray-100 px-2.5 py-0.5 rounded-full font-medium">
|
|
527
|
+
{subdomains.length}
|
|
528
|
+
</span>
|
|
529
|
+
</div>
|
|
396
530
|
<div className="space-y-4">
|
|
397
531
|
{subdomains.map((subdomain: any) => {
|
|
398
532
|
const subdomainId = subdomain?.data?.id || subdomain?.id;
|
|
@@ -404,7 +538,10 @@ export default function DomainGrid({ domain }: DomainGridProps) {
|
|
|
404
538
|
|
|
405
539
|
{/* Empty state */}
|
|
406
540
|
{entities.length === 0 && services.length === 0 && subdomains.length === 0 && (
|
|
407
|
-
<div className="text-center py-
|
|
541
|
+
<div className="text-center py-12">
|
|
542
|
+
<div className="flex items-center justify-center w-16 h-16 mx-auto mb-4 bg-gray-100 rounded-2xl">
|
|
543
|
+
<RectangleGroupIcon className="h-8 w-8 text-gray-400" />
|
|
544
|
+
</div>
|
|
408
545
|
<p className="text-gray-500">This domain has no entities, services, or subdomains defined.</p>
|
|
409
546
|
</div>
|
|
410
547
|
)}
|