mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge remote-tracking branch 'origin/master' into webpack5-upgrade-attempt
# Conflicts: # package.json # yarn.lock
This commit is contained in:
commit
c0559bf772
106
.eslintrc.js
106
.eslintrc.js
@ -11,17 +11,12 @@ module.exports = {
|
|||||||
"**/dist/**/*",
|
"**/dist/**/*",
|
||||||
"**/static/**/*",
|
"**/static/**/*",
|
||||||
"**/site/**/*",
|
"**/site/**/*",
|
||||||
|
"extensions/*/*.tgz",
|
||||||
],
|
],
|
||||||
settings: {
|
settings: {
|
||||||
react: {
|
react: {
|
||||||
version: packageJson.devDependencies.react || "detect",
|
version: packageJson.devDependencies.react || "detect",
|
||||||
},
|
},
|
||||||
// the package eslint-import-resolver-typescript is required for this line which fixes errors when using .d.ts files
|
|
||||||
"import/resolver": {
|
|
||||||
"typescript": {
|
|
||||||
"alwaysTryTypes": true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
@ -95,95 +90,9 @@ module.exports = {
|
|||||||
{
|
{
|
||||||
files: [
|
files: [
|
||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
],
|
|
||||||
parser: "@typescript-eslint/parser",
|
|
||||||
extends: [
|
|
||||||
"eslint:recommended",
|
|
||||||
"plugin:@typescript-eslint/recommended",
|
|
||||||
"plugin:import/recommended",
|
|
||||||
"plugin:import/typescript",
|
|
||||||
],
|
|
||||||
plugins: [
|
|
||||||
"header",
|
|
||||||
"unused-imports",
|
|
||||||
],
|
|
||||||
parserOptions: {
|
|
||||||
ecmaVersion: 2018,
|
|
||||||
sourceType: "module",
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
"no-constant-condition": ["error", { "checkLoops": false }],
|
|
||||||
"header/header": [2, "./license-header"],
|
|
||||||
"no-invalid-this": "off",
|
|
||||||
"@typescript-eslint/no-invalid-this": ["error"],
|
|
||||||
"@typescript-eslint/explicit-function-return-type": "off",
|
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
|
||||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
|
||||||
"@typescript-eslint/ban-types": "off",
|
|
||||||
"@typescript-eslint/ban-ts-comment": "off",
|
|
||||||
"@typescript-eslint/no-empty-interface": "off",
|
|
||||||
"@typescript-eslint/no-unused-vars": "off",
|
|
||||||
"space-before-function-paren": "off",
|
|
||||||
"@typescript-eslint/space-before-function-paren": ["error", {
|
|
||||||
"anonymous": "always",
|
|
||||||
"named": "never",
|
|
||||||
"asyncArrow": "always",
|
|
||||||
}],
|
|
||||||
"unused-imports/no-unused-imports-ts": process.env.PROD === "true" ? "error" : "warn",
|
|
||||||
"unused-imports/no-unused-vars-ts": [
|
|
||||||
"warn", {
|
|
||||||
"vars": "all",
|
|
||||||
"args": "after-used",
|
|
||||||
"ignoreRestSiblings": true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"comman-dangle": "off",
|
|
||||||
"@typescript-eslint/comma-dangle": ["error", "always-multiline"],
|
|
||||||
"comma-spacing": "off",
|
|
||||||
"@typescript-eslint/comma-spacing": "error",
|
|
||||||
"indent": ["error", 2, {
|
|
||||||
"SwitchCase": 1,
|
|
||||||
}],
|
|
||||||
"quotes": ["error", "double", {
|
|
||||||
"avoidEscape": true,
|
|
||||||
"allowTemplateLiterals": true,
|
|
||||||
}],
|
|
||||||
"object-curly-spacing": "off",
|
|
||||||
"@typescript-eslint/object-curly-spacing": ["error", "always", {
|
|
||||||
"objectsInObjects": false,
|
|
||||||
"arraysInObjects": true,
|
|
||||||
}],
|
|
||||||
"react/prop-types": "off",
|
|
||||||
"semi": "off",
|
|
||||||
"@typescript-eslint/semi": ["error"],
|
|
||||||
"linebreak-style": ["error", "unix"],
|
|
||||||
"eol-last": ["error", "always"],
|
|
||||||
"object-shorthand": "error",
|
|
||||||
"prefer-template": "error",
|
|
||||||
"template-curly-spacing": "error",
|
|
||||||
"no-unused-expressions": "off",
|
|
||||||
"@typescript-eslint/no-unused-expressions": "error",
|
|
||||||
"padding-line-between-statements": [
|
|
||||||
"error",
|
|
||||||
{ "blankLine": "always", "prev": "*", "next": "return" },
|
|
||||||
{ "blankLine": "always", "prev": "*", "next": "block-like" },
|
|
||||||
{ "blankLine": "always", "prev": "*", "next": "function" },
|
|
||||||
{ "blankLine": "always", "prev": "*", "next": "class" },
|
|
||||||
{ "blankLine": "always", "prev": ["const", "let", "var"], "next": "*" },
|
|
||||||
{ "blankLine": "any", "prev": ["const", "let", "var"], "next": ["const", "let", "var"] },
|
|
||||||
],
|
|
||||||
"no-template-curly-in-string": "error",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: [
|
|
||||||
"**/*.tsx",
|
"**/*.tsx",
|
||||||
],
|
],
|
||||||
parser: "@typescript-eslint/parser",
|
parser: "@typescript-eslint/parser",
|
||||||
plugins: [
|
|
||||||
"header",
|
|
||||||
"unused-imports",
|
|
||||||
],
|
|
||||||
extends: [
|
extends: [
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
"plugin:@typescript-eslint/recommended",
|
"plugin:@typescript-eslint/recommended",
|
||||||
@ -191,13 +100,19 @@ module.exports = {
|
|||||||
"plugin:import/recommended",
|
"plugin:import/recommended",
|
||||||
"plugin:import/typescript",
|
"plugin:import/typescript",
|
||||||
],
|
],
|
||||||
|
plugins: [
|
||||||
|
"header",
|
||||||
|
"unused-imports",
|
||||||
|
"react-hooks",
|
||||||
|
],
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
ecmaVersion: 2018,
|
ecmaVersion: 2018,
|
||||||
sourceType: "module",
|
sourceType: "module",
|
||||||
jsx: true,
|
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
"no-constant-condition": ["error", { "checkLoops": false }],
|
"no-constant-condition": ["error", {
|
||||||
|
"checkLoops": false,
|
||||||
|
}],
|
||||||
"header/header": [2, "./license-header"],
|
"header/header": [2, "./license-header"],
|
||||||
"react/prop-types": "off",
|
"react/prop-types": "off",
|
||||||
"no-invalid-this": "off",
|
"no-invalid-this": "off",
|
||||||
@ -211,9 +126,10 @@ module.exports = {
|
|||||||
"@typescript-eslint/ban-ts-ignore": "off",
|
"@typescript-eslint/ban-ts-ignore": "off",
|
||||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||||
"@typescript-eslint/ban-types": "off",
|
"@typescript-eslint/ban-types": "off",
|
||||||
|
"@typescript-eslint/ban-ts-comment": "off",
|
||||||
"@typescript-eslint/no-empty-function": "off",
|
"@typescript-eslint/no-empty-function": "off",
|
||||||
"react/display-name": "off",
|
|
||||||
"@typescript-eslint/no-unused-vars": "off",
|
"@typescript-eslint/no-unused-vars": "off",
|
||||||
|
"react/display-name": "off",
|
||||||
"space-before-function-paren": "off",
|
"space-before-function-paren": "off",
|
||||||
"@typescript-eslint/space-before-function-paren": ["error", {
|
"@typescript-eslint/space-before-function-paren": ["error", {
|
||||||
"anonymous": "always",
|
"anonymous": "always",
|
||||||
|
|||||||
@ -2,6 +2,4 @@
|
|||||||
* 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.
|
||||||
*/
|
*/
|
||||||
|
export default {};
|
||||||
.EditResource {
|
|
||||||
}
|
|
||||||
@ -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'");
|
||||||
|
|||||||
14
package.json
14
package.json
@ -320,8 +320,8 @@
|
|||||||
"@types/webpack": "^5.28.0",
|
"@types/webpack": "^5.28.0",
|
||||||
"@types/webpack-env": "^1.16.3",
|
"@types/webpack-env": "^1.16.3",
|
||||||
"@types/webpack-node-externals": "^2.5.3",
|
"@types/webpack-node-externals": "^2.5.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.7.0",
|
"@typescript-eslint/eslint-plugin": "^5.10.1",
|
||||||
"@typescript-eslint/parser": "^5.7.0",
|
"@typescript-eslint/parser": "^5.10.1",
|
||||||
"ansi_up": "^5.1.0",
|
"ansi_up": "^5.1.0",
|
||||||
"chart.js": "^2.9.4",
|
"chart.js": "^2.9.4",
|
||||||
"circular-dependency-plugin": "^5.2.2",
|
"circular-dependency-plugin": "^5.2.2",
|
||||||
@ -335,17 +335,17 @@
|
|||||||
"electron-notarize": "^0.3.0",
|
"electron-notarize": "^0.3.0",
|
||||||
"esbuild": "^0.13.15",
|
"esbuild": "^0.13.15",
|
||||||
"esbuild-loader": "^2.18.0",
|
"esbuild-loader": "^2.18.0",
|
||||||
"eslint": "^7.32.0",
|
"eslint": "^8.7.0",
|
||||||
"eslint-import-resolver-typescript": "^2.5.0",
|
|
||||||
"eslint-plugin-header": "^3.1.1",
|
"eslint-plugin-header": "^3.1.1",
|
||||||
"eslint-plugin-import": "^2.25.3",
|
"eslint-plugin-import": "^2.25.4",
|
||||||
"eslint-plugin-react": "^7.27.1",
|
"eslint-plugin-react": "^7.28.0",
|
||||||
"eslint-plugin-react-hooks": "^4.3.0",
|
"eslint-plugin-react-hooks": "^4.3.0",
|
||||||
"eslint-plugin-unused-imports": "^1.1.5",
|
"eslint-plugin-unused-imports": "^2.0.0",
|
||||||
"flex.box": "^3.4.4",
|
"flex.box": "^3.4.4",
|
||||||
"fork-ts-checker-webpack-plugin": "^6.5.0",
|
"fork-ts-checker-webpack-plugin": "^6.5.0",
|
||||||
"hoist-non-react-statics": "^3.3.2",
|
"hoist-non-react-statics": "^3.3.2",
|
||||||
"html-webpack-plugin": "^5.5.0",
|
"html-webpack-plugin": "^5.5.0",
|
||||||
|
"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;
|
||||||
@ -2,12 +2,12 @@
|
|||||||
* 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 { KubeObjectMenuRegistry } from "../../../../../extensions/registries";
|
|
||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import fsInjectable from "./fs.injectable";
|
||||||
|
|
||||||
const kubeObjectMenuRegistryInjectable = getInjectable({
|
const readFileInjectable = getInjectable({
|
||||||
instantiate: () => KubeObjectMenuRegistry.getInstance(),
|
instantiate: (di) => di.inject(fsInjectable).readFile,
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default kubeObjectMenuRegistryInjectable;
|
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 });
|
|
||||||
};
|
|
||||||
@ -111,7 +111,7 @@ export abstract class ItemStore<Item extends ItemObject> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async loadItem(...args: any[]): Promise<Item>
|
protected async loadItem(...args: any[]): Promise<Item>;
|
||||||
@action
|
@action
|
||||||
protected async loadItem(request: () => Promise<Item>, sortItems = true) {
|
protected async loadItem(request: () => Promise<Item>, sortItems = true) {
|
||||||
const item = await Promise.resolve(request()).catch(() => null);
|
const item = await Promise.resolve(request()).catch(() => null);
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
import moment from "moment-timezone";
|
import moment from "moment-timezone";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import os from "os";
|
import os from "os";
|
||||||
import { getAppVersion, ObservableToggleSet } from "../utils";
|
import { getAppVersion } from "../utils";
|
||||||
import type { editor } from "monaco-editor";
|
import type { editor } from "monaco-editor";
|
||||||
import merge from "lodash/merge";
|
import merge from "lodash/merge";
|
||||||
import { SemVer } from "semver";
|
import { SemVer } from "semver";
|
||||||
@ -236,10 +236,10 @@ const terminalCopyOnSelect: PreferenceDescription<boolean> = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const hiddenTableColumns: PreferenceDescription<[string, string[]][], Map<string, ObservableToggleSet<string>>> = {
|
const hiddenTableColumns: PreferenceDescription<[string, string[]][], Map<string, Set<string>>> = {
|
||||||
fromStore(val) {
|
fromStore(val) {
|
||||||
return new Map(
|
return new Map(
|
||||||
(val ?? []).map(([tableId, columnIds]) => [tableId, new ObservableToggleSet(columnIds)]),
|
(val ?? []).map(([tableId, columnIds]) => [tableId, new Set(columnIds)]),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
toStore(val) {
|
toStore(val) {
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import migrations, { fileNameMigration } from "../../migrations/user-store";
|
|||||||
import { getAppVersion } from "../utils/app-version";
|
import { getAppVersion } from "../utils/app-version";
|
||||||
import { kubeConfigDefaultPath } from "../kube-helpers";
|
import { kubeConfigDefaultPath } from "../kube-helpers";
|
||||||
import { appEventBus } from "../app-event-bus/event-bus";
|
import { appEventBus } from "../app-event-bus/event-bus";
|
||||||
import { ObservableToggleSet, toJS } from "../../renderer/utils";
|
import { getOrInsertSet, toggle, toJS } from "../../renderer/utils";
|
||||||
import { DESCRIPTORS, EditorConfiguration, ExtensionRegistry, KubeconfigSyncValue, UserPreferencesModel, TerminalConfig } from "./preferences-helpers";
|
import { DESCRIPTORS, EditorConfiguration, ExtensionRegistry, KubeconfigSyncValue, UserPreferencesModel, TerminalConfig } from "./preferences-helpers";
|
||||||
import logger from "../../main/logger";
|
import logger from "../../main/logger";
|
||||||
|
|
||||||
@ -71,7 +71,7 @@ export class UserStore extends BaseStore<UserStoreModel> /* implements UserStore
|
|||||||
* The column IDs under each configurable table ID that have been configured
|
* The column IDs under each configurable table ID that have been configured
|
||||||
* to not be shown
|
* to not be shown
|
||||||
*/
|
*/
|
||||||
hiddenTableColumns = observable.map<string, ObservableToggleSet<string>>();
|
hiddenTableColumns = observable.map<string, Set<string>>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Monaco editor configs
|
* Monaco editor configs
|
||||||
@ -133,16 +133,11 @@ export class UserStore extends BaseStore<UserStoreModel> /* implements UserStore
|
|||||||
return columnIds.some(columnId => config.has(columnId));
|
return columnIds.some(columnId => config.has(columnId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
|
||||||
/**
|
/**
|
||||||
* Toggles the hidden configuration of a table's column
|
* Toggles the hidden configuration of a table's column
|
||||||
*/
|
*/
|
||||||
toggleTableColumnVisibility(tableId: string, columnId: string) {
|
toggleTableColumnVisibility(tableId: string, columnId: string) {
|
||||||
if (!this.hiddenTableColumns.get(tableId)) {
|
toggle(getOrInsertSet(this.hiddenTableColumns, tableId), columnId);
|
||||||
this.hiddenTableColumns.set(tableId, new ObservableToggleSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
this.hiddenTableColumns.get(tableId).toggle(columnId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
|||||||
@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -3,6 +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 { runInAction } from "mobx";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the value behind `key`. If it was not present, first insert `value`
|
* Get the value behind `key`. If it was not present, first insert `value`
|
||||||
* @param map The map to interact with
|
* @param map The map to interact with
|
||||||
@ -26,6 +28,14 @@ export function getOrInsertMap<K, MK, MV>(map: Map<K, Map<MK, MV>>, key: K): Map
|
|||||||
return getOrInsert(map, key, new Map<MK, MV>());
|
return getOrInsert(map, key, new Map<MK, MV>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like `getOrInsert` but specifically for when `V` is `Set<any>` so that
|
||||||
|
* the typings are inferred.
|
||||||
|
*/
|
||||||
|
export function getOrInsertSet<K, SK>(map: Map<K, Set<SK>>, key: K): Set<SK> {
|
||||||
|
return getOrInsert(map, key, new Set<SK>());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Like `getOrInsert` but with delayed creation of the item
|
* Like `getOrInsert` but with delayed creation of the item
|
||||||
*/
|
*/
|
||||||
@ -36,3 +46,17 @@ export function getOrInsertWith<K, V>(map: Map<K, V>, key: K, value: () => V): V
|
|||||||
|
|
||||||
return map.get(key);
|
return map.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If `key` is in `set`, remove it otherwise add it.
|
||||||
|
* @param set The set to manipulate
|
||||||
|
* @param key The key to toggle the "is in"-ness of
|
||||||
|
*/
|
||||||
|
export function toggle<K>(set: Set<K>, key: K): void {
|
||||||
|
runInAction(() => {
|
||||||
|
// Returns true if value was already in Set; otherwise false.
|
||||||
|
if (!set.delete(key)) {
|
||||||
|
set.add(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@ -3,12 +3,14 @@
|
|||||||
* 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, ObservableMap } from "mobx";
|
import { action, ObservableMap, runInAction } from "mobx";
|
||||||
|
|
||||||
export function multiSet<T, V>(map: Map<T, V>, newEntries: [T, V][]): void {
|
export function multiSet<T, V>(map: Map<T, V>, newEntries: [T, V][]): void {
|
||||||
for (const [key, val] of newEntries) {
|
runInAction(() => {
|
||||||
map.set(key, val);
|
for (const [key, val] of newEntries) {
|
||||||
}
|
map.set(key, val);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ExtendedMap<K, V> extends Map<K, V> {
|
export class ExtendedMap<K, V> extends Map<K, V> {
|
||||||
|
|||||||
@ -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";
|
||||||
@ -45,10 +38,10 @@ export * from "./singleton";
|
|||||||
export * from "./sort-compare";
|
export * from "./sort-compare";
|
||||||
export * from "./splitArray";
|
export * from "./splitArray";
|
||||||
export * from "./tar";
|
export * from "./tar";
|
||||||
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";
|
||||||
|
|||||||
@ -1,24 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { ObservableSet } from "mobx";
|
|
||||||
|
|
||||||
export class ToggleSet<T> extends Set<T> {
|
|
||||||
public toggle(value: T): void {
|
|
||||||
if (!this.delete(value)) {
|
|
||||||
// Set.prototype.delete returns false if `value` was not in the set
|
|
||||||
this.add(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ObservableToggleSet<T> extends ObservableSet<T> {
|
|
||||||
public toggle(value: T): void {
|
|
||||||
if (!this.delete(value)) {
|
|
||||||
// Set.prototype.delete returns false if `value` was not in the set
|
|
||||||
this.add(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -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 { isLinux } from "../vars";
|
||||||
import fsInjectable from "../fs.injectable";
|
|
||||||
|
|
||||||
const writeJsonFileInjectable = getInjectable({
|
const isLinuxInjectable = getInjectable({
|
||||||
instantiate: (di) => writeJsonFile({ fs: di.inject(fsInjectable) }),
|
instantiate: () => isLinux,
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default writeJsonFileInjectable;
|
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;
|
|
||||||
};
|
|
||||||
@ -2,13 +2,12 @@
|
|||||||
* 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.
|
||||||
*/
|
*/
|
||||||
|
export type { StatusBarRegistration } from "../../renderer/components/cluster-manager/status-bar-registration";
|
||||||
|
export type { KubeObjectMenuRegistration, KubeObjectMenuComponents } from "../../renderer/components/kube-object-menu/dependencies/kube-object-menu-items/kube-object-menu-registration";
|
||||||
export type { AppPreferenceRegistration, AppPreferenceComponents } from "../../renderer/components/+preferences/app-preferences/app-preference-registration";
|
export type { AppPreferenceRegistration, AppPreferenceComponents } from "../../renderer/components/+preferences/app-preferences/app-preference-registration";
|
||||||
export type { KubeObjectDetailRegistration, KubeObjectDetailComponents } from "../registries/kube-object-detail-registry";
|
export type { KubeObjectDetailRegistration, KubeObjectDetailComponents } from "../registries/kube-object-detail-registry";
|
||||||
export type { KubeObjectMenuRegistration, KubeObjectMenuComponents } from "../registries/kube-object-menu-registry";
|
|
||||||
export type { KubeObjectStatusRegistration } from "../registries/kube-object-status-registry";
|
export type { KubeObjectStatusRegistration } from "../registries/kube-object-status-registry";
|
||||||
export type { PageRegistration, RegisteredPage, PageParams, PageComponentProps, PageComponents, PageTarget } from "../registries/page-registry";
|
export type { PageRegistration, RegisteredPage, PageParams, PageComponentProps, PageComponents, PageTarget } from "../registries/page-registry";
|
||||||
export type { ClusterPageMenuRegistration, ClusterPageMenuComponents } from "../registries/page-menu-registry";
|
export type { ClusterPageMenuRegistration, ClusterPageMenuComponents } from "../registries/page-menu-registry";
|
||||||
export type { StatusBarRegistration } from "../registries/status-bar-registry";
|
|
||||||
export type { ProtocolHandlerRegistration, RouteParams as ProtocolRouteParams, RouteHandler as ProtocolRouteHandler } from "../registries/protocol-handler";
|
export type { ProtocolHandlerRegistration, RouteParams as ProtocolRouteParams, RouteHandler as ProtocolRouteHandler } from "../registries/protocol-handler";
|
||||||
export type { CustomCategoryViewProps, CustomCategoryViewComponents, CustomCategoryViewRegistration } from "../../renderer/components/+catalog/custom-views";
|
export type { CustomCategoryViewProps, CustomCategoryViewComponents, CustomCategoryViewRegistration } from "../../renderer/components/+catalog/custom-views";
|
||||||
|
|||||||
@ -249,7 +249,6 @@ export class ExtensionLoader {
|
|||||||
const removeItems = [
|
const removeItems = [
|
||||||
registries.GlobalPageRegistry.getInstance().add(extension.globalPages, extension),
|
registries.GlobalPageRegistry.getInstance().add(extension.globalPages, extension),
|
||||||
registries.EntitySettingRegistry.getInstance().add(extension.entitySettings),
|
registries.EntitySettingRegistry.getInstance().add(extension.entitySettings),
|
||||||
registries.StatusBarRegistry.getInstance().add(extension.statusBarItems),
|
|
||||||
registries.CatalogEntityDetailRegistry.getInstance().add(extension.catalogEntityDetailItems),
|
registries.CatalogEntityDetailRegistry.getInstance().add(extension.catalogEntityDetailItems),
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -277,7 +276,6 @@ export class ExtensionLoader {
|
|||||||
const removeItems = [
|
const removeItems = [
|
||||||
registries.ClusterPageRegistry.getInstance().add(extension.clusterPages, extension),
|
registries.ClusterPageRegistry.getInstance().add(extension.clusterPages, extension),
|
||||||
registries.ClusterPageMenuRegistry.getInstance().add(extension.clusterPageMenus, extension),
|
registries.ClusterPageMenuRegistry.getInstance().add(extension.clusterPageMenus, extension),
|
||||||
registries.KubeObjectMenuRegistry.getInstance().add(extension.kubeObjectMenuItems),
|
|
||||||
registries.KubeObjectDetailRegistry.getInstance().add(extension.kubeObjectDetailItems),
|
registries.KubeObjectDetailRegistry.getInstance().add(extension.kubeObjectDetailItems),
|
||||||
registries.KubeObjectStatusRegistry.getInstance().add(extension.kubeObjectStatusTexts),
|
registries.KubeObjectStatusRegistry.getInstance().add(extension.kubeObjectStatusTexts),
|
||||||
registries.WorkloadsOverviewDetailRegistry.getInstance().add(extension.kubeWorkloadsOverviewItems),
|
registries.WorkloadsOverviewDetailRegistry.getInstance().add(extension.kubeWorkloadsOverviewItems),
|
||||||
|
|||||||
@ -42,10 +42,9 @@ export class ExtensionsStore extends BaseStore<LensExtensionsStoreModel> {
|
|||||||
return isBundled || Boolean(this.state.get(id)?.enabled);
|
return isBundled || Boolean(this.state.get(id)?.enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
mergeState = action((extensionsState: Record<LensExtensionId, LensExtensionState>) => {
|
||||||
mergeState = (extensionsState: Record<LensExtensionId, LensExtensionState>) => {
|
|
||||||
this.state.merge(extensionsState);
|
this.state.merge(extensionsState);
|
||||||
};
|
});
|
||||||
|
|
||||||
@action
|
@action
|
||||||
protected fromStore({ extensions }: LensExtensionsStoreModel) {
|
protected fromStore({ extensions }: LensExtensionsStoreModel) {
|
||||||
|
|||||||
@ -18,6 +18,8 @@ import type { CommandRegistration } from "../renderer/components/command-palette
|
|||||||
import type { AppPreferenceRegistration } from "../renderer/components/+preferences/app-preferences/app-preference-registration";
|
import type { AppPreferenceRegistration } from "../renderer/components/+preferences/app-preferences/app-preference-registration";
|
||||||
import type { AdditionalCategoryColumnRegistration } from "../renderer/components/+catalog/custom-category-columns";
|
import type { AdditionalCategoryColumnRegistration } from "../renderer/components/+catalog/custom-category-columns";
|
||||||
import type { CustomCategoryViewRegistration } from "../renderer/components/+catalog/custom-views";
|
import type { CustomCategoryViewRegistration } from "../renderer/components/+catalog/custom-views";
|
||||||
|
import type { StatusBarRegistration } from "../renderer/components/cluster-manager/status-bar-registration";
|
||||||
|
import type { KubeObjectMenuRegistration } from "../renderer/components/kube-object-menu/dependencies/kube-object-menu-items/kube-object-menu-registration";
|
||||||
|
|
||||||
export class LensRendererExtension extends LensExtension {
|
export class LensRendererExtension extends LensExtension {
|
||||||
globalPages: registries.PageRegistration[] = [];
|
globalPages: registries.PageRegistration[] = [];
|
||||||
@ -26,9 +28,9 @@ export class LensRendererExtension extends LensExtension {
|
|||||||
kubeObjectStatusTexts: registries.KubeObjectStatusRegistration[] = [];
|
kubeObjectStatusTexts: registries.KubeObjectStatusRegistration[] = [];
|
||||||
appPreferences: AppPreferenceRegistration[] = [];
|
appPreferences: AppPreferenceRegistration[] = [];
|
||||||
entitySettings: registries.EntitySettingRegistration[] = [];
|
entitySettings: registries.EntitySettingRegistration[] = [];
|
||||||
statusBarItems: registries.StatusBarRegistration[] = [];
|
statusBarItems: StatusBarRegistration[] = [];
|
||||||
kubeObjectDetailItems: registries.KubeObjectDetailRegistration[] = [];
|
kubeObjectDetailItems: registries.KubeObjectDetailRegistration[] = [];
|
||||||
kubeObjectMenuItems: registries.KubeObjectMenuRegistration[] = [];
|
kubeObjectMenuItems: KubeObjectMenuRegistration[] = [];
|
||||||
kubeWorkloadsOverviewItems: registries.WorkloadsOverviewDetailRegistration[] = [];
|
kubeWorkloadsOverviewItems: registries.WorkloadsOverviewDetailRegistration[] = [];
|
||||||
commands: CommandRegistration[] = [];
|
commands: CommandRegistration[] = [];
|
||||||
welcomeMenus: WelcomeMenuRegistration[] = [];
|
welcomeMenus: WelcomeMenuRegistration[] = [];
|
||||||
|
|||||||
@ -7,9 +7,7 @@
|
|||||||
|
|
||||||
export * from "./page-registry";
|
export * from "./page-registry";
|
||||||
export * from "./page-menu-registry";
|
export * from "./page-menu-registry";
|
||||||
export * from "./status-bar-registry";
|
|
||||||
export * from "./kube-object-detail-registry";
|
export * from "./kube-object-detail-registry";
|
||||||
export * from "./kube-object-menu-registry";
|
|
||||||
export * from "./kube-object-status-registry";
|
export * from "./kube-object-status-registry";
|
||||||
export * from "./entity-setting-registry";
|
export * from "./entity-setting-registry";
|
||||||
export * from "./catalog-entity-detail-registry";
|
export * from "./catalog-entity-detail-registry";
|
||||||
|
|||||||
@ -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 },
|
||||||
|
|||||||
@ -74,7 +74,7 @@ export class WindowManager extends Singleton {
|
|||||||
nodeIntegrationInSubFrames: true,
|
nodeIntegrationInSubFrames: true,
|
||||||
webviewTag: true,
|
webviewTag: true,
|
||||||
contextIsolation: false,
|
contextIsolation: false,
|
||||||
nativeWindowOpen: true,
|
nativeWindowOpen: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.windowState.manage(this.mainWindow);
|
this.windowState.manage(this.mainWindow);
|
||||||
|
|||||||
@ -73,9 +73,6 @@ export async function bootstrap(di: DependencyInjectionContainer) {
|
|||||||
logger.info(`${logPrefix} initializing EntitySettingsRegistry`);
|
logger.info(`${logPrefix} initializing EntitySettingsRegistry`);
|
||||||
initializers.initEntitySettingsRegistry();
|
initializers.initEntitySettingsRegistry();
|
||||||
|
|
||||||
logger.info(`${logPrefix} initializing KubeObjectMenuRegistry`);
|
|
||||||
initializers.initKubeObjectMenuRegistry();
|
|
||||||
|
|
||||||
logger.info(`${logPrefix} initializing KubeObjectDetailRegistry`);
|
logger.info(`${logPrefix} initializing KubeObjectDetailRegistry`);
|
||||||
initializers.initKubeObjectDetailRegistry();
|
initializers.initKubeObjectDetailRegistry();
|
||||||
|
|
||||||
|
|||||||
@ -70,8 +70,7 @@ class NonInjectedAddCluster extends React.Component<Dependencies> {
|
|||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
readonly refreshContexts = debounce(action(() => {
|
||||||
refreshContexts = debounce(() => {
|
|
||||||
const { config, error } = loadConfigFromString(this.customConfig.trim() || "{}");
|
const { config, error } = loadConfigFromString(this.customConfig.trim() || "{}");
|
||||||
|
|
||||||
this.kubeContexts.replace(getContexts(config));
|
this.kubeContexts.replace(getContexts(config));
|
||||||
@ -83,10 +82,9 @@ class NonInjectedAddCluster extends React.Component<Dependencies> {
|
|||||||
if (config.contexts.length === 0) {
|
if (config.contexts.length === 0) {
|
||||||
this.errors.push('No contexts defined, either missing the "contexts" field, or it is empty.');
|
this.errors.push('No contexts defined, either missing the "contexts" field, or it is empty.');
|
||||||
}
|
}
|
||||||
}, 500);
|
}), 500);
|
||||||
|
|
||||||
@action
|
addClusters = action(async () => {
|
||||||
addClusters = async () => {
|
|
||||||
this.isWaiting = true;
|
this.isWaiting = true;
|
||||||
appEventBus.emit({ name: "cluster-add", action: "click" });
|
appEventBus.emit({ name: "cluster-add", action: "click" });
|
||||||
|
|
||||||
@ -102,7 +100,7 @@ class NonInjectedAddCluster extends React.Component<Dependencies> {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
Notifications.error(`Failed to add clusters: ${error}`);
|
Notifications.error(`Failed to add clusters: ${error}`);
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -18,8 +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
|
import createInstallChartTabInjectable from "../dock/install-chart/create-install-chart-tab.injectable";
|
||||||
from "../dock/create-install-chart-tab/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,
|
||||||
|
|||||||
@ -57,8 +57,7 @@ export class CatalogAddButton extends React.Component<CatalogAddButtonProps> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
updateCategoryItems = action((category: CatalogCategory) => {
|
||||||
updateCategoryItems = (category: CatalogCategory) => {
|
|
||||||
if (category instanceof EventEmitter) {
|
if (category instanceof EventEmitter) {
|
||||||
const menuItems: CatalogEntityAddMenu[] = [];
|
const menuItems: CatalogEntityAddMenu[] = [];
|
||||||
const context: CatalogEntityAddMenuContext = {
|
const context: CatalogEntityAddMenuContext = {
|
||||||
@ -69,7 +68,7 @@ export class CatalogAddButton extends React.Component<CatalogAddButtonProps> {
|
|||||||
category.emit("catalogAddMenu", context);
|
category.emit("catalogAddMenu", context);
|
||||||
this.menuItems.set(category.getId(), menuItems);
|
this.menuItems.set(category.getId(), menuItems);
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
getCategoryFilteredItems = (category: CatalogCategory) => {
|
getCategoryFilteredItems = (category: CatalogCategory) => {
|
||||||
return category.filteredItems(this.menuItems.get(category.getId()) || []);
|
return category.filteredItems(this.menuItems.get(category.getId()) || []);
|
||||||
|
|||||||
@ -22,6 +22,8 @@
|
|||||||
|
|
||||||
.content {
|
.content {
|
||||||
min-height: 26px;
|
min-height: 26px;
|
||||||
|
line-height: 1.3;
|
||||||
|
padding: 2px var(--padding) 2px 0;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--sidebarItemHoverBackground);
|
background-color: var(--sidebarItemHoverBackground);
|
||||||
@ -39,6 +41,8 @@
|
|||||||
|
|
||||||
.iconContainer {
|
.iconContainer {
|
||||||
margin-left: 28px;
|
margin-left: 28px;
|
||||||
|
margin-top: 2px;
|
||||||
|
align-self: flex-start;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,6 +20,7 @@
|
|||||||
padding: 0 var(--padding);
|
padding: 0 var(--padding);
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
padding-right: 24px; // + reserved space for .pinIcon
|
padding-right: 24px; // + reserved space for .pinIcon
|
||||||
|
flex-grow: 2.5!important;
|
||||||
|
|
||||||
> span {
|
> span {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@ -143,8 +143,7 @@ class NonInjectedCatalog extends React.Component<Props & Dependencies> {
|
|||||||
return catalogCategoryRegistry.items;
|
return catalogCategoryRegistry.items;
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
onTabChange = action((tabId: string | null) => {
|
||||||
onTabChange = (tabId: string | null) => {
|
|
||||||
const activeCategory = this.categories.find(category => category.getId() === tabId);
|
const activeCategory = this.categories.find(category => category.getId() === tabId);
|
||||||
|
|
||||||
if (activeCategory) {
|
if (activeCategory) {
|
||||||
@ -152,7 +151,7 @@ class NonInjectedCatalog extends React.Component<Props & Dependencies> {
|
|||||||
} else {
|
} else {
|
||||||
navigate(catalogURL({ params: { group: browseCatalogTab }}));
|
navigate(catalogURL({ params: { group: browseCatalogTab }}));
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
renderNavigation() {
|
renderNavigation() {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -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,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import { DrawerItem, DrawerTitle } from "../drawer";
|
|||||||
import { Input } from "../input";
|
import { Input } from "../input";
|
||||||
import { Button } from "../button";
|
import { Button } from "../button";
|
||||||
import { Notifications } from "../notifications";
|
import { Notifications } from "../notifications";
|
||||||
import { base64, ObservableToggleSet } from "../../utils";
|
import { base64, toggle } from "../../utils";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { secretsStore } from "./secrets.store";
|
import { secretsStore } from "./secrets.store";
|
||||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
||||||
@ -27,7 +27,7 @@ interface Props extends KubeObjectDetailsProps<Secret> {
|
|||||||
export class SecretDetails extends React.Component<Props> {
|
export class SecretDetails extends React.Component<Props> {
|
||||||
@observable isSaving = false;
|
@observable isSaving = false;
|
||||||
@observable data: { [name: string]: string } = {};
|
@observable data: { [name: string]: string } = {};
|
||||||
revealSecret = new ObservableToggleSet<string>();
|
revealSecret = new Set<string>();
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
@ -99,7 +99,7 @@ export class SecretDetails extends React.Component<Props> {
|
|||||||
<Icon
|
<Icon
|
||||||
material={revealSecret ? "visibility" : "visibility_off"}
|
material={revealSecret ? "visibility" : "visibility_off"}
|
||||||
tooltip={revealSecret ? "Hide" : "Show"}
|
tooltip={revealSecret ? "Hide" : "Show"}
|
||||||
onClick={() => this.revealSecret.toggle(name)}
|
onClick={() => toggle(this.revealSecret, name)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -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,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { action, comparer, computed, IReactionDisposer, makeObservable, reaction } from "mobx";
|
import { action, comparer, computed, IReactionDisposer, makeObservable, reaction } from "mobx";
|
||||||
import { autoBind, noop, StorageHelper, ToggleSet } from "../../../utils";
|
import { autoBind, noop, StorageHelper, toggle } from "../../../utils";
|
||||||
import { KubeObjectStore, KubeObjectStoreLoadingParams } from "../../../../common/k8s-api/kube-object.store";
|
import { KubeObjectStore, KubeObjectStoreLoadingParams } from "../../../../common/k8s-api/kube-object.store";
|
||||||
import { Namespace, namespacesApi } from "../../../../common/k8s-api/endpoints/namespaces.api";
|
import { Namespace, namespacesApi } from "../../../../common/k8s-api/endpoints/namespaces.api";
|
||||||
|
|
||||||
@ -175,10 +175,10 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
|
|||||||
*/
|
*/
|
||||||
@action
|
@action
|
||||||
toggleContext(namespaces: string | string[]) {
|
toggleContext(namespaces: string | string[]) {
|
||||||
const nextState = new ToggleSet(this.contextNamespaces);
|
const nextState = new Set(this.contextNamespaces);
|
||||||
|
|
||||||
for (const namespace of [namespaces].flat()) {
|
for (const namespace of [namespaces].flat()) {
|
||||||
nextState.toggle(namespace);
|
toggle(nextState, namespace);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dependencies.storage.set([...nextState]);
|
this.dependencies.storage.set([...nextState]);
|
||||||
@ -191,9 +191,9 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
|
|||||||
* @param namespace The name of a namespace
|
* @param namespace The name of a namespace
|
||||||
*/
|
*/
|
||||||
toggleSingle(namespace: string) {
|
toggleSingle(namespace: string) {
|
||||||
const nextState = new ToggleSet(this.contextNamespaces);
|
const nextState = new Set(this.contextNamespaces);
|
||||||
|
|
||||||
nextState.toggle(namespace);
|
toggle(nextState, namespace);
|
||||||
this.dependencies.storage.set([...nextState]);
|
this.dependencies.storage.set([...nextState]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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";
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* 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 fse from "fs-extra";
|
import fse from "fs-extra";
|
||||||
import { action, computed, makeObservable, observable, reaction } from "mobx";
|
import { computed, makeObservable, observable, reaction } from "mobx";
|
||||||
import { disposeOnUnmount, observer } from "mobx-react";
|
import { disposeOnUnmount, observer } from "mobx-react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Notice } from "../+extensions/notice";
|
import { Notice } from "../+extensions/notice";
|
||||||
@ -93,7 +93,6 @@ export class KubeconfigSyncs extends React.Component {
|
|||||||
return Array.from(this.syncs.entries(), ([filePath, value]) => ({ filePath, ...value }));
|
return Array.from(this.syncs.entries(), ([filePath, value]) => ({ filePath, ...value }));
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
|
||||||
onPick = async (filePaths: string[]) => multiSet(this.syncs, await getAllEntries(filePaths));
|
onPick = async (filePaths: string[]) => multiSet(this.syncs, await getAllEntries(filePaths));
|
||||||
|
|
||||||
getIconName(entry: Entry) {
|
getIconName(entry: Entry) {
|
||||||
|
|||||||
@ -115,8 +115,7 @@ export class ClusterRoleBindingDialog extends React.Component<Props> {
|
|||||||
return this.serviceAccountOptions.filter(({ value }) => this.selectedAccounts.has(value));
|
return this.serviceAccountOptions.filter(({ value }) => this.selectedAccounts.has(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
onOpen = action(() => {
|
||||||
onOpen = () => {
|
|
||||||
const binding = this.clusterRoleBinding;
|
const binding = this.clusterRoleBinding;
|
||||||
|
|
||||||
if (!binding) {
|
if (!binding) {
|
||||||
@ -137,16 +136,15 @@ export class ClusterRoleBindingDialog extends React.Component<Props> {
|
|||||||
);
|
);
|
||||||
this.selectedUsers.replace(uSubjects.map(user => user.name));
|
this.selectedUsers.replace(uSubjects.map(user => user.name));
|
||||||
this.selectedGroups.replace(gSubjects.map(group => group.name));
|
this.selectedGroups.replace(gSubjects.map(group => group.name));
|
||||||
};
|
});
|
||||||
|
|
||||||
@action
|
reset = action(() => {
|
||||||
reset = () => {
|
|
||||||
this.selectedRoleRef = undefined;
|
this.selectedRoleRef = undefined;
|
||||||
this.bindingName = "";
|
this.bindingName = "";
|
||||||
this.selectedAccounts.clear();
|
this.selectedAccounts.clear();
|
||||||
this.selectedUsers.clear();
|
this.selectedUsers.clear();
|
||||||
this.selectedGroups.clear();
|
this.selectedGroups.clear();
|
||||||
};
|
});
|
||||||
|
|
||||||
createBindings = async () => {
|
createBindings = async () => {
|
||||||
const { selectedRoleRef, selectedBindings, bindingName } = this;
|
const { selectedRoleRef, selectedBindings, bindingName } = this;
|
||||||
|
|||||||
@ -116,8 +116,7 @@ export class RoleBindingDialog extends React.Component<Props> {
|
|||||||
return this.serviceAccountOptions.filter(({ value }) => this.selectedAccounts.has(value));
|
return this.serviceAccountOptions.filter(({ value }) => this.selectedAccounts.has(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
onOpen = action(() => {
|
||||||
onOpen = () => {
|
|
||||||
const binding = this.roleBinding;
|
const binding = this.roleBinding;
|
||||||
|
|
||||||
if (!binding) {
|
if (!binding) {
|
||||||
@ -140,17 +139,16 @@ export class RoleBindingDialog extends React.Component<Props> {
|
|||||||
);
|
);
|
||||||
this.selectedUsers.replace(uSubjects.map(user => user.name));
|
this.selectedUsers.replace(uSubjects.map(user => user.name));
|
||||||
this.selectedGroups.replace(gSubjects.map(group => group.name));
|
this.selectedGroups.replace(gSubjects.map(group => group.name));
|
||||||
};
|
});
|
||||||
|
|
||||||
@action
|
reset = action(() => {
|
||||||
reset = () => {
|
|
||||||
this.selectedRoleRef = undefined;
|
this.selectedRoleRef = undefined;
|
||||||
this.bindingName = "";
|
this.bindingName = "";
|
||||||
this.bindingNamespace = "";
|
this.bindingNamespace = "";
|
||||||
this.selectedAccounts.clear();
|
this.selectedAccounts.clear();
|
||||||
this.selectedUsers.clear();
|
this.selectedUsers.clear();
|
||||||
this.selectedGroups.clear();
|
this.selectedGroups.clear();
|
||||||
};
|
});
|
||||||
|
|
||||||
createBindings = async () => {
|
createBindings = async () => {
|
||||||
const { selectedRoleRef, bindingNamespace: namespace, selectedBindings } = this;
|
const { selectedRoleRef, bindingNamespace: namespace, selectedBindings } = this;
|
||||||
|
|||||||
@ -28,27 +28,30 @@ export class ServiceAccountsDetails extends React.Component<Props> {
|
|||||||
@observable secrets: Secret[];
|
@observable secrets: Secret[];
|
||||||
@observable imagePullSecrets: Secret[];
|
@observable imagePullSecrets: Secret[];
|
||||||
|
|
||||||
@disposeOnUnmount
|
componentDidMount(): void {
|
||||||
loadSecrets = autorun(async () => {
|
disposeOnUnmount(this, [
|
||||||
this.secrets = null;
|
autorun(async () => {
|
||||||
this.imagePullSecrets = null;
|
this.secrets = null;
|
||||||
const { object: serviceAccount } = this.props;
|
this.imagePullSecrets = null;
|
||||||
|
const { object: serviceAccount } = this.props;
|
||||||
|
|
||||||
if (!serviceAccount) {
|
if (!serviceAccount) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const namespace = serviceAccount.getNs();
|
const namespace = serviceAccount.getNs();
|
||||||
const secrets = serviceAccount.getSecrets().map(({ name }) => {
|
const secrets = serviceAccount.getSecrets().map(({ name }) => {
|
||||||
return secretsStore.load({ name, namespace });
|
return secretsStore.load({ name, namespace });
|
||||||
});
|
});
|
||||||
|
|
||||||
this.secrets = await Promise.all(secrets);
|
this.secrets = await Promise.all(secrets);
|
||||||
const imagePullSecrets = serviceAccount.getImagePullSecrets().map(async ({ name }) => {
|
const imagePullSecrets = serviceAccount.getImagePullSecrets().map(async ({ name }) => {
|
||||||
return secretsStore.load({ name, namespace }).catch(() => this.generateDummySecretObject(name));
|
return secretsStore.load({ name, namespace }).catch(() => this.generateDummySecretObject(name));
|
||||||
});
|
});
|
||||||
|
|
||||||
this.imagePullSecrets = await Promise.all(imagePullSecrets);
|
this.imagePullSecrets = await Promise.all(imagePullSecrets);
|
||||||
});
|
}),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|||||||
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import type { KubeObjectMenuProps } from "../../kube-object-menu";
|
||||||
|
import type { ServiceAccount } from "../../../../common/k8s-api/endpoints";
|
||||||
|
import { MenuItem } from "../../menu";
|
||||||
|
import { openServiceAccountKubeConfig } from "../../kubeconfig-dialog";
|
||||||
|
import { Icon } from "../../icon";
|
||||||
|
|
||||||
|
export function ServiceAccountMenu(props: KubeObjectMenuProps<ServiceAccount>) {
|
||||||
|
const { object, toolbar } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuItem onClick={() => openServiceAccountKubeConfig(object)}>
|
||||||
|
<Icon material="insert_drive_file" tooltip="Kubeconfig File" interactive={toolbar} />
|
||||||
|
<span className="title">Kubeconfig</span>
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -8,13 +8,8 @@ import "./view.scss";
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import type { RouteComponentProps } from "react-router";
|
import type { RouteComponentProps } from "react-router";
|
||||||
import type { ServiceAccount } from "../../../../common/k8s-api/endpoints/service-accounts.api";
|
|
||||||
import { Icon } from "../../icon";
|
|
||||||
import { KubeObjectListLayout } from "../../kube-object-list-layout";
|
import { KubeObjectListLayout } from "../../kube-object-list-layout";
|
||||||
import { KubeObjectStatusIcon } from "../../kube-object-status-icon";
|
import { KubeObjectStatusIcon } from "../../kube-object-status-icon";
|
||||||
import type { KubeObjectMenuProps } from "../../kube-object-menu";
|
|
||||||
import { openServiceAccountKubeConfig } from "../../kubeconfig-dialog";
|
|
||||||
import { MenuItem } from "../../menu";
|
|
||||||
import { CreateServiceAccountDialog } from "./create-dialog";
|
import { CreateServiceAccountDialog } from "./create-dialog";
|
||||||
import { serviceAccountsStore } from "./store";
|
import { serviceAccountsStore } from "./store";
|
||||||
import type { ServiceAccountsRouteParams } from "../../../../common/routes";
|
import type { ServiceAccountsRouteParams } from "../../../../common/routes";
|
||||||
@ -58,9 +53,6 @@ export class ServiceAccounts extends React.Component<Props> {
|
|||||||
account.getNs(),
|
account.getNs(),
|
||||||
account.getAge(),
|
account.getAge(),
|
||||||
]}
|
]}
|
||||||
renderItemMenu={(item: ServiceAccount) => {
|
|
||||||
return <ServiceAccountMenu object={item}/>;
|
|
||||||
}}
|
|
||||||
addRemoveButtons={{
|
addRemoveButtons={{
|
||||||
onAdd: () => CreateServiceAccountDialog.open(),
|
onAdd: () => CreateServiceAccountDialog.open(),
|
||||||
addTooltip: "Create new Service Account",
|
addTooltip: "Create new Service Account",
|
||||||
@ -72,13 +64,3 @@ export class ServiceAccounts extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ServiceAccountMenu(props: KubeObjectMenuProps<ServiceAccount>) {
|
|
||||||
const { object, toolbar } = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<MenuItem onClick={() => openServiceAccountKubeConfig(object)}>
|
|
||||||
<Icon material="insert_drive_file" tooltip="Kubeconfig File" interactive={toolbar} />
|
|
||||||
<span className="title">Kubeconfig</span>
|
|
||||||
</MenuItem>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@ -0,0 +1,63 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import React from "react";
|
||||||
|
import type { KubeObjectMenuProps } from "../kube-object-menu";
|
||||||
|
import { CronJob, cronJobApi } from "../../../common/k8s-api/endpoints";
|
||||||
|
import { MenuItem } from "../menu";
|
||||||
|
import { CronJobTriggerDialog } from "./cronjob-trigger-dialog";
|
||||||
|
import { Icon } from "../icon";
|
||||||
|
import { ConfirmDialog } from "../confirm-dialog";
|
||||||
|
import { Notifications } from "../notifications";
|
||||||
|
|
||||||
|
export function CronJobMenu(props: KubeObjectMenuProps<CronJob>) {
|
||||||
|
const { object, toolbar } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MenuItem onClick={() => CronJobTriggerDialog.open(object)}>
|
||||||
|
<Icon material="play_circle_filled" tooltip="Trigger" interactive={toolbar}/>
|
||||||
|
<span className="title">Trigger</span>
|
||||||
|
</MenuItem>
|
||||||
|
|
||||||
|
{object.isSuspend() ?
|
||||||
|
<MenuItem onClick={() => ConfirmDialog.open({
|
||||||
|
ok: async () => {
|
||||||
|
try {
|
||||||
|
await cronJobApi.resume({ namespace: object.getNs(), name: object.getName() });
|
||||||
|
} catch (err) {
|
||||||
|
Notifications.error(err);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
labelOk: `Resume`,
|
||||||
|
message: (
|
||||||
|
<p>
|
||||||
|
Resume CronJob <b>{object.getName()}</b>?
|
||||||
|
</p>),
|
||||||
|
})}>
|
||||||
|
<Icon material="play_circle_outline" tooltip="Resume" interactive={toolbar}/>
|
||||||
|
<span className="title">Resume</span>
|
||||||
|
</MenuItem>
|
||||||
|
|
||||||
|
: <MenuItem onClick={() => ConfirmDialog.open({
|
||||||
|
ok: async () => {
|
||||||
|
try {
|
||||||
|
await cronJobApi.suspend({ namespace: object.getNs(), name: object.getName() });
|
||||||
|
} catch (err) {
|
||||||
|
Notifications.error(err);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
labelOk: `Suspend`,
|
||||||
|
message: (
|
||||||
|
<p>
|
||||||
|
Suspend CronJob <b>{object.getName()}</b>?
|
||||||
|
</p>),
|
||||||
|
})}>
|
||||||
|
<Icon material="pause_circle_filled" tooltip="Suspend" interactive={toolbar}/>
|
||||||
|
<span className="title">Suspend</span>
|
||||||
|
</MenuItem>
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -8,18 +8,11 @@ import "./cronjobs.scss";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import type { RouteComponentProps } from "react-router";
|
import type { RouteComponentProps } from "react-router";
|
||||||
import { CronJob, cronJobApi } from "../../../common/k8s-api/endpoints/cron-job.api";
|
|
||||||
import { MenuItem } from "../menu";
|
|
||||||
import { Icon } from "../icon";
|
|
||||||
import { cronJobStore } from "./cronjob.store";
|
import { cronJobStore } from "./cronjob.store";
|
||||||
import { jobStore } from "../+workloads-jobs/job.store";
|
import { jobStore } from "../+workloads-jobs/job.store";
|
||||||
import { eventStore } from "../+events/event.store";
|
import { eventStore } from "../+events/event.store";
|
||||||
import type { KubeObjectMenuProps } from "../kube-object-menu";
|
|
||||||
import { KubeObjectListLayout } from "../kube-object-list-layout";
|
import { KubeObjectListLayout } from "../kube-object-list-layout";
|
||||||
import { CronJobTriggerDialog } from "./cronjob-trigger-dialog";
|
|
||||||
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
||||||
import { ConfirmDialog } from "../confirm-dialog/confirm-dialog";
|
|
||||||
import { Notifications } from "../notifications/notifications";
|
|
||||||
import type { CronJobsRouteParams } from "../../../common/routes";
|
import type { CronJobsRouteParams } from "../../../common/routes";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
|
|
||||||
@ -82,61 +75,8 @@ export class CronJobs extends React.Component<Props> {
|
|||||||
cronJob.getLastScheduleTime(),
|
cronJob.getLastScheduleTime(),
|
||||||
cronJob.getAge(),
|
cronJob.getAge(),
|
||||||
]}
|
]}
|
||||||
renderItemMenu={(item: CronJob) => {
|
|
||||||
return <CronJobMenu object={item}/>;
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CronJobMenu(props: KubeObjectMenuProps<CronJob>) {
|
|
||||||
const { object, toolbar } = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<MenuItem onClick={() => CronJobTriggerDialog.open(object)}>
|
|
||||||
<Icon material="play_circle_filled" tooltip="Trigger" interactive={toolbar}/>
|
|
||||||
<span className="title">Trigger</span>
|
|
||||||
</MenuItem>
|
|
||||||
|
|
||||||
{object.isSuspend() ?
|
|
||||||
<MenuItem onClick={() => ConfirmDialog.open({
|
|
||||||
ok: async () => {
|
|
||||||
try {
|
|
||||||
await cronJobApi.resume({ namespace: object.getNs(), name: object.getName() });
|
|
||||||
} catch (err) {
|
|
||||||
Notifications.error(err);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
labelOk: `Resume`,
|
|
||||||
message: (
|
|
||||||
<p>
|
|
||||||
Resume CronJob <b>{object.getName()}</b>?
|
|
||||||
</p>),
|
|
||||||
})}>
|
|
||||||
<Icon material="play_circle_outline" tooltip="Resume" interactive={toolbar}/>
|
|
||||||
<span className="title">Resume</span>
|
|
||||||
</MenuItem>
|
|
||||||
|
|
||||||
: <MenuItem onClick={() => ConfirmDialog.open({
|
|
||||||
ok: async () => {
|
|
||||||
try {
|
|
||||||
await cronJobApi.suspend({ namespace: object.getNs(), name: object.getName() });
|
|
||||||
} catch (err) {
|
|
||||||
Notifications.error(err);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
labelOk: `Suspend`,
|
|
||||||
message: (
|
|
||||||
<p>
|
|
||||||
Suspend CronJob <b>{object.getName()}</b>?
|
|
||||||
</p>),
|
|
||||||
})}>
|
|
||||||
<Icon material="pause_circle_filled" tooltip="Suspend" interactive={toolbar}/>
|
|
||||||
<span className="title">Suspend</span>
|
|
||||||
</MenuItem>
|
|
||||||
}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import React from "react";
|
||||||
|
import type { KubeObjectMenuProps } from "../kube-object-menu";
|
||||||
|
import { Deployment, deploymentApi } from "../../../common/k8s-api/endpoints";
|
||||||
|
import { MenuItem } from "../menu";
|
||||||
|
import { DeploymentScaleDialog } from "./deployment-scale-dialog";
|
||||||
|
import { Icon } from "../icon";
|
||||||
|
import { ConfirmDialog } from "../confirm-dialog";
|
||||||
|
import { Notifications } from "../notifications";
|
||||||
|
|
||||||
|
export function DeploymentMenu(props: KubeObjectMenuProps<Deployment>) {
|
||||||
|
const { object, toolbar } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MenuItem onClick={() => DeploymentScaleDialog.open(object)}>
|
||||||
|
<Icon material="open_with" tooltip="Scale" interactive={toolbar}/>
|
||||||
|
<span className="title">Scale</span>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem onClick={() => ConfirmDialog.open({
|
||||||
|
ok: async () =>
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
await deploymentApi.restart({
|
||||||
|
namespace: object.getNs(),
|
||||||
|
name: object.getName(),
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
Notifications.error(err);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
labelOk: `Restart`,
|
||||||
|
message: (
|
||||||
|
<p>
|
||||||
|
Are you sure you want to restart deployment <b>{object.getName()}</b>?
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
})}>
|
||||||
|
<Icon material="autorenew" tooltip="Restart" interactive={toolbar}/>
|
||||||
|
<span className="title">Restart</span>
|
||||||
|
</MenuItem>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -8,12 +8,7 @@ import "./deployments.scss";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import type { RouteComponentProps } from "react-router";
|
import type { RouteComponentProps } from "react-router";
|
||||||
import { Deployment, deploymentApi } from "../../../common/k8s-api/endpoints";
|
import type { Deployment } from "../../../common/k8s-api/endpoints";
|
||||||
import type { KubeObjectMenuProps } from "../kube-object-menu";
|
|
||||||
import { MenuItem } from "../menu";
|
|
||||||
import { Icon } from "../icon";
|
|
||||||
import { DeploymentScaleDialog } from "./deployment-scale-dialog";
|
|
||||||
import { ConfirmDialog } from "../confirm-dialog";
|
|
||||||
import { deploymentStore } from "./deployments.store";
|
import { deploymentStore } from "./deployments.store";
|
||||||
import { eventStore } from "../+events/event.store";
|
import { eventStore } from "../+events/event.store";
|
||||||
import { KubeObjectListLayout } from "../kube-object-list-layout";
|
import { KubeObjectListLayout } from "../kube-object-list-layout";
|
||||||
@ -21,7 +16,6 @@ import { cssNames } from "../../utils";
|
|||||||
import kebabCase from "lodash/kebabCase";
|
import kebabCase from "lodash/kebabCase";
|
||||||
import orderBy from "lodash/orderBy";
|
import orderBy from "lodash/orderBy";
|
||||||
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
||||||
import { Notifications } from "../notifications";
|
|
||||||
import type { DeploymentsRouteParams } from "../../../common/routes";
|
import type { DeploymentsRouteParams } from "../../../common/routes";
|
||||||
|
|
||||||
enum columnId {
|
enum columnId {
|
||||||
@ -91,43 +85,7 @@ export class Deployments extends React.Component<Props> {
|
|||||||
deployment.getAge(),
|
deployment.getAge(),
|
||||||
this.renderConditions(deployment),
|
this.renderConditions(deployment),
|
||||||
]}
|
]}
|
||||||
renderItemMenu={item => <DeploymentMenu object={item} />}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DeploymentMenu(props: KubeObjectMenuProps<Deployment>) {
|
|
||||||
const { object, toolbar } = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<MenuItem onClick={() => DeploymentScaleDialog.open(object)}>
|
|
||||||
<Icon material="open_with" tooltip="Scale" interactive={toolbar}/>
|
|
||||||
<span className="title">Scale</span>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem onClick={() => ConfirmDialog.open({
|
|
||||||
ok: async () =>
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
await deploymentApi.restart({
|
|
||||||
namespace: object.getNs(),
|
|
||||||
name: object.getName(),
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
Notifications.error(err);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
labelOk: `Restart`,
|
|
||||||
message: (
|
|
||||||
<p>
|
|
||||||
Are you sure you want to restart deployment <b>{object.getName()}</b>?
|
|
||||||
</p>
|
|
||||||
),
|
|
||||||
})}>
|
|
||||||
<Icon material="autorenew" tooltip="Restart" interactive={toolbar}/>
|
|
||||||
<span className="title">Restart</span>
|
|
||||||
</MenuItem>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@ -20,19 +20,22 @@ interface Props {
|
|||||||
export class PodDetailsSecrets extends Component<Props> {
|
export class PodDetailsSecrets extends Component<Props> {
|
||||||
@observable secrets: Map<string, Secret> = observable.map<string, Secret>();
|
@observable secrets: Map<string, Secret> = observable.map<string, Secret>();
|
||||||
|
|
||||||
@disposeOnUnmount
|
componentDidMount(): void {
|
||||||
secretsLoader = autorun(async () => {
|
disposeOnUnmount(this, [
|
||||||
const { pod } = this.props;
|
autorun(async () => {
|
||||||
|
const { pod } = this.props;
|
||||||
|
|
||||||
const secrets = await Promise.all(
|
const secrets = await Promise.all(
|
||||||
pod.getSecrets().map(secretName => secretsApi.get({
|
pod.getSecrets().map(secretName => secretsApi.get({
|
||||||
name: secretName,
|
name: secretName,
|
||||||
namespace: pod.getNs(),
|
namespace: pod.getNs(),
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
secrets.forEach(secret => secret && this.secrets.set(secret.getName(), secret));
|
secrets.forEach(secret => secret && this.secrets.set(secret.getName(), secret));
|
||||||
});
|
}),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|||||||
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import React from "react";
|
||||||
|
import type { KubeObjectMenuProps } from "../kube-object-menu";
|
||||||
|
import type { ReplicaSet } from "../../../common/k8s-api/endpoints";
|
||||||
|
import { MenuItem } from "../menu";
|
||||||
|
import { ReplicaSetScaleDialog } from "./replicaset-scale-dialog";
|
||||||
|
import { Icon } from "../icon";
|
||||||
|
|
||||||
|
export function ReplicaSetMenu(props: KubeObjectMenuProps<ReplicaSet>) {
|
||||||
|
const { object, toolbar } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MenuItem onClick={() => ReplicaSetScaleDialog.open(object)}>
|
||||||
|
<Icon material="open_with" tooltip="Scale" interactive={toolbar}/>
|
||||||
|
<span className="title">Scale</span>
|
||||||
|
</MenuItem>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -7,15 +7,10 @@ import "./replicasets.scss";
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import type { ReplicaSet } from "../../../common/k8s-api/endpoints";
|
|
||||||
import type { KubeObjectMenuProps } from "../kube-object-menu";
|
|
||||||
import { replicaSetStore } from "./replicasets.store";
|
import { replicaSetStore } from "./replicasets.store";
|
||||||
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
||||||
import type { RouteComponentProps } from "react-router";
|
import type { RouteComponentProps } from "react-router";
|
||||||
import { KubeObjectListLayout } from "../kube-object-list-layout";
|
import { KubeObjectListLayout } from "../kube-object-list-layout";
|
||||||
import { MenuItem } from "../menu/menu";
|
|
||||||
import { Icon } from "../icon/icon";
|
|
||||||
import { ReplicaSetScaleDialog } from "./replicaset-scale-dialog";
|
|
||||||
import type { ReplicaSetsRouteParams } from "../../../common/routes";
|
import type { ReplicaSetsRouteParams } from "../../../common/routes";
|
||||||
import { eventStore } from "../+events/event.store";
|
import { eventStore } from "../+events/event.store";
|
||||||
|
|
||||||
@ -70,23 +65,8 @@ export class ReplicaSets extends React.Component<Props> {
|
|||||||
replicaSet.getReady(),
|
replicaSet.getReady(),
|
||||||
replicaSet.getAge(),
|
replicaSet.getAge(),
|
||||||
]}
|
]}
|
||||||
renderItemMenu={(item: ReplicaSet) => {
|
|
||||||
return <ReplicaSetMenu object={item}/>;
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ReplicaSetMenu(props: KubeObjectMenuProps<ReplicaSet>) {
|
|
||||||
const { object, toolbar } = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<MenuItem onClick={() => ReplicaSetScaleDialog.open(object)}>
|
|
||||||
<Icon material="open_with" tooltip="Scale" interactive={toolbar}/>
|
|
||||||
<span className="title">Scale</span>
|
|
||||||
</MenuItem>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import React from "react";
|
||||||
|
import type { KubeObjectMenuProps } from "../kube-object-menu";
|
||||||
|
import type { StatefulSet } from "../../../common/k8s-api/endpoints";
|
||||||
|
import { MenuItem } from "../menu";
|
||||||
|
import { StatefulSetScaleDialog } from "./statefulset-scale-dialog";
|
||||||
|
import { Icon } from "../icon";
|
||||||
|
|
||||||
|
export function StatefulSetMenu(props: KubeObjectMenuProps<StatefulSet>) {
|
||||||
|
const { object, toolbar } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MenuItem onClick={() => StatefulSetScaleDialog.open(object)}>
|
||||||
|
<Icon material="open_with" tooltip="Scale" interactive={toolbar}/>
|
||||||
|
<span className="title">Scale</span>
|
||||||
|
</MenuItem>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -12,12 +12,8 @@ import type { StatefulSet } from "../../../common/k8s-api/endpoints";
|
|||||||
import { podsStore } from "../+workloads-pods/pods.store";
|
import { podsStore } from "../+workloads-pods/pods.store";
|
||||||
import { statefulSetStore } from "./statefulset.store";
|
import { statefulSetStore } from "./statefulset.store";
|
||||||
import { eventStore } from "../+events/event.store";
|
import { eventStore } from "../+events/event.store";
|
||||||
import type { KubeObjectMenuProps } from "../kube-object-menu";
|
|
||||||
import { KubeObjectListLayout } from "../kube-object-list-layout";
|
import { KubeObjectListLayout } from "../kube-object-list-layout";
|
||||||
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
||||||
import { StatefulSetScaleDialog } from "./statefulset-scale-dialog";
|
|
||||||
import { MenuItem } from "../menu/menu";
|
|
||||||
import { Icon } from "../icon/icon";
|
|
||||||
import type { StatefulSetsRouteParams } from "../../../common/routes";
|
import type { StatefulSetsRouteParams } from "../../../common/routes";
|
||||||
|
|
||||||
enum columnId {
|
enum columnId {
|
||||||
@ -72,21 +68,7 @@ export class StatefulSets extends React.Component<Props> {
|
|||||||
<KubeObjectStatusIcon key="icon" object={statefulSet}/>,
|
<KubeObjectStatusIcon key="icon" object={statefulSet}/>,
|
||||||
statefulSet.getAge(),
|
statefulSet.getAge(),
|
||||||
]}
|
]}
|
||||||
renderItemMenu={item => <StatefulSetMenu object={item} />}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function StatefulSetMenu(props: KubeObjectMenuProps<StatefulSet>) {
|
|
||||||
const { object, toolbar } = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<MenuItem onClick={() => StatefulSetScaleDialog.open(object)}>
|
|
||||||
<Icon material="open_with" tooltip="Scale" interactive={toolbar}/>
|
|
||||||
<span className="title">Scale</span>
|
|
||||||
</MenuItem>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* 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 rendererExtensionsInjectable from "../../../extensions/renderer-extensions.injectable";
|
||||||
|
import type { StatusBarRegistration } from "./status-bar-registration";
|
||||||
|
|
||||||
|
const bottomBarItemsInjectable = getInjectable({
|
||||||
|
instantiate: (di) => {
|
||||||
|
const extensions = di.inject(rendererExtensionsInjectable);
|
||||||
|
|
||||||
|
return computed(() =>
|
||||||
|
extensions
|
||||||
|
.get()
|
||||||
|
.flatMap((extension) => extension.statusBarItems)
|
||||||
|
.sort(leftItemsBeforeRight),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default bottomBarItemsInjectable;
|
||||||
|
|
||||||
|
const leftItemsBeforeRight = (firstItem: StatusBarRegistration, secondItem: StatusBarRegistration) =>
|
||||||
|
firstItem.components?.position?.localeCompare(secondItem.components?.position);
|
||||||
@ -4,10 +4,15 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { render } from "@testing-library/react";
|
|
||||||
import "@testing-library/jest-dom/extend-expect";
|
import "@testing-library/jest-dom/extend-expect";
|
||||||
import { BottomBar } from "./bottom-bar";
|
import { BottomBar } from "./bottom-bar";
|
||||||
import { StatusBarRegistry } from "../../../extensions/registries";
|
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||||
|
import type { DiRender } from "../test-utils/renderFor";
|
||||||
|
import { renderFor } from "../test-utils/renderFor";
|
||||||
|
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
|
import { LensRendererExtension } from "../../../extensions/lens-renderer-extension";
|
||||||
|
import { computed, IObservableArray, observable, runInAction } from "mobx";
|
||||||
|
import rendererExtensionsInjectable from "../../../extensions/renderer-extensions.injectable";
|
||||||
|
|
||||||
jest.mock("electron", () => ({
|
jest.mock("electron", () => ({
|
||||||
app: {
|
app: {
|
||||||
@ -15,13 +20,43 @@ jest.mock("electron", () => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe("<BottomBar />", () => {
|
class SomeTestExtension extends LensRendererExtension {
|
||||||
beforeEach(() => {
|
constructor(statusBarItems: IObservableArray<any>) {
|
||||||
StatusBarRegistry.createInstance();
|
super({
|
||||||
});
|
id: "some-id",
|
||||||
|
absolutePath: "irrelevant",
|
||||||
|
isBundled: false,
|
||||||
|
isCompatible: false,
|
||||||
|
isEnabled: false,
|
||||||
|
manifest: { name: "some-id", version: "some-version" },
|
||||||
|
manifestPath: "irrelevant",
|
||||||
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
this.statusBarItems = statusBarItems;
|
||||||
StatusBarRegistry.resetInstance();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("<BottomBar />", () => {
|
||||||
|
let render: DiRender;
|
||||||
|
let statusBarItems: IObservableArray<any>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
|
||||||
|
statusBarItems = observable.array([]);
|
||||||
|
|
||||||
|
const someTestExtension = new SomeTestExtension(statusBarItems);
|
||||||
|
|
||||||
|
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
|
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||||
|
|
||||||
|
di.override(rendererExtensionsInjectable, () => {
|
||||||
|
return computed(() => [someTestExtension]);
|
||||||
|
});
|
||||||
|
|
||||||
|
render = renderFor(di);
|
||||||
|
|
||||||
|
await di.runSetups();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders w/o errors", () => {
|
it("renders w/o errors", () => {
|
||||||
@ -30,6 +65,7 @@ describe("<BottomBar />", () => {
|
|||||||
expect(container).toBeInstanceOf(HTMLElement);
|
expect(container).toBeInstanceOf(HTMLElement);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
undefined,
|
undefined,
|
||||||
"hello",
|
"hello",
|
||||||
@ -39,7 +75,10 @@ describe("<BottomBar />", () => {
|
|||||||
[{}],
|
[{}],
|
||||||
{},
|
{},
|
||||||
])("renders w/o errors when .getItems() returns not type compliant (%p)", val => {
|
])("renders w/o errors when .getItems() returns not type compliant (%p)", val => {
|
||||||
StatusBarRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => val);
|
runInAction(() => {
|
||||||
|
statusBarItems.replace([val]);
|
||||||
|
});
|
||||||
|
|
||||||
expect(() => render(<BottomBar />)).not.toThrow();
|
expect(() => render(<BottomBar />)).not.toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -47,9 +86,12 @@ describe("<BottomBar />", () => {
|
|||||||
const testId = "testId";
|
const testId = "testId";
|
||||||
const text = "heee";
|
const text = "heee";
|
||||||
|
|
||||||
StatusBarRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => [
|
runInAction(() => {
|
||||||
{ item: <span data-testid={testId} >{text}</span> },
|
statusBarItems.replace([
|
||||||
]);
|
{ item: <span data-testid={testId} >{text}</span> },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
const { getByTestId } = render(<BottomBar />);
|
const { getByTestId } = render(<BottomBar />);
|
||||||
|
|
||||||
expect(getByTestId(testId)).toHaveTextContent(text);
|
expect(getByTestId(testId)).toHaveTextContent(text);
|
||||||
@ -59,9 +101,12 @@ describe("<BottomBar />", () => {
|
|||||||
const testId = "testId";
|
const testId = "testId";
|
||||||
const text = "heee";
|
const text = "heee";
|
||||||
|
|
||||||
StatusBarRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => [
|
runInAction(() => {
|
||||||
{ item: () => <span data-testid={testId} >{text}</span> },
|
statusBarItems.replace([
|
||||||
]);
|
{ item: () => <span data-testid={testId} >{text}</span> },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
const { getByTestId } = render(<BottomBar />);
|
const { getByTestId } = render(<BottomBar />);
|
||||||
|
|
||||||
expect(getByTestId(testId)).toHaveTextContent(text);
|
expect(getByTestId(testId)).toHaveTextContent(text);
|
||||||
@ -69,31 +114,33 @@ describe("<BottomBar />", () => {
|
|||||||
|
|
||||||
|
|
||||||
it("sort positioned items properly", () => {
|
it("sort positioned items properly", () => {
|
||||||
StatusBarRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => [
|
runInAction(() => {
|
||||||
{
|
statusBarItems.replace([
|
||||||
components: {
|
{
|
||||||
Item: () => <div data-testid="sortedElem">right</div>,
|
components: {
|
||||||
|
Item: () => <div data-testid="sortedElem">right</div>,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
components: {
|
||||||
components: {
|
Item: () => <div data-testid="sortedElem">right</div>,
|
||||||
Item: () => <div data-testid="sortedElem">right</div>,
|
position: "right",
|
||||||
position: "right",
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
components: {
|
||||||
components: {
|
Item: () => <div data-testid="sortedElem">left</div>,
|
||||||
Item: () => <div data-testid="sortedElem">left</div>,
|
position: "left",
|
||||||
position: "left",
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
components: {
|
||||||
components: {
|
Item: () => <div data-testid="sortedElem">left</div>,
|
||||||
Item: () => <div data-testid="sortedElem">left</div>,
|
position: "left",
|
||||||
position: "left",
|
},
|
||||||
},
|
},
|
||||||
},
|
]);
|
||||||
]);
|
});
|
||||||
|
|
||||||
const { getAllByTestId } = render(<BottomBar />);
|
const { getAllByTestId } = render(<BottomBar />);
|
||||||
const elems = getAllByTestId("sortedElem");
|
const elems = getAllByTestId("sortedElem");
|
||||||
|
|||||||
@ -7,11 +7,18 @@ import styles from "./bottom-bar.module.scss";
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { StatusBarRegistration, StatusBarRegistry } from "../../../extensions/registries";
|
|
||||||
import { cssNames } from "../../utils";
|
import { cssNames } from "../../utils";
|
||||||
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
|
import bottomBarItemsInjectable from "./bottom-bar-items.injectable";
|
||||||
|
import type { IComputedValue } from "mobx";
|
||||||
|
import type { StatusBarRegistration } from "./status-bar-registration";
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
items: IComputedValue<StatusBarRegistration[]>
|
||||||
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class BottomBar extends React.Component {
|
class NonInjectedBottomBar extends React.Component<Dependencies> {
|
||||||
renderRegisteredItem(registration: StatusBarRegistration) {
|
renderRegisteredItem(registration: StatusBarRegistration) {
|
||||||
const { item } = registration;
|
const { item } = registration;
|
||||||
|
|
||||||
@ -23,19 +30,9 @@ export class BottomBar extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderRegisteredItems() {
|
renderRegisteredItems() {
|
||||||
const items = StatusBarRegistry.getInstance().getItems();
|
|
||||||
|
|
||||||
if (!Array.isArray(items)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
items.sort(function sortLeftPositionFirst(a, b) {
|
|
||||||
return a.components?.position?.localeCompare(b.components?.position);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{items.map((registration, index) => {
|
{this.props.items.get().map((registration, index) => {
|
||||||
if (!registration?.item && !registration?.components?.Item) {
|
if (!registration?.item && !registration?.components?.Item) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -64,3 +61,14 @@ export class BottomBar extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const BottomBar = withInjectables<Dependencies>(
|
||||||
|
NonInjectedBottomBar,
|
||||||
|
|
||||||
|
{
|
||||||
|
getProps: (di, props) => ({
|
||||||
|
items: di.inject(bottomBarItemsInjectable),
|
||||||
|
...props,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|||||||
@ -2,12 +2,6 @@
|
|||||||
* 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.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Extensions API -> Status bar customizations
|
|
||||||
|
|
||||||
import type React from "react";
|
|
||||||
import { BaseRegistry } from "./base-registry";
|
|
||||||
|
|
||||||
interface StatusBarComponents {
|
interface StatusBarComponents {
|
||||||
Item?: React.ComponentType;
|
Item?: React.ComponentType;
|
||||||
/**
|
/**
|
||||||
@ -28,6 +22,3 @@ export interface StatusBarRegistration extends StatusBarRegistrationV2 {
|
|||||||
*/
|
*/
|
||||||
item?: React.ReactNode;
|
item?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class StatusBarRegistry extends BaseRegistry<StatusBarRegistration> {
|
|
||||||
}
|
|
||||||
@ -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";
|
||||||
|
|||||||
@ -48,9 +48,6 @@ export class Dialog extends React.PureComponent<DialogProps, DialogState> {
|
|||||||
pinned: false,
|
pinned: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@disposeOnUnmount
|
|
||||||
closeOnNavigate = reaction(() => navigation.toString(), () => this.close());
|
|
||||||
|
|
||||||
public state: DialogState = {
|
public state: DialogState = {
|
||||||
isOpen: this.props.isOpen,
|
isOpen: this.props.isOpen,
|
||||||
};
|
};
|
||||||
@ -64,7 +61,13 @@ export class Dialog extends React.PureComponent<DialogProps, DialogState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (this.isOpen) this.onOpen();
|
if (this.isOpen) {
|
||||||
|
this.onOpen();
|
||||||
|
}
|
||||||
|
|
||||||
|
disposeOnUnmount(this, [
|
||||||
|
reaction(() => navigation.toString(), () => this.close()),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps: DialogProps) {
|
componentDidUpdate(prevProps: DialogProps) {
|
||||||
|
|||||||
@ -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),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user