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 (#4757)
Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Co-authored-by: Sebastian Malton <sebastian@malton.name> Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
parent
65669f6a64
commit
0ce4e3d793
@ -354,8 +354,7 @@ utils.describeIf(minikubeReady(TEST_NAMESPACE))("Minikube based tests", () => {
|
|||||||
}
|
}
|
||||||
}, 10*60*1000);
|
}, 10*60*1000);
|
||||||
|
|
||||||
// TODO: Make re-rendering of KubeObjectListLayout not cause namespaceSelector to be closed
|
it("show logs and highlight the log search entries", async () => {
|
||||||
xit("show logs and highlight the log search entries", async () => {
|
|
||||||
await frame.click(`a[href="/workloads"]`);
|
await frame.click(`a[href="/workloads"]`);
|
||||||
await frame.click(`a[href="/pods"]`);
|
await frame.click(`a[href="/pods"]`);
|
||||||
|
|
||||||
@ -400,8 +399,7 @@ utils.describeIf(minikubeReady(TEST_NAMESPACE))("Minikube based tests", () => {
|
|||||||
await frame.waitForSelector("div.TableCell >> text='kube-system'");
|
await frame.waitForSelector("div.TableCell >> text='kube-system'");
|
||||||
}, 10*60*1000);
|
}, 10*60*1000);
|
||||||
|
|
||||||
// TODO: Make re-rendering of KubeObjectListLayout not cause namespaceSelector to be closed
|
it(`should create the ${TEST_NAMESPACE} and a pod in the namespace`, async () => {
|
||||||
xit(`should create the ${TEST_NAMESPACE} and a pod in the namespace`, async () => {
|
|
||||||
await frame.click('a[href="/namespaces"]');
|
await frame.click('a[href="/namespaces"]');
|
||||||
await frame.click("button.add-button");
|
await frame.click("button.add-button");
|
||||||
await frame.waitForSelector("div.AddNamespaceDialog >> text='Create Namespace'");
|
await frame.waitForSelector("div.AddNamespaceDialog >> text='Create Namespace'");
|
||||||
|
|||||||
@ -349,6 +349,7 @@
|
|||||||
"fork-ts-checker-webpack-plugin": "^5.2.1",
|
"fork-ts-checker-webpack-plugin": "^5.2.1",
|
||||||
"hoist-non-react-statics": "^3.3.2",
|
"hoist-non-react-statics": "^3.3.2",
|
||||||
"html-webpack-plugin": "^4.5.2",
|
"html-webpack-plugin": "^4.5.2",
|
||||||
|
"ignore-loader": "^0.1.2",
|
||||||
"include-media": "^1.4.9",
|
"include-media": "^1.4.9",
|
||||||
"jest": "26.6.3",
|
"jest": "26.6.3",
|
||||||
"jest-canvas-mock": "^2.3.1",
|
"jest-canvas-mock": "^2.3.1",
|
||||||
|
|||||||
@ -3,11 +3,11 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import logTabStoreInjectable from "./tab-store.injectable";
|
import fsInjectable from "./fs.injectable";
|
||||||
|
|
||||||
const updateTabNameInjectable = getInjectable({
|
const readDirInjectable = getInjectable({
|
||||||
instantiate: (di) => di.inject(logTabStoreInjectable).updateTabName,
|
instantiate: (di) => di.inject(fsInjectable).readdir,
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default updateTabNameInjectable;
|
export default readDirInjectable;
|
||||||
@ -3,12 +3,11 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import { writeJsonFile } from "./write-json-file";
|
import fsInjectable from "./fs.injectable";
|
||||||
import fsInjectable from "../fs.injectable";
|
|
||||||
|
|
||||||
const writeJsonFileInjectable = getInjectable({
|
const readFileInjectable = getInjectable({
|
||||||
instantiate: (di) => writeJsonFile({ fs: di.inject(fsInjectable) }),
|
instantiate: (di) => di.inject(fsInjectable).readFile,
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default writeJsonFileInjectable;
|
export default readFileInjectable;
|
||||||
@ -2,15 +2,11 @@
|
|||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { readJsonFile } from "./read-json-file";
|
|
||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import fsInjectable from "../fs.injectable";
|
import fsInjectable from "./fs.injectable";
|
||||||
|
|
||||||
const readJsonFileInjectable = getInjectable({
|
const readJsonFileInjectable = getInjectable({
|
||||||
instantiate: (di) => readJsonFile({
|
instantiate: (di) => di.inject(fsInjectable).readJson,
|
||||||
fs: di.inject(fsInjectable),
|
|
||||||
}),
|
|
||||||
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import type { JsonObject } from "type-fest";
|
|
||||||
|
|
||||||
interface Dependencies {
|
|
||||||
fs: {
|
|
||||||
readJson: (filePath: string) => Promise<JsonObject>;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const readJsonFile =
|
|
||||||
({ fs }: Dependencies) =>
|
|
||||||
(filePath: string) =>
|
|
||||||
fs.readJson(filePath);
|
|
||||||
38
src/common/fs/write-json-file.injectable.ts
Normal file
38
src/common/fs/write-json-file.injectable.ts
Normal file
@ -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 { EnsureOptions, WriteOptions } from "fs-extra";
|
||||||
|
import path from "path";
|
||||||
|
import type { JsonValue } from "type-fest";
|
||||||
|
import fsInjectable from "./fs.injectable";
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
writeJson: (file: string, object: any, options?: WriteOptions | BufferEncoding | string) => Promise<void>;
|
||||||
|
ensureDir: (dir: string, options?: EnsureOptions | number) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const writeJsonFile = ({ writeJson, ensureDir }: Dependencies) => async (filePath: string, content: JsonValue) => {
|
||||||
|
await ensureDir(path.dirname(filePath), { mode: 0o755 });
|
||||||
|
|
||||||
|
await writeJson(filePath, content, {
|
||||||
|
encoding: "utf-8",
|
||||||
|
spaces: 2,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const writeJsonFileInjectable = getInjectable({
|
||||||
|
instantiate: (di) => {
|
||||||
|
const { writeJson, ensureDir } = di.inject(fsInjectable);
|
||||||
|
|
||||||
|
return writeJsonFile({
|
||||||
|
writeJson,
|
||||||
|
ensureDir,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default writeJsonFileInjectable;
|
||||||
@ -1,31 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import path from "path";
|
|
||||||
import type { JsonObject } from "type-fest";
|
|
||||||
|
|
||||||
interface Dependencies {
|
|
||||||
fs: {
|
|
||||||
ensureDir: (
|
|
||||||
directoryName: string,
|
|
||||||
options: { mode: number }
|
|
||||||
) => Promise<void>;
|
|
||||||
|
|
||||||
writeJson: (
|
|
||||||
filePath: string,
|
|
||||||
contentObject: JsonObject,
|
|
||||||
options: { spaces: number }
|
|
||||||
) => Promise<void>;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const writeJsonFile =
|
|
||||||
({ fs }: Dependencies) =>
|
|
||||||
async (filePath: string, contentObject: JsonObject) => {
|
|
||||||
const directoryName = path.dirname(filePath);
|
|
||||||
|
|
||||||
await fs.ensureDir(directoryName, { mode: 0o755 });
|
|
||||||
|
|
||||||
await fs.writeJson(filePath, contentObject, { spaces: 2 });
|
|
||||||
};
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { bind } from "../index";
|
|
||||||
|
|
||||||
describe("bind", () => {
|
|
||||||
it("should work correctly", () => {
|
|
||||||
function foobar(bound: number, nonBound: number): number {
|
|
||||||
expect(typeof bound).toBe("number");
|
|
||||||
expect(typeof nonBound).toBe("number");
|
|
||||||
|
|
||||||
return bound + nonBound;
|
|
||||||
}
|
|
||||||
const foobarBound = bind(foobar, null, 5);
|
|
||||||
|
|
||||||
expect(foobarBound(10)).toBe(15);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -10,13 +10,6 @@ export function noop<T extends any[]>(...args: T): void {
|
|||||||
return void args;
|
return void args;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A typecorrect version of <function>.bind()
|
|
||||||
*/
|
|
||||||
export function bind<BoundArgs extends any[], NonBoundArgs extends any[], ReturnType>(fn: (...args: [...BoundArgs, ...NonBoundArgs]) => ReturnType, thisArg: any, ...boundArgs: BoundArgs): (...args: NonBoundArgs) => ReturnType {
|
|
||||||
return fn.bind(thisArg, ...boundArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
export * from "./app-version";
|
export * from "./app-version";
|
||||||
export * from "./autobind";
|
export * from "./autobind";
|
||||||
export * from "./camelCase";
|
export * from "./camelCase";
|
||||||
@ -49,6 +42,7 @@ export * from "./toggle-set";
|
|||||||
export * from "./toJS";
|
export * from "./toJS";
|
||||||
export * from "./type-narrowing";
|
export * from "./type-narrowing";
|
||||||
export * from "./types";
|
export * from "./types";
|
||||||
|
export * from "./wait-for-path";
|
||||||
|
|
||||||
import * as iter from "./iter";
|
import * as iter from "./iter";
|
||||||
import * as array from "./array";
|
import * as array from "./array";
|
||||||
|
|||||||
55
src/common/utils/wait-for-path.ts
Normal file
55
src/common/utils/wait-for-path.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { FSWatcher } from "chokidar";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for `filePath` and all parent directories to exist.
|
||||||
|
* @param pathname The file path to wait until it exists
|
||||||
|
*
|
||||||
|
* NOTE: There is technically a race condition in this function of the form
|
||||||
|
* "time-of-check to time-of-use" because we have to wait for each parent
|
||||||
|
* directory to exist first.
|
||||||
|
*/
|
||||||
|
export async function waitForPath(pathname: string): Promise<void> {
|
||||||
|
const dirOfPath = path.dirname(pathname);
|
||||||
|
|
||||||
|
if (dirOfPath === pathname) {
|
||||||
|
// The root of this filesystem, assume it exists
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
await waitForPath(dirOfPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const watcher = new FSWatcher({
|
||||||
|
depth: 0,
|
||||||
|
disableGlobbing: true,
|
||||||
|
});
|
||||||
|
const onAddOrAddDir = (filePath: string) => {
|
||||||
|
if (filePath === pathname) {
|
||||||
|
watcher.unwatch(dirOfPath);
|
||||||
|
watcher
|
||||||
|
.close()
|
||||||
|
.then(() => resolve())
|
||||||
|
.catch(reject);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const onError = (error: any) => {
|
||||||
|
watcher.unwatch(dirOfPath);
|
||||||
|
watcher
|
||||||
|
.close()
|
||||||
|
.then(() => reject(error))
|
||||||
|
.catch(() => reject(error));
|
||||||
|
};
|
||||||
|
|
||||||
|
watcher
|
||||||
|
.on("add", onAddOrAddDir)
|
||||||
|
.on("addDir", onAddOrAddDir)
|
||||||
|
.on("error", onError)
|
||||||
|
.add(dirOfPath);
|
||||||
|
});
|
||||||
|
}
|
||||||
13
src/common/vars/is-linux.injectable.ts
Normal file
13
src/common/vars/is-linux.injectable.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* 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 { isLinux } from "../vars";
|
||||||
|
|
||||||
|
const isLinuxInjectable = getInjectable({
|
||||||
|
instantiate: () => isLinux,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default isLinuxInjectable;
|
||||||
13
src/common/vars/is-windows.injectable.ts
Normal file
13
src/common/vars/is-windows.injectable.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* 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 { isWindows } from "../vars";
|
||||||
|
|
||||||
|
const isWindowsInjectable = getInjectable({
|
||||||
|
instantiate: () => isWindows,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default isWindowsInjectable;
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* 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?];
|
||||||
|
|
||||||
|
type MapInjectables<T> = {
|
||||||
|
[Key in keyof T]: T[Key] extends () => infer Res ? Res : never;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const asLegacyGlobalObjectForExtensionApiWithModifications = <
|
||||||
|
TInjectable extends Injectable<unknown, unknown, TInstantiationParameter>,
|
||||||
|
TInstantiationParameter,
|
||||||
|
OtherFields extends Record<string, () => any>,
|
||||||
|
>(
|
||||||
|
injectableKey: TInjectable,
|
||||||
|
otherFields: OtherFields,
|
||||||
|
...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 as any]();
|
||||||
|
|
||||||
|
if (typeof propertyValue === "function") {
|
||||||
|
return function (...args: any[]) {
|
||||||
|
return propertyValue.apply(instance, args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return propertyValue;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
) as ReturnType<TInjectable["instantiate"]> & MapInjectables<OtherFields>;
|
||||||
@ -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,18 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { asLegacyGlobalFunctionForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-function-for-extension-api";
|
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 createTerminalTabInjectable from "../../renderer/components/dock/terminal/create-terminal-tab.injectable";
|
||||||
import terminalStoreInjectable from "../../renderer/components/dock/terminal-store/terminal-store.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 { 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 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 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 sendCommandInjectable from "../../renderer/components/dock/terminal/send-command.injectable";
|
||||||
|
import { podsStore } from "../../renderer/components/+workloads-pods/pods.store";
|
||||||
|
import renameTabInjectable from "../../renderer/components/dock/dock/rename-tab.injectable";
|
||||||
|
|
||||||
// layouts
|
// layouts
|
||||||
export * from "../../renderer/components/layout/main-layout";
|
export * from "../../renderer/components/layout/main-layout";
|
||||||
@ -71,7 +75,30 @@ export * from "../../renderer/components/+events/kube-event-details";
|
|||||||
export * from "../../renderer/components/status-brick";
|
export * from "../../renderer/components/status-brick";
|
||||||
|
|
||||||
export const createTerminalTab = asLegacyGlobalFunctionForExtensionApi(createTerminalTabInjectable);
|
export const createTerminalTab = asLegacyGlobalFunctionForExtensionApi(createTerminalTabInjectable);
|
||||||
export const TerminalStore = asLegacyGlobalSingletonForExtensionApi(TerminalStoreClass, terminalStoreInjectable);
|
export const terminalStore = asLegacyGlobalObjectForExtensionApiWithModifications(terminalStoreInjectable, {
|
||||||
export const terminalStore = asLegacyGlobalObjectForExtensionApi(terminalStoreInjectable);
|
sendCommand: () => asLegacyGlobalFunctionForExtensionApi(sendCommandInjectable),
|
||||||
export const logTabStore = asLegacyGlobalObjectForExtensionApi(logTabStoreInjectable);
|
});
|
||||||
|
export const logTabStore = asLegacyGlobalObjectForExtensionApiWithModifications(logTabStoreInjectable, {
|
||||||
|
createPodTab: () => asLegacyGlobalFunctionForExtensionApi(createPodLogsTabInjectable),
|
||||||
|
createWorkloadTab: () => asLegacyGlobalFunctionForExtensionApi(createWorkloadLogsTabInjectable),
|
||||||
|
renameTab: () => (tabId: string): void => {
|
||||||
|
const renameTab = asLegacyGlobalFunctionForExtensionApi(renameTabInjectable);
|
||||||
|
const tabData = logTabStore.getData(tabId);
|
||||||
|
const pod = podsStore.getById(tabData.selectedPodId);
|
||||||
|
|
||||||
|
renameTab(tabId, `Pod ${pod.getName()}`);
|
||||||
|
},
|
||||||
|
tabs: () => undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
export class TerminalStore {
|
||||||
|
static getInstance() {
|
||||||
|
return terminalStore;
|
||||||
|
}
|
||||||
|
static createInstance() {
|
||||||
|
return terminalStore;
|
||||||
|
}
|
||||||
|
static resetInstance() {
|
||||||
|
console.warn("TerminalStore.resetInstance() does nothing");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -12,8 +12,8 @@ import getElectronAppPathInjectable from "./app-paths/get-electron-app-path/get-
|
|||||||
import setElectronAppPathInjectable from "./app-paths/set-electron-app-path/set-electron-app-path.injectable";
|
import setElectronAppPathInjectable from "./app-paths/set-electron-app-path/set-electron-app-path.injectable";
|
||||||
import appNameInjectable from "./app-paths/app-name/app-name.injectable";
|
import appNameInjectable from "./app-paths/app-name/app-name.injectable";
|
||||||
import registerChannelInjectable from "./app-paths/register-channel/register-channel.injectable";
|
import registerChannelInjectable from "./app-paths/register-channel/register-channel.injectable";
|
||||||
import writeJsonFileInjectable from "../common/fs/write-json-file/write-json-file.injectable";
|
import writeJsonFileInjectable from "../common/fs/write-json-file.injectable";
|
||||||
import readJsonFileInjectable from "../common/fs/read-json-file/read-json-file.injectable";
|
import readJsonFileInjectable from "../common/fs/read-json-file.injectable";
|
||||||
|
|
||||||
export const getDiForUnitTesting = (
|
export const getDiForUnitTesting = (
|
||||||
{ doGeneralOverrides } = { doGeneralOverrides: false },
|
{ doGeneralOverrides } = { doGeneralOverrides: false },
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import { Select, SelectOption } from "../select";
|
|||||||
import { Badge } from "../badge";
|
import { Badge } from "../badge";
|
||||||
import { Tooltip, withStyles } from "@material-ui/core";
|
import { Tooltip, withStyles } from "@material-ui/core";
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
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 {
|
interface Props {
|
||||||
chart: HelmChart;
|
chart: HelmChart;
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import { helmChartStore } from "./helm-chart.store";
|
|||||||
import type { HelmChart } from "../../../common/k8s-api/endpoints/helm-charts.api";
|
import type { HelmChart } from "../../../common/k8s-api/endpoints/helm-charts.api";
|
||||||
import { HelmChartDetails } from "./helm-chart-details";
|
import { HelmChartDetails } from "./helm-chart-details";
|
||||||
import { navigation } from "../../navigation";
|
import { navigation } from "../../navigation";
|
||||||
import { ItemListLayout } from "../item-object-list/item-list-layout";
|
import { ItemListLayout } from "../item-object-list/list-layout";
|
||||||
import { helmChartsURL } from "../../../common/routes";
|
import { helmChartsURL } from "../../../common/routes";
|
||||||
import type { HelmChartsRouteParams } from "../../../common/routes";
|
import type { HelmChartsRouteParams } from "../../../common/routes";
|
||||||
|
|
||||||
|
|||||||
@ -27,7 +27,7 @@ import { getDetailsUrl } from "../../kube-detail-params";
|
|||||||
import { Checkbox } from "../../checkbox";
|
import { Checkbox } from "../../checkbox";
|
||||||
import { MonacoEditor } from "../../monaco-editor";
|
import { MonacoEditor } from "../../monaco-editor";
|
||||||
import { IAsyncComputed, withInjectables } from "@ogre-tools/injectable-react";
|
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 updateReleaseInjectable from "../update-release/update-release.injectable";
|
||||||
import releaseInjectable from "./release.injectable";
|
import releaseInjectable from "./release.injectable";
|
||||||
import releaseDetailsInjectable from "./release-details.injectable";
|
import releaseDetailsInjectable from "./release-details.injectable";
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import { MenuActions, MenuActionsProps } from "../menu/menu-actions";
|
|||||||
import { MenuItem } from "../menu";
|
import { MenuItem } from "../menu";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
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 releaseRollbackDialogModelInjectable from "./release-rollback-dialog-model/release-rollback-dialog-model.injectable";
|
||||||
import deleteReleaseInjectable from "./delete-release/delete-release.injectable";
|
import deleteReleaseInjectable from "./delete-release/delete-release.injectable";
|
||||||
|
|
||||||
|
|||||||
@ -23,10 +23,8 @@ import { ReleaseRollbackDialog } from "./release-rollback-dialog";
|
|||||||
import { ReleaseDetails } from "./release-details/release-details";
|
import { ReleaseDetails } from "./release-details/release-details";
|
||||||
import removableReleasesInjectable from "./removable-releases.injectable";
|
import removableReleasesInjectable from "./removable-releases.injectable";
|
||||||
import type { RemovableHelmRelease } from "./removable-releases";
|
import type { RemovableHelmRelease } from "./removable-releases";
|
||||||
import { observer } from "mobx-react";
|
|
||||||
import type { IComputedValue } from "mobx";
|
import type { IComputedValue } from "mobx";
|
||||||
import releasesInjectable from "./releases.injectable";
|
import releasesInjectable from "./releases.injectable";
|
||||||
import { Spinner } from "../spinner";
|
|
||||||
|
|
||||||
enum columnId {
|
enum columnId {
|
||||||
name = "name",
|
name = "name",
|
||||||
@ -48,7 +46,6 @@ interface Dependencies {
|
|||||||
selectNamespace: (namespace: string) => void
|
selectNamespace: (namespace: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
|
||||||
class NonInjectedHelmReleases extends Component<Dependencies & Props> {
|
class NonInjectedHelmReleases extends Component<Dependencies & Props> {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { match: { params: { namespace }}} = this.props;
|
const { match: { params: { namespace }}} = this.props;
|
||||||
@ -89,12 +86,8 @@ class NonInjectedHelmReleases extends Component<Dependencies & Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.props.releasesArePending.get()) {
|
|
||||||
// TODO: Make Spinner "center" work properly
|
|
||||||
return <div className="flex center" style={{ height: "100%" }}><Spinner /></div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const releases = this.props.releases;
|
const releases = this.props.releases;
|
||||||
|
const releasesArePending = this.props.releasesArePending;
|
||||||
|
|
||||||
// TODO: Implement ItemListLayout without stateful stores
|
// TODO: Implement ItemListLayout without stateful stores
|
||||||
const legacyReleaseStore = {
|
const legacyReleaseStore = {
|
||||||
@ -103,7 +96,11 @@ class NonInjectedHelmReleases extends Component<Dependencies & Props> {
|
|||||||
},
|
},
|
||||||
|
|
||||||
loadAll: () => Promise.resolve(),
|
loadAll: () => Promise.resolve(),
|
||||||
isLoaded: true,
|
|
||||||
|
get isLoaded() {
|
||||||
|
return !releasesArePending.get();
|
||||||
|
},
|
||||||
|
|
||||||
failedLoading: false,
|
failedLoading: false,
|
||||||
|
|
||||||
getTotalCount: () => releases.get().length,
|
getTotalCount: () => releases.get().length,
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
|||||||
import { orderBy } from "lodash";
|
import { orderBy } from "lodash";
|
||||||
import type { IComputedValue } from "mobx";
|
import type { IComputedValue } from "mobx";
|
||||||
import type { CatalogCategory, CatalogEntity } from "../../../common/catalog";
|
import type { CatalogCategory, CatalogEntity } from "../../../common/catalog";
|
||||||
import { bind } from "../../utils";
|
|
||||||
import type { ItemListLayoutProps } from "../item-object-list";
|
import type { ItemListLayoutProps } from "../item-object-list";
|
||||||
import type { RegisteredAdditionalCategoryColumn } from "./custom-category-columns";
|
import type { RegisteredAdditionalCategoryColumn } from "./custom-category-columns";
|
||||||
import categoryColumnsInjectable from "./custom-category-columns.injectable";
|
import categoryColumnsInjectable from "./custom-category-columns.injectable";
|
||||||
@ -50,7 +49,7 @@ function getBrowseAllColumns(): RegisteredAdditionalCategoryColumn[] {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCategoryColumns({ extensionColumns }: Dependencies, { activeCategory }: GetCategoryColumnsParams): CategoryColumns {
|
const getCategoryColumns = ({ extensionColumns }: Dependencies) => ({ activeCategory }: GetCategoryColumnsParams): CategoryColumns => {
|
||||||
const allRegistrations = orderBy(
|
const allRegistrations = orderBy(
|
||||||
activeCategory
|
activeCategory
|
||||||
? getSpecificCategoryColumns(activeCategory, extensionColumns)
|
? getSpecificCategoryColumns(activeCategory, extensionColumns)
|
||||||
@ -83,12 +82,13 @@ function getCategoryColumns({ extensionColumns }: Dependencies, { activeCategory
|
|||||||
renderTableContents: entity => tableRowRenderers.map(fn => fn(entity)),
|
renderTableContents: entity => tableRowRenderers.map(fn => fn(entity)),
|
||||||
searchFilters,
|
searchFilters,
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const getCategoryColumnsInjectable = getInjectable({
|
const getCategoryColumnsInjectable = getInjectable({
|
||||||
instantiate: (di) => bind(getCategoryColumns, null, {
|
instantiate: (di) => getCategoryColumns({
|
||||||
extensionColumns: di.inject(categoryColumnsInjectable),
|
extensionColumns: di.inject(categoryColumnsInjectable),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -7,14 +7,13 @@ import { requestOpenFilePickingDialog } from "../../ipc";
|
|||||||
import { supportedExtensionFormats } from "./supported-extension-formats";
|
import { supportedExtensionFormats } from "./supported-extension-formats";
|
||||||
import attemptInstallsInjectable from "./attempt-installs/attempt-installs.injectable";
|
import attemptInstallsInjectable from "./attempt-installs/attempt-installs.injectable";
|
||||||
import directoryForDownloadsInjectable from "../../../common/app-paths/directory-for-downloads/directory-for-downloads.injectable";
|
import directoryForDownloadsInjectable from "../../../common/app-paths/directory-for-downloads/directory-for-downloads.injectable";
|
||||||
import { bind } from "../../utils";
|
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
attemptInstalls: (filePaths: string[]) => Promise<void>
|
attemptInstalls: (filePaths: string[]) => Promise<void>
|
||||||
directoryForDownloads: string
|
directoryForDownloads: string
|
||||||
}
|
}
|
||||||
|
|
||||||
async function installFromSelectFileDialog({ attemptInstalls, directoryForDownloads }: Dependencies) {
|
const installFromSelectFileDialog = ({ attemptInstalls, directoryForDownloads }: Dependencies) => async () => {
|
||||||
const { canceled, filePaths } = await requestOpenFilePickingDialog({
|
const { canceled, filePaths } = await requestOpenFilePickingDialog({
|
||||||
defaultPath: directoryForDownloads,
|
defaultPath: directoryForDownloads,
|
||||||
properties: ["openFile", "multiSelections"],
|
properties: ["openFile", "multiSelections"],
|
||||||
@ -26,13 +25,14 @@ async function installFromSelectFileDialog({ attemptInstalls, directoryForDownlo
|
|||||||
if (!canceled) {
|
if (!canceled) {
|
||||||
await attemptInstalls(filePaths);
|
await attemptInstalls(filePaths);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const installFromSelectFileDialogInjectable = getInjectable({
|
const installFromSelectFileDialogInjectable = getInjectable({
|
||||||
instantiate: (di) => bind(installFromSelectFileDialog, null, {
|
instantiate: (di) => installFromSelectFileDialog({
|
||||||
attemptInstalls: di.inject(attemptInstallsInjectable),
|
attemptInstalls: di.inject(attemptInstallsInjectable),
|
||||||
directoryForDownloads: di.inject(directoryForDownloadsInjectable),
|
directoryForDownloads: di.inject(directoryForDownloadsInjectable),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import "./port-forwards.scss";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { disposeOnUnmount, observer } from "mobx-react";
|
import { disposeOnUnmount, observer } from "mobx-react";
|
||||||
import type { RouteComponentProps } from "react-router-dom";
|
import type { RouteComponentProps } from "react-router-dom";
|
||||||
import { ItemListLayout } from "../item-object-list/item-list-layout";
|
import { ItemListLayout } from "../item-object-list/list-layout";
|
||||||
import type { PortForwardItem, PortForwardStore } from "../../port-forward";
|
import type { PortForwardItem, PortForwardStore } from "../../port-forward";
|
||||||
import { PortForwardMenu } from "./port-forward-menu";
|
import { PortForwardMenu } from "./port-forward-menu";
|
||||||
import { PortForwardsRouteParams, portForwardsURL } from "../../../common/routes";
|
import { PortForwardsRouteParams, portForwardsURL } from "../../../common/routes";
|
||||||
|
|||||||
@ -14,9 +14,8 @@ import { ActivateEntityCommand } from "../../activate-entity-command";
|
|||||||
import type { CommandContext, CommandRegistration } from "./commands";
|
import type { CommandContext, CommandRegistration } from "./commands";
|
||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import commandOverlayInjectable from "../command-overlay.injectable";
|
import commandOverlayInjectable from "../command-overlay.injectable";
|
||||||
import createTerminalTabInjectable
|
import createTerminalTabInjectable from "../../dock/terminal/create-terminal-tab.injectable";
|
||||||
from "../../dock/create-terminal-tab/create-terminal-tab.injectable";
|
import type { DockTabCreate } from "../../dock/dock/store";
|
||||||
import type { DockTabCreate } from "../../dock/dock-store/dock.store";
|
|
||||||
|
|
||||||
export function isKubernetesClusterActive(context: CommandContext): boolean {
|
export function isKubernetesClusterActive(context: CommandContext): boolean {
|
||||||
return context.entity?.kind === "KubernetesCluster";
|
return context.entity?.kind === "KubernetesCluster";
|
||||||
|
|||||||
@ -8,12 +8,12 @@ import { fireEvent } from "@testing-library/react";
|
|||||||
import "@testing-library/jest-dom/extend-expect";
|
import "@testing-library/jest-dom/extend-expect";
|
||||||
import fse from "fs-extra";
|
import fse from "fs-extra";
|
||||||
import { DockTabs } from "../dock-tabs";
|
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 { noop } from "../../../utils";
|
||||||
import { ThemeStore } from "../../../theme.store";
|
import { ThemeStore } from "../../../theme.store";
|
||||||
import { UserStore } from "../../../../common/user-store";
|
import { UserStore } from "../../../../common/user-store";
|
||||||
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
|
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 type { DiRender } from "../../test-utils/renderFor";
|
||||||
import { renderFor } from "../../test-utils/renderFor";
|
import { renderFor } from "../../test-utils/renderFor";
|
||||||
import directoryForUserDataInjectable
|
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,70 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import fs from "fs-extra";
|
|
||||||
import path from "path";
|
|
||||||
import os from "os";
|
|
||||||
import groupBy from "lodash/groupBy";
|
|
||||||
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>>
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CreateResourceStore extends DockTabStore<string> {
|
|
||||||
constructor(protected dependencies: Dependencies) {
|
|
||||||
super(dependencies, {
|
|
||||||
storageKey: "create_resource",
|
|
||||||
});
|
|
||||||
|
|
||||||
autoBind(this);
|
|
||||||
fs.ensureDirSync(this.userTemplatesFolder);
|
|
||||||
}
|
|
||||||
|
|
||||||
get lensTemplatesFolder():string {
|
|
||||||
return path.resolve(__static, "../templates/create-resource");
|
|
||||||
}
|
|
||||||
|
|
||||||
get userTemplatesFolder():string {
|
|
||||||
return path.join(os.homedir(), ".k8slens", "templates");
|
|
||||||
}
|
|
||||||
|
|
||||||
async getTemplates(templatesPath: string, defaultGroup: string) {
|
|
||||||
const templates = await filehound.create().path(templatesPath).ext(["yaml", "json"]).depth(1).find();
|
|
||||||
|
|
||||||
return templates ? this.groupTemplates(templates, templatesPath, defaultGroup) : {};
|
|
||||||
}
|
|
||||||
|
|
||||||
groupTemplates(templates: string[], templatesPath: string, defaultGroup: string) {
|
|
||||||
return groupBy(templates, (v:string) =>
|
|
||||||
path.relative(templatesPath, v).split(path.sep).length>1
|
|
||||||
? path.parse(path.relative(templatesPath, v)).dir
|
|
||||||
: defaultGroup);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getMergedTemplates() {
|
|
||||||
const userTemplates = await this.getTemplates(this.userTemplatesFolder, "ungrouped");
|
|
||||||
const lensTemplates = await this.getTemplates(this.lensTemplatesFolder, "lens");
|
|
||||||
|
|
||||||
return { ...userTemplates, ...lensTemplates };
|
|
||||||
}
|
|
||||||
|
|
||||||
async watchUserTemplates(callback: ()=> void){
|
|
||||||
watch(this.userTemplatesFolder, {
|
|
||||||
depth: 1,
|
|
||||||
ignoreInitial: true,
|
|
||||||
awaitWriteFinish: {
|
|
||||||
stabilityThreshold: 500,
|
|
||||||
},
|
|
||||||
}).on("all", () => {
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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 {
|
|
||||||
}
|
|
||||||
@ -1,184 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* 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 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 { withInjectables } from "@ogre-tools/injectable-react";
|
|
||||||
import createResourceStoreInjectable
|
|
||||||
from "./create-resource-store/create-resource-store.injectable";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
tab: DockTab;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Dependencies {
|
|
||||||
createResourceStore: CreateResourceStore
|
|
||||||
}
|
|
||||||
|
|
||||||
@observer
|
|
||||||
class NonInjectedCreateResource extends React.Component<Props & Dependencies> {
|
|
||||||
@observable currentTemplates: Map<string, SelectOption> = new Map();
|
|
||||||
@observable error = "";
|
|
||||||
@observable templates: GroupSelectOption<SelectOption>[] = [];
|
|
||||||
|
|
||||||
constructor(props: Props & Dependencies) {
|
|
||||||
super(props);
|
|
||||||
makeObservable(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.props.createResourceStore.getMergedTemplates().then(v => this.updateGroupSelectOptions(v));
|
|
||||||
this.props.createResourceStore.watchUserTemplates(() => this.props.createResourceStore.getMergedTemplates().then(v => this.updateGroupSelectOptions(v)));
|
|
||||||
}
|
|
||||||
|
|
||||||
updateGroupSelectOptions(templates: Record<string, string[]>) {
|
|
||||||
this.templates = Object.entries(templates)
|
|
||||||
.map(([name, grouping]) => this.convertToGroup(name, grouping));
|
|
||||||
}
|
|
||||||
|
|
||||||
convertToGroup(group: string, items: string[]): GroupSelectOption {
|
|
||||||
const options = items.map(v => ({ label: path.parse(v).name, value: v }));
|
|
||||||
|
|
||||||
return { label: group, options };
|
|
||||||
}
|
|
||||||
|
|
||||||
get tabId() {
|
|
||||||
return this.props.tab.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
get data() {
|
|
||||||
return this.props.createResourceStore.getData(this.tabId);
|
|
||||||
}
|
|
||||||
|
|
||||||
get currentTemplate() {
|
|
||||||
return this.currentTemplates.get(this.tabId) ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
onChange = (value: string) => {
|
|
||||||
this.error = ""; // reset first, validation goes later
|
|
||||||
this.props.createResourceStore.setData(this.tabId, value);
|
|
||||||
};
|
|
||||||
|
|
||||||
onError = (error: Error | string) => {
|
|
||||||
this.error = error.toString();
|
|
||||||
};
|
|
||||||
|
|
||||||
onSelectTemplate = (item: SelectOption) => {
|
|
||||||
this.currentTemplates.set(this.tabId, item);
|
|
||||||
fs.readFile(item.value, "utf8").then(v => {
|
|
||||||
this.props.createResourceStore.setData(this.tabId, v);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
create = async (): Promise<undefined> => {
|
|
||||||
if (this.error || !this.data.trim()) {
|
|
||||||
// do not save when field is empty or there is an error
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip empty documents
|
|
||||||
const resources = yaml.loadAll(this.data).filter(Boolean);
|
|
||||||
|
|
||||||
if (resources.length === 0) {
|
|
||||||
return void logger.info("Nothing to create");
|
|
||||||
}
|
|
||||||
|
|
||||||
const creatingResources = resources.map(async (resource: string) => {
|
|
||||||
try {
|
|
||||||
const data: KubeJsonApiData = await resourceApplierApi.update(resource);
|
|
||||||
const { kind, apiVersion, metadata: { name, namespace }} = data;
|
|
||||||
const resourceLink = apiManager.lookupApiLink({ kind, apiVersion, name, namespace });
|
|
||||||
|
|
||||||
const showDetails = () => {
|
|
||||||
navigate(getDetailsUrl(resourceLink));
|
|
||||||
close();
|
|
||||||
};
|
|
||||||
|
|
||||||
const close = Notifications.ok(
|
|
||||||
<p>
|
|
||||||
{kind} <a onClick={prevDefault(showDetails)}>{name}</a> successfully created.
|
|
||||||
</p>,
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
Notifications.error(error?.toString() ?? "Unknown error occured");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.allSettled(creatingResources);
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
renderControls() {
|
|
||||||
return (
|
|
||||||
<div className="flex gaps align-center">
|
|
||||||
<Select
|
|
||||||
autoConvertOptions={false}
|
|
||||||
controlShouldRenderValue={false} // always keep initial placeholder
|
|
||||||
className="TemplateSelect"
|
|
||||||
placeholder="Select Template ..."
|
|
||||||
options={this.templates}
|
|
||||||
menuPlacement="top"
|
|
||||||
themeName="outlined"
|
|
||||||
onChange={v => this.onSelectTemplate(v)}
|
|
||||||
value={this.currentTemplate}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { tabId, data, error } = this;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="CreateResource flex column">
|
|
||||||
<InfoPanel
|
|
||||||
tabId={tabId}
|
|
||||||
error={error}
|
|
||||||
controls={this.renderControls()}
|
|
||||||
submit={this.create}
|
|
||||||
submitLabel="Create"
|
|
||||||
showNotifications={false}
|
|
||||||
/>
|
|
||||||
<EditorPanel
|
|
||||||
tabId={tabId}
|
|
||||||
value={data}
|
|
||||||
onChange={this.onChange}
|
|
||||||
onError={this.onError}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CreateResource = withInjectables<Dependencies, Props>(
|
|
||||||
NonInjectedCreateResource,
|
|
||||||
|
|
||||||
{
|
|
||||||
getProps: (di, props) => ({
|
|
||||||
createResourceStore: di.inject(createResourceStoreInjectable),
|
|
||||||
...props,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import type { TabId } from "../dock/store";
|
||||||
|
import createResourceTabStoreInjectable from "./store.injectable";
|
||||||
|
|
||||||
|
const clearCreateResourceTabDataInjectable = getInjectable({
|
||||||
|
instantiate: (di) => {
|
||||||
|
const createResourceTabStore = di.inject(createResourceTabStoreInjectable);
|
||||||
|
|
||||||
|
return (tabId: TabId): void => {
|
||||||
|
createResourceTabStore.clearData(tabId);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default clearCreateResourceTabDataInjectable;
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import dockStoreInjectable from "../dock/store.injectable";
|
||||||
|
import { DockTabCreateSpecific, TabKind } from "../dock/store";
|
||||||
|
|
||||||
|
const createResourceTabInjectable = getInjectable({
|
||||||
|
instantiate: (di) => {
|
||||||
|
const dockStore = di.inject(dockStoreInjectable);
|
||||||
|
|
||||||
|
return (tabParams: DockTabCreateSpecific = {}) =>
|
||||||
|
dockStore.createTab({
|
||||||
|
title: "Create resource",
|
||||||
|
...tabParams,
|
||||||
|
kind: TabKind.CREATE_RESOURCE,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default createResourceTabInjectable;
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import { computed } from "mobx";
|
||||||
|
import type { GroupSelectOption, SelectOption } from "../../select";
|
||||||
|
import userCreateResourceTemplatesInjectable from "./user-templates.injectable";
|
||||||
|
import lensCreateResourceTemplatesInjectable from "./lens-templates.injectable";
|
||||||
|
|
||||||
|
export type RawTemplates = [group: string, items: [file: string, contents: string][]];
|
||||||
|
|
||||||
|
const createResourceTemplatesInjectable = getInjectable({
|
||||||
|
instantiate: async (di) => {
|
||||||
|
const lensResourceTemplates = await di.inject(lensCreateResourceTemplatesInjectable);
|
||||||
|
const userResourceTemplates = di.inject(userCreateResourceTemplatesInjectable);
|
||||||
|
|
||||||
|
return computed(() => {
|
||||||
|
const res = [
|
||||||
|
...userResourceTemplates.get(),
|
||||||
|
lensResourceTemplates,
|
||||||
|
];
|
||||||
|
|
||||||
|
return res.map(([group, items]) => ({
|
||||||
|
label: group,
|
||||||
|
options: items.map(([label, value]) => ({ label, value })),
|
||||||
|
}) as GroupSelectOption<SelectOption<string>>);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default createResourceTemplatesInjectable;
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const extensionMatchers = [
|
||||||
|
/\.yaml$/,
|
||||||
|
/\.yml$/,
|
||||||
|
/\.json$/,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a fileName matches a yaml or json file name structure
|
||||||
|
* @param fileName The fileName to check
|
||||||
|
*/
|
||||||
|
export function hasCorrectExtension(fileName: string): boolean {
|
||||||
|
return extensionMatchers.some(matcher => matcher.test(fileName));
|
||||||
|
}
|
||||||
@ -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 path from "path";
|
||||||
|
import { hasCorrectExtension } from "./has-correct-extension";
|
||||||
|
import "../../../../common/vars";
|
||||||
|
import readFileInjectable from "../../../../common/fs/read-file.injectable";
|
||||||
|
import readDirInjectable from "../../../../common/fs/read-dir.injectable";
|
||||||
|
import type { RawTemplates } from "./create-resource-templates.injectable";
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
readDir: (dirPath: string) => Promise<string[]>;
|
||||||
|
readFile: (filePath: string, encoding: "utf-8") => Promise<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getTemplates({ readDir, readFile }: Dependencies) {
|
||||||
|
const templatesFolder = path.resolve(__static, "../templates/create-resource");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapping between file names and their contents
|
||||||
|
*/
|
||||||
|
const templates: [file: string, contents: string][] = [];
|
||||||
|
|
||||||
|
for (const dirEntry of await readDir(templatesFolder)) {
|
||||||
|
if (hasCorrectExtension(dirEntry)) {
|
||||||
|
templates.push([path.parse(dirEntry).name, await readFile(path.join(templatesFolder, dirEntry), "utf-8")]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return templates;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lensCreateResourceTemplatesInjectable = getInjectable({
|
||||||
|
instantiate: async (di): Promise<RawTemplates> => {
|
||||||
|
const templates = await getTemplates({
|
||||||
|
readFile: di.inject(readFileInjectable),
|
||||||
|
readDir: di.inject(readDirInjectable),
|
||||||
|
});
|
||||||
|
|
||||||
|
return ["lens", templates];
|
||||||
|
},
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default lensCreateResourceTemplatesInjectable;
|
||||||
@ -3,17 +3,15 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import dockStoreInjectable from "../dock-store/dock-store.injectable";
|
import { CreateResourceTabStore } from "./store";
|
||||||
import { CreateResourceStore } from "./create-resource.store";
|
|
||||||
import createStorageInjectable from "../../../utils/create-storage/create-storage.injectable";
|
import createStorageInjectable from "../../../utils/create-storage/create-storage.injectable";
|
||||||
|
|
||||||
const createResourceStoreInjectable = getInjectable({
|
const createResourceTabStoreInjectable = getInjectable({
|
||||||
instantiate: (di) => new CreateResourceStore({
|
instantiate: (di) => new CreateResourceTabStore({
|
||||||
dockStore: di.inject(dockStoreInjectable),
|
|
||||||
createStorage: di.inject(createStorageInjectable),
|
createStorage: di.inject(createStorageInjectable),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default createResourceStoreInjectable;
|
export default createResourceTabStoreInjectable;
|
||||||
19
src/renderer/components/dock/create-resource/store.ts
Normal file
19
src/renderer/components/dock/create-resource/store.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* 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";
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
createStorage:<T> (storageKey: string, options: DockTabStorageState<T>) => StorageHelper<DockTabStorageState<T>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CreateResourceTabStore extends DockTabStore<string> {
|
||||||
|
constructor(protected dependencies: Dependencies) {
|
||||||
|
super(dependencies, {
|
||||||
|
storageKey: "create_resource",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,102 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import { computed, IComputedValue, observable } from "mobx";
|
||||||
|
import path from "path";
|
||||||
|
import os from "os";
|
||||||
|
import { delay, getOrInsert, waitForPath } from "../../../utils";
|
||||||
|
import { watch } from "chokidar";
|
||||||
|
import { readFile } from "fs/promises";
|
||||||
|
import logger from "../../../../common/logger";
|
||||||
|
import { hasCorrectExtension } from "./has-correct-extension";
|
||||||
|
import type { RawTemplates } from "./create-resource-templates.injectable";
|
||||||
|
|
||||||
|
const userTemplatesFolder = path.join(os.homedir(), ".k8slens", "templates");
|
||||||
|
|
||||||
|
function groupTemplates(templates: Map<string, string>): RawTemplates[] {
|
||||||
|
const res = new Map<string, [string, string][]>();
|
||||||
|
|
||||||
|
for (const [filePath, contents] of templates) {
|
||||||
|
const rawRelative = path.dirname(path.relative(userTemplatesFolder, filePath));
|
||||||
|
const title = rawRelative === "."
|
||||||
|
? "ungrouped"
|
||||||
|
: rawRelative;
|
||||||
|
|
||||||
|
getOrInsert(res, title, []).push([path.parse(filePath).name, contents]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...res.entries()];
|
||||||
|
}
|
||||||
|
|
||||||
|
function watchUserCreateResourceTemplates(): IComputedValue<RawTemplates[]> {
|
||||||
|
/**
|
||||||
|
* Map between filePaths and template contents
|
||||||
|
*/
|
||||||
|
const templates = observable.map<string, string>();
|
||||||
|
|
||||||
|
const onAddOrChange = async (filePath: string) => {
|
||||||
|
if (!hasCorrectExtension(filePath)) {
|
||||||
|
// ignore non yaml or json files
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const contents = await readFile(filePath, "utf-8");
|
||||||
|
|
||||||
|
templates.set(filePath, contents);
|
||||||
|
} catch (error) {
|
||||||
|
if (error?.code === "ENOENT") {
|
||||||
|
// ignore, file disappeared
|
||||||
|
} else {
|
||||||
|
logger.warn(`[USER-CREATE-RESOURCE-TEMPLATES]: encountered error while reading ${filePath}`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const onUnlink = (filePath: string) => {
|
||||||
|
templates.delete(filePath);
|
||||||
|
};
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
for (let i = 1;; i *= 2) {
|
||||||
|
try {
|
||||||
|
await waitForPath(userTemplatesFolder);
|
||||||
|
break;
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn(`[USER-CREATE-RESOURCE-TEMPLATES]: encountered error while waiting for ${userTemplatesFolder} to exist, waiting and trying again`, error);
|
||||||
|
await delay(i * 1000); // exponential backoff in seconds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOTE: There is technically a race condition here of the form "time-of-check to time-of-use"
|
||||||
|
*/
|
||||||
|
watch(userTemplatesFolder, {
|
||||||
|
disableGlobbing: true,
|
||||||
|
ignorePermissionErrors: true,
|
||||||
|
usePolling: false,
|
||||||
|
awaitWriteFinish: {
|
||||||
|
pollInterval: 100,
|
||||||
|
stabilityThreshold: 1000,
|
||||||
|
},
|
||||||
|
ignoreInitial: false,
|
||||||
|
atomic: 150, // for "atomic writes"
|
||||||
|
})
|
||||||
|
.on("add", onAddOrChange)
|
||||||
|
.on("change", onAddOrChange)
|
||||||
|
.on("unlink", onUnlink)
|
||||||
|
.on("error", error => {
|
||||||
|
logger.warn(`[USER-CREATE-RESOURCE-TEMPLATES]: encountered error while watching files under ${userTemplatesFolder}`, error);
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
|
return computed(() => groupTemplates(templates));
|
||||||
|
}
|
||||||
|
|
||||||
|
const userCreateResourceTemplatesInjectable = getInjectable({
|
||||||
|
instantiate: () => watchUserCreateResourceTemplates(),
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default userCreateResourceTemplatesInjectable;
|
||||||
154
src/renderer/components/dock/create-resource/view.tsx
Normal file
154
src/renderer/components/dock/create-resource/view.tsx
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { GroupSelectOption, Select, SelectOption } from "../../select";
|
||||||
|
import yaml from "js-yaml";
|
||||||
|
import { IComputedValue, makeObservable, observable } from "mobx";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
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 createResourceTabStoreInjectable from "./store.injectable";
|
||||||
|
import createResourceTemplatesInjectable from "./create-resource-templates.injectable";
|
||||||
|
import { Spinner } from "../../spinner";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
tab: DockTab;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
createResourceTemplates: IComputedValue<GroupSelectOption<SelectOption>[]>;
|
||||||
|
createResourceTabStore: CreateResourceTabStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
@observer
|
||||||
|
class NonInjectedCreateResource extends React.Component<Props & Dependencies> {
|
||||||
|
@observable error = "";
|
||||||
|
|
||||||
|
constructor(props: Props & Dependencies) {
|
||||||
|
super(props);
|
||||||
|
makeObservable(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
get tabId() {
|
||||||
|
return this.props.tab.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
get data() {
|
||||||
|
return this.props.createResourceTabStore.getData(this.tabId);
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange = (value: string) => {
|
||||||
|
this.error = ""; // reset first, validation goes later
|
||||||
|
this.props.createResourceTabStore.setData(this.tabId, value);
|
||||||
|
};
|
||||||
|
|
||||||
|
onError = (error: Error | string) => {
|
||||||
|
this.error = error.toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
onSelectTemplate = (item: SelectOption<string>) => {
|
||||||
|
this.props.createResourceTabStore.setData(this.tabId, item.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
create = async (): Promise<void> => {
|
||||||
|
if (this.error || !this.data.trim()) {
|
||||||
|
// do not save when field is empty or there is an error
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip empty documents
|
||||||
|
const resources = yaml.loadAll(this.data).filter(Boolean);
|
||||||
|
|
||||||
|
if (resources.length === 0) {
|
||||||
|
return void logger.info("Nothing to create");
|
||||||
|
}
|
||||||
|
|
||||||
|
const creatingResources = resources.map(async (resource: string) => {
|
||||||
|
try {
|
||||||
|
const data = await resourceApplierApi.update(resource) as KubeJsonApiData;
|
||||||
|
const { kind, apiVersion, metadata: { name, namespace }} = data;
|
||||||
|
|
||||||
|
const showDetails = () => {
|
||||||
|
const resourceLink = apiManager.lookupApiLink({ kind, apiVersion, name, namespace });
|
||||||
|
|
||||||
|
navigate(getDetailsUrl(resourceLink));
|
||||||
|
close();
|
||||||
|
};
|
||||||
|
|
||||||
|
const close = Notifications.ok(
|
||||||
|
<p>
|
||||||
|
{kind} <a onClick={prevDefault(showDetails)}>{name}</a> successfully created.
|
||||||
|
</p>,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
Notifications.error(error?.toString() ?? "Unknown error occured");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.allSettled(creatingResources);
|
||||||
|
};
|
||||||
|
|
||||||
|
renderControls() {
|
||||||
|
return (
|
||||||
|
<div className="flex gaps align-center">
|
||||||
|
<Select
|
||||||
|
autoConvertOptions={false}
|
||||||
|
controlShouldRenderValue={false} // always keep initial placeholder
|
||||||
|
className="TemplateSelect"
|
||||||
|
placeholder="Select Template ..."
|
||||||
|
options={this.props.createResourceTemplates.get()}
|
||||||
|
menuPlacement="top"
|
||||||
|
themeName="outlined"
|
||||||
|
onChange={ this.onSelectTemplate}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { tabId, data, error } = this;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="CreateResource flex column">
|
||||||
|
<InfoPanel
|
||||||
|
tabId={tabId}
|
||||||
|
error={error}
|
||||||
|
controls={this.renderControls()}
|
||||||
|
submit={this.create}
|
||||||
|
submitLabel="Create"
|
||||||
|
showNotifications={false}
|
||||||
|
/>
|
||||||
|
<EditorPanel
|
||||||
|
tabId={tabId}
|
||||||
|
value={data}
|
||||||
|
onChange={this.onChange}
|
||||||
|
onError={this.onError}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CreateResource = withInjectables<Dependencies, Props>(NonInjectedCreateResource, {
|
||||||
|
getPlaceholder: () => <Spinner center />,
|
||||||
|
|
||||||
|
getProps: async (di, props) => ({
|
||||||
|
createResourceTabStore: di.inject(createResourceTabStoreInjectable),
|
||||||
|
createResourceTemplates: await di.inject(createResourceTemplatesInjectable),
|
||||||
|
...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;
|
|
||||||
@ -4,13 +4,11 @@
|
|||||||
*/
|
*/
|
||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import { DockTabStore, DockTabStoreOptions } from "./dock-tab.store";
|
import { DockTabStore, DockTabStoreOptions } from "./dock-tab.store";
|
||||||
import dockStoreInjectable from "../dock-store/dock-store.injectable";
|
|
||||||
import createStorageInjectable from "../../../utils/create-storage/create-storage.injectable";
|
import createStorageInjectable from "../../../utils/create-storage/create-storage.injectable";
|
||||||
|
|
||||||
const createDockTabStoreInjectable = getInjectable({
|
const createDockTabStoreInjectable = getInjectable({
|
||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
const dependencies = {
|
const dependencies = {
|
||||||
dockStore: di.inject(dockStoreInjectable),
|
|
||||||
createStorage: di.inject(createStorageInjectable),
|
createStorage: di.inject(createStorageInjectable),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -3,9 +3,9 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { autorun, observable, reaction } from "mobx";
|
import { action, observable, reaction } from "mobx";
|
||||||
import { autoBind, StorageHelper, toJS } from "../../../utils";
|
import { autoBind, StorageHelper, toJS } from "../../../utils";
|
||||||
import type { DockStore, TabId } from "../dock-store/dock.store";
|
import type { TabId } from "../dock/store";
|
||||||
|
|
||||||
export interface DockTabStoreOptions {
|
export interface DockTabStoreOptions {
|
||||||
autoInit?: boolean; // load data from storage when `storageKey` is provided and bind events, default: true
|
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>;
|
export type DockTabStorageState<T> = Record<TabId, T>;
|
||||||
|
|
||||||
interface Dependencies {
|
interface DockTabStoreDependencies {
|
||||||
dockStore: DockStore
|
|
||||||
createStorage: <T>(storageKey: string, options: DockTabStorageState<T>) => StorageHelper<DockTabStorageState<T>>
|
createStorage: <T>(storageKey: string, options: DockTabStorageState<T>) => StorageHelper<DockTabStorageState<T>>
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DockTabStore<T> {
|
export class DockTabStore<T> {
|
||||||
protected storage?: StorageHelper<DockTabStorageState<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);
|
autoBind(this);
|
||||||
|
|
||||||
this.options = {
|
this.options = {
|
||||||
@ -48,17 +47,6 @@ export class DockTabStore<T> {
|
|||||||
reaction(() => this.toJSON(), data => this.storage.set(data));
|
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 {
|
protected finalizeDataForSave(data: T): T {
|
||||||
@ -75,8 +63,22 @@ export class DockTabStore<T> {
|
|||||||
return Object.fromEntries<T>(deepCopy);
|
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 {
|
isReady(tabId: TabId): boolean {
|
||||||
return Boolean(this.getData(tabId) !== undefined);
|
return this.getData(tabId) !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
getData(tabId: TabId) {
|
getData(tabId: TabId) {
|
||||||
@ -91,8 +93,11 @@ export class DockTabStore<T> {
|
|||||||
this.data.delete(tabId);
|
this.data.delete(tabId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
reset() {
|
reset() {
|
||||||
this.data.clear();
|
for (const tabId of this.data.keys()) {
|
||||||
|
this.clearData(tabId);
|
||||||
|
}
|
||||||
this.storage?.reset();
|
this.storage?.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,14 +8,14 @@ import "./dock-tab.scss";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { boundMethod, cssNames, prevDefault, isMiddleClick } from "../../utils";
|
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 { Tab, TabProps } from "../tabs";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { Menu, MenuItem } from "../menu";
|
import { Menu, MenuItem } from "../menu";
|
||||||
import { observable, makeObservable } from "mobx";
|
import { observable, makeObservable } from "mobx";
|
||||||
import { isMac } from "../../../common/vars";
|
import { isMac } from "../../../common/vars";
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
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> {
|
export interface DockTabProps extends TabProps<DockTabModel> {
|
||||||
moreActions?: React.ReactNode;
|
moreActions?: React.ReactNode;
|
||||||
|
|||||||
@ -8,9 +8,9 @@ import React, { Fragment } from "react";
|
|||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { Tabs } from "../tabs/tabs";
|
import { Tabs } from "../tabs/tabs";
|
||||||
import { DockTab } from "./dock-tab";
|
import { DockTab } from "./dock-tab";
|
||||||
import type { DockTab as DockTabModel } from "./dock-store/dock.store";
|
import type { DockTab as DockTabModel } from "./dock/store";
|
||||||
import { TabKind } from "./dock-store/dock.store";
|
import { TabKind } from "./dock/store";
|
||||||
import { TerminalTab } from "./terminal-tab";
|
import { TerminalTab } from "./terminal/dock-tab";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
tabs: DockTabModel[]
|
tabs: DockTabModel[]
|
||||||
|
|||||||
@ -13,18 +13,19 @@ import { Icon } from "../icon";
|
|||||||
import { MenuItem } from "../menu";
|
import { MenuItem } from "../menu";
|
||||||
import { MenuActions } from "../menu/menu-actions";
|
import { MenuActions } from "../menu/menu-actions";
|
||||||
import { ResizeDirection, ResizingAnchor } from "../resizing-anchor";
|
import { ResizeDirection, ResizingAnchor } from "../resizing-anchor";
|
||||||
import { CreateResource } from "./create-resource";
|
import { CreateResource } from "./create-resource/view";
|
||||||
import { DockTabs } from "./dock-tabs";
|
import { DockTabs } from "./dock-tabs";
|
||||||
import { DockStore, DockTab, TabKind } from "./dock-store/dock.store";
|
import { DockStore, DockTab, TabKind } from "./dock/store";
|
||||||
import { EditResource } from "./edit-resource";
|
import { EditResource } from "./edit-resource/view";
|
||||||
import { InstallChart } from "./install-chart";
|
import { InstallChart } from "./install-chart/view";
|
||||||
import { LogsDockTab } from "./logs/dock-tab";
|
import { LogsDockTab } from "./logs/view";
|
||||||
import { TerminalWindow } from "./terminal-window";
|
import { TerminalWindow } from "./terminal/view";
|
||||||
import { UpgradeChart } from "./upgrade-chart";
|
import { UpgradeChart } from "./upgrade-chart/view";
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
import createResourceTabInjectable from "./create-resource-tab/create-resource-tab.injectable";
|
import createResourceTabInjectable from "./create-resource/create-resource-tab.injectable";
|
||||||
import dockStoreInjectable from "./dock-store/dock-store.injectable";
|
import dockStoreInjectable from "./dock/store.injectable";
|
||||||
import createTerminalTabInjectable from "./create-terminal-tab/create-terminal-tab.injectable";
|
import createTerminalTabInjectable from "./terminal/create-terminal-tab.injectable";
|
||||||
|
import { ErrorBoundary } from "../error-boundary";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -160,7 +161,9 @@ class NonInjectedDock extends React.Component<Props & Dependencies> {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{this.renderTabContent()}
|
<ErrorBoundary>
|
||||||
|
{this.renderTabContent()}
|
||||||
|
</ErrorBoundary>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import type { TabId } from "./store";
|
||||||
|
import dockStoreInjectable from "./store.injectable";
|
||||||
|
|
||||||
|
const closeDockTabInjectable = getInjectable({
|
||||||
|
instantiate: (di) => {
|
||||||
|
const dockStore = di.inject(dockStoreInjectable);
|
||||||
|
|
||||||
|
return (tabId: TabId): void => {
|
||||||
|
dockStore.closeTab(tabId);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default closeDockTabInjectable;
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* 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 "./store.injectable";
|
||||||
|
import type { DockTab, DockTabCreate } from "./store";
|
||||||
|
|
||||||
|
const createDockTabInjectable = getInjectable({
|
||||||
|
instantiate: (di) => {
|
||||||
|
const dockStore = di.inject(dockStoreInjectable);
|
||||||
|
|
||||||
|
return (rawTabDesc: DockTabCreate, addNumber?: boolean): DockTab =>
|
||||||
|
dockStore.createTab(rawTabDesc, addNumber);
|
||||||
|
},
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default createDockTabInjectable;
|
||||||
@ -3,8 +3,8 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import createStorageInjectable from "../../../../utils/create-storage/create-storage.injectable";
|
import createStorageInjectable from "../../../utils/create-storage/create-storage.injectable";
|
||||||
import { DockStorageState, TabKind } from "../dock.store";
|
import { DockStorageState, TabKind } from "./store";
|
||||||
|
|
||||||
const dockStorageInjectable = getInjectable({
|
const dockStorageInjectable = getInjectable({
|
||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
22
src/renderer/components/dock/dock/rename-tab.injectable.ts
Normal file
22
src/renderer/components/dock/dock/rename-tab.injectable.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* 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 "./store.injectable";
|
||||||
|
import type { TabId } from "./store";
|
||||||
|
|
||||||
|
const renameTabInjectable = getInjectable({
|
||||||
|
|
||||||
|
instantiate: (di) => {
|
||||||
|
const dockStore = di.inject(dockStoreInjectable);
|
||||||
|
|
||||||
|
return (tabId: TabId, title: string): void => {
|
||||||
|
dockStore.renameTab(tabId, title);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default renameTabInjectable;
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import type { TabId } from "./store";
|
||||||
|
import dockStoreInjectable from "./store.injectable";
|
||||||
|
|
||||||
|
const selectDockTabInjectable = getInjectable({
|
||||||
|
instantiate: (di) => {
|
||||||
|
const dockStore = di.inject(dockStoreInjectable);
|
||||||
|
|
||||||
|
return (tabId: TabId): void => {
|
||||||
|
dockStore.selectTab(tabId);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
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 {
|
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 {
|
export class DockStore implements DockStorageState {
|
||||||
constructor(private dependencies: Dependencies) {
|
constructor(private readonly dependencies: Dependencies) {
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
autoBind(this);
|
autoBind(this);
|
||||||
this.init();
|
this.init();
|
||||||
@ -167,6 +169,16 @@ export class DockStore implements DockStorageState {
|
|||||||
private init() {
|
private init() {
|
||||||
// adjust terminal height if window size changes
|
// adjust terminal height if window size changes
|
||||||
window.addEventListener("resize", throttle(this.adjustHeight, 250));
|
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() {
|
get maxHeight() {
|
||||||
@ -317,6 +329,7 @@ export class DockStore implements DockStorageState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.tabs = this.tabs.filter(tab => tab.id !== tabId);
|
this.tabs = this.tabs.filter(tab => tab.id !== tabId);
|
||||||
|
this.dependencies.tabDataClearers[tab.kind](tab.id);
|
||||||
|
|
||||||
if (this.selectedTabId === tab.id) {
|
if (this.selectedTabId === tab.id) {
|
||||||
if (this.tabs.length) {
|
if (this.tabs.length) {
|
||||||
@ -330,6 +343,7 @@ export class DockStore implements DockStorageState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
closeTabs(tabs: DockTab[]) {
|
closeTabs(tabs: DockTab[]) {
|
||||||
tabs.forEach(tab => this.closeTab(tab.id));
|
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,21 @@
|
|||||||
|
/**
|
||||||
|
* 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 { TabId } from "../dock/store";
|
||||||
|
import editResourceTabStoreInjectable from "./store.injectable";
|
||||||
|
|
||||||
|
const clearEditResourceTabDataInjectable = getInjectable({
|
||||||
|
instantiate: (di) => {
|
||||||
|
const editResourceTabStore = di.inject(editResourceTabStoreInjectable);
|
||||||
|
|
||||||
|
return (tabId: TabId) => {
|
||||||
|
editResourceTabStore.clearData(tabId);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default clearEditResourceTabDataInjectable;
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
/**
|
||||||
|
* 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 { runInAction } from "mobx";
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
dockStore: DockStore;
|
||||||
|
editResourceStore: EditResourceTabStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
const 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) => createEditResourceTab({
|
||||||
|
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.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import "./edit-resource.scss";
|
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { computed, makeObservable, observable } from "mobx";
|
import { autorun, computed, makeObservable, observable } from "mobx";
|
||||||
import { observer } from "mobx-react";
|
import { disposeOnUnmount, observer } from "mobx-react";
|
||||||
import yaml from "js-yaml";
|
import yaml from "js-yaml";
|
||||||
import type { DockTab } from "./dock-store/dock.store";
|
import type { DockTab, TabId } from "../dock/store";
|
||||||
import type { EditResourceStore } from "./edit-resource-store/edit-resource.store";
|
import type { EditResourceTabStore } from "./store";
|
||||||
import { InfoPanel } from "./info-panel";
|
import { InfoPanel } from "../info-panel";
|
||||||
import { Badge } from "../badge";
|
import { Badge } from "../../badge";
|
||||||
import { EditorPanel } from "./editor-panel";
|
import { EditorPanel } from "../editor-panel";
|
||||||
import { Spinner } from "../spinner";
|
import { Spinner } from "../../spinner";
|
||||||
import type { KubeObject } from "../../../common/k8s-api/kube-object";
|
import type { KubeObject } from "../../../../common/k8s-api/kube-object";
|
||||||
import { createPatch } from "rfc6902";
|
import { createPatch } from "rfc6902";
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
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 {
|
interface Props {
|
||||||
tab: DockTab;
|
tab: DockTab;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
editResourceStore: EditResourceStore
|
editResourceStore: EditResourceTabStore;
|
||||||
|
closeTab: (tabId: TabId) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
@ -37,6 +38,26 @@ class NonInjectedEditResource extends React.Component<Props & Dependencies> {
|
|||||||
makeObservable(this);
|
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() {
|
get tabId() {
|
||||||
return this.props.tab.id;
|
return this.props.tab.id;
|
||||||
}
|
}
|
||||||
@ -132,13 +153,10 @@ class NonInjectedEditResource extends React.Component<Props & Dependencies> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EditResource = withInjectables<Dependencies, Props>(
|
export const EditResource = withInjectables<Dependencies, Props>(NonInjectedEditResource, {
|
||||||
NonInjectedEditResource,
|
getProps: (di, props) => ({
|
||||||
|
editResourceStore: di.inject(editResourceTabStoreInjectable),
|
||||||
{
|
closeTab: di.inject(closeDockTabInjectable),
|
||||||
getProps: (di, props) => ({
|
...props,
|
||||||
editResourceStore: di.inject(editResourceStoreInjectable),
|
}),
|
||||||
...props,
|
});
|
||||||
}),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
@ -8,11 +8,11 @@ import throttle from "lodash/throttle";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { makeObservable, observable, reaction } from "mobx";
|
import { makeObservable, observable, reaction } from "mobx";
|
||||||
import { disposeOnUnmount, observer } from "mobx-react";
|
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 { cssNames } from "../../utils";
|
||||||
import { MonacoEditor, MonacoEditorProps } from "../monaco-editor";
|
import { MonacoEditor, MonacoEditorProps } from "../monaco-editor";
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
import dockStoreInjectable from "./dock-store/dock-store.injectable";
|
import dockStoreInjectable from "./dock/store.injectable";
|
||||||
|
|
||||||
export interface EditorPanelProps {
|
export interface EditorPanelProps {
|
||||||
tabId: TabId;
|
tabId: TabId;
|
||||||
|
|||||||
@ -12,14 +12,14 @@ import { cssNames } from "../../utils";
|
|||||||
import { Button } from "../button";
|
import { Button } from "../button";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { Spinner } from "../spinner";
|
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 { Notifications } from "../notifications";
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
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 {
|
interface Props extends OptionalProps {
|
||||||
tabId: TabId;
|
tabId: TabId;
|
||||||
submit?: () => Promise<ReactNode | string>;
|
submit?: () => Promise<ReactNode | string | void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OptionalProps {
|
interface OptionalProps {
|
||||||
@ -80,7 +80,7 @@ class NonInjectedInfoPanel extends Component<Props & Dependencies> {
|
|||||||
try {
|
try {
|
||||||
const result = await this.props.submit();
|
const result = await this.props.submit();
|
||||||
|
|
||||||
if (showNotifications) Notifications.ok(result);
|
if (showNotifications && result) Notifications.ok(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (showNotifications) Notifications.error(error.toString());
|
if (showNotifications) Notifications.error(error.toString());
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import type { TabId } from "../dock/store";
|
||||||
|
import installChartTabStoreInjectable from "./store.injectable";
|
||||||
|
|
||||||
|
const clearInstallChartTabDataInjectable = getInjectable({
|
||||||
|
instantiate: (di) => {
|
||||||
|
const installChartTabStore = di.inject(installChartTabStoreInjectable);
|
||||||
|
|
||||||
|
return (tabId: TabId) => {
|
||||||
|
installChartTabStore.clearData(tabId);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default clearInstallChartTabDataInjectable;
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
/**
|
||||||
|
* 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 createDockTabInjectable from "../dock/create-dock-tab.injectable";
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
createDockTab: (rawTab: DockTabCreate, addNumber: boolean) => DockTab;
|
||||||
|
installChartStore: InstallChartTabStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createInstallChartTabInjectable = getInjectable({
|
||||||
|
instantiate: (di) => createInstallChartTab({
|
||||||
|
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.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import { InstallChartStore } from "./install-chart.store";
|
import { InstallChartTabStore } from "./store";
|
||||||
import dockStoreInjectable from "../dock-store/dock-store.injectable";
|
|
||||||
import createDockTabStoreInjectable from "../dock-tab-store/create-dock-tab-store.injectable";
|
import createDockTabStoreInjectable from "../dock-tab-store/create-dock-tab-store.injectable";
|
||||||
import type { IReleaseUpdateDetails } from "../../../../common/k8s-api/endpoints/helm-releases.api";
|
import type { IReleaseUpdateDetails } from "../../../../common/k8s-api/endpoints/helm-releases.api";
|
||||||
import createStorageInjectable from "../../../utils/create-storage/create-storage.injectable";
|
import createStorageInjectable from "../../../utils/create-storage/create-storage.injectable";
|
||||||
|
|
||||||
const installChartStoreInjectable = getInjectable({
|
const installChartTabStoreInjectable = getInjectable({
|
||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
const createDockTabStore = di.inject(createDockTabStoreInjectable);
|
const createDockTabStore = di.inject(createDockTabStoreInjectable);
|
||||||
|
|
||||||
return new InstallChartStore({
|
return new InstallChartTabStore({
|
||||||
dockStore: di.inject(dockStoreInjectable),
|
|
||||||
createStorage: di.inject(createStorageInjectable),
|
createStorage: di.inject(createStorageInjectable),
|
||||||
versionsStore: createDockTabStore<string[]>(),
|
versionsStore: createDockTabStore<string[]>(),
|
||||||
detailsStore: createDockTabStore<IReleaseUpdateDetails>(),
|
detailsStore: createDockTabStore<IReleaseUpdateDetails>(),
|
||||||
@ -23,4 +21,4 @@ const installChartStoreInjectable = getInjectable({
|
|||||||
lifecycle: lifecycleEnum.singleton,
|
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.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { action, autorun, makeObservable } from "mobx";
|
import { action, makeObservable, when } from "mobx";
|
||||||
import { DockStore, TabId, TabKind } from "../dock-store/dock.store";
|
import type { TabId } from "../dock/store";
|
||||||
import { DockTabStorageState, DockTabStore } from "../dock-tab-store/dock-tab.store";
|
import { DockTabStorageState, DockTabStore } from "../dock-tab-store/dock-tab.store";
|
||||||
import { getChartDetails, getChartValues } from "../../../../common/k8s-api/endpoints/helm-charts.api";
|
import { getChartDetails, getChartValues } from "../../../../common/k8s-api/endpoints/helm-charts.api";
|
||||||
import type { IReleaseUpdateDetails } from "../../../../common/k8s-api/endpoints/helm-releases.api";
|
import type { IReleaseUpdateDetails } from "../../../../common/k8s-api/endpoints/helm-releases.api";
|
||||||
import { Notifications } from "../../notifications";
|
|
||||||
import type { StorageHelper } from "../../../utils";
|
import type { StorageHelper } from "../../../utils";
|
||||||
|
|
||||||
export interface IChartInstallData {
|
export interface IChartInstallData {
|
||||||
@ -23,29 +22,18 @@ export interface IChartInstallData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
dockStore: DockStore,
|
createStorage: <T>(storageKey: string, options: DockTabStorageState<T>) => StorageHelper<DockTabStorageState<T>>;
|
||||||
createStorage: <T>(storageKey: string, options: DockTabStorageState<T>) => StorageHelper<DockTabStorageState<T>>
|
versionsStore: DockTabStore<string[]>;
|
||||||
|
detailsStore: DockTabStore<IReleaseUpdateDetails>;
|
||||||
versionsStore: DockTabStore<string[]>,
|
|
||||||
detailsStore: DockTabStore<IReleaseUpdateDetails>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class InstallChartStore extends DockTabStore<IChartInstallData> {
|
export class InstallChartTabStore extends DockTabStore<IChartInstallData> {
|
||||||
constructor(protected dependencies: Dependencies) {
|
constructor(protected dependencies: Dependencies) {
|
||||||
super(
|
super(
|
||||||
dependencies,
|
dependencies,
|
||||||
{ storageKey: "install_charts" },
|
{ storageKey: "install_charts" },
|
||||||
);
|
);
|
||||||
|
|
||||||
makeObservable(this);
|
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() {
|
get versions() {
|
||||||
@ -60,6 +48,8 @@ export class InstallChartStore extends DockTabStore<IChartInstallData> {
|
|||||||
async loadData(tabId: string) {
|
async loadData(tabId: string) {
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
|
||||||
|
await when(() => this.isReady(tabId));
|
||||||
|
|
||||||
if (!this.getData(tabId).values) {
|
if (!this.getData(tabId).values) {
|
||||||
promises.push(this.loadValues(tabId));
|
promises.push(this.loadValues(tabId));
|
||||||
}
|
}
|
||||||
@ -94,8 +84,4 @@ export class InstallChartStore extends DockTabStore<IChartInstallData> {
|
|||||||
return this.loadValues(tabId, attempt + 1);
|
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 React, { Component } from "react";
|
||||||
import { action, makeObservable, observable } from "mobx";
|
import { action, makeObservable, observable } from "mobx";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import type { DockStore, DockTab } from "./dock-store/dock.store";
|
import type { DockStore, DockTab } from "../dock/store";
|
||||||
import { InfoPanel } from "./info-panel";
|
import { InfoPanel } from "../info-panel";
|
||||||
import { Badge } from "../badge";
|
import { Badge } from "../../badge";
|
||||||
import { NamespaceSelect } from "../+namespaces/namespace-select";
|
import { NamespaceSelect } from "../../+namespaces/namespace-select";
|
||||||
import { prevDefault } from "../../utils";
|
import { prevDefault } from "../../../utils";
|
||||||
import type { IChartInstallData, InstallChartStore } from "./install-chart-store/install-chart.store";
|
import type { IChartInstallData, InstallChartTabStore } from "./store";
|
||||||
import { Spinner } from "../spinner";
|
import { Spinner } from "../../spinner";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../../icon";
|
||||||
import { Button } from "../button";
|
import { Button } from "../../button";
|
||||||
import { LogsDialog } from "../dialog/logs-dialog";
|
import { LogsDialog } from "../../dialog/logs-dialog";
|
||||||
import { Select, SelectOption } from "../select";
|
import { Select, SelectOption } from "../../select";
|
||||||
import { Input } from "../input";
|
import { Input } from "../../input";
|
||||||
import { EditorPanel } from "./editor-panel";
|
import { EditorPanel } from "../editor-panel";
|
||||||
import { navigate } from "../../navigation";
|
import { navigate } from "../../../navigation";
|
||||||
import { releaseURL } from "../../../common/routes";
|
import { releaseURL } from "../../../../common/routes";
|
||||||
import type {
|
import type { IReleaseCreatePayload, IReleaseUpdateDetails } from "../../../../common/k8s-api/endpoints/helm-releases.api";
|
||||||
IReleaseCreatePayload,
|
|
||||||
IReleaseUpdateDetails,
|
|
||||||
} from "../../../common/k8s-api/endpoints/helm-releases.api";
|
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
import installChartStoreInjectable from "./install-chart-store/install-chart-store.injectable";
|
import installChartTabStoreInjectable from "./store.injectable";
|
||||||
import dockStoreInjectable from "./dock-store/dock-store.injectable";
|
import dockStoreInjectable from "../dock/store.injectable";
|
||||||
import createReleaseInjectable from "../+apps-releases/create-release/create-release.injectable";
|
import createReleaseInjectable from "../../+apps-releases/create-release/create-release.injectable";
|
||||||
|
import { Notifications } from "../../notifications";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
tab: DockTab;
|
tab: DockTab;
|
||||||
@ -38,7 +36,7 @@ interface Props {
|
|||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
createRelease: (payload: IReleaseCreatePayload) => Promise<IReleaseUpdateDetails>
|
createRelease: (payload: IReleaseCreatePayload) => Promise<IReleaseUpdateDetails>
|
||||||
installChartStore: InstallChartStore
|
installChartStore: InstallChartTabStore
|
||||||
dockStore: DockStore
|
dockStore: DockStore
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,6 +50,11 @@ class NonInjectedInstallChart extends Component<Props & Dependencies> {
|
|||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount(): void {
|
||||||
|
this.props.installChartStore.loadData(this.tabId)
|
||||||
|
.catch(err => Notifications.error(String(err)));
|
||||||
|
}
|
||||||
|
|
||||||
get chartData() {
|
get chartData() {
|
||||||
return this.props.installChartStore.getData(this.tabId);
|
return this.props.installChartStore.getData(this.tabId);
|
||||||
}
|
}
|
||||||
@ -221,7 +224,7 @@ export const InstallChart = withInjectables<Dependencies, Props>(
|
|||||||
{
|
{
|
||||||
getProps: (di, props) => ({
|
getProps: (di, props) => ({
|
||||||
createRelease: di.inject(createReleaseInjectable),
|
createRelease: di.inject(createReleaseInjectable),
|
||||||
installChartStore: di.inject(installChartStoreInjectable),
|
installChartStore: di.inject(installChartTabStoreInjectable),
|
||||||
dockStore: di.inject(dockStoreInjectable),
|
dockStore: di.inject(dockStoreInjectable),
|
||||||
...props,
|
...props,
|
||||||
}),
|
}),
|
||||||
@ -8,7 +8,7 @@ import "@testing-library/jest-dom/extend-expect";
|
|||||||
import * as selectEvent from "react-select-event";
|
import * as selectEvent from "react-select-event";
|
||||||
import { Pod } from "../../../../../common/k8s-api/endpoints";
|
import { Pod } from "../../../../../common/k8s-api/endpoints";
|
||||||
import { LogResourceSelector } from "../resource-selector";
|
import { LogResourceSelector } from "../resource-selector";
|
||||||
import { dockerPod, deploymentPod1 } from "./pod.mock";
|
import { dockerPod, deploymentPod1, deploymentPod2 } from "./pod.mock";
|
||||||
import { ThemeStore } from "../../../../theme.store";
|
import { ThemeStore } from "../../../../theme.store";
|
||||||
import { UserStore } from "../../../../../common/user-store";
|
import { UserStore } from "../../../../../common/user-store";
|
||||||
import mockFs from "mock-fs";
|
import mockFs from "mock-fs";
|
||||||
@ -18,7 +18,9 @@ import { renderFor } from "../../../test-utils/renderFor";
|
|||||||
import directoryForUserDataInjectable from "../../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
import directoryForUserDataInjectable from "../../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
import callForLogsInjectable from "../call-for-logs.injectable";
|
import callForLogsInjectable from "../call-for-logs.injectable";
|
||||||
import { LogTabViewModel, LogTabViewModelDependencies } from "../logs-view-model";
|
import { LogTabViewModel, LogTabViewModelDependencies } from "../logs-view-model";
|
||||||
import type { TabId } from "../../dock-store/dock.store";
|
import type { TabId } from "../../dock/store";
|
||||||
|
import userEvent from "@testing-library/user-event";
|
||||||
|
import { SearchStore } from "../../../../search-store/search-store";
|
||||||
|
|
||||||
jest.mock("electron", () => ({
|
jest.mock("electron", () => ({
|
||||||
app: {
|
app: {
|
||||||
@ -36,10 +38,6 @@ jest.mock("electron", () => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const getComponent = (model: LogTabViewModel) => (
|
|
||||||
<LogResourceSelector model={model} />
|
|
||||||
);
|
|
||||||
|
|
||||||
function mockLogTabViewModel(tabId: TabId, deps: Partial<LogTabViewModelDependencies>): LogTabViewModel {
|
function mockLogTabViewModel(tabId: TabId, deps: Partial<LogTabViewModelDependencies>): LogTabViewModel {
|
||||||
return new LogTabViewModel(tabId, {
|
return new LogTabViewModel(tabId, {
|
||||||
getLogs: jest.fn(),
|
getLogs: jest.fn(),
|
||||||
@ -49,34 +47,74 @@ function mockLogTabViewModel(tabId: TabId, deps: Partial<LogTabViewModelDependen
|
|||||||
setLogTabData: jest.fn(),
|
setLogTabData: jest.fn(),
|
||||||
loadLogs: jest.fn(),
|
loadLogs: jest.fn(),
|
||||||
reloadLogs: jest.fn(),
|
reloadLogs: jest.fn(),
|
||||||
updateTabName: jest.fn(),
|
renameTab: jest.fn(),
|
||||||
stopLoadingLogs: jest.fn(),
|
stopLoadingLogs: jest.fn(),
|
||||||
|
getPodById: jest.fn(),
|
||||||
|
getPodsByOwnerId: jest.fn(),
|
||||||
|
searchStore: new SearchStore(),
|
||||||
|
areLogsPresent: jest.fn(),
|
||||||
...deps,
|
...deps,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const getOnePodViewModel = (tabId: TabId): LogTabViewModel => {
|
const getOnePodViewModel = (tabId: TabId, deps: Partial<LogTabViewModelDependencies> = {}): LogTabViewModel => {
|
||||||
const selectedPod = new Pod(dockerPod);
|
const selectedPod = new Pod(dockerPod);
|
||||||
|
|
||||||
return mockLogTabViewModel(tabId, {
|
return mockLogTabViewModel(tabId, {
|
||||||
getLogTabData: () => ({
|
getLogTabData: () => ({
|
||||||
pods: [selectedPod],
|
selectedPodId: selectedPod.getId(),
|
||||||
selectedPod,
|
selectedContainer: selectedPod.getContainers()[0].name,
|
||||||
selectedContainer: selectedPod.getContainers()[0],
|
namespace: selectedPod.getNs(),
|
||||||
|
showPrevious: false,
|
||||||
|
showTimestamps: false,
|
||||||
}),
|
}),
|
||||||
|
getPodById: (id) => {
|
||||||
|
if (id === selectedPod.getId()) {
|
||||||
|
return selectedPod;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
...deps,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFewPodsTabData = (tabId: TabId): LogTabViewModel => {
|
const getFewPodsTabData = (tabId: TabId, deps: Partial<LogTabViewModelDependencies> = {}): LogTabViewModel => {
|
||||||
const selectedPod = new Pod(deploymentPod1);
|
const selectedPod = new Pod(deploymentPod1);
|
||||||
const anotherPod = new Pod(dockerPod);
|
const anotherPod = new Pod(deploymentPod2);
|
||||||
|
|
||||||
return mockLogTabViewModel(tabId, {
|
return mockLogTabViewModel(tabId, {
|
||||||
getLogTabData: () => ({
|
getLogTabData: () => ({
|
||||||
pods: [selectedPod, anotherPod],
|
owner: {
|
||||||
selectedPod,
|
uid: "uuid",
|
||||||
selectedContainer: selectedPod.getContainers()[0],
|
kind: "Deployment",
|
||||||
|
name: "super-deployment",
|
||||||
|
},
|
||||||
|
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 [];
|
||||||
|
},
|
||||||
|
...deps,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -109,14 +147,14 @@ describe("<LogResourceSelector />", () => {
|
|||||||
|
|
||||||
it("renders w/o errors", () => {
|
it("renders w/o errors", () => {
|
||||||
const model = getOnePodViewModel("foobar");
|
const model = getOnePodViewModel("foobar");
|
||||||
const { container } = render(getComponent(model));
|
const { container } = render(<LogResourceSelector model={model} />);
|
||||||
|
|
||||||
expect(container).toBeInstanceOf(HTMLElement);
|
expect(container).toBeInstanceOf(HTMLElement);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders proper namespace", async () => {
|
it("renders proper namespace", async () => {
|
||||||
const model = getOnePodViewModel("foobar");
|
const model = getOnePodViewModel("foobar");
|
||||||
const { findByTestId } = render(getComponent(model));
|
const { findByTestId } = render(<LogResourceSelector model={model} />);
|
||||||
const ns = await findByTestId("namespace-badge");
|
const ns = await findByTestId("namespace-badge");
|
||||||
|
|
||||||
expect(ns).toHaveTextContent("default");
|
expect(ns).toHaveTextContent("default");
|
||||||
@ -124,7 +162,7 @@ describe("<LogResourceSelector />", () => {
|
|||||||
|
|
||||||
it("renders proper selected items within dropdowns", async () => {
|
it("renders proper selected items within dropdowns", async () => {
|
||||||
const model = getOnePodViewModel("foobar");
|
const model = getOnePodViewModel("foobar");
|
||||||
const { findByText } = render(getComponent(model));
|
const { findByText } = render(<LogResourceSelector model={model} />);
|
||||||
|
|
||||||
expect(await findByText("dockerExporter")).toBeInTheDocument();
|
expect(await findByText("dockerExporter")).toBeInTheDocument();
|
||||||
expect(await findByText("docker-exporter")).toBeInTheDocument();
|
expect(await findByText("docker-exporter")).toBeInTheDocument();
|
||||||
@ -132,33 +170,40 @@ describe("<LogResourceSelector />", () => {
|
|||||||
|
|
||||||
it("renders sibling pods in dropdown", async () => {
|
it("renders sibling pods in dropdown", async () => {
|
||||||
const model = getFewPodsTabData("foobar");
|
const model = getFewPodsTabData("foobar");
|
||||||
const { container, findByText } = render(getComponent(model));
|
const { container, findByText } = render(<LogResourceSelector model={model} />);
|
||||||
|
|
||||||
selectEvent.openMenu(container.querySelector(".pod-selector"));
|
selectEvent.openMenu(container.querySelector(".pod-selector"));
|
||||||
|
expect(await findByText("deploymentPod2", { selector: ".pod-selector-menu .Select__option" })).toBeInTheDocument();
|
||||||
expect(await findByText("dockerExporter", { selector: ".pod-selector-menu .Select__option" })).toBeInTheDocument();
|
|
||||||
expect(await findByText("deploymentPod1", { selector: ".pod-selector-menu .Select__option" })).toBeInTheDocument();
|
expect(await findByText("deploymentPod1", { selector: ".pod-selector-menu .Select__option" })).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders sibling containers in dropdown", async () => {
|
it("renders sibling containers in dropdown", async () => {
|
||||||
const model = getFewPodsTabData("foobar");
|
const model = getFewPodsTabData("foobar");
|
||||||
const { findByText, container } = render(getComponent(model));
|
const { findByText, container } = render(<LogResourceSelector model={model} />);
|
||||||
const containerSelector: HTMLElement = container.querySelector(".container-selector");
|
|
||||||
|
|
||||||
selectEvent.openMenu(containerSelector);
|
|
||||||
|
|
||||||
|
selectEvent.openMenu(container.querySelector(".container-selector"));
|
||||||
expect(await findByText("node-exporter-1")).toBeInTheDocument();
|
expect(await findByText("node-exporter-1")).toBeInTheDocument();
|
||||||
expect(await findByText("init-node-exporter")).toBeInTheDocument();
|
expect(await findByText("init-node-exporter")).toBeInTheDocument();
|
||||||
expect(await findByText("init-node-exporter-1")).toBeInTheDocument();
|
expect(await findByText("init-node-exporter-1")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders pod owner as dropdown title", async () => {
|
it("renders pod owner as badge", async () => {
|
||||||
const model = getFewPodsTabData("foobar");
|
const model = getFewPodsTabData("foobar");
|
||||||
const { findByText, container } = render(getComponent(model));
|
const { findByText } = render(<LogResourceSelector model={model} />);
|
||||||
const podSelector: HTMLElement = container.querySelector(".pod-selector");
|
|
||||||
|
|
||||||
selectEvent.openMenu(podSelector);
|
expect(await findByText("super-deployment", {
|
||||||
|
exact: false,
|
||||||
|
})).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
expect(await findByText("super-deployment")).toBeInTheDocument();
|
it("updates tab name if selected pod changes", async () => {
|
||||||
|
const renameTab = jest.fn();
|
||||||
|
const model = getFewPodsTabData("foobar", { renameTab });
|
||||||
|
const { findByText, container } = render(<LogResourceSelector model={model} />);
|
||||||
|
|
||||||
|
selectEvent.openMenu(container.querySelector(".pod-selector"));
|
||||||
|
|
||||||
|
userEvent.click(await findByText("deploymentPod2", { selector: ".pod-selector-menu .Select__option" }));
|
||||||
|
expect(renameTab).toBeCalledWith("foobar", "Pod deploymentPod2");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
147
src/renderer/components/dock/logs/__test__/log-search.test.tsx
Normal file
147
src/renderer/components/dock/logs/__test__/log-search.test.tsx
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { screen } from "@testing-library/react";
|
||||||
|
import { Pod } from "../../../../../common/k8s-api/endpoints";
|
||||||
|
import { dockerPod } from "./pod.mock";
|
||||||
|
import { getDiForUnitTesting } from "../../../../getDiForUnitTesting";
|
||||||
|
import type { DiRender } from "../../../test-utils/renderFor";
|
||||||
|
import { renderFor } from "../../../test-utils/renderFor";
|
||||||
|
import { LogTabViewModel, LogTabViewModelDependencies } from "../logs-view-model";
|
||||||
|
import type { TabId } from "../../dock/store";
|
||||||
|
import { LogSearch } from "../search";
|
||||||
|
import userEvent from "@testing-library/user-event";
|
||||||
|
import { SearchStore } from "../../../../search-store/search-store";
|
||||||
|
|
||||||
|
function mockLogTabViewModel(tabId: TabId, deps: Partial<LogTabViewModelDependencies>): LogTabViewModel {
|
||||||
|
return new LogTabViewModel(tabId, {
|
||||||
|
getLogs: jest.fn(),
|
||||||
|
getLogsWithoutTimestamps: jest.fn(),
|
||||||
|
getTimestampSplitLogs: jest.fn(),
|
||||||
|
getLogTabData: jest.fn(),
|
||||||
|
setLogTabData: jest.fn(),
|
||||||
|
loadLogs: jest.fn(),
|
||||||
|
reloadLogs: jest.fn(),
|
||||||
|
renameTab: jest.fn(),
|
||||||
|
stopLoadingLogs: jest.fn(),
|
||||||
|
getPodById: jest.fn(),
|
||||||
|
getPodsByOwnerId: jest.fn(),
|
||||||
|
areLogsPresent: jest.fn(),
|
||||||
|
searchStore: new SearchStore(),
|
||||||
|
...deps,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const getOnePodViewModel = (tabId: TabId, deps: Partial<LogTabViewModelDependencies> = {}): LogTabViewModel => {
|
||||||
|
const selectedPod = new Pod(dockerPod);
|
||||||
|
|
||||||
|
return mockLogTabViewModel(tabId, {
|
||||||
|
getLogTabData: () => ({
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
...deps,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("LogSearch tests", () => {
|
||||||
|
let render: DiRender;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
|
render = renderFor(di);
|
||||||
|
|
||||||
|
await di.runSetups();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders w/o errors", () => {
|
||||||
|
const model = getOnePodViewModel("foobar");
|
||||||
|
const { container } = render(
|
||||||
|
<LogSearch
|
||||||
|
model={model}
|
||||||
|
scrollToOverlay={jest.fn()}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(container).toBeInstanceOf(HTMLElement);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should scroll to new active overlay when clicking the previous button", async () => {
|
||||||
|
const scrollToOverlay = jest.fn();
|
||||||
|
const model = getOnePodViewModel("foobar", {
|
||||||
|
getLogsWithoutTimestamps: () => [
|
||||||
|
"hello",
|
||||||
|
"world",
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
render(
|
||||||
|
<LogSearch
|
||||||
|
model={model}
|
||||||
|
scrollToOverlay={scrollToOverlay}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
userEvent.click(await screen.findByPlaceholderText("Search..."));
|
||||||
|
userEvent.keyboard("o");
|
||||||
|
userEvent.click(await screen.findByText("keyboard_arrow_up"));
|
||||||
|
expect(scrollToOverlay).toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should scroll to new active overlay when clicking the next button", async () => {
|
||||||
|
const scrollToOverlay = jest.fn();
|
||||||
|
const model = getOnePodViewModel("foobar", {
|
||||||
|
getLogsWithoutTimestamps: () => [
|
||||||
|
"hello",
|
||||||
|
"world",
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
render(
|
||||||
|
<LogSearch
|
||||||
|
model={model}
|
||||||
|
scrollToOverlay={scrollToOverlay}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
userEvent.click(await screen.findByPlaceholderText("Search..."));
|
||||||
|
userEvent.keyboard("o");
|
||||||
|
userEvent.click(await screen.findByText("keyboard_arrow_down"));
|
||||||
|
expect(scrollToOverlay).toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("next and previous should be disabled initially", async () => {
|
||||||
|
const scrollToOverlay = jest.fn();
|
||||||
|
const model = getOnePodViewModel("foobar", {
|
||||||
|
getLogsWithoutTimestamps: () => [
|
||||||
|
"hello",
|
||||||
|
"world",
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
render(
|
||||||
|
<LogSearch
|
||||||
|
model={model}
|
||||||
|
scrollToOverlay={scrollToOverlay}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
userEvent.click(await screen.findByText("keyboard_arrow_down"));
|
||||||
|
userEvent.click(await screen.findByText("keyboard_arrow_up"));
|
||||||
|
expect(scrollToOverlay).not.toBeCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,146 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { podsStore } from "../../../+workloads-pods/pods.store";
|
|
||||||
import { UserStore } from "../../../../../common/user-store";
|
|
||||||
import { Pod } from "../../../../../common/k8s-api/endpoints";
|
|
||||||
import { ThemeStore } from "../../../../theme.store";
|
|
||||||
import { deploymentPod1, deploymentPod2, deploymentPod3, dockerPod } from "./pod.mock";
|
|
||||||
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 mockFs from "mock-fs";
|
|
||||||
|
|
||||||
mockWindow();
|
|
||||||
|
|
||||||
podsStore.items.push(new Pod(dockerPod));
|
|
||||||
podsStore.items.push(new Pod(deploymentPod1));
|
|
||||||
podsStore.items.push(new Pod(deploymentPod2));
|
|
||||||
|
|
||||||
describe("log tab store", () => {
|
|
||||||
let logTabStore: LogTabStore;
|
|
||||||
let dockStore: DockStore;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
|
||||||
|
|
||||||
mockFs();
|
|
||||||
|
|
||||||
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
|
||||||
|
|
||||||
await di.runSetups();
|
|
||||||
|
|
||||||
dockStore = di.inject(dockStoreInjectable);
|
|
||||||
logTabStore = di.inject(logTabStoreInjectable);
|
|
||||||
|
|
||||||
UserStore.createInstance();
|
|
||||||
ThemeStore.createInstance();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
UserStore.resetInstance();
|
|
||||||
ThemeStore.resetInstance();
|
|
||||||
mockFs.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("creates log tab without sibling pods", () => {
|
|
||||||
const selectedPod = new Pod(dockerPod);
|
|
||||||
const selectedContainer = selectedPod.getAllContainers()[0];
|
|
||||||
|
|
||||||
logTabStore.createPodTab({
|
|
||||||
selectedPod,
|
|
||||||
selectedContainer,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(logTabStore.getData(dockStore.selectedTabId)).toEqual({
|
|
||||||
pods: [selectedPod],
|
|
||||||
selectedPod,
|
|
||||||
selectedContainer,
|
|
||||||
showTimestamps: false,
|
|
||||||
previous: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("creates log tab with sibling pods", () => {
|
|
||||||
const selectedPod = new Pod(deploymentPod1);
|
|
||||||
const siblingPod = new Pod(deploymentPod2);
|
|
||||||
const selectedContainer = selectedPod.getInitContainers()[0];
|
|
||||||
|
|
||||||
logTabStore.createPodTab({
|
|
||||||
selectedPod,
|
|
||||||
selectedContainer,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(logTabStore.getData(dockStore.selectedTabId)).toEqual({
|
|
||||||
pods: [selectedPod, siblingPod],
|
|
||||||
selectedPod,
|
|
||||||
selectedContainer,
|
|
||||||
showTimestamps: false,
|
|
||||||
previous: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("removes item from pods list if pod deleted from store", () => {
|
|
||||||
const selectedPod = new Pod(deploymentPod1);
|
|
||||||
const selectedContainer = selectedPod.getInitContainers()[0];
|
|
||||||
|
|
||||||
logTabStore.createPodTab({
|
|
||||||
selectedPod,
|
|
||||||
selectedContainer,
|
|
||||||
});
|
|
||||||
|
|
||||||
podsStore.items.pop();
|
|
||||||
|
|
||||||
expect(logTabStore.getData(dockStore.selectedTabId)).toEqual({
|
|
||||||
pods: [selectedPod],
|
|
||||||
selectedPod,
|
|
||||||
selectedContainer,
|
|
||||||
showTimestamps: false,
|
|
||||||
previous: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("adds item into pods list if new sibling pod added to store", () => {
|
|
||||||
const selectedPod = new Pod(deploymentPod1);
|
|
||||||
const selectedContainer = selectedPod.getInitContainers()[0];
|
|
||||||
|
|
||||||
logTabStore.createPodTab({
|
|
||||||
selectedPod,
|
|
||||||
selectedContainer,
|
|
||||||
});
|
|
||||||
|
|
||||||
podsStore.items.push(new Pod(deploymentPod3));
|
|
||||||
|
|
||||||
expect(logTabStore.getData(dockStore.selectedTabId)).toEqual({
|
|
||||||
pods: [selectedPod, deploymentPod3],
|
|
||||||
selectedPod,
|
|
||||||
selectedContainer,
|
|
||||||
showTimestamps: false,
|
|
||||||
previous: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// FIXME: this is failed when it's not .only == depends on something above
|
|
||||||
it.only("closes tab if no pods left in store", async () => {
|
|
||||||
const selectedPod = new Pod(deploymentPod1);
|
|
||||||
const selectedContainer = selectedPod.getInitContainers()[0];
|
|
||||||
|
|
||||||
const id = logTabStore.createPodTab({
|
|
||||||
selectedPod,
|
|
||||||
selectedContainer,
|
|
||||||
});
|
|
||||||
|
|
||||||
podsStore.items.clear();
|
|
||||||
|
|
||||||
expect(logTabStore.getData(dockStore.selectedTabId)).toBeUndefined();
|
|
||||||
expect(logTabStore.getData(id)).toBeUndefined();
|
|
||||||
expect(dockStore.getTabById(id)).toBeUndefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* 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 { TabId } from "../dock/store";
|
||||||
|
import logStoreInjectable from "./store.injectable";
|
||||||
|
|
||||||
|
const areLogsPresentInjectable = getInjectable({
|
||||||
|
instantiate: (di) => {
|
||||||
|
const logStore = di.inject(logStoreInjectable);
|
||||||
|
|
||||||
|
return (tabId: TabId) => logStore.areLogsPresent(tabId);
|
||||||
|
},
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default areLogsPresentInjectable;
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import type { TabId } from "../dock/store";
|
||||||
|
import logTabStoreInjectable from "./tab-store.injectable";
|
||||||
|
|
||||||
|
const clearLogTabDataInjectable = getInjectable({
|
||||||
|
instantiate: (di) => {
|
||||||
|
const logTabStore = di.inject(logTabStoreInjectable);
|
||||||
|
|
||||||
|
return (tabId: TabId): void => {
|
||||||
|
logTabStore.clearData(tabId);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default clearLogTabDataInjectable;
|
||||||
@ -8,17 +8,22 @@ import "./controls.scss";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
|
|
||||||
import { Pod } from "../../../../common/k8s-api/endpoints";
|
import { cssNames } from "../../../utils";
|
||||||
import { cssNames, saveFileDialog } from "../../../utils";
|
|
||||||
import { Checkbox } from "../../checkbox";
|
import { Checkbox } from "../../checkbox";
|
||||||
import { Icon } from "../../icon";
|
import { Icon } from "../../icon";
|
||||||
import type { LogTabViewModel } from "./logs-view-model";
|
import type { LogTabViewModel } from "./logs-view-model";
|
||||||
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
|
import openSaveFileDialogInjectable from "../../../utils/save-file.injectable";
|
||||||
|
|
||||||
export interface LogControlsProps {
|
export interface LogControlsProps {
|
||||||
model: LogTabViewModel;
|
model: LogTabViewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LogControls = observer(({ model }: LogControlsProps) => {
|
interface Dependencies {
|
||||||
|
openSaveFileDialog: (filename: string, contents: BlobPart | BlobPart[], type: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NonInjectedLogControls = observer(({ openSaveFileDialog, model }: Dependencies & LogControlsProps) => {
|
||||||
const tabData = model.logTabData.get();
|
const tabData = model.logTabData.get();
|
||||||
|
|
||||||
if (!tabData) {
|
if (!tabData) {
|
||||||
@ -26,26 +31,25 @@ export const LogControls = observer(({ model }: LogControlsProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const logs = model.timestampSplitLogs.get();
|
const logs = model.timestampSplitLogs.get();
|
||||||
const { showTimestamps, previous } = tabData;
|
const { showTimestamps, showPrevious: previous } = tabData;
|
||||||
const since = logs.length ? logs[0][0] : null;
|
const since = logs.length ? logs[0][0] : null;
|
||||||
const pod = new Pod(tabData.selectedPod);
|
|
||||||
|
|
||||||
const toggleTimestamps = () => {
|
const toggleTimestamps = () => {
|
||||||
model.updateLogTabData({ showTimestamps: !showTimestamps });
|
model.updateLogTabData({ showTimestamps: !showTimestamps });
|
||||||
};
|
};
|
||||||
|
|
||||||
const togglePrevious = () => {
|
const togglePrevious = () => {
|
||||||
model.updateLogTabData({ previous: !previous });
|
model.updateLogTabData({ showPrevious: !previous });
|
||||||
model.reloadLogs();
|
model.reloadLogs();
|
||||||
};
|
};
|
||||||
|
|
||||||
const downloadLogs = () => {
|
const downloadLogs = () => {
|
||||||
const fileName = pod.getName();
|
const fileName = model.pod.get().getName();
|
||||||
const logsToDownload: string[] = showTimestamps
|
const logsToDownload: string[] = showTimestamps
|
||||||
? model.logs.get()
|
? model.logs.get()
|
||||||
: model.logsWithoutTimestamps.get();
|
: model.logsWithoutTimestamps.get();
|
||||||
|
|
||||||
saveFileDialog(`${fileName}.log`, logsToDownload.join("\n"), "text/plain");
|
openSaveFileDialog(`${fileName}.log`, logsToDownload.join("\n"), "text/plain");
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -81,3 +85,10 @@ export const LogControls = observer(({ model }: LogControlsProps) => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const LogControls = withInjectables<Dependencies, LogControlsProps>(NonInjectedLogControls, {
|
||||||
|
getProps: (di, props) => ({
|
||||||
|
openSaveFileDialog: di.inject(openSaveFileDialogInjectable),
|
||||||
|
...props,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|||||||
@ -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 { 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, "owner" | "selectedPodId" | "selectedContainer" | "namespace"> & Omit<Partial<LogTabData>, "owner" | "selectedPodId" | "selectedContainer" | "namespace">;
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
createDockTab: (rawTabDesc: DockTabCreate, addNumber?: boolean) => DockTab;
|
||||||
|
setLogTabData: (tabId: string, data: LogTabData) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const 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) => createLogsTab({
|
||||||
|
createDockTab: di.inject(createDockTabInjectable),
|
||||||
|
setLogTabData: di.inject(setLogTabDataInjectable),
|
||||||
|
}),
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default createLogsTabInjectable;
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* 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 { IPodContainer, Pod } from "../../../../common/k8s-api/endpoints";
|
||||||
|
import type { TabId } from "../dock/store";
|
||||||
|
import createLogsTabInjectable from "./create-logs-tab.injectable";
|
||||||
|
|
||||||
|
export interface PodLogsTabData {
|
||||||
|
selectedPod: Pod;
|
||||||
|
selectedContainer: IPodContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
const createPodLogsTabInjectable = getInjectable({
|
||||||
|
instantiate: (di) => {
|
||||||
|
const createLogsTab = di.inject(createLogsTabInjectable);
|
||||||
|
|
||||||
|
return ({ selectedPod, selectedContainer }: PodLogsTabData): TabId =>
|
||||||
|
createLogsTab(`Pod ${selectedPod.getName()}`, {
|
||||||
|
owner: selectedPod.getOwnerRefs()[0],
|
||||||
|
namespace: selectedPod.getNs(),
|
||||||
|
selectedContainer: selectedContainer.name,
|
||||||
|
selectedPodId: selectedPod.getId(),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default createPodLogsTabInjectable;
|
||||||
@ -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 { podsStore } from "../../+workloads-pods/pods.store";
|
||||||
|
import type { WorkloadKubeObject } from "../../../../common/k8s-api/workload-kube-object";
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
const 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(),
|
||||||
|
owner: {
|
||||||
|
kind: workload.kind,
|
||||||
|
name: workload.getName(),
|
||||||
|
uid: workload.getId(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const createWorkloadLogsTabInjectable = getInjectable({
|
||||||
|
instantiate: (di) => createWorkloadLogsTab({
|
||||||
|
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,16 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import type { LogTabData } from "./tab-store";
|
||||||
import logTabStoreInjectable from "./tab-store.injectable";
|
import logTabStoreInjectable from "./tab-store.injectable";
|
||||||
|
|
||||||
const getLogTabDataInjectable = getInjectable({
|
const getLogTabDataInjectable = getInjectable({
|
||||||
instantiate: (di) => di.inject(logTabStoreInjectable).getData,
|
instantiate: (di) => {
|
||||||
|
const logTabStore = di.inject(logTabStoreInjectable);
|
||||||
|
|
||||||
|
return (tabId: string): LogTabData => logTabStore.getData(tabId);
|
||||||
|
},
|
||||||
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,13 @@ import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
|||||||
import logStoreInjectable from "./store.injectable";
|
import logStoreInjectable from "./store.injectable";
|
||||||
|
|
||||||
const getLogsWithoutTimestampsInjectable = getInjectable({
|
const getLogsWithoutTimestampsInjectable = getInjectable({
|
||||||
instantiate: (di) => di.inject(logStoreInjectable).getLogsWithoutTimestampsByTabId,
|
instantiate: (di) => {
|
||||||
|
const logStore = di.inject(logStoreInjectable);
|
||||||
|
|
||||||
|
return (tabId: string): string[] =>
|
||||||
|
logStore.getLogsWithoutTimestamps(tabId);
|
||||||
|
},
|
||||||
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,12 @@ import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
|||||||
import logStoreInjectable from "./store.injectable";
|
import logStoreInjectable from "./store.injectable";
|
||||||
|
|
||||||
const getLogsInjectable = getInjectable({
|
const getLogsInjectable = getInjectable({
|
||||||
instantiate: (di) => di.inject(logStoreInjectable).getLogsByTabId,
|
instantiate: (di) => {
|
||||||
|
const logStore = di.inject(logStoreInjectable);
|
||||||
|
|
||||||
|
return (tabId: string): string[] => logStore.getLogs(tabId);
|
||||||
|
},
|
||||||
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,13 @@ import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
|||||||
import logStoreInjectable from "./store.injectable";
|
import logStoreInjectable from "./store.injectable";
|
||||||
|
|
||||||
const getTimestampSplitLogsInjectable = getInjectable({
|
const getTimestampSplitLogsInjectable = getInjectable({
|
||||||
instantiate: (di) => di.inject(logStoreInjectable).getTimestampSplitLogsByTabId,
|
instantiate: (di) => {
|
||||||
|
const logStore = di.inject(logStoreInjectable);
|
||||||
|
|
||||||
|
return (tabId: string): [string, string][] =>
|
||||||
|
logStore.getTimestampSplitLogs(tabId);
|
||||||
|
},
|
||||||
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* 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 { TabId } from "../dock/store";
|
||||||
|
import logTabStoreInjectable from "./tab-store.injectable";
|
||||||
|
|
||||||
|
const isLogsTabDataValidInjectable = getInjectable({
|
||||||
|
instantiate: (di) => {
|
||||||
|
const logTabStore = di.inject(logTabStoreInjectable);
|
||||||
|
|
||||||
|
return (tabId: TabId) => logTabStore.isDataValid(tabId);
|
||||||
|
},
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default isLogsTabDataValidInjectable;
|
||||||
@ -19,6 +19,7 @@ import { array, boundMethod, cssNames } from "../../../utils";
|
|||||||
import { VirtualList } from "../../virtual-list";
|
import { VirtualList } from "../../virtual-list";
|
||||||
import { ToBottom } from "./to-bottom";
|
import { ToBottom } from "./to-bottom";
|
||||||
import type { LogTabViewModel } from "../logs/logs-view-model";
|
import type { LogTabViewModel } from "../logs/logs-view-model";
|
||||||
|
import { Spinner } from "../../spinner";
|
||||||
|
|
||||||
export interface LogListProps {
|
export interface LogListProps {
|
||||||
model: LogTabViewModel;
|
model: LogTabViewModel;
|
||||||
@ -210,12 +211,18 @@ export class LogList extends React.Component<LogListProps> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const rowHeights = array.filled(this.logs.length, this.lineHeight);
|
if (this.props.model.isLoading.get()) {
|
||||||
|
return (
|
||||||
|
<div className="LogList flex box grow align-center justify-center">
|
||||||
|
<Spinner />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.logs.length) {
|
if (!this.logs.length) {
|
||||||
return (
|
return (
|
||||||
<div className="LogList flex box grow align-center justify-center">
|
<div className="LogList flex box grow align-center justify-center">
|
||||||
There are no logs available for container
|
There are no logs available for container {this.props.model.logTabData.get()?.selectedContainer}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -224,7 +231,7 @@ export class LogList extends React.Component<LogListProps> {
|
|||||||
<div className={cssNames("LogList flex" )}>
|
<div className={cssNames("LogList flex" )}>
|
||||||
<VirtualList
|
<VirtualList
|
||||||
items={this.logs}
|
items={this.logs}
|
||||||
rowHeights={rowHeights}
|
rowHeights={array.filled(this.logs.length, this.lineHeight)}
|
||||||
getRow={this.getLogRow}
|
getRow={this.getLogRow}
|
||||||
onScroll={this.onScroll}
|
onScroll={this.onScroll}
|
||||||
outerRef={this.virtualListDiv}
|
outerRef={this.virtualListDiv}
|
||||||
|
|||||||
@ -3,10 +3,22 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import type { IComputedValue } from "mobx";
|
||||||
|
import type { Pod } from "../../../../common/k8s-api/endpoints";
|
||||||
import logStoreInjectable from "./store.injectable";
|
import logStoreInjectable from "./store.injectable";
|
||||||
|
import type { LogTabData } from "./tab-store";
|
||||||
|
|
||||||
const loadLogsInjectable = getInjectable({
|
const loadLogsInjectable = getInjectable({
|
||||||
instantiate: (di) => di.inject(logStoreInjectable).load,
|
instantiate: (di) => {
|
||||||
|
const logStore = di.inject(logStoreInjectable);
|
||||||
|
|
||||||
|
return (
|
||||||
|
tabId: string,
|
||||||
|
pod: IComputedValue<Pod | undefined>,
|
||||||
|
logTabData: IComputedValue<LogTabData>,
|
||||||
|
): Promise<void> => logStore.load(tabId, pod, logTabData);
|
||||||
|
},
|
||||||
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
38
src/renderer/components/dock/logs/log-tab-data.validator.ts
Normal file
38
src/renderer/components/dock/logs/log-tab-data.validator.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import Joi from "joi";
|
||||||
|
import type { LogTabData, LogTabOwnerRef } from "./tab-store";
|
||||||
|
|
||||||
|
export const logTabDataValidator = Joi.object<LogTabData>({
|
||||||
|
owner: Joi
|
||||||
|
.object<LogTabOwnerRef>({
|
||||||
|
uid: Joi
|
||||||
|
.string()
|
||||||
|
.required(),
|
||||||
|
name: Joi
|
||||||
|
.string()
|
||||||
|
.required(),
|
||||||
|
kind: Joi
|
||||||
|
.string()
|
||||||
|
.required(),
|
||||||
|
})
|
||||||
|
.unknown(true)
|
||||||
|
.optional(),
|
||||||
|
selectedPodId: Joi
|
||||||
|
.string()
|
||||||
|
.required(),
|
||||||
|
namespace: Joi
|
||||||
|
.string()
|
||||||
|
.required(),
|
||||||
|
selectedContainer: Joi
|
||||||
|
.string()
|
||||||
|
.optional(),
|
||||||
|
showTimestamps: Joi
|
||||||
|
.boolean()
|
||||||
|
.required(),
|
||||||
|
showPrevious: Joi
|
||||||
|
.boolean()
|
||||||
|
.required(),
|
||||||
|
});
|
||||||
@ -4,16 +4,19 @@
|
|||||||
*/
|
*/
|
||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import { LogTabViewModel } from "./logs-view-model";
|
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 getLogsInjectable from "./get-logs.injectable";
|
||||||
import getLogsWithoutTimestampsInjectable from "./get-logs-without-timestamps.injectable";
|
import getLogsWithoutTimestampsInjectable from "./get-logs-without-timestamps.injectable";
|
||||||
import getTimestampSplitLogsInjectable from "./get-timestamp-split-logs.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 getLogTabDataInjectable from "./get-log-tab-data.injectable";
|
||||||
import loadLogsInjectable from "./load-logs.injectable";
|
import loadLogsInjectable from "./load-logs.injectable";
|
||||||
import setLogTabDataInjectable from "./set-log-tab-data.injectable";
|
import setLogTabDataInjectable from "./set-log-tab-data.injectable";
|
||||||
import updateTabNameInjectable from "./update-tab-name.injectable";
|
|
||||||
import stopLoadingLogsInjectable from "./stop-loading-logs.injectable";
|
import stopLoadingLogsInjectable from "./stop-loading-logs.injectable";
|
||||||
|
import { podsStore } from "../../+workloads-pods/pods.store";
|
||||||
|
import renameTabInjectable from "../dock/rename-tab.injectable";
|
||||||
|
import areLogsPresentInjectable from "./are-logs-present.injectable";
|
||||||
|
import searchStoreInjectable from "../../../search-store/search-store.injectable";
|
||||||
|
|
||||||
export interface InstantiateArgs {
|
export interface InstantiateArgs {
|
||||||
tabId: TabId;
|
tabId: TabId;
|
||||||
@ -24,12 +27,16 @@ const logsViewModelInjectable = getInjectable({
|
|||||||
getLogs: di.inject(getLogsInjectable),
|
getLogs: di.inject(getLogsInjectable),
|
||||||
getLogsWithoutTimestamps: di.inject(getLogsWithoutTimestampsInjectable),
|
getLogsWithoutTimestamps: di.inject(getLogsWithoutTimestampsInjectable),
|
||||||
getTimestampSplitLogs: di.inject(getTimestampSplitLogsInjectable),
|
getTimestampSplitLogs: di.inject(getTimestampSplitLogsInjectable),
|
||||||
reloadLogs: di.inject(reloadLoadsInjectable),
|
reloadLogs: di.inject(reloadLogsInjectable),
|
||||||
getLogTabData: di.inject(getLogTabDataInjectable),
|
getLogTabData: di.inject(getLogTabDataInjectable),
|
||||||
setLogTabData: di.inject(setLogTabDataInjectable),
|
setLogTabData: di.inject(setLogTabDataInjectable),
|
||||||
loadLogs: di.inject(loadLogsInjectable),
|
loadLogs: di.inject(loadLogsInjectable),
|
||||||
updateTabName: di.inject(updateTabNameInjectable),
|
renameTab: di.inject(renameTabInjectable),
|
||||||
stopLoadingLogs: di.inject(stopLoadingLogsInjectable),
|
stopLoadingLogs: di.inject(stopLoadingLogsInjectable),
|
||||||
|
areLogsPresent: di.inject(areLogsPresentInjectable),
|
||||||
|
getPodById: id => podsStore.getById(id),
|
||||||
|
getPodsByOwnerId: id => podsStore.getPodsByOwnerId(id),
|
||||||
|
searchStore: di.inject(searchStoreInjectable),
|
||||||
}),
|
}),
|
||||||
lifecycle: lifecycleEnum.transient,
|
lifecycle: lifecycleEnum.transient,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2,10 +2,11 @@
|
|||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import type { LogTabData } from "./tab.store";
|
import type { LogTabData } from "./tab-store";
|
||||||
import { computed, IComputedValue } from "mobx";
|
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 { SearchStore } from "../../../search-store/search-store";
|
||||||
|
import type { Pod } from "../../../../common/k8s-api/endpoints";
|
||||||
|
|
||||||
export interface LogTabViewModelDependencies {
|
export interface LogTabViewModelDependencies {
|
||||||
getLogs: (tabId: TabId) => string[];
|
getLogs: (tabId: TabId) => string[];
|
||||||
@ -13,27 +14,57 @@ export interface LogTabViewModelDependencies {
|
|||||||
getTimestampSplitLogs: (tabId: TabId) => [string, string][];
|
getTimestampSplitLogs: (tabId: TabId) => [string, string][];
|
||||||
getLogTabData: (tabId: TabId) => LogTabData;
|
getLogTabData: (tabId: TabId) => LogTabData;
|
||||||
setLogTabData: (tabId: TabId, data: LogTabData) => void;
|
setLogTabData: (tabId: TabId, data: LogTabData) => void;
|
||||||
loadLogs: (tabId: TabId, logTabData: IComputedValue<LogTabData>) => Promise<void>;
|
loadLogs: (tabId: TabId, pod: IComputedValue<Pod | undefined>, logTabData: IComputedValue<LogTabData>) => Promise<void>;
|
||||||
reloadLogs: (tabId: TabId, logTabData: IComputedValue<LogTabData>) => Promise<void>;
|
reloadLogs: (tabId: TabId, pod: IComputedValue<Pod | undefined>, logTabData: IComputedValue<LogTabData>) => Promise<void>;
|
||||||
updateTabName: (tabId: TabId) => void;
|
renameTab: (tabId: TabId, title: string) => void;
|
||||||
stopLoadingLogs: (tabId: TabId) => void;
|
stopLoadingLogs: (tabId: TabId) => void;
|
||||||
|
getPodById: (id: string) => Pod | undefined;
|
||||||
|
getPodsByOwnerId: (id: string) => Pod[];
|
||||||
|
areLogsPresent: (tabId: TabId) => boolean;
|
||||||
|
searchStore: SearchStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class LogTabViewModel {
|
export class LogTabViewModel {
|
||||||
constructor(protected readonly tabId: TabId, private readonly dependencies: LogTabViewModelDependencies) {}
|
constructor(protected readonly tabId: TabId, private readonly dependencies: LogTabViewModelDependencies) {}
|
||||||
|
|
||||||
|
get searchStore() {
|
||||||
|
return this.dependencies.searchStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly isLoading = computed(() => this.dependencies.areLogsPresent(this.tabId));
|
||||||
readonly logs = computed(() => this.dependencies.getLogs(this.tabId));
|
readonly logs = computed(() => this.dependencies.getLogs(this.tabId));
|
||||||
readonly logsWithoutTimestamps = computed(() => this.dependencies.getLogsWithoutTimestamps(this.tabId));
|
readonly logsWithoutTimestamps = computed(() => this.dependencies.getLogsWithoutTimestamps(this.tabId));
|
||||||
readonly timestampSplitLogs = computed(() => this.dependencies.getTimestampSplitLogs(this.tabId));
|
readonly timestampSplitLogs = computed(() => this.dependencies.getTimestampSplitLogs(this.tabId));
|
||||||
readonly logTabData = computed(() => this.dependencies.getLogTabData(this.tabId));
|
readonly logTabData = computed(() => this.dependencies.getLogTabData(this.tabId));
|
||||||
readonly searchStore = new SearchStore();
|
readonly pods = computed(() => {
|
||||||
|
const data = this.logTabData.get();
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof data.owner?.uid === "string") {
|
||||||
|
return this.dependencies.getPodsByOwnerId(data.owner.uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [this.dependencies.getPodById(data.selectedPodId)];
|
||||||
|
});
|
||||||
|
readonly pod = computed(() => {
|
||||||
|
const data = this.logTabData.get();
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.dependencies.getPodById(data.selectedPodId);
|
||||||
|
});
|
||||||
|
|
||||||
updateLogTabData = (partialData: Partial<LogTabData>) => {
|
updateLogTabData = (partialData: Partial<LogTabData>) => {
|
||||||
this.dependencies.setLogTabData(this.tabId, { ...this.logTabData.get(), ...partialData });
|
this.dependencies.setLogTabData(this.tabId, { ...this.logTabData.get(), ...partialData });
|
||||||
};
|
};
|
||||||
|
|
||||||
loadLogs = () => this.dependencies.loadLogs(this.tabId, this.logTabData);
|
loadLogs = () => this.dependencies.loadLogs(this.tabId, this.pod, this.logTabData);
|
||||||
reloadLogs = () => this.dependencies.reloadLogs(this.tabId, this.logTabData);
|
reloadLogs = () => this.dependencies.reloadLogs(this.tabId, this.pod, this.logTabData);
|
||||||
updateTabName = () => this.dependencies.updateTabName(this.tabId);
|
renameTab = (title: string) => this.dependencies.renameTab(this.tabId, title);
|
||||||
stopLoadingLogs = () => this.dependencies.stopLoadingLogs(this.tabId);
|
stopLoadingLogs = () => this.dependencies.stopLoadingLogs(this.tabId);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,11 +3,23 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import type { IComputedValue } from "mobx";
|
||||||
|
import type { Pod } from "../../../../common/k8s-api/endpoints";
|
||||||
import logStoreInjectable from "./store.injectable";
|
import logStoreInjectable from "./store.injectable";
|
||||||
|
import type { LogTabData } from "./tab-store";
|
||||||
|
|
||||||
|
const reloadLogsInjectable = getInjectable({
|
||||||
|
instantiate: (di) => {
|
||||||
|
const logStore = di.inject(logStoreInjectable);
|
||||||
|
|
||||||
|
return (
|
||||||
|
tabId: string,
|
||||||
|
pod: IComputedValue<Pod | undefined>,
|
||||||
|
logTabData: IComputedValue<LogTabData>,
|
||||||
|
): Promise<void> => logStore.reload(tabId, pod, logTabData);
|
||||||
|
},
|
||||||
|
|
||||||
const reloadLoadsInjectable = getInjectable({
|
|
||||||
instantiate: (di) => di.inject(logStoreInjectable).reload,
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default reloadLoadsInjectable;
|
export default reloadLogsInjectable;
|
||||||
|
|||||||
@ -5,19 +5,25 @@
|
|||||||
|
|
||||||
import "./resource-selector.scss";
|
import "./resource-selector.scss";
|
||||||
|
|
||||||
import React, { useEffect } from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
|
|
||||||
import { Pod } from "../../../../common/k8s-api/endpoints";
|
|
||||||
import { Badge } from "../../badge";
|
import { Badge } from "../../badge";
|
||||||
import { Select, SelectOption } from "../../select";
|
import { Select, SelectOption } from "../../select";
|
||||||
import { podsStore } from "../../+workloads-pods/pods.store";
|
|
||||||
import type { LogTabViewModel } from "./logs-view-model";
|
import type { LogTabViewModel } from "./logs-view-model";
|
||||||
|
import type { IPodContainer, Pod } from "../../../../common/k8s-api/endpoints";
|
||||||
|
|
||||||
export interface LogResourceSelectorProps {
|
export interface LogResourceSelectorProps {
|
||||||
model: LogTabViewModel;
|
model: LogTabViewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getSelectOptions(containers: IPodContainer[]): SelectOption<string>[] {
|
||||||
|
return containers.map(container => ({
|
||||||
|
value: container.name,
|
||||||
|
label: container.name,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
export const LogResourceSelector = observer(({ model }: LogResourceSelectorProps) => {
|
export const LogResourceSelector = observer(({ model }: LogResourceSelectorProps) => {
|
||||||
const tabData = model.logTabData.get();
|
const tabData = model.logTabData.get();
|
||||||
|
|
||||||
@ -25,66 +31,61 @@ export const LogResourceSelector = observer(({ model }: LogResourceSelectorProps
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { selectedPod, selectedContainer, pods } = tabData;
|
const { selectedContainer, owner } = tabData;
|
||||||
const pod = new Pod(selectedPod);
|
const pods = model.pods.get();
|
||||||
const containers = pod.getContainers();
|
const pod = model.pod.get();
|
||||||
const initContainers = pod.getInitContainers();
|
|
||||||
|
|
||||||
const onContainerChange = (option: SelectOption) => {
|
if (!pod) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const onContainerChange = (option: SelectOption<string>) => {
|
||||||
model.updateLogTabData({
|
model.updateLogTabData({
|
||||||
selectedContainer: containers
|
selectedContainer: option.value,
|
||||||
.concat(initContainers)
|
|
||||||
.find(container => container.name === option.value),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
model.reloadLogs();
|
model.reloadLogs();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPodChange = (option: SelectOption) => {
|
const onPodChange = ({ value }: SelectOption<Pod>) => {
|
||||||
const selectedPod = podsStore.getByName(option.value, pod.getNs());
|
model.updateLogTabData({
|
||||||
|
selectedPodId: value.getId(),
|
||||||
model.updateLogTabData({ selectedPod });
|
selectedContainer: value.getAllContainers()[0]?.name,
|
||||||
model.updateTabName();
|
|
||||||
};
|
|
||||||
|
|
||||||
const getSelectOptions = (items: string[]) => {
|
|
||||||
return items.map(item => {
|
|
||||||
return {
|
|
||||||
value: item,
|
|
||||||
label: item,
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
model.renameTab(`Pod ${value.getName()}`);
|
||||||
|
model.reloadLogs();
|
||||||
};
|
};
|
||||||
|
|
||||||
const containerSelectOptions = [
|
const containerSelectOptions = [
|
||||||
{
|
{
|
||||||
label: `Containers`,
|
label: "Containers",
|
||||||
options: getSelectOptions(containers.map(container => container.name)),
|
options: getSelectOptions(pod.getContainers()),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: `Init Containers`,
|
label: "Init Containers",
|
||||||
options: getSelectOptions(initContainers.map(container => container.name)),
|
options: getSelectOptions(pod.getInitContainers()),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const podSelectOptions = [
|
const podSelectOptions = pods.map(pod => ({
|
||||||
{
|
label: pod.getName(),
|
||||||
label: pod.getOwnerRefs()[0]?.name,
|
value: pod,
|
||||||
options: getSelectOptions(pods.map(pod => pod.metadata.name)),
|
}));
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
model.reloadLogs();
|
|
||||||
}, [selectedPod]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="LogResourceSelector flex gaps align-center">
|
<div className="LogResourceSelector flex gaps align-center">
|
||||||
<span>Namespace</span> <Badge data-testid="namespace-badge" label={pod.getNs()}/>
|
<span>Namespace</span> <Badge data-testid="namespace-badge" label={pod.getNs()}/>
|
||||||
|
{
|
||||||
|
owner && (
|
||||||
|
<>
|
||||||
|
<span>Owner</span> <Badge data-testid="namespace-badge" label={`${owner.kind} ${owner.name}`}/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
<span>Pod</span>
|
<span>Pod</span>
|
||||||
<Select
|
<Select
|
||||||
options={podSelectOptions}
|
options={podSelectOptions}
|
||||||
value={{ label: pod.getName(), value: pod.getName() }}
|
value={podSelectOptions.find(opt => opt.value === pod)}
|
||||||
|
formatOptionLabel={option => option.label}
|
||||||
onChange={onPodChange}
|
onChange={onPodChange}
|
||||||
autoConvertOptions={false}
|
autoConvertOptions={false}
|
||||||
className="pod-selector"
|
className="pod-selector"
|
||||||
@ -93,7 +94,7 @@ export const LogResourceSelector = observer(({ model }: LogResourceSelectorProps
|
|||||||
<span>Container</span>
|
<span>Container</span>
|
||||||
<Select
|
<Select
|
||||||
options={containerSelectOptions}
|
options={containerSelectOptions}
|
||||||
value={{ label: selectedContainer.name, value: selectedContainer.name }}
|
value={{ label: selectedContainer, value: selectedContainer }}
|
||||||
onChange={onContainerChange}
|
onChange={onContainerChange}
|
||||||
autoConvertOptions={false}
|
autoConvertOptions={false}
|
||||||
className="container-selector"
|
className="container-selector"
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user