@kuadrant/kuadrant-backstage-plugin-frontend 0.0.1-test.1-1593c3ec

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 (39) hide show
  1. package/README.md +491 -0
  2. package/dist/components/ApiAccessCard/ApiAccessCard.esm.js +42 -0
  3. package/dist/components/ApiAccessCard/ApiAccessCard.esm.js.map +1 -0
  4. package/dist/components/ApiAccessCard/index.esm.js +2 -0
  5. package/dist/components/ApiAccessCard/index.esm.js.map +1 -0
  6. package/dist/components/ApiKeyManagementTab/ApiKeyManagementTab.esm.js +441 -0
  7. package/dist/components/ApiKeyManagementTab/ApiKeyManagementTab.esm.js.map +1 -0
  8. package/dist/components/ApiKeyManagementTab/index.esm.js +2 -0
  9. package/dist/components/ApiKeyManagementTab/index.esm.js.map +1 -0
  10. package/dist/components/ApiProductInfoCard/ApiProductInfoCard.esm.js +47 -0
  11. package/dist/components/ApiProductInfoCard/ApiProductInfoCard.esm.js.map +1 -0
  12. package/dist/components/ApiProductInfoCard/index.esm.js +2 -0
  13. package/dist/components/ApiProductInfoCard/index.esm.js.map +1 -0
  14. package/dist/components/ApprovalQueueCard/ApprovalQueueCard.esm.js +349 -0
  15. package/dist/components/ApprovalQueueCard/ApprovalQueueCard.esm.js.map +1 -0
  16. package/dist/components/ApprovalQueueCard/index.esm.js +2 -0
  17. package/dist/components/ApprovalQueueCard/index.esm.js.map +1 -0
  18. package/dist/components/CreateAPIProductDialog/CreateAPIProductDialog.esm.js +289 -0
  19. package/dist/components/CreateAPIProductDialog/CreateAPIProductDialog.esm.js.map +1 -0
  20. package/dist/components/KuadrantPage/KuadrantPage.esm.js +198 -0
  21. package/dist/components/KuadrantPage/KuadrantPage.esm.js.map +1 -0
  22. package/dist/components/KuadrantPage/index.esm.js +2 -0
  23. package/dist/components/KuadrantPage/index.esm.js.map +1 -0
  24. package/dist/components/PermissionGate/PermissionGate.esm.js +33 -0
  25. package/dist/components/PermissionGate/PermissionGate.esm.js.map +1 -0
  26. package/dist/components/PlanPolicyDetailPage/PlanPolicyDetailPage.esm.js +89 -0
  27. package/dist/components/PlanPolicyDetailPage/PlanPolicyDetailPage.esm.js.map +1 -0
  28. package/dist/components/PlanPolicyDetailPage/index.esm.js +2 -0
  29. package/dist/components/PlanPolicyDetailPage/index.esm.js.map +1 -0
  30. package/dist/hooks/useUserRole.esm.js +49 -0
  31. package/dist/hooks/useUserRole.esm.js.map +1 -0
  32. package/dist/index.d.ts +31 -0
  33. package/dist/index.esm.js +6 -0
  34. package/dist/index.esm.js.map +1 -0
  35. package/dist/plugin.esm.js +68 -0
  36. package/dist/plugin.esm.js.map +1 -0
  37. package/dist/routes.esm.js +13 -0
  38. package/dist/routes.esm.js.map +1 -0
  39. package/package.json +85 -0
@@ -0,0 +1,289 @@
1
+ import React, { useState } from 'react';
2
+ import { Dialog, DialogTitle, DialogContent, Grid, TextField, MenuItem, Typography, Box, Chip, Button, DialogActions } from '@material-ui/core';
3
+ import { useApi, configApiRef, fetchApiRef } from '@backstage/core-plugin-api';
4
+ import { Alert } from '@material-ui/lab';
5
+ import useAsync from 'react-use/lib/useAsync';
6
+
7
+ const CreateAPIProductDialog = ({ open, onClose, onSuccess }) => {
8
+ const config = useApi(configApiRef);
9
+ const fetchApi = useApi(fetchApiRef);
10
+ const backendUrl = config.getString("backend.baseUrl");
11
+ const [name, setName] = useState("");
12
+ const [namespace, setNamespace] = useState("");
13
+ const [displayName, setDisplayName] = useState("");
14
+ const [description, setDescription] = useState("");
15
+ const [version, setVersion] = useState("v1");
16
+ const [approvalMode, setApprovalMode] = useState("manual");
17
+ const [tags, setTags] = useState([]);
18
+ const [tagInput, setTagInput] = useState("");
19
+ const [selectedPlanPolicy, setSelectedPlanPolicy] = useState("");
20
+ const [contactEmail, setContactEmail] = useState("");
21
+ const [contactTeam, setContactTeam] = useState("");
22
+ const [docsURL, setDocsURL] = useState("");
23
+ const [openAPISpec, setOpenAPISpec] = useState("");
24
+ const [error, setError] = useState("");
25
+ const [creating, setCreating] = useState(false);
26
+ const { value: planPolicies, loading: planPoliciesLoading } = useAsync(async () => {
27
+ const response = await fetchApi.fetch(`${backendUrl}/api/kuadrant/planpolicies`);
28
+ const data = await response.json();
29
+ return (data.items || []).filter(
30
+ (policy) => !namespace || policy.metadata.namespace === namespace
31
+ );
32
+ }, [backendUrl, fetchApi, open, namespace]);
33
+ const handleAddTag = () => {
34
+ if (tagInput.trim() && !tags.includes(tagInput.trim())) {
35
+ setTags([...tags, tagInput.trim()]);
36
+ setTagInput("");
37
+ }
38
+ };
39
+ const handleDeleteTag = (tagToDelete) => {
40
+ setTags(tags.filter((tag) => tag !== tagToDelete));
41
+ };
42
+ const handleCreate = async () => {
43
+ setError("");
44
+ setCreating(true);
45
+ try {
46
+ if (!selectedPlanPolicy) {
47
+ throw new Error("Please select a PlanPolicy");
48
+ }
49
+ const [selectedPlanNamespace, selectedPlanName] = selectedPlanPolicy.split("/");
50
+ if (selectedPlanNamespace !== namespace) {
51
+ throw new Error(`PlanPolicy must be in the same namespace as the APIProduct (${namespace}). Selected PlanPolicy is in ${selectedPlanNamespace}.`);
52
+ }
53
+ const apiProduct = {
54
+ apiVersion: "extensions.kuadrant.io/v1alpha1",
55
+ kind: "APIProduct",
56
+ metadata: {
57
+ name,
58
+ namespace
59
+ },
60
+ spec: {
61
+ displayName,
62
+ description,
63
+ version,
64
+ approvalMode,
65
+ tags,
66
+ planPolicyRef: {
67
+ name: selectedPlanName,
68
+ namespace: selectedPlanNamespace
69
+ },
70
+ plans: [],
71
+ // controller will inject plans from planPolicyRef
72
+ ...contactEmail || contactTeam ? {
73
+ contact: {
74
+ ...contactEmail && { email: contactEmail },
75
+ ...contactTeam && { team: contactTeam }
76
+ }
77
+ } : {},
78
+ ...docsURL || openAPISpec ? {
79
+ documentation: {
80
+ ...docsURL && { docsURL },
81
+ ...openAPISpec && { openAPISpec }
82
+ }
83
+ } : {}
84
+ }
85
+ };
86
+ const response = await fetchApi.fetch(`${backendUrl}/api/kuadrant/apiproducts`, {
87
+ method: "POST",
88
+ headers: {
89
+ "Content-Type": "application/json"
90
+ },
91
+ body: JSON.stringify(apiProduct)
92
+ });
93
+ if (!response.ok) {
94
+ const errorData = await response.json();
95
+ throw new Error(errorData.error || "failed to create apiproduct");
96
+ }
97
+ onSuccess();
98
+ handleClose();
99
+ } catch (err) {
100
+ setError(err instanceof Error ? err.message : String(err));
101
+ } finally {
102
+ setCreating(false);
103
+ }
104
+ };
105
+ const handleClose = () => {
106
+ setName("");
107
+ setNamespace("");
108
+ setDisplayName("");
109
+ setDescription("");
110
+ setVersion("v1");
111
+ setApprovalMode("manual");
112
+ setTags([]);
113
+ setTagInput("");
114
+ setSelectedPlanPolicy("");
115
+ setContactEmail("");
116
+ setContactTeam("");
117
+ setDocsURL("");
118
+ setOpenAPISpec("");
119
+ setError("");
120
+ onClose();
121
+ };
122
+ return /* @__PURE__ */ React.createElement(Dialog, { open, onClose: handleClose, maxWidth: "md", fullWidth: true }, /* @__PURE__ */ React.createElement(DialogTitle, null, "Create API Product"), /* @__PURE__ */ React.createElement(DialogContent, null, error && /* @__PURE__ */ React.createElement(Alert, { severity: "error", style: { marginBottom: 16 } }, error), /* @__PURE__ */ React.createElement(Grid, { container: true, spacing: 2 }, /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 6 }, /* @__PURE__ */ React.createElement(
123
+ TextField,
124
+ {
125
+ fullWidth: true,
126
+ label: "Name",
127
+ value: name,
128
+ onChange: (e) => setName(e.target.value),
129
+ placeholder: "my-api",
130
+ helperText: "Kubernetes resource name (lowercase, hyphens)",
131
+ margin: "normal",
132
+ required: true
133
+ }
134
+ )), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 6 }, /* @__PURE__ */ React.createElement(
135
+ TextField,
136
+ {
137
+ fullWidth: true,
138
+ label: "Namespace",
139
+ value: namespace,
140
+ onChange: (e) => setNamespace(e.target.value),
141
+ placeholder: "default",
142
+ margin: "normal",
143
+ required: true
144
+ }
145
+ )), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 6 }, /* @__PURE__ */ React.createElement(
146
+ TextField,
147
+ {
148
+ fullWidth: true,
149
+ label: "Display Name",
150
+ value: displayName,
151
+ onChange: (e) => setDisplayName(e.target.value),
152
+ placeholder: "My API",
153
+ margin: "normal",
154
+ required: true
155
+ }
156
+ )), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 6 }, /* @__PURE__ */ React.createElement(
157
+ TextField,
158
+ {
159
+ fullWidth: true,
160
+ label: "Version",
161
+ value: version,
162
+ onChange: (e) => setVersion(e.target.value),
163
+ placeholder: "v1",
164
+ margin: "normal"
165
+ }
166
+ )), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React.createElement(
167
+ TextField,
168
+ {
169
+ fullWidth: true,
170
+ select: true,
171
+ label: "Approval Mode",
172
+ value: approvalMode,
173
+ onChange: (e) => setApprovalMode(e.target.value),
174
+ margin: "normal",
175
+ helperText: "Automatic: keys are created immediately. Manual: requires approval."
176
+ },
177
+ /* @__PURE__ */ React.createElement(MenuItem, { value: "manual" }, "Manual"),
178
+ /* @__PURE__ */ React.createElement(MenuItem, { value: "automatic" }, "Automatic")
179
+ )), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React.createElement(
180
+ TextField,
181
+ {
182
+ fullWidth: true,
183
+ label: "Description",
184
+ value: description,
185
+ onChange: (e) => setDescription(e.target.value),
186
+ placeholder: "API description",
187
+ margin: "normal",
188
+ multiline: true,
189
+ rows: 2,
190
+ required: true
191
+ }
192
+ )), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React.createElement(Typography, { variant: "subtitle2", gutterBottom: true, style: { marginTop: 16 } }, "Tags"), /* @__PURE__ */ React.createElement(Box, { display: "flex", flexWrap: "wrap", marginBottom: 1, style: { gap: 8 } }, tags.map((tag) => /* @__PURE__ */ React.createElement(
193
+ Chip,
194
+ {
195
+ key: tag,
196
+ label: tag,
197
+ onDelete: () => handleDeleteTag(tag),
198
+ size: "small"
199
+ }
200
+ ))), /* @__PURE__ */ React.createElement(Box, { display: "flex", style: { gap: 8 } }, /* @__PURE__ */ React.createElement(
201
+ TextField,
202
+ {
203
+ fullWidth: true,
204
+ size: "small",
205
+ value: tagInput,
206
+ onChange: (e) => setTagInput(e.target.value),
207
+ onKeyPress: (e) => e.key === "Enter" && handleAddTag(),
208
+ placeholder: "Add tag"
209
+ }
210
+ ), /* @__PURE__ */ React.createElement(Button, { onClick: handleAddTag, variant: "outlined", size: "small" }, "Add"))), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React.createElement(
211
+ TextField,
212
+ {
213
+ fullWidth: true,
214
+ select: true,
215
+ label: "PlanPolicy",
216
+ value: selectedPlanPolicy,
217
+ onChange: (e) => setSelectedPlanPolicy(e.target.value),
218
+ margin: "normal",
219
+ required: true,
220
+ helperText: "Select an existing PlanPolicy resource",
221
+ disabled: planPoliciesLoading
222
+ },
223
+ planPoliciesLoading && /* @__PURE__ */ React.createElement(MenuItem, { value: "" }, "Loading..."),
224
+ !planPoliciesLoading && planPolicies && planPolicies.length === 0 && /* @__PURE__ */ React.createElement(MenuItem, { value: "" }, "No PlanPolicies available"),
225
+ !planPoliciesLoading && planPolicies && planPolicies.map((policy) => /* @__PURE__ */ React.createElement(
226
+ MenuItem,
227
+ {
228
+ key: `${policy.metadata.namespace}/${policy.metadata.name}`,
229
+ value: `${policy.metadata.namespace}/${policy.metadata.name}`
230
+ },
231
+ policy.metadata.name,
232
+ " (",
233
+ policy.metadata.namespace,
234
+ ")"
235
+ ))
236
+ )), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 6 }, /* @__PURE__ */ React.createElement(
237
+ TextField,
238
+ {
239
+ fullWidth: true,
240
+ label: "Contact Email",
241
+ value: contactEmail,
242
+ onChange: (e) => setContactEmail(e.target.value),
243
+ placeholder: "api-team@example.com",
244
+ margin: "normal"
245
+ }
246
+ )), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 6 }, /* @__PURE__ */ React.createElement(
247
+ TextField,
248
+ {
249
+ fullWidth: true,
250
+ label: "Contact Team",
251
+ value: contactTeam,
252
+ onChange: (e) => setContactTeam(e.target.value),
253
+ placeholder: "platform-team",
254
+ margin: "normal"
255
+ }
256
+ )), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 6 }, /* @__PURE__ */ React.createElement(
257
+ TextField,
258
+ {
259
+ fullWidth: true,
260
+ label: "Docs URL",
261
+ value: docsURL,
262
+ onChange: (e) => setDocsURL(e.target.value),
263
+ placeholder: "https://api.example.com/docs",
264
+ margin: "normal"
265
+ }
266
+ )), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 6 }, /* @__PURE__ */ React.createElement(
267
+ TextField,
268
+ {
269
+ fullWidth: true,
270
+ label: "OpenAPI Spec URL",
271
+ value: openAPISpec,
272
+ onChange: (e) => setOpenAPISpec(e.target.value),
273
+ placeholder: "https://api.example.com/openapi.json",
274
+ margin: "normal"
275
+ }
276
+ )))), /* @__PURE__ */ React.createElement(DialogActions, null, /* @__PURE__ */ React.createElement(Button, { onClick: handleClose }, "Cancel"), /* @__PURE__ */ React.createElement(
277
+ Button,
278
+ {
279
+ onClick: handleCreate,
280
+ color: "primary",
281
+ variant: "contained",
282
+ disabled: creating || !name || !namespace || !displayName || !description || !selectedPlanPolicy
283
+ },
284
+ creating ? "Creating..." : "Create"
285
+ )));
286
+ };
287
+
288
+ export { CreateAPIProductDialog };
289
+ //# sourceMappingURL=CreateAPIProductDialog.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CreateAPIProductDialog.esm.js","sources":["../../../src/components/CreateAPIProductDialog/CreateAPIProductDialog.tsx"],"sourcesContent":["import React, { useState } from 'react';\nimport {\n Dialog,\n DialogTitle,\n DialogContent,\n DialogActions,\n Button,\n TextField,\n Box,\n Typography,\n Chip,\n Grid,\n MenuItem,\n} from '@material-ui/core';\nimport { useApi, configApiRef, fetchApiRef } from '@backstage/core-plugin-api';\nimport { Alert } from '@material-ui/lab';\nimport useAsync from 'react-use/lib/useAsync';\n\ninterface CreateAPIProductDialogProps {\n open: boolean;\n onClose: () => void;\n onSuccess: () => void;\n}\n\nexport const CreateAPIProductDialog = ({ open, onClose, onSuccess }: CreateAPIProductDialogProps) => {\n const config = useApi(configApiRef);\n const fetchApi = useApi(fetchApiRef);\n const backendUrl = config.getString('backend.baseUrl');\n\n const [name, setName] = useState('');\n const [namespace, setNamespace] = useState('');\n const [displayName, setDisplayName] = useState('');\n const [description, setDescription] = useState('');\n const [version, setVersion] = useState('v1');\n const [approvalMode, setApprovalMode] = useState<'automatic' | 'manual'>('manual');\n const [tags, setTags] = useState<string[]>([]);\n const [tagInput, setTagInput] = useState('');\n const [selectedPlanPolicy, setSelectedPlanPolicy] = useState('');\n const [contactEmail, setContactEmail] = useState('');\n const [contactTeam, setContactTeam] = useState('');\n const [docsURL, setDocsURL] = useState('');\n const [openAPISpec, setOpenAPISpec] = useState('');\n const [error, setError] = useState('');\n const [creating, setCreating] = useState(false);\n\n const { value: planPolicies, loading: planPoliciesLoading } = useAsync(async () => {\n const response = await fetchApi.fetch(`${backendUrl}/api/kuadrant/planpolicies`);\n const data = await response.json();\n // filter to only show planpolicies in the same namespace as the apiproduct\n return (data.items || []).filter((policy: any) =>\n !namespace || policy.metadata.namespace === namespace\n );\n }, [backendUrl, fetchApi, open, namespace]);\n\n const handleAddTag = () => {\n if (tagInput.trim() && !tags.includes(tagInput.trim())) {\n setTags([...tags, tagInput.trim()]);\n setTagInput('');\n }\n };\n\n const handleDeleteTag = (tagToDelete: string) => {\n setTags(tags.filter(tag => tag !== tagToDelete));\n };\n\n const handleCreate = async () => {\n setError('');\n setCreating(true);\n\n try {\n if (!selectedPlanPolicy) {\n throw new Error('Please select a PlanPolicy');\n }\n\n const [selectedPlanNamespace, selectedPlanName] = selectedPlanPolicy.split('/');\n\n // Validate namespace matching\n if (selectedPlanNamespace !== namespace) {\n throw new Error(`PlanPolicy must be in the same namespace as the APIProduct (${namespace}). Selected PlanPolicy is in ${selectedPlanNamespace}.`);\n }\n\n const apiProduct = {\n apiVersion: 'extensions.kuadrant.io/v1alpha1',\n kind: 'APIProduct',\n metadata: {\n name,\n namespace,\n },\n spec: {\n displayName,\n description,\n version,\n approvalMode,\n tags,\n planPolicyRef: {\n name: selectedPlanName,\n namespace: selectedPlanNamespace,\n },\n plans: [], // controller will inject plans from planPolicyRef\n ...(contactEmail || contactTeam ? {\n contact: {\n ...(contactEmail && { email: contactEmail }),\n ...(contactTeam && { team: contactTeam }),\n },\n } : {}),\n ...(docsURL || openAPISpec ? {\n documentation: {\n ...(docsURL && { docsURL }),\n ...(openAPISpec && { openAPISpec }),\n },\n } : {}),\n },\n };\n\n const response = await fetchApi.fetch(`${backendUrl}/api/kuadrant/apiproducts`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(apiProduct),\n });\n\n if (!response.ok) {\n const errorData = await response.json();\n throw new Error(errorData.error || 'failed to create apiproduct');\n }\n\n onSuccess();\n handleClose();\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err));\n } finally {\n setCreating(false);\n }\n };\n\n const handleClose = () => {\n setName('');\n setNamespace('');\n setDisplayName('');\n setDescription('');\n setVersion('v1');\n setApprovalMode('manual');\n setTags([]);\n setTagInput('');\n setSelectedPlanPolicy('');\n setContactEmail('');\n setContactTeam('');\n setDocsURL('');\n setOpenAPISpec('');\n setError('');\n onClose();\n };\n\n return (\n <Dialog open={open} onClose={handleClose} maxWidth=\"md\" fullWidth>\n <DialogTitle>Create API Product</DialogTitle>\n <DialogContent>\n {error && (\n <Alert severity=\"error\" style={{ marginBottom: 16 }}>\n {error}\n </Alert>\n )}\n\n <Grid container spacing={2}>\n <Grid item xs={6}>\n <TextField\n fullWidth\n label=\"Name\"\n value={name}\n onChange={e => setName(e.target.value)}\n placeholder=\"my-api\"\n helperText=\"Kubernetes resource name (lowercase, hyphens)\"\n margin=\"normal\"\n required\n />\n </Grid>\n <Grid item xs={6}>\n <TextField\n fullWidth\n label=\"Namespace\"\n value={namespace}\n onChange={e => setNamespace(e.target.value)}\n placeholder=\"default\"\n margin=\"normal\"\n required\n />\n </Grid>\n <Grid item xs={6}>\n <TextField\n fullWidth\n label=\"Display Name\"\n value={displayName}\n onChange={e => setDisplayName(e.target.value)}\n placeholder=\"My API\"\n margin=\"normal\"\n required\n />\n </Grid>\n <Grid item xs={6}>\n <TextField\n fullWidth\n label=\"Version\"\n value={version}\n onChange={e => setVersion(e.target.value)}\n placeholder=\"v1\"\n margin=\"normal\"\n />\n </Grid>\n <Grid item xs={12}>\n <TextField\n fullWidth\n select\n label=\"Approval Mode\"\n value={approvalMode}\n onChange={e => setApprovalMode(e.target.value as 'automatic' | 'manual')}\n margin=\"normal\"\n helperText=\"Automatic: keys are created immediately. Manual: requires approval.\"\n >\n <MenuItem value=\"manual\">Manual</MenuItem>\n <MenuItem value=\"automatic\">Automatic</MenuItem>\n </TextField>\n </Grid>\n <Grid item xs={12}>\n <TextField\n fullWidth\n label=\"Description\"\n value={description}\n onChange={e => setDescription(e.target.value)}\n placeholder=\"API description\"\n margin=\"normal\"\n multiline\n rows={2}\n required\n />\n </Grid>\n\n <Grid item xs={12}>\n <Typography variant=\"subtitle2\" gutterBottom style={{ marginTop: 16 }}>\n Tags\n </Typography>\n <Box display=\"flex\" flexWrap=\"wrap\" marginBottom={1} style={{ gap: 8 }}>\n {tags.map(tag => (\n <Chip\n key={tag}\n label={tag}\n onDelete={() => handleDeleteTag(tag)}\n size=\"small\"\n />\n ))}\n </Box>\n <Box display=\"flex\" style={{ gap: 8 }}>\n <TextField\n fullWidth\n size=\"small\"\n value={tagInput}\n onChange={e => setTagInput(e.target.value)}\n onKeyPress={e => e.key === 'Enter' && handleAddTag()}\n placeholder=\"Add tag\"\n />\n <Button onClick={handleAddTag} variant=\"outlined\" size=\"small\">\n Add\n </Button>\n </Box>\n </Grid>\n\n <Grid item xs={12}>\n <TextField\n fullWidth\n select\n label=\"PlanPolicy\"\n value={selectedPlanPolicy}\n onChange={e => setSelectedPlanPolicy(e.target.value)}\n margin=\"normal\"\n required\n helperText=\"Select an existing PlanPolicy resource\"\n disabled={planPoliciesLoading}\n >\n {planPoliciesLoading && (\n <MenuItem value=\"\">Loading...</MenuItem>\n )}\n {!planPoliciesLoading && planPolicies && planPolicies.length === 0 && (\n <MenuItem value=\"\">No PlanPolicies available</MenuItem>\n )}\n {!planPoliciesLoading && planPolicies && planPolicies.map((policy: any) => (\n <MenuItem\n key={`${policy.metadata.namespace}/${policy.metadata.name}`}\n value={`${policy.metadata.namespace}/${policy.metadata.name}`}\n >\n {policy.metadata.name} ({policy.metadata.namespace})\n </MenuItem>\n ))}\n </TextField>\n </Grid>\n\n <Grid item xs={6}>\n <TextField\n fullWidth\n label=\"Contact Email\"\n value={contactEmail}\n onChange={e => setContactEmail(e.target.value)}\n placeholder=\"api-team@example.com\"\n margin=\"normal\"\n />\n </Grid>\n <Grid item xs={6}>\n <TextField\n fullWidth\n label=\"Contact Team\"\n value={contactTeam}\n onChange={e => setContactTeam(e.target.value)}\n placeholder=\"platform-team\"\n margin=\"normal\"\n />\n </Grid>\n <Grid item xs={6}>\n <TextField\n fullWidth\n label=\"Docs URL\"\n value={docsURL}\n onChange={e => setDocsURL(e.target.value)}\n placeholder=\"https://api.example.com/docs\"\n margin=\"normal\"\n />\n </Grid>\n <Grid item xs={6}>\n <TextField\n fullWidth\n label=\"OpenAPI Spec URL\"\n value={openAPISpec}\n onChange={e => setOpenAPISpec(e.target.value)}\n placeholder=\"https://api.example.com/openapi.json\"\n margin=\"normal\"\n />\n </Grid>\n </Grid>\n </DialogContent>\n <DialogActions>\n <Button onClick={handleClose}>Cancel</Button>\n <Button\n onClick={handleCreate}\n color=\"primary\"\n variant=\"contained\"\n disabled={creating || !name || !namespace || !displayName || !description || !selectedPlanPolicy}\n >\n {creating ? 'Creating...' : 'Create'}\n </Button>\n </DialogActions>\n </Dialog>\n );\n};\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,198 @@
1
+ import React, { useState } from 'react';
2
+ import { Box, Typography, Chip, Button, Grid, Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, IconButton } from '@material-ui/core';
3
+ import AddIcon from '@material-ui/icons/Add';
4
+ import DeleteIcon from '@material-ui/icons/Delete';
5
+ import { Page, Header, SupportButton, Content, ContentHeader, Progress, ResponseErrorPanel, InfoCard, Table, Link } from '@backstage/core-components';
6
+ import useAsync from 'react-use/lib/useAsync';
7
+ import { useApi, configApiRef, fetchApiRef } from '@backstage/core-plugin-api';
8
+ import { ApprovalQueueCard } from '../ApprovalQueueCard/ApprovalQueueCard.esm.js';
9
+ import { PermissionGate } from '../PermissionGate/PermissionGate.esm.js';
10
+ import { useUserRole } from '../../hooks/useUserRole.esm.js';
11
+ import { CreateAPIProductDialog } from '../CreateAPIProductDialog/CreateAPIProductDialog.esm.js';
12
+
13
+ const ResourceList = () => {
14
+ const config = useApi(configApiRef);
15
+ const fetchApi = useApi(fetchApiRef);
16
+ const backendUrl = config.getString("backend.baseUrl");
17
+ const { userInfo, loading: userLoading } = useUserRole();
18
+ const [createDialogOpen, setCreateDialogOpen] = useState(false);
19
+ const [refreshTrigger, setRefreshTrigger] = useState(0);
20
+ const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
21
+ const [apiProductToDelete, setApiProductToDelete] = useState(null);
22
+ const [deleting, setDeleting] = useState(false);
23
+ const { value: apiProducts, loading: apiProductsLoading, error: apiProductsError } = useAsync(async () => {
24
+ const response = await fetchApi.fetch(`${backendUrl}/api/kuadrant/apiproducts`);
25
+ return await response.json();
26
+ }, [backendUrl, fetchApi, refreshTrigger]);
27
+ const { value: planPolicies, loading: planPoliciesLoading, error: planPoliciesError } = useAsync(async () => {
28
+ const response = await fetchApi.fetch(`${backendUrl}/api/kuadrant/planpolicies`);
29
+ return await response.json();
30
+ }, [backendUrl, fetchApi, refreshTrigger]);
31
+ const loading = userLoading || apiProductsLoading || planPoliciesLoading;
32
+ const error = apiProductsError || planPoliciesError;
33
+ const handleCreateSuccess = () => {
34
+ setRefreshTrigger((prev) => prev + 1);
35
+ };
36
+ const handleDeleteClick = (namespace, name) => {
37
+ setApiProductToDelete({ namespace, name });
38
+ setDeleteDialogOpen(true);
39
+ };
40
+ const handleDeleteConfirm = async () => {
41
+ if (!apiProductToDelete) return;
42
+ setDeleting(true);
43
+ try {
44
+ const response = await fetchApi.fetch(
45
+ `${backendUrl}/api/kuadrant/apiproducts/${apiProductToDelete.namespace}/${apiProductToDelete.name}`,
46
+ { method: "DELETE" }
47
+ );
48
+ if (!response.ok) {
49
+ throw new Error("failed to delete apiproduct");
50
+ }
51
+ setRefreshTrigger((prev) => prev + 1);
52
+ } catch (err) {
53
+ console.error("error deleting apiproduct:", err);
54
+ } finally {
55
+ setDeleting(false);
56
+ setDeleteDialogOpen(false);
57
+ setApiProductToDelete(null);
58
+ }
59
+ };
60
+ const handleDeleteCancel = () => {
61
+ setDeleteDialogOpen(false);
62
+ setApiProductToDelete(null);
63
+ };
64
+ const formatDate = (dateString) => {
65
+ const date = new Date(dateString);
66
+ return date.toLocaleDateString("en-GB", {
67
+ year: "numeric",
68
+ month: "short",
69
+ day: "numeric"
70
+ });
71
+ };
72
+ const columns = [
73
+ {
74
+ title: "Name",
75
+ field: "name",
76
+ render: (row) => /* @__PURE__ */ React.createElement(Link, { to: `/catalog/default/api/${row.metadata.name}/api-product` }, /* @__PURE__ */ React.createElement("strong", null, row.spec?.displayName || row.metadata.name))
77
+ },
78
+ {
79
+ title: "Resource Name",
80
+ field: "metadata.name"
81
+ },
82
+ {
83
+ title: "Version",
84
+ field: "spec.version",
85
+ render: (row) => row.spec?.version || "-"
86
+ },
87
+ {
88
+ title: "Plans",
89
+ field: "plans",
90
+ render: (row) => {
91
+ const plans = row.spec?.plans || [];
92
+ if (plans.length === 0) return "-";
93
+ return /* @__PURE__ */ React.createElement(Box, { display: "flex", style: { gap: 4 } }, plans.map((plan, idx) => /* @__PURE__ */ React.createElement(Chip, { key: idx, label: plan.tier, size: "small" })));
94
+ }
95
+ },
96
+ {
97
+ title: "Namespace",
98
+ field: "metadata.namespace"
99
+ },
100
+ {
101
+ title: "Created",
102
+ field: "metadata.creationTimestamp",
103
+ render: (row) => formatDate(row.metadata.creationTimestamp)
104
+ },
105
+ {
106
+ title: "Actions",
107
+ field: "actions",
108
+ render: (row) => /* @__PURE__ */ React.createElement(
109
+ IconButton,
110
+ {
111
+ size: "small",
112
+ onClick: () => handleDeleteClick(row.metadata.namespace, row.metadata.name),
113
+ title: "delete apiproduct"
114
+ },
115
+ /* @__PURE__ */ React.createElement(DeleteIcon, { fontSize: "small" })
116
+ )
117
+ }
118
+ ];
119
+ const planPolicyColumns = [
120
+ {
121
+ title: "Name",
122
+ field: "metadata.name",
123
+ render: (row) => /* @__PURE__ */ React.createElement(Link, { to: `/kuadrant/planpolicy/${row.metadata.namespace}/${row.metadata.name}` }, /* @__PURE__ */ React.createElement("strong", null, row.metadata.name))
124
+ },
125
+ {
126
+ title: "Namespace",
127
+ field: "metadata.namespace"
128
+ }
129
+ ];
130
+ const renderResources = (resources) => {
131
+ if (!resources || resources.length === 0) {
132
+ return /* @__PURE__ */ React.createElement(Typography, { variant: "body2", color: "textSecondary" }, "No API products found");
133
+ }
134
+ return /* @__PURE__ */ React.createElement(
135
+ Table,
136
+ {
137
+ options: { paging: false, search: false, toolbar: false },
138
+ columns,
139
+ data: resources
140
+ }
141
+ );
142
+ };
143
+ const renderPlanPolicies = (resources) => {
144
+ if (!resources || resources.length === 0) {
145
+ return /* @__PURE__ */ React.createElement(Typography, { variant: "body2", color: "textSecondary" }, "No plan policies found");
146
+ }
147
+ return /* @__PURE__ */ React.createElement(
148
+ Table,
149
+ {
150
+ options: { paging: false, search: false, toolbar: false },
151
+ columns: planPolicyColumns,
152
+ data: resources
153
+ }
154
+ );
155
+ };
156
+ const getRoleLabel = (role) => {
157
+ switch (role) {
158
+ case "platform-engineer":
159
+ return { label: "Platform Engineer", color: "secondary" };
160
+ case "api-owner":
161
+ return { label: "API Owner", color: "primary" };
162
+ case "api-consumer":
163
+ return { label: "API Consumer", color: "default" };
164
+ default:
165
+ return { label: role, color: "default" };
166
+ }
167
+ };
168
+ return /* @__PURE__ */ React.createElement(Page, { themeId: "tool" }, /* @__PURE__ */ React.createElement(Header, { title: "Kuadrant", subtitle: "API Management for Kubernetes" }, /* @__PURE__ */ React.createElement(SupportButton, null, "Manage API products and access requests")), /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(ContentHeader, { title: "API Products" }, /* @__PURE__ */ React.createElement(Box, { display: "flex", alignItems: "center", style: { gap: 16 } }, userInfo && /* @__PURE__ */ React.createElement(Box, { display: "flex", alignItems: "center", style: { gap: 8 } }, /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, "Viewing as:"), /* @__PURE__ */ React.createElement(Chip, { label: userInfo.userId, color: "primary", size: "small" }), /* @__PURE__ */ React.createElement(
169
+ Chip,
170
+ {
171
+ label: getRoleLabel(userInfo.role).label,
172
+ color: getRoleLabel(userInfo.role).color,
173
+ size: "small"
174
+ }
175
+ )), /* @__PURE__ */ React.createElement(
176
+ Button,
177
+ {
178
+ variant: "contained",
179
+ color: "primary",
180
+ startIcon: /* @__PURE__ */ React.createElement(AddIcon, null),
181
+ onClick: () => setCreateDialogOpen(true)
182
+ },
183
+ "Create API Product"
184
+ ))), loading && /* @__PURE__ */ React.createElement(Progress, null), error && /* @__PURE__ */ React.createElement(ResponseErrorPanel, { error }), !loading && !error && /* @__PURE__ */ React.createElement(Grid, { container: true, spacing: 3, direction: "column" }, /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(InfoCard, { title: "API Products" }, renderResources(apiProducts?.items))), /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(InfoCard, { title: "Plan Policies" }, renderPlanPolicies(planPolicies?.items))), userInfo?.isApiOwner && /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(ApprovalQueueCard, null))), /* @__PURE__ */ React.createElement(
185
+ CreateAPIProductDialog,
186
+ {
187
+ open: createDialogOpen,
188
+ onClose: () => setCreateDialogOpen(false),
189
+ onSuccess: handleCreateSuccess
190
+ }
191
+ ), /* @__PURE__ */ React.createElement(Dialog, { open: deleteDialogOpen, onClose: handleDeleteCancel }, /* @__PURE__ */ React.createElement(DialogTitle, null, "Delete API Product"), /* @__PURE__ */ React.createElement(DialogContent, null, /* @__PURE__ */ React.createElement(DialogContentText, null, "Are you sure you want to delete ", apiProductToDelete?.name, " from namespace ", apiProductToDelete?.namespace, "? This will permanently remove the API Product from Kubernetes.")), /* @__PURE__ */ React.createElement(DialogActions, null, /* @__PURE__ */ React.createElement(Button, { onClick: handleDeleteCancel, color: "primary" }, "Cancel"), /* @__PURE__ */ React.createElement(Button, { onClick: handleDeleteConfirm, color: "secondary", disabled: deleting }, deleting ? "Deleting..." : "Delete")))));
192
+ };
193
+ const KuadrantPage = () => {
194
+ return /* @__PURE__ */ React.createElement(PermissionGate, { requireAnyRole: ["platform-engineer", "api-owner"] }, /* @__PURE__ */ React.createElement(ResourceList, null));
195
+ };
196
+
197
+ export { KuadrantPage, ResourceList };
198
+ //# sourceMappingURL=KuadrantPage.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KuadrantPage.esm.js","sources":["../../../src/components/KuadrantPage/KuadrantPage.tsx"],"sourcesContent":["import React, { useState } from 'react';\nimport { Typography, Grid, Box, Chip, Button, IconButton, Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions } from '@material-ui/core';\nimport AddIcon from '@material-ui/icons/Add';\nimport DeleteIcon from '@material-ui/icons/Delete';\nimport {\n InfoCard,\n Header,\n Page,\n Content,\n ContentHeader,\n SupportButton,\n Progress,\n ResponseErrorPanel,\n Link,\n Table,\n TableColumn,\n} from '@backstage/core-components';\nimport useAsync from 'react-use/lib/useAsync';\nimport { useApi, configApiRef, fetchApiRef } from '@backstage/core-plugin-api';\nimport { ApprovalQueueCard } from '../ApprovalQueueCard';\nimport { PermissionGate } from '../PermissionGate';\nimport { useUserRole } from '../../hooks/useUserRole';\nimport { CreateAPIProductDialog } from '../CreateAPIProductDialog';\n\ntype KuadrantResource = {\n metadata: {\n name: string;\n namespace: string;\n creationTimestamp: string;\n };\n spec?: any;\n};\n\ntype KuadrantList = {\n items: KuadrantResource[];\n};\n\nexport const ResourceList = () => {\n const config = useApi(configApiRef);\n const fetchApi = useApi(fetchApiRef);\n const backendUrl = config.getString('backend.baseUrl');\n const { userInfo, loading: userLoading } = useUserRole();\n const [createDialogOpen, setCreateDialogOpen] = useState(false);\n const [refreshTrigger, setRefreshTrigger] = useState(0);\n const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);\n const [apiProductToDelete, setApiProductToDelete] = useState<{ namespace: string; name: string } | null>(null);\n const [deleting, setDeleting] = useState(false);\n\n const { value: apiProducts, loading: apiProductsLoading, error: apiProductsError } = useAsync(async (): Promise<KuadrantList> => {\n const response = await fetchApi.fetch(`${backendUrl}/api/kuadrant/apiproducts`);\n return await response.json();\n }, [backendUrl, fetchApi, refreshTrigger]);\n\n const { value: planPolicies, loading: planPoliciesLoading, error: planPoliciesError } = useAsync(async (): Promise<KuadrantList> => {\n const response = await fetchApi.fetch(`${backendUrl}/api/kuadrant/planpolicies`);\n return await response.json();\n }, [backendUrl, fetchApi, refreshTrigger]);\n\n const loading = userLoading || apiProductsLoading || planPoliciesLoading;\n const error = apiProductsError || planPoliciesError;\n\n const handleCreateSuccess = () => {\n setRefreshTrigger(prev => prev + 1);\n };\n\n const handleDeleteClick = (namespace: string, name: string) => {\n setApiProductToDelete({ namespace, name });\n setDeleteDialogOpen(true);\n };\n\n const handleDeleteConfirm = async () => {\n if (!apiProductToDelete) return;\n\n setDeleting(true);\n try {\n const response = await fetchApi.fetch(\n `${backendUrl}/api/kuadrant/apiproducts/${apiProductToDelete.namespace}/${apiProductToDelete.name}`,\n { method: 'DELETE' }\n );\n\n if (!response.ok) {\n throw new Error('failed to delete apiproduct');\n }\n\n setRefreshTrigger(prev => prev + 1);\n } catch (err) {\n console.error('error deleting apiproduct:', err);\n } finally {\n setDeleting(false);\n setDeleteDialogOpen(false);\n setApiProductToDelete(null);\n }\n };\n\n const handleDeleteCancel = () => {\n setDeleteDialogOpen(false);\n setApiProductToDelete(null);\n };\n\n const formatDate = (dateString: string) => {\n const date = new Date(dateString);\n return date.toLocaleDateString('en-GB', {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n });\n };\n\n const columns: TableColumn[] = [\n {\n title: 'Name',\n field: 'name',\n render: (row: any) => (\n <Link to={`/catalog/default/api/${row.metadata.name}/api-product`}>\n <strong>{row.spec?.displayName || row.metadata.name}</strong>\n </Link>\n ),\n },\n {\n title: 'Resource Name',\n field: 'metadata.name',\n },\n {\n title: 'Version',\n field: 'spec.version',\n render: (row: any) => row.spec?.version || '-',\n },\n {\n title: 'Plans',\n field: 'plans',\n render: (row: any) => {\n const plans = row.spec?.plans || [];\n if (plans.length === 0) return '-';\n return (\n <Box display=\"flex\" style={{ gap: 4 }}>\n {plans.map((plan: any, idx: number) => (\n <Chip key={idx} label={plan.tier} size=\"small\" />\n ))}\n </Box>\n );\n },\n },\n {\n title: 'Namespace',\n field: 'metadata.namespace',\n },\n {\n title: 'Created',\n field: 'metadata.creationTimestamp',\n render: (row: any) => formatDate(row.metadata.creationTimestamp),\n },\n {\n title: 'Actions',\n field: 'actions',\n render: (row: any) => (\n <IconButton\n size=\"small\"\n onClick={() => handleDeleteClick(row.metadata.namespace, row.metadata.name)}\n title=\"delete apiproduct\"\n >\n <DeleteIcon fontSize=\"small\" />\n </IconButton>\n ),\n },\n ];\n\n const planPolicyColumns: TableColumn[] = [\n {\n title: 'Name',\n field: 'metadata.name',\n render: (row: any) => (\n <Link to={`/kuadrant/planpolicy/${row.metadata.namespace}/${row.metadata.name}`}>\n <strong>{row.metadata.name}</strong>\n </Link>\n ),\n },\n {\n title: 'Namespace',\n field: 'metadata.namespace',\n },\n ];\n\n const renderResources = (resources: KuadrantResource[] | undefined) => {\n if (!resources || resources.length === 0) {\n return <Typography variant=\"body2\" color=\"textSecondary\">No API products found</Typography>;\n }\n return (\n <Table\n options={{ paging: false, search: false, toolbar: false }}\n columns={columns}\n data={resources}\n />\n );\n };\n\n const renderPlanPolicies = (resources: KuadrantResource[] | undefined) => {\n if (!resources || resources.length === 0) {\n return <Typography variant=\"body2\" color=\"textSecondary\">No plan policies found</Typography>;\n }\n return (\n <Table\n options={{ paging: false, search: false, toolbar: false }}\n columns={planPolicyColumns}\n data={resources}\n />\n );\n };\n\n const getRoleLabel = (role: string) => {\n switch (role) {\n case 'platform-engineer':\n return { label: 'Platform Engineer', color: 'secondary' as const };\n case 'api-owner':\n return { label: 'API Owner', color: 'primary' as const };\n case 'api-consumer':\n return { label: 'API Consumer', color: 'default' as const };\n default:\n return { label: role, color: 'default' as const };\n }\n };\n\n return (\n <Page themeId=\"tool\">\n <Header title=\"Kuadrant\" subtitle=\"API Management for Kubernetes\">\n <SupportButton>Manage API products and access requests</SupportButton>\n </Header>\n <Content>\n <ContentHeader title=\"API Products\">\n <Box display=\"flex\" alignItems=\"center\" style={{ gap: 16 }}>\n {userInfo && (\n <Box display=\"flex\" alignItems=\"center\" style={{ gap: 8 }}>\n <Typography variant=\"body2\">Viewing as:</Typography>\n <Chip label={userInfo.userId} color=\"primary\" size=\"small\" />\n <Chip\n label={getRoleLabel(userInfo.role).label}\n color={getRoleLabel(userInfo.role).color}\n size=\"small\"\n />\n </Box>\n )}\n <Button\n variant=\"contained\"\n color=\"primary\"\n startIcon={<AddIcon />}\n onClick={() => setCreateDialogOpen(true)}\n >\n Create API Product\n </Button>\n </Box>\n </ContentHeader>\n {loading && <Progress />}\n {error && <ResponseErrorPanel error={error} />}\n {!loading && !error && (\n <Grid container spacing={3} direction=\"column\">\n <Grid item>\n <InfoCard title=\"API Products\">\n {renderResources(apiProducts?.items)}\n </InfoCard>\n </Grid>\n\n <Grid item>\n <InfoCard title=\"Plan Policies\">\n {renderPlanPolicies(planPolicies?.items)}\n </InfoCard>\n </Grid>\n\n {userInfo?.isApiOwner && (\n <Grid item>\n <ApprovalQueueCard />\n </Grid>\n )}\n </Grid>\n )}\n <CreateAPIProductDialog\n open={createDialogOpen}\n onClose={() => setCreateDialogOpen(false)}\n onSuccess={handleCreateSuccess}\n />\n <Dialog open={deleteDialogOpen} onClose={handleDeleteCancel}>\n <DialogTitle>Delete API Product</DialogTitle>\n <DialogContent>\n <DialogContentText>\n Are you sure you want to delete {apiProductToDelete?.name} from namespace {apiProductToDelete?.namespace}?\n This will permanently remove the API Product from Kubernetes.\n </DialogContentText>\n </DialogContent>\n <DialogActions>\n <Button onClick={handleDeleteCancel} color=\"primary\">\n Cancel\n </Button>\n <Button onClick={handleDeleteConfirm} color=\"secondary\" disabled={deleting}>\n {deleting ? 'Deleting...' : 'Delete'}\n </Button>\n </DialogActions>\n </Dialog>\n </Content>\n </Page>\n );\n};\n\nexport const KuadrantPage = () => {\n return (\n <PermissionGate requireAnyRole={['platform-engineer', 'api-owner']}>\n <ResourceList />\n </PermissionGate>\n );\n};\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export { KuadrantPage } from './KuadrantPage.esm.js';
2
+ //# sourceMappingURL=index.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
@@ -0,0 +1,33 @@
1
+ import React from 'react';
2
+ import { Box, Typography } from '@material-ui/core';
3
+ import { Progress } from '@backstage/core-components';
4
+ import { useUserRole } from '../../hooks/useUserRole.esm.js';
5
+
6
+ const PermissionGate = ({ children, requireRole, requireAnyRole, fallback }) => {
7
+ const { userInfo, loading } = useUserRole();
8
+ if (loading) {
9
+ return /* @__PURE__ */ React.createElement(Progress, null);
10
+ }
11
+ if (!userInfo) {
12
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, children);
13
+ }
14
+ const hasPermission = () => {
15
+ if (requireRole) {
16
+ return userInfo.role === requireRole;
17
+ }
18
+ if (requireAnyRole) {
19
+ return requireAnyRole.includes(userInfo.role);
20
+ }
21
+ return true;
22
+ };
23
+ if (!hasPermission()) {
24
+ if (fallback) {
25
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, fallback);
26
+ }
27
+ return /* @__PURE__ */ React.createElement(Box, { p: 4 }, /* @__PURE__ */ React.createElement(Typography, { color: "textSecondary" }, "you don't have permission to view this page"), /* @__PURE__ */ React.createElement(Box, { mt: 1 }, /* @__PURE__ */ React.createElement(Typography, { variant: "caption", color: "textSecondary" }, "required role: ", requireRole || requireAnyRole?.join(" or "))), /* @__PURE__ */ React.createElement(Typography, { variant: "caption", color: "textSecondary" }, "your role: ", userInfo.role));
28
+ }
29
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, children);
30
+ };
31
+
32
+ export { PermissionGate };
33
+ //# sourceMappingURL=PermissionGate.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PermissionGate.esm.js","sources":["../../../src/components/PermissionGate/PermissionGate.tsx"],"sourcesContent":["import React from 'react';\nimport { Typography, Box } from '@material-ui/core';\nimport { Progress } from '@backstage/core-components';\nimport { useUserRole } from '../../hooks/useUserRole';\n\ninterface PermissionGateProps {\n children: React.ReactNode;\n requireRole?: 'platform-engineer' | 'api-owner' | 'api-consumer';\n requireAnyRole?: Array<'platform-engineer' | 'api-owner' | 'api-consumer'>;\n fallback?: React.ReactNode;\n}\n\nexport const PermissionGate = ({ children, requireRole, requireAnyRole, fallback }: PermissionGateProps) => {\n const { userInfo, loading } = useUserRole();\n\n if (loading) {\n return <Progress />;\n }\n\n if (!userInfo) {\n // in dev mode without auth backend, allow access\n return <>{children}</>;\n }\n\n const hasPermission = () => {\n if (requireRole) {\n return userInfo.role === requireRole;\n }\n if (requireAnyRole) {\n return requireAnyRole.includes(userInfo.role as any);\n }\n return true;\n };\n\n if (!hasPermission()) {\n if (fallback) {\n return <>{fallback}</>;\n }\n return (\n <Box p={4}>\n <Typography color=\"textSecondary\">\n you don't have permission to view this page\n </Typography>\n <Box mt={1}>\n <Typography variant=\"caption\" color=\"textSecondary\">\n required role: {requireRole || requireAnyRole?.join(' or ')}\n </Typography>\n </Box>\n <Typography variant=\"caption\" color=\"textSecondary\">\n your role: {userInfo.role}\n </Typography>\n </Box>\n );\n }\n\n return <>{children}</>;\n};\n"],"names":[],"mappings":";;;;;AAYO,MAAM,iBAAiB,CAAC,EAAE,UAAU,WAAa,EAAA,cAAA,EAAgB,UAAoC,KAAA;AAC1G,EAAA,MAAM,EAAE,QAAA,EAAU,OAAQ,EAAA,GAAI,WAAY,EAAA;AAE1C,EAAA,IAAI,OAAS,EAAA;AACX,IAAA,2CAAQ,QAAS,EAAA,IAAA,CAAA;AAAA;AAGnB,EAAA,IAAI,CAAC,QAAU,EAAA;AAEb,IAAA,iEAAU,QAAS,CAAA;AAAA;AAGrB,EAAA,MAAM,gBAAgB,MAAM;AAC1B,IAAA,IAAI,WAAa,EAAA;AACf,MAAA,OAAO,SAAS,IAAS,KAAA,WAAA;AAAA;AAE3B,IAAA,IAAI,cAAgB,EAAA;AAClB,MAAO,OAAA,cAAA,CAAe,QAAS,CAAA,QAAA,CAAS,IAAW,CAAA;AAAA;AAErD,IAAO,OAAA,IAAA;AAAA,GACT;AAEA,EAAI,IAAA,CAAC,eAAiB,EAAA;AACpB,IAAA,IAAI,QAAU,EAAA;AACZ,MAAA,iEAAU,QAAS,CAAA;AAAA;AAErB,IAAA,2CACG,GAAI,EAAA,EAAA,CAAA,EAAG,CACN,EAAA,kBAAA,KAAA,CAAA,aAAA,CAAC,cAAW,KAAM,EAAA,eAAA,EAAA,EAAgB,6CAElC,CAAA,sCACC,GAAI,EAAA,EAAA,EAAA,EAAI,CACP,EAAA,kBAAA,KAAA,CAAA,aAAA,CAAC,cAAW,OAAQ,EAAA,SAAA,EAAU,KAAM,EAAA,eAAA,EAAA,EAAgB,mBAClC,WAAe,IAAA,cAAA,EAAgB,IAAK,CAAA,MAAM,CAC5D,CACF,CAAA,kBACC,KAAA,CAAA,aAAA,CAAA,UAAA,EAAA,EAAW,SAAQ,SAAU,EAAA,KAAA,EAAM,mBAAgB,aACtC,EAAA,QAAA,CAAS,IACvB,CACF,CAAA;AAAA;AAIJ,EAAA,iEAAU,QAAS,CAAA;AACrB;;;;"}