@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.
Files changed (91) hide show
  1. package/dist/components/ApiKeyDetailPage/ApiKeyDetailPage.esm.js +319 -0
  2. package/dist/components/ApiKeyDetailPage/ApiKeyDetailPage.esm.js.map +1 -0
  3. package/dist/components/ApiKeyDetailPage/index.esm.js +2 -0
  4. package/dist/components/ApiKeyDetailPage/index.esm.js.map +1 -0
  5. package/dist/components/ApiKeyManagementTab/ApiKeyManagementTab.esm.js +340 -162
  6. package/dist/components/ApiKeyManagementTab/ApiKeyManagementTab.esm.js.map +1 -1
  7. package/dist/components/ApiKeysPage/ApiKeysPage.esm.js +43 -0
  8. package/dist/components/ApiKeysPage/ApiKeysPage.esm.js.map +1 -0
  9. package/dist/components/ApiKeysPage/index.esm.js +2 -0
  10. package/dist/components/ApiKeysPage/index.esm.js.map +1 -0
  11. package/dist/components/ApprovalQueueCard/ApprovalQueueCard.esm.js +312 -118
  12. package/dist/components/ApprovalQueueCard/ApprovalQueueCard.esm.js.map +1 -1
  13. package/dist/components/ApprovalQueueTable/ApprovalQueueTable.esm.js +645 -0
  14. package/dist/components/ApprovalQueueTable/ApprovalQueueTable.esm.js.map +1 -0
  15. package/dist/components/EditAPIKeyDialog/EditAPIKeyDialog.esm.js +14 -2
  16. package/dist/components/EditAPIKeyDialog/EditAPIKeyDialog.esm.js.map +1 -1
  17. package/dist/components/EntityApiApprovalTab/EntityApiApprovalTab.esm.js +276 -0
  18. package/dist/components/EntityApiApprovalTab/EntityApiApprovalTab.esm.js.map +1 -0
  19. package/dist/components/EntityApiApprovalTab/index.esm.js +2 -0
  20. package/dist/components/EntityApiApprovalTab/index.esm.js.map +1 -0
  21. package/dist/components/FilterPanel/FilterPanel.esm.js +127 -0
  22. package/dist/components/FilterPanel/FilterPanel.esm.js.map +1 -0
  23. package/dist/components/KuadrantPage/KuadrantPage.esm.js +123 -40
  24. package/dist/components/KuadrantPage/KuadrantPage.esm.js.map +1 -1
  25. package/dist/components/KuadrantPage/index.esm.js +1 -1
  26. package/dist/components/{MyApiKeysCard/MyApiKeysCard.esm.js → MyApiKeysTable/MyApiKeysTable.esm.js} +294 -176
  27. package/dist/components/MyApiKeysTable/MyApiKeysTable.esm.js.map +1 -0
  28. package/dist/components/PlanPolicyDetailsCard/PlanPolicyDetails.esm.js +5 -4
  29. package/dist/components/PlanPolicyDetailsCard/PlanPolicyDetails.esm.js.map +1 -1
  30. package/dist/index.d.ts +8 -4
  31. package/dist/index.esm.js +1 -1
  32. package/dist/plugin.esm.js +30 -1
  33. package/dist/plugin.esm.js.map +1 -1
  34. package/dist/utils/styles.esm.js +14 -0
  35. package/dist/utils/styles.esm.js.map +1 -0
  36. package/dist-scalprum/{internal.plugin-kuadrant.6dd50c0e0265251d3d4f.js → internal.plugin-kuadrant.e37d8046ec4d7ed991e0.js} +2 -2
  37. package/dist-scalprum/internal.plugin-kuadrant.e37d8046ec4d7ed991e0.js.map +1 -0
  38. package/dist-scalprum/plugin-manifest.json +2 -2
  39. package/dist-scalprum/static/2967.c684efaf.chunk.js +2 -0
  40. package/dist-scalprum/static/2967.c684efaf.chunk.js.map +1 -0
  41. package/dist-scalprum/static/3097.4bd6b35f.chunk.js +2 -0
  42. package/dist-scalprum/static/3097.4bd6b35f.chunk.js.map +1 -0
  43. package/dist-scalprum/static/4306.3a218ff1.chunk.js +2 -0
  44. package/dist-scalprum/static/4306.3a218ff1.chunk.js.map +1 -0
  45. package/dist-scalprum/static/5010.acf9a415.chunk.js +3 -0
  46. package/dist-scalprum/static/5010.acf9a415.chunk.js.map +1 -0
  47. package/dist-scalprum/static/5453.ae292ab1.chunk.js +2 -0
  48. package/dist-scalprum/static/5453.ae292ab1.chunk.js.map +1 -0
  49. package/dist-scalprum/static/6800.736d5da3.chunk.js +2 -0
  50. package/dist-scalprum/static/6800.736d5da3.chunk.js.map +1 -0
  51. package/dist-scalprum/static/6813.036a322f.chunk.js +2 -0
  52. package/dist-scalprum/static/6813.036a322f.chunk.js.map +1 -0
  53. package/dist-scalprum/static/6840.4728fab9.chunk.js +2 -0
  54. package/dist-scalprum/static/6840.4728fab9.chunk.js.map +1 -0
  55. package/dist-scalprum/static/6979.9699b350.chunk.js +2 -0
  56. package/dist-scalprum/static/6979.9699b350.chunk.js.map +1 -0
  57. package/dist-scalprum/static/7684.3d1fc1a1.chunk.js +2 -0
  58. package/dist-scalprum/static/7684.3d1fc1a1.chunk.js.map +1 -0
  59. package/dist-scalprum/static/8365.d3360f18.chunk.js +2 -0
  60. package/dist-scalprum/static/8365.d3360f18.chunk.js.map +1 -0
  61. package/dist-scalprum/static/8416.3604a311.chunk.js +2 -0
  62. package/dist-scalprum/static/8416.3604a311.chunk.js.map +1 -0
  63. package/dist-scalprum/static/8563.7e068fb0.chunk.js +3 -0
  64. package/dist-scalprum/static/8563.7e068fb0.chunk.js.map +1 -0
  65. package/dist-scalprum/static/exposed-PluginRoot.0717f1ce.chunk.js +2 -0
  66. package/dist-scalprum/static/exposed-PluginRoot.0717f1ce.chunk.js.map +1 -0
  67. package/package.json +1 -1
  68. package/dist/components/MyApiKeysCard/MyApiKeysCard.esm.js.map +0 -1
  69. package/dist-scalprum/internal.plugin-kuadrant.6dd50c0e0265251d3d4f.js.map +0 -1
  70. package/dist-scalprum/static/3483.2f14a8ca.chunk.js +0 -3
  71. package/dist-scalprum/static/3483.2f14a8ca.chunk.js.map +0 -1
  72. package/dist-scalprum/static/4306.b68910c9.chunk.js +0 -2
  73. package/dist-scalprum/static/4306.b68910c9.chunk.js.map +0 -1
  74. package/dist-scalprum/static/4556.c6bedfc4.chunk.js +0 -3
  75. package/dist-scalprum/static/4556.c6bedfc4.chunk.js.map +0 -1
  76. package/dist-scalprum/static/5222.796292ca.chunk.js +0 -2
  77. package/dist-scalprum/static/5222.796292ca.chunk.js.map +0 -1
  78. package/dist-scalprum/static/532.e406b85b.chunk.js +0 -2
  79. package/dist-scalprum/static/532.e406b85b.chunk.js.map +0 -1
  80. package/dist-scalprum/static/5453.29118045.chunk.js +0 -2
  81. package/dist-scalprum/static/5453.29118045.chunk.js.map +0 -1
  82. package/dist-scalprum/static/6281.eb6e16a2.chunk.js +0 -2
  83. package/dist-scalprum/static/6281.eb6e16a2.chunk.js.map +0 -1
  84. package/dist-scalprum/static/8365.75ea3581.chunk.js +0 -2
  85. package/dist-scalprum/static/8365.75ea3581.chunk.js.map +0 -1
  86. package/dist-scalprum/static/8441.62394cfd.chunk.js +0 -2
  87. package/dist-scalprum/static/8441.62394cfd.chunk.js.map +0 -1
  88. package/dist-scalprum/static/exposed-PluginRoot.7023ce03.chunk.js +0 -2
  89. package/dist-scalprum/static/exposed-PluginRoot.7023ce03.chunk.js.map +0 -1
  90. /package/dist-scalprum/static/{4556.c6bedfc4.chunk.js.LICENSE.txt → 5010.acf9a415.chunk.js.LICENSE.txt} +0 -0
  91. /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 = ({ namespace: propNamespace }) => {
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(/* @__PURE__ */ new Map());
46
+ const [apiKeyValues, setApiKeyValues] = useState(
47
+ /* @__PURE__ */ new Map()
48
+ );
44
49
  const [apiKeyLoading, setApiKeyLoading] = useState(/* @__PURE__ */ new Set());
45
- const [alreadyReadKeys, setAlreadyReadKeys] = useState(/* @__PURE__ */ new Set());
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 { value: requests, loading: requestsLoading, error: requestsError } = useAsync(async () => {
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 { value: apiProduct, loading: plansLoading, error: plansError } = useAsync(async () => {
70
- const response = await fetchApi.fetch(`${backendUrl}/api/kuadrant/apiproducts`);
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 request deleted successfully",
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 request: ${errorMessage}`,
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({ message: "Request updated", severity: "success", display: "transient" });
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(`${backendUrl}/api/kuadrant/requests`, {
224
- method: "POST",
225
- headers: {
226
- "Content-Type": "application/json"
227
- },
228
- body: JSON.stringify({
229
- apiProductName,
230
- namespace,
231
- planTier: selectedPlan,
232
- useCase: useCase.trim() || "",
233
- userEmail
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(errorData.error || `failed to create request: ${response.status}`);
262
+ throw new Error(
263
+ errorData.error || `failed to create request: ${response.status}`
264
+ );
239
265
  }
240
266
  alertApi.post({
241
- message: "API access request submitted successfully",
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 create API access request: ${errorMessage}`,
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
- render: (data) => {
264
- const request = data.rowData;
265
- if (!request?.metadata?.name) {
266
- return /* @__PURE__ */ React.createElement(Box, null);
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
- ], [apiProductName, apiKeyValues]);
274
- const DetailPanelContent = ({ request, apiName: api, revealedApiKey }) => {
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(Box, { p: 3, bgcolor: "background.default", onClick: (e) => e.stopPropagation() }, request.spec.useCase && /* @__PURE__ */ React.createElement(Box, { mb: 3 }, /* @__PURE__ */ React.createElement(Typography, { variant: "h6", gutterBottom: true }, "Use Case"), /* @__PURE__ */ React.createElement(Box, { p: 2, bgcolor: "background.paper", borderRadius: 1, border: "1px solid rgba(0, 0, 0, 0.12)" }, /* @__PURE__ */ React.createElement(
279
- Typography,
318
+ return /* @__PURE__ */ React.createElement(
319
+ Box,
280
320
  {
281
- variant: "body2",
282
- style: {
283
- whiteSpace: "pre-wrap",
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
- ))), /* @__PURE__ */ React.createElement(Typography, { variant: "h6", gutterBottom: true }, "Usage Examples"), /* @__PURE__ */ React.createElement(Typography, { variant: "body2", paragraph: true }, "Use these code examples to test the API with your ", request.spec.planTier, " tier key."), /* @__PURE__ */ React.createElement(Box, { onClick: (e) => e.stopPropagation() }, /* @__PURE__ */ React.createElement(
290
- Tabs,
291
- {
292
- value: selectedLanguage,
293
- onChange: (e, newValue) => {
294
- e.stopPropagation();
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
- indicatorColor: "primary"
298
- },
299
- /* @__PURE__ */ React.createElement(Tab, { label: "cURL", onClick: (e) => e.stopPropagation() }),
300
- /* @__PURE__ */ React.createElement(Tab, { label: "Node.js", onClick: (e) => e.stopPropagation() }),
301
- /* @__PURE__ */ React.createElement(Tab, { label: "Python", onClick: (e) => e.stopPropagation() }),
302
- /* @__PURE__ */ React.createElement(Tab, { label: "Go", onClick: (e) => e.stopPropagation() })
303
- )), /* @__PURE__ */ React.createElement(Box, { mt: 2 }, selectedLanguage === 0 && /* @__PURE__ */ React.createElement(
304
- CodeSnippet,
305
- {
306
- text: `curl -X GET https://${hostname}/api/v1/endpoint \\
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
- language: "bash",
309
- showCopyCodeButton: true
310
- }
311
- ), selectedLanguage === 1 && /* @__PURE__ */ React.createElement(
312
- CodeSnippet,
313
- {
314
- text: `const fetch = require('node-fetch');
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
- language: "javascript",
329
- showCopyCodeButton: true
330
- }
331
- ), selectedLanguage === 2 && /* @__PURE__ */ React.createElement(
332
- CodeSnippet,
333
- {
334
- text: `import requests
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
- language: "python",
346
- showCopyCodeButton: true
347
- }
348
- ), selectedLanguage === 3 && /* @__PURE__ */ React.createElement(
349
- CodeSnippet,
350
- {
351
- text: `package main
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
- language: "go",
378
- showCopyCodeButton: true
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((r) => !r.status?.phase || r.status.phase === "Pending");
400
- const approvedRequests = myRequests.filter((r) => r.status?.phase === "Approved");
401
- const rejectedRequests = myRequests.filter((r) => r.status?.phase === "Rejected");
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({ namespace: row.metadata.namespace, name: row.metadata.name });
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(Tooltip, { title: isVisible ? "Hide API key" : "Reveal API key (one-time only)" }, /* @__PURE__ */ React.createElement("span", null, /* @__PURE__ */ React.createElement(
470
- IconButton,
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
- size: "small",
473
- onClick: handleRevealClick,
474
- disabled: isLoading || isAlreadyRead && !apiKeyValue
542
+ title: isVisible ? "Hide API key" : "Reveal API key (one-time only)"
475
543
  },
476
- isVisible ? /* @__PURE__ */ React.createElement(VisibilityOffIcon, null) : /* @__PURE__ */ React.createElement(VisibilityIcon, null)
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(ownerId, userId, canDeleteOwnKey, canDeleteAllKeys);
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) return /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, "-");
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(ownerId, userId, canDeleteOwnKey, canDeleteAllKeys);
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(Box, { display: "flex", flexDirection: "column", alignItems: "flex-end", mb: 2 }, /* @__PURE__ */ React.createElement(
608
- Button,
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
- variant: "contained",
611
- color: "primary",
612
- startIcon: /* @__PURE__ */ React.createElement(AddIcon, null),
613
- onClick: () => setOpen(true),
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
- "Request API Access"
619
- ), plans.length === 0 && /* @__PURE__ */ React.createElement(Typography, { variant: "caption", color: "textSecondary", style: { marginTop: 4 }, "data-testid": "no-plans-message" }, !apiProduct ? "API product not found" : (() => {
620
- const readyCondition = apiProduct.status?.conditions?.find((c) => c.type === "Ready");
621
- const planCondition = apiProduct.status?.conditions?.find((c) => c.type === "PlanPolicyDiscovered");
622
- if (readyCondition?.status !== "True") {
623
- return `HTTPRoute not ready: ${readyCondition?.message || "unknown"}`;
624
- }
625
- if (planCondition?.status !== "True") {
626
- return `No plans discovered: ${planCondition?.message || "no PlanPolicy found"}`;
627
- }
628
- return "No plans available";
629
- })()))), 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(
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(Dialog, { open, onClose: () => setOpen(false), maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ React.createElement(DialogTitle, null, "Request API Access"), /* @__PURE__ */ React.createElement(DialogContent, null, createError && /* @__PURE__ */ React.createElement(Box, { mb: 2, p: 2, bgcolor: "error.main", color: "error.contrastText", borderRadius: 1 }, /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, createError)), /* @__PURE__ */ React.createElement(FormControl, { fullWidth: true, margin: "normal", disabled: creating, "data-testid": "tier-select-form" }, /* @__PURE__ */ React.createElement(InputLabel, { id: "tier-select-label" }, "Select Tier"), /* @__PURE__ */ React.createElement(
680
- Select,
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
- onClick: handleRequestAccess,
710
- color: "primary",
711
- variant: "contained",
712
- disabled: !selectedPlan || creating,
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
- creating ? "Submitting..." : "Submit Request"
716
- ))), /* @__PURE__ */ React.createElement(
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(/* @__PURE__ */ React.createElement(MenuItem, { key: "edit", onClick: handleMenuEdit }, "Edit"));
902
+ items.push(
903
+ /* @__PURE__ */ React.createElement(MenuItem, { key: "edit", onClick: handleMenuEdit }, "Edit")
904
+ );
732
905
  }
733
- items.push(/* @__PURE__ */ React.createElement(MenuItem, { key: "delete", onClick: handleMenuDeleteClick }, "Delete"));
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(pendingKeyReveal.namespace, pendingKeyReveal.name);
961
+ fetchApiKeyFromSecret(
962
+ pendingKeyReveal.namespace,
963
+ pendingKeyReveal.name
964
+ );
787
965
  toggleVisibility(pendingKeyReveal.name);
788
966
  }
789
967
  setShowOnceWarningOpen(false);