@kuadrant/kuadrant-backstage-plugin-frontend 0.0.2-dev-dfeb437 → 0.0.2-dev-415994c
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/assets/empty-state-illustration.png +0 -0
- package/dist/components/ApiKeyManagementTab/ApiKeyManagementTab.esm.js +7 -53
- package/dist/components/ApiKeyManagementTab/ApiKeyManagementTab.esm.js.map +1 -1
- package/dist/components/ApiProductDetailPage/ApiProductDetailPage.esm.js +248 -0
- package/dist/components/ApiProductDetailPage/ApiProductDetailPage.esm.js.map +1 -0
- package/dist/components/ApiProductDetailPage/index.esm.js +2 -0
- package/dist/components/ApiProductDetailPage/index.esm.js.map +1 -0
- package/dist/components/ApiProductDetails/ApiProductDetails.esm.js +86 -0
- package/dist/components/ApiProductDetails/ApiProductDetails.esm.js.map +1 -0
- package/dist/components/ApiProductInfoCard/ApiProductInfoCard.esm.js +24 -43
- package/dist/components/ApiProductInfoCard/ApiProductInfoCard.esm.js.map +1 -1
- package/dist/components/ApprovalQueueTable/ApprovalQueueTable.esm.js +2 -1
- package/dist/components/ApprovalQueueTable/ApprovalQueueTable.esm.js.map +1 -1
- package/dist/components/CreateAPIProductDialog/CreateAPIProductDialog.esm.js +148 -134
- package/dist/components/CreateAPIProductDialog/CreateAPIProductDialog.esm.js.map +1 -1
- package/dist/components/EditAPIProductDialog/EditAPIProductDialog.esm.js +104 -128
- package/dist/components/EditAPIProductDialog/EditAPIProductDialog.esm.js.map +1 -1
- package/dist/components/KuadrantPage/KuadrantPage.esm.js +328 -125
- package/dist/components/KuadrantPage/KuadrantPage.esm.js.map +1 -1
- package/dist/components/MyApiKeysTable/MyApiKeysTable.esm.js +2 -1
- package/dist/components/MyApiKeysTable/MyApiKeysTable.esm.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.esm.js +1 -1
- package/dist/plugin.esm.js +8 -1
- package/dist/plugin.esm.js.map +1 -1
- package/dist/utils/codeSnippets.esm.js +257 -0
- package/dist/utils/codeSnippets.esm.js.map +1 -0
- package/dist/utils/validation.esm.js +1 -11
- package/dist/utils/validation.esm.js.map +1 -1
- package/dist-scalprum/{internal.plugin-kuadrant.f72b5e5792b0a8c4239f.js → internal.plugin-kuadrant.47663f119ccb78d7ec47.js} +2 -2
- package/dist-scalprum/internal.plugin-kuadrant.47663f119ccb78d7ec47.js.map +1 -0
- package/dist-scalprum/plugin-manifest.json +2 -2
- package/dist-scalprum/static/2759.fceb317f.chunk.js +2 -0
- package/dist-scalprum/static/2759.fceb317f.chunk.js.map +1 -0
- package/dist-scalprum/static/2928.4303c12e.chunk.js +3 -0
- package/dist-scalprum/static/2928.4303c12e.chunk.js.map +1 -0
- package/dist-scalprum/static/2967.ac3a4bee.chunk.js +2 -0
- package/dist-scalprum/static/2967.ac3a4bee.chunk.js.map +1 -0
- package/dist-scalprum/static/{6979.9699b350.chunk.js → 2987.1da15560.chunk.js} +2 -2
- package/dist-scalprum/static/2987.1da15560.chunk.js.map +1 -0
- package/dist-scalprum/static/3459.5c90b5a3.chunk.js +2 -0
- package/dist-scalprum/static/3459.5c90b5a3.chunk.js.map +1 -0
- package/dist-scalprum/static/3503.66b6e510.chunk.js +2 -0
- package/dist-scalprum/static/3503.66b6e510.chunk.js.map +1 -0
- package/dist-scalprum/static/3650.aa8552f3.chunk.js +2 -0
- package/dist-scalprum/static/3650.aa8552f3.chunk.js.map +1 -0
- package/dist-scalprum/static/4682.9ee4e285.chunk.js +2 -0
- package/dist-scalprum/static/4682.9ee4e285.chunk.js.map +1 -0
- package/dist-scalprum/static/5010.a4aa0f8e.chunk.js +3 -0
- package/dist-scalprum/static/5010.a4aa0f8e.chunk.js.map +1 -0
- package/dist-scalprum/static/5453.280127dd.chunk.js +2 -0
- package/dist-scalprum/static/5453.280127dd.chunk.js.map +1 -0
- package/dist-scalprum/static/6422.97baf774.chunk.js +2 -0
- package/dist-scalprum/static/6422.97baf774.chunk.js.map +1 -0
- package/dist-scalprum/static/7791.12162a71.chunk.js +2 -0
- package/dist-scalprum/static/7791.12162a71.chunk.js.map +1 -0
- package/dist-scalprum/static/8799.7c749838.chunk.js +2 -0
- package/dist-scalprum/static/8799.7c749838.chunk.js.map +1 -0
- package/dist-scalprum/static/empty-state-illustration.7e3ad5a9..png +0 -0
- package/dist-scalprum/static/exposed-PluginRoot.a5792fb2.chunk.js +2 -0
- package/dist-scalprum/static/exposed-PluginRoot.a5792fb2.chunk.js.map +1 -0
- package/package.json +1 -1
- package/dist-scalprum/internal.plugin-kuadrant.f72b5e5792b0a8c4239f.js.map +0 -1
- package/dist-scalprum/static/2120.44884438.chunk.js +0 -3
- package/dist-scalprum/static/2120.44884438.chunk.js.map +0 -1
- package/dist-scalprum/static/2967.c684efaf.chunk.js +0 -2
- package/dist-scalprum/static/2967.c684efaf.chunk.js.map +0 -1
- package/dist-scalprum/static/4306.3a218ff1.chunk.js +0 -2
- package/dist-scalprum/static/4306.3a218ff1.chunk.js.map +0 -1
- package/dist-scalprum/static/5010.acf9a415.chunk.js +0 -3
- package/dist-scalprum/static/5010.acf9a415.chunk.js.map +0 -1
- package/dist-scalprum/static/5453.c1f90bdf.chunk.js +0 -2
- package/dist-scalprum/static/5453.c1f90bdf.chunk.js.map +0 -1
- package/dist-scalprum/static/6813.036a322f.chunk.js +0 -2
- package/dist-scalprum/static/6813.036a322f.chunk.js.map +0 -1
- package/dist-scalprum/static/6979.9699b350.chunk.js.map +0 -1
- package/dist-scalprum/static/7684.3d1fc1a1.chunk.js +0 -2
- package/dist-scalprum/static/7684.3d1fc1a1.chunk.js.map +0 -1
- package/dist-scalprum/static/8416.3604a311.chunk.js +0 -2
- package/dist-scalprum/static/8416.3604a311.chunk.js.map +0 -1
- package/dist-scalprum/static/exposed-PluginRoot.bba1f323.chunk.js +0 -2
- package/dist-scalprum/static/exposed-PluginRoot.bba1f323.chunk.js.map +0 -1
- /package/dist-scalprum/static/{2120.44884438.chunk.js.LICENSE.txt → 2928.4303c12e.chunk.js.LICENSE.txt} +0 -0
- /package/dist-scalprum/static/{5010.acf9a415.chunk.js.LICENSE.txt → 5010.a4aa0f8e.chunk.js.LICENSE.txt} +0 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import { Box,
|
|
1
|
+
import React, { useState, useCallback, useMemo } from 'react';
|
|
2
|
+
import { makeStyles, Box, CircularProgress, Typography, Button, Chip, IconButton } from '@material-ui/core';
|
|
3
3
|
import AddIcon from '@material-ui/icons/Add';
|
|
4
4
|
import DeleteIcon from '@material-ui/icons/Delete';
|
|
5
5
|
import EditIcon from '@material-ui/icons/Edit';
|
|
6
6
|
import VpnKeyIcon from '@material-ui/icons/VpnKey';
|
|
7
7
|
import LockIcon from '@material-ui/icons/Lock';
|
|
8
|
-
import {
|
|
8
|
+
import { FilterPanel } from '../FilterPanel/FilterPanel.esm.js';
|
|
9
|
+
import { Page, Header, SupportButton, Content, ResponseErrorPanel, Table, Link } from '@backstage/core-components';
|
|
9
10
|
import useAsync from 'react-use/lib/useAsync';
|
|
10
11
|
import { useApi, configApiRef, fetchApiRef, alertApiRef, identityApiRef } from '@backstage/core-plugin-api';
|
|
11
12
|
import { PermissionGate } from '../PermissionGate/PermissionGate.esm.js';
|
|
@@ -14,8 +15,49 @@ import { kuadrantApiProductListPermission, kuadrantApiProductCreatePermission, k
|
|
|
14
15
|
import { useKuadrantPermission } from '../../utils/permissions.esm.js';
|
|
15
16
|
import { EditAPIProductDialog } from '../EditAPIProductDialog/EditAPIProductDialog.esm.js';
|
|
16
17
|
import { ConfirmDeleteDialog } from '../ConfirmDeleteDialog/ConfirmDeleteDialog.esm.js';
|
|
18
|
+
import emptyStateIllustration from '../../assets/empty-state-illustration.png';
|
|
17
19
|
|
|
20
|
+
const useStyles = makeStyles((theme) => ({
|
|
21
|
+
container: {
|
|
22
|
+
display: "flex",
|
|
23
|
+
height: "100%",
|
|
24
|
+
minHeight: 400
|
|
25
|
+
},
|
|
26
|
+
tableContainer: {
|
|
27
|
+
flex: 1,
|
|
28
|
+
overflow: "auto",
|
|
29
|
+
padding: 10
|
|
30
|
+
},
|
|
31
|
+
emptyState: {
|
|
32
|
+
display: "flex",
|
|
33
|
+
alignItems: "center",
|
|
34
|
+
justifyContent: "center",
|
|
35
|
+
padding: theme.spacing(6),
|
|
36
|
+
minHeight: 400
|
|
37
|
+
},
|
|
38
|
+
emptyStateContent: {
|
|
39
|
+
display: "flex",
|
|
40
|
+
alignItems: "center",
|
|
41
|
+
gap: theme.spacing(6),
|
|
42
|
+
maxWidth: 900
|
|
43
|
+
},
|
|
44
|
+
emptyStateText: {
|
|
45
|
+
flex: 1
|
|
46
|
+
},
|
|
47
|
+
emptyStateTitle: {
|
|
48
|
+
marginBottom: theme.spacing(2)
|
|
49
|
+
},
|
|
50
|
+
emptyStateDescription: {
|
|
51
|
+
marginBottom: theme.spacing(3),
|
|
52
|
+
color: theme.palette.text.secondary
|
|
53
|
+
},
|
|
54
|
+
emptyStateImage: {
|
|
55
|
+
maxWidth: 400,
|
|
56
|
+
height: "auto"
|
|
57
|
+
}
|
|
58
|
+
}));
|
|
18
59
|
const ResourceList = () => {
|
|
60
|
+
const classes = useStyles();
|
|
19
61
|
const config = useApi(configApiRef);
|
|
20
62
|
const fetchApi = useApi(fetchApiRef);
|
|
21
63
|
const alertApi = useApi(alertApiRef);
|
|
@@ -30,6 +72,14 @@ const ResourceList = () => {
|
|
|
30
72
|
const [apiProductToEdit, setApiProductToEdit] = useState(null);
|
|
31
73
|
const [deleting, setDeleting] = useState(false);
|
|
32
74
|
const [deleteStats, setDeleteStats] = useState(null);
|
|
75
|
+
const [filters, setFilters] = useState({
|
|
76
|
+
status: [],
|
|
77
|
+
policy: [],
|
|
78
|
+
route: [],
|
|
79
|
+
namespace: [],
|
|
80
|
+
tags: [],
|
|
81
|
+
authentication: []
|
|
82
|
+
});
|
|
33
83
|
const {
|
|
34
84
|
allowed: canCreateApiProduct,
|
|
35
85
|
loading: createPermissionLoading,
|
|
@@ -52,7 +102,6 @@ const ResourceList = () => {
|
|
|
52
102
|
);
|
|
53
103
|
const deletePermissionLoading = deleteOwnPermissionLoading || deleteAllPermissionLoading;
|
|
54
104
|
const {
|
|
55
|
-
allowed: canListPlanPolicies,
|
|
56
105
|
loading: planPolicyPermissionLoading,
|
|
57
106
|
error: planPolicyPermissionError
|
|
58
107
|
} = useKuadrantPermission(kuadrantPlanPolicyListPermission);
|
|
@@ -80,13 +129,154 @@ const ResourceList = () => {
|
|
|
80
129
|
);
|
|
81
130
|
return await response.json();
|
|
82
131
|
}, [backendUrl, fetchApi, refreshTrigger]);
|
|
132
|
+
const getPolicyForProduct = useCallback((product) => {
|
|
133
|
+
if (!planPolicies?.items) return null;
|
|
134
|
+
const targetRef = product.spec?.targetRef;
|
|
135
|
+
if (!targetRef) return null;
|
|
136
|
+
const policy = planPolicies.items.find((pp) => {
|
|
137
|
+
const ref = pp.targetRef;
|
|
138
|
+
return ref?.kind === "HTTPRoute" && ref?.name === targetRef.name && (!ref?.namespace || ref?.namespace === (targetRef.namespace || product.metadata.namespace));
|
|
139
|
+
});
|
|
140
|
+
return policy?.metadata.name || null;
|
|
141
|
+
}, [planPolicies]);
|
|
142
|
+
const getAuthSchemes = useCallback((product) => {
|
|
143
|
+
const authSchemes = product.status?.discoveredAuthScheme?.authentication || {};
|
|
144
|
+
const schemeObjects = Object.values(authSchemes);
|
|
145
|
+
const schemes = [];
|
|
146
|
+
if (schemeObjects.some((scheme) => scheme.hasOwnProperty("apiKey"))) {
|
|
147
|
+
schemes.push("API Key");
|
|
148
|
+
}
|
|
149
|
+
if (schemeObjects.some((scheme) => scheme.hasOwnProperty("jwt"))) {
|
|
150
|
+
schemes.push("OIDC");
|
|
151
|
+
}
|
|
152
|
+
if (schemes.length === 0) {
|
|
153
|
+
schemes.push("Unknown");
|
|
154
|
+
}
|
|
155
|
+
return schemes;
|
|
156
|
+
}, []);
|
|
83
157
|
const loading = apiProductsLoading || planPoliciesLoading || createPermissionLoading || deletePermissionLoading || planPolicyPermissionLoading;
|
|
84
158
|
const error = apiProductsError || planPoliciesError;
|
|
85
159
|
const permissionError = createPermissionError || deletePermissionError || planPolicyPermissionError;
|
|
86
|
-
const
|
|
160
|
+
const allProducts = useMemo(() => {
|
|
161
|
+
return apiProducts?.items || [];
|
|
162
|
+
}, [apiProducts]);
|
|
163
|
+
const filterSections = useMemo(() => {
|
|
164
|
+
const statusCounts = { Draft: 0, Published: 0 };
|
|
165
|
+
const policyCounts = /* @__PURE__ */ new Map();
|
|
166
|
+
const routeCounts = /* @__PURE__ */ new Map();
|
|
167
|
+
const namespaceCounts = /* @__PURE__ */ new Map();
|
|
168
|
+
const tagCounts = /* @__PURE__ */ new Map();
|
|
169
|
+
const authCounts = /* @__PURE__ */ new Map();
|
|
170
|
+
allProducts.forEach((p) => {
|
|
171
|
+
const status = p.spec?.publishStatus || "Draft";
|
|
172
|
+
statusCounts[status]++;
|
|
173
|
+
const policy = getPolicyForProduct(p) || "N/A";
|
|
174
|
+
policyCounts.set(policy, (policyCounts.get(policy) || 0) + 1);
|
|
175
|
+
const route = p.spec?.targetRef?.name || "unknown";
|
|
176
|
+
routeCounts.set(route, (routeCounts.get(route) || 0) + 1);
|
|
177
|
+
const ns = p.metadata.namespace;
|
|
178
|
+
namespaceCounts.set(ns, (namespaceCounts.get(ns) || 0) + 1);
|
|
179
|
+
const tags = p.spec?.tags || [];
|
|
180
|
+
tags.forEach((tag) => {
|
|
181
|
+
tagCounts.set(tag, (tagCounts.get(tag) || 0) + 1);
|
|
182
|
+
});
|
|
183
|
+
const authSchemes = getAuthSchemes(p);
|
|
184
|
+
authSchemes.forEach((scheme) => {
|
|
185
|
+
authCounts.set(scheme, (authCounts.get(scheme) || 0) + 1);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
return [
|
|
189
|
+
{
|
|
190
|
+
id: "status",
|
|
191
|
+
title: "Status",
|
|
192
|
+
options: [
|
|
193
|
+
{ value: "Draft", label: "Draft", count: statusCounts.Draft },
|
|
194
|
+
{ value: "Published", label: "Published", count: statusCounts.Published }
|
|
195
|
+
]
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
id: "authentication",
|
|
199
|
+
title: "Authentication",
|
|
200
|
+
options: Array.from(authCounts.entries()).map(([scheme, count]) => ({
|
|
201
|
+
value: scheme,
|
|
202
|
+
label: scheme,
|
|
203
|
+
count
|
|
204
|
+
}))
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
id: "policy",
|
|
208
|
+
title: "Policy",
|
|
209
|
+
options: Array.from(policyCounts.entries()).map(([name, count]) => ({
|
|
210
|
+
value: name,
|
|
211
|
+
label: name,
|
|
212
|
+
count
|
|
213
|
+
})),
|
|
214
|
+
collapsed: policyCounts.size > 5
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
id: "route",
|
|
218
|
+
title: "Route",
|
|
219
|
+
options: Array.from(routeCounts.entries()).map(([name, count]) => ({
|
|
220
|
+
value: name,
|
|
221
|
+
label: name,
|
|
222
|
+
count
|
|
223
|
+
})),
|
|
224
|
+
collapsed: routeCounts.size > 5
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
id: "namespace",
|
|
228
|
+
title: "Namespace",
|
|
229
|
+
options: Array.from(namespaceCounts.entries()).map(([ns, count]) => ({
|
|
230
|
+
value: ns,
|
|
231
|
+
label: ns,
|
|
232
|
+
count
|
|
233
|
+
})),
|
|
234
|
+
collapsed: namespaceCounts.size > 5
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
id: "tags",
|
|
238
|
+
title: "Tags",
|
|
239
|
+
options: Array.from(tagCounts.entries()).map(([tag, count]) => ({
|
|
240
|
+
value: tag,
|
|
241
|
+
label: tag,
|
|
242
|
+
count
|
|
243
|
+
})),
|
|
244
|
+
collapsed: tagCounts.size > 5
|
|
245
|
+
}
|
|
246
|
+
];
|
|
247
|
+
}, [allProducts, getPolicyForProduct, getAuthSchemes]);
|
|
248
|
+
const filteredProducts = useMemo(() => {
|
|
249
|
+
return allProducts.filter((p) => {
|
|
250
|
+
if (filters.status.length > 0) {
|
|
251
|
+
const status = p.spec?.publishStatus || "Draft";
|
|
252
|
+
if (!filters.status.includes(status)) return false;
|
|
253
|
+
}
|
|
254
|
+
if (filters.authentication.length > 0) {
|
|
255
|
+
const authSchemes = getAuthSchemes(p);
|
|
256
|
+
if (!filters.authentication.some((a) => authSchemes.includes(a))) return false;
|
|
257
|
+
}
|
|
258
|
+
if (filters.policy.length > 0) {
|
|
259
|
+
const policy = getPolicyForProduct(p) || "N/A";
|
|
260
|
+
if (!filters.policy.includes(policy)) return false;
|
|
261
|
+
}
|
|
262
|
+
if (filters.route.length > 0) {
|
|
263
|
+
const route = p.spec?.targetRef?.name || "unknown";
|
|
264
|
+
if (!filters.route.includes(route)) return false;
|
|
265
|
+
}
|
|
266
|
+
if (filters.namespace.length > 0) {
|
|
267
|
+
if (!filters.namespace.includes(p.metadata.namespace)) return false;
|
|
268
|
+
}
|
|
269
|
+
if (filters.tags.length > 0) {
|
|
270
|
+
const tags = p.spec?.tags || [];
|
|
271
|
+
if (!filters.tags.some((t) => tags.includes(t))) return false;
|
|
272
|
+
}
|
|
273
|
+
return true;
|
|
274
|
+
});
|
|
275
|
+
}, [allProducts, filters, getPolicyForProduct, getAuthSchemes]);
|
|
276
|
+
const handleCreateSuccess = (productInfo) => {
|
|
87
277
|
setRefreshTrigger((prev) => prev + 1);
|
|
88
278
|
alertApi.post({
|
|
89
|
-
message: "
|
|
279
|
+
message: `"${productInfo.displayName}" created successfully`,
|
|
90
280
|
severity: "success",
|
|
91
281
|
display: "transient"
|
|
92
282
|
});
|
|
@@ -97,8 +287,9 @@ const ResourceList = () => {
|
|
|
97
287
|
};
|
|
98
288
|
const handleEditSuccess = () => {
|
|
99
289
|
setRefreshTrigger((prev) => prev + 1);
|
|
290
|
+
const productName = apiProductToEdit?.name || "API Product";
|
|
100
291
|
alertApi.post({
|
|
101
|
-
message: "
|
|
292
|
+
message: `"${productName}" updated successfully`,
|
|
102
293
|
severity: "success",
|
|
103
294
|
display: "transient"
|
|
104
295
|
});
|
|
@@ -136,9 +327,10 @@ const ResourceList = () => {
|
|
|
136
327
|
if (!response.ok) {
|
|
137
328
|
throw new Error("failed to delete apiproduct");
|
|
138
329
|
}
|
|
330
|
+
const deletedName = apiProductToDelete?.name || "API Product";
|
|
139
331
|
setRefreshTrigger((prev) => prev + 1);
|
|
140
332
|
alertApi.post({
|
|
141
|
-
message: "
|
|
333
|
+
message: `"${deletedName}" deleted successfully`,
|
|
142
334
|
severity: "success",
|
|
143
335
|
display: "transient"
|
|
144
336
|
});
|
|
@@ -159,72 +351,89 @@ const ResourceList = () => {
|
|
|
159
351
|
setDeleteDialogOpen(false);
|
|
160
352
|
setApiProductToDelete(null);
|
|
161
353
|
};
|
|
162
|
-
const
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
354
|
+
const handlePublishToggle = async (row) => {
|
|
355
|
+
const namespace = row.metadata.namespace;
|
|
356
|
+
const name = row.metadata.name;
|
|
357
|
+
const displayName = row.spec?.displayName || name;
|
|
358
|
+
const currentStatus = row.spec?.publishStatus || "Draft";
|
|
359
|
+
const newStatus = currentStatus === "Published" ? "Draft" : "Published";
|
|
360
|
+
try {
|
|
361
|
+
const response = await fetchApi.fetch(
|
|
362
|
+
`${backendUrl}/api/kuadrant/apiproducts/${namespace}/${name}`,
|
|
363
|
+
{
|
|
364
|
+
method: "PATCH",
|
|
365
|
+
headers: { "Content-Type": "application/json" },
|
|
366
|
+
body: JSON.stringify({
|
|
367
|
+
spec: { publishStatus: newStatus }
|
|
368
|
+
})
|
|
369
|
+
}
|
|
370
|
+
);
|
|
371
|
+
if (!response.ok) {
|
|
372
|
+
throw new Error("failed to update publish status");
|
|
373
|
+
}
|
|
374
|
+
setRefreshTrigger((prev) => prev + 1);
|
|
375
|
+
alertApi.post({
|
|
376
|
+
message: `"${displayName}" ${newStatus === "Published" ? "published" : "unpublished"} successfully`,
|
|
377
|
+
severity: "success",
|
|
378
|
+
display: "transient"
|
|
379
|
+
});
|
|
380
|
+
} catch (err) {
|
|
381
|
+
console.error("error updating publish status:", err);
|
|
382
|
+
alertApi.post({
|
|
383
|
+
message: "Failed to update publish status",
|
|
384
|
+
severity: "error",
|
|
385
|
+
display: "transient"
|
|
386
|
+
});
|
|
387
|
+
}
|
|
169
388
|
};
|
|
170
389
|
const columns = [
|
|
171
390
|
{
|
|
172
391
|
title: "Name",
|
|
173
392
|
field: "spec.displayName",
|
|
174
393
|
render: (row) => {
|
|
175
|
-
const publishStatus = row.spec?.publishStatus;
|
|
176
|
-
const isPublished = publishStatus === "Published";
|
|
177
394
|
const displayName = row.spec?.displayName ?? row.metadata.name;
|
|
178
|
-
|
|
179
|
-
return /* @__PURE__ */ React.createElement(Link, { to: `/catalog/default/api/${row.metadata.name}/api-product` }, /* @__PURE__ */ React.createElement("strong", null, displayName));
|
|
180
|
-
}
|
|
181
|
-
return /* @__PURE__ */ React.createElement("span", { className: "text-muted" }, /* @__PURE__ */ React.createElement("strong", null, displayName));
|
|
395
|
+
return /* @__PURE__ */ React.createElement(Link, { to: `/kuadrant/api-products/${row.metadata.namespace}/${row.metadata.name}` }, /* @__PURE__ */ React.createElement("strong", null, displayName));
|
|
182
396
|
},
|
|
183
397
|
customFilterAndSearch: (term, row) => {
|
|
184
398
|
const displayName = row.spec?.displayName || row.metadata.name || "";
|
|
185
399
|
return displayName.toLowerCase().includes(term.toLowerCase());
|
|
186
400
|
}
|
|
187
401
|
},
|
|
188
|
-
{
|
|
189
|
-
title: "Resource Name",
|
|
190
|
-
field: "metadata.name"
|
|
191
|
-
},
|
|
192
402
|
{
|
|
193
403
|
title: "Version",
|
|
194
404
|
field: "spec.version",
|
|
195
405
|
render: (row) => row.spec?.version || "-"
|
|
196
406
|
},
|
|
197
407
|
{
|
|
198
|
-
title: "
|
|
408
|
+
title: "Route",
|
|
199
409
|
field: "spec.targetRef.name",
|
|
200
410
|
render: (row) => row.spec?.targetRef?.name || "-"
|
|
201
411
|
},
|
|
202
412
|
{
|
|
203
|
-
title: "
|
|
204
|
-
field: "
|
|
413
|
+
title: "Policy",
|
|
414
|
+
field: "policy",
|
|
415
|
+
render: (row) => getPolicyForProduct(row) || "N/A"
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
title: "Tags",
|
|
419
|
+
field: "spec.tags",
|
|
205
420
|
render: (row) => {
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
{
|
|
210
|
-
label: status,
|
|
211
|
-
size: "small",
|
|
212
|
-
color: status === "Published" ? "primary" : "default"
|
|
213
|
-
}
|
|
214
|
-
);
|
|
421
|
+
const tags = row.spec?.tags || [];
|
|
422
|
+
if (tags.length === 0) return "-";
|
|
423
|
+
return /* @__PURE__ */ React.createElement(Box, { display: "flex", style: { gap: 4, flexWrap: "wrap" } }, tags.map((tag) => /* @__PURE__ */ React.createElement(Chip, { key: tag, label: tag, size: "small", variant: "outlined" })));
|
|
215
424
|
}
|
|
216
425
|
},
|
|
217
426
|
{
|
|
218
|
-
title: "
|
|
219
|
-
field: "spec.
|
|
427
|
+
title: "Status",
|
|
428
|
+
field: "spec.publishStatus",
|
|
220
429
|
render: (row) => {
|
|
221
|
-
const
|
|
430
|
+
const status = row.spec?.publishStatus || "Draft";
|
|
222
431
|
return /* @__PURE__ */ React.createElement(
|
|
223
432
|
Chip,
|
|
224
433
|
{
|
|
225
|
-
label:
|
|
434
|
+
label: status,
|
|
226
435
|
size: "small",
|
|
227
|
-
color:
|
|
436
|
+
color: status === "Published" ? "primary" : "default"
|
|
228
437
|
}
|
|
229
438
|
);
|
|
230
439
|
}
|
|
@@ -267,11 +476,6 @@ const ResourceList = () => {
|
|
|
267
476
|
title: "Namespace",
|
|
268
477
|
field: "metadata.namespace"
|
|
269
478
|
},
|
|
270
|
-
{
|
|
271
|
-
title: "Created",
|
|
272
|
-
field: "metadata.creationTimestamp",
|
|
273
|
-
render: (row) => formatDate(row.metadata.creationTimestamp)
|
|
274
|
-
},
|
|
275
479
|
{
|
|
276
480
|
title: "Actions",
|
|
277
481
|
field: "actions",
|
|
@@ -281,8 +485,17 @@ const ResourceList = () => {
|
|
|
281
485
|
const isOwner = owner === userEntityRef;
|
|
282
486
|
const canEdit = canUpdateAllApiProducts || canUpdateOwnApiProduct && isOwner;
|
|
283
487
|
const canDelete = canDeleteAllApiProducts || canDeleteOwnApiProduct && isOwner;
|
|
284
|
-
|
|
285
|
-
return /* @__PURE__ */ React.createElement(Box, { display: "flex", style: { gap: 4 } }, canEdit && /* @__PURE__ */ React.createElement(
|
|
488
|
+
const isPublished = row.spec?.publishStatus === "Published";
|
|
489
|
+
return /* @__PURE__ */ React.createElement(Box, { display: "flex", alignItems: "center", style: { gap: 4 } }, canEdit && /* @__PURE__ */ React.createElement(
|
|
490
|
+
Button,
|
|
491
|
+
{
|
|
492
|
+
size: "small",
|
|
493
|
+
color: "primary",
|
|
494
|
+
onClick: () => handlePublishToggle(row),
|
|
495
|
+
style: { marginRight: 4, textTransform: "none" }
|
|
496
|
+
},
|
|
497
|
+
isPublished ? "Unpublish" : "Publish"
|
|
498
|
+
), canEdit && /* @__PURE__ */ React.createElement(
|
|
286
499
|
IconButton,
|
|
287
500
|
{
|
|
288
501
|
size: "small",
|
|
@@ -302,57 +515,6 @@ const ResourceList = () => {
|
|
|
302
515
|
}
|
|
303
516
|
}
|
|
304
517
|
];
|
|
305
|
-
const planPolicyColumns = [
|
|
306
|
-
{
|
|
307
|
-
title: "Name",
|
|
308
|
-
field: "metadata.name",
|
|
309
|
-
render: (row) => /* @__PURE__ */ React.createElement(
|
|
310
|
-
Link,
|
|
311
|
-
{
|
|
312
|
-
to: `/kuadrant/planpolicy/${row.metadata.namespace}/${row.metadata.name}`
|
|
313
|
-
},
|
|
314
|
-
/* @__PURE__ */ React.createElement("strong", null, row.metadata.name)
|
|
315
|
-
)
|
|
316
|
-
},
|
|
317
|
-
{
|
|
318
|
-
title: "Namespace",
|
|
319
|
-
field: "metadata.namespace"
|
|
320
|
-
}
|
|
321
|
-
];
|
|
322
|
-
const renderResources = (resources) => {
|
|
323
|
-
if (!resources || resources.length === 0) {
|
|
324
|
-
return /* @__PURE__ */ React.createElement(Typography, { variant: "body2", color: "textSecondary" }, "No API products found");
|
|
325
|
-
}
|
|
326
|
-
return /* @__PURE__ */ React.createElement(
|
|
327
|
-
Table,
|
|
328
|
-
{
|
|
329
|
-
options: {
|
|
330
|
-
paging: resources.length > 5,
|
|
331
|
-
pageSize: 20,
|
|
332
|
-
search: true,
|
|
333
|
-
filtering: true,
|
|
334
|
-
debounceInterval: 300,
|
|
335
|
-
toolbar: true,
|
|
336
|
-
emptyRowsWhenPaging: false
|
|
337
|
-
},
|
|
338
|
-
columns,
|
|
339
|
-
data: resources
|
|
340
|
-
}
|
|
341
|
-
);
|
|
342
|
-
};
|
|
343
|
-
const renderPlanPolicies = (resources) => {
|
|
344
|
-
if (!resources || resources.length === 0) {
|
|
345
|
-
return /* @__PURE__ */ React.createElement(Typography, { variant: "body2", color: "textSecondary" }, "No plan policies found");
|
|
346
|
-
}
|
|
347
|
-
return /* @__PURE__ */ React.createElement(
|
|
348
|
-
Table,
|
|
349
|
-
{
|
|
350
|
-
options: { paging: false, search: false, toolbar: false },
|
|
351
|
-
columns: planPolicyColumns,
|
|
352
|
-
data: resources
|
|
353
|
-
}
|
|
354
|
-
);
|
|
355
|
-
};
|
|
356
518
|
return /* @__PURE__ */ React.createElement(Page, { themeId: "tool" }, /* @__PURE__ */ React.createElement(
|
|
357
519
|
Header,
|
|
358
520
|
{
|
|
@@ -360,33 +522,74 @@ const ResourceList = () => {
|
|
|
360
522
|
subtitle: "Manage API products for Kubernetes"
|
|
361
523
|
},
|
|
362
524
|
/* @__PURE__ */ React.createElement(SupportButton, null, "Manage API products and plan policies")
|
|
363
|
-
), /* @__PURE__ */ React.createElement(Content, null, loading && /* @__PURE__ */ React.createElement(
|
|
364
|
-
|
|
525
|
+
), /* @__PURE__ */ React.createElement(Content, null, loading && /* @__PURE__ */ React.createElement(
|
|
526
|
+
Box,
|
|
365
527
|
{
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
alignItems: "center",
|
|
372
|
-
height: "100%",
|
|
373
|
-
mt: 1
|
|
374
|
-
},
|
|
375
|
-
/* @__PURE__ */ React.createElement(
|
|
376
|
-
Button,
|
|
377
|
-
{
|
|
378
|
-
variant: "contained",
|
|
379
|
-
color: "primary",
|
|
380
|
-
size: "small",
|
|
381
|
-
startIcon: /* @__PURE__ */ React.createElement(AddIcon, null),
|
|
382
|
-
onClick: () => setCreateDialogOpen(true)
|
|
383
|
-
},
|
|
384
|
-
"Create API Product"
|
|
385
|
-
)
|
|
386
|
-
) : undefined
|
|
528
|
+
display: "flex",
|
|
529
|
+
flexDirection: "column",
|
|
530
|
+
alignItems: "center",
|
|
531
|
+
justifyContent: "center",
|
|
532
|
+
minHeight: 300
|
|
387
533
|
},
|
|
388
|
-
|
|
389
|
-
|
|
534
|
+
/* @__PURE__ */ React.createElement(CircularProgress, null),
|
|
535
|
+
/* @__PURE__ */ React.createElement(Typography, { variant: "h6", style: { marginTop: 16 } }, "Loading data..."),
|
|
536
|
+
/* @__PURE__ */ React.createElement(Typography, { variant: "body2", color: "textSecondary" }, "Preparing your data... This should only take a moment.")
|
|
537
|
+
), error && /* @__PURE__ */ React.createElement(ResponseErrorPanel, { error }), permissionError && /* @__PURE__ */ React.createElement(Box, { p: 2 }, /* @__PURE__ */ React.createElement(Typography, { color: "error" }, "unable to check permissions: ", permissionError.message), /* @__PURE__ */ React.createElement(Typography, { variant: "body2", color: "textSecondary" }, "permission:", " ", createPermissionError ? "kuadrant.apiproduct.create" : deletePermissionError ? "kuadrant.apiproduct.delete" : planPolicyPermissionError ? "kuadrant.planpolicy.list" : "unknown"), /* @__PURE__ */ React.createElement(Typography, { variant: "body2", color: "textSecondary" }, "please try again or contact your administrator")), !loading && !error && !permissionError && allProducts.length === 0 && /* @__PURE__ */ React.createElement(Box, { className: classes.emptyState }, /* @__PURE__ */ React.createElement(Box, { className: classes.emptyStateContent }, /* @__PURE__ */ React.createElement(Box, { className: classes.emptyStateText }, /* @__PURE__ */ React.createElement(Typography, { variant: "h4", className: classes.emptyStateTitle }, "API Product"), /* @__PURE__ */ React.createElement(
|
|
538
|
+
Typography,
|
|
539
|
+
{
|
|
540
|
+
variant: "body1",
|
|
541
|
+
className: classes.emptyStateDescription
|
|
542
|
+
},
|
|
543
|
+
"Create API product by registering existing API, associate route and policy"
|
|
544
|
+
), canCreateApiProduct && /* @__PURE__ */ React.createElement(
|
|
545
|
+
Button,
|
|
546
|
+
{
|
|
547
|
+
variant: "contained",
|
|
548
|
+
color: "primary",
|
|
549
|
+
startIcon: /* @__PURE__ */ React.createElement(AddIcon, null),
|
|
550
|
+
onClick: () => setCreateDialogOpen(true)
|
|
551
|
+
},
|
|
552
|
+
"Create API Product"
|
|
553
|
+
)), /* @__PURE__ */ React.createElement(
|
|
554
|
+
"img",
|
|
555
|
+
{
|
|
556
|
+
src: emptyStateIllustration,
|
|
557
|
+
alt: "API Product illustration",
|
|
558
|
+
className: classes.emptyStateImage
|
|
559
|
+
}
|
|
560
|
+
))), !loading && !error && !permissionError && allProducts.length > 0 && /* @__PURE__ */ React.createElement(Box, { className: classes.container }, /* @__PURE__ */ React.createElement(
|
|
561
|
+
FilterPanel,
|
|
562
|
+
{
|
|
563
|
+
sections: filterSections,
|
|
564
|
+
filters,
|
|
565
|
+
onChange: setFilters
|
|
566
|
+
}
|
|
567
|
+
), /* @__PURE__ */ React.createElement(Box, { className: classes.tableContainer }, /* @__PURE__ */ React.createElement(Box, { display: "flex", justifyContent: "flex-end", mb: 2 }, canCreateApiProduct && /* @__PURE__ */ React.createElement(
|
|
568
|
+
Button,
|
|
569
|
+
{
|
|
570
|
+
variant: "contained",
|
|
571
|
+
color: "primary",
|
|
572
|
+
size: "small",
|
|
573
|
+
startIcon: /* @__PURE__ */ React.createElement(AddIcon, null),
|
|
574
|
+
onClick: () => setCreateDialogOpen(true)
|
|
575
|
+
},
|
|
576
|
+
"Create API Product"
|
|
577
|
+
)), filteredProducts.length === 0 ? /* @__PURE__ */ React.createElement(Box, { p: 4, textAlign: "center" }, /* @__PURE__ */ React.createElement(Typography, { variant: "body1", color: "textSecondary" }, "No API products match the selected filters.")) : /* @__PURE__ */ React.createElement(
|
|
578
|
+
Table,
|
|
579
|
+
{
|
|
580
|
+
options: {
|
|
581
|
+
paging: filteredProducts.length > 10,
|
|
582
|
+
pageSize: 20,
|
|
583
|
+
search: true,
|
|
584
|
+
filtering: false,
|
|
585
|
+
debounceInterval: 300,
|
|
586
|
+
toolbar: true,
|
|
587
|
+
emptyRowsWhenPaging: false
|
|
588
|
+
},
|
|
589
|
+
columns,
|
|
590
|
+
data: filteredProducts
|
|
591
|
+
}
|
|
592
|
+
))), /* @__PURE__ */ React.createElement(
|
|
390
593
|
CreateAPIProductDialog,
|
|
391
594
|
{
|
|
392
595
|
open: createDialogOpen,
|