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:
authored and GitHub committed 2022-02-03 09:11:28 -05:00
1 parent 00314aabc0
commit 76f48df56b
105 files changed
+1442 -709

No files matched your search

@@ -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) => {
-38
View File
@@ -1,38 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { observer } from "mobx-react";
import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
import { HelmCharts } from "../+apps-helm-charts";
import { HelmReleases } from "../+apps-releases";
import { helmChartsURL, helmChartsRoute, releaseURL, releaseRoute } from "../../../common/routes";
@observer
export class Apps extends React.Component {
static get tabRoutes(): TabLayoutRoute[] {
return [
{
title: "Charts",
component: HelmCharts,
url: helmChartsURL(),
routePath: helmChartsRoute.path.toString(),
},
{
title: "Releases",
component: HelmReleases,
url: releaseURL(),
routePath: releaseRoute.path.toString(),
},
];
}
render() {
return (
<TabLayout className="Apps" tabs={Apps.tabRoutes}/>
);
}
}
-6
View File
@@ -1,6 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export * from "./apps";
@@ -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,
}),
});
-6
View File
@@ -1,6 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export * from "./config";
@@ -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
View File
@@ -0,0 +1,31 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { observer } from "mobx-react";
import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
import type { IComputedValue } from "mobx";
import { withInjectables } from "@ogre-tools/injectable-react";
import configRoutesInjectable from "./route-tabs.injectable";
export interface ConfigRouteProps {}
interface Dependencies {
routes: IComputedValue<TabLayoutRoute[]>;
}
const NonInjectedConfigRoute = observer(({ routes }: Dependencies & ConfigRouteProps) => (
<TabLayout
className="Config"
tabs={routes.get()}
/>
));
export const ConfigRoute = withInjectables<Dependencies, ConfigRouteProps>(NonInjectedConfigRoute, {
getProps: (di, props) => ({
routes: di.inject(configRoutesInjectable),
...props,
}),
});
@@ -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;
@@ -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,
}),
});
@@ -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,
}),
});
-1
View File
@@ -3,5 +3,4 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export * from "./events";
export * from "./event-details";
@@ -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,
}),
});
@@ -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
View File
@@ -0,0 +1,31 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { observer } from "mobx-react";
import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { IComputedValue } from "mobx";
import helmRoutesInjectable from "./route-tabs.injectable";
export interface HelmRouteProps {}
interface Dependencies {
routes: IComputedValue<TabLayoutRoute[]>;
}
const NonInjectedHelmRoute = observer(({ routes }: Dependencies & HelmRouteProps) => (
<TabLayout
className="Apps"
tabs={routes.get()}
/>
));
export const HelmRoute = withInjectables<Dependencies, HelmRouteProps>(NonInjectedHelmRoute, {
getProps: (di, props) => ({
routes: di.inject(helmRoutesInjectable),
...props,
}),
});
@@ -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,
}),
},
);
@@ -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,
}),
});
@@ -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;
@@ -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,
}),
});
@@ -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";
-7
View File
@@ -1,7 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export * from "./nodes";
export * from "./node-details";
@@ -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());
@@ -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;
@@ -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,
}),
});
@@ -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;
@@ -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,
}),
});
@@ -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;
@@ -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;
@@ -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,
}),
});
@@ -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;
+28 -195
View File
@@ -7,99 +7,34 @@ import styles from "./sidebar.module.scss";
import type { TabLayoutRoute } from "./tab-layout";
import React from "react";
import { disposeOnUnmount, observer } from "mobx-react";
import { cssNames, Disposer } from "../../utils";
import { Icon } from "../icon";
import { Workloads } from "../+workloads";
import { UserManagement } from "../+user-management";
import { Storage } from "../+storage";
import { Network } from "../+network";
import { crdStore } from "../+custom-resources/crd.store";
import { CustomResources } from "../+custom-resources/custom-resources";
import { observer } from "mobx-react";
import { cssNames } from "../../utils";
import { isActiveRoute } from "../../navigation";
import { isAllowedResource } from "../../../common/utils/allowed-resource";
import { Spinner } from "../spinner";
import { ClusterPageMenuRegistration, ClusterPageMenuRegistry, ClusterPageRegistry, getExtensionPageUrl } from "../../../extensions/registries";
import { SidebarItem } from "./sidebar-item";
import { Apps } from "../+apps";
import * as routes from "../../../common/routes";
import { Config } from "../+config";
import { catalogEntityRegistry } from "../../api/catalog-entity-registry";
import { SidebarCluster } from "./sidebar-cluster";
import type { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
import type { KubeObject } from "../../../common/k8s-api/kube-object";
import { withInjectables } from "@ogre-tools/injectable-react";
import kubeWatchApiInjectable
from "../../kube-watch-api/kube-watch-api.injectable";
import { TabRoutesSidebarItems } from "./tab-routes-sidebar-items";
import { ConfigSidebarItem } from "../+config/sidebar-item";
import { ClusterSidebarItem } from "../+cluster/sidebar-item";
import { NodesSidebarItem } from "../+nodes/sidebar-item";
import { WorkloadsSidebarItem } from "../+workloads/sidebar-item";
import { NetworkSidebarItem } from "../+network/sidebar-item";
import { StorageSidebarItem } from "../+storage/sidebar-item";
import { NamespacesSidebarItem } from "../+namespaces/sidebar-item";
import { EventsSidebarItem } from "../+events/sidebar-item";
import { HelmSidebarItem } from "../+helm/sidebar-item";
import { UserManagementSidebarItem } from "../+user-management/sidebar-item";
import { CustomResourcesSidebarItem } from "../+custom-resources/sidebar-item";
interface Props {
interface SidebarProps {
className?: string;
}
interface Dependencies {
subscribeStores: (stores: KubeObjectStore<KubeObject>[]) => Disposer
}
@observer
class NonInjectedSidebar extends React.Component<Props & Dependencies> {
export class Sidebar extends React.Component<SidebarProps> {
static displayName = "Sidebar";
componentDidMount() {
disposeOnUnmount(this, [
this.props.subscribeStores([
crdStore,
]),
]);
}
renderCustomResources() {
if (crdStore.isLoading) {
return (
<div className="flex justify-center">
<Spinner/>
</div>
);
}
return Object.entries(crdStore.groups).map(([group, crds]) => {
const id = `crd-group:${group}`;
const crdGroupsPageUrl = routes.crdURL({ query: { groups: group }});
return (
<SidebarItem key={id} id={id} text={group} url={crdGroupsPageUrl}>
{crds.map((crd) => (
<SidebarItem
key={crd.getResourceApiBase()}
id={`crd-resource:${crd.getResourceApiBase()}`}
url={crd.getResourceUrl()}
text={crd.getResourceKind()}
/>
))}
</SidebarItem>
);
});
}
renderTreeFromTabRoutes(tabRoutes: TabLayoutRoute[] = []): React.ReactNode {
if (!tabRoutes.length) {
return null;
}
return tabRoutes.map(({ title, routePath, url = routePath, exact = true }) => {
const subMenuItemId = `tab-route-item-${url}`;
return (
<SidebarItem
key={subMenuItemId}
id={subMenuItemId}
url={url}
text={title}
isActive={isActiveRoute({ path: routePath, exact })}
/>
);
});
}
getTabLayoutRoutes(menu: ClusterPageMenuRegistration): TabLayoutRoute[] {
if (!menu.id) {
return [];
@@ -169,7 +104,7 @@ class NonInjectedSidebar extends React.Component<Props & Dependencies> {
text={menuItem.title}
icon={<menuItem.components.Icon/>}
>
{this.renderTreeFromTabRoutes(tabRoutes)}
<TabRoutesSidebarItems routes={tabRoutes} />
</SidebarItem>
);
});
@@ -186,122 +121,20 @@ class NonInjectedSidebar extends React.Component<Props & Dependencies> {
<div className={cssNames("flex flex-col", className)} data-testid="cluster-sidebar">
<SidebarCluster clusterEntity={this.clusterEntity}/>
<div className={styles.sidebarNav}>
<SidebarItem
id="cluster"
text="Cluster"
isActive={isActiveRoute(routes.clusterRoute)}
isHidden={!isAllowedResource("nodes")}
url={routes.clusterURL()}
icon={<Icon svg="kube"/>}
/>
<SidebarItem
id="nodes"
text="Nodes"
isActive={isActiveRoute(routes.nodesRoute)}
isHidden={!isAllowedResource("nodes")}
url={routes.nodesURL()}
icon={<Icon svg="nodes"/>}
/>
<SidebarItem
id="workloads"
text="Workloads"
isActive={isActiveRoute(routes.workloadsRoute)}
isHidden={Workloads.tabRoutes.length == 0}
url={routes.workloadsURL()}
icon={<Icon svg="workloads"/>}
>
{this.renderTreeFromTabRoutes(Workloads.tabRoutes)}
</SidebarItem>
<SidebarItem
id="config"
text="Configuration"
isActive={isActiveRoute(routes.configRoute)}
isHidden={Config.tabRoutes.length == 0}
url={routes.configURL()}
icon={<Icon material="list"/>}
>
{this.renderTreeFromTabRoutes(Config.tabRoutes)}
</SidebarItem>
<SidebarItem
id="networks"
text="Network"
isActive={isActiveRoute(routes.networkRoute)}
isHidden={Network.tabRoutes.length == 0}
url={routes.networkURL()}
icon={<Icon material="device_hub"/>}
>
{this.renderTreeFromTabRoutes(Network.tabRoutes)}
</SidebarItem>
<SidebarItem
id="storage"
text="Storage"
isActive={isActiveRoute(routes.storageRoute)}
isHidden={Storage.tabRoutes.length == 0}
url={routes.storageURL()}
icon={<Icon svg="storage"/>}
>
{this.renderTreeFromTabRoutes(Storage.tabRoutes)}
</SidebarItem>
<SidebarItem
id="namespaces"
text="Namespaces"
isActive={isActiveRoute(routes.namespacesRoute)}
isHidden={!isAllowedResource("namespaces")}
url={routes.namespacesURL()}
icon={<Icon material="layers"/>}
/>
<SidebarItem
id="events"
text="Events"
isActive={isActiveRoute(routes.eventRoute)}
isHidden={!isAllowedResource("events")}
url={routes.eventsURL()}
icon={<Icon material="access_time"/>}
/>
<SidebarItem
id="apps"
text="Apps" // helm charts
isActive={isActiveRoute(routes.appsRoute)}
url={routes.appsURL()}
icon={<Icon material="apps"/>}
>
{this.renderTreeFromTabRoutes(Apps.tabRoutes)}
</SidebarItem>
<SidebarItem
id="users"
text="Access Control"
isActive={isActiveRoute(routes.usersManagementRoute)}
isHidden={UserManagement.tabRoutes.length === 0}
url={routes.usersManagementURL()}
icon={<Icon material="security"/>}
>
{this.renderTreeFromTabRoutes(UserManagement.tabRoutes)}
</SidebarItem>
<SidebarItem
id="custom-resources"
text="Custom Resources"
url={routes.crdURL()}
isActive={isActiveRoute(routes.crdRoute)}
isHidden={!isAllowedResource("customresourcedefinitions")}
icon={<Icon material="extension"/>}
>
{this.renderTreeFromTabRoutes(CustomResources.tabRoutes)}
{this.renderCustomResources()}
</SidebarItem>
<ClusterSidebarItem />
<NodesSidebarItem />
<WorkloadsSidebarItem />
<ConfigSidebarItem />
<NetworkSidebarItem />
<StorageSidebarItem />
<NamespacesSidebarItem />
<EventsSidebarItem />
<HelmSidebarItem />
<UserManagementSidebarItem />
<CustomResourcesSidebarItem />
{this.renderRegisteredMenus()}
</div>
</div>
);
}
}
export const Sidebar = withInjectables<Dependencies, Props>(
NonInjectedSidebar,
{
getProps: (di, props) => ({
subscribeStores: di.inject(kubeWatchApiInjectable).subscribeStores,
...props,
}),
},
);
@@ -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),
}),
});
@@ -7,7 +7,7 @@ import { initClusterFrame } from "./init-cluster-frame";
import extensionLoaderInjectable from "../../../../extensions/extension-loader/extension-loader.injectable";
import catalogEntityRegistryInjectable from "../../../api/catalog-entity-registry/catalog-entity-registry.injectable";
import frameRoutingIdInjectable from "./frame-routing-id/frame-routing-id.injectable";
import hostedClusterInjectable from "../../../../common/cluster-store/hosted-cluster/hosted-cluster.injectable";
import hostedClusterInjectable from "../../../../common/cluster-store/hosted-cluster.injectable";
import appEventBusInjectable from "../../../../common/app-event-bus/app-event-bus.injectable";
import clusterFrameContextInjectable from "../../../cluster-frame-context/cluster-frame-context.injectable";
@@ -19,7 +19,7 @@ import { EndpointDetails } from "../components/+network-endpoints";
import { IngressDetails } from "../components/+network-ingresses";
import { NetworkPolicyDetails } from "../components/+network-policies";
import { ServiceDetails } from "../components/+network-services";
import { NodeDetails } from "../components/+nodes";
import { NodeDetails } from "../components/+nodes/details";
import { PodSecurityPolicyDetails } from "../components/+pod-security-policies";
import { StorageClassDetails } from "../components/+storage-classes";
import { PersistentVolumeClaimDetails } from "../components/+storage-volume-claims";
@@ -0,0 +1,38 @@
/**
* 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 type { IsAllowedResource } from "../../common/utils/is-allowed-resource.injectable";
import isAllowedResourceInjectable from "../../common/utils/is-allowed-resource.injectable";
import { Events } from "../components/+events/events";
export interface WorkloadEventsProps {}
interface Dependencies {
isAllowedResource: IsAllowedResource;
}
const NonInjectedWorkloadEvents = observer(({ isAllowedResource }: Dependencies & WorkloadEventsProps) => {
if (!isAllowedResource("events")) {
return null;
}
return (
<Events
className="box grow"
compact
hideFilters
/>
);
});
export const WorkloadEvents = withInjectables<Dependencies, WorkloadEventsProps>(NonInjectedWorkloadEvents, {
getProps: (di, props) => ({
isAllowedResource: di.inject(isAllowedResourceInjectable),
...props,
}),
});
@@ -3,11 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import { isAllowedResource } from "../../common/utils/allowed-resource";
import { WorkloadsOverviewDetailRegistry } from "../../extensions/registries";
import { Events } from "../components/+events";
import { OverviewStatuses } from "../components/+workloads-overview/overview-statuses";
import { WorkloadEvents } from "./workload-events";
export function initWorkloadsOverviewDetailRegistry() {
WorkloadsOverviewDetailRegistry.getInstance()
@@ -20,9 +18,7 @@ export function initWorkloadsOverviewDetailRegistry() {
{
priority: 5,
components: {
Details: () => (
isAllowedResource("events") && <Events compact hideFilters className="box grow" />
),
Details: WorkloadEvents,
},
},
]);
+5 -13
View File
@@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { comparer, reaction } from "mobx";
import { disposer, Disposer, noop } from "../../common/utils";
import { disposer, Disposer, noop, WrappedAbortController } from "../../common/utils";
import type { KubeObject } from "../../common/k8s-api/kube-object";
import AbortController from "abort-controller";
import { once } from "lodash";
@@ -14,16 +14,6 @@ import logger from "../../common/logger";
// Kubernetes watch-api client
// API: https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams
class WrappedAbortController extends AbortController {
constructor(protected parent: AbortController) {
super();
parent.signal.addEventListener("abort", () => {
this.abort();
});
}
}
interface SubscribeStoreParams {
store: KubeObjectStore<KubeObject>;
parent: AbortController;
@@ -58,7 +48,7 @@ class WatchCount {
throw new Error(`Cannot dec count more times than it has been inc: ${store.api.objectConstructor.kind}`);
}
logger.debug(`[KUBE-WATCH-API]: dec() count for ${store.api.objectConstructor.apiBase} is now ${newCount}`);
logger.info(`[KUBE-WATCH-API]: dec() count for ${store.api.objectConstructor.apiBase} is now ${newCount}`);
this.#data.set(store, newCount);
return newCount;
@@ -83,6 +73,8 @@ interface Dependencies {
clusterFrameContext: ClusterFrameContext
}
export type SubscribeStores = (stores: KubeObjectStore<KubeObject>[], opts?: KubeWatchSubscribeStoreOptions) => Disposer;
export class KubeWatchApi {
#watch = new WatchCount();
@@ -153,7 +145,7 @@ export class KubeWatchApi {
};
}
subscribeStores = (stores: KubeObjectStore<KubeObject>[], { namespaces, onLoadFailure }: KubeWatchSubscribeStoreOptions = {}): Disposer => {
subscribeStores: SubscribeStores = (stores, { namespaces, onLoadFailure } = {}) => {
const parent = new AbortController();
const unsubscribe = disposer(
...stores.map(store => this.subscribeStore({