mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
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:
1 parent
b2a570ce28
commit
9563ead2e6
114 files changed
+924
-736
No files matched your search
+23
-13
@@ -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'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}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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()}</>;
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user