mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Cascade different refactorings to eliminate global shared state from app paths
Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
1 parent
908a40975e
commit
9d33fff906
336 files changed
+9105
-3307
No files matched your search
@@ -20,16 +20,20 @@
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { fireEvent, render } from "@testing-library/react";
|
||||
import { fireEvent } from "@testing-library/react";
|
||||
import "@testing-library/jest-dom/extend-expect";
|
||||
import fse from "fs-extra";
|
||||
import { DockTabs } from "../dock-tabs";
|
||||
import { dockStore, DockTab, TabKind } from "../dock.store";
|
||||
import { DockStore, DockTab, TabKind } from "../dock-store/dock.store";
|
||||
import { noop } from "../../../utils";
|
||||
import { ThemeStore } from "../../../theme.store";
|
||||
import { TerminalStore } from "../terminal.store";
|
||||
import { UserStore } from "../../../../common/user-store";
|
||||
import { AppPaths } from "../../../../common/app-paths";
|
||||
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||
import dockStoreInjectable from "../dock-store/dock-store.injectable";
|
||||
import type { DiRender } from "../../test-utils/renderFor";
|
||||
import { renderFor } from "../../test-utils/renderFor";
|
||||
import directoryForUserDataInjectable
|
||||
from "../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
|
||||
jest.mock("electron", () => ({
|
||||
app: {
|
||||
@@ -46,7 +50,6 @@ jest.mock("electron", () => ({
|
||||
handle: jest.fn(),
|
||||
},
|
||||
}));
|
||||
AppPaths.init();
|
||||
|
||||
const initialTabs: DockTab[] = [
|
||||
{ id: "terminal", kind: TabKind.TERMINAL, title: "Terminal", pinned: false },
|
||||
@@ -56,7 +59,7 @@ const initialTabs: DockTab[] = [
|
||||
{ id: "logs", kind: TabKind.POD_LOGS, title: "Logs", pinned: false },
|
||||
];
|
||||
|
||||
const getComponent = () => (
|
||||
const getComponent = (dockStore: DockStore) => (
|
||||
<DockTabs
|
||||
tabs={dockStore.tabs}
|
||||
selectedTab={dockStore.selectedTab}
|
||||
@@ -65,40 +68,56 @@ const getComponent = () => (
|
||||
/>
|
||||
);
|
||||
|
||||
const renderTabs = () => render(getComponent());
|
||||
const getTabKinds = () => dockStore.tabs.map(tab => tab.kind);
|
||||
const getTabKinds = (dockStore: DockStore) => dockStore.tabs.map((tab) => tab.kind);
|
||||
|
||||
describe("<DockTabs />", () => {
|
||||
let dockStore: DockStore;
|
||||
let render: DiRender;
|
||||
|
||||
beforeEach(async () => {
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
|
||||
render = renderFor(di);
|
||||
|
||||
di.override(
|
||||
directoryForUserDataInjectable,
|
||||
() => "some-test-suite-specific-directory-for-user-data",
|
||||
);
|
||||
|
||||
await di.runSetups();
|
||||
|
||||
dockStore = di.inject(dockStoreInjectable);
|
||||
|
||||
UserStore.createInstance();
|
||||
ThemeStore.createInstance();
|
||||
TerminalStore.createInstance();
|
||||
await dockStore.whenReady;
|
||||
dockStore.tabs = initialTabs;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
ThemeStore.resetInstance();
|
||||
TerminalStore.resetInstance();
|
||||
UserStore.resetInstance();
|
||||
fse.remove("tmp");
|
||||
|
||||
// TODO: A unit test may not cause side effects. Here accessing file system is a side effect.
|
||||
fse.remove("some-test-suite-specific-directory-for-user-data");
|
||||
});
|
||||
|
||||
it("renders w/o errors", () => {
|
||||
const { container } = renderTabs();
|
||||
const { container } = render(getComponent(dockStore));
|
||||
|
||||
expect(container).toBeInstanceOf(HTMLElement);
|
||||
});
|
||||
|
||||
it("has 6 tabs (1 tab is initial terminal)", () => {
|
||||
const { container } = renderTabs();
|
||||
const { container } = render(getComponent(dockStore));
|
||||
const tabs = container.querySelectorAll(".Tab");
|
||||
|
||||
expect(tabs.length).toBe(initialTabs.length);
|
||||
});
|
||||
|
||||
it("opens a context menu", () => {
|
||||
const { container, getByText } = renderTabs();
|
||||
const { container, getByText } = render(getComponent(dockStore));
|
||||
const tab = container.querySelector(".Tab");
|
||||
|
||||
fireEvent.contextMenu(tab);
|
||||
@@ -106,17 +125,22 @@ describe("<DockTabs />", () => {
|
||||
});
|
||||
|
||||
it("closes selected tab", () => {
|
||||
const { container, getByText, rerender } = renderTabs();
|
||||
const { container, getByText, rerender } = render(
|
||||
getComponent(dockStore),
|
||||
);
|
||||
|
||||
const tab = container.querySelector(".Tab");
|
||||
|
||||
fireEvent.contextMenu(tab);
|
||||
fireEvent.click(getByText("Close"));
|
||||
rerender(getComponent());
|
||||
|
||||
rerender(getComponent(dockStore));
|
||||
|
||||
const tabs = container.querySelectorAll(".Tab");
|
||||
|
||||
expect(tabs.length).toBe(initialTabs.length - 1);
|
||||
expect(getTabKinds()).toEqual([
|
||||
|
||||
expect(getTabKinds(dockStore)).toEqual([
|
||||
TabKind.CREATE_RESOURCE,
|
||||
TabKind.EDIT_RESOURCE,
|
||||
TabKind.INSTALL_CHART,
|
||||
@@ -125,42 +149,42 @@ describe("<DockTabs />", () => {
|
||||
});
|
||||
|
||||
it("closes other tabs", () => {
|
||||
const { container, getByText, rerender } = renderTabs();
|
||||
const { container, getByText, rerender } = render(getComponent(dockStore));
|
||||
const tab = container.querySelectorAll(".Tab")[3];
|
||||
|
||||
fireEvent.contextMenu(tab);
|
||||
fireEvent.click(getByText("Close other tabs"));
|
||||
rerender(getComponent());
|
||||
rerender(getComponent(dockStore));
|
||||
|
||||
const tabs = container.querySelectorAll(".Tab");
|
||||
|
||||
expect(tabs.length).toBe(1);
|
||||
expect(getTabKinds()).toEqual([initialTabs[3].kind]);
|
||||
expect(getTabKinds(dockStore)).toEqual([initialTabs[3].kind]);
|
||||
});
|
||||
|
||||
it("closes all tabs", () => {
|
||||
const { container, getByText, rerender } = renderTabs();
|
||||
const { container, getByText, rerender } = render(getComponent(dockStore));
|
||||
const tab = container.querySelector(".Tab");
|
||||
|
||||
fireEvent.contextMenu(tab);
|
||||
const command = getByText("Close all tabs");
|
||||
|
||||
fireEvent.click(command);
|
||||
rerender(getComponent());
|
||||
rerender(getComponent(dockStore));
|
||||
const tabs = container.querySelectorAll(".Tab");
|
||||
|
||||
expect(tabs.length).toBe(0);
|
||||
});
|
||||
|
||||
it("closes tabs to the right", () => {
|
||||
const { container, getByText, rerender } = renderTabs();
|
||||
const { container, getByText, rerender } = render(getComponent(dockStore));
|
||||
const tab = container.querySelectorAll(".Tab")[3]; // 4th of 5
|
||||
|
||||
fireEvent.contextMenu(tab);
|
||||
fireEvent.click(getByText("Close tabs to the right"));
|
||||
rerender(getComponent());
|
||||
rerender(getComponent(dockStore));
|
||||
|
||||
expect(getTabKinds()).toEqual(
|
||||
expect(getTabKinds(dockStore)).toEqual(
|
||||
initialTabs.slice(0, 4).map(tab => tab.kind),
|
||||
);
|
||||
});
|
||||
@@ -169,7 +193,7 @@ describe("<DockTabs />", () => {
|
||||
dockStore.tabs = [{
|
||||
id: "terminal", kind: TabKind.TERMINAL, title: "Terminal", pinned: false,
|
||||
}];
|
||||
const { container, getByText } = renderTabs();
|
||||
const { container, getByText } = render(getComponent(dockStore));
|
||||
const tab = container.querySelector(".Tab");
|
||||
|
||||
fireEvent.contextMenu(tab);
|
||||
@@ -185,7 +209,7 @@ describe("<DockTabs />", () => {
|
||||
{ id: "terminal", kind: TabKind.TERMINAL, title: "Terminal", pinned: false },
|
||||
{ id: "logs", kind: TabKind.POD_LOGS, title: "Pod Logs", pinned: false },
|
||||
];
|
||||
const { container, getByText } = renderTabs();
|
||||
const { container, getByText } = render(getComponent(dockStore));
|
||||
const tab = container.querySelectorAll(".Tab")[1];
|
||||
|
||||
fireEvent.contextMenu(tab);
|
||||
|
||||
@@ -21,17 +21,20 @@
|
||||
|
||||
import React from "react";
|
||||
import "@testing-library/jest-dom/extend-expect";
|
||||
import { render } from "@testing-library/react";
|
||||
import selectEvent from "react-select-event";
|
||||
|
||||
import { Pod } from "../../../../common/k8s-api/endpoints";
|
||||
import { LogResourceSelector } from "../log-resource-selector";
|
||||
import type { LogTabData } from "../log-tab.store";
|
||||
import type { LogTabData } from "../log-tab-store/log-tab.store";
|
||||
import { dockerPod, deploymentPod1 } from "./pod.mock";
|
||||
import { ThemeStore } from "../../../theme.store";
|
||||
import { UserStore } from "../../../../common/user-store";
|
||||
import mockFs from "mock-fs";
|
||||
import { AppPaths } from "../../../../common/app-paths";
|
||||
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||
import type { DiRender } from "../../test-utils/renderFor";
|
||||
import { renderFor } from "../../test-utils/renderFor";
|
||||
import directoryForUserDataInjectable
|
||||
from "../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
|
||||
jest.mock("electron", () => ({
|
||||
app: {
|
||||
@@ -49,8 +52,6 @@ jest.mock("electron", () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
AppPaths.init();
|
||||
|
||||
const getComponent = (tabData: LogTabData) => {
|
||||
return (
|
||||
<LogResourceSelector
|
||||
@@ -84,10 +85,21 @@ const getFewPodsTabData = (): LogTabData => {
|
||||
};
|
||||
|
||||
describe("<LogResourceSelector />", () => {
|
||||
beforeEach(() => {
|
||||
let render: DiRender;
|
||||
|
||||
beforeEach(async () => {
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||
|
||||
render = renderFor(di);
|
||||
|
||||
await di.runSetups();
|
||||
|
||||
mockFs({
|
||||
"tmp": {},
|
||||
});
|
||||
|
||||
UserStore.createInstance();
|
||||
ThemeStore.createInstance();
|
||||
});
|
||||
|
||||
@@ -23,49 +23,47 @@ import { podsStore } from "../../+workloads-pods/pods.store";
|
||||
import { UserStore } from "../../../../common/user-store";
|
||||
import { Pod } from "../../../../common/k8s-api/endpoints";
|
||||
import { ThemeStore } from "../../../theme.store";
|
||||
import { dockStore } from "../dock.store";
|
||||
import { logTabStore } from "../log-tab.store";
|
||||
import { deploymentPod1, deploymentPod2, deploymentPod3, dockerPod } from "./pod.mock";
|
||||
import fse from "fs-extra";
|
||||
import { mockWindow } from "../../../../../__mocks__/windowMock";
|
||||
import { AppPaths } from "../../../../common/app-paths";
|
||||
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||
import logTabStoreInjectable from "../log-tab-store/log-tab-store.injectable";
|
||||
import type { LogTabStore } from "../log-tab-store/log-tab.store";
|
||||
import dockStoreInjectable from "../dock-store/dock-store.injectable";
|
||||
import type { DockStore } from "../dock-store/dock.store";
|
||||
import directoryForUserDataInjectable
|
||||
from "../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import mockFs from "mock-fs";
|
||||
|
||||
mockWindow();
|
||||
|
||||
jest.mock("electron", () => ({
|
||||
app: {
|
||||
getVersion: () => "99.99.99",
|
||||
getName: () => "lens",
|
||||
setName: jest.fn(),
|
||||
setPath: jest.fn(),
|
||||
getPath: () => "tmp",
|
||||
getLocale: () => "en",
|
||||
setLoginItemSettings: jest.fn(),
|
||||
},
|
||||
ipcMain: {
|
||||
on: jest.fn(),
|
||||
handle: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
AppPaths.init();
|
||||
|
||||
podsStore.items.push(new Pod(dockerPod));
|
||||
podsStore.items.push(new Pod(deploymentPod1));
|
||||
podsStore.items.push(new Pod(deploymentPod2));
|
||||
|
||||
describe("log tab store", () => {
|
||||
beforeEach(() => {
|
||||
let logTabStore: LogTabStore;
|
||||
let dockStore: DockStore;
|
||||
|
||||
beforeEach(async () => {
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
mockFs();
|
||||
|
||||
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||
|
||||
await di.runSetups();
|
||||
|
||||
dockStore = di.inject(dockStoreInjectable);
|
||||
logTabStore = di.inject(logTabStoreInjectable);
|
||||
|
||||
UserStore.createInstance();
|
||||
ThemeStore.createInstance();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
logTabStore.reset();
|
||||
dockStore.reset();
|
||||
UserStore.resetInstance();
|
||||
ThemeStore.resetInstance();
|
||||
fse.remove("tmp");
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
it("creates log tab without sibling pods", () => {
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import { createInstallChartTab } from "./create-install-chart-tab";
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import installChartStoreInjectable from "../install-chart-store/install-chart-store.injectable";
|
||||
import dockStoreInjectable from "../dock-store/dock-store.injectable";
|
||||
|
||||
const createInstallChartTabInjectable = getInjectable({
|
||||
instantiate: (di) => createInstallChartTab({
|
||||
installChartStore: di.inject(installChartStoreInjectable),
|
||||
createDockTab: di.inject(dockStoreInjectable).createTab,
|
||||
}),
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default createInstallChartTabInjectable;
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import type { HelmChart } from "../../../../common/k8s-api/endpoints/helm-charts.api";
|
||||
import {
|
||||
DockTab,
|
||||
DockTabCreate,
|
||||
DockTabCreateSpecific,
|
||||
TabKind,
|
||||
} from "../dock-store/dock.store";
|
||||
|
||||
import type { InstallChartStore } from "../install-chart-store/install-chart.store";
|
||||
|
||||
interface Dependencies {
|
||||
createDockTab: (rawTab: DockTabCreate, addNumber: boolean) => DockTab;
|
||||
installChartStore: InstallChartStore;
|
||||
}
|
||||
|
||||
export const createInstallChartTab =
|
||||
({ createDockTab, installChartStore }: Dependencies) =>
|
||||
(chart: HelmChart, tabParams: DockTabCreateSpecific = {}) => {
|
||||
const { name, repo, version } = chart;
|
||||
|
||||
const tab = createDockTab(
|
||||
{
|
||||
title: `Helm Install: ${repo}/${name}`,
|
||||
...tabParams,
|
||||
kind: TabKind.INSTALL_CHART,
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
installChartStore.setData(tab.id, {
|
||||
name,
|
||||
repo,
|
||||
version,
|
||||
namespace: "default",
|
||||
releaseName: "",
|
||||
description: "",
|
||||
});
|
||||
|
||||
return tab;
|
||||
};
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import dockStoreInjectable from "../dock-store/dock-store.injectable";
|
||||
import { CreateResourceStore } from "./create-resource.store";
|
||||
import createStorageInjectable from "../../../utils/create-storage/create-storage.injectable";
|
||||
|
||||
const createResourceStoreInjectable = getInjectable({
|
||||
instantiate: (di) => new CreateResourceStore({
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
createStorage: di.inject(createStorageInjectable),
|
||||
}),
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default createResourceStoreInjectable;
|
||||
+11
-15
@@ -25,15 +25,21 @@ import os from "os";
|
||||
import groupBy from "lodash/groupBy";
|
||||
import filehound from "filehound";
|
||||
import { watch } from "chokidar";
|
||||
import { autoBind } from "../../utils";
|
||||
import { DockTabStore } from "./dock-tab.store";
|
||||
import { dockStore, DockTabCreateSpecific, TabKind } from "./dock.store";
|
||||
import { autoBind, StorageHelper } from "../../../utils";
|
||||
import { DockTabStorageState, DockTabStore } from "../dock-tab-store/dock-tab.store";
|
||||
import type { DockStore } from "../dock-store/dock.store";
|
||||
|
||||
interface Dependencies {
|
||||
dockStore: DockStore,
|
||||
createStorage:<T> (storageKey: string, options: DockTabStorageState<T>) => StorageHelper<DockTabStorageState<T>>
|
||||
}
|
||||
|
||||
export class CreateResourceStore extends DockTabStore<string> {
|
||||
constructor() {
|
||||
super({
|
||||
constructor(protected dependencies: Dependencies) {
|
||||
super(dependencies, {
|
||||
storageKey: "create_resource",
|
||||
});
|
||||
|
||||
autoBind(this);
|
||||
fs.ensureDirSync(this.userTemplatesFolder);
|
||||
}
|
||||
@@ -78,13 +84,3 @@ export class CreateResourceStore extends DockTabStore<string> {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const createResourceStore = new CreateResourceStore();
|
||||
|
||||
export function createResourceTab(tabParams: DockTabCreateSpecific = {}) {
|
||||
return dockStore.createTab({
|
||||
title: "Create resource",
|
||||
...tabParams,
|
||||
kind: TabKind.CREATE_RESOURCE,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import { createResourceTab } from "./create-resource-tab";
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import dockStoreInjectable from "../dock-store/dock-store.injectable";
|
||||
|
||||
const createResourceTabInjectable = getInjectable({
|
||||
instantiate: (di) => createResourceTab({
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
}),
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default createResourceTabInjectable;
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import { DockStore, DockTabCreateSpecific, TabKind } from "../dock-store/dock.store";
|
||||
|
||||
interface Dependencies {
|
||||
dockStore: DockStore
|
||||
}
|
||||
|
||||
export const createResourceTab =
|
||||
({ dockStore }: Dependencies) =>
|
||||
(tabParams: DockTabCreateSpecific = {}) =>
|
||||
dockStore.createTab({
|
||||
title: "Create resource",
|
||||
...tabParams,
|
||||
kind: TabKind.CREATE_RESOURCE,
|
||||
});
|
||||
@@ -28,8 +28,8 @@ import { GroupSelectOption, Select, SelectOption } from "../select";
|
||||
import yaml from "js-yaml";
|
||||
import { makeObservable, observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { createResourceStore } from "./create-resource.store";
|
||||
import type { DockTab } from "./dock.store";
|
||||
import type { CreateResourceStore } from "./create-resource-store/create-resource.store";
|
||||
import type { DockTab } from "./dock-store/dock.store";
|
||||
import { EditorPanel } from "./editor-panel";
|
||||
import { InfoPanel } from "./info-panel";
|
||||
import * as resourceApplierApi from "../../../common/k8s-api/endpoints/resource-applier.api";
|
||||
@@ -40,25 +40,32 @@ import { getDetailsUrl } from "../kube-detail-params";
|
||||
import { apiManager } from "../../../common/k8s-api/api-manager";
|
||||
import { prevDefault } from "../../utils";
|
||||
import { navigate } from "../../navigation";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import createResourceStoreInjectable
|
||||
from "./create-resource-store/create-resource-store.injectable";
|
||||
|
||||
interface Props {
|
||||
tab: DockTab;
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
createResourceStore: CreateResourceStore
|
||||
}
|
||||
|
||||
@observer
|
||||
export class CreateResource extends React.Component<Props> {
|
||||
class NonInjectedCreateResource extends React.Component<Props & Dependencies> {
|
||||
@observable currentTemplates: Map<string, SelectOption> = new Map();
|
||||
@observable error = "";
|
||||
@observable templates: GroupSelectOption<SelectOption>[] = [];
|
||||
|
||||
constructor(props: Props) {
|
||||
constructor(props: Props & Dependencies) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
createResourceStore.getMergedTemplates().then(v => this.updateGroupSelectOptions(v));
|
||||
createResourceStore.watchUserTemplates(() => createResourceStore.getMergedTemplates().then(v => this.updateGroupSelectOptions(v)));
|
||||
this.props.createResourceStore.getMergedTemplates().then(v => this.updateGroupSelectOptions(v));
|
||||
this.props.createResourceStore.watchUserTemplates(() => this.props.createResourceStore.getMergedTemplates().then(v => this.updateGroupSelectOptions(v)));
|
||||
}
|
||||
|
||||
updateGroupSelectOptions(templates: Record<string, string[]>) {
|
||||
@@ -77,7 +84,7 @@ export class CreateResource extends React.Component<Props> {
|
||||
}
|
||||
|
||||
get data() {
|
||||
return createResourceStore.getData(this.tabId);
|
||||
return this.props.createResourceStore.getData(this.tabId);
|
||||
}
|
||||
|
||||
get currentTemplate() {
|
||||
@@ -86,7 +93,7 @@ export class CreateResource extends React.Component<Props> {
|
||||
|
||||
onChange = (value: string) => {
|
||||
this.error = ""; // reset first, validation goes later
|
||||
createResourceStore.setData(this.tabId, value);
|
||||
this.props.createResourceStore.setData(this.tabId, value);
|
||||
};
|
||||
|
||||
onError = (error: Error | string) => {
|
||||
@@ -96,7 +103,7 @@ export class CreateResource extends React.Component<Props> {
|
||||
onSelectTemplate = (item: SelectOption) => {
|
||||
this.currentTemplates.set(this.tabId, item);
|
||||
fs.readFile(item.value, "utf8").then(v => {
|
||||
createResourceStore.setData(this.tabId, v);
|
||||
this.props.createResourceStore.setData(this.tabId, v);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -180,3 +187,14 @@ export class CreateResource extends React.Component<Props> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const CreateResource = withInjectables<Dependencies, Props>(
|
||||
NonInjectedCreateResource,
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
createResourceStore: di.inject(createResourceStoreInjectable),
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import { createTerminalTab } from "./create-terminal-tab";
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import dockStoreInjectable from "../dock-store/dock-store.injectable";
|
||||
|
||||
const createTerminalTabInjectable = getInjectable({
|
||||
instantiate: (di) => createTerminalTab({
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
}),
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default createTerminalTabInjectable;
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import {
|
||||
DockStore,
|
||||
DockTabCreateSpecific,
|
||||
TabKind,
|
||||
} from "../dock-store/dock.store";
|
||||
|
||||
interface Dependencies {
|
||||
dockStore: DockStore;
|
||||
}
|
||||
|
||||
export const createTerminalTab =
|
||||
({ dockStore }: Dependencies) =>
|
||||
(tabParams: DockTabCreateSpecific = {}) =>
|
||||
dockStore.createTab({
|
||||
title: `Terminal`,
|
||||
...tabParams,
|
||||
kind: TabKind.TERMINAL,
|
||||
});
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { createUpgradeChartTab } from "./create-upgrade-chart-tab";
|
||||
import upgradeChartStoreInjectable from "../upgrade-chart-store/upgrade-chart-store.injectable";
|
||||
import dockStoreInjectable from "../dock-store/dock-store.injectable";
|
||||
|
||||
const createUpgradeChartTabInjectable = getInjectable({
|
||||
instantiate: (di) => createUpgradeChartTab({
|
||||
upgradeChartStore: di.inject(upgradeChartStoreInjectable),
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
}),
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default createUpgradeChartTabInjectable;
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import type { HelmRelease } from "../../../../common/k8s-api/endpoints/helm-releases.api";
|
||||
import { DockStore, DockTabCreateSpecific, TabKind } from "../dock-store/dock.store";
|
||||
import type { UpgradeChartStore } from "../upgrade-chart-store/upgrade-chart.store";
|
||||
|
||||
interface Dependencies {
|
||||
upgradeChartStore: UpgradeChartStore;
|
||||
dockStore: DockStore
|
||||
}
|
||||
|
||||
export const createUpgradeChartTab =
|
||||
({ upgradeChartStore, dockStore }: Dependencies) =>
|
||||
(release: HelmRelease, tabParams: DockTabCreateSpecific = {}) => {
|
||||
let tab = upgradeChartStore.getTabByRelease(release.getName());
|
||||
|
||||
if (tab) {
|
||||
dockStore.open();
|
||||
dockStore.selectTab(tab.id);
|
||||
}
|
||||
|
||||
if (!tab) {
|
||||
tab = dockStore.createTab(
|
||||
{
|
||||
title: `Helm Upgrade: ${release.getName()}`,
|
||||
...tabParams,
|
||||
kind: TabKind.UPGRADE_CHART,
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
upgradeChartStore.setData(tab.id, {
|
||||
releaseName: release.getName(),
|
||||
releaseNamespace: release.getNs(),
|
||||
});
|
||||
}
|
||||
|
||||
return tab;
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { DockStorageState, DockStore, TabKind } from "./dock.store";
|
||||
import createStorageInjectable from "../../../utils/create-storage/create-storage.injectable";
|
||||
|
||||
const dockStoreInjectable = getInjectable({
|
||||
instantiate: (di) => {
|
||||
const createStorage = di.inject(createStorageInjectable);
|
||||
|
||||
const storage = createStorage<DockStorageState>("dock", {
|
||||
height: 300,
|
||||
tabs: [
|
||||
{
|
||||
id: "terminal",
|
||||
kind: TabKind.TERMINAL,
|
||||
title: "Terminal",
|
||||
pinned: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return new DockStore({
|
||||
storage,
|
||||
});
|
||||
},
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default dockStoreInjectable;
|
||||
+19
-24
@@ -21,7 +21,7 @@
|
||||
|
||||
import * as uuid from "uuid";
|
||||
import { action, comparer, computed, makeObservable, observable, reaction, runInAction } from "mobx";
|
||||
import { autoBind, createStorage } from "../../utils";
|
||||
import { autoBind, StorageHelper } from "../../../utils";
|
||||
import throttle from "lodash/throttle";
|
||||
|
||||
export type TabId = string;
|
||||
@@ -113,8 +113,12 @@ export interface DockTabCloseEvent {
|
||||
tabId: TabId; // closed tab id
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
storage: StorageHelper<DockStorageState>
|
||||
}
|
||||
|
||||
export class DockStore implements DockStorageState {
|
||||
constructor() {
|
||||
constructor(private dependencies: Dependencies) {
|
||||
makeObservable(this);
|
||||
autoBind(this);
|
||||
this.init();
|
||||
@@ -123,45 +127,38 @@ export class DockStore implements DockStorageState {
|
||||
readonly minHeight = 100;
|
||||
@observable fullSize = false;
|
||||
|
||||
private storage = createStorage<DockStorageState>("dock", {
|
||||
height: 300,
|
||||
tabs: [
|
||||
{ id: "terminal", kind: TabKind.TERMINAL, title: "Terminal", pinned: false },
|
||||
],
|
||||
});
|
||||
|
||||
get whenReady() {
|
||||
return this.storage.whenReady;
|
||||
return this.dependencies.storage.whenReady;
|
||||
}
|
||||
|
||||
get isOpen(): boolean {
|
||||
return this.storage.get().isOpen;
|
||||
return this.dependencies.storage.get().isOpen;
|
||||
}
|
||||
|
||||
set isOpen(isOpen: boolean) {
|
||||
this.storage.merge({ isOpen });
|
||||
this.dependencies.storage.merge({ isOpen });
|
||||
}
|
||||
|
||||
get height(): number {
|
||||
return this.storage.get().height;
|
||||
return this.dependencies.storage.get().height;
|
||||
}
|
||||
|
||||
set height(height: number) {
|
||||
this.storage.merge({
|
||||
this.dependencies.storage.merge({
|
||||
height: Math.max(this.minHeight, Math.min(height || this.minHeight, this.maxHeight)),
|
||||
});
|
||||
}
|
||||
|
||||
get tabs(): DockTab[] {
|
||||
return this.storage.get().tabs;
|
||||
return this.dependencies.storage.get().tabs;
|
||||
}
|
||||
|
||||
set tabs(tabs: DockTab[]) {
|
||||
this.storage.merge({ tabs });
|
||||
this.dependencies.storage.merge({ tabs });
|
||||
}
|
||||
|
||||
get selectedTabId(): TabId | undefined {
|
||||
return this.storage.get().selectedTabId
|
||||
return this.dependencies.storage.get().selectedTabId
|
||||
|| (
|
||||
this.tabs.length > 0
|
||||
? this.tabs[0]?.id
|
||||
@@ -172,7 +169,7 @@ export class DockStore implements DockStorageState {
|
||||
set selectedTabId(tabId: TabId) {
|
||||
if (tabId && !this.getTabById(tabId)) return; // skip invalid ids
|
||||
|
||||
this.storage.merge({ selectedTabId: tabId });
|
||||
this.dependencies.storage.merge({ selectedTabId: tabId });
|
||||
}
|
||||
|
||||
@computed get selectedTab() {
|
||||
@@ -206,7 +203,7 @@ export class DockStore implements DockStorageState {
|
||||
}
|
||||
|
||||
onTabClose(callback: (evt: DockTabCloseEvent) => void, opts: { fireImmediately?: boolean } = {}) {
|
||||
return reaction(() => dockStore.tabs.map(tab => tab.id), (tabs: TabId[], prevTabs?: TabId[]) => {
|
||||
return reaction(() => this.tabs.map(tab => tab.id), (tabs: TabId[], prevTabs?: TabId[]) => {
|
||||
if (!Array.isArray(prevTabs)) {
|
||||
return; // tabs not yet modified
|
||||
}
|
||||
@@ -292,7 +289,7 @@ export class DockStore implements DockStorageState {
|
||||
}
|
||||
|
||||
@action
|
||||
createTab(rawTabDesc: DockTabCreate, addNumber = true): DockTab {
|
||||
createTab = (rawTabDesc: DockTabCreate, addNumber = true): DockTab => {
|
||||
const {
|
||||
id = uuid.v4(),
|
||||
kind,
|
||||
@@ -322,7 +319,7 @@ export class DockStore implements DockStorageState {
|
||||
this.open();
|
||||
|
||||
return tab;
|
||||
}
|
||||
};
|
||||
|
||||
@action
|
||||
closeTab(tabId: TabId) {
|
||||
@@ -381,8 +378,6 @@ export class DockStore implements DockStorageState {
|
||||
|
||||
@action
|
||||
reset() {
|
||||
this.storage?.reset();
|
||||
this.dependencies.storage?.reset();
|
||||
}
|
||||
}
|
||||
|
||||
export const dockStore = new DockStore();
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { DockTabStore, DockTabStoreOptions } from "./dock-tab.store";
|
||||
import dockStoreInjectable from "../dock-store/dock-store.injectable";
|
||||
import createStorageInjectable from "../../../utils/create-storage/create-storage.injectable";
|
||||
|
||||
const createDockTabStoreInjectable = getInjectable({
|
||||
instantiate: (di) => {
|
||||
const dependencies = {
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
createStorage: di.inject(createStorageInjectable),
|
||||
};
|
||||
|
||||
return <T>(options: DockTabStoreOptions = {}) => new DockTabStore<T>(dependencies, options);
|
||||
},
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default createDockTabStoreInjectable;
|
||||
+10
-5
@@ -20,8 +20,8 @@
|
||||
*/
|
||||
|
||||
import { autorun, observable, reaction } from "mobx";
|
||||
import { autoBind, createStorage, StorageHelper, toJS } from "../../utils";
|
||||
import { dockStore, TabId } from "./dock.store";
|
||||
import { autoBind, StorageHelper, toJS } from "../../../utils";
|
||||
import type { DockStore, TabId } from "../dock-store/dock.store";
|
||||
|
||||
export interface DockTabStoreOptions {
|
||||
autoInit?: boolean; // load data from storage when `storageKey` is provided and bind events, default: true
|
||||
@@ -30,11 +30,16 @@ export interface DockTabStoreOptions {
|
||||
|
||||
export type DockTabStorageState<T> = Record<TabId, T>;
|
||||
|
||||
interface Dependencies {
|
||||
dockStore: DockStore
|
||||
createStorage: <T>(storageKey: string, options: DockTabStorageState<T>) => StorageHelper<DockTabStorageState<T>>
|
||||
}
|
||||
|
||||
export class DockTabStore<T> {
|
||||
protected storage?: StorageHelper<DockTabStorageState<T>>;
|
||||
protected data = observable.map<TabId, T>();
|
||||
|
||||
constructor(protected options: DockTabStoreOptions = {}) {
|
||||
constructor(protected dependencies: Dependencies, protected options: DockTabStoreOptions) {
|
||||
autoBind(this);
|
||||
|
||||
this.options = {
|
||||
@@ -52,7 +57,7 @@ export class DockTabStore<T> {
|
||||
|
||||
// auto-save to local-storage
|
||||
if (storageKey) {
|
||||
this.storage = createStorage(storageKey, {});
|
||||
this.storage = this.dependencies.createStorage<T>(storageKey, {});
|
||||
this.storage.whenReady.then(() => {
|
||||
this.data.replace(this.storage.get());
|
||||
reaction(() => this.toJSON(), data => this.storage.set(data));
|
||||
@@ -61,7 +66,7 @@ export class DockTabStore<T> {
|
||||
|
||||
// clear data for closed tabs
|
||||
autorun(() => {
|
||||
const currentTabs = dockStore.tabs.map(tab => tab.id);
|
||||
const currentTabs = this.dependencies.dockStore.tabs.map(tab => tab.id);
|
||||
|
||||
Array.from(this.data.keys()).forEach(tabId => {
|
||||
if (!currentTabs.includes(tabId)) {
|
||||
@@ -24,22 +24,28 @@ import "./dock-tab.scss";
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { boundMethod, cssNames, prevDefault, isMiddleClick } from "../../utils";
|
||||
import { dockStore, DockTab as DockTabModel } from "./dock.store";
|
||||
import type { DockStore, DockTab as DockTabModel } from "./dock-store/dock.store";
|
||||
import { Tab, TabProps } from "../tabs";
|
||||
import { Icon } from "../icon";
|
||||
import { Menu, MenuItem } from "../menu";
|
||||
import { observable, makeObservable } from "mobx";
|
||||
import { isMac } from "../../../common/vars";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import dockStoreInjectable from "./dock-store/dock-store.injectable";
|
||||
|
||||
export interface DockTabProps extends TabProps<DockTabModel> {
|
||||
moreActions?: React.ReactNode;
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
dockStore: DockStore
|
||||
}
|
||||
|
||||
@observer
|
||||
export class DockTab extends React.Component<DockTabProps> {
|
||||
class NonInjectedDockTab extends React.Component<DockTabProps & Dependencies> {
|
||||
@observable menuVisible = false;
|
||||
|
||||
constructor(props: DockTabProps) {
|
||||
constructor(props: DockTabProps & Dependencies) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
}
|
||||
@@ -50,11 +56,11 @@ export class DockTab extends React.Component<DockTabProps> {
|
||||
|
||||
@boundMethod
|
||||
close() {
|
||||
dockStore.closeTab(this.tabId);
|
||||
this.props.dockStore.closeTab(this.tabId);
|
||||
}
|
||||
|
||||
renderMenu() {
|
||||
const { closeTab, closeAllTabs, closeOtherTabs, closeTabsToTheRight, tabs, getTabIndex } = dockStore;
|
||||
const { closeTab, closeAllTabs, closeOtherTabs, closeTabsToTheRight, tabs, getTabIndex } = this.props.dockStore;
|
||||
const closeAllDisabled = tabs.length === 1;
|
||||
const closeOtherDisabled = tabs.length === 1;
|
||||
const closeRightDisabled = getTabIndex(this.tabId) === tabs.length - 1;
|
||||
@@ -86,7 +92,7 @@ export class DockTab extends React.Component<DockTabProps> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, moreActions, ...tabProps } = this.props;
|
||||
const { className, moreActions, dockStore, ...tabProps } = this.props;
|
||||
const { title, pinned } = tabProps.value;
|
||||
const label = (
|
||||
<div className="flex gaps align-center" onAuxClick={isMiddleClick(prevDefault(this.close))}>
|
||||
@@ -116,3 +122,14 @@ export class DockTab extends React.Component<DockTabProps> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const DockTab = withInjectables<Dependencies, DockTabProps>(
|
||||
NonInjectedDockTab,
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
);
|
||||
@@ -24,8 +24,8 @@ import React, { Fragment } from "react";
|
||||
import { Icon } from "../icon";
|
||||
import { Tabs } from "../tabs/tabs";
|
||||
import { DockTab } from "./dock-tab";
|
||||
import type { DockTab as DockTabModel } from "./dock.store";
|
||||
import { TabKind } from "./dock.store";
|
||||
import type { DockTab as DockTabModel } from "./dock-store/dock.store";
|
||||
import { TabKind } from "./dock-store/dock.store";
|
||||
import { TerminalTab } from "./terminal-tab";
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -30,22 +30,30 @@ import { MenuItem } from "../menu";
|
||||
import { MenuActions } from "../menu/menu-actions";
|
||||
import { ResizeDirection, ResizingAnchor } from "../resizing-anchor";
|
||||
import { CreateResource } from "./create-resource";
|
||||
import { createResourceTab } from "./create-resource.store";
|
||||
import { DockTabs } from "./dock-tabs";
|
||||
import { dockStore, DockTab, TabKind } from "./dock.store";
|
||||
import { DockStore, DockTab, TabKind } from "./dock-store/dock.store";
|
||||
import { EditResource } from "./edit-resource";
|
||||
import { InstallChart } from "./install-chart";
|
||||
import { Logs } from "./logs";
|
||||
import { TerminalWindow } from "./terminal-window";
|
||||
import { createTerminalTab } from "./terminal.store";
|
||||
import { UpgradeChart } from "./upgrade-chart";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import createResourceTabInjectable from "./create-resource-tab/create-resource-tab.injectable";
|
||||
import dockStoreInjectable from "./dock-store/dock-store.injectable";
|
||||
import createTerminalTabInjectable from "./create-terminal-tab/create-terminal-tab.injectable";
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
createResourceTab: () => void
|
||||
createTerminalTab: () => void
|
||||
dockStore: DockStore
|
||||
}
|
||||
|
||||
@observer
|
||||
export class Dock extends React.Component<Props> {
|
||||
class NonInjectedDock extends React.Component<Props & Dependencies> {
|
||||
private element = React.createRef<HTMLDivElement>();
|
||||
|
||||
componentDidMount() {
|
||||
@@ -57,7 +65,7 @@ export class Dock extends React.Component<Props> {
|
||||
}
|
||||
|
||||
onKeyDown = (evt: KeyboardEvent) => {
|
||||
const { close, selectedTab, closeTab } = dockStore;
|
||||
const { close, selectedTab, closeTab } = this.props.dockStore;
|
||||
const { code, ctrlKey, metaKey, shiftKey } = evt;
|
||||
// Determine if user working inside <Dock/> or using any other areas in app
|
||||
const dockIsFocused = this.element?.current.contains(document.activeElement);
|
||||
@@ -75,7 +83,7 @@ export class Dock extends React.Component<Props> {
|
||||
};
|
||||
|
||||
onChangeTab = (tab: DockTab) => {
|
||||
const { open, selectTab } = dockStore;
|
||||
const { open, selectTab } = this.props.dockStore;
|
||||
|
||||
open();
|
||||
selectTab(tab.id);
|
||||
@@ -100,7 +108,7 @@ export class Dock extends React.Component<Props> {
|
||||
}
|
||||
|
||||
renderTabContent() {
|
||||
const { isOpen, height, selectedTab } = dockStore;
|
||||
const { isOpen, height, selectedTab } = this.props.dockStore;
|
||||
|
||||
if (!isOpen || !selectedTab) return null;
|
||||
|
||||
@@ -112,8 +120,8 @@ export class Dock extends React.Component<Props> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className } = this.props;
|
||||
const { isOpen, toggle, tabs, toggleFillSize, selectedTab, hasTabs, fullSize } = dockStore;
|
||||
const { className, dockStore } = this.props;
|
||||
const { isOpen, toggle, tabs, toggleFillSize, selectedTab, hasTabs, fullSize } = this.props.dockStore;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -142,11 +150,11 @@ export class Dock extends React.Component<Props> {
|
||||
<div className="toolbar flex gaps align-center box grow">
|
||||
<div className="dock-menu box grow">
|
||||
<MenuActions usePortal triggerIcon={{ material: "add", className: "new-dock-tab", tooltip: "New tab" }} closeOnScroll={false}>
|
||||
<MenuItem className="create-terminal-tab" onClick={() => createTerminalTab()}>
|
||||
<MenuItem className="create-terminal-tab" onClick={() => this.props.createTerminalTab()}>
|
||||
<Icon small svg="terminal" size={15} />
|
||||
Terminal session
|
||||
</MenuItem>
|
||||
<MenuItem className="create-resource-tab" onClick={() => createResourceTab()}>
|
||||
<MenuItem className="create-resource-tab" onClick={() => this.props.createResourceTab()}>
|
||||
<Icon small material="create" />
|
||||
Create resource
|
||||
</MenuItem>
|
||||
@@ -173,3 +181,17 @@ export class Dock extends React.Component<Props> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const Dock = withInjectables<Dependencies, Props>(
|
||||
NonInjectedDock,
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
createResourceTab: di.inject(createResourceTabInjectable),
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
createTerminalTab: di.inject(createTerminalTabInjectable),
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import dockStoreInjectable from "../dock-store/dock-store.injectable";
|
||||
import { EditResourceStore } from "./edit-resource.store";
|
||||
import createStorageInjectable from "../../../utils/create-storage/create-storage.injectable";
|
||||
|
||||
const editResourceStoreInjectable = getInjectable({
|
||||
instantiate: (di) =>
|
||||
new EditResourceStore({
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
createStorage: di.inject(createStorageInjectable),
|
||||
}),
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default editResourceStoreInjectable;
|
||||
+17
-37
@@ -19,13 +19,13 @@
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { autoBind, noop } from "../../utils";
|
||||
import { DockTabStore } from "./dock-tab.store";
|
||||
import { autoBind, noop, StorageHelper } from "../../../utils";
|
||||
import { DockTabStorageState, DockTabStore } from "../dock-tab-store/dock-tab.store";
|
||||
import { autorun, IReactionDisposer } from "mobx";
|
||||
import { dockStore, DockTab, DockTabCreateSpecific, TabId, TabKind } from "./dock.store";
|
||||
import type { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||
import { apiManager } from "../../../common/k8s-api/api-manager";
|
||||
import type { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
|
||||
import type { DockStore, DockTab, TabId } from "../dock-store/dock.store";
|
||||
import type { KubeObject } from "../../../../common/k8s-api/kube-object";
|
||||
import { apiManager } from "../../../../common/k8s-api/api-manager";
|
||||
import type { KubeObjectStore } from "../../../../common/k8s-api/kube-object.store";
|
||||
|
||||
export interface EditingResource {
|
||||
resource: string; // resource path, e.g. /api/v1/namespaces/default
|
||||
@@ -33,13 +33,19 @@ export interface EditingResource {
|
||||
firstDraft?: string;
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
dockStore: DockStore
|
||||
createStorage:<T> (storageKey: string, options: DockTabStorageState<T>) => StorageHelper<DockTabStorageState<T>>
|
||||
}
|
||||
|
||||
export class EditResourceStore extends DockTabStore<EditingResource> {
|
||||
private watchers = new Map<TabId, IReactionDisposer>();
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
constructor(protected dependencies: Dependencies) {
|
||||
super(dependencies, {
|
||||
storageKey: "edit_resource_store",
|
||||
});
|
||||
|
||||
autoBind(this);
|
||||
}
|
||||
|
||||
@@ -56,7 +62,7 @@ export class EditResourceStore extends DockTabStore<EditingResource> {
|
||||
const store = apiManager.getStore(resource);
|
||||
|
||||
if (store) {
|
||||
const isActiveTab = dockStore.isOpen && dockStore.selectedTabId === tabId;
|
||||
const isActiveTab = this.dependencies.dockStore.isOpen && this.dependencies.dockStore.selectedTabId === tabId;
|
||||
const obj = store.getByPath(resource);
|
||||
|
||||
// preload resource for editing
|
||||
@@ -65,7 +71,7 @@ export class EditResourceStore extends DockTabStore<EditingResource> {
|
||||
}
|
||||
// auto-close tab when resource removed from store
|
||||
else if (!obj && store.isLoaded) {
|
||||
dockStore.closeTab(tabId);
|
||||
this.dependencies.dockStore.closeTab(tabId);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
@@ -102,7 +108,7 @@ export class EditResourceStore extends DockTabStore<EditingResource> {
|
||||
return object.selfLink === resource;
|
||||
}) || [];
|
||||
|
||||
return dockStore.getTabById(tabId);
|
||||
return this.dependencies.dockStore.getTabById(tabId);
|
||||
}
|
||||
|
||||
clearInitialDraft(tabId: TabId): void {
|
||||
@@ -117,29 +123,3 @@ export class EditResourceStore extends DockTabStore<EditingResource> {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const editResourceStore = new EditResourceStore();
|
||||
|
||||
export function editResourceTab(object: KubeObject, tabParams: DockTabCreateSpecific = {}) {
|
||||
// use existing tab if already opened
|
||||
let tab = editResourceStore.getTabByResource(object);
|
||||
|
||||
if (tab) {
|
||||
dockStore.open();
|
||||
dockStore.selectTab(tab.id);
|
||||
}
|
||||
|
||||
// or create new tab
|
||||
if (!tab) {
|
||||
tab = dockStore.createTab({
|
||||
title: `${object.kind}: ${object.getName()}`,
|
||||
...tabParams,
|
||||
kind: TabKind.EDIT_RESOURCE,
|
||||
}, false);
|
||||
editResourceStore.setData(tab.id, {
|
||||
resource: object.selfLink,
|
||||
});
|
||||
}
|
||||
|
||||
return tab;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { editResourceTab } from "./edit-resource-tab";
|
||||
import editResourceStoreInjectable from "../edit-resource-store/edit-resource-store.injectable";
|
||||
import dockStoreInjectable from "../dock-store/dock-store.injectable";
|
||||
|
||||
const editResourceTabInjectable = getInjectable({
|
||||
instantiate: (di) => editResourceTab({
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
editResourceStore: di.inject(editResourceStoreInjectable),
|
||||
}),
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default editResourceTabInjectable;
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import type { KubeObject } from "../../../../common/k8s-api/kube-object";
|
||||
import { DockStore, DockTabCreateSpecific, TabKind } from "../dock-store/dock.store";
|
||||
import type { EditResourceStore } from "../edit-resource-store/edit-resource.store";
|
||||
|
||||
interface Dependencies {
|
||||
dockStore: DockStore;
|
||||
editResourceStore: EditResourceStore;
|
||||
}
|
||||
|
||||
export const editResourceTab =
|
||||
({ dockStore, editResourceStore }: Dependencies) =>
|
||||
(object: KubeObject, tabParams: DockTabCreateSpecific = {}) => {
|
||||
// use existing tab if already opened
|
||||
let tab = editResourceStore.getTabByResource(object);
|
||||
|
||||
if (tab) {
|
||||
dockStore.open();
|
||||
dockStore.selectTab(tab.id);
|
||||
}
|
||||
|
||||
// or create new tab
|
||||
if (!tab) {
|
||||
tab = dockStore.createTab(
|
||||
{
|
||||
title: `${object.kind}: ${object.getName()}`,
|
||||
...tabParams,
|
||||
kind: TabKind.EDIT_RESOURCE,
|
||||
},
|
||||
false,
|
||||
);
|
||||
editResourceStore.setData(tab.id, {
|
||||
resource: object.selfLink,
|
||||
});
|
||||
}
|
||||
|
||||
return tab;
|
||||
};
|
||||
@@ -25,24 +25,30 @@ import React from "react";
|
||||
import { computed, makeObservable, observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import yaml from "js-yaml";
|
||||
import type { DockTab } from "./dock.store";
|
||||
import { editResourceStore } from "./edit-resource.store";
|
||||
import type { DockTab } from "./dock-store/dock.store";
|
||||
import type { EditResourceStore } from "./edit-resource-store/edit-resource.store";
|
||||
import { InfoPanel } from "./info-panel";
|
||||
import { Badge } from "../badge";
|
||||
import { EditorPanel } from "./editor-panel";
|
||||
import { Spinner } from "../spinner";
|
||||
import type { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||
import { createPatch } from "rfc6902";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import editResourceStoreInjectable from "./edit-resource-store/edit-resource-store.injectable";
|
||||
|
||||
interface Props {
|
||||
tab: DockTab;
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
editResourceStore: EditResourceStore
|
||||
}
|
||||
|
||||
@observer
|
||||
export class EditResource extends React.Component<Props> {
|
||||
class NonInjectedEditResource extends React.Component<Props & Dependencies> {
|
||||
@observable error = "";
|
||||
|
||||
constructor(props: Props) {
|
||||
constructor(props: Props & Dependencies) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
}
|
||||
@@ -52,11 +58,11 @@ export class EditResource extends React.Component<Props> {
|
||||
}
|
||||
|
||||
get isReadyForEditing() {
|
||||
return editResourceStore.isReady(this.tabId);
|
||||
return this.props.editResourceStore.isReady(this.tabId);
|
||||
}
|
||||
|
||||
get resource(): KubeObject | undefined {
|
||||
return editResourceStore.getResource(this.tabId);
|
||||
return this.props.editResourceStore.getResource(this.tabId);
|
||||
}
|
||||
|
||||
@computed get draft(): string {
|
||||
@@ -64,7 +70,7 @@ export class EditResource extends React.Component<Props> {
|
||||
return ""; // wait until tab's data and kube-object resource are loaded
|
||||
}
|
||||
|
||||
const editData = editResourceStore.getData(this.tabId);
|
||||
const editData = this.props.editResourceStore.getData(this.tabId);
|
||||
|
||||
if (typeof editData.draft === "string") {
|
||||
return editData.draft;
|
||||
@@ -76,7 +82,7 @@ export class EditResource extends React.Component<Props> {
|
||||
}
|
||||
|
||||
saveDraft(draft: string) {
|
||||
editResourceStore.getData(this.tabId).draft = draft;
|
||||
this.props.editResourceStore.getData(this.tabId).draft = draft;
|
||||
}
|
||||
|
||||
onChange = (draft: string) => {
|
||||
@@ -93,13 +99,13 @@ export class EditResource extends React.Component<Props> {
|
||||
return null;
|
||||
}
|
||||
|
||||
const store = editResourceStore.getStore(this.tabId);
|
||||
const store = this.props.editResourceStore.getStore(this.tabId);
|
||||
const currentVersion = yaml.load(this.draft);
|
||||
const firstVersion = yaml.load(editResourceStore.getData(this.tabId).firstDraft ?? this.draft);
|
||||
const firstVersion = yaml.load(this.props.editResourceStore.getData(this.tabId).firstDraft ?? this.draft);
|
||||
const patches = createPatch(firstVersion, currentVersion);
|
||||
const updatedResource = await store.patch(this.resource, patches);
|
||||
|
||||
editResourceStore.clearInitialDraft(this.tabId);
|
||||
this.props.editResourceStore.clearInitialDraft(this.tabId);
|
||||
|
||||
return (
|
||||
<p>
|
||||
@@ -141,3 +147,14 @@ export class EditResource extends React.Component<Props> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const EditResource = withInjectables<Dependencies, Props>(
|
||||
NonInjectedEditResource,
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
editResourceStore: di.inject(editResourceStoreInjectable),
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
);
|
||||
@@ -24,9 +24,11 @@ import throttle from "lodash/throttle";
|
||||
import React from "react";
|
||||
import { makeObservable, observable, reaction } from "mobx";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import { dockStore, TabId } from "./dock.store";
|
||||
import type { DockStore, TabId } from "./dock-store/dock.store";
|
||||
import { cssNames } from "../../utils";
|
||||
import { MonacoEditor, MonacoEditorProps } from "../monaco-editor";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import dockStoreInjectable from "./dock-store/dock-store.injectable";
|
||||
|
||||
export interface EditorPanelProps {
|
||||
tabId: TabId;
|
||||
@@ -37,17 +39,21 @@ export interface EditorPanelProps {
|
||||
onError?: MonacoEditorProps["onError"];
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
dockStore: DockStore
|
||||
}
|
||||
|
||||
const defaultProps: Partial<EditorPanelProps> = {
|
||||
autoFocus: true,
|
||||
};
|
||||
|
||||
@observer
|
||||
export class EditorPanel extends React.Component<EditorPanelProps> {
|
||||
class NonInjectedEditorPanel extends React.Component<EditorPanelProps & Dependencies> {
|
||||
static defaultProps = defaultProps as object;
|
||||
|
||||
@observable.ref editor?: MonacoEditor;
|
||||
|
||||
constructor(props: EditorPanelProps) {
|
||||
constructor(props: EditorPanelProps & Dependencies) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
}
|
||||
@@ -55,12 +61,12 @@ export class EditorPanel extends React.Component<EditorPanelProps> {
|
||||
componentDidMount() {
|
||||
disposeOnUnmount(this, [
|
||||
// keep focus on editor's area when <Dock/> just opened
|
||||
reaction(() => dockStore.isOpen, isOpen => isOpen && this.editor?.focus(), {
|
||||
reaction(() => this.props.dockStore.isOpen, isOpen => isOpen && this.editor?.focus(), {
|
||||
fireImmediately: true,
|
||||
}),
|
||||
|
||||
// focus to editor on dock's resize or turning into fullscreen mode
|
||||
dockStore.onResize(throttle(() => this.editor?.focus(), 250)),
|
||||
this.props.dockStore.onResize(throttle(() => this.editor?.focus(), 250)),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -82,3 +88,14 @@ export class EditorPanel extends React.Component<EditorPanelProps> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const EditorPanel = withInjectables<Dependencies, EditorPanelProps>(
|
||||
NonInjectedEditorPanel,
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
);
|
||||
@@ -28,8 +28,10 @@ import { cssNames } from "../../utils";
|
||||
import { Button } from "../button";
|
||||
import { Icon } from "../icon";
|
||||
import { Spinner } from "../spinner";
|
||||
import { dockStore, TabId } from "./dock.store";
|
||||
import type { DockStore, TabId } from "./dock-store/dock.store";
|
||||
import { Notifications } from "../notifications";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import dockStoreInjectable from "./dock-store/dock-store.injectable";
|
||||
|
||||
interface Props extends OptionalProps {
|
||||
tabId: TabId;
|
||||
@@ -50,8 +52,12 @@ interface OptionalProps {
|
||||
showStatusPanel?: boolean;
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
dockStore: DockStore
|
||||
}
|
||||
|
||||
@observer
|
||||
export class InfoPanel extends Component<Props> {
|
||||
class NonInjectedInfoPanel extends Component<Props & Dependencies> {
|
||||
static defaultProps: OptionalProps = {
|
||||
submitLabel: "Submit",
|
||||
submittingMessage: "Submitting..",
|
||||
@@ -65,7 +71,7 @@ export class InfoPanel extends Component<Props> {
|
||||
@observable error = "";
|
||||
@observable waiting = false;
|
||||
|
||||
constructor(props: Props) {
|
||||
constructor(props: Props & Dependencies) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
}
|
||||
@@ -104,7 +110,7 @@ export class InfoPanel extends Component<Props> {
|
||||
};
|
||||
|
||||
close = () => {
|
||||
dockStore.closeTab(this.props.tabId);
|
||||
this.props.dockStore.closeTab(this.props.tabId);
|
||||
};
|
||||
|
||||
renderErrorIcon() {
|
||||
@@ -159,3 +165,14 @@ export class InfoPanel extends Component<Props> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const InfoPanel = withInjectables<Dependencies, Props>(
|
||||
NonInjectedInfoPanel,
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { InstallChartStore } from "./install-chart.store";
|
||||
import dockStoreInjectable from "../dock-store/dock-store.injectable";
|
||||
import createDockTabStoreInjectable from "../dock-tab-store/create-dock-tab-store.injectable";
|
||||
import type { IReleaseUpdateDetails } from "../../../../common/k8s-api/endpoints/helm-releases.api";
|
||||
import createStorageInjectable from "../../../utils/create-storage/create-storage.injectable";
|
||||
|
||||
const installChartStoreInjectable = getInjectable({
|
||||
instantiate: (di) => {
|
||||
const createDockTabStore = di.inject(createDockTabStoreInjectable);
|
||||
|
||||
return new InstallChartStore({
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
createStorage: di.inject(createStorageInjectable),
|
||||
versionsStore: createDockTabStore<string[]>(),
|
||||
detailsStore: createDockTabStore<IReleaseUpdateDetails>(),
|
||||
});
|
||||
},
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default installChartStoreInjectable;
|
||||
+29
-35
@@ -20,11 +20,12 @@
|
||||
*/
|
||||
|
||||
import { action, autorun, makeObservable } from "mobx";
|
||||
import { dockStore, DockTabCreateSpecific, TabId, TabKind } from "./dock.store";
|
||||
import { DockTabStore } from "./dock-tab.store";
|
||||
import { getChartDetails, getChartValues, HelmChart } from "../../../common/k8s-api/endpoints/helm-charts.api";
|
||||
import type { IReleaseUpdateDetails } from "../../../common/k8s-api/endpoints/helm-releases.api";
|
||||
import { Notifications } from "../notifications";
|
||||
import { DockStore, TabId, TabKind } from "../dock-store/dock.store";
|
||||
import { DockTabStorageState, DockTabStore } from "../dock-tab-store/dock-tab.store";
|
||||
import { getChartDetails, getChartValues } from "../../../../common/k8s-api/endpoints/helm-charts.api";
|
||||
import type { IReleaseUpdateDetails } from "../../../../common/k8s-api/endpoints/helm-releases.api";
|
||||
import { Notifications } from "../../notifications";
|
||||
import type { StorageHelper } from "../../../utils";
|
||||
|
||||
export interface IChartInstallData {
|
||||
name: string;
|
||||
@@ -37,17 +38,24 @@ export interface IChartInstallData {
|
||||
lastVersion?: boolean;
|
||||
}
|
||||
|
||||
export class InstallChartStore extends DockTabStore<IChartInstallData> {
|
||||
public versions = new DockTabStore<string[]>();
|
||||
public details = new DockTabStore<IReleaseUpdateDetails>();
|
||||
interface Dependencies {
|
||||
dockStore: DockStore,
|
||||
createStorage: <T>(storageKey: string, options: DockTabStorageState<T>) => StorageHelper<DockTabStorageState<T>>
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
storageKey: "install_charts",
|
||||
});
|
||||
versionsStore: DockTabStore<string[]>,
|
||||
detailsStore: DockTabStore<IReleaseUpdateDetails>
|
||||
}
|
||||
|
||||
export class InstallChartStore extends DockTabStore<IChartInstallData> {
|
||||
constructor(protected dependencies: Dependencies) {
|
||||
super(
|
||||
dependencies,
|
||||
{ storageKey: "install_charts" },
|
||||
);
|
||||
|
||||
makeObservable(this);
|
||||
autorun(() => {
|
||||
const { selectedTab, isOpen } = dockStore;
|
||||
const { selectedTab, isOpen } = dependencies.dockStore;
|
||||
|
||||
if (selectedTab?.kind === TabKind.INSTALL_CHART && isOpen) {
|
||||
this.loadData(selectedTab.id)
|
||||
@@ -56,6 +64,14 @@ export class InstallChartStore extends DockTabStore<IChartInstallData> {
|
||||
}, { delay: 250 });
|
||||
}
|
||||
|
||||
get versions() {
|
||||
return this.dependencies.versionsStore;
|
||||
}
|
||||
|
||||
get details() {
|
||||
return this.dependencies.detailsStore;
|
||||
}
|
||||
|
||||
@action
|
||||
async loadData(tabId: string) {
|
||||
const promises = [];
|
||||
@@ -99,25 +115,3 @@ export class InstallChartStore extends DockTabStore<IChartInstallData> {
|
||||
super.setData(tabId, data);
|
||||
}
|
||||
}
|
||||
|
||||
export const installChartStore = new InstallChartStore();
|
||||
|
||||
export function createInstallChartTab(chart: HelmChart, tabParams: DockTabCreateSpecific = {}) {
|
||||
const { name, repo, version } = chart;
|
||||
const tab = dockStore.createTab({
|
||||
title: `Helm Install: ${repo}/${name}`,
|
||||
...tabParams,
|
||||
kind: TabKind.INSTALL_CHART,
|
||||
}, false);
|
||||
|
||||
installChartStore.setData(tab.id, {
|
||||
name,
|
||||
repo,
|
||||
version,
|
||||
namespace: "default",
|
||||
releaseName: "",
|
||||
description: "",
|
||||
});
|
||||
|
||||
return tab;
|
||||
}
|
||||
@@ -24,39 +24,52 @@ import "./install-chart.scss";
|
||||
import React, { Component } from "react";
|
||||
import { action, makeObservable, observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { dockStore, DockTab } from "./dock.store";
|
||||
import type { DockStore, DockTab } from "./dock-store/dock.store";
|
||||
import { InfoPanel } from "./info-panel";
|
||||
import { Badge } from "../badge";
|
||||
import { NamespaceSelect } from "../+namespaces/namespace-select";
|
||||
import { prevDefault } from "../../utils";
|
||||
import { IChartInstallData, installChartStore } from "./install-chart.store";
|
||||
import type { IChartInstallData, InstallChartStore } from "./install-chart-store/install-chart.store";
|
||||
import { Spinner } from "../spinner";
|
||||
import { Icon } from "../icon";
|
||||
import { Button } from "../button";
|
||||
import { releaseStore } from "../+apps-releases/release.store";
|
||||
import { LogsDialog } from "../dialog/logs-dialog";
|
||||
import { Select, SelectOption } from "../select";
|
||||
import { Input } from "../input";
|
||||
import { EditorPanel } from "./editor-panel";
|
||||
import { navigate } from "../../navigation";
|
||||
import { releaseURL } from "../../../common/routes";
|
||||
import type {
|
||||
IReleaseCreatePayload,
|
||||
IReleaseUpdateDetails,
|
||||
} from "../../../common/k8s-api/endpoints/helm-releases.api";
|
||||
import releaseStoreInjectable from "../+apps-releases/release-store.injectable";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import installChartStoreInjectable from "./install-chart-store/install-chart-store.injectable";
|
||||
import dockStoreInjectable from "./dock-store/dock-store.injectable";
|
||||
|
||||
interface Props {
|
||||
tab: DockTab;
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
createRelease: (payload: IReleaseCreatePayload) => Promise<IReleaseUpdateDetails>
|
||||
installChartStore: InstallChartStore
|
||||
dockStore: DockStore
|
||||
}
|
||||
|
||||
@observer
|
||||
export class InstallChart extends Component<Props> {
|
||||
class NonInjectedInstallChart extends Component<Props & Dependencies> {
|
||||
@observable error = "";
|
||||
@observable showNotes = false;
|
||||
|
||||
constructor(props: Props) {
|
||||
constructor(props: Props & Dependencies) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
}
|
||||
|
||||
get chartData() {
|
||||
return installChartStore.getData(this.tabId);
|
||||
return this.props.installChartStore.getData(this.tabId);
|
||||
}
|
||||
|
||||
get tabId() {
|
||||
@@ -64,11 +77,11 @@ export class InstallChart extends Component<Props> {
|
||||
}
|
||||
|
||||
get versions() {
|
||||
return installChartStore.versions.getData(this.tabId);
|
||||
return this.props.installChartStore.versions.getData(this.tabId);
|
||||
}
|
||||
|
||||
get releaseDetails() {
|
||||
return installChartStore.details.getData(this.tabId);
|
||||
return this.props.installChartStore.details.getData(this.tabId);
|
||||
}
|
||||
|
||||
viewRelease = () => {
|
||||
@@ -80,20 +93,20 @@ export class InstallChart extends Component<Props> {
|
||||
namespace: release.namespace,
|
||||
},
|
||||
}));
|
||||
dockStore.closeTab(this.tabId);
|
||||
this.props.dockStore.closeTab(this.tabId);
|
||||
};
|
||||
|
||||
save(data: Partial<IChartInstallData>) {
|
||||
const chart = { ...this.chartData, ...data };
|
||||
|
||||
installChartStore.setData(this.tabId, chart);
|
||||
this.props.installChartStore.setData(this.tabId, chart);
|
||||
}
|
||||
|
||||
onVersionChange = (option: SelectOption) => {
|
||||
const version = option.value;
|
||||
|
||||
this.save({ version, values: "" });
|
||||
installChartStore.loadValues(this.tabId);
|
||||
this.props.installChartStore.loadValues(this.tabId);
|
||||
};
|
||||
|
||||
@action
|
||||
@@ -117,13 +130,13 @@ export class InstallChart extends Component<Props> {
|
||||
|
||||
install = async () => {
|
||||
const { repo, name, version, namespace, values, releaseName } = this.chartData;
|
||||
const details = await releaseStore.create({
|
||||
const details = await this.props.createRelease({
|
||||
name: releaseName || undefined,
|
||||
chart: name,
|
||||
repo, namespace, version, values,
|
||||
});
|
||||
|
||||
installChartStore.details.setData(this.tabId, details);
|
||||
this.props.installChartStore.details.setData(this.tabId, details);
|
||||
|
||||
return (
|
||||
<p>Chart Release <b>{details.release.name}</b> successfully created.</p>
|
||||
@@ -219,3 +232,16 @@ export class InstallChart extends Component<Props> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const InstallChart = withInjectables<Dependencies, Props>(
|
||||
NonInjectedInstallChart,
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
createRelease: di.inject(releaseStoreInjectable).create,
|
||||
installChartStore: di.inject(installChartStoreInjectable),
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
);
|
||||
@@ -26,10 +26,12 @@ import { observer } from "mobx-react";
|
||||
|
||||
import { Pod } from "../../../common/k8s-api/endpoints";
|
||||
import { cssNames, saveFileDialog } from "../../utils";
|
||||
import { logStore } from "./log.store";
|
||||
import { Checkbox } from "../checkbox";
|
||||
import { Icon } from "../icon";
|
||||
import type { LogTabData } from "./log-tab.store";
|
||||
import type { LogTabData } from "./log-tab-store/log-tab.store";
|
||||
import type { LogStore } from "./log-store/log.store";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import logStoreInjectable from "./log-store/log-store.injectable";
|
||||
|
||||
interface Props {
|
||||
tabData?: LogTabData
|
||||
@@ -38,8 +40,12 @@ interface Props {
|
||||
reload: () => void
|
||||
}
|
||||
|
||||
export const LogControls = observer((props: Props) => {
|
||||
const { tabData, save, reload, logs } = props;
|
||||
interface Dependencies {
|
||||
logStore: LogStore
|
||||
}
|
||||
|
||||
const NonInjectedLogControls = observer((props: Props & Dependencies) => {
|
||||
const { tabData, save, reload, logs, logStore } = props;
|
||||
|
||||
if (!tabData) {
|
||||
return null;
|
||||
@@ -98,3 +104,15 @@ export const LogControls = observer((props: Props) => {
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export const LogControls = withInjectables<Dependencies, Props>(
|
||||
NonInjectedLogControls,
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
logStore: di.inject(logStoreInjectable),
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -29,15 +29,18 @@ import { action, computed, observable, makeObservable, reaction } from "mobx";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import moment from "moment-timezone";
|
||||
import type { Align, ListOnScrollProps } from "react-window";
|
||||
|
||||
import { SearchStore, searchStore } from "../../../common/search-store";
|
||||
import { SearchStore } from "../../search-store/search-store";
|
||||
import { UserStore } from "../../../common/user-store";
|
||||
import { array, boundMethod, cssNames } from "../../utils";
|
||||
import { Spinner } from "../spinner";
|
||||
import { VirtualList } from "../virtual-list";
|
||||
import { logStore } from "./log.store";
|
||||
import { logTabStore } from "./log-tab.store";
|
||||
import type { LogStore } from "./log-store/log.store";
|
||||
import type { LogTabStore } from "./log-tab-store/log-tab.store";
|
||||
import { ToBottom } from "./to-bottom";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import logTabStoreInjectable from "./log-tab-store/log-tab-store.injectable";
|
||||
import logStoreInjectable from "./log-store/log-store.injectable";
|
||||
import searchStoreInjectable from "../../search-store/search-store.injectable";
|
||||
|
||||
interface Props {
|
||||
logs: string[]
|
||||
@@ -48,8 +51,14 @@ interface Props {
|
||||
|
||||
const colorConverter = new AnsiUp();
|
||||
|
||||
interface Dependencies {
|
||||
logTabStore: LogTabStore
|
||||
logStore: LogStore
|
||||
searchStore: SearchStore
|
||||
}
|
||||
|
||||
@observer
|
||||
export class LogList extends React.Component<Props> {
|
||||
class NonInjectedLogList extends React.Component<Props & Dependencies> {
|
||||
@observable isJumpButtonVisible = false;
|
||||
@observable isLastLineVisible = true;
|
||||
|
||||
@@ -57,7 +66,7 @@ export class LogList extends React.Component<Props> {
|
||||
private virtualListRef = React.createRef<VirtualList>(); // A reference for VirtualList component
|
||||
private lineHeight = 18; // Height of a log line. Should correlate with styles in pod-log-list.scss
|
||||
|
||||
constructor(props: Props) {
|
||||
constructor(props: Props & Dependencies) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
}
|
||||
@@ -108,14 +117,14 @@ export class LogList extends React.Component<Props> {
|
||||
*/
|
||||
@computed
|
||||
get logs() {
|
||||
const showTimestamps = logTabStore.getData(this.props.id)?.showTimestamps;
|
||||
const showTimestamps = this.props.logTabStore.getData(this.props.id)?.showTimestamps;
|
||||
|
||||
if (!showTimestamps) {
|
||||
return logStore.logsWithoutTimestamps;
|
||||
return this.props.logStore.logsWithoutTimestamps;
|
||||
}
|
||||
|
||||
return this.props.logs
|
||||
.map(log => logStore.splitOutTimestamp(log))
|
||||
.map(log => this.props.logStore.splitOutTimestamp(log))
|
||||
.map(([logTimestamp, log]) => (`${logTimestamp && moment.tz(logTimestamp, UserStore.getInstance().localeTimezone).format()}${log}`));
|
||||
}
|
||||
|
||||
@@ -187,7 +196,7 @@ export class LogList extends React.Component<Props> {
|
||||
* @returns A react element with a row itself
|
||||
*/
|
||||
getLogRow = (rowIndex: number) => {
|
||||
const { searchQuery, isActiveOverlay } = searchStore;
|
||||
const { searchQuery, isActiveOverlay } = this.props.searchStore;
|
||||
const item = this.logs[rowIndex];
|
||||
const contents: React.ReactElement[] = [];
|
||||
const ansiToHtml = (ansi: string) => DOMPurify.sanitize(colorConverter.ansi_to_html(ansi));
|
||||
@@ -270,3 +279,17 @@ export class LogList extends React.Component<Props> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const LogList = withInjectables<Dependencies, Props>(
|
||||
NonInjectedLogList,
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
logTabStore: di.inject(logTabStoreInjectable),
|
||||
logStore: di.inject(logStoreInjectable),
|
||||
searchStore: di.inject(searchStoreInjectable),
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -27,9 +27,11 @@ import { observer } from "mobx-react";
|
||||
import { Pod } from "../../../common/k8s-api/endpoints";
|
||||
import { Badge } from "../badge";
|
||||
import { Select, SelectOption } from "../select";
|
||||
import { LogTabData, logTabStore } from "./log-tab.store";
|
||||
import type { LogTabData, LogTabStore } from "./log-tab-store/log-tab.store";
|
||||
import { podsStore } from "../+workloads-pods/pods.store";
|
||||
import type { TabId } from "./dock.store";
|
||||
import type { TabId } from "./dock-store/dock.store";
|
||||
import logTabStoreInjectable from "./log-tab-store/log-tab-store.injectable";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
|
||||
interface Props {
|
||||
tabId: TabId
|
||||
@@ -38,8 +40,12 @@ interface Props {
|
||||
reload: () => void
|
||||
}
|
||||
|
||||
export const LogResourceSelector = observer((props: Props) => {
|
||||
const { tabData, save, reload, tabId } = props;
|
||||
interface Dependencies {
|
||||
logTabStore: LogTabStore
|
||||
}
|
||||
|
||||
const NonInjectedLogResourceSelector = observer((props: Props & Dependencies) => {
|
||||
const { tabData, save, reload, tabId, logTabStore } = props;
|
||||
const { selectedPod, selectedContainer, pods } = tabData;
|
||||
const pod = new Pod(selectedPod);
|
||||
const containers = pod.getContainers();
|
||||
@@ -58,6 +64,7 @@ export const LogResourceSelector = observer((props: Props) => {
|
||||
const selectedPod = podsStore.getByName(option.value, pod.getNs());
|
||||
|
||||
save({ selectedPod });
|
||||
|
||||
logTabStore.renameTab(tabId);
|
||||
};
|
||||
|
||||
@@ -114,3 +121,15 @@ export const LogResourceSelector = observer((props: Props) => {
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export const LogResourceSelector = withInjectables<Dependencies, Props>(
|
||||
NonInjectedLogResourceSelector,
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
logTabStore: di.inject(logTabStoreInjectable),
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -24,8 +24,10 @@ import "./log-search.scss";
|
||||
import React, { useEffect } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { SearchInput } from "../input";
|
||||
import { searchStore } from "../../../common/search-store";
|
||||
import type { SearchStore } from "../../search-store/search-store";
|
||||
import { Icon } from "../icon";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import searchStoreInjectable from "../../search-store/search-store.injectable";
|
||||
|
||||
export interface PodLogSearchProps {
|
||||
onSearch: (query: string) => void
|
||||
@@ -37,8 +39,12 @@ interface Props extends PodLogSearchProps {
|
||||
logs: string[]
|
||||
}
|
||||
|
||||
export const LogSearch = observer((props: Props) => {
|
||||
const { logs, onSearch, toPrevOverlay, toNextOverlay } = props;
|
||||
interface Dependencies {
|
||||
searchStore: SearchStore
|
||||
}
|
||||
|
||||
const NonInjectedLogSearch = observer((props: Props & Dependencies) => {
|
||||
const { logs, onSearch, toPrevOverlay, toNextOverlay, searchStore } = props;
|
||||
const { setNextOverlayActive, setPrevOverlayActive, searchQuery, occurrences, activeFind, totalFinds } = searchStore;
|
||||
const jumpDisabled = !searchQuery || !occurrences.length;
|
||||
const findCounts = (
|
||||
@@ -102,3 +108,14 @@ export const LogSearch = observer((props: Props) => {
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export const LogSearch = withInjectables<Dependencies, Props>(
|
||||
NonInjectedLogSearch,
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
searchStore: di.inject(searchStoreInjectable),
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { LogStore } from "./log.store";
|
||||
import logTabStoreInjectable from "../log-tab-store/log-tab-store.injectable";
|
||||
import dockStoreInjectable from "../dock-store/dock-store.injectable";
|
||||
|
||||
const logStoreInjectable = getInjectable({
|
||||
instantiate: (di) => new LogStore({
|
||||
logTabStore: di.inject(logTabStoreInjectable),
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
}),
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default logStoreInjectable;
|
||||
+14
-11
@@ -21,18 +21,23 @@
|
||||
|
||||
import { autorun, computed, observable, makeObservable } from "mobx";
|
||||
|
||||
import { IPodLogsQuery, Pod, podsApi } from "../../../common/k8s-api/endpoints";
|
||||
import { autoBind, interval } from "../../utils";
|
||||
import { dockStore, TabId, TabKind } from "./dock.store";
|
||||
import { logTabStore } from "./log-tab.store";
|
||||
import { IPodLogsQuery, Pod, podsApi } from "../../../../common/k8s-api/endpoints";
|
||||
import { autoBind, interval } from "../../../utils";
|
||||
import { DockStore, TabId, TabKind } from "../dock-store/dock.store";
|
||||
import type { LogTabStore } from "../log-tab-store/log-tab.store";
|
||||
|
||||
type PodLogLine = string;
|
||||
|
||||
const logLinesToLoad = 500;
|
||||
|
||||
interface Dependencies {
|
||||
logTabStore: LogTabStore
|
||||
dockStore: DockStore
|
||||
}
|
||||
|
||||
export class LogStore {
|
||||
private refresher = interval(10, () => {
|
||||
const id = dockStore.selectedTabId;
|
||||
const id = this.dependencies.dockStore.selectedTabId;
|
||||
|
||||
if (!this.podLogs.get(id)) return;
|
||||
this.loadMore(id);
|
||||
@@ -40,12 +45,12 @@ export class LogStore {
|
||||
|
||||
@observable podLogs = observable.map<TabId, PodLogLine[]>();
|
||||
|
||||
constructor() {
|
||||
constructor(private dependencies: Dependencies) {
|
||||
makeObservable(this);
|
||||
autoBind(this);
|
||||
|
||||
autorun(() => {
|
||||
const { selectedTab, isOpen } = dockStore;
|
||||
const { selectedTab, isOpen } = this.dependencies.dockStore;
|
||||
|
||||
if (selectedTab?.kind === TabKind.POD_LOGS && isOpen) {
|
||||
this.refresher.start();
|
||||
@@ -121,7 +126,7 @@ export class LogStore {
|
||||
* @returns A fetch request promise
|
||||
*/
|
||||
async loadLogs(tabId: TabId, params: Partial<IPodLogsQuery>): Promise<string[]> {
|
||||
const data = logTabStore.getData(tabId);
|
||||
const data = this.dependencies.logTabStore.getData(tabId);
|
||||
const { selectedContainer, previous } = data;
|
||||
const pod = new Pod(data.selectedPod);
|
||||
const namespace = pod.getNs();
|
||||
@@ -152,7 +157,7 @@ export class LogStore {
|
||||
*/
|
||||
@computed
|
||||
get logs() {
|
||||
return this.podLogs.get(dockStore.selectedTabId) ?? [];
|
||||
return this.podLogs.get(this.dependencies.dockStore.selectedTabId) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -201,5 +206,3 @@ export class LogStore {
|
||||
this.podLogs.delete(tabId);
|
||||
}
|
||||
}
|
||||
|
||||
export const logStore = new LogStore();
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { LogTabStore } from "./log-tab.store";
|
||||
import dockStoreInjectable from "../dock-store/dock-store.injectable";
|
||||
import createStorageInjectable from "../../../utils/create-storage/create-storage.injectable";
|
||||
|
||||
const logTabStoreInjectable = getInjectable({
|
||||
instantiate: (di) => new LogTabStore({
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
createStorage: di.inject(createStorageInjectable),
|
||||
}),
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default logTabStoreInjectable;
|
||||
+17
-12
@@ -21,13 +21,14 @@
|
||||
|
||||
import uniqueId from "lodash/uniqueId";
|
||||
import { reaction } from "mobx";
|
||||
import { podsStore } from "../+workloads-pods/pods.store";
|
||||
import { podsStore } from "../../+workloads-pods/pods.store";
|
||||
|
||||
import { IPodContainer, Pod } from "../../../common/k8s-api/endpoints";
|
||||
import type { WorkloadKubeObject } from "../../../common/k8s-api/workload-kube-object";
|
||||
import logger from "../../../common/logger";
|
||||
import { DockTabStore } from "./dock-tab.store";
|
||||
import { dockStore, DockTabCreateSpecific, TabKind } from "./dock.store";
|
||||
import { IPodContainer, Pod } from "../../../../common/k8s-api/endpoints";
|
||||
import type { WorkloadKubeObject } from "../../../../common/k8s-api/workload-kube-object";
|
||||
import logger from "../../../../common/logger";
|
||||
import { DockTabStorageState, DockTabStore } from "../dock-tab-store/dock-tab.store";
|
||||
import { DockStore, DockTabCreateSpecific, TabKind } from "../dock-store/dock.store";
|
||||
import type { StorageHelper } from "../../../utils";
|
||||
|
||||
export interface LogTabData {
|
||||
pods: Pod[];
|
||||
@@ -46,9 +47,14 @@ interface WorkloadLogsTabData {
|
||||
workload: WorkloadKubeObject
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
dockStore: DockStore
|
||||
createStorage: <T>(storageKey: string, options: DockTabStorageState<T>) => StorageHelper<DockTabStorageState<T>>
|
||||
}
|
||||
|
||||
export class LogTabStore extends DockTabStore<LogTabData> {
|
||||
constructor() {
|
||||
super({
|
||||
constructor(protected dependencies: Dependencies) {
|
||||
super(dependencies, {
|
||||
storageKey: "pod_logs",
|
||||
});
|
||||
|
||||
@@ -86,11 +92,11 @@ export class LogTabStore extends DockTabStore<LogTabData> {
|
||||
renameTab(tabId: string) {
|
||||
const { selectedPod } = this.getData(tabId);
|
||||
|
||||
dockStore.renameTab(tabId, `Pod ${selectedPod.metadata.name}`);
|
||||
this.dependencies.dockStore.renameTab(tabId, `Pod ${selectedPod.metadata.name}`);
|
||||
}
|
||||
|
||||
private createDockTab(tabParams: DockTabCreateSpecific) {
|
||||
dockStore.createTab({
|
||||
this.dependencies.dockStore.createTab({
|
||||
...tabParams,
|
||||
kind: TabKind.POD_LOGS,
|
||||
}, false);
|
||||
@@ -143,8 +149,7 @@ export class LogTabStore extends DockTabStore<LogTabData> {
|
||||
|
||||
private closeTab(tabId: string) {
|
||||
this.clearData(tabId);
|
||||
dockStore.closeTab(tabId);
|
||||
this.dependencies.dockStore.closeTab(tabId);
|
||||
}
|
||||
}
|
||||
|
||||
export const logTabStore = new LogTabStore();
|
||||
@@ -23,29 +23,39 @@ import React from "react";
|
||||
import { observable, reaction, makeObservable } from "mobx";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
|
||||
import { searchStore } from "../../../common/search-store";
|
||||
import { boundMethod } from "../../utils";
|
||||
import type { DockTab } from "./dock.store";
|
||||
import type { DockTab } from "./dock-store/dock.store";
|
||||
import { InfoPanel } from "./info-panel";
|
||||
import { LogResourceSelector } from "./log-resource-selector";
|
||||
import { LogList } from "./log-list";
|
||||
import { logStore } from "./log.store";
|
||||
import type { LogStore } from "./log-store/log.store";
|
||||
import { LogSearch } from "./log-search";
|
||||
import { LogControls } from "./log-controls";
|
||||
import { LogTabData, logTabStore } from "./log-tab.store";
|
||||
import type { LogTabData, LogTabStore } from "./log-tab-store/log-tab.store";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import logTabStoreInjectable from "./log-tab-store/log-tab-store.injectable";
|
||||
import logStoreInjectable from "./log-store/log-store.injectable";
|
||||
import type { SearchStore } from "../../search-store/search-store";
|
||||
import searchStoreInjectable from "../../search-store/search-store.injectable";
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
tab: DockTab
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
logTabStore: LogTabStore
|
||||
logStore: LogStore
|
||||
searchStore: SearchStore
|
||||
}
|
||||
|
||||
@observer
|
||||
export class Logs extends React.Component<Props> {
|
||||
class NonInjectedLogs extends React.Component<Props & Dependencies> {
|
||||
@observable isLoading = true;
|
||||
|
||||
private logListElement = React.createRef<LogList>(); // A reference for VirtualList component
|
||||
private logListElement = React.createRef<typeof LogList>(); // A reference for VirtualList component
|
||||
|
||||
constructor(props: Props) {
|
||||
constructor(props: Props & Dependencies) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
}
|
||||
@@ -62,12 +72,12 @@ export class Logs extends React.Component<Props> {
|
||||
|
||||
load = async () => {
|
||||
this.isLoading = true;
|
||||
await logStore.load(this.tabId);
|
||||
await this.props.logStore.load(this.tabId);
|
||||
this.isLoading = false;
|
||||
};
|
||||
|
||||
reload = async () => {
|
||||
logStore.clearLogs(this.tabId);
|
||||
this.props.logStore.clearLogs(this.tabId);
|
||||
await this.load();
|
||||
};
|
||||
|
||||
@@ -85,10 +95,12 @@ export class Logs extends React.Component<Props> {
|
||||
*/
|
||||
@boundMethod
|
||||
toOverlay() {
|
||||
const { activeOverlayLine } = searchStore;
|
||||
const { activeOverlayLine } = this.props.searchStore;
|
||||
|
||||
if (!this.logListElement.current || activeOverlayLine === undefined) return;
|
||||
// Scroll vertically
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
this.logListElement.current.scrollToItem(activeOverlayLine, "center");
|
||||
// Scroll horizontally in timeout since virtual list need some time to prepare its contents
|
||||
setTimeout(() => {
|
||||
@@ -104,14 +116,14 @@ export class Logs extends React.Component<Props> {
|
||||
return null;
|
||||
}
|
||||
|
||||
const logs = logStore.logs;
|
||||
const searchLogs = data.showTimestamps ? logs : logStore.logsWithoutTimestamps;
|
||||
const logs = this.props.logStore.logs;
|
||||
const searchLogs = data.showTimestamps ? logs : this.props.logStore.logsWithoutTimestamps;
|
||||
const controls = (
|
||||
<div className="flex gaps">
|
||||
<LogResourceSelector
|
||||
tabId={this.tabId}
|
||||
tabData={data}
|
||||
save={newData => logTabStore.setData(this.tabId, { ...data, ...newData })}
|
||||
save={newData => this.props.logTabStore.setData(this.tabId, { ...data, ...newData })}
|
||||
reload={this.reload}
|
||||
/>
|
||||
<LogSearch
|
||||
@@ -135,8 +147,8 @@ export class Logs extends React.Component<Props> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const logs = logStore.logs;
|
||||
const data = logTabStore.getData(this.tabId);
|
||||
const logs = this.props.logStore.logs;
|
||||
const data = this.props.logTabStore.getData(this.tabId);
|
||||
|
||||
if (!data) {
|
||||
this.reload();
|
||||
@@ -150,15 +162,30 @@ export class Logs extends React.Component<Props> {
|
||||
id={this.tabId}
|
||||
isLoading={this.isLoading}
|
||||
load={this.load}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
ref={this.logListElement}
|
||||
/>
|
||||
<LogControls
|
||||
logs={logs}
|
||||
tabData={data}
|
||||
save={newData => logTabStore.setData(this.tabId, { ...data, ...newData })}
|
||||
save={newData => this.props.logTabStore.setData(this.tabId, { ...data, ...newData })}
|
||||
reload={this.reload}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const Logs = withInjectables<Dependencies, Props>(
|
||||
NonInjectedLogs,
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
logTabStore: di.inject(logTabStoreInjectable),
|
||||
logStore: di.inject(logStoreInjectable),
|
||||
searchStore: di.inject(searchStoreInjectable),
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { TerminalStore } from "./terminal.store";
|
||||
import createTerminalTabInjectable from "../create-terminal-tab/create-terminal-tab.injectable";
|
||||
import dockStoreInjectable from "../dock-store/dock-store.injectable";
|
||||
import createTerminalInjectable from "../terminal/create-terminal.injectable";
|
||||
|
||||
const terminalStoreInjectable = getInjectable({
|
||||
instantiate: (di) => new TerminalStore({
|
||||
createTerminalTab: di.inject(createTerminalTabInjectable),
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
createTerminal: di.inject(createTerminalInjectable),
|
||||
}),
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default terminalStoreInjectable;
|
||||
+29
-45
@@ -20,36 +20,39 @@
|
||||
*/
|
||||
|
||||
import { autorun, observable, when } from "mobx";
|
||||
import { autoBind, noop, Singleton } from "../../utils";
|
||||
import { Terminal } from "./terminal";
|
||||
import { TerminalApi, TerminalChannels } from "../../api/terminal-api";
|
||||
import { dockStore, DockTab, DockTabCreateSpecific, TabId, TabKind } from "./dock.store";
|
||||
import { WebSocketApiState } from "../../api/websocket-api";
|
||||
import { Notifications } from "../notifications";
|
||||
import { autoBind, noop } from "../../../utils";
|
||||
import type { Terminal } from "../terminal/terminal";
|
||||
import { TerminalApi, TerminalChannels } from "../../../api/terminal-api";
|
||||
import {
|
||||
DockStore,
|
||||
DockTab,
|
||||
DockTabCreate,
|
||||
TabId,
|
||||
TabKind,
|
||||
} from "../dock-store/dock.store";
|
||||
import { WebSocketApiState } from "../../../api/websocket-api";
|
||||
import { Notifications } from "../../notifications";
|
||||
|
||||
export interface ITerminalTab extends DockTab {
|
||||
node?: string; // activate node shell mode
|
||||
}
|
||||
|
||||
export function createTerminalTab(tabParams: DockTabCreateSpecific = {}) {
|
||||
return dockStore.createTab({
|
||||
title: `Terminal`,
|
||||
...tabParams,
|
||||
kind: TabKind.TERMINAL,
|
||||
});
|
||||
interface Dependencies {
|
||||
createTerminalTab: () => DockTabCreate
|
||||
dockStore: DockStore
|
||||
createTerminal: (tabId: TabId, api: TerminalApi) => Terminal
|
||||
}
|
||||
|
||||
export class TerminalStore extends Singleton {
|
||||
export class TerminalStore {
|
||||
protected terminals = new Map<TabId, Terminal>();
|
||||
protected connections = observable.map<TabId, TerminalApi>();
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
constructor(private dependencies: Dependencies) {
|
||||
autoBind(this);
|
||||
|
||||
// connect active tab
|
||||
autorun(() => {
|
||||
const { selectedTab, isOpen } = dockStore;
|
||||
const { selectedTab, isOpen } = dependencies.dockStore;
|
||||
|
||||
if (selectedTab?.kind === TabKind.TERMINAL && isOpen) {
|
||||
this.connect(selectedTab.id);
|
||||
@@ -57,7 +60,7 @@ export class TerminalStore extends Singleton {
|
||||
});
|
||||
// disconnect closed tabs
|
||||
autorun(() => {
|
||||
const currentTabs = dockStore.tabs.map(tab => tab.id);
|
||||
const currentTabs = dependencies.dockStore.tabs.map(tab => tab.id);
|
||||
|
||||
for (const [tabId] of this.connections) {
|
||||
if (!currentTabs.includes(tabId)) this.disconnect(tabId);
|
||||
@@ -69,12 +72,12 @@ export class TerminalStore extends Singleton {
|
||||
if (this.isConnected(tabId)) {
|
||||
return;
|
||||
}
|
||||
const tab: ITerminalTab = dockStore.getTabById(tabId);
|
||||
const tab: ITerminalTab = this.dependencies.dockStore.getTabById(tabId);
|
||||
const api = new TerminalApi({
|
||||
id: tabId,
|
||||
node: tab.node,
|
||||
});
|
||||
const terminal = new Terminal(tabId, api);
|
||||
const terminal = this.dependencies.createTerminal(tabId, api);
|
||||
|
||||
this.connections.set(tabId, api);
|
||||
this.terminals.set(tabId, terminal);
|
||||
@@ -111,11 +114,11 @@ export class TerminalStore extends Singleton {
|
||||
const { enter, newTab, tabId } = options;
|
||||
|
||||
if (tabId) {
|
||||
dockStore.selectTab(tabId);
|
||||
this.dependencies.dockStore.selectTab(tabId);
|
||||
}
|
||||
|
||||
if (newTab) {
|
||||
const tab = createTerminalTab();
|
||||
const tab = this.dependencies.createTerminalTab();
|
||||
|
||||
await when(() => this.connections.has(tab.id));
|
||||
|
||||
@@ -134,7 +137,7 @@ export class TerminalStore extends Singleton {
|
||||
clearTimeout(notifyVeryLong);
|
||||
}
|
||||
|
||||
const terminalApi = this.connections.get(dockStore.selectedTabId);
|
||||
const terminalApi = this.connections.get(this.dependencies.dockStore.selectedTabId);
|
||||
|
||||
if (terminalApi) {
|
||||
if (enter) {
|
||||
@@ -146,7 +149,10 @@ export class TerminalStore extends Singleton {
|
||||
data: command,
|
||||
});
|
||||
} else {
|
||||
console.warn("The selected tab is does not have a connection. Cannot send command.", { tabId: dockStore.selectedTabId, command });
|
||||
console.warn(
|
||||
"The selected tab is does not have a connection. Cannot send command.",
|
||||
{ tabId: this.dependencies.dockStore.selectedTabId, command },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,25 +166,3 @@ export class TerminalStore extends Singleton {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use `TerminalStore.getInstance()` instead
|
||||
*/
|
||||
export const terminalStore = new Proxy({}, {
|
||||
get(target, p) {
|
||||
if (p === "$$typeof") {
|
||||
return "TerminalStore";
|
||||
}
|
||||
|
||||
const ts = TerminalStore.getInstance();
|
||||
const res = (ts as any)?.[p];
|
||||
|
||||
if (typeof res === "function") {
|
||||
return function (...args: any[]) {
|
||||
return res.apply(ts, args);
|
||||
};
|
||||
}
|
||||
|
||||
return res;
|
||||
},
|
||||
}) as TerminalStore;
|
||||
@@ -26,18 +26,26 @@ import { observer } from "mobx-react";
|
||||
import { boundMethod, cssNames } from "../../utils";
|
||||
import { DockTab, DockTabProps } from "./dock-tab";
|
||||
import { Icon } from "../icon";
|
||||
import { terminalStore } from "./terminal.store";
|
||||
import { dockStore } from "./dock.store";
|
||||
import type { TerminalStore } from "./terminal-store/terminal.store";
|
||||
import type { DockStore } from "./dock-store/dock.store";
|
||||
import { reaction } from "mobx";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import dockStoreInjectable from "./dock-store/dock-store.injectable";
|
||||
import terminalStoreInjectable from "./terminal-store/terminal-store.injectable";
|
||||
|
||||
interface Props extends DockTabProps {
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
dockStore: DockStore
|
||||
terminalStore: TerminalStore
|
||||
}
|
||||
|
||||
@observer
|
||||
export class TerminalTab extends React.Component<Props> {
|
||||
class NonInjectedTerminalTab extends React.Component<Props & Dependencies> {
|
||||
componentDidMount() {
|
||||
reaction(() => this.isDisconnected === true, () => {
|
||||
dockStore.closeTab(this.tabId);
|
||||
this.props.dockStore.closeTab(this.tabId);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -46,12 +54,12 @@ export class TerminalTab extends React.Component<Props> {
|
||||
}
|
||||
|
||||
get isDisconnected() {
|
||||
return terminalStore.isDisconnected(this.tabId);
|
||||
return this.props.terminalStore.isDisconnected(this.tabId);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
reconnect() {
|
||||
terminalStore.reconnect(this.tabId);
|
||||
this.props.terminalStore.reconnect(this.tabId);
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -60,9 +68,11 @@ export class TerminalTab extends React.Component<Props> {
|
||||
disconnected: this.isDisconnected,
|
||||
});
|
||||
|
||||
const { dockStore, terminalStore, ...tabProps } = this.props;
|
||||
|
||||
return (
|
||||
<DockTab
|
||||
{...this.props}
|
||||
{...tabProps}
|
||||
className={className}
|
||||
icon={tabIcon}
|
||||
moreActions={this.isDisconnected && (
|
||||
@@ -78,3 +88,16 @@ export class TerminalTab extends React.Component<Props> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const TerminalTab = withInjectables<Dependencies, Props>(
|
||||
NonInjectedTerminalTab,
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
terminalStore: di.inject(terminalStoreInjectable),
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -24,29 +24,37 @@ import "./terminal-window.scss";
|
||||
import React from "react";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import { cssNames } from "../../utils";
|
||||
import type { Terminal } from "./terminal";
|
||||
import { TerminalStore } from "./terminal.store";
|
||||
import type { Terminal } from "./terminal/terminal";
|
||||
import type { TerminalStore } from "./terminal-store/terminal.store";
|
||||
import { ThemeStore } from "../../theme.store";
|
||||
import { dockStore, DockTab, TabKind, TabId } from "./dock.store";
|
||||
import { DockTab, TabKind, TabId, DockStore } from "./dock-store/dock.store";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import dockStoreInjectable from "./dock-store/dock-store.injectable";
|
||||
import terminalStoreInjectable from "./terminal-store/terminal-store.injectable";
|
||||
|
||||
interface Props {
|
||||
tab: DockTab;
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
dockStore: DockStore
|
||||
terminalStore: TerminalStore
|
||||
}
|
||||
|
||||
@observer
|
||||
export class TerminalWindow extends React.Component<Props> {
|
||||
class NonInjectedTerminalWindow extends React.Component<Props & Dependencies> {
|
||||
public elem: HTMLElement;
|
||||
public terminal: Terminal;
|
||||
|
||||
componentDidMount() {
|
||||
disposeOnUnmount(this, [
|
||||
dockStore.onTabChange(({ tabId }) => this.activate(tabId), {
|
||||
this.props.dockStore.onTabChange(({ tabId }) => this.activate(tabId), {
|
||||
tabKind: TabKind.TERMINAL,
|
||||
fireImmediately: true,
|
||||
}),
|
||||
|
||||
// refresh terminal available space (cols/rows) when <Dock/> resized
|
||||
dockStore.onResize(() => this.terminal?.fitLazy(), {
|
||||
this.props.dockStore.onResize(() => this.terminal?.fitLazy(), {
|
||||
fireImmediately: true,
|
||||
}),
|
||||
]);
|
||||
@@ -54,7 +62,7 @@ export class TerminalWindow extends React.Component<Props> {
|
||||
|
||||
activate(tabId: TabId) {
|
||||
this.terminal?.detach(); // detach previous
|
||||
this.terminal = TerminalStore.getInstance().getTerminal(tabId);
|
||||
this.terminal = this.props.terminalStore.getTerminal(tabId);
|
||||
this.terminal.attachTo(this.elem);
|
||||
}
|
||||
|
||||
@@ -67,3 +75,16 @@ export class TerminalWindow extends React.Component<Props> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const TerminalWindow = withInjectables<Dependencies, Props>(
|
||||
NonInjectedTerminalWindow,
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
terminalStore: di.inject(terminalStoreInjectable),
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { Terminal } from "./terminal";
|
||||
import type { TabId } from "../dock-store/dock.store";
|
||||
import type { TerminalApi } from "../../../api/terminal-api";
|
||||
import dockStoreInjectable from "../dock-store/dock-store.injectable";
|
||||
|
||||
const createTerminalInjectable = getInjectable({
|
||||
instantiate: (di) => {
|
||||
const dependencies = {
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
};
|
||||
|
||||
return (tabId: TabId, api: TerminalApi) =>
|
||||
new Terminal(dependencies, tabId, api);
|
||||
},
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default createTerminalInjectable;
|
||||
+15
-11
@@ -23,15 +23,19 @@ import debounce from "lodash/debounce";
|
||||
import { reaction } from "mobx";
|
||||
import { Terminal as XTerm } from "xterm";
|
||||
import { FitAddon } from "xterm-addon-fit";
|
||||
import { dockStore, TabId } from "./dock.store";
|
||||
import { TerminalApi, TerminalChannels } from "../../api/terminal-api";
|
||||
import { ThemeStore } from "../../theme.store";
|
||||
import { boundMethod, disposer } from "../../utils";
|
||||
import { isMac } from "../../../common/vars";
|
||||
import type { DockStore, TabId } from "../dock-store/dock.store";
|
||||
import { TerminalApi, TerminalChannels } from "../../../api/terminal-api";
|
||||
import { ThemeStore } from "../../../theme.store";
|
||||
import { boundMethod, disposer } from "../../../utils";
|
||||
import { isMac } from "../../../../common/vars";
|
||||
import { camelCase, once } from "lodash";
|
||||
import { UserStore } from "../../../common/user-store";
|
||||
import { UserStore } from "../../../../common/user-store";
|
||||
import { clipboard } from "electron";
|
||||
import logger from "../../../common/logger";
|
||||
import logger from "../../../../common/logger";
|
||||
|
||||
interface Dependencies {
|
||||
dockStore: DockStore
|
||||
}
|
||||
|
||||
export class Terminal {
|
||||
public static readonly spawningPool = (() => {
|
||||
@@ -47,7 +51,7 @@ export class Terminal {
|
||||
})();
|
||||
|
||||
static async preloadFonts() {
|
||||
const fontPath = require("../fonts/roboto-mono-nerd.ttf").default; // eslint-disable-line @typescript-eslint/no-var-requires
|
||||
const fontPath = require("../../fonts/roboto-mono-nerd.ttf").default; // eslint-disable-line @typescript-eslint/no-var-requires
|
||||
const fontFace = new FontFace("RobotoMono", `url(${fontPath})`);
|
||||
|
||||
await fontFace.load();
|
||||
@@ -90,7 +94,7 @@ export class Terminal {
|
||||
}
|
||||
|
||||
get isActive() {
|
||||
const { isOpen, selectedTabId } = dockStore;
|
||||
const { isOpen, selectedTabId } = this.dependencies.dockStore;
|
||||
|
||||
return isOpen && selectedTabId === this.tabId;
|
||||
}
|
||||
@@ -108,7 +112,7 @@ export class Terminal {
|
||||
}
|
||||
}
|
||||
|
||||
constructor(public tabId: TabId, protected api: TerminalApi) {
|
||||
constructor(private dependencies: Dependencies, public tabId: TabId, protected api: TerminalApi) {
|
||||
// enable terminal addons
|
||||
this.xterm.loadAddon(this.fitAddon);
|
||||
|
||||
@@ -132,7 +136,7 @@ export class Terminal {
|
||||
reaction(() => ThemeStore.getInstance().activeTheme.colors, this.setTheme, {
|
||||
fireImmediately: true,
|
||||
}),
|
||||
dockStore.onResize(this.onResize),
|
||||
dependencies.dockStore.onResize(this.onResize),
|
||||
() => onDataHandler.dispose(),
|
||||
() => this.fitAddon.dispose(),
|
||||
() => this.api.removeAllListeners(),
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { UpgradeChartStore } from "./upgrade-chart.store";
|
||||
import releaseStoreInjectable from "../../+apps-releases/release-store.injectable";
|
||||
import dockStoreInjectable from "../dock-store/dock-store.injectable";
|
||||
import createDockTabStoreInjectable from "../dock-tab-store/create-dock-tab-store.injectable";
|
||||
import createStorageInjectable from "../../../utils/create-storage/create-storage.injectable";
|
||||
|
||||
const upgradeChartStoreInjectable = getInjectable({
|
||||
instantiate: (di) => {
|
||||
const createDockTabStore = di.inject(createDockTabStoreInjectable);
|
||||
|
||||
const valuesStore = createDockTabStore<string>();
|
||||
|
||||
return new UpgradeChartStore({
|
||||
releaseStore: di.inject(releaseStoreInjectable),
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
createStorage: di.inject(createStorageInjectable),
|
||||
valuesStore,
|
||||
});
|
||||
},
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default upgradeChartStoreInjectable;
|
||||
+27
-44
@@ -20,35 +20,44 @@
|
||||
*/
|
||||
|
||||
import { action, autorun, computed, IReactionDisposer, reaction, makeObservable } from "mobx";
|
||||
import { dockStore, DockTab, DockTabCreateSpecific, TabId, TabKind } from "./dock.store";
|
||||
import { DockTabStore } from "./dock-tab.store";
|
||||
import { getReleaseValues, HelmRelease } from "../../../common/k8s-api/endpoints/helm-releases.api";
|
||||
import { releaseStore } from "../+apps-releases/release.store";
|
||||
import { iter } from "../../utils";
|
||||
import { DockStore, DockTab, TabId, TabKind } from "../dock-store/dock.store";
|
||||
import { DockTabStorageState, DockTabStore } from "../dock-tab-store/dock-tab.store";
|
||||
import { getReleaseValues } from "../../../../common/k8s-api/endpoints/helm-releases.api";
|
||||
import type { ReleaseStore } from "../../+apps-releases/release.store";
|
||||
import { iter, StorageHelper } from "../../../utils";
|
||||
|
||||
export interface IChartUpgradeData {
|
||||
releaseName: string;
|
||||
releaseNamespace: string;
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
releaseStore: ReleaseStore
|
||||
valuesStore: DockTabStore<string>
|
||||
dockStore: DockStore
|
||||
createStorage: <T>(storageKey: string, options: DockTabStorageState<T>) => StorageHelper<DockTabStorageState<T>>
|
||||
}
|
||||
|
||||
export class UpgradeChartStore extends DockTabStore<IChartUpgradeData> {
|
||||
private watchers = new Map<string, IReactionDisposer>();
|
||||
|
||||
values = new DockTabStore<string>();
|
||||
|
||||
@computed private get releaseNameReverseLookup(): Map<string, string> {
|
||||
return new Map(iter.map(this.data, ([id, { releaseName }]) => [releaseName, id]));
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
get values() {
|
||||
return this.dependencies.valuesStore;
|
||||
}
|
||||
|
||||
constructor(protected dependencies : Dependencies) {
|
||||
super(dependencies, {
|
||||
storageKey: "chart_releases",
|
||||
});
|
||||
|
||||
makeObservable(this);
|
||||
|
||||
autorun(() => {
|
||||
const { selectedTab, isOpen } = dockStore;
|
||||
const { selectedTab, isOpen } = dependencies.dockStore;
|
||||
|
||||
if (selectedTab?.kind === TabKind.UPGRADE_CHART && isOpen) {
|
||||
this.loadData(selectedTab.id);
|
||||
@@ -67,20 +76,20 @@ export class UpgradeChartStore extends DockTabStore<IChartUpgradeData> {
|
||||
return;
|
||||
}
|
||||
const dispose = reaction(() => {
|
||||
const release = releaseStore.getByName(releaseName);
|
||||
const release = this.dependencies.releaseStore.getByName(releaseName);
|
||||
|
||||
return release?.getRevision(); // watch changes only by revision
|
||||
},
|
||||
release => {
|
||||
const releaseTab = this.getTabByRelease(releaseName);
|
||||
|
||||
if (!releaseStore.isLoaded || !releaseTab) {
|
||||
if (!this.dependencies.releaseStore.isLoaded || !releaseTab) {
|
||||
return;
|
||||
}
|
||||
|
||||
// auto-reload values if was loaded before
|
||||
if (release) {
|
||||
if (dockStore.selectedTab === releaseTab && this.values.getData(releaseTab.id)) {
|
||||
if (this.dependencies.dockStore.selectedTab === releaseTab && this.values.getData(releaseTab.id)) {
|
||||
this.loadValues(releaseTab.id);
|
||||
}
|
||||
}
|
||||
@@ -88,17 +97,17 @@ export class UpgradeChartStore extends DockTabStore<IChartUpgradeData> {
|
||||
else {
|
||||
dispose();
|
||||
this.watchers.delete(releaseName);
|
||||
dockStore.closeTab(releaseTab.id);
|
||||
this.dependencies.dockStore.closeTab(releaseTab.id);
|
||||
}
|
||||
});
|
||||
|
||||
this.watchers.set(releaseName, dispose);
|
||||
}
|
||||
|
||||
isLoading(tabId = dockStore.selectedTabId) {
|
||||
isLoading(tabId = this.dependencies.dockStore.selectedTabId) {
|
||||
const values = this.values.getData(tabId);
|
||||
|
||||
return !releaseStore.isLoaded || values === undefined;
|
||||
return !this.dependencies.releaseStore.isLoaded || values === undefined;
|
||||
}
|
||||
|
||||
@action
|
||||
@@ -106,7 +115,7 @@ export class UpgradeChartStore extends DockTabStore<IChartUpgradeData> {
|
||||
const values = this.values.getData(tabId);
|
||||
|
||||
await Promise.all([
|
||||
!releaseStore.isLoaded && releaseStore.loadFromContextNamespaces(),
|
||||
!this.dependencies.releaseStore.isLoaded && this.dependencies.releaseStore.loadFromContextNamespaces(),
|
||||
!values && this.loadValues(tabId),
|
||||
]);
|
||||
}
|
||||
@@ -121,32 +130,6 @@ export class UpgradeChartStore extends DockTabStore<IChartUpgradeData> {
|
||||
}
|
||||
|
||||
getTabByRelease(releaseName: string): DockTab {
|
||||
return dockStore.getTabById(this.releaseNameReverseLookup.get(releaseName));
|
||||
return this.dependencies.dockStore.getTabById(this.releaseNameReverseLookup.get(releaseName));
|
||||
}
|
||||
}
|
||||
|
||||
export const upgradeChartStore = new UpgradeChartStore();
|
||||
|
||||
export function createUpgradeChartTab(release: HelmRelease, tabParams: DockTabCreateSpecific = {}) {
|
||||
let tab = upgradeChartStore.getTabByRelease(release.getName());
|
||||
|
||||
if (tab) {
|
||||
dockStore.open();
|
||||
dockStore.selectTab(tab.id);
|
||||
}
|
||||
|
||||
if (!tab) {
|
||||
tab = dockStore.createTab({
|
||||
title: `Helm Upgrade: ${release.getName()}`,
|
||||
...tabParams,
|
||||
kind: TabKind.UPGRADE_CHART,
|
||||
}, false);
|
||||
|
||||
upgradeChartStore.setData(tab.id, {
|
||||
releaseName: release.getName(),
|
||||
releaseNamespace: release.getNs(),
|
||||
});
|
||||
}
|
||||
|
||||
return tab;
|
||||
}
|
||||
@@ -25,29 +25,37 @@ import React from "react";
|
||||
import { action, makeObservable, observable, reaction } from "mobx";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import { cssNames } from "../../utils";
|
||||
import type { DockTab } from "./dock.store";
|
||||
import type { DockTab } from "./dock-store/dock.store";
|
||||
import { InfoPanel } from "./info-panel";
|
||||
import { upgradeChartStore } from "./upgrade-chart.store";
|
||||
import type { UpgradeChartStore } from "./upgrade-chart-store/upgrade-chart.store";
|
||||
import { Spinner } from "../spinner";
|
||||
import { releaseStore } from "../+apps-releases/release.store";
|
||||
import type { ReleaseStore } from "../+apps-releases/release.store";
|
||||
import { Badge } from "../badge";
|
||||
import { EditorPanel } from "./editor-panel";
|
||||
import { helmChartStore, IChartVersion } from "../+apps-helm-charts/helm-chart.store";
|
||||
import type { HelmRelease } from "../../../common/k8s-api/endpoints/helm-releases.api";
|
||||
import { Select, SelectOption } from "../select";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import releaseStoreInjectable from "../+apps-releases/release-store.injectable";
|
||||
import upgradeChartStoreInjectable from "./upgrade-chart-store/upgrade-chart-store.injectable";
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
tab: DockTab;
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
releaseStore: ReleaseStore
|
||||
upgradeChartStore: UpgradeChartStore
|
||||
}
|
||||
|
||||
@observer
|
||||
export class UpgradeChart extends React.Component<Props> {
|
||||
export class NonInjectedUpgradeChart extends React.Component<Props & Dependencies> {
|
||||
@observable error: string;
|
||||
@observable versions = observable.array<IChartVersion>();
|
||||
@observable version: IChartVersion;
|
||||
|
||||
constructor(props: Props) {
|
||||
constructor(props: Props & Dependencies) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
}
|
||||
@@ -65,15 +73,15 @@ export class UpgradeChart extends React.Component<Props> {
|
||||
}
|
||||
|
||||
get release(): HelmRelease {
|
||||
const tabData = upgradeChartStore.getData(this.tabId);
|
||||
const tabData = this.props.upgradeChartStore.getData(this.tabId);
|
||||
|
||||
if (!tabData) return null;
|
||||
|
||||
return releaseStore.getByName(tabData.releaseName);
|
||||
return this.props.releaseStore.getByName(tabData.releaseName);
|
||||
}
|
||||
|
||||
get value() {
|
||||
return upgradeChartStore.values.getData(this.tabId);
|
||||
return this.props.upgradeChartStore.values.getData(this.tabId);
|
||||
}
|
||||
|
||||
async loadVersions() {
|
||||
@@ -89,7 +97,7 @@ export class UpgradeChart extends React.Component<Props> {
|
||||
@action
|
||||
onChange = (value: string) => {
|
||||
this.error = "";
|
||||
upgradeChartStore.values.setData(this.tabId, value);
|
||||
this.props.upgradeChartStore.values.setData(this.tabId, value);
|
||||
};
|
||||
|
||||
@action
|
||||
@@ -103,7 +111,7 @@ export class UpgradeChart extends React.Component<Props> {
|
||||
const releaseName = this.release.getName();
|
||||
const releaseNs = this.release.getNs();
|
||||
|
||||
await releaseStore.update(releaseName, releaseNs, {
|
||||
await this.props.releaseStore.update(releaseName, releaseNs, {
|
||||
chart: this.release.getChart(),
|
||||
values: this.value,
|
||||
repo, version,
|
||||
@@ -127,7 +135,7 @@ export class UpgradeChart extends React.Component<Props> {
|
||||
const { tabId, release, value, error, onChange, onError, upgrade, versions, version } = this;
|
||||
const { className } = this.props;
|
||||
|
||||
if (!release || upgradeChartStore.isLoading() || !version) {
|
||||
if (!release || this.props.upgradeChartStore.isLoading() || !version) {
|
||||
return <Spinner center/>;
|
||||
}
|
||||
const currentVersion = release.getVersion();
|
||||
@@ -169,3 +177,15 @@ export class UpgradeChart extends React.Component<Props> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const UpgradeChart = withInjectables<Dependencies, Props>(
|
||||
NonInjectedUpgradeChart,
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
releaseStore: di.inject(releaseStoreInjectable),
|
||||
upgradeChartStore: di.inject(upgradeChartStoreInjectable),
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
);
|
||||
Reference in new issue
Block a user