import type { ThemeId } from "../renderer/theme.store"; import { app, remote } from 'electron'; import semver from "semver"; import { readFile } from "fs-extra"; import { action, observable, reaction, toJS } from "mobx"; import { BaseStore } from "./base-store"; import migrations from "../migrations/user-store"; import { getAppVersion } from "./utils/app-version"; import { kubeConfigDefaultPath, loadConfig } from "./kube-helpers"; import { appEventBus } from "./event-bus"; import logger from "../main/logger"; import path from 'path'; export interface UserStoreModel { kubeConfigPath: string; lastSeenAppVersion: string; seenContexts: string[]; preferences: UserPreferences; } export interface UserPreferences { httpsProxy?: string; colorTheme?: string; allowUntrustedCAs?: boolean; allowTelemetry?: boolean; downloadMirror?: string | "default"; downloadKubectlBinaries?: boolean; downloadBinariesPath?: string; kubectlBinariesPath?: string; openAtLogin?: boolean; } export class UserStore extends BaseStore { static readonly defaultTheme: ThemeId = "lens-dark" private constructor() { super({ // configName: "lens-user-store", // todo: migrate from default "config.json" migrations: migrations, }); this.handleOnLoad(); } @observable lastSeenAppVersion = "0.0.0" @observable kubeConfigPath = kubeConfigDefaultPath; // used in add-cluster page for providing context @observable seenContexts = observable.set(); @observable newContexts = observable.set(); @observable preferences: UserPreferences = { allowTelemetry: true, allowUntrustedCAs: false, colorTheme: UserStore.defaultTheme, downloadMirror: "default", downloadKubectlBinaries: true, // Download kubectl binaries matching cluster version openAtLogin: true, }; protected async handleOnLoad() { await this.whenLoaded; // refresh new contexts this.refreshNewContexts(); reaction(() => this.kubeConfigPath, this.refreshNewContexts); if (app) { // track telemetry availability reaction(() => this.preferences.allowTelemetry, allowed => { appEventBus.emit({name: "telemetry", action: allowed ? "enabled" : "disabled"}); }); // open at system start-up reaction(() => this.preferences.openAtLogin, open => { app.setLoginItemSettings({ openAtLogin: open }); }, { fireImmediately: true, }); } } get isNewVersion() { return semver.gt(getAppVersion(), this.lastSeenAppVersion); } @action resetKubeConfigPath() { this.kubeConfigPath = kubeConfigDefaultPath; } @action async resetTheme() { await this.whenLoaded; this.preferences.colorTheme = UserStore.defaultTheme; } @action saveLastSeenAppVersion() { appEventBus.emit({name: "app", action: "whats-new-seen"}); this.lastSeenAppVersion = getAppVersion(); } protected refreshNewContexts = async () => { try { const kubeConfig = await readFile(this.kubeConfigPath, "utf8"); if (kubeConfig) { this.newContexts.clear(); loadConfig(kubeConfig).getContexts() .filter(ctx => ctx.cluster) .filter(ctx => !this.seenContexts.has(ctx.name)) .forEach(ctx => this.newContexts.add(ctx.name)); } } catch (err) { logger.error(err); this.resetKubeConfigPath(); } } @action markNewContextsAsSeen() { const { seenContexts, newContexts } = this; this.seenContexts.replace([...seenContexts, ...newContexts]); this.newContexts.clear(); } /** * Getting default directory to download kubectl binaries * @returns string */ getDefaultKubectlPath(): string { return path.join((app || remote.app).getPath("userData"), "binaries"); } @action protected async fromStore(data: Partial = {}) { const { lastSeenAppVersion, seenContexts = [], preferences, kubeConfigPath } = data; if (lastSeenAppVersion) { this.lastSeenAppVersion = lastSeenAppVersion; } if (kubeConfigPath) { this.kubeConfigPath = kubeConfigPath; } this.seenContexts.replace(seenContexts); Object.assign(this.preferences, preferences); } toJSON(): UserStoreModel { const model: UserStoreModel = { kubeConfigPath: this.kubeConfigPath, lastSeenAppVersion: this.lastSeenAppVersion, seenContexts: Array.from(this.seenContexts), preferences: this.preferences, }; return toJS(model, { recurseEverything: true, }); } } export const userStore = UserStore.getInstance();