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
1
Makefile
@ -47,7 +47,6 @@ test-app:
|
||||
yarn test
|
||||
|
||||
build: install-deps download-bins
|
||||
yarn install
|
||||
ifeq "$(DETECTED_OS)" "Windows"
|
||||
yarn dist:win
|
||||
else
|
||||
|
BIN
build/icon.ico
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 169 KiB |
BIN
build/icon.png
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 11 KiB |
@ -2,7 +2,7 @@
|
||||
"name": "kontena-lens",
|
||||
"productName": "Lens",
|
||||
"description": "Lens - The Kubernetes IDE",
|
||||
"version": "3.6.0-rc.1",
|
||||
"version": "3.6.0-rc.2",
|
||||
"main": "static/build/main.js",
|
||||
"copyright": "© 2020, Mirantis, Inc.",
|
||||
"license": "MIT",
|
||||
@ -196,7 +196,6 @@
|
||||
"jsonpath": "^1.0.2",
|
||||
"lodash": "^4.17.15",
|
||||
"mac-ca": "^1.0.4",
|
||||
"make-synchronous": "^0.1.1",
|
||||
"marked": "^1.1.0",
|
||||
"md5-file": "^5.0.0",
|
||||
"mobx": "^5.15.5",
|
||||
@ -250,6 +249,7 @@
|
||||
"@types/material-ui": "^0.21.7",
|
||||
"@types/md5-file": "^4.0.2",
|
||||
"@types/mini-css-extract-plugin": "^0.9.1",
|
||||
"@types/progress-bar-webpack-plugin": "^2.1.0",
|
||||
"@types/react": "^16.9.35",
|
||||
"@types/react-router-dom": "^5.1.5",
|
||||
"@types/react-select": "^3.0.13",
|
||||
@ -305,6 +305,7 @@
|
||||
"nodemon": "^2.0.4",
|
||||
"patch-package": "^6.2.2",
|
||||
"postinstall-postinstall": "^2.1.0",
|
||||
"progress-bar-webpack-plugin": "^2.1.0",
|
||||
"raw-loader": "^4.0.1",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
|
@ -3,21 +3,14 @@ import { ClusterId, clusterStore } from "./cluster-store";
|
||||
import { tracker } from "./tracker";
|
||||
|
||||
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({
|
||||
channel: "cluster:activate",
|
||||
handle: (clusterId: ClusterId) => {
|
||||
return clusterStore.getById(clusterId)?.activate();
|
||||
handle: (clusterId: ClusterId, frameId?: number) => {
|
||||
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);
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
|
@ -209,11 +209,17 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
|
||||
export const clusterStore = ClusterStore.getInstance<ClusterStore>();
|
||||
|
||||
export function getHostedClusterId(): ClusterId {
|
||||
const clusterHost = location.hostname.match(/^(.*?)\.localhost/);
|
||||
if (clusterHost) {
|
||||
return clusterHost[1]
|
||||
}
|
||||
export function getClusterIdFromHost(hostname: string): ClusterId {
|
||||
const subDomains = hostname.split(":")[0].split(".");
|
||||
return subDomains.slice(-2)[0]; // e.g host == "%clusterId.localhost:45345"
|
||||
}
|
||||
|
||||
export function getClusterFrameUrl(clusterId: ClusterId) {
|
||||
return `//${clusterId}.${location.host}`;
|
||||
}
|
||||
|
||||
export function getHostedClusterId() {
|
||||
return getClusterIdFromHost(location.hostname);
|
||||
}
|
||||
|
||||
export function getHostedCluster(): Cluster {
|
||||
|
@ -31,9 +31,10 @@ describe("empty config", () => {
|
||||
it("adds new cluster to store", async () => {
|
||||
const cluster = new Cluster({
|
||||
id: "foo",
|
||||
contextName: "minikube",
|
||||
preferences: {
|
||||
terminalCWD: "/tmp",
|
||||
icon: "data:image/jpeg;base64, iVBORw0KGgoAAAANSUhEUgAAA1wAAAKoCAYAAABjkf5",
|
||||
icon: "data:;base64,iVBORw0KGgoAAAANSUhEUgAAA1wAAAKoCAYAAABjkf5",
|
||||
clusterName: "minikube"
|
||||
},
|
||||
kubeConfigPath: ClusterStore.embedCustomKubeConfig("foo", "fancy foo config"),
|
||||
@ -54,6 +55,7 @@ describe("empty config", () => {
|
||||
it("check if store can contain multiple clusters", () => {
|
||||
const prodCluster = new Cluster({
|
||||
id: "prod",
|
||||
contextName: "prod",
|
||||
preferences: {
|
||||
clusterName: "prod"
|
||||
},
|
||||
@ -62,6 +64,7 @@ describe("empty config", () => {
|
||||
});
|
||||
const devCluster = new Cluster({
|
||||
id: "dev",
|
||||
contextName: "dev",
|
||||
preferences: {
|
||||
clusterName: "dev"
|
||||
},
|
||||
@ -142,11 +145,13 @@ describe("config with existing clusters", () => {
|
||||
{
|
||||
id: 'cluster1',
|
||||
kubeConfig: 'foo',
|
||||
contextName: 'foo',
|
||||
preferences: { terminalCWD: '/foo' }
|
||||
},
|
||||
{
|
||||
id: 'cluster2',
|
||||
kubeConfig: 'foo2',
|
||||
contextName: 'foo2',
|
||||
preferences: { terminalCWD: '/foo2' }
|
||||
}
|
||||
]
|
||||
@ -285,7 +290,7 @@ describe("pre 2.6.0 config with a cluster icon", () => {
|
||||
const storedClusterData = clusterStore.clustersList[0];
|
||||
expect(storedClusterData.hasOwnProperty('icon')).toBe(false);
|
||||
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',
|
||||
kubeConfig: 'kubeconfig content',
|
||||
contextName: 'cluster',
|
||||
preferences: {
|
||||
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 () => {
|
||||
const { icon } = clusterStore.clustersList[0].preferences;
|
||||
expect(icon.startsWith("data:image/jpeg;base64, ")).toBe(true);
|
||||
expect(icon.startsWith("data:;base64,")).toBe(true);
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -51,6 +51,10 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
||||
return this.workspaces.get(id);
|
||||
}
|
||||
|
||||
getByName(name: string): Workspace {
|
||||
return this.workspacesList.find(workspace => workspace.name === name);
|
||||
}
|
||||
|
||||
@action
|
||||
setActive(id = WorkspaceStore.defaultId, { redirectToLanding = true, resetActiveCluster = true } = {}) {
|
||||
if (id === this.currentWorkspaceId) return;
|
||||
@ -68,13 +72,16 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
||||
|
||||
@action
|
||||
saveWorkspace(workspace: Workspace) {
|
||||
const id = workspace.id;
|
||||
const { id, name } = workspace;
|
||||
const existingWorkspace = this.getById(id);
|
||||
if (!name.trim() || this.getByName(name.trim())) {
|
||||
return;
|
||||
}
|
||||
if (existingWorkspace) {
|
||||
Object.assign(existingWorkspace, workspace);
|
||||
} else {
|
||||
this.workspaces.set(id, workspace);
|
||||
}
|
||||
this.workspaces.set(id, workspace);
|
||||
return workspace;
|
||||
}
|
||||
|
||||
@action
|
||||
|
@ -92,6 +92,50 @@ describe("workspace store tests", () => {
|
||||
|
||||
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", () => {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import "../common/cluster-ipc";
|
||||
import type http from "http"
|
||||
import { autorun } from "mobx";
|
||||
import { ClusterId, clusterStore } from "../common/cluster-store"
|
||||
import { clusterStore, getClusterIdFromHost } from "../common/cluster-store"
|
||||
import { Cluster } from "./cluster"
|
||||
import logger from "./logger";
|
||||
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 {
|
||||
let cluster: Cluster = null
|
||||
|
||||
// lens-server is connecting to 127.0.0.1:<port>/<uid>
|
||||
if (req.headers.host.startsWith("127.0.0.1")) {
|
||||
const clusterId = req.url.split("/")[1]
|
||||
if (clusterId) {
|
||||
cluster = this.getCluster(clusterId)
|
||||
if (cluster) {
|
||||
// we need to swap path prefix so that request is proxied to kube api
|
||||
req.url = req.url.replace(`/${clusterId}`, apiKubePrefix)
|
||||
}
|
||||
const cluster = clusterStore.getById(clusterId)
|
||||
if (cluster) {
|
||||
// we need to swap path prefix so that request is proxied to kube api
|
||||
req.url = req.url.replace(`/${clusterId}`, apiKubePrefix)
|
||||
}
|
||||
} else {
|
||||
const id = req.headers.host.split(".")[0]
|
||||
cluster = this.getCluster(id)
|
||||
const clusterId = getClusterIdFromHost(req.headers.host);
|
||||
cluster = clusterStore.getById(clusterId)
|
||||
}
|
||||
|
||||
return cluster;
|
||||
|
@ -2,7 +2,7 @@ import type { ClusterId, ClusterModel, ClusterPreferences } from "../common/clus
|
||||
import type { IMetricsReqParams } from "../renderer/api/endpoints/metrics.api";
|
||||
import type { WorkspaceId } from "../common/workspace-store";
|
||||
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 { broadcastIpc } from "../common/ipc";
|
||||
import { ContextHandler } from "./context-handler"
|
||||
@ -77,13 +77,15 @@ export class Cluster implements ClusterModel {
|
||||
|
||||
constructor(model: ClusterModel) {
|
||||
this.updateModel(model);
|
||||
const kubeconfig = this.getKubeconfig()
|
||||
if (kubeconfig.getContextObject(this.contextName)) {
|
||||
this.apiUrl = kubeconfig.getCluster(kubeconfig.getContextObject(this.contextName).cluster).server
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
updateModel(model: ClusterModel) {
|
||||
Object.assign(this, model);
|
||||
this.apiUrl = this.getKubeconfig().getCurrentCluster()?.server;
|
||||
this.contextName = this.contextName || this.preferences.clusterName;
|
||||
}
|
||||
|
||||
@action
|
||||
@ -124,13 +126,13 @@ export class Cluster implements ClusterModel {
|
||||
this.eventDisposers.length = 0;
|
||||
}
|
||||
|
||||
async activate() {
|
||||
async activate(init = false) {
|
||||
logger.info(`[CLUSTER]: activate`, this.getMeta());
|
||||
await this.whenInitialized;
|
||||
if (!this.eventDisposers.length) {
|
||||
this.bindEvents();
|
||||
}
|
||||
if (this.disconnected || !this.accessible) {
|
||||
if (this.disconnected || (!init && !this.accessible)) {
|
||||
await this.reconnect();
|
||||
}
|
||||
await this.refresh();
|
||||
@ -409,6 +411,7 @@ export class Cluster implements ClusterModel {
|
||||
id: this.id,
|
||||
name: this.contextName,
|
||||
initialized: this.initialized,
|
||||
ready: this.ready,
|
||||
online: this.online,
|
||||
accessible: this.accessible,
|
||||
disconnected: this.disconnected,
|
||||
|
@ -1,9 +1,9 @@
|
||||
import type { ClusterId } from "../common/cluster-store";
|
||||
import { clusterStore } from "../common/cluster-store";
|
||||
import { BrowserWindow, dialog, ipcMain, shell, webContents } from "electron"
|
||||
import windowStateKeeper from "electron-window-state"
|
||||
import { observable } from "mobx";
|
||||
import { initMenu } from "./menu";
|
||||
import type { ClusterId } from "../common/cluster-store";
|
||||
|
||||
export class WindowManager {
|
||||
protected mainView: BrowserWindow;
|
||||
@ -42,7 +42,7 @@ export class WindowManager {
|
||||
});
|
||||
|
||||
// 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;
|
||||
});
|
||||
|
||||
|
@ -7,11 +7,6 @@ import { migration } from "../migration-wrapper";
|
||||
import fse from "fs-extra"
|
||||
import { ClusterModel, ClusterStore } from "../../common/cluster-store";
|
||||
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({
|
||||
version: "3.6.0-beta.1",
|
||||
@ -48,13 +43,8 @@ export default migration({
|
||||
printLog(`migrating ${cluster.preferences.icon} for ${cluster.preferences.clusterName}`)
|
||||
const iconPath = cluster.preferences.icon.replace("store://", "")
|
||||
const fileData = fse.readFileSync(path.join(userDataPath, iconPath));
|
||||
const { mime = "" } = makeSynchronous(getFileType)(fileData);
|
||||
|
||||
if (!mime) {
|
||||
printLog(`mime type not detected for ${cluster.preferences.clusterName}'s icon: ${iconPath}`)
|
||||
}
|
||||
|
||||
cluster.preferences.icon = `data:${mime};base64, ${fileData.toString('base64')}`;
|
||||
cluster.preferences.icon = `data:;base64,${fileData.toString('base64')}`;
|
||||
} else {
|
||||
delete cluster.preferences?.icon;
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import { isMac } from "../common/vars";
|
||||
import { userStore } from "../common/user-store";
|
||||
import { workspaceStore } from "../common/workspace-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 { themeStore } from "./theme.store";
|
||||
import { App } from "./components/app";
|
||||
@ -39,4 +39,4 @@ export async function bootstrap(App: AppComponent) {
|
||||
}
|
||||
|
||||
// run
|
||||
bootstrap(getHostedClusterId() ? App : LensApp);
|
||||
bootstrap(process.isMainFrame ? LensApp : App);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { observable, autorun } from "mobx";
|
||||
import { observer, disposeOnUnmount } from "mobx-react";
|
||||
import { Cluster } from "../../../../main/cluster";
|
||||
import { Input } from "../../input";
|
||||
import { SubTitle } from "../../layout/sub-title";
|
||||
@ -11,7 +11,15 @@ interface Props {
|
||||
|
||||
@observer
|
||||
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 = () => {
|
||||
this.props.cluster.preferences.terminalCWD = this.directory;
|
||||
|
@ -28,7 +28,7 @@ export class ClusterIconSetting extends React.Component<Props> {
|
||||
try {
|
||||
if (file) {
|
||||
const buf = Buffer.from(await file.arrayBuffer());
|
||||
cluster.preferences.icon = `data:${file.type};base64, ${buf.toString('base64')}`;
|
||||
cluster.preferences.icon = `data:${file.type};base64,${buf.toString('base64')}`;
|
||||
} else {
|
||||
// this has to be done as a seperate branch (and not always) because `cluster`
|
||||
// is observable and triggers an update loop.
|
||||
@ -73,4 +73,4 @@ export class ClusterIconSetting extends React.Component<Props> {
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React from "react";
|
||||
import { Cluster } from "../../../../main/cluster";
|
||||
import { Input } from "../../input";
|
||||
import { observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { observable, autorun } from "mobx";
|
||||
import { observer, disposeOnUnmount } from "mobx-react";
|
||||
import { SubTitle } from "../../layout/sub-title";
|
||||
import { isRequired } from "../../input/input.validators";
|
||||
|
||||
@ -12,7 +12,15 @@ interface Props {
|
||||
|
||||
@observer
|
||||
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 = () => {
|
||||
this.props.cluster.preferences.clusterName = this.name;
|
||||
|
@ -1,11 +1,11 @@
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { observer, disposeOnUnmount } from "mobx-react";
|
||||
import { prometheusProviders } from "../../../../common/prometheus-providers";
|
||||
import { Cluster } from "../../../../main/cluster";
|
||||
import { SubTitle } from "../../layout/sub-title";
|
||||
import { Select, SelectOption } from "../../select";
|
||||
import { Input } from "../../input";
|
||||
import { observable, computed } from "mobx";
|
||||
import { observable, computed, autorun } from "mobx";
|
||||
|
||||
const options: SelectOption<string>[] = [
|
||||
{ value: "", label: "Auto detect" },
|
||||
@ -27,14 +27,22 @@ export class ClusterPrometheusSetting extends React.Component<Props> {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { prometheus, prometheusProvider } = this.props.cluster.preferences;
|
||||
if (prometheus) {
|
||||
const prefix = prometheus.prefix || "";
|
||||
this.path = `${prometheus.namespace}/${prometheus.service}:${prometheus.port}${prefix}`;
|
||||
}
|
||||
if (prometheusProvider) {
|
||||
this.provider = prometheusProvider.type;
|
||||
}
|
||||
disposeOnUnmount(this,
|
||||
autorun(() => {
|
||||
const { prometheus, prometheusProvider } = this.props.cluster.preferences;
|
||||
if (prometheus) {
|
||||
const prefix = prometheus.prefix || "";
|
||||
this.path = `${prometheus.namespace}/${prometheus.service}:${prometheus.port}${prefix}`;
|
||||
} else {
|
||||
this.path = "";
|
||||
}
|
||||
if (prometheusProvider) {
|
||||
this.provider = prometheusProvider.type;
|
||||
} else {
|
||||
this.provider = "";
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
parsePrometheusPath = () => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { observable, autorun } from "mobx";
|
||||
import { observer, disposeOnUnmount } from "mobx-react";
|
||||
import { Cluster } from "../../../../main/cluster";
|
||||
import { Input } from "../../input";
|
||||
import { isUrl } from "../../input/input.validators";
|
||||
@ -12,7 +12,15 @@ interface Props {
|
||||
|
||||
@observer
|
||||
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 = () => {
|
||||
this.props.cluster.preferences.httpsProxy = this.proxy;
|
||||
|
@ -12,6 +12,7 @@ import { Icon } from "../icon";
|
||||
import { Input } from "../input";
|
||||
import { cssNames, prevDefault } from "../../utils";
|
||||
import { Button } from "../button";
|
||||
import { isRequired, Validator } from "../input/input.validators";
|
||||
|
||||
@observer
|
||||
export class Workspaces extends React.Component {
|
||||
@ -41,9 +42,9 @@ export class Workspaces extends React.Component {
|
||||
|
||||
saveWorkspace = (id: WorkspaceId) => {
|
||||
const draft = toJS(this.editingWorkspaces.get(id));
|
||||
if (draft) {
|
||||
const workspace = workspaceStore.saveWorkspace(draft);
|
||||
if (workspace) {
|
||||
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() {
|
||||
return (
|
||||
<WizardLayout className="Workspaces" infoPanel={this.renderInfo()}>
|
||||
@ -102,11 +112,15 @@ export class Workspaces extends React.Component {
|
||||
const isDefault = workspaceStore.isDefault(workspaceId);
|
||||
const isEditing = this.editingWorkspaces.has(workspaceId);
|
||||
const editingWorkspace = this.editingWorkspaces.get(workspaceId);
|
||||
const className = cssNames("workspace flex gaps align-center", {
|
||||
const className = cssNames("workspace flex gaps", {
|
||||
active: isActive,
|
||||
editing: isEditing,
|
||||
default: isDefault,
|
||||
});
|
||||
const existenceValidator: Validator = {
|
||||
message: () => `Workspace '${name}' already exists`,
|
||||
validate: value => !workspaceStore.getByName(value.trim())
|
||||
}
|
||||
return (
|
||||
<div key={workspaceId} className={className}>
|
||||
{!isEditing && (
|
||||
@ -139,23 +153,27 @@ export class Workspaces extends React.Component {
|
||||
placeholder={_i18n._(t`Name`)}
|
||||
value={editingWorkspace.name}
|
||||
onChange={v => editingWorkspace.name = v}
|
||||
onKeyPress={(e) => this.onInputKeypress(e, workspaceId)}
|
||||
validators={[isRequired, existenceValidator]}
|
||||
autoFocus
|
||||
/>
|
||||
<Input
|
||||
className="description"
|
||||
placeholder={_i18n._(t`Description`)}
|
||||
value={editingWorkspace.description}
|
||||
onChange={v => editingWorkspace.description = v}
|
||||
/>
|
||||
<Icon
|
||||
material="cancel"
|
||||
tooltip={<Trans>Cancel</Trans>}
|
||||
onClick={() => this.clearEditing(workspaceId)}
|
||||
onKeyPress={(e) => this.onInputKeypress(e, workspaceId)}
|
||||
/>
|
||||
<Icon
|
||||
material="save"
|
||||
tooltip={<Trans>Save</Trans>}
|
||||
onClick={() => this.saveWorkspace(workspaceId)}
|
||||
/>
|
||||
<Icon
|
||||
material="cancel"
|
||||
tooltip={<Trans>Cancel</Trans>}
|
||||
onClick={() => this.clearEditing(workspaceId)}
|
||||
/>
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
|
@ -44,8 +44,8 @@ export class App extends React.Component {
|
||||
const clusterId = getHostedClusterId();
|
||||
logger.info(`[APP]: Init dashboard, clusterId=${clusterId}, frameId=${frameId}`)
|
||||
await Terminal.preloadFonts()
|
||||
await clusterIpc.initView.invokeFromRenderer(clusterId, frameId);
|
||||
await getHostedCluster().whenInitialized;
|
||||
await clusterIpc.activate.invokeFromRenderer(clusterId, frameId);
|
||||
await getHostedCluster().whenReady; // cluster.refresh() is done at this point
|
||||
}
|
||||
|
||||
get startURL() {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import "./cluster-manager.scss"
|
||||
import React from "react";
|
||||
import { Redirect, Route, Switch } from "react-router";
|
||||
import { reaction } from "mobx";
|
||||
import { comparer, reaction } from "mobx";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import { ClustersMenu } from "./clusters-menu";
|
||||
import { BottomBar } from "./bottom-bar";
|
||||
@ -25,11 +25,14 @@ export class ClusterManager extends React.Component {
|
||||
fireImmediately: true
|
||||
}),
|
||||
reaction(() => [
|
||||
getMatchedClusterId(), // refresh when active cluster-view changed
|
||||
hasLoadedView(getMatchedClusterId()), // refresh when cluster's webview loaded
|
||||
getMatchedCluster()?.available, // refresh on disconnect active-cluster
|
||||
getMatchedCluster()?.ready, // refresh when cluster ready-state change
|
||||
], refreshViews, {
|
||||
fireImmediately: true
|
||||
})
|
||||
fireImmediately: true,
|
||||
equals: comparer.shallow,
|
||||
}),
|
||||
])
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,7 @@ export class ClusterStatus extends React.Component<Props> {
|
||||
error: res.error,
|
||||
});
|
||||
})
|
||||
if (!this.cluster.initialized || this.cluster.disconnected) {
|
||||
if (this.cluster.disconnected) {
|
||||
await this.refreshCluster();
|
||||
}
|
||||
}
|
||||
@ -63,7 +63,7 @@ export class ClusterStatus extends React.Component<Props> {
|
||||
if (!hasErrors || this.isReconnecting) {
|
||||
return (
|
||||
<>
|
||||
<CubeSpinner />
|
||||
<CubeSpinner/>
|
||||
<pre className="kube-auth-out">
|
||||
<p>{this.isReconnecting ? "Reconnecting..." : "Connecting..."}</p>
|
||||
{authOutput.map(({ data, error }, index) => {
|
||||
@ -75,7 +75,7 @@ export class ClusterStatus extends React.Component<Props> {
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Icon material="cloud_off" className="error" />
|
||||
<Icon material="cloud_off" className="error"/>
|
||||
<h2>
|
||||
{cluster.preferences.clusterName}
|
||||
</h2>
|
||||
|
@ -2,7 +2,7 @@ import { reaction } from "mobx";
|
||||
import { ipcRenderer } from "electron";
|
||||
import { matchPath, RouteProps } from "react-router";
|
||||
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";
|
||||
|
||||
export interface IClusterViewRouteParams {
|
||||
@ -34,11 +34,10 @@ export function getMatchedCluster() {
|
||||
}
|
||||
|
||||
if (ipcRenderer) {
|
||||
// Refresh global menu depending on active route's type (common/cluster view)
|
||||
const isMainView = !getHostedClusterId();
|
||||
if (isMainView) {
|
||||
if (process.isMainFrame) {
|
||||
// Keep track of active cluster-id for handling IPC/menus/etc.
|
||||
reaction(() => getMatchedClusterId(), clusterId => {
|
||||
ipcRenderer.send("cluster-view:change", clusterId);
|
||||
ipcRenderer.send("cluster-view:current-id", clusterId);
|
||||
}, {
|
||||
fireImmediately: true
|
||||
})
|
||||
|
@ -1,5 +1,5 @@
|
||||
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 logger from "../../../main/logger";
|
||||
|
||||
@ -21,29 +21,38 @@ export async function initView(clusterId: ClusterId) {
|
||||
}
|
||||
logger.info(`[LENS-VIEW]: init dashboard, clusterId=${clusterId}`)
|
||||
const cluster = clusterStore.getById(clusterId);
|
||||
await cluster.whenReady;
|
||||
const parentElem = document.getElementById("lens-views");
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.name = cluster.contextName;
|
||||
iframe.setAttribute("src", `//${clusterId}.${location.host}`)
|
||||
iframe.addEventListener("load", async () => {
|
||||
iframe.setAttribute("src", getClusterFrameUrl(clusterId))
|
||||
iframe.addEventListener("load", () => {
|
||||
logger.info(`[LENS-VIEW]: loaded from ${iframe.src}`)
|
||||
lensViews.get(clusterId).isLoaded = true;
|
||||
})
|
||||
}, { once: true });
|
||||
lensViews.set(clusterId, { clusterId, view: 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));
|
||||
logger.info(`[LENS-VIEW]: remove dashboard, clusterId=${clusterId}`)
|
||||
parentElem.removeChild(iframe)
|
||||
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() {
|
||||
const cluster = getMatchedCluster();
|
||||
lensViews.forEach(({ clusterId, view, isLoaded }) => {
|
||||
const isVisible = cluster && cluster.available && cluster.id === clusterId;
|
||||
view.style.display = isLoaded && isVisible ? "flex" : "none"
|
||||
const isCurrent = clusterId === cluster?.id;
|
||||
const isReady = cluster?.available && cluster?.ready;
|
||||
const isVisible = isCurrent && isLoaded && isReady;
|
||||
view.style.display = isVisible ? "flex" : "none"
|
||||
})
|
||||
}
|
||||
|
@ -92,7 +92,8 @@ export class Dialog extends React.PureComponent<DialogProps, DialogState> {
|
||||
this.props.onOpen();
|
||||
if (!this.props.pinned) {
|
||||
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();
|
||||
if (!this.props.pinned) {
|
||||
if (this.elem) this.elem.removeEventListener('click', this.onClickOutside);
|
||||
window.removeEventListener('keydown', this.onEscapeKey);
|
||||
document.body.removeEventListener('keydown', this.onEscapeKey);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,6 +40,7 @@ export class Drawer extends React.Component<DrawerProps> {
|
||||
});
|
||||
|
||||
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("click", this.onClickOutside)
|
||||
window.addEventListener("keydown", this.onEscapeKey)
|
||||
|
@ -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 |
@ -40,18 +40,19 @@
|
||||
div.logo-text {
|
||||
position: absolute;
|
||||
left: 42px;
|
||||
top: 11.5px;
|
||||
top: 11px;
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
margin-left: 2px;
|
||||
margin-top: 3px;
|
||||
margin-top: 2px;
|
||||
margin-right: 10px;
|
||||
|
||||
svg {
|
||||
--size: 28px;
|
||||
padding: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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!
|
||||
|
||||
## 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 path to Kubectl binary, instead of using bundled Kubectl
|
||||
- Log application logs also to log file
|
||||
|
@ -3,6 +3,7 @@ import webpack from "webpack";
|
||||
import ForkTsCheckerPlugin from "fork-ts-checker-webpack-plugin"
|
||||
import { isDevelopment, isProduction, mainDir, buildDir } from "./src/common/vars";
|
||||
import nodeExternals from "webpack-node-externals";
|
||||
import ProgressBarPlugin from "progress-bar-webpack-plugin";
|
||||
|
||||
export default function (): webpack.Configuration {
|
||||
console.info('WEBPACK:main', require("./src/common/vars"))
|
||||
@ -48,6 +49,7 @@ export default function (): webpack.Configuration {
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new ProgressBarPlugin(),
|
||||
new ForkTsCheckerPlugin(),
|
||||
]
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import HtmlWebpackPlugin from "html-webpack-plugin";
|
||||
import MiniCssExtractPlugin from "mini-css-extract-plugin";
|
||||
import TerserPlugin from "terser-webpack-plugin";
|
||||
import ForkTsCheckerPlugin from "fork-ts-checker-webpack-plugin"
|
||||
import ProgressBarPlugin from "progress-bar-webpack-plugin";
|
||||
|
||||
export default [
|
||||
webpackLensRenderer,
|
||||
@ -154,6 +155,7 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new ProgressBarPlugin(),
|
||||
new ForkTsCheckerPlugin(),
|
||||
|
||||
// todo: fix remain warnings about circular dependencies
|
||||
|
44
yarn.lock
@ -2011,6 +2011,21 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.0.1.tgz#b6e98083f13faa1e5231bfa3bdb1b0feff536b6d"
|
||||
integrity sha512-boy4xPNEtiw6N3abRhBi/e7hNvy3Tt8E9ZRAQrwAGzoCGZS/1wjo9KY7JHhnfnEsG5wSjDbymCozUM9a3ea7OQ==
|
||||
|
||||
"@types/progress-bar-webpack-plugin@^2.1.0":
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/progress-bar-webpack-plugin/-/progress-bar-webpack-plugin-2.1.0.tgz#f95b7e1199b5f5e8156321d2411f6fb06c833559"
|
||||
integrity sha512-HCyeEuuFzsXvIkbchGKJUhXRBGio7BlvVn0ULuBRE50UofuFy2Q8HvR+Q8C2w+QlBUoM+AjerGJU8DhyqYVw7g==
|
||||
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@*":
|
||||
version "15.7.3"
|
||||
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"
|
||||
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:
|
||||
version "1.0.11"
|
||||
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"
|
||||
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:
|
||||
version "2.0.3"
|
||||
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"
|
||||
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:
|
||||
version "3.0.1"
|
||||
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"
|
||||
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:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b"
|
||||
|