@cobaltcore-dev/aurora 0.20.1 → 0.20.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/{ContentHeader-BIScbvb4.mjs → ContentHeader-CIAzorGI.mjs} +2 -3
- package/dist/client/{ContentHeader-BIScbvb4.mjs.map → ContentHeader-CIAzorGI.mjs.map} +1 -1
- package/dist/client/{_flavorId-sS1sg_DZ.mjs → _flavorId-BMX239RH.mjs} +3 -3
- package/dist/client/{_flavorId-sS1sg_DZ.mjs.map → _flavorId-BMX239RH.mjs.map} +1 -1
- package/dist/client/{_flavorId-BQY7YhNx.mjs → _flavorId-BaNJ1CDY.mjs} +2 -2
- package/dist/client/{_flavorId-BQY7YhNx.mjs.map → _flavorId-BaNJ1CDY.mjs.map} +1 -1
- package/dist/client/{_floatingIpId-DFCaWI9K.mjs → _floatingIpId-DQFtZNeg.mjs} +2 -2
- package/dist/client/{_floatingIpId-DFCaWI9K.mjs.map → _floatingIpId-DQFtZNeg.mjs.map} +1 -1
- package/dist/client/{_floatingIpId-De0hCSSN2.mjs → _floatingIpId-E6x99cmW.mjs} +2 -2
- package/dist/client/_floatingIpId-E6x99cmW.mjs.map +1 -0
- package/dist/client/{_imageId-D8HiDS3P.mjs → _imageId-C2662dZr.mjs} +2 -2
- package/dist/client/{_imageId-D8HiDS3P.mjs.map → _imageId-C2662dZr.mjs.map} +1 -1
- package/dist/client/{_projectId-CoMD6nq6.mjs → _projectId-B4ScifQf.mjs} +3 -3
- package/dist/client/{_projectId-CoMD6nq6.mjs.map → _projectId-B4ScifQf.mjs.map} +1 -1
- package/dist/client/{_projectId-D8FHe0gr.mjs → _projectId-CYq4KZXB.mjs} +3 -3
- package/dist/client/{_projectId-D8FHe0gr.mjs.map → _projectId-CYq4KZXB.mjs.map} +1 -1
- package/dist/client/{_projectId-hMtrqW6m.mjs → _projectId-DJLjspKf.mjs} +23 -24
- package/dist/client/{_projectId-hMtrqW6m.mjs.map → _projectId-DJLjspKf.mjs.map} +1 -1
- package/dist/client/{_projectId-MjFv839t.mjs → _projectId-DxgPaiA7.mjs} +2 -2
- package/dist/client/{_projectId-MjFv839t.mjs.map → _projectId-DxgPaiA7.mjs.map} +1 -1
- package/dist/client/{_storageType-CH2khFcZ.mjs → _storageType-DsbBodWF.mjs} +2 -2
- package/dist/client/{_storageType-CH2khFcZ.mjs.map → _storageType-DsbBodWF.mjs.map} +1 -1
- package/dist/client/{_storageType-D-1H6SX1.mjs → _storageType-WwAATdsa.mjs} +886 -886
- package/dist/client/{_storageType-D-1H6SX1.mjs.map → _storageType-WwAATdsa.mjs.map} +1 -1
- package/dist/client/{flavors-D5aTQJT1.mjs → flavors-Bwjig3k2.mjs} +3 -3
- package/dist/client/{flavors-D5aTQJT1.mjs.map → flavors-Bwjig3k2.mjs.map} +1 -1
- package/dist/client/{flavors-TqwbWt4R.mjs → flavors-C7PVJUqZ.mjs} +2 -2
- package/dist/client/{flavors-TqwbWt4R.mjs.map → flavors-C7PVJUqZ.mjs.map} +1 -1
- package/dist/client/{images-sNuJa6ce.mjs → images-CD7DNwru.mjs} +2 -2
- package/dist/client/{images-sNuJa6ce.mjs.map → images-CD7DNwru.mjs.map} +1 -1
- package/dist/client/{images-CkW8zNjs.mjs → images-CfBrglGJ.mjs} +9 -9
- package/dist/client/{images-CkW8zNjs.mjs.map → images-CfBrglGJ.mjs.map} +1 -1
- package/dist/client/index.js +454 -317
- package/dist/client/index.js.map +1 -1
- package/dist/client/{objects-B3Z8h4RU.mjs → objects-CEBrL7k-.mjs} +2 -2
- package/dist/client/{objects-B3Z8h4RU.mjs.map → objects-CEBrL7k-.mjs.map} +1 -1
- package/dist/client/{objects-CGOfO00N.mjs → objects-DvIxpzGI.mjs} +3 -3
- package/dist/client/{objects-CGOfO00N.mjs.map → objects-DvIxpzGI.mjs.map} +1 -1
- package/dist/client/{pca-BClYQElh.mjs → pca-CpMqoF00.mjs} +3 -3
- package/dist/client/{pca-BClYQElh.mjs.map → pca-CpMqoF00.mjs.map} +1 -1
- package/dist/client/{pca-B4_2DqT2.mjs → pca-ZgGU0UPF.mjs} +2 -2
- package/dist/client/{pca-B4_2DqT2.mjs.map → pca-ZgGU0UPF.mjs.map} +1 -1
- package/dist/client/projects-DvSiI5Vg.mjs.map +1 -1
- package/dist/client/projects-yiK0HGSA.mjs.map +1 -1
- package/dist/client/routeInfo-Ct5r4Gen.mjs +56 -0
- package/dist/client/routeInfo-Ct5r4Gen.mjs.map +1 -0
- package/package.json +1 -1
- package/dist/client/Slot-CWb612-_.mjs +0 -28
- package/dist/client/Slot-CWb612-_.mjs.map +0 -1
- package/dist/client/_floatingIpId-De0hCSSN2.mjs.map +0 -1
- package/dist/client/projects-4wFhznPY.mjs +0 -34
- package/dist/client/projects-4wFhznPY.mjs.map +0 -1
- package/dist/client/projects-wd6Yc-GU.mjs +0 -107
- package/dist/client/projects-wd6Yc-GU.mjs.map +0 -1
- package/dist/client/routeInfo-CdTRlSxv.mjs +0 -32
- package/dist/client/routeInfo-CdTRlSxv.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_projectId-hMtrqW6m.mjs","names":["useNavigate","useMatches","useParams","useRouteContext","useState","useEffect","SideNavigation","SideNavigationList","SideNavigationGroup","SideNavigationItem","Divider","isRouteInfo","Slot","SideNavBar","projectId","projectName","domainName","sections","navigate","matches","provider","strict","slots","navBadge","service","serviceBadge","component","useShadowDOM","currentService","activeMatch","reverse","find","m","staticData","activeRouteInfo","undefined","activeSection","section","activeService","forceOpenCounter","setForceOpenCounter","Object","fromEntries","map","s","prev","ariaLabel","onClick","to","params","label","p","className","spacing","services","open","item","isStorageContainers","isSelected","span","selected","sideNavBanner","getServiceIndex","canAccessClavisPca","buildNavSections","projectId","availableServices","enabledServices","serviceIndex","isEnabled","service","includes","computeServices","label","t","navigate","nav","to","params","networkServices","storageServices","provider","storageType","clavisServices","section","services","filter","s","length","Breadcrumb","BreadcrumbItem","useMatches","useNavigate","useParams","useMemo","isRouteInfo","ProjectInfoBox","projectInfo","useLingui","navigate","matches","projectId","strict","breadcrumbs","crumbLabels","Compute","t","Network","Storage","Services","Images","Flavors","resolveProviderLabel","provider","items","push","icon","label","onClick","to","projectMatches","filter","m","routeId","startsWith","deepest","length","info","staticData","undefined","combinedLabel","domain","name","active","params","sectionCrumb","labelKey","crumb","useParamAsLabel","resolvedLabel","isDetail","intermediateCrumb","iTo","iParam","useParentTitleAsLabel","parentMatch","parentTitle","meta","find","title","iLabel","className","map","item","index","Outlet","useLoaderData","useRouteContext","AppShell","Container","Stack","SideNavBar","buildNavSections","ProjectInfoBox","useMemo","Route","RouteComponent","availableServices","projectId","crumbProject","crumbDomain","from","id","enabledServices","strict","sections","name","domain","component"],"sources":["../../src/client/routes/_auth/projects/-components/SideNavBar.tsx","../../src/client/routes/_auth/projects/-components/buildNavSections.ts","../../src/client/components/ProjectView/ProjectInfoBox.tsx","../../src/client/routes/_auth/projects/$projectId.tsx?tsr-split=component"],"sourcesContent":["import { useNavigate, useMatches, useParams, useRouteContext } from \"@tanstack/react-router\"\nimport { useState, useEffect } from \"react\"\nimport {\n SideNavigation,\n SideNavigationList,\n SideNavigationGroup,\n SideNavigationItem,\n Divider,\n} from \"@cloudoperators/juno-ui-components/index\"\nimport { isRouteInfo } from \"@/client/routes/routeInfo\"\nimport { Slot } from \"@/client/components/Slot\"\nimport type { NavSection } from \"./buildNavSections\"\n\ninterface SideNavBarProps {\n projectId: string\n projectName: string\n domainName?: string\n sections: NavSection[]\n}\n\nexport const SideNavBar = ({ projectId, projectName, domainName, sections }: SideNavBarProps) => {\n const navigate = useNavigate()\n const matches = useMatches()\n const { provider } = useParams({ strict: false }) as { provider?: string }\n const { slots } = useRouteContext({ strict: false })\n\n const navBadge = (service: string) => {\n if (!slots?.serviceBadge) return null\n return <Slot component={slots.serviceBadge} useShadowDOM={false} currentService={service} />\n }\n\n // Read active section/service from the deepest match that has valid RouteInfo staticData\n const activeMatch = [...matches].reverse().find((m) => isRouteInfo(m.staticData))\n const activeRouteInfo = activeMatch && isRouteInfo(activeMatch.staticData) ? activeMatch.staticData : undefined\n const activeSection = activeRouteInfo?.section ?? null\n const activeService = activeRouteInfo?.service ?? null\n\n // Track which sections should be forced open by incrementing a counter\n // This forces a remount of SideNavigationGroup since it doesn't expose onToggle\n const [forceOpenCounter, setForceOpenCounter] = useState<Record<string, number>>(() =>\n Object.fromEntries(sections.map((s) => [s.section, 0]))\n )\n\n // When navigating to a section, force it to expand by remounting\n useEffect(() => {\n if (activeSection) {\n setForceOpenCounter((prev) => ({ ...prev, [activeSection]: (prev[activeSection] || 0) + 1 }))\n }\n }, [activeSection])\n\n return (\n <SideNavigation ariaLabel=\"Project Side Navigation\">\n <>\n <SideNavigationList>\n <>\n <SideNavigationItem\n onClick={() => navigate({ to: \"/projects/$projectId\", params: { projectId } })}\n label={\n <>\n {domainName && <p className=\"text-theme-light text-xs leading-4 font-bold\">{domainName} /</p>}\n <p className=\"leading-5 font-normal\">{projectName}</p>\n </>\n }\n />\n <Divider spacing=\"1\" />\n {sections.map(({ section, label, services }) => (\n <SideNavigationGroup key={`${section}-${forceOpenCounter[section]}`} label={label} open={true}>\n {services.map((item) => {\n const isStorageContainers = activeSection === \"storage\" && activeService === \"containers\"\n const isSelected =\n activeSection === section &&\n (isStorageContainers ? item.params.provider === provider : activeService === item.service)\n\n return (\n <SideNavigationItem\n key={item.service}\n onClick={() => item.navigate(navigate)}\n label={\n <span className=\"flex items-start gap-2\">\n {item.label}\n {navBadge(item.service)}\n </span>\n }\n selected={isSelected}\n />\n )\n })}\n </SideNavigationGroup>\n ))}\n </>\n </SideNavigationList>\n {slots?.sideNavBanner && <Slot component={slots.sideNavBanner} />}\n </>\n </SideNavigation>\n )\n}\n","import { getServiceIndex } from \"@/server/Authentication/helpers\"\nimport { t } from \"@lingui/core/macro\"\nimport type { NavigateFn } from \"@tanstack/react-router\"\nimport { canAccessClavisPca } from \"@/client/routes/_auth/projects/$projectId/services/pca/-components/pcaAccess\"\n\nexport type NavItem = {\n service: string\n label: string\n navigate: (navigateFn: NavigateFn) => void\n params: Record<string, string>\n}\n\nexport type NavSection = {\n section: string\n label: string\n services: NavItem[]\n}\n\nexport function buildNavSections(\n projectId: string,\n availableServices: { type: string; name: string }[],\n enabledServices?: string[]\n): NavSection[] {\n const serviceIndex = getServiceIndex(availableServices)\n const isEnabled = (service: string) => !enabledServices || enabledServices.includes(service)\n\n const computeServices: NavItem[] = [\n ...(serviceIndex[\"image\"]?.[\"glance\"] && isEnabled(\"images\")\n ? [\n {\n service: \"images\",\n label: t`Images`,\n navigate: (nav: NavigateFn) => nav({ to: \"/projects/$projectId/compute/images\", params: { projectId } }),\n params: { projectId },\n },\n ]\n : []),\n ...(serviceIndex?.[\"compute\"]?.[\"nova\"] && isEnabled(\"flavors\")\n ? [\n {\n service: \"flavors\",\n label: t`Flavors`,\n navigate: (nav: NavigateFn) => nav({ to: \"/projects/$projectId/compute/flavors\", params: { projectId } }),\n params: { projectId },\n },\n ]\n : []),\n ]\n\n const networkServices: NavItem[] = serviceIndex[\"network\"]\n ? [\n ...(isEnabled(\"securitygroups\")\n ? [\n {\n service: \"securitygroups\",\n label: t`Security Groups`,\n navigate: (nav: NavigateFn) =>\n nav({ to: \"/projects/$projectId/network/securitygroups\", params: { projectId } }),\n params: { projectId },\n },\n ]\n : []),\n ...(isEnabled(\"floatingips\")\n ? [\n {\n service: \"floatingips\",\n label: t`Floating IPs`,\n navigate: (nav: NavigateFn) =>\n nav({ to: \"/projects/$projectId/network/floatingips\", params: { projectId } }),\n params: { projectId },\n },\n ]\n : []),\n ]\n : []\n\n const storageServices: NavItem[] = [\n ...(serviceIndex?.[\"object-store\"]?.[\"swift\"] && isEnabled(\"containers\")\n ? [\n {\n service: \"containers\",\n label: t`Object Storage (Swift)`,\n navigate: (nav: NavigateFn) =>\n nav({\n to: \"/projects/$projectId/storage/$provider/$storageType\",\n params: { projectId, provider: \"swift\", storageType: \"containers\" },\n }),\n params: { projectId, provider: \"swift\", storageType: \"containers\" },\n },\n ]\n : []),\n ...(serviceIndex?.[\"object-store-ceph\"]?.[\"ceph\"] && isEnabled(\"ceph-containers\")\n ? [\n {\n service: \"ceph-containers\",\n label: t`Object Storage (Ceph)`,\n navigate: (nav: NavigateFn) =>\n nav({\n to: \"/projects/$projectId/storage/$provider/$storageType\",\n params: { projectId, provider: \"ceph\", storageType: \"buckets\" },\n }),\n params: { projectId, provider: \"ceph\", storageType: \"buckets\" },\n },\n ]\n : []),\n ]\n\n const clavisServices: NavItem[] = canAccessClavisPca(serviceIndex, enabledServices)\n ? [\n {\n service: \"pca\",\n label: t`PCA (Clavis)`,\n navigate: (nav: NavigateFn) => nav({ to: \"/projects/$projectId/services/pca\", params: { projectId } }),\n params: { projectId },\n },\n ]\n : []\n\n return [\n { section: \"compute\", label: t`Compute`, services: computeServices },\n { section: \"network\", label: t`Network`, services: networkServices },\n { section: \"storage\", label: t`Storage`, services: storageServices },\n { section: \"services\", label: t`Services`, services: clavisServices },\n ].filter((s) => s.services.length > 0)\n}\n","import { Breadcrumb, BreadcrumbItem, KnownIcons } from \"@cloudoperators/juno-ui-components\"\nimport { useMatches, useNavigate, useParams } from \"@tanstack/react-router\"\nimport { useMemo } from \"react\"\nimport { useLingui } from \"@lingui/react/macro\"\nimport { isRouteInfo, CrumbLabelKey } from \"@/client/routes/routeInfo\"\n\ninterface ProjectInfoBoxProps {\n projectInfo: {\n id: string\n name: string\n description?: string\n domain?: {\n name?: string\n }\n }\n}\n\nexport function ProjectInfoBox({ projectInfo }: ProjectInfoBoxProps) {\n const { t } = useLingui()\n const navigate = useNavigate()\n const matches = useMatches()\n const { projectId } = useParams({ strict: false }) as { projectId: string }\n\n const breadcrumbs = useMemo(() => {\n const crumbLabels: Record<CrumbLabelKey, string> = {\n Compute: t`Compute`,\n Network: t`Network`,\n Storage: t`Storage`,\n Services: t`Services`,\n Images: t`Images`,\n Flavors: t`Flavors`,\n \"Security Groups\": t`Security Groups`,\n \"Floating IPs\": t`Floating IPs`,\n \"PCA (Clavis)\": t`PCA (Clavis)`,\n }\n\n const resolveProviderLabel = (provider: string | undefined) => {\n if (provider === \"swift\") return t`Object Storage (Swift)`\n if (provider === \"ceph\") return t`Object Storage (Ceph)`\n return t`Storage`\n }\n\n const items: Array<{ label?: string; icon?: KnownIcons; onClick?: () => void; active?: boolean }> = []\n\n items.push({ icon: \"home\", label: t`Home`, onClick: () => navigate({ to: \"/projects\" }) })\n\n const projectMatches = matches.filter(\n (m) => m.routeId !== \"/_auth/projects/$projectId\" && m.routeId.startsWith(\"/_auth/projects/$projectId\")\n )\n const deepest = projectMatches[projectMatches.length - 1]\n\n const info = deepest ? (isRouteInfo(deepest.staticData) ? deepest.staticData : undefined) : undefined\n\n const combinedLabel = projectInfo.domain?.name ? `${projectInfo.domain.name}/${projectInfo.name}` : projectInfo.name\n\n if (!deepest || !info) {\n items.push({ label: combinedLabel, active: true })\n return items\n }\n\n items.push({\n label: combinedLabel,\n onClick: () => navigate({ to: \"/projects/$projectId\", params: { projectId } }),\n })\n\n const params = deepest.params as Record<string, string>\n\n if (info.sectionCrumb?.to) {\n const { labelKey, to } = info.sectionCrumb\n const label = labelKey ? crumbLabels[labelKey] : undefined\n items.push({ label, onClick: () => navigate({ to: to as never, params: params as never }) })\n }\n\n if (info.crumb) {\n const { labelKey, to, useParamAsLabel } = info.crumb\n const resolvedLabel = useParamAsLabel\n ? resolveProviderLabel(params[useParamAsLabel])\n : labelKey\n ? crumbLabels[labelKey]\n : undefined\n\n if (info.isDetail) {\n items.push({ label: resolvedLabel, onClick: () => navigate({ to: to as never, params: params as never }) })\n\n if (info.intermediateCrumb) {\n const { to: iTo, useParamAsLabel: iParam, useParentTitleAsLabel } = info.intermediateCrumb\n const parentMatch = projectMatches[projectMatches.length - 2]\n const parentTitle = parentMatch?.meta?.find((m) => m != null && \"title\" in m)?.title as string | undefined\n const iLabel = useParentTitleAsLabel\n ? (parentTitle ?? (iParam ? params[iParam] : undefined))\n : iParam\n ? params[iParam]\n : undefined\n items.push(\n iTo\n ? { label: iLabel, onClick: () => navigate({ to: iTo as never, params: params as never }) }\n : { label: iLabel }\n )\n }\n\n const title = deepest.meta?.find((m) => m != null && \"title\" in m)?.title as string | undefined\n if (title) items.push({ label: title, active: true })\n } else {\n items.push(\n to\n ? { label: resolvedLabel, onClick: () => navigate({ to: to as never, params: params as never }) }\n : { label: resolvedLabel, active: true }\n )\n }\n }\n\n return items\n }, [matches, projectInfo, projectId, navigate, t])\n\n return (\n <Breadcrumb className=\"relative z-1 mt-8 mb-4\">\n {breadcrumbs.map((item, index) => (\n <BreadcrumbItem key={index} label={item.label} icon={item.icon} onClick={item.onClick} active={item.active} />\n ))}\n </Breadcrumb>\n )\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":"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"}
|
|
1
|
+
{"version":3,"file":"_projectId-DJLjspKf.mjs","names":["useNavigate","useMatches","useParams","useRouteContext","useState","useEffect","SideNavigation","SideNavigationList","SideNavigationGroup","SideNavigationItem","Divider","isRouteInfo","Slot","SideNavBar","projectId","projectName","domainName","sections","navigate","matches","provider","strict","slots","navBadge","service","serviceBadge","component","useShadowDOM","currentService","activeMatch","reverse","find","m","staticData","activeRouteInfo","undefined","activeSection","section","activeService","forceOpenCounter","setForceOpenCounter","Object","fromEntries","map","s","prev","ariaLabel","onClick","to","params","label","p","className","spacing","services","open","item","isStorageContainers","isSelected","span","selected","sideNavBanner","getServiceIndex","canAccessClavisPca","buildNavSections","projectId","availableServices","enabledServices","serviceIndex","isEnabled","service","includes","computeServices","label","t","navigate","nav","to","params","networkServices","storageServices","provider","storageType","clavisServices","section","services","filter","s","length","Breadcrumb","BreadcrumbItem","useMatches","useNavigate","useParams","useMemo","isRouteInfo","ProjectInfoBox","projectInfo","useLingui","navigate","matches","projectId","strict","breadcrumbs","crumbLabels","Compute","t","Network","Storage","Services","Images","Flavors","resolveProviderLabel","provider","items","push","icon","label","onClick","to","projectMatches","filter","m","routeId","startsWith","deepest","length","info","staticData","undefined","combinedLabel","domain","name","active","params","sectionCrumb","labelKey","crumb","useParamAsLabel","resolvedLabel","isDetail","intermediateCrumb","iTo","iParam","useParentTitleAsLabel","parentMatch","parentTitle","meta","find","title","iLabel","className","map","item","index","Outlet","useLoaderData","useRouteContext","AppShell","Container","Stack","SideNavBar","buildNavSections","ProjectInfoBox","useMemo","Route","RouteComponent","availableServices","projectId","crumbProject","crumbDomain","from","id","enabledServices","strict","sections","name","domain","component"],"sources":["../../src/client/routes/_auth/projects/-components/SideNavBar.tsx","../../src/client/routes/_auth/projects/-components/buildNavSections.ts","../../src/client/components/ProjectView/ProjectInfoBox.tsx","../../src/client/routes/_auth/projects/$projectId.tsx?tsr-split=component"],"sourcesContent":["import { useNavigate, useMatches, useParams, useRouteContext } from \"@tanstack/react-router\"\nimport { useState, useEffect } from \"react\"\nimport {\n SideNavigation,\n SideNavigationList,\n SideNavigationGroup,\n SideNavigationItem,\n Divider,\n} from \"@cloudoperators/juno-ui-components/index\"\nimport { isRouteInfo } from \"@/client/routes/routeInfo\"\nimport { Slot } from \"@/client/components/Slot\"\nimport type { NavSection } from \"./buildNavSections\"\n\ninterface SideNavBarProps {\n projectId: string\n projectName: string\n domainName?: string\n sections: NavSection[]\n}\n\nexport const SideNavBar = ({ projectId, projectName, domainName, sections }: SideNavBarProps) => {\n const navigate = useNavigate()\n const matches = useMatches()\n const { provider } = useParams({ strict: false }) as { provider?: string }\n const { slots } = useRouteContext({ strict: false })\n\n const navBadge = (service: string) => {\n if (!slots?.serviceBadge) return null\n return <Slot component={slots.serviceBadge} useShadowDOM={false} currentService={service} />\n }\n\n // Read active section/service from the deepest match that has valid RouteInfo staticData\n const activeMatch = [...matches].reverse().find((m) => isRouteInfo(m.staticData))\n const activeRouteInfo = activeMatch && isRouteInfo(activeMatch.staticData) ? activeMatch.staticData : undefined\n const activeSection = activeRouteInfo?.section ?? null\n const activeService = activeRouteInfo?.service ?? null\n\n // Track which sections should be forced open by incrementing a counter\n // This forces a remount of SideNavigationGroup since it doesn't expose onToggle\n const [forceOpenCounter, setForceOpenCounter] = useState<Record<string, number>>(() =>\n Object.fromEntries(sections.map((s) => [s.section, 0]))\n )\n\n // When navigating to a section, force it to expand by remounting\n useEffect(() => {\n if (activeSection) {\n setForceOpenCounter((prev) => ({ ...prev, [activeSection]: (prev[activeSection] || 0) + 1 }))\n }\n }, [activeSection])\n\n return (\n <SideNavigation ariaLabel=\"Project Side Navigation\">\n <>\n <SideNavigationList>\n <>\n <SideNavigationItem\n onClick={() => navigate({ to: \"/projects/$projectId\", params: { projectId } })}\n label={\n <>\n {domainName && <p className=\"text-theme-light text-xs leading-4 font-bold\">{domainName} /</p>}\n <p className=\"leading-5 font-normal\">{projectName}</p>\n </>\n }\n />\n <Divider spacing=\"1\" />\n {sections.map(({ section, label, services }) => (\n <SideNavigationGroup key={`${section}-${forceOpenCounter[section]}`} label={label} open={true}>\n {services.map((item) => {\n const isStorageContainers = activeSection === \"storage\" && activeService === \"containers\"\n const isSelected =\n activeSection === section &&\n (isStorageContainers ? item.params.provider === provider : activeService === item.service)\n\n return (\n <SideNavigationItem\n key={item.service}\n onClick={() => item.navigate(navigate)}\n label={\n <span className=\"flex items-start gap-2\">\n {item.label}\n {navBadge(item.service)}\n </span>\n }\n selected={isSelected}\n />\n )\n })}\n </SideNavigationGroup>\n ))}\n </>\n </SideNavigationList>\n {slots?.sideNavBanner && <Slot component={slots.sideNavBanner} />}\n </>\n </SideNavigation>\n )\n}\n","import { getServiceIndex } from \"@/server/Authentication/helpers\"\nimport { t } from \"@lingui/core/macro\"\nimport type { NavigateFn } from \"@tanstack/react-router\"\nimport { canAccessClavisPca } from \"@/client/routes/_auth/projects/$projectId/services/pca/-components/pcaAccess\"\n\nexport type NavItem = {\n service: string\n label: string\n navigate: (navigateFn: NavigateFn) => void\n params: Record<string, string>\n}\n\nexport type NavSection = {\n section: string\n label: string\n services: NavItem[]\n}\n\nexport function buildNavSections(\n projectId: string,\n availableServices: { type: string; name: string }[],\n enabledServices?: string[]\n): NavSection[] {\n const serviceIndex = getServiceIndex(availableServices)\n const isEnabled = (service: string) => !enabledServices || enabledServices.includes(service)\n\n const computeServices: NavItem[] = [\n ...(serviceIndex[\"image\"]?.[\"glance\"] && isEnabled(\"images\")\n ? [\n {\n service: \"images\",\n label: t`Images`,\n navigate: (nav: NavigateFn) => nav({ to: \"/projects/$projectId/compute/images\", params: { projectId } }),\n params: { projectId },\n },\n ]\n : []),\n ...(serviceIndex?.[\"compute\"]?.[\"nova\"] && isEnabled(\"flavors\")\n ? [\n {\n service: \"flavors\",\n label: t`Flavors`,\n navigate: (nav: NavigateFn) => nav({ to: \"/projects/$projectId/compute/flavors\", params: { projectId } }),\n params: { projectId },\n },\n ]\n : []),\n ]\n\n const networkServices: NavItem[] = serviceIndex[\"network\"]\n ? [\n ...(isEnabled(\"securitygroups\")\n ? [\n {\n service: \"securitygroups\",\n label: t`Security Groups`,\n navigate: (nav: NavigateFn) =>\n nav({ to: \"/projects/$projectId/network/securitygroups\", params: { projectId } }),\n params: { projectId },\n },\n ]\n : []),\n ...(isEnabled(\"floatingips\")\n ? [\n {\n service: \"floatingips\",\n label: t`Floating IPs`,\n navigate: (nav: NavigateFn) =>\n nav({ to: \"/projects/$projectId/network/floatingips\", params: { projectId } }),\n params: { projectId },\n },\n ]\n : []),\n ]\n : []\n\n const storageServices: NavItem[] = [\n ...(serviceIndex?.[\"object-store\"]?.[\"swift\"] && isEnabled(\"containers\")\n ? [\n {\n service: \"containers\",\n label: t`Object Storage (Swift)`,\n navigate: (nav: NavigateFn) =>\n nav({\n to: \"/projects/$projectId/storage/$provider/$storageType\",\n params: { projectId, provider: \"swift\", storageType: \"containers\" },\n }),\n params: { projectId, provider: \"swift\", storageType: \"containers\" },\n },\n ]\n : []),\n ...(serviceIndex?.[\"object-store-ceph\"]?.[\"ceph\"] && isEnabled(\"ceph-containers\")\n ? [\n {\n service: \"ceph-containers\",\n label: t`Object Storage (Ceph)`,\n navigate: (nav: NavigateFn) =>\n nav({\n to: \"/projects/$projectId/storage/$provider/$storageType\",\n params: { projectId, provider: \"ceph\", storageType: \"buckets\" },\n }),\n params: { projectId, provider: \"ceph\", storageType: \"buckets\" },\n },\n ]\n : []),\n ]\n\n const clavisServices: NavItem[] = canAccessClavisPca(serviceIndex, enabledServices)\n ? [\n {\n service: \"pca\",\n label: t`PCA (Clavis)`,\n navigate: (nav: NavigateFn) => nav({ to: \"/projects/$projectId/services/pca\", params: { projectId } }),\n params: { projectId },\n },\n ]\n : []\n\n return [\n { section: \"compute\", label: t`Compute`, services: computeServices },\n { section: \"network\", label: t`Network`, services: networkServices },\n { section: \"storage\", label: t`Storage`, services: storageServices },\n { section: \"services\", label: t`Services`, services: clavisServices },\n ].filter((s) => s.services.length > 0)\n}\n","import { Breadcrumb, BreadcrumbItem, KnownIcons } from \"@cloudoperators/juno-ui-components\"\nimport { useMatches, useNavigate, useParams } from \"@tanstack/react-router\"\nimport { useMemo } from \"react\"\nimport { useLingui } from \"@lingui/react/macro\"\nimport { isRouteInfo, CrumbLabelKey } from \"@/client/routes/routeInfo\"\n\ninterface ProjectInfoBoxProps {\n projectInfo: {\n id: string\n name: string\n description?: string\n domain?: {\n name?: string\n }\n }\n}\n\nexport function ProjectInfoBox({ projectInfo }: ProjectInfoBoxProps) {\n const { t } = useLingui()\n const navigate = useNavigate()\n const matches = useMatches()\n const { projectId } = useParams({ strict: false }) as { projectId: string }\n\n const breadcrumbs = useMemo(() => {\n const crumbLabels: Record<CrumbLabelKey, string> = {\n Compute: t`Compute`,\n Network: t`Network`,\n Storage: t`Storage`,\n Services: t`Services`,\n Images: t`Images`,\n Flavors: t`Flavors`,\n \"Security Groups\": t`Security Groups`,\n \"Floating IPs\": t`Floating IPs`,\n \"PCA (Clavis)\": t`PCA (Clavis)`,\n }\n\n const resolveProviderLabel = (provider: string | undefined) => {\n if (provider === \"swift\") return t`Object Storage (Swift)`\n if (provider === \"ceph\") return t`Object Storage (Ceph)`\n return t`Storage`\n }\n\n const items: Array<{ label?: string; icon?: KnownIcons; onClick?: () => void; active?: boolean }> = []\n\n items.push({ icon: \"home\", label: t`Home`, onClick: () => navigate({ to: \"/projects\" }) })\n\n const projectMatches = matches.filter(\n (m) => m.routeId !== \"/_auth/projects/$projectId\" && m.routeId.startsWith(\"/_auth/projects/$projectId\")\n )\n const deepest = projectMatches[projectMatches.length - 1]\n\n const info = deepest ? (isRouteInfo(deepest.staticData) ? deepest.staticData : undefined) : undefined\n\n const combinedLabel = projectInfo.domain?.name ? `${projectInfo.domain.name}/${projectInfo.name}` : projectInfo.name\n\n if (!deepest || !info) {\n items.push({ label: combinedLabel, active: true })\n return items\n }\n\n items.push({\n label: combinedLabel,\n onClick: () => navigate({ to: \"/projects/$projectId\", params: { projectId } }),\n })\n\n const params = deepest.params as Record<string, string>\n\n if (info.sectionCrumb?.to) {\n const { labelKey, to } = info.sectionCrumb\n const label = labelKey ? crumbLabels[labelKey] : undefined\n items.push({ label, onClick: () => navigate({ to: to as never, params: params as never }) })\n }\n\n if (info.crumb) {\n const { labelKey, to, useParamAsLabel } = info.crumb\n const resolvedLabel = useParamAsLabel\n ? resolveProviderLabel(params[useParamAsLabel])\n : labelKey\n ? crumbLabels[labelKey]\n : undefined\n\n if (info.isDetail) {\n items.push({ label: resolvedLabel, onClick: () => navigate({ to: to as never, params: params as never }) })\n\n if (info.intermediateCrumb) {\n const { to: iTo, useParamAsLabel: iParam, useParentTitleAsLabel } = info.intermediateCrumb\n const parentMatch = projectMatches[projectMatches.length - 2]\n const parentTitle = parentMatch?.meta?.find((m) => m != null && \"title\" in m)?.title as string | undefined\n const iLabel = useParentTitleAsLabel\n ? (parentTitle ?? (iParam ? params[iParam] : undefined))\n : iParam\n ? params[iParam]\n : undefined\n items.push(\n iTo\n ? { label: iLabel, onClick: () => navigate({ to: iTo as never, params: params as never }) }\n : { label: iLabel }\n )\n }\n\n const title = deepest.meta?.find((m) => m != null && \"title\" in m)?.title as string | undefined\n if (title) items.push({ label: title, active: true })\n } else {\n items.push(\n to\n ? { label: resolvedLabel, onClick: () => navigate({ to: to as never, params: params as never }) }\n : { label: resolvedLabel, active: true }\n )\n }\n }\n\n return items\n }, [matches, projectInfo, projectId, navigate, t])\n\n return (\n <Breadcrumb className=\"relative z-1 mt-8 mb-4\">\n {breadcrumbs.map((item, index) => (\n <BreadcrumbItem key={index} label={item.label} icon={item.icon} onClick={item.onClick} active={item.active} />\n ))}\n </Breadcrumb>\n )\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":";;;;;;;;;;;AAoBA,IAAaa,KAAc,EAAEC,cAAWC,gBAAaC,eAAYC,kBAA2B;CAC1F,IAAMC,IAAWlB,EAAAA,GACXmB,IAAUlB,EAAAA,GACV,EAAEmB,gBAAalB,EAAU,EAAEmB,QAAQ,GAAM,CAAA,GACzC,EAAEC,aAAUnB,EAAgB,EAAEkB,QAAQ,GAAM,CAAA,GAE5CE,KAAYC,MACXF,GAAOG,eACL,gBAACb,GAAAA;EAAKc,WAAWJ,EAAMG;EAAcE,cAAc;EAAOC,gBAAgBJ;MADhD,MAK7BK,IAAc,CAAA,GAAIV,CAAAA,EAASW,QAAO,EAAGC,MAAMC,MAAMrB,EAAYqB,EAAEC,UAAU,CAAA,GACzEC,IAAkBL,KAAelB,EAAYkB,EAAYI,UAAU,IAAIJ,EAAYI,aAAaE,KAAAA,GAChGC,IAAgBF,GAAiBG,WAAW,MAC5CC,IAAgBJ,GAAiBV,WAAW,MAI5C,CAACe,GAAkBC,KAAuBpC,QAC9CqC,OAAOC,YAAYzB,EAAS0B,KAAKC,MAAM,CAACA,EAAEP,SAAS,CAAA,CAAE,CAAA,CAAA;CAUvD,OANAhC,QAAU;EACR,AAAI+B,KACFI,GAAqBK,OAAU;GAAE,GAAGA;IAAOT,KAAiBS,EAAKT,MAAkB,KAAK;EAAE,EAAA;CAE9F,GAAG,CAACA,CAAAA,CAAc,GAGhB,gBAAC9B,GAAAA;EAAewC,WAAU;YACxB,gBAAA,GAAA,EAAA,UAAA,CACE,gBAACvC,GAAAA,EAAAA,UACC,gBAAA,GAAA,EAAA,UAAA;GACE,gBAACE,GAAAA;IACCsC,eAAe7B,EAAS;KAAE8B,IAAI;KAAwBC,QAAQ,EAAEnC,aAAU;IAAE,CAAA;IAC5EoC,OACE,gBAAA,GAAA,EAAA,UAAA,CACGlC,KAAc,gBAACmC,KAAAA;KAAEC,WAAU;gBAAgDpC,GAAW,IAAA;QACvF,gBAACmC,KAAAA;KAAEC,WAAU;eAAyBrC;;;GAI5C,gBAACL,GAAAA,EAAQ2C,SAAQ,IAAA,CAAA;GAChBpC,EAAS0B,KAAK,EAAEN,YAASa,UAAOI,kBAC/B,gBAAC9C,GAAAA;IAA2E0C;IAAOK,MAAM;cACtFD,EAASX,KAAKa,MAAAA;KAEb,IAAME,IACJtB,MAAkBC,MAFQD,MAAkB,aAAaE,MAAkB,eAGpDkB,EAAKP,OAAO7B,aAAaA,IAAWkB,MAAkBkB,EAAKhC;KAEpF,OACE,gBAACf,GAAAA;MAECsC,eAAeS,EAAKtC,SAASA,CAAAA;MAC7BgC,OACE,gBAACS,QAAAA;OAAKP,WAAU;kBACbI,EAAKN,OACL3B,EAASiC,EAAKhC,OAAO,CAAA;;MAG1BoC,UAAUF;QARLF,EAAKhC,OAAO;IAWvB,CAAA;MApBwB,GAAGa,EAAQ,GAAGE,EAAiBF,IAAU,CAAA;WAyBxEf,GAAOuC,iBAAiB,gBAACjD,GAAAA,EAAKc,WAAWJ,EAAMuC,cAAAA,CAAAA,CAAAA,EAAAA,CAAAA;;AAIxD;;;AC7EA,SAAgBG,EACdC,GACAC,GACAC,GAA0B;CAE1B,IAAMC,IAAeN,EAAgBI,CAAAA,GAC/BG,KAAaC,MAAoB,CAACH,KAAmBA,EAAgBI,SAASD,CAAAA,GAE9EE,IAA6B,CAAA,GAC7BJ,EAAa,OAAW,UAAaC,EAAU,QAAA,IAC/C,CACE;EACEC,SAAS;EACTG,OAAOC,EAAAA,EAAC,EAAA,IAAA,SAAO,CAAA;EACfC,WAAWC,MAAoBA,EAAI;GAAEC,IAAI;GAAuCC,QAAQ,EAAEb,aAAU;EAAE,CAAA;EACtGa,QAAQ,EAAEb,aAAU;CACtB,CAAA,IAEF,CAAA,GAAA,GACAG,GAAe,SAAa,QAAWC,EAAU,SAAA,IACjD,CACE;EACEC,SAAS;EACTG,OAAOC,EAAAA,EAAC,EAAA,IAAA,SAAQ,CAAA;EAChBC,WAAWC,MAAoBA,EAAI;GAAEC,IAAI;GAAwCC,QAAQ,EAAEb,aAAU;EAAE,CAAA;EACvGa,QAAQ,EAAEb,aAAU;CACtB,CAAA,IAEF,CAAA,CAAA,GAGAc,IAA6BX,EAAa,UAC5C,CAAA,GACMC,EAAU,gBAAA,IACV,CACE;EACEC,SAAS;EACTG,OAAOC,EAAAA,EAAC,EAAA,IAAA,SAAgB,CAAA;EACxBC,WAAWC,MACTA,EAAI;GAAEC,IAAI;GAA+CC,QAAQ,EAAEb,aAAU;EAAE,CAAA;EACjFa,QAAQ,EAAEb,aAAU;CACtB,CAAA,IAEF,CAAA,GAAA,GACAI,EAAU,aAAA,IACV,CACE;EACEC,SAAS;EACTG,OAAOC,EAAAA,EAAC,EAAA,IAAA,SAAa,CAAA;EACrBC,WAAWC,MACTA,EAAI;GAAEC,IAAI;GAA4CC,QAAQ,EAAEb,aAAU;EAAE,CAAA;EAC9Ea,QAAQ,EAAEb,aAAU;CACtB,CAAA,IAEF,CAAA,CAAA,IAEN,CAAA,GAEEe,IAA6B,CAAA,GAC7BZ,IAAe,iBAAkB,SAAYC,EAAU,YAAA,IACvD,CACE;EACEC,SAAS;EACTG,OAAOC,EAAAA,EAAC,EAAA,IAAA,SAAuB,CAAA;EAC/BC,WAAWC,MACTA,EAAI;GACFC,IAAI;GACJC,QAAQ;IAAEb;IAAWgB,UAAU;IAASC,aAAa;GAAa;EACpE,CAAA;EACFJ,QAAQ;GAAEb;GAAWgB,UAAU;GAASC,aAAa;EAAa;CACpE,CAAA,IAEF,CAAA,GAAA,GACAd,IAAe,sBAAuB,QAAWC,EAAU,iBAAA,IAC3D,CACE;EACEC,SAAS;EACTG,OAAOC,EAAAA,EAAC,EAAA,IAAA,SAAsB,CAAA;EAC9BC,WAAWC,MACTA,EAAI;GACFC,IAAI;GACJC,QAAQ;IAAEb;IAAWgB,UAAU;IAAQC,aAAa;GAAU;EAChE,CAAA;EACFJ,QAAQ;GAAEb;GAAWgB,UAAU;GAAQC,aAAa;EAAU;CAChE,CAAA,IAEF,CAAA,CAAA,GAGAC,IAA4BpB,EAAmBK,GAAcD,CAAAA,IAC/D,CACE;EACEG,SAAS;EACTG,OAAOC,EAAAA,EAAC,EAAA,IAAA,SAAa,CAAA;EACrBC,WAAWC,MAAoBA,EAAI;GAAEC,IAAI;GAAqCC,QAAQ,EAAEb,aAAU;EAAE,CAAA;EACpGa,QAAQ,EAAEb,aAAU;CACtB,CAAA,IAEF,CAAA;CAEJ,OAAO;EACL;GAAEmB,SAAS;GAAWX,OAAOC,EAAAA,EAAC,EAAA,IAAA,SAAQ,CAAA;GAAGW,UAAUb;EAAgB;EACnE;GAAEY,SAAS;GAAWX,OAAOC,EAAAA,EAAC,EAAA,IAAA,SAAQ,CAAA;GAAGW,UAAUN;EAAgB;EACnE;GAAEK,SAAS;GAAWX,OAAOC,EAAAA,EAAC,EAAA,IAAA,SAAQ,CAAA;GAAGW,UAAUL;EAAgB;EACnE;GAAEI,SAAS;GAAYX,OAAOC,EAAAA,EAAC,EAAA,IAAA,SAAS,CAAA;GAAGW,UAAUF;EAAe;GACpEG,QAAQC,MAAMA,EAAEF,SAASG,SAAS,CAAA;AACtC;;;AC3GA,SAAgBQ,EAAe,EAAEC,kBAAkC;CACjE,IAAM,EAAA,MAAA,GAAA,GAAA,MAAQC,EAAAA,GACRC,IAAWP,EAAAA,GACXQ,IAAUT,EAAAA,GACV,EAAEU,iBAAcR,EAAU,EAAES,QAAQ,GAAM,CAAA;CA6FhD,OACE,gBAACb,GAAAA;EAAW8D,WAAU;YA5FJzD,QAAQ;GAC1B,IAAMU,IAA6C;IACjDC,SAASC,EAAAA,EAAC,EAAA,IAAA,SAAQ,CAAA;IAClBC,SAASD,EAAAA,EAAC,EAAA,IAAA,SAAQ,CAAA;IAClBE,SAASF,EAAAA,EAAC,EAAA,IAAA,SAAQ,CAAA;IAClBG,UAAUH,EAAAA,EAAC,EAAA,IAAA,SAAS,CAAA;IACpBI,QAAQJ,EAAAA,EAAC,EAAA,IAAA,SAAO,CAAA;IAChBK,SAASL,EAAAA,EAAC,EAAA,IAAA,SAAQ,CAAA;IAClB,mBAAmBA,EAAAA,EAAC,EAAA,IAAA,SAAgB,CAAA;IACpC,gBAAgBA,EAAAA,EAAC,EAAA,IAAA,SAAa,CAAA;IAC9B,gBAAgBA,EAAAA,EAAC,EAAA,IAAA,SAAa,CAAA;GAChC,GAEMM,KAAwBC,MACxBA,MAAa,UAAgBP,EAAAA,EAAC,EAAA,IAAA,SAAuB,CAAA,IACrDO,MAAa,SAAeP,EAAAA,EAAC,EAAA,IAAA,SAAsB,CAAA,IAChDA,EAAAA,EAAC,EAAA,IAAA,SAAQ,CAAA,GAGZQ,IAA8F,CAAA;GAEpGA,EAAMC,KAAK;IAAEC,MAAM;IAAQC,OAAOX,EAAAA,EAAC,EAAA,IAAA,SAAK,CAAA;IAAGY,eAAenB,EAAS,EAAEoB,IAAI,YAAY,CAAA;GAAG,CAAA;GAExF,IAAMC,IAAiBpB,EAAQqB,QAC5BC,MAAMA,EAAEC,YAAY,gCAAgCD,EAAEC,QAAQC,WAAW,4BAAA,CAAA,GAEtEC,IAAUL,EAAeA,EAAeM,SAAS,IAEjDC,IAAOF,KAAW9B,EAAY8B,EAAQG,UAAU,IAAIH,EAAQG,aAAaC,KAAAA,GAEzEC,IAAgBjC,EAAYkC,QAAQC,OAAO,GAAGnC,EAAYkC,OAAOC,KAAK,GAAGnC,EAAYmC,SAASnC,EAAYmC;GAEhH,IAAI,CAACP,KAAW,CAACE,GAEf,OADAb,EAAMC,KAAK;IAAEE,OAAOa;IAAeG,QAAQ;GAAK,CAAA,GACzCnB;GAGTA,EAAMC,KAAK;IACTE,OAAOa;IACPZ,eAAenB,EAAS;KAAEoB,IAAI;KAAwBe,QAAQ,EAAEjC,aAAU;IAAE,CAAA;GAC9E,CAAA;GAEA,IAAMiC,IAAST,EAAQS;GAEvB,IAAIP,EAAKQ,cAAchB,IAAI;IACzB,IAAM,EAAEiB,aAAUjB,UAAOQ,EAAKQ,cACxBlB,IAAQmB,IAAWhC,EAAYgC,KAAYP,KAAAA;IACjDf,EAAMC,KAAK;KAAEE;KAAOC,eAAenB,EAAS;MAAMoB;MAAqBe;KAAgB,CAAA;IAAG,CAAA;GAC5F;GAEA,IAAIP,EAAKU,OAAO;IACd,IAAM,EAAED,aAAUjB,OAAImB,uBAAoBX,EAAKU,OACzCE,IAAgBD,IAClB1B,EAAqBsB,EAAOI,EAAgB,IAC5CF,IACEhC,EAAYgC,KACZP,KAAAA;IAEN,IAAIF,EAAKa,UAAU;KAGjB,IAFA1B,EAAMC,KAAK;MAAEE,OAAOsB;MAAerB,eAAenB,EAAS;OAAMoB;OAAqBe;MAAgB,CAAA;KAAG,CAAA,GAErGP,EAAKc,mBAAmB;MAC1B,IAAM,EAAEtB,IAAIuB,GAAKJ,iBAAiBK,GAAQC,6BAA0BjB,EAAKc,mBAEnEK,IADc1B,EAAeA,EAAeM,SAAS,IAC1BqB,MAAMC,MAAM1B,MAAMA,KAAK,QAAQ,WAAWA,CAAAA,GAAI2B,OACzEC,IAASN,IACVE,MAAgBH,IAAST,EAAOS,KAAUd,KAAAA,KAC3Cc,IACET,EAAOS,KACPd,KAAAA;MACNf,EAAMC,KACJ2B,IACI;OAAEzB,OAAOiC;OAAQhC,eAAenB,EAAS;QAAEoB,IAAIuB;QAAsBR;OAAgB,CAAA;MAAG,IACxF,EAAEjB,OAAOiC,EAAO,CAAA;KAExB;KAEA,IAAMD,IAAQxB,EAAQsB,MAAMC,MAAM1B,MAAMA,KAAK,QAAQ,WAAWA,CAAAA,GAAI2B;KACpE,AAAIA,KAAOnC,EAAMC,KAAK;MAAEE,OAAOgC;MAAOhB,QAAQ;KAAK,CAAA;IACrD,OACEnB,EAAMC,KACJI,IACI;KAAEF,OAAOsB;KAAerB,eAAenB,EAAS;MAAMoB;MAAqBe;KAAgB,CAAA;IAAG,IAC9F;KAAEjB,OAAOsB;KAAeN,QAAQ;IAAK,CAAA;GAG/C;GAEA,OAAOnB;EACT,GAAG;GAACd;GAASH;GAAaI;GAAWF;;GAIhCI,EAAYiD,KAAKC,GAAMC,MACtB,gBAAChE,GAAAA;GAA2B2B,OAAOoC,EAAKpC;GAAOD,MAAMqC,EAAKrC;GAAME,SAASmC,EAAKnC;GAASe,QAAQoB,EAAKpB;KAA/EqB,CAAAA,CAAAA;;AAI7B;;;ACpDA,SAASY,IAAAA;CACP,IAAM,EAAEC,sBAAmBC,cAAWC,iBAAcC,mBAAgBd,EAAc,EAChFe,MAAMN,EAAMO,GACd,CAAA,GACM,EAAEC,uBAAoBhB,EAAgB,EAAEiB,QAAQ,GAAM,CAAA;CAO5D,OACE,gBAAC,GAAA;EACC,UAAQ;EACR,gBACE,gBAAC,GAAA;GACWC,UAVDX,QACTF,EAAiBM,GAAWD,GAAoBM,CAAAA,GACtD;IAACL;IAAWD;IAAmBM;IAQfE;GACCP;GACX,aAAaC,GAAcO,QAAQR;GACnC,YAAYE,GAAaM;;EAG7B,WAAU;YAEV,gBAAC,GAAA,EAAA,UACC,gBAAC,GAAA;GAAM,WAAU;GAAW,cAAa;GAAQ,WAAU;GAAU,WAAU;GAAc,KAAI;aAE/F,gBAAC,OAAA;IAAI,WAAU;eACb,gBAAC,GAAA,EACC,aAAa;KACXJ,IAAIJ;KACJQ,MAAMP,GAAcO,QAAQR;KAC5BS,QAAQR,GAAcQ;IACxB,EAAA,CAAA,GAEF,gBAAC,GAAA,CAAA,CAAA,CAAA;;;;AAMb"}
|
|
@@ -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-
|
|
3
|
+
import { t as n } from "./_projectId-B4ScifQf.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-
|
|
25
|
+
//# sourceMappingURL=_projectId-DxgPaiA7.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_projectId-
|
|
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"}
|
|
@@ -2,7 +2,7 @@ import { t as e } from "./helpers-1PpYf-fC.mjs";
|
|
|
2
2
|
import { createFileRoute as t, lazyRouteComponent as n, redirect as r } from "@tanstack/react-router";
|
|
3
3
|
import { z as i } from "zod";
|
|
4
4
|
//#region src/client/routes/_auth/projects/$projectId/storage/$provider/$storageType/index.tsx
|
|
5
|
-
var a = () => import("./_storageType-zeSZe--V.mjs"), o = () => import("./_storageType-
|
|
5
|
+
var a = () => import("./_storageType-zeSZe--V.mjs"), o = () => import("./_storageType-WwAATdsa.mjs"), s = (t, n) => {
|
|
6
6
|
let { provider: i, projectId: a } = n, o = e(t);
|
|
7
7
|
if (!o["object-store"]) throw r({
|
|
8
8
|
to: "/projects/$projectId",
|
|
@@ -78,4 +78,4 @@ var a = () => import("./_storageType-zeSZe--V.mjs"), o = () => import("./_storag
|
|
|
78
78
|
//#endregion
|
|
79
79
|
export { l as t };
|
|
80
80
|
|
|
81
|
-
//# sourceMappingURL=_storageType-
|
|
81
|
+
//# sourceMappingURL=_storageType-DsbBodWF.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_storageType-
|
|
1
|
+
{"version":3,"file":"_storageType-DsbBodWF.mjs","names":["createFileRoute","redirect","z","getServiceIndex","checkServiceAvailability","availableServices","type","name","params","projectId","provider","serviceIndex","to","hasSwift","Boolean","hasCeph","cephFallbackEnabled","hasEffectiveCeph","fallbackProvider","fallbackStorageType","storageType","containersSearchSchema","object","sortBy","enum","optional","sortDirection","search","string","Route","staticData","section","service","sectionCrumb","labelKey","crumb","useParamAsLabel","analytics","RouteInfo","validateSearch","head","match","meta","title","component","lazyRouteComponent","$$splitComponentImporter","notFoundComponent","$$splitNotFoundComponentImporter","loader","context","trpcClient","auth","getAvailableServices","query","client","beforeLoad"],"sources":["../../src/client/routes/_auth/projects/$projectId/storage/$provider/$storageType/index.tsx"],"sourcesContent":["import { createFileRoute, redirect, useParams } from \"@tanstack/react-router\"\nimport { z } from \"zod\"\nimport { getServiceIndex } from \"@/server/Authentication/helpers\"\nimport { ErrorBoundary } from \"react-error-boundary\"\nimport { SwiftContainers } from \"../../-components/Swift/Containers\"\nimport { CephBuckets } from \"../../-components/Ceph/Buckets\"\nimport { Trans, useLingui } from \"@lingui/react/macro\"\nimport type { RouteInfo } from \"@/client/routes/routeInfo\"\nimport { ContentHeader } from \"@/client/components/ContentHeader/ContentHeader\"\n\n/**\n * Validates that the requested storage provider is available for the given project,\n * and redirects to an appropriate fallback route when it is not.\n *\n * Redirect rules (in priority order):\n * 1. No `object-store` service at all → redirect to the project overview.\n * 2. Unknown provider (neither \"swift\" nor \"ceph\") → redirect to the first\n * available provider, or to the project overview if none exist.\n * 3. Requested provider unavailable → redirect to the other provider,\n * or to the project overview if no alternative exists.\n *\n * Ceph has a temporary fallback flag (`cephFallbackEnabled`) that treats it as\n * available even when absent from the OpenStack service catalog.\n *\n * @throws {redirect} - Always throws a TanStack Router redirect; never returns normally\n * when the requested provider/project combination is unavailable.\n */\nexport const checkServiceAvailability = (\n availableServices: {\n type: string\n name: string\n }[],\n params: {\n projectId: string\n provider: string\n }\n) => {\n const { provider, projectId } = params\n\n const serviceIndex = getServiceIndex(availableServices)\n\n // Redirect to the \"Projects Overview\" page if no storage services available\n if (!serviceIndex[\"object-store\"]) {\n throw redirect({\n to: \"/projects/$projectId\",\n params: { projectId },\n })\n }\n\n // Check provider availability\n const hasSwift = Boolean(serviceIndex[\"object-store\"][\"swift\"])\n const hasCeph = Boolean(serviceIndex[\"object-store\"][\"ceph\"])\n\n // TEMPORARY: Allow Ceph access even if not in catalog (relies on env config)\n // TODO: Properly register Ceph in OpenStack service catalog\n const cephFallbackEnabled = true // Set to false once Ceph is in catalog\n\n // Effective availability includes fallback flag for Ceph\n const hasEffectiveCeph = hasCeph || cephFallbackEnabled\n const fallbackProvider = hasSwift ? \"swift\" : hasEffectiveCeph ? \"ceph\" : null\n const fallbackStorageType = hasSwift ? \"containers\" : hasEffectiveCeph ? \"buckets\" : null\n\n if (provider !== \"swift\" && provider !== \"ceph\") {\n if (!fallbackProvider || !fallbackStorageType) {\n throw redirect({\n to: \"/projects/$projectId\",\n params: { projectId },\n })\n }\n throw redirect({\n to: \"/projects/$projectId/storage/$provider/$storageType\",\n params: { ...params, provider: fallbackProvider, storageType: fallbackStorageType },\n })\n }\n\n if (provider === \"swift\" && !hasSwift) {\n if (!hasEffectiveCeph) {\n throw redirect({\n to: \"/projects/$projectId\",\n params: { projectId },\n })\n }\n\n throw redirect({\n to: \"/projects/$projectId/storage/$provider/$storageType\",\n params: { ...params, provider: \"ceph\", storageType: \"buckets\" },\n })\n }\n\n if (provider === \"ceph\" && !hasEffectiveCeph) {\n if (!hasSwift) {\n throw redirect({\n to: \"/projects/$projectId\",\n params: { projectId },\n })\n }\n\n throw redirect({\n to: \"/projects/$projectId/storage/$provider/$storageType\",\n params: { ...params, provider: \"swift\", storageType: \"containers\" },\n })\n }\n}\n\n// Search params schema\n// - sortBy: active sort column — persisted for deep links and back navigation\n// - sortDirection: \"asc\" | \"desc\" — persisted alongside sortBy\n// - search: active filter string — persisted so deep links preserve the current search\nconst containersSearchSchema = z.object({\n sortBy: z.enum([\"name\", \"count\", \"bytes\", \"last_modified\"]).optional(),\n sortDirection: z.enum([\"asc\", \"desc\"]).optional(),\n search: z.string().optional(),\n})\n\nexport const Route = createFileRoute(\"/_auth/projects/$projectId/storage/$provider/$storageType/\")({\n staticData: {\n section: \"storage\",\n service: \"containers\",\n sectionCrumb: { labelKey: \"Storage\" },\n crumb: { useParamAsLabel: \"provider\" },\n analytics: {\n name: \"storage.objectstore.list\",\n },\n } satisfies RouteInfo,\n validateSearch: containersSearchSchema,\n head: ({ match }) => ({\n meta: [\n {\n title:\n match.params.provider === \"swift\"\n ? \"Object Storage (Swift)\"\n : match.params.provider === \"ceph\"\n ? \"Object Storage (Ceph)\"\n : \"Storage Overview\",\n },\n ],\n }),\n component: () => {\n return <StorageDashboard />\n },\n notFoundComponent: () => {\n return <p>Storage service not found</p>\n },\n loader: async ({ context }) => {\n const { trpcClient } = context\n const availableServices = await trpcClient?.auth.getAvailableServices.query()\n\n return {\n client: trpcClient,\n availableServices,\n }\n },\n beforeLoad: async ({ context, params }) => {\n const { trpcClient } = context\n const availableServices = await trpcClient?.auth.getAvailableServices.query()\n checkServiceAvailability(availableServices!, params)\n },\n})\n\nfunction StorageDashboard() {\n const { project, provider } = useParams({\n from: \"/_auth/projects/$projectId/storage/$provider/$storageType/\",\n select: (params) => {\n return { project: params.projectId, provider: params.provider, storageType: params.storageType }\n },\n })\n\n const { t } = useLingui()\n\n let pageTitle: string\n switch (provider) {\n case \"swift\":\n pageTitle = t`Object Storage (Swift)`\n break\n case \"ceph\":\n pageTitle = t`Object Storage (Ceph)`\n break\n default:\n pageTitle = t`Storage Overview`\n }\n\n return (\n <div>\n <ContentHeader title={pageTitle} projectId={project} />\n {project ? (\n <ErrorBoundary\n resetKeys={[project, provider]}\n fallback={\n <div className=\"p-4 text-center\">\n <Trans>Error loading component</Trans>\n </div>\n }\n >\n {(() => {\n switch (provider) {\n case \"swift\":\n return <SwiftContainers />\n case \"ceph\":\n return <CephBuckets />\n default:\n return <div>Storage Overview Page</div> // replace when available\n }\n })()}\n </ErrorBoundary>\n ) : (\n <div className=\"p-4 text-center\">\n <Trans>No project selected</Trans>\n </div>\n )}\n </div>\n )\n}\n"],"mappings":";;;;sGA2BaI,KACXC,GAIAG,MAAAA;CAKA,IAAM,EAAEE,aAAUD,iBAAcD,GAE1BG,IAAeR,EAAgBE,CAAAA;CAGrC,IAAI,CAACM,EAAa,iBAChB,MAAMV,EAAS;EACbW,IAAI;EACJJ,QAAQ,EAAEC,aAAU;CACtB,CAAA;CAIF,IAAMI,IAAWC,EAAQH,EAAa,gBAAgB,OAQhDM,IAPUH,EAAQH,EAAa,gBAAgB,QAOjBK,IAC9BE,IAAmBL,IAAW,UAAUI,IAAmB,SAAS,MACpEE,IAAsBN,IAAW,eAAeI,IAAmB,YAAY;CAErF,IAAIP,MAAa,WAAWA,MAAa,QAOvC,MALQT,EADJ,CAACiB,KAAoB,CAACC,IACT;EACbP,IAAI;EACJJ,QAAQ,EAAEC,aAAU;CACtB,IAEa;EACbG,IAAI;EACJJ,QAAQ;GAAE,GAAGA;GAAQE,UAAUQ;GAAkBE,aAAaD;EAAoB;CACpF,CALE;CAQJ,IAAIT,MAAa,WAAW,CAACG,GAQ3B,MAAMZ,EAPDgB,IAOU;EACbL,IAAI;EACJJ,QAAQ;GAAE,GAAGA;GAAQE,UAAU;GAAQU,aAAa;EAAU;CAChE,IATiB;EACbR,IAAI;EACJJ,QAAQ,EAAEC,aAAU;CACtB,CAAA;CASJ,IAAIC,MAAa,UAAU,CAACO,GAQ1B,MAAMhB,EAPDY,IAOU;EACbD,IAAI;EACJJ,QAAQ;GAAE,GAAGA;GAAQE,UAAU;GAASU,aAAa;EAAa;CACpE,IATiB;EACbR,IAAI;EACJJ,QAAQ,EAAEC,aAAU;CACtB,CAAA;AAQN,GAMMY,IAAyBnB,EAAEoB,OAAO;CACtCC,QAAQrB,EAAEsB,KAAK;EAAC;EAAQ;EAAS;EAAS;EAAgB,EAAEC,SAAQ;CACpEC,eAAexB,EAAEsB,KAAK,CAAC,OAAO,MAAA,CAAO,EAAEC,SAAQ;CAC/CE,QAAQzB,EAAE0B,OAAM,EAAGH,SAAQ;AAC7B,CAAA,GAEaI,IAAQ7B,EAAgB,4DAAA,EAA8D;CACjG8B,YAAY;EACVC,SAAS;EACTC,SAAS;EACTC,cAAc,EAAEC,UAAU,UAAU;EACpCC,OAAO,EAAEC,iBAAiB,WAAW;EACrCC,WAAW,EACT9B,MAAM,2BACR;CACF;CACAgC,gBAAgBlB;CAChBmB,OAAO,EAAEC,gBAAa,EACpBC,MAAM,CACJ,EACEC,OACEF,EAAMjC,OAAOE,aAAa,UACtB,2BACA+B,EAAMjC,OAAOE,aAAa,SACxB,0BACA,mBACV,CAAA,EAEJ;CACAkC,WAASC,EAAAC,GAAA,WAAA;CAGTC,mBAAiBF,EAAAG,GAAA,mBAAA;CAGjBC,QAAQ,OAAO,EAAEC,iBAAS;EACxB,IAAM,EAAEC,kBAAeD;EAGvB,OAAO;GACLK,QAAQJ;GACR9C,mBAAAA,MAJ8B8C,GAAYC,KAAKC,qBAAqBC,MAAAA;EAKtE;CACF;CACAE,YAAY,OAAO,EAAEN,YAAS1C,gBAAQ;EACpC,IAAM,EAAE2C,kBAAeD;EAEvB9C,EAAyBC,MADO8C,GAAYC,KAAKC,qBAAqBC,MAAAA,GACzB9C,CAAAA;CAC/C;AACF,CAAA"}
|