@kuadrant/kuadrant-backstage-plugin-frontend 0.0.2-dev-83763ff → 0.0.2-dev-b696169
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/components/ApiKeyDetailPage/ApiKeyDetailPage.esm.js +319 -0
- package/dist/components/ApiKeyDetailPage/ApiKeyDetailPage.esm.js.map +1 -0
- package/dist/components/ApiKeyDetailPage/index.esm.js +2 -0
- package/dist/components/ApiKeyDetailPage/index.esm.js.map +1 -0
- package/dist/components/ApiKeyManagementTab/ApiKeyManagementTab.esm.js +340 -162
- package/dist/components/ApiKeyManagementTab/ApiKeyManagementTab.esm.js.map +1 -1
- package/dist/components/ApiKeysPage/ApiKeysPage.esm.js +43 -0
- package/dist/components/ApiKeysPage/ApiKeysPage.esm.js.map +1 -0
- package/dist/components/ApiKeysPage/index.esm.js +2 -0
- package/dist/components/ApiKeysPage/index.esm.js.map +1 -0
- package/dist/components/ApprovalQueueCard/ApprovalQueueCard.esm.js +312 -118
- package/dist/components/ApprovalQueueCard/ApprovalQueueCard.esm.js.map +1 -1
- package/dist/components/ApprovalQueueTable/ApprovalQueueTable.esm.js +645 -0
- package/dist/components/ApprovalQueueTable/ApprovalQueueTable.esm.js.map +1 -0
- package/dist/components/EditAPIKeyDialog/EditAPIKeyDialog.esm.js +14 -2
- package/dist/components/EditAPIKeyDialog/EditAPIKeyDialog.esm.js.map +1 -1
- package/dist/components/EntityApiApprovalTab/EntityApiApprovalTab.esm.js +276 -0
- package/dist/components/EntityApiApprovalTab/EntityApiApprovalTab.esm.js.map +1 -0
- package/dist/components/EntityApiApprovalTab/index.esm.js +2 -0
- package/dist/components/EntityApiApprovalTab/index.esm.js.map +1 -0
- package/dist/components/FilterPanel/FilterPanel.esm.js +127 -0
- package/dist/components/FilterPanel/FilterPanel.esm.js.map +1 -0
- package/dist/components/KuadrantPage/KuadrantPage.esm.js +123 -40
- package/dist/components/KuadrantPage/KuadrantPage.esm.js.map +1 -1
- package/dist/components/KuadrantPage/index.esm.js +1 -1
- package/dist/components/{MyApiKeysCard/MyApiKeysCard.esm.js → MyApiKeysTable/MyApiKeysTable.esm.js} +294 -176
- package/dist/components/MyApiKeysTable/MyApiKeysTable.esm.js.map +1 -0
- package/dist/components/PlanPolicyDetailsCard/PlanPolicyDetails.esm.js +5 -4
- package/dist/components/PlanPolicyDetailsCard/PlanPolicyDetails.esm.js.map +1 -1
- package/dist/index.d.ts +8 -4
- package/dist/index.esm.js +1 -1
- package/dist/plugin.esm.js +30 -1
- package/dist/plugin.esm.js.map +1 -1
- package/dist/utils/styles.esm.js +14 -0
- package/dist/utils/styles.esm.js.map +1 -0
- package/dist-scalprum/{internal.plugin-kuadrant.6dd50c0e0265251d3d4f.js → internal.plugin-kuadrant.e37d8046ec4d7ed991e0.js} +2 -2
- package/dist-scalprum/internal.plugin-kuadrant.e37d8046ec4d7ed991e0.js.map +1 -0
- package/dist-scalprum/plugin-manifest.json +2 -2
- package/dist-scalprum/static/2967.c684efaf.chunk.js +2 -0
- package/dist-scalprum/static/2967.c684efaf.chunk.js.map +1 -0
- package/dist-scalprum/static/3097.4bd6b35f.chunk.js +2 -0
- package/dist-scalprum/static/3097.4bd6b35f.chunk.js.map +1 -0
- package/dist-scalprum/static/4306.3a218ff1.chunk.js +2 -0
- package/dist-scalprum/static/4306.3a218ff1.chunk.js.map +1 -0
- package/dist-scalprum/static/5010.acf9a415.chunk.js +3 -0
- package/dist-scalprum/static/5010.acf9a415.chunk.js.map +1 -0
- package/dist-scalprum/static/5453.ae292ab1.chunk.js +2 -0
- package/dist-scalprum/static/5453.ae292ab1.chunk.js.map +1 -0
- package/dist-scalprum/static/6800.736d5da3.chunk.js +2 -0
- package/dist-scalprum/static/6800.736d5da3.chunk.js.map +1 -0
- package/dist-scalprum/static/6813.036a322f.chunk.js +2 -0
- package/dist-scalprum/static/6813.036a322f.chunk.js.map +1 -0
- package/dist-scalprum/static/6840.4728fab9.chunk.js +2 -0
- package/dist-scalprum/static/6840.4728fab9.chunk.js.map +1 -0
- package/dist-scalprum/static/6979.9699b350.chunk.js +2 -0
- package/dist-scalprum/static/6979.9699b350.chunk.js.map +1 -0
- package/dist-scalprum/static/7684.3d1fc1a1.chunk.js +2 -0
- package/dist-scalprum/static/7684.3d1fc1a1.chunk.js.map +1 -0
- package/dist-scalprum/static/8365.d3360f18.chunk.js +2 -0
- package/dist-scalprum/static/8365.d3360f18.chunk.js.map +1 -0
- package/dist-scalprum/static/8416.3604a311.chunk.js +2 -0
- package/dist-scalprum/static/8416.3604a311.chunk.js.map +1 -0
- package/dist-scalprum/static/8563.7e068fb0.chunk.js +3 -0
- package/dist-scalprum/static/8563.7e068fb0.chunk.js.map +1 -0
- package/dist-scalprum/static/exposed-PluginRoot.0717f1ce.chunk.js +2 -0
- package/dist-scalprum/static/exposed-PluginRoot.0717f1ce.chunk.js.map +1 -0
- package/package.json +1 -1
- package/dist/components/MyApiKeysCard/MyApiKeysCard.esm.js.map +0 -1
- package/dist-scalprum/internal.plugin-kuadrant.6dd50c0e0265251d3d4f.js.map +0 -1
- package/dist-scalprum/static/3483.2f14a8ca.chunk.js +0 -3
- package/dist-scalprum/static/3483.2f14a8ca.chunk.js.map +0 -1
- package/dist-scalprum/static/4306.b68910c9.chunk.js +0 -2
- package/dist-scalprum/static/4306.b68910c9.chunk.js.map +0 -1
- package/dist-scalprum/static/4556.c6bedfc4.chunk.js +0 -3
- package/dist-scalprum/static/4556.c6bedfc4.chunk.js.map +0 -1
- package/dist-scalprum/static/5222.796292ca.chunk.js +0 -2
- package/dist-scalprum/static/5222.796292ca.chunk.js.map +0 -1
- package/dist-scalprum/static/532.e406b85b.chunk.js +0 -2
- package/dist-scalprum/static/532.e406b85b.chunk.js.map +0 -1
- package/dist-scalprum/static/5453.29118045.chunk.js +0 -2
- package/dist-scalprum/static/5453.29118045.chunk.js.map +0 -1
- package/dist-scalprum/static/6281.eb6e16a2.chunk.js +0 -2
- package/dist-scalprum/static/6281.eb6e16a2.chunk.js.map +0 -1
- package/dist-scalprum/static/8365.75ea3581.chunk.js +0 -2
- package/dist-scalprum/static/8365.75ea3581.chunk.js.map +0 -1
- package/dist-scalprum/static/8441.62394cfd.chunk.js +0 -2
- package/dist-scalprum/static/8441.62394cfd.chunk.js.map +0 -1
- package/dist-scalprum/static/exposed-PluginRoot.7023ce03.chunk.js +0 -2
- package/dist-scalprum/static/exposed-PluginRoot.7023ce03.chunk.js.map +0 -1
- /package/dist-scalprum/static/{4556.c6bedfc4.chunk.js.LICENSE.txt → 5010.acf9a415.chunk.js.LICENSE.txt} +0 -0
- /package/dist-scalprum/static/{3483.2f14a8ca.chunk.js.LICENSE.txt → 8563.7e068fb0.chunk.js.LICENSE.txt} +0 -0
package/dist/components/{MyApiKeysCard/MyApiKeysCard.esm.js → MyApiKeysTable/MyApiKeysTable.esm.js}
RENAMED
|
@@ -1,69 +1,188 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import React, { useState, useMemo } from 'react';
|
|
2
|
+
import { useNavigate } from 'react-router-dom';
|
|
3
|
+
import { Progress, ResponseErrorPanel, Table, Link } from '@backstage/core-components';
|
|
4
|
+
import { useApi, configApiRef, fetchApiRef, alertApiRef } from '@backstage/core-plugin-api';
|
|
4
5
|
import useAsync from 'react-use/lib/useAsync';
|
|
5
|
-
import {
|
|
6
|
+
import { makeStyles, Box, Typography, Menu, MenuItem, Dialog, DialogTitle, DialogContent, DialogActions, Button, Chip, Tooltip, IconButton, CircularProgress } from '@material-ui/core';
|
|
6
7
|
import VisibilityIcon from '@material-ui/icons/Visibility';
|
|
7
8
|
import VisibilityOffIcon from '@material-ui/icons/VisibilityOff';
|
|
8
|
-
import MoreVertIcon from '@material-ui/icons/MoreVert';
|
|
9
9
|
import FileCopyIcon from '@material-ui/icons/FileCopy';
|
|
10
10
|
import WarningIcon from '@material-ui/icons/Warning';
|
|
11
|
+
import DeleteIcon from '@material-ui/icons/Delete';
|
|
11
12
|
import { EditAPIKeyDialog } from '../EditAPIKeyDialog/EditAPIKeyDialog.esm.js';
|
|
12
13
|
import { ConfirmDeleteDialog } from '../ConfirmDeleteDialog/ConfirmDeleteDialog.esm.js';
|
|
14
|
+
import { FilterPanel } from '../FilterPanel/FilterPanel.esm.js';
|
|
15
|
+
import { getStatusChipStyle } from '../../utils/styles.esm.js';
|
|
13
16
|
|
|
14
|
-
const
|
|
17
|
+
const useStyles = makeStyles((theme) => ({
|
|
18
|
+
container: {
|
|
19
|
+
display: "flex",
|
|
20
|
+
height: "100%",
|
|
21
|
+
minHeight: 400
|
|
22
|
+
},
|
|
23
|
+
tableContainer: {
|
|
24
|
+
flex: 1,
|
|
25
|
+
overflow: "auto"
|
|
26
|
+
},
|
|
27
|
+
useCasePanel: {
|
|
28
|
+
padding: theme.spacing(2),
|
|
29
|
+
backgroundColor: theme.palette.background.default
|
|
30
|
+
},
|
|
31
|
+
useCaseLabel: {
|
|
32
|
+
fontWeight: 600,
|
|
33
|
+
marginBottom: theme.spacing(1),
|
|
34
|
+
color: theme.palette.text.secondary,
|
|
35
|
+
textTransform: "uppercase",
|
|
36
|
+
fontSize: "0.75rem"
|
|
37
|
+
},
|
|
38
|
+
rejectedBanner: {
|
|
39
|
+
backgroundColor: theme.palette.error.light,
|
|
40
|
+
border: `1px solid ${theme.palette.error.main}`,
|
|
41
|
+
borderRadius: theme.shape.borderRadius,
|
|
42
|
+
padding: theme.spacing(1.5, 2),
|
|
43
|
+
marginBottom: theme.spacing(2),
|
|
44
|
+
display: "flex",
|
|
45
|
+
alignItems: "center",
|
|
46
|
+
gap: theme.spacing(1)
|
|
47
|
+
}
|
|
48
|
+
}));
|
|
49
|
+
const ExpandedRowContent = ({ request }) => {
|
|
50
|
+
const classes = useStyles();
|
|
51
|
+
const isRejected = request.status?.phase === "Rejected";
|
|
52
|
+
const apiProductName = request.spec.apiProductRef?.name || "unknown";
|
|
53
|
+
return /* @__PURE__ */ React.createElement(Box, { className: classes.useCasePanel, onClick: (e) => e.stopPropagation() }, isRejected && /* @__PURE__ */ React.createElement(Box, { className: classes.rejectedBanner }, /* @__PURE__ */ React.createElement(WarningIcon, { color: "error", fontSize: "small" }), /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, "This API key was rejected.", " ", /* @__PURE__ */ React.createElement(Link, { to: `/catalog/default/api/${apiProductName}/api-keys` }, "Request a new API key"))), /* @__PURE__ */ React.createElement(Typography, { className: classes.useCaseLabel }, "Use Case"), /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, request.spec.useCase || "No use case provided"));
|
|
54
|
+
};
|
|
55
|
+
const MyApiKeysTable = () => {
|
|
56
|
+
const classes = useStyles();
|
|
57
|
+
const navigate = useNavigate();
|
|
15
58
|
const config = useApi(configApiRef);
|
|
16
59
|
const fetchApi = useApi(fetchApiRef);
|
|
17
|
-
const identityApi = useApi(identityApiRef);
|
|
18
60
|
const alertApi = useApi(alertApiRef);
|
|
19
61
|
const backendUrl = config.getString("backend.baseUrl");
|
|
20
|
-
const [selectedTab, setSelectedTab] = useState(0);
|
|
21
|
-
const [, setUserId] = useState("");
|
|
22
62
|
const [visibleKeys, setVisibleKeys] = useState(/* @__PURE__ */ new Set());
|
|
23
63
|
const [menuAnchor, setMenuAnchor] = useState(null);
|
|
24
64
|
const [menuRequest, setMenuRequest] = useState(null);
|
|
25
|
-
const [editDialogState, setEditDialogState] = useState({
|
|
26
|
-
open: false,
|
|
27
|
-
request: null,
|
|
28
|
-
plans: []
|
|
29
|
-
});
|
|
65
|
+
const [editDialogState, setEditDialogState] = useState({ open: false, request: null, plans: [] });
|
|
30
66
|
const [refresh, setRefresh] = useState(0);
|
|
31
67
|
const [deleting, setDeleting] = useState(null);
|
|
32
68
|
const [deleteDialogState, setDeleteDialogState] = useState({ open: false, request: null });
|
|
33
|
-
const [apiKeyValues, setApiKeyValues] = useState(
|
|
69
|
+
const [apiKeyValues, setApiKeyValues] = useState(
|
|
70
|
+
/* @__PURE__ */ new Map()
|
|
71
|
+
);
|
|
34
72
|
const [apiKeyLoading, setApiKeyLoading] = useState(/* @__PURE__ */ new Set());
|
|
35
|
-
const [alreadyReadKeys, setAlreadyReadKeys] = useState(
|
|
73
|
+
const [alreadyReadKeys, setAlreadyReadKeys] = useState(
|
|
74
|
+
/* @__PURE__ */ new Set()
|
|
75
|
+
);
|
|
36
76
|
const [showOnceWarningOpen, setShowOnceWarningOpen] = useState(false);
|
|
37
77
|
const [pendingKeyReveal, setPendingKeyReveal] = useState(null);
|
|
38
|
-
useAsync(async () => {
|
|
39
|
-
const identity = await identityApi.getBackstageIdentity();
|
|
40
|
-
const extractedUserId = identity.userEntityRef.split("/")[1] || "guest";
|
|
41
|
-
console.log(`MyApiKeysCard: setting userId from userEntityRef: ${identity.userEntityRef} -> "${extractedUserId}"`);
|
|
42
|
-
setUserId(extractedUserId);
|
|
43
|
-
}, [identityApi]);
|
|
44
78
|
const [optimisticallyDeleted, setOptimisticallyDeleted] = useState(/* @__PURE__ */ new Set());
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
79
|
+
const [filters, setFilters] = useState({
|
|
80
|
+
status: [],
|
|
81
|
+
apiProduct: [],
|
|
82
|
+
tier: []
|
|
83
|
+
});
|
|
84
|
+
const {
|
|
85
|
+
value: data,
|
|
86
|
+
loading,
|
|
87
|
+
error
|
|
88
|
+
} = useAsync(async () => {
|
|
89
|
+
const [requestsResponse, productsResponse] = await Promise.all([
|
|
90
|
+
fetchApi.fetch(`${backendUrl}/api/kuadrant/requests/my`),
|
|
91
|
+
fetchApi.fetch(`${backendUrl}/api/kuadrant/apiproducts`)
|
|
92
|
+
]);
|
|
93
|
+
if (!requestsResponse.ok) {
|
|
50
94
|
throw new Error("failed to fetch requests");
|
|
51
95
|
}
|
|
52
|
-
const
|
|
53
|
-
|
|
96
|
+
const requestsData = await requestsResponse.json();
|
|
97
|
+
const requests = requestsData.items || [];
|
|
98
|
+
let products = [];
|
|
99
|
+
if (productsResponse.ok) {
|
|
100
|
+
const productsData = await productsResponse.json();
|
|
101
|
+
products = productsData.items || [];
|
|
102
|
+
}
|
|
103
|
+
const ownerMap = /* @__PURE__ */ new Map();
|
|
104
|
+
products.forEach((p) => {
|
|
105
|
+
const key = `${p.metadata.namespace}/${p.metadata.name}`;
|
|
106
|
+
const owner = p.metadata.annotations?.["backstage.io/owner"] || "unknown";
|
|
107
|
+
ownerMap.set(key, owner);
|
|
108
|
+
});
|
|
109
|
+
return { requests, products, ownerMap };
|
|
54
110
|
}, [backendUrl, fetchApi, refresh]);
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
111
|
+
const allRequests = useMemo(() => {
|
|
112
|
+
if (!data?.requests) return [];
|
|
113
|
+
return data.requests.filter(
|
|
114
|
+
(r) => !optimisticallyDeleted.has(r.metadata.name)
|
|
115
|
+
);
|
|
116
|
+
}, [data?.requests, optimisticallyDeleted]);
|
|
117
|
+
const filterSections = useMemo(() => {
|
|
118
|
+
const statusCounts = { Approved: 0, Pending: 0, Rejected: 0 };
|
|
119
|
+
const apiProductCounts = /* @__PURE__ */ new Map();
|
|
120
|
+
const tierCounts = /* @__PURE__ */ new Map();
|
|
121
|
+
allRequests.forEach((r) => {
|
|
122
|
+
const status = r.status?.phase || "Pending";
|
|
123
|
+
statusCounts[status]++;
|
|
124
|
+
const apiProduct = r.spec.apiProductRef?.name || "unknown";
|
|
125
|
+
apiProductCounts.set(
|
|
126
|
+
apiProduct,
|
|
127
|
+
(apiProductCounts.get(apiProduct) || 0) + 1
|
|
128
|
+
);
|
|
129
|
+
const tier = r.spec.planTier || "unknown";
|
|
130
|
+
tierCounts.set(tier, (tierCounts.get(tier) || 0) + 1);
|
|
131
|
+
});
|
|
132
|
+
return [
|
|
133
|
+
{
|
|
134
|
+
id: "status",
|
|
135
|
+
title: "Status",
|
|
136
|
+
options: [
|
|
137
|
+
{ value: "Approved", label: "Active", count: statusCounts.Approved },
|
|
138
|
+
{ value: "Pending", label: "Pending", count: statusCounts.Pending },
|
|
139
|
+
{
|
|
140
|
+
value: "Rejected",
|
|
141
|
+
label: "Rejected",
|
|
142
|
+
count: statusCounts.Rejected
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
id: "apiProduct",
|
|
148
|
+
title: "API Product",
|
|
149
|
+
options: Array.from(apiProductCounts.entries()).map(
|
|
150
|
+
([name, count]) => ({
|
|
151
|
+
value: name,
|
|
152
|
+
label: name,
|
|
153
|
+
count
|
|
154
|
+
})
|
|
155
|
+
),
|
|
156
|
+
collapsed: apiProductCounts.size > 5
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
id: "tier",
|
|
160
|
+
title: "Tier",
|
|
161
|
+
options: Array.from(tierCounts.entries()).map(([tier, count]) => ({
|
|
162
|
+
value: tier,
|
|
163
|
+
label: tier.charAt(0).toUpperCase() + tier.slice(1),
|
|
164
|
+
count
|
|
165
|
+
}))
|
|
166
|
+
}
|
|
167
|
+
];
|
|
168
|
+
}, [allRequests]);
|
|
169
|
+
const filteredRequests = useMemo(() => {
|
|
170
|
+
return allRequests.filter((r) => {
|
|
171
|
+
if (filters.status.length > 0) {
|
|
172
|
+
const status = r.status?.phase || "Pending";
|
|
173
|
+
if (!filters.status.includes(status)) return false;
|
|
174
|
+
}
|
|
175
|
+
if (filters.apiProduct.length > 0) {
|
|
176
|
+
const apiProduct = r.spec.apiProductRef?.name || "unknown";
|
|
177
|
+
if (!filters.apiProduct.includes(apiProduct)) return false;
|
|
178
|
+
}
|
|
179
|
+
if (filters.tier.length > 0) {
|
|
180
|
+
const tier = r.spec.planTier || "unknown";
|
|
181
|
+
if (!filters.tier.includes(tier)) return false;
|
|
182
|
+
}
|
|
183
|
+
return true;
|
|
184
|
+
});
|
|
185
|
+
}, [allRequests, filters]);
|
|
67
186
|
const toggleKeyVisibility = (keyName) => {
|
|
68
187
|
setVisibleKeys((prev) => {
|
|
69
188
|
const newSet = new Set(prev);
|
|
@@ -77,17 +196,15 @@ const MyApiKeysCard = () => {
|
|
|
77
196
|
};
|
|
78
197
|
const fetchApiKeyFromSecret = async (requestNamespace, requestName) => {
|
|
79
198
|
const key = `${requestNamespace}/${requestName}`;
|
|
80
|
-
if (apiKeyLoading.has(key))
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
199
|
+
if (apiKeyLoading.has(key)) return;
|
|
83
200
|
setApiKeyLoading((prev) => new Set(prev).add(key));
|
|
84
201
|
try {
|
|
85
202
|
const response = await fetchApi.fetch(
|
|
86
203
|
`${backendUrl}/api/kuadrant/apikeys/${requestNamespace}/${requestName}/secret`
|
|
87
204
|
);
|
|
88
205
|
if (response.ok) {
|
|
89
|
-
const
|
|
90
|
-
setApiKeyValues((prev) => new Map(prev).set(key,
|
|
206
|
+
const result = await response.json();
|
|
207
|
+
setApiKeyValues((prev) => new Map(prev).set(key, result.apiKey));
|
|
91
208
|
setAlreadyReadKeys((prev) => new Set(prev).add(key));
|
|
92
209
|
} else if (response.status === 403) {
|
|
93
210
|
setAlreadyReadKeys((prev) => new Set(prev).add(key));
|
|
@@ -134,7 +251,6 @@ const MyApiKeysCard = () => {
|
|
|
134
251
|
const plans = apiProduct.spec?.plans || [];
|
|
135
252
|
setEditDialogState({ open: true, request, plans });
|
|
136
253
|
} else {
|
|
137
|
-
console.error("Failed to fetch API product");
|
|
138
254
|
setEditDialogState({ open: true, request, plans: [] });
|
|
139
255
|
}
|
|
140
256
|
} catch (err) {
|
|
@@ -163,7 +279,11 @@ const MyApiKeysCard = () => {
|
|
|
163
279
|
throw new Error("Failed to delete request");
|
|
164
280
|
}
|
|
165
281
|
setRefresh((r) => r + 1);
|
|
166
|
-
alertApi.post({
|
|
282
|
+
alertApi.post({
|
|
283
|
+
message: "API key deleted",
|
|
284
|
+
severity: "success",
|
|
285
|
+
display: "transient"
|
|
286
|
+
});
|
|
167
287
|
setDeleteDialogState({ open: false, request: null });
|
|
168
288
|
} catch (err) {
|
|
169
289
|
console.error("Error deleting request:", err);
|
|
@@ -172,7 +292,11 @@ const MyApiKeysCard = () => {
|
|
|
172
292
|
next.delete(requestName);
|
|
173
293
|
return next;
|
|
174
294
|
});
|
|
175
|
-
alertApi.post({
|
|
295
|
+
alertApi.post({
|
|
296
|
+
message: "Failed to delete API key",
|
|
297
|
+
severity: "error",
|
|
298
|
+
display: "transient"
|
|
299
|
+
});
|
|
176
300
|
} finally {
|
|
177
301
|
setDeleting(null);
|
|
178
302
|
}
|
|
@@ -190,33 +314,13 @@ const MyApiKeysCard = () => {
|
|
|
190
314
|
}
|
|
191
315
|
},
|
|
192
316
|
{
|
|
193
|
-
title: "
|
|
194
|
-
field: "
|
|
195
|
-
render: (row) => {
|
|
196
|
-
const color = row.spec.planTier === "gold" ? "primary" : row.spec.planTier === "silver" ? "default" : "secondary";
|
|
197
|
-
return /* @__PURE__ */ React.createElement(Chip, { label: row.spec.planTier, color, size: "small" });
|
|
198
|
-
}
|
|
199
|
-
},
|
|
200
|
-
{
|
|
201
|
-
title: "Use Case",
|
|
202
|
-
field: "spec.useCase",
|
|
317
|
+
title: "Owner",
|
|
318
|
+
field: "owner",
|
|
203
319
|
render: (row) => {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
return /* @__PURE__ */ React.createElement(
|
|
208
|
-
Typography,
|
|
209
|
-
{
|
|
210
|
-
variant: "body2",
|
|
211
|
-
style: {
|
|
212
|
-
maxWidth: "200px",
|
|
213
|
-
overflow: "hidden",
|
|
214
|
-
textOverflow: "ellipsis",
|
|
215
|
-
whiteSpace: "nowrap"
|
|
216
|
-
}
|
|
217
|
-
},
|
|
218
|
-
row.spec.useCase
|
|
219
|
-
));
|
|
320
|
+
const key = `${row.metadata.namespace}/${row.spec.apiProductRef?.name}`;
|
|
321
|
+
const owner = data?.ownerMap?.get(key) || "unknown";
|
|
322
|
+
const displayOwner = owner.replace(/^user:default\//, "");
|
|
323
|
+
return /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, displayOwner);
|
|
220
324
|
}
|
|
221
325
|
},
|
|
222
326
|
{
|
|
@@ -224,20 +328,14 @@ const MyApiKeysCard = () => {
|
|
|
224
328
|
field: "status.phase",
|
|
225
329
|
render: (row) => {
|
|
226
330
|
const phase = row.status?.phase || "Pending";
|
|
227
|
-
const
|
|
228
|
-
return /* @__PURE__ */ React.createElement(Chip, { label
|
|
331
|
+
const label = phase === "Approved" ? "Active" : phase;
|
|
332
|
+
return /* @__PURE__ */ React.createElement(Chip, { label, size: "small", style: getStatusChipStyle(phase) });
|
|
229
333
|
}
|
|
230
334
|
},
|
|
231
335
|
{
|
|
232
|
-
title: "
|
|
233
|
-
field: "
|
|
234
|
-
render: (row) => {
|
|
235
|
-
if ((row.status?.phase === "Approved" || row.status?.phase === "Rejected") && row.status.reviewedBy) {
|
|
236
|
-
const reviewedDate = row.status.reviewedAt ? new Date(row.status.reviewedAt).toLocaleDateString() : "";
|
|
237
|
-
return /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, row.status.reviewedBy), reviewedDate && /* @__PURE__ */ React.createElement(Typography, { variant: "caption", color: "textSecondary" }, reviewedDate));
|
|
238
|
-
}
|
|
239
|
-
return /* @__PURE__ */ React.createElement(Typography, { variant: "body2", color: "textSecondary" }, "-");
|
|
240
|
-
}
|
|
336
|
+
title: "Tier",
|
|
337
|
+
field: "spec.planTier",
|
|
338
|
+
render: (row) => /* @__PURE__ */ React.createElement(Chip, { label: row.spec.planTier, size: "small", variant: "outlined" })
|
|
241
339
|
},
|
|
242
340
|
{
|
|
243
341
|
title: "API Key",
|
|
@@ -273,7 +371,10 @@ const MyApiKeysCard = () => {
|
|
|
273
371
|
clearApiKeyValue(row.metadata.namespace, row.metadata.name);
|
|
274
372
|
toggleKeyVisibility(row.metadata.name);
|
|
275
373
|
} else if (!isAlreadyRead) {
|
|
276
|
-
setPendingKeyReveal({
|
|
374
|
+
setPendingKeyReveal({
|
|
375
|
+
namespace: row.metadata.namespace,
|
|
376
|
+
name: row.metadata.name
|
|
377
|
+
});
|
|
277
378
|
setShowOnceWarningOpen(true);
|
|
278
379
|
}
|
|
279
380
|
};
|
|
@@ -287,15 +388,21 @@ const MyApiKeysCard = () => {
|
|
|
287
388
|
});
|
|
288
389
|
}
|
|
289
390
|
};
|
|
290
|
-
return /* @__PURE__ */ React.createElement(Box, { display: "flex", alignItems: "center", style: { gap: 8 } }, /* @__PURE__ */ React.createElement(Box, { fontFamily: "monospace", fontSize: "0.875rem" }, isLoading ? "Loading..." : isVisible && apiKeyValue ? apiKeyValue : "\u2022".repeat(20) + "..."), isVisible && apiKeyValue && /* @__PURE__ */ React.createElement(Tooltip, { title: "Copy to clipboard" }, /* @__PURE__ */ React.createElement(IconButton, { size: "small", onClick: handleCopy }, /* @__PURE__ */ React.createElement(FileCopyIcon, { fontSize: "small" }))), /* @__PURE__ */ React.createElement(
|
|
291
|
-
|
|
391
|
+
return /* @__PURE__ */ React.createElement(Box, { display: "flex", alignItems: "center", style: { gap: 8 } }, /* @__PURE__ */ React.createElement(Box, { fontFamily: "monospace", fontSize: "0.875rem" }, isLoading ? "Loading..." : isVisible && apiKeyValue ? apiKeyValue : "\u2022".repeat(20) + "..."), isVisible && apiKeyValue && /* @__PURE__ */ React.createElement(Tooltip, { title: "Copy to clipboard" }, /* @__PURE__ */ React.createElement(IconButton, { size: "small", onClick: handleCopy }, /* @__PURE__ */ React.createElement(FileCopyIcon, { fontSize: "small" }))), /* @__PURE__ */ React.createElement(
|
|
392
|
+
Tooltip,
|
|
292
393
|
{
|
|
293
|
-
|
|
294
|
-
onClick: handleRevealClick,
|
|
295
|
-
disabled: isLoading || isAlreadyRead && !apiKeyValue
|
|
394
|
+
title: isVisible ? "Hide API key" : "Reveal API key (one-time only)"
|
|
296
395
|
},
|
|
297
|
-
|
|
298
|
-
|
|
396
|
+
/* @__PURE__ */ React.createElement("span", null, /* @__PURE__ */ React.createElement(
|
|
397
|
+
IconButton,
|
|
398
|
+
{
|
|
399
|
+
size: "small",
|
|
400
|
+
onClick: handleRevealClick,
|
|
401
|
+
disabled: isLoading || isAlreadyRead && !apiKeyValue
|
|
402
|
+
},
|
|
403
|
+
isVisible ? /* @__PURE__ */ React.createElement(VisibilityOffIcon, { fontSize: "small" }) : /* @__PURE__ */ React.createElement(VisibilityIcon, { fontSize: "small" })
|
|
404
|
+
))
|
|
405
|
+
));
|
|
299
406
|
}
|
|
300
407
|
},
|
|
301
408
|
{
|
|
@@ -310,99 +417,88 @@ const MyApiKeysCard = () => {
|
|
|
310
417
|
}
|
|
311
418
|
},
|
|
312
419
|
{
|
|
313
|
-
title: "",
|
|
420
|
+
title: "Actions",
|
|
314
421
|
filtering: false,
|
|
422
|
+
width: "100px",
|
|
315
423
|
render: (row) => {
|
|
316
424
|
const isDeleting = deleting === row.metadata.name;
|
|
317
425
|
if (isDeleting) {
|
|
318
426
|
return /* @__PURE__ */ React.createElement(CircularProgress, { size: 20 });
|
|
319
427
|
}
|
|
320
|
-
return /* @__PURE__ */ React.createElement(
|
|
428
|
+
return /* @__PURE__ */ React.createElement(Box, { display: "flex", style: { gap: 4 } }, /* @__PURE__ */ React.createElement(Tooltip, { title: "View details" }, /* @__PURE__ */ React.createElement(
|
|
321
429
|
IconButton,
|
|
322
430
|
{
|
|
323
431
|
size: "small",
|
|
324
432
|
onClick: (e) => {
|
|
325
433
|
e.stopPropagation();
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
}
|
|
330
|
-
"aria-controls": menuAnchor ? "myapikeys-menu" : undefined,
|
|
331
|
-
"aria-haspopup": "true"
|
|
434
|
+
navigate(
|
|
435
|
+
`/kuadrant/api-keys/${row.metadata.namespace}/${row.metadata.name}`
|
|
436
|
+
);
|
|
437
|
+
}
|
|
332
438
|
},
|
|
333
|
-
/* @__PURE__ */ React.createElement(
|
|
334
|
-
)
|
|
439
|
+
/* @__PURE__ */ React.createElement(VisibilityIcon, { fontSize: "small" })
|
|
440
|
+
)), /* @__PURE__ */ React.createElement(Tooltip, { title: "Delete" }, /* @__PURE__ */ React.createElement(
|
|
441
|
+
IconButton,
|
|
442
|
+
{
|
|
443
|
+
size: "small",
|
|
444
|
+
onClick: (e) => {
|
|
445
|
+
e.stopPropagation();
|
|
446
|
+
setDeleteDialogState({ open: true, request: row });
|
|
447
|
+
}
|
|
448
|
+
},
|
|
449
|
+
/* @__PURE__ */ React.createElement(DeleteIcon, { fontSize: "small" })
|
|
450
|
+
)));
|
|
335
451
|
}
|
|
336
452
|
}
|
|
337
453
|
];
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
case 2:
|
|
359
|
-
return columns.filter((col) => col.title !== "API Key");
|
|
360
|
-
default:
|
|
361
|
-
return columns;
|
|
362
|
-
}
|
|
363
|
-
};
|
|
364
|
-
const tabData = getTabData();
|
|
365
|
-
const tabColumns = getTabColumns();
|
|
454
|
+
const detailPanelConfig = useMemo(
|
|
455
|
+
() => [
|
|
456
|
+
{
|
|
457
|
+
render: (data2) => {
|
|
458
|
+
const request = data2.rowData;
|
|
459
|
+
if (!request?.metadata?.name) {
|
|
460
|
+
return /* @__PURE__ */ React.createElement(Box, null);
|
|
461
|
+
}
|
|
462
|
+
return /* @__PURE__ */ React.createElement(ExpandedRowContent, { request });
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
],
|
|
466
|
+
[]
|
|
467
|
+
);
|
|
468
|
+
if (loading) {
|
|
469
|
+
return /* @__PURE__ */ React.createElement(Progress, null);
|
|
470
|
+
}
|
|
471
|
+
if (error) {
|
|
472
|
+
return /* @__PURE__ */ React.createElement(ResponseErrorPanel, { error });
|
|
473
|
+
}
|
|
366
474
|
const isPending = (row) => !row.status || row.status.phase === "Pending";
|
|
367
|
-
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
|
|
368
|
-
|
|
475
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Box, { className: classes.container }, /* @__PURE__ */ React.createElement(
|
|
476
|
+
FilterPanel,
|
|
369
477
|
{
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
478
|
+
sections: filterSections,
|
|
479
|
+
filters,
|
|
480
|
+
onChange: setFilters
|
|
481
|
+
}
|
|
482
|
+
), /* @__PURE__ */ React.createElement(Box, { className: classes.tableContainer }, filteredRequests.length === 0 ? /* @__PURE__ */ React.createElement(Box, { p: 4, textAlign: "center" }, /* @__PURE__ */ React.createElement(Typography, { variant: "body1", color: "textSecondary" }, allRequests.length === 0 ? "No API keys found. Request access to an API to get started." : "No API keys match the selected filters.")) : /* @__PURE__ */ React.createElement(
|
|
483
|
+
Table,
|
|
484
|
+
{
|
|
485
|
+
options: {
|
|
486
|
+
paging: filteredRequests.length > 10,
|
|
487
|
+
pageSize: 20,
|
|
488
|
+
search: true,
|
|
489
|
+
filtering: false,
|
|
490
|
+
debounceInterval: 300,
|
|
491
|
+
toolbar: true,
|
|
492
|
+
emptyRowsWhenPaging: false
|
|
381
493
|
},
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
paging: tabData.length > 5,
|
|
391
|
-
pageSize: 20,
|
|
392
|
-
search: true,
|
|
393
|
-
filtering: true,
|
|
394
|
-
debounceInterval: 300,
|
|
395
|
-
toolbar: true,
|
|
396
|
-
emptyRowsWhenPaging: false
|
|
397
|
-
},
|
|
398
|
-
columns: tabColumns,
|
|
399
|
-
data: tabData.map((item) => ({
|
|
400
|
-
...item,
|
|
401
|
-
id: item.metadata.name
|
|
402
|
-
}))
|
|
403
|
-
}
|
|
404
|
-
)
|
|
405
|
-
), /* @__PURE__ */ React.createElement(
|
|
494
|
+
columns,
|
|
495
|
+
data: filteredRequests.map((item) => ({
|
|
496
|
+
...item,
|
|
497
|
+
id: item.metadata.name
|
|
498
|
+
})),
|
|
499
|
+
detailPanel: detailPanelConfig
|
|
500
|
+
}
|
|
501
|
+
))), /* @__PURE__ */ React.createElement(
|
|
406
502
|
Menu,
|
|
407
503
|
{
|
|
408
504
|
id: "myapikeys-menu",
|
|
@@ -413,10 +509,29 @@ const MyApiKeysCard = () => {
|
|
|
413
509
|
},
|
|
414
510
|
menuRequest && (() => {
|
|
415
511
|
const items = [];
|
|
512
|
+
items.push(
|
|
513
|
+
/* @__PURE__ */ React.createElement(
|
|
514
|
+
MenuItem,
|
|
515
|
+
{
|
|
516
|
+
key: "view",
|
|
517
|
+
onClick: () => {
|
|
518
|
+
navigate(
|
|
519
|
+
`/kuadrant/api-keys/${menuRequest.metadata.namespace}/${menuRequest.metadata.name}`
|
|
520
|
+
);
|
|
521
|
+
handleMenuClose();
|
|
522
|
+
}
|
|
523
|
+
},
|
|
524
|
+
"View Details"
|
|
525
|
+
)
|
|
526
|
+
);
|
|
416
527
|
if (isPending(menuRequest)) {
|
|
417
|
-
items.push(
|
|
528
|
+
items.push(
|
|
529
|
+
/* @__PURE__ */ React.createElement(MenuItem, { key: "edit", onClick: handleEdit }, "Edit")
|
|
530
|
+
);
|
|
418
531
|
}
|
|
419
|
-
items.push(
|
|
532
|
+
items.push(
|
|
533
|
+
/* @__PURE__ */ React.createElement(MenuItem, { key: "delete", onClick: handleDeleteClick }, "Delete")
|
|
534
|
+
);
|
|
420
535
|
return items;
|
|
421
536
|
})()
|
|
422
537
|
), editDialogState.request && /* @__PURE__ */ React.createElement(
|
|
@@ -435,8 +550,8 @@ const MyApiKeysCard = () => {
|
|
|
435
550
|
ConfirmDeleteDialog,
|
|
436
551
|
{
|
|
437
552
|
open: deleteDialogState.open,
|
|
438
|
-
title: "Delete API Key
|
|
439
|
-
description: `Are you sure you want to delete
|
|
553
|
+
title: "Delete API Key",
|
|
554
|
+
description: `Are you sure you want to delete this API key for ${deleteDialogState.request?.spec.apiProductRef?.name || "this API"}?`,
|
|
440
555
|
deleting: deleting !== null,
|
|
441
556
|
onConfirm: handleDeleteConfirm,
|
|
442
557
|
onCancel: handleDeleteCancel
|
|
@@ -469,7 +584,10 @@ const MyApiKeysCard = () => {
|
|
|
469
584
|
color: "primary",
|
|
470
585
|
onClick: () => {
|
|
471
586
|
if (pendingKeyReveal) {
|
|
472
|
-
fetchApiKeyFromSecret(
|
|
587
|
+
fetchApiKeyFromSecret(
|
|
588
|
+
pendingKeyReveal.namespace,
|
|
589
|
+
pendingKeyReveal.name
|
|
590
|
+
);
|
|
473
591
|
toggleKeyVisibility(pendingKeyReveal.name);
|
|
474
592
|
}
|
|
475
593
|
setShowOnceWarningOpen(false);
|
|
@@ -481,5 +599,5 @@ const MyApiKeysCard = () => {
|
|
|
481
599
|
));
|
|
482
600
|
};
|
|
483
601
|
|
|
484
|
-
export {
|
|
485
|
-
//# sourceMappingURL=
|
|
602
|
+
export { MyApiKeysTable };
|
|
603
|
+
//# sourceMappingURL=MyApiKeysTable.esm.js.map
|