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:
parent
00314aabc0
commit
76f48df56b
@ -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",
|
||||
},
|
||||
|
21
src/common/cluster-store/allowed-resources.injectable.ts
Normal file
21
src/common/cluster-store/allowed-resources.injectable.ts
Normal 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;
|
@ -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) => {
|
@ -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";
|
||||
|
@ -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);
|
||||
|
@ -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(() => {
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
@ -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";
|
||||
|
@ -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 {
|
||||
|
15
src/common/utils/abort-controller.ts
Normal file
15
src/common/utils/abort-controller.ts
Normal 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();
|
||||
});
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
@ -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";
|
||||
|
22
src/common/utils/is-allowed-resource.injectable.ts
Normal file
22
src/common/utils/is-allowed-resource.injectable.ts
Normal 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;
|
@ -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";
|
||||
|
@ -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";
|
||||
|
@ -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) => {
|
||||
|
@ -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}/>
|
||||
);
|
||||
}
|
||||
}
|
@ -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";
|
37
src/renderer/components/+cluster/sidebar-item.tsx
Normal file
37
src/renderer/components/+cluster/sidebar-item.tsx
Normal 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,
|
||||
}),
|
||||
});
|
@ -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";
|
@ -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;
|
31
src/renderer/components/+config/route.tsx
Normal file
31
src/renderer/components/+config/route.tsx
Normal 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,
|
||||
}),
|
||||
});
|
45
src/renderer/components/+config/sidebar-item.tsx
Normal file
45
src/renderer/components/+config/sidebar-item.tsx
Normal 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,
|
||||
}),
|
||||
});
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
@ -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;
|
@ -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;
|
43
src/renderer/components/+custom-resources/route.tsx
Normal file
43
src/renderer/components/+custom-resources/route.tsx
Normal 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,
|
||||
}),
|
||||
});
|
76
src/renderer/components/+custom-resources/sidebar-item.tsx
Normal file
76
src/renderer/components/+custom-resources/sidebar-item.tsx
Normal 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,
|
||||
}),
|
||||
});
|
@ -3,5 +3,4 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
export * from "./events";
|
||||
export * from "./event-details";
|
||||
|
37
src/renderer/components/+events/sidebar-item.tsx
Normal file
37
src/renderer/components/+events/sidebar-item.tsx
Normal 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,
|
||||
}),
|
||||
});
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
34
src/renderer/components/+helm/route-tabs.injectable.ts
Normal file
34
src/renderer/components/+helm/route-tabs.injectable.ts
Normal 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;
|
31
src/renderer/components/+helm/route.tsx
Normal file
31
src/renderer/components/+helm/route.tsx
Normal 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,
|
||||
}),
|
||||
});
|
44
src/renderer/components/+helm/sidebar-item.tsx
Normal file
44
src/renderer/components/+helm/sidebar-item.tsx
Normal 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,
|
||||
}),
|
||||
});
|
@ -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";
|
||||
|
@ -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,
|
||||
}),
|
||||
},
|
||||
);
|
89
src/renderer/components/+namespaces/route.tsx
Normal file
89
src/renderer/components/+namespaces/route.tsx
Normal 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,
|
||||
}),
|
||||
});
|
37
src/renderer/components/+namespaces/sidebar-item.tsx
Normal file
37
src/renderer/components/+namespaces/sidebar-item.tsx
Normal 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,
|
||||
}),
|
||||
});
|
@ -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";
|
@ -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;
|
33
src/renderer/components/+network/route.tsx
Normal file
33
src/renderer/components/+network/route.tsx
Normal 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,
|
||||
}),
|
||||
});
|
45
src/renderer/components/+network/sidebar-item.tsx
Normal file
45
src/renderer/components/+network/sidebar-item.tsx
Normal 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,
|
||||
}),
|
||||
});
|
@ -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";
|
@ -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";
|
@ -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";
|
@ -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());
|
||||
|
37
src/renderer/components/+nodes/sidebar-item.tsx
Normal file
37
src/renderer/components/+nodes/sidebar-item.tsx
Normal 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,
|
||||
}),
|
||||
});
|
@ -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";
|
@ -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;
|
34
src/renderer/components/+storage/route.tsx
Normal file
34
src/renderer/components/+storage/route.tsx
Normal 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,
|
||||
}),
|
||||
});
|
||||
|
45
src/renderer/components/+storage/sidebar-item.tsx
Normal file
45
src/renderer/components/+storage/sidebar-item.tsx
Normal 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,
|
||||
}),
|
||||
});
|
@ -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";
|
@ -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;
|
31
src/renderer/components/+user-management/route.tsx
Normal file
31
src/renderer/components/+user-management/route.tsx
Normal 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,
|
||||
}),
|
||||
});
|
45
src/renderer/components/+user-management/sidebar-item.tsx
Normal file
45
src/renderer/components/+user-management/sidebar-item.tsx
Normal 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,
|
||||
}),
|
||||
});
|
@ -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,
|
||||
}),
|
||||
});
|
||||
|
@ -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;
|
39
src/renderer/components/+workloads-overview/workloads.ts
Normal file
39
src/renderer/components/+workloads-overview/workloads.ts
Normal 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],
|
||||
};
|
||||
}),
|
||||
);
|
@ -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";
|
@ -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;
|
34
src/renderer/components/+workloads/route.tsx
Normal file
34
src/renderer/components/+workloads/route.tsx
Normal 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,
|
||||
}),
|
||||
});
|
||||
|
45
src/renderer/components/+workloads/sidebar-item.tsx
Normal file
45
src/renderer/components/+workloads/sidebar-item.tsx
Normal 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,
|
||||
}),
|
||||
});
|
@ -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],
|
||||
]);
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
37
src/renderer/components/layout/tab-routes-sidebar-items.tsx
Normal file
37
src/renderer/components/layout/tab-routes-sidebar-items.tsx
Normal 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 })} />
|
||||
))
|
||||
}
|
||||
</>
|
||||
);
|
@ -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
Loading…
Reference in New Issue
Block a user