diff --git a/src/renderer/components/+404/not-found.tsx b/src/renderer/components/+404/not-found.tsx index e585abe326..a158e6f124 100644 --- a/src/renderer/components/+404/not-found.tsx +++ b/src/renderer/components/+404/not-found.tsx @@ -1,15 +1,15 @@ import React from "react"; import { Trans } from "@lingui/macro"; -import { MainLayout } from "../layout/main-layout"; +import { TabLayout } from "../layout/tab-layout"; export class NotFound extends React.Component { render() { return ( - +

Page not found

-
- ) + + ); } -} \ No newline at end of file +} diff --git a/src/renderer/components/+apps/apps.tsx b/src/renderer/components/+apps/apps.tsx index 8d54a0cee6..5ee297a1c0 100644 --- a/src/renderer/components/+apps/apps.tsx +++ b/src/renderer/components/+apps/apps.tsx @@ -2,7 +2,7 @@ import React from "react"; import { observer } from "mobx-react"; import { Redirect, Route, Switch } from "react-router"; import { Trans } from "@lingui/macro"; -import { MainLayout, TabRoute } from "../layout/main-layout"; +import { TabLayout, TabRoute } 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"; @@ -30,12 +30,12 @@ export class Apps extends React.Component { render() { const tabRoutes = Apps.tabRoutes; return ( - + {tabRoutes.map((route, index) => )} - + ) } } diff --git a/src/renderer/components/+cluster/cluster.tsx b/src/renderer/components/+cluster/cluster.tsx index a5d6e80579..9ef032335b 100644 --- a/src/renderer/components/+cluster/cluster.tsx +++ b/src/renderer/components/+cluster/cluster.tsx @@ -3,7 +3,7 @@ import "./cluster.scss" import React from "react"; import { computed, reaction } from "mobx"; import { disposeOnUnmount, observer } from "mobx-react"; -import { MainLayout } from "../layout/main-layout"; +import { TabLayout } from "../layout/tab-layout"; import { ClusterIssues } from "./cluster-issues"; import { Spinner } from "../spinner"; import { cssNames, interval, isElectron } from "../../utils"; @@ -54,7 +54,7 @@ export class Cluster extends React.Component { render() { const { isLoaded } = this; return ( - +
{!isLoaded && } {isLoaded && ( @@ -65,7 +65,7 @@ export class Cluster extends React.Component { )}
-
+ ) } } diff --git a/src/renderer/components/+config/config.tsx b/src/renderer/components/+config/config.tsx index 8d6d81d5fa..17c0c5c575 100644 --- a/src/renderer/components/+config/config.tsx +++ b/src/renderer/components/+config/config.tsx @@ -2,7 +2,7 @@ import React from "react"; import { observer } from "mobx-react"; import { Redirect, Route, Switch } from "react-router"; import { Trans } from "@lingui/macro"; -import { MainLayout, TabRoute } from "../layout/main-layout"; +import { TabLayout, TabRoute } from "../layout/tab-layout"; import { ConfigMaps, configMapsRoute, configMapsURL } from "../+config-maps"; import { Secrets, secretsRoute, secretsURL } from "../+config-secrets"; import { namespaceStore } from "../+namespaces/namespace.store"; @@ -68,12 +68,12 @@ export class Config extends React.Component { 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 155fdc3832..9dbd882bd1 100644 --- a/src/renderer/components/+custom-resources/custom-resources.tsx +++ b/src/renderer/components/+custom-resources/custom-resources.tsx @@ -2,7 +2,7 @@ import React from "react"; import { observer } from "mobx-react"; import { Redirect, Route, Switch } from "react-router"; import { Trans } from "@lingui/macro"; -import { MainLayout, TabRoute } from "../layout/main-layout"; +import { TabLayout, TabRoute } from "../layout/tab-layout"; import { crdResourcesRoute, crdRoute, crdURL, crdDefinitionsRoute } from "./crd.route"; import { CrdList } from "./crd-list"; import { CrdResources } from "./crd-resources"; @@ -25,13 +25,13 @@ export class CustomResources extends React.Component { render() { return ( - + - + ); } } \ No newline at end of file diff --git a/src/renderer/components/+events/events.tsx b/src/renderer/components/+events/events.tsx index a8f6d8f61b..868da0836a 100644 --- a/src/renderer/components/+events/events.tsx +++ b/src/renderer/components/+events/events.tsx @@ -2,7 +2,7 @@ import "./events.scss"; import React, { Fragment } from "react"; import { observer } from "mobx-react"; -import { MainLayout } from "../layout/main-layout"; +import { TabLayout } from "../layout/tab-layout"; import { eventStore } from "./event.store"; import { KubeObjectListLayout, KubeObjectListLayoutProps } from "../kube-object"; import { Trans } from "@lingui/macro"; @@ -118,9 +118,9 @@ export class Events extends React.Component { return events; } return ( - + {events} - + ) } } diff --git a/src/renderer/components/+namespaces/namespaces.tsx b/src/renderer/components/+namespaces/namespaces.tsx index c28a2d623b..43f2eb0b63 100644 --- a/src/renderer/components/+namespaces/namespaces.tsx +++ b/src/renderer/components/+namespaces/namespaces.tsx @@ -4,7 +4,7 @@ import React from "react"; import { Trans } from "@lingui/macro"; import { Namespace, namespacesApi, NamespaceStatus } from "../../api/endpoints"; import { AddNamespaceDialog } from "./add-namespace-dialog"; -import { MainLayout } from "../layout/main-layout"; +import { TabLayout } from "../layout/tab-layout"; import { Badge } from "../badge"; import { RouteComponentProps } from "react-router"; import { KubeObjectMenu, KubeObjectMenuProps } from "../kube-object/kube-object-menu"; @@ -26,7 +26,7 @@ interface Props extends RouteComponentProps { export class Namespaces extends React.Component { render() { return ( - + { })} /> - + ) } } diff --git a/src/renderer/components/+network/network.tsx b/src/renderer/components/+network/network.tsx index 96ea5bc27f..2932f5860c 100644 --- a/src/renderer/components/+network/network.tsx +++ b/src/renderer/components/+network/network.tsx @@ -5,7 +5,7 @@ import { observer } from "mobx-react"; import { Redirect, Route, Switch } from "react-router"; import { RouteComponentProps } from "react-router-dom"; import { Trans } from "@lingui/macro"; -import { MainLayout, TabRoute } from "../layout/main-layout"; +import { TabLayout, TabRoute } from "../layout/tab-layout"; import { Services, servicesRoute, servicesURL } from "../+network-services"; import { Endpoints, endpointRoute, endpointURL } from "../+network-endpoints"; import { Ingresses, ingressRoute, ingressURL } from "../+network-ingresses"; @@ -60,12 +60,12 @@ export class Network extends React.Component { render() { const tabRoutes = Network.tabRoutes; return ( - + {tabRoutes.map((route, index) => )} - + ) } } diff --git a/src/renderer/components/+nodes/nodes.tsx b/src/renderer/components/+nodes/nodes.tsx index e2d67a5870..4aecc26884 100644 --- a/src/renderer/components/+nodes/nodes.tsx +++ b/src/renderer/components/+nodes/nodes.tsx @@ -4,7 +4,7 @@ import { observer } from "mobx-react"; import { RouteComponentProps } from "react-router"; import { t, Trans } from "@lingui/macro"; import { cssNames, interval } from "../../utils"; -import { MainLayout } from "../layout/main-layout"; +import { TabLayout } from "../layout/tab-layout"; import { nodesStore } from "./nodes.store"; import { podsStore } from "../+workloads-pods/pods.store"; import { KubeObjectListLayout } from "../kube-object"; @@ -123,7 +123,7 @@ export class Nodes extends React.Component { render() { return ( - + { return }} /> - + ) } } diff --git a/src/renderer/components/+storage/storage.tsx b/src/renderer/components/+storage/storage.tsx index 9da302b908..c86afad2e0 100644 --- a/src/renderer/components/+storage/storage.tsx +++ b/src/renderer/components/+storage/storage.tsx @@ -5,7 +5,7 @@ import { observer } from "mobx-react"; import { Redirect, Route, Switch } from "react-router"; import { RouteComponentProps } from "react-router-dom"; import { Trans } from "@lingui/macro"; -import { MainLayout, TabRoute } from "../layout/main-layout"; +import { TabLayout, TabRoute } 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"; @@ -52,12 +52,12 @@ export class Storage extends React.Component { render() { const tabRoutes = Storage.tabRoutes; return ( - + {tabRoutes.map((route, index) => )} - + ) } } diff --git a/src/renderer/components/+user-management/user-management.tsx b/src/renderer/components/+user-management/user-management.tsx index 28a7964e76..97cf71140e 100644 --- a/src/renderer/components/+user-management/user-management.tsx +++ b/src/renderer/components/+user-management/user-management.tsx @@ -4,7 +4,7 @@ import { observer } from "mobx-react"; import { Redirect, Route, Switch } from "react-router"; import { RouteComponentProps } from "react-router-dom"; import { Trans } from "@lingui/macro"; -import { MainLayout, TabRoute } from "../layout/main-layout"; +import { TabLayout, TabRoute } from "../layout/tab-layout"; import { Roles } from "../+user-management-roles"; import { RoleBindings } from "../+user-management-roles-bindings"; import { ServiceAccounts } from "../+user-management-service-accounts"; @@ -55,12 +55,12 @@ export class UserManagement extends React.Component { render() { const tabRoutes = UserManagement.tabRoutes; return ( - + {tabRoutes.map((route, index) => )} - + ) } } diff --git a/src/renderer/components/+workloads/workloads.tsx b/src/renderer/components/+workloads/workloads.tsx index 94d1755eaa..fe6e1606b5 100644 --- a/src/renderer/components/+workloads/workloads.tsx +++ b/src/renderer/components/+workloads/workloads.tsx @@ -5,7 +5,7 @@ import { observer } from "mobx-react"; import { Redirect, Route, Switch } from "react-router"; import { RouteComponentProps } from "react-router-dom"; import { Trans } from "@lingui/macro"; -import { MainLayout, TabRoute } from "../layout/main-layout"; +import { TabLayout, TabRoute } 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 { namespaceStore } from "../+namespaces/namespace.store"; @@ -86,12 +86,12 @@ export class Workloads extends React.Component { 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 a60d794168..053d8fa3d6 100755 --- a/src/renderer/components/app.tsx +++ b/src/renderer/components/app.tsx @@ -35,6 +35,7 @@ import { getHostedCluster, getHostedClusterId } from "../../common/cluster-store import logger from "../../main/logger"; import { clusterIpc } from "../../common/cluster-ipc"; import { webFrame } from "electron"; +import { MainLayout } from "./layout/main-layout"; @observer export class App extends React.Component { @@ -59,21 +60,23 @@ export class App extends React.Component { - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/src/renderer/components/layout/main-layout.scss b/src/renderer/components/layout/main-layout.scss index 5b9361978b..3a02d36717 100755 --- a/src/renderer/components/layout/main-layout.scss +++ b/src/renderer/components/layout/main-layout.scss @@ -1,19 +1,18 @@ - .MainLayout { --sidebar-max-size: 200px; display: grid; - grid-template-areas: "aside header" "aside tabs" "aside main" "aside footer"; + grid-template-areas: + "aside header" + "aside tabs" + "aside main" + "aside footer"; grid-template-rows: [header] var(--main-layout-header) [tabs] min-content [main] 1fr [footer] auto; grid-template-columns: [sidebar] minmax(var(--main-layout-header), min-content) [main] 1fr; + height: 100%; - > .Tabs { - grid-area: tabs; - background: $layoutTabsBackground; - } - - header { + > header { grid-area: header; background: $layoutBackground; padding: $padding $padding * 2; @@ -28,7 +27,7 @@ } } - aside { + > aside { grid-area: aside; position: relative; background: $sidebarBackground; @@ -48,25 +47,14 @@ &.accessible:hover { width: var(--sidebar-max-size); transition-delay: 750ms; - box-shadow: 3px 3px 16px rgba(0, 0, 0, .35); + box-shadow: 3px 3px 16px rgba(0, 0, 0, 0.35); z-index: $zIndex-sidebar-hover; } } } - main { - @include custom-scrollbar; - $spacing: $margin * 2; - - .theme-light & { - @include custom-scrollbar(dark); - } - - grid-area: main; - overflow-y: scroll; // always reserve space for scrollbar (17px) - overflow-x: auto; - margin: $spacing; - margin-right: 0; + > main { + display: contents; } footer { @@ -74,4 +62,4 @@ grid-area: footer; min-width: 0; // restrict size when overflow content (e.g. tabs scrolling) } -} \ No newline at end of file +} diff --git a/src/renderer/components/layout/main-layout.tsx b/src/renderer/components/layout/main-layout.tsx index def55961fa..97f5727a2d 100755 --- a/src/renderer/components/layout/main-layout.tsx +++ b/src/renderer/components/layout/main-layout.tsx @@ -3,26 +3,16 @@ import "./main-layout.scss"; import React from "react"; import { observable, reaction } from "mobx"; import { disposeOnUnmount, observer } from "mobx-react"; -import { matchPath, RouteProps } from "react-router-dom"; import { createStorage, cssNames } from "../../utils"; -import { Tab, Tabs } from "../tabs"; import { Sidebar } from "./sidebar"; import { ErrorBoundary } from "../error-boundary"; import { Dock } from "../dock"; -import { navigate, navigation } from "../../navigation"; import { getHostedCluster } from "../../../common/cluster-store"; -export interface TabRoute extends RouteProps { - title: React.ReactNode; - url: string; -} - interface Props { className?: any; - tabs?: TabRoute[]; footer?: React.ReactNode; headerClass?: string; - contentClass?: string; footerClass?: string; } @@ -35,18 +25,17 @@ export class MainLayout extends React.Component { @disposeOnUnmount syncPinnedStateWithStorage = reaction( () => this.isPinned, - isPinned => this.storage.merge({ pinnedSidebar: isPinned }) + (isPinned) => this.storage.merge({ pinnedSidebar: isPinned }) ); toggleSidebar = () => { this.isPinned = !this.isPinned; this.isAccessible = false; - setTimeout(() => this.isAccessible = true, 250); - } + setTimeout(() => (this.isAccessible = true), 250); + }; render() { - const { className, contentClass, headerClass, tabs, footer, footerClass, children } = this.props; - const routePath = navigation.location.pathname; + const { className, headerClass, footer, footerClass, children } = this.props; const cluster = getHostedCluster(); if (!cluster) { return null; // fix: skip render when removing active (visible) cluster @@ -54,37 +43,18 @@ export class MainLayout extends React.Component { return (
- - {cluster.preferences.clusterName || cluster.contextName} - + {cluster.preferences.clusterName || cluster.contextName}
- {tabs && ( - navigate(url)}> - {tabs.map(({ title, path, url, ...routeProps }) => { - const isActive = !!matchPath(routePath, { path, ...routeProps }); - return - })} - - )} - -
- - {children} - +
+ {children}
-
- {footer === undefined ? : footer} -
+
{footer === undefined ? : footer}
); } diff --git a/src/renderer/components/layout/sidebar.tsx b/src/renderer/components/layout/sidebar.tsx index d46a1548cf..a3a3271e0d 100644 --- a/src/renderer/components/layout/sidebar.tsx +++ b/src/renderer/components/layout/sidebar.tsx @@ -1,4 +1,4 @@ -import type { TabRoute } from "./main-layout"; +import type { TabRoute } from "./tab-layout"; import "./sidebar.scss"; import React from "react"; @@ -27,7 +27,7 @@ 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 { isAllowedResource } from "../../../common/rbac" +import { isAllowedResource } from "../../../common/rbac"; const SidebarContext = React.createContext({ pinned: false }); type SidebarContextValue = { @@ -43,21 +43,21 @@ interface Props { @observer export class Sidebar extends React.Component { async componentDidMount() { - if (!crdStore.isLoaded && isAllowedResource('customresourcedefinitions')) { - crdStore.loadAll() + if (!crdStore.isLoaded && isAllowedResource("customresourcedefinitions")) { + crdStore.loadAll(); } } renderCustomResources() { return Object.entries(crdStore.groups).map(([group, crds]) => { - const submenus = crds.map(crd => { + const submenus = crds.map((crd) => { return { title: crd.getResourceKind(), component: CrdList, url: crd.getResourceUrl(), path: crdResourcesRoute.path, - } - }) + }; + }); return ( { subMenus={submenus} text={group} /> - ) - }) + ); + }); } render() { @@ -79,7 +79,7 @@ export class Sidebar extends React.Component {
- +
Lens
{
Cluster} - icon={} + icon={} /> Nodes} - icon={} + icon={} /> { routePath={workloadsRoute.path} subMenus={Workloads.tabRoutes} text={Workloads} - icon={} + icon={} /> { routePath={configRoute.path} subMenus={Config.tabRoutes} text={Configuration} - icon={} + icon={} /> { routePath={networkRoute.path} subMenus={Network.tabRoutes} text={Network} - icon={} + icon={} /> { url={storageURL({ query })} routePath={storageRoute.path} subMenus={Storage.tabRoutes} - icon={} + icon={} text={Storage} /> } + icon={} text={Namespaces} /> } + icon={} text={Events} /> { url={appsURL({ query })} subMenus={Apps.tabRoutes} routePath={appsRoute.path} - icon={} + icon={} text={Apps} /> { url={usersManagementURL({ query })} routePath={usersManagementRoute.path} subMenus={UserManagement.tabRoutes} - icon={} + icon={} text={Access Control} /> } + icon={} text={Custom Resources} > {this.renderCustomResources()} @@ -186,7 +186,7 @@ export class Sidebar extends React.Component {
- ) + ); } } @@ -203,7 +203,10 @@ interface SidebarNavItemProps { 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 { @@ -216,15 +219,15 @@ class SidebarNavItem extends React.Component { toggleSubMenu = () => { navItemState.set(this.props.id, !this.isExpanded); - } + }; isActive = () => { const { routePath, url } = this.props; const { pathname } = navigation.location; return !!matchPath(pathname, { - path: routePath || url + path: routePath || url, }); - } + }; render() { const { id, isHidden, subMenus = [], icon, text, url, children, className } = this.props; @@ -239,10 +242,7 @@ class SidebarNavItem extends React.Component {
{icon} {text} - +
    {subMenus.map(({ title, url }) => ( @@ -252,18 +252,18 @@ class SidebarNavItem extends React.Component { ))} {React.Children.toArray(children).map((child: React.ReactElement) => { return React.cloneElement(child, { - className: cssNames(child.props.className, { visible: this.isExpanded }) + className: cssNames(child.props.className, { visible: this.isExpanded }), }); })}
- ) + ); } return ( {icon} {text} - ) + ); } } diff --git a/src/renderer/components/layout/tab-layout.scss b/src/renderer/components/layout/tab-layout.scss new file mode 100755 index 0000000000..e8b62558d7 --- /dev/null +++ b/src/renderer/components/layout/tab-layout.scss @@ -0,0 +1,25 @@ + +.TabLayout { + display: contents; + + > .Tabs { + grid-area: tabs; + background: $layoutTabsBackground; + } + + + main { + @include custom-scrollbar; + $spacing: $margin * 2; + + .theme-light & { + @include custom-scrollbar(dark); + } + + grid-area: main; + overflow-y: scroll; // always reserve space for scrollbar (17px) + overflow-x: auto; + margin: $spacing; + margin-right: 0; + } +} \ No newline at end of file diff --git a/src/renderer/components/layout/tab-layout.tsx b/src/renderer/components/layout/tab-layout.tsx new file mode 100644 index 0000000000..82c0cac6cf --- /dev/null +++ b/src/renderer/components/layout/tab-layout.tsx @@ -0,0 +1,45 @@ +import "./tab-layout.scss"; + +import React, { ReactNode } from "react"; +import { matchPath, RouteProps } from "react-router-dom"; +import { observer } from "mobx-react"; +import { cssNames } from "../../utils"; +import { Tab, Tabs } from "../tabs"; +import { ErrorBoundary } from "../error-boundary"; +import { navigate, navigation } from "../../navigation"; +import { getHostedCluster } from "../../../common/cluster-store"; + +export interface TabRoute extends RouteProps { + title: React.ReactNode; + url: string; +} + +interface Props { + children: ReactNode; + className?: any; + tabs?: TabRoute[]; + contentClass?: string; +} + +export const TabLayout = observer(({ className, contentClass, tabs, children }: Props) => { + const routePath = navigation.location.pathname; + const cluster = getHostedCluster(); + if (!cluster) { + return null; // fix: skip render when removing active (visible) cluster + } + return ( +
+ {tabs && ( + navigate(url)}> + {tabs.map(({ title, path, url, ...routeProps }) => { + const isActive = !!matchPath(routePath, { path, ...routeProps }); + return ; + })} + + )} +
+ {children} +
+
+ ); +});