1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Merge branch 'master' into extensions-api

# Conflicts:
#	package.json
#	src/common/cluster-store.ts
#	src/renderer/bootstrap.tsx
#	src/renderer/components/cluster-manager/clusters-menu.tsx
#	static/RELEASE_NOTES.md
#	webpack.dll.ts
This commit is contained in:
Roman 2020-09-11 12:29:38 +03:00
commit a3dc928430
34 changed files with 273 additions and 139 deletions

View File

@ -47,7 +47,6 @@ test-app:
yarn test yarn test
build: install-deps download-bins build: install-deps download-bins
yarn install
ifeq "$(DETECTED_OS)" "Windows" ifeq "$(DETECTED_OS)" "Windows"
yarn dist:win yarn dist:win
else else

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -2,7 +2,7 @@
"name": "kontena-lens", "name": "kontena-lens",
"productName": "Lens", "productName": "Lens",
"description": "Lens - The Kubernetes IDE", "description": "Lens - The Kubernetes IDE",
"version": "3.6.0-rc.1", "version": "3.6.0-rc.2",
"main": "static/build/main.js", "main": "static/build/main.js",
"copyright": "© 2020, Mirantis, Inc.", "copyright": "© 2020, Mirantis, Inc.",
"license": "MIT", "license": "MIT",
@ -196,7 +196,6 @@
"jsonpath": "^1.0.2", "jsonpath": "^1.0.2",
"lodash": "^4.17.15", "lodash": "^4.17.15",
"mac-ca": "^1.0.4", "mac-ca": "^1.0.4",
"make-synchronous": "^0.1.1",
"marked": "^1.1.0", "marked": "^1.1.0",
"md5-file": "^5.0.0", "md5-file": "^5.0.0",
"mobx": "^5.15.5", "mobx": "^5.15.5",
@ -250,6 +249,7 @@
"@types/material-ui": "^0.21.7", "@types/material-ui": "^0.21.7",
"@types/md5-file": "^4.0.2", "@types/md5-file": "^4.0.2",
"@types/mini-css-extract-plugin": "^0.9.1", "@types/mini-css-extract-plugin": "^0.9.1",
"@types/progress-bar-webpack-plugin": "^2.1.0",
"@types/react": "^16.9.35", "@types/react": "^16.9.35",
"@types/react-router-dom": "^5.1.5", "@types/react-router-dom": "^5.1.5",
"@types/react-select": "^3.0.13", "@types/react-select": "^3.0.13",
@ -305,6 +305,7 @@
"nodemon": "^2.0.4", "nodemon": "^2.0.4",
"patch-package": "^6.2.2", "patch-package": "^6.2.2",
"postinstall-postinstall": "^2.1.0", "postinstall-postinstall": "^2.1.0",
"progress-bar-webpack-plugin": "^2.1.0",
"raw-loader": "^4.0.1", "raw-loader": "^4.0.1",
"react": "^16.13.1", "react": "^16.13.1",
"react-dom": "^16.13.1", "react-dom": "^16.13.1",

View File

@ -3,21 +3,14 @@ import { ClusterId, clusterStore } from "./cluster-store";
import { tracker } from "./tracker"; import { tracker } from "./tracker";
export const clusterIpc = { export const clusterIpc = {
initView: createIpcChannel({
channel: "cluster:init",
handle: async (clusterId: ClusterId, frameId: number) => {
const cluster = clusterStore.getById(clusterId);
if (cluster) {
cluster.frameId = frameId; // save cluster's webFrame.routingId to be able to send push-updates
return cluster.pushState();
}
},
}),
activate: createIpcChannel({ activate: createIpcChannel({
channel: "cluster:activate", channel: "cluster:activate",
handle: (clusterId: ClusterId) => { handle: (clusterId: ClusterId, frameId?: number) => {
return clusterStore.getById(clusterId)?.activate(); const cluster = clusterStore.getById(clusterId);
if (cluster) {
if (frameId) cluster.frameId = frameId; // save cluster's webFrame.routingId to be able to send push-updates
return cluster.activate(true);
}
}, },
}), }),

View File

@ -209,11 +209,17 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
export const clusterStore = ClusterStore.getInstance<ClusterStore>(); export const clusterStore = ClusterStore.getInstance<ClusterStore>();
export function getHostedClusterId(): ClusterId { export function getClusterIdFromHost(hostname: string): ClusterId {
const clusterHost = location.hostname.match(/^(.*?)\.localhost/); const subDomains = hostname.split(":")[0].split(".");
if (clusterHost) { return subDomains.slice(-2)[0]; // e.g host == "%clusterId.localhost:45345"
return clusterHost[1]
} }
export function getClusterFrameUrl(clusterId: ClusterId) {
return `//${clusterId}.${location.host}`;
}
export function getHostedClusterId() {
return getClusterIdFromHost(location.hostname);
} }
export function getHostedCluster(): Cluster { export function getHostedCluster(): Cluster {

View File

@ -31,9 +31,10 @@ describe("empty config", () => {
it("adds new cluster to store", async () => { it("adds new cluster to store", async () => {
const cluster = new Cluster({ const cluster = new Cluster({
id: "foo", id: "foo",
contextName: "minikube",
preferences: { preferences: {
terminalCWD: "/tmp", terminalCWD: "/tmp",
icon: "data:image/jpeg;base64, iVBORw0KGgoAAAANSUhEUgAAA1wAAAKoCAYAAABjkf5", icon: "data:;base64,iVBORw0KGgoAAAANSUhEUgAAA1wAAAKoCAYAAABjkf5",
clusterName: "minikube" clusterName: "minikube"
}, },
kubeConfigPath: ClusterStore.embedCustomKubeConfig("foo", "fancy foo config"), kubeConfigPath: ClusterStore.embedCustomKubeConfig("foo", "fancy foo config"),
@ -54,6 +55,7 @@ describe("empty config", () => {
it("check if store can contain multiple clusters", () => { it("check if store can contain multiple clusters", () => {
const prodCluster = new Cluster({ const prodCluster = new Cluster({
id: "prod", id: "prod",
contextName: "prod",
preferences: { preferences: {
clusterName: "prod" clusterName: "prod"
}, },
@ -62,6 +64,7 @@ describe("empty config", () => {
}); });
const devCluster = new Cluster({ const devCluster = new Cluster({
id: "dev", id: "dev",
contextName: "dev",
preferences: { preferences: {
clusterName: "dev" clusterName: "dev"
}, },
@ -142,11 +145,13 @@ describe("config with existing clusters", () => {
{ {
id: 'cluster1', id: 'cluster1',
kubeConfig: 'foo', kubeConfig: 'foo',
contextName: 'foo',
preferences: { terminalCWD: '/foo' } preferences: { terminalCWD: '/foo' }
}, },
{ {
id: 'cluster2', id: 'cluster2',
kubeConfig: 'foo2', kubeConfig: 'foo2',
contextName: 'foo2',
preferences: { terminalCWD: '/foo2' } preferences: { terminalCWD: '/foo2' }
} }
] ]
@ -285,7 +290,7 @@ describe("pre 2.6.0 config with a cluster icon", () => {
const storedClusterData = clusterStore.clustersList[0]; const storedClusterData = clusterStore.clustersList[0];
expect(storedClusterData.hasOwnProperty('icon')).toBe(false); expect(storedClusterData.hasOwnProperty('icon')).toBe(false);
expect(storedClusterData.preferences.hasOwnProperty('icon')).toBe(true); expect(storedClusterData.preferences.hasOwnProperty('icon')).toBe(true);
expect(storedClusterData.preferences.icon.startsWith("data:image/jpeg;base64,")).toBe(true); expect(storedClusterData.preferences.icon.startsWith("data:;base64,")).toBe(true);
}) })
}) })
@ -339,6 +344,7 @@ describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
{ {
id: 'cluster1', id: 'cluster1',
kubeConfig: 'kubeconfig content', kubeConfig: 'kubeconfig content',
contextName: 'cluster',
preferences: { preferences: {
icon: "store://icon_path", icon: "store://icon_path",
} }
@ -364,6 +370,6 @@ describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
it("migrates to modern format with icon not in file", async () => { it("migrates to modern format with icon not in file", async () => {
const { icon } = clusterStore.clustersList[0].preferences; const { icon } = clusterStore.clustersList[0].preferences;
expect(icon.startsWith("data:image/jpeg;base64, ")).toBe(true); expect(icon.startsWith("data:;base64,")).toBe(true);
}) })
}) })

View File

@ -51,6 +51,10 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
return this.workspaces.get(id); return this.workspaces.get(id);
} }
getByName(name: string): Workspace {
return this.workspacesList.find(workspace => workspace.name === name);
}
@action @action
setActive(id = WorkspaceStore.defaultId, { redirectToLanding = true, resetActiveCluster = true } = {}) { setActive(id = WorkspaceStore.defaultId, { redirectToLanding = true, resetActiveCluster = true } = {}) {
if (id === this.currentWorkspaceId) return; if (id === this.currentWorkspaceId) return;
@ -68,13 +72,16 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
@action @action
saveWorkspace(workspace: Workspace) { saveWorkspace(workspace: Workspace) {
const id = workspace.id; const { id, name } = workspace;
const existingWorkspace = this.getById(id); const existingWorkspace = this.getById(id);
if (!name.trim() || this.getByName(name.trim())) {
return;
}
if (existingWorkspace) { if (existingWorkspace) {
Object.assign(existingWorkspace, workspace); Object.assign(existingWorkspace, workspace);
} else {
this.workspaces.set(id, workspace);
} }
this.workspaces.set(id, workspace);
return workspace;
} }
@action @action

View File

@ -92,6 +92,50 @@ describe("workspace store tests", () => {
expect(ws.workspaces.size).toBe(2); expect(ws.workspaces.size).toBe(2);
}) })
it("cannot create workspace with existent name", () => {
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
ws.saveWorkspace({
id: "someid",
name: "default",
});
expect(ws.workspacesList.length).toBe(1); // default workspace only
})
it("cannot create workspace with empty name", () => {
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
ws.saveWorkspace({
id: "random",
name: "",
});
expect(ws.workspacesList.length).toBe(1); // default workspace only
})
it("cannot create workspace with ' ' name", () => {
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
ws.saveWorkspace({
id: "random",
name: " ",
});
expect(ws.workspacesList.length).toBe(1); // default workspace only
})
it("trim workspace name", () => {
const ws = WorkspaceStore.getInstance<WorkspaceStore>();
ws.saveWorkspace({
id: "random",
name: "default ",
});
expect(ws.workspacesList.length).toBe(1); // default workspace only
})
}) })
describe("for a non-empty config", () => { describe("for a non-empty config", () => {

View File

@ -1,7 +1,7 @@
import "../common/cluster-ipc"; import "../common/cluster-ipc";
import type http from "http" import type http from "http"
import { autorun } from "mobx"; import { autorun } from "mobx";
import { ClusterId, clusterStore } from "../common/cluster-store" import { clusterStore, getClusterIdFromHost } from "../common/cluster-store"
import { Cluster } from "./cluster" import { Cluster } from "./cluster"
import logger from "./logger"; import logger from "./logger";
import { apiKubePrefix } from "../common/vars"; import { apiKubePrefix } from "../common/vars";
@ -38,26 +38,20 @@ export class ClusterManager {
}) })
} }
protected getCluster(id: ClusterId) {
return clusterStore.getById(id);
}
getClusterForRequest(req: http.IncomingMessage): Cluster { getClusterForRequest(req: http.IncomingMessage): Cluster {
let cluster: Cluster = null let cluster: Cluster = null
// lens-server is connecting to 127.0.0.1:<port>/<uid> // lens-server is connecting to 127.0.0.1:<port>/<uid>
if (req.headers.host.startsWith("127.0.0.1")) { if (req.headers.host.startsWith("127.0.0.1")) {
const clusterId = req.url.split("/")[1] const clusterId = req.url.split("/")[1]
if (clusterId) { const cluster = clusterStore.getById(clusterId)
cluster = this.getCluster(clusterId)
if (cluster) { if (cluster) {
// we need to swap path prefix so that request is proxied to kube api // we need to swap path prefix so that request is proxied to kube api
req.url = req.url.replace(`/${clusterId}`, apiKubePrefix) req.url = req.url.replace(`/${clusterId}`, apiKubePrefix)
} }
}
} else { } else {
const id = req.headers.host.split(".")[0] const clusterId = getClusterIdFromHost(req.headers.host);
cluster = this.getCluster(id) cluster = clusterStore.getById(clusterId)
} }
return cluster; return cluster;

View File

@ -2,7 +2,7 @@ import type { ClusterId, ClusterModel, ClusterPreferences } from "../common/clus
import type { IMetricsReqParams } from "../renderer/api/endpoints/metrics.api"; import type { IMetricsReqParams } from "../renderer/api/endpoints/metrics.api";
import type { WorkspaceId } from "../common/workspace-store"; import type { WorkspaceId } from "../common/workspace-store";
import type { FeatureStatusMap } from "./feature" import type { FeatureStatusMap } from "./feature"
import { action, computed, intercept, observable, reaction, toJS, when } from "mobx"; import { action, computed, observable, reaction, toJS, when } from "mobx";
import { apiKubePrefix } from "../common/vars"; import { apiKubePrefix } from "../common/vars";
import { broadcastIpc } from "../common/ipc"; import { broadcastIpc } from "../common/ipc";
import { ContextHandler } from "./context-handler" import { ContextHandler } from "./context-handler"
@ -77,13 +77,15 @@ export class Cluster implements ClusterModel {
constructor(model: ClusterModel) { constructor(model: ClusterModel) {
this.updateModel(model); this.updateModel(model);
const kubeconfig = this.getKubeconfig()
if (kubeconfig.getContextObject(this.contextName)) {
this.apiUrl = kubeconfig.getCluster(kubeconfig.getContextObject(this.contextName).cluster).server
}
} }
@action @action
updateModel(model: ClusterModel) { updateModel(model: ClusterModel) {
Object.assign(this, model); Object.assign(this, model);
this.apiUrl = this.getKubeconfig().getCurrentCluster()?.server;
this.contextName = this.contextName || this.preferences.clusterName;
} }
@action @action
@ -124,13 +126,13 @@ export class Cluster implements ClusterModel {
this.eventDisposers.length = 0; this.eventDisposers.length = 0;
} }
async activate() { async activate(init = false) {
logger.info(`[CLUSTER]: activate`, this.getMeta()); logger.info(`[CLUSTER]: activate`, this.getMeta());
await this.whenInitialized; await this.whenInitialized;
if (!this.eventDisposers.length) { if (!this.eventDisposers.length) {
this.bindEvents(); this.bindEvents();
} }
if (this.disconnected || !this.accessible) { if (this.disconnected || (!init && !this.accessible)) {
await this.reconnect(); await this.reconnect();
} }
await this.refresh(); await this.refresh();
@ -409,6 +411,7 @@ export class Cluster implements ClusterModel {
id: this.id, id: this.id,
name: this.contextName, name: this.contextName,
initialized: this.initialized, initialized: this.initialized,
ready: this.ready,
online: this.online, online: this.online,
accessible: this.accessible, accessible: this.accessible,
disconnected: this.disconnected, disconnected: this.disconnected,

View File

@ -1,9 +1,9 @@
import type { ClusterId } from "../common/cluster-store";
import { clusterStore } from "../common/cluster-store"; import { clusterStore } from "../common/cluster-store";
import { BrowserWindow, dialog, ipcMain, shell, webContents } from "electron" import { BrowserWindow, dialog, ipcMain, shell, webContents } from "electron"
import windowStateKeeper from "electron-window-state" import windowStateKeeper from "electron-window-state"
import { observable } from "mobx"; import { observable } from "mobx";
import { initMenu } from "./menu"; import { initMenu } from "./menu";
import type { ClusterId } from "../common/cluster-store";
export class WindowManager { export class WindowManager {
protected mainView: BrowserWindow; protected mainView: BrowserWindow;
@ -42,7 +42,7 @@ export class WindowManager {
}); });
// track visible cluster from ui // track visible cluster from ui
ipcMain.on("cluster-view:change", (event, clusterId: ClusterId) => { ipcMain.on("cluster-view:current-id", (event, clusterId: ClusterId) => {
this.activeClusterId = clusterId; this.activeClusterId = clusterId;
}); });

View File

@ -7,11 +7,6 @@ import { migration } from "../migration-wrapper";
import fse from "fs-extra" import fse from "fs-extra"
import { ClusterModel, ClusterStore } from "../../common/cluster-store"; import { ClusterModel, ClusterStore } from "../../common/cluster-store";
import { loadConfig } from "../../common/kube-helpers"; import { loadConfig } from "../../common/kube-helpers";
import makeSynchronous from "make-synchronous"
const AsyncFunction = Object.getPrototypeOf(async function () { return }).constructor;
const getFileTypeFnString = `return require("file-type").fromBuffer(fileData)`;
const getFileType = new AsyncFunction("fileData", getFileTypeFnString);
export default migration({ export default migration({
version: "3.6.0-beta.1", version: "3.6.0-beta.1",
@ -48,13 +43,8 @@ export default migration({
printLog(`migrating ${cluster.preferences.icon} for ${cluster.preferences.clusterName}`) printLog(`migrating ${cluster.preferences.icon} for ${cluster.preferences.clusterName}`)
const iconPath = cluster.preferences.icon.replace("store://", "") const iconPath = cluster.preferences.icon.replace("store://", "")
const fileData = fse.readFileSync(path.join(userDataPath, iconPath)); const fileData = fse.readFileSync(path.join(userDataPath, iconPath));
const { mime = "" } = makeSynchronous(getFileType)(fileData);
if (!mime) { cluster.preferences.icon = `data:;base64,${fileData.toString('base64')}`;
printLog(`mime type not detected for ${cluster.preferences.clusterName}'s icon: ${iconPath}`)
}
cluster.preferences.icon = `data:${mime};base64, ${fileData.toString('base64')}`;
} else { } else {
delete cluster.preferences?.icon; delete cluster.preferences?.icon;
} }

View File

@ -5,7 +5,7 @@ import { isMac } from "../common/vars";
import { userStore } from "../common/user-store"; import { userStore } from "../common/user-store";
import { workspaceStore } from "../common/workspace-store"; import { workspaceStore } from "../common/workspace-store";
import { extensionStore } from "../extensions/extension-store"; import { extensionStore } from "../extensions/extension-store";
import { clusterStore, getHostedClusterId } from "../common/cluster-store"; import { clusterStore } from "../common/cluster-store";
import { i18nStore } from "./i18n"; import { i18nStore } from "./i18n";
import { themeStore } from "./theme.store"; import { themeStore } from "./theme.store";
import { App } from "./components/app"; import { App } from "./components/app";
@ -39,4 +39,4 @@ export async function bootstrap(App: AppComponent) {
} }
// run // run
bootstrap(getHostedClusterId() ? App : LensApp); bootstrap(process.isMainFrame ? LensApp : App);

View File

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { observable } from "mobx"; import { observable, autorun } from "mobx";
import { observer } from "mobx-react"; import { observer, disposeOnUnmount } from "mobx-react";
import { Cluster } from "../../../../main/cluster"; import { Cluster } from "../../../../main/cluster";
import { Input } from "../../input"; import { Input } from "../../input";
import { SubTitle } from "../../layout/sub-title"; import { SubTitle } from "../../layout/sub-title";
@ -11,7 +11,15 @@ interface Props {
@observer @observer
export class ClusterHomeDirSetting extends React.Component<Props> { export class ClusterHomeDirSetting extends React.Component<Props> {
@observable directory = this.props.cluster.preferences.terminalCWD || ""; @observable directory = "";
componentDidMount() {
disposeOnUnmount(this,
autorun(() => {
this.directory = this.props.cluster.preferences.terminalCWD || "";
})
);
}
save = () => { save = () => {
this.props.cluster.preferences.terminalCWD = this.directory; this.props.cluster.preferences.terminalCWD = this.directory;

View File

@ -1,8 +1,8 @@
import React from "react"; import React from "react";
import { Cluster } from "../../../../main/cluster"; import { Cluster } from "../../../../main/cluster";
import { Input } from "../../input"; import { Input } from "../../input";
import { observable } from "mobx"; import { observable, autorun } from "mobx";
import { observer } from "mobx-react"; import { observer, disposeOnUnmount } from "mobx-react";
import { SubTitle } from "../../layout/sub-title"; import { SubTitle } from "../../layout/sub-title";
import { isRequired } from "../../input/input.validators"; import { isRequired } from "../../input/input.validators";
@ -12,7 +12,15 @@ interface Props {
@observer @observer
export class ClusterNameSetting extends React.Component<Props> { export class ClusterNameSetting extends React.Component<Props> {
@observable name = this.props.cluster.preferences.clusterName || ""; @observable name = "";
componentDidMount() {
disposeOnUnmount(this,
autorun(() => {
this.name = this.props.cluster.preferences.clusterName;
})
);
}
save = () => { save = () => {
this.props.cluster.preferences.clusterName = this.name; this.props.cluster.preferences.clusterName = this.name;

View File

@ -1,11 +1,11 @@
import React from "react"; import React from "react";
import { observer } from "mobx-react"; import { observer, disposeOnUnmount } from "mobx-react";
import { prometheusProviders } from "../../../../common/prometheus-providers"; import { prometheusProviders } from "../../../../common/prometheus-providers";
import { Cluster } from "../../../../main/cluster"; import { Cluster } from "../../../../main/cluster";
import { SubTitle } from "../../layout/sub-title"; import { SubTitle } from "../../layout/sub-title";
import { Select, SelectOption } from "../../select"; import { Select, SelectOption } from "../../select";
import { Input } from "../../input"; import { Input } from "../../input";
import { observable, computed } from "mobx"; import { observable, computed, autorun } from "mobx";
const options: SelectOption<string>[] = [ const options: SelectOption<string>[] = [
{ value: "", label: "Auto detect" }, { value: "", label: "Auto detect" },
@ -27,14 +27,22 @@ export class ClusterPrometheusSetting extends React.Component<Props> {
} }
componentDidMount() { componentDidMount() {
disposeOnUnmount(this,
autorun(() => {
const { prometheus, prometheusProvider } = this.props.cluster.preferences; const { prometheus, prometheusProvider } = this.props.cluster.preferences;
if (prometheus) { if (prometheus) {
const prefix = prometheus.prefix || ""; const prefix = prometheus.prefix || "";
this.path = `${prometheus.namespace}/${prometheus.service}:${prometheus.port}${prefix}`; this.path = `${prometheus.namespace}/${prometheus.service}:${prometheus.port}${prefix}`;
} else {
this.path = "";
} }
if (prometheusProvider) { if (prometheusProvider) {
this.provider = prometheusProvider.type; this.provider = prometheusProvider.type;
} else {
this.provider = "";
} }
})
);
} }
parsePrometheusPath = () => { parsePrometheusPath = () => {

View File

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { observable } from "mobx"; import { observable, autorun } from "mobx";
import { observer } from "mobx-react"; import { observer, disposeOnUnmount } from "mobx-react";
import { Cluster } from "../../../../main/cluster"; import { Cluster } from "../../../../main/cluster";
import { Input } from "../../input"; import { Input } from "../../input";
import { isUrl } from "../../input/input.validators"; import { isUrl } from "../../input/input.validators";
@ -12,7 +12,15 @@ interface Props {
@observer @observer
export class ClusterProxySetting extends React.Component<Props> { export class ClusterProxySetting extends React.Component<Props> {
@observable proxy = this.props.cluster.preferences.httpsProxy || ""; @observable proxy = "";
componentDidMount() {
disposeOnUnmount(this,
autorun(() => {
this.proxy = this.props.cluster.preferences.httpsProxy || "";
})
);
}
save = () => { save = () => {
this.props.cluster.preferences.httpsProxy = this.proxy; this.props.cluster.preferences.httpsProxy = this.proxy;

View File

@ -12,6 +12,7 @@ import { Icon } from "../icon";
import { Input } from "../input"; import { Input } from "../input";
import { cssNames, prevDefault } from "../../utils"; import { cssNames, prevDefault } from "../../utils";
import { Button } from "../button"; import { Button } from "../button";
import { isRequired, Validator } from "../input/input.validators";
@observer @observer
export class Workspaces extends React.Component { export class Workspaces extends React.Component {
@ -41,9 +42,9 @@ export class Workspaces extends React.Component {
saveWorkspace = (id: WorkspaceId) => { saveWorkspace = (id: WorkspaceId) => {
const draft = toJS(this.editingWorkspaces.get(id)); const draft = toJS(this.editingWorkspaces.get(id));
if (draft) { const workspace = workspaceStore.saveWorkspace(draft);
if (workspace) {
this.clearEditing(id); this.clearEditing(id);
workspaceStore.saveWorkspace(draft);
} }
} }
@ -90,6 +91,15 @@ export class Workspaces extends React.Component {
}) })
} }
onInputKeypress = (evt: React.KeyboardEvent<any>, workspaceId: WorkspaceId) => {
if (evt.key == 'Enter') {
// Trigget input validation
evt.currentTarget.blur();
evt.currentTarget.focus();
this.saveWorkspace(workspaceId);
}
}
render() { render() {
return ( return (
<WizardLayout className="Workspaces" infoPanel={this.renderInfo()}> <WizardLayout className="Workspaces" infoPanel={this.renderInfo()}>
@ -102,11 +112,15 @@ export class Workspaces extends React.Component {
const isDefault = workspaceStore.isDefault(workspaceId); const isDefault = workspaceStore.isDefault(workspaceId);
const isEditing = this.editingWorkspaces.has(workspaceId); const isEditing = this.editingWorkspaces.has(workspaceId);
const editingWorkspace = this.editingWorkspaces.get(workspaceId); const editingWorkspace = this.editingWorkspaces.get(workspaceId);
const className = cssNames("workspace flex gaps align-center", { const className = cssNames("workspace flex gaps", {
active: isActive, active: isActive,
editing: isEditing, editing: isEditing,
default: isDefault, default: isDefault,
}); });
const existenceValidator: Validator = {
message: () => `Workspace '${name}' already exists`,
validate: value => !workspaceStore.getByName(value.trim())
}
return ( return (
<div key={workspaceId} className={className}> <div key={workspaceId} className={className}>
{!isEditing && ( {!isEditing && (
@ -139,23 +153,27 @@ export class Workspaces extends React.Component {
placeholder={_i18n._(t`Name`)} placeholder={_i18n._(t`Name`)}
value={editingWorkspace.name} value={editingWorkspace.name}
onChange={v => editingWorkspace.name = v} onChange={v => editingWorkspace.name = v}
onKeyPress={(e) => this.onInputKeypress(e, workspaceId)}
validators={[isRequired, existenceValidator]}
autoFocus
/> />
<Input <Input
className="description" className="description"
placeholder={_i18n._(t`Description`)} placeholder={_i18n._(t`Description`)}
value={editingWorkspace.description} value={editingWorkspace.description}
onChange={v => editingWorkspace.description = v} onChange={v => editingWorkspace.description = v}
/> onKeyPress={(e) => this.onInputKeypress(e, workspaceId)}
<Icon
material="cancel"
tooltip={<Trans>Cancel</Trans>}
onClick={() => this.clearEditing(workspaceId)}
/> />
<Icon <Icon
material="save" material="save"
tooltip={<Trans>Save</Trans>} tooltip={<Trans>Save</Trans>}
onClick={() => this.saveWorkspace(workspaceId)} onClick={() => this.saveWorkspace(workspaceId)}
/> />
<Icon
material="cancel"
tooltip={<Trans>Cancel</Trans>}
onClick={() => this.clearEditing(workspaceId)}
/>
</Fragment> </Fragment>
)} )}
</div> </div>

View File

@ -44,8 +44,8 @@ export class App extends React.Component {
const clusterId = getHostedClusterId(); const clusterId = getHostedClusterId();
logger.info(`[APP]: Init dashboard, clusterId=${clusterId}, frameId=${frameId}`) logger.info(`[APP]: Init dashboard, clusterId=${clusterId}, frameId=${frameId}`)
await Terminal.preloadFonts() await Terminal.preloadFonts()
await clusterIpc.initView.invokeFromRenderer(clusterId, frameId); await clusterIpc.activate.invokeFromRenderer(clusterId, frameId);
await getHostedCluster().whenInitialized; await getHostedCluster().whenReady; // cluster.refresh() is done at this point
} }
get startURL() { get startURL() {

View File

@ -1,7 +1,7 @@
import "./cluster-manager.scss" import "./cluster-manager.scss"
import React from "react"; import React from "react";
import { Redirect, Route, Switch } from "react-router"; import { Redirect, Route, Switch } from "react-router";
import { reaction } from "mobx"; import { comparer, reaction } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react"; import { disposeOnUnmount, observer } from "mobx-react";
import { ClustersMenu } from "./clusters-menu"; import { ClustersMenu } from "./clusters-menu";
import { BottomBar } from "./bottom-bar"; import { BottomBar } from "./bottom-bar";
@ -25,11 +25,14 @@ export class ClusterManager extends React.Component {
fireImmediately: true fireImmediately: true
}), }),
reaction(() => [ reaction(() => [
getMatchedClusterId(), // refresh when active cluster-view changed
hasLoadedView(getMatchedClusterId()), // refresh when cluster's webview loaded hasLoadedView(getMatchedClusterId()), // refresh when cluster's webview loaded
getMatchedCluster()?.available, // refresh on disconnect active-cluster getMatchedCluster()?.available, // refresh on disconnect active-cluster
getMatchedCluster()?.ready, // refresh when cluster ready-state change
], refreshViews, { ], refreshViews, {
fireImmediately: true fireImmediately: true,
}) equals: comparer.shallow,
}),
]) ])
} }

View File

@ -38,7 +38,7 @@ export class ClusterStatus extends React.Component<Props> {
error: res.error, error: res.error,
}); });
}) })
if (!this.cluster.initialized || this.cluster.disconnected) { if (this.cluster.disconnected) {
await this.refreshCluster(); await this.refreshCluster();
} }
} }

View File

@ -2,7 +2,7 @@ import { reaction } from "mobx";
import { ipcRenderer } from "electron"; import { ipcRenderer } from "electron";
import { matchPath, RouteProps } from "react-router"; import { matchPath, RouteProps } from "react-router";
import { buildURL, navigation } from "../../navigation"; import { buildURL, navigation } from "../../navigation";
import { clusterStore, getHostedClusterId } from "../../../common/cluster-store"; import { clusterStore } from "../../../common/cluster-store";
import { clusterSettingsRoute } from "../+cluster-settings/cluster-settings.route"; import { clusterSettingsRoute } from "../+cluster-settings/cluster-settings.route";
export interface IClusterViewRouteParams { export interface IClusterViewRouteParams {
@ -34,11 +34,10 @@ export function getMatchedCluster() {
} }
if (ipcRenderer) { if (ipcRenderer) {
// Refresh global menu depending on active route's type (common/cluster view) if (process.isMainFrame) {
const isMainView = !getHostedClusterId(); // Keep track of active cluster-id for handling IPC/menus/etc.
if (isMainView) {
reaction(() => getMatchedClusterId(), clusterId => { reaction(() => getMatchedClusterId(), clusterId => {
ipcRenderer.send("cluster-view:change", clusterId); ipcRenderer.send("cluster-view:current-id", clusterId);
}, { }, {
fireImmediately: true fireImmediately: true
}) })

View File

@ -1,5 +1,5 @@
import { observable, when } from "mobx"; import { observable, when } from "mobx";
import { ClusterId, clusterStore } from "../../../common/cluster-store"; import { ClusterId, clusterStore, getClusterFrameUrl } from "../../../common/cluster-store";
import { getMatchedCluster } from "./cluster-view.route" import { getMatchedCluster } from "./cluster-view.route"
import logger from "../../../main/logger"; import logger from "../../../main/logger";
@ -21,29 +21,38 @@ export async function initView(clusterId: ClusterId) {
} }
logger.info(`[LENS-VIEW]: init dashboard, clusterId=${clusterId}`) logger.info(`[LENS-VIEW]: init dashboard, clusterId=${clusterId}`)
const cluster = clusterStore.getById(clusterId); const cluster = clusterStore.getById(clusterId);
await cluster.whenReady;
const parentElem = document.getElementById("lens-views"); const parentElem = document.getElementById("lens-views");
const iframe = document.createElement("iframe"); const iframe = document.createElement("iframe");
iframe.name = cluster.contextName; iframe.name = cluster.contextName;
iframe.setAttribute("src", `//${clusterId}.${location.host}`) iframe.setAttribute("src", getClusterFrameUrl(clusterId))
iframe.addEventListener("load", async () => { iframe.addEventListener("load", () => {
logger.info(`[LENS-VIEW]: loaded from ${iframe.src}`) logger.info(`[LENS-VIEW]: loaded from ${iframe.src}`)
lensViews.get(clusterId).isLoaded = true; lensViews.get(clusterId).isLoaded = true;
}) }, { once: true });
lensViews.set(clusterId, { clusterId, view: iframe }); lensViews.set(clusterId, { clusterId, view: iframe });
parentElem.appendChild(iframe); parentElem.appendChild(iframe);
// auto-clean when cluster removed await autoCleanOnRemove(clusterId, iframe);
}
export async function autoCleanOnRemove(clusterId: ClusterId, iframe: HTMLIFrameElement) {
await when(() => !clusterStore.getById(clusterId)); await when(() => !clusterStore.getById(clusterId));
logger.info(`[LENS-VIEW]: remove dashboard, clusterId=${clusterId}`) logger.info(`[LENS-VIEW]: remove dashboard, clusterId=${clusterId}`)
parentElem.removeChild(iframe)
lensViews.delete(clusterId) lensViews.delete(clusterId)
// Keep frame in DOM to avoid possible bugs when same cluster re-created after being removed.
// In that case for some reasons `webFrame.routingId` returns some previous frameId (usage in app.tsx)
// Issue: https://github.com/lensapp/lens/issues/811
iframe.dataset.meta = `${iframe.name} was removed at ${new Date().toLocaleString()}`;
iframe.removeAttribute("src")
iframe.removeAttribute("name")
} }
export function refreshViews() { export function refreshViews() {
const cluster = getMatchedCluster(); const cluster = getMatchedCluster();
lensViews.forEach(({ clusterId, view, isLoaded }) => { lensViews.forEach(({ clusterId, view, isLoaded }) => {
const isVisible = cluster && cluster.available && cluster.id === clusterId; const isCurrent = clusterId === cluster?.id;
view.style.display = isLoaded && isVisible ? "flex" : "none" const isReady = cluster?.available && cluster?.ready;
const isVisible = isCurrent && isLoaded && isReady;
view.style.display = isVisible ? "flex" : "none"
}) })
} }

View File

@ -92,7 +92,8 @@ export class Dialog extends React.PureComponent<DialogProps, DialogState> {
this.props.onOpen(); this.props.onOpen();
if (!this.props.pinned) { if (!this.props.pinned) {
if (this.elem) this.elem.addEventListener('click', this.onClickOutside); if (this.elem) this.elem.addEventListener('click', this.onClickOutside);
window.addEventListener('keydown', this.onEscapeKey); // Using document.body target to handle keydown event before Drawer does
document.body.addEventListener('keydown', this.onEscapeKey);
} }
} }
@ -100,7 +101,7 @@ export class Dialog extends React.PureComponent<DialogProps, DialogState> {
this.props.onClose(); this.props.onClose();
if (!this.props.pinned) { if (!this.props.pinned) {
if (this.elem) this.elem.removeEventListener('click', this.onClickOutside); if (this.elem) this.elem.removeEventListener('click', this.onClickOutside);
window.removeEventListener('keydown', this.onEscapeKey); document.body.removeEventListener('keydown', this.onEscapeKey);
} }
} }

View File

@ -40,6 +40,7 @@ export class Drawer extends React.Component<DrawerProps> {
}); });
componentDidMount() { componentDidMount() {
// Using window target for events to make sure they will be catched after other places (e.g. Dialog)
window.addEventListener("mousedown", this.onMouseDown) window.addEventListener("mousedown", this.onMouseDown)
window.addEventListener("click", this.onClickOutside) window.addEventListener("click", this.onClickOutside)
window.addEventListener("keydown", this.onEscapeKey) window.addEventListener("keydown", this.onEscapeKey)

View File

@ -1 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><defs><style>.cls-2{fill:#3c90ce;}</style></defs><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-2" d="M256,496C123.67,496,16,388.33,16,256S123.67,16,256,16,496,123.67,496,256,388.33,496,256,496Zm0-470C129.17,26,26,129.18,26,256S129.17,486,256,486,486,382.82,486,256,382.83,26,256,26Z"/><path class="cls-2" d="M403.22,282l65.66-81.68A220.52,220.52,0,0,0,351.61,57.81Z"/><path class="cls-2" d="M476,256a220.86,220.86,0,0,0-4-41.5L334.74,385.3H434A219,219,0,0,0,476,256Z"/><path class="cls-2" d="M247.63,121.17,139.38,69.48A220,220,0,0,0,38.81,221Z"/><path class="cls-2" d="M140.75,184.81,37.07,234.35C36.38,241.48,36,248.69,36,256A219.13,219.13,0,0,0,91,401.4Z"/><path class="cls-2" d="M210.57,396.65l63.2,78.57a219.52,219.52,0,0,0,151.38-78.57Z"/><path class="cls-2" d="M124.21,307.38l-23.91,104A219.33,219.33,0,0,0,256,476c1.26,0,2.5-.07,3.76-.1Z"/><path class="cls-2" d="M364.52,164.42,338.67,52.12A220.21,220.21,0,0,0,151.16,62.54Z"/><path class="cls-2" d="M351.71,276.74c-.46-.11-1.12-.29-1.58-.37a45.28,45.28,0,0,0-5.2-.42,50.71,50.71,0,0,1-9.73-1.6,6.13,6.13,0,0,1-2.35-2.36l-2.19-.63a70.52,70.52,0,0,0-11.31-48.85l1.93-1.73a4.3,4.3,0,0,1,1-3.08,50.88,50.88,0,0,1,8.07-5.67,43.85,43.85,0,0,0,4.51-2.63c.35-.26.83-.67,1.2-1,2.6-2.08,3.2-5.66,1.33-8s-5.49-2.57-8.09-.49c-.38.29-.88.67-1.21,1a43.42,43.42,0,0,0-3.58,3.79,51.27,51.27,0,0,1-7.32,6.63,6.1,6.1,0,0,1-3.3.36l-2.06,1.47a71.09,71.09,0,0,0-45.06-21.77c-.05-.72-.11-2-.12-2.42a4.33,4.33,0,0,1-1.78-2.72,50.16,50.16,0,0,1,.62-9.84,45.29,45.29,0,0,0,.74-5.17c0-.43,0-1.07,0-1.54,0-3.33-2.43-6-5.43-6s-5.43,2.7-5.43,6c0,.05,0,.1,0,.15,0,.45,0,1,0,1.39a45.29,45.29,0,0,0,.74,5.17,50.87,50.87,0,0,1,.61,9.84,5.9,5.9,0,0,1-1.77,2.81l-.12,2.29a71.38,71.38,0,0,0-9.82,1.51,69.83,69.83,0,0,0-35.46,20.26c-.6-.41-1.65-1.16-2-1.39a4.32,4.32,0,0,1-3.23-.31,50.46,50.46,0,0,1-7.31-6.61,45,45,0,0,0-3.58-3.79c-.33-.29-.83-.67-1.2-1a6.42,6.42,0,0,0-3.78-1.42,5.22,5.22,0,0,0-4.33,1.91c-1.87,2.34-1.27,5.93,1.33,8l.09.06c.35.29.79.66,1.12.91a46.81,46.81,0,0,0,4.5,2.63,49.61,49.61,0,0,1,8.07,5.67,6,6,0,0,1,1.09,3.13l1.74,1.55a70.25,70.25,0,0,0-11.07,49l-2.28.66a7.61,7.61,0,0,1-2.33,2.36,51.5,51.5,0,0,1-9.73,1.6,45.38,45.38,0,0,0-5.21.41c-.41.08-1,.23-1.45.34h0l-.08,0c-3.2.78-5.26,3.72-4.6,6.61a5.75,5.75,0,0,0,7,4h.08l.1,0c.45-.1,1-.21,1.41-.32a45.65,45.65,0,0,0,4.87-1.87,51.49,51.49,0,0,1,9.46-2.78,6.07,6.07,0,0,1,3.12,1.1l2.37-.4A70.59,70.59,0,0,0,225,322.21l-1,2.37a5.46,5.46,0,0,1,.48,3.07,53.5,53.5,0,0,1-4.92,8.83,46,46,0,0,0-2.91,4.33c-.21.41-.48,1-.69,1.47a5.73,5.73,0,0,0,2.31,7.71c2.69,1.3,6-.07,7.49-3.06v0h0c.21-.42.5-1,.67-1.38a45,45,0,0,0,1.57-5c1.43-3.61,2.22-7.4,4.2-9.75a4.31,4.31,0,0,1,2.34-1.14l1.23-2.23a70.15,70.15,0,0,0,40.78,2.93,71.11,71.11,0,0,0,9.31-2.8l1.16,2.09a4.26,4.26,0,0,1,2.77,1.68,50.16,50.16,0,0,1,3.72,9.12,44.6,44.6,0,0,0,1.58,5c.18.4.47,1,.67,1.39,1.45,3,4.81,4.38,7.51,3.07s3.7-4.72,2.3-7.71c-.2-.43-.49-1.05-.7-1.46A45.8,45.8,0,0,0,302,336.4a51.18,51.18,0,0,1-4.82-8.62,4.27,4.27,0,0,1,.42-3.2,20.73,20.73,0,0,1-.9-2.19A70.66,70.66,0,0,0,328,283c.7.11,1.92.33,2.32.41a4.28,4.28,0,0,1,3-1.13,50.68,50.68,0,0,1,9.47,2.79,45.66,45.66,0,0,0,4.87,1.88c.39.1,1,.2,1.4.3l.11,0h.08a5.72,5.72,0,0,0,7-4C357,280.45,354.91,277.51,351.71,276.74Zm-51.57-55.29-23.2,16.45-.08,0a4.79,4.79,0,0,1-6.56-.88,4.71,4.71,0,0,1-1.05-2.77h0l-1.61-28.43A56.44,56.44,0,0,1,300.14,221.45Zm-43.7,31.16h8.73l5.43,6.79-2,8.46-7.84,3.77L253,267.85,251,259.39Zm-8.22-45.84a56.58,56.58,0,0,1,5.79-1l-1.61,28.47-.12.06a4.79,4.79,0,0,1-7.59,3.67l-.05,0-23.35-16.55A55.92,55.92,0,0,1,248.22,206.77Zm-35.39,25.3,21.32,19.07,0,.12a4.79,4.79,0,0,1-1.88,8.22l0,.09-27.32,7.89A55.83,55.83,0,0,1,212.83,232.07Zm28.54,50.65L230.51,309A56.27,56.27,0,0,1,208,280.73L236,276l0,.06A4.37,4.37,0,0,1,237,276a4.78,4.78,0,0,1,4.33,6.67Zm32,33.74a56.07,56.07,0,0,1-30.62-1.58L256.56,290h0a4.77,4.77,0,0,1,4-2.53A4.86,4.86,0,0,1,265,290h.1l13.82,25Q276.24,315.82,273.41,316.46Zm17.81-7.4-11-26.5,0-.05a4.78,4.78,0,0,1,2.32-6.2,4.65,4.65,0,0,1,1.83-.48,5.05,5.05,0,0,1,1.1.08l.05-.05,28.26,4.77A56,56,0,0,1,291.22,309.06Zm25.59-41.69-27.46-7.91,0-.12a4.79,4.79,0,0,1-3.4-5.68,4.7,4.7,0,0,1,1.52-2.54v-.06l21.2-19a56.92,56.92,0,0,1,8.17,35.28Z"/></g></g></svg> <?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<style type="text/css">
.rect{fill:none!important;}
</style>
<rect class="rect" width="512" height="512"/>
<g>
<path d="M237,496h184.8l-42.6-181.7L237,496z"/>
<path d="M16,392.3V496h194.2l81.2-103.7H16z"/>
<path d="M280.1,130.3L496,235.4v-209L280.1,130.3z"/>
<path d="M443.6,496H496V258.8l-122-59.4L443.6,496z"/>
<path d="M469,16H200.1l-32.4,145L469,16z"/>
<path d="M16,170v201.1h160L16,170z"/>
<path d="M178.5,16H16v120.1l105.9,133.1L178.5,16z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 730 B

View File

@ -40,18 +40,19 @@
div.logo-text { div.logo-text {
position: absolute; position: absolute;
left: 42px; left: 42px;
top: 11.5px; top: 11px;
} }
.logo-icon { .logo-icon {
width: 28px; width: 28px;
height: 28px; height: 28px;
margin-left: 2px; margin-left: 2px;
margin-top: 3px; margin-top: 2px;
margin-right: 10px; margin-right: 10px;
svg { svg {
--size: 28px; --size: 28px;
padding: 2px;
} }
} }

View File

@ -2,7 +2,12 @@
Here you can find description of changes we've built into each release. While we try our best to make each upgrade automatic and as smooth as possible, there may be some cases where you might need to do something to ensure the application works smoothly. So please read through the release highlights! Here you can find description of changes we've built into each release. While we try our best to make each upgrade automatic and as smooth as possible, there may be some cases where you might need to do something to ensure the application works smoothly. So please read through the release highlights!
## 3.6.0-rc.1 (current version) ## 3.6.0-rc.2 (current version)
- Refresh input values on cluster change
- Update logo
- Fix margins in cluster menu
## 3.6.0-rc.1
- Allow user to configure directory where Kubectl binaries are downloaded - Allow user to configure directory where Kubectl binaries are downloaded
- Allow user to configure path to Kubectl binary, instead of using bundled Kubectl - Allow user to configure path to Kubectl binary, instead of using bundled Kubectl
- Log application logs also to log file - Log application logs also to log file

View File

@ -3,6 +3,7 @@ import webpack from "webpack";
import ForkTsCheckerPlugin from "fork-ts-checker-webpack-plugin" import ForkTsCheckerPlugin from "fork-ts-checker-webpack-plugin"
import { isDevelopment, isProduction, mainDir, buildDir } from "./src/common/vars"; import { isDevelopment, isProduction, mainDir, buildDir } from "./src/common/vars";
import nodeExternals from "webpack-node-externals"; import nodeExternals from "webpack-node-externals";
import ProgressBarPlugin from "progress-bar-webpack-plugin";
export default function (): webpack.Configuration { export default function (): webpack.Configuration {
console.info('WEBPACK:main', require("./src/common/vars")) console.info('WEBPACK:main', require("./src/common/vars"))
@ -48,6 +49,7 @@ export default function (): webpack.Configuration {
] ]
}, },
plugins: [ plugins: [
new ProgressBarPlugin(),
new ForkTsCheckerPlugin(), new ForkTsCheckerPlugin(),
] ]
} }

View File

@ -5,6 +5,7 @@ import HtmlWebpackPlugin from "html-webpack-plugin";
import MiniCssExtractPlugin from "mini-css-extract-plugin"; import MiniCssExtractPlugin from "mini-css-extract-plugin";
import TerserPlugin from "terser-webpack-plugin"; import TerserPlugin from "terser-webpack-plugin";
import ForkTsCheckerPlugin from "fork-ts-checker-webpack-plugin" import ForkTsCheckerPlugin from "fork-ts-checker-webpack-plugin"
import ProgressBarPlugin from "progress-bar-webpack-plugin";
export default [ export default [
webpackLensRenderer, webpackLensRenderer,
@ -154,6 +155,7 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura
}, },
plugins: [ plugins: [
new ProgressBarPlugin(),
new ForkTsCheckerPlugin(), new ForkTsCheckerPlugin(),
// todo: fix remain warnings about circular dependencies // todo: fix remain warnings about circular dependencies

View File

@ -2011,6 +2011,21 @@
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.0.1.tgz#b6e98083f13faa1e5231bfa3bdb1b0feff536b6d" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.0.1.tgz#b6e98083f13faa1e5231bfa3bdb1b0feff536b6d"
integrity sha512-boy4xPNEtiw6N3abRhBi/e7hNvy3Tt8E9ZRAQrwAGzoCGZS/1wjo9KY7JHhnfnEsG5wSjDbymCozUM9a3ea7OQ== integrity sha512-boy4xPNEtiw6N3abRhBi/e7hNvy3Tt8E9ZRAQrwAGzoCGZS/1wjo9KY7JHhnfnEsG5wSjDbymCozUM9a3ea7OQ==
"@types/progress-bar-webpack-plugin@^2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@types/progress-bar-webpack-plugin/-/progress-bar-webpack-plugin-2.1.0.tgz#f95b7e1199b5f5e8156321d2411f6fb06c833559"
integrity sha512-HCyeEuuFzsXvIkbchGKJUhXRBGio7BlvVn0ULuBRE50UofuFy2Q8HvR+Q8C2w+QlBUoM+AjerGJU8DhyqYVw7g==
dependencies:
"@types/progress" "*"
"@types/webpack" "*"
"@types/progress@*":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@types/progress/-/progress-2.0.3.tgz#7ccbd9c6d4d601319126c469e73b5bb90dfc8ccc"
integrity sha512-bPOsfCZ4tsTlKiBjBhKnM8jpY5nmIll166IPD58D92hR7G7kZDfx5iB9wGF4NfZrdKolebjeAr3GouYkSGoJ/A==
dependencies:
"@types/node" "*"
"@types/prop-types@*": "@types/prop-types@*":
version "15.7.3" version "15.7.3"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
@ -7801,14 +7816,6 @@ make-plural@^6.2.1:
resolved "https://registry.yarnpkg.com/make-plural/-/make-plural-6.2.1.tgz#2790af1d05fb2fc35a111ce759ffdb0aca1339a3" resolved "https://registry.yarnpkg.com/make-plural/-/make-plural-6.2.1.tgz#2790af1d05fb2fc35a111ce759ffdb0aca1339a3"
integrity sha512-AmkruwJ9EjvyTv6AM8MBMK3TAeOJvhgTv5YQXzF0EP2qawhpvMjDpHvsdOIIT0Vn+BB0+IogmYZ1z+Ulm/m0Fg== integrity sha512-AmkruwJ9EjvyTv6AM8MBMK3TAeOJvhgTv5YQXzF0EP2qawhpvMjDpHvsdOIIT0Vn+BB0+IogmYZ1z+Ulm/m0Fg==
make-synchronous@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/make-synchronous/-/make-synchronous-0.1.1.tgz#0169f6ec769c3cf8948d66790da262740c1209e7"
integrity sha512-Y4SxxqhaoyMDokJQ0AZz0E+bLhRkOSR7Z/IQoTKPdS6HYi3aobal2kMHoHHoqBadPWjf07P4K1FQLXOx3wf9Yw==
dependencies:
subsume "^3.0.0"
type-fest "^0.16.0"
makeerror@1.0.x: makeerror@1.0.x:
version "1.0.11" version "1.0.11"
resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c"
@ -9351,6 +9358,14 @@ process@^0.11.10:
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI=
progress-bar-webpack-plugin@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/progress-bar-webpack-plugin/-/progress-bar-webpack-plugin-2.1.0.tgz#f7f8c8c461f40b87a8ff168443f494289b07ee65"
integrity sha512-UtlZbnxpYk1wufEWfhIjRn2U52zlY38uvnzFhs8rRxJxC1hSqw88JNR2Mbpqq9Kix8L1nGb3uQ+/1BiUWbigAg==
dependencies:
chalk "^3.0.0"
progress "^2.0.3"
progress@^2.0.0, progress@^2.0.3: progress@^2.0.0, progress@^2.0.3:
version "2.0.3" version "2.0.3"
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
@ -10916,14 +10931,6 @@ style-loader@^1.2.1:
loader-utils "^2.0.0" loader-utils "^2.0.0"
schema-utils "^2.6.6" schema-utils "^2.6.6"
subsume@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/subsume/-/subsume-3.0.0.tgz#22c92730f441ad72ee9af4bdad42dc4ff830cfaf"
integrity sha512-6n/UfV8UWKwJNO8OAOiKntwEMihuBeeoJfzpL542C+OuvT4iWG9SwjrXkOmsxjb4SteHUsos9SvrdqZ9+ICwTQ==
dependencies:
escape-string-regexp "^2.0.0"
unique-string "^2.0.0"
sumchecker@^3.0.1: sumchecker@^3.0.1:
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-3.0.1.tgz#6377e996795abb0b6d348e9b3e1dfb24345a8e42" resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-3.0.1.tgz#6377e996795abb0b6d348e9b3e1dfb24345a8e42"
@ -11432,11 +11439,6 @@ type-fest@^0.13.1:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934"
integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg== integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==
type-fest@^0.16.0:
version "0.16.0"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.16.0.tgz#3240b891a78b0deae910dbeb86553e552a148860"
integrity sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==
type-fest@^0.6.0: type-fest@^0.6.0:
version "0.6.0" version "0.6.0"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b"