mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Complete a majority of the rest of DI
- All KubeApi and KubeObjectStore's - All of Catalog - ApiManager - KubeObjectDetailRegistry - All BaseStore's and their migrations - LensProxy and Router - WindowManager - App Menu - Tray Icon - ThemeStore Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
6cba82c491
commit
1747cbf6c8
@ -11,6 +11,7 @@ module.exports = {
|
|||||||
"**/dist/**/*",
|
"**/dist/**/*",
|
||||||
"**/static/**/*",
|
"**/static/**/*",
|
||||||
"**/site/**/*",
|
"**/site/**/*",
|
||||||
|
"**/__mocks__/**/*",
|
||||||
],
|
],
|
||||||
settings: {
|
settings: {
|
||||||
react: {
|
react: {
|
||||||
@ -109,12 +110,15 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
ecmaVersion: 2018,
|
ecmaVersion: 2018,
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
project: ["./tsconfig.json"],
|
||||||
sourceType: "module",
|
sourceType: "module",
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
"no-constant-condition": ["error", { "checkLoops": false }],
|
"no-constant-condition": ["error", { "checkLoops": false }],
|
||||||
"header/header": [2, "./license-header"],
|
"header/header": [2, "./license-header"],
|
||||||
"no-invalid-this": "off",
|
"no-invalid-this": "off",
|
||||||
|
"@typescript-eslint/await-thenable": "error",
|
||||||
"@typescript-eslint/no-invalid-this": ["error"],
|
"@typescript-eslint/no-invalid-this": ["error"],
|
||||||
"@typescript-eslint/explicit-function-return-type": "off",
|
"@typescript-eslint/explicit-function-return-type": "off",
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
@ -129,6 +133,7 @@ module.exports = {
|
|||||||
"named": "never",
|
"named": "never",
|
||||||
"asyncArrow": "always",
|
"asyncArrow": "always",
|
||||||
}],
|
}],
|
||||||
|
"require-await": "error",
|
||||||
"unused-imports/no-unused-imports-ts": process.env.PROD === "true" ? "error" : "warn",
|
"unused-imports/no-unused-imports-ts": process.env.PROD === "true" ? "error" : "warn",
|
||||||
"unused-imports/no-unused-vars-ts": [
|
"unused-imports/no-unused-vars-ts": [
|
||||||
"warn", {
|
"warn", {
|
||||||
@ -193,7 +198,9 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
ecmaVersion: 2018,
|
ecmaVersion: 2018,
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
sourceType: "module",
|
sourceType: "module",
|
||||||
|
project: ["./tsconfig.json"],
|
||||||
jsx: true,
|
jsx: true,
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
@ -201,6 +208,7 @@ module.exports = {
|
|||||||
"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",
|
||||||
|
"@typescript-eslint/await-thenable": "error",
|
||||||
"@typescript-eslint/no-invalid-this": ["error"],
|
"@typescript-eslint/no-invalid-this": ["error"],
|
||||||
"@typescript-eslint/explicit-function-return-type": "off",
|
"@typescript-eslint/explicit-function-return-type": "off",
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
@ -220,6 +228,7 @@ module.exports = {
|
|||||||
"named": "never",
|
"named": "never",
|
||||||
"asyncArrow": "always",
|
"asyncArrow": "always",
|
||||||
}],
|
}],
|
||||||
|
"require-await": "error",
|
||||||
"unused-imports/no-unused-imports-ts": process.env.PROD === "true" ? "error" : "warn",
|
"unused-imports/no-unused-imports-ts": process.env.PROD === "true" ? "error" : "warn",
|
||||||
"unused-imports/no-unused-vars-ts": [
|
"unused-imports/no-unused-vars-ts": [
|
||||||
"warn", {
|
"warn", {
|
||||||
|
|||||||
20
__mocks__/monaco-editor.ts
Normal file
20
__mocks__/monaco-editor.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
languages: {
|
||||||
|
register: jest.fn(),
|
||||||
|
setMonarchTokensProvider: jest.fn(),
|
||||||
|
registerCompletionItemProvider: jest.fn(),
|
||||||
|
},
|
||||||
|
editor: {
|
||||||
|
defineTheme: jest.fn(),
|
||||||
|
getModel: jest.fn(),
|
||||||
|
createModel: jest.fn(),
|
||||||
|
},
|
||||||
|
Uri: {
|
||||||
|
file: jest.fn(),
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -5,11 +5,11 @@
|
|||||||
|
|
||||||
import fs from "fs-extra";
|
import fs from "fs-extra";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import defaultBaseLensTheme from "../src/renderer/themes/lens-dark.json";
|
import defaultBaseLensTheme from "../src/renderer/internal-themes/lens-dark.json";
|
||||||
|
|
||||||
const outputCssFile = path.resolve("src/renderer/themes/theme-vars.css");
|
const outputCssFile = path.resolve("src/renderer/internal-themes/theme-vars.css");
|
||||||
|
|
||||||
const banner = `/*
|
const banner = `/*
|
||||||
Generated Lens theme CSS-variables, don't edit manually.
|
Generated Lens theme CSS-variables, don't edit manually.
|
||||||
To refresh file run $: yarn run ts-node build/${path.basename(__filename)}
|
To refresh file run $: yarn run ts-node build/${path.basename(__filename)}
|
||||||
*/`;
|
*/`;
|
||||||
@ -28,4 +28,4 @@ ${themeCssVars.join("\n")}
|
|||||||
// Run
|
// Run
|
||||||
console.info(`"Saving default Lens theme css-variables to "${outputCssFile}""`);
|
console.info(`"Saving default Lens theme css-variables to "${outputCssFile}""`);
|
||||||
fs.ensureFileSync(outputCssFile);
|
fs.ensureFileSync(outputCssFile);
|
||||||
fs.writeFile(outputCssFile, content);
|
fs.writeFileSync(outputCssFile, content);
|
||||||
|
|||||||
@ -38,7 +38,7 @@ export async function generateTrayIcon(
|
|||||||
await fs.writeFile(pngIconDestPath, pngIconBuffer);
|
await fs.writeFile(pngIconDestPath, pngIconBuffer);
|
||||||
console.info(`[DONE]: Tray icon saved at "${pngIconDestPath}"`);
|
console.info(`[DONE]: Tray icon saved at "${pngIconDestPath}"`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`[ERROR]: ${err}`);
|
console.error(`[ERROR]: ${String(err)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,7 +49,9 @@ const iconSizes: Record<string, number> = {
|
|||||||
"3x": 48,
|
"3x": 48,
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.entries(iconSizes).forEach(([dpiSuffix, pixelSize]) => {
|
for (const dpiSuffix in iconSizes) {
|
||||||
|
const pixelSize = iconSizes[dpiSuffix];
|
||||||
|
|
||||||
generateTrayIcon({ dpiSuffix, pixelSize, shouldUseDarkColors: false });
|
generateTrayIcon({ dpiSuffix, pixelSize, shouldUseDarkColors: false });
|
||||||
generateTrayIcon({ dpiSuffix, pixelSize, shouldUseDarkColors: true });
|
generateTrayIcon({ dpiSuffix, pixelSize, shouldUseDarkColors: true });
|
||||||
});
|
}
|
||||||
|
|||||||
@ -32,7 +32,7 @@ class KubectlDownloader {
|
|||||||
method: "HEAD",
|
method: "HEAD",
|
||||||
uri: this.url,
|
uri: this.url,
|
||||||
resolveWithFullResponse: true,
|
resolveWithFullResponse: true,
|
||||||
}).catch(console.error);
|
});
|
||||||
|
|
||||||
if (response.headers["etag"]) {
|
if (response.headers["etag"]) {
|
||||||
return response.headers["etag"].replace(/"/g, "");
|
return response.headers["etag"].replace(/"/g, "");
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "kube-object-event-status",
|
"name": "kube-object-event-status",
|
||||||
"version": "0.0.1",
|
"version": "5.3.0-latest.1642433271626.1642519794031",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "kube-object-event-status",
|
"name": "kube-object-event-status",
|
||||||
"version": "0.0.1",
|
"version": "5.3.0-latest.1642433271626.1642519794031",
|
||||||
"description": "Adds kube object status from events",
|
"description": "Adds kube object status from events",
|
||||||
"renderer": "dist/renderer.js",
|
"renderer": "dist/renderer.js",
|
||||||
"lens": {
|
"lens": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lens-metrics-cluster-feature",
|
"name": "lens-metrics-cluster-feature",
|
||||||
"version": "0.0.1",
|
"version": "5.3.0-latest.1642433271626.1642519794031",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lens-metrics-cluster-feature",
|
"name": "lens-metrics-cluster-feature",
|
||||||
"version": "0.0.1",
|
"version": "5.3.0-latest.1642433271626.1642519794031",
|
||||||
"description": "Lens metrics cluster feature",
|
"description": "Lens metrics cluster feature",
|
||||||
"renderer": "dist/renderer.js",
|
"renderer": "dist/renderer.js",
|
||||||
"lens": {
|
"lens": {
|
||||||
|
|||||||
@ -71,7 +71,7 @@ export class MetricsFeature {
|
|||||||
return this.stack.kubectlApplyFolder(this.resourceFolder, config, ["--prune"]);
|
return this.stack.kubectlApplyFolder(this.resourceFolder, config, ["--prune"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async upgrade(config: MetricsConfiguration): Promise<string> {
|
upgrade(config: MetricsConfiguration): Promise<string> {
|
||||||
return this.install(config);
|
return this.install(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,7 +101,7 @@ export class MetricsFeature {
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
async uninstall(config: MetricsConfiguration): Promise<string> {
|
uninstall(config: MetricsConfiguration): Promise<string> {
|
||||||
return this.stack.kubectlDeleteFolder(this.resourceFolder, config);
|
return this.stack.kubectlDeleteFolder(this.resourceFolder, config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -156,17 +156,17 @@ export class MetricsSettings extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async togglePrometheus(enabled: boolean) {
|
togglePrometheus(enabled: boolean) {
|
||||||
this.featureStates.prometheus = enabled;
|
this.featureStates.prometheus = enabled;
|
||||||
this.changed = true;
|
this.changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async toggleKubeStateMetrics(enabled: boolean) {
|
toggleKubeStateMetrics(enabled: boolean) {
|
||||||
this.featureStates.kubeStateMetrics = enabled;
|
this.featureStates.kubeStateMetrics = enabled;
|
||||||
this.changed = true;
|
this.changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async toggleNodeExporter(enabled: boolean) {
|
toggleNodeExporter(enabled: boolean) {
|
||||||
this.featureStates.nodeExporter = enabled;
|
this.featureStates.nodeExporter = enabled;
|
||||||
this.changed = true;
|
this.changed = true;
|
||||||
}
|
}
|
||||||
|
|||||||
2
extensions/node-menu/package-lock.json
generated
2
extensions/node-menu/package-lock.json
generated
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lens-node-menu",
|
"name": "lens-node-menu",
|
||||||
"version": "0.0.1",
|
"version": "5.3.0-latest.1642433271626.1642519794031",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lens-node-menu",
|
"name": "lens-node-menu",
|
||||||
"version": "0.0.1",
|
"version": "5.3.0-latest.1642433271626.1642519794031",
|
||||||
"description": "Lens node menu",
|
"description": "Lens node menu",
|
||||||
"renderer": "dist/renderer.js",
|
"renderer": "dist/renderer.js",
|
||||||
"lens": {
|
"lens": {
|
||||||
|
|||||||
2
extensions/pod-menu/package-lock.json
generated
2
extensions/pod-menu/package-lock.json
generated
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lens-pod-menu",
|
"name": "lens-pod-menu",
|
||||||
"version": "0.0.1",
|
"version": "5.3.0-latest.1642433271626.1642519794031",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lens-pod-menu",
|
"name": "lens-pod-menu",
|
||||||
"version": "0.0.1",
|
"version": "5.3.0-latest.1642433271626.1642519794031",
|
||||||
"description": "Lens pod menu",
|
"description": "Lens pod menu",
|
||||||
"renderer": "dist/renderer.js",
|
"renderer": "dist/renderer.js",
|
||||||
"lens": {
|
"lens": {
|
||||||
|
|||||||
@ -55,7 +55,7 @@ export class PodAttachMenu extends React.Component<PodAttachMenuProps> {
|
|||||||
title: `Pod: ${pod.getName()} (namespace: ${pod.getNs()}) [Attached]`,
|
title: `Pod: ${pod.getName()} (namespace: ${pod.getNs()}) [Attached]`,
|
||||||
});
|
});
|
||||||
|
|
||||||
terminalStore.sendCommand(commandParts.join(" "), {
|
await terminalStore.sendCommand(commandParts.join(" "), {
|
||||||
enter: true,
|
enter: true,
|
||||||
tabId: shell.id,
|
tabId: shell.id,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -63,7 +63,7 @@ export class PodShellMenu extends React.Component<PodShellMenuProps> {
|
|||||||
title: `Pod: ${pod.getName()} (namespace: ${pod.getNs()})`,
|
title: `Pod: ${pod.getName()} (namespace: ${pod.getNs()})`,
|
||||||
});
|
});
|
||||||
|
|
||||||
terminalStore.sendCommand(commandParts.join(" "), {
|
await terminalStore.sendCommand(commandParts.join(" "), {
|
||||||
enter: true,
|
enter: true,
|
||||||
tabId: shell.id,
|
tabId: shell.id,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
"productName": "OpenLens",
|
"productName": "OpenLens",
|
||||||
"description": "OpenLens - Open Source IDE for Kubernetes",
|
"description": "OpenLens - Open Source IDE for Kubernetes",
|
||||||
"homepage": "https://github.com/lensapp/lens",
|
"homepage": "https://github.com/lensapp/lens",
|
||||||
"version": "5.3.0",
|
"version": "5.3.4",
|
||||||
"main": "static/build/main.js",
|
"main": "static/build/main.js",
|
||||||
"copyright": "© 2021 OpenLens Authors",
|
"copyright": "© 2021 OpenLens Authors",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -62,7 +62,8 @@
|
|||||||
},
|
},
|
||||||
"moduleNameMapper": {
|
"moduleNameMapper": {
|
||||||
"\\.(css|scss)$": "<rootDir>/__mocks__/styleMock.ts",
|
"\\.(css|scss)$": "<rootDir>/__mocks__/styleMock.ts",
|
||||||
"\\.(svg)$": "<rootDir>/__mocks__/imageMock.ts"
|
"\\.(svg)$": "<rootDir>/__mocks__/imageMock.ts",
|
||||||
|
"^monaco-editor": "<rootDir>/node_modules/monaco-editor"
|
||||||
},
|
},
|
||||||
"modulePathIgnorePatterns": [
|
"modulePathIgnorePatterns": [
|
||||||
"<rootDir>/dist",
|
"<rootDir>/dist",
|
||||||
@ -195,8 +196,8 @@
|
|||||||
"@hapi/call": "^8.0.1",
|
"@hapi/call": "^8.0.1",
|
||||||
"@hapi/subtext": "^7.0.3",
|
"@hapi/subtext": "^7.0.3",
|
||||||
"@kubernetes/client-node": "^0.16.1",
|
"@kubernetes/client-node": "^0.16.1",
|
||||||
"@ogre-tools/injectable": "3.1.1",
|
"@ogre-tools/injectable": "3.2.0",
|
||||||
"@ogre-tools/injectable-react": "3.1.1",
|
"@ogre-tools/injectable-react": "3.2.0",
|
||||||
"@sentry/electron": "^2.5.4",
|
"@sentry/electron": "^2.5.4",
|
||||||
"@sentry/integrations": "^6.15.0",
|
"@sentry/integrations": "^6.15.0",
|
||||||
"@types/circular-dependency-plugin": "5.0.4",
|
"@types/circular-dependency-plugin": "5.0.4",
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import { readFileSync } from "fs";
|
|||||||
import { getDisForUnitTesting } from "../../test-utils/get-dis-for-unit-testing";
|
import { getDisForUnitTesting } from "../../test-utils/get-dis-for-unit-testing";
|
||||||
|
|
||||||
import directoryForUserDataInjectable
|
import directoryForUserDataInjectable
|
||||||
from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
from "../app-paths/directory-for-user-data.injectable";
|
||||||
|
|
||||||
jest.mock("electron", () => ({
|
jest.mock("electron", () => ({
|
||||||
ipcMain: {
|
ipcMain: {
|
||||||
@ -59,7 +59,7 @@ class TestStore extends BaseStore<TestStoreModel> {
|
|||||||
super.onSync(data);
|
super.onSync(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveToFile(model: TestStoreModel) {
|
saveToFile(model: TestStoreModel) {
|
||||||
return super.saveToFile(model);
|
return super.saveToFile(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,7 +85,6 @@ describe("BaseStore", () => {
|
|||||||
await dis.runSetups();
|
await dis.runSetups();
|
||||||
|
|
||||||
store = undefined;
|
store = undefined;
|
||||||
TestStore.resetInstance();
|
|
||||||
|
|
||||||
const mockOpts = {
|
const mockOpts = {
|
||||||
"some-user-data-directory": {
|
"some-user-data-directory": {
|
||||||
@ -95,12 +94,11 @@ describe("BaseStore", () => {
|
|||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
|
|
||||||
store = TestStore.createInstance();
|
store = new TestStore();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
store.disableSync();
|
store.disableSync();
|
||||||
TestStore.resetInstance();
|
|
||||||
mockFs.restore();
|
mockFs.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -64,26 +64,26 @@ describe("CatalogCategoryRegistry", () => {
|
|||||||
registry.add(new TestCatalogCategory2());
|
registry.add(new TestCatalogCategory2());
|
||||||
|
|
||||||
expect(registry.items.length).toBe(2);
|
expect(registry.items.length).toBe(2);
|
||||||
expect(registry.filteredItems.length).toBe(2);
|
expect(registry.filteredItems.get().length).toBe(2);
|
||||||
|
|
||||||
const disposer = registry.addCatalogCategoryFilter(category => category.metadata.name === "Test Category");
|
const disposer = registry.addCatalogCategoryFilter(category => category.metadata.name === "Test Category");
|
||||||
|
|
||||||
expect(registry.items.length).toBe(2);
|
expect(registry.items.length).toBe(2);
|
||||||
expect(registry.filteredItems.length).toBe(1);
|
expect(registry.filteredItems.get().length).toBe(1);
|
||||||
|
|
||||||
const disposer2 = registry.addCatalogCategoryFilter(category => category.metadata.name === "foo");
|
const disposer2 = registry.addCatalogCategoryFilter(category => category.metadata.name === "foo");
|
||||||
|
|
||||||
expect(registry.items.length).toBe(2);
|
expect(registry.items.length).toBe(2);
|
||||||
expect(registry.filteredItems.length).toBe(0);
|
expect(registry.filteredItems.get().length).toBe(0);
|
||||||
|
|
||||||
disposer();
|
disposer();
|
||||||
|
|
||||||
expect(registry.items.length).toBe(2);
|
expect(registry.items.length).toBe(2);
|
||||||
expect(registry.filteredItems.length).toBe(0);
|
expect(registry.filteredItems.get().length).toBe(0);
|
||||||
|
|
||||||
disposer2();
|
disposer2();
|
||||||
|
|
||||||
expect(registry.items.length).toBe(2);
|
expect(registry.items.length).toBe(2);
|
||||||
expect(registry.filteredItems.length).toBe(2);
|
expect(registry.filteredItems.get().length).toBe(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -8,11 +8,11 @@ import mockFs from "mock-fs";
|
|||||||
import path from "path";
|
import path from "path";
|
||||||
import fse from "fs-extra";
|
import fse from "fs-extra";
|
||||||
import type { Cluster } from "../cluster/cluster";
|
import type { Cluster } from "../cluster/cluster";
|
||||||
import { ClusterStore } from "../cluster-store/cluster-store";
|
import type { ClusterStore } from "../cluster-store/store";
|
||||||
import { Console } from "console";
|
import { Console } from "console";
|
||||||
import { stdout, stderr } from "process";
|
import { stdout, stderr } from "process";
|
||||||
import getCustomKubeConfigDirectoryInjectable from "../app-paths/get-custom-kube-config-directory/get-custom-kube-config-directory.injectable";
|
import getCustomKubeConfigDirectoryInjectable from "../app-paths/get-custom-kube-config-directory.injectable";
|
||||||
import clusterStoreInjectable from "../cluster-store/cluster-store.injectable";
|
import clusterStoreInjectable from "../cluster-store/store.injectable";
|
||||||
import type { ClusterModel } from "../cluster-types";
|
import type { ClusterModel } from "../cluster-types";
|
||||||
import type {
|
import type {
|
||||||
DependencyInjectionContainer,
|
DependencyInjectionContainer,
|
||||||
@ -23,7 +23,7 @@ import { getDisForUnitTesting } from "../../test-utils/get-dis-for-unit-testing"
|
|||||||
import { createClusterInjectionToken } from "../cluster/create-cluster-injection-token";
|
import { createClusterInjectionToken } from "../cluster/create-cluster-injection-token";
|
||||||
|
|
||||||
import directoryForUserDataInjectable
|
import directoryForUserDataInjectable
|
||||||
from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
from "../app-paths/directory-for-user-data.injectable";
|
||||||
|
|
||||||
console = new Console(stdout, stderr);
|
console = new Console(stdout, stderr);
|
||||||
|
|
||||||
@ -100,15 +100,11 @@ describe("cluster-store", () => {
|
|||||||
describe("empty config", () => {
|
describe("empty config", () => {
|
||||||
let getCustomKubeConfigDirectory: (directoryName: string) => string;
|
let getCustomKubeConfigDirectory: (directoryName: string) => string;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(() => {
|
||||||
getCustomKubeConfigDirectory = mainDi.inject(
|
getCustomKubeConfigDirectory = mainDi.inject(
|
||||||
getCustomKubeConfigDirectoryInjectable,
|
getCustomKubeConfigDirectoryInjectable,
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: Remove these by removing Singleton base-class from BaseStore
|
|
||||||
ClusterStore.getInstance(false)?.unregisterIpcListener();
|
|
||||||
ClusterStore.resetInstance();
|
|
||||||
|
|
||||||
const mockOpts = {
|
const mockOpts = {
|
||||||
"some-directory-for-user-data": {
|
"some-directory-for-user-data": {
|
||||||
"lens-cluster-store.json": JSON.stringify({}),
|
"lens-cluster-store.json": JSON.stringify({}),
|
||||||
@ -143,7 +139,7 @@ describe("cluster-store", () => {
|
|||||||
clusterStore.addCluster(cluster);
|
clusterStore.addCluster(cluster);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("adds new cluster to store", async () => {
|
it("adds new cluster to store", () => {
|
||||||
const storedCluster = clusterStore.getById("foo");
|
const storedCluster = clusterStore.getById("foo");
|
||||||
|
|
||||||
expect(storedCluster.id).toBe("foo");
|
expect(storedCluster.id).toBe("foo");
|
||||||
@ -197,8 +193,6 @@ describe("cluster-store", () => {
|
|||||||
|
|
||||||
describe("config with existing clusters", () => {
|
describe("config with existing clusters", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ClusterStore.resetInstance();
|
|
||||||
|
|
||||||
const mockOpts = {
|
const mockOpts = {
|
||||||
"temp-kube-config": kubeconfig,
|
"temp-kube-config": kubeconfig,
|
||||||
"some-directory-for-user-data": {
|
"some-directory-for-user-data": {
|
||||||
@ -251,7 +245,7 @@ describe("cluster-store", () => {
|
|||||||
expect(storedCluster.preferences.terminalCWD).toBe("/foo");
|
expect(storedCluster.preferences.terminalCWD).toBe("/foo");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows getting all of the clusters", async () => {
|
it("allows getting all of the clusters", () => {
|
||||||
const storedClusters = clusterStore.clustersList;
|
const storedClusters = clusterStore.clustersList;
|
||||||
|
|
||||||
expect(storedClusters.length).toBe(3);
|
expect(storedClusters.length).toBe(3);
|
||||||
@ -285,8 +279,6 @@ users:
|
|||||||
token: kubeconfig-user-q4lm4:xxxyyyy
|
token: kubeconfig-user-q4lm4:xxxyyyy
|
||||||
`;
|
`;
|
||||||
|
|
||||||
ClusterStore.resetInstance();
|
|
||||||
|
|
||||||
const mockOpts = {
|
const mockOpts = {
|
||||||
"invalid-kube-config": invalidKubeconfig,
|
"invalid-kube-config": invalidKubeconfig,
|
||||||
"valid-kube-config": kubeconfig,
|
"valid-kube-config": kubeconfig,
|
||||||
@ -335,7 +327,6 @@ users:
|
|||||||
|
|
||||||
describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
|
describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ClusterStore.resetInstance();
|
|
||||||
const mockOpts = {
|
const mockOpts = {
|
||||||
"some-directory-for-user-data": {
|
"some-directory-for-user-data": {
|
||||||
"lens-cluster-store.json": JSON.stringify({
|
"lens-cluster-store.json": JSON.stringify({
|
||||||
@ -368,13 +359,13 @@ users:
|
|||||||
mockFs.restore();
|
mockFs.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("migrates to modern format with kubeconfig in a file", async () => {
|
it("migrates to modern format with kubeconfig in a file", () => {
|
||||||
const config = clusterStore.clustersList[0].kubeConfigPath;
|
const config = clusterStore.clustersList[0].kubeConfigPath;
|
||||||
|
|
||||||
expect(fs.readFileSync(config, "utf8")).toBe(minimalValidKubeConfig);
|
expect(fs.readFileSync(config, "utf8")).toBe(minimalValidKubeConfig);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("migrates to modern format with icon not in file", async () => {
|
it("migrates to modern format with icon not in file", () => {
|
||||||
const { icon } = clusterStore.clustersList[0].preferences;
|
const { icon } = clusterStore.clustersList[0].preferences;
|
||||||
|
|
||||||
expect(icon.startsWith("data:;base64,")).toBe(true);
|
expect(icon.startsWith("data:;base64,")).toBe(true);
|
||||||
|
|||||||
@ -1,416 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { anyObject } from "jest-mock-extended";
|
|
||||||
import { merge } from "lodash";
|
|
||||||
import mockFs from "mock-fs";
|
|
||||||
import logger from "../../main/logger";
|
|
||||||
import type { CatalogEntity, CatalogEntityData, CatalogEntityKindData } from "../catalog";
|
|
||||||
import { HotbarStore } from "../hotbar-store";
|
|
||||||
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
|
||||||
import directoryForUserDataInjectable
|
|
||||||
from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
|
||||||
|
|
||||||
jest.mock("../../main/catalog/catalog-entity-registry", () => ({
|
|
||||||
catalogEntityRegistry: {
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
metadata: {
|
|
||||||
uid: "1dfa26e2ebab15780a3547e9c7fa785c",
|
|
||||||
name: "mycluster",
|
|
||||||
source: "local",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
metadata: {
|
|
||||||
uid: "55b42c3c7ba3b04193416cda405269a5",
|
|
||||||
name: "my_shiny_cluster",
|
|
||||||
source: "remote",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
metadata: {
|
|
||||||
uid: "catalog-entity",
|
|
||||||
name: "Catalog",
|
|
||||||
source: "app",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
function getMockCatalogEntity(data: Partial<CatalogEntityData> & CatalogEntityKindData): CatalogEntity {
|
|
||||||
return merge(data, {
|
|
||||||
getName: jest.fn(() => data.metadata?.name),
|
|
||||||
getId: jest.fn(() => data.metadata?.uid),
|
|
||||||
getSource: jest.fn(() => data.metadata?.source ?? "unknown"),
|
|
||||||
isEnabled: jest.fn(() => data.status?.enabled ?? true),
|
|
||||||
onContextMenuOpen: jest.fn(),
|
|
||||||
onSettingsOpen: jest.fn(),
|
|
||||||
metadata: {},
|
|
||||||
spec: {},
|
|
||||||
status: {},
|
|
||||||
}) as CatalogEntity;
|
|
||||||
}
|
|
||||||
|
|
||||||
const testCluster = getMockCatalogEntity({
|
|
||||||
apiVersion: "v1",
|
|
||||||
kind: "Cluster",
|
|
||||||
status: {
|
|
||||||
phase: "Running",
|
|
||||||
},
|
|
||||||
metadata: {
|
|
||||||
uid: "test",
|
|
||||||
name: "test",
|
|
||||||
labels: {},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const minikubeCluster = getMockCatalogEntity({
|
|
||||||
apiVersion: "v1",
|
|
||||||
kind: "Cluster",
|
|
||||||
status: {
|
|
||||||
phase: "Running",
|
|
||||||
},
|
|
||||||
metadata: {
|
|
||||||
uid: "minikube",
|
|
||||||
name: "minikube",
|
|
||||||
labels: {},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const awsCluster = getMockCatalogEntity({
|
|
||||||
apiVersion: "v1",
|
|
||||||
kind: "Cluster",
|
|
||||||
status: {
|
|
||||||
phase: "Running",
|
|
||||||
},
|
|
||||||
metadata: {
|
|
||||||
uid: "aws",
|
|
||||||
name: "aws",
|
|
||||||
labels: {},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("HotbarStore", () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
|
||||||
|
|
||||||
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
|
||||||
|
|
||||||
await di.runSetups();
|
|
||||||
|
|
||||||
mockFs({
|
|
||||||
"some-directory-for-user-data": {
|
|
||||||
"lens-hotbar-store.json": JSON.stringify({}),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
HotbarStore.createInstance();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
HotbarStore.resetInstance();
|
|
||||||
mockFs.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("load", () => {
|
|
||||||
it("loads one hotbar by default", () => {
|
|
||||||
expect(HotbarStore.getInstance().hotbars.length).toEqual(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("add", () => {
|
|
||||||
it("adds a hotbar", () => {
|
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
|
||||||
|
|
||||||
hotbarStore.add({ name: "hottest" });
|
|
||||||
expect(hotbarStore.hotbars.length).toEqual(2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("hotbar items", () => {
|
|
||||||
it("initially creates 12 empty cells", () => {
|
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
|
||||||
|
|
||||||
expect(hotbarStore.getActive().items.length).toEqual(12);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("initially adds catalog entity as first item", () => {
|
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
|
||||||
|
|
||||||
expect(hotbarStore.getActive().items[0].entity.name).toEqual("Catalog");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("adds items", () => {
|
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
|
||||||
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
|
||||||
const items = hotbarStore.getActive().items.filter(Boolean);
|
|
||||||
|
|
||||||
expect(items.length).toEqual(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("removes items", () => {
|
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
|
||||||
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
|
||||||
hotbarStore.removeFromHotbar("test");
|
|
||||||
hotbarStore.removeFromHotbar("catalog-entity");
|
|
||||||
const items = hotbarStore.getActive().items.filter(Boolean);
|
|
||||||
|
|
||||||
expect(items).toStrictEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("does nothing if removing with invalid uid", () => {
|
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
|
||||||
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
|
||||||
hotbarStore.removeFromHotbar("invalid uid");
|
|
||||||
const items = hotbarStore.getActive().items.filter(Boolean);
|
|
||||||
|
|
||||||
expect(items.length).toEqual(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("moves item to empty cell", () => {
|
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
|
||||||
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
|
||||||
hotbarStore.addToHotbar(minikubeCluster);
|
|
||||||
hotbarStore.addToHotbar(awsCluster);
|
|
||||||
|
|
||||||
expect(hotbarStore.getActive().items[6]).toBeNull();
|
|
||||||
|
|
||||||
hotbarStore.restackItems(1, 5);
|
|
||||||
|
|
||||||
expect(hotbarStore.getActive().items[5]).toBeTruthy();
|
|
||||||
expect(hotbarStore.getActive().items[5].entity.uid).toEqual("test");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("moves items down", () => {
|
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
|
||||||
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
|
||||||
hotbarStore.addToHotbar(minikubeCluster);
|
|
||||||
hotbarStore.addToHotbar(awsCluster);
|
|
||||||
|
|
||||||
// aws -> catalog
|
|
||||||
hotbarStore.restackItems(3, 0);
|
|
||||||
|
|
||||||
const items = hotbarStore.getActive().items.map(item => item?.entity.uid || null);
|
|
||||||
|
|
||||||
expect(items.slice(0, 4)).toEqual(["aws", "catalog-entity", "test", "minikube"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("moves items up", () => {
|
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
|
||||||
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
|
||||||
hotbarStore.addToHotbar(minikubeCluster);
|
|
||||||
hotbarStore.addToHotbar(awsCluster);
|
|
||||||
|
|
||||||
// test -> aws
|
|
||||||
hotbarStore.restackItems(1, 3);
|
|
||||||
|
|
||||||
const items = hotbarStore.getActive().items.map(item => item?.entity.uid || null);
|
|
||||||
|
|
||||||
expect(items.slice(0, 4)).toEqual(["catalog-entity", "minikube", "aws", "test"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("logs an error if cellIndex is out of bounds", () => {
|
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
|
||||||
|
|
||||||
hotbarStore.add({ name: "hottest", id: "hottest" });
|
|
||||||
hotbarStore.setActiveHotbar("hottest");
|
|
||||||
|
|
||||||
const { error } = logger;
|
|
||||||
const mocked = jest.fn();
|
|
||||||
|
|
||||||
logger.error = mocked;
|
|
||||||
|
|
||||||
hotbarStore.addToHotbar(testCluster, -1);
|
|
||||||
expect(mocked).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject());
|
|
||||||
|
|
||||||
hotbarStore.addToHotbar(testCluster, 12);
|
|
||||||
expect(mocked).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject());
|
|
||||||
|
|
||||||
hotbarStore.addToHotbar(testCluster, 13);
|
|
||||||
expect(mocked).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject());
|
|
||||||
|
|
||||||
logger.error = error;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("throws an error if getId is invalid or returns not a string", () => {
|
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
|
||||||
|
|
||||||
expect(() => hotbarStore.addToHotbar({} as any)).toThrowError(TypeError);
|
|
||||||
expect(() => hotbarStore.addToHotbar({ getId: () => true } as any)).toThrowError(TypeError);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("throws an error if getName is invalid or returns not a string", () => {
|
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
|
||||||
|
|
||||||
expect(() => hotbarStore.addToHotbar({ getId: () => "" } as any)).toThrowError(TypeError);
|
|
||||||
expect(() => hotbarStore.addToHotbar({ getId: () => "", getName: () => 4 } as any)).toThrowError(TypeError);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("does nothing when item moved to same cell", () => {
|
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
|
||||||
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
|
||||||
hotbarStore.restackItems(1, 1);
|
|
||||||
|
|
||||||
expect(hotbarStore.getActive().items[1].entity.uid).toEqual("test");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("new items takes first empty cell", () => {
|
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
|
||||||
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
|
||||||
hotbarStore.addToHotbar(awsCluster);
|
|
||||||
hotbarStore.restackItems(0, 3);
|
|
||||||
hotbarStore.addToHotbar(minikubeCluster);
|
|
||||||
|
|
||||||
expect(hotbarStore.getActive().items[0].entity.uid).toEqual("minikube");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("throws if invalid arguments provided", () => {
|
|
||||||
// Prevent writing to stderr during this render.
|
|
||||||
const { error, warn } = console;
|
|
||||||
|
|
||||||
console.error = jest.fn();
|
|
||||||
console.warn = jest.fn();
|
|
||||||
|
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
|
||||||
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
|
||||||
|
|
||||||
expect(() => hotbarStore.restackItems(-5, 0)).toThrow();
|
|
||||||
expect(() => hotbarStore.restackItems(2, -1)).toThrow();
|
|
||||||
expect(() => hotbarStore.restackItems(14, 1)).toThrow();
|
|
||||||
expect(() => hotbarStore.restackItems(11, 112)).toThrow();
|
|
||||||
|
|
||||||
// Restore writing to stderr.
|
|
||||||
console.error = error;
|
|
||||||
console.warn = warn;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("checks if entity already pinned to hotbar", () => {
|
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
|
||||||
|
|
||||||
hotbarStore.addToHotbar(testCluster);
|
|
||||||
|
|
||||||
expect(hotbarStore.isAddedToActive(testCluster)).toBeTruthy();
|
|
||||||
expect(hotbarStore.isAddedToActive(awsCluster)).toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("pre beta-5 migrations", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
HotbarStore.resetInstance();
|
|
||||||
const mockOpts = {
|
|
||||||
"some-directory-for-user-data": {
|
|
||||||
"lens-hotbar-store.json": JSON.stringify({
|
|
||||||
__internal__: {
|
|
||||||
migrations: {
|
|
||||||
version: "5.0.0-beta.3",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"hotbars": [
|
|
||||||
{
|
|
||||||
"id": "3caac17f-aec2-4723-9694-ad204465d935",
|
|
||||||
"name": "myhotbar",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"entity": {
|
|
||||||
"uid": "1dfa26e2ebab15780a3547e9c7fa785c",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"entity": {
|
|
||||||
"uid": "55b42c3c7ba3b04193416cda405269a5",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"entity": {
|
|
||||||
"uid": "176fd331968660832f62283219d7eb6e",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"entity": {
|
|
||||||
"uid": "61c4fb45528840ebad1badc25da41d14",
|
|
||||||
"name": "user1-context",
|
|
||||||
"source": "local",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"entity": {
|
|
||||||
"uid": "27d6f99fe9e7548a6e306760bfe19969",
|
|
||||||
"name": "foo2",
|
|
||||||
"source": "local",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
{
|
|
||||||
"entity": {
|
|
||||||
"uid": "c0b20040646849bb4dcf773e43a0bf27",
|
|
||||||
"name": "multinode-demo",
|
|
||||||
"source": "local",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
mockFs(mockOpts);
|
|
||||||
|
|
||||||
HotbarStore.createInstance();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
mockFs.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("allows to retrieve a hotbar", () => {
|
|
||||||
const hotbar = HotbarStore.getInstance().getById("3caac17f-aec2-4723-9694-ad204465d935");
|
|
||||||
|
|
||||||
expect(hotbar.id).toBe("3caac17f-aec2-4723-9694-ad204465d935");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("clears cells without entity", () => {
|
|
||||||
const items = HotbarStore.getInstance().hotbars[0].items;
|
|
||||||
|
|
||||||
expect(items[2]).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("adds extra data to cells with according entity", () => {
|
|
||||||
const items = HotbarStore.getInstance().hotbars[0].items;
|
|
||||||
|
|
||||||
expect(items[0]).toEqual({
|
|
||||||
entity: {
|
|
||||||
name: "mycluster",
|
|
||||||
source: "local",
|
|
||||||
uid: "1dfa26e2ebab15780a3547e9c7fa785c",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(items[1]).toEqual({
|
|
||||||
entity: {
|
|
||||||
name: "my_shiny_cluster",
|
|
||||||
source: "remote",
|
|
||||||
uid: "55b42c3c7ba3b04193416cda405269a5",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -151,13 +151,13 @@ describe("kube helpers", () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
it("single context is ok", async () => {
|
it("single context is ok", () => {
|
||||||
const { config } = loadConfigFromString(JSON.stringify(mockKubeConfig));
|
const { config } = loadConfigFromString(JSON.stringify(mockKubeConfig));
|
||||||
|
|
||||||
expect(config.getCurrentContext()).toBe("minikube");
|
expect(config.getCurrentContext()).toBe("minikube");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("multiple context is ok", async () => {
|
it("multiple context is ok", () => {
|
||||||
mockKubeConfig.contexts.push({ context: { cluster: "cluster-2", user: "cluster-2" }, name: "cluster-2" });
|
mockKubeConfig.contexts.push({ context: { cluster: "cluster-2", user: "cluster-2" }, name: "cluster-2" });
|
||||||
const { config } = loadConfigFromString(JSON.stringify(mockKubeConfig));
|
const { config } = loadConfigFromString(JSON.stringify(mockKubeConfig));
|
||||||
|
|
||||||
@ -192,7 +192,7 @@ describe("kube helpers", () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
it("empty name in context causes it to be removed", async () => {
|
it("empty name in context causes it to be removed", () => {
|
||||||
mockKubeConfig.contexts.push({ context: { cluster: "cluster-2", user: "cluster-2" }, name: "" });
|
mockKubeConfig.contexts.push({ context: { cluster: "cluster-2", user: "cluster-2" }, name: "" });
|
||||||
expect(mockKubeConfig.contexts.length).toBe(2);
|
expect(mockKubeConfig.contexts.length).toBe(2);
|
||||||
const { config } = loadConfigFromString(JSON.stringify(mockKubeConfig));
|
const { config } = loadConfigFromString(JSON.stringify(mockKubeConfig));
|
||||||
@ -201,7 +201,7 @@ describe("kube helpers", () => {
|
|||||||
expect(config.contexts.length).toBe(1);
|
expect(config.contexts.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("empty cluster in context causes it to be removed", async () => {
|
it("empty cluster in context causes it to be removed", () => {
|
||||||
mockKubeConfig.contexts.push({ context: { cluster: "", user: "cluster-2" }, name: "cluster-2" });
|
mockKubeConfig.contexts.push({ context: { cluster: "", user: "cluster-2" }, name: "cluster-2" });
|
||||||
expect(mockKubeConfig.contexts.length).toBe(2);
|
expect(mockKubeConfig.contexts.length).toBe(2);
|
||||||
const { config } = loadConfigFromString(JSON.stringify(mockKubeConfig));
|
const { config } = loadConfigFromString(JSON.stringify(mockKubeConfig));
|
||||||
@ -210,7 +210,7 @@ describe("kube helpers", () => {
|
|||||||
expect(config.contexts.length).toBe(1);
|
expect(config.contexts.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("empty user in context causes it to be removed", async () => {
|
it("empty user in context causes it to be removed", () => {
|
||||||
mockKubeConfig.contexts.push({ context: { cluster: "cluster-2", user: "" }, name: "cluster-2" });
|
mockKubeConfig.contexts.push({ context: { cluster: "cluster-2", user: "" }, name: "cluster-2" });
|
||||||
expect(mockKubeConfig.contexts.length).toBe(2);
|
expect(mockKubeConfig.contexts.length).toBe(2);
|
||||||
const { config } = loadConfigFromString(JSON.stringify(mockKubeConfig));
|
const { config } = loadConfigFromString(JSON.stringify(mockKubeConfig));
|
||||||
@ -219,7 +219,7 @@ describe("kube helpers", () => {
|
|||||||
expect(config.contexts.length).toBe(1);
|
expect(config.contexts.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("invalid context in between valid contexts is removed", async () => {
|
it("invalid context in between valid contexts is removed", () => {
|
||||||
mockKubeConfig.contexts.push({ context: { cluster: "cluster-2", user: "" }, name: "cluster-2" });
|
mockKubeConfig.contexts.push({ context: { cluster: "cluster-2", user: "" }, name: "cluster-2" });
|
||||||
mockKubeConfig.contexts.push({ context: { cluster: "cluster-3", user: "cluster-3" }, name: "cluster-3" });
|
mockKubeConfig.contexts.push({ context: { cluster: "cluster-3", user: "cluster-3" }, name: "cluster-3" });
|
||||||
expect(mockKubeConfig.contexts.length).toBe(3);
|
expect(mockKubeConfig.contexts.length).toBe(3);
|
||||||
|
|||||||
82
src/common/__tests__/log-search-store.test.ts
Normal file
82
src/common/__tests__/log-search-store.test.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { LogSearchStore } from "../../renderer/components/dock/log-search/store";
|
||||||
|
|
||||||
|
const logs = [
|
||||||
|
"1:M 30 Oct 2020 16:17:41.553 # Connection with replica 172.17.0.12:6379 lost",
|
||||||
|
"1:M 30 Oct 2020 16:17:41.623 * Replica 172.17.0.12:6379 asks for synchronization",
|
||||||
|
"1:M 30 Oct 2020 16:17:41.623 * Starting Partial resynchronization request from 172.17.0.12:6379 accepted. Sending 0 bytes of backlog starting from offset 14407.",
|
||||||
|
];
|
||||||
|
|
||||||
|
describe("search store tests", () => {
|
||||||
|
let logSearchStore: LogSearchStore;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
logSearchStore = new LogSearchStore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does nothing with empty search query", () => {
|
||||||
|
logSearchStore.onSearch([], "");
|
||||||
|
expect(logSearchStore.occurrences).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("doesn't break if no text provided", () => {
|
||||||
|
logSearchStore.onSearch(null, "replica");
|
||||||
|
expect(logSearchStore.occurrences).toEqual([]);
|
||||||
|
|
||||||
|
logSearchStore.onSearch([], "replica");
|
||||||
|
expect(logSearchStore.occurrences).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("find 3 occurrences across 3 lines", () => {
|
||||||
|
logSearchStore.onSearch(logs, "172");
|
||||||
|
expect(logSearchStore.occurrences).toEqual([0, 1, 2]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("find occurrences within 1 line (case-insensitive)", () => {
|
||||||
|
logSearchStore.onSearch(logs, "Starting");
|
||||||
|
expect(logSearchStore.occurrences).toEqual([2, 2]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sets overlay index equal to first occurrence", () => {
|
||||||
|
logSearchStore.onSearch(logs, "Replica");
|
||||||
|
expect(logSearchStore.activeOverlayIndex).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("set overlay index to next occurrence", () => {
|
||||||
|
logSearchStore.onSearch(logs, "172");
|
||||||
|
logSearchStore.setNextOverlayActive();
|
||||||
|
expect(logSearchStore.activeOverlayIndex).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sets overlay to last occurrence", () => {
|
||||||
|
logSearchStore.onSearch(logs, "172");
|
||||||
|
logSearchStore.setPrevOverlayActive();
|
||||||
|
expect(logSearchStore.activeOverlayIndex).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("gets line index where overlay is located", () => {
|
||||||
|
logSearchStore.onSearch(logs, "synchronization");
|
||||||
|
expect(logSearchStore.activeOverlayLine).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("escapes string for using in regex", () => {
|
||||||
|
const regex = LogSearchStore.escapeRegex("some.interesting-query\\#?()[]");
|
||||||
|
|
||||||
|
expect(regex).toBe("some\\.interesting\\-query\\\\\\#\\?\\(\\)\\[\\]");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("gets active find number", () => {
|
||||||
|
logSearchStore.onSearch(logs, "172");
|
||||||
|
logSearchStore.setNextOverlayActive();
|
||||||
|
expect(logSearchStore.activeFind).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("gets total finds number", () => {
|
||||||
|
logSearchStore.onSearch(logs, "Starting");
|
||||||
|
expect(logSearchStore.totalFinds).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -4,6 +4,17 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import mockFs from "mock-fs";
|
import mockFs from "mock-fs";
|
||||||
|
import { Console } from "console";
|
||||||
|
import { SemVer } from "semver";
|
||||||
|
import electron from "electron";
|
||||||
|
import { stdout, stderr } from "process";
|
||||||
|
import { getDisForUnitTesting } from "../../test-utils/get-dis-for-unit-testing";
|
||||||
|
import userPreferencesStoreInjectable from "../user-preferences/store.injectable";
|
||||||
|
import type { DependencyInjectionContainer } from "@ogre-tools/injectable";
|
||||||
|
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data.injectable";
|
||||||
|
import type { ClusterStoreModel } from "../cluster-store/store";
|
||||||
|
import { defaultTheme } from "../vars";
|
||||||
|
import type { UserPreferencesStore } from "../user-preferences";
|
||||||
|
|
||||||
jest.mock("electron", () => ({
|
jest.mock("electron", () => ({
|
||||||
app: {
|
app: {
|
||||||
@ -21,22 +32,10 @@ jest.mock("electron", () => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
import { UserStore } from "../user-store";
|
|
||||||
import { Console } from "console";
|
|
||||||
import { SemVer } from "semver";
|
|
||||||
import electron from "electron";
|
|
||||||
import { stdout, stderr } from "process";
|
|
||||||
import { getDisForUnitTesting } from "../../test-utils/get-dis-for-unit-testing";
|
|
||||||
import userStoreInjectable from "../user-store/user-store.injectable";
|
|
||||||
import type { DependencyInjectionContainer } from "@ogre-tools/injectable";
|
|
||||||
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
|
||||||
import type { ClusterStoreModel } from "../cluster-store/cluster-store";
|
|
||||||
import { defaultTheme } from "../vars";
|
|
||||||
|
|
||||||
console = new Console(stdout, stderr);
|
console = new Console(stdout, stderr);
|
||||||
|
|
||||||
describe("user store tests", () => {
|
describe("user store tests", () => {
|
||||||
let userStore: UserStore;
|
let userStore: UserPreferencesStore;
|
||||||
let mainDi: DependencyInjectionContainer;
|
let mainDi: DependencyInjectionContainer;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
@ -59,12 +58,11 @@ describe("user store tests", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockFs({ "some-directory-for-user-data": { "config.json": "{}", "kube_config": "{}" }});
|
mockFs({ "some-directory-for-user-data": { "config.json": "{}", "kube_config": "{}" }});
|
||||||
|
|
||||||
userStore = mainDi.inject(userStoreInjectable);
|
userStore = mainDi.inject(userPreferencesStoreInjectable);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
mockFs.restore();
|
mockFs.restore();
|
||||||
UserStore.resetInstance();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows setting and retrieving lastSeenAppVersion", () => {
|
it("allows setting and retrieving lastSeenAppVersion", () => {
|
||||||
@ -82,7 +80,7 @@ describe("user store tests", () => {
|
|||||||
expect(userStore.colorTheme).toBe("light");
|
expect(userStore.colorTheme).toBe("light");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("correctly resets theme to default value", async () => {
|
it("correctly resets theme to default value", () => {
|
||||||
userStore.colorTheme = "some other theme";
|
userStore.colorTheme = "some other theme";
|
||||||
userStore.resetTheme();
|
userStore.resetTheme();
|
||||||
expect(userStore.colorTheme).toBe(defaultTheme);
|
expect(userStore.colorTheme).toBe(defaultTheme);
|
||||||
@ -126,11 +124,10 @@ describe("user store tests", () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
userStore = mainDi.inject(userStoreInjectable);
|
userStore = mainDi.inject(userPreferencesStoreInjectable);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
UserStore.resetInstance();
|
|
||||||
mockFs.restore();
|
mockFs.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import directoryForUserDataInjectable from "../directory-for-user-data/directory-for-user-data.injectable";
|
import directoryForUserDataInjectable from "./directory-for-user-data.injectable";
|
||||||
|
|
||||||
const directoryForBinariesInjectable = getInjectable({
|
const directoryForBinariesInjectable = getInjectable({
|
||||||
instantiate: (di) =>
|
instantiate: (di) =>
|
||||||
@ -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 { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import { appPathsInjectionToken } from "../app-path-injection-token";
|
import { appPathsInjectionToken } from "./app-path-injection-token";
|
||||||
|
|
||||||
const directoryForDownloadsInjectable = getInjectable({
|
const directoryForDownloadsInjectable = getInjectable({
|
||||||
instantiate: (di) => di.inject(appPathsInjectionToken).downloads,
|
instantiate: (di) => di.inject(appPathsInjectionToken).downloads,
|
||||||
@ -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 { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import { appPathsInjectionToken } from "../app-path-injection-token";
|
import { appPathsInjectionToken } from "./app-path-injection-token";
|
||||||
|
|
||||||
const directoryForExesInjectable = getInjectable({
|
const directoryForExesInjectable = getInjectable({
|
||||||
instantiate: (di) => di.inject(appPathsInjectionToken).exe,
|
instantiate: (di) => di.inject(appPathsInjectionToken).exe,
|
||||||
@ -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 { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import directoryForUserDataInjectable from "../directory-for-user-data/directory-for-user-data.injectable";
|
import directoryForUserDataInjectable from "./directory-for-user-data.injectable";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
const directoryForKubeConfigsInjectable = getInjectable({
|
const directoryForKubeConfigsInjectable = getInjectable({
|
||||||
@ -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 { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import { appPathsInjectionToken } from "../app-path-injection-token";
|
import { appPathsInjectionToken } from "./app-path-injection-token";
|
||||||
|
|
||||||
const directoryForTempInjectable = getInjectable({
|
const directoryForTempInjectable = getInjectable({
|
||||||
instantiate: (di) => di.inject(appPathsInjectionToken).temp,
|
instantiate: (di) => di.inject(appPathsInjectionToken).temp,
|
||||||
@ -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 { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import { appPathsInjectionToken } from "../app-path-injection-token";
|
import { appPathsInjectionToken } from "./app-path-injection-token";
|
||||||
|
|
||||||
const directoryForUserDataInjectable = getInjectable({
|
const directoryForUserDataInjectable = getInjectable({
|
||||||
instantiate: (di) => di.inject(appPathsInjectionToken).userData,
|
instantiate: (di) => di.inject(appPathsInjectionToken).userData,
|
||||||
@ -4,12 +4,12 @@
|
|||||||
*/
|
*/
|
||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import directoryForKubeConfigsInjectable from "../directory-for-kube-configs/directory-for-kube-configs.injectable";
|
import directoryForKubeConfigsInjectable from "./directory-for-kube-configs.injectable";
|
||||||
|
|
||||||
const getCustomKubeConfigDirectoryInjectable = getInjectable({
|
const getCustomKubeConfigDirectoryInjectable = getInjectable({
|
||||||
instantiate: (di) => (directoryName: string) => {
|
instantiate: (di) => (directoryName: string) => {
|
||||||
const directoryForKubeConfigs = di.inject(directoryForKubeConfigsInjectable);
|
const directoryForKubeConfigs = di.inject(directoryForKubeConfigsInjectable);
|
||||||
|
|
||||||
return path.resolve(
|
return path.resolve(
|
||||||
directoryForKubeConfigs,
|
directoryForKubeConfigs,
|
||||||
directoryName,
|
directoryName,
|
||||||
@ -8,15 +8,14 @@ import Config from "conf";
|
|||||||
import type { Options as ConfOptions } from "conf/dist/source/types";
|
import type { Options as ConfOptions } from "conf/dist/source/types";
|
||||||
import { ipcMain, ipcRenderer } from "electron";
|
import { ipcMain, ipcRenderer } from "electron";
|
||||||
import { IEqualsComparer, makeObservable, reaction, runInAction } from "mobx";
|
import { IEqualsComparer, makeObservable, reaction, runInAction } from "mobx";
|
||||||
import { getAppVersion, Singleton, toJS, Disposer } from "./utils";
|
import { getAppVersion, toJS, Disposer } from "./utils";
|
||||||
import logger from "../main/logger";
|
import logger from "../main/logger";
|
||||||
import { broadcastMessage, ipcMainOn, ipcRendererOn } from "./ipc";
|
import { broadcastMessage, ipcMainOn, ipcRendererOn } from "./ipc";
|
||||||
import isEqual from "lodash/isEqual";
|
import isEqual from "lodash/isEqual";
|
||||||
import { isTestEnv } from "./vars";
|
import { isTestEnv } from "./vars";
|
||||||
import { kebabCase } from "lodash";
|
import { kebabCase } from "lodash";
|
||||||
import { getLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
import { getLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
||||||
import directoryForUserDataInjectable
|
import directoryForUserDataInjectable from "./app-paths/directory-for-user-data.injectable";
|
||||||
from "./app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
|
||||||
|
|
||||||
export interface BaseStoreParams<T> extends ConfOptions<T> {
|
export interface BaseStoreParams<T> extends ConfOptions<T> {
|
||||||
syncOptions?: {
|
syncOptions?: {
|
||||||
@ -28,14 +27,13 @@ export interface BaseStoreParams<T> extends ConfOptions<T> {
|
|||||||
/**
|
/**
|
||||||
* Note: T should only contain base JSON serializable types.
|
* Note: T should only contain base JSON serializable types.
|
||||||
*/
|
*/
|
||||||
export abstract class BaseStore<T> extends Singleton {
|
export abstract class BaseStore<T> {
|
||||||
protected storeConfig?: Config<T>;
|
protected storeConfig?: Config<T>;
|
||||||
protected syncDisposers: Disposer[] = [];
|
protected syncDisposers: Disposer[] = [];
|
||||||
|
|
||||||
readonly displayName: string = this.constructor.name;
|
readonly displayName: string = this.constructor.name;
|
||||||
|
|
||||||
protected constructor(protected params: BaseStoreParams<T>) {
|
protected constructor(protected params: BaseStoreParams<T>) {
|
||||||
super();
|
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
|
|
||||||
if (ipcRenderer) {
|
if (ipcRenderer) {
|
||||||
|
|||||||
@ -2,9 +2,15 @@
|
|||||||
* 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 { kubernetesClusterCategory } from "../kubernetes-cluster";
|
import { KubernetesClusterCategory } from "../kubernetes-cluster";
|
||||||
|
|
||||||
describe("kubernetesClusterCategory", () => {
|
describe("kubernetesClusterCategory", () => {
|
||||||
|
let kubernetesClusterCategory: KubernetesClusterCategory;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
kubernetesClusterCategory = new KubernetesClusterCategory();
|
||||||
|
});
|
||||||
|
|
||||||
describe("filteredItems", () => {
|
describe("filteredItems", () => {
|
||||||
const item1 = {
|
const item1 = {
|
||||||
icon: "Icon",
|
icon: "Icon",
|
||||||
|
|||||||
@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
import { navigate } from "../../renderer/navigation";
|
import { navigate } from "../../renderer/navigation";
|
||||||
import { CatalogCategory, CatalogEntity, CatalogEntityMetadata, CatalogEntitySpec, CatalogEntityStatus } from "../catalog";
|
import { CatalogCategory, CatalogEntity, CatalogEntityMetadata, CatalogEntitySpec, CatalogEntityStatus } from "../catalog";
|
||||||
import { catalogCategoryRegistry } from "../catalog/catalog-category-registry";
|
|
||||||
|
|
||||||
interface GeneralEntitySpec extends CatalogEntitySpec {
|
interface GeneralEntitySpec extends CatalogEntitySpec {
|
||||||
path: string;
|
path: string;
|
||||||
@ -19,21 +18,9 @@ export class GeneralEntity extends CatalogEntity<CatalogEntityMetadata, CatalogE
|
|||||||
public readonly apiVersion = "entity.k8slens.dev/v1alpha1";
|
public readonly apiVersion = "entity.k8slens.dev/v1alpha1";
|
||||||
public readonly kind = "General";
|
public readonly kind = "General";
|
||||||
|
|
||||||
async onRun() {
|
onRun() {
|
||||||
navigate(this.spec.path);
|
navigate(this.spec.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onSettingsOpen(): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
public onDetailsOpen(): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
public onContextMenuOpen(): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GeneralCategory extends CatalogCategory {
|
export class GeneralCategory extends CatalogCategory {
|
||||||
@ -56,5 +43,3 @@ export class GeneralCategory extends CatalogCategory {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
catalogCategoryRegistry.add(new GeneralCategory());
|
|
||||||
|
|||||||
@ -3,12 +3,10 @@
|
|||||||
* 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 { catalogCategoryRegistry } from "../catalog/catalog-category-registry";
|
|
||||||
import { CatalogEntity, CatalogEntityActionContext, CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus, CatalogCategory, CatalogCategorySpec } from "../catalog";
|
import { CatalogEntity, CatalogEntityActionContext, CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus, CatalogCategory, CatalogCategorySpec } from "../catalog";
|
||||||
import { clusterActivateHandler, clusterDisconnectHandler } from "../cluster-ipc";
|
import { clusterActivateHandler, clusterDisconnectHandler } from "../cluster-ipc";
|
||||||
import { ClusterStore } from "../cluster-store/cluster-store";
|
|
||||||
import { broadcastMessage, requestMain } from "../ipc";
|
import { broadcastMessage, requestMain } from "../ipc";
|
||||||
import { app } from "electron";
|
import { app, ipcMain } from "electron";
|
||||||
import type { CatalogEntitySpec } from "../catalog/catalog-entity";
|
import type { CatalogEntitySpec } from "../catalog/catalog-entity";
|
||||||
import { IpcRendererNavigationEvents } from "../../renderer/navigation/events";
|
import { IpcRendererNavigationEvents } from "../../renderer/navigation/events";
|
||||||
|
|
||||||
@ -67,7 +65,8 @@ export class KubernetesCluster extends CatalogEntity<KubernetesClusterMetadata,
|
|||||||
|
|
||||||
async connect(): Promise<void> {
|
async connect(): Promise<void> {
|
||||||
if (app) {
|
if (app) {
|
||||||
await ClusterStore.getInstance().getById(this.metadata.uid)?.activate();
|
// TODO refactor
|
||||||
|
ipcMain.emit(clusterActivateHandler, this.metadata.uid, false);
|
||||||
} else {
|
} else {
|
||||||
await requestMain(clusterActivateHandler, this.metadata.uid, false);
|
await requestMain(clusterActivateHandler, this.metadata.uid, false);
|
||||||
}
|
}
|
||||||
@ -75,25 +74,17 @@ export class KubernetesCluster extends CatalogEntity<KubernetesClusterMetadata,
|
|||||||
|
|
||||||
async disconnect(): Promise<void> {
|
async disconnect(): Promise<void> {
|
||||||
if (app) {
|
if (app) {
|
||||||
ClusterStore.getInstance().getById(this.metadata.uid)?.disconnect();
|
ipcMain.emit(clusterDisconnectHandler, this.metadata.uid, false);
|
||||||
} else {
|
} else {
|
||||||
await requestMain(clusterDisconnectHandler, this.metadata.uid, false);
|
await requestMain(clusterDisconnectHandler, this.metadata.uid, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async onRun(context: CatalogEntityActionContext) {
|
onRun(context: CatalogEntityActionContext) {
|
||||||
context.navigate(`/cluster/${this.metadata.uid}`);
|
context.navigate(`/cluster/${this.metadata.uid}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
onDetailsOpen(): void {
|
onContextMenuOpen(context: CatalogEntityContextMenuContext) {
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
onSettingsOpen(): void {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
async onContextMenuOpen(context: CatalogEntityContextMenuContext) {
|
|
||||||
if (!this.metadata.source || this.metadata.source === "local") {
|
if (!this.metadata.source || this.metadata.source === "local") {
|
||||||
context.menuItems.push({
|
context.menuItems.push({
|
||||||
title: "Settings",
|
title: "Settings",
|
||||||
@ -122,14 +113,10 @@ export class KubernetesCluster extends CatalogEntity<KubernetesClusterMetadata,
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
catalogCategoryRegistry
|
|
||||||
.getCategoryForEntity<KubernetesClusterCategory>(this)
|
|
||||||
?.emit("contextMenuOpen", this, context);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class KubernetesClusterCategory extends CatalogCategory {
|
export class KubernetesClusterCategory extends CatalogCategory {
|
||||||
public readonly apiVersion = "catalog.k8slens.dev/v1alpha1";
|
public readonly apiVersion = "catalog.k8slens.dev/v1alpha1";
|
||||||
public readonly kind = "CatalogCategory";
|
public readonly kind = "CatalogCategory";
|
||||||
public metadata = {
|
public metadata = {
|
||||||
@ -149,7 +136,3 @@ class KubernetesClusterCategory extends CatalogCategory {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const kubernetesClusterCategory = new KubernetesClusterCategory();
|
|
||||||
|
|
||||||
catalogCategoryRegistry.add(kubernetesClusterCategory);
|
|
||||||
|
|||||||
@ -3,10 +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 { CatalogCategory, CatalogEntity, CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus } from "../catalog";
|
import { CatalogCategory, CatalogEntity, CatalogEntityMetadata, CatalogEntityStatus } from "../catalog";
|
||||||
import { catalogCategoryRegistry } from "../catalog/catalog-category-registry";
|
|
||||||
import { productName } from "../vars";
|
|
||||||
import { WeblinkStore } from "../weblink-store";
|
|
||||||
|
|
||||||
export type WebLinkStatusPhase = "available" | "unavailable";
|
export type WebLinkStatusPhase = "available" | "unavailable";
|
||||||
|
|
||||||
@ -14,9 +11,9 @@ export interface WebLinkStatus extends CatalogEntityStatus {
|
|||||||
phase: WebLinkStatusPhase;
|
phase: WebLinkStatusPhase;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WebLinkSpec = {
|
export interface WebLinkSpec {
|
||||||
url: string;
|
url: string;
|
||||||
};
|
}
|
||||||
|
|
||||||
export class WebLink extends CatalogEntity<CatalogEntityMetadata, WebLinkStatus, WebLinkSpec> {
|
export class WebLink extends CatalogEntity<CatalogEntityMetadata, WebLinkStatus, WebLinkSpec> {
|
||||||
public static readonly apiVersion = "entity.k8slens.dev/v1alpha1";
|
public static readonly apiVersion = "entity.k8slens.dev/v1alpha1";
|
||||||
@ -25,30 +22,9 @@ export class WebLink extends CatalogEntity<CatalogEntityMetadata, WebLinkStatus,
|
|||||||
public readonly apiVersion = WebLink.apiVersion;
|
public readonly apiVersion = WebLink.apiVersion;
|
||||||
public readonly kind = WebLink.kind;
|
public readonly kind = WebLink.kind;
|
||||||
|
|
||||||
async onRun() {
|
onRun() {
|
||||||
window.open(this.spec.url, "_blank");
|
window.open(this.spec.url, "_blank");
|
||||||
}
|
}
|
||||||
|
|
||||||
public onSettingsOpen(): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
async onContextMenuOpen(context: CatalogEntityContextMenuContext) {
|
|
||||||
if (this.metadata.source === "local") {
|
|
||||||
context.menuItems.push({
|
|
||||||
title: "Delete",
|
|
||||||
icon: "delete",
|
|
||||||
onClick: async () => WeblinkStore.getInstance().removeById(this.metadata.uid),
|
|
||||||
confirm: {
|
|
||||||
message: `Remove Web Link "${this.metadata.name}" from ${productName}?`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
catalogCategoryRegistry
|
|
||||||
.getCategoryForEntity<WebLinkCategory>(this)
|
|
||||||
?.emit("contextMenuOpen", this, context);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WebLinkCategory extends CatalogCategory {
|
export class WebLinkCategory extends CatalogCategory {
|
||||||
@ -71,5 +47,3 @@ export class WebLinkCategory extends CatalogCategory {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
catalogCategoryRegistry.add(new WebLinkCategory());
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import { action, computed, observable, makeObservable } from "mobx";
|
import { action, computed, observable, makeObservable } from "mobx";
|
||||||
import { Disposer, ExtendedMap, iter } from "../utils";
|
import { Disposer, ExtendedMap, iter } from "../utils";
|
||||||
import { CatalogCategory, CatalogEntityData, CatalogEntityKindData } from "./catalog-entity";
|
import { CatalogCategory, CatalogEntity, CatalogEntityData, CatalogEntityKindData } from "./catalog-entity";
|
||||||
import { once } from "lodash";
|
import { once } from "lodash";
|
||||||
|
|
||||||
export type CategoryFilter = (category: CatalogCategory) => any;
|
export type CategoryFilter = (category: CatalogCategory) => any;
|
||||||
@ -37,22 +37,19 @@ export class CatalogCategoryRegistry {
|
|||||||
return Array.from(this.categories);
|
return Array.from(this.categories);
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed get filteredItems() {
|
readonly filteredItems = computed(() => Array.from(
|
||||||
return Array.from(
|
iter.reduce(
|
||||||
iter.reduce(
|
this.filters,
|
||||||
this.filters,
|
iter.filter,
|
||||||
iter.filter,
|
this.categories.values(),
|
||||||
this.items.values(),
|
),
|
||||||
),
|
));
|
||||||
);
|
|
||||||
|
getForGroupKind(group: string, kind: string): CatalogCategory | undefined {
|
||||||
|
return this.groupKinds.get(group)?.get(kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getEntityForData = (data: CatalogEntityData & CatalogEntityKindData): CatalogEntity | null => {
|
||||||
getForGroupKind<T extends CatalogCategory>(group: string, kind: string): T | undefined {
|
|
||||||
return this.groupKinds.get(group)?.get(kind) as T;
|
|
||||||
}
|
|
||||||
|
|
||||||
getEntityForData(data: CatalogEntityData & CatalogEntityKindData) {
|
|
||||||
const category = this.getCategoryForEntity(data);
|
const category = this.getCategoryForEntity(data);
|
||||||
|
|
||||||
if (!category) {
|
if (!category) {
|
||||||
@ -69,18 +66,23 @@ export class CatalogCategoryRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new specVersion.entityClass(data);
|
return new specVersion.entityClass(data);
|
||||||
}
|
};
|
||||||
|
|
||||||
getCategoryForEntity<T extends CatalogCategory>(data: CatalogEntityData & CatalogEntityKindData): T | undefined {
|
getCategoryForEntity = <T extends CatalogCategory>({ kind, apiVersion }: CatalogEntityData & CatalogEntityKindData): T => {
|
||||||
const splitApiVersion = data.apiVersion.split("/");
|
const group = apiVersion.split("/")[0];
|
||||||
const group = splitApiVersion[0];
|
const category = this.getForGroupKind(group, kind);
|
||||||
|
|
||||||
return this.getForGroupKind(group, data.kind);
|
if (!category) {
|
||||||
}
|
// Throw here because it is very important that this is always true
|
||||||
|
throw new Error(`Unable to find a category for group=${group} kind=${kind}`);
|
||||||
|
}
|
||||||
|
|
||||||
getByName(name: string) {
|
return category as T;
|
||||||
|
};
|
||||||
|
|
||||||
|
getByName = (name: string) => {
|
||||||
return this.items.find(category => category.metadata?.name == name);
|
return this.items.find(category => category.metadata?.name == name);
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a new filter to the set of category filters
|
* Add a new filter to the set of category filters
|
||||||
@ -93,5 +95,3 @@ export class CatalogCategoryRegistry {
|
|||||||
return once(() => void this.filters.delete(fn));
|
return once(() => void this.filters.delete(fn));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const catalogCategoryRegistry = new CatalogCategoryRegistry();
|
|
||||||
|
|||||||
@ -351,7 +351,7 @@ export abstract class CatalogEntity<
|
|||||||
return this.status.enabled ?? true;
|
return this.status.enabled ?? true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract onRun?(context: CatalogEntityActionContext): void | Promise<void>;
|
public onRun?(context: CatalogEntityActionContext): void;
|
||||||
public abstract onContextMenuOpen(context: CatalogEntityContextMenuContext): void | Promise<void>;
|
public onContextMenuOpen?(context: CatalogEntityContextMenuContext): void;
|
||||||
public abstract onSettingsOpen(context: CatalogEntitySettingsContext): void | Promise<void>;
|
public onSettingsOpen?(context: CatalogEntitySettingsContext): void;
|
||||||
}
|
}
|
||||||
|
|||||||
13
src/common/cluster-store/get-cluster-by-id.injectable.ts
Normal file
13
src/common/cluster-store/get-cluster-by-id.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 clusterStoreInjectable from "./store.injectable";
|
||||||
|
|
||||||
|
const getClusterByIdInjectable = getInjectable({
|
||||||
|
instantiate: (di) => di.inject(clusterStoreInjectable).getById,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default getClusterByIdInjectable;
|
||||||
@ -4,15 +4,10 @@
|
|||||||
*/
|
*/
|
||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import { getHostedClusterId } from "../../utils";
|
import { getHostedClusterId } from "../../utils";
|
||||||
import clusterStoreInjectable from "../cluster-store.injectable";
|
import clusterStoreInjectable from "../store.injectable";
|
||||||
|
|
||||||
const hostedClusterInjectable = getInjectable({
|
const hostedClusterInjectable = getInjectable({
|
||||||
instantiate: (di) => {
|
instantiate: (di) => di.inject(clusterStoreInjectable).getById(getHostedClusterId()),
|
||||||
const hostedClusterId = getHostedClusterId();
|
|
||||||
|
|
||||||
return di.inject(clusterStoreInjectable).getById(hostedClusterId);
|
|
||||||
},
|
|
||||||
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
9
src/common/cluster-store/migrations-injection-token.ts
Normal file
9
src/common/cluster-store/migrations-injection-token.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||||
|
import type { Migrations } from "conf/dist/source/types";
|
||||||
|
import type { ClusterStoreModel } from "./store";
|
||||||
|
|
||||||
|
export const clusterStoreMigrationsInjectionToken = getInjectionToken<Migrations<ClusterStoreModel> | undefined>();
|
||||||
@ -3,14 +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 { ClusterStore } from "./cluster-store";
|
import { ClusterStore } from "./store";
|
||||||
import { createClusterInjectionToken } from "../cluster/create-cluster-injection-token";
|
import { createClusterInjectionToken } from "../cluster/create-cluster-injection-token";
|
||||||
|
import { clusterStoreMigrationsInjectionToken } from "./migrations-injection-token";
|
||||||
|
|
||||||
const clusterStoreInjectable = getInjectable({
|
const clusterStoreInjectable = getInjectable({
|
||||||
instantiate: (di) =>
|
instantiate: (di) => new ClusterStore({
|
||||||
ClusterStore.createInstance({
|
createCluster: di.inject(createClusterInjectionToken),
|
||||||
createCluster: di.inject(createClusterInjectionToken),
|
migrations: di.inject(clusterStoreMigrationsInjectionToken),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
});
|
});
|
||||||
@ -2,18 +2,18 @@
|
|||||||
* 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 { ipcMain, ipcRenderer, webFrame } from "electron";
|
import { ipcMain, ipcRenderer, webFrame } from "electron";
|
||||||
import { action, comparer, computed, makeObservable, observable, reaction } from "mobx";
|
import { action, comparer, computed, makeObservable, observable, reaction } from "mobx";
|
||||||
import { BaseStore } from "../base-store";
|
import { BaseStore } from "../base-store";
|
||||||
import { Cluster } from "../cluster/cluster";
|
import { Cluster } from "../cluster/cluster";
|
||||||
import migrations from "../../migrations/cluster-store";
|
|
||||||
import logger from "../../main/logger";
|
import logger from "../../main/logger";
|
||||||
import { appEventBus } from "../app-event-bus/event-bus";
|
import { appEventBus } from "../app-event-bus/event-bus";
|
||||||
import { ipcMainHandle, requestMain } from "../ipc";
|
import { ipcMainHandle, requestMain } from "../ipc";
|
||||||
import { disposer, toJS } from "../utils";
|
import { disposer, toJS } from "../utils";
|
||||||
import type { ClusterModel, ClusterId, ClusterState } from "../cluster-types";
|
import type { ClusterModel, ClusterId, ClusterState } from "../cluster-types";
|
||||||
|
import type { Migrations } from "conf/dist/source/types";
|
||||||
|
|
||||||
export interface ClusterStoreModel {
|
export interface ClusterStoreModel {
|
||||||
clusters?: ClusterModel[];
|
clusters?: ClusterModel[];
|
||||||
@ -22,7 +22,8 @@ export interface ClusterStoreModel {
|
|||||||
const initialStates = "cluster:states";
|
const initialStates = "cluster:states";
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
createCluster: (model: ClusterModel) => Cluster
|
createCluster: (model: ClusterModel) => Cluster;
|
||||||
|
migrations: Migrations<ClusterStoreModel>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||||
@ -38,7 +39,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
syncOptions: {
|
syncOptions: {
|
||||||
equals: comparer.structural,
|
equals: comparer.structural,
|
||||||
},
|
},
|
||||||
migrations,
|
migrations: dependencies.migrations,
|
||||||
});
|
});
|
||||||
|
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
@ -103,9 +104,9 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
return this.clusters.size > 0;
|
return this.clusters.size > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
getById(id: ClusterId): Cluster | null {
|
getById = (id: ClusterId): Cluster | null => {
|
||||||
return this.clusters.get(id) ?? null;
|
return this.clusters.get(id) ?? null;
|
||||||
}
|
};
|
||||||
|
|
||||||
addCluster(clusterOrModel: ClusterModel | Cluster): Cluster {
|
addCluster(clusterOrModel: ClusterModel | Cluster): Cluster {
|
||||||
appEventBus.emit({ name: "cluster", action: "add" });
|
appEventBus.emit({ name: "cluster", action: "add" });
|
||||||
@ -13,19 +13,20 @@ import type { KubeconfigManager } from "../../main/kubeconfig-manager/kubeconfig
|
|||||||
import { loadConfigFromFile, loadConfigFromFileSync, validateKubeConfig } from "../kube-helpers";
|
import { loadConfigFromFile, loadConfigFromFileSync, validateKubeConfig } from "../kube-helpers";
|
||||||
import { apiResourceRecord, apiResources, KubeApiResource, KubeResource } from "../rbac";
|
import { apiResourceRecord, apiResources, KubeApiResource, KubeResource } from "../rbac";
|
||||||
import logger from "../../main/logger";
|
import logger from "../../main/logger";
|
||||||
import { VersionDetector } from "../../main/cluster-detectors/version-detector";
|
|
||||||
import { DetectorRegistry } from "../../main/cluster-detectors/detector-registry";
|
|
||||||
import plimit from "p-limit";
|
import plimit from "p-limit";
|
||||||
import type { ClusterState, ClusterRefreshOptions, ClusterMetricsResourceType, ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences, UpdateClusterModel, KubeAuthUpdate } from "../cluster-types";
|
import type { ClusterState, ClusterRefreshOptions, ClusterMetricsResourceType, ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences, UpdateClusterModel, KubeAuthUpdate } from "../cluster-types";
|
||||||
import { ClusterMetadataKey, initialNodeShellImage, ClusterStatus } from "../cluster-types";
|
import { ClusterMetadataKey, initialNodeShellImage, ClusterStatus } from "../cluster-types";
|
||||||
import { disposer, toJS } from "../utils";
|
import { disposer, toJS } from "../utils";
|
||||||
import type { Response } from "request";
|
import type { Response } from "request";
|
||||||
|
import type { ClusterDetectionResult } from "../../main/cluster-detectors/base-cluster-detector";
|
||||||
|
|
||||||
interface Dependencies {
|
export interface ClusterDependencies {
|
||||||
directoryForKubeConfigs: string,
|
directoryForKubeConfigs: string;
|
||||||
createKubeconfigManager: (cluster: Cluster) => KubeconfigManager,
|
createKubeconfigManager: (cluster: Cluster) => KubeconfigManager;
|
||||||
createContextHandler: (cluster: Cluster) => ContextHandler,
|
createContextHandler: (cluster: Cluster) => ContextHandler;
|
||||||
createKubectl: (clusterVersion: string) => Kubectl
|
createKubectl: (clusterVersion: string) => Kubectl;
|
||||||
|
detectMetadataForCluster: (cluster: Cluster) => Promise<ClusterMetadata>;
|
||||||
|
detectVersion: (cluster: Cluster) => Promise<ClusterDetectionResult>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -212,7 +213,11 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
return this.preferences.defaultNamespace;
|
return this.preferences.defaultNamespace;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(private dependencies: Dependencies, model: ClusterModel) {
|
static create(...args: ConstructorParameters<typeof Cluster>) {
|
||||||
|
return new Cluster(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(private readonly dependencies: ClusterDependencies, model: ClusterModel) {
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
this.id = model.id;
|
this.id = model.id;
|
||||||
this.updateModel(model);
|
this.updateModel(model);
|
||||||
@ -414,7 +419,7 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
@action
|
@action
|
||||||
async refreshMetadata() {
|
async refreshMetadata() {
|
||||||
logger.info(`[CLUSTER]: refreshMetadata`, this.getMeta());
|
logger.info(`[CLUSTER]: refreshMetadata`, this.getMeta());
|
||||||
const metadata = await DetectorRegistry.getInstance().detectForCluster(this);
|
const metadata = await this.dependencies.detectMetadataForCluster(this);
|
||||||
const existingMetadata = this.metadata;
|
const existingMetadata = this.metadata;
|
||||||
|
|
||||||
this.metadata = Object.assign(existingMetadata, metadata);
|
this.metadata = Object.assign(existingMetadata, metadata);
|
||||||
@ -461,16 +466,15 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
async getProxyKubeconfigPath(): Promise<string> {
|
getProxyKubeconfigPath(): Promise<string> {
|
||||||
return this.proxyKubeconfigManager.getPath();
|
return this.proxyKubeconfigManager.getPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async getConnectionStatus(): Promise<ClusterStatus> {
|
protected async getConnectionStatus(): Promise<ClusterStatus> {
|
||||||
try {
|
try {
|
||||||
const versionDetector = new VersionDetector(this);
|
const { value } = await this.dependencies.detectVersion(this);
|
||||||
const versionData = await versionDetector.detect();
|
|
||||||
|
|
||||||
this.metadata.version = versionData.value;
|
this.metadata.version = value;
|
||||||
|
|
||||||
return ClusterStatus.AccessGranted;
|
return ClusterStatus.AccessGranted;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -531,7 +535,7 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
async isClusterAdmin(): Promise<boolean> {
|
isClusterAdmin(): Promise<boolean> {
|
||||||
return this.canI({
|
return this.canI({
|
||||||
namespace: "kube-system",
|
namespace: "kube-system",
|
||||||
resource: "*",
|
resource: "*",
|
||||||
@ -542,7 +546,7 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
async canUseWatchApi(customizeResource: V1ResourceAttributes = {}): Promise<boolean> {
|
canUseWatchApi(customizeResource: V1ResourceAttributes = {}): Promise<boolean> {
|
||||||
return this.canI({
|
return this.canI({
|
||||||
verb: "watch",
|
verb: "watch",
|
||||||
resource: "*",
|
resource: "*",
|
||||||
@ -697,9 +701,9 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
return true; // allowed by default for other resources
|
return true; // allowed by default for other resources
|
||||||
}
|
}
|
||||||
|
|
||||||
isMetricHidden(resource: ClusterMetricsResourceType): boolean {
|
isMetricHidden = (resource: ClusterMetricsResourceType): boolean => {
|
||||||
return Boolean(this.preferences.hiddenMetrics?.includes(resource));
|
return Boolean(this.preferences.hiddenMetrics?.includes(resource));
|
||||||
}
|
};
|
||||||
|
|
||||||
get nodeShellImage(): string {
|
get nodeShellImage(): string {
|
||||||
return this.preferences?.nodeShellImage || initialNodeShellImage;
|
return this.preferences?.nodeShellImage || initialNodeShellImage;
|
||||||
|
|||||||
@ -6,5 +6,4 @@ import { getInjectionToken } from "@ogre-tools/injectable";
|
|||||||
import type { ClusterModel } from "../cluster-types";
|
import type { ClusterModel } from "../cluster-types";
|
||||||
import type { Cluster } from "./cluster";
|
import type { Cluster } from "./cluster";
|
||||||
|
|
||||||
export const createClusterInjectionToken =
|
export const createClusterInjectionToken = getInjectionToken<(model: ClusterModel) => Cluster>();
|
||||||
getInjectionToken<(model: ClusterModel) => Cluster>();
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data.injectable";
|
||||||
|
|
||||||
const directoryForLensLocalStorageInjectable = getInjectable({
|
const directoryForLensLocalStorageInjectable = getInjectable({
|
||||||
instantiate: (di) =>
|
instantiate: (di) =>
|
||||||
|
|||||||
@ -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 { HotbarStore } from "./hotbar-store";
|
import fsInjectable from "./fs.injectable";
|
||||||
|
|
||||||
const hotbarManagerInjectable = getInjectable({
|
const readDirInjectable = getInjectable({
|
||||||
instantiate: () => HotbarStore.getInstance(),
|
instantiate: (di) => di.inject(fsInjectable).readdir,
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default hotbarManagerInjectable;
|
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 { apiManager } from "../../../../common/k8s-api/api-manager";
|
|
||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import fsInjectable from "./fs.injectable";
|
||||||
|
|
||||||
const apiManagerInjectable = getInjectable({
|
const readFileInjectable = getInjectable({
|
||||||
instantiate: () => apiManager,
|
instantiate: (di) => di.inject(fsInjectable).readFile,
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default apiManagerInjectable;
|
export default readFileInjectable;
|
||||||
13
src/common/fs/read-json-file.injectable.ts
Normal file
13
src/common/fs/read-json-file.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 fsInjectable from "./fs.injectable";
|
||||||
|
|
||||||
|
const readJsonFileInjectable = getInjectable({
|
||||||
|
instantiate: (di) => di.inject(fsInjectable).readJson,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default readJsonFileInjectable;
|
||||||
@ -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 { UserStore } from "./user-store";
|
import { watch } from "chokidar";
|
||||||
|
|
||||||
const userStoreInjectable = getInjectable({
|
|
||||||
instantiate: () => UserStore.createInstance(),
|
|
||||||
|
|
||||||
|
const watchFilePathInjectable = getInjectable({
|
||||||
|
instantiate: () => watch,
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default userStoreInjectable;
|
export default watchFilePathInjectable;
|
||||||
45
src/common/fs/write-json-file.injectable.ts
Normal file
45
src/common/fs/write-json-file.injectable.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* 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 { bind } from "../utils";
|
||||||
|
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>;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function writeJsonFile({ writeJson, ensureDir }: Dependencies, filePath: string, content: JsonValue, options?: WriteOptions | BufferEncoding) {
|
||||||
|
await ensureDir(path.dirname(filePath), { mode: 0o755 });
|
||||||
|
|
||||||
|
const resolvedOptions = typeof options === "string"
|
||||||
|
? {
|
||||||
|
encoding: options,
|
||||||
|
}
|
||||||
|
: options;
|
||||||
|
|
||||||
|
await writeJson(filePath, content, {
|
||||||
|
encoding: "utf-8",
|
||||||
|
spaces: 2,
|
||||||
|
...resolvedOptions,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const writeJsonFileInjectable = getInjectable({
|
||||||
|
instantiate: (di) => {
|
||||||
|
const { writeJson, ensureDir } = di.inject(fsInjectable);
|
||||||
|
|
||||||
|
return bind(writeJsonFile, null, {
|
||||||
|
writeJson,
|
||||||
|
ensureDir,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default writeJsonFileInjectable;
|
||||||
39
src/common/getDiForUnitTesting.ts
Normal file
39
src/common/getDiForUnitTesting.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import glob from "glob";
|
||||||
|
import { memoize } from "lodash/fp";
|
||||||
|
|
||||||
|
import {
|
||||||
|
createContainer,
|
||||||
|
ConfigurableDependencyInjectionContainer,
|
||||||
|
} from "@ogre-tools/injectable";
|
||||||
|
|
||||||
|
export const getDiForUnitTesting = () => {
|
||||||
|
const di: ConfigurableDependencyInjectionContainer = createContainer();
|
||||||
|
|
||||||
|
getInjectableFilePaths()
|
||||||
|
.map(key => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const injectable = require(key).default;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: key,
|
||||||
|
...injectable,
|
||||||
|
aliases: [injectable, ...(injectable.aliases || [])],
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
||||||
|
.forEach(injectable => di.register(injectable));
|
||||||
|
|
||||||
|
di.preventSideEffects();
|
||||||
|
|
||||||
|
return di;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getInjectableFilePaths = memoize(() => [
|
||||||
|
...glob.sync("./**/*.injectable.{ts,tsx}", { cwd: __dirname }),
|
||||||
|
...glob.sync("../extensions/**/*.injectable.{ts,tsx}", { cwd: __dirname }),
|
||||||
|
]);
|
||||||
207
src/common/hotbar-store/__tests__/hotbar.test.ts
Normal file
207
src/common/hotbar-store/__tests__/hotbar.test.ts
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { anyObject } from "jest-mock-extended";
|
||||||
|
import { merge } from "lodash";
|
||||||
|
import type { CatalogEntityData, CatalogEntityKindData, CatalogEntity } from "../../catalog";
|
||||||
|
import type { LensLogger } from "../../logger";
|
||||||
|
import { Hotbar } from "../hotbar";
|
||||||
|
import { getEmptyHotbar } from "../hotbar-types";
|
||||||
|
|
||||||
|
function getMockCatalogEntity(data: Partial<CatalogEntityData> & CatalogEntityKindData): CatalogEntity {
|
||||||
|
return merge(data, {
|
||||||
|
getName: jest.fn(() => data.metadata?.name),
|
||||||
|
getId: jest.fn(() => data.metadata?.uid),
|
||||||
|
getSource: jest.fn(() => data.metadata?.source ?? "unknown"),
|
||||||
|
isEnabled: jest.fn(() => data.status?.enabled ?? true),
|
||||||
|
onContextMenuOpen: jest.fn(),
|
||||||
|
onSettingsOpen: jest.fn(),
|
||||||
|
metadata: {},
|
||||||
|
spec: {},
|
||||||
|
status: {},
|
||||||
|
}) as CatalogEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
const testCluster = getMockCatalogEntity({
|
||||||
|
apiVersion: "v1",
|
||||||
|
kind: "Cluster",
|
||||||
|
status: {
|
||||||
|
phase: "Running",
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
uid: "test",
|
||||||
|
name: "test",
|
||||||
|
labels: {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const minikubeCluster = getMockCatalogEntity({
|
||||||
|
apiVersion: "v1",
|
||||||
|
kind: "Cluster",
|
||||||
|
status: {
|
||||||
|
phase: "Running",
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
uid: "minikube",
|
||||||
|
name: "minikube",
|
||||||
|
labels: {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const awsCluster = getMockCatalogEntity({
|
||||||
|
apiVersion: "v1",
|
||||||
|
kind: "Cluster",
|
||||||
|
status: {
|
||||||
|
phase: "Running",
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
uid: "aws",
|
||||||
|
name: "aws",
|
||||||
|
labels: {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe("Hotbar", () => {
|
||||||
|
let hotbar: Hotbar;
|
||||||
|
let logger: LensLogger;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
logger = {
|
||||||
|
debug: jest.fn(),
|
||||||
|
error: jest.fn(),
|
||||||
|
info: jest.fn(),
|
||||||
|
silly: jest.fn(),
|
||||||
|
warn: jest.fn(),
|
||||||
|
};
|
||||||
|
hotbar = new Hotbar(getEmptyHotbar("Default"), { logger });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adds items", () => {
|
||||||
|
hotbar.addItem(testCluster);
|
||||||
|
const items = hotbar.items.filter(Boolean);
|
||||||
|
|
||||||
|
expect(items.length).toEqual(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("removes items", () => {
|
||||||
|
hotbar.addItem(testCluster);
|
||||||
|
hotbar.removeItemById("test");
|
||||||
|
hotbar.removeItemById("catalog-entity");
|
||||||
|
const items = hotbar.items.filter(Boolean);
|
||||||
|
|
||||||
|
expect(items).toStrictEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does nothing if removing with invalid uid", () => {
|
||||||
|
hotbar.addItem(testCluster);
|
||||||
|
hotbar.removeItemById("invalid uid");
|
||||||
|
const items = hotbar.items.filter(Boolean);
|
||||||
|
|
||||||
|
expect(items.length).toEqual(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("moves item to empty cell", () => {
|
||||||
|
hotbar.addItem(testCluster);
|
||||||
|
hotbar.addItem(minikubeCluster);
|
||||||
|
hotbar.addItem(awsCluster);
|
||||||
|
|
||||||
|
expect(hotbar.items[6]).toBeNull();
|
||||||
|
|
||||||
|
hotbar.restackItems(1, 5);
|
||||||
|
|
||||||
|
expect(hotbar.items[5]).toBeTruthy();
|
||||||
|
expect(hotbar.items[5].entity.uid).toEqual("test");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("moves items down", () => {
|
||||||
|
hotbar.addItem(testCluster);
|
||||||
|
hotbar.addItem(minikubeCluster);
|
||||||
|
hotbar.addItem(awsCluster);
|
||||||
|
|
||||||
|
// aws -> catalog
|
||||||
|
hotbar.restackItems(3, 0);
|
||||||
|
|
||||||
|
const items = hotbar.items.map(item => item?.entity.uid || null);
|
||||||
|
|
||||||
|
expect(items.slice(0, 4)).toEqual(["aws", "catalog-entity", "test", "minikube"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("moves items up", () => {
|
||||||
|
hotbar.addItem(testCluster);
|
||||||
|
hotbar.addItem(minikubeCluster);
|
||||||
|
hotbar.addItem(awsCluster);
|
||||||
|
|
||||||
|
// test -> aws
|
||||||
|
hotbar.restackItems(1, 3);
|
||||||
|
|
||||||
|
const items = hotbar.items.map(item => item?.entity.uid || null);
|
||||||
|
|
||||||
|
expect(items.slice(0, 4)).toEqual(["catalog-entity", "minikube", "aws", "test"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("logs an error if cellIndex is out of bounds", () => {
|
||||||
|
hotbar.addItem(testCluster, -1);
|
||||||
|
expect(logger.error).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject());
|
||||||
|
|
||||||
|
hotbar.addItem(testCluster, 12);
|
||||||
|
expect(logger.error).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject());
|
||||||
|
|
||||||
|
hotbar.addItem(testCluster, 13);
|
||||||
|
expect(logger.error).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject());
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws an error if getId is invalid or returns not a string", () => {
|
||||||
|
expect(() => hotbar.addItem({} as any)).toThrowError(TypeError);
|
||||||
|
expect(() => hotbar.addItem({ getId: () => true } as any)).toThrowError(TypeError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws an error if getName is invalid or returns not a string", () => {
|
||||||
|
expect(() => hotbar.addItem({ getId: () => "" } as any)).toThrowError(TypeError);
|
||||||
|
expect(() => hotbar.addItem({ getId: () => "", getName: () => 4 } as any)).toThrowError(TypeError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does nothing when item moved to same cell", () => {
|
||||||
|
hotbar.addItem(testCluster);
|
||||||
|
hotbar.restackItems(1, 1);
|
||||||
|
|
||||||
|
expect(hotbar.items[1].entity.uid).toEqual("test");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("new items takes first empty cell", () => {
|
||||||
|
hotbar.addItem(testCluster);
|
||||||
|
hotbar.addItem(awsCluster);
|
||||||
|
hotbar.restackItems(0, 3);
|
||||||
|
hotbar.addItem(minikubeCluster);
|
||||||
|
|
||||||
|
expect(hotbar.items[0].entity.uid).toEqual("minikube");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws if invalid arguments provided", () => {
|
||||||
|
// Prevent writing to stderr during this render.
|
||||||
|
const { error, warn } = console;
|
||||||
|
|
||||||
|
console.error = jest.fn();
|
||||||
|
console.warn = jest.fn();
|
||||||
|
|
||||||
|
hotbar.addItem(testCluster);
|
||||||
|
|
||||||
|
expect(() => hotbar.restackItems(-5, 0)).toThrow();
|
||||||
|
expect(() => hotbar.restackItems(2, -1)).toThrow();
|
||||||
|
expect(() => hotbar.restackItems(14, 1)).toThrow();
|
||||||
|
expect(() => hotbar.restackItems(11, 112)).toThrow();
|
||||||
|
|
||||||
|
// Restore writing to stderr.
|
||||||
|
console.error = error;
|
||||||
|
console.warn = warn;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("checks if entity already pinned to hotbar", () => {
|
||||||
|
hotbar.addItem(testCluster);
|
||||||
|
|
||||||
|
expect(hotbar.hasItem(testCluster)).toBeTruthy();
|
||||||
|
expect(hotbar.hasItem(awsCluster)).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
166
src/common/hotbar-store/__tests__/store.test.ts
Normal file
166
src/common/hotbar-store/__tests__/store.test.ts
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { DependencyInjectionContainer } from "@ogre-tools/injectable";
|
||||||
|
import mockFs from "mock-fs";
|
||||||
|
import { getDisForUnitTesting } from "../../../test-utils/get-dis-for-unit-testing";
|
||||||
|
import directoryForUserDataInjectable from "../../app-paths/directory-for-user-data.injectable";
|
||||||
|
import type { HotbarStore } from "../store";
|
||||||
|
|
||||||
|
describe("HotbarStore", () => {
|
||||||
|
let hotbarStore: HotbarStore;
|
||||||
|
let mainDi: DependencyInjectionContainer;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const dis = getDisForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
|
mockFs();
|
||||||
|
|
||||||
|
mainDi = dis.mainDi;
|
||||||
|
|
||||||
|
mainDi.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||||
|
|
||||||
|
await dis.runSetups();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockFs({
|
||||||
|
"some-directory-for-user-data": {
|
||||||
|
"lens-hotbar-store.json": JSON.stringify({}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
mockFs.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("load", () => {
|
||||||
|
it("loads one hotbar by default", () => {
|
||||||
|
expect(hotbarStore.hotbars.length).toEqual(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("add", () => {
|
||||||
|
it("adds a hotbar", () => {
|
||||||
|
hotbarStore.add({ name: "hottest" });
|
||||||
|
expect(hotbarStore.hotbars.length).toEqual(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("hotbar items", () => {
|
||||||
|
it("initially creates 12 empty cells", () => {
|
||||||
|
expect(hotbarStore.getActive().items.length).toEqual(12);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("initially adds catalog entity as first item", () => {
|
||||||
|
expect(hotbarStore.getActive().items[0].entity.name).toEqual("Catalog");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("pre beta-5 migrations", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const mockOpts = {
|
||||||
|
"some-directory-for-user-data": {
|
||||||
|
"lens-hotbar-store.json": JSON.stringify({
|
||||||
|
__internal__: {
|
||||||
|
migrations: {
|
||||||
|
version: "5.0.0-beta.3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"hotbars": [
|
||||||
|
{
|
||||||
|
"id": "3caac17f-aec2-4723-9694-ad204465d935",
|
||||||
|
"name": "myhotbar",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"entity": {
|
||||||
|
"uid": "1dfa26e2ebab15780a3547e9c7fa785c",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": {
|
||||||
|
"uid": "55b42c3c7ba3b04193416cda405269a5",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": {
|
||||||
|
"uid": "176fd331968660832f62283219d7eb6e",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": {
|
||||||
|
"uid": "61c4fb45528840ebad1badc25da41d14",
|
||||||
|
"name": "user1-context",
|
||||||
|
"source": "local",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": {
|
||||||
|
"uid": "27d6f99fe9e7548a6e306760bfe19969",
|
||||||
|
"name": "foo2",
|
||||||
|
"source": "local",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
"entity": {
|
||||||
|
"uid": "c0b20040646849bb4dcf773e43a0bf27",
|
||||||
|
"name": "multinode-demo",
|
||||||
|
"source": "local",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
mockFs(mockOpts);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
mockFs.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows to retrieve a hotbar", () => {
|
||||||
|
const hotbar = hotbarStore.getById("3caac17f-aec2-4723-9694-ad204465d935");
|
||||||
|
|
||||||
|
expect(hotbar.id).toBe("3caac17f-aec2-4723-9694-ad204465d935");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("clears cells without entity", () => {
|
||||||
|
const items = hotbarStore.hotbars[0].items;
|
||||||
|
|
||||||
|
expect(items[2]).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adds extra data to cells with according entity", () => {
|
||||||
|
const items = hotbarStore.hotbars[0].items;
|
||||||
|
|
||||||
|
expect(items[0]).toEqual({
|
||||||
|
entity: {
|
||||||
|
name: "mycluster",
|
||||||
|
source: "local",
|
||||||
|
uid: "1dfa26e2ebab15780a3547e9c7fa785c",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(items[1]).toEqual({
|
||||||
|
entity: {
|
||||||
|
name: "my_shiny_cluster",
|
||||||
|
source: "remote",
|
||||||
|
uid: "55b42c3c7ba3b04193416cda405269a5",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
18
src/common/hotbar-store/active-hotbar.injectable.ts
Normal file
18
src/common/hotbar-store/active-hotbar.injectable.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* 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 hotbarStoreInjectable from "./store.injectable";
|
||||||
|
|
||||||
|
const activeHotbarInjectable = getInjectable({
|
||||||
|
instantiate: (di) => {
|
||||||
|
const hotbarStore = di.inject(hotbarStoreInjectable);
|
||||||
|
|
||||||
|
return computed(() => hotbarStore.getActive());
|
||||||
|
},
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default activeHotbarInjectable;
|
||||||
27
src/common/hotbar-store/add-to-active-hotbar.injectable.ts
Normal file
27
src/common/hotbar-store/add-to-active-hotbar.injectable.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import type { IComputedValue } from "mobx";
|
||||||
|
import type { CatalogEntity } from "../catalog";
|
||||||
|
import { bind } from "../utils";
|
||||||
|
import activeHotbarInjectable from "./active-hotbar.injectable";
|
||||||
|
import type { Hotbar } from "./hotbar";
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
hotbar: IComputedValue<Hotbar>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addToActiveHotbar({ hotbar }: Dependencies, entity: CatalogEntity, cellIndex?: number) {
|
||||||
|
return hotbar.get().addItem(entity, cellIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
const addToActiveHotbarInjectable = getInjectable({
|
||||||
|
instantiate: (di) => bind(addToActiveHotbar, null, {
|
||||||
|
hotbar: di.inject(activeHotbarInjectable),
|
||||||
|
}),
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default addToActiveHotbarInjectable;
|
||||||
13
src/common/hotbar-store/create-new-hotbar.injectable.ts
Normal file
13
src/common/hotbar-store/create-new-hotbar.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 hotbarStoreInjectable from "./store.injectable";
|
||||||
|
|
||||||
|
const createNewHotbarInjectable = getInjectable({
|
||||||
|
instantiate: (di) => di.inject(hotbarStoreInjectable).add,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default createNewHotbarInjectable;
|
||||||
13
src/common/hotbar-store/get-hotbar-by-name.injectable.ts
Normal file
13
src/common/hotbar-store/get-hotbar-by-name.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 hotbarStoreInjectable from "./store.injectable";
|
||||||
|
|
||||||
|
const getHotbarByNameInjectable = getInjectable({
|
||||||
|
instantiate: (di) => di.inject(hotbarStoreInjectable).getByName,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default getHotbarByNameInjectable;
|
||||||
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as uuid from "uuid";
|
import * as uuid from "uuid";
|
||||||
import { tuple, Tuple } from "./utils";
|
import { tuple, Tuple } from "../utils";
|
||||||
|
|
||||||
export interface HotbarItem {
|
export interface HotbarItem {
|
||||||
entity: {
|
entity: {
|
||||||
@ -17,8 +17,6 @@ export interface HotbarItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Hotbar = Required<CreateHotbarData>;
|
|
||||||
|
|
||||||
export interface CreateHotbarData {
|
export interface CreateHotbarData {
|
||||||
id?: string;
|
id?: string;
|
||||||
name: string;
|
name: string;
|
||||||
@ -31,7 +29,7 @@ export interface CreateHotbarOptions {
|
|||||||
|
|
||||||
export const defaultHotbarCells = 12; // Number is chosen to easy hit any item with keyboard
|
export const defaultHotbarCells = 12; // Number is chosen to easy hit any item with keyboard
|
||||||
|
|
||||||
export function getEmptyHotbar(name: string, id: string = uuid.v4()): Hotbar {
|
export function getEmptyHotbar(name: string, id: string = uuid.v4()): Required<CreateHotbarData> {
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
items: tuple.filled(defaultHotbarCells, null),
|
items: tuple.filled(defaultHotbarCells, null),
|
||||||
121
src/common/hotbar-store/hotbar.ts
Normal file
121
src/common/hotbar-store/hotbar.ts
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { action } from "mobx";
|
||||||
|
import type { CatalogEntity } from "../catalog";
|
||||||
|
import { broadcastMessage, HotbarTooManyItems } from "../ipc";
|
||||||
|
import type { LensLogger } from "../logger";
|
||||||
|
import type { Tuple } from "../utils";
|
||||||
|
import type { HotbarItem, defaultHotbarCells, CreateHotbarData } from "./hotbar-types";
|
||||||
|
|
||||||
|
export interface HotbarDependencies {
|
||||||
|
readonly logger: LensLogger;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Hotbar {
|
||||||
|
public readonly id: string;
|
||||||
|
public name: string;
|
||||||
|
public readonly items: Tuple<HotbarItem | null, typeof defaultHotbarCells>;
|
||||||
|
|
||||||
|
constructor(data: Required<CreateHotbarData>, protected readonly dependencies: HotbarDependencies) {
|
||||||
|
this.id = data.id;
|
||||||
|
this.name = data.name;
|
||||||
|
this.items = data.items;
|
||||||
|
}
|
||||||
|
|
||||||
|
setName = (name: string) => {
|
||||||
|
this.name = name;
|
||||||
|
};
|
||||||
|
|
||||||
|
addItem = action((item: CatalogEntity, cellIndex?: number) => {
|
||||||
|
const uid = item.getId();
|
||||||
|
const name = item.getName();
|
||||||
|
|
||||||
|
if (typeof uid !== "string") {
|
||||||
|
throw new TypeError("item's id must be a string");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof name !== "string") {
|
||||||
|
throw new TypeError("item's name must be a string");
|
||||||
|
}
|
||||||
|
|
||||||
|
const newItem = { entity: {
|
||||||
|
uid,
|
||||||
|
name,
|
||||||
|
source: item.metadata.source,
|
||||||
|
}};
|
||||||
|
|
||||||
|
|
||||||
|
if (this.hasItem(item)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cellIndex === undefined) {
|
||||||
|
// Add item to empty cell
|
||||||
|
const emptyCellIndex = this.items.indexOf(null);
|
||||||
|
|
||||||
|
if (emptyCellIndex != -1) {
|
||||||
|
this.items[emptyCellIndex] = newItem;
|
||||||
|
} else {
|
||||||
|
broadcastMessage(HotbarTooManyItems);
|
||||||
|
}
|
||||||
|
} else if (0 <= cellIndex && cellIndex < this.items.length) {
|
||||||
|
this.items[cellIndex] = newItem;
|
||||||
|
} else {
|
||||||
|
this.dependencies.logger.error(`[HOTBAR-${this.id}]: cannot pin entity to hotbar outside of index range`, { entityId: uid, cellIndex });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
private findClosestEmptyIndex(from: number, direction = 1) {
|
||||||
|
let index = from;
|
||||||
|
|
||||||
|
while(this.items[index] != null) {
|
||||||
|
index += direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if entity already pinned to hotbar
|
||||||
|
* @returns boolean
|
||||||
|
*/
|
||||||
|
hasItem = (entity: CatalogEntity): boolean => {
|
||||||
|
return this.items.findIndex(item => item?.entity.uid === entity.metadata.uid) >= 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
removeItemById = action((uid: string): void => {
|
||||||
|
const index = this.items.findIndex(item => item?.entity.uid === uid);
|
||||||
|
|
||||||
|
if (index < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.items[index] = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
restackItems = action((from: number, to: number): void => {
|
||||||
|
const { items } = this;
|
||||||
|
const source = items[from];
|
||||||
|
const moveDown = from < to;
|
||||||
|
|
||||||
|
if (from < 0 || to < 0 || from >= items.length || to >= items.length || isNaN(from) || isNaN(to)) {
|
||||||
|
throw new Error("Invalid 'from' or 'to' arguments");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (from == to) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
items.splice(from, 1, null);
|
||||||
|
|
||||||
|
if (items[to] == null) {
|
||||||
|
items.splice(to, 1, source);
|
||||||
|
} else {
|
||||||
|
// Move cells up or down to closes empty cell
|
||||||
|
items.splice(this.findClosestEmptyIndex(to, moveDown ? -1 : 1), 1);
|
||||||
|
items.splice(to, 0, source);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import type { IComputedValue } from "mobx";
|
||||||
|
import type { CatalogEntity } from "../catalog";
|
||||||
|
import { bind } from "../utils";
|
||||||
|
import activeHotbarInjectable from "./active-hotbar.injectable";
|
||||||
|
import type { Hotbar } from "./hotbar";
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
hotbar: IComputedValue<Hotbar>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAddedToActiveHotbar({ hotbar }: Dependencies, entity: CatalogEntity) {
|
||||||
|
return hotbar.get().hasItem(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isItemInActiveHotbarInjectable = getInjectable({
|
||||||
|
instantiate: (di) => bind(isAddedToActiveHotbar, null, {
|
||||||
|
hotbar: di.inject(activeHotbarInjectable),
|
||||||
|
}),
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default isItemInActiveHotbarInjectable;
|
||||||
9
src/common/hotbar-store/migrations-injectable-token.ts
Normal file
9
src/common/hotbar-store/migrations-injectable-token.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||||
|
import type { Migrations } from "conf/dist/source/types";
|
||||||
|
import type { HotbarStoreModel } from "./store";
|
||||||
|
|
||||||
|
export const hotbarStoreMigrationsInjectionToken = getInjectionToken<Migrations<HotbarStoreModel> | undefined>();
|
||||||
@ -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 hotbarStoreInjectable from "./store.injectable";
|
||||||
|
|
||||||
|
const removeAllHotbarItemsInjectable = getInjectable({
|
||||||
|
instantiate: (di) => di.inject(hotbarStoreInjectable).removeAllHotbarItems,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default removeAllHotbarItemsInjectable;
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import type { IComputedValue } from "mobx";
|
||||||
|
import { bind } from "../utils";
|
||||||
|
import activeHotbarInjectable from "./active-hotbar.injectable";
|
||||||
|
import type { Hotbar } from "./hotbar";
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
hotbar: IComputedValue<Hotbar>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeByIdFromActiveHotbar({ hotbar }: Dependencies, id: string) {
|
||||||
|
return hotbar.get().removeItemById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeByIdFromActiveHotbarInjectable = getInjectable({
|
||||||
|
instantiate: (di) => bind(removeByIdFromActiveHotbar, null, {
|
||||||
|
hotbar: di.inject(activeHotbarInjectable),
|
||||||
|
}),
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default removeByIdFromActiveHotbarInjectable;
|
||||||
16
src/common/hotbar-store/store.injectable.ts
Normal file
16
src/common/hotbar-store/store.injectable.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* 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 { hotbarStoreMigrationsInjectionToken } from "./migrations-injectable-token";
|
||||||
|
import { HotbarStore } from "./store";
|
||||||
|
|
||||||
|
const hotbarStoreInjectable = getInjectable({
|
||||||
|
instantiate: (di) => new HotbarStore({
|
||||||
|
migrations: di.inject(hotbarStoreMigrationsInjectionToken),
|
||||||
|
}),
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default hotbarStoreInjectable;
|
||||||
@ -4,26 +4,29 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { action, comparer, observable, makeObservable, computed } from "mobx";
|
import { action, comparer, observable, makeObservable, computed } from "mobx";
|
||||||
import { BaseStore } from "./base-store";
|
import { BaseStore } from "../base-store";
|
||||||
import migrations from "../migrations/hotbar-store";
|
import { toJS } from "../utils";
|
||||||
import { toJS } from "./utils";
|
import { catalogEntity } from "../../main/catalog-sources/general";
|
||||||
import { CatalogEntity } from "./catalog";
|
import { defaultHotbarCells, getEmptyHotbar, CreateHotbarData, CreateHotbarOptions } from "./hotbar-types";
|
||||||
import { catalogEntity } from "../main/catalog-sources/general";
|
import { Hotbar } from "./hotbar";
|
||||||
import logger from "../main/logger";
|
import logger from "../logger";
|
||||||
import { broadcastMessage, HotbarTooManyItems } from "./ipc";
|
import type { Migrations } from "conf/dist/source/types";
|
||||||
import { defaultHotbarCells, getEmptyHotbar, Hotbar, CreateHotbarData, CreateHotbarOptions } from "./hotbar-types";
|
|
||||||
|
|
||||||
export interface HotbarStoreModel {
|
export interface HotbarStoreModel {
|
||||||
hotbars: Hotbar[];
|
hotbars: Required<CreateHotbarData>[];
|
||||||
activeHotbarId: string;
|
activeHotbarId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface HotbarStoreDependencies {
|
||||||
|
migrations: Migrations<HotbarStoreModel> | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export class HotbarStore extends BaseStore<HotbarStoreModel> {
|
export class HotbarStore extends BaseStore<HotbarStoreModel> {
|
||||||
readonly displayName = "HotbarStore";
|
readonly displayName = "HotbarStore";
|
||||||
@observable hotbars: Hotbar[] = [];
|
@observable hotbars: Hotbar[] = [];
|
||||||
@observable private _activeHotbarId: string;
|
@observable private _activeHotbarId: string;
|
||||||
|
|
||||||
constructor() {
|
constructor({ migrations }: HotbarStoreDependencies) {
|
||||||
super({
|
super({
|
||||||
configName: "lens-hotbar-store",
|
configName: "lens-hotbar-store",
|
||||||
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
||||||
@ -81,9 +84,9 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
|
|||||||
|
|
||||||
hotbar.items[0] = initialItem;
|
hotbar.items[0] = initialItem;
|
||||||
|
|
||||||
this.hotbars = [hotbar];
|
this.hotbars = [new Hotbar(hotbar, { logger })];
|
||||||
} else {
|
} else {
|
||||||
this.hotbars = data.hotbars;
|
this.hotbars = data.hotbars.map(hotbar => new Hotbar(hotbar, { logger }));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.hotbars.forEach(ensureExactHotbarItemLength);
|
this.hotbars.forEach(ensureExactHotbarItemLength);
|
||||||
@ -110,16 +113,16 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
|
|||||||
return this.getById(this.activeHotbarId);
|
return this.getById(this.activeHotbarId);
|
||||||
}
|
}
|
||||||
|
|
||||||
getByName(name: string) {
|
getByName = (name: string) => {
|
||||||
return this.hotbars.find((hotbar) => hotbar.name === name);
|
return this.hotbars.find((hotbar) => hotbar.name === name);
|
||||||
}
|
};
|
||||||
|
|
||||||
getById(id: string) {
|
getById(id: string) {
|
||||||
return this.hotbars.find((hotbar) => hotbar.id === id);
|
return this.hotbars.find((hotbar) => hotbar.id === id);
|
||||||
}
|
}
|
||||||
|
|
||||||
add = action((data: CreateHotbarData, { setActive = false }: CreateHotbarOptions = {}) => {
|
add = action((data: CreateHotbarData, { setActive = false }: CreateHotbarOptions = {}) => {
|
||||||
const hotbar = getEmptyHotbar(data.name, data.id);
|
const hotbar = new Hotbar(getEmptyHotbar(data.name, data.id), { logger });
|
||||||
|
|
||||||
this.hotbars.push(hotbar);
|
this.hotbars.push(hotbar);
|
||||||
|
|
||||||
@ -150,138 +153,35 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@action
|
|
||||||
addToHotbar(item: CatalogEntity, cellIndex?: number) {
|
|
||||||
const hotbar = this.getActive();
|
|
||||||
const uid = item.metadata?.uid;
|
|
||||||
const name = item.metadata?.name;
|
|
||||||
|
|
||||||
if (typeof uid !== "string") {
|
|
||||||
throw new TypeError("CatalogEntity.metadata.uid must be a string");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof name !== "string") {
|
|
||||||
throw new TypeError("CatalogEntity.metadata.name must be a string");
|
|
||||||
}
|
|
||||||
|
|
||||||
const newItem = { entity: {
|
|
||||||
uid,
|
|
||||||
name,
|
|
||||||
source: item.metadata.source,
|
|
||||||
}};
|
|
||||||
|
|
||||||
|
|
||||||
if (this.isAddedToActive(item)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cellIndex === undefined) {
|
|
||||||
// Add item to empty cell
|
|
||||||
const emptyCellIndex = hotbar.items.indexOf(null);
|
|
||||||
|
|
||||||
if (emptyCellIndex != -1) {
|
|
||||||
hotbar.items[emptyCellIndex] = newItem;
|
|
||||||
} else {
|
|
||||||
broadcastMessage(HotbarTooManyItems);
|
|
||||||
}
|
|
||||||
} else if (0 <= cellIndex && cellIndex < hotbar.items.length) {
|
|
||||||
hotbar.items[cellIndex] = newItem;
|
|
||||||
} else {
|
|
||||||
logger.error(`[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range`, { entityId: uid, hotbarId: hotbar.id, cellIndex });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
removeFromHotbar(uid: string): void {
|
|
||||||
const hotbar = this.getActive();
|
|
||||||
const index = hotbar.items.findIndex(item => item?.entity.uid === uid);
|
|
||||||
|
|
||||||
if (index < 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
hotbar.items[index] = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove all hotbar items that reference the `uid`.
|
* Remove all hotbar items that reference the `uid`.
|
||||||
* @param uid The `EntityId` that each hotbar item refers to
|
* @param uid The `EntityId` that each hotbar item refers to
|
||||||
* @returns A function that will (in an action) undo the removing of the hotbar items. This function will not complete if the hotbar has changed.
|
* @returns A function that will (in an action) undo the removing of the hotbar items. This function will not complete if the hotbar has changed.
|
||||||
*/
|
*/
|
||||||
@action
|
removeAllHotbarItems = action((uid: string) => {
|
||||||
removeAllHotbarItems(uid: string) {
|
|
||||||
for (const hotbar of this.hotbars) {
|
for (const hotbar of this.hotbars) {
|
||||||
const index = hotbar.items.findIndex((i) => i?.entity.uid === uid);
|
hotbar.removeItemById(uid);
|
||||||
|
|
||||||
if (index >= 0) {
|
|
||||||
hotbar.items[index] = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
findClosestEmptyIndex(from: number, direction = 1) {
|
|
||||||
let index = from;
|
|
||||||
|
|
||||||
while(this.getActive().items[index] != null) {
|
|
||||||
index += direction;
|
|
||||||
}
|
|
||||||
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
restackItems(from: number, to: number): void {
|
|
||||||
const { items } = this.getActive();
|
|
||||||
const source = items[from];
|
|
||||||
const moveDown = from < to;
|
|
||||||
|
|
||||||
if (from < 0 || to < 0 || from >= items.length || to >= items.length || isNaN(from) || isNaN(to)) {
|
|
||||||
throw new Error("Invalid 'from' or 'to' arguments");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (from == to) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
items.splice(from, 1, null);
|
|
||||||
|
|
||||||
if (items[to] == null) {
|
|
||||||
items.splice(to, 1, source);
|
|
||||||
} else {
|
|
||||||
// Move cells up or down to closes empty cell
|
|
||||||
items.splice(this.findClosestEmptyIndex(to, moveDown ? -1 : 1), 1);
|
|
||||||
items.splice(to, 0, source);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switchToPrevious() {
|
switchToPrevious() {
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
let index = this.activeHotbarIndex - 1;
|
||||||
let index = hotbarStore.activeHotbarIndex - 1;
|
|
||||||
|
|
||||||
if (index < 0) {
|
if (index < 0) {
|
||||||
index = hotbarStore.hotbars.length - 1;
|
index = this.hotbars.length - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
hotbarStore.setActiveHotbar(index);
|
this.setActiveHotbar(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
switchToNext() {
|
switchToNext() {
|
||||||
const hotbarStore = HotbarStore.getInstance();
|
let index = this.activeHotbarIndex + 1;
|
||||||
let index = hotbarStore.activeHotbarIndex + 1;
|
|
||||||
|
|
||||||
if (index >= hotbarStore.hotbars.length) {
|
if (index >= this.hotbars.length) {
|
||||||
index = 0;
|
index = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
hotbarStore.setActiveHotbar(index);
|
this.setActiveHotbar(index);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if entity already pinned to hotbar
|
|
||||||
* @returns boolean
|
|
||||||
*/
|
|
||||||
isAddedToActive(entity: CatalogEntity) {
|
|
||||||
return !!this.getActive().items.find(item => item?.entity.uid === entity.metadata.uid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getDisplayLabel(hotbar: Hotbar): string {
|
getDisplayLabel(hotbar: Hotbar): string {
|
||||||
@ -28,7 +28,7 @@ const electronRemote = (() => {
|
|||||||
|
|
||||||
const subFramesChannel = "ipc:get-sub-frames";
|
const subFramesChannel = "ipc:get-sub-frames";
|
||||||
|
|
||||||
export async function requestMain(channel: string, ...args: any[]) {
|
export function requestMain(channel: string, ...args: any[]) {
|
||||||
return ipcRenderer.invoke(channel, ...args.map(sanitizePayload));
|
return ipcRenderer.invoke(channel, ...args.map(sanitizePayload));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -38,7 +38,7 @@ export function onceCorrect<
|
|||||||
if (verifier(args)) {
|
if (verifier(args)) {
|
||||||
source.removeListener(channel, wrappedListener); // remove immediately
|
source.removeListener(channel, wrappedListener); // remove immediately
|
||||||
|
|
||||||
(async () => (listener(event, ...args)))() // might return a promise, or throw, or reject
|
(async () => await listener(event, ...args))() // might return a promise, or throw, or reject
|
||||||
.catch((error: any) => logger.error("[IPC]: channel once handler threw error", { channel, error }));
|
.catch((error: any) => logger.error("[IPC]: channel once handler threw error", { channel, error }));
|
||||||
} else {
|
} else {
|
||||||
logger.error("[IPC]: channel was emitted with invalid data", { channel, args });
|
logger.error("[IPC]: channel was emitted with invalid data", { channel, args });
|
||||||
@ -70,7 +70,7 @@ export function onCorrect<
|
|||||||
}): Disposer {
|
}): Disposer {
|
||||||
function wrappedListener(event: ListenerEvent<IPC>, ...args: unknown[]) {
|
function wrappedListener(event: ListenerEvent<IPC>, ...args: unknown[]) {
|
||||||
if (verifier(args)) {
|
if (verifier(args)) {
|
||||||
(async () => (listener(event, ...args)))() // might return a promise, or throw, or reject
|
(async () => await listener(event, ...args))() // might return a promise, or throw, or reject
|
||||||
.catch(error => logger.error("[IPC]: channel on handler threw error", { channel, error }));
|
.catch(error => logger.error("[IPC]: channel on handler threw error", { channel, error }));
|
||||||
} else {
|
} else {
|
||||||
logger.error("[IPC]: channel was emitted with invalid data", { channel, args });
|
logger.error("[IPC]: channel was emitted with invalid data", { channel, args });
|
||||||
|
|||||||
@ -28,6 +28,8 @@ export abstract class ItemStore<Item extends ItemObject> {
|
|||||||
autoBind(this);
|
autoBind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readonly computedItems = computed(() => [...this.items]);
|
||||||
|
|
||||||
@computed get selectedItems(): Item[] {
|
@computed get selectedItems(): Item[] {
|
||||||
return this.items.filter(item => this.selectedItemsIds.get(item.getId()));
|
return this.items.filter(item => this.selectedItemsIds.get(item.getId()));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,45 +3,53 @@
|
|||||||
* 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 { ingressStore } from "../../../renderer/components/+network-ingresses/ingress.store";
|
import type { ConfigurableDependencyInjectionContainer } from "@ogre-tools/injectable";
|
||||||
import { apiManager } from "../api-manager";
|
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||||
|
import type { ApiManager } from "../api-manager";
|
||||||
|
import apiManagerInjectable from "../api-manager.injectable";
|
||||||
import { KubeApi } from "../kube-api";
|
import { KubeApi } from "../kube-api";
|
||||||
import { KubeObject } from "../kube-object";
|
import { KubeObject } from "../kube-object";
|
||||||
|
import { KubeObjectStore } from "../kube-object.store";
|
||||||
|
|
||||||
class TestApi extends KubeApi<KubeObject> {
|
class TestApi extends KubeApi<KubeObject> {
|
||||||
|
protected checkPreferredVersion() {
|
||||||
protected async checkPreferredVersion() {
|
return Promise.resolve();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("ApiManager", () => {
|
describe("ApiManager", () => {
|
||||||
describe("registerApi", () => {
|
let di: ConfigurableDependencyInjectionContainer;
|
||||||
it("re-register store if apiBase changed", async () => {
|
let apiManager: ApiManager;
|
||||||
const apiBase = "apis/v1/foo";
|
|
||||||
const fallbackApiBase = "/apis/extensions/v1beta1/foo";
|
|
||||||
const kubeApi = new TestApi({
|
|
||||||
objectConstructor: KubeObject,
|
|
||||||
apiBase,
|
|
||||||
fallbackApiBases: [fallbackApiBase],
|
|
||||||
checkPreferredVersion: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
apiManager.registerApi(apiBase, kubeApi);
|
beforeEach(() => {
|
||||||
|
di = getDiForUnitTesting();
|
||||||
|
apiManager = di.inject(apiManagerInjectable);
|
||||||
|
});
|
||||||
|
|
||||||
// Define to use test api for ingress store
|
it("allows apis to be accessible by their new apiBase if it changes", () => {
|
||||||
Object.defineProperty(ingressStore, "api", { value: kubeApi });
|
const apiBase = "apis/v1/foo";
|
||||||
apiManager.registerStore(ingressStore, [kubeApi]);
|
const fallbackApiBase = "/apis/extensions/v1beta1/foo";
|
||||||
|
const kubeApi = new TestApi({
|
||||||
// Test that store is returned with original apiBase
|
objectConstructor: KubeObject,
|
||||||
expect(apiManager.getStore(kubeApi)).toBe(ingressStore);
|
apiBase,
|
||||||
|
fallbackApiBases: [fallbackApiBase],
|
||||||
// Change apiBase similar as checkPreferredVersion does
|
checkPreferredVersion: true,
|
||||||
Object.defineProperty(kubeApi, "apiBase", { value: fallbackApiBase });
|
|
||||||
apiManager.registerApi(fallbackApiBase, kubeApi);
|
|
||||||
|
|
||||||
// Test that store is returned with new apiBase
|
|
||||||
expect(apiManager.getStore(kubeApi)).toBe(ingressStore);
|
|
||||||
});
|
});
|
||||||
|
const kubeStore = new class extends KubeObjectStore<KubeObject> {
|
||||||
|
api = kubeApi;
|
||||||
|
};
|
||||||
|
|
||||||
|
apiManager.registerApi(kubeApi);
|
||||||
|
apiManager.registerStore(kubeStore);
|
||||||
|
|
||||||
|
// Test that store is returned with original apiBase
|
||||||
|
expect(apiManager.getStore(kubeApi)).toBe(kubeStore);
|
||||||
|
|
||||||
|
// Change apiBase similar as checkPreferredVersion does
|
||||||
|
Object.defineProperty(kubeApi, "apiBase", { value: fallbackApiBase });
|
||||||
|
apiManager.registerApi(fallbackApiBase, kubeApi);
|
||||||
|
|
||||||
|
// Test that store is returned with new apiBase
|
||||||
|
expect(apiManager.getStore(kubeApi)).toBe(kubeStore);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { anyObject } from "jest-mock-extended";
|
import { anyObject } from "jest-mock-extended";
|
||||||
import { HelmChart } from "../endpoints/helm-charts.api";
|
import { HelmChart } from "../endpoints/helm-chart.api";
|
||||||
|
|
||||||
describe("HelmChart tests", () => {
|
describe("HelmChart tests", () => {
|
||||||
describe("HelmChart.create() tests", () => {
|
describe("HelmChart.create() tests", () => {
|
||||||
|
|||||||
@ -10,12 +10,6 @@ import { KubeObject } from "../kube-object";
|
|||||||
import AbortController from "abort-controller";
|
import AbortController from "abort-controller";
|
||||||
import { delay } from "../../utils/delay";
|
import { delay } from "../../utils/delay";
|
||||||
import { PassThrough } from "stream";
|
import { PassThrough } from "stream";
|
||||||
import { ApiManager, apiManager } from "../api-manager";
|
|
||||||
import { Ingress, Pod } from "../endpoints";
|
|
||||||
|
|
||||||
jest.mock("../api-manager");
|
|
||||||
|
|
||||||
const mockApiManager = apiManager as jest.Mocked<ApiManager>;
|
|
||||||
|
|
||||||
class TestKubeObject extends KubeObject {
|
class TestKubeObject extends KubeObject {
|
||||||
static kind = "Pod";
|
static kind = "Pod";
|
||||||
@ -24,13 +18,13 @@ class TestKubeObject extends KubeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class TestKubeApi extends KubeApi<TestKubeObject> {
|
class TestKubeApi extends KubeApi<TestKubeObject> {
|
||||||
public async checkPreferredVersion() {
|
public checkPreferredVersion() {
|
||||||
return super.checkPreferredVersion();
|
return super.checkPreferredVersion();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("forRemoteCluster", () => {
|
describe("forRemoteCluster", () => {
|
||||||
it("builds api client for KubeObject", async () => {
|
it("builds api client for KubeObject", () => {
|
||||||
const api = forRemoteCluster({
|
const api = forRemoteCluster({
|
||||||
cluster: {
|
cluster: {
|
||||||
server: "https://127.0.0.1:6443",
|
server: "https://127.0.0.1:6443",
|
||||||
@ -43,7 +37,7 @@ describe("forRemoteCluster", () => {
|
|||||||
expect(api).toBeInstanceOf(KubeApi);
|
expect(api).toBeInstanceOf(KubeApi);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("builds api client for given KubeApi", async () => {
|
it("builds api client for given KubeApi", () => {
|
||||||
const api = forRemoteCluster({
|
const api = forRemoteCluster({
|
||||||
cluster: {
|
cluster: {
|
||||||
server: "https://127.0.0.1:6443",
|
server: "https://127.0.0.1:6443",
|
||||||
@ -66,7 +60,7 @@ describe("forRemoteCluster", () => {
|
|||||||
},
|
},
|
||||||
}, TestKubeObject);
|
}, TestKubeObject);
|
||||||
|
|
||||||
(fetch as any).mockResponse(async (request: any) => {
|
(fetch as any).mockResponse((request: any) => {
|
||||||
expect(request.url).toEqual("https://127.0.0.1:6443/api/v1/pods");
|
expect(request.url).toEqual("https://127.0.0.1:6443/api/v1/pods");
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -91,7 +85,7 @@ describe("KubeApi", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("uses url from apiBase if apiBase contains the resource", async () => {
|
it("uses url from apiBase if apiBase contains the resource", async () => {
|
||||||
(fetch as any).mockResponse(async (request: any) => {
|
(fetch as any).mockResponse((request: any) => {
|
||||||
if (request.url === "http://127.0.0.1:9999/api-kube/apis/networking.k8s.io/v1") {
|
if (request.url === "http://127.0.0.1:9999/api-kube/apis/networking.k8s.io/v1") {
|
||||||
return {
|
return {
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
@ -137,7 +131,7 @@ describe("KubeApi", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("uses url from fallbackApiBases if apiBase lacks the resource", async () => {
|
it("uses url from fallbackApiBases if apiBase lacks the resource", async () => {
|
||||||
(fetch as any).mockResponse(async (request: any) => {
|
(fetch as any).mockResponse((request: any) => {
|
||||||
if (request.url === "http://127.0.0.1:9999/api-kube/apis/networking.k8s.io/v1") {
|
if (request.url === "http://127.0.0.1:9999/api-kube/apis/networking.k8s.io/v1") {
|
||||||
return {
|
return {
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
@ -178,94 +172,6 @@ describe("KubeApi", () => {
|
|||||||
expect(kubeApi.apiGroup).toEqual("extensions");
|
expect(kubeApi.apiGroup).toEqual("extensions");
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("checkPreferredVersion", () => {
|
|
||||||
it("registers with apiManager if checkPreferredVersion changes apiVersionPreferred", async () => {
|
|
||||||
expect.hasAssertions();
|
|
||||||
|
|
||||||
const api = new TestKubeApi({
|
|
||||||
objectConstructor: Ingress,
|
|
||||||
checkPreferredVersion: true,
|
|
||||||
fallbackApiBases: ["/apis/extensions/v1beta1/ingresses"],
|
|
||||||
request: {
|
|
||||||
get: jest.fn()
|
|
||||||
.mockImplementationOnce((path: string) => {
|
|
||||||
expect(path).toBe("/apis/networking.k8s.io/v1");
|
|
||||||
|
|
||||||
throw new Error("no");
|
|
||||||
})
|
|
||||||
.mockImplementationOnce((path: string) => {
|
|
||||||
expect(path).toBe("/apis/extensions/v1beta1");
|
|
||||||
|
|
||||||
return {
|
|
||||||
resources: [
|
|
||||||
{
|
|
||||||
name: "ingresses",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.mockImplementationOnce((path: string) => {
|
|
||||||
expect(path).toBe("/apis/extensions");
|
|
||||||
|
|
||||||
return {
|
|
||||||
preferredVersion: {
|
|
||||||
version: "v1beta1",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
} as any,
|
|
||||||
});
|
|
||||||
|
|
||||||
await api.checkPreferredVersion();
|
|
||||||
|
|
||||||
expect(api.apiVersionPreferred).toBe("v1beta1");
|
|
||||||
expect(mockApiManager.registerApi).toBeCalledWith("/apis/extensions/v1beta1/ingresses", expect.anything());
|
|
||||||
});
|
|
||||||
|
|
||||||
it("registers with apiManager if checkPreferredVersion changes apiVersionPreferred with non-grouped apis", async () => {
|
|
||||||
expect.hasAssertions();
|
|
||||||
|
|
||||||
const api = new TestKubeApi({
|
|
||||||
objectConstructor: Pod,
|
|
||||||
checkPreferredVersion: true,
|
|
||||||
fallbackApiBases: ["/api/v1beta1/pods"],
|
|
||||||
request: {
|
|
||||||
get: jest.fn()
|
|
||||||
.mockImplementationOnce((path: string) => {
|
|
||||||
expect(path).toBe("/api/v1");
|
|
||||||
|
|
||||||
throw new Error("no");
|
|
||||||
})
|
|
||||||
.mockImplementationOnce((path: string) => {
|
|
||||||
expect(path).toBe("/api/v1beta1");
|
|
||||||
|
|
||||||
return {
|
|
||||||
resources: [
|
|
||||||
{
|
|
||||||
name: "pods",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.mockImplementationOnce((path: string) => {
|
|
||||||
expect(path).toBe("/api");
|
|
||||||
|
|
||||||
return {
|
|
||||||
preferredVersion: {
|
|
||||||
version: "v1beta1",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
} as any,
|
|
||||||
});
|
|
||||||
|
|
||||||
await api.checkPreferredVersion();
|
|
||||||
|
|
||||||
expect(api.apiVersionPreferred).toBe("v1beta1");
|
|
||||||
expect(mockApiManager.registerApi).toBeCalledWith("/api/v1beta1/pods", expect.anything());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("patch", () => {
|
describe("patch", () => {
|
||||||
let api: TestKubeApi;
|
let api: TestKubeApi;
|
||||||
|
|
||||||
@ -279,7 +185,7 @@ describe("KubeApi", () => {
|
|||||||
it("sends strategic patch by default", async () => {
|
it("sends strategic patch by default", async () => {
|
||||||
expect.hasAssertions();
|
expect.hasAssertions();
|
||||||
|
|
||||||
(fetch as any).mockResponse(async (request: Request) => {
|
(fetch as any).mockResponse((request: Request) => {
|
||||||
expect(request.method).toEqual("PATCH");
|
expect(request.method).toEqual("PATCH");
|
||||||
expect(request.headers.get("content-type")).toMatch("strategic-merge-patch");
|
expect(request.headers.get("content-type")).toMatch("strategic-merge-patch");
|
||||||
expect(request.body.toString()).toEqual(JSON.stringify({ spec: { replicas: 2 }}));
|
expect(request.body.toString()).toEqual(JSON.stringify({ spec: { replicas: 2 }}));
|
||||||
@ -295,7 +201,7 @@ describe("KubeApi", () => {
|
|||||||
it("allows to use merge patch", async () => {
|
it("allows to use merge patch", async () => {
|
||||||
expect.hasAssertions();
|
expect.hasAssertions();
|
||||||
|
|
||||||
(fetch as any).mockResponse(async (request: Request) => {
|
(fetch as any).mockResponse((request: Request) => {
|
||||||
expect(request.method).toEqual("PATCH");
|
expect(request.method).toEqual("PATCH");
|
||||||
expect(request.headers.get("content-type")).toMatch("merge-patch");
|
expect(request.headers.get("content-type")).toMatch("merge-patch");
|
||||||
expect(request.body.toString()).toEqual(JSON.stringify({ spec: { replicas: 2 }}));
|
expect(request.body.toString()).toEqual(JSON.stringify({ spec: { replicas: 2 }}));
|
||||||
@ -311,7 +217,7 @@ describe("KubeApi", () => {
|
|||||||
it("allows to use json patch", async () => {
|
it("allows to use json patch", async () => {
|
||||||
expect.hasAssertions();
|
expect.hasAssertions();
|
||||||
|
|
||||||
(fetch as any).mockResponse(async (request: Request) => {
|
(fetch as any).mockResponse((request: Request) => {
|
||||||
expect(request.method).toEqual("PATCH");
|
expect(request.method).toEqual("PATCH");
|
||||||
expect(request.headers.get("content-type")).toMatch("json-patch");
|
expect(request.headers.get("content-type")).toMatch("json-patch");
|
||||||
expect(request.body.toString()).toEqual(JSON.stringify([{ op: "replace", path: "/spec/replicas", value: 2 }]));
|
expect(request.body.toString()).toEqual(JSON.stringify([{ op: "replace", path: "/spec/replicas", value: 2 }]));
|
||||||
@ -327,7 +233,7 @@ describe("KubeApi", () => {
|
|||||||
it("allows deep partial patch", async () => {
|
it("allows deep partial patch", async () => {
|
||||||
expect.hasAssertions();
|
expect.hasAssertions();
|
||||||
|
|
||||||
(fetch as any).mockResponse(async (request: Request) => {
|
(fetch as any).mockResponse((request: Request) => {
|
||||||
expect(request.method).toEqual("PATCH");
|
expect(request.method).toEqual("PATCH");
|
||||||
expect(request.headers.get("content-type")).toMatch("merge-patch");
|
expect(request.headers.get("content-type")).toMatch("merge-patch");
|
||||||
expect(request.body.toString()).toEqual(JSON.stringify({ metadata: { annotations: { provisioned: "true" }}}));
|
expect(request.body.toString()).toEqual(JSON.stringify({ metadata: { annotations: { provisioned: "true" }}}));
|
||||||
@ -355,7 +261,7 @@ describe("KubeApi", () => {
|
|||||||
|
|
||||||
it("sends correct request with empty namespace", async () => {
|
it("sends correct request with empty namespace", async () => {
|
||||||
expect.hasAssertions();
|
expect.hasAssertions();
|
||||||
(fetch as any).mockResponse(async (request: Request) => {
|
(fetch as any).mockResponse((request: Request) => {
|
||||||
expect(request.method).toEqual("DELETE");
|
expect(request.method).toEqual("DELETE");
|
||||||
expect(request.url).toEqual("http://127.0.0.1:9999/api-kube/api/v1/pods/foo?propagationPolicy=Background");
|
expect(request.url).toEqual("http://127.0.0.1:9999/api-kube/api/v1/pods/foo?propagationPolicy=Background");
|
||||||
|
|
||||||
@ -367,7 +273,7 @@ describe("KubeApi", () => {
|
|||||||
|
|
||||||
it("sends correct request without namespace", async () => {
|
it("sends correct request without namespace", async () => {
|
||||||
expect.hasAssertions();
|
expect.hasAssertions();
|
||||||
(fetch as any).mockResponse(async (request: Request) => {
|
(fetch as any).mockResponse((request: Request) => {
|
||||||
expect(request.method).toEqual("DELETE");
|
expect(request.method).toEqual("DELETE");
|
||||||
expect(request.url).toEqual("http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods/foo?propagationPolicy=Background");
|
expect(request.url).toEqual("http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods/foo?propagationPolicy=Background");
|
||||||
|
|
||||||
@ -379,7 +285,7 @@ describe("KubeApi", () => {
|
|||||||
|
|
||||||
it("sends correct request with namespace", async () => {
|
it("sends correct request with namespace", async () => {
|
||||||
expect.hasAssertions();
|
expect.hasAssertions();
|
||||||
(fetch as any).mockResponse(async (request: Request) => {
|
(fetch as any).mockResponse((request: Request) => {
|
||||||
expect(request.method).toEqual("DELETE");
|
expect(request.method).toEqual("DELETE");
|
||||||
expect(request.url).toEqual("http://127.0.0.1:9999/api-kube/api/v1/namespaces/kube-system/pods/foo?propagationPolicy=Background");
|
expect(request.url).toEqual("http://127.0.0.1:9999/api-kube/api/v1/namespaces/kube-system/pods/foo?propagationPolicy=Background");
|
||||||
|
|
||||||
@ -391,7 +297,7 @@ describe("KubeApi", () => {
|
|||||||
|
|
||||||
it("allows to change propagationPolicy", async () => {
|
it("allows to change propagationPolicy", async () => {
|
||||||
expect.hasAssertions();
|
expect.hasAssertions();
|
||||||
(fetch as any).mockResponse(async (request: Request) => {
|
(fetch as any).mockResponse((request: Request) => {
|
||||||
expect(request.method).toEqual("DELETE");
|
expect(request.method).toEqual("DELETE");
|
||||||
expect(request.url).toMatch("propagationPolicy=Orphan");
|
expect(request.url).toMatch("propagationPolicy=Orphan");
|
||||||
|
|
||||||
@ -422,7 +328,7 @@ describe("KubeApi", () => {
|
|||||||
it("sends a valid watch request", () => {
|
it("sends a valid watch request", () => {
|
||||||
const spy = jest.spyOn(request, "getResponse");
|
const spy = jest.spyOn(request, "getResponse");
|
||||||
|
|
||||||
(fetch as any).mockResponse(async () => {
|
(fetch as any).mockResponse(() => {
|
||||||
return {
|
return {
|
||||||
body: stream,
|
body: stream,
|
||||||
};
|
};
|
||||||
@ -432,10 +338,10 @@ describe("KubeApi", () => {
|
|||||||
expect(spy).toHaveBeenCalledWith("/api/v1/namespaces/kube-system/pods?watch=1&resourceVersion=", expect.anything(), expect.anything());
|
expect(spy).toHaveBeenCalledWith("/api/v1/namespaces/kube-system/pods?watch=1&resourceVersion=", expect.anything(), expect.anything());
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sends timeout as a query parameter", async () => {
|
it("sends timeout as a query parameter", () => {
|
||||||
const spy = jest.spyOn(request, "getResponse");
|
const spy = jest.spyOn(request, "getResponse");
|
||||||
|
|
||||||
(fetch as any).mockResponse(async () => {
|
(fetch as any).mockResponse(() => {
|
||||||
return {
|
return {
|
||||||
body: stream,
|
body: stream,
|
||||||
};
|
};
|
||||||
@ -448,7 +354,7 @@ describe("KubeApi", () => {
|
|||||||
it("aborts watch using abortController", async (done) => {
|
it("aborts watch using abortController", async (done) => {
|
||||||
const spy = jest.spyOn(request, "getResponse");
|
const spy = jest.spyOn(request, "getResponse");
|
||||||
|
|
||||||
(fetch as any).mockResponse(async (request: Request) => {
|
(fetch as any).mockResponse((request: Request) => {
|
||||||
(request as any).signal.addEventListener("abort", () => {
|
(request as any).signal.addEventListener("abort", () => {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -489,7 +395,7 @@ describe("KubeApi", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// we need to mock using jest as jest-fetch-mock doesn't support mocking the body completely
|
// we need to mock using jest as jest-fetch-mock doesn't support mocking the body completely
|
||||||
jest.spyOn(global, "fetch").mockImplementation(async () => {
|
jest.spyOn(global, "fetch").mockImplementation(() => {
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
body: stream,
|
body: stream,
|
||||||
@ -511,7 +417,7 @@ describe("KubeApi", () => {
|
|||||||
it("if request not closed after timeout", (done) => {
|
it("if request not closed after timeout", (done) => {
|
||||||
const spy = jest.spyOn(request, "getResponse");
|
const spy = jest.spyOn(request, "getResponse");
|
||||||
|
|
||||||
(fetch as any).mockResponse(async () => {
|
(fetch as any).mockResponse(() => {
|
||||||
return {
|
return {
|
||||||
body: stream,
|
body: stream,
|
||||||
};
|
};
|
||||||
@ -547,7 +453,7 @@ describe("KubeApi", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// we need to mock using jest as jest-fetch-mock doesn't support mocking the body completely
|
// we need to mock using jest as jest-fetch-mock doesn't support mocking the body completely
|
||||||
jest.spyOn(global, "fetch").mockImplementation(async () => {
|
jest.spyOn(global, "fetch").mockImplementation(() => {
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
body: stream,
|
body: stream,
|
||||||
@ -588,7 +494,7 @@ describe("KubeApi", () => {
|
|||||||
it("should add kind and apiVersion", async () => {
|
it("should add kind and apiVersion", async () => {
|
||||||
expect.hasAssertions();
|
expect.hasAssertions();
|
||||||
|
|
||||||
(fetch as any).mockResponse(async (request: Request) => {
|
(fetch as any).mockResponse((request: Request) => {
|
||||||
expect(request.method).toEqual("POST");
|
expect(request.method).toEqual("POST");
|
||||||
expect(JSON.parse(request.body.toString())).toEqual({
|
expect(JSON.parse(request.body.toString())).toEqual({
|
||||||
kind: "Pod",
|
kind: "Pod",
|
||||||
@ -642,7 +548,7 @@ describe("KubeApi", () => {
|
|||||||
it("doesn't override metadata.labels", async () => {
|
it("doesn't override metadata.labels", async () => {
|
||||||
expect.hasAssertions();
|
expect.hasAssertions();
|
||||||
|
|
||||||
(fetch as any).mockResponse(async (request: Request) => {
|
(fetch as any).mockResponse((request: Request) => {
|
||||||
expect(request.method).toEqual("POST");
|
expect(request.method).toEqual("POST");
|
||||||
expect(JSON.parse(request.body.toString())).toEqual({
|
expect(JSON.parse(request.body.toString())).toEqual({
|
||||||
kind: "Pod",
|
kind: "Pod",
|
||||||
@ -685,7 +591,7 @@ describe("KubeApi", () => {
|
|||||||
it("doesn't override metadata.labels", async () => {
|
it("doesn't override metadata.labels", async () => {
|
||||||
expect.hasAssertions();
|
expect.hasAssertions();
|
||||||
|
|
||||||
(fetch as any).mockResponse(async (request: Request) => {
|
(fetch as any).mockResponse((request: Request) => {
|
||||||
expect(request.method).toEqual("PUT");
|
expect(request.method).toEqual("PUT");
|
||||||
expect(JSON.parse(request.body.toString())).toEqual({
|
expect(JSON.parse(request.body.toString())).toEqual({
|
||||||
metadata: {
|
metadata: {
|
||||||
|
|||||||
256
src/common/k8s-api/api-manager.injectable.ts
Normal file
256
src/common/k8s-api/api-manager.injectable.ts
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
/**
|
||||||
|
* 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 { ClusterStore } from "../../renderer/components/+cluster/store";
|
||||||
|
import { HorizontalPodAutoscalerStore } from "../../renderer/components/+autoscalers/store";
|
||||||
|
import { LimitRangeStore } from "../../renderer/components/+limit-ranges/store";
|
||||||
|
import { ConfigMapStore } from "../../renderer/components/+config-maps/store";
|
||||||
|
import { PodDisruptionBudgetStore } from "../../renderer/components/+pod-disruption-budgets/store";
|
||||||
|
import { ResourceQuotaStore } from "../../renderer/components/+resource-quotas/store";
|
||||||
|
import { SecretStore } from "../../renderer/components/+secrets/store";
|
||||||
|
import { CustomResourceDefinitionStore } from "../../renderer/components/+custom-resource/store";
|
||||||
|
import { EventStore } from "../../renderer/components/+events/store";
|
||||||
|
import { NamespaceStore } from "../../renderer/components/+namespaces/store";
|
||||||
|
import { EndpointStore } from "../../renderer/components/+endpoints/store";
|
||||||
|
import { IngressStore } from "../../renderer/components/+ingresses/store";
|
||||||
|
import { NetworkPolicyStore } from "../../renderer/components/+network-policies/store";
|
||||||
|
import { ServiceStore } from "../../renderer/components/+services/store";
|
||||||
|
import { NodeStore } from "../../renderer/components/+nodes/store";
|
||||||
|
import { PodSecurityPolicyStore } from "../../renderer/components/+pod-security-policies/store";
|
||||||
|
import { StorageClassStore } from "../../renderer/components/+storage-classes/store";
|
||||||
|
import { PersistentVolumeClaimStore } from "../../renderer/components/+persistent-volume-claims/store";
|
||||||
|
import { PersistentVolumeStore } from "../../renderer/components/+persistent-volumes/store";
|
||||||
|
import { ClusterRoleBindingStore } from "../../renderer/components/+cluster-role-bindings/store";
|
||||||
|
import { ClusterRoleStore } from "../../renderer/components/+cluster-roles/store";
|
||||||
|
import { RoleBindingStore } from "../../renderer/components/+role-bindings/store";
|
||||||
|
import { RoleStore } from "../../renderer/components/+roles/store";
|
||||||
|
import { ServiceAccountStore } from "../../renderer/components/+service-accounts/store";
|
||||||
|
import { CronJobStore } from "../../renderer/components/+cronjobs/store";
|
||||||
|
import { DaemonSetStore } from "../../renderer/components/+daemonsets/store";
|
||||||
|
import { DeploymentStore } from "../../renderer/components/+deployments/store";
|
||||||
|
import { JobStore } from "../../renderer/components/+jobs/store";
|
||||||
|
import { PodStore } from "../../renderer/components/+pods/store";
|
||||||
|
import { ReplicaSetStore } from "../../renderer/components/+replica-sets/store";
|
||||||
|
import { StatefulSetStore } from "../../renderer/components/+stateful-sets/store";
|
||||||
|
import { ApiManager } from "./api-manager";
|
||||||
|
import { ClusterApi, ClusterRoleApi, ClusterRoleBindingApi, ConfigMapApi, CronJobApi, CustomResourceDefinition, CustomResourceDefinitionApi, DaemonSetApi, DeploymentApi, EndpointApi, EventApi, HorizontalPodAutoscalerApi, IngressApi, JobApi, LimitRangeApi, NamespaceApi, NetworkPolicyApi, NodeApi, PersistentVolumeApi, PersistentVolumeClaimApi, PodApi, PodDisruptionBudgetApi, PodMetricsApi, PodSecurityPolicyApi, ReplicaSetApi, ResourceQuotaApi, RoleApi, RoleBindingApi, SecretApi, SelfSubjectRulesReviewApi, ServiceAccountApi, ServiceApi, StatefulSetApi, StorageClassApi } from "./endpoints";
|
||||||
|
import { KubeApi } from "./kube-api";
|
||||||
|
import { KubeObject } from "./kube-object";
|
||||||
|
import { KubeObjectStore } from "./kube-object.store";
|
||||||
|
|
||||||
|
function createAndInit(): ApiManager {
|
||||||
|
const apiManager = new ApiManager();
|
||||||
|
|
||||||
|
const clusterApi = new ClusterApi();
|
||||||
|
|
||||||
|
apiManager.registerApi(clusterApi);
|
||||||
|
apiManager.registerStore(new ClusterStore(clusterApi));
|
||||||
|
|
||||||
|
const clusterRoleApi = new ClusterRoleApi();
|
||||||
|
|
||||||
|
apiManager.registerApi(clusterRoleApi);
|
||||||
|
apiManager.registerStore(new ClusterRoleStore(clusterRoleApi));
|
||||||
|
|
||||||
|
const clusterRoleBindingApi = new ClusterRoleBindingApi();
|
||||||
|
|
||||||
|
apiManager.registerApi(clusterRoleBindingApi);
|
||||||
|
apiManager.registerStore(new ClusterRoleBindingStore(clusterRoleBindingApi));
|
||||||
|
|
||||||
|
const configMapApi = new ConfigMapApi();
|
||||||
|
|
||||||
|
apiManager.registerApi(configMapApi);
|
||||||
|
apiManager.registerStore(new ConfigMapStore(configMapApi));
|
||||||
|
|
||||||
|
const podApi = new PodApi();
|
||||||
|
const podStore = new PodStore(podApi);
|
||||||
|
|
||||||
|
apiManager.registerApi(podApi);
|
||||||
|
apiManager.registerStore(podStore);
|
||||||
|
|
||||||
|
const jobApi = new JobApi();
|
||||||
|
const jobStore = new JobStore(jobApi, {
|
||||||
|
podStore,
|
||||||
|
});
|
||||||
|
|
||||||
|
apiManager.registerApi(jobApi);
|
||||||
|
apiManager.registerStore(jobStore);
|
||||||
|
|
||||||
|
const cronJobApi = new CronJobApi();
|
||||||
|
|
||||||
|
apiManager.registerApi(cronJobApi);
|
||||||
|
apiManager.registerStore(new CronJobStore(cronJobApi, {
|
||||||
|
jobStore,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const customResourceDefinitionApi = new CustomResourceDefinitionApi();
|
||||||
|
|
||||||
|
apiManager.registerApi(customResourceDefinitionApi);
|
||||||
|
apiManager.registerStore(new CustomResourceDefinitionStore(customResourceDefinitionApi, {
|
||||||
|
initCustomResourceStore(crd: CustomResourceDefinition) {
|
||||||
|
const objectConstructor = class extends KubeObject {
|
||||||
|
static readonly kind = crd.getResourceKind();
|
||||||
|
static readonly namespaced = crd.isNamespaced();
|
||||||
|
static readonly apiBase = crd.getResourceApiBase();
|
||||||
|
};
|
||||||
|
|
||||||
|
const api = apiManager.getApi(objectConstructor.apiBase)
|
||||||
|
?? new KubeApi({ objectConstructor });
|
||||||
|
|
||||||
|
if (!apiManager.hasApi(api)) {
|
||||||
|
apiManager.registerApi(api);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!apiManager.getStore(api)) {
|
||||||
|
apiManager.registerStore(new class extends KubeObjectStore<KubeObject> {
|
||||||
|
api = api;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
const daemonSetApi = new DaemonSetApi();
|
||||||
|
|
||||||
|
apiManager.registerApi(daemonSetApi);
|
||||||
|
apiManager.registerStore(new DaemonSetStore(daemonSetApi, {
|
||||||
|
podStore,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const deploymentApi = new DeploymentApi();
|
||||||
|
|
||||||
|
apiManager.registerApi(deploymentApi);
|
||||||
|
apiManager.registerStore(new DeploymentStore(deploymentApi, {
|
||||||
|
podStore,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const endpointApi = new EndpointApi();
|
||||||
|
|
||||||
|
apiManager.registerApi(endpointApi);
|
||||||
|
apiManager.registerStore(new EndpointStore(endpointApi));
|
||||||
|
|
||||||
|
const eventApi = new EventApi();
|
||||||
|
|
||||||
|
apiManager.registerApi(eventApi);
|
||||||
|
apiManager.registerStore(new EventStore(eventApi, {
|
||||||
|
podStore,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const horizontalPodAutoscalerApi = new HorizontalPodAutoscalerApi();
|
||||||
|
|
||||||
|
apiManager.registerApi(horizontalPodAutoscalerApi);
|
||||||
|
apiManager.registerStore(new HorizontalPodAutoscalerStore(horizontalPodAutoscalerApi));
|
||||||
|
|
||||||
|
const ingressApi = new IngressApi();
|
||||||
|
|
||||||
|
apiManager.registerApi(ingressApi);
|
||||||
|
apiManager.registerStore(new IngressStore(ingressApi));
|
||||||
|
|
||||||
|
const limitRangeApi = new LimitRangeApi();
|
||||||
|
|
||||||
|
apiManager.registerApi(limitRangeApi);
|
||||||
|
apiManager.registerStore(new LimitRangeStore(limitRangeApi));
|
||||||
|
|
||||||
|
const namespaceApi = new NamespaceApi();
|
||||||
|
|
||||||
|
apiManager.registerApi(namespaceApi);
|
||||||
|
apiManager.registerStore(new NamespaceStore(namespaceApi));
|
||||||
|
|
||||||
|
const networkPolicyApi = new NetworkPolicyApi();
|
||||||
|
|
||||||
|
apiManager.registerApi(networkPolicyApi);
|
||||||
|
apiManager.registerStore(new NetworkPolicyStore(networkPolicyApi));
|
||||||
|
|
||||||
|
const nodeApi = new NodeApi();
|
||||||
|
|
||||||
|
apiManager.registerApi(nodeApi);
|
||||||
|
apiManager.registerStore(new NodeStore(nodeApi));
|
||||||
|
|
||||||
|
const persistentVolumeApi = new PersistentVolumeApi();
|
||||||
|
const persistentVolumeStore = new PersistentVolumeStore(persistentVolumeApi);
|
||||||
|
|
||||||
|
apiManager.registerApi(persistentVolumeApi);
|
||||||
|
apiManager.registerStore(persistentVolumeStore);
|
||||||
|
|
||||||
|
const persistentVolumeClaimApi = new PersistentVolumeClaimApi();
|
||||||
|
|
||||||
|
apiManager.registerApi(persistentVolumeClaimApi);
|
||||||
|
apiManager.registerStore(new PersistentVolumeClaimStore(persistentVolumeClaimApi));
|
||||||
|
|
||||||
|
const podDisruptionBudgetApi = new PodDisruptionBudgetApi();
|
||||||
|
|
||||||
|
apiManager.registerApi(podDisruptionBudgetApi);
|
||||||
|
apiManager.registerStore(new PodDisruptionBudgetStore(podDisruptionBudgetApi));
|
||||||
|
|
||||||
|
const podSecurityPolicyApi = new PodSecurityPolicyApi();
|
||||||
|
|
||||||
|
apiManager.registerApi(podSecurityPolicyApi);
|
||||||
|
apiManager.registerStore(new PodSecurityPolicyStore(podSecurityPolicyApi));
|
||||||
|
|
||||||
|
const replicaSetApi = new ReplicaSetApi();
|
||||||
|
|
||||||
|
apiManager.registerApi(replicaSetApi);
|
||||||
|
apiManager.registerStore(new ReplicaSetStore(replicaSetApi, {
|
||||||
|
podStore,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const resourceQuotaApi = new ResourceQuotaApi();
|
||||||
|
|
||||||
|
apiManager.registerApi(resourceQuotaApi);
|
||||||
|
apiManager.registerStore(new ResourceQuotaStore(resourceQuotaApi));
|
||||||
|
|
||||||
|
const roleApi = new RoleApi();
|
||||||
|
|
||||||
|
apiManager.registerApi(roleApi);
|
||||||
|
apiManager.registerStore(new RoleStore(roleApi));
|
||||||
|
|
||||||
|
const roleBindingApi = new RoleBindingApi();
|
||||||
|
|
||||||
|
apiManager.registerApi(roleBindingApi);
|
||||||
|
apiManager.registerStore(new RoleBindingStore(roleBindingApi));
|
||||||
|
|
||||||
|
const secretApi = new SecretApi();
|
||||||
|
|
||||||
|
apiManager.registerApi(secretApi);
|
||||||
|
apiManager.registerStore(new SecretStore(secretApi));
|
||||||
|
|
||||||
|
const serviceAccountApi = new ServiceAccountApi();
|
||||||
|
|
||||||
|
apiManager.registerApi(serviceAccountApi);
|
||||||
|
apiManager.registerStore(new ServiceAccountStore(serviceAccountApi));
|
||||||
|
|
||||||
|
const serviceApi = new ServiceApi();
|
||||||
|
|
||||||
|
apiManager.registerApi(serviceApi);
|
||||||
|
apiManager.registerStore(new ServiceStore(serviceApi));
|
||||||
|
|
||||||
|
const statefulSetApi = new StatefulSetApi();
|
||||||
|
|
||||||
|
apiManager.registerApi(statefulSetApi);
|
||||||
|
apiManager.registerStore(new StatefulSetStore(statefulSetApi, {
|
||||||
|
podStore,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const storageClassApi = new StorageClassApi();
|
||||||
|
|
||||||
|
apiManager.registerApi(storageClassApi);
|
||||||
|
apiManager.registerStore(new StorageClassStore(storageClassApi, {
|
||||||
|
persistentVolumeStore,
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
// There is no store for these apis, so just register them
|
||||||
|
apiManager.registerApi(new PodMetricsApi());
|
||||||
|
apiManager.registerApi(new SelfSubjectRulesReviewApi());
|
||||||
|
|
||||||
|
return apiManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiManagerInjectable = getInjectable({
|
||||||
|
instantiate: createAndInit,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default apiManagerInjectable;
|
||||||
@ -5,44 +5,109 @@
|
|||||||
|
|
||||||
import type { KubeObjectStore } from "./kube-object.store";
|
import type { KubeObjectStore } from "./kube-object.store";
|
||||||
|
|
||||||
import { action, observable, makeObservable } from "mobx";
|
import { action, observable, makeObservable, computed } from "mobx";
|
||||||
import { autoBind, iter } from "../utils";
|
import { autoBind, iter } from "../utils";
|
||||||
import type { KubeApi } from "./kube-api";
|
import type { KubeApi } from "./kube-api";
|
||||||
import type { KubeObject } from "./kube-object";
|
import type { KubeObject } from "./kube-object";
|
||||||
import { IKubeObjectRef, parseKubeApi, createKubeApiURL } from "./kube-api-parse";
|
import { IKubeObjectRef, parseKubeApi, createKubeApiURL } from "./kube-api-parse";
|
||||||
|
|
||||||
export class ApiManager {
|
export class ApiManager {
|
||||||
private apis = observable.map<string, KubeApi<KubeObject>>();
|
private apiSet = observable.set<KubeApi<KubeObject>>();
|
||||||
private stores = observable.map<string, KubeObjectStore<KubeObject>>();
|
private stores = observable.map<KubeApi<KubeObject>, KubeObjectStore<KubeObject>>();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
autoBind(this);
|
autoBind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
getApi(pathOrCallback: string | ((api: KubeApi<KubeObject>) => boolean)) {
|
/**
|
||||||
if (typeof pathOrCallback === "string") {
|
* The private `apiBase` mapping of api instances. This is computed so that
|
||||||
return this.apis.get(pathOrCallback) || this.apis.get(parseKubeApi(pathOrCallback).apiBase);
|
* it can react to changes in the instances' apiBase fields.
|
||||||
|
*/
|
||||||
|
@computed private get apis() {
|
||||||
|
const res = new Map<string, KubeApi<KubeObject>>();
|
||||||
|
|
||||||
|
for (const api of this.apiSet) {
|
||||||
|
if (typeof api.apiBase !== "string" || !api.apiBase) {
|
||||||
|
throw new TypeError("KubeApi.apiBase must be a non-empty string");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.has(api.apiBase)) {
|
||||||
|
throw new Error(`Multiple api instances for ${api.apiBase}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.set(api.apiBase, api);
|
||||||
}
|
}
|
||||||
|
|
||||||
return iter.find(this.apis.values(), pathOrCallback ?? (() => true));
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param api The instance to check if it has been registered
|
||||||
|
* @returns Returns `true` if the api instance has been registered
|
||||||
|
*/
|
||||||
|
hasApi(api: KubeApi<KubeObject>): boolean {
|
||||||
|
return this.apiSet.has(api);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a registered api, if a callback is provided then the registered
|
||||||
|
* instances are iterated until it returns `true`
|
||||||
|
* @param pathOrCallbacks Either the `apiBase` of an instance, a resource path for the kind of the api, or a callback function. Will search for each until one is found.
|
||||||
|
* @returns The kube api instance that was registered
|
||||||
|
*/
|
||||||
|
getApi(...pathOrCallbacks: (string | ((api: KubeApi<KubeObject>) => boolean))[]): KubeApi<KubeObject> | undefined {
|
||||||
|
for (const pathOrCallback of pathOrCallbacks) {
|
||||||
|
if (typeof pathOrCallback === "string") {
|
||||||
|
return this.apis.get(pathOrCallback) || this.apis.get(parseKubeApi(pathOrCallback).apiBase);
|
||||||
|
}
|
||||||
|
|
||||||
|
return iter.find(this.apis.values(), pathOrCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the registered api instance by the kube object kind and version
|
||||||
|
* @param kind The kind of resource that the api is for
|
||||||
|
* @param apiVersion The version of the resource that the api is for
|
||||||
|
* @returns The kube api instance that was registered
|
||||||
|
*/
|
||||||
getApiByKind(kind: string, apiVersion: string) {
|
getApiByKind(kind: string, apiVersion: string) {
|
||||||
return iter.find(this.apis.values(), api => api.kind === kind && api.apiVersionWithGroup === apiVersion);
|
return iter.find(this.apis.values(), api => api.kind === kind && api.apiVersionWithGroup === apiVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
registerApi(apiBase: string, api: KubeApi<KubeObject>) {
|
/**
|
||||||
if (!api.apiBase) return;
|
* Registeres `api` so that it can be retreived in the future.
|
||||||
|
*
|
||||||
|
* Notes:
|
||||||
|
* - Changes to the instance's `apiBase` field are reacted to for the `getApi()` method
|
||||||
|
* @param api The instance to register
|
||||||
|
* @throws if `api.apiBase` is not a non-empty string
|
||||||
|
* @throws if there is already an instance with the same `apiBase` registered
|
||||||
|
*/
|
||||||
|
registerApi(api: KubeApi<KubeObject>): void;
|
||||||
|
/**
|
||||||
|
* @deprecated Just provide the `api` instance
|
||||||
|
*/
|
||||||
|
registerApi(apiOrBase: string, api: KubeApi<KubeObject>): void
|
||||||
|
@action
|
||||||
|
registerApi(apiOrBase: string | KubeApi<KubeObject>, api?: KubeApi<KubeObject>): void {
|
||||||
|
api = typeof apiOrBase === "string"
|
||||||
|
? api
|
||||||
|
: apiOrBase;
|
||||||
|
|
||||||
if (!this.apis.has(apiBase)) {
|
if (!this.apiSet.has(api)) {
|
||||||
this.stores.forEach((store) => {
|
if (typeof api.apiBase !== "string" || !api.apiBase) {
|
||||||
if (store.api === api) {
|
throw new TypeError("api.apiBase but be defined");
|
||||||
this.stores.set(apiBase, store);
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.apis.set(apiBase, api);
|
if (this.apis.has(api.apiBase)) {
|
||||||
|
throw new Error(`Cannot register second api for ${api.apiBase}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.apiSet.add(api);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,28 +123,70 @@ export class ApiManager {
|
|||||||
return api;
|
return api;
|
||||||
}
|
}
|
||||||
|
|
||||||
unregisterApi(api: string | KubeApi<KubeObject>) {
|
/**
|
||||||
if (typeof api === "string") this.apis.delete(api);
|
* Removes `api` from the set of registered apis
|
||||||
else {
|
* @param api The instance to de-register
|
||||||
const apis = Array.from(this.apis.entries());
|
* @returns `true` if the instance was previously registered
|
||||||
const entry = apis.find(entry => entry[1] === api);
|
*/
|
||||||
|
@action
|
||||||
|
unregisterApi(api: KubeApi<KubeObject>) {
|
||||||
|
return this.apiSet.delete(api);
|
||||||
|
}
|
||||||
|
|
||||||
if (entry) this.unregisterApi(entry[0]);
|
/**
|
||||||
|
* Registeres a `KubeObjectStore` instance that can be retrieved by the `apiBase` its api is for
|
||||||
|
* @param store The store to register
|
||||||
|
*/
|
||||||
|
registerStore(store: KubeObjectStore<KubeObject>): void;
|
||||||
|
/**
|
||||||
|
* @deprecated stores should only be registered for the single api that the store is for.
|
||||||
|
*/
|
||||||
|
registerStore(store: KubeObjectStore<KubeObject>, apis: KubeApi<KubeObject>[]): void;
|
||||||
|
@action
|
||||||
|
registerStore(store: KubeObjectStore<KubeObject>, apis?: KubeApi<KubeObject>[]) {
|
||||||
|
apis ??= [store.api];
|
||||||
|
|
||||||
|
for (const api of apis) {
|
||||||
|
if (!this.apiSet.has(api)) {
|
||||||
|
throw new Error(`Cannot register store under ${api.apiBase} api, as that api is not registered`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.stores.has(api)) {
|
||||||
|
throw new Error(`Each api instance can only have one store associated with it. Attempt to register a duplicate store for the ${api.apiBase} api`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.stores.set(api, store);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
/**
|
||||||
registerStore(store: KubeObjectStore<KubeObject>, apis: KubeApi<KubeObject>[] = [store.api]) {
|
*
|
||||||
apis.filter(Boolean).forEach(api => {
|
* @param apiOrBases The `apiBase`, resource descriptor, or `KubeApi` instance that the store is for. In order of searching
|
||||||
if (api.apiBase) this.stores.set(api.apiBase, store);
|
* @returns The registered store whose api has also been registered
|
||||||
});
|
*/
|
||||||
|
getStore(...apiOrBases: (string | KubeApi<KubeObject>)[]): KubeObjectStore<KubeObject> | undefined;
|
||||||
|
/**
|
||||||
|
* @deprecated Should use a cast instead as this is an unchecked type param.
|
||||||
|
*/
|
||||||
|
getStore<S extends KubeObjectStore<KubeObject>>(...apiOrBases: (string | KubeApi<KubeObject>)[]): S | undefined {
|
||||||
|
for (const apiOrBase of apiOrBases) {
|
||||||
|
const store = this.stores.get(this.resolveApi(apiOrBase)) as S;
|
||||||
|
|
||||||
|
if (store) {
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
getStore<S extends KubeObjectStore<KubeObject>>(api: string | KubeApi<KubeObject>): S | undefined {
|
/**
|
||||||
return this.stores.get(this.resolveApi(api)?.apiBase) as S;
|
* Get a URL pathname for a specific kube resource instance
|
||||||
}
|
* @param ref The kube object reference
|
||||||
|
* @param parentObject If provided then the namespace of this will be used if the `ref` does not provided it
|
||||||
lookupApiLink(ref: IKubeObjectRef, parentObject?: KubeObject): string {
|
* @returns A kube resource string
|
||||||
|
*/
|
||||||
|
lookupApiLink = (ref: IKubeObjectRef, parentObject?: KubeObject): string => {
|
||||||
const {
|
const {
|
||||||
kind, apiVersion, name,
|
kind, apiVersion, name,
|
||||||
namespace = parentObject?.getNs(),
|
namespace = parentObject?.getNs(),
|
||||||
@ -116,7 +223,5 @@ export class ApiManager {
|
|||||||
// otherwise generate link with default prefix
|
// otherwise generate link with default prefix
|
||||||
// resource still might exists in k8s, but api is not registered in the app
|
// resource still might exists in k8s, but api is not registered in the app
|
||||||
return createKubeApiURL({ apiVersion, name, namespace, resource });
|
return createKubeApiURL({ apiVersion, name, namespace, resource });
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const apiManager = new ApiManager();
|
|
||||||
|
|||||||
@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* 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 apiManagerInjectable from "../api-manager.injectable";
|
||||||
|
import type { ClusterRoleBindingApi } from "./cluster-role-binding.api";
|
||||||
|
|
||||||
|
const clusterRoleBindingApiInjectable = getInjectable({
|
||||||
|
instantiate: (di) => di.inject(apiManagerInjectable).getApi("/apis/rbac.authorization.k8s.io/v1/clusterrolebindings") as ClusterRoleBindingApi,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default clusterRoleBindingApiInjectable;
|
||||||
@ -2,8 +2,7 @@
|
|||||||
* 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 { isClusterPageContext } from "../../utils/cluster-id-url-parsing";
|
import { KubeApi, SpecificApiOptions } from "../kube-api";
|
||||||
import { KubeApi } from "../kube-api";
|
|
||||||
import { KubeObject } from "../kube-object";
|
import { KubeObject } from "../kube-object";
|
||||||
|
|
||||||
export type ClusterRoleBindingSubjectKind = "Group" | "ServiceAccount" | "User";
|
export type ClusterRoleBindingSubjectKind = "Group" | "ServiceAccount" | "User";
|
||||||
@ -38,17 +37,11 @@ export class ClusterRoleBinding extends KubeObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export class ClusterRoleBindingApi extends KubeApi<ClusterRoleBinding> {
|
||||||
* Only available within kubernetes cluster pages
|
constructor(args: SpecificApiOptions<ClusterRoleBinding> = {}) {
|
||||||
*/
|
super({
|
||||||
let clusterRoleBindingApi: KubeApi<ClusterRoleBinding>;
|
...args,
|
||||||
|
objectConstructor: ClusterRoleBinding,
|
||||||
if (isClusterPageContext()) {
|
});
|
||||||
clusterRoleBindingApi = new KubeApi({
|
}
|
||||||
objectConstructor: ClusterRoleBinding,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
|
||||||
clusterRoleBindingApi,
|
|
||||||
};
|
|
||||||
|
|||||||
14
src/common/k8s-api/endpoints/cluster-role.api.injectable.ts
Normal file
14
src/common/k8s-api/endpoints/cluster-role.api.injectable.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* 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 apiManagerInjectable from "../api-manager.injectable";
|
||||||
|
import type { ClusterRoleApi } from "./cluster-role.api";
|
||||||
|
|
||||||
|
const clusterRoleApiInjectable = getInjectable({
|
||||||
|
instantiate: (di) => di.inject(apiManagerInjectable).getApi("/apis/rbac.authorization.k8s.io/v1/clusterroles") as ClusterRoleApi,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default clusterRoleApiInjectable;
|
||||||
@ -3,8 +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 { isClusterPageContext } from "../../utils/cluster-id-url-parsing";
|
import { KubeApi, SpecificApiOptions } from "../kube-api";
|
||||||
import { KubeApi } from "../kube-api";
|
|
||||||
import { KubeObject } from "../kube-object";
|
import { KubeObject } from "../kube-object";
|
||||||
|
|
||||||
export interface ClusterRole {
|
export interface ClusterRole {
|
||||||
@ -26,17 +25,11 @@ export class ClusterRole extends KubeObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export class ClusterRoleApi extends KubeApi<ClusterRole> {
|
||||||
* Only available within kubernetes cluster pages
|
constructor(args: SpecificApiOptions<ClusterRole> = {}) {
|
||||||
*/
|
super({
|
||||||
let clusterRoleApi: KubeApi<ClusterRole>;
|
...args,
|
||||||
|
objectConstructor: ClusterRole,
|
||||||
if (isClusterPageContext()) { // initialize automatically only when within a cluster iframe/context
|
});
|
||||||
clusterRoleApi = new KubeApi({
|
}
|
||||||
objectConstructor: ClusterRole,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
|
||||||
clusterRoleApi,
|
|
||||||
};
|
|
||||||
|
|||||||
14
src/common/k8s-api/endpoints/cluster.api.injectable.ts
Normal file
14
src/common/k8s-api/endpoints/cluster.api.injectable.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* 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 apiManagerInjectable from "../api-manager.injectable";
|
||||||
|
import type { ClusterApi } from "./cluster.api";
|
||||||
|
|
||||||
|
const clusterApiInjectable = getInjectable({
|
||||||
|
instantiate: (di) => di.inject(apiManagerInjectable).getApi("/apis/cluster.k8s.io/v1alpha1/clusters") as ClusterApi,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default clusterApiInjectable;
|
||||||
@ -5,13 +5,7 @@
|
|||||||
|
|
||||||
import { IMetrics, IMetricsReqParams, metricsApi } from "./metrics.api";
|
import { IMetrics, IMetricsReqParams, metricsApi } from "./metrics.api";
|
||||||
import { KubeObject } from "../kube-object";
|
import { KubeObject } from "../kube-object";
|
||||||
import { KubeApi } from "../kube-api";
|
import { KubeApi, SpecificApiOptions } from "../kube-api";
|
||||||
import { isClusterPageContext } from "../../utils/cluster-id-url-parsing";
|
|
||||||
|
|
||||||
export class ClusterApi extends KubeApi<Cluster> {
|
|
||||||
static kind = "Cluster";
|
|
||||||
static namespaced = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getMetricsByNodeNames(nodeNames: string[], params?: IMetricsReqParams): Promise<IClusterMetrics> {
|
export function getMetricsByNodeNames(nodeNames: string[], params?: IMetricsReqParams): Promise<IClusterMetrics> {
|
||||||
const nodes = nodeNames.join("|");
|
const nodes = nodeNames.join("|");
|
||||||
@ -97,6 +91,7 @@ export interface Cluster {
|
|||||||
export class Cluster extends KubeObject {
|
export class Cluster extends KubeObject {
|
||||||
static kind = "Cluster";
|
static kind = "Cluster";
|
||||||
static apiBase = "/apis/cluster.k8s.io/v1alpha1/clusters";
|
static apiBase = "/apis/cluster.k8s.io/v1alpha1/clusters";
|
||||||
|
static namespaced = true;
|
||||||
|
|
||||||
getStatus() {
|
getStatus() {
|
||||||
if (this.metadata.deletionTimestamp) return ClusterStatus.REMOVING;
|
if (this.metadata.deletionTimestamp) return ClusterStatus.REMOVING;
|
||||||
@ -107,17 +102,11 @@ export class Cluster extends KubeObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export class ClusterApi extends KubeApi<Cluster> {
|
||||||
* Only available within kubernetes cluster pages
|
constructor(args: SpecificApiOptions<Cluster> = {}) {
|
||||||
*/
|
super({
|
||||||
let clusterApi: ClusterApi;
|
...args,
|
||||||
|
objectConstructor: Cluster,
|
||||||
if (isClusterPageContext()) { // initialize automatically only when within a cluster iframe/context
|
});
|
||||||
clusterApi = new ClusterApi({
|
}
|
||||||
objectConstructor: Cluster,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
|
||||||
clusterApi,
|
|
||||||
};
|
|
||||||
|
|||||||
@ -1,31 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { KubeObject } from "../kube-object";
|
|
||||||
import { KubeApi } from "../kube-api";
|
|
||||||
|
|
||||||
export interface IComponentStatusCondition {
|
|
||||||
type: string;
|
|
||||||
status: string;
|
|
||||||
message: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ComponentStatus {
|
|
||||||
conditions: IComponentStatusCondition[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ComponentStatus extends KubeObject {
|
|
||||||
static kind = "ComponentStatus";
|
|
||||||
static namespaced = false;
|
|
||||||
static apiBase = "/api/v1/componentstatuses";
|
|
||||||
|
|
||||||
getTruthyConditions() {
|
|
||||||
return this.conditions.filter(c => c.status === "True");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const componentStatusApi = new KubeApi({
|
|
||||||
objectConstructor: ComponentStatus,
|
|
||||||
});
|
|
||||||
14
src/common/k8s-api/endpoints/configmap.api.injectable.ts
Normal file
14
src/common/k8s-api/endpoints/configmap.api.injectable.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* 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 apiManagerInjectable from "../api-manager.injectable";
|
||||||
|
import type { ConfigMapApi } from "./configmap.api";
|
||||||
|
|
||||||
|
const configMapApiInjectable = getInjectable({
|
||||||
|
instantiate: (di) => di.inject(apiManagerInjectable).getApi("/api/v1/configmaps") as ConfigMapApi,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default configMapApiInjectable;
|
||||||
@ -5,9 +5,8 @@
|
|||||||
|
|
||||||
import { KubeObject } from "../kube-object";
|
import { KubeObject } from "../kube-object";
|
||||||
import type { KubeJsonApiData } from "../kube-json-api";
|
import type { KubeJsonApiData } from "../kube-json-api";
|
||||||
import { KubeApi } from "../kube-api";
|
import { KubeApi, SpecificApiOptions } from "../kube-api";
|
||||||
import { autoBind } from "../../utils";
|
import { autoBind } from "../../utils";
|
||||||
import { isClusterPageContext } from "../../utils/cluster-id-url-parsing";
|
|
||||||
|
|
||||||
export interface ConfigMap {
|
export interface ConfigMap {
|
||||||
data: {
|
data: {
|
||||||
@ -32,17 +31,11 @@ export class ConfigMap extends KubeObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export class ConfigMapApi extends KubeApi<ConfigMap> {
|
||||||
* Only available within kubernetes cluster pages
|
constructor(args: SpecificApiOptions<ConfigMap> = {}) {
|
||||||
*/
|
super({
|
||||||
let configMapApi: KubeApi<ConfigMap>;
|
...args,
|
||||||
|
objectConstructor: ConfigMap,
|
||||||
if (isClusterPageContext()) {
|
});
|
||||||
configMapApi = new KubeApi({
|
}
|
||||||
objectConstructor: ConfigMap,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
|
||||||
configMapApi,
|
|
||||||
};
|
|
||||||
|
|||||||
14
src/common/k8s-api/endpoints/cron-job.api.injectable.ts
Normal file
14
src/common/k8s-api/endpoints/cron-job.api.injectable.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* 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 { CronJobApi } from "./cron-job.api";
|
||||||
|
import apiManagerInjectable from "../api-manager.injectable";
|
||||||
|
|
||||||
|
const cronJobApiInjectable = getInjectable({
|
||||||
|
instantiate: (di) => di.inject(apiManagerInjectable).getApi("/apis/batch/v1beta1/cronjobs") as CronJobApi,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default cronJobApiInjectable;
|
||||||
@ -5,44 +5,11 @@
|
|||||||
|
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { KubeObject } from "../kube-object";
|
import { KubeObject } from "../kube-object";
|
||||||
import type { IPodContainer } from "./pods.api";
|
import type { IPodContainer } from "./pod.api";
|
||||||
import { formatDuration } from "../../utils/formatDuration";
|
import { formatDuration } from "../../utils/formatDuration";
|
||||||
import { autoBind } from "../../utils";
|
import { autoBind } from "../../utils";
|
||||||
import { KubeApi } from "../kube-api";
|
import { KubeApi, SpecificApiOptions } from "../kube-api";
|
||||||
import type { KubeJsonApiData } from "../kube-json-api";
|
import type { KubeJsonApiData } from "../kube-json-api";
|
||||||
import { isClusterPageContext } from "../../utils/cluster-id-url-parsing";
|
|
||||||
|
|
||||||
export class CronJobApi extends KubeApi<CronJob> {
|
|
||||||
suspend(params: { namespace: string; name: string }) {
|
|
||||||
return this.request.patch(this.getUrl(params), {
|
|
||||||
data: {
|
|
||||||
spec: {
|
|
||||||
suspend: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
"content-type": "application/strategic-merge-patch+json",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
resume(params: { namespace: string; name: string }) {
|
|
||||||
return this.request.patch(this.getUrl(params), {
|
|
||||||
data: {
|
|
||||||
spec: {
|
|
||||||
suspend: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
"content-type": "application/strategic-merge-patch+json",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CronJob {
|
export interface CronJob {
|
||||||
spec: {
|
spec: {
|
||||||
@ -125,17 +92,41 @@ export class CronJob extends KubeObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export class CronJobApi extends KubeApi<CronJob> {
|
||||||
* Only available within kubernetes cluster pages
|
constructor(args: SpecificApiOptions<CronJob> = {}) {
|
||||||
*/
|
super({
|
||||||
let cronJobApi: CronJobApi;
|
...args,
|
||||||
|
objectConstructor: CronJob,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (isClusterPageContext()) {
|
suspend(params: { namespace: string; name: string }) {
|
||||||
cronJobApi = new CronJobApi({
|
return this.request.patch(this.getUrl(params), {
|
||||||
objectConstructor: CronJob,
|
data: {
|
||||||
});
|
spec: {
|
||||||
|
suspend: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/strategic-merge-patch+json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
resume(params: { namespace: string; name: string }) {
|
||||||
|
return this.request.patch(this.getUrl(params), {
|
||||||
|
data: {
|
||||||
|
spec: {
|
||||||
|
suspend: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/strategic-merge-patch+json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
|
||||||
cronJobApi,
|
|
||||||
};
|
|
||||||
|
|||||||
@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* 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 apiManagerInjectable from "../api-manager.injectable";
|
||||||
|
import type { CustomResourceDefinitionApi } from "./custom-resource-definition.api";
|
||||||
|
|
||||||
|
const customResourceDefinitionApiInjectable = getInjectable({
|
||||||
|
instantiate: (di) => di.inject(apiManagerInjectable).getApi("/apis/apiextensions.k8s.io/v1/customresourcedefinitions") as CustomResourceDefinitionApi,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default customResourceDefinitionApiInjectable;
|
||||||
@ -4,9 +4,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { KubeCreationError, KubeObject } from "../kube-object";
|
import { KubeCreationError, KubeObject } from "../kube-object";
|
||||||
import { KubeApi } from "../kube-api";
|
import { KubeApi, SpecificApiOptions } from "../kube-api";
|
||||||
import { crdResourcesURL } from "../../routes";
|
import { crdResourcesURL } from "../../routes";
|
||||||
import { isClusterPageContext } from "../../utils/cluster-id-url-parsing";
|
|
||||||
import type { KubeJsonApiData } from "../kube-json-api";
|
import type { KubeJsonApiData } from "../kube-json-api";
|
||||||
|
|
||||||
type AdditionalPrinterColumnsCommon = {
|
type AdditionalPrinterColumnsCommon = {
|
||||||
@ -210,18 +209,11 @@ export class CustomResourceDefinition extends KubeObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export class CustomResourceDefinitionApi extends KubeApi<CustomResourceDefinition> {
|
||||||
* Only available within kubernetes cluster pages
|
constructor(args: SpecificApiOptions<CustomResourceDefinition> = {}) {
|
||||||
*/
|
super({
|
||||||
let crdApi: KubeApi<CustomResourceDefinition>;
|
...args,
|
||||||
|
objectConstructor: CustomResourceDefinition,
|
||||||
if (isClusterPageContext()) {
|
});
|
||||||
crdApi = new KubeApi<CustomResourceDefinition>({
|
}
|
||||||
objectConstructor: CustomResourceDefinition,
|
|
||||||
checkPreferredVersion: true,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
|
||||||
crdApi,
|
|
||||||
};
|
|
||||||
14
src/common/k8s-api/endpoints/daemon-set.api.injectable.ts
Normal file
14
src/common/k8s-api/endpoints/daemon-set.api.injectable.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* 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 apiManagerInjectable from "../api-manager.injectable";
|
||||||
|
import type { DaemonSetApi } from "./daemon-set.api";
|
||||||
|
|
||||||
|
const daemonSetApiInjectable = getInjectable({
|
||||||
|
instantiate: (di) => di.inject(apiManagerInjectable).getApi("/apis/apps/v1/daemonsets") as DaemonSetApi,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default daemonSetApiInjectable;
|
||||||
@ -6,11 +6,10 @@
|
|||||||
import get from "lodash/get";
|
import get from "lodash/get";
|
||||||
import { IAffinity, WorkloadKubeObject } from "../workload-kube-object";
|
import { IAffinity, WorkloadKubeObject } from "../workload-kube-object";
|
||||||
import { autoBind } from "../../utils";
|
import { autoBind } from "../../utils";
|
||||||
import { KubeApi } from "../kube-api";
|
import { KubeApi, SpecificApiOptions } from "../kube-api";
|
||||||
import { metricsApi } from "./metrics.api";
|
import { metricsApi } from "./metrics.api";
|
||||||
import type { KubeJsonApiData } from "../kube-json-api";
|
import type { KubeJsonApiData } from "../kube-json-api";
|
||||||
import type { IPodContainer, IPodMetrics } from "./pods.api";
|
import type { IPodContainer, IPodMetrics } from "./pod.api";
|
||||||
import { isClusterPageContext } from "../../utils/cluster-id-url-parsing";
|
|
||||||
import type { LabelSelector } from "../kube-object";
|
import type { LabelSelector } from "../kube-object";
|
||||||
|
|
||||||
export class DaemonSet extends WorkloadKubeObject {
|
export class DaemonSet extends WorkloadKubeObject {
|
||||||
@ -80,9 +79,6 @@ export class DaemonSet extends WorkloadKubeObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DaemonSetApi extends KubeApi<DaemonSet> {
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getMetricsForDaemonSets(daemonsets: DaemonSet[], namespace: string, selector = ""): Promise<IPodMetrics> {
|
export function getMetricsForDaemonSets(daemonsets: DaemonSet[], namespace: string, selector = ""): Promise<IPodMetrics> {
|
||||||
const podSelector = daemonsets.map(daemonset => `${daemonset.getName()}-[[:alnum:]]{5}`).join("|");
|
const podSelector = daemonsets.map(daemonset => `${daemonset.getName()}-[[:alnum:]]{5}`).join("|");
|
||||||
const opts = { category: "pods", pods: podSelector, namespace, selector };
|
const opts = { category: "pods", pods: podSelector, namespace, selector };
|
||||||
@ -100,17 +96,11 @@ export function getMetricsForDaemonSets(daemonsets: DaemonSet[], namespace: stri
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export class DaemonSetApi extends KubeApi<DaemonSet> {
|
||||||
* Only available within kubernetes cluster pages
|
constructor(args: SpecificApiOptions<DaemonSet> = {}) {
|
||||||
*/
|
super({
|
||||||
let daemonSetApi: DaemonSetApi;
|
...args,
|
||||||
|
objectConstructor: DaemonSet,
|
||||||
if (isClusterPageContext()) {
|
});
|
||||||
daemonSetApi = new DaemonSetApi({
|
}
|
||||||
objectConstructor: DaemonSet,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
|
||||||
daemonSetApi,
|
|
||||||
};
|
|
||||||
|
|||||||
14
src/common/k8s-api/endpoints/deployment.api.injectable.ts
Normal file
14
src/common/k8s-api/endpoints/deployment.api.injectable.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* 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 apiManagerInjectable from "../api-manager.injectable";
|
||||||
|
import type { DeploymentApi } from "./deployment.api";
|
||||||
|
|
||||||
|
const deploymentApiInjectable = getInjectable({
|
||||||
|
instantiate: (di) => di.inject(apiManagerInjectable).getApi("/apis/apps/v1/deployments") as DeploymentApi,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default deploymentApiInjectable;
|
||||||
@ -7,59 +7,12 @@ import moment from "moment";
|
|||||||
|
|
||||||
import { IAffinity, WorkloadKubeObject } from "../workload-kube-object";
|
import { IAffinity, WorkloadKubeObject } from "../workload-kube-object";
|
||||||
import { autoBind } from "../../utils";
|
import { autoBind } from "../../utils";
|
||||||
import { KubeApi } from "../kube-api";
|
import { KubeApi, SpecificApiOptions } from "../kube-api";
|
||||||
import { metricsApi } from "./metrics.api";
|
import { metricsApi } from "./metrics.api";
|
||||||
import type { IPodMetrics } from "./pods.api";
|
import type { IPodMetrics } from "./pod.api";
|
||||||
import type { KubeJsonApiData } from "../kube-json-api";
|
import type { KubeJsonApiData } from "../kube-json-api";
|
||||||
import { isClusterPageContext } from "../../utils/cluster-id-url-parsing";
|
|
||||||
import type { LabelSelector } from "../kube-object";
|
import type { LabelSelector } from "../kube-object";
|
||||||
|
|
||||||
export class DeploymentApi extends KubeApi<Deployment> {
|
|
||||||
protected getScaleApiUrl(params: { namespace: string; name: string }) {
|
|
||||||
return `${this.getUrl(params)}/scale`;
|
|
||||||
}
|
|
||||||
|
|
||||||
getReplicas(params: { namespace: string; name: string }): Promise<number> {
|
|
||||||
return this.request
|
|
||||||
.get(this.getScaleApiUrl(params))
|
|
||||||
.then(({ status }: any) => status?.replicas);
|
|
||||||
}
|
|
||||||
|
|
||||||
scale(params: { namespace: string; name: string }, replicas: number) {
|
|
||||||
return this.request.patch(this.getScaleApiUrl(params), {
|
|
||||||
data: {
|
|
||||||
spec: {
|
|
||||||
replicas,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
"content-type": "application/merge-patch+json",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
restart(params: { namespace: string; name: string }) {
|
|
||||||
return this.request.patch(this.getUrl(params), {
|
|
||||||
data: {
|
|
||||||
spec: {
|
|
||||||
template: {
|
|
||||||
metadata: {
|
|
||||||
annotations: { "kubectl.kubernetes.io/restartedAt" : moment.utc().format() },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
"content-type": "application/strategic-merge-patch+json",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getMetricsForDeployments(deployments: Deployment[], namespace: string, selector = ""): Promise<IPodMetrics> {
|
export function getMetricsForDeployments(deployments: Deployment[], namespace: string, selector = ""): Promise<IPodMetrics> {
|
||||||
const podSelector = deployments.map(deployment => `${deployment.getName()}-[[:alnum:]]{9,}-[[:alnum:]]{5}`).join("|");
|
const podSelector = deployments.map(deployment => `${deployment.getName()}-[[:alnum:]]{9,}-[[:alnum:]]{5}`).join("|");
|
||||||
const opts = { category: "pods", pods: podSelector, namespace, selector };
|
const opts = { category: "pods", pods: podSelector, namespace, selector };
|
||||||
@ -224,14 +177,61 @@ export class Deployment extends WorkloadKubeObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let deploymentApi: DeploymentApi;
|
interface ReplicasStatus {
|
||||||
|
status?: {
|
||||||
if (isClusterPageContext()) {
|
replicas: number;
|
||||||
deploymentApi = new DeploymentApi({
|
}
|
||||||
objectConstructor: Deployment,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export class DeploymentApi extends KubeApi<Deployment> {
|
||||||
deploymentApi,
|
constructor(args: SpecificApiOptions<Deployment> = {}) {
|
||||||
};
|
super({
|
||||||
|
...args,
|
||||||
|
objectConstructor: Deployment,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getScaleApiUrl(params: { namespace: string; name: string }) {
|
||||||
|
return `${this.getUrl(params)}/scale`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getReplicas(params: { namespace: string; name: string }): Promise<number> {
|
||||||
|
const { status } = await this.request.get<ReplicasStatus>(this.getScaleApiUrl(params));
|
||||||
|
|
||||||
|
return status?.replicas ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
scale(params: { namespace: string; name: string }, replicas: number) {
|
||||||
|
return this.request.patch(this.getScaleApiUrl(params), {
|
||||||
|
data: {
|
||||||
|
spec: {
|
||||||
|
replicas,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/merge-patch+json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
restart(params: { namespace: string; name: string }) {
|
||||||
|
return this.request.patch(this.getUrl(params), {
|
||||||
|
data: {
|
||||||
|
spec: {
|
||||||
|
template: {
|
||||||
|
metadata: {
|
||||||
|
annotations: { "kubectl.kubernetes.io/restartedAt" : moment.utc().format() },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/strategic-merge-patch+json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
14
src/common/k8s-api/endpoints/endpoint.api.injectable.ts
Normal file
14
src/common/k8s-api/endpoints/endpoint.api.injectable.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* 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 apiManagerInjectable from "../api-manager.injectable";
|
||||||
|
import type { EndpointApi } from "./endpoint.api";
|
||||||
|
|
||||||
|
const endpointApiInjectable = getInjectable({
|
||||||
|
instantiate: (di) => di.inject(apiManagerInjectable).getApi("/api/v1/endpoints") as EndpointApi,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default endpointApiInjectable;
|
||||||
@ -5,10 +5,9 @@
|
|||||||
|
|
||||||
import { autoBind } from "../../utils";
|
import { autoBind } from "../../utils";
|
||||||
import { KubeObject } from "../kube-object";
|
import { KubeObject } from "../kube-object";
|
||||||
import { KubeApi } from "../kube-api";
|
import { KubeApi, SpecificApiOptions } from "../kube-api";
|
||||||
import type { KubeJsonApiData } from "../kube-json-api";
|
import type { KubeJsonApiData } from "../kube-json-api";
|
||||||
import { get } from "lodash";
|
import { get } from "lodash";
|
||||||
import { isClusterPageContext } from "../../utils/cluster-id-url-parsing";
|
|
||||||
|
|
||||||
export interface IEndpointPort {
|
export interface IEndpointPort {
|
||||||
name?: string;
|
name?: string;
|
||||||
@ -131,17 +130,13 @@ export class Endpoint extends KubeObject {
|
|||||||
return "<none>";
|
return "<none>";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let endpointApi: KubeApi<Endpoint>;
|
export class EndpointApi extends KubeApi<Endpoint> {
|
||||||
|
constructor(args: SpecificApiOptions<Endpoint> = {}) {
|
||||||
if (isClusterPageContext()) {
|
super({
|
||||||
endpointApi = new KubeApi<Endpoint>({
|
...args,
|
||||||
objectConstructor: Endpoint,
|
objectConstructor: Endpoint,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
|
||||||
endpointApi,
|
|
||||||
};
|
|
||||||
|
|||||||
14
src/common/k8s-api/endpoints/event.api.injectable.ts
Normal file
14
src/common/k8s-api/endpoints/event.api.injectable.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* 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 apiManagerInjectable from "../api-manager.injectable";
|
||||||
|
import type { EventApi } from "./event.api";
|
||||||
|
|
||||||
|
const eventApiInjectable = getInjectable({
|
||||||
|
instantiate: (di) => di.inject(apiManagerInjectable).getApi("/api/v1/events") as EventApi,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default eventApiInjectable;
|
||||||
@ -6,10 +6,9 @@
|
|||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { KubeObject } from "../kube-object";
|
import { KubeObject } from "../kube-object";
|
||||||
import { formatDuration } from "../../utils/formatDuration";
|
import { formatDuration } from "../../utils/formatDuration";
|
||||||
import { KubeApi } from "../kube-api";
|
import { KubeApi, SpecificApiOptions } from "../kube-api";
|
||||||
import { isClusterPageContext } from "../../utils/cluster-id-url-parsing";
|
|
||||||
|
|
||||||
export interface KubeEvent {
|
export interface Event {
|
||||||
involvedObject: {
|
involvedObject: {
|
||||||
kind: string;
|
kind: string;
|
||||||
namespace: string;
|
namespace: string;
|
||||||
@ -34,7 +33,7 @@ export interface KubeEvent {
|
|||||||
reportingInstance: string;
|
reportingInstance: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class KubeEvent extends KubeObject {
|
export class Event extends KubeObject {
|
||||||
static kind = "Event";
|
static kind = "Event";
|
||||||
static namespaced = true;
|
static namespaced = true;
|
||||||
static apiBase = "/api/v1/events";
|
static apiBase = "/api/v1/events";
|
||||||
@ -62,14 +61,11 @@ export class KubeEvent extends KubeObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let eventApi: KubeApi<KubeEvent>;
|
export class EventApi extends KubeApi<Event> {
|
||||||
|
constructor(args: SpecificApiOptions<Event> = {}) {
|
||||||
if (isClusterPageContext()) {
|
super({
|
||||||
eventApi = new KubeApi<KubeEvent>({
|
...args,
|
||||||
objectConstructor: KubeEvent,
|
objectConstructor: Event,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
|
||||||
eventApi,
|
|
||||||
};
|
|
||||||
@ -65,7 +65,7 @@ export async function getChartDetails(repo: string, name: string, { version, req
|
|||||||
* @param name The name of the chart to request the data of
|
* @param name The name of the chart to request the data of
|
||||||
* @param version The version to get the values from
|
* @param version The version to get the values from
|
||||||
*/
|
*/
|
||||||
export async function getChartValues(repo: string, name: string, version: string): Promise<string> {
|
export function getChartValues(repo: string, name: string, version: string): Promise<string> {
|
||||||
return apiBase.get<string>(`/v2/charts/${repo}/${name}/values?${stringify({ version })}`);
|
return apiBase.get<string>(`/v2/charts/${repo}/${name}/values?${stringify({ version })}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4,10 +4,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import yaml from "js-yaml";
|
import yaml from "js-yaml";
|
||||||
import { autoBind, formatDuration } from "../../utils";
|
import { formatDuration } from "../../utils";
|
||||||
import capitalize from "lodash/capitalize";
|
import capitalize from "lodash/capitalize";
|
||||||
import { apiBase } from "../index";
|
import { apiBase } from "../index";
|
||||||
import { helmChartStore } from "../../../renderer/components/+apps-helm-charts/helm-chart.store";
|
import { helmChartStore } from "../../../renderer/components/+helm-charts/store";
|
||||||
import type { ItemObject } from "../../item.store";
|
import type { ItemObject } from "../../item.store";
|
||||||
import { KubeObject } from "../kube-object";
|
import { KubeObject } from "../kube-object";
|
||||||
import type { JsonApiData } from "../json-api";
|
import type { JsonApiData } from "../json-api";
|
||||||
@ -80,9 +80,9 @@ interface EndpointQuery {
|
|||||||
const endpoint = buildURLPositional<EndpointParams, EndpointQuery>("/v2/releases/:namespace?/:name?/:route?");
|
const endpoint = buildURLPositional<EndpointParams, EndpointQuery>("/v2/releases/:namespace?/:name?/:route?");
|
||||||
|
|
||||||
export async function listReleases(namespace?: string): Promise<HelmRelease[]> {
|
export async function listReleases(namespace?: string): Promise<HelmRelease[]> {
|
||||||
const releases = await apiBase.get<HelmRelease[]>(endpoint({ namespace }));
|
const releases = await apiBase.get<HelmReleaseDto[]>(endpoint({ namespace }));
|
||||||
|
|
||||||
return releases.map(HelmRelease.create);
|
return releases.map(toHelmRelease);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getRelease(name: string, namespace: string): Promise<IReleaseDetails> {
|
export async function getRelease(name: string, namespace: string): Promise<IReleaseDetails> {
|
||||||
@ -96,7 +96,7 @@ export async function getRelease(name: string, namespace: string): Promise<IRele
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createRelease(payload: IReleaseCreatePayload): Promise<IReleaseUpdateDetails> {
|
export function createRelease(payload: IReleaseCreatePayload): Promise<IReleaseUpdateDetails> {
|
||||||
const { repo, chart: rawChart, values: rawValues, ...data } = payload;
|
const { repo, chart: rawChart, values: rawValues, ...data } = payload;
|
||||||
const chart = `${repo}/${rawChart}`;
|
const chart = `${repo}/${rawChart}`;
|
||||||
const values = yaml.load(rawValues);
|
const values = yaml.load(rawValues);
|
||||||
@ -110,7 +110,7 @@ export async function createRelease(payload: IReleaseCreatePayload): Promise<IRe
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateRelease(name: string, namespace: string, payload: IReleaseUpdatePayload): Promise<IReleaseUpdateDetails> {
|
export function updateRelease(name: string, namespace: string, payload: IReleaseUpdatePayload): Promise<IReleaseUpdateDetails> {
|
||||||
const { repo, chart: rawChart, values: rawValues, ...data } = payload;
|
const { repo, chart: rawChart, values: rawValues, ...data } = payload;
|
||||||
const chart = `${repo}/${rawChart}`;
|
const chart = `${repo}/${rawChart}`;
|
||||||
const values = yaml.load(rawValues);
|
const values = yaml.load(rawValues);
|
||||||
@ -124,27 +124,27 @@ export async function updateRelease(name: string, namespace: string, payload: IR
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteRelease(name: string, namespace: string): Promise<JsonApiData> {
|
export function deleteRelease(name: string, namespace: string): Promise<JsonApiData> {
|
||||||
const path = endpoint({ name, namespace });
|
const path = endpoint({ name, namespace });
|
||||||
|
|
||||||
return apiBase.del(path);
|
return apiBase.del(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getReleaseValues(name: string, namespace: string, all?: boolean): Promise<string> {
|
export function getReleaseValues(name: string, namespace: string, all?: boolean): Promise<string> {
|
||||||
const route = "values";
|
const route = "values";
|
||||||
const path = endpoint({ name, namespace, route }, { all });
|
const path = endpoint({ name, namespace, route }, { all });
|
||||||
|
|
||||||
return apiBase.get<string>(path);
|
return apiBase.get<string>(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getReleaseHistory(name: string, namespace: string): Promise<IReleaseRevision[]> {
|
export function getReleaseHistory(name: string, namespace: string): Promise<IReleaseRevision[]> {
|
||||||
const route = "history";
|
const route = "history";
|
||||||
const path = endpoint({ name, namespace, route });
|
const path = endpoint({ name, namespace, route });
|
||||||
|
|
||||||
return apiBase.get(path);
|
return apiBase.get(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function rollbackRelease(name: string, namespace: string, revision: number): Promise<JsonApiData> {
|
export function rollbackRelease(name: string, namespace: string, revision: number): Promise<JsonApiData> {
|
||||||
const route = "rollback";
|
const route = "rollback";
|
||||||
const path = endpoint({ name, namespace, route });
|
const path = endpoint({ name, namespace, route });
|
||||||
const data = { revision };
|
const data = { revision };
|
||||||
@ -152,7 +152,7 @@ export async function rollbackRelease(name: string, namespace: string, revision:
|
|||||||
return apiBase.put(path, { data });
|
return apiBase.put(path, { data });
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HelmRelease {
|
interface HelmReleaseDto {
|
||||||
appVersion: string;
|
appVersion: string;
|
||||||
name: string;
|
name: string;
|
||||||
namespace: string;
|
namespace: string;
|
||||||
@ -162,27 +162,30 @@ export interface HelmRelease {
|
|||||||
revision: string;
|
revision: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class HelmRelease implements ItemObject {
|
export interface HelmRelease extends HelmReleaseDto, ItemObject {
|
||||||
constructor(data: any) {
|
getNs: () => string
|
||||||
Object.assign(this, data);
|
getChart: (withVersion?: boolean) => string
|
||||||
autoBind(this);
|
getRevision: () => number
|
||||||
}
|
getStatus: () => string
|
||||||
|
getVersion: () => string
|
||||||
|
getUpdated: (humanize?: boolean, compact?: boolean) => string | number
|
||||||
|
getRepo: () => Promise<string>
|
||||||
|
}
|
||||||
|
|
||||||
static create(data: any) {
|
const toHelmRelease = (release: HelmReleaseDto) : HelmRelease => ({
|
||||||
return new HelmRelease(data);
|
...release,
|
||||||
}
|
|
||||||
|
|
||||||
getId() {
|
getId() {
|
||||||
return this.namespace + this.name;
|
return this.namespace + this.name;
|
||||||
}
|
},
|
||||||
|
|
||||||
getName() {
|
getName() {
|
||||||
return this.name;
|
return this.name;
|
||||||
}
|
},
|
||||||
|
|
||||||
getNs() {
|
getNs() {
|
||||||
return this.namespace;
|
return this.namespace;
|
||||||
}
|
},
|
||||||
|
|
||||||
getChart(withVersion = false) {
|
getChart(withVersion = false) {
|
||||||
let chart = this.chart;
|
let chart = this.chart;
|
||||||
@ -194,24 +197,24 @@ export class HelmRelease implements ItemObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return chart;
|
return chart;
|
||||||
}
|
},
|
||||||
|
|
||||||
getRevision() {
|
getRevision() {
|
||||||
return parseInt(this.revision, 10);
|
return parseInt(this.revision, 10);
|
||||||
}
|
},
|
||||||
|
|
||||||
getStatus() {
|
getStatus() {
|
||||||
return capitalize(this.status);
|
return capitalize(this.status);
|
||||||
}
|
},
|
||||||
|
|
||||||
getVersion() {
|
getVersion() {
|
||||||
const versions = this.chart.match(/(?<=-)(v?\d+)[^-].*$/);
|
const versions = this.chart.match(/(?<=-)(v?\d+)[^-].*$/);
|
||||||
|
|
||||||
return versions?.[0] ?? "";
|
return versions?.[0] ?? "";
|
||||||
}
|
},
|
||||||
|
|
||||||
getUpdated(humanize = true, compact = true) {
|
getUpdated(humanize = true, compact = true) {
|
||||||
const updated = this.updated.replace(/\s\w*$/, ""); // 2019-11-26 10:58:09 +0300 MSK -> 2019-11-26 10:58:09 +0300 to pass into Date()
|
const updated = this.updated.replace(/\s\w*$/, ""); // 2019-11-26 10:58:09 +0300 MSK -> 2019-11-26 10:58:09 +0300 to pass into Date()
|
||||||
const updatedDate = new Date(updated).getTime();
|
const updatedDate = new Date(updated).getTime();
|
||||||
const diff = Date.now() - updatedDate;
|
const diff = Date.now() - updatedDate;
|
||||||
|
|
||||||
@ -220,7 +223,7 @@ export class HelmRelease implements ItemObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return diff;
|
return diff;
|
||||||
}
|
},
|
||||||
|
|
||||||
// Helm does not store from what repository the release is installed,
|
// Helm does not store from what repository the release is installed,
|
||||||
// so we have to try to guess it by searching charts
|
// so we have to try to guess it by searching charts
|
||||||
@ -228,8 +231,10 @@ export class HelmRelease implements ItemObject {
|
|||||||
const chartName = this.getChart();
|
const chartName = this.getChart();
|
||||||
const version = this.getVersion();
|
const version = this.getVersion();
|
||||||
const versions = await helmChartStore.getVersions(chartName);
|
const versions = await helmChartStore.getVersions(chartName);
|
||||||
const chartVersion = versions.find(chartVersion => chartVersion.version === version);
|
const chartVersion = versions.find(
|
||||||
|
(chartVersion) => chartVersion.version === version,
|
||||||
|
);
|
||||||
|
|
||||||
return chartVersion ? chartVersion.repo : "";
|
return chartVersion ? chartVersion.repo : "";
|
||||||
}
|
},
|
||||||
}
|
});
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* 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 apiManagerInjectable from "../api-manager.injectable";
|
||||||
|
import type { HorizontalPodAutoscalerApi } from "./horizontal-pod-autoscaler.api";
|
||||||
|
|
||||||
|
const horizontalPodAutoscalerApiInjectable = getInjectable({
|
||||||
|
instantiate: (di) => di.inject(apiManagerInjectable).getApi("/apis/autoscaling/v2beta1/horizontalpodautoscalers") as HorizontalPodAutoscalerApi,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default horizontalPodAutoscalerApiInjectable;
|
||||||
@ -4,8 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { KubeObject } from "../kube-object";
|
import { KubeObject } from "../kube-object";
|
||||||
import { KubeApi } from "../kube-api";
|
import { KubeApi, SpecificApiOptions } from "../kube-api";
|
||||||
import { isClusterPageContext } from "../../utils/cluster-id-url-parsing";
|
|
||||||
|
|
||||||
export enum HpaMetricType {
|
export enum HpaMetricType {
|
||||||
Resource = "Resource",
|
Resource = "Resource",
|
||||||
@ -148,14 +147,11 @@ export class HorizontalPodAutoscaler extends KubeObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let hpaApi: KubeApi<HorizontalPodAutoscaler>;
|
export class HorizontalPodAutoscalerApi extends KubeApi<HorizontalPodAutoscaler> {
|
||||||
|
constructor(args: SpecificApiOptions<HorizontalPodAutoscaler> = {}) {
|
||||||
if (isClusterPageContext()) {
|
super({
|
||||||
hpaApi = new KubeApi<HorizontalPodAutoscaler>({
|
...args,
|
||||||
objectConstructor: HorizontalPodAutoscaler,
|
objectConstructor: HorizontalPodAutoscaler,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
|
||||||
hpaApi,
|
|
||||||
};
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user