From 7e0aeca1192a9347235f06725578dbd7e1d7f515 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Thu, 14 Oct 2021 08:46:23 -0400 Subject: [PATCH 01/18] Remove exec.command checking (#3881) --- package.json | 1 - src/common/__tests__/kube-helpers.test.ts | 73 +------ src/common/custom-errors.ts | 34 ---- src/common/kube-helpers.ts | 233 +++++++++------------- types/command-exists.d.ts | 32 --- yarn.lock | 5 - 6 files changed, 99 insertions(+), 279 deletions(-) delete mode 100644 src/common/custom-errors.ts delete mode 100644 types/command-exists.d.ts diff --git a/package.json b/package.json index 13f2d355e8..5a6d205087 100644 --- a/package.json +++ b/package.json @@ -193,7 +193,6 @@ "byline": "^5.0.0", "chalk": "^4.1.0", "chokidar": "^3.4.3", - "command-exists": "1.2.9", "conf": "^7.1.2", "crypto-js": "^4.1.1", "electron-devtools-installer": "^3.2.0", diff --git a/src/common/__tests__/kube-helpers.test.ts b/src/common/__tests__/kube-helpers.test.ts index 5d2bd35344..277398ee02 100644 --- a/src/common/__tests__/kube-helpers.test.ts +++ b/src/common/__tests__/kube-helpers.test.ts @@ -20,7 +20,7 @@ */ import { KubeConfig } from "@kubernetes/client-node"; -import { validateKubeConfig, loadConfigFromString, getNodeWarningConditions } from "../kube-helpers"; +import { validateKubeConfig, loadConfigFromString } from "../kube-helpers"; const kubeconfig = ` apiVersion: v1 @@ -120,38 +120,6 @@ describe("kube helpers", () => { ); }); }); - - describe("with invalid exec command", () => { - it("returns an error", () => { - expect(String(validateKubeConfig(kc, "invalidExec"))).toEqual( - expect.stringContaining("User Exec command \"foo\" not found on host. Please ensure binary is found in PATH or use absolute path to binary in Kubeconfig") - ); - }); - }); - }); - - describe("with validateCluster as false", () => { - describe("with invalid cluster object", () => { - it("does not return an error", () => { - expect(validateKubeConfig(kc, "invalidCluster", { validateCluster: false })).toBeUndefined(); - }); - }); - }); - - describe("with validateUser as false", () => { - describe("with invalid user object", () => { - it("does not return an error", () => { - expect(validateKubeConfig(kc, "invalidUser", { validateUser: false })).toBeUndefined(); - }); - }); - }); - - describe("with validateExec as false", () => { - describe("with invalid exec object", () => { - it("does not return an error", () => { - expect(validateKubeConfig(kc, "invalidExec", { validateExec: false })).toBeUndefined(); - }); - }); }); }); @@ -280,43 +248,4 @@ describe("kube helpers", () => { }); }); }); - - describe("getNodeWarningConditions", () => { - it("should return an empty array if no status or no conditions", () => { - expect(getNodeWarningConditions({}).length).toBe(0); - }); - - it("should return an empty array if all conditions are good", () => { - expect(getNodeWarningConditions({ - status: { - conditions: [ - { - type: "Ready", - status: "foobar" - } - ] - } - }).length).toBe(0); - }); - - it("should all not ready conditions", () => { - const conds = getNodeWarningConditions({ - status: { - conditions: [ - { - type: "Ready", - status: "foobar" - }, - { - type: "NotReady", - status: "true" - }, - ] - } - }); - - expect(conds.length).toBe(1); - expect(conds[0].type).toBe("NotReady"); - }); - }); }); diff --git a/src/common/custom-errors.ts b/src/common/custom-errors.ts deleted file mode 100644 index 2cbc75ccf0..0000000000 --- a/src/common/custom-errors.ts +++ /dev/null @@ -1,34 +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. - */ - -export class ExecValidationNotFoundError extends Error { - constructor(execPath: string, isAbsolute: boolean) { - super(`User Exec command "${execPath}" not found on host.`); - let message = `User Exec command "${execPath}" not found on host.`; - - if (!isAbsolute) { - message += ` Please ensure binary is found in PATH or use absolute path to binary in Kubeconfig`; - } - this.message = message; - this.name = this.constructor.name; - Error.captureStackTrace(this, this.constructor); - } -} diff --git a/src/common/kube-helpers.ts b/src/common/kube-helpers.ts index aff05e68c6..16046b4676 100644 --- a/src/common/kube-helpers.ts +++ b/src/common/kube-helpers.ts @@ -19,24 +19,16 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { KubeConfig, V1Node, V1Pod } from "@kubernetes/client-node"; +import { KubeConfig } from "@kubernetes/client-node"; import fse from "fs-extra"; import path from "path"; import os from "os"; import yaml from "js-yaml"; import logger from "../main/logger"; -import commandExists from "command-exists"; -import { ExecValidationNotFoundError } from "./custom-errors"; import { Cluster, Context, newClusters, newContexts, newUsers, User } from "@kubernetes/client-node/dist/config_types"; import { resolvePath } from "./utils"; import Joi from "joi"; -export type KubeConfigValidationOpts = { - validateCluster?: boolean; - validateUser?: boolean; - validateExec?: boolean; -}; - export const kubeConfigDefaultPath = path.join(os.homedir(), ".kube", "config"); export function loadConfigFromFileSync(filePath: string): ConfigResult { @@ -86,35 +78,34 @@ const contextSchema = Joi.object({ }), }); -const kubeConfigSchema = Joi - .object({ - users: Joi - .array() - .items(userSchema) - .optional(), - clusters: Joi - .array() - .items(clusterSchema) - .optional(), - contexts: Joi - .array() - .items(contextSchema) - .optional(), - "current-context": Joi - .string() - .min(1) - .optional(), - }) +const kubeConfigSchema = Joi.object({ + users: Joi + .array() + .items(userSchema) + .optional(), + clusters: Joi + .array() + .items(clusterSchema) + .optional(), + contexts: Joi + .array() + .items(contextSchema) + .optional(), + "current-context": Joi + .string() + .min(1) + .optional(), +}) .required(); -export interface KubeConfigOptions { +interface KubeConfigOptions { clusters: Cluster[]; users: User[]; contexts: Context[]; currentContext?: string; } -export interface OptionsResult { +interface OptionsResult { options: KubeConfigOptions; error: Joi.ValidationError; } @@ -132,7 +123,12 @@ function loadToOptions(rawYaml: string): OptionsResult { arrays: true, } }); - const { clusters: rawClusters, users: rawUsers, contexts: rawContexts, "current-context": currentContext } = value ?? {}; + const { + clusters: rawClusters, + users: rawUsers, + contexts: rawContexts, + "current-context": currentContext, + } = value ?? {}; const clusters = newClusters(rawClusters); const users = newUsers(rawUsers); const contexts = newContexts(rawContexts); @@ -175,66 +171,78 @@ export interface SplitConfigEntry { * Breaks kube config into several configs. Each context as it own KubeConfig object */ export function splitConfig(kubeConfig: KubeConfig): SplitConfigEntry[] { - const { contexts = [] } = kubeConfig; - - return contexts.map(context => { + return kubeConfig.getContexts().map(ctx => { const config = new KubeConfig(); + const cluster = kubeConfig.getCluster(ctx.cluster); + const user = kubeConfig.getUser(ctx.user); + const context = kubeConfig.getContextObject(ctx.name); - config.clusters = [kubeConfig.getCluster(context.cluster)].filter(Boolean); - config.users = [kubeConfig.getUser(context.user)].filter(Boolean); - config.contexts = [kubeConfig.getContextObject(context.name)].filter(Boolean); - config.setCurrentContext(context.name); + if (cluster) { + config.addCluster(cluster); + } + + if (user) { + config.addUser(user); + } + + if (context) { + config.addContext(context); + } + + config.setCurrentContext(ctx.name); return { config, - error: validateKubeConfig(config, context.name)?.toString(), + error: validateKubeConfig(config, ctx.name)?.toString(), }; }); } +/** + * Pretty format the object as human readable yaml, such as would be on the filesystem + * @param kubeConfig The kubeconfig object to format as pretty yaml + * @returns The yaml representation of the kubeconfig object + */ export function dumpConfigYaml(kubeConfig: Partial): string { + const clusters = kubeConfig.clusters.map(cluster => ({ + name: cluster.name, + cluster: { + "certificate-authority-data": cluster.caData, + "certificate-authority": cluster.caFile, + server: cluster.server, + "insecure-skip-tls-verify": cluster.skipTLSVerify + } + })); + const contexts = kubeConfig.contexts.map(context => ({ + name: context.name, + context: { + cluster: context.cluster, + user: context.user, + namespace: context.namespace + } + })); + const users = kubeConfig.users.map(user => ({ + name: user.name, + user: { + "client-certificate-data": user.certData, + "client-certificate": user.certFile, + "client-key-data": user.keyData, + "client-key": user.keyFile, + "auth-provider": user.authProvider, + exec: user.exec, + token: user.token, + username: user.username, + password: user.password + } + })); const config = { apiVersion: "v1", kind: "Config", preferences: {}, "current-context": kubeConfig.currentContext, - clusters: kubeConfig.clusters.map(cluster => { - return { - name: cluster.name, - cluster: { - "certificate-authority-data": cluster.caData, - "certificate-authority": cluster.caFile, - server: cluster.server, - "insecure-skip-tls-verify": cluster.skipTLSVerify - } - }; - }), - contexts: kubeConfig.contexts.map(context => { - return { - name: context.name, - context: { - cluster: context.cluster, - user: context.user, - namespace: context.namespace - } - }; - }), - users: kubeConfig.users.map(user => { - return { - name: user.name, - user: { - "client-certificate-data": user.certData, - "client-certificate": user.certFile, - "client-key-data": user.keyData, - "client-key": user.keyFile, - "auth-provider": user.authProvider, - exec: user.exec, - token: user.token, - username: user.username, - password: user.password - } - }; - }) + clusters, + contexts, + users, }; logger.debug("Dumping KubeConfig:", config); @@ -243,70 +251,25 @@ export function dumpConfigYaml(kubeConfig: Partial): string { return yaml.safeDump(config, { skipInvalid: true }); } -export function podHasIssues(pod: V1Pod) { - // Logic adapted from dashboard - const notReady = !!pod.status.conditions.find(condition => { - return condition.type == "Ready" && condition.status !== "True"; - }); - - return ( - notReady || - pod.status.phase !== "Running" || - pod.spec.priority > 500000 // We're interested in high priority pods events regardless of their running status - ); -} - -export function getNodeWarningConditions(node: V1Node) { - return node.status?.conditions?.filter(c => - c.status.toLowerCase() === "true" && c.type !== "Ready" && c.type !== "HostUpgrades" - ) ?? []; -} - /** * Checks if `config` has valid `Context`, `User`, `Cluster`, and `exec` fields (if present when required) * * Note: This function returns an error instead of throwing it, returning `undefined` if the validation passes */ -export function validateKubeConfig(config: KubeConfig, contextName: string, validationOpts: KubeConfigValidationOpts = {}): Error | undefined { - try { - // we only receive a single context, cluster & user object here so lets validate them as this - // will be called when we add a new cluster to Lens +export function validateKubeConfig(config: KubeConfig, contextName: string): Error | undefined { + const contextObject = config.getContextObject(contextName); - const { validateUser = true, validateCluster = true, validateExec = true } = validationOpts; - - const contextObject = config.getContextObject(contextName); - - // Validate the Context Object - if (!contextObject) { - return new Error(`No valid context object provided in kubeconfig for context '${contextName}'`); - } - - // Validate the Cluster Object - if (validateCluster && !config.getCluster(contextObject.cluster)) { - return new Error(`No valid cluster object provided in kubeconfig for context '${contextName}'`); - } - - const user = config.getUser(contextObject.user); - - // Validate the User Object - if (validateUser && !user) { - return new Error(`No valid user object provided in kubeconfig for context '${contextName}'`); - } - - // Validate exec command if present - if (validateExec && user?.exec) { - const execCommand = user.exec["command"]; - // check if the command is absolute or not - const isAbsolute = path.isAbsolute(execCommand); - - // validate the exec struct in the user object, start with the command field - if (!commandExists.sync(execCommand)) { - return new ExecValidationNotFoundError(execCommand, isAbsolute); - } - } - - return undefined; - } catch (error) { - return error; + if (!contextObject) { + return new Error(`No valid context object provided in kubeconfig for context '${contextName}'`); } + + if (!config.getCluster(contextObject.cluster)) { + return new Error(`No valid cluster object provided in kubeconfig for context '${contextName}'`); + } + + if (!config.getUser(contextObject.user)) { + return new Error(`No valid user object provided in kubeconfig for context '${contextName}'`); + } + + return undefined; } diff --git a/types/command-exists.d.ts b/types/command-exists.d.ts deleted file mode 100644 index 8f07bb978e..0000000000 --- a/types/command-exists.d.ts +++ /dev/null @@ -1,32 +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. - */ - -export = commandExists; - -declare function commandExists(commandName: string): Promise; -declare function commandExists( - commandName: string, - cb: (error: null, exists: boolean) => void -): void; - -declare namespace commandExists { - function sync(commandName: string): boolean; -} diff --git a/yarn.lock b/yarn.lock index bd4f062a41..006af08175 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4066,11 +4066,6 @@ combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" -command-exists@1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.9.tgz#c50725af3808c8ab0260fd60b01fbfa25b954f69" - integrity sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w== - commander@2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" From 75ddf48914f5ee3e2a97e32b571cf9f9120b702f Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Thu, 14 Oct 2021 08:50:10 -0400 Subject: [PATCH 02/18] Upgrade typedoc-plugin-markdown to fix verify-docs (#4031) --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 5a6d205087..5543c00d56 100644 --- a/package.json +++ b/package.json @@ -379,7 +379,7 @@ "type-fest": "^1.0.2", "typed-emitter": "^1.3.1", "typedoc": "0.22.5", - "typedoc-plugin-markdown": "^3.9.0", + "typedoc-plugin-markdown": "^3.11.3", "typeface-roboto": "^1.1.13", "typescript": "^4.4.3", "typescript-plugin-css-modules": "^3.4.0", diff --git a/yarn.lock b/yarn.lock index 006af08175..5b521a025c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13894,10 +13894,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typedoc-plugin-markdown@^3.9.0: - version "3.9.0" - resolved "https://registry.yarnpkg.com/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.9.0.tgz#d9c0390b8ddeeda56fdbf01264521ef04b3c19c7" - integrity sha512-s445YeUe8bH7me15T+hsHZgNmAvvF7QIpX02vFgseLGtghAwmtdZYVOqPneWoKqRv/JNpPSuyZb3CeblML9jOg== +typedoc-plugin-markdown@^3.11.3: + version "3.11.3" + resolved "https://registry.yarnpkg.com/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.11.3.tgz#be340b905903f1ce552fa2fa7d677db408ab1041" + integrity sha512-rWiHbEIe0oZetDIsBR24XJVxGOJ91kDcHoj2KhFKxCLoJGX659EKBQkHne9QJ4W2stGhu1fRgFyQaouSBnxukA== dependencies: handlebars "^4.7.7" From 53c9d35832a07e85f1a133e47f59a081fe04eab8 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Thu, 14 Oct 2021 08:50:24 -0400 Subject: [PATCH 03/18] Fix cluster icon click in sidebar redirecting to wrong route (#4037) --- src/renderer/components/layout/sidebar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/components/layout/sidebar.tsx b/src/renderer/components/layout/sidebar.tsx index 66860fa399..ed9b76a8d1 100644 --- a/src/renderer/components/layout/sidebar.tsx +++ b/src/renderer/components/layout/sidebar.tsx @@ -193,7 +193,7 @@ export class Sidebar extends React.Component { source={metadata.source} src={spec.icon?.src} className="mr-5" - onClick={() => navigate(routes.clusterURL())} + onClick={() => navigate("/")} />
{metadata.name} From 6d003515e75f18ae0412bc36510a2c78ae919d66 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Oct 2021 08:55:09 -0400 Subject: [PATCH 04/18] Bump tailwindcss from 2.2.4 to 2.2.17 (#4039) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 231 +++++++++++++++++++++++++-------------------------- 2 files changed, 116 insertions(+), 117 deletions(-) diff --git a/package.json b/package.json index 5543c00d56..7c4470a9cb 100644 --- a/package.json +++ b/package.json @@ -372,7 +372,7 @@ "sass-loader": "^8.0.2", "sharp": "^0.29.1", "style-loader": "^2.0.0", - "tailwindcss": "^2.2.4", + "tailwindcss": "^2.2.17", "ts-jest": "26.5.6", "ts-loader": "^7.0.5", "ts-node": "^10.2.1", diff --git a/yarn.lock b/yarn.lock index 5b521a025c..972cd660e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -493,13 +493,6 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" -"@fullhuman/postcss-purgecss@^4.0.3": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@fullhuman/postcss-purgecss/-/postcss-purgecss-4.0.3.tgz#55d71712ec1c7a88e0d1ba5f10ce7fb6aa05beb4" - integrity sha512-/EnQ9UDWGGqHkn1UKAwSgh+gJHPKmD+Z+5dQ4gWT4qq2NUyez3zqAfZNwFH3eSgmgO+wjTXfhlLchx2M9/K+7Q== - dependencies: - purgecss "^4.0.3" - "@hapi/b64@5.x.x": version "5.0.0" resolved "https://registry.yarnpkg.com/@hapi/b64/-/b64-5.0.0.tgz#b8210cbd72f4774985e78569b77e97498d24277d" @@ -2766,10 +2759,10 @@ arg@^4.1.0: resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== -arg@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.0.tgz#a20e2bb5710e82950a516b3f933fee5ed478be90" - integrity sha512-4P8Zm2H+BRS+c/xX1LrHw0qKpEhdlZjLCgWy+d78T9vqa2Z2SiD2wMrYuWIAFy5IZUD7nnNXroRttz+0RzlrzQ== +arg@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.1.tgz#eb0c9a8f77786cad2af8ff2b862899842d7b6adb" + integrity sha512-e0hDa9H2Z9AwFkk2qDlwhoMYE4eToKarchkQHovNdLTCYMHZHeRjI71crOh+dio4K6u1IcwubQqo79Ga4CyAQA== argparse@^1.0.7: version "1.0.10" @@ -3673,10 +3666,10 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad" - integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg== +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== dependencies: ansi-styles "^4.1.0" supports-color "^7.1.0" @@ -4012,7 +4005,7 @@ color@3.0.x: color-convert "^1.9.1" color-string "^1.5.2" -color@^3.1.2, color@^3.1.3: +color@^3.1.2: version "3.1.3" resolved "https://registry.yarnpkg.com/color/-/color-3.1.3.tgz#ca67fb4e7b97d611dcde39eceed422067d91596e" integrity sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ== @@ -4310,10 +4303,10 @@ cosmiconfig@^6.0.0: path-type "^4.0.0" yaml "^1.7.2" -cosmiconfig@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.0.tgz#ef9b44d773959cae63ddecd122de23853b60f8d3" - integrity sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA== +cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" + integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== dependencies: "@types/parse-json" "^4.0.0" import-fresh "^3.2.1" @@ -4446,6 +4439,11 @@ css-box-model@^1.2.0: dependencies: tiny-invariant "^1.0.6" +css-color-names@^0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" + integrity sha1-gIrcLnnPhHOAabZGyyDsJ762KeA= + css-loader@^5.2.7: version "5.2.7" resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.2.7.tgz#9b9f111edf6fb2be5dc62525644cbc9c232064ae" @@ -4876,10 +4874,10 @@ dezalgo@^1.0.0, dezalgo@~1.0.3: asap "^2.0.0" wrappy "1" -didyoumean@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.1.tgz#e92edfdada6537d484d73c0172fd1eba0c4976ff" - integrity sha1-6S7f2tplN9SE1zwBcv0eugxJdv8= +didyoumean@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" + integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== diff-sequences@^26.6.2: version "26.6.2" @@ -5975,29 +5973,16 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^3.1.1: - version "3.2.4" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3" - integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ== +fast-glob@^3.1.1, fast-glob@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" + integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.0" + glob-parent "^5.1.2" merge2 "^1.3.0" - micromatch "^4.0.2" - picomatch "^2.2.1" - -fast-glob@^3.2.5: - version "3.2.5" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.5.tgz#7939af2a656de79a4f1901903ee8adcaa7cb9661" - integrity sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.0" - merge2 "^1.3.0" - micromatch "^4.0.2" - picomatch "^2.2.1" + micromatch "^4.0.4" fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: version "2.1.0" @@ -6558,19 +6543,19 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob-parent@^5.1.0, glob-parent@^5.1.2, glob-parent@~5.1.2: +glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" -glob-parent@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.0.tgz#f851b59b388e788f3a44d63fab50382b2859c33c" - integrity sha512-Hdd4287VEJcZXUwv1l8a+vXC1GjOQqXe+VS30w/ypihpcnu9M1n3xeYeJu5CBpeEQj2nAab2xxz28GuA3vp4Ww== +glob-parent@^6.0.1: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== dependencies: - is-glob "^4.0.1" + is-glob "^4.0.3" glob-to-regexp@^0.4.1: version "0.4.1" @@ -6912,6 +6897,11 @@ he@^1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== +hex-color-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" + integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== + history@^4.10.1, history@^4.9.0: version "4.10.1" resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" @@ -6969,6 +6959,16 @@ hpack.js@^2.1.6: readable-stream "^2.0.1" wbuf "^1.1.0" +hsl-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/hsl-regex/-/hsl-regex-1.0.0.tgz#d49330c789ed819e276a4c0d272dffa30b18fe6e" + integrity sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4= + +hsla-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38" + integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg= + html-encoding-sniffer@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" @@ -7348,11 +7348,6 @@ indent-string@^4.0.0: resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== -indexes-of@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" - integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= - infer-owner@^1.0.3, infer-owner@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" @@ -7565,6 +7560,18 @@ is-cidr@^3.0.0: dependencies: cidr-regex "^2.0.10" +is-color-stop@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-color-stop/-/is-color-stop-1.1.0.tgz#cfff471aee4dd5c9e158598fbe12967b5cdad345" + integrity sha1-z/9HGu5N1cnhWFmPvhKWe1za00U= + dependencies: + css-color-names "^0.0.4" + hex-color-regex "^1.1.0" + hsl-regex "^1.0.0" + hsla-regex "^1.0.0" + rgb-regex "^1.0.1" + rgba-regex "^1.0.0" + is-core-module@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a" @@ -7665,10 +7672,10 @@ is-glob@^3.1.0: dependencies: is-extglob "^2.1.0" -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" - integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== dependencies: is-extglob "^2.1.1" @@ -9154,11 +9161,6 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== -lodash.toarray@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561" - integrity sha1-JMS/zWsvuji/0FlNsRedjptlZWE= - lodash.topath@^4.5.2: version "4.5.2" resolved "https://registry.yarnpkg.com/lodash.topath/-/lodash.topath-4.5.2.tgz#3616351f3bba61994a0931989660bd03254fd009" @@ -9464,6 +9466,14 @@ micromatch@^4.0.0, micromatch@^4.0.2: braces "^3.0.1" picomatch "^2.0.5" +micromatch@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" + integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== + dependencies: + braces "^3.0.1" + picomatch "^2.2.3" + miller-rabin@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" @@ -9847,12 +9857,12 @@ node-addon-api@^4.1.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.2.0.tgz#117cbb5a959dff0992e1c586ae0393573e4d2a87" integrity sha512-eazsqzwG2lskuzBqCGPi7Ac2UgOoMz8JVOXVhTvvPDYhthvNpefx8jWD8Np7Gv+2Sz0FlPWZk0nJV0z598Wn8Q== -node-emoji@^1.8.1: - version "1.10.0" - resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.10.0.tgz#8886abd25d9c7bb61802a658523d1f8d2a89b2da" - integrity sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw== +node-emoji@^1.11.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.11.0.tgz#69a0150e6946e2f115e9d7ea4df7971e2628301c" + integrity sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A== dependencies: - lodash.toarray "^4.4.0" + lodash "^4.17.21" node-fetch-npm@^2.0.2: version "2.0.4" @@ -10332,12 +10342,7 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" -object-hash@^2.0.1: - version "2.0.3" - resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.0.3.tgz#d12db044e03cd2ca3d77c0570d87225b02e1e6ea" - integrity sha512-JPKn0GMu+Fa3zt3Bmr66JhokJU5BaNBIh4ZeTlaCBzrBsOeXzwcKKAK1tbLiPKgvwmPXsDvvLHoWh5Bm7ofIYg== - -object-hash@^2.2.0: +object-hash@^2.0.1, object-hash@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== @@ -10937,6 +10942,11 @@ picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== +picomatch@^2.2.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" + integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== + pify@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -11128,31 +11138,14 @@ postcss-modules-values@^4.0.0: dependencies: icss-utils "^5.0.0" -postcss-nested@5.0.5: - version "5.0.5" - resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-5.0.5.tgz#f0a107d33a9fab11d7637205f5321e27223e3603" - integrity sha512-GSRXYz5bccobpTzLQZXOnSOfKl6TwVr5CyAQJUPub4nuRJSOECK5AqurxVgmtxP48p0Kc/ndY/YyS1yqldX0Ew== +postcss-nested@5.0.6: + version "5.0.6" + resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-5.0.6.tgz#466343f7fc8d3d46af3e7dba3fcd47d052a945bc" + integrity sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA== dependencies: - postcss-selector-parser "^6.0.4" + postcss-selector-parser "^6.0.6" -postcss-selector-parser@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz#934cf799d016c83411859e09dcecade01286ec5c" - integrity sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg== - dependencies: - cssesc "^3.0.0" - indexes-of "^1.0.1" - uniq "^1.0.1" - -postcss-selector-parser@^6.0.4: - version "6.0.5" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.5.tgz#042d74e137db83e6f294712096cb413f5aa612c4" - integrity sha512-aFYPoYmXbZ1V6HZaSvat08M97A8HqO6Pjz+PiNpw/DhuRrC72XWAdp3hL6wusDCN31sSmcZyMGa2hZEuX+Xfhg== - dependencies: - cssesc "^3.0.0" - util-deprecate "^1.0.2" - -postcss-selector-parser@^6.0.6: +postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.6: version "6.0.6" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz#2c5bba8174ac2f6981ab631a42ab0ee54af332ea" integrity sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg== @@ -12223,6 +12216,16 @@ rfc6902@^4.0.2: resolved "https://registry.yarnpkg.com/rfc6902/-/rfc6902-4.0.2.tgz#ce99d3562b9e3287d403462e6bcc81eead8fcea0" integrity sha512-MJOC4iDSv3Qn5/QvhPbrNoRongti6moXSShcRmtbNqOk0WPxlviEdMV4bb9PaULhSxLUXzWd4AjAMKQ3j3y54w== +rgb-regex@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" + integrity sha1-wODWiC3w4jviVKR16O3UGRX+rrE= + +rgba-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" + integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= + rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@^2.7.1: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" @@ -13353,38 +13356,39 @@ table@^6.0.9: string-width "^4.2.3" strip-ansi "^6.0.1" -tailwindcss@^2.2.4: - version "2.2.4" - resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-2.2.4.tgz#6a2e259b1e26125aeaa7cdc479963fd217c308b0" - integrity sha512-OdBCPgazNNsknSP+JfrPzkay9aqKjhKtFhbhgxHgvEFdHy/GuRPo2SCJ4w1SFTN8H6FPI4m6qD/Jj20NWY1GkA== +tailwindcss@^2.2.17: + version "2.2.17" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-2.2.17.tgz#c6332731f9ff1b6628ff589c95c38685347775e3" + integrity sha512-WgRpn+Pxn7eWqlruxnxEbL9ByVRWi3iC10z4b6dW0zSdnkPVC4hPMSWLQkkW8GCyBIv/vbJ0bxIi9dVrl4CfoA== dependencies: - "@fullhuman/postcss-purgecss" "^4.0.3" - arg "^5.0.0" + arg "^5.0.1" bytes "^3.0.0" - chalk "^4.1.1" + chalk "^4.1.2" chokidar "^3.5.2" - color "^3.1.3" - cosmiconfig "^7.0.0" + color "^4.0.1" + cosmiconfig "^7.0.1" detective "^5.2.0" - didyoumean "^1.2.1" + didyoumean "^1.2.2" dlv "^1.1.3" - fast-glob "^3.2.5" + fast-glob "^3.2.7" fs-extra "^10.0.0" - glob-parent "^6.0.0" + glob-parent "^6.0.1" html-tags "^3.1.0" + is-color-stop "^1.1.0" is-glob "^4.0.1" lodash "^4.17.21" lodash.topath "^4.5.2" modern-normalize "^1.1.0" - node-emoji "^1.8.1" + node-emoji "^1.11.0" normalize-path "^3.0.0" object-hash "^2.2.0" postcss-js "^3.0.3" postcss-load-config "^3.1.0" - postcss-nested "5.0.5" + postcss-nested "5.0.6" postcss-selector-parser "^6.0.6" postcss-value-parser "^4.1.0" pretty-hrtime "^1.0.3" + purgecss "^4.0.3" quick-lru "^5.1.1" reduce-css-calc "^2.1.8" resolve "^1.20.0" @@ -13990,11 +13994,6 @@ union-value@^1.0.0: is-extendable "^0.1.1" set-value "^2.0.1" -uniq@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" - integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= - unique-filename@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" From fea379c89ffa2f3033cf1d4e8dba04a687a3d64b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Oct 2021 09:28:10 -0400 Subject: [PATCH 05/18] Bump @types/node from 14.17.14 to 14.17.26 (#4041) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 7c4470a9cb..258930ab34 100644 --- a/package.json +++ b/package.json @@ -286,7 +286,7 @@ "@types/mini-css-extract-plugin": "^0.9.1", "@types/mock-fs": "^4.13.1", "@types/module-alias": "^2.0.1", - "@types/node": "14.17.14", + "@types/node": "14.17.26", "@types/node-fetch": "^2.5.12", "@types/npm": "^2.0.32", "@types/progress-bar-webpack-plugin": "^2.1.2", diff --git a/yarn.lock b/yarn.lock index 972cd660e3..f60a264b64 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1733,10 +1733,10 @@ "@types/node" "*" form-data "^3.0.0" -"@types/node@*", "@types/node@14.17.14", "@types/node@^14.6.2": - version "14.17.14" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.14.tgz#6fda9785b41570eb628bac27be4b602769a3f938" - integrity sha512-rsAj2u8Xkqfc332iXV12SqIsjVi07H479bOP4q94NAcjzmAvapumEhuVIt53koEf7JFrpjgNKjBga5Pnn/GL8A== +"@types/node@*", "@types/node@14.17.26", "@types/node@^14.6.2": + version "14.17.26" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.26.tgz#47a53c7e7804490155a4646d60c8e194816d073c" + integrity sha512-eSTNkK/nfmnC7IKpOJZixDgG0W2/eHz1qyFN7o/rwwwIHsVRp+G9nbh4BrQ77kbQ2zPu286AQRxkuRLPcR3gXw== "@types/node@^10.12.0": version "10.17.24" From a01eecdc79c218f2335c4b061a401ab076d813c4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Oct 2021 09:28:35 -0400 Subject: [PATCH 06/18] Bump @testing-library/dom from 8.2.0 to 8.9.0 (#4040) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 258930ab34..e2580b0980 100644 --- a/package.json +++ b/package.json @@ -260,7 +260,7 @@ "@pmmmwh/react-refresh-webpack-plugin": "^0.4.3", "@sentry/react": "^6.13.3", "@sentry/types": "^6.8.0", - "@testing-library/dom": "^8.2.0", + "@testing-library/dom": "^8.9.0", "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^11.2.6", "@testing-library/user-event": "^13.2.1", diff --git a/yarn.lock b/yarn.lock index f60a264b64..2614e6d285 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1211,7 +1211,21 @@ dependencies: defer-to-connect "^2.0.0" -"@testing-library/dom@>=7", "@testing-library/dom@^7.28.1": +"@testing-library/dom@>=7", "@testing-library/dom@^8.9.0": + version "8.9.0" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.9.0.tgz#608ee6f235688a27f8ee180c0d81ff77a5363d59" + integrity sha512-fhmAYtGpFqzKdPq5aLNn/T396qfhYkttHT/5RytdDNSCzg9K/0F/WXF5iDsNBK1M3ZIQbPy7Y0qm4Kup5bqT/w== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/runtime" "^7.12.5" + "@types/aria-query" "^4.2.0" + aria-query "^4.2.2" + chalk "^4.1.0" + dom-accessibility-api "^0.5.6" + lz-string "^1.4.4" + pretty-format "^27.0.2" + +"@testing-library/dom@^7.28.1": version "7.30.3" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.30.3.tgz#779ea9bbb92d63302461800a388a5a890ac22519" integrity sha512-7JhIg2MW6WPwyikH2iL3o7z+FTVgSOd2jqCwTAHqK7Qal2gRRYiUQyURAxtbK9VXm/UTyG9bRihv8C5Tznr2zw== @@ -1225,20 +1239,6 @@ lz-string "^1.4.4" pretty-format "^26.6.2" -"@testing-library/dom@^8.2.0": - version "8.2.0" - resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.2.0.tgz#ac46a1b9d7c81f0d341ae38fb5424b64c27d151e" - integrity sha512-U8cTWENQPHO3QHvxBdfltJ+wC78ytMdg69ASvIdkGdQ/XRg4M9H2vvM3mHddxl+w/fM6NNqzGMwpQoh82v9VIA== - dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/runtime" "^7.12.5" - "@types/aria-query" "^4.2.0" - aria-query "^4.2.2" - chalk "^4.1.0" - dom-accessibility-api "^0.5.6" - lz-string "^1.4.4" - pretty-format "^27.0.2" - "@testing-library/jest-dom@^5.14.1": version "5.14.1" resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.14.1.tgz#8501e16f1e55a55d675fe73eecee32cdaddb9766" From 9d5689ce69a48037b9f5a111a8cc31a1d30448b0 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Thu, 14 Oct 2021 10:25:48 -0400 Subject: [PATCH 07/18] Only allow extensions to use non-lens specific cluster phases (#4036) --- .../catalog-entities/kubernetes-cluster.ts | 17 ++++++++++--- src/extensions/common-api/catalog.ts | 19 +++++++++++++- src/main/cluster-manager.ts | 25 ++++++++++++------- src/main/cluster.ts | 2 +- .../components/hotbar/hotbar-entity-icon.tsx | 3 ++- 5 files changed, 50 insertions(+), 16 deletions(-) diff --git a/src/common/catalog-entities/kubernetes-cluster.ts b/src/common/catalog-entities/kubernetes-cluster.ts index 3335d3803d..31e0251966 100644 --- a/src/common/catalog-entities/kubernetes-cluster.ts +++ b/src/common/catalog-entities/kubernetes-cluster.ts @@ -54,15 +54,24 @@ export interface KubernetesClusterSpec extends CatalogEntitySpec { accessibleNamespaces?: string[]; } +export enum LensKubernetesClusterStatus { + DELETING = "deleting", + CONNECTING = "connecting", + CONNECTED = "connected", + DISCONNECTED = "disconnected" +} + export interface KubernetesClusterMetadata extends CatalogEntityMetadata { distro?: string; kubeVersion?: string; } +/** + * @deprecated This is no longer used as it is incorrect. Other sources can add more values + */ export type KubernetesClusterStatusPhase = "connected" | "connecting" | "disconnected" | "deleting"; export interface KubernetesClusterStatus extends CatalogEntityStatus { - phase: KubernetesClusterStatusPhase; } export class KubernetesCluster extends CatalogEntity { @@ -110,15 +119,15 @@ export class KubernetesCluster extends CatalogEntity requestMain(clusterDisconnectHandler, this.metadata.uid) }); break; - case "disconnected": + case LensKubernetesClusterStatus.DISCONNECTED: context.menuItems.push({ title: "Connect", icon: "link", diff --git a/src/extensions/common-api/catalog.ts b/src/extensions/common-api/catalog.ts index 689310b69d..2d703b0dff 100644 --- a/src/extensions/common-api/catalog.ts +++ b/src/extensions/common-api/catalog.ts @@ -19,5 +19,22 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -export * from "../../common/catalog-entities"; +export { + KubernetesCluster, + kubernetesClusterCategory, + GeneralEntity, + WebLink, +} from "../../common/catalog-entities"; + +export type { + KubernetesClusterPrometheusMetrics, + KubernetesClusterSpec, + KubernetesClusterMetadata, + WebLinkSpec, + WebLinkStatus, + WebLinkStatusPhase, + KubernetesClusterStatusPhase, + KubernetesClusterStatus, +} from "../../common/catalog-entities"; + export * from "../../common/catalog/catalog-entity"; diff --git a/src/main/cluster-manager.ts b/src/main/cluster-manager.ts index e72cbd59cc..8cd6664b6a 100644 --- a/src/main/cluster-manager.ts +++ b/src/main/cluster-manager.ts @@ -27,7 +27,7 @@ import logger from "./logger"; import { apiKubePrefix } from "../common/vars"; import { getClusterIdFromHost, Singleton } from "../common/utils"; import { catalogEntityRegistry } from "./catalog"; -import { KubernetesCluster, KubernetesClusterPrometheusMetrics, KubernetesClusterStatusPhase } from "../common/catalog-entities/kubernetes-cluster"; +import { KubernetesCluster, KubernetesClusterPrometheusMetrics, LensKubernetesClusterStatus } from "../common/catalog-entities/kubernetes-cluster"; import { ipcMainOn } from "../common/ipc"; import { once } from "lodash"; import { ClusterStore } from "../common/cluster-store"; @@ -35,6 +35,8 @@ import type { ClusterId } from "../common/cluster-types"; const logPrefix = "[CLUSTER-MANAGER]:"; +const lensSpecificClusterStatuses: Set = new Set(Object.values(LensKubernetesClusterStatus)); + export class ClusterManager extends Singleton { private store = ClusterStore.getInstance(); deleting = observable.set(); @@ -90,6 +92,8 @@ export class ClusterManager extends Singleton { @action protected updateCatalog(clusters: Cluster[]) { + logger.debug("[CLUSTER-MANAGER]: updating catalog from cluster store"); + for (const cluster of clusters) { this.updateEntityFromCluster(cluster); } @@ -144,27 +148,28 @@ export class ClusterManager extends Singleton { @action protected updateEntityStatus(entity: KubernetesCluster, cluster?: Cluster) { if (this.deleting.has(entity.getId())) { - entity.status.phase = "deleting"; + entity.status.phase = LensKubernetesClusterStatus.DELETING; entity.status.enabled = false; } else { - entity.status.phase = ((): KubernetesClusterStatusPhase => { + entity.status.phase = (() => { if (!cluster) { - return "disconnected"; + return LensKubernetesClusterStatus.DISCONNECTED; } if (cluster.accessible) { - return "connected"; + return LensKubernetesClusterStatus.CONNECTED; } if (!cluster.disconnected) { - return "connecting"; + return LensKubernetesClusterStatus.CONNECTING; } - if (entity?.status?.phase) { + // Extensions are not allowed to use the Lens specific status phases + if (!lensSpecificClusterStatuses.has(entity?.status?.phase)) { return entity.status.phase; } - return "disconnected"; + return LensKubernetesClusterStatus.DISCONNECTED; })(); entity.status.enabled = true; @@ -276,7 +281,9 @@ export function catalogEntityFromCluster(cluster: Cluster) { icon: {} }, status: { - phase: cluster.disconnected ? "disconnected" : "connected", + phase: cluster.disconnected + ? LensKubernetesClusterStatus.DISCONNECTED + : LensKubernetesClusterStatus.CONNECTED, reason: "", message: "", active: !cluster.disconnected diff --git a/src/main/cluster.ts b/src/main/cluster.ts index fd8142aa57..b84939a5d3 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -394,7 +394,6 @@ export class Cluster implements ClusterModel, ClusterState { * @internal */ @action disconnect() { - logger.info(`[CLUSTER]: disconnect`, this.getMeta()); this.unbindEvents(); this.contextHandler?.stopServer(); this.disconnected = true; @@ -405,6 +404,7 @@ export class Cluster implements ClusterModel, ClusterState { this.allowedNamespaces = []; this.resourceAccessStatuses.clear(); this.pushState(); + logger.info(`[CLUSTER]: disconnect`, this.getMeta()); } /** diff --git a/src/renderer/components/hotbar/hotbar-entity-icon.tsx b/src/renderer/components/hotbar/hotbar-entity-icon.tsx index fc6628f3eb..05ce960a23 100644 --- a/src/renderer/components/hotbar/hotbar-entity-icon.tsx +++ b/src/renderer/components/hotbar/hotbar-entity-icon.tsx @@ -31,6 +31,7 @@ import { cssNames, IClassName } from "../../utils"; import { Icon } from "../icon"; import { HotbarIcon } from "./hotbar-icon"; import { HotbarStore } from "../../../common/hotbar-store"; +import { LensKubernetesClusterStatus } from "../../../common/catalog-entities/kubernetes-cluster"; interface Props extends DOMAttributes { entity: CatalogEntity; @@ -78,7 +79,7 @@ export class HotbarEntityIcon extends React.Component { return null; } - const className = cssNames("led", { online: this.props.entity.status.phase == "connected"}); // TODO: make it more generic + const className = cssNames("led", { online: this.props.entity.status.phase === LensKubernetesClusterStatus.CONNECTED}); // TODO: make it more generic return
; } From 03c17104321c8b183e88cf1fdf9ae71eca9c5628 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Oct 2021 10:58:20 -0400 Subject: [PATCH 08/18] Bump ws from 7.4.6 to 7.5.5 (#4042) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index e2580b0980..be99cf1890 100644 --- a/package.json +++ b/package.json @@ -250,7 +250,7 @@ "winston": "^3.3.3", "winston-console-format": "^1.0.8", "winston-transport-browserconsole": "^1.0.5", - "ws": "^7.4.6" + "ws": "^7.5.5" }, "devDependencies": { "@emeraldpay/hashicon-react": "^0.4.0", diff --git a/yarn.lock b/yarn.lock index 2614e6d285..38ce254f43 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14710,10 +14710,10 @@ ws@^6.2.1: dependencies: async-limiter "~1.0.0" -ws@^7.3.1, ws@^7.4.6: - version "7.4.6" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" - integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== +ws@^7.3.1, ws@^7.4.6, ws@^7.5.5: + version "7.5.5" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.5.tgz#8b4bc4af518cfabd0473ae4f99144287b33eb881" + integrity sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w== xdg-basedir@^3.0.0: version "3.0.0" From d2203b3a30ea1ce4464f7c3fc0d3d7e20f140e01 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Thu, 14 Oct 2021 15:20:12 -0400 Subject: [PATCH 09/18] Fix verify-docs error (#4045) --- Makefile | 8 +++++--- docs/extensions/typedoc-readme.md.tpl | 6 +++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index c39b42468f..b22527daec 100644 --- a/Makefile +++ b/Makefile @@ -122,6 +122,8 @@ clean-npm: .PHONY: clean clean: clean-npm clean-extensions rm -rf binaries/client - rm -rf dist/* - rm -rf static/build/* - rm -rf node_modules/ + rm -rf dist + rm -rf static/build + rm -rf node_modules + rm -rf site + rm -rf docs/extensions/api diff --git a/docs/extensions/typedoc-readme.md.tpl b/docs/extensions/typedoc-readme.md.tpl index fe0bdb73a9..0f83a467a1 100644 --- a/docs/extensions/typedoc-readme.md.tpl +++ b/docs/extensions/typedoc-readme.md.tpl @@ -2,6 +2,6 @@ ## APIs -- [Common](modules/common.md) -- [Main](modules/main.md) -- [Renderer](modules/renderer.md) +- [Common](modules/Common.md) +- [Main](modules/Main.md) +- [Renderer](modules/Renderer.md) From 4c5cd2b7370051149cec1e4815dd7f895ce50ff8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Oct 2021 08:42:12 -0400 Subject: [PATCH 10/18] Bump concurrently from 5.2.0 to 5.3.0 (#4053) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index be99cf1890..6f7bbc58e9 100644 --- a/package.json +++ b/package.json @@ -323,7 +323,7 @@ "chart.js": "^2.9.4", "circular-dependency-plugin": "^5.2.2", "color": "^3.1.2", - "concurrently": "^5.2.0", + "concurrently": "^5.3.0", "css-loader": "^5.2.7", "deepdash": "^5.3.9", "dompurify": "^2.3.3", diff --git a/yarn.lock b/yarn.lock index 38ce254f43..3b67b121f6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4136,10 +4136,10 @@ concat-stream@^1.5.0, concat-stream@^1.6.2: readable-stream "^2.2.2" typedarray "^0.0.6" -concurrently@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-5.2.0.tgz#ead55121d08a0fc817085584c123cedec2e08975" - integrity sha512-XxcDbQ4/43d6CxR7+iV8IZXhur4KbmEJk1CetVMUqCy34z9l0DkszbY+/9wvmSnToTej0SYomc2WSRH+L0zVJw== +concurrently@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-5.3.0.tgz#7500de6410d043c912b2da27de3202cb489b1e7b" + integrity sha512-8MhqOB6PWlBfA2vJ8a0bSFKATOdWlHiQlk11IfmQBPaHVP8oP2gsh2MObE6UR3hqDHqvaIvLTyceNW6obVuFHQ== dependencies: chalk "^2.4.2" date-fns "^2.0.1" From e227702d9161e50d8dacdae25d7dfa21bdecc878 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Oct 2021 08:43:08 -0400 Subject: [PATCH 11/18] Bump sass from 1.41.1 to 1.43.2 (#4050) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 6f7bbc58e9..ca08f1e91c 100644 --- a/package.json +++ b/package.json @@ -368,7 +368,7 @@ "react-select-event": "^5.1.0", "react-table": "^7.7.0", "react-window": "^1.8.5", - "sass": "^1.41.1", + "sass": "^1.43.2", "sass-loader": "^8.0.2", "sharp": "^0.29.1", "style-loader": "^2.0.0", diff --git a/yarn.lock b/yarn.lock index 3b67b121f6..43316cc677 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12339,10 +12339,10 @@ sass-loader@^8.0.2: schema-utils "^2.6.1" semver "^6.3.0" -sass@^1.32.13, sass@^1.41.1: - version "1.41.1" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.41.1.tgz#bca5bed2154192779c29f48fca9c644c60c38d98" - integrity sha512-vIjX7izRxw3Wsiez7SX7D+j76v7tenfO18P59nonjr/nzCkZuoHuF7I/Fo0ZRZPKr88v29ivIdE9BqGDgQD/Nw== +sass@^1.32.13, sass@^1.43.2: + version "1.43.2" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.43.2.tgz#c02501520c624ad6622529a8b3724eb08da82d65" + integrity sha512-DncYhjl3wBaPMMJR0kIUaH3sF536rVrOcqqVGmTZHQRRzj7LQlyGV7Mb8aCKFyILMr5VsPHwRYtyKpnKYlmQSQ== dependencies: chokidar ">=3.0.0 <4.0.0" From f1ce97ecc842eb94ff2559808f2ccce65fca8cb6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Oct 2021 08:43:40 -0400 Subject: [PATCH 12/18] Bump @types/url-parse from 1.4.3 to 1.4.4 (#4052) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index ca08f1e91c..958dee2fff 100644 --- a/package.json +++ b/package.json @@ -310,7 +310,7 @@ "@types/tcp-port-used": "^1.0.0", "@types/tempy": "^0.3.0", "@types/triple-beam": "^1.3.2", - "@types/url-parse": "^1.4.3", + "@types/url-parse": "^1.4.4", "@types/uuid": "^8.3.1", "@types/webdriverio": "^4.13.0", "@types/webpack": "^4.41.31", diff --git a/yarn.lock b/yarn.lock index 43316cc677..db6bc169f7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2078,10 +2078,10 @@ resolved "https://registry.yarnpkg.com/@types/underscore/-/underscore-1.10.0.tgz#5cb0dff2a5f616fc8e0c61b482bf01fa20a03cec" integrity sha512-ZAbqul7QAKpM2h1PFGa5ETN27ulmqtj0QviYHasw9LffvXZvVHuraOx/FOsIPPDNGZN0Qo1nASxxSfMYOtSoCw== -"@types/url-parse@^1.4.3": - version "1.4.3" - resolved "https://registry.yarnpkg.com/@types/url-parse/-/url-parse-1.4.3.tgz#fba49d90f834951cb000a674efee3d6f20968329" - integrity sha512-4kHAkbV/OfW2kb5BLVUuUMoumB3CP8rHqlw48aHvFy5tf9ER0AfOonBlX29l/DD68G70DmyhRlSYfQPSYpC5Vw== +"@types/url-parse@^1.4.4": + version "1.4.4" + resolved "https://registry.yarnpkg.com/@types/url-parse/-/url-parse-1.4.4.tgz#ebeb0ec8b581318739cf73e9f9b186f610764255" + integrity sha512-KtQLad12+4T/NfSxpoDhmr22+fig3T7/08QCgmutYA6QSznSRmEtuL95GrhVV40/0otTEdFc+etRcCTqhh1q5Q== "@types/uuid@^8.3.1": version "8.3.1" From c5de0b1e007a668501a5fede431ebfe7f4c8b8c3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Oct 2021 08:46:53 -0400 Subject: [PATCH 13/18] Bump playwright from 1.14.0 to 1.15.2 (#4051) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 24 ++++++------------------ 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 958dee2fff..ab927fd245 100644 --- a/package.json +++ b/package.json @@ -353,7 +353,7 @@ "node-gyp": "7.1.2", "node-loader": "^1.0.3", "nodemon": "^2.0.13", - "playwright": "^1.14.0", + "playwright": "^1.15.2", "postcss": "^8.3.6", "postcss-loader": "4.3.0", "postinstall-postinstall": "^2.1.0", diff --git a/yarn.lock b/yarn.lock index db6bc169f7..32ae056719 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9504,12 +9504,7 @@ mime@1.6.0, mime@^1.4.1: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -mime@^2.4.4: - version "2.4.6" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1" - integrity sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA== - -mime@^2.4.6, mime@^2.5.2: +mime@^2.4.4, mime@^2.4.6, mime@^2.5.2: version "2.5.2" resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe" integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg== @@ -11002,10 +10997,10 @@ pkg-up@^3.1.0: dependencies: find-up "^3.0.0" -playwright@^1.14.0: - version "1.14.0" - resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.14.0.tgz#18301b11f5278a446d36b5cf96f67db36ce2cd20" - integrity sha512-aR5oZ1iVsjQkGfYCjgYAmyMAVu0MQ0i8MgdnfdqDu9EVLfbnpuuFmTv/Rb7/Yjno1kOrDUP9+RyNC+zfG3wozA== +playwright@^1.15.2: + version "1.15.2" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.15.2.tgz#b350056d1fffbe5de5b1bdaca6ab73e35758baad" + integrity sha512-+Z+7ckihyxR6rK5q8DWC6eUbKARfXpyxpjNcoJfgwSr64lAOzjhyFQiPC/JkdIqhsLgZjxpWfl1S7fLb+wPkgA== dependencies: commander "^6.1.0" debug "^4.1.1" @@ -12956,14 +12951,7 @@ stack-trace@0.0.x: resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= -stack-utils@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.2.tgz#5cf48b4557becb4638d0bc4f21d23f5d19586593" - integrity sha512-0H7QK2ECz3fyZMzQ8rH0j2ykpfbnd20BFtfg/SqVC2+sCTtcw0aDTGB7dk+de4U4uUeuz6nOtJcrkFFLG1B0Rg== - dependencies: - escape-string-regexp "^2.0.0" - -stack-utils@^2.0.3: +stack-utils@^2.0.2, stack-utils@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.3.tgz#cd5f030126ff116b78ccb3c027fe302713b61277" integrity sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw== From 246305cd63c3c96a612f7f314e75cf4fe344defb Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Fri, 15 Oct 2021 09:06:24 -0400 Subject: [PATCH 14/18] Fix accessible namespaces notification not navigating to correct settings (#4048) --- src/jest.setup.ts | 2 +- src/main/cluster.ts | 17 ++---- .../+entity-settings/entity-settings.tsx | 54 ++++++++++--------- .../cluster-manager/cluster-status.tsx | 2 +- .../components/layout/setting-layout.tsx | 51 +++++++----------- src/renderer/ipc/index.tsx | 37 +++++++++---- 6 files changed, 81 insertions(+), 82 deletions(-) diff --git a/src/jest.setup.ts b/src/jest.setup.ts index a234960386..872639f891 100644 --- a/src/jest.setup.ts +++ b/src/jest.setup.ts @@ -38,6 +38,6 @@ fetchMock.enableMocks(); // Mock __non_webpack_require__ for tests globalThis.__non_webpack_require__ = jest.fn(); -process.on("unhandledRejection", (err) => { +process.on("unhandledRejection", (err: any) => { fail(err); }); diff --git a/src/main/cluster.ts b/src/main/cluster.ts index b84939a5d3..4ac4074ca5 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -35,6 +35,7 @@ import plimit from "p-limit"; import type { ClusterState, ClusterRefreshOptions, ClusterMetricsResourceType, ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences, UpdateClusterModel } from "../common/cluster-types"; import { ClusterMetadataKey, initialNodeShellImage, ClusterStatus } from "../common/cluster-types"; import { storedKubeConfigFolder, toJS } from "../common/utils"; +import type { Response } from "request"; /** * Cluster @@ -642,8 +643,6 @@ export class Cluster implements ClusterModel, ClusterState { }; } - protected getAllowedNamespacesErrorCount = 0; - protected async getAllowedNamespaces() { if (this.accessibleNamespaces.length) { return this.accessibleNamespaces; @@ -655,24 +654,16 @@ export class Cluster implements ClusterModel, ClusterState { const { body: { items } } = await api.listNamespace(); const namespaces = items.map(ns => ns.metadata.name); - this.getAllowedNamespacesErrorCount = 0; // reset on success - return namespaces; } catch (error) { const ctx = (await this.getProxyKubeconfig()).getContextObject(this.contextName); const namespaceList = [ctx.namespace].filter(Boolean); if (namespaceList.length === 0 && error instanceof HttpError && error.statusCode === 403) { - this.getAllowedNamespacesErrorCount += 1; + const { response } = error as HttpError & { response: Response }; - if (this.getAllowedNamespacesErrorCount > 3) { - // reset on send - this.getAllowedNamespacesErrorCount = 0; - - // then broadcast, make sure it is 3 successive attempts - logger.info("[CLUSTER]: listing namespaces is forbidden, broadcasting", { clusterId: this.id, error }); - broadcastMessage(ClusterListNamespaceForbiddenChannel, this.id); - } + logger.info("[CLUSTER]: listing namespaces is forbidden, broadcasting", { clusterId: this.id, error: response.body }); + broadcastMessage(ClusterListNamespaceForbiddenChannel, this.id); } return namespaceList; diff --git a/src/renderer/components/+entity-settings/entity-settings.tsx b/src/renderer/components/+entity-settings/entity-settings.tsx index bcb5895413..ef4bc480d0 100644 --- a/src/renderer/components/+entity-settings/entity-settings.tsx +++ b/src/renderer/components/+entity-settings/entity-settings.tsx @@ -34,6 +34,7 @@ import type { EntitySettingsRouteParams } from "../../../common/routes"; import { groupBy } from "lodash"; import { SettingLayout } from "../layout/setting-layout"; import { HotbarIcon } from "../hotbar/hotbar-icon"; +import logger from "../../../common/logger"; interface Props extends RouteComponentProps { } @@ -45,6 +46,17 @@ export class EntitySettings extends React.Component { constructor(props: Props) { super(props); makeObservable(this); + + const { hash } = navigation.location; + + if (hash) { + const menuId = hash.slice(1); + const item = this.menuItems.find((item) => item.id === menuId); + + if (item) { + this.activeTab = item.id; + } + } } get entityId() { @@ -61,18 +73,10 @@ export class EntitySettings extends React.Component { return EntitySettingRegistry.getInstance().getItemsForKind(this.entity.kind, this.entity.apiVersion, this.entity.metadata.source); } - async componentDidMount() { - const { hash } = navigation.location; + get activeSetting() { + this.activeTab ||= this.menuItems[0]?.id; - if (hash) { - const item = this.menuItems.find((item) => item.title === hash.slice(1)); - - if (item) { - this.activeTab = item.id; - } - } - - this.ensureActiveTab(); + return this.menuItems.find((setting) => setting.id === this.activeTab); } onTabChange = (tabId: string) => { @@ -122,33 +126,31 @@ export class EntitySettings extends React.Component { ); } - ensureActiveTab() { - if (!this.activeTab) { - this.activeTab = this.menuItems[0]?.id; - } - } - render() { if (!this.entity) { - console.error("entity not found", this.entityId); + logger.error("[ENTITY-SETTINGS]: entity not found", this.entityId); return null; } - this.ensureActiveTab(); - const activeSetting = this.menuItems.find((setting) => setting.id === this.activeTab); + const { activeSetting } = this; + return ( -
-

{activeSetting.title}

-
- -
-
+ { + activeSetting && ( +
+

{activeSetting.title}

+
+ +
+
+ ) + }
); } diff --git a/src/renderer/components/cluster-manager/cluster-status.tsx b/src/renderer/components/cluster-manager/cluster-status.tsx index a3c3f96b5f..7c7cdd2714 100644 --- a/src/renderer/components/cluster-manager/cluster-status.tsx +++ b/src/renderer/components/cluster-manager/cluster-status.tsx @@ -90,7 +90,7 @@ export class ClusterStatus extends React.Component { params: { entityId: this.props.clusterId, }, - fragment: "Proxy", + fragment: "proxy", })); }; diff --git a/src/renderer/components/layout/setting-layout.tsx b/src/renderer/components/layout/setting-layout.tsx index 45f665df1c..9f458658e8 100644 --- a/src/renderer/components/layout/setting-layout.tsx +++ b/src/renderer/components/layout/setting-layout.tsx @@ -23,7 +23,7 @@ import "./setting-layout.scss"; import React from "react"; import { observer } from "mobx-react"; -import { boundMethod, cssNames, IClassName } from "../../utils"; +import { cssNames, IClassName } from "../../utils"; import { navigation } from "../../navigation"; import { Icon } from "../icon"; @@ -36,17 +36,10 @@ export interface SettingLayoutProps extends React.DOMAttributes { back?: (evt: React.MouseEvent | KeyboardEvent) => void; } -function scrollToAnchor() { - const { hash } = window.location; - - if (hash) { - document.querySelector(`${hash}`).scrollIntoView(); - } -} - const defaultProps: Partial = { provideBackButtonNavigation: true, contentGaps: true, + back: () => navigation.goBack(), }; /** @@ -56,19 +49,14 @@ const defaultProps: Partial = { export class SettingLayout extends React.Component { static defaultProps = defaultProps as object; - @boundMethod - back(evt?: React.MouseEvent | KeyboardEvent) { - if (this.props.back) { - this.props.back(evt); - } else { - navigation.goBack(); - } - } - async componentDidMount() { - window.addEventListener("keydown", this.onEscapeKey); + const { hash } = window.location; - scrollToAnchor(); + if (hash) { + document.querySelector(hash)?.scrollIntoView(); + } + + window.addEventListener("keydown", this.onEscapeKey); } componentWillUnmount() { @@ -82,7 +70,7 @@ export class SettingLayout extends React.Component { if (evt.code === "Escape") { evt.stopPropagation(); - this.back(evt); + this.props.back(evt); } }; @@ -107,17 +95,18 @@ export class SettingLayout extends React.Component { {children}
- { this.props.provideBackButtonNavigation && ( -
-
- + { + this.props.provideBackButtonNavigation && ( +
+
+ +
+
- - -
- )} + ) + }
diff --git a/src/renderer/ipc/index.tsx b/src/renderer/ipc/index.tsx index 62dd96a264..f99cee5823 100644 --- a/src/renderer/ipc/index.tsx +++ b/src/renderer/ipc/index.tsx @@ -77,16 +77,15 @@ function UpdateAvailableHandler(event: IpcRendererEvent, ...[backchannel, update ); } -const listNamespacesForbiddenHandlerDisplayedAt = new Map(); +const notificationLastDisplayedAt = new Map(); const intervalBetweenNotifications = 1000 * 60; // 60s function ListNamespacesForbiddenHandler(event: IpcRendererEvent, ...[clusterId]: ListNamespaceForbiddenArgs): void { - const lastDisplayedAt = listNamespacesForbiddenHandlerDisplayedAt.get(clusterId); - const wasDisplayed = Boolean(lastDisplayedAt); + const lastDisplayedAt = notificationLastDisplayedAt.get(clusterId); const now = Date.now(); - if (!wasDisplayed || (now - lastDisplayedAt) > intervalBetweenNotifications) { - listNamespacesForbiddenHandlerDisplayedAt.set(clusterId, now); + if (!notificationLastDisplayedAt.has(clusterId) || (now - lastDisplayedAt) > intervalBetweenNotifications) { + notificationLastDisplayedAt.set(clusterId, now); } else { // don't bother the user too often return; @@ -94,21 +93,39 @@ function ListNamespacesForbiddenHandler(event: IpcRendererEvent, ...[clusterId]: const notificationId = `list-namespaces-forbidden:${clusterId}`; + if (notificationsStore.getById(notificationId)) { + // notification is still visible + return; + } + Notifications.info( (
Add Accessible Namespaces -

Cluster {ClusterStore.getInstance().getById(clusterId).name} does not have permissions to list namespaces. Please add the namespaces you have access to.

+

+ Cluster {ClusterStore.getInstance().getById(clusterId).name} does not have permissions to list namespaces.{" "} + Please add the namespaces you have access to. +

-
), { id: notificationId, + /** + * Set the time when the notification is closed as well so that there is at + * least a minute between closing the notification as seeing it again + */ + onClose: () => notificationLastDisplayedAt.set(clusterId, Date.now()), } ); } From dabfce3609ceaa42ee55d3d2cdb7413aa898c26b Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 15 Oct 2021 18:17:08 +0300 Subject: [PATCH 15/18] Update immer to version 9.x (#3882) --- package.json | 2 +- .../utils/__tests__/storageHelper.test.ts | 148 +++++++----------- src/renderer/utils/storageHelper.ts | 24 +-- yarn.lock | 8 +- 4 files changed, 74 insertions(+), 108 deletions(-) diff --git a/package.json b/package.json index ab927fd245..5a452a19d3 100644 --- a/package.json +++ b/package.json @@ -205,7 +205,7 @@ "grapheme-splitter": "^1.0.4", "handlebars": "^4.7.7", "http-proxy": "^1.18.1", - "immer": "^8.0.4", + "immer": "^9.0.6", "joi": "^17.4.2", "js-yaml": "^3.14.0", "jsdom": "^16.7.0", diff --git a/src/renderer/utils/__tests__/storageHelper.test.ts b/src/renderer/utils/__tests__/storageHelper.test.ts index 11127ee3c4..57141450c1 100644 --- a/src/renderer/utils/__tests__/storageHelper.test.ts +++ b/src/renderer/utils/__tests__/storageHelper.test.ts @@ -19,149 +19,109 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { reaction } from "mobx"; -import { StorageAdapter, StorageHelper } from "../storageHelper"; +import { observable, reaction } from "mobx"; +import { StorageHelper } from "../storageHelper"; import { delay } from "../../../common/utils/delay"; -describe("renderer/utils/StorageHelper", () => { - describe("window.localStorage might be used as StorageAdapter", () => { - type StorageModel = string; +type StorageModel = { + [prop: string]: any /*json-serializable*/; + message?: string; + description?: any; +}; +describe("renderer/utils/StorageHelper", () => { + describe("Using custom StorageAdapter", () => { const storageKey = "ui-settings"; + const remoteStorageMock = observable.map(); let storageHelper: StorageHelper; + let storageHelperAsync: StorageHelper; beforeEach(() => { - localStorage.clear(); + remoteStorageMock.set(storageKey, { + message: "saved-before", // pretending as previously saved data + }); storageHelper = new StorageHelper(storageKey, { autoInit: false, - storage: localStorage, - defaultValue: "test", - }); - }); - - it("initialized with default value", async () => { - localStorage.setItem(storageKey, "saved"); // pretending it was saved previously - - expect(storageHelper.key).toBe(storageKey); - expect(storageHelper.defaultValue).toBe("test"); - expect(storageHelper.get()).toBe("test"); - - storageHelper.init(); - - expect(storageHelper.key).toBe(storageKey); - expect(storageHelper.defaultValue).toBe("test"); - expect(storageHelper.get()).toBe("saved"); - }); - - it("updates storage", async () => { - storageHelper.init(); - - storageHelper.set("test2"); - expect(localStorage.getItem(storageKey)).toBe("test2"); - - localStorage.setItem(storageKey, "test3"); - storageHelper.init({ force: true }); // reload from underlying storage and merge - expect(storageHelper.get()).toBe("test3"); - }); - }); - - describe("Using custom StorageAdapter", () => { - type SettingsStorageModel = { - [key: string]: any; - message: string; - }; - - const storageKey = "mySettings"; - const storageMock: Record = {}; - let storageHelper: StorageHelper; - let storageHelperAsync: StorageHelper; - let storageAdapter: StorageAdapter; - - const storageHelperDefaultValue: SettingsStorageModel = { - message: "hello-world", - anyOtherStorableData: 123, - }; - - beforeEach(() => { - storageMock[storageKey] = { - message: "saved-before", - } as SettingsStorageModel; - - storageAdapter = { - onChange: jest.fn(), - getItem: jest.fn((key: string) => { - return storageMock[key]; - }), - setItem: jest.fn((key: string, value: any) => { - storageMock[key] = value; - }), - removeItem: jest.fn((key: string) => { - delete storageMock[key]; - }), - }; - - storageHelper = new StorageHelper(storageKey, { - autoInit: false, - defaultValue: storageHelperDefaultValue, - storage: storageAdapter, + defaultValue: { + message: "blabla", + description: "default" + }, + storage: { + getItem(key: string): StorageModel { + return Object.assign( + storageHelper.defaultValue, + remoteStorageMock.get(key), + ); + }, + setItem(key: string, value: StorageModel) { + remoteStorageMock.set(key, value); + }, + removeItem(key: string) { + remoteStorageMock.delete(key); + } + }, }); storageHelperAsync = new StorageHelper(storageKey, { autoInit: false, - defaultValue: storageHelperDefaultValue, + defaultValue: storageHelper.defaultValue, storage: { - ...storageAdapter, - async getItem(key: string): Promise { + ...storageHelper.storage, + async getItem(key: string): Promise { await delay(500); // fake loading timeout - return storageAdapter.getItem(key); + return storageHelper.storage.getItem(key); } }, }); }); - it("loads data from storage with fallback to default-value", () => { - expect(storageHelper.get()).toEqual(storageHelperDefaultValue); + it("initialized with default value", async () => { storageHelper.init(); - - expect(storageHelper.get().message).toBe("saved-before"); - expect(storageAdapter.getItem).toHaveBeenCalledWith(storageHelper.key); + expect(storageHelper.key).toBe(storageKey); + expect(storageHelper.get()).toEqual(storageHelper.defaultValue); }); it("async loading from storage supported too", async () => { expect(storageHelperAsync.initialized).toBeFalsy(); storageHelperAsync.init(); await delay(300); - expect(storageHelperAsync.get()).toEqual(storageHelperDefaultValue); + expect(storageHelperAsync.get()).toEqual(storageHelper.defaultValue); await delay(200); expect(storageHelperAsync.get().message).toBe("saved-before"); }); it("set() fully replaces data in storage", () => { storageHelper.init(); - storageHelper.set({ message: "test2" }); - expect(storageHelper.get().message).toBe("test2"); - expect(storageMock[storageKey]).toEqual({ message: "test2" }); - expect(storageAdapter.setItem).toHaveBeenCalledWith(storageHelper.key, { message: "test2" }); + storageHelper.set({ message: "msg" }); + storageHelper.get().description = "desc"; + expect(storageHelper.get().message).toBe("msg"); + expect(storageHelper.get().description).toBe("desc"); + expect(remoteStorageMock.get(storageKey)).toEqual({ + message: "msg", + description: "desc", + } as StorageModel); }); it("merge() does partial data tree updates", () => { storageHelper.init(); storageHelper.merge({ message: "updated" }); - expect(storageHelper.get()).toEqual({ ...storageHelperDefaultValue, message: "updated" }); - expect(storageAdapter.setItem).toHaveBeenCalledWith(storageHelper.key, { ...storageHelperDefaultValue, message: "updated" }); + expect(storageHelper.get()).toEqual({ ...storageHelper.defaultValue, message: "updated" }); + expect(remoteStorageMock.get(storageKey)).toEqual({ ...storageHelper.defaultValue, message: "updated" }); + // `draft` modified inside, returning `void` is expected storageHelper.merge(draft => { draft.message = "updated2"; }); - expect(storageHelper.get()).toEqual({ ...storageHelperDefaultValue, message: "updated2" }); + expect(storageHelper.get()).toEqual({ ...storageHelper.defaultValue, message: "updated2" }); + // returning object modifies `draft` as well storageHelper.merge(draft => ({ message: draft.message.replace("2", "3") })); - expect(storageHelper.get()).toEqual({ ...storageHelperDefaultValue, message: "updated3" }); + expect(storageHelper.get()).toEqual({ ...storageHelper.defaultValue, message: "updated3" }); }); }); diff --git a/src/renderer/utils/storageHelper.ts b/src/renderer/utils/storageHelper.ts index a7931ae41e..80c5df8fba 100755 --- a/src/renderer/utils/storageHelper.ts +++ b/src/renderer/utils/storageHelper.ts @@ -20,10 +20,9 @@ */ // Helper for working with storages (e.g. window.localStorage, NodeJS/file-system, etc.) - import { action, comparer, makeObservable, observable, toJS, when, } from "mobx"; -import produce, { Draft } from "immer"; -import { isEqual, isFunction, isPlainObject } from "lodash"; +import produce, { Draft, isDraft } from "immer"; +import { isEqual } from "lodash"; import logger from "../../main/logger"; export interface StorageAdapter { @@ -150,15 +149,22 @@ export class StorageHelper { @action merge(value: Partial | ((draft: Draft) => Partial | void)) { - const nextValue = produce(this.toJSON(), (state: Draft) => { - const newValue = isFunction(value) ? value(state) : value; + const nextValue = produce(this.toJSON(), (draft: Draft) => { - return isPlainObject(newValue) - ? Object.assign(state, newValue) // partial updates for returned plain objects - : newValue; + if (typeof value == "function") { + const newValue = value(draft); + + // merge returned plain objects from `value-as-callback` usage + // otherwise `draft` can be just modified inside a callback without returning any value (void) + if (newValue && !isDraft(newValue)) { + Object.assign(draft, newValue); + } + } else { + Object.assign(draft, value); + } }); - this.set(nextValue as T); + this.set(nextValue); } toJSON(): T { diff --git a/yarn.lock b/yarn.lock index 32ae056719..b6a5f1bbde 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7278,10 +7278,10 @@ immediate@~3.0.5: resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps= -immer@^8.0.4: - version "8.0.4" - resolved "https://registry.yarnpkg.com/immer/-/immer-8.0.4.tgz#3a21605a4e2dded852fb2afd208ad50969737b7a" - integrity sha512-jMfL18P+/6P6epANRvRk6q8t+3gGhqsJ9EuJ25AXE+9bNTYtssvzeYbEd0mXRYWCmmXSIbnlpz6vd6iJlmGGGQ== +immer@^9.0.6: + version "9.0.6" + resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.6.tgz#7a96bf2674d06c8143e327cbf73539388ddf1a73" + integrity sha512-G95ivKpy+EvVAnAab4fVa4YGYn24J1SpEktnJX7JJ45Bd7xqME/SCplFzYFmTbrkwZbQ4xJK1xMTUYBkN6pWsQ== import-cwd@^3.0.0: version "3.0.0" From 052d12fc197f48c479705f9cfa8dc1a10e182f61 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Fri, 15 Oct 2021 12:11:46 -0400 Subject: [PATCH 16/18] Check object instanceof on all detail panels (#4054) * Check object instanceof on all detail panels Signed-off-by: Sebastian Malton * Fix unit tests - Remove prettier as snapShot testing is too tight of a bound Signed-off-by: Sebastian Malton --- package.json | 1 - .../+catalog/catalog-entity-item.tsx | 8 +- .../components/+catalog/catalog.test.tsx | 112 ++++++------------ .../+config-autoscalers/hpa-details.tsx | 26 ++-- .../limit-range-details.tsx | 20 +++- .../+config-maps/config-map-details.tsx | 42 ++++--- .../pod-disruption-budgets-details.tsx | 14 ++- .../resource-quota-details.tsx | 13 +- .../+config-secrets/secret-details.tsx | 13 +- .../+custom-resources/crd-details.tsx | 14 ++- .../crd-resource-details.tsx | 17 ++- .../components/+events/event-details.tsx | 14 ++- .../components/+events/kube-event-details.tsx | 14 ++- .../+namespaces/namespace-details.tsx | 12 +- .../+network-endpoints/endpoint-details.tsx | 13 +- .../+network-ingresses/ingress-details.tsx | 9 +- .../network-policy-details.tsx | 10 +- .../+network-services/service-details.tsx | 14 ++- .../components/+nodes/node-details.tsx | 12 +- .../pod-security-policy-details.tsx | 35 ++++-- .../storage-class-details.tsx | 16 ++- .../volume-claim-details.tsx | 8 ++ .../+storage-volumes/volume-details.tsx | 8 ++ .../+workloads-cronjobs/cronjob-details.tsx | 24 ++-- .../daemonset-details.tsx | 12 +- .../deployment-details.tsx | 12 +- .../+workloads-jobs/job-details.tsx | 12 +- .../+workloads-pods/pod-details.tsx | 12 +- .../replicaset-details.tsx | 12 +- .../statefulset-details.tsx | 12 +- .../kube-object-meta/kube-object-meta.tsx | 14 ++- yarn.lock | 5 - 32 files changed, 392 insertions(+), 168 deletions(-) diff --git a/package.json b/package.json index 5a452a19d3..bb426467d9 100644 --- a/package.json +++ b/package.json @@ -357,7 +357,6 @@ "postcss": "^8.3.6", "postcss-loader": "4.3.0", "postinstall-postinstall": "^2.1.0", - "prettier": "^2.4.1", "progress-bar-webpack-plugin": "^2.1.0", "randomcolor": "^0.6.2", "raw-loader": "^4.0.2", diff --git a/src/renderer/components/+catalog/catalog-entity-item.tsx b/src/renderer/components/+catalog/catalog-entity-item.tsx index b16406758f..1489ef9efc 100644 --- a/src/renderer/components/+catalog/catalog-entity-item.tsx +++ b/src/renderer/components/+catalog/catalog-entity-item.tsx @@ -21,7 +21,7 @@ import styles from "./catalog.module.css"; import React from "react"; import { action, computed } from "mobx"; -import type { CatalogEntity } from "../../api/catalog-entity"; +import { CatalogEntity } from "../../api/catalog-entity"; import type { ItemObject } from "../../../common/item.store"; import { Badge } from "../badge"; import { navigation } from "../../navigation"; @@ -33,7 +33,11 @@ import type { CatalogEntityRegistry } from "../../api/catalog-entity-registry"; const css = makeCss(styles); export class CatalogEntityItem implements ItemObject { - constructor(public entity: T, private registry: CatalogEntityRegistry) {} + constructor(public entity: T, private registry: CatalogEntityRegistry) { + if (!(entity instanceof CatalogEntity)) { + throw Object.assign(new TypeError("CatalogEntityItem cannot wrap a non-CatalogEntity type"), { typeof: typeof entity, prototype: Object.getPrototypeOf(entity) }); + } + } get kind() { return this.entity.kind; diff --git a/src/renderer/components/+catalog/catalog.test.tsx b/src/renderer/components/+catalog/catalog.test.tsx index bb1ce35a5b..417496a038 100644 --- a/src/renderer/components/+catalog/catalog.test.tsx +++ b/src/renderer/components/+catalog/catalog.test.tsx @@ -26,7 +26,7 @@ import { Catalog } from "./catalog"; import { createMemoryHistory } from "history"; import { mockWindow } from "../../../../__mocks__/windowMock"; import { kubernetesClusterCategory } from "../../../common/catalog-entities/kubernetes-cluster"; -import { catalogCategoryRegistry, CatalogCategoryRegistry } from "../../../common/catalog"; +import { catalogCategoryRegistry, CatalogCategoryRegistry, CatalogEntity, CatalogEntityActionContext, CatalogEntityData } from "../../../common/catalog"; import { CatalogEntityRegistry } from "../../../renderer/api/catalog-entity-registry"; import { CatalogEntityDetailRegistry } from "../../../extensions/registries"; import { CatalogEntityItem } from "./catalog-entity-item"; @@ -46,6 +46,18 @@ jest.mock("@electron/remote", () => { }; }); +class MockCatalogEntity extends CatalogEntity { + public apiVersion = "api"; + public kind = "kind"; + + constructor(data: CatalogEntityData, public onRun: (context: CatalogEntityActionContext) => void | Promise) { + super(data); + } + + public onContextMenuOpen(): void | Promise {} + public onSettingsOpen(): void | Promise {} +} + describe("", () => { const history = createMemoryHistory(); const mockLocation = { @@ -66,30 +78,21 @@ describe("", () => { url: "", }; - const catalogEntityUid = "a_catalogEntity_uid"; - const catalogEntity = { - enabled: true, - apiVersion: "api", - kind: "kind", - metadata: { - uid: catalogEntityUid, - name: "a catalog entity", - labels: { - test: "label", + function createMockCatalogEntity(onRun: (context: CatalogEntityActionContext) => void | Promise) { + return new MockCatalogEntity({ + metadata: { + uid: "a_catalogEntity_uid", + name: "a catalog entity", + labels: { + test: "label", + }, }, - }, - status: { - phase: "", - }, - spec: {}, - }; - const catalogEntityItemMethods = { - getId: () => catalogEntity.metadata.uid, - getName: () => catalogEntity.metadata.name, - onContextMenuOpen: () => {}, - onSettingsOpen: () => {}, - onRun: () => {}, - }; + status: { + phase: "", + }, + spec: {}, + }, onRun); + } beforeEach(() => { CatalogEntityDetailRegistry.createInstance(); @@ -117,11 +120,7 @@ describe("", () => { const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry); const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry); const onRun = jest.fn(); - const catalogEntityItem = new CatalogEntityItem({ - ...catalogEntity, - ...catalogEntityItemMethods, - onRun, - }, catalogEntityRegistry); + const catalogEntityItem = new CatalogEntityItem(createMockCatalogEntity(onRun), catalogEntityRegistry); // mock as if there is a selected item > the detail panel opens jest @@ -130,29 +129,8 @@ describe("", () => { catalogEntityRegistry.addOnBeforeRun( (event) => { - expect(event.target).toMatchInlineSnapshot(` - Object { - "apiVersion": "api", - "enabled": true, - "getId": [Function], - "getName": [Function], - "kind": "kind", - "metadata": Object { - "labels": Object { - "test": "label", - }, - "name": "a catalog entity", - "uid": "a_catalogEntity_uid", - }, - "onContextMenuOpen": [Function], - "onRun": [MockFunction], - "onSettingsOpen": [Function], - "spec": Object {}, - "status": Object { - "phase": "", - }, - } - `); + expect(event.target.getId()).toBe("a_catalogEntity_uid"); + expect(event.target.getName()).toBe("a catalog entity"); setTimeout(() => { expect(onRun).toHaveBeenCalled(); @@ -178,11 +156,7 @@ describe("", () => { const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry); const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry); const onRun = jest.fn(); - const catalogEntityItem = new CatalogEntityItem({ - ...catalogEntity, - ...catalogEntityItemMethods, - onRun, - }, catalogEntityRegistry); + const catalogEntityItem = new CatalogEntityItem(createMockCatalogEntity(onRun), catalogEntityRegistry); // mock as if there is a selected item > the detail panel opens jest @@ -216,11 +190,7 @@ describe("", () => { const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry); const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry); const onRun = jest.fn(); - const catalogEntityItem = new CatalogEntityItem({ - ...catalogEntity, - ...catalogEntityItemMethods, - onRun, - }, catalogEntityRegistry); + const catalogEntityItem = new CatalogEntityItem(createMockCatalogEntity(onRun), catalogEntityRegistry); // mock as if there is a selected item > the detail panel opens jest @@ -255,11 +225,7 @@ describe("", () => { const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry); const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry); const onRun = jest.fn(() => done()); - const catalogEntityItem = new CatalogEntityItem({ - ...catalogEntity, - ...catalogEntityItemMethods, - onRun, - }, catalogEntityRegistry); + const catalogEntityItem = new CatalogEntityItem(createMockCatalogEntity(onRun), catalogEntityRegistry); // mock as if there is a selected item > the detail panel opens jest @@ -289,11 +255,7 @@ describe("", () => { const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry); const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry); const onRun = jest.fn(); - const catalogEntityItem = new CatalogEntityItem({ - ...catalogEntity, - ...catalogEntityItemMethods, - onRun, - }, catalogEntityRegistry); + const catalogEntityItem = new CatalogEntityItem(createMockCatalogEntity(onRun), catalogEntityRegistry); // mock as if there is a selected item > the detail panel opens jest @@ -330,11 +292,7 @@ describe("", () => { const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry); const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry); const onRun = jest.fn(); - const catalogEntityItem = new CatalogEntityItem({ - ...catalogEntity, - ...catalogEntityItemMethods, - onRun, - }, catalogEntityRegistry); + const catalogEntityItem = new CatalogEntityItem(createMockCatalogEntity(onRun), catalogEntityRegistry); // mock as if there is a selected item > the detail panel opens jest diff --git a/src/renderer/components/+config-autoscalers/hpa-details.tsx b/src/renderer/components/+config-autoscalers/hpa-details.tsx index 4dd544e5b1..3e6c74b9bb 100644 --- a/src/renderer/components/+config-autoscalers/hpa-details.tsx +++ b/src/renderer/components/+config-autoscalers/hpa-details.tsx @@ -33,6 +33,7 @@ import { Table, TableCell, TableHead, TableRow } from "../table"; import { apiManager } from "../../../common/k8s-api/api-manager"; import { KubeObjectMeta } from "../kube-object-meta"; import { getDetailsUrl } from "../kube-detail-params"; +import logger from "../../../common/logger"; export interface HpaDetailsProps extends KubeObjectDetailsProps { } @@ -80,17 +81,13 @@ export class HpaDetails extends React.Component { Current / Target { - hpa.getMetrics().map((metric, index) => { - const name = renderName(metric); - const values = hpa.getMetricValues(metric); - - return ( + hpa.getMetrics() + .map((metric, index) => ( - {name} - {values} + {renderName(metric)} + {hpa.getMetricValues(metric)} - ); - }) + )) } ); @@ -99,7 +96,16 @@ export class HpaDetails extends React.Component { render() { const { object: hpa } = this.props; - if (!hpa) return null; + if (!hpa) { + return null; + } + + if (!(hpa instanceof HorizontalPodAutoscaler)) { + logger.error("[HpaDetails]: passed object that is not an instanceof HorizontalPodAutoscaler", hpa); + + return null; + } + const { scaleTargetRef } = hpa.spec; return ( diff --git a/src/renderer/components/+config-limit-ranges/limit-range-details.tsx b/src/renderer/components/+config-limit-ranges/limit-range-details.tsx index e9acbd0a22..14ecbec7ed 100644 --- a/src/renderer/components/+config-limit-ranges/limit-range-details.tsx +++ b/src/renderer/components/+config-limit-ranges/limit-range-details.tsx @@ -28,6 +28,7 @@ import { LimitPart, LimitRange, LimitRangeItem, Resource } from "../../../common import { KubeObjectMeta } from "../kube-object-meta"; import { DrawerItem } from "../drawer/drawer-item"; import { Badge } from "../badge"; +import logger from "../../../common/logger"; interface Props extends KubeObjectDetailsProps { } @@ -57,15 +58,13 @@ function renderResourceLimits(limit: LimitRangeItem, resource: Resource) { function renderLimitDetails(limits: LimitRangeItem[], resources: Resource[]) { - return resources.map(resource => + return resources.map(resource => ( { - limits.map(limit => - renderResourceLimits(limit, resource) - ) + limits.map(limit => renderResourceLimits(limit, resource)) } - ); + )); } @observer @@ -73,7 +72,16 @@ export class LimitRangeDetails extends React.Component { render() { const { object: limitRange } = this.props; - if (!limitRange) return null; + if (!limitRange) { + return null; + } + + if (!(limitRange instanceof LimitRange)) { + logger.error("[LimitRangeDetails]: passed object that is not an instanceof LimitRange", limitRange); + + return null; + } + const containerLimits = limitRange.getContainerLimits(); const podLimits = limitRange.getPodLimits(); const pvcLimits = limitRange.getPVCLimits(); diff --git a/src/renderer/components/+config-maps/config-map-details.tsx b/src/renderer/components/+config-maps/config-map-details.tsx index 6150aa1ad9..082a340a88 100644 --- a/src/renderer/components/+config-maps/config-map-details.tsx +++ b/src/renderer/components/+config-maps/config-map-details.tsx @@ -30,8 +30,9 @@ import { Input } from "../input"; import { Button } from "../button"; import { configMapsStore } from "./config-maps.store"; import type { KubeObjectDetailsProps } from "../kube-object-details"; -import type { ConfigMap } from "../../../common/k8s-api/endpoints"; +import { ConfigMap } from "../../../common/k8s-api/endpoints"; import { KubeObjectMeta } from "../kube-object-meta"; +import logger from "../../../common/logger"; interface Props extends KubeObjectDetailsProps { } @@ -82,7 +83,16 @@ export class ConfigMapDetails extends React.Component { render() { const { object: configMap } = this.props; - if (!configMap) return null; + if (!configMap) { + return null; + } + + if (!(configMap instanceof ConfigMap)) { + logger.error("[ConfigMapDetails]: passed object that is not an instanceof ConfigMap", configMap); + + return null; + } + const data = Array.from(this.data.entries()); return ( @@ -93,22 +103,20 @@ export class ConfigMapDetails extends React.Component { <> { - data.map(([name, value]) => { - return ( -
-
{name}
-
- this.data.set(name, v)} - /> -
+ data.map(([name, value]) => ( +
+
{name}
+
+ this.data.set(name, v)} + />
- ); - }) +
+ )) }