@cobaltcore-dev/aurora 0.21.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/client/{ContentHeader-CIAzorGI.mjs → ContentHeader-BIScbvb4.mjs} +3 -2
  2. package/dist/client/{ContentHeader-CIAzorGI.mjs.map → ContentHeader-BIScbvb4.mjs.map} +1 -1
  3. package/dist/client/Slot-CWb612-_.mjs +28 -0
  4. package/dist/client/Slot-CWb612-_.mjs.map +1 -0
  5. package/dist/client/{_flavorId-BsIWL0XK.mjs → _flavorId-BMJcnI7H.mjs} +2 -2
  6. package/dist/client/{_flavorId-BsIWL0XK.mjs.map → _flavorId-BMJcnI7H.mjs.map} +1 -1
  7. package/dist/client/{_flavorId-Cu1nWwOq.mjs → _flavorId-DUWdFEMH.mjs} +3 -3
  8. package/dist/client/{_flavorId-Cu1nWwOq.mjs.map → _flavorId-DUWdFEMH.mjs.map} +1 -1
  9. package/dist/client/{_floatingIpId-8-UPUV8M.mjs → _floatingIpId-CjQLzpC5.mjs} +2 -2
  10. package/dist/client/{_floatingIpId-8-UPUV8M.mjs.map → _floatingIpId-CjQLzpC5.mjs.map} +1 -1
  11. package/dist/client/{_floatingIpId-B_M3gyMA.mjs → _floatingIpId-KcWWq9ZW2.mjs} +2 -2
  12. package/dist/client/_floatingIpId-KcWWq9ZW2.mjs.map +1 -0
  13. package/dist/client/{_imageId-D1Z0xIco.mjs → _imageId-BkEpwbCr.mjs} +2 -2
  14. package/dist/client/{_imageId-D1Z0xIco.mjs.map → _imageId-BkEpwbCr.mjs.map} +1 -1
  15. package/dist/client/{_projectId-B4ScifQf.mjs → _projectId-CoMD6nq6.mjs} +3 -3
  16. package/dist/client/{_projectId-B4ScifQf.mjs.map → _projectId-CoMD6nq6.mjs.map} +1 -1
  17. package/dist/client/{_projectId-CYq4KZXB.mjs → _projectId-D8FHe0gr.mjs} +3 -3
  18. package/dist/client/{_projectId-CYq4KZXB.mjs.map → _projectId-D8FHe0gr.mjs.map} +1 -1
  19. package/dist/client/{_projectId-DxgPaiA7.mjs → _projectId-MjFv839t.mjs} +2 -2
  20. package/dist/client/{_projectId-DxgPaiA7.mjs.map → _projectId-MjFv839t.mjs.map} +1 -1
  21. package/dist/client/{_projectId-DJLjspKf.mjs → _projectId-hMtrqW6m.mjs} +24 -23
  22. package/dist/client/{_projectId-DJLjspKf.mjs.map → _projectId-hMtrqW6m.mjs.map} +1 -1
  23. package/dist/client/{_storageType-BkhbKG_G.mjs → _storageType-CI5f4CsI.mjs} +2 -2
  24. package/dist/client/{_storageType-BkhbKG_G.mjs.map → _storageType-CI5f4CsI.mjs.map} +1 -1
  25. package/dist/client/{_storageType-ByLL106s.mjs → _storageType-DePX1Aah.mjs} +3 -3
  26. package/dist/client/{_storageType-ByLL106s.mjs.map → _storageType-DePX1Aah.mjs.map} +1 -1
  27. package/dist/client/{flavors-q1j-y3e-.mjs → flavors-DLxUiBhE.mjs} +2 -2
  28. package/dist/client/{flavors-q1j-y3e-.mjs.map → flavors-DLxUiBhE.mjs.map} +1 -1
  29. package/dist/client/{flavors-CT1bHP61.mjs → flavors-DkGuWvge.mjs} +3 -3
  30. package/dist/client/{flavors-CT1bHP61.mjs.map → flavors-DkGuWvge.mjs.map} +1 -1
  31. package/dist/client/{images-BklcxYWi.mjs → images-D9nfOtSC.mjs} +3 -3
  32. package/dist/client/{images-BklcxYWi.mjs.map → images-D9nfOtSC.mjs.map} +1 -1
  33. package/dist/client/{images-CPYewah0.mjs → images-h9i7S4LN.mjs} +2 -2
  34. package/dist/client/{images-CPYewah0.mjs.map → images-h9i7S4LN.mjs.map} +1 -1
  35. package/dist/client/index.js +36 -35
  36. package/dist/client/index.js.map +1 -1
  37. package/dist/client/{objects-CEBrL7k-.mjs → objects-B3Z8h4RU.mjs} +2 -2
  38. package/dist/client/{objects-CEBrL7k-.mjs.map → objects-B3Z8h4RU.mjs.map} +1 -1
  39. package/dist/client/{objects-DvIxpzGI.mjs → objects-CGOfO00N.mjs} +3 -3
  40. package/dist/client/{objects-DvIxpzGI.mjs.map → objects-CGOfO00N.mjs.map} +1 -1
  41. package/dist/client/{pca-BAuZA0K3.mjs → pca-C7cMT8fW.mjs} +2 -2
  42. package/dist/client/{pca-BAuZA0K3.mjs.map → pca-C7cMT8fW.mjs.map} +1 -1
  43. package/dist/client/{pca-Ddk0YfgK.mjs → pca-Nppb8aVa.mjs} +3 -3
  44. package/dist/client/{pca-Ddk0YfgK.mjs.map → pca-Nppb8aVa.mjs.map} +1 -1
  45. package/dist/client/routeInfo-CdTRlSxv.mjs +32 -0
  46. package/dist/client/routeInfo-CdTRlSxv.mjs.map +1 -0
  47. package/dist/client/{routes-DsisKFfG.mjs → routes-Bz4zb2H4.mjs} +2 -2
  48. package/dist/client/routes-Bz4zb2H4.mjs.map +1 -0
  49. package/dist/client/{routes-BzBTVAjK.mjs → routes-CaqfgBYu.mjs} +39 -32
  50. package/dist/client/routes-CaqfgBYu.mjs.map +1 -0
  51. package/package.json +3 -3
  52. package/dist/client/_floatingIpId-B_M3gyMA.mjs.map +0 -1
  53. package/dist/client/routeInfo-Ct5r4Gen.mjs +0 -56
  54. package/dist/client/routeInfo-Ct5r4Gen.mjs.map +0 -1
  55. package/dist/client/routes-BzBTVAjK.mjs.map +0 -1
  56. package/dist/client/routes-DsisKFfG.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"_imageId-D1Z0xIco.mjs","names":["React","useState","DescriptionList","DescriptionTerm","DescriptionDefinition","Container","ContentHeading","Stack","Message","Box","Button","ButtonRow","SizeDisplay","trpcReact","MEMBER_STATUSES","ClipboardText","ImageMembersTable","SharedImageBox","image","myMemberData","canUpdateMember","onStatusChange","isLoading","useLingui","isPending","status","PENDING","isRejected","REJECTED","sharedAt","created_at","Date","toLocaleString","t","updatedAt","updated_at","ownerProject","owner","p","className","span","ul","li","onClick","ACCEPTED","disabled","GeneralImageData","px","py","alignTerms","text","id","name","size","min_disk","min_ram","disk_format","container_format","toLocaleDateString","SecuritySection","currentProjectId","isSharedWithMe","visibility","undefined","protected","checksum","CustomPropertiesSection","knownFields","Set","customProperties","Object","entries","filter","key","has","sort","a","b","localeCompare","hasProperties","length","map","value","Fragment","JSON","stringify","String","getTabClassName","active","base","SharingDetailsTab","permissions","isAddingMember","setIsAddingMember","message","setMessage","data","imageMembers","isMembersLoading","compute","listImageMembers","useQuery","project_id","imageId","enabled","variant","type","onDismiss","canAdd","canCreateMember","canRemove","canDeleteMember","projectId","ImageDetailsView","activeTab","onTabChange","onMemberStatusChange","isMemberStatusChanging","actions","isImageOwner","showTabs","direction","gap","div","button","Button","ButtonRow","Stack","Spinner","PopupMenu","PopupMenuToggle","PopupMenuOptions","PopupMenuItem","Toast","ToastProps","useNavigate","useParams","useSearch","Trans","useLingui","trpcReact","ImageDetailsView","EditImageDetailsModal","EditImageMetadataModal","DeleteImageModal","ActivateImageModal","DeactivateImageModal","IMAGE_STATUSES","IMAGE_VISIBILITY","GlanceImage","MemberStatus","TRPCClientError","InferrableClientTypes","getImageAccessStatusUpdatedToast","getImageAccessStatusErrorToast","useState","ContentHeader","Route","RouteComponent","projectId","imageId","from","tab","navigate","t","data","image","status","error","compute","getImageById","useQuery","project_id","permissionsData","canUser","permission","permissions","canDelete","canUpdate","canCreateMember","canDeleteMember","canUpdateMember","utils","useUtils","editDetailsModalOpen","setEditDetailsModalOpen","editMetadataModalOpen","setEditMetadataModalOpen","deleteModalOpen","setDeleteModalOpen","activateModalOpen","setActivateModalOpen","deactivateModalOpen","setDeactivateModalOpen","toastData","setToastData","updateImageMutation","updateImage","useMutation","onSuccess","updatedImage","setData","listImagesWithPagination","invalidate","deleteImageMutation","deleteImage","deactivateImageMutation","deactivateImage","reactivateImageMutation","reactivateImage","updateImageVisibilityMutation","updateImageVisibility","isSharedWithMe","visibility","SHARED","owner","undefined","myMemberData","getImageMember","memberId","enabled","updateMemberMutation","updateImageMember","listImageMembers","listSharedImagesByMemberStatus","handleMemberStatusChange","newStatus","mutateAsync","onDismiss","errorMessage","message","handleBack","to","params","isLoading","isPending","convertToJsonPatchOperations","updatedProperties","Partial","originalImage","Array","op","path","value","operations","Object","entries","forEach","key","push","propertyExists","handleSaveEdit","Promise","handleDelete","deletedImage","id","handleActivate","img","handleDeactivate","handleUpdateVisibility","newVisibility","isDeactivated","DEACTIVATED","isPrivate","PRIVATE","isMemberAccepted","isImageOwner","canRejectSharedImage","canUpdateOwnImage","canDeleteOwnImage","protected","canManageSharing","hasMoreActions","headerActions","search","String","name","newTab","component"],"sources":["../../src/client/routes/_auth/projects/$projectId/compute/-components/Images/-components/ImageDetailsView.tsx","../../src/client/routes/_auth/projects/$projectId/compute/images/$imageId.tsx?tsr-split=component"],"sourcesContent":["import React, { useState } from \"react\"\nimport {\n DescriptionList,\n DescriptionTerm,\n DescriptionDefinition,\n Container,\n ContentHeading,\n Stack,\n Message,\n Box,\n Button,\n ButtonRow,\n} from \"@cloudoperators/juno-ui-components\"\nimport { Trans, useLingui } from \"@lingui/react/macro\"\nimport { GlanceImage, ImageMember, MemberStatus } from \"@/server/Compute/types/image\"\nimport { SizeDisplay } from \"./SizeDisplay\"\nimport { trpcReact } from \"@/client/trpcClient\"\nimport { MEMBER_STATUSES } from \"../../../-constants/filters\"\nimport ClipboardText from \"@/client/components/ClipboardText\"\nimport { ImageMembersTable } from \"./ImageMembersTable\"\n\ninterface ImageDetailsViewProps {\n image: GlanceImage\n currentProjectId?: string\n activeTab?: \"details\" | \"sharing\"\n onTabChange?: (tab: \"details\" | \"sharing\") => void\n permissions?: {\n canCreateMember: boolean\n canDeleteMember: boolean\n canUpdateMember: boolean\n }\n myMemberData?: ImageMember\n onMemberStatusChange?: (status: MemberStatus) => void\n isMemberStatusChanging?: boolean\n actions?: React.ReactNode\n}\n\nconst SharedImageBox: React.FC<{\n image: GlanceImage\n myMemberData: ImageMember\n canUpdateMember: boolean\n onStatusChange: (status: MemberStatus) => void\n isLoading: boolean\n}> = ({ image, myMemberData, canUpdateMember, onStatusChange, isLoading }) => {\n const { t } = useLingui()\n const isPending = myMemberData.status === MEMBER_STATUSES.PENDING\n const isRejected = myMemberData.status === MEMBER_STATUSES.REJECTED\n\n const sharedAt = myMemberData.created_at ? new Date(myMemberData.created_at).toLocaleString() : t`N/A`\n const updatedAt = myMemberData.updated_at ? new Date(myMemberData.updated_at).toLocaleString() : t`N/A`\n const ownerProject = image.owner ?? \"\"\n\n return (\n <Box>\n {isPending && (\n <p className=\"text-theme-highest font-semibold\">\n <Trans>Your action is required</Trans>\n </p>\n )}\n <p>\n <Trans>\n This image was shared with you by <span className=\"font-semibold\">{ownerProject}</span> on {sharedAt}.\n </Trans>\n </p>\n <ul>\n <li>\n <span className=\"font-semibold\">\n <Trans>Access Status:</Trans>\n </span>{\" \"}\n {myMemberData.status}\n </li>\n <li>\n <span className=\"font-semibold\">\n <Trans>Shared:</Trans>\n </span>{\" \"}\n {sharedAt}\n </li>\n <li>\n <span className=\"font-semibold\">\n <Trans>Updated:</Trans>\n </span>{\" \"}\n {updatedAt}\n </li>\n </ul>\n\n {canUpdateMember && (isPending || isRejected) && (\n <ButtonRow>\n <Button onClick={() => onStatusChange(MEMBER_STATUSES.ACCEPTED)} disabled={isLoading}>\n <Trans>Accept</Trans>\n </Button>\n {isPending && (\n <Button onClick={() => onStatusChange(MEMBER_STATUSES.REJECTED)} disabled={isLoading}>\n <Trans>Reject</Trans>\n </Button>\n )}\n </ButtonRow>\n )}\n </Box>\n )\n}\n\nexport const GeneralImageData: React.FC<{ image: GlanceImage }> = ({ image }) => {\n const { t } = useLingui()\n\n return (\n <Container px={false} py>\n <ContentHeading>{t`General Image Data`}</ContentHeading>\n <DescriptionList alignTerms=\"right\">\n <DescriptionTerm>{t`ID`}</DescriptionTerm>\n <DescriptionDefinition>\n <ClipboardText text={image.id} />\n </DescriptionDefinition>\n\n <DescriptionTerm>{t`Name`}</DescriptionTerm>\n <DescriptionDefinition>{image.name}</DescriptionDefinition>\n\n <DescriptionTerm>{t`Status`}</DescriptionTerm>\n <DescriptionDefinition>{image.status}</DescriptionDefinition>\n\n <DescriptionTerm>{t`Size`}</DescriptionTerm>\n <DescriptionDefinition>\n <SizeDisplay size={image.size} />\n </DescriptionDefinition>\n\n <DescriptionTerm>{t`Min. Disk`}</DescriptionTerm>\n <DescriptionDefinition>{image.min_disk} GB</DescriptionDefinition>\n\n <DescriptionTerm>{t`Min. RAM`}</DescriptionTerm>\n <DescriptionDefinition>{image.min_ram} MB</DescriptionDefinition>\n\n <DescriptionTerm>{t`Disk Format`}</DescriptionTerm>\n <DescriptionDefinition>\n <span className=\"uppercase\">{image.disk_format}</span>\n </DescriptionDefinition>\n\n <DescriptionTerm>{t`Container Format`}</DescriptionTerm>\n <DescriptionDefinition>\n <span className=\"uppercase\">{image.container_format}</span>\n </DescriptionDefinition>\n\n <DescriptionTerm>{t`Created At`}</DescriptionTerm>\n <DescriptionDefinition>\n {image.created_at ? new Date(image.created_at).toLocaleDateString() : t`N/A`}\n </DescriptionDefinition>\n\n <DescriptionTerm>{t`Updated At`}</DescriptionTerm>\n <DescriptionDefinition>\n {image.updated_at ? new Date(image.updated_at).toLocaleDateString() : t`N/A`}\n </DescriptionDefinition>\n </DescriptionList>\n </Container>\n )\n}\n\nexport const SecuritySection: React.FC<{ image: GlanceImage; currentProjectId?: string }> = ({\n image,\n currentProjectId,\n}) => {\n const { t } = useLingui()\n\n const isSharedWithMe = image.visibility === \"shared\" && image.owner !== undefined && image.owner !== currentProjectId\n\n return (\n <Container px={false} py>\n <ContentHeading>{t`Security`}</ContentHeading>\n <DescriptionList alignTerms=\"right\">\n <DescriptionTerm>{isSharedWithMe ? t`Shared by Project` : t`Owner Project ID`}</DescriptionTerm>\n <DescriptionDefinition>{image.owner ? <ClipboardText text={image.owner} /> : \"\"}</DescriptionDefinition>\n\n <DescriptionTerm>{t`Visibility`}</DescriptionTerm>\n <DescriptionDefinition>{image.visibility}</DescriptionDefinition>\n\n <DescriptionTerm>{t`Protected`}</DescriptionTerm>\n <DescriptionDefinition>{image.protected ? t`Yes` : t`No`}</DescriptionDefinition>\n\n <DescriptionTerm>{t`Checksum`}</DescriptionTerm>\n <DescriptionDefinition>{image?.checksum ? image.checksum : \"\"}</DescriptionDefinition>\n </DescriptionList>\n </Container>\n )\n}\n\nexport const CustomPropertiesSection: React.FC<{ image: GlanceImage }> = ({ image }) => {\n const { t } = useLingui()\n\n const knownFields = new Set([\n \"id\",\n \"name\",\n \"status\",\n \"visibility\",\n \"size\",\n \"disk_format\",\n \"container_format\",\n \"min_disk\",\n \"min_ram\",\n \"owner\",\n \"protected\",\n \"created_at\",\n \"updated_at\",\n \"checksum\",\n ])\n\n const customProperties = Object.entries(image)\n .filter(([key]) => !knownFields.has(key))\n .sort(([a], [b]) => a.localeCompare(b))\n\n const hasProperties = customProperties.length > 0\n\n return (\n <Container px={false} py>\n <ContentHeading>{t`Custom Properties / Metadata`}</ContentHeading>\n {hasProperties ? (\n <DescriptionList alignTerms=\"right\" className=\"grid-cols-4\">\n {customProperties.map(([key, value]) => (\n <React.Fragment key={key}>\n <DescriptionTerm className=\"col-span-1\">{key}</DescriptionTerm>\n <DescriptionDefinition className=\"col-span-1\">\n {value === null || value === undefined ? (\n <span>null</span>\n ) : typeof value === \"object\" ? (\n <span className=\"break-all\">{JSON.stringify(value)}</span>\n ) : typeof value === \"boolean\" ? (\n value ? (\n t`True`\n ) : (\n t`False`\n )\n ) : (\n <span className=\"break-all\">{String(value)}</span>\n )}\n </DescriptionDefinition>\n </React.Fragment>\n ))}\n </DescriptionList>\n ) : (\n <p className=\"text-theme-light\">{t`No custom properties defined`}</p>\n )}\n </Container>\n )\n}\n\nconst getTabClassName = (active: boolean) => {\n const base = \"px-6 py-3 font-semibold border-b-2 transition-colors\"\n return active\n ? `${base} border-theme-accent text-theme-highest`\n : `${base} border-transparent text-theme-secondary hover:text-theme-high`\n}\n\nconst SharingDetailsTab: React.FC<ImageDetailsViewProps> = ({ image, permissions, currentProjectId }) => {\n const [isAddingMember, setIsAddingMember] = useState(false)\n const [message, setMessage] = useState<{ text: string; type: \"error\" | \"info\" } | null>(null)\n\n const { data: imageMembers, isLoading: isMembersLoading } = trpcReact.compute.listImageMembers.useQuery(\n { project_id: currentProjectId!, imageId: image.id },\n { enabled: !!image.id && !!currentProjectId }\n )\n\n return (\n <Container px={false} py>\n {message && (\n <Message text={message.text} variant={message.type} onDismiss={() => setMessage(null)} className=\"mb-4\" />\n )}\n <ImageMembersTable\n image={image}\n imageMembers={imageMembers}\n isMembersLoading={isMembersLoading}\n canAdd={permissions?.canCreateMember ?? false}\n canRemove={permissions?.canDeleteMember ?? false}\n isAddingMember={isAddingMember}\n setIsAddingMember={setIsAddingMember}\n setMessage={setMessage}\n projectId={currentProjectId!}\n />\n </Container>\n )\n}\n\nexport const ImageDetailsView: React.FC<ImageDetailsViewProps> = ({\n image,\n currentProjectId,\n activeTab = \"details\",\n onTabChange,\n permissions,\n myMemberData,\n onMemberStatusChange,\n isMemberStatusChanging,\n actions,\n}) => {\n const { t } = useLingui()\n\n const isSharedWithMe = image.visibility === \"shared\" && image.owner !== undefined && image.owner !== currentProjectId\n const isImageOwner = image.owner === currentProjectId\n const showTabs = isImageOwner && image.visibility === \"shared\"\n\n return (\n <Stack direction=\"vertical\" gap=\"6\">\n {isSharedWithMe && myMemberData && onMemberStatusChange && (\n <SharedImageBox\n image={image}\n myMemberData={myMemberData}\n canUpdateMember={permissions?.canUpdateMember ?? false}\n onStatusChange={onMemberStatusChange}\n isLoading={isMemberStatusChanging ?? false}\n />\n )}\n\n {showTabs && (\n <div className=\"border-theme-background-lvl-3 border-b\">\n <Stack direction=\"horizontal\" gap=\"0\">\n <button className={getTabClassName(activeTab === \"details\")} onClick={() => onTabChange?.(\"details\")}>\n {t`Details`}\n </button>\n <button className={getTabClassName(activeTab === \"sharing\")} onClick={() => onTabChange?.(\"sharing\")}>\n {t`Sharing Details`}\n </button>\n </Stack>\n </div>\n )}\n\n {(activeTab === \"details\" || !showTabs) && (\n <>\n {actions}\n <GeneralImageData image={image} />\n <SecuritySection image={image} currentProjectId={currentProjectId} />\n <CustomPropertiesSection image={image} />\n </>\n )}\n\n {activeTab === \"sharing\" && showTabs && <SharingDetailsTab image={image} permissions={permissions} />}\n </Stack>\n )\n}\n","import {\n Button,\n ButtonRow,\n Stack,\n Spinner,\n PopupMenu,\n PopupMenuToggle,\n PopupMenuOptions,\n PopupMenuItem,\n Toast,\n ToastProps,\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 { ImageDetailsView } from \"../-components/Images/-components/ImageDetailsView\"\nimport { EditImageDetailsModal } from \"../-components/Images/-components/EditImageDetailsModal\"\nimport { EditImageMetadataModal } from \"../-components/Images/-components/EditImageMetadataModal\"\nimport { DeleteImageModal } from \"../-components/Images/-components/DeleteImageModal\"\nimport { ActivateImageModal } from \"../-components/Images/-components/ActivateImageModal\"\nimport { DeactivateImageModal } from \"../-components/Images/-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} from \"../-components/Images/-components/ImageToastNotifications\"\nimport { useState } from \"react\"\nimport { ContentHeader } from \"@/client/components/ContentHeader/ContentHeader\"\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 isDetail: true,\n sectionCrumb: { labelKey: \"Compute\" },\n crumb: { labelKey: \"Images\", to: \"/projects/$projectId/compute/images\" },\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})\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 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 const [toastData, setToastData] = useState<ToastProps | null>(null)\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 setToastData(getImageAccessStatusUpdatedToast(newStatus, { onDismiss: () => setToastData(null) }))\n } catch (error) {\n const errorMessage = (error as TRPCClientError<InferrableClientTypes>)?.message\n setToastData(getImageAccessStatusErrorToast(errorMessage, { onDismiss: () => setToastData(null) }))\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 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 } catch {\n setActivateModalOpen(false)\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 } catch {\n setDeactivateModalOpen(false)\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 } catch {\n // error handled by mutation state\n }\n }\n\n // Handle loading state\n if (status === \"pending\") {\n return (\n <Stack className=\"fixed inset-0\" distribution=\"center\" alignment=\"center\" direction=\"vertical\">\n <Spinner variant=\"primary\" size=\"large\" className=\"mb-2\" />\n <Trans>Loading Image Details...</Trans>\n </Stack>\n )\n }\n\n // Handle error state\n if (status === \"error\") {\n const errorMessage = error?.message || \"Unknown error\"\n\n return (\n <Stack className=\"fixed inset-0\" distribution=\"center\" alignment=\"center\" direction=\"vertical\" gap=\"5\">\n <p className=\"text-theme-error font-semibold\">\n <Trans>Error loading image</Trans>\n </p>\n <p className=\"text-theme-highest\">{errorMessage}</p>\n <Button onClick={handleBack} variant=\"primary\">\n <Trans>Back to Images</Trans>\n </Button>\n </Stack>\n )\n }\n\n // Handle no data state\n if (!image) {\n return (\n <Stack className=\"fixed inset-0\" distribution=\"center\" alignment=\"center\" direction=\"vertical\" gap=\"5\">\n <p className=\"text-theme-highest\">\n <Trans>Image not found</Trans>\n </p>\n <Button onClick={handleBack} variant=\"primary\">\n <Trans>Back to Images</Trans>\n </Button>\n </Stack>\n )\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 =\n hasMoreActions || (!isSharedWithMe && permissions.canUpdate) ? (\n <ButtonRow>\n {hasMoreActions && (\n <PopupMenu>\n <PopupMenuToggle as=\"div\">\n <Button icon=\"moreVert\" disabled={isLoading}>\n <Trans>More Actions</Trans>\n </Button>\n </PopupMenuToggle>\n <PopupMenuOptions>\n {canRejectSharedImage && (\n <PopupMenuItem label={t`Reject`} onClick={() => handleMemberStatusChange(\"rejected\")} />\n )}\n {!isSharedWithMe && permissions.canUpdate && (\n <PopupMenuItem\n label={isDeactivated ? t`Activate` : t`Deactivate`}\n onClick={() => (isDeactivated ? setActivateModalOpen(true) : setDeactivateModalOpen(true))}\n />\n )}\n {!isSharedWithMe && permissions.canUpdate && isPrivate && (\n <PopupMenuItem label={t`Set to \"Shared\"`} onClick={() => handleUpdateVisibility(\"shared\")} />\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 {!isSharedWithMe && permissions.canDelete && !image.protected && (\n <PopupMenuItem label={t`Delete`} onClick={() => setDeleteModalOpen(true)} />\n )}\n </PopupMenuOptions>\n </PopupMenu>\n )}\n {!isSharedWithMe && permissions.canUpdate && (\n <Button onClick={() => setEditMetadataModalOpen(true)} disabled={isLoading}>\n <Trans>Edit Metadata</Trans>\n </Button>\n )}\n {!isSharedWithMe && permissions.canUpdate && (\n <Button onClick={() => setEditDetailsModalOpen(true)} variant=\"primary\" disabled={isLoading}>\n <Trans>Edit Details</Trans>\n </Button>\n )}\n </ButtonRow>\n ) : undefined\n\n // Render success state\n return (\n <>\n <ContentHeader title={String(image.name ?? image.id)} projectId={projectId} actions={headerActions} />\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 {toastData && <Toast {...toastData} />}\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":"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"}
1
+ {"version":3,"file":"_imageId-BkEpwbCr.mjs","names":["React","useState","DescriptionList","DescriptionTerm","DescriptionDefinition","Container","ContentHeading","Stack","Message","Box","Button","ButtonRow","SizeDisplay","trpcReact","MEMBER_STATUSES","ClipboardText","ImageMembersTable","SharedImageBox","image","myMemberData","canUpdateMember","onStatusChange","isLoading","useLingui","isPending","status","PENDING","isRejected","REJECTED","sharedAt","created_at","Date","toLocaleString","t","updatedAt","updated_at","ownerProject","owner","p","className","span","ul","li","onClick","ACCEPTED","disabled","GeneralImageData","px","py","alignTerms","text","id","name","size","min_disk","min_ram","disk_format","container_format","toLocaleDateString","SecuritySection","currentProjectId","isSharedWithMe","visibility","undefined","protected","checksum","CustomPropertiesSection","knownFields","Set","customProperties","Object","entries","filter","key","has","sort","a","b","localeCompare","hasProperties","length","map","value","Fragment","JSON","stringify","String","getTabClassName","active","base","SharingDetailsTab","permissions","isAddingMember","setIsAddingMember","message","setMessage","data","imageMembers","isMembersLoading","compute","listImageMembers","useQuery","project_id","imageId","enabled","variant","type","onDismiss","canAdd","canCreateMember","canRemove","canDeleteMember","projectId","ImageDetailsView","activeTab","onTabChange","onMemberStatusChange","isMemberStatusChanging","actions","isImageOwner","showTabs","direction","gap","div","button","Button","ButtonRow","Stack","Spinner","PopupMenu","PopupMenuToggle","PopupMenuOptions","PopupMenuItem","Toast","ToastProps","useNavigate","useParams","useSearch","Trans","useLingui","trpcReact","ImageDetailsView","EditImageDetailsModal","EditImageMetadataModal","DeleteImageModal","ActivateImageModal","DeactivateImageModal","IMAGE_STATUSES","IMAGE_VISIBILITY","GlanceImage","MemberStatus","TRPCClientError","InferrableClientTypes","getImageAccessStatusUpdatedToast","getImageAccessStatusErrorToast","useState","ContentHeader","Route","RouteComponent","projectId","imageId","from","tab","navigate","t","data","image","status","error","compute","getImageById","useQuery","project_id","permissionsData","canUser","permission","permissions","canDelete","canUpdate","canCreateMember","canDeleteMember","canUpdateMember","utils","useUtils","editDetailsModalOpen","setEditDetailsModalOpen","editMetadataModalOpen","setEditMetadataModalOpen","deleteModalOpen","setDeleteModalOpen","activateModalOpen","setActivateModalOpen","deactivateModalOpen","setDeactivateModalOpen","toastData","setToastData","updateImageMutation","updateImage","useMutation","onSuccess","updatedImage","setData","listImagesWithPagination","invalidate","deleteImageMutation","deleteImage","deactivateImageMutation","deactivateImage","reactivateImageMutation","reactivateImage","updateImageVisibilityMutation","updateImageVisibility","isSharedWithMe","visibility","SHARED","owner","undefined","myMemberData","getImageMember","memberId","enabled","updateMemberMutation","updateImageMember","listImageMembers","listSharedImagesByMemberStatus","handleMemberStatusChange","newStatus","mutateAsync","onDismiss","errorMessage","message","handleBack","to","params","isLoading","isPending","convertToJsonPatchOperations","updatedProperties","Partial","originalImage","Array","op","path","value","operations","Object","entries","forEach","key","push","propertyExists","handleSaveEdit","Promise","handleDelete","deletedImage","id","handleActivate","img","handleDeactivate","handleUpdateVisibility","newVisibility","isDeactivated","DEACTIVATED","isPrivate","PRIVATE","isMemberAccepted","isImageOwner","canRejectSharedImage","canUpdateOwnImage","canDeleteOwnImage","protected","canManageSharing","hasMoreActions","headerActions","search","String","name","newTab","component"],"sources":["../../src/client/routes/_auth/projects/$projectId/compute/-components/Images/-components/ImageDetailsView.tsx","../../src/client/routes/_auth/projects/$projectId/compute/images/$imageId.tsx?tsr-split=component"],"sourcesContent":["import React, { useState } from \"react\"\nimport {\n DescriptionList,\n DescriptionTerm,\n DescriptionDefinition,\n Container,\n ContentHeading,\n Stack,\n Message,\n Box,\n Button,\n ButtonRow,\n} from \"@cloudoperators/juno-ui-components\"\nimport { Trans, useLingui } from \"@lingui/react/macro\"\nimport { GlanceImage, ImageMember, MemberStatus } from \"@/server/Compute/types/image\"\nimport { SizeDisplay } from \"./SizeDisplay\"\nimport { trpcReact } from \"@/client/trpcClient\"\nimport { MEMBER_STATUSES } from \"../../../-constants/filters\"\nimport ClipboardText from \"@/client/components/ClipboardText\"\nimport { ImageMembersTable } from \"./ImageMembersTable\"\n\ninterface ImageDetailsViewProps {\n image: GlanceImage\n currentProjectId?: string\n activeTab?: \"details\" | \"sharing\"\n onTabChange?: (tab: \"details\" | \"sharing\") => void\n permissions?: {\n canCreateMember: boolean\n canDeleteMember: boolean\n canUpdateMember: boolean\n }\n myMemberData?: ImageMember\n onMemberStatusChange?: (status: MemberStatus) => void\n isMemberStatusChanging?: boolean\n actions?: React.ReactNode\n}\n\nconst SharedImageBox: React.FC<{\n image: GlanceImage\n myMemberData: ImageMember\n canUpdateMember: boolean\n onStatusChange: (status: MemberStatus) => void\n isLoading: boolean\n}> = ({ image, myMemberData, canUpdateMember, onStatusChange, isLoading }) => {\n const { t } = useLingui()\n const isPending = myMemberData.status === MEMBER_STATUSES.PENDING\n const isRejected = myMemberData.status === MEMBER_STATUSES.REJECTED\n\n const sharedAt = myMemberData.created_at ? new Date(myMemberData.created_at).toLocaleString() : t`N/A`\n const updatedAt = myMemberData.updated_at ? new Date(myMemberData.updated_at).toLocaleString() : t`N/A`\n const ownerProject = image.owner ?? \"\"\n\n return (\n <Box>\n {isPending && (\n <p className=\"text-theme-highest font-semibold\">\n <Trans>Your action is required</Trans>\n </p>\n )}\n <p>\n <Trans>\n This image was shared with you by <span className=\"font-semibold\">{ownerProject}</span> on {sharedAt}.\n </Trans>\n </p>\n <ul>\n <li>\n <span className=\"font-semibold\">\n <Trans>Access Status:</Trans>\n </span>{\" \"}\n {myMemberData.status}\n </li>\n <li>\n <span className=\"font-semibold\">\n <Trans>Shared:</Trans>\n </span>{\" \"}\n {sharedAt}\n </li>\n <li>\n <span className=\"font-semibold\">\n <Trans>Updated:</Trans>\n </span>{\" \"}\n {updatedAt}\n </li>\n </ul>\n\n {canUpdateMember && (isPending || isRejected) && (\n <ButtonRow>\n <Button onClick={() => onStatusChange(MEMBER_STATUSES.ACCEPTED)} disabled={isLoading}>\n <Trans>Accept</Trans>\n </Button>\n {isPending && (\n <Button onClick={() => onStatusChange(MEMBER_STATUSES.REJECTED)} disabled={isLoading}>\n <Trans>Reject</Trans>\n </Button>\n )}\n </ButtonRow>\n )}\n </Box>\n )\n}\n\nexport const GeneralImageData: React.FC<{ image: GlanceImage }> = ({ image }) => {\n const { t } = useLingui()\n\n return (\n <Container px={false} py>\n <ContentHeading>{t`General Image Data`}</ContentHeading>\n <DescriptionList alignTerms=\"right\">\n <DescriptionTerm>{t`ID`}</DescriptionTerm>\n <DescriptionDefinition>\n <ClipboardText text={image.id} />\n </DescriptionDefinition>\n\n <DescriptionTerm>{t`Name`}</DescriptionTerm>\n <DescriptionDefinition>{image.name}</DescriptionDefinition>\n\n <DescriptionTerm>{t`Status`}</DescriptionTerm>\n <DescriptionDefinition>{image.status}</DescriptionDefinition>\n\n <DescriptionTerm>{t`Size`}</DescriptionTerm>\n <DescriptionDefinition>\n <SizeDisplay size={image.size} />\n </DescriptionDefinition>\n\n <DescriptionTerm>{t`Min. Disk`}</DescriptionTerm>\n <DescriptionDefinition>{image.min_disk} GB</DescriptionDefinition>\n\n <DescriptionTerm>{t`Min. RAM`}</DescriptionTerm>\n <DescriptionDefinition>{image.min_ram} MB</DescriptionDefinition>\n\n <DescriptionTerm>{t`Disk Format`}</DescriptionTerm>\n <DescriptionDefinition>\n <span className=\"uppercase\">{image.disk_format}</span>\n </DescriptionDefinition>\n\n <DescriptionTerm>{t`Container Format`}</DescriptionTerm>\n <DescriptionDefinition>\n <span className=\"uppercase\">{image.container_format}</span>\n </DescriptionDefinition>\n\n <DescriptionTerm>{t`Created At`}</DescriptionTerm>\n <DescriptionDefinition>\n {image.created_at ? new Date(image.created_at).toLocaleDateString() : t`N/A`}\n </DescriptionDefinition>\n\n <DescriptionTerm>{t`Updated At`}</DescriptionTerm>\n <DescriptionDefinition>\n {image.updated_at ? new Date(image.updated_at).toLocaleDateString() : t`N/A`}\n </DescriptionDefinition>\n </DescriptionList>\n </Container>\n )\n}\n\nexport const SecuritySection: React.FC<{ image: GlanceImage; currentProjectId?: string }> = ({\n image,\n currentProjectId,\n}) => {\n const { t } = useLingui()\n\n const isSharedWithMe = image.visibility === \"shared\" && image.owner !== undefined && image.owner !== currentProjectId\n\n return (\n <Container px={false} py>\n <ContentHeading>{t`Security`}</ContentHeading>\n <DescriptionList alignTerms=\"right\">\n <DescriptionTerm>{isSharedWithMe ? t`Shared by Project` : t`Owner Project ID`}</DescriptionTerm>\n <DescriptionDefinition>{image.owner ? <ClipboardText text={image.owner} /> : \"\"}</DescriptionDefinition>\n\n <DescriptionTerm>{t`Visibility`}</DescriptionTerm>\n <DescriptionDefinition>{image.visibility}</DescriptionDefinition>\n\n <DescriptionTerm>{t`Protected`}</DescriptionTerm>\n <DescriptionDefinition>{image.protected ? t`Yes` : t`No`}</DescriptionDefinition>\n\n <DescriptionTerm>{t`Checksum`}</DescriptionTerm>\n <DescriptionDefinition>{image?.checksum ? image.checksum : \"\"}</DescriptionDefinition>\n </DescriptionList>\n </Container>\n )\n}\n\nexport const CustomPropertiesSection: React.FC<{ image: GlanceImage }> = ({ image }) => {\n const { t } = useLingui()\n\n const knownFields = new Set([\n \"id\",\n \"name\",\n \"status\",\n \"visibility\",\n \"size\",\n \"disk_format\",\n \"container_format\",\n \"min_disk\",\n \"min_ram\",\n \"owner\",\n \"protected\",\n \"created_at\",\n \"updated_at\",\n \"checksum\",\n ])\n\n const customProperties = Object.entries(image)\n .filter(([key]) => !knownFields.has(key))\n .sort(([a], [b]) => a.localeCompare(b))\n\n const hasProperties = customProperties.length > 0\n\n return (\n <Container px={false} py>\n <ContentHeading>{t`Custom Properties / Metadata`}</ContentHeading>\n {hasProperties ? (\n <DescriptionList alignTerms=\"right\" className=\"grid-cols-4\">\n {customProperties.map(([key, value]) => (\n <React.Fragment key={key}>\n <DescriptionTerm className=\"col-span-1\">{key}</DescriptionTerm>\n <DescriptionDefinition className=\"col-span-1\">\n {value === null || value === undefined ? (\n <span>null</span>\n ) : typeof value === \"object\" ? (\n <span className=\"break-all\">{JSON.stringify(value)}</span>\n ) : typeof value === \"boolean\" ? (\n value ? (\n t`True`\n ) : (\n t`False`\n )\n ) : (\n <span className=\"break-all\">{String(value)}</span>\n )}\n </DescriptionDefinition>\n </React.Fragment>\n ))}\n </DescriptionList>\n ) : (\n <p className=\"text-theme-light\">{t`No custom properties defined`}</p>\n )}\n </Container>\n )\n}\n\nconst getTabClassName = (active: boolean) => {\n const base = \"px-6 py-3 font-semibold border-b-2 transition-colors\"\n return active\n ? `${base} border-theme-accent text-theme-highest`\n : `${base} border-transparent text-theme-secondary hover:text-theme-high`\n}\n\nconst SharingDetailsTab: React.FC<ImageDetailsViewProps> = ({ image, permissions, currentProjectId }) => {\n const [isAddingMember, setIsAddingMember] = useState(false)\n const [message, setMessage] = useState<{ text: string; type: \"error\" | \"info\" } | null>(null)\n\n const { data: imageMembers, isLoading: isMembersLoading } = trpcReact.compute.listImageMembers.useQuery(\n { project_id: currentProjectId!, imageId: image.id },\n { enabled: !!image.id && !!currentProjectId }\n )\n\n return (\n <Container px={false} py>\n {message && (\n <Message text={message.text} variant={message.type} onDismiss={() => setMessage(null)} className=\"mb-4\" />\n )}\n <ImageMembersTable\n image={image}\n imageMembers={imageMembers}\n isMembersLoading={isMembersLoading}\n canAdd={permissions?.canCreateMember ?? false}\n canRemove={permissions?.canDeleteMember ?? false}\n isAddingMember={isAddingMember}\n setIsAddingMember={setIsAddingMember}\n setMessage={setMessage}\n projectId={currentProjectId!}\n />\n </Container>\n )\n}\n\nexport const ImageDetailsView: React.FC<ImageDetailsViewProps> = ({\n image,\n currentProjectId,\n activeTab = \"details\",\n onTabChange,\n permissions,\n myMemberData,\n onMemberStatusChange,\n isMemberStatusChanging,\n actions,\n}) => {\n const { t } = useLingui()\n\n const isSharedWithMe = image.visibility === \"shared\" && image.owner !== undefined && image.owner !== currentProjectId\n const isImageOwner = image.owner === currentProjectId\n const showTabs = isImageOwner && image.visibility === \"shared\"\n\n return (\n <Stack direction=\"vertical\" gap=\"6\">\n {isSharedWithMe && myMemberData && onMemberStatusChange && (\n <SharedImageBox\n image={image}\n myMemberData={myMemberData}\n canUpdateMember={permissions?.canUpdateMember ?? false}\n onStatusChange={onMemberStatusChange}\n isLoading={isMemberStatusChanging ?? false}\n />\n )}\n\n {showTabs && (\n <div className=\"border-theme-background-lvl-3 border-b\">\n <Stack direction=\"horizontal\" gap=\"0\">\n <button className={getTabClassName(activeTab === \"details\")} onClick={() => onTabChange?.(\"details\")}>\n {t`Details`}\n </button>\n <button className={getTabClassName(activeTab === \"sharing\")} onClick={() => onTabChange?.(\"sharing\")}>\n {t`Sharing Details`}\n </button>\n </Stack>\n </div>\n )}\n\n {(activeTab === \"details\" || !showTabs) && (\n <>\n {actions}\n <GeneralImageData image={image} />\n <SecuritySection image={image} currentProjectId={currentProjectId} />\n <CustomPropertiesSection image={image} />\n </>\n )}\n\n {activeTab === \"sharing\" && showTabs && <SharingDetailsTab image={image} permissions={permissions} />}\n </Stack>\n )\n}\n","import {\n Button,\n ButtonRow,\n Stack,\n Spinner,\n PopupMenu,\n PopupMenuToggle,\n PopupMenuOptions,\n PopupMenuItem,\n Toast,\n ToastProps,\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 { ImageDetailsView } from \"../-components/Images/-components/ImageDetailsView\"\nimport { EditImageDetailsModal } from \"../-components/Images/-components/EditImageDetailsModal\"\nimport { EditImageMetadataModal } from \"../-components/Images/-components/EditImageMetadataModal\"\nimport { DeleteImageModal } from \"../-components/Images/-components/DeleteImageModal\"\nimport { ActivateImageModal } from \"../-components/Images/-components/ActivateImageModal\"\nimport { DeactivateImageModal } from \"../-components/Images/-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} from \"../-components/Images/-components/ImageToastNotifications\"\nimport { useState } from \"react\"\nimport { ContentHeader } from \"@/client/components/ContentHeader/ContentHeader\"\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 isDetail: true,\n sectionCrumb: { labelKey: \"Compute\" },\n crumb: { labelKey: \"Images\", to: \"/projects/$projectId/compute/images\" },\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})\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 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 const [toastData, setToastData] = useState<ToastProps | null>(null)\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 setToastData(getImageAccessStatusUpdatedToast(newStatus, { onDismiss: () => setToastData(null) }))\n } catch (error) {\n const errorMessage = (error as TRPCClientError<InferrableClientTypes>)?.message\n setToastData(getImageAccessStatusErrorToast(errorMessage, { onDismiss: () => setToastData(null) }))\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 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 } catch {\n setActivateModalOpen(false)\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 } catch {\n setDeactivateModalOpen(false)\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 } catch {\n // error handled by mutation state\n }\n }\n\n // Handle loading state\n if (status === \"pending\") {\n return (\n <Stack className=\"fixed inset-0\" distribution=\"center\" alignment=\"center\" direction=\"vertical\">\n <Spinner variant=\"primary\" size=\"large\" className=\"mb-2\" />\n <Trans>Loading Image Details...</Trans>\n </Stack>\n )\n }\n\n // Handle error state\n if (status === \"error\") {\n const errorMessage = error?.message || \"Unknown error\"\n\n return (\n <Stack className=\"fixed inset-0\" distribution=\"center\" alignment=\"center\" direction=\"vertical\" gap=\"5\">\n <p className=\"text-theme-error font-semibold\">\n <Trans>Error loading image</Trans>\n </p>\n <p className=\"text-theme-highest\">{errorMessage}</p>\n <Button onClick={handleBack} variant=\"primary\">\n <Trans>Back to Images</Trans>\n </Button>\n </Stack>\n )\n }\n\n // Handle no data state\n if (!image) {\n return (\n <Stack className=\"fixed inset-0\" distribution=\"center\" alignment=\"center\" direction=\"vertical\" gap=\"5\">\n <p className=\"text-theme-highest\">\n <Trans>Image not found</Trans>\n </p>\n <Button onClick={handleBack} variant=\"primary\">\n <Trans>Back to Images</Trans>\n </Button>\n </Stack>\n )\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 =\n hasMoreActions || (!isSharedWithMe && permissions.canUpdate) ? (\n <ButtonRow>\n {hasMoreActions && (\n <PopupMenu>\n <PopupMenuToggle as=\"div\">\n <Button icon=\"moreVert\" disabled={isLoading}>\n <Trans>More Actions</Trans>\n </Button>\n </PopupMenuToggle>\n <PopupMenuOptions>\n {canRejectSharedImage && (\n <PopupMenuItem label={t`Reject`} onClick={() => handleMemberStatusChange(\"rejected\")} />\n )}\n {!isSharedWithMe && permissions.canUpdate && (\n <PopupMenuItem\n label={isDeactivated ? t`Activate` : t`Deactivate`}\n onClick={() => (isDeactivated ? setActivateModalOpen(true) : setDeactivateModalOpen(true))}\n />\n )}\n {!isSharedWithMe && permissions.canUpdate && isPrivate && (\n <PopupMenuItem label={t`Set to \"Shared\"`} onClick={() => handleUpdateVisibility(\"shared\")} />\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 {!isSharedWithMe && permissions.canDelete && !image.protected && (\n <PopupMenuItem label={t`Delete`} onClick={() => setDeleteModalOpen(true)} />\n )}\n </PopupMenuOptions>\n </PopupMenu>\n )}\n {!isSharedWithMe && permissions.canUpdate && (\n <Button onClick={() => setEditMetadataModalOpen(true)} disabled={isLoading}>\n <Trans>Edit Metadata</Trans>\n </Button>\n )}\n {!isSharedWithMe && permissions.canUpdate && (\n <Button onClick={() => setEditDetailsModalOpen(true)} variant=\"primary\" disabled={isLoading}>\n <Trans>Edit Details</Trans>\n </Button>\n )}\n </ButtonRow>\n ) : undefined\n\n // Render success state\n return (\n <>\n <ContentHeader title={String(image.name ?? image.id)} projectId={projectId} actions={headerActions} />\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 {toastData && <Toast {...toastData} />}\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":"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"}
@@ -2,8 +2,8 @@ import { createFileRoute as e, lazyRouteComponent as t } from "@tanstack/react-r
2
2
  import { TRPCClientError as n } from "@trpc/client";
3
3
  //#region src/client/routes/_auth/projects/-components/resolveProjectScope.ts
4
4
  var r = (e) => e.scopeProject?.id === e.projectId ? "valid" : e.userProject == null || e.userProject.id === e.projectId ? "scope_failed" : "not_found", i = e("/_auth/projects/$projectId")({
5
- component: t(() => import("./_projectId-DJLjspKf.mjs"), "component"),
6
- errorComponent: t(() => import("./_projectId-DxgPaiA7.mjs"), "errorComponent"),
5
+ component: t(() => import("./_projectId-hMtrqW6m.mjs"), "component"),
6
+ errorComponent: t(() => import("./_projectId-MjFv839t.mjs"), "errorComponent"),
7
7
  loader: async (e) => {
8
8
  let { context: t, params: i } = e, a;
9
9
  try {
@@ -43,4 +43,4 @@ var r = (e) => e.scopeProject?.id === e.projectId ? "valid" : e.userProject == n
43
43
  //#endregion
44
44
  export { i as t };
45
45
 
46
- //# sourceMappingURL=_projectId-B4ScifQf.mjs.map
46
+ //# sourceMappingURL=_projectId-CoMD6nq6.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"_projectId-B4ScifQf.mjs","names":["resolveProjectScope","input","scopeProject","id","projectId","userProject","createFileRoute","TRPCClientError","resolveProjectScope","Route","component","lazyRouteComponent","$$splitComponentImporter","errorComponent","$$splitErrorComponentImporter","loader","options","context","params","scopeData","trpcClient","auth","setCurrentScope","mutate","type","projectId","error","data","code","undefined","availableServices","project","Promise","all","getAvailableServices","query","getProject","catch","projectScopeStatus","scopeProject","userProject","Error","statusCode","accountId","domain","id","description","crumbDomain","path","name","crumbProject"],"sources":["../../src/client/routes/_auth/projects/-components/resolveProjectScope.ts","../../src/client/routes/_auth/projects/$projectId.tsx"],"sourcesContent":["export type ProjectScopeStatus = \"valid\" | \"not_found\" | \"scope_failed\"\n\n/**\n * Determines the status of a project scope operation by comparing the requested project\n * against the successfully scoped project and the user's available projects.\n *\n * @param input - The scope resolution input\n * @param input.projectId - The project ID that was requested to be scoped\n * @param input.scopeProject - The project object returned from the scope operation (null/undefined if scope failed)\n * @param input.userProject - The project fetched by ID (null/undefined if not found or couldn't be fetched)\n *\n * @returns ProjectScopeStatus\n * - \"valid\": The scope operation succeeded and the scoped project matches the requested project ID\n * - \"not_found\": The project doesn't exist or the user doesn't have access to it (only when userProject was successfully fetched)\n * - \"scope_failed\": The project exists but the scope operation failed, OR we couldn't fetch the project record\n *\n * @example\n * // Successful scope\n * resolveProjectScope({\n * projectId: \"project-123\",\n * scopeProject: { id: \"project-123\" },\n * userProject: { id: \"project-123\" }\n * }) // returns \"valid\"\n *\n * @example\n * // Project not found (when we successfully fetched the project record)\n * resolveProjectScope({\n * projectId: \"project-999\",\n * scopeProject: undefined,\n * userProject: { id: \"project-123\" }\n * }) // returns \"not_found\"\n *\n * @example\n * // Scope operation failed for an accessible project\n * resolveProjectScope({\n * projectId: \"project-123\",\n * scopeProject: undefined,\n * userProject: { id: \"project-123\" }\n * }) // returns \"scope_failed\"\n *\n * @example\n * // Can't verify project access (userProject fetch failed)\n * resolveProjectScope({\n * projectId: \"project-123\",\n * scopeProject: undefined,\n * userProject: null\n * }) // returns \"scope_failed\"\n */\nexport const resolveProjectScope = (input: {\n projectId: string\n scopeProject: { id?: string } | null | undefined\n userProject: { id: string } | null | undefined\n}): ProjectScopeStatus => {\n if (input.scopeProject?.id === input.projectId) {\n return \"valid\"\n }\n\n // If we couldn't fetch userProject at all, we can't verify the project's existence.\n // Treat this as a scope failure rather than \"not_found\" to avoid misleading 404 errors\n // when the real issue is a backend/network failure.\n if (input.userProject == null) {\n return \"scope_failed\"\n }\n\n return input.userProject.id === input.projectId ? \"scope_failed\" : \"not_found\"\n}\n","import { createFileRoute, Outlet, useLoaderData, useRouteContext, useRouter } from \"@tanstack/react-router\"\nimport { AppShell, Container, Stack } from \"@cloudoperators/juno-ui-components\"\nimport { SideNavBar } from \"@/client/routes/_auth/projects/-components/SideNavBar\"\nimport { buildNavSections } from \"@/client/routes/_auth/projects/-components/buildNavSections\"\nimport { ProjectInfoBox } from \"@/client/components/ProjectView/ProjectInfoBox\"\nimport { RouteError } from \"@/client/components/Error/RouteError\"\nimport { useMemo } from \"react\"\nimport { TRPCClientError } from \"@trpc/client\"\nimport { resolveProjectScope } from \"./-components/resolveProjectScope\"\nimport { StatusError } from \"@/client/components/Error/StatusError\"\nimport { useLingui } from \"@lingui/react/macro\"\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 let scopeData\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 && error.data?.code === \"NOT_FOUND\") {\n scopeData = undefined\n } else {\n throw error\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 projectScopeStatus = resolveProjectScope({\n projectId: params.projectId,\n scopeProject: scopeData?.project,\n userProject: project,\n })\n\n if (projectScopeStatus === \"scope_failed\") {\n throw new Error(\"Failed to scope session to project\")\n }\n\n if (projectScopeStatus === \"not_found\") {\n const error = new Error(\"Project not found\") as Error & { statusCode?: number }\n error.statusCode = 404\n throw error\n }\n\n const accountId = scopeData?.domain?.id || \"\"\n const description = project?.description ?? null\n\n return {\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 { availableServices, projectId, crumbProject, crumbDomain } = useLoaderData({\n from: Route.id,\n })\n const { enabledServices } = useRouteContext({ strict: false })\n\n const sections = useMemo(\n () => buildNavSections(projectId, availableServices!, enabledServices),\n [projectId, availableServices, enabledServices]\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 <ProjectInfoBox\n projectInfo={{\n id: projectId,\n name: crumbProject?.name || projectId,\n domain: crumbProject?.domain,\n }}\n />\n <Outlet />\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 const router = useRouter()\n\n // Handle 404 errors with a custom UI\n if (error.statusCode === 404 || error.message === \"Project not found\") {\n return (\n <StatusError\n statusCode={404}\n title={t`Project Not Found`}\n message={t`The project you are looking for doesn't exist or you don't have access to it.`}\n onHomeClick={() => {\n navigate({ to: \"/projects\" })\n }}\n onBackClick={() => {\n router.history.back()\n }}\n />\n )\n }\n\n // For all other errors, use the default RouteError component\n return <RouteError error={error} />\n}\n"],"mappings":";;;AAgDA,IAAaA,KAAuBC,MAK9BA,EAAMC,cAAcC,OAAOF,EAAMG,YAC5B,UAMLH,EAAMI,eAAe,QAIlBJ,EAAMI,YAAYF,OAAOF,EAAMG,YAH7B,iBAG0D,aCpDxDK,IAAQH,EAAgB,4BAAA,EAA8B;CACjEI,WAASC,6CAAA,WAAA;CACTE,gBAAcF,6CAAA,gBAAA;CACdI,QAAQ,OAAOC,MAAAA;EACb,IAAM,EAAEC,YAASC,cAAWF,GAExBG;EAEJ,IAAI;GACFA,IAAY,MAAMF,EAAQG,YAAYC,KAAKC,gBAAgBC,OAAO;IAChEC,MAAM;IACNC,WAAWP,EAAOO,aAAa;GACjC,CAAA;EACF,SAASC,GAAO;GACd,IAAIA,aAAiBnB,KAAmBmB,EAAMC,MAAMC,SAAS,aAC3DT,IAAYU,KAAAA;QAEZ,MAAMH;EAEV;EAEA,IAAM,CAACI,GAAmBC,KAAW,MAAMC,QAAQC,IAAI,CACrDhB,EAAQG,YAAYC,KAAKa,qBAAqBC,MAAAA,GAC9ClB,EAAQG,YAAYW,QAAQK,WAAWD,MAAM,EAAEV,WAAWP,EAAOO,UAAU,CAAA,EAAGY,YAAY,IAAA,CAAA,CAC3F,GAEKC,IAAqB9B,EAAoB;GAC7CiB,WAAWP,EAAOO;GAClBc,cAAcpB,GAAWY;GACzBS,aAAaT;EACf,CAAA;EAEA,IAAIO,MAAuB,gBACzB,MAAUG,MAAM,oCAAA;EAGlB,IAAIH,MAAuB,aAAa;GACtC,IAAMZ,IAAQ,gBAAIe,MAAM,mBAAA;GAExB,MADAf,EAAMgB,aAAa,KACbhB;EACR;EAEA,IAAMiB,IAAYxB,GAAWyB,QAAQC,MAAM,IACrCC,IAAcf,GAASe,eAAe;EAE5C,OAAO;GACL1B,YAAYH,EAAQG;GACpB2B,aAAa;IAAEC,MAAM;IAAaC,MAAM9B,GAAWyB,QAAQK;GAAK;GAChEC,cAAc/B,GAAWY;GACzBD;GACAa;GACAlB,WAAWP,EAAOO;GAClBqB;EACF;CACF;AACF,CAAA"}
1
+ {"version":3,"file":"_projectId-CoMD6nq6.mjs","names":["resolveProjectScope","input","scopeProject","id","projectId","userProject","createFileRoute","TRPCClientError","resolveProjectScope","Route","component","lazyRouteComponent","$$splitComponentImporter","errorComponent","$$splitErrorComponentImporter","loader","options","context","params","scopeData","trpcClient","auth","setCurrentScope","mutate","type","projectId","error","data","code","undefined","availableServices","project","Promise","all","getAvailableServices","query","getProject","catch","projectScopeStatus","scopeProject","userProject","Error","statusCode","accountId","domain","id","description","crumbDomain","path","name","crumbProject"],"sources":["../../src/client/routes/_auth/projects/-components/resolveProjectScope.ts","../../src/client/routes/_auth/projects/$projectId.tsx"],"sourcesContent":["export type ProjectScopeStatus = \"valid\" | \"not_found\" | \"scope_failed\"\n\n/**\n * Determines the status of a project scope operation by comparing the requested project\n * against the successfully scoped project and the user's available projects.\n *\n * @param input - The scope resolution input\n * @param input.projectId - The project ID that was requested to be scoped\n * @param input.scopeProject - The project object returned from the scope operation (null/undefined if scope failed)\n * @param input.userProject - The project fetched by ID (null/undefined if not found or couldn't be fetched)\n *\n * @returns ProjectScopeStatus\n * - \"valid\": The scope operation succeeded and the scoped project matches the requested project ID\n * - \"not_found\": The project doesn't exist or the user doesn't have access to it (only when userProject was successfully fetched)\n * - \"scope_failed\": The project exists but the scope operation failed, OR we couldn't fetch the project record\n *\n * @example\n * // Successful scope\n * resolveProjectScope({\n * projectId: \"project-123\",\n * scopeProject: { id: \"project-123\" },\n * userProject: { id: \"project-123\" }\n * }) // returns \"valid\"\n *\n * @example\n * // Project not found (when we successfully fetched the project record)\n * resolveProjectScope({\n * projectId: \"project-999\",\n * scopeProject: undefined,\n * userProject: { id: \"project-123\" }\n * }) // returns \"not_found\"\n *\n * @example\n * // Scope operation failed for an accessible project\n * resolveProjectScope({\n * projectId: \"project-123\",\n * scopeProject: undefined,\n * userProject: { id: \"project-123\" }\n * }) // returns \"scope_failed\"\n *\n * @example\n * // Can't verify project access (userProject fetch failed)\n * resolveProjectScope({\n * projectId: \"project-123\",\n * scopeProject: undefined,\n * userProject: null\n * }) // returns \"scope_failed\"\n */\nexport const resolveProjectScope = (input: {\n projectId: string\n scopeProject: { id?: string } | null | undefined\n userProject: { id: string } | null | undefined\n}): ProjectScopeStatus => {\n if (input.scopeProject?.id === input.projectId) {\n return \"valid\"\n }\n\n // If we couldn't fetch userProject at all, we can't verify the project's existence.\n // Treat this as a scope failure rather than \"not_found\" to avoid misleading 404 errors\n // when the real issue is a backend/network failure.\n if (input.userProject == null) {\n return \"scope_failed\"\n }\n\n return input.userProject.id === input.projectId ? \"scope_failed\" : \"not_found\"\n}\n","import { createFileRoute, Outlet, useLoaderData, useRouteContext, useRouter } from \"@tanstack/react-router\"\nimport { AppShell, Container, Stack } from \"@cloudoperators/juno-ui-components\"\nimport { SideNavBar } from \"@/client/routes/_auth/projects/-components/SideNavBar\"\nimport { buildNavSections } from \"@/client/routes/_auth/projects/-components/buildNavSections\"\nimport { ProjectInfoBox } from \"@/client/components/ProjectView/ProjectInfoBox\"\nimport { RouteError } from \"@/client/components/Error/RouteError\"\nimport { useMemo } from \"react\"\nimport { TRPCClientError } from \"@trpc/client\"\nimport { resolveProjectScope } from \"./-components/resolveProjectScope\"\nimport { StatusError } from \"@/client/components/Error/StatusError\"\nimport { useLingui } from \"@lingui/react/macro\"\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 let scopeData\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 && error.data?.code === \"NOT_FOUND\") {\n scopeData = undefined\n } else {\n throw error\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 projectScopeStatus = resolveProjectScope({\n projectId: params.projectId,\n scopeProject: scopeData?.project,\n userProject: project,\n })\n\n if (projectScopeStatus === \"scope_failed\") {\n throw new Error(\"Failed to scope session to project\")\n }\n\n if (projectScopeStatus === \"not_found\") {\n const error = new Error(\"Project not found\") as Error & { statusCode?: number }\n error.statusCode = 404\n throw error\n }\n\n const accountId = scopeData?.domain?.id || \"\"\n const description = project?.description ?? null\n\n return {\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 { availableServices, projectId, crumbProject, crumbDomain } = useLoaderData({\n from: Route.id,\n })\n const { enabledServices } = useRouteContext({ strict: false })\n\n const sections = useMemo(\n () => buildNavSections(projectId, availableServices!, enabledServices),\n [projectId, availableServices, enabledServices]\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 <ProjectInfoBox\n projectInfo={{\n id: projectId,\n name: crumbProject?.name || projectId,\n domain: crumbProject?.domain,\n }}\n />\n <Outlet />\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 const router = useRouter()\n\n // Handle 404 errors with a custom UI\n if (error.statusCode === 404 || error.message === \"Project not found\") {\n return (\n <StatusError\n statusCode={404}\n title={t`Project Not Found`}\n message={t`The project you are looking for doesn't exist or you don't have access to it.`}\n onHomeClick={() => {\n navigate({ to: \"/projects\" })\n }}\n onBackClick={() => {\n router.history.back()\n }}\n />\n )\n }\n\n // For all other errors, use the default RouteError component\n return <RouteError error={error} />\n}\n"],"mappings":";;;AAgDA,IAAaA,KAAuBC,MAK9BA,EAAMC,cAAcC,OAAOF,EAAMG,YAC5B,UAMLH,EAAMI,eAAe,QAIlBJ,EAAMI,YAAYF,OAAOF,EAAMG,YAH7B,iBAG0D,aCpDxDK,IAAQH,EAAgB,4BAAA,EAA8B;CACjEI,WAASC,6CAAA,WAAA;CACTE,gBAAcF,6CAAA,gBAAA;CACdI,QAAQ,OAAOC,MAAAA;EACb,IAAM,EAAEC,YAASC,cAAWF,GAExBG;EAEJ,IAAI;GACFA,IAAY,MAAMF,EAAQG,YAAYC,KAAKC,gBAAgBC,OAAO;IAChEC,MAAM;IACNC,WAAWP,EAAOO,aAAa;GACjC,CAAA;EACF,SAASC,GAAO;GACd,IAAIA,aAAiBnB,KAAmBmB,EAAMC,MAAMC,SAAS,aAC3DT,IAAYU,KAAAA;QAEZ,MAAMH;EAEV;EAEA,IAAM,CAACI,GAAmBC,KAAW,MAAMC,QAAQC,IAAI,CACrDhB,EAAQG,YAAYC,KAAKa,qBAAqBC,MAAAA,GAC9ClB,EAAQG,YAAYW,QAAQK,WAAWD,MAAM,EAAEV,WAAWP,EAAOO,UAAU,CAAA,EAAGY,YAAY,IAAA,CAAA,CAC3F,GAEKC,IAAqB9B,EAAoB;GAC7CiB,WAAWP,EAAOO;GAClBc,cAAcpB,GAAWY;GACzBS,aAAaT;EACf,CAAA;EAEA,IAAIO,MAAuB,gBACzB,MAAUG,MAAM,oCAAA;EAGlB,IAAIH,MAAuB,aAAa;GACtC,IAAMZ,IAAQ,gBAAIe,MAAM,mBAAA;GAExB,MADAf,EAAMgB,aAAa,KACbhB;EACR;EAEA,IAAMiB,IAAYxB,GAAWyB,QAAQC,MAAM,IACrCC,IAAcf,GAASe,eAAe;EAE5C,OAAO;GACL1B,YAAYH,EAAQG;GACpB2B,aAAa;IAAEC,MAAM;IAAaC,MAAM9B,GAAWyB,QAAQK;GAAK;GAChEC,cAAc/B,GAAWY;GACzBD;GACAa;GACAlB,WAAWP,EAAOO;GAClBqB;EACF;CACF;AACF,CAAA"}
@@ -1,8 +1,8 @@
1
1
  import { f as e, tt as t, x as n } from "./build-DgeVmENN.mjs";
2
- import { n as r } from "./routeInfo-Ct5r4Gen.mjs";
2
+ import { t as r } from "./Slot-CWb612-_.mjs";
3
3
  import { t as i } from "./helpers-1PpYf-fC.mjs";
4
4
  import { t as a } from "./pcaAccess-Br4kVjLQ.mjs";
5
- import { t as o } from "./ContentHeader-CIAzorGI.mjs";
5
+ import { t as o } from "./ContentHeader-BIScbvb4.mjs";
6
6
  import { jsx as s, jsxs as c } from "react/jsx-runtime";
7
7
  import { useLoaderData as l, useNavigate as u, useRouteContext as d } from "@tanstack/react-router";
8
8
  import { Trans as f, useLingui as p } from "@lingui/react";
@@ -95,4 +95,4 @@ function h() {
95
95
  //#endregion
96
96
  export { h as component };
97
97
 
98
- //# sourceMappingURL=_projectId-CYq4KZXB.mjs.map
98
+ //# sourceMappingURL=_projectId-D8FHe0gr.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"_projectId-CYq4KZXB.mjs","names":["useLoaderData","useNavigate","Card","Icon","Stack","getServiceIndex","Trans","useLingui","useRouteContext","ContentHeader","Slot","canAccessClavisPca","Route","ServiceCardProps","group","label","to","service","ServiceCard","navigate","slots","strict","serviceBadge","RouteComponent","crumbProject","availableServices","projectId","description","from","t","enabledServices","isEnabled","includes","serviceIndex","base","cards","push","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, Icon, Stack } from \"@cloudoperators/juno-ui-components\"\nimport { getServiceIndex } from \"@/server/Authentication/helpers\"\nimport { Trans } from \"@lingui/react/macro\"\nimport { useLingui } from \"@lingui/react/macro\"\nimport { useRouteContext } from \"@tanstack/react-router\"\nimport { ContentHeader } from \"@/client/components/ContentHeader/ContentHeader\"\nimport { Slot } from \"@/client/components/Slot\"\nimport type { RouteInfo } from \"@/client/routes/routeInfo\"\nimport { canAccessClavisPca } from \"@/client/routes/_auth/projects/$projectId/services/pca/-components/pcaAccess\"\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 <div className=\"flex items-center gap-1\">\n <p className=\"text-theme-light text-xs leading-5 font-medium\">{group}</p>\n <Icon icon=\"expandMore\" size=\"16\" className=\"text-theme-high\" />\n </div>\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 } = 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 if (canAccessClavisPca(serviceIndex, enabledServices)) {\n cards.push({ group: t`Services`, label: t`PCA (Clavis)`, to: `${base}/services/pca`, service: \"pca\" })\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
+ {"version":3,"file":"_projectId-D8FHe0gr.mjs","names":["useLoaderData","useNavigate","Card","Icon","Stack","getServiceIndex","Trans","useLingui","useRouteContext","ContentHeader","Slot","canAccessClavisPca","Route","ServiceCardProps","group","label","to","service","ServiceCard","navigate","slots","strict","serviceBadge","RouteComponent","crumbProject","availableServices","projectId","description","from","t","enabledServices","isEnabled","includes","serviceIndex","base","cards","push","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, Icon, Stack } from \"@cloudoperators/juno-ui-components\"\nimport { getServiceIndex } from \"@/server/Authentication/helpers\"\nimport { Trans } from \"@lingui/react/macro\"\nimport { useLingui } from \"@lingui/react/macro\"\nimport { useRouteContext } from \"@tanstack/react-router\"\nimport { ContentHeader } from \"@/client/components/ContentHeader/ContentHeader\"\nimport { Slot } from \"@/client/components/Slot\"\nimport type { RouteInfo } from \"@/client/routes/routeInfo\"\nimport { canAccessClavisPca } from \"@/client/routes/_auth/projects/$projectId/services/pca/-components/pcaAccess\"\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 <div className=\"flex items-center gap-1\">\n <p className=\"text-theme-light text-xs leading-5 font-medium\">{group}</p>\n <Icon icon=\"expandMore\" size=\"16\" className=\"text-theme-high\" />\n </div>\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 } = 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 if (canAccessClavisPca(serviceIndex, enabledServices)) {\n cards.push({ group: t`Services`, label: t`PCA (Clavis)`, to: `${base}/services/pca`, service: \"pca\" })\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,6 +1,6 @@
1
1
  import { t as e } from "./RouteError-BmZRk_ss.mjs";
2
2
  import { t } from "./StatusError-CLB026fg.mjs";
3
- import { t as n } from "./_projectId-B4ScifQf.mjs";
3
+ import { t as n } from "./_projectId-CoMD6nq6.mjs";
4
4
  import { jsx as r } from "react/jsx-runtime";
5
5
  import { useRouter as i } from "@tanstack/react-router";
6
6
  import { useLingui as a } from "@lingui/react";
@@ -22,4 +22,4 @@ function o({ error: o }) {
22
22
  //#endregion
23
23
  export { o as errorComponent };
24
24
 
25
- //# sourceMappingURL=_projectId-DxgPaiA7.mjs.map
25
+ //# sourceMappingURL=_projectId-MjFv839t.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"_projectId-DxgPaiA7.mjs","names":["useRouter","RouteError","StatusError","useLingui","Route","ProjectErrorComponent","error","Error","statusCode","t","navigate","useNavigate","router","message","to","history","back","errorComponent"],"sources":["../../src/client/routes/_auth/projects/$projectId.tsx?tsr-split=errorComponent"],"sourcesContent":["import { createFileRoute, Outlet, useLoaderData, useRouteContext, useRouter } from \"@tanstack/react-router\"\nimport { AppShell, Container, Stack } from \"@cloudoperators/juno-ui-components\"\nimport { SideNavBar } from \"@/client/routes/_auth/projects/-components/SideNavBar\"\nimport { buildNavSections } from \"@/client/routes/_auth/projects/-components/buildNavSections\"\nimport { ProjectInfoBox } from \"@/client/components/ProjectView/ProjectInfoBox\"\nimport { RouteError } from \"@/client/components/Error/RouteError\"\nimport { useMemo } from \"react\"\nimport { TRPCClientError } from \"@trpc/client\"\nimport { resolveProjectScope } from \"./-components/resolveProjectScope\"\nimport { StatusError } from \"@/client/components/Error/StatusError\"\nimport { useLingui } from \"@lingui/react/macro\"\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 let scopeData\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 && error.data?.code === \"NOT_FOUND\") {\n scopeData = undefined\n } else {\n throw error\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 projectScopeStatus = resolveProjectScope({\n projectId: params.projectId,\n scopeProject: scopeData?.project,\n userProject: project,\n })\n\n if (projectScopeStatus === \"scope_failed\") {\n throw new Error(\"Failed to scope session to project\")\n }\n\n if (projectScopeStatus === \"not_found\") {\n const error = new Error(\"Project not found\") as Error & { statusCode?: number }\n error.statusCode = 404\n throw error\n }\n\n const accountId = scopeData?.domain?.id || \"\"\n const description = project?.description ?? null\n\n return {\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 { availableServices, projectId, crumbProject, crumbDomain } = useLoaderData({\n from: Route.id,\n })\n const { enabledServices } = useRouteContext({ strict: false })\n\n const sections = useMemo(\n () => buildNavSections(projectId, availableServices!, enabledServices),\n [projectId, availableServices, enabledServices]\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 <ProjectInfoBox\n projectInfo={{\n id: projectId,\n name: crumbProject?.name || projectId,\n domain: crumbProject?.domain,\n }}\n />\n <Outlet />\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 const router = useRouter()\n\n // Handle 404 errors with a custom UI\n if (error.statusCode === 404 || error.message === \"Project not found\") {\n return (\n <StatusError\n statusCode={404}\n title={t`Project Not Found`}\n message={t`The project you are looking for doesn't exist or you don't have access to it.`}\n onHomeClick={() => {\n navigate({ to: \"/projects\" })\n }}\n onBackClick={() => {\n router.history.back()\n }}\n />\n )\n }\n\n // For all other errors, use the default RouteError component\n return <RouteError error={error} />\n}\n"],"mappings":";;;;;;;AAgHA,SAASK,EAAsB,EAAEC,YAAmD;CAClF,IAAM,EAAA,MAAA,GAAA,GAAA,MAAQH,EAAAA,GACRO,IAAWN,EAAMO,YAAW,GAC5BC,IAASZ,EAAAA;CAoBf,OAjBIM,EAAME,eAAe,OAAOF,EAAMO,YAAY,sBAE9C,gBAAC,GAAA;EACC,YAAY;EACZ,OAAOJ,EAAAA,EAAC,EAAA,IAAA,SAAA,CAAA;EACR,SAASA,EAAAA,EAAC,EAAA,IAAA,SAAA,CAAA;EACV,mBAAa;GACXC,EAAS,EAAEI,IAAI,YAAY,CAAA;EAC7B;EACA,mBAAa;GACXF,EAAOG,QAAQC,KAAI;EACrB;MAMC,gBAAC,GAAA,EAAkBV,SAAAA,CAAAA;AAC5B"}
1
+ {"version":3,"file":"_projectId-MjFv839t.mjs","names":["useRouter","RouteError","StatusError","useLingui","Route","ProjectErrorComponent","error","Error","statusCode","t","navigate","useNavigate","router","message","to","history","back","errorComponent"],"sources":["../../src/client/routes/_auth/projects/$projectId.tsx?tsr-split=errorComponent"],"sourcesContent":["import { createFileRoute, Outlet, useLoaderData, useRouteContext, useRouter } from \"@tanstack/react-router\"\nimport { AppShell, Container, Stack } from \"@cloudoperators/juno-ui-components\"\nimport { SideNavBar } from \"@/client/routes/_auth/projects/-components/SideNavBar\"\nimport { buildNavSections } from \"@/client/routes/_auth/projects/-components/buildNavSections\"\nimport { ProjectInfoBox } from \"@/client/components/ProjectView/ProjectInfoBox\"\nimport { RouteError } from \"@/client/components/Error/RouteError\"\nimport { useMemo } from \"react\"\nimport { TRPCClientError } from \"@trpc/client\"\nimport { resolveProjectScope } from \"./-components/resolveProjectScope\"\nimport { StatusError } from \"@/client/components/Error/StatusError\"\nimport { useLingui } from \"@lingui/react/macro\"\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 let scopeData\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 && error.data?.code === \"NOT_FOUND\") {\n scopeData = undefined\n } else {\n throw error\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 projectScopeStatus = resolveProjectScope({\n projectId: params.projectId,\n scopeProject: scopeData?.project,\n userProject: project,\n })\n\n if (projectScopeStatus === \"scope_failed\") {\n throw new Error(\"Failed to scope session to project\")\n }\n\n if (projectScopeStatus === \"not_found\") {\n const error = new Error(\"Project not found\") as Error & { statusCode?: number }\n error.statusCode = 404\n throw error\n }\n\n const accountId = scopeData?.domain?.id || \"\"\n const description = project?.description ?? null\n\n return {\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 { availableServices, projectId, crumbProject, crumbDomain } = useLoaderData({\n from: Route.id,\n })\n const { enabledServices } = useRouteContext({ strict: false })\n\n const sections = useMemo(\n () => buildNavSections(projectId, availableServices!, enabledServices),\n [projectId, availableServices, enabledServices]\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 <ProjectInfoBox\n projectInfo={{\n id: projectId,\n name: crumbProject?.name || projectId,\n domain: crumbProject?.domain,\n }}\n />\n <Outlet />\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 const router = useRouter()\n\n // Handle 404 errors with a custom UI\n if (error.statusCode === 404 || error.message === \"Project not found\") {\n return (\n <StatusError\n statusCode={404}\n title={t`Project Not Found`}\n message={t`The project you are looking for doesn't exist or you don't have access to it.`}\n onHomeClick={() => {\n navigate({ to: \"/projects\" })\n }}\n onBackClick={() => {\n router.history.back()\n }}\n />\n )\n }\n\n // For all other errors, use the default RouteError component\n return <RouteError error={error} />\n}\n"],"mappings":";;;;;;;AAgHA,SAASK,EAAsB,EAAEC,YAAmD;CAClF,IAAM,EAAA,MAAA,GAAA,GAAA,MAAQH,EAAAA,GACRO,IAAWN,EAAMO,YAAW,GAC5BC,IAASZ,EAAAA;CAoBf,OAjBIM,EAAME,eAAe,OAAOF,EAAMO,YAAY,sBAE9C,gBAAC,GAAA;EACC,YAAY;EACZ,OAAOJ,EAAAA,EAAC,EAAA,IAAA,SAAA,CAAA;EACR,SAASA,EAAAA,EAAC,EAAA,IAAA,SAAA,CAAA;EACV,mBAAa;GACXC,EAAS,EAAEI,IAAI,YAAY,CAAA;EAC7B;EACA,mBAAa;GACXF,EAAOG,QAAQC,KAAI;EACrB;MAMC,gBAAC,GAAA,EAAkBV,SAAAA,CAAAA;AAC5B"}
@@ -1,8 +1,9 @@
1
1
  import { Q as e, T as t, Z as n, _ as r, i, j as a, nt as o, t as s, tt as c, z as l } from "./build-DgeVmENN.mjs";
2
- import { n as u, t as d } from "./routeInfo-Ct5r4Gen.mjs";
3
- import { t as f } from "./_projectId-B4ScifQf.mjs";
4
- import { t as p } from "./helpers-1PpYf-fC.mjs";
5
- import { t as m } from "./pcaAccess-Br4kVjLQ.mjs";
2
+ import { t as u } from "./Slot-CWb612-_.mjs";
3
+ import { t as d } from "./_projectId-CoMD6nq6.mjs";
4
+ import { t as f } from "./helpers-1PpYf-fC.mjs";
5
+ import { t as p } from "./pcaAccess-Br4kVjLQ.mjs";
6
+ import { t as m } from "./routeInfo-CdTRlSxv.mjs";
6
7
  import { Fragment as h, jsx as g, jsxs as _ } from "react/jsx-runtime";
7
8
  import { useEffect as v, useMemo as y, useState as b } from "react";
8
9
  import { Outlet as x, useLoaderData as S, useMatches as C, useNavigate as w, useParams as T, useRouteContext as E } from "@tanstack/react-router";
@@ -10,11 +11,11 @@ import { useLingui as D } from "@lingui/react";
10
11
  import { i18n as O } from "@lingui/core";
11
12
  //#region src/client/routes/_auth/projects/-components/SideNavBar.tsx
12
13
  var k = ({ projectId: e, projectName: n, domainName: o, sections: c }) => {
13
- let l = w(), f = C(), { provider: p } = T({ strict: !1 }), { slots: m } = E({ strict: !1 }), y = (e) => m?.serviceBadge ? /*#__PURE__*/ g(u, {
14
- component: m.serviceBadge,
14
+ let l = w(), d = C(), { provider: f } = T({ strict: !1 }), { slots: p } = E({ strict: !1 }), y = (e) => p?.serviceBadge ? /*#__PURE__*/ g(u, {
15
+ component: p.serviceBadge,
15
16
  useShadowDOM: !1,
16
17
  currentService: e
17
- }) : null, x = [...f].reverse().find((e) => d(e.staticData)), S = x && d(x.staticData) ? x.staticData : void 0, D = S?.section ?? null, O = S?.service ?? null, [k, A] = b(() => Object.fromEntries(c.map((e) => [e.section, 0])));
18
+ }) : null, x = [...d].reverse().find((e) => m(e.staticData)), S = x && m(x.staticData) ? x.staticData : void 0, D = S?.section ?? null, O = S?.service ?? null, [k, A] = b(() => Object.fromEntries(c.map((e) => [e.section, 0])));
18
19
  return v(() => {
19
20
  D && A((e) => ({
20
21
  ...e,
@@ -41,7 +42,7 @@ var k = ({ projectId: e, projectName: n, domainName: o, sections: c }) => {
41
42
  label: t,
42
43
  open: !0,
43
44
  children: n.map((t) => {
44
- let n = D === e && (D === "storage" && O === "containers" ? t.params.provider === p : O === t.service);
45
+ let n = D === e && (D === "storage" && O === "containers" ? t.params.provider === f : O === t.service);
45
46
  return /*#__PURE__*/ g(r, {
46
47
  onClick: () => t.navigate(l),
47
48
  label: /*#__PURE__*/ _("span", {
@@ -52,13 +53,13 @@ var k = ({ projectId: e, projectName: n, domainName: o, sections: c }) => {
52
53
  }, t.service);
53
54
  })
54
55
  }, `${e}-${k[e]}`))
55
- ] }) }), m?.sideNavBanner && /*#__PURE__*/ g(u, { component: m.sideNavBanner })] })
56
+ ] }) }), p?.sideNavBanner && /*#__PURE__*/ g(u, { component: p.sideNavBanner })] })
56
57
  });
57
58
  };
58
59
  //#endregion
59
60
  //#region src/client/routes/_auth/projects/-components/buildNavSections.ts
60
61
  function A(e, t, n) {
61
- let r = p(t), i = (e) => !n || n.includes(e), a = [...r.image?.glance && i("images") ? [{
62
+ let r = f(t), i = (e) => !n || n.includes(e), a = [...r.image?.glance && i("images") ? [{
62
63
  service: "images",
63
64
  label: O._({ id: "an5hVd" }),
64
65
  navigate: (t) => t({
@@ -122,7 +123,7 @@ function A(e, t, n) {
122
123
  provider: "ceph",
123
124
  storageType: "buckets"
124
125
  }
125
- }] : []], c = m(r, n) ? [{
126
+ }] : []], c = p(r, n) ? [{
126
127
  service: "pca",
127
128
  label: O._({ id: "miy5mb" }),
128
129
  navigate: (t) => t({
@@ -177,45 +178,45 @@ function j({ projectInfo: t }) {
177
178
  label: n._({ id: "i0qMbr" }),
178
179
  onClick: () => i({ to: "/projects" })
179
180
  });
180
- let c = a.filter((e) => e.routeId !== "/_auth/projects/$projectId" && e.routeId.startsWith("/_auth/projects/$projectId")), l = c[c.length - 1], u = l && d(l.staticData) ? l.staticData : void 0, f = t.domain?.name ? `${t.domain.name}/${t.name}` : t.name;
181
+ let c = a.filter((e) => e.routeId !== "/_auth/projects/$projectId" && e.routeId.startsWith("/_auth/projects/$projectId")), l = c[c.length - 1], u = l && m(l.staticData) ? l.staticData : void 0, d = t.domain?.name ? `${t.domain.name}/${t.name}` : t.name;
181
182
  if (!l || !u) return o.push({
182
- label: f,
183
+ label: d,
183
184
  active: !0
184
185
  }), o;
185
186
  o.push({
186
- label: f,
187
+ label: d,
187
188
  onClick: () => i({
188
189
  to: "/projects/$projectId",
189
190
  params: { projectId: s }
190
191
  })
191
192
  });
192
- let p = l.params;
193
+ let f = l.params;
193
194
  if (u.sectionCrumb?.to) {
194
195
  let { labelKey: t, to: n } = u.sectionCrumb, r = t ? e[t] : void 0;
195
196
  o.push({
196
197
  label: r,
197
198
  onClick: () => i({
198
199
  to: n,
199
- params: p
200
+ params: f
200
201
  })
201
202
  });
202
203
  }
203
204
  if (u.crumb) {
204
- let { labelKey: t, to: n, useParamAsLabel: a } = u.crumb, s = a ? r(p[a]) : t ? e[t] : void 0;
205
+ let { labelKey: t, to: n, useParamAsLabel: a } = u.crumb, s = a ? r(f[a]) : t ? e[t] : void 0;
205
206
  if (u.isDetail) {
206
207
  if (o.push({
207
208
  label: s,
208
209
  onClick: () => i({
209
210
  to: n,
210
- params: p
211
+ params: f
211
212
  })
212
213
  }), u.intermediateCrumb) {
213
- let { to: e, useParamAsLabel: t, useParentTitleAsLabel: n } = u.intermediateCrumb, r = c[c.length - 2]?.meta?.find((e) => e != null && "title" in e)?.title, a = n ? r ?? (t ? p[t] : void 0) : t ? p[t] : void 0;
214
+ let { to: e, useParamAsLabel: t, useParentTitleAsLabel: n } = u.intermediateCrumb, r = c[c.length - 2]?.meta?.find((e) => e != null && "title" in e)?.title, a = n ? r ?? (t ? f[t] : void 0) : t ? f[t] : void 0;
214
215
  o.push(e ? {
215
216
  label: a,
216
217
  onClick: () => i({
217
218
  to: e,
218
- params: p
219
+ params: f
219
220
  })
220
221
  } : { label: a });
221
222
  }
@@ -228,7 +229,7 @@ function j({ projectInfo: t }) {
228
229
  label: s,
229
230
  onClick: () => i({
230
231
  to: n,
231
- params: p
232
+ params: f
232
233
  })
233
234
  } : {
234
235
  label: s,
@@ -253,7 +254,7 @@ function j({ projectInfo: t }) {
253
254
  //#endregion
254
255
  //#region src/client/routes/_auth/projects/$projectId.tsx?tsr-split=component
255
256
  function M() {
256
- let { availableServices: e, projectId: t, crumbProject: r, crumbDomain: i } = S({ from: f.id }), { enabledServices: a } = E({ strict: !1 });
257
+ let { availableServices: e, projectId: t, crumbProject: r, crumbDomain: i } = S({ from: d.id }), { enabledServices: a } = E({ strict: !1 });
257
258
  return /*#__PURE__*/ g(l, {
258
259
  embedded: !0,
259
260
  sideNavigation: /*#__PURE__*/ g(k, {
@@ -287,4 +288,4 @@ function M() {
287
288
  //#endregion
288
289
  export { M as component };
289
290
 
290
- //# sourceMappingURL=_projectId-DJLjspKf.mjs.map
291
+ //# sourceMappingURL=_projectId-hMtrqW6m.mjs.map