mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge branch 'master' into feature/port-forward-disable
This commit is contained in:
commit
9ebbe5b84f
@ -108,6 +108,8 @@ module.exports = {
|
|||||||
parser: "@typescript-eslint/parser",
|
parser: "@typescript-eslint/parser",
|
||||||
extends: [
|
extends: [
|
||||||
"plugin:@typescript-eslint/recommended",
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
"plugin:import/recommended",
|
||||||
|
"plugin:import/typescript",
|
||||||
],
|
],
|
||||||
plugins: [
|
plugins: [
|
||||||
"header",
|
"header",
|
||||||
@ -193,6 +195,8 @@ module.exports = {
|
|||||||
extends: [
|
extends: [
|
||||||
"plugin:@typescript-eslint/recommended",
|
"plugin:@typescript-eslint/recommended",
|
||||||
"plugin:react/recommended",
|
"plugin:react/recommended",
|
||||||
|
"plugin:import/recommended",
|
||||||
|
"plugin:import/typescript",
|
||||||
],
|
],
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
ecmaVersion: 2018,
|
ecmaVersion: 2018,
|
||||||
@ -202,6 +206,7 @@ module.exports = {
|
|||||||
rules: {
|
rules: {
|
||||||
"no-irregular-whitespace": "error",
|
"no-irregular-whitespace": "error",
|
||||||
"header/header": [2, "./license-header"],
|
"header/header": [2, "./license-header"],
|
||||||
|
"react/prop-types": "off",
|
||||||
"no-invalid-this": "off",
|
"no-invalid-this": "off",
|
||||||
"@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",
|
||||||
@ -246,7 +251,6 @@ module.exports = {
|
|||||||
"objectsInObjects": false,
|
"objectsInObjects": false,
|
||||||
"arraysInObjects": true,
|
"arraysInObjects": true,
|
||||||
}],
|
}],
|
||||||
"react/prop-types": "off",
|
|
||||||
"semi": "off",
|
"semi": "off",
|
||||||
"@typescript-eslint/semi": ["error"],
|
"@typescript-eslint/semi": ["error"],
|
||||||
"linebreak-style": ["error", "unix"],
|
"linebreak-style": ["error", "unix"],
|
||||||
|
|||||||
@ -37,9 +37,9 @@ export default class ExampleMainExtension extends Main.LensExtension {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### App Menus
|
### Menus
|
||||||
|
|
||||||
This extension can register custom app menus that will be displayed on OS native menus.
|
This extension can register custom app and tray menus that will be displayed on OS native menus.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
@ -56,6 +56,29 @@ export default class ExampleMainExtension extends Main.LensExtension {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
trayMenus = [
|
||||||
|
{
|
||||||
|
label: "My links",
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: "Lens",
|
||||||
|
click() {
|
||||||
|
Main.Navigation.navigate("https://k8slens.dev");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "separator"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Lens Github",
|
||||||
|
click() {
|
||||||
|
Main.Navigation.navigate("https://github.com/lensapp/lens");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
The Main Extension API is the interface to Lens's main process.
|
The Main Extension API is the interface to Lens's main process.
|
||||||
Lens runs in both main and renderer processes.
|
Lens runs in both main and renderer processes.
|
||||||
The Main Extension API allows you to access, configure, and customize Lens data, add custom application menu items and [protocol handlers](protocol-handlers.md), and run custom code in Lens's main process.
|
The Main Extension API allows you to access, configure, and customize Lens data, add custom application menu items and [protocol handlers](protocol-handlers.md), and run custom code in Lens's main process.
|
||||||
It also provides convenient methods for navigating to built-in Lens pages and extension pages, as well as adding and removing sources of catalog entities.
|
It also provides convenient methods for navigating to built-in Lens pages and extension pages, as well as adding and removing sources of catalog entities.
|
||||||
|
|
||||||
## `Main.LensExtension` Class
|
## `Main.LensExtension` Class
|
||||||
|
|
||||||
@ -45,7 +45,6 @@ For more details on accessing Lens state data, please see the [Stores](../stores
|
|||||||
### `appMenus`
|
### `appMenus`
|
||||||
|
|
||||||
The Main Extension API allows you to customize the UI application menu.
|
The Main Extension API allows you to customize the UI application menu.
|
||||||
Note that this is the only UI feature that the Main Extension API allows you to customize.
|
|
||||||
The following example demonstrates adding an item to the **Help** menu.
|
The following example demonstrates adding an item to the **Help** menu.
|
||||||
|
|
||||||
``` typescript
|
``` typescript
|
||||||
@ -65,7 +64,7 @@ export default class SamplePageMainExtension extends Main.LensExtension {
|
|||||||
```
|
```
|
||||||
|
|
||||||
`appMenus` is an array of objects that satisfy the `MenuRegistration` interface.
|
`appMenus` is an array of objects that satisfy the `MenuRegistration` interface.
|
||||||
`MenuRegistration` extends React's `MenuItemConstructorOptions` interface.
|
`MenuRegistration` extends Electron's `MenuItemConstructorOptions` interface.
|
||||||
The properties of the appMenus array objects are defined as follows:
|
The properties of the appMenus array objects are defined as follows:
|
||||||
|
|
||||||
* `parentId` is the name of the menu where your new menu item will be listed.
|
* `parentId` is the name of the menu where your new menu item will be listed.
|
||||||
@ -96,6 +95,35 @@ export default class SamplePageMainExtension extends Main.LensExtension {
|
|||||||
When the menu item is clicked the `navigate()` method looks for and displays a global page with id `"myGlobalPage"`.
|
When the menu item is clicked the `navigate()` method looks for and displays a global page with id `"myGlobalPage"`.
|
||||||
This page would be defined in your extension's `Renderer.LensExtension` implementation (See [`Renderer.LensExtension`](renderer-extension.md)).
|
This page would be defined in your extension's `Renderer.LensExtension` implementation (See [`Renderer.LensExtension`](renderer-extension.md)).
|
||||||
|
|
||||||
|
### `trayMenus`
|
||||||
|
|
||||||
|
`trayMenus` is an array of `TrayMenuRegistration` objects. Most importantly you can define a `label` and a `click` handler. Other properties are `submenu`, `enabled`, `toolTip`, `id` and `type`.
|
||||||
|
|
||||||
|
``` typescript
|
||||||
|
interface TrayMenuRegistration {
|
||||||
|
label?: string;
|
||||||
|
click?: (menuItem: TrayMenuRegistration) => void;
|
||||||
|
id?: string;
|
||||||
|
type?: "normal" | "separator" | "submenu"
|
||||||
|
toolTip?: string;
|
||||||
|
enabled?: boolean;
|
||||||
|
submenu?: TrayMenuRegistration[]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The following example demonstrates how tray menus can be added from extension:
|
||||||
|
|
||||||
|
``` typescript
|
||||||
|
import { Main } from "@k8slens/extensions";
|
||||||
|
|
||||||
|
export default class SampleTrayMenuMainExtension extends Main.LensExtension {
|
||||||
|
trayMenus = [{
|
||||||
|
label: "menu from the extension",
|
||||||
|
click: () => { console.log("tray menu clicked!") }
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### `addCatalogSource()` and `removeCatalogSource()` Methods
|
### `addCatalogSource()` and `removeCatalogSource()` Methods
|
||||||
|
|
||||||
The `Main.LensExtension` class also provides the `addCatalogSource()` and `removeCatalogSource()` methods, for managing custom catalog items (or entities).
|
The `Main.LensExtension` class also provides the `addCatalogSource()` and `removeCatalogSource()` methods, for managing custom catalog items (or entities).
|
||||||
|
|||||||
36
extensions/.eslintrc.js
Normal file
36
extensions/.eslintrc.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
files: [
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
],
|
||||||
|
rules: {
|
||||||
|
"import/no-unresolved": ["error", {
|
||||||
|
ignore: ["@k8slens/extensions"],
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
24
package.json
24
package.json
@ -62,8 +62,7 @@
|
|||||||
},
|
},
|
||||||
"moduleNameMapper": {
|
"moduleNameMapper": {
|
||||||
"\\.(css|scss)$": "<rootDir>/__mocks__/styleMock.ts",
|
"\\.(css|scss)$": "<rootDir>/__mocks__/styleMock.ts",
|
||||||
"\\.(svg)$": "<rootDir>/__mocks__/imageMock.ts",
|
"\\.(svg)$": "<rootDir>/__mocks__/imageMock.ts"
|
||||||
"src/(.*)": "<rootDir>/__mocks__/windowMock.ts"
|
|
||||||
},
|
},
|
||||||
"modulePathIgnorePatterns": [
|
"modulePathIgnorePatterns": [
|
||||||
"<rootDir>/dist",
|
"<rootDir>/dist",
|
||||||
@ -196,10 +195,11 @@
|
|||||||
"@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": "1.5.0",
|
"@ogre-tools/injectable": "2.0.0",
|
||||||
"@ogre-tools/injectable-react": "1.5.2",
|
"@ogre-tools/injectable-react": "2.0.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",
|
||||||
"abort-controller": "^3.0.0",
|
"abort-controller": "^3.0.0",
|
||||||
"auto-bind": "^4.0.0",
|
"auto-bind": "^4.0.0",
|
||||||
"autobind-decorator": "^2.4.0",
|
"autobind-decorator": "^2.4.0",
|
||||||
@ -214,7 +214,7 @@
|
|||||||
"filehound": "^1.17.5",
|
"filehound": "^1.17.5",
|
||||||
"fs-extra": "^9.0.1",
|
"fs-extra": "^9.0.1",
|
||||||
"glob-to-regexp": "^0.4.1",
|
"glob-to-regexp": "^0.4.1",
|
||||||
"got": "^11.8.2",
|
"got": "^11.8.3",
|
||||||
"grapheme-splitter": "^1.0.4",
|
"grapheme-splitter": "^1.0.4",
|
||||||
"handlebars": "^4.7.7",
|
"handlebars": "^4.7.7",
|
||||||
"http-proxy": "^1.18.1",
|
"http-proxy": "^1.18.1",
|
||||||
@ -265,7 +265,7 @@
|
|||||||
"ws": "^7.5.5"
|
"ws": "^7.5.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@async-fn/jest": "^1.5.0",
|
"@async-fn/jest": "1.5.3",
|
||||||
"@material-ui/core": "^4.12.3",
|
"@material-ui/core": "^4.12.3",
|
||||||
"@material-ui/icons": "^4.11.2",
|
"@material-ui/icons": "^4.11.2",
|
||||||
"@material-ui/lab": "^4.0.0-alpha.60",
|
"@material-ui/lab": "^4.0.0-alpha.60",
|
||||||
@ -333,7 +333,7 @@
|
|||||||
"concurrently": "^5.3.0",
|
"concurrently": "^5.3.0",
|
||||||
"css-loader": "^5.2.7",
|
"css-loader": "^5.2.7",
|
||||||
"deepdash": "^5.3.9",
|
"deepdash": "^5.3.9",
|
||||||
"dompurify": "^2.3.3",
|
"dompurify": "^2.3.4",
|
||||||
"electron": "^13.6.1",
|
"electron": "^13.6.1",
|
||||||
"electron-builder": "^22.14.5",
|
"electron-builder": "^22.14.5",
|
||||||
"electron-notarize": "^0.3.0",
|
"electron-notarize": "^0.3.0",
|
||||||
@ -341,6 +341,7 @@
|
|||||||
"esbuild-loader": "^2.16.0",
|
"esbuild-loader": "^2.16.0",
|
||||||
"eslint": "^7.32.0",
|
"eslint": "^7.32.0",
|
||||||
"eslint-plugin-header": "^3.1.1",
|
"eslint-plugin-header": "^3.1.1",
|
||||||
|
"eslint-plugin-import": "^2.25.3",
|
||||||
"eslint-plugin-react": "^7.27.1",
|
"eslint-plugin-react": "^7.27.1",
|
||||||
"eslint-plugin-react-hooks": "^4.3.0",
|
"eslint-plugin-react-hooks": "^4.3.0",
|
||||||
"eslint-plugin-unused-imports": "^1.1.5",
|
"eslint-plugin-unused-imports": "^1.1.5",
|
||||||
@ -361,8 +362,7 @@
|
|||||||
"nodemon": "^2.0.14",
|
"nodemon": "^2.0.14",
|
||||||
"playwright": "^1.17.1",
|
"playwright": "^1.17.1",
|
||||||
"postcss": "^8.4.5",
|
"postcss": "^8.4.5",
|
||||||
"postcss-loader": "4.3.0",
|
"postcss-loader": "^4.3.0",
|
||||||
"postinstall-postinstall": "^2.1.0",
|
|
||||||
"progress-bar-webpack-plugin": "^2.1.0",
|
"progress-bar-webpack-plugin": "^2.1.0",
|
||||||
"randomcolor": "^0.6.2",
|
"randomcolor": "^0.6.2",
|
||||||
"raw-loader": "^4.0.2",
|
"raw-loader": "^4.0.2",
|
||||||
@ -373,11 +373,11 @@
|
|||||||
"react-select-event": "^5.1.0",
|
"react-select-event": "^5.1.0",
|
||||||
"react-table": "^7.7.0",
|
"react-table": "^7.7.0",
|
||||||
"react-window": "^1.8.6",
|
"react-window": "^1.8.6",
|
||||||
"sass": "^1.44.0",
|
"sass": "^1.45.1",
|
||||||
"sass-loader": "^8.0.2",
|
"sass-loader": "^10.2.0",
|
||||||
"sharp": "^0.29.3",
|
"sharp": "^0.29.3",
|
||||||
"style-loader": "^2.0.0",
|
"style-loader": "^2.0.0",
|
||||||
"tailwindcss": "^2.2.19",
|
"tailwindcss": "^3.0.7",
|
||||||
"ts-jest": "26.5.6",
|
"ts-jest": "26.5.6",
|
||||||
"ts-loader": "^7.0.5",
|
"ts-loader": "^7.0.5",
|
||||||
"ts-node": "^10.4.0",
|
"ts-node": "^10.4.0",
|
||||||
|
|||||||
@ -20,9 +20,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { anyObject } from "jest-mock-extended";
|
import { anyObject } from "jest-mock-extended";
|
||||||
|
import { merge } from "lodash";
|
||||||
import mockFs from "mock-fs";
|
import mockFs from "mock-fs";
|
||||||
import logger from "../../main/logger";
|
import logger from "../../main/logger";
|
||||||
import { AppPaths } from "../app-paths";
|
import { AppPaths } from "../app-paths";
|
||||||
|
import type { CatalogEntity, CatalogEntityData, CatalogEntityKindData } from "../catalog";
|
||||||
import { ClusterStore } from "../cluster-store";
|
import { ClusterStore } from "../cluster-store";
|
||||||
import { HotbarStore } from "../hotbar-store";
|
import { HotbarStore } from "../hotbar-store";
|
||||||
|
|
||||||
@ -54,68 +56,58 @@ jest.mock("../../main/catalog/catalog-entity-registry", () => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const testCluster = {
|
function getMockCatalogEntity(data: Partial<CatalogEntityData> & CatalogEntityKindData): CatalogEntity {
|
||||||
uid: "test",
|
return merge(data, {
|
||||||
name: "test",
|
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",
|
apiVersion: "v1",
|
||||||
kind: "Cluster",
|
kind: "Cluster",
|
||||||
status: {
|
status: {
|
||||||
phase: "Running",
|
phase: "Running",
|
||||||
},
|
},
|
||||||
spec: {},
|
|
||||||
getName: jest.fn(),
|
|
||||||
getId: jest.fn(),
|
|
||||||
onDetailsOpen: jest.fn(),
|
|
||||||
onContextMenuOpen: jest.fn(),
|
|
||||||
onSettingsOpen: jest.fn(),
|
|
||||||
metadata: {
|
metadata: {
|
||||||
uid: "test",
|
uid: "test",
|
||||||
name: "test",
|
name: "test",
|
||||||
labels: {},
|
labels: {},
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
const minikubeCluster = {
|
const minikubeCluster = getMockCatalogEntity({
|
||||||
uid: "minikube",
|
|
||||||
name: "minikube",
|
|
||||||
apiVersion: "v1",
|
apiVersion: "v1",
|
||||||
kind: "Cluster",
|
kind: "Cluster",
|
||||||
status: {
|
status: {
|
||||||
phase: "Running",
|
phase: "Running",
|
||||||
},
|
},
|
||||||
spec: {},
|
|
||||||
getName: jest.fn(),
|
|
||||||
getId: jest.fn(),
|
|
||||||
onDetailsOpen: jest.fn(),
|
|
||||||
onContextMenuOpen: jest.fn(),
|
|
||||||
onSettingsOpen: jest.fn(),
|
|
||||||
metadata: {
|
metadata: {
|
||||||
uid: "minikube",
|
uid: "minikube",
|
||||||
name: "minikube",
|
name: "minikube",
|
||||||
labels: {},
|
labels: {},
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
const awsCluster = {
|
const awsCluster = getMockCatalogEntity({
|
||||||
uid: "aws",
|
|
||||||
name: "aws",
|
|
||||||
apiVersion: "v1",
|
apiVersion: "v1",
|
||||||
kind: "Cluster",
|
kind: "Cluster",
|
||||||
status: {
|
status: {
|
||||||
phase: "Running",
|
phase: "Running",
|
||||||
},
|
},
|
||||||
spec: {},
|
|
||||||
getName: jest.fn(),
|
|
||||||
getId: jest.fn(),
|
|
||||||
onDetailsOpen: jest.fn(),
|
|
||||||
onContextMenuOpen: jest.fn(),
|
|
||||||
onSettingsOpen: jest.fn(),
|
|
||||||
metadata: {
|
metadata: {
|
||||||
uid: "aws",
|
uid: "aws",
|
||||||
name: "aws",
|
name: "aws",
|
||||||
labels: {},
|
labels: {},
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
jest.mock("electron", () => ({
|
jest.mock("electron", () => ({
|
||||||
app: {
|
app: {
|
||||||
|
|||||||
@ -42,9 +42,9 @@ import { Console } from "console";
|
|||||||
import { SemVer } from "semver";
|
import { SemVer } from "semver";
|
||||||
import electron from "electron";
|
import electron from "electron";
|
||||||
import { stdout, stderr } from "process";
|
import { stdout, stderr } from "process";
|
||||||
import { ThemeStore } from "../../renderer/theme.store";
|
|
||||||
import type { ClusterStoreModel } from "../cluster-store";
|
import type { ClusterStoreModel } from "../cluster-store";
|
||||||
import { AppPaths } from "../app-paths";
|
import { AppPaths } from "../app-paths";
|
||||||
|
import { defaultTheme } from "../vars";
|
||||||
|
|
||||||
console = new Console(stdout, stderr);
|
console = new Console(stdout, stderr);
|
||||||
AppPaths.init();
|
AppPaths.init();
|
||||||
@ -75,7 +75,7 @@ describe("user store tests", () => {
|
|||||||
us.httpsProxy = "abcd://defg";
|
us.httpsProxy = "abcd://defg";
|
||||||
|
|
||||||
expect(us.httpsProxy).toBe("abcd://defg");
|
expect(us.httpsProxy).toBe("abcd://defg");
|
||||||
expect(us.colorTheme).toBe(ThemeStore.defaultTheme);
|
expect(us.colorTheme).toBe(defaultTheme);
|
||||||
|
|
||||||
us.colorTheme = "light";
|
us.colorTheme = "light";
|
||||||
expect(us.colorTheme).toBe("light");
|
expect(us.colorTheme).toBe("light");
|
||||||
@ -86,7 +86,7 @@ describe("user store tests", () => {
|
|||||||
|
|
||||||
us.colorTheme = "some other theme";
|
us.colorTheme = "some other theme";
|
||||||
us.resetTheme();
|
us.resetTheme();
|
||||||
expect(us.colorTheme).toBe(ThemeStore.defaultTheme);
|
expect(us.colorTheme).toBe(defaultTheme);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("correctly calculates if the last seen version is an old release", () => {
|
it("correctly calculates if the last seen version is an old release", () => {
|
||||||
|
|||||||
@ -23,7 +23,8 @@ import { app, ipcMain, ipcRenderer } from "electron";
|
|||||||
import { observable, when } from "mobx";
|
import { observable, when } from "mobx";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import logger from "./logger";
|
import logger from "./logger";
|
||||||
import { fromEntries, toJS } from "./utils";
|
import { fromEntries } from "./utils/objects";
|
||||||
|
import { toJS } from "./utils/toJS";
|
||||||
import { isWindows } from "./vars";
|
import { isWindows } from "./vars";
|
||||||
|
|
||||||
export type PathName = Parameters<typeof app["getPath"]>[0];
|
export type PathName = Parameters<typeof app["getPath"]>[0];
|
||||||
|
|||||||
@ -20,11 +20,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { catalogCategoryRegistry } from "../catalog/catalog-category-registry";
|
import { catalogCategoryRegistry } from "../catalog/catalog-category-registry";
|
||||||
import { CatalogEntity, CatalogEntityActionContext, CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus } 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";
|
import { ClusterStore } from "../cluster-store";
|
||||||
import { broadcastMessage, requestMain } from "../ipc";
|
import { broadcastMessage, requestMain } from "../ipc";
|
||||||
import { CatalogCategory, CatalogCategorySpec } from "../catalog";
|
|
||||||
import { app } from "electron";
|
import { app } 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";
|
||||||
|
|||||||
@ -38,14 +38,44 @@ export type CatalogEntityConstructor<Entity extends CatalogEntity> = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
export interface CatalogCategoryVersion<Entity extends CatalogEntity> {
|
export interface CatalogCategoryVersion<Entity extends CatalogEntity> {
|
||||||
|
/**
|
||||||
|
* The specific version that the associated constructor is for. This MUST be
|
||||||
|
* a DNS label and SHOULD be of the form `vN`, `vNalphaY`, or `vNbetaY` where
|
||||||
|
* `N` and `Y` are both integers greater than 0.
|
||||||
|
*
|
||||||
|
* Examples: The following are valid values for this field.
|
||||||
|
* - `v1`
|
||||||
|
* - `v1beta1`
|
||||||
|
* - `v1alpha2`
|
||||||
|
* - `v3beta2`
|
||||||
|
*/
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The constructor for the entities.
|
||||||
|
*/
|
||||||
entityClass: CatalogEntityConstructor<Entity>;
|
entityClass: CatalogEntityConstructor<Entity>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CatalogCategorySpec {
|
export interface CatalogCategorySpec {
|
||||||
|
/**
|
||||||
|
* The grouping for for the category. This MUST be a DNS label.
|
||||||
|
*/
|
||||||
group: string;
|
group: string;
|
||||||
|
/**
|
||||||
|
* The specific versions of the constructors.
|
||||||
|
*
|
||||||
|
* NOTE: the field `.apiVersion` after construction MUST match `{.group}/{.versions.[] | .name}`.
|
||||||
|
* For example, if `group = "entity.k8slens.dev"` and there is an entry in `.versions` with
|
||||||
|
* `name = "v1alpha1"` then the resulting `.apiVersion` MUST be `entity.k8slens.dev/v1alpha1`
|
||||||
|
*/
|
||||||
versions: CatalogCategoryVersion<CatalogEntity>[];
|
versions: CatalogCategoryVersion<CatalogEntity>[];
|
||||||
names: {
|
names: {
|
||||||
|
/**
|
||||||
|
* The kind of entity that this category is for. This value MUST be a DNS
|
||||||
|
* label and MUST be equal to the `kind` fields that are produced by the
|
||||||
|
* `.versions.[] | .entityClass` fields.
|
||||||
|
*/
|
||||||
kind: string;
|
kind: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -114,6 +144,7 @@ export abstract class CatalogCategory extends (EventEmitter as new () => TypedEm
|
|||||||
export interface CatalogEntityMetadata {
|
export interface CatalogEntityMetadata {
|
||||||
uid: string;
|
uid: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
shortName?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
source?: string;
|
source?: string;
|
||||||
labels: Record<string, string>;
|
labels: Record<string, string>;
|
||||||
@ -211,7 +242,14 @@ export abstract class CatalogEntity<
|
|||||||
Status extends CatalogEntityStatus = CatalogEntityStatus,
|
Status extends CatalogEntityStatus = CatalogEntityStatus,
|
||||||
Spec extends CatalogEntitySpec = CatalogEntitySpec,
|
Spec extends CatalogEntitySpec = CatalogEntitySpec,
|
||||||
> implements CatalogEntityKindData {
|
> implements CatalogEntityKindData {
|
||||||
|
/**
|
||||||
|
* The group and version of this class.
|
||||||
|
*/
|
||||||
public abstract readonly apiVersion: string;
|
public abstract readonly apiVersion: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A DNS label name of the entity.
|
||||||
|
*/
|
||||||
public abstract readonly kind: string;
|
public abstract readonly kind: string;
|
||||||
|
|
||||||
@observable metadata: Metadata;
|
@observable metadata: Metadata;
|
||||||
@ -225,14 +263,35 @@ export abstract class CatalogEntity<
|
|||||||
this.spec = data.spec;
|
this.spec = data.spec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the UID of this entity
|
||||||
|
*/
|
||||||
public getId(): string {
|
public getId(): string {
|
||||||
return this.metadata.uid;
|
return this.metadata.uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the name of this entity
|
||||||
|
*/
|
||||||
public getName(): string {
|
public getName(): string {
|
||||||
return this.metadata.name;
|
return this.metadata.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the specified source of this entity, defaulting to `"unknown"` if not
|
||||||
|
* provided
|
||||||
|
*/
|
||||||
|
public getSource(): string {
|
||||||
|
return this.metadata.source ?? "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get if this entity is enabled.
|
||||||
|
*/
|
||||||
|
public isEnabled(): boolean {
|
||||||
|
return this.status.enabled ?? true;
|
||||||
|
}
|
||||||
|
|
||||||
public abstract onRun?(context: CatalogEntityActionContext): void | Promise<void>;
|
public abstract onRun?(context: CatalogEntityActionContext): void | Promise<void>;
|
||||||
public abstract onContextMenuOpen(context: CatalogEntityContextMenuContext): void | Promise<void>;
|
public abstract onContextMenuOpen(context: CatalogEntityContextMenuContext): void | Promise<void>;
|
||||||
public abstract onSettingsOpen(context: CatalogEntitySettingsContext): void | Promise<void>;
|
public abstract onSettingsOpen(context: CatalogEntitySettingsContext): void | Promise<void>;
|
||||||
|
|||||||
@ -18,10 +18,4 @@
|
|||||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
export const BundledExtensionsLoaded = "extension-loader:bundled-extensions-loaded";
|
||||||
module.exports = {
|
|
||||||
plugins: [
|
|
||||||
require("tailwindcss/nesting"),
|
|
||||||
require("tailwindcss"),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
@ -27,3 +27,4 @@ export * from "./update-available.ipc";
|
|||||||
export * from "./cluster.ipc";
|
export * from "./cluster.ipc";
|
||||||
export * from "./type-enforced-ipc";
|
export * from "./type-enforced-ipc";
|
||||||
export * from "./hotbar";
|
export * from "./hotbar";
|
||||||
|
export * from "./extension-loader.ipc";
|
||||||
|
|||||||
@ -30,7 +30,15 @@ import { ClusterFrameInfo, clusterFrameMap } from "../cluster-frames";
|
|||||||
import type { Disposer } from "../utils";
|
import type { Disposer } from "../utils";
|
||||||
import type remote from "@electron/remote";
|
import type remote from "@electron/remote";
|
||||||
|
|
||||||
const electronRemote = ipcMain ? null : require("@electron/remote");
|
const electronRemote = (() => {
|
||||||
|
if (ipcRenderer) {
|
||||||
|
try {
|
||||||
|
return require("@electron/remote");
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
})();
|
||||||
|
|
||||||
const subFramesChannel = "ipc:get-sub-frames";
|
const subFramesChannel = "ipc:get-sub-frames";
|
||||||
|
|
||||||
|
|||||||
@ -19,10 +19,10 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CustomResourceDefinition } from "../endpoints";
|
import { CustomResourceDefinition, CustomResourceDefinitionSpec } from "../endpoints";
|
||||||
|
|
||||||
describe("Crds", () => {
|
describe("Crds", () => {
|
||||||
describe("getVersion", () => {
|
describe("getVersion()", () => {
|
||||||
it("should throw if none of the versions are served", () => {
|
it("should throw if none of the versions are served", () => {
|
||||||
const crd = new CustomResourceDefinition({
|
const crd = new CustomResourceDefinition({
|
||||||
apiVersion: "apiextensions.k8s.io/v1",
|
apiVersion: "apiextensions.k8s.io/v1",
|
||||||
@ -136,7 +136,7 @@ describe("Crds", () => {
|
|||||||
expect(crd.getVersion()).toBe("123");
|
expect(crd.getVersion()).toBe("123");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should get the version name from the version field", () => {
|
it("should get the version name from the version field, ignoring versions on v1beta", () => {
|
||||||
const crd = new CustomResourceDefinition({
|
const crd = new CustomResourceDefinition({
|
||||||
apiVersion: "apiextensions.k8s.io/v1beta1",
|
apiVersion: "apiextensions.k8s.io/v1beta1",
|
||||||
kind: "CustomResourceDefinition",
|
kind: "CustomResourceDefinition",
|
||||||
@ -147,7 +147,14 @@ describe("Crds", () => {
|
|||||||
},
|
},
|
||||||
spec: {
|
spec: {
|
||||||
version: "abc",
|
version: "abc",
|
||||||
},
|
versions: [
|
||||||
|
{
|
||||||
|
name: "foobar",
|
||||||
|
served: true,
|
||||||
|
storage: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as CustomResourceDefinitionSpec,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(crd.getVersion()).toBe("abc");
|
expect(crd.getVersion()).toBe("abc");
|
||||||
|
|||||||
@ -48,34 +48,36 @@ export interface CRDVersion {
|
|||||||
additionalPrinterColumns?: AdditionalPrinterColumnsV1[];
|
additionalPrinterColumns?: AdditionalPrinterColumnsV1[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CustomResourceDefinition {
|
export interface CustomResourceDefinitionSpec {
|
||||||
spec: {
|
group: string;
|
||||||
group: string;
|
/**
|
||||||
/**
|
* @deprecated for apiextensions.k8s.io/v1 but used in v1beta1
|
||||||
* @deprecated for apiextensions.k8s.io/v1 but used previously
|
*/
|
||||||
*/
|
version?: string;
|
||||||
version?: string;
|
names: {
|
||||||
names: {
|
plural: string;
|
||||||
plural: string;
|
singular: string;
|
||||||
singular: string;
|
kind: string;
|
||||||
kind: string;
|
listKind: string;
|
||||||
listKind: string;
|
|
||||||
};
|
|
||||||
scope: "Namespaced" | "Cluster" | string;
|
|
||||||
/**
|
|
||||||
* @deprecated for apiextensions.k8s.io/v1 but used previously
|
|
||||||
*/
|
|
||||||
validation?: object;
|
|
||||||
versions?: CRDVersion[];
|
|
||||||
conversion: {
|
|
||||||
strategy?: string;
|
|
||||||
webhook?: any;
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* @deprecated for apiextensions.k8s.io/v1 but used previously
|
|
||||||
*/
|
|
||||||
additionalPrinterColumns?: AdditionalPrinterColumnsV1Beta[];
|
|
||||||
};
|
};
|
||||||
|
scope: "Namespaced" | "Cluster";
|
||||||
|
/**
|
||||||
|
* @deprecated for apiextensions.k8s.io/v1 but used in v1beta1
|
||||||
|
*/
|
||||||
|
validation?: object;
|
||||||
|
versions?: CRDVersion[];
|
||||||
|
conversion: {
|
||||||
|
strategy?: string;
|
||||||
|
webhook?: any;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* @deprecated for apiextensions.k8s.io/v1 but used in v1beta1
|
||||||
|
*/
|
||||||
|
additionalPrinterColumns?: AdditionalPrinterColumnsV1Beta[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CustomResourceDefinition {
|
||||||
|
spec: CustomResourceDefinitionSpec;
|
||||||
status: {
|
status: {
|
||||||
conditions: {
|
conditions: {
|
||||||
lastTransitionTime: string;
|
lastTransitionTime: string;
|
||||||
@ -150,27 +152,30 @@ export class CustomResourceDefinition extends KubeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getPreferedVersion(): CRDVersion {
|
getPreferedVersion(): CRDVersion {
|
||||||
// Prefer the modern `versions` over the legacy `version`
|
const { apiVersion } = this;
|
||||||
if (this.spec.versions) {
|
|
||||||
for (const version of this.spec.versions) {
|
|
||||||
if (version.storage) {
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (this.spec.version) {
|
|
||||||
const { additionalPrinterColumns: apc } = this.spec;
|
|
||||||
const additionalPrinterColumns = apc?.map(({ JSONPath, ...apc }) => ({ ...apc, jsonPath: JSONPath }));
|
|
||||||
|
|
||||||
return {
|
switch (apiVersion) {
|
||||||
name: this.spec.version,
|
case "apiextensions.k8s.io/v1":
|
||||||
served: true,
|
for (const version of this.spec.versions) {
|
||||||
storage: true,
|
if (version.storage) {
|
||||||
schema: this.spec.validation,
|
return version;
|
||||||
additionalPrinterColumns,
|
}
|
||||||
};
|
}
|
||||||
|
break;
|
||||||
|
case "apiextensions.k8s.io/v1beta1":
|
||||||
|
const { additionalPrinterColumns: apc } = this.spec;
|
||||||
|
const additionalPrinterColumns = apc?.map(({ JSONPath, ...apc }) => ({ ...apc, jsonPath: JSONPath }));
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: this.spec.version,
|
||||||
|
served: true,
|
||||||
|
storage: true,
|
||||||
|
schema: this.spec.validation,
|
||||||
|
additionalPrinterColumns,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`Failed to find a version for CustomResourceDefinition ${this.metadata.name}`);
|
throw new Error(`Unknown apiVersion=${apiVersion}: Failed to find a version for CustomResourceDefinition ${this.metadata.name}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
getVersion() {
|
getVersion() {
|
||||||
@ -197,7 +202,7 @@ export class CustomResourceDefinition extends KubeObject {
|
|||||||
const columns = this.getPreferedVersion().additionalPrinterColumns ?? [];
|
const columns = this.getPreferedVersion().additionalPrinterColumns ?? [];
|
||||||
|
|
||||||
return columns
|
return columns
|
||||||
.filter(column => column.name != "Age" && (ignorePriority || !column.priority));
|
.filter(column => column.name.toLowerCase() != "age" && (ignorePriority || !column.priority));
|
||||||
}
|
}
|
||||||
|
|
||||||
getValidation() {
|
getValidation() {
|
||||||
|
|||||||
@ -34,6 +34,9 @@ import type { IKubeWatchEvent } from "./kube-watch-api";
|
|||||||
import { KubeJsonApi, KubeJsonApiData } from "./kube-json-api";
|
import { KubeJsonApi, KubeJsonApiData } from "./kube-json-api";
|
||||||
import { noop } from "../utils";
|
import { noop } from "../utils";
|
||||||
import type { RequestInit } from "node-fetch";
|
import type { RequestInit } from "node-fetch";
|
||||||
|
|
||||||
|
// BUG: https://github.com/mysticatea/abort-controller/pull/22
|
||||||
|
// eslint-disable-next-line import/no-named-as-default
|
||||||
import AbortController from "abort-controller";
|
import AbortController from "abort-controller";
|
||||||
import { Agent, AgentOptions } from "https";
|
import { Agent, AgentOptions } from "https";
|
||||||
import type { Patch } from "rfc6902";
|
import type { Patch } from "rfc6902";
|
||||||
|
|||||||
@ -30,6 +30,9 @@ import { ensureObjectSelfLink, IKubeApiQueryParams, KubeApi } from "./kube-api";
|
|||||||
import { parseKubeApi } from "./kube-api-parse";
|
import { parseKubeApi } from "./kube-api-parse";
|
||||||
import type { KubeJsonApiData } from "./kube-json-api";
|
import type { KubeJsonApiData } from "./kube-json-api";
|
||||||
import type { RequestInit } from "node-fetch";
|
import type { RequestInit } from "node-fetch";
|
||||||
|
|
||||||
|
// BUG: https://github.com/mysticatea/abort-controller/pull/22
|
||||||
|
// eslint-disable-next-line import/no-named-as-default
|
||||||
import AbortController from "abort-controller";
|
import AbortController from "abort-controller";
|
||||||
import type { Patch } from "rfc6902";
|
import type { Patch } from "rfc6902";
|
||||||
|
|
||||||
|
|||||||
@ -22,11 +22,11 @@
|
|||||||
import moment from "moment-timezone";
|
import moment from "moment-timezone";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import os from "os";
|
import os from "os";
|
||||||
import { ThemeStore } from "../../renderer/theme.store";
|
|
||||||
import { getAppVersion, ObservableToggleSet } from "../utils";
|
import { getAppVersion, ObservableToggleSet } from "../utils";
|
||||||
import type { editor } from "monaco-editor";
|
import type { editor } from "monaco-editor";
|
||||||
import merge from "lodash/merge";
|
import merge from "lodash/merge";
|
||||||
import { SemVer } from "semver";
|
import { SemVer } from "semver";
|
||||||
|
import { defaultTheme } from "../vars";
|
||||||
|
|
||||||
export interface KubeconfigSyncEntry extends KubeconfigSyncValue {
|
export interface KubeconfigSyncEntry extends KubeconfigSyncValue {
|
||||||
filePath: string;
|
filePath: string;
|
||||||
@ -72,10 +72,10 @@ const shell: PreferenceDescription<string | undefined> = {
|
|||||||
|
|
||||||
const colorTheme: PreferenceDescription<string> = {
|
const colorTheme: PreferenceDescription<string> = {
|
||||||
fromStore(val) {
|
fromStore(val) {
|
||||||
return val || ThemeStore.defaultTheme;
|
return val || defaultTheme;
|
||||||
},
|
},
|
||||||
toStore(val) {
|
toStore(val) {
|
||||||
if (!val || val === ThemeStore.defaultTheme) {
|
if (!val || val === defaultTheme) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { array } from "../utils";
|
import * as array from "../utils/array";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A strict N-tuple of type T
|
* A strict N-tuple of type T
|
||||||
|
|||||||
@ -41,6 +41,7 @@ export const isIntegrationTesting = process.argv.includes(integrationTestingArg)
|
|||||||
export const productName = packageInfo.productName;
|
export const productName = packageInfo.productName;
|
||||||
export const appName = `${packageInfo.productName}${isDevelopment ? "Dev" : ""}`;
|
export const appName = `${packageInfo.productName}${isDevelopment ? "Dev" : ""}`;
|
||||||
export const publicPath = "/build/" as string;
|
export const publicPath = "/build/" as string;
|
||||||
|
export const defaultTheme = "lens-dark" as string;
|
||||||
|
|
||||||
// Webpack build paths
|
// Webpack build paths
|
||||||
export const contextDir = process.cwd();
|
export const contextDir = process.cwd();
|
||||||
|
|||||||
@ -18,14 +18,12 @@
|
|||||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
import type { Injectable } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import { lifecycleEnum } from "@ogre-tools/injectable";
|
|
||||||
import { ExtensionLoader } from "./extension-loader";
|
import { ExtensionLoader } from "./extension-loader";
|
||||||
|
|
||||||
const extensionLoaderInjectable: Injectable<ExtensionLoader> = {
|
const extensionLoaderInjectable = getInjectable({
|
||||||
getDependencies: () => ({}),
|
|
||||||
instantiate: () => new ExtensionLoader(),
|
instantiate: () => new ExtensionLoader(),
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
};
|
});
|
||||||
|
|
||||||
export default extensionLoaderInjectable;
|
export default extensionLoaderInjectable;
|
||||||
|
|||||||
@ -255,17 +255,15 @@ export class ExtensionLoader {
|
|||||||
|
|
||||||
loadOnClusterManagerRenderer() {
|
loadOnClusterManagerRenderer() {
|
||||||
logger.debug(`${logModule}: load on main renderer (cluster manager)`);
|
logger.debug(`${logModule}: load on main renderer (cluster manager)`);
|
||||||
this.autoInitExtensions(async (extension: LensRendererExtension) => {
|
|
||||||
|
return this.autoInitExtensions(async (extension: LensRendererExtension) => {
|
||||||
const removeItems = [
|
const removeItems = [
|
||||||
registries.GlobalPageRegistry.getInstance().add(extension.globalPages, extension),
|
registries.GlobalPageRegistry.getInstance().add(extension.globalPages, extension),
|
||||||
registries.AppPreferenceRegistry.getInstance().add(extension.appPreferences),
|
registries.AppPreferenceRegistry.getInstance().add(extension.appPreferences),
|
||||||
registries.EntitySettingRegistry.getInstance().add(extension.entitySettings),
|
registries.EntitySettingRegistry.getInstance().add(extension.entitySettings),
|
||||||
registries.StatusBarRegistry.getInstance().add(extension.statusBarItems),
|
registries.StatusBarRegistry.getInstance().add(extension.statusBarItems),
|
||||||
registries.CommandRegistry.getInstance().add(extension.commands),
|
registries.CommandRegistry.getInstance().add(extension.commands),
|
||||||
registries.WelcomeMenuRegistry.getInstance().add(extension.welcomeMenus),
|
|
||||||
registries.WelcomeBannerRegistry.getInstance().add(extension.welcomeBanners),
|
|
||||||
registries.CatalogEntityDetailRegistry.getInstance().add(extension.catalogEntityDetailItems),
|
registries.CatalogEntityDetailRegistry.getInstance().add(extension.catalogEntityDetailItems),
|
||||||
registries.TopBarRegistry.getInstance().add(extension.topBarItems),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
this.events.on("remove", (removedExtension: LensRendererExtension) => {
|
this.events.on("remove", (removedExtension: LensRendererExtension) => {
|
||||||
@ -311,7 +309,9 @@ export class ExtensionLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected autoInitExtensions(register: (ext: LensExtension) => Promise<Disposer[]>) {
|
protected autoInitExtensions(register: (ext: LensExtension) => Promise<Disposer[]>) {
|
||||||
return reaction(() => this.toJSON(), installedExtensions => {
|
const loadingExtensions: { isBundled: boolean, loaded: Promise<void> }[] = [];
|
||||||
|
|
||||||
|
reaction(() => this.toJSON(), installedExtensions => {
|
||||||
for (const [extId, extension] of installedExtensions) {
|
for (const [extId, extension] of installedExtensions) {
|
||||||
const alreadyInit = this.instances.has(extId) || this.nonInstancesByName.has(extension.manifest.name);
|
const alreadyInit = this.instances.has(extId) || this.nonInstancesByName.has(extension.manifest.name);
|
||||||
|
|
||||||
@ -326,7 +326,14 @@ export class ExtensionLoader {
|
|||||||
|
|
||||||
const instance = new LensExtensionClass(extension);
|
const instance = new LensExtensionClass(extension);
|
||||||
|
|
||||||
instance.enable(register);
|
const loaded = instance.enable(register).catch((err) => {
|
||||||
|
logger.error(`${logModule}: failed to enable`, { ext: extension, err });
|
||||||
|
});
|
||||||
|
|
||||||
|
loadingExtensions.push({
|
||||||
|
isBundled: extension.isBundled,
|
||||||
|
loaded,
|
||||||
|
});
|
||||||
this.instances.set(extId, instance);
|
this.instances.set(extId, instance);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(`${logModule}: activation extension error`, { ext: extension, err });
|
logger.error(`${logModule}: activation extension error`, { ext: extension, err });
|
||||||
@ -338,6 +345,8 @@ export class ExtensionLoader {
|
|||||||
}, {
|
}, {
|
||||||
fireImmediately: true,
|
fireImmediately: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return loadingExtensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected requireExtension(extension: InstalledExtension): LensExtensionConstructor | null {
|
protected requireExtension(extension: InstalledExtension): LensExtensionConstructor | null {
|
||||||
|
|||||||
@ -18,24 +18,18 @@
|
|||||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
import { Injectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import { computed, IComputedValue } from "mobx";
|
import { computed } from "mobx";
|
||||||
import type { LensExtension } from "./lens-extension";
|
|
||||||
import type { ExtensionLoader } from "./extension-loader";
|
|
||||||
import extensionLoaderInjectable from "./extension-loader/extension-loader.injectable";
|
import extensionLoaderInjectable from "./extension-loader/extension-loader.injectable";
|
||||||
|
|
||||||
const extensionsInjectable: Injectable<
|
const extensionsInjectable = getInjectable({
|
||||||
IComputedValue<LensExtension[]>,
|
instantiate: (di) => {
|
||||||
{ extensionLoader: ExtensionLoader }
|
const extensionLoader = di.inject(extensionLoaderInjectable);
|
||||||
> = {
|
|
||||||
getDependencies: di => ({
|
return computed(() => extensionLoader.enabledExtensionInstances);
|
||||||
extensionLoader: di.inject(extensionLoaderInjectable),
|
},
|
||||||
}),
|
|
||||||
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
instantiate: ({ extensionLoader }) =>
|
|
||||||
computed(() => extensionLoader.enabledExtensionInstances),
|
|
||||||
};
|
|
||||||
|
|
||||||
export default extensionsInjectable;
|
export default extensionsInjectable;
|
||||||
|
|||||||
@ -25,9 +25,10 @@ import { catalogEntityRegistry } from "../main/catalog";
|
|||||||
import type { CatalogEntity } from "../common/catalog";
|
import type { CatalogEntity } from "../common/catalog";
|
||||||
import type { IObservableArray } from "mobx";
|
import type { IObservableArray } from "mobx";
|
||||||
import type { MenuRegistration } from "../main/menu/menu-registration";
|
import type { MenuRegistration } from "../main/menu/menu-registration";
|
||||||
|
import type { TrayMenuRegistration } from "../main/tray/tray-menu-registration";
|
||||||
export class LensMainExtension extends LensExtension {
|
export class LensMainExtension extends LensExtension {
|
||||||
appMenus: MenuRegistration[] = [];
|
appMenus: MenuRegistration[] = [];
|
||||||
|
trayMenus: TrayMenuRegistration[] = [];
|
||||||
|
|
||||||
async navigate(pageId?: string, params?: Record<string, any>, frameId?: number) {
|
async navigate(pageId?: string, params?: Record<string, any>, frameId?: number) {
|
||||||
return WindowManager.getInstance().navigateExtension(this.id, pageId, params, frameId);
|
return WindowManager.getInstance().navigateExtension(this.id, pageId, params, frameId);
|
||||||
|
|||||||
@ -26,7 +26,10 @@ import type { CatalogEntity } from "../common/catalog";
|
|||||||
import type { Disposer } from "../common/utils";
|
import type { Disposer } from "../common/utils";
|
||||||
import { catalogEntityRegistry, EntityFilter } from "../renderer/api/catalog-entity-registry";
|
import { catalogEntityRegistry, EntityFilter } from "../renderer/api/catalog-entity-registry";
|
||||||
import { catalogCategoryRegistry, CategoryFilter } from "../renderer/api/catalog-category-registry";
|
import { catalogCategoryRegistry, CategoryFilter } from "../renderer/api/catalog-category-registry";
|
||||||
|
import type { TopBarRegistration } from "../renderer/components/layout/top-bar/top-bar-registration";
|
||||||
import type { KubernetesCluster } from "../common/catalog-entities";
|
import type { KubernetesCluster } from "../common/catalog-entities";
|
||||||
|
import type { WelcomeMenuRegistration } from "../renderer/components/+welcome/welcome-menu-items/welcome-menu-registration";
|
||||||
|
import type { WelcomeBannerRegistration } from "../renderer/components/+welcome/welcome-banner-items/welcome-banner-registration";
|
||||||
|
|
||||||
export class LensRendererExtension extends LensExtension {
|
export class LensRendererExtension extends LensExtension {
|
||||||
globalPages: registries.PageRegistration[] = [];
|
globalPages: registries.PageRegistration[] = [];
|
||||||
@ -40,10 +43,10 @@ export class LensRendererExtension extends LensExtension {
|
|||||||
kubeObjectMenuItems: registries.KubeObjectMenuRegistration[] = [];
|
kubeObjectMenuItems: registries.KubeObjectMenuRegistration[] = [];
|
||||||
kubeWorkloadsOverviewItems: registries.WorkloadsOverviewDetailRegistration[] = [];
|
kubeWorkloadsOverviewItems: registries.WorkloadsOverviewDetailRegistration[] = [];
|
||||||
commands: registries.CommandRegistration[] = [];
|
commands: registries.CommandRegistration[] = [];
|
||||||
welcomeMenus: registries.WelcomeMenuRegistration[] = [];
|
welcomeMenus: WelcomeMenuRegistration[] = [];
|
||||||
welcomeBanners: registries.WelcomeBannerRegistration[] = [];
|
welcomeBanners: WelcomeBannerRegistration[] = [];
|
||||||
catalogEntityDetailItems: registries.CatalogEntityDetailRegistration<CatalogEntity>[] = [];
|
catalogEntityDetailItems: registries.CatalogEntityDetailRegistration<CatalogEntity>[] = [];
|
||||||
topBarItems: registries.TopBarRegistration[] = [];
|
topBarItems: TopBarRegistration[] = [];
|
||||||
|
|
||||||
async navigate<P extends object>(pageId?: string, params?: P) {
|
async navigate<P extends object>(pageId?: string, params?: P) {
|
||||||
const { navigate } = await import("../renderer/navigation");
|
const { navigate } = await import("../renderer/navigation");
|
||||||
|
|||||||
33
src/extensions/main-extensions.injectable.ts
Normal file
33
src/extensions/main-extensions.injectable.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import type { IComputedValue } from "mobx";
|
||||||
|
import extensionsInjectable from "./extensions.injectable";
|
||||||
|
import type { LensMainExtension } from "./lens-main-extension";
|
||||||
|
|
||||||
|
const mainExtensionsInjectable = getInjectable({
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
|
||||||
|
instantiate: (di) =>
|
||||||
|
di.inject(extensionsInjectable) as IComputedValue<LensMainExtension[]>,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default mainExtensionsInjectable;
|
||||||
@ -22,7 +22,7 @@
|
|||||||
// Base class for extensions-api registries
|
// Base class for extensions-api registries
|
||||||
import { action, observable, makeObservable } from "mobx";
|
import { action, observable, makeObservable } from "mobx";
|
||||||
import { Singleton } from "../../common/utils";
|
import { Singleton } from "../../common/utils";
|
||||||
import { LensExtension } from "../lens-extension";
|
import type { LensExtension } from "../lens-extension";
|
||||||
|
|
||||||
export class BaseRegistry<T, I = T> extends Singleton {
|
export class BaseRegistry<T, I = T> extends Singleton {
|
||||||
private items = observable.map<T, I>([], { deep: false });
|
private items = observable.map<T, I>([], { deep: false });
|
||||||
|
|||||||
@ -30,9 +30,6 @@ export * from "./kube-object-menu-registry";
|
|||||||
export * from "./kube-object-status-registry";
|
export * from "./kube-object-status-registry";
|
||||||
export * from "./command-registry";
|
export * from "./command-registry";
|
||||||
export * from "./entity-setting-registry";
|
export * from "./entity-setting-registry";
|
||||||
export * from "./welcome-menu-registry";
|
|
||||||
export * from "./welcome-banner-registry";
|
|
||||||
export * from "./catalog-entity-detail-registry";
|
export * from "./catalog-entity-detail-registry";
|
||||||
export * from "./workloads-overview-detail-registry";
|
export * from "./workloads-overview-detail-registry";
|
||||||
export * from "./topbar-registry";
|
|
||||||
export * from "./protocol-handler";
|
export * from "./protocol-handler";
|
||||||
|
|||||||
33
src/extensions/renderer-extensions.injectable.ts
Normal file
33
src/extensions/renderer-extensions.injectable.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import type { IComputedValue } from "mobx";
|
||||||
|
import extensionsInjectable from "./extensions.injectable";
|
||||||
|
import type { LensRendererExtension } from "./lens-renderer-extension";
|
||||||
|
|
||||||
|
const rendererExtensionsInjectable = getInjectable({
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
|
||||||
|
instantiate: (di) =>
|
||||||
|
di.inject(extensionsInjectable) as IComputedValue<LensRendererExtension[]>,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default rendererExtensionsInjectable;
|
||||||
@ -25,10 +25,9 @@ import { isLinux, isMac, isPublishConfigured, isTestEnv } from "../common/vars";
|
|||||||
import { delay } from "../common/utils";
|
import { delay } from "../common/utils";
|
||||||
import { areArgsUpdateAvailableToBackchannel, AutoUpdateChecking, AutoUpdateLogPrefix, AutoUpdateNoUpdateAvailable, broadcastMessage, onceCorrect, UpdateAvailableChannel, UpdateAvailableToBackchannel } from "../common/ipc";
|
import { areArgsUpdateAvailableToBackchannel, AutoUpdateChecking, AutoUpdateLogPrefix, AutoUpdateNoUpdateAvailable, broadcastMessage, onceCorrect, UpdateAvailableChannel, UpdateAvailableToBackchannel } from "../common/ipc";
|
||||||
import { once } from "lodash";
|
import { once } from "lodash";
|
||||||
import { ipcMain } from "electron";
|
import { ipcMain, autoUpdater as electronAutoUpdater } from "electron";
|
||||||
import { nextUpdateChannel } from "./utils/update-channel";
|
import { nextUpdateChannel } from "./utils/update-channel";
|
||||||
import { UserStore } from "../common/user-store";
|
import { UserStore } from "../common/user-store";
|
||||||
import { autoUpdater as electronAutoUpdater } from "electron";
|
|
||||||
|
|
||||||
let installVersion: null | string = null;
|
let installVersion: null | string = null;
|
||||||
|
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as tempy from "tempy";
|
import tempy from "tempy";
|
||||||
import fse from "fs-extra";
|
import fse from "fs-extra";
|
||||||
import * as yaml from "js-yaml";
|
import * as yaml from "js-yaml";
|
||||||
import { promiseExecFile } from "../../common/utils/promise-exec";
|
import { promiseExecFile } from "../../common/utils/promise-exec";
|
||||||
@ -43,6 +43,7 @@ async function execHelm(args: string[], options?: BaseEncodingOptions & ExecFile
|
|||||||
export async function listReleases(pathToKubeconfig: string, namespace?: string): Promise<Record<string, any>[]> {
|
export async function listReleases(pathToKubeconfig: string, namespace?: string): Promise<Record<string, any>[]> {
|
||||||
const args = [
|
const args = [
|
||||||
"ls",
|
"ls",
|
||||||
|
"--all",
|
||||||
"--output", "json",
|
"--output", "json",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -60,7 +60,7 @@ import { SentryInit } from "../common/sentry";
|
|||||||
import { ensureDir } from "fs-extra";
|
import { ensureDir } from "fs-extra";
|
||||||
import { Router } from "./router";
|
import { Router } from "./router";
|
||||||
import { initMenu } from "./menu/menu";
|
import { initMenu } from "./menu/menu";
|
||||||
import { initTray } from "./tray";
|
import { initTray } from "./tray/tray";
|
||||||
import { kubeApiRequest, shellApiRequest, ShellRequestAuthenticator } from "./proxy-functions";
|
import { kubeApiRequest, shellApiRequest, ShellRequestAuthenticator } from "./proxy-functions";
|
||||||
import { AppPaths } from "../common/app-paths";
|
import { AppPaths } from "../common/app-paths";
|
||||||
import { ShellSession } from "./shell-session/shell-session";
|
import { ShellSession } from "./shell-session/shell-session";
|
||||||
@ -68,6 +68,7 @@ import { getDi } from "./getDi";
|
|||||||
import electronMenuItemsInjectable from "./menu/electron-menu-items.injectable";
|
import electronMenuItemsInjectable from "./menu/electron-menu-items.injectable";
|
||||||
import extensionLoaderInjectable from "../extensions/extension-loader/extension-loader.injectable";
|
import extensionLoaderInjectable from "../extensions/extension-loader/extension-loader.injectable";
|
||||||
import lensProtocolRouterMainInjectable from "./protocol-handler/lens-protocol-router-main/lens-protocol-router-main.injectable";
|
import lensProtocolRouterMainInjectable from "./protocol-handler/lens-protocol-router-main/lens-protocol-router-main.injectable";
|
||||||
|
import trayMenuItemsInjectable from "./tray/tray-menu-items.injectable";
|
||||||
|
|
||||||
const di = getDi();
|
const di = getDi();
|
||||||
|
|
||||||
@ -104,6 +105,7 @@ mangleProxyEnv();
|
|||||||
logger.debug("[APP-MAIN] initializing ipc main handlers");
|
logger.debug("[APP-MAIN] initializing ipc main handlers");
|
||||||
|
|
||||||
const menuItems = di.inject(electronMenuItemsInjectable);
|
const menuItems = di.inject(electronMenuItemsInjectable);
|
||||||
|
const trayMenuItems = di.inject(trayMenuItemsInjectable);
|
||||||
|
|
||||||
initializers.initIpcMainHandlers(menuItems);
|
initializers.initIpcMainHandlers(menuItems);
|
||||||
|
|
||||||
@ -244,7 +246,7 @@ app.on("ready", async () => {
|
|||||||
|
|
||||||
onQuitCleanup.push(
|
onQuitCleanup.push(
|
||||||
initMenu(windowManager, menuItems),
|
initMenu(windowManager, menuItems),
|
||||||
initTray(windowManager),
|
initTray(windowManager, trayMenuItems),
|
||||||
() => ShellSession.cleanup(),
|
() => ShellSession.cleanup(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -18,24 +18,19 @@
|
|||||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
import { Injectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import { computed, IComputedValue } from "mobx";
|
import { computed } from "mobx";
|
||||||
import type { LensMainExtension } from "../../extensions/lens-main-extension";
|
import mainExtensionsInjectable from "../../extensions/main-extensions.injectable";
|
||||||
import extensionsInjectable from "../../extensions/extensions.injectable";
|
|
||||||
import type { MenuRegistration } from "./menu-registration";
|
|
||||||
|
|
||||||
const electronMenuItemsInjectable: Injectable<
|
const electronMenuItemsInjectable = getInjectable({
|
||||||
IComputedValue<MenuRegistration[]>,
|
|
||||||
{ extensions: IComputedValue<LensMainExtension[]> }
|
|
||||||
> = {
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
|
||||||
getDependencies: di => ({
|
instantiate: (di) => {
|
||||||
extensions: di.inject(extensionsInjectable),
|
const extensions = di.inject(mainExtensionsInjectable);
|
||||||
}),
|
|
||||||
|
|
||||||
instantiate: ({ extensions }) =>
|
return computed(() =>
|
||||||
computed(() => extensions.get().flatMap(extension => extension.appMenus)),
|
extensions.get().flatMap((extension) => extension.appMenus));
|
||||||
};
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export default electronMenuItemsInjectable;
|
export default electronMenuItemsInjectable;
|
||||||
|
|||||||
@ -24,8 +24,8 @@ import electronMenuItemsInjectable from "./electron-menu-items.injectable";
|
|||||||
import type { IComputedValue } from "mobx";
|
import type { IComputedValue } from "mobx";
|
||||||
import { computed, ObservableMap, runInAction } from "mobx";
|
import { computed, ObservableMap, runInAction } from "mobx";
|
||||||
import type { MenuRegistration } from "./menu-registration";
|
import type { MenuRegistration } from "./menu-registration";
|
||||||
import extensionsInjectable from "../../extensions/extensions.injectable";
|
|
||||||
import { getDiForUnitTesting } from "../getDiForUnitTesting";
|
import { getDiForUnitTesting } from "../getDiForUnitTesting";
|
||||||
|
import mainExtensionsInjectable from "../../extensions/main-extensions.injectable";
|
||||||
|
|
||||||
describe("electron-menu-items", () => {
|
describe("electron-menu-items", () => {
|
||||||
let di: ConfigurableDependencyInjectionContainer;
|
let di: ConfigurableDependencyInjectionContainer;
|
||||||
@ -38,8 +38,8 @@ describe("electron-menu-items", () => {
|
|||||||
extensionsStub = new ObservableMap();
|
extensionsStub = new ObservableMap();
|
||||||
|
|
||||||
di.override(
|
di.override(
|
||||||
extensionsInjectable,
|
mainExtensionsInjectable,
|
||||||
computed(() => [...extensionsStub.values()]),
|
() => computed(() => [...extensionsStub.values()]),
|
||||||
);
|
);
|
||||||
|
|
||||||
electronMenuItems = di.inject(electronMenuItemsInjectable);
|
electronMenuItems = di.inject(electronMenuItemsInjectable);
|
||||||
|
|||||||
@ -38,7 +38,7 @@ export class PrometheusOperator extends PrometheusProvider {
|
|||||||
case "cluster":
|
case "cluster":
|
||||||
switch (queryName) {
|
switch (queryName) {
|
||||||
case "memoryUsage":
|
case "memoryUsage":
|
||||||
return `sum(node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes))`.replace(/_bytes/g, `_bytes{node=~"${opts.nodes}"}`);
|
return `sum(node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes))`.replace(/_bytes/g, `_bytes * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"}`);
|
||||||
case "workloadMemoryUsage":
|
case "workloadMemoryUsage":
|
||||||
return `sum(container_memory_working_set_bytes{container!="", instance=~"${opts.nodes}"}) by (component)`;
|
return `sum(container_memory_working_set_bytes{container!="", instance=~"${opts.nodes}"}) by (component)`;
|
||||||
case "memoryRequests":
|
case "memoryRequests":
|
||||||
@ -50,7 +50,7 @@ export class PrometheusOperator extends PrometheusProvider {
|
|||||||
case "memoryAllocatableCapacity":
|
case "memoryAllocatableCapacity":
|
||||||
return `sum(kube_node_status_allocatable{node=~"${opts.nodes}", resource="memory"})`;
|
return `sum(kube_node_status_allocatable{node=~"${opts.nodes}", resource="memory"})`;
|
||||||
case "cpuUsage":
|
case "cpuUsage":
|
||||||
return `sum(rate(node_cpu_seconds_total{node=~"${opts.nodes}", mode=~"user|system"}[${this.rateAccuracy}]))`;
|
return `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}])* on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"})`;
|
||||||
case "cpuRequests":
|
case "cpuRequests":
|
||||||
return `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="cpu"})`;
|
return `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="cpu"})`;
|
||||||
case "cpuLimits":
|
case "cpuLimits":
|
||||||
@ -66,31 +66,31 @@ export class PrometheusOperator extends PrometheusProvider {
|
|||||||
case "podAllocatableCapacity":
|
case "podAllocatableCapacity":
|
||||||
return `sum(kube_node_status_allocatable{node=~"${opts.nodes}", resource="pods"})`;
|
return `sum(kube_node_status_allocatable{node=~"${opts.nodes}", resource="pods"})`;
|
||||||
case "fsSize":
|
case "fsSize":
|
||||||
return `sum(node_filesystem_size_bytes{node=~"${opts.nodes}", mountpoint="/"}) by (node)`;
|
return `sum(node_filesystem_size_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"})`;
|
||||||
case "fsUsage":
|
case "fsUsage":
|
||||||
return `sum(node_filesystem_size_bytes{node=~"${opts.nodes}", mountpoint="/"} - node_filesystem_avail_bytes{node=~"${opts.nodes}", mountpoint="/"}) by (node)`;
|
return `sum(node_filesystem_size_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"} - node_filesystem_avail_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"})`;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "nodes":
|
case "nodes":
|
||||||
switch (queryName) {
|
switch (queryName) {
|
||||||
case "memoryUsage":
|
case "memoryUsage":
|
||||||
return `sum (node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) by (node)`;
|
return `sum((node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) * on (pod, namespace) group_left(node) kube_pod_info) by (node)`;
|
||||||
case "workloadMemoryUsage":
|
case "workloadMemoryUsage":
|
||||||
return `sum(container_memory_working_set_bytes{container!=""}) by (node)`;
|
return `sum(container_memory_working_set_bytes{container!="POD", container!=""}) by (node)`;
|
||||||
case "memoryCapacity":
|
case "memoryCapacity":
|
||||||
return `sum(kube_node_status_capacity{resource="memory"}) by (node)`;
|
return `sum(kube_node_status_capacity{resource="memory"}) by (node)`;
|
||||||
case "memoryAllocatableCapacity":
|
case "memoryAllocatableCapacity":
|
||||||
return `sum(kube_node_status_allocatable{resource="memory"}) by (node)`;
|
return `sum(kube_node_status_allocatable{resource="memory"}) by (node)`;
|
||||||
case "cpuUsage":
|
case "cpuUsage":
|
||||||
return `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}])) by(node)`;
|
return `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}]) * on (pod, namespace) group_left(node) kube_pod_info) by (node)`;
|
||||||
case "cpuCapacity":
|
case "cpuCapacity":
|
||||||
return `sum(kube_node_status_allocatable{resource="cpu"}) by (node)`;
|
return `sum(kube_node_status_allocatable{resource="cpu"}) by (node)`;
|
||||||
case "cpuAllocatableCapacity":
|
case "cpuAllocatableCapacity":
|
||||||
return `sum(kube_node_status_allocatable{resource="cpu"}) by (node)`;
|
return `sum(kube_node_status_allocatable{resource="cpu"}) by (node)`;
|
||||||
case "fsSize":
|
case "fsSize":
|
||||||
return `sum(node_filesystem_size_bytes{mountpoint="/"}) by (node)`;
|
return `sum(node_filesystem_size_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info) by (node)`;
|
||||||
case "fsUsage":
|
case "fsUsage":
|
||||||
return `sum(node_filesystem_size_bytes{mountpoint="/"} - node_filesystem_avail_bytes{mountpoint="/"}) by (node)`;
|
return `sum((node_filesystem_size_bytes{mountpoint="/"} - node_filesystem_avail_bytes{mountpoint="/"}) * on (pod, namespace) group_left(node) kube_pod_info) by (node)`;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "pods":
|
case "pods":
|
||||||
|
|||||||
@ -18,23 +18,17 @@
|
|||||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
import type { Injectable } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import { lifecycleEnum } from "@ogre-tools/injectable";
|
|
||||||
import extensionLoaderInjectable from "../../../extensions/extension-loader/extension-loader.injectable";
|
import extensionLoaderInjectable from "../../../extensions/extension-loader/extension-loader.injectable";
|
||||||
import type { Dependencies } from "./lens-protocol-router-main";
|
|
||||||
import { LensProtocolRouterMain } from "./lens-protocol-router-main";
|
import { LensProtocolRouterMain } from "./lens-protocol-router-main";
|
||||||
|
|
||||||
const lensProtocolRouterMainInjectable: Injectable<
|
const lensProtocolRouterMainInjectable = getInjectable({
|
||||||
LensProtocolRouterMain,
|
instantiate: (di) =>
|
||||||
Dependencies
|
new LensProtocolRouterMain({
|
||||||
> = {
|
extensionLoader: di.inject(extensionLoaderInjectable),
|
||||||
getDependencies: di => ({
|
}),
|
||||||
extensionLoader: di.inject(extensionLoaderInjectable),
|
|
||||||
}),
|
|
||||||
|
|
||||||
instantiate: dependencies => new LensProtocolRouterMain(dependencies),
|
|
||||||
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
};
|
});
|
||||||
|
|
||||||
export default lensProtocolRouterMainInjectable;
|
export default lensProtocolRouterMainInjectable;
|
||||||
|
|||||||
@ -51,7 +51,7 @@ function checkHost<Query>(url: URLParse<Query>): boolean {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Dependencies {
|
interface Dependencies {
|
||||||
extensionLoader: ExtensionLoader
|
extensionLoader: ExtensionLoader
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -25,7 +25,7 @@ import { exec } from "child_process";
|
|||||||
import fs from "fs-extra";
|
import fs from "fs-extra";
|
||||||
import * as yaml from "js-yaml";
|
import * as yaml from "js-yaml";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import * as tempy from "tempy";
|
import tempy from "tempy";
|
||||||
import logger from "./logger";
|
import logger from "./logger";
|
||||||
import { appEventBus } from "../common/event-bus";
|
import { appEventBus } from "../common/event-bus";
|
||||||
import { cloneJsonObject } from "../common/utils";
|
import { cloneJsonObject } from "../common/utils";
|
||||||
|
|||||||
36
src/main/tray/tray-menu-items.injectable.ts
Normal file
36
src/main/tray/tray-menu-items.injectable.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import { computed } from "mobx";
|
||||||
|
import mainExtensionsInjectable from "../../extensions/main-extensions.injectable";
|
||||||
|
|
||||||
|
const trayItemsInjectable = getInjectable({
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
|
||||||
|
instantiate: (di) => {
|
||||||
|
const extensions = di.inject(mainExtensionsInjectable);
|
||||||
|
|
||||||
|
return computed(() =>
|
||||||
|
extensions.get().flatMap(extension => extension.trayMenus));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default trayItemsInjectable;
|
||||||
136
src/main/tray/tray-menu-items.test.ts
Normal file
136
src/main/tray/tray-menu-items.test.ts
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import type { ConfigurableDependencyInjectionContainer } from "@ogre-tools/injectable";
|
||||||
|
import { LensMainExtension } from "../../extensions/lens-main-extension";
|
||||||
|
import trayItemsInjectable from "./tray-menu-items.injectable";
|
||||||
|
import type { IComputedValue } from "mobx";
|
||||||
|
import { computed, ObservableMap, runInAction } from "mobx";
|
||||||
|
import { getDiForUnitTesting } from "../getDiForUnitTesting";
|
||||||
|
import mainExtensionsInjectable from "../../extensions/main-extensions.injectable";
|
||||||
|
import type { TrayMenuRegistration } from "./tray-menu-registration";
|
||||||
|
|
||||||
|
describe("tray-menu-items", () => {
|
||||||
|
let di: ConfigurableDependencyInjectionContainer;
|
||||||
|
let trayMenuItems: IComputedValue<TrayMenuRegistration[]>;
|
||||||
|
let extensionsStub: ObservableMap<string, LensMainExtension>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
di = getDiForUnitTesting();
|
||||||
|
|
||||||
|
extensionsStub = new ObservableMap();
|
||||||
|
|
||||||
|
di.override(
|
||||||
|
mainExtensionsInjectable,
|
||||||
|
() => computed(() => [...extensionsStub.values()]),
|
||||||
|
);
|
||||||
|
|
||||||
|
trayMenuItems = di.inject(trayItemsInjectable);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not have any items yet", () => {
|
||||||
|
expect(trayMenuItems.get()).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when extension is enabled", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const someExtension = new SomeTestExtension({
|
||||||
|
id: "some-extension-id",
|
||||||
|
trayMenus: [{ label: "tray-menu-from-some-extension" }],
|
||||||
|
});
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
extensionsStub.set("some-extension-id", someExtension);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("has tray menu items", () => {
|
||||||
|
expect(trayMenuItems.get()).toEqual([
|
||||||
|
{
|
||||||
|
label: "tray-menu-from-some-extension",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when disabling extension, does not have tray menu items", () => {
|
||||||
|
runInAction(() => {
|
||||||
|
extensionsStub.delete("some-extension-id");
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(trayMenuItems.get()).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when other extension is enabled", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const someOtherExtension = new SomeTestExtension({
|
||||||
|
id: "some-extension-id",
|
||||||
|
trayMenus: [{ label: "some-label-from-second-extension" }],
|
||||||
|
});
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
extensionsStub.set("some-other-extension-id", someOtherExtension);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("has tray menu items for both extensions", () => {
|
||||||
|
expect(trayMenuItems.get()).toEqual([
|
||||||
|
{
|
||||||
|
label: "tray-menu-from-some-extension",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
label: "some-label-from-second-extension",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when extension is disabled, still returns tray menu items for extensions that are enabled", () => {
|
||||||
|
runInAction(() => {
|
||||||
|
extensionsStub.delete("some-other-extension-id");
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(trayMenuItems.get()).toEqual([
|
||||||
|
{
|
||||||
|
label: "tray-menu-from-some-extension",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
class SomeTestExtension extends LensMainExtension {
|
||||||
|
constructor({ id, trayMenus }: {
|
||||||
|
id: string;
|
||||||
|
trayMenus: TrayMenuRegistration[];
|
||||||
|
}) {
|
||||||
|
super({
|
||||||
|
id,
|
||||||
|
absolutePath: "irrelevant",
|
||||||
|
isBundled: false,
|
||||||
|
isCompatible: false,
|
||||||
|
isEnabled: false,
|
||||||
|
manifest: { name: id, version: "some-version" },
|
||||||
|
manifestPath: "irrelevant",
|
||||||
|
});
|
||||||
|
|
||||||
|
this.trayMenus = trayMenus;
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/main/tray/tray-menu-registration.d.ts
vendored
Normal file
30
src/main/tray/tray-menu-registration.d.ts
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface TrayMenuRegistration {
|
||||||
|
label?: string;
|
||||||
|
click?: (menuItem: TrayMenuRegistration) => void;
|
||||||
|
id?: string;
|
||||||
|
type?: "normal" | "separator" | "submenu"
|
||||||
|
toolTip?: string;
|
||||||
|
enabled?: boolean;
|
||||||
|
submenu?: TrayMenuRegistration[]
|
||||||
|
}
|
||||||
@ -20,16 +20,18 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import packageInfo from "../../package.json";
|
import packageInfo from "../../../package.json";
|
||||||
import { Menu, Tray } from "electron";
|
import { Menu, Tray } from "electron";
|
||||||
import { autorun } from "mobx";
|
import { autorun, IComputedValue } from "mobx";
|
||||||
import { showAbout } from "./menu/menu";
|
import { showAbout } from "../menu/menu";
|
||||||
import { checkForUpdates, isAutoUpdateEnabled } from "./app-updater";
|
import { checkForUpdates, isAutoUpdateEnabled } from "../app-updater";
|
||||||
import type { WindowManager } from "./window-manager";
|
import type { WindowManager } from "../window-manager";
|
||||||
import logger from "./logger";
|
import logger from "../logger";
|
||||||
import { isDevelopment, isWindows, productName } from "../common/vars";
|
import { isDevelopment, isWindows, productName } from "../../common/vars";
|
||||||
import { exitApp } from "./exit-app";
|
import { exitApp } from "../exit-app";
|
||||||
import { preferencesURL } from "../common/routes";
|
import { preferencesURL } from "../../common/routes";
|
||||||
|
import { toJS } from "../../common/utils";
|
||||||
|
import type { TrayMenuRegistration } from "./tray-menu-registration";
|
||||||
|
|
||||||
const TRAY_LOG_PREFIX = "[TRAY]";
|
const TRAY_LOG_PREFIX = "[TRAY]";
|
||||||
|
|
||||||
@ -44,7 +46,10 @@ export function getTrayIcon(): string {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initTray(windowManager: WindowManager) {
|
export function initTray(
|
||||||
|
windowManager: WindowManager,
|
||||||
|
trayMenuItems: IComputedValue<TrayMenuRegistration[]>,
|
||||||
|
) {
|
||||||
const icon = getTrayIcon();
|
const icon = getTrayIcon();
|
||||||
|
|
||||||
tray = new Tray(icon);
|
tray = new Tray(icon);
|
||||||
@ -62,7 +67,7 @@ export function initTray(windowManager: WindowManager) {
|
|||||||
const disposers = [
|
const disposers = [
|
||||||
autorun(() => {
|
autorun(() => {
|
||||||
try {
|
try {
|
||||||
const menu = createTrayMenu(windowManager);
|
const menu = createTrayMenu(windowManager, toJS(trayMenuItems.get()));
|
||||||
|
|
||||||
tray.setContextMenu(menu);
|
tray.setContextMenu(menu);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -78,8 +83,21 @@ export function initTray(windowManager: WindowManager) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function createTrayMenu(windowManager: WindowManager): Menu {
|
function getMenuItemConstructorOptions(trayItem: TrayMenuRegistration): Electron.MenuItemConstructorOptions {
|
||||||
const template: Electron.MenuItemConstructorOptions[] = [
|
return {
|
||||||
|
...trayItem,
|
||||||
|
submenu: trayItem.submenu ? trayItem.submenu.map(getMenuItemConstructorOptions) : undefined,
|
||||||
|
click: trayItem.click ? () => {
|
||||||
|
trayItem.click(trayItem);
|
||||||
|
} : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTrayMenu(
|
||||||
|
windowManager: WindowManager,
|
||||||
|
extensionTrayItems: TrayMenuRegistration[],
|
||||||
|
): Menu {
|
||||||
|
let template: Electron.MenuItemConstructorOptions[] = [
|
||||||
{
|
{
|
||||||
label: `Open ${productName}`,
|
label: `Open ${productName}`,
|
||||||
click() {
|
click() {
|
||||||
@ -108,6 +126,8 @@ function createTrayMenu(windowManager: WindowManager): Menu {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template = template.concat(extensionTrayItems.map(getMenuItemConstructorOptions));
|
||||||
|
|
||||||
return Menu.buildFromTemplate(template.concat([
|
return Menu.buildFromTemplate(template.concat([
|
||||||
{
|
{
|
||||||
label: `About ${productName}`,
|
label: `About ${productName}`,
|
||||||
@ -24,7 +24,7 @@ import { makeObservable, observable } from "mobx";
|
|||||||
import { app, BrowserWindow, dialog, ipcMain, shell, webContents } from "electron";
|
import { app, BrowserWindow, dialog, ipcMain, shell, webContents } from "electron";
|
||||||
import windowStateKeeper from "electron-window-state";
|
import windowStateKeeper from "electron-window-state";
|
||||||
import { appEventBus } from "../common/event-bus";
|
import { appEventBus } from "../common/event-bus";
|
||||||
import { ipcMainOn } from "../common/ipc";
|
import { BundledExtensionsLoaded, ipcMainOn } from "../common/ipc";
|
||||||
import { delay, iter, Singleton } from "../common/utils";
|
import { delay, iter, Singleton } from "../common/utils";
|
||||||
import { ClusterFrameInfo, clusterFrameMap } from "../common/cluster-frames";
|
import { ClusterFrameInfo, clusterFrameMap } from "../common/cluster-frames";
|
||||||
import { IpcRendererNavigationEvents } from "../renderer/navigation/events";
|
import { IpcRendererNavigationEvents } from "../renderer/navigation/events";
|
||||||
@ -181,7 +181,7 @@ export class WindowManager extends Singleton {
|
|||||||
|
|
||||||
if (!this.mainWindow) {
|
if (!this.mainWindow) {
|
||||||
viewHasLoaded = new Promise<void>(resolve => {
|
viewHasLoaded = new Promise<void>(resolve => {
|
||||||
ipcMain.once(IpcRendererNavigationEvents.LOADED, () => resolve());
|
ipcMain.once(BundledExtensionsLoaded, () => resolve());
|
||||||
});
|
});
|
||||||
await this.initMainWindow(showSplash);
|
await this.initMainWindow(showSplash);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,7 +20,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { CatalogEntityRegistry } from "../catalog-entity-registry";
|
import { CatalogEntityRegistry } from "../catalog-entity-registry";
|
||||||
import "../../../common/catalog-entities";
|
|
||||||
import { catalogCategoryRegistry } from "../../../common/catalog/catalog-category-registry";
|
import { catalogCategoryRegistry } from "../../../common/catalog/catalog-category-registry";
|
||||||
import { CatalogCategory, CatalogEntityData, CatalogEntityKindData } from "../catalog-entity";
|
import { CatalogCategory, CatalogEntityData, CatalogEntityKindData } from "../catalog-entity";
|
||||||
import { KubernetesCluster, WebLink } from "../../../common/catalog-entities";
|
import { KubernetesCluster, WebLink } from "../../../common/catalog-entities";
|
||||||
|
|||||||
@ -28,14 +28,21 @@ import { ClusterStore } from "../../common/cluster-store";
|
|||||||
import { Disposer, iter } from "../utils";
|
import { Disposer, iter } from "../utils";
|
||||||
import { once } from "lodash";
|
import { once } from "lodash";
|
||||||
import logger from "../../common/logger";
|
import logger from "../../common/logger";
|
||||||
import { catalogEntityRunContext } from "./catalog-entity";
|
|
||||||
import { CatalogRunEvent } from "../../common/catalog/catalog-run-event";
|
import { CatalogRunEvent } from "../../common/catalog/catalog-run-event";
|
||||||
import { ipcRenderer } from "electron";
|
import { ipcRenderer } from "electron";
|
||||||
import { CatalogIpcEvents } from "../../common/ipc/catalog";
|
import { CatalogIpcEvents } from "../../common/ipc/catalog";
|
||||||
|
import { navigate } from "../navigation";
|
||||||
|
|
||||||
export type EntityFilter = (entity: CatalogEntity) => any;
|
export type EntityFilter = (entity: CatalogEntity) => any;
|
||||||
export type CatalogEntityOnBeforeRun = (event: CatalogRunEvent) => void | Promise<void>;
|
export type CatalogEntityOnBeforeRun = (event: CatalogRunEvent) => void | Promise<void>;
|
||||||
|
|
||||||
|
export const catalogEntityRunContext = {
|
||||||
|
navigate: (url: string) => navigate(url),
|
||||||
|
setCommandPaletteContext: (entity?: CatalogEntity) => {
|
||||||
|
catalogEntityRegistry.activeEntity = entity;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export class CatalogEntityRegistry {
|
export class CatalogEntityRegistry {
|
||||||
@observable protected activeEntityId: string | undefined = undefined;
|
@observable protected activeEntityId: string | undefined = undefined;
|
||||||
protected _entities = observable.map<string, CatalogEntity>([], { deep: true });
|
protected _entities = observable.map<string, CatalogEntity>([], { deep: true });
|
||||||
|
|||||||
@ -19,10 +19,7 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { navigate } from "../navigation";
|
export { catalogEntityRunContext } from "./catalog-entity-registry";
|
||||||
import type { CatalogEntity } from "../../common/catalog";
|
|
||||||
import { catalogEntityRegistry } from "./catalog-entity-registry";
|
|
||||||
|
|
||||||
export { CatalogCategory, CatalogEntity } from "../../common/catalog";
|
export { CatalogCategory, CatalogEntity } from "../../common/catalog";
|
||||||
export type {
|
export type {
|
||||||
CatalogEntityData,
|
CatalogEntityData,
|
||||||
@ -33,10 +30,3 @@ export type {
|
|||||||
CatalogEntityContextMenu,
|
CatalogEntityContextMenu,
|
||||||
CatalogEntityContextMenuContext,
|
CatalogEntityContextMenuContext,
|
||||||
} from "../../common/catalog";
|
} from "../../common/catalog";
|
||||||
|
|
||||||
export const catalogEntityRunContext = {
|
|
||||||
navigate: (url: string) => navigate(url),
|
|
||||||
setCommandPaletteContext: (entity?: CatalogEntity) => {
|
|
||||||
catalogEntityRegistry.activeEntity = entity;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|||||||
@ -114,9 +114,6 @@ export async function bootstrap(comp: () => Promise<AppComponent>, di: Dependenc
|
|||||||
logger.info(`${logPrefix} initializing KubeObjectDetailRegistry`);
|
logger.info(`${logPrefix} initializing KubeObjectDetailRegistry`);
|
||||||
initializers.initKubeObjectDetailRegistry();
|
initializers.initKubeObjectDetailRegistry();
|
||||||
|
|
||||||
logger.info(`${logPrefix} initializing WelcomeMenuRegistry`);
|
|
||||||
initializers.initWelcomeMenuRegistry();
|
|
||||||
|
|
||||||
logger.info(`${logPrefix} initializing WorkloadsOverviewDetailRegistry`);
|
logger.info(`${logPrefix} initializing WorkloadsOverviewDetailRegistry`);
|
||||||
initializers.initWorkloadsOverviewDetailRegistry();
|
initializers.initWorkloadsOverviewDetailRegistry();
|
||||||
|
|
||||||
|
|||||||
@ -38,7 +38,7 @@
|
|||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.theme-light) {
|
@include theme-light {
|
||||||
.editor {
|
.editor {
|
||||||
border-color: var(--borderFaintColor);
|
border-color: var(--borderFaintColor);
|
||||||
}
|
}
|
||||||
@ -19,7 +19,7 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import styles from "./add-cluster.module.css";
|
import styles from "./add-cluster.module.scss";
|
||||||
|
|
||||||
import type { KubeConfig } from "@kubernetes/client-node";
|
import type { KubeConfig } from "@kubernetes/client-node";
|
||||||
import fse from "fs-extra";
|
import fse from "fs-extra";
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import styles from "./catalog-entity-details.module.css";
|
import styles from "./catalog-entity-details.module.scss";
|
||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { Drawer, DrawerItem } from "../drawer";
|
import { Drawer, DrawerItem } from "../drawer";
|
||||||
@ -27,14 +27,15 @@ import type { CatalogCategory, CatalogEntity } from "../../../common/catalog";
|
|||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { CatalogEntityDrawerMenu } from "./catalog-entity-drawer-menu";
|
import { CatalogEntityDrawerMenu } from "./catalog-entity-drawer-menu";
|
||||||
import { CatalogEntityDetailRegistry } from "../../../extensions/registries";
|
import { CatalogEntityDetailRegistry } from "../../../extensions/registries";
|
||||||
import type { CatalogEntityItem } from "./catalog-entity-item";
|
|
||||||
import { isDevelopment } from "../../../common/vars";
|
import { isDevelopment } from "../../../common/vars";
|
||||||
import { cssNames } from "../../utils";
|
import { cssNames } from "../../utils";
|
||||||
import { Avatar } from "../avatar";
|
import { Avatar } from "../avatar";
|
||||||
|
import { getLabelBadges } from "./helpers";
|
||||||
|
|
||||||
interface Props<T extends CatalogEntity> {
|
interface Props<T extends CatalogEntity> {
|
||||||
item: CatalogEntityItem<T> | null | undefined;
|
entity: T;
|
||||||
hideDetails(): void;
|
hideDetails(): void;
|
||||||
|
onRun: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
@ -47,32 +48,30 @@ export class CatalogEntityDetails<T extends CatalogEntity> extends Component<Pro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderContent(item: CatalogEntityItem<T>) {
|
renderContent(entity: T) {
|
||||||
const detailItems = CatalogEntityDetailRegistry.getInstance().getItemsForKind(item.kind, item.apiVersion);
|
const { onRun, hideDetails } = this.props;
|
||||||
const details = detailItems.map(({ components }, index) => {
|
const detailItems = CatalogEntityDetailRegistry.getInstance().getItemsForKind(entity.kind, entity.apiVersion);
|
||||||
return <components.Details entity={item.entity} key={index}/>;
|
const details = detailItems.map(({ components }, index) => <components.Details entity={entity} key={index} />);
|
||||||
});
|
const showDefaultDetails = detailItems.find((item) => item.priority > 999) === undefined;
|
||||||
|
|
||||||
const showDetails = detailItems.find((item) => item.priority > 999) === undefined;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{showDetails && (
|
{showDefaultDetails && (
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className={styles.entityIcon}>
|
<div className={styles.entityIcon}>
|
||||||
<Avatar
|
<Avatar
|
||||||
title={item.name}
|
title={entity.getName()}
|
||||||
colorHash={`${item.name}-${item.source}`}
|
colorHash={`${entity.getName()}-${entity.getSource()}`}
|
||||||
size={128}
|
size={128}
|
||||||
src={item.entity.spec.icon?.src}
|
src={entity.spec.icon?.src}
|
||||||
data-testid="detail-panel-hot-bar-icon"
|
data-testid="detail-panel-hot-bar-icon"
|
||||||
background={item.entity.spec.icon?.background}
|
background={entity.spec.icon?.background}
|
||||||
onClick={() => item.onRun()}
|
onClick={onRun}
|
||||||
className={styles.avatar}
|
className={styles.avatar}
|
||||||
>
|
>
|
||||||
{item.entity.spec.icon?.material && <Icon material={item.entity.spec.icon?.material}/>}
|
{entity.spec.icon?.material && <Icon material={entity.spec.icon?.material}/>}
|
||||||
</Avatar>
|
</Avatar>
|
||||||
{item?.enabled && (
|
{entity.isEnabled() && (
|
||||||
<div className={styles.hint}>
|
<div className={styles.hint}>
|
||||||
Click to open
|
Click to open
|
||||||
</div>
|
</div>
|
||||||
@ -80,23 +79,23 @@ export class CatalogEntityDetails<T extends CatalogEntity> extends Component<Pro
|
|||||||
</div>
|
</div>
|
||||||
<div className={cssNames("box grow", styles.metadata)}>
|
<div className={cssNames("box grow", styles.metadata)}>
|
||||||
<DrawerItem name="Name">
|
<DrawerItem name="Name">
|
||||||
{item.name}
|
{entity.getName()}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
<DrawerItem name="Kind">
|
<DrawerItem name="Kind">
|
||||||
{item.kind}
|
{entity.kind}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
<DrawerItem name="Source">
|
<DrawerItem name="Source">
|
||||||
{item.source}
|
{entity.getSource()}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
<DrawerItem name="Status">
|
<DrawerItem name="Status">
|
||||||
{item.phase}
|
{entity.status.phase}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
<DrawerItem name="Labels">
|
<DrawerItem name="Labels">
|
||||||
{...item.getLabelBadges(this.props.hideDetails)}
|
{getLabelBadges(entity, hideDetails)}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
{isDevelopment && (
|
{isDevelopment && (
|
||||||
<DrawerItem name="Id">
|
<DrawerItem name="Id">
|
||||||
{item.getId()}
|
{entity.getId()}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -110,19 +109,18 @@ export class CatalogEntityDetails<T extends CatalogEntity> extends Component<Pro
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { item, hideDetails } = this.props;
|
const { entity, hideDetails } = this.props;
|
||||||
const title = `${item.kind}: ${item.name}`;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
className={styles.entityDetails}
|
className={styles.entityDetails}
|
||||||
usePortal={true}
|
usePortal={true}
|
||||||
open={true}
|
open={true}
|
||||||
title={title}
|
title={`${entity.kind}: ${entity.getName()}`}
|
||||||
toolbar={<CatalogEntityDrawerMenu item={item} key={item.getId()} />}
|
toolbar={<CatalogEntityDrawerMenu entity={entity} key={entity.getId()} />}
|
||||||
onClose={hideDetails}
|
onClose={hideDetails}
|
||||||
>
|
>
|
||||||
{item && this.renderContent(item)}
|
{this.renderContent(entity)}
|
||||||
</Drawer>
|
</Drawer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,11 +29,10 @@ import { navigate } from "../../navigation";
|
|||||||
import { MenuItem } from "../menu";
|
import { MenuItem } from "../menu";
|
||||||
import { ConfirmDialog } from "../confirm-dialog";
|
import { ConfirmDialog } from "../confirm-dialog";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import type { CatalogEntityItem } from "./catalog-entity-item";
|
|
||||||
import { HotbarToggleMenuItem } from "./hotbar-toggle-menu-item";
|
import { HotbarToggleMenuItem } from "./hotbar-toggle-menu-item";
|
||||||
|
|
||||||
export interface CatalogEntityDrawerMenuProps<T extends CatalogEntity> extends MenuActionsProps {
|
export interface CatalogEntityDrawerMenuProps<T extends CatalogEntity> extends MenuActionsProps {
|
||||||
item: CatalogEntityItem<T> | null | undefined;
|
entity: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
@ -50,7 +49,7 @@ export class CatalogEntityDrawerMenu<T extends CatalogEntity> extends React.Comp
|
|||||||
menuItems: [],
|
menuItems: [],
|
||||||
navigate: (url: string) => navigate(url),
|
navigate: (url: string) => navigate(url),
|
||||||
};
|
};
|
||||||
this.props.item?.onContextMenuOpen(this.contextMenu);
|
this.props.entity?.onContextMenuOpen(this.contextMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMenuItemClick(menuItem: CatalogEntityContextMenu) {
|
onMenuItemClick(menuItem: CatalogEntityContextMenu) {
|
||||||
@ -108,9 +107,9 @@ export class CatalogEntityDrawerMenu<T extends CatalogEntity> extends React.Comp
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { className, item: entity, ...menuProps } = this.props;
|
const { className, entity, ...menuProps } = this.props;
|
||||||
|
|
||||||
if (!this.contextMenu || !entity.enabled) {
|
if (!this.contextMenu || !entity.isEnabled()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,7 +119,7 @@ export class CatalogEntityDrawerMenu<T extends CatalogEntity> extends React.Comp
|
|||||||
toolbar
|
toolbar
|
||||||
{...menuProps}
|
{...menuProps}
|
||||||
>
|
>
|
||||||
{this.getMenuItems(entity.entity)}
|
{this.getMenuItems(entity)}
|
||||||
</MenuActions>
|
</MenuActions>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
import styles from "./catalog.module.css";
|
import styles from "./catalog.module.scss";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { action, computed } from "mobx";
|
import { action, computed } from "mobx";
|
||||||
import { CatalogEntity } from "../../api/catalog-entity";
|
import { CatalogEntity } from "../../api/catalog-entity";
|
||||||
|
|||||||
@ -25,9 +25,8 @@ import type { CatalogEntity } from "../../api/catalog-entity";
|
|||||||
import { ItemStore } from "../../../common/item.store";
|
import { ItemStore } from "../../../common/item.store";
|
||||||
import { CatalogCategory, catalogCategoryRegistry } from "../../../common/catalog";
|
import { CatalogCategory, catalogCategoryRegistry } from "../../../common/catalog";
|
||||||
import { autoBind, disposer } from "../../../common/utils";
|
import { autoBind, disposer } from "../../../common/utils";
|
||||||
import { CatalogEntityItem } from "./catalog-entity-item";
|
|
||||||
|
|
||||||
export class CatalogEntityStore extends ItemStore<CatalogEntityItem<CatalogEntity>> {
|
export class CatalogEntityStore extends ItemStore<CatalogEntity> {
|
||||||
constructor(private registry: CatalogEntityRegistry = catalogEntityRegistry) {
|
constructor(private registry: CatalogEntityRegistry = catalogEntityRegistry) {
|
||||||
super();
|
super();
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
@ -39,10 +38,10 @@ export class CatalogEntityStore extends ItemStore<CatalogEntityItem<CatalogEntit
|
|||||||
|
|
||||||
@computed get entities() {
|
@computed get entities() {
|
||||||
if (!this.activeCategory) {
|
if (!this.activeCategory) {
|
||||||
return this.registry.filteredItems.map(entity => new CatalogEntityItem(entity, this.registry));
|
return this.registry.filteredItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.registry.getItemsForCategory(this.activeCategory, { filtered: true }).map(entity => new CatalogEntityItem(entity, this.registry));
|
return this.registry.getItemsForCategory(this.activeCategory, { filtered: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed get selectedItem() {
|
@computed get selectedItem() {
|
||||||
@ -68,4 +67,8 @@ export class CatalogEntityStore extends ItemStore<CatalogEntityItem<CatalogEntit
|
|||||||
// concurrency is true to fix bug if catalog filter is removed and added at the same time
|
// concurrency is true to fix bug if catalog filter is removed and added at the same time
|
||||||
return this.loadItems(() => this.entities, undefined, true);
|
return this.loadItems(() => this.entities, undefined, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onRun(entity: CatalogEntity): void {
|
||||||
|
this.registry.onRun(entity);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,8 +19,8 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import treeStyles from "./catalog-tree.module.css";
|
import treeStyles from "./catalog-tree.module.scss";
|
||||||
import styles from "./catalog-menu.module.css";
|
import styles from "./catalog-menu.module.scss";
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { TreeItem, TreeItemProps, TreeView } from "@material-ui/lab";
|
import { TreeItem, TreeItemProps, TreeView } from "@material-ui/lab";
|
||||||
|
|||||||
@ -29,7 +29,6 @@ import { kubernetesClusterCategory } from "../../../common/catalog-entities/kube
|
|||||||
import { catalogCategoryRegistry, CatalogCategoryRegistry, CatalogEntity, CatalogEntityActionContext, CatalogEntityData } from "../../../common/catalog";
|
import { catalogCategoryRegistry, CatalogCategoryRegistry, CatalogEntity, CatalogEntityActionContext, CatalogEntityData } from "../../../common/catalog";
|
||||||
import { CatalogEntityRegistry } from "../../../renderer/api/catalog-entity-registry";
|
import { CatalogEntityRegistry } from "../../../renderer/api/catalog-entity-registry";
|
||||||
import { CatalogEntityDetailRegistry } from "../../../extensions/registries";
|
import { CatalogEntityDetailRegistry } from "../../../extensions/registries";
|
||||||
import { CatalogEntityItem } from "./catalog-entity-item";
|
|
||||||
import { CatalogEntityStore } from "./catalog-entity.store";
|
import { CatalogEntityStore } from "./catalog-entity.store";
|
||||||
import { AppPaths } from "../../../common/app-paths";
|
import { AppPaths } from "../../../common/app-paths";
|
||||||
|
|
||||||
@ -130,7 +129,7 @@ describe("<Catalog />", () => {
|
|||||||
const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry);
|
const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry);
|
||||||
const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry);
|
const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry);
|
||||||
const onRun = jest.fn();
|
const onRun = jest.fn();
|
||||||
const catalogEntityItem = new CatalogEntityItem(createMockCatalogEntity(onRun), catalogEntityRegistry);
|
const catalogEntityItem = createMockCatalogEntity(onRun);
|
||||||
|
|
||||||
// mock as if there is a selected item > the detail panel opens
|
// mock as if there is a selected item > the detail panel opens
|
||||||
jest
|
jest
|
||||||
@ -166,7 +165,7 @@ describe("<Catalog />", () => {
|
|||||||
const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry);
|
const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry);
|
||||||
const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry);
|
const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry);
|
||||||
const onRun = jest.fn();
|
const onRun = jest.fn();
|
||||||
const catalogEntityItem = new CatalogEntityItem(createMockCatalogEntity(onRun), catalogEntityRegistry);
|
const catalogEntityItem = createMockCatalogEntity(onRun);
|
||||||
|
|
||||||
// mock as if there is a selected item > the detail panel opens
|
// mock as if there is a selected item > the detail panel opens
|
||||||
jest
|
jest
|
||||||
@ -200,7 +199,7 @@ describe("<Catalog />", () => {
|
|||||||
const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry);
|
const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry);
|
||||||
const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry);
|
const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry);
|
||||||
const onRun = jest.fn();
|
const onRun = jest.fn();
|
||||||
const catalogEntityItem = new CatalogEntityItem(createMockCatalogEntity(onRun), catalogEntityRegistry);
|
const catalogEntityItem = createMockCatalogEntity(onRun);
|
||||||
|
|
||||||
// mock as if there is a selected item > the detail panel opens
|
// mock as if there is a selected item > the detail panel opens
|
||||||
jest
|
jest
|
||||||
@ -235,7 +234,7 @@ describe("<Catalog />", () => {
|
|||||||
const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry);
|
const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry);
|
||||||
const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry);
|
const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry);
|
||||||
const onRun = jest.fn(() => done());
|
const onRun = jest.fn(() => done());
|
||||||
const catalogEntityItem = new CatalogEntityItem(createMockCatalogEntity(onRun), catalogEntityRegistry);
|
const catalogEntityItem = createMockCatalogEntity(onRun);
|
||||||
|
|
||||||
// mock as if there is a selected item > the detail panel opens
|
// mock as if there is a selected item > the detail panel opens
|
||||||
jest
|
jest
|
||||||
@ -265,7 +264,7 @@ describe("<Catalog />", () => {
|
|||||||
const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry);
|
const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry);
|
||||||
const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry);
|
const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry);
|
||||||
const onRun = jest.fn();
|
const onRun = jest.fn();
|
||||||
const catalogEntityItem = new CatalogEntityItem(createMockCatalogEntity(onRun), catalogEntityRegistry);
|
const catalogEntityItem = createMockCatalogEntity(onRun);
|
||||||
|
|
||||||
// mock as if there is a selected item > the detail panel opens
|
// mock as if there is a selected item > the detail panel opens
|
||||||
jest
|
jest
|
||||||
@ -302,7 +301,7 @@ describe("<Catalog />", () => {
|
|||||||
const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry);
|
const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry);
|
||||||
const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry);
|
const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry);
|
||||||
const onRun = jest.fn();
|
const onRun = jest.fn();
|
||||||
const catalogEntityItem = new CatalogEntityItem(createMockCatalogEntity(onRun), catalogEntityRegistry);
|
const catalogEntityItem = createMockCatalogEntity(onRun);
|
||||||
|
|
||||||
// mock as if there is a selected item > the detail panel opens
|
// mock as if there is a selected item > the detail panel opens
|
||||||
jest
|
jest
|
||||||
|
|||||||
@ -19,14 +19,13 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import styles from "./catalog.module.css";
|
import styles from "./catalog.module.scss";
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { disposeOnUnmount, observer } from "mobx-react";
|
import { disposeOnUnmount, observer } from "mobx-react";
|
||||||
import { ItemListLayout } from "../item-object-list";
|
import { ItemListLayout } from "../item-object-list";
|
||||||
import { action, makeObservable, observable, reaction, runInAction, when } from "mobx";
|
import { action, makeObservable, observable, reaction, runInAction, when } from "mobx";
|
||||||
import { CatalogEntityStore } from "./catalog-entity.store";
|
import { CatalogEntityStore } from "./catalog-entity.store";
|
||||||
import type { CatalogEntityItem } from "./catalog-entity-item";
|
|
||||||
import { navigate } from "../../navigation";
|
import { navigate } from "../../navigation";
|
||||||
import { MenuItem, MenuActions } from "../menu";
|
import { MenuItem, MenuActions } from "../menu";
|
||||||
import type { CatalogEntityContextMenu, CatalogEntityContextMenuContext } from "../../api/catalog-entity";
|
import type { CatalogEntityContextMenu, CatalogEntityContextMenuContext } from "../../api/catalog-entity";
|
||||||
@ -45,6 +44,8 @@ import { RenderDelay } from "../render-delay/render-delay";
|
|||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { HotbarToggleMenuItem } from "./hotbar-toggle-menu-item";
|
import { HotbarToggleMenuItem } from "./hotbar-toggle-menu-item";
|
||||||
import { Avatar } from "../avatar";
|
import { Avatar } from "../avatar";
|
||||||
|
import { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||||
|
import { getLabelBadges } from "./helpers";
|
||||||
|
|
||||||
export const previousActiveTab = createStorage("catalog-previous-active-tab", browseCatalogTab);
|
export const previousActiveTab = createStorage("catalog-previous-active-tab", browseCatalogTab);
|
||||||
|
|
||||||
@ -125,19 +126,19 @@ export class Catalog extends React.Component<Props> {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
addToHotbar(item: CatalogEntityItem<CatalogEntity>): void {
|
addToHotbar(entity: CatalogEntity): void {
|
||||||
HotbarStore.getInstance().addToHotbar(item.entity);
|
HotbarStore.getInstance().addToHotbar(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeFromHotbar(item: CatalogEntityItem<CatalogEntity>): void {
|
removeFromHotbar(entity: CatalogEntity): void {
|
||||||
HotbarStore.getInstance().removeFromHotbar(item.getId());
|
HotbarStore.getInstance().removeFromHotbar(entity.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
onDetails = (item: CatalogEntityItem<CatalogEntity>) => {
|
onDetails = (entity: CatalogEntity) => {
|
||||||
if (this.catalogEntityStore.selectedItemId) {
|
if (this.catalogEntityStore.selectedItemId) {
|
||||||
this.catalogEntityStore.selectedItemId = null;
|
this.catalogEntityStore.selectedItemId = null;
|
||||||
} else {
|
} else {
|
||||||
item.onRun();
|
this.catalogEntityStore.onRun(entity);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -179,16 +180,16 @@ export class Catalog extends React.Component<Props> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderItemMenu = (item: CatalogEntityItem<CatalogEntity>) => {
|
renderItemMenu = (entity: CatalogEntity) => {
|
||||||
const onOpen = () => {
|
const onOpen = () => {
|
||||||
this.contextMenu.menuItems = [];
|
this.contextMenu.menuItems = [];
|
||||||
|
|
||||||
item.onContextMenuOpen(this.contextMenu);
|
entity.onContextMenuOpen(this.contextMenu);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuActions onOpen={onOpen}>
|
<MenuActions onOpen={onOpen}>
|
||||||
<MenuItem key="open-details" onClick={() => this.catalogEntityStore.selectedItemId = item.getId()}>
|
<MenuItem key="open-details" onClick={() => this.catalogEntityStore.selectedItemId = entity.getId()}>
|
||||||
View Details
|
View Details
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{
|
{
|
||||||
@ -200,7 +201,7 @@ export class Catalog extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
<HotbarToggleMenuItem
|
<HotbarToggleMenuItem
|
||||||
key="hotbar-toggle"
|
key="hotbar-toggle"
|
||||||
entity={item.entity}
|
entity={entity}
|
||||||
addContent="Add to Hotbar"
|
addContent="Add to Hotbar"
|
||||||
removeContent="Remove from Hotbar"
|
removeContent="Remove from Hotbar"
|
||||||
/>
|
/>
|
||||||
@ -208,29 +209,29 @@ export class Catalog extends React.Component<Props> {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
renderName(item: CatalogEntityItem<CatalogEntity>) {
|
renderName(entity: CatalogEntity) {
|
||||||
const isItemInHotbar = HotbarStore.getInstance().isAddedToActive(item.entity);
|
const isItemInHotbar = HotbarStore.getInstance().isAddedToActive(entity);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Avatar
|
<Avatar
|
||||||
title={item.getName()}
|
title={entity.getName()}
|
||||||
colorHash={`${item.getName()}-${item.source}`}
|
colorHash={`${entity.getName()}-${entity.getSource()}`}
|
||||||
src={item.entity.spec.icon?.src}
|
src={entity.spec.icon?.src}
|
||||||
background={item.entity.spec.icon?.background}
|
background={entity.spec.icon?.background}
|
||||||
className={styles.catalogAvatar}
|
className={styles.catalogAvatar}
|
||||||
size={24}
|
size={24}
|
||||||
>
|
>
|
||||||
{item.entity.spec.icon?.material && <Icon material={item.entity.spec.icon?.material} small/>}
|
{entity.spec.icon?.material && <Icon material={entity.spec.icon?.material} small/>}
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<span>{item.name}</span>
|
<span>{entity.getName()}</span>
|
||||||
<Icon
|
<Icon
|
||||||
small
|
small
|
||||||
className={styles.pinIcon}
|
className={styles.pinIcon}
|
||||||
material={!isItemInHotbar && "push_pin"}
|
material={!isItemInHotbar && "push_pin"}
|
||||||
svg={isItemInHotbar ? "push_off" : "push_pin"}
|
svg={isItemInHotbar ? "push_off" : "push_pin"}
|
||||||
tooltip={isItemInHotbar ? "Remove from Hotbar" : "Add to Hotbar"}
|
tooltip={isItemInHotbar ? "Remove from Hotbar" : "Add to Hotbar"}
|
||||||
onClick={prevDefault(() => isItemInHotbar ? this.removeFromHotbar(item) : this.addToHotbar(item))}
|
onClick={prevDefault(() => isItemInHotbar ? this.removeFromHotbar(entity) : this.addToHotbar(entity))}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@ -253,13 +254,19 @@ export class Catalog extends React.Component<Props> {
|
|||||||
isConfigurable={true}
|
isConfigurable={true}
|
||||||
store={this.catalogEntityStore}
|
store={this.catalogEntityStore}
|
||||||
sortingCallbacks={{
|
sortingCallbacks={{
|
||||||
[sortBy.name]: item => item.name,
|
[sortBy.name]: entity => entity.getName(),
|
||||||
[sortBy.source]: item => item.source,
|
[sortBy.source]: entity => entity.getSource(),
|
||||||
[sortBy.status]: item => item.phase,
|
[sortBy.status]: entity => entity.status.phase,
|
||||||
[sortBy.kind]: item => item.kind,
|
[sortBy.kind]: entity => entity.kind,
|
||||||
}}
|
}}
|
||||||
searchFilters={[
|
searchFilters={[
|
||||||
entity => entity.searchFields,
|
entity => [
|
||||||
|
entity.getName(),
|
||||||
|
entity.getId(),
|
||||||
|
entity.status.phase,
|
||||||
|
`source=${entity.getSource()}`,
|
||||||
|
...KubeObject.stringifyLabels(entity.metadata.labels),
|
||||||
|
],
|
||||||
]}
|
]}
|
||||||
renderTableHeader={[
|
renderTableHeader={[
|
||||||
{ title: "Name", className: styles.entityName, sortBy: sortBy.name, id: "name" },
|
{ title: "Name", className: styles.entityName, sortBy: sortBy.name, id: "name" },
|
||||||
@ -268,15 +275,15 @@ export class Catalog extends React.Component<Props> {
|
|||||||
{ title: "Labels", className: `${styles.labelsCell} scrollable`, id: "labels" },
|
{ title: "Labels", className: `${styles.labelsCell} scrollable`, id: "labels" },
|
||||||
{ title: "Status", className: styles.statusCell, sortBy: sortBy.status, id: "status" },
|
{ title: "Status", className: styles.statusCell, sortBy: sortBy.status, id: "status" },
|
||||||
].filter(Boolean)}
|
].filter(Boolean)}
|
||||||
customizeTableRowProps={item => ({
|
customizeTableRowProps={entity => ({
|
||||||
disabled: !item.enabled,
|
disabled: !entity.isEnabled(),
|
||||||
})}
|
})}
|
||||||
renderTableContents={item => [
|
renderTableContents={entity => [
|
||||||
this.renderName(item),
|
this.renderName(entity),
|
||||||
!activeCategory && item.kind,
|
!activeCategory && entity.kind,
|
||||||
item.source,
|
entity.getSource(),
|
||||||
item.getLabelBadges(),
|
getLabelBadges(entity),
|
||||||
<span key="phase" className={item.phase}>{item.phase}</span>,
|
<span key="phase" className={entity.status.phase}>{entity.status.phase}</span>,
|
||||||
].filter(Boolean)}
|
].filter(Boolean)}
|
||||||
onDetails={this.onDetails}
|
onDetails={this.onDetails}
|
||||||
renderItemMenu={this.renderItemMenu}
|
renderItemMenu={this.renderItemMenu}
|
||||||
@ -289,16 +296,19 @@ export class Catalog extends React.Component<Props> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const selectedEntity = this.catalogEntityStore.selectedItem;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MainLayout sidebar={this.renderNavigation()}>
|
<MainLayout sidebar={this.renderNavigation()}>
|
||||||
<div className="p-6 h-full">
|
<div className="p-6 h-full">
|
||||||
{this.renderList()}
|
{this.renderList()}
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
this.catalogEntityStore.selectedItem
|
selectedEntity
|
||||||
? <CatalogEntityDetails
|
? <CatalogEntityDetails
|
||||||
item={this.catalogEntityStore.selectedItem}
|
entity={selectedEntity}
|
||||||
hideDetails={() => this.catalogEntityStore.selectedItemId = null}
|
hideDetails={() => this.catalogEntityStore.selectedItemId = null}
|
||||||
|
onRun={() => this.catalogEntityStore.onRun(selectedEntity)}
|
||||||
/>
|
/>
|
||||||
: (
|
: (
|
||||||
<RenderDelay>
|
<RenderDelay>
|
||||||
|
|||||||
49
src/renderer/components/+catalog/helpers.tsx
Normal file
49
src/renderer/components/+catalog/helpers.tsx
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import styles from "./catalog.module.scss";
|
||||||
|
import React from "react";
|
||||||
|
import { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||||
|
import type { CatalogEntity } from "../../api/catalog-entity";
|
||||||
|
import { Badge } from "../badge";
|
||||||
|
import { searchUrlParam } from "../input";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param entity The entity to render badge labels for
|
||||||
|
*/
|
||||||
|
export function getLabelBadges(entity: CatalogEntity, onClick?: (evt: React.MouseEvent<any, MouseEvent>) => void) {
|
||||||
|
return KubeObject.stringifyLabels(entity.metadata.labels)
|
||||||
|
.map(label => (
|
||||||
|
<Badge
|
||||||
|
scrollable
|
||||||
|
className={styles.badge}
|
||||||
|
key={label}
|
||||||
|
label={label}
|
||||||
|
title={label}
|
||||||
|
onClick={(event) => {
|
||||||
|
searchUrlParam.set(label);
|
||||||
|
onClick?.(event);
|
||||||
|
event.stopPropagation();
|
||||||
|
}}
|
||||||
|
expandable={false}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
}
|
||||||
@ -60,15 +60,15 @@
|
|||||||
flex-grow: 2;
|
flex-grow: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.noIssues {
|
.noIssues {
|
||||||
.ok-title {
|
.Icon {
|
||||||
font-size: large;
|
color: white;
|
||||||
color: var(--textColorAccent);
|
}
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.allGood {
|
.title {
|
||||||
color: white;
|
font-size: large;
|
||||||
}
|
color: var(--textColorAccent);
|
||||||
}
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -19,7 +19,7 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import styles from "./cluster-issues.module.css";
|
import styles from "./cluster-issues.module.scss";
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
@ -149,9 +149,9 @@ export class ClusterIssues extends React.Component<Props> {
|
|||||||
if (!warnings.length) {
|
if (!warnings.length) {
|
||||||
return (
|
return (
|
||||||
<div className={cssNames(styles.noIssues, "flex column box grow gaps align-center justify-center")}>
|
<div className={cssNames(styles.noIssues, "flex column box grow gaps align-center justify-center")}>
|
||||||
<div><Icon className={styles.allGood} material="check" big sticker/></div>
|
<Icon className={styles.Icon} material="check" big sticker/>
|
||||||
<div className="ok-title">No issues found</div>
|
<p className={styles.title}>No issues found</p>
|
||||||
<span>Everything is fine in the Cluster</span>
|
<p>Everything is fine in the Cluster</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import styles from "./cluster-metrics.module.css";
|
import styles from "./cluster-metrics.module.scss";
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import styles from "./cluster-overview.module.css";
|
import styles from "./cluster-overview.module.scss";
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { reaction } from "mobx";
|
import { reaction } from "mobx";
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import styles from "./cluster-pie-charts.module.css";
|
import styles from "./cluster-pie-charts.module.scss";
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import styles from "./entity-settings.module.css";
|
import styles from "./entity-settings.module.scss";
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { observable, makeObservable } from "mobx";
|
import { observable, makeObservable } from "mobx";
|
||||||
|
|||||||
@ -18,20 +18,19 @@
|
|||||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
import type { Injectable } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import { lifecycleEnum } from "@ogre-tools/injectable";
|
import { attemptInstallByInfo } from "./attempt-install-by-info";
|
||||||
import { attemptInstallByInfo, ExtensionInfo } from "./attempt-install-by-info";
|
|
||||||
import attemptInstallInjectable from "../attempt-install/attempt-install.injectable";
|
import attemptInstallInjectable from "../attempt-install/attempt-install.injectable";
|
||||||
import getBaseRegistryUrlInjectable from "../get-base-registry-url/get-base-registry-url.injectable";
|
import getBaseRegistryUrlInjectable from "../get-base-registry-url/get-base-registry-url.injectable";
|
||||||
|
|
||||||
const attemptInstallByInfoInjectable: Injectable<(extensionInfo: ExtensionInfo) => Promise<void>, {}> = {
|
const attemptInstallByInfoInjectable = getInjectable({
|
||||||
getDependencies: di => ({
|
instantiate: (di) =>
|
||||||
attemptInstall: di.inject(attemptInstallInjectable),
|
attemptInstallByInfo({
|
||||||
getBaseRegistryUrl: di.inject(getBaseRegistryUrlInjectable),
|
attemptInstall: di.inject(attemptInstallInjectable),
|
||||||
}),
|
getBaseRegistryUrl: di.inject(getBaseRegistryUrlInjectable),
|
||||||
|
}),
|
||||||
|
|
||||||
instantiate: attemptInstallByInfo,
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
};
|
});
|
||||||
|
|
||||||
export default attemptInstallByInfoInjectable;
|
export default attemptInstallByInfoInjectable;
|
||||||
|
|||||||
@ -35,7 +35,7 @@ export interface ExtensionInfo {
|
|||||||
requireConfirmation?: boolean;
|
requireConfirmation?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Dependencies {
|
interface Dependencies {
|
||||||
attemptInstall: (request: InstallRequest, d: ExtendableDisposer) => Promise<void>;
|
attemptInstall: (request: InstallRequest, d: ExtendableDisposer) => Promise<void>;
|
||||||
getBaseRegistryUrl: () => Promise<string>;
|
getBaseRegistryUrl: () => Promise<string>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,29 +18,21 @@
|
|||||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
import type { Injectable } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import { lifecycleEnum } from "@ogre-tools/injectable";
|
|
||||||
|
|
||||||
import type { ExtendableDisposer } from "../../../../common/utils";
|
|
||||||
import extensionLoaderInjectable from "../../../../extensions/extension-loader/extension-loader.injectable";
|
import extensionLoaderInjectable from "../../../../extensions/extension-loader/extension-loader.injectable";
|
||||||
import uninstallExtensionInjectable from "../uninstall-extension/uninstall-extension.injectable";
|
import uninstallExtensionInjectable from "../uninstall-extension/uninstall-extension.injectable";
|
||||||
import type { Dependencies } from "./attempt-install";
|
|
||||||
import { attemptInstall } from "./attempt-install";
|
import { attemptInstall } from "./attempt-install";
|
||||||
import type { InstallRequest } from "./install-request";
|
|
||||||
import unpackExtensionInjectable from "./unpack-extension/unpack-extension.injectable";
|
import unpackExtensionInjectable from "./unpack-extension/unpack-extension.injectable";
|
||||||
|
|
||||||
const attemptInstallInjectable: Injectable<
|
const attemptInstallInjectable = getInjectable({
|
||||||
(request: InstallRequest, d?: ExtendableDisposer) => Promise<void>,
|
instantiate: (di) =>
|
||||||
Dependencies
|
attemptInstall({
|
||||||
> = {
|
extensionLoader: di.inject(extensionLoaderInjectable),
|
||||||
getDependencies: di => ({
|
uninstallExtension: di.inject(uninstallExtensionInjectable),
|
||||||
extensionLoader: di.inject(extensionLoaderInjectable),
|
unpackExtension: di.inject(unpackExtensionInjectable),
|
||||||
uninstallExtension: di.inject(uninstallExtensionInjectable),
|
}),
|
||||||
unpackExtension: di.inject(unpackExtensionInjectable),
|
|
||||||
}),
|
|
||||||
|
|
||||||
instantiate: attemptInstall,
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
};
|
});
|
||||||
|
|
||||||
export default attemptInstallInjectable;
|
export default attemptInstallInjectable;
|
||||||
|
|||||||
@ -41,7 +41,7 @@ import {
|
|||||||
import { getExtensionDestFolder } from "./get-extension-dest-folder/get-extension-dest-folder";
|
import { getExtensionDestFolder } from "./get-extension-dest-folder/get-extension-dest-folder";
|
||||||
import type { InstallRequest } from "./install-request";
|
import type { InstallRequest } from "./install-request";
|
||||||
|
|
||||||
export interface Dependencies {
|
interface Dependencies {
|
||||||
extensionLoader: ExtensionLoader;
|
extensionLoader: ExtensionLoader;
|
||||||
uninstallExtension: (id: LensExtensionId) => Promise<boolean>;
|
uninstallExtension: (id: LensExtensionId) => Promise<boolean>;
|
||||||
unpackExtension: (
|
unpackExtension: (
|
||||||
|
|||||||
@ -18,26 +18,17 @@
|
|||||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
import type { Injectable } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import { lifecycleEnum } from "@ogre-tools/injectable";
|
import { unpackExtension } from "./unpack-extension";
|
||||||
import { Dependencies, unpackExtension } from "./unpack-extension";
|
|
||||||
import type { InstallRequestValidated } from "../create-temp-files-and-validate/create-temp-files-and-validate";
|
|
||||||
import type { Disposer } from "../../../../../common/utils";
|
|
||||||
import extensionLoaderInjectable from "../../../../../extensions/extension-loader/extension-loader.injectable";
|
import extensionLoaderInjectable from "../../../../../extensions/extension-loader/extension-loader.injectable";
|
||||||
|
|
||||||
const unpackExtensionInjectable: Injectable<
|
const unpackExtensionInjectable = getInjectable({
|
||||||
(
|
instantiate: (di) =>
|
||||||
request: InstallRequestValidated,
|
unpackExtension({
|
||||||
disposeDownloading?: Disposer,
|
extensionLoader: di.inject(extensionLoaderInjectable),
|
||||||
) => Promise<void>,
|
}),
|
||||||
Dependencies
|
|
||||||
> = {
|
|
||||||
getDependencies: di => ({
|
|
||||||
extensionLoader: di.inject(extensionLoaderInjectable),
|
|
||||||
}),
|
|
||||||
|
|
||||||
instantiate: unpackExtension,
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
};
|
});
|
||||||
|
|
||||||
export default unpackExtensionInjectable;
|
export default unpackExtensionInjectable;
|
||||||
|
|||||||
@ -32,7 +32,7 @@ import fse from "fs-extra";
|
|||||||
import { when } from "mobx";
|
import { when } from "mobx";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
export interface Dependencies {
|
interface Dependencies {
|
||||||
extensionLoader: ExtensionLoader
|
extensionLoader: ExtensionLoader
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -18,21 +18,17 @@
|
|||||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
import type { Injectable } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import { lifecycleEnum } from "@ogre-tools/injectable";
|
import { attemptInstalls } from "./attempt-installs";
|
||||||
import { attemptInstalls, Dependencies } from "./attempt-installs";
|
|
||||||
import attemptInstallInjectable from "../attempt-install/attempt-install.injectable";
|
import attemptInstallInjectable from "../attempt-install/attempt-install.injectable";
|
||||||
|
|
||||||
const attemptInstallsInjectable: Injectable<
|
const attemptInstallsInjectable = getInjectable({
|
||||||
(filePaths: string[]) => Promise<void>,
|
instantiate: (di) =>
|
||||||
Dependencies
|
attemptInstalls({
|
||||||
> = {
|
attemptInstall: di.inject(attemptInstallInjectable),
|
||||||
getDependencies: di => ({
|
}),
|
||||||
attemptInstall: di.inject(attemptInstallInjectable),
|
|
||||||
}),
|
|
||||||
|
|
||||||
instantiate: attemptInstalls,
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
};
|
});
|
||||||
|
|
||||||
export default attemptInstallsInjectable;
|
export default attemptInstallsInjectable;
|
||||||
|
|||||||
@ -22,7 +22,7 @@ import { readFileNotify } from "../read-file-notify/read-file-notify";
|
|||||||
import path from "path";
|
import path from "path";
|
||||||
import type { InstallRequest } from "../attempt-install/install-request";
|
import type { InstallRequest } from "../attempt-install/install-request";
|
||||||
|
|
||||||
export interface Dependencies {
|
interface Dependencies {
|
||||||
attemptInstall: (request: InstallRequest) => Promise<void>;
|
attemptInstall: (request: InstallRequest) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -18,25 +18,17 @@
|
|||||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
import type { Injectable } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import { lifecycleEnum } from "@ogre-tools/injectable";
|
import { confirmUninstallExtension } from "./confirm-uninstall-extension";
|
||||||
import {
|
|
||||||
confirmUninstallExtension,
|
|
||||||
Dependencies,
|
|
||||||
} from "./confirm-uninstall-extension";
|
|
||||||
import type { InstalledExtension } from "../../../../extensions/extension-discovery";
|
|
||||||
import uninstallExtensionInjectable from "../uninstall-extension/uninstall-extension.injectable";
|
import uninstallExtensionInjectable from "../uninstall-extension/uninstall-extension.injectable";
|
||||||
|
|
||||||
const confirmUninstallExtensionInjectable: Injectable<
|
const confirmUninstallExtensionInjectable = getInjectable({
|
||||||
(extension: InstalledExtension) => Promise<void>,
|
instantiate: (di) =>
|
||||||
Dependencies
|
confirmUninstallExtension({
|
||||||
> = {
|
uninstallExtension: di.inject(uninstallExtensionInjectable),
|
||||||
getDependencies: di => ({
|
}),
|
||||||
uninstallExtension: di.inject(uninstallExtensionInjectable),
|
|
||||||
}),
|
|
||||||
|
|
||||||
instantiate: confirmUninstallExtension,
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
};
|
});
|
||||||
|
|
||||||
export default confirmUninstallExtensionInjectable;
|
export default confirmUninstallExtensionInjectable;
|
||||||
|
|||||||
@ -24,8 +24,8 @@ import type { LensExtensionId } from "../../../../extensions/lens-extension";
|
|||||||
import { extensionDisplayName } from "../../../../extensions/lens-extension";
|
import { extensionDisplayName } from "../../../../extensions/lens-extension";
|
||||||
import { ConfirmDialog } from "../../confirm-dialog";
|
import { ConfirmDialog } from "../../confirm-dialog";
|
||||||
|
|
||||||
export interface Dependencies {
|
interface Dependencies {
|
||||||
uninstallExtension: (id: LensExtensionId) => Promise<void>;
|
uninstallExtension: (id: LensExtensionId) => Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const confirmUninstallExtension =
|
export const confirmUninstallExtension =
|
||||||
|
|||||||
@ -18,23 +18,17 @@
|
|||||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
import type { Injectable } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import { lifecycleEnum } from "@ogre-tools/injectable";
|
|
||||||
import extensionLoaderInjectable from "../../../../extensions/extension-loader/extension-loader.injectable";
|
import extensionLoaderInjectable from "../../../../extensions/extension-loader/extension-loader.injectable";
|
||||||
import type { LensExtensionId } from "../../../../extensions/lens-extension";
|
import { disableExtension } from "./disable-extension";
|
||||||
import { Dependencies, disableExtension } from "./disable-extension";
|
|
||||||
|
|
||||||
const disableExtensionInjectable: Injectable<
|
const disableExtensionInjectable = getInjectable({
|
||||||
(id: LensExtensionId) => void,
|
instantiate: (di) =>
|
||||||
Dependencies
|
disableExtension({
|
||||||
> = {
|
extensionLoader: di.inject(extensionLoaderInjectable),
|
||||||
getDependencies: di => ({
|
}),
|
||||||
extensionLoader: di.inject(extensionLoaderInjectable),
|
|
||||||
}),
|
|
||||||
|
|
||||||
instantiate: disableExtension,
|
|
||||||
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
};
|
});
|
||||||
|
|
||||||
export default disableExtensionInjectable;
|
export default disableExtensionInjectable;
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
import type { LensExtensionId } from "../../../../extensions/lens-extension";
|
import type { LensExtensionId } from "../../../../extensions/lens-extension";
|
||||||
import type { ExtensionLoader } from "../../../../extensions/extension-loader";
|
import type { ExtensionLoader } from "../../../../extensions/extension-loader";
|
||||||
|
|
||||||
export interface Dependencies {
|
interface Dependencies {
|
||||||
extensionLoader: ExtensionLoader;
|
extensionLoader: ExtensionLoader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -18,23 +18,17 @@
|
|||||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
import type { Injectable } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import { lifecycleEnum } from "@ogre-tools/injectable";
|
|
||||||
import extensionLoaderInjectable from "../../../../extensions/extension-loader/extension-loader.injectable";
|
import extensionLoaderInjectable from "../../../../extensions/extension-loader/extension-loader.injectable";
|
||||||
import type { LensExtensionId } from "../../../../extensions/lens-extension";
|
import { enableExtension } from "./enable-extension";
|
||||||
import { Dependencies, enableExtension } from "./enable-extension";
|
|
||||||
|
|
||||||
const enableExtensionInjectable: Injectable<
|
const enableExtensionInjectable = getInjectable({
|
||||||
(id: LensExtensionId) => void,
|
instantiate: (di) =>
|
||||||
Dependencies
|
enableExtension({
|
||||||
> = {
|
extensionLoader: di.inject(extensionLoaderInjectable),
|
||||||
getDependencies: di => ({
|
}),
|
||||||
extensionLoader: di.inject(extensionLoaderInjectable),
|
|
||||||
}),
|
|
||||||
|
|
||||||
instantiate: enableExtension,
|
|
||||||
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
};
|
});
|
||||||
|
|
||||||
export default enableExtensionInjectable;
|
export default enableExtensionInjectable;
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
import type { LensExtensionId } from "../../../../extensions/lens-extension";
|
import type { LensExtensionId } from "../../../../extensions/lens-extension";
|
||||||
import type { ExtensionLoader } from "../../../../extensions/extension-loader";
|
import type { ExtensionLoader } from "../../../../extensions/extension-loader";
|
||||||
|
|
||||||
export interface Dependencies {
|
interface Dependencies {
|
||||||
extensionLoader: ExtensionLoader;
|
extensionLoader: ExtensionLoader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -19,18 +19,20 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Injectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import {
|
||||||
|
getInjectable,
|
||||||
|
lifecycleEnum,
|
||||||
|
} from "@ogre-tools/injectable";
|
||||||
import { UserStore } from "../../../../common/user-store";
|
import { UserStore } from "../../../../common/user-store";
|
||||||
import { Dependencies, getBaseRegistryUrl } from "./get-base-registry-url";
|
import { getBaseRegistryUrl } from "./get-base-registry-url";
|
||||||
|
|
||||||
const getBaseRegistryUrlInjectable: Injectable<() => Promise<string>, Dependencies> = {
|
const getBaseRegistryUrlInjectable = getInjectable({
|
||||||
getDependencies: () => ({
|
instantiate: () => getBaseRegistryUrl({
|
||||||
// TODO: use injection
|
// TODO: use injection
|
||||||
getRegistryUrlPreference: () => UserStore.getInstance().extensionRegistryUrl,
|
getRegistryUrlPreference: () => UserStore.getInstance().extensionRegistryUrl,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
instantiate: getBaseRegistryUrl,
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
};
|
});
|
||||||
|
|
||||||
export default getBaseRegistryUrlInjectable;
|
export default getBaseRegistryUrlInjectable;
|
||||||
|
|||||||
@ -24,7 +24,7 @@ import { defaultExtensionRegistryUrl, ExtensionRegistry, ExtensionRegistryLocati
|
|||||||
import { promiseExecFile } from "../../../utils";
|
import { promiseExecFile } from "../../../utils";
|
||||||
import { Notifications } from "../../notifications";
|
import { Notifications } from "../../notifications";
|
||||||
|
|
||||||
export interface Dependencies {
|
interface Dependencies {
|
||||||
getRegistryUrlPreference: () => ExtensionRegistry,
|
getRegistryUrlPreference: () => ExtensionRegistry,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -18,25 +18,19 @@
|
|||||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
import type { Injectable } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import { lifecycleEnum } from "@ogre-tools/injectable";
|
|
||||||
import attemptInstallInjectable from "../attempt-install/attempt-install.injectable";
|
import attemptInstallInjectable from "../attempt-install/attempt-install.injectable";
|
||||||
import type { Dependencies } from "./install-from-input";
|
|
||||||
import { installFromInput } from "./install-from-input";
|
import { installFromInput } from "./install-from-input";
|
||||||
import attemptInstallByInfoInjectable
|
import attemptInstallByInfoInjectable from "../attempt-install-by-info/attempt-install-by-info.injectable";
|
||||||
from "../attempt-install-by-info/attempt-install-by-info.injectable";
|
|
||||||
|
|
||||||
const installFromInputInjectable: Injectable<
|
const installFromInputInjectable = getInjectable({
|
||||||
(input: string) => Promise<void>,
|
instantiate: (di) =>
|
||||||
Dependencies
|
installFromInput({
|
||||||
> = {
|
attemptInstall: di.inject(attemptInstallInjectable),
|
||||||
getDependencies: di => ({
|
attemptInstallByInfo: di.inject(attemptInstallByInfoInjectable),
|
||||||
attemptInstall: di.inject(attemptInstallInjectable),
|
}),
|
||||||
attemptInstallByInfo: di.inject(attemptInstallByInfoInjectable),
|
|
||||||
}),
|
|
||||||
|
|
||||||
instantiate: installFromInput,
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
};
|
});
|
||||||
|
|
||||||
export default installFromInputInjectable;
|
export default installFromInputInjectable;
|
||||||
|
|||||||
@ -30,7 +30,7 @@ import { readFileNotify } from "../read-file-notify/read-file-notify";
|
|||||||
import type { InstallRequest } from "../attempt-install/install-request";
|
import type { InstallRequest } from "../attempt-install/install-request";
|
||||||
import type { ExtensionInfo } from "../attempt-install-by-info/attempt-install-by-info";
|
import type { ExtensionInfo } from "../attempt-install-by-info/attempt-install-by-info";
|
||||||
|
|
||||||
export interface Dependencies {
|
interface Dependencies {
|
||||||
attemptInstall: (request: InstallRequest, disposer?: ExtendableDisposer) => Promise<void>,
|
attemptInstall: (request: InstallRequest, disposer?: ExtendableDisposer) => Promise<void>,
|
||||||
attemptInstallByInfo: (extensionInfo: ExtensionInfo) => Promise<void>
|
attemptInstallByInfo: (extensionInfo: ExtensionInfo) => Promise<void>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,18 +18,17 @@
|
|||||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
import type { Injectable } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import { lifecycleEnum } from "@ogre-tools/injectable";
|
import { installFromSelectFileDialog } from "./install-from-select-file-dialog";
|
||||||
import { Dependencies, installFromSelectFileDialog } from "./install-from-select-file-dialog";
|
|
||||||
import attemptInstallsInjectable from "../attempt-installs/attempt-installs.injectable";
|
import attemptInstallsInjectable from "../attempt-installs/attempt-installs.injectable";
|
||||||
|
|
||||||
const installFromSelectFileDialogInjectable: Injectable<() => Promise<void>, Dependencies> = {
|
const installFromSelectFileDialogInjectable = getInjectable({
|
||||||
getDependencies: di => ({
|
instantiate: (di) =>
|
||||||
attemptInstalls: di.inject(attemptInstallsInjectable),
|
installFromSelectFileDialog({
|
||||||
}),
|
attemptInstalls: di.inject(attemptInstallsInjectable),
|
||||||
|
}),
|
||||||
instantiate: installFromSelectFileDialog,
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
};
|
});
|
||||||
|
|
||||||
export default installFromSelectFileDialogInjectable;
|
export default installFromSelectFileDialogInjectable;
|
||||||
|
|||||||
@ -22,7 +22,7 @@ import { dialog } from "../../../remote-helpers";
|
|||||||
import { AppPaths } from "../../../../common/app-paths";
|
import { AppPaths } from "../../../../common/app-paths";
|
||||||
import { supportedExtensionFormats } from "../supported-extension-formats";
|
import { supportedExtensionFormats } from "../supported-extension-formats";
|
||||||
|
|
||||||
export interface Dependencies {
|
interface Dependencies {
|
||||||
attemptInstalls: (filePaths: string[]) => Promise<void>
|
attemptInstalls: (filePaths: string[]) => Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -18,21 +18,17 @@
|
|||||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
import type { Injectable } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import { lifecycleEnum } from "@ogre-tools/injectable";
|
import { installOnDrop } from "./install-on-drop";
|
||||||
import { Dependencies, installOnDrop } from "./install-on-drop";
|
|
||||||
import attemptInstallsInjectable from "../attempt-installs/attempt-installs.injectable";
|
import attemptInstallsInjectable from "../attempt-installs/attempt-installs.injectable";
|
||||||
|
|
||||||
const installOnDropInjectable: Injectable<
|
const installOnDropInjectable = getInjectable({
|
||||||
(files: File[]) => Promise<void>,
|
instantiate: (di) =>
|
||||||
Dependencies
|
installOnDrop({
|
||||||
> = {
|
attemptInstalls: di.inject(attemptInstallsInjectable),
|
||||||
getDependencies: di => ({
|
}),
|
||||||
attemptInstalls: di.inject(attemptInstallsInjectable),
|
|
||||||
}),
|
|
||||||
|
|
||||||
instantiate: installOnDrop,
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
};
|
});
|
||||||
|
|
||||||
export default installOnDropInjectable;
|
export default installOnDropInjectable;
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
import logger from "../../../../main/logger";
|
import logger from "../../../../main/logger";
|
||||||
|
|
||||||
export interface Dependencies {
|
interface Dependencies {
|
||||||
attemptInstalls: (filePaths: string[]) => Promise<void>;
|
attemptInstalls: (filePaths: string[]) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import styles from "./install.module.css";
|
import styles from "./install.module.scss";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { prevDefault } from "../../utils";
|
import { prevDefault } from "../../utils";
|
||||||
import { Button } from "../button";
|
import { Button } from "../button";
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import styles from "./installed-extensions.module.css";
|
import styles from "./installed-extensions.module.scss";
|
||||||
import React, { useMemo } from "react";
|
import React, { useMemo } from "react";
|
||||||
import { ExtensionDiscovery, InstalledExtension } from "../../../extensions/extension-discovery";
|
import { ExtensionDiscovery, InstalledExtension } from "../../../extensions/extension-discovery";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import styles from "./notice.module.css";
|
import styles from "./notice.module.scss";
|
||||||
import React, { DOMAttributes } from "react";
|
import React, { DOMAttributes } from "react";
|
||||||
import { cssNames } from "../../utils";
|
import { cssNames } from "../../utils";
|
||||||
|
|
||||||
|
|||||||
@ -18,23 +18,17 @@
|
|||||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
import type { Injectable } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import { lifecycleEnum } from "@ogre-tools/injectable";
|
import extensionLoaderInjectable from "../../../../extensions/extension-loader/extension-loader.injectable";
|
||||||
import type { LensExtensionId } from "../../../../extensions/lens-extension";
|
import { uninstallExtension } from "./uninstall-extension";
|
||||||
import extensionLoaderInjectable
|
|
||||||
from "../../../../extensions/extension-loader/extension-loader.injectable";
|
|
||||||
import { Dependencies, uninstallExtension } from "./uninstall-extension";
|
|
||||||
|
|
||||||
const uninstallExtensionInjectable: Injectable<
|
const uninstallExtensionInjectable = getInjectable({
|
||||||
(extensionId: LensExtensionId) => Promise<boolean>,
|
instantiate: (di) =>
|
||||||
Dependencies
|
uninstallExtension({
|
||||||
> = {
|
extensionLoader: di.inject(extensionLoaderInjectable),
|
||||||
getDependencies: di => ({
|
}),
|
||||||
extensionLoader: di.inject(extensionLoaderInjectable),
|
|
||||||
}),
|
|
||||||
|
|
||||||
instantiate: uninstallExtension,
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
};
|
});
|
||||||
|
|
||||||
export default uninstallExtensionInjectable;
|
export default uninstallExtensionInjectable;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user