diff --git a/extensions/support-page/renderer.tsx b/extensions/support-page/renderer.tsx index 87142c79da..9334a7a2a4 100644 --- a/extensions/support-page/renderer.tsx +++ b/extensions/support-page/renderer.tsx @@ -4,16 +4,16 @@ import { supportPageRoute, supportPageURL } from "./src/support.route"; import { Support } from "./src/support"; export default class SupportPageRendererExtension extends LensRendererExtension { - globalPages = [ - { - ...supportPageRoute, - url: supportPageURL(), - hideInMenu: true, - components: { - Page: Support, - } - } - ] + // globalPages = [ + // { + // ...supportPageRoute, + // url: supportPageURL(), + // hideInMenu: true, + // components: { + // Page: Support, + // } + // } + // ] statusBarItems = [ { diff --git a/src/extensions/dynamic-page.tsx b/src/extensions/dynamic-page.tsx deleted file mode 100644 index 854e0f27fd..0000000000 --- a/src/extensions/dynamic-page.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from "react"; -import { cssNames } from "../renderer/utils"; -import { TabLayout } from "../renderer/components/layout/tab-layout"; -import { PageRegistration } from "./registries/page-registry" - -export class DynamicPage extends React.Component<{ page: PageRegistration }> { - render() { - const { className, components: { Page }, subPages = [] } = this.props.page; - return ( - - - - ) - } -} diff --git a/src/extensions/extension-loader.ts b/src/extensions/extension-loader.ts index fc918476fb..095fafea29 100644 --- a/src/extensions/extension-loader.ts +++ b/src/extensions/extension-loader.ts @@ -65,6 +65,7 @@ export class ExtensionLoader { logger.info('[EXTENSIONS-LOADER]: load on main renderer (cluster manager)') this.autoInitExtensions((extension: LensRendererExtension) => [ registries.globalPageRegistry.add(...extension.globalPages), + registries.globalPageMenuRegistry.add(...extension.globalPageMenus), registries.appPreferenceRegistry.add(...extension.appPreferences), registries.clusterFeatureRegistry.add(...extension.clusterFeatures), registries.statusBarRegistry.add(...extension.statusBarItems), @@ -75,6 +76,7 @@ export class ExtensionLoader { logger.info('[EXTENSIONS-LOADER]: load on cluster renderer (dashboard)') this.autoInitExtensions((extension: LensRendererExtension) => [ registries.clusterPageRegistry.add(...extension.clusterPages), + registries.clusterPageMenuRegistry.add(...extension.clusterPageMenus), registries.kubeObjectMenuRegistry.add(...extension.kubeObjectMenuItems), registries.kubeObjectDetailRegistry.add(...extension.kubeObjectDetailItems), ]); diff --git a/src/extensions/lens-renderer-extension.ts b/src/extensions/lens-renderer-extension.ts index 502734854f..fb595bc68d 100644 --- a/src/extensions/lens-renderer-extension.ts +++ b/src/extensions/lens-renderer-extension.ts @@ -1,14 +1,16 @@ import type { AppPreferenceRegistration, ClusterFeatureRegistration, - KubeObjectMenuRegistration, KubeObjectDetailRegistration, - PageRegistration, StatusBarRegistration + KubeObjectMenuRegistration, KubeObjectDetailRegistration, StatusBarRegistration, + PageRegistration, PageMenuRegistration, PageRegistrationCluster, PageMenuRegistrationCluster, } from "./registries" import { observable } from "mobx"; import { LensExtension } from "./lens-extension" export class LensRendererExtension extends LensExtension { @observable.shallow globalPages: PageRegistration[] = [] - @observable.shallow clusterPages: PageRegistration[] = [] + @observable.shallow clusterPages: PageRegistrationCluster[] = [] + @observable.shallow globalPageMenus: PageMenuRegistration[] = [] + @observable.shallow clusterPageMenus: PageMenuRegistrationCluster[] = [] @observable.shallow appPreferences: AppPreferenceRegistration[] = [] @observable.shallow clusterFeatures: ClusterFeatureRegistration[] = [] @observable.shallow statusBarItems: StatusBarRegistration[] = [] diff --git a/src/extensions/registries/cluster-feature-registry.ts b/src/extensions/registries/cluster-feature-registry.ts index f17ee0ff92..0b579fd639 100644 --- a/src/extensions/registries/cluster-feature-registry.ts +++ b/src/extensions/registries/cluster-feature-registry.ts @@ -1,3 +1,4 @@ +import type React from "react" import { BaseRegistry } from "./base-registry"; import { ClusterFeature } from "../cluster-feature"; diff --git a/src/extensions/registries/index.ts b/src/extensions/registries/index.ts index 137a725170..cb9afcb495 100644 --- a/src/extensions/registries/index.ts +++ b/src/extensions/registries/index.ts @@ -1,6 +1,7 @@ // All registries managed by extensions api export * from "./page-registry" +export * from "./page-menu-registry" export * from "./menu-registry" export * from "./app-preference-registry" export * from "./status-bar-registry" diff --git a/src/extensions/registries/page-menu-registry.ts b/src/extensions/registries/page-menu-registry.ts new file mode 100644 index 0000000000..150a8c459d --- /dev/null +++ b/src/extensions/registries/page-menu-registry.ts @@ -0,0 +1,25 @@ +// Extensions-api -> Register page menu items + +import type React from "react"; +import type { IconProps } from "../../renderer/components/icon"; +import { BaseRegistry } from "./base-registry"; + +export interface PageMenuRegistration { + url: string; + title: React.ReactNode; + components: PageMenuComponents; +} + +export interface PageMenuRegistrationCluster extends PageMenuRegistration { + subMenus?: Omit[]; +} + +export interface PageMenuComponents { + Icon: React.ComponentType; +} + +export class PageMenuRegistry extends BaseRegistry { +} + +export const globalPageMenuRegistry = new PageMenuRegistry(); +export const clusterPageMenuRegistry = new PageMenuRegistry(); diff --git a/src/extensions/registries/page-registry.ts b/src/extensions/registries/page-registry.ts index 0f448e1284..8494eeda27 100644 --- a/src/extensions/registries/page-registry.ts +++ b/src/extensions/registries/page-registry.ts @@ -1,31 +1,29 @@ // Extensions-api -> Custom page registration import type React from "react"; -import type { RouteProps } from "react-router"; -import type { IconProps } from "../../renderer/components/icon"; -import type { IClassName } from "../../renderer/utils"; -import type { TabRoute } from "../../renderer/components/layout/tab-layout"; import { BaseRegistry } from "./base-registry"; -export interface PageRegistration extends RouteProps { - className?: IClassName; - url?: string; // initial url to be used for building menus and tabs, otherwise "path" applied by default - title?: React.ReactNode; // used in sidebar's & tabs-layout if provided - hideInMenu?: boolean; // hide element within app's navigation menu - subPages?: (PageRegistration & TabRoute)[]; +export interface PageRegistration { + routePath: string; // react-router's path, e.g. "/page/:id" + exact?: boolean; // route matching flag, see: https://reactrouter.com/web/api/NavLink/exact-bool components: PageComponents; } +export interface PageRegistrationCluster extends PageRegistration { + subPages?: Omit; +} + export interface PageComponents { Page: React.ComponentType; - MenuIcon?: React.ComponentType; } -export class GlobalPageRegistry extends BaseRegistry { +export class PageRegistry extends BaseRegistry { + protected routePrefixPath = "/extensions/:name" // todo: figure out how to provide inside extension + + getItems() { + return super.getItems(); + } } -export class ClusterPageRegistry extends BaseRegistry { -} - -export const globalPageRegistry = new GlobalPageRegistry(); -export const clusterPageRegistry = new ClusterPageRegistry(); +export const globalPageRegistry = new PageRegistry(); +export const clusterPageRegistry = new PageRegistry(); diff --git a/src/extensions/renderer-api/navigation.ts b/src/extensions/renderer-api/navigation.ts index d069f753e1..b49337c5d3 100644 --- a/src/extensions/renderer-api/navigation.ts +++ b/src/extensions/renderer-api/navigation.ts @@ -1,3 +1,2 @@ export { navigate, hideDetails, showDetails, getDetailsUrl } from "../../renderer/navigation" -export { RouteProps } from "react-router" export { IURLParams } from "../../common/utils/buildUrl"; diff --git a/src/renderer/components/+apps/apps.tsx b/src/renderer/components/+apps/apps.tsx index 5ee297a1c0..469765aa68 100644 --- a/src/renderer/components/+apps/apps.tsx +++ b/src/renderer/components/+apps/apps.tsx @@ -1,41 +1,34 @@ import React from "react"; import { observer } from "mobx-react"; -import { Redirect, Route, Switch } from "react-router"; import { Trans } from "@lingui/macro"; -import { TabLayout, TabRoute } from "../layout/tab-layout"; +import { TabLayout, TabLayoutRoute } from "../layout/tab-layout"; import { HelmCharts, helmChartsRoute, helmChartsURL } from "../+apps-helm-charts"; import { HelmReleases, releaseRoute, releaseURL } from "../+apps-releases"; import { namespaceStore } from "../+namespaces/namespace.store"; @observer export class Apps extends React.Component { - static get tabRoutes(): TabRoute[] { + static get tabRoutes(): TabLayoutRoute[] { const query = namespaceStore.getContextParams(); return [ { title: Charts, component: HelmCharts, url: helmChartsURL(), - path: helmChartsRoute.path, + routePath: helmChartsRoute.path.toString(), }, { title: Releases, component: HelmReleases, url: releaseURL({ query }), - path: releaseRoute.path, + routePath: releaseRoute.path.toString(), }, ] } render() { - const tabRoutes = Apps.tabRoutes; return ( - - - {tabRoutes.map((route, index) => )} - - - + ) } } diff --git a/src/renderer/components/+config/config.route.ts b/src/renderer/components/+config/config.route.ts index f480e84952..d747f49efd 100644 --- a/src/renderer/components/+config/config.route.ts +++ b/src/renderer/components/+config/config.route.ts @@ -5,7 +5,7 @@ import { configMapsURL } from "../+config-maps/config-maps.route"; export const configRoute: RouteProps = { get path() { - return Config.tabRoutes.map(({ path }) => path).flat() + return Config.tabRoutes.map(({ routePath }) => routePath).flat() } } diff --git a/src/renderer/components/+config/config.tsx b/src/renderer/components/+config/config.tsx index 7e42c7fcc8..f738cb273e 100644 --- a/src/renderer/components/+config/config.tsx +++ b/src/renderer/components/+config/config.tsx @@ -1,33 +1,26 @@ import React from "react"; import { observer } from "mobx-react"; -import { Redirect, Route, Switch } from "react-router"; import { Trans } from "@lingui/macro"; -import { TabLayout, TabRoute } from "../layout/tab-layout"; +import { TabLayout, TabLayoutRoute } from "../layout/tab-layout"; import { ConfigMaps, configMapsRoute, configMapsURL } from "../+config-maps"; import { Secrets, secretsRoute, secretsURL } from "../+config-secrets"; import { namespaceStore } from "../+namespaces/namespace.store"; import { resourceQuotaRoute, ResourceQuotas, resourceQuotaURL } from "../+config-resource-quotas"; -import { PodDisruptionBudgets, pdbRoute, pdbURL } from "../+config-pod-disruption-budgets"; -import { configURL } from "./config.route"; +import { pdbRoute, pdbURL, PodDisruptionBudgets } from "../+config-pod-disruption-budgets"; import { HorizontalPodAutoscalers, hpaRoute, hpaURL } from "../+config-autoscalers"; import { isAllowedResource } from "../../../common/rbac" -import { buildURL } from "../../../common/utils/buildUrl"; - -export const certificatesURL = buildURL("/certificates"); -export const issuersURL = buildURL("/issuers"); -export const clusterIssuersURL = buildURL("/clusterissuers"); @observer export class Config extends React.Component { - static get tabRoutes(): TabRoute[] { + static get tabRoutes(): TabLayoutRoute[] { const query = namespaceStore.getContextParams() - const routes: TabRoute[] = [] + const routes: TabLayoutRoute[] = [] if (isAllowedResource("configmaps")) { routes.push({ title: ConfigMaps, component: ConfigMaps, url: configMapsURL({ query }), - path: configMapsRoute.path, + routePath: configMapsRoute.path.toString(), }) } if (isAllowedResource("secrets")) { @@ -35,7 +28,7 @@ export class Config extends React.Component { title: Secrets, component: Secrets, url: secretsURL({ query }), - path: secretsRoute.path, + routePath: secretsRoute.path.toString(), }) } if (isAllowedResource("resourcequotas")) { @@ -43,7 +36,7 @@ export class Config extends React.Component { title: Resource Quotas, component: ResourceQuotas, url: resourceQuotaURL({ query }), - path: resourceQuotaRoute.path, + routePath: resourceQuotaRoute.path.toString(), }) } if (isAllowedResource("horizontalpodautoscalers")) { @@ -51,7 +44,7 @@ export class Config extends React.Component { title: HPA, component: HorizontalPodAutoscalers, url: hpaURL({ query }), - path: hpaRoute.path, + routePath: hpaRoute.path.toString(), }) } if (isAllowedResource("poddisruptionbudgets")) { @@ -59,21 +52,15 @@ export class Config extends React.Component { title: Pod Disruption Budgets, component: PodDisruptionBudgets, url: pdbURL({ query }), - path: pdbRoute.path, + routePath: pdbRoute.path.toString(), }) } return routes; } render() { - const tabRoutes = Config.tabRoutes; return ( - - - {tabRoutes.map((route, index) => )} - - - + ) } } diff --git a/src/renderer/components/+custom-resources/custom-resources.tsx b/src/renderer/components/+custom-resources/custom-resources.tsx index 9dbd882bd1..ee34809013 100644 --- a/src/renderer/components/+custom-resources/custom-resources.tsx +++ b/src/renderer/components/+custom-resources/custom-resources.tsx @@ -2,23 +2,20 @@ import React from "react"; import { observer } from "mobx-react"; import { Redirect, Route, Switch } from "react-router"; import { Trans } from "@lingui/macro"; -import { TabLayout, TabRoute } from "../layout/tab-layout"; +import { TabLayout, TabLayoutRoute } from "../layout/tab-layout"; import { crdResourcesRoute, crdRoute, crdURL, crdDefinitionsRoute } from "./crd.route"; import { CrdList } from "./crd-list"; import { CrdResources } from "./crd-resources"; -// todo: next steps - customization via plugins -// todo: list views (rows content), full details view and if possible chart/prometheus hooks - @observer export class CustomResources extends React.Component { - static get tabRoutes(): TabRoute[] { + static get tabRoutes(): TabLayoutRoute[] { return [ { title: Definitions, component: CustomResources, url: crdURL(), - path: crdRoute.path, + routePath: crdRoute.path.toString(), } ] } diff --git a/src/renderer/components/+network/network.route.ts b/src/renderer/components/+network/network.route.ts index 3029b85fa2..c13f7cedf7 100644 --- a/src/renderer/components/+network/network.route.ts +++ b/src/renderer/components/+network/network.route.ts @@ -5,7 +5,7 @@ import { IURLParams } from "../../../common/utils/buildUrl"; export const networkRoute: RouteProps = { get path() { - return Network.tabRoutes.map(({ path }) => path).flat() + return Network.tabRoutes.map(({ routePath }) => routePath).flat() } } diff --git a/src/renderer/components/+network/network.tsx b/src/renderer/components/+network/network.tsx index 2932f5860c..9e0b2d92c6 100644 --- a/src/renderer/components/+network/network.tsx +++ b/src/renderer/components/+network/network.tsx @@ -2,32 +2,26 @@ import "./network.scss" import React from "react"; import { observer } from "mobx-react"; -import { Redirect, Route, Switch } from "react-router"; -import { RouteComponentProps } from "react-router-dom"; import { Trans } from "@lingui/macro"; -import { TabLayout, TabRoute } from "../layout/tab-layout"; +import { TabLayout, TabLayoutRoute } from "../layout/tab-layout"; import { Services, servicesRoute, servicesURL } from "../+network-services"; -import { Endpoints, endpointRoute, endpointURL } from "../+network-endpoints"; +import { endpointRoute, Endpoints, endpointURL } from "../+network-endpoints"; import { Ingresses, ingressRoute, ingressURL } from "../+network-ingresses"; import { NetworkPolicies, networkPoliciesRoute, networkPoliciesURL } from "../+network-policies"; import { namespaceStore } from "../+namespaces/namespace.store"; -import { networkURL } from "./network.route"; import { isAllowedResource } from "../../../common/rbac"; -interface Props extends RouteComponentProps<{}> { -} - @observer -export class Network extends React.Component { - static get tabRoutes(): TabRoute[] { +export class Network extends React.Component { + static get tabRoutes(): TabLayoutRoute[] { const query = namespaceStore.getContextParams() - const routes: TabRoute[] = []; + const routes: TabLayoutRoute[] = []; if (isAllowedResource("services")) { routes.push({ title: Services, component: Services, url: servicesURL({ query }), - path: servicesRoute.path, + routePath: servicesRoute.path.toString(), }) } if (isAllowedResource("endpoints")) { @@ -35,7 +29,7 @@ export class Network extends React.Component { title: Endpoints, component: Endpoints, url: endpointURL({ query }), - path: endpointRoute.path, + routePath: endpointRoute.path.toString(), }) } if (isAllowedResource("ingresses")) { @@ -43,7 +37,7 @@ export class Network extends React.Component { title: Ingresses, component: Ingresses, url: ingressURL({ query }), - path: ingressRoute.path, + routePath: ingressRoute.path.toString(), }) } if (isAllowedResource("networkpolicies")) { @@ -51,21 +45,15 @@ export class Network extends React.Component { title: Network Policies, component: NetworkPolicies, url: networkPoliciesURL({ query }), - path: networkPoliciesRoute.path, + routePath: networkPoliciesRoute.path.toString(), }) } return routes } render() { - const tabRoutes = Network.tabRoutes; return ( - - - {tabRoutes.map((route, index) => )} - - - + ) } } diff --git a/src/renderer/components/+storage/storage.route.ts b/src/renderer/components/+storage/storage.route.ts index 6a7ab065f9..6fe3bb6a0a 100644 --- a/src/renderer/components/+storage/storage.route.ts +++ b/src/renderer/components/+storage/storage.route.ts @@ -5,7 +5,7 @@ import { IURLParams } from "../../../common/utils/buildUrl"; export const storageRoute: RouteProps = { get path() { - return Storage.tabRoutes.map(({ path }) => path).flat() + return Storage.tabRoutes.map(({ routePath }) => routePath).flat() } } diff --git a/src/renderer/components/+storage/storage.tsx b/src/renderer/components/+storage/storage.tsx index c86afad2e0..e8a105944b 100644 --- a/src/renderer/components/+storage/storage.tsx +++ b/src/renderer/components/+storage/storage.tsx @@ -2,31 +2,25 @@ import "./storage.scss" import React from "react"; import { observer } from "mobx-react"; -import { Redirect, Route, Switch } from "react-router"; -import { RouteComponentProps } from "react-router-dom"; import { Trans } from "@lingui/macro"; -import { TabLayout, TabRoute } from "../layout/tab-layout"; +import { TabLayout, TabLayoutRoute } from "../layout/tab-layout"; import { PersistentVolumes, volumesRoute, volumesURL } from "../+storage-volumes"; import { StorageClasses, storageClassesRoute, storageClassesURL } from "../+storage-classes"; import { PersistentVolumeClaims, volumeClaimsRoute, volumeClaimsURL } from "../+storage-volume-claims"; import { namespaceStore } from "../+namespaces/namespace.store"; -import { storageURL } from "./storage.route"; import { isAllowedResource } from "../../../common/rbac"; -interface Props extends RouteComponentProps<{}> { -} - @observer -export class Storage extends React.Component { +export class Storage extends React.Component { static get tabRoutes() { - const tabRoutes: TabRoute[] = []; + const tabRoutes: TabLayoutRoute[] = []; const query = namespaceStore.getContextParams() tabRoutes.push({ title: Persistent Volume Claims, component: PersistentVolumeClaims, url: volumeClaimsURL({ query }), - path: volumeClaimsRoute.path, + routePath: volumeClaimsRoute.path.toString(), }) if (isAllowedResource('persistentvolumes')) { @@ -34,7 +28,7 @@ export class Storage extends React.Component { title: Persistent Volumes, component: PersistentVolumes, url: volumesURL(), - path: volumesRoute.path, + routePath: volumesRoute.path.toString(), }); } @@ -43,21 +37,15 @@ export class Storage extends React.Component { title: Storage Classes, component: StorageClasses, url: storageClassesURL(), - path: storageClassesRoute.path, + routePath: storageClassesRoute.path.toString(), }) } return tabRoutes; } render() { - const tabRoutes = Storage.tabRoutes; return ( - - - {tabRoutes.map((route, index) => )} - - - + ) } } diff --git a/src/renderer/components/+user-management/user-management.route.ts b/src/renderer/components/+user-management/user-management.route.ts index 04de465e3f..aa520fb128 100644 --- a/src/renderer/components/+user-management/user-management.route.ts +++ b/src/renderer/components/+user-management/user-management.route.ts @@ -4,7 +4,7 @@ import { UserManagement } from "./user-management" export const usersManagementRoute: RouteProps = { get path() { - return UserManagement.tabRoutes.map(({ path }) => path).flat() + return UserManagement.tabRoutes.map(({ routePath }) => routePath).flat() } } diff --git a/src/renderer/components/+user-management/user-management.tsx b/src/renderer/components/+user-management/user-management.tsx index 97cf71140e..e7985fae6a 100644 --- a/src/renderer/components/+user-management/user-management.tsx +++ b/src/renderer/components/+user-management/user-management.tsx @@ -1,44 +1,39 @@ import "./user-management.scss" import React from "react"; import { observer } from "mobx-react"; -import { Redirect, Route, Switch } from "react-router"; -import { RouteComponentProps } from "react-router-dom"; import { Trans } from "@lingui/macro"; -import { TabLayout, TabRoute } from "../layout/tab-layout"; +import { TabLayout, TabLayoutRoute } from "../layout/tab-layout"; import { Roles } from "../+user-management-roles"; import { RoleBindings } from "../+user-management-roles-bindings"; import { ServiceAccounts } from "../+user-management-service-accounts"; -import { roleBindingsRoute, roleBindingsURL, rolesRoute, rolesURL, serviceAccountsRoute, serviceAccountsURL, usersManagementURL } from "./user-management.route"; +import { roleBindingsRoute, roleBindingsURL, rolesRoute, rolesURL, serviceAccountsRoute, serviceAccountsURL } from "./user-management.route"; import { namespaceStore } from "../+namespaces/namespace.store"; import { PodSecurityPolicies, podSecurityPoliciesRoute, podSecurityPoliciesURL } from "../+pod-security-policies"; import { isAllowedResource } from "../../../common/rbac"; -interface Props extends RouteComponentProps<{}> { -} - @observer -export class UserManagement extends React.Component { +export class UserManagement extends React.Component { static get tabRoutes() { - const tabRoutes: TabRoute[] = []; + const tabRoutes: TabLayoutRoute[] = []; const query = namespaceStore.getContextParams() tabRoutes.push( { title: Service Accounts, component: ServiceAccounts, url: serviceAccountsURL({ query }), - path: serviceAccountsRoute.path, + routePath: serviceAccountsRoute.path.toString(), }, { title: Role Bindings, component: RoleBindings, url: roleBindingsURL({ query }), - path: roleBindingsRoute.path, + routePath: roleBindingsRoute.path.toString(), }, { title: Roles, component: Roles, url: rolesURL({ query }), - path: rolesRoute.path, + routePath: rolesRoute.path.toString(), }, ) if (isAllowedResource("podsecuritypolicies")) { @@ -46,21 +41,15 @@ export class UserManagement extends React.Component { title: Pod Security Policies, component: PodSecurityPolicies, url: podSecurityPoliciesURL(), - path: podSecurityPoliciesRoute.path, + routePath: podSecurityPoliciesRoute.path.toString(), }) } return tabRoutes; } render() { - const tabRoutes = UserManagement.tabRoutes; return ( - - - {tabRoutes.map((route, index) => )} - - - + ) } } diff --git a/src/renderer/components/+workloads/workloads.route.ts b/src/renderer/components/+workloads/workloads.route.ts index 47044b0d18..03e1f384a1 100644 --- a/src/renderer/components/+workloads/workloads.route.ts +++ b/src/renderer/components/+workloads/workloads.route.ts @@ -5,7 +5,7 @@ import { Workloads } from "./workloads"; export const workloadsRoute: RouteProps = { get path() { - return Workloads.tabRoutes.map(({ path }) => path).flat() + return Workloads.tabRoutes.map(({ routePath }) => routePath).flat() } } diff --git a/src/renderer/components/+workloads/workloads.tsx b/src/renderer/components/+workloads/workloads.tsx index fe6e1606b5..8c75e0b203 100644 --- a/src/renderer/components/+workloads/workloads.tsx +++ b/src/renderer/components/+workloads/workloads.tsx @@ -2,12 +2,10 @@ import "./workloads.scss" import React from "react"; import { observer } from "mobx-react"; -import { Redirect, Route, Switch } from "react-router"; -import { RouteComponentProps } from "react-router-dom"; import { Trans } from "@lingui/macro"; -import { TabLayout, TabRoute } from "../layout/tab-layout"; +import { TabLayout, TabLayoutRoute } from "../layout/tab-layout"; import { WorkloadsOverview } from "../+workloads-overview/overview"; -import { cronJobsRoute, cronJobsURL, daemonSetsRoute, daemonSetsURL, deploymentsRoute, deploymentsURL, jobsRoute, jobsURL, overviewRoute, overviewURL, podsRoute, podsURL, statefulSetsRoute, statefulSetsURL, workloadsURL } from "./workloads.route"; +import { cronJobsRoute, cronJobsURL, daemonSetsRoute, daemonSetsURL, deploymentsRoute, deploymentsURL, jobsRoute, jobsURL, overviewRoute, overviewURL, podsRoute, podsURL, statefulSetsRoute, statefulSetsURL } from "./workloads.route"; import { namespaceStore } from "../+namespaces/namespace.store"; import { Pods } from "../+workloads-pods"; import { Deployments } from "../+workloads-deployments"; @@ -17,19 +15,16 @@ import { Jobs } from "../+workloads-jobs"; import { CronJobs } from "../+workloads-cronjobs"; import { isAllowedResource } from "../../../common/rbac" -interface Props extends RouteComponentProps { -} - @observer -export class Workloads extends React.Component { - static get tabRoutes(): TabRoute[] { +export class Workloads extends React.Component { + static get tabRoutes(): TabLayoutRoute[] { const query = namespaceStore.getContextParams(); - const routes: TabRoute[] = [ + const routes: TabLayoutRoute[] = [ { title: Overview, component: WorkloadsOverview, url: overviewURL({ query }), - path: overviewRoute.path + routePath: overviewRoute.path.toString() } ] if (isAllowedResource("pods")) { @@ -37,7 +32,7 @@ export class Workloads extends React.Component { title: Pods, component: Pods, url: podsURL({ query }), - path: podsRoute.path + routePath: podsRoute.path.toString() }) } if (isAllowedResource("deployments")) { @@ -45,7 +40,7 @@ export class Workloads extends React.Component { title: Deployments, component: Deployments, url: deploymentsURL({ query }), - path: deploymentsRoute.path, + routePath: deploymentsRoute.path.toString(), }) } if (isAllowedResource("daemonsets")) { @@ -53,7 +48,7 @@ export class Workloads extends React.Component { title: DaemonSets, component: DaemonSets, url: daemonSetsURL({ query }), - path: daemonSetsRoute.path, + routePath: daemonSetsRoute.path.toString(), }) } if (isAllowedResource("statefulsets")) { @@ -61,7 +56,7 @@ export class Workloads extends React.Component { title: StatefulSets, component: StatefulSets, url: statefulSetsURL({ query }), - path: statefulSetsRoute.path, + routePath: statefulSetsRoute.path.toString(), }) } if (isAllowedResource("jobs")) { @@ -69,7 +64,7 @@ export class Workloads extends React.Component { title: Jobs, component: Jobs, url: jobsURL({ query }), - path: jobsRoute.path, + routePath: jobsRoute.path.toString(), }) } if (isAllowedResource("cronjobs")) { @@ -77,21 +72,15 @@ export class Workloads extends React.Component { title: CronJobs, component: CronJobs, url: cronJobsURL({ query }), - path: cronJobsRoute.path, + routePath: cronJobsRoute.path.toString(), }) } return routes; } render() { - const tabRoutes = Workloads.tabRoutes; return ( - - - {tabRoutes.map((route, index) => )} - - - + ) } } diff --git a/src/renderer/components/app.tsx b/src/renderer/components/app.tsx index a5ebe554d1..0e388dfb85 100755 --- a/src/renderer/components/app.tsx +++ b/src/renderer/components/app.tsx @@ -29,6 +29,7 @@ import { CustomResources } from "./+custom-resources/custom-resources"; import { crdRoute } from "./+custom-resources"; import { isAllowedResource } from "../../common/rbac"; import { MainLayout } from "./layout/main-layout"; +import { TabLayout, TabLayoutRoute } from "./layout/tab-layout"; import { ErrorBoundary } from "./error-boundary"; import { Terminal } from "./dock/terminal"; import { getHostedCluster, getHostedClusterId } from "../../common/cluster-store"; @@ -36,7 +37,6 @@ import logger from "../../main/logger"; import { clusterIpc } from "../../common/cluster-ipc"; import { webFrame } from "electron"; import { clusterPageRegistry } from "../../extensions/registries/page-registry"; -import { DynamicPage } from "../../extensions/dynamic-page"; import { extensionLoader } from "../../extensions/extension-loader"; import { appEventBus } from "../../common/event-bus"; import whatInput from 'what-input'; @@ -52,9 +52,13 @@ export class App extends React.Component { await clusterIpc.setFrameId.invokeFromRenderer(clusterId, frameId); await getHostedCluster().whenReady; // cluster.activate() is done at this point extensionLoader.loadOnClusterRenderer(); - appEventBus.emit({name: "cluster", action: "open", params: { - clusterId: clusterId - }}) + appEventBus.emit({ + name: "cluster", + action: "open", + params: { + clusterId: clusterId + } + }) window.addEventListener("online", () => { window.location.reload() }) @@ -86,12 +90,19 @@ export class App extends React.Component { - {clusterPageRegistry.getItems().map(page => { - return }/> + {clusterPageRegistry.getItems().map(({ components: { Page }, subPages = [], exact, routePath }) => { + // return ( + // ( + // + // + // + // )}/> + // ) })} - + + diff --git a/src/renderer/components/cluster-manager/cluster-manager.tsx b/src/renderer/components/cluster-manager/cluster-manager.tsx index 4a42a0419d..1522376c3e 100644 --- a/src/renderer/components/cluster-manager/cluster-manager.tsx +++ b/src/renderer/components/cluster-manager/cluster-manager.tsx @@ -69,8 +69,8 @@ export class ClusterManager extends React.Component { - {globalPageRegistry.getItems().map(({ path, url = String(path), components: { Page } }) => { - return + {globalPageRegistry.getItems().map(({ routePath, exact, components: { Page } }) => { + return })} diff --git a/src/renderer/components/cluster-manager/clusters-menu.tsx b/src/renderer/components/cluster-manager/clusters-menu.tsx index 3d1e6debaf..9e5c550f67 100644 --- a/src/renderer/components/cluster-manager/clusters-menu.tsx +++ b/src/renderer/components/cluster-manager/clusters-menu.tsx @@ -5,6 +5,7 @@ import { remote } from "electron" import type { Cluster } from "../../../main/cluster"; import { DragDropContext, Draggable, DraggableProvided, Droppable, DroppableProvided, DropResult } from "react-beautiful-dnd"; import { observer } from "mobx-react"; +import { matchPath } from "react-router"; import { _i18n } from "../../i18n"; import { t, Trans } from "@lingui/macro"; import { userStore } from "../../../common/user-store"; @@ -14,7 +15,7 @@ import { ClusterIcon } from "../cluster-icon"; import { Icon } from "../icon"; import { autobind, cssNames, IClassName } from "../../utils"; import { Badge } from "../badge"; -import { navigate } from "../../navigation"; +import { navigate, navigation } from "../../navigation"; import { addClusterURL } from "../+add-cluster"; import { clusterSettingsURL } from "../+cluster-settings"; import { landingURL } from "../+landing-page"; @@ -22,7 +23,7 @@ import { Tooltip } from "../tooltip"; import { ConfirmDialog } from "../confirm-dialog"; import { clusterIpc } from "../../../common/cluster-ipc"; import { clusterViewURL } from "./cluster-view.route"; -import { globalPageRegistry } from "../../../extensions/registries/page-registry"; +import { globalPageMenuRegistry } from "../../../extensions/registries"; interface Props { className?: IClassName; @@ -138,7 +139,7 @@ export class ClustersMenu extends React.Component { -
+
Add Cluster @@ -148,9 +149,17 @@ export class ClustersMenu extends React.Component { )}
- {globalPageRegistry.getItems().map(({ path, url = String(path), hideInMenu, components: { MenuIcon } }) => { - if (!MenuIcon || hideInMenu) return; - return navigate(url)}/> + {globalPageMenuRegistry.getItems().map(({ title, url, components: { Icon } }) => { + const routePath = "" // todo: find matching route in page-registry + exact + const isActive = !!matchPath(navigation.location.pathname, { path: routePath/*, exact: false*/ }); + return ( + navigate(url)} + /> + ) })}
diff --git a/src/renderer/components/layout/sidebar.tsx b/src/renderer/components/layout/sidebar.tsx index 8bce1a32c8..bd5f26d10c 100644 --- a/src/renderer/components/layout/sidebar.tsx +++ b/src/renderer/components/layout/sidebar.tsx @@ -1,4 +1,4 @@ -import type { TabRoute } from "./tab-layout"; +import type { TabLayoutRoute } from "./tab-layout"; import "./sidebar.scss"; import React from "react"; @@ -27,9 +27,9 @@ import { crdStore } from "../+custom-resources/crd.store"; import { CrdList, crdResourcesRoute, crdRoute, crdURL } from "../+custom-resources"; import { CustomResources } from "../+custom-resources/custom-resources"; import { navigation } from "../../navigation"; -import { clusterPageRegistry } from "../../../extensions/registries/page-registry"; -import { isAllowedResource } from "../../../common/rbac"; +import { isAllowedResource } from "../../../common/rbac" import { Spinner } from "../spinner"; +import { clusterPageMenuRegistry } from "../../../extensions/registries"; const SidebarContext = React.createContext({ pinned: false }); type SidebarContextValue = { @@ -56,18 +56,17 @@ export class Sidebar extends React.Component { } return Object.entries(crdStore.groups).map(([group, crds]) => { - const submenus = crds.map((crd) => { + const submenus: TabLayoutRoute[] = crds.map((crd) => { return { title: crd.getResourceKind(), component: CrdList, url: crd.getResourceUrl(), - path: crdResourcesRoute.path, + routePath: String(crdResourcesRoute.path), }; }); return ( {
Cluster} icon={} /> Nodes} icon={} /> { icon={} /> { icon={} /> { icon={} /> { text={Storage} /> } text={Namespaces} /> { text={Events} /> { text={Apps} /> { text={Access Control} /> { > {this.renderCustomResources()} - {clusterPageRegistry.getItems().map(({ path, title, url = String(path), hideInMenu, components: { MenuIcon } }) => { - if (!MenuIcon || hideInMenu) { - return; - } + {clusterPageMenuRegistry.getItems().map(({ title, url, components: { Icon } }) => { + const routePath = "" // todo: find in page-registry return ( } + key={url} url={url} + routePath={routePath} + text={title} icon={} /> ) })} @@ -211,46 +195,45 @@ export class Sidebar extends React.Component { } interface SidebarNavItemProps { - id: string; url: string; text: React.ReactNode | string; className?: string; icon?: React.ReactNode; isHidden?: boolean; routePath?: string | string[]; - subMenus?: TabRoute[]; + subMenus?: TabLayoutRoute[]; } const navItemStorage = createStorage<[string, boolean][]>("sidebar_menu_item", []); const navItemState = observable.map(navItemStorage.get()); -reaction( - () => [...navItemState], - (value) => navItemStorage.set(value) -); +reaction(() => [...navItemState], (value) => navItemStorage.set(value)); @observer class SidebarNavItem extends React.Component { static contextType = SidebarContext; public context: SidebarContextValue; + get itemId() { + return this.props.url; + } + @computed get isExpanded() { - return navItemState.get(this.props.id); + return navItemState.get(this.itemId); } toggleSubMenu = () => { - navItemState.set(this.props.id, !this.isExpanded); + navItemState.set(this.itemId, !this.isExpanded); }; isActive = () => { - const { routePath, url } = this.props; - const { pathname } = navigation.location; - return !!matchPath(pathname, { - path: routePath || url, + const { url, routePath = url } = this.props; + return !!matchPath(navigation.location.pathname, { + path: routePath }); }; render() { - const { id, isHidden, subMenus = [], icon, text, url, children, className } = this.props; + const { isHidden, subMenus = [], icon, text, url, children, className } = this.props; if (isHidden) { return null; } @@ -258,7 +241,7 @@ class SidebarNavItem extends React.Component { if (extendedView) { const isActive = this.isActive(); return ( -
+
{icon} {text} diff --git a/src/renderer/components/layout/tab-layout.tsx b/src/renderer/components/layout/tab-layout.tsx index 7369399627..6f00b13f7f 100644 --- a/src/renderer/components/layout/tab-layout.tsx +++ b/src/renderer/components/layout/tab-layout.tsx @@ -1,38 +1,55 @@ import "./tab-layout.scss"; + import React, { ReactNode } from "react"; -import { matchPath, RouteProps } from "react-router-dom"; +import { matchPath, Redirect, Route, Switch } from "react-router"; import { observer } from "mobx-react"; -import { cssNames } from "../../utils"; +import { cssNames, IClassName } from "../../utils"; import { Tab, Tabs } from "../tabs"; import { ErrorBoundary } from "../error-boundary"; import { navigate, navigation } from "../../navigation"; -export interface TabRoute extends RouteProps { - title: React.ReactNode; - url: string; -} - export interface TabLayoutProps { - children: ReactNode; - className?: any; - tabs?: TabRoute[]; - contentClass?: string; + className?: IClassName; + contentClass?: IClassName; + tabs?: TabLayoutRoute[]; + children?: ReactNode; } -export const TabLayout = observer(({ className, contentClass, tabs, children }: TabLayoutProps) => { - const routePath = navigation.location.pathname; +export interface TabLayoutRoute { + routePath: string; + title: React.ReactNode; + 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 +} + +export const TabLayout = observer(({ className, contentClass, tabs = [], children }: TabLayoutProps) => { + const currentLocation = navigation.location.pathname; + const hasTabs = tabs.length > 0; + const startTabUrl = hasTabs ? (tabs.find(tab => tab.default) || tabs[0])?.url : null; return (
- {tabs && ( + {hasTabs && ( navigate(url)}> - {tabs.map(({ title, path, url, ...routeProps }) => { - const isActive = !!matchPath(routePath, { path, ...routeProps }); + {tabs.map(({ title, routePath, url = routePath, exact }) => { + const isActive = !!matchPath(currentLocation, { path: routePath, exact }); return ; })} )} -
- {children} +
+ + {hasTabs && ( + + {tabs.map(({ routePath, exact, component }) => { + return ; + })} + + + )} + {children} +
);