mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge branch 'master' into logs-search
This commit is contained in:
commit
4caddf5046
@ -43,6 +43,8 @@ jobs:
|
|||||||
displayName: Build bundled extensions
|
displayName: Build bundled extensions
|
||||||
- script: make integration-win
|
- script: make integration-win
|
||||||
displayName: Run integration tests
|
displayName: Run integration tests
|
||||||
|
- script: make test-extensions
|
||||||
|
displayName: Run In-tree Extension tests
|
||||||
- script: make build
|
- script: make build
|
||||||
condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))"
|
condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))"
|
||||||
displayName: Build
|
displayName: Build
|
||||||
@ -86,6 +88,8 @@ jobs:
|
|||||||
displayName: Run tests
|
displayName: Run tests
|
||||||
- script: make integration-mac
|
- script: make integration-mac
|
||||||
displayName: Run integration tests
|
displayName: Run integration tests
|
||||||
|
- script: make test-extensions
|
||||||
|
displayName: Run In-tree Extension tests
|
||||||
- script: make build
|
- script: make build
|
||||||
condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))"
|
condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))"
|
||||||
displayName: Build
|
displayName: Build
|
||||||
@ -127,6 +131,8 @@ jobs:
|
|||||||
condition: eq(variables.CACHE_RESTORED, 'true')
|
condition: eq(variables.CACHE_RESTORED, 'true')
|
||||||
- script: make install-deps
|
- script: make install-deps
|
||||||
displayName: Install dependencies
|
displayName: Install dependencies
|
||||||
|
- script: make test-extensions
|
||||||
|
displayName: Run In-tree Extension tests
|
||||||
- script: make lint
|
- script: make lint
|
||||||
displayName: Lint
|
displayName: Lint
|
||||||
- script: make build-npm
|
- script: make build-npm
|
||||||
|
|||||||
3
.github/workflows/labeler.yml
vendored
3
.github/workflows/labeler.yml
vendored
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: "Pull Request Labeler"
|
name: "Pull Request Labeler"
|
||||||
|
|
||||||
'on':
|
on:
|
||||||
- pull_request
|
- pull_request
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@ -9,6 +9,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/labeler@v2
|
- uses: actions/labeler@v2
|
||||||
|
if: github.repository == 'lensapp/lens'
|
||||||
with:
|
with:
|
||||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
configuration-path: .github/labeler-config.yml
|
configuration-path: .github/labeler-config.yml
|
||||||
|
|||||||
15
Makefile
15
Makefile
@ -33,15 +33,15 @@ lint:
|
|||||||
test: download-bins
|
test: download-bins
|
||||||
yarn test
|
yarn test
|
||||||
|
|
||||||
integration-linux:
|
integration-linux: build-extension-types build-extensions
|
||||||
yarn build:linux
|
yarn build:linux
|
||||||
yarn integration
|
yarn integration
|
||||||
|
|
||||||
integration-mac:
|
integration-mac: build-extension-types build-extensions
|
||||||
yarn build:mac
|
yarn build:mac
|
||||||
yarn integration
|
yarn integration
|
||||||
|
|
||||||
integration-win:
|
integration-win: build-extension-types build-extensions
|
||||||
yarn build:win
|
yarn build:win
|
||||||
yarn integration
|
yarn integration
|
||||||
|
|
||||||
@ -58,10 +58,15 @@ endif
|
|||||||
build-extensions:
|
build-extensions:
|
||||||
$(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), $(MAKE) -C $(dir) build;)
|
$(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), $(MAKE) -C $(dir) build;)
|
||||||
|
|
||||||
build-npm:
|
test-extensions:
|
||||||
yarn compile:extension-types
|
$(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), $(MAKE) -C $(dir) test;)
|
||||||
|
|
||||||
|
build-npm: build-extension-types
|
||||||
yarn npm:fix-package-version
|
yarn npm:fix-package-version
|
||||||
|
|
||||||
|
build-extension-types:
|
||||||
|
yarn compile:extension-types
|
||||||
|
|
||||||
publish-npm: build-npm
|
publish-npm: build-npm
|
||||||
npm config set '//registry.npmjs.org/:_authToken' "${NPM_TOKEN}"
|
npm config set '//registry.npmjs.org/:_authToken' "${NPM_TOKEN}"
|
||||||
cd src/extensions/npm/extensions && npm publish --access=public
|
cd src/extensions/npm/extensions && npm publish --access=public
|
||||||
|
|||||||
@ -6,4 +6,4 @@ import appInfo from "../package.json"
|
|||||||
const packagePath = path.join(__dirname, "../src/extensions/npm/extensions/package.json")
|
const packagePath = path.join(__dirname, "../src/extensions/npm/extensions/package.json")
|
||||||
|
|
||||||
packageInfo.version = appInfo.version
|
packageInfo.version = appInfo.version
|
||||||
fs.writeFileSync(packagePath, JSON.stringify(packageInfo, null, 2))
|
fs.writeFileSync(packagePath, JSON.stringify(packageInfo, null, 2) + "\n")
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
install-deps:
|
install-deps:
|
||||||
npm install
|
yarn install
|
||||||
|
|
||||||
build: install-deps
|
build: install-deps
|
||||||
npm run build
|
yarn run build
|
||||||
|
|
||||||
|
test:
|
||||||
|
yarn run test
|
||||||
|
|||||||
@ -10,7 +10,8 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack --config webpack.config.js",
|
"build": "webpack --config webpack.config.js",
|
||||||
"dev": "npm run build --watch"
|
"dev": "npm run build --watch",
|
||||||
|
"test": "echo NO TESTS"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react-open-doodles": "^1.0.5"
|
"react-open-doodles": "^1.0.5"
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
install-deps:
|
install-deps:
|
||||||
npm install
|
yarn install
|
||||||
|
|
||||||
build: install-deps
|
build: install-deps
|
||||||
npm run build
|
yarn run build
|
||||||
|
|
||||||
|
test:
|
||||||
|
yarn run test
|
||||||
|
|||||||
@ -5,7 +5,8 @@
|
|||||||
"main": "dist/main.js",
|
"main": "dist/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack -p",
|
"build": "webpack -p",
|
||||||
"dev": "webpack --watch"
|
"dev": "webpack --watch",
|
||||||
|
"test": "echo NO TESTS"
|
||||||
},
|
},
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
install-deps:
|
install-deps:
|
||||||
npm install
|
yarn install
|
||||||
|
|
||||||
build: install-deps
|
build: install-deps
|
||||||
npm run build
|
yarn run build
|
||||||
|
|
||||||
|
test:
|
||||||
|
yarn run test
|
||||||
|
|||||||
@ -9,7 +9,8 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack --config webpack.config.js",
|
"build": "webpack --config webpack.config.js",
|
||||||
"dev": "npm run build --watch"
|
"dev": "npm run build --watch",
|
||||||
|
"test": "echo NO TESTS"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"semver": "^7.3.2"
|
"semver": "^7.3.2"
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
install-deps:
|
install-deps:
|
||||||
npm install
|
yarn install
|
||||||
|
|
||||||
build: install-deps
|
build: install-deps
|
||||||
npm run build
|
yarn run build
|
||||||
|
|
||||||
|
test:
|
||||||
|
yarn run test
|
||||||
|
|||||||
@ -9,7 +9,8 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack --config webpack.config.js",
|
"build": "webpack --config webpack.config.js",
|
||||||
"dev": "npm run build --watch"
|
"dev": "npm run build --watch",
|
||||||
|
"test": "echo NO TESTS"
|
||||||
},
|
},
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
install-deps:
|
install-deps:
|
||||||
npm install
|
yarn install
|
||||||
|
|
||||||
build: install-deps
|
build: install-deps
|
||||||
npm run build
|
yarn run build
|
||||||
|
|
||||||
|
test:
|
||||||
|
yarn run test
|
||||||
|
|||||||
@ -9,7 +9,8 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack --config webpack.config.js",
|
"build": "webpack --config webpack.config.js",
|
||||||
"dev": "npm run build --watch"
|
"dev": "npm run build --watch",
|
||||||
|
"test": "echo NO TESTS"
|
||||||
},
|
},
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
install-deps:
|
install-deps:
|
||||||
npm install
|
yarn install
|
||||||
|
|
||||||
build: install-deps
|
build: install-deps
|
||||||
npm run build
|
yarn run build
|
||||||
|
|
||||||
|
test:
|
||||||
|
yarn run test
|
||||||
|
|||||||
6
extensions/support-page/package-lock.json
generated
6
extensions/support-page/package-lock.json
generated
@ -26,12 +26,6 @@
|
|||||||
"integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==",
|
"integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/node": {
|
|
||||||
"version": "14.11.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.11.tgz",
|
|
||||||
"integrity": "sha512-UcaAZrL8uO5GNS+NLxkYg1RiOMgdLxCXGqs+TTupltXN8rTvUEKTOpqCV3tlcAIZJXzcBQajzmjdrvuPvnuMUw==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"@types/prop-types": {
|
"@types/prop-types": {
|
||||||
"version": "15.7.3",
|
"version": "15.7.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
|
||||||
|
|||||||
@ -6,12 +6,12 @@
|
|||||||
"renderer": "dist/renderer.js",
|
"renderer": "dist/renderer.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack -p",
|
"build": "webpack -p",
|
||||||
"dev": "webpack --watch"
|
"dev": "webpack --watch",
|
||||||
|
"test": "echo NO TESTS"
|
||||||
},
|
},
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
||||||
"@types/node": "^14.11.11",
|
|
||||||
"@types/react": "^16.9.53",
|
"@types/react": "^16.9.53",
|
||||||
"@types/react-router": "^5.1.8",
|
"@types/react-router": "^5.1.8",
|
||||||
"@types/webpack": "^4.41.17",
|
"@types/webpack": "^4.41.17",
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
install-deps:
|
install-deps:
|
||||||
npm install
|
yarn install
|
||||||
|
|
||||||
build: install-deps
|
build: install-deps
|
||||||
npm run build
|
yarn run build
|
||||||
|
|
||||||
|
test:
|
||||||
|
yarn run test
|
||||||
|
|||||||
12
extensions/telemetry/package-lock.json
generated
12
extensions/telemetry/package-lock.json
generated
@ -8,12 +8,6 @@
|
|||||||
"version": "file:../../src/extensions/npm/extensions",
|
"version": "file:../../src/extensions/npm/extensions",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/analytics-node": {
|
|
||||||
"version": "3.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/analytics-node/-/analytics-node-3.1.3.tgz",
|
|
||||||
"integrity": "sha512-Yk299LUqnyJ6fNYQkLFd0yTfUwIvgfxH3f5WEX3ib0PC5T+mZgqcOPMDhNZ4AOD/A9tXKJQeBIb6KvgzuXflaQ==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"@segment/loosely-validate-event": {
|
"@segment/loosely-validate-event": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@segment/loosely-validate-event/-/loosely-validate-event-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@segment/loosely-validate-event/-/loosely-validate-event-2.0.0.tgz",
|
||||||
@ -24,6 +18,12 @@
|
|||||||
"join-component": "^1.1.0"
|
"join-component": "^1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/analytics-node": {
|
||||||
|
"version": "3.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/analytics-node/-/analytics-node-3.1.3.tgz",
|
||||||
|
"integrity": "sha512-Yk299LUqnyJ6fNYQkLFd0yTfUwIvgfxH3f5WEX3ib0PC5T+mZgqcOPMDhNZ4AOD/A9tXKJQeBIb6KvgzuXflaQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"@webassemblyjs/ast": {
|
"@webassemblyjs/ast": {
|
||||||
"version": "1.9.0",
|
"version": "1.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
|
||||||
|
|||||||
@ -10,7 +10,8 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack -p",
|
"build": "webpack -p",
|
||||||
"dev": "webpack --watch"
|
"dev": "webpack --watch",
|
||||||
|
"test": "echo NO TESTS"
|
||||||
},
|
},
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@ -549,6 +549,14 @@ msgstr "Condition"
|
|||||||
msgid "Conditions"
|
msgid "Conditions"
|
||||||
msgstr "Conditions"
|
msgstr "Conditions"
|
||||||
|
|
||||||
|
#: src/renderer/components/+workloads-deployments/deployments.tsx: 118
|
||||||
|
msgid "Restart"
|
||||||
|
msgstr "Restart"
|
||||||
|
|
||||||
|
#: src/renderer/components/+workloads-deployments/deployments.tsx: 121
|
||||||
|
msgid "Are you sure you want to restart deployment <0>{0}</0>?"
|
||||||
|
msgstr "Are you sure you want to restart deployment <0>{0}</0>?"
|
||||||
|
|
||||||
#: src/renderer/components/+config-maps/config-maps.tsx:33
|
#: src/renderer/components/+config-maps/config-maps.tsx:33
|
||||||
msgid "Config Maps"
|
msgid "Config Maps"
|
||||||
msgstr "Config Maps"
|
msgstr "Config Maps"
|
||||||
|
|||||||
@ -545,6 +545,14 @@ msgstr ""
|
|||||||
msgid "Conditions"
|
msgid "Conditions"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/renderer/components/+workloads-deployments/deployments.tsx: 118
|
||||||
|
msgid "Restart"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/renderer/components/+workloads-deployments/deployments.tsx: 121
|
||||||
|
msgid "Are you sure you want to restart deployment <0>{0}</0>?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/renderer/components/+config-maps/config-maps.tsx:33
|
#: src/renderer/components/+config-maps/config-maps.tsx:33
|
||||||
msgid "Config Maps"
|
msgid "Config Maps"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
@ -550,6 +550,14 @@ msgstr "Состояние"
|
|||||||
msgid "Conditions"
|
msgid "Conditions"
|
||||||
msgstr "Состояния"
|
msgstr "Состояния"
|
||||||
|
|
||||||
|
#: src/renderer/components/+workloads-deployments/deployments.tsx: 118
|
||||||
|
msgid "Restart"
|
||||||
|
msgstr "Перезагрузка"
|
||||||
|
|
||||||
|
#: src/renderer/components/+workloads-deployments/deployments.tsx: 121
|
||||||
|
msgid "Are you sure you want to restart deployment <0>{0}</0>?"
|
||||||
|
msgstr "Выполнить перезагрузку деплоймента <0>{0}</0>?"
|
||||||
|
|
||||||
#: src/renderer/components/+config-maps/config-maps.tsx:33
|
#: src/renderer/components/+config-maps/config-maps.tsx:33
|
||||||
msgid "Config Maps"
|
msgid "Config Maps"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
"name": "kontena-lens",
|
"name": "kontena-lens",
|
||||||
"productName": "Lens",
|
"productName": "Lens",
|
||||||
"description": "Lens - The Kubernetes IDE",
|
"description": "Lens - The Kubernetes IDE",
|
||||||
"version": "4.0.0-alpha.3",
|
"version": "4.0.0-alpha.4",
|
||||||
"main": "static/build/main.js",
|
"main": "static/build/main.js",
|
||||||
"copyright": "© 2020, Mirantis, Inc.",
|
"copyright": "© 2020, Mirantis, Inc.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import path from "path"
|
|||||||
import Config from "conf"
|
import Config from "conf"
|
||||||
import { Options as ConfOptions } from "conf/dist/source/types"
|
import { Options as ConfOptions } from "conf/dist/source/types"
|
||||||
import { app, ipcMain, IpcMainEvent, ipcRenderer, IpcRendererEvent, remote } from "electron"
|
import { app, ipcMain, IpcMainEvent, ipcRenderer, IpcRendererEvent, remote } from "electron"
|
||||||
import { action, observable, reaction, runInAction, toJS, when } from "mobx";
|
import { action, IReactionOptions, observable, reaction, runInAction, toJS, when } from "mobx";
|
||||||
import Singleton from "./utils/singleton";
|
import Singleton from "./utils/singleton";
|
||||||
import { getAppVersion } from "./utils/app-version";
|
import { getAppVersion } from "./utils/app-version";
|
||||||
import logger from "../main/logger";
|
import logger from "../main/logger";
|
||||||
@ -12,6 +12,7 @@ import isEqual from "lodash/isEqual";
|
|||||||
export interface BaseStoreParams<T = any> extends ConfOptions<T> {
|
export interface BaseStoreParams<T = any> extends ConfOptions<T> {
|
||||||
autoLoad?: boolean;
|
autoLoad?: boolean;
|
||||||
syncEnabled?: boolean;
|
syncEnabled?: boolean;
|
||||||
|
syncOptions?: IReactionOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BaseStore<T = any> extends Singleton {
|
export class BaseStore<T = any> extends Singleton {
|
||||||
@ -20,7 +21,7 @@ export class BaseStore<T = any> extends Singleton {
|
|||||||
|
|
||||||
whenLoaded = when(() => this.isLoaded);
|
whenLoaded = when(() => this.isLoaded);
|
||||||
@observable isLoaded = false;
|
@observable isLoaded = false;
|
||||||
@observable protected data: T;
|
@observable data = {} as T;
|
||||||
|
|
||||||
protected constructor(protected params: BaseStoreParams) {
|
protected constructor(protected params: BaseStoreParams) {
|
||||||
super();
|
super();
|
||||||
@ -36,8 +37,12 @@ export class BaseStore<T = any> extends Singleton {
|
|||||||
return path.basename(this.storeConfig.path);
|
return path.basename(this.storeConfig.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get path() {
|
||||||
|
return this.storeConfig.path;
|
||||||
|
}
|
||||||
|
|
||||||
get syncChannel() {
|
get syncChannel() {
|
||||||
return `store-sync:${this.name}`
|
return `STORE-SYNC:${this.path}`
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async init() {
|
protected async init() {
|
||||||
@ -56,19 +61,19 @@ export class BaseStore<T = any> extends Singleton {
|
|||||||
...confOptions,
|
...confOptions,
|
||||||
projectName: "lens",
|
projectName: "lens",
|
||||||
projectVersion: getAppVersion(),
|
projectVersion: getAppVersion(),
|
||||||
cwd: this.storePath(),
|
cwd: this.cwd(),
|
||||||
});
|
});
|
||||||
logger.info(`[STORE]: LOADED from ${this.storeConfig.path}`);
|
logger.info(`[STORE]: LOADED from ${this.path}`);
|
||||||
this.fromStore(this.storeConfig.store);
|
this.fromStore(this.storeConfig.store);
|
||||||
this.isLoaded = true;
|
this.isLoaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected storePath() {
|
protected cwd() {
|
||||||
return (app || remote.app).getPath("userData")
|
return (app || remote.app).getPath("userData")
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async saveToFile(model: T) {
|
protected async saveToFile(model: T) {
|
||||||
logger.info(`[STORE]: SAVING ${this.name}`);
|
logger.info(`[STORE]: SAVING ${this.path}`);
|
||||||
// todo: update when fixed https://github.com/sindresorhus/conf/issues/114
|
// todo: update when fixed https://github.com/sindresorhus/conf/issues/114
|
||||||
Object.entries(model).forEach(([key, value]) => {
|
Object.entries(model).forEach(([key, value]) => {
|
||||||
this.storeConfig.set(key, value);
|
this.storeConfig.set(key, value);
|
||||||
@ -77,7 +82,7 @@ export class BaseStore<T = any> extends Singleton {
|
|||||||
|
|
||||||
enableSync() {
|
enableSync() {
|
||||||
this.syncDisposers.push(
|
this.syncDisposers.push(
|
||||||
reaction(() => this.toJSON(), model => this.onModelChange(model)),
|
reaction(() => this.toJSON(), model => this.onModelChange(model), this.params.syncOptions),
|
||||||
);
|
);
|
||||||
if (ipcMain) {
|
if (ipcMain) {
|
||||||
const callback = (event: IpcMainEvent, model: T) => {
|
const callback = (event: IpcMainEvent, model: T) => {
|
||||||
@ -169,6 +174,7 @@ export class BaseStore<T = any> extends Singleton {
|
|||||||
|
|
||||||
@action
|
@action
|
||||||
protected fromStore(data: T) {
|
protected fromStore(data: T) {
|
||||||
|
if (!data) return;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1 +1,2 @@
|
|||||||
export { ClusterFeature as Feature, ClusterFeatureStatus as FeatureStatus } from "../cluster-feature"
|
export { ClusterFeature as Feature } from "../cluster-feature"
|
||||||
|
export type { ClusterFeatureStatus as FeatureStatus } from "../cluster-feature"
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
export { ExtensionStore } from "../extension-store"
|
export { ExtensionStore } from "../extension-store"
|
||||||
export { clusterStore, ClusterModel } from "../../common/cluster-store"
|
export { clusterStore } from "../../common/cluster-store"
|
||||||
export { Cluster } from "../../main/cluster"
|
export type { ClusterModel } from "../../common/cluster-store"
|
||||||
export { workspaceStore, Workspace, WorkspaceModel } from "../../common/workspace-store"
|
export { Cluster } from "../../main/cluster"
|
||||||
|
export { workspaceStore, Workspace } from "../../common/workspace-store"
|
||||||
|
export type { WorkspaceModel } from "../../common/workspace-store"
|
||||||
|
|||||||
@ -1,20 +1,13 @@
|
|||||||
import type { ExtensionId, ExtensionManifest, ExtensionModel, LensExtension } from "./lens-extension"
|
import type { LensExtension, LensExtensionConstructor, LensExtensionId } from "./lens-extension"
|
||||||
import type { LensMainExtension } from "./lens-main-extension"
|
import type { LensMainExtension } from "./lens-main-extension"
|
||||||
import type { LensRendererExtension } from "./lens-renderer-extension"
|
import type { LensRendererExtension } from "./lens-renderer-extension"
|
||||||
|
import type { InstalledExtension } from "./extension-manager";
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { broadcastIpc } from "../common/ipc"
|
import { broadcastIpc } from "../common/ipc"
|
||||||
import { observable, reaction, toJS, } from "mobx"
|
import { computed, observable, reaction, when } from "mobx"
|
||||||
import logger from "../main/logger"
|
import logger from "../main/logger"
|
||||||
import { app, ipcRenderer, remote } from "electron"
|
import { app, ipcRenderer, remote } from "electron"
|
||||||
import {
|
import * as registries from "./registries";
|
||||||
appPreferenceRegistry, clusterFeatureRegistry, clusterPageRegistry, globalPageRegistry,
|
|
||||||
kubeObjectDetailRegistry, kubeObjectMenuRegistry, menuRegistry, statusBarRegistry
|
|
||||||
} from "./registries";
|
|
||||||
|
|
||||||
export interface InstalledExtension extends ExtensionModel {
|
|
||||||
manifestPath: string;
|
|
||||||
manifest: ExtensionManifest;
|
|
||||||
}
|
|
||||||
|
|
||||||
// lazy load so that we get correct userData
|
// lazy load so that we get correct userData
|
||||||
export function extensionPackagesRoot() {
|
export function extensionPackagesRoot() {
|
||||||
@ -22,69 +15,82 @@ export function extensionPackagesRoot() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ExtensionLoader {
|
export class ExtensionLoader {
|
||||||
@observable extensions = observable.map<ExtensionId, InstalledExtension>([], { deep: false });
|
@observable isLoaded = false;
|
||||||
@observable instances = observable.map<ExtensionId, LensExtension>([], { deep: false })
|
protected extensions = observable.map<LensExtensionId, InstalledExtension>([], { deep: false });
|
||||||
|
protected instances = observable.map<LensExtensionId, LensExtension>([], { deep: false })
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
if (ipcRenderer) {
|
if (ipcRenderer) {
|
||||||
ipcRenderer.on("extensions:loaded", (event, extensions: InstalledExtension[]) => {
|
ipcRenderer.on("extensions:loaded", (event, extensions: InstalledExtension[]) => {
|
||||||
|
this.isLoaded = true;
|
||||||
extensions.forEach((ext) => {
|
extensions.forEach((ext) => {
|
||||||
if (!this.getById(ext.manifestPath)) {
|
if (!this.extensions.has(ext.manifestPath)) {
|
||||||
this.extensions.set(ext.manifestPath, ext)
|
this.extensions.set(ext.manifestPath, ext)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@computed get userExtensions(): LensExtension[] {
|
||||||
|
return [...this.instances.values()].filter(ext => !ext.isBundled)
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
const { extensionManager } = await import("./extension-manager");
|
||||||
|
const installedExtensions = await extensionManager.load();
|
||||||
|
this.extensions.replace(installedExtensions);
|
||||||
|
this.isLoaded = true;
|
||||||
|
this.loadOnMain();
|
||||||
|
}
|
||||||
|
|
||||||
loadOnMain() {
|
loadOnMain() {
|
||||||
logger.info('[EXTENSIONS-LOADER]: load on main')
|
logger.info('[EXTENSIONS-LOADER]: load on main')
|
||||||
this.autoloadExtensions((extension: LensMainExtension) => {
|
this.autoInitExtensions((extension: LensMainExtension) => [
|
||||||
extension.registerTo(menuRegistry, extension.appMenus)
|
registries.menuRegistry.add(...extension.appMenus)
|
||||||
})
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadOnClusterManagerRenderer() {
|
loadOnClusterManagerRenderer() {
|
||||||
logger.info('[EXTENSIONS-LOADER]: load on main renderer (cluster manager)')
|
logger.info('[EXTENSIONS-LOADER]: load on main renderer (cluster manager)')
|
||||||
this.autoloadExtensions((extension: LensRendererExtension) => {
|
this.autoInitExtensions((extension: LensRendererExtension) => [
|
||||||
extension.registerTo(globalPageRegistry, extension.globalPages)
|
registries.globalPageRegistry.add(...extension.globalPages),
|
||||||
extension.registerTo(appPreferenceRegistry, extension.appPreferences)
|
registries.appPreferenceRegistry.add(...extension.appPreferences),
|
||||||
extension.registerTo(clusterFeatureRegistry, extension.clusterFeatures)
|
registries.clusterFeatureRegistry.add(...extension.clusterFeatures),
|
||||||
extension.registerTo(statusBarRegistry, extension.statusBarItems)
|
registries.statusBarRegistry.add(...extension.statusBarItems),
|
||||||
})
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadOnClusterRenderer() {
|
loadOnClusterRenderer() {
|
||||||
logger.info('[EXTENSIONS-LOADER]: load on cluster renderer (dashboard)')
|
logger.info('[EXTENSIONS-LOADER]: load on cluster renderer (dashboard)')
|
||||||
this.autoloadExtensions((extension: LensRendererExtension) => {
|
this.autoInitExtensions((extension: LensRendererExtension) => [
|
||||||
extension.registerTo(clusterPageRegistry, extension.clusterPages)
|
registries.clusterPageRegistry.add(...extension.clusterPages),
|
||||||
extension.registerTo(kubeObjectMenuRegistry, extension.kubeObjectMenuItems)
|
registries.kubeObjectMenuRegistry.add(...extension.kubeObjectMenuItems),
|
||||||
extension.registerTo(kubeObjectDetailRegistry, extension.kubeObjectDetailItems)
|
registries.kubeObjectDetailRegistry.add(...extension.kubeObjectDetailItems),
|
||||||
})
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected autoloadExtensions(callback: (instance: LensExtension) => void) {
|
protected autoInitExtensions(register: (ext: LensExtension) => Function[]) {
|
||||||
return reaction(() => this.extensions.toJS(), (installedExtensions) => {
|
return reaction(() => this.extensions.toJS(), (installedExtensions) => {
|
||||||
for(const [id, ext] of installedExtensions) {
|
for (const [id, ext] of installedExtensions) {
|
||||||
let instance = this.instances.get(ext.id)
|
let instance = this.instances.get(ext.manifestPath)
|
||||||
if (!instance) {
|
if (!instance) {
|
||||||
const extensionModule = this.requireExtension(ext)
|
const extensionModule = this.requireExtension(ext)
|
||||||
if (!extensionModule) {
|
if (!extensionModule) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const LensExtensionClass = extensionModule.default;
|
|
||||||
instance = new LensExtensionClass({ ...ext.manifest, manifestPath: ext.manifestPath, id: ext.manifestPath }, ext.manifest);
|
|
||||||
try {
|
try {
|
||||||
instance.enable()
|
const LensExtensionClass: LensExtensionConstructor = extensionModule.default;
|
||||||
callback(instance)
|
instance = new LensExtensionClass(ext);
|
||||||
} finally {
|
instance.whenEnabled(() => register(instance));
|
||||||
this.instances.set(ext.id, instance)
|
this.instances.set(ext.manifestPath, instance);
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(`[EXTENSIONS-LOADER]: init extension instance error`, { ext, err })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
fireImmediately: true,
|
fireImmediately: true,
|
||||||
delay: 0,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,37 +111,17 @@ export class ExtensionLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getById(id: ExtensionId): InstalledExtension {
|
async broadcastExtensions(frameId?: number) {
|
||||||
return this.extensions.get(id);
|
await when(() => this.isLoaded);
|
||||||
}
|
|
||||||
|
|
||||||
async removeById(id: ExtensionId) {
|
|
||||||
const extension = this.getById(id);
|
|
||||||
if (extension) {
|
|
||||||
const instance = this.instances.get(extension.id)
|
|
||||||
if (instance) {
|
|
||||||
await instance.disable()
|
|
||||||
}
|
|
||||||
this.extensions.delete(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
broadcastExtensions(frameId?: number) {
|
|
||||||
broadcastIpc({
|
broadcastIpc({
|
||||||
channel: "extensions:loaded",
|
channel: "extensions:loaded",
|
||||||
frameId: frameId,
|
frameId: frameId,
|
||||||
frameOnly: !!frameId,
|
frameOnly: !!frameId,
|
||||||
args: [this.toJSON().extensions],
|
args: [
|
||||||
})
|
Array.from(this.extensions.toJS().values())
|
||||||
}
|
],
|
||||||
|
|
||||||
toJSON() {
|
|
||||||
return toJS({
|
|
||||||
extensions: Array.from(this.extensions).map(([id, instance]) => instance),
|
|
||||||
}, {
|
|
||||||
recurseEverything: true,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const extensionLoader = new ExtensionLoader()
|
export const extensionLoader = new ExtensionLoader();
|
||||||
|
|||||||
@ -1,12 +1,18 @@
|
|||||||
import type { ExtensionManifest } from "./lens-extension"
|
import type { LensExtensionId, LensExtensionManifest } from "./lens-extension"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import os from "os"
|
import os from "os"
|
||||||
import fs from "fs-extra"
|
import fs from "fs-extra"
|
||||||
|
import child_process from "child_process";
|
||||||
import logger from "../main/logger"
|
import logger from "../main/logger"
|
||||||
import { extensionPackagesRoot, InstalledExtension } from "./extension-loader"
|
import { extensionPackagesRoot } from "./extension-loader"
|
||||||
import * as child_process from 'child_process';
|
|
||||||
import { getBundledExtensions } from "../common/utils/app-version"
|
import { getBundledExtensions } from "../common/utils/app-version"
|
||||||
|
|
||||||
|
export interface InstalledExtension {
|
||||||
|
manifest: LensExtensionManifest;
|
||||||
|
manifestPath: string;
|
||||||
|
isBundled?: boolean; // defined in package.json
|
||||||
|
}
|
||||||
|
|
||||||
type Dependencies = {
|
type Dependencies = {
|
||||||
[name: string]: string;
|
[name: string]: string;
|
||||||
}
|
}
|
||||||
@ -51,7 +57,7 @@ export class ExtensionManager {
|
|||||||
return path.join(this.extensionPackagesRoot, "package.json")
|
return path.join(this.extensionPackagesRoot, "package.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
async load(): Promise<Map<LensExtensionId, InstalledExtension>> {
|
||||||
logger.info("[EXTENSION-MANAGER] loading extensions from " + this.extensionPackagesRoot)
|
logger.info("[EXTENSION-MANAGER] loading extensions from " + this.extensionPackagesRoot)
|
||||||
if (fs.existsSync(path.join(this.extensionPackagesRoot, "package-lock.json"))) {
|
if (fs.existsSync(path.join(this.extensionPackagesRoot, "package-lock.json"))) {
|
||||||
await fs.remove(path.join(this.extensionPackagesRoot, "package-lock.json"))
|
await fs.remove(path.join(this.extensionPackagesRoot, "package-lock.json"))
|
||||||
@ -71,8 +77,8 @@ export class ExtensionManager {
|
|||||||
return await this.loadExtensions();
|
return await this.loadExtensions();
|
||||||
}
|
}
|
||||||
|
|
||||||
async getExtensionByManifest(manifestPath: string): Promise<InstalledExtension> {
|
protected async getByManifest(manifestPath: string): Promise<InstalledExtension> {
|
||||||
let manifestJson: ExtensionManifest;
|
let manifestJson: LensExtensionManifest;
|
||||||
try {
|
try {
|
||||||
fs.accessSync(manifestPath, fs.constants.F_OK); // check manifest file for existence
|
fs.accessSync(manifestPath, fs.constants.F_OK); // check manifest file for existence
|
||||||
manifestJson = __non_webpack_require__(manifestPath)
|
manifestJson = __non_webpack_require__(manifestPath)
|
||||||
@ -80,11 +86,8 @@ export class ExtensionManager {
|
|||||||
|
|
||||||
logger.info("[EXTENSION-MANAGER] installed extension " + manifestJson.name)
|
logger.info("[EXTENSION-MANAGER] installed extension " + manifestJson.name)
|
||||||
return {
|
return {
|
||||||
id: manifestJson.name,
|
|
||||||
version: manifestJson.version,
|
|
||||||
name: manifestJson.name,
|
|
||||||
manifestPath: path.join(this.nodeModulesPath, manifestJson.name, "package.json"),
|
manifestPath: path.join(this.nodeModulesPath, manifestJson.name, "package.json"),
|
||||||
manifest: manifestJson
|
manifest: manifestJson,
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(`[EXTENSION-MANAGER]: can't install extension at ${manifestPath}: ${err}`, { manifestJson });
|
logger.error(`[EXTENSION-MANAGER]: can't install extension at ${manifestPath}: ${err}`, { manifestJson });
|
||||||
@ -109,10 +112,10 @@ export class ExtensionManager {
|
|||||||
async loadExtensions() {
|
async loadExtensions() {
|
||||||
const bundledExtensions = await this.loadBundledExtensions()
|
const bundledExtensions = await this.loadBundledExtensions()
|
||||||
const localExtensions = await this.loadFromFolder(this.localFolderPath)
|
const localExtensions = await this.loadFromFolder(this.localFolderPath)
|
||||||
await fs.writeFile(path.join(this.packageJsonPath), JSON.stringify(this.packagesJson, null, 2), {mode: 0o600})
|
await fs.writeFile(path.join(this.packageJsonPath), JSON.stringify(this.packagesJson, null, 2), { mode: 0o600 })
|
||||||
await this.installPackages()
|
await this.installPackages()
|
||||||
const extensions = bundledExtensions.concat(localExtensions)
|
const extensions = bundledExtensions.concat(localExtensions)
|
||||||
return new Map(extensions.map(ext => [ext.id, ext]));
|
return new Map(extensions.map(ext => [ext.manifestPath, ext]));
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadBundledExtensions() {
|
async loadBundledExtensions() {
|
||||||
@ -126,8 +129,9 @@ export class ExtensionManager {
|
|||||||
}
|
}
|
||||||
const absPath = path.resolve(folderPath, fileName);
|
const absPath = path.resolve(folderPath, fileName);
|
||||||
const manifestPath = path.resolve(absPath, "package.json");
|
const manifestPath = path.resolve(absPath, "package.json");
|
||||||
const ext = await this.getExtensionByManifest(manifestPath).catch(() => null)
|
const ext = await this.getByManifest(manifestPath).catch(() => null)
|
||||||
if (ext) {
|
if (ext) {
|
||||||
|
ext.isBundled = true;
|
||||||
extensions.push(ext)
|
extensions.push(ext)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -152,7 +156,7 @@ export class ExtensionManager {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const manifestPath = path.resolve(absPath, "package.json");
|
const manifestPath = path.resolve(absPath, "package.json");
|
||||||
const ext = await this.getExtensionByManifest(manifestPath).catch(() => null)
|
const ext = await this.getByManifest(manifestPath).catch(() => null)
|
||||||
if (ext) {
|
if (ext) {
|
||||||
extensions.push(ext)
|
extensions.push(ext)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,7 +15,7 @@ export class ExtensionStore<T = any> extends BaseStore<T> {
|
|||||||
await super.load()
|
await super.load()
|
||||||
}
|
}
|
||||||
|
|
||||||
protected storePath() {
|
protected cwd() {
|
||||||
return path.join(super.storePath(), "extension-store", this.extension.name)
|
return path.join(super.cwd(), "extension-store", this.extension.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,75 +1,111 @@
|
|||||||
import { readJsonSync } from "fs-extra";
|
import type { InstalledExtension } from "./extension-manager";
|
||||||
import { action, observable, toJS } from "mobx";
|
import { action, reaction } from "mobx";
|
||||||
import logger from "../main/logger";
|
import logger from "../main/logger";
|
||||||
import { BaseRegistry } from "./registries/base-registry";
|
import { ExtensionStore } from "./extension-store";
|
||||||
|
|
||||||
export type ExtensionId = string | ExtensionPackageJsonPath;
|
export type LensExtensionId = string; // path to manifest (package.json)
|
||||||
export type ExtensionPackageJsonPath = string;
|
export type LensExtensionConstructor = new (...args: ConstructorParameters<typeof LensExtension>) => LensExtension;
|
||||||
export type ExtensionVersion = string | number;
|
|
||||||
|
|
||||||
export interface ExtensionModel {
|
export interface LensExtensionManifest {
|
||||||
id: ExtensionId;
|
|
||||||
version: ExtensionVersion;
|
|
||||||
name: string;
|
name: string;
|
||||||
manifestPath: string;
|
version: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
enabled?: boolean;
|
main?: string; // path to %ext/dist/main.js
|
||||||
updateUrl?: string;
|
renderer?: string; // path to %ext/dist/renderer.js
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExtensionManifest extends ExtensionModel {
|
export interface LensExtensionStoreModel {
|
||||||
main?: string;
|
isEnabled: boolean;
|
||||||
renderer?: string;
|
|
||||||
description?: string; // todo: add more fields similar to package.json + some extra
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class LensExtension implements ExtensionModel {
|
export class LensExtension<S extends ExtensionStore<LensExtensionStoreModel> = any> {
|
||||||
public id: ExtensionId;
|
protected store: S;
|
||||||
public updateUrl: string;
|
readonly manifest: LensExtensionManifest;
|
||||||
protected disposers: (() => void)[] = [];
|
readonly manifestPath: string;
|
||||||
|
readonly isBundled: boolean;
|
||||||
|
|
||||||
@observable name = "";
|
constructor({ manifest, manifestPath, isBundled }: InstalledExtension) {
|
||||||
@observable description = "";
|
this.manifest = manifest
|
||||||
@observable version: ExtensionVersion = "0.0.0";
|
this.manifestPath = manifestPath
|
||||||
@observable manifest: ExtensionManifest;
|
this.isBundled = !!isBundled
|
||||||
@observable manifestPath: string;
|
this.init();
|
||||||
@observable isEnabled = false;
|
}
|
||||||
|
|
||||||
constructor(model: ExtensionModel, manifest: ExtensionManifest) {
|
protected async init(store: S = createBaseStore().getInstance()) {
|
||||||
this.importModel(model, manifest);
|
this.store = store;
|
||||||
|
await this.store.loadExtension(this);
|
||||||
|
reaction(() => this.store.data.isEnabled, (isEnabled = true) => {
|
||||||
|
this.toggle(isEnabled); // handle activation & deactivation
|
||||||
|
}, {
|
||||||
|
fireImmediately: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get isEnabled() {
|
||||||
|
return !!this.store.data.isEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
get id(): LensExtensionId {
|
||||||
|
return this.manifestPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
get name() {
|
||||||
|
return this.manifest.name
|
||||||
|
}
|
||||||
|
|
||||||
|
get version() {
|
||||||
|
return this.manifest.version
|
||||||
|
}
|
||||||
|
|
||||||
|
get description() {
|
||||||
|
return this.manifest.description
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
async importModel({ enabled, manifestPath, ...model }: ExtensionModel, manifest?: ExtensionManifest) {
|
|
||||||
try {
|
|
||||||
this.manifest = manifest || await readJsonSync(manifestPath, { throws: true })
|
|
||||||
this.manifestPath = manifestPath;
|
|
||||||
Object.assign(this, model);
|
|
||||||
} catch (err) {
|
|
||||||
logger.error(`[EXTENSION]: cannot read manifest at ${manifestPath}`, { ...model, err: String(err) })
|
|
||||||
this.disable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async migrate(appVersion: string) {
|
|
||||||
// mock
|
|
||||||
}
|
|
||||||
|
|
||||||
async enable() {
|
async enable() {
|
||||||
this.isEnabled = true;
|
if (this.isEnabled) return;
|
||||||
logger.info(`[EXTENSION]: enabled ${this.name}@${this.version}`);
|
this.store.data.isEnabled = true;
|
||||||
this.onActivate();
|
this.onActivate();
|
||||||
|
logger.info(`[EXTENSION]: enabled ${this.name}@${this.version}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
async disable() {
|
async disable() {
|
||||||
|
if (!this.isEnabled) return;
|
||||||
|
this.store.data.isEnabled = false;
|
||||||
this.onDeactivate();
|
this.onDeactivate();
|
||||||
this.isEnabled = false;
|
|
||||||
this.disposers.forEach(cleanUp => cleanUp());
|
|
||||||
this.disposers.length = 0;
|
|
||||||
logger.info(`[EXTENSION]: disabled ${this.name}@${this.version}`);
|
logger.info(`[EXTENSION]: disabled ${this.name}@${this.version}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: add more hooks
|
toggle(enable?: boolean) {
|
||||||
|
if (typeof enable === "boolean") {
|
||||||
|
enable ? this.enable() : this.disable()
|
||||||
|
} else {
|
||||||
|
this.isEnabled ? this.disable() : this.enable()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async whenEnabled(handlers: () => Function[]) {
|
||||||
|
const disposers: Function[] = [];
|
||||||
|
const unregisterHandlers = () => {
|
||||||
|
disposers.forEach(unregister => unregister())
|
||||||
|
disposers.length = 0;
|
||||||
|
}
|
||||||
|
const cancelReaction = reaction(() => this.isEnabled, isEnabled => {
|
||||||
|
if (isEnabled) {
|
||||||
|
disposers.push(...handlers());
|
||||||
|
} else {
|
||||||
|
unregisterHandlers();
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
fireImmediately: true
|
||||||
|
})
|
||||||
|
return () => {
|
||||||
|
unregisterHandlers();
|
||||||
|
cancelReaction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected onActivate() {
|
protected onActivate() {
|
||||||
// mock
|
// mock
|
||||||
}
|
}
|
||||||
@ -77,37 +113,14 @@ export class LensExtension implements ExtensionModel {
|
|||||||
protected onDeactivate() {
|
protected onDeactivate() {
|
||||||
// mock
|
// mock
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
registerTo<T = any>(registry: BaseRegistry<T>, items: T[] = []) {
|
function createBaseStore() {
|
||||||
const disposers = items.map(item => registry.add(item));
|
return class extends ExtensionStore<LensExtensionStoreModel> {
|
||||||
this.disposers.push(...disposers);
|
constructor() {
|
||||||
return () => {
|
super({
|
||||||
this.disposers = this.disposers.filter(disposer => !disposers.includes(disposer))
|
configName: "state"
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getMeta() {
|
|
||||||
return toJS({
|
|
||||||
id: this.id,
|
|
||||||
manifest: this.manifest,
|
|
||||||
manifestPath: this.manifestPath,
|
|
||||||
enabled: this.isEnabled
|
|
||||||
}, {
|
|
||||||
recurseEverything: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON(): ExtensionModel {
|
|
||||||
return toJS({
|
|
||||||
id: this.id,
|
|
||||||
name: this.name,
|
|
||||||
version: this.version,
|
|
||||||
description: this.description,
|
|
||||||
manifestPath: this.manifestPath,
|
|
||||||
enabled: this.isEnabled,
|
|
||||||
updateUrl: this.updateUrl,
|
|
||||||
}, {
|
|
||||||
recurseEverything: true,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
src/extensions/npm/extensions/.gitignore
vendored
1
src/extensions/npm/extensions/.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
api.d.ts
|
api.d.ts
|
||||||
|
yarn.lock
|
||||||
|
|||||||
@ -12,5 +12,8 @@
|
|||||||
"author": {
|
"author": {
|
||||||
"name": "Mirantis, Inc.",
|
"name": "Mirantis, Inc.",
|
||||||
"email": "info@k8slens.dev"
|
"email": "info@k8slens.dev"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^14.14.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
// Base class for extensions-api registries
|
// Base class for extensions-api registries
|
||||||
import { observable } from "mobx";
|
import { action, observable } from "mobx";
|
||||||
|
|
||||||
export class BaseRegistry<T = any> {
|
export class BaseRegistry<T = any> {
|
||||||
protected items = observable<T>([], { deep: false });
|
protected items = observable<T>([], { deep: false });
|
||||||
@ -8,10 +8,16 @@ export class BaseRegistry<T = any> {
|
|||||||
return this.items.toJS();
|
return this.items.toJS();
|
||||||
}
|
}
|
||||||
|
|
||||||
add(item: T) {
|
@action
|
||||||
this.items.push(item);
|
add(...items: T[]) {
|
||||||
return () => {
|
this.items.push(...items);
|
||||||
|
return () => this.remove(...items);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
remove(...items: T[]) {
|
||||||
|
items.forEach(item => {
|
||||||
this.items.remove(item); // works because of {deep: false};
|
this.items.remove(item); // works because of {deep: false};
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,13 +15,12 @@ import { shellSync } from "./shell-sync"
|
|||||||
import { getFreePort } from "./port"
|
import { getFreePort } from "./port"
|
||||||
import { mangleProxyEnv } from "./proxy-env"
|
import { mangleProxyEnv } from "./proxy-env"
|
||||||
import { registerFileProtocol } from "../common/register-protocol";
|
import { registerFileProtocol } from "../common/register-protocol";
|
||||||
|
import logger from "./logger"
|
||||||
import { clusterStore } from "../common/cluster-store"
|
import { clusterStore } from "../common/cluster-store"
|
||||||
import { userStore } from "../common/user-store";
|
import { userStore } from "../common/user-store";
|
||||||
import { workspaceStore } from "../common/workspace-store";
|
import { workspaceStore } from "../common/workspace-store";
|
||||||
import { appEventBus } from "../common/event-bus"
|
import { appEventBus } from "../common/event-bus"
|
||||||
import { extensionManager } from "../extensions/extension-manager";
|
|
||||||
import { extensionLoader } from "../extensions/extension-loader";
|
import { extensionLoader } from "../extensions/extension-loader";
|
||||||
import logger from "./logger"
|
|
||||||
|
|
||||||
const workingDir = path.join(app.getPath("appData"), appName);
|
const workingDir = path.join(app.getPath("appData"), appName);
|
||||||
let proxyPort: number;
|
let proxyPort: number;
|
||||||
@ -48,7 +47,7 @@ app.on("ready", async () => {
|
|||||||
|
|
||||||
registerFileProtocol("static", __static);
|
registerFileProtocol("static", __static);
|
||||||
|
|
||||||
// preload isomorphic stores
|
// preload
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
userStore.load(),
|
userStore.load(),
|
||||||
clusterStore.load(),
|
clusterStore.load(),
|
||||||
@ -76,12 +75,8 @@ app.on("ready", async () => {
|
|||||||
app.exit();
|
app.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
windowManager = new WindowManager(proxyPort);
|
LensExtensionsApi.windowManager = windowManager = new WindowManager(proxyPort);
|
||||||
|
extensionLoader.init(); // call after windowManager to see splash earlier
|
||||||
LensExtensionsApi.windowManager = windowManager; // expose to extensions
|
|
||||||
extensionLoader.loadOnMain()
|
|
||||||
extensionLoader.extensions.replace(await extensionManager.load())
|
|
||||||
extensionLoader.broadcastExtensions()
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
appEventBus.emit({ name: "app", action: "start" })
|
appEventBus.emit({ name: "app", action: "start" })
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { addClusterURL } from "../renderer/components/+add-cluster/add-cluster.r
|
|||||||
import { preferencesURL } from "../renderer/components/+preferences/preferences.route";
|
import { preferencesURL } from "../renderer/components/+preferences/preferences.route";
|
||||||
import { whatsNewURL } from "../renderer/components/+whats-new/whats-new.route";
|
import { whatsNewURL } from "../renderer/components/+whats-new/whats-new.route";
|
||||||
import { clusterSettingsURL } from "../renderer/components/+cluster-settings/cluster-settings.route";
|
import { clusterSettingsURL } from "../renderer/components/+cluster-settings/cluster-settings.route";
|
||||||
|
import { extensionsURL } from "../renderer/components/+extensions/extensions.route";
|
||||||
import { menuRegistry } from "../extensions/registries/menu-registry";
|
import { menuRegistry } from "../extensions/registries/menu-registry";
|
||||||
import logger from "./logger";
|
import logger from "./logger";
|
||||||
|
|
||||||
@ -70,6 +71,13 @@ export function buildMenu(windowManager: WindowManager) {
|
|||||||
navigate(preferencesURL())
|
navigate(preferencesURL())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'Extensions',
|
||||||
|
accelerator: 'CmdOrCtrl+Shift+E',
|
||||||
|
click() {
|
||||||
|
navigate(extensionsURL())
|
||||||
|
}
|
||||||
|
},
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{ role: 'services' },
|
{ role: 'services' },
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import moment from "moment";
|
||||||
|
|
||||||
import { IAffinity, WorkloadKubeObject } from "../workload-kube-object";
|
import { IAffinity, WorkloadKubeObject } from "../workload-kube-object";
|
||||||
import { autobind } from "../../utils";
|
import { autobind } from "../../utils";
|
||||||
import { KubeApi } from "../kube-api";
|
import { KubeApi } from "../kube-api";
|
||||||
@ -23,6 +25,25 @@ export class DeploymentApi extends KubeApi<Deployment> {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
restart(params: { namespace: string; name: string }) {
|
||||||
|
return this.request.patch(this.getUrl(params), {
|
||||||
|
data: {
|
||||||
|
spec: {
|
||||||
|
template: {
|
||||||
|
metadata: {
|
||||||
|
annotations: {"kubectl.kubernetes.io/restartedAt" : moment.utc().format()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/strategic-merge-patch+json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind()
|
@autobind()
|
||||||
@ -38,6 +59,7 @@ export class Deployment extends WorkloadKubeObject {
|
|||||||
metadata: {
|
metadata: {
|
||||||
creationTimestamp?: string;
|
creationTimestamp?: string;
|
||||||
labels: { [app: string]: string };
|
labels: { [app: string]: string };
|
||||||
|
annotations?: { [app: string]: string };
|
||||||
};
|
};
|
||||||
spec: {
|
spec: {
|
||||||
containers: {
|
containers: {
|
||||||
|
|||||||
@ -64,7 +64,7 @@ export class JsonApi<D = JsonApiData, P extends JsonApiParams = JsonApiParams> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
patch<T = D>(path: string, params?: P, reqInit: RequestInit = {}) {
|
patch<T = D>(path: string, params?: P, reqInit: RequestInit = {}) {
|
||||||
return this.request<T>(path, params, { ...reqInit, method: "patch" });
|
return this.request<T>(path, params, { ...reqInit, method: "PATCH" });
|
||||||
}
|
}
|
||||||
|
|
||||||
del<T = D>(path: string, params?: P, reqInit: RequestInit = {}) {
|
del<T = D>(path: string, params?: P, reqInit: RequestInit = {}) {
|
||||||
|
|||||||
8
src/renderer/components/+extensions/extensions.route.ts
Normal file
8
src/renderer/components/+extensions/extensions.route.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { RouteProps } from "react-router";
|
||||||
|
import { buildURL } from "../../../common/utils/buildUrl";
|
||||||
|
|
||||||
|
export const extensionsRoute: RouteProps = {
|
||||||
|
path: "/extensions"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const extensionsURL = buildURL(extensionsRoute.path)
|
||||||
35
src/renderer/components/+extensions/extensions.scss
Normal file
35
src/renderer/components/+extensions/extensions.scss
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
.Extensions {
|
||||||
|
--width: 100%;
|
||||||
|
--max-width: auto;
|
||||||
|
|
||||||
|
.extension {
|
||||||
|
--flex-gap: $padding / 3;
|
||||||
|
padding: $padding $padding * 2;
|
||||||
|
background: $colorVague;
|
||||||
|
border-radius: $radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
.extensions-path {
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.WizardLayout {
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
.info-col {
|
||||||
|
flex: 0.6;
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.SearchInput {
|
||||||
|
margin-top: $margin / 2;
|
||||||
|
margin-bottom: $margin * 2;
|
||||||
|
max-width: none;
|
||||||
|
|
||||||
|
> label {
|
||||||
|
padding: $padding $padding * 2;
|
||||||
|
border-radius: $radius;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
112
src/renderer/components/+extensions/extensions.tsx
Normal file
112
src/renderer/components/+extensions/extensions.tsx
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import "./extensions.scss";
|
||||||
|
import { shell } from "electron";
|
||||||
|
import React from "react";
|
||||||
|
import { computed, observable } from "mobx";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
import { t, Trans } from "@lingui/macro";
|
||||||
|
import { _i18n } from "../../i18n";
|
||||||
|
import { Button } from "../button";
|
||||||
|
import { WizardLayout } from "../layout/wizard-layout";
|
||||||
|
import { Input } from "../input";
|
||||||
|
import { Icon } from "../icon";
|
||||||
|
import { PageLayout } from "../layout/page-layout";
|
||||||
|
import { extensionLoader } from "../../../extensions/extension-loader";
|
||||||
|
import { extensionManager } from "../../../extensions/extension-manager";
|
||||||
|
|
||||||
|
@observer
|
||||||
|
export class Extensions extends React.Component {
|
||||||
|
@observable search = ""
|
||||||
|
|
||||||
|
@computed get extensions() {
|
||||||
|
const searchText = this.search.toLowerCase();
|
||||||
|
return extensionLoader.userExtensions.filter(({ name, description }) => {
|
||||||
|
return [
|
||||||
|
name.toLowerCase().includes(searchText),
|
||||||
|
description.toLowerCase().includes(searchText),
|
||||||
|
].some(v => v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
get extensionsPath() {
|
||||||
|
return extensionManager.localFolderPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderInfo() {
|
||||||
|
return (
|
||||||
|
<div className="flex column gaps">
|
||||||
|
<h2>Lens Extension API</h2>
|
||||||
|
<div>
|
||||||
|
The Extensions API in Lens allows users to customize and enhance the Lens experience by creating their own menus or page content that is extended from the existing pages. Many of the core
|
||||||
|
features of Lens are built as extensions and use the same Extension API.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Extensions loaded from:
|
||||||
|
<div className="extensions-path flex inline">
|
||||||
|
<code>{this.extensionsPath}</code>
|
||||||
|
<Icon
|
||||||
|
material="folder"
|
||||||
|
tooltip="Open folder"
|
||||||
|
onClick={() => shell.openPath(this.extensionsPath)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Check out documentation to <a href="https://docs.k8slens.dev/" target="_blank">learn more</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderExtensions() {
|
||||||
|
const { extensions, extensionsPath, search } = this;
|
||||||
|
if (!extensions.length) {
|
||||||
|
return (
|
||||||
|
<div className="flex align-center box grow justify-center gaps">
|
||||||
|
{search && <Trans>No search results found</Trans>}
|
||||||
|
{!search && <p><Trans>There are no extensions in</Trans> <code>{extensionsPath}</code></p>}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return extensions.map(ext => {
|
||||||
|
const { id, name, description, isEnabled } = ext;
|
||||||
|
return (
|
||||||
|
<div key={id} className="extension flex gaps align-center">
|
||||||
|
<div className="box grow flex column gaps">
|
||||||
|
<div className="package">
|
||||||
|
Name: <code className="name">{name}</code>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Description: <span className="text-secondary">{description}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{!isEnabled && (
|
||||||
|
<Button plain active onClick={() => ext.enable()}>Enable</Button>
|
||||||
|
)}
|
||||||
|
{isEnabled && (
|
||||||
|
<Button accent onClick={() => ext.disable()}>Disable</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<PageLayout showOnTop className="Extensions" header={<h2>Extensions</h2>}>
|
||||||
|
<WizardLayout infoPanel={this.renderInfo()}>
|
||||||
|
<Input
|
||||||
|
autoFocus
|
||||||
|
theme="round-black"
|
||||||
|
className="SearchInput"
|
||||||
|
placeholder={_i18n._(t`Search extensions`)}
|
||||||
|
value={this.search}
|
||||||
|
onChange={(value) => this.search = value}
|
||||||
|
/>
|
||||||
|
<div className="extension-list flex column gaps">
|
||||||
|
{this.renderExtensions()}
|
||||||
|
</div>
|
||||||
|
</WizardLayout>
|
||||||
|
</PageLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
2
src/renderer/components/+extensions/index.ts
Normal file
2
src/renderer/components/+extensions/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./extensions.route"
|
||||||
|
export * from "./extensions"
|
||||||
@ -4,11 +4,12 @@ import React from "react";
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { RouteComponentProps } from "react-router";
|
import { RouteComponentProps } from "react-router";
|
||||||
import { t, Trans } from "@lingui/macro";
|
import { t, Trans } from "@lingui/macro";
|
||||||
import { Deployment } from "../../api/endpoints";
|
import { Deployment, deploymentApi } from "../../api/endpoints";
|
||||||
import { KubeObjectMenuProps } from "../kube-object/kube-object-menu";
|
import { KubeObjectMenuProps } from "../kube-object/kube-object-menu";
|
||||||
import { MenuItem } from "../menu";
|
import { MenuItem } from "../menu";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { DeploymentScaleDialog } from "./deployment-scale-dialog";
|
import { DeploymentScaleDialog } from "./deployment-scale-dialog";
|
||||||
|
import { ConfirmDialog } from "../confirm-dialog";
|
||||||
import { deploymentStore } from "./deployments.store";
|
import { deploymentStore } from "./deployments.store";
|
||||||
import { replicaSetStore } from "../+workloads-replicasets/replicasets.store";
|
import { replicaSetStore } from "../+workloads-replicasets/replicasets.store";
|
||||||
import { podsStore } from "../+workloads-pods/pods.store";
|
import { podsStore } from "../+workloads-pods/pods.store";
|
||||||
@ -22,6 +23,8 @@ import kebabCase from "lodash/kebabCase";
|
|||||||
import orderBy from "lodash/orderBy";
|
import orderBy from "lodash/orderBy";
|
||||||
import { KubeEventIcon } from "../+events/kube-event-icon";
|
import { KubeEventIcon } from "../+events/kube-event-icon";
|
||||||
import { kubeObjectMenuRegistry } from "../../../extensions/registries/kube-object-menu-registry";
|
import { kubeObjectMenuRegistry } from "../../../extensions/registries/kube-object-menu-registry";
|
||||||
|
import { apiManager } from "../../api/api-manager";
|
||||||
|
import { Notifications } from "../notifications";
|
||||||
|
|
||||||
enum sortBy {
|
enum sortBy {
|
||||||
name = "name",
|
name = "name",
|
||||||
@ -96,10 +99,34 @@ export class Deployments extends React.Component<Props> {
|
|||||||
export function DeploymentMenu(props: KubeObjectMenuProps<Deployment>) {
|
export function DeploymentMenu(props: KubeObjectMenuProps<Deployment>) {
|
||||||
const { object, toolbar } = props;
|
const { object, toolbar } = props;
|
||||||
return (
|
return (
|
||||||
<MenuItem onClick={() => DeploymentScaleDialog.open(object)}>
|
<>
|
||||||
<Icon material="open_with" title={_i18n._(t`Scale`)} interactive={toolbar}/>
|
<MenuItem onClick={() => DeploymentScaleDialog.open(object)}>
|
||||||
<span className="title"><Trans>Scale</Trans></span>
|
<Icon material="open_with" title={_i18n._(t`Scale`)} interactive={toolbar}/>
|
||||||
</MenuItem>
|
<span className="title"><Trans>Scale</Trans></span>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem onClick={() => ConfirmDialog.open({
|
||||||
|
ok: async () =>
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
await deploymentApi.restart({
|
||||||
|
namespace: object.getNs(),
|
||||||
|
name: object.getName(),
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
Notifications.error(err);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
labelOk: _i18n._(t`Restart`),
|
||||||
|
message: (
|
||||||
|
<p>
|
||||||
|
<Trans>Are you sure you want to restart deployment <b>{object.getName()}</b>?</Trans>
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
})}>
|
||||||
|
<Icon material="autorenew" title={_i18n._(t`Restart`)} interactive={toolbar}/>
|
||||||
|
<span className="title"><Trans>Restart</Trans></span>
|
||||||
|
</MenuItem>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,4 +137,3 @@ kubeObjectMenuRegistry.add({
|
|||||||
MenuItem: DeploymentMenu
|
MenuItem: DeploymentMenu
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import { clusterViewRoute, clusterViewURL } from "./cluster-view.route";
|
|||||||
import { clusterStore } from "../../../common/cluster-store";
|
import { clusterStore } from "../../../common/cluster-store";
|
||||||
import { hasLoadedView, initView, lensViews, refreshViews } from "./lens-views";
|
import { hasLoadedView, initView, lensViews, refreshViews } from "./lens-views";
|
||||||
import { globalPageRegistry } from "../../../extensions/registries/page-registry";
|
import { globalPageRegistry } from "../../../extensions/registries/page-registry";
|
||||||
|
import { Extensions, extensionsRoute } from "../+extensions";
|
||||||
import { getMatchedClusterId } from "../../navigation";
|
import { getMatchedClusterId } from "../../navigation";
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
@ -63,6 +64,7 @@ export class ClusterManager extends React.Component {
|
|||||||
<Switch>
|
<Switch>
|
||||||
<Route component={LandingPage} {...landingRoute} />
|
<Route component={LandingPage} {...landingRoute} />
|
||||||
<Route component={Preferences} {...preferencesRoute} />
|
<Route component={Preferences} {...preferencesRoute} />
|
||||||
|
<Route component={Extensions} {...extensionsRoute} />
|
||||||
<Route component={Workspaces} {...workspacesRoute} />
|
<Route component={Workspaces} {...workspacesRoute} />
|
||||||
<Route component={AddCluster} {...addClusterRoute} />
|
<Route component={AddCluster} {...addClusterRoute} />
|
||||||
<Route component={ClusterView} {...clusterViewRoute} />
|
<Route component={ClusterView} {...clusterViewRoute} />
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
.PageLayout {
|
.PageLayout {
|
||||||
$spacing: $padding * 2;
|
$spacing: $padding * 2;
|
||||||
|
--width: 60%;
|
||||||
|
--max-width: 1000px;
|
||||||
|
--min-width: 570px;
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -26,12 +29,15 @@
|
|||||||
> .content-wrapper {
|
> .content-wrapper {
|
||||||
@include custom-scrollbar-themed;
|
@include custom-scrollbar-themed;
|
||||||
padding: $spacing * 2;
|
padding: $spacing * 2;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
> .content {
|
> .content {
|
||||||
|
flex: 1;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
width: 60%;
|
width: var(--width);
|
||||||
min-width: 570px;
|
min-width: var(--min-width);
|
||||||
max-width: 1000px;
|
max-width: var(--max-width);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -167,7 +167,6 @@ export class Tooltip extends React.Component<TooltipProps> {
|
|||||||
top = topCenter;
|
top = topCenter;
|
||||||
break;
|
break;
|
||||||
case "top_right":
|
case "top_right":
|
||||||
default:
|
|
||||||
left = targetBounds.right - tooltipBounds.width;
|
left = targetBounds.right - tooltipBounds.width;
|
||||||
top = topCenter;
|
top = topCenter;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Here you can find description of changes we've built into each release. While we try our best to make each upgrade automatic and as smooth as possible, there may be some cases where you might need to do something to ensure the application works smoothly. So please read through the release highlights!
|
Here you can find description of changes we've built into each release. While we try our best to make each upgrade automatic and as smooth as possible, there may be some cases where you might need to do something to ensure the application works smoothly. So please read through the release highlights!
|
||||||
|
|
||||||
## 4.0.0-alpha.3 (current version)
|
## 4.0.0-alpha.4 (current version)
|
||||||
|
|
||||||
- Extension API
|
- Extension API
|
||||||
- Improved pod logs
|
- Improved pod logs
|
||||||
@ -14,6 +14,8 @@ Here you can find description of changes we've built into each release. While we
|
|||||||
- Status bar visual fixes
|
- Status bar visual fixes
|
||||||
- Fix proxy upgrade socket timeouts
|
- Fix proxy upgrade socket timeouts
|
||||||
- Fix UI staleness after network issues
|
- Fix UI staleness after network issues
|
||||||
|
- Add +/- buttons in scale deployment popup screen
|
||||||
|
- Update chart details when selecting another chart
|
||||||
|
|
||||||
## 3.6.6
|
## 3.6.6
|
||||||
- Fix labels' word boundary to cover only drawer badges
|
- Fix labels' word boundary to cover only drawer badges
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user