diff --git a/.azure-pipelines-k8s-matrix.yml b/.azure-pipelines-k8s-matrix.yml index 9f1f38ec67..cdcc18b740 100644 --- a/.azure-pipelines-k8s-matrix.yml +++ b/.azure-pipelines-k8s-matrix.yml @@ -15,7 +15,7 @@ trigger: none jobs: - job: Linux pool: - vmImage: ubuntu-20.04 + vmImage: ubuntu-18.04 strategy: matrix: kube_1.16: diff --git a/.azure-pipelines.yml b/.azure-pipelines.yml index e360a818e6..3c928430ed 100644 --- a/.azure-pipelines.yml +++ b/.azure-pipelines.yml @@ -109,7 +109,7 @@ jobs: - job: Linux pool: - vmImage: ubuntu-20.04 + vmImage: ubuntu-18.04 strategy: matrix: node_14.x: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 25b3327ac0..7590f42289 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,7 +2,7 @@ name: Test on: pull_request: branches: - - "*" + - "**" push: branches: - master @@ -12,7 +12,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-20.04, macos-11, windows-2019] + os: [ubuntu-18.04, macos-11, windows-2019] node-version: [14.x] steps: - name: Checkout Release from lens diff --git a/.yarnrc b/.yarnrc index 9f7a2e491b..3666616e0b 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,3 +1,3 @@ disturl "https://atom.io/download/electron" -target "12.0.15" +target "13.4.0" runtime "electron" diff --git a/RELEASE_GUIDE.md b/RELEASE_GUIDE.md index fde9786459..b11a47c134 100644 --- a/RELEASE_GUIDE.md +++ b/RELEASE_GUIDE.md @@ -2,7 +2,9 @@ Lens releases are built by CICD automatically on git tags. The typical release process flow is the following: -1. From a clean and up to date `master` run `make release-version ` where `` is one of the following: +1. If doing a patch release checkout the `release/vMAJOR.MINOR` branch for the appropriate `MAJOR`/`MINOR` version and manually `cherry-pick` the PRs required for the patch that were commited to master. If there are any conflicts they must be resolved manually. If necessary, get assistance from the PR authors. + +1. From a clean and up to date `master` (or `release/vMAJOR.MINOR` if doing a patch release) run `make release-version ` where `` is one of the following: - `major` - `minor` - `patch` @@ -17,15 +19,15 @@ Lens releases are built by CICD automatically on git tags. The typical release p - `rc` This assumes origin is set to https://github.com/lensapp/lens.git. If not then set GIT_REMOTE to the remote that is set to https://github.com/lensapp/lens.git. For example run `GIT_REMOTE=upstream make release-version ...` -1. Open the PR (git should have printed a link to GitHub in the console) with the contents of all the accepted PRs since the last release. The PR description needs to be filled with the draft release description. From https://github.com/lensapp/lens click on Releases, the draft release should be first in the list, click `Edit` and copy/paste the markdown to the PR description. Add the `skip-changelog` label and click `Create Pull Request`. -1. After the PR is accepted and passes CI (and before merging), go to the same branch and run `make tag-release` (set GIT_REMOTE if necessary). This additionally runs the azure jobs to build the binaries and put them on S3. +1. Open the PR (git should have printed a link to GitHub in the console) with the contents of all the accepted PRs since the last release. The PR description needs to be filled with the draft release description. From https://github.com/lensapp/lens click on Releases, the draft release should be first in the list, click `Edit` and copy/paste the markdown to the PR description. Add the `skip-changelog` label and click `Create Pull Request`. If this is a patch release be sure to set the PR base branch to `release/vMAJOR.MINOR` instead of `master`. +1. After the PR is accepted and passes CI (and before merging), go to the same branch and run `make tag-release` (set GIT_REMOTE if necessary). This additionally triggers the azure jobs to build the binaries and put them on S3. 1. If the CI fails at this stage the problem needs to be fixed. Sometimes an azure job fails due to outside service issues (e.g. Apple signing occasionally fails), in which case the specific azure job can be rerun from https://dev.azure.com/lensapp/lensapp/_build. Otherwise changes to the codebase may need to be done and committed to the release branch and pushed to https://github.com/lensapp/lens. CI will run again. As well the release tag needs to be manually set to this new commit. You can do something like: - `git push origin :refs/tags/vX.Y.Z-beta.N` (removes the tag from https://github.com/lensapp/lens) - `git tag -fa vX.Y.Z-beta.N` (move the tag locally to the current commit) - `git push origin --tags` (update the tags on https://github.com/lensapp/lens to reflect this local change) 1. Once CI passes again go to the releases tab on GitHub. You can use the existing draft release prepared by k8slens-bot (select the correct tag). Or you can create a new release from the tag that was created, and make sure that the change log is the same as that of the PR, and the title is the tag. Either way, click the prerelease checkbox if this is not a new major, minor, or patch version before clicking `Publish release`. -1. Merge the release PR after the release is published. GitHub should delete the branch once it is merged. +1. Merge the release PR after the release is published. If it is a patch release then there is no need to squash the cherry-picked commits as part of the merge. GitHub should delete the branch once it is merged. 1. If you have just released a new major or minor version then create a new `release/vMAJOR.MINOR` branch from that same tag and push it to https://github.com/lensapp/lens. Given the commit of the merged release PR from the master branch you can do this like `git push origin :refs/heads/release/vX.Y` @@ -37,4 +39,4 @@ Other tasks - generate a changelog from the prerelease descriptions (for major/minor releases) - announce the release on lens and lens-hq slack channels (release is announced automatically on the community slack lens channel through the above publishing process) - announce on lens-hq that master is open for PR merges for the next release (for major/minor releases) - - update issues on github (bump those that did not make it into the release to a subsequent release) (for major/minor releases) \ No newline at end of file + - update issues on github (bump those that did not make it into the release to a subsequent release) (for major/minor/patch releases) \ No newline at end of file diff --git a/extensions/metrics-cluster-feature/package-lock.json b/extensions/metrics-cluster-feature/package-lock.json index 981cb43dff..1d2e2217cb 100644 --- a/extensions/metrics-cluster-feature/package-lock.json +++ b/extensions/metrics-cluster-feature/package-lock.json @@ -6645,9 +6645,9 @@ } }, "tmpl": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", - "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true }, "to-arraybuffer": { diff --git a/extensions/node-menu/package-lock.json b/extensions/node-menu/package-lock.json index 02d953b830..88ada2da7e 100644 --- a/extensions/node-menu/package-lock.json +++ b/extensions/node-menu/package-lock.json @@ -6608,9 +6608,9 @@ } }, "tmpl": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", - "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true }, "to-arraybuffer": { diff --git a/extensions/pod-menu/package-lock.json b/extensions/pod-menu/package-lock.json index 3abe5a107f..b2c2e736fe 100644 --- a/extensions/pod-menu/package-lock.json +++ b/extensions/pod-menu/package-lock.json @@ -6561,9 +6561,9 @@ } }, "tmpl": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", - "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true }, "to-arraybuffer": { diff --git a/package.json b/package.json index 9da0f23cac..6b19e77edc 100644 --- a/package.json +++ b/package.json @@ -185,7 +185,6 @@ "@kubernetes/client-node": "^0.15.1", "@sentry/electron": "^2.5.0", "@sentry/integrations": "^6.10.0", - "@types/which": "^2.0.1", "abort-controller": "^3.0.0", "array-move": "^3.0.1", "auto-bind": "^4.0.0", @@ -194,6 +193,7 @@ "byline": "^5.0.0", "chalk": "^4.1.0", "chokidar": "^3.4.3", + "command-exists": "1.2.9", "conf": "^7.0.1", "crypto-js": "^4.1.1", "electron-devtools-installer": "^3.2.0", @@ -223,7 +223,7 @@ "monaco-editor": "^0.26.1", "node-fetch": "^2.6.1", "node-pty": "^0.10.1", - "npm": "^6.14.8", + "npm": "^6.14.15", "openid-client": "^3.15.2", "p-limit": "^3.1.0", "path-to-regexp": "^6.1.0", @@ -246,7 +246,6 @@ "tempy": "^0.5.0", "url-parse": "^1.5.1", "uuid": "^8.3.2", - "which": "^2.0.2", "win-ca": "^3.2.0", "winston": "^3.3.3", "winston-console-format": "^1.0.8", @@ -328,7 +327,7 @@ "css-loader": "^5.2.6", "deepdash": "^5.3.5", "dompurify": "^2.3.1", - "electron": "^12.0.17", + "electron": "^13.4.0", "electron-builder": "^22.11.11", "electron-notarize": "^0.3.0", "esbuild": "^0.12.24", @@ -368,7 +367,7 @@ "react-select-event": "^5.1.0", "react-table": "^7.7.0", "react-window": "^1.8.5", - "sass": "^1.39.0", + "sass": "^1.41.1", "sass-loader": "^8.0.2", "sharp": "^0.29.0", "style-loader": "^2.0.0", diff --git a/src/common/catalog-entities/web-link.ts b/src/common/catalog-entities/web-link.ts index d54aa8533a..8309945f24 100644 --- a/src/common/catalog-entities/web-link.ts +++ b/src/common/catalog-entities/web-link.ts @@ -57,6 +57,10 @@ export class WebLink extends CatalogEntity(this) + ?.emit("contextMenuOpen", this, context); } } diff --git a/src/common/custom-errors.ts b/src/common/custom-errors.ts index 0016af87ca..2cbc75ccf0 100644 --- a/src/common/custom-errors.ts +++ b/src/common/custom-errors.ts @@ -19,18 +19,15 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import path from "path"; - export class ExecValidationNotFoundError extends Error { - constructor(execPath: string) { + constructor(execPath: string, isAbsolute: boolean) { + super(`User Exec command "${execPath}" not found on host.`); let message = `User Exec command "${execPath}" not found on host.`; - if (!path.isAbsolute(execPath)) { + if (!isAbsolute) { message += ` Please ensure binary is found in PATH or use absolute path to binary in Kubeconfig`; } - - super(message); - + this.message = message; this.name = this.constructor.name; Error.captureStackTrace(this, this.constructor); } diff --git a/src/common/k8s-api/kube-object.store.ts b/src/common/k8s-api/kube-object.store.ts index dc428eb08b..f90eaab8e5 100644 --- a/src/common/k8s-api/kube-object.store.ts +++ b/src/common/k8s-api/kube-object.store.ts @@ -159,7 +159,7 @@ export abstract class KubeObjectStore extends ItemStore this.loadedNamespaces = namespaces; return Promise // load resources per namespace - .all(namespaces.map(namespace => api.list({ namespace, reqInit }))) + .all(namespaces.map(namespace => api.list({ namespace, reqInit }, this.query))) .then(items => items.flat().filter(Boolean)); } } diff --git a/src/common/kube-helpers.ts b/src/common/kube-helpers.ts index 4bd420e7b2..aff05e68c6 100644 --- a/src/common/kube-helpers.ts +++ b/src/common/kube-helpers.ts @@ -25,11 +25,11 @@ import path from "path"; import os from "os"; import yaml from "js-yaml"; import logger from "../main/logger"; +import commandExists from "command-exists"; import { ExecValidationNotFoundError } from "./custom-errors"; import { Cluster, Context, newClusters, newContexts, newUsers, User } from "@kubernetes/client-node/dist/config_types"; import { resolvePath } from "./utils"; import Joi from "joi"; -import which from "which"; export type KubeConfigValidationOpts = { validateCluster?: boolean; @@ -295,17 +295,13 @@ export function validateKubeConfig(config: KubeConfig, contextName: string, vali // Validate exec command if present if (validateExec && user?.exec) { - try { - which.sync(user.exec.command); + const execCommand = user.exec["command"]; + // check if the command is absolute or not + const isAbsolute = path.isAbsolute(execCommand); - // If this doesn't throw an error it also means that it has found the executable. - } catch (error) { - switch (error?.code) { - case "ENOENT": - return new ExecValidationNotFoundError(user.exec.command); - default: - return error; - } + // validate the exec struct in the user object, start with the command field + if (!commandExists.sync(execCommand)) { + return new ExecValidationNotFoundError(execCommand, isAbsolute); } } diff --git a/src/common/logger.ts b/src/common/logger.ts index 825fa2493b..ca975871b8 100644 --- a/src/common/logger.ts +++ b/src/common/logger.ts @@ -59,24 +59,24 @@ if (ipcMain) { ), }) ); + + if (!isTestEnv) { + transports.push( + new winston.transports.File({ + handleExceptions: false, + level: logLevel, + filename: "lens.log", + dirname: getPath("logs"), + maxsize: 16 * 1024, + maxFiles: 16, + tailable: true, + }) + ); + } } else { transports.push(new BrowserConsole()); } -if (!isTestEnv) { - transports.push( - new winston.transports.File({ - handleExceptions: false, - level: logLevel, - filename: "lens.log", - dirname: getPath("logs"), - maxsize: 16 * 1024, - maxFiles: 16, - tailable: true, - }) - ); -} - export default winston.createLogger({ format: format.simple(), transports, diff --git a/src/extensions/registries/base-registry.ts b/src/extensions/registries/base-registry.ts index af00fb48fd..9b596172c9 100644 --- a/src/extensions/registries/base-registry.ts +++ b/src/extensions/registries/base-registry.ts @@ -25,7 +25,7 @@ import { Singleton } from "../../common/utils"; import { LensExtension } from "../lens-extension"; export class BaseRegistry extends Singleton { - private items = observable.map([], {deep: false}); + private items = observable.map([], { deep: false }); constructor() { super(); diff --git a/src/extensions/registries/command-registry.ts b/src/extensions/registries/command-registry.ts index 5c38e32c71..3916b0f721 100644 --- a/src/extensions/registries/command-registry.ts +++ b/src/extensions/registries/command-registry.ts @@ -25,9 +25,9 @@ import { BaseRegistry } from "./base-registry"; import type { LensExtension } from "../lens-extension"; import type { CatalogEntity } from "../../common/catalog"; -export type CommandContext = { +export interface CommandContext { entity?: CatalogEntity; -}; +} export interface CommandRegistration { id: string; diff --git a/src/main/window-manager.ts b/src/main/window-manager.ts index be6d71498e..f751d79790 100644 --- a/src/main/window-manager.ts +++ b/src/main/window-manager.ts @@ -31,7 +31,6 @@ import { IpcRendererNavigationEvents } from "../renderer/navigation/events"; import logger from "./logger"; import { productName } from "../common/vars"; import { LensProxy } from "./lens-proxy"; -import * as path from "path"; function isHideable(window: BrowserWindow | null): boolean { return Boolean(window && !window.isDestroyed()); @@ -85,7 +84,6 @@ export class WindowManager extends Singleton { titleBarStyle: "hiddenInset", backgroundColor: "#1e2124", webPreferences: { - preload: path.join(__static, "build", "preload.js"), nodeIntegration: true, nodeIntegrationInSubFrames: true, enableRemoteModule: true, @@ -111,10 +109,6 @@ export class WindowManager extends Singleton { app.dock?.hide(); // hide icon in dock (mac-os) }) .webContents - .on("new-window", (event, url) => { - event.preventDefault(); - shell.openExternal(url); - }) .on("dom-ready", () => { appEventBus.emit({ name: "app", action: "dom-ready" }); }) @@ -152,6 +146,10 @@ export class WindowManager extends Singleton { // Always disable Node.js integration for all webviews webPreferences.nodeIntegration = false; + }).setWindowOpenHandler((details) => { + shell.openExternal(details.url); + + return { action: "deny" }; }); } diff --git a/src/renderer/api/catalog-entity-registry.ts b/src/renderer/api/catalog-entity-registry.ts index 7b09ff273b..d731e2562a 100644 --- a/src/renderer/api/catalog-entity-registry.ts +++ b/src/renderer/api/catalog-entity-registry.ts @@ -31,7 +31,7 @@ import { once } from "lodash"; export type EntityFilter = (entity: CatalogEntity) => any; export class CatalogEntityRegistry { - @observable.ref activeEntity: CatalogEntity; + @observable protected activeEntityId: string | undefined = undefined; protected _entities = observable.map([], { deep: true }); protected filters = observable.set([], { deep: false, @@ -46,6 +46,22 @@ export class CatalogEntityRegistry { makeObservable(this); } + get activeEntity(): CatalogEntity | null { + return this._entities.get(this.activeEntityId) || null; + } + + set activeEntity(raw: CatalogEntity | string | null) { + if (raw) { + const id = typeof raw === "string" + ? raw + : raw.getId(); + + this.activeEntityId = id; + } else { + this.activeEntityId = undefined; + } + } + init() { ipcRendererOn("catalog:items", (event, items: (CatalogEntityData & CatalogEntityKindData)[]) => { this.updateItems(items); diff --git a/src/renderer/components/+add-cluster/add-cluster.scss b/src/renderer/components/+add-cluster/add-cluster.scss index 8abb38e9d0..faac757c40 100644 --- a/src/renderer/components/+add-cluster/add-cluster.scss +++ b/src/renderer/components/+add-cluster/add-cluster.scss @@ -50,11 +50,4 @@ display: block; padding-top: 6px; } - - .actions-panel { - .Spinner { - vertical-align: middle; - margin-left: $spacing; - } - } } diff --git a/src/renderer/components/+add-cluster/add-cluster.tsx b/src/renderer/components/+add-cluster/add-cluster.tsx index 16a6dec19e..81a75019fa 100644 --- a/src/renderer/components/+add-cluster/add-cluster.tsx +++ b/src/renderer/components/+add-cluster/add-cluster.tsx @@ -24,7 +24,7 @@ import "./add-cluster.scss"; import type { KubeConfig } from "@kubernetes/client-node"; import fse from "fs-extra"; import { debounce } from "lodash"; -import { action, computed, observable, makeObservable, runInAction } from "mobx"; +import { action, computed, observable, makeObservable } from "mobx"; import { observer } from "mobx-react"; import path from "path"; import React from "react"; @@ -41,7 +41,6 @@ import { SettingLayout } from "../layout/setting-layout"; import MonacoEditor from "react-monaco-editor"; import { ThemeStore } from "../../theme.store"; import { UserStore } from "../../../common/user-store"; -import { Spinner } from "../spinner"; interface Option { config: KubeConfig; @@ -63,8 +62,7 @@ export class AddCluster extends React.Component { @observable kubeContexts = observable.map(); @observable customConfig = ""; @observable isWaiting = false; - @observable isCheckingInput = false; - @observable errorText: string; + @observable errors: string[] = []; constructor(props: {}) { super(props); @@ -77,40 +75,26 @@ export class AddCluster extends React.Component { @computed get allErrors(): string[] { return [ - this.errorText, + ...this.errors, ...iter.map(this.kubeContexts.values(), ({ error }) => error) ].filter(Boolean); } - _refreshContexts = debounce(() => { - runInAction(() => { - try { - const text = this.customConfig.trim(); + @action + refreshContexts = debounce(() => { + const { config, error } = loadConfigFromString(this.customConfig.trim() || "{}"); - if (!text) { - return this.kubeContexts.clear(); - } + this.kubeContexts.replace(getContexts(config)); + + if (error) { + this.errors.push(error.toString()); + } - const { config, error } = loadConfigFromString(text); - - this.kubeContexts.replace(getContexts(config)); - this.errorText = error?.toString(); - } catch (error) { - this.kubeContexts.clear(); - this.errorText = error?.toString() || "An error occured"; - } finally { - this.isCheckingInput = false; - } - }); + if (config.contexts.length === 0) { + this.errors.push('No contexts defined, either missing the "contexts" field, or it is empty.'); + } }, 500); - refreshContexts = () => { - // Clear the kubeContexts immediately - this.isCheckingInput = true; - this.kubeContexts.clear(); - this._refreshContexts(); - }; - @action addClusters = async () => { this.isWaiting = true; @@ -147,7 +131,7 @@ export class AddCluster extends React.Component { value={this.customConfig} onChange={value => { this.customConfig = value; - this.errorText = ""; + this.errors.length = 0; this.refreshContexts(); }} /> @@ -168,7 +152,6 @@ export class AddCluster extends React.Component { tooltip={this.kubeContexts.size === 0 || "Paste in at least one cluster to add."} tooltipOverrideDisabled /> - {this.isCheckingInput && } ); diff --git a/src/renderer/components/+entity-settings/entity-settings.scss b/src/renderer/components/+entity-settings/entity-settings.module.css similarity index 76% rename from src/renderer/components/+entity-settings/entity-settings.scss rename to src/renderer/components/+entity-settings/entity-settings.module.css index 30c01b8a67..604ecd5c5b 100644 --- a/src/renderer/components/+entity-settings/entity-settings.scss +++ b/src/renderer/components/+entity-settings/entity-settings.module.css @@ -19,26 +19,12 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -.EntitySettings { - $spacing: $padding * 3; - - - // TODO: move sub-component styles to separate files - .admin-note { - font-size: small; - opacity: 0.5; - margin-left: $margin; - } - - .button-area { - margin-top: $margin * 2; - } - - .file-loader { - margin-top: $margin * 2; - } - - .Input, .Select { - margin-top: $padding; - } -} +.entityName { + @apply font-bold overflow-hidden; + word-break: break-word; + color: var(--textColorAccent); + display: -webkit-box; + /* Simulate text-overflow:ellipsis styles but for multiple text lines */ + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; +} \ No newline at end of file diff --git a/src/renderer/components/+entity-settings/entity-settings.tsx b/src/renderer/components/+entity-settings/entity-settings.tsx index f656b6fa9d..bcb5895413 100644 --- a/src/renderer/components/+entity-settings/entity-settings.tsx +++ b/src/renderer/components/+entity-settings/entity-settings.tsx @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import "./entity-settings.scss"; +import styles from "./entity-settings.module.css"; import React from "react"; import { observable, makeObservable } from "mobx"; @@ -98,7 +98,9 @@ export class EntitySettings extends React.Component { source={this.entity.metadata.source} src={this.entity.spec.icon?.src} /> -

{this.entity.metadata.name}

+
+ {this.entity.metadata.name} +
{ groups.map((group, groupIndex) => ( @@ -138,7 +140,6 @@ export class EntitySettings extends React.Component { return ( diff --git a/src/renderer/components/activate-entity-command/activate-entity-command.tsx b/src/renderer/components/activate-entity-command/activate-entity-command.tsx new file mode 100644 index 0000000000..110afd6b3c --- /dev/null +++ b/src/renderer/components/activate-entity-command/activate-entity-command.tsx @@ -0,0 +1,58 @@ +/** + * 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 { computed } from "mobx"; +import { observer } from "mobx-react"; +import React from "react"; +import { CatalogEntity, catalogEntityRunContext } from "../../api/catalog-entity"; +import { catalogEntityRegistry } from "../../api/catalog-entity-registry"; +import { CommandOverlay } from "../command-palette"; +import { Select } from "../select"; + +@observer +export class ActivateEntityCommand extends React.Component { + @computed get options() { + return catalogEntityRegistry.items.map(entity => ({ + label: `${entity.kind}: ${entity.getName()}`, + value: entity, + })); + } + + onSelect(entity: CatalogEntity): void { + entity.onRun?.(catalogEntityRunContext); + CommandOverlay.close(); + } + + render() { + return ( +