@cobaltcore-dev/aurora 2.0.0 → 2.1.0

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 (115) hide show
  1. package/dist/client/ContentHeader-fKKZVu-o.mjs +116 -0
  2. package/dist/client/ContentHeader-fKKZVu-o.mjs.map +1 -0
  3. package/dist/client/{DeleteFlavorModal-C2MqUgGR.mjs → DeleteFlavorModal-Be2GFiB_.mjs} +3 -2
  4. package/dist/client/DeleteFlavorModal-Be2GFiB_.mjs.map +1 -0
  5. package/dist/client/{RouteIdLevelDefaultError-CSseJ1yG.mjs → RouteIdLevelDefaultError-DPF_x2tG.mjs} +9 -9
  6. package/dist/client/RouteIdLevelDefaultError-DPF_x2tG.mjs.map +1 -0
  7. package/dist/client/StorageNotFound-B-zVinVD.mjs +30 -0
  8. package/dist/client/StorageNotFound-B-zVinVD.mjs.map +1 -0
  9. package/dist/client/{_flavorId-DYv_StX22.mjs → _flavorId-0PioWpEp.mjs} +4 -4
  10. package/dist/client/_flavorId-0PioWpEp.mjs.map +1 -0
  11. package/dist/client/{_flavorId-YV3jCS5g.mjs → _flavorId-B7fp6Nk8.mjs} +3 -3
  12. package/dist/client/{_flavorId-YV3jCS5g.mjs.map → _flavorId-B7fp6Nk8.mjs.map} +1 -1
  13. package/dist/client/{_flavorId-CaA4-Pnf.mjs → _flavorId-CgueqKTs.mjs} +3 -3
  14. package/dist/client/{_flavorId-CaA4-Pnf.mjs.map → _flavorId-CgueqKTs.mjs.map} +1 -1
  15. package/dist/client/{_floatingIpId-Dpsc7GU4.mjs → _floatingIpId-B8r64AL9.mjs} +3 -3
  16. package/dist/client/{_floatingIpId-Dpsc7GU4.mjs.map → _floatingIpId-B8r64AL9.mjs.map} +1 -1
  17. package/dist/client/{_floatingIpId-BeYElanB.mjs → _floatingIpId-lEobpOER.mjs} +2 -2
  18. package/dist/client/{_floatingIpId-BeYElanB.mjs.map → _floatingIpId-lEobpOER.mjs.map} +1 -1
  19. package/dist/client/{_imageId-pBSS0YcC.mjs → _imageId-Ccv8PedW.mjs} +3 -3
  20. package/dist/client/{_imageId-pBSS0YcC.mjs.map → _imageId-Ccv8PedW.mjs.map} +1 -1
  21. package/dist/client/_imageId-CiqEQB_J.mjs +558 -0
  22. package/dist/client/{_imageId-mfKp3JHn.mjs.map → _imageId-CiqEQB_J.mjs.map} +1 -1
  23. package/dist/client/{_imageId-mpXUMCh_.mjs → _imageId-D9LR-02T.mjs} +3 -3
  24. package/dist/client/{_imageId-mpXUMCh_.mjs.map → _imageId-D9LR-02T.mjs.map} +1 -1
  25. package/dist/client/{_projectId-BnTBxKfp.mjs → _projectId-BDARsteX.mjs} +3 -3
  26. package/dist/client/{_projectId-BnTBxKfp.mjs.map → _projectId-BDARsteX.mjs.map} +1 -1
  27. package/dist/client/{_projectId-CVrdJ52s.mjs → _projectId-CtyAHqhH.mjs} +2 -2
  28. package/dist/client/{_projectId-CVrdJ52s.mjs.map → _projectId-CtyAHqhH.mjs.map} +1 -1
  29. package/dist/client/_projectId-D3VOH5lN.mjs +94 -0
  30. package/dist/client/_projectId-D3VOH5lN.mjs.map +1 -0
  31. package/dist/client/{_projectId-AjgJX6Gl.mjs → _projectId-ZBk6vka0.mjs} +83 -81
  32. package/dist/client/_projectId-ZBk6vka0.mjs.map +1 -0
  33. package/dist/client/{_securityGroupId-AO3wnH8g.mjs → _securityGroupId-BwgZnoiR.mjs} +2 -2
  34. package/dist/client/{_securityGroupId-AO3wnH8g.mjs.map → _securityGroupId-BwgZnoiR.mjs.map} +1 -1
  35. package/dist/client/{_securityGroupId-Bq0ZyG6z.mjs → _securityGroupId-Cz2NUHdB.mjs} +3 -3
  36. package/dist/client/{_securityGroupId-Bq0ZyG6z.mjs.map → _securityGroupId-Cz2NUHdB.mjs.map} +1 -1
  37. package/dist/client/{_storageType-CzZ4u9x_.mjs → _storageType-BNpP1A2N.mjs} +3 -2
  38. package/dist/client/_storageType-BNpP1A2N.mjs.map +1 -0
  39. package/dist/client/_storageType-BxSGKhAI.mjs +15 -0
  40. package/dist/client/_storageType-BxSGKhAI.mjs.map +1 -0
  41. package/dist/client/_storageType-DZYCmiLt.mjs +30 -0
  42. package/dist/client/_storageType-DZYCmiLt.mjs.map +1 -0
  43. package/dist/client/{_storageType-c-DAu8qr.mjs → _storageType-UqAMP-8d.mjs} +286 -285
  44. package/dist/client/_storageType-UqAMP-8d.mjs.map +1 -0
  45. package/dist/client/_storageType-u51_7OwJ.mjs +7 -0
  46. package/dist/client/_storageType-u51_7OwJ.mjs.map +1 -0
  47. package/dist/client/components/Errors/RouteIdLevelDefaultError.d.ts +2 -1
  48. package/dist/client/{flavors-ZudiAiTU.mjs → flavors-Ce3J-cFa.mjs} +2 -2
  49. package/dist/client/{flavors-ZudiAiTU.mjs.map → flavors-Ce3J-cFa.mjs.map} +1 -1
  50. package/dist/client/{flavors-Bgq3KZmx.mjs → flavors-D5ThLaXO.mjs} +84 -77
  51. package/dist/client/flavors-D5ThLaXO.mjs.map +1 -0
  52. package/dist/client/{floatingips-B1oLH8z6.mjs → floatingips-DXnRlLvV.mjs} +2 -2
  53. package/dist/client/{floatingips-B1oLH8z6.mjs.map → floatingips-DXnRlLvV.mjs.map} +1 -1
  54. package/dist/client/{floatingips-BtLg3yEQ.mjs → floatingips-HRbuLxiN.mjs} +3 -3
  55. package/dist/client/{floatingips-BtLg3yEQ.mjs.map → floatingips-HRbuLxiN.mjs.map} +1 -1
  56. package/dist/client/{floatingips-BeQEkVH8.mjs → floatingips-nN1kQmsu.mjs} +2 -2
  57. package/dist/client/{floatingips-BeQEkVH8.mjs.map → floatingips-nN1kQmsu.mjs.map} +1 -1
  58. package/dist/client/{images-CnTW3bpy.mjs → images-C8M3ysWR.mjs} +3 -3
  59. package/dist/client/{images-CnTW3bpy.mjs.map → images-C8M3ysWR.mjs.map} +1 -1
  60. package/dist/client/{images-DZHDYDQu.mjs → images-Cf5R28NR.mjs} +2 -2
  61. package/dist/client/{images-DZHDYDQu.mjs.map → images-Cf5R28NR.mjs.map} +1 -1
  62. package/dist/client/index.js +198 -197
  63. package/dist/client/index.js.map +1 -1
  64. package/dist/client/{objects-jYQBFzwg.mjs → objects-BudUAfWY.mjs} +4265 -4275
  65. package/dist/client/objects-BudUAfWY.mjs.map +1 -0
  66. package/dist/client/objects-Dc7sKUr8.mjs +67 -0
  67. package/dist/client/objects-Dc7sKUr8.mjs.map +1 -0
  68. package/dist/client/objects-Dw7oeTq2.mjs +80 -0
  69. package/dist/client/objects-Dw7oeTq2.mjs.map +1 -0
  70. package/dist/client/objects-_g7nGIn5.mjs +25 -0
  71. package/dist/client/objects-_g7nGIn5.mjs.map +1 -0
  72. package/dist/client/objects-eBxvY6_n.mjs +78 -0
  73. package/dist/client/objects-eBxvY6_n.mjs.map +1 -0
  74. package/dist/client/projects-yiK0HGSA.mjs.map +1 -1
  75. package/dist/client/{securitygroups-BRndk7wh.mjs → securitygroups-67fFxiwt.mjs} +2 -2
  76. package/dist/client/{securitygroups-BRndk7wh.mjs.map → securitygroups-67fFxiwt.mjs.map} +1 -1
  77. package/dist/client/{securitygroups--0PS2wbM.mjs → securitygroups-DXrfyuVF.mjs} +3 -3
  78. package/dist/client/{securitygroups--0PS2wbM.mjs.map → securitygroups-DXrfyuVF.mjs.map} +1 -1
  79. package/dist/client/{securitygroups-Cs2Op-tu.mjs → securitygroups-WLB1_IM6.mjs} +3 -3
  80. package/dist/client/{securitygroups-Cs2Op-tu.mjs.map → securitygroups-WLB1_IM6.mjs.map} +1 -1
  81. package/dist/client/{securitygroups-CdN4yAAw.mjs → securitygroups-o0IXZM6S.mjs} +2 -2
  82. package/dist/client/{securitygroups-CdN4yAAw.mjs.map → securitygroups-o0IXZM6S.mjs.map} +1 -1
  83. package/dist/client/serviceCatalog-B3lKWC9V.mjs +6 -0
  84. package/dist/client/serviceCatalog-B3lKWC9V.mjs.map +1 -0
  85. package/dist/client/storageProviders-CJ5rOh13.mjs +15 -0
  86. package/dist/client/storageProviders-CJ5rOh13.mjs.map +1 -0
  87. package/dist/client/trpcClient.d.ts +96 -3
  88. package/dist/client/utils/serviceCatalog.d.ts +9 -0
  89. package/dist/client/utils/storageProviders.d.ts +44 -0
  90. package/dist/server/index.d.ts +96 -3
  91. package/dist/server/index.js +1224 -387
  92. package/package.json +1 -1
  93. package/dist/client/ContentHeader-CPJbgYWn.mjs +0 -115
  94. package/dist/client/ContentHeader-CPJbgYWn.mjs.map +0 -1
  95. package/dist/client/DeleteFlavorModal-C2MqUgGR.mjs.map +0 -1
  96. package/dist/client/RouteIdLevelDefaultError-CSseJ1yG.mjs.map +0 -1
  97. package/dist/client/_flavorId-DYv_StX22.mjs.map +0 -1
  98. package/dist/client/_imageId-mfKp3JHn.mjs +0 -558
  99. package/dist/client/_projectId-AjgJX6Gl.mjs.map +0 -1
  100. package/dist/client/_projectId-DgciZslU.mjs +0 -92
  101. package/dist/client/_projectId-DgciZslU.mjs.map +0 -1
  102. package/dist/client/_storageType-BRWWJtow.mjs +0 -14
  103. package/dist/client/_storageType-BRWWJtow.mjs.map +0 -1
  104. package/dist/client/_storageType-CzZ4u9x_.mjs.map +0 -1
  105. package/dist/client/_storageType-c-DAu8qr.mjs.map +0 -1
  106. package/dist/client/_storageType-zeSZe--V.mjs +0 -7
  107. package/dist/client/_storageType-zeSZe--V.mjs.map +0 -1
  108. package/dist/client/flavors-Bgq3KZmx.mjs.map +0 -1
  109. package/dist/client/objects-25xsh4Ia.mjs +0 -66
  110. package/dist/client/objects-25xsh4Ia.mjs.map +0 -1
  111. package/dist/client/objects-KXY6dgwq.mjs +0 -82
  112. package/dist/client/objects-KXY6dgwq.mjs.map +0 -1
  113. package/dist/client/objects-QryuClU3.mjs +0 -82
  114. package/dist/client/objects-QryuClU3.mjs.map +0 -1
  115. package/dist/client/objects-jYQBFzwg.mjs.map +0 -1
@@ -0,0 +1,94 @@
1
+ import { Q as e, f as t } from "./build-WuxXRAqf.mjs";
2
+ import { t as n } from "./Slot-CWb612-_.mjs";
3
+ import { t as r } from "./helpers-1PpYf-fC.mjs";
4
+ import { t as i } from "./serviceCatalog-B3lKWC9V.mjs";
5
+ import { n as a, t as o } from "./storageProviders-CJ5rOh13.mjs";
6
+ import { t as s } from "./ContentHeader-fKKZVu-o.mjs";
7
+ import { jsx as c, jsxs as l } from "react/jsx-runtime";
8
+ import { useLoaderData as u, useNavigate as d, useRouteContext as f } from "@tanstack/react-router";
9
+ import { Trans as p, useLingui as m } from "@lingui/react";
10
+ //#region src/client/routes/_auth/projects/$projectId/index.tsx?tsr-split=component
11
+ function h({ group: e, label: r, to: i, service: a }) {
12
+ let o = d(), { slots: s } = f({ strict: !1 });
13
+ return /*#__PURE__*/ l(t, {
14
+ onClick: () => o({ to: i }),
15
+ className: "flex min-h-40 flex-col gap-4 px-3 pt-2 pb-3",
16
+ "data-testid": "service-card",
17
+ children: [/*#__PURE__*/ l("div", {
18
+ className: "flex min-w-0 flex-col",
19
+ children: [/*#__PURE__*/ c("p", {
20
+ className: "text-theme-light text-xs leading-5 font-medium",
21
+ children: e
22
+ }), /*#__PURE__*/ l("div", {
23
+ className: "flex items-center gap-2",
24
+ children: [/*#__PURE__*/ c("p", {
25
+ className: "text-theme-high text-lg leading-7 font-bold",
26
+ "data-testid": "service-card-label",
27
+ children: r
28
+ }), s?.serviceBadge && /*#__PURE__*/ c(n, {
29
+ component: s.serviceBadge,
30
+ useShadowDOM: !1,
31
+ currentService: a
32
+ })]
33
+ })]
34
+ }), /*#__PURE__*/ c("div", { className: "flex-1" })]
35
+ });
36
+ }
37
+ function g() {
38
+ let { crumbProject: t, availableServices: n, projectId: d, description: g } = u({ from: "/_auth/projects/$projectId" }), { i18n: _, _: v } = m(), { enabledServices: y, serviceExtensions: b } = f({ strict: !1 }), x = (e) => !y || y.includes(e), S = r(n ?? []), C = `/projects/${d}`, w = [];
39
+ S.image?.glance && x("images") && w.push({
40
+ group: _._({ id: "rp0Bd0" }),
41
+ label: _._({ id: "an5hVd" }),
42
+ to: `${C}/compute/images`,
43
+ service: "images"
44
+ }), S.compute?.nova && x("flavors") && w.push({
45
+ group: _._({ id: "rp0Bd0" }),
46
+ label: _._({ id: "neiJm0" }),
47
+ to: `${C}/compute/flavors`,
48
+ service: "flavors"
49
+ }), S.network && (x("securitygroups") && w.push({
50
+ group: _._({ id: "OR475H" }),
51
+ label: _._({ id: "4opp4r" }),
52
+ to: `${C}/network/securitygroups`,
53
+ service: "securitygroups"
54
+ }), x("floatingips") && w.push({
55
+ group: _._({ id: "OR475H" }),
56
+ label: _._({ id: "u77/s4" }),
57
+ to: `${C}/network/floatingips`,
58
+ service: "floatingips"
59
+ })), i(S, o.SWIFT) && x("containers") && w.push({
60
+ group: _._({ id: "BrrIs8" }),
61
+ label: _._({ id: "+OEi73" }),
62
+ to: `${C}/storage/${o.SWIFT}/${a[o.SWIFT]}`,
63
+ service: "containers"
64
+ }), i(S, o.CEPH) && x("ceph-containers") && w.push({
65
+ group: _._({ id: "BrrIs8" }),
66
+ label: _._({ id: "KhNDX4" }),
67
+ to: `${C}/storage/${o.CEPH}/${a[o.CEPH]}`,
68
+ service: "ceph-containers"
69
+ });
70
+ for (let e of b ?? []) S[e.serviceType]?.[e.serviceName] && (!y || y.includes(e.serviceType)) && w.push({
71
+ group: _._({ id: "MILoeL" }),
72
+ label: e.label,
73
+ to: `/projects/${d}/services/${e.serviceType}`,
74
+ service: e.serviceType
75
+ });
76
+ return /*#__PURE__*/ l(e, {
77
+ direction: "vertical",
78
+ children: [/*#__PURE__*/ c(s, {
79
+ title: t?.name ?? _._({ id: "e0NrBM" }),
80
+ projectId: d,
81
+ description: g
82
+ }), w.length > 0 ? /*#__PURE__*/ c("div", {
83
+ className: "grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4",
84
+ children: w.map((e) => /*#__PURE__*/ c(h, { ...e }, e.to))
85
+ }) : /*#__PURE__*/ c("p", {
86
+ className: "text-theme-light text-sm",
87
+ children: /*#__PURE__*/ c(p, { id: "Q1W//7" })
88
+ })]
89
+ });
90
+ }
91
+ //#endregion
92
+ export { g as component };
93
+
94
+ //# sourceMappingURL=_projectId-D3VOH5lN.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_projectId-D3VOH5lN.mjs","names":["useLoaderData","useNavigate","Card","Stack","getServiceIndex","Trans","useLingui","useRouteContext","ContentHeader","Slot","hasServiceByName","STORAGE_PROVIDER","STORAGE_TYPE_BY_PROVIDER","ServiceCardProps","group","label","to","service","ServiceCard","navigate","slots","strict","serviceBadge","RouteComponent","crumbProject","availableServices","projectId","description","from","t","enabledServices","serviceExtensions","isEnabled","includes","serviceIndex","base","cards","push","SWIFT","CEPH","extension","serviceType","serviceName","name","length","map","card","component"],"sources":["../../src/client/routes/_auth/projects/$projectId/index.tsx?tsr-split=component"],"sourcesContent":["import { createFileRoute, useLoaderData, useNavigate } from \"@tanstack/react-router\"\nimport { Card, Stack } from \"@cloudoperators/juno-ui-components\"\nimport { getServiceIndex } from \"@/server/Authentication/helpers\"\nimport { Trans } from \"@lingui/react/macro\"\nimport { useLingui } from \"@lingui/react/macro\"\nimport { useRouteContext } from \"@tanstack/react-router\"\nimport { ContentHeader } from \"@/client/components/ContentHeader/ContentHeader\"\nimport { Slot } from \"@/client/components/Slot\"\nimport type { RouteInfo } from \"@/client/routes/routeInfo\"\nimport { hasServiceByName } from \"@/client/utils/serviceCatalog\"\nimport { STORAGE_PROVIDER, STORAGE_TYPE_BY_PROVIDER } from \"@/client/utils/storageProviders\"\n\nexport const Route = createFileRoute(\"/_auth/projects/$projectId/\")({\n staticData: {\n analytics: {\n name: \"projects.detail\",\n },\n } satisfies RouteInfo,\n component: RouteComponent,\n})\n\ninterface ServiceCardProps {\n group: string\n label: string\n to: string\n service: string\n}\n\nfunction ServiceCard({ group, label, to, service }: ServiceCardProps) {\n const navigate = useNavigate()\n const { slots } = useRouteContext({ strict: false })\n\n return (\n <Card\n onClick={() => navigate({ to: to as never })}\n className=\"flex min-h-40 flex-col gap-4 px-3 pt-2 pb-3\"\n data-testid=\"service-card\"\n >\n <div className=\"flex min-w-0 flex-col\">\n <p className=\"text-theme-light text-xs leading-5 font-medium\">{group}</p>\n <div className=\"flex items-center gap-2\">\n <p className=\"text-theme-high text-lg leading-7 font-bold\" data-testid=\"service-card-label\">\n {label}\n </p>\n {slots?.serviceBadge && <Slot component={slots.serviceBadge} useShadowDOM={false} currentService={service} />}\n </div>\n </div>\n <div className=\"flex-1\" />\n </Card>\n )\n}\n\nfunction RouteComponent() {\n const { crumbProject, availableServices, projectId, description } = useLoaderData({\n from: \"/_auth/projects/$projectId\",\n })\n const { t } = useLingui()\n const { enabledServices, serviceExtensions } = useRouteContext({ strict: false })\n const isEnabled = (service: string) => !enabledServices || enabledServices.includes(service)\n\n const serviceIndex = getServiceIndex(availableServices ?? [])\n const base = `/projects/${projectId}`\n\n const cards: ServiceCardProps[] = []\n\n if (serviceIndex[\"image\"]?.[\"glance\"] && isEnabled(\"images\"))\n cards.push({ group: t`Compute`, label: t`Images`, to: `${base}/compute/images`, service: \"images\" })\n if (serviceIndex[\"compute\"]?.[\"nova\"] && isEnabled(\"flavors\"))\n cards.push({ group: t`Compute`, label: t`Flavors`, to: `${base}/compute/flavors`, service: \"flavors\" })\n if (serviceIndex[\"network\"]) {\n if (isEnabled(\"securitygroups\"))\n cards.push({\n group: t`Network`,\n label: t`Security Groups`,\n to: `${base}/network/securitygroups`,\n service: \"securitygroups\",\n })\n if (isEnabled(\"floatingips\"))\n cards.push({\n group: t`Network`,\n label: t`Floating IPs`,\n to: `${base}/network/floatingips`,\n service: \"floatingips\",\n })\n }\n if (hasServiceByName(serviceIndex, STORAGE_PROVIDER.SWIFT) && isEnabled(\"containers\"))\n cards.push({\n group: t`Storage`,\n label: t`Object Storage (Swift)`,\n to: `${base}/storage/${STORAGE_PROVIDER.SWIFT}/${STORAGE_TYPE_BY_PROVIDER[STORAGE_PROVIDER.SWIFT]}`,\n service: \"containers\",\n })\n if (hasServiceByName(serviceIndex, STORAGE_PROVIDER.CEPH) && isEnabled(\"ceph-containers\"))\n cards.push({\n group: t`Storage`,\n label: t`Object Storage (Ceph)`,\n to: `${base}/storage/${STORAGE_PROVIDER.CEPH}/${STORAGE_TYPE_BY_PROVIDER[STORAGE_PROVIDER.CEPH]}`,\n service: \"ceph-containers\",\n })\n\n for (const extension of serviceExtensions ?? []) {\n // if the extension's service is in the catalog and enabled, display a card for it\n if (\n serviceIndex[extension.serviceType]?.[extension.serviceName] &&\n (!enabledServices || enabledServices.includes(extension.serviceType))\n )\n cards.push({\n group: t`Services`,\n label: extension.label,\n to: `/projects/${projectId}/services/${extension.serviceType}`,\n service: extension.serviceType,\n })\n }\n\n return (\n <Stack direction=\"vertical\">\n <ContentHeader title={crumbProject?.name ?? t`Project`} projectId={projectId} description={description} />\n {cards.length > 0 ? (\n <div className=\"grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4\">\n {cards.map((card) => (\n <ServiceCard key={card.to} {...card} />\n ))}\n </div>\n ) : (\n <p className=\"text-theme-light text-sm\">\n <Trans>No services available for this project.</Trans>\n </p>\n )}\n </Stack>\n )\n}\n"],"mappings":"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"}
@@ -1,68 +1,70 @@
1
1
  import { A as e, G as t, N as n, Q as r, Y as i, _ as a, b as o, i as s, rt as c, ut as l, w as u, z as d } from "./build-WuxXRAqf.mjs";
2
2
  import { t as f } from "./Slot-CWb612-_.mjs";
3
- import { n as p, t as m } from "./_projectId-BnTBxKfp.mjs";
3
+ import { n as p, t as m } from "./_projectId-BDARsteX.mjs";
4
4
  import { t as h } from "./helpers-1PpYf-fC.mjs";
5
- import { t as g } from "./routeInfo-DP3v74Wg.mjs";
6
- import { t as _ } from "./useSetBreadcrumb-tn7lBaQV.mjs";
7
- import { n as v, r as y, t as b } from "./useBreadcrumbs-ha3IlaxX.mjs";
8
- import { Fragment as x, jsx as S, jsxs as C } from "react/jsx-runtime";
9
- import { useContext as w, useEffect as T, useMemo as E, useState as D } from "react";
10
- import { Outlet as O, useLoaderData as k, useMatches as A, useNavigate as j, useParams as M, useRouteContext as N } from "@tanstack/react-router";
11
- import { Trans as P, useLingui as F } from "@lingui/react";
12
- import { i18n as I } from "@lingui/core";
5
+ import { t as g } from "./serviceCatalog-B3lKWC9V.mjs";
6
+ import { n as _, t as v } from "./storageProviders-CJ5rOh13.mjs";
7
+ import { t as y } from "./routeInfo-DP3v74Wg.mjs";
8
+ import { t as b } from "./useSetBreadcrumb-tn7lBaQV.mjs";
9
+ import { n as x, r as S, t as C } from "./useBreadcrumbs-ha3IlaxX.mjs";
10
+ import { Fragment as w, jsx as T, jsxs as E } from "react/jsx-runtime";
11
+ import { useContext as D, useEffect as O, useMemo as k, useState as A } from "react";
12
+ import { Outlet as j, useLoaderData as M, useMatches as N, useNavigate as P, useParams as F, useRouteContext as I } from "@tanstack/react-router";
13
+ import { Trans as L, useLingui as R } from "@lingui/react";
14
+ import { i18n as z } from "@lingui/core";
13
15
  //#region src/client/routes/_auth/projects/-components/SideNavBar.tsx
14
- var L = ({ projectId: e, projectName: t, domainName: r, sections: i }) => {
15
- let s = j(), c = A(), { provider: d, serviceType: p } = M({ strict: !1 }), { slots: m } = N({ strict: !1 }), h = (e) => m?.serviceBadge ? /*#__PURE__*/ S(f, {
16
+ var B = ({ projectId: e, projectName: t, domainName: r, sections: i }) => {
17
+ let s = P(), c = N(), { provider: d, serviceType: p } = F({ strict: !1 }), { slots: m } = I({ strict: !1 }), h = (e) => m?.serviceBadge ? /*#__PURE__*/ T(f, {
16
18
  component: m.serviceBadge,
17
19
  useShadowDOM: !1,
18
20
  currentService: e
19
- }) : null, _ = [...c].reverse().find((e) => g(e.staticData) && (e.staticData.section !== void 0 || e.staticData.service !== void 0)), v = _ && g(_.staticData) ? _.staticData : void 0, y = v?.section ?? null, b = v?.service ?? p ?? null, [w, E] = D(() => Object.fromEntries(i.map((e) => [e.section, 0])));
20
- return T(() => {
21
- y && E((e) => ({
21
+ }) : null, g = [...c].reverse().find((e) => y(e.staticData) && (e.staticData.section !== void 0 || e.staticData.service !== void 0)), _ = g && y(g.staticData) ? g.staticData : void 0, v = _?.section ?? null, b = _?.service ?? p ?? null, [x, S] = A(() => Object.fromEntries(i.map((e) => [e.section, 0])));
22
+ return O(() => {
23
+ v && S((e) => ({
22
24
  ...e,
23
- [y]: (e[y] || 0) + 1
25
+ [v]: (e[v] || 0) + 1
24
26
  }));
25
- }, [y]), /*#__PURE__*/ S(u, {
27
+ }, [v]), /*#__PURE__*/ T(u, {
26
28
  ariaLabel: "Project Side Navigation",
27
- children: /*#__PURE__*/ C(x, { children: [/*#__PURE__*/ S(o, { children: /*#__PURE__*/ C(x, { children: [
28
- /*#__PURE__*/ S(n, {
29
+ children: /*#__PURE__*/ E(w, { children: [/*#__PURE__*/ T(o, { children: /*#__PURE__*/ E(w, { children: [
30
+ /*#__PURE__*/ T(n, {
29
31
  onClick: () => s({
30
32
  to: "/projects/$projectId",
31
33
  params: { projectId: e }
32
34
  }),
33
- label: /*#__PURE__*/ C(x, { children: [r && /*#__PURE__*/ C("p", {
35
+ label: /*#__PURE__*/ E(w, { children: [r && /*#__PURE__*/ E("p", {
34
36
  className: "text-theme-light text-xs leading-4 font-bold",
35
37
  children: [r, " /"]
36
- }), /*#__PURE__*/ S("p", {
38
+ }), /*#__PURE__*/ T("p", {
37
39
  className: "leading-5 font-normal",
38
40
  children: t
39
41
  })] })
40
42
  }),
41
- /*#__PURE__*/ S(a, { spacing: "1" }),
42
- i.map(({ section: e, label: t, services: r }) => /*#__PURE__*/ S(l, {
43
+ /*#__PURE__*/ T(a, { spacing: "1" }),
44
+ i.map(({ section: e, label: t, services: r }) => /*#__PURE__*/ T(l, {
43
45
  label: t,
44
46
  open: !0,
45
47
  children: r.map((t) => {
46
- let r = y === e && (y === "storage" && b === "containers" ? t.params.provider === d : b === t.service);
47
- return /*#__PURE__*/ S(n, {
48
+ let r = v === e && (v === "storage" && b === "containers" ? t.params.provider === d : b === t.service);
49
+ return /*#__PURE__*/ T(n, {
48
50
  onClick: () => t.navigate(s),
49
- label: /*#__PURE__*/ C("span", {
51
+ label: /*#__PURE__*/ E("span", {
50
52
  className: "flex items-start gap-2",
51
53
  children: [t.label, h(t.service)]
52
54
  }),
53
55
  selected: r
54
56
  }, t.service);
55
57
  })
56
- }, `${e}-${w[e]}`))
57
- ] }) }), m?.sideNavBanner && /*#__PURE__*/ S(f, { component: m.sideNavBanner })] })
58
+ }, `${e}-${x[e]}`))
59
+ ] }) }), m?.sideNavBanner && /*#__PURE__*/ T(f, { component: m.sideNavBanner })] })
58
60
  });
59
61
  };
60
62
  //#endregion
61
63
  //#region src/client/routes/_auth/projects/-components/buildNavSections.ts
62
- function R(e, t, n, r) {
64
+ function V(e, t, n, r) {
63
65
  let i = h(t), a = (e) => !n || n.includes(e), o = [...i.image?.glance && a("images") ? [{
64
66
  service: "images",
65
- label: I._({ id: "an5hVd" }),
67
+ label: z._({ id: "an5hVd" }),
66
68
  navigate: (t) => t({
67
69
  to: "/projects/$projectId/compute/images",
68
70
  params: { projectId: e }
@@ -70,7 +72,7 @@ function R(e, t, n, r) {
70
72
  params: { projectId: e }
71
73
  }] : [], ...i?.compute?.nova && a("flavors") ? [{
72
74
  service: "flavors",
73
- label: I._({ id: "neiJm0" }),
75
+ label: z._({ id: "neiJm0" }),
74
76
  navigate: (t) => t({
75
77
  to: "/projects/$projectId/compute/flavors",
76
78
  params: { projectId: e }
@@ -78,7 +80,7 @@ function R(e, t, n, r) {
78
80
  params: { projectId: e }
79
81
  }] : []], s = i.network ? [...a("securitygroups") ? [{
80
82
  service: "securitygroups",
81
- label: I._({ id: "4opp4r" }),
83
+ label: z._({ id: "4opp4r" }),
82
84
  navigate: (t) => t({
83
85
  to: "/projects/$projectId/network/securitygroups",
84
86
  params: { projectId: e }
@@ -86,63 +88,63 @@ function R(e, t, n, r) {
86
88
  params: { projectId: e }
87
89
  }] : [], ...a("floatingips") ? [{
88
90
  service: "floatingips",
89
- label: I._({ id: "u77/s4" }),
91
+ label: z._({ id: "u77/s4" }),
90
92
  navigate: (t) => t({
91
93
  to: "/projects/$projectId/network/floatingips",
92
94
  params: { projectId: e }
93
95
  }),
94
96
  params: { projectId: e }
95
- }] : []] : [], c = [...i?.["object-store"]?.swift && a("containers") ? [{
97
+ }] : []] : [], c = [...g(i, v.SWIFT) && a("containers") ? [{
96
98
  service: "containers",
97
- label: I._({ id: "+OEi73" }),
99
+ label: z._({ id: "+OEi73" }),
98
100
  navigate: (t) => t({
99
101
  to: "/projects/$projectId/storage/$provider/$storageType",
100
102
  params: {
101
103
  projectId: e,
102
- provider: "swift",
103
- storageType: "containers"
104
+ provider: v.SWIFT,
105
+ storageType: _[v.SWIFT]
104
106
  }
105
107
  }),
106
108
  params: {
107
109
  projectId: e,
108
- provider: "swift",
109
- storageType: "containers"
110
+ provider: v.SWIFT,
111
+ storageType: _[v.SWIFT]
110
112
  }
111
- }] : [], ...i?.["object-store-ceph"]?.ceph && a("ceph-containers") ? [{
113
+ }] : [], ...g(i, v.CEPH) && a("ceph-containers") ? [{
112
114
  service: "ceph-containers",
113
- label: I._({ id: "KhNDX4" }),
115
+ label: z._({ id: "KhNDX4" }),
114
116
  navigate: (t) => t({
115
117
  to: "/projects/$projectId/storage/$provider/$storageType",
116
118
  params: {
117
119
  projectId: e,
118
- provider: "ceph",
119
- storageType: "buckets"
120
+ provider: v.CEPH,
121
+ storageType: _[v.CEPH]
120
122
  }
121
123
  }),
122
124
  params: {
123
125
  projectId: e,
124
- provider: "ceph",
125
- storageType: "buckets"
126
+ provider: v.CEPH,
127
+ storageType: _[v.CEPH]
126
128
  }
127
129
  }] : []], l = /* @__PURE__ */ new Map([
128
130
  ["compute", {
129
131
  section: "compute",
130
- label: I._({ id: "rp0Bd0" }),
132
+ label: z._({ id: "rp0Bd0" }),
131
133
  services: o
132
134
  }],
133
135
  ["network", {
134
136
  section: "network",
135
- label: I._({ id: "OR475H" }),
137
+ label: z._({ id: "OR475H" }),
136
138
  services: s
137
139
  }],
138
140
  ["storage", {
139
141
  section: "storage",
140
- label: I._({ id: "BrrIs8" }),
142
+ label: z._({ id: "BrrIs8" }),
141
143
  services: c
142
144
  }],
143
145
  ["services", {
144
146
  section: "services",
145
- label: I._({ id: "MILoeL" }),
147
+ label: z._({ id: "MILoeL" }),
146
148
  services: []
147
149
  }]
148
150
  ]);
@@ -165,8 +167,8 @@ function R(e, t, n, r) {
165
167
  }
166
168
  //#endregion
167
169
  //#region src/client/components/Breadcrumbs.tsx
168
- function z() {
169
- let e = j(), { projectId: t } = M({ strict: !1 }), { breadcrumbs: n } = w(v), r = b(), a = E(() => n.length === 0 || r.length === 0 ? r : [
170
+ function H() {
171
+ let e = P(), { projectId: t } = F({ strict: !1 }), { breadcrumbs: n } = D(x), r = C(), a = k(() => n.length === 0 || r.length === 0 ? r : [
170
172
  ...r.slice(0, -1),
171
173
  {
172
174
  ...r[r.length - 1],
@@ -183,9 +185,9 @@ function z() {
183
185
  t,
184
186
  e
185
187
  ]);
186
- return /*#__PURE__*/ S(i, {
188
+ return /*#__PURE__*/ T(i, {
187
189
  className: "relative z-1 mt-8 mb-4",
188
- children: a.map((e, t) => /*#__PURE__*/ S(d, {
190
+ children: a.map((e, t) => /*#__PURE__*/ T(d, {
189
191
  label: e.label,
190
192
  icon: e.icon,
191
193
  onClick: e.onClick,
@@ -195,20 +197,20 @@ function z() {
195
197
  }
196
198
  //#endregion
197
199
  //#region src/client/routes/_auth/projects/$projectId.tsx?tsr-split=component
198
- function B() {
199
- let { i18n: n, _: i } = F(), a = k({ from: m.id }), o = m.useNavigate(), { enabledServices: l, serviceExtensions: u } = N({ strict: !1 }), d = a.crumbProject?.name || a.projectId, f = a.crumbDomain?.name ? `${a.crumbDomain.name}/${d}` : d;
200
- if (_(m.id, f), a.scopeError) {
200
+ function U() {
201
+ let { i18n: n, _: i } = R(), a = M({ from: m.id }), o = m.useNavigate(), { enabledServices: l, serviceExtensions: u } = I({ strict: !1 }), d = a.crumbProject?.name || a.projectId, f = a.crumbDomain?.name ? `${a.crumbDomain.name}/${d}` : d;
202
+ if (b(m.id, f), a.scopeError) {
201
203
  let { code: t, message: r, currentDomain: i } = a.scopeError, l = i?.name;
202
- if (t === "UNAUTHORIZED") return /*#__PURE__*/ S(e, {
204
+ if (t === "UNAUTHORIZED") return /*#__PURE__*/ T(e, {
203
205
  className: "py-8",
204
- children: /*#__PURE__*/ S(c, {
206
+ children: /*#__PURE__*/ T(c, {
205
207
  status: "error",
206
- title: n._({ id: "M5rEN5" }),
207
- body: n._({ id: "gPKlad" }),
208
- action: /*#__PURE__*/ S(s, {
208
+ title: n._({ id: "odyhRS" }),
209
+ body: n._({ id: "EpLAGw" }),
210
+ action: /*#__PURE__*/ T(s, {
209
211
  variant: "primary",
210
212
  onClick: () => o({ to: "/" }),
211
- children: /*#__PURE__*/ S(P, { id: "ADVQ46" })
213
+ children: /*#__PURE__*/ T(L, { id: "5lWFkC" })
212
214
  })
213
215
  })
214
216
  });
@@ -217,65 +219,65 @@ function B() {
217
219
  id: "+A0ERd",
218
220
  values: { domainName: l }
219
221
  }) : n._({ id: "5rPh4Y" });
220
- return /*#__PURE__*/ S(e, {
222
+ return /*#__PURE__*/ T(e, {
221
223
  className: "py-8",
222
- children: /*#__PURE__*/ S(c, {
224
+ children: /*#__PURE__*/ T(c, {
223
225
  status: "error",
224
226
  code: 404,
225
227
  title: n._({ id: "SK7RzY" }),
226
228
  body: t,
227
- action: /*#__PURE__*/ S(s, {
229
+ action: /*#__PURE__*/ T(s, {
228
230
  variant: "primary",
229
231
  onClick: () => o({ to: "/projects" }),
230
- children: /*#__PURE__*/ S(P, { id: "aKznJS" })
232
+ children: /*#__PURE__*/ T(L, { id: "aKznJS" })
231
233
  })
232
234
  })
233
235
  });
234
236
  }
235
- return t === "FORBIDDEN" ? /*#__PURE__*/ S(e, {
237
+ return t === "FORBIDDEN" ? /*#__PURE__*/ T(e, {
236
238
  className: "py-8",
237
- children: /*#__PURE__*/ S(c, {
239
+ children: /*#__PURE__*/ T(c, {
238
240
  status: "error",
239
241
  code: 403,
240
242
  title: n._({ id: "VTfZPy" }),
241
243
  body: n._({ id: "hXKwm5" }),
242
- action: /*#__PURE__*/ S(s, {
244
+ action: /*#__PURE__*/ T(s, {
243
245
  variant: "primary",
244
246
  onClick: () => o({ to: "/projects" }),
245
- children: /*#__PURE__*/ S(P, { id: "aKznJS" })
247
+ children: /*#__PURE__*/ T(L, { id: "aKznJS" })
246
248
  })
247
249
  })
248
- }) : /*#__PURE__*/ S(p, { error: Error(r) });
250
+ }) : /*#__PURE__*/ T(p, { error: Error(r) });
249
251
  }
250
- let { availableServices: h, projectId: g, crumbProject: v, crumbDomain: b } = a, x = E(() => R(g, h, l, u), [
252
+ let { availableServices: h, projectId: g, crumbProject: _, crumbDomain: v } = a, y = k(() => V(g, h, l, u), [
251
253
  g,
252
254
  h,
253
255
  l,
254
256
  u
255
257
  ]);
256
- return /*#__PURE__*/ S(t, {
258
+ return /*#__PURE__*/ T(t, {
257
259
  embedded: !0,
258
- sideNavigation: /*#__PURE__*/ S(L, {
259
- sections: x,
260
+ sideNavigation: /*#__PURE__*/ T(B, {
261
+ sections: y,
260
262
  projectId: g,
261
- projectName: v?.name || g,
262
- domainName: b?.name
263
+ projectName: _?.name || g,
264
+ domainName: v?.name
263
265
  }),
264
266
  className: "h-min-screen",
265
- children: /*#__PURE__*/ S(e, { children: /*#__PURE__*/ S(r, {
267
+ children: /*#__PURE__*/ T(e, { children: /*#__PURE__*/ T(r, {
266
268
  direction: "vertical",
267
269
  distribution: "start",
268
270
  alignment: "stretch",
269
271
  className: "xl:flex-row",
270
272
  gap: "6",
271
- children: /*#__PURE__*/ S("div", {
273
+ children: /*#__PURE__*/ T("div", {
272
274
  className: "min-w-0 flex-1",
273
- children: /*#__PURE__*/ C(y, { children: [/*#__PURE__*/ S(z, {}), /*#__PURE__*/ S(O, {})] })
275
+ children: /*#__PURE__*/ E(S, { children: [/*#__PURE__*/ T(H, {}), /*#__PURE__*/ T(j, {})] })
274
276
  })
275
277
  }) })
276
278
  });
277
279
  }
278
280
  //#endregion
279
- export { B as component };
281
+ export { U as component };
280
282
 
281
- //# sourceMappingURL=_projectId-AjgJX6Gl.mjs.map
283
+ //# sourceMappingURL=_projectId-ZBk6vka0.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_projectId-ZBk6vka0.mjs","names":["useNavigate","useMatches","useParams","useRouteContext","useState","useEffect","SideNavigation","SideNavigationList","SideNavigationGroup","SideNavigationItem","Divider","isRouteInfo","Slot","SideNavBar","projectId","projectName","domainName","sections","navigate","matches","provider","serviceType","strict","slots","navBadge","service","serviceBadge","component","useShadowDOM","currentService","activeMatch","reverse","find","m","staticData","section","undefined","activeRouteInfo","activeSection","activeService","forceOpenCounter","setForceOpenCounter","Object","fromEntries","map","s","prev","ariaLabel","onClick","to","params","label","p","className","spacing","services","open","item","isStorageContainers","isSelected","span","selected","sideNavBanner","getServiceIndex","hasServiceByName","STORAGE_PROVIDER","STORAGE_TYPE_BY_PROVIDER","buildNavSections","projectId","availableServices","enabledServices","serviceExtensions","serviceIndex","isEnabled","service","includes","computeServices","label","t","navigate","nav","to","params","networkServices","storageServices","SWIFT","provider","storageType","CEPH","sectionMap","Map","section","services","extension","serviceType","serviceName","get","push","values","filter","s","length","Breadcrumb","BreadcrumbItem","JunoBreadcrumbItem","useNavigate","useParams","useContext","useMemo","BreadcrumbExtensionContext","useBreadcrumbs","Breadcrumbs","navigate","projectId","strict","breadcrumbs","extensionCrumbs","allBreadcrumbs","length","slice","active","onClick","to","params","undefined","className","map","item","index","label","icon","Outlet","useLoaderData","useRouteContext","AppShell","Button","Container","Stack","Status","SideNavBar","buildNavSections","Breadcrumbs","RouteError","useMemo","useLingui","Trans","BreadcrumbExtensionProvider","useSetBreadcrumb","ScopeError","type","code","message","currentDomain","id","name","Route","RouteComponent","t","loaderData","from","navigate","useNavigate","enabledServices","serviceExtensions","strict","projectName","crumbProject","projectId","projectLabel","crumbDomain","scopeError","domainName","to","bodyText","Error","availableServices","sections","component"],"sources":["../../src/client/routes/_auth/projects/-components/SideNavBar.tsx","../../src/client/routes/_auth/projects/-components/buildNavSections.ts","../../src/client/components/Breadcrumbs.tsx","../../src/client/routes/_auth/projects/$projectId.tsx?tsr-split=component"],"sourcesContent":["import { useNavigate, useMatches, useParams, useRouteContext } from \"@tanstack/react-router\"\nimport { useState, useEffect } from \"react\"\nimport {\n SideNavigation,\n SideNavigationList,\n SideNavigationGroup,\n SideNavigationItem,\n Divider,\n} from \"@cloudoperators/juno-ui-components/index\"\nimport { isRouteInfo } from \"@/client/routes/routeInfo\"\nimport { Slot } from \"@/client/components/Slot\"\nimport type { NavSection } from \"./buildNavSections\"\n\ninterface SideNavBarProps {\n projectId: string\n projectName: string\n domainName?: string\n sections: NavSection[]\n}\n\nexport const SideNavBar = ({ projectId, projectName, domainName, sections }: SideNavBarProps) => {\n const navigate = useNavigate()\n const matches = useMatches()\n const { provider, serviceType } = useParams({ strict: false }) as { provider?: string; serviceType?: string }\n const { slots } = useRouteContext({ strict: false })\n\n const navBadge = (service: string) => {\n if (!slots?.serviceBadge) return null\n return <Slot component={slots.serviceBadge} useShadowDOM={false} currentService={service} />\n }\n\n // Read active section/service from the deepest match that has meaningful RouteInfo staticData.\n // isRouteInfo uses an all-optional schema, so an empty {} also passes — require at least section\n // or service to be set so that stub routes (index/splat) don't shadow their layout parent.\n const activeMatch = [...matches]\n .reverse()\n .find(\n (m) => isRouteInfo(m.staticData) && (m.staticData.section !== undefined || m.staticData.service !== undefined)\n )\n const activeRouteInfo = activeMatch && isRouteInfo(activeMatch.staticData) ? activeMatch.staticData : undefined\n const activeSection = activeRouteInfo?.section ?? null\n // Extension mount routes carry only staticData.section (no static `service`), so fall back to\n // the $serviceType URL param to identify the active service and highlight its nav item.\n const activeService = activeRouteInfo?.service ?? serviceType ?? null\n\n // Track which sections should be forced open by incrementing a counter\n // This forces a remount of SideNavigationGroup since it doesn't expose onToggle\n const [forceOpenCounter, setForceOpenCounter] = useState<Record<string, number>>(() =>\n Object.fromEntries(sections.map((s) => [s.section, 0]))\n )\n\n // When navigating to a section, force it to expand by remounting\n useEffect(() => {\n if (activeSection) {\n setForceOpenCounter((prev) => ({ ...prev, [activeSection]: (prev[activeSection] || 0) + 1 }))\n }\n }, [activeSection])\n\n return (\n <SideNavigation ariaLabel=\"Project Side Navigation\">\n <>\n <SideNavigationList>\n <>\n <SideNavigationItem\n onClick={() => navigate({ to: \"/projects/$projectId\", params: { projectId } })}\n label={\n <>\n {domainName && <p className=\"text-theme-light text-xs leading-4 font-bold\">{domainName} /</p>}\n <p className=\"leading-5 font-normal\">{projectName}</p>\n </>\n }\n />\n <Divider spacing=\"1\" />\n {sections.map(({ section, label, services }) => (\n <SideNavigationGroup key={`${section}-${forceOpenCounter[section]}`} label={label} open={true}>\n {services.map((item) => {\n const isStorageContainers = activeSection === \"storage\" && activeService === \"containers\"\n const isSelected =\n activeSection === section &&\n (isStorageContainers ? item.params.provider === provider : activeService === item.service)\n\n return (\n <SideNavigationItem\n key={item.service}\n onClick={() => item.navigate(navigate)}\n label={\n <span className=\"flex items-start gap-2\">\n {item.label}\n {navBadge(item.service)}\n </span>\n }\n selected={isSelected}\n />\n )\n })}\n </SideNavigationGroup>\n ))}\n </>\n </SideNavigationList>\n {slots?.sideNavBanner && <Slot component={slots.sideNavBanner} />}\n </>\n </SideNavigation>\n )\n}\n","import { getServiceIndex } from \"@/server/Authentication/helpers\"\nimport { t } from \"@lingui/core/macro\"\nimport type { NavigateFn } from \"@tanstack/react-router\"\nimport type { ServiceExtension } from \"@/client/AuroraApp\"\nimport { hasServiceByName } from \"@/client/utils/serviceCatalog\"\nimport { STORAGE_PROVIDER, STORAGE_TYPE_BY_PROVIDER } from \"@/client/utils/storageProviders\"\n\nexport type NavItem = {\n service: string\n label: string\n navigate: (navigateFn: NavigateFn) => void\n params: Record<string, string>\n}\n\nexport type NavSection = {\n section: string\n label: string\n services: NavItem[]\n}\n\nexport function buildNavSections(\n projectId: string,\n availableServices: { type: string; name: string }[],\n enabledServices?: string[],\n serviceExtensions?: ServiceExtension[]\n): NavSection[] {\n const serviceIndex = getServiceIndex(availableServices)\n const isEnabled = (service: string) => !enabledServices || enabledServices.includes(service)\n\n const computeServices: NavItem[] = [\n ...(serviceIndex[\"image\"]?.[\"glance\"] && isEnabled(\"images\")\n ? [\n {\n service: \"images\",\n label: t`Images`,\n navigate: (nav: NavigateFn) => nav({ to: \"/projects/$projectId/compute/images\", params: { projectId } }),\n params: { projectId },\n },\n ]\n : []),\n ...(serviceIndex?.[\"compute\"]?.[\"nova\"] && isEnabled(\"flavors\")\n ? [\n {\n service: \"flavors\",\n label: t`Flavors`,\n navigate: (nav: NavigateFn) => nav({ to: \"/projects/$projectId/compute/flavors\", params: { projectId } }),\n params: { projectId },\n },\n ]\n : []),\n ]\n\n const networkServices: NavItem[] = serviceIndex[\"network\"]\n ? [\n ...(isEnabled(\"securitygroups\")\n ? [\n {\n service: \"securitygroups\",\n label: t`Security Groups`,\n navigate: (nav: NavigateFn) =>\n nav({ to: \"/projects/$projectId/network/securitygroups\", params: { projectId } }),\n params: { projectId },\n },\n ]\n : []),\n ...(isEnabled(\"floatingips\")\n ? [\n {\n service: \"floatingips\",\n label: t`Floating IPs`,\n navigate: (nav: NavigateFn) =>\n nav({ to: \"/projects/$projectId/network/floatingips\", params: { projectId } }),\n params: { projectId },\n },\n ]\n : []),\n ]\n : []\n\n const storageServices: NavItem[] = [\n ...(hasServiceByName(serviceIndex, STORAGE_PROVIDER.SWIFT) && isEnabled(\"containers\")\n ? [\n {\n service: \"containers\",\n label: t`Object Storage (Swift)`,\n navigate: (nav: NavigateFn) =>\n nav({\n to: \"/projects/$projectId/storage/$provider/$storageType\",\n params: {\n projectId,\n provider: STORAGE_PROVIDER.SWIFT,\n storageType: STORAGE_TYPE_BY_PROVIDER[STORAGE_PROVIDER.SWIFT],\n },\n }),\n params: {\n projectId,\n provider: STORAGE_PROVIDER.SWIFT,\n storageType: STORAGE_TYPE_BY_PROVIDER[STORAGE_PROVIDER.SWIFT],\n },\n },\n ]\n : []),\n ...(hasServiceByName(serviceIndex, STORAGE_PROVIDER.CEPH) && isEnabled(\"ceph-containers\")\n ? [\n {\n service: \"ceph-containers\",\n label: t`Object Storage (Ceph)`,\n navigate: (nav: NavigateFn) =>\n nav({\n to: \"/projects/$projectId/storage/$provider/$storageType\",\n params: {\n projectId,\n provider: STORAGE_PROVIDER.CEPH,\n storageType: STORAGE_TYPE_BY_PROVIDER[STORAGE_PROVIDER.CEPH],\n },\n }),\n params: {\n projectId,\n provider: STORAGE_PROVIDER.CEPH,\n storageType: STORAGE_TYPE_BY_PROVIDER[STORAGE_PROVIDER.CEPH],\n },\n },\n ]\n : []),\n ]\n\n // Seed the section map with built-in sections\n const sectionMap = new Map<string, NavSection>([\n [\"compute\", { section: \"compute\", label: t`Compute`, services: computeServices }],\n [\"network\", { section: \"network\", label: t`Network`, services: networkServices }],\n [\"storage\", { section: \"storage\", label: t`Storage`, services: storageServices }],\n [\"services\", { section: \"services\", label: t`Services`, services: [] }],\n ])\n\n // Merge service extensions: activate when the service exists in the project's catalog\n for (const extension of serviceExtensions ?? []) {\n if (!serviceIndex[extension.serviceType]?.[extension.serviceName]) continue\n if (enabledServices && !enabledServices.includes(extension.serviceType)) continue\n\n sectionMap.get(\"services\")?.services.push({\n service: extension.serviceType,\n label: extension.label,\n navigate: (nav: NavigateFn) =>\n nav({\n to: \"/projects/$projectId/services/$serviceType\",\n params: { projectId, serviceType: extension.serviceType },\n }),\n params: { projectId, serviceType: extension.serviceType },\n })\n }\n\n return [...sectionMap.values()].filter((s) => s.services.length > 0)\n}\n","import { Breadcrumb, BreadcrumbItem as JunoBreadcrumbItem } from \"@cloudoperators/juno-ui-components\"\nimport type { KnownIcons } from \"@cloudoperators/juno-ui-components\"\nimport { useNavigate, useParams } from \"@tanstack/react-router\"\nimport { useContext, useMemo } from \"react\"\nimport { BreadcrumbExtensionContext } from \"@/client/context/BreadcrumbExtensionContext\"\nimport { useBreadcrumbs, type BreadcrumbItem } from \"@/client/hooks/useBreadcrumbs\"\n\nexport function Breadcrumbs() {\n const navigate = useNavigate()\n const { projectId } = useParams({ strict: false }) as { projectId?: string }\n const { breadcrumbs: extensionCrumbs } = useContext(BreadcrumbExtensionContext)\n const breadcrumbs = useBreadcrumbs()\n\n const allBreadcrumbs = useMemo((): BreadcrumbItem[] => {\n if (extensionCrumbs.length === 0 || breadcrumbs.length === 0) return breadcrumbs\n\n // Extension crumbs present: deactivate the last OSS crumb (always the project crumb on\n // /services/* routes) and append the extension crumbs pushed by the sub-app.\n return [\n ...breadcrumbs.slice(0, -1),\n {\n ...breadcrumbs[breadcrumbs.length - 1],\n active: false,\n onClick: projectId\n ? () => navigate({ to: \"/projects/$projectId\" as never, params: { projectId } as never })\n : undefined,\n },\n ...extensionCrumbs,\n ]\n }, [breadcrumbs, extensionCrumbs, projectId, navigate])\n\n return (\n <Breadcrumb className=\"relative z-1 mt-8 mb-4\">\n {allBreadcrumbs.map((item, index) => (\n <JunoBreadcrumbItem\n key={index}\n label={item.label}\n icon={item.icon as KnownIcons | undefined}\n onClick={item.onClick}\n active={item.active}\n />\n ))}\n </Breadcrumb>\n )\n}\n","import { createFileRoute, Outlet, useLoaderData, useRouteContext } from \"@tanstack/react-router\"\nimport { AppShell, Button, Container, Stack, Status } from \"@cloudoperators/juno-ui-components\"\nimport { SideNavBar } from \"@/client/routes/_auth/projects/-components/SideNavBar\"\nimport { buildNavSections } from \"@/client/routes/_auth/projects/-components/buildNavSections\"\nimport { Breadcrumbs } from \"@/client/components/Breadcrumbs\"\nimport { RouteError } from \"@/client/components/Errors/RouteError\"\nimport { useMemo } from \"react\"\nimport { TRPCClientError } from \"@trpc/client\"\nimport { useLingui, Trans } from \"@lingui/react/macro\"\nimport { BreadcrumbExtensionProvider } from \"@/client/context/BreadcrumbExtensionContext\"\nimport { useSetBreadcrumb } from \"@/client/hooks/useSetBreadcrumb\"\n\n// Type for scope error that we catch and handle gracefully\ntype ScopeError = {\n type: \"scope_error\"\n code: string\n message: string\n currentDomain?: { id: string; name: string } | null\n}\n\nexport const Route = createFileRoute(\"/_auth/projects/$projectId\")({\n component: RouteComponent,\n errorComponent: ProjectErrorComponent,\n loader: async (options) => {\n const { context, params } = options\n\n // Catch rescope errors and return them as data instead of throwing\n // This allows the component to render a friendly error UI\n let scopeData\n let scopeError: ScopeError | undefined\n\n try {\n scopeData = await context.trpcClient?.auth.setCurrentScope.mutate({\n type: \"project\",\n projectId: params.projectId || \"\",\n })\n } catch (error) {\n if (error instanceof TRPCClientError) {\n // Try to get the current session's domain to show in the error message\n let currentDomain: { id: string; name: string } | null | undefined\n try {\n const currentScope = await context.trpcClient?.auth.getCurrentScope.query()\n if (currentScope?.domain?.id && currentScope?.domain?.name) {\n currentDomain = { id: currentScope.domain.id, name: currentScope.domain.name }\n }\n } catch {\n // Ignore errors fetching current scope\n }\n\n // Return error as data so the component can handle it\n scopeError = {\n type: \"scope_error\",\n code: error.data?.code || \"UNKNOWN\",\n message: error.message,\n currentDomain,\n }\n } else {\n // Unknown errors should still propagate\n throw error\n }\n }\n\n // If scope failed, return early with error state\n if (scopeError) {\n return {\n scopeError,\n projectId: params.projectId,\n // Provide minimal data for error rendering\n trpcClient: context.trpcClient,\n crumbDomain: undefined,\n crumbProject: undefined,\n availableServices: undefined,\n accountId: \"\",\n description: null,\n }\n }\n\n const [availableServices, project] = await Promise.all([\n context.trpcClient?.auth.getAvailableServices.query(),\n context.trpcClient?.project.getProject.query({ projectId: params.projectId }).catch(() => null),\n ])\n\n const accountId = scopeData?.domain?.id || \"\"\n const description = project?.description ?? null\n\n return {\n scopeError: undefined,\n trpcClient: context.trpcClient,\n crumbDomain: { path: `/projects`, name: scopeData?.domain?.name },\n crumbProject: scopeData?.project,\n availableServices,\n accountId,\n projectId: params.projectId,\n description,\n }\n },\n})\n\nfunction RouteComponent() {\n const { t } = useLingui()\n const loaderData = useLoaderData({ from: Route.id })\n const navigate = Route.useNavigate()\n const { enabledServices, serviceExtensions } = useRouteContext({ strict: false })\n\n const projectName = loaderData.crumbProject?.name || loaderData.projectId\n const projectLabel = loaderData.crumbDomain?.name ? `${loaderData.crumbDomain.name}/${projectName}` : projectName\n useSetBreadcrumb(Route.id, projectLabel)\n\n // Handle scope errors with a friendly UI using Juno's Status component\n if (loaderData.scopeError) {\n const { code, message, currentDomain } = loaderData.scopeError\n const domainName = currentDomain?.name\n\n if (code === \"UNAUTHORIZED\") {\n return (\n <Container className=\"py-8\">\n <Status\n status=\"error\"\n title={t`Session Changed`}\n body={t`Your session was changed or ended in another browser tab. This can happen if you logged out or switched to a different account or domain elsewhere. Please log in again to continue.`}\n action={\n <Button variant=\"primary\" onClick={() => navigate({ to: \"/\" })}>\n <Trans>Sign in</Trans>\n </Button>\n }\n />\n </Container>\n )\n }\n\n if (code === \"NOT_FOUND\") {\n const bodyText = domainName\n ? t`This project doesn't exist or is not accessible from your current domain. Your current domain is ${domainName}. Please select a project from your current domain.`\n : t`This project doesn't exist or is not accessible from your current domain. Please select a project from your current domain.`\n\n return (\n <Container className=\"py-8\">\n <Status\n status=\"error\"\n code={404}\n title={t`Project Not Accessible`}\n body={bodyText}\n action={\n <Button variant=\"primary\" onClick={() => navigate({ to: \"/projects\" })}>\n <Trans>Go to Projects</Trans>\n </Button>\n }\n />\n </Container>\n )\n }\n\n if (code === \"FORBIDDEN\") {\n return (\n <Container className=\"py-8\">\n <Status\n status=\"error\"\n code={403}\n title={t`Access Denied`}\n body={t`You don't have permission to access this project.`}\n action={\n <Button variant=\"primary\" onClick={() => navigate({ to: \"/projects\" })}>\n <Trans>Go to Projects</Trans>\n </Button>\n }\n />\n </Container>\n )\n }\n\n // Fallback for other error codes\n return <RouteError error={new Error(message)} />\n }\n\n const { availableServices, projectId, crumbProject, crumbDomain } = loaderData\n\n const sections = useMemo(\n () => buildNavSections(projectId, availableServices!, enabledServices, serviceExtensions),\n [projectId, availableServices, enabledServices, serviceExtensions]\n )\n\n return (\n <AppShell\n embedded\n sideNavigation={\n <SideNavBar\n sections={sections}\n projectId={projectId}\n projectName={crumbProject?.name || projectId}\n domainName={crumbDomain?.name}\n />\n }\n className=\"h-min-screen\"\n >\n <Container>\n <Stack direction=\"vertical\" distribution=\"start\" alignment=\"stretch\" className=\"xl:flex-row\" gap=\"6\">\n {/* Main content area */}\n <div className=\"min-w-0 flex-1\">\n <BreadcrumbExtensionProvider>\n <Breadcrumbs />\n <Outlet />\n </BreadcrumbExtensionProvider>\n </div>\n </Stack>\n </Container>\n </AppShell>\n )\n}\n\nfunction ProjectErrorComponent({ error }: { error: Error & { statusCode?: number } }) {\n const { t } = useLingui()\n const navigate = Route.useNavigate()\n\n // Handle 404 errors with a custom UI\n if (error.statusCode === 404 || error.message === \"Project not found\") {\n return (\n <Container className=\"py-8\">\n <Status\n status=\"error\"\n code={404}\n title={t`Project Not Found`}\n body={t`The project you are looking for doesn't exist or you don't have access to it.`}\n action={\n <Button variant=\"primary\" onClick={() => navigate({ to: \"/projects\" })}>\n <Trans>Go to Projects</Trans>\n </Button>\n }\n />\n </Container>\n )\n }\n\n // For all other errors, use the default RouteError component\n return <RouteError error={error} safeErrorMessage={error instanceof TRPCClientError ? error.message : undefined} />\n}\n"],"mappings":"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"}
@@ -14,7 +14,7 @@ var i = t("/_auth/projects/$projectId/network/securitygroups/$securityGroupId/")
14
14
  return { sgTitle: n?.name || n?.id || null };
15
15
  },
16
16
  head: ({ loaderData: e }) => ({ meta: [{ title: e?.sgTitle ?? "Security Group" }] }),
17
- component: n(() => import("./_securityGroupId-Bq0ZyG6z.mjs"), "component"),
17
+ component: n(() => import("./_securityGroupId-Cz2NUHdB.mjs"), "component"),
18
18
  beforeLoad: async ({ context: t, params: n }) => {
19
19
  let { trpcClient: i } = t, a = await i?.auth.getAvailableServices.query() || [], o = e(a);
20
20
  if (!o.network || !o.network.neutron) throw r({
@@ -26,4 +26,4 @@ var i = t("/_auth/projects/$projectId/network/securitygroups/$securityGroupId/")
26
26
  //#endregion
27
27
  export { i as t };
28
28
 
29
- //# sourceMappingURL=_securityGroupId-AO3wnH8g.mjs.map
29
+ //# sourceMappingURL=_securityGroupId-BwgZnoiR.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"_securityGroupId-AO3wnH8g.mjs","names":["createFileRoute","redirect","getServiceIndex","Route","staticData","section","service","analytics","name","RouteInfo","loader","context","params","sg","trpcClient","network","securityGroup","getById","query","project_id","projectId","securityGroupId","sgTitle","id","head","loaderData","meta","title","component","lazyRouteComponent","$$splitComponentImporter","beforeLoad","availableServices","auth","getAvailableServices","serviceIndex","to"],"sources":["../../src/client/routes/_auth/projects/$projectId/network/securitygroups/$securityGroupId/index.tsx"],"sourcesContent":["import {\n Button,\n PopupMenu,\n PopupMenuItem,\n PopupMenuOptions,\n PopupMenuToggle,\n Stack,\n Status,\n} from \"@cloudoperators/juno-ui-components/index\"\nimport { createFileRoute, redirect, useNavigate } from \"@tanstack/react-router\"\nimport { useMemo } from \"react\"\nimport { Trans, useLingui } from \"@lingui/react/macro\"\nimport { getServiceIndex } from \"@/server/Authentication/helpers\"\nimport { useProjectId } from \"@/client/hooks\"\nimport type { RouteInfo } from \"@/client/routes/routeInfo\"\nimport { ContentHeader } from \"@/client/components/ContentHeader/ContentHeader\"\nimport { useListWithFiltering } from \"@/client/utils/useListWithFiltering\"\nimport { trpcReact } from \"@/client/trpcClient\"\nimport { SecurityGroupDetailsView } from \"./-components/SecurityGroupDetailsView\"\nimport { EditSecurityGroupModal } from \"../-components/-modals/EditSecurityGroupModal\"\nimport { DeleteSecurityGroupDialog } from \"../-components/-modals/DeleteSecurityGroupDialog\"\nimport { useSecurityGroupDetails } from \"./-hooks/useSecurityGroupDetails\"\nimport { useSecurityGroupPermissions } from \"../-hooks/useSecurityGroupPermissions\"\nimport { useSetBreadcrumb } from \"@/client/hooks/useSetBreadcrumb\"\n\nexport const Route = createFileRoute(\"/_auth/projects/$projectId/network/securitygroups/$securityGroupId/\")({\n staticData: {\n section: \"network\",\n service: \"securitygroups\",\n analytics: {\n name: \"network.securitygroups.detail\",\n },\n } satisfies RouteInfo,\n loader: async ({ context, params }) => {\n const sg = await context.trpcClient?.network.securityGroup.getById.query({\n project_id: params.projectId,\n securityGroupId: params.securityGroupId,\n })\n return { sgTitle: sg?.name || sg?.id || null }\n },\n head: ({ loaderData }) => ({\n meta: [{ title: loaderData?.sgTitle ?? \"Security Group\" }],\n }),\n component: RouteComponent,\n beforeLoad: async ({ context, params }) => {\n const { trpcClient } = context\n\n const availableServices = (await trpcClient?.auth.getAvailableServices.query()) || []\n\n const serviceIndex = getServiceIndex(availableServices)\n\n // Redirect to the \"Projects Overview\" page if network service not available\n if (!serviceIndex[\"network\"]) {\n throw redirect({\n to: \"/projects/$projectId/network/securitygroups\",\n params: { projectId: params.projectId },\n })\n }\n\n if (!serviceIndex[\"network\"][\"neutron\"]) {\n // Redirect to the \"Network Services Overview\" page if the \"Neutron\" service is not available\n throw redirect({\n to: \"/projects/$projectId/network/securitygroups\",\n params: { projectId: params.projectId },\n })\n }\n },\n})\n\nfunction RouteComponent() {\n const { securityGroupId } = Route.useParams()\n const { trpcClient } = Route.useRouteContext()\n const projectId = useProjectId()\n const navigate = useNavigate()\n const { t } = useLingui()\n\n if (!trpcClient) {\n throw new Error(\"trpcClient is not available in route context\")\n }\n\n const {\n permissions,\n isLoading: isLoadingPermissions,\n isError: isPermissionsError,\n } = useSecurityGroupPermissions(projectId)\n\n // Rules filtering using the same pattern as List page\n const {\n searchTerm: rulesSearchTerm,\n sortSettings,\n filterSettings,\n handleSearchChange,\n handleSortChange,\n handleFilterChange,\n } = useListWithFiltering<\"direction\" | \"protocol\" | \"description\">({\n defaultSortKey: \"direction\",\n defaultSortDir: \"asc\",\n sortOptions: [\n { label: t`Direction`, value: \"direction\" },\n { label: t`Protocol`, value: \"protocol\" },\n { label: t`Description`, value: \"description\" },\n ],\n filterSettings: {\n filters: [\n {\n displayName: t`Direction`,\n filterName: \"direction\",\n values: [\"ingress\", \"egress\"],\n supportsMultiValue: false,\n },\n {\n displayName: t`Ethertype`,\n filterName: \"ethertype\",\n values: [\"IPv4\", \"IPv6\"],\n supportsMultiValue: false,\n },\n {\n displayName: t`Protocol`,\n filterName: \"protocol\",\n values: [\"tcp\", \"udp\", \"icmp\", \"ipv6-icmp\"],\n supportsMultiValue: false,\n },\n ],\n },\n })\n\n // Group filter controls for the hook\n const filterControls = {\n searchTerm: rulesSearchTerm,\n onSearchChange: handleSearchChange,\n sortSettings,\n onSortChange: handleSortChange,\n filterSettings,\n onFilterChange: handleFilterChange,\n }\n\n // Use custom hook for logic (now includes filtering/sorting)\n const {\n securityGroup,\n filteredAndSortedRules,\n isLoading,\n isError,\n error,\n isUpdating,\n updateError,\n isDeleting,\n deleteError,\n isDeletingRule,\n deleteRuleError,\n isCreatingRule,\n createRuleError,\n editModalOpen,\n deleteModalOpen,\n handleEdit,\n handleCloseEditModal,\n handleUpdate,\n handleDelete,\n handleCloseDeleteModal,\n handleDeleteSecurityGroup,\n handleDeleteRule,\n handleCreateRule,\n } = useSecurityGroupDetails({\n securityGroupId,\n filterControls,\n })\n\n useSetBreadcrumb(Route.id, securityGroup?.name ?? undefined)\n\n // Fetch available security groups for the Add Rule dropdown\n const { data: securityGroups } = trpcReact.network.securityGroup.list.useQuery({ project_id: projectId })\n const availableSecurityGroups = useMemo(() => {\n return (securityGroups || [])\n .filter((sg) => sg.id !== securityGroupId) // Exclude current group\n .map((sg) => ({\n id: sg.id,\n name: sg.name || sg.id,\n }))\n }, [securityGroups, securityGroupId])\n\n const handleBack = () => {\n navigate({\n to: \"/projects/$projectId/network/securitygroups\",\n params: { projectId },\n })\n }\n\n // Handle loading permissions\n if (isLoadingPermissions) {\n return <Status status=\"progress\" title={t`Loading Permissions...`} />\n }\n\n // Handle permissions error - default to no permissions\n const safePermissions = isPermissionsError\n ? {\n canView: false,\n canCreate: false,\n canUpdate: false,\n canDelete: false,\n canCreateRule: false,\n canDeleteRule: false,\n canManageAccess: false,\n canViewRBAC: false,\n }\n : permissions!\n\n // Handle loading state\n if (isLoading) {\n return <Status status=\"progress\" title={t`Loading Security Group Details...`} />\n }\n\n // Handle error state\n if (isError) {\n const errorMessage = error?.message || t`Unknown Error`\n\n return (\n <Status\n status=\"error\"\n title={t`Error Loading Security Group`}\n body={errorMessage}\n action={\n <Button onClick={handleBack} variant=\"primary\">\n <Trans>Back to Security Groups</Trans>\n </Button>\n }\n />\n )\n }\n\n // Handle no data state\n if (!securityGroup) {\n return (\n <Status\n status=\"empty\"\n title={t`Security Group Not Found`}\n action={\n <Button onClick={handleBack} variant=\"primary\">\n <Trans>Back to Security Groups</Trans>\n </Button>\n }\n />\n )\n }\n\n // Groups owned by another project are shared read-only copies: they cannot be edited or deleted,\n // the same rule the list applies to its row actions.\n const isReadOnly = Boolean(securityGroup.project_id && securityGroup.project_id !== projectId)\n const canEdit = safePermissions.canUpdate && !isReadOnly\n const canDelete = safePermissions.canDelete && !isReadOnly\n\n const headerActions = (canEdit || canDelete) && (\n <Stack gap=\"0.5\" alignment=\"center\">\n {canDelete && (\n <PopupMenu className=\"flex items-center\">\n <PopupMenuToggle as=\"div\">\n <Button icon=\"moreVert\" title={t`More Actions`} disabled={isDeleting} />\n </PopupMenuToggle>\n <PopupMenuOptions>\n <PopupMenuItem label={t`Delete Group`} onClick={handleDelete} />\n </PopupMenuOptions>\n </PopupMenu>\n )}\n\n {canEdit && (\n <Button variant=\"primary\" onClick={handleEdit} disabled={isDeleting}>\n <Trans>Edit Details</Trans>\n </Button>\n )}\n </Stack>\n )\n\n const handleConfirmDelete = async () => {\n try {\n await handleDeleteSecurityGroup()\n handleBack()\n } catch {\n // onError surfaces the toast; the dialog keeps showing deleteError\n }\n }\n\n // Render success state\n return (\n <Stack direction=\"vertical\">\n <ContentHeader title={securityGroup.name || securityGroup.id} projectId={projectId} actions={headerActions} />\n\n <SecurityGroupDetailsView\n securityGroup={securityGroup}\n filteredAndSortedRules={filteredAndSortedRules}\n onDeleteRule={handleDeleteRule}\n isDeletingRule={isDeletingRule}\n deleteRuleError={deleteRuleError}\n filterControls={filterControls}\n onCreateRule={handleCreateRule}\n isCreatingRule={isCreatingRule}\n createRuleError={createRuleError}\n availableSecurityGroups={availableSecurityGroups}\n currentProjectId={projectId}\n permissions={safePermissions}\n />\n\n <EditSecurityGroupModal\n securityGroup={securityGroup}\n open={editModalOpen}\n onClose={handleCloseEditModal}\n onUpdate={handleUpdate}\n isLoading={isUpdating}\n error={updateError}\n />\n\n {deleteModalOpen && (\n <DeleteSecurityGroupDialog\n securityGroup={securityGroup}\n isOpen={deleteModalOpen}\n onClose={handleCloseDeleteModal}\n onDelete={handleConfirmDelete}\n isDeleting={isDeleting}\n error={deleteError}\n />\n )}\n </Stack>\n )\n}\n"],"mappings":";;AAyBA,IAAaG,IAAQH,EAAgB,qEAAA,CAAA,CAAuE;CAC1GI,YAAY;EACVC,SAAS;EACTC,SAAS;EACTC,WAAW,EACTC,MAAM,gCACR;CACF;CACAE,QAAQ,OAAO,EAAEC,YAASC,gBAAQ;EAChC,IAAMC,IAAK,MAAMF,EAAQG,YAAYC,QAAQC,cAAcC,QAAQC,MAAM;GACvEC,YAAYP,EAAOQ;GACnBC,iBAAiBT,EAAOS;EAC1B,CAAA;EACA,OAAO,EAAEC,SAAST,GAAIL,QAAQK,GAAIU,MAAM,KAAK;CAC/C;CACAC,OAAO,EAAEC,qBAAkB,EACzBC,MAAM,CAAC,EAAEC,OAAOF,GAAYH,WAAW,iBAAiB,CAAC,EAC3D;CACAM,WAASC,mDAAA,WAAA;CACTE,YAAY,OAAO,EAAEpB,YAASC,gBAAQ;EACpC,IAAM,EAAEE,kBAAeH,GAEjBqB,IAAoB,MAAOlB,GAAYmB,KAAKC,qBAAqBhB,MAAAA,KAAY,CAAA,GAE7EiB,IAAejC,EAAgB8B,CAAAA;EAUrC,IAPI,CAACG,EAAa,WAOd,CAACA,EAAa,QAAW,SAE3B,MAAMlC,EAAS;GACbmC,IAAI;GACJxB,QAAQ,EAAEQ,WAAWR,EAAOQ,UAAU;EACxC,CAAA;CAEJ;AACF,CAAA"}
1
+ {"version":3,"file":"_securityGroupId-BwgZnoiR.mjs","names":["createFileRoute","redirect","getServiceIndex","Route","staticData","section","service","analytics","name","RouteInfo","loader","context","params","sg","trpcClient","network","securityGroup","getById","query","project_id","projectId","securityGroupId","sgTitle","id","head","loaderData","meta","title","component","lazyRouteComponent","$$splitComponentImporter","beforeLoad","availableServices","auth","getAvailableServices","serviceIndex","to"],"sources":["../../src/client/routes/_auth/projects/$projectId/network/securitygroups/$securityGroupId/index.tsx"],"sourcesContent":["import {\n Button,\n PopupMenu,\n PopupMenuItem,\n PopupMenuOptions,\n PopupMenuToggle,\n Stack,\n Status,\n} from \"@cloudoperators/juno-ui-components/index\"\nimport { createFileRoute, redirect, useNavigate } from \"@tanstack/react-router\"\nimport { useMemo } from \"react\"\nimport { Trans, useLingui } from \"@lingui/react/macro\"\nimport { getServiceIndex } from \"@/server/Authentication/helpers\"\nimport { useProjectId } from \"@/client/hooks\"\nimport type { RouteInfo } from \"@/client/routes/routeInfo\"\nimport { ContentHeader } from \"@/client/components/ContentHeader/ContentHeader\"\nimport { useListWithFiltering } from \"@/client/utils/useListWithFiltering\"\nimport { trpcReact } from \"@/client/trpcClient\"\nimport { SecurityGroupDetailsView } from \"./-components/SecurityGroupDetailsView\"\nimport { EditSecurityGroupModal } from \"../-components/-modals/EditSecurityGroupModal\"\nimport { DeleteSecurityGroupDialog } from \"../-components/-modals/DeleteSecurityGroupDialog\"\nimport { useSecurityGroupDetails } from \"./-hooks/useSecurityGroupDetails\"\nimport { useSecurityGroupPermissions } from \"../-hooks/useSecurityGroupPermissions\"\nimport { useSetBreadcrumb } from \"@/client/hooks/useSetBreadcrumb\"\n\nexport const Route = createFileRoute(\"/_auth/projects/$projectId/network/securitygroups/$securityGroupId/\")({\n staticData: {\n section: \"network\",\n service: \"securitygroups\",\n analytics: {\n name: \"network.securitygroups.detail\",\n },\n } satisfies RouteInfo,\n loader: async ({ context, params }) => {\n const sg = await context.trpcClient?.network.securityGroup.getById.query({\n project_id: params.projectId,\n securityGroupId: params.securityGroupId,\n })\n return { sgTitle: sg?.name || sg?.id || null }\n },\n head: ({ loaderData }) => ({\n meta: [{ title: loaderData?.sgTitle ?? \"Security Group\" }],\n }),\n component: RouteComponent,\n beforeLoad: async ({ context, params }) => {\n const { trpcClient } = context\n\n const availableServices = (await trpcClient?.auth.getAvailableServices.query()) || []\n\n const serviceIndex = getServiceIndex(availableServices)\n\n // Redirect to the \"Projects Overview\" page if network service not available\n if (!serviceIndex[\"network\"]) {\n throw redirect({\n to: \"/projects/$projectId/network/securitygroups\",\n params: { projectId: params.projectId },\n })\n }\n\n if (!serviceIndex[\"network\"][\"neutron\"]) {\n // Redirect to the \"Network Services Overview\" page if the \"Neutron\" service is not available\n throw redirect({\n to: \"/projects/$projectId/network/securitygroups\",\n params: { projectId: params.projectId },\n })\n }\n },\n})\n\nfunction RouteComponent() {\n const { securityGroupId } = Route.useParams()\n const { trpcClient } = Route.useRouteContext()\n const projectId = useProjectId()\n const navigate = useNavigate()\n const { t } = useLingui()\n\n if (!trpcClient) {\n throw new Error(\"trpcClient is not available in route context\")\n }\n\n const {\n permissions,\n isLoading: isLoadingPermissions,\n isError: isPermissionsError,\n } = useSecurityGroupPermissions(projectId)\n\n // Rules filtering using the same pattern as List page\n const {\n searchTerm: rulesSearchTerm,\n sortSettings,\n filterSettings,\n handleSearchChange,\n handleSortChange,\n handleFilterChange,\n } = useListWithFiltering<\"direction\" | \"protocol\" | \"description\">({\n defaultSortKey: \"direction\",\n defaultSortDir: \"asc\",\n sortOptions: [\n { label: t`Direction`, value: \"direction\" },\n { label: t`Protocol`, value: \"protocol\" },\n { label: t`Description`, value: \"description\" },\n ],\n filterSettings: {\n filters: [\n {\n displayName: t`Direction`,\n filterName: \"direction\",\n values: [\"ingress\", \"egress\"],\n supportsMultiValue: false,\n },\n {\n displayName: t`Ethertype`,\n filterName: \"ethertype\",\n values: [\"IPv4\", \"IPv6\"],\n supportsMultiValue: false,\n },\n {\n displayName: t`Protocol`,\n filterName: \"protocol\",\n values: [\"tcp\", \"udp\", \"icmp\", \"ipv6-icmp\"],\n supportsMultiValue: false,\n },\n ],\n },\n })\n\n // Group filter controls for the hook\n const filterControls = {\n searchTerm: rulesSearchTerm,\n onSearchChange: handleSearchChange,\n sortSettings,\n onSortChange: handleSortChange,\n filterSettings,\n onFilterChange: handleFilterChange,\n }\n\n // Use custom hook for logic (now includes filtering/sorting)\n const {\n securityGroup,\n filteredAndSortedRules,\n isLoading,\n isError,\n error,\n isUpdating,\n updateError,\n isDeleting,\n deleteError,\n isDeletingRule,\n deleteRuleError,\n isCreatingRule,\n createRuleError,\n editModalOpen,\n deleteModalOpen,\n handleEdit,\n handleCloseEditModal,\n handleUpdate,\n handleDelete,\n handleCloseDeleteModal,\n handleDeleteSecurityGroup,\n handleDeleteRule,\n handleCreateRule,\n } = useSecurityGroupDetails({\n securityGroupId,\n filterControls,\n })\n\n useSetBreadcrumb(Route.id, securityGroup?.name ?? undefined)\n\n // Fetch available security groups for the Add Rule dropdown\n const { data: securityGroups } = trpcReact.network.securityGroup.list.useQuery({ project_id: projectId })\n const availableSecurityGroups = useMemo(() => {\n return (securityGroups || [])\n .filter((sg) => sg.id !== securityGroupId) // Exclude current group\n .map((sg) => ({\n id: sg.id,\n name: sg.name || sg.id,\n }))\n }, [securityGroups, securityGroupId])\n\n const handleBack = () => {\n navigate({\n to: \"/projects/$projectId/network/securitygroups\",\n params: { projectId },\n })\n }\n\n // Handle loading permissions\n if (isLoadingPermissions) {\n return <Status status=\"progress\" title={t`Loading Permissions...`} />\n }\n\n // Handle permissions error - default to no permissions\n const safePermissions = isPermissionsError\n ? {\n canView: false,\n canCreate: false,\n canUpdate: false,\n canDelete: false,\n canCreateRule: false,\n canDeleteRule: false,\n canManageAccess: false,\n canViewRBAC: false,\n }\n : permissions!\n\n // Handle loading state\n if (isLoading) {\n return <Status status=\"progress\" title={t`Loading Security Group Details...`} />\n }\n\n // Handle error state\n if (isError) {\n const errorMessage = error?.message || t`Unknown Error`\n\n return (\n <Status\n status=\"error\"\n title={t`Error Loading Security Group`}\n body={errorMessage}\n action={\n <Button onClick={handleBack} variant=\"primary\">\n <Trans>Back to Security Groups</Trans>\n </Button>\n }\n />\n )\n }\n\n // Handle no data state\n if (!securityGroup) {\n return (\n <Status\n status=\"empty\"\n title={t`Security Group Not Found`}\n action={\n <Button onClick={handleBack} variant=\"primary\">\n <Trans>Back to Security Groups</Trans>\n </Button>\n }\n />\n )\n }\n\n // Groups owned by another project are shared read-only copies: they cannot be edited or deleted,\n // the same rule the list applies to its row actions.\n const isReadOnly = Boolean(securityGroup.project_id && securityGroup.project_id !== projectId)\n const canEdit = safePermissions.canUpdate && !isReadOnly\n const canDelete = safePermissions.canDelete && !isReadOnly\n\n const headerActions = (canEdit || canDelete) && (\n <Stack gap=\"0.5\" alignment=\"center\">\n {canDelete && (\n <PopupMenu className=\"flex items-center\">\n <PopupMenuToggle as=\"div\">\n <Button icon=\"moreVert\" title={t`More Actions`} disabled={isDeleting} />\n </PopupMenuToggle>\n <PopupMenuOptions>\n <PopupMenuItem label={t`Delete Group`} onClick={handleDelete} />\n </PopupMenuOptions>\n </PopupMenu>\n )}\n\n {canEdit && (\n <Button variant=\"primary\" onClick={handleEdit} disabled={isDeleting}>\n <Trans>Edit Details</Trans>\n </Button>\n )}\n </Stack>\n )\n\n const handleConfirmDelete = async () => {\n try {\n await handleDeleteSecurityGroup()\n handleBack()\n } catch {\n // onError surfaces the toast; the dialog keeps showing deleteError\n }\n }\n\n // Render success state\n return (\n <Stack direction=\"vertical\">\n <ContentHeader title={securityGroup.name || securityGroup.id} projectId={projectId} actions={headerActions} />\n\n <SecurityGroupDetailsView\n securityGroup={securityGroup}\n filteredAndSortedRules={filteredAndSortedRules}\n onDeleteRule={handleDeleteRule}\n isDeletingRule={isDeletingRule}\n deleteRuleError={deleteRuleError}\n filterControls={filterControls}\n onCreateRule={handleCreateRule}\n isCreatingRule={isCreatingRule}\n createRuleError={createRuleError}\n availableSecurityGroups={availableSecurityGroups}\n currentProjectId={projectId}\n permissions={safePermissions}\n />\n\n <EditSecurityGroupModal\n securityGroup={securityGroup}\n open={editModalOpen}\n onClose={handleCloseEditModal}\n onUpdate={handleUpdate}\n isLoading={isUpdating}\n error={updateError}\n />\n\n {deleteModalOpen && (\n <DeleteSecurityGroupDialog\n securityGroup={securityGroup}\n isOpen={deleteModalOpen}\n onClose={handleCloseDeleteModal}\n onDelete={handleConfirmDelete}\n isDeleting={isDeleting}\n error={deleteError}\n />\n )}\n </Stack>\n )\n}\n"],"mappings":";;AAyBA,IAAaG,IAAQH,EAAgB,qEAAA,CAAA,CAAuE;CAC1GI,YAAY;EACVC,SAAS;EACTC,SAAS;EACTC,WAAW,EACTC,MAAM,gCACR;CACF;CACAE,QAAQ,OAAO,EAAEC,YAASC,gBAAQ;EAChC,IAAMC,IAAK,MAAMF,EAAQG,YAAYC,QAAQC,cAAcC,QAAQC,MAAM;GACvEC,YAAYP,EAAOQ;GACnBC,iBAAiBT,EAAOS;EAC1B,CAAA;EACA,OAAO,EAAEC,SAAST,GAAIL,QAAQK,GAAIU,MAAM,KAAK;CAC/C;CACAC,OAAO,EAAEC,qBAAkB,EACzBC,MAAM,CAAC,EAAEC,OAAOF,GAAYH,WAAW,iBAAiB,CAAC,EAC3D;CACAM,WAASC,mDAAA,WAAA;CACTE,YAAY,OAAO,EAAEpB,YAASC,gBAAQ;EACpC,IAAM,EAAEE,kBAAeH,GAEjBqB,IAAoB,MAAOlB,GAAYmB,KAAKC,qBAAqBhB,MAAAA,KAAY,CAAA,GAE7EiB,IAAejC,EAAgB8B,CAAAA;EAUrC,IAPI,CAACG,EAAa,WAOd,CAACA,EAAa,QAAW,SAE3B,MAAMlC,EAAS;GACbmC,IAAI;GACJxB,QAAQ,EAAEQ,WAAWR,EAAOQ,UAAU;EACxC,CAAA;CAEJ;AACF,CAAA"}