@kuadrant/kuadrant-backstage-plugin-frontend 0.0.2-dev-1cc1a15 → 0.0.2-dev-7d09bfa
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/ApiAccessCard/ApiAccessCard.esm.js +229 -11
- package/dist/components/ApiAccessCard/ApiAccessCard.esm.js.map +1 -1
- package/dist/components/ApiKeyManagementTab/ApiKeyManagementTab.esm.js +15 -151
- package/dist/components/ApiKeyManagementTab/ApiKeyManagementTab.esm.js.map +1 -1
- package/dist/components/ApiProductDetailPage/ApiProductDetailPage.esm.js +10 -15
- package/dist/components/ApiProductDetailPage/ApiProductDetailPage.esm.js.map +1 -1
- package/dist/components/ApiProductDetails/ApiProductDetails.esm.js +6 -1
- package/dist/components/ApiProductDetails/ApiProductDetails.esm.js.map +1 -1
- package/dist/components/ApiProductInfoCard/ApiProductInfoCard.esm.js +15 -2
- package/dist/components/ApiProductInfoCard/ApiProductInfoCard.esm.js.map +1 -1
- package/dist/components/CreateAPIProductDialog/CreateAPIProductDialog.esm.js +2 -4
- package/dist/components/CreateAPIProductDialog/CreateAPIProductDialog.esm.js.map +1 -1
- package/dist/components/OidcProviderCard/OidcProviderCard.esm.js +23 -0
- package/dist/components/OidcProviderCard/OidcProviderCard.esm.js.map +1 -0
- package/dist/components/RequestAccessDialog/RequestAccessDialog.esm.js +167 -0
- package/dist/components/RequestAccessDialog/RequestAccessDialog.esm.js.map +1 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.esm.js +1 -1
- package/dist/plugin.esm.js +1 -9
- package/dist/plugin.esm.js.map +1 -1
- package/dist/utils/validation.esm.js.map +1 -1
- package/dist-scalprum/configSchema.json +27 -1
- package/dist-scalprum/internal.plugin-kuadrant.5d9e1e73c7f21bbed39a.js +2 -0
- package/dist-scalprum/internal.plugin-kuadrant.5d9e1e73c7f21bbed39a.js.map +1 -0
- package/dist-scalprum/plugin-manifest.json +2 -2
- package/dist-scalprum/static/1994.19e6a1c5.chunk.js +3 -0
- package/dist-scalprum/static/1994.19e6a1c5.chunk.js.map +1 -0
- package/dist-scalprum/static/{2628.6619bf8b.chunk.js → 2628.0605e07f.chunk.js} +3 -3
- package/dist-scalprum/static/2628.0605e07f.chunk.js.map +1 -0
- package/dist-scalprum/static/2752.df63f31c.chunk.js +2 -0
- package/dist-scalprum/static/2752.df63f31c.chunk.js.map +1 -0
- package/dist-scalprum/static/2822.27a4ac5b.chunk.js +2 -0
- package/dist-scalprum/static/2822.27a4ac5b.chunk.js.map +1 -0
- package/dist-scalprum/static/2967.8d9c1b1f.chunk.js +2 -0
- package/dist-scalprum/static/2967.8d9c1b1f.chunk.js.map +1 -0
- package/dist-scalprum/static/{3097.4bd6b35f.chunk.js → 3097.582b68d3.chunk.js} +2 -2
- package/dist-scalprum/static/{3097.4bd6b35f.chunk.js.map → 3097.582b68d3.chunk.js.map} +1 -1
- package/dist-scalprum/static/327.cd6b3fee.chunk.js +2 -0
- package/dist-scalprum/static/327.cd6b3fee.chunk.js.map +1 -0
- package/dist-scalprum/static/3459.a7c29521.chunk.js +2 -0
- package/dist-scalprum/static/3459.a7c29521.chunk.js.map +1 -0
- package/dist-scalprum/static/3584.c820a5c7.chunk.js +2 -0
- package/dist-scalprum/static/3584.c820a5c7.chunk.js.map +1 -0
- package/dist-scalprum/static/3587.490690d6.chunk.js +2 -0
- package/dist-scalprum/static/3587.490690d6.chunk.js.map +1 -0
- package/dist-scalprum/static/{3647.b96f9b3e.chunk.js → 3647.67079e5f.chunk.js} +2 -2
- package/dist-scalprum/static/{3647.b96f9b3e.chunk.js.map → 3647.67079e5f.chunk.js.map} +1 -1
- package/dist-scalprum/static/3650.4f0dc550.chunk.js +2 -0
- package/dist-scalprum/static/3650.4f0dc550.chunk.js.map +1 -0
- package/dist-scalprum/static/3947.7458971d.chunk.js +2 -0
- package/dist-scalprum/static/3947.7458971d.chunk.js.map +1 -0
- package/dist-scalprum/static/3984.7bc07774.chunk.js +2 -0
- package/dist-scalprum/static/3984.7bc07774.chunk.js.map +1 -0
- package/dist-scalprum/static/4302.9a59485e.chunk.js +2 -0
- package/dist-scalprum/static/4302.9a59485e.chunk.js.map +1 -0
- package/dist-scalprum/static/441.f708f1e0.chunk.js +2 -0
- package/dist-scalprum/static/441.f708f1e0.chunk.js.map +1 -0
- package/dist-scalprum/static/4611.0d064cdf.chunk.js +2 -0
- package/dist-scalprum/static/4611.0d064cdf.chunk.js.map +1 -0
- package/dist-scalprum/static/4651.c85cecc4.chunk.js +2 -0
- package/dist-scalprum/static/4651.c85cecc4.chunk.js.map +1 -0
- package/dist-scalprum/static/{4682.6959fcd1.chunk.js → 4682.3c7098a8.chunk.js} +2 -2
- package/dist-scalprum/static/{4682.6959fcd1.chunk.js.map → 4682.3c7098a8.chunk.js.map} +1 -1
- package/dist-scalprum/static/501.87de76da.chunk.js +3 -0
- package/dist-scalprum/static/501.87de76da.chunk.js.LICENSE.txt +21 -0
- package/dist-scalprum/static/501.87de76da.chunk.js.map +1 -0
- package/dist-scalprum/static/{5010.a4aa0f8e.chunk.js → 5010.2228c754.chunk.js} +3 -3
- package/dist-scalprum/static/{5010.a4aa0f8e.chunk.js.map → 5010.2228c754.chunk.js.map} +1 -1
- package/dist-scalprum/static/5203.43732b3f.chunk.js +2 -0
- package/dist-scalprum/static/5203.43732b3f.chunk.js.map +1 -0
- package/dist-scalprum/static/5235.2a59dc45.chunk.js +2 -0
- package/dist-scalprum/static/5235.2a59dc45.chunk.js.map +1 -0
- package/dist-scalprum/static/5453.b3ee2392.chunk.js +2 -0
- package/dist-scalprum/static/5453.b3ee2392.chunk.js.map +1 -0
- package/dist-scalprum/static/{7556.aa8a002f.chunk.js → 5568.5dbce633.chunk.js} +3 -3
- package/dist-scalprum/static/{3466.43dfe991.chunk.js.LICENSE.txt → 5568.5dbce633.chunk.js.LICENSE.txt} +9 -0
- package/dist-scalprum/static/5568.5dbce633.chunk.js.map +1 -0
- package/dist-scalprum/static/6272.ef31cb1c.chunk.js +2 -0
- package/dist-scalprum/static/6272.ef31cb1c.chunk.js.map +1 -0
- package/dist-scalprum/static/6371.c4899d73.chunk.js +3 -0
- package/dist-scalprum/static/6371.c4899d73.chunk.js.map +1 -0
- package/dist-scalprum/static/6386.6386.ce38cef3.css +5 -0
- package/dist-scalprum/static/6386.6386.ce38cef3.css.map +1 -0
- package/dist-scalprum/static/6386.903891f3.chunk.js +3 -0
- package/dist-scalprum/static/6386.903891f3.chunk.js.LICENSE.txt +26 -0
- package/dist-scalprum/static/6386.903891f3.chunk.js.map +1 -0
- package/dist-scalprum/static/65.0e01be7c.chunk.js +2 -0
- package/dist-scalprum/static/65.0e01be7c.chunk.js.map +1 -0
- package/dist-scalprum/static/6753.76832e72.chunk.js +2 -0
- package/dist-scalprum/static/6753.76832e72.chunk.js.map +1 -0
- package/dist-scalprum/static/{8563.7e068fb0.chunk.js → 6763.d6cd937f.chunk.js} +3 -3
- package/dist-scalprum/static/6763.d6cd937f.chunk.js.map +1 -0
- package/dist-scalprum/static/{6800.736d5da3.chunk.js → 6800.8ec3a2eb.chunk.js} +2 -2
- package/dist-scalprum/static/6800.8ec3a2eb.chunk.js.map +1 -0
- package/dist-scalprum/static/{6840.4728fab9.chunk.js → 6840.6cc88a16.chunk.js} +2 -2
- package/dist-scalprum/static/6840.6cc88a16.chunk.js.map +1 -0
- package/dist-scalprum/static/7076.8745d395.chunk.js +2 -0
- package/dist-scalprum/static/7076.8745d395.chunk.js.map +1 -0
- package/dist-scalprum/static/7367.62c9669e.chunk.js +3 -0
- package/dist-scalprum/static/7367.62c9669e.chunk.js.LICENSE.txt +21 -0
- package/dist-scalprum/static/7367.62c9669e.chunk.js.map +1 -0
- package/dist-scalprum/static/7791.55db7365.chunk.js +2 -0
- package/dist-scalprum/static/7791.55db7365.chunk.js.map +1 -0
- package/dist-scalprum/static/8172.e89bbae7.chunk.js +2 -0
- package/dist-scalprum/static/8172.e89bbae7.chunk.js.map +1 -0
- package/dist-scalprum/static/8627.111cbac9.chunk.js +2 -0
- package/dist-scalprum/static/8627.111cbac9.chunk.js.map +1 -0
- package/dist-scalprum/static/8799.4ea4639c.chunk.js +2 -0
- package/dist-scalprum/static/8799.4ea4639c.chunk.js.map +1 -0
- package/dist-scalprum/static/9510.e4112e19.chunk.js +3 -0
- package/dist-scalprum/static/{2946.167c50c2.chunk.js.LICENSE.txt → 9510.e4112e19.chunk.js.LICENSE.txt} +0 -10
- package/dist-scalprum/static/9510.e4112e19.chunk.js.map +1 -0
- package/dist-scalprum/static/9644.7d342123.chunk.js +2 -0
- package/dist-scalprum/static/9644.7d342123.chunk.js.map +1 -0
- package/dist-scalprum/static/993.c164940e.chunk.js +2 -0
- package/dist-scalprum/static/993.c164940e.chunk.js.map +1 -0
- package/dist-scalprum/static/exposed-PluginRoot.5b6638e2.chunk.js +2 -0
- package/dist-scalprum/static/exposed-PluginRoot.5b6638e2.chunk.js.map +1 -0
- package/package.json +3 -1
- package/dist/components/PlanPolicyDetailPage/PlanPolicyDetailPage.esm.js +0 -89
- package/dist/components/PlanPolicyDetailPage/PlanPolicyDetailPage.esm.js.map +0 -1
- package/dist/components/PlanPolicyDetailPage/index.esm.js +0 -2
- package/dist/components/PlanPolicyDetailPage/index.esm.js.map +0 -1
- package/dist-scalprum/internal.plugin-kuadrant.58a9d553e354df0eed24.js +0 -2
- package/dist-scalprum/internal.plugin-kuadrant.58a9d553e354df0eed24.js.map +0 -1
- package/dist-scalprum/static/1085.536aa0fa.chunk.js +0 -3
- package/dist-scalprum/static/1085.536aa0fa.chunk.js.map +0 -1
- package/dist-scalprum/static/1613.71f0fccd.chunk.js +0 -3
- package/dist-scalprum/static/1613.71f0fccd.chunk.js.LICENSE.txt +0 -10
- package/dist-scalprum/static/1613.71f0fccd.chunk.js.map +0 -1
- package/dist-scalprum/static/1836.b74b4c40.chunk.js +0 -3
- package/dist-scalprum/static/1836.b74b4c40.chunk.js.LICENSE.txt +0 -10
- package/dist-scalprum/static/1836.b74b4c40.chunk.js.map +0 -1
- package/dist-scalprum/static/2198.5905970e.chunk.js +0 -2
- package/dist-scalprum/static/2198.5905970e.chunk.js.map +0 -1
- package/dist-scalprum/static/2628.6619bf8b.chunk.js.map +0 -1
- package/dist-scalprum/static/2759.fceb317f.chunk.js +0 -2
- package/dist-scalprum/static/2759.fceb317f.chunk.js.map +0 -1
- package/dist-scalprum/static/2928.4303c12e.chunk.js +0 -3
- package/dist-scalprum/static/2928.4303c12e.chunk.js.map +0 -1
- package/dist-scalprum/static/2946.167c50c2.chunk.js +0 -3
- package/dist-scalprum/static/2946.167c50c2.chunk.js.map +0 -1
- package/dist-scalprum/static/2967.ac3a4bee.chunk.js +0 -2
- package/dist-scalprum/static/2967.ac3a4bee.chunk.js.map +0 -1
- package/dist-scalprum/static/2987.1da15560.chunk.js +0 -2
- package/dist-scalprum/static/2987.1da15560.chunk.js.map +0 -1
- package/dist-scalprum/static/3459.5c90b5a3.chunk.js +0 -2
- package/dist-scalprum/static/3459.5c90b5a3.chunk.js.map +0 -1
- package/dist-scalprum/static/3466.43dfe991.chunk.js +0 -3
- package/dist-scalprum/static/3466.43dfe991.chunk.js.map +0 -1
- package/dist-scalprum/static/3503.66b6e510.chunk.js +0 -2
- package/dist-scalprum/static/3503.66b6e510.chunk.js.map +0 -1
- package/dist-scalprum/static/3650.515c743a.chunk.js +0 -2
- package/dist-scalprum/static/3650.515c743a.chunk.js.map +0 -1
- package/dist-scalprum/static/3657.59d45756.chunk.js +0 -3
- package/dist-scalprum/static/3657.59d45756.chunk.js.LICENSE.txt +0 -10
- package/dist-scalprum/static/3657.59d45756.chunk.js.map +0 -1
- package/dist-scalprum/static/428.0a290bc6.chunk.js +0 -2
- package/dist-scalprum/static/428.0a290bc6.chunk.js.map +0 -1
- package/dist-scalprum/static/441.9f02e61b.chunk.js +0 -2
- package/dist-scalprum/static/441.9f02e61b.chunk.js.map +0 -1
- package/dist-scalprum/static/5453.280127dd.chunk.js +0 -2
- package/dist-scalprum/static/5453.280127dd.chunk.js.map +0 -1
- package/dist-scalprum/static/5603.05d9ca7f.chunk.js +0 -2
- package/dist-scalprum/static/5603.05d9ca7f.chunk.js.map +0 -1
- package/dist-scalprum/static/6272.b5ee5195.chunk.js +0 -3
- package/dist-scalprum/static/6272.b5ee5195.chunk.js.LICENSE.txt +0 -9
- package/dist-scalprum/static/6272.b5ee5195.chunk.js.map +0 -1
- package/dist-scalprum/static/6371.c83dc422.chunk.js +0 -2
- package/dist-scalprum/static/6371.c83dc422.chunk.js.map +0 -1
- package/dist-scalprum/static/6422.97baf774.chunk.js +0 -2
- package/dist-scalprum/static/6422.97baf774.chunk.js.map +0 -1
- package/dist-scalprum/static/6800.736d5da3.chunk.js.map +0 -1
- package/dist-scalprum/static/6840.4728fab9.chunk.js.map +0 -1
- package/dist-scalprum/static/7556.aa8a002f.chunk.js.LICENSE.txt +0 -8
- package/dist-scalprum/static/7556.aa8a002f.chunk.js.map +0 -1
- package/dist-scalprum/static/7601.4df83556.chunk.js +0 -3
- package/dist-scalprum/static/7601.4df83556.chunk.js.LICENSE.txt +0 -9
- package/dist-scalprum/static/7601.4df83556.chunk.js.map +0 -1
- package/dist-scalprum/static/7791.39417f8c.chunk.js +0 -2
- package/dist-scalprum/static/7791.39417f8c.chunk.js.map +0 -1
- package/dist-scalprum/static/7984.c8511b89.chunk.js +0 -2
- package/dist-scalprum/static/7984.c8511b89.chunk.js.map +0 -1
- package/dist-scalprum/static/8365.d3360f18.chunk.js +0 -2
- package/dist-scalprum/static/8365.d3360f18.chunk.js.map +0 -1
- package/dist-scalprum/static/8563.7e068fb0.chunk.js.map +0 -1
- package/dist-scalprum/static/8799.7c749838.chunk.js +0 -2
- package/dist-scalprum/static/8799.7c749838.chunk.js.map +0 -1
- package/dist-scalprum/static/exposed-PluginRoot.a5792fb2.chunk.js +0 -2
- package/dist-scalprum/static/exposed-PluginRoot.a5792fb2.chunk.js.map +0 -1
- /package/dist-scalprum/static/{2928.4303c12e.chunk.js.LICENSE.txt → 1994.19e6a1c5.chunk.js.LICENSE.txt} +0 -0
- /package/dist-scalprum/static/{2628.6619bf8b.chunk.js.LICENSE.txt → 2628.0605e07f.chunk.js.LICENSE.txt} +0 -0
- /package/dist-scalprum/static/{5010.a4aa0f8e.chunk.js.LICENSE.txt → 5010.2228c754.chunk.js.LICENSE.txt} +0 -0
- /package/dist-scalprum/static/{1085.536aa0fa.chunk.js.LICENSE.txt → 6371.c4899d73.chunk.js.LICENSE.txt} +0 -0
- /package/dist-scalprum/static/{8563.7e068fb0.chunk.js.LICENSE.txt → 6763.d6cd937f.chunk.js.LICENSE.txt} +0 -0
|
@@ -1,22 +1,39 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
2
|
import { useAsync } from 'react-use';
|
|
3
3
|
import { Progress, ResponseErrorPanel, InfoCard } from '@backstage/core-components';
|
|
4
|
-
import { Box, Typography, Chip } from '@material-ui/core';
|
|
5
|
-
import
|
|
4
|
+
import { Box, Typography, Button, Dialog, DialogTitle, DialogContent, DialogActions, Chip, Tooltip, IconButton } from '@material-ui/core';
|
|
5
|
+
import AddIcon from '@material-ui/icons/Add';
|
|
6
|
+
import VisibilityIcon from '@material-ui/icons/Visibility';
|
|
7
|
+
import VisibilityOffIcon from '@material-ui/icons/VisibilityOff';
|
|
8
|
+
import FileCopyIcon from '@material-ui/icons/FileCopy';
|
|
9
|
+
import WarningIcon from '@material-ui/icons/Warning';
|
|
10
|
+
import { useApi, configApiRef, identityApiRef, fetchApiRef, alertApiRef } from '@backstage/core-plugin-api';
|
|
6
11
|
import { useEntity } from '@backstage/plugin-catalog-react';
|
|
12
|
+
import { RequestAccessDialog } from '../RequestAccessDialog/RequestAccessDialog.esm.js';
|
|
13
|
+
import { useKuadrantPermission } from '../../utils/permissions.esm.js';
|
|
14
|
+
import { kuadrantApiKeyCreatePermission } from '../../permissions.esm.js';
|
|
7
15
|
|
|
8
16
|
const ApiAccessCard = ({ namespace: propNamespace }) => {
|
|
9
17
|
const { entity } = useEntity();
|
|
10
18
|
const config = useApi(configApiRef);
|
|
11
19
|
const identityApi = useApi(identityApiRef);
|
|
12
20
|
const fetchApi = useApi(fetchApiRef);
|
|
21
|
+
const alertApi = useApi(alertApiRef);
|
|
13
22
|
const backendUrl = config.getString("backend.baseUrl");
|
|
14
|
-
const [,
|
|
23
|
+
const [userEmail, setUserEmail] = useState("");
|
|
24
|
+
const [requestDialogOpen, setRequestDialogOpen] = useState(false);
|
|
25
|
+
const [refresh, setRefresh] = useState(0);
|
|
26
|
+
const [visibleKeys, setVisibleKeys] = useState(/* @__PURE__ */ new Set());
|
|
27
|
+
const [apiKeyValues, setApiKeyValues] = useState(/* @__PURE__ */ new Map());
|
|
28
|
+
const [apiKeyLoading, setApiKeyLoading] = useState(/* @__PURE__ */ new Set());
|
|
29
|
+
const [alreadyReadKeys, setAlreadyReadKeys] = useState(/* @__PURE__ */ new Set());
|
|
30
|
+
const [showOnceWarningOpen, setShowOnceWarningOpen] = useState(false);
|
|
31
|
+
const [pendingKeyReveal, setPendingKeyReveal] = useState(null);
|
|
15
32
|
const apiProductName = entity.metadata.annotations?.["kuadrant.io/apiproduct"] || entity.metadata.name;
|
|
16
33
|
const namespace = entity.metadata.annotations?.["kuadrant.io/namespace"] || propNamespace || "default";
|
|
17
34
|
useAsync(async () => {
|
|
18
|
-
const
|
|
19
|
-
|
|
35
|
+
const profile = await identityApi.getProfileInfo();
|
|
36
|
+
setUserEmail(profile.email || "");
|
|
20
37
|
}, [identityApi]);
|
|
21
38
|
const { value: requests, loading: keysLoading, error: keysError } = useAsync(async () => {
|
|
22
39
|
const url = `${backendUrl}/api/kuadrant/requests/my?namespace=${namespace}` ;
|
|
@@ -29,18 +46,219 @@ const ApiAccessCard = ({ namespace: propNamespace }) => {
|
|
|
29
46
|
return allRequests.filter(
|
|
30
47
|
(r) => r.spec.apiProductRef?.name === apiProductName && r.status?.phase === "Approved"
|
|
31
48
|
);
|
|
32
|
-
}, [namespace, apiProductName, backendUrl, fetchApi]);
|
|
33
|
-
|
|
49
|
+
}, [namespace, apiProductName, backendUrl, fetchApi, refresh]);
|
|
50
|
+
const { value: apiProduct, loading: productLoading } = useAsync(async () => {
|
|
51
|
+
const response = await fetchApi.fetch(`${backendUrl}/api/kuadrant/apiproducts`);
|
|
52
|
+
if (!response.ok) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
const data = await response.json();
|
|
56
|
+
return data.items?.find(
|
|
57
|
+
(p) => p.metadata.namespace === namespace && p.metadata.name === apiProductName
|
|
58
|
+
);
|
|
59
|
+
}, [namespace, apiProductName, fetchApi, backendUrl]);
|
|
60
|
+
const resourceRef = apiProduct ? `apiproduct:${apiProduct.metadata.namespace}/${apiProduct.metadata.name}` : undefined;
|
|
61
|
+
const { allowed: canCreateRequest, loading: permissionLoading } = useKuadrantPermission(
|
|
62
|
+
kuadrantApiKeyCreatePermission,
|
|
63
|
+
resourceRef
|
|
64
|
+
);
|
|
65
|
+
const fetchApiKeyFromSecret = async (requestNamespace, requestName) => {
|
|
66
|
+
const key = `${requestNamespace}/${requestName}`;
|
|
67
|
+
if (apiKeyLoading.has(key)) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
setApiKeyLoading((prev) => new Set(prev).add(key));
|
|
71
|
+
try {
|
|
72
|
+
const response = await fetchApi.fetch(
|
|
73
|
+
`${backendUrl}/api/kuadrant/apikeys/${requestNamespace}/${requestName}/secret`
|
|
74
|
+
);
|
|
75
|
+
if (response.ok) {
|
|
76
|
+
const data = await response.json();
|
|
77
|
+
setApiKeyValues((prev) => new Map(prev).set(key, data.apiKey));
|
|
78
|
+
setAlreadyReadKeys((prev) => new Set(prev).add(key));
|
|
79
|
+
} else if (response.status === 403) {
|
|
80
|
+
setAlreadyReadKeys((prev) => new Set(prev).add(key));
|
|
81
|
+
alertApi.post({
|
|
82
|
+
message: "This API key has already been viewed and cannot be retrieved again.",
|
|
83
|
+
severity: "warning",
|
|
84
|
+
display: "transient"
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
} catch (err) {
|
|
88
|
+
console.error("failed to fetch api key:", err);
|
|
89
|
+
} finally {
|
|
90
|
+
setApiKeyLoading((prev) => {
|
|
91
|
+
const next = new Set(prev);
|
|
92
|
+
next.delete(key);
|
|
93
|
+
return next;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
const clearApiKeyValue = (requestNamespace, requestName) => {
|
|
98
|
+
const key = `${requestNamespace}/${requestName}`;
|
|
99
|
+
setApiKeyValues((prev) => {
|
|
100
|
+
const next = new Map(prev);
|
|
101
|
+
next.delete(key);
|
|
102
|
+
return next;
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
const toggleVisibility = (keyName) => {
|
|
106
|
+
setVisibleKeys((prev) => {
|
|
107
|
+
const newSet = new Set(prev);
|
|
108
|
+
if (newSet.has(keyName)) {
|
|
109
|
+
newSet.delete(keyName);
|
|
110
|
+
} else {
|
|
111
|
+
newSet.add(keyName);
|
|
112
|
+
}
|
|
113
|
+
return newSet;
|
|
114
|
+
});
|
|
115
|
+
};
|
|
116
|
+
const loading = keysLoading || productLoading || permissionLoading;
|
|
117
|
+
if (loading) {
|
|
34
118
|
return /* @__PURE__ */ React.createElement(Progress, null);
|
|
35
119
|
}
|
|
36
120
|
if (keysError) {
|
|
37
121
|
return /* @__PURE__ */ React.createElement(ResponseErrorPanel, { error: keysError });
|
|
38
122
|
}
|
|
39
123
|
const keys = requests || [];
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
124
|
+
const plans = apiProduct?.status?.discoveredPlans || [];
|
|
125
|
+
const canRequest = canCreateRequest && plans.length > 0;
|
|
126
|
+
const renderKeyRow = (request) => {
|
|
127
|
+
const key = `${request.metadata.namespace}/${request.metadata.name}`;
|
|
128
|
+
const isVisible = visibleKeys.has(request.metadata.name);
|
|
129
|
+
const isLoading = apiKeyLoading.has(key);
|
|
130
|
+
const apiKeyValue = apiKeyValues.get(key);
|
|
131
|
+
const hasSecretRef = request.status?.secretRef?.name;
|
|
132
|
+
const canReadSecret = request.status?.canReadSecret !== false;
|
|
133
|
+
const isAlreadyRead = alreadyReadKeys.has(key) || !canReadSecret;
|
|
134
|
+
const handleRevealClick = () => {
|
|
135
|
+
if (isVisible) {
|
|
136
|
+
clearApiKeyValue(request.metadata.namespace, request.metadata.name);
|
|
137
|
+
toggleVisibility(request.metadata.name);
|
|
138
|
+
} else if (!isAlreadyRead) {
|
|
139
|
+
setPendingKeyReveal({
|
|
140
|
+
namespace: request.metadata.namespace,
|
|
141
|
+
name: request.metadata.name
|
|
142
|
+
});
|
|
143
|
+
setShowOnceWarningOpen(true);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
const handleCopy = async () => {
|
|
147
|
+
if (apiKeyValue) {
|
|
148
|
+
await navigator.clipboard.writeText(apiKeyValue);
|
|
149
|
+
alertApi.post({
|
|
150
|
+
message: "API key copied to clipboard",
|
|
151
|
+
severity: "success",
|
|
152
|
+
display: "transient"
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
return /* @__PURE__ */ React.createElement(
|
|
157
|
+
Box,
|
|
158
|
+
{
|
|
159
|
+
key: request.metadata.name,
|
|
160
|
+
mb: 1,
|
|
161
|
+
p: 1.5,
|
|
162
|
+
border: 1,
|
|
163
|
+
borderColor: "grey.300",
|
|
164
|
+
borderRadius: 4
|
|
165
|
+
},
|
|
166
|
+
/* @__PURE__ */ React.createElement(Box, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 1 }, /* @__PURE__ */ React.createElement(Typography, { variant: "body2", style: { fontWeight: 500 } }, request.metadata.name), /* @__PURE__ */ React.createElement(Chip, { label: request.spec.planTier, color: "primary", size: "small" })),
|
|
167
|
+
hasSecretRef && /* @__PURE__ */ React.createElement(Box, { display: "flex", alignItems: "center" }, /* @__PURE__ */ React.createElement(
|
|
168
|
+
Typography,
|
|
169
|
+
{
|
|
170
|
+
variant: "body2",
|
|
171
|
+
style: {
|
|
172
|
+
fontFamily: "monospace",
|
|
173
|
+
fontSize: "0.8rem",
|
|
174
|
+
flex: 1,
|
|
175
|
+
overflow: "hidden",
|
|
176
|
+
textOverflow: "ellipsis"
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
isLoading ? "Loading..." : isVisible && apiKeyValue ? apiKeyValue : isAlreadyRead && !apiKeyValue ? "Already viewed" : "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"
|
|
180
|
+
), 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(
|
|
181
|
+
Tooltip,
|
|
182
|
+
{
|
|
183
|
+
title: isAlreadyRead && !apiKeyValue ? "Key already viewed" : isVisible ? "Hide API key" : "Reveal API key (one-time only)"
|
|
184
|
+
},
|
|
185
|
+
/* @__PURE__ */ React.createElement("span", null, /* @__PURE__ */ React.createElement(
|
|
186
|
+
IconButton,
|
|
187
|
+
{
|
|
188
|
+
size: "small",
|
|
189
|
+
onClick: handleRevealClick,
|
|
190
|
+
disabled: isLoading || isAlreadyRead && !apiKeyValue
|
|
191
|
+
},
|
|
192
|
+
isVisible ? /* @__PURE__ */ React.createElement(VisibilityOffIcon, { fontSize: "small" }) : /* @__PURE__ */ React.createElement(VisibilityIcon, { fontSize: "small" })
|
|
193
|
+
))
|
|
194
|
+
))
|
|
195
|
+
);
|
|
196
|
+
};
|
|
197
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(InfoCard, { title: "Kuadrant API Keys" }, /* @__PURE__ */ React.createElement(Box, { p: 2 }, keys.length > 0 ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Typography, { variant: "body2", color: "textSecondary", gutterBottom: true }, keys.length, " active key", keys.length !== 1 ? "s" : ""), keys.map(renderKeyRow)) : /* @__PURE__ */ React.createElement(Typography, { variant: "body1", gutterBottom: true }, "You don't have any API keys for this API yet"), /* @__PURE__ */ React.createElement(Box, { mt: 2 }, canRequest ? /* @__PURE__ */ React.createElement(
|
|
198
|
+
Button,
|
|
199
|
+
{
|
|
200
|
+
variant: "contained",
|
|
201
|
+
color: "primary",
|
|
202
|
+
size: "small",
|
|
203
|
+
startIcon: /* @__PURE__ */ React.createElement(AddIcon, null),
|
|
204
|
+
onClick: () => setRequestDialogOpen(true)
|
|
205
|
+
},
|
|
206
|
+
"Request API Access"
|
|
207
|
+
) : /* @__PURE__ */ React.createElement(Typography, { variant: "caption", color: "textSecondary" }, "Visit the API Keys tab to view and manage access")))), /* @__PURE__ */ React.createElement(
|
|
208
|
+
RequestAccessDialog,
|
|
209
|
+
{
|
|
210
|
+
open: requestDialogOpen,
|
|
211
|
+
onClose: () => setRequestDialogOpen(false),
|
|
212
|
+
onSuccess: () => {
|
|
213
|
+
setRequestDialogOpen(false);
|
|
214
|
+
setRefresh((r) => r + 1);
|
|
215
|
+
},
|
|
216
|
+
apiProductName,
|
|
217
|
+
namespace,
|
|
218
|
+
userEmail,
|
|
219
|
+
plans
|
|
220
|
+
}
|
|
221
|
+
), /* @__PURE__ */ React.createElement(
|
|
222
|
+
Dialog,
|
|
223
|
+
{
|
|
224
|
+
open: showOnceWarningOpen,
|
|
225
|
+
onClose: () => {
|
|
226
|
+
setShowOnceWarningOpen(false);
|
|
227
|
+
setPendingKeyReveal(null);
|
|
228
|
+
},
|
|
229
|
+
maxWidth: "sm"
|
|
230
|
+
},
|
|
231
|
+
/* @__PURE__ */ React.createElement(DialogTitle, null, /* @__PURE__ */ React.createElement(Box, { display: "flex", alignItems: "center" }, /* @__PURE__ */ React.createElement(WarningIcon, { color: "primary", style: { marginRight: 8 } }), "View API Key")),
|
|
232
|
+
/* @__PURE__ */ React.createElement(DialogContent, null, /* @__PURE__ */ React.createElement(Typography, { variant: "body1", paragraph: true }, "This API key can only be viewed ", /* @__PURE__ */ React.createElement("strong", null, "once"), ". After you reveal it, you will not be able to retrieve it again."), /* @__PURE__ */ React.createElement(Typography, { variant: "body2", color: "textSecondary" }, "Make sure to copy and store it securely before closing this view.")),
|
|
233
|
+
/* @__PURE__ */ React.createElement(DialogActions, null, /* @__PURE__ */ React.createElement(
|
|
234
|
+
Button,
|
|
235
|
+
{
|
|
236
|
+
onClick: () => {
|
|
237
|
+
setShowOnceWarningOpen(false);
|
|
238
|
+
setPendingKeyReveal(null);
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
"Cancel"
|
|
242
|
+
), /* @__PURE__ */ React.createElement(
|
|
243
|
+
Button,
|
|
244
|
+
{
|
|
245
|
+
variant: "contained",
|
|
246
|
+
color: "primary",
|
|
247
|
+
onClick: () => {
|
|
248
|
+
if (pendingKeyReveal) {
|
|
249
|
+
fetchApiKeyFromSecret(
|
|
250
|
+
pendingKeyReveal.namespace,
|
|
251
|
+
pendingKeyReveal.name
|
|
252
|
+
);
|
|
253
|
+
toggleVisibility(pendingKeyReveal.name);
|
|
254
|
+
}
|
|
255
|
+
setShowOnceWarningOpen(false);
|
|
256
|
+
setPendingKeyReveal(null);
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
"Reveal API Key"
|
|
260
|
+
))
|
|
261
|
+
));
|
|
44
262
|
};
|
|
45
263
|
|
|
46
264
|
export { ApiAccessCard };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApiAccessCard.esm.js","sources":["../../../src/components/ApiAccessCard/ApiAccessCard.tsx"],"sourcesContent":["import React, { useState } from 'react';\nimport { useAsync } from 'react-use';\nimport {\n InfoCard,\n Progress,\n ResponseErrorPanel,\n} from '@backstage/core-components';\nimport {\n Typography,\n Box,\n Chip,\n} from '@material-ui/core';\nimport { useApi, configApiRef, identityApiRef, fetchApiRef } from '@backstage/core-plugin-api';\nimport { useEntity } from '@backstage/plugin-catalog-react';\n\ninterface APIKey {\n metadata: {\n name: string;\n namespace: string;\n };\n spec: {\n apiProductRef: {\n name: string;\n };\n planTier: string;\n };\n status?: {\n phase: 'Pending' | 'Approved' | 'Rejected';\n apiKey?: string;\n };\n}\n\nexport interface ApiAccessCardProps {\n // deprecated: use entity annotations instead\n namespace?: string;\n}\n\nexport const ApiAccessCard = ({ namespace: propNamespace }: ApiAccessCardProps) => {\n const { entity } = useEntity();\n const config = useApi(configApiRef);\n const identityApi = useApi(identityApiRef);\n const fetchApi = useApi(fetchApiRef);\n const backendUrl = config.getString('backend.baseUrl');\n const [, setUserId] = useState<string>('guest');\n\n // get apiproduct name from entity annotation (set by entity provider)\n const apiProductName = entity.metadata.annotations?.['kuadrant.io/apiproduct'] || entity.metadata.name;\n const namespace = entity.metadata.annotations?.['kuadrant.io/namespace'] || propNamespace || 'default';\n\n // get current user identity\n useAsync(async () => {\n const identity = await identityApi.getBackstageIdentity();\n setUserId(identity.userEntityRef.split('/')[1] || 'guest');\n }, [identityApi]);\n\n const { value: requests, loading: keysLoading, error: keysError } = useAsync(async () => {\n const url = namespace\n ? `${backendUrl}/api/kuadrant/requests/my?namespace=${namespace}`\n : `${backendUrl}/api/kuadrant/requests/my`;\n const response = await fetchApi.fetch(url);\n if (!response.ok) {\n throw new Error('failed to fetch api key requests');\n }\n const data = await response.json();\n // filter to only this apiproduct's approved requests\n const allRequests = data.items || [];\n return allRequests.filter((r: APIKey) =>\n r.spec.apiProductRef?.name === apiProductName && r.status?.phase === 'Approved'\n );\n }, [namespace, apiProductName, backendUrl, fetchApi]);\n\n if (keysLoading) {\n return <Progress />;\n }\n\n if (keysError) {\n return <ResponseErrorPanel error={keysError} />;\n }\n\n const keys = (requests as APIKey[]) || [];\n\n return (\n <>\n <InfoCard title=\"Kuadrant API Keys\">\n <Box p={2}>\n {keys.length > 0 ? (\n <>\n <Typography variant=\"body1\" gutterBottom>\n You have {keys.length} active API key{keys.length !== 1 ? 's' : ''} for this API\n </Typography>\n {keys.map((request: APIKey) => {\n const planTier = request.spec.planTier;\n\n return (\n <Box key={request.metadata.name} mb={1} p={1} border={1} borderColor=\"grey.300\" borderRadius={4}>\n <Box display=\"flex\" justifyContent=\"space-between\" alignItems=\"center\">\n <Typography variant=\"body2\">\n {request.metadata.name}\n </Typography>\n <Chip label={planTier} color=\"primary\" size=\"small\" />\n </Box>\n </Box>\n );\n })}\n <Box mt={2}>\n <Typography variant=\"caption\" color=\"textSecondary\">\n Visit the API Keys tab to view keys, make new requests, or manage access\n </Typography>\n </Box>\n </>\n ) : (\n <>\n <Typography variant=\"body1\" gutterBottom>\n You don't have any API keys for this API yet\n </Typography>\n <Typography variant=\"body2\" color=\"textSecondary\">\n Visit the API Keys tab to request access\n </Typography>\n </>\n )}\n </Box>\n </InfoCard>\n </>\n );\n};\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ApiAccessCard.esm.js","sources":["../../../src/components/ApiAccessCard/ApiAccessCard.tsx"],"sourcesContent":["import React, { useState } from 'react';\nimport { useAsync } from 'react-use';\nimport {\n InfoCard,\n Progress,\n ResponseErrorPanel,\n} from '@backstage/core-components';\nimport {\n Typography,\n Box,\n Chip,\n Button,\n IconButton,\n Tooltip,\n Dialog,\n DialogTitle,\n DialogContent,\n DialogActions,\n} from '@material-ui/core';\nimport AddIcon from '@material-ui/icons/Add';\nimport VisibilityIcon from '@material-ui/icons/Visibility';\nimport VisibilityOffIcon from '@material-ui/icons/VisibilityOff';\nimport FileCopyIcon from '@material-ui/icons/FileCopy';\nimport WarningIcon from '@material-ui/icons/Warning';\nimport { useApi, configApiRef, identityApiRef, fetchApiRef, alertApiRef } from '@backstage/core-plugin-api';\nimport { useEntity } from '@backstage/plugin-catalog-react';\nimport { RequestAccessDialog, Plan } from '../RequestAccessDialog';\nimport { useKuadrantPermission } from '../../utils/permissions';\nimport { kuadrantApiKeyCreatePermission } from '../../permissions';\n\ninterface APIKey {\n metadata: {\n name: string;\n namespace: string;\n };\n spec: {\n apiProductRef: {\n name: string;\n };\n planTier: string;\n };\n status?: {\n phase: 'Pending' | 'Approved' | 'Rejected';\n secretRef?: {\n name: string;\n key: string;\n };\n canReadSecret?: boolean;\n };\n}\n\ninterface APIProduct {\n metadata: {\n name: string;\n namespace: string;\n };\n status?: {\n discoveredPlans?: Plan[];\n };\n}\n\nexport interface ApiAccessCardProps {\n // deprecated: use entity annotations instead\n namespace?: string;\n}\n\nexport const ApiAccessCard = ({ namespace: propNamespace }: ApiAccessCardProps) => {\n const { entity } = useEntity();\n const config = useApi(configApiRef);\n const identityApi = useApi(identityApiRef);\n const fetchApi = useApi(fetchApiRef);\n const alertApi = useApi(alertApiRef);\n const backendUrl = config.getString('backend.baseUrl');\n const [userEmail, setUserEmail] = useState<string>('');\n const [requestDialogOpen, setRequestDialogOpen] = useState(false);\n const [refresh, setRefresh] = useState(0);\n\n // key reveal state\n const [visibleKeys, setVisibleKeys] = useState<Set<string>>(new Set());\n const [apiKeyValues, setApiKeyValues] = useState<Map<string, string>>(new Map());\n const [apiKeyLoading, setApiKeyLoading] = useState<Set<string>>(new Set());\n const [alreadyReadKeys, setAlreadyReadKeys] = useState<Set<string>>(new Set());\n const [showOnceWarningOpen, setShowOnceWarningOpen] = useState(false);\n const [pendingKeyReveal, setPendingKeyReveal] = useState<{\n namespace: string;\n name: string;\n } | null>(null);\n\n // get apiproduct name from entity annotation (set by entity provider)\n const apiProductName = entity.metadata.annotations?.['kuadrant.io/apiproduct'] || entity.metadata.name;\n const namespace = entity.metadata.annotations?.['kuadrant.io/namespace'] || propNamespace || 'default';\n\n // get current user identity\n useAsync(async () => {\n const profile = await identityApi.getProfileInfo();\n setUserEmail(profile.email || '');\n }, [identityApi]);\n\n // fetch user's approved keys\n const { value: requests, loading: keysLoading, error: keysError } = useAsync(async () => {\n const url = namespace\n ? `${backendUrl}/api/kuadrant/requests/my?namespace=${namespace}`\n : `${backendUrl}/api/kuadrant/requests/my`;\n const response = await fetchApi.fetch(url);\n if (!response.ok) {\n throw new Error('failed to fetch api key requests');\n }\n const data = await response.json();\n const allRequests = data.items || [];\n return allRequests.filter((r: APIKey) =>\n r.spec.apiProductRef?.name === apiProductName && r.status?.phase === 'Approved'\n );\n }, [namespace, apiProductName, backendUrl, fetchApi, refresh]);\n\n // fetch apiproduct to get available plans\n const { value: apiProduct, loading: productLoading } = useAsync(async () => {\n const response = await fetchApi.fetch(`${backendUrl}/api/kuadrant/apiproducts`);\n if (!response.ok) {\n return null;\n }\n const data = await response.json();\n return data.items?.find(\n (p: APIProduct) =>\n p.metadata.namespace === namespace &&\n p.metadata.name === apiProductName,\n );\n }, [namespace, apiProductName, fetchApi, backendUrl]);\n\n const resourceRef = apiProduct\n ? `apiproduct:${apiProduct.metadata.namespace}/${apiProduct.metadata.name}`\n : undefined;\n\n const { allowed: canCreateRequest, loading: permissionLoading } = useKuadrantPermission(\n kuadrantApiKeyCreatePermission,\n resourceRef,\n );\n\n const fetchApiKeyFromSecret = async (requestNamespace: string, requestName: string) => {\n const key = `${requestNamespace}/${requestName}`;\n if (apiKeyLoading.has(key)) {\n return;\n }\n\n setApiKeyLoading((prev) => new Set(prev).add(key));\n try {\n const response = await fetchApi.fetch(\n `${backendUrl}/api/kuadrant/apikeys/${requestNamespace}/${requestName}/secret`,\n );\n if (response.ok) {\n const data = await response.json();\n setApiKeyValues((prev) => new Map(prev).set(key, data.apiKey));\n setAlreadyReadKeys((prev) => new Set(prev).add(key));\n } else if (response.status === 403) {\n setAlreadyReadKeys((prev) => new Set(prev).add(key));\n alertApi.post({\n message: 'This API key has already been viewed and cannot be retrieved again.',\n severity: 'warning',\n display: 'transient',\n });\n }\n } catch (err) {\n console.error('failed to fetch api key:', err);\n } finally {\n setApiKeyLoading((prev) => {\n const next = new Set(prev);\n next.delete(key);\n return next;\n });\n }\n };\n\n const clearApiKeyValue = (requestNamespace: string, requestName: string) => {\n const key = `${requestNamespace}/${requestName}`;\n setApiKeyValues((prev) => {\n const next = new Map(prev);\n next.delete(key);\n return next;\n });\n };\n\n const toggleVisibility = (keyName: string) => {\n setVisibleKeys((prev) => {\n const newSet = new Set(prev);\n if (newSet.has(keyName)) {\n newSet.delete(keyName);\n } else {\n newSet.add(keyName);\n }\n return newSet;\n });\n };\n\n const loading = keysLoading || productLoading || permissionLoading;\n\n if (loading) {\n return <Progress />;\n }\n\n if (keysError) {\n return <ResponseErrorPanel error={keysError} />;\n }\n\n const keys = (requests as APIKey[]) || [];\n const plans = (apiProduct?.status?.discoveredPlans || []) as Plan[];\n const canRequest = canCreateRequest && plans.length > 0;\n\n const renderKeyRow = (request: APIKey) => {\n const key = `${request.metadata.namespace}/${request.metadata.name}`;\n const isVisible = visibleKeys.has(request.metadata.name);\n const isLoading = apiKeyLoading.has(key);\n const apiKeyValue = apiKeyValues.get(key);\n const hasSecretRef = request.status?.secretRef?.name;\n const canReadSecret = request.status?.canReadSecret !== false;\n const isAlreadyRead = alreadyReadKeys.has(key) || !canReadSecret;\n\n const handleRevealClick = () => {\n if (isVisible) {\n clearApiKeyValue(request.metadata.namespace, request.metadata.name);\n toggleVisibility(request.metadata.name);\n } else if (!isAlreadyRead) {\n setPendingKeyReveal({\n namespace: request.metadata.namespace,\n name: request.metadata.name,\n });\n setShowOnceWarningOpen(true);\n }\n };\n\n const handleCopy = async () => {\n if (apiKeyValue) {\n await navigator.clipboard.writeText(apiKeyValue);\n alertApi.post({\n message: 'API key copied to clipboard',\n severity: 'success',\n display: 'transient',\n });\n }\n };\n\n return (\n <Box\n key={request.metadata.name}\n mb={1}\n p={1.5}\n border={1}\n borderColor=\"grey.300\"\n borderRadius={4}\n >\n <Box display=\"flex\" justifyContent=\"space-between\" alignItems=\"center\" mb={1}>\n <Typography variant=\"body2\" style={{ fontWeight: 500 }}>\n {request.metadata.name}\n </Typography>\n <Chip label={request.spec.planTier} color=\"primary\" size=\"small\" />\n </Box>\n {hasSecretRef && (\n <Box display=\"flex\" alignItems=\"center\">\n <Typography\n variant=\"body2\"\n style={{\n fontFamily: 'monospace',\n fontSize: '0.8rem',\n flex: 1,\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {isLoading\n ? 'Loading...'\n : isVisible && apiKeyValue\n ? apiKeyValue\n : isAlreadyRead && !apiKeyValue\n ? 'Already viewed'\n : '••••••••••••••••'}\n </Typography>\n {isVisible && apiKeyValue && (\n <Tooltip title=\"Copy to clipboard\">\n <IconButton size=\"small\" onClick={handleCopy}>\n <FileCopyIcon fontSize=\"small\" />\n </IconButton>\n </Tooltip>\n )}\n <Tooltip\n title={\n isAlreadyRead && !apiKeyValue\n ? 'Key already viewed'\n : isVisible\n ? 'Hide API key'\n : 'Reveal API key (one-time only)'\n }\n >\n <span>\n <IconButton\n size=\"small\"\n onClick={handleRevealClick}\n disabled={isLoading || (isAlreadyRead && !apiKeyValue)}\n >\n {isVisible ? <VisibilityOffIcon fontSize=\"small\" /> : <VisibilityIcon fontSize=\"small\" />}\n </IconButton>\n </span>\n </Tooltip>\n </Box>\n )}\n </Box>\n );\n };\n\n return (\n <>\n <InfoCard title=\"Kuadrant API Keys\">\n <Box p={2}>\n {keys.length > 0 ? (\n <>\n <Typography variant=\"body2\" color=\"textSecondary\" gutterBottom>\n {keys.length} active key{keys.length !== 1 ? 's' : ''}\n </Typography>\n {keys.map(renderKeyRow)}\n </>\n ) : (\n <Typography variant=\"body1\" gutterBottom>\n You don't have any API keys for this API yet\n </Typography>\n )}\n <Box mt={2}>\n {canRequest ? (\n <Button\n variant=\"contained\"\n color=\"primary\"\n size=\"small\"\n startIcon={<AddIcon />}\n onClick={() => setRequestDialogOpen(true)}\n >\n Request API Access\n </Button>\n ) : (\n <Typography variant=\"caption\" color=\"textSecondary\">\n Visit the API Keys tab to view and manage access\n </Typography>\n )}\n </Box>\n </Box>\n </InfoCard>\n\n <RequestAccessDialog\n open={requestDialogOpen}\n onClose={() => setRequestDialogOpen(false)}\n onSuccess={() => {\n setRequestDialogOpen(false);\n setRefresh((r) => r + 1);\n }}\n apiProductName={apiProductName}\n namespace={namespace}\n userEmail={userEmail}\n plans={plans}\n />\n\n <Dialog\n open={showOnceWarningOpen}\n onClose={() => {\n setShowOnceWarningOpen(false);\n setPendingKeyReveal(null);\n }}\n maxWidth=\"sm\"\n >\n <DialogTitle>\n <Box display=\"flex\" alignItems=\"center\">\n <WarningIcon color=\"primary\" style={{ marginRight: 8 }} />\n View API Key\n </Box>\n </DialogTitle>\n <DialogContent>\n <Typography variant=\"body1\" paragraph>\n This API key can only be viewed <strong>once</strong>. After you\n reveal it, you will not be able to retrieve it again.\n </Typography>\n <Typography variant=\"body2\" color=\"textSecondary\">\n Make sure to copy and store it securely before closing this view.\n </Typography>\n </DialogContent>\n <DialogActions>\n <Button\n onClick={() => {\n setShowOnceWarningOpen(false);\n setPendingKeyReveal(null);\n }}\n >\n Cancel\n </Button>\n <Button\n variant=\"contained\"\n color=\"primary\"\n onClick={() => {\n if (pendingKeyReveal) {\n fetchApiKeyFromSecret(\n pendingKeyReveal.namespace,\n pendingKeyReveal.name,\n );\n toggleVisibility(pendingKeyReveal.name);\n }\n setShowOnceWarningOpen(false);\n setPendingKeyReveal(null);\n }}\n >\n Reveal API Key\n </Button>\n </DialogActions>\n </Dialog>\n </>\n );\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useState, useMemo } from 'react';
|
|
2
2
|
import { useAsync } from 'react-use';
|
|
3
3
|
import { CodeSnippet, Progress, ResponseErrorPanel, Table } from '@backstage/core-components';
|
|
4
|
-
import { Box, Typography, Tabs, Tab, Grid, Button,
|
|
4
|
+
import { Box, Typography, Tabs, Tab, Grid, Button, Menu, MenuItem, Dialog, DialogTitle, DialogContent, DialogActions, Chip, Tooltip, IconButton, CircularProgress } from '@material-ui/core';
|
|
5
5
|
import { useApi, configApiRef, identityApiRef, fetchApiRef, alertApiRef } from '@backstage/core-plugin-api';
|
|
6
6
|
import { useEntity } from '@backstage/plugin-catalog-react';
|
|
7
7
|
import VisibilityIcon from '@material-ui/icons/Visibility';
|
|
@@ -12,12 +12,12 @@ 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';
|
|
16
15
|
import { kuadrantApiKeyCreatePermission, kuadrantApiKeyDeleteOwnPermission, kuadrantApiKeyDeleteAllPermission, kuadrantApiKeyUpdateOwnPermission } from '../../permissions.esm.js';
|
|
17
16
|
import { useKuadrantPermission, canDeleteResource } from '../../utils/permissions.esm.js';
|
|
18
17
|
import { EditAPIKeyDialog } from '../EditAPIKeyDialog/EditAPIKeyDialog.esm.js';
|
|
19
18
|
import { ConfirmDeleteDialog } from '../ConfirmDeleteDialog/ConfirmDeleteDialog.esm.js';
|
|
20
19
|
import { generateAuthCodeSnippets } from '../../utils/codeSnippets.esm.js';
|
|
20
|
+
import { RequestAccessDialog } from '../RequestAccessDialog/RequestAccessDialog.esm.js';
|
|
21
21
|
|
|
22
22
|
const ApiKeyManagementTab = ({
|
|
23
23
|
namespace: propNamespace
|
|
@@ -32,11 +32,7 @@ const ApiKeyManagementTab = ({
|
|
|
32
32
|
const [refresh, setRefresh] = useState(0);
|
|
33
33
|
const [userId, setUserId] = useState("");
|
|
34
34
|
const [userEmail, setUserEmail] = useState("");
|
|
35
|
-
const [
|
|
36
|
-
const [selectedPlan, setSelectedPlan] = useState("");
|
|
37
|
-
const [useCase, setUseCase] = useState("");
|
|
38
|
-
const [creating, setCreating] = useState(false);
|
|
39
|
-
const [createError, setCreateError] = useState(null);
|
|
35
|
+
const [requestDialogOpen, setRequestDialogOpen] = useState(false);
|
|
40
36
|
const [editDialogOpen, setEditDialogOpen] = useState(false);
|
|
41
37
|
const [requestToEdit, setRequestToEdit] = useState(null);
|
|
42
38
|
const [menuAnchor, setMenuAnchor] = useState(null);
|
|
@@ -242,54 +238,6 @@ const ApiKeyManagementTab = ({
|
|
|
242
238
|
return newSet;
|
|
243
239
|
});
|
|
244
240
|
};
|
|
245
|
-
const handleRequestAccess = async () => {
|
|
246
|
-
if (!selectedPlan) return;
|
|
247
|
-
setCreating(true);
|
|
248
|
-
setCreateError(null);
|
|
249
|
-
try {
|
|
250
|
-
const response = await fetchApi.fetch(
|
|
251
|
-
`${backendUrl}/api/kuadrant/requests`,
|
|
252
|
-
{
|
|
253
|
-
method: "POST",
|
|
254
|
-
headers: {
|
|
255
|
-
"Content-Type": "application/json"
|
|
256
|
-
},
|
|
257
|
-
body: JSON.stringify({
|
|
258
|
-
apiProductName,
|
|
259
|
-
namespace,
|
|
260
|
-
planTier: selectedPlan,
|
|
261
|
-
useCase: useCase.trim() || "",
|
|
262
|
-
userEmail
|
|
263
|
-
})
|
|
264
|
-
}
|
|
265
|
-
);
|
|
266
|
-
if (!response.ok) {
|
|
267
|
-
const errorData = await response.json().catch(() => ({}));
|
|
268
|
-
throw new Error(
|
|
269
|
-
errorData.error || `failed to create request: ${response.status}`
|
|
270
|
-
);
|
|
271
|
-
}
|
|
272
|
-
alertApi.post({
|
|
273
|
-
message: "API key requested successfully",
|
|
274
|
-
severity: "success",
|
|
275
|
-
display: "transient"
|
|
276
|
-
});
|
|
277
|
-
setOpen(false);
|
|
278
|
-
setSelectedPlan("");
|
|
279
|
-
setUseCase("");
|
|
280
|
-
setRefresh((r) => r + 1);
|
|
281
|
-
} catch (err) {
|
|
282
|
-
const errorMessage = err instanceof Error ? err.message : "unknown error occurred";
|
|
283
|
-
alertApi.post({
|
|
284
|
-
message: `Failed to request API key: ${errorMessage}`,
|
|
285
|
-
severity: "error",
|
|
286
|
-
display: "transient"
|
|
287
|
-
});
|
|
288
|
-
setCreateError(errorMessage);
|
|
289
|
-
} finally {
|
|
290
|
-
setCreating(false);
|
|
291
|
-
}
|
|
292
|
-
};
|
|
293
241
|
const detailPanelConfig = useMemo(
|
|
294
242
|
() => [
|
|
295
243
|
{
|
|
@@ -664,7 +612,7 @@ const ApiKeyManagementTab = ({
|
|
|
664
612
|
variant: "contained",
|
|
665
613
|
color: "primary",
|
|
666
614
|
startIcon: /* @__PURE__ */ React.createElement(AddIcon, null),
|
|
667
|
-
onClick: () =>
|
|
615
|
+
onClick: () => setRequestDialogOpen(true),
|
|
668
616
|
disabled: plans.length === 0,
|
|
669
617
|
"data-testid": "request-api-access-button",
|
|
670
618
|
"data-plans-count": plans.length
|
|
@@ -746,103 +694,19 @@ const ApiKeyManagementTab = ({
|
|
|
746
694
|
detailPanel: detailPanelConfig
|
|
747
695
|
}
|
|
748
696
|
))), /* @__PURE__ */ React.createElement(
|
|
749
|
-
|
|
697
|
+
RequestAccessDialog,
|
|
750
698
|
{
|
|
751
|
-
open,
|
|
752
|
-
onClose: () =>
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
/* @__PURE__ */ React.createElement(DialogTitle, null, "Request API Access"),
|
|
757
|
-
/* @__PURE__ */ React.createElement(DialogContent, null, /* @__PURE__ */ React.createElement(
|
|
758
|
-
Box,
|
|
759
|
-
{
|
|
760
|
-
mb: 2,
|
|
761
|
-
p: 1.5,
|
|
762
|
-
bgcolor: "info.light",
|
|
763
|
-
borderRadius: 1,
|
|
764
|
-
display: "flex",
|
|
765
|
-
alignItems: "flex-start",
|
|
766
|
-
style: { gap: 8 }
|
|
699
|
+
open: requestDialogOpen,
|
|
700
|
+
onClose: () => setRequestDialogOpen(false),
|
|
701
|
+
onSuccess: () => {
|
|
702
|
+
setRequestDialogOpen(false);
|
|
703
|
+
setRefresh((r) => r + 1);
|
|
767
704
|
},
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
style: { marginTop: 2 }
|
|
774
|
-
}
|
|
775
|
-
),
|
|
776
|
-
/* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, "Your request will be reviewed by an API owner before access is granted.")
|
|
777
|
-
), createError && /* @__PURE__ */ React.createElement(
|
|
778
|
-
Box,
|
|
779
|
-
{
|
|
780
|
-
mb: 2,
|
|
781
|
-
p: 2,
|
|
782
|
-
bgcolor: "error.main",
|
|
783
|
-
color: "error.contrastText",
|
|
784
|
-
borderRadius: 1
|
|
785
|
-
},
|
|
786
|
-
/* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, createError)
|
|
787
|
-
), /* @__PURE__ */ React.createElement(
|
|
788
|
-
FormControl,
|
|
789
|
-
{
|
|
790
|
-
fullWidth: true,
|
|
791
|
-
margin: "normal",
|
|
792
|
-
disabled: creating,
|
|
793
|
-
"data-testid": "tier-select-form"
|
|
794
|
-
},
|
|
795
|
-
/* @__PURE__ */ React.createElement(InputLabel, { id: "tier-select-label" }, "Select Tier"),
|
|
796
|
-
/* @__PURE__ */ React.createElement(
|
|
797
|
-
Select,
|
|
798
|
-
{
|
|
799
|
-
labelId: "tier-select-label",
|
|
800
|
-
"data-testid": "tier-select",
|
|
801
|
-
value: selectedPlan,
|
|
802
|
-
onChange: (e) => setSelectedPlan(e.target.value),
|
|
803
|
-
disabled: creating
|
|
804
|
-
},
|
|
805
|
-
plans.map((plan) => {
|
|
806
|
-
const limitDesc = Object.entries(plan.limits || {}).map(([key, val]) => `${val} per ${key}`).join(", ");
|
|
807
|
-
return /* @__PURE__ */ React.createElement(
|
|
808
|
-
MenuItem,
|
|
809
|
-
{
|
|
810
|
-
key: plan.tier,
|
|
811
|
-
value: plan.tier,
|
|
812
|
-
"data-testid": `tier-option-${plan.tier}`
|
|
813
|
-
},
|
|
814
|
-
plan.tier,
|
|
815
|
-
" ",
|
|
816
|
-
limitDesc ? `(${limitDesc})` : ""
|
|
817
|
-
);
|
|
818
|
-
})
|
|
819
|
-
)
|
|
820
|
-
), /* @__PURE__ */ React.createElement(
|
|
821
|
-
TextField,
|
|
822
|
-
{
|
|
823
|
-
label: "Use Case (optional)",
|
|
824
|
-
placeholder: "Describe how you plan to use this API",
|
|
825
|
-
multiline: true,
|
|
826
|
-
rows: 3,
|
|
827
|
-
fullWidth: true,
|
|
828
|
-
margin: "normal",
|
|
829
|
-
value: useCase,
|
|
830
|
-
onChange: (e) => setUseCase(e.target.value),
|
|
831
|
-
helperText: "Explain your intended use of this API for admin review",
|
|
832
|
-
disabled: creating
|
|
833
|
-
}
|
|
834
|
-
)),
|
|
835
|
-
/* @__PURE__ */ React.createElement(DialogActions, null, /* @__PURE__ */ React.createElement(Button, { onClick: () => setOpen(false), disabled: creating }, "Cancel"), /* @__PURE__ */ React.createElement(
|
|
836
|
-
Button,
|
|
837
|
-
{
|
|
838
|
-
onClick: handleRequestAccess,
|
|
839
|
-
color: "primary",
|
|
840
|
-
variant: "contained",
|
|
841
|
-
disabled: !selectedPlan || creating,
|
|
842
|
-
startIcon: creating ? /* @__PURE__ */ React.createElement(CircularProgress, { size: 16, color: "inherit" }) : undefined
|
|
843
|
-
},
|
|
844
|
-
creating ? "Submitting..." : "Submit Request"
|
|
845
|
-
))
|
|
705
|
+
apiProductName,
|
|
706
|
+
namespace,
|
|
707
|
+
userEmail,
|
|
708
|
+
plans
|
|
709
|
+
}
|
|
846
710
|
), /* @__PURE__ */ React.createElement(
|
|
847
711
|
Menu,
|
|
848
712
|
{
|