epic: getting rid of vue -- part 2

Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
Roman committed 2020-07-08 12:12:11 +03:00
1 parent 63de5fce3c
commit 1f26f0b3a7
22 files changed
+469 -297

No files matched your search

+7 -6
View File
@@ -21,7 +21,7 @@ export class BaseStore<T = any> extends Singleton {
public whenLoaded = when(() => this.isLoaded);
@observable isLoaded = false;
@observable protected data = {} as T;
@observable protected data: T;
protected constructor(protected params: BaseStoreParams) {
super();
@@ -69,9 +69,9 @@ export class BaseStore<T = any> extends Singleton {
},
...confOptions,
});
const data = this.storeConfig.store;
console.info(`[STORE]: [LOADED] ${this.storeConfig.path}`, data);
this.fromStore(data);
const jsonModel = this.storeConfig.store;
console.info(`[STORE]: [LOADED] ${this.storeConfig.path}`, jsonModel);
this.fromStore(jsonModel);
this.isLoaded = true;
}
@@ -111,8 +111,8 @@ export class BaseStore<T = any> extends Singleton {
}
@action
protected fromStore(data: Partial<T> = {}) {
Object.assign(this.data, data);
protected fromStore(data: T) {
this.data = data;
}
@action
@@ -120,6 +120,7 @@ export class BaseStore<T = any> extends Singleton {
this.data = produce(this.data, updater);
}
// todo: use "serializr" ?
toJSON(): T {
return toJS(this.data, {
recurseEverything: true,
+5 -6
View File
@@ -81,12 +81,11 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
@action
protected fromStore({ clusters = [] }: Partial<ClusterStoreModel> = {}) {
// fixme: handle clusters update + delete
clusters.forEach(model => {
if (!this.clusters.has(model.id)) {
this.clusters.set(model.id, new Cluster(model));
}
})
const clustersMap = new Map<ClusterId, Cluster>();
clusters.forEach(clusterModel => {
clustersMap.set(clusterModel.id, new Cluster(clusterModel));
});
this.clusters.replace(clustersMap);
}
toJSON(): ClusterStoreModel {
+7 -3
View File
@@ -9,12 +9,16 @@ export interface IpcOptions {
timeout?: number;
}
export interface IpcMessageHandler {
(...args: any[]): any;
}
export async function invokeMessage(channel: string, ...args: any[]) {
logger.debug(`[IPC]: invoke channel "${channel}"`, { args });
return ipcRenderer.invoke(channel, ...args);
}
export function onMessage(channel: string, handler: (...args: any[]) => any, options: IpcOptions = {}) {
export function onMessage(channel: string, handler: IpcMessageHandler, options: IpcOptions = {}) {
const { timeout = 0 } = options;
ipcMain.handle(channel, async (event, ...args: any[]) => {
logger.debug(`[IPC]: handle "${channel}"`, { event, args });
@@ -37,8 +41,8 @@ export function onMessage(channel: string, handler: (...args: any[]) => any, opt
})
}
export function onMessages(messages: Record<string, Function>, options?: IpcOptions) {
export function onMessages(messages: Record<string, IpcMessageHandler>, options?: IpcOptions) {
Object.entries(messages).forEach(([channel, handler]) => {
this.onMessage(channel, handler, options);
onMessage(channel, handler, options);
})
}
+17
View File
@@ -0,0 +1,17 @@
// Register custom protocols
import path from "path";
import { protocol } from "electron"
import logger from "../main/logger";
export function registerFileProtocol(name: string, basePath: string) {
protocol.registerFileProtocol(name, (request, callback) => {
const filePath = request.url.replace(name + "://", "");
const absPath = path.resolve(basePath, filePath);
callback(absPath);
}, (error) => {
if (error) {
logger.error(`Failed to register protocol "${name}"`, { basePath, error });
}
})
}
-25
View File
@@ -1,25 +0,0 @@
// Setup static folder for common assets
import path from "path";
import { protocol } from "electron"
import logger from "../main/logger";
import { staticDir, staticProto, outDir } from "./vars";
export function registerStaticProtocol(rootFolder = staticDir) {
const scheme = staticProto.replace("://", "");
protocol.registerFileProtocol(scheme, (request, callback) => {
const relativePath = request.url.replace(staticProto, "");
const absPath = path.resolve(rootFolder, relativePath);
callback(absPath);
}, (error) => {
logger.debug(`Failed to register protocol "${scheme}"`, error);
})
}
export function getStaticUrl(filePath: string) {
return staticProto + filePath;
}
export function getStaticPath(filePath: string) {
return path.resolve(staticDir, filePath);
}
+2 -2
View File
@@ -7,8 +7,8 @@ import * as path from "path"
// Writes kubeconfigs to "embedded" store, i.e. .../Lens/kubeconfigs/
export function writeEmbeddedKubeConfig(clusterId: string, kubeConfig: string): string {
// This can be called from main & renderer
const a = (app || remote.app)
const kubeConfigBase = path.join(a.getPath("userData"), "kubeconfigs")
const userData = (app || remote.app).getPath("userData");
const kubeConfigBase = path.join(userData, "kubeconfigs")
ensureDirSync(kubeConfigBase)
const kubeConfigFile = path.join(kubeConfigBase, clusterId)
+5 -5
View File
@@ -1,9 +1,7 @@
// App's common configuration for any process (main, renderer, build pipeline, etc.)
import packageInfo from "../../package.json"
import path from "path";
export const appName = "lens"
// Flags
export const isMac = process.platform === "darwin"
export const isWindows = process.platform === "win32"
export const isDebugging = process.env.DEBUG === "true";
@@ -12,6 +10,10 @@ export const isDevelopment = isDebugging || !isProduction;
export const buildVersion = process.env.BUILD_VERSION;
export const isTestEnv = !!process.env.JEST_WORKER_ID;
export const appName = `${packageInfo.productName}${isDevelopment ? "Dev" : ""}`
export const appProto = "lens" // app's "userData" folder (e.g. "lens://icons/logo.svg")
export const staticProto = "static" // static content folder (e.g. "static://RELEASE_NOTES.md")
// Paths
export const contextDir = process.cwd();
export const staticDir = path.join(contextDir, "static");
@@ -22,8 +24,6 @@ export const htmlTemplate = path.resolve(rendererDir, "template.html");
export const sassCommonVars = path.resolve(rendererDir, "components/vars.scss");
// Apis
export const staticProto = "static://"
export const apiPrefix = {
BASE: '/api',
KUBE_BASE: '/api-kube', // kubernetes cluster api
+46 -30
View File
@@ -1,11 +1,11 @@
import { action, computed, toJS } from "mobx";
import { action, observable } from "mobx";
import { BaseStore } from "./base-store";
import { clusterStore } from "./cluster-store"
export type WorkspaceId = string;
export interface WorkspaceStoreModel {
currentWorkspace?: WorkspaceId; // last visited/activated
currentWorkspace?: WorkspaceId;
workspaces: Workspace[]
}
@@ -18,17 +18,14 @@ export interface Workspace {
export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
static readonly defaultId: WorkspaceId = "default"
protected data: WorkspaceStoreModel = {
currentWorkspace: WorkspaceStore.defaultId,
workspaces: [{
@observable currentWorkspace = WorkspaceStore.defaultId;
@observable workspaces = observable.map<WorkspaceId, Workspace>({
[WorkspaceStore.defaultId]: {
id: WorkspaceStore.defaultId,
name: "default"
}]
}
@computed get workspaces() {
return toJS(this.data.workspaces);
}
}
});
private constructor() {
super({
@@ -36,40 +33,59 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
});
}
public getById(id: WorkspaceId): Workspace {
return this.workspaces.find(workspace => workspace.id === id);
}
public getIndexById(id: WorkspaceId): number {
return this.workspaces.findIndex(workspace => workspace.id === id);
getById(id: WorkspaceId): Workspace {
return this.workspaces.get(id);
}
@action
setCurrent(id: WorkspaceId) {
this.data.currentWorkspace = id;
if (!this.getById(id)) return;
this.currentWorkspace = id;
}
@action
public saveWorkspace(newWorkspace: Workspace) {
const workspace = this.getById(newWorkspace.id);
if (workspace) {
Object.assign(workspace, newWorkspace);
public saveWorkspace(workspace: Workspace) {
const id = workspace.id;
const existingWorkspace = this.getById(id);
if (existingWorkspace) {
Object.assign(existingWorkspace, workspace);
} else {
this.data.workspaces.push(newWorkspace);
this.workspaces.set(id, workspace);
}
}
@action
public removeWorkspace(workspaceOrId: Workspace | WorkspaceId) {
const workspace = this.getById(typeof workspaceOrId == "string" ? workspaceOrId : workspaceOrId.id);
public removeWorkspace(id: WorkspaceId) {
const workspace = this.getById(id);
if (!workspace) return;
if (workspace.id === WorkspaceStore.defaultId) {
if (id === WorkspaceStore.defaultId) {
throw new Error("Cannot remove default workspace");
}
const index = this.getIndexById(workspace.id);
if (index > -1) {
this.data.workspaces.splice(index, 1)
clusterStore.removeByWorkspaceId(workspace.id)
if (id === this.currentWorkspace) {
this.currentWorkspace = WorkspaceStore.defaultId;
}
this.workspaces.delete(id);
clusterStore.removeByWorkspaceId(id)
}
@action
protected fromStore({ currentWorkspace, workspaces = [] }: WorkspaceStoreModel) {
if (currentWorkspace) {
this.currentWorkspace = currentWorkspace
}
if (workspaces.length) {
this.workspaces.clear();
workspaces.forEach(workspace => {
this.workspaces.set(workspace.id, workspace)
})
}
}
toJSON(): WorkspaceStoreModel {
const { currentWorkspace, workspaces } = this;
return {
currentWorkspace: currentWorkspace,
workspaces: Array.from(workspaces.values()),
}
}
}
+73 -57
View File
@@ -1,5 +1,5 @@
import { autorun } from "mobx";
import { apiPrefix } from "../common/vars";
import { apiPrefix, appProto } from "../common/vars";
import { app } from "electron"
import path from "path"
import http from "http"
@@ -26,8 +26,8 @@ export class ClusterManager {
}
constructor(protected port: number) {
ClusterManager.ipcListen(this);
autorun(() => {
// fixme: detect and stop removed clusters from config file ?
clusterStore.clusters.forEach((cluster: Cluster) => {
if (!cluster.initialized) {
cluster.init(this.port);
@@ -35,6 +35,8 @@ export class ClusterManager {
}
})
});
ClusterManager.ipcListen(this);
}
stop() {
@@ -48,6 +50,7 @@ export class ClusterManager {
}
protected async addCluster(clusterModel: ClusterModel): Promise<Cluster> {
tracker.event("cluster", "add");
try {
await validateConfig(clusterModel.kubeConfigPath);
return clusterStore.addCluster({
@@ -60,7 +63,13 @@ export class ClusterManager {
}
}
protected stopCluster(clusterId: ClusterId) {
tracker.event("cluster", "stop");
this.getCluster(clusterId)?.stopServer();
}
protected removeAllByWorkspace(workspaceId: string) {
tracker.event("cluster", "remove-workspace");
const clusters = clusterStore.getByWorkspaceId(workspaceId);
clusters.forEach(cluster => {
this.removeCluster(cluster.id);
@@ -68,6 +77,7 @@ export class ClusterManager {
}
protected removeCluster(clusterId: string): Cluster {
tracker.event("cluster", "remove");
const cluster = this.getCluster(clusterId);
if (cluster) {
cluster.stopServer()
@@ -97,64 +107,70 @@ export class ClusterManager {
return cluster;
}
protected async uploadClusterIcon(cluster: Cluster, fileName: string, src: string): Promise<string> {
await ensureDir(ClusterManager.clusterIconDir)
fileName = filenamify(cluster.contextName + "-" + fileName)
const dest = path.join(ClusterManager.clusterIconDir, fileName)
await copyFile(src, dest)
return "store:///icons/" + fileName
protected async uploadClusterIcon({ clusterId, name: fileName, path: src }: ClusterIconUpload): Promise<string> {
const cluster = this.getCluster(clusterId);
if (cluster) {
tracker.event("cluster", "upload-icon");
await ensureDir(ClusterManager.clusterIconDir)
fileName = filenamify(cluster.contextName + "-" + fileName)
const dest = path.join(ClusterManager.clusterIconDir, fileName)
await copyFile(src, dest)
cluster.preferences.icon = `${appProto}:///icons/${fileName}`
return cluster.preferences.icon;
}
}
// todo: remove current icon file ?
protected resetClusterIcon(clusterId: ClusterId) {
const cluster = this.getCluster(clusterId);
if (cluster) {
tracker.event("cluster", "reset-icon")
cluster.preferences.icon = null;
}
}
// todo: check feature failures
protected async installFeature({ clusterId, name, config }: FeatureInstallRequest) {
tracker.event("cluster", "install-feature")
return this.getCluster(clusterId)?.installFeature(name, config)
}
protected async upgradeFeature({ clusterId, name, config }: FeatureInstallRequest) {
tracker.event("cluster", "upgrade-feature")
return this.getCluster(clusterId)?.upgradeFeature(name, config)
}
protected async uninstallFeature({ clusterId, name }: FeatureInstallRequest) {
tracker.event("cluster", "uninstall-feature")
return this.getCluster(clusterId)?.uninstallFeature(name);
}
protected async refreshCluster(clusterId: ClusterId) {
await this.getCluster(clusterId)?.refreshCluster();
}
protected async getEventsCount(clusterId: ClusterId): Promise<number> {
return await this.getCluster(clusterId)?.getEventCount() || 0;
}
static ipcListen(clusterManager: ClusterManager) {
onMessages({
[ClusterIpcMessage.CLUSTER_ADD]: async (model: ClusterModel): Promise<boolean> => {
tracker.event("cluster", "add");
await clusterManager.addCluster(model);
return true;
},
[ClusterIpcMessage.CLUSTER_STOP]: (clusterId: ClusterId) => {
tracker.event("cluster", "stop");
clusterManager.getCluster(clusterId)?.stopServer();
},
[ClusterIpcMessage.CLUSTER_REMOVE]: (clusterId: ClusterId) => {
tracker.event("cluster", "remove");
clusterManager.removeCluster(clusterId);
},
[ClusterIpcMessage.CLUSTER_REMOVE_WORKSPACE]: (workspaceId: ClusterId) => {
clusterManager.removeAllByWorkspace(workspaceId);
},
[ClusterIpcMessage.CLUSTER_REFRESH]: (clusterId: ClusterId) => {
clusterManager.getCluster(clusterId)?.refreshCluster();
},
[ClusterIpcMessage.CLUSTER_EVENTS]: async (clusterId: ClusterId): Promise<number> => {
return await clusterManager.getCluster(clusterId)?.getEventCount() || 0;
},
// todo: check feature failures
[ClusterIpcMessage.FEATURE_INSTALL]: ({ clusterId, name, config }: FeatureInstallRequest) => {
tracker.event("cluster", "install-feature")
return clusterManager.getCluster(clusterId)?.installFeature(name, config)
},
[ClusterIpcMessage.FEATURE_UPGRADE]: ({ clusterId, name, config }: FeatureInstallRequest) => {
tracker.event("cluster", "upgrade-feature")
return clusterManager.getCluster(clusterId)?.upgradeFeature(name, config)
},
[ClusterIpcMessage.FEATURE_REMOVE]: ({ clusterId, name }: FeatureInstallRequest) => {
tracker.event("cluster", "uninstall-feature")
return clusterManager.getCluster(clusterId)?.uninstallFeature(name);
},
[ClusterIpcMessage.ICON_SAVE]: async ({ clusterId, name, path }: ClusterIconUpload) => {
tracker.event("cluster", "upload-icon");
const cluster = clusterManager.getCluster(clusterId);
if (!cluster) return false;
cluster.preferences.icon = await clusterManager.uploadClusterIcon(cluster, name, path);
},
[ClusterIpcMessage.ICON_RESET]: async (clusterId: ClusterId) => {
tracker.event("cluster", "reset-icon")
const cluster = clusterManager.getCluster(clusterId);
if (!cluster) return false;
cluster.preferences.icon = null; // todo: remove current file-icon ?
},
}, {
const handlers = {
[ClusterIpcMessage.CLUSTER_ADD]: clusterManager.addCluster,
[ClusterIpcMessage.CLUSTER_STOP]: clusterManager.stopCluster,
[ClusterIpcMessage.CLUSTER_REMOVE]: clusterManager.removeCluster,
[ClusterIpcMessage.CLUSTER_REMOVE_WORKSPACE]: clusterManager.removeAllByWorkspace,
[ClusterIpcMessage.CLUSTER_REFRESH]: clusterManager.refreshCluster,
[ClusterIpcMessage.CLUSTER_EVENTS]: clusterManager.getEventsCount,
[ClusterIpcMessage.FEATURE_INSTALL]: clusterManager.installFeature,
[ClusterIpcMessage.FEATURE_UPGRADE]: clusterManager.upgradeFeature,
[ClusterIpcMessage.FEATURE_REMOVE]: clusterManager.uninstallFeature,
[ClusterIpcMessage.ICON_SAVE]: clusterManager.uploadClusterIcon,
[ClusterIpcMessage.ICON_RESET]: clusterManager.removeCluster,
};
Object.entries(handlers).forEach(([key, handler]) => {
handlers[key as keyof typeof handlers] = handler.bind(clusterManager);
})
onMessages(handlers, {
timeout: 2000,
})
}
+58 -23
View File
@@ -1,13 +1,13 @@
import { observable } from "mobx";
import type { ClusterId, ClusterModel, ClusterPreferences } from "../common/cluster-store"
import type { FeatureStatusMap } from "./feature"
import { observable, toJS } from "mobx";
import { apiPrefix } from "../common/vars";
import { ContextHandler } from "./context-handler"
import { FeatureStatusMap } from "./feature"
import { AuthorizationV1Api, CoreV1Api, KubeConfig, V1ResourceAttributes } from "@kubernetes/client-node"
import { Kubectl } from "./kubectl";
import { KubeconfigManager } from "./kubeconfig-manager"
import { getNodeWarningConditions, loadConfig, podHasIssues } from "./k8s"
import { getFeatures, installFeature, uninstallFeature, upgradeFeature } from "./feature-manager";
import type { ClusterId, ClusterModel, ClusterPreferences } from "../common/cluster-store"
import request from "request-promise-native"
import logger from "./logger"
@@ -17,30 +17,43 @@ enum ClusterStatus {
Offline = 0
}
export interface ClusterState extends ClusterModel {
url: string;
apiUrl: string;
online?: boolean;
accessible?: boolean;
failureReason?: string;
nodes?: number;
eventCount?: number;
version?: string;
distribution?: string;
isAdmin?: boolean;
features?: FeatureStatusMap;
}
export class Cluster implements ClusterModel {
public contextHandler: ContextHandler;
public kubeCtl: Kubectl
protected kubeconfigManager: KubeconfigManager;
@observable initialized = false;
@observable id: ClusterId;
@observable workspace: string;
@observable kubeConfigPath: string;
@observable contextName: string;
@observable url: string;
@observable port: number;
@observable apiUrl: string;
@observable online: boolean;
@observable accessible: boolean;
@observable failureReason: string;
@observable nodes: number;
@observable version: string;
@observable distribution: string;
@observable isAdmin: boolean;
@observable eventCount: number;
@observable preferences: ClusterPreferences = {};
public contextHandler: ContextHandler;
public url: string;
public port: number;
public apiUrl: string;
public online: boolean;
public accessible: boolean;
public failureReason: string;
public nodes: number;
public version: string;
public distribution: string;
public isAdmin: boolean;
public eventCount: number;
public kubeCtl: Kubectl
public features: FeatureStatusMap = {};
protected kubeconfigManager: KubeconfigManager;
@observable features: FeatureStatusMap = {};
constructor(model: ClusterModel) {
Object.assign(this, model)
@@ -50,7 +63,7 @@ export class Cluster implements ClusterModel {
const { contextName } = this
try {
const kubeConfig = loadConfig(this.kubeConfigPath)
kubeConfig.setCurrentContext(contextName); // fixme: is it needed at all?
kubeConfig.setCurrentContext(contextName); // fixme: is it required, when if so?
this.port = port;
this.apiUrl = kubeConfig.getCurrentCluster().server
this.contextHandler = new ContextHandler(kubeConfig, this)
@@ -238,12 +251,34 @@ export class Cluster implements ClusterModel {
}
toJSON(): ClusterModel {
return {
return toJS({
id: this.id,
contextName: this.contextName,
kubeConfigPath: this.kubeConfigPath,
workspace: this.workspace,
preferences: this.preferences,
}
}, {
recurseEverything: true
})
}
getState(): ClusterState {
const storeModel = this.toJSON();
return toJS({
...storeModel,
url: this.url,
apiUrl: this.apiUrl,
online: this.online,
accessible: this.accessible,
failureReason: this.failureReason,
nodes: this.nodes,
version: this.version,
distribution: this.distribution,
isAdmin: this.isAdmin,
features: this.features,
eventCount: this.eventCount,
}, {
recurseEverything: true
})
}
}
+6 -16
View File
@@ -3,30 +3,22 @@ import path from "path"
import hb from "handlebars"
import { ResourceApplier } from "./resource-applier"
import { KubeConfig, CoreV1Api, Watch } from "@kubernetes/client-node"
import logger from "./logger";
import { Cluster } from "./cluster";
import logger from "./logger";
export type FeatureInstallRequest = {
export type FeatureStatusMap = Record<string, FeatureStatus>
export interface FeatureInstallRequest {
clusterId: string;
name: string;
config?: any;
}
export type FeatureInstallResponse = {
success: boolean;
message: string;
}
export type FeatureStatus = {
export interface FeatureStatus {
currentVersion: string;
installed: boolean;
latestVersion: string;
canUpgrade: boolean;
// TODO We need bunch of other stuff too: upgradeable, latestVersion, ...
};
export type FeatureStatusMap = {
[name: string]: FeatureStatus;
}
export abstract class Feature {
@@ -35,9 +27,7 @@ export abstract class Feature {
latestVersion: string;
constructor(config: any) {
if(config) {
this.config = config;
}
if(config) this.config = config;
}
// TODO Return types for these?
+26 -63
View File
@@ -2,12 +2,10 @@
import "../common/system-ca"
import "../common/prometheus-providers"
import { app, dialog, protocol } from "electron"
import { appName, isDevelopment, isMac } from "../common/vars";
import { PromiseIpc } from "electron-promise-ipc"
import { app, dialog } from "electron"
import { appName, appProto, isDevelopment, isMac, staticDir, staticProto } from "../common/vars";
import path from "path"
import { format as formatUrl } from "url"
import logger from "./logger"
import initMenu from "./menu"
import * as proxy from "./proxy"
import { WindowManager } from "./window-manager";
@@ -16,25 +14,13 @@ import AppUpdater from "./app-updater"
import { shellSync } from "./shell-sync"
import { getFreePort } from "./port"
import { mangleProxyEnv } from "./proxy-env"
import { findMainWebContents } from "./webcontents"
import { registerStaticProtocol } from "../common/register-static";
import { registerFileProtocol } from "../common/register-protocol";
import { clusterStore } from "../common/cluster-store"
import { userStore } from "../common/user-store";
import { workspaceStore } from "../common/workspace-store";
import { tracker } from "../common/tracker";
import logger from "./logger"
if (isDevelopment) {
const appName = "LensDev";
const appData = app.getPath("appData");
app.setName(appName);
app.setPath("userData", path.join(appData, appName));
}
mangleProxyEnv()
if (app.commandLine.getSwitchValue("proxy-server") !== "") {
process.env.HTTPS_PROXY = app.commandLine.getSwitchValue("proxy-server")
}
const promiseIpc = new PromiseIpc({ timeout: 2000 })
let windowManager: WindowManager = null;
let clusterManager: ClusterManager = null;
let proxyServer: proxy.LensProxy = null;
@@ -45,24 +31,30 @@ const vmURL = formatUrl({
slashes: true,
})
async function main() {
shellSync(app.getLocale())
mangleProxyEnv()
if (app.commandLine.getSwitchValue("proxy-server") !== "") {
process.env.HTTPS_PROXY = app.commandLine.getSwitchValue("proxy-server")
}
async function main() {
shellSync(app.getLocale());
// todo: check other usages .getPath("userData") and enable "lazy-evaluation"
const workingDir = path.join(app.getPath("appData"), appName);
app.setName(appName);
app.setPath("userData", workingDir);
logger.info(`Start app from "${workingDir}"`)
tracker.event("app", "start");
const updater = new AppUpdater()
updater.start();
tracker.event("app", "start");
initMenu();
registerFileProtocol(appProto, app.getPath("userData"));
registerFileProtocol(staticProto, staticDir);
registerStaticProtocol();
protocol.registerFileProtocol('store', (request, callback) => {
const url = request.url.substr(8)
callback(path.normalize(`${app.getPath("userData")}/${url}`))
}, (error) => {
if (error) console.error('Failed to register protocol')
})
let port: number = null
// find free port
let port: number
try {
port = await getFreePort()
} catch (error) {
@@ -71,10 +63,11 @@ async function main() {
app.quit();
}
// preload required stores
// preload configuration from stores
await Promise.all([
userStore.load(),
clusterStore.load(),
workspaceStore.load(),
]);
// create cluster manager
@@ -89,39 +82,9 @@ async function main() {
app.quit();
}
// boot windowmanager
// manage lens windows
windowManager = new WindowManager();
windowManager.showMain(vmURL)
initMenu({
logoutHook: async () => {
// IPC send needs webContents as we're sending it to renderer
promiseIpc.send('logout', findMainWebContents(), {}).then((data: any) => {
logger.debug("logout IPC sent");
})
},
showPreferencesHook: async () => {
// IPC send needs webContents as we're sending it to renderer
promiseIpc.send('navigate', findMainWebContents(), { name: 'preferences-page' }).then((data: any) => {
logger.debug("navigate: preferences IPC sent");
})
},
addClusterHook: async () => {
promiseIpc.send('navigate', findMainWebContents(), { name: "add-cluster-page" }).then((data: any) => {
logger.debug("navigate: add-cluster-page IPC sent");
})
},
showWhatsNewHook: async () => {
promiseIpc.send('navigate', findMainWebContents(), { name: "whats-new-page" }).then((data: any) => {
logger.debug("navigate: whats-new-page IPC sent");
})
},
clusterSettingsHook: async () => {
promiseIpc.send('navigate', findMainWebContents(), { name: "cluster-settings-page" }).then((data: any) => {
logger.debug("navigate: cluster-settings-page IPC sent");
})
},
}, promiseIpc)
}
app.on("ready", main)
+5 -4
View File
@@ -1,5 +1,6 @@
import { PromiseIpc } from "electron-promise-ipc";
import { app, BrowserWindow, dialog, Menu, MenuItem, MenuItemConstructorOptions, shell, webContents } from "electron"
import { isDevelopment, isMac, issuesTrackerUrl, isWindows, slackUrl } from "../common/vars";
import { isMac, issuesTrackerUrl, isWindows, slackUrl } from "../common/vars";
// todo: refactor + split menu sections to separated files, e.g. menus/file.menu.ts
@@ -38,10 +39,8 @@ function showAbout(_menuitem: MenuItem, browserWindow: BrowserWindow) {
/**
* Constructs the menu based on the example at: https://electronjs.org/docs/api/menu#main-process
* Menu items are constructed piece-by-piece to have slightly better control on individual sub-menus
*
* @param ipc the main promiceIpc handle. Needed to be able to hook IPC sending into logout click handler.
*/
export default function initMenu(opts: MenuOptions, promiseIpc: any) {
export default function initMenu(opts: Partial<MenuOptions> = {}) {
const mt: MenuItemConstructorOptions[] = [];
const macAppMenu: MenuItemConstructorOptions = {
label: app.getName(),
@@ -199,6 +198,8 @@ export default function initMenu(opts: MenuOptions, promiseIpc: any) {
const menu = Menu.buildFromTemplate(mt);
Menu.setApplicationMenu(menu);
const promiseIpc = new PromiseIpc({ timeout: 2000 })
promiseIpc.on("enableClusterSettingsMenuItem", (clusterId: string) => {
setClusterSettingsEnabled(true)
});
+1 -5
View File
@@ -1,17 +1,13 @@
import { BrowserWindow, shell } from "electron"
import { PromiseIpc } from "electron-promise-ipc"
import windowStateKeeper from "electron-window-state"
import { getStaticUrl } from "../common/register-static";
import { tracker } from "../common/tracker";
export class WindowManager {
public mainWindow: BrowserWindow = null;
public splashWindow: BrowserWindow = null;
protected promiseIpc: any
protected windowState: windowStateKeeper.State;
constructor({ showSplash = true } = {}) {
this.promiseIpc = new PromiseIpc({ timeout: 2000 })
// Manage main window size&position with persistence
this.windowState = windowStateKeeper({
defaultHeight: 900,
@@ -31,7 +27,7 @@ export class WindowManager {
}
})
if (showSplash) {
this.splashWindow.loadURL(getStaticUrl("splash.html"))
this.splashWindow.loadURL("static://splash.html")
this.splashWindow.show()
}
@@ -29,14 +29,14 @@
<script>
import marked from 'marked'
import {readFileSync} from 'fs'
import { getStaticPath } from "../../../common/register-static"
import { userStore } from "../../../common/user-store"
export default {
name: 'WhatsNewPage',
data() {
let releaseNotes = getStaticPath("RELEASE_NOTES.md");
let content = marked(readFileSync(releaseNotes, 'utf8'));
// todo: check if "fs" can work with custom protocols
let fileContents = readFileSync("static://RELEASE_NOTES.md", 'utf8');
let content = marked(fileContents);
return {
error: "",
content: content,
+2 -10
View File
@@ -2,14 +2,14 @@
@import "fonts";
*, *:before, *:after {
@include set-draggable(false);
box-sizing: border-box;
padding: 0;
margin: 0;
border: 0;
outline: none;
-webkit-font-smoothing: antialiased;
-webkit-user-drag: none;
-webkit-app-region: no-drag;
}
::selection {
@@ -167,14 +167,6 @@ a {
}
}
.drag {
-webkit-app-region: drag;
}
.nodrag {
-webkit-app-region: no-drag;
}
// app's common loading indicator, displaying on the route transitions
#loading {
position: absolute;
+10
View File
@@ -89,3 +89,13 @@
text-decoration: underline;
cursor: pointer;
}
@mixin set-draggable($isDraggable: true) {
@if ($isDraggable) {
-webkit-user-drag: auto;
-webkit-app-region: drag;
} @else {
-webkit-user-drag: none;
-webkit-app-region: no-drag;
}
}
-14
View File
@@ -1,14 +0,0 @@
import "../../common/system-ca"
import { userStore } from "../common/user-store";
import { workspaceStore } from "../common/workspace-store";
import { clusterStore } from "../common/cluster-store";
// import { App } from "./components/app";
await Promise.all([
userStore.whenLoaded,
workspaceStore.whenLoaded,
clusterStore.whenLoaded,
]);
// App.init();
document.getElementById("app").innerHTML = "<p>Hello from renderer!</p>"
+19
View File
@@ -0,0 +1,19 @@
import "../common/system-ca"
import { userStore } from "../common/user-store";
import { workspaceStore } from "../common/workspace-store";
import { clusterStore } from "../common/cluster-store";
// import { App } from "./components/app";
async function render() {
await Promise.all([
userStore.whenLoaded,
workspaceStore.whenLoaded,
clusterStore.whenLoaded,
]);
// App.init();
document.getElementById("app").innerHTML = "<p>Hello from renderer!</p>"
}
// run
render();