@cobaltcore-dev/aurora 0.20.0 → 0.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/{_projectId-b4NGeECR.mjs → _projectId-CoMD6nq6.mjs} +7 -7
- package/dist/client/_projectId-CoMD6nq6.mjs.map +1 -0
- package/dist/client/{_projectId-Ig3QqYzt.mjs → _projectId-MjFv839t.mjs} +2 -2
- package/dist/client/_projectId-MjFv839t.mjs.map +1 -0
- package/dist/client/{_projectId-Cq9JnhRj.mjs → _projectId-hMtrqW6m.mjs} +3 -3
- package/dist/client/_projectId-hMtrqW6m.mjs.map +1 -0
- package/dist/client/index.js +2 -2
- package/dist/client/projects-4wFhznPY.mjs +34 -0
- package/dist/client/projects-4wFhznPY.mjs.map +1 -0
- package/dist/client/projects-DvSiI5Vg.mjs.map +1 -1
- package/dist/client/{projects-sn_XPdCh.mjs → projects-wd6Yc-GU.mjs} +2 -2
- package/dist/client/{projects-sn_XPdCh.mjs.map → projects-wd6Yc-GU.mjs.map} +1 -1
- package/dist/client/projects-yiK0HGSA.mjs.map +1 -1
- package/dist/client/trpcClient.d.ts +37 -0
- package/dist/server/index.d.ts +37 -0
- package/dist/server/index.js +44 -29
- package/package.json +1 -1
- package/dist/client/_projectId-Cq9JnhRj.mjs.map +0 -1
- package/dist/client/_projectId-Ig3QqYzt.mjs.map +0 -1
- package/dist/client/_projectId-b4NGeECR.mjs.map +0 -1
- package/dist/client/projects-CDFynG9q.mjs +0 -23
- package/dist/client/projects-CDFynG9q.mjs.map +0 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createFileRoute as e, lazyRouteComponent as t } from "@tanstack/react-router";
|
|
2
2
|
import { TRPCClientError as n } from "@trpc/client";
|
|
3
3
|
//#region src/client/routes/_auth/projects/-components/resolveProjectScope.ts
|
|
4
|
-
var r = (e) => e.scopeProject?.id === e.projectId ? "valid" : e.
|
|
5
|
-
component: t(() => import("./_projectId-
|
|
6
|
-
errorComponent: t(() => import("./_projectId-
|
|
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-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 {
|
|
@@ -15,17 +15,17 @@ var r = (e) => e.scopeProject?.id === e.projectId ? "valid" : e.userProjects ==
|
|
|
15
15
|
if (e instanceof n && e.data?.code === "NOT_FOUND") a = void 0;
|
|
16
16
|
else throw e;
|
|
17
17
|
}
|
|
18
|
-
let [o, s] = await Promise.all([t.trpcClient?.auth.getAvailableServices.query(), t.trpcClient?.project.
|
|
18
|
+
let [o, s] = await Promise.all([t.trpcClient?.auth.getAvailableServices.query(), t.trpcClient?.project.getProject.query({ projectId: i.projectId }).catch(() => null)]), c = r({
|
|
19
19
|
projectId: i.projectId,
|
|
20
20
|
scopeProject: a?.project,
|
|
21
|
-
|
|
21
|
+
userProject: s
|
|
22
22
|
});
|
|
23
23
|
if (c === "scope_failed") throw Error("Failed to scope session to project");
|
|
24
24
|
if (c === "not_found") {
|
|
25
25
|
let e = /* @__PURE__ */ Error("Project not found");
|
|
26
26
|
throw e.statusCode = 404, e;
|
|
27
27
|
}
|
|
28
|
-
let l = a?.domain?.id || "", u = s?.
|
|
28
|
+
let l = a?.domain?.id || "", u = s?.description ?? null;
|
|
29
29
|
return {
|
|
30
30
|
trpcClient: t.trpcClient,
|
|
31
31
|
crumbDomain: {
|
|
@@ -43,4 +43,4 @@ var r = (e) => e.scopeProject?.id === e.projectId ? "valid" : e.userProjects ==
|
|
|
43
43
|
//#endregion
|
|
44
44
|
export { i as t };
|
|
45
45
|
|
|
46
|
-
//# sourceMappingURL=_projectId-
|
|
46
|
+
//# sourceMappingURL=_projectId-CoMD6nq6.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
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,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-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-
|
|
25
|
+
//# sourceMappingURL=_projectId-MjFv839t.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
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,6 +1,6 @@
|
|
|
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
2
|
import { t as u } from "./Slot-CWb612-_.mjs";
|
|
3
|
-
import { t as d } from "./_projectId-
|
|
3
|
+
import { t as d } from "./_projectId-CoMD6nq6.mjs";
|
|
4
4
|
import { t as f } from "./helpers-1PpYf-fC.mjs";
|
|
5
5
|
import { t as p } from "./pcaAccess-Br4kVjLQ.mjs";
|
|
6
6
|
import { t as m } from "./routeInfo-CdTRlSxv.mjs";
|
|
@@ -107,7 +107,7 @@ function A(e, t, n) {
|
|
|
107
107
|
provider: "swift",
|
|
108
108
|
storageType: "containers"
|
|
109
109
|
}
|
|
110
|
-
}] : [], ...i("ceph-containers") ? [{
|
|
110
|
+
}] : [], ...r?.["object-store-ceph"]?.ceph && i("ceph-containers") ? [{
|
|
111
111
|
service: "ceph-containers",
|
|
112
112
|
label: O._({ id: "KhNDX4" }),
|
|
113
113
|
navigate: (t) => t({
|
|
@@ -288,4 +288,4 @@ function M() {
|
|
|
288
288
|
//#endregion
|
|
289
289
|
export { M as component };
|
|
290
290
|
|
|
291
|
-
//# sourceMappingURL=_projectId-
|
|
291
|
+
//# sourceMappingURL=_projectId-hMtrqW6m.mjs.map
|
|
@@ -0,0 +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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"}
|
package/dist/client/index.js
CHANGED
|
@@ -6,8 +6,8 @@ import { t as C } from "./Slot-CWb612-_.mjs";
|
|
|
6
6
|
import { t as w } from "./RouteError-BmZRk_ss.mjs";
|
|
7
7
|
import { t as ee } from "./StatusError-CLB026fg.mjs";
|
|
8
8
|
import { t as te } from "./useErrorTranslation-Dc0eE8Zt.mjs";
|
|
9
|
-
import { t as ne } from "./projects-
|
|
10
|
-
import { t as re } from "./_projectId-
|
|
9
|
+
import { t as ne } from "./projects-4wFhznPY.mjs";
|
|
10
|
+
import { t as re } from "./_projectId-CoMD6nq6.mjs";
|
|
11
11
|
import { t as T } from "./helpers-1PpYf-fC.mjs";
|
|
12
12
|
import { t as ie } from "./pcaAccess-Br4kVjLQ.mjs";
|
|
13
13
|
import { t as ae } from "./pca-B4_2DqT2.mjs";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { K as e, tt as t } from "./build-DgeVmENN.mjs";
|
|
2
|
+
import { jsx as n, jsxs as r } from "react/jsx-runtime";
|
|
3
|
+
import { createFileRoute as i, lazyRouteComponent as a } from "@tanstack/react-router";
|
|
4
|
+
import { z as o } from "zod";
|
|
5
|
+
//#region src/client/routes/_auth/projects/index.tsx
|
|
6
|
+
var s = () => import("./projects-yiK0HGSA.mjs"), c = () => import("./projects-DvSiI5Vg.mjs"), l = () => import("./projects-wd6Yc-GU.mjs"), u = o.object({ search: o.string().optional() }), d = i("/_auth/projects/")({
|
|
7
|
+
staticData: { analytics: { name: "projects.list" } },
|
|
8
|
+
component: a(l, "component"),
|
|
9
|
+
pendingComponent: () => /*#__PURE__*/ r(t, {
|
|
10
|
+
className: "fixed inset-0",
|
|
11
|
+
distribution: "center",
|
|
12
|
+
alignment: "center",
|
|
13
|
+
children: [/*#__PURE__*/ n("div", { className: "absolute inset-0 backdrop-blur-sm" }), /*#__PURE__*/ n(e, {
|
|
14
|
+
variant: "primary",
|
|
15
|
+
size: "large"
|
|
16
|
+
})]
|
|
17
|
+
}),
|
|
18
|
+
errorComponent: a(c, "errorComponent"),
|
|
19
|
+
notFoundComponent: a(s, "notFoundComponent"),
|
|
20
|
+
validateSearch: u,
|
|
21
|
+
loaderDeps: ({ search: e }) => ({ search: e.search || "" }),
|
|
22
|
+
loader: async ({ context: e, deps: t }) => {
|
|
23
|
+
let n = await e.trpcClient?.project.getAuthProjects.query(), r = n;
|
|
24
|
+
if (t.search && t.search.trim() !== "") {
|
|
25
|
+
let e = t.search.toLowerCase();
|
|
26
|
+
r = n?.filter((t) => t.name?.toLowerCase().includes(e) || t.description?.toLowerCase().includes(e));
|
|
27
|
+
}
|
|
28
|
+
return { projects: r };
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
//#endregion
|
|
32
|
+
export { d as t };
|
|
33
|
+
|
|
34
|
+
//# sourceMappingURL=projects-4wFhznPY.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projects-4wFhznPY.mjs","names":["createFileRoute","Spinner","Stack","z","searchSchema","object","search","string","optional","Route","staticData","analytics","name","RouteInfo","component","lazyRouteComponent","$$splitComponentImporter","pendingComponent","errorComponent","$$splitErrorComponentImporter","notFoundComponent","$$splitNotFoundComponentImporter","validateSearch","loaderDeps","loader","context","deps","allProjects","trpcClient","project","getAuthProjects","query","projects","trim","searchTermLower","toLowerCase","filter","includes","description"],"sources":["../../src/client/routes/_auth/projects/index.tsx"],"sourcesContent":["import { createFileRoute } from \"@tanstack/react-router\"\nimport { useRouteContext } from \"@tanstack/react-router\"\nimport { ProjectsOverviewNavBar } from \"@/client/routes/_auth/projects/-components/ProjectOverviewNavBar\"\nimport { ProjectCardView } from \"@/client/routes/_auth/projects/-components/ProjectCardView\"\nimport { RouteError } from \"@/client/components/Error/RouteError\"\nimport { TRPCClientError } from \"@trpc/client\"\nimport { Container, ContentHeading, Spinner, Stack } from \"@cloudoperators/juno-ui-components\"\nimport { Trans } from \"@lingui/react/macro\"\nimport { z } from \"zod\"\nimport { Slot } from \"@/client/components/Slot\"\nimport type { RouteInfo } from \"@/client/routes/routeInfo\"\n\nconst searchSchema = z.object({\n search: z.string().optional(),\n})\n\nexport const Route = createFileRoute(\"/_auth/projects/\")({\n staticData: {\n analytics: {\n name: \"projects.list\",\n },\n } satisfies RouteInfo,\n component: ProjectsOverview,\n pendingComponent: () => (\n <Stack className=\"fixed inset-0\" distribution=\"center\" alignment=\"center\">\n <div className=\"absolute inset-0 backdrop-blur-sm\" />\n <Spinner variant=\"primary\" size=\"large\" />\n </Stack>\n ),\n errorComponent: ({ error }) => (\n <RouteError error={error} safeErrorMessage={error instanceof TRPCClientError ? error.message : undefined} />\n ),\n notFoundComponent: () => {\n return <p>Projects not found</p>\n },\n validateSearch: searchSchema,\n loaderDeps: ({ search }) => ({\n search: search.search || \"\",\n }),\n loader: async ({ context, deps }) => {\n const allProjects = await context.trpcClient?.project.getAuthProjects.query()\n\n let projects = allProjects\n if (deps.search && deps.search.trim() !== \"\") {\n const searchTermLower = deps.search.toLowerCase()\n projects = allProjects?.filter(\n (project) =>\n project.name?.toLowerCase().includes(searchTermLower) ||\n project.description?.toLowerCase().includes(searchTermLower)\n )\n }\n\n return { projects }\n },\n})\n\nfunction ProjectsOverview() {\n const { projects } = Route.useLoaderData()\n const { search = \"\" } = Route.useSearch()\n const navigate = Route.useNavigate()\n const { slots } = useRouteContext({ strict: false })\n\n const handleSearch = (value: string) => {\n navigate({ search: { search: value }, replace: true })\n }\n\n return (\n <Container py>\n <ContentHeading className=\"pb-4\">\n <Trans>Projects</Trans>\n </ContentHeading>\n {slots?.projectsBanner && <Slot component={slots.projectsBanner} useShadowDOM={false} />}\n <ProjectsOverviewNavBar searchTerm={search} onSearch={handleSearch} />\n <div className=\"pt-5\">\n <ProjectCardView projects={projects} />\n </div>\n </Container>\n )\n}\n"],"mappings":";;;;;2IAYMI,IAAeD,EAAEE,OAAO,EAC5BC,QAAQH,EAAEI,OAAM,EAAGC,SAAQ,EAC7B,CAAA,GAEaC,IAAQT,EAAgB,kBAAA,EAAoB;CACvDU,YAAY,EACVC,WAAW,EACTC,MAAM,gBACR,EACF;CACAE,WAASC,EAAAC,GAAA,WAAA;CACTC,wBACE,gBAAC,GAAA;EAAM,WAAU;EAAgB,cAAa;EAAS,WAAU;aAC/D,gBAAC,OAAA,EAAI,WAAU,oCAAA,CAAA,GACf,gBAAC,GAAA;GAAQ,SAAQ;GAAU,MAAK;;;CAGpCC,gBAAcH,EAAAI,GAAA,gBAAA;CAGdC,mBAAiBL,EAAAM,GAAA,mBAAA;CAGjBC,gBAAgBlB;CAChBmB,aAAa,EAAEjB,iBAAc,EAC3BA,QAAQA,EAAOA,UAAU,GAC3B;CACAkB,QAAQ,OAAO,EAAEC,YAASC,cAAM;EAC9B,IAAMC,IAAc,MAAMF,EAAQG,YAAYC,QAAQC,gBAAgBC,MAAAA,GAElEC,IAAWL;EACf,IAAID,EAAKpB,UAAUoB,EAAKpB,OAAO2B,KAAI,MAAO,IAAI;GAC5C,IAAMC,IAAkBR,EAAKpB,OAAO6B,YAAW;GAC/CH,IAAWL,GAAaS,QACrBP,MACCA,EAAQjB,MAAMuB,YAAAA,EAAcE,SAASH,CAAAA,KACrCL,EAAQS,aAAaH,YAAAA,EAAcE,SAASH,CAAAA,CAAAA;EAElD;EAEA,OAAO,EAAEF,YAAS;CACpB;AACF,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"projects-DvSiI5Vg.mjs","names":["RouteError","TRPCClientError","Route","SplitErrorComponent","error","message","undefined","errorComponent"],"sources":["../../src/client/routes/_auth/projects/index.tsx?tsr-split=errorComponent"],"sourcesContent":["import { createFileRoute } from \"@tanstack/react-router\"\nimport { useRouteContext } from \"@tanstack/react-router\"\nimport { ProjectsOverviewNavBar } from \"@/client/routes/_auth/projects/-components/ProjectOverviewNavBar\"\nimport { ProjectCardView } from \"@/client/routes/_auth/projects/-components/ProjectCardView\"\nimport { RouteError } from \"@/client/components/Error/RouteError\"\nimport { TRPCClientError } from \"@trpc/client\"\nimport { Container, ContentHeading } from \"@cloudoperators/juno-ui-components\"\nimport { Trans } from \"@lingui/react/macro\"\nimport { z } from \"zod\"\nimport { Slot } from \"@/client/components/Slot\"\nimport type { RouteInfo } from \"@/client/routes/routeInfo\"\n\nconst searchSchema = z.object({\n search: z.string().optional(),\n})\n\nexport const Route = createFileRoute(\"/_auth/projects/\")({\n staticData: {\n analytics: {\n name: \"projects.list\",\n },\n } satisfies RouteInfo,\n component: ProjectsOverview,\n errorComponent: ({ error }) => (\n <RouteError error={error} safeErrorMessage={error instanceof TRPCClientError ? error.message : undefined} />\n ),\n notFoundComponent: () => {\n return <p>Projects not found</p>\n },\n validateSearch: searchSchema,\n loaderDeps: ({ search }) => ({\n search: search.search || \"\",\n }),\n loader: async ({ context, deps }) => {\n const allProjects = await context.trpcClient?.project.getAuthProjects.query()\n\n let projects = allProjects\n if (deps.search && deps.search.trim() !== \"\") {\n const searchTermLower = deps.search.toLowerCase()\n projects = allProjects?.filter(\n (project) =>\n project.name?.toLowerCase().includes(searchTermLower) ||\n project.description?.toLowerCase().includes(searchTermLower)\n )\n }\n\n return { projects }\n },\n})\n\nfunction ProjectsOverview() {\n const { projects } = Route.useLoaderData()\n const { search = \"\" } = Route.useSearch()\n const navigate = Route.useNavigate()\n const { slots } = useRouteContext({ strict: false })\n\n const handleSearch = (value: string) => {\n navigate({ search: { search: value }, replace: true })\n }\n\n return (\n <Container py>\n <ContentHeading className=\"pb-4\">\n <Trans>Projects</Trans>\n </ContentHeading>\n {slots?.projectsBanner && <Slot component={slots.projectsBanner} useShadowDOM={false} />}\n <ProjectsOverviewNavBar searchTerm={search} onSearch={handleSearch} />\n <div className=\"pt-5\">\n <ProjectCardView projects={projects} />\n </div>\n </Container>\n )\n}\n"],"mappings":";;;;AAK8C,IAAAG,
|
|
1
|
+
{"version":3,"file":"projects-DvSiI5Vg.mjs","names":["RouteError","TRPCClientError","Route","SplitErrorComponent","error","message","undefined","errorComponent"],"sources":["../../src/client/routes/_auth/projects/index.tsx?tsr-split=errorComponent"],"sourcesContent":["import { createFileRoute } from \"@tanstack/react-router\"\nimport { useRouteContext } from \"@tanstack/react-router\"\nimport { ProjectsOverviewNavBar } from \"@/client/routes/_auth/projects/-components/ProjectOverviewNavBar\"\nimport { ProjectCardView } from \"@/client/routes/_auth/projects/-components/ProjectCardView\"\nimport { RouteError } from \"@/client/components/Error/RouteError\"\nimport { TRPCClientError } from \"@trpc/client\"\nimport { Container, ContentHeading, Spinner, Stack } from \"@cloudoperators/juno-ui-components\"\nimport { Trans } from \"@lingui/react/macro\"\nimport { z } from \"zod\"\nimport { Slot } from \"@/client/components/Slot\"\nimport type { RouteInfo } from \"@/client/routes/routeInfo\"\n\nconst searchSchema = z.object({\n search: z.string().optional(),\n})\n\nexport const Route = createFileRoute(\"/_auth/projects/\")({\n staticData: {\n analytics: {\n name: \"projects.list\",\n },\n } satisfies RouteInfo,\n component: ProjectsOverview,\n pendingComponent: () => (\n <Stack className=\"fixed inset-0\" distribution=\"center\" alignment=\"center\">\n <div className=\"absolute inset-0 backdrop-blur-sm\" />\n <Spinner variant=\"primary\" size=\"large\" />\n </Stack>\n ),\n errorComponent: ({ error }) => (\n <RouteError error={error} safeErrorMessage={error instanceof TRPCClientError ? error.message : undefined} />\n ),\n notFoundComponent: () => {\n return <p>Projects not found</p>\n },\n validateSearch: searchSchema,\n loaderDeps: ({ search }) => ({\n search: search.search || \"\",\n }),\n loader: async ({ context, deps }) => {\n const allProjects = await context.trpcClient?.project.getAuthProjects.query()\n\n let projects = allProjects\n if (deps.search && deps.search.trim() !== \"\") {\n const searchTermLower = deps.search.toLowerCase()\n projects = allProjects?.filter(\n (project) =>\n project.name?.toLowerCase().includes(searchTermLower) ||\n project.description?.toLowerCase().includes(searchTermLower)\n )\n }\n\n return { projects }\n },\n})\n\nfunction ProjectsOverview() {\n const { projects } = Route.useLoaderData()\n const { search = \"\" } = Route.useSearch()\n const navigate = Route.useNavigate()\n const { slots } = useRouteContext({ strict: false })\n\n const handleSearch = (value: string) => {\n navigate({ search: { search: value }, replace: true })\n }\n\n return (\n <Container py>\n <ContentHeading className=\"pb-4\">\n <Trans>Projects</Trans>\n </ContentHeading>\n {slots?.projectsBanner && <Slot component={slots.projectsBanner} useShadowDOM={false} />}\n <ProjectsOverviewNavBar searchTerm={search} onSearch={handleSearch} />\n <div className=\"pt-5\">\n <ProjectCardView projects={projects} />\n </div>\n </Container>\n )\n}\n"],"mappings":";;;;AAK8C,IAAAG,KAwB3B,EAAEC,eACjB,gBAAC,GAAA;CAAkBA;CAAO,kBAAkBA,aAAiBH,IAAkBG,EAAMC,UAAUC,KAAAA"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Y as e, Z as t, n, x as r } from "./build-DgeVmENN.mjs";
|
|
2
2
|
import { t as i } from "./Slot-CWb612-_.mjs";
|
|
3
|
-
import { t as a } from "./projects-
|
|
3
|
+
import { t as a } from "./projects-4wFhznPY.mjs";
|
|
4
4
|
import { jsx as o, jsxs as s } from "react/jsx-runtime";
|
|
5
5
|
import { useEffect as c, useRef as l, useState as u } from "react";
|
|
6
6
|
import { useNavigate as d, useRouteContext as f } from "@tanstack/react-router";
|
|
@@ -104,4 +104,4 @@ function v() {
|
|
|
104
104
|
//#endregion
|
|
105
105
|
export { v as component };
|
|
106
106
|
|
|
107
|
-
//# sourceMappingURL=projects-
|
|
107
|
+
//# sourceMappingURL=projects-wd6Yc-GU.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"projects-
|
|
1
|
+
{"version":3,"file":"projects-wd6Yc-GU.mjs","names":["useEffect","useRef","useState","SearchInput","ProjectsOverviewNavBar","onSearch","searchTerm","useLingui","timerRef","inputFocusedRef","inputValue","setInputValue","current","clearTimeout","handleSearchChange","e","value","target","setTimeout","handleClear","className","type","placeholder","t","onChange","onFocus","onBlur","onClear","useNavigate","Card","ProjectCard","project","navigate","padding","onClick","to","params","projectId","id","className","div","p","domain_name","domain_id","data-testid","name","description","ProjectCardView","projects","length","sort","a","b","localeCompare","map","useRouteContext","ProjectsOverviewNavBar","ProjectCardView","Container","ContentHeading","Trans","Slot","Route","ProjectsOverview","projects","useLoaderData","search","useSearch","navigate","useNavigate","slots","strict","handleSearch","value","replace","projectsBanner","component"],"sources":["../../src/client/routes/_auth/projects/-components/ProjectOverviewNavBar.tsx","../../src/client/routes/_auth/projects/-components/ProjectCardView.tsx","../../src/client/routes/_auth/projects/index.tsx?tsr-split=component"],"sourcesContent":["import { ChangeEvent, useEffect, useRef, useState } from \"react\"\nimport { SearchInput } from \"@cloudoperators/juno-ui-components\"\nimport { useLingui } from \"@lingui/react/macro\"\n\ntype ProjectsOverviewNavBarProps = {\n searchTerm?: string\n onSearch: (value: string) => void\n}\n\nexport function ProjectsOverviewNavBar({ onSearch, searchTerm = \"\" }: ProjectsOverviewNavBarProps) {\n const { t } = useLingui()\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null)\n const inputFocusedRef = useRef(false)\n const [inputValue, setInputValue] = useState(searchTerm)\n\n useEffect(() => {\n if (!inputFocusedRef.current && !timerRef.current && searchTerm !== inputValue) {\n setInputValue(searchTerm)\n }\n }, [searchTerm])\n\n useEffect(() => {\n return () => {\n if (timerRef.current) clearTimeout(timerRef.current)\n }\n }, [])\n\n const handleSearchChange = (e: ChangeEvent<HTMLInputElement>) => {\n const value = e.target.value\n setInputValue(value)\n if (timerRef.current) clearTimeout(timerRef.current)\n timerRef.current = setTimeout(() => {\n timerRef.current = null\n onSearch(value)\n }, 300)\n }\n\n const handleClear = () => {\n if (timerRef.current) {\n clearTimeout(timerRef.current)\n timerRef.current = null\n }\n setInputValue(\"\")\n onSearch(\"\")\n }\n\n return (\n <SearchInput\n className=\"w-full\"\n type=\"text\"\n placeholder={t`Search...`}\n onChange={handleSearchChange}\n onFocus={() => {\n inputFocusedRef.current = true\n }}\n onBlur={() => {\n inputFocusedRef.current = false\n }}\n onClear={handleClear}\n value={inputValue}\n />\n )\n}\n","import { useNavigate } from \"@tanstack/react-router\"\nimport { Project } from \"@/server/Project/types/models\"\nimport { Card } from \"@cloudoperators/juno-ui-components\"\nimport { Trans } from \"@lingui/react/macro\"\n\ntype ProjectCardProps = {\n project: Project\n}\ntype ProjectCardViewProps = {\n projects: Project[] | undefined\n}\n\nexport function ProjectCard({ project }: ProjectCardProps) {\n const navigate = useNavigate()\n return (\n <Card\n padding\n onClick={() => navigate({ to: \"/projects/$projectId\", params: { projectId: project.id } })}\n className=\"flex flex-col gap-4\"\n >\n <div className=\"flex min-w-0 flex-col\">\n <p className=\"text-theme-light text-xs leading-5 font-normal\">\n {project.domain_name ?? project.domain_id ?? <Trans>Unknown domain</Trans>}\n </p>\n <p data-testid=\"project-name\" className=\"text-theme-high text-lg leading-7 font-bold\">\n {project.name}\n </p>\n </div>\n {project.description && (\n <p className=\"text-theme-default line-clamp-2 text-sm font-normal\">{project.description}</p>\n )}\n </Card>\n )\n}\n\nexport function ProjectCardView({ projects }: ProjectCardViewProps) {\n return (\n <div className=\"w-full\">\n <div className=\"grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4\">\n {projects?.length ? (\n [...projects]\n .sort((a, b) => a.name.localeCompare(b.name))\n .map((project) => <ProjectCard key={project.id} project={project} />)\n ) : (\n <p>\n <Trans>No projects available.</Trans>\n </p>\n )}\n </div>\n </div>\n )\n}\n","import { createFileRoute } from \"@tanstack/react-router\"\nimport { useRouteContext } from \"@tanstack/react-router\"\nimport { ProjectsOverviewNavBar } from \"@/client/routes/_auth/projects/-components/ProjectOverviewNavBar\"\nimport { ProjectCardView } from \"@/client/routes/_auth/projects/-components/ProjectCardView\"\nimport { RouteError } from \"@/client/components/Error/RouteError\"\nimport { TRPCClientError } from \"@trpc/client\"\nimport { Container, ContentHeading, Spinner, Stack } from \"@cloudoperators/juno-ui-components\"\nimport { Trans } from \"@lingui/react/macro\"\nimport { z } from \"zod\"\nimport { Slot } from \"@/client/components/Slot\"\nimport type { RouteInfo } from \"@/client/routes/routeInfo\"\n\nconst searchSchema = z.object({\n search: z.string().optional(),\n})\n\nexport const Route = createFileRoute(\"/_auth/projects/\")({\n staticData: {\n analytics: {\n name: \"projects.list\",\n },\n } satisfies RouteInfo,\n component: ProjectsOverview,\n pendingComponent: () => (\n <Stack className=\"fixed inset-0\" distribution=\"center\" alignment=\"center\">\n <div className=\"absolute inset-0 backdrop-blur-sm\" />\n <Spinner variant=\"primary\" size=\"large\" />\n </Stack>\n ),\n errorComponent: ({ error }) => (\n <RouteError error={error} safeErrorMessage={error instanceof TRPCClientError ? error.message : undefined} />\n ),\n notFoundComponent: () => {\n return <p>Projects not found</p>\n },\n validateSearch: searchSchema,\n loaderDeps: ({ search }) => ({\n search: search.search || \"\",\n }),\n loader: async ({ context, deps }) => {\n const allProjects = await context.trpcClient?.project.getAuthProjects.query()\n\n let projects = allProjects\n if (deps.search && deps.search.trim() !== \"\") {\n const searchTermLower = deps.search.toLowerCase()\n projects = allProjects?.filter(\n (project) =>\n project.name?.toLowerCase().includes(searchTermLower) ||\n project.description?.toLowerCase().includes(searchTermLower)\n )\n }\n\n return { projects }\n },\n})\n\nfunction ProjectsOverview() {\n const { projects } = Route.useLoaderData()\n const { search = \"\" } = Route.useSearch()\n const navigate = Route.useNavigate()\n const { slots } = useRouteContext({ strict: false })\n\n const handleSearch = (value: string) => {\n navigate({ search: { search: value }, replace: true })\n }\n\n return (\n <Container py>\n <ContentHeading className=\"pb-4\">\n <Trans>Projects</Trans>\n </ContentHeading>\n {slots?.projectsBanner && <Slot component={slots.projectsBanner} useShadowDOM={false} />}\n <ProjectsOverviewNavBar searchTerm={search} onSearch={handleSearch} />\n <div className=\"pt-5\">\n <ProjectCardView projects={projects} />\n </div>\n </Container>\n )\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"projects-yiK0HGSA.mjs","names":["SplitNotFoundComponent","notFoundComponent"],"sources":["../../src/client/routes/_auth/projects/index.tsx?tsr-split=notFoundComponent"],"sourcesContent":["import { createFileRoute } from \"@tanstack/react-router\"\nimport { useRouteContext } from \"@tanstack/react-router\"\nimport { ProjectsOverviewNavBar } from \"@/client/routes/_auth/projects/-components/ProjectOverviewNavBar\"\nimport { ProjectCardView } from \"@/client/routes/_auth/projects/-components/ProjectCardView\"\nimport { RouteError } from \"@/client/components/Error/RouteError\"\nimport { TRPCClientError } from \"@trpc/client\"\nimport { Container, ContentHeading } from \"@cloudoperators/juno-ui-components\"\nimport { Trans } from \"@lingui/react/macro\"\nimport { z } from \"zod\"\nimport { Slot } from \"@/client/components/Slot\"\nimport type { RouteInfo } from \"@/client/routes/routeInfo\"\n\nconst searchSchema = z.object({\n search: z.string().optional(),\n})\n\nexport const Route = createFileRoute(\"/_auth/projects/\")({\n staticData: {\n analytics: {\n name: \"projects.list\",\n },\n } satisfies RouteInfo,\n component: ProjectsOverview,\n errorComponent: ({ error }) => (\n <RouteError error={error} safeErrorMessage={error instanceof TRPCClientError ? error.message : undefined} />\n ),\n notFoundComponent: () => {\n return <p>Projects not found</p>\n },\n validateSearch: searchSchema,\n loaderDeps: ({ search }) => ({\n search: search.search || \"\",\n }),\n loader: async ({ context, deps }) => {\n const allProjects = await context.trpcClient?.project.getAuthProjects.query()\n\n let projects = allProjects\n if (deps.search && deps.search.trim() !== \"\") {\n const searchTermLower = deps.search.toLowerCase()\n projects = allProjects?.filter(\n (project) =>\n project.name?.toLowerCase().includes(searchTermLower) ||\n project.description?.toLowerCase().includes(searchTermLower)\n )\n }\n\n return { projects }\n },\n})\n\nfunction ProjectsOverview() {\n const { projects } = Route.useLoaderData()\n const { search = \"\" } = Route.useSearch()\n const navigate = Route.useNavigate()\n const { slots } = useRouteContext({ strict: false })\n\n const handleSearch = (value: string) => {\n navigate({ search: { search: value }, replace: true })\n }\n\n return (\n <Container py>\n <ContentHeading className=\"pb-4\">\n <Trans>Projects</Trans>\n </ContentHeading>\n {slots?.projectsBanner && <Slot component={slots.projectsBanner} useShadowDOM={false} />}\n <ProjectsOverviewNavBar searchTerm={search} onSearch={handleSearch} />\n <div className=\"pt-5\">\n <ProjectCardView projects={projects} />\n </div>\n </Container>\n )\n}\n"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"projects-yiK0HGSA.mjs","names":["SplitNotFoundComponent","notFoundComponent"],"sources":["../../src/client/routes/_auth/projects/index.tsx?tsr-split=notFoundComponent"],"sourcesContent":["import { createFileRoute } from \"@tanstack/react-router\"\nimport { useRouteContext } from \"@tanstack/react-router\"\nimport { ProjectsOverviewNavBar } from \"@/client/routes/_auth/projects/-components/ProjectOverviewNavBar\"\nimport { ProjectCardView } from \"@/client/routes/_auth/projects/-components/ProjectCardView\"\nimport { RouteError } from \"@/client/components/Error/RouteError\"\nimport { TRPCClientError } from \"@trpc/client\"\nimport { Container, ContentHeading, Spinner, Stack } from \"@cloudoperators/juno-ui-components\"\nimport { Trans } from \"@lingui/react/macro\"\nimport { z } from \"zod\"\nimport { Slot } from \"@/client/components/Slot\"\nimport type { RouteInfo } from \"@/client/routes/routeInfo\"\n\nconst searchSchema = z.object({\n search: z.string().optional(),\n})\n\nexport const Route = createFileRoute(\"/_auth/projects/\")({\n staticData: {\n analytics: {\n name: \"projects.list\",\n },\n } satisfies RouteInfo,\n component: ProjectsOverview,\n pendingComponent: () => (\n <Stack className=\"fixed inset-0\" distribution=\"center\" alignment=\"center\">\n <div className=\"absolute inset-0 backdrop-blur-sm\" />\n <Spinner variant=\"primary\" size=\"large\" />\n </Stack>\n ),\n errorComponent: ({ error }) => (\n <RouteError error={error} safeErrorMessage={error instanceof TRPCClientError ? error.message : undefined} />\n ),\n notFoundComponent: () => {\n return <p>Projects not found</p>\n },\n validateSearch: searchSchema,\n loaderDeps: ({ search }) => ({\n search: search.search || \"\",\n }),\n loader: async ({ context, deps }) => {\n const allProjects = await context.trpcClient?.project.getAuthProjects.query()\n\n let projects = allProjects\n if (deps.search && deps.search.trim() !== \"\") {\n const searchTermLower = deps.search.toLowerCase()\n projects = allProjects?.filter(\n (project) =>\n project.name?.toLowerCase().includes(searchTermLower) ||\n project.description?.toLowerCase().includes(searchTermLower)\n )\n }\n\n return { projects }\n },\n})\n\nfunction ProjectsOverview() {\n const { projects } = Route.useLoaderData()\n const { search = \"\" } = Route.useSearch()\n const navigate = Route.useNavigate()\n const { slots } = useRouteContext({ strict: false })\n\n const handleSearch = (value: string) => {\n navigate({ search: { search: value }, replace: true })\n }\n\n return (\n <Container py>\n <ContentHeading className=\"pb-4\">\n <Trans>Projects</Trans>\n </ContentHeading>\n {slots?.projectsBanner && <Slot component={slots.projectsBanner} useShadowDOM={false} />}\n <ProjectsOverviewNavBar searchTerm={search} onSearch={handleSearch} />\n <div className=\"pt-5\">\n <ProjectCardView projects={projects} />\n </div>\n </Container>\n )\n}\n"],"mappings":";;cAiCW,gBAAC,KAAA,EAAA,UAAE,qBAAA,CAAA"}
|
|
@@ -1015,6 +1015,43 @@ export declare const trpcReactClient: TRPCClient<import('@trpc/server').TRPCBuil
|
|
|
1015
1015
|
}[] | undefined;
|
|
1016
1016
|
meta: object;
|
|
1017
1017
|
}>;
|
|
1018
|
+
getProject: import('@trpc/server').TRPCQueryProcedure<{
|
|
1019
|
+
input: {
|
|
1020
|
+
projectId: string;
|
|
1021
|
+
};
|
|
1022
|
+
output: {
|
|
1023
|
+
id: string;
|
|
1024
|
+
name: string;
|
|
1025
|
+
enabled: boolean;
|
|
1026
|
+
domain_id?: string | null | undefined;
|
|
1027
|
+
parent_id?: string | null | undefined;
|
|
1028
|
+
is_domain?: boolean | null | undefined;
|
|
1029
|
+
tags?: string[] | null | undefined;
|
|
1030
|
+
options?: Record<string, string | boolean> | null | undefined;
|
|
1031
|
+
description?: string | null | undefined;
|
|
1032
|
+
links?: {
|
|
1033
|
+
self?: string | null | undefined;
|
|
1034
|
+
} | undefined;
|
|
1035
|
+
domain_name?: string | undefined;
|
|
1036
|
+
parents?: {
|
|
1037
|
+
project: {
|
|
1038
|
+
id: string;
|
|
1039
|
+
name: string;
|
|
1040
|
+
enabled: boolean;
|
|
1041
|
+
domain_id?: string | null | undefined;
|
|
1042
|
+
parent_id?: string | null | undefined;
|
|
1043
|
+
is_domain?: boolean | null | undefined;
|
|
1044
|
+
tags?: string[] | null | undefined;
|
|
1045
|
+
options?: Record<string, string | boolean> | null | undefined;
|
|
1046
|
+
description?: string | null | undefined;
|
|
1047
|
+
links?: {
|
|
1048
|
+
self?: string | null | undefined;
|
|
1049
|
+
} | undefined;
|
|
1050
|
+
};
|
|
1051
|
+
}[] | undefined;
|
|
1052
|
+
} | null;
|
|
1053
|
+
meta: object;
|
|
1054
|
+
}>;
|
|
1018
1055
|
}>>;
|
|
1019
1056
|
}> & import('@trpc/server').TRPCDecorateCreateRouterOptions<{
|
|
1020
1057
|
storage: {
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1668,6 +1668,43 @@ declare const buildBaseRouter: (policyDir: string) => _trpc_server.TRPCBuiltRout
|
|
|
1668
1668
|
}[] | undefined;
|
|
1669
1669
|
meta: object;
|
|
1670
1670
|
}>;
|
|
1671
|
+
getProject: _trpc_server.TRPCQueryProcedure<{
|
|
1672
|
+
input: {
|
|
1673
|
+
projectId: string;
|
|
1674
|
+
};
|
|
1675
|
+
output: {
|
|
1676
|
+
id: string;
|
|
1677
|
+
name: string;
|
|
1678
|
+
enabled: boolean;
|
|
1679
|
+
domain_id?: string | null | undefined;
|
|
1680
|
+
parent_id?: string | null | undefined;
|
|
1681
|
+
is_domain?: boolean | null | undefined;
|
|
1682
|
+
tags?: string[] | null | undefined;
|
|
1683
|
+
options?: Record<string, string | boolean> | null | undefined;
|
|
1684
|
+
description?: string | null | undefined;
|
|
1685
|
+
links?: {
|
|
1686
|
+
self?: string | null | undefined;
|
|
1687
|
+
} | undefined;
|
|
1688
|
+
domain_name?: string | undefined;
|
|
1689
|
+
parents?: {
|
|
1690
|
+
project: {
|
|
1691
|
+
id: string;
|
|
1692
|
+
name: string;
|
|
1693
|
+
enabled: boolean;
|
|
1694
|
+
domain_id?: string | null | undefined;
|
|
1695
|
+
parent_id?: string | null | undefined;
|
|
1696
|
+
is_domain?: boolean | null | undefined;
|
|
1697
|
+
tags?: string[] | null | undefined;
|
|
1698
|
+
options?: Record<string, string | boolean> | null | undefined;
|
|
1699
|
+
description?: string | null | undefined;
|
|
1700
|
+
links?: {
|
|
1701
|
+
self?: string | null | undefined;
|
|
1702
|
+
} | undefined;
|
|
1703
|
+
};
|
|
1704
|
+
}[] | undefined;
|
|
1705
|
+
} | null;
|
|
1706
|
+
meta: object;
|
|
1707
|
+
}>;
|
|
1671
1708
|
}>>;
|
|
1672
1709
|
}> & _trpc_server.TRPCDecorateCreateRouterOptions<{
|
|
1673
1710
|
storage: {
|
package/dist/server/index.js
CHANGED
|
@@ -33490,36 +33490,29 @@ __name(createS3Client, "createS3Client");
|
|
|
33490
33490
|
// src/server/Storage/cephProcedure.ts
|
|
33491
33491
|
var NO_CEPH_CREDENTIALS = "NO_CEPH_CREDENTIALS";
|
|
33492
33492
|
function resolveS3Config(ctx) {
|
|
33493
|
-
|
|
33494
|
-
|
|
33495
|
-
|
|
33496
|
-
|
|
33497
|
-
|
|
33498
|
-
|
|
33499
|
-
|
|
33500
|
-
throw new Error("Ceph service endpoint not found in catalog. Ensure the Ceph service is registered in OpenStack.");
|
|
33501
|
-
}
|
|
33502
|
-
const swiftIndex = endpoint.indexOf("/swift/");
|
|
33503
|
-
const baseEndpoint = swiftIndex !== -1 ? endpoint.substring(0, swiftIndex) : endpoint;
|
|
33504
|
-
const endpoints = service.availableEndpoints?.();
|
|
33505
|
-
const openstackRegion = endpoints?.[0]?.region;
|
|
33506
|
-
if (!openstackRegion) {
|
|
33507
|
-
throw new Error("Region not found in Ceph service endpoints");
|
|
33508
|
-
}
|
|
33509
|
-
if (!ctx.cephRegion) {
|
|
33510
|
-
throw new Error("cephRegion is required. Pass it via createServer({ cephRegion: '...' }).");
|
|
33511
|
-
}
|
|
33512
|
-
const region = ctx.cephRegion;
|
|
33513
|
-
return {
|
|
33514
|
-
endpoint: baseEndpoint,
|
|
33515
|
-
region
|
|
33516
|
-
};
|
|
33517
|
-
} catch (error) {
|
|
33518
|
-
console.error("[ceph] Failed to resolve Ceph service from catalog:", error);
|
|
33519
|
-
throw new Error("Ceph service not found in catalog. Ensure the Ceph service is registered in OpenStack.", {
|
|
33520
|
-
cause: error
|
|
33521
|
-
});
|
|
33493
|
+
const service = ctx.openstack?.service("ceph");
|
|
33494
|
+
if (!service) {
|
|
33495
|
+
throw new Error("Ceph service not found in OpenStack service catalog");
|
|
33496
|
+
}
|
|
33497
|
+
const endpoint = service.getEndpoint?.();
|
|
33498
|
+
if (!endpoint) {
|
|
33499
|
+
throw new Error("Ceph service endpoint not found in catalog. Ensure the Ceph service is registered in OpenStack.");
|
|
33522
33500
|
}
|
|
33501
|
+
const swiftIndex = endpoint.indexOf("/swift/");
|
|
33502
|
+
const baseEndpoint = swiftIndex !== -1 ? endpoint.substring(0, swiftIndex) : endpoint;
|
|
33503
|
+
const endpoints = service.availableEndpoints?.();
|
|
33504
|
+
const openstackRegion = endpoints?.[0]?.region;
|
|
33505
|
+
if (!openstackRegion) {
|
|
33506
|
+
throw new Error("Region not found in Ceph service endpoints");
|
|
33507
|
+
}
|
|
33508
|
+
if (!ctx.cephRegion) {
|
|
33509
|
+
throw new Error("Ceph region not configured");
|
|
33510
|
+
}
|
|
33511
|
+
const region = ctx.cephRegion;
|
|
33512
|
+
return {
|
|
33513
|
+
endpoint: baseEndpoint,
|
|
33514
|
+
region
|
|
33515
|
+
};
|
|
33523
33516
|
}
|
|
33524
33517
|
__name(resolveS3Config, "resolveS3Config");
|
|
33525
33518
|
var cephCredentialMiddleware = projectScopedProcedure.use(/* @__PURE__ */ __name(async function resolveCeph(opts) {
|
|
@@ -35884,6 +35877,28 @@ var projectRouter = {
|
|
|
35884
35877
|
}
|
|
35885
35878
|
projects.sort((a, b) => a.name.localeCompare(b.name));
|
|
35886
35879
|
return projects;
|
|
35880
|
+
}),
|
|
35881
|
+
getProject: protectedProcedure.input(import_zod20.z.object({
|
|
35882
|
+
projectId: import_zod20.z.string()
|
|
35883
|
+
})).query(async ({ ctx, input }) => {
|
|
35884
|
+
if (!ctx.openstack) {
|
|
35885
|
+
throw new import_server16.TRPCError({
|
|
35886
|
+
code: "UNAUTHORIZED",
|
|
35887
|
+
message: "No authenticated session"
|
|
35888
|
+
});
|
|
35889
|
+
}
|
|
35890
|
+
const token = ctx.openstack.getToken();
|
|
35891
|
+
if (!token?.authToken) {
|
|
35892
|
+
throw new import_server16.TRPCError({
|
|
35893
|
+
code: "UNAUTHORIZED",
|
|
35894
|
+
message: "No auth token available"
|
|
35895
|
+
});
|
|
35896
|
+
}
|
|
35897
|
+
const response = await callIdentityAPI(ctx.identityEndpoint, token.authToken, `projects/${input.projectId}`).catch(() => null);
|
|
35898
|
+
if (!response) return null;
|
|
35899
|
+
const data = await response.json();
|
|
35900
|
+
const parsed = projectResponseSchema.safeParse(data);
|
|
35901
|
+
return parsed.success ? parsed.data.project : null;
|
|
35887
35902
|
})
|
|
35888
35903
|
};
|
|
35889
35904
|
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"_projectId-Cq9JnhRj.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 ...(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, projects] = await Promise.all([\n context.trpcClient?.auth.getAvailableServices.query(),\n context.trpcClient?.project.getAuthProjects.query().catch(() => null),\n ])\n\n const projectScopeStatus = resolveProjectScope({\n projectId: params.projectId,\n scopeProject: scopeData?.project,\n userProjects: projects,\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 = projects?.find((p) => p.id === params.projectId)?.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 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"_projectId-Ig3QqYzt.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, projects] = await Promise.all([\n context.trpcClient?.auth.getAvailableServices.query(),\n context.trpcClient?.project.getAuthProjects.query().catch(() => null),\n ])\n\n const projectScopeStatus = resolveProjectScope({\n projectId: params.projectId,\n scopeProject: scopeData?.project,\n userProjects: projects,\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 = projects?.find((p) => p.id === params.projectId)?.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 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"_projectId-b4NGeECR.mjs","names":["resolveProjectScope","input","scopeProject","id","projectId","userProjects","isInUserProjects","some","p","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","projects","Promise","all","getAvailableServices","query","project","getAuthProjects","catch","projectScopeStatus","scopeProject","userProjects","Error","statusCode","accountId","domain","id","description","find","p","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.userProjects - List of projects the user has access to (null/undefined if 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 userProjects was successfully fetched)\n * - \"scope_failed\": The project exists in the user's list but the scope operation failed, OR we couldn't fetch the user's project list\n *\n * @example\n * // Successful scope\n * resolveProjectScope({\n * projectId: \"project-123\",\n * scopeProject: { id: \"project-123\" },\n * userProjects: [{ id: \"project-123\" }]\n * }) // returns \"valid\"\n *\n * @example\n * // Project not found (when we successfully fetched the user's project list)\n * resolveProjectScope({\n * projectId: \"project-999\",\n * scopeProject: undefined,\n * userProjects: [{ 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 * userProjects: [{ id: \"project-123\" }]\n * }) // returns \"scope_failed\"\n *\n * @example\n * // Can't verify project access (userProjects fetch failed)\n * resolveProjectScope({\n * projectId: \"project-123\",\n * scopeProject: undefined,\n * userProjects: null\n * }) // returns \"scope_failed\"\n */\nexport const resolveProjectScope = (input: {\n projectId: string\n scopeProject: { id?: string } | null | undefined\n userProjects: { id: string }[] | null | undefined\n}): ProjectScopeStatus => {\n if (input.scopeProject?.id === input.projectId) {\n return \"valid\"\n }\n\n // If we couldn't fetch userProjects 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.userProjects == null) {\n return \"scope_failed\"\n }\n\n const isInUserProjects = input.userProjects.some((p) => p.id === input.projectId)\n\n return isInUserProjects ? \"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, projects] = await Promise.all([\n context.trpcClient?.auth.getAvailableServices.query(),\n context.trpcClient?.project.getAuthProjects.query().catch(() => null),\n ])\n\n const projectScopeStatus = resolveProjectScope({\n projectId: params.projectId,\n scopeProject: scopeData?.project,\n userProjects: projects,\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 = projects?.find((p) => p.id === params.projectId)?.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,gBAAgB,QAIDJ,EAAMI,aAAaE,MAAMC,MAAMA,EAAEL,OAAOF,EAAMG,SAEhEE,IALE,iBAKkC,aCtDhCM,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,KAAY,MAAMC,QAAQC,IAAI,CACtDhB,EAAQG,YAAYC,KAAKa,qBAAqBC,MAAAA,GAC9ClB,EAAQG,YAAYgB,QAAQC,gBAAgBF,MAAAA,EAAQG,YAAY,IAAA,CAAA,CACjE,GAEKC,IAAqB/B,EAAoB;GAC7CiB,WAAWP,EAAOO;GAClBe,cAAcrB,GAAWiB;GACzBK,cAAcV;EAChB,CAAA;EAEA,IAAIQ,MAAuB,gBACzB,MAAUG,MAAM,oCAAA;EAGlB,IAAIH,MAAuB,aAAa;GACtC,IAAMb,IAAQ,gBAAIgB,MAAM,mBAAA;GAExB,MADAhB,EAAMiB,aAAa,KACbjB;EACR;EAEA,IAAMkB,IAAYzB,GAAW0B,QAAQC,MAAM,IACrCC,IAAchB,GAAUiB,MAAMC,MAAMA,EAAEH,OAAO5B,EAAOO,SAAS,GAAGsB,eAAe;EAErF,OAAO;GACL3B,YAAYH,EAAQG;GACpB8B,aAAa;IAAEC,MAAM;IAAaC,MAAMjC,GAAW0B,QAAQO;GAAK;GAChEC,cAAclC,GAAWiB;GACzBN;GACAc;GACAnB,WAAWP,EAAOO;GAClBsB;EACF;CACF;AACF,CAAA"}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { createFileRoute as e, lazyRouteComponent as t } from "@tanstack/react-router";
|
|
2
|
-
import { z as n } from "zod";
|
|
3
|
-
//#region src/client/routes/_auth/projects/index.tsx
|
|
4
|
-
var r = () => import("./projects-yiK0HGSA.mjs"), i = () => import("./projects-DvSiI5Vg.mjs"), a = () => import("./projects-sn_XPdCh.mjs"), o = n.object({ search: n.string().optional() }), s = e("/_auth/projects/")({
|
|
5
|
-
staticData: { analytics: { name: "projects.list" } },
|
|
6
|
-
component: t(a, "component"),
|
|
7
|
-
errorComponent: t(i, "errorComponent"),
|
|
8
|
-
notFoundComponent: t(r, "notFoundComponent"),
|
|
9
|
-
validateSearch: o,
|
|
10
|
-
loaderDeps: ({ search: e }) => ({ search: e.search || "" }),
|
|
11
|
-
loader: async ({ context: e, deps: t }) => {
|
|
12
|
-
let n = await e.trpcClient?.project.getAuthProjects.query(), r = n;
|
|
13
|
-
if (t.search && t.search.trim() !== "") {
|
|
14
|
-
let e = t.search.toLowerCase();
|
|
15
|
-
r = n?.filter((t) => t.name?.toLowerCase().includes(e) || t.description?.toLowerCase().includes(e));
|
|
16
|
-
}
|
|
17
|
-
return { projects: r };
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
//#endregion
|
|
21
|
-
export { s as t };
|
|
22
|
-
|
|
23
|
-
//# sourceMappingURL=projects-CDFynG9q.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"projects-CDFynG9q.mjs","names":["createFileRoute","z","searchSchema","object","search","string","optional","Route","staticData","analytics","name","RouteInfo","component","lazyRouteComponent","$$splitComponentImporter","errorComponent","$$splitErrorComponentImporter","notFoundComponent","$$splitNotFoundComponentImporter","validateSearch","loaderDeps","loader","context","deps","allProjects","trpcClient","project","getAuthProjects","query","projects","trim","searchTermLower","toLowerCase","filter","includes","description"],"sources":["../../src/client/routes/_auth/projects/index.tsx"],"sourcesContent":["import { createFileRoute } from \"@tanstack/react-router\"\nimport { useRouteContext } from \"@tanstack/react-router\"\nimport { ProjectsOverviewNavBar } from \"@/client/routes/_auth/projects/-components/ProjectOverviewNavBar\"\nimport { ProjectCardView } from \"@/client/routes/_auth/projects/-components/ProjectCardView\"\nimport { RouteError } from \"@/client/components/Error/RouteError\"\nimport { TRPCClientError } from \"@trpc/client\"\nimport { Container, ContentHeading } from \"@cloudoperators/juno-ui-components\"\nimport { Trans } from \"@lingui/react/macro\"\nimport { z } from \"zod\"\nimport { Slot } from \"@/client/components/Slot\"\nimport type { RouteInfo } from \"@/client/routes/routeInfo\"\n\nconst searchSchema = z.object({\n search: z.string().optional(),\n})\n\nexport const Route = createFileRoute(\"/_auth/projects/\")({\n staticData: {\n analytics: {\n name: \"projects.list\",\n },\n } satisfies RouteInfo,\n component: ProjectsOverview,\n errorComponent: ({ error }) => (\n <RouteError error={error} safeErrorMessage={error instanceof TRPCClientError ? error.message : undefined} />\n ),\n notFoundComponent: () => {\n return <p>Projects not found</p>\n },\n validateSearch: searchSchema,\n loaderDeps: ({ search }) => ({\n search: search.search || \"\",\n }),\n loader: async ({ context, deps }) => {\n const allProjects = await context.trpcClient?.project.getAuthProjects.query()\n\n let projects = allProjects\n if (deps.search && deps.search.trim() !== \"\") {\n const searchTermLower = deps.search.toLowerCase()\n projects = allProjects?.filter(\n (project) =>\n project.name?.toLowerCase().includes(searchTermLower) ||\n project.description?.toLowerCase().includes(searchTermLower)\n )\n }\n\n return { projects }\n },\n})\n\nfunction ProjectsOverview() {\n const { projects } = Route.useLoaderData()\n const { search = \"\" } = Route.useSearch()\n const navigate = Route.useNavigate()\n const { slots } = useRouteContext({ strict: false })\n\n const handleSearch = (value: string) => {\n navigate({ search: { search: value }, replace: true })\n }\n\n return (\n <Container py>\n <ContentHeading className=\"pb-4\">\n <Trans>Projects</Trans>\n </ContentHeading>\n {slots?.projectsBanner && <Slot component={slots.projectsBanner} useShadowDOM={false} />}\n <ProjectsOverviewNavBar searchTerm={search} onSearch={handleSearch} />\n <div className=\"pt-5\">\n <ProjectCardView projects={projects} />\n </div>\n </Container>\n )\n}\n"],"mappings":";;;2IAYME,IAAeD,EAAEE,OAAO,EAC5BC,QAAQH,EAAEI,OAAM,EAAGC,SAAQ,EAC7B,CAAA,GAEaC,IAAQP,EAAgB,kBAAA,EAAoB;CACvDQ,YAAY,EACVC,WAAW,EACTC,MAAM,gBACR,EACF;CACAE,WAASC,EAAAC,GAAA,WAAA;CACTC,gBAAcF,EAAAG,GAAA,gBAAA;CAGdC,mBAAiBJ,EAAAK,GAAA,mBAAA;CAGjBC,gBAAgBjB;CAChBkB,aAAa,EAAEhB,iBAAc,EAC3BA,QAAQA,EAAOA,UAAU,GAC3B;CACAiB,QAAQ,OAAO,EAAEC,YAASC,cAAM;EAC9B,IAAMC,IAAc,MAAMF,EAAQG,YAAYC,QAAQC,gBAAgBC,MAAAA,GAElEC,IAAWL;EACf,IAAID,EAAKnB,UAAUmB,EAAKnB,OAAO0B,KAAI,MAAO,IAAI;GAC5C,IAAMC,IAAkBR,EAAKnB,OAAO4B,YAAW;GAC/CH,IAAWL,GAAaS,QACrBP,MACCA,EAAQhB,MAAMsB,YAAAA,EAAcE,SAASH,CAAAA,KACrCL,EAAQS,aAAaH,YAAAA,EAAcE,SAASH,CAAAA,CAAAA;EAElD;EAEA,OAAO,EAAEF,YAAS;CACpB;AACF,CAAA"}
|