mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Full dependency inversion of <Dock> and all current tab kinds
- Fixes pod logs not working on initial load - Removes DockStore as a dependency of all *TabStore - Removes all the reactions about loading new data from the stores - Removes dependencies on selectedTabId from all *TabStore's Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
a9abcf99e4
commit
595fb90a2a
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { Injectable } from "@ogre-tools/injectable";
|
||||
import { getLegacyGlobalDiForExtensionApi } from "./legacy-global-di-for-extension-api";
|
||||
|
||||
type TentativeTuple<T> = T extends object ? [T] : [undefined?];
|
||||
|
||||
export const asLegacyGlobalObjectForExtensionApiWithModifications = <
|
||||
TInjectable extends Injectable<unknown, unknown, TInstantiationParameter>,
|
||||
TInstantiationParameter,
|
||||
>(
|
||||
injectableKey: TInjectable,
|
||||
otherFields: Record<string | symbol, () => any>,
|
||||
...instantiationParameter: TentativeTuple<TInstantiationParameter>
|
||||
) =>
|
||||
new Proxy(
|
||||
{},
|
||||
{
|
||||
get(target, propertyName) {
|
||||
if (propertyName === "$$typeof") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const instance: any = getLegacyGlobalDiForExtensionApi().inject(
|
||||
injectableKey,
|
||||
...instantiationParameter,
|
||||
);
|
||||
|
||||
const propertyValue = instance[propertyName] ?? otherFields[propertyName]?.();
|
||||
|
||||
if (typeof propertyValue === "function") {
|
||||
return function (...args: any[]) {
|
||||
return propertyValue.apply(instance, args);
|
||||
};
|
||||
}
|
||||
|
||||
return propertyValue;
|
||||
},
|
||||
},
|
||||
) as ReturnType<TInjectable["instantiate"]>;
|
||||
@ -1,43 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { Injectable } from "@ogre-tools/injectable";
|
||||
import { getLegacyGlobalDiForExtensionApi } from "./legacy-global-di-for-extension-api";
|
||||
|
||||
type TentativeTuple<T> = T extends object ? [T] : [undefined?];
|
||||
|
||||
export const asLegacyGlobalSingletonForExtensionApi = <
|
||||
TClass extends abstract new (...args: any[]) => any,
|
||||
TInjectable extends Injectable<unknown, unknown, TInstantiationParameter>,
|
||||
TInstantiationParameter,
|
||||
>(
|
||||
Class: TClass,
|
||||
injectableKey: TInjectable,
|
||||
...instantiationParameter: TentativeTuple<TInstantiationParameter>
|
||||
) =>
|
||||
new Proxy(Class, {
|
||||
construct: () => {
|
||||
throw new Error("A legacy singleton class must be created by createInstance()");
|
||||
},
|
||||
|
||||
get: (target: any, propertyName) => {
|
||||
if (propertyName === "getInstance" || propertyName === "createInstance") {
|
||||
return () =>
|
||||
getLegacyGlobalDiForExtensionApi().inject(
|
||||
injectableKey,
|
||||
...instantiationParameter,
|
||||
);
|
||||
}
|
||||
|
||||
if (propertyName === "resetInstance") {
|
||||
return () => getLegacyGlobalDiForExtensionApi().purge(injectableKey);
|
||||
}
|
||||
|
||||
return target[propertyName];
|
||||
},
|
||||
}) as InstanceType<TClass> & {
|
||||
getInstance: () => InstanceType<TClass>;
|
||||
createInstance: () => InstanceType<TClass>;
|
||||
resetInstance: () => void;
|
||||
};
|
||||
@ -3,14 +3,17 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { asLegacyGlobalFunctionForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-function-for-extension-api";
|
||||
import createTerminalTabInjectable from "../../renderer/components/dock/create-terminal-tab/create-terminal-tab.injectable";
|
||||
import terminalStoreInjectable from "../../renderer/components/dock/terminal-store/terminal-store.injectable";
|
||||
import createTerminalTabInjectable from "../../renderer/components/dock/terminal/create-terminal-tab.injectable";
|
||||
import terminalStoreInjectable from "../../renderer/components/dock/terminal/store.injectable";
|
||||
import { asLegacyGlobalObjectForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api";
|
||||
import logTabStoreInjectable from "../../renderer/components/dock/logs/tab-store.injectable";
|
||||
import { asLegacyGlobalSingletonForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-singleton-for-extension-api";
|
||||
import { TerminalStore as TerminalStoreClass } from "../../renderer/components/dock/terminal-store/terminal.store";
|
||||
|
||||
import commandOverlayInjectable from "../../renderer/components/command-palette/command-overlay.injectable";
|
||||
import { asLegacyGlobalObjectForExtensionApiWithModifications } from "../as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api-with-modifications";
|
||||
import createPodLogsTabInjectable from "../../renderer/components/dock/logs/create-pod-logs-tab.injectable";
|
||||
import createWorkloadLogsTabInjectable from "../../renderer/components/dock/logs/create-workload-logs-tab.injectable";
|
||||
import updateTabNameInjectable from "../../renderer/components/dock/logs/update-tab-name.injectable";
|
||||
import sendCommandInjectable from "../../renderer/components/dock/terminal/send-command.injectable";
|
||||
|
||||
// layouts
|
||||
export * from "../../renderer/components/layout/main-layout";
|
||||
@ -71,7 +74,24 @@ export * from "../../renderer/components/+events/kube-event-details";
|
||||
export * from "../../renderer/components/status-brick";
|
||||
|
||||
export const createTerminalTab = asLegacyGlobalFunctionForExtensionApi(createTerminalTabInjectable);
|
||||
export const TerminalStore = asLegacyGlobalSingletonForExtensionApi(TerminalStoreClass, terminalStoreInjectable);
|
||||
export const terminalStore = asLegacyGlobalObjectForExtensionApi(terminalStoreInjectable);
|
||||
export const logTabStore = asLegacyGlobalObjectForExtensionApi(logTabStoreInjectable);
|
||||
export const terminalStore = asLegacyGlobalObjectForExtensionApiWithModifications(terminalStoreInjectable, {
|
||||
sendCommand: () => asLegacyGlobalFunctionForExtensionApi(sendCommandInjectable),
|
||||
});
|
||||
export const logTabStore = asLegacyGlobalObjectForExtensionApiWithModifications(logTabStoreInjectable, {
|
||||
createPodTab: () => asLegacyGlobalFunctionForExtensionApi(createPodLogsTabInjectable),
|
||||
createWorkloadTab: () => asLegacyGlobalFunctionForExtensionApi(createWorkloadLogsTabInjectable),
|
||||
renameTab: () => asLegacyGlobalFunctionForExtensionApi(updateTabNameInjectable),
|
||||
tabs: () => undefined,
|
||||
});
|
||||
|
||||
export class TerminalStore {
|
||||
static getInstance() {
|
||||
return terminalStore;
|
||||
}
|
||||
static createInstance() {
|
||||
return terminalStore;
|
||||
}
|
||||
static resetInstance() {
|
||||
console.warn("TerminalStore.resetInstance() does nothing");
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ import { Select, SelectOption } from "../select";
|
||||
import { Badge } from "../badge";
|
||||
import { Tooltip, withStyles } from "@material-ui/core";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import createInstallChartTabInjectable from "../dock/create-install-chart-tab/create-install-chart-tab.injectable";
|
||||
import createInstallChartTabInjectable from "../dock/install-chart/create-install-chart-tab.injectable";
|
||||
|
||||
interface Props {
|
||||
chart: HelmChart;
|
||||
|
||||
@ -27,7 +27,7 @@ import { getDetailsUrl } from "../../kube-detail-params";
|
||||
import { Checkbox } from "../../checkbox";
|
||||
import { MonacoEditor } from "../../monaco-editor";
|
||||
import { IAsyncComputed, withInjectables } from "@ogre-tools/injectable-react";
|
||||
import createUpgradeChartTabInjectable from "../../dock/create-upgrade-chart-tab/create-upgrade-chart-tab.injectable";
|
||||
import createUpgradeChartTabInjectable from "../../dock/upgrade-chart/create-upgrade-chart-tab.injectable";
|
||||
import updateReleaseInjectable from "../update-release/update-release.injectable";
|
||||
import releaseInjectable from "./release.injectable";
|
||||
import releaseDetailsInjectable from "./release-details.injectable";
|
||||
|
||||
@ -10,7 +10,7 @@ import { MenuActions, MenuActionsProps } from "../menu/menu-actions";
|
||||
import { MenuItem } from "../menu";
|
||||
import { Icon } from "../icon";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import createUpgradeChartTabInjectable from "../dock/create-upgrade-chart-tab/create-upgrade-chart-tab.injectable";
|
||||
import createUpgradeChartTabInjectable from "../dock/upgrade-chart/create-upgrade-chart-tab.injectable";
|
||||
import releaseRollbackDialogModelInjectable from "./release-rollback-dialog-model/release-rollback-dialog-model.injectable";
|
||||
import deleteReleaseInjectable from "./delete-release/delete-release.injectable";
|
||||
|
||||
|
||||
@ -14,9 +14,8 @@ import { ActivateEntityCommand } from "../../activate-entity-command";
|
||||
import type { CommandContext, CommandRegistration } from "./commands";
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import commandOverlayInjectable from "../command-overlay.injectable";
|
||||
import createTerminalTabInjectable
|
||||
from "../../dock/create-terminal-tab/create-terminal-tab.injectable";
|
||||
import type { DockTabCreate } from "../../dock/dock-store/dock.store";
|
||||
import createTerminalTabInjectable from "../../dock/terminal/create-terminal-tab.injectable";
|
||||
import type { DockTabCreate } from "../../dock/dock/store";
|
||||
|
||||
export function isKubernetesClusterActive(context: CommandContext): boolean {
|
||||
return context.entity?.kind === "KubernetesCluster";
|
||||
|
||||
@ -8,12 +8,12 @@ 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/dock.store";
|
||||
import { DockStore, DockTab, TabKind } from "../dock/store";
|
||||
import { noop } from "../../../utils";
|
||||
import { ThemeStore } from "../../../theme.store";
|
||||
import { UserStore } from "../../../../common/user-store";
|
||||
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
|
||||
import dockStoreInjectable from "../dock-store/dock-store.injectable";
|
||||
import dockStoreInjectable from "../dock/store.injectable";
|
||||
import type { DiRender } from "../../test-utils/renderFor";
|
||||
import { renderFor } from "../../test-utils/renderFor";
|
||||
import directoryForUserDataInjectable
|
||||
|
||||
@ -1,19 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
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;
|
||||
@ -1,44 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
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;
|
||||
};
|
||||
@ -1,17 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
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;
|
||||
@ -1,18 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
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,
|
||||
});
|
||||
@ -1,7 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
.CreateResource {
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { bind } from "../../../utils";
|
||||
import type { TabId } from "../dock/store";
|
||||
import type { CreateResourceTabStore } from "./store";
|
||||
import createResourceTabStoreInjectable from "./store.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
createResourceTabStore: CreateResourceTabStore;
|
||||
}
|
||||
|
||||
function clearCreateResourceTabData({ createResourceTabStore }: Dependencies, tabId: TabId): void {
|
||||
createResourceTabStore.clearData(tabId);
|
||||
}
|
||||
|
||||
const clearCreateResourceTabDataInjectable = getInjectable({
|
||||
instantiate: (di) => bind(clearCreateResourceTabData, null, {
|
||||
createResourceTabStore: di.inject(createResourceTabStoreInjectable),
|
||||
}),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default clearCreateResourceTabDataInjectable;
|
||||
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { bind } from "../../../utils";
|
||||
import dockStoreInjectable from "../dock/store.injectable";
|
||||
import { DockStore, DockTabCreateSpecific, TabKind } from "../dock/store";
|
||||
|
||||
interface Dependencies {
|
||||
dockStore: DockStore
|
||||
}
|
||||
|
||||
function createResourceTab({ dockStore }: Dependencies, tabParams: DockTabCreateSpecific = {}) {
|
||||
return dockStore.createTab({
|
||||
title: "Create resource",
|
||||
...tabParams,
|
||||
kind: TabKind.CREATE_RESOURCE,
|
||||
});
|
||||
}
|
||||
|
||||
const createResourceTabInjectable = getInjectable({
|
||||
instantiate: (di) => bind(createResourceTab, null, {
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
}),
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default createResourceTabInjectable;
|
||||
@ -3,17 +3,15 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import dockStoreInjectable from "../dock-store/dock-store.injectable";
|
||||
import { CreateResourceStore } from "./create-resource.store";
|
||||
import { CreateResourceTabStore } from "./store";
|
||||
import createStorageInjectable from "../../../utils/create-storage/create-storage.injectable";
|
||||
|
||||
const createResourceStoreInjectable = getInjectable({
|
||||
instantiate: (di) => new CreateResourceStore({
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
const createResourceTabStoreInjectable = getInjectable({
|
||||
instantiate: (di) => new CreateResourceTabStore({
|
||||
createStorage: di.inject(createStorageInjectable),
|
||||
}),
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default createResourceStoreInjectable;
|
||||
export default createResourceTabStoreInjectable;
|
||||
@ -11,14 +11,12 @@ import filehound from "filehound";
|
||||
import { watch } from "chokidar";
|
||||
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>>
|
||||
createStorage:<T> (storageKey: string, options: DockTabStorageState<T>) => StorageHelper<DockTabStorageState<T>>;
|
||||
}
|
||||
|
||||
export class CreateResourceStore extends DockTabStore<string> {
|
||||
export class CreateResourceTabStore extends DockTabStore<string> {
|
||||
constructor(protected dependencies: Dependencies) {
|
||||
super(dependencies, {
|
||||
storageKey: "create_resource",
|
||||
@ -3,37 +3,35 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import "./create-resource.scss";
|
||||
|
||||
import React from "react";
|
||||
import path from "path";
|
||||
import fs from "fs-extra";
|
||||
import { GroupSelectOption, Select, SelectOption } from "../select";
|
||||
import { GroupSelectOption, Select, SelectOption } from "../../select";
|
||||
import yaml from "js-yaml";
|
||||
import { makeObservable, observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
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";
|
||||
import { Notifications } from "../notifications";
|
||||
import logger from "../../../common/logger";
|
||||
import type { KubeJsonApiData } from "../../../common/k8s-api/kube-json-api";
|
||||
import { getDetailsUrl } from "../kube-detail-params";
|
||||
import { apiManager } from "../../../common/k8s-api/api-manager";
|
||||
import { prevDefault } from "../../utils";
|
||||
import { navigate } from "../../navigation";
|
||||
import type { CreateResourceTabStore } from "./store";
|
||||
import type { DockTab } from "../dock/store";
|
||||
import { EditorPanel } from "../editor-panel";
|
||||
import { InfoPanel } from "../info-panel";
|
||||
import * as resourceApplierApi from "../../../../common/k8s-api/endpoints/resource-applier.api";
|
||||
import { Notifications } from "../../notifications";
|
||||
import logger from "../../../../common/logger";
|
||||
import type { KubeJsonApiData } from "../../../../common/k8s-api/kube-json-api";
|
||||
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";
|
||||
import createResourceTabStoreInjectable
|
||||
from "./store.injectable";
|
||||
|
||||
interface Props {
|
||||
tab: DockTab;
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
createResourceStore: CreateResourceStore
|
||||
createResourceTabStore: CreateResourceTabStore;
|
||||
}
|
||||
|
||||
@observer
|
||||
@ -48,8 +46,8 @@ class NonInjectedCreateResource extends React.Component<Props & Dependencies> {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.createResourceStore.getMergedTemplates().then(v => this.updateGroupSelectOptions(v));
|
||||
this.props.createResourceStore.watchUserTemplates(() => this.props.createResourceStore.getMergedTemplates().then(v => this.updateGroupSelectOptions(v)));
|
||||
this.props.createResourceTabStore.getMergedTemplates().then(v => this.updateGroupSelectOptions(v));
|
||||
this.props.createResourceTabStore.watchUserTemplates(() => this.props.createResourceTabStore.getMergedTemplates().then(v => this.updateGroupSelectOptions(v)));
|
||||
}
|
||||
|
||||
updateGroupSelectOptions(templates: Record<string, string[]>) {
|
||||
@ -68,7 +66,7 @@ class NonInjectedCreateResource extends React.Component<Props & Dependencies> {
|
||||
}
|
||||
|
||||
get data() {
|
||||
return this.props.createResourceStore.getData(this.tabId);
|
||||
return this.props.createResourceTabStore.getData(this.tabId);
|
||||
}
|
||||
|
||||
get currentTemplate() {
|
||||
@ -77,7 +75,7 @@ class NonInjectedCreateResource extends React.Component<Props & Dependencies> {
|
||||
|
||||
onChange = (value: string) => {
|
||||
this.error = ""; // reset first, validation goes later
|
||||
this.props.createResourceStore.setData(this.tabId, value);
|
||||
this.props.createResourceTabStore.setData(this.tabId, value);
|
||||
};
|
||||
|
||||
onError = (error: Error | string) => {
|
||||
@ -87,7 +85,7 @@ class NonInjectedCreateResource extends React.Component<Props & Dependencies> {
|
||||
onSelectTemplate = (item: SelectOption) => {
|
||||
this.currentTemplates.set(this.tabId, item);
|
||||
fs.readFile(item.value, "utf8").then(v => {
|
||||
this.props.createResourceStore.setData(this.tabId, v);
|
||||
this.props.createResourceTabStore.setData(this.tabId, v);
|
||||
});
|
||||
};
|
||||
|
||||
@ -177,7 +175,7 @@ export const CreateResource = withInjectables<Dependencies, Props>(
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
createResourceStore: di.inject(createResourceStoreInjectable),
|
||||
createResourceTabStore: di.inject(createResourceTabStoreInjectable),
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
@ -1,17 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
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;
|
||||
@ -1,22 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
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,
|
||||
});
|
||||
@ -1,19 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { 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;
|
||||
@ -1,41 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
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;
|
||||
};
|
||||
@ -1,18 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { DockStore } from "./dock.store";
|
||||
import dockStorageInjectable from "./dock-storage/dock-storage.injectable";
|
||||
|
||||
const dockStoreInjectable = getInjectable({
|
||||
instantiate: (di) =>
|
||||
new DockStore({
|
||||
storage: di.inject(dockStorageInjectable),
|
||||
}),
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default dockStoreInjectable;
|
||||
@ -3,14 +3,12 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { DockTabStore, DockTabStoreOptions } from "./dock-tab.store";
|
||||
import dockStoreInjectable from "../dock-store/dock-store.injectable";
|
||||
import { DockTabStore, DockTabStoreDependencies, DockTabStoreOptions } from "./dock-tab.store";
|
||||
import createStorageInjectable from "../../../utils/create-storage/create-storage.injectable";
|
||||
|
||||
const createDockTabStoreInjectable = getInjectable({
|
||||
instantiate: (di) => {
|
||||
const dependencies = {
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
const dependencies: DockTabStoreDependencies = {
|
||||
createStorage: di.inject(createStorageInjectable),
|
||||
};
|
||||
|
||||
|
||||
@ -3,9 +3,9 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { autorun, observable, reaction } from "mobx";
|
||||
import { action, observable, reaction } from "mobx";
|
||||
import { autoBind, StorageHelper, toJS } from "../../../utils";
|
||||
import type { DockStore, TabId } from "../dock-store/dock.store";
|
||||
import type { TabId } from "../dock/store";
|
||||
|
||||
export interface DockTabStoreOptions {
|
||||
autoInit?: boolean; // load data from storage when `storageKey` is provided and bind events, default: true
|
||||
@ -14,16 +14,15 @@ export interface DockTabStoreOptions {
|
||||
|
||||
export type DockTabStorageState<T> = Record<TabId, T>;
|
||||
|
||||
interface Dependencies {
|
||||
dockStore: DockStore
|
||||
export interface DockTabStoreDependencies {
|
||||
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>();
|
||||
private data = observable.map<TabId, T>();
|
||||
|
||||
constructor(protected dependencies: Dependencies, protected options: DockTabStoreOptions) {
|
||||
constructor(protected dependencies: DockTabStoreDependencies, protected options: DockTabStoreOptions) {
|
||||
autoBind(this);
|
||||
|
||||
this.options = {
|
||||
@ -48,17 +47,6 @@ export class DockTabStore<T> {
|
||||
reaction(() => this.toJSON(), data => this.storage.set(data));
|
||||
});
|
||||
}
|
||||
|
||||
// clear data for closed tabs
|
||||
autorun(() => {
|
||||
const currentTabs = this.dependencies.dockStore.tabs.map(tab => tab.id);
|
||||
|
||||
Array.from(this.data.keys()).forEach(tabId => {
|
||||
if (!currentTabs.includes(tabId)) {
|
||||
this.clearData(tabId);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected finalizeDataForSave(data: T): T {
|
||||
@ -75,8 +63,22 @@ export class DockTabStore<T> {
|
||||
return Object.fromEntries<T>(deepCopy);
|
||||
}
|
||||
|
||||
protected getAllData() {
|
||||
return this.data.toJSON();
|
||||
}
|
||||
|
||||
findTabIdFromData(inspecter: (val: T) => any): TabId | undefined {
|
||||
for (const [tabId, data] of this.data) {
|
||||
if (inspecter(data)) {
|
||||
return tabId;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
isReady(tabId: TabId): boolean {
|
||||
return Boolean(this.getData(tabId) !== undefined);
|
||||
return this.getData(tabId) !== undefined;
|
||||
}
|
||||
|
||||
getData(tabId: TabId) {
|
||||
@ -91,8 +93,11 @@ export class DockTabStore<T> {
|
||||
this.data.delete(tabId);
|
||||
}
|
||||
|
||||
@action
|
||||
reset() {
|
||||
this.data.clear();
|
||||
for (const tabId of this.data.keys()) {
|
||||
this.clearData(tabId);
|
||||
}
|
||||
this.storage?.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,14 +8,14 @@ import "./dock-tab.scss";
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { boundMethod, cssNames, prevDefault, isMiddleClick } from "../../utils";
|
||||
import type { DockStore, DockTab as DockTabModel } from "./dock-store/dock.store";
|
||||
import type { DockStore, DockTab as DockTabModel } from "./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";
|
||||
import dockStoreInjectable from "./dock/store.injectable";
|
||||
|
||||
export interface DockTabProps extends TabProps<DockTabModel> {
|
||||
moreActions?: React.ReactNode;
|
||||
|
||||
@ -8,9 +8,9 @@ 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/dock.store";
|
||||
import { TabKind } from "./dock-store/dock.store";
|
||||
import { TerminalTab } from "./terminal-tab";
|
||||
import type { DockTab as DockTabModel } from "./dock/store";
|
||||
import { TabKind } from "./dock/store";
|
||||
import { TerminalTab } from "./terminal/dock-tab";
|
||||
|
||||
interface Props {
|
||||
tabs: DockTabModel[]
|
||||
|
||||
@ -13,18 +13,18 @@ import { Icon } from "../icon";
|
||||
import { MenuItem } from "../menu";
|
||||
import { MenuActions } from "../menu/menu-actions";
|
||||
import { ResizeDirection, ResizingAnchor } from "../resizing-anchor";
|
||||
import { CreateResource } from "./create-resource";
|
||||
import { CreateResource } from "./create-resource/view";
|
||||
import { DockTabs } from "./dock-tabs";
|
||||
import { DockStore, DockTab, TabKind } from "./dock-store/dock.store";
|
||||
import { EditResource } from "./edit-resource";
|
||||
import { InstallChart } from "./install-chart";
|
||||
import { LogsDockTab } from "./logs/dock-tab";
|
||||
import { TerminalWindow } from "./terminal-window";
|
||||
import { UpgradeChart } from "./upgrade-chart";
|
||||
import { DockStore, DockTab, TabKind } from "./dock/store";
|
||||
import { EditResource } from "./edit-resource/view";
|
||||
import { InstallChart } from "./install-chart/view";
|
||||
import { LogsDockTab } from "./logs/view";
|
||||
import { TerminalWindow } from "./terminal/view";
|
||||
import { UpgradeChart } from "./upgrade-chart/view";
|
||||
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";
|
||||
import createResourceTabInjectable from "./create-resource/create-resource-tab.injectable";
|
||||
import dockStoreInjectable from "./dock/store.injectable";
|
||||
import createTerminalTabInjectable from "./terminal/create-terminal-tab.injectable";
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { bind } from "../../../utils";
|
||||
import type { DockStore, TabId } from "./store";
|
||||
import dockStoreInjectable from "./store.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
dockStore: DockStore;
|
||||
}
|
||||
|
||||
function closeDockTab({ dockStore }: Dependencies, tabId: TabId): void {
|
||||
dockStore.closeTab(tabId);
|
||||
}
|
||||
|
||||
const closeDockTabInjectable = getInjectable({
|
||||
instantiate: (di) => bind(closeDockTab, null, {
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
}),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default closeDockTabInjectable;
|
||||
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { bind } from "../../../utils";
|
||||
import dockStoreInjectable from "./store.injectable";
|
||||
import type { DockStore, DockTab, DockTabCreate } from "./store";
|
||||
|
||||
interface Dependencies {
|
||||
dockStore: DockStore;
|
||||
}
|
||||
|
||||
function createDockTab({ dockStore }: Dependencies, rawTabDesc: DockTabCreate, addNumber?: boolean): DockTab {
|
||||
return dockStore.createTab(rawTabDesc, addNumber);
|
||||
}
|
||||
|
||||
const createDockTabInjectable = getInjectable({
|
||||
instantiate: (di) => bind(createDockTab, null, {
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
}),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default createDockTabInjectable;
|
||||
@ -3,8 +3,8 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import createStorageInjectable from "../../../../utils/create-storage/create-storage.injectable";
|
||||
import { DockStorageState, TabKind } from "../dock.store";
|
||||
import createStorageInjectable from "../../../utils/create-storage/create-storage.injectable";
|
||||
import { DockStorageState, TabKind } from "./store";
|
||||
|
||||
const dockStorageInjectable = getInjectable({
|
||||
instantiate: (di) => {
|
||||
25
src/renderer/components/dock/dock/rename-tab.injectable.ts
Normal file
25
src/renderer/components/dock/dock/rename-tab.injectable.ts
Normal file
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { bind } from "../../../utils";
|
||||
import dockStoreInjectable from "./store.injectable";
|
||||
import type { DockStore, TabId } from "./store";
|
||||
|
||||
interface Dependencies {
|
||||
dockStore: DockStore;
|
||||
}
|
||||
|
||||
function renameTab({ dockStore }: Dependencies, tabId: TabId, title: string): void {
|
||||
dockStore.renameTab(tabId, title);
|
||||
}
|
||||
|
||||
const renameTabInjectable = getInjectable({
|
||||
instantiate: (di) => bind(renameTab, null, {
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
}),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default renameTabInjectable;
|
||||
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { bind } from "../../../utils";
|
||||
import type { DockStore, TabId } from "./store";
|
||||
import dockStoreInjectable from "./store.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
dockStore: DockStore;
|
||||
}
|
||||
|
||||
function selectDockTab({ dockStore }: Dependencies, tabId: TabId): void {
|
||||
dockStore.selectTab(tabId);
|
||||
}
|
||||
|
||||
const selectDockTabInjectable = getInjectable({
|
||||
instantiate: (di) => bind(selectDockTab, null, {
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
}),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default selectDockTabInjectable;
|
||||
35
src/renderer/components/dock/dock/store.injectable.ts
Normal file
35
src/renderer/components/dock/dock/store.injectable.ts
Normal file
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { DockStore, TabKind } from "./store";
|
||||
import dockStorageInjectable from "./dock-storage.injectable";
|
||||
import clearLogTabDataInjectable from "../logs/clear-log-tab-data.injectable";
|
||||
import clearUpgradeChartTabDataInjectable from "../upgrade-chart/clear-upgrade-chart-tab-data.injectable";
|
||||
import clearCreateResourceTabDataInjectable from "../create-resource/clear-create-resource-tab-data.injectable";
|
||||
import clearEditResourceTabDataInjectable from "../edit-resource/clear-edit-resource-tab-data.injectable";
|
||||
import clearTerminalTabDataInjectable from "../terminal/clear-terminal-tab-data.injectable";
|
||||
import clearInstallChartTabDataInjectable from "../install-chart/clear-install-chart-tab-data.injectable";
|
||||
import isLogsTabDataValidInjectable from "../logs/is-logs-tab-data-valid.injectable";
|
||||
|
||||
const dockStoreInjectable = getInjectable({
|
||||
instantiate: (di) => new DockStore({
|
||||
storage: di.inject(dockStorageInjectable),
|
||||
tabDataClearers: {
|
||||
[TabKind.POD_LOGS]: di.inject(clearLogTabDataInjectable),
|
||||
[TabKind.UPGRADE_CHART]: di.inject(clearUpgradeChartTabDataInjectable),
|
||||
[TabKind.CREATE_RESOURCE]: di.inject(clearCreateResourceTabDataInjectable),
|
||||
[TabKind.EDIT_RESOURCE]: di.inject(clearEditResourceTabDataInjectable),
|
||||
[TabKind.INSTALL_CHART]: di.inject(clearInstallChartTabDataInjectable),
|
||||
[TabKind.TERMINAL]: di.inject(clearTerminalTabDataInjectable),
|
||||
},
|
||||
tabDataValidator: {
|
||||
[TabKind.POD_LOGS]: di.inject(isLogsTabDataValidInjectable),
|
||||
},
|
||||
}),
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default dockStoreInjectable;
|
||||
@ -98,11 +98,13 @@ export interface DockTabCloseEvent {
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
storage: StorageHelper<DockStorageState>
|
||||
readonly storage: StorageHelper<DockStorageState>
|
||||
readonly tabDataClearers: Record<TabKind, (tabId: TabId) => void>;
|
||||
readonly tabDataValidator: Partial<Record<TabKind, (tabId: TabId) => boolean>>;
|
||||
}
|
||||
|
||||
export class DockStore implements DockStorageState {
|
||||
constructor(private dependencies: Dependencies) {
|
||||
constructor(private readonly dependencies: Dependencies) {
|
||||
makeObservable(this);
|
||||
autoBind(this);
|
||||
this.init();
|
||||
@ -167,6 +169,16 @@ export class DockStore implements DockStorageState {
|
||||
private init() {
|
||||
// adjust terminal height if window size changes
|
||||
window.addEventListener("resize", throttle(this.adjustHeight, 250));
|
||||
|
||||
this.whenReady.then(action(() => {
|
||||
for (const tab of this.tabs) {
|
||||
const validator = this.dependencies.tabDataValidator[tab.kind];
|
||||
|
||||
if (validator && !validator(tab.id)) {
|
||||
this.closeTab(tab.id);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
get maxHeight() {
|
||||
@ -317,6 +329,7 @@ export class DockStore implements DockStorageState {
|
||||
}
|
||||
|
||||
this.tabs = this.tabs.filter(tab => tab.id !== tabId);
|
||||
this.dependencies.tabDataClearers[tab.kind](tab.id);
|
||||
|
||||
if (this.selectedTabId === tab.id) {
|
||||
if (this.tabs.length) {
|
||||
@ -330,6 +343,7 @@ export class DockStore implements DockStorageState {
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
closeTabs(tabs: DockTab[]) {
|
||||
tabs.forEach(tab => this.closeTab(tab.id));
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import 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;
|
||||
@ -1,109 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { autoBind, noop, StorageHelper } from "../../../utils";
|
||||
import { DockTabStorageState, DockTabStore } from "../dock-tab-store/dock-tab.store";
|
||||
import { autorun, IReactionDisposer } from "mobx";
|
||||
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
|
||||
draft?: string; // edited draft in yaml
|
||||
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(protected dependencies: Dependencies) {
|
||||
super(dependencies, {
|
||||
storageKey: "edit_resource_store",
|
||||
});
|
||||
|
||||
autoBind(this);
|
||||
}
|
||||
|
||||
protected async init() {
|
||||
super.init();
|
||||
await this.storage.whenReady;
|
||||
|
||||
autorun(() => {
|
||||
Array.from(this.data).forEach(([tabId, { resource }]) => {
|
||||
if (this.watchers.get(tabId)) {
|
||||
return;
|
||||
}
|
||||
this.watchers.set(tabId, autorun(() => {
|
||||
const store = apiManager.getStore(resource);
|
||||
|
||||
if (store) {
|
||||
const isActiveTab = this.dependencies.dockStore.isOpen && this.dependencies.dockStore.selectedTabId === tabId;
|
||||
const obj = store.getByPath(resource);
|
||||
|
||||
// preload resource for editing
|
||||
if (!obj && !store.isLoaded && !store.isLoading && isActiveTab) {
|
||||
store.loadFromPath(resource).catch(noop);
|
||||
}
|
||||
// auto-close tab when resource removed from store
|
||||
else if (!obj && store.isLoaded) {
|
||||
this.dependencies.dockStore.closeTab(tabId);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
delay: 100, // make sure all kube-object stores are initialized
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected finalizeDataForSave({ draft, ...data }: EditingResource): EditingResource {
|
||||
return data; // skip saving draft to local-storage
|
||||
}
|
||||
|
||||
isReady(tabId: TabId) {
|
||||
const tabDataReady = super.isReady(tabId);
|
||||
|
||||
return Boolean(tabDataReady && this.getResource(tabId)); // ready to edit resource
|
||||
}
|
||||
|
||||
getStore(tabId: TabId): KubeObjectStore<KubeObject> | undefined {
|
||||
return apiManager.getStore(this.getResourcePath(tabId));
|
||||
}
|
||||
|
||||
getResource(tabId: TabId): KubeObject | undefined {
|
||||
return this.getStore(tabId)?.getByPath(this.getResourcePath(tabId));
|
||||
}
|
||||
|
||||
getResourcePath(tabId: TabId): string | undefined {
|
||||
return this.getData(tabId)?.resource;
|
||||
}
|
||||
|
||||
getTabByResource(object: KubeObject): DockTab {
|
||||
const [tabId] = Array.from(this.data).find(([, { resource }]) => {
|
||||
return object.selfLink === resource;
|
||||
}) || [];
|
||||
|
||||
return this.dependencies.dockStore.getTabById(tabId);
|
||||
}
|
||||
|
||||
clearInitialDraft(tabId: TabId): void {
|
||||
delete this.getData(tabId)?.firstDraft;
|
||||
}
|
||||
|
||||
reset() {
|
||||
super.reset();
|
||||
Array.from(this.watchers).forEach(([tabId, dispose]) => {
|
||||
this.watchers.delete(tabId);
|
||||
dispose();
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { 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;
|
||||
@ -1,41 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
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;
|
||||
};
|
||||
@ -1,7 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
.EditResource {
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { bind } from "../../../utils";
|
||||
import type { TabId } from "../dock/store";
|
||||
import type { EditResourceTabStore } from "./store";
|
||||
import editResourceTabStoreInjectable from "./store.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
editResourceTabStore: EditResourceTabStore;
|
||||
}
|
||||
|
||||
function clearEditResourceTabData({ editResourceTabStore }: Dependencies, tabId: TabId) {
|
||||
editResourceTabStore.clearData(tabId);
|
||||
}
|
||||
|
||||
const clearEditResourceTabDataInjectable = getInjectable({
|
||||
instantiate: (di) => bind(clearEditResourceTabData, null, {
|
||||
editResourceTabStore: di.inject(editResourceTabStoreInjectable),
|
||||
}),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default clearEditResourceTabDataInjectable;
|
||||
@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import editResourceTabStoreInjectable from "./store.injectable";
|
||||
import dockStoreInjectable from "../dock/store.injectable";
|
||||
import type { KubeObject } from "../../../../common/k8s-api/kube-object";
|
||||
import { DockStore, DockTabCreateSpecific, TabId, TabKind } from "../dock/store";
|
||||
import type { EditResourceTabStore } from "./store";
|
||||
import { bind } from "../../../utils";
|
||||
import { runInAction } from "mobx";
|
||||
|
||||
interface Dependencies {
|
||||
dockStore: DockStore;
|
||||
editResourceStore: EditResourceTabStore;
|
||||
}
|
||||
|
||||
function createEditResourceTab({ dockStore, editResourceStore }: Dependencies, object: KubeObject, tabParams: DockTabCreateSpecific = {}): TabId {
|
||||
// use existing tab if already opened
|
||||
const tabId = editResourceStore.getTabIdByResource(object);
|
||||
|
||||
if (tabId) {
|
||||
dockStore.open();
|
||||
dockStore.selectTab(tabId);
|
||||
|
||||
return tabId;
|
||||
}
|
||||
|
||||
return runInAction(() => {
|
||||
const tab = dockStore.createTab(
|
||||
{
|
||||
title: `${object.kind}: ${object.getName()}`,
|
||||
...tabParams,
|
||||
kind: TabKind.EDIT_RESOURCE,
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
editResourceStore.setData(tab.id, {
|
||||
resource: object.selfLink,
|
||||
});
|
||||
|
||||
return tab.id;
|
||||
});
|
||||
}
|
||||
|
||||
const createEditResourceTabInjectable = getInjectable({
|
||||
instantiate: (di) => bind(createEditResourceTab, null, {
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
editResourceStore: di.inject(editResourceTabStoreInjectable),
|
||||
}),
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default createEditResourceTabInjectable;
|
||||
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { EditResourceTabStore } from "./store";
|
||||
import createStorageInjectable from "../../../utils/create-storage/create-storage.injectable";
|
||||
|
||||
const editResourceTabStoreInjectable = getInjectable({
|
||||
instantiate: (di) => new EditResourceTabStore({
|
||||
createStorage: di.inject(createStorageInjectable),
|
||||
}),
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default editResourceTabStoreInjectable;
|
||||
57
src/renderer/components/dock/edit-resource/store.ts
Normal file
57
src/renderer/components/dock/edit-resource/store.ts
Normal file
@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { StorageHelper } from "../../../utils";
|
||||
import { DockTabStorageState, DockTabStore } from "../dock-tab-store/dock-tab.store";
|
||||
import type { TabId } 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";
|
||||
|
||||
export interface EditingResource {
|
||||
resource: string; // resource path, e.g. /api/v1/namespaces/default
|
||||
draft?: string; // edited draft in yaml
|
||||
firstDraft?: string;
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
createStorage:<T> (storageKey: string, options: DockTabStorageState<T>) => StorageHelper<DockTabStorageState<T>>
|
||||
}
|
||||
|
||||
export class EditResourceTabStore extends DockTabStore<EditingResource> {
|
||||
constructor(protected dependencies: Dependencies) {
|
||||
super(dependencies, {
|
||||
storageKey: "edit_resource_store",
|
||||
});
|
||||
}
|
||||
|
||||
protected finalizeDataForSave({ draft, ...data }: EditingResource): EditingResource {
|
||||
return data; // skip saving draft to local-storage
|
||||
}
|
||||
|
||||
isReady(tabId: TabId) {
|
||||
return super.isReady(tabId) && Boolean(this.getResource(tabId)); // ready to edit resource
|
||||
}
|
||||
|
||||
getStore(tabId: TabId): KubeObjectStore<KubeObject> | undefined {
|
||||
return apiManager.getStore(this.getResourcePath(tabId));
|
||||
}
|
||||
|
||||
getResource(tabId: TabId): KubeObject | undefined {
|
||||
return this.getStore(tabId)?.getByPath(this.getResourcePath(tabId));
|
||||
}
|
||||
|
||||
getResourcePath(tabId: TabId): string | undefined {
|
||||
return this.getData(tabId)?.resource;
|
||||
}
|
||||
|
||||
getTabIdByResource(object: KubeObject): TabId {
|
||||
return this.findTabIdFromData(({ resource }) => object.selfLink === resource);
|
||||
}
|
||||
|
||||
clearInitialDraft(tabId: TabId): void {
|
||||
delete this.getData(tabId)?.firstDraft;
|
||||
}
|
||||
}
|
||||
@ -3,29 +3,30 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import "./edit-resource.scss";
|
||||
|
||||
import React from "react";
|
||||
import { computed, makeObservable, observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { autorun, computed, makeObservable, observable } from "mobx";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import yaml from "js-yaml";
|
||||
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 type { DockTab, TabId } from "../dock/store";
|
||||
import type { EditResourceTabStore } from "./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";
|
||||
import editResourceTabStoreInjectable from "./store.injectable";
|
||||
import { noop } from "../../../utils";
|
||||
import closeDockTabInjectable from "../dock/close-dock-tab.injectable";
|
||||
|
||||
interface Props {
|
||||
tab: DockTab;
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
editResourceStore: EditResourceStore
|
||||
editResourceStore: EditResourceTabStore;
|
||||
closeTab: (tabId: TabId) => void;
|
||||
}
|
||||
|
||||
@observer
|
||||
@ -37,6 +38,26 @@ class NonInjectedEditResource extends React.Component<Props & Dependencies> {
|
||||
makeObservable(this);
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
disposeOnUnmount(this, [
|
||||
autorun(() => {
|
||||
const store = this.props.editResourceStore.getStore(this.props.tab.id);
|
||||
const tabData = this.props.editResourceStore.getData(this.props.tab.id);
|
||||
const obj = this.resource;
|
||||
|
||||
if (!obj) {
|
||||
if (store.isLoaded) {
|
||||
// auto-close tab when resource removed from store
|
||||
this.props.closeTab(this.props.tab.id);
|
||||
} else if (!store.isLoading) {
|
||||
// preload resource for editing
|
||||
store.loadFromPath(tabData.resource).catch(noop);
|
||||
}
|
||||
}
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
get tabId() {
|
||||
return this.props.tab.id;
|
||||
}
|
||||
@ -132,13 +153,10 @@ class NonInjectedEditResource extends React.Component<Props & Dependencies> {
|
||||
}
|
||||
}
|
||||
|
||||
export const EditResource = withInjectables<Dependencies, Props>(
|
||||
NonInjectedEditResource,
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
editResourceStore: di.inject(editResourceStoreInjectable),
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
);
|
||||
export const EditResource = withInjectables<Dependencies, Props>(NonInjectedEditResource, {
|
||||
getProps: (di, props) => ({
|
||||
editResourceStore: di.inject(editResourceTabStoreInjectable),
|
||||
closeTab: di.inject(closeDockTabInjectable),
|
||||
...props,
|
||||
}),
|
||||
});
|
||||
@ -8,11 +8,11 @@ import throttle from "lodash/throttle";
|
||||
import React from "react";
|
||||
import { makeObservable, observable, reaction } from "mobx";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import type { DockStore, TabId } from "./dock-store/dock.store";
|
||||
import type { DockStore, TabId } from "./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";
|
||||
import dockStoreInjectable from "./dock/store.injectable";
|
||||
|
||||
export interface EditorPanelProps {
|
||||
tabId: TabId;
|
||||
|
||||
@ -12,10 +12,10 @@ import { cssNames } from "../../utils";
|
||||
import { Button } from "../button";
|
||||
import { Icon } from "../icon";
|
||||
import { Spinner } from "../spinner";
|
||||
import type { DockStore, TabId } from "./dock-store/dock.store";
|
||||
import type { DockStore, TabId } from "./dock/store";
|
||||
import { Notifications } from "../notifications";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import dockStoreInjectable from "./dock-store/dock-store.injectable";
|
||||
import dockStoreInjectable from "./dock/store.injectable";
|
||||
|
||||
interface Props extends OptionalProps {
|
||||
tabId: TabId;
|
||||
|
||||
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { bind } from "../../../utils";
|
||||
import type { TabId } from "../dock/store";
|
||||
import type { InstallChartTabStore } from "./store";
|
||||
import installChartTabStoreInjectable from "./store.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
installChartTabStore: InstallChartTabStore;
|
||||
}
|
||||
|
||||
function clearInstallChartTabData({ installChartTabStore }: Dependencies, tabId: TabId) {
|
||||
installChartTabStore.clearData(tabId);
|
||||
}
|
||||
|
||||
const clearInstallChartTabDataInjectable = getInjectable({
|
||||
instantiate: (di) => bind(clearInstallChartTabData, null, {
|
||||
installChartTabStore: di.inject(installChartTabStoreInjectable),
|
||||
}),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default clearInstallChartTabDataInjectable;
|
||||
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import installChartTabStoreInjectable from "./store.injectable";
|
||||
import type { HelmChart } from "../../../../common/k8s-api/endpoints/helm-charts.api";
|
||||
import { DockTab, DockTabCreate, DockTabCreateSpecific, TabKind } from "../dock/store";
|
||||
import type { InstallChartTabStore } from "./store";
|
||||
import { bind } from "../../../utils";
|
||||
import createDockTabInjectable from "../dock/create-dock-tab.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
createDockTab: (rawTab: DockTabCreate, addNumber: boolean) => DockTab;
|
||||
installChartStore: InstallChartTabStore;
|
||||
}
|
||||
|
||||
function 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;
|
||||
}
|
||||
|
||||
const createInstallChartTabInjectable = getInjectable({
|
||||
instantiate: (di) => bind(createInstallChartTab, null, {
|
||||
installChartStore: di.inject(installChartTabStoreInjectable),
|
||||
createDockTab: di.inject(createDockTabInjectable),
|
||||
}),
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default createInstallChartTabInjectable;
|
||||
@ -3,18 +3,16 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { InstallChartStore } from "./install-chart.store";
|
||||
import dockStoreInjectable from "../dock-store/dock-store.injectable";
|
||||
import { InstallChartTabStore } from "./store";
|
||||
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({
|
||||
const installChartTabStoreInjectable = getInjectable({
|
||||
instantiate: (di) => {
|
||||
const createDockTabStore = di.inject(createDockTabStoreInjectable);
|
||||
|
||||
return new InstallChartStore({
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
return new InstallChartTabStore({
|
||||
createStorage: di.inject(createStorageInjectable),
|
||||
versionsStore: createDockTabStore<string[]>(),
|
||||
detailsStore: createDockTabStore<IReleaseUpdateDetails>(),
|
||||
@ -23,4 +21,4 @@ const installChartStoreInjectable = getInjectable({
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default installChartStoreInjectable;
|
||||
export default installChartTabStoreInjectable;
|
||||
@ -3,12 +3,11 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { action, autorun, makeObservable } from "mobx";
|
||||
import { DockStore, TabId, TabKind } from "../dock-store/dock.store";
|
||||
import { action, makeObservable } from "mobx";
|
||||
import type { TabId } from "../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 {
|
||||
@ -23,29 +22,18 @@ export interface IChartInstallData {
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
dockStore: DockStore,
|
||||
createStorage: <T>(storageKey: string, options: DockTabStorageState<T>) => StorageHelper<DockTabStorageState<T>>
|
||||
|
||||
versionsStore: DockTabStore<string[]>,
|
||||
detailsStore: DockTabStore<IReleaseUpdateDetails>
|
||||
createStorage: <T>(storageKey: string, options: DockTabStorageState<T>) => StorageHelper<DockTabStorageState<T>>;
|
||||
versionsStore: DockTabStore<string[]>;
|
||||
detailsStore: DockTabStore<IReleaseUpdateDetails>;
|
||||
}
|
||||
|
||||
export class InstallChartStore extends DockTabStore<IChartInstallData> {
|
||||
export class InstallChartTabStore extends DockTabStore<IChartInstallData> {
|
||||
constructor(protected dependencies: Dependencies) {
|
||||
super(
|
||||
dependencies,
|
||||
{ storageKey: "install_charts" },
|
||||
);
|
||||
|
||||
makeObservable(this);
|
||||
autorun(() => {
|
||||
const { selectedTab, isOpen } = dependencies.dockStore;
|
||||
|
||||
if (selectedTab?.kind === TabKind.INSTALL_CHART && isOpen) {
|
||||
this.loadData(selectedTab.id)
|
||||
.catch(err => Notifications.error(String(err)));
|
||||
}
|
||||
}, { delay: 250 });
|
||||
}
|
||||
|
||||
get versions() {
|
||||
@ -94,8 +82,4 @@ export class InstallChartStore extends DockTabStore<IChartInstallData> {
|
||||
return this.loadValues(tabId, attempt + 1);
|
||||
}
|
||||
}
|
||||
|
||||
setData(tabId: TabId, data: IChartInstallData){
|
||||
super.setData(tabId, data);
|
||||
}
|
||||
}
|
||||
@ -8,29 +8,27 @@ import "./install-chart.scss";
|
||||
import React, { Component } from "react";
|
||||
import { action, makeObservable, observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
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 type { IChartInstallData, InstallChartStore } from "./install-chart-store/install-chart.store";
|
||||
import { Spinner } from "../spinner";
|
||||
import { Icon } from "../icon";
|
||||
import { Button } from "../button";
|
||||
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 type { DockStore, DockTab } from "../dock/store";
|
||||
import { InfoPanel } from "../info-panel";
|
||||
import { Badge } from "../../badge";
|
||||
import { NamespaceSelect } from "../../+namespaces/namespace-select";
|
||||
import { prevDefault } from "../../../utils";
|
||||
import type { IChartInstallData, InstallChartTabStore } from "./store";
|
||||
import { Spinner } from "../../spinner";
|
||||
import { Icon } from "../../icon";
|
||||
import { Button } from "../../button";
|
||||
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 { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import installChartStoreInjectable from "./install-chart-store/install-chart-store.injectable";
|
||||
import dockStoreInjectable from "./dock-store/dock-store.injectable";
|
||||
import createReleaseInjectable from "../+apps-releases/create-release/create-release.injectable";
|
||||
import installChartTabStoreInjectable from "./store.injectable";
|
||||
import dockStoreInjectable from "../dock/store.injectable";
|
||||
import createReleaseInjectable from "../../+apps-releases/create-release/create-release.injectable";
|
||||
import { Notifications } from "../../notifications";
|
||||
|
||||
interface Props {
|
||||
tab: DockTab;
|
||||
@ -38,7 +36,7 @@ interface Props {
|
||||
|
||||
interface Dependencies {
|
||||
createRelease: (payload: IReleaseCreatePayload) => Promise<IReleaseUpdateDetails>
|
||||
installChartStore: InstallChartStore
|
||||
installChartStore: InstallChartTabStore
|
||||
dockStore: DockStore
|
||||
}
|
||||
|
||||
@ -52,6 +50,11 @@ class NonInjectedInstallChart extends Component<Props & Dependencies> {
|
||||
makeObservable(this);
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
this.props.installChartStore.loadData(this.tabId)
|
||||
.catch(err => Notifications.error(String(err)));
|
||||
}
|
||||
|
||||
get chartData() {
|
||||
return this.props.installChartStore.getData(this.tabId);
|
||||
}
|
||||
@ -221,7 +224,7 @@ export const InstallChart = withInjectables<Dependencies, Props>(
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
createRelease: di.inject(createReleaseInjectable),
|
||||
installChartStore: di.inject(installChartStoreInjectable),
|
||||
installChartStore: di.inject(installChartTabStoreInjectable),
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
...props,
|
||||
}),
|
||||
@ -8,7 +8,7 @@ import "@testing-library/jest-dom/extend-expect";
|
||||
import * as selectEvent from "react-select-event";
|
||||
import { Pod } from "../../../../../common/k8s-api/endpoints";
|
||||
import { LogResourceSelector } from "../resource-selector";
|
||||
import { dockerPod, deploymentPod1 } from "./pod.mock";
|
||||
import { dockerPod, deploymentPod1, deploymentPod2 } from "./pod.mock";
|
||||
import { ThemeStore } from "../../../../theme.store";
|
||||
import { UserStore } from "../../../../../common/user-store";
|
||||
import mockFs from "mock-fs";
|
||||
@ -18,7 +18,7 @@ import { renderFor } from "../../../test-utils/renderFor";
|
||||
import directoryForUserDataInjectable from "../../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import callForLogsInjectable from "../call-for-logs.injectable";
|
||||
import { LogTabViewModel, LogTabViewModelDependencies } from "../logs-view-model";
|
||||
import type { TabId } from "../../dock-store/dock.store";
|
||||
import type { TabId } from "../../dock/store";
|
||||
|
||||
jest.mock("electron", () => ({
|
||||
app: {
|
||||
@ -51,6 +51,8 @@ function mockLogTabViewModel(tabId: TabId, deps: Partial<LogTabViewModelDependen
|
||||
reloadLogs: jest.fn(),
|
||||
updateTabName: jest.fn(),
|
||||
stopLoadingLogs: jest.fn(),
|
||||
getPodById: jest.fn(),
|
||||
getPodsByOwnerId: jest.fn(),
|
||||
...deps,
|
||||
});
|
||||
}
|
||||
@ -60,23 +62,53 @@ const getOnePodViewModel = (tabId: TabId): LogTabViewModel => {
|
||||
|
||||
return mockLogTabViewModel(tabId, {
|
||||
getLogTabData: () => ({
|
||||
pods: [selectedPod],
|
||||
selectedPod,
|
||||
selectedContainer: selectedPod.getContainers()[0],
|
||||
selectedPodId: selectedPod.getId(),
|
||||
selectedContainer: selectedPod.getContainers()[0].name,
|
||||
namespace: selectedPod.getNs(),
|
||||
showPrevious: false,
|
||||
showTimestamps: false,
|
||||
}),
|
||||
getPodById: (id) => {
|
||||
if (id === selectedPod.getId()) {
|
||||
return selectedPod;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const getFewPodsTabData = (tabId: TabId): LogTabViewModel => {
|
||||
const selectedPod = new Pod(deploymentPod1);
|
||||
const anotherPod = new Pod(dockerPod);
|
||||
const anotherPod = new Pod(deploymentPod2);
|
||||
|
||||
return mockLogTabViewModel(tabId, {
|
||||
getLogTabData: () => ({
|
||||
pods: [selectedPod, anotherPod],
|
||||
selectedPod,
|
||||
selectedContainer: selectedPod.getContainers()[0],
|
||||
ownerId: "uuid",
|
||||
selectedPodId: selectedPod.getId(),
|
||||
selectedContainer: selectedPod.getContainers()[0].name,
|
||||
namespace: selectedPod.getNs(),
|
||||
showPrevious: false,
|
||||
showTimestamps: false,
|
||||
}),
|
||||
getPodById: (id) => {
|
||||
if (id === selectedPod.getId()) {
|
||||
return selectedPod;
|
||||
}
|
||||
|
||||
if (id === anotherPod.getId()) {
|
||||
return anotherPod;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
},
|
||||
getPodsByOwnerId: (id) => {
|
||||
if (id === "uuid") {
|
||||
return [selectedPod, anotherPod];
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@ -11,12 +11,13 @@ import { deploymentPod1, deploymentPod2, deploymentPod3, dockerPod } from "./pod
|
||||
import { mockWindow } from "../../../../../../__mocks__/windowMock";
|
||||
import { getDiForUnitTesting } from "../../../../getDiForUnitTesting";
|
||||
import logTabStoreInjectable from "../tab-store.injectable";
|
||||
import type { LogTabStore } from "../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 type { LogTabStore } from "../tab-store";
|
||||
import dockStoreInjectable from "../../dock/store.injectable";
|
||||
import type { DockStore, TabId } from "../../dock/store";
|
||||
import directoryForUserDataInjectable from "../../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import mockFs from "mock-fs";
|
||||
import type { PodLogsTabData } from "../create-pod-logs-tab.injectable";
|
||||
import createPodLogsTabInjectable from "../create-pod-logs-tab.injectable";
|
||||
|
||||
mockWindow();
|
||||
|
||||
@ -27,6 +28,7 @@ podsStore.items.push(new Pod(deploymentPod2));
|
||||
describe("log tab store", () => {
|
||||
let logTabStore: LogTabStore;
|
||||
let dockStore: DockStore;
|
||||
let createPodTab: ({ selectedPod, selectedContainer }: PodLogsTabData) => TabId;
|
||||
|
||||
beforeEach(async () => {
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
@ -39,6 +41,7 @@ describe("log tab store", () => {
|
||||
|
||||
dockStore = di.inject(dockStoreInjectable);
|
||||
logTabStore = di.inject(logTabStoreInjectable);
|
||||
createPodTab = di.inject(createPodLogsTabInjectable);
|
||||
|
||||
UserStore.createInstance();
|
||||
ThemeStore.createInstance();
|
||||
@ -54,7 +57,7 @@ describe("log tab store", () => {
|
||||
const selectedPod = new Pod(dockerPod);
|
||||
const selectedContainer = selectedPod.getAllContainers()[0];
|
||||
|
||||
logTabStore.createPodTab({
|
||||
createPodTab({
|
||||
selectedPod,
|
||||
selectedContainer,
|
||||
});
|
||||
@ -73,7 +76,7 @@ describe("log tab store", () => {
|
||||
const siblingPod = new Pod(deploymentPod2);
|
||||
const selectedContainer = selectedPod.getInitContainers()[0];
|
||||
|
||||
logTabStore.createPodTab({
|
||||
createPodTab({
|
||||
selectedPod,
|
||||
selectedContainer,
|
||||
});
|
||||
@ -91,7 +94,7 @@ describe("log tab store", () => {
|
||||
const selectedPod = new Pod(deploymentPod1);
|
||||
const selectedContainer = selectedPod.getInitContainers()[0];
|
||||
|
||||
logTabStore.createPodTab({
|
||||
createPodTab({
|
||||
selectedPod,
|
||||
selectedContainer,
|
||||
});
|
||||
@ -111,7 +114,7 @@ describe("log tab store", () => {
|
||||
const selectedPod = new Pod(deploymentPod1);
|
||||
const selectedContainer = selectedPod.getInitContainers()[0];
|
||||
|
||||
logTabStore.createPodTab({
|
||||
createPodTab({
|
||||
selectedPod,
|
||||
selectedContainer,
|
||||
});
|
||||
@ -132,7 +135,7 @@ describe("log tab store", () => {
|
||||
const selectedPod = new Pod(deploymentPod1);
|
||||
const selectedContainer = selectedPod.getInitContainers()[0];
|
||||
|
||||
const id = logTabStore.createPodTab({
|
||||
const id = createPodTab({
|
||||
selectedPod,
|
||||
selectedContainer,
|
||||
});
|
||||
|
||||
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { bind } from "../../../utils";
|
||||
import type { TabId } from "../dock/store";
|
||||
import type { LogTabStore } from "./tab-store";
|
||||
import logTabStoreInjectable from "./tab-store.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
logTabStore: LogTabStore;
|
||||
}
|
||||
|
||||
function clearLogTabData({ logTabStore }: Dependencies, tabId: TabId): void {
|
||||
logTabStore.clearData(tabId);
|
||||
}
|
||||
|
||||
const clearLogTabDataInjectable = getInjectable({
|
||||
instantiate: (di) => bind(clearLogTabData, null, {
|
||||
logTabStore: di.inject(logTabStoreInjectable),
|
||||
}),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default clearLogTabDataInjectable;
|
||||
@ -8,7 +8,6 @@ import "./controls.scss";
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
|
||||
import { Pod } from "../../../../common/k8s-api/endpoints";
|
||||
import { cssNames, saveFileDialog } from "../../../utils";
|
||||
import { Checkbox } from "../../checkbox";
|
||||
import { Icon } from "../../icon";
|
||||
@ -26,21 +25,20 @@ export const LogControls = observer(({ model }: LogControlsProps) => {
|
||||
}
|
||||
|
||||
const logs = model.timestampSplitLogs.get();
|
||||
const { showTimestamps, previous } = tabData;
|
||||
const { showTimestamps, showPrevious: previous } = tabData;
|
||||
const since = logs.length ? logs[0][0] : null;
|
||||
const pod = new Pod(tabData.selectedPod);
|
||||
|
||||
const toggleTimestamps = () => {
|
||||
model.updateLogTabData({ showTimestamps: !showTimestamps });
|
||||
};
|
||||
|
||||
const togglePrevious = () => {
|
||||
model.updateLogTabData({ previous: !previous });
|
||||
model.updateLogTabData({ showPrevious: !previous });
|
||||
model.reloadLogs();
|
||||
};
|
||||
|
||||
const downloadLogs = () => {
|
||||
const fileName = pod.getName();
|
||||
const fileName = model.pod.get().getName();
|
||||
const logsToDownload: string[] = showTimestamps
|
||||
? model.logs.get()
|
||||
: model.logsWithoutTimestamps.get();
|
||||
|
||||
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { bind } from "../../../utils";
|
||||
import { DockTabCreate, DockTab, TabKind, TabId } from "../dock/store";
|
||||
import type { LogTabData } from "./tab-store";
|
||||
import * as uuid from "uuid";
|
||||
import { runInAction } from "mobx";
|
||||
import createDockTabInjectable from "../dock/create-dock-tab.injectable";
|
||||
import setLogTabDataInjectable from "./set-log-tab-data.injectable";
|
||||
|
||||
export type CreateLogsTabData = Pick<LogTabData, "ownerId" | "selectedPodId" | "selectedContainer" | "namespace"> & Omit<Partial<LogTabData>, "ownerId" | "selectedPodId" | "selectedContainer" | "namespace">;
|
||||
|
||||
interface Dependencies {
|
||||
createDockTab: (rawTabDesc: DockTabCreate, addNumber?: boolean) => DockTab;
|
||||
setLogTabData: (tabId: string, data: LogTabData) => void;
|
||||
}
|
||||
|
||||
function createLogsTab({ createDockTab, setLogTabData }: Dependencies, title: string, data: CreateLogsTabData): TabId {
|
||||
const id = `log-tab-${uuid.v4()}`;
|
||||
|
||||
runInAction(() => {
|
||||
createDockTab({
|
||||
id,
|
||||
title,
|
||||
kind: TabKind.POD_LOGS,
|
||||
}, false);
|
||||
setLogTabData(id, {
|
||||
showTimestamps: false,
|
||||
showPrevious: false,
|
||||
...data,
|
||||
});
|
||||
});
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
const createLogsTabInjectable = getInjectable({
|
||||
instantiate: (di) => bind(createLogsTab, null, {
|
||||
createDockTab: di.inject(createDockTabInjectable),
|
||||
setLogTabData: di.inject(setLogTabDataInjectable),
|
||||
}),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default createLogsTabInjectable;
|
||||
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import type { Pod, IPodContainer } from "../../../../common/k8s-api/endpoints";
|
||||
import { bind } from "../../../utils";
|
||||
import type { TabId } from "../dock/store";
|
||||
import createLogsTabInjectable, { CreateLogsTabData } from "./create-logs-tab.injectable";
|
||||
|
||||
export interface PodLogsTabData {
|
||||
selectedPod: Pod;
|
||||
selectedContainer: IPodContainer;
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
createLogsTab: (title: string, data: CreateLogsTabData) => TabId;
|
||||
}
|
||||
|
||||
function createPodLogsTab({ createLogsTab }: Dependencies, { selectedPod, selectedContainer }: PodLogsTabData): TabId {
|
||||
const podOwner = selectedPod.getOwnerRefs()[0];
|
||||
|
||||
return createLogsTab(`Pod ${selectedPod.getName()}`, {
|
||||
ownerId: podOwner?.uid,
|
||||
namespace: selectedPod.getNs(),
|
||||
selectedContainer: selectedContainer.name,
|
||||
selectedPodId: selectedPod.getId(),
|
||||
});
|
||||
}
|
||||
|
||||
const createPodLogsTabInjectable = getInjectable({
|
||||
instantiate: (di) => bind(createPodLogsTab, null, {
|
||||
createLogsTab: di.inject(createLogsTabInjectable),
|
||||
}),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default createPodLogsTabInjectable;
|
||||
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { podsStore } from "../../+workloads-pods/pods.store";
|
||||
import type { WorkloadKubeObject } from "../../../../common/k8s-api/workload-kube-object";
|
||||
import { bind } from "../../../utils";
|
||||
import type { TabId } from "../dock/store";
|
||||
import createLogsTabInjectable, { CreateLogsTabData } from "./create-logs-tab.injectable";
|
||||
|
||||
export interface WorkloadLogsTabData {
|
||||
workload: WorkloadKubeObject
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
createLogsTab: (title: string, data: CreateLogsTabData) => TabId;
|
||||
}
|
||||
|
||||
function createWorkloadLogsTab({ createLogsTab }: Dependencies, { workload }: WorkloadLogsTabData): TabId | undefined {
|
||||
const pods = podsStore.getPodsByOwnerId(workload.getId());
|
||||
|
||||
if (pods.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const selectedPod = pods[0];
|
||||
|
||||
return createLogsTab(`${workload.kind} ${selectedPod.getName()}`, {
|
||||
selectedContainer: selectedPod.getAllContainers()[0].name,
|
||||
selectedPodId: selectedPod.getId(),
|
||||
namespace: selectedPod.getNs(),
|
||||
ownerId: workload.getId(),
|
||||
});
|
||||
}
|
||||
|
||||
const createWorkloadLogsTabInjectable = getInjectable({
|
||||
instantiate: (di) => bind(createWorkloadLogsTab, null, {
|
||||
createLogsTab: di.inject(createLogsTabInjectable),
|
||||
}),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default createWorkloadLogsTabInjectable;
|
||||
@ -1,101 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { boundMethod } from "../../../utils";
|
||||
import { InfoPanel } from "../info-panel";
|
||||
import { LogResourceSelector } from "./resource-selector";
|
||||
import { LogList } from "./list";
|
||||
import { LogSearch } from "./search";
|
||||
import { LogControls } from "./controls";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import logsViewModelInjectable from "./logs-view-model.injectable";
|
||||
import type { LogTabViewModel } from "./logs-view-model";
|
||||
import type { DockTab } from "../dock-store/dock.store";
|
||||
|
||||
export interface LogsDockTabProps {
|
||||
className?: string;
|
||||
tab: DockTab;
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
model: LogTabViewModel;
|
||||
}
|
||||
|
||||
@observer
|
||||
class NonInjectedLogsDockTab extends React.Component<LogsDockTabProps & Dependencies> {
|
||||
private logListElement = React.createRef<LogList>(); // A reference for VirtualList component
|
||||
|
||||
componentDidMount(): void {
|
||||
this.props.model.reloadLogs();
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
this.props.model.stopLoadingLogs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolling to active overlay (search word highlight)
|
||||
*/
|
||||
@boundMethod
|
||||
scrollToOverlay() {
|
||||
const { activeOverlayLine } = this.props.model.searchStore;
|
||||
|
||||
if (!this.logListElement.current || activeOverlayLine === undefined) return;
|
||||
// Scroll vertically
|
||||
this.logListElement.current.scrollToItem(activeOverlayLine, "center");
|
||||
// Scroll horizontally in timeout since virtual list need some time to prepare its contents
|
||||
setTimeout(() => {
|
||||
const overlay = document.querySelector(".PodLogs .list span.active");
|
||||
|
||||
if (!overlay) return;
|
||||
overlay.scrollIntoViewIfNeeded();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { model, tab } = this.props;
|
||||
const { logTabData } = model;
|
||||
const data = logTabData.get();
|
||||
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="PodLogs flex column">
|
||||
<InfoPanel
|
||||
tabId={tab.id}
|
||||
controls={(
|
||||
<div className="flex gaps">
|
||||
<LogResourceSelector model={model} />
|
||||
<LogSearch
|
||||
onSearch={this.scrollToOverlay}
|
||||
model={model}
|
||||
toPrevOverlay={this.scrollToOverlay}
|
||||
toNextOverlay={this.scrollToOverlay}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
showSubmitClose={false}
|
||||
showButtons={false}
|
||||
showStatusPanel={false}
|
||||
/>
|
||||
<LogList model={model} ref={this.logListElement} />
|
||||
<LogControls model={model} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const LogsDockTab = withInjectables<Dependencies, LogsDockTabProps>(NonInjectedLogsDockTab, {
|
||||
getProps: (di, props) => ({
|
||||
model: di.inject(logsViewModelInjectable, {
|
||||
tabId: props.tab.id,
|
||||
}),
|
||||
...props,
|
||||
}),
|
||||
});
|
||||
@ -3,10 +3,22 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { bind } from "../../../utils";
|
||||
import type { LogTabData, LogTabStore } from "./tab-store";
|
||||
import logTabStoreInjectable from "./tab-store.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
logTabStore: LogTabStore;
|
||||
}
|
||||
|
||||
function getLogTabData({ logTabStore }: Dependencies, tabId: string): LogTabData {
|
||||
return logTabStore.getData(tabId);
|
||||
}
|
||||
|
||||
const getLogTabDataInjectable = getInjectable({
|
||||
instantiate: (di) => di.inject(logTabStoreInjectable).getData,
|
||||
instantiate: (di) => bind(getLogTabData, null, {
|
||||
logTabStore: di.inject(logTabStoreInjectable),
|
||||
}),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
|
||||
@ -3,10 +3,22 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { bind } from "../../../utils";
|
||||
import type { LogStore } from "./store";
|
||||
import logStoreInjectable from "./store.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
logStore: LogStore;
|
||||
}
|
||||
|
||||
function getLogsWithoutTimestamps({ logStore }: Dependencies, tabId: string): string[] {
|
||||
return logStore.getLogsWithoutTimestamps(tabId);
|
||||
}
|
||||
|
||||
const getLogsWithoutTimestampsInjectable = getInjectable({
|
||||
instantiate: (di) => di.inject(logStoreInjectable).getLogsWithoutTimestampsByTabId,
|
||||
instantiate: (di) => bind(getLogsWithoutTimestamps, null, {
|
||||
logStore: di.inject(logStoreInjectable),
|
||||
}),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
|
||||
@ -3,10 +3,22 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { bind } from "../../../utils";
|
||||
import type { LogStore } from "./store";
|
||||
import logStoreInjectable from "./store.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
logStore: LogStore;
|
||||
}
|
||||
|
||||
function getLogs({ logStore }: Dependencies, tabId: string): string[] {
|
||||
return logStore.getLogs(tabId);
|
||||
}
|
||||
|
||||
const getLogsInjectable = getInjectable({
|
||||
instantiate: (di) => di.inject(logStoreInjectable).getLogsByTabId,
|
||||
instantiate: (di) => bind(getLogs, null, {
|
||||
logStore: di.inject(logStoreInjectable),
|
||||
}),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
|
||||
@ -3,10 +3,22 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { bind } from "../../../utils";
|
||||
import type { LogStore } from "./store";
|
||||
import logStoreInjectable from "./store.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
logStore: LogStore;
|
||||
}
|
||||
|
||||
function getTimestampSplitLogs({ logStore }: Dependencies, tabId: string): [string, string][] {
|
||||
return logStore.getTimestampSplitLogs(tabId);
|
||||
}
|
||||
|
||||
const getTimestampSplitLogsInjectable = getInjectable({
|
||||
instantiate: (di) => di.inject(logStoreInjectable).getTimestampSplitLogsByTabId,
|
||||
instantiate: (di) => bind(getTimestampSplitLogs, null, {
|
||||
logStore: di.inject(logStoreInjectable),
|
||||
}),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
|
||||
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { bind } from "../../../utils";
|
||||
import type { TabId } from "../dock/store";
|
||||
import type { LogTabStore } from "./tab-store";
|
||||
import logTabStoreInjectable from "./tab-store.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
logTabStore: LogTabStore;
|
||||
}
|
||||
|
||||
function isLogsTabDataValid({ logTabStore }: Dependencies, tabId: TabId) {
|
||||
return logTabStore.isDataValid(tabId);
|
||||
}
|
||||
|
||||
const isLogsTabDataValidInjectable = getInjectable({
|
||||
instantiate: (di) => bind(isLogsTabDataValid, null, {
|
||||
logTabStore: di.inject(logTabStoreInjectable),
|
||||
}),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default isLogsTabDataValidInjectable;
|
||||
@ -3,10 +3,24 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { bind } from "../../../utils";
|
||||
import type { LogStore } from "./store";
|
||||
import logStoreInjectable from "./store.injectable";
|
||||
import type { LogTabData } from "./tab-store";
|
||||
|
||||
interface Dependencies {
|
||||
logStore: LogStore;
|
||||
}
|
||||
|
||||
function loadLogs({ logStore }: Dependencies, tabId: string, logTabData: IComputedValue<LogTabData>): Promise<void> {
|
||||
return logStore.load(tabId, logTabData);
|
||||
}
|
||||
|
||||
const loadLogsInjectable = getInjectable({
|
||||
instantiate: (di) => di.inject(logStoreInjectable).load,
|
||||
instantiate: (di) => bind(loadLogs, null, {
|
||||
logStore: di.inject(logStoreInjectable),
|
||||
}),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
|
||||
26
src/renderer/components/dock/logs/log-tab-data.validator.ts
Normal file
26
src/renderer/components/dock/logs/log-tab-data.validator.ts
Normal file
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import Joi from "joi";
|
||||
|
||||
export const logTabDataValidator = Joi.object({
|
||||
ownerId: Joi
|
||||
.string()
|
||||
.optional(),
|
||||
selectedPodId: Joi
|
||||
.string()
|
||||
.required(),
|
||||
namespace: Joi
|
||||
.string()
|
||||
.required(),
|
||||
selectedContainer: Joi
|
||||
.string()
|
||||
.optional(),
|
||||
showTimestamps: Joi
|
||||
.boolean()
|
||||
.required(),
|
||||
previous: Joi
|
||||
.boolean()
|
||||
.required(),
|
||||
});
|
||||
@ -4,16 +4,17 @@
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { LogTabViewModel } from "./logs-view-model";
|
||||
import type { TabId } from "../dock-store/dock.store";
|
||||
import type { TabId } from "../dock/store";
|
||||
import getLogsInjectable from "./get-logs.injectable";
|
||||
import getLogsWithoutTimestampsInjectable from "./get-logs-without-timestamps.injectable";
|
||||
import getTimestampSplitLogsInjectable from "./get-timestamp-split-logs.injectable";
|
||||
import reloadLoadsInjectable from "./reload-logs.injectable";
|
||||
import reloadLogsInjectable from "./reload-logs.injectable";
|
||||
import getLogTabDataInjectable from "./get-log-tab-data.injectable";
|
||||
import loadLogsInjectable from "./load-logs.injectable";
|
||||
import setLogTabDataInjectable from "./set-log-tab-data.injectable";
|
||||
import updateTabNameInjectable from "./update-tab-name.injectable";
|
||||
import stopLoadingLogsInjectable from "./stop-loading-logs.injectable";
|
||||
import { podsStore } from "../../+workloads-pods/pods.store";
|
||||
|
||||
export interface InstantiateArgs {
|
||||
tabId: TabId;
|
||||
@ -24,12 +25,14 @@ const logsViewModelInjectable = getInjectable({
|
||||
getLogs: di.inject(getLogsInjectable),
|
||||
getLogsWithoutTimestamps: di.inject(getLogsWithoutTimestampsInjectable),
|
||||
getTimestampSplitLogs: di.inject(getTimestampSplitLogsInjectable),
|
||||
reloadLogs: di.inject(reloadLoadsInjectable),
|
||||
reloadLogs: di.inject(reloadLogsInjectable),
|
||||
getLogTabData: di.inject(getLogTabDataInjectable),
|
||||
setLogTabData: di.inject(setLogTabDataInjectable),
|
||||
loadLogs: di.inject(loadLogsInjectable),
|
||||
updateTabName: di.inject(updateTabNameInjectable),
|
||||
stopLoadingLogs: di.inject(stopLoadingLogsInjectable),
|
||||
getPodById: id => podsStore.getById(id),
|
||||
getPodsByOwnerId: id => podsStore.getPodsByOwnerId(id),
|
||||
}),
|
||||
lifecycle: lifecycleEnum.transient,
|
||||
});
|
||||
|
||||
@ -2,10 +2,11 @@
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { LogTabData } from "./tab.store";
|
||||
import type { LogTabData } from "./tab-store";
|
||||
import { computed, IComputedValue } from "mobx";
|
||||
import type { TabId } from "../dock-store/dock.store";
|
||||
import type { TabId } from "../dock/store";
|
||||
import { SearchStore } from "../../../search-store/search-store";
|
||||
import type { Pod } from "../../../../common/k8s-api/endpoints";
|
||||
|
||||
export interface LogTabViewModelDependencies {
|
||||
getLogs: (tabId: TabId) => string[];
|
||||
@ -15,8 +16,10 @@ export interface LogTabViewModelDependencies {
|
||||
setLogTabData: (tabId: TabId, data: LogTabData) => void;
|
||||
loadLogs: (tabId: TabId, logTabData: IComputedValue<LogTabData>) => Promise<void>;
|
||||
reloadLogs: (tabId: TabId, logTabData: IComputedValue<LogTabData>) => Promise<void>;
|
||||
updateTabName: (tabId: TabId) => void;
|
||||
updateTabName: (tabId: TabId, pod: Pod) => void;
|
||||
stopLoadingLogs: (tabId: TabId) => void;
|
||||
getPodById: (id: string) => Pod | undefined;
|
||||
getPodsByOwnerId: (id: string) => Pod[];
|
||||
}
|
||||
|
||||
export class LogTabViewModel {
|
||||
@ -26,6 +29,28 @@ export class LogTabViewModel {
|
||||
readonly logsWithoutTimestamps = computed(() => this.dependencies.getLogsWithoutTimestamps(this.tabId));
|
||||
readonly timestampSplitLogs = computed(() => this.dependencies.getTimestampSplitLogs(this.tabId));
|
||||
readonly logTabData = computed(() => this.dependencies.getLogTabData(this.tabId));
|
||||
readonly pods = computed(() => {
|
||||
const data = this.logTabData.get();
|
||||
|
||||
if (!data) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (typeof data.ownerId === "string") {
|
||||
return this.dependencies.getPodsByOwnerId(data.ownerId);
|
||||
}
|
||||
|
||||
return [this.dependencies.getPodById(data.selectedPodId)];
|
||||
});
|
||||
readonly pod = computed(() => {
|
||||
const data = this.logTabData.get();
|
||||
|
||||
if (!data) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.dependencies.getPodById(data.selectedPodId);
|
||||
});
|
||||
readonly searchStore = new SearchStore();
|
||||
|
||||
updateLogTabData = (partialData: Partial<LogTabData>) => {
|
||||
@ -34,6 +59,6 @@ export class LogTabViewModel {
|
||||
|
||||
loadLogs = () => this.dependencies.loadLogs(this.tabId, this.logTabData);
|
||||
reloadLogs = () => this.dependencies.reloadLogs(this.tabId, this.logTabData);
|
||||
updateTabName = () => this.dependencies.updateTabName(this.tabId);
|
||||
updateTabName = () => this.dependencies.updateTabName(this.tabId, this.pod.get());
|
||||
stopLoadingLogs = () => this.dependencies.stopLoadingLogs(this.tabId);
|
||||
}
|
||||
|
||||
@ -3,11 +3,25 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { bind } from "../../../utils";
|
||||
import type { LogStore } from "./store";
|
||||
import logStoreInjectable from "./store.injectable";
|
||||
import type { LogTabData } from "./tab-store";
|
||||
|
||||
const reloadLoadsInjectable = getInjectable({
|
||||
instantiate: (di) => di.inject(logStoreInjectable).reload,
|
||||
interface Dependencies {
|
||||
logStore: LogStore;
|
||||
}
|
||||
|
||||
function reloadLogs({ logStore }: Dependencies, tabId: string, logTabData: IComputedValue<LogTabData>): Promise<void> {
|
||||
return logStore.reload(tabId, logTabData);
|
||||
}
|
||||
|
||||
const reloadLogsInjectable = getInjectable({
|
||||
instantiate: (di) => bind(reloadLogs, null, {
|
||||
logStore: di.inject(logStoreInjectable),
|
||||
}),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default reloadLoadsInjectable;
|
||||
export default reloadLogsInjectable;
|
||||
|
||||
@ -8,11 +8,10 @@ import "./resource-selector.scss";
|
||||
import React, { useEffect } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
|
||||
import { Pod } from "../../../../common/k8s-api/endpoints";
|
||||
import { Badge } from "../../badge";
|
||||
import { Select, SelectOption } from "../../select";
|
||||
import { podsStore } from "../../+workloads-pods/pods.store";
|
||||
import type { LogTabViewModel } from "./logs-view-model";
|
||||
import { action } from "mobx";
|
||||
|
||||
export interface LogResourceSelectorProps {
|
||||
model: LogTabViewModel;
|
||||
@ -25,27 +24,26 @@ export const LogResourceSelector = observer(({ model }: LogResourceSelectorProps
|
||||
return null;
|
||||
}
|
||||
|
||||
const { selectedPod, selectedContainer, pods } = tabData;
|
||||
const pod = new Pod(selectedPod);
|
||||
const { selectedContainer } = tabData;
|
||||
const pods = model.pods.get();
|
||||
const pod = model.pod.get();
|
||||
const containers = pod.getContainers();
|
||||
const initContainers = pod.getInitContainers();
|
||||
|
||||
const onContainerChange = (option: SelectOption) => {
|
||||
const onContainerChange = (option: SelectOption<string>) => {
|
||||
model.updateLogTabData({
|
||||
selectedContainer: containers
|
||||
.concat(initContainers)
|
||||
.find(container => container.name === option.value),
|
||||
selectedContainer: option.value,
|
||||
});
|
||||
|
||||
model.reloadLogs();
|
||||
};
|
||||
|
||||
const onPodChange = (option: SelectOption) => {
|
||||
const selectedPod = podsStore.getByName(option.value, pod.getNs());
|
||||
|
||||
model.updateLogTabData({ selectedPod });
|
||||
const onPodChange = action((option: SelectOption<string>) => {
|
||||
model.updateLogTabData({
|
||||
selectedPodId: option.value,
|
||||
});
|
||||
model.updateTabName();
|
||||
};
|
||||
});
|
||||
|
||||
const getSelectOptions = (items: string[]) => {
|
||||
return items.map(item => {
|
||||
@ -76,7 +74,7 @@ export const LogResourceSelector = observer(({ model }: LogResourceSelectorProps
|
||||
|
||||
useEffect(() => {
|
||||
model.reloadLogs();
|
||||
}, [selectedPod]);
|
||||
}, [pod.getId()]);
|
||||
|
||||
return (
|
||||
<div className="LogResourceSelector flex gaps align-center">
|
||||
@ -93,7 +91,7 @@ export const LogResourceSelector = observer(({ model }: LogResourceSelectorProps
|
||||
<span>Container</span>
|
||||
<Select
|
||||
options={containerSelectOptions}
|
||||
value={{ label: selectedContainer.name, value: selectedContainer.name }}
|
||||
value={{ label: selectedContainer, value: selectedContainer }}
|
||||
onChange={onContainerChange}
|
||||
autoConvertOptions={false}
|
||||
className="container-selector"
|
||||
|
||||
@ -3,10 +3,22 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { bind } from "../../../utils";
|
||||
import type { LogTabData, LogTabStore } from "./tab-store";
|
||||
import logTabStoreInjectable from "./tab-store.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
logTabStore: LogTabStore;
|
||||
}
|
||||
|
||||
function setLogTabData({ logTabStore }: Dependencies, tabId: string, data: LogTabData): void {
|
||||
return logTabStore.setData(tabId, data);
|
||||
}
|
||||
|
||||
const setLogTabDataInjectable = getInjectable({
|
||||
instantiate: (di) => di.inject(logTabStoreInjectable).setData,
|
||||
instantiate: (di) => bind(setLogTabData, null, {
|
||||
logTabStore: di.inject(logTabStoreInjectable),
|
||||
}),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
|
||||
@ -3,10 +3,22 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { bind } from "../../../utils";
|
||||
import type { LogStore } from "./store";
|
||||
import logStoreInjectable from "./store.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
logStore: LogStore;
|
||||
}
|
||||
|
||||
function stopLoadingLogs({ logStore }: Dependencies, tabId: string): void {
|
||||
return logStore.stopLoadingLogs(tabId);
|
||||
}
|
||||
|
||||
const stopLoadingLogsInjectable = getInjectable({
|
||||
instantiate: (di) => di.inject(logStoreInjectable).stopLoadingLogs,
|
||||
instantiate: (di) => bind(stopLoadingLogs, null, {
|
||||
logStore: di.inject(logStoreInjectable),
|
||||
}),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
|
||||
@ -5,10 +5,12 @@
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { LogStore } from "./store";
|
||||
import callForLogsInjectable from "./call-for-logs.injectable";
|
||||
import { podsStore } from "../../+workloads-pods/pods.store";
|
||||
|
||||
const logStoreInjectable = getInjectable({
|
||||
instantiate: (di) => new LogStore({
|
||||
callForLogs: di.inject(callForLogsInjectable),
|
||||
getPodById: id => podsStore.getById(id),
|
||||
}),
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
|
||||
@ -3,31 +3,28 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { computed, observable, makeObservable, IComputedValue } from "mobx";
|
||||
|
||||
import { IPodLogsQuery, Pod } from "../../../../common/k8s-api/endpoints";
|
||||
import { autoBind, getOrInsertWith, interval, IntervalFn } from "../../../utils";
|
||||
import type { TabId } from "../dock-store/dock.store";
|
||||
import type { LogTabData } from "./tab.store";
|
||||
import { observable, IComputedValue } from "mobx";
|
||||
import type { IPodLogsQuery, Pod } from "../../../../common/k8s-api/endpoints";
|
||||
import { getOrInsertWith, interval, IntervalFn } from "../../../utils";
|
||||
import type { TabId } from "../dock/store";
|
||||
import type { LogTabData } from "./tab-store";
|
||||
|
||||
type PodLogLine = string;
|
||||
|
||||
const logLinesToLoad = 500;
|
||||
|
||||
interface Dependencies {
|
||||
getPodById: (id: string) => Pod | undefined;
|
||||
callForLogs: ({ namespace, name }: { namespace: string, name: string }, query: IPodLogsQuery) => Promise<string>
|
||||
}
|
||||
|
||||
export class LogStore {
|
||||
@observable protected podLogs = observable.map<TabId, PodLogLine[]>();
|
||||
protected podLogs = observable.map<TabId, PodLogLine[]>();
|
||||
protected refreshers = new Map<TabId, IntervalFn>();
|
||||
|
||||
constructor(private dependencies: Dependencies) {
|
||||
makeObservable(this);
|
||||
autoBind(this);
|
||||
}
|
||||
constructor(private dependencies: Dependencies) {}
|
||||
|
||||
handlerError(tabId: TabId, error: any): void {
|
||||
protected handlerError(tabId: TabId, error: any): void {
|
||||
if (error.error && !(error.message || error.reason || error.code)) {
|
||||
error = error.error;
|
||||
}
|
||||
@ -47,10 +44,10 @@ export class LogStore {
|
||||
* Also, it handles loading errors, rewriting whole logs with error
|
||||
* messages
|
||||
*/
|
||||
load = async (tabId: TabId, logTabData: IComputedValue<LogTabData>) => {
|
||||
public async load(tabId: TabId, logTabData: IComputedValue<LogTabData>): Promise<void> {
|
||||
try {
|
||||
const logs = await this.loadLogs(logTabData, {
|
||||
tailLines: this.getLinesByTabId(tabId) + logLinesToLoad,
|
||||
tailLines: this.getLogLines(tabId) + logLinesToLoad,
|
||||
});
|
||||
|
||||
this.getRefresher(tabId, logTabData).start();
|
||||
@ -58,7 +55,7 @@ export class LogStore {
|
||||
} catch (error) {
|
||||
this.handlerError(tabId, error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private getRefresher(tabId: TabId, logTabData: IComputedValue<LogTabData>): IntervalFn {
|
||||
return getOrInsertWith(this.refreshers, tabId, () => (
|
||||
@ -84,7 +81,7 @@ export class LogStore {
|
||||
* starting from last line received.
|
||||
* @param tabId
|
||||
*/
|
||||
loadMore = async (tabId: TabId, logTabData: IComputedValue<LogTabData>) => {
|
||||
public async loadMore(tabId: TabId, logTabData: IComputedValue<LogTabData>): Promise<void> {
|
||||
if (!this.podLogs.get(tabId).length) {
|
||||
return;
|
||||
}
|
||||
@ -100,7 +97,7 @@ export class LogStore {
|
||||
} catch (error) {
|
||||
this.handlerError(tabId, error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Main logs loading function adds necessary data to payload and makes
|
||||
@ -110,16 +107,16 @@ export class LogStore {
|
||||
* @returns A fetch request promise
|
||||
*/
|
||||
private async loadLogs(logTabData: IComputedValue<LogTabData>, params: Partial<IPodLogsQuery>): Promise<string[]> {
|
||||
const { selectedContainer, previous, selectedPod } = logTabData.get();
|
||||
const pod = new Pod(selectedPod);
|
||||
const { selectedContainer, showPrevious, selectedPodId } = logTabData.get();
|
||||
const pod = this.dependencies.getPodById(selectedPodId);
|
||||
const namespace = pod.getNs();
|
||||
const name = pod.getName();
|
||||
|
||||
const result = await this.dependencies.callForLogs({ namespace, name }, {
|
||||
...params,
|
||||
timestamps: true, // Always setting timestamp to separate old logs from new ones
|
||||
container: selectedContainer.name,
|
||||
previous,
|
||||
container: selectedContainer,
|
||||
previous: showPrevious,
|
||||
});
|
||||
|
||||
return result.trimEnd().split("\n");
|
||||
@ -130,26 +127,25 @@ export class LogStore {
|
||||
* Converts logs into a string array
|
||||
* @returns Length of log lines
|
||||
*/
|
||||
@computed
|
||||
get lines(): number {
|
||||
return this.logs.length;
|
||||
}
|
||||
|
||||
public getLinesByTabId = (tabId: TabId): number => {
|
||||
return this.getLogsByTabId(tabId).length;
|
||||
};
|
||||
getLogLines(tabId: TabId): number{
|
||||
return this.getLogs(tabId).length;
|
||||
}
|
||||
|
||||
public getLogsByTabId = (tabId: TabId): string[] => {
|
||||
getLogs(tabId: TabId): string[]{
|
||||
return this.podLogs.get(tabId) ?? [];
|
||||
};
|
||||
}
|
||||
|
||||
public getLogsWithoutTimestampsByTabId = (tabId: TabId): string[] => {
|
||||
return this.getLogsByTabId(tabId).map(this.removeTimestamps);
|
||||
};
|
||||
getLogsWithoutTimestamps(tabId: TabId): string[]{
|
||||
return this.getLogs(tabId).map(this.removeTimestamps);
|
||||
}
|
||||
|
||||
public getTimestampSplitLogsByTabId = (tabId: TabId): [string, string][] => {
|
||||
return this.getLogsByTabId(tabId).map(this.splitOutTimestamp);
|
||||
};
|
||||
getTimestampSplitLogs(tabId: TabId): [string, string][]{
|
||||
return this.getLogs(tabId).map(this.splitOutTimestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This now only returns the empty array
|
||||
@ -173,7 +169,7 @@ export class LogStore {
|
||||
* (this allows to avoid getting the last stamp in the selection)
|
||||
* @param tabId
|
||||
*/
|
||||
getLastSinceTime(tabId: TabId) {
|
||||
getLastSinceTime(tabId: TabId): string {
|
||||
const logs = this.podLogs.get(tabId);
|
||||
const timestamps = this.getTimestamps(logs[logs.length - 1]);
|
||||
const stamp = new Date(timestamps ? timestamps[0] : null);
|
||||
@ -183,7 +179,7 @@ export class LogStore {
|
||||
return stamp.toISOString();
|
||||
}
|
||||
|
||||
splitOutTimestamp = (logs: string): [string, string] => {
|
||||
splitOutTimestamp(logs: string): [string, string] {
|
||||
const extraction = /^(\d+\S+)(.*)/m.exec(logs);
|
||||
|
||||
if (!extraction || extraction.length < 3) {
|
||||
@ -191,23 +187,23 @@ export class LogStore {
|
||||
}
|
||||
|
||||
return [extraction[1], extraction[2]];
|
||||
};
|
||||
}
|
||||
|
||||
getTimestamps(logs: string) {
|
||||
getTimestamps(logs: string): RegExpMatchArray {
|
||||
return logs.match(/^\d+\S+/gm);
|
||||
}
|
||||
|
||||
removeTimestamps = (logs: string) => {
|
||||
removeTimestamps(logs: string): string {
|
||||
return logs.replace(/^\d+.*?\s/gm, "");
|
||||
};
|
||||
}
|
||||
|
||||
clearLogs(tabId: TabId) {
|
||||
clearLogs(tabId: TabId): void {
|
||||
this.podLogs.delete(tabId);
|
||||
}
|
||||
|
||||
reload = (tabId: TabId, logTabData: IComputedValue<LogTabData>) => {
|
||||
reload(tabId: TabId, logTabData: IComputedValue<LogTabData>): Promise<void> {
|
||||
this.clearLogs(tabId);
|
||||
|
||||
return this.load(tabId, logTabData);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,13 +3,11 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { LogTabStore } from "./tab.store";
|
||||
import dockStoreInjectable from "../dock-store/dock-store.injectable";
|
||||
import { LogTabStore } from "./tab-store";
|
||||
import createStorageInjectable from "../../../utils/create-storage/create-storage.injectable";
|
||||
|
||||
const logTabStoreInjectable = getInjectable({
|
||||
instantiate: (di) => new LogTabStore({
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
createStorage: di.inject(createStorageInjectable),
|
||||
}),
|
||||
|
||||
|
||||
66
src/renderer/components/dock/logs/tab-store.ts
Normal file
66
src/renderer/components/dock/logs/tab-store.ts
Normal file
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { DockTabStorageState, DockTabStore } from "../dock-tab-store/dock-tab.store";
|
||||
import type { StorageHelper } from "../../../utils";
|
||||
import type { TabId } from "../dock/store";
|
||||
import { logTabDataValidator } from "./log-tab-data.validator";
|
||||
|
||||
export interface LogTabData {
|
||||
/**
|
||||
* The workload's uid for a workload logs tab
|
||||
*/
|
||||
ownerId?: string;
|
||||
|
||||
/**
|
||||
* The uid of the currently selected pod
|
||||
*/
|
||||
selectedPodId: string;
|
||||
|
||||
/**
|
||||
* The namespace of the pods/workload
|
||||
*/
|
||||
namespace: string;
|
||||
|
||||
/**
|
||||
* The name of the currently selected container within the currently selected
|
||||
* pod
|
||||
*/
|
||||
selectedContainer: string;
|
||||
|
||||
/**
|
||||
* Whether to show timestamps in the logs
|
||||
*/
|
||||
showTimestamps: boolean;
|
||||
|
||||
/**
|
||||
* Whether to show the logs of the previous container instance
|
||||
*/
|
||||
showPrevious: boolean;
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
createStorage: <T>(storageKey: string, options: DockTabStorageState<T>) => StorageHelper<DockTabStorageState<T>>
|
||||
}
|
||||
|
||||
export class LogTabStore extends DockTabStore<LogTabData> {
|
||||
constructor(protected dependencies: Dependencies) {
|
||||
super(dependencies, {
|
||||
storageKey: "pod_logs",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the data for `tabId` is valid
|
||||
*/
|
||||
isDataValid(tabId: TabId): boolean {
|
||||
if (!this.getData(tabId)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !logTabDataValidator.validate(this.getData(tabId)).error;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,139 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import uniqueId from "lodash/uniqueId";
|
||||
import { reaction } from "mobx";
|
||||
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 { 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[];
|
||||
selectedPod: Pod;
|
||||
selectedContainer: IPodContainer
|
||||
showTimestamps?: boolean
|
||||
previous?: boolean
|
||||
}
|
||||
|
||||
interface PodLogsTabData {
|
||||
selectedPod: Pod
|
||||
selectedContainer: IPodContainer
|
||||
}
|
||||
|
||||
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(protected dependencies: Dependencies) {
|
||||
super(dependencies, {
|
||||
storageKey: "pod_logs",
|
||||
});
|
||||
|
||||
reaction(() => podsStore.items.length, () => this.updateTabsData());
|
||||
}
|
||||
|
||||
createPodTab({ selectedPod, selectedContainer }: PodLogsTabData): string {
|
||||
const podOwner = selectedPod.getOwnerRefs()[0];
|
||||
const pods = podsStore.getPodsByOwnerId(podOwner?.uid);
|
||||
const title = `Pod ${selectedPod.getName()}`;
|
||||
|
||||
return this.createLogsTab(title, {
|
||||
pods: pods.length ? pods : [selectedPod],
|
||||
selectedPod,
|
||||
selectedContainer,
|
||||
});
|
||||
}
|
||||
|
||||
createWorkloadTab({ workload }: WorkloadLogsTabData): void {
|
||||
const pods = podsStore.getPodsByOwnerId(workload.getId());
|
||||
|
||||
if (!pods.length) return;
|
||||
|
||||
const selectedPod = pods[0];
|
||||
const selectedContainer = selectedPod.getAllContainers()[0];
|
||||
const title = `${workload.kind} ${selectedPod.getName()}`;
|
||||
|
||||
this.createLogsTab(title, {
|
||||
pods,
|
||||
selectedPod,
|
||||
selectedContainer,
|
||||
});
|
||||
}
|
||||
|
||||
updateTabName(tabId: string) {
|
||||
const { selectedPod } = this.getData(tabId);
|
||||
|
||||
this.dependencies.dockStore.renameTab(tabId, `Pod ${selectedPod.metadata.name}`);
|
||||
}
|
||||
|
||||
private createDockTab(tabParams: DockTabCreateSpecific) {
|
||||
this.dependencies.dockStore.createTab({
|
||||
...tabParams,
|
||||
kind: TabKind.POD_LOGS,
|
||||
}, false);
|
||||
}
|
||||
|
||||
private createLogsTab(title: string, data: LogTabData): string {
|
||||
const id = uniqueId("log-tab-");
|
||||
|
||||
this.createDockTab({ id, title });
|
||||
this.setData(id, {
|
||||
...data,
|
||||
showTimestamps: false,
|
||||
previous: false,
|
||||
});
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
private updateTabsData() {
|
||||
for (const [tabId, tabData] of this.data) {
|
||||
try {
|
||||
if (!tabData.selectedPod) {
|
||||
tabData.selectedPod = tabData.pods[0];
|
||||
}
|
||||
|
||||
const pod = new Pod(tabData.selectedPod);
|
||||
const pods = podsStore.getPodsByOwnerId(pod.getOwnerRefs()[0]?.uid);
|
||||
const isSelectedPodInList = pods.find(item => item.getId() == pod.getId());
|
||||
const selectedPod = isSelectedPodInList ? pod : pods[0];
|
||||
const selectedContainer = isSelectedPodInList ? tabData.selectedContainer : pod.getAllContainers()[0];
|
||||
|
||||
if (pods.length > 0) {
|
||||
this.setData(tabId, {
|
||||
...tabData,
|
||||
selectedPod,
|
||||
selectedContainer,
|
||||
pods,
|
||||
});
|
||||
|
||||
this.updateTabName(tabId);
|
||||
} else {
|
||||
this.closeTab(tabId);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`[LOG-TAB-STORE]: failed to set data for tabId=${tabId} deleting`, error);
|
||||
this.data.delete(tabId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private closeTab(tabId: string) {
|
||||
this.clearData(tabId);
|
||||
this.dependencies.dockStore.closeTab(tabId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,10 +3,23 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import logTabStoreInjectable from "./tab-store.injectable";
|
||||
import type { Pod } from "../../../../common/k8s-api/endpoints";
|
||||
import { bind } from "../../../utils";
|
||||
import type { TabId } from "../dock/store";
|
||||
import renameTabInjectable from "../dock/rename-tab.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
renameTab: (tabId: TabId, title: string) => void;
|
||||
}
|
||||
|
||||
function updateTabName({ renameTab }: Dependencies, tabId: string, pod: Pod): void {
|
||||
renameTab(tabId, `Pod ${pod.getName()}`);
|
||||
}
|
||||
|
||||
const updateTabNameInjectable = getInjectable({
|
||||
instantiate: (di) => di.inject(logTabStoreInjectable).updateTabName,
|
||||
instantiate: (di) => bind(updateTabName, null, {
|
||||
renameTab: di.inject(renameTabInjectable),
|
||||
}),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
|
||||
105
src/renderer/components/dock/logs/view.tsx
Normal file
105
src/renderer/components/dock/logs/view.tsx
Normal file
@ -0,0 +1,105 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import React, { createRef, useEffect } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { InfoPanel } from "../info-panel";
|
||||
import { LogResourceSelector } from "./resource-selector";
|
||||
import { LogList } from "./list";
|
||||
import { LogSearch } from "./search";
|
||||
import { LogControls } from "./controls";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import logsViewModelInjectable from "./logs-view-model.injectable";
|
||||
import type { LogTabViewModel } from "./logs-view-model";
|
||||
import type { DockTab } from "../dock/store";
|
||||
import { cssNames, Disposer } from "../../../utils";
|
||||
import type { KubeWatchSubscribeStoreOptions } from "../../../kube-watch-api/kube-watch-api";
|
||||
import subscribeStoresInjectable from "../../../kube-watch-api/subscribe-stores.injectable";
|
||||
import type { KubeObjectStore } from "../../../../common/k8s-api/kube-object.store";
|
||||
import type { KubeObject } from "../../../../common/k8s-api/kube-object";
|
||||
import { podsStore } from "../../+workloads-pods/pods.store";
|
||||
|
||||
export interface LogsDockTabProps {
|
||||
className?: string;
|
||||
tab: DockTab;
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
model: LogTabViewModel;
|
||||
subscribeStores: (stores: KubeObjectStore<KubeObject>[], opts?: KubeWatchSubscribeStoreOptions) => Disposer;
|
||||
}
|
||||
|
||||
const NonInjectedLogsDockTab = observer(({ className, tab, model, subscribeStores }: Dependencies & LogsDockTabProps) => {
|
||||
const logListElement = createRef<LogList>();
|
||||
const data = model.logTabData.get();
|
||||
|
||||
useEffect(() => {
|
||||
model.reloadLogs();
|
||||
|
||||
return model.stopLoadingLogs;
|
||||
}, []);
|
||||
useEffect(() => subscribeStores([
|
||||
podsStore,
|
||||
], {
|
||||
namespaces: data ? [data.namespace] : [],
|
||||
}), [data?.namespace]);
|
||||
|
||||
const scrollToOverlay = () => {
|
||||
const { activeOverlayLine } = model.searchStore;
|
||||
|
||||
if (!logListElement.current || activeOverlayLine === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Scroll vertically
|
||||
logListElement.current.scrollToItem(activeOverlayLine, "center");
|
||||
// Scroll horizontally in timeout since virtual list need some time to prepare its contents
|
||||
setTimeout(() => {
|
||||
const overlay = document.querySelector(".PodLogs .list span.active");
|
||||
|
||||
if (!overlay) return;
|
||||
overlay.scrollIntoViewIfNeeded();
|
||||
}, 100);
|
||||
};
|
||||
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cssNames("PodLogs flex column", className)}>
|
||||
<InfoPanel
|
||||
tabId={tab.id}
|
||||
controls={(
|
||||
<div className="flex gaps">
|
||||
<LogResourceSelector model={model} />
|
||||
<LogSearch
|
||||
onSearch={scrollToOverlay}
|
||||
model={model}
|
||||
toPrevOverlay={scrollToOverlay}
|
||||
toNextOverlay={scrollToOverlay}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
showSubmitClose={false}
|
||||
showButtons={false}
|
||||
showStatusPanel={false}
|
||||
/>
|
||||
<LogList model={model} ref={logListElement} />
|
||||
<LogControls model={model} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
export const LogsDockTab = withInjectables<Dependencies, LogsDockTabProps>(NonInjectedLogsDockTab, {
|
||||
getProps: (di, props) => ({
|
||||
model: di.inject(logsViewModelInjectable, {
|
||||
tabId: props.tab.id,
|
||||
}),
|
||||
subscribeStores: di.inject(subscribeStoresInjectable),
|
||||
...props,
|
||||
}),
|
||||
});
|
||||
@ -1,152 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { autorun, observable, when } from "mobx";
|
||||
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
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
createTerminalTab: () => DockTabCreate
|
||||
dockStore: DockStore
|
||||
createTerminal: (tabId: TabId, api: TerminalApi) => Terminal
|
||||
}
|
||||
|
||||
export class TerminalStore {
|
||||
protected terminals = new Map<TabId, Terminal>();
|
||||
protected connections = observable.map<TabId, TerminalApi>();
|
||||
|
||||
constructor(private dependencies: Dependencies) {
|
||||
autoBind(this);
|
||||
|
||||
// connect active tab
|
||||
autorun(() => {
|
||||
const { selectedTab, isOpen } = dependencies.dockStore;
|
||||
|
||||
if (selectedTab?.kind === TabKind.TERMINAL && isOpen) {
|
||||
this.connect(selectedTab.id);
|
||||
}
|
||||
});
|
||||
// disconnect closed tabs
|
||||
autorun(() => {
|
||||
const currentTabs = dependencies.dockStore.tabs.map(tab => tab.id);
|
||||
|
||||
for (const [tabId] of this.connections) {
|
||||
if (!currentTabs.includes(tabId)) this.disconnect(tabId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
connect(tabId: TabId) {
|
||||
if (this.isConnected(tabId)) {
|
||||
return;
|
||||
}
|
||||
const tab: ITerminalTab = this.dependencies.dockStore.getTabById(tabId);
|
||||
const api = new TerminalApi({
|
||||
id: tabId,
|
||||
node: tab.node,
|
||||
});
|
||||
const terminal = this.dependencies.createTerminal(tabId, api);
|
||||
|
||||
this.connections.set(tabId, api);
|
||||
this.terminals.set(tabId, terminal);
|
||||
|
||||
api.connect();
|
||||
}
|
||||
|
||||
disconnect(tabId: TabId) {
|
||||
if (!this.isConnected(tabId)) {
|
||||
return;
|
||||
}
|
||||
const terminal = this.terminals.get(tabId);
|
||||
const terminalApi = this.connections.get(tabId);
|
||||
|
||||
terminal.destroy();
|
||||
terminalApi.destroy();
|
||||
this.connections.delete(tabId);
|
||||
this.terminals.delete(tabId);
|
||||
}
|
||||
|
||||
reconnect(tabId: TabId) {
|
||||
this.connections.get(tabId)?.connect();
|
||||
}
|
||||
|
||||
isConnected(tabId: TabId) {
|
||||
return Boolean(this.connections.get(tabId));
|
||||
}
|
||||
|
||||
isDisconnected(tabId: TabId) {
|
||||
return this.connections.get(tabId)?.readyState === WebSocketApiState.CLOSED;
|
||||
}
|
||||
|
||||
async sendCommand(command: string, options: { enter?: boolean; newTab?: boolean; tabId?: TabId } = {}) {
|
||||
const { enter, newTab, tabId } = options;
|
||||
|
||||
if (tabId) {
|
||||
this.dependencies.dockStore.selectTab(tabId);
|
||||
}
|
||||
|
||||
if (newTab) {
|
||||
const tab = this.dependencies.createTerminalTab();
|
||||
|
||||
await when(() => this.connections.has(tab.id));
|
||||
|
||||
const shellIsReady = when(() => this.connections.get(tab.id).isReady);
|
||||
const notifyVeryLong = setTimeout(() => {
|
||||
shellIsReady.cancel();
|
||||
Notifications.info(
|
||||
"If terminal shell is not ready please check your shell init files, if applicable.",
|
||||
{
|
||||
timeout: 4_000,
|
||||
},
|
||||
);
|
||||
}, 10_000);
|
||||
|
||||
await shellIsReady.catch(noop);
|
||||
clearTimeout(notifyVeryLong);
|
||||
}
|
||||
|
||||
const terminalApi = this.connections.get(this.dependencies.dockStore.selectedTabId);
|
||||
|
||||
if (terminalApi) {
|
||||
if (enter) {
|
||||
command += "\r";
|
||||
}
|
||||
|
||||
terminalApi.sendMessage({
|
||||
type: TerminalChannels.STDIN,
|
||||
data: command,
|
||||
});
|
||||
} else {
|
||||
console.warn(
|
||||
"The selected tab is does not have a connection. Cannot send command.",
|
||||
{ tabId: this.dependencies.dockStore.selectedTabId, command },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
getTerminal(tabId: TabId) {
|
||||
return this.terminals.get(tabId);
|
||||
}
|
||||
|
||||
reset() {
|
||||
[...this.connections].forEach(([tabId]) => {
|
||||
this.disconnect(tabId);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
.TerminalTab {
|
||||
|
||||
}
|
||||
@ -1,74 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import "./terminal-window.scss";
|
||||
|
||||
import React from "react";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import { cssNames } from "../../utils";
|
||||
import type { Terminal } from "./terminal/terminal";
|
||||
import type { TerminalStore } from "./terminal-store/terminal.store";
|
||||
import { ThemeStore } from "../../theme.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
|
||||
class NonInjectedTerminalWindow extends React.Component<Props & Dependencies> {
|
||||
public elem: HTMLElement;
|
||||
public terminal: Terminal;
|
||||
|
||||
componentDidMount() {
|
||||
disposeOnUnmount(this, [
|
||||
this.props.dockStore.onTabChange(({ tabId }) => this.activate(tabId), {
|
||||
tabKind: TabKind.TERMINAL,
|
||||
fireImmediately: true,
|
||||
}),
|
||||
|
||||
// refresh terminal available space (cols/rows) when <Dock/> resized
|
||||
this.props.dockStore.onResize(() => this.terminal?.fitLazy(), {
|
||||
fireImmediately: true,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
activate(tabId: TabId) {
|
||||
this.terminal?.detach(); // detach previous
|
||||
this.terminal = this.props.terminalStore.getTerminal(tabId);
|
||||
this.terminal.attachTo(this.elem);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
className={cssNames("TerminalWindow", ThemeStore.getInstance().activeTheme.type)}
|
||||
ref={elem => this.elem = elem}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const TerminalWindow = withInjectables<Dependencies, Props>(
|
||||
NonInjectedTerminalWindow,
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
terminalStore: di.inject(terminalStoreInjectable),
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { bind } from "../../../utils";
|
||||
import type { TabId } from "../dock/store";
|
||||
import type { TerminalStore } from "./store";
|
||||
import terminalStoreInjectable from "./store.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
terminalStore: TerminalStore;
|
||||
}
|
||||
|
||||
function clearTerminalTabData({ terminalStore }: Dependencies, tabId: TabId): void {
|
||||
terminalStore.destroy(tabId);
|
||||
}
|
||||
|
||||
const clearTerminalTabDataInjectable = getInjectable({
|
||||
instantiate: (di) => bind(clearTerminalTabData, null, {
|
||||
terminalStore: di.inject(terminalStoreInjectable),
|
||||
}),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default clearTerminalTabDataInjectable;
|
||||
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import dockStoreInjectable from "../dock/store.injectable";
|
||||
import { DockStore, DockTabCreateSpecific, TabKind } from "../dock/store";
|
||||
import { bind } from "../../../utils";
|
||||
|
||||
interface Dependencies {
|
||||
dockStore: DockStore;
|
||||
}
|
||||
|
||||
export function createTerminalTab({ dockStore }: Dependencies, tabParams: DockTabCreateSpecific = {}) {
|
||||
return dockStore.createTab({
|
||||
title: `Terminal`,
|
||||
...tabParams,
|
||||
kind: TabKind.TERMINAL,
|
||||
});
|
||||
}
|
||||
|
||||
const createTerminalTabInjectable = getInjectable({
|
||||
instantiate: (di) => bind(createTerminalTab, null, {
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
}),
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default createTerminalTabInjectable;
|
||||
@ -4,20 +4,11 @@
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { Terminal } from "./terminal";
|
||||
import type { TabId } from "../dock-store/dock.store";
|
||||
import type { TabId } from "../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);
|
||||
},
|
||||
|
||||
instantiate: () => (tabId: TabId, api: TerminalApi) => new Terminal(tabId, api),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
|
||||
@ -3,19 +3,17 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import "./terminal-tab.scss";
|
||||
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { boundMethod, cssNames } from "../../utils";
|
||||
import { DockTab, DockTabProps } from "./dock-tab";
|
||||
import { Icon } from "../icon";
|
||||
import type { TerminalStore } from "./terminal-store/terminal.store";
|
||||
import type { DockStore } from "./dock-store/dock.store";
|
||||
import { boundMethod, cssNames } from "../../../utils";
|
||||
import { DockTab, DockTabProps } from "../dock-tab";
|
||||
import { Icon } from "../../icon";
|
||||
import type { TerminalStore } from "./store";
|
||||
import type { DockStore } from "../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";
|
||||
import dockStoreInjectable from "../dock/store.injectable";
|
||||
import terminalStoreInjectable from "./store.injectable";
|
||||
|
||||
interface Props extends DockTabProps {
|
||||
}
|
||||
@ -73,15 +71,11 @@ class NonInjectedTerminalTab extends React.Component<Props & Dependencies> {
|
||||
}
|
||||
}
|
||||
|
||||
export const TerminalTab = withInjectables<Dependencies, Props>(
|
||||
NonInjectedTerminalTab,
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
terminalStore: di.inject(terminalStoreInjectable),
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
);
|
||||
export const TerminalTab = withInjectables<Dependencies, Props>(NonInjectedTerminalTab, {
|
||||
getProps: (di, props) => ({
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
terminalStore: di.inject(terminalStoreInjectable),
|
||||
...props,
|
||||
}),
|
||||
});
|
||||
|
||||
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import type { TerminalApi } from "../../../api/terminal-api";
|
||||
import { bind } from "../../../utils";
|
||||
import type { TabId } from "../dock/store";
|
||||
import type { TerminalStore } from "./store";
|
||||
import terminalStoreInjectable from "./store.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
terminalStore: TerminalStore;
|
||||
}
|
||||
|
||||
function getTerminalApi({ terminalStore }: Dependencies, tabId: TabId): TerminalApi {
|
||||
return terminalStore.getTerminalApi(tabId);
|
||||
}
|
||||
|
||||
const getTerminalApiInjectable = getInjectable({
|
||||
instantiate: (di) => bind(getTerminalApi, null, {
|
||||
terminalStore: di.inject(terminalStoreInjectable),
|
||||
}),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default getTerminalApiInjectable;
|
||||
@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { when } from "mobx";
|
||||
import { TerminalApi, TerminalChannels } from "../../../api/terminal-api";
|
||||
import { bind, noop } from "../../../utils";
|
||||
import { Notifications } from "../../notifications";
|
||||
import selectDockTabInjectable from "../dock/select-dock-tab.injectable";
|
||||
import type { DockTab, TabId } from "../dock/store";
|
||||
import createTerminalTabInjectable from "./create-terminal-tab.injectable";
|
||||
import getTerminalApiInjectable from "./get-terminal-api.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
selectTab: (tabId: TabId) => void;
|
||||
createTerminalTab: () => DockTab;
|
||||
getTerminalApi: (tabId: TabId) => TerminalApi;
|
||||
}
|
||||
|
||||
export interface SendCommandOptions {
|
||||
/**
|
||||
* Emit an enter after the command
|
||||
*/
|
||||
enter?: boolean;
|
||||
|
||||
/**
|
||||
* @deprecated This option is ignored and infered to be `true` if `tabId` is not provided
|
||||
*/
|
||||
newTab?: any;
|
||||
|
||||
/**
|
||||
* Specify a specific terminal tab to send this command to
|
||||
*/
|
||||
tabId?: TabId;
|
||||
}
|
||||
|
||||
async function sendCommand({ selectTab, createTerminalTab, getTerminalApi }: Dependencies, command: string, options: SendCommandOptions = {}): Promise<void> {
|
||||
let { tabId } = options;
|
||||
|
||||
if (tabId) {
|
||||
selectTab(tabId);
|
||||
} else {
|
||||
const tab = createTerminalTab();
|
||||
|
||||
tabId = tab.id;
|
||||
await when(() => Boolean(getTerminalApi(tab.id)));
|
||||
|
||||
const shellIsReady = when(() => getTerminalApi(tab.id).isReady);
|
||||
const notifyVeryLong = setTimeout(() => {
|
||||
shellIsReady.cancel();
|
||||
Notifications.info(
|
||||
"If terminal shell is not ready please check your shell init files, if applicable.",
|
||||
{
|
||||
timeout: 4_000,
|
||||
},
|
||||
);
|
||||
}, 10_000);
|
||||
|
||||
await shellIsReady.catch(noop);
|
||||
clearTimeout(notifyVeryLong);
|
||||
}
|
||||
|
||||
const terminalApi = getTerminalApi(tabId);
|
||||
|
||||
if (terminalApi) {
|
||||
if (options.enter) {
|
||||
command += "\r";
|
||||
}
|
||||
|
||||
terminalApi.sendMessage({
|
||||
type: TerminalChannels.STDIN,
|
||||
data: command,
|
||||
});
|
||||
} else {
|
||||
console.warn(
|
||||
"The selected tab is does not have a connection. Cannot send command.",
|
||||
{ tabId, command },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const sendCommandInjectable = getInjectable({
|
||||
instantiate: (di) => bind(sendCommand, null, {
|
||||
createTerminalTab: di.inject(createTerminalTabInjectable),
|
||||
selectTab: di.inject(selectDockTabInjectable),
|
||||
getTerminalApi: di.inject(getTerminalApiInjectable),
|
||||
}),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default sendCommandInjectable;
|
||||
@ -3,15 +3,11 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
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";
|
||||
import { TerminalStore } from "./store";
|
||||
import createTerminalInjectable from "./create-terminal.injectable";
|
||||
|
||||
const terminalStoreInjectable = getInjectable({
|
||||
instantiate: (di) => new TerminalStore({
|
||||
createTerminalTab: di.inject(createTerminalTabInjectable),
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
createTerminal: di.inject(createTerminalInjectable),
|
||||
}),
|
||||
|
||||
87
src/renderer/components/dock/terminal/store.ts
Normal file
87
src/renderer/components/dock/terminal/store.ts
Normal file
@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { action, observable } from "mobx";
|
||||
import type { Terminal } from "./terminal";
|
||||
import { TerminalApi } from "../../../api/terminal-api";
|
||||
import type { DockTab, TabId } from "../dock/store";
|
||||
import { WebSocketApiState } from "../../../api/websocket-api";
|
||||
|
||||
export interface ITerminalTab extends DockTab {
|
||||
node?: string; // activate node shell mode
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
createTerminal: (tabId: TabId, api: TerminalApi) => Terminal;
|
||||
}
|
||||
|
||||
export class TerminalStore {
|
||||
protected terminals = new Map<TabId, Terminal>();
|
||||
protected connections = observable.map<TabId, TerminalApi>();
|
||||
|
||||
constructor(private dependencies: Dependencies) {
|
||||
}
|
||||
|
||||
@action
|
||||
connect(tab: ITerminalTab) {
|
||||
if (this.isConnected(tab.id)) {
|
||||
return;
|
||||
}
|
||||
const api = new TerminalApi({
|
||||
id: tab.id,
|
||||
node: tab.node,
|
||||
});
|
||||
const terminal = this.dependencies.createTerminal(tab.id, api);
|
||||
|
||||
this.connections.set(tab.id, api);
|
||||
this.terminals.set(tab.id, terminal);
|
||||
|
||||
api.connect();
|
||||
}
|
||||
|
||||
@action
|
||||
destroy(tabId: TabId) {
|
||||
const terminal = this.terminals.get(tabId);
|
||||
const terminalApi = this.connections.get(tabId);
|
||||
|
||||
terminal?.destroy();
|
||||
terminalApi?.destroy();
|
||||
this.connections.delete(tabId);
|
||||
this.terminals.delete(tabId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use `this.destroy()` instead
|
||||
*/
|
||||
disconnect(tabId: TabId) {
|
||||
this.destroy(tabId);
|
||||
}
|
||||
|
||||
reconnect(tabId: TabId) {
|
||||
this.connections.get(tabId)?.connect();
|
||||
}
|
||||
|
||||
isConnected(tabId: TabId) {
|
||||
return Boolean(this.connections.get(tabId));
|
||||
}
|
||||
|
||||
isDisconnected(tabId: TabId) {
|
||||
return this.connections.get(tabId)?.readyState === WebSocketApiState.CLOSED;
|
||||
}
|
||||
|
||||
getTerminal(tabId: TabId) {
|
||||
return this.terminals.get(tabId);
|
||||
}
|
||||
|
||||
getTerminalApi(tabId: TabId) {
|
||||
return this.connections.get(tabId);
|
||||
}
|
||||
|
||||
reset() {
|
||||
[...this.connections].forEach(([tabId]) => {
|
||||
this.destroy(tabId);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -7,7 +7,7 @@ import debounce from "lodash/debounce";
|
||||
import { reaction } from "mobx";
|
||||
import { Terminal as XTerm } from "xterm";
|
||||
import { FitAddon } from "xterm-addon-fit";
|
||||
import type { DockStore, TabId } from "../dock-store/dock.store";
|
||||
import type { TabId } from "../dock/store";
|
||||
import { TerminalApi, TerminalChannels } from "../../../api/terminal-api";
|
||||
import { ThemeStore } from "../../../theme.store";
|
||||
import { disposer } from "../../../utils";
|
||||
@ -18,12 +18,7 @@ import { clipboard } from "electron";
|
||||
import logger from "../../../../common/logger";
|
||||
import type { TerminalConfig } from "../../../../common/user-store/preferences-helpers";
|
||||
|
||||
interface Dependencies {
|
||||
dockStore: DockStore
|
||||
}
|
||||
|
||||
export class Terminal {
|
||||
|
||||
private terminalConfig: TerminalConfig = UserStore.getInstance().terminalConfig;
|
||||
|
||||
public static get spawningPool() {
|
||||
@ -56,12 +51,6 @@ export class Terminal {
|
||||
return this.xterm.element.querySelector(".xterm-viewport");
|
||||
}
|
||||
|
||||
get isActive() {
|
||||
const { isOpen, selectedTabId } = this.dependencies.dockStore;
|
||||
|
||||
return isOpen && selectedTabId === this.tabId;
|
||||
}
|
||||
|
||||
attachTo(parentElem: HTMLElement) {
|
||||
parentElem.appendChild(this.elem);
|
||||
this.onActivate();
|
||||
@ -75,7 +64,7 @@ export class Terminal {
|
||||
}
|
||||
}
|
||||
|
||||
constructor(private dependencies: Dependencies, public tabId: TabId, protected api: TerminalApi) {
|
||||
constructor(public tabId: TabId, protected api: TerminalApi) {
|
||||
// enable terminal addons
|
||||
this.xterm.loadAddon(this.fitAddon);
|
||||
|
||||
@ -107,7 +96,6 @@ export class Terminal {
|
||||
reaction(() => UserStore.getInstance().terminalConfig.fontFamily, this.setFontFamily, {
|
||||
fireImmediately: true,
|
||||
}),
|
||||
dependencies.dockStore.onResize(this.onResize),
|
||||
() => onDataHandler.dispose(),
|
||||
() => this.fitAddon.dispose(),
|
||||
() => this.api.removeAllListeners(),
|
||||
@ -126,7 +114,7 @@ export class Terminal {
|
||||
|
||||
fit = () => {
|
||||
// Since this function is debounced we need to read this value as late as possible
|
||||
if (!this.isActive || !this.xterm) {
|
||||
if (!this.xterm) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
74
src/renderer/components/dock/terminal/view.tsx
Normal file
74
src/renderer/components/dock/terminal/view.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import "./terminal-window.scss";
|
||||
|
||||
import React from "react";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import { cssNames } from "../../../utils";
|
||||
import type { Terminal } from "./terminal";
|
||||
import type { TerminalStore } from "./store";
|
||||
import { ThemeStore } from "../../../theme.store";
|
||||
import type { DockTab, DockStore } from "../dock/store";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import dockStoreInjectable from "../dock/store.injectable";
|
||||
import terminalStoreInjectable from "./store.injectable";
|
||||
|
||||
interface Props {
|
||||
tab: DockTab;
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
dockStore: DockStore;
|
||||
terminalStore: TerminalStore;
|
||||
}
|
||||
|
||||
@observer
|
||||
class NonInjectedTerminalWindow extends React.Component<Props & Dependencies> {
|
||||
public elem: HTMLElement;
|
||||
public terminal: Terminal;
|
||||
|
||||
componentDidMount() {
|
||||
this.props.terminalStore.connect(this.props.tab);
|
||||
this.terminal = this.props.terminalStore.getTerminal(this.props.tab.id);
|
||||
this.terminal.attachTo(this.elem);
|
||||
|
||||
disposeOnUnmount(this, [
|
||||
// refresh terminal available space (cols/rows) when <Dock/> resized
|
||||
this.props.dockStore.onResize(() => this.terminal.onResize(), {
|
||||
fireImmediately: true,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
componentDidUpdate(): void {
|
||||
this.terminal.detach();
|
||||
this.props.terminalStore.connect(this.props.tab);
|
||||
this.terminal = this.props.terminalStore.getTerminal(this.props.tab.id);
|
||||
this.terminal.attachTo(this.elem);
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
this.terminal.detach();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
className={cssNames("TerminalWindow", ThemeStore.getInstance().activeTheme.type)}
|
||||
ref={elem => this.elem = elem}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const TerminalWindow = withInjectables<Dependencies, Props>(NonInjectedTerminalWindow, {
|
||||
getProps: (di, props) => ({
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
terminalStore: di.inject(terminalStoreInjectable),
|
||||
...props,
|
||||
}),
|
||||
});
|
||||
|
||||
@ -1,128 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import {
|
||||
action,
|
||||
autorun,
|
||||
computed,
|
||||
IReactionDisposer,
|
||||
reaction,
|
||||
makeObservable,
|
||||
} from "mobx";
|
||||
import { DockStore, DockTab, TabId, TabKind } from "../dock-store/dock.store";
|
||||
import { DockTabStorageState, DockTabStore } from "../dock-tab-store/dock-tab.store";
|
||||
import {
|
||||
getReleaseValues,
|
||||
HelmRelease,
|
||||
} from "../../../../common/k8s-api/endpoints/helm-releases.api";
|
||||
import { iter, StorageHelper } from "../../../utils";
|
||||
import type { IAsyncComputed } from "@ogre-tools/injectable-react";
|
||||
|
||||
export interface IChartUpgradeData {
|
||||
releaseName: string;
|
||||
releaseNamespace: string;
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
releases: IAsyncComputed<HelmRelease[]>
|
||||
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>();
|
||||
|
||||
@computed private get releaseNameReverseLookup(): Map<string, string> {
|
||||
return new Map(iter.map(this.data, ([id, { releaseName }]) => [releaseName, id]));
|
||||
}
|
||||
|
||||
get values() {
|
||||
return this.dependencies.valuesStore;
|
||||
}
|
||||
|
||||
constructor(protected dependencies : Dependencies) {
|
||||
super(dependencies, {
|
||||
storageKey: "chart_releases",
|
||||
});
|
||||
|
||||
makeObservable(this);
|
||||
|
||||
autorun(() => {
|
||||
const { selectedTab, isOpen } = dependencies.dockStore;
|
||||
|
||||
if (selectedTab?.kind === TabKind.UPGRADE_CHART && isOpen) {
|
||||
this.loadData(selectedTab.id);
|
||||
}
|
||||
}, { delay: 250 });
|
||||
|
||||
autorun(() => {
|
||||
const objects = [...this.data.values()];
|
||||
|
||||
objects.forEach(({ releaseName }) => this.createReleaseWatcher(releaseName));
|
||||
});
|
||||
}
|
||||
|
||||
private createReleaseWatcher(releaseName: string) {
|
||||
if (this.watchers.get(releaseName)) {
|
||||
return;
|
||||
}
|
||||
const dispose = reaction(() => {
|
||||
const release = this.dependencies.releases.value.get().find(release => release.getName() === releaseName);
|
||||
|
||||
return release?.getRevision(); // watch changes only by revision
|
||||
},
|
||||
release => {
|
||||
const releaseTab = this.getTabByRelease(releaseName);
|
||||
|
||||
if (!releaseTab) {
|
||||
return;
|
||||
}
|
||||
|
||||
// auto-reload values if was loaded before
|
||||
if (release) {
|
||||
if (this.dependencies.dockStore.selectedTab === releaseTab && this.values.getData(releaseTab.id)) {
|
||||
this.loadValues(releaseTab.id);
|
||||
}
|
||||
}
|
||||
// clean up watcher, close tab if release not exists / was removed
|
||||
else {
|
||||
dispose();
|
||||
this.watchers.delete(releaseName);
|
||||
this.dependencies.dockStore.closeTab(releaseTab.id);
|
||||
}
|
||||
});
|
||||
|
||||
this.watchers.set(releaseName, dispose);
|
||||
}
|
||||
|
||||
isLoading(tabId = this.dependencies.dockStore.selectedTabId) {
|
||||
const values = this.values.getData(tabId);
|
||||
|
||||
return values === undefined;
|
||||
}
|
||||
|
||||
@action
|
||||
async loadData(tabId: TabId) {
|
||||
const values = this.values.getData(tabId);
|
||||
|
||||
await Promise.all([
|
||||
!values && this.loadValues(tabId),
|
||||
]);
|
||||
}
|
||||
|
||||
@action
|
||||
async loadValues(tabId: TabId) {
|
||||
this.values.clearData(tabId); // reset
|
||||
const { releaseName, releaseNamespace } = this.getData(tabId);
|
||||
const values = await getReleaseValues(releaseName, releaseNamespace, true);
|
||||
|
||||
this.values.setData(tabId, values);
|
||||
}
|
||||
|
||||
getTabByRelease(releaseName: string): DockTab {
|
||||
return this.dependencies.dockStore.getTabById(this.releaseNameReverseLookup.get(releaseName));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { bind } from "../../../utils";
|
||||
import type { TabId } from "../dock/store";
|
||||
import type { UpgradeChartTabStore } from "./store";
|
||||
import upgradeChartTabStoreInjectable from "./store.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
upgradeChartTabStore: UpgradeChartTabStore;
|
||||
}
|
||||
|
||||
function clearUpgradeChartTabData({ upgradeChartTabStore }: Dependencies, tabId: TabId) {
|
||||
upgradeChartTabStore.clearData(tabId);
|
||||
}
|
||||
|
||||
const clearUpgradeChartTabDataInjectable = getInjectable({
|
||||
instantiate: (di) => bind(clearUpgradeChartTabData, null, {
|
||||
upgradeChartTabStore: di.inject(upgradeChartTabStoreInjectable),
|
||||
}),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default clearUpgradeChartTabDataInjectable;
|
||||
|
||||
@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import upgradeChartTabStoreInjectable from "./store.injectable";
|
||||
import dockStoreInjectable from "../dock/store.injectable";
|
||||
import type { HelmRelease } from "../../../../common/k8s-api/endpoints/helm-releases.api";
|
||||
import { DockStore, DockTabCreateSpecific, TabId, TabKind } from "../dock/store";
|
||||
import type { UpgradeChartTabStore } from "./store";
|
||||
import { bind } from "../../../utils";
|
||||
import { runInAction } from "mobx";
|
||||
|
||||
interface Dependencies {
|
||||
upgradeChartStore: UpgradeChartTabStore;
|
||||
dockStore: DockStore
|
||||
}
|
||||
|
||||
function createUpgradeChartTab({ upgradeChartStore, dockStore }: Dependencies, release: HelmRelease, tabParams: DockTabCreateSpecific = {}): TabId {
|
||||
const tabId = upgradeChartStore.getTabIdByRelease(release.getName());
|
||||
|
||||
if (tabId) {
|
||||
dockStore.open();
|
||||
dockStore.selectTab(tabId);
|
||||
|
||||
return tabId;
|
||||
}
|
||||
|
||||
return runInAction(() => {
|
||||
const 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.id;
|
||||
});
|
||||
}
|
||||
|
||||
const createUpgradeChartTabInjectable = getInjectable({
|
||||
instantiate: (di) => bind(createUpgradeChartTab, null, {
|
||||
upgradeChartStore: di.inject(upgradeChartTabStoreInjectable),
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
}),
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default createUpgradeChartTabInjectable;
|
||||
@ -3,27 +3,21 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { UpgradeChartStore } from "./upgrade-chart.store";
|
||||
import dockStoreInjectable from "../dock-store/dock-store.injectable";
|
||||
import { UpgradeChartTabStore } from "./store";
|
||||
import createDockTabStoreInjectable from "../dock-tab-store/create-dock-tab-store.injectable";
|
||||
import createStorageInjectable from "../../../utils/create-storage/create-storage.injectable";
|
||||
import releasesInjectable from "../../+apps-releases/releases.injectable";
|
||||
|
||||
const upgradeChartStoreInjectable = getInjectable({
|
||||
const upgradeChartTabStoreInjectable = getInjectable({
|
||||
instantiate: (di) => {
|
||||
const createDockTabStore = di.inject(createDockTabStoreInjectable);
|
||||
|
||||
const valuesStore = createDockTabStore<string>();
|
||||
|
||||
return new UpgradeChartStore({
|
||||
releases: di.inject(releasesInjectable),
|
||||
dockStore: di.inject(dockStoreInjectable),
|
||||
return new UpgradeChartTabStore({
|
||||
createStorage: di.inject(createStorageInjectable),
|
||||
valuesStore,
|
||||
valuesStore: createDockTabStore<string>(),
|
||||
});
|
||||
},
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default upgradeChartStoreInjectable;
|
||||
export default upgradeChartTabStoreInjectable;
|
||||
51
src/renderer/components/dock/upgrade-chart/store.ts
Normal file
51
src/renderer/components/dock/upgrade-chart/store.ts
Normal file
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { action, computed, makeObservable } from "mobx";
|
||||
import type { TabId } from "../dock/store";
|
||||
import { DockTabStorageState, DockTabStore } from "../dock-tab-store/dock-tab.store";
|
||||
import { getReleaseValues } from "../../../../common/k8s-api/endpoints/helm-releases.api";
|
||||
import type { StorageHelper } from "../../../utils";
|
||||
|
||||
export interface IChartUpgradeData {
|
||||
releaseName: string;
|
||||
releaseNamespace: string;
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
valuesStore: DockTabStore<string>;
|
||||
createStorage: <T>(storageKey: string, options: DockTabStorageState<T>) => StorageHelper<DockTabStorageState<T>>;
|
||||
}
|
||||
|
||||
export class UpgradeChartTabStore extends DockTabStore<IChartUpgradeData> {
|
||||
@computed private get releaseNameReverseLookup(): Map<string, string> {
|
||||
return new Map(this.getAllData().map(([id, { releaseName }]) => [releaseName, id]));
|
||||
}
|
||||
|
||||
get values() {
|
||||
return this.dependencies.valuesStore;
|
||||
}
|
||||
|
||||
constructor(protected dependencies : Dependencies) {
|
||||
super(dependencies, {
|
||||
storageKey: "chart_releases",
|
||||
});
|
||||
|
||||
makeObservable(this);
|
||||
}
|
||||
|
||||
@action
|
||||
async reloadValues(tabId: TabId) {
|
||||
this.values.clearData(tabId); // reset
|
||||
const { releaseName, releaseNamespace } = this.getData(tabId);
|
||||
const values = await getReleaseValues(releaseName, releaseNamespace, true);
|
||||
|
||||
this.values.setData(tabId, values);
|
||||
}
|
||||
|
||||
getTabIdByRelease(releaseName: string): TabId {
|
||||
return this.releaseNameReverseLookup.get(releaseName);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user