mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Make navigating to Helm Charts not blow up
Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>
This commit is contained in:
parent
203418ac37
commit
823cab8170
@ -0,0 +1,454 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`helm-charts - navigation to Helm charts when navigating to Helm charts renders 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="flex flex-col"
|
||||||
|
data-testid="cluster-sidebar"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class=""
|
||||||
|
style="width: 40px; height: 40px;"
|
||||||
|
>
|
||||||
|
??
|
||||||
|
</div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="undefined sidebar-active-status"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="SidebarItem"
|
||||||
|
data-id-test="workloads"
|
||||||
|
data-is-active-test="false"
|
||||||
|
data-test-id="workloads"
|
||||||
|
data-testid="sidebar-item"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="nav-item flex gaps align-center expandable"
|
||||||
|
data-testid="sidebar-item-link-for-workloads"
|
||||||
|
href="/"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="Icon svg focusable"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
/>
|
||||||
|
</i>
|
||||||
|
<span
|
||||||
|
class="link-text box grow"
|
||||||
|
>
|
||||||
|
Workloads
|
||||||
|
</span>
|
||||||
|
<i
|
||||||
|
class="Icon expand-icon box right material focusable"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="keyboard_arrow_down"
|
||||||
|
>
|
||||||
|
keyboard_arrow_down
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="SidebarItem"
|
||||||
|
data-id-test="config"
|
||||||
|
data-is-active-test="false"
|
||||||
|
data-test-id="config"
|
||||||
|
data-testid="sidebar-item"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="nav-item flex gaps align-center"
|
||||||
|
data-testid="sidebar-item-link-for-config"
|
||||||
|
href="/"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="Icon material focusable"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="list"
|
||||||
|
>
|
||||||
|
list
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
<span
|
||||||
|
class="link-text box grow"
|
||||||
|
>
|
||||||
|
Config
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="SidebarItem"
|
||||||
|
data-id-test="network"
|
||||||
|
data-is-active-test="false"
|
||||||
|
data-test-id="network"
|
||||||
|
data-testid="sidebar-item"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="nav-item flex gaps align-center expandable"
|
||||||
|
data-testid="sidebar-item-link-for-network"
|
||||||
|
href="/"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="Icon material focusable"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="device_hub"
|
||||||
|
>
|
||||||
|
device_hub
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
<span
|
||||||
|
class="link-text box grow"
|
||||||
|
>
|
||||||
|
Network
|
||||||
|
</span>
|
||||||
|
<i
|
||||||
|
class="Icon expand-icon box right material focusable"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="keyboard_arrow_down"
|
||||||
|
>
|
||||||
|
keyboard_arrow_down
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="SidebarItem"
|
||||||
|
data-id-test="storage"
|
||||||
|
data-is-active-test="false"
|
||||||
|
data-test-id="storage"
|
||||||
|
data-testid="sidebar-item"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="nav-item flex gaps align-center"
|
||||||
|
data-testid="sidebar-item-link-for-storage"
|
||||||
|
href="/"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="Icon material focusable"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="storage"
|
||||||
|
>
|
||||||
|
storage
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
<span
|
||||||
|
class="link-text box grow"
|
||||||
|
>
|
||||||
|
Storage
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="SidebarItem"
|
||||||
|
data-id-test="helm"
|
||||||
|
data-is-active-test="true"
|
||||||
|
data-test-id="helm"
|
||||||
|
data-testid="sidebar-item"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
aria-current="page"
|
||||||
|
class="nav-item flex gaps align-center expandable active"
|
||||||
|
data-testid="sidebar-item-link-for-helm"
|
||||||
|
href="/"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="Icon svg focusable"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
/>
|
||||||
|
</i>
|
||||||
|
<span
|
||||||
|
class="link-text box grow"
|
||||||
|
>
|
||||||
|
Helm
|
||||||
|
</span>
|
||||||
|
<i
|
||||||
|
class="Icon expand-icon box right material focusable"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="keyboard_arrow_down"
|
||||||
|
>
|
||||||
|
keyboard_arrow_down
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="SidebarItem"
|
||||||
|
data-id-test="user-management"
|
||||||
|
data-is-active-test="false"
|
||||||
|
data-test-id="user-management"
|
||||||
|
data-testid="sidebar-item"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="nav-item flex gaps align-center"
|
||||||
|
data-testid="sidebar-item-link-for-user-management"
|
||||||
|
href="/"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="Icon material focusable"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="security"
|
||||||
|
>
|
||||||
|
security
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
<span
|
||||||
|
class="link-text box grow"
|
||||||
|
>
|
||||||
|
Access Control
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="SidebarItem"
|
||||||
|
data-id-test="custom-resources"
|
||||||
|
data-is-active-test="false"
|
||||||
|
data-test-id="custom-resources"
|
||||||
|
data-testid="sidebar-item"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="nav-item flex gaps align-center expandable"
|
||||||
|
data-testid="sidebar-item-link-for-custom-resources"
|
||||||
|
href="/"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="Icon material focusable"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="extension"
|
||||||
|
>
|
||||||
|
extension
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
<span
|
||||||
|
class="link-text box grow"
|
||||||
|
>
|
||||||
|
Custom Resources
|
||||||
|
</span>
|
||||||
|
<i
|
||||||
|
class="Icon expand-icon box right material focusable"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="keyboard_arrow_down"
|
||||||
|
>
|
||||||
|
keyboard_arrow_down
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="TabLayout"
|
||||||
|
data-testid="tab-layout"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="Tabs center scrollable"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="Tab flex gaps align-center active"
|
||||||
|
data-is-active-test="true"
|
||||||
|
data-testid="tab-link-for-charts"
|
||||||
|
role="tab"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="label"
|
||||||
|
>
|
||||||
|
Charts
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="Tab flex gaps align-center"
|
||||||
|
data-is-active-test="false"
|
||||||
|
data-testid="tab-link-for-releases"
|
||||||
|
role="tab"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="label"
|
||||||
|
>
|
||||||
|
Releases
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<main>
|
||||||
|
<div
|
||||||
|
data-testid="page-for-helm-charts"
|
||||||
|
style="display: none;"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="ItemListLayout flex column HelmCharts"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="header flex gaps align-center"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="Input SearchInput focused"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="input-area flex gaps align-center"
|
||||||
|
id=""
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="input box grow"
|
||||||
|
placeholder="Search Helm Charts..."
|
||||||
|
spellcheck="false"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<i
|
||||||
|
class="Icon material focusable small"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="search"
|
||||||
|
>
|
||||||
|
search
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="input-info flex gaps"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="items box grow flex column"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="Table flex column HelmCharts box grow some-active-theme-type selectable scrollable sortable autoSize virtual"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="TableHead sticky nowrap topLine"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="TableCell icon nowrap"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="content"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="TableCell name nowrap sorting"
|
||||||
|
id="name"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="content"
|
||||||
|
>
|
||||||
|
Name
|
||||||
|
</div>
|
||||||
|
<i
|
||||||
|
class="Icon sortIcon material focusable"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="arrow_drop_down"
|
||||||
|
>
|
||||||
|
arrow_drop_down
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="TableCell description nowrap"
|
||||||
|
id="description"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="content"
|
||||||
|
>
|
||||||
|
Description
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="TableCell version nowrap"
|
||||||
|
id="version"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="content"
|
||||||
|
>
|
||||||
|
Version
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="TableCell app-version nowrap"
|
||||||
|
id="app-version"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="content"
|
||||||
|
>
|
||||||
|
App Version
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="TableCell repository nowrap sorting"
|
||||||
|
id="repo"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="content"
|
||||||
|
>
|
||||||
|
Repository
|
||||||
|
</div>
|
||||||
|
<i
|
||||||
|
class="Icon sortIcon material focusable"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="arrow_drop_down"
|
||||||
|
>
|
||||||
|
arrow_drop_down
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="TableCell menu nowrap"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="content"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="Icon material interactive focusable"
|
||||||
|
id="menu_actions_15"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="more_vert"
|
||||||
|
>
|
||||||
|
more_vert
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="Spinner singleColor center"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="AddRemoveButtons flex gaps"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
51
src/behaviours/helm-charts/navigation-to-helm-charts.test.ts
Normal file
51
src/behaviours/helm-charts/navigation-to-helm-charts.test.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import type { RenderResult } from "@testing-library/react";
|
||||||
|
import {
|
||||||
|
ApplicationBuilder,
|
||||||
|
getApplicationBuilder,
|
||||||
|
} from "../../renderer/components/test-utils/get-application-builder";
|
||||||
|
|
||||||
|
describe("helm-charts - navigation to Helm charts", () => {
|
||||||
|
let applicationBuilder: ApplicationBuilder;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
applicationBuilder = getApplicationBuilder();
|
||||||
|
|
||||||
|
// applicationBuilder.beforeSetups(({ rendererDi }) => {
|
||||||
|
// const userStoreStub = {
|
||||||
|
// extensionRegistryUrl: { customUrl: "some-custom-url" },
|
||||||
|
// } as unknown as UserStore;
|
||||||
|
//
|
||||||
|
// rendererDi.override(userStoreInjectable, () => userStoreStub);
|
||||||
|
//
|
||||||
|
// const themeStoreStub = ({ themeOptions: [] }) as unknown as ThemeStore;
|
||||||
|
//
|
||||||
|
// rendererDi.override(themeStoreInjectable, () => themeStoreStub);
|
||||||
|
// });
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when navigating to Helm charts", () => {
|
||||||
|
let rendered: RenderResult;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
applicationBuilder.setEnvironmentToClusterFrame();
|
||||||
|
|
||||||
|
rendered = await applicationBuilder.render();
|
||||||
|
|
||||||
|
applicationBuilder.helmCharts.navigate();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
expect(rendered.container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows page for Helm charts", () => {
|
||||||
|
const page = rendered.getByTestId("page-for-helm-charts");
|
||||||
|
|
||||||
|
expect(page).not.toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -66,6 +66,8 @@ import getElectronAppPathInjectable from "./app-paths/get-electron-app-path/get-
|
|||||||
import setElectronAppPathInjectable from "./app-paths/set-electron-app-path/set-electron-app-path.injectable";
|
import setElectronAppPathInjectable from "./app-paths/set-electron-app-path/set-electron-app-path.injectable";
|
||||||
import setupMainWindowVisibilityAfterActivationInjectable from "./electron-app/after-application-is-ready/setup-main-window-visibility-after-activation.injectable";
|
import setupMainWindowVisibilityAfterActivationInjectable from "./electron-app/after-application-is-ready/setup-main-window-visibility-after-activation.injectable";
|
||||||
import setupDeviceShutdownInjectable from "./electron-app/after-application-is-ready/setup-device-shutdown.injectable";
|
import setupDeviceShutdownInjectable from "./electron-app/after-application-is-ready/setup-device-shutdown.injectable";
|
||||||
|
import setupApplicationNameInjectable from "./electron-app/before-application-is-ready/setup-application-name.injectable";
|
||||||
|
import setupRunnablesBeforeClosingOfApplicationInjectable from "./electron-app/before-application-is-ready/setup-runnables-before-closing-of-application.injectable";
|
||||||
|
|
||||||
export const getDiForUnitTesting = (
|
export const getDiForUnitTesting = (
|
||||||
{ doGeneralOverrides } = { doGeneralOverrides: false },
|
{ doGeneralOverrides } = { doGeneralOverrides: false },
|
||||||
@ -195,6 +197,8 @@ const overrideElectronFeatures = (di: DiContainer) => {
|
|||||||
|
|
||||||
di.override(setupDeepLinkingInjectable, () => ({ run: () => {} }));
|
di.override(setupDeepLinkingInjectable, () => ({ run: () => {} }));
|
||||||
di.override(exitAppInjectable, () => () => {});
|
di.override(exitAppInjectable, () => () => {});
|
||||||
|
di.override(setupApplicationNameInjectable, () => ({ run: () => {} }));
|
||||||
|
di.override(setupRunnablesBeforeClosingOfApplicationInjectable, () => ({ run: () => {} }));
|
||||||
di.override(getCommandLineSwitchInjectable, () => () => "irrelevant");
|
di.override(getCommandLineSwitchInjectable, () => () => "irrelevant");
|
||||||
di.override(requestSingleInstanceLockInjectable, () => () => true);
|
di.override(requestSingleInstanceLockInjectable, () => () => true);
|
||||||
di.override(disableHardwareAccelerationInjectable, () => () => {});
|
di.override(disableHardwareAccelerationInjectable, () => () => {});
|
||||||
|
|||||||
@ -17,14 +17,11 @@ describe("Helm Service tests", () => {
|
|||||||
|
|
||||||
it("list charts with deprecated entries", async () => {
|
it("list charts with deprecated entries", async () => {
|
||||||
mockHelmRepoManager.mockReturnValue({
|
mockHelmRepoManager.mockReturnValue({
|
||||||
init: jest.fn(),
|
repositories: jest.fn().mockImplementation(async () => [
|
||||||
repositories: jest.fn().mockImplementation(async () => {
|
{ name: "stable", url: "stableurl" },
|
||||||
return [
|
{ name: "experiment", url: "experimenturl" },
|
||||||
{ name: "stable", url: "stableurl" },
|
]),
|
||||||
{ name: "experiment", url: "experimenturl" },
|
} as any);
|
||||||
];
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const charts = await helmService.listCharts();
|
const charts = await helmService.listCharts();
|
||||||
|
|
||||||
@ -127,13 +124,12 @@ describe("Helm Service tests", () => {
|
|||||||
|
|
||||||
it("list charts sorted by version in descending order", async () => {
|
it("list charts sorted by version in descending order", async () => {
|
||||||
mockHelmRepoManager.mockReturnValue({
|
mockHelmRepoManager.mockReturnValue({
|
||||||
init: jest.fn(),
|
|
||||||
repositories: jest.fn().mockImplementation(async () => {
|
repositories: jest.fn().mockImplementation(async () => {
|
||||||
return [
|
return [
|
||||||
{ name: "bitnami", url: "bitnamiurl" },
|
{ name: "bitnami", url: "bitnamiurl" },
|
||||||
];
|
];
|
||||||
}),
|
}),
|
||||||
});
|
} as any);
|
||||||
|
|
||||||
const charts = await helmService.listCharts();
|
const charts = await helmService.listCharts();
|
||||||
|
|
||||||
|
|||||||
@ -3,16 +3,161 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { HelmRepoManager } from "./helm-repo-manager";
|
|
||||||
|
import yaml from "js-yaml";
|
||||||
|
import { readFile } from "fs-extra";
|
||||||
|
import { customRequestPromise } from "../../common/request";
|
||||||
|
import orderBy from "lodash/orderBy";
|
||||||
|
import logger from "../logger";
|
||||||
|
import { execHelm } from "./exec";
|
||||||
|
import type { HelmEnv, HelmRepo, HelmRepoConfig } from "./helm-repo-manager";
|
||||||
|
|
||||||
|
class HelmRepoManager {
|
||||||
|
protected repos: HelmRepo[];
|
||||||
|
protected helmEnv: HelmEnv;
|
||||||
|
protected didUpdateOnce: boolean;
|
||||||
|
|
||||||
|
public async loadAvailableRepos(): Promise<HelmRepo[]> {
|
||||||
|
const res = await customRequestPromise({
|
||||||
|
uri: "https://github.com/lensapp/artifact-hub-repositories/releases/download/latest/repositories.json",
|
||||||
|
json: true,
|
||||||
|
resolveWithFullResponse: true,
|
||||||
|
timeout: 10000,
|
||||||
|
});
|
||||||
|
|
||||||
|
return orderBy(res.body as HelmRepo[], (repo) => repo.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ensureInitialized() {
|
||||||
|
this.helmEnv ??= await this.parseHelmEnv();
|
||||||
|
|
||||||
|
const repos = await this.list();
|
||||||
|
|
||||||
|
if (repos.length === 0) {
|
||||||
|
await this.addRepo({
|
||||||
|
name: "bitnami",
|
||||||
|
url: "https://charts.bitnami.com/bitnami",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.didUpdateOnce) {
|
||||||
|
await this.update();
|
||||||
|
this.didUpdateOnce = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async parseHelmEnv() {
|
||||||
|
const output = await execHelm(["env"]);
|
||||||
|
const lines = output.split(/\r?\n/); // split by new line feed
|
||||||
|
const env: HelmEnv = {};
|
||||||
|
|
||||||
|
lines.forEach((line: string) => {
|
||||||
|
const [key, value] = line.split("=");
|
||||||
|
|
||||||
|
if (key && value) {
|
||||||
|
env[key] = value.replace(/"/g, ""); // strip quotas
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return env;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async repo(name: string): Promise<HelmRepo> {
|
||||||
|
const repos = await this.repositories();
|
||||||
|
|
||||||
|
return repos.find((repo) => repo.name === name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async list(): Promise<HelmRepo[]> {
|
||||||
|
try {
|
||||||
|
const rawConfig = await readFile(
|
||||||
|
this.helmEnv.HELM_REPOSITORY_CONFIG,
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
const parsedConfig = yaml.load(rawConfig) as HelmRepoConfig;
|
||||||
|
|
||||||
|
if (typeof parsedConfig === "object" && parsedConfig) {
|
||||||
|
return parsedConfig.repositories;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore error
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async repositories(): Promise<HelmRepo[]> {
|
||||||
|
try {
|
||||||
|
await this.ensureInitialized();
|
||||||
|
|
||||||
|
const repos = await this.list();
|
||||||
|
|
||||||
|
return repos.map((repo) => ({
|
||||||
|
...repo,
|
||||||
|
cacheFilePath: `${this.helmEnv.HELM_REPOSITORY_CACHE}/${repo.name}-index.yaml`,
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`[HELM]: repositories listing error`, error);
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async update() {
|
||||||
|
return execHelm(["repo", "update"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async addRepo({
|
||||||
|
name,
|
||||||
|
url,
|
||||||
|
insecureSkipTlsVerify,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
caFile,
|
||||||
|
keyFile,
|
||||||
|
certFile,
|
||||||
|
}: HelmRepo) {
|
||||||
|
logger.info(`[HELM]: adding repo ${name} from ${url}`);
|
||||||
|
const args = ["repo", "add", name, url];
|
||||||
|
|
||||||
|
if (insecureSkipTlsVerify) {
|
||||||
|
args.push("--insecure-skip-tls-verify");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (username) {
|
||||||
|
args.push("--username", username);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password) {
|
||||||
|
args.push("--password", password);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (caFile) {
|
||||||
|
args.push("--ca-file", caFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyFile) {
|
||||||
|
args.push("--key-file", keyFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (certFile) {
|
||||||
|
args.push("--cert-file", certFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
return execHelm(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async removeRepo({ name, url }: HelmRepo): Promise<string> {
|
||||||
|
logger.info(`[HELM]: removing repo ${name} (${url})`);
|
||||||
|
|
||||||
|
return execHelm(["repo", "remove", name]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const helmRepoManagerInjectable = getInjectable({
|
const helmRepoManagerInjectable = getInjectable({
|
||||||
id: "helm-repo-manager",
|
id: "helm-repo-manager",
|
||||||
|
|
||||||
instantiate: () => {
|
instantiate: () => new HelmRepoManager(),
|
||||||
HelmRepoManager.resetInstance();
|
|
||||||
|
|
||||||
return HelmRepoManager.createInstance();
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default helmRepoManagerInjectable;
|
export default helmRepoManagerInjectable;
|
||||||
|
|||||||
@ -3,13 +3,11 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import yaml from "js-yaml";
|
import {
|
||||||
import { readFile } from "fs-extra";
|
asLegacyGlobalSingletonForExtensionApi,
|
||||||
import { Singleton } from "../../common/utils/singleton";
|
} from "../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-singleton-object-for-extension-api";
|
||||||
import { customRequestPromise } from "../../common/request";
|
|
||||||
import orderBy from "lodash/orderBy";
|
import helmRepoManagerInjectable from "./helm-repo-manager.injectable";
|
||||||
import logger from "../logger";
|
|
||||||
import { execHelm } from "./exec";
|
|
||||||
|
|
||||||
export type HelmEnv = Record<string, string> & {
|
export type HelmEnv = Record<string, string> & {
|
||||||
HELM_REPOSITORY_CACHE?: string;
|
HELM_REPOSITORY_CACHE?: string;
|
||||||
@ -32,144 +30,4 @@ export interface HelmRepo {
|
|||||||
password?: string;
|
password?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class HelmRepoManager extends Singleton {
|
export const HelmRepoManager = asLegacyGlobalSingletonForExtensionApi(helmRepoManagerInjectable);
|
||||||
protected repos: HelmRepo[];
|
|
||||||
protected helmEnv: HelmEnv;
|
|
||||||
protected didUpdateOnce: boolean;
|
|
||||||
|
|
||||||
public async loadAvailableRepos(): Promise<HelmRepo[]> {
|
|
||||||
const res = await customRequestPromise({
|
|
||||||
uri: "https://github.com/lensapp/artifact-hub-repositories/releases/download/latest/repositories.json",
|
|
||||||
json: true,
|
|
||||||
resolveWithFullResponse: true,
|
|
||||||
timeout: 10000,
|
|
||||||
});
|
|
||||||
|
|
||||||
return orderBy(res.body as HelmRepo[], repo => repo.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async ensureInitialized() {
|
|
||||||
this.helmEnv ??= await this.parseHelmEnv();
|
|
||||||
|
|
||||||
const repos = await this.list();
|
|
||||||
|
|
||||||
if (repos.length === 0) {
|
|
||||||
await this.addRepo({
|
|
||||||
name: "bitnami",
|
|
||||||
url: "https://charts.bitnami.com/bitnami",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.didUpdateOnce) {
|
|
||||||
await this.update();
|
|
||||||
this.didUpdateOnce = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async parseHelmEnv() {
|
|
||||||
const output = await execHelm(["env"]);
|
|
||||||
const lines = output.split(/\r?\n/); // split by new line feed
|
|
||||||
const env: HelmEnv = {};
|
|
||||||
|
|
||||||
lines.forEach((line: string) => {
|
|
||||||
const [key, value] = line.split("=");
|
|
||||||
|
|
||||||
if (key && value) {
|
|
||||||
env[key] = value.replace(/"/g, ""); // strip quotas
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return env;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async repo(name: string): Promise<HelmRepo> {
|
|
||||||
const repos = await this.repositories();
|
|
||||||
|
|
||||||
return repos.find(repo => repo.name === name);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async list(): Promise<HelmRepo[]> {
|
|
||||||
try {
|
|
||||||
const rawConfig = await readFile(this.helmEnv.HELM_REPOSITORY_CONFIG, "utf8");
|
|
||||||
const parsedConfig = yaml.load(rawConfig) as HelmRepoConfig;
|
|
||||||
|
|
||||||
if (typeof parsedConfig === "object" && parsedConfig) {
|
|
||||||
return parsedConfig.repositories;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// ignore error
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public async repositories(): Promise<HelmRepo[]> {
|
|
||||||
try {
|
|
||||||
await this.ensureInitialized();
|
|
||||||
|
|
||||||
const repos = await this.list();
|
|
||||||
|
|
||||||
return repos.map(repo => ({
|
|
||||||
...repo,
|
|
||||||
cacheFilePath: `${this.helmEnv.HELM_REPOSITORY_CACHE}/${repo.name}-index.yaml`,
|
|
||||||
}));
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`[HELM]: repositories listing error`, error);
|
|
||||||
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async update() {
|
|
||||||
return execHelm([
|
|
||||||
"repo",
|
|
||||||
"update",
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async addRepo({ name, url, insecureSkipTlsVerify, username, password, caFile, keyFile, certFile }: HelmRepo) {
|
|
||||||
logger.info(`[HELM]: adding repo ${name} from ${url}`);
|
|
||||||
const args = [
|
|
||||||
"repo",
|
|
||||||
"add",
|
|
||||||
name,
|
|
||||||
url,
|
|
||||||
];
|
|
||||||
|
|
||||||
if (insecureSkipTlsVerify) {
|
|
||||||
args.push("--insecure-skip-tls-verify");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (username) {
|
|
||||||
args.push("--username", username);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (password) {
|
|
||||||
args.push("--password", password);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (caFile) {
|
|
||||||
args.push("--ca-file", caFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keyFile) {
|
|
||||||
args.push("--key-file", keyFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (certFile) {
|
|
||||||
args.push("--cert-file", certFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
return execHelm(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async removeRepo({ name, url }: HelmRepo): Promise<string> {
|
|
||||||
logger.info(`[HELM]: removing repo ${name} (${url})`);
|
|
||||||
|
|
||||||
return execHelm([
|
|
||||||
"repo",
|
|
||||||
"remove",
|
|
||||||
name,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -74,6 +74,8 @@ class NonInjectedHelmCharts extends Component<Dependencies> {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<SiblingsInTabLayout>
|
<SiblingsInTabLayout>
|
||||||
|
<div data-testid="page-for-helm-charts" style={{ display: "none" }}/>
|
||||||
|
|
||||||
<ItemListLayout
|
<ItemListLayout
|
||||||
isConfigurable
|
isConfigurable
|
||||||
tableId="helm_charts"
|
tableId="helm_charts"
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import { Spinner } from "../../spinner";
|
|||||||
import { Table, TableCell, TableHead, TableRow } from "../../table";
|
import { Table, TableCell, TableHead, TableRow } from "../../table";
|
||||||
import { Button } from "../../button";
|
import { Button } from "../../button";
|
||||||
import { Notifications } from "../../notifications";
|
import { Notifications } from "../../notifications";
|
||||||
import { ThemeStore } from "../../../theme.store";
|
import type { ThemeStore } from "../../../theme.store";
|
||||||
import { apiManager } from "../../../../common/k8s-api/api-manager";
|
import { apiManager } from "../../../../common/k8s-api/api-manager";
|
||||||
import { SubTitle } from "../../layout/sub-title";
|
import { SubTitle } from "../../layout/sub-title";
|
||||||
import { getDetailsUrl } from "../../kube-detail-params";
|
import { getDetailsUrl } from "../../kube-detail-params";
|
||||||
@ -37,6 +37,7 @@ import releaseValuesInjectable from "./release-values.injectable";
|
|||||||
import userSuppliedValuesAreShownInjectable from "./user-supplied-values-are-shown.injectable";
|
import userSuppliedValuesAreShownInjectable from "./user-supplied-values-are-shown.injectable";
|
||||||
import type { KubeObject } from "../../../../common/k8s-api/kube-object";
|
import type { KubeObject } from "../../../../common/k8s-api/kube-object";
|
||||||
import { KubeObjectAge } from "../../kube-object/age";
|
import { KubeObjectAge } from "../../kube-object/age";
|
||||||
|
import themeStoreInjectable from "../../../theme-store.injectable";
|
||||||
|
|
||||||
export interface ReleaseDetailsProps {
|
export interface ReleaseDetailsProps {
|
||||||
hideDetails(): void;
|
hideDetails(): void;
|
||||||
@ -49,6 +50,7 @@ interface Dependencies {
|
|||||||
updateRelease: (name: string, namespace: string, payload: IReleaseUpdatePayload) => Promise<IReleaseUpdateDetails>;
|
updateRelease: (name: string, namespace: string, payload: IReleaseUpdatePayload) => Promise<IReleaseUpdateDetails>;
|
||||||
createUpgradeChartTab: (release: HelmRelease) => void;
|
createUpgradeChartTab: (release: HelmRelease) => void;
|
||||||
userSuppliedValuesAreShown: { toggle: () => void; value: boolean };
|
userSuppliedValuesAreShown: { toggle: () => void; value: boolean };
|
||||||
|
themeStore: ThemeStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
@ -249,13 +251,13 @@ class NonInjectedReleaseDetails extends Component<ReleaseDetailsProps & Dependen
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { hideDetails } = this.props;
|
const { hideDetails, themeStore } = this.props;
|
||||||
const title = this.release ? `Release: ${this.release.getName()}` : "";
|
const title = this.release ? `Release: ${this.release.getName()}` : "";
|
||||||
const toolbar = <HelmReleaseMenu release={this.release} toolbar hideDetails={hideDetails}/>;
|
const toolbar = <HelmReleaseMenu release={this.release} toolbar hideDetails={hideDetails}/>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
className={cssNames("ReleaseDetails", ThemeStore.getInstance().activeTheme.type)}
|
className={cssNames("ReleaseDetails", themeStore.activeTheme.type)}
|
||||||
usePortal={true}
|
usePortal={true}
|
||||||
open={!!this.release}
|
open={!!this.release}
|
||||||
title={title}
|
title={title}
|
||||||
@ -281,6 +283,8 @@ export const ReleaseDetails = withInjectables<Dependencies, ReleaseDetailsProps>
|
|||||||
|
|
||||||
updateRelease: di.inject(updateReleaseInjectable),
|
updateRelease: di.inject(updateReleaseInjectable),
|
||||||
createUpgradeChartTab: di.inject(createUpgradeChartTabInjectable),
|
createUpgradeChartTab: di.inject(createUpgradeChartTabInjectable),
|
||||||
|
themeStore: di.inject(themeStoreInjectable),
|
||||||
|
|
||||||
...props,
|
...props,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|||||||
@ -21,13 +21,16 @@ import { Spinner } from "../spinner";
|
|||||||
import type { ItemObject, ItemStore } from "../../../common/item.store";
|
import type { ItemObject, ItemStore } from "../../../common/item.store";
|
||||||
import type { Filter } from "./page-filters.store";
|
import type { Filter } from "./page-filters.store";
|
||||||
import { pageFilters } from "./page-filters.store";
|
import { pageFilters } from "./page-filters.store";
|
||||||
import { ThemeStore } from "../../theme.store";
|
import type { ThemeStore } from "../../theme.store";
|
||||||
import { MenuActions } from "../menu/menu-actions";
|
import { MenuActions } from "../menu/menu-actions";
|
||||||
import { MenuItem } from "../menu";
|
import { MenuItem } from "../menu";
|
||||||
import { Checkbox } from "../checkbox";
|
import { Checkbox } from "../checkbox";
|
||||||
import { UserStore } from "../../../common/user-store";
|
import type { UserStore } from "../../../common/user-store";
|
||||||
import type { OpenConfirmDialog } from "../confirm-dialog/open.injectable";
|
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
|
import type { OpenConfirmDialog } from "../confirm-dialog/open.injectable";
|
||||||
|
|
||||||
|
import themeStoreInjectable from "../../theme-store.injectable";
|
||||||
|
import userStoreInjectable from "../../../common/user-store/user-store.injectable";
|
||||||
import openConfirmDialogInjectable from "../confirm-dialog/open.injectable";
|
import openConfirmDialogInjectable from "../confirm-dialog/open.injectable";
|
||||||
|
|
||||||
export interface ItemListLayoutContentProps<I extends ItemObject> {
|
export interface ItemListLayoutContentProps<I extends ItemObject> {
|
||||||
@ -66,12 +69,14 @@ export interface ItemListLayoutContentProps<I extends ItemObject> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
|
themeStore: ThemeStore;
|
||||||
|
userStore: UserStore;
|
||||||
openConfirmDialog: OpenConfirmDialog;
|
openConfirmDialog: OpenConfirmDialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
class NonInjectedItemListLayoutContent<I extends ItemObject> extends React.Component<ItemListLayoutContentProps<I> & Dependencies> {
|
class NonInjectedItemListLayoutContent<I extends ItemObject> extends React.Component<Dependencies & ItemListLayoutContentProps<I>> {
|
||||||
constructor(props: ItemListLayoutContentProps<I> & Dependencies) {
|
constructor(props: Dependencies & ItemListLayoutContentProps<I>) {
|
||||||
super(props);
|
super(props);
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
autoBind(this);
|
autoBind(this);
|
||||||
@ -255,7 +260,7 @@ class NonInjectedItemListLayoutContent<I extends ItemObject> extends React.Compo
|
|||||||
detailsItem, className, tableProps = {}, tableId, getItems,
|
detailsItem, className, tableProps = {}, tableId, getItems,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const selectedItemId = detailsItem && detailsItem.getId();
|
const selectedItemId = detailsItem && detailsItem.getId();
|
||||||
const classNames = cssNames(className, "box", "grow", ThemeStore.getInstance().activeTheme.type);
|
const classNames = cssNames(className, "box", "grow", this.props.themeStore.activeTheme.type);
|
||||||
const items = getItems();
|
const items = getItems();
|
||||||
const selectedItems = store.pickOnlySelected(items);
|
const selectedItems = store.pickOnlySelected(items);
|
||||||
|
|
||||||
@ -298,7 +303,7 @@ class NonInjectedItemListLayoutContent<I extends ItemObject> extends React.Compo
|
|||||||
showColumn({ id: columnId, showWithColumn }: TableCellProps): boolean {
|
showColumn({ id: columnId, showWithColumn }: TableCellProps): boolean {
|
||||||
const { tableId, isConfigurable } = this.props;
|
const { tableId, isConfigurable } = this.props;
|
||||||
|
|
||||||
return !isConfigurable || !UserStore.getInstance().isTableColumnHidden(tableId, columnId, showWithColumn);
|
return !isConfigurable || !this.props.userStore.isTableColumnHidden(tableId, columnId, showWithColumn);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderColumnVisibilityMenu() {
|
renderColumnVisibilityMenu() {
|
||||||
@ -312,7 +317,7 @@ class NonInjectedItemListLayoutContent<I extends ItemObject> extends React.Compo
|
|||||||
<Checkbox
|
<Checkbox
|
||||||
label={cellProps.title ?? `<${cellProps.className}>`}
|
label={cellProps.title ?? `<${cellProps.className}>`}
|
||||||
value={this.showColumn(cellProps)}
|
value={this.showColumn(cellProps)}
|
||||||
onChange={() => UserStore.getInstance().toggleTableColumnVisibility(tableId, cellProps.id)}
|
onChange={() => this.props.userStore.toggleTableColumnVisibility(tableId, cellProps.id)}
|
||||||
/>
|
/>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)
|
)
|
||||||
@ -322,9 +327,20 @@ class NonInjectedItemListLayoutContent<I extends ItemObject> extends React.Compo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ItemListLayoutContent = withInjectables<Dependencies, ItemListLayoutContentProps<ItemObject>>(NonInjectedItemListLayoutContent, {
|
const InjectedItemListLayoutContent = withInjectables<Dependencies, ItemListLayoutContentProps<ItemObject>>(
|
||||||
getProps: (di, props) => ({
|
NonInjectedItemListLayoutContent,
|
||||||
...props,
|
{
|
||||||
openConfirmDialog: di.inject(openConfirmDialogInjectable),
|
getProps: (di, props) => ({
|
||||||
}),
|
themeStore: di.inject(themeStoreInjectable),
|
||||||
}) as <I extends ItemObject>(props: ItemListLayoutContentProps<I>) => React.ReactElement;
|
userStore: di.inject(userStoreInjectable),
|
||||||
|
openConfirmDialog: di.inject(openConfirmDialogInjectable),
|
||||||
|
...props,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export function ItemListLayoutContent<I extends ItemObject>(
|
||||||
|
props: ItemListLayoutContentProps<I>,
|
||||||
|
) {
|
||||||
|
return <InjectedItemListLayoutContent {...props} />;
|
||||||
|
}
|
||||||
|
|||||||
@ -18,9 +18,11 @@ import ReactSelect, {
|
|||||||
type StylesConfig,
|
type StylesConfig,
|
||||||
} from "react-select";
|
} from "react-select";
|
||||||
import ReactSelectCreatable, { type CreatableProps } from "react-select/creatable";
|
import ReactSelectCreatable, { type CreatableProps } from "react-select/creatable";
|
||||||
import { ThemeStore } from "../../theme.store";
|
import type { ThemeStore } from "../../theme.store";
|
||||||
import { autoBind, cssNames } from "../../utils";
|
import { autoBind, cssNames } from "../../utils";
|
||||||
import { merge } from "lodash";
|
import { merge } from "lodash";
|
||||||
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
|
import themeStoreInjectable from "../../theme-store.injectable";
|
||||||
|
|
||||||
const { Menu } = components;
|
const { Menu } = components;
|
||||||
|
|
||||||
@ -34,6 +36,10 @@ export interface SelectOption<T = any> {
|
|||||||
label?: React.ReactNode;
|
label?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
themeStore: ThemeStore;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SelectProps<T = any> extends ReactSelectProps<T, boolean>, CreatableProps<T, boolean, GroupBase<T>> {
|
export interface SelectProps<T = any> extends ReactSelectProps<T, boolean>, CreatableProps<T, boolean, GroupBase<T>> {
|
||||||
id?: string; // Optional only because of Extension API. Required to make Select deterministic in unit tests
|
id?: string; // Optional only because of Extension API. Required to make Select deterministic in unit tests
|
||||||
value?: T;
|
value?: T;
|
||||||
@ -45,21 +51,21 @@ export interface SelectProps<T = any> extends ReactSelectProps<T, boolean>, Crea
|
|||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class Select extends React.Component<SelectProps> {
|
class NonInjectedSelect extends React.Component<SelectProps & Dependencies> {
|
||||||
static defaultProps: Omit<SelectProps, "id"> = {
|
static defaultProps: Omit<SelectProps, "id"> = {
|
||||||
autoConvertOptions: true,
|
autoConvertOptions: true,
|
||||||
menuPortalTarget: document.body,
|
menuPortalTarget: document.body,
|
||||||
menuPlacement: "auto",
|
menuPlacement: "auto",
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props: SelectProps) {
|
constructor(props: SelectProps & Dependencies) {
|
||||||
super(props);
|
super(props);
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
autoBind(this);
|
autoBind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed get themeClass() {
|
@computed get themeClass() {
|
||||||
const themeName = this.props.themeName || ThemeStore.getInstance().activeTheme.type;
|
const themeName = this.props.themeName || this.props.themeStore.activeTheme.type;
|
||||||
|
|
||||||
return `theme-${themeName}`;
|
return `theme-${themeName}`;
|
||||||
}
|
}
|
||||||
@ -153,3 +159,14 @@ export class Select extends React.Component<SelectProps> {
|
|||||||
: <ReactSelect {...selectProps} />;
|
: <ReactSelect {...selectProps} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const Select = withInjectables<Dependencies, SelectProps>(
|
||||||
|
NonInjectedSelect,
|
||||||
|
|
||||||
|
{
|
||||||
|
getProps: (di, props) => ({
|
||||||
|
themeStore: di.inject(themeStoreInjectable),
|
||||||
|
...props,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|||||||
@ -33,6 +33,10 @@ import preferenceNavigationItemsInjectable from "../+preferences/preferences-nav
|
|||||||
import navigateToPreferencesInjectable from "../../../common/front-end-routing/routes/preferences/navigate-to-preferences.injectable";
|
import navigateToPreferencesInjectable from "../../../common/front-end-routing/routes/preferences/navigate-to-preferences.injectable";
|
||||||
import type { MenuItemOpts } from "../../../main/menu/application-menu-items.injectable";
|
import type { MenuItemOpts } from "../../../main/menu/application-menu-items.injectable";
|
||||||
import applicationMenuItemsInjectable from "../../../main/menu/application-menu-items.injectable";
|
import applicationMenuItemsInjectable from "../../../main/menu/application-menu-items.injectable";
|
||||||
|
import navigateToHelmChartsInjectable from "../../../common/front-end-routing/routes/cluster/helm/charts/navigate-to-helm-charts.injectable";
|
||||||
|
import clusterFrameContextInjectable from "../../cluster-frame-context/cluster-frame-context.injectable";
|
||||||
|
import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
|
||||||
|
import hostedClusterInjectable from "../../../common/cluster-store/hosted-cluster.injectable";
|
||||||
|
|
||||||
type Callback = (dis: DiContainers) => void | Promise<void>;
|
type Callback = (dis: DiContainers) => void | Promise<void>;
|
||||||
|
|
||||||
@ -56,6 +60,10 @@ export interface ApplicationBuilder {
|
|||||||
click: (id: string) => void;
|
click: (id: string) => void;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
helmCharts: {
|
||||||
|
navigate: () => void;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DiContainers {
|
interface DiContainers {
|
||||||
@ -192,6 +200,14 @@ export const getApplicationBuilder = () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
helmCharts: {
|
||||||
|
navigate: () => {
|
||||||
|
const navigateToHelmCharts = rendererDi.inject(navigateToHelmChartsInjectable);
|
||||||
|
|
||||||
|
navigateToHelmCharts();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
setEnvironmentToClusterFrame: () => {
|
setEnvironmentToClusterFrame: () => {
|
||||||
environment = environments.clusterFrame;
|
environment = environments.clusterFrame;
|
||||||
|
|
||||||
@ -206,6 +222,17 @@ export const getApplicationBuilder = () => {
|
|||||||
() => "/irrelevant",
|
() => "/irrelevant",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
rendererDi.override(hostedClusterInjectable, () => ({
|
||||||
|
accessibleNamespaces: [],
|
||||||
|
}));
|
||||||
|
|
||||||
|
const clusterFrameContext = rendererDi.inject(
|
||||||
|
clusterFrameContextInjectable,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Todo: get rid of global state.
|
||||||
|
KubeObjectStore.defaultContext.set(clusterFrameContext);
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -33,6 +33,9 @@ import { getAbsolutePathFake } from "../common/test-utils/get-absolute-path-fake
|
|||||||
import joinPathsInjectable from "../common/path/join-paths.injectable";
|
import joinPathsInjectable from "../common/path/join-paths.injectable";
|
||||||
import { joinPathsFake } from "../common/test-utils/join-paths-fake";
|
import { joinPathsFake } from "../common/test-utils/join-paths-fake";
|
||||||
import hotbarStoreInjectable from "../common/hotbar-store.injectable";
|
import hotbarStoreInjectable from "../common/hotbar-store.injectable";
|
||||||
|
import themeStoreInjectable from "./theme-store.injectable";
|
||||||
|
import apiManagerInjectable from "./components/kube-object-menu/dependencies/api-manager.injectable";
|
||||||
|
import { ApiManager } from "../common/k8s-api/api-manager";
|
||||||
|
|
||||||
export const getDiForUnitTesting = (
|
export const getDiForUnitTesting = (
|
||||||
{ doGeneralOverrides } = { doGeneralOverrides: false },
|
{ doGeneralOverrides } = { doGeneralOverrides: false },
|
||||||
@ -69,7 +72,16 @@ export const getDiForUnitTesting = (
|
|||||||
|
|
||||||
// eslint-disable-next-line unused-imports/no-unused-vars-ts
|
// eslint-disable-next-line unused-imports/no-unused-vars-ts
|
||||||
di.override(clusterStoreInjectable, () => ({ getById: (id): Cluster => ({}) as Cluster }) as ClusterStore);
|
di.override(clusterStoreInjectable, () => ({ getById: (id): Cluster => ({}) as Cluster }) as ClusterStore);
|
||||||
di.override(userStoreInjectable, () => ({}) as UserStore);
|
|
||||||
|
di.override(
|
||||||
|
userStoreInjectable,
|
||||||
|
() =>
|
||||||
|
({
|
||||||
|
isTableColumnHidden: () => false,
|
||||||
|
} as unknown as UserStore),
|
||||||
|
);
|
||||||
|
|
||||||
|
di.override(apiManagerInjectable, () => new ApiManager());
|
||||||
|
|
||||||
di.override(getValueFromRegisteredChannelInjectable, () => () => undefined);
|
di.override(getValueFromRegisteredChannelInjectable, () => () => undefined);
|
||||||
di.override(registerIpcChannelListenerInjectable, () => () => undefined);
|
di.override(registerIpcChannelListenerInjectable, () => () => undefined);
|
||||||
@ -92,6 +104,13 @@ export const getDiForUnitTesting = (
|
|||||||
error: (message: string, ...args: any) => console.error(message, ...args),
|
error: (message: string, ...args: any) => console.error(message, ...args),
|
||||||
info: noop,
|
info: noop,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
di.override(themeStoreInjectable, () => ({
|
||||||
|
activeTheme: {
|
||||||
|
type: "some-active-theme-type",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return di;
|
return di;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user