Make base store non Singleton (#6690)

* Remove Singleton from BaseStore to remove global shared state

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove more usages of Singleton

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Replace use of legacy global execHelm with injectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove last use of legacy global execHelm

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Extract BaseStore deps into constructor argument

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Introduce method to make store migrations injectable
- Use it for ClusterStore

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Switch HotbarStore to injectable migrations

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Switch UserStore to injectable migrations

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Move migration utils into common/utils/

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Switch WeblinkStore to injectable migrations

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove dead code

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix type error in base-store tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove tests that reference lastSeenVersion
- That value is not used anywhere in code

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove usage of legacy global .getInstance

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove usage of legacy global ClusterStore.getInstance

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add simple migrations dependency for stores without any preexisting migrations

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix messed up import

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add typing to transient injectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Cleanup formatting

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix typing in tests to satisfy requirement to have cacheFile

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* More consistent use of BaseStore.displayName

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add catching of error while starting main application

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Move initializing sentry to runnable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove unneeded appPathsInjectionToken
- Only had once impl, which was in common anyway

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add support for multiple "runAfter" runnables
- Needed so that several dependencies can be declared

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Use multiple runAfter support to fix crash on renderer

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove traces

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add global override to fix tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix base store tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix runManyFor tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix hotbar store tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix user store tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add global override for getConfigurationFileModel to fix tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove overrides for configuration stores

- Now that there is an override for getConfiguration

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Overhaul FS fakes with full in-memory filesystem
- This increases our confidence in fs related logic

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove use of global shared Electron.App

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add fake access support

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Handle copy as part of fake FS

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add ensureDir/Sync support to fake FS

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix type error

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Use pathExistsSync instead of fsInjectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add createReadStream to fake FS

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add stat to fake FS

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove dead code

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix test failures due to incomplete overrides

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fully injectable-ize BaseStore so that ApplicationBuilder tests work

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Consolidate more bootstrapping into startFrame

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Move initializing CatalogCategories to runnable in bootstrap

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Convert contextMenuOpen initializers into runnables

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Convert navigateForExtension init to runnable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Make cluster state sync fully injectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Move init hotbar store into runnables

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Make LensTheme fully injectable and runnable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Cleanup old code from missed from previous commit

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Manually split out terminal color names and fully type LensTheme

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix old imports

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove unnecessart awaits

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove dead code

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fully cherry pick injectablizing custom monaco themes

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix duplicate mock warning

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix incorrectly fully cherry picking new runnable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Complete cherry-pick of current cluster injcetablization

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix override file name

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix injecting before app paths are set up

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix injecting before app paths are set up

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix ordering of runnable and order of injection

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Convert all renderer runnables to late-inject style
- To help fix issues around injection time

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix react-beautiful-dnd mocks

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Update and fix WriteJson(Sync) to fix error in tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix HotbarStore.load being called twice is being buggy

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Update listing-active-helm-repositories-in-preferences snapshots

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix sidebar-and-tab-navigation-tests
- Move enabling extensions in tests to a proper location
- Fix flushing promises

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove props from dnd mock to make snapshot diffs smaller

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix import

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Update snapshots

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix tests by overriding things that are no longer overriden by default
- NOTE: They are overridden when using ApplicationBuilder

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix hotbar store tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix cluster store tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix extension-loader tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix extension-discovery tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix cluster-role-dialog tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix user store tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix kubeconfig sync tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix sidebar and tab tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove unused code

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix pick paths import type error and simplify signature

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix type error in legacy ipc registration

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove another use of legacy requestOpenPathPicker

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Replace use of legacy global PathPicker.Pick

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix usage in light of changed prop names

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix catalog tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix more type errors

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix test flakiness by removing side effects from userStore preferences

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Update snapshots

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix loading

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix type error

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix crash

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Cherry pick updated startFrameInjectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add tests to verify runMany behaviour in new possible incorrect configuration

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix init ordering during start frame

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix cluster state sync

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Update snapshots after removing side-effects

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add override for technical test

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Correctly mark currentlyInClusterFrame as causedSideEffects

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Better formatting

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix behaviour regression

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add better logging

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix BaseStore sync

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Update last snapshot

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add global override for randomBytes

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Make startMainApplication not an injection time side effect

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Choose better names for start-frame runnable tokens

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove duplication of code in RunManyFor

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add unit tests and fix handling empty runAfter array

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Replace use of mobx from runManyFor with custom barrier

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add missing test

Signed-off-by: Sebastian Malton <sebastian@malton.name>

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton authored and GitHub committed 2022-12-15 17:07:19 +02:00
1 parent 7c5faaaf1d
commit 25f37ac1d1
364 files changed
+10035 -5246

No files matched your search

@@ -4,28 +4,25 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import appPathsStateInjectable from "../../common/app-paths/app-paths-state.injectable";
import { beforeFrameStartsInjectionToken } from "../before-frame-starts/before-frame-starts-injection-token";
import { beforeFrameStartsFirstInjectionToken } from "../before-frame-starts/tokens";
import { appPathsChannel } from "../../common/app-paths/app-paths-channel";
import { requestFromChannelInjectionToken } from "../../common/utils/channel/request-from-channel-injection-token";
const setupAppPathsInjectable = getInjectable({
id: "setup-app-paths",
instantiate: (di) => {
const requestFromChannel = di.inject(requestFromChannelInjectionToken);
const appPathsState = di.inject(appPathsStateInjectable);
instantiate: (di) => ({
id: "setup-app-paths",
run: async () => {
const requestFromChannel = di.inject(requestFromChannelInjectionToken);
const appPathsState = di.inject(appPathsStateInjectable);
const appPaths = await requestFromChannel(appPathsChannel);
return {
id: "setup-app-paths",
run: async () => {
const appPaths = await requestFromChannel(appPathsChannel);
appPathsState.set(appPaths);
},
}),
appPathsState.set(appPaths);
},
};
},
injectionToken: beforeFrameStartsInjectionToken,
injectionToken: beforeFrameStartsFirstInjectionToken,
});
export default setupAppPathsInjectable;
@@ -0,0 +1,14 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { shouldBaseStoreDisableSyncInIpcListenerInjectionToken } from "../../common/base-store/disable-sync";
const shouldBaseStoreDisableSyncInIpcListenerInjectable = getInjectable({
id: "should-base-store-disable-sync-in-ipc-listener",
instantiate: () => true,
injectionToken: shouldBaseStoreDisableSyncInIpcListenerInjectionToken,
});
export default shouldBaseStoreDisableSyncInIpcListenerInjectable;
@@ -0,0 +1,17 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { baseStoreIpcChannelPrefixesInjectionToken } from "../../common/base-store/channel-prefix";
const baseStoreIpcChannelPrefixInjectable = getInjectable({
id: "base-store-ipc-channel-prefix",
instantiate: () => ({
local: "store-sync-renderer",
remote: "store-sync-main",
}),
injectionToken: baseStoreIpcChannelPrefixesInjectionToken,
});
export default baseStoreIpcChannelPrefixInjectable;
@@ -0,0 +1,15 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { persistStateToConfigInjectionToken } from "../../common/base-store/save-to-file";
import { noop } from "../utils";
const persistStateToConfigInjectable = getInjectable({
id: "persist-state-to-config",
instantiate: () => noop,
injectionToken: persistStateToConfigInjectionToken,
});
export default persistStateToConfigInjectable;
@@ -1,10 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectionToken } from "@ogre-tools/injectable";
import type { Runnable } from "../../common/runnable/run-many-for";
export const beforeFrameStartsInjectionToken = getInjectionToken<Runnable>({
id: "before-frame-starts",
});
@@ -0,0 +1,23 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { setAutoFreeze, enableMapSet } from "immer";
import { beforeFrameStartsFirstInjectionToken } from "../tokens";
const configureImmerInjectable = getInjectable({
id: "configure-immer",
instantiate: () => ({
id: "configure-immer",
run: () => {
// Docs: https://immerjs.github.io/immer/
// Required in `utils/storage-helper.ts`
setAutoFreeze(false); // allow to merge mobx observables
enableMapSet(); // allow to merge maps and sets
},
}),
injectionToken: beforeFrameStartsFirstInjectionToken,
});
export default configureImmerInjectable;
@@ -0,0 +1,28 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { configure } from "mobx";
import { beforeFrameStartsFirstInjectionToken } from "../tokens";
const configureMobxInjectable = getInjectable({
id: "configure-mobx",
instantiate: () => ({
id: "configure-mobx",
run: () => {
// Docs: https://mobx.js.org/configuration.html
configure({
enforceActions: "never",
// TODO: enable later (read more: https://mobx.js.org/migrating-from-4-or-5.html)
// computedRequiresReaction: true,
// reactionRequiresObservable: true,
// observableRequiresReaction: true,
});
},
}),
injectionToken: beforeFrameStartsFirstInjectionToken,
});
export default configureMobxInjectable;
@@ -0,0 +1,24 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { customMonacoThemeInjectionToken } from "../../components/monaco-editor";
import addNewMonacoThemeInjectable from "../../monaco/add-new-theme.injectable";
import { beforeFrameStartsInjectionToken } from "../tokens";
const loadMonacoThemesInjectable = getInjectable({
id: "load-monaco-themes",
instantiate: (di) => ({
id: "load-monaco-themes",
run: () => {
const customThemes = di.injectMany(customMonacoThemeInjectionToken);
const addNewMonacoTheme = di.inject(addNewMonacoThemeInjectable);
customThemes.forEach(addNewMonacoTheme);
},
}),
injectionToken: beforeFrameStartsInjectionToken,
});
export default loadMonacoThemesInjectable;
@@ -0,0 +1,79 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import autoRegistrationEmitterInjectable from "../../../common/k8s-api/api-manager/auto-registration-emitter.injectable";
import apiManagerInjectable from "../../../common/k8s-api/api-manager/manager.injectable";
import { CustomResourceStore } from "../../../common/k8s-api/api-manager/resource.store";
import type { CustomResourceDefinition } from "../../../common/k8s-api/endpoints";
import { KubeApi } from "../../../common/k8s-api/kube-api";
import { KubeObject } from "../../../common/k8s-api/kube-object";
import { beforeClusterFrameStartsInjectionToken } from "../tokens";
const setupAutoRegistrationInjectable = getInjectable({
id: "setup-auto-registration",
instantiate: (di) => ({
id: "setup-auto-registration",
run: () => {
const autoRegistrationEmitter = di.inject(autoRegistrationEmitterInjectable);
const beforeApiManagerInitializationCrds: CustomResourceDefinition[] = [];
const beforeApiManagerInitializationApis: KubeApi[] = [];
let initialized = false;
const autoInitCustomResourceStore = (crd: CustomResourceDefinition) => {
const objectConstructor = class extends KubeObject {
static readonly kind = crd.getResourceKind();
static readonly namespaced = crd.isNamespaced();
static readonly apiBase = crd.getResourceApiBase();
};
const api = (() => {
const rawApi = apiManager.getApi(objectConstructor.apiBase);
if (rawApi) {
return rawApi;
}
const api = new KubeApi({ objectConstructor });
apiManager.registerApi(api);
return api;
})();
if (!apiManager.getStore(api)) {
apiManager.registerStore(new CustomResourceStore(api));
}
};
const autoInitKubeApi = (api: KubeApi) => {
apiManager.registerApi(api);
};
autoRegistrationEmitter
.on("customResourceDefinition", (crd) => {
if (initialized) {
autoInitCustomResourceStore(crd);
} else {
beforeApiManagerInitializationCrds.push(crd);
}
})
.on("kubeApi", (api) => {
if (initialized) {
autoInitKubeApi(api);
} else {
beforeApiManagerInitializationApis.push(api);
}
});
const apiManager = di.inject(apiManagerInjectable);
beforeApiManagerInitializationCrds.forEach(autoInitCustomResourceStore);
beforeApiManagerInitializationApis.forEach(autoInitKubeApi);
initialized = true;
},
}),
injectionToken: beforeClusterFrameStartsInjectionToken,
});
export default setupAutoRegistrationInjectable;
@@ -0,0 +1,32 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { reaction } from "mobx";
import { currentClusterMessageChannel } from "../../../common/cluster/current-cluster-channel";
import { sendMessageToChannelInjectionToken } from "../../../common/utils/channel/message-to-channel-injection-token";
import matchedClusterIdInjectable from "../../navigation/matched-cluster-id.injectable";
import { beforeMainFrameStartsInjectionToken } from "../tokens";
const setupCurrentClusterBroadcastInjectable = getInjectable({
id: "setup-current-cluster-broadcast",
instantiate: (di) => ({
id: "setup-current-cluster-broadcast",
run: () => {
const matchedClusterId = di.inject(matchedClusterIdInjectable);
const sendMessageToChannel = di.inject(sendMessageToChannelInjectionToken);
reaction(
() => matchedClusterId.get(),
clusterId => sendMessageToChannel(currentClusterMessageChannel, clusterId),
{
fireImmediately: true,
},
);
},
}),
injectionToken: beforeMainFrameStartsInjectionToken,
});
export default setupCurrentClusterBroadcastInjectable;
@@ -0,0 +1,81 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import kubernetesClusterCategoryInjectable from "../../../common/catalog/categories/kubernetes-cluster.injectable";
import navigateToAddClusterInjectable from "../../../common/front-end-routing/routes/add-cluster/navigate-to-add-cluster.injectable";
import isLinuxInjectable from "../../../common/vars/is-linux.injectable";
import isWindowsInjectable from "../../../common/vars/is-windows.injectable";
import openPathPickingDialogInjectable from "../../../features/path-picking-dialog/renderer/pick-paths.injectable";
import addSyncEntriesInjectable from "../../initializers/add-sync-entries.injectable";
import { beforeFrameStartsInjectionToken } from "../tokens";
const setupKubernetesClusterCatalogAddMenuListenerInjectable = getInjectable({
id: "setup-kubernetes-cluster-catalog-add-menu-listener",
instantiate: (di) => ({
id: "setup-kubernetes-cluster-catalog-add-menu-listener",
run: () => {
// NOTE: these have to be here so that they are initialized only after the `runAfter` is ran
const navigateToAddCluster = di.inject(navigateToAddClusterInjectable);
const addSyncEntries = di.inject(addSyncEntriesInjectable);
const kubernetesClusterCategory = di.inject(kubernetesClusterCategoryInjectable);
const isWindows = di.inject(isWindowsInjectable);
const isLinux = di.inject(isLinuxInjectable);
const openPathPickingDialog = di.inject(openPathPickingDialogInjectable);
kubernetesClusterCategory.on("catalogAddMenu", ctx => {
ctx.menuItems.push(
{
icon: "text_snippet",
title: "Add from kubeconfig",
onClick: navigateToAddCluster,
},
);
if (isWindows || isLinux) {
ctx.menuItems.push(
{
icon: "create_new_folder",
title: "Sync kubeconfig folder(s)",
defaultAction: true,
onClick: () => openPathPickingDialog({
message: "Sync folder(s)",
buttonLabel: "Sync",
properties: ["showHiddenFiles", "multiSelections", "openDirectory"],
onPick: addSyncEntries,
}),
},
{
icon: "note_add",
title: "Sync kubeconfig file(s)",
onClick: () => openPathPickingDialog({
message: "Sync file(s)",
buttonLabel: "Sync",
properties: ["showHiddenFiles", "multiSelections", "openFile"],
onPick: addSyncEntries,
}),
},
);
} else {
ctx.menuItems.push(
{
icon: "create_new_folder",
title: "Sync kubeconfig(s)",
defaultAction: true,
onClick: () => openPathPickingDialog({
message: "Sync file(s)",
buttonLabel: "Sync",
properties: ["showHiddenFiles", "multiSelections", "openFile", "openDirectory"],
onPick: addSyncEntries,
}),
},
);
}
});
},
}),
injectionToken: beforeFrameStartsInjectionToken,
});
export default setupKubernetesClusterCatalogAddMenuListenerInjectable;
@@ -0,0 +1,56 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import catalogCategoryRegistryInjectable from "../../../common/catalog/category-registry.injectable";
import getClusterByIdInjectable from "../../../common/cluster-store/get-by-id.injectable";
import readFileInjectable from "../../../common/fs/read-file.injectable";
import { loadConfigFromString } from "../../../common/kube-helpers";
import loggerInjectable from "../../../common/logger.injectable";
import openDeleteClusterDialogInjectable from "../../components/delete-cluster-dialog/open.injectable";
import { beforeFrameStartsInjectionToken } from "../tokens";
const setupKubernetesClusterContextMenuOpenInjectable = getInjectable({
id: "setup-kubernetes-cluster-context-menu-open",
instantiate: (di) => ({
id: "setup-kubernetes-cluster-context-menu-open",
run: () => {
const catalogCategoryRegistry = di.inject(catalogCategoryRegistryInjectable);
const openDeleteClusterDialog = di.inject(openDeleteClusterDialogInjectable);
const readFile = di.inject(readFileInjectable);
const getClusterById = di.inject(getClusterByIdInjectable);
const logger = di.inject(loggerInjectable);
catalogCategoryRegistry
.getForGroupKind("entity.k8slens.dev", "KubernetesCluster")
?.on("contextMenuOpen", (entity, context) => {
if (entity.metadata?.source == "local") {
context.menuItems.push({
title: "Remove",
icon: "delete",
onClick: async () => {
const clusterId = entity.getId();
const cluster = getClusterById(entity.getId());
if (!cluster) {
return logger.warn("[KUBERNETES-CLUSTER]: cannot delete cluster, does not exist in store", { clusterId });
}
const result = loadConfigFromString(await readFile(cluster.kubeConfigPath));
if (result.error) {
logger.error("[KUBERNETES-CLUSTER]: failed to parse kubeconfig file", result.error);
} else {
openDeleteClusterDialog({ cluster, config: result.config });
}
},
});
}
});
},
}),
injectionToken: beforeFrameStartsInjectionToken,
});
export default setupKubernetesClusterContextMenuOpenInjectable;
@@ -0,0 +1,23 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import isMacInjectable from "../../../common/vars/is-mac.injectable";
import { beforeFrameStartsInjectionToken } from "../tokens";
const setupRootMacClassnameInjectable = getInjectable({
id: "setup-root-mac-classname",
instantiate: (di) => ({
id: "setup-root-mac-classname",
run: () => {
const isMac = di.inject(isMacInjectable);
const rootElem = document.getElementById("app");
rootElem?.classList.toggle("is-mac", isMac);
},
}),
injectionToken: beforeFrameStartsInjectionToken,
});
export default setupRootMacClassnameInjectable;
@@ -0,0 +1,23 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import initializeSentryReportingWithInjectable from "../../../common/error-reporting/initialize-sentry-reporting.injectable";
import { beforeMainFrameStartsInjectionToken } from "../tokens";
import { init } from "@sentry/electron/renderer";
const setupSentryInjectable = getInjectable({
id: "setup-sentry",
instantiate: (di) => ({
id: "setup-sentry",
run: () => {
const initializeSentryReportingWith = di.inject(initializeSentryReportingWithInjectable);
initializeSentryReportingWith(init);
},
}),
injectionToken: beforeMainFrameStartsInjectionToken,
});
export default setupSentryInjectable;
@@ -0,0 +1,34 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import React from "react";
import catalogCategoryRegistryInjectable from "../../../common/catalog/category-registry.injectable";
import { WeblinkAddCommand } from "../../components/catalog-entities/weblink-add-command";
import commandOverlayInjectable from "../../components/command-palette/command-overlay.injectable";
import { beforeFrameStartsInjectionToken } from "../tokens";
const setupWeblickContextMenuOpenInjectable = getInjectable({
id: "setup-weblick-context-menu-open",
instantiate: (di) => ({
id: "setup-weblick-context-menu-open",
run: () => {
const catalogCategoryRegistry = di.inject(catalogCategoryRegistryInjectable);
const commandOverlay = di.inject(commandOverlayInjectable);
catalogCategoryRegistry
.getForGroupKind("entity.k8slens.dev", "WebLink")
?.on("catalogAddMenu", ctx => {
ctx.menuItems.push({
title: "Add web link",
icon: "public",
onClick: () => commandOverlay.open(<WeblinkAddCommand />),
});
});
},
}),
injectionToken: beforeFrameStartsInjectionToken,
});
export default setupWeblickContextMenuOpenInjectable;
@@ -0,0 +1,26 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectionToken } from "@ogre-tools/injectable";
import type { Runnable } from "../../common/runnable/run-many-for";
// NOTE: these are run before any other token, mostly to set up things that all other runnables need
export const beforeFrameStartsFirstInjectionToken = getInjectionToken<Runnable>({
id: "even-before-frame-starts",
});
// NOTE: these are only run when process.isMainFrame === true
export const beforeMainFrameStartsInjectionToken = getInjectionToken<Runnable>({
id: "even-before-main-frame-starts",
});
// NOTE: these are only run when process.isMainFrame === false
export const beforeClusterFrameStartsInjectionToken = getInjectionToken<Runnable>({
id: "even-before-cluster-frame-starts",
});
export const beforeFrameStartsInjectionToken = getInjectionToken<Runnable>({
id: "before-frame-starts",
});
-91
View File
@@ -13,132 +13,41 @@ import * as ReactRouter from "react-router";
import * as ReactRouterDom from "react-router-dom";
import * as LensExtensionsCommonApi from "../extensions/common-api";
import * as LensExtensionsRendererApi from "../extensions/renderer-api";
import { delay } from "../common/utils";
import { isMac, isDevelopment } from "../common/vars";
import { DefaultProps } from "./mui-base-theme";
import configurePackages from "../common/configure-packages";
import * as initializers from "./initializers";
import logger from "../common/logger";
import { WeblinkStore } from "../common/weblink-store";
import { registerCustomThemes } from "./components/monaco-editor";
import { getDi } from "./getDi";
import { DiContextProvider } from "@ogre-tools/injectable-react";
import type { DiContainer } from "@ogre-tools/injectable";
import extensionLoaderInjectable from "../extensions/extension-loader/extension-loader.injectable";
import extensionDiscoveryInjectable from "../extensions/extension-discovery/extension-discovery.injectable";
import extensionInstallationStateStoreInjectable from "../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
import clusterStoreInjectable from "../common/cluster-store/cluster-store.injectable";
import initRootFrameInjectable from "./frames/root-frame/init-root-frame/init-root-frame.injectable";
import initClusterFrameInjectable from "./frames/cluster-frame/init-cluster-frame/init-cluster-frame.injectable";
import commandOverlayInjectable from "./components/command-palette/command-overlay.injectable";
import { Router } from "react-router";
import historyInjectable from "./navigation/history.injectable";
import themeStoreInjectable from "./themes/store.injectable";
import navigateToAddClusterInjectable from "../common/front-end-routing/routes/add-cluster/navigate-to-add-cluster.injectable";
import addSyncEntriesInjectable from "./initializers/add-sync-entries.injectable";
import hotbarStoreInjectable from "../common/hotbars/store.injectable";
import { bindEvents } from "./navigation/events";
import openDeleteClusterDialogInjectable from "./components/delete-cluster-dialog/open.injectable";
import { init } from "@sentry/electron/renderer";
import kubernetesClusterCategoryInjectable from "../common/catalog/categories/kubernetes-cluster.injectable";
import autoRegistrationInjectable from "../common/k8s-api/api-manager/auto-registration.injectable";
import assert from "assert";
import startFrameInjectable from "./start-frame/start-frame.injectable";
import initializeSentryReportingWithInjectable from "../common/error-reporting/initialize-sentry-reporting.injectable";
configurePackages(); // global packages
registerCustomThemes(); // monaco editor themes
/**
* If this is a development build, wait a second to attach
* Chrome Debugger to renderer process
* https://stackoverflow.com/questions/52844870/debugging-electron-renderer-process-with-vscode
*/
async function attachChromeDebugger() {
if (isDevelopment) {
await delay(1000);
}
}
export async function bootstrap(di: DiContainer) {
const initializeSentryReportingWith = di.inject(initializeSentryReportingWithInjectable);
if (process.isMainFrame) {
initializeSentryReportingWith(init);
}
const startFrame = di.inject(startFrameInjectable);
await startFrame();
// TODO: Consolidate import time side-effect to setup time
bindEvents();
const rootElem = document.getElementById("app");
const logPrefix = `[BOOTSTRAP-${process.isMainFrame ? "ROOT" : "CLUSTER"}-FRAME]:`;
assert(rootElem, "#app MUST exist");
/**
* This is injected here to initialize it for the side effect.
*
* The side effect CANNOT be within `apiManagerInjectable` itself since that causes circular
* dependencies with the current need for legacy di use.
*
* This also MUST be done before anything else so that it can start listening for the events for
* auto initialization.
*/
di.inject(autoRegistrationInjectable);
await attachChromeDebugger();
rootElem.classList.toggle("is-mac", isMac);
logger.info(`${logPrefix} initializing CatalogCategoryRegistryEntries`);
initializers.initCatalogCategoryRegistryEntries({
navigateToAddCluster: di.inject(navigateToAddClusterInjectable),
addSyncEntries: di.inject(addSyncEntriesInjectable),
kubernetesClusterCategory: di.inject(kubernetesClusterCategoryInjectable),
});
logger.info(`${logPrefix} initializing Catalog`);
initializers.initCatalog({
openCommandDialog: di.inject(commandOverlayInjectable).open,
openDeleteClusterDialog: di.inject(openDeleteClusterDialogInjectable),
});
const extensionLoader = di.inject(extensionLoaderInjectable);
logger.info(`${logPrefix} initializing IpcRendererListeners`);
initializers.initIpcRendererListeners(extensionLoader);
extensionLoader.init();
const extensionDiscovery = di.inject(extensionDiscoveryInjectable);
extensionDiscovery.init();
// ClusterStore depends on: UserStore
const clusterStore = di.inject(clusterStoreInjectable);
await clusterStore.loadInitialOnRenderer();
// HotbarStore depends on: ClusterStore
di.inject(hotbarStoreInjectable).load();
// ThemeStore depends on: UserStore
// TODO: Remove temporal dependencies
di.inject(themeStoreInjectable);
WeblinkStore.createInstance();
const extensionInstallationStateStore = di.inject(extensionInstallationStateStoreInjectable);
extensionInstallationStateStore.bindIpcListeners();
// Register additional store listeners
clusterStore.registerIpcListener();
let App;
let initializeApp;
@@ -13,6 +13,7 @@ import type { CategoryColumns, GetCategoryColumnsParams } from "../columns/get.i
import getCategoryColumnsInjectable from "../columns/get.injectable";
import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable";
import extensionInjectable from "../../../../extensions/extension-loader/extension/extension.injectable";
import currentlyInClusterFrameInjectable from "../../../routes/currently-in-cluster-frame.injectable";
class TestCategory extends CatalogCategory {
apiVersion = "catalog.k8slens.dev/v1alpha1";
@@ -46,6 +47,8 @@ describe("Custom Category Columns", () => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(hotbarStoreInjectable, () => ({}));
di.override(currentlyInClusterFrameInjectable, () => false);
getCategoryColumns = di.inject(getCategoryColumnsInjectable);
});
@@ -26,6 +26,10 @@ import broadcastMessageInjectable from "../../../common/ipc/broadcast-message.in
import type { AsyncFnMock } from "@async-fn/jest";
import asyncFn from "@async-fn/jest";
import { flushPromises } from "../../../common/test-utils/flush-promises";
import userStoreInjectable from "../../../common/user-store/user-store.injectable";
import releaseChannelInjectable from "../../../common/vars/release-channel.injectable";
import defaultUpdateChannelInjectable from "../../../features/application-update/common/selected-update-channel/default-update-channel.injectable";
import currentlyInClusterFrameInjectable from "../../routes/currently-in-cluster-frame.injectable";
class MockCatalogEntity extends CatalogEntity {
public apiVersion = "api";
@@ -61,11 +65,11 @@ describe("<Catalog />", () => {
let catalogEntityItem: MockCatalogEntity;
let render: DiRender;
beforeEach(() => {
beforeEach(async () => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
di.override(currentlyInClusterFrameInjectable, () => false);
di.override(broadcastMessageInjectable, () => async () => {});
di.permitSideEffects(getConfigurationFileModelInjectable);
@@ -82,6 +86,13 @@ describe("<Catalog />", () => {
Object.assign(catalogEntityStore, {
selectedItem: computed(() => catalogEntityItem),
});
di.override(releaseChannelInjectable, () => ({
get: () => "latest" as const,
init: async () => {},
}));
await di.inject(defaultUpdateChannelInjectable).init();
di.inject(userStoreInjectable).load();
});
describe("can use catalogEntityRegistry.addOnBeforeRun to add hooks for catalog entities", () => {
@@ -27,7 +27,7 @@ import type { PageParam } from "../../navigation";
import type { ToggleKubeDetailsPane } from "../kube-detail-params/toggle-details.injectable";
import kubeSelectedUrlParamInjectable from "../kube-detail-params/kube-selected-url.injectable";
import toggleKubeDetailsPaneInjectable from "../kube-detail-params/toggle-details.injectable";
import type { LensTheme } from "../../themes/store";
import type { LensTheme } from "../../themes/lens-theme";
import activeThemeInjectable from "../../themes/active.injectable";
export interface ClusterIssuesProps {
@@ -16,7 +16,7 @@ import type { PieChartData } from "../chart";
import { PieChart } from "../chart";
import { ClusterNoMetrics } from "./cluster-no-metrics";
import { bytesToUnits, cssNames } from "../../utils";
import type { LensTheme } from "../../themes/store";
import type { LensTheme } from "../../themes/lens-theme";
import { getMetricLastPoints } from "../../../common/k8s-api/endpoints/metrics.api";
import { withInjectables } from "@ogre-tools/injectable-react";
import clusterOverviewStoreInjectable from "./cluster-overview-store/cluster-overview-store.injectable";
@@ -23,12 +23,13 @@ import installExtensionFromInputInjectable from "../install-extension-from-input
import type { ExtensionInstallationStateStore } from "../../../../extensions/extension-installation-state-store/extension-installation-state-store";
import extensionInstallationStateStoreInjectable from "../../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
import { observable, when } from "mobx";
import type { DeleteFile } from "../../../../common/fs/delete-file.injectable";
import deleteFileInjectable from "../../../../common/fs/delete-file.injectable";
import type { RemovePath } from "../../../../common/fs/remove.injectable";
import removePathInjectable from "../../../../common/fs/remove.injectable";
import type { DownloadJson } from "../../../../common/fetch/download-json.injectable";
import type { DownloadBinary } from "../../../../common/fetch/download-binary.injectable";
import downloadJsonInjectable from "../../../../common/fetch/download-json.injectable";
import downloadBinaryInjectable from "../../../../common/fetch/download-binary.injectable";
import currentlyInClusterFrameInjectable from "../../../routes/currently-in-cluster-frame.injectable";
describe("Extensions", () => {
let extensionLoader: ExtensionLoader;
@@ -36,7 +37,7 @@ describe("Extensions", () => {
let installExtensionFromInput: jest.MockedFunction<InstallExtensionFromInput>;
let extensionInstallationStateStore: ExtensionInstallationStateStore;
let render: DiRender;
let deleteFileMock: jest.MockedFunction<DeleteFile>;
let deleteFileMock: jest.MockedFunction<RemovePath>;
let downloadJson: jest.MockedFunction<DownloadJson>;
let downloadBinary: jest.MockedFunction<DownloadBinary>;
@@ -45,6 +46,7 @@ describe("Extensions", () => {
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
di.override(directoryForDownloadsInjectable, () => "some-directory-for-downloads");
di.override(currentlyInClusterFrameInjectable, () => false);
render = renderFor(di);
@@ -52,7 +54,7 @@ describe("Extensions", () => {
di.override(installExtensionFromInputInjectable, () => installExtensionFromInput);
deleteFileMock = jest.fn();
di.override(deleteFileInjectable, () => deleteFileMock);
di.override(removePathInjectable, () => deleteFileMock);
downloadJson = jest.fn().mockImplementation((url) => { throw new Error(`Unexpected call to downloadJson for url=${url}`); });
di.override(downloadJsonInjectable, () => downloadJson);
@@ -1,21 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import {
getInjectable,
} from "@ogre-tools/injectable";
import { UserStore } from "../../../../common/user-store";
import { getBaseRegistryUrl } from "./get-base-registry-url";
const getBaseRegistryUrlInjectable = getInjectable({
id: "get-base-registry-url",
instantiate: () => getBaseRegistryUrl({
// TODO: use injection
getRegistryUrlPreference: () => UserStore.getInstance().extensionRegistryUrl,
}),
});
export default getBaseRegistryUrlInjectable;
@@ -0,0 +1,58 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import React from "react";
import execFileInjectable from "../../../../common/fs/exec-file.injectable";
import loggerInjectable from "../../../../common/logger.injectable";
import { defaultExtensionRegistryUrl } from "../../../../common/user-store/preferences-helpers";
import userStoreInjectable from "../../../../common/user-store/user-store.injectable";
import showErrorNotificationInjectable from "../../notifications/show-error-notification.injectable";
const getBaseRegistryUrlInjectable = getInjectable({
id: "get-base-registry-url",
instantiate: (di) => {
const { extensionRegistryUrl } = di.inject(userStoreInjectable);
const showErrorNotification = di.inject(showErrorNotificationInjectable);
const logger = di.inject(loggerInjectable);
const execFile = di.inject(execFileInjectable);
return async () => {
switch (extensionRegistryUrl.location) {
case "custom":
return extensionRegistryUrl.customUrl;
case "npmrc": {
const filteredEnv = Object.fromEntries(
Object.entries(process.env)
.filter(([key]) => !key.startsWith("npm")),
);
const result = await execFile("npm", ["config", "get", "registry"], { env: filteredEnv });
if (result.callWasSuccessful) {
return result.response.trim();
}
showErrorNotification((
<p>
Failed to get configured registry from
<code>.npmrc</code>
. Falling back to default registry.
</p>
));
logger.warn("[EXTENSIONS]: failed to get configured registry from .npmrc", result.error);
}
// fallthrough
default:
case "default":
return defaultExtensionRegistryUrl;
}
};
},
});
export default getBaseRegistryUrlInjectable;
@@ -1,49 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import type { ExtensionRegistry } from "../../../../common/user-store/preferences-helpers";
import { defaultExtensionRegistryUrl } from "../../../../common/user-store/preferences-helpers";
import { promiseExecFile } from "../../../utils";
import { Notifications } from "../../notifications";
interface Dependencies {
getRegistryUrlPreference: () => ExtensionRegistry;
}
export const getBaseRegistryUrl = ({ getRegistryUrlPreference }: Dependencies) => async () => {
const extensionRegistryUrl = getRegistryUrlPreference();
switch (extensionRegistryUrl.location) {
case "custom":
return extensionRegistryUrl.customUrl;
case "npmrc": {
try {
const filteredEnv = Object.fromEntries(
Object.entries(process.env)
.filter(([key]) => !key.startsWith("npm")),
);
const { stdout } = await promiseExecFile("npm", ["config", "get", "registry"], { env: filteredEnv });
return stdout.trim();
} catch (error) {
Notifications.error((
<p>
Failed to get configured registry from
<code>.npmrc</code>
. Falling back to default registry.
</p>
));
console.warn("[EXTENSIONS]: failed to get configured registry from .npmrc", error);
}
}
// fallthrough
default:
case "default":
return defaultExtensionRegistryUrl;
}
};
@@ -3,37 +3,30 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { requestOpenFilePickingDialog } from "../../ipc";
import { supportedExtensionFormats } from "./supported-extension-formats";
import attemptInstallsInjectable from "./attempt-installs.injectable";
import directoryForDownloadsInjectable from "../../../common/app-paths/directory-for-downloads/directory-for-downloads.injectable";
import openPathPickingDialogInjectable from "../../../features/path-picking-dialog/renderer/pick-paths.injectable";
interface Dependencies {
attemptInstalls: (filePaths: string[]) => Promise<void>;
directoryForDownloads: string;
}
const installFromSelectFileDialog = ({ attemptInstalls, directoryForDownloads }: Dependencies) => async () => {
const { canceled, filePaths } = await requestOpenFilePickingDialog({
defaultPath: directoryForDownloads,
properties: ["openFile", "multiSelections"],
message: `Select extensions to install (formats: ${supportedExtensionFormats.join(", ")}), `,
buttonLabel: "Use configuration",
filters: [{ name: "tarball", extensions: supportedExtensionFormats }],
});
if (!canceled) {
await attemptInstalls(filePaths);
}
};
export type InstallFromSelectFileDialog = () => Promise<void>;
const installFromSelectFileDialogInjectable = getInjectable({
id: "install-from-select-file-dialog",
instantiate: (di) => installFromSelectFileDialog({
attemptInstalls: di.inject(attemptInstallsInjectable),
directoryForDownloads: di.inject(directoryForDownloadsInjectable),
}),
instantiate: (di): InstallFromSelectFileDialog => {
const attemptInstalls = di.inject(attemptInstallsInjectable);
const directoryForDownloads = di.inject(directoryForDownloadsInjectable);
const openPathPickingDialog = di.inject(openPathPickingDialogInjectable);
return () => openPathPickingDialog({
defaultPath: directoryForDownloads,
properties: ["openFile", "multiSelections"],
message: `Select extensions to install (formats: ${supportedExtensionFormats.join(", ")}), `,
buttonLabel: "Use configuration",
filters: [{ name: "tarball", extensions: supportedExtensionFormats }],
onPick: attemptInstalls,
});
},
});
export default installFromSelectFileDialogInjectable;
@@ -8,7 +8,7 @@ import { runInAction, action, observable, computed } from "mobx";
import type { TargetHelmRelease } from "../target-helm-release.injectable";
import type { RequestDetailedHelmRelease, DetailedHelmRelease } from "./request-detailed-helm-release.injectable";
import requestDetailedHelmReleaseInjectable from "./request-detailed-helm-release.injectable";
import type { LensTheme } from "../../../../themes/store";
import type { LensTheme } from "../../../../themes/lens-theme";
import type { RequestHelmReleaseConfiguration } from "../../../../../common/k8s-api/endpoints/helm-releases.api/request-configuration.injectable";
import requestHelmReleaseConfigurationInjectable from "../../../../../common/k8s-api/endpoints/helm-releases.api/request-configuration.injectable";
import { pipeline } from "@ogre-tools/fp";
@@ -12,7 +12,7 @@ import { ResourceMetricsContext } from "../resource-metrics";
import { observer } from "mobx-react";
import { mapValues } from "lodash";
import { type MetricsTab, metricTabOptions } from "../chart/options";
import type { LensTheme } from "../../themes/store";
import type { LensTheme } from "../../themes/lens-theme";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { IComputedValue } from "mobx";
import activeThemeInjectable from "../../themes/active.injectable";
@@ -10,7 +10,7 @@ import { BarChart, memoryOptions } from "../chart";
import { isMetricsEmpty, normalizeMetrics } from "../../../common/k8s-api/endpoints/metrics.api";
import { NoMetrics } from "../resource-metrics/no-metrics";
import { ResourceMetricsContext } from "../resource-metrics";
import type { LensTheme } from "../../themes/store";
import type { LensTheme } from "../../themes/lens-theme";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { IComputedValue } from "mobx";
import activeThemeInjectable from "../../themes/active.injectable";
@@ -12,6 +12,7 @@ import type { DiRender } from "../../../test-utils/renderFor";
import { renderFor } from "../../../test-utils/renderFor";
import clusterRoleStoreInjectable from "../../+cluster-roles/store.injectable";
import storesAndApisCanBeCreatedInjectable from "../../../../stores-apis-can-be-created.injectable";
import directoryForUserDataInjectable from "../../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
describe("ClusterRoleBindingDialog tests", () => {
let render: DiRender;
@@ -20,6 +21,7 @@ describe("ClusterRoleBindingDialog tests", () => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(storesAndApisCanBeCreatedInjectable, () => true);
di.override(directoryForUserDataInjectable, () => "/some-path-for-user-data");
render = renderFor(di);
@@ -15,6 +15,7 @@ import type { DiContainer } from "@ogre-tools/injectable";
import rendererExtensionsInjectable from "../../../../extensions/renderer-extensions.injectable";
import { LensRendererExtension } from "../../../../extensions/lens-renderer-extension";
import type { WelcomeBannerRegistration } from "../welcome-banner-items/welcome-banner-registration";
import currentlyInClusterFrameInjectable from "../../../routes/currently-in-cluster-frame.injectable";
jest.mock("electron", () => ({
ipcRenderer: {
@@ -32,6 +33,9 @@ describe("<Welcome/>", () => {
beforeEach(() => {
di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(currentlyInClusterFrameInjectable, () => false);
render = renderFor(di);
welcomeBannersStub = [];
@@ -11,7 +11,7 @@ import { observer } from "mobx-react";
import type { PieChartData } from "../chart";
import { PieChart } from "../chart";
import { object } from "../../utils";
import type { LensTheme } from "../../themes/store";
import type { LensTheme } from "../../themes/lens-theme";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { PascalCase } from "type-fest";
import type { IComputedValue } from "mobx";
@@ -10,7 +10,7 @@ import { BarChart } from "../chart";
import { isMetricsEmpty, normalizeMetrics } from "../../../common/k8s-api/endpoints/metrics.api";
import { NoMetrics } from "../resource-metrics/no-metrics";
import { ResourceMetricsContext } from "../resource-metrics";
import type { LensTheme } from "../../themes/store";
import type { LensTheme } from "../../themes/lens-theme";
import { mapValues } from "lodash";
import { type MetricsTab, metricTabOptions } from "../chart/options";
import { withInjectables } from "@ogre-tools/injectable-react";
-1
View File
@@ -8,7 +8,6 @@
@tailwind utilities;
@import "./flexbox";
@import "../themes/theme-vars";
@import "./fonts";
:root {
@@ -7,13 +7,15 @@ import React from "react";
import { observer } from "mobx-react";
import { Input } from "../input";
import { isUrl } from "../input/input_validators";
import { WeblinkStore } from "../../../common/weblink-store";
import type { WeblinkStore } from "../../../common/weblinks-store/weblink-store";
import { computed, makeObservable, observable } from "mobx";
import { withInjectables } from "@ogre-tools/injectable-react";
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
import weblinkStoreInjectable from "../../../common/weblinks-store/weblink-store.injectable";
interface Dependencies {
closeCommandOverlay: () => void;
weblinkStore: WeblinkStore;
}
@@ -40,7 +42,7 @@ class NonInjectedWeblinkAddCommand extends React.Component<Dependencies> {
}
onSubmit(name: string) {
WeblinkStore.getInstance().add({
this.props.weblinkStore.add({
name: name || this.url,
url: this.url,
});
@@ -64,7 +66,7 @@ class NonInjectedWeblinkAddCommand extends React.Component<Dependencies> {
value={this.url}
onChange={(v) => this.onChangeUrl(v)}
onSubmit={(v) => this.onSubmitUrl(v)}
showValidationLine={true}
showValidationLine={true}
/>
{ this.nameHidden && (
<small className="hint">
@@ -93,7 +95,8 @@ class NonInjectedWeblinkAddCommand extends React.Component<Dependencies> {
export const WeblinkAddCommand = withInjectables<Dependencies>(NonInjectedWeblinkAddCommand, {
getProps: (di, props) => ({
closeCommandOverlay: di.inject(commandOverlayInjectable).close,
...props,
closeCommandOverlay: di.inject(commandOverlayInjectable).close,
weblinkStore: di.inject(weblinkStoreInjectable),
}),
});
+1 -1
View File
@@ -13,7 +13,7 @@ import type { ChartProps } from "./chart";
import { Chart, ChartKind } from "./chart";
import { bytesToUnits, cssNames, isObject } from "../../utils";
import { ZebraStripesPlugin } from "./zebra-stripes.plugin";
import type { LensTheme } from "../../themes/store";
import type { LensTheme } from "../../themes/lens-theme";
import { NoMetrics } from "../resource-metrics/no-metrics";
import assert from "assert";
import { withInjectables } from "@ogre-tools/injectable-react";
+1 -1
View File
@@ -11,7 +11,7 @@ import ChartJS from "chart.js";
import type { ChartProps } from "./chart";
import { Chart } from "./chart";
import { cssNames } from "../../utils";
import type { LensTheme } from "../../themes/store";
import type { LensTheme } from "../../themes/lens-theme";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { IComputedValue } from "mobx";
import activeThemeInjectable from "../../themes/active.injectable";
@@ -37,11 +37,18 @@ export class ClusterFrameHandler {
@action
public initView(clusterId: ClusterId) {
const cluster = this.dependencies.getClusterById(clusterId);
if (!cluster) {
this.dependencies.logger.warn(`[LENS-VIEW]: not initializing view; unknown clusterId="${clusterId}"`);
return;
}
const parentElem = document.getElementById("lens-views");
assert(parentElem, "DOM with #lens-views must be present");
if (!cluster || this.views.has(clusterId)) {
if (this.views.has(clusterId)) {
return;
}
@@ -11,7 +11,6 @@ import { disposeOnUnmount, observer } from "mobx-react";
import { ClusterStatus } from "./cluster-status";
import type { ClusterFrameHandler } from "./cluster-frame-handler";
import type { Cluster } from "../../../common/cluster/cluster";
import { ClusterStore } from "../../../common/cluster-store/cluster-store";
import { requestClusterActivation } from "../../ipc";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { NavigateToCatalog } from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
@@ -20,18 +19,19 @@ import clusterViewRouteParametersInjectable from "./cluster-view-route-parameter
import clusterFrameHandlerInjectable from "./cluster-frame-handler.injectable";
import type { CatalogEntityRegistry } from "../../api/catalog/entity/registry";
import catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable";
import type { GetClusterById } from "../../../common/cluster-store/get-by-id.injectable";
import getClusterByIdInjectable from "../../../common/cluster-store/get-by-id.injectable";
interface Dependencies {
clusterId: IComputedValue<string>;
clusterFrames: ClusterFrameHandler;
navigateToCatalog: NavigateToCatalog;
entityRegistry: CatalogEntityRegistry;
getClusterById: GetClusterById;
}
@observer
class NonInjectedClusterView extends React.Component<Dependencies> {
private readonly store = ClusterStore.getInstance();
constructor(props: Dependencies) {
super(props);
makeObservable(this);
@@ -42,7 +42,7 @@ class NonInjectedClusterView extends React.Component<Dependencies> {
}
@computed get cluster(): Cluster | undefined {
return this.store.getById(this.clusterId);
return this.props.getClusterById(this.clusterId);
}
private readonly isViewLoaded = computed(() => this.props.clusterFrames.hasLoadedView(this.clusterId), {
@@ -110,10 +110,11 @@ class NonInjectedClusterView extends React.Component<Dependencies> {
export const ClusterView = withInjectables<Dependencies>(NonInjectedClusterView, {
getProps: (di) => ({
clusterId: di.inject(clusterViewRouteParametersInjectable).clusterId,
...di.inject(clusterViewRouteParametersInjectable),
navigateToCatalog: di.inject(navigateToCatalogInjectable),
clusterFrames: di.inject(clusterFrameHandlerInjectable),
entityRegistry: di.inject(catalogEntityRegistryInjectable),
getClusterById: di.inject(getClusterByIdInjectable),
}),
});
@@ -13,7 +13,7 @@ import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
import type { DiRender } from "../../test-utils/renderFor";
import { renderFor } from "../../test-utils/renderFor";
import showErrorNotificationInjectable from "../../notifications/show-error-notification.injectable";
import statInjectable from "../../../../common/fs/stat/stat.injectable";
import statInjectable from "../../../../common/fs/stat.injectable";
describe("ClusterLocalTerminalSettings", () => {
let render: DiRender;
@@ -10,7 +10,6 @@ import { Input } from "../input";
import { SubTitle } from "../layout/sub-title";
import type { ShowNotification } from "../notifications";
import { Icon } from "../icon";
import { PathPicker } from "../path-picker";
import { withInjectables } from "@ogre-tools/injectable-react";
import showErrorNotificationInjectable from "../notifications/show-error-notification.injectable";
import type { ValidateDirectory } from "../../../common/fs/validate-directory.injectable";
@@ -19,6 +18,8 @@ import type { ResolveTilde } from "../../../common/path/resolve-tilde.injectable
import resolveTildeInjectable from "../../../common/path/resolve-tilde.injectable";
import Gutter from "../gutter/gutter";
import isWindowsInjectable from "../../../common/vars/is-windows.injectable";
import type { OpenPathPickingDialog } from "../../../features/path-picking-dialog/renderer/pick-paths.injectable";
import openPathPickingDialogInjectable from "../../../features/path-picking-dialog/renderer/pick-paths.injectable";
export interface ClusterLocalTerminalSettingProps {
cluster: Cluster;
@@ -27,6 +28,7 @@ interface Dependencies {
showErrorNotification: ShowNotification;
validateDirectory: ValidateDirectory;
resolveTilde: ResolveTilde;
openPathPickingDialog: OpenPathPickingDialog;
isWindows: boolean;
}
@@ -36,6 +38,7 @@ const NonInjectedClusterLocalTerminalSetting = observer(({
validateDirectory,
resolveTilde,
isWindows,
openPathPickingDialog,
}: Dependencies & ClusterLocalTerminalSettingProps) => {
if (!cluster) {
return null;
@@ -95,8 +98,8 @@ const NonInjectedClusterLocalTerminalSetting = observer(({
};
const openFilePicker = () => {
PathPicker.pick({
label: "Choose Working Directory",
openPathPickingDialog({
message: "Choose Working Directory",
buttonLabel: "Pick",
properties: ["openDirectory", "showHiddenFiles"],
onPick: ([directory]) => setAndCommitDirectory(directory),
@@ -168,5 +171,6 @@ export const ClusterLocalTerminalSetting = withInjectables<Dependencies, Cluster
validateDirectory: di.inject(validateDirectoryInjectable),
resolveTilde: di.inject(resolveTildeInjectable),
isWindows: di.inject(isWindowsInjectable),
openPathPickingDialog: di.inject(openPathPickingDialogInjectable),
}),
});
+17 -4
View File
@@ -15,13 +15,15 @@ import { disposeOnUnmount, observer } from "mobx-react";
import moment from "moment-timezone";
import type { Align, ListOnScrollProps } from "react-window";
import { SearchStore } from "../../../search-store/search-store";
import { UserStore } from "../../../../common/user-store";
import type { UserStore } from "../../../../common/user-store";
import { array, autoBind, cssNames } from "../../../utils";
import type { VirtualListRef } from "../../virtual-list";
import { VirtualList } from "../../virtual-list";
import { ToBottom } from "./to-bottom";
import type { LogTabViewModel } from "../logs/logs-view-model";
import { Spinner } from "../../spinner";
import { withInjectables } from "@ogre-tools/injectable-react";
import userStoreInjectable from "../../../../common/user-store/user-store.injectable";
export interface LogListProps {
model: LogTabViewModel;
@@ -33,8 +35,12 @@ export interface LogListRef {
scrollToItem: (index: number, align: Align) => void;
}
interface Dependencies {
userStore: UserStore;
}
@observer
class NonForwardedLogList extends React.Component<LogListProps & { innerRef: ForwardedRef<LogListRef> }> {
class NonForwardedLogList extends React.Component<Dependencies & LogListProps & { innerRef: ForwardedRef<LogListRef> }> {
@observable isJumpButtonVisible = false;
@observable isLastLineVisible = true;
@@ -122,7 +128,7 @@ class NonForwardedLogList extends React.Component<LogListProps & { innerRef: For
return this.props.model.timestampSplitLogs
.get()
.map(([logTimestamp, log]) => (`${logTimestamp && moment.tz(logTimestamp, UserStore.getInstance().localeTimezone).format()}${log}`));
.map(([logTimestamp, log]) => (`${logTimestamp && moment.tz(logTimestamp, this.props.userStore.localeTimezone).format()}${log}`));
}
/**
@@ -273,6 +279,13 @@ class NonForwardedLogList extends React.Component<LogListProps & { innerRef: For
}
}
const InjectedNonForwardedLogList = withInjectables<Dependencies, LogListProps & { innerRef: ForwardedRef<LogListRef> }>(NonForwardedLogList, {
getProps: (di, props) => ({
...props,
userStore: di.inject(userStoreInjectable),
}),
});
export const LogList = React.forwardRef<LogListRef, LogListProps>((props, ref) => (
<NonForwardedLogList {...props} innerRef={ref} />
<InjectedNonForwardedLogList {...props} innerRef={ref} />
));
@@ -10,9 +10,9 @@ import type { TerminalApi } from "../../../api/terminal-api";
import terminalSpawningPoolInjectable from "./terminal-spawning-pool.injectable";
import terminalConfigInjectable from "../../../../common/user-store/terminal-config.injectable";
import terminalCopyOnSelectInjectable from "../../../../common/user-store/terminal-copy-on-select.injectable";
import themeStoreInjectable from "../../../themes/store.injectable";
import isMacInjectable from "../../../../common/vars/is-mac.injectable";
import openLinkInBrowserInjectable from "../../../../common/utils/open-link-in-browser.injectable";
import xtermColorThemeInjectable from "../../../themes/terminal-colors.injectable";
export type CreateTerminal = (tabId: TabId, api: TerminalApi) => Terminal;
@@ -23,9 +23,9 @@ const createTerminalInjectable = getInjectable({
spawningPool: di.inject(terminalSpawningPoolInjectable),
terminalConfig: di.inject(terminalConfigInjectable),
terminalCopyOnSelect: di.inject(terminalCopyOnSelectInjectable),
themeStore: di.inject(themeStoreInjectable),
isMac: di.inject(isMacInjectable),
openLinkInBrowser: di.inject(openLinkInBrowserInjectable),
xtermColorTheme: di.inject(xtermColorThemeInjectable),
};
return (tabId, api) => new Terminal(dependencies, { tabId, api });
@@ -10,7 +10,6 @@ import { Terminal as XTerm } from "xterm";
import { FitAddon } from "xterm-addon-fit";
import type { TabId } from "../dock/store";
import type { TerminalApi } from "../../../api/terminal-api";
import type { ThemeStore } from "../../../themes/store";
import { disposer } from "../../../utils";
import { once } from "lodash";
import { clipboard } from "electron";
@@ -25,8 +24,8 @@ export interface TerminalDependencies {
readonly spawningPool: HTMLElement;
readonly terminalConfig: IComputedValue<TerminalConfig>;
readonly terminalCopyOnSelect: IComputedValue<boolean>;
readonly themeStore: ThemeStore;
readonly isMac: boolean;
readonly xtermColorTheme: IComputedValue<Record<string, string>>;
openLinkInBrowser: OpenLinkInBrowser;
}
@@ -75,10 +74,6 @@ export class Terminal {
return this.dependencies.terminalConfig.get().fontSize;
}
get theme(): Record<string/*paramName*/, string/*color*/> {
return this.dependencies.themeStore.xtermColors;
}
constructor(protected readonly dependencies: TerminalDependencies, {
tabId,
api,
@@ -120,9 +115,12 @@ export class Terminal {
this.disposer.push(
this.xterm.registerLinkProvider(linkProvider),
reaction(() => this.theme, colors => this.xterm.setOption("theme", colors), {
fireImmediately: true,
}),
reaction(() => this.dependencies.xtermColorTheme.get(),
colors => this.xterm.options.theme = colors,
{
fireImmediately: true,
},
),
reaction(() => this.fontSize, this.setFontSize, { fireImmediately: true }),
reaction(() => this.fontFamily, this.setFontFamily, { fireImmediately: true }),
() => onDataHandler.dispose(),
@@ -10,7 +10,7 @@ import { disposeOnUnmount, observer } from "mobx-react";
import { cssNames } from "../../../utils";
import type { Terminal } from "./terminal";
import type { TerminalStore } from "./store";
import type { LensTheme } from "../../../themes/store";
import type { LensTheme } from "../../../themes/lens-theme";
import type { DockTab, DockStore } from "../dock/store";
import { withInjectables } from "@ogre-tools/injectable-react";
import dockStoreInjectable from "../dock/store.injectable";
@@ -21,7 +21,7 @@ import { NoItems } from "../no-items";
import { Spinner } from "../spinner";
import type { ItemObject } from "../../../common/item.store";
import type { Filter, PageFiltersStore } from "./page-filters/store";
import type { LensTheme } from "../../themes/store";
import type { LensTheme } from "../../themes/lens-theme";
import { MenuActions } from "../menu/menu-actions";
import { MenuItem } from "../menu";
import { Checkbox } from "../checkbox";
@@ -4,30 +4,28 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import { action } from "mobx";
import { beforeFrameStartsInjectionToken } from "../../../before-frame-starts/before-frame-starts-injection-token";
import { beforeFrameStartsInjectionToken } from "../../../before-frame-starts/tokens";
import ipcRendererInjectable from "../../../utils/channel/ipc-renderer.injectable";
import topBarStateInjectable from "./state.injectable";
// TODO: replace with a SyncBox
const startTopbarStateSyncInjectable = getInjectable({
id: "start-topbar-state-sync",
instantiate: (di) => {
const state = di.inject(topBarStateInjectable);
const ipcRenderer = di.inject(ipcRendererInjectable);
instantiate: (di) => ({
id: "start-topbar-state-sync",
run: () => {
const state = di.inject(topBarStateInjectable);
const ipcRenderer = di.inject(ipcRendererInjectable);
return {
id: "start-topbar-state-sync",
run: () => {
ipcRenderer.on("history:can-go-back", action((event, canGoBack: boolean) => {
state.prevEnabled = canGoBack;
}));
ipcRenderer.on("history:can-go-back", action((event, canGoBack: boolean) => {
state.prevEnabled = canGoBack;
}));
ipcRenderer.on("history:can-go-forward", action((event, canGoForward: boolean) => {
state.nextEnabled = canGoForward;
}));
},
};
},
ipcRenderer.on("history:can-go-forward", action((event, canGoForward: boolean) => {
state.nextEnabled = canGoForward;
}));
},
}),
injectionToken: beforeFrameStartsInjectionToken,
causesSideEffects: true,
});
@@ -21,6 +21,7 @@ import toggleMaximizeWindowInjectable from "./toggle-maximize-window/toggle-maxi
import topBarStateInjectable from "./state.injectable";
import platformInjectable from "../../../../common/vars/platform.injectable";
import goForwardInjectable from "./top-bar-items/navigation-to-forward/go-forward/go-forward.injectable";
import currentlyInClusterFrameInjectable from "../../../routes/currently-in-cluster-frame.injectable";
describe("<TopBar/>", () => {
let di: DiContainer;
@@ -42,6 +43,7 @@ describe("<TopBar/>", () => {
di.override(closeWindowInjectable, () => closeWindow = jest.fn());
di.override(maximizeWindowInjectable, () => maximizeWindow = jest.fn());
di.override(toggleMaximizeWindowInjectable, () => toggleMaximizeWindow = jest.fn());
di.override(currentlyInClusterFrameInjectable, () => false);
render = renderFor(di);
});
@@ -6,17 +6,27 @@
import React from "react";
import { observer } from "mobx-react";
import moment from "moment-timezone";
import { UserStore } from "../../../common/user-store";
import type { UserStore } from "../../../common/user-store";
import { withInjectables } from "@ogre-tools/injectable-react";
import userStoreInjectable from "../../../common/user-store/user-store.injectable";
export interface LocaleDateProps {
date: string;
}
@observer
export class LocaleDate extends React.Component<LocaleDateProps> {
render() {
const { date } = this.props;
return moment.tz(date, UserStore.getInstance().localeTimezone).format();
}
interface Dependencies {
userStore: UserStore;
}
const NonInjectedLocaleDate = observer(({ date, userStore }: LocaleDateProps & Dependencies) => (
<>
{`${moment.tz(date, userStore.localeTimezone).format()}`}
</>
));
export const LocaleDate = withInjectables<Dependencies, LocaleDateProps>(NonInjectedLocaleDate, {
getProps: (di, props) => ({
...props,
userStore: di.inject(userStoreInjectable),
}),
});
@@ -14,7 +14,7 @@ import { type MonacoValidator, monacoValidators } from "./monaco-validators";
import { debounce, merge } from "lodash";
import { autoBind, cssNames, disposer } from "../../utils";
import type { UserStore } from "../../../common/user-store";
import type { LensTheme } from "../../themes/store";
import type { LensTheme } from "../../themes/lens-theme";
import { withInjectables } from "@ogre-tools/injectable-react";
import userStoreInjectable from "../../../common/user-store/user-store.injectable";
import activeThemeInjectable from "../../themes/active.injectable";
@@ -4,27 +4,16 @@
*/
// Monaco editor themes customization
import { editor } from "monaco-editor";
import cloudsMidnight from "./monaco-themes/clouds-midnight.json";
import { getInjectionToken } from "@ogre-tools/injectable";
import type { editor } from "monaco-editor";
export type MonacoTheme = "vs" | "vs-dark" | "hc-black" | MonacoCustomTheme;
export type MonacoCustomTheme = "clouds-midnight";
export interface MonacoThemeData extends editor.IStandaloneThemeData {
name?: string;
name: string;
}
// Registered names could be then used in "themes/*.json" in "{monacoTheme: [name]}"
export const customThemes: Partial<Record<MonacoCustomTheme, MonacoThemeData>> = {
"clouds-midnight": cloudsMidnight as MonacoThemeData,
};
export function registerCustomThemes(): void {
Object.entries(customThemes).forEach(([name, theme]) => {
editor.defineTheme(name, theme);
});
}
export async function loadCustomTheme(name: string): Promise<MonacoThemeData> {
return import(`./monaco-themes/${name}.json`);
}
export const customMonacoThemeInjectionToken = getInjectionToken<MonacoThemeData>({
id: "custom-monaco-theme-token",
});
@@ -0,0 +1,143 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { customMonacoThemeInjectionToken } from "../monaco-themes";
import { getInjectable } from "@ogre-tools/injectable";
const cloudsMidnightThemeInjectable = getInjectable({
id: "clouds-midnight-theme",
instantiate: () => ({
"name": "clouds-midnight",
"base": "vs-dark" as const,
"inherit": true,
"rules": [
{
"background": "191919",
"token": "",
},
{
"foreground": "3c403b",
"token": "comment",
},
{
"foreground": "5d90cd",
"token": "string",
},
{
"foreground": "46a609",
"token": "constant.numeric",
},
{
"foreground": "39946a",
"token": "constant.language",
},
{
"foreground": "927c5d",
"token": "keyword",
},
{
"foreground": "927c5d",
"token": "support.constant.property-value",
},
{
"foreground": "927c5d",
"token": "constant.other.color",
},
{
"foreground": "366f1a",
"token": "keyword.other.unit",
},
{
"foreground": "a46763",
"token": "entity.other.attribute-name.html",
},
{
"foreground": "4b4b4b",
"token": "keyword.operator",
},
{
"foreground": "e92e2e",
"token": "storage",
},
{
"foreground": "858585",
"token": "entity.other.inherited-class",
},
{
"foreground": "606060",
"token": "entity.name.tag",
},
{
"foreground": "a165ac",
"token": "constant.character.entity",
},
{
"foreground": "a165ac",
"token": "support.class.js",
},
{
"foreground": "606060",
"token": "entity.other.attribute-name",
},
{
"foreground": "e92e2e",
"token": "meta.selector.css",
},
{
"foreground": "e92e2e",
"token": "entity.name.tag.css",
},
{
"foreground": "e92e2e",
"token": "entity.other.attribute-name.id.css",
},
{
"foreground": "e92e2e",
"token": "entity.other.attribute-name.class.css",
},
{
"foreground": "616161",
"token": "meta.property-name.css",
},
{
"foreground": "e92e2e",
"token": "support.function",
},
{
"foreground": "ffffff",
"background": "e92e2e",
"token": "invalid",
},
{
"foreground": "e92e2e",
"token": "punctuation.section.embedded",
},
{
"foreground": "606060",
"token": "punctuation.definition.tag",
},
{
"foreground": "a165ac",
"token": "constant.other.color.rgb-value.css",
},
{
"foreground": "a165ac",
"token": "support.constant.property-value.css",
},
],
"colors": {
"editor.foreground": "#929292",
"editor.background": "#191919",
"editor.selectionBackground": "#000000",
"editor.lineHighlightBackground": "#D7D7D708",
"editorCursor.foreground": "#7DA5DC",
"editorWhitespace.foreground": "#BFBFBF",
},
}),
injectionToken: customMonacoThemeInjectionToken,
});
export default cloudsMidnightThemeInjectable;
@@ -3,17 +3,19 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { withInjectables } from "@ogre-tools/injectable-react";
import type { FileFilter, OpenDialogOptions } from "electron";
import { observer } from "mobx-react";
import React from "react";
import type { OpenPathPickingDialog } from "../../../features/path-picking-dialog/renderer/pick-paths.injectable";
import openPathPickingDialogInjectable from "../../../features/path-picking-dialog/renderer/pick-paths.injectable";
import { cssNames } from "../../utils";
import { Button } from "../button";
import { requestOpenFilePickingDialog } from "../../ipc";
export interface PathPickOpts {
label: string;
onPick?: (paths: string[]) => any;
onCancel?: () => any;
message: string;
onPick?: (paths: string[]) => void | Promise<void>;
onCancel?: () => void | Promise<void>;
defaultPath?: string;
buttonLabel?: string;
filters?: FileFilter[];
@@ -26,40 +28,32 @@ export interface PathPickerProps extends PathPickOpts {
disabled?: boolean;
}
@observer
export class PathPicker extends React.Component<PathPickerProps> {
static async pick(opts: PathPickOpts) {
const { onPick, onCancel, label, ...dialogOptions } = opts;
const { canceled, filePaths } = await requestOpenFilePickingDialog({
message: label,
...dialogOptions,
});
if (canceled) {
await onCancel?.();
} else {
await onPick?.(filePaths);
}
}
async onClick() {
const { className, disabled, ...pickOpts } = this.props;
return PathPicker.pick(pickOpts);
}
render() {
const { className, label, disabled } = this.props;
return (
<Button
primary
label={label}
disabled={disabled}
className={cssNames("PathPicker", className)}
onClick={() => void this.onClick()}
/>
);
}
interface Dependencies {
openPathPickingDialog: OpenPathPickingDialog;
}
const NonInjectedPathPicker = observer((props: PathPickerProps & Dependencies) => {
const {
className,
disabled,
openPathPickingDialog,
...pickOpts
} = props;
return (
<Button
primary
label={pickOpts.message}
disabled={disabled}
className={cssNames("PathPicker", className)}
onClick={() => void openPathPickingDialog(pickOpts)}
/>
);
});
export const PathPicker = withInjectables<Dependencies, PathPickerProps>(NonInjectedPathPicker, {
getProps: (di, props) => ({
...props,
openPathPickingDialog: di.inject(openPathPickingDialogInjectable),
}),
});
+1 -1
View File
@@ -13,7 +13,7 @@ import { action, computed, makeObservable } from "mobx";
import { observer } from "mobx-react";
import ReactSelect, { components, createFilter } from "react-select";
import type { Props as ReactSelectProps, GroupBase, MultiValue, OptionsOrGroups, PropsValue, SingleValue } from "react-select";
import type { LensTheme } from "../../themes/store";
import type { LensTheme } from "../../themes/lens-theme";
import { autoBind, cssNames } from "../../utils";
import { withInjectables } from "@ogre-tools/injectable-react";
import activeThemeInjectable from "../../themes/active.injectable";
@@ -63,12 +63,15 @@ import type { FakeExtensionOptions } from "./get-extension-fake";
import { getExtensionFakeForMain, getExtensionFakeForRenderer } from "./get-extension-fake";
import namespaceApiInjectable from "../../../common/k8s-api/endpoints/namespace.api.injectable";
import { Namespace } from "../../../common/k8s-api/endpoints";
import { overrideFsWithFakes } from "../../../test-utils/override-fs-with-fakes";
import { getOverrideFsWithFakes } from "../../../test-utils/override-fs-with-fakes";
import applicationMenuItemCompositeInjectable from "../../../features/application-menu/main/application-menu-item-composite.injectable";
import { getCompositePaths } from "../../../common/utils/composite/get-composite-paths/get-composite-paths";
import { discoverFor } from "./discovery-of-html-elements";
import { findComposite } from "../../../common/utils/composite/find-composite/find-composite";
import shouldStartHiddenInjectable from "../../../main/electron-app/features/should-start-hidden.injectable";
import fsInjectable from "../../../common/fs/fs.injectable";
import joinPathsInjectable from "../../../common/path/join-paths.injectable";
import homeDirectoryPathInjectable from "../../../common/os/home-directory-path.injectable";
type Callback = (di: DiContainer) => void | Promise<void>;
@@ -114,6 +117,7 @@ export interface ApplicationBuilder {
allowKubeResource: (resourceName: KubeResource) => ApplicationBuilder;
beforeApplicationStart: (callback: Callback) => ApplicationBuilder;
afterApplicationStart: (callback: Callback) => ApplicationBuilder;
beforeWindowStart: (callback: Callback) => ApplicationBuilder;
afterWindowStart: (callback: Callback) => ApplicationBuilder;
@@ -170,12 +174,22 @@ export const getApplicationBuilder = () => {
const { overrideForWindow, sendToWindow } = overrideChannels(mainDi);
const beforeApplicationStartCallbacks: Callback[] = [];
const afterApplicationStartCallbacks: Callback[] = [];
const beforeWindowStartCallbacks: Callback[] = [];
const afterWindowStartCallbacks: Callback[] = [];
const fsState = new Map();
const overrideFsWithFakes = getOverrideFsWithFakes();
overrideFsWithFakes(mainDi, fsState);
overrideFsWithFakes(mainDi);
// Set up ~/.kube as existing as a folder
{
const { ensureDirSync } = mainDi.inject(fsInjectable);
const joinPaths = mainDi.inject(joinPathsInjectable);
const homeDirectoryPath = mainDi.inject(homeDirectoryPathInjectable);
ensureDirSync(joinPaths(homeDirectoryPath, ".kube"));
}
let environment = environments.application;
@@ -210,7 +224,7 @@ export const getApplicationBuilder = () => {
const windowDi = getRendererDi({ doGeneralOverrides: true });
overrideForWindow(windowDi, windowId);
overrideFsWithFakes(windowDi, fsState);
overrideFsWithFakes(windowDi);
runInAction(() => {
windowDi.register(rendererExtensionsStateInjectable);
@@ -276,6 +290,24 @@ export const getApplicationBuilder = () => {
const namespaces = observable.set<string>();
const namespaceItems = observable.array<Namespace>();
const selectedNamespaces = observable.set<string>();
const startMainApplication = mainDi.inject(startMainApplicationInjectable);
const startApplication = async ({ shouldStartHidden }: { shouldStartHidden: boolean }) => {
mainDi.inject(lensProxyPortInjectable).set(42);
for (const callback of beforeApplicationStartCallbacks) {
await callback(mainDi);
}
mainDi.override(shouldStartHiddenInjectable, () => shouldStartHidden);
await startMainApplication();
for (const callback of afterApplicationStartCallbacks) {
await callback(mainDi);
}
applicationHasStarted = true;
};
const builder: ApplicationBuilder = {
mainDi,
@@ -556,7 +588,7 @@ export const getApplicationBuilder = () => {
},
enable: (...extensions) => {
builder.beforeWindowStart((windowDi) => {
builder.afterWindowStart((windowDi) => {
const rendererExtensionInstances = extensions.map((options) =>
getExtensionFakeForRenderer(
windowDi,
@@ -571,7 +603,7 @@ export const getApplicationBuilder = () => {
);
});
builder.beforeApplicationStart((mainDi) => {
builder.afterApplicationStart((mainDi) => {
const mainExtensionInstances = extensions.map((extension) =>
getExtensionFakeForMain(mainDi, extension.id, extension.name, extension.mainOptions || {}),
);
@@ -585,7 +617,7 @@ export const getApplicationBuilder = () => {
},
disable: (...extensions) => {
builder.beforeWindowStart(windowDi => {
builder.afterWindowStart(windowDi => {
extensions
.map((ext) => ext.id)
.forEach(
@@ -593,7 +625,7 @@ export const getApplicationBuilder = () => {
);
});
builder.beforeApplicationStart(mainDi => {
builder.afterApplicationStart(mainDi => {
extensions
.map((ext) => ext.id)
.forEach(
@@ -623,6 +655,16 @@ export const getApplicationBuilder = () => {
return builder;
},
afterApplicationStart(callback) {
if (applicationHasStarted) {
callback(mainDi);
}
afterApplicationStartCallbacks.push(callback);
return builder;
},
beforeWindowStart(callback) {
const alreadyRenderedWindows = builder.applicationWindow.getAll();
@@ -648,29 +690,11 @@ export const getApplicationBuilder = () => {
},
startHidden: async () => {
mainDi.inject(lensProxyPortInjectable).set(42);
for (const callback of beforeApplicationStartCallbacks) {
await callback(mainDi);
}
mainDi.override(shouldStartHiddenInjectable, () => true);
await mainDi.inject(startMainApplicationInjectable);
applicationHasStarted = true;
await startApplication({ shouldStartHidden: true });
},
async render() {
mainDi.inject(lensProxyPortInjectable).set(42);
for (const callback of beforeApplicationStartCallbacks) {
await callback(mainDi);
}
mainDi.override(shouldStartHiddenInjectable, () => false);
await mainDi.inject(startMainApplicationInjectable);
applicationHasStarted = true;
await startApplication({ shouldStartHidden: false });
return builder
.applicationWindow
@@ -25,6 +25,7 @@ import allowedResourcesInjectable from "../../cluster-frame-context/allowed-reso
import hostedClusterIdInjectable from "../../cluster-frame-context/hosted-cluster-id.injectable";
import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable";
import { useFakeTime } from "../../../common/test-utils/use-fake-time";
import currentlyInClusterFrameInjectable from "../../routes/currently-in-cluster-frame.injectable";
describe("<ClusterFrame />", () => {
let render: () => RenderResult;
@@ -45,6 +46,7 @@ describe("<ClusterFrame />", () => {
di.override(legacyOnChannelListenInjectable, () => jest.fn().mockImplementation(() => jest.fn()));
di.override(directoryForUserDataInjectable, () => "/some/irrelavent/path");
di.override(storesAndApisCanBeCreatedInjectable, () => true);
di.override(currentlyInClusterFrameInjectable, () => true);
useFakeTime("2000-01-01 12:00:00am");
@@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { beforeFrameStartsInjectionToken } from "../../before-frame-starts/before-frame-starts-injection-token";
import { beforeFrameStartsInjectionToken } from "../../before-frame-starts/tokens";
import injectSystemCAsInjectable from "../../../common/certificate-authorities/inject-system-cas.injectable";
const setupSystemCaInjectable = getInjectable({
+5 -41
View File
@@ -9,16 +9,9 @@ import { createContainer, isInjectable } from "@ogre-tools/injectable";
import { Environments, setLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
import requestFromChannelInjectable from "./utils/channel/request-from-channel.injectable";
import loggerInjectable from "../common/logger.injectable";
import { overrideFsWithFakes } from "../test-utils/override-fs-with-fakes";
import { getOverrideFsWithFakes } from "../test-utils/override-fs-with-fakes";
import { createMemoryHistory } from "history";
import focusWindowInjectable from "./navigation/focus-window.injectable";
import extensionsStoreInjectable from "../extensions/extensions-store/extensions-store.injectable";
import type { ExtensionsStore } from "../extensions/extensions-store/extensions-store";
import fileSystemProvisionerStoreInjectable from "../extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable";
import type { FileSystemProvisionerStore } from "../extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store";
import userStoreInjectable from "../common/user-store/user-store.injectable";
import type { UserStore } from "../common/user-store";
import hotbarStoreInjectable from "../common/hotbars/store.injectable";
import terminalSpawningPoolInjectable from "./components/dock/terminal/terminal-spawning-pool.injectable";
import hostedClusterIdInjectable from "./cluster-frame-context/hosted-cluster-id.injectable";
import historyInjectable from "./navigation/history.injectable";
@@ -26,16 +19,14 @@ import { ApiManager } from "../common/k8s-api/api-manager";
import lensResourcesDirInjectable from "../common/vars/lens-resources-dir.injectable";
import broadcastMessageInjectable from "../common/ipc/broadcast-message.injectable";
import apiManagerInjectable from "../common/k8s-api/api-manager/manager.injectable";
import { observable, computed, runInAction } from "mobx";
import { computed, runInAction } from "mobx";
import requestAnimationFrameInjectable from "./components/animate/request-animation-frame.injectable";
import getRandomIdInjectable from "../common/utils/get-random-id.injectable";
import getFilePathsInjectable from "../features/helm-charts/child-features/preferences/renderer/adding-of-custom-helm-repository/helm-file-input/get-file-paths.injectable";
import callForPublicHelmRepositoriesInjectable from "../features/helm-charts/child-features/preferences/renderer/adding-of-public-helm-repository/public-helm-repositories/call-for-public-helm-repositories.injectable";
import requestPublicHelmRepositoriesInjectable from "../features/helm-charts/child-features/preferences/renderer/adding-of-public-helm-repository/public-helm-repositories/request-public-helm-repositories.injectable";
import platformInjectable from "../common/vars/platform.injectable";
import startTopbarStateSyncInjectable from "./components/layout/top-bar/start-state-sync.injectable";
import { registerMobX } from "@ogre-tools/injectable-extension-for-mobx";
import watchHistoryStateInjectable from "./remote-helpers/watch-history-state.injectable";
import type { HotbarStore } from "../common/hotbars/store";
import cronJobTriggerDialogClusterFrameChildComponentInjectable from "./components/+workloads-cronjobs/cron-job-trigger-dialog-cluster-frame-child-component.injectable";
import deploymentScaleDialogClusterFrameChildComponentInjectable from "./components/+workloads-deployments/scale/deployment-scale-dialog-cluster-frame-child-component.injectable";
import replicasetScaleDialogClusterFrameChildComponentInjectable from "./components/+workloads-replicasets/scale-dialog/replicaset-scale-dialog-cluster-frame-child-component.injectable";
@@ -50,7 +41,6 @@ import legacyOnChannelListenInjectable from "./ipc/legacy-channel-listen.injecta
import storageSaveDelayInjectable from "./utils/create-storage/storage-save-delay.injectable";
import environmentVariablesInjectable from "../common/utils/environment-variables.injectable";
import type { GlobalOverride } from "../common/test-utils/get-global-override";
import type { PartialDeep } from "type-fest";
export const getDiForUnitTesting = (
opts: { doGeneralOverrides?: boolean } = {},
@@ -141,40 +131,14 @@ export const getDiForUnitTesting = (
overrideFunctionalInjectables(di, [
broadcastMessageInjectable,
getFilePathsInjectable,
callForPublicHelmRepositoriesInjectable,
requestPublicHelmRepositoriesInjectable,
]);
di.override(extensionsStoreInjectable, () => ({
isEnabled: () => false,
}) as Partial<ExtensionsStore> as ExtensionsStore);
di.override(hotbarStoreInjectable, () => ({
getActive: () => ({
name: "some-hotbar",
items: [null, null, null, null, null, null, null, null, null, null, null, null],
id: "some-hotbar",
}),
getDisplayIndex: () => "0",
isAddedToActive: () => false,
}) as Partial<HotbarStore> as HotbarStore);
di.override(fileSystemProvisionerStoreInjectable, () => ({}) as FileSystemProvisionerStore);
di.override(userStoreInjectable, () => ({
isTableColumnHidden: () => false,
extensionRegistryUrl: { customUrl: "some-custom-url" },
syncKubeconfigEntries: observable.map(),
terminalConfig: { fontSize: 42 },
editorConfiguration: { minimap: {}, tabSize: 42, fontSize: 42 },
load: () => {},
} as PartialDeep<UserStore> as UserStore));
di.override(apiManagerInjectable, () => new ApiManager());
di.override(requestFromChannelInjectable, () => () => Promise.resolve(undefined as never));
overrideFsWithFakes(di);
getOverrideFsWithFakes()(di);
di.override(focusWindowInjectable, () => () => {});
@@ -2,13 +2,13 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getAllEntries } from "../../features/preferences/renderer/preference-items/kubernetes/kubeconfig-sync/kubeconfig-sync";
import { Notifications } from "../components/notifications";
import { getInjectable } from "@ogre-tools/injectable";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
import React from "react";
import navigateToKubernetesPreferencesInjectable from "../../features/preferences/common/navigate-to-kubernetes-preferences.injectable";
import loggerInjectable from "../../common/logger.injectable";
import discoverAllKubeconfigSyncKindsInjectable from "../../features/preferences/renderer/preference-items/kubernetes/kubeconfig-sync/discover-all-sync-kinds.injectable";
import { action } from "mobx";
const addSyncEntriesInjectable = getInjectable({
id: "add-sync-entries",
@@ -16,12 +16,16 @@ const addSyncEntriesInjectable = getInjectable({
instantiate: (di) => {
const userStore = di.inject(userStoreInjectable);
const navigateToKubernetesPreferences = di.inject(navigateToKubernetesPreferencesInjectable);
const logger = di.inject(loggerInjectable);
const discoverAllKubeconfigSyncKinds = di.inject(discoverAllKubeconfigSyncKindsInjectable);
return async (filePaths: string[]) => {
userStore.syncKubeconfigEntries.merge(
await getAllEntries(filePaths, logger),
);
const kinds = await discoverAllKubeconfigSyncKinds(filePaths);
action(() => {
for (const [path] of kinds) {
userStore.syncKubeconfigEntries.set(path, {});
}
});
Notifications.ok((
<div>
@@ -1,76 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubernetesClusterCategory } from "../../common/catalog-entities";
import { isLinux, isWindows } from "../../common/vars";
import { PathPicker } from "../components/path-picker";
interface Dependencies {
navigateToAddCluster: () => void;
addSyncEntries: (filePaths: string[]) => void;
kubernetesClusterCategory: KubernetesClusterCategory;
}
export function initCatalogCategoryRegistryEntries({
navigateToAddCluster,
addSyncEntries,
kubernetesClusterCategory,
} : Dependencies) {
kubernetesClusterCategory.on("catalogAddMenu", ctx => {
ctx.menuItems.push(
{
icon: "text_snippet",
title: "Add from kubeconfig",
onClick: navigateToAddCluster,
},
);
if (isWindows || isLinux) {
ctx.menuItems.push(
{
icon: "create_new_folder",
title: "Sync kubeconfig folder(s)",
defaultAction: true,
onClick: async () => {
await PathPicker.pick({
label: "Sync folder(s)",
buttonLabel: "Sync",
properties: ["showHiddenFiles", "multiSelections", "openDirectory"],
onPick: addSyncEntries,
});
},
},
{
icon: "note_add",
title: "Sync kubeconfig file(s)",
onClick: async () => {
await PathPicker.pick({
label: "Sync file(s)",
buttonLabel: "Sync",
properties: ["showHiddenFiles", "multiSelections", "openFile"],
onPick: addSyncEntries,
});
},
},
);
} else {
ctx.menuItems.push(
{
icon: "create_new_folder",
title: "Sync kubeconfig(s)",
defaultAction: true,
onClick: async () => {
await PathPicker.pick({
label: "Sync file(s)",
buttonLabel: "Sync",
properties: ["showHiddenFiles", "multiSelections", "openFile", "openDirectory"],
onPick: addSyncEntries,
});
},
},
);
}
});
}
-58
View File
@@ -1,58 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import fs from "fs";
import "../../common/catalog-entities/kubernetes-cluster";
import { ClusterStore } from "../../common/cluster-store/cluster-store";
import { catalogCategoryRegistry } from "../api/catalog-category-registry";
import { WeblinkAddCommand } from "../components/catalog-entities/weblink-add-command";
import { loadConfigFromString } from "../../common/kube-helpers";
import type { OpenDeleteClusterDialog } from "../components/delete-cluster-dialog/open.injectable";
async function onClusterDelete(clusterId: string, openDeleteClusterDialog: OpenDeleteClusterDialog) {
const cluster = ClusterStore.getInstance().getById(clusterId);
if (!cluster) {
return console.warn("[KUBERNETES-CLUSTER]: cannot delete cluster, does not exist in store", { clusterId });
}
const result = loadConfigFromString(await fs.promises.readFile(cluster.kubeConfigPath, "utf-8"));
if (result.error) {
throw result.error;
}
openDeleteClusterDialog({ cluster, config: result.config });
}
interface Dependencies {
openCommandDialog: (component: React.ReactElement) => void;
openDeleteClusterDialog: OpenDeleteClusterDialog;
}
export function initCatalog({ openCommandDialog, openDeleteClusterDialog }: Dependencies) {
catalogCategoryRegistry
.getForGroupKind("entity.k8slens.dev", "WebLink")
?.on("catalogAddMenu", ctx => {
ctx.menuItems.push({
title: "Add web link",
icon: "public",
onClick: () => openCommandDialog(<WeblinkAddCommand />),
});
});
catalogCategoryRegistry
.getForGroupKind("entity.k8slens.dev", "KubernetesCluster")
?.on("contextMenuOpen", (entity, context) => {
if (entity.metadata?.source == "local") {
context.menuItems.push({
title: "Remove",
icon: "delete",
onClick: () => onClusterDelete(entity.getId(), openDeleteClusterDialog),
});
}
});
}
-8
View File
@@ -1,8 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export * from "./catalog";
export * from "./ipc";
export * from "./catalog-category-registry";
-16
View File
@@ -1,16 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { ipcRendererOn } from "../../common/ipc";
import type { ExtensionLoader } from "../../extensions/extension-loader";
import type { LensRendererExtension } from "../../extensions/lens-renderer-extension";
export function initIpcRendererListeners(extensionLoader: ExtensionLoader) {
ipcRendererOn("extension:navigate", (event, extId: string, pageId ?: string, params?: Record<string, any>) => {
const ext = extensionLoader.getInstanceById(extId) as LensRendererExtension | undefined;
ext?.navigate(pageId, params);
});
}
-6
View File
@@ -3,11 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { OpenDialogOptions } from "electron";
import { clusterActivateHandler, clusterDisconnectHandler, clusterSetFrameIdHandler, clusterStates } from "../../common/ipc/cluster";
import type { ClusterId, ClusterState } from "../../common/cluster-types";
import { windowActionHandleChannel, windowLocationChangedChannel, windowOpenAppMenuAsContextMenuChannel, type WindowAction } from "../../common/ipc/window";
import { openFilePickingDialogChannel } from "../../common/ipc/dialog";
import { extensionDiscoveryStateChannel, extensionLoaderFromMainChannel } from "../../common/ipc/extension-handling";
import type { InstalledExtension } from "../../extensions/extension-discovery/extension-discovery";
import type { LensExtensionId } from "../../extensions/lens-extension";
@@ -44,10 +42,6 @@ export function requestWindowAction(type: WindowAction): Promise<void> {
return requestMain(windowActionHandleChannel, type);
}
export function requestOpenFilePickingDialog(opts: OpenDialogOptions): Promise<{ canceled: boolean; filePaths: string[] }> {
return requestMain(openFilePickingDialogChannel, opts);
}
export function requestSetClusterFrameId(clusterId: ClusterId): Promise<void> {
return requestMain(clusterSetFrameIdHandler, clusterId);
}
@@ -6,12 +6,12 @@ import { getInjectable } from "@ogre-tools/injectable";
import navigateToEntitySettingsInjectable from "../../common/front-end-routing/routes/entity-settings/navigate-to-entity-settings.injectable";
import type { ListNamespaceForbiddenArgs } from "../../common/ipc/cluster";
import { Notifications } from "../components/notifications";
import { ClusterStore } from "../../common/cluster-store/cluster-store";
import { Button } from "../components/button";
import type { IpcRendererEvent } from "electron";
import React from "react";
import notificationsStoreInjectable from "../components/notifications/notifications-store.injectable";
import { getMillisecondsFromUnixEpoch } from "../../common/utils/date/get-current-date-time";
import getClusterByIdInjectable from "../../common/cluster-store/get-by-id.injectable";
const listNamespacesForbiddenHandlerInjectable = getInjectable({
id: "list-namespaces-forbidden-handler",
@@ -19,6 +19,7 @@ const listNamespacesForbiddenHandlerInjectable = getInjectable({
instantiate: (di) => {
const navigateToEntitySettings = di.inject(navigateToEntitySettingsInjectable);
const notificationsStore = di.inject(notificationsStoreInjectable);
const getClusterById = di.inject(getClusterByIdInjectable);
const notificationLastDisplayedAt = new Map<string, number>();
const intervalBetweenNotifications = 1000 * 60; // 60s
@@ -52,7 +53,7 @@ const listNamespacesForbiddenHandlerInjectable = getInjectable({
<b>Add Accessible Namespaces</b>
<p>
{"Cluster "}
<b>{ClusterStore.getInstance().getById(clusterId)?.name ?? "<unknown cluster>"}</b>
<b>{getClusterById(clusterId)?.name ?? "<unknown cluster>"}</b>
{" does not have permissions to list namespaces. Please add the namespaces you have access to."}
</p>
<div className="flex gaps row align-left box grow">
@@ -0,0 +1,9 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getGlobalOverride } from "../../common/test-utils/get-global-override";
import addNewMonacoThemeInjectable from "./add-new-theme.injectable";
export default getGlobalOverride(addNewMonacoThemeInjectable, () => () => {});
@@ -0,0 +1,15 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { editor } from "monaco-editor";
import type { MonacoThemeData } from "../components/monaco-editor";
const addNewMonacoThemeInjectable = getInjectable({
id: "add-new-monaco-theme",
instantiate: () => (data: MonacoThemeData) => editor.defineTheme(data.name, data),
causesSideEffects: true,
});
export default addNewMonacoThemeInjectable;
@@ -15,6 +15,7 @@ import catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.i
// TODO: Importing from features is not OK. Make protocol-router to comply with Open Closed Principle to allow moving implementation under a feature
import navigateToPreferencesInjectable from "../../../features/preferences/common/navigate-to-preferences.injectable";
import getClusterByIdInjectable from "../../../common/cluster-store/get-by-id.injectable";
const bindProtocolAddRouteHandlersInjectable = getInjectable({
id: "bind-protocol-add-route-handlers",
@@ -29,6 +30,7 @@ const bindProtocolAddRouteHandlersInjectable = getInjectable({
navigateToClusterView: di.inject(navigateToClusterViewInjectable),
navigateToPreferences: di.inject(navigateToPreferencesInjectable),
entityRegistry: di.inject(catalogEntityRegistryInjectable),
getClusterById: di.inject(getClusterByIdInjectable),
}),
});
@@ -6,7 +6,6 @@
import React from "react";
import type { LensProtocolRouterRenderer } from "../lens-protocol-router-renderer/lens-protocol-router-renderer";
import type { CatalogEntityRegistry } from "../../api/catalog/entity/registry";
import { ClusterStore } from "../../../common/cluster-store/cluster-store";
import {
EXTENSION_NAME_MATCH,
EXTENSION_PUBLISHER_MATCH,
@@ -18,6 +17,7 @@ import type { NavigateToEntitySettings } from "../../../common/front-end-routing
import type { NavigateToClusterView } from "../../../common/front-end-routing/routes/cluster-view/navigate-to-cluster-view.injectable";
import assert from "assert";
import type { AttemptInstallByInfo } from "../../components/+extensions/attempt-install-by-info.injectable";
import type { GetClusterById } from "../../../common/cluster-store/get-by-id.injectable";
// TODO: make it so that the handlers are type safe and we don't need to do the asserts
interface Dependencies {
@@ -30,6 +30,7 @@ interface Dependencies {
navigateToClusterView: NavigateToClusterView;
navigateToPreferences: (tabId: string) => void;
entityRegistry: CatalogEntityRegistry;
getClusterById: GetClusterById;
}
export const bindProtocolAddRouteHandlers = ({
@@ -42,6 +43,7 @@ export const bindProtocolAddRouteHandlers = ({
navigateToClusterView,
navigateToPreferences,
entityRegistry,
getClusterById,
}: Dependencies) => () => {
lensProtocolRouterRenderer
.addInternalHandler("/preferences", ({ search: { highlight: tabId }}) => {
@@ -93,7 +95,7 @@ export const bindProtocolAddRouteHandlers = ({
// Handlers below are deprecated and only kept for backward compact purposes
.addInternalHandler("/cluster/:clusterId", ({ pathname: { clusterId }}) => {
assert(clusterId);
const cluster = ClusterStore.getInstance().getById(clusterId);
const cluster = getClusterById(clusterId);
if (cluster) {
navigateToClusterView(clusterId);
@@ -109,7 +111,7 @@ export const bindProtocolAddRouteHandlers = ({
})
.addInternalHandler("/cluster/:clusterId/settings", ({ pathname: { clusterId }}) => {
assert(clusterId);
const cluster = ClusterStore.getInstance().getById(clusterId);
const cluster = getClusterById(clusterId);
if (cluster) {
navigateToEntitySettings(clusterId);
@@ -7,6 +7,7 @@ import { getInjectable } from "@ogre-tools/injectable";
const currentlyInClusterFrameInjectable = getInjectable({
id: "currently-in-cluster-frame",
instantiate: () => !process.isMainFrame,
causesSideEffects: true,
});
export default currentlyInClusterFrameInjectable;
@@ -4,16 +4,32 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import { runManyFor } from "../../common/runnable/run-many-for";
import { beforeFrameStartsInjectionToken } from "../before-frame-starts/before-frame-starts-injection-token";
import { beforeFrameStartsInjectionToken, beforeClusterFrameStartsInjectionToken, beforeFrameStartsFirstInjectionToken, beforeMainFrameStartsInjectionToken } from "../before-frame-starts/tokens";
import currentlyInClusterFrameInjectable from "../routes/currently-in-cluster-frame.injectable";
const startFrameInjectable = getInjectable({
id: "start-frame",
// TODO: Consolidate contents of bootstrap.tsx here
instantiate: (di) => async () => {
const beforeFrameStarts = runManyFor(di)(beforeFrameStartsInjectionToken);
instantiate: (di) => {
const runMany = runManyFor(di);
const beforeFrameStartsFirst = runMany(beforeFrameStartsFirstInjectionToken);
const beforeMainFrameStarts = runMany(beforeMainFrameStartsInjectionToken);
const beforeClusterFrameStarts = runMany(beforeClusterFrameStartsInjectionToken);
const beforeFrameStarts = runMany(beforeFrameStartsInjectionToken);
const currentlyInClusterFrame = di.inject(currentlyInClusterFrameInjectable);
await beforeFrameStarts();
return async () => {
await beforeFrameStartsFirst();
if (currentlyInClusterFrame) {
await beforeClusterFrameStarts();
} else {
await beforeMainFrameStarts();
}
await beforeFrameStarts();
};
},
});
@@ -4,20 +4,21 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
import { beforeFrameStartsInjectionToken } from "../before-frame-starts/before-frame-starts-injection-token";
import { beforeFrameStartsInjectionToken } from "../before-frame-starts/tokens";
import initDefaultUpdateChannelInjectable from "../vars/default-update-channel/init.injectable";
const initUserStoreInjectable = getInjectable({
id: "init-user-store",
instantiate: (di) => {
const userStore = di.inject(userStoreInjectable);
instantiate: (di) => ({
id: "init-user-store",
run: () => {
// This must be done here so as to actually only be instantiated after the dependencies are
const userStore = di.inject(userStoreInjectable);
return {
id: "init-user-store",
run: () => userStore.load(),
runAfter: di.inject(initDefaultUpdateChannelInjectable),
};
},
return userStore.load();
},
runAfter: di.inject(initDefaultUpdateChannelInjectable),
}),
injectionToken: beforeFrameStartsInjectionToken,
});
@@ -5,8 +5,8 @@
import { getInjectable } from "@ogre-tools/injectable";
import type { IComputedValue } from "mobx";
import { computed } from "mobx";
import type { LensThemeType } from "./store";
import themeStoreInjectable from "./store.injectable";
import activeThemeInjectable from "./active.injectable";
import type { LensThemeType } from "./lens-theme";
export type ActiveThemeType = IComputedValue<LensThemeType>;
@@ -14,9 +14,9 @@ const activeThemeTypeInjectable = getInjectable({
id: "active-theme-type",
instantiate: (di) => {
const store = di.inject(themeStoreInjectable);
const activeTheme = di.inject(activeThemeInjectable);
return computed(() => store.activeTheme.type);
return computed(() => activeTheme.get().type);
},
});
+25 -3
View File
@@ -3,15 +3,37 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import assert from "assert";
import { computed } from "mobx";
import themeStoreInjectable from "./store.injectable";
import lensColorThemePreferenceInjectable from "../../common/user-store/lens-color-theme.injectable";
import { lensThemeDeclarationInjectionToken } from "./declaration";
import defaultLensThemeInjectable from "./default-theme.injectable";
import systemThemeConfigurationInjectable from "./system-theme.injectable";
import lensThemesInjectable from "./themes.injectable";
const activeThemeInjectable = getInjectable({
id: "active-theme",
instantiate: (di) => {
const store = di.inject(themeStoreInjectable);
const lensThemes = di.inject(lensThemesInjectable);
const themeDecls = di.injectMany(lensThemeDeclarationInjectionToken);
const lensColorThemePreference = di.inject(lensColorThemePreferenceInjectable);
const systemThemeConfiguration = di.inject(systemThemeConfigurationInjectable);
const defaultLensTheme = di.inject(defaultLensThemeInjectable);
return computed(() => store.activeTheme);
return computed(() => {
const pref = lensColorThemePreference.get();
if (pref.useSystemTheme) {
const systemThemeType = systemThemeConfiguration.get();
const matchingTheme = themeDecls.find(theme => theme.type === systemThemeType);
assert(matchingTheme, `Missing theme declaration for system theme "${systemThemeType}"`);
return matchingTheme;
}
return lensThemes.get(pref.lensThemeId) ?? defaultLensTheme;
});
},
});
@@ -0,0 +1,38 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import loggerInjectable from "../../common/logger.injectable";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
import { object } from "../utils";
import type { LensTheme } from "./lens-theme";
export type ApplyLensTheme = (theme: LensTheme) => void;
const applyLensThemeInjectable = getInjectable({
id: "apply-lens-theme",
instantiate: (di): ApplyLensTheme => {
const logger = di.inject(loggerInjectable);
const userStore = di.inject(userStoreInjectable);
return (theme) => {
try {
const colors = object.entries(theme.colors);
for (const [name, value] of colors) {
document.documentElement.style.setProperty(`--${name}`, value);
}
// Adding universal theme flag which can be used in component styles
document.body.classList.toggle("theme-light", theme.type === "light");
} catch (error) {
logger.error("[THEME]: Failed to apply active theme", error);
userStore.resetTheme();
}
};
},
causesSideEffects: true,
});
export default applyLensThemeInjectable;
@@ -0,0 +1,9 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getGlobalOverride } from "../../common/test-utils/get-global-override";
import applyLensThemeInjectable from "./apply-lens-theme.injectable";
export default getGlobalOverride(applyLensThemeInjectable, () => () => {});
+12
View File
@@ -0,0 +1,12 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectionToken } from "@ogre-tools/injectable";
import type { ReadonlyDeep } from "type-fest";
import type { LensTheme } from "./lens-theme";
export const lensThemeDeclarationInjectionToken = getInjectionToken<ReadonlyDeep<LensTheme>>({
id: "lens-theme-declaration",
});
@@ -0,0 +1,26 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { lensThemeDeclarationInjectionToken } from "./declaration";
const defaultLensThemeInjectable = getInjectable({
id: "default-lens-theme",
instantiate: (di) => {
const themes = di.injectMany(lensThemeDeclarationInjectionToken);
const [defaultTheme, ...rest] = themes.filter(theme => theme.isDefault);
if (rest.length > 0) {
throw new Error("Multiple LensTheme's are declared as the default");
}
if (!defaultTheme) {
throw new Error("No LensTheme is declared as the default");
}
return defaultTheme;
},
});
export default defaultLensThemeInjectable;
+156
View File
@@ -0,0 +1,156 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { lensThemeDeclarationInjectionToken } from "./declaration";
const lensDarkThemeInjectable = getInjectable({
id: "lens-dark-theme",
instantiate: () => ({
name: "Dark",
type: "dark" as const,
description: "Original Lens dark theme",
author: "Mirantis",
monacoTheme: "clouds-midnight" as const,
colors: {
blue: "#3d90ce",
magenta: "#c93dce",
golden: "#ffc63d",
halfGray: "#87909c80",
primary: "#3d90ce",
textColorPrimary: "#8e9297",
textColorSecondary: "#a0a0a0",
textColorTertiary: "#909ba6",
textColorAccent: "#ffffff",
textColorDimmed: "#8e92978c",
borderColor: "#4c5053",
borderFaintColor: "#373a3e",
mainBackground: "#1e2124",
secondaryBackground: "#1e2125",
contentColor: "#262b2f",
layoutBackground: "#2e3136",
layoutTabsBackground: "#252729",
layoutTabsActiveColor: "#ffffff",
layoutTabsLineColor: "#87909c80",
sidebarBackground: "#36393e",
sidebarLogoBackground: "#414448",
sidebarActiveColor: "#ffffff",
sidebarSubmenuActiveColor: "#ffffff",
sidebarItemHoverBackground: "#3a3e44",
badgeBackgroundColor: "#ffba44",
buttonPrimaryBackground: "#3d90ce",
buttonDefaultBackground: "#414448",
buttonLightBackground: "#f1f1f1",
buttonAccentBackground: "#e85555",
buttonDisabledBackground: "#808080",
tableBgcStripe: "#2a2d33",
tableBgcSelected: "#383c42",
tableHeaderBackground: "#262b2f",
tableHeaderColor: "#ffffff",
tableSelectedRowColor: "#ffffff",
helmLogoBackground: "#ffffff",
helmStableRepo: "#3d90ce",
helmIncubatorRepo: "#ff7043",
helmDescriptionHr: "#41474a",
helmDescriptionBlockquoteColor: "#bbb",
helmDescriptionBlockquoteBorder: "#8a8f93",
helmDescriptionBlockquoteBackground: "#3b4348",
helmDescriptionHeaders: "#3e4147",
helmDescriptionH6: "#6a737d",
helmDescriptionTdBorder: "#47494a",
helmDescriptionTrBackground: "#1c2125",
helmDescriptionCodeBackground: "#ffffff1a",
helmDescriptionPreBackground: "#1b1f21",
helmDescriptionPreColor: "#b4b5b4",
colorSuccess: "#43a047",
colorOk: "#4caf50",
colorInfo: "#3d90ce",
colorError: "#ce3933",
colorSoftError: "#e85555",
colorWarning: "#ff9800",
colorVague: "#36393e",
colorTerminated: "#4c5053",
dockHeadBackground: "#2e3136",
dockInfoBackground: "#1e2125",
dockInfoBorderColor: "#303136",
dockEditorBackground: "#000000",
dockEditorTag: "#8e97a3",
dockEditorKeyword: "#ffffff",
dockEditorComment: "#808080",
dockEditorActiveLineBackground: "#3a3d41",
dockBadgeBackground: "#36393e",
dockTabBorderColor: "#43424d",
dockTabActiveBackground: "#3a3e45",
logsBackground: "#000000",
logsForeground: "#ffffff",
logRowHoverBackground: "#35373a",
dialogTextColor: "#87909c",
dialogBackground: "#ffffff",
dialogHeaderBackground: "#36393e",
dialogFooterBackground: "#f4f4f4",
drawerTogglerBackground: "#2f343a",
drawerTitleText: "#ffffff",
drawerSubtitleBackground: "#373a3e",
drawerItemNameColor: "#87909c",
drawerItemValueColor: "#a0a0a0",
clusterMenuBackground: "#252729",
clusterMenuBorderColor: "#252729",
clusterMenuCellBackground: "#2e3136",
clusterSettingsBackground: "#1e2124",
addClusterIconColor: "#252729",
boxShadow: "#0000003a",
iconActiveColor: "#ffffff",
iconActiveBackground: "#ffffff18",
filterAreaBackground: "#23272b",
chartLiveBarBackground: "#00000033",
chartStripesColor: "#ffffff08",
chartCapacityColor: "#4c545f",
pieChartDefaultColor: "#30353a",
inputOptionHoverColor: "#87909c",
inputControlBackground: "#1e2125",
inputControlBorder: "#414448",
inputControlHoverBorder: "#474a4f",
lineProgressBackground: "#414448",
radioActiveBackground: "#36393e",
menuActiveBackground: "#3d90ce",
menuSelectedOptionBgc: "#36393e",
canvasBackground: "#24292e",
scrollBarColor: "#5f6064",
settingsBackground: "#262b2e",
settingsColor: "#909ba6",
navSelectedBackground: "#262b2e",
navHoverColor: "#dcddde",
hrColor: "#ffffff0f",
tooltipBackground: "#18191c",
},
terminalColors: {
background: "#000000",
foreground: "#ffffff",
cursor: "#ffffff",
cursorAccent: "#000000",
selection: "#ffffff77",
black: "#2e3436",
red: "#cc0000",
green: "#4e9a06",
yellow: "#c4a000",
blue: "#3465a4",
magenta: "#75507b",
cyan: "#06989a",
white: "#d3d7cf",
brightBlack: "#555753",
brightRed: "#ef2929",
brightGreen: "#8ae234",
brightYellow: "#fce94f",
brightBlue: "#729fcf",
brightMagenta: "#ad7fa8",
brightCyan: "#34e2e2",
brightWhite: "#eeeeec",
},
isDefault: true,
}),
injectionToken: lensThemeDeclarationInjectionToken,
});
export default lensDarkThemeInjectable;
-148
View File
@@ -1,148 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { LensTheme } from "./store";
const lensDarkTheme: LensTheme = {
"name": "Dark",
"type": "dark",
"description": "Original Lens dark theme",
"author": "Mirantis",
"monacoTheme": "clouds-midnight",
"colors": {
"blue": "#3d90ce",
"magenta": "#c93dce",
"golden": "#ffc63d",
"halfGray": "#87909c80",
"primary": "#3d90ce",
"textColorPrimary": "#8e9297",
"textColorSecondary": "#a0a0a0",
"textColorTertiary": "#909ba6",
"textColorAccent": "#ffffff",
"textColorDimmed": "#8e92978c",
"borderColor": "#4c5053",
"borderFaintColor": "#373a3e",
"mainBackground": "#1e2124",
"secondaryBackground": "#1e2125",
"contentColor": "#262b2f",
"layoutBackground": "#2e3136",
"layoutTabsBackground": "#252729",
"layoutTabsActiveColor": "#ffffff",
"layoutTabsLineColor": "#87909c80",
"sidebarBackground": "#36393e",
"sidebarLogoBackground": "#414448",
"sidebarActiveColor": "#ffffff",
"sidebarSubmenuActiveColor": "#ffffff",
"sidebarItemHoverBackground": "#3a3e44",
"badgeBackgroundColor": "#ffba44",
"buttonPrimaryBackground": "#3d90ce",
"buttonDefaultBackground": "#414448",
"buttonLightBackground": "#f1f1f1",
"buttonAccentBackground": "#e85555",
"buttonDisabledBackground": "#808080",
"tableBgcStripe": "#2a2d33",
"tableBgcSelected": "#383c42",
"tableHeaderBackground": "#262b2f",
"tableHeaderColor": "#ffffff",
"tableSelectedRowColor": "#ffffff",
"helmLogoBackground": "#ffffff",
"helmStableRepo": "#3d90ce",
"helmIncubatorRepo": "#ff7043",
"helmDescriptionHr": "#41474a",
"helmDescriptionBlockquoteColor": "#bbb",
"helmDescriptionBlockquoteBorder": "#8a8f93",
"helmDescriptionBlockquoteBackground": "#3b4348",
"helmDescriptionHeaders": "#3e4147",
"helmDescriptionH6": "#6a737d",
"helmDescriptionTdBorder": "#47494a",
"helmDescriptionTrBackground": "#1c2125",
"helmDescriptionCodeBackground": "#ffffff1a",
"helmDescriptionPreBackground": "#1b1f21",
"helmDescriptionPreColor": "#b4b5b4",
"colorSuccess": "#43a047",
"colorOk": "#4caf50",
"colorInfo": "#3d90ce",
"colorError": "#ce3933",
"colorSoftError": "#e85555",
"colorWarning": "#ff9800",
"colorVague": "#36393e",
"colorTerminated": "#4c5053",
"dockHeadBackground": "#2e3136",
"dockInfoBackground": "#1e2125",
"dockInfoBorderColor": "#303136",
"dockEditorBackground": "#000000",
"dockEditorTag": "#8e97a3",
"dockEditorKeyword": "#ffffff",
"dockEditorComment": "#808080",
"dockEditorActiveLineBackground": "#3a3d41",
"dockBadgeBackground": "#36393e",
"dockTabBorderColor": "#43424d",
"dockTabActiveBackground": "#3a3e45",
"logsBackground": "#000000",
"logsForeground": "#ffffff",
"logRowHoverBackground": "#35373a",
"terminalBackground": "#000000",
"terminalForeground": "#ffffff",
"terminalCursor": "#ffffff",
"terminalCursorAccent": "#000000",
"terminalSelection": "#ffffff77",
"terminalBlack": "#2e3436",
"terminalRed": "#cc0000",
"terminalGreen": "#4e9a06",
"terminalYellow": "#c4a000",
"terminalBlue": "#3465a4",
"terminalMagenta": "#75507b",
"terminalCyan": "#06989a",
"terminalWhite": "#d3d7cf",
"terminalBrightBlack": "#555753",
"terminalBrightRed": "#ef2929",
"terminalBrightGreen": "#8ae234",
"terminalBrightYellow": "#fce94f",
"terminalBrightBlue": "#729fcf",
"terminalBrightMagenta": "#ad7fa8",
"terminalBrightCyan": "#34e2e2",
"terminalBrightWhite": "#eeeeec",
"dialogTextColor": "#87909c",
"dialogBackground": "#ffffff",
"dialogHeaderBackground": "#36393e",
"dialogFooterBackground": "#f4f4f4",
"drawerTogglerBackground": "#2f343a",
"drawerTitleText": "#ffffff",
"drawerSubtitleBackground": "#373a3e",
"drawerItemNameColor": "#87909c",
"drawerItemValueColor": "#a0a0a0",
"clusterMenuBackground": "#252729",
"clusterMenuBorderColor": "#252729",
"clusterMenuCellBackground": "#2e3136",
"clusterSettingsBackground": "#1e2124",
"addClusterIconColor": "#252729",
"boxShadow": "#0000003a",
"iconActiveColor": "#ffffff",
"iconActiveBackground": "#ffffff18",
"filterAreaBackground": "#23272b",
"chartLiveBarBackground": "#00000033",
"chartStripesColor": "#ffffff08",
"chartCapacityColor": "#4c545f",
"pieChartDefaultColor": "#30353a",
"inputOptionHoverColor": "#87909c",
"inputControlBackground": "#1e2125",
"inputControlBorder": "#414448",
"inputControlHoverBorder": "#474a4f",
"lineProgressBackground": "#414448",
"radioActiveBackground": "#36393e",
"menuActiveBackground": "#3d90ce",
"menuSelectedOptionBgc": "#36393e",
"canvasBackground": "#24292e",
"scrollBarColor": "#5f6064",
"settingsBackground": "#262b2e",
"settingsColor": "#909ba6",
"navSelectedBackground": "#262b2e",
"navHoverColor": "#dcddde",
"hrColor": "#ffffff0f",
"tooltipBackground": "#18191c",
},
};
export default lensDarkTheme;
@@ -0,0 +1,154 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { lensThemeDeclarationInjectionToken } from "./declaration";
const lensLightThemeInjectable = getInjectable({
id: "lens-light-theme",
instantiate: () => ({
name: "Light",
type: "light" as const,
description: "Original Lens light theme",
author: "Mirantis",
monacoTheme: "vs" as const,
colors: {
blue: "#3d90ce",
magenta: "#c93dce",
golden: "#ffc63d",
halfGray: "#87909c80",
primary: "#3d90ce",
textColorPrimary: "#555555",
textColorSecondary: "#51575d",
textColorTertiary: "#555555",
textColorAccent: "#222222",
textColorDimmed: "#5557598c",
borderColor: "#c9cfd3",
borderFaintColor: "#dfdfdf",
mainBackground: "#f1f1f1",
secondaryBackground: "#f2f3f5",
contentColor: "#ffffff",
layoutBackground: "#e8e8e8",
layoutTabsBackground: "#f8f8f8",
layoutTabsActiveColor: "#333333",
layoutTabsLineColor: "#87909c80",
sidebarLogoBackground: "#f1f1f1",
sidebarActiveColor: "#ffffff",
sidebarSubmenuActiveColor: "#3d90ce",
sidebarBackground: "#e8e8e8",
sidebarItemHoverBackground: "#f0f2f5",
badgeBackgroundColor: "#ffba44",
buttonPrimaryBackground: "#3d90ce",
buttonDefaultBackground: "#414448",
buttonLightBackground: "#f1f1f1",
buttonAccentBackground: "#e85555",
buttonDisabledBackground: "#808080",
tableBgcStripe: "#f8f8f8",
tableBgcSelected: "#f4f5f5",
tableHeaderBackground: "#f1f1f1",
tableHeaderColor: "#555555",
tableSelectedRowColor: "#222222",
helmLogoBackground: "#ffffff",
helmStableRepo: "#3d90ce",
helmIncubatorRepo: "#ff7043",
helmDescriptionHr: "#dddddd",
helmDescriptionBlockquoteColor: "#555555",
helmDescriptionBlockquoteBorder: "#8a8f93",
helmDescriptionBlockquoteBackground: "#eeeeee",
helmDescriptionHeaders: "#3e4147",
helmDescriptionH6: "#6a737d",
helmDescriptionTdBorder: "#c6c6c6",
helmDescriptionTrBackground: "#1c2125",
helmDescriptionCodeBackground: "#ffffff1a",
helmDescriptionPreBackground: "#eeeeee",
helmDescriptionPreColor: "#555555",
colorSuccess: "#206923",
colorOk: "#399c3d",
colorInfo: "#2d71a4",
colorError: "#ce3933",
colorSoftError: "#e85555",
colorWarning: "#ff9800",
colorVague: "#ededed",
colorTerminated: "#9dabb5",
dockHeadBackground: "#e8e8e8",
dockInfoBackground: "#f3f3f3",
dockInfoBorderColor: "#c9cfd3",
dockEditorBackground: "#24292e",
dockEditorTag: "#8e97a3",
dockEditorKeyword: "#ffffff",
dockEditorComment: "#808080",
dockEditorActiveLineBackground: "#3a3d41",
dockBadgeBackground: "#dedede",
dockTabBorderColor: "#d5d4de",
dockTabActiveBackground: "#ffffff",
logsBackground: "#24292e",
logsForeground: "#ffffff",
logRowHoverBackground: "#35373a",
dialogTextColor: "#87909c",
dialogBackground: "#ffffff",
dialogHeaderBackground: "#36393e",
dialogFooterBackground: "#f4f4f4",
drawerTogglerBackground: "#eaeced",
drawerTitleText: "#ffffff",
drawerSubtitleBackground: "#f1f1f1",
drawerItemNameColor: "#727272",
drawerItemValueColor: "#555555",
clusterMenuBackground: "#d7d8da",
clusterMenuBorderColor: "#c9cfd3",
clusterMenuCellBackground: "#bbbbbb",
clusterSettingsBackground: "#ffffff",
addClusterIconColor: "#8d8d8d",
boxShadow: "#0000003a",
iconActiveColor: "#ffffff",
iconActiveBackground: "#a6a6a694",
filterAreaBackground: "#f7f7f7",
chartLiveBarBackground: "#00000033",
chartStripesColor: "#00000009",
chartCapacityColor: "#cccccc",
pieChartDefaultColor: "#efefef",
inputOptionHoverColor: "#ffffff",
inputControlBackground: "#f6f6f7",
inputControlBorder: "#cccdcf",
inputControlHoverBorder: "#b9bbbe",
lineProgressBackground: "#e8e8e8",
radioActiveBackground: "#f1f1f1",
menuActiveBackground: "#3d90ce",
menuSelectedOptionBgc: "#e8e8e8",
canvasBackground: "#24292e",
scrollBarColor: "#bbbbbb",
settingsBackground: "#ffffff",
settingsColor: "#555555",
navSelectedBackground: "#ffffff",
navHoverColor: "#2e3135",
hrColor: "#06060714",
tooltipBackground: "#ffffff",
},
terminalColors: {
background: "#ffffff",
foreground: "#2d2d2d",
cursor: "#2d2d2d",
cursorAccent: "#ffffff",
selection: "#bfbfbf",
black: "#2d2d2d",
red: "#cd3734 ",
green: "#18cf12",
yellow: "#acb300",
blue: "#3d90ce",
magenta: "#c100cd",
cyan: "#07c4b9",
white: "#d3d7cf",
brightBlack: "#a8a8a8",
brightRed: "#ff6259",
brightGreen: "#5cdb59",
brightYellow: "#f8c000",
brightBlue: "#008db6",
brightMagenta: "#ee55f8",
brightCyan: "#50e8df",
brightWhite: "#eeeeec",
},
}),
injectionToken: lensThemeDeclarationInjectionToken,
});
export default lensLightThemeInjectable;
-147
View File
@@ -1,147 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { LensTheme } from "./store";
const lensLightTheme: LensTheme = {
"name": "Light",
"type": "light",
"description": "Original Lens light theme",
"author": "Mirantis",
"monacoTheme": "vs",
"colors": {
"blue": "#3d90ce",
"magenta": "#c93dce",
"golden": "#ffc63d",
"halfGray": "#87909c80",
"primary": "#3d90ce",
"textColorPrimary": "#555555",
"textColorSecondary": "#51575d",
"textColorTertiary": "#555555",
"textColorAccent": "#222222",
"textColorDimmed": "#5557598c",
"borderColor": "#c9cfd3",
"borderFaintColor": "#dfdfdf",
"mainBackground": "#f1f1f1",
"secondaryBackground": "#f2f3f5",
"contentColor": "#ffffff",
"layoutBackground": "#e8e8e8",
"layoutTabsBackground": "#f8f8f8",
"layoutTabsActiveColor": "#333333",
"layoutTabsLineColor": "#87909c80",
"sidebarLogoBackground": "#f1f1f1",
"sidebarActiveColor": "#ffffff",
"sidebarSubmenuActiveColor": "#3d90ce",
"sidebarBackground": "#e8e8e8",
"sidebarItemHoverBackground": "#f0f2f5",
"badgeBackgroundColor": "#ffba44",
"buttonPrimaryBackground": "#3d90ce",
"buttonDefaultBackground": "#414448",
"buttonLightBackground": "#f1f1f1",
"buttonAccentBackground": "#e85555",
"buttonDisabledBackground": "#808080",
"tableBgcStripe": "#f8f8f8",
"tableBgcSelected": "#f4f5f5",
"tableHeaderBackground": "#f1f1f1",
"tableHeaderColor": "#555555",
"tableSelectedRowColor": "#222222",
"helmLogoBackground": "#ffffff",
"helmStableRepo": "#3d90ce",
"helmIncubatorRepo": "#ff7043",
"helmDescriptionHr": "#dddddd",
"helmDescriptionBlockquoteColor": "#555555",
"helmDescriptionBlockquoteBorder": "#8a8f93",
"helmDescriptionBlockquoteBackground": "#eeeeee",
"helmDescriptionHeaders": "#3e4147",
"helmDescriptionH6": "#6a737d",
"helmDescriptionTdBorder": "#c6c6c6",
"helmDescriptionTrBackground": "#1c2125",
"helmDescriptionCodeBackground": "#ffffff1a",
"helmDescriptionPreBackground": "#eeeeee",
"helmDescriptionPreColor": "#555555",
"colorSuccess": "#206923",
"colorOk": "#399c3d",
"colorInfo": "#2d71a4",
"colorError": "#ce3933",
"colorSoftError": "#e85555",
"colorWarning": "#ff9800",
"colorVague": "#ededed",
"colorTerminated": "#9dabb5",
"dockHeadBackground": "#e8e8e8",
"dockInfoBackground": "#f3f3f3",
"dockInfoBorderColor": "#c9cfd3",
"dockEditorBackground": "#24292e",
"dockEditorTag": "#8e97a3",
"dockEditorKeyword": "#ffffff",
"dockEditorComment": "#808080",
"dockEditorActiveLineBackground": "#3a3d41",
"dockBadgeBackground": "#dedede",
"dockTabBorderColor": "#d5d4de",
"dockTabActiveBackground": "#ffffff",
"logsBackground": "#24292e",
"logsForeground": "#ffffff",
"logRowHoverBackground": "#35373a",
"terminalBackground": "#ffffff",
"terminalForeground": "#2d2d2d",
"terminalCursor": "#2d2d2d",
"terminalCursorAccent": "#ffffff",
"terminalSelection": "#bfbfbf",
"terminalBlack": "#2d2d2d",
"terminalRed": "#cd3734 ",
"terminalGreen": "#18cf12",
"terminalYellow": "#acb300",
"terminalBlue": "#3d90ce",
"terminalMagenta": "#c100cd",
"terminalCyan": "#07c4b9",
"terminalWhite": "#d3d7cf",
"terminalBrightBlack": "#a8a8a8",
"terminalBrightRed": "#ff6259",
"terminalBrightGreen": "#5cdb59",
"terminalBrightYellow": "#f8c000",
"terminalBrightBlue": "#008db6",
"terminalBrightMagenta": "#ee55f8",
"terminalBrightCyan": "#50e8df",
"terminalBrightWhite": "#eeeeec",
"dialogTextColor": "#87909c",
"dialogBackground": "#ffffff",
"dialogHeaderBackground": "#36393e",
"dialogFooterBackground": "#f4f4f4",
"drawerTogglerBackground": "#eaeced",
"drawerTitleText": "#ffffff",
"drawerSubtitleBackground": "#f1f1f1",
"drawerItemNameColor": "#727272",
"drawerItemValueColor": "#555555",
"clusterMenuBackground": "#d7d8da",
"clusterMenuBorderColor": "#c9cfd3",
"clusterMenuCellBackground": "#bbbbbb",
"clusterSettingsBackground": "#ffffff",
"addClusterIconColor": "#8d8d8d",
"boxShadow": "#0000003a",
"iconActiveColor": "#ffffff",
"iconActiveBackground": "#a6a6a694",
"filterAreaBackground": "#f7f7f7",
"chartLiveBarBackground": "#00000033",
"chartStripesColor": "#00000009",
"chartCapacityColor": "#cccccc",
"pieChartDefaultColor": "#efefef",
"inputOptionHoverColor": "#ffffff",
"inputControlBackground": "#f6f6f7",
"inputControlBorder": "#cccdcf",
"inputControlHoverBorder": "#b9bbbe",
"lineProgressBackground": "#e8e8e8",
"radioActiveBackground": "#f1f1f1",
"menuActiveBackground": "#3d90ce",
"menuSelectedOptionBgc": "#e8e8e8",
"canvasBackground": "#24292e",
"scrollBarColor": "#bbbbbb",
"settingsBackground": "#ffffff",
"settingsColor": "#555555",
"navSelectedBackground": "#ffffff",
"navHoverColor": "#2e3135",
"hrColor": "#06060714",
"tooltipBackground": "#ffffff",
},
};
export default lensLightTheme;
+154
View File
@@ -0,0 +1,154 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { MonacoTheme } from "../components/monaco-editor";
export type ThemeId = string;
export type LensThemeType = "dark" | "light";
export interface LensTheme {
name: string;
type: LensThemeType;
colors: Record<LensColorName, string>;
terminalColors: Partial<Record<TerminalColorName, string>>;
description: string;
author: string;
monacoTheme: MonacoTheme;
isDefault?: boolean;
}
export type TerminalColorName =
| "foreground"
| "background"
| "cursor"
| "cursorAccent"
| "selection"
| "selectionForeground"
| "black"
| "red"
| "green"
| "yellow"
| "blue"
| "magenta"
| "cyan"
| "white"
| "brightBlack"
| "brightRed"
| "brightGreen"
| "brightYellow"
| "brightBlue"
| "brightMagenta"
| "brightCyan"
| "brightWhite";
export type LensColorName =
| "blue"
| "magenta"
| "golden"
| "halfGray"
| "primary"
| "textColorPrimary"
| "textColorSecondary"
| "textColorTertiary"
| "textColorAccent"
| "textColorDimmed"
| "borderColor"
| "borderFaintColor"
| "mainBackground"
| "secondaryBackground"
| "contentColor"
| "layoutBackground"
| "layoutTabsBackground"
| "layoutTabsActiveColor"
| "layoutTabsLineColor"
| "sidebarLogoBackground"
| "sidebarActiveColor"
| "sidebarSubmenuActiveColor"
| "sidebarBackground"
| "sidebarItemHoverBackground"
| "badgeBackgroundColor"
| "buttonPrimaryBackground"
| "buttonDefaultBackground"
| "buttonLightBackground"
| "buttonAccentBackground"
| "buttonDisabledBackground"
| "tableBgcStripe"
| "tableBgcSelected"
| "tableHeaderBackground"
| "tableHeaderColor"
| "tableSelectedRowColor"
| "helmLogoBackground"
| "helmStableRepo"
| "helmIncubatorRepo"
| "helmDescriptionHr"
| "helmDescriptionBlockquoteColor"
| "helmDescriptionBlockquoteBorder"
| "helmDescriptionBlockquoteBackground"
| "helmDescriptionHeaders"
| "helmDescriptionH6"
| "helmDescriptionTdBorder"
| "helmDescriptionTrBackground"
| "helmDescriptionCodeBackground"
| "helmDescriptionPreBackground"
| "helmDescriptionPreColor"
| "colorSuccess"
| "colorOk"
| "colorInfo"
| "colorError"
| "colorSoftError"
| "colorWarning"
| "colorVague"
| "colorTerminated"
| "dockHeadBackground"
| "dockInfoBackground"
| "dockInfoBorderColor"
| "dockEditorBackground"
| "dockEditorTag"
| "dockEditorKeyword"
| "dockEditorComment"
| "dockEditorActiveLineBackground"
| "dockBadgeBackground"
| "dockTabBorderColor"
| "dockTabActiveBackground"
| "logsBackground"
| "logsForeground"
| "logRowHoverBackground"
| "dialogTextColor"
| "dialogBackground"
| "dialogHeaderBackground"
| "dialogFooterBackground"
| "drawerTogglerBackground"
| "drawerTitleText"
| "drawerSubtitleBackground"
| "drawerItemNameColor"
| "drawerItemValueColor"
| "clusterMenuBackground"
| "clusterMenuBorderColor"
| "clusterMenuCellBackground"
| "clusterSettingsBackground"
| "addClusterIconColor"
| "boxShadow"
| "iconActiveColor"
| "iconActiveBackground"
| "filterAreaBackground"
| "chartLiveBarBackground"
| "chartStripesColor"
| "chartCapacityColor"
| "pieChartDefaultColor"
| "inputOptionHoverColor"
| "inputControlBackground"
| "inputControlBorder"
| "inputControlHoverBorder"
| "lineProgressBackground"
| "radioActiveBackground"
| "menuActiveBackground"
| "menuSelectedOptionBgc"
| "canvasBackground"
| "scrollBarColor"
| "settingsBackground"
| "settingsColor"
| "navSelectedBackground"
| "navHoverColor"
| "hrColor"
| "tooltipBackground";
@@ -0,0 +1,37 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { reaction } from "mobx";
import initializeSystemThemeTypeInjectable from "../../features/theme/system-type/renderer/initialize.injectable";
import { beforeFrameStartsInjectionToken } from "../before-frame-starts/tokens";
import initUserStoreInjectable from "../stores/init-user-store.injectable";
import activeThemeInjectable from "./active.injectable";
import applyLensThemeInjectable from "./apply-lens-theme.injectable";
const setupApplyActiveThemeInjectable = getInjectable({
id: "setup-apply-active-theme",
instantiate: (di) => ({
id: "setup-apply-active-theme",
run: () => {
const activeTheme = di.inject(activeThemeInjectable);
const applyLensTheme = di.inject(applyLensThemeInjectable);
reaction(
() => activeTheme.get(),
applyLensTheme,
{
fireImmediately: true,
},
);
},
runAfter: [
di.inject(initializeSystemThemeTypeInjectable),
di.inject(initUserStoreInjectable),
],
}),
injectionToken: beforeFrameStartsInjectionToken,
});
export default setupApplyActiveThemeInjectable;
-19
View File
@@ -1,19 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
import ipcRendererInjectable from "../utils/channel/ipc-renderer.injectable";
import { ThemeStore } from "./store";
const themeStoreInjectable = getInjectable({
id: "theme-store",
instantiate: (di) => new ThemeStore({
ipcRenderer: di.inject(ipcRendererInjectable),
userStore: di.inject(userStoreInjectable),
}),
});
export default themeStoreInjectable;
-150
View File
@@ -1,150 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { comparer, computed, makeObservable, observable, reaction } from "mobx";
import { autoBind } from "../utils";
import logger from "../../main/logger";
import lensDarkTheme from "./lens-dark";
import lensLightTheme from "./lens-light";
import type { MonacoTheme } from "../components/monaco-editor";
import { defaultThemeId } from "../../common/vars";
import { camelCase } from "lodash";
import type { IpcRenderer } from "electron";
import { getNativeThemeChannel, setNativeThemeChannel } from "../../common/ipc/native-theme";
import type { ReadonlyDeep } from "type-fest/source/readonly-deep";
import assert from "assert";
export type ThemeId = string;
export type LensThemeType = "dark" | "light";
export interface LensTheme {
name: string;
type: LensThemeType;
colors: Record<string, string>;
description: string;
author: string;
monacoTheme: MonacoTheme;
}
interface Dependencies {
readonly userStore: {
colorTheme: string;
terminalTheme: ThemeId;
resetTheme(): void;
};
readonly ipcRenderer: IpcRenderer;
}
export class ThemeStore {
private terminalColorPrefix = "terminal";
#themes = observable.map<ThemeId, LensTheme>({
"lens-dark": lensDarkTheme,
"lens-light": lensLightTheme,
});
@observable private osNativeThemeType: "dark" | "light" | undefined;
@computed private get colorThemePreference(): ThemeId | "system" {
return this.dependencies.userStore.colorTheme;
}
@computed private get activeThemeId(): ThemeId {
if (this.colorThemePreference === "system") {
if (this.osNativeThemeType) {
return `lens-${this.osNativeThemeType}`;
} else {
return defaultThemeId;
}
} else {
return this.colorThemePreference;
}
}
@computed private get terminalThemeId(): ThemeId {
return this.dependencies.userStore.terminalTheme;
}
private readonly defaultTheme: LensTheme;
@computed get activeTheme(): LensTheme {
return this.themes.get(this.activeThemeId) ?? this.defaultTheme;
}
@computed private get terminalColors(): [string, string][] {
const theme = this.themes.get(this.terminalThemeId) ?? this.activeTheme;
return Object
.entries(theme.colors)
.filter(([name]) => name.startsWith(this.terminalColorPrefix));
}
// Replacing keys stored in styles to format accepted by terminal
// E.g. terminalBrightBlack -> brightBlack
@computed get xtermColors(): Record<string, string> {
return Object.fromEntries(
this.terminalColors.map(([name, color]) => [
camelCase(name.replace(this.terminalColorPrefix, "")),
color,
]),
);
}
get themes() {
return this.#themes as ReadonlyDeep<Map<string, LensTheme>>;
}
constructor(protected readonly dependencies: Dependencies) {
makeObservable(this);
autoBind(this);
this.init();
const defaultTheme = this.#themes.get(defaultThemeId);
assert(defaultTheme, `${defaultThemeId} is invalid as there is no corresponding theme`);
this.defaultTheme = defaultTheme;
}
async init() {
this.osNativeThemeType = await this.dependencies.ipcRenderer.invoke(getNativeThemeChannel);
this.dependencies.ipcRenderer.on(setNativeThemeChannel, (event, theme: "dark" | "light") => {
this.osNativeThemeType = theme;
});
// auto-apply active theme
reaction(() => ({
themeId: this.activeThemeId,
terminalThemeId: this.terminalThemeId,
}), () => {
try {
this.applyActiveTheme();
} catch (err) {
logger.error(`Failed to apply active theme: ${err}`);
this.dependencies.userStore.resetTheme();
}
}, {
fireImmediately: true,
equals: comparer.shallow,
});
}
getThemeById(themeId: ThemeId): LensTheme | undefined {
return this.themes.get(themeId);
}
protected applyActiveTheme() {
const colors = Object.entries({
...this.activeTheme.colors,
...Object.fromEntries(this.terminalColors),
});
colors.forEach(([name, value]) => {
document.documentElement.style.setProperty(`--${name}`, value);
});
// Adding universal theme flag which can be used in component styles
document.body.classList.toggle("theme-light", this.activeTheme.type === "light");
}
}
@@ -0,0 +1,13 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { observable } from "mobx";
const systemThemeConfigurationInjectable = getInjectable({
id: "system-theme-configuration",
instantiate: () => observable.box<"dark" | "light">("dark"),
});
export default systemThemeConfigurationInjectable;
@@ -0,0 +1,32 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import terminalThemePreferenceInjectable from "../../common/user-store/terminal-theme.injectable";
import activeThemeInjectable from "./active.injectable";
import lensThemesInjectable from "./themes.injectable";
const xtermColorThemeInjectable = getInjectable({
id: "terminal-colors",
instantiate: (di) => {
const activeTheme = di.inject(activeThemeInjectable);
const terminalThemePreference = di.inject(terminalThemePreferenceInjectable);
const themes = di.inject(lensThemesInjectable);
const terminalTheme = computed(() => {
const themePref = terminalThemePreference.get();
if (themePref.matchLensTheme) {
return activeTheme.get();
}
return themes.get(themePref.themeId) ?? activeTheme.get();
});
return computed(() => terminalTheme.get().terminalColors);
},
});
export default xtermColorThemeInjectable;
-138
View File
@@ -1,138 +0,0 @@
/*
Generated Lens theme CSS-variables, don't edit manually.
To refresh file run $: yarn run ts-node build/build_theme_vars.ts
*/
:root {
--blue: #3d90ce;
--magenta: #c93dce;
--golden: #ffc63d;
--halfGray: #87909c80;
--primary: #3d90ce;
--textColorPrimary: #8e9297;
--textColorSecondary: #a0a0a0;
--textColorTertiary: #909ba6;
--textColorAccent: #ffffff;
--textColorDimmed: #8e92978c;
--borderColor: #4c5053;
--borderFaintColor: #373a3e;
--mainBackground: #1e2124;
--secondaryBackground: #1e2125;
--contentColor: #262b2f;
--layoutBackground: #2e3136;
--layoutTabsBackground: #252729;
--layoutTabsActiveColor: #ffffff;
--layoutTabsLineColor: #87909c80;
--sidebarBackground: #36393e;
--sidebarLogoBackground: #414448;
--sidebarActiveColor: #ffffff;
--sidebarSubmenuActiveColor: #ffffff;
--sidebarItemHoverBackground: #3a3e44;
--badgeBackgroundColor: #ffba44;
--buttonPrimaryBackground: #3d90ce;
--buttonDefaultBackground: #414448;
--buttonLightBackground: #f1f1f1;
--buttonAccentBackground: #e85555;
--buttonDisabledBackground: #808080;
--tableBgcStripe: #2a2d33;
--tableBgcSelected: #383c42;
--tableHeaderBackground: #262b2f;
--tableHeaderColor: #ffffff;
--tableSelectedRowColor: #ffffff;
--helmLogoBackground: #ffffff;
--helmStableRepo: #3d90ce;
--helmIncubatorRepo: #ff7043;
--helmDescriptionHr: #41474a;
--helmDescriptionBlockquoteColor: #bbb;
--helmDescriptionBlockquoteBorder: #8a8f93;
--helmDescriptionBlockquoteBackground: #3b4348;
--helmDescriptionHeaders: #3e4147;
--helmDescriptionH6: #6a737d;
--helmDescriptionTdBorder: #47494a;
--helmDescriptionTrBackground: #1c2125;
--helmDescriptionCodeBackground: #ffffff1a;
--helmDescriptionPreBackground: #1b1f21;
--helmDescriptionPreColor: #b4b5b4;
--colorSuccess: #43a047;
--colorOk: #4caf50;
--colorInfo: #3d90ce;
--colorError: #ce3933;
--colorSoftError: #e85555;
--colorWarning: #ff9800;
--colorVague: #36393e;
--colorTerminated: #4c5053;
--dockHeadBackground: #2e3136;
--dockInfoBackground: #1e2125;
--dockInfoBorderColor: #303136;
--dockEditorBackground: #000000;
--dockEditorTag: #8e97a3;
--dockEditorKeyword: #ffffff;
--dockEditorComment: #808080;
--dockEditorActiveLineBackground: #3a3d41;
--dockBadgeBackground: #36393e;
--dockTabBorderColor: #43424d;
--dockTabActiveBackground: #3a3e45;
--logsBackground: #000000;
--logsForeground: #ffffff;
--logRowHoverBackground: #35373a;
--terminalBackground: #000000;
--terminalForeground: #ffffff;
--terminalCursor: #ffffff;
--terminalCursorAccent: #000000;
--terminalSelection: #ffffff77;
--terminalBlack: #2e3436;
--terminalRed: #cc0000;
--terminalGreen: #4e9a06;
--terminalYellow: #c4a000;
--terminalBlue: #3465a4;
--terminalMagenta: #75507b;
--terminalCyan: #06989a;
--terminalWhite: #d3d7cf;
--terminalBrightBlack: #555753;
--terminalBrightRed: #ef2929;
--terminalBrightGreen: #8ae234;
--terminalBrightYellow: #fce94f;
--terminalBrightBlue: #729fcf;
--terminalBrightMagenta: #ad7fa8;
--terminalBrightCyan: #34e2e2;
--terminalBrightWhite: #eeeeec;
--dialogTextColor: #87909c;
--dialogBackground: #ffffff;
--dialogHeaderBackground: #36393e;
--dialogFooterBackground: #f4f4f4;
--drawerTogglerBackground: #2f343a;
--drawerTitleText: #ffffff;
--drawerSubtitleBackground: #373a3e;
--drawerItemNameColor: #87909c;
--drawerItemValueColor: #a0a0a0;
--clusterMenuBackground: #252729;
--clusterMenuBorderColor: #252729;
--clusterMenuCellBackground: #2e3136;
--clusterSettingsBackground: #1e2124;
--addClusterIconColor: #252729;
--boxShadow: #0000003a;
--iconActiveColor: #ffffff;
--iconActiveBackground: #ffffff18;
--filterAreaBackground: #23272b;
--chartLiveBarBackground: #00000033;
--chartStripesColor: #ffffff08;
--chartCapacityColor: #4c545f;
--pieChartDefaultColor: #30353a;
--inputOptionHoverColor: #87909c;
--inputControlBackground: #1e2125;
--inputControlBorder: #414448;
--inputControlHoverBorder: #474a4f;
--lineProgressBackground: #414448;
--radioActiveBackground: #36393e;
--menuActiveBackground: #3d90ce;
--menuSelectedOptionBgc: #36393e;
--canvasBackground: #24292e;
--scrollBarColor: #5f6064;
--settingsBackground: #262b2e;
--settingsColor: #909ba6;
--navSelectedBackground: #262b2e;
--navHoverColor: #dcddde;
--hrColor: #ffffff0f;
--tooltipBackground: #18191c;
}
+17
View File
@@ -0,0 +1,17 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { lensThemeDeclarationInjectionToken } from "./declaration";
const lensThemesInjectable = getInjectable({
id: "lens-themes",
instantiate: (di) => {
const themes = di.injectMany(lensThemeDeclarationInjectionToken);
return new Map(themes.map(theme => [theme.name, theme]));
},
});
export default lensThemesInjectable;
@@ -6,7 +6,7 @@
import { observable, reaction } from "mobx";
import { StorageHelper } from "../storageHelper";
import { delay } from "../../../common/utils/delay";
import { toJS } from "../../../common/utils";
import { noop, toJS } from "../../../common/utils";
interface StorageModel {
[prop: string]: any /*json-serializable*/;
@@ -26,7 +26,15 @@ describe("renderer/utils/StorageHelper", () => {
message: "saved-before", // pretending as previously saved data
});
storageHelper = new StorageHelper<StorageModel>(storageKey, {
storageHelper = new StorageHelper<StorageModel>({
logger: {
debug: noop,
error: noop,
info: noop,
silly: noop,
warn: noop,
},
}, storageKey, {
autoInit: false,
defaultValue: {
message: "blabla",
@@ -48,7 +56,15 @@ describe("renderer/utils/StorageHelper", () => {
},
});
storageHelperAsync = new StorageHelper(storageKey, {
storageHelperAsync = new StorageHelper({
logger: {
debug: noop,
error: noop,
info: noop,
silly: noop,
warn: noop,
},
}, storageKey, {
autoInit: false,
defaultValue: storageHelper.defaultValue,
storage: {
@@ -118,7 +134,15 @@ describe("renderer/utils/StorageHelper", () => {
beforeEach(() => {
observedChanges.length = 0;
storageHelper = new StorageHelper<typeof defaultValue>("some-key", {
storageHelper = new StorageHelper<typeof defaultValue>({
logger: {
debug: noop,
error: noop,
info: noop,
silly: noop,
warn: noop,
},
}, "some-key", {
autoInit: true,
defaultValue,
storage: {
@@ -3,22 +3,20 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { beforeFrameStartsInjectionToken } from "../../../before-frame-starts/before-frame-starts-injection-token";
import { beforeFrameStartsInjectionToken } from "../../../before-frame-starts/tokens";
import listeningOnMessageChannelsInjectable from "../../../../common/utils/channel/listening-on-message-channels.injectable";
const startListeningOfChannelsInjectable = getInjectable({
id: "start-listening-of-channels-renderer",
instantiate: (di) => {
const listeningOfChannels = di.inject(listeningOnMessageChannelsInjectable);
instantiate: (di) => ({
id: "start-listening-of-channels-renderer",
run: () => {
const listeningOfChannels = di.inject(listeningOnMessageChannelsInjectable);
return {
id: "start-listening-of-channels-renderer",
run: async () => {
await listeningOfChannels.start();
},
};
},
listeningOfChannels.start();
},
}),
injectionToken: beforeFrameStartsInjectionToken,
});
@@ -7,17 +7,19 @@
// Because app creates random port between restarts => storage session wiped out each time.
import { comparer, reaction, toJS, when } from "mobx";
import type { StorageLayer } from "../storageHelper";
import { StorageHelper } from "../storageHelper";
import type { JsonObject, JsonValue } from "type-fest";
import { storageHelperLogPrefix, StorageHelper } from "../storageHelper";
import type { JsonObject } from "type-fest";
import type { Logger } from "../../../common/logger";
import type { JoinPaths } from "../../../common/path/join-paths.injectable";
import type { WriteJson } from "../../../common/fs/write-json-file.injectable";
import type { ReadJson } from "../../../common/fs/read-json-file.injectable";
interface Dependencies {
storage: { initialized: boolean; loaded: boolean; data: Record<string, any> };
storage: { initialized: boolean; loaded: boolean; data: Partial<Record<string, unknown>> };
logger: Logger;
directoryForLensLocalStorage: string;
readJsonFile: (filePath: string) => Promise<JsonValue>;
writeJsonFile: (filePath: string, contentObject: JsonObject) => Promise<void>;
readJsonFile: ReadJson;
writeJsonFile: WriteJson;
joinPaths: JoinPaths;
hostedClusterId: string | undefined;
saveDelay: number;
@@ -37,9 +39,7 @@ export const createStorage = ({
writeJsonFile,
hostedClusterId,
saveDelay,
}: Dependencies): CreateStorage => (key, defaultValue) => {
const { logPrefix } = StorageHelper;
}: Dependencies): CreateStorage => <T>(key: string, defaultValue: T) => {
if (!storage.initialized) {
storage.initialized = true;
@@ -51,7 +51,7 @@ export const createStorage = ({
} catch {
// do nothing
} finally {
logger.info(`${logPrefix} loading finished for ${filePath}`);
logger.info(`${storageHelperLogPrefix} loading finished for ${filePath}`);
storage.loaded = true;
}
@@ -62,30 +62,32 @@ export const createStorage = ({
});
async function saveFile(state: Record<string, any> = {}) {
logger.info(`${logPrefix} saving ${filePath}`);
logger.info(`${storageHelperLogPrefix} saving ${filePath}`);
try {
await writeJsonFile(filePath, state);
} catch (error) {
logger.error(`${logPrefix} saving failed: ${error}`, {
logger.error(`${storageHelperLogPrefix} saving failed: ${error}`, {
json: state, jsonFilePath: filePath,
});
}
}
})()
.catch(error => logger.error(`${logPrefix} Failed to initialize storage: ${error}`));
.catch(error => logger.error(`${storageHelperLogPrefix} Failed to initialize storage: ${error}`));
}
return new StorageHelper(key, {
return new StorageHelper({
logger,
}, key, {
autoInit: true,
defaultValue,
storage: {
async getItem(key: string) {
await when(() => storage.loaded);
return storage.data[key];
return storage.data[key] as T;
},
setItem(key: string, value: any) {
setItem(key: string, value: T) {
storage.data[key] = value;
},
removeItem(key: string) {
+14 -9
View File
@@ -8,8 +8,8 @@ import { action, comparer, computed, makeObservable, observable, observe, toJS,
import type { Draft } from "immer";
import { produce, isDraft } from "immer";
import { isEqual, isPlainObject } from "lodash";
import logger from "../../main/logger";
import assert from "assert";
import type { Logger } from "../../common/logger";
export interface StorageChange<T> {
key: string;
@@ -26,9 +26,9 @@ export interface StorageAdapter<T> {
}
export interface StorageHelperOptions<T> {
autoInit?: boolean; // start preloading data immediately, default: true
storage: StorageAdapter<T>;
defaultValue: T;
readonly autoInit?: boolean; // start preloading data immediately, default: true
readonly storage: StorageAdapter<T>;
readonly defaultValue: T;
}
export interface StorageLayer<T> {
@@ -41,11 +41,16 @@ export interface StorageLayer<T> {
merge(value: Partial<T> | ((draft: Draft<T>) => Partial<T> | void)): void;
}
export const storageHelperLogPrefix = "[STORAGE-HELPER]:";
interface Dependencies {
readonly logger: Logger;
}
export class StorageHelper<T> implements StorageLayer<T> {
static logPrefix = "[StorageHelper]:";
readonly storage: StorageAdapter<T>;
private data = observable.box<T | undefined>(undefined, {
private readonly data = observable.box<T | undefined>(undefined, {
deep: true,
equals: comparer.structural,
});
@@ -61,7 +66,7 @@ export class StorageHelper<T> implements StorageLayer<T> {
return this.options.defaultValue;
}
constructor(readonly key: string, private options: StorageHelperOptions<T>) {
constructor(private readonly dependencies: Dependencies, readonly key: string, private readonly options: StorageHelperOptions<T>) {
makeObservable(this);
const { storage, autoInit = true } = options;
@@ -89,7 +94,7 @@ export class StorageHelper<T> implements StorageLayer<T> {
};
private onError = (error: any): void => {
logger.error(`${StorageHelper.logPrefix} loading error: ${error}`, this);
this.dependencies.logger.error(`${storageHelperLogPrefix} loading error: ${error}`, this);
};
@action
@@ -127,7 +132,7 @@ export class StorageHelper<T> implements StorageLayer<T> {
this.storage.onChange?.({ value, oldValue, key: this.key });
} catch (error) {
logger.error(`${StorageHelper.logPrefix} updating storage: ${error}`, this, { value, oldValue });
this.dependencies.logger.error(`${storageHelperLogPrefix} updating storage: ${error}`, this, { value, oldValue });
}
}
@@ -3,43 +3,34 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { beforeFrameStartsInjectionToken } from "../../before-frame-starts/before-frame-starts-injection-token";
import { beforeFrameStartsInjectionToken } from "../../before-frame-starts/tokens";
import { syncBoxInitialValueChannel } from "../../../common/utils/sync-box/channels";
import createSyncBoxStateInjectable from "../../../common/utils/sync-box/sync-box-state.injectable";
import { requestFromChannelInjectionToken } from "../../../common/utils/channel/request-from-channel-injection-token";
import { runInAction } from "mobx";
import type { SyncBox } from "../../../common/utils/sync-box/sync-box-injection-token";
import { syncBoxInjectionToken } from "../../../common/utils/sync-box/sync-box-injection-token";
import assert from "assert";
const provideInitialValuesForSyncBoxesInjectable = getInjectable({
id: "provide-initial-values-for-sync-boxes",
instantiate: (di) => {
const requestFromChannel = di.inject(requestFromChannelInjectionToken);
instantiate: (di) => ({
id: "provide-initial-values-for-sync-boxes",
run: async () => {
const requestFromChannel = di.inject(requestFromChannelInjectionToken);
const syncBoxes = di.injectMany(syncBoxInjectionToken);
const initialValues = await requestFromChannel(syncBoxInitialValueChannel);
const syncBoxes = di.injectMany(syncBoxInjectionToken);
runInAction(() => {
for (const { id, value } of initialValues) {
const syncBox = syncBoxes.find((box) => box.id === id);
const setSyncBoxState = (syncBox: SyncBox<any>, state: any) =>
di.inject(createSyncBoxStateInjectable, syncBox.id).set(state);
return {
id: "provide-initial-values-for-sync-boxes",
run: async () => {
const initialValues = await requestFromChannel(syncBoxInitialValueChannel);
runInAction(() => {
initialValues.forEach(({ id, value }) => {
const syncBox = syncBoxes.find((box) => box.id === id);
assert(syncBox);
setSyncBoxState(syncBox, value);
});
});
},
};
},
assert(syncBox);
di.inject(createSyncBoxStateInjectable, syncBox.id).set(value);
}
});
},
}),
injectionToken: beforeFrameStartsInjectionToken,
});
@@ -3,19 +3,19 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { beforeFrameStartsInjectionToken } from "../../before-frame-starts/before-frame-starts-injection-token";
import { beforeFrameStartsInjectionToken } from "../../before-frame-starts/tokens";
import buildVersionInjectable from "./build-version.injectable";
const initializeBuildVersionInjectable = getInjectable({
id: "initialize-build-version",
instantiate: (di) => {
const buildVersion = di.inject(buildVersionInjectable);
instantiate: (di) => ({
id: "initialize-build-version",
run: async () => {
const buildVersion = di.inject(buildVersionInjectable);
return {
id: "initialize-build-version",
run: () => buildVersion.init(),
};
},
await buildVersion.init();
},
}),
injectionToken: beforeFrameStartsInjectionToken,
});
@@ -3,21 +3,21 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { beforeFrameStartsInjectionToken } from "../../before-frame-starts/before-frame-starts-injection-token";
import { beforeFrameStartsInjectionToken } from "../../before-frame-starts/tokens";
import initReleaseChannelInjectable from "../release-channel/init.injectable";
import defaultUpdateChannelInjectable from "../../../features/application-update/common/selected-update-channel/default-update-channel.injectable";
const initDefaultUpdateChannelInjectable = getInjectable({
id: "init-default-update-channel",
instantiate: (di) => {
const defaultUpdateChannel = di.inject(defaultUpdateChannelInjectable);
instantiate: (di) => ({
id: "init-default-update-channel",
run: async () => {
const defaultUpdateChannel = di.inject(defaultUpdateChannelInjectable);
return {
id: "init-default-update-channel",
run: () => defaultUpdateChannel.init(),
runAfter: di.inject(initReleaseChannelInjectable),
};
},
await defaultUpdateChannel.init();
},
runAfter: di.inject(initReleaseChannelInjectable),
}),
injectionToken: beforeFrameStartsInjectionToken,
});
@@ -4,20 +4,20 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import releaseChannelInjectable from "../../../common/vars/release-channel.injectable";
import { beforeFrameStartsInjectionToken } from "../../before-frame-starts/before-frame-starts-injection-token";
import { beforeFrameStartsInjectionToken } from "../../before-frame-starts/tokens";
import initSemanticBuildVersionInjectable from "../semantic-build-version/init.injectable";
const initReleaseChannelInjectable = getInjectable({
id: "init-release-channel",
instantiate: (di) => {
const releaseChannel = di.inject(releaseChannelInjectable);
instantiate: (di) => ({
id: "init-release-channel",
run: async () => {
const releaseChannel = di.inject(releaseChannelInjectable);
return {
id: "init-release-channel",
run: () => releaseChannel.init(),
runAfter: di.inject(initSemanticBuildVersionInjectable),
};
},
await releaseChannel.init();
},
runAfter: di.inject(initSemanticBuildVersionInjectable),
}),
injectionToken: beforeFrameStartsInjectionToken,
});
@@ -4,20 +4,20 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import buildSemanticVersionInjectable from "../../../common/vars/build-semantic-version.injectable";
import { beforeFrameStartsInjectionToken } from "../../before-frame-starts/before-frame-starts-injection-token";
import { beforeFrameStartsInjectionToken } from "../../before-frame-starts/tokens";
import initializeBuildVersionInjectable from "../build-version/init.injectable";
const initSemanticBuildVersionInjectable = getInjectable({
id: "init-semantic-build-version",
instantiate: (di) => {
const buildSemanticVersion = di.inject(buildSemanticVersionInjectable);
instantiate: (di) => ({
id: "init-semantic-build-version",
run: async () => {
const buildSemanticVersion = di.inject(buildSemanticVersionInjectable);
return {
id: "init-semantic-build-version",
run: () => buildSemanticVersion.init(),
runAfter: di.inject(initializeBuildVersionInjectable),
};
},
await buildSemanticVersion.init();
},
runAfter: di.inject(initializeBuildVersionInjectable),
}),
injectionToken: beforeFrameStartsInjectionToken,
});