1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Fix CustomResourceDefinitions not being navigable

- Make buildURL more type safe

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2022-02-07 11:58:37 -05:00
parent 06ec1b39a4
commit de6e86b8fb
45 changed files with 174 additions and 191 deletions

View File

@ -5,7 +5,7 @@
import { KubeCreationError, KubeObject } from "../kube-object";
import { KubeApi } from "../kube-api";
import { crdResourcesURL } from "../../routes";
import { customResourceDefinitionsURL } from "../../routes";
import { isClusterPageContext } from "../../utils/cluster-id-url-parsing";
import type { KubeJsonApiData } from "../kube-json-api";
@ -99,7 +99,7 @@ export class CustomResourceDefinition extends KubeObject {
}
getResourceUrl() {
return crdResourcesURL({
return customResourceDefinitionsURL({
params: {
group: this.getGroup(),
name: this.getPluralName(),

View File

@ -11,7 +11,7 @@ import { helmChartStore } from "../../../renderer/components/+helm-charts/helm-c
import type { ItemObject } from "../../item.store";
import { KubeObject } from "../kube-object";
import type { JsonApiData } from "../json-api";
import { buildURLPositional } from "../../utils/buildUrl";
import { buildURL } from "../../utils/buildUrl";
import type { KubeJsonApiData } from "../kube-json-api";
interface IReleasePayload {
@ -77,16 +77,16 @@ interface EndpointQuery {
all?: boolean;
}
const endpoint = buildURLPositional<EndpointParams, EndpointQuery>("/v2/releases/:namespace?/:name?/:route?");
const endpoint = buildURL<EndpointParams, EndpointQuery>("/v2/releases/:namespace?/:name?/:route?");
export async function listReleases(namespace?: string): Promise<HelmRelease[]> {
const releases = await apiBase.get<HelmReleaseDto[]>(endpoint({ namespace }));
const releases = await apiBase.get<HelmReleaseDto[]>(endpoint({ params: { namespace }}));
return releases.map(toHelmRelease);
}
export async function getRelease(name: string, namespace: string): Promise<IReleaseDetails> {
const path = endpoint({ name, namespace });
const path = endpoint({ params: { name, namespace }});
const { resources: rawResources, ...details } = await apiBase.get<IReleaseRawDetails>(path);
const resources = rawResources.map(KubeObject.create);
@ -115,7 +115,7 @@ export async function updateRelease(name: string, namespace: string, payload: IR
const chart = `${repo}/${rawChart}`;
const values = yaml.load(rawValues);
return apiBase.put(endpoint({ name, namespace }), {
return apiBase.put(endpoint({ params: { name, namespace }}), {
data: {
chart,
values,
@ -125,28 +125,28 @@ export async function updateRelease(name: string, namespace: string, payload: IR
}
export async function deleteRelease(name: string, namespace: string): Promise<JsonApiData> {
const path = endpoint({ name, namespace });
const path = endpoint({ params: { name, namespace }});
return apiBase.del(path);
}
export async function getReleaseValues(name: string, namespace: string, all?: boolean): Promise<string> {
const route = "values";
const path = endpoint({ name, namespace, route }, { all });
const path = endpoint({ params: { name, namespace, route }, query: { all }});
return apiBase.get<string>(path);
}
export async function getReleaseHistory(name: string, namespace: string): Promise<IReleaseRevision[]> {
const route = "history";
const path = endpoint({ name, namespace, route });
const path = endpoint({ params: { name, namespace, route }});
return apiBase.get(path);
}
export async function rollbackRelease(name: string, namespace: string, revision: number): Promise<JsonApiData> {
const route = "rollback";
const path = endpoint({ name, namespace, route });
const path = endpoint({ params: { name, namespace, route }});
const data = { revision };
return apiBase.put(path, { data });

View File

@ -3,8 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RouteProps } from "react-router";
import { buildURL } from "../utils/buildUrl";
import { buildURL, RouteProps } from "../utils/buildUrl";
export const addClusterRoute: RouteProps = {
path: "/add-cluster",

View File

@ -3,8 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RouteProps } from "react-router";
import { buildURL } from "../utils/buildUrl";
import { buildURL, RouteProps } from "../utils/buildUrl";
export interface CatalogViewRouteParam {
group?: string;

View File

@ -3,8 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RouteProps } from "react-router";
import { buildURL } from "../utils/buildUrl";
import { buildURL, RouteProps } from "../utils/buildUrl";
export interface ClusterViewRouteParams {
clusterId: string;

View File

@ -3,8 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RouteProps } from "react-router";
import { buildURL } from "../utils/buildUrl";
import { buildURL, RouteProps } from "../utils/buildUrl";
export const clusterRoute: RouteProps = {
path: "/overview",

View File

@ -3,8 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RouteProps } from "react-router";
import { buildURL } from "../utils/buildUrl";
import { buildURL, RouteProps } from "../utils/buildUrl";
export const configMapsRoute: RouteProps = {
path: "/configmaps",

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RouteProps } from "react-router";
import type { RouteProps as RouterProps } from "react-router";
import type { URLParams } from "../utils/buildUrl";
import { configMapsRoute, configMapsURL } from "./config-maps";
import { hpaRoute } from "./hpa";
@ -12,15 +12,15 @@ import { pdbRoute } from "./pod-disruption-budgets";
import { resourceQuotaRoute } from "./resource-quotas";
import { secretsRoute } from "./secrets";
export const configRoute: RouteProps = {
export const configRoute: RouterProps = {
path: [
configMapsRoute,
secretsRoute,
resourceQuotaRoute,
limitRangesRoute,
hpaRoute,
pdbRoute,
].map(route => route.path.toString()),
configMapsRoute.path,
secretsRoute.path,
resourceQuotaRoute.path,
limitRangesRoute.path,
hpaRoute.path,
pdbRoute.path,
],
};
export const configURL = (params?: URLParams) => configMapsURL(params);

View File

@ -3,29 +3,17 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RouteProps } from "react-router";
import { buildURL } from "../utils/buildUrl";
import { buildURL, RouteProps } from "../utils/buildUrl";
export const crdRoute: RouteProps = {
path: "/crd",
export const customResourcesRoute = "/crd";
export const customResourceDefinitionsRoute: RouteProps = {
path: `${customResourcesRoute}/definitions/:group?/:name?`,
};
export const crdDefinitionsRoute: RouteProps = {
path: `${crdRoute.path}/definitions`,
};
export const crdResourcesRoute: RouteProps = {
path: `${crdRoute.path}/:group/:name`,
};
export interface CRDListQuery {
groups?: string;
export interface CustomResourceDefinitionsRouteParams {
group?: string;
name?: string;
}
export interface CRDRouteParams {
group: string;
name: string;
}
export const crdURL = buildURL<{}, CRDListQuery>(crdDefinitionsRoute.path);
export const crdResourcesURL = buildURL<CRDRouteParams>(crdResourcesRoute.path);
export const customResourceDefinitionsURL = buildURL<CustomResourceDefinitionsRouteParams>(customResourceDefinitionsRoute.path);

View File

@ -3,8 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RouteProps } from "react-router";
import { buildURL } from "../utils/buildUrl";
import { buildURL, RouteProps } from "../utils/buildUrl";
export const endpointRoute: RouteProps = {
path: "/endpoints",

View File

@ -3,8 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RouteProps } from "react-router";
import { buildURL } from "../utils/buildUrl";
import { buildURL, RouteProps } from "../utils/buildUrl";
export interface EntitySettingsRouteParams {
entityId: string;

View File

@ -3,8 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RouteProps } from "react-router";
import { buildURL } from "../utils/buildUrl";
import { buildURL, RouteProps } from "../utils/buildUrl";
export const eventRoute: RouteProps = {
path: "/events",

View File

@ -3,8 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RouteProps } from "react-router";
import { buildURL } from "../utils/buildUrl";
import { buildURL, RouteProps } from "../utils/buildUrl";
export const extensionsRoute: RouteProps = {
path: "/extensions",

View File

@ -3,8 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RouteProps } from "react-router";
import { buildURL } from "../utils/buildUrl";
import { buildURL, RouteProps } from "../utils/buildUrl";
import { helmRoute } from "./helm";
export const helmChartsRoute: RouteProps = {

View File

@ -3,8 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RouteProps } from "react-router";
import { buildURL } from "../utils/buildUrl";
import { buildURL, RouteProps } from "../utils/buildUrl";
export const helmRoute: RouteProps = {
path: "/helm",

View File

@ -3,8 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RouteProps } from "react-router";
import { buildURL } from "../utils/buildUrl";
import { buildURL, RouteProps } from "../utils/buildUrl";
export const hpaRoute: RouteProps = {
path: "/hpa",

View File

@ -3,8 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RouteProps } from "react-router";
import { buildURL } from "../utils/buildUrl";
import { buildURL, RouteProps } from "../utils/buildUrl";
export const ingressRoute: RouteProps = {
path: "/ingresses",

View File

@ -3,8 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RouteProps } from "react-router";
import { buildURL } from "../utils/buildUrl";
import { buildURL, RouteProps } from "../utils/buildUrl";
export const limitRangesRoute: RouteProps = {
path: "/limitranges",

View File

@ -3,8 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RouteProps } from "react-router";
import { buildURL } from "../utils/buildUrl";
import { buildURL, RouteProps } from "../utils/buildUrl";
export const namespacesRoute: RouteProps = {
path: "/namespaces",

View File

@ -3,8 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RouteProps } from "react-router";
import { buildURL } from "../utils/buildUrl";
import { buildURL, RouteProps } from "../utils/buildUrl";
export const networkPoliciesRoute: RouteProps = {
path: "/network-policies",

View File

@ -4,7 +4,6 @@
*/
import type { RouteProps } from "react-router";
import type { URLParams } from "../utils/buildUrl";
import { endpointRoute } from "./endpoints";
import { ingressRoute } from "./ingresses";
import { networkPoliciesRoute } from "./network-policies";
@ -21,4 +20,4 @@ export const networkRoute: RouteProps = {
].map(route => route.path.toString()),
};
export const networkURL = (params?: URLParams) => servicesURL(params);
export const networkURL = servicesURL;

View File

@ -3,8 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RouteProps } from "react-router";
import { buildURL } from "../utils/buildUrl";
import { buildURL, RouteProps } from "../utils/buildUrl";
export const nodesRoute: RouteProps = {
path: "/nodes",

View File

@ -3,8 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RouteProps } from "react-router";
import { buildURL } from "../utils/buildUrl";
import { buildURL, RouteProps } from "../utils/buildUrl";
export const pdbRoute: RouteProps = {
path: "/poddisruptionbudgets",

View File

@ -3,8 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RouteProps } from "react-router";
import { buildURL } from "../utils/buildUrl";
import { buildURL, RouteProps } from "../utils/buildUrl";
export const portForwardsRoute: RouteProps = {
path: "/port-forwards/:forwardport?",

View File

@ -3,8 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RouteProps } from "react-router";
import { buildURL } from "../utils/buildUrl";
import { buildURL, RouteProps } from "../utils/buildUrl";
export const preferencesRoute: RouteProps = {
path: "/preferences",

View File

@ -3,8 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RouteProps } from "react-router";
import { buildURL } from "../utils/buildUrl";
import { buildURL, RouteProps } from "../utils/buildUrl";
import { helmRoute } from "./helm";
export const releaseRoute: RouteProps = {

View File

@ -3,8 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RouteProps } from "react-router";
import { buildURL } from "../utils/buildUrl";
import { buildURL, RouteProps } from "../utils/buildUrl";
export const resourceQuotaRoute: RouteProps = {
path: "/resourcequotas",

View File

@ -3,8 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RouteProps } from "react-router";
import { buildURL } from "../utils/buildUrl";
import { buildURL, RouteProps } from "../utils/buildUrl";
export const secretsRoute: RouteProps = {
path: "/secrets",

View File

@ -3,8 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RouteProps } from "react-router";
import { buildURL } from "../utils/buildUrl";
import { buildURL, RouteProps } from "../utils/buildUrl";
export const servicesRoute: RouteProps = {
path: "/services",

View File

@ -3,8 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RouteProps } from "react-router";
import { buildURL } from "../utils/buildUrl";
import { buildURL, RouteProps } from "../utils/buildUrl";
export const storageClassesRoute: RouteProps = {
path: "/storage-classes",

View File

@ -4,7 +4,6 @@
*/
import type { RouteProps } from "react-router";
import type { URLParams } from "../utils/buildUrl";
import { storageClassesRoute } from "./storage-classes";
import { volumeClaimsRoute, volumeClaimsURL } from "./volume-claims";
import { volumesRoute } from "./volumes";
@ -17,4 +16,4 @@ export const storageRoute: RouteProps = {
].map(route => route.path.toString()),
};
export const storageURL = (params?: URLParams) => volumeClaimsURL(params);
export const storageURL = volumeClaimsURL;

View File

@ -3,8 +3,8 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RouteProps } from "react-router";
import { buildURL, URLParams } from "../utils/buildUrl";
import type { RouteProps as RouterProps } from "react-router";
import { buildURL, RouteProps, URLParams } from "../utils/buildUrl";
// Routes
export const serviceAccountsRoute: RouteProps = {
@ -26,15 +26,15 @@ export const clusterRoleBindingsRoute: RouteProps = {
path: "/cluster-role-bindings",
};
export const usersManagementRoute: RouteProps = {
export const usersManagementRoute: RouterProps = {
path: [
serviceAccountsRoute,
podSecurityPoliciesRoute,
roleBindingsRoute,
clusterRoleBindingsRoute,
rolesRoute,
clusterRolesRoute,
].map(route => route.path.toString()),
serviceAccountsRoute.path,
podSecurityPoliciesRoute.path,
roleBindingsRoute.path,
clusterRoleBindingsRoute.path,
rolesRoute.path,
clusterRolesRoute.path,
],
};
// Route params

View File

@ -3,8 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RouteProps } from "react-router";
import { buildURL } from "../utils/buildUrl";
import { buildURL, RouteProps } from "../utils/buildUrl";
export const volumeClaimsRoute: RouteProps = {
path: "/persistent-volume-claims",

View File

@ -3,8 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RouteProps } from "react-router";
import { buildURL } from "../utils/buildUrl";
import { buildURL, RouteProps } from "../utils/buildUrl";
export const volumesRoute: RouteProps = {
path: "/persistent-volumes",

View File

@ -3,8 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RouteProps } from "react-router";
import { buildURL } from "../utils/buildUrl";
import { buildURL, RouteProps } from "../utils/buildUrl";
export const welcomeRoute: RouteProps = {
path: "/welcome",

View File

@ -3,8 +3,8 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RouteProps } from "react-router";
import { buildURL, URLParams } from "../utils/buildUrl";
import type { RouteProps as RouterProps } from "react-router";
import { buildURL, RouteProps, URLParams } from "../utils/buildUrl";
import type { KubeResource } from "../rbac";
// Routes
@ -33,17 +33,17 @@ export const cronJobsRoute: RouteProps = {
path: "/cronjobs",
};
export const workloadsRoute: RouteProps = {
export const workloadsRoute: RouterProps = {
path: [
overviewRoute,
podsRoute,
deploymentsRoute,
daemonSetsRoute,
statefulSetsRoute,
replicaSetsRoute,
jobsRoute,
cronJobsRoute,
].map(route => route.path.toString()),
overviewRoute.path,
podsRoute.path,
deploymentsRoute.path,
daemonSetsRoute.path,
statefulSetsRoute.path,
replicaSetsRoute.path,
jobsRoute.path,
cronJobsRoute.path,
],
};
// Route params

View File

@ -4,32 +4,37 @@
*/
import { compile } from "path-to-regexp";
import type { RouteProps as RouterProps } from "react-router";
import { format } from "url";
export interface URLParams<P extends object = {}, Q extends object = {}> {
export interface RouteProps extends RouterProps {
path?: string;
}
type MaybeIfRecord<FieldName extends string, Type> = Type extends object
? { [field in FieldName]?: Type }
: {};
export type URLParams<P = unknown, Q = unknown> = {
fragment?: string;
} & MaybeIfRecord<"params", P> & MaybeIfRecord<"query", Q>;
interface InternalURLParams<P, Q> {
fragment?: string;
params?: P;
query?: Q;
fragment?: string;
}
export function buildURL<P extends object = {}, Q extends object = {}>(path: string | any) {
const pathBuilder = compile(String(path));
export function buildURL<P = unknown, Q = unknown>(path: string): (urlParams?: URLParams<P, Q>) => string {
const pathBuilder = compile(path);
return function ({ params, query, fragment }: URLParams<P, Q> = {}): string {
const queryParams = query ? new URLSearchParams(Object.entries(query)).toString() : "";
const parts = [
pathBuilder(params),
queryParams && `?${queryParams}`,
fragment && `#${fragment}`,
];
return (urlParams) => {
const { params = {}, query = {}, fragment } = urlParams as InternalURLParams<P, Q> ?? {};
return parts.filter(Boolean).join("");
};
}
export function buildURLPositional<P extends object = {}, Q extends object = {}>(path: string | any) {
const builder = buildURL(path);
return function (params?: P, query?: Q, fragment?: string): string {
return builder({ params, query, fragment });
return format({
pathname: pathBuilder(params),
query: new URLSearchParams(query).toString(),
search: fragment,
});
};
}

View File

@ -32,7 +32,7 @@ enum columnId {
}
@observer
export class CrdList extends React.Component {
export class CustomResourceDefinitions extends React.Component {
constructor(props: {}) {
super(props);
makeObservable(this);

View File

@ -16,9 +16,9 @@ import { crdStore } from "./crd.store";
import type { TableSortCallbacks } from "../table";
import { apiManager } from "../../../common/k8s-api/api-manager";
import { parseJsonPath } from "../../utils/jsonPath";
import type { CRDRouteParams } from "../../../common/routes";
import type { CustomResourceDefinitionsRouteParams } from "../../../common/routes";
interface Props extends RouteComponentProps<CRDRouteParams> {
interface Props extends RouteComponentProps<CustomResourceDefinitionsRouteParams> {
}
enum columnId {
@ -28,7 +28,7 @@ enum columnId {
}
@observer
export class CrdResources extends React.Component<Props> {
export class CustomResourceDefinitionObjectList extends React.Component<Props> {
constructor(props: Props) {
super(props);
makeObservable(this);
@ -93,16 +93,12 @@ export class CrdResources extends React.Component<Props> {
renderTableHeader={[
{ title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
isNamespaced && { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
...extraColumns.map(column => {
const { name } = column;
return {
title: name,
className: name.toLowerCase(),
sortBy: name,
id: name,
};
}),
...extraColumns.map(({ name }) => ({
title: name,
className: name.toLowerCase(),
sortBy: name,
id: name,
})),
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]}
renderTableContents={crdInstance => [

View File

@ -3,55 +3,69 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import { computed, IComputedValue } from "mobx";
import { comparer, computed, IComputedValue } from "mobx";
import React from "react";
import type { RouteComponentProps } from "react-router";
import type { RequireExactlyOne } from "type-fest";
import type { CustomResourceDefinition } from "../../../common/k8s-api/endpoints";
import { crdURL, crdDefinitionsRoute } from "../../../common/routes";
import { customResourceDefinitionsURL, customResourceDefinitionsRoute, CustomResourceDefinitionsRouteParams } from "../../../common/routes";
import type { TabLayoutRoute } from "../layout/tab-layout";
import { CrdList } from "./crd-list";
import { CrdResources } from "./crd-resources";
import { CustomResourceDefinitions } from "./crd-list";
import { CustomResourceDefinitionObjectList } from "./crd-resources";
import groupedCustomResourceDefinitionsInjectable from "./grouped-custom-resources.injectable";
export interface CustomResourceTabLayoutRoute extends TabLayoutRoute {
export interface CustomResourceTabLayoutRoute extends Omit<TabLayoutRoute, "component"> {
id: string;
component: React.ComponentType<RouteComponentProps<CustomResourceDefinitionsRouteParams>>;
}
export interface CustomResourceGroupTabLayoutRoute extends CustomResourceTabLayoutRoute {
subRoutes?: CustomResourceTabLayoutRoute[];
}
export type CustomResourceGroupTabLayoutRoute = Omit<TabLayoutRoute, "component"> & {
id: string;
} & RequireExactlyOne<{
subRoutes: CustomResourceTabLayoutRoute[];
component: React.ComponentType<{}>;
}>;
interface Dependencies {
customResourcesDefinitions: IComputedValue<Map<string, CustomResourceDefinition[]>>;
}
function getRouteTabs({ customResourcesDefinitions }: Dependencies) {
function getRouteTabs({ customResourcesDefinitions }: Dependencies): IComputedValue<CustomResourceGroupTabLayoutRoute[]> {
return computed(() => {
const definitionsUrl = customResourceDefinitionsURL();
const tabs: CustomResourceGroupTabLayoutRoute[] = [
{
id: "definitions",
title: "Definitions",
component: CrdList,
url: crdURL(),
routePath: String(crdDefinitionsRoute.path),
component: () => <CustomResourceDefinitions />,
url: definitionsUrl,
routePath: definitionsUrl,
exact: true,
},
];
for (const [group, definitions] of customResourcesDefinitions.get()) {
const groupUrl = customResourceDefinitionsURL({ params: { group }});
tabs.push({
id: `crd-group:${group}`,
title: group,
routePath: crdURL({ query: { groups: group }}),
component: CrdResources,
url: groupUrl,
routePath: groupUrl,
subRoutes: definitions.map(crd => ({
id: `crd-resource:${crd.getResourceApiBase()}`,
title: crd.getResourceKind(),
routePath: crd.getResourceUrl(),
component: CrdResources,
url: crd.getResourceUrl(),
routePath: customResourceDefinitionsRoute.path,
component: (props) => <CustomResourceDefinitionObjectList {...props} />,
})),
});
}
return tabs;
}, {
equals: comparer.shallow,
});
}

View File

@ -7,7 +7,7 @@ import React from "react";
import { observer } from "mobx-react";
import { Redirect, Route, Switch } from "react-router";
import { TabLayout } from "../layout/tab-layout";
import { crdURL } from "../../../common/routes";
import { customResourceDefinitionsURL } from "../../../common/routes";
import type { IComputedValue } from "mobx";
import type { CustomResourceGroupTabLayoutRoute } from "./route-tabs.injectable";
import { withInjectables } from "@ogre-tools/injectable-react";
@ -21,16 +21,26 @@ const NonInjectedCustomResourcesRoute = observer(({ routes }: Dependencies) => (
<TabLayout>
<Switch>
{
routes.get().map(({ id, component, routePath, exact }) => (
<Route
key={id}
component={component}
path={routePath}
exact={exact}
/>
))
routes.get()
.flatMap(({ id, component, routePath, exact, subRoutes }) => (
subRoutes?.map(({ id, component, routePath, exact }) => (
<Route
key={id}
component={component}
path={routePath}
exact={exact}
/>
)) ?? (
<Route
key={id}
component={component}
path={routePath}
exact={exact}
/>
)
))
}
<Redirect to={crdURL()}/>
<Redirect to={customResourceDefinitionsURL()}/>
</Switch>
</TabLayout>
));

View File

@ -9,7 +9,7 @@ import { observer } from "mobx-react";
import customResourcesRouteTabsInjectable, { type CustomResourceGroupTabLayoutRoute } from "./route-tabs.injectable";
import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable";
import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable";
import { crdURL, crdRoute } from "../../../common/routes";
import { customResourceDefinitionsURL, customResourcesRoute } from "../../../common/routes";
import { isActiveRoute } from "../../navigation";
import { Icon } from "../icon";
import { SidebarItem } from "../layout/sidebar-item";
@ -35,8 +35,8 @@ const NonInjectedCustomResourcesSidebarItem = observer(({ routes, isAllowedResou
<SidebarItem
id="custom-resources"
text="Custom Resources"
url={crdURL()}
isActive={isActiveRoute(crdRoute)}
url={customResourceDefinitionsURL()}
isActive={isActiveRoute(customResourcesRoute)}
isHidden={!isAllowedResource("customresourcedefinitions")}
icon={<Icon material="extension"/>}
>
@ -45,13 +45,13 @@ const NonInjectedCustomResourcesSidebarItem = observer(({ routes, isAllowedResou
key={route.id}
id={route.id}
text={route.title}
url={route.routePath}
url={route.url}
>
{route.subRoutes?.map((subRoute) => (
<SidebarItem
key={subRoute.id}
id={subRoute.id}
url={subRoute.routePath}
url={subRoute.url}
text={subRoute.title}
/>
))}

View File

@ -154,7 +154,7 @@ function getInternalCommands({ openCommandDialog, getEntitySettingItems, createT
id: "cluster.viewCustomResourceDefinitions",
title: "Cluster: View Custom Resource Definitions",
isActive: isKubernetesClusterActive,
action: ({ navigate }) => navigate(routes.crdURL()),
action: ({ navigate }) => navigate(routes.customResourceDefinitionsURL()),
},
{
id: "entity.viewSettings",

View File

@ -23,7 +23,7 @@ export interface TabLayoutProps {
export interface TabLayoutRoute {
routePath: string;
title: React.ReactNode;
component: React.ComponentType<any>;
component: React.ComponentType<{}>;
url?: string; // page-url, if not provided `routePath` is used (doesn't work when path has some :placeholder(s))
exact?: boolean; // route-path matching rule
default?: boolean; // initial tab to open with provided `url, by default tabs[0] is used

View File

@ -155,7 +155,7 @@ class NonInjectedClusterFrame extends React.Component<Dependencies> {
<Route component={StorageRoute} {...routes.storageRoute}/>
<Route component={NamespacesRoute} {...routes.namespacesRoute}/>
<Route component={Events} {...routes.eventRoute}/>
<Route component={CustomResourcesRoute} {...routes.crdRoute}/>
<Route component={CustomResourcesRoute} {...routes.customResourcesRoute}/>
<Route component={UserManagementRoute} {...routes.usersManagementRoute}/>
<Route component={HelmRoute} {...routes.helmRoute}/>
{this.renderExtensionTabLayoutRoutes()}