Technical requirements for behavioural unit tests (#5084)

* Implement a lot of technical requirements for behavioural unit tests

Note: the crux of this was to make routing env-agnostic, and not based on URLs as magic strings, but instead something type-enforced.

Note: extension-based routes comply to same exact interface by "late-registering" their routes when installed. Routes are just injectables.

Note: another chunk of global shared state is no more.

Note: a lot of explicit side effects have been cornered to injectables.

Note: a lot of stuff has become reactive as part if this.

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make a directory commonly available

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Require id for <Select /> to prevent non-deterministic renders

This was caused by global state in a 3rd party lib: "react-select".

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Specify id for all <Select /> to satisfy previous commit

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Prevent explicit side effect in component by using existing dependency instead

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Extract instantiation of "conf" as injectables for causing side effects

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Introduce a legacy-helper to make gradual refactoring of inheritors of Singleton easier

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Make legacy unit tests for hotbar green and more simple by using the new legacy helper

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Temporarily kludge all unit tests green with a disclaimer about allowing side-effects

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Remove kludge in previous commit by explicitly permitting specific side effects where old unit tests require it

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Prevent old unit test with side effects from accessing file system

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Migrate to actual typing for di.permitSideEffects

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Prevent unit tests from failing because of non-standard method of HTML-element not present in js-dom

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Adapt integration tests to recent changes

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Fix code style

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Fix artifact from bad rebase

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Add a deprecation from a review comment

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Remove change that is not required

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove redundant comment

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Fix code style

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove redundant file

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Fix bad merge

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Improve variable name

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Tweak logger interface to be more descriptive

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make injecting legacy singleton always provide new instance

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove conditional typing when not needed

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Improve naming of variable

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove unnecessary code style changes

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove flag for causing side effects from too broad scope

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Override side-effects in unit test using injectable instead of monkey patching

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Flag some side-effects and add general overrides

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Fix unit tests in CI by removing explicit side-effect

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Remove explicit side-effect from getting default shell

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce abstraction for getting absolute paths

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to using abstraction for getting absolute path to control explicit side effect

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce abstraction for joining paths

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to using abstraction for joining paths to control explicit side effect

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Fix fake implementation for join paths

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Fix test after removing explicit side effect

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove explicit side effects from kubeconfig-syncs

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Fix arguments after removing explicit side effect

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make registrators not async for not being needed anymore

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make generalCatalogEntities non-observable, as there is no requirement

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Remove redundant code

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>

* Simplify logic for registering general catalog entity sources

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Add TODO

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Replace function for getting application menu items with reactive solution

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Fix typo in interface name

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove global shared state usages of hot bar store

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove redundant enum

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
Iku-tursoandJanne Savolainen authored and GitHub committed 2022-03-31 16:57:05 +03:00
1 parent ab2cd27a27
commit a277cfcf02
583 files changed
+13073 -6556

No files matched your search

@@ -0,0 +1,25 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import ipcRendererInjectable from "./ipc-renderer/ipc-renderer.injectable";
import type {
IpcChannelListener,
} from "../../ipc-channel-listeners/ipc-channel-listener-injection-token";
const registerIpcChannelListenerInjectable = getInjectable({
id: "register-ipc-channel-listener",
instantiate: (di) => {
const ipc = di.inject(ipcRendererInjectable);
return ({ channel, handle }: IpcChannelListener) => {
ipc.on(channel.name, (_, data) => {
handle(data);
});
};
},
});
export default registerIpcChannelListenerInjectable;
+28 -6
View File
@@ -20,9 +20,7 @@ import { DefaultProps } from "./mui-base-theme";
import configurePackages from "../common/configure-packages";
import * as initializers from "./initializers";
import logger from "../common/logger";
import { HotbarStore } from "../common/hotbar-store";
import { WeblinkStore } from "../common/weblink-store";
import { ThemeStore } from "./theme.store";
import { SentryInit } from "../common/sentry";
import { registerCustomThemes } from "./components/monaco-editor";
import { getDi } from "./getDi";
@@ -36,6 +34,15 @@ import userStoreInjectable from "../common/user-store/user-store.injectable";
import initRootFrameInjectable from "./frames/root-frame/init-root-frame/init-root-frame.injectable";
import initClusterFrameInjectable from "./frames/cluster-frame/init-cluster-frame/init-cluster-frame.injectable";
import commandOverlayInjectable from "./components/command-palette/command-overlay.injectable";
import { Router } from "react-router";
import historyInjectable from "./navigation/history.injectable";
import themeStoreInjectable from "./theme-store.injectable";
import navigateToAddClusterInjectable from "../common/front-end-routing/routes/add-cluster/navigate-to-add-cluster.injectable";
import addSyncEntriesInjectable from "./initializers/add-sync-entries.injectable";
import hotbarStoreInjectable from "../common/hotbar-store.injectable";
import { bindEvents } from "./navigation/events";
import deleteClusterDialogModelInjectable
from "./components/delete-cluster-dialog/delete-cluster-dialog-model/delete-cluster-dialog-model.injectable";
if (process.isMainFrame) {
SentryInit();
@@ -58,6 +65,9 @@ async function attachChromeDebugger() {
export async function bootstrap(di: DiContainer) {
await di.runSetups();
// TODO: Consolidate import time side-effect to setup time
bindEvents();
const rootElem = document.getElementById("app");
const logPrefix = `[BOOTSTRAP-${process.isMainFrame ? "ROOT" : "CLUSTER"}-FRAME]:`;
@@ -79,12 +89,19 @@ export async function bootstrap(di: DiContainer) {
logger.info(`${logPrefix} initializing CatalogEntityDetailRegistry`);
initializers.initCatalogEntityDetailRegistry();
const navigateToAddCluster = di.inject(navigateToAddClusterInjectable);
const addSyncEntries = di.inject(addSyncEntriesInjectable);
logger.info(`${logPrefix} initializing CatalogCategoryRegistryEntries`);
initializers.initCatalogCategoryRegistryEntries();
initializers.initCatalogCategoryRegistryEntries({
navigateToAddCluster,
addSyncEntries,
});
logger.info(`${logPrefix} initializing Catalog`);
initializers.initCatalog({
openCommandDialog: di.inject(commandOverlayInjectable).open,
deleteClusterDialogModel: di.inject(deleteClusterDialogModelInjectable),
});
const extensionLoader = di.inject(extensionLoaderInjectable);
@@ -104,10 +121,11 @@ export async function bootstrap(di: DiContainer) {
await clusterStore.loadInitialOnRenderer();
// HotbarStore depends on: ClusterStore
HotbarStore.createInstance();
di.inject(hotbarStoreInjectable);
// ThemeStore depends on: UserStore
ThemeStore.createInstance();
// TODO: Remove temporal dependencies
di.inject(themeStoreInjectable);
WeblinkStore.createInstance();
@@ -142,9 +160,13 @@ export async function bootstrap(di: DiContainer) {
});
}
const history = di.inject(historyInjectable);
render(
<DiContextProvider value={{ di }}>
{DefaultProps(App)}
<Router history={history}>
{DefaultProps(App)}
</Router>
</DiContextProvider>,
rootElem,
@@ -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 } from "@ogre-tools/injectable";
import { AddCluster } from "./add-cluster";
import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token";
import addClusterRouteInjectable from "../../../common/front-end-routing/routes/add-cluster/add-cluster-route.injectable";
const addClusterRouteComponentInjectable = getInjectable({
id: "add-cluster-route-component",
instantiate: (di) => ({
route: di.inject(addClusterRouteInjectable),
Component: AddCluster,
}),
injectionToken: routeSpecificComponentInjectionToken,
});
export default addClusterRouteComponentInjectable;
@@ -13,20 +13,17 @@ import { observer } from "mobx-react";
import path from "path";
import React from "react";
import * as uuid from "uuid";
import { catalogURL } from "../../../common/routes";
import { appEventBus } from "../../../common/app-event-bus/event-bus";
import { loadConfigFromString, splitConfig } from "../../../common/kube-helpers";
import { docsUrl } from "../../../common/vars";
import { navigate } from "../../navigation";
import { iter } from "../../utils";
import { Button } from "../button";
import { Notifications } from "../notifications";
import { SettingLayout } from "../layout/setting-layout";
import { MonacoEditor } from "../monaco-editor";
import { withInjectables } from "@ogre-tools/injectable-react";
import getCustomKubeConfigDirectoryInjectable
from "../../../common/app-paths/get-custom-kube-config-directory/get-custom-kube-config-directory.injectable";
import getCustomKubeConfigDirectoryInjectable from "../../../common/app-paths/get-custom-kube-config-directory/get-custom-kube-config-directory.injectable";
import navigateToCatalogInjectable, { NavigateToCatalog } from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
interface Option {
config: KubeConfig;
@@ -35,6 +32,7 @@ interface Option {
interface Dependencies {
getCustomKubeConfigDirectory: (directoryName: string) => string;
navigateToCatalog: NavigateToCatalog;
}
function getContexts(config: KubeConfig): Map<string, Option> {
@@ -96,7 +94,7 @@ class NonInjectedAddCluster extends React.Component<Dependencies> {
Notifications.ok(`Successfully added ${this.kubeContexts.size} new cluster(s)`);
return navigate(catalogURL());
return this.props.navigateToCatalog();
} catch (error) {
Notifications.error(`Failed to add clusters: ${error}`);
}
@@ -104,7 +102,7 @@ class NonInjectedAddCluster extends React.Component<Dependencies> {
render() {
return (
<SettingLayout className={styles.AddClusters}>
<SettingLayout className={styles.AddClusters} data-testid="add-cluster-page">
<h2>Add Clusters from Kubeconfig</h2>
<p>
Clusters added here are <b>not</b> merged into the <code>~/.kube/config</code> file.{" "}
@@ -149,5 +147,7 @@ export const AddCluster = withInjectables<Dependencies>(NonInjectedAddCluster, {
getCustomKubeConfigDirectory: di.inject(
getCustomKubeConfigDirectoryInjectable,
),
navigateToCatalog: di.inject(navigateToCatalogInjectable),
}),
});
@@ -12,6 +12,7 @@ import { CatalogCategory } from "../../../api/catalog-entity";
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
import type { AdditionalCategoryColumnRegistration, CategoryColumnRegistration } from "../custom-category-columns";
import getCategoryColumnsInjectable, { CategoryColumns, GetCategoryColumnsParams } from "../get-category-columns.injectable";
import hotbarStoreInjectable from "../../../../common/hotbar-store.injectable";
class TestCategory extends CatalogCategory {
apiVersion = "catalog.k8slens.dev/v1alpha1";
@@ -39,6 +40,8 @@ describe("Custom Category Columns", () => {
beforeEach(() => {
di = getDiForUnitTesting();
di.override(hotbarStoreInjectable, () => ({}));
});
describe("without extensions", () => {
@@ -3,5 +3,4 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
.Network {
}
export const browseCatalogTab = "browse";
@@ -3,8 +3,8 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { browseCatalogTab } from "../../../../common/routes";
import createStorageInjectable from "../../../utils/create-storage/create-storage.injectable";
import { browseCatalogTab } from "../catalog-browse-tab";
const catalogPreviousActiveTabStorageInjectable = getInjectable({
id: "catalog-previous-active-tab-storage",
@@ -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 } from "@ogre-tools/injectable";
import { Catalog } from "./catalog";
import catalogRouteInjectable from "../../../common/front-end-routing/routes/catalog/catalog-route.injectable";
import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token";
const catalogRouteComponentInjectable = getInjectable({
id: "catalog-route-component",
instantiate: (di) => ({
route: di.inject(catalogRouteInjectable),
Component: Catalog,
}),
injectionToken: routeSpecificComponentInjectionToken,
});
export default catalogRouteComponentInjectable;
@@ -0,0 +1,24 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import routePathParametersInjectable from "../../routes/route-path-parameters.injectable";
import catalogRouteInjectable from "../../../common/front-end-routing/routes/catalog/catalog-route.injectable";
const catalogRouteParametersInjectable = getInjectable({
id: "catalog-route-parameters",
instantiate: (di) => {
const route = di.inject(catalogRouteInjectable);
const pathParameters = di.inject(routePathParametersInjectable, route);
return {
group: computed(() => pathParameters.get().group),
kind: computed(() => pathParameters.get().kind),
};
},
});
export default catalogRouteParametersInjectable;
@@ -7,7 +7,6 @@ import React from "react";
import { screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Catalog } from "./catalog";
import { createMemoryHistory } from "history";
import { mockWindow } from "../../../../__mocks__/windowMock";
import { CatalogCategoryRegistry, CatalogEntity, CatalogEntityActionContext, CatalogEntityData } from "../../../common/catalog";
import { CatalogEntityRegistry } from "../../api/catalog-entity-registry";
@@ -16,15 +15,15 @@ import type { CatalogEntityStore } from "./catalog-entity-store/catalog-entity.s
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import type { DiContainer } from "@ogre-tools/injectable";
import catalogEntityStoreInjectable from "./catalog-entity-store/catalog-entity-store.injectable";
import catalogEntityRegistryInjectable
from "../../api/catalog-entity-registry/catalog-entity-registry.injectable";
import catalogEntityRegistryInjectable from "../../api/catalog-entity-registry/catalog-entity-registry.injectable";
import type { DiRender } from "../test-utils/renderFor";
import { renderFor } from "../test-utils/renderFor";
import { ThemeStore } from "../../theme.store";
import { UserStore } from "../../../common/user-store";
import mockFs from "mock-fs";
import directoryForUserDataInjectable
from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import getConfigurationFileModelInjectable from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
import appVersionInjectable from "../../../common/get-configuration-file-model/app-version/app-version.injectable";
import type { AppEvent } from "../../../common/app-event-bus/event-bus";
import appEventBusInjectable from "../../../common/app-event-bus/app-event-bus.injectable";
@@ -66,25 +65,6 @@ class MockCatalogEntity extends CatalogEntity {
}
describe("<Catalog />", () => {
const history = createMemoryHistory();
const mockLocation = {
pathname: "",
search: "",
state: "",
hash: "",
};
const mockMatch = {
params: {
// will be used to match activeCategory
// need to be the same as property values in kubernetesClusterCategory
group: "entity.k8slens.dev",
kind: "KubernetesCluster",
},
isExact: true,
path: "",
url: "",
};
function createMockCatalogEntity(onRun: (context: CatalogEntityActionContext) => void | Promise<void>) {
return new MockCatalogEntity({
metadata: {
@@ -112,6 +92,9 @@ describe("<Catalog />", () => {
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
di.permitSideEffects(getConfigurationFileModelInjectable);
di.permitSideEffects(appVersionInjectable);
await di.runSetups();
mockFs();
@@ -169,11 +152,7 @@ describe("<Catalog />", () => {
);
render(
<Catalog
history={history}
location={mockLocation}
match={mockMatch}
/>,
<Catalog />,
);
userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon"));
@@ -198,13 +177,7 @@ describe("<Catalog />", () => {
},
);
render(
<Catalog
history={history}
location={mockLocation}
match={mockMatch}
/>,
);
render(<Catalog />);
userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon"));
});
@@ -229,13 +202,7 @@ describe("<Catalog />", () => {
},
);
render(
<Catalog
history={history}
location={mockLocation}
match={mockMatch}
/>,
);
render(<Catalog />);
userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon"));
});
@@ -255,13 +222,7 @@ describe("<Catalog />", () => {
},
);
render(
<Catalog
history={history}
location={mockLocation}
match={mockMatch}
/>,
);
render(<Catalog />);
userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon"));
});
@@ -288,13 +249,7 @@ describe("<Catalog />", () => {
},
);
render(
<Catalog
history={history}
location={mockLocation}
match={mockMatch}
/>,
);
render(<Catalog />);
userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon"));
});
@@ -319,24 +274,14 @@ describe("<Catalog />", () => {
},
);
render(
<Catalog
history={history}
location={mockLocation}
match={mockMatch}
/>,
);
render(<Catalog />);
userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon"));
});
it("emits catalog open AppEvent", () => {
render(
<Catalog
history={history}
location={mockLocation}
match={mockMatch}
/>,
<Catalog />,
);
expect(emitEvent).toHaveBeenCalledWith( {
@@ -347,11 +292,7 @@ describe("<Catalog />", () => {
it("emits catalog change AppEvent when changing the category", () => {
render(
<Catalog
history={history}
location={mockLocation}
match={mockMatch}
/>,
<Catalog />,
);
userEvent.click(screen.getByText("Web Links"));
+34 -19
View File
@@ -8,21 +8,18 @@ import styles from "./catalog.module.scss";
import React from "react";
import { disposeOnUnmount, observer } from "mobx-react";
import { ItemListLayout } from "../item-object-list";
import { action, IComputedValue, makeObservable, observable, reaction, runInAction, when } from "mobx";
import { action, computed, IComputedValue, makeObservable, observable, reaction, runInAction, when } from "mobx";
import type { CatalogEntityStore } from "./catalog-entity-store/catalog-entity.store";
import { navigate } from "../../navigation";
import { MenuItem, MenuActions } from "../menu";
import type { CatalogEntityContextMenu, CatalogEntityContextMenuContext } from "../../api/catalog-entity";
import { HotbarStore } from "../../../common/hotbar-store";
import { ConfirmDialog } from "../confirm-dialog";
import { catalogCategoryRegistry, CatalogEntity } from "../../../common/catalog";
import { CatalogAddButton } from "./catalog-add-button";
import type { RouteComponentProps } from "react-router";
import { Notifications } from "../notifications";
import { MainLayout } from "../layout/main-layout";
import { prevDefault } from "../../utils";
import { CatalogEntityDetails } from "./catalog-entity-details";
import { browseCatalogTab, catalogURL, CatalogViewRouteParam } from "../../../common/routes";
import { CatalogMenu } from "./catalog-menu";
import { RenderDelay } from "../render-delay/render-delay";
import { Icon } from "../icon";
@@ -36,10 +33,13 @@ import getCategoryColumnsInjectable from "./get-category-columns.injectable";
import type { RegisteredCustomCategoryViewDecl } from "./custom-views.injectable";
import customCategoryViewsInjectable from "./custom-views.injectable";
import type { CustomCategoryViewComponents } from "./custom-views";
import navigateToCatalogInjectable, { NavigateToCatalog } from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
import catalogRouteParametersInjectable from "./catalog-route-parameters.injectable";
import { browseCatalogTab } from "./catalog-browse-tab";
import type { AppEvent } from "../../../common/app-event-bus/event-bus";
import appEventBusInjectable from "../../../common/app-event-bus/app-event-bus.injectable";
export interface CatalogProps extends RouteComponentProps<CatalogViewRouteParam> {}
import hotbarStoreInjectable from "../../../common/hotbar-store.injectable";
import type { HotbarStore } from "../../../common/hotbar-store";
interface Dependencies {
catalogPreviousActiveTabStorage: { set: (value: string ) => void };
@@ -47,23 +47,35 @@ interface Dependencies {
getCategoryColumns: (params: GetCategoryColumnsParams) => CategoryColumns;
customCategoryViews: IComputedValue<Map<string, Map<string, RegisteredCustomCategoryViewDecl>>>;
emitEvent: (event: AppEvent) => void;
routeParameters: {
group: IComputedValue<string>;
kind: IComputedValue<string>;
};
navigateToCatalog: NavigateToCatalog;
hotbarStore: HotbarStore;
}
@observer
class NonInjectedCatalog extends React.Component<CatalogProps & Dependencies> {
class NonInjectedCatalog extends React.Component<Dependencies> {
@observable private contextMenu: CatalogEntityContextMenuContext;
@observable activeTab?: string;
constructor(props: CatalogProps & Dependencies) {
constructor(props: Dependencies) {
super(props);
makeObservable(this);
}
@computed
get routeActiveTab(): string {
const { group, kind } = this.props.match.params ?? {};
const { group, kind } = this.props.routeParameters;
if (group && kind) {
return `${group}/${kind}`;
const dereferencedGroup = group.get();
const dereferencedKind = kind.get();
if (dereferencedGroup && dereferencedKind) {
return `${dereferencedGroup}/${dereferencedKind}`;
}
return browseCatalogTab;
@@ -115,11 +127,11 @@ class NonInjectedCatalog extends React.Component<CatalogProps & Dependencies> {
}
addToHotbar(entity: CatalogEntity): void {
HotbarStore.getInstance().addToHotbar(entity);
this.props.hotbarStore.addToHotbar(entity);
}
removeFromHotbar(entity: CatalogEntity): void {
HotbarStore.getInstance().removeFromHotbar(entity.getId());
this.props.hotbarStore.removeFromHotbar(entity.getId());
}
onDetails = (entity: CatalogEntity) => {
@@ -163,9 +175,10 @@ class NonInjectedCatalog extends React.Component<CatalogProps & Dependencies> {
});
if (activeCategory) {
navigate(catalogURL({ params: { group: activeCategory.spec.group, kind: activeCategory.spec.names.kind }}));
this.props.navigateToCatalog({ group: activeCategory.spec.group, kind: activeCategory.spec.names.kind });
} else {
navigate(catalogURL({ params: { group: browseCatalogTab }}));
this.props.navigateToCatalog({ group: browseCatalogTab });
}
});
@@ -205,7 +218,7 @@ class NonInjectedCatalog extends React.Component<CatalogProps & Dependencies> {
};
renderName(entity: CatalogEntity) {
const isItemInHotbar = HotbarStore.getInstance().isAddedToActive(entity);
const isItemInHotbar = this.props.hotbarStore.isAddedToActive(entity);
return (
<>
@@ -321,13 +334,15 @@ class NonInjectedCatalog extends React.Component<CatalogProps & Dependencies> {
}
}
export const Catalog = withInjectables<Dependencies, CatalogProps>( NonInjectedCatalog, {
getProps: (di, props) => ({
export const Catalog = withInjectables<Dependencies>( NonInjectedCatalog, {
getProps: (di) => ({
catalogEntityStore: di.inject(catalogEntityStoreInjectable),
catalogPreviousActiveTabStorage: di.inject(catalogPreviousActiveTabStorageInjectable),
getCategoryColumns: di.inject(getCategoryColumnsInjectable),
customCategoryViews: di.inject(customCategoryViewsInjectable),
routeParameters: di.inject(catalogRouteParametersInjectable),
navigateToCatalog: di.inject(navigateToCatalogInjectable),
emitEvent: di.inject(appEventBusInjectable).emit,
...props,
hotbarStore: di.inject(hotbarStoreInjectable),
}),
});
@@ -9,10 +9,12 @@ import type { CatalogCategory, CatalogEntity } from "../../../common/catalog";
import type { ItemListLayoutProps } from "../item-object-list";
import type { RegisteredAdditionalCategoryColumn } from "./custom-category-columns";
import categoryColumnsInjectable from "./custom-category-columns.injectable";
import { defaultCategoryColumns, browseAllColumns, nameCategoryColumn } from "./internal-category-columns";
import { defaultCategoryColumns, browseAllColumns } from "./internal-category-columns";
import nameCategoryColumnInjectable from "./name-category-column.injectable";
interface Dependencies {
extensionColumns: IComputedValue<Map<string, Map<string, RegisteredAdditionalCategoryColumn[]>>>;
nameCategoryColumn: RegisteredAdditionalCategoryColumn;
}
export interface GetCategoryColumnsParams {
@@ -21,7 +23,7 @@ export interface GetCategoryColumnsParams {
export type CategoryColumns = Required<Pick<ItemListLayoutProps<CatalogEntity>, "sortingCallbacks" | "searchFilters" | "renderTableContents" | "renderTableHeader">>;
function getSpecificCategoryColumns(activeCategory: CatalogCategory, extensionColumns: IComputedValue<Map<string, Map<string, RegisteredAdditionalCategoryColumn[]>>>): RegisteredAdditionalCategoryColumn[] {
function getSpecificCategoryColumns(activeCategory: CatalogCategory, extensionColumns: IComputedValue<Map<string, Map<string, RegisteredAdditionalCategoryColumn[]>>>, nameCategoryColumn: RegisteredAdditionalCategoryColumn): RegisteredAdditionalCategoryColumn[] {
const fromExtensions = (
extensionColumns
.get()
@@ -41,7 +43,7 @@ function getSpecificCategoryColumns(activeCategory: CatalogCategory, extensionCo
];
}
function getBrowseAllColumns(): RegisteredAdditionalCategoryColumn[] {
function getBrowseAllColumns(nameCategoryColumn: RegisteredAdditionalCategoryColumn): RegisteredAdditionalCategoryColumn[] {
return [
...browseAllColumns,
nameCategoryColumn,
@@ -49,11 +51,11 @@ function getBrowseAllColumns(): RegisteredAdditionalCategoryColumn[] {
];
}
const getCategoryColumns = ({ extensionColumns }: Dependencies) => ({ activeCategory }: GetCategoryColumnsParams): CategoryColumns => {
const getCategoryColumns = ({ extensionColumns, nameCategoryColumn }: Dependencies) => ({ activeCategory }: GetCategoryColumnsParams): CategoryColumns => {
const allRegistrations = orderBy(
activeCategory
? getSpecificCategoryColumns(activeCategory, extensionColumns)
: getBrowseAllColumns(),
? getSpecificCategoryColumns(activeCategory, extensionColumns, nameCategoryColumn)
: getBrowseAllColumns(nameCategoryColumn),
"priority",
"asc",
);
@@ -89,6 +91,7 @@ const getCategoryColumnsInjectable = getInjectable({
instantiate: (di) => getCategoryColumns({
extensionColumns: di.inject(categoryColumnsInjectable),
nameCategoryColumn: di.inject(nameCategoryColumnInjectable),
}),
});
@@ -4,26 +4,54 @@
*/
import React, { ReactNode, useState } from "react";
import { HotbarStore } from "../../../common/hotbar-store";
import { MenuItem } from "../menu";
import type { CatalogEntity } from "../../api/catalog-entity";
import { withInjectables } from "@ogre-tools/injectable-react";
import hotbarStoreInjectable from "../../../common/hotbar-store.injectable";
import type { HotbarStore } from "../../../common/hotbar-store";
export function HotbarToggleMenuItem(props: { entity: CatalogEntity; addContent: ReactNode; removeContent: ReactNode }) {
const store = HotbarStore.getInstance();
const [itemInHotbar, setItemInHotbar] = useState(store.isAddedToActive(props.entity));
interface Dependencies {
hotbarStore: HotbarStore;
}
interface HotbarToggleMenuItemProps {
entity: CatalogEntity;
addContent: ReactNode;
removeContent: ReactNode;
}
function NonInjectedHotbarToggleMenuItem({
addContent,
entity,
hotbarStore,
removeContent,
}: Dependencies & HotbarToggleMenuItemProps) {
const [itemInHotbar, setItemInHotbar] = useState(hotbarStore.isAddedToActive(entity));
return (
<MenuItem onClick={() => {
if (itemInHotbar) {
store.removeFromHotbar(props.entity.getId());
hotbarStore.removeFromHotbar(entity.getId());
setItemInHotbar(false);
} else {
store.addToHotbar(props.entity);
hotbarStore.addToHotbar(entity);
setItemInHotbar(true);
}
}}>
{itemInHotbar ? props.removeContent : props.addContent }
{itemInHotbar ? removeContent : addContent }
</MenuItem>
);
}
export const HotbarToggleMenuItem = withInjectables<Dependencies, HotbarToggleMenuItemProps>(
NonInjectedHotbarToggleMenuItem,
{
getProps: (di, props) => ({
hotbarStore: di.inject(hotbarStoreInjectable),
...props,
}),
},
);
@@ -6,49 +6,10 @@
import styles from "./catalog.module.scss";
import React from "react";
import { HotbarStore } from "../../../common/hotbar-store";
import type { CatalogEntity } from "../../api/catalog-entity";
import { Avatar } from "../avatar";
import type { RegisteredAdditionalCategoryColumn } from "./custom-category-columns";
import { Icon } from "../icon";
import { prevDefault } from "../../utils";
import { getLabelBadges } from "./helpers";
import { KubeObject } from "../../../common/k8s-api/kube-object";
function renderEntityName(entity: CatalogEntity) {
const hotbarStore = HotbarStore.getInstance();
const isItemInHotbar = hotbarStore.isAddedToActive(entity);
const onClick = prevDefault(
isItemInHotbar
? () => hotbarStore.removeFromHotbar(entity.getId())
: () => hotbarStore.addToHotbar(entity),
);
return (
<>
<Avatar
title={entity.getName()}
colorHash={`${entity.getName()}-${entity.getSource()}`}
src={entity.spec.icon?.src}
background={entity.spec.icon?.background}
className={styles.catalogAvatar}
size={24}
>
{entity.spec.icon?.material && <Icon material={entity.spec.icon?.material} small/>}
</Avatar>
<span>{entity.getName()}</span>
<Icon
small
className={styles.pinIcon}
material={!isItemInHotbar && "push_pin"}
svg={isItemInHotbar ? "push_off" : "push_pin"}
tooltip={isItemInHotbar ? "Remove from Hotbar" : "Add to Hotbar"}
onClick={onClick}
/>
</>
);
}
export const browseAllColumns: RegisteredAdditionalCategoryColumn[] = [
{
id: "kind",
@@ -63,20 +24,6 @@ export const browseAllColumns: RegisteredAdditionalCategoryColumn[] = [
},
];
export const nameCategoryColumn: RegisteredAdditionalCategoryColumn = {
id: "name",
priority: 0,
renderCell: renderEntityName,
titleProps: {
title: "Name",
className: styles.entityName,
id: "name",
sortBy: "name",
},
searchFilter: entity => entity.getName(),
sortCallback: entity => `name=${entity.getName()}`,
};
export const defaultCategoryColumns: RegisteredAdditionalCategoryColumn[] = [
{
id: "source",
@@ -0,0 +1,67 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import styles from "./catalog.module.scss";
import type { CatalogEntity } from "../../../common/catalog";
import { prevDefault } from "../../utils";
import { Avatar } from "../avatar";
import { Icon } from "../icon";
import React from "react";
import type { RegisteredAdditionalCategoryColumn } from "./custom-category-columns";
import hotbarStoreInjectable from "../../../common/hotbar-store.injectable";
import type { HotbarStore } from "../../../common/hotbar-store";
const renderEntityName = (hotbarStore: HotbarStore) => (entity: CatalogEntity) => {
const isItemInHotbar = hotbarStore.isAddedToActive(entity);
const onClick = prevDefault(
isItemInHotbar
? () => hotbarStore.removeFromHotbar(entity.getId())
: () => hotbarStore.addToHotbar(entity),
);
return (
<>
<Avatar
title={entity.getName()}
colorHash={`${entity.getName()}-${entity.getSource()}`}
src={entity.spec.icon?.src}
background={entity.spec.icon?.background}
className={styles.catalogAvatar}
size={24}
>
{entity.spec.icon?.material && <Icon material={entity.spec.icon?.material} small/>}
</Avatar>
<span>{entity.getName()}</span>
<Icon
small
className={styles.pinIcon}
material={!isItemInHotbar && "push_pin"}
svg={isItemInHotbar ? "push_off" : "push_pin"}
tooltip={isItemInHotbar ? "Remove from Hotbar" : "Add to Hotbar"}
onClick={onClick}
/>
</>
);
};
const nameCategoryColumnInjectable = getInjectable({
id: "name-category-column",
instantiate: (di): RegisteredAdditionalCategoryColumn => ({
id: "name",
priority: 0,
renderCell: renderEntityName(di.inject(hotbarStoreInjectable)),
titleProps: {
title: "Name",
className: styles.entityName,
id: "name",
sortBy: "name",
},
searchFilter: (entity) => entity.getName(),
sortCallback: (entity) => `name=${entity.getName()}`,
}),
});
export default nameCategoryColumnInjectable;
@@ -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 } from "@ogre-tools/injectable";
import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token";
import { ClusterOverview } from "./cluster-overview";
import clusterOverviewRouteInjectable from "../../../common/front-end-routing/routes/cluster/overview/cluster-overview-route.injectable";
const clusterOverviewRouteComponentInjectable = getInjectable({
id: "cluster-overview-route-component",
instantiate: (di) => ({
route: di.inject(clusterOverviewRouteInjectable),
Component: ClusterOverview,
}),
injectionToken: routeSpecificComponentInjectionToken,
});
export default clusterOverviewRouteComponentInjectable;
@@ -0,0 +1,42 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { Icon } from "../icon";
import React from "react";
import {
SidebarItemRegistration,
sidebarItemsInjectionToken,
} from "../layout/sidebar-items.injectable";
import { computed } from "mobx";
import clusterOverviewRouteInjectable from "../../../common/front-end-routing/routes/cluster/overview/cluster-overview-route.injectable";
import routeIsActiveInjectable from "../../routes/route-is-active.injectable";
import navigateToClusterOverviewInjectable from "../../../common/front-end-routing/routes/cluster/overview/navigate-to-cluster-overview.injectable";
const clusterOverviewSidebarItemsInjectable = getInjectable({
id: "cluster-overview-sidebar-items",
instantiate: (di) => {
const route = di.inject(clusterOverviewRouteInjectable);
const navigateToClusterOverview = di.inject(navigateToClusterOverviewInjectable);
const routeIsActive = di.inject(routeIsActiveInjectable, route);
return computed((): SidebarItemRegistration[] => [
{
id: "cluster-overview",
parentId: null,
title: "Cluster",
getIcon: () => <Icon svg="kube" />,
onClick: navigateToClusterOverview,
isActive: routeIsActive,
isVisible: route.isEnabled,
orderNumber: 10,
},
]);
},
injectionToken: sidebarItemsInjectionToken,
});
export default clusterOverviewSidebarItemsInjectable;
@@ -11,7 +11,7 @@ import { disposeOnUnmount, observer } from "mobx-react";
import { nodesStore } from "../+nodes/nodes.store";
import { podsStore } from "../+workloads-pods/pods.store";
import { Disposer, getHostedClusterId, interval } from "../../utils";
import { TabLayout } from "../layout/tab-layout";
import { TabLayout } from "../layout/tab-layout-2";
import { Spinner } from "../spinner";
import { ClusterIssues } from "./cluster-issues";
import { ClusterMetrics } from "./cluster-metrics";
@@ -1,37 +0,0 @@
/**
* 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,
}),
});
@@ -0,0 +1,37 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import horizontalPodAutoscalersRouteInjectable from "../../../common/front-end-routing/routes/cluster/config/horizontal-pod-autoscalers/horizontal-pod-autoscalers-route.injectable";
import { configSidebarItemId } from "../+config/config-sidebar-items.injectable";
import { SidebarItemRegistration, sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable";
import routeIsActiveInjectable from "../../routes/route-is-active.injectable";
import navigateToHorizontalPodAutoscalersInjectable from "../../../common/front-end-routing/routes/cluster/config/horizontal-pod-autoscalers/navigate-to-horizontal-pod-autoscalers.injectable";
const horizontalPodAutoScalersSidebarItemsInjectable = getInjectable({
id: "horizontal-pod-auto-scalers-sidebar-items",
instantiate: (di) => {
const route = di.inject(horizontalPodAutoscalersRouteInjectable);
const navigateToHorizontalPodAutoscalers = di.inject(navigateToHorizontalPodAutoscalersInjectable);
const routeIsActive = di.inject(routeIsActiveInjectable, route);
return computed((): SidebarItemRegistration[] => [
{
id: "horizontal-pod-auto-scalers",
parentId: configSidebarItemId,
title: "HPA",
onClick: navigateToHorizontalPodAutoscalers,
isActive: routeIsActive,
isVisible: route.isEnabled,
orderNumber: 50,
},
]);
},
injectionToken: sidebarItemsInjectionToken,
});
export default horizontalPodAutoScalersSidebarItemsInjectable;
@@ -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 } from "@ogre-tools/injectable";
import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token";
import horizontalPodAutoscalersRouteInjectable from "../../../common/front-end-routing/routes/cluster/config/horizontal-pod-autoscalers/horizontal-pod-autoscalers-route.injectable";
import { HorizontalPodAutoscalers } from "./hpa";
const horizontalPodAutoscalersRouteComponentInjectable = getInjectable({
id: "horizontal-pod-autoscalers-route-component",
instantiate: (di) => ({
route: di.inject(horizontalPodAutoscalersRouteInjectable),
Component: HorizontalPodAutoscalers,
}),
injectionToken: routeSpecificComponentInjectionToken,
});
export default horizontalPodAutoscalersRouteComponentInjectable;
@@ -7,14 +7,13 @@ import "./hpa.scss";
import React from "react";
import { observer } from "mobx-react";
import type { RouteComponentProps } from "react-router";
import { KubeObjectListLayout } from "../kube-object-list-layout";
import type { HorizontalPodAutoscaler } from "../../../common/k8s-api/endpoints/hpa.api";
import { hpaStore } from "./hpa.store";
import { Badge } from "../badge";
import { cssNames } from "../../utils";
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
import type { HpaRouteParams } from "../../../common/routes";
import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout";
import { KubeObjectAge } from "../kube-object/age";
enum columnId {
@@ -28,11 +27,8 @@ enum columnId {
status = "status",
}
export interface HorizontalPodAutoscalersProps extends RouteComponentProps<HpaRouteParams> {
}
@observer
export class HorizontalPodAutoscalers extends React.Component<HorizontalPodAutoscalersProps> {
export class HorizontalPodAutoscalers extends React.Component {
getTargets(hpa: HorizontalPodAutoscaler) {
const metrics = hpa.getMetrics();
@@ -47,57 +43,59 @@ export class HorizontalPodAutoscalers extends React.Component<HorizontalPodAutos
render() {
return (
<KubeObjectListLayout
isConfigurable
tableId="configuration_hpa"
className="HorizontalPodAutoscalers"
store={hpaStore}
sortingCallbacks={{
[columnId.name]: hpa => hpa.getName(),
[columnId.namespace]: hpa => hpa.getNs(),
[columnId.minPods]: hpa => hpa.getMinPods(),
[columnId.maxPods]: hpa => hpa.getMaxPods(),
[columnId.replicas]: hpa => hpa.getReplicas(),
[columnId.age]: hpa => -hpa.getCreationTimestamp(),
}}
searchFilters={[
hpa => hpa.getSearchFields(),
]}
renderHeaderTitle="Horizontal Pod Autoscalers"
renderTableHeader={[
{ title: "Name", className: "name", sortBy: columnId.name },
{ className: "warning", showWithColumn: columnId.name },
{ title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Metrics", className: "metrics", id: columnId.metrics },
{ title: "Min Pods", className: "min-pods", sortBy: columnId.minPods, id: columnId.minPods },
{ title: "Max Pods", className: "max-pods", sortBy: columnId.maxPods, id: columnId.maxPods },
{ title: "Replicas", className: "replicas", sortBy: columnId.replicas, id: columnId.replicas },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
{ title: "Status", className: "status scrollable", id: columnId.status },
]}
renderTableContents={hpa => [
hpa.getName(),
<KubeObjectStatusIcon key="icon" object={hpa} />,
hpa.getNs(),
this.getTargets(hpa),
hpa.getMinPods(),
hpa.getMaxPods(),
hpa.getReplicas(),
<KubeObjectAge key="age" object={hpa} />,
hpa.getConditions()
.filter(({ isReady }) => isReady)
.map(({ type, tooltip }) => (
<Badge
key={type}
label={type}
tooltip={tooltip}
className={cssNames(type.toLowerCase())}
expandable={false}
scrollable={true}
/>
)),
]}
/>
<SiblingsInTabLayout>
<KubeObjectListLayout
isConfigurable
tableId="configuration_hpa"
className="HorizontalPodAutoscalers"
store={hpaStore}
sortingCallbacks={{
[columnId.name]: hpa => hpa.getName(),
[columnId.namespace]: hpa => hpa.getNs(),
[columnId.minPods]: hpa => hpa.getMinPods(),
[columnId.maxPods]: hpa => hpa.getMaxPods(),
[columnId.replicas]: hpa => hpa.getReplicas(),
[columnId.age]: hpa => -hpa.getCreationTimestamp(),
}}
searchFilters={[
hpa => hpa.getSearchFields(),
]}
renderHeaderTitle="Horizontal Pod Autoscalers"
renderTableHeader={[
{ title: "Name", className: "name", sortBy: columnId.name },
{ className: "warning", showWithColumn: columnId.name },
{ title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Metrics", className: "metrics", id: columnId.metrics },
{ title: "Min Pods", className: "min-pods", sortBy: columnId.minPods, id: columnId.minPods },
{ title: "Max Pods", className: "max-pods", sortBy: columnId.maxPods, id: columnId.maxPods },
{ title: "Replicas", className: "replicas", sortBy: columnId.replicas, id: columnId.replicas },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
{ title: "Status", className: "status scrollable", id: columnId.status },
]}
renderTableContents={hpa => [
hpa.getName(),
<KubeObjectStatusIcon key="icon" object={hpa} />,
hpa.getNs(),
this.getTargets(hpa),
hpa.getMinPods(),
hpa.getMaxPods(),
hpa.getReplicas(),
<KubeObjectAge key="age" object={hpa} />,
hpa.getConditions()
.filter(({ isReady }) => isReady)
.map(({ type, tooltip }) => (
<Badge
key={type}
label={type}
tooltip={tooltip}
className={cssNames(type.toLowerCase())}
expandable={false}
scrollable={true}
/>
)),
]}
/>
</SiblingsInTabLayout>
);
}
}
@@ -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 } from "@ogre-tools/injectable";
import { LimitRanges } from "./limit-ranges";
import limitRangesRouteInjectable from "../../../common/front-end-routing/routes/cluster/config/limit-ranges/limit-ranges-route.injectable";
import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token";
const limitRangesRouteComponentInjectable = getInjectable({
id: "limit-ranges-route-component",
instantiate: (di) => ({
route: di.inject(limitRangesRouteInjectable),
Component: LimitRanges,
}),
injectionToken: routeSpecificComponentInjectionToken,
});
export default limitRangesRouteComponentInjectable;
@@ -0,0 +1,38 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import limitRangesRouteInjectable from "../../../common/front-end-routing/routes/cluster/config/limit-ranges/limit-ranges-route.injectable";
import { configSidebarItemId } from "../+config/config-sidebar-items.injectable";
import { sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable";
import routeIsActiveInjectable from "../../routes/route-is-active.injectable";
import navigateToLimitRangesInjectable from "../../../common/front-end-routing/routes/cluster/config/limit-ranges/navigate-to-limit-ranges.injectable";
const limitRangesSidebarItemsInjectable = getInjectable({
id: "limit-ranges-sidebar-items",
instantiate: (di) => {
const route = di.inject(limitRangesRouteInjectable);
const navigateToLimitRanges = di.inject(navigateToLimitRangesInjectable);
const routeIsActive = di.inject(routeIsActiveInjectable, route);
return computed(() => [
{
id: "limit-ranges",
parentId: configSidebarItemId,
title: "Limit Ranges",
onClick: navigateToLimitRanges,
isActive: routeIsActive,
isVisible: route.isEnabled,
orderNumber: 40,
},
]);
},
injectionToken: sidebarItemsInjectionToken,
});
export default limitRangesSidebarItemsInjectable;
@@ -5,13 +5,12 @@
import "./limit-ranges.scss";
import type { RouteComponentProps } from "react-router";
import { observer } from "mobx-react";
import { KubeObjectListLayout } from "../kube-object-list-layout";
import { limitRangeStore } from "./limit-ranges.store";
import React from "react";
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
import type { LimitRangeRouteParams } from "../../../common/routes";
import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout";
import { KubeObjectAge } from "../kube-object/age";
enum columnId {
@@ -20,41 +19,40 @@ enum columnId {
age = "age",
}
export interface LimitRangesProps extends RouteComponentProps<LimitRangeRouteParams> {
}
@observer
export class LimitRanges extends React.Component<LimitRangesProps> {
export class LimitRanges extends React.Component {
render() {
return (
<KubeObjectListLayout
isConfigurable
tableId="configuration_limitranges"
className="LimitRanges"
store={limitRangeStore}
sortingCallbacks={{
[columnId.name]: limitRange => limitRange.getName(),
[columnId.namespace]: limitRange => limitRange.getNs(),
[columnId.age]: limitRange => -limitRange.getCreationTimestamp(),
}}
searchFilters={[
item => item.getName(),
item => item.getNs(),
]}
renderHeaderTitle="Limit Ranges"
renderTableHeader={[
{ title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning", showWithColumn: columnId.name },
{ title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]}
renderTableContents={limitRange => [
limitRange.getName(),
<KubeObjectStatusIcon key="icon" object={limitRange}/>,
limitRange.getNs(),
<KubeObjectAge key="age" object={limitRange} />,
]}
/>
<SiblingsInTabLayout>
<KubeObjectListLayout
isConfigurable
tableId="configuration_limitranges"
className="LimitRanges"
store={limitRangeStore}
sortingCallbacks={{
[columnId.name]: limitRange => limitRange.getName(),
[columnId.namespace]: limitRange => limitRange.getNs(),
[columnId.age]: limitRange => -limitRange.getCreationTimestamp(),
}}
searchFilters={[
item => item.getName(),
item => item.getNs(),
]}
renderHeaderTitle="Limit Ranges"
renderTableHeader={[
{ title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning", showWithColumn: columnId.name },
{ title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]}
renderTableContents={limitRange => [
limitRange.getName(),
<KubeObjectStatusIcon key="icon" object={limitRange}/>,
limitRange.getNs(),
<KubeObjectAge key="age" object={limitRange} />,
]}
/>
</SiblingsInTabLayout>
);
}
}
@@ -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 } from "@ogre-tools/injectable";
import { ConfigMaps } from "./config-maps";
import configMapsRouteInjectable from "../../../common/front-end-routing/routes/cluster/config/config-maps/config-maps-route.injectable";
import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token";
const configMapsRouteComponentInjectable = getInjectable({
id: "config-maps-route-component",
instantiate: (di) => ({
route: di.inject(configMapsRouteInjectable),
Component: ConfigMaps,
}),
injectionToken: routeSpecificComponentInjectionToken,
});
export default configMapsRouteComponentInjectable;
@@ -0,0 +1,38 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import configMapsRouteInjectable from "../../../common/front-end-routing/routes/cluster/config/config-maps/config-maps-route.injectable";
import { configSidebarItemId } from "../+config/config-sidebar-items.injectable";
import { sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable";
import routeIsActiveInjectable from "../../routes/route-is-active.injectable";
import navigateToConfigMapsInjectable from "../../../common/front-end-routing/routes/cluster/config/config-maps/navigate-to-config-maps.injectable";
const configMapsSidebarItemsInjectable = getInjectable({
id: "config-maps-sidebar-items",
instantiate: (di) => {
const route = di.inject(configMapsRouteInjectable);
const navigateToConfigMaps = di.inject(navigateToConfigMapsInjectable);
const routeIsActive = di.inject(routeIsActiveInjectable, route);
return computed(() => [
{
id: "config-maps",
parentId: configSidebarItemId,
title: "ConfigMaps",
onClick: navigateToConfigMaps,
isActive: routeIsActive,
isVisible: route.isEnabled,
orderNumber: 10,
},
]);
},
injectionToken: sidebarItemsInjectionToken,
});
export default configMapsSidebarItemsInjectable;
@@ -7,11 +7,10 @@ import "./config-maps.scss";
import React from "react";
import { observer } from "mobx-react";
import type { RouteComponentProps } from "react-router";
import { configMapsStore } from "./config-maps.store";
import { KubeObjectListLayout } from "../kube-object-list-layout";
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
import type { ConfigMapsRouteParams } from "../../../common/routes";
import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout";
import { KubeObjectAge } from "../kube-object/age";
enum columnId {
@@ -21,44 +20,43 @@ enum columnId {
age = "age",
}
export interface ConfigMapsProps extends RouteComponentProps<ConfigMapsRouteParams> {
}
@observer
export class ConfigMaps extends React.Component<ConfigMapsProps> {
export class ConfigMaps extends React.Component {
render() {
return (
<KubeObjectListLayout
isConfigurable
tableId="configuration_configmaps"
className="ConfigMaps"
store={configMapsStore}
sortingCallbacks={{
[columnId.name]: configMap => configMap.getName(),
[columnId.namespace]: configMap => configMap.getNs(),
[columnId.keys]: configMap => configMap.getKeys(),
[columnId.age]: configMap => -configMap.getCreationTimestamp(),
}}
searchFilters={[
configMap => configMap.getSearchFields(),
configMap => configMap.getKeys(),
]}
renderHeaderTitle="Config Maps"
renderTableHeader={[
{ title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning", showWithColumn: columnId.name },
{ title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Keys", className: "keys", sortBy: columnId.keys, id: columnId.keys },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]}
renderTableContents={configMap => [
configMap.getName(),
<KubeObjectStatusIcon key="icon" object={configMap}/>,
configMap.getNs(),
configMap.getKeys().join(", "),
<KubeObjectAge key="age" object={configMap} />,
]}
/>
<SiblingsInTabLayout>
<KubeObjectListLayout
isConfigurable
tableId="configuration_configmaps"
className="ConfigMaps"
store={configMapsStore}
sortingCallbacks={{
[columnId.name]: configMap => configMap.getName(),
[columnId.namespace]: configMap => configMap.getNs(),
[columnId.keys]: configMap => configMap.getKeys(),
[columnId.age]: configMap => -configMap.getCreationTimestamp(),
}}
searchFilters={[
configMap => configMap.getSearchFields(),
configMap => configMap.getKeys(),
]}
renderHeaderTitle="Config Maps"
renderTableHeader={[
{ title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning", showWithColumn: columnId.name },
{ title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Keys", className: "keys", sortBy: columnId.keys, id: columnId.keys },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]}
renderTableContents={configMap => [
configMap.getName(),
<KubeObjectStatusIcon key="icon" object={configMap} />,
configMap.getNs(),
configMap.getKeys().join(", "),
<KubeObjectAge key="age" object={configMap} />,
]}
/>
</SiblingsInTabLayout>
);
}
}
@@ -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 } from "@ogre-tools/injectable";
import { PodDisruptionBudgets } from "./pod-disruption-budgets";
import podDisruptionBudgetsRouteInjectable from "../../../common/front-end-routing/routes/cluster/config/pod-disruption-budgets/pod-disruption-budgets-route.injectable";
import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token";
const podDisruptionBudgetsRouteComponentInjectable = getInjectable({
id: "pod-disruption-budgets-route-component",
instantiate: (di) => ({
route: di.inject(podDisruptionBudgetsRouteInjectable),
Component: PodDisruptionBudgets,
}),
injectionToken: routeSpecificComponentInjectionToken,
});
export default podDisruptionBudgetsRouteComponentInjectable;
@@ -0,0 +1,38 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import podDisruptionBudgetsRouteInjectable from "../../../common/front-end-routing/routes/cluster/config/pod-disruption-budgets/pod-disruption-budgets-route.injectable";
import { configSidebarItemId } from "../+config/config-sidebar-items.injectable";
import { sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable";
import routeIsActiveInjectable from "../../routes/route-is-active.injectable";
import navigateToPodDisruptionBudgetsInjectable from "../../../common/front-end-routing/routes/cluster/config/pod-disruption-budgets/navigate-to-pod-disruption-budgets.injectable";
const podDisruptionBudgetsSidebarItemsInjectable = getInjectable({
id: "pod-disruption-budgets-sidebar-items",
instantiate: (di) => {
const route = di.inject(podDisruptionBudgetsRouteInjectable);
const navigateToPodDisruptionBudgets = di.inject(navigateToPodDisruptionBudgetsInjectable);
const routeIsActive = di.inject(routeIsActiveInjectable, route);
return computed(() => [
{
id: "pod-disruption-budgets",
parentId: configSidebarItemId,
title: "Pod Disruption Budgets",
onClick: navigateToPodDisruptionBudgets,
isActive: routeIsActive,
isVisible: route.isEnabled,
orderNumber: 60,
},
]);
},
injectionToken: sidebarItemsInjectionToken,
});
export default podDisruptionBudgetsSidebarItemsInjectable;
@@ -12,6 +12,7 @@ import type { PodDisruptionBudget } from "../../../common/k8s-api/endpoints/podd
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
import type { KubeObjectDetailsProps } from "../kube-object-details";
import { KubeObjectListLayout } from "../kube-object-list-layout";
import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout";
import { KubeObjectAge } from "../kube-object/age";
enum columnId {
@@ -31,45 +32,47 @@ export interface PodDisruptionBudgetsProps extends KubeObjectDetailsProps<PodDis
export class PodDisruptionBudgets extends React.Component<PodDisruptionBudgetsProps> {
render() {
return (
<KubeObjectListLayout
isConfigurable
tableId="configuration_distribution_budgets"
className="PodDisruptionBudgets"
store={podDisruptionBudgetsStore}
sortingCallbacks={{
[columnId.name]: pdb => pdb.getName(),
[columnId.namespace]: pdb => pdb.getNs(),
[columnId.minAvailable]: pdb => pdb.getMinAvailable(),
[columnId.maxUnavailable]: pdb => pdb.getMaxUnavailable(),
[columnId.currentHealthy]: pdb => pdb.getCurrentHealthy(),
[columnId.desiredHealthy]: pdb => pdb.getDesiredHealthy(),
[columnId.age]: pdb => -pdb.getCreationTimestamp(),
}}
searchFilters={[
pdb => pdb.getSearchFields(),
]}
renderHeaderTitle="Pod Disruption Budgets"
renderTableHeader={[
{ title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning", showWithColumn: columnId.name },
{ title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Min Available", className: "min-available", sortBy: columnId.minAvailable, id: columnId.minAvailable },
{ title: "Max Unavailable", className: "max-unavailable", sortBy: columnId.maxUnavailable, id: columnId.maxUnavailable },
{ title: "Current Healthy", className: "current-healthy", sortBy: columnId.currentHealthy, id: columnId.currentHealthy },
{ title: "Desired Healthy", className: "desired-healthy", sortBy: columnId.desiredHealthy, id: columnId.desiredHealthy },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]}
renderTableContents={pdb => [
pdb.getName(),
<KubeObjectStatusIcon key="icon" object={pdb} />,
pdb.getNs(),
pdb.getMinAvailable(),
pdb.getMaxUnavailable(),
pdb.getCurrentHealthy(),
pdb.getDesiredHealthy(),
<KubeObjectAge key="age" object={pdb} />,
]}
/>
<SiblingsInTabLayout>
<KubeObjectListLayout
isConfigurable
tableId="configuration_distribution_budgets"
className="PodDisruptionBudgets"
store={podDisruptionBudgetsStore}
sortingCallbacks={{
[columnId.name]: pdb => pdb.getName(),
[columnId.namespace]: pdb => pdb.getNs(),
[columnId.minAvailable]: pdb => pdb.getMinAvailable(),
[columnId.maxUnavailable]: pdb => pdb.getMaxUnavailable(),
[columnId.currentHealthy]: pdb => pdb.getCurrentHealthy(),
[columnId.desiredHealthy]: pdb => pdb.getDesiredHealthy(),
[columnId.age]: pdb => -pdb.getCreationTimestamp(),
}}
searchFilters={[
pdb => pdb.getSearchFields(),
]}
renderHeaderTitle="Pod Disruption Budgets"
renderTableHeader={[
{ title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning", showWithColumn: columnId.name },
{ title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Min Available", className: "min-available", sortBy: columnId.minAvailable, id: columnId.minAvailable },
{ title: "Max Unavailable", className: "max-unavailable", sortBy: columnId.maxUnavailable, id: columnId.maxUnavailable },
{ title: "Current Healthy", className: "current-healthy", sortBy: columnId.currentHealthy, id: columnId.currentHealthy },
{ title: "Desired Healthy", className: "desired-healthy", sortBy: columnId.desiredHealthy, id: columnId.desiredHealthy },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]}
renderTableContents={pdb => [
pdb.getName(),
<KubeObjectStatusIcon key="icon" object={pdb} />,
pdb.getNs(),
pdb.getMinAvailable(),
pdb.getMaxUnavailable(),
pdb.getCurrentHealthy(),
pdb.getDesiredHealthy(),
<KubeObjectAge key="age" object={pdb} />,
]}
/>
</SiblingsInTabLayout>
);
}
}
@@ -168,6 +168,7 @@ export class AddQuotaDialog extends React.Component<AddQuotaDialogProps> {
<SubTitle title="Namespace" />
<NamespaceSelect
id="namespace-input"
value={this.namespace}
placeholder="Namespace"
themeName="light"
@@ -178,6 +179,7 @@ export class AddQuotaDialog extends React.Component<AddQuotaDialogProps> {
<SubTitle title="Values" />
<div className="flex gaps align-center">
<Select
id="quota-input"
className="quota-select"
themeName="light"
placeholder="Select a quota.."
@@ -0,0 +1,23 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { ResourceQuotas } from "./resource-quotas";
import resourceQuotasRouteInjectable from "../../../common/front-end-routing/routes/cluster/config/resource-quotas/resource-quotas-route.injectable";
import {
routeSpecificComponentInjectionToken,
} from "../../routes/route-specific-component-injection-token";
const resourceQuotasRouteComponentInjectable = getInjectable({
id: "resource-quotas-route-component",
instantiate: (di) => ({
route: di.inject(resourceQuotasRouteInjectable),
Component: ResourceQuotas,
}),
injectionToken: routeSpecificComponentInjectionToken,
});
export default resourceQuotasRouteComponentInjectable;
@@ -0,0 +1,38 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import resourceQuotasRouteInjectable from "../../../common/front-end-routing/routes/cluster/config/resource-quotas/resource-quotas-route.injectable";
import { configSidebarItemId } from "../+config/config-sidebar-items.injectable";
import { sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable";
import routeIsActiveInjectable from "../../routes/route-is-active.injectable";
import navigateToResourceQuotasInjectable from "../../../common/front-end-routing/routes/cluster/config/resource-quotas/navigate-to-resource-quotas.injectable";
const resourceQuotasSidebarItemsInjectable = getInjectable({
id: "resource-quotas-sidebar-items",
instantiate: (di) => {
const route = di.inject(resourceQuotasRouteInjectable);
const navigateToResourceQuotas = di.inject(navigateToResourceQuotasInjectable);
const routeIsActive = di.inject(routeIsActiveInjectable, route);
return computed(() => [
{
id: "resource-quotas",
parentId: configSidebarItemId,
title: "Resource Quotas",
onClick: navigateToResourceQuotas,
isActive: routeIsActive,
isVisible: route.isEnabled,
orderNumber: 30,
},
]);
},
injectionToken: sidebarItemsInjectionToken,
});
export default resourceQuotasSidebarItemsInjectable;
@@ -7,12 +7,11 @@ import "./resource-quotas.scss";
import React from "react";
import { observer } from "mobx-react";
import type { RouteComponentProps } from "react-router";
import { KubeObjectListLayout } from "../kube-object-list-layout";
import { AddQuotaDialog } from "./add-quota-dialog";
import { resourceQuotaStore } from "./resource-quotas.store";
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
import type { ResourceQuotaRouteParams } from "../../../common/routes";
import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout";
import { KubeObjectAge } from "../kube-object/age";
enum columnId {
@@ -21,14 +20,11 @@ enum columnId {
age = "age",
}
export interface ResourceQuotasProps extends RouteComponentProps<ResourceQuotaRouteParams> {
}
@observer
export class ResourceQuotas extends React.Component<ResourceQuotasProps> {
export class ResourceQuotas extends React.Component {
render() {
return (
<>
<SiblingsInTabLayout>
<KubeObjectListLayout
isConfigurable
tableId="configuration_quotas"
@@ -62,7 +58,7 @@ export class ResourceQuotas extends React.Component<ResourceQuotasProps> {
}}
/>
<AddQuotaDialog/>
</>
</SiblingsInTabLayout>
);
}
}
@@ -216,6 +216,7 @@ export class AddSecretDialog extends React.Component<AddSecretDialogProps> {
<div className="secret-namespace">
<SubTitle title="Namespace" />
<NamespaceSelect
id="secret-namespace-input"
themeName="light"
value={namespace}
onChange={({ value }) => this.namespace = value}
@@ -224,6 +225,7 @@ export class AddSecretDialog extends React.Component<AddSecretDialogProps> {
<div className="secret-type">
<SubTitle title="Secret type" />
<Select
id="secret-input"
themeName="light"
options={this.types}
value={type} onChange={({ value }: SelectOption) => this.type = value}
@@ -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 } from "@ogre-tools/injectable";
import { Secrets } from "./secrets";
import secretsRouteInjectable from "../../../common/front-end-routing/routes/cluster/config/secrets/secrets-route.injectable";
import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token";
const secretsRouteComponentInjectable = getInjectable({
id: "secrets-route-component",
instantiate: (di) => ({
route: di.inject(secretsRouteInjectable),
Component: Secrets,
}),
injectionToken: routeSpecificComponentInjectionToken,
});
export default secretsRouteComponentInjectable;
@@ -0,0 +1,38 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import secretsRouteInjectable from "../../../common/front-end-routing/routes/cluster/config/secrets/secrets-route.injectable";
import { configSidebarItemId } from "../+config/config-sidebar-items.injectable";
import { sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable";
import routeIsActiveInjectable from "../../routes/route-is-active.injectable";
import navigateToSecretsInjectable from "../../../common/front-end-routing/routes/cluster/config/secrets/navigate-to-secrets.injectable";
const secretsSidebarItemsInjectable = getInjectable({
id: "secrets-sidebar-items",
instantiate: (di) => {
const route = di.inject(secretsRouteInjectable);
const navigateToSecrets = di.inject(navigateToSecretsInjectable);
const routeIsActive = di.inject(routeIsActiveInjectable, route);
return computed(() => [
{
id: "secrets",
parentId: configSidebarItemId,
title: "Secrets",
onClick: navigateToSecrets,
isActive: routeIsActive,
isVisible: route.isEnabled,
orderNumber: 20,
},
]);
},
injectionToken: sidebarItemsInjectionToken,
});
export default secretsSidebarItemsInjectable;
@@ -7,13 +7,12 @@ import "./secrets.scss";
import React from "react";
import { observer } from "mobx-react";
import type { RouteComponentProps } from "react-router";
import { AddSecretDialog } from "./add-secret-dialog";
import { KubeObjectListLayout } from "../kube-object-list-layout";
import { Badge } from "../badge";
import { secretsStore } from "./secrets.store";
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
import type { SecretsRouteParams } from "../../../common/routes";
import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout";
import { KubeObjectAge } from "../kube-object/age";
enum columnId {
@@ -25,14 +24,11 @@ enum columnId {
age = "age",
}
export interface SecretsProps extends RouteComponentProps<SecretsRouteParams> {
}
@observer
export class Secrets extends React.Component<SecretsProps> {
export class Secrets extends React.Component {
render() {
return (
<>
<SiblingsInTabLayout>
<KubeObjectListLayout
isConfigurable
tableId="configuration_secrets"
@@ -75,7 +71,7 @@ export class Secrets extends React.Component<SecretsProps> {
}}
/>
<AddSecretDialog/>
</>
</SiblingsInTabLayout>
);
}
}
@@ -0,0 +1,35 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { Icon } from "../icon";
import React from "react";
import {
SidebarItemRegistration,
sidebarItemsInjectionToken,
} from "../layout/sidebar-items.injectable";
import { computed } from "mobx";
import { noop } from "lodash/fp";
export const configSidebarItemId = "config";
const configSidebarItemsInjectable = getInjectable({
id: "config-sidebar-items",
instantiate: () =>
computed((): SidebarItemRegistration[] => [
{
id: configSidebarItemId,
parentId: null,
title: "Config",
getIcon: () => <Icon material="list" />,
onClick: noop,
orderNumber: 40,
},
]),
injectionToken: sidebarItemsInjectionToken,
});
export default configSidebarItemsInjectable;
@@ -1,91 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import { HorizontalPodAutoscalers } from "../+config-autoscalers";
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";
interface Dependencies {
isAllowedResource: IsAllowedResource;
}
function getRouteTabs({ isAllowedResource }: Dependencies) {
return computed(() => {
const tabs: TabLayoutRoute[] = [];
if (isAllowedResource("configmaps")) {
tabs.push({
title: "ConfigMaps",
component: ConfigMaps,
url: routes.configMapsURL(),
routePath: routes.configMapsRoute.path.toString(),
});
}
if (isAllowedResource("secrets")) {
tabs.push({
title: "Secrets",
component: Secrets,
url: routes.secretsURL(),
routePath: routes.secretsRoute.path.toString(),
});
}
if (isAllowedResource("resourcequotas")) {
tabs.push({
title: "Resource Quotas",
component: ResourceQuotas,
url: routes.resourceQuotaURL(),
routePath: routes.resourceQuotaRoute.path.toString(),
});
}
if (isAllowedResource("limitranges")) {
tabs.push({
title: "Limit Ranges",
component: LimitRanges,
url: routes.limitRangeURL(),
routePath: routes.limitRangesRoute.path.toString(),
});
}
if (isAllowedResource("horizontalpodautoscalers")) {
tabs.push({
title: "HPA",
component: HorizontalPodAutoscalers,
url: routes.hpaURL(),
routePath: routes.hpaRoute.path.toString(),
});
}
if (isAllowedResource("poddisruptionbudgets")) {
tabs.push({
title: "Pod Disruption Budgets",
component: PodDisruptionBudgets,
url: routes.pdbURL(),
routePath: routes.pdbRoute.path.toString(),
});
}
return tabs;
});
}
const configRoutesInjectable = getInjectable({
id: "config-routes",
instantiate: (di) => getRouteTabs({
isAllowedResource: di.inject(isAllowedResourceInjectable),
}),
});
export default configRoutesInjectable;
-31
View File
@@ -1,31 +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 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,
}),
});
@@ -1,45 +0,0 @@
/**
* 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 { renderTabRoutesSidebarItems } 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"/>}
>
{renderTabRoutesSidebarItems(tabRoutes)}
</SidebarItem>
);
});
export const ConfigSidebarItem = withInjectables<Dependencies, ConfigSidebarItemProps>(NonInjectedConfigSidebarItem, {
getProps: (di, props) => ({
routes: di.inject(configRoutesInjectable),
...props,
}),
});
@@ -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 } from "@ogre-tools/injectable";
import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token";
import crdListRouteInjectable from "../../../common/front-end-routing/routes/cluster/custom-resources/crd-list/crd-list-route.injectable";
import { CustomResourceDefinitions } from "./crd-list";
const crdListRouteComponentInjectable = getInjectable({
id: "crd-list-route-component",
instantiate: (di) => ({
route: di.inject(crdListRouteInjectable),
Component: CustomResourceDefinitions,
}),
injectionToken: routeSpecificComponentInjectionToken,
});
export default crdListRouteComponentInjectable;
@@ -16,6 +16,7 @@ import { Select, SelectOption } from "../select";
import { createPageParam } from "../../navigation";
import { Icon } from "../icon";
import { KubeObjectAge } from "../kube-object/age";
import { TabLayout } from "../layout/tab-layout-2";
export const crdGroupsUrlParam = createPageParam<string[]>({
name: "groups",
@@ -64,81 +65,84 @@ export class CustomResourceDefinitions extends React.Component {
const { items, selectedGroups } = this;
return (
<KubeObjectListLayout
isConfigurable
tableId="crd"
className="CrdList"
store={crdStore}
// Don't subscribe the `crdStore` because <Sidebar> already has and is always mounted
subscribeStores={false}
items={items}
sortingCallbacks={{
[columnId.kind]: crd => crd.getResourceKind(),
[columnId.group]: crd => crd.getGroup(),
[columnId.version]: crd => crd.getVersion(),
[columnId.scope]: crd => crd.getScope(),
[columnId.age]: crd => -crd.getCreationTimestamp(),
}}
searchFilters={[
crd => crd.getResourceKind(),
crd => crd.getGroup(),
crd => crd.getVersion(),
crd => crd.getScope(),
crd => -crd.getCreationTimestamp(),
]}
renderHeaderTitle="Custom Resources"
customizeHeader={({ filters, ...headerPlaceholders }) => {
let placeholder = <>All groups</>;
<TabLayout>
<KubeObjectListLayout
isConfigurable
tableId="crd"
className="CrdList"
store={crdStore}
// Don't subscribe the `crdStore` because <Sidebar> already has and is always mounted
subscribeStores={false}
items={items}
sortingCallbacks={{
[columnId.kind]: crd => crd.getResourceKind(),
[columnId.group]: crd => crd.getGroup(),
[columnId.version]: crd => crd.getVersion(),
[columnId.scope]: crd => crd.getScope(),
[columnId.age]: crd => -crd.getCreationTimestamp(),
}}
searchFilters={[
crd => crd.getResourceKind(),
crd => crd.getGroup(),
crd => crd.getVersion(),
crd => crd.getScope(),
crd => -crd.getCreationTimestamp(),
]}
renderHeaderTitle="Custom Resources"
customizeHeader={({ filters, ...headerPlaceholders }) => {
let placeholder = <>All groups</>;
if (selectedGroups.length == 1) placeholder = <>Group: {selectedGroups[0]}</>;
if (selectedGroups.length >= 2) placeholder = <>Groups: {selectedGroups.join(", ")}</>;
if (selectedGroups.length == 1) placeholder = <>Group: {selectedGroups[0]}</>;
if (selectedGroups.length >= 2) placeholder = <>Groups: {selectedGroups.join(", ")}</>;
return {
// todo: move to global filters
filters: (
<>
{filters}
<Select
className="group-select"
placeholder={placeholder}
options={Object.keys(crdStore.groups)}
onChange={({ value: group }: SelectOption) => this.toggleSelection(group)}
closeMenuOnSelect={false}
controlShouldRenderValue={false}
formatOptionLabel={({ value: group }: SelectOption) => {
const isSelected = selectedGroups.includes(group);
return {
// todo: move to global filters
filters: (
<>
{filters}
<Select
id="crd-input"
className="group-select"
placeholder={placeholder}
options={Object.keys(crdStore.groups)}
onChange={({ value: group }: SelectOption) => this.toggleSelection(group)}
closeMenuOnSelect={false}
controlShouldRenderValue={false}
formatOptionLabel={({ value: group }: SelectOption) => {
const isSelected = selectedGroups.includes(group);
return (
<div className="flex gaps align-center">
<Icon small material="folder"/>
<span>{group}</span>
{isSelected && <Icon small material="check" className="box right"/>}
</div>
);
}}
/>
</>
),
...headerPlaceholders,
};
}}
renderTableHeader={[
{ title: "Resource", className: "kind", sortBy: columnId.kind, id: columnId.kind },
{ title: "Group", className: "group", sortBy: columnId.group, id: columnId.group },
{ title: "Version", className: "version", sortBy: columnId.version, id: columnId.version },
{ title: "Scope", className: "scope", sortBy: columnId.scope, id: columnId.scope },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]}
renderTableContents={crd => [
<Link key="link" to={crd.getResourceUrl()} onClick={stopPropagation}>
{crd.getResourceKind()}
</Link>,
crd.getGroup(),
crd.getVersion(),
crd.getScope(),
<KubeObjectAge key="age" object={crd} />,
]}
/>
return (
<div className="flex gaps align-center">
<Icon small material="folder"/>
<span>{group}</span>
{isSelected && <Icon small material="check" className="box right"/>}
</div>
);
}}
/>
</>
),
...headerPlaceholders,
};
}}
renderTableHeader={[
{ title: "Resource", className: "kind", sortBy: columnId.kind, id: columnId.kind },
{ title: "Group", className: "group", sortBy: columnId.group, id: columnId.group },
{ title: "Version", className: "version", sortBy: columnId.version, id: columnId.version },
{ title: "Scope", className: "scope", sortBy: columnId.scope, id: columnId.scope },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]}
renderTableContents={crd => [
<Link key="link" to={crd.getResourceUrl()} onClick={stopPropagation}>
{crd.getResourceKind()}
</Link>,
crd.getGroup(),
crd.getVersion(),
crd.getScope(),
<KubeObjectAge key="age" object={crd} />,
]}
/>
</TabLayout>
);
}
}
@@ -8,35 +8,36 @@ import "./crd-resources.scss";
import React from "react";
import { value } from "jsonpath";
import { observer } from "mobx-react";
import type { RouteComponentProps } from "react-router";
import { KubeObjectListLayout } from "../kube-object-list-layout";
import { computed, makeObservable } from "mobx";
import { computed, IComputedValue, makeObservable } from "mobx";
import { crdStore } from "./crd.store";
import { apiManager } from "../../../common/k8s-api/api-manager";
import { parseJsonPath } from "../../utils/jsonPath";
import type { CRDRouteParams } from "../../../common/routes";
import { TabLayout } from "../layout/tab-layout-2";
import { withInjectables } from "@ogre-tools/injectable-react";
import customResourcesRouteParametersInjectable from "./custom-resources-route-parameters.injectable";
import { KubeObjectAge } from "../kube-object/age";
export interface CustomResourceDefinitionResourcesProps extends RouteComponentProps<CRDRouteParams> {
}
enum columnId {
name = "name",
namespace = "namespace",
age = "age",
}
interface Dependencies {
group: IComputedValue<string>;
name: IComputedValue<string>;
}
@observer
export class CustomResourceDefinitionResources extends React.Component<CustomResourceDefinitionResourcesProps> {
constructor(props: CustomResourceDefinitionResourcesProps) {
class NonInjectedCrdResources extends React.Component<Dependencies> {
constructor(props: Dependencies) {
super(props);
makeObservable(this);
}
@computed get crd() {
const { group, name } = this.props.match.params;
return crdStore.getByGroup(group, name);
return crdStore.getByGroup(this.props.group.get(), this.props.name.get());
}
@computed get store() {
@@ -55,69 +56,87 @@ export class CustomResourceDefinitionResources extends React.Component<CustomRes
const version = crd.getPreferedVersion();
return (
<KubeObjectListLayout
isConfigurable
key={`crd_resources_${crd.getResourceApiBase()}`}
tableId="crd_resources"
className="CrdResources"
store={store}
sortingCallbacks={{
[columnId.name]: customResource => customResource.getName(),
[columnId.namespace]: customResource => customResource.getNs(),
[columnId.age]: customResource => -customResource.getCreationTimestamp(),
...Object.fromEntries(extraColumns.map(({ name, jsonPath }) => [
name,
customResource => value(customResource, parseJsonPath(jsonPath.slice(1))),
])),
}}
searchFilters={[
customResource => customResource.getSearchFields(),
]}
renderHeaderTitle={crd.getResourceKind()}
customizeHeader={({ searchProps, ...headerPlaceholders }) => ({
searchProps: {
...searchProps,
placeholder: `${crd.getResourceKind()} search ...`,
},
...headerPlaceholders,
})}
renderTableHeader={[
{ title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
isNamespaced && { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
...extraColumns.map(({ name }) => ({
title: name,
className: name.toLowerCase(),
sortBy: name,
id: name,
})),
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]}
renderTableContents={crdInstance => [
crdInstance.getName(),
isNamespaced && crdInstance.getNs(),
...extraColumns.map((column) => {
let rawValue = value(crdInstance, parseJsonPath(column.jsonPath.slice(1)));
<TabLayout>
<KubeObjectListLayout
isConfigurable
key={`crd_resources_${crd.getResourceApiBase()}`}
tableId="crd_resources"
className="CrdResources"
store={store}
sortingCallbacks={{
[columnId.name]: customResource => customResource.getName(),
[columnId.namespace]: customResource => customResource.getNs(),
[columnId.age]: customResource => -customResource.getCreationTimestamp(),
...Object.fromEntries(extraColumns.map(({ name, jsonPath }) => [
name,
customResource => value(customResource, parseJsonPath(jsonPath.slice(1))),
])),
}}
searchFilters={[
customResource => customResource.getSearchFields(),
]}
renderHeaderTitle={crd.getResourceKind()}
customizeHeader={({ searchProps, ...headerPlaceholders }) => ({
searchProps: {
...searchProps,
placeholder: `${crd.getResourceKind()} search ...`,
},
...headerPlaceholders,
})}
renderTableHeader={[
{ title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
isNamespaced && { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
...extraColumns.map(({ name }) => ({
title: name,
className: name.toLowerCase(),
sortBy: name,
id: name,
})),
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]}
renderTableContents={crdInstance => [
crdInstance.getName(),
isNamespaced && crdInstance.getNs(),
...extraColumns.map((column) => {
let rawValue = value(crdInstance, parseJsonPath(column.jsonPath.slice(1)));
if (Array.isArray(rawValue) || typeof rawValue === "object") {
rawValue = JSON.stringify(rawValue);
}
if (Array.isArray(rawValue) || typeof rawValue === "object") {
rawValue = JSON.stringify(rawValue);
}
return {
renderBoolean: true,
children: rawValue,
};
}),
<KubeObjectAge key="age" object={crdInstance} />,
]}
failedToLoadMessage={(
<>
<p>Failed to load {crd.getPluralName()}</p>
{!version.served && (
<p>Prefered version ({crd.getGroup()}/{version.name}) is not served</p>
)}
</>
)}
/>
return {
renderBoolean: true,
children: rawValue,
};
}),
<KubeObjectAge key="age" object={crdInstance} />,
]}
failedToLoadMessage={(
<>
<p>Failed to load {crd.getPluralName()}</p>
{!version.served && (
<p>Prefered version ({crd.getGroup()}/{version.name}) is not served</p>
)}
</>
)}
/>
</TabLayout>
);
}
}
export const CrdResources = withInjectables<Dependencies>(
NonInjectedCrdResources,
{
getProps: (di) => {
const routeParameters = di.inject(customResourcesRouteParametersInjectable);
return {
group: routeParameters.group,
name: routeParameters.name,
};
},
},
);
@@ -0,0 +1,68 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { noop, some } from "lodash/fp";
import { computed } from "mobx";
import {
SidebarItemRegistration,
sidebarItemsInjectionToken,
} from "../layout/sidebar-items.injectable";
import { Icon } from "../icon";
import React from "react";
import crdListRouteInjectable from "../../../common/front-end-routing/routes/cluster/custom-resources/crd-list/crd-list-route.injectable";
import sidebarItemsForDefinitionGroupsInjectable from "./sidebar-items-for-definition-groups.injectable";
import routeIsActiveInjectable from "../../routes/route-is-active.injectable";
import navigateToCrdListInjectable from "../../../common/front-end-routing/routes/cluster/custom-resources/crd-list/navigate-to-crd-list.injectable";
const customResourceSidebarItemsInjectable = getInjectable({
id: "custom-resource-sidebar-items",
instantiate: (di) => {
const navigateToCrdList = di.inject(navigateToCrdListInjectable);
const crdListRoute = di.inject(crdListRouteInjectable);
const crdListRouteIsActive = di.inject(routeIsActiveInjectable, crdListRoute);
const definitionGroupSidebarItems = di.inject(
sidebarItemsForDefinitionGroupsInjectable,
);
return computed((): SidebarItemRegistration[] => {
const definitionsItem = {
id: "definitions",
parentId: "custom-resources",
title: "Definitions",
onClick: navigateToCrdList,
isActive: crdListRouteIsActive,
isVisible: crdListRoute.isEnabled,
orderNumber: 10,
};
const definitionGroupItems = definitionGroupSidebarItems.get();
const childrenAndGrandChildren = [
definitionsItem,
...definitionGroupItems,
];
const parentItem: SidebarItemRegistration = {
id: "custom-resources",
parentId: null,
title: "Custom Resources",
getIcon: () => <Icon material="extension" />,
onClick: noop,
isVisible: computed(() => some(item => item.isVisible.get(), childrenAndGrandChildren)),
orderNumber: 110,
};
return [parentItem, definitionsItem, ...definitionGroupItems];
});
},
injectionToken: sidebarItemsInjectionToken,
});
export default customResourceSidebarItemsInjectable;
@@ -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 } from "@ogre-tools/injectable";
import { CrdResources } from "./crd-resources";
import customResourcesRouteInjectable from "../../../common/front-end-routing/routes/cluster/custom-resources/custom-resources/custom-resources-route.injectable";
import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token";
const customResourcesRouteComponentInjectable = getInjectable({
id: "custom-resources-route-component",
instantiate: (di) => ({
route: di.inject(customResourcesRouteInjectable),
Component: CrdResources,
}),
injectionToken: routeSpecificComponentInjectionToken,
});
export default customResourcesRouteComponentInjectable;
@@ -0,0 +1,24 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import routePathParametersInjectable from "../../routes/route-path-parameters.injectable";
import customResourcesRouteInjectable from "../../../common/front-end-routing/routes/cluster/custom-resources/custom-resources/custom-resources-route.injectable";
const customResourcesRouteParametersInjectable = getInjectable({
id: "custom-resources-route-parameters",
instantiate: (di) => {
const route = di.inject(customResourcesRouteInjectable);
const pathParameters = di.inject(routePathParametersInjectable, route);
return {
group: computed(() => pathParameters.get().group),
name: computed(() => pathParameters.get().name),
};
},
});
export default customResourcesRouteParametersInjectable;
@@ -6,11 +6,23 @@
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import { crdStore } from "./crd.store";
import subscribeStoresInjectable from "../../kube-watch-api/subscribe-stores.injectable";
import currentlyInClusterFrameInjectable from "../../routes/currently-in-cluster-frame.injectable";
const customResourceDefinitionsInjectable = getInjectable({
id: "custom-resource-definitions",
instantiate: () => computed(() => [...crdStore.items]),
instantiate: (di) => {
const currentlyInClusterFrame = di.inject(currentlyInClusterFrameInjectable);
if (currentlyInClusterFrame) {
const subscribeStores = di.inject(subscribeStoresInjectable);
subscribeStores([crdStore]);
}
return computed(() => [...crdStore.items]);
},
});
export default customResourceDefinitionsInjectable;
@@ -1,35 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } 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({
id: "grouped-custom-resource-definitions",
instantiate: (di) => getGroupedCustomResourceDefinitions({
definitions: di.inject(customResourceDefinitionsInjectable),
}),
});
export default groupedCustomResourceDefinitionsInjectable;
@@ -1,61 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } 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 groupedCustomResourceDefinitionsInjectable from "./grouped-custom-resources.injectable";
export interface CustomResourceTabLayoutRoute extends Omit<TabLayoutRoute, "component"> {
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",
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 }}),
subRoutes: definitions.map(crd => ({
id: `crd-resource:${crd.getResourceApiBase()}`,
title: crd.getResourceKind(),
routePath: crd.getResourceUrl(),
})),
});
}
return tabs;
});
}
const customResourcesRouteTabsInjectable = getInjectable({
id: "custom-resources-route-tabs",
instantiate: (di) => getRouteTabs({
customResourcesDefinitions: di.inject(groupedCustomResourceDefinitionsInjectable),
}),
});
export default customResourcesRouteTabsInjectable;
@@ -1,22 +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 { Redirect, Route, Switch } from "react-router";
import { TabLayout } from "../layout/tab-layout";
import { crdDefinitionsRoute, crdResourcesRoute, crdURL } from "../../../common/routes";
import { CustomResourceDefinitions } from "./crd-list";
import { CustomResourceDefinitionResources } from "./crd-resources";
export const CustomResourcesRoute = () => (
<TabLayout>
<Switch>
<Route component={CustomResourceDefinitions} {...crdDefinitionsRoute} exact/>
<Route component={CustomResourceDefinitionResources} {...crdResourcesRoute}/>
<Redirect to={crdURL()}/>
</Switch>
</TabLayout>
);
@@ -1,76 +0,0 @@
/**
* 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,
}),
});
@@ -0,0 +1,81 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import crdListRouteInjectable from "../../../common/front-end-routing/routes/cluster/custom-resources/crd-list/crd-list-route.injectable";
import customResourceDefinitionsInjectable from "./custom-resources.injectable";
import { groupBy, matches, noop, some, toPairs } from "lodash/fp";
import customResourcesRouteInjectable from "../../../common/front-end-routing/routes/cluster/custom-resources/custom-resources/custom-resources-route.injectable";
import currentPathParametersInjectable from "../../routes/current-path-parameters.injectable";
import type { SidebarItemRegistration } from "../layout/sidebar-items.injectable";
import routeIsActiveInjectable from "../../routes/route-is-active.injectable";
import navigateToCustomResourcesInjectable from "../../../common/front-end-routing/routes/cluster/custom-resources/custom-resources/navigate-to-custom-resources.injectable";
const sidebarItemsForDefinitionGroupsInjectable = getInjectable({
id: "sidebar-items-for-definition-groups",
instantiate: (di) => {
const customResourceDefinitions = di.inject(
customResourceDefinitionsInjectable,
);
const crdRoute = di.inject(customResourcesRouteInjectable);
const crdRouteIsActive = di.inject(routeIsActiveInjectable, crdRoute);
const crdListRoute = di.inject(crdListRouteInjectable);
const pathParameters = di.inject(currentPathParametersInjectable);
const navigateToCustomResources = di.inject(navigateToCustomResourcesInjectable);
return computed((): SidebarItemRegistration[] => {
const definitions = customResourceDefinitions.get();
const groupedCrds = toPairs(
groupBy((crd) => crd.getGroup(), definitions),
);
return groupedCrds.flatMap(([group, definitions]) => {
const childItems = definitions.map((crd) => {
const title = crd.getResourceKind();
const crdPathParameters = {
group: crd.getGroup(),
name: crd.getPluralName(),
};
return {
id: `custom-resource-definition-group-${group}-crd-${crd.getId()}`,
parentId: `custom-resource-definition-group-${group}`,
title,
onClick: () => navigateToCustomResources(crdPathParameters),
isActive: computed(
() =>
crdRouteIsActive.get() &&
matches(crdPathParameters, pathParameters.get()),
),
isVisible: crdListRoute.isEnabled,
orderNumber: 10,
};
});
return [
{
id: `custom-resource-definition-group-${group}`,
parentId: "custom-resources",
title: group,
onClick: noop,
isVisible: computed(() => some(item => item.isVisible.get(), childItems)),
orderNumber: 10,
},
...childItems,
];
});
});
},
});
export default sidebarItemsForDefinitionGroupsInjectable;
@@ -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 } from "@ogre-tools/injectable";
import { EntitySettings } from "./entity-settings";
import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token";
import entitySettingsRouteInjectable from "../../../common/front-end-routing/routes/entity-settings/entity-settings-route.injectable";
const entitySettingsRouteComponentInjectable = getInjectable({
id: "entity-settings-route-component",
instantiate: (di) => ({
route: di.inject(entitySettingsRouteInjectable),
Component: EntitySettings,
}),
injectionToken: routeSpecificComponentInjectionToken,
});
export default entitySettingsRouteComponentInjectable;
@@ -0,0 +1,23 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import routePathParametersInjectable from "../../routes/route-path-parameters.injectable";
import entitySettingsRouteInjectable from "../../../common/front-end-routing/routes/entity-settings/entity-settings-route.injectable";
const entitySettingsRouteParametersInjectable = getInjectable({
id: "entity-settings-route-parameters",
instantiate: (di) => {
const route = di.inject(entitySettingsRouteInjectable);
const pathParameters = di.inject(routePathParametersInjectable, route);
return {
entityId: computed(() => pathParameters.get().entityId),
};
},
});
export default entitySettingsRouteParametersInjectable;
@@ -6,28 +6,29 @@
import styles from "./entity-settings.module.scss";
import React from "react";
import { observable, makeObservable } from "mobx";
import type { RouteComponentProps } from "react-router";
import { observable, makeObservable, IComputedValue, computed } from "mobx";
import { observer } from "mobx-react";
import { navigation } from "../../navigation";
import { Tabs, Tab } from "../tabs";
import type { CatalogEntity } from "../../api/catalog-entity";
import { catalogEntityRegistry } from "../../api/catalog-entity-registry";
import { EntitySettingRegistry } from "../../../extensions/registries";
import type { EntitySettingsRouteParams } from "../../../common/routes";
import { groupBy } from "lodash";
import { SettingLayout } from "../layout/setting-layout";
import logger from "../../../common/logger";
import { Avatar } from "../avatar";
import { withInjectables } from "@ogre-tools/injectable-react";
import entitySettingsRouteParametersInjectable from "./entity-settings-route-parameters.injectable";
export interface EntitySettingsProps extends RouteComponentProps<EntitySettingsRouteParams> {
interface Dependencies {
entityId: IComputedValue<string>;
}
@observer
export class EntitySettings extends React.Component<EntitySettingsProps> {
class NonInjectedEntitySettings extends React.Component<Dependencies> {
@observable activeTab: string;
constructor(props: EntitySettingsProps) {
constructor(props: Dependencies) {
super(props);
makeObservable(this);
@@ -43,8 +44,9 @@ export class EntitySettings extends React.Component<EntitySettingsProps> {
}
}
@computed
get entityId() {
return this.props.match.params.entityId;
return this.props.entityId.get();
}
get entity(): CatalogEntity {
@@ -120,7 +122,6 @@ export class EntitySettings extends React.Component<EntitySettingsProps> {
const { activeSetting } = this;
return (
<SettingLayout
navigation={this.renderNavigation()}
@@ -140,3 +141,17 @@ export class EntitySettings extends React.Component<EntitySettingsProps> {
);
}
}
export const EntitySettings = withInjectables<Dependencies>(
NonInjectedEntitySettings,
{
getProps: (di) => {
const routeParameters = di.inject(entitySettingsRouteParametersInjectable);
return {
entityId: routeParameters.entityId,
};
},
},
);
@@ -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 } from "@ogre-tools/injectable";
import { Events } from "./events";
import eventsRouteInjectable from "../../../common/front-end-routing/routes/cluster/events/events-route.injectable";
import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token";
const eventsRouteComponentInjectable = getInjectable({
id: "events-route-component",
instantiate: (di) => ({
route: di.inject(eventsRouteInjectable),
Component: Events,
}),
injectionToken: routeSpecificComponentInjectionToken,
});
export default eventsRouteComponentInjectable;
@@ -0,0 +1,43 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import React from "react";
import {
SidebarItemRegistration,
sidebarItemsInjectionToken,
} from "../layout/sidebar-items.injectable";
import { Icon } from "../icon";
import eventsRouteInjectable from "../../../common/front-end-routing/routes/cluster/events/events-route.injectable";
import routeIsActiveInjectable from "../../routes/route-is-active.injectable";
import navigateToEventsInjectable from "../../../common/front-end-routing/routes/cluster/events/navigate-to-events.injectable";
const eventsSidebarItemsInjectable = getInjectable({
id: "events-sidebar-items",
instantiate: (di) => {
const route = di.inject(eventsRouteInjectable);
const navigateToEvents = di.inject(navigateToEventsInjectable);
const routeIsActive = di.inject(routeIsActiveInjectable, route);
return computed((): SidebarItemRegistration[] => [
{
id: "events",
parentId: null,
getIcon: () => <Icon material="access_time" />,
title: "Events",
onClick: navigateToEvents,
isActive: routeIsActive,
isVisible: route.isEnabled,
orderNumber: 80,
},
]);
},
injectionToken: sidebarItemsInjectionToken,
});
export default eventsSidebarItemsInjectable;
+21 -5
View File
@@ -9,7 +9,7 @@ import React, { Fragment } from "react";
import { computed, observable, makeObservable } from "mobx";
import { observer } from "mobx-react";
import { orderBy } from "lodash";
import { TabLayout } from "../layout/tab-layout";
import { TabLayout } from "../layout/tab-layout-2";
import { EventStore, eventStore } from "./event.store";
import { KubeObjectListLayout, KubeObjectListLayoutProps } from "../kube-object-list-layout";
import type { KubeEvent } from "../../../common/k8s-api/endpoints/events.api";
@@ -19,9 +19,10 @@ import { Tooltip } from "../tooltip";
import { Link } from "react-router-dom";
import { cssNames, IClassName, stopPropagation } from "../../utils";
import { Icon } from "../icon";
import { eventsURL } from "../../../common/routes";
import { getDetailsUrl } from "../kube-detail-params";
import { apiManager } from "../../../common/k8s-api/api-manager";
import { withInjectables } from "@ogre-tools/injectable-react";
import navigateToEventsInjectable from "../../../common/front-end-routing/routes/cluster/events/navigate-to-events.injectable";
import { KubeObjectAge } from "../kube-object/age";
import { ReactiveDuration } from "../duration/reactive-duration";
@@ -46,8 +47,12 @@ const defaultProps: Partial<EventsProps> = {
compactLimit: 10,
};
interface Dependencies {
navigateToEvents: () => void;
}
@observer
export class Events extends React.Component<EventsProps> {
class NonInjectedEvents extends React.Component<Dependencies & EventsProps> {
static defaultProps = defaultProps as object;
@observable sorting: TableSortParams = {
@@ -64,7 +69,7 @@ export class Events extends React.Component<EventsProps> {
[columnId.lastSeen]: event => -new Date(event.lastTimestamp).getTime(),
};
constructor(props: EventsProps) {
constructor(props: Dependencies & EventsProps) {
super(props);
makeObservable(this);
}
@@ -105,7 +110,7 @@ export class Events extends React.Component<EventsProps> {
return {
title,
info: <span> ({visibleItems.length} of <Link to={eventsURL()}>{items.length}</Link>)</span>,
info: <span> ({visibleItems.length} of <a onClick={this.props.navigateToEvents}>{items.length}</a>)</span>,
};
}
@@ -204,3 +209,14 @@ export class Events extends React.Component<EventsProps> {
);
}
}
export const Events = withInjectables<Dependencies, EventsProps>(
NonInjectedEvents,
{
getProps: (di, props) => ({
navigateToEvents: di.inject(navigateToEventsInjectable),
...props,
}),
},
);
@@ -1,37 +0,0 @@
/**
* 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,
}),
});
@@ -20,6 +20,10 @@ import { DiRender, renderFor } from "../../test-utils/renderFor";
import extensionDiscoveryInjectable from "../../../../extensions/extension-discovery/extension-discovery.injectable";
import directoryForUserDataInjectable from "../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import directoryForDownloadsInjectable from "../../../../common/app-paths/directory-for-downloads/directory-for-downloads.injectable";
import getConfigurationFileModelInjectable
from "../../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
import appVersionInjectable
from "../../../../common/get-configuration-file-model/app-version/app-version.injectable";
mockWindow();
@@ -53,6 +57,9 @@ describe("Extensions", () => {
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
di.override(directoryForDownloadsInjectable, () => "some-directory-for-downloads");
di.permitSideEffects(getConfigurationFileModelInjectable);
di.permitSideEffects(appVersionInjectable);
mockFs({
"some-directory-for-user-data": {},
});
@@ -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 } from "@ogre-tools/injectable";
import { Extensions } from "./extensions";
import extensionsRouteInjectable from "../../../common/front-end-routing/routes/extensions/extensions-route.injectable";
import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token";
const extensionsRouteComponentInjectable = getInjectable({
id: "extensions-route-component",
instantiate: (di) => ({
route: di.inject(extensionsRouteInjectable),
Component: Extensions,
}),
injectionToken: routeSpecificComponentInjectionToken,
});
export default extensionsRouteComponentInjectable;
@@ -71,7 +71,7 @@ class NonInjectedExtensions extends React.Component<Dependencies> {
return (
<DropFileInput onDropFiles={this.props.installOnDrop}>
<SettingLayout className="Extensions" contentGaps={false}>
<SettingLayout className="Extensions" contentGaps={false} data-testid="extensions-page">
<section>
<h1>Extensions</h1>
@@ -117,6 +117,7 @@ class NonInjectedHelmChartDetails extends Component<HelmChartDetailsProps & Depe
</div>
<DrawerItem name="Version" className="version" onClick={stopPropagation}>
<Select
id="chart-version-input"
themeName="outlined"
menuPortalTarget={null}
options={chartVersions.map(chart => ({
@@ -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 } from "@ogre-tools/injectable";
import { HelmCharts } from "./helm-charts";
import helmChartsRouteInjectable from "../../../common/front-end-routing/routes/cluster/helm/charts/helm-charts-route.injectable";
import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token";
const helmChartsRouteComponentInjectable = getInjectable({
id: "helm-charts-route-component",
instantiate: (di) => ({
route: di.inject(helmChartsRouteInjectable),
Component: HelmCharts,
}),
injectionToken: routeSpecificComponentInjectionToken,
});
export default helmChartsRouteComponentInjectable;
@@ -0,0 +1,24 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import routePathParametersInjectable from "../../routes/route-path-parameters.injectable";
import helmChartsRouteInjectable from "../../../common/front-end-routing/routes/cluster/helm/charts/helm-charts-route.injectable";
const helmChartsRouteParametersInjectable = getInjectable({
id: "helm-charts-route-parameters",
instantiate: (di) => {
const route = di.inject(helmChartsRouteInjectable);
const pathParameters = di.inject(routePathParametersInjectable, route);
return {
chartName: computed(() => pathParameters.get().chartName),
repo: computed(() => pathParameters.get().repo),
};
},
});
export default helmChartsRouteParametersInjectable;
@@ -0,0 +1,37 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import helmChartsRouteInjectable from "../../../common/front-end-routing/routes/cluster/helm/charts/helm-charts-route.injectable";
import { helmSidebarItemId } from "../+helm/helm-sidebar-items.injectable";
import { sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable";
import routeIsActiveInjectable from "../../routes/route-is-active.injectable";
import navigateToHelmChartsInjectable from "../../../common/front-end-routing/routes/cluster/helm/charts/navigate-to-helm-charts.injectable";
const helmChartsSidebarItemsInjectable = getInjectable({
id: "helm-charts-sidebar-items",
instantiate: (di) => {
const route = di.inject(helmChartsRouteInjectable);
const navigateToHelmCharts = di.inject(navigateToHelmChartsInjectable);
const routeIsActive = di.inject(routeIsActiveInjectable, route);
return computed(() => [
{
id: "charts",
parentId: helmSidebarItemId,
title: "Charts",
onClick: navigateToHelmCharts,
isActive: routeIsActive,
isVisible: route.isEnabled,
orderNumber: 10,
},
]);
},
injectionToken: sidebarItemsInjectionToken,
});
export default helmChartsSidebarItemsInjectable;
@@ -6,15 +6,17 @@
import "./helm-charts.scss";
import React, { Component } from "react";
import type { RouteComponentProps } from "react-router";
import { observer } from "mobx-react";
import { helmChartStore } from "./helm-chart.store";
import type { HelmChart } from "../../../common/k8s-api/endpoints/helm-charts.api";
import { HelmChartDetails } from "./helm-chart-details";
import { navigation } from "../../navigation";
import { ItemListLayout } from "../item-object-list/list-layout";
import { helmChartsURL } from "../../../common/routes";
import type { HelmChartsRouteParams } from "../../../common/routes";
import type { IComputedValue } from "mobx";
import { withInjectables } from "@ogre-tools/injectable-react";
import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout";
import helmChartsRouteParametersInjectable from "./helm-charts-route-parameters.injectable";
import type { NavigateToHelmCharts } from "../../../common/front-end-routing/routes/cluster/helm/charts/navigate-to-helm-charts.injectable";
import navigateToHelmChartsInjectable from "../../../common/front-end-routing/routes/cluster/helm/charts/navigate-to-helm-charts.injectable";
enum columnId {
name = "name",
@@ -24,19 +26,25 @@ enum columnId {
repo = "repo",
}
export interface HelmChartsProps extends RouteComponentProps<HelmChartsRouteParams> {
interface Dependencies {
routeParameters: {
chartName: IComputedValue<string>;
repo: IComputedValue<string>;
};
navigateToHelmCharts: NavigateToHelmCharts;
}
@observer
export class HelmCharts extends Component<HelmChartsProps> {
class NonInjectedHelmCharts extends Component<Dependencies> {
componentDidMount() {
helmChartStore.loadAll();
}
get selectedChart() {
const { match: { params: { chartName, repo }}} = this.props;
const { chartName, repo } = this.props.routeParameters;
return helmChartStore.getByName(chartName, repo);
return helmChartStore.getByName(chartName.get(), repo.get());
}
onDetails = (chart: HelmChart) => {
@@ -49,15 +57,13 @@ export class HelmCharts extends Component<HelmChartsProps> {
showDetails = (chart: HelmChart) => {
if (!chart) {
navigation.push(helmChartsURL());
this.props.navigateToHelmCharts();
}
else {
navigation.push(helmChartsURL({
params: {
chartName: chart.getName(),
repo: chart.getRepository(),
},
}));
this.props.navigateToHelmCharts({
chartName: chart.getName(),
repo: chart.getRepository(),
});
}
};
@@ -67,7 +73,7 @@ export class HelmCharts extends Component<HelmChartsProps> {
render() {
return (
<>
<SiblingsInTabLayout>
<ItemListLayout
isConfigurable
tableId="helm_charts"
@@ -122,7 +128,19 @@ export class HelmCharts extends Component<HelmChartsProps> {
hideDetails={this.hideDetails}
/>
)}
</>
</SiblingsInTabLayout>
);
}
}
export const HelmCharts = withInjectables<Dependencies>(
NonInjectedHelmCharts,
{
getProps: (di) => ({
routeParameters: di.inject(helmChartsRouteParametersInjectable),
navigateToHelmCharts: di.inject(navigateToHelmChartsInjectable),
}),
},
);
@@ -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 } from "@ogre-tools/injectable";
import { HelmReleases } from "./releases";
import helmReleasesRouteInjectable from "../../../common/front-end-routing/routes/cluster/helm/releases/helm-releases-route.injectable";
import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token";
const helmReleasesRouteComponentInjectable = getInjectable({
id: "helm-releases-route-component",
instantiate: (di) => ({
route: di.inject(helmReleasesRouteInjectable),
Component: HelmReleases,
}),
injectionToken: routeSpecificComponentInjectionToken,
});
export default helmReleasesRouteComponentInjectable;
@@ -0,0 +1,24 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import helmReleasesRouteInjectable from "../../../common/front-end-routing/routes/cluster/helm/releases/helm-releases-route.injectable";
import routePathParametersInjectable from "../../routes/route-path-parameters.injectable";
const helmReleasesRouteParametersInjectable = getInjectable({
id: "helm-releases-route-parameters",
instantiate: (di) => {
const route = di.inject(helmReleasesRouteInjectable);
const pathParameters = di.inject(routePathParametersInjectable, route);
return {
namespace: computed(() => pathParameters.get().namespace),
name: computed(() => pathParameters.get().name),
};
},
});
export default helmReleasesRouteParametersInjectable;
@@ -0,0 +1,38 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import helmReleasesRouteInjectable from "../../../common/front-end-routing/routes/cluster/helm/releases/helm-releases-route.injectable";
import { helmSidebarItemId } from "../+helm/helm-sidebar-items.injectable";
import { sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable";
import routeIsActiveInjectable from "../../routes/route-is-active.injectable";
import navigateToHelmReleasesInjectable from "../../../common/front-end-routing/routes/cluster/helm/releases/navigate-to-helm-releases.injectable";
const helmReleasesSidebarItemsInjectable = getInjectable({
id: "helm-releases-sidebar-items",
instantiate: (di) => {
const route = di.inject(helmReleasesRouteInjectable);
const navigateToHelmReleases = di.inject(navigateToHelmReleasesInjectable);
const routeIsActive = di.inject(routeIsActiveInjectable, route);
return computed(() => [
{
id: "releases",
parentId: helmSidebarItemId,
title: "Releases",
onClick: navigateToHelmReleases,
isActive: routeIsActive,
isVisible: route.isEnabled,
orderNumber: 20,
},
]);
},
injectionToken: sidebarItemsInjectionToken,
});
export default helmReleasesSidebarItemsInjectable;
@@ -1,31 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import { matchPath } from "react-router";
import observableHistoryInjectable from "../../../navigation/observable-history.injectable";
import { releaseRoute, ReleaseRouteParams } from "../../../../common/routes";
const releaseRouteParametersInjectable = getInjectable({
id: "release-route-parameters",
instantiate: (di) => {
const observableHistory = di.inject(observableHistoryInjectable);
return computed(() => {
const releasePathParameters = matchPath<ReleaseRouteParams>(observableHistory.location.pathname, {
path: releaseRoute.path,
});
if (!releasePathParameters) {
return {};
}
return releasePathParameters.params;
});
},
});
export default releaseRouteParametersInjectable;
@@ -5,18 +5,19 @@
import { getInjectable } from "@ogre-tools/injectable";
import { matches } from "lodash/fp";
import releasesInjectable from "../releases.injectable";
import releaseRouteParametersInjectable from "./release-route-parameters.injectable";
import { computed } from "mobx";
import helmReleasesRouteParametersInjectable from "../helm-releases-route-parameters.injectable";
const releaseInjectable = getInjectable({
id: "release",
instantiate: (di) => {
const releases = di.inject(releasesInjectable);
const releaseRouteParameters = di.inject(releaseRouteParametersInjectable);
const routeParameters = di.inject(helmReleasesRouteParametersInjectable);
return computed(() => {
const { name, namespace } = releaseRouteParameters.get();
const name = routeParameters.name.get();
const namespace = routeParameters.namespace.get();
if (!name || !namespace) {
return null;
@@ -75,6 +75,7 @@ class NonInjectedReleaseRollbackDialog extends React.Component<ReleaseRollbackDi
<div className="flex gaps align-center">
<b>Revision</b>
<Select
id="revision-input"
themeName="light"
value={revision}
options={revisions}
@@ -7,11 +7,7 @@ import "../item-object-list/item-list-layout.scss";
import "./releases.scss";
import React, { Component } from "react";
import type { RouteComponentProps } from "react-router";
import type { HelmRelease } from "../../../common/k8s-api/endpoints/helm-releases.api";
import { navigation } from "../../navigation";
import type { ReleaseRouteParams } from "../../../common/routes";
import { releaseURL } from "../../../common/routes";
import { withInjectables } from "@ogre-tools/injectable-react";
import namespaceStoreInjectable from "../+namespaces/namespace-store/namespace-store.injectable";
import { ItemListLayout } from "../item-object-list";
@@ -25,6 +21,10 @@ import removableReleasesInjectable from "./removable-releases.injectable";
import type { RemovableHelmRelease } from "./removable-releases";
import type { IComputedValue } from "mobx";
import releasesInjectable from "./releases.injectable";
import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout";
import helmReleasesRouteParametersInjectable from "./helm-releases-route-parameters.injectable";
import type { NavigateToHelmReleases } from "../../../common/front-end-routing/routes/cluster/helm/releases/navigate-to-helm-releases.injectable";
import navigateToHelmReleasesInjectable from "../../../common/front-end-routing/routes/cluster/helm/releases/navigate-to-helm-releases.injectable";
enum columnId {
name = "name",
@@ -37,18 +37,18 @@ enum columnId {
updated = "update",
}
export interface HelmReleasesProps extends RouteComponentProps<ReleaseRouteParams> {
}
interface Dependencies {
releases: IComputedValue<RemovableHelmRelease[]>;
releasesArePending: IComputedValue<boolean>;
selectNamespace: (namespace: string) => void;
namespace: IComputedValue<string>;
navigateToHelmReleases: NavigateToHelmReleases;
}
class NonInjectedHelmReleases extends Component<Dependencies & HelmReleasesProps> {
class NonInjectedHelmReleases extends Component<Dependencies> {
// TODO: This side-effect in mount must go.
componentDidMount() {
const { match: { params: { namespace }}} = this.props;
const namespace = this.props.namespace.get();
if (namespace) {
this.props.selectNamespace(namespace);
@@ -60,16 +60,14 @@ class NonInjectedHelmReleases extends Component<Dependencies & HelmReleasesProps
};
showDetails = (item: HelmRelease) => {
navigation.push(releaseURL({
params: {
name: item.getName(),
namespace: item.getNs(),
},
}));
this.props.navigateToHelmReleases({
name: item.getName(),
namespace: item.getNs(),
});
};
hideDetails = () => {
navigation.push(releaseURL());
this.props.navigateToHelmReleases();
};
renderRemoveDialogMessage(selectedItems: HelmRelease[]) {
@@ -144,7 +142,7 @@ class NonInjectedHelmReleases extends Component<Dependencies & HelmReleasesProps
} as ItemStore<RemovableHelmRelease>;
return (
<>
<SiblingsInTabLayout>
<ItemListLayout
store={legacyReleaseStore}
getItems={() => legacyReleaseStore.items}
@@ -171,7 +169,7 @@ class NonInjectedHelmReleases extends Component<Dependencies & HelmReleasesProps
filters: (
<>
{filters}
<NamespaceSelectFilter />
<NamespaceSelectFilter id="namespace-select-filter" />
</>
),
searchProps: {
@@ -218,20 +216,25 @@ class NonInjectedHelmReleases extends Component<Dependencies & HelmReleasesProps
/>
<ReleaseRollbackDialog/>
</>
</SiblingsInTabLayout>
);
}
}
export const HelmReleases = withInjectables<Dependencies, HelmReleasesProps>(
export const HelmReleases = withInjectables<Dependencies>(
NonInjectedHelmReleases,
{
getProps: (di, props) => ({
releases: di.inject(removableReleasesInjectable),
releasesArePending: di.inject(releasesInjectable).pending,
selectNamespace: di.inject(namespaceStoreInjectable).selectNamespaces,
...props,
}),
getProps: (di) => {
const routeParameters = di.inject(helmReleasesRouteParametersInjectable);
return {
releases: di.inject(removableReleasesInjectable),
releasesArePending: di.inject(releasesInjectable).pending,
selectNamespace: di.inject(namespaceStoreInjectable).selectNamespaces,
navigateToHelmReleases: di.inject(navigateToHelmReleasesInjectable),
namespace: routeParameters.namespace,
};
},
},
);
@@ -0,0 +1,35 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import React from "react";
import {
SidebarItemRegistration,
sidebarItemsInjectionToken,
} from "../layout/sidebar-items.injectable";
import { Icon } from "../icon";
import { noop } from "lodash/fp";
export const helmSidebarItemId = "helm";
const helmSidebarItemsInjectable = getInjectable({
id: "helm-sidebar-items",
instantiate: () =>
computed((): SidebarItemRegistration[] => [
{
id: helmSidebarItemId,
parentId: null,
getIcon: () => <Icon svg="helm" />,
title: "Helm",
onClick: noop,
orderNumber: 90,
},
]),
injectionToken: sidebarItemsInjectionToken,
});
export default helmSidebarItemsInjectable;
@@ -1,34 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } 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({
id: "helm-routes",
instantiate: () => getRouteTabs(),
});
export default helmRoutesInjectable;
-31
View File
@@ -1,31 +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 { 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,
}),
});
@@ -1,44 +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 { 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 { renderTabRoutesSidebarItems } 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 svg="helm" />}
>
{renderTabRoutesSidebarItems(tabRoutes)}
</SidebarItem>
);
});
export const HelmSidebarItem = withInjectables<Dependencies, HelmSidebarItemProps>(NonInjectedHelmSidebarItem, {
getProps: (di, props) => ({
routes: di.inject(networkRouteTabsInjectable),
...props,
}),
});
@@ -30,6 +30,7 @@ const NonInjectedNamespaceSelectFilter = observer(({ model }: SelectProps & Depe
onClick={model.onClick}
>
<NamespaceSelect
id="namespace-select-filter-input"
isMulti={true}
menuIsOpen={model.menuIsOpen}
components={{ Placeholder }}
@@ -77,6 +77,7 @@ class NonInjectedNamespaceSelect extends React.Component<NamespaceSelectProps &
return (
<Select
id="namespace-input"
className={cssNames("NamespaceSelect", className)}
menuClass="NamespaceSelectMenu"
formatOptionLabel={this.formatOptionLabel}
@@ -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 } from "@ogre-tools/injectable";
import { NamespacesRoute } from "./route";
import namespacesRouteInjectable from "../../../common/front-end-routing/routes/cluster/namespaces/namespaces-route.injectable";
import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token";
const namespacesRouteComponentInjectable = getInjectable({
id: "namespaces-route-component",
instantiate: (di) => ({
route: di.inject(namespacesRouteInjectable),
Component: NamespacesRoute,
}),
injectionToken: routeSpecificComponentInjectionToken,
});
export default namespacesRouteComponentInjectable;
@@ -0,0 +1,42 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import {
SidebarItemRegistration,
sidebarItemsInjectionToken,
} from "../layout/sidebar-items.injectable";
import { Icon } from "../icon";
import React from "react";
import namespacesRouteInjectable from "../../../common/front-end-routing/routes/cluster/namespaces/namespaces-route.injectable";
import routeIsActiveInjectable from "../../routes/route-is-active.injectable";
import navigateToNamespacesInjectable from "../../../common/front-end-routing/routes/cluster/namespaces/navigate-to-namespaces.injectable";
const namespacesSidebarItemsInjectable = getInjectable({
id: "namespaces",
instantiate: (di) => {
const route = di.inject(namespacesRouteInjectable);
const navigateToNamespaces = di.inject(navigateToNamespacesInjectable);
const routeIsActive = di.inject(routeIsActiveInjectable, route);
return computed((): SidebarItemRegistration[] => [
{
id: "namespaces",
parentId: null,
getIcon: () => <Icon material="layers" />,
title: "Namespaces",
onClick: navigateToNamespaces,
isActive: routeIsActive,
isVisible: route.isEnabled,
orderNumber: 70,
},
]);
},
injectionToken: sidebarItemsInjectionToken,
});
export default namespacesSidebarItemsInjectable;
+5 -12
View File
@@ -8,17 +8,14 @@ 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 { TabLayout } from "../layout/tab-layout-2";
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";
import addNamespaceDialogModelInjectable from "./add-namespace-dialog-model/add-namespace-dialog-model.injectable";
import { KubeObjectAge } from "../kube-object/age";
enum columnId {
@@ -28,15 +25,12 @@ enum columnId {
status = "status",
}
export interface NamespacesRouteProps extends RouteComponentProps<NamespacesRouteParams> {
}
interface Dependencies {
namespaceStore: NamespaceStore;
openAddNamespaceDialog: () => void;
}
export const NonInjectedNamespacesRoute = ({ namespaceStore, openAddNamespaceDialog }: Dependencies & NamespacesRouteProps) => (
export const NonInjectedNamespacesRoute = ({ namespaceStore, openAddNamespaceDialog }: Dependencies) => (
<TabLayout>
<KubeObjectListLayout
isConfigurable
@@ -81,10 +75,9 @@ export const NonInjectedNamespacesRoute = ({ namespaceStore, openAddNamespaceDia
);
export const NamespacesRoute = withInjectables<Dependencies, NamespacesRouteProps>(NonInjectedNamespacesRoute, {
getProps: (di, props) => ({
export const NamespacesRoute = withInjectables<Dependencies>(NonInjectedNamespacesRoute, {
getProps: (di) => ({
namespaceStore: di.inject(namespaceStoreInjectable),
openAddNamespaceDialog: di.inject(addNamespaceDialogModelInjectable).open,
...props,
}),
});
@@ -1,37 +0,0 @@
/**
* 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,
}),
});
@@ -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 } from "@ogre-tools/injectable";
import { Endpoints } from "./endpoints";
import endpointsRouteInjectable from "../../../common/front-end-routing/routes/cluster/network/endpoints/endpoints-route.injectable";
import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token";
const endpointsRouteComponentInjectable = getInjectable({
id: "endpoints-route-component",
instantiate: (di) => ({
route: di.inject(endpointsRouteInjectable),
Component: Endpoints,
}),
injectionToken: routeSpecificComponentInjectionToken,
});
export default endpointsRouteComponentInjectable;
@@ -0,0 +1,37 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import endpointsRouteInjectable from "../../../common/front-end-routing/routes/cluster/network/endpoints/endpoints-route.injectable";
import { networkSidebarItemId } from "../+network/network-sidebar-items.injectable";
import { sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable";
import routeIsActiveInjectable from "../../routes/route-is-active.injectable";
import navigateToEndpointsInjectable from "../../../common/front-end-routing/routes/cluster/network/endpoints/navigate-to-endpoints.injectable";
const endpointsSidebarItemsInjectable = getInjectable({
id: "endpoints-sidebar-items",
instantiate: (di) => {
const route = di.inject(endpointsRouteInjectable);
const navigateToEndpoints = di.inject(navigateToEndpointsInjectable);
const routeIsActive = di.inject(routeIsActiveInjectable, route);
return computed(() => [
{
id: "endpoints",
parentId: networkSidebarItemId,
title: "Endpoints",
onClick: navigateToEndpoints,
isActive: routeIsActive,
isVisible: route.isEnabled,
orderNumber: 20,
},
]);
},
injectionToken: sidebarItemsInjectionToken,
});
export default endpointsSidebarItemsInjectable;
@@ -7,11 +7,10 @@ import "./endpoints.scss";
import React from "react";
import { observer } from "mobx-react";
import type { RouteComponentProps } from "react-router-dom";
import { endpointStore } from "./endpoints.store";
import { KubeObjectListLayout } from "../kube-object-list-layout";
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
import type { EndpointRouteParams } from "../../../common/routes";
import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout";
import { KubeObjectAge } from "../kube-object/age";
enum columnId {
@@ -21,48 +20,47 @@ enum columnId {
age = "age",
}
export interface EndpointsProps extends RouteComponentProps<EndpointRouteParams> {
}
@observer
export class Endpoints extends React.Component<EndpointsProps> {
export class Endpoints extends React.Component {
render() {
return (
<KubeObjectListLayout
isConfigurable
tableId="network_endpoints"
className="Endpoints" store={endpointStore}
sortingCallbacks={{
[columnId.name]: endpoint => endpoint.getName(),
[columnId.namespace]: endpoint => endpoint.getNs(),
[columnId.age]: endpoint => -endpoint.getCreationTimestamp(),
}}
searchFilters={[
endpoint => endpoint.getSearchFields(),
]}
renderHeaderTitle="Endpoints"
renderTableHeader={[
{ title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning", showWithColumn: columnId.name },
{ title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Endpoints", className: "endpoints", id: columnId.endpoints },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]}
renderTableContents={endpoint => [
endpoint.getName(),
<KubeObjectStatusIcon key="icon" object={endpoint} />,
endpoint.getNs(),
endpoint.toString(),
<KubeObjectAge key="age" object={endpoint} />,
]}
tableProps={{
customRowHeights: (item, lineHeight, paddings) => {
const lines = item.getEndpointSubsets().length || 1;
<SiblingsInTabLayout>
<KubeObjectListLayout
isConfigurable
tableId="network_endpoints"
className="Endpoints" store={endpointStore}
sortingCallbacks={{
[columnId.name]: endpoint => endpoint.getName(),
[columnId.namespace]: endpoint => endpoint.getNs(),
[columnId.age]: endpoint => -endpoint.getCreationTimestamp(),
}}
searchFilters={[
endpoint => endpoint.getSearchFields(),
]}
renderHeaderTitle="Endpoints"
renderTableHeader={[
{ title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning", showWithColumn: columnId.name },
{ title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Endpoints", className: "endpoints", id: columnId.endpoints },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]}
renderTableContents={endpoint => [
endpoint.getName(),
<KubeObjectStatusIcon key="icon" object={endpoint} />,
endpoint.getNs(),
endpoint.toString(),
<KubeObjectAge key="age" object={endpoint} />,
]}
tableProps={{
customRowHeights: (item, lineHeight, paddings) => {
const lines = item.getEndpointSubsets().length || 1;
return lines * lineHeight + paddings;
},
}}
/>
return lines * lineHeight + paddings;
},
}}
/>
</SiblingsInTabLayout>
);
}
}
@@ -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 } from "@ogre-tools/injectable";
import { Ingresses } from "./ingresses";
import ingressesRouteInjectable from "../../../common/front-end-routing/routes/cluster/network/ingresses/ingresses-route.injectable";
import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token";
const ingressesRouteComponentInjectable = getInjectable({
id: "ingresses-route-component",
instantiate: (di) => ({
route: di.inject(ingressesRouteInjectable),
Component: Ingresses,
}),
injectionToken: routeSpecificComponentInjectionToken,
});
export default ingressesRouteComponentInjectable;
@@ -0,0 +1,38 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import ingressesRouteInjectable from "../../../common/front-end-routing/routes/cluster/network/ingresses/ingresses-route.injectable";
import { sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable";
import { networkSidebarItemId } from "../+network/network-sidebar-items.injectable";
import routeIsActiveInjectable from "../../routes/route-is-active.injectable";
import navigateToIngressesInjectable from "../../../common/front-end-routing/routes/cluster/network/ingresses/navigate-to-ingresses.injectable";
const ingressesSidebarItemsInjectable = getInjectable({
id: "ingresses-sidebar-items",
instantiate: (di) => {
const route = di.inject(ingressesRouteInjectable);
const navigateToIngresses = di.inject(navigateToIngressesInjectable);
const routeIsActive = di.inject(routeIsActiveInjectable, route);
return computed(() => [
{
id: "ingresses",
parentId: networkSidebarItemId,
title: "Ingresses",
onClick: navigateToIngresses,
isActive: routeIsActive,
isVisible: route.isEnabled,
orderNumber: 30,
},
]);
},
injectionToken: sidebarItemsInjectionToken,
});
export default ingressesSidebarItemsInjectable;
@@ -7,11 +7,10 @@ import "./ingresses.scss";
import React from "react";
import { observer } from "mobx-react";
import type { RouteComponentProps } from "react-router-dom";
import { ingressStore } from "./ingress.store";
import { KubeObjectListLayout } from "../kube-object-list-layout";
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
import type { IngressRouteParams } from "../../../common/routes";
import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout";
import { KubeObjectAge } from "../kube-object/age";
import { computeRouteDeclarations } from "../../../common/k8s-api/endpoints";
@@ -23,59 +22,65 @@ enum columnId {
age = "age",
}
export interface IngressesProps extends RouteComponentProps<IngressRouteParams> {
}
@observer
export class Ingresses extends React.Component<IngressesProps> {
export class Ingresses extends React.Component {
render() {
return (
<KubeObjectListLayout
isConfigurable
tableId="network_ingresses"
className="Ingresses" store={ingressStore}
sortingCallbacks={{
[columnId.name]: ingress => ingress.getName(),
[columnId.namespace]: ingress => ingress.getNs(),
[columnId.age]: ingress => -ingress.getCreationTimestamp(),
}}
searchFilters={[
ingress => ingress.getSearchFields(),
ingress => ingress.getPorts(),
]}
renderHeaderTitle="Ingresses"
renderTableHeader={[
{ title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning", showWithColumn: columnId.name },
{ title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "LoadBalancers", className: "loadbalancers", id: columnId.loadBalancers },
{ title: "Rules", className: "rules", id: columnId.rules },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]}
renderTableContents={ingress => [
ingress.getName(),
<KubeObjectStatusIcon key="icon" object={ingress} />,
ingress.getNs(),
ingress.getLoadBalancers().map(lb => <p key={lb}>{lb}</p>),
computeRouteDeclarations(ingress).map(decl => (
decl.displayAsLink
? (
<span key={decl.url}>
<a
href={decl.url}
rel="noreferrer"
target="_blank"
onClick={e => e.stopPropagation()}
>
{decl.url}
</a> ⇢ {decl.service}
</span>
)
: <span key={decl.url}>{decl.url} ⇢ {decl.service}</span>
)),
<KubeObjectAge key="age" object={ingress} />,
]}
/>
<SiblingsInTabLayout>
<KubeObjectListLayout
isConfigurable
tableId="network_ingresses"
className="Ingresses" store={ingressStore}
sortingCallbacks={{
[columnId.name]: ingress => ingress.getName(),
[columnId.namespace]: ingress => ingress.getNs(),
[columnId.age]: ingress => -ingress.getCreationTimestamp(),
}}
searchFilters={[
ingress => ingress.getSearchFields(),
ingress => ingress.getPorts(),
]}
renderHeaderTitle="Ingresses"
renderTableHeader={[
{ title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning", showWithColumn: columnId.name },
{ title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "LoadBalancers", className: "loadbalancers", id: columnId.loadBalancers },
{ title: "Rules", className: "rules", id: columnId.rules },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]}
renderTableContents={ingress => [
ingress.getName(),
<KubeObjectStatusIcon key="icon" object={ingress} />,
ingress.getNs(),
ingress.getLoadBalancers().map(lb => <p key={lb}>{lb}</p>),
computeRouteDeclarations(ingress).map(decl => (
decl.displayAsLink
? (
<span key={decl.url}>
<a
href={decl.url}
rel="noreferrer"
target="_blank"
onClick={e => e.stopPropagation()}
>
{decl.url}
</a> ⇢ {decl.service}
</span>
)
: <span key={decl.url}>{decl.url} ⇢ {decl.service}</span>
)),
<KubeObjectAge key="age" object={ingress} />,
]}
tableProps={{
customRowHeights: (item, lineHeight, paddings) => {
const lines = item.getRoutes().length || 1;
return lines * lineHeight + paddings;
},
}}
/>
</SiblingsInTabLayout>
);
}
}
@@ -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 } from "@ogre-tools/injectable";
import { NetworkPolicies } from "./network-policies";
import networkPoliciesRouteInjectable from "../../../common/front-end-routing/routes/cluster/network/network-policies/network-policies-route.injectable";
import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token";
const networkPoliciesRouteComponentInjectable = getInjectable({
id: "network-policies-route-component",
instantiate: (di) => ({
route: di.inject(networkPoliciesRouteInjectable),
Component: NetworkPolicies,
}),
injectionToken: routeSpecificComponentInjectionToken,
});
export default networkPoliciesRouteComponentInjectable;
@@ -0,0 +1,38 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import networkPoliciesRouteInjectable from "../../../common/front-end-routing/routes/cluster/network/network-policies/network-policies-route.injectable";
import { networkSidebarItemId } from "../+network/network-sidebar-items.injectable";
import { sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable";
import routeIsActiveInjectable from "../../routes/route-is-active.injectable";
import navigateToNetworkPoliciesInjectable from "../../../common/front-end-routing/routes/cluster/network/network-policies/navigate-to-network-policies.injectable";
const networkPoliciesSidebarItemsInjectable = getInjectable({
id: "network-policies-sidebar-items",
instantiate: (di) => {
const route = di.inject(networkPoliciesRouteInjectable);
const navigateToNetworkPolicies = di.inject(navigateToNetworkPoliciesInjectable);
const routeIsActive = di.inject(routeIsActiveInjectable, route);
return computed(() => [
{
id: "network-policies",
parentId: networkSidebarItemId,
title: "Network Policies",
onClick: navigateToNetworkPolicies,
isActive: routeIsActive,
isVisible: route.isEnabled,
orderNumber: 40,
},
]);
},
injectionToken: sidebarItemsInjectionToken,
});
export default networkPoliciesSidebarItemsInjectable;
@@ -7,11 +7,10 @@ import "./network-policies.scss";
import React from "react";
import { observer } from "mobx-react";
import type { RouteComponentProps } from "react-router-dom";
import { KubeObjectListLayout } from "../kube-object-list-layout";
import { networkPolicyStore } from "./network-policy.store";
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
import type { NetworkPoliciesRouteParams } from "../../../common/routes";
import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout";
import { KubeObjectAge } from "../kube-object/age";
enum columnId {
@@ -21,42 +20,41 @@ enum columnId {
age = "age",
}
export interface NetworkPoliciesProps extends RouteComponentProps<NetworkPoliciesRouteParams> {
}
@observer
export class NetworkPolicies extends React.Component<NetworkPoliciesProps> {
export class NetworkPolicies extends React.Component {
render() {
return (
<KubeObjectListLayout
isConfigurable
tableId="network_policies"
className="NetworkPolicies"
store={networkPolicyStore}
sortingCallbacks={{
[columnId.name]: networkPolicy => networkPolicy.getName(),
[columnId.namespace]: networkPolicy => networkPolicy.getNs(),
[columnId.age]: networkPolicy => -networkPolicy.getCreationTimestamp(),
}}
searchFilters={[
networkPolicy => networkPolicy.getSearchFields(),
]}
renderHeaderTitle="Network Policies"
renderTableHeader={[
{ title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning", showWithColumn: columnId.name },
{ title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Policy Types", className: "type", id: columnId.types },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]}
renderTableContents={networkPolicy => [
networkPolicy.getName(),
<KubeObjectStatusIcon key="icon" object={networkPolicy} />,
networkPolicy.getNs(),
networkPolicy.getTypes().join(", "),
<KubeObjectAge key="age" object={networkPolicy} />,
]}
/>
<SiblingsInTabLayout>
<KubeObjectListLayout
isConfigurable
tableId="network_policies"
className="NetworkPolicies"
store={networkPolicyStore}
sortingCallbacks={{
[columnId.name]: networkPolicy => networkPolicy.getName(),
[columnId.namespace]: networkPolicy => networkPolicy.getNs(),
[columnId.age]: networkPolicy => -networkPolicy.getCreationTimestamp(),
}}
searchFilters={[
networkPolicy => networkPolicy.getSearchFields(),
]}
renderHeaderTitle="Network Policies"
renderTableHeader={[
{ title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning", showWithColumn: columnId.name },
{ title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Policy Types", className: "type", id: columnId.types },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]}
renderTableContents={networkPolicy => [
networkPolicy.getName(),
<KubeObjectStatusIcon key="icon" object={networkPolicy} />,
networkPolicy.getNs(),
networkPolicy.getTypes().join(", "),
<KubeObjectAge key="age" object={networkPolicy} />,
]}
/>
</SiblingsInTabLayout>
);
}
}
@@ -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 } from "@ogre-tools/injectable";
import { PortForwards } from "./port-forwards";
import portForwardsRouteInjectable from "../../../common/front-end-routing/routes/cluster/network/port-forwards/port-forwards-route.injectable";
import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token";
const portForwardsRouteComponentInjectable = getInjectable({
id: "port-forwards-route-component",
instantiate: (di) => ({
route: di.inject(portForwardsRouteInjectable),
Component: PortForwards,
}),
injectionToken: routeSpecificComponentInjectionToken,
});
export default portForwardsRouteComponentInjectable;
@@ -0,0 +1,23 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import routePathParametersInjectable from "../../routes/route-path-parameters.injectable";
import portForwardsRouteInjectable from "../../../common/front-end-routing/routes/cluster/network/port-forwards/port-forwards-route.injectable";
const portForwardsRouteParametersInjectable = getInjectable({
id: "port-forwards-route-parameters",
instantiate: (di) => {
const route = di.inject(portForwardsRouteInjectable);
const pathParameters = di.inject(routePathParametersInjectable, route);
return {
forwardport: computed(() => pathParameters.get().forwardport),
};
},
});
export default portForwardsRouteParametersInjectable;
@@ -0,0 +1,39 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import portForwardsRouteInjectable from "../../../common/front-end-routing/routes/cluster/network/port-forwards/port-forwards-route.injectable";
import { networkSidebarItemId } from "../+network/network-sidebar-items.injectable";
import { sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable";
import routeIsActiveInjectable from "../../routes/route-is-active.injectable";
import navigateToPortForwardsInjectable from "../../../common/front-end-routing/routes/cluster/network/port-forwards/navigate-to-port-forwards.injectable";
const portForwardsSidebarItemsInjectable = getInjectable({
id: "port-forwards-sidebar-items",
instantiate: (di) => {
const route = di.inject(portForwardsRouteInjectable);
const navigateToPortForwards = di.inject(navigateToPortForwardsInjectable);
const routeIsActive = di.inject(routeIsActiveInjectable, route);
return computed(() => [
{
id: "port-forwards",
parentId: networkSidebarItemId,
title: "Port Forwarding",
onClick: navigateToPortForwards,
isActive: routeIsActive,
isVisible: route.isEnabled,
orderNumber: 50,
},
]);
},
injectionToken: sidebarItemsInjectionToken,
});
export default portForwardsSidebarItemsInjectable;
@@ -7,15 +7,16 @@ import "./port-forwards.scss";
import React from "react";
import { disposeOnUnmount, observer } from "mobx-react";
import type { RouteComponentProps } from "react-router-dom";
import { ItemListLayout } from "../item-object-list/list-layout";
import type { PortForwardItem, PortForwardStore } from "../../port-forward";
import { PortForwardMenu } from "./port-forward-menu";
import { PortForwardsRouteParams, portForwardsURL } from "../../../common/routes";
import { PortForwardDetails } from "./port-forward-details";
import { navigation } from "../../navigation";
import { withInjectables } from "@ogre-tools/injectable-react";
import portForwardStoreInjectable from "../../port-forward/port-forward-store/port-forward-store.injectable";
import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout";
import { computed, IComputedValue, makeObservable } from "mobx";
import portForwardsRouteParametersInjectable from "./port-forwards-route-parameters.injectable";
import navigateToPortForwardsInjectable, { NavigateToPortForwards } from "../../../common/front-end-routing/routes/cluster/network/port-forwards/navigate-to-port-forwards.injectable";
enum columnId {
name = "name",
@@ -27,26 +28,30 @@ enum columnId {
status = "status",
}
export interface PortForwardsProps extends RouteComponentProps<PortForwardsRouteParams> {
}
interface Dependencies {
portForwardStore: PortForwardStore;
forwardport: IComputedValue<string>;
navigateToPortForwards: NavigateToPortForwards;
}
@observer
class NonInjectedPortForwards extends React.Component<PortForwardsProps & Dependencies> {
class NonInjectedPortForwards extends React.Component<Dependencies> {
constructor(props: Dependencies) {
super(props);
makeObservable(this);
}
componentDidMount() {
disposeOnUnmount(this, [
this.props.portForwardStore.watch(),
]);
}
@computed
get selectedPortForward() {
const { match: { params: { forwardport }}} = this.props;
return this.props.portForwardStore.getById(forwardport);
return this.props.portForwardStore.getById(this.props.forwardport.get());
}
onDetails = (item: PortForwardItem) => {
@@ -58,15 +63,13 @@ class NonInjectedPortForwards extends React.Component<PortForwardsProps & Depend
};
showDetails = (item: PortForwardItem) => {
navigation.push(portForwardsURL({
params: {
forwardport: item.getId(),
},
}));
this.props.navigateToPortForwards({
forwardport: item.getId(),
});
};
hideDetails = () => {
navigation.push(portForwardsURL());
this.props.navigateToPortForwards();
};
renderRemoveDialogMessage(selectedItems: PortForwardItem[]) {
@@ -82,7 +85,7 @@ class NonInjectedPortForwards extends React.Component<PortForwardsProps & Depend
render() {
return (
<>
<SiblingsInTabLayout>
<ItemListLayout
isConfigurable
tableId="port_forwards"
@@ -138,19 +141,24 @@ class NonInjectedPortForwards extends React.Component<PortForwardsProps & Depend
hideDetails={this.hideDetails}
/>
)}
</>
</SiblingsInTabLayout>
);
}
}
export const PortForwards = withInjectables<Dependencies, PortForwardsProps>(
export const PortForwards = withInjectables<Dependencies>(
NonInjectedPortForwards,
{
getProps: (di, props) => ({
portForwardStore: di.inject(portForwardStoreInjectable),
...props,
}),
getProps: (di) => {
const routeParameters = di.inject(portForwardsRouteParametersInjectable);
return {
portForwardStore: di.inject(portForwardStoreInjectable),
forwardport: routeParameters.forwardport,
navigateToPortForwards: di.inject(navigateToPortForwardsInjectable),
};
},
},
);
Loaded 100 of 583 files, more files were not shown because too many files have changed in this diff. Show more