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 { KubeCreationError, KubeObject } from "../kube-object";
import { KubeApi } from "../kube-api"; import { KubeApi } from "../kube-api";
import { crdResourcesURL } from "../../routes"; import { customResourceDefinitionsURL } from "../../routes";
import { isClusterPageContext } from "../../utils/cluster-id-url-parsing"; import { isClusterPageContext } from "../../utils/cluster-id-url-parsing";
import type { KubeJsonApiData } from "../kube-json-api"; import type { KubeJsonApiData } from "../kube-json-api";
@ -99,7 +99,7 @@ export class CustomResourceDefinition extends KubeObject {
} }
getResourceUrl() { getResourceUrl() {
return crdResourcesURL({ return customResourceDefinitionsURL({
params: { params: {
group: this.getGroup(), group: this.getGroup(),
name: this.getPluralName(), 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 type { ItemObject } from "../../item.store";
import { KubeObject } from "../kube-object"; import { KubeObject } from "../kube-object";
import type { JsonApiData } from "../json-api"; import type { JsonApiData } from "../json-api";
import { buildURLPositional } from "../../utils/buildUrl"; import { buildURL } from "../../utils/buildUrl";
import type { KubeJsonApiData } from "../kube-json-api"; import type { KubeJsonApiData } from "../kube-json-api";
interface IReleasePayload { interface IReleasePayload {
@ -77,16 +77,16 @@ interface EndpointQuery {
all?: boolean; 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[]> { 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); return releases.map(toHelmRelease);
} }
export async function getRelease(name: string, namespace: string): Promise<IReleaseDetails> { 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, ...details } = await apiBase.get<IReleaseRawDetails>(path);
const resources = rawResources.map(KubeObject.create); 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 chart = `${repo}/${rawChart}`;
const values = yaml.load(rawValues); const values = yaml.load(rawValues);
return apiBase.put(endpoint({ name, namespace }), { return apiBase.put(endpoint({ params: { name, namespace }}), {
data: { data: {
chart, chart,
values, 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> { 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); return apiBase.del(path);
} }
export async function getReleaseValues(name: string, namespace: string, all?: boolean): Promise<string> { export async function getReleaseValues(name: string, namespace: string, all?: boolean): Promise<string> {
const route = "values"; const route = "values";
const path = endpoint({ name, namespace, route }, { all }); const path = endpoint({ params: { name, namespace, route }, query: { all }});
return apiBase.get<string>(path); return apiBase.get<string>(path);
} }
export async function getReleaseHistory(name: string, namespace: string): Promise<IReleaseRevision[]> { export async function getReleaseHistory(name: string, namespace: string): Promise<IReleaseRevision[]> {
const route = "history"; const route = "history";
const path = endpoint({ name, namespace, route }); const path = endpoint({ params: { name, namespace, route }});
return apiBase.get(path); return apiBase.get(path);
} }
export async function rollbackRelease(name: string, namespace: string, revision: number): Promise<JsonApiData> { export async function rollbackRelease(name: string, namespace: string, revision: number): Promise<JsonApiData> {
const route = "rollback"; const route = "rollback";
const path = endpoint({ name, namespace, route }); const path = endpoint({ params: { name, namespace, route }});
const data = { revision }; const data = { revision };
return apiBase.put(path, { data }); return apiBase.put(path, { data });

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * 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 type { URLParams } from "../utils/buildUrl";
import { configMapsRoute, configMapsURL } from "./config-maps"; import { configMapsRoute, configMapsURL } from "./config-maps";
import { hpaRoute } from "./hpa"; import { hpaRoute } from "./hpa";
@ -12,15 +12,15 @@ import { pdbRoute } from "./pod-disruption-budgets";
import { resourceQuotaRoute } from "./resource-quotas"; import { resourceQuotaRoute } from "./resource-quotas";
import { secretsRoute } from "./secrets"; import { secretsRoute } from "./secrets";
export const configRoute: RouteProps = { export const configRoute: RouterProps = {
path: [ path: [
configMapsRoute, configMapsRoute.path,
secretsRoute, secretsRoute.path,
resourceQuotaRoute, resourceQuotaRoute.path,
limitRangesRoute, limitRangesRoute.path,
hpaRoute, hpaRoute.path,
pdbRoute, pdbRoute.path,
].map(route => route.path.toString()), ],
}; };
export const configURL = (params?: URLParams) => configMapsURL(params); 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. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import type { RouteProps } from "react-router"; import { buildURL, RouteProps } from "../utils/buildUrl";
import { buildURL } from "../utils/buildUrl";
export const crdRoute: RouteProps = { export const customResourcesRoute = "/crd";
path: "/crd",
export const customResourceDefinitionsRoute: RouteProps = {
path: `${customResourcesRoute}/definitions/:group?/:name?`,
}; };
export const crdDefinitionsRoute: RouteProps = { export interface CustomResourceDefinitionsRouteParams {
path: `${crdRoute.path}/definitions`, group?: string;
}; name?: string;
export const crdResourcesRoute: RouteProps = {
path: `${crdRoute.path}/:group/:name`,
};
export interface CRDListQuery {
groups?: string;
} }
export interface CRDRouteParams { export const customResourceDefinitionsURL = buildURL<CustomResourceDefinitionsRouteParams>(customResourceDefinitionsRoute.path);
group: string;
name: string;
}
export const crdURL = buildURL<{}, CRDListQuery>(crdDefinitionsRoute.path);
export const crdResourcesURL = buildURL<CRDRouteParams>(crdResourcesRoute.path);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,6 @@
*/ */
import type { RouteProps } from "react-router"; import type { RouteProps } from "react-router";
import type { URLParams } from "../utils/buildUrl";
import { endpointRoute } from "./endpoints"; import { endpointRoute } from "./endpoints";
import { ingressRoute } from "./ingresses"; import { ingressRoute } from "./ingresses";
import { networkPoliciesRoute } from "./network-policies"; import { networkPoliciesRoute } from "./network-policies";
@ -21,4 +20,4 @@ export const networkRoute: RouteProps = {
].map(route => route.path.toString()), ].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. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import type { RouteProps } from "react-router"; import { buildURL, RouteProps } from "../utils/buildUrl";
import { buildURL } from "../utils/buildUrl";
export const nodesRoute: RouteProps = { export const nodesRoute: RouteProps = {
path: "/nodes", path: "/nodes",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,6 @@
*/ */
import type { RouteProps } from "react-router"; import type { RouteProps } from "react-router";
import type { URLParams } from "../utils/buildUrl";
import { storageClassesRoute } from "./storage-classes"; import { storageClassesRoute } from "./storage-classes";
import { volumeClaimsRoute, volumeClaimsURL } from "./volume-claims"; import { volumeClaimsRoute, volumeClaimsURL } from "./volume-claims";
import { volumesRoute } from "./volumes"; import { volumesRoute } from "./volumes";
@ -17,4 +16,4 @@ export const storageRoute: RouteProps = {
].map(route => route.path.toString()), ].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. * 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 { buildURL, URLParams } from "../utils/buildUrl"; import { buildURL, RouteProps, URLParams } from "../utils/buildUrl";
// Routes // Routes
export const serviceAccountsRoute: RouteProps = { export const serviceAccountsRoute: RouteProps = {
@ -26,15 +26,15 @@ export const clusterRoleBindingsRoute: RouteProps = {
path: "/cluster-role-bindings", path: "/cluster-role-bindings",
}; };
export const usersManagementRoute: RouteProps = { export const usersManagementRoute: RouterProps = {
path: [ path: [
serviceAccountsRoute, serviceAccountsRoute.path,
podSecurityPoliciesRoute, podSecurityPoliciesRoute.path,
roleBindingsRoute, roleBindingsRoute.path,
clusterRoleBindingsRoute, clusterRoleBindingsRoute.path,
rolesRoute, rolesRoute.path,
clusterRolesRoute, clusterRolesRoute.path,
].map(route => route.path.toString()), ],
}; };
// Route params // Route params

View File

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

View File

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

View File

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

View File

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

View File

@ -4,32 +4,37 @@
*/ */
import { compile } from "path-to-regexp"; 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; params?: P;
query?: Q; query?: Q;
fragment?: string;
} }
export function buildURL<P extends object = {}, Q extends object = {}>(path: string | any) { export function buildURL<P = unknown, Q = unknown>(path: string): (urlParams?: URLParams<P, Q>) => string {
const pathBuilder = compile(String(path)); const pathBuilder = compile(path);
return function ({ params, query, fragment }: URLParams<P, Q> = {}): string { return (urlParams) => {
const queryParams = query ? new URLSearchParams(Object.entries(query)).toString() : ""; const { params = {}, query = {}, fragment } = urlParams as InternalURLParams<P, Q> ?? {};
const parts = [
pathBuilder(params),
queryParams && `?${queryParams}`,
fragment && `#${fragment}`,
];
return parts.filter(Boolean).join(""); return format({
}; pathname: pathBuilder(params),
} query: new URLSearchParams(query).toString(),
search: fragment,
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 });
}; };
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -23,7 +23,7 @@ export interface TabLayoutProps {
export interface TabLayoutRoute { export interface TabLayoutRoute {
routePath: string; routePath: string;
title: React.ReactNode; 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)) 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 exact?: boolean; // route-path matching rule
default?: boolean; // initial tab to open with provided `url, by default tabs[0] is used 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={StorageRoute} {...routes.storageRoute}/>
<Route component={NamespacesRoute} {...routes.namespacesRoute}/> <Route component={NamespacesRoute} {...routes.namespacesRoute}/>
<Route component={Events} {...routes.eventRoute}/> <Route component={Events} {...routes.eventRoute}/>
<Route component={CustomResourcesRoute} {...routes.crdRoute}/> <Route component={CustomResourcesRoute} {...routes.customResourcesRoute}/>
<Route component={UserManagementRoute} {...routes.usersManagementRoute}/> <Route component={UserManagementRoute} {...routes.usersManagementRoute}/>
<Route component={HelmRoute} {...routes.helmRoute}/> <Route component={HelmRoute} {...routes.helmRoute}/>
{this.renderExtensionTabLayoutRoutes()} {this.renderExtensionTabLayoutRoutes()}