@cobaltcore-dev/aurora 2.0.0 → 2.1.1

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 (128) 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-C4nAi7gY.mjs +724 -0
  4. package/dist/client/DeleteFlavorModal-C4nAi7gY.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-BPvssEzn.mjs +277 -0
  10. package/dist/client/_flavorId-BPvssEzn.mjs.map +1 -0
  11. package/dist/client/{_flavorId-CaA4-Pnf.mjs → _flavorId-CFUBcU-l.mjs} +3 -3
  12. package/dist/client/{_flavorId-CaA4-Pnf.mjs.map → _flavorId-CFUBcU-l.mjs.map} +1 -1
  13. package/dist/client/{_flavorId-YV3jCS5g.mjs → _flavorId-CZcIp0Ih.mjs} +3 -3
  14. package/dist/client/{_flavorId-YV3jCS5g.mjs.map → _flavorId-CZcIp0Ih.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-BAezmV0Y.mjs +526 -0
  20. package/dist/client/_imageId-BAezmV0Y.mjs.map +1 -0
  21. package/dist/client/{_imageId-pBSS0YcC.mjs → _imageId-D-jqz3e3.mjs} +3 -3
  22. package/dist/client/_imageId-D-jqz3e3.mjs.map +1 -0
  23. package/dist/client/{_imageId-mpXUMCh_.mjs → _imageId-gMdrdnVt.mjs} +3 -3
  24. package/dist/client/_imageId-gMdrdnVt.mjs.map +1 -0
  25. package/dist/client/{_projectId-BnTBxKfp.mjs → _projectId-BYccdAtX.mjs} +3 -3
  26. package/dist/client/{_projectId-BnTBxKfp.mjs.map → _projectId-BYccdAtX.mjs.map} +1 -1
  27. package/dist/client/{_projectId-AjgJX6Gl.mjs → _projectId-CBjZC1tv.mjs} +85 -83
  28. package/dist/client/_projectId-CBjZC1tv.mjs.map +1 -0
  29. package/dist/client/{_projectId-CVrdJ52s.mjs → _projectId-CLEa1zgw.mjs} +2 -2
  30. package/dist/client/{_projectId-CVrdJ52s.mjs.map → _projectId-CLEa1zgw.mjs.map} +1 -1
  31. package/dist/client/_projectId-D3VOH5lN.mjs +94 -0
  32. package/dist/client/_projectId-D3VOH5lN.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-CJVSNz6R.mjs} +2 -2
  49. package/dist/client/{flavors-ZudiAiTU.mjs.map → flavors-CJVSNz6R.mjs.map} +1 -1
  50. package/dist/client/{flavors-Bgq3KZmx.mjs → flavors-CflJDIEH.mjs} +90 -82
  51. package/dist/client/flavors-CflJDIEH.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/imageHelpers-DBhL2ebL.mjs +1272 -0
  59. package/dist/client/imageHelpers-DBhL2ebL.mjs.map +1 -0
  60. package/dist/client/{images-CnTW3bpy.mjs → images-BsylWUiH.mjs} +837 -849
  61. package/dist/client/images-BsylWUiH.mjs.map +1 -0
  62. package/dist/client/{images-DZHDYDQu.mjs → images-zQq1nSVv.mjs} +2 -2
  63. package/dist/client/{images-DZHDYDQu.mjs.map → images-zQq1nSVv.mjs.map} +1 -1
  64. package/dist/client/index.js +200 -198
  65. package/dist/client/index.js.map +1 -1
  66. package/dist/client/{objects-jYQBFzwg.mjs → objects-BudUAfWY.mjs} +4265 -4275
  67. package/dist/client/objects-BudUAfWY.mjs.map +1 -0
  68. package/dist/client/objects-Dc7sKUr8.mjs +67 -0
  69. package/dist/client/objects-Dc7sKUr8.mjs.map +1 -0
  70. package/dist/client/objects-Dw7oeTq2.mjs +80 -0
  71. package/dist/client/objects-Dw7oeTq2.mjs.map +1 -0
  72. package/dist/client/objects-_g7nGIn5.mjs +25 -0
  73. package/dist/client/objects-_g7nGIn5.mjs.map +1 -0
  74. package/dist/client/objects-eBxvY6_n.mjs +78 -0
  75. package/dist/client/objects-eBxvY6_n.mjs.map +1 -0
  76. package/dist/client/projects-yiK0HGSA.mjs.map +1 -1
  77. package/dist/client/{securitygroups--0PS2wbM.mjs → securitygroups-CSmsBzUb.mjs} +105 -104
  78. package/dist/client/securitygroups-CSmsBzUb.mjs.map +1 -0
  79. package/dist/client/{securitygroups-BRndk7wh.mjs → securitygroups-T7kaNFEp.mjs} +2 -2
  80. package/dist/client/{securitygroups-BRndk7wh.mjs.map → securitygroups-T7kaNFEp.mjs.map} +1 -1
  81. package/dist/client/{securitygroups-Cs2Op-tu.mjs → securitygroups-WLB1_IM6.mjs} +3 -3
  82. package/dist/client/{securitygroups-Cs2Op-tu.mjs.map → securitygroups-WLB1_IM6.mjs.map} +1 -1
  83. package/dist/client/{securitygroups-CdN4yAAw.mjs → securitygroups-o0IXZM6S.mjs} +2 -2
  84. package/dist/client/{securitygroups-CdN4yAAw.mjs.map → securitygroups-o0IXZM6S.mjs.map} +1 -1
  85. package/dist/client/serviceCatalog-B3lKWC9V.mjs +6 -0
  86. package/dist/client/serviceCatalog-B3lKWC9V.mjs.map +1 -0
  87. package/dist/client/storageProviders-CJ5rOh13.mjs +15 -0
  88. package/dist/client/storageProviders-CJ5rOh13.mjs.map +1 -0
  89. package/dist/client/trpcClient.d.ts +96 -4
  90. package/dist/client/useErrorTranslation-Dc0eE8Zt.mjs +83 -0
  91. package/dist/client/useErrorTranslation-Dc0eE8Zt.mjs.map +1 -0
  92. package/dist/client/utils/serviceCatalog.d.ts +9 -0
  93. package/dist/client/utils/storageProviders.d.ts +44 -0
  94. package/dist/server/index.d.ts +96 -4
  95. package/dist/server/index.js +1993 -904
  96. package/package.json +2 -2
  97. package/dist/client/ContentHeader-CPJbgYWn.mjs +0 -115
  98. package/dist/client/ContentHeader-CPJbgYWn.mjs.map +0 -1
  99. package/dist/client/DeleteFlavorModal-C2MqUgGR.mjs +0 -801
  100. package/dist/client/DeleteFlavorModal-C2MqUgGR.mjs.map +0 -1
  101. package/dist/client/ImageToastNotifications-BWfzOUMc.mjs +0 -1135
  102. package/dist/client/ImageToastNotifications-BWfzOUMc.mjs.map +0 -1
  103. package/dist/client/RouteIdLevelDefaultError-CSseJ1yG.mjs.map +0 -1
  104. package/dist/client/_flavorId-DYv_StX22.mjs +0 -276
  105. package/dist/client/_flavorId-DYv_StX22.mjs.map +0 -1
  106. package/dist/client/_imageId-mfKp3JHn.mjs +0 -558
  107. package/dist/client/_imageId-mfKp3JHn.mjs.map +0 -1
  108. package/dist/client/_imageId-mpXUMCh_.mjs.map +0 -1
  109. package/dist/client/_imageId-pBSS0YcC.mjs.map +0 -1
  110. package/dist/client/_projectId-AjgJX6Gl.mjs.map +0 -1
  111. package/dist/client/_projectId-DgciZslU.mjs +0 -92
  112. package/dist/client/_projectId-DgciZslU.mjs.map +0 -1
  113. package/dist/client/_storageType-BRWWJtow.mjs +0 -14
  114. package/dist/client/_storageType-BRWWJtow.mjs.map +0 -1
  115. package/dist/client/_storageType-CzZ4u9x_.mjs.map +0 -1
  116. package/dist/client/_storageType-c-DAu8qr.mjs.map +0 -1
  117. package/dist/client/_storageType-zeSZe--V.mjs +0 -7
  118. package/dist/client/_storageType-zeSZe--V.mjs.map +0 -1
  119. package/dist/client/flavors-Bgq3KZmx.mjs.map +0 -1
  120. package/dist/client/images-CnTW3bpy.mjs.map +0 -1
  121. package/dist/client/objects-25xsh4Ia.mjs +0 -66
  122. package/dist/client/objects-25xsh4Ia.mjs.map +0 -1
  123. package/dist/client/objects-KXY6dgwq.mjs +0 -82
  124. package/dist/client/objects-KXY6dgwq.mjs.map +0 -1
  125. package/dist/client/objects-QryuClU3.mjs +0 -82
  126. package/dist/client/objects-QryuClU3.mjs.map +0 -1
  127. package/dist/client/objects-jYQBFzwg.mjs.map +0 -1
  128. package/dist/client/securitygroups--0PS2wbM.mjs.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"_imageId-mpXUMCh_.mjs","names":["Button","useNavigate","useLingui","GlanceImage","MemberStatus","RouteIdLevelDefaultError","ImageErrorComponent","t","navigate","projectId","Route","useParams","to","params","errorComponent"],"sources":["../../src/client/routes/_auth/projects/$projectId/compute/images/$imageId.tsx?tsr-split=errorComponent"],"sourcesContent":["import {\n Button,\n Stack,\n Status,\n PopupMenu,\n PopupMenuToggle,\n PopupMenuOptions,\n PopupMenuItem,\n PopupMenuSectionSeparator,\n toast,\n} from \"@cloudoperators/juno-ui-components/index\"\nimport { createFileRoute, redirect, useNavigate, useParams, useSearch } from \"@tanstack/react-router\"\nimport { z } from \"zod\"\nimport type { RouteInfo } from \"@/client/routes/routeInfo\"\nimport { Trans, useLingui } from \"@lingui/react/macro\"\nimport { getServiceIndex } from \"@/server/Authentication/helpers\"\nimport { trpcReact } from \"@/client/trpcClient\"\nimport { useSetBreadcrumb } from \"@/client/hooks/useSetBreadcrumb\"\nimport { ImageDetailsView } from \"./-components/ImageDetailsView\"\nimport { EditImageDetailsModal } from \"./-components/EditImageDetailsModal\"\nimport { EditImageMetadataModal } from \"./-components/EditImageMetadataModal\"\nimport { DeleteImageModal } from \"./-components/DeleteImageModal\"\nimport { ActivateImageModal } from \"./-components/ActivateImageModal\"\nimport { DeactivateImageModal } from \"./-components/DeactivateImageModal\"\nimport { IMAGE_STATUSES, IMAGE_VISIBILITY } from \"../-constants/filters\"\nimport { GlanceImage, MemberStatus } from \"@/server/Compute/types/image\"\nimport { TRPCClientError } from \"@trpc/client\"\nimport { InferrableClientTypes } from \"@trpc/server/unstable-core-do-not-import\"\nimport {\n getImageAccessStatusUpdatedToast,\n getImageAccessStatusErrorToast,\n getImageUpdatedToast,\n getImageActivatedToast,\n getImageActivationErrorToast,\n getImageDeactivatedToast,\n getImageDeactivationErrorToast,\n getImageVisibilityUpdatedToast,\n getImageVisibilityUpdateErrorToast,\n} from \"./-components/ImageToastNotifications\"\nimport { useState } from \"react\"\nimport { ContentHeader } from \"@/client/components/ContentHeader/ContentHeader\"\nimport { RouteIdLevelDefaultError } from \"@/client/components/Errors/RouteIdLevelDefaultError\"\n\nfunction ImageErrorComponent() {\n const { t } = useLingui()\n const navigate = useNavigate()\n const { projectId } = Route.useParams()\n\n return (\n <RouteIdLevelDefaultError\n action={\n <Button\n variant=\"primary\"\n onClick={() => navigate({ to: \"/projects/$projectId/compute/images\", params: { projectId } })}\n >\n {t`Back to Images`}\n </Button>\n }\n />\n )\n}\n\nexport const Route = createFileRoute(\"/_auth/projects/$projectId/compute/images/$imageId\")({\n staticData: {\n section: \"compute\",\n service: \"images\",\n analytics: {\n name: \"compute.images.detail\",\n },\n } satisfies RouteInfo,\n validateSearch: z.object({\n tab: z.enum([\"details\", \"sharing\"]).optional(),\n }),\n loader: async ({ context, params }) => {\n try {\n const image = await context.trpcClient?.compute.getImageById.query({\n project_id: params.projectId,\n imageId: params.imageId,\n })\n return { imageTitle: (image?.name as string | undefined) ?? image?.id ?? null }\n } catch {\n return { imageTitle: null }\n }\n },\n head: ({ loaderData }) => ({\n meta: [{ title: loaderData?.imageTitle ?? \"Image Details\" }],\n }),\n component: RouteComponent,\n beforeLoad: async ({ context, params }) => {\n const { trpcClient } = context\n const { projectId } = params\n\n const availableServices = (await trpcClient?.auth.getAvailableServices.query()) || []\n\n const serviceIndex = getServiceIndex(availableServices)\n\n // Redirect to the \"Projects Overview\" page if none of compute services available\n if (!serviceIndex[\"image\"] && !serviceIndex[\"compute\"]) {\n throw redirect({\n to: \"/projects/$projectId\",\n params: { projectId },\n })\n }\n\n if (!serviceIndex[\"image\"][\"glance\"]) {\n // Redirect to the \"Compute Services Overview\" page if the \"Glance\" service is not available\n throw redirect({\n to: \"/projects/$projectId\",\n params: { projectId: params.projectId },\n })\n }\n },\n errorComponent: ImageErrorComponent,\n})\n\nfunction RouteComponent() {\n const { projectId, imageId } = useParams({\n from: \"/_auth/projects/$projectId/compute/images/$imageId\",\n })\n const { tab } = useSearch({\n from: \"/_auth/projects/$projectId/compute/images/$imageId\",\n })\n\n const navigate = useNavigate()\n const { t } = useLingui()\n\n const {\n data: image,\n status,\n error,\n } = trpcReact.compute.getImageById.useQuery({ project_id: projectId, imageId: imageId })\n\n useSetBreadcrumb(Route.id, image?.name as string | undefined)\n\n const { data: permissionsData } = trpcReact.compute.canUser.useQuery({\n project_id: projectId,\n permission: [\n \"images:delete\",\n \"images:update\",\n \"images:create_member\",\n \"images:delete_member\",\n \"images:update_member\",\n ],\n })\n\n const permissions = {\n canDelete: permissionsData?.[0] ?? false,\n canUpdate: permissionsData?.[1] ?? false,\n canCreateMember: permissionsData?.[2] ?? false,\n canDeleteMember: permissionsData?.[3] ?? false,\n canUpdateMember: permissionsData?.[4] ?? false,\n }\n\n const utils = trpcReact.useUtils()\n\n const [editDetailsModalOpen, setEditDetailsModalOpen] = useState(false)\n const [editMetadataModalOpen, setEditMetadataModalOpen] = useState(false)\n const [deleteModalOpen, setDeleteModalOpen] = useState(false)\n const [activateModalOpen, setActivateModalOpen] = useState(false)\n const [deactivateModalOpen, setDeactivateModalOpen] = useState(false)\n\n const updateImageMutation = trpcReact.compute.updateImage.useMutation({\n onSuccess: (updatedImage) => {\n utils.compute.getImageById.setData({ project_id: projectId, imageId }, updatedImage)\n utils.compute.listImagesWithPagination.invalidate()\n },\n })\n\n const deleteImageMutation = trpcReact.compute.deleteImage.useMutation({\n onSuccess: () => {\n utils.compute.listImagesWithPagination.invalidate()\n },\n })\n\n const deactivateImageMutation = trpcReact.compute.deactivateImage.useMutation({\n onSuccess: () => {\n utils.compute.getImageById.invalidate({ project_id: projectId, imageId })\n },\n })\n\n const reactivateImageMutation = trpcReact.compute.reactivateImage.useMutation({\n onSuccess: () => {\n utils.compute.getImageById.invalidate({ project_id: projectId, imageId })\n },\n })\n\n const updateImageVisibilityMutation = trpcReact.compute.updateImageVisibility.useMutation({\n onSuccess: (updatedImage) => {\n utils.compute.getImageById.setData({ project_id: projectId, imageId }, updatedImage)\n },\n })\n\n const isSharedWithMe =\n image?.visibility === IMAGE_VISIBILITY.SHARED && image?.owner !== undefined && image?.owner !== projectId\n\n const { data: myMemberData } = trpcReact.compute.getImageMember.useQuery(\n { project_id: projectId, imageId: imageId, memberId: projectId },\n { enabled: isSharedWithMe && !!imageId && !!projectId }\n )\n\n const updateMemberMutation = trpcReact.compute.updateImageMember.useMutation({\n onSuccess: () => {\n utils.compute.getImageMember.invalidate({ project_id: projectId, imageId: imageId, memberId: projectId })\n utils.compute.listImageMembers.invalidate({ project_id: projectId, imageId: imageId })\n utils.compute.listImagesWithPagination.invalidate()\n utils.compute.listSharedImagesByMemberStatus.invalidate()\n },\n })\n\n const handleMemberStatusChange = async (newStatus: MemberStatus) => {\n try {\n await updateMemberMutation.mutateAsync({ project_id: projectId, imageId, memberId: projectId, status: newStatus })\n const { message, ...options } = getImageAccessStatusUpdatedToast(newStatus)\n toast.info(message, options)\n } catch (error) {\n const errorMessage = (error as TRPCClientError<InferrableClientTypes>)?.message\n const { message, ...options } = getImageAccessStatusErrorToast(errorMessage)\n toast.error(message, options)\n }\n }\n\n const handleBack = () => {\n navigate({\n to: \"/projects/$projectId/compute/images\",\n params: { projectId },\n })\n }\n\n const isLoading =\n updateImageMutation.isPending ||\n deleteImageMutation.isPending ||\n deactivateImageMutation.isPending ||\n reactivateImageMutation.isPending ||\n updateImageVisibilityMutation.isPending\n\n const convertToJsonPatchOperations = (\n updatedProperties: Partial<GlanceImage>,\n originalImage: GlanceImage\n ): Array<{ op: \"add\" | \"replace\" | \"remove\"; path: string; value?: unknown }> => {\n const operations: Array<{ op: \"add\" | \"replace\" | \"remove\"; path: string; value?: unknown }> = []\n Object.entries(updatedProperties).forEach(([key, value]) => {\n const path = `/${key}`\n if (value === null || value === undefined) {\n if (key in originalImage) operations.push({ op: \"remove\", path })\n } else {\n const propertyExists = key in originalImage\n operations.push({ op: propertyExists ? \"replace\" : \"add\", path, value })\n }\n })\n return operations\n }\n\n const handleSaveEdit = async (updatedProperties: Partial<GlanceImage>): Promise<boolean> => {\n if (!image) return false\n const operations = convertToJsonPatchOperations(updatedProperties, image)\n try {\n await updateImageMutation.mutateAsync({ project_id: projectId, imageId, operations })\n setEditDetailsModalOpen(false)\n const { message, ...options } = getImageUpdatedToast(String(image.name ?? image.id))\n toast.success(message, options)\n return true\n } catch {\n return false\n }\n }\n\n const handleDelete = async (deletedImage: GlanceImage) => {\n try {\n await deleteImageMutation.mutateAsync({ project_id: projectId, imageId: deletedImage.id })\n setDeleteModalOpen(false)\n handleBack()\n } catch {\n setDeleteModalOpen(false)\n }\n }\n\n const handleActivate = async (img: GlanceImage) => {\n try {\n await reactivateImageMutation.mutateAsync({ project_id: projectId, imageId: img.id })\n setActivateModalOpen(false)\n const { message, ...options } = getImageActivatedToast(String(img.name ?? img.id))\n toast.success(message, options)\n } catch (error) {\n setActivateModalOpen(false)\n const { message, ...options } = getImageActivationErrorToast(img.id, (error as Error)?.message ?? \"\")\n toast.error(message, options)\n }\n }\n\n const handleDeactivate = async (img: GlanceImage) => {\n try {\n await deactivateImageMutation.mutateAsync({ project_id: projectId, imageId: img.id })\n setDeactivateModalOpen(false)\n const { message, ...options } = getImageDeactivatedToast(String(img.name ?? img.id))\n toast.success(message, options)\n } catch (error) {\n setDeactivateModalOpen(false)\n const { message, ...options } = getImageDeactivationErrorToast(img.id, (error as Error)?.message ?? \"\")\n toast.error(message, options)\n }\n }\n\n const handleUpdateVisibility = async (newVisibility: \"public\" | \"private\" | \"shared\" | \"community\") => {\n if (!image) return\n try {\n await updateImageVisibilityMutation.mutateAsync({\n project_id: projectId,\n imageId: image.id,\n visibility: newVisibility,\n })\n const { message, ...options } = getImageVisibilityUpdatedToast(String(image.name ?? image.id), newVisibility)\n toast.success(message, options)\n } catch (error) {\n const { message, ...options } = getImageVisibilityUpdateErrorToast(\n String(image.name ?? image.id),\n (error as Error)?.message ?? \"\"\n )\n toast.error(message, options)\n }\n }\n\n // Handle loading state\n if (status === \"pending\") {\n return <Status status=\"progress\" title={t`Loading Image Details...`} />\n }\n\n // Handle error state\n if (status === \"error\") {\n const errorMessage = error?.message || t`Unknown Error`\n\n return (\n <RouteIdLevelDefaultError\n errorTitle={t`Error Loading Image`}\n errorDescription={errorMessage}\n action={\n <Button onClick={handleBack} variant=\"primary\">\n <Trans>Back to Images</Trans>\n </Button>\n }\n />\n )\n }\n\n // Handle no data state\n if (!image) {\n return <ImageErrorComponent />\n }\n\n const isDeactivated = image.status === IMAGE_STATUSES.DEACTIVATED\n const isPrivate = image.visibility === IMAGE_VISIBILITY.PRIVATE\n const isMemberAccepted = myMemberData?.status === \"accepted\"\n const isImageOwner = image.owner === projectId\n\n const canRejectSharedImage = isSharedWithMe && isMemberAccepted && permissions.canUpdateMember\n const canUpdateOwnImage = !isSharedWithMe && permissions.canUpdate\n const canDeleteOwnImage = !isSharedWithMe && permissions.canDelete && !image.protected\n const canManageSharing =\n !isSharedWithMe &&\n isImageOwner &&\n image.visibility === IMAGE_VISIBILITY.SHARED &&\n (permissions.canCreateMember || permissions.canDeleteMember)\n\n const hasMoreActions = canRejectSharedImage || canUpdateOwnImage || canDeleteOwnImage || canManageSharing\n\n const headerActions = (hasMoreActions || (!isSharedWithMe && permissions.canUpdate)) && (\n <Stack gap=\"0.5\" alignment=\"center\">\n {(hasMoreActions || (!isSharedWithMe && permissions.canUpdate)) && (\n <PopupMenu className=\"flex items-center\">\n <PopupMenuToggle as=\"div\">\n <Button icon=\"moreVert\" title={t`More Actions`} disabled={isLoading} />\n </PopupMenuToggle>\n <PopupMenuOptions>\n {!isSharedWithMe && permissions.canUpdate && (\n <>\n <PopupMenuItem\n label={isDeactivated ? t`Activate` : t`Deactivate`}\n onClick={() => (isDeactivated ? setActivateModalOpen(true) : setDeactivateModalOpen(true))}\n />\n <PopupMenuItem\n onClick={() => setEditMetadataModalOpen(true)}\n label={t`Edit Metadata`}\n disabled={isLoading}\n />\n </>\n )}\n {!isSharedWithMe &&\n isImageOwner &&\n image.visibility === IMAGE_VISIBILITY.SHARED &&\n (permissions.canCreateMember || permissions.canDeleteMember) && (\n <PopupMenuItem\n label={t`Manage Access`}\n onClick={() =>\n navigate({\n to: \"/projects/$projectId/compute/images/$imageId\",\n params: { projectId, imageId: image.id },\n search: { tab: \"sharing\" },\n })\n }\n />\n )}\n {canRejectSharedImage && (\n <PopupMenuItem label={t`Reject`} onClick={() => handleMemberStatusChange(\"rejected\")} />\n )}\n {!isSharedWithMe && permissions.canUpdate && isPrivate && (\n <PopupMenuItem label={t`Set to \"Shared\"`} onClick={() => handleUpdateVisibility(\"shared\")} />\n )}\n {!isSharedWithMe && permissions.canDelete && !image.protected && (\n <>\n <PopupMenuSectionSeparator />\n <PopupMenuItem label={t`Delete`} onClick={() => setDeleteModalOpen(true)} />\n </>\n )}\n </PopupMenuOptions>\n </PopupMenu>\n )}\n\n {!isSharedWithMe && permissions.canUpdate && (\n <Button onClick={() => setEditDetailsModalOpen(true)} variant=\"primary\" disabled={isLoading}>\n <Trans>Edit Details</Trans>\n </Button>\n )}\n </Stack>\n )\n\n // Render success state\n return (\n <>\n <ContentHeader title={String(image.name ?? image.id)} projectId={projectId} actions={headerActions} />\n\n <div className=\"mt-3\">\n <ImageDetailsView\n key={image.id}\n image={image}\n currentProjectId={projectId}\n activeTab={tab ?? \"details\"}\n onTabChange={(newTab) =>\n navigate({\n search: { tab: newTab === \"details\" ? undefined : newTab } as unknown as true,\n })\n }\n permissions={{\n canCreateMember: permissions.canCreateMember,\n canDeleteMember: permissions.canDeleteMember,\n canUpdateMember: permissions.canUpdateMember,\n }}\n myMemberData={myMemberData}\n onMemberStatusChange={handleMemberStatusChange}\n isMemberStatusChanging={updateMemberMutation.isPending}\n />\n </div>\n\n {editDetailsModalOpen && (\n <EditImageDetailsModal\n image={image}\n isOpen={editDetailsModalOpen}\n isLoading={updateImageMutation.isPending}\n onClose={() => setEditDetailsModalOpen(false)}\n onSave={handleSaveEdit}\n />\n )}\n\n {editMetadataModalOpen && (\n <EditImageMetadataModal\n image={image}\n isOpen={editMetadataModalOpen}\n isLoading={updateImageMutation.isPending}\n onClose={() => setEditMetadataModalOpen(false)}\n onSave={handleSaveEdit}\n />\n )}\n\n {deleteModalOpen && (\n <DeleteImageModal\n image={image}\n isOpen={deleteModalOpen}\n isLoading={deleteImageMutation.isPending}\n isDisabled={!!image.protected}\n onClose={() => setDeleteModalOpen(false)}\n onDelete={handleDelete}\n />\n )}\n\n {activateModalOpen && (\n <ActivateImageModal\n image={image}\n isOpen={activateModalOpen}\n isLoading={reactivateImageMutation.isPending}\n onClose={() => setActivateModalOpen(false)}\n onActivate={handleActivate}\n />\n )}\n\n {deactivateModalOpen && (\n <DeactivateImageModal\n image={image}\n isOpen={deactivateModalOpen}\n isLoading={deactivateImageMutation.isPending}\n onClose={() => setDeactivateModalOpen(false)}\n onDeactivate={handleDeactivate}\n />\n )}\n </>\n )\n}\n"],"mappings":";;;;;;;AA2CA,SAASM,IAAAA;CACP,IAAM,EAAA,MAAA,GAAA,GAAA,MAAQJ,EAAAA,GACRM,IAAWP,EAAAA,GACX,EAAEQ,iBAAcC,EAAMC,UAAS;CAErC,OACE,gBAAC,GAAA,EACC,QACE,gBAAC,GAAA;EACC,SAAQ;EACR,eAAeH,EAAS;GAAEI,IAAI;GAAuCC,QAAQ,EAAEJ,aAAU;EAAE,CAAA;EAE1FF,UAAAA,EAAAA,EAAC,EAAA,IAAA,SAAA,CAAA;;AAKZ"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"_imageId-pBSS0YcC.mjs","names":["createFileRoute","redirect","z","getServiceIndex","GlanceImage","MemberStatus","Route","staticData","section","service","analytics","name","RouteInfo","validateSearch","object","tab","enum","optional","loader","context","params","image","trpcClient","compute","getImageById","query","project_id","projectId","imageId","imageTitle","id","head","loaderData","meta","title","component","lazyRouteComponent","$$splitComponentImporter","beforeLoad","availableServices","auth","getAvailableServices","serviceIndex","to","errorComponent","$$splitErrorComponentImporter"],"sources":["../../src/client/routes/_auth/projects/$projectId/compute/images/$imageId.tsx"],"sourcesContent":["import {\n Button,\n Stack,\n Status,\n PopupMenu,\n PopupMenuToggle,\n PopupMenuOptions,\n PopupMenuItem,\n PopupMenuSectionSeparator,\n toast,\n} from \"@cloudoperators/juno-ui-components/index\"\nimport { createFileRoute, redirect, useNavigate, useParams, useSearch } from \"@tanstack/react-router\"\nimport { z } from \"zod\"\nimport type { RouteInfo } from \"@/client/routes/routeInfo\"\nimport { Trans, useLingui } from \"@lingui/react/macro\"\nimport { getServiceIndex } from \"@/server/Authentication/helpers\"\nimport { trpcReact } from \"@/client/trpcClient\"\nimport { useSetBreadcrumb } from \"@/client/hooks/useSetBreadcrumb\"\nimport { ImageDetailsView } from \"./-components/ImageDetailsView\"\nimport { EditImageDetailsModal } from \"./-components/EditImageDetailsModal\"\nimport { EditImageMetadataModal } from \"./-components/EditImageMetadataModal\"\nimport { DeleteImageModal } from \"./-components/DeleteImageModal\"\nimport { ActivateImageModal } from \"./-components/ActivateImageModal\"\nimport { DeactivateImageModal } from \"./-components/DeactivateImageModal\"\nimport { IMAGE_STATUSES, IMAGE_VISIBILITY } from \"../-constants/filters\"\nimport { GlanceImage, MemberStatus } from \"@/server/Compute/types/image\"\nimport { TRPCClientError } from \"@trpc/client\"\nimport { InferrableClientTypes } from \"@trpc/server/unstable-core-do-not-import\"\nimport {\n getImageAccessStatusUpdatedToast,\n getImageAccessStatusErrorToast,\n getImageUpdatedToast,\n getImageActivatedToast,\n getImageActivationErrorToast,\n getImageDeactivatedToast,\n getImageDeactivationErrorToast,\n getImageVisibilityUpdatedToast,\n getImageVisibilityUpdateErrorToast,\n} from \"./-components/ImageToastNotifications\"\nimport { useState } from \"react\"\nimport { ContentHeader } from \"@/client/components/ContentHeader/ContentHeader\"\nimport { RouteIdLevelDefaultError } from \"@/client/components/Errors/RouteIdLevelDefaultError\"\n\nfunction ImageErrorComponent() {\n const { t } = useLingui()\n const navigate = useNavigate()\n const { projectId } = Route.useParams()\n\n return (\n <RouteIdLevelDefaultError\n action={\n <Button\n variant=\"primary\"\n onClick={() => navigate({ to: \"/projects/$projectId/compute/images\", params: { projectId } })}\n >\n {t`Back to Images`}\n </Button>\n }\n />\n )\n}\n\nexport const Route = createFileRoute(\"/_auth/projects/$projectId/compute/images/$imageId\")({\n staticData: {\n section: \"compute\",\n service: \"images\",\n analytics: {\n name: \"compute.images.detail\",\n },\n } satisfies RouteInfo,\n validateSearch: z.object({\n tab: z.enum([\"details\", \"sharing\"]).optional(),\n }),\n loader: async ({ context, params }) => {\n try {\n const image = await context.trpcClient?.compute.getImageById.query({\n project_id: params.projectId,\n imageId: params.imageId,\n })\n return { imageTitle: (image?.name as string | undefined) ?? image?.id ?? null }\n } catch {\n return { imageTitle: null }\n }\n },\n head: ({ loaderData }) => ({\n meta: [{ title: loaderData?.imageTitle ?? \"Image Details\" }],\n }),\n component: RouteComponent,\n beforeLoad: async ({ context, params }) => {\n const { trpcClient } = context\n const { projectId } = params\n\n const availableServices = (await trpcClient?.auth.getAvailableServices.query()) || []\n\n const serviceIndex = getServiceIndex(availableServices)\n\n // Redirect to the \"Projects Overview\" page if none of compute services available\n if (!serviceIndex[\"image\"] && !serviceIndex[\"compute\"]) {\n throw redirect({\n to: \"/projects/$projectId\",\n params: { projectId },\n })\n }\n\n if (!serviceIndex[\"image\"][\"glance\"]) {\n // Redirect to the \"Compute Services Overview\" page if the \"Glance\" service is not available\n throw redirect({\n to: \"/projects/$projectId\",\n params: { projectId: params.projectId },\n })\n }\n },\n errorComponent: ImageErrorComponent,\n})\n\nfunction RouteComponent() {\n const { projectId, imageId } = useParams({\n from: \"/_auth/projects/$projectId/compute/images/$imageId\",\n })\n const { tab } = useSearch({\n from: \"/_auth/projects/$projectId/compute/images/$imageId\",\n })\n\n const navigate = useNavigate()\n const { t } = useLingui()\n\n const {\n data: image,\n status,\n error,\n } = trpcReact.compute.getImageById.useQuery({ project_id: projectId, imageId: imageId })\n\n useSetBreadcrumb(Route.id, image?.name as string | undefined)\n\n const { data: permissionsData } = trpcReact.compute.canUser.useQuery({\n project_id: projectId,\n permission: [\n \"images:delete\",\n \"images:update\",\n \"images:create_member\",\n \"images:delete_member\",\n \"images:update_member\",\n ],\n })\n\n const permissions = {\n canDelete: permissionsData?.[0] ?? false,\n canUpdate: permissionsData?.[1] ?? false,\n canCreateMember: permissionsData?.[2] ?? false,\n canDeleteMember: permissionsData?.[3] ?? false,\n canUpdateMember: permissionsData?.[4] ?? false,\n }\n\n const utils = trpcReact.useUtils()\n\n const [editDetailsModalOpen, setEditDetailsModalOpen] = useState(false)\n const [editMetadataModalOpen, setEditMetadataModalOpen] = useState(false)\n const [deleteModalOpen, setDeleteModalOpen] = useState(false)\n const [activateModalOpen, setActivateModalOpen] = useState(false)\n const [deactivateModalOpen, setDeactivateModalOpen] = useState(false)\n\n const updateImageMutation = trpcReact.compute.updateImage.useMutation({\n onSuccess: (updatedImage) => {\n utils.compute.getImageById.setData({ project_id: projectId, imageId }, updatedImage)\n utils.compute.listImagesWithPagination.invalidate()\n },\n })\n\n const deleteImageMutation = trpcReact.compute.deleteImage.useMutation({\n onSuccess: () => {\n utils.compute.listImagesWithPagination.invalidate()\n },\n })\n\n const deactivateImageMutation = trpcReact.compute.deactivateImage.useMutation({\n onSuccess: () => {\n utils.compute.getImageById.invalidate({ project_id: projectId, imageId })\n },\n })\n\n const reactivateImageMutation = trpcReact.compute.reactivateImage.useMutation({\n onSuccess: () => {\n utils.compute.getImageById.invalidate({ project_id: projectId, imageId })\n },\n })\n\n const updateImageVisibilityMutation = trpcReact.compute.updateImageVisibility.useMutation({\n onSuccess: (updatedImage) => {\n utils.compute.getImageById.setData({ project_id: projectId, imageId }, updatedImage)\n },\n })\n\n const isSharedWithMe =\n image?.visibility === IMAGE_VISIBILITY.SHARED && image?.owner !== undefined && image?.owner !== projectId\n\n const { data: myMemberData } = trpcReact.compute.getImageMember.useQuery(\n { project_id: projectId, imageId: imageId, memberId: projectId },\n { enabled: isSharedWithMe && !!imageId && !!projectId }\n )\n\n const updateMemberMutation = trpcReact.compute.updateImageMember.useMutation({\n onSuccess: () => {\n utils.compute.getImageMember.invalidate({ project_id: projectId, imageId: imageId, memberId: projectId })\n utils.compute.listImageMembers.invalidate({ project_id: projectId, imageId: imageId })\n utils.compute.listImagesWithPagination.invalidate()\n utils.compute.listSharedImagesByMemberStatus.invalidate()\n },\n })\n\n const handleMemberStatusChange = async (newStatus: MemberStatus) => {\n try {\n await updateMemberMutation.mutateAsync({ project_id: projectId, imageId, memberId: projectId, status: newStatus })\n const { message, ...options } = getImageAccessStatusUpdatedToast(newStatus)\n toast.info(message, options)\n } catch (error) {\n const errorMessage = (error as TRPCClientError<InferrableClientTypes>)?.message\n const { message, ...options } = getImageAccessStatusErrorToast(errorMessage)\n toast.error(message, options)\n }\n }\n\n const handleBack = () => {\n navigate({\n to: \"/projects/$projectId/compute/images\",\n params: { projectId },\n })\n }\n\n const isLoading =\n updateImageMutation.isPending ||\n deleteImageMutation.isPending ||\n deactivateImageMutation.isPending ||\n reactivateImageMutation.isPending ||\n updateImageVisibilityMutation.isPending\n\n const convertToJsonPatchOperations = (\n updatedProperties: Partial<GlanceImage>,\n originalImage: GlanceImage\n ): Array<{ op: \"add\" | \"replace\" | \"remove\"; path: string; value?: unknown }> => {\n const operations: Array<{ op: \"add\" | \"replace\" | \"remove\"; path: string; value?: unknown }> = []\n Object.entries(updatedProperties).forEach(([key, value]) => {\n const path = `/${key}`\n if (value === null || value === undefined) {\n if (key in originalImage) operations.push({ op: \"remove\", path })\n } else {\n const propertyExists = key in originalImage\n operations.push({ op: propertyExists ? \"replace\" : \"add\", path, value })\n }\n })\n return operations\n }\n\n const handleSaveEdit = async (updatedProperties: Partial<GlanceImage>): Promise<boolean> => {\n if (!image) return false\n const operations = convertToJsonPatchOperations(updatedProperties, image)\n try {\n await updateImageMutation.mutateAsync({ project_id: projectId, imageId, operations })\n setEditDetailsModalOpen(false)\n const { message, ...options } = getImageUpdatedToast(String(image.name ?? image.id))\n toast.success(message, options)\n return true\n } catch {\n return false\n }\n }\n\n const handleDelete = async (deletedImage: GlanceImage) => {\n try {\n await deleteImageMutation.mutateAsync({ project_id: projectId, imageId: deletedImage.id })\n setDeleteModalOpen(false)\n handleBack()\n } catch {\n setDeleteModalOpen(false)\n }\n }\n\n const handleActivate = async (img: GlanceImage) => {\n try {\n await reactivateImageMutation.mutateAsync({ project_id: projectId, imageId: img.id })\n setActivateModalOpen(false)\n const { message, ...options } = getImageActivatedToast(String(img.name ?? img.id))\n toast.success(message, options)\n } catch (error) {\n setActivateModalOpen(false)\n const { message, ...options } = getImageActivationErrorToast(img.id, (error as Error)?.message ?? \"\")\n toast.error(message, options)\n }\n }\n\n const handleDeactivate = async (img: GlanceImage) => {\n try {\n await deactivateImageMutation.mutateAsync({ project_id: projectId, imageId: img.id })\n setDeactivateModalOpen(false)\n const { message, ...options } = getImageDeactivatedToast(String(img.name ?? img.id))\n toast.success(message, options)\n } catch (error) {\n setDeactivateModalOpen(false)\n const { message, ...options } = getImageDeactivationErrorToast(img.id, (error as Error)?.message ?? \"\")\n toast.error(message, options)\n }\n }\n\n const handleUpdateVisibility = async (newVisibility: \"public\" | \"private\" | \"shared\" | \"community\") => {\n if (!image) return\n try {\n await updateImageVisibilityMutation.mutateAsync({\n project_id: projectId,\n imageId: image.id,\n visibility: newVisibility,\n })\n const { message, ...options } = getImageVisibilityUpdatedToast(String(image.name ?? image.id), newVisibility)\n toast.success(message, options)\n } catch (error) {\n const { message, ...options } = getImageVisibilityUpdateErrorToast(\n String(image.name ?? image.id),\n (error as Error)?.message ?? \"\"\n )\n toast.error(message, options)\n }\n }\n\n // Handle loading state\n if (status === \"pending\") {\n return <Status status=\"progress\" title={t`Loading Image Details...`} />\n }\n\n // Handle error state\n if (status === \"error\") {\n const errorMessage = error?.message || t`Unknown Error`\n\n return (\n <RouteIdLevelDefaultError\n errorTitle={t`Error Loading Image`}\n errorDescription={errorMessage}\n action={\n <Button onClick={handleBack} variant=\"primary\">\n <Trans>Back to Images</Trans>\n </Button>\n }\n />\n )\n }\n\n // Handle no data state\n if (!image) {\n return <ImageErrorComponent />\n }\n\n const isDeactivated = image.status === IMAGE_STATUSES.DEACTIVATED\n const isPrivate = image.visibility === IMAGE_VISIBILITY.PRIVATE\n const isMemberAccepted = myMemberData?.status === \"accepted\"\n const isImageOwner = image.owner === projectId\n\n const canRejectSharedImage = isSharedWithMe && isMemberAccepted && permissions.canUpdateMember\n const canUpdateOwnImage = !isSharedWithMe && permissions.canUpdate\n const canDeleteOwnImage = !isSharedWithMe && permissions.canDelete && !image.protected\n const canManageSharing =\n !isSharedWithMe &&\n isImageOwner &&\n image.visibility === IMAGE_VISIBILITY.SHARED &&\n (permissions.canCreateMember || permissions.canDeleteMember)\n\n const hasMoreActions = canRejectSharedImage || canUpdateOwnImage || canDeleteOwnImage || canManageSharing\n\n const headerActions = (hasMoreActions || (!isSharedWithMe && permissions.canUpdate)) && (\n <Stack gap=\"0.5\" alignment=\"center\">\n {(hasMoreActions || (!isSharedWithMe && permissions.canUpdate)) && (\n <PopupMenu className=\"flex items-center\">\n <PopupMenuToggle as=\"div\">\n <Button icon=\"moreVert\" title={t`More Actions`} disabled={isLoading} />\n </PopupMenuToggle>\n <PopupMenuOptions>\n {!isSharedWithMe && permissions.canUpdate && (\n <>\n <PopupMenuItem\n label={isDeactivated ? t`Activate` : t`Deactivate`}\n onClick={() => (isDeactivated ? setActivateModalOpen(true) : setDeactivateModalOpen(true))}\n />\n <PopupMenuItem\n onClick={() => setEditMetadataModalOpen(true)}\n label={t`Edit Metadata`}\n disabled={isLoading}\n />\n </>\n )}\n {!isSharedWithMe &&\n isImageOwner &&\n image.visibility === IMAGE_VISIBILITY.SHARED &&\n (permissions.canCreateMember || permissions.canDeleteMember) && (\n <PopupMenuItem\n label={t`Manage Access`}\n onClick={() =>\n navigate({\n to: \"/projects/$projectId/compute/images/$imageId\",\n params: { projectId, imageId: image.id },\n search: { tab: \"sharing\" },\n })\n }\n />\n )}\n {canRejectSharedImage && (\n <PopupMenuItem label={t`Reject`} onClick={() => handleMemberStatusChange(\"rejected\")} />\n )}\n {!isSharedWithMe && permissions.canUpdate && isPrivate && (\n <PopupMenuItem label={t`Set to \"Shared\"`} onClick={() => handleUpdateVisibility(\"shared\")} />\n )}\n {!isSharedWithMe && permissions.canDelete && !image.protected && (\n <>\n <PopupMenuSectionSeparator />\n <PopupMenuItem label={t`Delete`} onClick={() => setDeleteModalOpen(true)} />\n </>\n )}\n </PopupMenuOptions>\n </PopupMenu>\n )}\n\n {!isSharedWithMe && permissions.canUpdate && (\n <Button onClick={() => setEditDetailsModalOpen(true)} variant=\"primary\" disabled={isLoading}>\n <Trans>Edit Details</Trans>\n </Button>\n )}\n </Stack>\n )\n\n // Render success state\n return (\n <>\n <ContentHeader title={String(image.name ?? image.id)} projectId={projectId} actions={headerActions} />\n\n <div className=\"mt-3\">\n <ImageDetailsView\n key={image.id}\n image={image}\n currentProjectId={projectId}\n activeTab={tab ?? \"details\"}\n onTabChange={(newTab) =>\n navigate({\n search: { tab: newTab === \"details\" ? undefined : newTab } as unknown as true,\n })\n }\n permissions={{\n canCreateMember: permissions.canCreateMember,\n canDeleteMember: permissions.canDeleteMember,\n canUpdateMember: permissions.canUpdateMember,\n }}\n myMemberData={myMemberData}\n onMemberStatusChange={handleMemberStatusChange}\n isMemberStatusChanging={updateMemberMutation.isPending}\n />\n </div>\n\n {editDetailsModalOpen && (\n <EditImageDetailsModal\n image={image}\n isOpen={editDetailsModalOpen}\n isLoading={updateImageMutation.isPending}\n onClose={() => setEditDetailsModalOpen(false)}\n onSave={handleSaveEdit}\n />\n )}\n\n {editMetadataModalOpen && (\n <EditImageMetadataModal\n image={image}\n isOpen={editMetadataModalOpen}\n isLoading={updateImageMutation.isPending}\n onClose={() => setEditMetadataModalOpen(false)}\n onSave={handleSaveEdit}\n />\n )}\n\n {deleteModalOpen && (\n <DeleteImageModal\n image={image}\n isOpen={deleteModalOpen}\n isLoading={deleteImageMutation.isPending}\n isDisabled={!!image.protected}\n onClose={() => setDeleteModalOpen(false)}\n onDelete={handleDelete}\n />\n )}\n\n {activateModalOpen && (\n <ActivateImageModal\n image={image}\n isOpen={activateModalOpen}\n isLoading={reactivateImageMutation.isPending}\n onClose={() => setActivateModalOpen(false)}\n onActivate={handleActivate}\n />\n )}\n\n {deactivateModalOpen && (\n <DeactivateImageModal\n image={image}\n isOpen={deactivateModalOpen}\n isLoading={deactivateImageMutation.isPending}\n onClose={() => setDeactivateModalOpen(false)}\n onDeactivate={handleDeactivate}\n />\n )}\n </>\n )\n}\n"],"mappings":";;;AA8DA,IAAaM,IAAQN,EAAgB,oDAAA,CAAA,CAAsD;CACzFO,YAAY;EACVC,SAAS;EACTC,SAAS;EACTC,WAAW,EACTC,MAAM,wBACR;CACF;CACAE,gBAAgBX,EAAEY,OAAO,EACvBC,KAAKb,EAAEc,KAAK,CAAC,WAAW,SAAU,CAAA,CAAA,CAAEC,SAAQ,EAC9C,CAAA;CACAC,QAAQ,OAAO,EAAEC,YAASC,gBAAQ;EAChC,IAAI;GACF,IAAMC,IAAQ,MAAMF,EAAQG,YAAYC,QAAQC,aAAaC,MAAM;IACjEC,YAAYN,EAAOO;IACnBC,SAASR,EAAOQ;GAClB,CAAA;GACA,OAAO,EAAEC,YAAaR,GAAOV,QAA+BU,GAAOS,MAAM,KAAK;EAChF,QAAQ;GACN,OAAO,EAAED,YAAY,KAAK;EAC5B;CACF;CACAE,OAAO,EAAEC,qBAAkB,EACzBC,MAAM,CAAC,EAAEC,OAAOF,GAAYH,cAAc,gBAAgB,CAAC,EAC7D;CACAM,WAASC,2CAAA,WAAA;CACTE,YAAY,OAAO,EAAEnB,YAASC,gBAAQ;EACpC,IAAM,EAAEE,kBAAeH,GACjB,EAAEQ,iBAAcP,GAEhBmB,IAAoB,MAAOjB,GAAYkB,KAAKC,qBAAqBhB,MAAAA,KAAY,CAAA,GAE7EiB,IAAevC,EAAgBoC,CAAAA;EAGrC,IAAI,CAACG,EAAa,SAAY,CAACA,EAAa,SAC1C,MAAMzC,EAAS;GACb0C,IAAI;GACJvB,QAAQ,EAAEO,aAAU;EACtB,CAAA;EAGF,IAAI,CAACe,EAAa,MAAS,QAEzB,MAAMzC,EAAS;GACb0C,IAAI;GACJvB,QAAQ,EAAEO,WAAWP,EAAOO,UAAU;EACxC,CAAA;CAEJ;CACAiB,gBAAcR,2CAAA,gBAAA;AAChB,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"_projectId-AjgJX6Gl.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","buildNavSections","projectId","availableServices","enabledServices","serviceExtensions","serviceIndex","isEnabled","service","includes","computeServices","label","t","navigate","nav","to","params","networkServices","storageServices","provider","storageType","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\"\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 ...(serviceIndex?.[\"object-store\"]?.[\"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: { projectId, provider: \"swift\", storageType: \"containers\" },\n }),\n params: { projectId, provider: \"swift\", storageType: \"containers\" },\n },\n ]\n : []),\n ...(serviceIndex?.[\"object-store-ceph\"]?.[\"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: { projectId, provider: \"ceph\", storageType: \"buckets\" },\n }),\n params: { projectId, provider: \"ceph\", storageType: \"buckets\" },\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 Expired`}\n body={t`Your session has expired. Please log in again. This may have occurred because you logged out or switched domains in another browser tab.`}\n action={\n <Button variant=\"primary\" onClick={() => navigate({ to: \"/\" })}>\n <Trans>Log 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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"}
@@ -1,92 +0,0 @@
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 "./ContentHeader-CPJbgYWn.mjs";
5
- import { jsx as a, jsxs as o } from "react/jsx-runtime";
6
- import { useLoaderData as s, useNavigate as c, useRouteContext as l } from "@tanstack/react-router";
7
- import { Trans as u, useLingui as d } from "@lingui/react";
8
- //#region src/client/routes/_auth/projects/$projectId/index.tsx?tsr-split=component
9
- function f({ group: e, label: r, to: i, service: s }) {
10
- let u = c(), { slots: d } = l({ strict: !1 });
11
- return /*#__PURE__*/ o(t, {
12
- onClick: () => u({ to: i }),
13
- className: "flex min-h-40 flex-col gap-4 px-3 pt-2 pb-3",
14
- "data-testid": "service-card",
15
- children: [/*#__PURE__*/ o("div", {
16
- className: "flex min-w-0 flex-col",
17
- children: [/*#__PURE__*/ a("p", {
18
- className: "text-theme-light text-xs leading-5 font-medium",
19
- children: e
20
- }), /*#__PURE__*/ o("div", {
21
- className: "flex items-center gap-2",
22
- children: [/*#__PURE__*/ a("p", {
23
- className: "text-theme-high text-lg leading-7 font-bold",
24
- "data-testid": "service-card-label",
25
- children: r
26
- }), d?.serviceBadge && /*#__PURE__*/ a(n, {
27
- component: d.serviceBadge,
28
- useShadowDOM: !1,
29
- currentService: s
30
- })]
31
- })]
32
- }), /*#__PURE__*/ a("div", { className: "flex-1" })]
33
- });
34
- }
35
- function p() {
36
- let { crumbProject: t, availableServices: n, projectId: c, description: p } = s({ from: "/_auth/projects/$projectId" }), { i18n: m, _: h } = d(), { enabledServices: g, serviceExtensions: _ } = l({ strict: !1 }), v = (e) => !g || g.includes(e), y = r(n ?? []), b = `/projects/${c}`, x = [];
37
- y.image?.glance && v("images") && x.push({
38
- group: m._({ id: "rp0Bd0" }),
39
- label: m._({ id: "an5hVd" }),
40
- to: `${b}/compute/images`,
41
- service: "images"
42
- }), y.compute?.nova && v("flavors") && x.push({
43
- group: m._({ id: "rp0Bd0" }),
44
- label: m._({ id: "neiJm0" }),
45
- to: `${b}/compute/flavors`,
46
- service: "flavors"
47
- }), y.network && (v("securitygroups") && x.push({
48
- group: m._({ id: "OR475H" }),
49
- label: m._({ id: "4opp4r" }),
50
- to: `${b}/network/securitygroups`,
51
- service: "securitygroups"
52
- }), v("floatingips") && x.push({
53
- group: m._({ id: "OR475H" }),
54
- label: m._({ id: "u77/s4" }),
55
- to: `${b}/network/floatingips`,
56
- service: "floatingips"
57
- })), y["object-store"]?.swift && v("containers") && x.push({
58
- group: m._({ id: "BrrIs8" }),
59
- label: m._({ id: "+OEi73" }),
60
- to: `${b}/storage/swift/containers`,
61
- service: "containers"
62
- }), y["object-store-ceph"]?.ceph && v("ceph-containers") && x.push({
63
- group: m._({ id: "BrrIs8" }),
64
- label: m._({ id: "KhNDX4" }),
65
- to: `${b}/storage/ceph/buckets`,
66
- service: "ceph-containers"
67
- });
68
- for (let e of _ ?? []) y[e.serviceType]?.[e.serviceName] && (!g || g.includes(e.serviceType)) && x.push({
69
- group: m._({ id: "MILoeL" }),
70
- label: e.label,
71
- to: `/projects/${c}/services/${e.serviceType}`,
72
- service: e.serviceType
73
- });
74
- return /*#__PURE__*/ o(e, {
75
- direction: "vertical",
76
- children: [/*#__PURE__*/ a(i, {
77
- title: t?.name ?? m._({ id: "e0NrBM" }),
78
- projectId: c,
79
- description: p
80
- }), x.length > 0 ? /*#__PURE__*/ a("div", {
81
- className: "grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4",
82
- children: x.map((e) => /*#__PURE__*/ a(f, { ...e }, e.to))
83
- }) : /*#__PURE__*/ a("p", {
84
- className: "text-theme-light text-sm",
85
- children: /*#__PURE__*/ a(u, { id: "Q1W//7" })
86
- })]
87
- });
88
- }
89
- //#endregion
90
- export { p as component };
91
-
92
- //# sourceMappingURL=_projectId-DgciZslU.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"_projectId-DgciZslU.mjs","names":["useLoaderData","useNavigate","Card","Stack","getServiceIndex","Trans","useLingui","useRouteContext","ContentHeader","Slot","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","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\"\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 (serviceIndex[\"object-store\"]?.[\"swift\"] && isEnabled(\"containers\"))\n cards.push({\n group: t`Storage`,\n label: t`Object Storage (Swift)`,\n to: `${base}/storage/swift/containers`,\n service: \"containers\",\n })\n if (serviceIndex[\"object-store-ceph\"]?.[\"ceph\"] && isEnabled(\"ceph-containers\"))\n cards.push({\n group: t`Storage`,\n label: t`Object Storage (Ceph)`,\n to: `${base}/storage/ceph/buckets`,\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,14 +0,0 @@
1
- import { t as e } from "./_storageType-CzZ4u9x_.mjs";
2
- import { t } from "./useSetBreadcrumb-tn7lBaQV.mjs";
3
- import { jsx as n } from "react/jsx-runtime";
4
- import { Outlet as r } from "@tanstack/react-router";
5
- import { useLingui as i } from "@lingui/react";
6
- //#region src/client/routes/_auth/projects/$projectId/storage/$provider/$storageType.tsx?tsr-split=component
7
- function a() {
8
- let { i18n: a, _: o } = i(), { provider: s } = e.useParams(), c = s === "swift" ? a._({ id: "+OEi73" }) : s === "ceph" ? a._({ id: "KhNDX4" }) : a._({ id: "BrrIs8" });
9
- return t(e.id, c), /*#__PURE__*/ n(r, {});
10
- }
11
- //#endregion
12
- export { a as component };
13
-
14
- //# sourceMappingURL=_storageType-BRWWJtow.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"_storageType-BRWWJtow.mjs","names":["Outlet","useLingui","useSetBreadcrumb","Route","StorageTypeLayout","t","provider","useParams","label","id","component"],"sources":["../../src/client/routes/_auth/projects/$projectId/storage/$provider/$storageType.tsx?tsr-split=component"],"sourcesContent":["import { createFileRoute, Outlet } from \"@tanstack/react-router\"\nimport { useLingui } from \"@lingui/react/macro\"\nimport type { RouteInfo } from \"@/client/routes/routeInfo\"\nimport { useSetBreadcrumb } from \"@/client/hooks/useSetBreadcrumb\"\n\nexport const Route = createFileRoute(\"/_auth/projects/$projectId/storage/$provider/$storageType\")({\n staticData: {\n section: \"storage\",\n service: \"containers\",\n } satisfies RouteInfo,\n component: StorageTypeLayout,\n})\n\nfunction StorageTypeLayout() {\n const { t } = useLingui()\n const { provider } = Route.useParams()\n const label =\n provider === \"swift\" ? t`Object Storage (Swift)` : provider === \"ceph\" ? t`Object Storage (Ceph)` : t`Storage`\n useSetBreadcrumb(Route.id, label)\n return <Outlet />\n}\n"],"mappings":";;;;;;AAaA,SAASI,IAAAA;CACP,IAAM,EAAA,MAAA,GAAA,GAAA,MAAQH,EAAAA,GACR,EAAEK,gBAAaH,EAAMI,UAAS,GAC9BC,IACJF,MAAa,UAAUD,EAAAA,EAAC,EAAA,IAAA,SAAA,CAAA,IAA2BC,MAAa,SAASD,EAAAA,EAAC,EAAA,IAAA,SAAA,CAAA,IAA0BA,EAAAA,EAAC,EAAA,IAAA,SAAA,CAAA;CAEvG,OADAH,EAAiBC,EAAMM,IAAID,CAAAA,GACpB,gBAAC,GAAA,CAAA,CAAA;AACV"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"_storageType-CzZ4u9x_.mjs","names":["createFileRoute","Route","staticData","section","service","RouteInfo","component","lazyRouteComponent","$$splitComponentImporter"],"sources":["../../src/client/routes/_auth/projects/$projectId/storage/$provider/$storageType.tsx"],"sourcesContent":["import { createFileRoute, Outlet } from \"@tanstack/react-router\"\nimport { useLingui } from \"@lingui/react/macro\"\nimport type { RouteInfo } from \"@/client/routes/routeInfo\"\nimport { useSetBreadcrumb } from \"@/client/hooks/useSetBreadcrumb\"\n\nexport const Route = createFileRoute(\"/_auth/projects/$projectId/storage/$provider/$storageType\")({\n staticData: {\n section: \"storage\",\n service: \"containers\",\n } satisfies RouteInfo,\n component: StorageTypeLayout,\n})\n\nfunction StorageTypeLayout() {\n const { t } = useLingui()\n const { provider } = Route.useParams()\n const label =\n provider === \"swift\" ? t`Object Storage (Swift)` : provider === \"ceph\" ? t`Object Storage (Ceph)` : t`Storage`\n useSetBreadcrumb(Route.id, label)\n return <Outlet />\n}\n"],"mappings":";AAKA,IAAaC,IAAQD,EAAgB,2DAAA,CAAA,CAA6D;CAChGE,YAAY;EACVC,SAAS;EACTC,SAAS;CACX;CACAE,WAASC,+CAAA,WAAA;AACX,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"_storageType-c-DAu8qr.mjs","names":["useState","trpcReact","Modal","TextInput","Stack","Message","useProjectId","useModalTracking","CreateContainerModal","isOpen","onClose","onSuccess","onPartialSuccess","maxContainerNameLength","existingContainers","useLingui","projectId","containerName","setContainerName","nameError","setNameError","submitError","setSubmitError","trackClose","markSubmitted","resetTracking","actionPrefix","utils","useUtils","createContainerMutation","storage","swift","createContainer","useMutation","data","listContainers","invalidate","name","trim","optionsApplied","optionsError","t","handleClose","onError","error","code","message","reset","validateName","trimmed","length","includes","some","c","handleNameChange","e","value","target","handleSubmit","mutate","project_id","container","handleKeyDown","key","title","open","onCancel","confirmButtonLabel","isPending","onConfirm","cancelButtonLabel","size","disableConfirmButton","disableCancelButton","disableCloseButton","direction","gap","variant","dismissible","onDismiss","role","aria-live","data-testid","p","className","label","required","onChange","onKeyDown","invalid","errortext","undefined","disabled","autoFocus","placeholder","useEffect","useState","useNavigate","useParams","Button","Checkbox","DataGrid","DataGridHeadCell","DataGridRow","DataGridCell","PopupMenu","PopupMenuItem","PopupMenuOptions","PopupMenuToggle","Status","formatBytesBinary","formatSwiftDate","useVirtualizedTableBody","CreateContainerModal","EmptyContainerModal","DeleteContainerModal","EditContainerMetadataModal","ManageContainerAccessModal","ContainerTableView","containers","createModalOpen","setCreateModalOpen","maxContainerNameLength","existingContainers","onCreateSuccess","onCreatePartialSuccess","onEmptySuccess","onEmptyError","onDeleteSuccess","onDeleteError","onPropertiesSuccess","onPropertiesError","onAclSuccess","onAclError","selectedContainers","setSelectedContainers","hasAnyBulkAction","projectId","provider","storageType","from","useLingui","navigate","scrollbarWidth","setScrollbarWidth","emptyModalContainer","setEmptyModalContainer","deleteModalContainer","setDeleteModalContainer","propertiesModalContainer","setPropertiesModalContainer","accessControlModalContainer","setAccessControlModalContainer","ref","tableBodyRef","elementRef","parentRef","height","bodyHeight","virtualItems","totalSize","measureElement","count","length","current","width","offsetWidth","clientWidth","handleSelectContainer","containerName","includes","filter","name","columnCount","gridColumnTemplate","div","className","style","paddingRight","columns","data-testid","marginRight","colSpan","status","title","t","body","position","map","virtualRow","container","index","isSelected","handleRowNavigate","to","params","data-index","top","left","transform","start","display","gridTemplateColumns","alignItems","tabIndex","onClick","onKeyDown","e","key","preventDefault","stopPropagation","checked","onChange","span","toLocaleString","last_modified","bytes","as","icon","aria-label","label","isOpen","onClose","onSuccess","onPartialSuccess","onError","Tooltip","TooltipTrigger","TooltipContent","Icon","formatBytesBinary","LimitItem","label","value","undefined","span","className","icon","size","CapabilityItem","Section","title","children","ContainerLimitsTooltip","serviceInfo","accountInfo","open","useLingui","swift","capabilityMap","account_quotas","t","container_quotas","bulk_delete","bulk_upload","slo","container_sync","tempurl","symlink","versioned_writes","capabilities","Object","entries","filter","key","map","triggerEvent","placement","objectCount","toLocaleString","bytesUsed","quotaBytes","max_file_size","max_container_name_length","max_object_name_length","container_listing_limit","account_listing_limit","max_header_size","max_meta_count","max_meta_name_length","max_meta_value_length","max_meta_overall_size","max_deletes_per_request","max_containers_per_extraction","max_manifest_segments","length","cap","z","useForm","useStore","useState","trpcReact","Modal","Status","Stack","Form","FormSection","TextInput","useProjectId","useModalTracking","MAX_VISIBLE","CONFIRM_WORD","EmptyContainersModal","isOpen","containers","onClose","onComplete","i18n","useLingui","projectId","progress","setProgress","trackClose","markSubmitted","resetTracking","actionPrefix","formSchema","object","confirm","string","transform","val","trim","refine","value","message","t","form","defaultValues","validators","onSubmit","emptyContainerMutation","isPending","emptiedCount","totalDeleted","errors","total","length","i","current","container","deleted","mutateAsync","project_id","name","err","Error","String","push","utils","storage","swift","listContainers","invalidate","handleClose","canEmpty","store","state","isSubmitting","values","useUtils","emptyContainer","useMutation","reset","totalCount","visibleContainers","slice","hiddenCount","progressCurrent","progressTotal","title","open","onCancel","confirmButtonLabel","confirmButtonVariant","cancelButtonLabel","onConfirm","handleSubmit","disableConfirmButton","disableCancelButton","disableCloseButton","size","status","className","div","p","strong","direction","gap","map","count","span","_","plural","id","e","preventDefault","Field","children","field","label","placeholder","onChange","handleChange","target","disabled","autoFocus","required","useState","useEffect","useRef","startTransition","SortInput","trpcReact","Button","Checkbox","DataGridToolbar","PopupMenu","PopupMenuItem","PopupMenuOptions","PopupMenuToggle","SearchInput","Status","Stack","toast","formatBytesBinary","parseSwiftDate","ContainerTableView","getContainerCreatedToast","getContainerCreatedWithWarningToast","getContainerEmptiedToast","getContainerEmptyErrorToast","getContainerDeletedToast","getContainerDeleteErrorToast","getContainerUpdatedToast","getContainerUpdateErrorToast","getContainerAclUpdatedToast","getContainerAclUpdateErrorToast","getContainersEmptyCompleteToast","ContainerLimitsTooltip","EmptyContainersModal","useNavigate","Route","useProjectId","SwiftContainers","i18n","useLingui","projectId","navigate","from","fullPath","sortBy","sortDirection","search","searchParam","useSearch","hasAnyBulkAction","createModalOpen","setCreateModalOpen","emptyAllModalOpen","setEmptyAllModalOpen","selectedContainers","setSelectedContainers","localSearchTerm","setLocalSearchTerm","debounceTimer","undefined","clearTimeout","current","handleCreateSuccess","containerName","message","options","success","handleCreatePartialSuccess","reason","warning","handleEmptySuccess","deletedCount","handleEmptyError","errorMessage","error","handleDeleteSuccess","handleDeleteError","handlePropertiesSuccess","handlePropertiesError","handleAclSuccess","handleAclError","handleEmptyAllComplete","emptiedCount","totalDeleted","errors","length","failedContainerNames","Set","map","e","split","prev","filter","name","has","severity","sortSettings","label","t","value","data","containers","isLoading","storage","swift","listContainers","useQuery","project_id","format","accountInfo","getAccountMetadata","serviceInfo","getServiceInfo","sortContainers","sort","a","b","comparison","localeCompare","count","bytes","last_modified","at","getTime","bt","filteredContainers","container","toLowerCase","includes","sortedContainers","handleSearchChange","term","handleSortChange","newSortSettings","resolvedSortBy","toString","resolvedDirection","status","title","body","bytesUsed","quotaBytes","remainingBytes","selectedContainerSummaries","c","hasSelection","selectedCount","totalCount","filteredCount","isFiltered","displayedNames","allSelected","every","n","someSelected","some","handleToggleSelectAll","div","className","direction","distribution","alignment","gap","selectClassName","onSortByChange","v","onSortDirectionChange","dir","variant","onClick","placeholder","data-testid","onInput","currentTarget","window","setTimeout","onSearch","onClear","checked","indeterminate","onChange","as","disabled","size","icon","_","plural","span","maxContainerNameLength","max_container_name_length","existingContainers","onCreateSuccess","onCreatePartialSuccess","onEmptySuccess","onEmptyError","onDeleteSuccess","onDeleteError","onPropertiesSuccess","onPropertiesError","onAclSuccess","onAclError","isOpen","onClose","onComplete","useParams","ErrorBoundary","SwiftContainers","CephBuckets","Trans","useLingui","ContentHeader","StorageDashboard","project","provider","from","select","params","projectId","storageType","t","pageTitle","SplitComponent","component"],"sources":["../../src/client/routes/_auth/projects/$projectId/storage/-components/Swift/Containers/CreateContainerModal.tsx","../../src/client/routes/_auth/projects/$projectId/storage/-components/Swift/Containers/ContainerTableView.tsx","../../src/client/routes/_auth/projects/$projectId/storage/-components/Swift/Containers/ContainerLimitsTooltip.tsx","../../src/client/routes/_auth/projects/$projectId/storage/-components/Swift/Containers/EmptyContainersModal.tsx","../../src/client/routes/_auth/projects/$projectId/storage/-components/Swift/Containers/index.tsx","../../src/client/routes/_auth/projects/$projectId/storage/$provider/$storageType/index.tsx?tsr-split=component"],"sourcesContent":["import { useState } from \"react\"\nimport { Trans, useLingui } from \"@lingui/react/macro\"\nimport { trpcReact } from \"@/client/trpcClient\"\nimport { Modal, TextInput, Stack, Message } from \"@cloudoperators/juno-ui-components\"\nimport { useProjectId } from \"@/client/hooks/useProjectId\"\nimport { useModalTracking } from \"@/client/hooks/useModalTracking\"\nimport { ContainerSummary } from \"@/server/Storage/types/swift\"\n\ninterface CreateContainerModalProps {\n isOpen: boolean\n onClose: () => void\n onSuccess?: (containerName: string) => void\n onPartialSuccess?: (containerName: string, reason: string) => void\n maxContainerNameLength?: number\n existingContainers?: ContainerSummary[]\n}\n\nexport const CreateContainerModal = ({\n isOpen,\n onClose,\n onSuccess,\n onPartialSuccess,\n maxContainerNameLength = 256,\n existingContainers = [],\n}: CreateContainerModalProps) => {\n const { t } = useLingui()\n const projectId = useProjectId()\n const [containerName, setContainerName] = useState(\"\")\n const [nameError, setNameError] = useState<string | null>(null)\n const [submitError, setSubmitError] = useState<string | null>(null)\n\n const { trackClose, markSubmitted, resetTracking } = useModalTracking({\n isOpen,\n actionPrefix: \"storage.swift.container.create\",\n })\n\n const utils = trpcReact.useUtils()\n\n const createContainerMutation = trpcReact.storage.swift.createContainer.useMutation({\n onSuccess: (data) => {\n utils.storage.swift.listContainers.invalidate()\n const name = containerName.trim()\n if (data.optionsApplied) {\n onSuccess?.(name)\n } else {\n onPartialSuccess?.(name, data.optionsError ?? t`The container's settings could not be applied.`)\n }\n handleClose()\n },\n onError: (error) => {\n if (error.data?.code === \"CONFLICT\") {\n setNameError(t`A container with this name already exists`)\n setSubmitError(null)\n } else {\n setSubmitError(error.message || t`The container could not be created. Try again or choose a different name.`)\n }\n\n resetTracking()\n },\n })\n\n const handleClose = () => {\n trackClose()\n setContainerName(\"\")\n setNameError(null)\n setSubmitError(null)\n resetTracking()\n createContainerMutation.reset()\n onClose()\n }\n\n const validateName = (name: string): boolean => {\n const trimmed = name.trim()\n\n if (!trimmed) {\n setNameError(t`Container name is required`)\n return false\n }\n if (trimmed.length > maxContainerNameLength) {\n setNameError(t`Container name must be ${maxContainerNameLength} characters or fewer`)\n return false\n }\n if (trimmed.includes(\"/\")) {\n setNameError(t`Container name cannot contain slashes`)\n return false\n }\n\n if (existingContainers.some((c) => c.name === trimmed)) {\n setNameError(t`A container with this name already exists`)\n return false\n }\n\n setNameError(null)\n return true\n }\n\n const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const value = e.target.value\n setContainerName(value)\n setSubmitError(null)\n if (nameError) validateName(value)\n }\n\n const handleSubmit = () => {\n if (!validateName(containerName)) return\n setSubmitError(null)\n markSubmitted()\n createContainerMutation.mutate({\n project_id: projectId,\n container: containerName.trim(),\n })\n }\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === \"Enter\") {\n handleSubmit()\n }\n }\n\n if (!isOpen) return null\n\n return (\n <Modal\n title={t`Create Container`}\n open={isOpen}\n onCancel={handleClose}\n confirmButtonLabel={createContainerMutation.isPending ? t`Creating...` : t`Create`}\n onConfirm={handleSubmit}\n cancelButtonLabel={t`Cancel`}\n size=\"small\"\n disableConfirmButton={createContainerMutation.isPending || !containerName.trim()}\n disableCancelButton={createContainerMutation.isPending}\n disableCloseButton={createContainerMutation.isPending}\n >\n <Stack direction=\"vertical\" gap=\"6\">\n {submitError && (\n <Message\n variant=\"error\"\n dismissible\n onDismiss={() => setSubmitError(null)}\n role=\"alert\"\n aria-live=\"assertive\"\n data-testid=\"create-container-error\"\n >\n {submitError}\n </Message>\n )}\n <p className=\"text-theme-default\">\n <Trans>\n Inside a project, objects are stored in containers. Containers are where you define access permissions and\n quotas.\n </Trans>\n </p>\n <TextInput\n label={t`Container name`}\n required\n value={containerName}\n onChange={handleNameChange}\n onKeyDown={handleKeyDown}\n invalid={!!nameError}\n errortext={nameError || undefined}\n disabled={createContainerMutation.isPending}\n autoFocus\n placeholder={t`Enter container name`}\n />\n </Stack>\n </Modal>\n )\n}\n","import { useEffect, useState } from \"react\"\nimport { useNavigate, useParams } from \"@tanstack/react-router\"\nimport {\n Button,\n Checkbox,\n DataGrid,\n DataGridHeadCell,\n DataGridRow,\n DataGridCell,\n PopupMenu,\n PopupMenuItem,\n PopupMenuOptions,\n PopupMenuToggle,\n Status,\n} from \"@cloudoperators/juno-ui-components\"\nimport { Trans, useLingui } from \"@lingui/react/macro\"\nimport { ContainerSummary } from \"@/server/Storage/types/swift\"\nimport { formatBytesBinary } from \"@/client/utils/formatBytes\"\nimport { formatSwiftDate } from \"@/client/utils/formatSwiftDate\"\nimport { useVirtualizedTableBody } from \"@/client/hooks/useVirtualizedTableBody\"\nimport { CreateContainerModal } from \"./CreateContainerModal\"\nimport { EmptyContainerModal } from \"./EmptyContainerModal\"\nimport { DeleteContainerModal } from \"./DeleteContainerModal\"\nimport { EditContainerMetadataModal } from \"./EditContainerMetadataModal\"\nimport { ManageContainerAccessModal } from \"./ManageContainerAccessModal\"\n\ninterface ContainerTableViewProps {\n containers: ContainerSummary[]\n createModalOpen: boolean\n setCreateModalOpen: (open: boolean) => void\n maxContainerNameLength?: number\n existingContainers?: ContainerSummary[]\n onCreateSuccess: (containerName: string) => void\n onCreatePartialSuccess: (containerName: string, reason: string) => void\n onEmptySuccess: (containerName: string, deletedCount: number) => void\n onEmptyError: (containerName: string, errorMessage: string) => void\n onDeleteSuccess: (containerName: string) => void\n onDeleteError: (containerName: string, errorMessage: string) => void\n onPropertiesSuccess: (containerName: string) => void\n onPropertiesError: (containerName: string, errorMessage: string) => void\n onAclSuccess: (containerName: string) => void\n onAclError: (containerName: string, errorMessage: string) => void\n selectedContainers: string[]\n setSelectedContainers: (containers: string[]) => void\n // When false, the row-selection column (header checkbox + per-row checkboxes)\n // is dropped entirely and the grid renders one fewer column. Defaults to true\n // so existing callers that don't pass it keep the selectable layout.\n hasAnyBulkAction?: boolean\n}\n\nexport const ContainerTableView = ({\n containers,\n createModalOpen,\n setCreateModalOpen,\n maxContainerNameLength,\n existingContainers = [],\n onCreateSuccess,\n onCreatePartialSuccess,\n onEmptySuccess,\n onEmptyError,\n onDeleteSuccess,\n onDeleteError,\n onPropertiesSuccess,\n onPropertiesError,\n onAclSuccess,\n onAclError,\n selectedContainers,\n setSelectedContainers,\n hasAnyBulkAction = true,\n}: ContainerTableViewProps) => {\n const { projectId, provider, storageType } = useParams({\n from: \"/_auth/projects/$projectId/storage/$provider/$storageType/\",\n })\n\n const { t } = useLingui()\n const navigate = useNavigate()\n\n const [scrollbarWidth, setScrollbarWidth] = useState(0)\n const [emptyModalContainer, setEmptyModalContainer] = useState<ContainerSummary | null>(null)\n const [deleteModalContainer, setDeleteModalContainer] = useState<ContainerSummary | null>(null)\n const [propertiesModalContainer, setPropertiesModalContainer] = useState<ContainerSummary | null>(null)\n const [accessControlModalContainer, setAccessControlModalContainer] = useState<ContainerSummary | null>(null)\n\n // Height measured from the space actually left below the table, plus a\n // virtualizer that stays silent until that height is known.\n const {\n ref: tableBodyRef,\n elementRef: parentRef,\n height: bodyHeight,\n virtualItems,\n totalSize,\n measureElement,\n } = useVirtualizedTableBody({ count: containers.length })\n\n // Calculate scrollbar width\n useEffect(() => {\n if (parentRef.current) {\n const width = parentRef.current.offsetWidth - parentRef.current.clientWidth\n setScrollbarWidth(width)\n }\n }, [containers.length, bodyHeight])\n\n const handleSelectContainer = (containerName: string) => {\n if (selectedContainers.includes(containerName)) {\n setSelectedContainers(selectedContainers.filter((name) => name !== containerName))\n } else {\n setSelectedContainers([...selectedContainers, containerName])\n }\n }\n\n // Column count and template depend on whether the selection column is shown.\n // With selection: checkbox, name, count, last modified, size, actions menu (6).\n // Without selection: the leading 40px checkbox column is dropped (5).\n const columnCount = hasAnyBulkAction ? 6 : 5\n const gridColumnTemplate = hasAnyBulkAction\n ? \"40px minmax(200px, 2fr) minmax(100px, 1fr) minmax(180px, 2fr) minmax(100px, 1fr) 60px\"\n : \"minmax(200px, 2fr) minmax(100px, 1fr) minmax(180px, 2fr) minmax(100px, 1fr) 60px\"\n\n return (\n <>\n <div className=\"relative\">\n {/* Table Header with scrollbar padding */}\n <div style={{ paddingRight: `${scrollbarWidth}px` }}>\n <DataGrid\n columns={columnCount}\n gridColumnTemplate={gridColumnTemplate}\n className=\"containers\"\n data-testid=\"containers-table-header\"\n >\n <DataGridRow>\n {hasAnyBulkAction && <DataGridHeadCell />}\n <DataGridHeadCell>\n <Trans>Container Name</Trans>\n </DataGridHeadCell>\n <DataGridHeadCell>\n <Trans>Object Count</Trans>\n </DataGridHeadCell>\n <DataGridHeadCell>\n <Trans>Last Modified</Trans>\n </DataGridHeadCell>\n <DataGridHeadCell>\n <Trans>Total Size</Trans>\n </DataGridHeadCell>\n <DataGridHeadCell style={{ marginRight: `-${scrollbarWidth}px` }} />\n </DataGridRow>\n\n {(!containers || containers.length === 0) && (\n <DataGridRow>\n <DataGridCell colSpan={columnCount}>\n <Status\n status=\"empty\"\n title={t`No containers found`}\n body={t`There are no containers available with the current search criteria. Try adjusting your search term.`}\n />\n </DataGridCell>\n </DataGridRow>\n )}\n </DataGrid>\n </div>\n\n {/* Virtualized Table Body — sized to the space actually left below the\n table, so banners above it shrink the table instead of growing the\n page. */}\n <div\n ref={tableBodyRef}\n className=\"overflow-auto\"\n style={{ height: `${bodyHeight ?? 0}px` }}\n data-testid=\"containers-table-body\"\n >\n <DataGrid\n columns={columnCount}\n gridColumnTemplate={gridColumnTemplate}\n style={{\n height: `${totalSize}px`,\n width: \"100%\",\n position: \"relative\",\n }}\n >\n {virtualItems.map((virtualRow) => {\n const container = containers[virtualRow.index]\n const isSelected = selectedContainers.includes(container.name)\n\n const handleRowNavigate = () =>\n navigate({\n to: \"/projects/$projectId/storage/$provider/$storageType/$containerName/objects\",\n params: { projectId, provider, storageType, containerName: container.name },\n })\n\n return (\n <DataGridRow\n key={container.name}\n data-index={virtualRow.index}\n ref={measureElement}\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n width: \"100%\",\n transform: `translateY(${virtualRow.start}px)`,\n display: \"grid\",\n gridTemplateColumns: gridColumnTemplate,\n alignItems: \"stretch\",\n }}\n data-testid={`container-row-${container.name}`}\n tabIndex={0}\n onClick={handleRowNavigate}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault()\n handleRowNavigate()\n }\n }}\n >\n {hasAnyBulkAction && (\n <DataGridCell\n onClick={(e) => e.stopPropagation()}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.stopPropagation()\n }\n }}\n >\n <Checkbox\n checked={isSelected}\n onChange={() => handleSelectContainer(container.name)}\n data-testid={`select-container-${container.name}`}\n />\n </DataGridCell>\n )}\n <DataGridCell className=\"min-w-0 overflow-hidden\">\n <span className=\"block truncate\" title={container.name}>\n {container.name}\n </span>\n </DataGridCell>\n <DataGridCell>{container.count.toLocaleString()}</DataGridCell>\n <DataGridCell>{formatSwiftDate(container.last_modified) ?? t`N/A`}</DataGridCell>\n <DataGridCell>{formatBytesBinary(container.bytes)}</DataGridCell>\n <DataGridCell onClick={(e) => e.stopPropagation()}>\n <PopupMenu>\n <PopupMenuToggle as=\"div\">\n <Button\n icon=\"moreVert\"\n title={t`Container actions`}\n aria-label={t`Container actions`}\n className=\"!bg-transparent\"\n />\n </PopupMenuToggle>\n <PopupMenuOptions>\n <PopupMenuItem\n label={t`Manage Access`}\n onClick={() => setAccessControlModalContainer(container)}\n data-testid={`access-control-action-${container.name}`}\n />\n <PopupMenuItem\n label={t`Preview and Edit metadata`}\n onClick={() => setPropertiesModalContainer(container)}\n data-testid={`properties-action-${container.name}`}\n />\n <PopupMenuItem\n label={t`Empty Container`}\n onClick={() => setEmptyModalContainer(container)}\n data-testid={`empty-action-${container.name}`}\n />\n <PopupMenuItem\n label={t`Delete Container`}\n onClick={() => setDeleteModalContainer(container)}\n data-testid={`delete-action-${container.name}`}\n />\n </PopupMenuOptions>\n </PopupMenu>\n </DataGridCell>\n </DataGridRow>\n )\n })}\n </DataGrid>\n </div>\n </div>\n\n <CreateContainerModal\n isOpen={createModalOpen}\n onClose={() => setCreateModalOpen(false)}\n onSuccess={onCreateSuccess}\n onPartialSuccess={onCreatePartialSuccess}\n maxContainerNameLength={maxContainerNameLength}\n existingContainers={existingContainers}\n />\n\n <EmptyContainerModal\n isOpen={emptyModalContainer !== null}\n container={emptyModalContainer}\n onClose={() => setEmptyModalContainer(null)}\n onSuccess={onEmptySuccess}\n onError={onEmptyError}\n />\n\n <DeleteContainerModal\n isOpen={deleteModalContainer !== null}\n container={deleteModalContainer}\n onClose={() => setDeleteModalContainer(null)}\n onSuccess={onDeleteSuccess}\n onError={onDeleteError}\n />\n\n <EditContainerMetadataModal\n isOpen={propertiesModalContainer !== null}\n container={propertiesModalContainer}\n onClose={() => setPropertiesModalContainer(null)}\n onSuccess={onPropertiesSuccess}\n onError={onPropertiesError}\n />\n\n <ManageContainerAccessModal\n isOpen={accessControlModalContainer !== null}\n container={accessControlModalContainer}\n onClose={() => setAccessControlModalContainer(null)}\n onSuccess={onAclSuccess}\n onError={onAclError}\n />\n </>\n )\n}\n","import { Trans, useLingui } from \"@lingui/react/macro\"\nimport { Tooltip, TooltipTrigger, TooltipContent, Icon } from \"@cloudoperators/juno-ui-components\"\nimport { ServiceInfo, AccountInfo } from \"@/server/Storage/types/swift\"\nimport { formatBytesBinary } from \"@/client/utils/formatBytes\"\n\ninterface ContainerLimitsTooltipProps {\n serviceInfo?: ServiceInfo\n accountInfo?: AccountInfo\n open?: boolean\n}\n\ninterface LimitItemProps {\n label: string\n value?: string | number\n}\n\nconst LimitItem = ({ label, value }: LimitItemProps) => {\n if (value === undefined || value === null) return null\n return (\n <span className=\"flex items-start gap-1.5\">\n <Icon icon=\"check\" size=\"16px\" className=\"text-theme-success mt-0.5 shrink-0\" />\n <span>\n <span className=\"font-medium\">{label}:</span> {value}\n </span>\n </span>\n )\n}\n\nconst CapabilityItem = ({ label }: { label: string }) => (\n <span className=\"flex items-start gap-1.5\">\n <Icon icon=\"check\" size=\"16px\" className=\"text-theme-success mt-0.5 shrink-0\" />\n <span>{label}</span>\n </span>\n)\n\nconst Section = ({ title, children }: { title: React.ReactNode; children: React.ReactNode }) => (\n <span className=\"flex flex-col gap-1.5\">\n <span className=\"text-theme-default block font-semibold\">{title}</span>\n {children}\n </span>\n)\n\nexport const ContainerLimitsTooltip = ({ serviceInfo, accountInfo, open }: ContainerLimitsTooltipProps) => {\n const { t } = useLingui()\n\n const swift = serviceInfo?.swift\n\n const capabilityMap = {\n account_quotas: t`Account quotas`,\n container_quotas: t`Container quotas`,\n bulk_delete: t`Efficient bulk deletion`,\n bulk_upload: t`Bulk upload of archive files`,\n slo: t`Static large object support`,\n container_sync: t`Container syncing`,\n tempurl: t`Temporary URLs`,\n symlink: t`Symlinks`,\n versioned_writes: t`Versioned writes`,\n } as const\n\n const capabilities = Object.entries(capabilityMap)\n .filter(([key]) => swift?.[key as keyof typeof swift])\n .map(([, label]) => label)\n\n return (\n <Tooltip triggerEvent=\"hover\" placement=\"bottom-end\" open={open}>\n <TooltipTrigger className=\"max-h-5\">\n <span>\n <Icon icon=\"info\" size=\"20px\" className=\"text-theme-light hover:text-theme-default cursor-pointer\" />\n </span>\n </TooltipTrigger>\n <TooltipContent className=\"z-10 w-72 text-sm\">\n <span className=\"flex flex-col gap-3 p-1\">\n {/* Account stats */}\n {accountInfo && (\n <Section title={<Trans>Account</Trans>}>\n <LimitItem label={t`Objects`} value={accountInfo.objectCount.toLocaleString()} />\n <LimitItem label={t`Used`} value={formatBytesBinary(accountInfo.bytesUsed)} />\n {accountInfo.quotaBytes !== undefined && (\n <LimitItem label={t`Quota`} value={formatBytesBinary(accountInfo.quotaBytes)} />\n )}\n </Section>\n )}\n\n {/* Limits */}\n {swift && (\n <Section title={<Trans>Limits</Trans>}>\n <LimitItem\n label={t`Max file size`}\n value={swift.max_file_size !== undefined ? formatBytesBinary(swift.max_file_size) : undefined}\n />\n <LimitItem label={t`Max container name length`} value={swift.max_container_name_length} />\n <LimitItem label={t`Max object name length`} value={swift.max_object_name_length} />\n <LimitItem label={t`Container listing limit`} value={swift.container_listing_limit?.toLocaleString()} />\n <LimitItem label={t`Account listing limit`} value={swift.account_listing_limit?.toLocaleString()} />\n <LimitItem label={t`Max header size`} value={swift.max_header_size} />\n <LimitItem label={t`Max meta count`} value={swift.max_meta_count} />\n <LimitItem label={t`Max meta name length`} value={swift.max_meta_name_length} />\n <LimitItem label={t`Max meta value length`} value={swift.max_meta_value_length} />\n <LimitItem label={t`Max meta overall size`} value={swift.max_meta_overall_size} />\n {swift.bulk_delete && (\n <LimitItem\n label={t`Max deletes per request`}\n value={swift.bulk_delete.max_deletes_per_request.toLocaleString()}\n />\n )}\n {swift.bulk_upload && (\n <LimitItem\n label={t`Max containers per extraction`}\n value={swift.bulk_upload.max_containers_per_extraction.toLocaleString()}\n />\n )}\n {swift.slo && (\n <LimitItem label={t`Max SLO segments`} value={swift.slo.max_manifest_segments.toLocaleString()} />\n )}\n </Section>\n )}\n\n {/* Capabilities */}\n {capabilities.length > 0 && (\n <Section title={<Trans>Capabilities</Trans>}>\n {capabilities.map((cap) => (\n <CapabilityItem key={cap} label={cap} />\n ))}\n </Section>\n )}\n </span>\n </TooltipContent>\n </Tooltip>\n )\n}\n","import { z } from \"zod\"\nimport { useForm, useStore } from \"@tanstack/react-form\"\nimport { useState } from \"react\"\nimport { Plural, Trans, useLingui } from \"@lingui/react/macro\"\nimport { plural } from \"@lingui/core/macro\"\nimport { trpcReact } from \"@/client/trpcClient\"\nimport { Modal, Status, Stack, Form, FormSection, TextInput } from \"@cloudoperators/juno-ui-components\"\nimport { ContainerSummary } from \"@/server/Storage/types/swift\"\nimport { useProjectId } from \"@/client/hooks/useProjectId\"\nimport { useModalTracking } from \"@/client/hooks/useModalTracking\"\n\n// Max number of container names shown in the list before truncating\nconst MAX_VISIBLE = 20\n\n// The literal word the user must type to confirm this destructive action.\n// The gating logic (isConfirmed) compares against this constant, NOT the\n// translated label — so a mistranslated label can never weaken or break the\n// confirmation gate. The English label below should name the same word for clarity.\nconst CONFIRM_WORD = \"empty\"\n\ninterface EmptyContainersResult {\n emptiedCount: number\n totalDeleted: number\n errors: string[]\n}\n\ninterface EmptyContainersModalProps {\n isOpen: boolean\n containers: ContainerSummary[]\n onClose: () => void\n onComplete?: (result: EmptyContainersResult) => void\n}\n\nexport const EmptyContainersModal = ({ isOpen, containers, onClose, onComplete }: EmptyContainersModalProps) => {\n const { t, i18n } = useLingui()\n const projectId = useProjectId()\n\n const [progress, setProgress] = useState<{ current: number; total: number } | null>(null)\n\n const { trackClose, markSubmitted, resetTracking } = useModalTracking({\n isOpen,\n actionPrefix: \"storage.swift.containers.empty\",\n })\n\n const formSchema = z.object({\n confirm: z\n .string()\n .transform((val) => val.trim())\n .refine((value) => value === CONFIRM_WORD, {\n message: t`Type \"${CONFIRM_WORD}\" to confirm`,\n }),\n })\n\n const form = useForm({\n defaultValues: {\n confirm: \"\",\n },\n validators: {\n onSubmit: formSchema,\n },\n onSubmit: async () => {\n if (emptyContainerMutation.isPending || progress !== null) return\n\n markSubmitted()\n let emptiedCount = 0\n let totalDeleted = 0\n const errors: string[] = []\n const total = containers.length\n\n for (let i = 0; i < containers.length; i++) {\n setProgress({ current: i + 1, total })\n const container = containers[i]\n\n try {\n const deleted = await emptyContainerMutation.mutateAsync({ project_id: projectId, container: container.name })\n totalDeleted += deleted\n emptiedCount++\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n errors.push(`${container.name}: ${message}`)\n }\n }\n\n if (emptiedCount > 0) {\n await utils.storage.swift.listContainers.invalidate()\n }\n\n onComplete?.({ emptiedCount, totalDeleted, errors })\n\n handleClose()\n },\n })\n\n const canEmpty = useStore(form.store, (state) => state.isSubmitting || state.values.confirm.trim() !== CONFIRM_WORD)\n\n const utils = trpcReact.useUtils()\n\n const emptyContainerMutation = trpcReact.storage.swift.emptyContainer.useMutation()\n\n const handleClose = () => {\n trackClose()\n emptyContainerMutation.reset()\n setProgress(null)\n form.reset()\n resetTracking()\n onClose()\n }\n\n if (!isOpen || containers.length === 0) return null\n\n const totalCount = containers.length\n const visibleContainers = containers.slice(0, MAX_VISIBLE)\n const hiddenCount = totalCount - visibleContainers.length\n const isPending = emptyContainerMutation.isPending || progress !== null\n const progressCurrent = progress?.current\n const progressTotal = progress?.total\n\n return (\n <Modal\n title={<Plural value={totalCount} one=\"Empty Container\" other=\"Empty # Containers\" />}\n open={isOpen}\n onCancel={() => {\n trackClose()\n handleClose()\n }}\n confirmButtonLabel={isPending ? t`Emptying...` : t`Empty`}\n confirmButtonVariant=\"primary-danger\"\n cancelButtonLabel={t`Cancel`}\n onConfirm={form.handleSubmit}\n disableConfirmButton={isPending || canEmpty}\n disableCancelButton={isPending}\n disableCloseButton={isPending}\n size=\"small\"\n >\n {isPending ? (\n <Status\n status=\"progress\"\n title={\n progress ? t`Emptying Container ${progressCurrent} of ${progressTotal}, Please Wait...` : t`Emptying...`\n }\n className=\"mt-0\"\n />\n ) : (\n <div>\n <p>\n <Trans>All objects in the selected containers will be permanently deleted. This cannot be undone.</Trans>\n </p>\n <p>\n <Trans>\n For <strong>dynamic</strong> and <strong>static large objects</strong> only the manifests are deleted —\n the related segments are not deleted.\n </Trans>\n </p>\n <div className=\"my-6\">\n <p className=\"text-sm font-semibold\">\n <Trans>Containers to be emptied:</Trans>\n </p>\n <div className=\"bg-theme-background-lvl-2 mt-2 max-h-48 overflow-y-auto rounded p-3\">\n <Stack direction=\"vertical\" gap=\"1\">\n {visibleContainers.map((container) => {\n const count = container.count\n return (\n <div key={container.name} className=\"text-theme-default overflow-x-hidden text-sm wrap-anywhere\">\n {container.name}\n {count != null && (\n <span className=\"text-theme-light ml-2\">\n ({i18n._(plural(count, { one: \"# object\", other: \"# objects\" }))})\n </span>\n )}\n </div>\n )\n })}\n {hiddenCount > 0 && (\n <div className=\"text-theme-light pt-2 text-sm\">\n <Trans>… and {hiddenCount} more</Trans>\n </div>\n )}\n </Stack>\n </div>\n </div>\n\n <Form\n className=\"mb-0\"\n id=\"empty-containers-form\"\n onSubmit={(e) => {\n e.preventDefault()\n form.handleSubmit()\n }}\n >\n <FormSection>\n <form.Field\n name=\"confirm\"\n children={(field) => (\n <TextInput\n label={t`Type \"empty\" to confirm`}\n placeholder={CONFIRM_WORD}\n id={field.name}\n name={field.name}\n value={field.state.value}\n onChange={(e) => field.handleChange(e.target.value)}\n disabled={isPending}\n autoFocus\n required\n />\n )}\n />\n </FormSection>\n </Form>\n </div>\n )}\n </Modal>\n )\n}\n","import { useState, useEffect, useRef, startTransition } from \"react\"\nimport { Plural, Trans, useLingui } from \"@lingui/react/macro\"\nimport { plural } from \"@lingui/core/macro\"\nimport { SortInput } from \"@/client/components/ListToolbar/SortInput\"\nimport { SortSettings } from \"@/client/components/ListToolbar/types\"\nimport { ContainerSummary } from \"@/server/Storage/types/swift\"\nimport { trpcReact } from \"@/client/trpcClient\"\nimport {\n Button,\n Checkbox,\n DataGridToolbar,\n PopupMenu,\n PopupMenuItem,\n PopupMenuOptions,\n PopupMenuToggle,\n SearchInput,\n Status,\n Stack,\n toast,\n} from \"@cloudoperators/juno-ui-components\"\nimport { formatBytesBinary } from \"@/client/utils/formatBytes\"\nimport { parseSwiftDate } from \"@/client/utils/formatSwiftDate\"\nimport { ContainerTableView } from \"./ContainerTableView\"\nimport {\n getContainerCreatedToast,\n getContainerCreatedWithWarningToast,\n getContainerEmptiedToast,\n getContainerEmptyErrorToast,\n getContainerDeletedToast,\n getContainerDeleteErrorToast,\n getContainerUpdatedToast,\n getContainerUpdateErrorToast,\n getContainerAclUpdatedToast,\n getContainerAclUpdateErrorToast,\n getContainersEmptyCompleteToast,\n} from \"./ContainerToastNotifications\"\nimport { ContainerLimitsTooltip } from \"./ContainerLimitsTooltip\"\nimport { EmptyContainersModal } from \"./EmptyContainersModal\"\nimport { useNavigate } from \"@tanstack/react-router\"\nimport { Route } from \"../../../$provider/$storageType/\"\nimport { useProjectId } from \"@/client/hooks/useProjectId\"\n\nexport const SwiftContainers = () => {\n const { t, i18n } = useLingui()\n const projectId = useProjectId()\n const navigate = useNavigate({ from: Route.fullPath })\n\n // Sort and search state are persisted in the URL so they survive navigation,\n // browser back/forward, and deep links.\n const { sortBy, sortDirection, search: searchParam = \"\" } = Route.useSearch()\n\n // Whether the list exposes any bulk action — drives the selection column in\n // ContainerTableView and the Zone 3 bulk-action controls. Hardcoded to true\n // for now; the only bulk action today is the destructive Empty.\n //\n // TODO(perms): wire this to the real Swift container permission source\n // (e.g. a usePermissions hook or a tRPC permissions query) instead of\n // hardcoding it.\n const hasAnyBulkAction = true\n\n const [createModalOpen, setCreateModalOpen] = useState(false)\n const [emptyAllModalOpen, setEmptyAllModalOpen] = useState(false)\n const [selectedContainers, setSelectedContainers] = useState<string[]>([])\n\n // Local mirror of the committed search term so typing stays responsive while\n // the URL commit is debounced (see Zone 2 SearchInput below).\n const [localSearchTerm, setLocalSearchTerm] = useState(searchParam)\n const debounceTimer = useRef<number | undefined>(undefined)\n useEffect(() => () => clearTimeout(debounceTimer.current), [])\n\n // Keep the input in sync when the committed search term changes from outside\n // the input — browser back/forward, deep links, or programmatic navigation —\n // so the field never drifts from the URL-backed filter state. When the change\n // originated from our own debounced commit, searchParam already equals\n // localSearchTerm, so this is a no-op and won't disturb the caret.\n useEffect(() => {\n setLocalSearchTerm(searchParam)\n }, [searchParam])\n\n const handleCreateSuccess = (containerName: string) => {\n const { message, ...options } = getContainerCreatedToast(containerName)\n toast.success(message, options)\n }\n\n const handleCreatePartialSuccess = (containerName: string, reason: string) => {\n const { message, ...options } = getContainerCreatedWithWarningToast(containerName, reason)\n toast.warning(message, options)\n }\n\n const handleEmptySuccess = (containerName: string, deletedCount: number) => {\n const { message, ...options } = getContainerEmptiedToast(containerName, deletedCount)\n toast.success(message, options)\n }\n\n const handleEmptyError = (containerName: string, errorMessage: string) => {\n const { message, ...options } = getContainerEmptyErrorToast(containerName, errorMessage)\n toast.error(message, options)\n }\n\n const handleDeleteSuccess = (containerName: string) => {\n const { message, ...options } = getContainerDeletedToast(containerName)\n toast.success(message, options)\n }\n\n const handleDeleteError = (containerName: string, errorMessage: string) => {\n const { message, ...options } = getContainerDeleteErrorToast(containerName, errorMessage)\n toast.error(message, options)\n }\n\n const handlePropertiesSuccess = (containerName: string) => {\n const { message, ...options } = getContainerUpdatedToast(containerName)\n toast.success(message, options)\n }\n\n const handlePropertiesError = (containerName: string, errorMessage: string) => {\n const { message, ...options } = getContainerUpdateErrorToast(containerName, errorMessage)\n toast.error(message, options)\n }\n\n const handleAclSuccess = (containerName: string) => {\n const { message, ...options } = getContainerAclUpdatedToast(containerName)\n toast.success(message, options)\n }\n\n const handleAclError = (containerName: string, errorMessage: string) => {\n const { message, ...options } = getContainerAclUpdateErrorToast(containerName, errorMessage)\n toast.error(message, options)\n }\n\n const handleEmptyAllComplete = ({\n emptiedCount,\n totalDeleted,\n errors,\n }: {\n emptiedCount: number\n totalDeleted: number\n errors: string[]\n }) => {\n if (errors.length === 0) {\n setSelectedContainers([])\n } else {\n // Extract the container name from each error string formatted as \"<containerName>: <message>\".\n // Using exact name extraction avoids the false-positive substring match that\n // errorMessage.includes(containerName) would produce (e.g. \"foo\" matched inside \"foobar\" error).\n const failedContainerNames = new Set(errors.map((e) => e.split(\": \")[0]))\n setSelectedContainers((prev) => prev.filter((name) => failedContainerNames.has(name)))\n }\n\n const { message, severity, ...options } = getContainersEmptyCompleteToast(emptiedCount, totalDeleted, errors)\n toast[severity](message, options)\n }\n\n const sortSettings: SortSettings = {\n options: [\n { label: t`Container Name`, value: \"name\" },\n { label: t`Object Count`, value: \"count\" },\n { label: t`Total Size`, value: \"bytes\" },\n { label: t`Last Modified`, value: \"last_modified\" },\n ],\n sortBy: sortBy ?? \"name\",\n sortDirection: sortDirection ?? \"asc\",\n }\n\n // Fetch containers from tRPC\n const {\n data: containers,\n isLoading,\n error,\n } = trpcReact.storage.swift.listContainers.useQuery({\n project_id: projectId,\n format: \"json\",\n })\n\n // Fetch account metadata for quota information\n const { data: accountInfo } = trpcReact.storage.swift.getAccountMetadata.useQuery({\n project_id: projectId,\n })\n\n // Fetch Swift service info\n const { data: serviceInfo } = trpcReact.storage.swift.getServiceInfo.useQuery()\n\n // Sort containers based on sort settings\n const sortContainers = (containers: ContainerSummary[]): ContainerSummary[] => {\n return [...containers].sort((a, b) => {\n let comparison: number\n\n switch (sortBy ?? \"name\") {\n case \"name\":\n comparison = a.name.localeCompare(b.name)\n break\n case \"count\":\n comparison = a.count - b.count\n break\n case \"bytes\":\n comparison = a.bytes - b.bytes\n break\n case \"last_modified\": {\n // Both missing → equal (keeps the comparator transitive); exactly one\n // missing → push it to the end.\n if (!a.last_modified && !b.last_modified) return 0\n if (!a.last_modified || !b.last_modified) {\n return a.last_modified ? -1 : 1\n }\n // #1236: Swift listing timestamps are UTC without a \"Z\" — parse them as\n // UTC so the ordering is correct (matters near DST boundaries). If a\n // (non-empty) value can't be parsed, treat the pair as equal so the\n // order is left untouched (matches the old NaN behaviour).\n const at = parseSwiftDate(a.last_modified)?.getTime()\n const bt = parseSwiftDate(b.last_modified)?.getTime()\n comparison = at == null || bt == null ? 0 : at - bt\n break\n }\n default:\n comparison = a.name.localeCompare(b.name)\n }\n\n return (sortDirection ?? \"asc\") === \"desc\" ? -comparison : comparison\n })\n }\n\n // Filter containers based on search term\n const filteredContainers = (containers || []).filter((container) =>\n container.name.toLowerCase().includes(searchParam.toLowerCase())\n )\n\n // Apply sorting to filtered containers\n const sortedContainers = sortContainers(filteredContainers)\n\n const handleSearchChange = (term: string | number | string[] | undefined) => {\n const value = typeof term === \"string\" ? term : \"\"\n startTransition(() => {\n navigate({\n search: (prev) => ({ ...prev, search: value || undefined }),\n })\n })\n }\n\n const handleSortChange = (newSortSettings: SortSettings) => {\n const resolvedSortBy = (newSortSettings.sortBy?.toString() || \"name\") as\n \"name\" | \"count\" | \"bytes\" | \"last_modified\"\n const resolvedDirection = (newSortSettings.sortDirection || \"asc\") as \"asc\" | \"desc\"\n startTransition(() => {\n navigate({\n search: (prev) => ({\n ...prev,\n sortBy: resolvedSortBy,\n sortDirection: resolvedDirection,\n }),\n })\n })\n }\n\n // Handle loading state\n if (isLoading) {\n return <Status status=\"progress\" title={t`Loading Containers...`} />\n }\n\n // Handle error state\n if (error) {\n return <Status status=\"error\" title={t`Error Loading Containers`} body={error.message} />\n }\n\n // Calculate quota information\n const bytesUsed = accountInfo?.bytesUsed || 0\n const quotaBytes = accountInfo?.quotaBytes || 0\n const remainingBytes = quotaBytes > 0 ? quotaBytes - bytesUsed : 0\n\n // Resolve selected ContainerSummary objects from the full unfiltered list so\n // the modal always operates on what was actually selected — not the filtered\n // subset currently visible in the table.\n const selectedContainerSummaries = (containers || []).filter((c) => selectedContainers.includes(c.name))\n const hasSelection = selectedContainerSummaries.length > 0\n const selectedCount = selectedContainerSummaries.length\n\n const totalCount = (containers || []).length\n const filteredCount = filteredContainers.length\n const isFiltered = filteredCount !== totalCount\n\n // Select-all operates on the currently displayed (filtered + sorted) rows.\n const displayedNames = sortedContainers.map((c) => c.name)\n const allSelected = displayedNames.length > 0 && displayedNames.every((n) => selectedContainers.includes(n))\n const someSelected = displayedNames.some((n) => selectedContainers.includes(n))\n\n const handleToggleSelectAll = () => {\n if (allSelected) {\n setSelectedContainers((prev) => prev.filter((n) => !displayedNames.includes(n)))\n } else {\n setSelectedContainers((prev) => [...new Set([...prev, ...displayedNames])])\n }\n }\n\n return (\n <div className=\"relative\">\n <Stack direction=\"vertical\">\n {/* Zone 1 — sort controls and the create action (plain Stack, no background) */}\n <Stack distribution=\"end\" alignment=\"center\" gap=\"2\" className=\"pb-2\">\n <Stack gap=\"0.5\" alignment=\"center\">\n <SortInput\n options={sortSettings.options}\n sortBy={sortSettings.sortBy}\n sortDirection={sortSettings.sortDirection ?? \"asc\"}\n selectClassName=\"min-w-40\"\n onSortByChange={(v) =>\n handleSortChange({ ...sortSettings, sortBy: v, sortDirection: sortSettings.sortDirection })\n }\n onSortDirectionChange={(dir) => handleSortChange({ ...sortSettings, sortDirection: dir })}\n />\n </Stack>\n <Button variant=\"primary\" className=\"whitespace-nowrap\" onClick={() => setCreateModalOpen(true)}>\n <Trans>Create Container</Trans>\n </Button>\n </Stack>\n\n {/* Zone 2 — search (+ filter pills). DataGridToolbar provides the background.\n Swift containers expose no filterable dimensions yet, so there is no\n FiltersInput / SelectedFilters here. When filter dimensions are added,\n add FiltersInput alongside the search (switch the inner Stack to\n distribution=\"between\") and render SelectedFilters below, using\n applyFilterSelection from urlHelpers for merge logic. */}\n <DataGridToolbar>\n <Stack direction=\"vertical\" gap=\"2\">\n <Stack distribution=\"end\" alignment=\"center\">\n <SearchInput\n placeholder={t`Search containers...`}\n data-testid=\"searchbar\"\n value={localSearchTerm}\n onInput={(e: React.FormEvent<HTMLInputElement>) => {\n const v = e.currentTarget.value\n setLocalSearchTerm(v)\n clearTimeout(debounceTimer.current)\n debounceTimer.current = window.setTimeout(() => handleSearchChange(v), 500)\n }}\n onSearch={(v) => {\n clearTimeout(debounceTimer.current)\n handleSearchChange(typeof v === \"string\" ? v : \"\")\n }}\n onClear={() => {\n clearTimeout(debounceTimer.current)\n setLocalSearchTerm(\"\")\n handleSearchChange(\"\")\n }}\n />\n </Stack>\n </Stack>\n </DataGridToolbar>\n\n {/* Zone 3 — bulk actions (gated) plus the container/quota info.\n Unlike the Images reference (where Zone 3 carries only bulk actions and is\n omitted entirely without permissions), this bar also hosts the Swift-specific\n count + remaining-quota + limits info, which must always be visible. So the\n bar always renders; only the bulk controls are gated. Bulk controls\n sit on the left; the count + quota are pushed right via ml-auto so they\n stay right-aligned regardless of whether the bulk block renders. */}\n <DataGridToolbar>\n <Stack distribution=\"start\" gap=\"2\" alignment=\"center\" className=\"text-sm\">\n {hasAnyBulkAction ? (\n <Stack gap=\"2\" alignment=\"center\">\n <Checkbox\n checked={allSelected}\n indeterminate={someSelected && !allSelected}\n onChange={handleToggleSelectAll}\n />\n <PopupMenu className=\"flex items-center\">\n <PopupMenuToggle as=\"div\">\n <Button disabled={!hasSelection} size=\"small\" icon=\"moreVert\" label={t`Actions`} />\n </PopupMenuToggle>\n {hasSelection && (\n <PopupMenuOptions>\n <PopupMenuItem\n disabled={!hasSelection}\n label={i18n._(\n plural(selectedCount, {\n one: \"Empty Container\",\n other: \"Empty # Containers\",\n })\n )}\n onClick={() => setEmptyAllModalOpen(true)}\n />\n </PopupMenuOptions>\n )}\n </PopupMenu>\n </Stack>\n ) : (\n <span />\n )}\n\n <div className=\"text-theme-light ml-auto flex items-center gap-1\" data-testid=\"containers-info-block\">\n {isFiltered ? (\n <Plural\n value={filteredCount}\n one={`${filteredCount} of ${totalCount} container`}\n other={`${filteredCount} of ${totalCount} containers`}\n />\n ) : (\n <Plural value={totalCount} one={`${totalCount} container`} other={`${totalCount} containers`} />\n )}\n {quotaBytes > 0 && (\n <>\n <span>,</span>\n <span>\n <Trans>Remaining Quota:</Trans>{\" \"}\n <span className=\"text-theme-default font-semibold\">{formatBytesBinary(remainingBytes)}</span>\n </span>\n </>\n )}\n <ContainerLimitsTooltip serviceInfo={serviceInfo} accountInfo={accountInfo} />\n </div>\n </Stack>\n </DataGridToolbar>\n </Stack>\n\n <ContainerTableView\n containers={sortedContainers}\n createModalOpen={createModalOpen}\n setCreateModalOpen={setCreateModalOpen}\n maxContainerNameLength={serviceInfo?.swift?.max_container_name_length}\n existingContainers={containers}\n onCreateSuccess={handleCreateSuccess}\n onCreatePartialSuccess={handleCreatePartialSuccess}\n onEmptySuccess={handleEmptySuccess}\n onEmptyError={handleEmptyError}\n onDeleteSuccess={handleDeleteSuccess}\n onDeleteError={handleDeleteError}\n onPropertiesSuccess={handlePropertiesSuccess}\n onPropertiesError={handlePropertiesError}\n onAclSuccess={handleAclSuccess}\n onAclError={handleAclError}\n selectedContainers={selectedContainers}\n setSelectedContainers={setSelectedContainers}\n hasAnyBulkAction={hasAnyBulkAction}\n />\n\n <EmptyContainersModal\n isOpen={emptyAllModalOpen}\n containers={selectedContainerSummaries}\n onClose={() => setEmptyAllModalOpen(false)}\n onComplete={handleEmptyAllComplete}\n />\n </div>\n )\n}\n","import { createFileRoute, redirect, useParams } from \"@tanstack/react-router\"\nimport { z } from \"zod\"\nimport { getServiceIndex } from \"@/server/Authentication/helpers\"\nimport { ErrorBoundary } from \"react-error-boundary\"\nimport { SwiftContainers } from \"../../-components/Swift/Containers\"\nimport { CephBuckets } from \"../../-components/Ceph/Buckets\"\nimport { Trans, useLingui } from \"@lingui/react/macro\"\nimport type { RouteInfo } from \"@/client/routes/routeInfo\"\nimport { ContentHeader } from \"@/client/components/ContentHeader/ContentHeader\"\n\n/**\n * Validates that the requested storage provider is available for the given project,\n * and redirects to an appropriate fallback route when it is not.\n *\n * Redirect rules (in priority order):\n * 1. No `object-store` service at all → redirect to the project overview.\n * 2. Unknown provider (neither \"swift\" nor \"ceph\") → redirect to the first\n * available provider, or to the project overview if none exist.\n * 3. Requested provider unavailable → redirect to the other provider,\n * or to the project overview if no alternative exists.\n *\n * Ceph has a temporary fallback flag (`cephFallbackEnabled`) that treats it as\n * available even when absent from the OpenStack service catalog.\n *\n * @throws {redirect} - Always throws a TanStack Router redirect; never returns normally\n * when the requested provider/project combination is unavailable.\n */\nexport const checkServiceAvailability = (\n availableServices: {\n type: string\n name: string\n }[],\n params: {\n projectId: string\n provider: string\n }\n) => {\n const { provider, projectId } = params\n\n const serviceIndex = getServiceIndex(availableServices)\n\n // Redirect to the \"Projects Overview\" page if no storage services available\n if (!serviceIndex[\"object-store\"]) {\n throw redirect({\n to: \"/projects/$projectId\",\n params: { projectId },\n })\n }\n\n // Check provider availability\n const hasSwift = Boolean(serviceIndex[\"object-store\"][\"swift\"])\n const hasCeph = Boolean(serviceIndex[\"object-store\"][\"ceph\"])\n\n // TEMPORARY: Allow Ceph access even if not in catalog (relies on env config)\n // TODO: Properly register Ceph in OpenStack service catalog\n const cephFallbackEnabled = true // Set to false once Ceph is in catalog\n\n // Effective availability includes fallback flag for Ceph\n const hasEffectiveCeph = hasCeph || cephFallbackEnabled\n const fallbackProvider = hasSwift ? \"swift\" : hasEffectiveCeph ? \"ceph\" : null\n const fallbackStorageType = hasSwift ? \"containers\" : hasEffectiveCeph ? \"buckets\" : null\n\n if (provider !== \"swift\" && provider !== \"ceph\") {\n if (!fallbackProvider || !fallbackStorageType) {\n throw redirect({\n to: \"/projects/$projectId\",\n params: { projectId },\n })\n }\n throw redirect({\n to: \"/projects/$projectId/storage/$provider/$storageType\",\n params: { ...params, provider: fallbackProvider, storageType: fallbackStorageType },\n })\n }\n\n if (provider === \"swift\" && !hasSwift) {\n if (!hasEffectiveCeph) {\n throw redirect({\n to: \"/projects/$projectId\",\n params: { projectId },\n })\n }\n\n throw redirect({\n to: \"/projects/$projectId/storage/$provider/$storageType\",\n params: { ...params, provider: \"ceph\", storageType: \"buckets\" },\n })\n }\n\n if (provider === \"ceph\" && !hasEffectiveCeph) {\n if (!hasSwift) {\n throw redirect({\n to: \"/projects/$projectId\",\n params: { projectId },\n })\n }\n\n throw redirect({\n to: \"/projects/$projectId/storage/$provider/$storageType\",\n params: { ...params, provider: \"swift\", storageType: \"containers\" },\n })\n }\n}\n\n// Search params schema\n// - sortBy: active sort column — persisted for deep links and back navigation\n// - sortDirection: \"asc\" | \"desc\" — persisted alongside sortBy\n// - search: active filter string — persisted so deep links preserve the current search\nconst containersSearchSchema = z.object({\n sortBy: z.enum([\"name\", \"count\", \"bytes\", \"last_modified\"]).optional(),\n sortDirection: z.enum([\"asc\", \"desc\"]).optional(),\n search: z.string().optional(),\n})\n\nexport const Route = createFileRoute(\"/_auth/projects/$projectId/storage/$provider/$storageType/\")({\n staticData: {\n section: \"storage\",\n service: \"containers\",\n analytics: {\n name: \"storage.objectstore.list\",\n },\n } satisfies RouteInfo,\n validateSearch: containersSearchSchema,\n head: ({ match }) => ({\n meta: [\n {\n title:\n match.params.provider === \"swift\"\n ? \"Object Storage (Swift)\"\n : match.params.provider === \"ceph\"\n ? \"Object Storage (Ceph)\"\n : \"Storage Overview\",\n },\n ],\n }),\n component: () => {\n return <StorageDashboard />\n },\n notFoundComponent: () => {\n return <p>Storage service not found</p>\n },\n loader: async ({ context }) => {\n const { trpcClient } = context\n const availableServices = await trpcClient?.auth.getAvailableServices.query()\n\n return {\n client: trpcClient,\n availableServices,\n }\n },\n beforeLoad: async ({ context, params }) => {\n const { trpcClient } = context\n const availableServices = await trpcClient?.auth.getAvailableServices.query()\n checkServiceAvailability(availableServices!, params)\n },\n})\n\nfunction StorageDashboard() {\n const { project, provider } = useParams({\n from: \"/_auth/projects/$projectId/storage/$provider/$storageType/\",\n select: (params) => {\n return { project: params.projectId, provider: params.provider, storageType: params.storageType }\n },\n })\n\n const { t } = useLingui()\n\n let pageTitle: string\n switch (provider) {\n case \"swift\":\n pageTitle = t`Object Storage (Swift)`\n break\n case \"ceph\":\n pageTitle = t`Object Storage (Ceph)`\n break\n default:\n pageTitle = t`Storage Overview`\n }\n\n return (\n <div>\n <ContentHeader title={pageTitle} projectId={project} />\n {project ? (\n <ErrorBoundary\n resetKeys={[project, provider]}\n fallback={\n <div className=\"p-4 text-center\">\n <Trans>Error loading component</Trans>\n </div>\n }\n >\n {(() => {\n switch (provider) {\n case \"swift\":\n return <SwiftContainers />\n case \"ceph\":\n return <CephBuckets />\n default:\n return <div>Storage Overview Page</div> // replace when available\n }\n })()}\n </ErrorBoundary>\n ) : (\n <div className=\"p-4 text-center\">\n <Trans>No project selected</Trans>\n </div>\n )}\n </div>\n )\n}\n"],"mappings":"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"}
@@ -1,7 +0,0 @@
1
- import { jsx as e } from "react/jsx-runtime";
2
- //#region src/client/routes/_auth/projects/$projectId/storage/$provider/$storageType/index.tsx?tsr-split=notFoundComponent
3
- var t = () => /*#__PURE__*/ e("p", { children: "Storage service not found" });
4
- //#endregion
5
- export { t as notFoundComponent };
6
-
7
- //# sourceMappingURL=_storageType-zeSZe--V.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"_storageType-zeSZe--V.mjs","names":["SplitNotFoundComponent","notFoundComponent"],"sources":["../../src/client/routes/_auth/projects/$projectId/storage/$provider/$storageType/index.tsx?tsr-split=notFoundComponent"],"sourcesContent":["import { createFileRoute, redirect, useParams } from \"@tanstack/react-router\"\nimport { z } from \"zod\"\nimport { getServiceIndex } from \"@/server/Authentication/helpers\"\nimport { ErrorBoundary } from \"react-error-boundary\"\nimport { SwiftContainers } from \"../../-components/Swift/Containers\"\nimport { CephBuckets } from \"../../-components/Ceph/Buckets\"\nimport { Trans, useLingui } from \"@lingui/react/macro\"\nimport type { RouteInfo } from \"@/client/routes/routeInfo\"\nimport { ContentHeader } from \"@/client/components/ContentHeader/ContentHeader\"\n\n/**\n * Validates that the requested storage provider is available for the given project,\n * and redirects to an appropriate fallback route when it is not.\n *\n * Redirect rules (in priority order):\n * 1. No `object-store` service at all → redirect to the project overview.\n * 2. Unknown provider (neither \"swift\" nor \"ceph\") → redirect to the first\n * available provider, or to the project overview if none exist.\n * 3. Requested provider unavailable → redirect to the other provider,\n * or to the project overview if no alternative exists.\n *\n * Ceph has a temporary fallback flag (`cephFallbackEnabled`) that treats it as\n * available even when absent from the OpenStack service catalog.\n *\n * @throws {redirect} - Always throws a TanStack Router redirect; never returns normally\n * when the requested provider/project combination is unavailable.\n */\nexport const checkServiceAvailability = (\n availableServices: {\n type: string\n name: string\n }[],\n params: {\n projectId: string\n provider: string\n }\n) => {\n const { provider, projectId } = params\n\n const serviceIndex = getServiceIndex(availableServices)\n\n // Redirect to the \"Projects Overview\" page if no storage services available\n if (!serviceIndex[\"object-store\"]) {\n throw redirect({\n to: \"/projects/$projectId\",\n params: { projectId },\n })\n }\n\n // Check provider availability\n const hasSwift = Boolean(serviceIndex[\"object-store\"][\"swift\"])\n const hasCeph = Boolean(serviceIndex[\"object-store\"][\"ceph\"])\n\n // TEMPORARY: Allow Ceph access even if not in catalog (relies on env config)\n // TODO: Properly register Ceph in OpenStack service catalog\n const cephFallbackEnabled = true // Set to false once Ceph is in catalog\n\n // Effective availability includes fallback flag for Ceph\n const hasEffectiveCeph = hasCeph || cephFallbackEnabled\n const fallbackProvider = hasSwift ? \"swift\" : hasEffectiveCeph ? \"ceph\" : null\n const fallbackStorageType = hasSwift ? \"containers\" : hasEffectiveCeph ? \"buckets\" : null\n\n if (provider !== \"swift\" && provider !== \"ceph\") {\n if (!fallbackProvider || !fallbackStorageType) {\n throw redirect({\n to: \"/projects/$projectId\",\n params: { projectId },\n })\n }\n throw redirect({\n to: \"/projects/$projectId/storage/$provider/$storageType\",\n params: { ...params, provider: fallbackProvider, storageType: fallbackStorageType },\n })\n }\n\n if (provider === \"swift\" && !hasSwift) {\n if (!hasEffectiveCeph) {\n throw redirect({\n to: \"/projects/$projectId\",\n params: { projectId },\n })\n }\n\n throw redirect({\n to: \"/projects/$projectId/storage/$provider/$storageType\",\n params: { ...params, provider: \"ceph\", storageType: \"buckets\" },\n })\n }\n\n if (provider === \"ceph\" && !hasEffectiveCeph) {\n if (!hasSwift) {\n throw redirect({\n to: \"/projects/$projectId\",\n params: { projectId },\n })\n }\n\n throw redirect({\n to: \"/projects/$projectId/storage/$provider/$storageType\",\n params: { ...params, provider: \"swift\", storageType: \"containers\" },\n })\n }\n}\n\n// Search params schema\n// - sortBy: active sort column — persisted for deep links and back navigation\n// - sortDirection: \"asc\" | \"desc\" — persisted alongside sortBy\n// - search: active filter string — persisted so deep links preserve the current search\nconst containersSearchSchema = z.object({\n sortBy: z.enum([\"name\", \"count\", \"bytes\", \"last_modified\"]).optional(),\n sortDirection: z.enum([\"asc\", \"desc\"]).optional(),\n search: z.string().optional(),\n})\n\nexport const Route = createFileRoute(\"/_auth/projects/$projectId/storage/$provider/$storageType/\")({\n staticData: {\n section: \"storage\",\n service: \"containers\",\n analytics: {\n name: \"storage.objectstore.list\",\n },\n } satisfies RouteInfo,\n validateSearch: containersSearchSchema,\n head: ({ match }) => ({\n meta: [\n {\n title:\n match.params.provider === \"swift\"\n ? \"Object Storage (Swift)\"\n : match.params.provider === \"ceph\"\n ? \"Object Storage (Ceph)\"\n : \"Storage Overview\",\n },\n ],\n }),\n component: () => {\n return <StorageDashboard />\n },\n notFoundComponent: () => {\n return <p>Storage service not found</p>\n },\n loader: async ({ context }) => {\n const { trpcClient } = context\n const availableServices = await trpcClient?.auth.getAvailableServices.query()\n\n return {\n client: trpcClient,\n availableServices,\n }\n },\n beforeLoad: async ({ context, params }) => {\n const { trpcClient } = context\n const availableServices = await trpcClient?.auth.getAvailableServices.query()\n checkServiceAvailability(availableServices!, params)\n },\n})\n\nfunction StorageDashboard() {\n const { project, provider } = useParams({\n from: \"/_auth/projects/$projectId/storage/$provider/$storageType/\",\n select: (params) => {\n return { project: params.projectId, provider: params.provider, storageType: params.storageType }\n },\n })\n\n const { t } = useLingui()\n\n let pageTitle: string\n switch (provider) {\n case \"swift\":\n pageTitle = t`Object Storage (Swift)`\n break\n case \"ceph\":\n pageTitle = t`Object Storage (Ceph)`\n break\n default:\n pageTitle = t`Storage Overview`\n }\n\n return (\n <div>\n <ContentHeader title={pageTitle} projectId={project} />\n {project ? (\n <ErrorBoundary\n resetKeys={[project, provider]}\n fallback={\n <div className=\"p-4 text-center\">\n <Trans>Error loading component</Trans>\n </div>\n }\n >\n {(() => {\n switch (provider) {\n case \"swift\":\n return <SwiftContainers />\n case \"ceph\":\n return <CephBuckets />\n default:\n return <div>Storage Overview Page</div> // replace when available\n }\n })()}\n </ErrorBoundary>\n ) : (\n <div className=\"p-4 text-center\">\n <Trans>No project selected</Trans>\n </div>\n )}\n </div>\n )\n}\n"],"mappings":";;AA2GA,IAAAA,UAgCW,gBAAC,KAAA,EAAE,UAAA,4BAAA,CAAA"}