Fixing Singleton typing to correctly return child class (#1914)

- Add distinction between `getInstance` and `getInstanceOrCreate` since
  it is not always possible to create an instance (since you might not
  know the correct arguments)

- Remove all the `export const *Store = *Store.getInstance<*Store>();`
  calls as it defeats the purpose of `Singleton`. Plus with the typing
  changes the appropriate `*Store.getInstance()` is "short enough".

- Special case the two extension export facades to not need to use
  `getInstanceOrCreate`. Plus since they are just facades it is always
  possible to create them.

- Move some other types to be also `Singleton`'s: ExtensionLoader,
  ExtensionDiscovery, ThemeStore, LocalizationStore, ...

- Fixed dev-run always using the same port with electron inspect

- Update Store documentation with new recommendations about creating
  instances of singletons

- Fix all unit tests to create their dependent singletons

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton authored and GitHub committed 2021-04-21 09:59:59 -04:00
1 parent b2a570ce28
commit 9563ead2e6
114 files changed
+924 -736

No files matched your search

+23 -13
View File
@@ -6,19 +6,20 @@ import * as MobxReact from "mobx-react";
import * as ReactRouter from "react-router";
import * as ReactRouterDom from "react-router-dom";
import { render, unmountComponentAtNode } from "react-dom";
import { clusterStore } from "../common/cluster-store";
import { userStore } from "../common/user-store";
import { delay } from "../common/utils";
import { isMac, isDevelopment } from "../common/vars";
import { HotbarStore } from "../common/hotbar-store";
import { ClusterStore } from "../common/cluster-store";
import { UserStore } from "../common/user-store";
import * as LensExtensions from "../extensions/extension-api";
import { extensionDiscovery } from "../extensions/extension-discovery";
import { extensionLoader } from "../extensions/extension-loader";
import { extensionsStore } from "../extensions/extensions-store";
import { hotbarStore } from "../common/hotbar-store";
import { filesystemProvisionerStore } from "../main/extension-filesystem";
import { ExtensionDiscovery } from "../extensions/extension-discovery";
import { ExtensionLoader } from "../extensions/extension-loader";
import { ExtensionsStore } from "../extensions/extensions-store";
import { FilesystemProvisionerStore } from "../main/extension-filesystem";
import { App } from "./components/app";
import { LensApp } from "./lens-app";
import { themeStore } from "./theme.store";
import { ThemeStore } from "./theme.store";
import { HelmRepoManager } from "../main/helm/helm-repo-manager";
/**
* If this is a development buid, wait a second to attach
@@ -50,8 +51,16 @@ export async function bootstrap(App: AppComponent) {
await attachChromeDebugger();
rootElem.classList.toggle("is-mac", isMac);
extensionLoader.init();
extensionDiscovery.init();
ExtensionLoader.getInstanceOrCreate().init();
ExtensionDiscovery.getInstanceOrCreate().init();
const userStore = UserStore.getInstanceOrCreate();
const clusterStore = ClusterStore.getInstanceOrCreate();
const extensionsStore = ExtensionsStore.getInstanceOrCreate();
const filesystemStore = FilesystemProvisionerStore.getInstanceOrCreate();
const themeStore = ThemeStore.getInstanceOrCreate();
const hotbarStore = HotbarStore.getInstanceOrCreate();
const helmRepoManager = HelmRepoManager.getInstanceOrCreate();
// preload common stores
await Promise.all([
@@ -59,8 +68,9 @@ export async function bootstrap(App: AppComponent) {
hotbarStore.load(),
clusterStore.load(),
extensionsStore.load(),
filesystemProvisionerStore.load(),
filesystemStore.load(),
themeStore.init(),
helmRepoManager.init(),
]);
// Register additional store listeners
@@ -72,8 +82,8 @@ export async function bootstrap(App: AppComponent) {
}
window.addEventListener("message", (ev: MessageEvent) => {
if (ev.data === "teardown") {
userStore.unregisterIpcListener();
clusterStore.unregisterIpcListener();
UserStore.getInstance(false)?.unregisterIpcListener();
ClusterStore.getInstance(false)?.unregisterIpcListener();
unmountComponentAtNode(rootElem);
window.location.href = "about:blank";
}
@@ -11,10 +11,10 @@ import { AceEditor } from "../ace-editor";
import { Button } from "../button";
import { Icon } from "../icon";
import { kubeConfigDefaultPath, loadConfig, splitConfig, validateConfig, validateKubeConfig } from "../../../common/kube-helpers";
import { ClusterModel, ClusterStore, clusterStore } from "../../../common/cluster-store";
import { ClusterModel, ClusterStore } from "../../../common/cluster-store";
import { v4 as uuid } from "uuid";
import { navigate } from "../../navigation";
import { userStore } from "../../../common/user-store";
import { UserStore } from "../../../common/user-store";
import { cssNames } from "../../utils";
import { Notifications } from "../notifications";
import { Tab, Tabs } from "../tabs";
@@ -44,13 +44,13 @@ export class AddCluster extends React.Component {
@observable showSettings = false;
componentDidMount() {
clusterStore.setActive(null);
this.setKubeConfig(userStore.kubeConfigPath);
ClusterStore.getInstance().setActive(null);
this.setKubeConfig(UserStore.getInstance().kubeConfigPath);
appEventBus.emit({ name: "cluster-add", action: "start" });
}
componentWillUnmount() {
userStore.markNewContextsAsSeen();
UserStore.getInstance().markNewContextsAsSeen();
}
@action
@@ -60,9 +60,9 @@ export class AddCluster extends React.Component {
validateConfig(this.kubeConfigLocal);
this.refreshContexts();
this.kubeConfigPath = filePath;
userStore.kubeConfigPath = filePath; // save to store
UserStore.getInstance().kubeConfigPath = filePath; // save to store
} catch (err) {
if (!userStore.isDefaultKubeConfigPath) {
if (!UserStore.getInstance().isDefaultKubeConfigPath) {
Notifications.error(
<div>Can&apos;t setup <code>{filePath}</code> as kubeconfig: {String(err)}</div>
);
@@ -181,7 +181,7 @@ export class AddCluster extends React.Component {
});
runInAction(() => {
clusterStore.addClusters(...newClusters);
ClusterStore.getInstance().addClusters(...newClusters);
Notifications.ok(
<>Successfully imported <b>{newClusters.length}</b> cluster(s)</>
@@ -308,7 +308,7 @@ export class AddCluster extends React.Component {
}
onKubeConfigInputBlur = () => {
const isChanged = this.kubeConfigPath !== userStore.kubeConfigPath;
const isChanged = this.kubeConfigPath !== UserStore.getInstance().kubeConfigPath;
if (isChanged) {
this.kubeConfigPath = this.kubeConfigPath.replace("~", os.homedir());
@@ -316,7 +316,7 @@ export class AddCluster extends React.Component {
try {
this.setKubeConfig(this.kubeConfigPath, { throwError: true });
} catch (err) {
this.setKubeConfig(userStore.kubeConfigPath); // revert to previous valid path
this.setKubeConfig(UserStore.getInstance().kubeConfigPath); // revert to previous valid path
}
}
};
@@ -328,7 +328,7 @@ export class AddCluster extends React.Component {
};
protected formatContextLabel = ({ value: context }: SelectOption<string>) => {
const isNew = userStore.newContexts.has(context);
const isNew = UserStore.getInstance().newContexts.has(context);
const isSelected = this.selectedContexts.includes(context);
return (
@@ -19,7 +19,7 @@ import { Button } from "../button";
import { releaseStore } from "./release.store";
import { Notifications } from "../notifications";
import { createUpgradeChartTab } from "../dock/upgrade-chart.store";
import { themeStore } from "../../theme.store";
import { ThemeStore } from "../../theme.store";
import { apiManager } from "../../api/api-manager";
import { SubTitle } from "../layout/sub-title";
import { secretsStore } from "../+config-secrets/secrets.store";
@@ -259,7 +259,7 @@ export class ReleaseDetails extends Component<Props> {
return (
<Drawer
className={cssNames("ReleaseDetails", themeStore.activeTheme.type)}
className={cssNames("ReleaseDetails", ThemeStore.getInstance().activeTheme.type)}
usePortal={true}
open={!!release}
title={title}
+3 -3
View File
@@ -11,7 +11,7 @@ import { MenuItem, MenuActions } from "../menu";
import { Icon } from "../icon";
import { CatalogEntityContextMenu, CatalogEntityContextMenuContext, catalogEntityRunContext } from "../../api/catalog-entity";
import { Badge } from "../badge";
import { hotbarStore } from "../../../common/hotbar-store";
import { HotbarStore } from "../../../common/hotbar-store";
import { autobind } from "../../utils";
import { Notifications } from "../notifications";
import { ConfirmDialog } from "../confirm-dialog";
@@ -57,7 +57,7 @@ export class Catalog extends React.Component {
}
addToHotbar(item: CatalogEntityItem) {
const hotbar = hotbarStore.getByName("default"); // FIXME
const hotbar = HotbarStore.getInstance().getByName("default"); // FIXME
if (!hotbar) {
return;
@@ -67,7 +67,7 @@ export class Catalog extends React.Component {
}
removeFromHotbar(item: CatalogEntityItem) {
const hotbar = hotbarStore.getByName("default"); // FIXME
const hotbar = HotbarStore.getInstance().getByName("default"); // FIXME
if (!hotbar) {
return;
@@ -11,7 +11,7 @@ import { eventStore } from "../+events/event.store";
import { autobind, cssNames, prevDefault } from "../../utils";
import { ItemObject } from "../../item.store";
import { Spinner } from "../spinner";
import { themeStore } from "../../theme.store";
import { ThemeStore } from "../../theme.store";
import { lookupApiLink } from "../../api/kube-api";
import { kubeSelectedUrlParam, showDetails } from "../kube-object";
@@ -145,7 +145,7 @@ export class ClusterIssues extends React.Component<Props> {
sortByDefault={{ sortBy: sortBy.object, orderBy: "asc" }}
sortSyncWithUrl={false}
getTableRow={this.getTableRow}
className={cssNames("box grow", themeStore.activeTheme.type)}
className={cssNames("box grow", ThemeStore.getInstance().activeTheme.type)}
>
<TableHead nowrap>
<TableCell className="message">Message</TableCell>
@@ -5,7 +5,7 @@ import { reaction } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react";
import { nodesStore } from "../+nodes/nodes.store";
import { podsStore } from "../+workloads-pods/pods.store";
import { clusterStore, getHostedCluster } from "../../../common/cluster-store";
import { ClusterStore, getHostedCluster } from "../../../common/cluster-store";
import { interval } from "../../utils";
import { TabLayout } from "../layout/tab-layout";
import { Spinner } from "../spinner";
@@ -66,7 +66,7 @@ export class ClusterOverview extends React.Component {
render() {
const isLoaded = nodesStore.isLoaded && podsStore.isLoaded;
const isMetricsHidden = clusterStore.isMetricHidden(ResourceType.Cluster);
const isMetricsHidden = ClusterStore.getInstance().isMetricHidden(ResourceType.Cluster);
return (
<TabLayout>
@@ -9,7 +9,7 @@ import { nodesStore } from "../+nodes/nodes.store";
import { ChartData, PieChart } from "../chart";
import { ClusterNoMetrics } from "./cluster-no-metrics";
import { bytesToUnits } from "../../utils";
import { themeStore } from "../../theme.store";
import { ThemeStore } from "../../theme.store";
import { getMetricLastPoints } from "../../api/endpoints/metrics.api";
export const ClusterPieCharts = observer(() => {
@@ -29,7 +29,7 @@ export const ClusterPieCharts = observer(() => {
const { podUsage, podCapacity } = data;
const cpuLimitsOverload = cpuLimits > cpuCapacity;
const memoryLimitsOverload = memoryLimits > memoryCapacity;
const defaultColor = themeStore.activeTheme.colors.pieChartDefaultColor;
const defaultColor = ThemeStore.getInstance().activeTheme.colors.pieChartDefaultColor;
if (!memoryCapacity || !cpuCapacity || !podCapacity) return null;
const cpuData: ChartData = {
@@ -2,10 +2,12 @@ import "@testing-library/jest-dom/extend-expect";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import fse from "fs-extra";
import React from "react";
import { extensionDiscovery } from "../../../../extensions/extension-discovery";
import { UserStore } from "../../../../common/user-store";
import { ExtensionDiscovery } from "../../../../extensions/extension-discovery";
import { ExtensionLoader } from "../../../../extensions/extension-loader";
import { ThemeStore } from "../../../theme.store";
import { ConfirmDialog } from "../../confirm-dialog";
import { Notifications } from "../../notifications";
import { ExtensionStateStore } from "../extension-install.store";
import { Extensions } from "../extensions";
jest.mock("fs-extra");
@@ -18,46 +20,52 @@ jest.mock("../../../../common/utils", () => ({
extractTar: jest.fn(() => Promise.resolve())
}));
jest.mock("../../../../extensions/extension-discovery", () => ({
...jest.requireActual("../../../../extensions/extension-discovery"),
extensionDiscovery: {
localFolderPath: "/fake/path",
uninstallExtension: jest.fn(() => Promise.resolve()),
isLoaded: true
}
}));
jest.mock("../../../../extensions/extension-loader", () => ({
...jest.requireActual("../../../../extensions/extension-loader"),
extensionLoader: {
userExtensions: new Map([
["extensionId", {
id: "extensionId",
manifest: {
name: "test",
version: "1.2.3"
},
absolutePath: "/absolute/path",
manifestPath: "/symlinked/path/package.json",
isBundled: false,
isEnabled: true
}]
])
}
}));
jest.mock("../../notifications", () => ({
ok: jest.fn(),
error: jest.fn(),
info: jest.fn()
}));
jest.mock("electron", () => {
return {
app: {
getVersion: () => "99.99.99",
getPath: () => "tmp",
getLocale: () => "en",
setLoginItemSettings: (): void => void 0,
}
};
});
describe("Extensions", () => {
beforeEach(() => {
ExtensionStateStore.resetInstance();
beforeEach(async () => {
UserStore.resetInstance();
ThemeStore.resetInstance();
await UserStore.getInstanceOrCreate().load();
await ThemeStore.getInstanceOrCreate().init();
ExtensionLoader.resetInstance();
ExtensionDiscovery.resetInstance();
Extensions.installStates.clear();
ExtensionDiscovery.getInstanceOrCreate().uninstallExtension = jest.fn(() => Promise.resolve());
ExtensionLoader.getInstanceOrCreate().addExtension({
id: "extensionId",
manifest: {
name: "test",
version: "1.2.3"
},
absolutePath: "/absolute/path",
manifestPath: "/symlinked/path/package.json",
isBundled: false,
isEnabled: true
});
});
it("disables uninstall and disable buttons while uninstalling", async () => {
ExtensionDiscovery.getInstance().isLoaded = true;
render(<><Extensions /><ConfirmDialog/></>);
expect(screen.getByText("Disable").closest("button")).not.toBeDisabled();
@@ -68,13 +76,14 @@ describe("Extensions", () => {
// Approve confirm dialog
fireEvent.click(screen.getByText("Yes"));
expect(extensionDiscovery.uninstallExtension).toHaveBeenCalled();
expect(ExtensionDiscovery.getInstance().uninstallExtension).toHaveBeenCalled();
expect(screen.getByText("Disable").closest("button")).toBeDisabled();
expect(screen.getByText("Uninstall").closest("button")).toBeDisabled();
});
it("displays error notification on uninstall error", () => {
(extensionDiscovery.uninstallExtension as any).mockImplementationOnce(() =>
ExtensionDiscovery.getInstance().isLoaded = true;
(ExtensionDiscovery.getInstance().uninstallExtension as any).mockImplementationOnce(() =>
Promise.reject()
);
render(<><Extensions /><ConfirmDialog/></>);
@@ -115,14 +124,14 @@ describe("Extensions", () => {
});
it("displays spinner while extensions are loading", () => {
extensionDiscovery.isLoaded = false;
ExtensionDiscovery.getInstance().isLoaded = false;
const { container } = render(<Extensions />);
expect(container.querySelector(".Spinner")).toBeInTheDocument();
extensionDiscovery.isLoaded = true;
ExtensionDiscovery.getInstance().isLoaded = true;
waitFor(() =>
waitFor(() =>
expect(container.querySelector(".Spinner")).not.toBeInTheDocument()
);
});
@@ -1,13 +0,0 @@
import { observable } from "mobx";
import { autobind, Singleton } from "../../utils";
interface ExtensionState {
displayName: string;
// Possible states the extension can be
state: "installing" | "uninstalling";
}
@autobind()
export class ExtensionStateStore extends Singleton {
extensionState = observable.map<string, ExtensionState>();
}
@@ -7,8 +7,8 @@ import path from "path";
import React from "react";
import { downloadFile, extractTar, listTarEntries, readFileFromTar } from "../../../common/utils";
import { docsUrl } from "../../../common/vars";
import { extensionDiscovery, InstalledExtension, manifestFilename } from "../../../extensions/extension-discovery";
import { extensionLoader } from "../../../extensions/extension-loader";
import { ExtensionDiscovery, InstalledExtension, manifestFilename } from "../../../extensions/extension-discovery";
import { ExtensionLoader } from "../../../extensions/extension-loader";
import { extensionDisplayName, LensExtensionManifest, sanitizeExtensionName } from "../../../extensions/lens-extension";
import logger from "../../../main/logger";
import { prevDefault } from "../../utils";
@@ -21,7 +21,6 @@ import { SubTitle } from "../layout/sub-title";
import { Notifications } from "../notifications";
import { Spinner } from "../spinner/spinner";
import { TooltipPosition } from "../tooltip";
import { ExtensionStateStore } from "./extension-install.store";
import "./extensions.scss";
interface InstallRequest {
@@ -39,6 +38,12 @@ interface InstallRequestValidated extends InstallRequestPreloaded {
tempFile: string; // temp system path to packed extension for unpacking
}
interface ExtensionState {
displayName: string;
// Possible states the extension can be
state: "installing" | "uninstalling";
}
@observer
export class Extensions extends React.Component {
private static supportedFormats = ["tar", "tgz"];
@@ -50,9 +55,7 @@ export class Extensions extends React.Component {
}
};
get extensionStateStore() {
return ExtensionStateStore.getInstance<ExtensionStateStore>();
}
static installStates = observable.map<string, ExtensionState>();
@observable search = "";
@observable installPath = "";
@@ -64,7 +67,7 @@ export class Extensions extends React.Component {
* Extensions that were removed from extensions but are still in "uninstalling" state
*/
@computed get removedUninstalling() {
return Array.from(this.extensionStateStore.extensionState.entries())
return Array.from(Extensions.installStates.entries())
.filter(([id, extension]) =>
extension.state === "uninstalling"
&& !this.extensions.find(extension => extension.id === id)
@@ -76,7 +79,7 @@ export class Extensions extends React.Component {
* Extensions that were added to extensions but are still in "installing" state
*/
@computed get addedInstalling() {
return Array.from(this.extensionStateStore.extensionState.entries())
return Array.from(Extensions.installStates.entries())
.filter(([id, extension]) =>
extension.state === "installing"
&& this.extensions.find(extension => extension.id === id)
@@ -91,7 +94,7 @@ export class Extensions extends React.Component {
Notifications.ok(
<p>Extension <b>{displayName}</b> successfully uninstalled!</p>
);
this.extensionStateStore.extensionState.delete(id);
Extensions.installStates.delete(id);
});
this.addedInstalling.forEach(({ id, displayName }) => {
@@ -104,7 +107,7 @@ export class Extensions extends React.Component {
Notifications.ok(
<p>Extension <b>{displayName}</b> successfully installed!</p>
);
this.extensionStateStore.extensionState.delete(id);
Extensions.installStates.delete(id);
this.installPath = "";
// Enable installed extensions by default.
@@ -117,7 +120,7 @@ export class Extensions extends React.Component {
@computed get extensions() {
const searchText = this.search.toLowerCase();
return Array.from(extensionLoader.userExtensions.values())
return Array.from(ExtensionLoader.getInstance().userExtensions.values())
.filter(({ manifest: { name, description }}) => (
name.toLowerCase().includes(searchText)
|| description?.toLowerCase().includes(searchText)
@@ -125,7 +128,7 @@ export class Extensions extends React.Component {
}
get extensionsPath() {
return extensionDiscovery.localFolderPath;
return ExtensionDiscovery.getInstance().localFolderPath;
}
getExtensionPackageTemp(fileName = "") {
@@ -342,11 +345,11 @@ export class Extensions extends React.Component {
async unpackExtension({ fileName, tempFile, manifest: { name, version } }: InstallRequestValidated) {
const displayName = extensionDisplayName(name, version);
const extensionId = path.join(extensionDiscovery.nodeModulesPath, name, "package.json");
const extensionId = path.join(ExtensionDiscovery.getInstance().nodeModulesPath, name, "package.json");
logger.info(`Unpacking extension ${displayName}`, { fileName, tempFile });
this.extensionStateStore.extensionState.set(extensionId, {
Extensions.installStates.set(extensionId, {
state: "installing",
displayName
});
@@ -381,8 +384,8 @@ export class Extensions extends React.Component {
);
// Remove install state on install failure
if (this.extensionStateStore.extensionState.get(extensionId)?.state === "installing") {
this.extensionStateStore.extensionState.delete(extensionId);
if (Extensions.installStates.get(extensionId)?.state === "installing") {
Extensions.installStates.delete(extensionId);
}
} finally {
// clean up
@@ -406,20 +409,20 @@ export class Extensions extends React.Component {
const displayName = extensionDisplayName(extension.manifest.name, extension.manifest.version);
try {
this.extensionStateStore.extensionState.set(extension.id, {
Extensions.installStates.set(extension.id, {
state: "uninstalling",
displayName
});
await extensionDiscovery.uninstallExtension(extension);
await ExtensionDiscovery.getInstance().uninstallExtension(extension);
} catch (error) {
Notifications.error(
<p>Uninstalling extension <b>{displayName}</b> has failed: <em>{error?.message ?? ""}</em></p>
);
// Remove uninstall state on uninstall failure
if (this.extensionStateStore.extensionState.get(extension.id)?.state === "uninstalling") {
this.extensionStateStore.extensionState.delete(extension.id);
if (Extensions.installStates.get(extension.id)?.state === "uninstalling") {
Extensions.installStates.delete(extension.id);
}
}
}
@@ -445,7 +448,7 @@ export class Extensions extends React.Component {
return extensions.map(extension => {
const { id, isEnabled, manifest } = extension;
const { name, description, version } = manifest;
const isUninstalling = this.extensionStateStore.extensionState.get(id)?.state === "uninstalling";
const isUninstalling = Extensions.installStates.get(id)?.state === "uninstalling";
return (
<div key={id} className="extension flex gaps align-center">
@@ -478,7 +481,7 @@ export class Extensions extends React.Component {
* True if at least one extension is in installing state
*/
@computed get isInstalling() {
return [...this.extensionStateStore.extensionState.values()].some(extension => extension.state === "installing");
return [...Extensions.installStates.values()].some(extension => extension.state === "installing");
}
render() {
@@ -536,7 +539,11 @@ export class Extensions extends React.Component {
value={this.search}
onChange={(value) => this.search = value}
/>
{extensionDiscovery.isLoaded ? this.renderExtensions() : <div className="spinner-wrapper"><Spinner/></div>}
{
ExtensionDiscovery.getInstance().isLoaded
? this.renderExtensions()
: <div className="spinner-wrapper"><Spinner/></div>
}
</div>
</PageLayout>
</DropFileInput>
@@ -15,7 +15,7 @@ import { KubeObjectMeta } from "../kube-object/kube-object-meta";
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
import { getBackendServiceNamePort } from "../../api/endpoints/ingress.api";
import { ResourceType } from "../cluster-settings/components/cluster-metrics-setting";
import { clusterStore } from "../../../common/cluster-store";
import { ClusterStore } from "../../../common/cluster-store";
interface Props extends KubeObjectDetailsProps<Ingress> {
}
@@ -101,6 +101,7 @@ export class IngressDetails extends React.Component<Props> {
if (!ingress) {
return null;
}
const { spec, status } = ingress;
const ingressPoints = status?.loadBalancer?.ingress;
const { metrics } = ingressStore;
@@ -108,8 +109,7 @@ export class IngressDetails extends React.Component<Props> {
"Network",
"Duration",
];
const isMetricHidden = clusterStore.isMetricHidden(ResourceType.Ingress);
const isMetricHidden = ClusterStore.getInstance().isMetricHidden(ResourceType.Ingress);
const { serviceName, servicePort } = ingress.getServiceNamePort();
return (
@@ -6,7 +6,7 @@ import { NoMetrics } from "../resource-metrics/no-metrics";
import { IResourceMetricsValue, ResourceMetricsContext } from "../resource-metrics";
import { observer } from "mobx-react";
import { ChartOptions, ChartPoint } from "chart.js";
import { themeStore } from "../../theme.store";
import { ThemeStore } from "../../theme.store";
import { mapValues } from "lodash";
type IContext = IResourceMetricsValue<Node, { metrics: IClusterMetrics }>;
@@ -14,7 +14,7 @@ type IContext = IResourceMetricsValue<Node, { metrics: IClusterMetrics }>;
export const NodeCharts = observer(() => {
const { params: { metrics }, tabId, object } = useContext<IContext>(ResourceMetricsContext);
const id = object.getId();
const { chartCapacityColor } = themeStore.activeTheme.colors;
const { chartCapacityColor } = ThemeStore.getInstance().activeTheme.colors;
if (!metrics) {
return null;
@@ -18,7 +18,7 @@ import { KubeObjectMeta } from "../kube-object/kube-object-meta";
import { KubeEventDetails } from "../+events/kube-event-details";
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
import { ResourceType } from "../cluster-settings/components/cluster-metrics-setting";
import { clusterStore } from "../../../common/cluster-store";
import { ClusterStore } from "../../../common/cluster-store";
interface Props extends KubeObjectDetailsProps<Node> {
}
@@ -54,7 +54,7 @@ export class NodeDetails extends React.Component<Props> {
"Disk",
"Pods",
];
const isMetricHidden = clusterStore.isMetricHidden(ResourceType.Node);
const isMetricHidden = ClusterStore.getInstance().isMetricHidden(ResourceType.Node);
return (
<div className="NodeDetails">
@@ -13,7 +13,7 @@ import { systemName, isUrl, isPath } from "../input/input_validators";
import { SubTitle } from "../layout/sub-title";
import { Icon } from "../icon";
import { Notifications } from "../notifications";
import { HelmRepo, repoManager } from "../../../main/helm/helm-repo-manager";
import { HelmRepo, HelmRepoManager } from "../../../main/helm/helm-repo-manager";
interface Props extends Partial<DialogProps> {
onAddRepo: Function
@@ -79,7 +79,7 @@ export class AddHelmRepoDialog extends React.Component<Props> {
async addCustomRepo() {
try {
await repoManager.addСustomRepo(this.helmRepo);
await HelmRepoManager.getInstance().addСustomRepo(this.helmRepo);
Notifications.ok(<>Helm repository <b>{this.helmRepo.name}</b> has added</>);
this.props.onAddRepo();
this.close();
@@ -3,7 +3,7 @@ import "./helm-charts.scss";
import React from "react";
import { action, computed, observable } from "mobx";
import { HelmRepo, repoManager } from "../../../main/helm/helm-repo-manager";
import { HelmRepo, HelmRepoManager } from "../../../main/helm/helm-repo-manager";
import { Button } from "../button";
import { Icon } from "../icon";
import { Notifications } from "../notifications";
@@ -34,9 +34,9 @@ export class HelmCharts extends React.Component {
try {
if (!this.repos.length) {
this.repos = await repoManager.loadAvailableRepos(); // via https://helm.sh
this.repos = await HelmRepoManager.getInstance().loadAvailableRepos(); // via https://helm.sh
}
const repos = await repoManager.repositories(); // via helm-cli
const repos = await HelmRepoManager.getInstance().repositories(); // via helm-cli
this.addedRepos.clear();
repos.forEach(repo => this.addedRepos.set(repo.name, repo));
@@ -49,7 +49,7 @@ export class HelmCharts extends React.Component {
async addRepo(repo: HelmRepo) {
try {
await repoManager.addRepo(repo);
await HelmRepoManager.getInstance().addRepo(repo);
this.addedRepos.set(repo.name, repo);
} catch (err) {
Notifications.error(<>Adding helm branch <b>{repo.name}</b> has failed: {String(err)}</>);
@@ -58,7 +58,7 @@ export class HelmCharts extends React.Component {
async removeRepo(repo: HelmRepo) {
try {
await repoManager.removeRepo(repo);
await HelmRepoManager.getInstance().removeRepo(repo);
this.addedRepos.delete(repo.name);
} catch (err) {
Notifications.error(
@@ -1,7 +1,7 @@
import React, { useState } from "react";
import { Input, InputValidators } from "../input";
import { SubTitle } from "../layout/sub-title";
import { UserPreferences, userStore } from "../../../common/user-store";
import { getDefaultKubectlPath, UserPreferences } from "../../../common/user-store";
import { observer } from "mobx-react";
import { bundledKubectlPath } from "../../../main/kubectl";
import { SelectOption, Select } from "../select";
@@ -59,7 +59,7 @@ export const KubectlBinaries = observer(({ preferences }: { preferences: UserPre
<Input
theme="round-black"
value={downloadPath}
placeholder={userStore.getDefaultKubectlPath()}
placeholder={getDefaultKubectlPath()}
validators={pathValidator}
onChange={setDownloadPath}
onBlur={save}
@@ -5,10 +5,10 @@ import moment from "moment-timezone";
import { computed, observable, reaction } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react";
import { userStore } from "../../../common/user-store";
import { isWindows } from "../../../common/vars";
import { appPreferenceRegistry, RegisteredAppPreference } from "../../../extensions/registries/app-preference-registry";
import { themeStore } from "../../theme.store";
import { UserStore } from "../../../common/user-store";
import { ThemeStore } from "../../theme.store";
import { Input } from "../input";
import { PageLayout } from "../layout/page-layout";
import { SubTitle } from "../layout/sub-title";
@@ -30,12 +30,12 @@ enum Pages {
@observer
export class Preferences extends React.Component {
@observable httpProxy = userStore.preferences.httpsProxy || "";
@observable shell = userStore.preferences.shell || "";
@observable httpProxy = UserStore.getInstance().preferences.httpsProxy || "";
@observable shell = UserStore.getInstance().preferences.shell || "";
@observable activeTab = Pages.Application;
@computed get themeOptions(): SelectOption<string>[] {
return themeStore.themes.map(theme => ({
return ThemeStore.getInstance().themes.map(theme => ({
label: theme.name,
value: theme.id,
}));
@@ -98,18 +98,16 @@ export class Preferences extends React.Component {
}
render() {
const { preferences } = userStore;
const extensions = appPreferenceRegistry.getItems();
const telemetryExtensions = extensions.filter(e => e.showInPreferencesTab == Pages.Telemetry);
let defaultShell = process.env.SHELL || process.env.PTYSHELL;
if (!defaultShell) {
if (isWindows) {
defaultShell = "powershell.exe";
} else {
defaultShell = "System default shell";
}
}
const { preferences } = UserStore.getInstance();
const defaultShell = process.env.SHELL
|| process.env.PTYSHELL
|| (
isWindows
? "powershell.exe"
: "System default shell"
);
return (
<PageLayout
@@ -167,7 +165,7 @@ export class Preferences extends React.Component {
<Select
options={this.timezoneOptions}
value={preferences.localeTimezone}
onChange={({ value }: SelectOption) => userStore.setLocaleTimezone(value)}
onChange={({ value }: SelectOption) => UserStore.getInstance().setLocaleTimezone(value)}
themeName="lens"
/>
</section>
@@ -15,7 +15,7 @@ import { getDetailsUrl, KubeObjectDetailsProps, KubeObjectMeta } from "../kube-o
import { PersistentVolumeClaim } from "../../api/endpoints";
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
import { ResourceType } from "../cluster-settings/components/cluster-metrics-setting";
import { clusterStore } from "../../../common/cluster-store";
import { ClusterStore } from "../../../common/cluster-store";
interface Props extends KubeObjectDetailsProps<PersistentVolumeClaim> {
}
@@ -43,7 +43,7 @@ export class PersistentVolumeClaimDetails extends React.Component<Props> {
const metricTabs = [
"Disk"
];
const isMetricHidden = clusterStore.isMetricHidden(ResourceType.VolumeClaim);
const isMetricHidden = ClusterStore.getInstance().isMetricHidden(ResourceType.VolumeClaim);
return (
<div className="PersistentVolumeClaimDetails">
@@ -5,13 +5,13 @@ import { BarChart, ChartDataSets, memoryOptions } from "../chart";
import { isMetricsEmpty, normalizeMetrics } from "../../api/endpoints/metrics.api";
import { NoMetrics } from "../resource-metrics/no-metrics";
import { IResourceMetricsValue, ResourceMetricsContext } from "../resource-metrics";
import { themeStore } from "../../theme.store";
import { ThemeStore } from "../../theme.store";
type IContext = IResourceMetricsValue<PersistentVolumeClaim, { metrics: IPvcMetrics }>;
export const VolumeClaimDiskChart = observer(() => {
const { params: { metrics }, object } = useContext<IContext>(ResourceMetricsContext);
const { chartCapacityColor } = themeStore.activeTheme.colors;
const { chartCapacityColor } = ThemeStore.getInstance().activeTheme.colors;
const id = object.getId();
if (!metrics) return null;
@@ -3,7 +3,7 @@ import fs from "fs";
import path from "path";
import React from "react";
import { observer } from "mobx-react";
import { userStore } from "../../../common/user-store";
import { UserStore } from "../../../common/user-store";
import { navigate } from "../../navigation";
import { Button } from "../button";
import marked from "marked";
@@ -14,7 +14,7 @@ export class WhatsNew extends React.Component {
ok = () => {
navigate("/");
userStore.saveLastSeenAppVersion();
UserStore.getInstance().saveLastSeenAppVersion();
};
render() {
@@ -19,7 +19,7 @@ import { PodDetailsList } from "../+workloads-pods/pod-details-list";
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
import { ResourceType } from "../cluster-settings/components/cluster-metrics-setting";
import { clusterStore } from "../../../common/cluster-store";
import { ClusterStore } from "../../../common/cluster-store";
interface Props extends KubeObjectDetailsProps<DaemonSet> {
}
@@ -49,7 +49,7 @@ export class DaemonSetDetails extends React.Component<Props> {
const nodeSelector = daemonSet.getNodeSelectors();
const childPods = daemonSetStore.getChildPods(daemonSet);
const metrics = daemonSetStore.metrics;
const isMetricHidden = clusterStore.isMetricHidden(ResourceType.DaemonSet);
const isMetricHidden = ClusterStore.getInstance().isMetricHidden(ResourceType.DaemonSet);
return (
<div className="DaemonSetDetails">
@@ -20,7 +20,7 @@ import { PodDetailsList } from "../+workloads-pods/pod-details-list";
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
import { ResourceType } from "../cluster-settings/components/cluster-metrics-setting";
import { clusterStore } from "../../../common/cluster-store";
import { ClusterStore } from "../../../common/cluster-store";
interface Props extends KubeObjectDetailsProps<Deployment> {
}
@@ -49,7 +49,7 @@ export class DeploymentDetails extends React.Component<Props> {
const selectors = deployment.getSelectors();
const childPods = deploymentStore.getChildPods(deployment);
const metrics = deploymentStore.metrics;
const isMetricHidden = clusterStore.isMetricHidden(ResourceType.Deployment);
const isMetricHidden = ClusterStore.getInstance().isMetricHidden(ResourceType.Deployment);
return (
<div className="DeploymentDetails">
@@ -8,7 +8,7 @@ import { observer } from "mobx-react";
import { PieChart } from "../chart";
import { cssVar } from "../../utils";
import { ChartData, ChartDataSets } from "chart.js";
import { themeStore } from "../../theme.store";
import { ThemeStore } from "../../theme.store";
interface SimpleChartDataSets extends ChartDataSets {
backgroundColor?: string[];
@@ -41,7 +41,7 @@ export class OverviewWorkloadStatus extends React.Component<Props> {
labels: [] as string[],
datasets: [{
data: [1],
backgroundColor: [themeStore.activeTheme.colors.pieChartDefaultColor],
backgroundColor: [ThemeStore.getInstance().activeTheme.colors.pieChartDefaultColor],
label: "Empty"
}]
};
@@ -5,13 +5,13 @@ import { BarChart, cpuOptions, memoryOptions } from "../chart";
import { isMetricsEmpty, normalizeMetrics } from "../../api/endpoints/metrics.api";
import { NoMetrics } from "../resource-metrics/no-metrics";
import { IResourceMetricsValue, ResourceMetricsContext } from "../resource-metrics";
import { themeStore } from "../../theme.store";
import { ThemeStore } from "../../theme.store";
type IContext = IResourceMetricsValue<any, { metrics: IPodMetrics }>;
export const ContainerCharts = observer(() => {
const { params: { metrics }, tabId } = useContext<IContext>(ResourceMetricsContext);
const { chartCapacityColor } = themeStore.activeTheme.colors;
const { chartCapacityColor } = ThemeStore.getInstance().activeTheme.colors;
if (!metrics) return null;
if (isMetricsEmpty(metrics)) return <NoMetrics/>;
@@ -6,7 +6,7 @@ import { isMetricsEmpty, normalizeMetrics } from "../../api/endpoints/metrics.ap
import { NoMetrics } from "../resource-metrics/no-metrics";
import { IResourceMetricsValue, ResourceMetricsContext } from "../resource-metrics";
import { WorkloadKubeObject } from "../../api/workload-kube-object";
import { themeStore } from "../../theme.store";
import { ThemeStore } from "../../theme.store";
export const podMetricTabs = [
"CPU",
@@ -19,7 +19,7 @@ type IContext = IResourceMetricsValue<WorkloadKubeObject, { metrics: IPodMetrics
export const PodCharts = observer(() => {
const { params: { metrics }, tabId, object } = useContext<IContext>(ResourceMetricsContext);
const { chartCapacityColor } = themeStore.activeTheme.colors;
const { chartCapacityColor } = ThemeStore.getInstance().activeTheme.colors;
const id = object.getId();
if (!metrics) return null;
@@ -12,8 +12,8 @@ import { ResourceMetrics } from "../resource-metrics";
import { IMetrics } from "../../api/endpoints/metrics.api";
import { ContainerCharts } from "./container-charts";
import { ResourceType } from "../cluster-settings/components/cluster-metrics-setting";
import { clusterStore } from "../../../common/cluster-store";
import { LocaleDate } from "../locale-date";
import { ClusterStore } from "../../../common/cluster-store";
interface Props {
pod: Pod;
@@ -66,7 +66,7 @@ export class PodDetailsContainer extends React.Component<Props> {
"Memory",
"Filesystem",
];
const isMetricHidden = clusterStore.isMetricHidden(ResourceType.Container);
const isMetricHidden = ClusterStore.getInstance().isMetricHidden(ResourceType.Container);
return (
<div className="PodDetailsContainer">
@@ -23,7 +23,7 @@ import { PodCharts, podMetricTabs } from "./pod-charts";
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
import { ResourceType } from "../cluster-settings/components/cluster-metrics-setting";
import { clusterStore } from "../../../common/cluster-store";
import { ClusterStore } from "../../../common/cluster-store";
interface Props extends KubeObjectDetailsProps<Pod> {
}
@@ -68,7 +68,7 @@ export class PodDetails extends React.Component<Props> {
const nodeSelector = pod.getNodeSelectors();
const volumes = pod.getVolumes();
const metrics = podsStore.metrics;
const isMetricHidden = clusterStore.isMetricHidden(ResourceType.Pod);
const isMetricHidden = ClusterStore.getInstance().isMetricHidden(ResourceType.Pod);
return (
<div className="PodDetails">
@@ -18,7 +18,7 @@ import { PodDetailsList } from "../+workloads-pods/pod-details-list";
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
import { ResourceType } from "../cluster-settings/components/cluster-metrics-setting";
import { clusterStore } from "../../../common/cluster-store";
import { ClusterStore } from "../../../common/cluster-store";
interface Props extends KubeObjectDetailsProps<ReplicaSet> {
}
@@ -49,7 +49,7 @@ export class ReplicaSetDetails extends React.Component<Props> {
const nodeSelector = replicaSet.getNodeSelectors();
const images = replicaSet.getImages();
const childPods = replicaSetStore.getChildPods(replicaSet);
const isMetricHidden = clusterStore.isMetricHidden(ResourceType.ReplicaSet);
const isMetricHidden = ClusterStore.getInstance().isMetricHidden(ResourceType.ReplicaSet);
return (
<div className="ReplicaSetDetails">
@@ -19,7 +19,7 @@ import { PodDetailsList } from "../+workloads-pods/pod-details-list";
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
import { ResourceType } from "../cluster-settings/components/cluster-metrics-setting";
import { clusterStore } from "../../../common/cluster-store";
import { ClusterStore } from "../../../common/cluster-store";
interface Props extends KubeObjectDetailsProps<StatefulSet> {
}
@@ -48,7 +48,7 @@ export class StatefulSetDetails extends React.Component<Props> {
const nodeSelector = statefulSet.getNodeSelectors();
const childPods = statefulSetStore.getChildPods(statefulSet);
const metrics = statefulSetStore.metrics;
const isMetricHidden = clusterStore.isMetricHidden(ResourceType.StatefulSet);
const isMetricHidden = ClusterStore.getInstance().isMetricHidden(ResourceType.StatefulSet);
return (
<div className="StatefulSetDetails">
+2 -2
View File
@@ -34,7 +34,7 @@ import { getHostedCluster, getHostedClusterId } from "../../common/cluster-store
import logger from "../../main/logger";
import { webFrame } from "electron";
import { clusterPageRegistry, getExtensionPageUrl } from "../../extensions/registries/page-registry";
import { extensionLoader } from "../../extensions/extension-loader";
import { ExtensionLoader } from "../../extensions/extension-loader";
import { appEventBus } from "../../common/event-bus";
import { requestMain } from "../../common/ipc";
import whatInput from "what-input";
@@ -62,7 +62,7 @@ export class App extends React.Component {
await requestMain(clusterSetFrameIdHandler, clusterId);
await getHostedCluster().whenReady; // cluster.activate() is done at this point
extensionLoader.loadOnClusterRenderer();
ExtensionLoader.getInstance().loadOnClusterRenderer();
setTimeout(() => {
appEventBus.emit({
name: "cluster",
+2 -2
View File
@@ -7,7 +7,7 @@ import { ChartData, ChartOptions, ChartPoint, ChartTooltipItem, Scriptable } fro
import { Chart, ChartKind, ChartProps } from "./chart";
import { bytesToUnits, cssNames } from "../../utils";
import { ZebraStripes } from "./zebra-stripes.plugin";
import { themeStore } from "../../theme.store";
import { ThemeStore } from "../../theme.store";
import { NoMetrics } from "../resource-metrics/no-metrics";
interface Props extends ChartProps {
@@ -26,7 +26,7 @@ export class BarChart extends React.Component<Props> {
render() {
const { name, data, className, timeLabelStep, plugins, options: customOptions, ...settings } = this.props;
const { textColorPrimary, borderFaintColor, chartStripesColor } = themeStore.activeTheme.colors;
const { textColorPrimary, borderFaintColor, chartStripesColor } = ThemeStore.getInstance().activeTheme.colors;
const getBarColor: Scriptable<string> = ({ dataset }) => {
const color = dataset.borderColor;
+2 -2
View File
@@ -4,7 +4,7 @@ import { observer } from "mobx-react";
import ChartJS, { ChartOptions } from "chart.js";
import { Chart, ChartProps } from "./chart";
import { cssNames } from "../../utils";
import { themeStore } from "../../theme.store";
import { ThemeStore } from "../../theme.store";
interface Props extends ChartProps {
}
@@ -13,7 +13,7 @@ interface Props extends ChartProps {
export class PieChart extends React.Component<Props> {
render() {
const { data, className, options, ...chartProps } = this.props;
const { contentColor } = themeStore.activeTheme.colors;
const { contentColor } = ThemeStore.getInstance().activeTheme.colors;
const cutouts = [88, 76, 63];
const opts: ChartOptions = this.props.showChart === false ? {} : {
maintainAspectRatio: false,
@@ -7,7 +7,6 @@ jest.mock("../../../extensions/registries");
import { statusBarRegistry } from "../../../extensions/registries";
describe("<BottomBar />", () => {
it("renders w/o errors", () => {
const { container } = render(<BottomBar />);
@@ -10,7 +10,7 @@ import { Preferences, preferencesRoute } from "../+preferences";
import { AddCluster, addClusterRoute } from "../+add-cluster";
import { ClusterView } from "./cluster-view";
import { clusterViewRoute } from "./cluster-view.route";
import { clusterStore } from "../../../common/cluster-store";
import { ClusterStore } from "../../../common/cluster-store";
import { hasLoadedView, initView, lensViews, refreshViews } from "./lens-views";
import { globalPageRegistry } from "../../../extensions/registries/page-registry";
import { Extensions, extensionsRoute } from "../+extensions";
@@ -21,7 +21,7 @@ import { EntitySettings, entitySettingsRoute } from "../+entity-settings";
@observer
export class ClusterManager extends React.Component {
componentDidMount() {
const getMatchedCluster = () => clusterStore.getById(getMatchedClusterId());
const getMatchedCluster = () => ClusterStore.getInstance().getById(getMatchedClusterId());
disposeOnUnmount(this, [
reaction(getMatchedClusterId, initView, {
@@ -59,9 +59,12 @@ export class ClusterManager extends React.Component {
<Route component={AddCluster} {...addClusterRoute} />
<Route component={ClusterView} {...clusterViewRoute} />
<Route component={EntitySettings} {...entitySettingsRoute} />
{globalPageRegistry.getItems().map(({ url, components: { Page } }) => {
return <Route key={url} path={url} component={Page}/>;
})}
{
globalPageRegistry.getItems()
.map(({ url, components: { Page } }) => (
<Route key={url} path={url} component={Page} />
))
}
<Redirect exact to={this.startUrl}/>
</Switch>
</main>
@@ -10,7 +10,7 @@ import { Icon } from "../icon";
import { Button } from "../button";
import { cssNames, IClassName } from "../../utils";
import { Cluster } from "../../../main/cluster";
import { ClusterId, clusterStore } from "../../../common/cluster-store";
import { ClusterId, ClusterStore } from "../../../common/cluster-store";
import { CubeSpinner } from "../spinner";
import { clusterActivateHandler } from "../../../common/cluster-ipc";
@@ -25,7 +25,7 @@ export class ClusterStatus extends React.Component<Props> {
@observable isReconnecting = false;
get cluster(): Cluster {
return clusterStore.getById(this.props.clusterId);
return ClusterStore.getInstance().getById(this.props.clusterId);
}
@computed get hasErrors(): boolean {
@@ -7,9 +7,9 @@ import { IClusterViewRouteParams } from "./cluster-view.route";
import { ClusterStatus } from "./cluster-status";
import { hasLoadedView } from "./lens-views";
import { Cluster } from "../../../main/cluster";
import { clusterStore } from "../../../common/cluster-store";
import { navigate } from "../../navigation";
import { catalogURL } from "../+catalog";
import { ClusterStore } from "../../../common/cluster-store";
interface Props extends RouteComponentProps<IClusterViewRouteParams> {
}
@@ -21,12 +21,12 @@ export class ClusterView extends React.Component<Props> {
}
get cluster(): Cluster {
return clusterStore.getById(this.clusterId);
return ClusterStore.getInstance().getById(this.clusterId);
}
async componentDidMount() {
disposeOnUnmount(this, [
reaction(() => this.clusterId, clusterId => clusterStore.setActive(clusterId), {
reaction(() => this.clusterId, clusterId => ClusterStore.getInstance().setActive(clusterId), {
fireImmediately: true,
}),
reaction(() => this.cluster.online, (online) => {
@@ -1,5 +1,5 @@
import { observable, when } from "mobx";
import { ClusterId, clusterStore, getClusterFrameUrl } from "../../../common/cluster-store";
import { ClusterId, ClusterStore, getClusterFrameUrl } from "../../../common/cluster-store";
import { getMatchedClusterId } from "../../navigation";
import logger from "../../../main/logger";
@@ -19,7 +19,7 @@ export async function initView(clusterId: ClusterId) {
if (!clusterId || lensViews.has(clusterId)) {
return;
}
const cluster = clusterStore.getById(clusterId);
const cluster = ClusterStore.getInstance().getById(clusterId);
if (!cluster) {
return;
@@ -44,13 +44,9 @@ export async function initView(clusterId: ClusterId) {
export async function autoCleanOnRemove(clusterId: ClusterId, iframe: HTMLIFrameElement) {
await when(() => {
const cluster = clusterStore.getById(clusterId);
const cluster = ClusterStore.getInstance().getById(clusterId);
if (!cluster) return true;
const view = lensViews.get(clusterId);
return cluster.disconnected && view?.isLoaded;
return !cluster || (cluster.disconnected && lensViews.get(clusterId)?.isLoaded);
});
logger.info(`[LENS-VIEW]: remove dashboard, clusterId=${clusterId}`);
lensViews.delete(clusterId);
@@ -64,7 +60,7 @@ export async function autoCleanOnRemove(clusterId: ClusterId, iframe: HTMLIFrame
}
export function refreshViews() {
const cluster = clusterStore.getById(getMatchedClusterId());
const cluster = ClusterStore.getInstance().getById(getMatchedClusterId());
lensViews.forEach(({ clusterId, view, isLoaded }) => {
const isCurrent = clusterId === cluster?.id;
@@ -1,7 +1,7 @@
import { navigate } from "../../navigation";
import { commandRegistry } from "../../../extensions/registries/command-registry";
import { clusterStore } from "../../../common/cluster-store";
import { entitySettingsURL } from "../+entity-settings";
import { ClusterStore } from "../../../common/cluster-store";
commandRegistry.add({
id: "cluster.viewCurrentClusterSettings",
@@ -9,7 +9,7 @@ commandRegistry.add({
scope: "global",
action: () => navigate(entitySettingsURL({
params: {
entityId: clusterStore.active.id
entityId: ClusterStore.getInstance().active.id
}
})),
isActive: (context) => !!context.entity
@@ -1,5 +1,5 @@
import React from "react";
import { clusterStore } from "../../../common/cluster-store";
import { ClusterStore } from "../../../common/cluster-store";
import { ClusterProxySetting } from "./components/cluster-proxy-setting";
import { ClusterNameSetting } from "./components/cluster-name-setting";
import { ClusterHomeDirSetting } from "./components/cluster-home-dir-setting";
@@ -13,7 +13,7 @@ import { CatalogEntity } from "../../api/catalog-entity";
function getClusterForEntity(entity: CatalogEntity) {
const cluster = clusterStore.getById(entity.metadata.uid);
const cluster = ClusterStore.getInstance().getById(entity.metadata.uid);
if (!cluster?.enabled) {
return null;
@@ -1,6 +1,6 @@
import React from "react";
import { observer } from "mobx-react";
import { clusterStore } from "../../../../common/cluster-store";
import { ClusterStore } from "../../../../common/cluster-store";
import { Cluster } from "../../../../main/cluster";
import { autobind } from "../../../utils";
import { Button } from "../../button";
@@ -21,7 +21,7 @@ export class RemoveClusterButton extends React.Component<Props> {
labelOk: "Yes",
labelCancel: "No",
ok: async () => {
await clusterStore.removeById(cluster.id);
await ClusterStore.getInstance().removeById(cluster.id);
}
});
}
@@ -4,7 +4,7 @@ import { computed, observable, toJS } from "mobx";
import { observer } from "mobx-react";
import React from "react";
import { commandRegistry } from "../../../extensions/registries/command-registry";
import { clusterStore } from "../../../common/cluster-store";
import { ClusterStore } from "../../../common/cluster-store";
import { CommandOverlay } from "./command-container";
import { broadcastMessage } from "../../../common/ipc";
import { navigate } from "../../navigation";
@@ -20,7 +20,7 @@ export class CommandDialog extends React.Component {
};
return commandRegistry.getItems().filter((command) => {
if (command.scope === "entity" && !clusterStore.active) {
if (command.scope === "entity" && !ClusterStore.getInstance().active) {
return false;
}
@@ -16,6 +16,20 @@ const getComponent = () => (
/>
);
Object.defineProperty(window, "matchMedia", {
writable: true,
value: jest.fn().mockImplementation(query => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // Deprecated
removeListener: jest.fn(), // Deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
const renderTabs = () => render(getComponent());
const getTabKinds = () => dockStore.tabs.map(tab => tab.kind);
@@ -7,6 +7,8 @@ import { Pod } from "../../../api/endpoints";
import { LogResourceSelector } from "../log-resource-selector";
import { LogTabData } from "../log-tab.store";
import { dockerPod, deploymentPod1 } from "./pod.mock";
import { ThemeStore } from "../../../theme.store";
import { UserStore } from "../../../../common/user-store";
const getComponent = (tabData: LogTabData) => {
return (
@@ -41,6 +43,16 @@ const getFewPodsTabData = (): LogTabData => {
};
describe("<LogResourceSelector />", () => {
beforeEach(() => {
UserStore.getInstanceOrCreate();
ThemeStore.getInstanceOrCreate();
});
afterEach(() => {
UserStore.resetInstance();
ThemeStore.resetInstance();
});
it("renders w/o errors", () => {
const tabData = getOnePodTabData();
const { container } = render(getComponent(tabData));
+2 -2
View File
@@ -10,7 +10,7 @@ import moment from "moment-timezone";
import { Align, ListOnScrollProps } from "react-window";
import { SearchStore, searchStore } from "../../../common/search-store";
import { userStore } from "../../../common/user-store";
import { UserStore } from "../../../common/user-store";
import { cssNames } from "../../utils";
import { Button } from "../button";
import { Icon } from "../icon";
@@ -81,7 +81,7 @@ export class LogList extends React.Component<Props> {
@computed
get logs() {
const showTimestamps = logTabStore.getData(this.props.id).showTimestamps;
const { preferences } = userStore;
const { preferences } = UserStore.getInstance();
if (!showTimestamps) {
return logStore.logsWithoutTimestamps;
@@ -7,7 +7,7 @@ import { cssNames } from "../../utils";
import { IDockTab } from "./dock.store";
import { Terminal } from "./terminal";
import { terminalStore } from "./terminal.store";
import { themeStore } from "../../theme.store";
import { ThemeStore } from "../../theme.store";
interface Props {
className?: string;
@@ -38,7 +38,7 @@ export class TerminalWindow extends React.Component<Props> {
return (
<div
className={cssNames("TerminalWindow", className, themeStore.activeTheme.type)}
className={cssNames("TerminalWindow", className, ThemeStore.getInstance().activeTheme.type)}
ref={e => this.elem = e}
/>
);
+6 -11
View File
@@ -4,9 +4,10 @@ import { Terminal as XTerm } from "xterm";
import { FitAddon } from "xterm-addon-fit";
import { dockStore, TabId } from "./dock.store";
import { TerminalApi } from "../../api/terminal-api";
import { themeStore } from "../../theme.store";
import { ThemeStore } from "../../theme.store";
import { autobind } from "../../utils";
import { isMac } from "../../../common/vars";
import { camelCase } from "lodash";
export class Terminal {
static spawningPool: HTMLElement;
@@ -40,16 +41,10 @@ export class Terminal {
// Replacing keys stored in styles to format accepted by terminal
// E.g. terminalBrightBlack -> brightBlack
const colorPrefix = "terminal";
const terminalColors = Object.entries(colors)
const terminalColorEntries = Object.entries(colors)
.filter(([name]) => name.startsWith(colorPrefix))
.reduce<any>((colors, [name, color]) => {
const colorName = name.split("").slice(colorPrefix.length);
colorName[0] = colorName[0].toLowerCase();
colors[colorName.join("")] = color;
return colors;
}, {});
.map(([name, color]) => [camelCase(name.slice(colorPrefix.length)), color]);
const terminalColors = Object.fromEntries(terminalColorEntries);
this.xterm.setOption("theme", terminalColors);
}
@@ -109,7 +104,7 @@ export class Terminal {
window.addEventListener("resize", this.onResize);
this.disposers.push(
reaction(() => toJS(themeStore.activeTheme.colors), this.setTheme, {
reaction(() => toJS(ThemeStore.getInstance().activeTheme.colors), this.setTheme, {
fireImmediately: true
}),
dockStore.onResize(this.onResize),
@@ -10,7 +10,7 @@ import { Menu, MenuItem } from "../menu";
import { Icon } from "../icon";
import { observable } from "mobx";
import { navigate } from "../../navigation";
import { hotbarStore } from "../../../common/hotbar-store";
import { HotbarStore } from "../../../common/hotbar-store";
import { ConfirmDialog } from "../confirm-dialog";
interface Props extends DOMAttributes<HTMLElement> {
@@ -60,7 +60,7 @@ export class HotbarIcon extends React.Component<Props> {
}
removeFromHotbar(item: CatalogEntity) {
const hotbar = hotbarStore.getByName("default"); // FIXME
const hotbar = HotbarStore.getInstance().getByName("default"); // FIXME
if (!hotbar) {
return;
@@ -5,7 +5,7 @@ import { observer } from "mobx-react";
import { HotbarIcon } from "./hotbar-icon";
import { cssNames, IClassName } from "../../utils";
import { catalogEntityRegistry } from "../../api/catalog-entity-registry";
import { hotbarStore } from "../../../common/hotbar-store";
import { HotbarStore } from "../../../common/hotbar-store";
import { catalogEntityRunContext } from "../../api/catalog-entity";
interface Props {
@@ -16,7 +16,7 @@ interface Props {
export class HotbarMenu extends React.Component<Props> {
get hotbarItems() {
const hotbar = hotbarStore.getByName("default"); // FIXME
const hotbar = HotbarStore.getInstance().getByName("default"); // FIXME
if (!hotbar) {
return [];
@@ -47,4 +47,3 @@ export class HotbarMenu extends React.Component<Props> {
);
}
}
@@ -16,11 +16,11 @@ import { Filter, FilterType, pageFilters } from "./page-filters.store";
import { PageFiltersList } from "./page-filters-list";
import { PageFiltersSelect } from "./page-filters-select";
import { NamespaceSelectFilter } from "../+namespaces/namespace-select-filter";
import { themeStore } from "../../theme.store";
import { ThemeStore } from "../../theme.store";
import { MenuActions } from "../menu/menu-actions";
import { MenuItem } from "../menu";
import { Checkbox } from "../checkbox";
import { userStore } from "../../../common/user-store";
import { UserStore } from "../../../common/user-store";
import { namespaceStore } from "../+namespaces/namespace.store";
// todo: refactor, split to small re-usable components
@@ -440,7 +440,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
const { removeItemsDialog, items } = this;
const { selectedItems } = store;
const selectedItemId = detailsItem && detailsItem.getId();
const classNames = cssNames(className, "box", "grow", themeStore.activeTheme.type);
const classNames = cssNames(className, "box", "grow", ThemeStore.getInstance().activeTheme.type);
return (
<div className="items box grow flex column">
@@ -469,7 +469,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
}
@computed get hiddenColumns() {
return userStore.getHiddenTableColumns(this.props.tableId);
return UserStore.getInstance().getHiddenTableColumns(this.props.tableId);
}
isHiddenColumn({ id: columnId, showWithColumn }: TableCellProps): boolean {
@@ -491,7 +491,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
hiddenColumns.delete(columnId);
}
userStore.setHiddenTableColumns(this.props.tableId, hiddenColumns);
UserStore.getInstance().setHiddenTableColumns(this.props.tableId, hiddenColumns);
}
renderColumnVisibilityMenu() {
@@ -6,6 +6,7 @@ import "@testing-library/jest-dom/extend-expect";
import { MainLayoutHeader } from "../main-layout-header";
import { Cluster } from "../../../../main/cluster";
import { ClusterStore } from "../../../../common/cluster-store";
const cluster: Cluster = new Cluster({
id: "foo",
@@ -14,6 +15,14 @@ const cluster: Cluster = new Cluster({
});
describe("<MainLayoutHeader />", () => {
beforeEach(() => {
ClusterStore.getInstanceOrCreate();
});
afterEach(() => {
ClusterStore.resetInstance();
});
it("renders w/o errors", () => {
const { container } = render(<MainLayoutHeader cluster={cluster} />);
@@ -1,7 +1,7 @@
import React from "react";
import { observer } from "mobx-react";
import moment from "moment-timezone";
import { userStore } from "../../../common/user-store";
import { UserStore } from "../../../common/user-store";
interface Props {
date: string
@@ -10,7 +10,7 @@ interface Props {
@observer
export class LocaleDate extends React.Component<Props> {
render() {
const { preferences } = userStore;
const { preferences } = UserStore.getInstance();
const { date } = this.props;
return <>{moment.tz(date, preferences.localeTimezone).format()}</>;
+2 -2
View File
@@ -8,7 +8,7 @@ import { observer } from "mobx-react";
import { autobind, cssNames } from "../../utils";
import ReactSelect, { ActionMeta, components, Props as ReactSelectProps, Styles } from "react-select";
import Creatable, { CreatableProps } from "react-select/creatable";
import { themeStore } from "../../theme.store";
import { ThemeStore } from "../../theme.store";
const { Menu } = components;
@@ -40,7 +40,7 @@ export class Select extends React.Component<SelectProps> {
};
@computed get theme() {
return this.props.themeName || themeStore.activeTheme.type;
return this.props.themeName || ThemeStore.getInstance().activeTheme.type;
}
private styles: Styles = {
+2 -2
View File
@@ -5,7 +5,7 @@ import { Notifications, notificationsStore } from "../components/notifications";
import { Button } from "../components/button";
import { isMac } from "../../common/vars";
import { invalidKubeconfigHandler } from "./invalid-kubeconfig-handler";
import { clusterStore } from "../../common/cluster-store";
import { ClusterStore } from "../../common/cluster-store";
import { navigate } from "../navigation";
import { entitySettingsURL } from "../components/+entity-settings";
@@ -76,7 +76,7 @@ function ListNamespacesForbiddenHandler(event: IpcRendererEvent, ...[clusterId]:
(
<div className="flex column gaps">
<b>Add Accessible Namespaces</b>
<p>Cluster <b>{clusterStore.active.name}</b> does not have permissions to list namespaces. Please add the namespaces you have access to.</p>
<p>Cluster <b>{ClusterStore.getInstance().active.name}</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">
<Button active outlined label="Go to Accessible Namespaces Settings" onClick={()=> {
navigate(entitySettingsURL({ params: { entityId: clusterId }, fragment: "accessible-namespaces" }));
@@ -1,6 +1,6 @@
import React from "react";
import { ipcRenderer, IpcRendererEvent, shell } from "electron";
import { clusterStore } from "../../common/cluster-store";
import { ClusterStore } from "../../common/cluster-store";
import { InvalidKubeConfigArgs, InvalidKubeconfigChannel } from "../../common/ipc/invalid-kubeconfig";
import { Notifications, notificationsStore } from "../components/notifications";
import { Button } from "../components/button";
@@ -10,13 +10,13 @@ export const invalidKubeconfigHandler = {
channel: InvalidKubeconfigChannel,
listener: InvalidKubeconfigListener,
verifier: (args: [unknown]): args is InvalidKubeConfigArgs => {
return args.length === 1 && typeof args[0] === "string" && !!clusterStore.getById(args[0]);
return args.length === 1 && typeof args[0] === "string" && !!ClusterStore.getInstance().getById(args[0]);
},
};
function InvalidKubeconfigListener(event: IpcRendererEvent, ...[clusterId]: InvalidKubeConfigArgs): void {
const notificationId = `invalid-kubeconfig:${clusterId}`;
const cluster = clusterStore.getById(clusterId);
const cluster = ClusterStore.getInstance().getById(clusterId);
const contextName = cluster.name !== cluster.contextName ? `(context: ${cluster.contextName})` : "";
Notifications.error(
@@ -29,7 +29,7 @@ function InvalidKubeconfigListener(event: IpcRendererEvent, ...[clusterId]: Inva
<p>Do you want to remove the cluster now?</p>
<div className="flex gaps row align-left box grow">
<Button active outlined label="Remove" onClick={()=> {
clusterStore.removeById(clusterId);
ClusterStore.getInstance().removeById(clusterId);
notificationsStore.remove(notificationId);
}} />
<Button active outlined label="Cancel" onClick={() => notificationsStore.remove(notificationId)} />
@@ -42,5 +42,3 @@ function InvalidKubeconfigListener(event: IpcRendererEvent, ...[clusterId]: Inva
}
);
}
+8 -11
View File
@@ -2,14 +2,14 @@ import "../common/system-ca";
import React from "react";
import { Route, Router, Switch } from "react-router";
import { observer } from "mobx-react";
import { userStore } from "../common/user-store";
import { UserStore } from "../common/user-store";
import { history } from "./navigation";
import { ClusterManager } from "./components/cluster-manager";
import { ErrorBoundary } from "./components/error-boundary";
import { WhatsNew, whatsNewRoute } from "./components/+whats-new";
import { Notifications } from "./components/notifications";
import { ConfirmDialog } from "./components/confirm-dialog";
import { extensionLoader } from "../extensions/extension-loader";
import { ExtensionLoader } from "../extensions/extension-loader";
import { broadcastMessage } from "../common/ipc";
import { CommandContainer } from "./components/command-palette/command-container";
import { LensProtocolRouterRenderer, bindProtocolAddRouteHandlers } from "./protocol-handler";
@@ -24,15 +24,12 @@ import { reaction } from "mobx";
export class LensApp extends React.Component {
static async init() {
catalogEntityRegistry.init();
extensionLoader.loadOnClusterManagerRenderer();
LensProtocolRouterRenderer.getInstance<LensProtocolRouterRenderer>().init();
ExtensionLoader.getInstance().loadOnClusterManagerRenderer();
LensProtocolRouterRenderer.getInstanceOrCreate().init();
bindProtocolAddRouteHandlers();
window.addEventListener("offline", () => {
broadcastMessage("network:offline");
});
window.addEventListener("online", () => {
broadcastMessage("network:online");
});
window.addEventListener("offline", () => broadcastMessage("network:offline"));
window.addEventListener("online", () => broadcastMessage("network:online"));
registerIpcHandlers();
ipcRenderer.send(IpcRendererNavigationEvents.LOADED);
@@ -55,7 +52,7 @@ export class LensApp extends React.Component {
<Router history={history}>
<ErrorBoundary>
<Switch>
{userStore.isNewVersion && <Route component={WhatsNew}/>}
{UserStore.getInstance().isNewVersion && <Route component={WhatsNew}/>}
<Route component={WhatsNew} {...whatsNewRoute}/>
<Route component={ClusterManager}/>
</Switch>
@@ -5,13 +5,13 @@ import { preferencesURL } from "../components/+preferences";
import { clusterViewURL } from "../components/cluster-manager/cluster-view.route";
import { LensProtocolRouterRenderer } from "./router";
import { navigate } from "../navigation/helpers";
import { clusterStore } from "../../common/cluster-store";
import { entitySettingsURL } from "../components/+entity-settings";
import { catalogEntityRegistry } from "../api/catalog-entity-registry";
import { ClusterStore } from "../../common/cluster-store";
export function bindProtocolAddRouteHandlers() {
LensProtocolRouterRenderer
.getInstance<LensProtocolRouterRenderer>()
.getInstance()
.addInternalHandler("/preferences", ({ search: { highlight }}) => {
navigate(preferencesURL({ fragment: highlight }));
})
@@ -36,9 +36,9 @@ export function bindProtocolAddRouteHandlers() {
.addInternalHandler("/extensions", () => {
navigate(extensionsURL());
})
// Handlers below are deprecated and only kept for backward compat purposes
// Handlers below are deprecated and only kept for backward compact purposes
.addInternalHandler("/cluster/:clusterId", ({ pathname: { clusterId } }) => {
const cluster = clusterStore.getById(clusterId);
const cluster = ClusterStore.getInstance().getById(clusterId);
if (cluster) {
navigate(clusterViewURL({ params: { clusterId } }));
@@ -47,7 +47,7 @@ export function bindProtocolAddRouteHandlers() {
}
})
.addInternalHandler("/cluster/:clusterId/settings", ({ pathname: { clusterId } }) => {
const cluster = clusterStore.getById(clusterId);
const cluster = ClusterStore.getInstance().getById(clusterId);
if (cluster) {
navigate(entitySettingsURL({ params: { entityId: clusterId } }));
+35 -26
View File
@@ -1,6 +1,6 @@
import { computed, observable, reaction } from "mobx";
import { autobind } from "./utils";
import { userStore } from "../common/user-store";
import { autobind, Singleton } from "./utils";
import { UserStore } from "../common/user-store";
import logger from "../main/logger";
export type ThemeId = string;
@@ -20,21 +20,29 @@ export interface Theme {
}
@autobind()
export class ThemeStore {
export class ThemeStore extends Singleton {
protected styles: HTMLStyleElement;
// bundled themes from `themes/${themeId}.json`
@observable themes: Theme[] = [
{ id: "lens-dark", type: ThemeType.DARK },
{ id: "lens-light", type: ThemeType.LIGHT },
];
private allThemes = observable.map<string, Theme>([
["lens-dark", { id: "lens-dark", type: ThemeType.DARK }],
["lens-light", { id: "lens-light", type: ThemeType.LIGHT }],
]);
@computed get activeThemeId() {
return userStore.preferences.colorTheme;
@computed get themeIds(): string[] {
return Array.from(this.allThemes.keys());
}
@computed get themes(): Theme[] {
return Array.from(this.allThemes.values());
}
@computed get activeThemeId(): string {
return UserStore.getInstance().preferences.colorTheme;
}
@computed get activeTheme(): Theme {
const activeTheme = this.themes.find(theme => theme.id === this.activeThemeId) || this.themes[0];
const activeTheme = this.allThemes.get(this.activeThemeId) ?? this.allThemes.get("lens-dark");
return {
colors: {},
@@ -43,14 +51,15 @@ export class ThemeStore {
}
constructor() {
super();
// auto-apply active theme
reaction(() => this.activeThemeId, async themeId => {
try {
await this.loadTheme(themeId);
this.applyTheme();
this.applyTheme(await this.loadTheme(themeId));
} catch (err) {
logger.error(err);
userStore.resetTheme();
UserStore.getInstance().resetTheme();
}
}, {
fireImmediately: true,
@@ -59,26 +68,28 @@ export class ThemeStore {
async init() {
// preload all themes
await Promise.all(
this.themes.map(theme => this.loadTheme(theme.id))
);
await Promise.all(this.themeIds.map(this.loadTheme));
}
getThemeById(themeId: ThemeId): Theme {
return this.themes.find(theme => theme.id === themeId);
return this.allThemes.get(themeId);
}
@autobind()
protected async loadTheme(themeId: ThemeId): Promise<Theme> {
try {
// todo: figure out why await import() doesn't work
const theme = require( // eslint-disable-line @typescript-eslint/no-var-requires
/* webpackChunkName: "themes/[name]" */
`./themes/${themeId}.json`
);
const existingTheme = this.getThemeById(themeId);
if (existingTheme) {
Object.assign(existingTheme, theme); // merge
const theme = await import(
/* webpackChunkName: "themes/[name]" */
`./themes/${themeId}.json`
);
existingTheme.author = theme.author;
existingTheme.colors = theme.colors;
existingTheme.description = theme.description;
existingTheme.name = theme.name;
}
return existingTheme;
@@ -87,7 +98,7 @@ export class ThemeStore {
}
}
protected applyTheme(theme = this.activeTheme) {
protected applyTheme(theme: Theme) {
if (!this.styles) {
this.styles = document.createElement("style");
this.styles.id = "lens-theme";
@@ -104,5 +115,3 @@ export class ThemeStore {
body.classList.toggle("theme-light", theme.type === ThemeType.LIGHT);
}
}
export const themeStore = new ThemeStore();
@@ -1,8 +1,17 @@
import { reaction } from "mobx";
import { StorageAdapter, StorageHelper } from "../storageHelper";
import { delay } from "../../../common/utils/delay";
import { ClusterStore } from "../../../common/cluster-store";
describe("renderer/utils/StorageHelper", () => {
beforeEach(() => {
ClusterStore.getInstanceOrCreate();
});
afterEach(() => {
ClusterStore.resetInstance();
});
describe("window.localStorage might be used as StorageAdapter", () => {
type StorageModel = string;
+2 -2
View File
@@ -7,7 +7,7 @@ import { app, remote } from "electron";
import { observable, reaction, when } from "mobx";
import fse from "fs-extra";
import { StorageHelper } from "./storageHelper";
import { clusterStore, getHostedClusterId } from "../../common/cluster-store";
import { ClusterStore, getHostedClusterId } from "../../common/cluster-store";
import logger from "../../main/logger";
let initialized = false;
@@ -33,7 +33,7 @@ export function createStorage<T>(key: string, defaultValue: T, observableOptions
// remove json-file when cluster deleted
if (clusterId !== undefined) {
when(() => clusterStore.removedClusters.has(clusterId)).then(removeFile);
when(() => ClusterStore.getInstance().removedClusters.has(clusterId)).then(removeFile);
}
}