diff --git a/.azure-pipelines.yml b/.azure-pipelines.yml index afec611177..223f85518d 100644 --- a/.azure-pipelines.yml +++ b/.azure-pipelines.yml @@ -24,12 +24,12 @@ jobs: Write-Output ("##vso[task.setvariable variable=CI_BUILD_TAG;]$CI_BUILD_TAG") condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))" displayName: Set the tag name as an environment variable - + - task: NodeTool@0 inputs: versionSpec: $(node_version) displayName: Install Node.js - + - task: Cache@2 inputs: key: 'yarn | "$(Agent.OS)"" | yarn.lock' @@ -37,7 +37,7 @@ jobs: yarn | "$(Agent.OS)" path: $(YARN_CACHE_FOLDER) displayName: Cache Yarn packages - + - bash: | set -e git clone "https://${GH_TOKEN}@github.com/lensapp/lens-ide.git" .lens-ide-overlay @@ -47,13 +47,13 @@ jobs: env: GH_TOKEN: $(LENS_IDE_GH_TOKEN) displayName: Customize config - + - script: make node_modules displayName: Install dependencies - + - script: make build-npm displayName: Generate npm package - + - script: make build condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))" env: @@ -75,12 +75,12 @@ jobs: - script: CI_BUILD_TAG=`git describe --tags` && echo "##vso[task.setvariable variable=CI_BUILD_TAG]$CI_BUILD_TAG" condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))" displayName: Set the tag name as an environment variable - + - task: NodeTool@0 inputs: versionSpec: $(node_version) displayName: Install Node.js - + - task: Cache@2 inputs: key: 'yarn | "$(Agent.OS)" | yarn.lock' @@ -88,7 +88,7 @@ jobs: yarn | "$(Agent.OS)" path: $(YARN_CACHE_FOLDER) displayName: Cache Yarn packages - + - bash: | set -e git clone "https://${GH_TOKEN}@github.com/lensapp/lens-ide.git" .lens-ide-overlay @@ -98,13 +98,13 @@ jobs: env: GH_TOKEN: $(LENS_IDE_GH_TOKEN) displayName: Customize config - + - script: make node_modules displayName: Install dependencies - + - script: make build-npm displayName: Generate npm package - + - script: make build condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))" env: @@ -128,12 +128,12 @@ jobs: - script: CI_BUILD_TAG=`git describe --tags` && echo "##vso[task.setvariable variable=CI_BUILD_TAG]$CI_BUILD_TAG" condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))" displayName: Set the tag name as an environment variable - + - task: NodeTool@0 inputs: versionSpec: $(node_version) displayName: Install Node.js - + - task: Cache@2 inputs: key: 'yarn | "$(Agent.OS)" | yarn.lock' @@ -141,7 +141,7 @@ jobs: yarn | "$(Agent.OS)" path: $(YARN_CACHE_FOLDER) displayName: Cache Yarn packages - + - bash: | set -e git clone "https://${GH_TOKEN}@github.com/lensapp/lens-ide.git" .lens-ide-overlay @@ -151,13 +151,13 @@ jobs: env: GH_TOKEN: $(LENS_IDE_GH_TOKEN) displayName: Customize config - + - script: make node_modules displayName: Install dependencies - + - script: make build-npm displayName: Generate npm package - + - bash: | sudo chown root:root / sudo apt-get update && sudo apt-get install -y snapd @@ -168,7 +168,7 @@ jobs: env: SNAP_LOGIN: $(SNAP_LOGIN) displayName: Setup snapcraft - + - script: make build condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))" env: diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000000..fc2692be70 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,71 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ master ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ master ] + schedule: + - cron: '41 3 * * 2' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'javascript' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/Makefile b/Makefile index 8b2558e7f7..1a8baf0d37 100644 --- a/Makefile +++ b/Makefile @@ -60,6 +60,8 @@ build: node_modules binaries/client $(MAKE) build-extensions -B yarn run compile ifeq "$(DETECTED_OS)" "Windows" +# https://github.com/ukoloff/win-ca#clear-pem-folder-on-publish + rm -rf node_modules/win-ca/pem yarn run electron-builder --publish onTag --x64 --ia32 else yarn run electron-builder --publish onTag diff --git a/docs/extensions/get-started/anatomy.md b/docs/extensions/get-started/anatomy.md index 87b3042189..745bff5a89 100644 --- a/docs/extensions/get-started/anatomy.md +++ b/docs/extensions/get-started/anatomy.md @@ -6,7 +6,7 @@ In this section you will learn how this extension works under the hood. The Hello World sample extension does three things: - Implements `onActivate()` and outputs a message to the console. -- Implements `onDectivate()` and outputs a message to the console. +- Implements `onDeactivate()` and outputs a message to the console. - Registers `ClusterPage` so that the page is visible in the left-side menu of the cluster dashboard. Let's take a closer look at our Hello World sample's source code and see how these three things are achieved. diff --git a/docs/extensions/guides/generator.md b/docs/extensions/guides/generator.md index cbbdaa31bd..64838c1bc4 100644 --- a/docs/extensions/guides/generator.md +++ b/docs/extensions/guides/generator.md @@ -72,4 +72,4 @@ To dive deeper, consider looking at [Common Capabilities](../capabilities/common If you find problems with the Lens Extension Generator, or have feature requests, you are welcome to raise an [issue](https://github.com/lensapp/generator-lens-ext/issues). You can find the latest Lens contribution guidelines [here](https://docs.k8slens.dev/latest/contributing). -The Generator source code is hosted at [Github](https://github.com/lensapp/generator-lens-ext). +The Generator source code is hosted at [GitHub](https://github.com/lensapp/generator-lens-ext). diff --git a/docs/extensions/guides/kube-object-list-layout.md b/docs/extensions/guides/kube-object-list-layout.md index f5ee72e902..6d350c98cf 100644 --- a/docs/extensions/guides/kube-object-list-layout.md +++ b/docs/extensions/guides/kube-object-list-layout.md @@ -213,7 +213,7 @@ export class CertificatePage extends React.Component<{ extension: LensRendererEx return ( certificate.getName(), [sortBy.namespace]: (certificate: Certificate) => certificate.metadata.namespace, diff --git a/docs/extensions/guides/main-extension.md b/docs/extensions/guides/main-extension.md index 8a501ca533..fec937db17 100644 --- a/docs/extensions/guides/main-extension.md +++ b/docs/extensions/guides/main-extension.md @@ -94,7 +94,7 @@ 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"`. -This page would be defined in your extension's `Renderer.LensExtension` implmentation (See [`Renderer.LensExtension`](renderer-extension.md)). +This page would be defined in your extension's `Renderer.LensExtension` implementation (See [`Renderer.LensExtension`](renderer-extension.md)). ### `addCatalogSource()` and `removeCatalogSource()` Methods diff --git a/docs/extensions/guides/resource-stack.md b/docs/extensions/guides/resource-stack.md index 8d36bb6495..4d8c494339 100644 --- a/docs/extensions/guides/resource-stack.md +++ b/docs/extensions/guides/resource-stack.md @@ -90,7 +90,7 @@ This is the cluster that the resource stack will be applied to, and the construc Similarly, `ExampleClusterFeature` implements an `uninstall()` method which simply invokes the `kubectlDeleteFolder()` method of the `Renderer.K8sApi.ResourceStack` class. `kubectlDeleteFolder()` tries to delete from the cluster all kubernetes resources found in the folder passed to it, again in this case `../resources`. -`ExampleClusterFeature` also implements an `isInstalled()` method, which demonstrates how you can utiliize the kubernetes api to inspect the resource stack status. +`ExampleClusterFeature` also implements an `isInstalled()` method, which demonstrates how you can utilize the kubernetes api to inspect the resource stack status. `isInstalled()` simply tries to find a pod named `example-pod`, as a way to determine if the pod is already installed. This method can be useful in creating a context-sensitive UI for installing/uninstalling the feature, as demonstrated in the next sample code. diff --git a/docs/extensions/guides/stores.md b/docs/extensions/guides/stores.md index 3a990f58b7..8b31548a32 100644 --- a/docs/extensions/guides/stores.md +++ b/docs/extensions/guides/stores.md @@ -148,8 +148,8 @@ export class ExamplePreferenceInput extends React.Component { return ( { ExamplePreferencesStore.getInstace().enabled = v; }} + value={ExamplePreferencesStore.getInstance().enabled} + onChange={v => { ExamplePreferencesStore.getInstance().enabled = v; }} /> ); } diff --git a/docs/extensions/testing-and-publishing/publishing.md b/docs/extensions/testing-and-publishing/publishing.md index d8e7b8efad..9ea33c4e19 100644 --- a/docs/extensions/testing-and-publishing/publishing.md +++ b/docs/extensions/testing-and-publishing/publishing.md @@ -43,4 +43,4 @@ Say you have your project folder at `~/my-extension/` and you want to create an npm pack ``` -This will create a NPM tarball that can be hosted on Github Releases or any other publicly available file hosting service. +This will create a NPM tarball that can be hosted on GitHub Releases or any other publicly available file hosting service. diff --git a/extensions/kube-object-event-status/package/package.json b/extensions/kube-object-event-status/package/package.json deleted file mode 100644 index e6ab1bb688..0000000000 --- a/extensions/kube-object-event-status/package/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "kube-object-event-status", - "version": "0.1.0", - "description": "Adds kube object status from events", - "renderer": "dist/renderer.js", - "lens": { - "metadata": {}, - "styles": [] - }, - "scripts": { - "build": "webpack && npm pack", - "dev": "webpack --watch", - "test": "echo NO TESTS" - }, - "files": [ - "dist/**/*" - ], - "dependencies": {}, - "devDependencies": { - "@k8slens/extensions": "file:../../src/extensions/npm/extensions", - "ts-loader": "^8.0.4", - "typescript": "^4.0.3", - "webpack": "^4.44.2" - } -} diff --git a/package.json b/package.json index d3535c10fe..2fe4ec005f 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "productName": "OpenLens", "description": "OpenLens - Open Source IDE for Kubernetes", "homepage": "https://github.com/lensapp/lens", - "version": "5.0.2", + "version": "5.1.2", "main": "static/build/main.js", "copyright": "Š 2021 OpenLens Authors", "license": "MIT", @@ -49,7 +49,8 @@ }, "config": { "bundledKubectlVersion": "1.18.15", - "bundledHelmVersion": "3.5.4" + "bundledHelmVersion": "3.5.4", + "sentryDsn": "" }, "engines": { "node": ">=12 <13" @@ -62,8 +63,7 @@ }, "moduleNameMapper": { "\\.(css|scss)$": "/__mocks__/styleMock.ts", - "\\.(svg)$": "/__mocks__/imageMock.ts", - "^@lingui/macro$": "/__mocks__/@linguiMacro.ts" + "\\.(svg)$": "/__mocks__/imageMock.ts" }, "modulePathIgnorePatterns": [ "/dist", @@ -183,6 +183,8 @@ "@hapi/call": "^8.0.1", "@hapi/subtext": "^7.0.3", "@kubernetes/client-node": "^0.14.3", + "@sentry/electron": "^2.5.0", + "@sentry/integrations": "^6.8.0", "abort-controller": "^3.0.0", "array-move": "^3.0.1", "auto-bind": "^4.0.0", @@ -198,7 +200,6 @@ "electron-updater": "^4.3.1", "electron-window-state": "^5.0.3", "filehound": "^1.17.4", - "filenamify": "^4.1.0", "fs-extra": "^9.0.1", "grapheme-splitter": "^1.0.4", "handlebars": "^4.7.7", @@ -215,7 +216,7 @@ "mobx": "^6.3.0", "mobx-observable-history": "^2.0.1", "mobx-react": "^7.1.0", - "mock-fs": "^4.12.0", + "mock-fs": "^4.14.0", "moment": "^2.29.1", "moment-timezone": "^0.5.33", "node-pty": "^0.9.0", @@ -251,6 +252,8 @@ "@material-ui/icons": "^4.11.2", "@material-ui/lab": "^4.0.0-alpha.57", "@pmmmwh/react-refresh-webpack-plugin": "^0.4.3", + "@sentry/react": "^6.8.0", + "@sentry/types": "^6.8.0", "@testing-library/jest-dom": "^5.13.0", "@testing-library/react": "^11.2.6", "@types/byline": "^4.2.32", @@ -274,11 +277,11 @@ "@types/marked": "^2.0.3", "@types/md5-file": "^4.0.2", "@types/mini-css-extract-plugin": "^0.9.1", - "@types/mock-fs": "^4.10.0", + "@types/mock-fs": "^4.13.1", "@types/module-alias": "^2.0.0", "@types/node": "12.20", "@types/npm": "^2.0.31", - "@types/progress-bar-webpack-plugin": "^2.1.0", + "@types/progress-bar-webpack-plugin": "^2.1.2", "@types/proper-lockfile": "^4.1.1", "@types/randomcolor": "^0.5.5", "@types/react": "^17.0.0", @@ -294,9 +297,8 @@ "@types/request-promise-native": "^1.0.17", "@types/semver": "^7.2.0", "@types/sharp": "^0.28.3", - "@types/shelljs": "^0.8.8", "@types/spdy": "^3.4.4", - "@types/tar": "^4.0.4", + "@types/tar": "^4.0.5", "@types/tcp-port-used": "^1.0.0", "@types/tempy": "^0.3.0", "@types/url-parse": "^1.4.3", @@ -329,15 +331,15 @@ "eslint-plugin-unused-imports": "^1.0.1", "file-loader": "^6.2.0", "flex.box": "^3.4.4", - "fork-ts-checker-webpack-plugin": "^5.0.0", + "fork-ts-checker-webpack-plugin": "^5.2.1", "hoist-non-react-statics": "^3.3.2", - "html-webpack-plugin": "^4.3.0", + "html-webpack-plugin": "^4.5.2", "identity-obj-proxy": "^3.0.0", "include-media": "^1.4.9", - "jest": "^26.0.1", + "jest": "26.6.3", "jest-canvas-mock": "^2.3.0", "jest-fetch-mock": "^3.0.3", - "jest-mock-extended": "^1.0.10", + "jest-mock-extended": "^1.0.16", "make-plural": "^6.2.2", "mini-css-extract-plugin": "^1.6.0", "node-loader": "^1.0.3", @@ -350,7 +352,7 @@ "postinstall-postinstall": "^2.1.0", "progress-bar-webpack-plugin": "^2.1.0", "randomcolor": "^0.6.2", - "raw-loader": "^4.0.1", + "raw-loader": "^4.0.2", "react-beautiful-dnd": "^13.1.0", "react-refresh": "^0.9.0", "react-router-dom": "^5.2.0", @@ -363,7 +365,7 @@ "spectron": "11.0.0", "style-loader": "^2.0.0", "tailwindcss": "^2.2.4", - "ts-jest": "26.3.0", + "ts-jest": "26.5.6", "ts-loader": "^7.0.5", "ts-node": "^8.10.2", "type-fest": "^1.0.2", @@ -372,14 +374,14 @@ "typedoc-plugin-markdown": "^3.9.0", "typeface-roboto": "^1.1.13", "typescript": "^4.3.2", - "typescript-plugin-css-modules": "^3.2.0", + "typescript-plugin-css-modules": "^3.4.0", "url-loader": "^4.1.0", - "webpack": "^4.44.2", + "webpack": "^4.46.0", "webpack-cli": "^3.3.11", "webpack-dev-server": "^3.11.0", "webpack-node-externals": "^1.7.2", "what-input": "^5.2.10", "xterm": "^4.12.0", - "xterm-addon-fit": "^0.4.0" + "xterm-addon-fit": "^0.5.0" } } diff --git a/src/common/__tests__/catalog-category-registry.test.ts b/src/common/__tests__/catalog-category-registry.test.ts index 11f7a88afd..90615d552c 100644 --- a/src/common/__tests__/catalog-category-registry.test.ts +++ b/src/common/__tests__/catalog-category-registry.test.ts @@ -56,7 +56,7 @@ class TestCatalogCategory2 extends CatalogCategory { } describe("CatalogCategoryRegistry", () => { - it("should remove only the category registered when running the disopser", () => { + it("should remove only the category registered when running the disposer", () => { const registry = new TestCatalogCategoryRegistry(); expect(registry.items.length).toBe(0); diff --git a/src/common/__tests__/event-emitter.test.ts b/src/common/__tests__/event-emitter.test.ts new file mode 100644 index 0000000000..b54df2af98 --- /dev/null +++ b/src/common/__tests__/event-emitter.test.ts @@ -0,0 +1,102 @@ +/** + * 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 { EventEmitter } from "../event-emitter"; + +describe("EventEmitter", () => { + it("should stop early if a listener returns false", () => { + let called = false; + const e = new EventEmitter<[]>(); + + e.addListener(() => false, {}); + e.addListener(() => { called = true; }, {}); + e.emit(); + + expect(called).toBe(false); + }); + + it("shouldn't stop early if a listener returns 0", () => { + let called = false; + const e = new EventEmitter<[]>(); + + e.addListener(() => 0 as any, {}); + e.addListener(() => { called = true; }, {}); + e.emit(); + + expect(called).toBe(true); + }); + + it("prepended listeners should be called before others", () => { + const callOrder: number[] = []; + const e = new EventEmitter<[]>(); + + e.addListener(() => { callOrder.push(1); }, {}); + e.addListener(() => { callOrder.push(2); }, {}); + e.addListener(() => { callOrder.push(3); }, { prepend: true }); + e.emit(); + + expect(callOrder).toStrictEqual([3, 1, 2]); + }); + + it("once listeners should be called only once", () => { + const callOrder: number[] = []; + const e = new EventEmitter<[]>(); + + e.addListener(() => { callOrder.push(1); }, {}); + e.addListener(() => { callOrder.push(2); }, {}); + e.addListener(() => { callOrder.push(3); }, { once: true }); + e.emit(); + e.emit(); + + expect(callOrder).toStrictEqual([1, 2, 3, 1, 2]); + }); + + it("removeListener should stop the listener from being called", () => { + const callOrder: number[] = []; + const e = new EventEmitter<[]>(); + const r = () => { callOrder.push(3); }; + + e.addListener(() => { callOrder.push(1); }, {}); + e.addListener(() => { callOrder.push(2); }, {}); + e.addListener(r); + + e.emit(); + e.removeListener(r); + e.emit(); + + expect(callOrder).toStrictEqual([1, 2, 3, 1, 2]); + }); + + it("removeAllListeners should stop the all listeners from being called", () => { + const callOrder: number[] = []; + const e = new EventEmitter<[]>(); + + e.addListener(() => { callOrder.push(1); }); + e.addListener(() => { callOrder.push(2); }); + e.addListener(() => { callOrder.push(3); }); + + e.emit(); + e.removeAllListeners(); + e.emit(); + + expect(callOrder).toStrictEqual([1, 2, 3]); + }); +}); diff --git a/src/common/__tests__/user-store.test.ts b/src/common/__tests__/user-store.test.ts index 6e5a6433ef..c5a15aec28 100644 --- a/src/common/__tests__/user-store.test.ts +++ b/src/common/__tests__/user-store.test.ts @@ -19,10 +19,6 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { Console } from "console"; - -console = new Console(process.stdout, process.stderr); - import mockFs from "mock-fs"; jest.mock("electron", () => { @@ -37,27 +33,27 @@ jest.mock("electron", () => { }); import { UserStore } from "../user-store"; +import { Console } from "console"; import { SemVer } from "semver"; import electron from "electron"; import { stdout, stderr } from "process"; import { beforeEachWrapped } from "../../../integration/helpers/utils"; import { ThemeStore } from "../../renderer/theme.store"; +import type { ClusterStoreModel } from "../cluster-store"; console = new Console(stdout, stderr); describe("user store tests", () => { describe("for an empty config", () => { beforeEachWrapped(() => { - UserStore.resetInstance(); mockFs({ tmp: { "config.json": "{}", "kube_config": "{}" } }); (UserStore.createInstance() as any).refreshNewContexts = jest.fn(() => Promise.resolve()); - - UserStore.getInstance(); }); afterEach(() => { mockFs.restore(); + UserStore.resetInstance(); }); it("allows setting and retrieving lastSeenAppVersion", () => { @@ -99,14 +95,31 @@ describe("user store tests", () => { describe("migrations", () => { beforeEachWrapped(() => { - UserStore.resetInstance(); mockFs({ "tmp": { "config.json": JSON.stringify({ user: { username: "foobar" }, preferences: { colorTheme: "light" }, lastSeenAppVersion: "1.2.3" - }) + }), + "lens-cluster-store.json": JSON.stringify({ + clusters: [ + { + id: "foobar", + kubeConfigPath: "tmp/extension_data/foo/bar", + }, + { + id: "barfoo", + kubeConfigPath: "some/other/path", + }, + ] + } as ClusterStoreModel), + "extension_data": {}, + }, + "some": { + "other": { + "path": "is file", + } } }); @@ -114,6 +127,7 @@ describe("user store tests", () => { }); afterEach(() => { + UserStore.resetInstance(); mockFs.restore(); }); @@ -122,5 +136,12 @@ describe("user store tests", () => { expect(us.lastSeenAppVersion).toBe("0.0.0"); }); + + it.only("skips clusters for adding to kube-sync with files under extension_data/", () => { + const us = UserStore.getInstance(); + + expect(us.syncKubeconfigEntries.has("tmp/extension_data/foo/bar")).toBe(false); + expect(us.syncKubeconfigEntries.has("some/other/path")).toBe(true); + }); }); }); diff --git a/src/common/catalog-entities/kubernetes-cluster.ts b/src/common/catalog-entities/kubernetes-cluster.ts index 3b79c3ab56..83a94547bb 100644 --- a/src/common/catalog-entities/kubernetes-cluster.ts +++ b/src/common/catalog-entities/kubernetes-cluster.ts @@ -55,15 +55,23 @@ export interface KubernetesClusterSpec extends CatalogEntitySpec { }; } +export interface KubernetesClusterMetadata extends CatalogEntityMetadata { + distro?: string; + kubeVersion?: string; +} + export type KubernetesClusterStatusPhase = "connected" | "connecting" | "disconnected" | "deleting"; export interface KubernetesClusterStatus extends CatalogEntityStatus { phase: KubernetesClusterStatusPhase; } -export class KubernetesCluster extends CatalogEntity { - public readonly apiVersion = "entity.k8slens.dev/v1alpha1"; - public readonly kind = "KubernetesCluster"; +export class KubernetesCluster extends CatalogEntity { + public static readonly apiVersion = "entity.k8slens.dev/v1alpha1"; + public static readonly kind = "KubernetesCluster"; + + public readonly apiVersion = KubernetesCluster.apiVersion; + public readonly kind = KubernetesCluster.kind; async connect(): Promise { if (app) { diff --git a/src/common/cluster-store.ts b/src/common/cluster-store.ts index fa5219ceb6..b6e198a213 100644 --- a/src/common/cluster-store.ts +++ b/src/common/cluster-store.ts @@ -101,6 +101,8 @@ export interface ClusterPreferences extends ClusterPrometheusPreferences { icon?: string; httpsProxy?: string; hiddenMetrics?: string[]; + nodeShellImage?: string; + imagePullSecret?: string; } export interface ClusterPrometheusPreferences { @@ -117,6 +119,8 @@ export interface ClusterPrometheusPreferences { const initialStates = "cluster:states"; +export const initialNodeShellImage = "docker.io/alpine:3.13"; + export class ClusterStore extends BaseStore { private static StateChannel = "cluster:state"; diff --git a/src/common/event-emitter.ts b/src/common/event-emitter.ts index 5ff785d431..4e1fb45151 100644 --- a/src/common/event-emitter.ts +++ b/src/common/event-emitter.ts @@ -29,35 +29,31 @@ interface Options { type Callback = (...data: D) => void | boolean; export class EventEmitter { - protected listeners = new Map, Options>(); + protected listeners: [Callback, Options][] = []; addListener(callback: Callback, options: Options = {}) { - if (options.prepend) { - const listeners = [...this.listeners]; + const fn = options.prepend ? "unshift" : "push"; - listeners.unshift([callback, options]); - this.listeners = new Map(listeners); - } - else { - this.listeners.set(callback, options); - } + this.listeners[fn]([callback, options]); } removeListener(callback: Callback) { - this.listeners.delete(callback); + this.listeners = this.listeners.filter(([cb]) => cb !== callback); } removeAllListeners() { - this.listeners.clear(); + this.listeners.length = 0; } emit(...data: D) { - [...this.listeners].every(([callback, options]) => { - if (options.once) { + for (const [callback, { once }] of this.listeners) { + if (once) { this.removeListener(callback); } - return callback(...data) !== false; - }); + if (callback(...data) === false) { + break; + } + } } } diff --git a/src/common/hotbar-store.ts b/src/common/hotbar-store.ts index 90694e9976..1634bab5cb 100644 --- a/src/common/hotbar-store.ts +++ b/src/common/hotbar-store.ts @@ -52,7 +52,7 @@ export interface HotbarStoreModel { activeHotbarId: string; } -export const defaultHotbarCells = 12; // Number is choosen to easy hit any item with keyboard +export const defaultHotbarCells = 12; // Number is chosen to easy hit any item with keyboard export class HotbarStore extends BaseStore { @observable hotbars: Hotbar[] = []; @@ -203,7 +203,7 @@ export class HotbarStore extends BaseStore { } /** - * Remvove all hotbar items that reference the `uid`. + * Remove all hotbar items that reference the `uid`. * @param uid The `EntityId` that each hotbar item refers to * @returns A function that will (in an action) undo the removing of the hotbar items. This function will not complete if the hotbar has changed. */ diff --git a/src/common/rbac.ts b/src/common/rbac.ts index 55b8ca414c..9d8242cd42 100644 --- a/src/common/rbac.ts +++ b/src/common/rbac.ts @@ -40,14 +40,14 @@ export interface KubeApiResourceData { export const apiResourceRecord: Record = { "clusterroles": { kind: "ClusterRole", group: "rbac.authorization.k8s.io" }, "clusterrolebindings": { kind: "ClusterRoleBinding", group: "rbac.authorization.k8s.io" }, - "configmaps": { kind: "ConfigMap" }, + "configmaps": { kind: "ConfigMap" }, //empty group means "core" "cronjobs": { kind: "CronJob", group: "batch" }, "customresourcedefinitions": { kind: "CustomResourceDefinition", group: "apiextensions.k8s.io" }, "daemonsets": { kind: "DaemonSet", group: "apps" }, "deployments": { kind: "Deployment", group: "apps" }, "endpoints": { kind: "Endpoint" }, "events": { kind: "Event" }, - "horizontalpodautoscalers": { kind: "HorizontalPodAutoscaler" }, + "horizontalpodautoscalers": { kind: "HorizontalPodAutoscaler", group: "autoscaling" }, "ingresses": { kind: "Ingress", group: "networking.k8s.io" }, "jobs": { kind: "Job", group: "batch" }, "namespaces": { kind: "Namespace" }, @@ -58,13 +58,13 @@ export const apiResourceRecord: Record = { "persistentvolumeclaims": { kind: "PersistentVolumeClaim" }, "pods": { kind: "Pod" }, "poddisruptionbudgets": { kind: "PodDisruptionBudget", group: "policy" }, - "podsecuritypolicies": { kind: "PodSecurityPolicy" }, + "podsecuritypolicies": { kind: "PodSecurityPolicy", group: "policy" }, "resourcequotas": { kind: "ResourceQuota" }, "replicasets": { kind: "ReplicaSet", group: "apps" }, "roles": { kind: "Role", group: "rbac.authorization.k8s.io" }, "rolebindings": { kind: "RoleBinding", group: "rbac.authorization.k8s.io" }, "secrets": { kind: "Secret" }, - "serviceaccounts": { kind: "ServiceAccount", group: "core" }, + "serviceaccounts": { kind: "ServiceAccount" }, "services": { kind: "Service" }, "statefulsets": { kind: "StatefulSet", group: "apps" }, "storageclasses": { kind: "StorageClass", group: "storage.k8s.io" }, diff --git a/src/common/sentry.ts b/src/common/sentry.ts new file mode 100644 index 0000000000..da0e749994 --- /dev/null +++ b/src/common/sentry.ts @@ -0,0 +1,77 @@ +/** + * 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 { CaptureConsole, Dedupe, Offline } from "@sentry/integrations"; +import * as Sentry from "@sentry/electron"; +import { sentryDsn, isProduction } from "./vars"; +import { UserStore } from "./user-store"; +import logger from "../main/logger"; + +/** + * "Translate" 'browser' to 'main' as Lens developer more familiar with the term 'main' + */ +function mapProcessName(processType: string) { + if (processType === "browser") { + return "main"; + } + + return processType; +} + +/** + * Initialize Sentry for the current process so to send errors for debugging. + */ +export function SentryInit() { + const processName = mapProcessName(process.type); + + Sentry.init({ + beforeSend: (event) => { + // default to false, in case instance of UserStore is not created (yet) + const allowErrorReporting = UserStore.getInstance(false)?.allowErrorReporting ?? false; + + if (allowErrorReporting) { + return event; + } + + logger.info(`🔒 [SENTRY-BEFORE-SEND-HOOK]: allowErrorReporting: ${allowErrorReporting}. Sentry event is caught but not sent to server.`); + logger.info("🔒 [SENTRY-BEFORE-SEND-HOOK]: === START OF SENTRY EVENT ==="); + logger.info(event); + logger.info("🔒 [SENTRY-BEFORE-SEND-HOOK]: === END OF SENTRY EVENT ==="); + + // if return null, the event won't be sent + // ref https://github.com/getsentry/sentry-javascript/issues/2039 + return null; + }, + dsn: sentryDsn, + integrations: [ + new CaptureConsole({ levels: ["error"] }), + new Dedupe(), + new Offline() + ], + initialScope: { + tags: { + + "process": processName + } + }, + environment: isProduction ? "production" : "development", + }); +} diff --git a/src/common/system-ca.ts b/src/common/system-ca.ts index a12e55cd02..7ed0120569 100644 --- a/src/common/system-ca.ts +++ b/src/common/system-ca.ts @@ -33,5 +33,9 @@ if (isMac) { } if (isWindows) { - winca.inject("+"); // see: https://github.com/ukoloff/win-ca#caveats + try { + winca.inject("+"); // see: https://github.com/ukoloff/win-ca#caveats + } catch (error) { + logger.error(`[CA]: failed to force load: ${error}`); + } } diff --git a/src/common/user-store/index.ts b/src/common/user-store/index.ts index dc00ab6737..a8dc6eb028 100644 --- a/src/common/user-store/index.ts +++ b/src/common/user-store/index.ts @@ -20,4 +20,4 @@ */ export * from "./user-store"; -export type { KubeconfigSyncEntry, KubeconfigSyncValue } from "./preferences-helpers"; +export type { KubeconfigSyncEntry, KubeconfigSyncValue, UserPreferencesModel } from "./preferences-helpers"; diff --git a/src/common/user-store/preferences-helpers.ts b/src/common/user-store/preferences-helpers.ts index b35f7ddc51..4363511120 100644 --- a/src/common/user-store/preferences-helpers.ts +++ b/src/common/user-store/preferences-helpers.ts @@ -106,6 +106,19 @@ const allowTelemetry: PreferenceDescription = { }, }; +const allowErrorReporting: PreferenceDescription = { + fromStore(val) { + return val ?? true; + }, + toStore(val) { + if (val === true) { + return undefined; + } + + return val; + }, +}; + const downloadMirror: PreferenceDescription = { fromStore(val) { return val ?? "default"; @@ -180,9 +193,9 @@ const hiddenTableColumns: PreferenceDescription<[string, string[]][], Map /* implements UserStore @observable seenContexts = observable.set(); @observable newContexts = observable.set(); @observable allowTelemetry: boolean; + @observable allowErrorReporting: boolean; @observable allowUntrustedCAs: boolean; @observable colorTheme: string; @observable localeTimezone: string; @@ -118,13 +120,13 @@ export class UserStore extends BaseStore /* implements UserStore */ isTableColumnHidden(tableId: string, ...columnIds: string[]): boolean { if (columnIds.length === 0) { - return true; + return false; } const config = this.hiddenTableColumns.get(tableId); if (!config) { - return true; + return false; } return columnIds.some(columnId => config.has(columnId)); @@ -155,8 +157,8 @@ export class UserStore extends BaseStore /* implements UserStore } @action - protected fromStore(data: Partial = {}) { - const { lastSeenAppVersion, preferences } = data; + protected fromStore({ lastSeenAppVersion, preferences }: Partial = {}) { + logger.debug("UserStore.fromStore()", { lastSeenAppVersion, preferences }); if (lastSeenAppVersion) { this.lastSeenAppVersion = lastSeenAppVersion; @@ -168,6 +170,7 @@ export class UserStore extends BaseStore /* implements UserStore this.localeTimezone = DESCRIPTORS.localeTimezone.fromStore(preferences?.localeTimezone); this.allowUntrustedCAs = DESCRIPTORS.allowUntrustedCAs.fromStore(preferences?.allowUntrustedCAs); this.allowTelemetry = DESCRIPTORS.allowTelemetry.fromStore(preferences?.allowTelemetry); + this.allowErrorReporting = DESCRIPTORS.allowErrorReporting.fromStore(preferences?.allowErrorReporting); this.downloadMirror = DESCRIPTORS.downloadMirror.fromStore(preferences?.downloadMirror); this.downloadKubectlBinaries = DESCRIPTORS.downloadKubectlBinaries.fromStore(preferences?.downloadKubectlBinaries); this.downloadBinariesPath = DESCRIPTORS.downloadBinariesPath.fromStore(preferences?.downloadBinariesPath); @@ -187,6 +190,7 @@ export class UserStore extends BaseStore /* implements UserStore localeTimezone: DESCRIPTORS.localeTimezone.toStore(this.localeTimezone), allowUntrustedCAs: DESCRIPTORS.allowUntrustedCAs.toStore(this.allowUntrustedCAs), allowTelemetry: DESCRIPTORS.allowTelemetry.toStore(this.allowTelemetry), + allowErrorReporting: DESCRIPTORS.allowErrorReporting.toStore(this.allowErrorReporting), downloadMirror: DESCRIPTORS.downloadMirror.toStore(this.downloadMirror), downloadKubectlBinaries: DESCRIPTORS.downloadKubectlBinaries.toStore(this.downloadKubectlBinaries), downloadBinariesPath: DESCRIPTORS.downloadBinariesPath.toStore(this.downloadBinariesPath), diff --git a/src/common/utils/__tests__/paths.test.ts b/src/common/utils/__tests__/paths.test.ts new file mode 100644 index 0000000000..0eec289fb9 --- /dev/null +++ b/src/common/utils/__tests__/paths.test.ts @@ -0,0 +1,130 @@ +/** + * 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 { describeIf } from "../../../../integration/helpers/utils"; +import { isWindows } from "../../vars"; +import { isLogicalChildPath } from "../paths"; + +describe("isLogicalChildPath", () => { + describeIf(isWindows)("windows tests", () => { + it.each([ + { + parentPath: "C:\\Foo", + testPath: "C:\\Foo\\Bar", + expected: true, + }, + { + parentPath: "C:\\Foo", + testPath: "C:\\Bar", + expected: false, + }, + { + parentPath: "C:\\Foo", + testPath: "C:/Bar", + expected: false, + }, + { + parentPath: "C:\\Foo", + testPath: "C:/Foo/Bar", + expected: true, + }, + { + parentPath: "C:\\Foo", + testPath: "D:\\Foo\\Bar", + expected: false, + }, + ])("test %#", (testData) => { + expect(isLogicalChildPath(testData.parentPath, testData.testPath)).toBe(testData.expected); + }); + }); + + describeIf(!isWindows)("posix tests", () => { + it.each([ + { + parentPath: "/foo", + testPath: "/foo", + expected: false, + }, + { + parentPath: "/foo", + testPath: "/bar", + expected: false, + }, + { + parentPath: "/foo", + testPath: "/foobar", + expected: false, + }, + { + parentPath: "/foo", + testPath: "/foo/bar", + expected: true, + }, + { + parentPath: "/foo", + testPath: "/foo/../bar", + expected: false, + }, + { + parentPath: "/foo", + testPath: "/foo/./bar", + expected: true, + }, + { + parentPath: "/foo", + testPath: "/foo/.bar", + expected: true, + }, + { + parentPath: "/foo", + testPath: "/foo/..bar", + expected: true, + }, + { + parentPath: "/foo", + testPath: "/foo/...bar", + expected: true, + }, + { + parentPath: "/foo", + testPath: "/foo/..\\.bar", + expected: true, + }, + { + parentPath: "/bar/../foo", + testPath: "/foo/bar", + expected: true, + }, + { + parentPath: "/foo", + testPath: "/foo/\\bar", + expected: true, + }, + { + parentPath: "/foo", + testPath: "./bar", + expected: false, + }, + ])("test %#", (testData) => { + expect(isLogicalChildPath(testData.parentPath, testData.testPath)).toBe(testData.expected); + }); + }); +}); diff --git a/src/common/utils/extended-map.ts b/src/common/utils/extended-map.ts index c759fa3459..bd29cbbfe0 100644 --- a/src/common/utils/extended-map.ts +++ b/src/common/utils/extended-map.ts @@ -27,7 +27,7 @@ export class ExtendedMap extends Map { } /** - * Get the value behind `key`. If it was not pressent, first insert the value returned by `getVal` + * Get the value behind `key`. If it was not present, first insert the value returned by `getVal` * @param key The key to insert into the map with * @param getVal A function that returns a new instance of `V`. * @returns The value in the map diff --git a/src/common/utils/index.ts b/src/common/utils/index.ts index d652933341..1644c07f36 100644 --- a/src/common/utils/index.ts +++ b/src/common/utils/index.ts @@ -45,6 +45,7 @@ export * from "./openExternal"; export * from "./paths"; export * from "./reject-promise"; export * from "./singleton"; +export * from "./sort-compare"; export * from "./splitArray"; export * from "./tar"; export * from "./toggle-set"; diff --git a/src/common/utils/paths.ts b/src/common/utils/paths.ts index 391e1392f2..68d8f61ffa 100644 --- a/src/common/utils/paths.ts +++ b/src/common/utils/paths.ts @@ -33,3 +33,35 @@ function resolveTilde(filePath: string) { export function resolvePath(filePath: string): string { return path.resolve(resolveTilde(filePath)); } + +/** + * Checks if `testPath` represents a potential filesystem entry that would be + * logically "within" the `parentPath` directory. + * + * This function will return `true` in the above case, and `false` otherwise. + * It will return `false` if the two paths are the same (after resolving them). + * + * The function makes no FS calls and is platform dependant. Meaning that the + * results are only guaranteed to be correct for the platform you are running + * on. + * @param parentPath The known path of a directory + * @param testPath The path that is to be tested + */ +export function isLogicalChildPath(parentPath: string, testPath: string): boolean { + parentPath = path.resolve(parentPath); + testPath = path.resolve(testPath); + + if (parentPath === testPath) { + return false; + } + + while (testPath.length >= parentPath.length) { + if (testPath === parentPath) { + return true; + } + + testPath = path.dirname(testPath); + } + + return false; +} diff --git a/src/common/utils/sort-compare.ts b/src/common/utils/sort-compare.ts new file mode 100644 index 0000000000..cd4e1b128a --- /dev/null +++ b/src/common/utils/sort-compare.ts @@ -0,0 +1,55 @@ +/** + * 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 semver, { SemVer } from "semver"; + +export function sortCompare(left: T, right: T): -1 | 0 | 1 { + if (left < right) { + return -1; + } + + if (left === right) { + return 0; + } + + return 1; +} + +interface ChartVersion { + version: string; + __version?: SemVer; +} + +export function sortCompareChartVersions(left: ChartVersion, right: ChartVersion): -1 | 0 | 1 { + if (left.__version && right.__version) { + return semver.compare(right.__version, left.__version); + } + + if (!left.__version && right.__version) { + return 1; + } + + if (left.__version && !right.__version) { + return -1; + } + + return sortCompare(left.version, right.version); +} diff --git a/src/common/vars.ts b/src/common/vars.ts index a49c8da910..0dea82cab6 100644 --- a/src/common/vars.ts +++ b/src/common/vars.ts @@ -69,4 +69,6 @@ export const slackUrl = "https://join.slack.com/t/k8slens/shared_invite/enQtOTc5 export const supportUrl = "https://docs.k8slens.dev/latest/support/" as string; export const appSemVer = new SemVer(packageInfo.version); -export const docsUrl = `https://docs.k8slens.dev/main/` as string; +export const docsUrl = "https://docs.k8slens.dev/main/" as string; + +export const sentryDsn = packageInfo.config?.sentryDsn ?? ""; diff --git a/src/extensions/__tests__/extension-compatibility.test.ts b/src/extensions/__tests__/extension-compatibility.test.ts new file mode 100644 index 0000000000..d8e789a9d9 --- /dev/null +++ b/src/extensions/__tests__/extension-compatibility.test.ts @@ -0,0 +1,138 @@ +/** + * 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 { isCompatibleExtension } from "../extension-compatibility"; +import { Console } from "console"; +import { stdout, stderr } from "process"; +import type { LensExtensionManifest } from "../lens-extension"; +import { appSemVer } from "../../common/vars"; + +console = new Console(stdout, stderr); + +describe("extension compatibility", () => { + describe("appSemVer with no prerelease tag", () => { + beforeAll(() => { + appSemVer.major = 5; + appSemVer.minor = 0; + appSemVer.patch = 3; + appSemVer.prerelease = []; + }); + + it("has no extension comparator", () => { + const manifest = { name: "extensionName", version: "0.0.1"}; + + expect(isCompatibleExtension(manifest,)).toBe(false); + }); + + it.each([ + { + comparator: "", + expected: false, + }, + { + comparator: "bad comparator", + expected: false, + }, + { + comparator: "^4.0.0", + expected: false, + }, + { + comparator: "^5.0.0", + expected: true, + }, + { + comparator: "^6.0.0", + expected: false, + }, + { + comparator: "^4.0.0-alpha.1", + expected: false, + }, + { + comparator: "^5.0.0-alpha.1", + expected: true, + }, + { + comparator: "^6.0.0-alpha.1", + expected: false, + }, + ])("extension comparator test: %p", ({ comparator, expected }) => { + const manifest: LensExtensionManifest = { name: "extensionName", version: "0.0.1", engines: { lens: comparator}}; + + expect(isCompatibleExtension(manifest,)).toBe(expected); + }); + }); + + describe("appSemVer with prerelease tag", () => { + beforeAll(() => { + appSemVer.major = 5; + appSemVer.minor = 0; + appSemVer.patch = 3; + appSemVer.prerelease = ["beta", 3]; + }); + + it("has no extension comparator", () => { + const manifest = { name: "extensionName", version: "0.0.1"}; + + expect(isCompatibleExtension(manifest,)).toBe(false); + }); + + it.each([ + { + comparator: "", + expected: false, + }, + { + comparator: "bad comparator", + expected: false, + }, + { + comparator: "^4.0.0", + expected: false, + }, + { + comparator: "^5.0.0", + expected: true, + }, + { + comparator: "^6.0.0", + expected: false, + }, + { + comparator: "^4.0.0-alpha.1", + expected: false, + }, + { + comparator: "^5.0.0-alpha.1", + expected: true, + }, + { + comparator: "^6.0.0-alpha.1", + expected: false, + }, + ])("extension comparator test: %p", ({ comparator, expected }) => { + const manifest: LensExtensionManifest = { name: "extensionName", version: "0.0.1", engines: { lens: comparator}}; + + expect(isCompatibleExtension(manifest,)).toBe(expected); + }); + }); +}); diff --git a/src/extensions/__tests__/extension-loader.test.ts b/src/extensions/__tests__/extension-loader.test.ts index 5e0844d02d..e35afe5a1b 100644 --- a/src/extensions/__tests__/extension-loader.test.ts +++ b/src/extensions/__tests__/extension-loader.test.ts @@ -84,7 +84,7 @@ jest.mock( (channel: string, listener: (event: any, ...args: any[]) => void) => { if (channel === "extensions:main") { // First initialize with extensions 1 and 2 - // and then broadcast event to remove extensioin 2 and add extension number 3 + // and then broadcast event to remove extension 2 and add extension number 3 setTimeout(() => { listener({}, [ [ diff --git a/src/extensions/extension-compatibility.ts b/src/extensions/extension-compatibility.ts new file mode 100644 index 0000000000..3d958072dc --- /dev/null +++ b/src/extensions/extension-compatibility.ts @@ -0,0 +1,37 @@ +/** + * 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 semver from "semver"; +import { appSemVer, isProduction } from "../common/vars"; +import type { LensExtensionManifest } from "./lens-extension"; + +export function isCompatibleExtension(manifest: LensExtensionManifest): boolean { + if (manifest.engines?.lens) { + /* include Lens's prerelease tag in the matching so the extension's compatibility is not limited by it */ + return semver.satisfies(appSemVer, manifest.engines.lens, { includePrerelease: true }); + } + + return false; +} + +export function isCompatibleBundledExtension(manifest: LensExtensionManifest): boolean { + return !isProduction || manifest.version === appSemVer.raw; +} diff --git a/src/extensions/extension-discovery.ts b/src/extensions/extension-discovery.ts index 85231cc88e..077f29d694 100644 --- a/src/extensions/extension-discovery.ts +++ b/src/extensions/extension-discovery.ts @@ -34,9 +34,8 @@ import { extensionInstaller } from "./extension-installer"; import { ExtensionsStore } from "./extensions-store"; import { ExtensionLoader } from "./extension-loader"; import type { LensExtensionId, LensExtensionManifest } from "./lens-extension"; -import semver from "semver"; -import { appSemVer } from "../common/vars"; import { isProduction } from "../common/vars"; +import { isCompatibleBundledExtension, isCompatibleExtension } from "./extension-compatibility"; export interface InstalledExtension { id: LensExtensionId; @@ -362,17 +361,7 @@ export class ExtensionDiscovery extends Singleton { const extensionDir = path.dirname(manifestPath); const npmPackage = path.join(extensionDir, `${manifest.name}-${manifest.version}.tgz`); const absolutePath = (isProduction && await fse.pathExists(npmPackage)) ? npmPackage : extensionDir; - let isCompatible = isBundled; - - if (manifest.engines?.lens) { - const appSemVerLatestImplied = appSemVer; - - if (appSemVerLatestImplied.prerelease?.[0] === "latest") { - /* remove the "latest" prerelease tag so as not to require the extension to specify it */ - appSemVerLatestImplied.prerelease = []; - } - isCompatible = semver.satisfies(appSemVerLatestImplied, manifest.engines.lens); - } + const isCompatible = (isBundled && isCompatibleBundledExtension(manifest)) || isCompatibleExtension(manifest); return { id, diff --git a/src/extensions/ipc/ipc-main.ts b/src/extensions/ipc/ipc-main.ts index eec89e9966..5884a3989e 100644 --- a/src/extensions/ipc/ipc-main.ts +++ b/src/extensions/ipc/ipc-main.ts @@ -36,7 +36,7 @@ export abstract class IpcMain extends IpcRegistrar { * Listen for broadcasts within your extension * @param channel The channel to listen for broadcasts on * @param listener The function that will be called with the arguments of the broadcast - * @returns An optional disopser, Lens will cleanup when the extension is disabled or uninstalled even if this is not called + * @returns An optional disposer, Lens will cleanup when the extension is disabled or uninstalled even if this is not called */ listen(channel: string, listener: (event: Electron.IpcRendererEvent, ...args: any[]) => any): Disposer { const prefixedChannel = `extensions@${this[IpcPrefix]}:${channel}`; diff --git a/src/extensions/lens-extension.ts b/src/extensions/lens-extension.ts index ac77a3b229..5d562953ca 100644 --- a/src/extensions/lens-extension.ts +++ b/src/extensions/lens-extension.ts @@ -74,7 +74,7 @@ export class LensExtension { * getExtensionFileFolder returns the path to an already created folder. This * folder is for the sole use of this extension. * - * Note: there is no security done on this folder, only obfiscation of the + * Note: there is no security done on this folder, only obfuscation of the * folder name. */ async getExtensionFileFolder(): Promise { diff --git a/src/extensions/lens-renderer-extension.ts b/src/extensions/lens-renderer-extension.ts index a3026d99a5..dd5670fd67 100644 --- a/src/extensions/lens-renderer-extension.ts +++ b/src/extensions/lens-renderer-extension.ts @@ -23,6 +23,7 @@ import type * as registries from "./registries"; import type { Cluster } from "../main/cluster"; import { LensExtension } from "./lens-extension"; import { getExtensionPageUrl } from "./registries/page-registry"; +import type { CatalogEntity } from "../common/catalog"; export class LensRendererExtension extends LensExtension { globalPages: registries.PageRegistration[] = []; @@ -37,7 +38,7 @@ export class LensRendererExtension extends LensExtension { kubeWorkloadsOverviewItems: registries.WorkloadsOverviewDetailRegistration[] = []; commands: registries.CommandRegistration[] = []; welcomeMenus: registries.WelcomeMenuRegistration[] = []; - catalogEntityDetailItems: registries.CatalogEntityDetailRegistration[] = []; + catalogEntityDetailItems: registries.CatalogEntityDetailRegistration[] = []; topBarItems: registries.TopBarRegistration[] = []; async navigate

(pageId?: string, params?: P) { diff --git a/src/extensions/registries/__tests__/page-registry.test.ts b/src/extensions/registries/__tests__/page-registry.test.ts index ed9bf983cd..9a64d95b6b 100644 --- a/src/extensions/registries/__tests__/page-registry.test.ts +++ b/src/extensions/registries/__tests__/page-registry.test.ts @@ -22,14 +22,24 @@ import { ClusterPageRegistry, getExtensionPageUrl, GlobalPageRegistry, PageParams } from "../page-registry"; import { LensExtension } from "../../lens-extension"; import React from "react"; +import fse from "fs-extra"; import { Console } from "console"; import { stdout, stderr } from "process"; +import { ThemeStore } from "../../../renderer/theme.store"; +import { TerminalStore } from "../../renderer-api/components"; +import { UserStore } from "../../../common/user-store"; + +jest.mock("electron", () => ({ + app: { + getPath: () => "tmp", + }, +})); console = new Console(stdout, stderr); let ext: LensExtension = null; -describe("getPageUrl", () => { +describe("page registry tests", () => { beforeEach(async () => { ext = new LensExtension({ manifest: { @@ -43,6 +53,9 @@ describe("getPageUrl", () => { isEnabled: true, isCompatible: true }); + UserStore.createInstance(); + ThemeStore.createInstance(); + TerminalStore.createInstance(); ClusterPageRegistry.createInstance(); GlobalPageRegistry.createInstance().add({ id: "page-with-params", @@ -54,70 +67,6 @@ describe("getPageUrl", () => { test2: "" // no default value, just declaration }, }, ext); - }); - - afterEach(() => { - GlobalPageRegistry.resetInstance(); - ClusterPageRegistry.resetInstance(); - }); - - it("returns a page url for extension", () => { - expect(getExtensionPageUrl({ extensionId: ext.name })).toBe("/extension/foo-bar"); - }); - - it("allows to pass base url as parameter", () => { - expect(getExtensionPageUrl({ extensionId: ext.name, pageId: "/test" })).toBe("/extension/foo-bar/test"); - }); - - it("removes @ and replace `/` to `--`", () => { - expect(getExtensionPageUrl({ extensionId: "@foo/bar" })).toBe("/extension/foo--bar"); - }); - - it("adds / prefix", () => { - expect(getExtensionPageUrl({ extensionId: ext.name, pageId: "test" })).toBe("/extension/foo-bar/test"); - }); - - it("normalize possible multi-slashes in page.id", () => { - expect(getExtensionPageUrl({ extensionId: ext.name, pageId: "//test/" })).toBe("/extension/foo-bar/test"); - }); - - it("gets page url with custom params", () => { - const params: PageParams = { test1: "one", test2: "2" }; - const searchParams = new URLSearchParams(params); - const pageUrl = getExtensionPageUrl({ - extensionId: ext.name, - pageId: "page-with-params", - params, - }); - - expect(pageUrl).toBe(`/extension/foo-bar/page-with-params?${searchParams}`); - }); - - it("gets page url with default custom params", () => { - const defaultPageUrl = getExtensionPageUrl({ - extensionId: ext.name, - pageId: "page-with-params", - }); - - expect(defaultPageUrl).toBe(`/extension/foo-bar/page-with-params?test1=test1-default`); - }); -}); - -describe("globalPageRegistry", () => { - beforeEach(async () => { - ext = new LensExtension({ - manifest: { - name: "@acme/foo-bar", - version: "0.1.1" - }, - id: "/this/is/fake/package.json", - absolutePath: "/absolute/fake/", - manifestPath: "/this/is/fake/package.json", - isBundled: false, - isEnabled: true, - isCompatible: true - }); - ClusterPageRegistry.createInstance(); GlobalPageRegistry.createInstance().add([ { id: "test-page", @@ -142,33 +91,82 @@ describe("globalPageRegistry", () => { afterEach(() => { GlobalPageRegistry.resetInstance(); ClusterPageRegistry.resetInstance(); + TerminalStore.resetInstance(); + ThemeStore.resetInstance(); + UserStore.resetInstance(); + fse.remove("tmp"); }); - describe("getByPageTarget", () => { - it("matching to first registered page without id", () => { - const page = GlobalPageRegistry.getInstance().getByPageTarget({ extensionId: ext.name }); - - expect(page.id).toEqual(undefined); - expect(page.extensionId).toEqual(ext.name); - expect(page.url).toEqual(getExtensionPageUrl({ extensionId: ext.name })); + describe("getPageUrl", () => { + it("returns a page url for extension", () => { + expect(getExtensionPageUrl({ extensionId: ext.name })).toBe("/extension/foo-bar"); }); - it("returns matching page", () => { - const page = GlobalPageRegistry.getInstance().getByPageTarget({ - pageId: "test-page", - extensionId: ext.name - }); - - expect(page.id).toEqual("test-page"); + it("allows to pass base url as parameter", () => { + expect(getExtensionPageUrl({ extensionId: ext.name, pageId: "/test" })).toBe("/extension/foo-bar/test"); }); - it("returns null if target not found", () => { - const page = GlobalPageRegistry.getInstance().getByPageTarget({ - pageId: "wrong-page", - extensionId: ext.name + it("removes @ and replace `/` to `--`", () => { + expect(getExtensionPageUrl({ extensionId: "@foo/bar" })).toBe("/extension/foo--bar"); + }); + + it("adds / prefix", () => { + expect(getExtensionPageUrl({ extensionId: ext.name, pageId: "test" })).toBe("/extension/foo-bar/test"); + }); + + it("normalize possible multi-slashes in page.id", () => { + expect(getExtensionPageUrl({ extensionId: ext.name, pageId: "//test/" })).toBe("/extension/foo-bar/test"); + }); + + it("gets page url with custom params", () => { + const params: PageParams = { test1: "one", test2: "2" }; + const searchParams = new URLSearchParams(params); + const pageUrl = getExtensionPageUrl({ + extensionId: ext.name, + pageId: "page-with-params", + params, }); - expect(page).toBeNull(); + expect(pageUrl).toBe(`/extension/foo-bar/page-with-params?${searchParams}`); + }); + + it("gets page url with default custom params", () => { + const defaultPageUrl = getExtensionPageUrl({ + extensionId: ext.name, + pageId: "page-with-params", + }); + + expect(defaultPageUrl).toBe(`/extension/foo-bar/page-with-params?test1=test1-default`); + }); + }); + + describe("globalPageRegistry", () => { + describe("getByPageTarget", () => { + it("matching to first registered page without id", () => { + const page = GlobalPageRegistry.getInstance().getByPageTarget({ extensionId: ext.name }); + + expect(page.id).toEqual(undefined); + expect(page.extensionId).toEqual(ext.name); + expect(page.url).toEqual(getExtensionPageUrl({ extensionId: ext.name })); + }); + + it("returns matching page", () => { + const page = GlobalPageRegistry.getInstance().getByPageTarget({ + pageId: "test-page", + extensionId: ext.name + }); + + expect(page.id).toEqual("test-page"); + }); + + it("returns null if target not found", () => { + const page = GlobalPageRegistry.getInstance().getByPageTarget({ + pageId: "wrong-page", + extensionId: ext.name + }); + + expect(page).toBeNull(); + }); }); }); }); diff --git a/src/extensions/registries/catalog-entity-detail-registry.ts b/src/extensions/registries/catalog-entity-detail-registry.ts index b4b9fb0dd2..4ee9c4d667 100644 --- a/src/extensions/registries/catalog-entity-detail-registry.ts +++ b/src/extensions/registries/catalog-entity-detail-registry.ts @@ -20,20 +20,25 @@ */ import type React from "react"; +import type { CatalogEntity } from "../common-api/catalog"; import { BaseRegistry } from "./base-registry"; -export interface CatalogEntityDetailComponents { - Details: React.ComponentType; +export interface CatalogEntityDetailsProps { + entity: T; } -export interface CatalogEntityDetailRegistration { +export interface CatalogEntityDetailComponents { + Details: React.ComponentType>; +} + +export interface CatalogEntityDetailRegistration { kind: string; apiVersions: string[]; - components: CatalogEntityDetailComponents; + components: CatalogEntityDetailComponents; priority?: number; } -export class CatalogEntityDetailRegistry extends BaseRegistry { +export class CatalogEntityDetailRegistry extends BaseRegistry> { getItemsForKind(kind: string, apiVersion: string) { const items = this.getItems().filter((item) => { return item.kind === kind && item.apiVersions.includes(apiVersion); diff --git a/src/extensions/renderer-api/components.ts b/src/extensions/renderer-api/components.ts index f19ef18f7a..cb4cfd0377 100644 --- a/src/extensions/renderer-api/components.ts +++ b/src/extensions/renderer-api/components.ts @@ -67,5 +67,5 @@ export * from "../../renderer/components/+events/kube-event-details"; // specific exports export * from "../../renderer/components/status-brick"; -export { terminalStore, createTerminalTab } from "../../renderer/components/dock/terminal.store"; +export { terminalStore, createTerminalTab, TerminalStore } from "../../renderer/components/dock/terminal.store"; export { logTabStore } from "../../renderer/components/dock/log-tab.store"; diff --git a/src/main/__test__/context-handler.test.ts b/src/main/__test__/context-handler.test.ts index 0ba3a2d028..0652ed5733 100644 --- a/src/main/__test__/context-handler.test.ts +++ b/src/main/__test__/context-handler.test.ts @@ -207,7 +207,7 @@ describe("ContextHandler", () => { expect(service.id === "id_0"); }); - it("shouldn't pick the second provider of 2 succcess(es) after 1 failure(s)", async () => { + it("shouldn't pick the second provider of 2 success(es) after 1 failure(s)", async () => { const reg = PrometheusProviderRegistry.getInstance(); let count = 0; diff --git a/src/main/catalog-sources/kubeconfig-sync.ts b/src/main/catalog-sources/kubeconfig-sync.ts index 6a6f4120f9..e60d9400de 100644 --- a/src/main/catalog-sources/kubeconfig-sync.ts +++ b/src/main/catalog-sources/kubeconfig-sync.ts @@ -264,6 +264,12 @@ async function watchFileChanges(filePath: string): Promise<[IComputedValue>(); const derivedSource = computed(() => Array.from(iter.flatMap(rootSource.values(), from => iter.map(from.values(), child => child[1])))); diff --git a/src/main/catalog/catalog-entity-registry.ts b/src/main/catalog/catalog-entity-registry.ts index 4cecf4f49b..f2188132eb 100644 --- a/src/main/catalog/catalog-entity-registry.ts +++ b/src/main/catalog/catalog-entity-registry.ts @@ -20,7 +20,7 @@ */ import { action, computed, IComputedValue, IObservableArray, makeObservable, observable } from "mobx"; -import { CatalogCategoryRegistry, catalogCategoryRegistry, CatalogEntity } from "../../common/catalog"; +import { CatalogCategoryRegistry, catalogCategoryRegistry, CatalogEntity, CatalogEntityKindData } from "../../common/catalog"; import { iter } from "../../common/utils"; export class CatalogEntityRegistry { @@ -62,6 +62,10 @@ export class CatalogEntityRegistry { return items as T[]; } + + getItemsByEntityClass({ apiVersion, kind }: CatalogEntityKindData): T[] { + return this.getItemsForApiKind(apiVersion, kind); + } } export const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry); diff --git a/src/main/cluster-manager.ts b/src/main/cluster-manager.ts index 42d2dd1d19..628bcb22f6 100644 --- a/src/main/cluster-manager.ts +++ b/src/main/cluster-manager.ts @@ -23,7 +23,7 @@ import "../common/cluster-ipc"; import type http from "http"; import { action, autorun, makeObservable, observable, observe, reaction, toJS } from "mobx"; import { ClusterId, ClusterStore, getClusterIdFromHost } from "../common/cluster-store"; -import type { Cluster } from "./cluster"; +import { Cluster } from "./cluster"; import logger from "./logger"; import { apiKubePrefix } from "../common/vars"; import { Singleton } from "../common/utils"; @@ -32,6 +32,8 @@ import { KubernetesCluster, KubernetesClusterPrometheusMetrics, KubernetesCluste import { ipcMainOn } from "../common/ipc"; import { once } from "lodash"; +const logPrefix = "[CLUSTER-MANAGER]:"; + export class ClusterManager extends Singleton { private store = ClusterStore.getInstance(); deleting = observable.set(); @@ -73,7 +75,7 @@ export class ClusterManager extends Singleton { if (removedClusters.length > 0) { const meta = removedClusters.map(cluster => cluster.getMeta()); - logger.info(`[CLUSTER-MANAGER]: removing clusters`, meta); + logger.info(`${logPrefix} removing clusters`, meta); removedClusters.forEach(cluster => cluster.disconnect()); this.store.removedClusters.clear(); } @@ -103,13 +105,13 @@ export class ClusterManager extends Singleton { this.updateEntityStatus(entity, cluster); - entity.metadata.labels = Object.assign({}, cluster.labels, entity.metadata.labels); - entity.metadata.labels.distro = cluster.distribution; - - if (cluster.preferences?.clusterName) { - entity.metadata.name = cluster.preferences.clusterName; - } - + entity.metadata.labels = { + ...entity.metadata.labels, + ...cluster.labels, + }; + entity.metadata.distro = cluster.distribution; + entity.metadata.kubeVersion = cluster.version; + entity.metadata.name = cluster.name; entity.spec.metrics ||= { source: "local" }; if (entity.spec.metrics.source === "local") { @@ -161,14 +163,22 @@ export class ClusterManager extends Singleton { const cluster = this.store.getById(entity.metadata.uid); if (!cluster) { - this.store.addCluster({ - id: entity.metadata.uid, - preferences: { - clusterName: entity.metadata.name - }, - kubeConfigPath: entity.spec.kubeconfigPath, - contextName: entity.spec.kubeconfigContext - }); + try { + this.store.addCluster({ + id: entity.metadata.uid, + preferences: { + clusterName: entity.metadata.name + }, + kubeConfigPath: entity.spec.kubeconfigPath, + contextName: entity.spec.kubeconfigContext + }); + } catch (error) { + if (error.code === "ENOENT" && error.path === entity.spec.kubeconfigPath) { + logger.warn(`${logPrefix} kubeconfig file disappeared`, { path: entity.spec.kubeconfigPath }); + } else { + logger.error(`${logPrefix} failed to add cluster: ${error}`); + } + } } else { cluster.kubeConfigPath = entity.spec.kubeconfigPath; cluster.contextName = entity.spec.kubeconfigContext; @@ -179,7 +189,7 @@ export class ClusterManager extends Singleton { } protected onNetworkOffline = () => { - logger.info("[CLUSTER-MANAGER]: network is offline"); + logger.info(`${logPrefix} network is offline`); this.store.clustersList.forEach((cluster) => { if (!cluster.disconnected) { cluster.online = false; @@ -190,7 +200,7 @@ export class ClusterManager extends Singleton { }; protected onNetworkOnline = () => { - logger.info("[CLUSTER-MANAGER]: network is online"); + logger.info(`${logPrefix} network is online`); this.store.clustersList.forEach((cluster) => { if (!cluster.disconnected) { cluster.refreshConnectionStatus().catch((e) => e); @@ -236,8 +246,10 @@ export function catalogEntityFromCluster(cluster: Cluster) { name: cluster.name, source: "local", labels: { - distro: cluster.distribution, - } + ...cluster.labels, + }, + distro: cluster.distribution, + kubeVersion: cluster.version, }, spec: { kubeconfigPath: cluster.kubeConfigPath, diff --git a/src/main/cluster.ts b/src/main/cluster.ts index 1266a9c00a..58dbbf944f 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -34,6 +34,7 @@ import { VersionDetector } from "./cluster-detectors/version-detector"; import { detectorRegistry } from "./cluster-detectors/detector-registry"; import plimit from "p-limit"; import { toJS } from "../common/utils"; +import { initialNodeShellImage } from "../common/cluster-store"; export enum ClusterStatus { AccessGranted = 2, @@ -234,8 +235,18 @@ export class Cluster implements ClusterModel, ClusterState { return this.preferences.clusterName || this.contextName; } + /** + * The detected kubernetes distribution + */ @computed get distribution(): string { - return this.metadata.distribution?.toString() || "unknown"; + return this.metadata[ClusterMetadataKey.DISTRIBUTION]?.toString() || "unknown"; + } + + /** + * The detected kubernetes version + */ + @computed get version(): string { + return this.metadata[ClusterMetadataKey.VERSION]?.toString() || "unknown"; } /** @@ -250,13 +261,6 @@ export class Cluster implements ClusterModel, ClusterState { return toJS({ prometheus, prometheusProvider }); } - /** - * Kubernetes version - */ - get version(): string { - return String(this.metadata?.version ?? ""); - } - constructor(model: ClusterModel) { makeObservable(this); this.id = model.id; @@ -745,4 +749,12 @@ export class Cluster implements ClusterModel, ClusterState { isMetricHidden(resource: ClusterMetricsResourceType): boolean { return Boolean(this.preferences.hiddenMetrics?.includes(resource)); } + + get nodeShellImage(): string { + return this.preferences.nodeShellImage || initialNodeShellImage; + } + + get imagePullSecret(): string { + return this.preferences.imagePullSecret || ""; + } } diff --git a/src/main/helm/__mocks__/helm-chart-manager.ts b/src/main/helm/__mocks__/helm-chart-manager.ts index 7d4a5e2a65..6a511273da 100644 --- a/src/main/helm/__mocks__/helm-chart-manager.ts +++ b/src/main/helm/__mocks__/helm-chart-manager.ts @@ -34,6 +34,36 @@ export class HelmChartManager { switch (this.repo.name) { case "stable": return Promise.resolve({ + "invalid-semver": [ + { + apiVersion: "3.0.0", + name: "weird-versioning", + version: "I am not semver", + repo: "stable", + digest: "test" + }, + { + apiVersion: "3.0.0", + name: "weird-versioning", + version: "v4.3.0", + repo: "stable", + digest: "test" + }, + { + apiVersion: "3.0.0", + name: "weird-versioning", + version: "I am not semver but more", + repo: "stable", + digest: "test" + }, + { + apiVersion: "3.0.0", + name: "weird-versioning", + version: "v4.4.0", + repo: "stable", + digest: "test" + }, + ], "apm-server": [ { apiVersion: "3.0.0", diff --git a/src/main/helm/__tests__/helm-service.test.ts b/src/main/helm/__tests__/helm-service.test.ts index 6ac169d5d0..56127c1684 100644 --- a/src/main/helm/__tests__/helm-service.test.ts +++ b/src/main/helm/__tests__/helm-service.test.ts @@ -62,6 +62,36 @@ describe("Helm Service tests", () => { digest: "test" } ], + "invalid-semver": [ + { + apiVersion: "3.0.0", + name: "weird-versioning", + version: "v4.4.0", + repo: "stable", + digest: "test" + }, + { + apiVersion: "3.0.0", + name: "weird-versioning", + version: "v4.3.0", + repo: "stable", + digest: "test" + }, + { + apiVersion: "3.0.0", + name: "weird-versioning", + version: "I am not semver", + repo: "stable", + digest: "test" + }, + { + apiVersion: "3.0.0", + name: "weird-versioning", + version: "I am not semver but more", + repo: "stable", + digest: "test" + }, + ], "redis": [ { apiVersion: "3.0.0", diff --git a/src/main/helm/helm-repo-manager.ts b/src/main/helm/helm-repo-manager.ts index dc30d92edd..d1c904539d 100644 --- a/src/main/helm/helm-repo-manager.ts +++ b/src/main/helm/helm-repo-manager.ts @@ -146,7 +146,7 @@ export class HelmRepoManager extends Singleton { return stdout; } - public static async addСustomRepo(repoAttributes : HelmRepo) { + public static async addCustomRepo(repoAttributes : HelmRepo) { logger.info(`[HELM]: adding repo "${repoAttributes.name}" from ${repoAttributes.url}`); const helm = await helmCli.binaryPath(); diff --git a/src/main/helm/helm-service.ts b/src/main/helm/helm-service.ts index 01262b6ef8..3cf56ccb13 100644 --- a/src/main/helm/helm-service.ts +++ b/src/main/helm/helm-service.ts @@ -19,13 +19,14 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import semver from "semver"; +import semver, { SemVer } from "semver"; import type { Cluster } from "../cluster"; import logger from "../logger"; import { HelmRepoManager } from "./helm-repo-manager"; import { HelmChartManager } from "./helm-chart-manager"; -import type { HelmChartList, RepoHelmChartList } from "../../renderer/api/endpoints/helm-charts.api"; +import type { HelmChart, HelmChartList, RepoHelmChartList } from "../../renderer/api/endpoints/helm-charts.api"; import { deleteRelease, getHistory, getRelease, getValues, installChart, listReleases, rollback, upgradeRelease } from "./helm-release-manager"; +import { iter, sortCompareChartVersions } from "../../common/utils"; interface GetReleaseValuesArgs { cluster: Cluster; @@ -132,28 +133,55 @@ class HelmService { } private excludeDeprecatedChartGroups(chartGroups: RepoHelmChartList) { - const groups = new Map(Object.entries(chartGroups)); + return Object.fromEntries( + iter.filterMap( + Object.entries(chartGroups), + ([name, charts]) => { + for (const chart of charts) { + if (chart.deprecated) { + // ignore chart group if any chart is deprecated + return undefined; + } + } - for (const [chartName, charts] of groups) { - if (charts[0].deprecated) { - groups.delete(chartName); - } + return [name, charts]; + } + ) + ); + } + + private sortCharts(charts: HelmChart[]) { + interface ExtendedHelmChart extends HelmChart { + __version: SemVer; } - return Object.fromEntries(groups); + const chartsWithVersion = Array.from( + iter.map( + charts, + (chart => { + const __version = semver.coerce(chart.version, { includePrerelease: true, loose: true }); + + if (!__version) { + logger.error(`[HELM-SERVICE]: Version from helm chart is not loosely coercable to semver.`, { name: chart.name, version: chart.version, repo: chart.repo }); + } + + (chart as ExtendedHelmChart).__version = __version; + + return chart as ExtendedHelmChart; + }) + ), + ); + + return chartsWithVersion + .sort(sortCompareChartVersions) + .map(chart => (delete chart.__version, chart as HelmChart)); } private sortChartsByVersion(chartGroups: RepoHelmChartList) { - for (const key in chartGroups) { - chartGroups[key] = chartGroups[key].sort((first, second) => { - const firstVersion = semver.coerce(first.version || 0); - const secondVersion = semver.coerce(second.version || 0); - - return semver.compare(secondVersion, firstVersion); - }); - } - - return chartGroups; + return Object.fromEntries( + Object.entries(chartGroups) + .map(([name, charts]) => [name, this.sortCharts(charts)]) + ); } } diff --git a/src/main/index.ts b/src/main/index.ts index 0599f820cb..a5116cfdb0 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -59,6 +59,11 @@ import { UserStore } from "../common/user-store"; import { WeblinkStore } from "../common/weblink-store"; import { ExtensionsStore } from "../extensions/extensions-store"; import { FilesystemProvisionerStore } from "./extension-filesystem"; +import { SentryInit } from "../common/sentry"; + +// This has to be called before start using winton-based logger +// For example, before any logger.log +SentryInit(); const workingDir = path.join(app.getPath("appData"), appName); const cleanup = disposer(); @@ -132,15 +137,20 @@ app.on("ready", async () => { /** * The following sync MUST be done before HotbarStore creation, because that * store has migrations that will remove items that previous migrations add - * if this is not presant + * if this is not present */ syncGeneralEntities(); logger.info("💾 Loading stores"); UserStore.createInstance().startMainReactions(); + + // ClusterStore depends on: UserStore ClusterStore.createInstance().provideInitialFromMain(); + + // HotbarStore depends on: ClusterStore HotbarStore.createInstance(); + ExtensionsStore.createInstance(); FilesystemProvisionerStore.createInstance(); WeblinkStore.createInstance(); diff --git a/src/main/initializers/ipc.ts b/src/main/initializers/ipc.ts index 95c4571a41..3614c602d7 100644 --- a/src/main/initializers/ipc.ts +++ b/src/main/initializers/ipc.ts @@ -20,7 +20,7 @@ */ import type { IpcMainInvokeEvent } from "electron"; -import type { KubernetesCluster } from "../../common/catalog-entities"; +import { KubernetesCluster } from "../../common/catalog-entities"; import { clusterFrameMap } from "../../common/cluster-frames"; import { clusterActivateHandler, clusterSetFrameIdHandler, clusterVisibilityHandler, clusterRefreshHandler, clusterDisconnectHandler, clusterKubectlApplyAllHandler, clusterKubectlDeleteAllHandler, clusterDeleteHandler } from "../../common/cluster-ipc"; import { ClusterId, ClusterStore } from "../../common/cluster-store"; @@ -50,9 +50,9 @@ export function initIpcMainHandlers() { }); ipcMainHandle(clusterVisibilityHandler, (event: IpcMainInvokeEvent, clusterId: ClusterId, visible: boolean) => { - const entity = catalogEntityRegistry.getById(clusterId); + const entity = catalogEntityRegistry.getById(clusterId); - for (const kubeEntity of catalogEntityRegistry.getItemsForApiKind(entity.apiVersion, entity.kind)) { + for (const kubeEntity of catalogEntityRegistry.getItemsByEntityClass(KubernetesCluster)) { kubeEntity.status.active = false; } diff --git a/src/main/prometheus/helm.ts b/src/main/prometheus/helm.ts index f583578ddb..4f98107946 100644 --- a/src/main/prometheus/helm.ts +++ b/src/main/prometheus/helm.ts @@ -27,7 +27,7 @@ export class PrometheusHelm extends PrometheusLens { readonly id: string = "helm"; readonly name: string = "Helm"; readonly rateAccuracy: string = "5m"; - readonly isConfigurable: boolean = false; + readonly isConfigurable: boolean = true; public async getPrometheusService(client: CoreV1Api): Promise { return this.getFirstNamespacedServer(client, "app=prometheus,component=server,heritage=Helm"); diff --git a/src/main/prometheus/lens.ts b/src/main/prometheus/lens.ts index 63341fe30e..c00bc31069 100644 --- a/src/main/prometheus/lens.ts +++ b/src/main/prometheus/lens.ts @@ -28,7 +28,7 @@ export class PrometheusLens extends PrometheusProvider { readonly id: string = "lens"; readonly name: string = "Lens"; readonly rateAccuracy: string = "1m"; - readonly isConfigurable: boolean = true; + readonly isConfigurable: boolean = false; public async getPrometheusService(client: CoreV1Api): Promise { try { diff --git a/src/main/prometheus/operator.ts b/src/main/prometheus/operator.ts index 58d41210b5..f037ef8a53 100644 --- a/src/main/prometheus/operator.ts +++ b/src/main/prometheus/operator.ts @@ -27,7 +27,7 @@ export class PrometheusOperator extends PrometheusProvider { readonly rateAccuracy: string = "1m"; readonly id: string = "operator"; readonly name: string = "Prometheus Operator"; - readonly isConfigurable: boolean = false; + readonly isConfigurable: boolean = true; public async getPrometheusService(client: CoreV1Api): Promise { return this.getFirstNamespacedServer(client, "operated-prometheus=true", "self-monitor=true"); diff --git a/src/main/prometheus/stacklight.ts b/src/main/prometheus/stacklight.ts index 7960e666a8..8148f3bff7 100644 --- a/src/main/prometheus/stacklight.ts +++ b/src/main/prometheus/stacklight.ts @@ -28,7 +28,7 @@ export class PrometheusStacklight extends PrometheusProvider { readonly id: string = "stacklight"; readonly name: string = "Stacklight"; readonly rateAccuracy: string = "1m"; - readonly isConfigurable: boolean = false; + readonly isConfigurable: boolean = true; public async getPrometheusService(client: CoreV1Api): Promise { try { diff --git a/src/main/proxy/lens-proxy.ts b/src/main/proxy/lens-proxy.ts index 00ef5fbb48..9ffff0fd6a 100644 --- a/src/main/proxy/lens-proxy.ts +++ b/src/main/proxy/lens-proxy.ts @@ -61,7 +61,8 @@ export class LensProxy extends Singleton { if (req.url.startsWith(`${apiPrefix}?`)) { handleWsUpgrade(req, socket, head); } else { - this.handleProxyUpgrade(proxy, req, socket, head); + this.handleProxyUpgrade(proxy, req, socket, head) + .catch(error => logger.error(`[LENS-PROXY]: failed to handle proxy upgrade: ${error}`)); } }); } @@ -168,7 +169,10 @@ export class LensProxy extends Singleton { if (!res.headersSent && req.url) { const url = new URL(req.url, "http://localhost"); - if (url.searchParams.has("watch")) res.flushHeaders(); + if (url.searchParams.has("watch")) { + res.statusCode = proxyRes.statusCode; + res.flushHeaders(); + } } }); @@ -177,6 +181,8 @@ export class LensProxy extends Singleton { return; } + logger.error(`[LENS-PROXY]: http proxy errored for cluster: ${error}`, { url: req.url }); + if (target) { logger.debug(`Failed proxy to target: ${JSON.stringify(target, null, 2)}`); @@ -189,14 +195,15 @@ export class LensProxy extends Singleton { logger.debug(`Retrying proxy request to url: ${reqId}`); setTimeout(() => { this.retryCounters.set(reqId, retryCount + 1); - this.handleRequest(proxy, req, res); + this.handleRequest(proxy, req, res) + .catch(error => logger.error(`[LENS-PROXY]: failed to handle request on proxy error: ${error}`)); }, timeoutMs); } } } try { - res.writeHead(500).end("Oops, something went wrong."); + res.writeHead(500).end(`Oops, something went wrong.\n${error}`); } catch (e) { logger.error(`[LENS-PROXY]: Failed to write headers: `, e); } diff --git a/src/main/routes/port-forward-route.ts b/src/main/routes/port-forward-route.ts index cfd88e771f..e97b4171cb 100644 --- a/src/main/routes/port-forward-route.ts +++ b/src/main/routes/port-forward-route.ts @@ -134,11 +134,22 @@ export class PortForwardRoute { name: resourceName, port, }); - const started = await portForward.start(); - if (!started) { + try { + const started = await portForward.start(); + + if (!started) { + logger.warn("[PORT-FORWARD-ROUTE]: failed to start a port-forward", { namespace, port, resourceType, resourceName }); + + return respondJson(response, { + message: "Failed to open port-forward" + }, 400); + } + } catch (error) { + logger.warn(`[PORT-FORWARD-ROUTE]: failed to open a port-forward: ${error}`, { namespace, port, resourceType, resourceName }); + return respondJson(response, { - message: "Failed to open port-forward" + message: error?.toString() || "Failed to open port-forward", }, 400); } } diff --git a/src/main/shell-session/local-shell-session.ts b/src/main/shell-session/local-shell-session.ts index 384e9317a8..ed1208fcbd 100644 --- a/src/main/shell-session/local-shell-session.ts +++ b/src/main/shell-session/local-shell-session.ts @@ -31,8 +31,11 @@ export class LocalShellSession extends ShellSession { return [helmCli.getBinaryDir()]; } - public async open() { + protected get cwd(): string | undefined { + return this.cluster.preferences?.terminalCWD; + } + public async open() { const env = await this.getCachedShellEnv(); const shell = env.PTYSHELL; const args = await this.getShellArgs(shell); @@ -51,7 +54,7 @@ export class LocalShellSession extends ShellSession { case "bash": return ["--init-file", path.join(await this.kubectlBinDirP, ".bash_set_path")]; case "fish": - return ["--login", "--init-command", `export PATH="${helmpath}:${kubectlPathDir}:$PATH"; export KUBECONFIG="${this.kubeconfigPathP}"`]; + return ["--login", "--init-command", `export PATH="${helmpath}:${kubectlPathDir}:$PATH"; export KUBECONFIG="${await this.kubeconfigPathP}"`]; case "zsh": return ["--login"]; default: diff --git a/src/main/shell-session/node-shell-session.ts b/src/main/shell-session/node-shell-session.ts index a3cc918371..aa8c14ba9b 100644 --- a/src/main/shell-session/node-shell-session.ts +++ b/src/main/shell-session/node-shell-session.ts @@ -32,6 +32,10 @@ export class NodeShellSession extends ShellSession { protected podId = `node-shell-${uuid()}`; protected kc: KubeConfig; + protected get cwd(): string | undefined { + return undefined; + } + constructor(socket: WebSocket, cluster: Cluster, protected nodeName: string) { super(socket, cluster); } @@ -77,13 +81,16 @@ export class NodeShellSession extends ShellSession { }], containers: [{ name: "shell", - image: "docker.io/alpine:3.13", + image: this.cluster.nodeShellImage, securityContext: { privileged: true, }, command: ["nsenter"], args: ["-t", "1", "-m", "-u", "-i", "-n", "sleep", "14000"] }], + imagePullSecrets: [{ + name: this.cluster.imagePullSecret, + }] } }); } diff --git a/src/main/shell-session/shell-session.ts b/src/main/shell-session/shell-session.ts index 039caa44ce..4771ec8b77 100644 --- a/src/main/shell-session/shell-session.ts +++ b/src/main/shell-session/shell-session.ts @@ -19,6 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +import fse from "fs-extra"; import type { Cluster } from "../cluster"; import { Kubectl } from "../kubectl"; import type * as WebSocket from "ws"; @@ -50,9 +51,7 @@ export abstract class ShellSession { protected kubectlBinDirP: Promise; protected kubeconfigPathP: Promise; - protected get cwd(): string | undefined { - return this.cluster.preferences?.terminalCWD; - } + protected abstract get cwd(): string | undefined; constructor(protected websocket: WebSocket, protected cluster: Cluster) { this.kubectl = new Kubectl(cluster.version); @@ -60,10 +59,14 @@ export abstract class ShellSession { this.kubectlBinDirP = this.kubectl.binDir(); } - open(shell: string, args: string[], env: Record): void { + protected async open(shell: string, args: string[], env: Record) { + const cwd = (this.cwd && await fse.pathExists(this.cwd)) + ? this.cwd + : env.HOME; + this.shellProcess = pty.spawn(shell, args, { cols: 80, - cwd: this.cwd || env.HOME, + cwd, env, name: "xterm-256color", rows: 30, diff --git a/src/main/tray.ts b/src/main/tray.ts index 24b00d122a..3a772503f8 100644 --- a/src/main/tray.ts +++ b/src/main/tray.ts @@ -93,7 +93,7 @@ function createTrayMenu(windowManager: WindowManager): Menu { click() { windowManager .navigate(preferencesURL()) - .catch(error => logger.error(`${TRAY_LOG_PREFIX}: Failed to nativate to Preferences`, { error })); + .catch(error => logger.error(`${TRAY_LOG_PREFIX}: Failed to navigate to Preferences`, { error })); }, } ]; diff --git a/src/main/utils/get-port.ts b/src/main/utils/get-port.ts index cdce0ce740..c44e843712 100644 --- a/src/main/utils/get-port.ts +++ b/src/main/utils/get-port.ts @@ -21,6 +21,7 @@ import type { Readable } from "stream"; import URLParse from "url-parse"; +import logger from "../logger"; interface GetPortArgs { /** @@ -47,11 +48,15 @@ interface GetPortArgs { * @returns A Promise for port number */ export function getPortFrom(stream: Readable, args: GetPortArgs): Promise { + const logLines: string[] = []; + return new Promise((resolve, reject) => { const handler = (data: any) => { const logItem: string = data.toString(); const match = logItem.match(args.lineRegex); + logLines.push(logItem); + if (match) { // use unknown protocol so that there is no default port const addr = new URLParse(`s://${match.groups.address.trim()}`); @@ -64,6 +69,7 @@ export function getPortFrom(stream: Readable, args: GetPortArgs): Promise { stream.off("data", handler); + logger.warn(`[getPortFrom]: failed to retrieve port via ${args.lineRegex.toString()}: ${logLines}`); reject(new Error("failed to retrieve port from stream")); }, args.timeout ?? 5000); diff --git a/src/migrations/cluster-store/5.0.0-beta.10.ts b/src/migrations/cluster-store/5.0.0-beta.10.ts index 3a2b5bdfe3..4b7fa9f3bd 100644 --- a/src/migrations/cluster-store/5.0.0-beta.10.ts +++ b/src/migrations/cluster-store/5.0.0-beta.10.ts @@ -45,7 +45,7 @@ export default { workspaces.set(id, name); } - const clusters: ClusterModel[] = store.get("clusters"); + const clusters: ClusterModel[] = store.get("clusters") ?? []; for (const cluster of clusters) { if (cluster.workspace && workspaces.has(cluster.workspace)) { diff --git a/src/migrations/cluster-store/5.0.0-beta.13.ts b/src/migrations/cluster-store/5.0.0-beta.13.ts index 25e0cd1543..e6f260db1b 100644 --- a/src/migrations/cluster-store/5.0.0-beta.13.ts +++ b/src/migrations/cluster-store/5.0.0-beta.13.ts @@ -108,7 +108,7 @@ export default { run(store) { const folder = path.resolve(app.getPath("userData"), "lens-local-storage"); - const oldClusters: ClusterModel[] = store.get("clusters"); + const oldClusters: ClusterModel[] = store.get("clusters") ?? []; const clusters = new Map(); for (const { id: oldId, ...cluster } of oldClusters) { diff --git a/src/migrations/hotbar-store/5.0.0-beta.10.ts b/src/migrations/hotbar-store/5.0.0-beta.10.ts index af6af5370c..0bf780b201 100644 --- a/src/migrations/hotbar-store/5.0.0-beta.10.ts +++ b/src/migrations/hotbar-store/5.0.0-beta.10.ts @@ -148,8 +148,8 @@ export default { store.set("hotbars", hotbars); } catch (error) { - if (!(error.code === "ENOENT" && error.path.endsWith("lens-workspace-store.json"))) { - // ignore lens-workspace-store.json being missing + // ignore files being missing + if (error.code !== "ENOENT") { throw error; } } diff --git a/src/migrations/user-store/5.0.3-beta.1.ts b/src/migrations/user-store/5.0.3-beta.1.ts new file mode 100644 index 0000000000..ef3448116d --- /dev/null +++ b/src/migrations/user-store/5.0.3-beta.1.ts @@ -0,0 +1,80 @@ +/** + * 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 { app } from "electron"; +import { existsSync, readFileSync } from "fs"; +import path from "path"; +import os from "os"; +import { ClusterStore, ClusterStoreModel } from "../../common/cluster-store"; +import type { KubeconfigSyncEntry, UserPreferencesModel } from "../../common/user-store"; +import { MigrationDeclaration, migrationLog } from "../helpers"; +import { isLogicalChildPath } from "../../common/utils"; + +export default { + version: "5.0.3-beta.1", + run(store) { + try { + const { syncKubeconfigEntries = [], ...preferences }: UserPreferencesModel = store.get("preferences") ?? {}; + const { clusters = [] }: ClusterStoreModel = JSON.parse(readFileSync(path.resolve(app.getPath("userData"), "lens-cluster-store.json"), "utf-8")) ?? {}; + const extensionDataDir = path.resolve(app.getPath("userData"), "extension_data"); + const syncPaths = new Set(syncKubeconfigEntries.map(s => s.filePath)); + + syncPaths.add(path.join(os.homedir(), ".kube")); + + for (const cluster of clusters) { + const dirOfKubeconfig = path.dirname(cluster.kubeConfigPath); + + if (dirOfKubeconfig === ClusterStore.storedKubeConfigFolder) { + migrationLog(`Skipping ${cluster.id} because kubeConfigPath is under ClusterStore.storedKubeConfigFolder`); + continue; + } + + if (syncPaths.has(cluster.kubeConfigPath) || syncPaths.has(dirOfKubeconfig)) { + migrationLog(`Skipping ${cluster.id} because kubeConfigPath is already being synced`); + continue; + } + + if (isLogicalChildPath(extensionDataDir, cluster.kubeConfigPath)) { + migrationLog(`Skipping ${cluster.id} because kubeConfigPath is placed under an extension_data folder`); + continue; + } + + if (!existsSync(cluster.kubeConfigPath)) { + migrationLog(`Skipping ${cluster.id} because kubeConfigPath no longer exists`); + continue; + } + + migrationLog(`Adding ${cluster.kubeConfigPath} from ${cluster.id} to sync paths`); + syncPaths.add(cluster.kubeConfigPath); + } + + const updatedSyncEntries: KubeconfigSyncEntry[] = [...syncPaths].map(filePath => ({ filePath })); + + migrationLog("Final list of synced paths", updatedSyncEntries); + store.set("preferences", { ...preferences, syncKubeconfigEntries: updatedSyncEntries }); + } catch (error) { + if (error.code !== "ENOENT") { + // ignore files being missing + throw error; + } + } + }, +} as MigrationDeclaration; diff --git a/src/migrations/user-store/index.ts b/src/migrations/user-store/index.ts index c08ef2c5c5..87628daeef 100644 --- a/src/migrations/user-store/index.ts +++ b/src/migrations/user-store/index.ts @@ -25,6 +25,7 @@ import { joinMigrations } from "../helpers"; import version210Beta4 from "./2.1.0-beta.4"; import version500Alpha3 from "./5.0.0-alpha.3"; +import version503Beta1 from "./5.0.3-beta.1"; import { fileNameMigration } from "./file-name-migration"; export { @@ -34,4 +35,5 @@ export { export default joinMigrations( version210Beta4, version500Alpha3, + version503Beta1, ); diff --git a/src/renderer/api/__tests__/kube-api-parse.test.ts b/src/renderer/api/__tests__/kube-api-parse.test.ts index beb8df8221..18b805212d 100644 --- a/src/renderer/api/__tests__/kube-api-parse.test.ts +++ b/src/renderer/api/__tests__/kube-api-parse.test.ts @@ -129,8 +129,18 @@ const tests: KubeApiParseTestData[] = [ }], ]; +const throwtests = [ + undefined, + "", + "ajklsmh" +]; + describe("parseApi unit tests", () => { - it.each(tests)("testing %s", (url, expected) => { + it.each(tests)("testing %j", (url, expected) => { expect(parseKubeApi(url)).toStrictEqual(expected); }); + + it.each(throwtests)("testing %j should throw", (url) => { + expect(() => parseKubeApi(url)).toThrowError("invalid apiPath"); + }); }); diff --git a/src/renderer/components/app-init/app-init.scss b/src/renderer/api/__tests__/pods.api.test.ts similarity index 75% rename from src/renderer/components/app-init/app-init.scss rename to src/renderer/api/__tests__/pods.api.test.ts index 7583111eba..e7a90f8e38 100644 --- a/src/renderer/components/app-init/app-init.scss +++ b/src/renderer/api/__tests__/pods.api.test.ts @@ -19,11 +19,20 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -.AppInit { - height: 100%; +import { Pod } from "../endpoints"; - .waiting-services { - font-size: small; - opacity: .75; - } -} \ No newline at end of file +describe("Pod tests", () => { + it("getAllContainers() should never throw", () => { + const pod = new Pod({ + apiVersion: "foo", + kind: "Pod", + metadata: { + name: "foobar", + resourceVersion: "foobar", + uid: "foobar", + }, + }); + + expect(pod.getAllContainers()).toStrictEqual([]); + }); +}); diff --git a/src/renderer/api/api-manager.ts b/src/renderer/api/api-manager.ts index eafcf0d8ee..9477446386 100644 --- a/src/renderer/api/api-manager.ts +++ b/src/renderer/api/api-manager.ts @@ -58,8 +58,14 @@ export class ApiManager { } } - protected resolveApi(api: string | KubeApi): KubeApi { - if (typeof api === "string") return this.getApi(api); + protected resolveApi(api?: string | KubeApi): KubeApi | undefined { + if (!api) { + return undefined; + } + + if (typeof api === "string") { + return this.getApi(api); + } return api; } diff --git a/src/renderer/api/endpoints/nodes.api.ts b/src/renderer/api/endpoints/nodes.api.ts index 17a7571209..d2c35761ca 100644 --- a/src/renderer/api/endpoints/nodes.api.ts +++ b/src/renderer/api/endpoints/nodes.api.ts @@ -156,9 +156,13 @@ export class Node extends KubeObject { } getRoleLabels() { - const roleLabels = Object.keys(this.metadata.labels).filter(key => - key.includes("node-role.kubernetes.io") - ).map(key => key.match(/([^/]+$)/)[0]); // all after last slash + if (!this.metadata?.labels || typeof this.metadata.labels !== "object") { + return ""; + } + + const roleLabels = Object.keys(this.metadata.labels) + .filter(key => key.includes("node-role.kubernetes.io")) + .map(key => key.match(/([^/]+$)/)[0]); // all after last slash if (this.metadata.labels["kubernetes.io/role"] != undefined) { roleLabels.push(this.metadata.labels["kubernetes.io/role"]); diff --git a/src/renderer/api/endpoints/pods.api.ts b/src/renderer/api/endpoints/pods.api.ts index 8ac8024227..5b85a3d42b 100644 --- a/src/renderer/api/endpoints/pods.api.ts +++ b/src/renderer/api/endpoints/pods.api.ts @@ -213,7 +213,7 @@ export class Pod extends WorkloadKubeObject { autoBind(this); } - declare spec: { + declare spec?: { volumes?: { name: string; persistentVolumeClaim: { @@ -291,11 +291,11 @@ export class Pod extends WorkloadKubeObject { }; getInitContainers() { - return this.spec.initContainers || []; + return this.spec?.initContainers || []; } getContainers() { - return this.spec.containers || []; + return this.spec?.containers || []; } getAllContainers() { diff --git a/src/renderer/api/kube-api-parse.ts b/src/renderer/api/kube-api-parse.ts index b02067fb63..689a6b9858 100644 --- a/src/renderer/api/kube-api-parse.ts +++ b/src/renderer/api/kube-api-parse.ts @@ -24,6 +24,9 @@ import type { KubeObject } from "./kube-object"; import { splitArray } from "../../common/utils"; import { apiManager } from "./api-manager"; +import { isDebugging } from "../../common/vars"; +import logger from "../../main/logger"; +import { inspect } from "util"; export interface IKubeObjectRef { kind: string; @@ -47,8 +50,26 @@ export interface IKubeApiParsed extends IKubeApiLinkRef { } export function parseKubeApi(path: string): IKubeApiParsed { - path = new URL(path, location.origin).pathname; - const [, prefix, ...parts] = path.split("/"); + if (!isDebugging) { + return _parseKubeApi(path); + } + + try { + const res = _parseKubeApi(path); + + logger.debug(`parseKubeApi(${inspect(path, false, null, false)}) -> ${inspect(res, false, null, false)}`); + + return res; + } catch (error) { + logger.debug(`parseKubeApi(${inspect(path, false, null, false)}) threw: ${error}`); + + throw error; + } +} + +function _parseKubeApi(path: string): IKubeApiParsed { + const apiPath = new URL(path, location.origin).pathname; + const [, prefix, ...parts] = apiPath.split("/"); const apiPrefix = `/${prefix}`; const [left, right, namespaced] = splitArray(parts, "namespaces"); let apiGroup, apiVersion, namespace, resource, name; @@ -70,12 +91,14 @@ export function parseKubeApi(path: string): IKubeApiParsed { apiGroup = left.join("/"); } else { switch (left.length) { + case 0: + throw new Error(`invalid apiPath: ${apiPath}`); case 4: [apiGroup, apiVersion, resource, name] = left; break; case 2: resource = left.pop(); - // fallthrough + // fallthrough case 1: apiVersion = left.pop(); apiGroup = ""; @@ -91,7 +114,7 @@ export function parseKubeApi(path: string): IKubeApiParsed { * There is no well defined selection from an array of items that were * separated by '/' * - * Solution is to create a huristic. Namely: + * Solution is to create a heuristic. Namely: * 1. if '.' in left[0] then apiGroup <- left[0] * 2. if left[1] matches /^v[0-9]/ then apiGroup, apiVersion <- left[0], left[1] * 3. otherwise assume apiVersion <- left[0] @@ -113,7 +136,7 @@ export function parseKubeApi(path: string): IKubeApiParsed { const apiBase = [apiPrefix, apiGroup, apiVersion, resource].filter(v => v).join("/"); if (!apiBase) { - throw new Error(`invalid apiPath: ${path}`); + throw new Error(`invalid apiPath: ${apiPath}`); } return { diff --git a/src/renderer/api/kube-object.ts b/src/renderer/api/kube-object.ts index df249f64e6..024651c4d9 100644 --- a/src/renderer/api/kube-object.ts +++ b/src/renderer/api/kube-object.ts @@ -254,12 +254,10 @@ export class KubeObject ({ - ...ownerRef, - namespace: this.getNs(), - })); + return refs.map(ownerRef => ({ ...ownerRef, namespace })); } getSearchFields() { diff --git a/src/renderer/api/terminal-api.ts b/src/renderer/api/terminal-api.ts index 3b2c8a1cd9..347dfac224 100644 --- a/src/renderer/api/terminal-api.ts +++ b/src/renderer/api/terminal-api.ts @@ -19,11 +19,13 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { stringify } from "querystring"; import { boundMethod, base64, EventEmitter } from "../utils"; import { WebSocketApi } from "./websocket-api"; import isEqual from "lodash/isEqual"; import { isDevelopment } from "../../common/vars"; +import url from "url"; +import { makeObservable, observable } from "mobx"; +import type { ParsedUrlQueryInput } from "querystring"; export enum TerminalChannels { STDIN = 0, @@ -55,7 +57,9 @@ export class TerminalApi extends WebSocketApi { protected size: { Width: number; Height: number }; public onReady = new EventEmitter<[]>(); - public isReady = false; + @observable public isReady = false; + @observable public shellRunCommandsFinished = false; + public readonly url: string; constructor(protected options: TerminalApiQuery) { super({ @@ -63,34 +67,33 @@ export class TerminalApi extends WebSocketApi { flushOnOpen: false, pingIntervalSeconds: 30, }); - } + makeObservable(this); - async getUrl() { - let { port } = location; - const { hostname, protocol } = location; - const { id, node } = this.options; - const wss = `ws${protocol === "https:" ? "s" : ""}://`; - const query: TerminalApiQuery = { id }; + const { hostname, protocol, port } = location; + const query: ParsedUrlQueryInput = { + id: options.id, + }; - if (port) { - port = `:${port}`; + if (options.node) { + query.node = options.node; + query.type = options.type || "node"; } - if (node) { - query.node = node; - query.type = "node"; - } - - return `${wss}${hostname}${port}/api?${stringify(query)}`; + this.url = url.format({ + protocol: protocol.includes("https") ? "wss" : "ws", + hostname, + port, + pathname: "/api", + query, + slashes: true, + }); } - async connect() { - const apiUrl = await this.getUrl(); - + connect() { this.emitStatus("Connecting ..."); this.onData.addListener(this._onReady, { prepend: true }); - - return super.connect(apiUrl); + this.onData.addListener(this._onShellRunCommandsFinished); + super.connect(this.url); } destroy() { @@ -106,6 +109,24 @@ export class TerminalApi extends WebSocketApi { this.onReady.removeAllListeners(); } + _onShellRunCommandsFinished = (data: string) => { + if (!data) { + return; + } + + /** + * This is a heuistic for ditermining when a shell has finished executing + * its own rc file (or RunCommands file) such as `.bashrc` or `.zshrc`. + * + * This heuistic assumes that the prompt line of a terminal is a single line + * and ends with a whitespace character. + */ + if (data.match(/\r?\n/) === null && data.match(/\s$/)) { + this.shellRunCommandsFinished = true; + this.onData.removeListener(this._onShellRunCommandsFinished); + } + }; + @boundMethod protected _onReady(data: string) { if (!data) return true; diff --git a/src/renderer/api/workload-kube-object.ts b/src/renderer/api/workload-kube-object.ts index 34fa5d5109..8d1f2a6a36 100644 --- a/src/renderer/api/workload-kube-object.ts +++ b/src/renderer/api/workload-kube-object.ts @@ -26,6 +26,7 @@ export interface IToleration { key?: string; operator?: string; effect?: string; + value?: string; tolerationSeconds?: number; } diff --git a/src/renderer/bootstrap.tsx b/src/renderer/bootstrap.tsx index 7768a9e0eb..87c30dba59 100644 --- a/src/renderer/bootstrap.tsx +++ b/src/renderer/bootstrap.tsx @@ -47,11 +47,13 @@ import { WeblinkStore } from "../common/weblink-store"; import { ExtensionsStore } from "../extensions/extensions-store"; import { FilesystemProvisionerStore } from "../main/extension-filesystem"; import { ThemeStore } from "./theme.store"; +import { SentryInit } from "../common/sentry"; +import { TerminalStore } from "./components/dock/terminal.store"; configurePackages(); /** - * If this is a development buid, wait a second to attach + * If this is a development build, wait a second to attach * Chrome Debugger to renderer process * https://stackoverflow.com/questions/52844870/debugging-electron-renderer-process-with-vscode */ @@ -78,6 +80,7 @@ export async function bootstrap(App: AppComponent) { initializers.intiKubeObjectDetailRegistry(); initializers.initWelcomeMenuRegistry(); initializers.initWorkloadsOverviewDetailRegistry(); + initializers.initCatalogEntityDetailRegistry(); initializers.initCatalog(); initializers.initIpcRendererListeners(); @@ -85,18 +88,31 @@ export async function bootstrap(App: AppComponent) { ExtensionDiscovery.createInstance().init(); UserStore.createInstance(); - await ClusterStore.createInstance().loadInitialOnRenderer(); + + SentryInit(); + + // ClusterStore depends on: UserStore + const cs = ClusterStore.createInstance(); + + await cs.loadInitialOnRenderer(); + + // HotbarStore depends on: ClusterStore HotbarStore.createInstance(); ExtensionsStore.createInstance(); FilesystemProvisionerStore.createInstance(); + + // ThemeStore depends on: UserStore ThemeStore.createInstance(); + + // TerminalStore depends on: ThemeStore + TerminalStore.createInstance(); WeblinkStore.createInstance(); ExtensionInstallationStateStore.bindIpcListeners(); HelmRepoManager.createInstance(); // initialize the manager // Register additional store listeners - ClusterStore.getInstance().registerIpcListener(); + cs.registerIpcListener(); // init app's dependencies if any if (App.init) { diff --git a/src/renderer/components/+apps-helm-charts/helm-chart.store.ts b/src/renderer/components/+apps-helm-charts/helm-chart.store.ts index c07dfaa886..71a5072068 100644 --- a/src/renderer/components/+apps-helm-charts/helm-chart.store.ts +++ b/src/renderer/components/+apps-helm-charts/helm-chart.store.ts @@ -21,7 +21,7 @@ import semver from "semver"; import { observable, makeObservable } from "mobx"; -import { autoBind } from "../../utils"; +import { autoBind, sortCompareChartVersions } from "../../utils"; import { getChartDetails, HelmChart, listCharts } from "../../api/endpoints/helm-charts.api"; import { ItemStore } from "../../item.store"; import flatten from "lodash/flatten"; @@ -60,12 +60,10 @@ export class HelmChartStore extends ItemStore { } protected sortVersions = (versions: IChartVersion[]) => { - return versions.sort((first, second) => { - const firstVersion = semver.coerce(first.version || 0); - const secondVersion = semver.coerce(second.version || 0); - - return semver.compare(secondVersion, firstVersion); - }); + return versions + .map(chartVersion => ({ ...chartVersion, __version: semver.coerce(chartVersion.version, { includePrerelease: true, loose: true }), })) + .sort(sortCompareChartVersions) + .map(({ __version, ...chartVersion }) => chartVersion); }; async getVersions(chartName: string, force?: boolean): Promise { diff --git a/src/renderer/components/+apps-releases/release-details.scss b/src/renderer/components/+apps-releases/release-details.scss index 3afe13961c..a4bd53610c 100644 --- a/src/renderer/components/+apps-releases/release-details.scss +++ b/src/renderer/components/+apps-releases/release-details.scss @@ -27,13 +27,7 @@ } .status { - .Badge { - @include release-status-bgs; - - &:first-child { - margin-left: 0; - } - } + @include release-status-bgs; } .chart { diff --git a/src/renderer/components/+apps-releases/release-details.tsx b/src/renderer/components/+apps-releases/release-details.tsx index e5bda1f873..fae2e3ba71 100644 --- a/src/renderer/components/+apps-releases/release-details.tsx +++ b/src/renderer/components/+apps-releases/release-details.tsx @@ -272,7 +272,7 @@ export class ReleaseDetails extends Component { {this.renderValues()} diff --git a/src/renderer/components/+apps-releases/release.store.ts b/src/renderer/components/+apps-releases/release.store.ts index ae69d38bc1..9d704566e7 100644 --- a/src/renderer/components/+apps-releases/release.store.ts +++ b/src/renderer/components/+apps-releases/release.store.ts @@ -55,12 +55,16 @@ export class ReleaseStore extends ItemStore { this.loadFromContextNamespaces(); } this.releaseSecrets.replace(newSecrets); + }, { + fireImmediately: true, }); } - watchSelecteNamespaces(): (() => void) { + watchSelectedNamespaces(): (() => void) { return reaction(() => namespaceStore.context.contextNamespaces, namespaces => { this.loadAll(namespaces); + }, { + fireImmediately: true, }); } @@ -91,7 +95,7 @@ export class ReleaseStore extends ItemStore { this.failedLoading = false; } catch (error) { this.failedLoading = true; - console.error("Loading Helm Chart releases has failed", error); + console.warn("Loading Helm Chart releases has failed", error); if (error.error) { Notifications.error(error.error); diff --git a/src/renderer/components/+apps-releases/releases.tsx b/src/renderer/components/+apps-releases/releases.tsx index 9a746b8909..c39807d73e 100644 --- a/src/renderer/components/+apps-releases/releases.tsx +++ b/src/renderer/components/+apps-releases/releases.tsx @@ -36,6 +36,7 @@ import { secretsStore } from "../+config-secrets/secrets.store"; import { NamespaceSelectFilter } from "../+namespaces/namespace-select-filter"; import type { ReleaseRouteParams } from "../../../common/routes"; import { releaseURL } from "../../../common/routes"; +import { namespaceStore } from "../+namespaces/namespace.store"; enum columnId { name = "name", @@ -54,9 +55,15 @@ interface Props extends RouteComponentProps { @observer export class HelmReleases extends Component { componentDidMount() { + const { match: { params: { namespace } } } = this.props; + + if (namespace) { + namespaceStore.selectNamespaces(namespace); + } + disposeOnUnmount(this, [ releaseStore.watchAssociatedSecrets(), - releaseStore.watchSelecteNamespaces(), + releaseStore.watchSelectedNamespaces(), ]); } diff --git a/src/renderer/components/+catalog/catalog-entity.store.tsx b/src/renderer/components/+catalog/catalog-entity.store.tsx index fea9e5e6ba..b606d54c1a 100644 --- a/src/renderer/components/+catalog/catalog-entity.store.tsx +++ b/src/renderer/components/+catalog/catalog-entity.store.tsx @@ -88,6 +88,7 @@ export class CatalogEntityItem implements ItemObject { onClick?.(event); event.stopPropagation(); }} + expandable={false} /> )); } diff --git a/src/renderer/components/+cluster/cluster-issues.tsx b/src/renderer/components/+cluster/cluster-issues.tsx index 90a8a1a74b..f35f8bea61 100644 --- a/src/renderer/components/+cluster/cluster-issues.tsx +++ b/src/renderer/components/+cluster/cluster-issues.tsx @@ -22,7 +22,7 @@ import "./cluster-issues.scss"; import React from "react"; -import { observer } from "mobx-react"; +import { disposeOnUnmount, observer } from "mobx-react"; import { computed, makeObservable } from "mobx"; import { Icon } from "../icon"; import { SubHeader } from "../layout/sub-header"; @@ -35,6 +35,7 @@ import { Spinner } from "../spinner"; import { ThemeStore } from "../../theme.store"; import { lookupApiLink } from "../../api/kube-api"; import { kubeSelectedUrlParam, showDetails } from "../kube-object"; +import { kubeWatchApi } from "../../api/kube-watch-api"; interface Props { className?: string; @@ -65,6 +66,10 @@ export class ClusterIssues extends React.Component { constructor(props: Props) { super(props); makeObservable(this); + + disposeOnUnmount(this, [ + kubeWatchApi.subscribeStores([eventStore, nodesStore]) + ]); } @computed get warnings() { diff --git a/src/renderer/components/+config-autoscalers/hpa-details.scss b/src/renderer/components/+config-autoscalers/hpa-details.scss index ef9506976f..c1c6e930fc 100644 --- a/src/renderer/components/+config-autoscalers/hpa-details.scss +++ b/src/renderer/components/+config-autoscalers/hpa-details.scss @@ -22,7 +22,7 @@ @import "autoscaler.mixins"; .HpaDetails { - .Badge { + .status { @include hpa-status-bgc; } diff --git a/src/renderer/components/+config-autoscalers/hpa-details.tsx b/src/renderer/components/+config-autoscalers/hpa-details.tsx index 1a6db54e78..cf56b2c35c 100644 --- a/src/renderer/components/+config-autoscalers/hpa-details.tsx +++ b/src/renderer/components/+config-autoscalers/hpa-details.tsx @@ -125,7 +125,7 @@ export class HpaDetails extends React.Component { {hpa.getReplicas()} - + {hpa.getConditions().map(({ type, tooltip, isReady }) => { if (!isReady) return null; diff --git a/src/renderer/components/+config-autoscalers/hpa.scss b/src/renderer/components/+config-autoscalers/hpa.scss index 41fa3f6d08..dc79016fcf 100644 --- a/src/renderer/components/+config-autoscalers/hpa.scss +++ b/src/renderer/components/+config-autoscalers/hpa.scss @@ -38,10 +38,7 @@ &.status { flex: 1.5; @include table-cell-labels-offsets; - - .Badge { - @include hpa-status-bgc; - } + @include hpa-status-bgc; } &.age { diff --git a/src/renderer/components/+config-autoscalers/hpa.tsx b/src/renderer/components/+config-autoscalers/hpa.tsx index eca60e8c63..539bbb18d7 100644 --- a/src/renderer/components/+config-autoscalers/hpa.tsx +++ b/src/renderer/components/+config-autoscalers/hpa.tsx @@ -104,6 +104,7 @@ export class HorizontalPodAutoscalers extends React.Component { label={type} tooltip={tooltip} className={cssNames(type.toLowerCase())} + expandable={false} /> ); }) diff --git a/src/renderer/components/+config-resource-quotas/add-quota-dialog.tsx b/src/renderer/components/+config-resource-quotas/add-quota-dialog.tsx index 1227e97049..b341d2c86a 100644 --- a/src/renderer/components/+config-resource-quotas/add-quota-dialog.tsx +++ b/src/renderer/components/+config-resource-quotas/add-quota-dialog.tsx @@ -175,6 +175,7 @@ export class AddQuotaDialog extends React.Component { this.quotaName = v.toLowerCase()} className="box grow" @@ -218,7 +219,7 @@ export class AddQuotaDialog extends React.Component {

{this.quotaEntries.map(([quota, value]) => { return ( -
+
{quota}
{value}
this.quotas[quota] = ""} /> diff --git a/src/renderer/components/+config-secrets/add-secret-dialog.tsx b/src/renderer/components/+config-secrets/add-secret-dialog.tsx index bfd7a3262e..23adfaf804 100644 --- a/src/renderer/components/+config-secrets/add-secret-dialog.tsx +++ b/src/renderer/components/+config-secrets/add-secret-dialog.tsx @@ -223,6 +223,7 @@ export class AddSecretDialog extends React.Component { this.name = v} /> diff --git a/src/renderer/components/+config-secrets/secrets.tsx b/src/renderer/components/+config-secrets/secrets.tsx index d74305a499..3bfeada163 100644 --- a/src/renderer/components/+config-secrets/secrets.tsx +++ b/src/renderer/components/+config-secrets/secrets.tsx @@ -79,7 +79,7 @@ export class Secrets extends React.Component { secret.getName(), , secret.getNs(), - secret.getLabels().map(label => ), + secret.getLabels().map(label => ), secret.getKeys().join(", "), secret.type, secret.getAge(), diff --git a/src/renderer/components/+custom-resources/crd-details.scss b/src/renderer/components/+custom-resources/crd-details.scss index e5d016bb30..8e969a580b 100644 --- a/src/renderer/components/+custom-resources/crd-details.scss +++ b/src/renderer/components/+custom-resources/crd-details.scss @@ -23,11 +23,9 @@ .CRDDetails { .conditions { - .Badge { - @include crd-condition-bgc; - } + @include crd-condition-bgc; } - + .Table { margin-left: -$margin * 2; margin-right: -$margin * 2; @@ -41,21 +39,8 @@ flex: 0.5; } - .description { - flex: 1.5; - - .Badge { - background: transparent; - } - } - .json-path { flex: 3; - - .Badge { - white-space: pre-line; - text-overflow: initial; - } } } } diff --git a/src/renderer/components/+custom-resources/crd-details.tsx b/src/renderer/components/+custom-resources/crd-details.tsx index 146c299884..bef1dc6f67 100644 --- a/src/renderer/components/+custom-resources/crd-details.tsx +++ b/src/renderer/components/+custom-resources/crd-details.tsx @@ -25,7 +25,6 @@ import React from "react"; import { Link } from "react-router-dom"; import { observer } from "mobx-react"; import type { CustomResourceDefinition } from "../../api/endpoints/crd.api"; -import { cssNames } from "../../utils"; import { AceEditor } from "../ace-editor"; import { Badge } from "../badge"; import { DrawerItem, DrawerTitle } from "../drawer"; @@ -86,7 +85,8 @@ export class CRDDetails extends React.Component {

{message}

diff --git a/src/renderer/components/+custom-resources/crd-list.tsx b/src/renderer/components/+custom-resources/crd-list.tsx index 822eff575f..e8bfee160d 100644 --- a/src/renderer/components/+custom-resources/crd-list.tsx +++ b/src/renderer/components/+custom-resources/crd-list.tsx @@ -139,7 +139,7 @@ export class CrdList extends React.Component { ]} renderTableContents={(crd: CustomResourceDefinition) => [ - {crd.getResourceTitle()} + {crd.getResourceKind()} , crd.getGroup(), crd.getVersion(), diff --git a/src/renderer/components/+custom-resources/crd-resource-details.scss b/src/renderer/components/+custom-resources/crd-resource-details.scss index af2c36ef2c..b7e57a0295 100644 --- a/src/renderer/components/+custom-resources/crd-resource-details.scss +++ b/src/renderer/components/+custom-resources/crd-resource-details.scss @@ -21,13 +21,9 @@ .CrdResourceDetails { .status { - .value { - .Badge { - &.ready { - color: white; - background-color: $colorOk; - } - } + .ready { + color: white; + background-color: $colorOk; } } } \ No newline at end of file diff --git a/src/renderer/components/+custom-resources/crd-resource-details.tsx b/src/renderer/components/+custom-resources/crd-resource-details.tsx index f7305788eb..dc6181c63c 100644 --- a/src/renderer/components/+custom-resources/crd-resource-details.tsx +++ b/src/renderer/components/+custom-resources/crd-resource-details.tsx @@ -90,7 +90,8 @@ export class CrdResourceDetails extends React.Component { .map(({ kind, message, status }, index) => ( )); diff --git a/src/renderer/components/+custom-resources/crd.mixins.scss b/src/renderer/components/+custom-resources/crd.mixins.scss index 62a160b1ff..89c7c86acc 100644 --- a/src/renderer/components/+custom-resources/crd.mixins.scss +++ b/src/renderer/components/+custom-resources/crd.mixins.scss @@ -30,7 +30,7 @@ $crd-condition-colors: ( @mixin crd-condition-bgc { @each $status, $color in $crd-condition-colors { - &.#{$status} { + .#{$status} { background: $color; color: white; } diff --git a/src/renderer/components/+extensions/__tests__/extensions.test.tsx b/src/renderer/components/+extensions/__tests__/extensions.test.tsx index 3c2484b92e..f51ba24575 100644 --- a/src/renderer/components/+extensions/__tests__/extensions.test.tsx +++ b/src/renderer/components/+extensions/__tests__/extensions.test.tsx @@ -59,14 +59,7 @@ describe("Extensions", () => { }); ExtensionInstallationStateStore.reset(); - UserStore.resetInstance(); - UserStore.createInstance(); - - ExtensionDiscovery.resetInstance(); - ExtensionDiscovery.createInstance().uninstallExtension = jest.fn(() => Promise.resolve()); - - ExtensionLoader.resetInstance(); ExtensionLoader.createInstance().addExtension({ id: "extensionId", manifest: { @@ -79,10 +72,15 @@ describe("Extensions", () => { isEnabled: true, isCompatible: true }); + ExtensionDiscovery.createInstance().uninstallExtension = jest.fn(() => Promise.resolve()); + UserStore.createInstance(); }); afterEach(() => { mockFs.restore(); + UserStore.resetInstance(); + ExtensionDiscovery.resetInstance(); + ExtensionLoader.resetInstance(); }); it("disables uninstall and disable buttons while uninstalling", async () => { diff --git a/src/renderer/components/+extensions/extension-install.store.ts b/src/renderer/components/+extensions/extension-install.store.ts index c02bb482fd..4ca79ec337 100644 --- a/src/renderer/components/+extensions/extension-install.store.ts +++ b/src/renderer/components/+extensions/extension-install.store.ts @@ -225,7 +225,7 @@ export class ExtensionInstallationStateStore { } /** - * If there is at least one extension currently ininstalling + * If there is at least one extension currently uninstalling */ static get anyUninstalling(): boolean { return ExtensionInstallationStateStore.uninstalling > 0; diff --git a/src/renderer/components/+extensions/extensions.tsx b/src/renderer/components/+extensions/extensions.tsx index b135487343..0265a6ddf3 100644 --- a/src/renderer/components/+extensions/extensions.tsx +++ b/src/renderer/components/+extensions/extensions.tsx @@ -49,7 +49,7 @@ import { docsUrl } from "../../../common/vars"; function getMessageFromError(error: any): string { if (!error || typeof error !== "object") { - return "an error has occured"; + return "an error has occurred"; } if (error.message) { @@ -63,7 +63,7 @@ function getMessageFromError(error: any): string { const rawMessage = String(error); if (rawMessage === String({})) { - return "an error has occured"; + return "an error has occurred"; } return rawMessage; diff --git a/src/renderer/components/+namespaces/add-namespace-dialog.tsx b/src/renderer/components/+namespaces/add-namespace-dialog.tsx index 213891c233..2427c9fd9b 100644 --- a/src/renderer/components/+namespaces/add-namespace-dialog.tsx +++ b/src/renderer/components/+namespaces/add-namespace-dialog.tsx @@ -100,6 +100,7 @@ export class AddNamespaceDialog extends React.Component { required autoFocus iconLeft="layers" placeholder="Namespace" + trim validators={systemName} value={namespace} onChange={v => this.namespace = v.toLowerCase()} /> diff --git a/src/renderer/components/+namespaces/namespace-select-filter.tsx b/src/renderer/components/+namespaces/namespace-select-filter.tsx index 8ae2e97958..514749ed44 100644 --- a/src/renderer/components/+namespaces/namespace-select-filter.tsx +++ b/src/renderer/components/+namespaces/namespace-select-filter.tsx @@ -30,6 +30,8 @@ import { NamespaceSelect } from "./namespace-select"; import { namespaceStore } from "./namespace.store"; import type { SelectOption, SelectProps } from "../select"; +import { isLinux, isMac, isWindows } from "../../../common/vars"; +import { observable } from "mobx"; const Placeholder = observer((props: PlaceholderProps) => { const getPlaceholder = (): React.ReactNode => { @@ -55,13 +57,16 @@ const Placeholder = observer((props: PlaceholderProps) => { @observer export class NamespaceSelectFilter extends React.Component { + static isMultiSelection = observable.box(false); + static isMenuOpen = observable.box(false); + formatOptionLabel({ value: namespace, label }: SelectOption) { if (namespace) { const isSelected = namespaceStore.hasContext(namespace); return (
- + {namespace} {isSelected && }
@@ -72,26 +77,55 @@ export class NamespaceSelectFilter extends React.Component { } onChange([{ value: namespace }]: SelectOption[]) { - if (namespace) { + if (NamespaceSelectFilter.isMultiSelection.get() && namespace) { namespaceStore.toggleContext(namespace); + } else if (!NamespaceSelectFilter.isMultiSelection.get() && namespace) { + namespaceStore.toggleSingle(namespace); } else { - namespaceStore.toggleAll(false); // "All namespaces" clicked + namespaceStore.toggleAll(true); // "All namespaces" clicked } } + onKeyDown = (e: React.KeyboardEvent) => { + if (isMac && e.metaKey || (isWindows || isLinux) && e.ctrlKey) { + NamespaceSelectFilter.isMultiSelection.set(true); + } + }; + + onKeyUp = (e: React.KeyboardEvent) => { + if (isMac && e.key === "Meta" || (isWindows || isLinux) && e.key === "Control") { + NamespaceSelectFilter.isMultiSelection.set(false); + } + }; + + onClick = () => { + if (!NamespaceSelectFilter.isMultiSelection.get()) { + NamespaceSelectFilter.isMenuOpen.set(!NamespaceSelectFilter.isMenuOpen.get()); + } + }; + + reset = () => { + NamespaceSelectFilter.isMultiSelection.set(false); + NamespaceSelectFilter.isMenuOpen.set(false); + }; + render() { return ( - +
+ +
); } } diff --git a/src/renderer/components/+namespaces/namespace.store.ts b/src/renderer/components/+namespaces/namespace.store.ts index c292f0f9fa..e8db0948ca 100644 --- a/src/renderer/components/+namespaces/namespace.store.ts +++ b/src/renderer/components/+namespaces/namespace.store.ts @@ -161,6 +161,12 @@ export class NamespaceStore extends KubeObjectStore { } } + @action + toggleSingle(namespace: string){ + this.clearSelected(); + this.selectNamespaces(namespace); + } + @action toggleAll(showAll?: boolean) { if (typeof showAll === "boolean") { diff --git a/src/renderer/components/+network-services/service-details.tsx b/src/renderer/components/+network-services/service-details.tsx index 6450850076..ec10cd5f94 100644 --- a/src/renderer/components/+network-services/service-details.tsx +++ b/src/renderer/components/+network-services/service-details.tsx @@ -39,9 +39,12 @@ interface Props extends KubeObjectDetailsProps { @observer export class ServiceDetails extends React.Component { componentDidMount() { + const { object: service } = this.props; + disposeOnUnmount(this, [ kubeWatchApi.subscribeStores([endpointStore], { preload: true, + namespaces: [service.getNs()], }), ]); } diff --git a/src/renderer/components/+nodes/node-details-resources.tsx b/src/renderer/components/+nodes/node-details-resources.tsx index df613cf436..636a3f55f0 100644 --- a/src/renderer/components/+nodes/node-details-resources.tsx +++ b/src/renderer/components/+nodes/node-details-resources.tsx @@ -54,7 +54,7 @@ export class NodeDetailsResources extends React.Component { selectable scrollable={false} > - + CPU Memory Ephemeral Storage diff --git a/src/renderer/components/+nodes/node-details.scss b/src/renderer/components/+nodes/node-details.scss index 895fdadfee..8d7672fde5 100644 --- a/src/renderer/components/+nodes/node-details.scss +++ b/src/renderer/components/+nodes/node-details.scss @@ -24,9 +24,6 @@ --status-default-bg: #{$colorError}; .conditions { - .Badge { - cursor: default; - @include node-status-bgs; - } + @include node-status-bgs; } } \ No newline at end of file diff --git a/src/renderer/components/+nodes/nodes-mixins.scss b/src/renderer/components/+nodes/nodes-mixins.scss index 5ac0e4400a..74979c7a9f 100644 --- a/src/renderer/components/+nodes/nodes-mixins.scss +++ b/src/renderer/components/+nodes/nodes-mixins.scss @@ -33,7 +33,7 @@ $node-status-color-list: ( @mixin node-status-bgs { @each $status, $color in $node-status-color-list { - &.#{$status} { + .#{$status} { background: $color; color: white; } diff --git a/src/renderer/components/+preferences/add-helm-repo-dialog.tsx b/src/renderer/components/+preferences/add-helm-repo-dialog.tsx index 1bf978de68..36388088e1 100644 --- a/src/renderer/components/+preferences/add-helm-repo-dialog.tsx +++ b/src/renderer/components/+preferences/add-helm-repo-dialog.tsx @@ -107,7 +107,7 @@ export class AddHelmRepoDialog extends React.Component { async addCustomRepo() { try { - await HelmRepoManager.addĐĄustomRepo(this.helmRepo); + await HelmRepoManager.addCustomRepo(this.helmRepo); Notifications.ok(<>Helm repository {this.helmRepo.name} has added); this.props.onAddRepo(); this.close(); @@ -145,7 +145,7 @@ export class AddHelmRepoDialog extends React.Component { /> {this.renderFileInput(`Key file`, FileType.KeyFile, AddHelmRepoDialog.keyExtensions)} {this.renderFileInput(`Ca file`, FileType.CaFile, AddHelmRepoDialog.certExtensions)} - {this.renderFileInput(`Cerificate file`, FileType.CertFile, AddHelmRepoDialog.certExtensions)} + {this.renderFileInput(`Certificate file`, FileType.CertFile, AddHelmRepoDialog.certExtensions)} { this.helmRepo.name = v} /> diff --git a/src/renderer/components/+preferences/preferences.tsx b/src/renderer/components/+preferences/preferences.tsx index 9bf82f9e4f..f384d049db 100644 --- a/src/renderer/components/+preferences/preferences.tsx +++ b/src/renderer/components/+preferences/preferences.tsx @@ -40,6 +40,8 @@ import { Tab, Tabs } from "../tabs"; import { FormSwitch, Switcher } from "../switch"; import { KubeconfigSyncs } from "./kubeconfig-syncs"; import { SettingLayout } from "../layout/setting-layout"; +import { Checkbox } from "../checkbox"; +import { sentryDsn } from "../../../common/vars"; enum Pages { Application = "application", @@ -79,7 +81,7 @@ export class Preferences extends React.Component { const fragment = hash.slice(1); // hash is /^(#\w.)?$/ if (fragment) { - // ignore empty framents + // ignore empty fragments document.getElementById(fragment)?.scrollIntoView(); } }, { @@ -257,6 +259,29 @@ export class Preferences extends React.Component {

Telemetry

{telemetryExtensions.map(this.renderExtension)} + {sentryDsn ? ( + +
+ + { + UserStore.getInstance().allowErrorReporting = value; + }} + /> +
+ + Automatic error reports provide vital information about issues and application crashes. + It is highly recommended to keep this feature enabled to ensure fast turnaround for issues you might encounter. + +
+
+
+
) : + // we don't need to shows the checkbox at all if Sentry dsn is not a valid url + null + }
)} {this.activeTab == Pages.Extensions && ( diff --git a/src/renderer/components/+user-management/+cluster-role-bindings/details.scss b/src/renderer/components/+user-management/+cluster-role-bindings/details.scss index 1efb4412a2..1afe80f236 100644 --- a/src/renderer/components/+user-management/+cluster-role-bindings/details.scss +++ b/src/renderer/components/+user-management/+cluster-role-bindings/details.scss @@ -19,5 +19,5 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -.RoleBindingDetails { -} \ No newline at end of file +.ClusterRoleBindingDetails { +} diff --git a/src/renderer/components/+user-management/+cluster-role-bindings/details.tsx b/src/renderer/components/+user-management/+cluster-role-bindings/details.tsx index 08087324cc..7dea8c083e 100644 --- a/src/renderer/components/+user-management/+cluster-role-bindings/details.tsx +++ b/src/renderer/components/+user-management/+cluster-role-bindings/details.tsx @@ -81,7 +81,7 @@ export class ClusterRoleBindingDetails extends React.Component { const subjects = clusterRoleBinding.getSubjects(); return ( -
+
diff --git a/src/renderer/components/+user-management/+cluster-roles/add-dialog.scss b/src/renderer/components/+user-management/+cluster-roles/add-dialog.scss index 4b94e11c4b..cc0e3fcdd6 100644 --- a/src/renderer/components/+user-management/+cluster-roles/add-dialog.scss +++ b/src/renderer/components/+user-management/+cluster-roles/add-dialog.scss @@ -19,8 +19,8 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -.AddRoleDialog { +.AddClusterRoleDialog { .AceEditor { min-height: 200px; } -} \ No newline at end of file +} diff --git a/src/renderer/components/+user-management/+cluster-roles/add-dialog.tsx b/src/renderer/components/+user-management/+cluster-roles/add-dialog.tsx index 4006334903..f6866355b3 100644 --- a/src/renderer/components/+user-management/+cluster-roles/add-dialog.tsx +++ b/src/renderer/components/+user-management/+cluster-roles/add-dialog.tsx @@ -76,12 +76,12 @@ export class AddClusterRoleDialog extends React.Component { return ( - Create ClusterRole} + Create ClusterRole} done={AddClusterRoleDialog.close} > { this.name = v.toLowerCase()} /> diff --git a/src/renderer/components/+workloads-cronjobs/cronjob-details.scss b/src/renderer/components/+workloads-cronjobs/cronjob-details.scss index 513afd4273..f5e3c76ec8 100644 --- a/src/renderer/components/+workloads-cronjobs/cronjob-details.scss +++ b/src/renderer/components/+workloads-cronjobs/cronjob-details.scss @@ -33,8 +33,6 @@ } .conditions { - .Badge { - @include job-condition-bgs; - } + @include job-condition-bgs; } } \ No newline at end of file diff --git a/src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx b/src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx index f4006bd89f..0e2f98d16a 100644 --- a/src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx +++ b/src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx @@ -123,6 +123,7 @@ export class CronJobTriggerDialog extends Component { this.jobName = v.toLowerCase()} diff --git a/src/renderer/components/+workloads-cronjobs/cronjobs.tsx b/src/renderer/components/+workloads-cronjobs/cronjobs.tsx index b1a812cc4f..b1c5e3424c 100644 --- a/src/renderer/components/+workloads-cronjobs/cronjobs.tsx +++ b/src/renderer/components/+workloads-cronjobs/cronjobs.tsx @@ -37,6 +37,7 @@ import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { ConfirmDialog } from "../confirm-dialog/confirm-dialog"; import { Notifications } from "../notifications/notifications"; import type { CronJobsRouteParams } from "../../../common/routes"; +import moment from "moment"; enum columnId { name = "name", @@ -65,7 +66,11 @@ export class CronJobs extends React.Component { [columnId.namespace]: (cronJob: CronJob) => cronJob.getNs(), [columnId.suspend]: (cronJob: CronJob) => cronJob.getSuspendFlag(), [columnId.active]: (cronJob: CronJob) => cronJobStore.getActiveJobsNum(cronJob), - [columnId.lastSchedule]: (cronJob: CronJob) => cronJob.getLastScheduleTime(), + [columnId.lastSchedule]: (cronJob: CronJob) => ( + cronJob.status?.lastScheduleTime + ? moment().diff(cronJob.status.lastScheduleTime) + : 0 + ), [columnId.age]: (cronJob: CronJob) => cronJob.getTimeDiffFromNow(), }} searchFilters={[ diff --git a/src/renderer/components/+workloads-deployments/deployment-details.scss b/src/renderer/components/+workloads-deployments/deployment-details.scss index 75ff19bbd4..490edf169f 100644 --- a/src/renderer/components/+workloads-deployments/deployment-details.scss +++ b/src/renderer/components/+workloads-deployments/deployment-details.scss @@ -21,21 +21,19 @@ .DeploymentDetails { .conditions { - .Badge { - &.available { - color: white; - background-color: $deployment-available; - } + .available { + color: white; + background-color: $deployment-available; + } - &.progressing { - color: white; - background-color: $deployment-progressing; - } + .progressing { + color: white; + background-color: $deployment-progressing; + } - &.replica-failure { - color: white; - background-color: $deployment-replicafailure; - } + .replica-failure { + color: white; + background-color: $deployment-replicafailure; } } } \ No newline at end of file diff --git a/src/renderer/components/+workloads-deployments/deployment-details.tsx b/src/renderer/components/+workloads-deployments/deployment-details.tsx index acd514749f..87f4e08378 100644 --- a/src/renderer/components/+workloads-deployments/deployment-details.tsx +++ b/src/renderer/components/+workloads-deployments/deployment-details.tsx @@ -27,7 +27,6 @@ import { disposeOnUnmount, observer } from "mobx-react"; import { DrawerItem } from "../drawer"; import { Badge } from "../badge"; import type { Deployment } from "../../api/endpoints"; -import { cssNames } from "../../utils"; import { PodDetailsTolerations } from "../+workloads-pods/pod-details-tolerations"; import { PodDetailsAffinities } from "../+workloads-pods/pod-details-affinities"; import { podsStore } from "../+workloads-pods/pods.store"; @@ -118,7 +117,8 @@ export class DeploymentDetails extends React.Component {

{message}

diff --git a/src/renderer/components/+workloads-jobs/job-details.scss b/src/renderer/components/+workloads-jobs/job-details.scss index ffd78bc791..8ac3a66a93 100644 --- a/src/renderer/components/+workloads-jobs/job-details.scss +++ b/src/renderer/components/+workloads-jobs/job-details.scss @@ -22,8 +22,6 @@ .JobDetails { .conditions { - .Badge { - @include job-condition-bgs; - } + @include job-condition-bgs; } } \ No newline at end of file diff --git a/src/renderer/components/+workloads-pods/container-charts.tsx b/src/renderer/components/+workloads-pods/container-charts.tsx index df55fc9b59..0c810b47de 100644 --- a/src/renderer/components/+workloads-pods/container-charts.tsx +++ b/src/renderer/components/+workloads-pods/container-charts.tsx @@ -27,6 +27,7 @@ import { isMetricsEmpty, normalizeMetrics } from "../../api/endpoints/metrics.ap import { NoMetrics } from "../resource-metrics/no-metrics"; import { IResourceMetricsValue, ResourceMetricsContext } from "../resource-metrics"; import { ThemeStore } from "../../theme.store"; +import { mapValues } from "lodash"; type IContext = IResourceMetricsValue; @@ -37,10 +38,7 @@ export const ContainerCharts = observer(() => { if (!metrics) return null; if (isMetricsEmpty(metrics)) return ; - const values = Object.values(metrics) - .map(normalizeMetrics) - .map(({ data }) => data.result[0].values); - const [ + const { cpuUsage, cpuRequests, cpuLimits, @@ -48,7 +46,7 @@ export const ContainerCharts = observer(() => { memoryRequests, memoryLimits, fsUsage - ] = values; + } = mapValues(metrics, metric => normalizeMetrics(metric).data.result[0].values); const datasets = [ // CPU diff --git a/src/renderer/components/+workloads-pods/pod-charts.tsx b/src/renderer/components/+workloads-pods/pod-charts.tsx index d3aafa0214..cc05aeec55 100644 --- a/src/renderer/components/+workloads-pods/pod-charts.tsx +++ b/src/renderer/components/+workloads-pods/pod-charts.tsx @@ -19,15 +19,16 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import React, { useContext } from "react"; +import { mapValues } from "lodash"; import { observer } from "mobx-react"; -import type { IPodMetrics } from "../../api/endpoints"; -import { BarChart, cpuOptions, memoryOptions } from "../chart"; +import React, { useContext } from "react"; import { isMetricsEmpty, normalizeMetrics } from "../../api/endpoints/metrics.api"; -import { NoMetrics } from "../resource-metrics/no-metrics"; +import { BarChart, cpuOptions, memoryOptions } from "../chart"; import { IResourceMetricsValue, ResourceMetricsContext } from "../resource-metrics"; +import { NoMetrics } from "../resource-metrics/no-metrics"; + import type { WorkloadKubeObject } from "../../api/workload-kube-object"; -import { ThemeStore } from "../../theme.store"; +import type { IPodMetrics } from "../../api/endpoints"; export const podMetricTabs = [ "CPU", @@ -40,27 +41,19 @@ type IContext = IResourceMetricsValue { const { params: { metrics }, tabId, object } = useContext(ResourceMetricsContext); - const { chartCapacityColor } = ThemeStore.getInstance().activeTheme.colors; const id = object.getId(); if (!metrics) return null; if (isMetricsEmpty(metrics)) return ; const options = tabId == 0 ? cpuOptions : memoryOptions; - const values = Object.values(metrics) - .map(normalizeMetrics) - .map(({ data }) => data.result[0].values); - const [ + const { cpuUsage, - cpuRequests, - cpuLimits, memoryUsage, - memoryRequests, - memoryLimits, fsUsage, networkReceive, networkTransmit - ] = values; + } = mapValues(metrics, metric => normalizeMetrics(metric).data.result[0].values); const datasets = [ // CPU @@ -71,20 +64,6 @@ export const PodCharts = observer(() => { tooltip: `Container CPU cores usage`, borderColor: "#3D90CE", data: cpuUsage.map(([x, y]) => ({ x, y })) - }, - { - id: `${id}-cpuRequests`, - label: `Requests`, - tooltip: `Container CPU requests`, - borderColor: "#30b24d", - data: cpuRequests.map(([x, y]) => ({ x, y })) - }, - { - id: `${id}-cpuLimits`, - label: `Limits`, - tooltip: `CPU limits`, - borderColor: chartCapacityColor, - data: cpuLimits.map(([x, y]) => ({ x, y })) } ], // Memory @@ -96,20 +75,6 @@ export const PodCharts = observer(() => { borderColor: "#c93dce", data: memoryUsage.map(([x, y]) => ({ x, y })) }, - { - id: `${id}-memoryRequests`, - label: `Requests`, - tooltip: `Container memory requests`, - borderColor: "#30b24d", - data: memoryRequests.map(([x, y]) => ({ x, y })) - }, - { - id: `${id}-memoryLimits`, - label: `Limits`, - tooltip: `Container memory limits`, - borderColor: chartCapacityColor, - data: memoryLimits.map(([x, y]) => ({ x, y })) - } ], // Network [ diff --git a/src/renderer/components/+workloads-pods/pod-details-container.scss b/src/renderer/components/+workloads-pods/pod-details-container.scss index dc670c4c09..fca42c9433 100644 --- a/src/renderer/components/+workloads-pods/pod-details-container.scss +++ b/src/renderer/components/+workloads-pods/pod-details-container.scss @@ -58,8 +58,4 @@ @include pod-status-colors; } - - .Badge { - white-space: normal; - } } \ No newline at end of file diff --git a/src/renderer/components/+workloads-pods/pod-details-list.scss b/src/renderer/components/+workloads-pods/pod-details-list.scss index 05dd787a2c..04ccd2d05e 100644 --- a/src/renderer/components/+workloads-pods/pod-details-list.scss +++ b/src/renderer/components/+workloads-pods/pod-details-list.scss @@ -28,6 +28,10 @@ &.virtual { height: 500px; } + + .TableHead.sticky { + top: calc(var(--spacing) * -1); + } } .TableCell { diff --git a/src/renderer/components/+workloads-pods/pod-details.tsx b/src/renderer/components/+workloads-pods/pod-details.tsx index 2a7516b816..2bb08deeff 100644 --- a/src/renderer/components/+workloads-pods/pod-details.tsx +++ b/src/renderer/components/+workloads-pods/pod-details.tsx @@ -142,7 +142,7 @@ export class PodDetails extends React.Component { ); diff --git a/src/renderer/components/+workloads-pods/pod-tolerations.tsx b/src/renderer/components/+workloads-pods/pod-tolerations.tsx index 5ca65cc2b3..9fdaef1c81 100644 --- a/src/renderer/components/+workloads-pods/pod-tolerations.tsx +++ b/src/renderer/components/+workloads-pods/pod-tolerations.tsx @@ -35,6 +35,7 @@ enum sortBy { Operator = "operator", Effect = "effect", Seconds = "seconds", + Value = "value", } const sortingCallbacks = { @@ -45,7 +46,7 @@ const sortingCallbacks = { }; const getTableRow = (toleration: IToleration) => { - const { key, operator, effect, tolerationSeconds } = toleration; + const { key, operator, effect, tolerationSeconds, value } = toleration; return ( { > {key} {operator} + {value} {effect} {tolerationSeconds} @@ -74,6 +76,7 @@ export function PodTolerations({ tolerations }: Props) { Key Operator + Value Effect Seconds diff --git a/src/renderer/components/+workloads-pods/pods.store.ts b/src/renderer/components/+workloads-pods/pods.store.ts index f9d5d87464..62a6a720af 100644 --- a/src/renderer/components/+workloads-pods/pods.store.ts +++ b/src/renderer/components/+workloads-pods/pods.store.ts @@ -51,11 +51,9 @@ export class PodsStore extends KubeObjectStore { async loadKubeMetrics(namespace?: string) { try { - const metrics = await podMetricsApi.list({ namespace }); - - this.kubeMetrics.replace(metrics); + this.kubeMetrics.replace(await podMetricsApi.list({ namespace })); } catch (error) { - console.error("loadKubeMetrics failed", error); + console.warn("loadKubeMetrics failed", error); } } diff --git a/src/renderer/components/+workloads-pods/pods.tsx b/src/renderer/components/+workloads-pods/pods.tsx index 807d531b20..d7ef7175e8 100644 --- a/src/renderer/components/+workloads-pods/pods.tsx +++ b/src/renderer/components/+workloads-pods/pods.tsx @@ -127,7 +127,7 @@ export class Pods extends React.Component { { title: "Status", className: "status", sortBy: columnId.status, id: columnId.status }, ]} renderTableContents={(pod: Pod) => [ - , + , , pod.getNs(), this.renderContainersStatus(pod), @@ -145,7 +145,7 @@ export class Pods extends React.Component { ); }), pod.getNodeName() ? - + {pod.getNodeName()} diff --git a/src/renderer/components/+workloads/workloads-mixins.scss b/src/renderer/components/+workloads/workloads-mixins.scss index 61a56f03f8..03c05c2d48 100644 --- a/src/renderer/components/+workloads/workloads-mixins.scss +++ b/src/renderer/components/+workloads/workloads-mixins.scss @@ -98,7 +98,7 @@ $cronjob-condition-color-list: ( @mixin job-condition-bgs { @each $condition, $color in $job-condition-color-list { - &.#{$condition} { + .#{$condition} { color: white; background: $color; } diff --git a/src/renderer/components/app-init/app-init.tsx b/src/renderer/components/app-init/app-init.tsx deleted file mode 100644 index 7d1460bb1e..0000000000 --- a/src/renderer/components/app-init/app-init.tsx +++ /dev/null @@ -1,73 +0,0 @@ -/** - * 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 "./app-init.scss"; - -import React from "react"; -import { render } from "react-dom"; -import { CubeSpinner } from "../spinner"; -import { apiBase } from "../../api"; - -interface Props { - serviceWaitingList?: string[]; -} - -export class AppInit extends React.Component { - static async start(rootElem: HTMLElement) { - render(, rootElem); // show loading indicator asap - await AppInit.readyStateCheck(rootElem); // wait while all good to run - } - - protected static async readyStateCheck(rootElem: HTMLElement) { - const waitingList = await apiBase.get("/ready"); - - if (waitingList.length > 0) { - // update waiting state - render(, rootElem); - - // check again in 1-5 seconds - return new Promise(resolve => { - const timeoutDelay = 1000 + Math.random() * 4000; - - setTimeout(() => resolve(AppInit.readyStateCheck(rootElem)), timeoutDelay); - }); - } - } - - render() { - const { serviceWaitingList = [] } = this.props; - const serviceNames = serviceWaitingList.join(", "); - - return ( -
-
-
Lens - Loading..
- {serviceWaitingList.length > 0 && ( -

- Waiting services to be running: {serviceNames} -

- )} - -
-
- ); - } -} diff --git a/src/renderer/components/app.tsx b/src/renderer/components/app.tsx index 1e1c14fd4c..52c9d5d0c3 100755 --- a/src/renderer/components/app.tsx +++ b/src/renderer/components/app.tsx @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ import React from "react"; -import { observable, makeObservable } from "mobx"; +import { observable, makeObservable, reaction } from "mobx"; import { disposeOnUnmount, observer } from "mobx-react"; import { Redirect, Route, Router, Switch } from "react-router"; import { history } from "../navigation"; @@ -69,6 +69,7 @@ import { Nodes } from "./+nodes"; import { Workloads } from "./+workloads"; import { Config } from "./+config"; import { Storage } from "./+storage"; +import { catalogEntityRegistry } from "../api/catalog-entity-registry"; @observer export class App extends React.Component { @@ -78,6 +79,7 @@ export class App extends React.Component { } static async init() { + catalogEntityRegistry.init(); const frameId = webFrame.routingId; const clusterId = getHostedClusterId(); @@ -86,6 +88,15 @@ export class App extends React.Component { await requestMain(clusterSetFrameIdHandler, clusterId); await getHostedCluster().whenReady; // cluster.activate() is done at this point + + const activeEntityDisposer = reaction(() => catalogEntityRegistry.getById(clusterId), (entity) => { + if (!entity) { + return; + } + catalogEntityRegistry.activeEntity = entity; + activeEntityDisposer(); + }, {fireImmediately: true}); + ExtensionLoader.getInstance().loadOnClusterRenderer(); setTimeout(() => { appEventBus.emit({ diff --git a/src/renderer/components/badge/badge.scss b/src/renderer/components/badge/badge.module.css similarity index 75% rename from src/renderer/components/badge/badge.scss rename to src/renderer/components/badge/badge.module.css index 23f8e99ff0..19dc8c7631 100644 --- a/src/renderer/components/badge/badge.scss +++ b/src/renderer/components/badge/badge.module.css @@ -19,25 +19,41 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -.Badge { +.badge { + position: relative; display: inline-block; - white-space: nowrap; max-width: 100%; +} + +.badge + .badge { + margin-left: 8px; +} + +.badge.interactive:hover { + background-color: var(--mainBackground); + cursor: pointer; +} + +.badge:not(.isExpanded) { + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - - &:not(.flat) { - background: $colorVague; - color: $textColorSecondary; - border-radius: $radius; - padding: .2em .4em; - } - - &.small { - font-size: $font-size-small; - } - - &.clickable { - cursor: pointer; - } +} + +.badge:not(.flat) { + background: var(--colorVague); + border-radius: 3px; + padding: .2em .4em; +} + +.small { + font-size: var(--font-size-small); +} + +.clickable { + cursor: pointer; +} + +.disabled { + opacity: 0.5; } diff --git a/src/renderer/components/badge/badge.tsx b/src/renderer/components/badge/badge.tsx index 14959b04d1..a16b5482c0 100644 --- a/src/renderer/components/badge/badge.tsx +++ b/src/renderer/components/badge/badge.tsx @@ -19,29 +19,84 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import "./badge.scss"; +import styles from "./badge.module.css"; import React from "react"; +import { computed, makeObservable, observable } from "mobx"; +import { observer } from "mobx-react"; import { cssNames } from "../../utils/cssNames"; import { TooltipDecoratorProps, withTooltip } from "../tooltip"; +import { boundMethod } from "../../utils"; export interface BadgeProps extends React.HTMLAttributes, TooltipDecoratorProps { small?: boolean; flat?: boolean; label?: React.ReactNode; + expandable?: boolean; + disabled?: boolean; } +// Common handler for all Badge instances +document.addEventListener("selectionchange", () => { + Badge.badgeMeta.hasTextSelected ||= window.getSelection().toString().trim().length > 0; +}); + @withTooltip +@observer export class Badge extends React.Component { - render() { - const { className, label, small, flat, children, ...elemProps } = this.props; - const clickable = Boolean(this.props.onClick); + static defaultProps: Partial = { + expandable: true + }; - return <> - + static badgeMeta = observable({ + hasTextSelected: false + }); + + @observable.ref elem: HTMLElement; + @observable isExpanded = false; + + constructor(props: BadgeProps) { + super(props); + makeObservable(this); + } + + @computed get isExpandable() { + if (!this.props.expandable) return false; + + return this.elem?.clientWidth < this.elem?.scrollWidth; + } + + @boundMethod + onMouseUp() { + if (!this.isExpandable || Badge.badgeMeta.hasTextSelected) { + Badge.badgeMeta.hasTextSelected = false; + } else { + this.isExpanded = !this.isExpanded; + } + } + + @boundMethod + bindRef(elem: HTMLElement) { + this.elem = elem; + } + + render() { + const { className, label, disabled, small, children, flat, expandable, ...elemProps } = this.props; + const clickable = Boolean(this.props.onClick) || this.isExpandable; + const classNames = cssNames(styles.badge, className, { + [styles.small]: small, + [styles.flat]: flat, + [styles.clickable]: clickable, + [styles.interactive]: this.isExpandable, + [styles.isExpanded]: this.isExpanded, + [styles.disabled]: disabled + }); + + return ( +
{label} {children} - - ; +
+ ); } } diff --git a/src/renderer/components/chart/chart.scss b/src/renderer/components/chart/chart.scss index 3429b251f9..554d62b883 100644 --- a/src/renderer/components/chart/chart.scss +++ b/src/renderer/components/chart/chart.scss @@ -29,7 +29,7 @@ } .legend { - .Badge { + .LegendBadge { background: transparent; transition: background-color 250ms; white-space: normal; diff --git a/src/renderer/components/chart/chart.tsx b/src/renderer/components/chart/chart.tsx index b68ece5523..d456c397d1 100644 --- a/src/renderer/components/chart/chart.tsx +++ b/src/renderer/components/chart/chart.tsx @@ -169,7 +169,7 @@ export class Chart extends React.Component { const labelElem = (title: string, color: string, tooltip?: string) => ( @@ -177,6 +177,7 @@ export class Chart extends React.Component {
)} tooltip={tooltip} + expandable={false} /> ); diff --git a/src/renderer/components/chart/pie-chart.scss b/src/renderer/components/chart/pie-chart.scss index 7034ee28a1..76c098e39d 100644 --- a/src/renderer/components/chart/pie-chart.scss +++ b/src/renderer/components/chart/pie-chart.scss @@ -30,7 +30,7 @@ flex-direction: column; > * { - &.Badge:hover { + &.LegendBadge:hover { background-color: transparent; } diff --git a/src/renderer/components/cluster-manager/bottom-bar.test.tsx b/src/renderer/components/cluster-manager/bottom-bar.test.tsx index bf6c11245e..066a4d3499 100644 --- a/src/renderer/components/cluster-manager/bottom-bar.test.tsx +++ b/src/renderer/components/cluster-manager/bottom-bar.test.tsx @@ -47,7 +47,7 @@ describe("", () => { expect(container).toBeInstanceOf(HTMLElement); }); - it("renders w/o errors when .getItems() returns unexpected (not type complient) data", async () => { + it("renders w/o errors when .getItems() returns unexpected (not type compliant) data", async () => { StatusBarRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => undefined); expect(() => render()).not.toThrow(); StatusBarRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => "hello"); diff --git a/src/renderer/components/cluster-manager/cluster-status.scss b/src/renderer/components/cluster-manager/cluster-status.scss index 1d048e7459..77d6e7693f 100644 --- a/src/renderer/components/cluster-manager/cluster-status.scss +++ b/src/renderer/components/cluster-manager/cluster-status.scss @@ -33,6 +33,15 @@ max-width: 70vw; max-height: 40vh; white-space: pre-line; + + p { + margin-bottom: $margin; + } + } + + .Spinner { + --spinner-size: 38px; + --spinner-border: calc(var(--spinner-size) / 10); } .Icon { diff --git a/src/renderer/components/cluster-manager/cluster-status.tsx b/src/renderer/components/cluster-manager/cluster-status.tsx index 456f606570..366a809b59 100644 --- a/src/renderer/components/cluster-manager/cluster-status.tsx +++ b/src/renderer/components/cluster-manager/cluster-status.tsx @@ -32,7 +32,7 @@ import type { Cluster } from "../../../main/cluster"; import { cssNames, IClassName } from "../../utils"; import { Button } from "../button"; import { Icon } from "../icon"; -import { CubeSpinner } from "../spinner"; +import { Spinner } from "../spinner"; import type { KubeAuthProxyLog } from "../../../main/kube-auth-proxy"; import { navigate } from "../../navigation"; import { entitySettingsURL } from "../../../common/routes"; @@ -100,9 +100,9 @@ export class ClusterStatus extends React.Component { if (!hasErrors || this.isReconnecting) { return ( <> - +
-            

{this.isReconnecting ? "Reconnecting..." : "Connecting..."}

+

{this.isReconnecting ? "Reconnecting" : "Connecting"}…

{authOutput.map(({ data, error }, index) => { return

{data}

; })} diff --git a/src/renderer/components/cluster-settings/cluster-settings.tsx b/src/renderer/components/cluster-settings/cluster-settings.tsx index 2f11883fa0..21081bc5ce 100644 --- a/src/renderer/components/cluster-settings/cluster-settings.tsx +++ b/src/renderer/components/cluster-settings/cluster-settings.tsx @@ -118,3 +118,17 @@ export function MetricsSettings({ entity }: EntitySettingViewProps) { ); } + +export function NodeShellSettings({entity}: EntitySettingViewProps) { + const cluster = getClusterForEntity(entity); + + if(!cluster) { + return null; + } + + return ( +
+ +
+ ); +} diff --git a/src/renderer/components/cluster-settings/components/cluster-accessible-namespaces.tsx b/src/renderer/components/cluster-settings/components/cluster-accessible-namespaces.tsx index 6794646bea..62f426efe1 100644 --- a/src/renderer/components/cluster-settings/components/cluster-accessible-namespaces.tsx +++ b/src/renderer/components/cluster-settings/components/cluster-accessible-namespaces.tsx @@ -25,6 +25,7 @@ import type { Cluster } from "../../../../main/cluster"; import { SubTitle } from "../../layout/sub-title"; import { EditableList } from "../../editable-list"; import { observable, makeObservable } from "mobx"; +import { systemName } from "../../input/input_validators"; interface Props { cluster: Cluster; @@ -49,9 +50,10 @@ export class ClusterAccessibleNamespaces extends React.Component { this.namespaces.add(newNamespace); this.props.cluster.accessibleNamespaces = Array.from(this.namespaces); }} + validators={systemName} items={Array.from(this.namespaces)} - remove={({ oldItem: oldNamesapce }) => { - this.namespaces.delete(oldNamesapce); + remove={({ oldItem: oldNamespace }) => { + this.namespaces.delete(oldNamespace); this.props.cluster.accessibleNamespaces = Array.from(this.namespaces); }} inputTheme="round-black" diff --git a/src/renderer/components/cluster-settings/components/cluster-node-shell-setting.tsx b/src/renderer/components/cluster-settings/components/cluster-node-shell-setting.tsx new file mode 100644 index 0000000000..a5b4300fe3 --- /dev/null +++ b/src/renderer/components/cluster-settings/components/cluster-node-shell-setting.tsx @@ -0,0 +1,112 @@ +/** + * 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 { Cluster } from "../../../../main/cluster"; +import { autorun, makeObservable, observable } from "mobx"; +import { SubTitle } from "../../layout/sub-title"; +import React from "react"; +import { Input } from "../../input/input"; +import { disposeOnUnmount, observer } from "mobx-react"; +import { Icon } from "../../icon/icon"; +import { initialNodeShellImage } from "../../../../common/cluster-store"; + +interface Props { + cluster: Cluster; +} + +@observer +export class ClusterNodeShellSetting extends React.Component { + @observable nodeShellImage = ""; + @observable imagePullSecret = ""; + + constructor(props: Props) { + super(props); + makeObservable(this); + } + + componentDidMount() { + disposeOnUnmount(this, + autorun(() => { + this.nodeShellImage = this.props.cluster.nodeShellImage; + this.imagePullSecret = this.props.cluster.imagePullSecret; + }) + ); + } + + onImageChange = (value: string) => { + this.nodeShellImage = value; + }; + + onSecretChange = (value: string) => { + this.imagePullSecret = value; + }; + + saveImage = () => { + this.props.cluster.preferences.nodeShellImage = this.nodeShellImage; + }; + + saveSecret = () => { + this.props.cluster.preferences.imagePullSecret = this.imagePullSecret; + }; + + resetImage = () => { + this.nodeShellImage = initialNodeShellImage; //revert to default + }; + + clearSecret = () => { + this.imagePullSecret = ""; + }; + + render() { + + return ( + <> +
+ + } + /> + + Node shell image. Used for creating node shell pod. + +
+
+ + } + /> + + Name of a pre-existing secret (optional). Used for pulling image from a private registry. + +
+ + ); + } +} diff --git a/src/renderer/components/cluster-settings/components/cluster-show-metrics.tsx b/src/renderer/components/cluster-settings/components/cluster-show-metrics.tsx index 6544f2325b..effdc16160 100644 --- a/src/renderer/components/cluster-settings/components/cluster-show-metrics.tsx +++ b/src/renderer/components/cluster-settings/components/cluster-show-metrics.tsx @@ -69,13 +69,14 @@ export class ShowMetricsSetting extends React.Component { const tooltipId = `${name}`; return ( - + {name} this.removeMetric(name)} tooltip="Remove" + className="mx-3" /> ); diff --git a/src/renderer/components/cluster-settings/components/index.ts b/src/renderer/components/cluster-settings/components/index.ts index e82425c9c7..beb80269b7 100644 --- a/src/renderer/components/cluster-settings/components/index.ts +++ b/src/renderer/components/cluster-settings/components/index.ts @@ -28,3 +28,4 @@ export * from "./cluster-prometheus-setting"; export * from "./cluster-proxy-setting"; export * from "./cluster-show-metrics"; export * from "./cluster-icon-settings"; +export * from "./cluster-node-shell-setting"; diff --git a/src/renderer/components/dock/__test__/dock-tabs.test.tsx b/src/renderer/components/dock/__test__/dock-tabs.test.tsx index ad27853900..cdc1994f65 100644 --- a/src/renderer/components/dock/__test__/dock-tabs.test.tsx +++ b/src/renderer/components/dock/__test__/dock-tabs.test.tsx @@ -22,23 +22,27 @@ import React from "react"; import { fireEvent, render } from "@testing-library/react"; import "@testing-library/jest-dom/extend-expect"; +import fse from "fs-extra"; import { DockTabs } from "../dock-tabs"; -import { dockStore, IDockTab, TabKind } from "../dock.store"; +import { dockStore, DockTab, TabKind } from "../dock.store"; import { noop } from "../../../utils"; +import { ThemeStore } from "../../../theme.store"; +import { TerminalStore } from "../terminal.store"; +import { UserStore } from "../../../../common/user-store"; jest.mock("electron", () => ({ app: { - getPath: () => "/foo", + getPath: () => "tmp", }, })); -const initialTabs: IDockTab[] = [ - { id: "terminal", kind: TabKind.TERMINAL, title: "Terminal" }, - { id: "create", kind: TabKind.CREATE_RESOURCE, title: "Create resource" }, - { id: "edit", kind: TabKind.EDIT_RESOURCE, title: "Edit resource" }, - { id: "install", kind: TabKind.INSTALL_CHART, title: "Install chart" }, - { id: "logs", kind: TabKind.POD_LOGS, title: "Logs" }, +const initialTabs: DockTab[] = [ + { id: "terminal", kind: TabKind.TERMINAL, title: "Terminal", pinned: false, }, + { id: "create", kind: TabKind.CREATE_RESOURCE, title: "Create resource", pinned: false, }, + { id: "edit", kind: TabKind.EDIT_RESOURCE, title: "Edit resource", pinned: false, }, + { id: "install", kind: TabKind.INSTALL_CHART, title: "Install chart", pinned: false, }, + { id: "logs", kind: TabKind.POD_LOGS, title: "Logs", pinned: false, }, ]; const getComponent = () => ( @@ -55,10 +59,20 @@ const getTabKinds = () => dockStore.tabs.map(tab => tab.kind); describe("", () => { beforeEach(async () => { + UserStore.createInstance(); + ThemeStore.createInstance(); + TerminalStore.createInstance(); await dockStore.whenReady; dockStore.tabs = initialTabs; }); + afterEach(() => { + ThemeStore.resetInstance(); + TerminalStore.resetInstance(); + UserStore.resetInstance(); + fse.remove("tmp"); + }); + it("renders w/o errors", () => { const { container } = renderTabs(); @@ -142,7 +156,7 @@ describe("", () => { it("disables 'Close All' & 'Close Other' items if only 1 tab available", () => { dockStore.tabs = [{ - id: "terminal", kind: TabKind.TERMINAL, title: "Terminal" + id: "terminal", kind: TabKind.TERMINAL, title: "Terminal", pinned: false, }]; const { container, getByText } = renderTabs(); const tab = container.querySelector(".Tab"); @@ -157,8 +171,8 @@ describe("", () => { it("disables 'Close To The Right' item if last tab clicked", () => { dockStore.tabs = [ - { id: "terminal", kind: TabKind.TERMINAL, title: "Terminal" }, - { id: "logs", kind: TabKind.POD_LOGS, title: "Pod Logs" }, + { id: "terminal", kind: TabKind.TERMINAL, title: "Terminal", pinned: false, }, + { id: "logs", kind: TabKind.POD_LOGS, title: "Pod Logs", pinned: false, }, ]; const { container, getByText } = renderTabs(); const tab = container.querySelectorAll(".Tab")[1]; diff --git a/src/renderer/components/dock/__test__/log-tab.store.test.ts b/src/renderer/components/dock/__test__/log-tab.store.test.ts index 3e1542ed6a..da7d0b1052 100644 --- a/src/renderer/components/dock/__test__/log-tab.store.test.ts +++ b/src/renderer/components/dock/__test__/log-tab.store.test.ts @@ -20,14 +20,18 @@ */ import { podsStore } from "../../+workloads-pods/pods.store"; +import { UserStore } from "../../../../common/user-store"; import { Pod } from "../../../api/endpoints"; +import { ThemeStore } from "../../../theme.store"; import { dockStore } from "../dock.store"; import { logTabStore } from "../log-tab.store"; +import { TerminalStore } from "../terminal.store"; import { deploymentPod1, deploymentPod2, deploymentPod3, dockerPod } from "./pod.mock"; +import fse from "fs-extra"; jest.mock("electron", () => ({ app: { - getPath: () => "/foo", + getPath: () => "tmp", }, })); @@ -36,9 +40,19 @@ podsStore.items.push(new Pod(deploymentPod1)); podsStore.items.push(new Pod(deploymentPod2)); describe("log tab store", () => { + beforeEach(() => { + UserStore.createInstance(); + ThemeStore.createInstance(); + TerminalStore.createInstance(); + }); + afterEach(() => { logTabStore.reset(); dockStore.reset(); + UserStore.resetInstance(); + ThemeStore.resetInstance(); + TerminalStore.resetInstance(); + fse.remove("tmp"); }); it("creates log tab without sibling pods", () => { diff --git a/src/renderer/components/dock/__test__/to-bottom.test.tsx b/src/renderer/components/dock/__test__/to-bottom.test.tsx new file mode 100644 index 0000000000..c19a7b19e3 --- /dev/null +++ b/src/renderer/components/dock/__test__/to-bottom.test.tsx @@ -0,0 +1,53 @@ +/** + * 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 React from "react"; +import "@testing-library/jest-dom/extend-expect"; +import { fireEvent, render } from "@testing-library/react"; +import { ToBottom } from "../to-bottom"; +import { noop } from "../../../utils"; + +describe("", () => { + it("renders w/o errors", () => { + const { container } = render(); + + expect(container).toBeInstanceOf(HTMLElement); + }); + + it("has 'To bottom' label", () => { + const { getByText } = render(); + + expect(getByText("To bottom")).toBeInTheDocument(); + }); + + it("has a arrow down icon", () => { + const { getByText } = render(); + + expect(getByText("expand_more")).toBeInTheDocument(); + }); + + it("fires an onclick event", () => { + const callback = jest.fn(); + const { getByText } = render(); + + fireEvent.click(getByText("To bottom")); + expect(callback).toBeCalled(); + }); +}); diff --git a/src/renderer/components/dock/create-resource.store.ts b/src/renderer/components/dock/create-resource.store.ts index 2e049deacf..f0b6b41154 100644 --- a/src/renderer/components/dock/create-resource.store.ts +++ b/src/renderer/components/dock/create-resource.store.ts @@ -27,7 +27,7 @@ import filehound from "filehound"; import { watch } from "chokidar"; import { autoBind } from "../../utils"; import { DockTabStore } from "./dock-tab.store"; -import { dockStore, IDockTab, TabKind } from "./dock.store"; +import { dockStore, DockTabCreateSpecific, TabKind } from "./dock.store"; export class CreateResourceStore extends DockTabStore { constructor() { @@ -81,10 +81,10 @@ export class CreateResourceStore extends DockTabStore { export const createResourceStore = new CreateResourceStore(); -export function createResourceTab(tabParams: Partial = {}) { +export function createResourceTab(tabParams: DockTabCreateSpecific = {}) { return dockStore.createTab({ - kind: TabKind.CREATE_RESOURCE, title: "Create resource", - ...tabParams + ...tabParams, + kind: TabKind.CREATE_RESOURCE, }); } diff --git a/src/renderer/components/dock/create-resource.tsx b/src/renderer/components/dock/create-resource.tsx index 413cbebc6d..f3423a7771 100644 --- a/src/renderer/components/dock/create-resource.tsx +++ b/src/renderer/components/dock/create-resource.tsx @@ -30,7 +30,7 @@ import { observable, makeObservable } from "mobx"; import { observer } from "mobx-react"; import { cssNames } from "../../utils"; import { createResourceStore } from "./create-resource.store"; -import type { IDockTab } from "./dock.store"; +import type { DockTab } from "./dock.store"; import { EditorPanel } from "./editor-panel"; import { InfoPanel } from "./info-panel"; import { resourceApplierApi } from "../../api/endpoints/resource-applier.api"; @@ -39,7 +39,7 @@ import { Notifications } from "../notifications"; interface Props { className?: string; - tab: IDockTab; + tab: DockTab; } @observer diff --git a/src/renderer/components/dock/dock-tab.tsx b/src/renderer/components/dock/dock-tab.tsx index dd4ff63824..a42a2dd64b 100644 --- a/src/renderer/components/dock/dock-tab.tsx +++ b/src/renderer/components/dock/dock-tab.tsx @@ -24,13 +24,13 @@ import "./dock-tab.scss"; import React from "react"; import { observer } from "mobx-react"; import { boundMethod, cssNames, prevDefault, isMiddleClick } from "../../utils"; -import { dockStore, IDockTab } from "./dock.store"; +import { dockStore, DockTab as DockTabModel } from "./dock.store"; import { Tab, TabProps } from "../tabs"; import { Icon } from "../icon"; import { Menu, MenuItem } from "../menu"; import { observable, makeObservable } from "mobx"; -export interface DockTabProps extends TabProps { +export interface DockTabProps extends TabProps { moreActions?: React.ReactNode; } diff --git a/src/renderer/components/dock/dock-tabs.tsx b/src/renderer/components/dock/dock-tabs.tsx index 6064259cbc..372ef100d9 100644 --- a/src/renderer/components/dock/dock-tabs.tsx +++ b/src/renderer/components/dock/dock-tabs.tsx @@ -24,19 +24,19 @@ import React, { Fragment } from "react"; import { Icon } from "../icon"; import { Tabs } from "../tabs/tabs"; import { DockTab } from "./dock-tab"; -import type { IDockTab } from "./dock.store"; +import type { DockTab as DockTabModel } from "./dock.store"; import { TabKind } from "./dock.store"; import { TerminalTab } from "./terminal-tab"; interface Props { - tabs: IDockTab[] + tabs: DockTabModel[] autoFocus: boolean - selectedTab: IDockTab - onChangeTab: (tab: IDockTab) => void + selectedTab: DockTabModel + onChangeTab: (tab: DockTabModel) => void } export const DockTabs = ({ tabs, autoFocus, selectedTab, onChangeTab }: Props) => { - const renderTab = (tab?: IDockTab) => { + const renderTab = (tab?: DockTabModel) => { if (!tab) { return null; } diff --git a/src/renderer/components/dock/dock.store.ts b/src/renderer/components/dock/dock.store.ts index 2c9789ce41..eb5902d7db 100644 --- a/src/renderer/components/dock/dock.store.ts +++ b/src/renderer/components/dock/dock.store.ts @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import MD5 from "crypto-js/md5"; +import * as uuid from "uuid"; import { action, computed, IReactionOptions, makeObservable, observable, reaction } from "mobx"; import { autoBind, createStorage } from "../../utils"; import throttle from "lodash/throttle"; @@ -35,16 +35,55 @@ export enum TabKind { POD_LOGS = "pod-logs", } -export interface IDockTab { +/** + * This is the storage model for dock tabs. + * + * All fields are required. + */ +export type DockTab = Required; + +/** + * These are the arguments for creating a new Tab on the dock + */ +export interface DockTabCreate { + /** + * The ID of the tab for reference purposes. + */ id?: TabId; + + /** + * What kind of dock tab it is + */ kind: TabKind; + + /** + * The tab's title, defaults to `kind` + */ title?: string; - pinned?: boolean; // not closable + + /** + * If true then the dock entry will take up the whole view and will not be + * closable. + */ + pinned?: boolean; + + /** + * Extra fields are supported. + */ + [key: string]: any; } +/** + * This type is for function which specifically create a single type of dock tab. + * + * That way users should get a type error if they try and specify a `kind` + * themselves. + */ +export type DockTabCreateSpecific = Omit; + export interface DockStorageState { height: number; - tabs: IDockTab[]; + tabs: DockTab[]; selectedTabId?: TabId; isOpen?: boolean; } @@ -62,7 +101,7 @@ export class DockStore implements DockStorageState { private storage = createStorage("dock", { height: 300, tabs: [ - { id: "terminal", kind: TabKind.TERMINAL, title: "Terminal" }, + { id: "terminal", kind: TabKind.TERMINAL, title: "Terminal", pinned: false }, ], }); @@ -88,16 +127,21 @@ export class DockStore implements DockStorageState { }); } - get tabs(): IDockTab[] { + get tabs(): DockTab[] { return this.storage.get().tabs; } - set tabs(tabs: IDockTab[]) { + set tabs(tabs: DockTab[]) { this.storage.merge({ tabs }); } get selectedTabId(): TabId | undefined { - return this.storage.get().selectedTabId || this.tabs[0]?.id; + return this.storage.get().selectedTabId + || ( + this.tabs.length > 0 + ? this.tabs[0]?.id + : undefined + ); } set selectedTabId(tabId: TabId) { @@ -191,15 +235,31 @@ export class DockStore implements DockStorageState { } @action - createTab(anonTab: IDockTab, addNumber = true): IDockTab { - const tabId = MD5(Math.random().toString() + Date.now()).toString(); - const tab: IDockTab = { id: tabId, ...anonTab }; + createTab(rawTabDesc: DockTabCreate, addNumber = true): DockTab { + const { + id = uuid.v4(), + kind, + pinned = false, + ...restOfTabFields + } = rawTabDesc; + let { title = kind } = rawTabDesc; if (addNumber) { - const tabNumber = this.getNewTabNumber(tab.kind); + const tabNumber = this.getNewTabNumber(kind); - if (tabNumber > 1) tab.title += ` (${tabNumber})`; + if (tabNumber > 1) { + title += ` (${tabNumber})`; + } } + + const tab: DockTab = { + ...restOfTabFields, + id, + kind, + pinned, + title + }; + this.tabs.push(tab); this.selectTab(tab.id); this.open(); @@ -222,9 +282,9 @@ export class DockStore implements DockStorageState { if (newTab?.kind === TabKind.TERMINAL) { // close the dock when selected sibling inactive terminal tab - const { terminalStore } = await import("./terminal.store"); + const { TerminalStore } = await import("./terminal.store"); - if (!terminalStore.isConnected(newTab.id)) this.close(); + if (!TerminalStore.getInstance(false)?.isConnected(newTab.id)) this.close(); } this.selectTab(newTab.id); } else { @@ -234,7 +294,7 @@ export class DockStore implements DockStorageState { } } - closeTabs(tabs: IDockTab[]) { + closeTabs(tabs: DockTab[]) { tabs.forEach(tab => this.closeTab(tab.id)); } diff --git a/src/renderer/components/dock/dock.tsx b/src/renderer/components/dock/dock.tsx index c3c4ca520b..a8cea06d7b 100644 --- a/src/renderer/components/dock/dock.tsx +++ b/src/renderer/components/dock/dock.tsx @@ -32,7 +32,7 @@ import { ResizeDirection, ResizingAnchor } from "../resizing-anchor"; import { CreateResource } from "./create-resource"; import { createResourceTab } from "./create-resource.store"; import { DockTabs } from "./dock-tabs"; -import { dockStore, IDockTab, TabKind } from "./dock.store"; +import { dockStore, DockTab, TabKind } from "./dock.store"; import { EditResource } from "./edit-resource"; import { InstallChart } from "./install-chart"; import { Logs } from "./logs"; @@ -62,14 +62,14 @@ export class Dock extends React.Component { } }; - onChangeTab = (tab: IDockTab) => { + onChangeTab = (tab: DockTab) => { const { open, selectTab } = dockStore; open(); selectTab(tab.id); }; - renderTab(tab: IDockTab) { + renderTab(tab: DockTab) { switch (tab.kind) { case TabKind.CREATE_RESOURCE: return ; diff --git a/src/renderer/components/dock/edit-resource.store.ts b/src/renderer/components/dock/edit-resource.store.ts index 9b10491901..7931d86451 100644 --- a/src/renderer/components/dock/edit-resource.store.ts +++ b/src/renderer/components/dock/edit-resource.store.ts @@ -22,7 +22,7 @@ import { autoBind, noop } from "../../utils"; import { DockTabStore } from "./dock-tab.store"; import { autorun, IReactionDisposer } from "mobx"; -import { dockStore, IDockTab, TabId, TabKind } from "./dock.store"; +import { dockStore, DockTab, DockTabCreateSpecific, TabId, TabKind } from "./dock.store"; import type { KubeObject } from "../../api/kube-object"; import { apiManager } from "../../api/api-manager"; import type { KubeObjectStore } from "../../kube-object.store"; @@ -96,7 +96,7 @@ export class EditResourceStore extends DockTabStore { return this.getData(tabId)?.resource; } - getTabByResource(object: KubeObject): IDockTab { + getTabByResource(object: KubeObject): DockTab { const [tabId] = Array.from(this.data).find(([, { resource }]) => { return object.selfLink === resource; }) || []; @@ -115,7 +115,7 @@ export class EditResourceStore extends DockTabStore { export const editResourceStore = new EditResourceStore(); -export function editResourceTab(object: KubeObject, tabParams: Partial = {}) { +export function editResourceTab(object: KubeObject, tabParams: DockTabCreateSpecific = {}) { // use existing tab if already opened let tab = editResourceStore.getTabByResource(object); @@ -128,8 +128,8 @@ export function editResourceTab(object: KubeObject, tabParams: Partial if (!tab) { tab = dockStore.createTab({ title: `${object.kind}: ${object.getName()}`, + ...tabParams, kind: TabKind.EDIT_RESOURCE, - ...tabParams }, false); editResourceStore.setData(tab.id, { resource: object.selfLink, diff --git a/src/renderer/components/dock/edit-resource.tsx b/src/renderer/components/dock/edit-resource.tsx index 455bbda61a..0496ef3104 100644 --- a/src/renderer/components/dock/edit-resource.tsx +++ b/src/renderer/components/dock/edit-resource.tsx @@ -25,7 +25,7 @@ import React from "react"; import { action, computed, makeObservable, observable } from "mobx"; import { observer } from "mobx-react"; import jsYaml from "js-yaml"; -import type { IDockTab } from "./dock.store"; +import type { DockTab } from "./dock.store"; import { cssNames } from "../../utils"; import { editResourceStore } from "./edit-resource.store"; import { InfoPanel } from "./info-panel"; @@ -36,7 +36,7 @@ import type { KubeObject } from "../../api/kube-object"; interface Props { className?: string; - tab: IDockTab; + tab: DockTab; } @observer diff --git a/src/renderer/components/dock/info-panel.scss b/src/renderer/components/dock/info-panel.scss index 24b727f5d8..0933b236c1 100644 --- a/src/renderer/components/dock/info-panel.scss +++ b/src/renderer/components/dock/info-panel.scss @@ -30,10 +30,6 @@ margin-right: var(--padding); } - .Badge { - background-color: var(--dockBadgeBackground); - } - > .controls { white-space: nowrap; flex: 1 1; diff --git a/src/renderer/components/dock/install-chart.store.ts b/src/renderer/components/dock/install-chart.store.ts index 20e0a7a0ac..f7ae6930e6 100644 --- a/src/renderer/components/dock/install-chart.store.ts +++ b/src/renderer/components/dock/install-chart.store.ts @@ -20,7 +20,7 @@ */ import { action, autorun, makeObservable } from "mobx"; -import { dockStore, IDockTab, TabId, TabKind } from "./dock.store"; +import { dockStore, DockTabCreateSpecific, TabId, TabKind } from "./dock.store"; import { DockTabStore } from "./dock-tab.store"; import { getChartDetails, getChartValues, HelmChart } from "../../api/endpoints/helm-charts.api"; import type { IReleaseUpdateDetails } from "../../api/endpoints/helm-releases.api"; @@ -98,12 +98,12 @@ export class InstallChartStore extends DockTabStore { export const installChartStore = new InstallChartStore(); -export function createInstallChartTab(chart: HelmChart, tabParams: Partial = {}) { +export function createInstallChartTab(chart: HelmChart, tabParams: DockTabCreateSpecific = {}) { const { name, repo, version } = chart; const tab = dockStore.createTab({ - kind: TabKind.INSTALL_CHART, title: `Helm Install: ${repo}/${name}`, - ...tabParams + ...tabParams, + kind: TabKind.INSTALL_CHART, }, false); installChartStore.setData(tab.id, { diff --git a/src/renderer/components/dock/install-chart.tsx b/src/renderer/components/dock/install-chart.tsx index 69e0313eec..b0113018ea 100644 --- a/src/renderer/components/dock/install-chart.tsx +++ b/src/renderer/components/dock/install-chart.tsx @@ -24,7 +24,7 @@ import "./install-chart.scss"; import React, { Component } from "react"; import { observable, makeObservable } from "mobx"; import { observer } from "mobx-react"; -import { dockStore, IDockTab } from "./dock.store"; +import { dockStore, DockTab } from "./dock.store"; import { InfoPanel } from "./info-panel"; import { Badge } from "../badge"; import { NamespaceSelect } from "../+namespaces/namespace-select"; @@ -42,7 +42,7 @@ import { navigate } from "../../navigation"; import { releaseURL } from "../../../common/routes"; interface Props { - tab: IDockTab; + tab: DockTab; } @observer diff --git a/src/renderer/components/dock/log-list.scss b/src/renderer/components/dock/log-list.scss index edae55ad61..3bf5bfd833 100644 --- a/src/renderer/components/dock/log-list.scss +++ b/src/renderer/components/dock/log-list.scss @@ -84,17 +84,4 @@ overflow-x: hidden!important; // fixing scroll to bottom issues in PodLogs } } - - .JumpToBottom { - position: absolute; - right: 30px; - padding: 4px 9px; - border-radius: 20px; - z-index: 2; - top: 20px; - - .Icon { - --size: calc(var(--unit) * 2); - } - } } diff --git a/src/renderer/components/dock/log-list.tsx b/src/renderer/components/dock/log-list.tsx index 9437a854e2..211e66f936 100644 --- a/src/renderer/components/dock/log-list.tsx +++ b/src/renderer/components/dock/log-list.tsx @@ -25,20 +25,19 @@ import React from "react"; import AnsiUp from "ansi_up"; import DOMPurify from "dompurify"; import debounce from "lodash/debounce"; -import { action, computed, observable, makeObservable } from "mobx"; -import { observer } from "mobx-react"; +import { action, computed, observable, makeObservable, reaction } from "mobx"; +import { disposeOnUnmount, observer } from "mobx-react"; import moment from "moment-timezone"; import type { Align, ListOnScrollProps } from "react-window"; import { SearchStore, searchStore } from "../../../common/search-store"; import { UserStore } from "../../../common/user-store"; -import { cssNames } from "../../utils"; -import { Button } from "../button"; -import { Icon } from "../icon"; +import { boundMethod, cssNames } from "../../utils"; import { Spinner } from "../spinner"; import { VirtualList } from "../virtual-list"; import { logStore } from "./log.store"; import { logTabStore } from "./log-tab.store"; +import { ToBottom } from "./to-bottom"; interface Props { logs: string[] @@ -64,41 +63,44 @@ export class LogList extends React.Component { } componentDidMount() { - this.scrollToBottom(); + disposeOnUnmount(this, [ + reaction(() => this.props.logs, this.onLogsInitialLoad), + reaction(() => this.props.logs, this.onLogsUpdate), + reaction(() => this.props.logs, this.onUserScrolledUp) + ]); } - componentDidUpdate(prevProps: Props) { - const { logs, id } = this.props; - - if (id != prevProps.id) { + @boundMethod + onLogsInitialLoad(logs: string[], prevLogs: string[]) { + if (!prevLogs.length && logs.length) { this.isLastLineVisible = true; - - return; } + } - if (logs == prevProps.logs || !this.virtualListDiv.current) return; + @boundMethod + onLogsUpdate() { + if (this.isLastLineVisible) { + setTimeout(() => { + this.scrollToBottom(); + }, 500); // Giving some time to VirtualList to prepare its outerRef (this.virtualListDiv) element + } + } - const newLogsLoaded = prevProps.logs.length < logs.length; + @boundMethod + onUserScrolledUp(logs: string[], prevLogs: string[]) { + if (!this.virtualListDiv.current) return; + + const newLogsAdded = prevLogs.length < logs.length; const scrolledToBeginning = this.virtualListDiv.current.scrollTop === 0; - if (this.isLastLineVisible || prevProps.logs.length == 0) { - this.scrollToBottom(); // Scroll down to keep user watching/reading experience - - return; - } - - if (scrolledToBeginning && newLogsLoaded) { - const firstLineContents = prevProps.logs[0]; + if (newLogsAdded && scrolledToBeginning) { + const firstLineContents = prevLogs[0]; const lineToScroll = this.props.logs.findIndex((value) => value == firstLineContents); if (lineToScroll !== -1) { this.scrollToItem(lineToScroll, "start"); } } - - if (!logs.length) { - this.isLastLineVisible = false; - } } /** @@ -114,7 +116,7 @@ export class LogList extends React.Component { return this.props.logs .map(log => logStore.splitOutTimestamp(log)) - .map(([logTimestamp, log]) => (`${moment.tz(logTimestamp, UserStore.getInstance().localeTimezone).format()}${log}`)); + .map(([logTimestamp, log]) => (`${logTimestamp && moment.tz(logTimestamp, UserStore.getInstance().localeTimezone).format()}${log}`)); } /** @@ -158,7 +160,6 @@ export class LogList extends React.Component { } }; - @action scrollToBottom = () => { if (!this.virtualListDiv.current) return; this.virtualListDiv.current.scrollTop = this.virtualListDiv.current.scrollHeight; @@ -169,7 +170,6 @@ export class LogList extends React.Component { }; onScroll = (props: ListOnScrollProps) => { - if (!this.virtualListDiv.current) return; this.isLastLineVisible = false; this.onScrollDebounced(props); }; @@ -264,29 +264,9 @@ export class LogList extends React.Component { className="box grow" /> {this.isJumpButtonVisible && ( - + )}
); } } - -interface JumpToBottomProps { - onClick: () => void -} - -const JumpToBottom = ({ onClick }: JumpToBottomProps) => { - return ( - - ); -}; diff --git a/src/renderer/components/dock/log-tab.store.ts b/src/renderer/components/dock/log-tab.store.ts index 560cea5cff..bd7d61539b 100644 --- a/src/renderer/components/dock/log-tab.store.ts +++ b/src/renderer/components/dock/log-tab.store.ts @@ -26,7 +26,7 @@ import { podsStore } from "../+workloads-pods/pods.store"; import { IPodContainer, Pod } from "../../api/endpoints"; import type { WorkloadKubeObject } from "../../api/workload-kube-object"; import { DockTabStore } from "./dock-tab.store"; -import { dockStore, IDockTab, TabKind } from "./dock.store"; +import { dockStore, DockTabCreateSpecific, TabKind } from "./dock.store"; export interface LogTabData { pods: Pod[]; @@ -90,10 +90,10 @@ export class LogTabStore extends DockTabStore { dockStore.renameTab(tabId, `Pod ${selectedPod.metadata.name}`); } - private createDockTab(tabParams: Partial) { + private createDockTab(tabParams: DockTabCreateSpecific) { dockStore.createTab({ + ...tabParams, kind: TabKind.POD_LOGS, - ...tabParams }, false); } @@ -108,8 +108,10 @@ export class LogTabStore extends DockTabStore { }); } - private updateTabsData() { - this.data.forEach((tabData, tabId) => { + private async updateTabsData() { + const promises: Promise[] = []; + + for (const [tabId, tabData] of this.data) { const pod = new Pod(tabData.selectedPod); const pods = podsStore.getPodsByOwnerId(pod.getOwnerRefs()[0]?.uid); const isSelectedPodInList = pods.find(item => item.getId() == pod.getId()); @@ -126,14 +128,16 @@ export class LogTabStore extends DockTabStore { this.renameTab(tabId); } else { - this.closeTab(tabId); + promises.push(this.closeTab(tabId)); } - }); + } + + await Promise.all(promises); } - private closeTab(tabId: string) { + private async closeTab(tabId: string) { this.clearData(tabId); - dockStore.closeTab(tabId); + await dockStore.closeTab(tabId); } } diff --git a/src/renderer/components/dock/log.store.ts b/src/renderer/components/dock/log.store.ts index 91682e8c0c..710ed3a690 100644 --- a/src/renderer/components/dock/log.store.ts +++ b/src/renderer/components/dock/log.store.ts @@ -183,7 +183,7 @@ export class LogStore { const extraction = /^(\d+\S+)(.*)/m.exec(logs); if (!extraction || extraction.length < 3) { - return ["", ""]; + return ["", logs]; } return [extraction[1], extraction[2]]; diff --git a/src/renderer/components/dock/logs.tsx b/src/renderer/components/dock/logs.tsx index 4649d28eeb..6c5c8d115f 100644 --- a/src/renderer/components/dock/logs.tsx +++ b/src/renderer/components/dock/logs.tsx @@ -25,7 +25,7 @@ import { disposeOnUnmount, observer } from "mobx-react"; import { searchStore } from "../../../common/search-store"; import { boundMethod } from "../../utils"; -import type { IDockTab } from "./dock.store"; +import type { DockTab } from "./dock.store"; import { InfoPanel } from "./info-panel"; import { LogResourceSelector } from "./log-resource-selector"; import { LogList } from "./log-list"; @@ -36,7 +36,7 @@ import { LogTabData, logTabStore } from "./log-tab.store"; interface Props { className?: string - tab: IDockTab + tab: DockTab } @observer diff --git a/src/renderer/components/dock/terminal-window.tsx b/src/renderer/components/dock/terminal-window.tsx index d5b4d4a53b..652086d811 100644 --- a/src/renderer/components/dock/terminal-window.tsx +++ b/src/renderer/components/dock/terminal-window.tsx @@ -25,14 +25,14 @@ import React from "react"; import { reaction } from "mobx"; import { disposeOnUnmount, observer } from "mobx-react"; import { cssNames } from "../../utils"; -import type { IDockTab } from "./dock.store"; +import type { DockTab } from "./dock.store"; import type { Terminal } from "./terminal"; import { terminalStore } from "./terminal.store"; import { ThemeStore } from "../../theme.store"; interface Props { className?: string; - tab: IDockTab; + tab: DockTab; } @observer diff --git a/src/renderer/components/dock/terminal.store.ts b/src/renderer/components/dock/terminal.store.ts index 7f0396336e..dd5251bac2 100644 --- a/src/renderer/components/dock/terminal.store.ts +++ b/src/renderer/components/dock/terminal.store.ts @@ -19,30 +19,32 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { autorun, observable } from "mobx"; -import { autoBind } from "../../utils"; +import { autorun, observable, when } from "mobx"; +import { autoBind, noop, Singleton } from "../../utils"; import { Terminal } from "./terminal"; import { TerminalApi } from "../../api/terminal-api"; -import { dockStore, IDockTab, TabId, TabKind } from "./dock.store"; +import { dockStore, DockTab, DockTabCreateSpecific, TabId, TabKind } from "./dock.store"; import { WebSocketApiState } from "../../api/websocket-api"; +import { Notifications } from "../notifications"; -export interface ITerminalTab extends IDockTab { +export interface ITerminalTab extends DockTab { node?: string; // activate node shell mode } -export function createTerminalTab(tabParams: Partial = {}) { +export function createTerminalTab(tabParams: DockTabCreateSpecific = {}) { return dockStore.createTab({ - kind: TabKind.TERMINAL, title: `Terminal`, - ...tabParams + ...tabParams, + kind: TabKind.TERMINAL, }); } -export class TerminalStore { +export class TerminalStore extends Singleton { protected terminals = new Map(); protected connections = observable.map(); constructor() { + super(); autoBind(this); // connect active tab @@ -63,7 +65,7 @@ export class TerminalStore { }); } - async connect(tabId: TabId) { + connect(tabId: TabId) { if (this.isConnected(tabId)) { return; } @@ -103,18 +105,36 @@ export class TerminalStore { return this.connections.get(tabId)?.readyState === WebSocketApiState.CLOSED; } - sendCommand(command: string, options: { enter?: boolean; newTab?: boolean; tabId?: TabId } = {}) { + async sendCommand(command: string, options: { enter?: boolean; newTab?: boolean; tabId?: TabId } = {}) { const { enter, newTab, tabId } = options; - const { selectTab, getTabById } = dockStore; - const tab = tabId && getTabById(tabId); - if (tab) selectTab(tabId); - if (newTab) createTerminalTab(); + if (tabId) { + dockStore.selectTab(tabId); + } + + if (newTab) { + const tab = createTerminalTab(); + + await when(() => this.connections.has(tab.id)); + + const rcIsFinished = when(() => this.connections.get(tab.id).shellRunCommandsFinished); + const notifyVeryLong = setTimeout(() => { + rcIsFinished.cancel(); + Notifications.info("Terminal shell is taking a long time to complete startup. Please check your .rc file. Bypassing shell completion check.", { + timeout: 4_000, + }); + }, 10_000); + + await rcIsFinished.catch(noop); + clearTimeout(notifyVeryLong); + } const terminalApi = this.connections.get(dockStore.selectedTabId); if (terminalApi) { terminalApi.sendCommand(command + (enter ? "\r" : "")); + } else { + console.warn("The selected tab is does not have a connection. Cannot send command.", { tabId: dockStore.selectedTabId, command }); } } @@ -129,4 +149,24 @@ export class TerminalStore { } } -export const terminalStore = new TerminalStore(); +/** + * @deprecated use `TerminalStore.getInstance()` instead + */ +export const terminalStore = new Proxy({}, { + get(target, p) { + if (p === "$$typeof") { + return "TerminalStore"; + } + + const ts = TerminalStore.getInstance(); + const res = (ts as any)?.[p]; + + if (typeof res === "function") { + return function(...args: any[]) { + return res.apply(ts, args); + }; + } + + return res; + }, +}) as TerminalStore; diff --git a/src/renderer/components/spinner/cube-spinner.tsx b/src/renderer/components/dock/to-bottom.tsx similarity index 56% rename from src/renderer/components/spinner/cube-spinner.tsx rename to src/renderer/components/dock/to-bottom.tsx index 80b9fea57f..f95ff6bebc 100644 --- a/src/renderer/components/spinner/cube-spinner.tsx +++ b/src/renderer/components/dock/to-bottom.tsx @@ -18,34 +18,21 @@ * 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 "./cube-spinner.scss"; import React from "react"; -import { cssNames } from "../../utils"; +import { Icon } from "../icon"; -export interface CubeSpinnerProps { - className?: string; - center?: boolean; -} - -export class CubeSpinner extends React.Component { - render() { - const { className, center } = this.props; - - return ( -
-
-
-
-
-
-
-
-
-
-
-
-
- ); - } +export function ToBottom({ onClick }: { onClick: () => void }) { + return ( + + ); } diff --git a/src/renderer/components/dock/upgrade-chart.store.ts b/src/renderer/components/dock/upgrade-chart.store.ts index 5aafdfb0ea..4a43ae4ab1 100644 --- a/src/renderer/components/dock/upgrade-chart.store.ts +++ b/src/renderer/components/dock/upgrade-chart.store.ts @@ -20,7 +20,7 @@ */ import { action, autorun, computed, IReactionDisposer, reaction, makeObservable } from "mobx"; -import { dockStore, IDockTab, TabId, TabKind } from "./dock.store"; +import { dockStore, DockTab, DockTabCreateSpecific, TabId, TabKind } from "./dock.store"; import { DockTabStore } from "./dock-tab.store"; import { getReleaseValues, HelmRelease } from "../../api/endpoints/helm-releases.api"; import { releaseStore } from "../+apps-releases/release.store"; @@ -120,14 +120,14 @@ export class UpgradeChartStore extends DockTabStore { this.values.setData(tabId, values); } - getTabByRelease(releaseName: string): IDockTab { + getTabByRelease(releaseName: string): DockTab { return dockStore.getTabById(this.releaseNameReverseLookup.get(releaseName)); } } export const upgradeChartStore = new UpgradeChartStore(); -export function createUpgradeChartTab(release: HelmRelease, tabParams: Partial = {}) { +export function createUpgradeChartTab(release: HelmRelease, tabParams: DockTabCreateSpecific = {}) { let tab = upgradeChartStore.getTabByRelease(release.getName()); if (tab) { @@ -137,9 +137,9 @@ export function createUpgradeChartTab(release: HelmRelease, tabParams: Partial div { float: left; margin: $spacing; diff --git a/src/renderer/components/editable-list/editable-list.scss b/src/renderer/components/editable-list/editable-list.scss index 0f88d009d9..6c62c0c59e 100644 --- a/src/renderer/components/editable-list/editable-list.scss +++ b/src/renderer/components/editable-list/editable-list.scss @@ -20,8 +20,13 @@ */ .EditableList { + --gradientColor: var(--colorVague); + .el-contents { display: flex; + background-color: var(--colorVague); + color: $textColorSecondary; + border-radius: $radius; flex-direction: column; &:not(:empty) { @@ -35,20 +40,53 @@ } .el-item { - display: grid; - grid-template-columns: 1fr auto; - padding: $padding $padding * 2; - margin-bottom: $padding / 4; - backdrop-filter: brightness(0.75); - border-radius: var(--border-radius); - - :last-child { - margin-bottom: unset; - } - - :first-child { + display: flex; + flex-direction: row; + padding: $padding 0; + margin-bottom: 1px; + + .el-value-container { + position: relative; + max-width: calc(100% - 29px); align-self: center; + + &::before, &::after { + content: ' '; + position: absolute; + z-index: 20; + display: block; + width: 8px; + height: var(--font-size); + } + + &::before { + left: 0px; + top: 0px; + background: linear-gradient(to right, var(--gradientColor) 0px, transparent); + } + + &::after { + right: 0px; + top: 0px; + background: linear-gradient(to left, var(--gradientColor) 0px, transparent); + } } + + .el-value { + white-space: nowrap; + overflow: scroll!important; + text-overflow: unset!important; + padding-left: 8px; + padding-right: 8px; + + &::-webkit-scrollbar { + display: none; + } + } + } + + :last-child { + margin-bottom: unset; } } } diff --git a/src/renderer/components/editable-list/editable-list.tsx b/src/renderer/components/editable-list/editable-list.tsx index 0b427b6f2a..61bf70bb70 100644 --- a/src/renderer/components/editable-list/editable-list.tsx +++ b/src/renderer/components/editable-list/editable-list.tsx @@ -25,7 +25,7 @@ import { observer } from "mobx-react"; import React from "react"; import { Icon } from "../icon"; -import { Input, InputProps } from "../input"; +import { Input, InputProps, InputValidator } from "../input"; import { boundMethod } from "../../utils"; export interface Props { @@ -33,6 +33,7 @@ export interface Props { add: (newItem: string) => void, remove: (info: { oldItem: T, index: number }) => void, placeholder?: string, + validators?: InputValidator | InputValidator[]; // An optional prop used to convert T to a displayable string // defaults to `String` @@ -61,7 +62,7 @@ export class EditableList extends React.Component> { } render() { - const { items, remove, renderItem, placeholder, inputTheme } = this.props; + const { items, remove, renderItem, placeholder, validators, inputTheme } = this.props; return (
@@ -69,6 +70,7 @@ export class EditableList extends React.Component> {
@@ -76,7 +78,9 @@ export class EditableList extends React.Component> { { items.map((item, index) => (
-
{renderItem(item, index)}
+
+
{renderItem(item, index)}
+
remove(({ index, oldItem: item }))} />
diff --git a/src/renderer/components/error-boundary/error-boundary.tsx b/src/renderer/components/error-boundary/error-boundary.tsx index d2b0ef90c5..dd345e9f48 100644 --- a/src/renderer/components/error-boundary/error-boundary.tsx +++ b/src/renderer/components/error-boundary/error-boundary.tsx @@ -21,74 +21,54 @@ import "./error-boundary.scss"; -import React, { ErrorInfo } from "react"; -import { reaction } from "mobx"; -import { disposeOnUnmount, observer } from "mobx-react"; +import React from "react"; import { Button } from "../button"; import { navigation } from "../../navigation"; import { issuesTrackerUrl, slackUrl } from "../../../common/vars"; - -interface Props { -} - -interface State { - error?: Error; - errorInfo?: ErrorInfo; -} +import * as Sentry from "@sentry/react"; +import { observer } from "mobx-react"; @observer -export class ErrorBoundary extends React.Component { - public state: State = {}; - - @disposeOnUnmount - resetOnNavigate = reaction( - () => navigation.toString(), - () => this.setState({ error: null, errorInfo: null }) - ); - - componentDidCatch(error: Error, errorInfo: ErrorInfo) { - this.setState({ error, errorInfo }); - } - - back = () => { - navigation.goBack(); - }; - +export class ErrorBoundary extends React.Component { render() { - const { error, errorInfo } = this.state; + return ( + { + const slackLink = Slack; + const githubLink = GitHub; + const pageUrl = location.pathname; - if (error) { - const slackLink = Slack; - const githubLink = Github; - const pageUrl = location.pathname; - - return ( -
-
- App crash at {pageUrl} -
-

- To help us improve the product please report bugs to {slackLink} community or {githubLink} issues tracker. -

-
- -

Component stack:

- {errorInfo.componentStack} -
- -

Error stack:


- {error.stack} -
-
-
- ); - } - - return this.props.children; + return ( +
+
+ App crash at {pageUrl} +
+

+ To help us improve the product please report bugs to {slackLink} community or {githubLink} issues tracker. +

+
+ +

Component stack:

+ {componentStack} +
+ +

Error stack:


+ {error.stack} +
+
+
+ ); + }}> + {this.props.children} +
+ ); } } diff --git a/src/renderer/components/file-picker/file-picker.tsx b/src/renderer/components/file-picker/file-picker.tsx index cc1e850cd2..3661bb2cb2 100644 --- a/src/renderer/components/file-picker/file-picker.tsx +++ b/src/renderer/components/file-picker/file-picker.tsx @@ -127,7 +127,7 @@ export class FilePicker extends React.Component { return files; } - handleIndiviualFileSizes(files: File[]): File[] { + handleIndividualFileSizes(files: File[]): File[] { const { onOverSizeLimit, maxSize } = this.props; switch (onOverSizeLimit) { @@ -178,7 +178,7 @@ export class FilePicker extends React.Component { try { const numberLimitedFiles = this.handleFileCount(files); - const sizeLimitedFiles = this.handleIndiviualFileSizes(numberLimitedFiles); + const sizeLimitedFiles = this.handleIndividualFileSizes(numberLimitedFiles); const totalSizeLimitedFiles = this.handleTotalFileSizes(sizeLimitedFiles); if ("uploadDir" in this.props) { diff --git a/src/renderer/components/hotbar/hotbar-selector.scss b/src/renderer/components/hotbar/hotbar-selector.scss index 4db5ae9900..d0fe3fa57c 100644 --- a/src/renderer/components/hotbar/hotbar-selector.scss +++ b/src/renderer/components/hotbar/hotbar-selector.scss @@ -33,7 +33,7 @@ top: -20px; } - .Badge { + .SelectorIndex { cursor: pointer; background: var(--secondaryBackground); width: 100%; diff --git a/src/renderer/components/hotbar/hotbar-selector.tsx b/src/renderer/components/hotbar/hotbar-selector.tsx index f0ca39d834..3bac4475fe 100644 --- a/src/renderer/components/hotbar/hotbar-selector.tsx +++ b/src/renderer/components/hotbar/hotbar-selector.tsx @@ -49,6 +49,7 @@ export function HotbarSelector({ hotbar }: Props) { preferredPositions: [TooltipPosition.TOP, TooltipPosition.TOP_LEFT], children: hotbar.name }} + className="SelectorIndex" />
store.switchToNext()} /> diff --git a/src/renderer/components/icon/icon.scss b/src/renderer/components/icon/icon.scss index b10eb3708b..02501b3716 100644 --- a/src/renderer/components/icon/icon.scss +++ b/src/renderer/components/icon/icon.scss @@ -153,4 +153,4 @@ @extend .active; } } -} \ No newline at end of file +} diff --git a/src/renderer/components/input/input.tsx b/src/renderer/components/input/input.tsx index fa1692e988..91b58c24b5 100644 --- a/src/renderer/components/input/input.tsx +++ b/src/renderer/components/input/input.tsx @@ -31,6 +31,7 @@ import isString from "lodash/isString"; import isFunction from "lodash/isFunction"; import isBoolean from "lodash/isBoolean"; import uniqueId from "lodash/uniqueId"; +import { debounce } from "lodash"; const { conditionalValidators, ...InputValidators } = Validators; @@ -40,11 +41,13 @@ export type { InputValidator }; type InputElement = HTMLInputElement | HTMLTextAreaElement; type InputElementProps = InputHTMLAttributes & TextareaHTMLAttributes & DOMAttributes; -export type InputProps = Omit & { +export type InputProps = Omit & { theme?: "round-black" | "round"; className?: string; - value?: T; - autoSelectOnFocus?: boolean + value?: string; + trim?: boolean; + autoSelectOnFocus?: boolean; + defaultValue?: string; multiLine?: boolean; // use text-area as input field maxRows?: number; // when multiLine={true} define max rows size dirty?: boolean; // show validation errors even if the field wasn't touched yet @@ -54,17 +57,17 @@ export type InputProps = Omit): void; - onSubmit?(value: T, evt: React.KeyboardEvent): void; + onChange?(value: string, evt: React.ChangeEvent): void; + onSubmit?(value: string, evt: React.KeyboardEvent): void; }; interface State { - focused?: boolean; - dirty?: boolean; - dirtyOnBlur?: boolean; - valid?: boolean; - validating?: boolean; - errors?: React.ReactNode[]; + focused: boolean; + dirty: boolean; + valid: boolean; + validating: boolean; + errors: React.ReactNode[]; + submitted: boolean; } const defaultProps: Partial = { @@ -72,6 +75,7 @@ const defaultProps: Partial = { maxRows: 10000, showValidationLine: true, validators: [], + defaultValue: "", }; export class Input extends React.Component { @@ -81,15 +85,14 @@ export class Input extends React.Component { public validators: InputValidator[] = []; public state: State = { - dirty: !!this.props.dirty, + focused: false, valid: true, + validating: false, + dirty: !!this.props.dirty, errors: [], + submitted: false, }; - isValid() { - return this.state.valid; - } - setValue(value = "") { if (value !== this.getValue()) { const nativeInputValueSetter = Object.getOwnPropertyDescriptor(this.input.constructor.prototype, "value").set; @@ -102,12 +105,10 @@ export class Input extends React.Component { } getValue(): string { - const { value, defaultValue = "" } = this.props; + const { trim, value, defaultValue } = this.props; + const rawValue = value ?? this.input?.value ?? defaultValue; - if (value !== undefined) return value; // controlled input - if (this.input) return this.input.value; // uncontrolled input - - return defaultValue as string; + return trim ? rawValue.trim() : rawValue; } focus() { @@ -138,7 +139,8 @@ export class Input extends React.Component { private validationId: string; - async validate(value = this.getValue()) { + async validate() { + const value = this.getValue(); let validationId = (this.validationId = ""); // reset every time for async validators const asyncValidators: Promise[] = []; const errors: React.ReactNode[] = []; @@ -213,7 +215,6 @@ export class Input extends React.Component { } setDirty(dirty = true) { - if (this.state.dirty === dirty) return; this.setState({ dirty }); } @@ -221,30 +222,25 @@ export class Input extends React.Component { onFocus(evt: React.FocusEvent) { const { onFocus, autoSelectOnFocus } = this.props; - if (onFocus) onFocus(evt); + onFocus?.(evt); if (autoSelectOnFocus) this.select(); this.setState({ focused: true }); } @boundMethod onBlur(evt: React.FocusEvent) { - const { onBlur } = this.props; - - if (onBlur) onBlur(evt); - if (this.state.dirtyOnBlur) this.setState({ dirty: true, dirtyOnBlur: false }); + this.props.onBlur?.(evt); this.setState({ focused: false }); } + setDirtyOnChange = debounce(() => this.setDirty(), 500); + @boundMethod - onChange(evt: React.ChangeEvent) { + onChange(evt: React.ChangeEvent) { this.props.onChange?.(evt.currentTarget.value, evt); this.validate(); this.autoFitHeight(); - - // mark input as dirty for the first time only onBlur() to avoid immediate error-state show when start typing - if (!this.state.dirty) { - this.setState({ dirtyOnBlur: true }); - } + this.setDirtyOnChange(); // re-render component when used as uncontrolled input // when used @defaultValue instead of @value changing real input.value doesn't call render() @@ -255,20 +251,20 @@ export class Input extends React.Component { @boundMethod onKeyDown(evt: React.KeyboardEvent) { - const modified = evt.shiftKey || evt.metaKey || evt.altKey || evt.ctrlKey; - this.props.onKeyDown?.(evt); - switch (evt.key) { - case "Enter": - if (this.props.onSubmit && !modified && !evt.repeat && this.isValid()) { - this.props.onSubmit(this.getValue(), evt); + if (evt.shiftKey || evt.metaKey || evt.altKey || evt.ctrlKey || evt.repeat) { + return; + } - if (this.isUncontrolled) { - this.setValue(); - } - } - break; + if (evt.key === "Enter") { + if (this.state.valid) { + this.props.onSubmit?.(this.getValue(), evt); + this.setDirtyOnChange.cancel(); + this.setState({ submitted: true }); + } else { + this.setDirty(); + } } } @@ -291,7 +287,11 @@ export class Input extends React.Component { const { defaultValue, value, dirty, validators } = this.props; if (prevProps.value !== value || defaultValue !== prevProps.defaultValue) { - this.validate(); + if (!this.state.submitted) { + this.validate(); + } else { + this.setState({ submitted: false }); + } this.autoFitHeight(); } diff --git a/src/renderer/components/item-object-list/page-filters-list.tsx b/src/renderer/components/item-object-list/page-filters-list.tsx index 1bee3be8ba..6620350b01 100644 --- a/src/renderer/components/item-object-list/page-filters-list.tsx +++ b/src/renderer/components/item-object-list/page-filters-list.tsx @@ -70,7 +70,7 @@ export class PageFiltersList extends React.Component { diff --git a/src/renderer/components/kube-object-status-icon/kube-object-status-icon.tsx b/src/renderer/components/kube-object-status-icon/kube-object-status-icon.tsx index ebde73f894..9cd9d6b3af 100644 --- a/src/renderer/components/kube-object-status-icon/kube-object-status-icon.tsx +++ b/src/renderer/components/kube-object-status-icon/kube-object-status-icon.tsx @@ -63,7 +63,7 @@ interface SplitStatusesByLevel { } /** - * This fuction returns the class level for corresponding to the highest status level + * This function returns the class level for corresponding to the highest status level * and the statuses split by their levels. * @param src a list of status items */ diff --git a/src/renderer/components/kube-object/kube-object-details.tsx b/src/renderer/components/kube-object/kube-object-details.tsx index 8098f77064..1f0c494b4e 100644 --- a/src/renderer/components/kube-object/kube-object-details.tsx +++ b/src/renderer/components/kube-object/kube-object-details.tsx @@ -34,6 +34,7 @@ import { CrdResourceDetails } from "../+custom-resources"; import { KubeObjectMenu } from "./kube-object-menu"; import type { CustomResourceDefinition } from "../../api/endpoints"; import { KubeObjectDetailRegistry } from "../../api/kube-object-detail-registry"; +import logger from "../../../main/logger"; /** * Used to store `object.selfLink` to show more info about resource in the details panel. @@ -67,6 +68,7 @@ export function hideDetails() { } export function getDetailsUrl(selfLink: string, resetSelected = false, mergeGlobals = true) { + logger.debug("getDetailsUrl", { selfLink, resetSelected, mergeGlobals }); const params = new URLSearchParams(mergeGlobals ? navigation.searchParams : ""); params.set(kubeDetailsUrlParam.name, selfLink); @@ -100,10 +102,12 @@ export class KubeObjectDetails extends React.Component { } @computed get object() { - const store = apiManager.getStore(this.path); - - if (store) { - return store.getByPath(this.path); + try { + return apiManager + .getStore(this.path) + ?.getByPath(this.path); + } catch (error) { + logger.error(`[KUBE-OBJECT-DETAILS]: failed to get store or object: ${error}`, { path: this.path }); } } diff --git a/src/renderer/components/layout/sidebar.tsx b/src/renderer/components/layout/sidebar.tsx index 2aa3f37488..9842d9e45e 100644 --- a/src/renderer/components/layout/sidebar.tsx +++ b/src/renderer/components/layout/sidebar.tsx @@ -73,7 +73,7 @@ export class Sidebar extends React.Component { key={crd.getResourceApiBase()} id={`crd-resource:${crd.getResourceApiBase()}`} url={crd.getResourceUrl()} - text={crd.getResourceTitle()} + text={crd.getResourceKind()} /> ))} diff --git a/src/renderer/components/markdown-viewer/markdown-viewer.scss b/src/renderer/components/markdown-viewer/markdown-viewer.scss index 9afa1f29cc..ccd2599e18 100644 --- a/src/renderer/components/markdown-viewer/markdown-viewer.scss +++ b/src/renderer/components/markdown-viewer/markdown-viewer.scss @@ -383,8 +383,8 @@ blockquote { padding: 8px 1em; - color: $helmDescriptionBlockqouteColor; - border-left: 0.25em solid $helmDescriptionBlockqouteBorder; + color: $helmDescriptionBlockquoteColor; + border-left: 0.25em solid $helmDescriptionBlockquoteBorder; background: $helmDescriptionBlockquoteBackground; } diff --git a/src/renderer/components/notifications/notifications.store.tsx b/src/renderer/components/notifications/notifications.store.tsx index 753a0978d3..63ab5d5e05 100644 --- a/src/renderer/components/notifications/notifications.store.tsx +++ b/src/renderer/components/notifications/notifications.store.tsx @@ -39,7 +39,7 @@ export interface Notification { message: NotificationMessage; status?: NotificationStatus; timeout?: number; // auto-hiding timeout in milliseconds, 0 = no hide - onClose?(): void; // additonal logic on when the notification times out or is closed by the "x" + onClose?(): void; // additional logic on when the notification times out or is closed by the "x" } export class NotificationsStore { diff --git a/src/renderer/components/notifications/notifications.tsx b/src/renderer/components/notifications/notifications.tsx index c940ec469a..b4e9344aa0 100644 --- a/src/renderer/components/notifications/notifications.tsx +++ b/src/renderer/components/notifications/notifications.tsx @@ -88,7 +88,7 @@ export class Notifications extends React.Component { getMessage(notification: Notification) { let { message } = notification; - if (message instanceof JsonApiErrorParsed) { + if (message instanceof JsonApiErrorParsed || message instanceof Error) { message = message.toString(); } diff --git a/src/renderer/components/resizing-anchor/resizing-anchor.tsx b/src/renderer/components/resizing-anchor/resizing-anchor.tsx index a71a604e51..ec564ca219 100644 --- a/src/renderer/components/resizing-anchor/resizing-anchor.tsx +++ b/src/renderer/components/resizing-anchor/resizing-anchor.tsx @@ -100,7 +100,7 @@ interface Props { minExtent?: number; /** - * The following events are triggerred with respect to the above values. + * The following events are triggered with respect to the above values. * - The "__Exceed" call will be made when the unbounded extent goes from * < the above to >= the above * - The "__Subceed" call is similar but is triggered when the unbounded diff --git a/src/renderer/components/spinner/cube-spinner.scss b/src/renderer/components/spinner/cube-spinner.scss deleted file mode 100644 index 18dab776d7..0000000000 --- a/src/renderer/components/spinner/cube-spinner.scss +++ /dev/null @@ -1,114 +0,0 @@ -/** - * 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. - */ - -// http://tobiasahlin.com/spinkit/ - -.CubeSpinner { - --size: 70px; - - &.center { - position: absolute; - left: 50%; - top: 50%; - transform: translate(-50%, -50%); - } - - .sk-cube-grid { - $size: 70px; - width: $size; - height: $size; - transform: rotate(45deg); - margin: $margin * 4 auto; - border-bottom-left-radius: $radius; - border-top-right-radius: $radius; - overflow: hidden; - } - - .sk-cube-grid .sk-cube { - width: 33%; - height: 33%; - float: left; - -webkit-animation: sk-cubeGridScaleDelay 1.3s infinite ease-in-out; - animation: sk-cubeGridScaleDelay 1.3s infinite ease-in-out; - } - - .sk-cube-grid .sk-cube1 { - -webkit-animation-delay: 0.2s; - animation-delay: 0.2s; - } - - .sk-cube-grid .sk-cube2 { - -webkit-animation-delay: 0.3s; - animation-delay: 0.3s; - } - - .sk-cube-grid .sk-cube3 { - -webkit-animation-delay: 0.4s; - animation-delay: 0.4s; - } - - .sk-cube-grid .sk-cube4 { - -webkit-animation-delay: 0.1s; - animation-delay: 0.1s; - } - - .sk-cube-grid .sk-cube5 { - -webkit-animation-delay: 0.2s; - animation-delay: 0.2s; - } - - .sk-cube-grid .sk-cube6 { - -webkit-animation-delay: 0.3s; - animation-delay: 0.3s; - } - - .sk-cube-grid .sk-cube7 { - -webkit-animation-delay: 0s; - animation-delay: 0s; - } - - .sk-cube-grid .sk-cube8 { - -webkit-animation-delay: 0.1s; - animation-delay: 0.1s; - } - - .sk-cube-grid .sk-cube9 { - -webkit-animation-delay: 0.2s; - animation-delay: 0.2s; - } - - @keyframes sk-cubeGridScaleDelay { - 0%, 35% { - background-color: #36393e; - } - 0%, 70%, 100% { - -webkit-transform: scale3D(1, 1, 1); - transform: scale3D(1, 1, 1); - } - 35% { - -webkit-transform: scale3D(0, 0, 1); - transform: scale3D(0, 0, 1); - } - 100% { - background-color: #3389ca; - } - } -} \ No newline at end of file diff --git a/src/renderer/components/spinner/index.ts b/src/renderer/components/spinner/index.ts index d060b78aa0..53ed99304a 100644 --- a/src/renderer/components/spinner/index.ts +++ b/src/renderer/components/spinner/index.ts @@ -20,4 +20,3 @@ */ export * from "./spinner"; -export * from "./cube-spinner"; diff --git a/src/renderer/components/table/table-head.scss b/src/renderer/components/table/table-head.scss index 4e194b2708..efa0bdb9b9 100644 --- a/src/renderer/components/table/table-head.scss +++ b/src/renderer/components/table/table-head.scss @@ -38,7 +38,7 @@ position: -webkit-sticky; // safari position: sticky; z-index: 1; - top: 0; // turn on sticky behaviour + top: 0; } &.nowrap { @@ -48,6 +48,7 @@ .TableCell { display: flex; align-items: center; + word-break: normal; &.checkbox { > .Checkbox { @@ -55,4 +56,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/renderer/components/table/table.mixins.scss b/src/renderer/components/table/table.mixins.scss index 3f7a021aff..12e009631d 100644 --- a/src/renderer/components/table/table.mixins.scss +++ b/src/renderer/components/table/table.mixins.scss @@ -49,8 +49,4 @@ @mixin table-cell-labels-offsets { padding-top: $padding / 2; padding-bottom: 0; - - .Badge + .Badge { - margin-left: $padding / 2; - } } \ No newline at end of file diff --git a/src/renderer/components/tabs/tabs.tsx b/src/renderer/components/tabs/tabs.tsx index fb3fb5e58c..8a910211fa 100644 --- a/src/renderer/components/tabs/tabs.tsx +++ b/src/renderer/components/tabs/tabs.tsx @@ -84,7 +84,7 @@ export interface TabProps extends DOMAttributes { export class Tab extends React.PureComponent { static contextType = TabsContext; declare context: TabsContextValue; - public elem: HTMLElement; + public ref = React.createRef(); get isActive() { const { active, value } = this.props; @@ -93,11 +93,11 @@ export class Tab extends React.PureComponent { } focus() { - this.elem.focus(); + this.ref.current?.focus(); } scrollIntoView() { - this.elem.scrollIntoView({ + this.ref.current?.scrollIntoView?.({ behavior: "smooth", inline: "center", }); @@ -106,30 +106,28 @@ export class Tab extends React.PureComponent { @boundMethod onClick(evt: React.MouseEvent) { const { value, active, disabled, onClick } = this.props; - const { onChange } = this.context; - if (disabled || active) return; - if (onClick) onClick(evt); - if (onChange) onChange(value); + if (disabled || active) { + return; + } + + onClick?.(evt); + this.context.onChange?.(value); } @boundMethod onFocus(evt: React.FocusEvent) { - const { onFocus } = this.props; - - if (onFocus) onFocus(evt); + this.props.onFocus?.(evt); this.scrollIntoView(); } @boundMethod onKeyDown(evt: React.KeyboardEvent) { - const ENTER_KEY = evt.keyCode === 13; - const SPACE_KEY = evt.keyCode === 32; + if (evt.key === " " || evt.key === "Enter") { + this.ref.current?.click(); + } - if (SPACE_KEY || ENTER_KEY) this.elem.click(); - const { onKeyDown } = this.props; - - if (onKeyDown) onKeyDown(evt); + this.props?.onKeyDown(evt); } componentDidMount() { @@ -138,11 +136,6 @@ export class Tab extends React.PureComponent { } } - @boundMethod - protected bindRef(elem: HTMLElement) { - this.elem = elem; - } - render() { const { active, disabled, icon, label, value, ...elemProps } = this.props; let { className } = this.props; @@ -160,7 +153,7 @@ export class Tab extends React.PureComponent { onClick={this.onClick} onFocus={this.onFocus} onKeyDown={this.onKeyDown} - ref={this.bindRef} + ref={this.ref} > {typeof icon === "string" ? : icon}
diff --git a/src/renderer/initializers/catalog-entity-detail-registry.tsx b/src/renderer/initializers/catalog-entity-detail-registry.tsx new file mode 100644 index 0000000000..8cae26afbf --- /dev/null +++ b/src/renderer/initializers/catalog-entity-detail-registry.tsx @@ -0,0 +1,50 @@ +/** + * 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 React from "react"; +import { KubernetesCluster } from "../../common/catalog-entities"; +import { CatalogEntityDetailRegistry, CatalogEntityDetailsProps } from "../../extensions/registries"; +import { DrawerItem, DrawerTitle } from "../components/drawer"; + +export function initCatalogEntityDetailRegistry() { + CatalogEntityDetailRegistry.getInstance() + .add([ + { + apiVersions: [KubernetesCluster.apiVersion], + kind: KubernetesCluster.kind, + components: { + Details: ({ entity }: CatalogEntityDetailsProps) => ( + <> + +
+ + {entity.metadata.distro || "unknown"} + + + {entity.metadata.kubeVersion || "unknown"} + +
+ + ), + }, + } + ]); +} diff --git a/src/renderer/initializers/entity-settings-registry.ts b/src/renderer/initializers/entity-settings-registry.ts index 10723c8ebf..31c8c81c21 100644 --- a/src/renderer/initializers/entity-settings-registry.ts +++ b/src/renderer/initializers/entity-settings-registry.ts @@ -70,6 +70,15 @@ export function initEntitySettingsRegistry() { components: { View: clusterSettings.MetricsSettings, } + }, + { + apiVersions: ["entity.k8slens.dev/v1alpha1"], + kind: "KubernetesCluster", + title: "Node Shell", + group: "Settings", + components: { + View: clusterSettings.NodeShellSettings, + } } ]); } diff --git a/src/renderer/initializers/index.ts b/src/renderer/initializers/index.ts index 03f32f1a9d..943fef6a30 100644 --- a/src/renderer/initializers/index.ts +++ b/src/renderer/initializers/index.ts @@ -19,12 +19,13 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +export * from "./catalog-entity-detail-registry"; +export * from "./catalog"; export * from "./command-registry"; export * from "./entity-settings-registry"; +export * from "./ipc"; export * from "./kube-object-detail-registry"; export * from "./kube-object-menu-registry"; export * from "./registries"; export * from "./welcome-menu-registry"; export * from "./workloads-overview-detail-registry"; -export * from "./catalog"; -export * from "./ipc"; diff --git a/src/renderer/kube-object.store.ts b/src/renderer/kube-object.store.ts index 62dd1cb117..881a9ae6f3 100644 --- a/src/renderer/kube-object.store.ts +++ b/src/renderer/kube-object.store.ts @@ -195,7 +195,7 @@ export abstract class KubeObjectStore extends ItemSt if (error.message) { Notifications.error(error.message); } - console.error("Loading store items failed", { error }); + console.warn("[KubeObjectStore] loadAll failed", this.api.apiBase, error); this.resetOnError(error); this.failedLoading = true; } finally { diff --git a/src/renderer/themes/lens-dark.json b/src/renderer/themes/lens-dark.json index fff229699b..9bc65a204a 100644 --- a/src/renderer/themes/lens-dark.json +++ b/src/renderer/themes/lens-dark.json @@ -44,8 +44,8 @@ "helmStableRepo": "#3d90ce", "helmIncubatorRepo": "#ff7043", "helmDescriptionHr": "#41474a", - "helmDescriptionBlockqouteColor": "#bbb", - "helmDescriptionBlockqouteBorder": "#8a8f93", + "helmDescriptionBlockquoteColor": "#bbb", + "helmDescriptionBlockquoteBorder": "#8a8f93", "helmDescriptionBlockquoteBackground": "#3b4348", "helmDescriptionHeaders": "#3e4147", "helmDescriptionH6": "#6a737d", @@ -113,7 +113,7 @@ "iconActiveColor": "#ffffff", "iconActiveBackground": "#ffffff18", "filterAreaBackground": "#23272b", - "chartLiveBarBackgound": "#00000033", + "chartLiveBarBackground": "#00000033", "chartStripesColor": "#ffffff08", "chartCapacityColor": "#4c545f", "pieChartDefaultColor": "#30353a", diff --git a/src/renderer/themes/lens-light.json b/src/renderer/themes/lens-light.json index 8893fe27ac..eae521c1cd 100644 --- a/src/renderer/themes/lens-light.json +++ b/src/renderer/themes/lens-light.json @@ -45,8 +45,8 @@ "helmStableRepo": "#3d90ce", "helmIncubatorRepo": "#ff7043", "helmDescriptionHr": "#dddddd", - "helmDescriptionBlockqouteColor": "#555555", - "helmDescriptionBlockqouteBorder": "#8a8f93", + "helmDescriptionBlockquoteColor": "#555555", + "helmDescriptionBlockquoteBorder": "#8a8f93", "helmDescriptionBlockquoteBackground": "#eeeeee", "helmDescriptionHeaders": "#3e4147", "helmDescriptionH6": "#6a737d", @@ -114,7 +114,7 @@ "iconActiveColor": "#ffffff", "iconActiveBackground": "#a6a6a694", "filterAreaBackground": "#f7f7f7", - "chartLiveBarBackgound": "#00000033", + "chartLiveBarBackground": "#00000033", "chartStripesColor": "#00000009", "chartCapacityColor": "#cccccc", "pieChartDefaultColor": "#efefef", diff --git a/src/renderer/themes/theme-vars.scss b/src/renderer/themes/theme-vars.scss index ac6424a183..80792216b9 100644 --- a/src/renderer/themes/theme-vars.scss +++ b/src/renderer/themes/theme-vars.scss @@ -74,8 +74,8 @@ $helmImgBackground: var(--helmImgBackground); $helmStableRepo: var(--helmStableRepo); $helmIncubatorRepo: var(--helmIncubatorRepo); $helmDescriptionHr: var(--helmDescriptionHr); -$helmDescriptionBlockqouteColor: var(--helmDescriptionBlockqouteColor); -$helmDescriptionBlockqouteBorder: var(--helmDescriptionBlockqouteBorder); +$helmDescriptionBlockquoteColor: var(--helmDescriptionBlockquoteColor); +$helmDescriptionBlockquoteBorder: var(--helmDescriptionBlockquoteBorder); $helmDescriptionBlockquoteBackground: var(--helmDescriptionBlockquoteBackground); $helmDescriptionHeaders: var(--helmDescriptionHeaders); $helmDescriptionH6: var(--helmDescriptionH6); @@ -132,7 +132,7 @@ $drawerItemNameColor: var(--drawerItemNameColor); $drawerItemValueColor: var(--drawerItemValueColor); // Charts -$chartLiveBarBackgound: var(--chartLiveBarBackgound); +$chartLiveBarBackground: var(--chartLiveBarBackground); $chartStripesColor: var(--chartStripesColor); $chartCapacityColor: var(--chartCapacityColor); $pieChartDefaultColor: var(--pieChartDefaultColor); diff --git a/src/renderer/utils/__tests__/jsonPath.test.tsx b/src/renderer/utils/__tests__/jsonPath.test.tsx index 6e2049ab19..43468bb1dc 100644 --- a/src/renderer/utils/__tests__/jsonPath.test.tsx +++ b/src/renderer/utils/__tests__/jsonPath.test.tsx @@ -28,7 +28,7 @@ describe("parseJsonPath", () => { expect(res).toBe(".metadata.labels['kubesphere.io/alias-name']"); }); - test("should convert keys with escpaped charatecrs to use indexed notation", () => { + test("should convert keys with escaped characters to use indexed notation", () => { const res = parseJsonPath(".metadata.labels.kubesphere\\\"io/alias-name"); expect(res).toBe(".metadata.labels['kubesphere\"io/alias-name']"); diff --git a/src/renderer/utils/jsonPath.ts b/src/renderer/utils/jsonPath.ts index d055bf6ecb..92855d241a 100644 --- a/src/renderer/utils/jsonPath.ts +++ b/src/renderer/utils/jsonPath.ts @@ -48,7 +48,7 @@ function convertToIndexNotation(key: string, firstItem = false) { } return `['${key}']`; - } else { // no illegal chracters found, do not touch + } else { // no illegal characters found, do not touch const prefix = firstItem ? "" : "."; return `${prefix}${key}`; diff --git a/webpack.renderer.ts b/webpack.renderer.ts index 027d18ac08..c0353ef1f8 100755 --- a/webpack.renderer.ts +++ b/webpack.renderer.ts @@ -174,7 +174,6 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura isDevelopment && new webpack.HotModuleReplacementPlugin(), isDevelopment && new ReactRefreshWebpackPlugin(), - ].filter(Boolean), }; } diff --git a/yarn.lock b/yarn.lock index 50a3a09e7c..8c294f729d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,7 +7,14 @@ resolved "https://registry.yarnpkg.com/7zip-bin/-/7zip-bin-5.0.3.tgz#bc5b5532ecafd923a61f2fb097e3b108c0106a3f" integrity sha512-GLyWIFBbGvpKPGo55JyRZAo4lVbnBiD52cKlw/0Vt+wnmKvWJkpZvsjVoaIolyBXDeAQKSicRtqFNPem9w0WYA== -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.1", "@babel/code-frame@^7.8.3": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.8.3": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.14.5.tgz#23b08d740e83f49c5e59945fbf1b43e80bbf4edb" + integrity sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw== + dependencies: + "@babel/highlight" "^7.14.5" + +"@babel/code-frame@^7.10.1": version "7.10.1" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.1.tgz#d5481c5095daa1c57e16e54c6f9198443afb49ff" integrity sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw== @@ -108,6 +115,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz#ec5a5cf0eec925b66c60580328b122c01230a127" integrity sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA== +"@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz#5ac822ce97eec46741ab70a517971e443a70c5a9" + integrity sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ== + "@babel/helper-replace-supers@^7.10.1": version "7.10.1" resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.10.1.tgz#ec6859d20c5d8087f6a2dc4e014db7228975f13d" @@ -133,10 +145,10 @@ dependencies: "@babel/types" "^7.10.1" -"@babel/helper-validator-identifier@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz#5770b0c1a826c4f53f5ede5e153163e0318e94b5" - integrity sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw== +"@babel/helper-validator-identifier@^7.10.1", "@babel/helper-validator-identifier@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz#d0f0e277c512e0c938277faa85a3968c9a44c0e8" + integrity sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg== "@babel/helper-validator-identifier@^7.10.4": version "7.10.4" @@ -152,12 +164,12 @@ "@babel/traverse" "^7.10.1" "@babel/types" "^7.10.1" -"@babel/highlight@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.1.tgz#841d098ba613ba1a427a2b383d79e35552c38ae0" - integrity sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg== +"@babel/highlight@^7.10.1", "@babel/highlight@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.5.tgz#6861a52f03966405001f6aa534a01a24d99e8cd9" + integrity sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg== dependencies: - "@babel/helper-validator-identifier" "^7.10.1" + "@babel/helper-validator-identifier" "^7.14.5" chalk "^2.0.0" js-tokens "^4.0.0" @@ -196,6 +208,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.1" +"@babel/plugin-syntax-import-meta@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-json-strings@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" @@ -245,6 +264,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" +"@babel/plugin-syntax-top-level-await@^7.8.3": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/runtime-corejs3@^7.10.2": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.12.1.tgz#51b9092befbeeed938335a109dbe0df51451e9dc" @@ -580,193 +606,166 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== -"@jest/console@^26.0.1": - version "26.0.1" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-26.0.1.tgz#62b3b2fa8990f3cbffbef695c42ae9ddbc8f4b39" - integrity sha512-9t1KUe/93coV1rBSxMmBAOIK3/HVpwxArCA1CxskKyRiv6o8J70V8C/V3OJminVCTa2M0hQI9AWRd5wxu2dAHw== +"@jest/console@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-26.6.2.tgz#4e04bc464014358b03ab4937805ee36a0aeb98f2" + integrity sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g== dependencies: - "@jest/types" "^26.0.1" + "@jest/types" "^26.6.2" + "@types/node" "*" chalk "^4.0.0" - jest-message-util "^26.0.1" - jest-util "^26.0.1" + jest-message-util "^26.6.2" + jest-util "^26.6.2" slash "^3.0.0" -"@jest/core@^26.0.1": - version "26.0.1" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-26.0.1.tgz#aa538d52497dfab56735efb00e506be83d841fae" - integrity sha512-Xq3eqYnxsG9SjDC+WLeIgf7/8KU6rddBxH+SCt18gEpOhAGYC/Mq+YbtlNcIdwjnnT+wDseXSbU0e5X84Y4jTQ== +"@jest/core@^26.6.3": + version "26.6.3" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-26.6.3.tgz#7639fcb3833d748a4656ada54bde193051e45fad" + integrity sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw== dependencies: - "@jest/console" "^26.0.1" - "@jest/reporters" "^26.0.1" - "@jest/test-result" "^26.0.1" - "@jest/transform" "^26.0.1" - "@jest/types" "^26.0.1" + "@jest/console" "^26.6.2" + "@jest/reporters" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" exit "^0.1.2" graceful-fs "^4.2.4" - jest-changed-files "^26.0.1" - jest-config "^26.0.1" - jest-haste-map "^26.0.1" - jest-message-util "^26.0.1" + jest-changed-files "^26.6.2" + jest-config "^26.6.3" + jest-haste-map "^26.6.2" + jest-message-util "^26.6.2" jest-regex-util "^26.0.0" - jest-resolve "^26.0.1" - jest-resolve-dependencies "^26.0.1" - jest-runner "^26.0.1" - jest-runtime "^26.0.1" - jest-snapshot "^26.0.1" - jest-util "^26.0.1" - jest-validate "^26.0.1" - jest-watcher "^26.0.1" + jest-resolve "^26.6.2" + jest-resolve-dependencies "^26.6.3" + jest-runner "^26.6.3" + jest-runtime "^26.6.3" + jest-snapshot "^26.6.2" + jest-util "^26.6.2" + jest-validate "^26.6.2" + jest-watcher "^26.6.2" micromatch "^4.0.2" p-each-series "^2.1.0" rimraf "^3.0.0" slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/environment@^26.0.1": - version "26.0.1" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-26.0.1.tgz#82f519bba71959be9b483675ee89de8c8f72a5c8" - integrity sha512-xBDxPe8/nx251u0VJ2dFAFz2H23Y98qdIaNwnMK6dFQr05jc+Ne/2np73lOAx+5mSBO/yuQldRrQOf6hP1h92g== +"@jest/environment@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-26.6.2.tgz#ba364cc72e221e79cc8f0a99555bf5d7577cf92c" + integrity sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA== dependencies: - "@jest/fake-timers" "^26.0.1" - "@jest/types" "^26.0.1" - jest-mock "^26.0.1" + "@jest/fake-timers" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + jest-mock "^26.6.2" -"@jest/fake-timers@^26.0.1": - version "26.0.1" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.0.1.tgz#f7aeff13b9f387e9d0cac9a8de3bba538d19d796" - integrity sha512-Oj/kCBnTKhm7CR+OJSjZty6N1bRDr9pgiYQr4wY221azLz5PHi08x/U+9+QpceAYOWheauLP8MhtSVFrqXQfhg== +"@jest/fake-timers@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.6.2.tgz#459c329bcf70cee4af4d7e3f3e67848123535aad" + integrity sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA== dependencies: - "@jest/types" "^26.0.1" + "@jest/types" "^26.6.2" "@sinonjs/fake-timers" "^6.0.1" - jest-message-util "^26.0.1" - jest-mock "^26.0.1" - jest-util "^26.0.1" + "@types/node" "*" + jest-message-util "^26.6.2" + jest-mock "^26.6.2" + jest-util "^26.6.2" -"@jest/globals@^26.0.1": - version "26.0.1" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-26.0.1.tgz#3f67b508a7ce62b6e6efc536f3d18ec9deb19a9c" - integrity sha512-iuucxOYB7BRCvT+TYBzUqUNuxFX1hqaR6G6IcGgEqkJ5x4htNKo1r7jk1ji9Zj8ZMiMw0oB5NaA7k5Tx6MVssA== +"@jest/globals@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-26.6.2.tgz#5b613b78a1aa2655ae908eba638cc96a20df720a" + integrity sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA== dependencies: - "@jest/environment" "^26.0.1" - "@jest/types" "^26.0.1" - expect "^26.0.1" + "@jest/environment" "^26.6.2" + "@jest/types" "^26.6.2" + expect "^26.6.2" -"@jest/reporters@^26.0.1": - version "26.0.1" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-26.0.1.tgz#14ae00e7a93e498cec35b0c00ab21c375d9b078f" - integrity sha512-NWWy9KwRtE1iyG/m7huiFVF9YsYv/e+mbflKRV84WDoJfBqUrNRyDbL/vFxQcYLl8IRqI4P3MgPn386x76Gf2g== +"@jest/reporters@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-26.6.2.tgz#1f518b99637a5f18307bd3ecf9275f6882a667f6" + integrity sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^26.0.1" - "@jest/test-result" "^26.0.1" - "@jest/transform" "^26.0.1" - "@jest/types" "^26.0.1" + "@jest/console" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" chalk "^4.0.0" collect-v8-coverage "^1.0.0" exit "^0.1.2" glob "^7.1.2" graceful-fs "^4.2.4" istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^4.0.0" + istanbul-lib-instrument "^4.0.3" istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" istanbul-reports "^3.0.2" - jest-haste-map "^26.0.1" - jest-resolve "^26.0.1" - jest-util "^26.0.1" - jest-worker "^26.0.0" + jest-haste-map "^26.6.2" + jest-resolve "^26.6.2" + jest-util "^26.6.2" + jest-worker "^26.6.2" slash "^3.0.0" source-map "^0.6.0" string-length "^4.0.1" terminal-link "^2.0.0" - v8-to-istanbul "^4.1.3" + v8-to-istanbul "^7.0.0" optionalDependencies: - node-notifier "^7.0.0" + node-notifier "^8.0.0" -"@jest/source-map@^26.0.0": - version "26.0.0" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-26.0.0.tgz#fd7706484a7d3faf7792ae29783933bbf48a4749" - integrity sha512-S2Z+Aj/7KOSU2TfW0dyzBze7xr95bkm5YXNUqqCek+HE0VbNNSNzrRwfIi5lf7wvzDTSS0/ib8XQ1krFNyYgbQ== +"@jest/source-map@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-26.6.2.tgz#29af5e1e2e324cafccc936f218309f54ab69d535" + integrity sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA== dependencies: callsites "^3.0.0" graceful-fs "^4.2.4" source-map "^0.6.0" -"@jest/test-result@^26.0.1": - version "26.0.1" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-26.0.1.tgz#1ffdc1ba4bc289919e54b9414b74c9c2f7b2b718" - integrity sha512-oKwHvOI73ICSYRPe8WwyYPTtiuOAkLSbY8/MfWF3qDEd/sa8EDyZzin3BaXTqufir/O/Gzea4E8Zl14XU4Mlyg== +"@jest/test-result@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-26.6.2.tgz#55da58b62df134576cc95476efa5f7949e3f5f18" + integrity sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ== dependencies: - "@jest/console" "^26.0.1" - "@jest/types" "^26.0.1" + "@jest/console" "^26.6.2" + "@jest/types" "^26.6.2" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^26.0.1": - version "26.0.1" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-26.0.1.tgz#b0563424728f3fe9e75d1442b9ae4c11da73f090" - integrity sha512-ssga8XlwfP8YjbDcmVhwNlrmblddMfgUeAkWIXts1V22equp2GMIHxm7cyeD5Q/B0ZgKPK/tngt45sH99yLLGg== +"@jest/test-sequencer@^26.6.3": + version "26.6.3" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz#98e8a45100863886d074205e8ffdc5a7eb582b17" + integrity sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw== dependencies: - "@jest/test-result" "^26.0.1" + "@jest/test-result" "^26.6.2" graceful-fs "^4.2.4" - jest-haste-map "^26.0.1" - jest-runner "^26.0.1" - jest-runtime "^26.0.1" + jest-haste-map "^26.6.2" + jest-runner "^26.6.3" + jest-runtime "^26.6.3" -"@jest/transform@^26.0.1": - version "26.0.1" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-26.0.1.tgz#0e3ecbb34a11cd4b2080ed0a9c4856cf0ceb0639" - integrity sha512-pPRkVkAQ91drKGbzCfDOoHN838+FSbYaEAvBXvKuWeeRRUD8FjwXkqfUNUZL6Ke48aA/1cqq/Ni7kVMCoqagWA== +"@jest/transform@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-26.6.2.tgz#5ac57c5fa1ad17b2aae83e73e45813894dcf2e4b" + integrity sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA== dependencies: "@babel/core" "^7.1.0" - "@jest/types" "^26.0.1" + "@jest/types" "^26.6.2" babel-plugin-istanbul "^6.0.0" chalk "^4.0.0" convert-source-map "^1.4.0" fast-json-stable-stringify "^2.0.0" graceful-fs "^4.2.4" - jest-haste-map "^26.0.1" + jest-haste-map "^26.6.2" jest-regex-util "^26.0.0" - jest-util "^26.0.1" + jest-util "^26.6.2" micromatch "^4.0.2" pirates "^4.0.1" slash "^3.0.0" source-map "^0.6.1" write-file-atomic "^3.0.0" -"@jest/types@^25.5.0": - version "25.5.0" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.5.0.tgz#4d6a4793f7b9599fc3680877b856a97dbccf2a9d" - integrity sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^1.1.1" - "@types/yargs" "^15.0.0" - chalk "^3.0.0" - -"@jest/types@^26.0.1": - version "26.0.1" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.0.1.tgz#b78333fbd113fa7aec8d39de24f88de8686dac67" - integrity sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^1.1.1" - "@types/yargs" "^15.0.0" - chalk "^4.0.0" - -"@jest/types@^26.3.0": - version "26.3.0" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.3.0.tgz#97627bf4bdb72c55346eef98e3b3f7ddc4941f71" - integrity sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^15.0.0" - chalk "^4.0.0" - "@jest/types@^26.6.1": version "26.6.1" resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.1.tgz#2638890e8031c0bc8b4681e0357ed986e2f866c5" @@ -944,6 +943,171 @@ schema-utils "^2.6.5" source-map "^0.7.3" +"@sentry/browser@6.7.1": + version "6.7.1" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-6.7.1.tgz#e01144a08984a486ecc91d7922cc457e9c9bd6b7" + integrity sha512-R5PYx4TTvifcU790XkK6JVGwavKaXwycDU0MaAwfc4Vf7BLm5KCNJCsDySu1RPAap/017MVYf54p6dWvKiRviA== + dependencies: + "@sentry/core" "6.7.1" + "@sentry/types" "6.7.1" + "@sentry/utils" "6.7.1" + tslib "^1.9.3" + +"@sentry/browser@6.8.0": + version "6.8.0" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-6.8.0.tgz#023707cd2302f6818014e9a7e124856b2d064178" + integrity sha512-nxa71csHlG5sMHUxI4e4xxuCWtbCv/QbBfMsYw7ncJSfCKG3yNlCVh8NJ7NS0rZW/MJUT6S6+r93zw0HetNDOA== + dependencies: + "@sentry/core" "6.8.0" + "@sentry/types" "6.8.0" + "@sentry/utils" "6.8.0" + tslib "^1.9.3" + +"@sentry/core@6.7.1": + version "6.7.1" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-6.7.1.tgz#c3aaa6415d06bec65ac446b13b84f073805633e3" + integrity sha512-VAv8OR/7INn2JfiLcuop4hfDcyC7mfL9fdPndQEhlacjmw8gRrgXjR7qyhnCTgzFLkHI7V5bcdIzA83TRPYQpA== + dependencies: + "@sentry/hub" "6.7.1" + "@sentry/minimal" "6.7.1" + "@sentry/types" "6.7.1" + "@sentry/utils" "6.7.1" + tslib "^1.9.3" + +"@sentry/core@6.8.0": + version "6.8.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-6.8.0.tgz#bfac76844deee9126460c18dc6166015992efdc3" + integrity sha512-vJzWt/znEB+JqVwtwfjkRrAYRN+ep+l070Ti8GhJnvwU4IDtVlV3T/jVNrj6rl6UChcczaJQMxVxtG5x0crlAA== + dependencies: + "@sentry/hub" "6.8.0" + "@sentry/minimal" "6.8.0" + "@sentry/types" "6.8.0" + "@sentry/utils" "6.8.0" + tslib "^1.9.3" + +"@sentry/electron@^2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@sentry/electron/-/electron-2.5.0.tgz#4168ff04ee01cb5a99ce042f3435660a510c634d" + integrity sha512-OiJWi9BKtlj4UeoaCArVXIvfW808fgW1GLmeiC7wD7B64ALHSYSwu8tkqZK+IMVhPmQN04AUyoYXrZohfJ7sOg== + dependencies: + "@sentry/browser" "6.7.1" + "@sentry/core" "6.7.1" + "@sentry/minimal" "6.7.1" + "@sentry/node" "6.7.1" + "@sentry/types" "6.7.1" + "@sentry/utils" "6.7.1" + tslib "^2.2.0" + +"@sentry/hub@6.7.1": + version "6.7.1" + resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-6.7.1.tgz#d46d24deec67f0731a808ca16796e6765b371bc1" + integrity sha512-eVCTWvvcp6xa0A5GGNHMQEWslmKPlisE5rGmsV/kjvSUv3zSrI0eIDfb51ikdnCiBjHpK2NBWP8Vy8cZOEJegg== + dependencies: + "@sentry/types" "6.7.1" + "@sentry/utils" "6.7.1" + tslib "^1.9.3" + +"@sentry/hub@6.8.0": + version "6.8.0" + resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-6.8.0.tgz#cb0f8509093919ed3c1ef98ef8cf63dc102a6524" + integrity sha512-hFrI2Ss1fTov7CH64FJpigqRxH7YvSnGeqxT9Jc1BL7nzW/vgCK+Oh2mOZbosTcrzoDv+lE8ViOnSN3w/fo+rg== + dependencies: + "@sentry/types" "6.8.0" + "@sentry/utils" "6.8.0" + tslib "^1.9.3" + +"@sentry/integrations@^6.8.0": + version "6.8.0" + resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-6.8.0.tgz#990d042d683ed4b45fcbe0cbf3e8077587a45f85" + integrity sha512-K8xmWGQFKxkKj5rMwGENm0SbbUs/SVhHE+V8+KkhXFmHAwfNAYTFaNg0Ryu9DADAJ4QYzZvYeRxl8voFecOqzA== + dependencies: + "@sentry/types" "6.8.0" + "@sentry/utils" "6.8.0" + localforage "^1.8.1" + tslib "^1.9.3" + +"@sentry/minimal@6.7.1": + version "6.7.1" + resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-6.7.1.tgz#babf85ee2f167e9dcf150d750d7a0b250c98449c" + integrity sha512-HDDPEnQRD6hC0qaHdqqKDStcdE1KhkFh0RCtJNMCDn0zpav8Dj9AteF70x6kLSlliAJ/JFwi6AmQrLz+FxPexw== + dependencies: + "@sentry/hub" "6.7.1" + "@sentry/types" "6.7.1" + tslib "^1.9.3" + +"@sentry/minimal@6.8.0": + version "6.8.0" + resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-6.8.0.tgz#d6c3e4c96f231367aeb2b8a87a83b53d28e7c6db" + integrity sha512-MRxUKXiiYwKjp8mOQMpTpEuIby1Jh3zRTU0cmGZtfsZ38BC1JOle8xlwC4FdtOH+VvjSYnPBMya5lgNHNPUJDQ== + dependencies: + "@sentry/hub" "6.8.0" + "@sentry/types" "6.8.0" + tslib "^1.9.3" + +"@sentry/node@6.7.1": + version "6.7.1" + resolved "https://registry.yarnpkg.com/@sentry/node/-/node-6.7.1.tgz#b09e2eca8e187168feba7bd865a23935bf0f5cc0" + integrity sha512-rtZo1O8ROv4lZwuljQz3iFZW89oXSlgXCG2VqkxQyRspPWu89abROpxLjYzsWwQ8djnur1XjFv51kOLDUTS6Qw== + dependencies: + "@sentry/core" "6.7.1" + "@sentry/hub" "6.7.1" + "@sentry/tracing" "6.7.1" + "@sentry/types" "6.7.1" + "@sentry/utils" "6.7.1" + cookie "^0.4.1" + https-proxy-agent "^5.0.0" + lru_map "^0.3.3" + tslib "^1.9.3" + +"@sentry/react@^6.8.0": + version "6.8.0" + resolved "https://registry.yarnpkg.com/@sentry/react/-/react-6.8.0.tgz#3cf4a2e1ef042ee227836e268e702e9cb3b67e41" + integrity sha512-yXNnDaVw8kIacbwQjA27w8DA74WxmDVw4RlUTJGtq35SDmWsaGN1mwvU+mE1u3zEg927QTCBYig2Zqk8Tdt/fQ== + dependencies: + "@sentry/browser" "6.8.0" + "@sentry/minimal" "6.8.0" + "@sentry/types" "6.8.0" + "@sentry/utils" "6.8.0" + hoist-non-react-statics "^3.3.2" + tslib "^1.9.3" + +"@sentry/tracing@6.7.1": + version "6.7.1" + resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-6.7.1.tgz#b11f0c17a6c5dc14ef44733e5436afb686683268" + integrity sha512-wyS3nWNl5mzaC1qZ2AIp1hjXnfO9EERjMIJjCihs2LWBz1r3efxrHxJHs8wXlNWvrT3KLhq/7vvF5CdU82uPeQ== + dependencies: + "@sentry/hub" "6.7.1" + "@sentry/minimal" "6.7.1" + "@sentry/types" "6.7.1" + "@sentry/utils" "6.7.1" + tslib "^1.9.3" + +"@sentry/types@6.7.1": + version "6.7.1" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.7.1.tgz#c8263e1886df5e815570c4668eb40a1cfaa1c88b" + integrity sha512-9AO7HKoip2MBMNQJEd6+AKtjj2+q9Ze4ooWUdEvdOVSt5drg7BGpK221/p9JEOyJAZwEPEXdcMd3VAIMiOb4MA== + +"@sentry/types@6.8.0", "@sentry/types@^6.8.0": + version "6.8.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.8.0.tgz#97fd531a0ed1e75e65b4a24b26509fb7c15eb7b8" + integrity sha512-PbSxqlh6Fd5thNU5f8EVYBVvX+G7XdPA+ThNb2QvSK8yv3rIf0McHTyF6sIebgJ38OYN7ZFK7vvhC/RgSAfYTA== + +"@sentry/utils@6.7.1": + version "6.7.1" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-6.7.1.tgz#909184ad580f0f6375e1e4d4a6ffd33dfe64a4d1" + integrity sha512-Tq2otdbWlHAkctD+EWTYKkEx6BL1Qn3Z/imkO06/PvzpWvVhJWQ5qHAzz5XnwwqNHyV03KVzYB6znq1Bea9HuA== + dependencies: + "@sentry/types" "6.7.1" + tslib "^1.9.3" + +"@sentry/utils@6.8.0": + version "6.8.0" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-6.8.0.tgz#0ffafa5b69fe0cdeabad5c4a6cc68a426eaa6b37" + integrity sha512-OYlI2JNrcWKMdvYbWNdQwR4QBVv2V0y5wK0U6f53nArv6RsyO5TzwRu5rMVSIZofUUqjoE5hl27jqnR+vpUrsA== + dependencies: + "@sentry/types" "6.8.0" + tslib "^1.9.3" + "@sideway/address@^4.1.0": version "4.1.0" resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.0.tgz#0b301ada10ac4e0e3fa525c90615e0b61a72b78d" @@ -1035,16 +1199,22 @@ "@babel/runtime" "^7.12.5" "@testing-library/dom" "^7.28.1" -"@types/anymatch@*": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a" - integrity sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA== - "@types/aria-query@^4.2.0": version "4.2.0" resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.0.tgz#14264692a9d6e2fa4db3df5e56e94b5e25647ac0" integrity sha512-iIgQNzCm0v7QMhhe4Jjn9uRh+I6GoPmt03CbEtwx3ao8/EfoQcmgtqH4vQ5Db/lxiIGaWDv6nwvunuh0RyX0+A== +"@types/babel__core@^7.0.0": + version "7.1.15" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.15.tgz#2ccfb1ad55a02c83f8e0ad327cbc332f55eb1024" + integrity sha512-bxlMKPDbY8x5h6HBwVzEOk2C8fb6SLfYQ5Jw3uBYuYF1lfWk/kbLd81la82vrIkBb0l+JdmrZaDikPrNxpS/Ew== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + "@types/babel__core@^7.1.7": version "7.1.8" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.8.tgz#057f725aca3641f49fc11c7a87a9de5ec588a5d7" @@ -1078,6 +1248,13 @@ dependencies: "@babel/types" "^7.3.0" +"@types/babel__traverse@^7.0.4": + version "7.14.2" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.14.2.tgz#ffcd470bbb3f8bf30481678fb5502278ca833a43" + integrity sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA== + dependencies: + "@babel/types" "^7.3.0" + "@types/body-parser@*": version "1.19.0" resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f" @@ -1235,14 +1412,6 @@ dependencies: "@types/node" "*" -"@types/glob@*": - version "7.1.2" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.2.tgz#06ca26521353a545d94a0adc74f38a59d232c987" - integrity sha512-VgNIkxK+j7Nz5P7jvUZlRvhuPSmsEfS03b0alKcq5V/STUKAa3Plemsn5mrQUO7am6OErJ4rhGEGJbACclrtRA== - dependencies: - "@types/minimatch" "*" - "@types/node" "*" - "@types/glob@^7.1.1": version "7.1.3" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" @@ -1362,14 +1531,6 @@ dependencies: "@types/istanbul-lib-coverage" "*" -"@types/istanbul-reports@^1.1.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz#e875cc689e47bce549ec81f3df5e6f6f11cfaeb2" - integrity sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw== - dependencies: - "@types/istanbul-lib-coverage" "*" - "@types/istanbul-lib-report" "*" - "@types/istanbul-reports@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz#508b13aa344fa4976234e75dddcc34925737d821" @@ -1385,14 +1546,6 @@ jest-diff "^26.0.0" pretty-format "^26.0.0" -"@types/jest@26.x": - version "26.0.13" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.13.tgz#5a7b9d5312f5dd521a38329c38ee9d3802a0b85e" - integrity sha512-sCzjKow4z9LILc6DhBvn5AkIfmQzDZkgtVVKmGwVrs5tuid38ws281D4l+7x1kP487+FlKDh5kfMZ8WSPAdmdA== - dependencies: - jest-diff "^25.2.1" - pretty-format "^25.2.1" - "@types/jest@^26.0.22": version "26.0.22" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.22.tgz#8308a1debdf1b807aa47be2838acdcd91e88fbe6" @@ -1420,7 +1573,7 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd" integrity sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ== -"@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6": +"@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6": version "7.0.7" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA== @@ -1493,10 +1646,10 @@ dependencies: "@types/node" "*" -"@types/mock-fs@^4.10.0": - version "4.10.0" - resolved "https://registry.yarnpkg.com/@types/mock-fs/-/mock-fs-4.10.0.tgz#460061b186993d76856f669d5317cda8a007c24b" - integrity sha512-FQ5alSzmHMmliqcL36JqIA4Yyn9jyJKvRSGV3mvPh108VFatX7naJDzSG4fnFQNZFq9dIx0Dzoe6ddflMB2Xkg== +"@types/mock-fs@^4.13.1": + version "4.13.1" + resolved "https://registry.yarnpkg.com/@types/mock-fs/-/mock-fs-4.13.1.tgz#9201554ceb23671badbfa8ac3f1fa9e0706305be" + integrity sha512-m6nFAJ3lBSnqbvDZioawRvpLXSaPyn52Srf7OfzjubYbYX8MTUdIgDxQl0wEapm4m/pNYSd9TXocpQ0TvZFlYA== dependencies: "@types/node" "*" @@ -1560,13 +1713,13 @@ resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.0.1.tgz#b6e98083f13faa1e5231bfa3bdb1b0feff536b6d" integrity sha512-boy4xPNEtiw6N3abRhBi/e7hNvy3Tt8E9ZRAQrwAGzoCGZS/1wjo9KY7JHhnfnEsG5wSjDbymCozUM9a3ea7OQ== -"@types/progress-bar-webpack-plugin@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@types/progress-bar-webpack-plugin/-/progress-bar-webpack-plugin-2.1.0.tgz#f95b7e1199b5f5e8156321d2411f6fb06c833559" - integrity sha512-HCyeEuuFzsXvIkbchGKJUhXRBGio7BlvVn0ULuBRE50UofuFy2Q8HvR+Q8C2w+QlBUoM+AjerGJU8DhyqYVw7g== +"@types/progress-bar-webpack-plugin@^2.1.2": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@types/progress-bar-webpack-plugin/-/progress-bar-webpack-plugin-2.1.2.tgz#1097946f06efcc087fb2563b153ee61240d9e1dd" + integrity sha512-zSmm7M96PAwwD/YZp7ML9KOtfaRRC2HcU8mrddySWLlSpIGQPynVoc7V97lS0U4tIxlXl02FDQbLSzZq52BEAg== dependencies: "@types/progress" "*" - "@types/webpack" "*" + "@types/webpack" "^4" "@types/progress@*": version "2.0.3" @@ -1757,14 +1910,6 @@ dependencies: "@types/node" "*" -"@types/shelljs@^0.8.8": - version "0.8.8" - resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.8.8.tgz#e439c69929b88a2c8123c1a55e09eb708315addf" - integrity sha512-lD3LWdg6j8r0VRBFahJVaxoW0SIcswxKaFUrmKl33RJVeeoNYQAz4uqCJ5Z6v4oIBOsC5GozX+I5SorIKiTcQA== - dependencies: - "@types/glob" "*" - "@types/node" "*" - "@types/shot@*": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/shot/-/shot-4.0.0.tgz#7545500c489b65c69b5bc5446ba4fef3bd26af92" @@ -1784,10 +1929,10 @@ dependencies: "@types/node" "*" -"@types/stack-utils@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" - integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw== +"@types/stack-utils@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" + integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== "@types/stream-buffers@^3.0.3": version "3.0.3" @@ -1801,10 +1946,15 @@ resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.5.tgz#9adbc12950582aa65ead76bffdf39fe0c27a3c02" integrity sha512-/gG2M/Imw7cQFp8PGvz/SwocNrmKFjFsm5Pb8HdbHkZ1K8pmuPzOX4VeVoiEecFCVf4CsN1r3/BRvx+6sNqwtQ== -"@types/tar@^4.0.3", "@types/tar@^4.0.4": - version "4.0.4" - resolved "https://registry.yarnpkg.com/@types/tar/-/tar-4.0.4.tgz#d680de60855e7778a51c672b755869a3b8d2889f" - integrity sha512-0Xv+xcmkTsOZdIF4yCnd7RkOOyfyqPaqJ7RZFKnwdxfDbkN3eAAE9sHl8zJFqBz4VhxolW9EErbjR1oyH7jK2A== +"@types/tapable@^1": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.8.tgz#b94a4391c85666c7b73299fd3ad79d4faa435310" + integrity sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ== + +"@types/tar@^4.0.3", "@types/tar@^4.0.5": + version "4.0.5" + resolved "https://registry.yarnpkg.com/@types/tar/-/tar-4.0.5.tgz#5f953f183e36a15c6ce3f336568f6051b7b183f3" + integrity sha512-cgwPhNEabHaZcYIy5xeMtux2EmYBitfqEceBUi2t5+ETy4dW6kswt6WX4+HqLeiiKOo42EXbGiDmVJ2x+vi37Q== dependencies: "@types/minipass" "*" "@types/node" "*" @@ -1904,16 +2054,16 @@ "@types/source-list-map" "*" source-map "^0.7.3" -"@types/webpack@*", "@types/webpack@^4.41.17", "@types/webpack@^4.41.8": - version "4.41.17" - resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.17.tgz#0a69005e644d657c85b7d6ec1c826a71bebd1c93" - integrity sha512-6FfeCidTSHozwKI67gIVQQ5Mp0g4X96c2IXxX75hYEQJwST/i6NyZexP//zzMOBb+wG9jJ7oO8fk9yObP2HWAw== +"@types/webpack@*", "@types/webpack@^4", "@types/webpack@^4.41.17", "@types/webpack@^4.41.8": + version "4.41.30" + resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.30.tgz#fd3db6d0d41e145a8eeeafcd3c4a7ccde9068ddc" + integrity sha512-GUHyY+pfuQ6haAfzu4S14F+R5iGRwN6b2FRNJY7U0NilmFAqbsOfK6j1HwuLBAqwRIT+pVdNDJGJ6e8rpp0KHA== dependencies: - "@types/anymatch" "*" "@types/node" "*" - "@types/tapable" "*" + "@types/tapable" "^1" "@types/uglify-js" "*" "@types/webpack-sources" "*" + anymatch "^3.0.0" source-map "^0.6.0" "@types/ws@^6.0.1": @@ -2283,21 +2433,11 @@ acorn@^6.4.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== -acorn@^7.0.0: +acorn@^7.0.0, acorn@^7.1.1, acorn@^7.4.0: version "7.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^7.1.1: - version "7.2.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.2.0.tgz#17ea7e40d7c8640ff54a694c889c26f31704effe" - integrity sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ== - -acorn@^7.4.0: - version "7.4.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.0.tgz#e1ad486e6c54501634c6c397c5c121daa383607c" - integrity sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w== - agent-base@4, agent-base@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" @@ -2305,6 +2445,13 @@ agent-base@4, agent-base@^4.3.0: dependencies: es6-promisify "^5.0.0" +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + agent-base@~4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" @@ -2340,27 +2487,12 @@ ajv-errors@^1.0.0: resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== -ajv-keywords@^3.1.0, ajv-keywords@^3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da" - integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ== - -ajv-keywords@^3.5.2: +ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: version "3.5.2" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== -ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.0, ajv@^6.12.2, ajv@^6.5.5: - version "6.12.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd" - integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ajv@^6.12.4, ajv@^6.12.5: +ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.0, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5, ajv@^6.5.5: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -2474,6 +2606,14 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" +anymatch@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + anymatch@^3.0.3, anymatch@~3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" @@ -2809,16 +2949,16 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.0.tgz#a17b3a8ea811060e74d47d306122400ad4497ae2" integrity sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA== -babel-jest@^26.0.1: - version "26.0.1" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.0.1.tgz#450139ce4b6c17174b136425bda91885c397bc46" - integrity sha512-Z4GGmSNQ8pX3WS1O+6v3fo41YItJJZsVxG5gIQ+HuB/iuAQBJxMTHTwz292vuYws1LnHfwSRgoqI+nxdy/pcvw== +babel-jest@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.6.3.tgz#d87d25cb0037577a0c89f82e5755c5d293c01056" + integrity sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA== dependencies: - "@jest/transform" "^26.0.1" - "@jest/types" "^26.0.1" + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" "@types/babel__core" "^7.1.7" babel-plugin-istanbul "^6.0.0" - babel-preset-jest "^26.0.0" + babel-preset-jest "^26.6.2" chalk "^4.0.0" graceful-fs "^4.2.4" slash "^3.0.0" @@ -2850,13 +2990,14 @@ babel-plugin-istanbul@^6.0.0: istanbul-lib-instrument "^4.0.0" test-exclude "^6.0.0" -babel-plugin-jest-hoist@^26.0.0: - version "26.0.0" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.0.0.tgz#fd1d35f95cf8849fc65cb01b5e58aedd710b34a8" - integrity sha512-+AuoehOrjt9irZL7DOt2+4ZaTM6dlu1s5TTS46JBa0/qem4dy7VNW3tMb96qeEqcIh20LD73TVNtmVEeymTG7w== +babel-plugin-jest-hoist@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz#8185bd030348d254c6d7dd974355e6a28b21e62d" + integrity sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw== dependencies: "@babel/template" "^7.3.3" "@babel/types" "^7.3.3" + "@types/babel__core" "^7.0.0" "@types/babel__traverse" "^7.0.6" babel-plugin-macros@^2.0.0: @@ -2873,14 +3014,15 @@ babel-plugin-syntax-jsx@^6.18.0: resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" integrity sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY= -babel-preset-current-node-syntax@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-0.1.2.tgz#fb4a4c51fe38ca60fede1dc74ab35eb843cb41d6" - integrity sha512-u/8cS+dEiK1SFILbOC8/rUI3ml9lboKuuMvZ/4aQnQmhecQAgPw5ew066C1ObnEAUmlx7dv/s2z52psWEtLNiw== +babel-preset-current-node-syntax@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" + integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== dependencies: "@babel/plugin-syntax-async-generators" "^7.8.4" "@babel/plugin-syntax-bigint" "^7.8.3" "@babel/plugin-syntax-class-properties" "^7.8.3" + "@babel/plugin-syntax-import-meta" "^7.8.3" "@babel/plugin-syntax-json-strings" "^7.8.3" "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" @@ -2888,14 +3030,15 @@ babel-preset-current-node-syntax@^0.1.2: "@babel/plugin-syntax-object-rest-spread" "^7.8.3" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-top-level-await" "^7.8.3" -babel-preset-jest@^26.0.0: - version "26.0.0" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-26.0.0.tgz#1eac82f513ad36c4db2e9263d7c485c825b1faa6" - integrity sha512-9ce+DatAa31DpR4Uir8g4Ahxs5K4W4L8refzt+qHWQANb6LhGcAEfIFgLUwk67oya2cCUd6t4eUMtO/z64ocNw== +babel-preset-jest@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz#747872b1171df032252426586881d62d31798fee" + integrity sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ== dependencies: - babel-plugin-jest-hoist "^26.0.0" - babel-preset-current-node-syntax "^0.1.2" + babel-plugin-jest-hoist "^26.6.2" + babel-preset-current-node-syntax "^1.0.0" babel-runtime@^6.26.0: version "6.26.0" @@ -2906,9 +3049,9 @@ babel-runtime@^6.26.0: regenerator-runtime "^0.11.0" balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== base64-js@^1.0.2: version "1.3.1" @@ -3736,6 +3879,11 @@ circular-dependency-plugin@^5.2.2: resolved "https://registry.yarnpkg.com/circular-dependency-plugin/-/circular-dependency-plugin-5.2.2.tgz#39e836079db1d3cf2f988dc48c5188a44058b600" integrity sha512-g38K9Cm5WRwlaH6g03B9OEz/0qRizI+2I7n+Gz+L5DxXJAPAiWQvwlYNm1V1jkdpUv95bOe/ASm2vfi/G560jQ== +cjs-module-lexer@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz#4186fcca0eae175970aee870b9fe2d6cf8d5655f" + integrity sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw== + class-utils@^0.3.5: version "0.3.6" resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" @@ -4210,6 +4358,11 @@ cookie@0.4.0: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== +cookie@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" + integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== + copy-anything@^2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-2.0.3.tgz#842407ba02466b0df844819bbe3baebbe5d45d87" @@ -4601,6 +4754,13 @@ debug@3.1.0, debug@~3.1.0: dependencies: ms "2.0.0" +debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== + dependencies: + ms "2.1.2" + debug@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.0.tgz#373687bffa678b38b1cd91f861b63850035ddc87" @@ -4622,13 +4782,6 @@ debug@^3.1.0: dependencies: ms "^2.1.1" -debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" - integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== - dependencies: - ms "2.1.2" - debug@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" @@ -4888,21 +5041,16 @@ didyoumean@^1.2.1: resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.1.tgz#e92edfdada6537d484d73c0172fd1eba0c4976ff" integrity sha1-6S7f2tplN9SE1zwBcv0eugxJdv8= -diff-sequences@^25.2.6: - version "25.2.6" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd" - integrity sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg== - -diff-sequences@^26.0.0: - version "26.0.0" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.0.0.tgz#0760059a5c287637b842bd7085311db7060e88a6" - integrity sha512-JC/eHYEC3aSS0vZGjuoc4vHA0yAQTzhQQldXMeMF+JlxLGJlCO38Gma82NV9gk1jGFz8mDzUMeaKXvjRRdJ2dg== - diff-sequences@^26.5.0: version "26.5.0" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.5.0.tgz#ef766cf09d43ed40406611f11c6d8d9dd8b2fefd" integrity sha512-ZXx86srb/iYy6jG71k++wBN9P9J05UNQ5hQHQd9MtMPvcqXPx/vKU69jfHV637D00Q2gSgPk2D+jSx3l1lDW/Q== +diff-sequences@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1" + integrity sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q== + diff@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" @@ -5107,6 +5255,11 @@ dotenv-expand@^5.1.0: resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== +dotenv@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" + integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== + dotenv@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-5.0.1.tgz#a5317459bd3d79ab88cff6e44057a6a3fbb1fcef" @@ -5282,6 +5435,11 @@ elliptic@^6.0.0, elliptic@^6.5.2: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" +emittery@^0.7.1: + version "0.7.2" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.7.2.tgz#25595908e13af0f5674ab419396e2fb394cdfa82" + integrity sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ== + emoji-regex@^7.0.1: version "7.0.3" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" @@ -5337,19 +5495,10 @@ enhanced-resolve@4.1.0: memory-fs "^0.4.0" tapable "^1.0.0" -enhanced-resolve@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz#2937e2b8066cd0fe7ce0990a98f0d71a35189f66" - integrity sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA== - dependencies: - graceful-fs "^4.1.2" - memory-fs "^0.5.0" - tapable "^1.0.0" - -enhanced-resolve@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz#3b806f3bfafc1ec7de69551ef93cca46c1704126" - integrity sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ== +enhanced-resolve@^4.0.0, enhanced-resolve@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz#2f3cfd84dbe3b487f18f2db2ef1e064a571ca5ec" + integrity sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg== dependencies: graceful-fs "^4.1.2" memory-fs "^0.5.0" @@ -5620,15 +5769,7 @@ eslint-scope@^4.0.3: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-scope@^5.0.0, eslint-scope@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.0.tgz#d0f971dfe59c69e0cada684b23d49dbf82600ce5" - integrity sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w== - dependencies: - esrecurse "^4.1.0" - estraverse "^4.1.1" - -eslint-scope@^5.1.1: +eslint-scope@^5.0.0, eslint-scope@^5.1.0, eslint-scope@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== @@ -5909,16 +6050,16 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2: dependencies: homedir-polyfill "^1.0.1" -expect@^26.0.1: - version "26.0.1" - resolved "https://registry.yarnpkg.com/expect/-/expect-26.0.1.tgz#18697b9611a7e2725e20ba3ceadda49bc9865421" - integrity sha512-QcCy4nygHeqmbw564YxNbHTJlXh47dVID2BUP52cZFpLU9zHViMFK6h07cC1wf7GYCTIigTdAXhVua8Yl1FkKg== +expect@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/expect/-/expect-26.6.2.tgz#c6b996bf26bf3fe18b67b2d0f51fc981ba934417" + integrity sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA== dependencies: - "@jest/types" "^26.0.1" + "@jest/types" "^26.6.2" ansi-styles "^4.0.0" - jest-get-type "^26.0.0" - jest-matcher-utils "^26.0.1" - jest-message-util "^26.0.1" + jest-get-type "^26.3.0" + jest-matcher-utils "^26.6.2" + jest-message-util "^26.6.2" jest-regex-util "^26.0.0" express@^4.17.1: @@ -6180,20 +6321,6 @@ filelist@^1.0.1: dependencies: minimatch "^3.0.4" -filename-reserved-regex@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz#abf73dfab735d045440abfea2d91f389ebbfa229" - integrity sha1-q/c9+rc10EVECr/qLZHzieu/oik= - -filenamify@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-4.1.0.tgz#54d110810ae74eebfe115c1b995bd07e03cf2184" - integrity sha512-KQV/uJDI9VQgN7sHH1Zbk6+42cD6mnQ2HONzkXUfPJ+K2FC8GZ1dpewbbHw0Sz8Tf5k3EVdHVayM4DoAwWlmtg== - dependencies: - filename-reserved-regex "^2.0.0" - strip-outer "^1.0.1" - trim-repeated "^1.0.0" - fill-range@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" @@ -6340,20 +6467,21 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= -fork-ts-checker-webpack-plugin@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.0.0.tgz#43a8efda935aba382ca88ae169ab11b70041c06a" - integrity sha512-XdMGiyz12rl8HFEj1D9NsC0yxevLZqWncMbuhelV1a1h7ciX7ftauTHIBzO0gKGjqoqZG0NqnrnN7xavIHvzDQ== +fork-ts-checker-webpack-plugin@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.2.1.tgz#79326d869797906fa8b24e2abcf9421fc805450d" + integrity sha512-SVi+ZAQOGbtAsUWrZvGzz38ga2YqjWvca1pXQFUArIVXqli0lLoDQ8uS0wg0kSpcwpZmaW5jVCZXQebkyUQSsw== dependencies: "@babel/code-frame" "^7.8.3" - chalk "^2.4.1" + "@types/json-schema" "^7.0.5" + chalk "^4.1.0" cosmiconfig "^6.0.0" deepmerge "^4.2.2" fs-extra "^9.0.0" memfs "^3.1.2" minimatch "^3.0.4" - schema-utils "1.0.0" - semver "^5.6.0" + schema-utils "2.7.0" + semver "^7.3.2" tapable "^1.0.0" form-data@^2.5.0: @@ -6472,10 +6600,10 @@ fs-minipass@^2.0.0: dependencies: minipass "^3.0.0" -fs-monkey@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.1.tgz#4a82f36944365e619f4454d9fff106553067b781" - integrity sha512-fcSa+wyTqZa46iWweI7/ZiUfegOZl0SG8+dltIwFXo7+zYU9J9kpS3NB6pZcSlJdhvIwp81Adx2XhZorncxiaA== +fs-monkey@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.3.tgz#ae3ac92d53bb328efe0e9a1d9541f6ad8d48e2d3" + integrity sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q== fs-vacuum@^1.2.10, fs-vacuum@~1.2.10: version "1.2.10" @@ -7152,17 +7280,17 @@ html-tags@^3.1.0: resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.1.0.tgz#7b5e6f7e665e9fb41f30007ed9e0d41e97fb2140" integrity sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg== -html-webpack-plugin@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-4.3.0.tgz#53bf8f6d696c4637d5b656d3d9863d89ce8174fd" - integrity sha512-C0fzKN8yQoVLTelcJxZfJCE+aAvQiY2VUf3UuKrR4a9k5UMWYOtpDLsaXwATbcVCnI05hUS7L9ULQHWLZhyi3w== +html-webpack-plugin@^4.5.2: + version "4.5.2" + resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-4.5.2.tgz#76fc83fa1a0f12dd5f7da0404a54e2699666bc12" + integrity sha512-q5oYdzjKUIPQVjOosjgvCHQOv9Ett9CYYHlgvJeXG0qQvdSojnBq4vAdQBwn1+yGveAwHCoe/rMR86ozX3+c2A== dependencies: "@types/html-minifier-terser" "^5.0.0" "@types/tapable" "^1.0.5" "@types/webpack" "^4.41.8" html-minifier-terser "^5.0.1" loader-utils "^1.2.3" - lodash "^4.17.15" + lodash "^4.17.20" pretty-error "^2.1.1" tapable "^1.1.3" util.promisify "1.0.0" @@ -7288,6 +7416,14 @@ https-proxy-agent@^2.2.3: agent-base "^4.3.0" debug "^3.1.0" +https-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" + integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== + dependencies: + agent-base "6" + debug "4" + human-signals@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" @@ -7313,7 +7449,7 @@ iconv-corefoundation@^1.1.5: cli-truncate "^1.1.0" node-addon-api "^1.6.3" -iconv-lite@0.4.24, iconv-lite@^0.4.17: +iconv-lite@0.4.24, iconv-lite@^0.4.17, iconv-lite@^0.4.4: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -7334,13 +7470,6 @@ icss-utils@^3.0.1: dependencies: postcss "^6.0.2" -icss-utils@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467" - integrity sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA== - dependencies: - postcss "^7.0.14" - icss-utils@^5.0.0, icss-utils@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" @@ -7427,7 +7556,7 @@ import-fresh@^2.0.0: caller-path "^2.0.0" resolve-from "^3.0.0" -import-fresh@^3.0.0, import-fresh@^3.1.0: +import-fresh@^3.0.0: version "3.2.1" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ== @@ -7435,6 +7564,14 @@ import-fresh@^3.0.0, import-fresh@^3.1.0: parent-module "^1.0.0" resolve-from "^4.0.0" +import-fresh@^3.1.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + import-fresh@^3.2.1: version "3.2.2" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.2.tgz#fc129c160c5d68235507f4331a6baad186bdbc3e" @@ -8084,7 +8221,7 @@ is-wsl@^1.1.0: resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= -is-wsl@^2.1.1: +is-wsl@^2.1.1, is-wsl@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== @@ -8147,7 +8284,7 @@ istanbul-lib-coverage@^3.0.0: resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz#f5944a37c70b550b02a78a5c3b2055b280cec8ec" integrity sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg== -istanbul-lib-instrument@^4.0.0: +istanbul-lib-instrument@^4.0.0, istanbul-lib-instrument@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d" integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ== @@ -8210,67 +8347,57 @@ jest-canvas-mock@^2.3.0: cssfontparser "^1.2.1" moo-color "^1.0.2" -jest-changed-files@^26.0.1: - version "26.0.1" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-26.0.1.tgz#1334630c6a1ad75784120f39c3aa9278e59f349f" - integrity sha512-q8LP9Sint17HaE2LjxQXL+oYWW/WeeXMPE2+Op9X3mY8IEGFVc14xRxFjUuXUbcPAlDLhtWdIEt59GdQbn76Hw== +jest-changed-files@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-26.6.2.tgz#f6198479e1cc66f22f9ae1e22acaa0b429c042d0" + integrity sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ== dependencies: - "@jest/types" "^26.0.1" + "@jest/types" "^26.6.2" execa "^4.0.0" throat "^5.0.0" -jest-cli@^26.0.1: - version "26.0.1" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-26.0.1.tgz#3a42399a4cbc96a519b99ad069a117d955570cac" - integrity sha512-pFLfSOBcbG9iOZWaMK4Een+tTxi/Wcm34geqZEqrst9cZDkTQ1LZ2CnBrTlHWuYAiTMFr0EQeK52ScyFU8wK+w== +jest-cli@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-26.6.3.tgz#43117cfef24bc4cd691a174a8796a532e135e92a" + integrity sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg== dependencies: - "@jest/core" "^26.0.1" - "@jest/test-result" "^26.0.1" - "@jest/types" "^26.0.1" + "@jest/core" "^26.6.3" + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" chalk "^4.0.0" exit "^0.1.2" graceful-fs "^4.2.4" import-local "^3.0.2" is-ci "^2.0.0" - jest-config "^26.0.1" - jest-util "^26.0.1" - jest-validate "^26.0.1" + jest-config "^26.6.3" + jest-util "^26.6.2" + jest-validate "^26.6.2" prompts "^2.0.1" - yargs "^15.3.1" + yargs "^15.4.1" -jest-config@^26.0.1: - version "26.0.1" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-26.0.1.tgz#096a3d4150afadf719d1fab00e9a6fb2d6d67507" - integrity sha512-9mWKx2L1LFgOXlDsC4YSeavnblN6A4CPfXFiobq+YYLaBMymA/SczN7xYTSmLaEYHZOcB98UdoN4m5uNt6tztg== +jest-config@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-26.6.3.tgz#64f41444eef9eb03dc51d5c53b75c8c71f645349" + integrity sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg== dependencies: "@babel/core" "^7.1.0" - "@jest/test-sequencer" "^26.0.1" - "@jest/types" "^26.0.1" - babel-jest "^26.0.1" + "@jest/test-sequencer" "^26.6.3" + "@jest/types" "^26.6.2" + babel-jest "^26.6.3" chalk "^4.0.0" deepmerge "^4.2.2" glob "^7.1.1" graceful-fs "^4.2.4" - jest-environment-jsdom "^26.0.1" - jest-environment-node "^26.0.1" - jest-get-type "^26.0.0" - jest-jasmine2 "^26.0.1" + jest-environment-jsdom "^26.6.2" + jest-environment-node "^26.6.2" + jest-get-type "^26.3.0" + jest-jasmine2 "^26.6.3" jest-regex-util "^26.0.0" - jest-resolve "^26.0.1" - jest-util "^26.0.1" - jest-validate "^26.0.1" + jest-resolve "^26.6.2" + jest-util "^26.6.2" + jest-validate "^26.6.2" micromatch "^4.0.2" - pretty-format "^26.0.1" - -jest-diff@^25.2.1: - version "25.5.0" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.5.0.tgz#1dd26ed64f96667c068cef026b677dfa01afcfa9" - integrity sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A== - dependencies: - chalk "^3.0.0" - diff-sequences "^25.2.6" - jest-get-type "^25.2.6" - pretty-format "^25.5.0" + pretty-format "^26.6.2" jest-diff@^26.0.0: version "26.6.1" @@ -8282,15 +8409,15 @@ jest-diff@^26.0.0: jest-get-type "^26.3.0" pretty-format "^26.6.1" -jest-diff@^26.0.1: - version "26.0.1" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.0.1.tgz#c44ab3cdd5977d466de69c46929e0e57f89aa1de" - integrity sha512-odTcHyl5X+U+QsczJmOjWw5tPvww+y9Yim5xzqxVl/R1j4z71+fHW4g8qu1ugMmKdFdxw+AtQgs5mupPnzcIBQ== +jest-diff@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.6.2.tgz#1aa7468b52c3a68d7d5c5fdcdfcd5e49bd164394" + integrity sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA== dependencies: chalk "^4.0.0" - diff-sequences "^26.0.0" - jest-get-type "^26.0.0" - pretty-format "^26.0.1" + diff-sequences "^26.6.2" + jest-get-type "^26.3.0" + pretty-format "^26.6.2" jest-docblock@^26.0.0: version "26.0.0" @@ -8299,39 +8426,41 @@ jest-docblock@^26.0.0: dependencies: detect-newline "^3.0.0" -jest-each@^26.0.1: - version "26.0.1" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-26.0.1.tgz#633083061619302fc90dd8f58350f9d77d67be04" - integrity sha512-OTgJlwXCAR8NIWaXFL5DBbeS4QIYPuNASkzSwMCJO+ywo9BEa6TqkaSWsfR7VdbMLdgYJqSfQcIyjJCNwl5n4Q== +jest-each@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-26.6.2.tgz#02526438a77a67401c8a6382dfe5999952c167cb" + integrity sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A== dependencies: - "@jest/types" "^26.0.1" + "@jest/types" "^26.6.2" chalk "^4.0.0" - jest-get-type "^26.0.0" - jest-util "^26.0.1" - pretty-format "^26.0.1" + jest-get-type "^26.3.0" + jest-util "^26.6.2" + pretty-format "^26.6.2" -jest-environment-jsdom@^26.0.1: - version "26.0.1" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-26.0.1.tgz#217690852e5bdd7c846a4e3b50c8ffd441dfd249" - integrity sha512-u88NJa3aptz2Xix2pFhihRBAatwZHWwSiRLBDBQE1cdJvDjPvv7ZGA0NQBxWwDDn7D0g1uHqxM8aGgfA9Bx49g== +jest-environment-jsdom@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz#78d09fe9cf019a357009b9b7e1f101d23bd1da3e" + integrity sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q== dependencies: - "@jest/environment" "^26.0.1" - "@jest/fake-timers" "^26.0.1" - "@jest/types" "^26.0.1" - jest-mock "^26.0.1" - jest-util "^26.0.1" - jsdom "^16.2.2" + "@jest/environment" "^26.6.2" + "@jest/fake-timers" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + jest-mock "^26.6.2" + jest-util "^26.6.2" + jsdom "^16.4.0" -jest-environment-node@^26.0.1: - version "26.0.1" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-26.0.1.tgz#584a9ff623124ff6eeb49e0131b5f7612b310b13" - integrity sha512-4FRBWcSn5yVo0KtNav7+5NH5Z/tEgDLp7VRQVS5tCouWORxj+nI+1tOLutM07Zb2Qi7ja+HEDoOUkjBSWZg/IQ== +jest-environment-node@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-26.6.2.tgz#824e4c7fb4944646356f11ac75b229b0035f2b0c" + integrity sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag== dependencies: - "@jest/environment" "^26.0.1" - "@jest/fake-timers" "^26.0.1" - "@jest/types" "^26.0.1" - jest-mock "^26.0.1" - jest-util "^26.0.1" + "@jest/environment" "^26.6.2" + "@jest/fake-timers" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + jest-mock "^26.6.2" + jest-util "^26.6.2" jest-fetch-mock@^3.0.3: version "3.0.3" @@ -8341,291 +8470,280 @@ jest-fetch-mock@^3.0.3: cross-fetch "^3.0.4" promise-polyfill "^8.1.3" -jest-get-type@^25.2.6: - version "25.2.6" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-25.2.6.tgz#0b0a32fab8908b44d508be81681487dbabb8d877" - integrity sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig== - -jest-get-type@^26.0.0: - version "26.0.0" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.0.0.tgz#381e986a718998dbfafcd5ec05934be538db4039" - integrity sha512-zRc1OAPnnws1EVfykXOj19zo2EMw5Hi6HLbFCSjpuJiXtOWAYIjNsHVSbpQ8bDX7L5BGYGI8m+HmKdjHYFF0kg== - jest-get-type@^26.3.0: version "26.3.0" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0" integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig== -jest-haste-map@^26.0.1: - version "26.0.1" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.0.1.tgz#40dcc03c43ac94d25b8618075804d09cd5d49de7" - integrity sha512-J9kBl/EdjmDsvyv7CiyKY5+DsTvVOScenprz/fGqfLg/pm1gdjbwwQ98nW0t+OIt+f+5nAVaElvn/6wP5KO7KA== +jest-haste-map@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.6.2.tgz#dd7e60fe7dc0e9f911a23d79c5ff7fb5c2cafeaa" + integrity sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w== dependencies: - "@jest/types" "^26.0.1" + "@jest/types" "^26.6.2" "@types/graceful-fs" "^4.1.2" + "@types/node" "*" anymatch "^3.0.3" fb-watchman "^2.0.0" graceful-fs "^4.2.4" - jest-serializer "^26.0.0" - jest-util "^26.0.1" - jest-worker "^26.0.0" + jest-regex-util "^26.0.0" + jest-serializer "^26.6.2" + jest-util "^26.6.2" + jest-worker "^26.6.2" micromatch "^4.0.2" sane "^4.0.3" walker "^1.0.7" - which "^2.0.2" optionalDependencies: fsevents "^2.1.2" -jest-jasmine2@^26.0.1: - version "26.0.1" - resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-26.0.1.tgz#947c40ee816636ba23112af3206d6fa7b23c1c1c" - integrity sha512-ILaRyiWxiXOJ+RWTKupzQWwnPaeXPIoLS5uW41h18varJzd9/7I0QJGqg69fhTT1ev9JpSSo9QtalriUN0oqOg== +jest-jasmine2@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz#adc3cf915deacb5212c93b9f3547cd12958f2edd" + integrity sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg== dependencies: "@babel/traverse" "^7.1.0" - "@jest/environment" "^26.0.1" - "@jest/source-map" "^26.0.0" - "@jest/test-result" "^26.0.1" - "@jest/types" "^26.0.1" + "@jest/environment" "^26.6.2" + "@jest/source-map" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" chalk "^4.0.0" co "^4.6.0" - expect "^26.0.1" + expect "^26.6.2" is-generator-fn "^2.0.0" - jest-each "^26.0.1" - jest-matcher-utils "^26.0.1" - jest-message-util "^26.0.1" - jest-runtime "^26.0.1" - jest-snapshot "^26.0.1" - jest-util "^26.0.1" - pretty-format "^26.0.1" + jest-each "^26.6.2" + jest-matcher-utils "^26.6.2" + jest-message-util "^26.6.2" + jest-runtime "^26.6.3" + jest-snapshot "^26.6.2" + jest-util "^26.6.2" + pretty-format "^26.6.2" throat "^5.0.0" -jest-leak-detector@^26.0.1: - version "26.0.1" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-26.0.1.tgz#79b19ab3f41170e0a78eb8fa754a116d3447fb8c" - integrity sha512-93FR8tJhaYIWrWsbmVN1pQ9ZNlbgRpfvrnw5LmgLRX0ckOJ8ut/I35CL7awi2ecq6Ca4lL59bEK9hr7nqoHWPA== +jest-leak-detector@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz#7717cf118b92238f2eba65054c8a0c9c653a91af" + integrity sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg== dependencies: - jest-get-type "^26.0.0" - pretty-format "^26.0.1" + jest-get-type "^26.3.0" + pretty-format "^26.6.2" -jest-matcher-utils@^26.0.1: - version "26.0.1" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-26.0.1.tgz#12e1fc386fe4f14678f4cc8dbd5ba75a58092911" - integrity sha512-PUMlsLth0Azen8Q2WFTwnSkGh2JZ8FYuwijC8NR47vXKpsrKmA1wWvgcj1CquuVfcYiDEdj985u5Wmg7COEARw== +jest-matcher-utils@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz#8e6fd6e863c8b2d31ac6472eeb237bc595e53e7a" + integrity sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw== dependencies: chalk "^4.0.0" - jest-diff "^26.0.1" - jest-get-type "^26.0.0" - pretty-format "^26.0.1" + jest-diff "^26.6.2" + jest-get-type "^26.3.0" + pretty-format "^26.6.2" -jest-message-util@^26.0.1: - version "26.0.1" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.0.1.tgz#07af1b42fc450b4cc8e90e4c9cef11b33ce9b0ac" - integrity sha512-CbK8uQREZ8umUfo8+zgIfEt+W7HAHjQCoRaNs4WxKGhAYBGwEyvxuK81FXa7VeB9pwDEXeeKOB2qcsNVCAvB7Q== +jest-message-util@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.6.2.tgz#58173744ad6fc0506b5d21150b9be56ef001ca07" + integrity sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA== dependencies: "@babel/code-frame" "^7.0.0" - "@jest/types" "^26.0.1" - "@types/stack-utils" "^1.0.1" + "@jest/types" "^26.6.2" + "@types/stack-utils" "^2.0.0" chalk "^4.0.0" graceful-fs "^4.2.4" micromatch "^4.0.2" + pretty-format "^26.6.2" slash "^3.0.0" stack-utils "^2.0.2" -jest-mock-extended@^1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/jest-mock-extended/-/jest-mock-extended-1.0.10.tgz#a4b1f5b0bb1121acf7c58cd5423d04c473532702" - integrity sha512-R2wKiOgEUPoHZ2kLsAQeQP2IfVEgo3oQqWLSXKdMXK06t3UHkQirA2Xnsdqg/pX6KPWTsdnrzE2ig6nqNjdgVw== +jest-mock-extended@^1.0.16: + version "1.0.16" + resolved "https://registry.yarnpkg.com/jest-mock-extended/-/jest-mock-extended-1.0.16.tgz#f6a96c795acc2f5ae9ee74d7b048c3b4399c6fc8" + integrity sha512-W1lI7cO0ZYmTQUBZH11nSDKyU36SxAyzBjZNxBWcpFnCKHGNWIPdFA8RJMhmobLQ8WLI4y9AMIbYzMN9q/GiRQ== dependencies: ts-essentials "^4.0.0" -jest-mock@^26.0.1: - version "26.0.1" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.0.1.tgz#7fd1517ed4955397cf1620a771dc2d61fad8fd40" - integrity sha512-MpYTBqycuPYSY6xKJognV7Ja46/TeRbAZept987Zp+tuJvMN0YBWyyhG9mXyYQaU3SBI0TUlSaO5L3p49agw7Q== +jest-mock@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.6.2.tgz#d6cb712b041ed47fe0d9b6fc3474bc6543feb302" + integrity sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew== dependencies: - "@jest/types" "^26.0.1" + "@jest/types" "^26.6.2" + "@types/node" "*" -jest-pnp-resolver@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz#ecdae604c077a7fbc70defb6d517c3c1c898923a" - integrity sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ== +jest-pnp-resolver@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" + integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== jest-regex-util@^26.0.0: version "26.0.0" resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-26.0.0.tgz#d25e7184b36e39fd466c3bc41be0971e821fee28" integrity sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A== -jest-resolve-dependencies@^26.0.1: - version "26.0.1" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-26.0.1.tgz#607ba7ccc32151d185a477cff45bf33bce417f0b" - integrity sha512-9d5/RS/ft0vB/qy7jct/qAhzJsr6fRQJyGAFigK3XD4hf9kIbEH5gks4t4Z7kyMRhowU6HWm/o8ILqhaHdSqLw== +jest-resolve-dependencies@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz#6680859ee5d22ee5dcd961fe4871f59f4c784fb6" + integrity sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg== dependencies: - "@jest/types" "^26.0.1" + "@jest/types" "^26.6.2" jest-regex-util "^26.0.0" - jest-snapshot "^26.0.1" + jest-snapshot "^26.6.2" -jest-resolve@^26.0.1: - version "26.0.1" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-26.0.1.tgz#21d1ee06f9ea270a343a8893051aeed940cde736" - integrity sha512-6jWxk0IKZkPIVTvq6s72RH735P8f9eCJW3IM5CX/SJFeKq1p2cZx0U49wf/SdMlhaB/anann5J2nCJj6HrbezQ== +jest-resolve@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-26.6.2.tgz#a3ab1517217f469b504f1b56603c5bb541fbb507" + integrity sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ== dependencies: - "@jest/types" "^26.0.1" + "@jest/types" "^26.6.2" chalk "^4.0.0" graceful-fs "^4.2.4" - jest-pnp-resolver "^1.2.1" - jest-util "^26.0.1" + jest-pnp-resolver "^1.2.2" + jest-util "^26.6.2" read-pkg-up "^7.0.1" - resolve "^1.17.0" + resolve "^1.18.1" slash "^3.0.0" -jest-runner@^26.0.1: - version "26.0.1" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-26.0.1.tgz#ea03584b7ae4bacfb7e533d680a575a49ae35d50" - integrity sha512-CApm0g81b49Znm4cZekYQK67zY7kkB4umOlI2Dx5CwKAzdgw75EN+ozBHRvxBzwo1ZLYZ07TFxkaPm+1t4d8jA== +jest-runner@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-26.6.3.tgz#2d1fed3d46e10f233fd1dbd3bfaa3fe8924be159" + integrity sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ== dependencies: - "@jest/console" "^26.0.1" - "@jest/environment" "^26.0.1" - "@jest/test-result" "^26.0.1" - "@jest/types" "^26.0.1" + "@jest/console" "^26.6.2" + "@jest/environment" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" chalk "^4.0.0" + emittery "^0.7.1" exit "^0.1.2" graceful-fs "^4.2.4" - jest-config "^26.0.1" + jest-config "^26.6.3" jest-docblock "^26.0.0" - jest-haste-map "^26.0.1" - jest-jasmine2 "^26.0.1" - jest-leak-detector "^26.0.1" - jest-message-util "^26.0.1" - jest-resolve "^26.0.1" - jest-runtime "^26.0.1" - jest-util "^26.0.1" - jest-worker "^26.0.0" + jest-haste-map "^26.6.2" + jest-leak-detector "^26.6.2" + jest-message-util "^26.6.2" + jest-resolve "^26.6.2" + jest-runtime "^26.6.3" + jest-util "^26.6.2" + jest-worker "^26.6.2" source-map-support "^0.5.6" throat "^5.0.0" -jest-runtime@^26.0.1: - version "26.0.1" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-26.0.1.tgz#a121a6321235987d294168e282d52b364d7d3f89" - integrity sha512-Ci2QhYFmANg5qaXWf78T2Pfo6GtmIBn2rRaLnklRyEucmPccmCKvS9JPljcmtVamsdMmkyNkVFb9pBTD6si9Lw== +jest-runtime@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-26.6.3.tgz#4f64efbcfac398331b74b4b3c82d27d401b8fa2b" + integrity sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw== dependencies: - "@jest/console" "^26.0.1" - "@jest/environment" "^26.0.1" - "@jest/fake-timers" "^26.0.1" - "@jest/globals" "^26.0.1" - "@jest/source-map" "^26.0.0" - "@jest/test-result" "^26.0.1" - "@jest/transform" "^26.0.1" - "@jest/types" "^26.0.1" + "@jest/console" "^26.6.2" + "@jest/environment" "^26.6.2" + "@jest/fake-timers" "^26.6.2" + "@jest/globals" "^26.6.2" + "@jest/source-map" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" "@types/yargs" "^15.0.0" chalk "^4.0.0" + cjs-module-lexer "^0.6.0" collect-v8-coverage "^1.0.0" exit "^0.1.2" glob "^7.1.3" graceful-fs "^4.2.4" - jest-config "^26.0.1" - jest-haste-map "^26.0.1" - jest-message-util "^26.0.1" - jest-mock "^26.0.1" + jest-config "^26.6.3" + jest-haste-map "^26.6.2" + jest-message-util "^26.6.2" + jest-mock "^26.6.2" jest-regex-util "^26.0.0" - jest-resolve "^26.0.1" - jest-snapshot "^26.0.1" - jest-util "^26.0.1" - jest-validate "^26.0.1" + jest-resolve "^26.6.2" + jest-snapshot "^26.6.2" + jest-util "^26.6.2" + jest-validate "^26.6.2" slash "^3.0.0" strip-bom "^4.0.0" - yargs "^15.3.1" + yargs "^15.4.1" -jest-serializer@^26.0.0: - version "26.0.0" - resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-26.0.0.tgz#f6c521ddb976943b93e662c0d4d79245abec72a3" - integrity sha512-sQGXLdEGWFAE4wIJ2ZaIDb+ikETlUirEOBsLXdoBbeLhTHkZUJwgk3+M8eyFizhM6le43PDCCKPA1hzkSDo4cQ== +jest-serializer@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-26.6.2.tgz#d139aafd46957d3a448f3a6cdabe2919ba0742d1" + integrity sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g== dependencies: + "@types/node" "*" graceful-fs "^4.2.4" -jest-snapshot@^26.0.1: - version "26.0.1" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-26.0.1.tgz#1baa942bd83d47b837a84af7fcf5fd4a236da399" - integrity sha512-jxd+cF7+LL+a80qh6TAnTLUZHyQoWwEHSUFJjkw35u3Gx+BZUNuXhYvDqHXr62UQPnWo2P6fvQlLjsU93UKyxA== +jest-snapshot@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-26.6.2.tgz#f3b0af1acb223316850bd14e1beea9837fb39c84" + integrity sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og== dependencies: "@babel/types" "^7.0.0" - "@jest/types" "^26.0.1" + "@jest/types" "^26.6.2" + "@types/babel__traverse" "^7.0.4" "@types/prettier" "^2.0.0" chalk "^4.0.0" - expect "^26.0.1" + expect "^26.6.2" graceful-fs "^4.2.4" - jest-diff "^26.0.1" - jest-get-type "^26.0.0" - jest-matcher-utils "^26.0.1" - jest-message-util "^26.0.1" - jest-resolve "^26.0.1" - make-dir "^3.0.0" + jest-diff "^26.6.2" + jest-get-type "^26.3.0" + jest-haste-map "^26.6.2" + jest-matcher-utils "^26.6.2" + jest-message-util "^26.6.2" + jest-resolve "^26.6.2" natural-compare "^1.4.0" - pretty-format "^26.0.1" + pretty-format "^26.6.2" semver "^7.3.2" -jest-util@26.x: - version "26.3.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.3.0.tgz#a8974b191df30e2bf523ebbfdbaeb8efca535b3e" - integrity sha512-4zpn6bwV0+AMFN0IYhH/wnzIQzRaYVrz1A8sYnRnj4UXDXbOVtWmlaZkO9mipFqZ13okIfN87aDoJWB7VH6hcw== +jest-util@^26.1.0, jest-util@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.6.2.tgz#907535dbe4d5a6cb4c47ac9b926f6af29576cbc1" + integrity sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q== dependencies: - "@jest/types" "^26.3.0" + "@jest/types" "^26.6.2" "@types/node" "*" chalk "^4.0.0" graceful-fs "^4.2.4" is-ci "^2.0.0" micromatch "^4.0.2" -jest-util@^26.0.1: - version "26.0.1" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.0.1.tgz#72c4c51177b695fdd795ca072a6f94e3d7cef00a" - integrity sha512-byQ3n7ad1BO/WyFkYvlWQHTsomB6GIewBh8tlGtusiylAlaxQ1UpS0XYH0ngOyhZuHVLN79Qvl6/pMiDMSSG1g== +jest-validate@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.6.2.tgz#23d380971587150467342911c3d7b4ac57ab20ec" + integrity sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ== dependencies: - "@jest/types" "^26.0.1" - chalk "^4.0.0" - graceful-fs "^4.2.4" - is-ci "^2.0.0" - make-dir "^3.0.0" - -jest-validate@^26.0.1: - version "26.0.1" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.0.1.tgz#a62987e1da5b7f724130f904725e22f4e5b2e23c" - integrity sha512-u0xRc+rbmov/VqXnX3DlkxD74rHI/CfS5xaV2VpeaVySjbb1JioNVOyly5b56q2l9ZKe7bVG5qWmjfctkQb0bA== - dependencies: - "@jest/types" "^26.0.1" + "@jest/types" "^26.6.2" camelcase "^6.0.0" chalk "^4.0.0" - jest-get-type "^26.0.0" + jest-get-type "^26.3.0" leven "^3.1.0" - pretty-format "^26.0.1" + pretty-format "^26.6.2" -jest-watcher@^26.0.1: - version "26.0.1" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-26.0.1.tgz#5b5e3ebbdf10c240e22a98af66d645631afda770" - integrity sha512-pdZPydsS8475f89kGswaNsN3rhP6lnC3/QDCppP7bg1L9JQz7oU9Mb/5xPETk1RHDCWeqmVC47M4K5RR7ejxFw== +jest-watcher@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-26.6.2.tgz#a5b683b8f9d68dbcb1d7dae32172d2cca0592975" + integrity sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ== dependencies: - "@jest/test-result" "^26.0.1" - "@jest/types" "^26.0.1" + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" - jest-util "^26.0.1" + jest-util "^26.6.2" string-length "^4.0.1" -jest-worker@^26.0.0: - version "26.0.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.0.0.tgz#4920c7714f0a96c6412464718d0c58a3df3fb066" - integrity sha512-pPaYa2+JnwmiZjK9x7p9BoZht+47ecFCDFA/CJxspHzeDvQcfVBLWzCiWyo+EGrSiQMWZtCFo9iSvMZnAAo8vw== +jest-worker@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" + integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== dependencies: + "@types/node" "*" merge-stream "^2.0.0" supports-color "^7.0.0" -jest@^26.0.1: - version "26.0.1" - resolved "https://registry.yarnpkg.com/jest/-/jest-26.0.1.tgz#5c51a2e58dff7525b65f169721767173bf832694" - integrity sha512-29Q54kn5Bm7ZGKIuH2JRmnKl85YRigp0o0asTc6Sb6l2ch1DCXIeZTLLFy9ultJvhkTqbswF5DEx4+RlkmCxWg== +jest@26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest/-/jest-26.6.3.tgz#40e8fdbe48f00dfa1f0ce8121ca74b88ac9148ef" + integrity sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q== dependencies: - "@jest/core" "^26.0.1" + "@jest/core" "^26.6.3" import-local "^3.0.2" - jest-cli "^26.0.1" + jest-cli "^26.6.3" joi@^17.3.0: version "17.3.0" @@ -8703,38 +8821,6 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= -jsdom@^16.2.2: - version "16.2.2" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.2.2.tgz#76f2f7541646beb46a938f5dc476b88705bedf2b" - integrity sha512-pDFQbcYtKBHxRaP55zGXCJWgFHkDAYbKcsXEK/3Icu9nKYZkutUXfLBwbD+09XDutkYSHcgfQLZ0qvpAAm9mvg== - dependencies: - abab "^2.0.3" - acorn "^7.1.1" - acorn-globals "^6.0.0" - cssom "^0.4.4" - cssstyle "^2.2.0" - data-urls "^2.0.0" - decimal.js "^10.2.0" - domexception "^2.0.1" - escodegen "^1.14.1" - html-encoding-sniffer "^2.0.1" - is-potential-custom-element-name "^1.0.0" - nwsapi "^2.2.0" - parse5 "5.1.1" - request "^2.88.2" - request-promise-native "^1.0.8" - saxes "^5.0.0" - symbol-tree "^3.2.4" - tough-cookie "^3.0.1" - w3c-hr-time "^1.0.2" - w3c-xmlserializer "^2.0.0" - webidl-conversions "^6.0.0" - whatwg-encoding "^1.0.5" - whatwg-mimetype "^2.3.0" - whatwg-url "^8.0.0" - ws "^7.2.3" - xml-name-validator "^3.0.0" - jsdom@^16.4.0: version "16.4.0" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.4.0.tgz#36005bde2d136f73eee1a830c6d45e55408edddb" @@ -8822,10 +8908,10 @@ json3@^3.3.2: resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81" integrity sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA== -json5@2.x, json5@^2.1.2: - version "2.1.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43" - integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA== +json5@2.x, json5@^2.1.2, json5@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" + integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== dependencies: minimist "^1.2.5" @@ -8841,13 +8927,6 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" -json5@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" - integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== - dependencies: - minimist "^1.2.5" - jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" @@ -9089,12 +9168,13 @@ lcid@^2.0.0: dependencies: invert-kv "^2.0.0" -less@^3.11.1: - version "3.13.1" - resolved "https://registry.yarnpkg.com/less/-/less-3.13.1.tgz#0ebc91d2a0e9c0c6735b83d496b0ab0583077909" - integrity sha512-SwA1aQXGUvp+P5XdZslUOhhLnClSLIjWvJhmd+Vgib5BFIr9lMNlQwmwUNOjXThF/A0x+MCYYPeWEfeWiLRnTw== +less@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/less/-/less-4.1.1.tgz#15bf253a9939791dc690888c3ff424f3e6c7edba" + integrity sha512-w09o8tZFPThBscl5d0Ggp3RcrKIouBoQscnOMgFH3n5V3kN/CXGHNfCkRPtxJk6nKryDXaV9aHLK55RXuH4sAw== dependencies: copy-anything "^2.0.1" + parse-node-version "^1.0.1" tslib "^1.10.0" optionalDependencies: errno "^0.1.1" @@ -9102,7 +9182,7 @@ less@^3.11.1: image-size "~0.5.0" make-dir "^2.1.0" mime "^1.4.1" - native-request "^1.0.5" + needle "^2.5.2" source-map "~0.6.0" leven@^3.1.0: @@ -9260,6 +9340,13 @@ libnpx@^10.2.4: y18n "^4.0.0" yargs "^14.2.3" +lie@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e" + integrity sha1-mkNrLMd0bKWd56QfpGmz77dr2H4= + dependencies: + immediate "~3.0.5" + lie@~3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" @@ -9340,6 +9427,13 @@ loader-utils@^2.0.0: emojis-list "^3.0.0" json5 "^2.1.2" +localforage@^1.8.1: + version "1.9.0" + resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.9.0.tgz#f3e4d32a8300b362b4634cc4e066d9d00d2f09d1" + integrity sha512-rR1oyNrKulpe+VM9cYmcFn6tsHuokyVHFaCM3+osEmxaHTbEk8oQu6eGDfS6DQLWi/N67XRmB8ECG37OES368g== + dependencies: + lie "3.1.1" + locate-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" @@ -9417,11 +9511,6 @@ lodash.isequal@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= -lodash.memoize@4.x: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= - lodash.sortby@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" @@ -9452,7 +9541,7 @@ lodash.without@~4.4.0: resolved "https://registry.yarnpkg.com/lodash.without/-/lodash.without-4.4.0.tgz#3cd4574a00b67bae373a94b748772640507b7aac" integrity sha1-PNRXSgC2e643OpS3SHcmQFB7eqw= -lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.3.0, lodash@^4.8.0, lodash@~4.17.10: +lodash@4.x, lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.3.0, lodash@^4.8.0, lodash@~4.17.10: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -9527,6 +9616,11 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +lru_map@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd" + integrity sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0= + lunr@^2.3.9: version "2.3.9" resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.9.tgz#18b123142832337dd6e964df1a5a7707b25d35e1" @@ -9682,11 +9776,11 @@ mem@^4.0.0: p-is-promise "^2.0.0" memfs@^3.1.2: - version "3.2.0" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.2.0.tgz#f9438e622b5acd1daa8a4ae160c496fdd1325b26" - integrity sha512-f/xxz2TpdKv6uDn6GtHee8ivFyxwxmPuXatBb1FBwxYNuVpbM3k/Y1Z+vC0mH/dIXXrukYfe3qe5J32Dfjg93A== + version "3.2.2" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.2.2.tgz#5de461389d596e3f23d48bb7c2afb6161f4df40e" + integrity sha512-RE0CwmIM3CEvpcdK3rZ19BC4E6hv9kADkMN5rPduRak58cNArWLi/9jFLsa4rhsjfVxMP3v0jO7FHXq7SvFY5Q== dependencies: - fs-monkey "1.0.1" + fs-monkey "1.0.3" "memoize-one@>=3.1.1 <6", memoize-one@^5.0.0, memoize-one@^5.1.1: version "5.1.1" @@ -9988,10 +10082,10 @@ mobx@^6.3.0: resolved "https://registry.yarnpkg.com/mobx/-/mobx-6.3.0.tgz#a8fb693c3047bdfcb1eaff9aa48e36a7eb084f96" integrity sha512-Aa1+VXsg4WxqJMTQfWoYuJi5UD10VZhiobSmcs5kcmI3BIT0aVtn7DysvCeDADCzl7dnbX+0BTHUj/v7gLlZpQ== -mock-fs@^4.12.0: - version "4.12.0" - resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.12.0.tgz#a5d50b12d2d75e5bec9dac3b67ffe3c41d31ade4" - integrity sha512-/P/HtrlvBxY4o/PzXY9cCNBrdylDNxg7gnrv2sMNxj+UJ2m8jSpl0/A6fuJeNAWr99ZvGWH8XCbE0vmnM5KupQ== +mock-fs@^4.14.0: + version "4.14.0" + resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.14.0.tgz#ce5124d2c601421255985e6e94da80a7357b1b18" + integrity sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw== modern-normalize@^1.1.0: version "1.1.0" @@ -10099,11 +10193,6 @@ napi-build-utils@^1.0.1: resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== -native-request@^1.0.5: - version "1.0.8" - resolved "https://registry.yarnpkg.com/native-request/-/native-request-1.0.8.tgz#8f66bf606e0f7ea27c0e5995eb2f5d03e33ae6fb" - integrity sha512-vU2JojJVelUGp6jRcLwToPoWGxSx23z/0iX+I77J3Ht17rf2INGjrhOoQnjVo60nQd8wVsgzKkPfRXBiVdD2ag== - native-url@^0.2.6: version "0.2.6" resolved "https://registry.yarnpkg.com/native-url/-/native-url-0.2.6.tgz#ca1258f5ace169c716ff44eccbddb674e10399ae" @@ -10116,6 +10205,15 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= +needle@^2.5.2: + version "2.8.0" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.8.0.tgz#1c8ef9c1a2c29dcc1e83d73809d7bc681c80a048" + integrity sha512-ZTq6WYkN/3782H1393me3utVYdq2XyqNUFBsprEE3VMAT0+hP/cItpnITpqsY6ep2yeFE4Tqtqwc74VqUlUYtw== + dependencies: + debug "^3.2.6" + iconv-lite "^0.4.4" + sax "^1.2.4" + negotiator@0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" @@ -10274,16 +10372,16 @@ node-modules-regexp@^1.0.0: resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= -node-notifier@^7.0.0: - version "7.0.1" - resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-7.0.1.tgz#a355e33e6bebacef9bf8562689aed0f4230ca6f9" - integrity sha512-VkzhierE7DBmQEElhTGJIoiZa1oqRijOtgOlsXg32KrJRXsPy0NXFBqWGW/wTswnJlDCs5viRYaqWguqzsKcmg== +node-notifier@^8.0.0: + version "8.0.2" + resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-8.0.2.tgz#f3167a38ef0d2c8a866a83e318c1ba0efeb702c5" + integrity sha512-oJP/9NAdd9+x2Q+rfphB2RJCHjod70RcRLjosiPMMu5gjIfwVnOUGq2nbTjTUbmy0DJ/tFIVT30+Qe3nzl4TJg== dependencies: growly "^1.3.0" - is-wsl "^2.1.1" - semver "^7.2.1" + is-wsl "^2.2.0" + semver "^7.3.2" shellwords "^0.1.1" - uuid "^7.0.3" + uuid "^8.3.0" which "^2.0.2" node-pty@^0.9.0: @@ -11216,15 +11314,20 @@ parse-json@^4.0.0: json-parse-better-errors "^1.0.1" parse-json@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.0.0.tgz#73e5114c986d143efa3712d4ea24db9a4266f60f" - integrity sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw== + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== dependencies: "@babel/code-frame" "^7.0.0" error-ex "^1.3.1" - json-parse-better-errors "^1.0.1" + json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" +parse-node-version@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parse-node-version/-/parse-node-version-1.0.1.tgz#e2b5dbede00e7fa9bc363607f53327e8b073189b" + integrity sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA== + parse-passwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" @@ -11505,7 +11608,7 @@ postcss-js@^3.0.3: camelcase-css "^2.0.1" postcss "^8.1.6" -postcss-load-config@^2.0.0, postcss-load-config@^2.1.0: +postcss-load-config@^2.0.0: version "2.1.2" resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.1.2.tgz#c5ea504f2c4aef33c7359a34de3573772ad7502a" integrity sha512-/rDeGV6vMUo3mwJZmeHfEDvwnTKKqQ0S7OHUi/kJvvtx3aWtyWG2/0ZWnzCt2keEclwN6Tf0DST2v9kITdOKYw== @@ -11611,7 +11714,7 @@ postcss@^6.0.14, postcss@^6.0.2: source-map "^0.6.1" supports-color "^5.4.0" -postcss@^7.0.0, postcss@^7.0.14, postcss@^7.0.27: +postcss@^7.0.0: version "7.0.35" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.35.tgz#d2be00b998f7f211d8a276974079f2e92b970e24" integrity sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg== @@ -11620,10 +11723,10 @@ postcss@^7.0.0, postcss@^7.0.14, postcss@^7.0.27: source-map "^0.6.1" supports-color "^6.1.0" -postcss@^8.1.6, postcss@^8.2.1, postcss@^8.2.14, postcss@^8.2.15: - version "8.3.0" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.0.tgz#b1a713f6172ca427e3f05ef1303de8b65683325f" - integrity sha512-+ogXpdAjWGa+fdYY5BQ96V/6tAo+TdSSIMP5huJBIygdWwKtVoB5JWZ7yUd4xZ8r+8Kvvx4nyg/PQ071H4UtcQ== +postcss@^8.1.6, postcss@^8.2.1, postcss@^8.2.14, postcss@^8.2.15, postcss@^8.3.0: + version "8.3.5" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.5.tgz#982216b113412bc20a86289e91eb994952a5b709" + integrity sha512-NxTuJocUhYGsMiMFHDUkmjSKT3EdH4/WbGF6GCi1NDGk+vbcUTun4fpbOqaPtD8IIsztA2ilZm2DhYCuyN58gA== dependencies: colorette "^1.2.2" nanoid "^3.1.23" @@ -11683,16 +11786,6 @@ pretty-error@^2.1.1: renderkid "^2.0.1" utila "~0.4" -pretty-format@^25.2.1, pretty-format@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.5.0.tgz#7873c1d774f682c34b8d48b6743a2bf2ac55791a" - integrity sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ== - dependencies: - "@jest/types" "^25.5.0" - ansi-regex "^5.0.0" - ansi-styles "^4.0.0" - react-is "^16.12.0" - pretty-format@^26.0.0, pretty-format@^26.6.1: version "26.6.1" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.1.tgz#af9a2f63493a856acddeeb11ba6bcf61989660a8" @@ -11703,16 +11796,6 @@ pretty-format@^26.0.0, pretty-format@^26.6.1: ansi-styles "^4.0.0" react-is "^17.0.1" -pretty-format@^26.0.1: - version "26.0.1" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.0.1.tgz#a4fe54fe428ad2fd3413ca6bbd1ec8c2e277e197" - integrity sha512-SWxz6MbupT3ZSlL0Po4WF/KujhQaVehijR2blyRDCzk9e45EaYMVhMBn49fnRuHxtkSpXTes1GxNpVmH86Bxfw== - dependencies: - "@jest/types" "^26.0.1" - ansi-regex "^5.0.0" - ansi-styles "^4.0.0" - react-is "^16.12.0" - pretty-format@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93" @@ -12023,13 +12106,13 @@ raw-body@2.4.0: iconv-lite "0.4.24" unpipe "1.0.0" -raw-loader@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-4.0.1.tgz#14e1f726a359b68437e183d5a5b7d33a3eba6933" - integrity sha512-baolhQBSi3iNh1cglJjA0mYzga+wePk7vdEX//1dTFd+v4TsQlQE0jitJSNF1OIP82rdYulH7otaVmdlDaJ64A== +raw-loader@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-4.0.2.tgz#1aac6b7d1ad1501e66efdac1522c73e59a584eb6" + integrity sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA== dependencies: loader-utils "^2.0.0" - schema-utils "^2.6.5" + schema-utils "^3.0.0" rc@^1.0.1, rc@^1.1.6, rc@^1.2.7, rc@^1.2.8: version "1.2.8" @@ -12070,7 +12153,7 @@ react-input-autosize@^2.2.2: dependencies: prop-types "^15.5.8" -react-is@^16.12.0, react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1: +react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -12653,7 +12736,7 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@^1.1.6, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.17.0, resolve@^1.20.0, resolve@^1.3.2: +resolve@^1.1.6, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.18.1, resolve@^1.20.0, resolve@^1.3.2: version "1.20.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== @@ -12868,10 +12951,10 @@ sass-loader@^8.0.2: schema-utils "^2.6.1" semver "^6.3.0" -sass@^1.26.5: - version "1.32.12" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.32.12.tgz#a2a47ad0f1c168222db5206444a30c12457abb9f" - integrity sha512-zmXn03k3hN0KaiVTjohgkg98C3UowhL1/VSGdj4/VAAiMKGQOE80PFPxFP2Kyq0OUskPKcY5lImkhBKEHlypJA== +sass@^1.32.13: + version "1.35.2" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.35.2.tgz#b732314fcdaf7ef8d0f1698698adc378043cb821" + integrity sha512-jhO5KAR+AMxCEwIH3v+4zbB2WB0z67V1X0jbapfVwQQdjHZUGUyukpnoM6+iCMfsIUC016w9OPKQ5jrNOS9uXw== dependencies: chokidar ">=3.0.0 <4.0.0" @@ -12895,7 +12978,16 @@ scheduler@^0.20.1: loose-envify "^1.1.0" object-assign "^4.1.1" -schema-utils@1.0.0, schema-utils@^1.0.0: +schema-utils@2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7" + integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A== + dependencies: + "@types/json-schema" "^7.0.4" + ajv "^6.12.2" + ajv-keywords "^3.4.1" + +schema-utils@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g== @@ -13852,13 +13944,6 @@ strip-json-comments@~2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= -strip-outer@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/strip-outer/-/strip-outer-1.0.1.tgz#b2fd2abf6604b9d1e6013057195df836b8a9d631" - integrity sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg== - dependencies: - escape-string-regexp "^1.0.2" - style-loader@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-2.0.0.tgz#9669602fd4690740eaaec137799a03addbbc393c" @@ -13867,7 +13952,7 @@ style-loader@^2.0.0: loader-utils "^2.0.0" schema-utils "^3.0.0" -stylus@^0.54.7: +stylus@^0.54.8: version "0.54.8" resolved "https://registry.yarnpkg.com/stylus/-/stylus-0.54.8.tgz#3da3e65966bc567a7b044bfe0eece653e099d147" integrity sha512-vr54Or4BZ7pJafo2mpf0ZcwA74rpuYCZbxrHBsH8kbcXOwSfvBFwsRfpGO5OD5fhG5HDCFW737PKaawI7OqEAg== @@ -14337,13 +14422,6 @@ trim-newlines@^1.0.0: resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" integrity sha1-WIeWa7WCpFA6QetST301ARgVphM= -trim-repeated@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/trim-repeated/-/trim-repeated-1.0.0.tgz#e3646a2ea4e891312bf7eace6cfb05380bc01c21" - integrity sha1-42RqLqTokTEr9+rObPsFOAvAHCE= - dependencies: - escape-string-regexp "^1.0.2" - triple-beam@^1.2.0, triple-beam@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9" @@ -14368,22 +14446,21 @@ ts-essentials@^4.0.0: resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-4.0.0.tgz#506c42b270bbd0465574b90416533175b09205ab" integrity sha512-uQJX+SRY9mtbKU+g9kl5Fi7AEMofPCvHfJkQlaygpPmHPZrtgaBqbWFOYyiA47RhnSwwnXdepUJrgqUYxoUyhQ== -ts-jest@26.3.0: - version "26.3.0" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.3.0.tgz#6b2845045347dce394f069bb59358253bc1338a9" - integrity sha512-Jq2uKfx6bPd9+JDpZNMBJMdMQUC3sJ08acISj8NXlVgR2d5OqslEHOR2KHMgwymu8h50+lKIm0m0xj/ioYdW2Q== +ts-jest@26.5.6: + version "26.5.6" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.5.6.tgz#c32e0746425274e1dfe333f43cd3c800e014ec35" + integrity sha512-rua+rCP8DxpA8b4DQD/6X2HQS8Zy/xzViVYfEs2OQu68tkCuKLV0Md8pmX55+W24uRIyAsf/BajRfxOs+R2MKA== dependencies: - "@types/jest" "26.x" bs-logger "0.x" buffer-from "1.x" fast-json-stable-stringify "2.x" - jest-util "26.x" + jest-util "^26.1.0" json5 "2.x" - lodash.memoize "4.x" + lodash "4.x" make-error "1.x" mkdirp "1.x" semver "7.x" - yargs-parser "18.x" + yargs-parser "20.x" ts-loader@^7.0.5: version "7.0.5" @@ -14427,6 +14504,11 @@ tslib@^2.1.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c" integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w== +tslib@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e" + integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg== + tsutils@^3.17.1: version "3.17.1" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" @@ -14572,23 +14654,23 @@ typeface-roboto@^1.1.13: resolved "https://registry.yarnpkg.com/typeface-roboto/-/typeface-roboto-1.1.13.tgz#9c4517cb91e311706c74823e857b4bac9a764ae5" integrity sha512-YXvbd3a1QTREoD+FJoEkl0VQNJoEjewR2H11IjVv4bp6ahuIcw0yyw/3udC4vJkHw3T3cUh85FTg8eWef3pSaw== -typescript-plugin-css-modules@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/typescript-plugin-css-modules/-/typescript-plugin-css-modules-3.2.0.tgz#44241064395d565f242689b96eb50bf413e68ddf" - integrity sha512-oMe5IDKuPvLBOeaYupAqqq8UMTu7Jo5e0InqkQSSKXuZCcRm0+LQfUVMyM62IFpgzxHLuN32a7YdBxAaSXZrRQ== +typescript-plugin-css-modules@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/typescript-plugin-css-modules/-/typescript-plugin-css-modules-3.4.0.tgz#4ff6905d88028684d1608c05c62cb6346e5548cc" + integrity sha512-2MdjfSg4MGex1csCWRUwKD+MpgnvcvLLr9bSAMemU/QYGqBsXdez0cc06H/fFhLtRoKJjXg6PSTur3Gy1Umhpw== dependencies: - dotenv "^8.2.0" - icss-utils "^4.1.1" - less "^3.11.1" + dotenv "^10.0.0" + icss-utils "^5.1.0" + less "^4.1.1" lodash.camelcase "^4.3.0" - postcss "^7.0.27" + postcss "^8.3.0" postcss-filter-plugins "^3.0.1" postcss-icss-keyframes "^0.2.1" postcss-icss-selectors "^2.0.3" - postcss-load-config "^2.1.0" + postcss-load-config "^3.0.1" reserved-words "^0.1.2" - sass "^1.26.5" - stylus "^0.54.7" + sass "^1.32.13" + stylus "^0.54.8" tsconfig-paths "^3.9.0" typescript@^4.0.3: @@ -14793,9 +14875,9 @@ update-notifier@^5.1.0: xdg-basedir "^4.0.0" uri-js@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" - integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== dependencies: punycode "^2.1.0" @@ -14912,12 +14994,7 @@ uuid@^3.3.2, uuid@^3.3.3, uuid@^3.4.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -uuid@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b" - integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg== - -uuid@^8.3.2: +uuid@^8.3.0, uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== @@ -14932,10 +15009,10 @@ v8-compile-cache@^2.0.3: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745" integrity sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ== -v8-to-istanbul@^4.1.3: - version "4.1.4" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-4.1.4.tgz#b97936f21c0e2d9996d4985e5c5156e9d4e49cd6" - integrity sha512-Rw6vJHj1mbdK8edjR7+zuJrpDtKIgNdAvTSAcpYfgMIw+u2dPDntD3dgN4XQFLU2/fvFQdzj+EeSGfd/jnY5fQ== +v8-to-istanbul@^7.0.0: + version "7.1.2" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz#30898d1a7fa0c84d225a2c1434fb958f290883c1" + integrity sha512-TxNb7YEUwkLXCQYeudi6lgQ/SZrzNO4kMdlqVxaZPUIUjCv6iSSypUQX70kNBSERpQ8fk48+d61FXk+tgqcWow== dependencies: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^1.6.0" @@ -15088,7 +15165,7 @@ webidl-conversions@^5.0.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA== -webidl-conversions@^6.0.0, webidl-conversions@^6.1.0: +webidl-conversions@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== @@ -15189,10 +15266,10 @@ webpack-sources@^2.2.0: source-list-map "^2.0.1" source-map "^0.6.1" -webpack@^4.44.2: - version "4.44.2" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.44.2.tgz#6bfe2b0af055c8b2d1e90ed2cd9363f841266b72" - integrity sha512-6KJVGlCxYdISyurpQ0IPTklv+DULv05rs2hseIXer6D7KrUicRDLFb4IUM1S6LUAKypPM/nSiVSuv8jHu1m3/Q== +webpack@^4.46.0: + version "4.46.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.46.0.tgz#bf9b4404ea20a073605e0a011d188d77cb6ad542" + integrity sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q== dependencies: "@webassemblyjs/ast" "1.9.0" "@webassemblyjs/helper-module-context" "1.9.0" @@ -15202,7 +15279,7 @@ webpack@^4.44.2: ajv "^6.10.2" ajv-keywords "^3.4.1" chrome-trace-event "^1.0.2" - enhanced-resolve "^4.3.0" + enhanced-resolve "^4.5.0" eslint-scope "^4.0.3" json-parse-better-errors "^1.0.2" loader-runner "^2.4.0" @@ -15507,10 +15584,10 @@ xtend@^4.0.0, xtend@^4.0.2, xtend@~4.0.1: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== -xterm-addon-fit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/xterm-addon-fit/-/xterm-addon-fit-0.4.0.tgz#06e0c5d0a6aaacfb009ef565efa1c81e93d90193" - integrity sha512-p4BESuV/g2L6pZzFHpeNLLnep9mp/DkF3qrPglMiucSFtD8iJxtMufEoEJbN8LZwB4i+8PFpFvVuFrGOSpW05w== +xterm-addon-fit@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/xterm-addon-fit/-/xterm-addon-fit-0.5.0.tgz#2d51b983b786a97dcd6cde805e700c7f913bc596" + integrity sha512-DsS9fqhXHacEmsPxBJZvfj2la30Iz9xk+UKjhQgnYNkrUIN5CYLbw7WEfz117c7+S86S/tpHPfvNxJsF5/G8wQ== xterm@^4.12.0: version "4.12.0" @@ -15552,23 +15629,15 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yaml@^1.10.0, yaml@^1.10.2: +yaml@^1.10.2, yaml@^1.7.2: version "1.10.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== -yaml@^1.7.2: - version "1.10.0" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e" - integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg== - -yargs-parser@18.x, yargs-parser@^18.1.1: - version "18.1.3" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" - integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" +yargs-parser@20.x, yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== yargs-parser@^13.1.0, yargs-parser@^13.1.2: version "13.1.2" @@ -15586,10 +15655,13 @@ yargs-parser@^15.0.1: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^20.2.2: - version "20.2.7" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.7.tgz#61df85c113edfb5a7a4e36eb8aa60ef423cbc90a" - integrity sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw== +yargs-parser@^18.1.2: + version "18.1.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" yargs-parser@^7.0.0: version "7.0.0" @@ -15648,10 +15720,10 @@ yargs@^14.2.3: y18n "^4.0.0" yargs-parser "^15.0.1" -yargs@^15.3.1: - version "15.3.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.3.1.tgz#9505b472763963e54afe60148ad27a330818e98b" - integrity sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA== +yargs@^15.4.1: + version "15.4.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== dependencies: cliui "^6.0.0" decamelize "^1.2.0" @@ -15663,7 +15735,7 @@ yargs@^15.3.1: string-width "^4.2.0" which-module "^2.0.0" y18n "^4.0.0" - yargs-parser "^18.1.1" + yargs-parser "^18.1.2" yargs@^16.2.0: version "16.2.0"