mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge remote-tracking branch 'origin/master' into monaco_editor_refactoring
# Conflicts: # src/renderer/components/+custom-resources/crd-details.tsx # src/renderer/utils/storageHelper.ts
This commit is contained in:
commit
d55d6a1344
8
Makefile
8
Makefile
@ -122,6 +122,8 @@ clean-npm:
|
|||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean: clean-npm clean-extensions
|
clean: clean-npm clean-extensions
|
||||||
rm -rf binaries/client
|
rm -rf binaries/client
|
||||||
rm -rf dist/*
|
rm -rf dist
|
||||||
rm -rf static/build/*
|
rm -rf static/build
|
||||||
rm -rf node_modules/
|
rm -rf node_modules
|
||||||
|
rm -rf site
|
||||||
|
rm -rf docs/extensions/api
|
||||||
|
|||||||
@ -2,6 +2,6 @@
|
|||||||
|
|
||||||
## APIs
|
## APIs
|
||||||
|
|
||||||
- [Common](modules/common.md)
|
- [Common](modules/Common.md)
|
||||||
- [Main](modules/main.md)
|
- [Main](modules/Main.md)
|
||||||
- [Renderer](modules/renderer.md)
|
- [Renderer](modules/Renderer.md)
|
||||||
|
|||||||
22
package.json
22
package.json
@ -193,7 +193,6 @@
|
|||||||
"byline": "^5.0.0",
|
"byline": "^5.0.0",
|
||||||
"chalk": "^4.1.0",
|
"chalk": "^4.1.0",
|
||||||
"chokidar": "^3.4.3",
|
"chokidar": "^3.4.3",
|
||||||
"command-exists": "1.2.9",
|
|
||||||
"conf": "^7.1.2",
|
"conf": "^7.1.2",
|
||||||
"crypto-js": "^4.1.1",
|
"crypto-js": "^4.1.1",
|
||||||
"electron-devtools-installer": "^3.2.0",
|
"electron-devtools-installer": "^3.2.0",
|
||||||
@ -205,7 +204,7 @@
|
|||||||
"grapheme-splitter": "^1.0.4",
|
"grapheme-splitter": "^1.0.4",
|
||||||
"handlebars": "^4.7.7",
|
"handlebars": "^4.7.7",
|
||||||
"http-proxy": "^1.18.1",
|
"http-proxy": "^1.18.1",
|
||||||
"immer": "^8.0.4",
|
"immer": "^9.0.6",
|
||||||
"joi": "^17.4.2",
|
"joi": "^17.4.2",
|
||||||
"js-yaml": "^3.14.0",
|
"js-yaml": "^3.14.0",
|
||||||
"jsdom": "^16.7.0",
|
"jsdom": "^16.7.0",
|
||||||
@ -250,7 +249,7 @@
|
|||||||
"winston": "^3.3.3",
|
"winston": "^3.3.3",
|
||||||
"winston-console-format": "^1.0.8",
|
"winston-console-format": "^1.0.8",
|
||||||
"winston-transport-browserconsole": "^1.0.5",
|
"winston-transport-browserconsole": "^1.0.5",
|
||||||
"ws": "^7.4.6"
|
"ws": "^7.5.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@emeraldpay/hashicon-react": "^0.4.0",
|
"@emeraldpay/hashicon-react": "^0.4.0",
|
||||||
@ -260,7 +259,7 @@
|
|||||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
|
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
|
||||||
"@sentry/react": "^6.13.3",
|
"@sentry/react": "^6.13.3",
|
||||||
"@sentry/types": "^6.8.0",
|
"@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/jest-dom": "^5.14.1",
|
||||||
"@testing-library/react": "^11.2.6",
|
"@testing-library/react": "^11.2.6",
|
||||||
"@testing-library/user-event": "^13.2.1",
|
"@testing-library/user-event": "^13.2.1",
|
||||||
@ -286,7 +285,7 @@
|
|||||||
"@types/mini-css-extract-plugin": "^0.9.1",
|
"@types/mini-css-extract-plugin": "^0.9.1",
|
||||||
"@types/mock-fs": "^4.13.1",
|
"@types/mock-fs": "^4.13.1",
|
||||||
"@types/module-alias": "^2.0.1",
|
"@types/module-alias": "^2.0.1",
|
||||||
"@types/node": "14.17.14",
|
"@types/node": "14.17.26",
|
||||||
"@types/node-fetch": "^2.5.12",
|
"@types/node-fetch": "^2.5.12",
|
||||||
"@types/npm": "^2.0.32",
|
"@types/npm": "^2.0.32",
|
||||||
"@types/progress-bar-webpack-plugin": "^2.1.2",
|
"@types/progress-bar-webpack-plugin": "^2.1.2",
|
||||||
@ -310,7 +309,7 @@
|
|||||||
"@types/tcp-port-used": "^1.0.0",
|
"@types/tcp-port-used": "^1.0.0",
|
||||||
"@types/tempy": "^0.3.0",
|
"@types/tempy": "^0.3.0",
|
||||||
"@types/triple-beam": "^1.3.2",
|
"@types/triple-beam": "^1.3.2",
|
||||||
"@types/url-parse": "^1.4.3",
|
"@types/url-parse": "^1.4.4",
|
||||||
"@types/uuid": "^8.3.1",
|
"@types/uuid": "^8.3.1",
|
||||||
"@types/webdriverio": "^4.13.0",
|
"@types/webdriverio": "^4.13.0",
|
||||||
"@types/webpack": "^4.41.31",
|
"@types/webpack": "^4.41.31",
|
||||||
@ -323,7 +322,7 @@
|
|||||||
"chart.js": "^2.9.4",
|
"chart.js": "^2.9.4",
|
||||||
"circular-dependency-plugin": "^5.2.2",
|
"circular-dependency-plugin": "^5.2.2",
|
||||||
"color": "^3.1.2",
|
"color": "^3.1.2",
|
||||||
"concurrently": "^5.2.0",
|
"concurrently": "^5.3.0",
|
||||||
"css-loader": "^5.2.7",
|
"css-loader": "^5.2.7",
|
||||||
"deepdash": "^5.3.9",
|
"deepdash": "^5.3.9",
|
||||||
"dompurify": "^2.3.3",
|
"dompurify": "^2.3.3",
|
||||||
@ -353,11 +352,10 @@
|
|||||||
"node-gyp": "7.1.2",
|
"node-gyp": "7.1.2",
|
||||||
"node-loader": "^1.0.3",
|
"node-loader": "^1.0.3",
|
||||||
"nodemon": "^2.0.13",
|
"nodemon": "^2.0.13",
|
||||||
"playwright": "^1.14.0",
|
"playwright": "^1.15.2",
|
||||||
"postcss": "^8.3.6",
|
"postcss": "^8.3.6",
|
||||||
"postcss-loader": "4.3.0",
|
"postcss-loader": "4.3.0",
|
||||||
"postinstall-postinstall": "^2.1.0",
|
"postinstall-postinstall": "^2.1.0",
|
||||||
"prettier": "^2.4.1",
|
|
||||||
"progress-bar-webpack-plugin": "^2.1.0",
|
"progress-bar-webpack-plugin": "^2.1.0",
|
||||||
"randomcolor": "^0.6.2",
|
"randomcolor": "^0.6.2",
|
||||||
"raw-loader": "^4.0.2",
|
"raw-loader": "^4.0.2",
|
||||||
@ -368,18 +366,18 @@
|
|||||||
"react-select-event": "^5.1.0",
|
"react-select-event": "^5.1.0",
|
||||||
"react-table": "^7.7.0",
|
"react-table": "^7.7.0",
|
||||||
"react-window": "^1.8.5",
|
"react-window": "^1.8.5",
|
||||||
"sass": "^1.41.1",
|
"sass": "^1.43.2",
|
||||||
"sass-loader": "^8.0.2",
|
"sass-loader": "^8.0.2",
|
||||||
"sharp": "^0.29.1",
|
"sharp": "^0.29.1",
|
||||||
"style-loader": "^2.0.0",
|
"style-loader": "^2.0.0",
|
||||||
"tailwindcss": "^2.2.4",
|
"tailwindcss": "^2.2.17",
|
||||||
"ts-jest": "26.5.6",
|
"ts-jest": "26.5.6",
|
||||||
"ts-loader": "^7.0.5",
|
"ts-loader": "^7.0.5",
|
||||||
"ts-node": "^10.2.1",
|
"ts-node": "^10.2.1",
|
||||||
"type-fest": "^1.0.2",
|
"type-fest": "^1.0.2",
|
||||||
"typed-emitter": "^1.3.1",
|
"typed-emitter": "^1.3.1",
|
||||||
"typedoc": "0.22.5",
|
"typedoc": "0.22.5",
|
||||||
"typedoc-plugin-markdown": "^3.9.0",
|
"typedoc-plugin-markdown": "^3.11.3",
|
||||||
"typeface-roboto": "^1.1.13",
|
"typeface-roboto": "^1.1.13",
|
||||||
"typescript": "^4.4.3",
|
"typescript": "^4.4.3",
|
||||||
"typescript-plugin-css-modules": "^3.4.0",
|
"typescript-plugin-css-modules": "^3.4.0",
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { KubeConfig } from "@kubernetes/client-node";
|
import { KubeConfig } from "@kubernetes/client-node";
|
||||||
import { validateKubeConfig, loadConfigFromString, getNodeWarningConditions } from "../kube-helpers";
|
import { validateKubeConfig, loadConfigFromString } from "../kube-helpers";
|
||||||
|
|
||||||
const kubeconfig = `
|
const kubeconfig = `
|
||||||
apiVersion: v1
|
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");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -54,15 +54,24 @@ export interface KubernetesClusterSpec extends CatalogEntitySpec {
|
|||||||
accessibleNamespaces?: string[];
|
accessibleNamespaces?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum LensKubernetesClusterStatus {
|
||||||
|
DELETING = "deleting",
|
||||||
|
CONNECTING = "connecting",
|
||||||
|
CONNECTED = "connected",
|
||||||
|
DISCONNECTED = "disconnected"
|
||||||
|
}
|
||||||
|
|
||||||
export interface KubernetesClusterMetadata extends CatalogEntityMetadata {
|
export interface KubernetesClusterMetadata extends CatalogEntityMetadata {
|
||||||
distro?: string;
|
distro?: string;
|
||||||
kubeVersion?: 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 type KubernetesClusterStatusPhase = "connected" | "connecting" | "disconnected" | "deleting";
|
||||||
|
|
||||||
export interface KubernetesClusterStatus extends CatalogEntityStatus {
|
export interface KubernetesClusterStatus extends CatalogEntityStatus {
|
||||||
phase: KubernetesClusterStatusPhase;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class KubernetesCluster extends CatalogEntity<KubernetesClusterMetadata, KubernetesClusterStatus, KubernetesClusterSpec> {
|
export class KubernetesCluster extends CatalogEntity<KubernetesClusterMetadata, KubernetesClusterStatus, KubernetesClusterSpec> {
|
||||||
@ -110,15 +119,15 @@ export class KubernetesCluster extends CatalogEntity<KubernetesClusterMetadata,
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (this.status.phase) {
|
switch (this.status.phase) {
|
||||||
case "connected":
|
case LensKubernetesClusterStatus.CONNECTED:
|
||||||
case "connecting":
|
case LensKubernetesClusterStatus.CONNECTING:
|
||||||
context.menuItems.push({
|
context.menuItems.push({
|
||||||
title: "Disconnect",
|
title: "Disconnect",
|
||||||
icon: "link_off",
|
icon: "link_off",
|
||||||
onClick: () => requestMain(clusterDisconnectHandler, this.metadata.uid)
|
onClick: () => requestMain(clusterDisconnectHandler, this.metadata.uid)
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "disconnected":
|
case LensKubernetesClusterStatus.DISCONNECTED:
|
||||||
context.menuItems.push({
|
context.menuItems.push({
|
||||||
title: "Connect",
|
title: "Connect",
|
||||||
icon: "link",
|
icon: "link",
|
||||||
|
|||||||
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -19,24 +19,16 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { KubeConfig, V1Node, V1Pod } from "@kubernetes/client-node";
|
import { KubeConfig } from "@kubernetes/client-node";
|
||||||
import fse from "fs-extra";
|
import fse from "fs-extra";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import os from "os";
|
import os from "os";
|
||||||
import yaml from "js-yaml";
|
import yaml from "js-yaml";
|
||||||
import logger from "../main/logger";
|
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 { Cluster, Context, newClusters, newContexts, newUsers, User } from "@kubernetes/client-node/dist/config_types";
|
||||||
import { resolvePath } from "./utils";
|
import { resolvePath } from "./utils";
|
||||||
import Joi from "joi";
|
import Joi from "joi";
|
||||||
|
|
||||||
export type KubeConfigValidationOpts = {
|
|
||||||
validateCluster?: boolean;
|
|
||||||
validateUser?: boolean;
|
|
||||||
validateExec?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const kubeConfigDefaultPath = path.join(os.homedir(), ".kube", "config");
|
export const kubeConfigDefaultPath = path.join(os.homedir(), ".kube", "config");
|
||||||
|
|
||||||
export function loadConfigFromFileSync(filePath: string): ConfigResult {
|
export function loadConfigFromFileSync(filePath: string): ConfigResult {
|
||||||
@ -86,8 +78,7 @@ const contextSchema = Joi.object({
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const kubeConfigSchema = Joi
|
const kubeConfigSchema = Joi.object({
|
||||||
.object({
|
|
||||||
users: Joi
|
users: Joi
|
||||||
.array()
|
.array()
|
||||||
.items(userSchema)
|
.items(userSchema)
|
||||||
@ -104,17 +95,17 @@ const kubeConfigSchema = Joi
|
|||||||
.string()
|
.string()
|
||||||
.min(1)
|
.min(1)
|
||||||
.optional(),
|
.optional(),
|
||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
export interface KubeConfigOptions {
|
interface KubeConfigOptions {
|
||||||
clusters: Cluster[];
|
clusters: Cluster[];
|
||||||
users: User[];
|
users: User[];
|
||||||
contexts: Context[];
|
contexts: Context[];
|
||||||
currentContext?: string;
|
currentContext?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OptionsResult {
|
interface OptionsResult {
|
||||||
options: KubeConfigOptions;
|
options: KubeConfigOptions;
|
||||||
error: Joi.ValidationError;
|
error: Joi.ValidationError;
|
||||||
}
|
}
|
||||||
@ -132,7 +123,12 @@ function loadToOptions(rawYaml: string): OptionsResult {
|
|||||||
arrays: true,
|
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 clusters = newClusters(rawClusters);
|
||||||
const users = newUsers(rawUsers);
|
const users = newUsers(rawUsers);
|
||||||
const contexts = newContexts(rawContexts);
|
const contexts = newContexts(rawContexts);
|
||||||
@ -175,31 +171,40 @@ export interface SplitConfigEntry {
|
|||||||
* Breaks kube config into several configs. Each context as it own KubeConfig object
|
* Breaks kube config into several configs. Each context as it own KubeConfig object
|
||||||
*/
|
*/
|
||||||
export function splitConfig(kubeConfig: KubeConfig): SplitConfigEntry[] {
|
export function splitConfig(kubeConfig: KubeConfig): SplitConfigEntry[] {
|
||||||
const { contexts = [] } = kubeConfig;
|
return kubeConfig.getContexts().map(ctx => {
|
||||||
|
|
||||||
return contexts.map(context => {
|
|
||||||
const config = new KubeConfig();
|
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);
|
if (cluster) {
|
||||||
config.users = [kubeConfig.getUser(context.user)].filter(Boolean);
|
config.addCluster(cluster);
|
||||||
config.contexts = [kubeConfig.getContextObject(context.name)].filter(Boolean);
|
}
|
||||||
config.setCurrentContext(context.name);
|
|
||||||
|
if (user) {
|
||||||
|
config.addUser(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context) {
|
||||||
|
config.addContext(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
config.setCurrentContext(ctx.name);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
config,
|
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<KubeConfig>): string {
|
export function dumpConfigYaml(kubeConfig: Partial<KubeConfig>): string {
|
||||||
const config = {
|
const clusters = kubeConfig.clusters.map(cluster => ({
|
||||||
apiVersion: "v1",
|
|
||||||
kind: "Config",
|
|
||||||
preferences: {},
|
|
||||||
"current-context": kubeConfig.currentContext,
|
|
||||||
clusters: kubeConfig.clusters.map(cluster => {
|
|
||||||
return {
|
|
||||||
name: cluster.name,
|
name: cluster.name,
|
||||||
cluster: {
|
cluster: {
|
||||||
"certificate-authority-data": cluster.caData,
|
"certificate-authority-data": cluster.caData,
|
||||||
@ -207,20 +212,16 @@ export function dumpConfigYaml(kubeConfig: Partial<KubeConfig>): string {
|
|||||||
server: cluster.server,
|
server: cluster.server,
|
||||||
"insecure-skip-tls-verify": cluster.skipTLSVerify
|
"insecure-skip-tls-verify": cluster.skipTLSVerify
|
||||||
}
|
}
|
||||||
};
|
}));
|
||||||
}),
|
const contexts = kubeConfig.contexts.map(context => ({
|
||||||
contexts: kubeConfig.contexts.map(context => {
|
|
||||||
return {
|
|
||||||
name: context.name,
|
name: context.name,
|
||||||
context: {
|
context: {
|
||||||
cluster: context.cluster,
|
cluster: context.cluster,
|
||||||
user: context.user,
|
user: context.user,
|
||||||
namespace: context.namespace
|
namespace: context.namespace
|
||||||
}
|
}
|
||||||
};
|
}));
|
||||||
}),
|
const users = kubeConfig.users.map(user => ({
|
||||||
users: kubeConfig.users.map(user => {
|
|
||||||
return {
|
|
||||||
name: user.name,
|
name: user.name,
|
||||||
user: {
|
user: {
|
||||||
"client-certificate-data": user.certData,
|
"client-certificate-data": user.certData,
|
||||||
@ -233,8 +234,15 @@ export function dumpConfigYaml(kubeConfig: Partial<KubeConfig>): string {
|
|||||||
username: user.username,
|
username: user.username,
|
||||||
password: user.password
|
password: user.password
|
||||||
}
|
}
|
||||||
};
|
}));
|
||||||
})
|
const config = {
|
||||||
|
apiVersion: "v1",
|
||||||
|
kind: "Config",
|
||||||
|
preferences: {},
|
||||||
|
"current-context": kubeConfig.currentContext,
|
||||||
|
clusters,
|
||||||
|
contexts,
|
||||||
|
users,
|
||||||
};
|
};
|
||||||
|
|
||||||
logger.debug("Dumping KubeConfig:", config);
|
logger.debug("Dumping KubeConfig:", config);
|
||||||
@ -243,70 +251,25 @@ export function dumpConfigYaml(kubeConfig: Partial<KubeConfig>): string {
|
|||||||
return yaml.safeDump(config, { skipInvalid: true });
|
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)
|
* 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
|
* 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 {
|
export function validateKubeConfig(config: KubeConfig, contextName: string): 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
|
|
||||||
|
|
||||||
const { validateUser = true, validateCluster = true, validateExec = true } = validationOpts;
|
|
||||||
|
|
||||||
const contextObject = config.getContextObject(contextName);
|
const contextObject = config.getContextObject(contextName);
|
||||||
|
|
||||||
// Validate the Context Object
|
|
||||||
if (!contextObject) {
|
if (!contextObject) {
|
||||||
return new Error(`No valid context object provided in kubeconfig for context '${contextName}'`);
|
return new Error(`No valid context object provided in kubeconfig for context '${contextName}'`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the Cluster Object
|
if (!config.getCluster(contextObject.cluster)) {
|
||||||
if (validateCluster && !config.getCluster(contextObject.cluster)) {
|
|
||||||
return new Error(`No valid cluster object provided in kubeconfig for context '${contextName}'`);
|
return new Error(`No valid cluster object provided in kubeconfig for context '${contextName}'`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = config.getUser(contextObject.user);
|
if (!config.getUser(contextObject.user)) {
|
||||||
|
|
||||||
// Validate the User Object
|
|
||||||
if (validateUser && !user) {
|
|
||||||
return new Error(`No valid user object provided in kubeconfig for context '${contextName}'`);
|
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;
|
return undefined;
|
||||||
} catch (error) {
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,5 +19,22 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export * 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";
|
export * from "../../common/catalog/catalog-entity";
|
||||||
|
|||||||
@ -38,6 +38,6 @@ fetchMock.enableMocks();
|
|||||||
// Mock __non_webpack_require__ for tests
|
// Mock __non_webpack_require__ for tests
|
||||||
globalThis.__non_webpack_require__ = jest.fn();
|
globalThis.__non_webpack_require__ = jest.fn();
|
||||||
|
|
||||||
process.on("unhandledRejection", (err) => {
|
process.on("unhandledRejection", (err: any) => {
|
||||||
fail(err);
|
fail(err);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -26,7 +26,7 @@ import { FSWatcher, watch } from "chokidar";
|
|||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import type stream from "stream";
|
import type stream from "stream";
|
||||||
import { Disposer, ExtendedObservableMap, iter, Singleton, storedKubeConfigFolder } from "../../common/utils";
|
import { bytesToUnits, Disposer, ExtendedObservableMap, iter, noop, Singleton, storedKubeConfigFolder } from "../../common/utils";
|
||||||
import logger from "../logger";
|
import logger from "../logger";
|
||||||
import type { KubeConfig } from "@kubernetes/client-node";
|
import type { KubeConfig } from "@kubernetes/client-node";
|
||||||
import { loadConfigFromString, splitConfig } from "../../common/kube-helpers";
|
import { loadConfigFromString, splitConfig } from "../../common/kube-helpers";
|
||||||
@ -54,6 +54,15 @@ const ignoreGlobs = [
|
|||||||
matcher: globToRegExp(rawGlob),
|
matcher: globToRegExp(rawGlob),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This should be much larger than any kubeconfig text file
|
||||||
|
*
|
||||||
|
* Even if you have a cert-file, key-file, and client-cert files that is only
|
||||||
|
* 12kb of extra data (at 4096 bytes each) which allows for around 150 entries.
|
||||||
|
*/
|
||||||
|
const folderSyncMaxAllowedFileReadSize = 2 * 1024 * 1024; // 2 MiB
|
||||||
|
const fileSyncMaxAllowedFileReadSize = 16 * folderSyncMaxAllowedFileReadSize; // 32 MiB
|
||||||
|
|
||||||
export class KubeconfigSyncManager extends Singleton {
|
export class KubeconfigSyncManager extends Singleton {
|
||||||
protected sources = observable.map<string, [IComputedValue<CatalogEntity[]>, Disposer]>();
|
protected sources = observable.map<string, [IComputedValue<CatalogEntity[]>, Disposer]>();
|
||||||
protected syncing = false;
|
protected syncing = false;
|
||||||
@ -228,15 +237,30 @@ export function computeDiff(contents: string, source: RootSource, filePath: stri
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function diffChangedConfig(filePath: string, source: RootSource): Disposer {
|
interface DiffChangedConfigArgs {
|
||||||
|
filePath: string;
|
||||||
|
source: RootSource;
|
||||||
|
stats: fs.Stats;
|
||||||
|
maxAllowedFileReadSize: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function diffChangedConfig({ filePath, source, stats, maxAllowedFileReadSize }: DiffChangedConfigArgs): Disposer {
|
||||||
logger.debug(`${logPrefix} file changed`, { filePath });
|
logger.debug(`${logPrefix} file changed`, { filePath });
|
||||||
|
|
||||||
|
if (stats.size >= maxAllowedFileReadSize) {
|
||||||
|
logger.warn(`${logPrefix} skipping ${filePath}: size=${bytesToUnits(stats.size)} is larger than maxSize=${bytesToUnits(maxAllowedFileReadSize)}`);
|
||||||
|
source.clear();
|
||||||
|
|
||||||
|
return noop;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: replace with an AbortController with fs.readFile when we upgrade to Node 16 (after it comes out)
|
// TODO: replace with an AbortController with fs.readFile when we upgrade to Node 16 (after it comes out)
|
||||||
const fileReader = fs.createReadStream(filePath, {
|
const fileReader = fs.createReadStream(filePath, {
|
||||||
mode: fs.constants.O_RDONLY,
|
mode: fs.constants.O_RDONLY,
|
||||||
});
|
});
|
||||||
const readStream: stream.Readable = fileReader;
|
const readStream: stream.Readable = fileReader;
|
||||||
const bufs: Buffer[] = [];
|
const decoder = new TextDecoder("utf-8", { fatal: true });
|
||||||
|
let fileString = "";
|
||||||
let closed = false;
|
let closed = false;
|
||||||
|
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
@ -253,7 +277,15 @@ function diffChangedConfig(filePath: string, source: RootSource): Disposer {
|
|||||||
};
|
};
|
||||||
|
|
||||||
readStream
|
readStream
|
||||||
.on("data", chunk => bufs.push(chunk))
|
.on("data", (chunk: Buffer) => {
|
||||||
|
try {
|
||||||
|
fileString += decoder.decode(chunk, { stream: true });
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn(`${logPrefix} skipping ${filePath}: ${error}`);
|
||||||
|
source.clear();
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
})
|
||||||
.on("close", () => cleanup())
|
.on("close", () => cleanup())
|
||||||
.on("error", error => {
|
.on("error", error => {
|
||||||
cleanup();
|
cleanup();
|
||||||
@ -261,7 +293,7 @@ function diffChangedConfig(filePath: string, source: RootSource): Disposer {
|
|||||||
})
|
})
|
||||||
.on("end", () => {
|
.on("end", () => {
|
||||||
if (!closed) {
|
if (!closed) {
|
||||||
computeDiff(Buffer.concat(bufs).toString("utf-8"), source, filePath);
|
computeDiff(fileString, source, filePath);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -279,6 +311,9 @@ function watchFileChanges(filePath: string): [IComputedValue<CatalogEntity[]>, D
|
|||||||
const stat = await fs.promises.stat(filePath);
|
const stat = await fs.promises.stat(filePath);
|
||||||
const isFolderSync = stat.isDirectory();
|
const isFolderSync = stat.isDirectory();
|
||||||
const cleanupFns = new Map<string, Disposer>();
|
const cleanupFns = new Map<string, Disposer>();
|
||||||
|
const maxAllowedFileReadSize = isFolderSync
|
||||||
|
? folderSyncMaxAllowedFileReadSize
|
||||||
|
: fileSyncMaxAllowedFileReadSize;
|
||||||
|
|
||||||
watcher = watch(filePath, {
|
watcher = watch(filePath, {
|
||||||
followSymlinks: true,
|
followSymlinks: true,
|
||||||
@ -294,7 +329,7 @@ function watchFileChanges(filePath: string): [IComputedValue<CatalogEntity[]>, D
|
|||||||
});
|
});
|
||||||
|
|
||||||
watcher
|
watcher
|
||||||
.on("change", (childFilePath) => {
|
.on("change", (childFilePath, stats) => {
|
||||||
const cleanup = cleanupFns.get(childFilePath);
|
const cleanup = cleanupFns.get(childFilePath);
|
||||||
|
|
||||||
if (!cleanup) {
|
if (!cleanup) {
|
||||||
@ -303,9 +338,14 @@ function watchFileChanges(filePath: string): [IComputedValue<CatalogEntity[]>, D
|
|||||||
}
|
}
|
||||||
|
|
||||||
cleanup();
|
cleanup();
|
||||||
cleanupFns.set(childFilePath, diffChangedConfig(childFilePath, rootSource.getOrInsert(childFilePath, observable.map)));
|
cleanupFns.set(childFilePath, diffChangedConfig({
|
||||||
|
filePath: childFilePath,
|
||||||
|
source: rootSource.getOrInsert(childFilePath, observable.map),
|
||||||
|
stats,
|
||||||
|
maxAllowedFileReadSize,
|
||||||
|
}));
|
||||||
})
|
})
|
||||||
.on("add", (childFilePath) => {
|
.on("add", (childFilePath, stats) => {
|
||||||
if (isFolderSync) {
|
if (isFolderSync) {
|
||||||
const fileName = path.basename(childFilePath);
|
const fileName = path.basename(childFilePath);
|
||||||
|
|
||||||
@ -316,7 +356,12 @@ function watchFileChanges(filePath: string): [IComputedValue<CatalogEntity[]>, D
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanupFns.set(childFilePath, diffChangedConfig(childFilePath, rootSource.getOrInsert(childFilePath, observable.map)));
|
cleanupFns.set(childFilePath, diffChangedConfig({
|
||||||
|
filePath: childFilePath,
|
||||||
|
source: rootSource.getOrInsert(childFilePath, observable.map),
|
||||||
|
stats,
|
||||||
|
maxAllowedFileReadSize,
|
||||||
|
}));
|
||||||
})
|
})
|
||||||
.on("unlink", (childFilePath) => {
|
.on("unlink", (childFilePath) => {
|
||||||
cleanupFns.get(childFilePath)?.();
|
cleanupFns.get(childFilePath)?.();
|
||||||
|
|||||||
@ -27,7 +27,7 @@ import logger from "./logger";
|
|||||||
import { apiKubePrefix } from "../common/vars";
|
import { apiKubePrefix } from "../common/vars";
|
||||||
import { getClusterIdFromHost, Singleton } from "../common/utils";
|
import { getClusterIdFromHost, Singleton } from "../common/utils";
|
||||||
import { catalogEntityRegistry } from "./catalog";
|
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 { ipcMainOn } from "../common/ipc";
|
||||||
import { once } from "lodash";
|
import { once } from "lodash";
|
||||||
import { ClusterStore } from "../common/cluster-store";
|
import { ClusterStore } from "../common/cluster-store";
|
||||||
@ -35,6 +35,8 @@ import type { ClusterId } from "../common/cluster-types";
|
|||||||
|
|
||||||
const logPrefix = "[CLUSTER-MANAGER]:";
|
const logPrefix = "[CLUSTER-MANAGER]:";
|
||||||
|
|
||||||
|
const lensSpecificClusterStatuses: Set<string> = new Set(Object.values(LensKubernetesClusterStatus));
|
||||||
|
|
||||||
export class ClusterManager extends Singleton {
|
export class ClusterManager extends Singleton {
|
||||||
private store = ClusterStore.getInstance();
|
private store = ClusterStore.getInstance();
|
||||||
deleting = observable.set<ClusterId>();
|
deleting = observable.set<ClusterId>();
|
||||||
@ -90,6 +92,8 @@ export class ClusterManager extends Singleton {
|
|||||||
|
|
||||||
@action
|
@action
|
||||||
protected updateCatalog(clusters: Cluster[]) {
|
protected updateCatalog(clusters: Cluster[]) {
|
||||||
|
logger.debug("[CLUSTER-MANAGER]: updating catalog from cluster store");
|
||||||
|
|
||||||
for (const cluster of clusters) {
|
for (const cluster of clusters) {
|
||||||
this.updateEntityFromCluster(cluster);
|
this.updateEntityFromCluster(cluster);
|
||||||
}
|
}
|
||||||
@ -144,27 +148,28 @@ export class ClusterManager extends Singleton {
|
|||||||
@action
|
@action
|
||||||
protected updateEntityStatus(entity: KubernetesCluster, cluster?: Cluster) {
|
protected updateEntityStatus(entity: KubernetesCluster, cluster?: Cluster) {
|
||||||
if (this.deleting.has(entity.getId())) {
|
if (this.deleting.has(entity.getId())) {
|
||||||
entity.status.phase = "deleting";
|
entity.status.phase = LensKubernetesClusterStatus.DELETING;
|
||||||
entity.status.enabled = false;
|
entity.status.enabled = false;
|
||||||
} else {
|
} else {
|
||||||
entity.status.phase = ((): KubernetesClusterStatusPhase => {
|
entity.status.phase = (() => {
|
||||||
if (!cluster) {
|
if (!cluster) {
|
||||||
return "disconnected";
|
return LensKubernetesClusterStatus.DISCONNECTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cluster.accessible) {
|
if (cluster.accessible) {
|
||||||
return "connected";
|
return LensKubernetesClusterStatus.CONNECTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cluster.disconnected) {
|
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 entity.status.phase;
|
||||||
}
|
}
|
||||||
|
|
||||||
return "disconnected";
|
return LensKubernetesClusterStatus.DISCONNECTED;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
entity.status.enabled = true;
|
entity.status.enabled = true;
|
||||||
@ -276,7 +281,9 @@ export function catalogEntityFromCluster(cluster: Cluster) {
|
|||||||
icon: {}
|
icon: {}
|
||||||
},
|
},
|
||||||
status: {
|
status: {
|
||||||
phase: cluster.disconnected ? "disconnected" : "connected",
|
phase: cluster.disconnected
|
||||||
|
? LensKubernetesClusterStatus.DISCONNECTED
|
||||||
|
: LensKubernetesClusterStatus.CONNECTED,
|
||||||
reason: "",
|
reason: "",
|
||||||
message: "",
|
message: "",
|
||||||
active: !cluster.disconnected
|
active: !cluster.disconnected
|
||||||
|
|||||||
@ -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 type { ClusterState, ClusterRefreshOptions, ClusterMetricsResourceType, ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences, UpdateClusterModel } from "../common/cluster-types";
|
||||||
import { ClusterMetadataKey, initialNodeShellImage, ClusterStatus } from "../common/cluster-types";
|
import { ClusterMetadataKey, initialNodeShellImage, ClusterStatus } from "../common/cluster-types";
|
||||||
import { storedKubeConfigFolder, toJS } from "../common/utils";
|
import { storedKubeConfigFolder, toJS } from "../common/utils";
|
||||||
|
import type { Response } from "request";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cluster
|
* Cluster
|
||||||
@ -394,7 +395,6 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
@action disconnect() {
|
@action disconnect() {
|
||||||
logger.info(`[CLUSTER]: disconnect`, this.getMeta());
|
|
||||||
this.unbindEvents();
|
this.unbindEvents();
|
||||||
this.contextHandler?.stopServer();
|
this.contextHandler?.stopServer();
|
||||||
this.disconnected = true;
|
this.disconnected = true;
|
||||||
@ -405,6 +405,7 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
this.allowedNamespaces = [];
|
this.allowedNamespaces = [];
|
||||||
this.resourceAccessStatuses.clear();
|
this.resourceAccessStatuses.clear();
|
||||||
this.pushState();
|
this.pushState();
|
||||||
|
logger.info(`[CLUSTER]: disconnect`, this.getMeta());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -642,8 +643,6 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getAllowedNamespacesErrorCount = 0;
|
|
||||||
|
|
||||||
protected async getAllowedNamespaces() {
|
protected async getAllowedNamespaces() {
|
||||||
if (this.accessibleNamespaces.length) {
|
if (this.accessibleNamespaces.length) {
|
||||||
return this.accessibleNamespaces;
|
return this.accessibleNamespaces;
|
||||||
@ -655,25 +654,17 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
const { body: { items } } = await api.listNamespace();
|
const { body: { items } } = await api.listNamespace();
|
||||||
const namespaces = items.map(ns => ns.metadata.name);
|
const namespaces = items.map(ns => ns.metadata.name);
|
||||||
|
|
||||||
this.getAllowedNamespacesErrorCount = 0; // reset on success
|
|
||||||
|
|
||||||
return namespaces;
|
return namespaces;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const ctx = (await this.getProxyKubeconfig()).getContextObject(this.contextName);
|
const ctx = (await this.getProxyKubeconfig()).getContextObject(this.contextName);
|
||||||
const namespaceList = [ctx.namespace].filter(Boolean);
|
const namespaceList = [ctx.namespace].filter(Boolean);
|
||||||
|
|
||||||
if (namespaceList.length === 0 && error instanceof HttpError && error.statusCode === 403) {
|
if (namespaceList.length === 0 && error instanceof HttpError && error.statusCode === 403) {
|
||||||
this.getAllowedNamespacesErrorCount += 1;
|
const { response } = error as HttpError & { response: Response };
|
||||||
|
|
||||||
if (this.getAllowedNamespacesErrorCount > 3) {
|
logger.info("[CLUSTER]: listing namespaces is forbidden, broadcasting", { clusterId: this.id, error: response.body });
|
||||||
// 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);
|
broadcastMessage(ClusterListNamespaceForbiddenChannel, this.id);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return namespaceList;
|
return namespaceList;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
import styles from "./catalog.module.css";
|
import styles from "./catalog.module.css";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { action, computed } from "mobx";
|
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 type { ItemObject } from "../../../common/item.store";
|
||||||
import { Badge } from "../badge";
|
import { Badge } from "../badge";
|
||||||
import { navigation } from "../../navigation";
|
import { navigation } from "../../navigation";
|
||||||
@ -33,7 +33,11 @@ import type { CatalogEntityRegistry } from "../../api/catalog-entity-registry";
|
|||||||
const css = makeCss(styles);
|
const css = makeCss(styles);
|
||||||
|
|
||||||
export class CatalogEntityItem<T extends CatalogEntity> implements ItemObject {
|
export class CatalogEntityItem<T extends CatalogEntity> 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() {
|
get kind() {
|
||||||
return this.entity.kind;
|
return this.entity.kind;
|
||||||
|
|||||||
@ -26,7 +26,7 @@ import { Catalog } from "./catalog";
|
|||||||
import { createMemoryHistory } from "history";
|
import { createMemoryHistory } from "history";
|
||||||
import { mockWindow } from "../../../../__mocks__/windowMock";
|
import { mockWindow } from "../../../../__mocks__/windowMock";
|
||||||
import { kubernetesClusterCategory } from "../../../common/catalog-entities/kubernetes-cluster";
|
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 { CatalogEntityRegistry } from "../../../renderer/api/catalog-entity-registry";
|
||||||
import { CatalogEntityDetailRegistry } from "../../../extensions/registries";
|
import { CatalogEntityDetailRegistry } from "../../../extensions/registries";
|
||||||
import { CatalogEntityItem } from "./catalog-entity-item";
|
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<void>) {
|
||||||
|
super(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public onContextMenuOpen(): void | Promise<void> {}
|
||||||
|
public onSettingsOpen(): void | Promise<void> {}
|
||||||
|
}
|
||||||
|
|
||||||
describe("<Catalog />", () => {
|
describe("<Catalog />", () => {
|
||||||
const history = createMemoryHistory();
|
const history = createMemoryHistory();
|
||||||
const mockLocation = {
|
const mockLocation = {
|
||||||
@ -66,13 +78,10 @@ describe("<Catalog />", () => {
|
|||||||
url: "",
|
url: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const catalogEntityUid = "a_catalogEntity_uid";
|
function createMockCatalogEntity(onRun: (context: CatalogEntityActionContext) => void | Promise<void>) {
|
||||||
const catalogEntity = {
|
return new MockCatalogEntity({
|
||||||
enabled: true,
|
|
||||||
apiVersion: "api",
|
|
||||||
kind: "kind",
|
|
||||||
metadata: {
|
metadata: {
|
||||||
uid: catalogEntityUid,
|
uid: "a_catalogEntity_uid",
|
||||||
name: "a catalog entity",
|
name: "a catalog entity",
|
||||||
labels: {
|
labels: {
|
||||||
test: "label",
|
test: "label",
|
||||||
@ -82,14 +91,8 @@ describe("<Catalog />", () => {
|
|||||||
phase: "",
|
phase: "",
|
||||||
},
|
},
|
||||||
spec: {},
|
spec: {},
|
||||||
};
|
}, onRun);
|
||||||
const catalogEntityItemMethods = {
|
}
|
||||||
getId: () => catalogEntity.metadata.uid,
|
|
||||||
getName: () => catalogEntity.metadata.name,
|
|
||||||
onContextMenuOpen: () => {},
|
|
||||||
onSettingsOpen: () => {},
|
|
||||||
onRun: () => {},
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
CatalogEntityDetailRegistry.createInstance();
|
CatalogEntityDetailRegistry.createInstance();
|
||||||
@ -117,11 +120,7 @@ describe("<Catalog />", () => {
|
|||||||
const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry);
|
const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry);
|
||||||
const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry);
|
const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry);
|
||||||
const onRun = jest.fn();
|
const onRun = jest.fn();
|
||||||
const catalogEntityItem = new CatalogEntityItem({
|
const catalogEntityItem = new CatalogEntityItem(createMockCatalogEntity(onRun), catalogEntityRegistry);
|
||||||
...catalogEntity,
|
|
||||||
...catalogEntityItemMethods,
|
|
||||||
onRun,
|
|
||||||
}, catalogEntityRegistry);
|
|
||||||
|
|
||||||
// mock as if there is a selected item > the detail panel opens
|
// mock as if there is a selected item > the detail panel opens
|
||||||
jest
|
jest
|
||||||
@ -130,29 +129,8 @@ describe("<Catalog />", () => {
|
|||||||
|
|
||||||
catalogEntityRegistry.addOnBeforeRun(
|
catalogEntityRegistry.addOnBeforeRun(
|
||||||
(event) => {
|
(event) => {
|
||||||
expect(event.target).toMatchInlineSnapshot(`
|
expect(event.target.getId()).toBe("a_catalogEntity_uid");
|
||||||
Object {
|
expect(event.target.getName()).toBe("a catalog entity");
|
||||||
"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": "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
expect(onRun).toHaveBeenCalled();
|
expect(onRun).toHaveBeenCalled();
|
||||||
@ -178,11 +156,7 @@ describe("<Catalog />", () => {
|
|||||||
const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry);
|
const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry);
|
||||||
const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry);
|
const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry);
|
||||||
const onRun = jest.fn();
|
const onRun = jest.fn();
|
||||||
const catalogEntityItem = new CatalogEntityItem({
|
const catalogEntityItem = new CatalogEntityItem(createMockCatalogEntity(onRun), catalogEntityRegistry);
|
||||||
...catalogEntity,
|
|
||||||
...catalogEntityItemMethods,
|
|
||||||
onRun,
|
|
||||||
}, catalogEntityRegistry);
|
|
||||||
|
|
||||||
// mock as if there is a selected item > the detail panel opens
|
// mock as if there is a selected item > the detail panel opens
|
||||||
jest
|
jest
|
||||||
@ -216,11 +190,7 @@ describe("<Catalog />", () => {
|
|||||||
const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry);
|
const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry);
|
||||||
const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry);
|
const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry);
|
||||||
const onRun = jest.fn();
|
const onRun = jest.fn();
|
||||||
const catalogEntityItem = new CatalogEntityItem({
|
const catalogEntityItem = new CatalogEntityItem(createMockCatalogEntity(onRun), catalogEntityRegistry);
|
||||||
...catalogEntity,
|
|
||||||
...catalogEntityItemMethods,
|
|
||||||
onRun,
|
|
||||||
}, catalogEntityRegistry);
|
|
||||||
|
|
||||||
// mock as if there is a selected item > the detail panel opens
|
// mock as if there is a selected item > the detail panel opens
|
||||||
jest
|
jest
|
||||||
@ -255,11 +225,7 @@ describe("<Catalog />", () => {
|
|||||||
const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry);
|
const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry);
|
||||||
const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry);
|
const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry);
|
||||||
const onRun = jest.fn(() => done());
|
const onRun = jest.fn(() => done());
|
||||||
const catalogEntityItem = new CatalogEntityItem({
|
const catalogEntityItem = new CatalogEntityItem(createMockCatalogEntity(onRun), catalogEntityRegistry);
|
||||||
...catalogEntity,
|
|
||||||
...catalogEntityItemMethods,
|
|
||||||
onRun,
|
|
||||||
}, catalogEntityRegistry);
|
|
||||||
|
|
||||||
// mock as if there is a selected item > the detail panel opens
|
// mock as if there is a selected item > the detail panel opens
|
||||||
jest
|
jest
|
||||||
@ -289,11 +255,7 @@ describe("<Catalog />", () => {
|
|||||||
const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry);
|
const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry);
|
||||||
const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry);
|
const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry);
|
||||||
const onRun = jest.fn();
|
const onRun = jest.fn();
|
||||||
const catalogEntityItem = new CatalogEntityItem({
|
const catalogEntityItem = new CatalogEntityItem(createMockCatalogEntity(onRun), catalogEntityRegistry);
|
||||||
...catalogEntity,
|
|
||||||
...catalogEntityItemMethods,
|
|
||||||
onRun,
|
|
||||||
}, catalogEntityRegistry);
|
|
||||||
|
|
||||||
// mock as if there is a selected item > the detail panel opens
|
// mock as if there is a selected item > the detail panel opens
|
||||||
jest
|
jest
|
||||||
@ -330,11 +292,7 @@ describe("<Catalog />", () => {
|
|||||||
const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry);
|
const catalogEntityRegistry = new CatalogEntityRegistry(catalogCategoryRegistry);
|
||||||
const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry);
|
const catalogEntityStore = new CatalogEntityStore(catalogEntityRegistry);
|
||||||
const onRun = jest.fn();
|
const onRun = jest.fn();
|
||||||
const catalogEntityItem = new CatalogEntityItem({
|
const catalogEntityItem = new CatalogEntityItem(createMockCatalogEntity(onRun), catalogEntityRegistry);
|
||||||
...catalogEntity,
|
|
||||||
...catalogEntityItemMethods,
|
|
||||||
onRun,
|
|
||||||
}, catalogEntityRegistry);
|
|
||||||
|
|
||||||
// mock as if there is a selected item > the detail panel opens
|
// mock as if there is a selected item > the detail panel opens
|
||||||
jest
|
jest
|
||||||
|
|||||||
@ -33,6 +33,7 @@ import { Table, TableCell, TableHead, TableRow } from "../table";
|
|||||||
import { apiManager } from "../../../common/k8s-api/api-manager";
|
import { apiManager } from "../../../common/k8s-api/api-manager";
|
||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
import { getDetailsUrl } from "../kube-detail-params";
|
import { getDetailsUrl } from "../kube-detail-params";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
export interface HpaDetailsProps extends KubeObjectDetailsProps<HorizontalPodAutoscaler> {
|
export interface HpaDetailsProps extends KubeObjectDetailsProps<HorizontalPodAutoscaler> {
|
||||||
}
|
}
|
||||||
@ -80,17 +81,13 @@ export class HpaDetails extends React.Component<HpaDetailsProps> {
|
|||||||
<TableCell className="metrics">Current / Target</TableCell>
|
<TableCell className="metrics">Current / Target</TableCell>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
{
|
{
|
||||||
hpa.getMetrics().map((metric, index) => {
|
hpa.getMetrics()
|
||||||
const name = renderName(metric);
|
.map((metric, index) => (
|
||||||
const values = hpa.getMetricValues(metric);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TableRow key={index}>
|
<TableRow key={index}>
|
||||||
<TableCell className="name">{name}</TableCell>
|
<TableCell className="name">{renderName(metric)}</TableCell>
|
||||||
<TableCell className="metrics">{values}</TableCell>
|
<TableCell className="metrics">{hpa.getMetricValues(metric)}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
);
|
))
|
||||||
})
|
|
||||||
}
|
}
|
||||||
</Table>
|
</Table>
|
||||||
);
|
);
|
||||||
@ -99,7 +96,16 @@ export class HpaDetails extends React.Component<HpaDetailsProps> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: hpa } = this.props;
|
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;
|
const { scaleTargetRef } = hpa.spec;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -28,6 +28,7 @@ import { LimitPart, LimitRange, LimitRangeItem, Resource } from "../../../common
|
|||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
import { DrawerItem } from "../drawer/drawer-item";
|
import { DrawerItem } from "../drawer/drawer-item";
|
||||||
import { Badge } from "../badge";
|
import { Badge } from "../badge";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<LimitRange> {
|
interface Props extends KubeObjectDetailsProps<LimitRange> {
|
||||||
}
|
}
|
||||||
@ -57,15 +58,13 @@ function renderResourceLimits(limit: LimitRangeItem, resource: Resource) {
|
|||||||
|
|
||||||
function renderLimitDetails(limits: LimitRangeItem[], resources: Resource[]) {
|
function renderLimitDetails(limits: LimitRangeItem[], resources: Resource[]) {
|
||||||
|
|
||||||
return resources.map(resource =>
|
return resources.map(resource => (
|
||||||
<DrawerItem key={resource} name={resource}>
|
<DrawerItem key={resource} name={resource}>
|
||||||
{
|
{
|
||||||
limits.map(limit =>
|
limits.map(limit => renderResourceLimits(limit, resource))
|
||||||
renderResourceLimits(limit, resource)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
@ -73,7 +72,16 @@ export class LimitRangeDetails extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: limitRange } = this.props;
|
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 containerLimits = limitRange.getContainerLimits();
|
||||||
const podLimits = limitRange.getPodLimits();
|
const podLimits = limitRange.getPodLimits();
|
||||||
const pvcLimits = limitRange.getPVCLimits();
|
const pvcLimits = limitRange.getPVCLimits();
|
||||||
|
|||||||
@ -30,8 +30,9 @@ import { Input } from "../input";
|
|||||||
import { Button } from "../button";
|
import { Button } from "../button";
|
||||||
import { configMapsStore } from "./config-maps.store";
|
import { configMapsStore } from "./config-maps.store";
|
||||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
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 { KubeObjectMeta } from "../kube-object-meta";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<ConfigMap> {
|
interface Props extends KubeObjectDetailsProps<ConfigMap> {
|
||||||
}
|
}
|
||||||
@ -82,7 +83,16 @@ export class ConfigMapDetails extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: configMap } = this.props;
|
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());
|
const data = Array.from(this.data.entries());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -93,8 +103,7 @@ export class ConfigMapDetails extends React.Component<Props> {
|
|||||||
<>
|
<>
|
||||||
<DrawerTitle title="Data"/>
|
<DrawerTitle title="Data"/>
|
||||||
{
|
{
|
||||||
data.map(([name, value]) => {
|
data.map(([name, value]) => (
|
||||||
return (
|
|
||||||
<div key={name} className="data">
|
<div key={name} className="data">
|
||||||
<div className="name">{name}</div>
|
<div className="name">{name}</div>
|
||||||
<div className="flex gaps align-flex-start">
|
<div className="flex gaps align-flex-start">
|
||||||
@ -107,8 +116,7 @@ export class ConfigMapDetails extends React.Component<Props> {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
))
|
||||||
})
|
|
||||||
}
|
}
|
||||||
<Button
|
<Button
|
||||||
primary
|
primary
|
||||||
|
|||||||
@ -26,8 +26,9 @@ import { observer } from "mobx-react";
|
|||||||
import { DrawerItem } from "../drawer";
|
import { DrawerItem } from "../drawer";
|
||||||
import { Badge } from "../badge";
|
import { Badge } from "../badge";
|
||||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
||||||
import type { PodDisruptionBudget } from "../../../common/k8s-api/endpoints";
|
import { PodDisruptionBudget } from "../../../common/k8s-api/endpoints";
|
||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<PodDisruptionBudget> {
|
interface Props extends KubeObjectDetailsProps<PodDisruptionBudget> {
|
||||||
}
|
}
|
||||||
@ -38,7 +39,16 @@ export class PodDisruptionBudgetDetails extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: pdb } = this.props;
|
const { object: pdb } = this.props;
|
||||||
|
|
||||||
if (!pdb) return null;
|
if (!pdb) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(pdb instanceof PodDisruptionBudget)) {
|
||||||
|
logger.error("[PodDisruptionBudgetDetails]: passed object that is not an instanceof PodDisruptionBudget", pdb);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const selectors = pdb.getSelectors();
|
const selectors = pdb.getSelectors();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -26,10 +26,11 @@ import { observer } from "mobx-react";
|
|||||||
import { DrawerItem, DrawerTitle } from "../drawer";
|
import { DrawerItem, DrawerTitle } from "../drawer";
|
||||||
import { cpuUnitsToNumber, cssNames, unitsToBytes, metricUnitsToNumber } from "../../utils";
|
import { cpuUnitsToNumber, cssNames, unitsToBytes, metricUnitsToNumber } from "../../utils";
|
||||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
||||||
import type { ResourceQuota } from "../../../common/k8s-api/endpoints/resource-quota.api";
|
import { ResourceQuota } from "../../../common/k8s-api/endpoints/resource-quota.api";
|
||||||
import { LineProgress } from "../line-progress";
|
import { LineProgress } from "../line-progress";
|
||||||
import { Table, TableCell, TableHead, TableRow } from "../table";
|
import { Table, TableCell, TableHead, TableRow } from "../table";
|
||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<ResourceQuota> {
|
interface Props extends KubeObjectDetailsProps<ResourceQuota> {
|
||||||
}
|
}
|
||||||
@ -77,7 +78,15 @@ export class ResourceQuotaDetails extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: quota } = this.props;
|
const { object: quota } = this.props;
|
||||||
|
|
||||||
if (!quota) return null;
|
if (!quota) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(quota instanceof ResourceQuota)) {
|
||||||
|
logger.error("[ResourceQuotaDetails]: passed object that is not an instanceof ResourceQuota", quota);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="ResourceQuotaDetails">
|
<div className="ResourceQuotaDetails">
|
||||||
|
|||||||
@ -33,8 +33,9 @@ import { base64 } from "../../utils";
|
|||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { secretsStore } from "./secrets.store";
|
import { secretsStore } from "./secrets.store";
|
||||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
||||||
import type { Secret } from "../../../common/k8s-api/endpoints";
|
import { Secret } from "../../../common/k8s-api/endpoints";
|
||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<Secret> {
|
interface Props extends KubeObjectDetailsProps<Secret> {
|
||||||
}
|
}
|
||||||
@ -84,7 +85,15 @@ export class SecretDetails extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: secret } = this.props;
|
const { object: secret } = this.props;
|
||||||
|
|
||||||
if (!secret) return null;
|
if (!secret) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(secret instanceof Secret)) {
|
||||||
|
logger.error("[SecretDetails]: passed object that is not an instanceof Secret", secret);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="SecretDetails">
|
<div className="SecretDetails">
|
||||||
|
|||||||
@ -24,7 +24,7 @@ import "./crd-details.scss";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import type { CustomResourceDefinition } from "../../../common/k8s-api/endpoints/crd.api";
|
import { CustomResourceDefinition } from "../../../common/k8s-api/endpoints/crd.api";
|
||||||
import { Badge } from "../badge";
|
import { Badge } from "../badge";
|
||||||
import { DrawerItem, DrawerTitle } from "../drawer";
|
import { DrawerItem, DrawerTitle } from "../drawer";
|
||||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
||||||
@ -32,6 +32,8 @@ import { Table, TableCell, TableHead, TableRow } from "../table";
|
|||||||
import { Input } from "../input";
|
import { Input } from "../input";
|
||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
import { MonacoEditor } from "../monaco-editor";
|
import { MonacoEditor } from "../monaco-editor";
|
||||||
|
import { UserStore } from "../../../common/user-store";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<CustomResourceDefinition> {
|
interface Props extends KubeObjectDetailsProps<CustomResourceDefinition> {
|
||||||
}
|
}
|
||||||
@ -41,7 +43,16 @@ export class CRDDetails extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: crd } = this.props;
|
const { object: crd } = this.props;
|
||||||
|
|
||||||
if (!crd) return null;
|
if (!crd) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(crd instanceof CustomResourceDefinition)) {
|
||||||
|
logger.error("[CRDDetails]: passed object that is not an instanceof CustomResourceDefinition", crd);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const { plural, singular, kind, listKind } = crd.getNames();
|
const { plural, singular, kind, listKind } = crd.getNames();
|
||||||
const printerColumns = crd.getPrinterColumns();
|
const printerColumns = crd.getPrinterColumns();
|
||||||
const validation = crd.getValidation();
|
const validation = crd.getValidation();
|
||||||
|
|||||||
@ -30,9 +30,10 @@ import { DrawerItem } from "../drawer";
|
|||||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
import { Input } from "../input";
|
import { Input } from "../input";
|
||||||
import type { AdditionalPrinterColumnsV1, CustomResourceDefinition } from "../../../common/k8s-api/endpoints/crd.api";
|
import { AdditionalPrinterColumnsV1, CustomResourceDefinition } from "../../../common/k8s-api/endpoints/crd.api";
|
||||||
import { parseJsonPath } from "../../utils/jsonPath";
|
import { parseJsonPath } from "../../utils/jsonPath";
|
||||||
import type { KubeObject, KubeObjectMetadata, KubeObjectStatus } from "../../../common/k8s-api/kube-object";
|
import { KubeObject, KubeObjectMetadata, KubeObjectStatus } from "../../../common/k8s-api/kube-object";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<KubeObject> {
|
interface Props extends KubeObjectDetailsProps<KubeObject> {
|
||||||
crd: CustomResourceDefinition;
|
crd: CustomResourceDefinition;
|
||||||
@ -101,6 +102,18 @@ export class CrdResourceDetails extends React.Component<Props> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!(object instanceof KubeObject)) {
|
||||||
|
logger.error("[CrdResourceDetails]: passed object that is not an instanceof KubeObject", object);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(crd instanceof CustomResourceDefinition)) {
|
||||||
|
logger.error("[CrdResourceDetails]: passed crd that is not an instanceof CustomResourceDefinition", crd);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const extraColumns = crd.getPrinterColumns();
|
const extraColumns = crd.getPrinterColumns();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -34,6 +34,7 @@ import type { EntitySettingsRouteParams } from "../../../common/routes";
|
|||||||
import { groupBy } from "lodash";
|
import { groupBy } from "lodash";
|
||||||
import { SettingLayout } from "../layout/setting-layout";
|
import { SettingLayout } from "../layout/setting-layout";
|
||||||
import { HotbarIcon } from "../hotbar/hotbar-icon";
|
import { HotbarIcon } from "../hotbar/hotbar-icon";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends RouteComponentProps<EntitySettingsRouteParams> {
|
interface Props extends RouteComponentProps<EntitySettingsRouteParams> {
|
||||||
}
|
}
|
||||||
@ -45,6 +46,17 @@ export class EntitySettings extends React.Component<Props> {
|
|||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
makeObservable(this);
|
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() {
|
get entityId() {
|
||||||
@ -61,18 +73,10 @@ export class EntitySettings extends React.Component<Props> {
|
|||||||
return EntitySettingRegistry.getInstance().getItemsForKind(this.entity.kind, this.entity.apiVersion, this.entity.metadata.source);
|
return EntitySettingRegistry.getInstance().getItemsForKind(this.entity.kind, this.entity.apiVersion, this.entity.metadata.source);
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
get activeSetting() {
|
||||||
const { hash } = navigation.location;
|
this.activeTab ||= this.menuItems[0]?.id;
|
||||||
|
|
||||||
if (hash) {
|
return this.menuItems.find((setting) => setting.id === this.activeTab);
|
||||||
const item = this.menuItems.find((item) => item.title === hash.slice(1));
|
|
||||||
|
|
||||||
if (item) {
|
|
||||||
this.activeTab = item.id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ensureActiveTab();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onTabChange = (tabId: string) => {
|
onTabChange = (tabId: string) => {
|
||||||
@ -122,33 +126,31 @@ export class EntitySettings extends React.Component<Props> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ensureActiveTab() {
|
|
||||||
if (!this.activeTab) {
|
|
||||||
this.activeTab = this.menuItems[0]?.id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (!this.entity) {
|
if (!this.entity) {
|
||||||
console.error("entity not found", this.entityId);
|
logger.error("[ENTITY-SETTINGS]: entity not found", this.entityId);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ensureActiveTab();
|
const { activeSetting } = this;
|
||||||
const activeSetting = this.menuItems.find((setting) => setting.id === this.activeTab);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingLayout
|
<SettingLayout
|
||||||
navigation={this.renderNavigation()}
|
navigation={this.renderNavigation()}
|
||||||
contentGaps={false}
|
contentGaps={false}
|
||||||
>
|
>
|
||||||
|
{
|
||||||
|
activeSetting && (
|
||||||
<section>
|
<section>
|
||||||
<h2 data-testid={`${activeSetting.id}-header`}>{activeSetting.title}</h2>
|
<h2 data-testid={`${activeSetting.id}-header`}>{activeSetting.title}</h2>
|
||||||
<section>
|
<section>
|
||||||
<activeSetting.components.View entity={this.entity} key={activeSetting.title} />
|
<activeSetting.components.View entity={this.entity} key={activeSetting.title} />
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
</SettingLayout>
|
</SettingLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,12 +27,13 @@ import { DrawerItem, DrawerTitle } from "../drawer";
|
|||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
||||||
import type { KubeEvent } from "../../../common/k8s-api/endpoints/events.api";
|
import { KubeEvent } from "../../../common/k8s-api/endpoints/events.api";
|
||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
import { Table, TableCell, TableHead, TableRow } from "../table";
|
import { Table, TableCell, TableHead, TableRow } from "../table";
|
||||||
import { LocaleDate } from "../locale-date";
|
import { LocaleDate } from "../locale-date";
|
||||||
import { getDetailsUrl } from "../kube-detail-params";
|
import { getDetailsUrl } from "../kube-detail-params";
|
||||||
import { apiManager } from "../../../common/k8s-api/api-manager";
|
import { apiManager } from "../../../common/k8s-api/api-manager";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<KubeEvent> {
|
interface Props extends KubeObjectDetailsProps<KubeEvent> {
|
||||||
}
|
}
|
||||||
@ -42,7 +43,16 @@ export class EventDetails extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: event } = this.props;
|
const { object: event } = this.props;
|
||||||
|
|
||||||
if (!event) return null;
|
if (!event) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(event instanceof KubeEvent)) {
|
||||||
|
logger.error("[EventDetails]: passed object that is not an instanceof KubeEvent", event);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const { message, reason, count, type, involvedObject } = event;
|
const { message, reason, count, type, involvedObject } = event;
|
||||||
const { kind, name, namespace, fieldPath } = involvedObject;
|
const { kind, name, namespace, fieldPath } = involvedObject;
|
||||||
|
|
||||||
|
|||||||
@ -23,11 +23,12 @@ import "./kube-event-details.scss";
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import type { KubeObject } from "../../../common/k8s-api/kube-object";
|
import { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||||
import { DrawerItem, DrawerTitle } from "../drawer";
|
import { DrawerItem, DrawerTitle } from "../drawer";
|
||||||
import { cssNames } from "../../utils";
|
import { cssNames } from "../../utils";
|
||||||
import { LocaleDate } from "../locale-date";
|
import { LocaleDate } from "../locale-date";
|
||||||
import { eventStore } from "./event.store";
|
import { eventStore } from "./event.store";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
export interface KubeEventDetailsProps {
|
export interface KubeEventDetailsProps {
|
||||||
object: KubeObject;
|
object: KubeObject;
|
||||||
@ -41,6 +42,17 @@ export class KubeEventDetails extends React.Component<KubeEventDetailsProps> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { object } = this.props;
|
const { object } = this.props;
|
||||||
|
|
||||||
|
if (!object) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(object instanceof KubeObject)) {
|
||||||
|
logger.error("[KubeEventDetails]: passed object that is not an instanceof KubeObject", object);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const events = eventStore.getEventsByObject(object);
|
const events = eventStore.getEventsByObject(object);
|
||||||
|
|
||||||
if (!events.length) {
|
if (!events.length) {
|
||||||
|
|||||||
@ -38,6 +38,7 @@ import { PodCharts, podMetricTabs } from "../+workloads-pods/pod-charts";
|
|||||||
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
||||||
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
||||||
import { getDetailsUrl } from "../kube-detail-params";
|
import { getDetailsUrl } from "../kube-detail-params";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<Namespace> {
|
interface Props extends KubeObjectDetailsProps<Namespace> {
|
||||||
}
|
}
|
||||||
@ -81,7 +82,16 @@ export class NamespaceDetails extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: namespace } = this.props;
|
const { object: namespace } = this.props;
|
||||||
|
|
||||||
if (!namespace) return null;
|
if (!namespace) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(namespace instanceof Namespace)) {
|
||||||
|
logger.error("[NamespaceDetails]: passed object that is not an instanceof Namespace", namespace);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const status = namespace.getStatus();
|
const status = namespace.getStatus();
|
||||||
const isMetricHidden = getActiveClusterEntity()?.isMetricHidden(ClusterMetricsResourceType.Namespace);
|
const isMetricHidden = getActiveClusterEntity()?.isMetricHidden(ClusterMetricsResourceType.Namespace);
|
||||||
|
|
||||||
|
|||||||
@ -25,9 +25,10 @@ import React from "react";
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { DrawerTitle } from "../drawer";
|
import { DrawerTitle } from "../drawer";
|
||||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
||||||
import type { Endpoint } from "../../../common/k8s-api/endpoints";
|
import { Endpoint } from "../../../common/k8s-api/endpoints";
|
||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
import { EndpointSubsetList } from "./endpoint-subset-list";
|
import { EndpointSubsetList } from "./endpoint-subset-list";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<Endpoint> {
|
interface Props extends KubeObjectDetailsProps<Endpoint> {
|
||||||
}
|
}
|
||||||
@ -37,7 +38,15 @@ export class EndpointDetails extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: endpoint } = this.props;
|
const { object: endpoint } = this.props;
|
||||||
|
|
||||||
if (!endpoint) return null;
|
if (!endpoint) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(endpoint instanceof Endpoint)) {
|
||||||
|
logger.error("[EndpointDetails]: passed object that is not an instanceof Endpoint", endpoint);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="EndpointDetails">
|
<div className="EndpointDetails">
|
||||||
|
|||||||
@ -25,7 +25,7 @@ import React from "react";
|
|||||||
import { disposeOnUnmount, observer } from "mobx-react";
|
import { disposeOnUnmount, observer } from "mobx-react";
|
||||||
import { makeObservable, observable, reaction } from "mobx";
|
import { makeObservable, observable, reaction } from "mobx";
|
||||||
import { DrawerItem, DrawerTitle } from "../drawer";
|
import { DrawerItem, DrawerTitle } from "../drawer";
|
||||||
import type { ILoadBalancerIngress, Ingress } from "../../../common/k8s-api/endpoints";
|
import { ILoadBalancerIngress, Ingress } from "../../../common/k8s-api/endpoints";
|
||||||
import { Table, TableCell, TableHead, TableRow } from "../table";
|
import { Table, TableCell, TableHead, TableRow } from "../table";
|
||||||
import { ResourceMetrics } from "../resource-metrics";
|
import { ResourceMetrics } from "../resource-metrics";
|
||||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
||||||
@ -35,6 +35,7 @@ import { getBackendServiceNamePort, getMetricsForIngress, IIngressMetrics } from
|
|||||||
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
||||||
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
||||||
import { boundMethod } from "../../utils";
|
import { boundMethod } from "../../utils";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<Ingress> {
|
interface Props extends KubeObjectDetailsProps<Ingress> {
|
||||||
}
|
}
|
||||||
@ -132,6 +133,12 @@ export class IngressDetails extends React.Component<Props> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!(ingress instanceof Ingress)) {
|
||||||
|
logger.error("[IngressDetails]: passed object that is not an instanceof Ingress", ingress);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const { spec, status } = ingress;
|
const { spec, status } = ingress;
|
||||||
const ingressPoints = status?.loadBalancer?.ingress;
|
const ingressPoints = status?.loadBalancer?.ingress;
|
||||||
const { metrics } = this;
|
const { metrics } = this;
|
||||||
|
|||||||
@ -24,12 +24,13 @@ import "./network-policy-details.scss";
|
|||||||
import get from "lodash/get";
|
import get from "lodash/get";
|
||||||
import React, { Fragment } from "react";
|
import React, { Fragment } from "react";
|
||||||
import { DrawerItem, DrawerTitle } from "../drawer";
|
import { DrawerItem, DrawerTitle } from "../drawer";
|
||||||
import type { IPolicyEgress, IPolicyIngress, IPolicyIpBlock, IPolicySelector, NetworkPolicy } from "../../../common/k8s-api/endpoints/network-policy.api";
|
import { IPolicyEgress, IPolicyIngress, IPolicyIpBlock, IPolicySelector, NetworkPolicy } from "../../../common/k8s-api/endpoints/network-policy.api";
|
||||||
import { Badge } from "../badge";
|
import { Badge } from "../badge";
|
||||||
import { SubTitle } from "../layout/sub-title";
|
import { SubTitle } from "../layout/sub-title";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<NetworkPolicy> {
|
interface Props extends KubeObjectDetailsProps<NetworkPolicy> {
|
||||||
}
|
}
|
||||||
@ -117,6 +118,13 @@ export class NetworkPolicyDetails extends React.Component<Props> {
|
|||||||
if (!policy) {
|
if (!policy) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!(policy instanceof NetworkPolicy)) {
|
||||||
|
logger.error("[NetworkPolicyDetails]: passed object that is not an instanceof NetworkPolicy", policy);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const { ingress, egress } = policy.spec;
|
const { ingress, egress } = policy.spec;
|
||||||
const selector = policy.getMatchLabels();
|
const selector = policy.getMatchLabels();
|
||||||
|
|
||||||
|
|||||||
@ -26,13 +26,14 @@ import { disposeOnUnmount, observer } from "mobx-react";
|
|||||||
import { DrawerItem, DrawerTitle } from "../drawer";
|
import { DrawerItem, DrawerTitle } from "../drawer";
|
||||||
import { Badge } from "../badge";
|
import { Badge } from "../badge";
|
||||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
||||||
import type { Service } from "../../../common/k8s-api/endpoints";
|
import { Service } from "../../../common/k8s-api/endpoints";
|
||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
import { ServicePortComponent } from "./service-port-component";
|
import { ServicePortComponent } from "./service-port-component";
|
||||||
import { endpointStore } from "../+network-endpoints/endpoints.store";
|
import { endpointStore } from "../+network-endpoints/endpoints.store";
|
||||||
import { ServiceDetailsEndpoint } from "./service-details-endpoint";
|
import { ServiceDetailsEndpoint } from "./service-details-endpoint";
|
||||||
import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api";
|
import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api";
|
||||||
import { portForwardStore } from "../../port-forward";
|
import { portForwardStore } from "../../port-forward";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<Service> {
|
interface Props extends KubeObjectDetailsProps<Service> {
|
||||||
}
|
}
|
||||||
@ -54,7 +55,16 @@ export class ServiceDetails extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: service } = this.props;
|
const { object: service } = this.props;
|
||||||
|
|
||||||
if (!service) return null;
|
if (!service) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(service instanceof Service)) {
|
||||||
|
logger.error("[ServiceDetails]: passed object that is not an instanceof Service", service);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const { spec } = service;
|
const { spec } = service;
|
||||||
const endpoint = endpointStore.getByName(service.getName(), service.getNs());
|
const endpoint = endpointStore.getByName(service.getName(), service.getNs());
|
||||||
|
|
||||||
|
|||||||
@ -40,6 +40,7 @@ import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
|||||||
import { NodeDetailsResources } from "./node-details-resources";
|
import { NodeDetailsResources } from "./node-details-resources";
|
||||||
import { DrawerTitle } from "../drawer/drawer-title";
|
import { DrawerTitle } from "../drawer/drawer-title";
|
||||||
import { boundMethod } from "../../utils";
|
import { boundMethod } from "../../utils";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<Node> {
|
interface Props extends KubeObjectDetailsProps<Node> {
|
||||||
}
|
}
|
||||||
@ -72,7 +73,16 @@ export class NodeDetails extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: node } = this.props;
|
const { object: node } = this.props;
|
||||||
|
|
||||||
if (!node) return null;
|
if (!node) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(node instanceof Node)) {
|
||||||
|
logger.error("[NodeDetails]: passed object that is not an instanceof Node", node);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const { status } = node;
|
const { status } = node;
|
||||||
const { nodeInfo, addresses } = status;
|
const { nodeInfo, addresses } = status;
|
||||||
const conditions = node.getActiveConditions();
|
const conditions = node.getActiveConditions();
|
||||||
|
|||||||
@ -25,22 +25,26 @@ import React from "react";
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { DrawerItem, DrawerTitle } from "../drawer";
|
import { DrawerItem, DrawerTitle } from "../drawer";
|
||||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
||||||
import type { PodSecurityPolicy } from "../../../common/k8s-api/endpoints";
|
import { PodSecurityPolicy } from "../../../common/k8s-api/endpoints";
|
||||||
import { Badge } from "../badge";
|
import { Badge } from "../badge";
|
||||||
import { Table, TableCell, TableHead, TableRow } from "../table";
|
import { Table, TableCell, TableHead, TableRow } from "../table";
|
||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<PodSecurityPolicy> {
|
interface Props extends KubeObjectDetailsProps<PodSecurityPolicy> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface RuleGroup {
|
||||||
|
rule: string;
|
||||||
|
ranges?: {
|
||||||
|
max: number;
|
||||||
|
min: number;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class PodSecurityPolicyDetails extends React.Component<Props> {
|
export class PodSecurityPolicyDetails extends React.Component<Props> {
|
||||||
renderRuleGroup(
|
renderRuleGroup( title: React.ReactNode, group: RuleGroup) {
|
||||||
title: React.ReactNode,
|
|
||||||
group: {
|
|
||||||
rule: string;
|
|
||||||
ranges?: { max: number; min: number }[];
|
|
||||||
}) {
|
|
||||||
if (!group) return null;
|
if (!group) return null;
|
||||||
const { rule, ranges } = group;
|
const { rule, ranges } = group;
|
||||||
|
|
||||||
@ -67,6 +71,13 @@ export class PodSecurityPolicyDetails extends React.Component<Props> {
|
|||||||
if (!psp) {
|
if (!psp) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!(psp instanceof PodSecurityPolicy)) {
|
||||||
|
logger.error("[PodSecurityPolicyDetails]: passed object that is not an instanceof PodSecurityPolicy", psp);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
allowedHostPaths, allowedCapabilities, allowedCSIDrivers, allowedFlexVolumes, allowedProcMountTypes,
|
allowedHostPaths, allowedCapabilities, allowedCSIDrivers, allowedFlexVolumes, allowedProcMountTypes,
|
||||||
allowedUnsafeSysctls, allowPrivilegeEscalation, defaultAddCapabilities, forbiddenSysctls, fsGroup,
|
allowedUnsafeSysctls, allowPrivilegeEscalation, defaultAddCapabilities, forbiddenSysctls, fsGroup,
|
||||||
@ -172,14 +183,14 @@ export class PodSecurityPolicyDetails extends React.Component<Props> {
|
|||||||
<TableCell>Path Prefix</TableCell>
|
<TableCell>Path Prefix</TableCell>
|
||||||
<TableCell>Read-only</TableCell>
|
<TableCell>Read-only</TableCell>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
{allowedHostPaths.map(({ pathPrefix, readOnly }, index) => {
|
{
|
||||||
return (
|
allowedHostPaths.map(({ pathPrefix, readOnly }, index) => (
|
||||||
<TableRow key={index}>
|
<TableRow key={index}>
|
||||||
<TableCell>{pathPrefix}</TableCell>
|
<TableCell>{pathPrefix}</TableCell>
|
||||||
<TableCell>{readOnly ? "Yes" : "No"}</TableCell>
|
<TableCell>{readOnly ? "Yes" : "No"}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
);
|
))
|
||||||
})}
|
}
|
||||||
</Table>
|
</Table>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -193,7 +204,7 @@ export class PodSecurityPolicyDetails extends React.Component<Props> {
|
|||||||
<>
|
<>
|
||||||
<DrawerTitle title="Runtime Class"/>
|
<DrawerTitle title="Runtime Class"/>
|
||||||
<DrawerItem name="Allowed Runtime Class Names">
|
<DrawerItem name="Allowed Runtime Class Names">
|
||||||
{(runtimeClass.allowedRuntimeClassNames || []).join(", ") || "-"}
|
{runtimeClass.allowedRuntimeClassNames?.join(", ") || "-"}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
<DrawerItem name="Default Runtime Class Name">
|
<DrawerItem name="Default Runtime Class Name">
|
||||||
{runtimeClass.defaultRuntimeClassName || "-"}
|
{runtimeClass.defaultRuntimeClassName || "-"}
|
||||||
|
|||||||
@ -27,11 +27,12 @@ import { DrawerItem, DrawerTitle } from "../drawer";
|
|||||||
import { Badge } from "../badge";
|
import { Badge } from "../badge";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
||||||
import type { StorageClass } from "../../../common/k8s-api/endpoints";
|
import { StorageClass } from "../../../common/k8s-api/endpoints";
|
||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
import { storageClassStore } from "./storage-class.store";
|
import { storageClassStore } from "./storage-class.store";
|
||||||
import { VolumeDetailsList } from "../+storage-volumes/volume-details-list";
|
import { VolumeDetailsList } from "../+storage-volumes/volume-details-list";
|
||||||
import { volumesStore } from "../+storage-volumes/volumes.store";
|
import { volumesStore } from "../+storage-volumes/volumes.store";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<StorageClass> {
|
interface Props extends KubeObjectDetailsProps<StorageClass> {
|
||||||
}
|
}
|
||||||
@ -44,9 +45,18 @@ export class StorageClassDetails extends React.Component<Props> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { object: storageClass } = this.props;
|
const { object: storageClass } = this.props;
|
||||||
const persistentVolumes = storageClassStore.getPersistentVolumes(storageClass);
|
|
||||||
|
|
||||||
if (!storageClass) return null;
|
if (!storageClass) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(storageClass instanceof StorageClass)) {
|
||||||
|
logger.error("[StorageClassDetails]: passed object that is not an instanceof StorageClass", storageClass);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const persistentVolumes = storageClassStore.getPersistentVolumes(storageClass);
|
||||||
const { provisioner, parameters, mountOptions } = storageClass;
|
const { provisioner, parameters, mountOptions } = storageClass;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -37,6 +37,7 @@ import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
|||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
import { getDetailsUrl } from "../kube-detail-params";
|
import { getDetailsUrl } from "../kube-detail-params";
|
||||||
import { boundMethod } from "../../utils";
|
import { boundMethod } from "../../utils";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<PersistentVolumeClaim> {
|
interface Props extends KubeObjectDetailsProps<PersistentVolumeClaim> {
|
||||||
}
|
}
|
||||||
@ -68,6 +69,13 @@ export class PersistentVolumeClaimDetails extends React.Component<Props> {
|
|||||||
if (!volumeClaim) {
|
if (!volumeClaim) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!(volumeClaim instanceof PersistentVolumeClaim)) {
|
||||||
|
logger.error("[PersistentVolumeClaimDetails]: passed object that is not an instanceof PersistentVolumeClaim", volumeClaim);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const { storageClassName, accessModes } = volumeClaim.spec;
|
const { storageClassName, accessModes } = volumeClaim.spec;
|
||||||
const { metrics } = this;
|
const { metrics } = this;
|
||||||
const pods = volumeClaim.getPods(podsStore.items);
|
const pods = volumeClaim.getPods(podsStore.items);
|
||||||
|
|||||||
@ -31,6 +31,7 @@ import { PersistentVolume, pvcApi } from "../../../common/k8s-api/endpoints";
|
|||||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
import { getDetailsUrl } from "../kube-detail-params";
|
import { getDetailsUrl } from "../kube-detail-params";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<PersistentVolume> {
|
interface Props extends KubeObjectDetailsProps<PersistentVolume> {
|
||||||
}
|
}
|
||||||
@ -43,6 +44,13 @@ export class PersistentVolumeDetails extends React.Component<Props> {
|
|||||||
if (!volume) {
|
if (!volume) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!(volume instanceof PersistentVolume)) {
|
||||||
|
logger.error("[PersistentVolumeDetails]: passed object that is not an instanceof PersistentVolume", volume);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const { accessModes, capacity, persistentVolumeReclaimPolicy, storageClassName, claimRef, flexVolume, mountOptions, nfs } = volume.spec;
|
const { accessModes, capacity, persistentVolumeReclaimPolicy, storageClassName, claimRef, flexVolume, mountOptions, nfs } = volume.spec;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -31,8 +31,9 @@ import { Link } from "react-router-dom";
|
|||||||
import { cronJobStore } from "./cronjob.store";
|
import { cronJobStore } from "./cronjob.store";
|
||||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
||||||
import { getDetailsUrl } from "../kube-detail-params";
|
import { getDetailsUrl } from "../kube-detail-params";
|
||||||
import type { CronJob, Job } from "../../../common/k8s-api/endpoints";
|
import { CronJob, Job } from "../../../common/k8s-api/endpoints";
|
||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<CronJob> {
|
interface Props extends KubeObjectDetailsProps<CronJob> {
|
||||||
}
|
}
|
||||||
@ -46,18 +47,27 @@ export class CronJobDetails extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: cronJob } = this.props;
|
const { object: cronJob } = this.props;
|
||||||
|
|
||||||
if (!cronJob) return null;
|
if (!cronJob) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(cronJob instanceof CronJob)) {
|
||||||
|
logger.error("[CronJobDetails]: passed object that is not an instanceof CronJob", cronJob);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const childJobs = jobStore.getJobsByOwner(cronJob);
|
const childJobs = jobStore.getJobsByOwner(cronJob);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="CronJobDetails">
|
<div className="CronJobDetails">
|
||||||
<KubeObjectMeta object={cronJob}/>
|
<KubeObjectMeta object={cronJob}/>
|
||||||
<DrawerItem name="Schedule">
|
<DrawerItem name="Schedule">
|
||||||
{cronJob.isNeverRun() ? (
|
{
|
||||||
<>
|
cronJob.isNeverRun()
|
||||||
never ({cronJob.getSchedule()})
|
? `never (${cronJob.getSchedule()})`
|
||||||
</>
|
: cronJob.getSchedule()
|
||||||
) : cronJob.getSchedule()}
|
}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
<DrawerItem name="Active">
|
<DrawerItem name="Active">
|
||||||
{cronJobStore.getActiveJobsNum(cronJob)}
|
{cronJobStore.getActiveJobsNum(cronJob)}
|
||||||
|
|||||||
@ -40,6 +40,7 @@ import { KubeObjectMeta } from "../kube-object-meta";
|
|||||||
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
||||||
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
||||||
import { boundMethod } from "../../utils";
|
import { boundMethod } from "../../utils";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<DaemonSet> {
|
interface Props extends KubeObjectDetailsProps<DaemonSet> {
|
||||||
}
|
}
|
||||||
@ -72,7 +73,16 @@ export class DaemonSetDetails extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: daemonSet } = this.props;
|
const { object: daemonSet } = this.props;
|
||||||
|
|
||||||
if (!daemonSet) return null;
|
if (!daemonSet) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(daemonSet instanceof DaemonSet)) {
|
||||||
|
logger.error("[DaemonSetDetails]: passed object that is not an instanceof DaemonSet", daemonSet);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const { spec } = daemonSet;
|
const { spec } = daemonSet;
|
||||||
const selectors = daemonSet.getSelectors();
|
const selectors = daemonSet.getSelectors();
|
||||||
const images = daemonSet.getImages();
|
const images = daemonSet.getImages();
|
||||||
|
|||||||
@ -42,6 +42,7 @@ import { DeploymentReplicaSets } from "./deployment-replicasets";
|
|||||||
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
||||||
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
||||||
import { boundMethod } from "../../utils";
|
import { boundMethod } from "../../utils";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<Deployment> {
|
interface Props extends KubeObjectDetailsProps<Deployment> {
|
||||||
}
|
}
|
||||||
@ -75,7 +76,16 @@ export class DeploymentDetails extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: deployment } = this.props;
|
const { object: deployment } = this.props;
|
||||||
|
|
||||||
if (!deployment) return null;
|
if (!deployment) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(deployment instanceof Deployment)) {
|
||||||
|
logger.error("[DeploymentDetails]: passed object that is not an instanceof Deployment", deployment);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const { status, spec } = deployment;
|
const { status, spec } = deployment;
|
||||||
const nodeSelector = deployment.getNodeSelectors();
|
const nodeSelector = deployment.getNodeSelectors();
|
||||||
const selectors = deployment.getSelectors();
|
const selectors = deployment.getSelectors();
|
||||||
|
|||||||
@ -44,6 +44,7 @@ import { ResourceMetrics } from "../resource-metrics";
|
|||||||
import { boundMethod } from "autobind-decorator";
|
import { boundMethod } from "autobind-decorator";
|
||||||
import { getDetailsUrl } from "../kube-detail-params";
|
import { getDetailsUrl } from "../kube-detail-params";
|
||||||
import { apiManager } from "../../../common/k8s-api/api-manager";
|
import { apiManager } from "../../../common/k8s-api/api-manager";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<Job> {
|
interface Props extends KubeObjectDetailsProps<Job> {
|
||||||
}
|
}
|
||||||
@ -71,7 +72,16 @@ export class JobDetails extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: job } = this.props;
|
const { object: job } = this.props;
|
||||||
|
|
||||||
if (!job) return null;
|
if (!job) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(job instanceof Job)) {
|
||||||
|
logger.error("[JobDetails]: passed object that is not an instanceof Job", job);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const selectors = job.getSelectors();
|
const selectors = job.getSelectors();
|
||||||
const nodeSelector = job.getNodeSelectors();
|
const nodeSelector = job.getNodeSelectors();
|
||||||
const images = job.getImages();
|
const images = job.getImages();
|
||||||
|
|||||||
@ -43,6 +43,7 @@ import { KubeObjectMeta } from "../kube-object-meta";
|
|||||||
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
||||||
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
||||||
import { getDetailsUrl } from "../kube-detail-params";
|
import { getDetailsUrl } from "../kube-detail-params";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<Pod> {
|
interface Props extends KubeObjectDetailsProps<Pod> {
|
||||||
}
|
}
|
||||||
@ -77,7 +78,16 @@ export class PodDetails extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: pod } = this.props;
|
const { object: pod } = this.props;
|
||||||
|
|
||||||
if (!pod) return null;
|
if (!pod) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(pod instanceof Pod)) {
|
||||||
|
logger.error("[PodDetails]: passed object that is not an instanceof Pod", pod);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const { status, spec } = pod;
|
const { status, spec } = pod;
|
||||||
const { conditions, podIP } = status;
|
const { conditions, podIP } = status;
|
||||||
const podIPs = pod.getIPs();
|
const podIPs = pod.getIPs();
|
||||||
|
|||||||
@ -39,6 +39,7 @@ import { KubeObjectMeta } from "../kube-object-meta";
|
|||||||
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
||||||
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
||||||
import { boundMethod } from "../../utils";
|
import { boundMethod } from "../../utils";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<ReplicaSet> {
|
interface Props extends KubeObjectDetailsProps<ReplicaSet> {
|
||||||
}
|
}
|
||||||
@ -71,7 +72,16 @@ export class ReplicaSetDetails extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: replicaSet } = this.props;
|
const { object: replicaSet } = this.props;
|
||||||
|
|
||||||
if (!replicaSet) return null;
|
if (!replicaSet) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(replicaSet instanceof ReplicaSet)) {
|
||||||
|
logger.error("[ReplicaSetDetails]: passed object that is not an instanceof ReplicaSet", replicaSet);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const { metrics } = this;
|
const { metrics } = this;
|
||||||
const { status } = replicaSet;
|
const { status } = replicaSet;
|
||||||
const { availableReplicas, replicas } = status;
|
const { availableReplicas, replicas } = status;
|
||||||
|
|||||||
@ -40,6 +40,7 @@ import { KubeObjectMeta } from "../kube-object-meta";
|
|||||||
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
||||||
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
||||||
import { boundMethod } from "../../utils";
|
import { boundMethod } from "../../utils";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<StatefulSet> {
|
interface Props extends KubeObjectDetailsProps<StatefulSet> {
|
||||||
}
|
}
|
||||||
@ -72,7 +73,16 @@ export class StatefulSetDetails extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: statefulSet } = this.props;
|
const { object: statefulSet } = this.props;
|
||||||
|
|
||||||
if (!statefulSet) return null;
|
if (!statefulSet) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(statefulSet instanceof StatefulSet)) {
|
||||||
|
logger.error("[StatefulSetDetails]: passed object that is not an instanceof StatefulSet", statefulSet);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const images = statefulSet.getImages();
|
const images = statefulSet.getImages();
|
||||||
const selectors = statefulSet.getSelectors();
|
const selectors = statefulSet.getSelectors();
|
||||||
const nodeSelector = statefulSet.getNodeSelectors();
|
const nodeSelector = statefulSet.getNodeSelectors();
|
||||||
|
|||||||
@ -90,7 +90,7 @@ export class ClusterStatus extends React.Component<Props> {
|
|||||||
params: {
|
params: {
|
||||||
entityId: this.props.clusterId,
|
entityId: this.props.clusterId,
|
||||||
},
|
},
|
||||||
fragment: "Proxy",
|
fragment: "proxy",
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -31,6 +31,7 @@ import { cssNames, IClassName } from "../../utils";
|
|||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { HotbarIcon } from "./hotbar-icon";
|
import { HotbarIcon } from "./hotbar-icon";
|
||||||
import { HotbarStore } from "../../../common/hotbar-store";
|
import { HotbarStore } from "../../../common/hotbar-store";
|
||||||
|
import { LensKubernetesClusterStatus } from "../../../common/catalog-entities/kubernetes-cluster";
|
||||||
|
|
||||||
interface Props extends DOMAttributes<HTMLElement> {
|
interface Props extends DOMAttributes<HTMLElement> {
|
||||||
entity: CatalogEntity;
|
entity: CatalogEntity;
|
||||||
@ -78,7 +79,7 @@ export class HotbarEntityIcon extends React.Component<Props> {
|
|||||||
return null;
|
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 <div className={className} />;
|
return <div className={className} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,13 +20,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import type { KubeMetaField, KubeObject } from "../../../common/k8s-api/kube-object";
|
import { KubeMetaField, KubeObject } from "../../../common/k8s-api/kube-object";
|
||||||
import { DrawerItem, DrawerItemLabels } from "../drawer";
|
import { DrawerItem, DrawerItemLabels } from "../drawer";
|
||||||
import { apiManager } from "../../../common/k8s-api/api-manager";
|
import { apiManager } from "../../../common/k8s-api/api-manager";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
||||||
import { LocaleDate } from "../locale-date";
|
import { LocaleDate } from "../locale-date";
|
||||||
import { getDetailsUrl } from "../kube-detail-params";
|
import { getDetailsUrl } from "../kube-detail-params";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
export interface KubeObjectMetaProps {
|
export interface KubeObjectMetaProps {
|
||||||
object: KubeObject;
|
object: KubeObject;
|
||||||
@ -46,6 +47,17 @@ export class KubeObjectMeta extends React.Component<KubeObjectMetaProps> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { object } = this.props;
|
const { object } = this.props;
|
||||||
|
|
||||||
|
if (!object) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(object instanceof KubeObject)) {
|
||||||
|
logger.error("[KubeObjectMeta]: passed object that is not an instanceof KubeObject", object);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getNs, getLabels, getResourceVersion, selfLink, getAnnotations,
|
getNs, getLabels, getResourceVersion, selfLink, getAnnotations,
|
||||||
getFinalizers, getId, getAge, getName, metadata: { creationTimestamp },
|
getFinalizers, getId, getAge, getName, metadata: { creationTimestamp },
|
||||||
|
|||||||
@ -23,7 +23,7 @@ import "./setting-layout.scss";
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { boundMethod, cssNames, IClassName } from "../../utils";
|
import { cssNames, IClassName } from "../../utils";
|
||||||
import { navigation } from "../../navigation";
|
import { navigation } from "../../navigation";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
|
|
||||||
@ -36,17 +36,10 @@ export interface SettingLayoutProps extends React.DOMAttributes<any> {
|
|||||||
back?: (evt: React.MouseEvent | KeyboardEvent) => void;
|
back?: (evt: React.MouseEvent | KeyboardEvent) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrollToAnchor() {
|
|
||||||
const { hash } = window.location;
|
|
||||||
|
|
||||||
if (hash) {
|
|
||||||
document.querySelector(`${hash}`).scrollIntoView();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultProps: Partial<SettingLayoutProps> = {
|
const defaultProps: Partial<SettingLayoutProps> = {
|
||||||
provideBackButtonNavigation: true,
|
provideBackButtonNavigation: true,
|
||||||
contentGaps: true,
|
contentGaps: true,
|
||||||
|
back: () => navigation.goBack(),
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -56,19 +49,14 @@ const defaultProps: Partial<SettingLayoutProps> = {
|
|||||||
export class SettingLayout extends React.Component<SettingLayoutProps> {
|
export class SettingLayout extends React.Component<SettingLayoutProps> {
|
||||||
static defaultProps = defaultProps as object;
|
static defaultProps = defaultProps as object;
|
||||||
|
|
||||||
@boundMethod
|
|
||||||
back(evt?: React.MouseEvent | KeyboardEvent) {
|
|
||||||
if (this.props.back) {
|
|
||||||
this.props.back(evt);
|
|
||||||
} else {
|
|
||||||
navigation.goBack();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
window.addEventListener("keydown", this.onEscapeKey);
|
const { hash } = window.location;
|
||||||
|
|
||||||
scrollToAnchor();
|
if (hash) {
|
||||||
|
document.querySelector(hash)?.scrollIntoView();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("keydown", this.onEscapeKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
@ -82,7 +70,7 @@ export class SettingLayout extends React.Component<SettingLayoutProps> {
|
|||||||
|
|
||||||
if (evt.code === "Escape") {
|
if (evt.code === "Escape") {
|
||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
this.back(evt);
|
this.props.back(evt);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -107,17 +95,18 @@ export class SettingLayout extends React.Component<SettingLayoutProps> {
|
|||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
<div className="toolsRegion">
|
<div className="toolsRegion">
|
||||||
{ this.props.provideBackButtonNavigation && (
|
{
|
||||||
|
this.props.provideBackButtonNavigation && (
|
||||||
<div className="fixedTools">
|
<div className="fixedTools">
|
||||||
<div className="closeBtn" role="button" aria-label="Close" onClick={this.back}>
|
<div className="closeBtn" role="button" aria-label="Close" onClick={back}>
|
||||||
<Icon material="close"/>
|
<Icon material="close" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="esc" aria-hidden="true">
|
<div className="esc" aria-hidden="true">
|
||||||
ESC
|
ESC
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -193,7 +193,7 @@ export class Sidebar extends React.Component<Props> {
|
|||||||
source={metadata.source}
|
source={metadata.source}
|
||||||
src={spec.icon?.src}
|
src={spec.icon?.src}
|
||||||
className="mr-5"
|
className="mr-5"
|
||||||
onClick={() => navigate(routes.clusterURL())}
|
onClick={() => navigate("/")}
|
||||||
/>
|
/>
|
||||||
<div className={styles.clusterName}>
|
<div className={styles.clusterName}>
|
||||||
{metadata.name}
|
{metadata.name}
|
||||||
|
|||||||
@ -77,16 +77,15 @@ function UpdateAvailableHandler(event: IpcRendererEvent, ...[backchannel, update
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const listNamespacesForbiddenHandlerDisplayedAt = new Map<string, number>();
|
const notificationLastDisplayedAt = new Map<string, number>();
|
||||||
const intervalBetweenNotifications = 1000 * 60; // 60s
|
const intervalBetweenNotifications = 1000 * 60; // 60s
|
||||||
|
|
||||||
function ListNamespacesForbiddenHandler(event: IpcRendererEvent, ...[clusterId]: ListNamespaceForbiddenArgs): void {
|
function ListNamespacesForbiddenHandler(event: IpcRendererEvent, ...[clusterId]: ListNamespaceForbiddenArgs): void {
|
||||||
const lastDisplayedAt = listNamespacesForbiddenHandlerDisplayedAt.get(clusterId);
|
const lastDisplayedAt = notificationLastDisplayedAt.get(clusterId);
|
||||||
const wasDisplayed = Boolean(lastDisplayedAt);
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
||||||
if (!wasDisplayed || (now - lastDisplayedAt) > intervalBetweenNotifications) {
|
if (!notificationLastDisplayedAt.has(clusterId) || (now - lastDisplayedAt) > intervalBetweenNotifications) {
|
||||||
listNamespacesForbiddenHandlerDisplayedAt.set(clusterId, now);
|
notificationLastDisplayedAt.set(clusterId, now);
|
||||||
} else {
|
} else {
|
||||||
// don't bother the user too often
|
// don't bother the user too often
|
||||||
return;
|
return;
|
||||||
@ -94,21 +93,39 @@ function ListNamespacesForbiddenHandler(event: IpcRendererEvent, ...[clusterId]:
|
|||||||
|
|
||||||
const notificationId = `list-namespaces-forbidden:${clusterId}`;
|
const notificationId = `list-namespaces-forbidden:${clusterId}`;
|
||||||
|
|
||||||
|
if (notificationsStore.getById(notificationId)) {
|
||||||
|
// notification is still visible
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Notifications.info(
|
Notifications.info(
|
||||||
(
|
(
|
||||||
<div className="flex column gaps">
|
<div className="flex column gaps">
|
||||||
<b>Add Accessible Namespaces</b>
|
<b>Add Accessible Namespaces</b>
|
||||||
<p>Cluster <b>{ClusterStore.getInstance().getById(clusterId).name}</b> does not have permissions to list namespaces. Please add the namespaces you have access to.</p>
|
<p>
|
||||||
|
Cluster <b>{ClusterStore.getInstance().getById(clusterId).name}</b> does not have permissions to list namespaces.{" "}
|
||||||
|
Please add the namespaces you have access to.
|
||||||
|
</p>
|
||||||
<div className="flex gaps row align-left box grow">
|
<div className="flex gaps row align-left box grow">
|
||||||
<Button active outlined label="Go to Accessible Namespaces Settings" onClick={() => {
|
<Button
|
||||||
navigate(entitySettingsURL({ params: { entityId: clusterId }, fragment: "accessible-namespaces" }));
|
active
|
||||||
|
outlined
|
||||||
|
label="Go to Accessible Namespaces Settings"
|
||||||
|
onClick={() => {
|
||||||
|
navigate(entitySettingsURL({ params: { entityId: clusterId }, fragment: "namespaces" }));
|
||||||
notificationsStore.remove(notificationId);
|
notificationsStore.remove(notificationId);
|
||||||
}} />
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
id: notificationId,
|
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()),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,149 +19,109 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { reaction } from "mobx";
|
import { observable, reaction } from "mobx";
|
||||||
import { StorageAdapter, StorageHelper } from "../storageHelper";
|
import { StorageHelper } from "../storageHelper";
|
||||||
import { delay } from "../../../common/utils/delay";
|
import { delay } from "../../../common/utils/delay";
|
||||||
|
|
||||||
describe("renderer/utils/StorageHelper", () => {
|
type StorageModel = {
|
||||||
describe("window.localStorage might be used as StorageAdapter", () => {
|
[prop: string]: any /*json-serializable*/;
|
||||||
type StorageModel = string;
|
message?: string;
|
||||||
|
description?: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("renderer/utils/StorageHelper", () => {
|
||||||
|
describe("Using custom StorageAdapter", () => {
|
||||||
const storageKey = "ui-settings";
|
const storageKey = "ui-settings";
|
||||||
|
const remoteStorageMock = observable.map<string, StorageModel>();
|
||||||
let storageHelper: StorageHelper<StorageModel>;
|
let storageHelper: StorageHelper<StorageModel>;
|
||||||
|
let storageHelperAsync: StorageHelper<StorageModel>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
localStorage.clear();
|
remoteStorageMock.set(storageKey, {
|
||||||
|
message: "saved-before", // pretending as previously saved data
|
||||||
|
});
|
||||||
|
|
||||||
storageHelper = new StorageHelper<StorageModel>(storageKey, {
|
storageHelper = new StorageHelper<StorageModel>(storageKey, {
|
||||||
autoInit: false,
|
autoInit: false,
|
||||||
storage: localStorage,
|
defaultValue: {
|
||||||
defaultValue: "test",
|
message: "blabla",
|
||||||
});
|
description: "default"
|
||||||
});
|
},
|
||||||
|
storage: {
|
||||||
it("initialized with default value", async () => {
|
getItem(key: string): StorageModel {
|
||||||
localStorage.setItem(storageKey, "saved"); // pretending it was saved previously
|
return Object.assign(
|
||||||
|
storageHelper.defaultValue,
|
||||||
expect(storageHelper.key).toBe(storageKey);
|
remoteStorageMock.get(key),
|
||||||
expect(storageHelper.defaultValue).toBe("test");
|
);
|
||||||
expect(storageHelper.get()).toBe("test");
|
},
|
||||||
|
setItem(key: string, value: StorageModel) {
|
||||||
storageHelper.init();
|
remoteStorageMock.set(key, value);
|
||||||
|
},
|
||||||
expect(storageHelper.key).toBe(storageKey);
|
removeItem(key: string) {
|
||||||
expect(storageHelper.defaultValue).toBe("test");
|
remoteStorageMock.delete(key);
|
||||||
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<string, any> = {};
|
|
||||||
let storageHelper: StorageHelper<SettingsStorageModel>;
|
|
||||||
let storageHelperAsync: StorageHelper<SettingsStorageModel>;
|
|
||||||
let storageAdapter: StorageAdapter<SettingsStorageModel>;
|
|
||||||
|
|
||||||
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,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
storageHelperAsync = new StorageHelper(storageKey, {
|
storageHelperAsync = new StorageHelper(storageKey, {
|
||||||
autoInit: false,
|
autoInit: false,
|
||||||
defaultValue: storageHelperDefaultValue,
|
defaultValue: storageHelper.defaultValue,
|
||||||
storage: {
|
storage: {
|
||||||
...storageAdapter,
|
...storageHelper.storage,
|
||||||
async getItem(key: string): Promise<SettingsStorageModel> {
|
async getItem(key: string): Promise<StorageModel> {
|
||||||
await delay(500); // fake loading timeout
|
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", () => {
|
it("initialized with default value", async () => {
|
||||||
expect(storageHelper.get()).toEqual(storageHelperDefaultValue);
|
|
||||||
storageHelper.init();
|
storageHelper.init();
|
||||||
|
expect(storageHelper.key).toBe(storageKey);
|
||||||
expect(storageHelper.get().message).toBe("saved-before");
|
expect(storageHelper.get()).toEqual(storageHelper.defaultValue);
|
||||||
expect(storageAdapter.getItem).toHaveBeenCalledWith(storageHelper.key);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("async loading from storage supported too", async () => {
|
it("async loading from storage supported too", async () => {
|
||||||
expect(storageHelperAsync.initialized).toBeFalsy();
|
expect(storageHelperAsync.initialized).toBeFalsy();
|
||||||
storageHelperAsync.init();
|
storageHelperAsync.init();
|
||||||
await delay(300);
|
await delay(300);
|
||||||
expect(storageHelperAsync.get()).toEqual(storageHelperDefaultValue);
|
expect(storageHelperAsync.get()).toEqual(storageHelper.defaultValue);
|
||||||
await delay(200);
|
await delay(200);
|
||||||
expect(storageHelperAsync.get().message).toBe("saved-before");
|
expect(storageHelperAsync.get().message).toBe("saved-before");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("set() fully replaces data in storage", () => {
|
it("set() fully replaces data in storage", () => {
|
||||||
storageHelper.init();
|
storageHelper.init();
|
||||||
storageHelper.set({ message: "test2" });
|
storageHelper.set({ message: "msg" });
|
||||||
expect(storageHelper.get().message).toBe("test2");
|
storageHelper.get().description = "desc";
|
||||||
expect(storageMock[storageKey]).toEqual({ message: "test2" });
|
expect(storageHelper.get().message).toBe("msg");
|
||||||
expect(storageAdapter.setItem).toHaveBeenCalledWith(storageHelper.key, { message: "test2" });
|
expect(storageHelper.get().description).toBe("desc");
|
||||||
|
expect(remoteStorageMock.get(storageKey)).toEqual({
|
||||||
|
message: "msg",
|
||||||
|
description: "desc",
|
||||||
|
} as StorageModel);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("merge() does partial data tree updates", () => {
|
it("merge() does partial data tree updates", () => {
|
||||||
storageHelper.init();
|
storageHelper.init();
|
||||||
storageHelper.merge({ message: "updated" });
|
storageHelper.merge({ message: "updated" });
|
||||||
|
|
||||||
expect(storageHelper.get()).toEqual({ ...storageHelperDefaultValue, message: "updated" });
|
expect(storageHelper.get()).toEqual({ ...storageHelper.defaultValue, message: "updated" });
|
||||||
expect(storageAdapter.setItem).toHaveBeenCalledWith(storageHelper.key, { ...storageHelperDefaultValue, message: "updated" });
|
expect(remoteStorageMock.get(storageKey)).toEqual({ ...storageHelper.defaultValue, message: "updated" });
|
||||||
|
|
||||||
|
// `draft` modified inside, returning `void` is expected
|
||||||
storageHelper.merge(draft => {
|
storageHelper.merge(draft => {
|
||||||
draft.message = "updated2";
|
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 => ({
|
storageHelper.merge(draft => ({
|
||||||
message: draft.message.replace("2", "3")
|
message: draft.message.replace("2", "3")
|
||||||
}));
|
}));
|
||||||
expect(storageHelper.get()).toEqual({ ...storageHelperDefaultValue, message: "updated3" });
|
expect(storageHelper.get()).toEqual({ ...storageHelper.defaultValue, message: "updated3" });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -20,10 +20,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Helper for working with storages (e.g. window.localStorage, NodeJS/file-system, etc.)
|
// Helper for working with storages (e.g. window.localStorage, NodeJS/file-system, etc.)
|
||||||
|
|
||||||
import { action, comparer, IReactionDisposer, IReactionOptions, makeObservable, observable, reaction, toJS, when, } from "mobx";
|
import { action, comparer, IReactionDisposer, IReactionOptions, makeObservable, observable, reaction, toJS, when, } from "mobx";
|
||||||
import produce, { Draft } from "immer";
|
import produce, { Draft, isDraft } from "immer";
|
||||||
import { isEqual, isFunction, isPlainObject } from "lodash";
|
import { isEqual } from "lodash";
|
||||||
import logger from "../../main/logger";
|
import logger from "../../main/logger";
|
||||||
import { disposer } from "../../common/utils";
|
import { disposer } from "../../common/utils";
|
||||||
|
|
||||||
@ -157,15 +156,22 @@ export class StorageHelper<T> {
|
|||||||
|
|
||||||
@action
|
@action
|
||||||
merge(value: Partial<T> | ((draft: Draft<T>) => Partial<T> | void)) {
|
merge(value: Partial<T> | ((draft: Draft<T>) => Partial<T> | void)) {
|
||||||
const nextValue = produce(this.toJSON(), (state: Draft<T>) => {
|
const nextValue = produce<T>(this.toJSON(), (draft: Draft<T>) => {
|
||||||
const newValue = isFunction(value) ? value(state) : value;
|
|
||||||
|
|
||||||
return isPlainObject(newValue)
|
if (typeof value == "function") {
|
||||||
? Object.assign(state, newValue) // partial updates for returned plain objects
|
const newValue = value(draft);
|
||||||
: newValue;
|
|
||||||
|
// 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 {
|
toJSON(): T {
|
||||||
|
|||||||
14
templates/create-resource/Pod.yaml
Normal file
14
templates/create-resource/Pod.yaml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: static-web
|
||||||
|
labels:
|
||||||
|
role: myrole
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: web
|
||||||
|
image: nginx
|
||||||
|
ports:
|
||||||
|
- name: web
|
||||||
|
containerPort: 80
|
||||||
|
protocol: TCP
|
||||||
32
types/command-exists.d.ts
vendored
32
types/command-exists.d.ts
vendored
@ -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<string>;
|
|
||||||
declare function commandExists(
|
|
||||||
commandName: string,
|
|
||||||
cb: (error: null, exists: boolean) => void
|
|
||||||
): void;
|
|
||||||
|
|
||||||
declare namespace commandExists {
|
|
||||||
function sync(commandName: string): boolean;
|
|
||||||
}
|
|
||||||
351
yarn.lock
351
yarn.lock
@ -493,13 +493,6 @@
|
|||||||
minimatch "^3.0.4"
|
minimatch "^3.0.4"
|
||||||
strip-json-comments "^3.1.1"
|
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":
|
"@hapi/b64@5.x.x":
|
||||||
version "5.0.0"
|
version "5.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@hapi/b64/-/b64-5.0.0.tgz#b8210cbd72f4774985e78569b77e97498d24277d"
|
resolved "https://registry.yarnpkg.com/@hapi/b64/-/b64-5.0.0.tgz#b8210cbd72f4774985e78569b77e97498d24277d"
|
||||||
@ -1218,7 +1211,21 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
defer-to-connect "^2.0.0"
|
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"
|
version "7.30.3"
|
||||||
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.30.3.tgz#779ea9bbb92d63302461800a388a5a890ac22519"
|
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.30.3.tgz#779ea9bbb92d63302461800a388a5a890ac22519"
|
||||||
integrity sha512-7JhIg2MW6WPwyikH2iL3o7z+FTVgSOd2jqCwTAHqK7Qal2gRRYiUQyURAxtbK9VXm/UTyG9bRihv8C5Tznr2zw==
|
integrity sha512-7JhIg2MW6WPwyikH2iL3o7z+FTVgSOd2jqCwTAHqK7Qal2gRRYiUQyURAxtbK9VXm/UTyG9bRihv8C5Tznr2zw==
|
||||||
@ -1232,20 +1239,6 @@
|
|||||||
lz-string "^1.4.4"
|
lz-string "^1.4.4"
|
||||||
pretty-format "^26.6.2"
|
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":
|
"@testing-library/jest-dom@^5.14.1":
|
||||||
version "5.14.1"
|
version "5.14.1"
|
||||||
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.14.1.tgz#8501e16f1e55a55d675fe73eecee32cdaddb9766"
|
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.14.1.tgz#8501e16f1e55a55d675fe73eecee32cdaddb9766"
|
||||||
@ -1740,10 +1733,10 @@
|
|||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
form-data "^3.0.0"
|
form-data "^3.0.0"
|
||||||
|
|
||||||
"@types/node@*", "@types/node@14.17.14", "@types/node@^14.6.2":
|
"@types/node@*", "@types/node@14.17.26", "@types/node@^14.6.2":
|
||||||
version "14.17.14"
|
version "14.17.26"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.14.tgz#6fda9785b41570eb628bac27be4b602769a3f938"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.26.tgz#47a53c7e7804490155a4646d60c8e194816d073c"
|
||||||
integrity sha512-rsAj2u8Xkqfc332iXV12SqIsjVi07H479bOP4q94NAcjzmAvapumEhuVIt53koEf7JFrpjgNKjBga5Pnn/GL8A==
|
integrity sha512-eSTNkK/nfmnC7IKpOJZixDgG0W2/eHz1qyFN7o/rwwwIHsVRp+G9nbh4BrQ77kbQ2zPu286AQRxkuRLPcR3gXw==
|
||||||
|
|
||||||
"@types/node@^10.12.0":
|
"@types/node@^10.12.0":
|
||||||
version "10.17.24"
|
version "10.17.24"
|
||||||
@ -2085,10 +2078,10 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/underscore/-/underscore-1.10.0.tgz#5cb0dff2a5f616fc8e0c61b482bf01fa20a03cec"
|
resolved "https://registry.yarnpkg.com/@types/underscore/-/underscore-1.10.0.tgz#5cb0dff2a5f616fc8e0c61b482bf01fa20a03cec"
|
||||||
integrity sha512-ZAbqul7QAKpM2h1PFGa5ETN27ulmqtj0QviYHasw9LffvXZvVHuraOx/FOsIPPDNGZN0Qo1nASxxSfMYOtSoCw==
|
integrity sha512-ZAbqul7QAKpM2h1PFGa5ETN27ulmqtj0QviYHasw9LffvXZvVHuraOx/FOsIPPDNGZN0Qo1nASxxSfMYOtSoCw==
|
||||||
|
|
||||||
"@types/url-parse@^1.4.3":
|
"@types/url-parse@^1.4.4":
|
||||||
version "1.4.3"
|
version "1.4.4"
|
||||||
resolved "https://registry.yarnpkg.com/@types/url-parse/-/url-parse-1.4.3.tgz#fba49d90f834951cb000a674efee3d6f20968329"
|
resolved "https://registry.yarnpkg.com/@types/url-parse/-/url-parse-1.4.4.tgz#ebeb0ec8b581318739cf73e9f9b186f610764255"
|
||||||
integrity sha512-4kHAkbV/OfW2kb5BLVUuUMoumB3CP8rHqlw48aHvFy5tf9ER0AfOonBlX29l/DD68G70DmyhRlSYfQPSYpC5Vw==
|
integrity sha512-KtQLad12+4T/NfSxpoDhmr22+fig3T7/08QCgmutYA6QSznSRmEtuL95GrhVV40/0otTEdFc+etRcCTqhh1q5Q==
|
||||||
|
|
||||||
"@types/uuid@^8.3.1":
|
"@types/uuid@^8.3.1":
|
||||||
version "8.3.1"
|
version "8.3.1"
|
||||||
@ -2766,10 +2759,10 @@ arg@^4.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
|
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
|
||||||
integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
|
integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
|
||||||
|
|
||||||
arg@^5.0.0:
|
arg@^5.0.1:
|
||||||
version "5.0.0"
|
version "5.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.0.tgz#a20e2bb5710e82950a516b3f933fee5ed478be90"
|
resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.1.tgz#eb0c9a8f77786cad2af8ff2b862899842d7b6adb"
|
||||||
integrity sha512-4P8Zm2H+BRS+c/xX1LrHw0qKpEhdlZjLCgWy+d78T9vqa2Z2SiD2wMrYuWIAFy5IZUD7nnNXroRttz+0RzlrzQ==
|
integrity sha512-e0hDa9H2Z9AwFkk2qDlwhoMYE4eToKarchkQHovNdLTCYMHZHeRjI71crOh+dio4K6u1IcwubQqo79Ga4CyAQA==
|
||||||
|
|
||||||
argparse@^1.0.7:
|
argparse@^1.0.7:
|
||||||
version "1.0.10"
|
version "1.0.10"
|
||||||
@ -3673,10 +3666,10 @@ chalk@^3.0.0:
|
|||||||
ansi-styles "^4.1.0"
|
ansi-styles "^4.1.0"
|
||||||
supports-color "^7.1.0"
|
supports-color "^7.1.0"
|
||||||
|
|
||||||
chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1:
|
chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2:
|
||||||
version "4.1.1"
|
version "4.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad"
|
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
|
||||||
integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==
|
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
|
||||||
dependencies:
|
dependencies:
|
||||||
ansi-styles "^4.1.0"
|
ansi-styles "^4.1.0"
|
||||||
supports-color "^7.1.0"
|
supports-color "^7.1.0"
|
||||||
@ -4012,7 +4005,7 @@ color@3.0.x:
|
|||||||
color-convert "^1.9.1"
|
color-convert "^1.9.1"
|
||||||
color-string "^1.5.2"
|
color-string "^1.5.2"
|
||||||
|
|
||||||
color@^3.1.2, color@^3.1.3:
|
color@^3.1.2:
|
||||||
version "3.1.3"
|
version "3.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/color/-/color-3.1.3.tgz#ca67fb4e7b97d611dcde39eceed422067d91596e"
|
resolved "https://registry.yarnpkg.com/color/-/color-3.1.3.tgz#ca67fb4e7b97d611dcde39eceed422067d91596e"
|
||||||
integrity sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ==
|
integrity sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ==
|
||||||
@ -4066,11 +4059,6 @@ combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
|
|||||||
dependencies:
|
dependencies:
|
||||||
delayed-stream "~1.0.0"
|
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:
|
commander@2.9.0:
|
||||||
version "2.9.0"
|
version "2.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4"
|
||||||
@ -4148,10 +4136,10 @@ concat-stream@^1.5.0, concat-stream@^1.6.2:
|
|||||||
readable-stream "^2.2.2"
|
readable-stream "^2.2.2"
|
||||||
typedarray "^0.0.6"
|
typedarray "^0.0.6"
|
||||||
|
|
||||||
concurrently@^5.2.0:
|
concurrently@^5.3.0:
|
||||||
version "5.2.0"
|
version "5.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-5.2.0.tgz#ead55121d08a0fc817085584c123cedec2e08975"
|
resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-5.3.0.tgz#7500de6410d043c912b2da27de3202cb489b1e7b"
|
||||||
integrity sha512-XxcDbQ4/43d6CxR7+iV8IZXhur4KbmEJk1CetVMUqCy34z9l0DkszbY+/9wvmSnToTej0SYomc2WSRH+L0zVJw==
|
integrity sha512-8MhqOB6PWlBfA2vJ8a0bSFKATOdWlHiQlk11IfmQBPaHVP8oP2gsh2MObE6UR3hqDHqvaIvLTyceNW6obVuFHQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
chalk "^2.4.2"
|
chalk "^2.4.2"
|
||||||
date-fns "^2.0.1"
|
date-fns "^2.0.1"
|
||||||
@ -4315,10 +4303,10 @@ cosmiconfig@^6.0.0:
|
|||||||
path-type "^4.0.0"
|
path-type "^4.0.0"
|
||||||
yaml "^1.7.2"
|
yaml "^1.7.2"
|
||||||
|
|
||||||
cosmiconfig@^7.0.0:
|
cosmiconfig@^7.0.0, cosmiconfig@^7.0.1:
|
||||||
version "7.0.0"
|
version "7.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.0.tgz#ef9b44d773959cae63ddecd122de23853b60f8d3"
|
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d"
|
||||||
integrity sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==
|
integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/parse-json" "^4.0.0"
|
"@types/parse-json" "^4.0.0"
|
||||||
import-fresh "^3.2.1"
|
import-fresh "^3.2.1"
|
||||||
@ -4451,6 +4439,11 @@ css-box-model@^1.2.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
tiny-invariant "^1.0.6"
|
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:
|
css-loader@^5.2.7:
|
||||||
version "5.2.7"
|
version "5.2.7"
|
||||||
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.2.7.tgz#9b9f111edf6fb2be5dc62525644cbc9c232064ae"
|
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.2.7.tgz#9b9f111edf6fb2be5dc62525644cbc9c232064ae"
|
||||||
@ -4881,10 +4874,10 @@ dezalgo@^1.0.0, dezalgo@~1.0.3:
|
|||||||
asap "^2.0.0"
|
asap "^2.0.0"
|
||||||
wrappy "1"
|
wrappy "1"
|
||||||
|
|
||||||
didyoumean@^1.2.1:
|
didyoumean@^1.2.2:
|
||||||
version "1.2.1"
|
version "1.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.1.tgz#e92edfdada6537d484d73c0172fd1eba0c4976ff"
|
resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037"
|
||||||
integrity sha1-6S7f2tplN9SE1zwBcv0eugxJdv8=
|
integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==
|
||||||
|
|
||||||
diff-sequences@^26.6.2:
|
diff-sequences@^26.6.2:
|
||||||
version "26.6.2"
|
version "26.6.2"
|
||||||
@ -5980,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"
|
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
||||||
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
||||||
|
|
||||||
fast-glob@^3.1.1:
|
fast-glob@^3.1.1, fast-glob@^3.2.7:
|
||||||
version "3.2.4"
|
version "3.2.7"
|
||||||
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3"
|
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1"
|
||||||
integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==
|
integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@nodelib/fs.stat" "^2.0.2"
|
"@nodelib/fs.stat" "^2.0.2"
|
||||||
"@nodelib/fs.walk" "^1.2.3"
|
"@nodelib/fs.walk" "^1.2.3"
|
||||||
glob-parent "^5.1.0"
|
glob-parent "^5.1.2"
|
||||||
merge2 "^1.3.0"
|
merge2 "^1.3.0"
|
||||||
micromatch "^4.0.2"
|
micromatch "^4.0.4"
|
||||||
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"
|
|
||||||
|
|
||||||
fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0:
|
fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
@ -6542,19 +6522,19 @@ glob-parent@^3.1.0:
|
|||||||
is-glob "^3.1.0"
|
is-glob "^3.1.0"
|
||||||
path-dirname "^1.0.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"
|
version "5.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
|
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
|
||||||
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
|
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
|
||||||
dependencies:
|
dependencies:
|
||||||
is-glob "^4.0.1"
|
is-glob "^4.0.1"
|
||||||
|
|
||||||
glob-parent@^6.0.0:
|
glob-parent@^6.0.1:
|
||||||
version "6.0.0"
|
version "6.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.0.tgz#f851b59b388e788f3a44d63fab50382b2859c33c"
|
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3"
|
||||||
integrity sha512-Hdd4287VEJcZXUwv1l8a+vXC1GjOQqXe+VS30w/ypihpcnu9M1n3xeYeJu5CBpeEQj2nAab2xxz28GuA3vp4Ww==
|
integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==
|
||||||
dependencies:
|
dependencies:
|
||||||
is-glob "^4.0.1"
|
is-glob "^4.0.3"
|
||||||
|
|
||||||
glob-to-regexp@^0.4.1:
|
glob-to-regexp@^0.4.1:
|
||||||
version "0.4.1"
|
version "0.4.1"
|
||||||
@ -6896,6 +6876,11 @@ he@^1.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
||||||
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
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:
|
history@^4.10.1, history@^4.9.0:
|
||||||
version "4.10.1"
|
version "4.10.1"
|
||||||
resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3"
|
resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3"
|
||||||
@ -6953,6 +6938,16 @@ hpack.js@^2.1.6:
|
|||||||
readable-stream "^2.0.1"
|
readable-stream "^2.0.1"
|
||||||
wbuf "^1.1.0"
|
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:
|
html-encoding-sniffer@^2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3"
|
resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3"
|
||||||
@ -7262,10 +7257,10 @@ immediate@~3.0.5:
|
|||||||
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
|
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
|
||||||
integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=
|
integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=
|
||||||
|
|
||||||
immer@^8.0.4:
|
immer@^9.0.6:
|
||||||
version "8.0.4"
|
version "9.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/immer/-/immer-8.0.4.tgz#3a21605a4e2dded852fb2afd208ad50969737b7a"
|
resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.6.tgz#7a96bf2674d06c8143e327cbf73539388ddf1a73"
|
||||||
integrity sha512-jMfL18P+/6P6epANRvRk6q8t+3gGhqsJ9EuJ25AXE+9bNTYtssvzeYbEd0mXRYWCmmXSIbnlpz6vd6iJlmGGGQ==
|
integrity sha512-G95ivKpy+EvVAnAab4fVa4YGYn24J1SpEktnJX7JJ45Bd7xqME/SCplFzYFmTbrkwZbQ4xJK1xMTUYBkN6pWsQ==
|
||||||
|
|
||||||
import-cwd@^3.0.0:
|
import-cwd@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
@ -7332,11 +7327,6 @@ indent-string@^4.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
|
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
|
||||||
integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
|
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:
|
infer-owner@^1.0.3, infer-owner@^1.0.4:
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467"
|
resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467"
|
||||||
@ -7549,6 +7539,18 @@ is-cidr@^3.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
cidr-regex "^2.0.10"
|
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:
|
is-core-module@^2.2.0:
|
||||||
version "2.2.0"
|
version "2.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a"
|
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a"
|
||||||
@ -7649,10 +7651,10 @@ is-glob@^3.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-extglob "^2.1.0"
|
is-extglob "^2.1.0"
|
||||||
|
|
||||||
is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1:
|
is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
|
||||||
version "4.0.1"
|
version "4.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
|
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
|
||||||
integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
|
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
|
||||||
dependencies:
|
dependencies:
|
||||||
is-extglob "^2.1.1"
|
is-extglob "^2.1.1"
|
||||||
|
|
||||||
@ -9138,11 +9140,6 @@ lodash.merge@^4.6.2:
|
|||||||
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
|
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
|
||||||
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
|
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:
|
lodash.topath@^4.5.2:
|
||||||
version "4.5.2"
|
version "4.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.topath/-/lodash.topath-4.5.2.tgz#3616351f3bba61994a0931989660bd03254fd009"
|
resolved "https://registry.yarnpkg.com/lodash.topath/-/lodash.topath-4.5.2.tgz#3616351f3bba61994a0931989660bd03254fd009"
|
||||||
@ -9448,6 +9445,14 @@ micromatch@^4.0.0, micromatch@^4.0.2:
|
|||||||
braces "^3.0.1"
|
braces "^3.0.1"
|
||||||
picomatch "^2.0.5"
|
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:
|
miller-rabin@^4.0.0:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d"
|
resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d"
|
||||||
@ -9478,12 +9483,7 @@ mime@1.6.0, mime@^1.4.1:
|
|||||||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
|
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
|
||||||
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
|
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
|
||||||
|
|
||||||
mime@^2.4.4:
|
mime@^2.4.4, mime@^2.4.6, mime@^2.5.2:
|
||||||
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:
|
|
||||||
version "2.5.2"
|
version "2.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe"
|
resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe"
|
||||||
integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==
|
integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==
|
||||||
@ -9838,12 +9838,12 @@ node-addon-api@^4.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.2.0.tgz#117cbb5a959dff0992e1c586ae0393573e4d2a87"
|
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.2.0.tgz#117cbb5a959dff0992e1c586ae0393573e4d2a87"
|
||||||
integrity sha512-eazsqzwG2lskuzBqCGPi7Ac2UgOoMz8JVOXVhTvvPDYhthvNpefx8jWD8Np7Gv+2Sz0FlPWZk0nJV0z598Wn8Q==
|
integrity sha512-eazsqzwG2lskuzBqCGPi7Ac2UgOoMz8JVOXVhTvvPDYhthvNpefx8jWD8Np7Gv+2Sz0FlPWZk0nJV0z598Wn8Q==
|
||||||
|
|
||||||
node-emoji@^1.8.1:
|
node-emoji@^1.11.0:
|
||||||
version "1.10.0"
|
version "1.11.0"
|
||||||
resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.10.0.tgz#8886abd25d9c7bb61802a658523d1f8d2a89b2da"
|
resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.11.0.tgz#69a0150e6946e2f115e9d7ea4df7971e2628301c"
|
||||||
integrity sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw==
|
integrity sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==
|
||||||
dependencies:
|
dependencies:
|
||||||
lodash.toarray "^4.4.0"
|
lodash "^4.17.21"
|
||||||
|
|
||||||
node-fetch-npm@^2.0.2:
|
node-fetch-npm@^2.0.2:
|
||||||
version "2.0.4"
|
version "2.0.4"
|
||||||
@ -10323,12 +10323,7 @@ object-copy@^0.1.0:
|
|||||||
define-property "^0.2.5"
|
define-property "^0.2.5"
|
||||||
kind-of "^3.0.3"
|
kind-of "^3.0.3"
|
||||||
|
|
||||||
object-hash@^2.0.1:
|
object-hash@^2.0.1, object-hash@^2.2.0:
|
||||||
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:
|
|
||||||
version "2.2.0"
|
version "2.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5"
|
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5"
|
||||||
integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==
|
integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==
|
||||||
@ -10928,6 +10923,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"
|
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
|
||||||
integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
|
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:
|
pify@^2.0.0:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
|
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
|
||||||
@ -10983,10 +10983,10 @@ pkg-up@^3.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
find-up "^3.0.0"
|
find-up "^3.0.0"
|
||||||
|
|
||||||
playwright@^1.14.0:
|
playwright@^1.15.2:
|
||||||
version "1.14.0"
|
version "1.15.2"
|
||||||
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.14.0.tgz#18301b11f5278a446d36b5cf96f67db36ce2cd20"
|
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.15.2.tgz#b350056d1fffbe5de5b1bdaca6ab73e35758baad"
|
||||||
integrity sha512-aR5oZ1iVsjQkGfYCjgYAmyMAVu0MQ0i8MgdnfdqDu9EVLfbnpuuFmTv/Rb7/Yjno1kOrDUP9+RyNC+zfG3wozA==
|
integrity sha512-+Z+7ckihyxR6rK5q8DWC6eUbKARfXpyxpjNcoJfgwSr64lAOzjhyFQiPC/JkdIqhsLgZjxpWfl1S7fLb+wPkgA==
|
||||||
dependencies:
|
dependencies:
|
||||||
commander "^6.1.0"
|
commander "^6.1.0"
|
||||||
debug "^4.1.1"
|
debug "^4.1.1"
|
||||||
@ -11119,31 +11119,14 @@ postcss-modules-values@^4.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
icss-utils "^5.0.0"
|
icss-utils "^5.0.0"
|
||||||
|
|
||||||
postcss-nested@5.0.5:
|
postcss-nested@5.0.6:
|
||||||
version "5.0.5"
|
version "5.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-5.0.5.tgz#f0a107d33a9fab11d7637205f5321e27223e3603"
|
resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-5.0.6.tgz#466343f7fc8d3d46af3e7dba3fcd47d052a945bc"
|
||||||
integrity sha512-GSRXYz5bccobpTzLQZXOnSOfKl6TwVr5CyAQJUPub4nuRJSOECK5AqurxVgmtxP48p0Kc/ndY/YyS1yqldX0Ew==
|
integrity sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==
|
||||||
dependencies:
|
dependencies:
|
||||||
postcss-selector-parser "^6.0.4"
|
postcss-selector-parser "^6.0.6"
|
||||||
|
|
||||||
postcss-selector-parser@^6.0.2:
|
postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.6:
|
||||||
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:
|
|
||||||
version "6.0.6"
|
version "6.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz#2c5bba8174ac2f6981ab631a42ab0ee54af332ea"
|
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz#2c5bba8174ac2f6981ab631a42ab0ee54af332ea"
|
||||||
integrity sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==
|
integrity sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==
|
||||||
@ -11223,11 +11206,6 @@ prepend-http@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
|
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
|
||||||
integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
|
integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
|
||||||
|
|
||||||
prettier@^2.4.1:
|
|
||||||
version "2.4.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.4.1.tgz#671e11c89c14a4cfc876ce564106c4a6726c9f5c"
|
|
||||||
integrity sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==
|
|
||||||
|
|
||||||
pretty-error@^2.1.1:
|
pretty-error@^2.1.1:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.1.tgz#5f4f87c8f91e5ae3f3ba87ab4cf5e03b1a17f1a3"
|
resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.1.tgz#5f4f87c8f91e5ae3f3ba87ab4cf5e03b1a17f1a3"
|
||||||
@ -12196,6 +12174,16 @@ rfc6902@^4.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/rfc6902/-/rfc6902-4.0.2.tgz#ce99d3562b9e3287d403462e6bcc81eead8fcea0"
|
resolved "https://registry.yarnpkg.com/rfc6902/-/rfc6902-4.0.2.tgz#ce99d3562b9e3287d403462e6bcc81eead8fcea0"
|
||||||
integrity sha512-MJOC4iDSv3Qn5/QvhPbrNoRongti6moXSShcRmtbNqOk0WPxlviEdMV4bb9PaULhSxLUXzWd4AjAMKQ3j3y54w==
|
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:
|
rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@^2.7.1:
|
||||||
version "2.7.1"
|
version "2.7.1"
|
||||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
|
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
|
||||||
@ -12309,10 +12297,10 @@ sass-loader@^8.0.2:
|
|||||||
schema-utils "^2.6.1"
|
schema-utils "^2.6.1"
|
||||||
semver "^6.3.0"
|
semver "^6.3.0"
|
||||||
|
|
||||||
sass@^1.32.13, sass@^1.41.1:
|
sass@^1.32.13, sass@^1.43.2:
|
||||||
version "1.41.1"
|
version "1.43.2"
|
||||||
resolved "https://registry.yarnpkg.com/sass/-/sass-1.41.1.tgz#bca5bed2154192779c29f48fca9c644c60c38d98"
|
resolved "https://registry.yarnpkg.com/sass/-/sass-1.43.2.tgz#c02501520c624ad6622529a8b3724eb08da82d65"
|
||||||
integrity sha512-vIjX7izRxw3Wsiez7SX7D+j76v7tenfO18P59nonjr/nzCkZuoHuF7I/Fo0ZRZPKr88v29ivIdE9BqGDgQD/Nw==
|
integrity sha512-DncYhjl3wBaPMMJR0kIUaH3sF536rVrOcqqVGmTZHQRRzj7LQlyGV7Mb8aCKFyILMr5VsPHwRYtyKpnKYlmQSQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
chokidar ">=3.0.0 <4.0.0"
|
chokidar ">=3.0.0 <4.0.0"
|
||||||
|
|
||||||
@ -12926,14 +12914,7 @@ stack-trace@0.0.x:
|
|||||||
resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0"
|
resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0"
|
||||||
integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=
|
integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=
|
||||||
|
|
||||||
stack-utils@^2.0.2:
|
stack-utils@^2.0.2, stack-utils@^2.0.3:
|
||||||
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:
|
|
||||||
version "2.0.3"
|
version "2.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.3.tgz#cd5f030126ff116b78ccb3c027fe302713b61277"
|
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.3.tgz#cd5f030126ff116b78ccb3c027fe302713b61277"
|
||||||
integrity sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw==
|
integrity sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw==
|
||||||
@ -13326,38 +13307,39 @@ table@^6.0.9:
|
|||||||
string-width "^4.2.3"
|
string-width "^4.2.3"
|
||||||
strip-ansi "^6.0.1"
|
strip-ansi "^6.0.1"
|
||||||
|
|
||||||
tailwindcss@^2.2.4:
|
tailwindcss@^2.2.17:
|
||||||
version "2.2.4"
|
version "2.2.17"
|
||||||
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-2.2.4.tgz#6a2e259b1e26125aeaa7cdc479963fd217c308b0"
|
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-2.2.17.tgz#c6332731f9ff1b6628ff589c95c38685347775e3"
|
||||||
integrity sha512-OdBCPgazNNsknSP+JfrPzkay9aqKjhKtFhbhgxHgvEFdHy/GuRPo2SCJ4w1SFTN8H6FPI4m6qD/Jj20NWY1GkA==
|
integrity sha512-WgRpn+Pxn7eWqlruxnxEbL9ByVRWi3iC10z4b6dW0zSdnkPVC4hPMSWLQkkW8GCyBIv/vbJ0bxIi9dVrl4CfoA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@fullhuman/postcss-purgecss" "^4.0.3"
|
arg "^5.0.1"
|
||||||
arg "^5.0.0"
|
|
||||||
bytes "^3.0.0"
|
bytes "^3.0.0"
|
||||||
chalk "^4.1.1"
|
chalk "^4.1.2"
|
||||||
chokidar "^3.5.2"
|
chokidar "^3.5.2"
|
||||||
color "^3.1.3"
|
color "^4.0.1"
|
||||||
cosmiconfig "^7.0.0"
|
cosmiconfig "^7.0.1"
|
||||||
detective "^5.2.0"
|
detective "^5.2.0"
|
||||||
didyoumean "^1.2.1"
|
didyoumean "^1.2.2"
|
||||||
dlv "^1.1.3"
|
dlv "^1.1.3"
|
||||||
fast-glob "^3.2.5"
|
fast-glob "^3.2.7"
|
||||||
fs-extra "^10.0.0"
|
fs-extra "^10.0.0"
|
||||||
glob-parent "^6.0.0"
|
glob-parent "^6.0.1"
|
||||||
html-tags "^3.1.0"
|
html-tags "^3.1.0"
|
||||||
|
is-color-stop "^1.1.0"
|
||||||
is-glob "^4.0.1"
|
is-glob "^4.0.1"
|
||||||
lodash "^4.17.21"
|
lodash "^4.17.21"
|
||||||
lodash.topath "^4.5.2"
|
lodash.topath "^4.5.2"
|
||||||
modern-normalize "^1.1.0"
|
modern-normalize "^1.1.0"
|
||||||
node-emoji "^1.8.1"
|
node-emoji "^1.11.0"
|
||||||
normalize-path "^3.0.0"
|
normalize-path "^3.0.0"
|
||||||
object-hash "^2.2.0"
|
object-hash "^2.2.0"
|
||||||
postcss-js "^3.0.3"
|
postcss-js "^3.0.3"
|
||||||
postcss-load-config "^3.1.0"
|
postcss-load-config "^3.1.0"
|
||||||
postcss-nested "5.0.5"
|
postcss-nested "5.0.6"
|
||||||
postcss-selector-parser "^6.0.6"
|
postcss-selector-parser "^6.0.6"
|
||||||
postcss-value-parser "^4.1.0"
|
postcss-value-parser "^4.1.0"
|
||||||
pretty-hrtime "^1.0.3"
|
pretty-hrtime "^1.0.3"
|
||||||
|
purgecss "^4.0.3"
|
||||||
quick-lru "^5.1.1"
|
quick-lru "^5.1.1"
|
||||||
reduce-css-calc "^2.1.8"
|
reduce-css-calc "^2.1.8"
|
||||||
resolve "^1.20.0"
|
resolve "^1.20.0"
|
||||||
@ -13867,10 +13849,10 @@ typedarray@^0.0.6:
|
|||||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||||
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
|
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
|
||||||
|
|
||||||
typedoc-plugin-markdown@^3.9.0:
|
typedoc-plugin-markdown@^3.11.3:
|
||||||
version "3.9.0"
|
version "3.11.3"
|
||||||
resolved "https://registry.yarnpkg.com/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.9.0.tgz#d9c0390b8ddeeda56fdbf01264521ef04b3c19c7"
|
resolved "https://registry.yarnpkg.com/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.11.3.tgz#be340b905903f1ce552fa2fa7d677db408ab1041"
|
||||||
integrity sha512-s445YeUe8bH7me15T+hsHZgNmAvvF7QIpX02vFgseLGtghAwmtdZYVOqPneWoKqRv/JNpPSuyZb3CeblML9jOg==
|
integrity sha512-rWiHbEIe0oZetDIsBR24XJVxGOJ91kDcHoj2KhFKxCLoJGX659EKBQkHne9QJ4W2stGhu1fRgFyQaouSBnxukA==
|
||||||
dependencies:
|
dependencies:
|
||||||
handlebars "^4.7.7"
|
handlebars "^4.7.7"
|
||||||
|
|
||||||
@ -13963,11 +13945,6 @@ union-value@^1.0.0:
|
|||||||
is-extendable "^0.1.1"
|
is-extendable "^0.1.1"
|
||||||
set-value "^2.0.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:
|
unique-filename@^1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230"
|
resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230"
|
||||||
@ -14677,10 +14654,10 @@ ws@^6.2.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
async-limiter "~1.0.0"
|
async-limiter "~1.0.0"
|
||||||
|
|
||||||
ws@^7.3.1, ws@^7.4.6:
|
ws@^7.3.1, ws@^7.4.6, ws@^7.5.5:
|
||||||
version "7.4.6"
|
version "7.5.5"
|
||||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c"
|
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.5.tgz#8b4bc4af518cfabd0473ae4f99144287b33eb881"
|
||||||
integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==
|
integrity sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==
|
||||||
|
|
||||||
xdg-basedir@^3.0.0:
|
xdg-basedir@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user