1
0
mirror of https://github.com/lensapp/lens.git synced 2024-09-20 13:57:23 +03:00

Fix rerendering of KubeObject views every 30s (#4801)

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>
Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
Sebastian Malton 2022-02-03 09:11:28 -05:00 committed by GitHub
parent 00314aabc0
commit 76f48df56b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
105 changed files with 1442 additions and 709 deletions

View File

@ -233,16 +233,16 @@ const commonPageTests: CommonPageTest[] = [{
},
},
{
drawerId: "apps",
drawerId: "helm",
pages: [
{
name: "Charts",
href: "/apps/charts",
href: "/helm/charts",
expectedSelector: "div.HelmCharts input",
},
{
name: "Releases",
href: "/apps/releases",
href: "/helm/releases",
expectedSelector: "h5.title",
expectedText: "Releases",
},

View File

@ -0,0 +1,21 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import { comparer, computed } from "mobx";
import hostedClusterInjectable from "./hosted-cluster.injectable";
const allowedResourcesInjectable = getInjectable({
instantiate: (di) => {
const cluster = di.inject(hostedClusterInjectable);
return computed(() => new Set(cluster.allowedResources), {
// This needs to be here so that during refresh changes are only propogated when necessary
equals: (cur, prev) => comparer.structural(cur, prev),
});
},
lifecycle: lifecycleEnum.singleton,
});
export default allowedResourcesInjectable;

View File

@ -3,8 +3,8 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import { getHostedClusterId } from "../../utils";
import clusterStoreInjectable from "../cluster-store.injectable";
import { getHostedClusterId } from "../utils";
import clusterStoreInjectable from "./cluster-store.injectable";
const hostedClusterInjectable = getInjectable({
instantiate: (di) => {

View File

@ -7,7 +7,7 @@ import yaml from "js-yaml";
import { formatDuration } from "../../utils";
import capitalize from "lodash/capitalize";
import { apiBase } from "../index";
import { helmChartStore } from "../../../renderer/components/+apps-helm-charts/helm-chart.store";
import { helmChartStore } from "../../../renderer/components/+helm-charts/helm-chart.store";
import type { ItemObject } from "../../item.store";
import { KubeObject } from "../kube-object";
import type { JsonApiData } from "../json-api";

View File

@ -16,12 +16,9 @@ import { KubeObjectConstructor, KubeObject, KubeStatus } from "./kube-object";
import byline from "byline";
import type { IKubeWatchEvent } from "./kube-watch-event";
import { KubeJsonApi, KubeJsonApiData } from "./kube-json-api";
import { noop } from "../utils";
import { noop, WrappedAbortController } from "../utils";
import type { RequestInit } from "node-fetch";
// BUG: https://github.com/mysticatea/abort-controller/pull/22
// eslint-disable-next-line import/no-named-as-default
import AbortController from "abort-controller";
import type AbortController from "abort-controller";
import { Agent, AgentOptions } from "https";
import type { Patch } from "rfc6902";
@ -582,14 +579,7 @@ export class KubeApi<T extends KubeObject> {
const { watchId = `${this.kind.toLowerCase()}-${this.watchId++}` } = opts;
// Create AbortController for this request
const abortController = new AbortController();
// If caller aborts, abort using request's abortController
if (opts.abortController) {
opts.abortController.signal.addEventListener("abort", () => {
abortController.abort();
});
}
const abortController = new WrappedAbortController(opts.abortController);
abortController.signal.addEventListener("abort", () => {
logger.info(`[KUBE-API] watch (${watchId}) aborted ${watchUrl}`);
@ -687,7 +677,6 @@ export class KubeApi<T extends KubeObject> {
protected modifyWatchEvent(event: IKubeWatchEvent<KubeJsonApiData>) {
if (event.type === "ERROR") {
return;
}
ensureObjectSelfLink(this, event.object);

View File

@ -6,7 +6,7 @@
import type { ClusterContext } from "./cluster-context";
import { action, computed, makeObservable, observable, reaction, when } from "mobx";
import { autoBind, noop, rejectPromiseBy } from "../utils";
import { autoBind, Disposer, noop, rejectPromiseBy } from "../utils";
import { KubeObject, KubeStatus } from "./kube-object";
import type { IKubeWatchEvent } from "./kube-watch-event";
import { ItemStore } from "../item.store";
@ -378,7 +378,7 @@ export abstract class KubeObjectStore<T extends KubeObject> extends ItemStore<T>
});
}
subscribe({ onLoadFailure, abortController = new AbortController() }: KubeObjectStoreSubscribeParams = {}) {
subscribe({ onLoadFailure, abortController = new AbortController() }: KubeObjectStoreSubscribeParams = {}): Disposer {
if (this.api.isNamespaced) {
Promise.race([rejectPromiseBy(abortController.signal), Promise.all([this.contextReady, this.namespacesReady])])
.then(() => {

View File

@ -5,10 +5,10 @@
import type { RouteProps } from "react-router";
import { buildURL } from "../utils/buildUrl";
import { appsRoute } from "./apps";
import { helmRoute } from "./helm";
export const helmChartsRoute: RouteProps = {
path: `${appsRoute.path}/charts/:repo?/:chartName?`,
path: `${helmRoute.path}/charts/:repo?/:chartName?`,
};
export interface HelmChartsRouteParams {

View File

@ -6,8 +6,8 @@
import type { RouteProps } from "react-router";
import { buildURL } from "../utils/buildUrl";
export const appsRoute: RouteProps = {
path: "/apps",
export const helmRoute: RouteProps = {
path: "/helm",
};
export const appsURL = buildURL(appsRoute.path);
export const helmURL = buildURL(helmRoute.path);

View File

@ -4,7 +4,6 @@
*/
export * from "./add-cluster";
export * from "./apps";
export * from "./catalog";
export * from "./cluster-view";
export * from "./cluster";
@ -16,6 +15,7 @@ export * from "./entity-settings";
export * from "./events";
export * from "./extensions";
export * from "./helm-charts";
export * from "./helm";
export * from "./hpa";
export * from "./ingresses";
export * from "./limit-ranges";

View File

@ -5,10 +5,10 @@
import type { RouteProps } from "react-router";
import { buildURL } from "../utils/buildUrl";
import { appsRoute } from "./apps";
import { helmRoute } from "./helm";
export const releaseRoute: RouteProps = {
path: `${appsRoute.path}/releases/:namespace?/:name?`,
path: `${helmRoute.path}/releases/:namespace?/:name?`,
};
export interface ReleaseRouteParams {

View File

@ -0,0 +1,15 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import AbortController from "abort-controller";
export class WrappedAbortController extends AbortController {
constructor(parent?: AbortController) {
super();
parent?.signal.addEventListener("abort", () => {
this.abort();
});
}
}

View File

@ -1,25 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { ClusterStore } from "../cluster-store/cluster-store";
import type { KubeResource } from "../rbac";
import { getHostedClusterId } from "./cluster-id-url-parsing";
export function isAllowedResource(resource: KubeResource | KubeResource[]) {
const resources = [resource].flat();
const cluster = ClusterStore.getInstance().getById(getHostedClusterId());
if (!cluster?.allowedResources) {
return false;
}
if (resources.length === 0) {
return true;
}
const allowedResources = new Set(cluster.allowedResources);
return resources.every(resource => allowedResources.has(resource));
}

View File

@ -10,6 +10,7 @@ export function noop<T extends any[]>(...args: T): void {
return void args;
}
export * from "./abort-controller";
export * from "./app-version";
export * from "./autobind";
export * from "./camelCase";

View File

@ -0,0 +1,22 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import allowedResourcesInjectable from "../cluster-store/allowed-resources.injectable";
import type { KubeResource } from "../rbac";
export type IsAllowedResource = (resource: KubeResource) => boolean;
// TODO: This injectable obscures MobX de-referencing. Make it more apparent in usage.
const isAllowedResourceInjectable = getInjectable({
instantiate: (di) => {
const allowedResources = di.inject(allowedResourcesInjectable);
return (resource: KubeResource) => allowedResources.get().has(resource);
},
lifecycle: lifecycleEnum.singleton,
});
export default isAllowedResourceInjectable;

View File

@ -3,7 +3,14 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export { isAllowedResource } from "../../common/utils/allowed-resource";
/**
* @deprecated This function never works
* @returns false
*/
export function isAllowedResource(...args: any[]) {
return Boolean(void args);
}
export { ResourceStack } from "../../common/k8s/resource-stack";
export { apiManager } from "../../common/k8s-api/api-manager";
export { KubeApi, forCluster, forRemoteCluster } from "../../common/k8s-api/kube-api";

View File

@ -2,8 +2,19 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeResource } from "../../common/rbac";
import isAllowedResourceInjectable from "../../common/utils/is-allowed-resource.injectable";
import { asLegacyGlobalFunctionForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-function-for-extension-api";
import { castArray } from "lodash/fp";
export function isAllowedResource(resource: KubeResource | KubeResource[]) {
const _isAllowedResource = asLegacyGlobalFunctionForExtensionApi(isAllowedResourceInjectable);
const resources = castArray(resource);
return resources.every(x => _isAllowedResource(x));
}
export { isAllowedResource } from "../../common/utils/allowed-resource";
export { ResourceStack } from "../../common/k8s/resource-stack";
export { apiManager } from "../../common/k8s-api/api-manager";
export { KubeObjectStore } from "../../common/k8s-api/kube-object.store";

View File

@ -5,7 +5,7 @@
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import { ClusterFrameContext } from "./cluster-frame-context";
import namespaceStoreInjectable from "../components/+namespaces/namespace-store/namespace-store.injectable";
import hostedClusterInjectable from "../../common/cluster-store/hosted-cluster/hosted-cluster.injectable";
import hostedClusterInjectable from "../../common/cluster-store/hosted-cluster.injectable";
const clusterFrameContextInjectable = getInjectable({
instantiate: (di) => {

View File

@ -1,38 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { observer } from "mobx-react";
import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
import { HelmCharts } from "../+apps-helm-charts";
import { HelmReleases } from "../+apps-releases";
import { helmChartsURL, helmChartsRoute, releaseURL, releaseRoute } from "../../../common/routes";
@observer
export class Apps extends React.Component {
static get tabRoutes(): TabLayoutRoute[] {
return [
{
title: "Charts",
component: HelmCharts,
url: helmChartsURL(),
routePath: helmChartsRoute.path.toString(),
},
{
title: "Releases",
component: HelmReleases,
url: releaseURL(),
routePath: releaseRoute.path.toString(),
},
];
}
render() {
return (
<TabLayout className="Apps" tabs={Apps.tabRoutes}/>
);
}
}

View File

@ -1,6 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export * from "./apps";

View File

@ -0,0 +1,37 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { withInjectables } from "@ogre-tools/injectable-react";
import { observer } from "mobx-react";
import React from "react";
import { clusterRoute, clusterURL } from "../../../common/routes";
import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable";
import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable";
import { isActiveRoute } from "../../navigation";
import { Icon } from "../icon";
import { SidebarItem } from "../layout/sidebar-item";
export interface ClusterSidebarItemProps {}
interface Dependencies {
isAllowedResource: IsAllowedResource;
}
const NonInjectedClusterSidebarItem = observer(({ isAllowedResource }: Dependencies & ClusterSidebarItemProps) => (
<SidebarItem
id="cluster"
text="Cluster"
isActive={isActiveRoute(clusterRoute)}
isHidden={!isAllowedResource("nodes")}
url={clusterURL()}
icon={<Icon svg="kube"/>}
/>
));
export const ClusterSidebarItem = withInjectables<Dependencies, ClusterSidebarItemProps>(NonInjectedClusterSidebarItem, {
getProps: (di, props) => ({
isAllowedResource: di.inject(isAllowedResourceInjectable),
...props,
}),
});

View File

@ -1,6 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export * from "./config";

View File

@ -2,22 +2,24 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { observer } from "mobx-react";
import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
import { ConfigMaps } from "../+config-maps";
import { Secrets } from "../+config-secrets";
import { ResourceQuotas } from "../+config-resource-quotas";
import { PodDisruptionBudgets } from "../+config-pod-disruption-budgets";
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import { computed } from "mobx";
import { HorizontalPodAutoscalers } from "../+config-autoscalers";
import { isAllowedResource } from "../../../common/utils/allowed-resource";
import { LimitRanges } from "../+config-limit-ranges";
import { ConfigMaps } from "../+config-maps";
import { PodDisruptionBudgets } from "../+config-pod-disruption-budgets";
import { ResourceQuotas } from "../+config-resource-quotas";
import { Secrets } from "../+config-secrets";
import isAllowedResourceInjectable, { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable";
import type { TabLayoutRoute } from "../layout/tab-layout";
import * as routes from "../../../common/routes";
@observer
export class Config extends React.Component {
static get tabRoutes(): TabLayoutRoute[] {
interface Dependencies {
isAllowedResource: IsAllowedResource;
}
function getRouteTabs({ isAllowedResource }: Dependencies) {
return computed(() => {
const tabs: TabLayoutRoute[] = [];
if (isAllowedResource("configmaps")) {
@ -75,11 +77,14 @@ export class Config extends React.Component {
}
return tabs;
}
render() {
return (
<TabLayout className="Config" tabs={Config.tabRoutes}/>
);
}
});
}
const configRoutesInjectable = getInjectable({
instantiate: (di) => getRouteTabs({
isAllowedResource: di.inject(isAllowedResourceInjectable),
}),
lifecycle: lifecycleEnum.singleton,
});
export default configRoutesInjectable;

View File

@ -0,0 +1,31 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { observer } from "mobx-react";
import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
import type { IComputedValue } from "mobx";
import { withInjectables } from "@ogre-tools/injectable-react";
import configRoutesInjectable from "./route-tabs.injectable";
export interface ConfigRouteProps {}
interface Dependencies {
routes: IComputedValue<TabLayoutRoute[]>;
}
const NonInjectedConfigRoute = observer(({ routes }: Dependencies & ConfigRouteProps) => (
<TabLayout
className="Config"
tabs={routes.get()}
/>
));
export const ConfigRoute = withInjectables<Dependencies, ConfigRouteProps>(NonInjectedConfigRoute, {
getProps: (di, props) => ({
routes: di.inject(configRoutesInjectable),
...props,
}),
});

View File

@ -0,0 +1,45 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { withInjectables } from "@ogre-tools/injectable-react";
import type { IComputedValue } from "mobx";
import { observer } from "mobx-react";
import React from "react";
import { configRoute, configURL } from "../../../common/routes";
import { isActiveRoute } from "../../navigation";
import { Icon } from "../icon";
import { SidebarItem } from "../layout/sidebar-item";
import type { TabLayoutRoute } from "../layout/tab-layout";
import { TabRoutesSidebarItems } from "../layout/tab-routes-sidebar-items";
import configRoutesInjectable from "./route-tabs.injectable";
export interface ConfigSidebarItemProps {}
interface Dependencies {
routes: IComputedValue<TabLayoutRoute[]>;
}
const NonInjectedConfigSidebarItem = observer(({ routes }: Dependencies & ConfigSidebarItemProps) => {
const tabRoutes = routes.get();
return (
<SidebarItem
id="config"
text="Configuration"
isActive={isActiveRoute(configRoute)}
isHidden={tabRoutes.length == 0}
url={configURL()}
icon={<Icon material="list"/>}
>
<TabRoutesSidebarItems routes={tabRoutes} />
</SidebarItem>
);
});
export const ConfigSidebarItem = withInjectables<Dependencies, ConfigSidebarItemProps>(NonInjectedConfigSidebarItem, {
getProps: (di, props) => ({
routes: di.inject(configRoutesInjectable),
...props,
}),
});

View File

@ -1,38 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { observer } from "mobx-react";
import { Redirect, Route, Switch } from "react-router";
import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
import { CrdList } from "./crd-list";
import { CrdResources } from "./crd-resources";
import { crdURL, crdDefinitionsRoute, crdResourcesRoute } from "../../../common/routes";
@observer
export class CustomResources extends React.Component {
static get tabRoutes(): TabLayoutRoute[] {
return [
{
title: "Definitions",
component: CustomResources,
url: crdURL(),
routePath: String(crdDefinitionsRoute.path),
},
];
}
render() {
return (
<TabLayout>
<Switch>
<Route component={CrdList} {...crdDefinitionsRoute} exact/>
<Route component={CrdResources} {...crdResourcesRoute}/>
<Redirect to={crdURL()}/>
</Switch>
</TabLayout>
);
}
}

View File

@ -0,0 +1,34 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import { computed, IComputedValue } from "mobx";
import type { CustomResourceDefinition } from "../../../common/k8s-api/endpoints";
import { getOrInsert } from "../../utils";
import customResourceDefinitionsInjectable from "./custom-resources.injectable";
interface Dependencies {
definitions: IComputedValue<CustomResourceDefinition[]>;
}
function getGroupedCustomResourceDefinitions({ definitions }: Dependencies) {
return computed(() => {
const groups = new Map<string, CustomResourceDefinition[]>();
for (const crd of definitions.get()) {
getOrInsert(groups, crd.getGroup(), []).push(crd);
}
return groups;
});
}
const groupedCustomResourceDefinitionsInjectable = getInjectable({
instantiate: (di) => getGroupedCustomResourceDefinitions({
definitions: di.inject(customResourceDefinitionsInjectable),
}),
lifecycle: lifecycleEnum.singleton,
});
export default groupedCustomResourceDefinitionsInjectable;

View File

@ -0,0 +1,65 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import { computed, IComputedValue } from "mobx";
import type { CustomResourceDefinition } from "../../../common/k8s-api/endpoints";
import { crdURL, crdDefinitionsRoute } from "../../../common/routes";
import type { TabLayoutRoute } from "../layout/tab-layout";
import { CrdList } from "./crd-list";
import { CrdResources } from "./crd-resources";
import groupedCustomResourceDefinitionsInjectable from "./grouped-custom-resources.injectable";
export interface CustomResourceTabLayoutRoute extends TabLayoutRoute {
id: string;
}
export interface CustomResourceGroupTabLayoutRoute extends CustomResourceTabLayoutRoute {
subRoutes?: CustomResourceTabLayoutRoute[];
}
interface Dependencies {
customResourcesDefinitions: IComputedValue<Map<string, CustomResourceDefinition[]>>;
}
function getRouteTabs({ customResourcesDefinitions }: Dependencies) {
return computed(() => {
const tabs: CustomResourceGroupTabLayoutRoute[] = [
{
id: "definitions",
title: "Definitions",
component: CrdList,
url: crdURL(),
routePath: String(crdDefinitionsRoute.path),
exact: true,
},
];
for (const [group, definitions] of customResourcesDefinitions.get()) {
tabs.push({
id: `crd-group:${group}`,
title: group,
routePath: crdURL({ query: { groups: group }}),
component: CrdResources,
subRoutes: definitions.map(crd => ({
id: `crd-resource:${crd.getResourceApiBase()}`,
title: crd.getResourceKind(),
routePath: crd.getResourceUrl(),
component: CrdResources,
})),
});
}
return tabs;
});
}
const customResourcesRouteTabsInjectable = getInjectable({
instantiate: (di) => getRouteTabs({
customResourcesDefinitions: di.inject(groupedCustomResourceDefinitionsInjectable),
}),
lifecycle: lifecycleEnum.singleton,
});
export default customResourcesRouteTabsInjectable;

View File

@ -0,0 +1,43 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { observer } from "mobx-react";
import { Redirect, Route, Switch } from "react-router";
import { TabLayout } from "../layout/tab-layout";
import { crdURL } from "../../../common/routes";
import type { IComputedValue } from "mobx";
import type { CustomResourceGroupTabLayoutRoute } from "./route-tabs.injectable";
import { withInjectables } from "@ogre-tools/injectable-react";
import customResourcesRouteTabsInjectable from "./route-tabs.injectable";
interface Dependencies {
routes: IComputedValue<CustomResourceGroupTabLayoutRoute[]>;
}
const NonInjectedCustomResourcesRoute = observer(({ routes }: Dependencies) => (
<TabLayout>
<Switch>
{
routes.get().map(({ id, component, routePath, exact }) => (
<Route
key={id}
component={component}
path={routePath}
exact={exact}
/>
))
}
<Redirect to={crdURL()}/>
</Switch>
</TabLayout>
));
export const CustomResourcesRoute = withInjectables<Dependencies>(NonInjectedCustomResourcesRoute, {
getProps: (di, props) => ({
routes: di.inject(customResourcesRouteTabsInjectable),
...props,
}),
});

View File

@ -0,0 +1,76 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React, { useEffect } from "react";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { IComputedValue } from "mobx";
import { observer } from "mobx-react";
import customResourcesRouteTabsInjectable, { type CustomResourceGroupTabLayoutRoute } from "./route-tabs.injectable";
import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable";
import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable";
import { crdURL, crdRoute } from "../../../common/routes";
import { isActiveRoute } from "../../navigation";
import { Icon } from "../icon";
import { SidebarItem } from "../layout/sidebar-item";
import subscribeStoresInjectable from "../../kube-watch-api/subscribe-stores.injectable";
import type { SubscribeStores } from "../../kube-watch-api/kube-watch-api";
import { crdStore } from "./crd.store";
import { Spinner } from "../spinner";
export interface CustomResourcesSidebarItemProps {}
interface Dependencies {
routes: IComputedValue<CustomResourceGroupTabLayoutRoute[]>;
isAllowedResource: IsAllowedResource;
subscribeStores: SubscribeStores;
}
const NonInjectedCustomResourcesSidebarItem = observer(({ routes, isAllowedResource, subscribeStores }: Dependencies & CustomResourcesSidebarItemProps) => {
useEffect(() => subscribeStores([
crdStore,
]), []);
return (
<SidebarItem
id="custom-resources"
text="Custom Resources"
url={crdURL()}
isActive={isActiveRoute(crdRoute)}
isHidden={!isAllowedResource("customresourcedefinitions")}
icon={<Icon material="extension"/>}
>
{routes.get().map((route) => (
<SidebarItem
key={route.id}
id={route.id}
text={route.title}
url={route.routePath}
>
{route.subRoutes?.map((subRoute) => (
<SidebarItem
key={subRoute.id}
id={subRoute.id}
url={subRoute.routePath}
text={subRoute.title}
/>
))}
</SidebarItem>
))}
{crdStore.isLoading && (
<div className="flex justify-center">
<Spinner />
</div>
)}
</SidebarItem>
);
});
export const CustomResourcesSidebarItem = withInjectables<Dependencies, CustomResourcesSidebarItemProps>(NonInjectedCustomResourcesSidebarItem, {
getProps: (di, props) => ({
routes: di.inject(customResourcesRouteTabsInjectable),
isAllowedResource: di.inject(isAllowedResourceInjectable),
subscribeStores: di.inject(subscribeStoresInjectable),
...props,
}),
});

View File

@ -3,5 +3,4 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export * from "./events";
export * from "./event-details";

View File

@ -0,0 +1,37 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { withInjectables } from "@ogre-tools/injectable-react";
import { observer } from "mobx-react";
import React from "react";
import { eventRoute, eventsURL } from "../../../common/routes";
import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable";
import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable";
import { isActiveRoute } from "../../navigation";
import { Icon } from "../icon";
import { SidebarItem } from "../layout/sidebar-item";
export interface EventsSidebarItemProps {}
interface Dependencies {
isAllowedResource: IsAllowedResource;
}
const NonInjectedEventsSidebarItem = observer(({ isAllowedResource }: Dependencies & EventsSidebarItemProps) => (
<SidebarItem
id="events"
text="Events"
isActive={isActiveRoute(eventRoute)}
isHidden={!isAllowedResource("events")}
url={eventsURL()}
icon={<Icon material="access_time"/>}
/>
));
export const EventsSidebarItem = withInjectables<Dependencies, EventsSidebarItemProps>(NonInjectedEventsSidebarItem, {
getProps: (di, props) => ({
isAllowedResource: di.inject(isAllowedResourceInjectable),
...props,
}),
});

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -0,0 +1,34 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import { computed } from "mobx";
import type { TabLayoutRoute } from "../layout/tab-layout";
import { HelmCharts } from "../+helm-charts";
import { HelmReleases } from "../+helm-releases";
import { helmChartsURL, helmChartsRoute, releaseURL, releaseRoute } from "../../../common/routes";
function getRouteTabs() {
return computed((): TabLayoutRoute[] => [
{
title: "Charts",
component: HelmCharts,
url: helmChartsURL(),
routePath: helmChartsRoute.path.toString(),
},
{
title: "Releases",
component: HelmReleases,
url: releaseURL(),
routePath: releaseRoute.path.toString(),
},
]);
}
const helmRoutesInjectable = getInjectable({
instantiate: () => getRouteTabs(),
lifecycle: lifecycleEnum.singleton,
});
export default helmRoutesInjectable;

View File

@ -0,0 +1,31 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { observer } from "mobx-react";
import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { IComputedValue } from "mobx";
import helmRoutesInjectable from "./route-tabs.injectable";
export interface HelmRouteProps {}
interface Dependencies {
routes: IComputedValue<TabLayoutRoute[]>;
}
const NonInjectedHelmRoute = observer(({ routes }: Dependencies & HelmRouteProps) => (
<TabLayout
className="Apps"
tabs={routes.get()}
/>
));
export const HelmRoute = withInjectables<Dependencies, HelmRouteProps>(NonInjectedHelmRoute, {
getProps: (di, props) => ({
routes: di.inject(helmRoutesInjectable),
...props,
}),
});

View File

@ -0,0 +1,44 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { IComputedValue } from "mobx";
import { observer } from "mobx-react";
import { isActiveRoute } from "../../navigation";
import { Icon } from "../icon";
import { SidebarItem } from "../layout/sidebar-item";
import type { TabLayoutRoute } from "../layout/tab-layout";
import { TabRoutesSidebarItems } from "../layout/tab-routes-sidebar-items";
import { helmRoute, helmURL } from "../../../common/routes";
import networkRouteTabsInjectable from "./route-tabs.injectable";
export interface HelmSidebarItemProps {}
interface Dependencies {
routes: IComputedValue<TabLayoutRoute[]>;
}
const NonInjectedHelmSidebarItem = observer(({ routes }: Dependencies & HelmSidebarItemProps) => {
const tabRoutes = routes.get();
return (
<SidebarItem
id="helm"
text="Helm"
isActive={isActiveRoute(helmRoute)}
url={helmURL()}
icon={<Icon material="apps"/>}
>
<TabRoutesSidebarItems routes={tabRoutes} />
</SidebarItem>
);
});
export const HelmSidebarItem = withInjectables<Dependencies, HelmSidebarItemProps>(NonInjectedHelmSidebarItem, {
getProps: (di, props) => ({
routes: di.inject(networkRouteTabsInjectable),
...props,
}),
});

View File

@ -3,6 +3,5 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export * from "./namespaces";
export * from "./namespace-details";
export * from "./add-namespace-dialog";

View File

@ -1,96 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import "./namespaces.scss";
import React from "react";
import { NamespaceStatus } from "../../../common/k8s-api/endpoints";
import { AddNamespaceDialog } from "./add-namespace-dialog";
import { TabLayout } from "../layout/tab-layout";
import { Badge } from "../badge";
import type { RouteComponentProps } from "react-router";
import { KubeObjectListLayout } from "../kube-object-list-layout";
import type { NamespaceStore } from "./namespace-store/namespace.store";
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
import type { NamespacesRouteParams } from "../../../common/routes";
import { withInjectables } from "@ogre-tools/injectable-react";
import namespaceStoreInjectable from "./namespace-store/namespace-store.injectable";
import addNamespaceDialogModelInjectable
from "./add-namespace-dialog-model/add-namespace-dialog-model.injectable";
enum columnId {
name = "name",
labels = "labels",
age = "age",
status = "status",
}
interface Props extends RouteComponentProps<NamespacesRouteParams> {
}
interface Dependencies {
namespaceStore: NamespaceStore
openAddNamespaceDialog: () => void
}
class NonInjectedNamespaces extends React.Component<Props & Dependencies> {
render() {
return (
<TabLayout>
<KubeObjectListLayout
isConfigurable
tableId="namespaces"
className="Namespaces"
store={this.props.namespaceStore}
sortingCallbacks={{
[columnId.name]: ns => ns.getName(),
[columnId.labels]: ns => ns.getLabels(),
[columnId.age]: ns => ns.getTimeDiffFromNow(),
[columnId.status]: ns => ns.getStatus(),
}}
searchFilters={[
item => item.getSearchFields(),
item => item.getStatus(),
]}
renderHeaderTitle="Namespaces"
renderTableHeader={[
{ title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning", showWithColumn: columnId.name },
{ title: "Labels", className: "labels scrollable", sortBy: columnId.labels, id: columnId.labels },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
{ title: "Status", className: "status", sortBy: columnId.status, id: columnId.status },
]}
renderTableContents={item => [
item.getName(),
<KubeObjectStatusIcon key="icon" object={item} />,
item.getLabels().map(label => <Badge scrollable key={label} label={label}/>),
item.getAge(),
{ title: item.getStatus(), className: item.getStatus().toLowerCase() },
]}
addRemoveButtons={{
addTooltip: "Add Namespace",
onAdd: () => this.props.openAddNamespaceDialog(),
}}
customizeTableRowProps={item => ({
disabled: item.getStatus() === NamespaceStatus.TERMINATING,
})}
/>
<AddNamespaceDialog/>
</TabLayout>
);
}
}
export const Namespaces = withInjectables<Dependencies, Props>(
NonInjectedNamespaces,
{
getProps: (di, props) => ({
namespaceStore: di.inject(namespaceStoreInjectable),
openAddNamespaceDialog: di.inject(addNamespaceDialogModelInjectable).open,
...props,
}),
},
);

View File

@ -0,0 +1,89 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import "./namespaces.scss";
import React from "react";
import { NamespaceStatus } from "../../../common/k8s-api/endpoints";
import { AddNamespaceDialog } from "./add-namespace-dialog";
import { TabLayout } from "../layout/tab-layout";
import { Badge } from "../badge";
import type { RouteComponentProps } from "react-router";
import { KubeObjectListLayout } from "../kube-object-list-layout";
import type { NamespaceStore } from "./namespace-store/namespace.store";
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
import type { NamespacesRouteParams } from "../../../common/routes";
import { withInjectables } from "@ogre-tools/injectable-react";
import namespaceStoreInjectable from "./namespace-store/namespace-store.injectable";
import addNamespaceDialogModelInjectable
from "./add-namespace-dialog-model/add-namespace-dialog-model.injectable";
enum columnId {
name = "name",
labels = "labels",
age = "age",
status = "status",
}
export interface NamespacesRouteProps extends RouteComponentProps<NamespacesRouteParams> {
}
interface Dependencies {
namespaceStore: NamespaceStore
openAddNamespaceDialog: () => void
}
export const NonInjectedNamespacesRoute = ({ namespaceStore, openAddNamespaceDialog }: Dependencies & NamespacesRouteProps) => (
<TabLayout>
<KubeObjectListLayout
isConfigurable
tableId="namespaces"
className="Namespaces"
store={namespaceStore}
sortingCallbacks={{
[columnId.name]: ns => ns.getName(),
[columnId.labels]: ns => ns.getLabels(),
[columnId.age]: ns => ns.getTimeDiffFromNow(),
[columnId.status]: ns => ns.getStatus(),
}}
searchFilters={[
item => item.getSearchFields(),
item => item.getStatus(),
]}
renderHeaderTitle="Namespaces"
renderTableHeader={[
{ title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning", showWithColumn: columnId.name },
{ title: "Labels", className: "labels scrollable", sortBy: columnId.labels, id: columnId.labels },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
{ title: "Status", className: "status", sortBy: columnId.status, id: columnId.status },
]}
renderTableContents={item => [
item.getName(),
<KubeObjectStatusIcon key="icon" object={item} />,
item.getLabels().map(label => <Badge scrollable key={label} label={label}/>),
item.getAge(),
{ title: item.getStatus(), className: item.getStatus().toLowerCase() },
]}
addRemoveButtons={{
addTooltip: "Add Namespace",
onAdd: openAddNamespaceDialog,
}}
customizeTableRowProps={item => ({
disabled: item.getStatus() === NamespaceStatus.TERMINATING,
})}
/>
<AddNamespaceDialog/>
</TabLayout>
);
export const NamespacesRoute = withInjectables<Dependencies, NamespacesRouteProps>(NonInjectedNamespacesRoute, {
getProps: (di, props) => ({
namespaceStore: di.inject(namespaceStoreInjectable),
openAddNamespaceDialog: di.inject(addNamespaceDialogModelInjectable).open,
...props,
}),
});

View File

@ -0,0 +1,37 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { withInjectables } from "@ogre-tools/injectable-react";
import { observer } from "mobx-react";
import React from "react";
import { namespacesRoute, namespacesURL } from "../../../common/routes";
import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable";
import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable";
import { isActiveRoute } from "../../navigation";
import { Icon } from "../icon";
import { SidebarItem } from "../layout/sidebar-item";
export interface NamespacesSidebarItemProps {}
interface Dependencies {
isAllowedResource: IsAllowedResource;
}
const NonInjectedNamespacesSidebarItem = observer(({ isAllowedResource }: Dependencies & NamespacesSidebarItemProps) => (
<SidebarItem
id="namespaces"
text="Namespaces"
isActive={isActiveRoute(namespacesRoute)}
isHidden={!isAllowedResource("namespaces")}
url={namespacesURL()}
icon={<Icon material="layers"/>}
/>
));
export const NamespacesSidebarItem = withInjectables<Dependencies, NamespacesSidebarItemProps>(NonInjectedNamespacesSidebarItem, {
getProps: (di, props) => ({
isAllowedResource: di.inject(isAllowedResourceInjectable),
...props,
}),
});

View File

@ -1,6 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export * from "./network";

View File

@ -2,23 +2,24 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import "./network.scss";
import React from "react";
import { observer } from "mobx-react";
import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import { computed } from "mobx";
import type { TabLayoutRoute } from "../layout/tab-layout";
import { Services } from "../+network-services";
import { Endpoints } from "../+network-endpoints";
import { Ingresses } from "../+network-ingresses";
import { NetworkPolicies } from "../+network-policies";
import { PortForwards } from "../+network-port-forwards";
import { isAllowedResource } from "../../../common/utils/allowed-resource";
import * as routes from "../../../common/routes";
import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable";
import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable";
@observer
export class Network extends React.Component {
static get tabRoutes(): TabLayoutRoute[] {
interface Dependencies {
isAllowedResource: IsAllowedResource;
}
function getRouteTabs({ isAllowedResource }: Dependencies) {
return computed(() => {
const tabs: TabLayoutRoute[] = [];
if (isAllowedResource("services")) {
@ -65,11 +66,14 @@ export class Network extends React.Component {
});
return tabs;
}
render() {
return (
<TabLayout className="Network" tabs={Network.tabRoutes}/>
);
}
});
}
const networkRouteTabsInjectable = getInjectable({
instantiate: (di) => getRouteTabs({
isAllowedResource: di.inject(isAllowedResourceInjectable),
}),
lifecycle: lifecycleEnum.singleton,
});
export default networkRouteTabsInjectable;

View File

@ -0,0 +1,33 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import "./network.scss";
import React from "react";
import { observer } from "mobx-react";
import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
import type { IComputedValue } from "mobx";
import { withInjectables } from "@ogre-tools/injectable-react";
import networkRouteTabsInjectable from "./route-tabs.injectable";
export interface NetworksRouteProps {}
interface Dependencies {
routes: IComputedValue<TabLayoutRoute[]>;
}
const NonInjectedNetworksRoute = observer(({ routes }: Dependencies & NetworksRouteProps) => (
<TabLayout
className="Network"
tabs={routes.get()}
/>
));
export const NetworkRoute = withInjectables<Dependencies, NetworksRouteProps>(NonInjectedNetworksRoute, {
getProps: (di, props) => ({
routes: di.inject(networkRouteTabsInjectable),
...props,
}),
});

View File

@ -0,0 +1,45 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { IComputedValue } from "mobx";
import { observer } from "mobx-react";
import { isActiveRoute } from "../../navigation";
import { Icon } from "../icon";
import { SidebarItem } from "../layout/sidebar-item";
import type { TabLayoutRoute } from "../layout/tab-layout";
import { TabRoutesSidebarItems } from "../layout/tab-routes-sidebar-items";
import { networkRoute, networkURL } from "../../../common/routes";
import networkRouteTabsInjectable from "./route-tabs.injectable";
export interface NetworkSidebarItemProps {}
interface Dependencies {
routes: IComputedValue<TabLayoutRoute[]>;
}
const NonInjectedNetworkSidebarItem = observer(({ routes }: Dependencies & NetworkSidebarItemProps) => {
const tabRoutes = routes.get();
return (
<SidebarItem
id="networks"
text="Network"
isActive={isActiveRoute(networkRoute)}
isHidden={tabRoutes.length == 0}
url={networkURL()}
icon={<Icon material="device_hub"/>}
>
<TabRoutesSidebarItems routes={tabRoutes} />
</SidebarItem>
);
});
export const NetworkSidebarItem = withInjectables<Dependencies, NetworkSidebarItemProps>(NonInjectedNetworkSidebarItem, {
getProps: (di, props) => ({
routes: di.inject(networkRouteTabsInjectable),
...props,
}),
});

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import "./node-details-resources.scss";
import "./details-resources.scss";
import { Table } from "../table/table";
import { TableHead } from "../table/table-head";

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import "./node-details.scss";
import "./details.scss";
import React from "react";
import upperFirst from "lodash/upperFirst";
@ -21,7 +21,7 @@ import { PodDetailsList } from "../+workloads-pods/pod-details-list";
import { KubeObjectMeta } from "../kube-object-meta";
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
import { NodeDetailsResources } from "./node-details-resources";
import { NodeDetailsResources } from "./details-resources";
import { DrawerTitle } from "../drawer/drawer-title";
import { boundMethod, Disposer } from "../../utils";
import logger from "../../../common/logger";

View File

@ -1,7 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export * from "./nodes";
export * from "./node-details";

View File

@ -50,7 +50,7 @@ interface UsageArgs {
}
@observer
export class Nodes extends React.Component<Props> {
export class NodesRoute extends React.Component<Props> {
@observable.ref metrics: Partial<INodeMetrics> = {};
private metricsWatcher = interval(30, async () => this.metrics = await getMetricsForAllNodes());

View File

@ -0,0 +1,37 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { withInjectables } from "@ogre-tools/injectable-react";
import { observer } from "mobx-react";
import React from "react";
import { nodesRoute, nodesURL } from "../../../common/routes";
import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable";
import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable";
import { isActiveRoute } from "../../navigation";
import { Icon } from "../icon";
import { SidebarItem } from "../layout/sidebar-item";
export interface NodeSidebarItemProps {}
interface Dependencies {
isAllowedResource: IsAllowedResource;
}
const NonInjectedNodeSidebarItem = observer(({ isAllowedResource }: Dependencies & NodeSidebarItemProps) => (
<SidebarItem
id="nodes"
text="Nodes"
isActive={isActiveRoute(nodesRoute)}
isHidden={!isAllowedResource("nodes")}
url={nodesURL()}
icon={<Icon svg="nodes"/>}
/>
));
export const NodesSidebarItem = withInjectables<Dependencies, NodeSidebarItemProps>(NonInjectedNodeSidebarItem, {
getProps: (di, props) => ({
isAllowedResource: di.inject(isAllowedResourceInjectable),
...props,
}),
});

View File

@ -1,6 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export * from "./storage";

View File

@ -2,21 +2,22 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import "./storage.scss";
import React from "react";
import { observer } from "mobx-react";
import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import { computed } from "mobx";
import type { TabLayoutRoute } from "../layout/tab-layout";
import { PersistentVolumes } from "../+storage-volumes";
import { StorageClasses } from "../+storage-classes";
import { PersistentVolumeClaims } from "../+storage-volume-claims";
import { isAllowedResource } from "../../../common/utils/allowed-resource";
import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable";
import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable";
import * as routes from "../../../common/routes";
@observer
export class Storage extends React.Component {
static get tabRoutes() {
interface Dependencies {
isAllowedResource: IsAllowedResource;
}
function getRouteTabs({ isAllowedResource }: Dependencies) {
return computed(() => {
const tabs: TabLayoutRoute[] = [];
if (isAllowedResource("persistentvolumeclaims")) {
@ -47,11 +48,14 @@ export class Storage extends React.Component {
}
return tabs;
}
render() {
return (
<TabLayout className="Storage" tabs={Storage.tabRoutes}/>
);
}
});
}
const storageRouteTabsInjectable = getInjectable({
instantiate: (di) => getRouteTabs({
isAllowedResource: di.inject(isAllowedResourceInjectable),
}),
lifecycle: lifecycleEnum.singleton,
});
export default storageRouteTabsInjectable;

View File

@ -0,0 +1,34 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import "./storage.scss";
import React from "react";
import { observer } from "mobx-react";
import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
import type { IComputedValue } from "mobx";
import { withInjectables } from "@ogre-tools/injectable-react";
import storageRouteTabsInjectable from "./route-tabs.injectable";
export interface StorageRouteProps {}
interface Dependencies {
routes: IComputedValue<TabLayoutRoute[]>;
}
const NonInjectedStorageRoute = observer(({ routes }: Dependencies & StorageRouteProps) => (
<TabLayout
className="Storage"
tabs={routes.get()}
/>
));
export const StorageRoute = withInjectables<Dependencies, StorageRouteProps>(NonInjectedStorageRoute, {
getProps: (di, props) => ({
routes: di.inject(storageRouteTabsInjectable),
...props,
}),
});

View File

@ -0,0 +1,45 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { withInjectables } from "@ogre-tools/injectable-react";
import type { IComputedValue } from "mobx";
import { observer } from "mobx-react";
import React from "react";
import { storageRoute, storageURL } from "../../../common/routes";
import { isActiveRoute } from "../../navigation";
import { Icon } from "../icon";
import { SidebarItem } from "../layout/sidebar-item";
import type { TabLayoutRoute } from "../layout/tab-layout";
import { TabRoutesSidebarItems } from "../layout/tab-routes-sidebar-items";
import storageRouteTabsInjectable from "./route-tabs.injectable";
export interface StorageSidebarItemProps {}
interface Dependencies {
routes: IComputedValue<TabLayoutRoute[]>;
}
const NonInjectedStorageSidebarItem = observer(({ routes }: Dependencies & StorageSidebarItemProps) => {
const tabRoutes = routes.get();
return (
<SidebarItem
id="storage"
text="Storage"
isActive={isActiveRoute(storageRoute)}
isHidden={tabRoutes.length == 0}
url={storageURL()}
icon={<Icon svg="storage"/>}
>
<TabRoutesSidebarItems routes={tabRoutes} />
</SidebarItem>
);
});
export const StorageSidebarItem = withInjectables<Dependencies, StorageSidebarItemProps>(NonInjectedStorageSidebarItem, {
getProps: (di, props) => ({
routes: di.inject(storageRouteTabsInjectable),
...props,
}),
});

View File

@ -1,6 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export * from "./user-management";

View File

@ -2,28 +2,29 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import "./user-management.scss";
import React from "react";
import { observer } from "mobx-react";
import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
import { PodSecurityPolicies } from "../+pod-security-policies";
import { isAllowedResource } from "../../../common/utils/allowed-resource";
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import { computed } from "mobx";
import type { TabLayoutRoute } from "../layout/tab-layout";
import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable";
import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable";
import * as routes from "../../../common/routes";
import { PodSecurityPolicies } from "../+pod-security-policies";
import { ClusterRoleBindings } from "./+cluster-role-bindings";
import { ServiceAccounts } from "./+service-accounts";
import { Roles } from "./+roles";
import { RoleBindings } from "./+role-bindings";
import { ClusterRoles } from "./+cluster-roles";
import { RoleBindings } from "./+role-bindings";
import { Roles } from "./+roles";
import { ServiceAccounts } from "./+service-accounts";
@observer
export class UserManagement extends React.Component {
static get tabRoutes() {
const tabRoutes: TabLayoutRoute[] = [];
interface Dependencies {
isAllowedResource: IsAllowedResource;
}
function getRouteTabs({ isAllowedResource }: Dependencies) {
return computed(() => {
const tabs: TabLayoutRoute[] = [];
if (isAllowedResource("serviceaccounts")) {
tabRoutes.push({
tabs.push({
title: "Service Accounts",
component: ServiceAccounts,
url: routes.serviceAccountsURL(),
@ -32,7 +33,7 @@ export class UserManagement extends React.Component {
}
if (isAllowedResource("clusterroles")) {
tabRoutes.push({
tabs.push({
title: "Cluster Roles",
component: ClusterRoles,
url: routes.clusterRolesURL(),
@ -41,7 +42,7 @@ export class UserManagement extends React.Component {
}
if (isAllowedResource("roles")) {
tabRoutes.push({
tabs.push({
title: "Roles",
component: Roles,
url: routes.rolesURL(),
@ -50,7 +51,7 @@ export class UserManagement extends React.Component {
}
if (isAllowedResource("clusterrolebindings")) {
tabRoutes.push({
tabs.push({
title: "Cluster Role Bindings",
component: ClusterRoleBindings,
url: routes.clusterRoleBindingsURL(),
@ -59,7 +60,7 @@ export class UserManagement extends React.Component {
}
if (isAllowedResource("rolebindings")) {
tabRoutes.push({
tabs.push({
title: "Role Bindings",
component: RoleBindings,
url: routes.roleBindingsURL(),
@ -68,7 +69,7 @@ export class UserManagement extends React.Component {
}
if (isAllowedResource("podsecuritypolicies")) {
tabRoutes.push({
tabs.push({
title: "Pod Security Policies",
component: PodSecurityPolicies,
url: routes.podSecurityPoliciesURL(),
@ -76,12 +77,15 @@ export class UserManagement extends React.Component {
});
}
return tabRoutes;
}
render() {
return (
<TabLayout className="UserManagement" tabs={UserManagement.tabRoutes}/>
);
}
return tabs;
});
}
const userManagementRouteTabsInjectable = getInjectable({
instantiate: (di) => getRouteTabs({
isAllowedResource: di.inject(isAllowedResourceInjectable),
}),
lifecycle: lifecycleEnum.singleton,
});
export default userManagementRouteTabsInjectable;

View File

@ -0,0 +1,31 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import "./user-management.scss";
import React from "react";
import { observer } from "mobx-react";
import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
import type { IComputedValue } from "mobx";
import { withInjectables } from "@ogre-tools/injectable-react";
import userManagementRouteTabsInjectable from "./route-tabs.injectable";
interface Dependencies {
routes: IComputedValue<TabLayoutRoute[]>;
}
const NonInjectedUserManagementRoute = observer(({ routes }: Dependencies) => (
<TabLayout
className="UserManagement"
tabs={routes.get()}
/>
));
export const UserManagementRoute = withInjectables<Dependencies>(NonInjectedUserManagementRoute, {
getProps: (di, props) => ({
routes: di.inject(userManagementRouteTabsInjectable),
...props,
}),
});

View File

@ -0,0 +1,45 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { withInjectables } from "@ogre-tools/injectable-react";
import type { IComputedValue } from "mobx";
import { observer } from "mobx-react";
import React from "react";
import { usersManagementRoute, usersManagementURL } from "../../../common/routes";
import { isActiveRoute } from "../../navigation";
import { Icon } from "../icon";
import { SidebarItem } from "../layout/sidebar-item";
import type { TabLayoutRoute } from "../layout/tab-layout";
import { TabRoutesSidebarItems } from "../layout/tab-routes-sidebar-items";
import userManagementRouteTabsInjectable from "./route-tabs.injectable";
export interface UserManagementSidebarItemProps {}
interface Dependencies {
routes: IComputedValue<TabLayoutRoute[]>;
}
const NonInjectedUserManagementSidebarItem = observer(({ routes }: Dependencies & UserManagementSidebarItemProps) => {
const tabRoutes = routes.get();
return (
<SidebarItem
id="users"
text="Access Control"
isActive={isActiveRoute(usersManagementRoute)}
isHidden={tabRoutes.length === 0}
url={usersManagementURL()}
icon={<Icon material="security"/>}
>
<TabRoutesSidebarItems routes={tabRoutes} />
</SidebarItem>
);
});
export const UserManagementSidebarItem = withInjectables<Dependencies, UserManagementSidebarItemProps>(NonInjectedUserManagementSidebarItem, {
getProps: (di, props) => ({
routes: di.inject(userManagementRouteTabsInjectable),
...props,
}),
});

View File

@ -9,73 +9,49 @@ import React from "react";
import { observer } from "mobx-react";
import { OverviewWorkloadStatus } from "./overview-workload-status";
import { Link } from "react-router-dom";
import { workloadStores } from "../+workloads";
import type { NamespaceStore } from "../+namespaces/namespace-store/namespace.store";
import type { KubeResource } from "../../../common/rbac";
import { ResourceNames } from "../../utils/rbac";
import { boundMethod } from "../../utils";
import { workloadURL } from "../../../common/routes";
import { isAllowedResource } from "../../../common/utils/allowed-resource";
import { withInjectables } from "@ogre-tools/injectable-react";
import namespaceStoreInjectable from "../+namespaces/namespace-store/namespace-store.injectable";
import type { IComputedValue } from "mobx";
import workloadsInjectable from "./workloads.injectable";
const resources: KubeResource[] = [
"pods",
"deployments",
"statefulsets",
"daemonsets",
"replicasets",
"jobs",
"cronjobs",
];
export interface OverviewStatusesProps {}
interface Workload {
resource: KubeResource;
amountOfItems: number;
href: string;
status: Record<string, number>;
title: string
}
interface Dependencies {
namespaceStore: NamespaceStore
workloads: IComputedValue<Workload[]>;
}
@observer
class NonInjectedOverviewStatuses extends React.Component<Dependencies> {
@boundMethod
renderWorkload(resource: KubeResource): React.ReactElement {
const store = workloadStores.get(resource);
const NonInjectedOverviewStatuses = observer(
({ workloads }: Dependencies & OverviewStatusesProps) => (
<div className="OverviewStatuses">
<div className="workloads">
{workloads.get()
.map(({ resource, title, href, status, amountOfItems }) => (
<div className="workload" key={resource}>
<div className="title">
<Link to={href}>
{title} ({amountOfItems})
</Link>
</div>
if (!store) {
return null;
}
const items = store.getAllByNs(this.props.namespaceStore.contextNamespaces);
return (
<div className="workload" key={resource}>
<div className="title">
<Link to={workloadURL[resource]()}>{ResourceNames[resource]} ({items.length})</Link>
</div>
<OverviewWorkloadStatus status={store.getStatuses(items)} />
<OverviewWorkloadStatus status={status} />
</div>
))}
</div>
);
}
render() {
const workloads = resources
.filter(isAllowedResource)
.map(this.renderWorkload);
return (
<div className="OverviewStatuses">
<div className="workloads">
{workloads}
</div>
</div>
);
}
}
export const OverviewStatuses = withInjectables<Dependencies>(
NonInjectedOverviewStatuses,
{
getProps: (di) => ({
namespaceStore: di.inject(namespaceStoreInjectable),
}),
},
</div>
),
);
export const OverviewStatuses = withInjectables<Dependencies, OverviewStatusesProps>(NonInjectedOverviewStatuses, {
getProps: (di, props) => ({
workloads: di.inject(workloadsInjectable),
...props,
}),
});

View File

@ -0,0 +1,40 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import type { KubeResource } from "../../../common/rbac";
import type { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
import type { KubeObject } from "../../../common/k8s-api/kube-object";
import { podsStore } from "../+workloads-pods/pods.store";
import { deploymentStore } from "../+workloads-deployments/deployments.store";
import { daemonSetStore } from "../+workloads-daemonsets/daemonsets.store";
import { statefulSetStore } from "../+workloads-statefulsets/statefulset.store";
import { replicaSetStore } from "../+workloads-replicasets/replicasets.store";
import { jobStore } from "../+workloads-jobs/job.store";
import { cronJobStore } from "../+workloads-cronjobs/cronjob.store";
import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable";
import namespaceStoreInjectable from "../+namespaces/namespace-store/namespace-store.injectable";
import { workloads } from "./workloads";
const workloadsInjectable = getInjectable({
instantiate: (di) =>
workloads({
isAllowedResource: di.inject(isAllowedResourceInjectable),
namespaceStore: di.inject(namespaceStoreInjectable),
workloadStores: new Map<KubeResource, KubeObjectStore<KubeObject>>([
["pods", podsStore],
["deployments", deploymentStore],
["daemonsets", daemonSetStore],
["statefulsets", statefulSetStore],
["replicasets", replicaSetStore],
["jobs", jobStore],
["cronjobs", cronJobStore],
]),
}),
lifecycle: lifecycleEnum.singleton,
});
export default workloadsInjectable;

View File

@ -0,0 +1,39 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { computed } from "mobx";
import type { KubeResource } from "../../../common/rbac";
import type { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
import type { KubeObject } from "../../../common/k8s-api/kube-object";
import { workloadURL } from "../../../common/routes";
import { ResourceNames } from "../../utils/rbac";
import type { NamespaceStore } from "../+namespaces/namespace-store/namespace.store";
import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable";
interface Dependencies {
workloadStores: Map<KubeResource, KubeObjectStore<KubeObject>>;
isAllowedResource: IsAllowedResource;
namespaceStore: NamespaceStore;
}
export const workloads = ({
workloadStores,
isAllowedResource,
namespaceStore,
}: Dependencies) =>
computed(() =>
[...workloadStores.entries()]
.filter(([resource]) => isAllowedResource(resource))
.map(([resource, store]) => {
const items = store.getAllByNs(namespaceStore.contextNamespaces);
return {
resource,
href: workloadURL[resource](),
amountOfItems: items.length,
status: store.getStatuses(items),
title: ResourceNames[resource],
};
}),
);

View File

@ -1,7 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export * from "./workloads";
export * from "./workloads.stores";

View File

@ -2,11 +2,9 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import "./workloads.scss";
import React from "react";
import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import { computed } from "mobx";
import type { TabLayoutRoute } from "../layout/tab-layout";
import { WorkloadsOverview } from "../+workloads-overview/overview";
import { Pods } from "../+workloads-pods";
import { Deployments } from "../+workloads-deployments";
@ -14,12 +12,17 @@ import { DaemonSets } from "../+workloads-daemonsets";
import { StatefulSets } from "../+workloads-statefulsets";
import { Jobs } from "../+workloads-jobs";
import { CronJobs } from "../+workloads-cronjobs";
import { isAllowedResource } from "../../../common/utils/allowed-resource";
import { ReplicaSets } from "../+workloads-replicasets";
import * as routes from "../../../common/routes";
import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable";
import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable";
export class Workloads extends React.Component {
static get tabRoutes(): TabLayoutRoute[] {
interface Dependencies {
isAllowedResource: IsAllowedResource;
}
function getRouteTabs({ isAllowedResource }: Dependencies) {
return computed(() => {
const tabs: TabLayoutRoute[] = [
{
title: "Overview",
@ -93,11 +96,14 @@ export class Workloads extends React.Component {
}
return tabs;
}
render() {
return (
<TabLayout className="Workloads" tabs={Workloads.tabRoutes}/>
);
}
});
}
const workloadsRouteTabsInjectable = getInjectable({
instantiate: (di) => getRouteTabs({
isAllowedResource: di.inject(isAllowedResourceInjectable),
}),
lifecycle: lifecycleEnum.singleton,
});
export default workloadsRouteTabsInjectable;

View File

@ -0,0 +1,34 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import "./workloads.scss";
import React from "react";
import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
import type { IComputedValue } from "mobx";
import { withInjectables } from "@ogre-tools/injectable-react";
import { observer } from "mobx-react";
import workloadsRouteTabsInjectable from "./route-tabs.injectable";
export interface WorkloadsRouteProps {}
interface Dependencies {
routes: IComputedValue<TabLayoutRoute[]>;
}
const NonInjectedWorkloadsRoute = observer(({ routes }: Dependencies & WorkloadsRouteProps) => (
<TabLayout
className="Workloads"
tabs={routes.get()}
/>
));
export const WorkloadsRoute = withInjectables<Dependencies, WorkloadsRouteProps>(NonInjectedWorkloadsRoute, {
getProps: (di, props) => ({
routes: di.inject(workloadsRouteTabsInjectable),
...props,
}),
});

View File

@ -0,0 +1,45 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { IComputedValue } from "mobx";
import { observer } from "mobx-react";
import { workloadsRoute, workloadsURL } from "../../../common/routes";
import { isActiveRoute } from "../../navigation";
import { Icon } from "../icon";
import { SidebarItem } from "../layout/sidebar-item";
import type { TabLayoutRoute } from "../layout/tab-layout";
import { TabRoutesSidebarItems } from "../layout/tab-routes-sidebar-items";
import workloadsRouteTabsInjectable from "./route-tabs.injectable";
export interface WorkloadSidebarItemProps {}
interface Dependencies {
routes: IComputedValue<TabLayoutRoute[]>;
}
const NonInjectedWorkloadsSidebarItem = observer(({ routes }: Dependencies & WorkloadSidebarItemProps) => {
const tabRoutes = routes.get();
return (
<SidebarItem
id="workloads"
text="Workloads"
isActive={isActiveRoute(workloadsRoute)}
isHidden={tabRoutes.length == 0}
url={workloadsURL()}
icon={<Icon svg="workloads"/>}
>
<TabRoutesSidebarItems routes={tabRoutes} />
</SidebarItem>
);
});
export const WorkloadsSidebarItem = withInjectables<Dependencies, WorkloadSidebarItemProps>(NonInjectedWorkloadsSidebarItem, {
getProps: (di, props) => ({
routes: di.inject(workloadsRouteTabsInjectable),
...props,
}),
});

View File

@ -1,25 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
import { podsStore } from "../+workloads-pods/pods.store";
import { deploymentStore } from "../+workloads-deployments/deployments.store";
import { daemonSetStore } from "../+workloads-daemonsets/daemonsets.store";
import { statefulSetStore } from "../+workloads-statefulsets/statefulset.store";
import { jobStore } from "../+workloads-jobs/job.store";
import { cronJobStore } from "../+workloads-cronjobs/cronjob.store";
import type { KubeResource } from "../../../common/rbac";
import { replicaSetStore } from "../+workloads-replicasets/replicasets.store";
import type { KubeObject } from "../../../common/k8s-api/kube-object";
export const workloadStores = new Map<KubeResource, KubeObjectStore<KubeObject>>([
["pods", podsStore],
["deployments", deploymentStore],
["daemonsets", daemonSetStore],
["statefulsets", statefulSetStore],
["replicasets", replicaSetStore],
["jobs", jobStore],
["cronjobs", cronJobStore],
]);

View File

@ -27,7 +27,7 @@ import type { IReleaseCreatePayload, IReleaseUpdateDetails } from "../../../../c
import { withInjectables } from "@ogre-tools/injectable-react";
import installChartTabStoreInjectable from "./store.injectable";
import dockStoreInjectable from "../dock/store.injectable";
import createReleaseInjectable from "../../+apps-releases/create-release/create-release.injectable";
import createReleaseInjectable from "../../+helm-releases/create-release/create-release.injectable";
import { Notifications } from "../../notifications";
interface Props {

View File

@ -15,13 +15,13 @@ import type { UpgradeChartTabStore } from "./store";
import { Spinner } from "../../spinner";
import { Badge } from "../../badge";
import { EditorPanel } from "../editor-panel";
import { helmChartStore, IChartVersion } from "../../+apps-helm-charts/helm-chart.store";
import { helmChartStore, IChartVersion } from "../../+helm-charts/helm-chart.store";
import type { HelmRelease, IReleaseUpdateDetails, IReleaseUpdatePayload } from "../../../../common/k8s-api/endpoints/helm-releases.api";
import { Select, SelectOption } from "../../select";
import { IAsyncComputed, withInjectables } from "@ogre-tools/injectable-react";
import upgradeChartTabStoreInjectable from "./store.injectable";
import updateReleaseInjectable from "../../+apps-releases/update-release/update-release.injectable";
import releasesInjectable from "../../+apps-releases/releases.injectable";
import updateReleaseInjectable from "../../+helm-releases/update-release/update-release.injectable";
import releasesInjectable from "../../+helm-releases/releases.injectable";
interface Props {
className?: string;

View File

@ -7,99 +7,34 @@ import styles from "./sidebar.module.scss";
import type { TabLayoutRoute } from "./tab-layout";
import React from "react";
import { disposeOnUnmount, observer } from "mobx-react";
import { cssNames, Disposer } from "../../utils";
import { Icon } from "../icon";
import { Workloads } from "../+workloads";
import { UserManagement } from "../+user-management";
import { Storage } from "../+storage";
import { Network } from "../+network";
import { crdStore } from "../+custom-resources/crd.store";
import { CustomResources } from "../+custom-resources/custom-resources";
import { observer } from "mobx-react";
import { cssNames } from "../../utils";
import { isActiveRoute } from "../../navigation";
import { isAllowedResource } from "../../../common/utils/allowed-resource";
import { Spinner } from "../spinner";
import { ClusterPageMenuRegistration, ClusterPageMenuRegistry, ClusterPageRegistry, getExtensionPageUrl } from "../../../extensions/registries";
import { SidebarItem } from "./sidebar-item";
import { Apps } from "../+apps";
import * as routes from "../../../common/routes";
import { Config } from "../+config";
import { catalogEntityRegistry } from "../../api/catalog-entity-registry";
import { SidebarCluster } from "./sidebar-cluster";
import type { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
import type { KubeObject } from "../../../common/k8s-api/kube-object";
import { withInjectables } from "@ogre-tools/injectable-react";
import kubeWatchApiInjectable
from "../../kube-watch-api/kube-watch-api.injectable";
import { TabRoutesSidebarItems } from "./tab-routes-sidebar-items";
import { ConfigSidebarItem } from "../+config/sidebar-item";
import { ClusterSidebarItem } from "../+cluster/sidebar-item";
import { NodesSidebarItem } from "../+nodes/sidebar-item";
import { WorkloadsSidebarItem } from "../+workloads/sidebar-item";
import { NetworkSidebarItem } from "../+network/sidebar-item";
import { StorageSidebarItem } from "../+storage/sidebar-item";
import { NamespacesSidebarItem } from "../+namespaces/sidebar-item";
import { EventsSidebarItem } from "../+events/sidebar-item";
import { HelmSidebarItem } from "../+helm/sidebar-item";
import { UserManagementSidebarItem } from "../+user-management/sidebar-item";
import { CustomResourcesSidebarItem } from "../+custom-resources/sidebar-item";
interface Props {
interface SidebarProps {
className?: string;
}
interface Dependencies {
subscribeStores: (stores: KubeObjectStore<KubeObject>[]) => Disposer
}
@observer
class NonInjectedSidebar extends React.Component<Props & Dependencies> {
export class Sidebar extends React.Component<SidebarProps> {
static displayName = "Sidebar";
componentDidMount() {
disposeOnUnmount(this, [
this.props.subscribeStores([
crdStore,
]),
]);
}
renderCustomResources() {
if (crdStore.isLoading) {
return (
<div className="flex justify-center">
<Spinner/>
</div>
);
}
return Object.entries(crdStore.groups).map(([group, crds]) => {
const id = `crd-group:${group}`;
const crdGroupsPageUrl = routes.crdURL({ query: { groups: group }});
return (
<SidebarItem key={id} id={id} text={group} url={crdGroupsPageUrl}>
{crds.map((crd) => (
<SidebarItem
key={crd.getResourceApiBase()}
id={`crd-resource:${crd.getResourceApiBase()}`}
url={crd.getResourceUrl()}
text={crd.getResourceKind()}
/>
))}
</SidebarItem>
);
});
}
renderTreeFromTabRoutes(tabRoutes: TabLayoutRoute[] = []): React.ReactNode {
if (!tabRoutes.length) {
return null;
}
return tabRoutes.map(({ title, routePath, url = routePath, exact = true }) => {
const subMenuItemId = `tab-route-item-${url}`;
return (
<SidebarItem
key={subMenuItemId}
id={subMenuItemId}
url={url}
text={title}
isActive={isActiveRoute({ path: routePath, exact })}
/>
);
});
}
getTabLayoutRoutes(menu: ClusterPageMenuRegistration): TabLayoutRoute[] {
if (!menu.id) {
return [];
@ -169,7 +104,7 @@ class NonInjectedSidebar extends React.Component<Props & Dependencies> {
text={menuItem.title}
icon={<menuItem.components.Icon/>}
>
{this.renderTreeFromTabRoutes(tabRoutes)}
<TabRoutesSidebarItems routes={tabRoutes} />
</SidebarItem>
);
});
@ -186,122 +121,20 @@ class NonInjectedSidebar extends React.Component<Props & Dependencies> {
<div className={cssNames("flex flex-col", className)} data-testid="cluster-sidebar">
<SidebarCluster clusterEntity={this.clusterEntity}/>
<div className={styles.sidebarNav}>
<SidebarItem
id="cluster"
text="Cluster"
isActive={isActiveRoute(routes.clusterRoute)}
isHidden={!isAllowedResource("nodes")}
url={routes.clusterURL()}
icon={<Icon svg="kube"/>}
/>
<SidebarItem
id="nodes"
text="Nodes"
isActive={isActiveRoute(routes.nodesRoute)}
isHidden={!isAllowedResource("nodes")}
url={routes.nodesURL()}
icon={<Icon svg="nodes"/>}
/>
<SidebarItem
id="workloads"
text="Workloads"
isActive={isActiveRoute(routes.workloadsRoute)}
isHidden={Workloads.tabRoutes.length == 0}
url={routes.workloadsURL()}
icon={<Icon svg="workloads"/>}
>
{this.renderTreeFromTabRoutes(Workloads.tabRoutes)}
</SidebarItem>
<SidebarItem
id="config"
text="Configuration"
isActive={isActiveRoute(routes.configRoute)}
isHidden={Config.tabRoutes.length == 0}
url={routes.configURL()}
icon={<Icon material="list"/>}
>
{this.renderTreeFromTabRoutes(Config.tabRoutes)}
</SidebarItem>
<SidebarItem
id="networks"
text="Network"
isActive={isActiveRoute(routes.networkRoute)}
isHidden={Network.tabRoutes.length == 0}
url={routes.networkURL()}
icon={<Icon material="device_hub"/>}
>
{this.renderTreeFromTabRoutes(Network.tabRoutes)}
</SidebarItem>
<SidebarItem
id="storage"
text="Storage"
isActive={isActiveRoute(routes.storageRoute)}
isHidden={Storage.tabRoutes.length == 0}
url={routes.storageURL()}
icon={<Icon svg="storage"/>}
>
{this.renderTreeFromTabRoutes(Storage.tabRoutes)}
</SidebarItem>
<SidebarItem
id="namespaces"
text="Namespaces"
isActive={isActiveRoute(routes.namespacesRoute)}
isHidden={!isAllowedResource("namespaces")}
url={routes.namespacesURL()}
icon={<Icon material="layers"/>}
/>
<SidebarItem
id="events"
text="Events"
isActive={isActiveRoute(routes.eventRoute)}
isHidden={!isAllowedResource("events")}
url={routes.eventsURL()}
icon={<Icon material="access_time"/>}
/>
<SidebarItem
id="apps"
text="Apps" // helm charts
isActive={isActiveRoute(routes.appsRoute)}
url={routes.appsURL()}
icon={<Icon material="apps"/>}
>
{this.renderTreeFromTabRoutes(Apps.tabRoutes)}
</SidebarItem>
<SidebarItem
id="users"
text="Access Control"
isActive={isActiveRoute(routes.usersManagementRoute)}
isHidden={UserManagement.tabRoutes.length === 0}
url={routes.usersManagementURL()}
icon={<Icon material="security"/>}
>
{this.renderTreeFromTabRoutes(UserManagement.tabRoutes)}
</SidebarItem>
<SidebarItem
id="custom-resources"
text="Custom Resources"
url={routes.crdURL()}
isActive={isActiveRoute(routes.crdRoute)}
isHidden={!isAllowedResource("customresourcedefinitions")}
icon={<Icon material="extension"/>}
>
{this.renderTreeFromTabRoutes(CustomResources.tabRoutes)}
{this.renderCustomResources()}
</SidebarItem>
<ClusterSidebarItem />
<NodesSidebarItem />
<WorkloadsSidebarItem />
<ConfigSidebarItem />
<NetworkSidebarItem />
<StorageSidebarItem />
<NamespacesSidebarItem />
<EventsSidebarItem />
<HelmSidebarItem />
<UserManagementSidebarItem />
<CustomResourcesSidebarItem />
{this.renderRegisteredMenus()}
</div>
</div>
);
}
}
export const Sidebar = withInjectables<Dependencies, Props>(
NonInjectedSidebar,
{
getProps: (di, props) => ({
subscribeStores: di.inject(kubeWatchApiInjectable).subscribeStores,
...props,
}),
},
);

View File

@ -0,0 +1,37 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { isActiveRoute } from "../../navigation";
import { SidebarItem } from "./sidebar-item";
import type { TabLayoutRoute } from "./tab-layout";
export interface SidebarTreeProps {
routes: TabLayoutRoute[];
}
function withId(src: TabLayoutRoute) {
return {
...src,
id: `tab-route-item-${src.url ?? src.routePath}`,
};
}
export const TabRoutesSidebarItems = ({ routes }: SidebarTreeProps) => (
<>
{
routes
.map(withId)
.map(({ title, routePath, url = routePath, exact = true, id }) => (
<SidebarItem
key={id}
id={id}
url={url}
text={title}
isActive={isActiveRoute({ path: routePath, exact })} />
))
}
</>
);

View File

@ -3,17 +3,16 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { observable, makeObservable } from "mobx";
import { makeObservable, computed } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react";
import { Redirect, Route, Router, Switch } from "react-router";
import { UserManagement } from "../../components/+user-management/user-management";
import { UserManagementRoute } from "../../components/+user-management/route";
import { ConfirmDialog } from "../../components/confirm-dialog";
import { ClusterOverview } from "../../components/+cluster/cluster-overview";
import { Events } from "../../components/+events/events";
import { DeploymentScaleDialog } from "../../components/+workloads-deployments/deployment-scale-dialog";
import { CronJobTriggerDialog } from "../../components/+workloads-cronjobs/cronjob-trigger-dialog";
import { CustomResources } from "../../components/+custom-resources/custom-resources";
import { isAllowedResource } from "../../../common/utils/allowed-resource";
import { CustomResourcesRoute } from "../../components/+custom-resources/route";
import { ClusterPageRegistry, getExtensionPageUrl } from "../../../extensions/registries/page-registry";
import { ClusterPageMenuRegistration, ClusterPageMenuRegistry } from "../../../extensions/registries";
import { StatefulSetScaleDialog } from "../../components/+workloads-statefulsets/statefulset-scale-dialog";
@ -28,13 +27,12 @@ import { KubeObjectDetails } from "../../components/kube-object-details";
import { KubeConfigDialog } from "../../components/kubeconfig-dialog";
import { Sidebar } from "../../components/layout/sidebar";
import { Dock } from "../../components/dock";
import { Apps } from "../../components/+apps";
import { Namespaces } from "../../components/+namespaces";
import { Network } from "../../components/+network";
import { Nodes } from "../../components/+nodes";
import { Workloads } from "../../components/+workloads";
import { Config } from "../../components/+config";
import { Storage } from "../../components/+storage";
import { NamespacesRoute } from "../../components/+namespaces/route";
import { NetworkRoute } from "../../components/+network/route";
import { NodesRoute } from "../../components/+nodes/route";
import { WorkloadsRoute } from "../../components/+workloads/route";
import { ConfigRoute } from "../../components/+config/route";
import { StorageRoute } from "../../components/+storage/route";
import { watchHistoryState } from "../../remote-helpers/history-updater";
import { PortForwardDialog } from "../../port-forward";
import { DeleteClusterDialog } from "../../components/delete-cluster-dialog";
@ -42,19 +40,24 @@ import type { NamespaceStore } from "../../components/+namespaces/namespace-stor
import { withInjectables } from "@ogre-tools/injectable-react";
import namespaceStoreInjectable from "../../components/+namespaces/namespace-store/namespace-store.injectable";
import type { ClusterId } from "../../../common/cluster-types";
import hostedClusterInjectable from "../../../common/cluster-store/hosted-cluster/hosted-cluster.injectable";
import hostedClusterInjectable from "../../../common/cluster-store/hosted-cluster.injectable";
import type { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
import type { KubeObject } from "../../../common/k8s-api/kube-object";
import type { Disposer } from "../../../common/utils";
import kubeWatchApiInjectable from "../../kube-watch-api/kube-watch-api.injectable";
import historyInjectable from "../../navigation/history.injectable";
import type { History } from "history";
import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable";
import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable";
import { HelmRoute } from "../../components/+helm/route";
import type { KubeResource } from "../../../common/rbac";
interface Dependencies {
history: History,
namespaceStore: NamespaceStore
hostedClusterId: ClusterId
subscribeStores: (stores: KubeObjectStore<KubeObject>[]) => Disposer
history: History;
namespaceStore: NamespaceStore;
hostedClusterId: ClusterId;
subscribeStores: (stores: KubeObjectStore<KubeObject>[]) => Disposer;
isAllowedResource: IsAllowedResource;
}
@observer
@ -75,7 +78,13 @@ class NonInjectedClusterFrame extends React.Component<Dependencies> {
]);
}
@observable startUrl = isAllowedResource(["events", "nodes", "pods"]) ? routes.clusterURL() : routes.workloadsURL();
@computed get startUrl() {
const resources : KubeResource[] = ["events", "nodes", "pods"];
return resources.every(x => this.props.isAllowedResource(x))
? routes.clusterURL()
: routes.workloadsURL();
}
getTabLayoutRoutes(menuItem: ClusterPageMenuRegistration) {
const routes: TabLayoutRoute[] = [];
@ -138,18 +147,17 @@ class NonInjectedClusterFrame extends React.Component<Dependencies> {
<ErrorBoundary>
<MainLayout sidebar={<Sidebar />} footer={<Dock />}>
<Switch>
<Route component={ClusterOverview} {...routes.clusterRoute}/>
<Route component={Nodes} {...routes.nodesRoute}/>
<Route component={Workloads} {...routes.workloadsRoute}/>
<Route component={Config} {...routes.configRoute}/>
<Route component={Network} {...routes.networkRoute}/>
<Route component={Storage} {...routes.storageRoute}/>
<Route component={Namespaces} {...routes.namespacesRoute}/>
<Route component={NodesRoute} {...routes.nodesRoute}/>
<Route component={WorkloadsRoute} {...routes.workloadsRoute}/>
<Route component={ConfigRoute} {...routes.configRoute}/>
<Route component={NetworkRoute} {...routes.networkRoute}/>
<Route component={StorageRoute} {...routes.storageRoute}/>
<Route component={NamespacesRoute} {...routes.namespacesRoute}/>
<Route component={Events} {...routes.eventRoute}/>
<Route component={CustomResources} {...routes.crdRoute}/>
<Route component={UserManagement} {...routes.usersManagementRoute}/>
<Route component={Apps} {...routes.appsRoute}/>
<Route component={CustomResourcesRoute} {...routes.crdRoute}/>
<Route component={UserManagementRoute} {...routes.usersManagementRoute}/>
<Route component={HelmRoute} {...routes.helmRoute}/>
{this.renderExtensionTabLayoutRoutes()}
{this.renderExtensionRoutes()}
<Redirect exact from="/" to={this.startUrl}/>
@ -185,5 +193,6 @@ export const ClusterFrame = withInjectables<Dependencies>(NonInjectedClusterFram
namespaceStore: di.inject(namespaceStoreInjectable),
hostedClusterId: di.inject(hostedClusterInjectable).id,
subscribeStores: di.inject(kubeWatchApiInjectable).subscribeStores,
isAllowedResource: di.inject(isAllowedResourceInjectable),
}),
});

Some files were not shown because too many files have changed in this diff Show More