diff --git a/src/common/base-store.ts b/src/common/base-store.ts index 776f8ce41e..852341df22 100644 --- a/src/common/base-store.ts +++ b/src/common/base-store.ts @@ -36,8 +36,8 @@ export class BaseStore extends Singleton { return path.basename(this.storeConfig.path); } - get syncEvent() { - return `[STORE]:[SYNC]:${this.name}` + get syncChannel() { + return `store-sync:${this.name}` } protected async init() { @@ -57,21 +57,22 @@ export class BaseStore extends Singleton { projectName: "lens", projectVersion: getAppVersion(), get cwd() { - return (app || remote.app).getPath("userData"); + return (app || remote.app).getPath("userData"); // todo: remove usage of remote.app (deprecated) }, }); - const storeModel = Object.assign({}, this.storeConfig.store); - Reflect.deleteProperty(storeModel, "__internal__"); // fixme: avoid "external-internals" + const storedModel = Object.assign({}, this.storeConfig.store); + Reflect.deleteProperty(storedModel, "__internal__"); // fixme: avoid "external-internals" logger.info(`[STORE]: LOADED from ${this.storeConfig.path}`); - this.fromStore(storeModel); + this.fromStore(storedModel); this.isLoaded = true; } protected async save(model: T) { logger.info(`[STORE]: SAVING ${this.name}`); + // todo: avoid multiple file updates // fixme: https://github.com/sindresorhus/conf/issues/114 Object.entries(model).forEach(([key, value]) => { - this.storeConfig.set(key, value); // save update to config file + this.storeConfig.set(key, value); }); } @@ -80,18 +81,18 @@ export class BaseStore extends Singleton { reaction(() => this.toJSON(), model => this.onModelChange(model)), ); if (ipcMain) { - ipcMain.on(this.syncEvent, (event, model: T) => { - logger.info(`[STORE]: SYNC ${this.name} from renderer`); + ipcMain.on(this.syncChannel, (event, model: T) => { + logger.debug(`[STORE]: SYNC ${this.name} from renderer`, { model }); this.onSync(model); }); - this.syncDisposers.push(() => ipcMain.removeAllListeners(this.syncEvent)); + this.syncDisposers.push(() => ipcMain.removeAllListeners(this.syncChannel)); } if (ipcRenderer) { - ipcRenderer.on(this.syncEvent, (event, model: T) => { - logger.info(`[STORE]: SYNC ${this.name} from main`); + ipcRenderer.on(this.syncChannel, (event, model: T) => { + logger.debug(`[STORE]: SYNC ${this.name} from main`, { model }); this.onSync(model); }); - this.syncDisposers.push(() => ipcRenderer.removeAllListeners(this.syncEvent)); + this.syncDisposers.push(() => ipcRenderer.removeAllListeners(this.syncChannel)); } } @@ -108,12 +109,12 @@ export class BaseStore extends Singleton { protected async onModelChange(model: T) { if (ipcMain) { - this.save(model); // save to config file - broadcastMessage({ channel: this.syncEvent }, model); // broadcast to renderer views + this.save(model); // save config file + broadcastMessage({ channel: this.syncChannel }, model); // broadcast to renderer views } // send "update-request" to main-process if (ipcRenderer) { - ipcRenderer.send(this.syncEvent, model); + ipcRenderer.send(this.syncChannel, model); } } diff --git a/src/common/ipc-helpers.ts b/src/common/ipc-helpers.ts index af39ba41f3..5ccc77e9eb 100644 --- a/src/common/ipc-helpers.ts +++ b/src/common/ipc-helpers.ts @@ -21,15 +21,16 @@ export interface IpcBroadcastOpts { } export function broadcastMessage({ channel, filter }: IpcBroadcastOpts, ...args: any[]) { - let webContentsList = webContents.getAllWebContents(); - if (filter) { - webContentsList = webContentsList.filter(filter); + if (!filter) { + filter = webContent => webContent.getType() === "window" } - webContentsList.forEach(webContent => { + webContents.getAllWebContents().filter(filter).forEach(webContent => { + logger.info(`[IPC]: broadcasting ${channel} to ${webContent.getType()}=${webContent.id}`); webContent.send(channel, ...args); }) } +// fixme: support timeout export async function invokeMessage(channel: IpcChannel, ...args: any[]): Promise { logger.info(`[IPC]: invoke channel "${channel}"`, { args }); return ipcRenderer.invoke(channel, ...args); diff --git a/src/main/kube-auth-proxy.ts b/src/main/kube-auth-proxy.ts index e62bdbb221..9c8b72d6ed 100644 --- a/src/main/kube-auth-proxy.ts +++ b/src/main/kube-auth-proxy.ts @@ -84,7 +84,7 @@ export class KubeAuthProxy { const channel = `kube-auth:${this.cluster.id}` const message = { data, stream }; logger.debug(channel, message); - broadcastMessage({ channel }, message); + broadcastMessage({ channel }, message); // todo: send message only to cluster's window } public exit() { diff --git a/src/main/router.ts b/src/main/router.ts index 65cefe7ac9..b493acd6e8 100644 --- a/src/main/router.ts +++ b/src/main/router.ts @@ -2,7 +2,7 @@ import Call from "@hapi/call" import Subtext from "@hapi/subtext" import http from "http" import path from "path" -import { readFile, stat } from "fs-extra" +import { readFile } from "fs-extra" import { Cluster } from "./cluster" import { apiPrefix, appName, outDir } from "../common/vars"; import { configRoute, helmRoute, kubeconfigRoute, metricsRoute, portForwardRoute, resourceApplierRoute, watchRoute } from "./routes"; @@ -97,13 +97,12 @@ export class Router { protected async handleStaticFile(filePath: string, response: http.ServerResponse) { const asset = path.resolve(outDir, filePath); - const info = await stat(asset); - if (info.isFile()) { + try { const data = await readFile(asset); response.setHeader("Content-Type", this.getMimeType(asset)); response.write(data) response.end() - } else { + } catch (err) { this.handleStaticFile(`${appName}.html`, response); } } diff --git a/src/main/routes/metrics-route.ts b/src/main/routes/metrics-route.ts index 7c7ec7aa28..ccbe187b6e 100644 --- a/src/main/routes/metrics-route.ts +++ b/src/main/routes/metrics-route.ts @@ -1,3 +1,4 @@ +import url from "url" import { LensApiRequest } from "../router" import { LensApi } from "../lens-api" import requestPromise from "request-promise-native" @@ -8,9 +9,19 @@ export type IMetricsQuery = string | string[] | { } class MetricsRoute extends LensApi { - public async routeMetrics(request: LensApiRequest) { + + public async routeMetrics(request: LensApiRequest) { const { response, cluster, payload } = request const { contextHandler, kubeProxyUrl } = cluster; + const headers: Record = { + "Host": url.parse(cluster.webContentUrl).host, + "Content-type": "application/json", + } + const queryParams: IMetricsQuery = {} + request.query.forEach((value: string, key: string) => { + queryParams[key] = value + }) + let metricsUrl: string let prometheusProvider: PrometheusProvider try { @@ -22,20 +33,23 @@ class MetricsRoute extends LensApi { return } // prometheus metrics loader - const attempts: Record = {}; + const attempts: { [query: string]: number } = {}; const maxAttempts = 5; - const loadMetrics = (promQuery: string): Promise => { - const queryString = request.query.toString() + `&query=` + promQuery; - const attempt = attempts[queryString] = (attempts[queryString] || 0) + 1; + const loadMetrics = (orgQuery: string): Promise => { + const query = orgQuery.trim() + const attempt = attempts[query] = (attempts[query] || 0) + 1; return requestPromise(metricsUrl, { - json: true, - qs: queryString, - useQuerystring: true, resolveWithFullResponse: false, + headers: headers, + json: true, + qs: { + query: query, + ...queryParams + } }).catch(async (error) => { if (attempt < maxAttempts && (error.statusCode && error.statusCode != 404)) { await new Promise(resolve => setTimeout(resolve, attempt * 1000)); // add delay before repeating request - return loadMetrics(queryString); + return loadMetrics(query); } return { status: error.toString(), diff --git a/src/main/window-manager.ts b/src/main/window-manager.ts index f2e17ce3a3..1bcdfbab0d 100644 --- a/src/main/window-manager.ts +++ b/src/main/window-manager.ts @@ -71,22 +71,24 @@ export class WindowManager { const isLoadedBefore = !!this.getView(clusterId); const view = this.initView(clusterId); logger.info(`[WINDOW-MANAGER]: activating cluster view`, { - id: cluster.id, + id: view.id, + clusterId: cluster.id, contextName: cluster.contextName, isLoadedBefore: isLoadedBefore, }); if (activeView !== view) { + this.activeView = view; if (!isLoadedBefore) { await cluster.whenReady; await view.loadURL(cluster.webContentUrl); + this.hideSplash(); } + // refresh position and hide previous active window if (activeView) { - view.setBounds(activeView.getBounds()); // refresh position and swap windows + view.setBounds(activeView.getBounds()); activeView.hide(); } view.show(); - this.hideSplash(); - this.activeView = view; } } catch (err) { logger.error(`[WINDOW-MANAGER]: can't activate cluster view`, { diff --git a/src/renderer/components/app.scss b/src/renderer/components/app.scss index 6b6e81289b..69c6c56c27 100755 --- a/src/renderer/components/app.scss +++ b/src/renderer/components/app.scss @@ -39,6 +39,16 @@ html, body { min-height: 100%; } +// fixme: doesn't work +#draggable-top { + @include set-draggable; + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 20px; +} + body { font: $font-size $font-main; } diff --git a/src/renderer/components/app.tsx b/src/renderer/components/app.tsx index 4826ebff13..673587381c 100755 --- a/src/renderer/components/app.tsx +++ b/src/renderer/components/app.tsx @@ -1,7 +1,6 @@ import "./app.scss"; import React, { Fragment } from "react"; -import { observer } from "mobx-react"; import { i18nStore } from "../i18n"; import { configStore } from "../config.store"; import { Terminal } from "./dock/terminal"; @@ -30,9 +29,7 @@ import { DeploymentScaleDialog } from "./+workloads-deployments/deployment-scale import { CustomResources } from "./+custom-resources/custom-resources"; import { crdRoute } from "./+custom-resources"; import { isAllowedResource } from "../api/rbac"; -import { clusterStore } from "../../common/cluster-store"; -@observer export class App extends React.Component { static rootElem = document.getElementById('app'); @@ -48,13 +45,6 @@ export class App extends React.Component { - ( -
-

App is running!

-

Current cluster:

-
{JSON.stringify(clusterStore.activeCluster.toJSON(), null, 2)}
-
- )}/> diff --git a/src/renderer/components/cluster-manager/cluster-manager.scss b/src/renderer/components/cluster-manager/cluster-manager.scss index 3b61531563..792d74146e 100644 --- a/src/renderer/components/cluster-manager/cluster-manager.scss +++ b/src/renderer/components/cluster-manager/cluster-manager.scss @@ -1,22 +1,17 @@ .ClusterManager { display: grid; - grid-template-areas: "draggable draggable" "menu lens-view" "bottom-bar bottom-bar"; + grid-template-areas: "menu lens-view" "menu lens-view" "bottom-bar bottom-bar"; grid-template-rows: auto 1fr min-content; grid-template-columns: min-content 1fr; height: 100%; - .draggable-top { - @include set-draggable; - grid-area: draggable; - height: 25px; - } - #lens-view { position: relative; grid-area: lens-view; } .ClustersMenu { + margin-top: 25px; grid-area: menu; } diff --git a/src/renderer/components/cluster-manager/cluster-manager.tsx b/src/renderer/components/cluster-manager/cluster-manager.tsx index 7d272f823d..0fce242fb9 100644 --- a/src/renderer/components/cluster-manager/cluster-manager.tsx +++ b/src/renderer/components/cluster-manager/cluster-manager.tsx @@ -8,7 +8,7 @@ export class ClusterManager extends React.Component { const { children: lensView } = this.props; return (
-
+
{lensView}
diff --git a/src/renderer/components/cluster-manager/clusters-menu.scss b/src/renderer/components/cluster-manager/clusters-menu.scss index 241af09bbd..bf0f9f1ae2 100644 --- a/src/renderer/components/cluster-manager/clusters-menu.scss +++ b/src/renderer/components/cluster-manager/clusters-menu.scss @@ -4,7 +4,7 @@ --flex-gap: #{$padding * 2}; --menu-bgc: #252729; - padding: $padding * 1.5; + padding: $padding * 2; background: var(--menu-bgc); .add-cluster { diff --git a/src/renderer/components/layout/main-layout.scss b/src/renderer/components/layout/main-layout.scss index a9036ad0d5..9f73b9f75a 100755 --- a/src/renderer/components/layout/main-layout.scss +++ b/src/renderer/components/layout/main-layout.scss @@ -7,7 +7,7 @@ grid-template-areas: "aside header" "aside tabs" "aside main" "aside footer"; grid-template-rows: [header] var(--main-layout-header) [tabs] min-content [main] 1fr [footer] auto; grid-template-columns: [sidebar] minmax(var(--main-layout-header), min-content) [main] 1fr; - height: 100vh; + height: 100%; &.light { main {