diff --git a/src/common/__tests__/user-store.test.ts b/src/common/__tests__/user-store.test.ts index b1b3f60ce6..6e5a6433ef 100644 --- a/src/common/__tests__/user-store.test.ts +++ b/src/common/__tests__/user-store.test.ts @@ -41,6 +41,7 @@ import { SemVer } from "semver"; import electron from "electron"; import { stdout, stderr } from "process"; import { beforeEachWrapped } from "../../../integration/helpers/utils"; +import { ThemeStore } from "../../renderer/theme.store"; console = new Console(stdout, stderr); @@ -72,7 +73,7 @@ describe("user store tests", () => { us.httpsProxy = "abcd://defg"; expect(us.httpsProxy).toBe("abcd://defg"); - expect(us.colorTheme).toBe(UserStore.defaultTheme); + expect(us.colorTheme).toBe(ThemeStore.defaultTheme); us.colorTheme = "light"; expect(us.colorTheme).toBe("light"); @@ -83,7 +84,7 @@ describe("user store tests", () => { us.colorTheme = "some other theme"; us.resetTheme(); - expect(us.colorTheme).toBe(UserStore.defaultTheme); + expect(us.colorTheme).toBe(ThemeStore.defaultTheme); }); it("correctly calculates if the last seen version is an old release", () => { diff --git a/src/common/user-store/index.ts b/src/common/user-store/index.ts new file mode 100644 index 0000000000..dc00ab6737 --- /dev/null +++ b/src/common/user-store/index.ts @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +export * from "./user-store"; +export type { KubeconfigSyncEntry, KubeconfigSyncValue } from "./preferences-helpers"; diff --git a/src/common/user-store/preferences-helpers.ts b/src/common/user-store/preferences-helpers.ts new file mode 100644 index 0000000000..b35f7ddc51 --- /dev/null +++ b/src/common/user-store/preferences-helpers.ts @@ -0,0 +1,237 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import moment from "moment-timezone"; +import path from "path"; +import os from "os"; +import { ThemeStore } from "../../renderer/theme.store"; +import { ObservableToggleSet } from "../utils"; + +export interface KubeconfigSyncEntry extends KubeconfigSyncValue { + filePath: string; +} + +export interface KubeconfigSyncValue { } + +interface PreferenceDescription { + fromStore(val: T | undefined): R; + toStore(val: R): T | undefined; +} + +const httpsProxy: PreferenceDescription = { + fromStore(val) { + return val; + }, + toStore(val) { + return val || undefined; + }, +}; + +const shell: PreferenceDescription = { + fromStore(val) { + return val; + }, + toStore(val) { + return val || undefined; + }, +}; + +const colorTheme: PreferenceDescription = { + fromStore(val) { + return val || ThemeStore.defaultTheme; + }, + toStore(val) { + if (!val || val === ThemeStore.defaultTheme) { + return undefined; + } + + return val; + }, +}; + +const localeTimezone: PreferenceDescription = { + fromStore(val) { + return val || moment.tz.guess(true) || "UTC"; + }, + toStore(val) { + if (!val || val === moment.tz.guess(true) || val === "UTC") { + return undefined; + } + + return val; + }, +}; + +const allowUntrustedCAs: PreferenceDescription = { + fromStore(val) { + return val ?? false; + }, + toStore(val) { + if (!val) { + return undefined; + } + + return val; + }, +}; + +const allowTelemetry: PreferenceDescription = { + fromStore(val) { + return val ?? true; + }, + toStore(val) { + if (val === true) { + return undefined; + } + + return val; + }, +}; + +const downloadMirror: PreferenceDescription = { + fromStore(val) { + return val ?? "default"; + }, + toStore(val) { + if (!val || val === "default") { + return undefined; + } + + return val; + }, +}; + +const downloadKubectlBinaries: PreferenceDescription = { + fromStore(val) { + return val ?? true; + }, + toStore(val) { + if (val === true) { + return undefined; + } + + return val; + }, +}; + +const downloadBinariesPath: PreferenceDescription = { + fromStore(val) { + return val; + }, + toStore(val) { + if (!val) { + return undefined; + } + + return val; + }, +}; + +const kubectlBinariesPath: PreferenceDescription = { + fromStore(val) { + return val; + }, + toStore(val) { + if (!val) { + return undefined; + } + + return val; + }, +}; + +const openAtLogin: PreferenceDescription = { + fromStore(val) { + return val ?? false; + }, + toStore(val) { + if (!val) { + return undefined; + } + + return val; + }, +}; + +const hiddenTableColumns: PreferenceDescription<[string, string[]][], Map>> = { + fromStore(val) { + return new Map( + (val ?? []).map(([tableId, columnIds]) => [tableId, new ObservableToggleSet(columnIds)]) + ); + }, + toStore(val) { + const res: [string, string[]][] = []; + + for (const [table, columnes] of val) { + if (columnes.size) { + res.push([table, Array.from(columnes)]); + } + } + + return res.length ? res : undefined; + }, +}; + +const mainKubeFolder = path.join(os.homedir(), ".kube"); + +const syncKubeconfigEntries: PreferenceDescription> = { + fromStore(val) { + return new Map( + val + ?.map(({ filePath, ...rest }) => [filePath, rest]) + ?? [[mainKubeFolder, {}]] + ); + }, + toStore(val) { + if (val.size === 1 && val.has(mainKubeFolder)) { + return undefined; + } + + return Array.from(val, ([filePath, rest]) => ({ filePath, ...rest })); + }, +}; + +type PreferencesModelType = typeof DESCRIPTORS[field] extends PreferenceDescription ? T : never; +type UserStoreModelType = typeof DESCRIPTORS[field] extends PreferenceDescription ? T : never; + +export type UserStoreFlatModel = { + [field in keyof typeof DESCRIPTORS]: UserStoreModelType; +}; + +export type UserPreferencesModel = { + [field in keyof typeof DESCRIPTORS]: PreferencesModelType; +}; + +export const DESCRIPTORS = { + httpsProxy, + shell, + colorTheme, + localeTimezone, + allowUntrustedCAs, + allowTelemetry, + downloadMirror, + downloadKubectlBinaries, + downloadBinariesPath, + kubectlBinariesPath, + openAtLogin, + hiddenTableColumns, + syncKubeconfigEntries, +}; diff --git a/src/common/user-store.ts b/src/common/user-store/user-store.ts similarity index 55% rename from src/common/user-store.ts rename to src/common/user-store/user-store.ts index c1e956b331..1c0a5852c9 100644 --- a/src/common/user-store.ts +++ b/src/common/user-store/user-store.ts @@ -19,50 +19,25 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import type { ThemeId } from "../renderer/theme.store"; import { app, remote } from "electron"; import semver from "semver"; import { action, computed, observable, reaction, makeObservable } from "mobx"; -import moment from "moment-timezone"; -import { BaseStore } from "./base-store"; -import migrations from "../migrations/user-store"; -import { getAppVersion } from "./utils/app-version"; -import { appEventBus } from "./event-bus"; +import { BaseStore } from "../base-store"; +import migrations from "../../migrations/user-store"; +import { getAppVersion } from "../utils/app-version"; +import { kubeConfigDefaultPath } from "../kube-helpers"; +import { appEventBus } from "../event-bus"; import path from "path"; -import os from "os"; -import { fileNameMigration } from "../migrations/user-store"; -import { ObservableToggleSet, toJS } from "../renderer/utils"; +import { fileNameMigration } from "../../migrations/user-store"; +import { ObservableToggleSet, toJS } from "../../renderer/utils"; +import { DESCRIPTORS, KubeconfigSyncValue, UserPreferencesModel } from "./preferences-helpers"; export interface UserStoreModel { lastSeenAppVersion: string; preferences: UserPreferencesModel; } -export interface KubeconfigSyncEntry extends KubeconfigSyncValue { - filePath: string; -} - -export interface KubeconfigSyncValue { } - -export interface UserPreferencesModel { - httpsProxy?: string; - shell?: string; - colorTheme?: string; - localeTimezone?: string; - allowUntrustedCAs?: boolean; - allowTelemetry?: boolean; - downloadMirror?: string | "default"; - downloadKubectlBinaries?: boolean; - downloadBinariesPath?: string; - kubectlBinariesPath?: string; - openAtLogin?: boolean; - hiddenTableColumns?: [string, string[]][]; - syncKubeconfigEntries?: KubeconfigSyncEntry[]; -} - -export class UserStore extends BaseStore { - static readonly defaultTheme: ThemeId = "lens-dark"; - +export class UserStore extends BaseStore /* implements UserStoreFlatModel (when strict null is enabled) */ { constructor() { super({ configName: "lens-user-store", @@ -75,11 +50,18 @@ export class UserStore extends BaseStore { } @observable lastSeenAppVersion = "0.0.0"; - @observable allowTelemetry = true; - @observable allowUntrustedCAs = false; - @observable colorTheme = UserStore.defaultTheme; - @observable localeTimezone = moment.tz.guess(true) || "UTC"; - @observable downloadMirror = "default"; + + /** + * used in add-cluster page for providing context + */ + @observable kubeConfigPath = kubeConfigDefaultPath; + @observable seenContexts = observable.set(); + @observable newContexts = observable.set(); + @observable allowTelemetry: boolean; + @observable allowUntrustedCAs: boolean; + @observable colorTheme: string; + @observable localeTimezone: string; + @observable downloadMirror: string; @observable httpsProxy?: string; @observable shell?: string; @observable downloadBinariesPath?: string; @@ -88,8 +70,8 @@ export class UserStore extends BaseStore { /** * Download kubectl binaries matching cluster version */ - @observable downloadKubectlBinaries = true; - @observable openAtLogin = false; + @observable downloadKubectlBinaries: boolean; + @observable openAtLogin: boolean; /** * The column IDs under each configurable table ID that have been configured @@ -100,9 +82,7 @@ export class UserStore extends BaseStore { /** * The set of file/folder paths to be synced */ - syncKubeconfigEntries = observable.map([ - [path.join(os.homedir(), ".kube"), {}] - ]); + syncKubeconfigEntries = observable.map(); @computed get isNewVersion() { return semver.gt(getAppVersion(), this.lastSeenAppVersion); @@ -160,7 +140,7 @@ export class UserStore extends BaseStore { @action resetTheme() { - this.colorTheme = UserStore.defaultTheme; + this.colorTheme = DESCRIPTORS.colorTheme.fromStore(undefined); } @action @@ -182,64 +162,38 @@ export class UserStore extends BaseStore { this.lastSeenAppVersion = lastSeenAppVersion; } - if (!preferences) { - return; - } - - this.httpsProxy = preferences.httpsProxy; - this.shell = preferences.shell; - this.colorTheme = preferences.colorTheme; - this.localeTimezone = preferences.localeTimezone; - this.allowUntrustedCAs = preferences.allowUntrustedCAs; - this.allowTelemetry = preferences.allowTelemetry; - this.downloadMirror = preferences.downloadMirror; - this.downloadKubectlBinaries = preferences.downloadKubectlBinaries; - this.downloadBinariesPath = preferences.downloadBinariesPath; - this.kubectlBinariesPath = preferences.kubectlBinariesPath; - this.openAtLogin = preferences.openAtLogin; - - if (preferences.hiddenTableColumns) { - this.hiddenTableColumns.replace( - preferences.hiddenTableColumns - .map(([tableId, columnIds]) => [tableId, new ObservableToggleSet(columnIds)]) - ); - } - - if (preferences.syncKubeconfigEntries) { - this.syncKubeconfigEntries.replace( - preferences.syncKubeconfigEntries.map(({ filePath, ...rest }) => [filePath, rest]) - ); - } + this.httpsProxy = DESCRIPTORS.httpsProxy.fromStore(preferences?.httpsProxy); + this.shell = DESCRIPTORS.shell.fromStore(preferences?.shell); + this.colorTheme = DESCRIPTORS.colorTheme.fromStore(preferences?.colorTheme); + this.localeTimezone = DESCRIPTORS.localeTimezone.fromStore(preferences?.localeTimezone); + this.allowUntrustedCAs = DESCRIPTORS.allowUntrustedCAs.fromStore(preferences?.allowUntrustedCAs); + this.allowTelemetry = DESCRIPTORS.allowTelemetry.fromStore(preferences?.allowTelemetry); + this.downloadMirror = DESCRIPTORS.downloadMirror.fromStore(preferences?.downloadMirror); + this.downloadKubectlBinaries = DESCRIPTORS.downloadKubectlBinaries.fromStore(preferences?.downloadKubectlBinaries); + this.downloadBinariesPath = DESCRIPTORS.downloadBinariesPath.fromStore(preferences?.downloadBinariesPath); + this.kubectlBinariesPath = DESCRIPTORS.kubectlBinariesPath.fromStore(preferences?.kubectlBinariesPath); + this.openAtLogin = DESCRIPTORS.openAtLogin.fromStore(preferences?.openAtLogin); + this.hiddenTableColumns.replace(DESCRIPTORS.hiddenTableColumns.fromStore(preferences?.hiddenTableColumns)); + this.syncKubeconfigEntries.replace(DESCRIPTORS.syncKubeconfigEntries.fromStore(preferences?.syncKubeconfigEntries)); } toJSON(): UserStoreModel { - const hiddenTableColumns: [string, string[]][] = []; - const syncKubeconfigEntries: KubeconfigSyncEntry[] = []; - - for (const [key, values] of this.hiddenTableColumns.entries()) { - hiddenTableColumns.push([key, Array.from(values)]); - } - - for (const [filePath, rest] of this.syncKubeconfigEntries) { - syncKubeconfigEntries.push({ filePath, ...rest }); - } - const model: UserStoreModel = { lastSeenAppVersion: this.lastSeenAppVersion, preferences: { - httpsProxy: toJS(this.httpsProxy), - shell: toJS(this.shell), - colorTheme: toJS(this.colorTheme), - localeTimezone: toJS(this.localeTimezone), - allowUntrustedCAs: toJS(this.allowUntrustedCAs), - allowTelemetry: toJS(this.allowTelemetry), - downloadMirror: toJS(this.downloadMirror), - downloadKubectlBinaries: toJS(this.downloadKubectlBinaries), - downloadBinariesPath: toJS(this.downloadBinariesPath), - kubectlBinariesPath: toJS(this.kubectlBinariesPath), - openAtLogin: toJS(this.openAtLogin), - hiddenTableColumns, - syncKubeconfigEntries, + httpsProxy: DESCRIPTORS.httpsProxy.toStore(this.httpsProxy), + shell: DESCRIPTORS.shell.toStore(this.shell), + colorTheme: DESCRIPTORS.colorTheme.toStore(this.colorTheme), + localeTimezone: DESCRIPTORS.localeTimezone.toStore(this.localeTimezone), + allowUntrustedCAs: DESCRIPTORS.allowUntrustedCAs.toStore(this.allowUntrustedCAs), + allowTelemetry: DESCRIPTORS.allowTelemetry.toStore(this.allowTelemetry), + downloadMirror: DESCRIPTORS.downloadMirror.toStore(this.downloadMirror), + downloadKubectlBinaries: DESCRIPTORS.downloadKubectlBinaries.toStore(this.downloadKubectlBinaries), + downloadBinariesPath: DESCRIPTORS.downloadBinariesPath.toStore(this.downloadBinariesPath), + kubectlBinariesPath: DESCRIPTORS.kubectlBinariesPath.toStore(this.kubectlBinariesPath), + openAtLogin: DESCRIPTORS.openAtLogin.toStore(this.openAtLogin), + hiddenTableColumns: DESCRIPTORS.hiddenTableColumns.toStore(this.hiddenTableColumns), + syncKubeconfigEntries: DESCRIPTORS.syncKubeconfigEntries.toStore(this.syncKubeconfigEntries), }, }; diff --git a/src/renderer/theme.store.ts b/src/renderer/theme.store.ts index 6caf9cf78a..5bfd76210d 100644 --- a/src/renderer/theme.store.ts +++ b/src/renderer/theme.store.ts @@ -46,6 +46,7 @@ export interface ThemeItems extends Theme { } export class ThemeStore extends Singleton { + static readonly defaultTheme = "lens-dark"; protected styles: HTMLStyleElement; // bundled themes from `themes/${themeId}.json`