@kuadrant/kuadrant-backstage-plugin-frontend 0.0.2-dev-61ff3d3 → 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 +93 -43
- 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.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.90488643192ec2f36944.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.90488643192ec2f36944.js.map +0 -1
- package/dist-scalprum/static/1483.be69af13.chunk.js +0 -2
- package/dist-scalprum/static/1483.be69af13.chunk.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.146b1fd6.chunk.js +0 -2
- package/dist-scalprum/static/532.146b1fd6.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/exposed-PluginRoot.4183ceef.chunk.js +0 -2
- package/dist-scalprum/static/exposed-PluginRoot.4183ceef.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
|
@@ -12,12 +12,15 @@ import AddIcon from '@material-ui/icons/Add';
|
|
|
12
12
|
import MoreVertIcon from '@material-ui/icons/MoreVert';
|
|
13
13
|
import FileCopyIcon from '@material-ui/icons/FileCopy';
|
|
14
14
|
import WarningIcon from '@material-ui/icons/Warning';
|
|
15
|
+
import InfoIcon from '@material-ui/icons/Info';
|
|
15
16
|
import { kuadrantApiKeyCreatePermission, kuadrantApiKeyDeleteOwnPermission, kuadrantApiKeyDeleteAllPermission, kuadrantApiKeyUpdateOwnPermission } from '../../permissions.esm.js';
|
|
16
17
|
import { useKuadrantPermission, canDeleteResource } from '../../utils/permissions.esm.js';
|
|
17
18
|
import { EditAPIKeyDialog } from '../EditAPIKeyDialog/EditAPIKeyDialog.esm.js';
|
|
18
19
|
import { ConfirmDeleteDialog } from '../ConfirmDeleteDialog/ConfirmDeleteDialog.esm.js';
|
|
19
20
|
|
|
20
|
-
const ApiKeyManagementTab = ({
|
|
21
|
+
const ApiKeyManagementTab = ({
|
|
22
|
+
namespace: propNamespace
|
|
23
|
+
}) => {
|
|
21
24
|
const { entity } = useEntity();
|
|
22
25
|
const config = useApi(configApiRef);
|
|
23
26
|
const identityApi = useApi(identityApiRef);
|
|
@@ -40,9 +43,13 @@ const ApiKeyManagementTab = ({ namespace: propNamespace }) => {
|
|
|
40
43
|
const [deleting, setDeleting] = useState(null);
|
|
41
44
|
const [optimisticallyDeleted, setOptimisticallyDeleted] = useState(/* @__PURE__ */ new Set());
|
|
42
45
|
const [deleteDialogState, setDeleteDialogState] = useState({ open: false, request: null });
|
|
43
|
-
const [apiKeyValues, setApiKeyValues] = useState(
|
|
46
|
+
const [apiKeyValues, setApiKeyValues] = useState(
|
|
47
|
+
/* @__PURE__ */ new Map()
|
|
48
|
+
);
|
|
44
49
|
const [apiKeyLoading, setApiKeyLoading] = useState(/* @__PURE__ */ new Set());
|
|
45
|
-
const [alreadyReadKeys, setAlreadyReadKeys] = useState(
|
|
50
|
+
const [alreadyReadKeys, setAlreadyReadKeys] = useState(
|
|
51
|
+
/* @__PURE__ */ new Set()
|
|
52
|
+
);
|
|
46
53
|
const [showOnceWarningOpen, setShowOnceWarningOpen] = useState(false);
|
|
47
54
|
const [pendingKeyReveal, setPendingKeyReveal] = useState(null);
|
|
48
55
|
const apiProductName = entity.metadata.annotations?.["kuadrant.io/apiproduct"] || entity.metadata.name;
|
|
@@ -53,7 +60,11 @@ const ApiKeyManagementTab = ({ namespace: propNamespace }) => {
|
|
|
53
60
|
setUserId(identity.userEntityRef);
|
|
54
61
|
setUserEmail(profile.email || "");
|
|
55
62
|
}, [identityApi]);
|
|
56
|
-
const {
|
|
63
|
+
const {
|
|
64
|
+
value: requests,
|
|
65
|
+
loading: requestsLoading,
|
|
66
|
+
error: requestsError
|
|
67
|
+
} = useAsync(async () => {
|
|
57
68
|
const response = await fetchApi.fetch(
|
|
58
69
|
`${backendUrl}/api/kuadrant/requests/my?namespace=${namespace}`
|
|
59
70
|
);
|
|
@@ -66,8 +77,14 @@ const ApiKeyManagementTab = ({ namespace: propNamespace }) => {
|
|
|
66
77
|
// APIProducts and APIKeys (and its Secret) will be in the same NS
|
|
67
78
|
);
|
|
68
79
|
}, [apiProductName, namespace, refresh, fetchApi, backendUrl]);
|
|
69
|
-
const {
|
|
70
|
-
|
|
80
|
+
const {
|
|
81
|
+
value: apiProduct,
|
|
82
|
+
loading: plansLoading,
|
|
83
|
+
error: plansError
|
|
84
|
+
} = useAsync(async () => {
|
|
85
|
+
const response = await fetchApi.fetch(
|
|
86
|
+
`${backendUrl}/api/kuadrant/apiproducts`
|
|
87
|
+
);
|
|
71
88
|
if (!response.ok) {
|
|
72
89
|
throw new Error("failed to fetch api products");
|
|
73
90
|
}
|
|
@@ -110,7 +127,7 @@ const ApiKeyManagementTab = ({ namespace: propNamespace }) => {
|
|
|
110
127
|
throw new Error("failed to delete request");
|
|
111
128
|
}
|
|
112
129
|
alertApi.post({
|
|
113
|
-
message: "API key
|
|
130
|
+
message: "API key deleted successfully",
|
|
114
131
|
severity: "success",
|
|
115
132
|
display: "transient"
|
|
116
133
|
});
|
|
@@ -123,7 +140,7 @@ const ApiKeyManagementTab = ({ namespace: propNamespace }) => {
|
|
|
123
140
|
return next;
|
|
124
141
|
});
|
|
125
142
|
alertApi.post({
|
|
126
|
-
message: `Failed to delete API key
|
|
143
|
+
message: `Failed to delete API key: ${errorMessage}`,
|
|
127
144
|
severity: "error",
|
|
128
145
|
display: "transient"
|
|
129
146
|
});
|
|
@@ -178,7 +195,11 @@ const ApiKeyManagementTab = ({ namespace: propNamespace }) => {
|
|
|
178
195
|
const handleEditSuccess = () => {
|
|
179
196
|
setRefresh((r) => r + 1);
|
|
180
197
|
setEditDialogOpen(false);
|
|
181
|
-
alertApi.post({
|
|
198
|
+
alertApi.post({
|
|
199
|
+
message: "API key updated",
|
|
200
|
+
severity: "success",
|
|
201
|
+
display: "transient"
|
|
202
|
+
});
|
|
182
203
|
setRequestToEdit(null);
|
|
183
204
|
};
|
|
184
205
|
const handleMenuClose = () => {
|
|
@@ -220,25 +241,30 @@ const ApiKeyManagementTab = ({ namespace: propNamespace }) => {
|
|
|
220
241
|
setCreating(true);
|
|
221
242
|
setCreateError(null);
|
|
222
243
|
try {
|
|
223
|
-
const response = await fetchApi.fetch(
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
244
|
+
const response = await fetchApi.fetch(
|
|
245
|
+
`${backendUrl}/api/kuadrant/requests`,
|
|
246
|
+
{
|
|
247
|
+
method: "POST",
|
|
248
|
+
headers: {
|
|
249
|
+
"Content-Type": "application/json"
|
|
250
|
+
},
|
|
251
|
+
body: JSON.stringify({
|
|
252
|
+
apiProductName,
|
|
253
|
+
namespace,
|
|
254
|
+
planTier: selectedPlan,
|
|
255
|
+
useCase: useCase.trim() || "",
|
|
256
|
+
userEmail
|
|
257
|
+
})
|
|
258
|
+
}
|
|
259
|
+
);
|
|
236
260
|
if (!response.ok) {
|
|
237
261
|
const errorData = await response.json().catch(() => ({}));
|
|
238
|
-
throw new Error(
|
|
262
|
+
throw new Error(
|
|
263
|
+
errorData.error || `failed to create request: ${response.status}`
|
|
264
|
+
);
|
|
239
265
|
}
|
|
240
266
|
alertApi.post({
|
|
241
|
-
message: "API
|
|
267
|
+
message: "API key requested successfully",
|
|
242
268
|
severity: "success",
|
|
243
269
|
display: "transient"
|
|
244
270
|
});
|
|
@@ -249,7 +275,7 @@ const ApiKeyManagementTab = ({ namespace: propNamespace }) => {
|
|
|
249
275
|
} catch (err) {
|
|
250
276
|
const errorMessage = err instanceof Error ? err.message : "unknown error occurred";
|
|
251
277
|
alertApi.post({
|
|
252
|
-
message: `Failed to
|
|
278
|
+
message: `Failed to request API key: ${errorMessage}`,
|
|
253
279
|
severity: "error",
|
|
254
280
|
display: "transient"
|
|
255
281
|
});
|
|
@@ -258,60 +284,94 @@ const ApiKeyManagementTab = ({ namespace: propNamespace }) => {
|
|
|
258
284
|
setCreating(false);
|
|
259
285
|
}
|
|
260
286
|
};
|
|
261
|
-
const detailPanelConfig = useMemo(
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
287
|
+
const detailPanelConfig = useMemo(
|
|
288
|
+
() => [
|
|
289
|
+
{
|
|
290
|
+
render: (data) => {
|
|
291
|
+
const request = data.rowData;
|
|
292
|
+
if (!request?.metadata?.name) {
|
|
293
|
+
return /* @__PURE__ */ React.createElement(Box, null);
|
|
294
|
+
}
|
|
295
|
+
const key = `${request.metadata.namespace}/${request.metadata.name}`;
|
|
296
|
+
const revealedKey = apiKeyValues.get(key);
|
|
297
|
+
return /* @__PURE__ */ React.createElement(
|
|
298
|
+
DetailPanelContent,
|
|
299
|
+
{
|
|
300
|
+
request,
|
|
301
|
+
apiName: apiProductName,
|
|
302
|
+
revealedApiKey: revealedKey
|
|
303
|
+
}
|
|
304
|
+
);
|
|
267
305
|
}
|
|
268
|
-
const key = `${request.metadata.namespace}/${request.metadata.name}`;
|
|
269
|
-
const revealedKey = apiKeyValues.get(key);
|
|
270
|
-
return /* @__PURE__ */ React.createElement(DetailPanelContent, { request, apiName: apiProductName, revealedApiKey: revealedKey });
|
|
271
306
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
307
|
+
],
|
|
308
|
+
[apiProductName, apiKeyValues]
|
|
309
|
+
);
|
|
310
|
+
const DetailPanelContent = ({
|
|
311
|
+
request,
|
|
312
|
+
apiName: api,
|
|
313
|
+
revealedApiKey
|
|
314
|
+
}) => {
|
|
275
315
|
const [selectedLanguage, setSelectedLanguage] = useState(0);
|
|
276
316
|
const hostname = request.status?.apiHostname || `${api}.apps.example.com`;
|
|
277
317
|
const displayApiKey = revealedApiKey || "<your-api-key>";
|
|
278
|
-
return /* @__PURE__ */ React.createElement(
|
|
279
|
-
|
|
318
|
+
return /* @__PURE__ */ React.createElement(
|
|
319
|
+
Box,
|
|
280
320
|
{
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
wordBreak: "break-word",
|
|
285
|
-
overflowWrap: "break-word"
|
|
286
|
-
}
|
|
321
|
+
p: 3,
|
|
322
|
+
bgcolor: "background.default",
|
|
323
|
+
onClick: (e) => e.stopPropagation()
|
|
287
324
|
},
|
|
288
|
-
request.spec.useCase
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
setSelectedLanguage(newValue);
|
|
325
|
+
request.spec.useCase && /* @__PURE__ */ React.createElement(Box, { mb: 3 }, /* @__PURE__ */ React.createElement(Typography, { variant: "h6", gutterBottom: true }, "Use Case"), /* @__PURE__ */ React.createElement(
|
|
326
|
+
Box,
|
|
327
|
+
{
|
|
328
|
+
p: 2,
|
|
329
|
+
bgcolor: "background.paper",
|
|
330
|
+
borderRadius: 1,
|
|
331
|
+
border: "1px solid rgba(0, 0, 0, 0.12)"
|
|
296
332
|
},
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
333
|
+
/* @__PURE__ */ React.createElement(
|
|
334
|
+
Typography,
|
|
335
|
+
{
|
|
336
|
+
variant: "body2",
|
|
337
|
+
style: {
|
|
338
|
+
whiteSpace: "pre-wrap",
|
|
339
|
+
wordBreak: "break-word",
|
|
340
|
+
overflowWrap: "break-word"
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
request.spec.useCase
|
|
344
|
+
)
|
|
345
|
+
)),
|
|
346
|
+
/* @__PURE__ */ React.createElement(Typography, { variant: "h6", gutterBottom: true }, "Usage Examples"),
|
|
347
|
+
/* @__PURE__ */ React.createElement(Typography, { variant: "body2", paragraph: true }, "Use these code examples to test the API with your", " ", request.spec.planTier, " tier key."),
|
|
348
|
+
/* @__PURE__ */ React.createElement(Box, { onClick: (e) => e.stopPropagation() }, /* @__PURE__ */ React.createElement(
|
|
349
|
+
Tabs,
|
|
350
|
+
{
|
|
351
|
+
value: selectedLanguage,
|
|
352
|
+
onChange: (e, newValue) => {
|
|
353
|
+
e.stopPropagation();
|
|
354
|
+
setSelectedLanguage(newValue);
|
|
355
|
+
},
|
|
356
|
+
indicatorColor: "primary"
|
|
357
|
+
},
|
|
358
|
+
/* @__PURE__ */ React.createElement(Tab, { label: "cURL", onClick: (e) => e.stopPropagation() }),
|
|
359
|
+
/* @__PURE__ */ React.createElement(Tab, { label: "Node.js", onClick: (e) => e.stopPropagation() }),
|
|
360
|
+
/* @__PURE__ */ React.createElement(Tab, { label: "Python", onClick: (e) => e.stopPropagation() }),
|
|
361
|
+
/* @__PURE__ */ React.createElement(Tab, { label: "Go", onClick: (e) => e.stopPropagation() })
|
|
362
|
+
)),
|
|
363
|
+
/* @__PURE__ */ React.createElement(Box, { mt: 2 }, selectedLanguage === 0 && /* @__PURE__ */ React.createElement(
|
|
364
|
+
CodeSnippet,
|
|
365
|
+
{
|
|
366
|
+
text: `curl -X GET https://${hostname}/api/v1/endpoint \\
|
|
307
367
|
-H "Authorization: Bearer ${displayApiKey}"`,
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
368
|
+
language: "bash",
|
|
369
|
+
showCopyCodeButton: true
|
|
370
|
+
}
|
|
371
|
+
), selectedLanguage === 1 && /* @__PURE__ */ React.createElement(
|
|
372
|
+
CodeSnippet,
|
|
373
|
+
{
|
|
374
|
+
text: `const fetch = require('node-fetch');
|
|
315
375
|
|
|
316
376
|
const apiKey = '${displayApiKey}';
|
|
317
377
|
const endpoint = 'https://${hostname}/api/v1/endpoint';
|
|
@@ -325,13 +385,13 @@ fetch(endpoint, {
|
|
|
325
385
|
.then(response => response.json())
|
|
326
386
|
.then(data => console.log(data))
|
|
327
387
|
.catch(error => console.error('Error:', error));`,
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
388
|
+
language: "javascript",
|
|
389
|
+
showCopyCodeButton: true
|
|
390
|
+
}
|
|
391
|
+
), selectedLanguage === 2 && /* @__PURE__ */ React.createElement(
|
|
392
|
+
CodeSnippet,
|
|
393
|
+
{
|
|
394
|
+
text: `import requests
|
|
335
395
|
|
|
336
396
|
api_key = '${displayApiKey}'
|
|
337
397
|
endpoint = 'https://${hostname}/api/v1/endpoint'
|
|
@@ -342,13 +402,13 @@ headers = {
|
|
|
342
402
|
|
|
343
403
|
response = requests.get(endpoint, headers=headers)
|
|
344
404
|
print(response.json())`,
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
405
|
+
language: "python",
|
|
406
|
+
showCopyCodeButton: true
|
|
407
|
+
}
|
|
408
|
+
), selectedLanguage === 3 && /* @__PURE__ */ React.createElement(
|
|
409
|
+
CodeSnippet,
|
|
410
|
+
{
|
|
411
|
+
text: `package main
|
|
352
412
|
|
|
353
413
|
import (
|
|
354
414
|
"fmt"
|
|
@@ -374,10 +434,11 @@ func main() {
|
|
|
374
434
|
body, _ := io.ReadAll(resp.Body)
|
|
375
435
|
fmt.Println(string(body))
|
|
376
436
|
}`,
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
437
|
+
language: "go",
|
|
438
|
+
showCopyCodeButton: true
|
|
439
|
+
}
|
|
440
|
+
))
|
|
441
|
+
);
|
|
381
442
|
};
|
|
382
443
|
const loading = requestsLoading || plansLoading || createRequestPermissionLoading || deleteOwnPermissionLoading || deleteAllPermissionLoading || updateRequestPermissionLoading;
|
|
383
444
|
const error = requestsError || plansError;
|
|
@@ -396,9 +457,15 @@ func main() {
|
|
|
396
457
|
(r) => !optimisticallyDeleted.has(r.metadata.name)
|
|
397
458
|
);
|
|
398
459
|
const plans = apiProduct?.status?.discoveredPlans || [];
|
|
399
|
-
const pendingRequests = myRequests.filter(
|
|
400
|
-
|
|
401
|
-
|
|
460
|
+
const pendingRequests = myRequests.filter(
|
|
461
|
+
(r) => !r.status?.phase || r.status.phase === "Pending"
|
|
462
|
+
);
|
|
463
|
+
const approvedRequests = myRequests.filter(
|
|
464
|
+
(r) => r.status?.phase === "Approved"
|
|
465
|
+
);
|
|
466
|
+
const rejectedRequests = myRequests.filter(
|
|
467
|
+
(r) => r.status?.phase === "Rejected"
|
|
468
|
+
);
|
|
402
469
|
const approvedColumns = [
|
|
403
470
|
{
|
|
404
471
|
title: "Tier",
|
|
@@ -442,7 +509,10 @@ func main() {
|
|
|
442
509
|
clearApiKeyValue(row.metadata.namespace, row.metadata.name);
|
|
443
510
|
toggleVisibility(row.metadata.name);
|
|
444
511
|
} else if (!isAlreadyRead) {
|
|
445
|
-
setPendingKeyReveal({
|
|
512
|
+
setPendingKeyReveal({
|
|
513
|
+
namespace: row.metadata.namespace,
|
|
514
|
+
name: row.metadata.name
|
|
515
|
+
});
|
|
446
516
|
setShowOnceWarningOpen(true);
|
|
447
517
|
}
|
|
448
518
|
};
|
|
@@ -466,15 +536,21 @@ func main() {
|
|
|
466
536
|
}
|
|
467
537
|
},
|
|
468
538
|
isLoading ? "Loading..." : isVisible && apiKeyValue ? apiKeyValue : "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"
|
|
469
|
-
), 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(
|
|
470
|
-
|
|
539
|
+
), 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(
|
|
540
|
+
Tooltip,
|
|
471
541
|
{
|
|
472
|
-
|
|
473
|
-
onClick: handleRevealClick,
|
|
474
|
-
disabled: isLoading || isAlreadyRead && !apiKeyValue
|
|
542
|
+
title: isVisible ? "Hide API key" : "Reveal API key (one-time only)"
|
|
475
543
|
},
|
|
476
|
-
|
|
477
|
-
|
|
544
|
+
/* @__PURE__ */ React.createElement("span", null, /* @__PURE__ */ React.createElement(
|
|
545
|
+
IconButton,
|
|
546
|
+
{
|
|
547
|
+
size: "small",
|
|
548
|
+
onClick: handleRevealClick,
|
|
549
|
+
disabled: isLoading || isAlreadyRead && !apiKeyValue
|
|
550
|
+
},
|
|
551
|
+
isVisible ? /* @__PURE__ */ React.createElement(VisibilityOffIcon, null) : /* @__PURE__ */ React.createElement(VisibilityIcon, null)
|
|
552
|
+
))
|
|
553
|
+
));
|
|
478
554
|
}
|
|
479
555
|
},
|
|
480
556
|
{
|
|
@@ -488,7 +564,12 @@ func main() {
|
|
|
488
564
|
return /* @__PURE__ */ React.createElement(CircularProgress, { size: 20 });
|
|
489
565
|
}
|
|
490
566
|
const ownerId = row.spec.requestedBy.userId;
|
|
491
|
-
const canDelete = canDeleteResource(
|
|
567
|
+
const canDelete = canDeleteResource(
|
|
568
|
+
ownerId,
|
|
569
|
+
userId,
|
|
570
|
+
canDeleteOwnKey,
|
|
571
|
+
canDeleteAllKeys
|
|
572
|
+
);
|
|
492
573
|
if (!canDelete) return null;
|
|
493
574
|
return /* @__PURE__ */ React.createElement(
|
|
494
575
|
IconButton,
|
|
@@ -563,7 +644,8 @@ func main() {
|
|
|
563
644
|
title: "Reviewed",
|
|
564
645
|
field: "status.reviewedAt",
|
|
565
646
|
render: (row) => {
|
|
566
|
-
if (!row.status?.reviewedAt)
|
|
647
|
+
if (!row.status?.reviewedAt)
|
|
648
|
+
return /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, "-");
|
|
567
649
|
return /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, new Date(row.status.reviewedAt).toLocaleDateString());
|
|
568
650
|
}
|
|
569
651
|
},
|
|
@@ -579,7 +661,12 @@ func main() {
|
|
|
579
661
|
}
|
|
580
662
|
const isPending = !row.status?.phase || row.status.phase === "Pending";
|
|
581
663
|
const ownerId = row.spec.requestedBy.userId;
|
|
582
|
-
const canDelete = canDeleteResource(
|
|
664
|
+
const canDelete = canDeleteResource(
|
|
665
|
+
ownerId,
|
|
666
|
+
userId,
|
|
667
|
+
canDeleteOwnKey,
|
|
668
|
+
canDeleteAllKeys
|
|
669
|
+
);
|
|
583
670
|
const canEdit = canUpdateRequest && ownerId === userId;
|
|
584
671
|
if (!isPending || !canEdit && !canDelete) return null;
|
|
585
672
|
return /* @__PURE__ */ React.createElement(
|
|
@@ -604,29 +691,52 @@ func main() {
|
|
|
604
691
|
const pendingRequestColumns = requestColumns.filter(
|
|
605
692
|
(col) => col.title !== "Reviewed" && col.title !== "Reason"
|
|
606
693
|
);
|
|
607
|
-
return /* @__PURE__ */ React.createElement(Box, { p: 2 }, /* @__PURE__ */ React.createElement(Grid, { container: true, spacing: 3, direction: "column" }, canCreateRequest && /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(
|
|
608
|
-
|
|
694
|
+
return /* @__PURE__ */ React.createElement(Box, { p: 2 }, /* @__PURE__ */ React.createElement(Grid, { container: true, spacing: 3, direction: "column" }, canCreateRequest && /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(
|
|
695
|
+
Box,
|
|
609
696
|
{
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
disabled: plans.length === 0,
|
|
615
|
-
"data-testid": "request-api-access-button",
|
|
616
|
-
"data-plans-count": plans.length
|
|
697
|
+
display: "flex",
|
|
698
|
+
flexDirection: "column",
|
|
699
|
+
alignItems: "flex-end",
|
|
700
|
+
mb: 2
|
|
617
701
|
},
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
702
|
+
/* @__PURE__ */ React.createElement(
|
|
703
|
+
Button,
|
|
704
|
+
{
|
|
705
|
+
variant: "contained",
|
|
706
|
+
color: "primary",
|
|
707
|
+
startIcon: /* @__PURE__ */ React.createElement(AddIcon, null),
|
|
708
|
+
onClick: () => setOpen(true),
|
|
709
|
+
disabled: plans.length === 0,
|
|
710
|
+
"data-testid": "request-api-access-button",
|
|
711
|
+
"data-plans-count": plans.length
|
|
712
|
+
},
|
|
713
|
+
"Request API Access"
|
|
714
|
+
),
|
|
715
|
+
plans.length === 0 && /* @__PURE__ */ React.createElement(
|
|
716
|
+
Typography,
|
|
717
|
+
{
|
|
718
|
+
variant: "caption",
|
|
719
|
+
color: "textSecondary",
|
|
720
|
+
style: { marginTop: 4 },
|
|
721
|
+
"data-testid": "no-plans-message"
|
|
722
|
+
},
|
|
723
|
+
!apiProduct ? "API product not found" : (() => {
|
|
724
|
+
const readyCondition = apiProduct.status?.conditions?.find(
|
|
725
|
+
(c) => c.type === "Ready"
|
|
726
|
+
);
|
|
727
|
+
const planCondition = apiProduct.status?.conditions?.find(
|
|
728
|
+
(c) => c.type === "PlanPolicyDiscovered"
|
|
729
|
+
);
|
|
730
|
+
if (readyCondition?.status !== "True") {
|
|
731
|
+
return `HTTPRoute not ready: ${readyCondition?.message || "unknown"}`;
|
|
732
|
+
}
|
|
733
|
+
if (planCondition?.status !== "True") {
|
|
734
|
+
return `No plans discovered: ${planCondition?.message || "no PlanPolicy found"}`;
|
|
735
|
+
}
|
|
736
|
+
return "No plans available";
|
|
737
|
+
})()
|
|
738
|
+
)
|
|
739
|
+
)), pendingRequests.length === 0 && rejectedRequests.length === 0 && approvedRequests.length === 0 && /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Box, { p: 3, textAlign: "center" }, /* @__PURE__ */ React.createElement(Typography, { variant: "body1", color: "textSecondary" }, "No API keys yet. Request access to get started."))), pendingRequests.length > 0 && /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(
|
|
630
740
|
Table,
|
|
631
741
|
{
|
|
632
742
|
title: "Pending Requests",
|
|
@@ -676,44 +786,105 @@ func main() {
|
|
|
676
786
|
data: approvedRequests,
|
|
677
787
|
detailPanel: detailPanelConfig
|
|
678
788
|
}
|
|
679
|
-
))), /* @__PURE__ */ React.createElement(
|
|
680
|
-
|
|
681
|
-
{
|
|
682
|
-
labelId: "tier-select-label",
|
|
683
|
-
"data-testid": "tier-select",
|
|
684
|
-
value: selectedPlan,
|
|
685
|
-
onChange: (e) => setSelectedPlan(e.target.value),
|
|
686
|
-
disabled: creating
|
|
687
|
-
},
|
|
688
|
-
plans.map((plan) => {
|
|
689
|
-
const limitDesc = Object.entries(plan.limits || {}).map(([key, val]) => `${val} per ${key}`).join(", ");
|
|
690
|
-
return /* @__PURE__ */ React.createElement(MenuItem, { key: plan.tier, value: plan.tier, "data-testid": `tier-option-${plan.tier}` }, plan.tier, " ", limitDesc ? `(${limitDesc})` : "");
|
|
691
|
-
})
|
|
692
|
-
)), /* @__PURE__ */ React.createElement(
|
|
693
|
-
TextField,
|
|
694
|
-
{
|
|
695
|
-
label: "Use Case (optional)",
|
|
696
|
-
placeholder: "Describe how you plan to use this API",
|
|
697
|
-
multiline: true,
|
|
698
|
-
rows: 3,
|
|
699
|
-
fullWidth: true,
|
|
700
|
-
margin: "normal",
|
|
701
|
-
value: useCase,
|
|
702
|
-
onChange: (e) => setUseCase(e.target.value),
|
|
703
|
-
helperText: "Explain your intended use of this API for admin review",
|
|
704
|
-
disabled: creating
|
|
705
|
-
}
|
|
706
|
-
)), /* @__PURE__ */ React.createElement(DialogActions, null, /* @__PURE__ */ React.createElement(Button, { onClick: () => setOpen(false), disabled: creating }, "Cancel"), /* @__PURE__ */ React.createElement(
|
|
707
|
-
Button,
|
|
789
|
+
))), /* @__PURE__ */ React.createElement(
|
|
790
|
+
Dialog,
|
|
708
791
|
{
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
startIcon: creating ? /* @__PURE__ */ React.createElement(CircularProgress, { size: 16, color: "inherit" }) : undefined
|
|
792
|
+
open,
|
|
793
|
+
onClose: () => setOpen(false),
|
|
794
|
+
maxWidth: "sm",
|
|
795
|
+
fullWidth: true
|
|
714
796
|
},
|
|
715
|
-
|
|
716
|
-
|
|
797
|
+
/* @__PURE__ */ React.createElement(DialogTitle, null, "Request API Access"),
|
|
798
|
+
/* @__PURE__ */ React.createElement(DialogContent, null, /* @__PURE__ */ React.createElement(
|
|
799
|
+
Box,
|
|
800
|
+
{
|
|
801
|
+
mb: 2,
|
|
802
|
+
p: 1.5,
|
|
803
|
+
bgcolor: "info.light",
|
|
804
|
+
borderRadius: 1,
|
|
805
|
+
display: "flex",
|
|
806
|
+
alignItems: "flex-start",
|
|
807
|
+
style: { gap: 8 }
|
|
808
|
+
},
|
|
809
|
+
/* @__PURE__ */ React.createElement(
|
|
810
|
+
InfoIcon,
|
|
811
|
+
{
|
|
812
|
+
color: "primary",
|
|
813
|
+
fontSize: "small",
|
|
814
|
+
style: { marginTop: 2 }
|
|
815
|
+
}
|
|
816
|
+
),
|
|
817
|
+
/* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, "Your request will be reviewed by an API owner before access is granted.")
|
|
818
|
+
), createError && /* @__PURE__ */ React.createElement(
|
|
819
|
+
Box,
|
|
820
|
+
{
|
|
821
|
+
mb: 2,
|
|
822
|
+
p: 2,
|
|
823
|
+
bgcolor: "error.main",
|
|
824
|
+
color: "error.contrastText",
|
|
825
|
+
borderRadius: 1
|
|
826
|
+
},
|
|
827
|
+
/* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, createError)
|
|
828
|
+
), /* @__PURE__ */ React.createElement(
|
|
829
|
+
FormControl,
|
|
830
|
+
{
|
|
831
|
+
fullWidth: true,
|
|
832
|
+
margin: "normal",
|
|
833
|
+
disabled: creating,
|
|
834
|
+
"data-testid": "tier-select-form"
|
|
835
|
+
},
|
|
836
|
+
/* @__PURE__ */ React.createElement(InputLabel, { id: "tier-select-label" }, "Select Tier"),
|
|
837
|
+
/* @__PURE__ */ React.createElement(
|
|
838
|
+
Select,
|
|
839
|
+
{
|
|
840
|
+
labelId: "tier-select-label",
|
|
841
|
+
"data-testid": "tier-select",
|
|
842
|
+
value: selectedPlan,
|
|
843
|
+
onChange: (e) => setSelectedPlan(e.target.value),
|
|
844
|
+
disabled: creating
|
|
845
|
+
},
|
|
846
|
+
plans.map((plan) => {
|
|
847
|
+
const limitDesc = Object.entries(plan.limits || {}).map(([key, val]) => `${val} per ${key}`).join(", ");
|
|
848
|
+
return /* @__PURE__ */ React.createElement(
|
|
849
|
+
MenuItem,
|
|
850
|
+
{
|
|
851
|
+
key: plan.tier,
|
|
852
|
+
value: plan.tier,
|
|
853
|
+
"data-testid": `tier-option-${plan.tier}`
|
|
854
|
+
},
|
|
855
|
+
plan.tier,
|
|
856
|
+
" ",
|
|
857
|
+
limitDesc ? `(${limitDesc})` : ""
|
|
858
|
+
);
|
|
859
|
+
})
|
|
860
|
+
)
|
|
861
|
+
), /* @__PURE__ */ React.createElement(
|
|
862
|
+
TextField,
|
|
863
|
+
{
|
|
864
|
+
label: "Use Case (optional)",
|
|
865
|
+
placeholder: "Describe how you plan to use this API",
|
|
866
|
+
multiline: true,
|
|
867
|
+
rows: 3,
|
|
868
|
+
fullWidth: true,
|
|
869
|
+
margin: "normal",
|
|
870
|
+
value: useCase,
|
|
871
|
+
onChange: (e) => setUseCase(e.target.value),
|
|
872
|
+
helperText: "Explain your intended use of this API for admin review",
|
|
873
|
+
disabled: creating
|
|
874
|
+
}
|
|
875
|
+
)),
|
|
876
|
+
/* @__PURE__ */ React.createElement(DialogActions, null, /* @__PURE__ */ React.createElement(Button, { onClick: () => setOpen(false), disabled: creating }, "Cancel"), /* @__PURE__ */ React.createElement(
|
|
877
|
+
Button,
|
|
878
|
+
{
|
|
879
|
+
onClick: handleRequestAccess,
|
|
880
|
+
color: "primary",
|
|
881
|
+
variant: "contained",
|
|
882
|
+
disabled: !selectedPlan || creating,
|
|
883
|
+
startIcon: creating ? /* @__PURE__ */ React.createElement(CircularProgress, { size: 16, color: "inherit" }) : undefined
|
|
884
|
+
},
|
|
885
|
+
creating ? "Submitting..." : "Submit Request"
|
|
886
|
+
))
|
|
887
|
+
), /* @__PURE__ */ React.createElement(
|
|
717
888
|
Menu,
|
|
718
889
|
{
|
|
719
890
|
id: "actions-menu",
|
|
@@ -728,9 +899,13 @@ func main() {
|
|
|
728
899
|
const canEdit = canUpdateRequest && ownerId === userId && isPending;
|
|
729
900
|
const items = [];
|
|
730
901
|
if (canEdit) {
|
|
731
|
-
items.push(
|
|
902
|
+
items.push(
|
|
903
|
+
/* @__PURE__ */ React.createElement(MenuItem, { key: "edit", onClick: handleMenuEdit }, "Edit")
|
|
904
|
+
);
|
|
732
905
|
}
|
|
733
|
-
items.push(
|
|
906
|
+
items.push(
|
|
907
|
+
/* @__PURE__ */ React.createElement(MenuItem, { key: "delete", onClick: handleMenuDeleteClick }, "Delete")
|
|
908
|
+
);
|
|
734
909
|
return items;
|
|
735
910
|
})()
|
|
736
911
|
), requestToEdit && /* @__PURE__ */ React.createElement(
|
|
@@ -783,7 +958,10 @@ func main() {
|
|
|
783
958
|
color: "primary",
|
|
784
959
|
onClick: () => {
|
|
785
960
|
if (pendingKeyReveal) {
|
|
786
|
-
fetchApiKeyFromSecret(
|
|
961
|
+
fetchApiKeyFromSecret(
|
|
962
|
+
pendingKeyReveal.namespace,
|
|
963
|
+
pendingKeyReveal.name
|
|
964
|
+
);
|
|
787
965
|
toggleVisibility(pendingKeyReveal.name);
|
|
788
966
|
}
|
|
789
967
|
setShowOnceWarningOpen(false);
|