@nospt/plugin-dev-ai-hub 1.1.3 → 1.2.0-rc2
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 +93 -2
- package/config.schema.json +15 -0
- package/dist/api/DevAiHubResourceClient.esm.js +42 -2
- package/dist/api/DevAiHubResourceClient.esm.js.map +1 -1
- package/dist/components/AiResourcesPage/AiResourcesPage.esm.js +98 -20
- package/dist/components/AiResourcesPage/AiResourcesPage.esm.js.map +1 -1
- package/dist/components/AiResourcesPage/AiResourcesPage.module.css.esm.js +2 -2
- package/dist/components/AiResourcesPage/ResourceCard.esm.js +13 -4
- package/dist/components/AiResourcesPage/ResourceCard.esm.js.map +1 -1
- package/dist/components/AiResourcesPage/ResourceCard.module.css.esm.js +2 -2
- package/dist/components/AiResourcesPage/ResourceDetailPanel.esm.js +55 -9
- package/dist/components/AiResourcesPage/ResourceDetailPanel.esm.js.map +1 -1
- package/dist/components/AiResourcesPage/ResourceFilters.esm.js +57 -3
- package/dist/components/AiResourcesPage/ResourceFilters.esm.js.map +1 -1
- package/dist/components/AiResourcesPage/ResourceInstallDialog.esm.js +4 -2
- package/dist/components/AiResourcesPage/ResourceInstallDialog.esm.js.map +1 -1
- package/dist/components/AiResourcesPage/SortControl.esm.js +139 -0
- package/dist/components/AiResourcesPage/SortControl.esm.js.map +1 -0
- package/dist/components/AiResourcesPage/SortControl.module.css.esm.js +8 -0
- package/dist/components/AiResourcesPage/SortControl.module.css.esm.js.map +1 -0
- package/dist/components/AiResourcesPage/ownerLabel.esm.js +6 -0
- package/dist/components/AiResourcesPage/ownerLabel.esm.js.map +1 -0
- package/dist/components/AiResourcesPage/typeMeta.esm.js +1 -12
- package/dist/components/AiResourcesPage/typeMeta.esm.js.map +1 -1
- package/dist/components/Backoffice/Backoffice.module.css.esm.js +8 -0
- package/dist/components/Backoffice/Backoffice.module.css.esm.js.map +1 -0
- package/dist/components/Backoffice/BackofficeCuration.esm.js +191 -0
- package/dist/components/Backoffice/BackofficeCuration.esm.js.map +1 -0
- package/dist/components/Backoffice/BackofficeExclusion.esm.js +185 -0
- package/dist/components/Backoffice/BackofficeExclusion.esm.js.map +1 -0
- package/dist/components/Backoffice/BackofficeGate.esm.js +25 -0
- package/dist/components/Backoffice/BackofficeGate.esm.js.map +1 -0
- package/dist/components/Backoffice/BackofficeHeaderAction.esm.js +43 -0
- package/dist/components/Backoffice/BackofficeHeaderAction.esm.js.map +1 -0
- package/dist/components/Backoffice/BackofficeOverview.esm.js +517 -0
- package/dist/components/Backoffice/BackofficeOverview.esm.js.map +1 -0
- package/dist/components/Backoffice/BackofficeSection.esm.js +37 -0
- package/dist/components/Backoffice/BackofficeSection.esm.js.map +1 -0
- package/dist/components/Backoffice/TypeDistributionBar.esm.js +44 -0
- package/dist/components/Backoffice/TypeDistributionBar.esm.js.map +1 -0
- package/dist/components/Backoffice/rowEntrance.esm.js +11 -0
- package/dist/components/Backoffice/rowEntrance.esm.js.map +1 -0
- package/dist/components/Backoffice/useCurationAccess.esm.js +12 -0
- package/dist/components/Backoffice/useCurationAccess.esm.js.map +1 -0
- package/dist/components/RecommendedBadge/RecommendedBadgeIcon.esm.js +25 -0
- package/dist/components/RecommendedBadge/RecommendedBadgeIcon.esm.js.map +1 -0
- package/dist/components/RecommendedBadge/recommended-badge.svg +9 -0
- package/dist/components/ToolIcon/ToolIcon.esm.js +41 -6
- package/dist/components/ToolIcon/ToolIcon.esm.js.map +1 -1
- package/dist/hooks/useDefaultSortMode.esm.js +37 -0
- package/dist/hooks/useDefaultSortMode.esm.js.map +1 -0
- package/dist/hooks/useFrameworkLabel.esm.js +42 -0
- package/dist/hooks/useFrameworkLabel.esm.js.map +1 -0
- package/dist/hooks/useResources.esm.js +25 -14
- package/dist/hooks/useResources.esm.js.map +1 -1
- package/dist/hooks/useTelemetryCounts.esm.js +16 -7
- package/dist/hooks/useTelemetryCounts.esm.js.map +1 -1
- package/dist/hooks/useTelemetrySummary.esm.js +46 -0
- package/dist/hooks/useTelemetrySummary.esm.js.map +1 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.esm.js +1 -1
- package/dist/plugin.esm.js +90 -3
- package/dist/plugin.esm.js.map +1 -1
- package/dist/routes.esm.js +2 -1
- package/dist/routes.esm.js.map +1 -1
- package/dist/theme/typeTokens.module.css.esm.js +5 -0
- package/dist/theme/typeTokens.module.css.esm.js.map +1 -0
- package/package.json +10 -4
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
2
|
+
import { useState, useEffect, useMemo } from 'react';
|
|
3
|
+
import { Text } from '@backstage/ui';
|
|
4
|
+
import { RiGitBranchLine, RiCheckboxCircleLine, RiErrorWarningLine, RiInstallLine, RiEyeLine, RiFileCopyLine, RiDownloadLine } from '@remixicon/react';
|
|
5
|
+
import { RESOURCE_TYPES } from '@nospt/plugin-dev-ai-hub-common';
|
|
6
|
+
import { useResources } from '../../hooks/useResources.esm.js';
|
|
7
|
+
import { useTelemetrySummary } from '../../hooks/useTelemetrySummary.esm.js';
|
|
8
|
+
import { countLabel } from '../AiResourcesPage/countLabel.esm.js';
|
|
9
|
+
import { formatOwnerLabel } from '../AiResourcesPage/ownerLabel.esm.js';
|
|
10
|
+
import { getTypeMeta } from '../AiResourcesPage/typeMeta.esm.js';
|
|
11
|
+
import { useFrameworkLabel } from '../../hooks/useFrameworkLabel.esm.js';
|
|
12
|
+
import { ToolIcon } from '../ToolIcon/ToolIcon.esm.js';
|
|
13
|
+
import { BackofficeSection } from './BackofficeSection.esm.js';
|
|
14
|
+
import { rowEntrance } from './rowEntrance.esm.js';
|
|
15
|
+
import { TypeDistributionBar } from './TypeDistributionBar.esm.js';
|
|
16
|
+
import styles from './Backoffice.module.css.esm.js';
|
|
17
|
+
|
|
18
|
+
const TOP_INSTALLED_LIMIT = 5;
|
|
19
|
+
const TOP_OWNERS_LIMIT = 8;
|
|
20
|
+
const NO_OWNER_LABEL = "No owner";
|
|
21
|
+
const ENTRANCE_DURATION_MS = 700;
|
|
22
|
+
const USAGE_STATS = [
|
|
23
|
+
{
|
|
24
|
+
action: "install",
|
|
25
|
+
label: "Installs",
|
|
26
|
+
Icon: RiInstallLine,
|
|
27
|
+
accent: "var(--devaihub-type-plugin)",
|
|
28
|
+
accentBg: "var(--devaihub-type-plugin-bg)"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
action: "view",
|
|
32
|
+
label: "Views",
|
|
33
|
+
Icon: RiEyeLine,
|
|
34
|
+
accent: "var(--devaihub-type-mcp-config)",
|
|
35
|
+
accentBg: "var(--devaihub-type-mcp-config-bg)"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
action: "copy",
|
|
39
|
+
label: "Copies",
|
|
40
|
+
Icon: RiFileCopyLine,
|
|
41
|
+
accent: "var(--devaihub-type-agent)",
|
|
42
|
+
accentBg: "var(--devaihub-type-agent-bg)"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
action: "download",
|
|
46
|
+
label: "Downloads",
|
|
47
|
+
Icon: RiDownloadLine,
|
|
48
|
+
accent: "var(--devaihub-type-marketplace)",
|
|
49
|
+
accentBg: "var(--devaihub-type-marketplace-bg)"
|
|
50
|
+
}
|
|
51
|
+
];
|
|
52
|
+
const LIFECYCLE_ACCENT = {
|
|
53
|
+
production: {
|
|
54
|
+
accent: "var(--bui-positive-bg, #15803d)",
|
|
55
|
+
accentBg: "color-mix(in srgb, var(--bui-positive-bg, #15803d) 14%, transparent)"
|
|
56
|
+
},
|
|
57
|
+
experimental: {
|
|
58
|
+
accent: "var(--bui-warning-bg, #92400e)",
|
|
59
|
+
accentBg: "color-mix(in srgb, var(--bui-warning-bg, #92400e) 14%, transparent)"
|
|
60
|
+
},
|
|
61
|
+
deprecated: {
|
|
62
|
+
accent: "var(--bui-negative-bg, #b91c1c)",
|
|
63
|
+
accentBg: "color-mix(in srgb, var(--bui-negative-bg, #b91c1c) 14%, transparent)"
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const OWNER_PALETTE = RESOURCE_TYPES.map((type) => {
|
|
67
|
+
const meta = getTypeMeta(type);
|
|
68
|
+
return { accent: meta.color, accentBg: meta.colorBg };
|
|
69
|
+
});
|
|
70
|
+
function capitalize(value) {
|
|
71
|
+
return value.length === 0 ? value : value[0].toUpperCase() + value.slice(1);
|
|
72
|
+
}
|
|
73
|
+
function ownerInitials(owner) {
|
|
74
|
+
const parts = owner.replace(/[^a-zA-Z0-9]+/g, " ").trim().split(" ").filter(Boolean);
|
|
75
|
+
if (parts.length === 0) return "?";
|
|
76
|
+
if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
|
|
77
|
+
return (parts[0][0] + parts[1][0]).toUpperCase();
|
|
78
|
+
}
|
|
79
|
+
function BackofficeOverview() {
|
|
80
|
+
const frameworkLabel = useFrameworkLabel();
|
|
81
|
+
const { items, error, loading } = useResources();
|
|
82
|
+
const {
|
|
83
|
+
summary,
|
|
84
|
+
error: usageError,
|
|
85
|
+
loading: usageLoading
|
|
86
|
+
} = useTelemetrySummary();
|
|
87
|
+
const [playEntrance, setPlayEntrance] = useState(true);
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
if (loading || !playEntrance) return void 0;
|
|
90
|
+
const timer = setTimeout(
|
|
91
|
+
() => setPlayEntrance(false),
|
|
92
|
+
ENTRANCE_DURATION_MS
|
|
93
|
+
);
|
|
94
|
+
return () => clearTimeout(timer);
|
|
95
|
+
}, [loading, playEntrance]);
|
|
96
|
+
const counts = useMemo(() => {
|
|
97
|
+
const empty = Object.fromEntries(RESOURCE_TYPES.map((t) => [t, 0]));
|
|
98
|
+
return (items ?? []).reduce((acc, resource) => {
|
|
99
|
+
acc[resource.type] += 1;
|
|
100
|
+
return acc;
|
|
101
|
+
}, empty);
|
|
102
|
+
}, [items]);
|
|
103
|
+
const lifecycleCounts = useMemo(() => {
|
|
104
|
+
const byLifecycle = /* @__PURE__ */ new Map();
|
|
105
|
+
for (const resource of items ?? []) {
|
|
106
|
+
const key = resource.lifecycle || "unknown";
|
|
107
|
+
byLifecycle.set(key, (byLifecycle.get(key) ?? 0) + 1);
|
|
108
|
+
}
|
|
109
|
+
return [...byLifecycle.entries()].sort(([, a], [, b]) => b - a);
|
|
110
|
+
}, [items]);
|
|
111
|
+
const ownerCounts = useMemo(() => {
|
|
112
|
+
const byOwner = /* @__PURE__ */ new Map();
|
|
113
|
+
for (const resource of items ?? []) {
|
|
114
|
+
const key = formatOwnerLabel(resource.owner) || NO_OWNER_LABEL;
|
|
115
|
+
byOwner.set(key, (byOwner.get(key) ?? 0) + 1);
|
|
116
|
+
}
|
|
117
|
+
return [...byOwner.entries()].sort(([, a], [, b]) => b - a);
|
|
118
|
+
}, [items]);
|
|
119
|
+
const frameworkCounts = useMemo(() => {
|
|
120
|
+
const byFramework = /* @__PURE__ */ new Map();
|
|
121
|
+
for (const resource of items ?? []) {
|
|
122
|
+
if (resource.frameworks.length === 0) {
|
|
123
|
+
byFramework.set("all", (byFramework.get("all") ?? 0) + 1);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
for (const framework of resource.frameworks) {
|
|
127
|
+
byFramework.set(framework, (byFramework.get(framework) ?? 0) + 1);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return [...byFramework.entries()].sort(([, a], [, b]) => b - a);
|
|
131
|
+
}, [items]);
|
|
132
|
+
const health = useMemo(() => {
|
|
133
|
+
const list = items ?? [];
|
|
134
|
+
return {
|
|
135
|
+
missingOwner: list.filter((resource) => !resource.owner).length,
|
|
136
|
+
notInstallable: list.filter((resource) => !resource.sourceLocation).length,
|
|
137
|
+
missingDescription: list.filter((resource) => !resource.description).length,
|
|
138
|
+
noTags: list.filter((resource) => resource.tags.length === 0).length,
|
|
139
|
+
missingVersion: list.filter((resource) => !resource.version).length,
|
|
140
|
+
// Set only when a deployment's framework allow-list (ADR-0003) actually
|
|
141
|
+
// suppressed something an author declared — worth surfacing here
|
|
142
|
+
// because it is a deployment-level effect, not a per-resource authoring
|
|
143
|
+
// gap the resource's own owner can fix.
|
|
144
|
+
frameworksRestricted: list.filter(
|
|
145
|
+
(resource) => resource.frameworksRestricted
|
|
146
|
+
).length
|
|
147
|
+
};
|
|
148
|
+
}, [items]);
|
|
149
|
+
const usage = useMemo(() => {
|
|
150
|
+
if (!summary) return void 0;
|
|
151
|
+
const totals = {
|
|
152
|
+
install: 0,
|
|
153
|
+
copy: 0,
|
|
154
|
+
download: 0,
|
|
155
|
+
view: 0
|
|
156
|
+
};
|
|
157
|
+
for (const resourceCounts of Object.values(summary)) {
|
|
158
|
+
totals.install += resourceCounts.install;
|
|
159
|
+
totals.copy += resourceCounts.copy;
|
|
160
|
+
totals.download += resourceCounts.download;
|
|
161
|
+
totals.view += resourceCounts.view;
|
|
162
|
+
}
|
|
163
|
+
const topInstalled = (items ?? []).map((resource) => ({
|
|
164
|
+
resource,
|
|
165
|
+
count: summary[resource.entityRef]?.install ?? 0
|
|
166
|
+
})).filter((entry) => entry.count > 0).sort((a, b) => b.count - a.count).slice(0, TOP_INSTALLED_LIMIT);
|
|
167
|
+
return { totals, topInstalled };
|
|
168
|
+
}, [summary, items]);
|
|
169
|
+
const total = items?.length ?? 0;
|
|
170
|
+
const usageIsEmpty = !!usage && usage.totals.install === 0 && usage.totals.copy === 0 && usage.totals.download === 0 && usage.totals.view === 0;
|
|
171
|
+
const healthChecks = [
|
|
172
|
+
{
|
|
173
|
+
ok: health.missingOwner === 0,
|
|
174
|
+
text: health.missingOwner === 0 ? "Every resource has an owner" : `${health.missingOwner} ${health.missingOwner === 1 ? "resource has" : "resources have"} no owner`
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
ok: health.notInstallable === 0,
|
|
178
|
+
text: health.notInstallable === 0 ? "Every resource is installable" : `${health.notInstallable} ${health.notInstallable === 1 ? "resource is" : "resources are"} browsable only (no source location)`
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
ok: health.missingDescription === 0,
|
|
182
|
+
text: health.missingDescription === 0 ? "Every resource has a description" : `${health.missingDescription} ${health.missingDescription === 1 ? "resource has" : "resources have"} no description`
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
ok: health.noTags === 0,
|
|
186
|
+
text: health.noTags === 0 ? "Every resource has at least one tag" : `${health.noTags} ${health.noTags === 1 ? "resource has" : "resources have"} no tags`
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
ok: health.missingVersion === 0,
|
|
190
|
+
text: health.missingVersion === 0 ? "Every resource declares a version" : `${health.missingVersion} ${health.missingVersion === 1 ? "resource has" : "resources have"} no version`
|
|
191
|
+
}
|
|
192
|
+
];
|
|
193
|
+
if (health.frameworksRestricted > 0) {
|
|
194
|
+
healthChecks.push({
|
|
195
|
+
ok: false,
|
|
196
|
+
text: `${health.frameworksRestricted} ${health.frameworksRestricted === 1 ? "resource has" : "resources have"} its declared frameworks narrowed by the deployment's allow-list`
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
return /* @__PURE__ */ jsxs("div", { className: styles.surface, children: [
|
|
200
|
+
/* @__PURE__ */ jsxs(
|
|
201
|
+
BackofficeSection,
|
|
202
|
+
{
|
|
203
|
+
title: "Catalog overview",
|
|
204
|
+
description: "Every AiResource this account can see, grouped by type, lifecycle and health.",
|
|
205
|
+
loading,
|
|
206
|
+
error,
|
|
207
|
+
isEmpty: !loading && !error && total === 0,
|
|
208
|
+
emptyMessage: "No AI resources are visible to you yet. Register an AiResource in the catalog and it will appear here.",
|
|
209
|
+
children: [
|
|
210
|
+
/* @__PURE__ */ jsxs("div", { className: styles.totalSummary, children: [
|
|
211
|
+
/* @__PURE__ */ jsx(Text, { variant: "title-large", weight: "bold", as: "span", children: total }),
|
|
212
|
+
/* @__PURE__ */ jsx(Text, { variant: "body-small", color: "secondary", children: "resources in total" })
|
|
213
|
+
] }),
|
|
214
|
+
/* @__PURE__ */ jsx("div", { className: styles.grid, children: RESOURCE_TYPES.map((type, index) => {
|
|
215
|
+
const meta = getTypeMeta(type);
|
|
216
|
+
const entrance = rowEntrance(playEntrance, index);
|
|
217
|
+
const count = counts[type];
|
|
218
|
+
const percent = total > 0 ? Math.round(count / total * 100) : 0;
|
|
219
|
+
return /* @__PURE__ */ jsxs(
|
|
220
|
+
"div",
|
|
221
|
+
{
|
|
222
|
+
role: "group",
|
|
223
|
+
"aria-label": `${meta.pluralLabel}: ${countLabel(
|
|
224
|
+
count,
|
|
225
|
+
"AiResource"
|
|
226
|
+
)} (${percent}%)`,
|
|
227
|
+
className: entrance.className ? `${styles.typeCard} ${entrance.className}` : styles.typeCard,
|
|
228
|
+
style: {
|
|
229
|
+
...entrance.style,
|
|
230
|
+
"--card-accent": meta.color,
|
|
231
|
+
"--card-accent-bg": meta.colorBg
|
|
232
|
+
},
|
|
233
|
+
children: [
|
|
234
|
+
/* @__PURE__ */ jsxs("div", { className: styles.countCardLabel, children: [
|
|
235
|
+
/* @__PURE__ */ jsx("span", { className: styles.typeCardIcon, children: /* @__PURE__ */ jsx(meta.Icon, { size: 16 }) }),
|
|
236
|
+
/* @__PURE__ */ jsx(
|
|
237
|
+
Text,
|
|
238
|
+
{
|
|
239
|
+
variant: "body-small",
|
|
240
|
+
color: "secondary",
|
|
241
|
+
className: styles.cardLabel,
|
|
242
|
+
children: meta.pluralLabel
|
|
243
|
+
}
|
|
244
|
+
)
|
|
245
|
+
] }),
|
|
246
|
+
/* @__PURE__ */ jsxs("span", { className: styles.typeValue, children: [
|
|
247
|
+
percent,
|
|
248
|
+
"%"
|
|
249
|
+
] }),
|
|
250
|
+
/* @__PURE__ */ jsx(Text, { variant: "body-small", color: "secondary", children: countLabel(count, "AiResource") })
|
|
251
|
+
]
|
|
252
|
+
},
|
|
253
|
+
type
|
|
254
|
+
);
|
|
255
|
+
}) }),
|
|
256
|
+
/* @__PURE__ */ jsx(
|
|
257
|
+
TypeDistributionBar,
|
|
258
|
+
{
|
|
259
|
+
counts,
|
|
260
|
+
types: RESOURCE_TYPES,
|
|
261
|
+
total
|
|
262
|
+
}
|
|
263
|
+
),
|
|
264
|
+
/* @__PURE__ */ jsx(
|
|
265
|
+
Text,
|
|
266
|
+
{
|
|
267
|
+
variant: "body-small",
|
|
268
|
+
weight: "bold",
|
|
269
|
+
as: "h3",
|
|
270
|
+
className: styles.subsectionHeading,
|
|
271
|
+
children: "By lifecycle"
|
|
272
|
+
}
|
|
273
|
+
),
|
|
274
|
+
/* @__PURE__ */ jsx("div", { className: styles.grid, children: lifecycleCounts.map(([lifecycle, count], index) => {
|
|
275
|
+
const entrance = rowEntrance(playEntrance, index);
|
|
276
|
+
const accent = LIFECYCLE_ACCENT[lifecycle];
|
|
277
|
+
const percent = total > 0 ? Math.round(count / total * 100) : 0;
|
|
278
|
+
return /* @__PURE__ */ jsxs(
|
|
279
|
+
"div",
|
|
280
|
+
{
|
|
281
|
+
className: entrance.className ? `${styles.countCard} ${entrance.className}` : styles.countCard,
|
|
282
|
+
style: {
|
|
283
|
+
...entrance.style,
|
|
284
|
+
...accent ? {
|
|
285
|
+
"--card-accent": accent.accent,
|
|
286
|
+
"--card-accent-bg": accent.accentBg
|
|
287
|
+
} : {},
|
|
288
|
+
"--share": `${percent}%`
|
|
289
|
+
},
|
|
290
|
+
children: [
|
|
291
|
+
/* @__PURE__ */ jsxs("div", { className: styles.countCardLabel, children: [
|
|
292
|
+
/* @__PURE__ */ jsx("span", { className: styles.countCardIcon, children: /* @__PURE__ */ jsx(RiGitBranchLine, { size: 16 }) }),
|
|
293
|
+
/* @__PURE__ */ jsx(
|
|
294
|
+
Text,
|
|
295
|
+
{
|
|
296
|
+
variant: "body-small",
|
|
297
|
+
color: "secondary",
|
|
298
|
+
className: styles.cardLabel,
|
|
299
|
+
children: capitalize(lifecycle)
|
|
300
|
+
}
|
|
301
|
+
)
|
|
302
|
+
] }),
|
|
303
|
+
/* @__PURE__ */ jsx("span", { className: styles.countValue, children: count }),
|
|
304
|
+
/* @__PURE__ */ jsx("span", { className: styles.shareBar, "aria-hidden": true, children: /* @__PURE__ */ jsx("span", { className: styles.shareBarFill }) })
|
|
305
|
+
]
|
|
306
|
+
},
|
|
307
|
+
lifecycle
|
|
308
|
+
);
|
|
309
|
+
}) }),
|
|
310
|
+
/* @__PURE__ */ jsx(
|
|
311
|
+
Text,
|
|
312
|
+
{
|
|
313
|
+
variant: "body-small",
|
|
314
|
+
weight: "bold",
|
|
315
|
+
as: "h3",
|
|
316
|
+
className: styles.subsectionHeading,
|
|
317
|
+
children: "By owner"
|
|
318
|
+
}
|
|
319
|
+
),
|
|
320
|
+
/* @__PURE__ */ jsx("ul", { className: styles.topList, children: ownerCounts.slice(0, TOP_OWNERS_LIMIT).map(([owner, count], index) => {
|
|
321
|
+
const entrance = rowEntrance(playEntrance, index);
|
|
322
|
+
const percent = total > 0 ? Math.round(count / total * 100) : 0;
|
|
323
|
+
const palette = owner === NO_OWNER_LABEL ? void 0 : OWNER_PALETTE[index % OWNER_PALETTE.length];
|
|
324
|
+
return /* @__PURE__ */ jsxs(
|
|
325
|
+
"li",
|
|
326
|
+
{
|
|
327
|
+
className: entrance.className ? `${styles.barRow} ${entrance.className}` : styles.barRow,
|
|
328
|
+
style: {
|
|
329
|
+
...entrance.style,
|
|
330
|
+
...palette ? {
|
|
331
|
+
"--card-accent": palette.accent,
|
|
332
|
+
"--card-accent-bg": palette.accentBg
|
|
333
|
+
} : {},
|
|
334
|
+
"--share": `${percent}%`
|
|
335
|
+
},
|
|
336
|
+
children: [
|
|
337
|
+
/* @__PURE__ */ jsxs("div", { className: styles.barRowMain, children: [
|
|
338
|
+
/* @__PURE__ */ jsxs("div", { className: styles.barRowLeft, children: [
|
|
339
|
+
/* @__PURE__ */ jsx("span", { className: styles.ownerAvatar, "aria-hidden": true, children: ownerInitials(owner) }),
|
|
340
|
+
/* @__PURE__ */ jsx(Text, { variant: "body-small", children: owner })
|
|
341
|
+
] }),
|
|
342
|
+
/* @__PURE__ */ jsx("span", { className: styles.topListCount, children: count })
|
|
343
|
+
] }),
|
|
344
|
+
/* @__PURE__ */ jsx("span", { className: styles.shareBar, "aria-hidden": true, children: /* @__PURE__ */ jsx("span", { className: styles.shareBarFill }) })
|
|
345
|
+
]
|
|
346
|
+
},
|
|
347
|
+
owner
|
|
348
|
+
);
|
|
349
|
+
}) }),
|
|
350
|
+
ownerCounts.length > TOP_OWNERS_LIMIT && /* @__PURE__ */ jsxs(Text, { variant: "body-small", color: "secondary", children: [
|
|
351
|
+
"+",
|
|
352
|
+
ownerCounts.length - TOP_OWNERS_LIMIT,
|
|
353
|
+
" more",
|
|
354
|
+
" ",
|
|
355
|
+
ownerCounts.length - TOP_OWNERS_LIMIT === 1 ? "owner" : "owners"
|
|
356
|
+
] }),
|
|
357
|
+
/* @__PURE__ */ jsx(
|
|
358
|
+
Text,
|
|
359
|
+
{
|
|
360
|
+
variant: "body-small",
|
|
361
|
+
weight: "bold",
|
|
362
|
+
as: "h3",
|
|
363
|
+
className: styles.subsectionHeading,
|
|
364
|
+
children: "By framework"
|
|
365
|
+
}
|
|
366
|
+
),
|
|
367
|
+
/* @__PURE__ */ jsx("div", { className: styles.grid, children: frameworkCounts.map(([framework, count], index) => {
|
|
368
|
+
const entrance = rowEntrance(playEntrance, index);
|
|
369
|
+
const percent = total > 0 ? Math.min(100, Math.round(count / total * 100)) : 0;
|
|
370
|
+
return /* @__PURE__ */ jsxs(
|
|
371
|
+
"div",
|
|
372
|
+
{
|
|
373
|
+
className: entrance.className ? `${styles.countCard} ${entrance.className}` : styles.countCard,
|
|
374
|
+
style: {
|
|
375
|
+
...entrance.style,
|
|
376
|
+
"--card-accent": "var(--bui-fg-secondary)",
|
|
377
|
+
"--share": `${percent}%`
|
|
378
|
+
},
|
|
379
|
+
children: [
|
|
380
|
+
/* @__PURE__ */ jsxs("div", { className: styles.countCardLabel, children: [
|
|
381
|
+
/* @__PURE__ */ jsx("span", { className: styles.countCardIcon, children: /* @__PURE__ */ jsx(ToolIcon, { tool: framework, size: 16 }) }),
|
|
382
|
+
/* @__PURE__ */ jsx(
|
|
383
|
+
Text,
|
|
384
|
+
{
|
|
385
|
+
variant: "body-small",
|
|
386
|
+
color: "secondary",
|
|
387
|
+
className: styles.cardLabel,
|
|
388
|
+
children: frameworkLabel(framework)
|
|
389
|
+
}
|
|
390
|
+
)
|
|
391
|
+
] }),
|
|
392
|
+
/* @__PURE__ */ jsx("span", { className: styles.countValue, children: count }),
|
|
393
|
+
/* @__PURE__ */ jsx("span", { className: styles.shareBar, "aria-hidden": true, children: /* @__PURE__ */ jsx("span", { className: styles.shareBarFill }) })
|
|
394
|
+
]
|
|
395
|
+
},
|
|
396
|
+
framework
|
|
397
|
+
);
|
|
398
|
+
}) }),
|
|
399
|
+
/* @__PURE__ */ jsx(
|
|
400
|
+
Text,
|
|
401
|
+
{
|
|
402
|
+
variant: "body-small",
|
|
403
|
+
weight: "bold",
|
|
404
|
+
as: "h3",
|
|
405
|
+
className: styles.subsectionHeading,
|
|
406
|
+
children: "Catalog health"
|
|
407
|
+
}
|
|
408
|
+
),
|
|
409
|
+
/* @__PURE__ */ jsx("ul", { className: styles.healthList, children: healthChecks.map(({ ok, text }) => /* @__PURE__ */ jsxs(
|
|
410
|
+
"li",
|
|
411
|
+
{
|
|
412
|
+
className: `${styles.healthCard} ${ok ? styles.healthCardOk : styles.healthCardWarning}`,
|
|
413
|
+
children: [
|
|
414
|
+
/* @__PURE__ */ jsx("span", { className: styles.healthCardIcon, children: ok ? /* @__PURE__ */ jsx(RiCheckboxCircleLine, { size: 16 }) : /* @__PURE__ */ jsx(RiErrorWarningLine, { size: 16 }) }),
|
|
415
|
+
/* @__PURE__ */ jsx(Text, { variant: "body-small", children: text })
|
|
416
|
+
]
|
|
417
|
+
},
|
|
418
|
+
text
|
|
419
|
+
)) })
|
|
420
|
+
]
|
|
421
|
+
}
|
|
422
|
+
),
|
|
423
|
+
/* @__PURE__ */ jsx(
|
|
424
|
+
BackofficeSection,
|
|
425
|
+
{
|
|
426
|
+
title: "Usage",
|
|
427
|
+
description: "Lifetime install, view, copy and download counts across the catalog (ADR-0007).",
|
|
428
|
+
loading: usageLoading,
|
|
429
|
+
error: usageError,
|
|
430
|
+
isEmpty: usageIsEmpty,
|
|
431
|
+
emptyMessage: "No activity has been recorded yet.",
|
|
432
|
+
children: usage && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
433
|
+
/* @__PURE__ */ jsx("div", { className: styles.grid, children: USAGE_STATS.map(
|
|
434
|
+
({ action, label, Icon, accent, accentBg }, index) => {
|
|
435
|
+
const entrance = rowEntrance(playEntrance, index);
|
|
436
|
+
return /* @__PURE__ */ jsxs(
|
|
437
|
+
"div",
|
|
438
|
+
{
|
|
439
|
+
className: entrance.className ? `${styles.countCard} ${entrance.className}` : styles.countCard,
|
|
440
|
+
style: {
|
|
441
|
+
...entrance.style,
|
|
442
|
+
"--card-accent": accent,
|
|
443
|
+
"--card-accent-bg": accentBg
|
|
444
|
+
},
|
|
445
|
+
children: [
|
|
446
|
+
/* @__PURE__ */ jsxs("div", { className: styles.countCardLabel, children: [
|
|
447
|
+
/* @__PURE__ */ jsx("span", { className: styles.countCardIcon, children: /* @__PURE__ */ jsx(Icon, { size: 16 }) }),
|
|
448
|
+
/* @__PURE__ */ jsx(
|
|
449
|
+
Text,
|
|
450
|
+
{
|
|
451
|
+
variant: "body-small",
|
|
452
|
+
color: "secondary",
|
|
453
|
+
className: styles.cardLabel,
|
|
454
|
+
children: label
|
|
455
|
+
}
|
|
456
|
+
)
|
|
457
|
+
] }),
|
|
458
|
+
/* @__PURE__ */ jsx("span", { className: styles.countValue, children: usage.totals[action] })
|
|
459
|
+
]
|
|
460
|
+
},
|
|
461
|
+
action
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
) }),
|
|
465
|
+
usage.topInstalled.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
466
|
+
/* @__PURE__ */ jsx(
|
|
467
|
+
Text,
|
|
468
|
+
{
|
|
469
|
+
variant: "body-small",
|
|
470
|
+
weight: "bold",
|
|
471
|
+
as: "h3",
|
|
472
|
+
className: styles.subsectionHeading,
|
|
473
|
+
children: "Most installed"
|
|
474
|
+
}
|
|
475
|
+
),
|
|
476
|
+
/* @__PURE__ */ jsx("ol", { className: styles.topList, children: usage.topInstalled.map(({ resource, count }, index) => {
|
|
477
|
+
const entrance = rowEntrance(playEntrance, index);
|
|
478
|
+
const leader = usage.topInstalled[0].count;
|
|
479
|
+
const percent = leader > 0 ? Math.round(count / leader * 100) : 0;
|
|
480
|
+
return /* @__PURE__ */ jsxs(
|
|
481
|
+
"li",
|
|
482
|
+
{
|
|
483
|
+
className: entrance.className ? `${styles.barRow} ${entrance.className}` : styles.barRow,
|
|
484
|
+
style: {
|
|
485
|
+
...entrance.style,
|
|
486
|
+
"--card-accent": "var(--devaihub-type-plugin)",
|
|
487
|
+
"--share": `${percent}%`
|
|
488
|
+
},
|
|
489
|
+
children: [
|
|
490
|
+
/* @__PURE__ */ jsxs("div", { className: styles.barRowMain, children: [
|
|
491
|
+
/* @__PURE__ */ jsxs("div", { className: styles.barRowLeft, children: [
|
|
492
|
+
/* @__PURE__ */ jsx(
|
|
493
|
+
"span",
|
|
494
|
+
{
|
|
495
|
+
className: index === 0 ? `${styles.rankBadge} ${styles.rankBadgeLead}` : styles.rankBadge,
|
|
496
|
+
children: index + 1
|
|
497
|
+
}
|
|
498
|
+
),
|
|
499
|
+
/* @__PURE__ */ jsx(Text, { variant: "body-small", children: resource.title ?? resource.name })
|
|
500
|
+
] }),
|
|
501
|
+
/* @__PURE__ */ jsx("span", { className: styles.topListCount, children: count })
|
|
502
|
+
] }),
|
|
503
|
+
/* @__PURE__ */ jsx("span", { className: styles.shareBar, "aria-hidden": true, children: /* @__PURE__ */ jsx("span", { className: styles.shareBarFill }) })
|
|
504
|
+
]
|
|
505
|
+
},
|
|
506
|
+
resource.entityRef
|
|
507
|
+
);
|
|
508
|
+
}) })
|
|
509
|
+
] })
|
|
510
|
+
] })
|
|
511
|
+
}
|
|
512
|
+
)
|
|
513
|
+
] });
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
export { BackofficeOverview };
|
|
517
|
+
//# sourceMappingURL=BackofficeOverview.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BackofficeOverview.esm.js","sources":["../../../src/components/Backoffice/BackofficeOverview.tsx"],"sourcesContent":["import { useEffect, useMemo, useState, type ElementType } from 'react';\nimport { Text } from '@backstage/ui';\nimport {\n RiCheckboxCircleLine,\n RiDownloadLine,\n RiErrorWarningLine,\n RiEyeLine,\n RiFileCopyLine,\n RiGitBranchLine,\n RiInstallLine,\n} from '@remixicon/react';\nimport {\n RESOURCE_TYPES,\n type ResourceType,\n type TelemetryAction,\n} from '@nospt/plugin-dev-ai-hub-common';\nimport { useResources } from '../../hooks/useResources';\nimport { useTelemetrySummary } from '../../hooks/useTelemetrySummary';\nimport { countLabel } from '../AiResourcesPage/countLabel';\nimport { formatOwnerLabel } from '../AiResourcesPage/ownerLabel';\nimport { getTypeMeta } from '../AiResourcesPage/typeMeta';\nimport { useFrameworkLabel } from '../../hooks/useFrameworkLabel';\nimport { ToolIcon } from '../ToolIcon';\nimport { BackofficeSection } from './BackofficeSection';\nimport { rowEntrance } from './rowEntrance';\nimport { TypeDistributionBar } from './TypeDistributionBar';\nimport styles from './Backoffice.module.css';\n\n/** How many resources the \"Most installed\" ranking shows (#83). */\nconst TOP_INSTALLED_LIMIT = 5;\n\n/** How many owning teams/users the \"By owner\" breakdown shows before\n * collapsing the rest into a single \"+N more\" line (#83) — large catalogs\n * can have dozens of owners, and this page is a summary, not a directory. */\nconst TOP_OWNERS_LIMIT = 8;\n\n/** Display label for resources with no owner set, matching the \"Catalog\n * health\" row's own wording for the same gap. */\nconst NO_OWNER_LABEL = 'No owner';\n\n/** How long the initial-render entrance animation plays for (#83), matching\n * AiResourcesPage's playEntrance window. */\nconst ENTRANCE_DURATION_MS = 700;\n\nconst USAGE_STATS: {\n action: TelemetryAction;\n label: string;\n Icon: ElementType;\n accent: string;\n accentBg: string;\n}[] = [\n {\n action: 'install',\n label: 'Installs',\n Icon: RiInstallLine,\n accent: 'var(--devaihub-type-plugin)',\n accentBg: 'var(--devaihub-type-plugin-bg)',\n },\n {\n action: 'view',\n label: 'Views',\n Icon: RiEyeLine,\n accent: 'var(--devaihub-type-mcp-config)',\n accentBg: 'var(--devaihub-type-mcp-config-bg)',\n },\n {\n action: 'copy',\n label: 'Copies',\n Icon: RiFileCopyLine,\n accent: 'var(--devaihub-type-agent)',\n accentBg: 'var(--devaihub-type-agent-bg)',\n },\n {\n action: 'download',\n label: 'Downloads',\n Icon: RiDownloadLine,\n accent: 'var(--devaihub-type-marketplace)',\n accentBg: 'var(--devaihub-type-marketplace-bg)',\n },\n];\n\n/** Semantic accent per lifecycle stage for the \"By lifecycle\" cards (#83):\n * production reads as healthy, experimental as in-progress, deprecated as a\n * warning. An unlisted stage falls through to the neutral card treatment. */\nconst LIFECYCLE_ACCENT: Record<string, { accent: string; accentBg: string }> = {\n production: {\n accent: 'var(--bui-positive-bg, #15803d)',\n accentBg:\n 'color-mix(in srgb, var(--bui-positive-bg, #15803d) 14%, transparent)',\n },\n experimental: {\n accent: 'var(--bui-warning-bg, #92400e)',\n accentBg:\n 'color-mix(in srgb, var(--bui-warning-bg, #92400e) 14%, transparent)',\n },\n deprecated: {\n accent: 'var(--bui-negative-bg, #b91c1c)',\n accentBg:\n 'color-mix(in srgb, var(--bui-negative-bg, #b91c1c) 14%, transparent)',\n },\n};\n\n/** A set of distinguishable accent colours for the \"By owner\" avatars,\n * cycling the six type hues (ADR-0008) purely as a palette — no type meaning\n * is implied. Built once from the type registry so it tracks the theme. */\nconst OWNER_PALETTE = RESOURCE_TYPES.map(type => {\n const meta = getTypeMeta(type);\n return { accent: meta.color, accentBg: meta.colorBg };\n});\n\nfunction capitalize(value: string): string {\n return value.length === 0 ? value : value[0].toUpperCase() + value.slice(1);\n}\n\n/** Up to two uppercase letters for an owner's avatar disc (#83). Uppercasing\n * also keeps these initials from ever colliding with a lowercase resource\n * title in the DOM, which the ranked-list tests match on. */\nfunction ownerInitials(owner: string): string {\n const parts = owner\n .replace(/[^a-zA-Z0-9]+/g, ' ')\n .trim()\n .split(' ')\n .filter(Boolean);\n if (parts.length === 0) return '?';\n if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();\n return (parts[0][0] + parts[1][0]).toUpperCase();\n}\n\n/**\n * The first backoffice section: what the catalog currently holds, by type,\n * by lifecycle, and how healthy it is (#83). A second section rolls up\n * lifetime usage (#83, ADR-0007) so an admin sees \"what exists\" and \"what\n * gets used\" on the same landing page.\n *\n * Both remain deliberately read-only. Catalog health and usage are derived\n * from data the plugin already fetches — health from `useResources`, usage\n * from a single aggregate telemetry query — so this stays a summary of\n * existing state rather than a place that writes anything.\n */\nexport function BackofficeOverview() {\n const frameworkLabel = useFrameworkLabel();\n const { items, error, loading } = useResources();\n const {\n summary,\n error: usageError,\n loading: usageLoading,\n } = useTelemetrySummary();\n\n // Staggered row entrance plays once, on the initial successful load only\n // (same trade as AiResourcesPage's playEntrance) — re-playing it on every\n // background refresh would be noisy rather than delightful.\n const [playEntrance, setPlayEntrance] = useState(true);\n useEffect(() => {\n if (loading || !playEntrance) return undefined;\n const timer = setTimeout(\n () => setPlayEntrance(false),\n ENTRANCE_DURATION_MS,\n );\n return () => clearTimeout(timer);\n }, [loading, playEntrance]);\n const counts = useMemo(() => {\n const empty = Object.fromEntries(RESOURCE_TYPES.map(t => [t, 0])) as Record<\n ResourceType,\n number\n >;\n return (items ?? []).reduce((acc, resource) => {\n acc[resource.type] += 1;\n return acc;\n }, empty);\n }, [items]);\n\n // Lifecycle is a free-form string (ADR-0001), not the fixed ResourceType\n // enum, so it groups dynamically instead of pre-seeding every possible\n // value the way the type grid above does.\n const lifecycleCounts = useMemo(() => {\n const byLifecycle = new Map<string, number>();\n for (const resource of items ?? []) {\n const key = resource.lifecycle || 'unknown';\n byLifecycle.set(key, (byLifecycle.get(key) ?? 0) + 1);\n }\n return [...byLifecycle.entries()].sort(([, a], [, b]) => b - a);\n }, [items]);\n\n // Same free-form grouping as lifecycle above, keyed on the display label\n // (not the raw entity ref) so \"group:default/team-a\" and a hypothetical\n // differently-namespaced ref for the same team don't fork into two rows.\n const ownerCounts = useMemo(() => {\n const byOwner = new Map<string, number>();\n for (const resource of items ?? []) {\n const key = formatOwnerLabel(resource.owner) || NO_OWNER_LABEL;\n byOwner.set(key, (byOwner.get(key) ?? 0) + 1);\n }\n return [...byOwner.entries()].sort(([, a], [, b]) => b - a);\n }, [items]);\n\n // A resource with no declared frameworks is compatible with every host\n // (ADR-0003; see ResourceInstallDialog's `frameworksRestricted` handling),\n // so it counts under the same \"All tools\" bucket a resource that\n // explicitly declares `all` would — both answer \"does this work\n // everywhere?\" the same way for this rollup.\n const frameworkCounts = useMemo(() => {\n const byFramework = new Map<string, number>();\n for (const resource of items ?? []) {\n if (resource.frameworks.length === 0) {\n byFramework.set('all', (byFramework.get('all') ?? 0) + 1);\n continue;\n }\n for (const framework of resource.frameworks) {\n byFramework.set(framework, (byFramework.get(framework) ?? 0) + 1);\n }\n }\n return [...byFramework.entries()].sort(([, a], [, b]) => b - a);\n }, [items]);\n\n const health = useMemo(() => {\n const list = items ?? [];\n return {\n missingOwner: list.filter(resource => !resource.owner).length,\n notInstallable: list.filter(resource => !resource.sourceLocation).length,\n missingDescription: list.filter(resource => !resource.description).length,\n noTags: list.filter(resource => resource.tags.length === 0).length,\n missingVersion: list.filter(resource => !resource.version).length,\n // Set only when a deployment's framework allow-list (ADR-0003) actually\n // suppressed something an author declared — worth surfacing here\n // because it is a deployment-level effect, not a per-resource authoring\n // gap the resource's own owner can fix.\n frameworksRestricted: list.filter(\n resource => resource.frameworksRestricted,\n ).length,\n };\n }, [items]);\n\n // The telemetry table has no notion of catalog visibility (unlike\n // `/resources`, ADR-0006 applies there, not here), so joining every\n // summary entry against the resources this account can already see is\n // what keeps \"most installed\" from naming something the viewer cannot\n // otherwise find.\n const usage = useMemo(() => {\n if (!summary) return undefined;\n const totals: Record<TelemetryAction, number> = {\n install: 0,\n copy: 0,\n download: 0,\n view: 0,\n };\n for (const resourceCounts of Object.values(summary)) {\n totals.install += resourceCounts.install;\n totals.copy += resourceCounts.copy;\n totals.download += resourceCounts.download;\n totals.view += resourceCounts.view;\n }\n const topInstalled = (items ?? [])\n .map(resource => ({\n resource,\n count: summary[resource.entityRef]?.install ?? 0,\n }))\n .filter(entry => entry.count > 0)\n .sort((a, b) => b.count - a.count)\n .slice(0, TOP_INSTALLED_LIMIT);\n return { totals, topInstalled };\n }, [summary, items]);\n\n const total = items?.length ?? 0;\n const usageIsEmpty =\n !!usage &&\n usage.totals.install === 0 &&\n usage.totals.copy === 0 &&\n usage.totals.download === 0 &&\n usage.totals.view === 0;\n\n // One row per catalog-health check, each pre-worded so the render is a plain\n // map instead of six hand-written branches (#83). The `ok` flag drives the\n // green/amber card treatment; the wording is unchanged from the per-row\n // version it replaced.\n const healthChecks: { ok: boolean; text: string }[] = [\n {\n ok: health.missingOwner === 0,\n text:\n health.missingOwner === 0\n ? 'Every resource has an owner'\n : `${health.missingOwner} ${\n health.missingOwner === 1 ? 'resource has' : 'resources have'\n } no owner`,\n },\n {\n ok: health.notInstallable === 0,\n text:\n health.notInstallable === 0\n ? 'Every resource is installable'\n : `${health.notInstallable} ${\n health.notInstallable === 1 ? 'resource is' : 'resources are'\n } browsable only (no source location)`,\n },\n {\n ok: health.missingDescription === 0,\n text:\n health.missingDescription === 0\n ? 'Every resource has a description'\n : `${health.missingDescription} ${\n health.missingDescription === 1\n ? 'resource has'\n : 'resources have'\n } no description`,\n },\n {\n ok: health.noTags === 0,\n text:\n health.noTags === 0\n ? 'Every resource has at least one tag'\n : `${health.noTags} ${\n health.noTags === 1 ? 'resource has' : 'resources have'\n } no tags`,\n },\n {\n ok: health.missingVersion === 0,\n text:\n health.missingVersion === 0\n ? 'Every resource declares a version'\n : `${health.missingVersion} ${\n health.missingVersion === 1 ? 'resource has' : 'resources have'\n } no version`,\n },\n ];\n if (health.frameworksRestricted > 0) {\n healthChecks.push({\n ok: false,\n text: `${health.frameworksRestricted} ${\n health.frameworksRestricted === 1 ? 'resource has' : 'resources have'\n } its declared frameworks narrowed by the deployment's allow-list`,\n });\n }\n\n return (\n <div className={styles.surface}>\n <BackofficeSection\n title=\"Catalog overview\"\n description=\"Every AiResource this account can see, grouped by type, lifecycle and health.\"\n loading={loading}\n error={error}\n isEmpty={!loading && !error && total === 0}\n emptyMessage=\"No AI resources are visible to you yet. Register an AiResource in the catalog and it will appear here.\"\n >\n <div className={styles.totalSummary}>\n <Text variant=\"title-large\" weight=\"bold\" as=\"span\">\n {total}\n </Text>\n <Text variant=\"body-small\" color=\"secondary\">\n resources in total\n </Text>\n </div>\n\n <div className={styles.grid}>\n {RESOURCE_TYPES.map((type, index) => {\n const meta = getTypeMeta(type);\n const entrance = rowEntrance(playEntrance, index);\n const count = counts[type];\n const percent = total > 0 ? Math.round((count / total) * 100) : 0;\n return (\n <div\n key={type}\n role=\"group\"\n aria-label={`${meta.pluralLabel}: ${countLabel(\n count,\n 'AiResource',\n )} (${percent}%)`}\n className={\n entrance.className\n ? `${styles.typeCard} ${entrance.className}`\n : styles.typeCard\n }\n style={\n {\n ...entrance.style,\n '--card-accent': meta.color,\n '--card-accent-bg': meta.colorBg,\n } as React.CSSProperties\n }\n >\n <div className={styles.countCardLabel}>\n <span className={styles.typeCardIcon}>\n <meta.Icon size={16} />\n </span>\n <Text\n variant=\"body-small\"\n color=\"secondary\"\n className={styles.cardLabel}\n >\n {meta.pluralLabel}\n </Text>\n </div>\n <span className={styles.typeValue}>{percent}%</span>\n <Text variant=\"body-small\" color=\"secondary\">\n {countLabel(count, 'AiResource')}\n </Text>\n </div>\n );\n })}\n </div>\n\n <TypeDistributionBar\n counts={counts}\n types={RESOURCE_TYPES}\n total={total}\n />\n\n <Text\n variant=\"body-small\"\n weight=\"bold\"\n as=\"h3\"\n className={styles.subsectionHeading}\n >\n By lifecycle\n </Text>\n <div className={styles.grid}>\n {lifecycleCounts.map(([lifecycle, count], index) => {\n const entrance = rowEntrance(playEntrance, index);\n const accent = LIFECYCLE_ACCENT[lifecycle];\n const percent = total > 0 ? Math.round((count / total) * 100) : 0;\n return (\n <div\n key={lifecycle}\n className={\n entrance.className\n ? `${styles.countCard} ${entrance.className}`\n : styles.countCard\n }\n style={\n {\n ...entrance.style,\n ...(accent\n ? {\n '--card-accent': accent.accent,\n '--card-accent-bg': accent.accentBg,\n }\n : {}),\n '--share': `${percent}%`,\n } as React.CSSProperties\n }\n >\n <div className={styles.countCardLabel}>\n <span className={styles.countCardIcon}>\n <RiGitBranchLine size={16} />\n </span>\n <Text\n variant=\"body-small\"\n color=\"secondary\"\n className={styles.cardLabel}\n >\n {capitalize(lifecycle)}\n </Text>\n </div>\n <span className={styles.countValue}>{count}</span>\n <span className={styles.shareBar} aria-hidden>\n <span className={styles.shareBarFill} />\n </span>\n </div>\n );\n })}\n </div>\n\n <Text\n variant=\"body-small\"\n weight=\"bold\"\n as=\"h3\"\n className={styles.subsectionHeading}\n >\n By owner\n </Text>\n <ul className={styles.topList}>\n {ownerCounts\n .slice(0, TOP_OWNERS_LIMIT)\n .map(([owner, count], index) => {\n const entrance = rowEntrance(playEntrance, index);\n const percent = total > 0 ? Math.round((count / total) * 100) : 0;\n const palette =\n owner === NO_OWNER_LABEL\n ? undefined\n : OWNER_PALETTE[index % OWNER_PALETTE.length];\n return (\n <li\n key={owner}\n className={\n entrance.className\n ? `${styles.barRow} ${entrance.className}`\n : styles.barRow\n }\n style={\n {\n ...entrance.style,\n ...(palette\n ? {\n '--card-accent': palette.accent,\n '--card-accent-bg': palette.accentBg,\n }\n : {}),\n '--share': `${percent}%`,\n } as React.CSSProperties\n }\n >\n <div className={styles.barRowMain}>\n <div className={styles.barRowLeft}>\n <span className={styles.ownerAvatar} aria-hidden>\n {ownerInitials(owner)}\n </span>\n <Text variant=\"body-small\">{owner}</Text>\n </div>\n <span className={styles.topListCount}>{count}</span>\n </div>\n <span className={styles.shareBar} aria-hidden>\n <span className={styles.shareBarFill} />\n </span>\n </li>\n );\n })}\n </ul>\n {ownerCounts.length > TOP_OWNERS_LIMIT && (\n <Text variant=\"body-small\" color=\"secondary\">\n +{ownerCounts.length - TOP_OWNERS_LIMIT} more{' '}\n {ownerCounts.length - TOP_OWNERS_LIMIT === 1 ? 'owner' : 'owners'}\n </Text>\n )}\n\n <Text\n variant=\"body-small\"\n weight=\"bold\"\n as=\"h3\"\n className={styles.subsectionHeading}\n >\n By framework\n </Text>\n <div className={styles.grid}>\n {frameworkCounts.map(([framework, count], index) => {\n const entrance = rowEntrance(playEntrance, index);\n const percent =\n total > 0 ? Math.min(100, Math.round((count / total) * 100)) : 0;\n return (\n <div\n key={framework}\n className={\n entrance.className\n ? `${styles.countCard} ${entrance.className}`\n : styles.countCard\n }\n style={\n {\n ...entrance.style,\n '--card-accent': 'var(--bui-fg-secondary)',\n '--share': `${percent}%`,\n } as React.CSSProperties\n }\n >\n <div className={styles.countCardLabel}>\n <span className={styles.countCardIcon}>\n <ToolIcon tool={framework} size={16} />\n </span>\n <Text\n variant=\"body-small\"\n color=\"secondary\"\n className={styles.cardLabel}\n >\n {frameworkLabel(framework)}\n </Text>\n </div>\n <span className={styles.countValue}>{count}</span>\n <span className={styles.shareBar} aria-hidden>\n <span className={styles.shareBarFill} />\n </span>\n </div>\n );\n })}\n </div>\n\n <Text\n variant=\"body-small\"\n weight=\"bold\"\n as=\"h3\"\n className={styles.subsectionHeading}\n >\n Catalog health\n </Text>\n <ul className={styles.healthList}>\n {healthChecks.map(({ ok, text }) => (\n <li\n key={text}\n className={`${styles.healthCard} ${\n ok ? styles.healthCardOk : styles.healthCardWarning\n }`}\n >\n <span className={styles.healthCardIcon}>\n {ok ? (\n <RiCheckboxCircleLine size={16} />\n ) : (\n <RiErrorWarningLine size={16} />\n )}\n </span>\n <Text variant=\"body-small\">{text}</Text>\n </li>\n ))}\n </ul>\n </BackofficeSection>\n\n <BackofficeSection\n title=\"Usage\"\n description=\"Lifetime install, view, copy and download counts across the catalog (ADR-0007).\"\n loading={usageLoading}\n error={usageError}\n isEmpty={usageIsEmpty}\n emptyMessage=\"No activity has been recorded yet.\"\n >\n {usage && (\n <>\n <div className={styles.grid}>\n {USAGE_STATS.map(\n ({ action, label, Icon, accent, accentBg }, index) => {\n const entrance = rowEntrance(playEntrance, index);\n return (\n <div\n key={action}\n className={\n entrance.className\n ? `${styles.countCard} ${entrance.className}`\n : styles.countCard\n }\n style={\n {\n ...entrance.style,\n '--card-accent': accent,\n '--card-accent-bg': accentBg,\n } as React.CSSProperties\n }\n >\n <div className={styles.countCardLabel}>\n <span className={styles.countCardIcon}>\n <Icon size={16} />\n </span>\n <Text\n variant=\"body-small\"\n color=\"secondary\"\n className={styles.cardLabel}\n >\n {label}\n </Text>\n </div>\n <span className={styles.countValue}>\n {usage.totals[action]}\n </span>\n </div>\n );\n },\n )}\n </div>\n\n {usage.topInstalled.length > 0 && (\n <>\n <Text\n variant=\"body-small\"\n weight=\"bold\"\n as=\"h3\"\n className={styles.subsectionHeading}\n >\n Most installed\n </Text>\n <ol className={styles.topList}>\n {usage.topInstalled.map(({ resource, count }, index) => {\n const entrance = rowEntrance(playEntrance, index);\n const leader = usage.topInstalled[0].count;\n const percent =\n leader > 0 ? Math.round((count / leader) * 100) : 0;\n return (\n <li\n key={resource.entityRef}\n className={\n entrance.className\n ? `${styles.barRow} ${entrance.className}`\n : styles.barRow\n }\n style={\n {\n ...entrance.style,\n '--card-accent': 'var(--devaihub-type-plugin)',\n '--share': `${percent}%`,\n } as React.CSSProperties\n }\n >\n <div className={styles.barRowMain}>\n <div className={styles.barRowLeft}>\n <span\n className={\n index === 0\n ? `${styles.rankBadge} ${styles.rankBadgeLead}`\n : styles.rankBadge\n }\n >\n {index + 1}\n </span>\n <Text variant=\"body-small\">\n {resource.title ?? resource.name}\n </Text>\n </div>\n <span className={styles.topListCount}>{count}</span>\n </div>\n <span className={styles.shareBar} aria-hidden>\n <span className={styles.shareBarFill} />\n </span>\n </li>\n );\n })}\n </ol>\n </>\n )}\n </>\n )}\n </BackofficeSection>\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { Text, Skeleton } from '@backstage/ui';
|
|
3
|
+
import styles from './Backoffice.module.css.esm.js';
|
|
4
|
+
|
|
5
|
+
function BackofficeSection(props) {
|
|
6
|
+
const {
|
|
7
|
+
title,
|
|
8
|
+
description,
|
|
9
|
+
loading,
|
|
10
|
+
error,
|
|
11
|
+
isEmpty,
|
|
12
|
+
emptyMessage = "Nothing to show yet.",
|
|
13
|
+
children
|
|
14
|
+
} = props;
|
|
15
|
+
return /* @__PURE__ */ jsxs("section", { className: styles.section, children: [
|
|
16
|
+
/* @__PURE__ */ jsxs("header", { className: styles.sectionHeader, children: [
|
|
17
|
+
/* @__PURE__ */ jsx(Text, { variant: "title-small", weight: "bold", as: "h2", children: title }),
|
|
18
|
+
description && /* @__PURE__ */ jsx(Text, { variant: "body-small", color: "secondary", as: "p", children: description })
|
|
19
|
+
] }),
|
|
20
|
+
loading && /* @__PURE__ */ jsx("div", { className: styles.grid, "data-testid": "backoffice-section-loading", children: Array.from({ length: 4 }).map((_, i) => /* @__PURE__ */ jsx(
|
|
21
|
+
Skeleton,
|
|
22
|
+
{
|
|
23
|
+
style: { height: 88, borderRadius: "var(--bui-radius-2)" }
|
|
24
|
+
},
|
|
25
|
+
i
|
|
26
|
+
)) }),
|
|
27
|
+
!loading && error && /* @__PURE__ */ jsxs("div", { className: styles.stateBlock, role: "alert", children: [
|
|
28
|
+
/* @__PURE__ */ jsx(Text, { variant: "body-medium", weight: "bold", as: "p", children: "Could not load this section" }),
|
|
29
|
+
/* @__PURE__ */ jsx(Text, { variant: "body-small", color: "secondary", as: "p", children: error.message })
|
|
30
|
+
] }),
|
|
31
|
+
!loading && !error && isEmpty && /* @__PURE__ */ jsx("div", { className: styles.stateBlock, children: /* @__PURE__ */ jsx(Text, { variant: "body-small", color: "secondary", as: "p", children: emptyMessage }) }),
|
|
32
|
+
!loading && !error && !isEmpty && children
|
|
33
|
+
] });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export { BackofficeSection };
|
|
37
|
+
//# sourceMappingURL=BackofficeSection.esm.js.map
|