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:
parent
b2a570ce28
commit
9563ead2e6
@ -79,6 +79,8 @@ module.exports = {
|
|||||||
sourceType: "module",
|
sourceType: "module",
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
|
"no-invalid-this": "off",
|
||||||
|
"@typescript-eslint/no-invalid-this": ["error"],
|
||||||
"@typescript-eslint/explicit-function-return-type": "off",
|
"@typescript-eslint/explicit-function-return-type": "off",
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||||
@ -137,6 +139,8 @@ module.exports = {
|
|||||||
jsx: true,
|
jsx: true,
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
|
"no-invalid-this": "off",
|
||||||
|
"@typescript-eslint/no-invalid-this": ["error"],
|
||||||
"@typescript-eslint/explicit-function-return-type": "off",
|
"@typescript-eslint/explicit-function-return-type": "off",
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
"@typescript-eslint/interface-name-prefix": "off",
|
"@typescript-eslint/interface-name-prefix": "off",
|
||||||
|
|||||||
@ -30,7 +30,7 @@ Each guide or code sample includes the following:
|
|||||||
| Sample | APIs |
|
| Sample | APIs |
|
||||||
| ----- | ----- |
|
| ----- | ----- |
|
||||||
[hello-world](https://github.com/lensapp/lens-extension-samples/tree/master/helloworld-sample) | LensMainExtension <br> LensRendererExtension <br> Component.Icon <br> Component.IconProps |
|
[hello-world](https://github.com/lensapp/lens-extension-samples/tree/master/helloworld-sample) | LensMainExtension <br> LensRendererExtension <br> Component.Icon <br> Component.IconProps |
|
||||||
[minikube](https://github.com/lensapp/lens-extension-samples/tree/master/minikube-sample) | LensMainExtension <br> Store.clusterStore <br> Store.workspaceStore |
|
[minikube](https://github.com/lensapp/lens-extension-samples/tree/master/minikube-sample) | LensMainExtension <br> Store.ClusterStore <br> Store.workspaceStore |
|
||||||
[styling-css-modules-sample](https://github.com/lensapp/lens-extension-samples/tree/master/styling-css-modules-sample) | LensMainExtension <br> LensRendererExtension <br> Component.Icon <br> Component.IconProps |
|
[styling-css-modules-sample](https://github.com/lensapp/lens-extension-samples/tree/master/styling-css-modules-sample) | LensMainExtension <br> LensRendererExtension <br> Component.Icon <br> Component.IconProps |
|
||||||
[styling-emotion-sample](https://github.com/lensapp/lens-extension-samples/tree/master/styling-emotion-sample) | LensMainExtension <br> LensRendererExtension <br> Component.Icon <br> Component.IconProps |
|
[styling-emotion-sample](https://github.com/lensapp/lens-extension-samples/tree/master/styling-emotion-sample) | LensMainExtension <br> LensRendererExtension <br> Component.Icon <br> Component.IconProps |
|
||||||
[styling-sass-sample](https://github.com/lensapp/lens-extension-samples/tree/master/styling-sass-sample) | LensMainExtension <br> LensRendererExtension <br> Component.Icon <br> Component.IconProps |
|
[styling-sass-sample](https://github.com/lensapp/lens-extension-samples/tree/master/styling-sass-sample) | LensMainExtension <br> LensRendererExtension <br> Component.Icon <br> Component.IconProps |
|
||||||
|
|||||||
@ -45,8 +45,6 @@ It accesses some Lens state data, and it periodically logs the name of the clust
|
|||||||
```typescript
|
```typescript
|
||||||
import { LensMainExtension, Store } from "@k8slens/extensions";
|
import { LensMainExtension, Store } from "@k8slens/extensions";
|
||||||
|
|
||||||
const clusterStore = Store.clusterStore
|
|
||||||
|
|
||||||
export default class ActiveClusterExtensionMain extends LensMainExtension {
|
export default class ActiveClusterExtensionMain extends LensMainExtension {
|
||||||
|
|
||||||
timer: NodeJS.Timeout
|
timer: NodeJS.Timeout
|
||||||
@ -54,11 +52,11 @@ export default class ActiveClusterExtensionMain extends LensMainExtension {
|
|||||||
onActivate() {
|
onActivate() {
|
||||||
console.log("Cluster logger activated");
|
console.log("Cluster logger activated");
|
||||||
this.timer = setInterval(() => {
|
this.timer = setInterval(() => {
|
||||||
if (!clusterStore.active) {
|
if (!Store.ClusterStore.getInstance().active) {
|
||||||
console.log("No active cluster");
|
console.log("No active cluster");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log("active cluster is", clusterStore.active.contextName)
|
console.log("active cluster is", Store.ClusterStore.getInstance().active.contextName)
|
||||||
}, 5000)
|
}, 5000)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -57,7 +57,7 @@ The example above logs messages when the extension is enabled and disabled.
|
|||||||
Cluster pages appear in the cluster dashboard.
|
Cluster pages appear in the cluster dashboard.
|
||||||
Use cluster pages to display information about or add functionality to the active cluster.
|
Use cluster pages to display information about or add functionality to the active cluster.
|
||||||
It is also possible to include custom details from other clusters.
|
It is also possible to include custom details from other clusters.
|
||||||
Use your extension to access Kubernetes resources in the active cluster with [`clusterStore`](../stores#clusterstore).
|
Use your extension to access Kubernetes resources in the active cluster with [`ClusterStore.getInstance()`](../stores#Clusterstore).
|
||||||
|
|
||||||
Add a cluster page definition to a `LensRendererExtension` subclass with the following example:
|
Add a cluster page definition to a `LensRendererExtension` subclass with the following example:
|
||||||
|
|
||||||
|
|||||||
@ -15,6 +15,12 @@ This guide shows how to create a store for the [`appPreferences`](../renderer-ex
|
|||||||
The preference is a simple boolean that indicates whether or not something is enabled.
|
The preference is a simple boolean that indicates whether or not something is enabled.
|
||||||
However, in the example, the enabled state is not stored anywhere, and it reverts to the default when Lens is restarted.
|
However, in the example, the enabled state is not stored anywhere, and it reverts to the default when Lens is restarted.
|
||||||
|
|
||||||
|
`Store.ExtensionStore`'s child class will need to be created before being used.
|
||||||
|
It is recommended to call the inherited static method `getInstanceOrCreate()` only in one place, generally within you extension's `onActivate()` method.
|
||||||
|
It is also recommenced to delete the instance, using the inherited static method `resetInstance()`, in your extension's `onDeactivate()` method.
|
||||||
|
Everywhere else in your code you should use the `getInstance()` static method.
|
||||||
|
This is so that your data is kept up to date and not persisted longer than expected.
|
||||||
|
|
||||||
The following example code creates a store for the `appPreferences` guide example:
|
The following example code creates a store for the `appPreferences` guide example:
|
||||||
|
|
||||||
``` typescript
|
``` typescript
|
||||||
@ -50,8 +56,6 @@ export class ExamplePreferencesStore extends Store.ExtensionStore<ExamplePrefere
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const examplePreferencesStore = ExamplePreferencesStore.getInstance<ExamplePreferencesStore>();
|
|
||||||
```
|
```
|
||||||
|
|
||||||
First, our example defines the extension's data model using the simple `ExamplePreferencesModel` type.
|
First, our example defines the extension's data model using the simple `ExamplePreferencesModel` type.
|
||||||
@ -71,46 +75,51 @@ It is called when the store is being saved.
|
|||||||
`toJSON()` must provide a JSON serializable object, facilitating its storage in JSON format.
|
`toJSON()` must provide a JSON serializable object, facilitating its storage in JSON format.
|
||||||
The `toJS()` function from [`mobx`](https://mobx.js.org/README.html) is convenient for this purpose, and is used here.
|
The `toJS()` function from [`mobx`](https://mobx.js.org/README.html) is convenient for this purpose, and is used here.
|
||||||
|
|
||||||
Finally, `examplePreferencesStore` is created by calling `ExamplePreferencesStore.getInstance<ExamplePreferencesStore>()`, and exported for use by other parts of the extension.
|
Finally, `ExamplePreferencesStore` is created by calling `ExamplePreferencesStore.getInstanceOrCreate()`, and exported for use by other parts of the extension.
|
||||||
Note that `examplePreferencesStore` is a singleton.
|
Note that `ExamplePreferencesStore` is a singleton.
|
||||||
Calling this function again will not create a new store.
|
Calling this function will create an instance if one has not been made before.
|
||||||
|
Through normal use you should call `ExamplePreferencesStore.getInstance()` as that will throw an error if an instance does not exist.
|
||||||
|
This provides some logical safety in that it limits where a new instance can be created.
|
||||||
|
Thus it prevents an instance from being created when the constructor args are not present at the call site.
|
||||||
|
|
||||||
|
If you are doing some cleanup it is recommended to call `ExamplePreferencesStore.getInstance(false)` which returns `undefined` instead of throwing when there is no instance.
|
||||||
|
|
||||||
The following example code, modified from the [`appPreferences`](../renderer-extension#apppreferences) guide demonstrates how to use the extension store.
|
The following example code, modified from the [`appPreferences`](../renderer-extension#apppreferences) guide demonstrates how to use the extension store.
|
||||||
`examplePreferencesStore` must be loaded in the main process, where loaded stores are automatically saved when exiting Lens.
|
`ExamplePreferencesStore` must be loaded in the main process, where loaded stores are automatically saved when exiting Lens.
|
||||||
This can be done in `./main.ts`:
|
This can be done in `./main.ts`:
|
||||||
|
|
||||||
``` typescript
|
``` typescript
|
||||||
import { LensMainExtension } from "@k8slens/extensions";
|
import { LensMainExtension } from "@k8slens/extensions";
|
||||||
import { examplePreferencesStore } from "./src/example-preference-store";
|
import { ExamplePreferencesStore } from "./src/example-preference-store";
|
||||||
|
|
||||||
export default class ExampleMainExtension extends LensMainExtension {
|
export default class ExampleMainExtension extends LensMainExtension {
|
||||||
async onActivate() {
|
async onActivate() {
|
||||||
await examplePreferencesStore.loadExtension(this);
|
await ExamplePreferencesStore.getInstanceOrCreate().loadExtension(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Here, `examplePreferencesStore` loads with `examplePreferencesStore.loadExtension(this)`, which is conveniently called from the `onActivate()` method of `ExampleMainExtension`.
|
Here, `ExamplePreferencesStore` loads with `ExamplePreferencesStore.getInstanceOrCreate().loadExtension(this)`, which is conveniently called from the `onActivate()` method of `ExampleMainExtension`.
|
||||||
Similarly, `examplePreferencesStore` must load in the renderer process where the `appPreferences` are handled.
|
Similarly, `ExamplePreferencesStore` must load in the renderer process where the `appPreferences` are handled.
|
||||||
This can be done in `./renderer.ts`:
|
This can be done in `./renderer.ts`:
|
||||||
|
|
||||||
``` typescript
|
``` typescript
|
||||||
import { LensRendererExtension } from "@k8slens/extensions";
|
import { LensRendererExtension } from "@k8slens/extensions";
|
||||||
import { ExamplePreferenceHint, ExamplePreferenceInput } from "./src/example-preference";
|
import { ExamplePreferenceHint, ExamplePreferenceInput } from "./src/example-preference";
|
||||||
import { examplePreferencesStore } from "./src/example-preference-store";
|
import { ExamplePreferencesStore } from "./src/example-preference-store";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
export default class ExampleRendererExtension extends LensRendererExtension {
|
export default class ExampleRendererExtension extends LensRendererExtension {
|
||||||
|
|
||||||
async onActivate() {
|
async onActivate() {
|
||||||
await examplePreferencesStore.loadExtension(this);
|
await ExamplePreferencesStore.getInstanceOrCreate().loadExtension(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
appPreferences = [
|
appPreferences = [
|
||||||
{
|
{
|
||||||
title: "Example Preferences",
|
title: "Example Preferences",
|
||||||
components: {
|
components: {
|
||||||
Input: () => <ExamplePreferenceInput preference={examplePreferencesStore}/>,
|
Input: () => <ExamplePreferenceInput />,
|
||||||
Hint: () => <ExamplePreferenceHint/>
|
Hint: () => <ExamplePreferenceHint/>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -118,8 +127,8 @@ export default class ExampleRendererExtension extends LensRendererExtension {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Again, `examplePreferencesStore.loadExtension(this)` is called to load `examplePreferencesStore`, this time from the `onActivate()` method of `ExampleRendererExtension`.
|
Again, `ExamplePreferencesStore.getInstanceOrCreate().loadExtension(this)` is called to load `ExamplePreferencesStore`, this time from the `onActivate()` method of `ExampleRendererExtension`.
|
||||||
There is no longer the need for the `preference` field in the `ExampleRendererExtension` class because the props for `ExamplePreferenceInput` is now `examplePreferencesStore`.
|
|
||||||
`ExamplePreferenceInput` is defined in `./src/example-preference.tsx`:
|
`ExamplePreferenceInput` is defined in `./src/example-preference.tsx`:
|
||||||
|
|
||||||
``` typescript
|
``` typescript
|
||||||
@ -128,21 +137,15 @@ import { observer } from "mobx-react";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { ExamplePreferencesStore } from "./example-preference-store";
|
import { ExamplePreferencesStore } from "./example-preference-store";
|
||||||
|
|
||||||
export class ExamplePreferenceProps {
|
|
||||||
preference: ExamplePreferencesStore;
|
|
||||||
}
|
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class ExamplePreferenceInput extends React.Component<ExamplePreferenceProps> {
|
export class ExamplePreferenceInput extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { preference } = this.props;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Component.Checkbox
|
<Component.Checkbox
|
||||||
label="I understand appPreferences"
|
label="I understand appPreferences"
|
||||||
value={preference.enabled}
|
value={ExamplePreferencesStore.getInstace().enabled}
|
||||||
onChange={v => { preference.enabled = v; }}
|
onChange={v => { ExamplePreferencesStore.getInstace().enabled = v; }}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -159,4 +162,4 @@ export class ExamplePreferenceHint extends React.Component {
|
|||||||
|
|
||||||
The only change here is that `ExamplePreferenceProps` defines its `preference` field as an `ExamplePreferencesStore` type.
|
The only change here is that `ExamplePreferenceProps` defines its `preference` field as an `ExamplePreferencesStore` type.
|
||||||
Everything else works as before, except that now the `enabled` state persists across Lens restarts because it is managed by the
|
Everything else works as before, except that now the `enabled` state persists across Lens restarts because it is managed by the
|
||||||
`examplePreferencesStore`.
|
`ExamplePreferencesStore`.
|
||||||
|
|||||||
@ -60,8 +60,8 @@ describe("Lens integration tests", () => {
|
|||||||
it("ensures helm repos", async () => {
|
it("ensures helm repos", async () => {
|
||||||
const repos = await listHelmRepositories();
|
const repos = await listHelmRepositories();
|
||||||
|
|
||||||
if (!repos[0]) {
|
if (repos.length === 0) {
|
||||||
fail("Lens failed to add Bitnami repository");
|
fail("Lens failed to add any repositories");
|
||||||
}
|
}
|
||||||
|
|
||||||
await app.client.click("[data-testid=kube-tab]");
|
await app.client.click("[data-testid=kube-tab]");
|
||||||
|
|||||||
@ -114,16 +114,14 @@ type HelmRepository = {
|
|||||||
url: string;
|
url: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function listHelmRepositories(retries = 0): Promise<HelmRepository[]>{
|
export async function listHelmRepositories(): Promise<HelmRepository[]>{
|
||||||
if (retries < 5) {
|
for (let i = 0; i < 10; i += 1) {
|
||||||
try {
|
try {
|
||||||
const { stdout: reposJson } = await promiseExec("helm repo list -o json");
|
const { stdout } = await promiseExec("helm repo list -o json");
|
||||||
|
|
||||||
return JSON.parse(reposJson);
|
return JSON.parse(stdout);
|
||||||
} catch {
|
} catch {
|
||||||
await new Promise(r => setTimeout(r, 2000)); // if no repositories, wait for Lens adding bitnami repository
|
await new Promise(r => setTimeout(r, 2000)); // if no repositories, wait for Lens adding bitnami repository
|
||||||
|
|
||||||
return await listHelmRepositories((retries + 1));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,8 +4,9 @@ import yaml from "js-yaml";
|
|||||||
import { Cluster } from "../../main/cluster";
|
import { Cluster } from "../../main/cluster";
|
||||||
import { ClusterStore, getClusterIdFromHost } from "../cluster-store";
|
import { ClusterStore, getClusterIdFromHost } from "../cluster-store";
|
||||||
import { Console } from "console";
|
import { Console } from "console";
|
||||||
|
import { stdout, stderr } from "process";
|
||||||
|
|
||||||
console = new Console(process.stdout, process.stderr); // fix mockFS
|
console = new Console(stdout, stderr);
|
||||||
|
|
||||||
const testDataIcon = fs.readFileSync("test-data/cluster-store-migration-icon.png");
|
const testDataIcon = fs.readFileSync("test-data/cluster-store-migration-icon.png");
|
||||||
const kubeconfig = `
|
const kubeconfig = `
|
||||||
@ -47,10 +48,8 @@ jest.mock("electron", () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
let clusterStore: ClusterStore;
|
|
||||||
|
|
||||||
describe("empty config", () => {
|
describe("empty config", () => {
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
ClusterStore.resetInstance();
|
ClusterStore.resetInstance();
|
||||||
const mockOpts = {
|
const mockOpts = {
|
||||||
"tmp": {
|
"tmp": {
|
||||||
@ -59,9 +58,8 @@ describe("empty config", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
clusterStore = ClusterStore.getInstance<ClusterStore>();
|
|
||||||
|
|
||||||
return clusterStore.load();
|
await ClusterStore.getInstanceOrCreate().load();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -70,7 +68,7 @@ describe("empty config", () => {
|
|||||||
|
|
||||||
describe("with foo cluster added", () => {
|
describe("with foo cluster added", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
clusterStore.addCluster(
|
ClusterStore.getInstance().addCluster(
|
||||||
new Cluster({
|
new Cluster({
|
||||||
id: "foo",
|
id: "foo",
|
||||||
contextName: "foo",
|
contextName: "foo",
|
||||||
@ -85,7 +83,7 @@ describe("empty config", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("adds new cluster to store", async () => {
|
it("adds new cluster to store", async () => {
|
||||||
const storedCluster = clusterStore.getById("foo");
|
const storedCluster = ClusterStore.getInstance().getById("foo");
|
||||||
|
|
||||||
expect(storedCluster.id).toBe("foo");
|
expect(storedCluster.id).toBe("foo");
|
||||||
expect(storedCluster.preferences.terminalCWD).toBe("/tmp");
|
expect(storedCluster.preferences.terminalCWD).toBe("/tmp");
|
||||||
@ -94,19 +92,19 @@ describe("empty config", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("removes cluster from store", async () => {
|
it("removes cluster from store", async () => {
|
||||||
await clusterStore.removeById("foo");
|
await ClusterStore.getInstance().removeById("foo");
|
||||||
expect(clusterStore.getById("foo")).toBeNull();
|
expect(ClusterStore.getInstance().getById("foo")).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sets active cluster", () => {
|
it("sets active cluster", () => {
|
||||||
clusterStore.setActive("foo");
|
ClusterStore.getInstance().setActive("foo");
|
||||||
expect(clusterStore.active.id).toBe("foo");
|
expect(ClusterStore.getInstance().active.id).toBe("foo");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("with prod and dev clusters added", () => {
|
describe("with prod and dev clusters added", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
clusterStore.addClusters(
|
ClusterStore.getInstance().addClusters(
|
||||||
new Cluster({
|
new Cluster({
|
||||||
id: "prod",
|
id: "prod",
|
||||||
contextName: "foo",
|
contextName: "foo",
|
||||||
@ -127,8 +125,8 @@ describe("empty config", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("check if store can contain multiple clusters", () => {
|
it("check if store can contain multiple clusters", () => {
|
||||||
expect(clusterStore.hasClusters()).toBeTruthy();
|
expect(ClusterStore.getInstance().hasClusters()).toBeTruthy();
|
||||||
expect(clusterStore.clusters.size).toBe(2);
|
expect(ClusterStore.getInstance().clusters.size).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("check if cluster's kubeconfig file saved", () => {
|
it("check if cluster's kubeconfig file saved", () => {
|
||||||
@ -178,9 +176,8 @@ describe("config with existing clusters", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
clusterStore = ClusterStore.getInstance<ClusterStore>();
|
|
||||||
|
|
||||||
return clusterStore.load();
|
return ClusterStore.getInstanceOrCreate().load();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -188,24 +185,24 @@ describe("config with existing clusters", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("allows to retrieve a cluster", () => {
|
it("allows to retrieve a cluster", () => {
|
||||||
const storedCluster = clusterStore.getById("cluster1");
|
const storedCluster = ClusterStore.getInstance().getById("cluster1");
|
||||||
|
|
||||||
expect(storedCluster.id).toBe("cluster1");
|
expect(storedCluster.id).toBe("cluster1");
|
||||||
expect(storedCluster.preferences.terminalCWD).toBe("/foo");
|
expect(storedCluster.preferences.terminalCWD).toBe("/foo");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows to delete a cluster", () => {
|
it("allows to delete a cluster", () => {
|
||||||
clusterStore.removeById("cluster2");
|
ClusterStore.getInstance().removeById("cluster2");
|
||||||
const storedCluster = clusterStore.getById("cluster1");
|
const storedCluster = ClusterStore.getInstance().getById("cluster1");
|
||||||
|
|
||||||
expect(storedCluster).toBeTruthy();
|
expect(storedCluster).toBeTruthy();
|
||||||
const storedCluster2 = clusterStore.getById("cluster2");
|
const storedCluster2 = ClusterStore.getInstance().getById("cluster2");
|
||||||
|
|
||||||
expect(storedCluster2).toBeNull();
|
expect(storedCluster2).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows getting all of the clusters", async () => {
|
it("allows getting all of the clusters", async () => {
|
||||||
const storedClusters = clusterStore.clustersList;
|
const storedClusters = ClusterStore.getInstance().clustersList;
|
||||||
|
|
||||||
expect(storedClusters.length).toBe(3);
|
expect(storedClusters.length).toBe(3);
|
||||||
expect(storedClusters[0].id).toBe("cluster1");
|
expect(storedClusters[0].id).toBe("cluster1");
|
||||||
@ -216,7 +213,7 @@ describe("config with existing clusters", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("marks owned cluster disabled by default", () => {
|
it("marks owned cluster disabled by default", () => {
|
||||||
const storedClusters = clusterStore.clustersList;
|
const storedClusters = ClusterStore.getInstance().clustersList;
|
||||||
|
|
||||||
expect(storedClusters[0].enabled).toBe(true);
|
expect(storedClusters[0].enabled).toBe(true);
|
||||||
expect(storedClusters[2].enabled).toBe(false);
|
expect(storedClusters[2].enabled).toBe(false);
|
||||||
@ -276,9 +273,8 @@ users:
|
|||||||
};
|
};
|
||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
clusterStore = ClusterStore.getInstance<ClusterStore>();
|
|
||||||
|
|
||||||
return clusterStore.load();
|
return ClusterStore.getInstanceOrCreate().load();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -286,7 +282,7 @@ users:
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("does not enable clusters with invalid kubeconfig", () => {
|
it("does not enable clusters with invalid kubeconfig", () => {
|
||||||
const storedClusters = clusterStore.clustersList;
|
const storedClusters = ClusterStore.getInstance().clustersList;
|
||||||
|
|
||||||
expect(storedClusters.length).toBe(2);
|
expect(storedClusters.length).toBe(2);
|
||||||
expect(storedClusters[0].enabled).toBeFalsy;
|
expect(storedClusters[0].enabled).toBeFalsy;
|
||||||
@ -319,9 +315,8 @@ describe("pre 2.0 config with an existing cluster", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
clusterStore = ClusterStore.getInstance<ClusterStore>();
|
|
||||||
|
|
||||||
return clusterStore.load();
|
return ClusterStore.getInstanceOrCreate().load();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -329,7 +324,7 @@ describe("pre 2.0 config with an existing cluster", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("migrates to modern format with kubeconfig in a file", async () => {
|
it("migrates to modern format with kubeconfig in a file", async () => {
|
||||||
const config = clusterStore.clustersList[0].kubeConfigPath;
|
const config = ClusterStore.getInstance().clustersList[0].kubeConfigPath;
|
||||||
|
|
||||||
expect(fs.readFileSync(config, "utf8")).toContain(`"contexts":[]`);
|
expect(fs.readFileSync(config, "utf8")).toContain(`"contexts":[]`);
|
||||||
});
|
});
|
||||||
@ -347,16 +342,51 @@ describe("pre 2.6.0 config with a cluster that has arrays in auth config", () =>
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
cluster1: {
|
cluster1: {
|
||||||
kubeConfig: "apiVersion: v1\nclusters:\n- cluster:\n server: https://10.211.55.6:8443\n name: minikube\ncontexts:\n- context:\n cluster: minikube\n user: minikube\n name: minikube\ncurrent-context: minikube\nkind: Config\npreferences: {}\nusers:\n- name: minikube\n user:\n client-certificate: /Users/kimmo/.minikube/client.crt\n client-key: /Users/kimmo/.minikube/client.key\n auth-provider:\n config:\n access-token:\n - should be string\n expiry:\n - should be string\n"
|
kubeConfig: JSON.stringify({
|
||||||
|
apiVersion: "v1",
|
||||||
|
clusters: [{
|
||||||
|
cluster: {
|
||||||
|
server: "https://10.211.55.6:8443",
|
||||||
|
},
|
||||||
|
name: "minikube",
|
||||||
|
}],
|
||||||
|
contexts: [{
|
||||||
|
context: {
|
||||||
|
cluster: "minikube",
|
||||||
|
user: "minikube",
|
||||||
|
name: "minikube",
|
||||||
|
},
|
||||||
|
name: "minikube",
|
||||||
|
}],
|
||||||
|
"current-context": "minikube",
|
||||||
|
kind: "Config",
|
||||||
|
preferences: {},
|
||||||
|
users: [{
|
||||||
|
name: "minikube",
|
||||||
|
user: {
|
||||||
|
"client-certificate": "/Users/foo/.minikube/client.crt",
|
||||||
|
"client-key": "/Users/foo/.minikube/client.key",
|
||||||
|
"auth-provider": {
|
||||||
|
config: {
|
||||||
|
"access-token": [
|
||||||
|
"should be string"
|
||||||
|
],
|
||||||
|
expiry: [
|
||||||
|
"should be string"
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
clusterStore = ClusterStore.getInstance<ClusterStore>();
|
|
||||||
|
|
||||||
return clusterStore.load();
|
return ClusterStore.getInstanceOrCreate().load();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -364,10 +394,12 @@ describe("pre 2.6.0 config with a cluster that has arrays in auth config", () =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("replaces array format access token and expiry into string", async () => {
|
it("replaces array format access token and expiry into string", async () => {
|
||||||
const file = clusterStore.clustersList[0].kubeConfigPath;
|
const file = ClusterStore.getInstance().clustersList[0].kubeConfigPath;
|
||||||
const config = fs.readFileSync(file, "utf8");
|
const config = fs.readFileSync(file, "utf8");
|
||||||
const kc = yaml.safeLoad(config);
|
const kc = yaml.safeLoad(config);
|
||||||
|
|
||||||
|
console.log(kc);
|
||||||
|
|
||||||
expect(kc.users[0].user["auth-provider"].config["access-token"]).toBe("should be string");
|
expect(kc.users[0].user["auth-provider"].config["access-token"]).toBe("should be string");
|
||||||
expect(kc.users[0].user["auth-provider"].config["expiry"]).toBe("should be string");
|
expect(kc.users[0].user["auth-provider"].config["expiry"]).toBe("should be string");
|
||||||
});
|
});
|
||||||
@ -397,9 +429,8 @@ describe("pre 2.6.0 config with a cluster icon", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
clusterStore = ClusterStore.getInstance<ClusterStore>();
|
|
||||||
|
|
||||||
return clusterStore.load();
|
return ClusterStore.getInstanceOrCreate().load();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -407,7 +438,7 @@ describe("pre 2.6.0 config with a cluster icon", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("moves the icon into preferences", async () => {
|
it("moves the icon into preferences", async () => {
|
||||||
const storedClusterData = clusterStore.clustersList[0];
|
const storedClusterData = ClusterStore.getInstance().clustersList[0];
|
||||||
|
|
||||||
expect(storedClusterData.hasOwnProperty("icon")).toBe(false);
|
expect(storedClusterData.hasOwnProperty("icon")).toBe(false);
|
||||||
expect(storedClusterData.preferences.hasOwnProperty("icon")).toBe(true);
|
expect(storedClusterData.preferences.hasOwnProperty("icon")).toBe(true);
|
||||||
@ -437,9 +468,8 @@ describe("for a pre 2.7.0-beta.0 config without a workspace", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
clusterStore = ClusterStore.getInstance<ClusterStore>();
|
|
||||||
|
|
||||||
return clusterStore.load();
|
return ClusterStore.getInstanceOrCreate().load();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -474,9 +504,8 @@ describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
clusterStore = ClusterStore.getInstance<ClusterStore>();
|
|
||||||
|
|
||||||
return clusterStore.load();
|
return ClusterStore.getInstanceOrCreate().load();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -484,13 +513,13 @@ describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("migrates to modern format with kubeconfig in a file", async () => {
|
it("migrates to modern format with kubeconfig in a file", async () => {
|
||||||
const config = clusterStore.clustersList[0].kubeConfigPath;
|
const config = ClusterStore.getInstance().clustersList[0].kubeConfigPath;
|
||||||
|
|
||||||
expect(fs.readFileSync(config, "utf8")).toBe(minimalValidKubeConfig);
|
expect(fs.readFileSync(config, "utf8")).toBe(minimalValidKubeConfig);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("migrates to modern format with icon not in file", async () => {
|
it("migrates to modern format with icon not in file", async () => {
|
||||||
const { icon } = clusterStore.clustersList[0].preferences;
|
const { icon } = ClusterStore.getInstance().clustersList[0].preferences;
|
||||||
|
|
||||||
expect(icon.startsWith("data:;base64,")).toBe(true);
|
expect(icon.startsWith("data:;base64,")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,4 +1,8 @@
|
|||||||
import { appEventBus, AppEvent } from "../event-bus";
|
import { appEventBus, AppEvent } from "../event-bus";
|
||||||
|
import { Console } from "console";
|
||||||
|
import { stdout, stderr } from "process";
|
||||||
|
|
||||||
|
console = new Console(stdout, stderr);
|
||||||
|
|
||||||
describe("event bus tests", () => {
|
describe("event bus tests", () => {
|
||||||
describe("emit", () => {
|
describe("emit", () => {
|
||||||
|
|||||||
@ -1,8 +1,12 @@
|
|||||||
import mockFs from "mock-fs";
|
import mockFs from "mock-fs";
|
||||||
import { HotbarStore, hotbarStore } from "../hotbar-store";
|
import { ClusterStore } from "../cluster-store";
|
||||||
|
import { HotbarStore } from "../hotbar-store";
|
||||||
|
|
||||||
describe("HotbarStore", () => {
|
describe("HotbarStore", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
ClusterStore.resetInstance();
|
||||||
|
ClusterStore.getInstanceOrCreate();
|
||||||
|
|
||||||
HotbarStore.resetInstance();
|
HotbarStore.resetInstance();
|
||||||
mockFs({ tmp: { "lens-hotbar-store.json": "{}" } });
|
mockFs({ tmp: { "lens-hotbar-store.json": "{}" } });
|
||||||
});
|
});
|
||||||
@ -13,8 +17,8 @@ describe("HotbarStore", () => {
|
|||||||
|
|
||||||
describe("load", () => {
|
describe("load", () => {
|
||||||
it("loads one hotbar by default", () => {
|
it("loads one hotbar by default", () => {
|
||||||
hotbarStore.load();
|
HotbarStore.getInstanceOrCreate().load();
|
||||||
expect(hotbarStore.hotbars.length).toEqual(1);
|
expect(HotbarStore.getInstance().hotbars.length).toEqual(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,4 +1,8 @@
|
|||||||
import { SearchStore } from "../search-store";
|
import { SearchStore } from "../search-store";
|
||||||
|
import { Console } from "console";
|
||||||
|
import { stdout, stderr } from "process";
|
||||||
|
|
||||||
|
console = new Console(stdout, stderr);
|
||||||
|
|
||||||
let searchStore: SearchStore = null;
|
let searchStore: SearchStore = null;
|
||||||
const logs = [
|
const logs = [
|
||||||
|
|||||||
@ -10,7 +10,7 @@ jest.mock("electron", () => {
|
|||||||
getVersion: () => "99.99.99",
|
getVersion: () => "99.99.99",
|
||||||
getPath: () => "tmp",
|
getPath: () => "tmp",
|
||||||
getLocale: () => "en",
|
getLocale: () => "en",
|
||||||
setLoginItemSettings: jest.fn(),
|
setLoginItemSettings: (): void => void 0,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -18,12 +18,19 @@ jest.mock("electron", () => {
|
|||||||
import { UserStore } from "../user-store";
|
import { UserStore } from "../user-store";
|
||||||
import { SemVer } from "semver";
|
import { SemVer } from "semver";
|
||||||
import electron from "electron";
|
import electron from "electron";
|
||||||
|
import { stdout, stderr } from "process";
|
||||||
|
|
||||||
|
console = new Console(stdout, stderr);
|
||||||
|
|
||||||
describe("user store tests", () => {
|
describe("user store tests", () => {
|
||||||
describe("for an empty config", () => {
|
describe("for an empty config", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
UserStore.resetInstance();
|
UserStore.resetInstance();
|
||||||
mockFs({ tmp: { "config.json": "{}" } });
|
mockFs({ tmp: { "config.json": "{}", "kube_config": "{}" } });
|
||||||
|
|
||||||
|
(UserStore.getInstanceOrCreate() as any).refreshNewContexts = jest.fn(() => Promise.resolve());
|
||||||
|
|
||||||
|
return UserStore.getInstance().load();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -31,14 +38,14 @@ describe("user store tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("allows setting and retrieving lastSeenAppVersion", () => {
|
it("allows setting and retrieving lastSeenAppVersion", () => {
|
||||||
const us = UserStore.getInstance<UserStore>();
|
const us = UserStore.getInstance();
|
||||||
|
|
||||||
us.lastSeenAppVersion = "1.2.3";
|
us.lastSeenAppVersion = "1.2.3";
|
||||||
expect(us.lastSeenAppVersion).toBe("1.2.3");
|
expect(us.lastSeenAppVersion).toBe("1.2.3");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows adding and listing seen contexts", () => {
|
it("allows adding and listing seen contexts", () => {
|
||||||
const us = UserStore.getInstance<UserStore>();
|
const us = UserStore.getInstance();
|
||||||
|
|
||||||
us.seenContexts.add("foo");
|
us.seenContexts.add("foo");
|
||||||
expect(us.seenContexts.size).toBe(1);
|
expect(us.seenContexts.size).toBe(1);
|
||||||
@ -51,7 +58,7 @@ describe("user store tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("allows setting and getting preferences", () => {
|
it("allows setting and getting preferences", () => {
|
||||||
const us = UserStore.getInstance<UserStore>();
|
const us = UserStore.getInstance();
|
||||||
|
|
||||||
us.preferences.httpsProxy = "abcd://defg";
|
us.preferences.httpsProxy = "abcd://defg";
|
||||||
|
|
||||||
@ -63,7 +70,7 @@ describe("user store tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("correctly resets theme to default value", async () => {
|
it("correctly resets theme to default value", async () => {
|
||||||
const us = UserStore.getInstance<UserStore>();
|
const us = UserStore.getInstance();
|
||||||
|
|
||||||
us.isLoaded = true;
|
us.isLoaded = true;
|
||||||
|
|
||||||
@ -73,7 +80,7 @@ describe("user store tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("correctly calculates if the last seen version is an old release", () => {
|
it("correctly calculates if the last seen version is an old release", () => {
|
||||||
const us = UserStore.getInstance<UserStore>();
|
const us = UserStore.getInstance();
|
||||||
|
|
||||||
expect(us.isNewVersion).toBe(true);
|
expect(us.isNewVersion).toBe(true);
|
||||||
|
|
||||||
@ -94,6 +101,8 @@ describe("user store tests", () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return UserStore.getInstanceOrCreate().load();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -101,7 +110,7 @@ describe("user store tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("sets last seen app version to 0.0.0", () => {
|
it("sets last seen app version to 0.0.0", () => {
|
||||||
const us = UserStore.getInstance<UserStore>();
|
const us = UserStore.getInstance();
|
||||||
|
|
||||||
expect(us.lastSeenAppVersion).toBe("0.0.0");
|
expect(us.lastSeenAppVersion).toBe("0.0.0");
|
||||||
});
|
});
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { observable } from "mobx";
|
|||||||
import { catalogCategoryRegistry } from "../catalog-category-registry";
|
import { catalogCategoryRegistry } from "../catalog-category-registry";
|
||||||
import { CatalogCategory, CatalogEntity, CatalogEntityActionContext, CatalogEntityAddMenuContext, CatalogEntityContextMenuContext, CatalogEntityData, CatalogEntityMetadata, CatalogEntityStatus } from "../catalog-entity";
|
import { CatalogCategory, CatalogEntity, CatalogEntityActionContext, CatalogEntityAddMenuContext, CatalogEntityContextMenuContext, CatalogEntityData, CatalogEntityMetadata, CatalogEntityStatus } from "../catalog-entity";
|
||||||
import { clusterDisconnectHandler } from "../cluster-ipc";
|
import { clusterDisconnectHandler } from "../cluster-ipc";
|
||||||
import { clusterStore } from "../cluster-store";
|
import { ClusterStore } from "../cluster-store";
|
||||||
import { requestMain } from "../ipc";
|
import { requestMain } from "../ipc";
|
||||||
|
|
||||||
export type KubernetesClusterSpec = {
|
export type KubernetesClusterSpec = {
|
||||||
@ -56,7 +56,7 @@ export class KubernetesCluster implements CatalogEntity {
|
|||||||
icon: "delete",
|
icon: "delete",
|
||||||
title: "Delete",
|
title: "Delete",
|
||||||
onlyVisibleForSource: "local",
|
onlyVisibleForSource: "local",
|
||||||
onClick: async () => clusterStore.removeById(this.metadata.uid),
|
onClick: async () => ClusterStore.getInstance().removeById(this.metadata.uid),
|
||||||
confirm: {
|
confirm: {
|
||||||
message: `Remove Kubernetes Cluster "${this.metadata.name} from Lens?`
|
message: `Remove Kubernetes Cluster "${this.metadata.name} from Lens?`
|
||||||
}
|
}
|
||||||
@ -68,7 +68,7 @@ export class KubernetesCluster implements CatalogEntity {
|
|||||||
icon: "link_off",
|
icon: "link_off",
|
||||||
title: "Disconnect",
|
title: "Disconnect",
|
||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
clusterStore.deactivate(this.metadata.uid);
|
ClusterStore.getInstance().deactivate(this.metadata.uid);
|
||||||
requestMain(clusterDisconnectHandler, this.metadata.uid);
|
requestMain(clusterDisconnectHandler, this.metadata.uid);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { handleRequest } from "./ipc";
|
import { handleRequest } from "./ipc";
|
||||||
import { ClusterId, clusterStore } from "./cluster-store";
|
import { ClusterId, ClusterStore } from "./cluster-store";
|
||||||
import { appEventBus } from "./event-bus";
|
import { appEventBus } from "./event-bus";
|
||||||
import { ResourceApplier } from "../main/resource-applier";
|
import { ResourceApplier } from "../main/resource-applier";
|
||||||
import { ipcMain, IpcMainInvokeEvent } from "electron";
|
import { ipcMain, IpcMainInvokeEvent } from "electron";
|
||||||
@ -11,10 +11,9 @@ export const clusterRefreshHandler = "cluster:refresh";
|
|||||||
export const clusterDisconnectHandler = "cluster:disconnect";
|
export const clusterDisconnectHandler = "cluster:disconnect";
|
||||||
export const clusterKubectlApplyAllHandler = "cluster:kubectl-apply-all";
|
export const clusterKubectlApplyAllHandler = "cluster:kubectl-apply-all";
|
||||||
|
|
||||||
|
|
||||||
if (ipcMain) {
|
if (ipcMain) {
|
||||||
handleRequest(clusterActivateHandler, (event, clusterId: ClusterId, force = false) => {
|
handleRequest(clusterActivateHandler, (event, clusterId: ClusterId, force = false) => {
|
||||||
const cluster = clusterStore.getById(clusterId);
|
const cluster = ClusterStore.getInstance().getById(clusterId);
|
||||||
|
|
||||||
if (cluster) {
|
if (cluster) {
|
||||||
return cluster.activate(force);
|
return cluster.activate(force);
|
||||||
@ -22,7 +21,7 @@ if (ipcMain) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
handleRequest(clusterSetFrameIdHandler, (event: IpcMainInvokeEvent, clusterId: ClusterId) => {
|
handleRequest(clusterSetFrameIdHandler, (event: IpcMainInvokeEvent, clusterId: ClusterId) => {
|
||||||
const cluster = clusterStore.getById(clusterId);
|
const cluster = ClusterStore.getInstance().getById(clusterId);
|
||||||
|
|
||||||
if (cluster) {
|
if (cluster) {
|
||||||
clusterFrameMap.set(cluster.id, { frameId: event.frameId, processId: event.processId });
|
clusterFrameMap.set(cluster.id, { frameId: event.frameId, processId: event.processId });
|
||||||
@ -32,14 +31,14 @@ if (ipcMain) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
handleRequest(clusterRefreshHandler, (event, clusterId: ClusterId) => {
|
handleRequest(clusterRefreshHandler, (event, clusterId: ClusterId) => {
|
||||||
const cluster = clusterStore.getById(clusterId);
|
const cluster = ClusterStore.getInstance().getById(clusterId);
|
||||||
|
|
||||||
if (cluster) return cluster.refresh({ refreshMetadata: true });
|
if (cluster) return cluster.refresh({ refreshMetadata: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
handleRequest(clusterDisconnectHandler, (event, clusterId: ClusterId) => {
|
handleRequest(clusterDisconnectHandler, (event, clusterId: ClusterId) => {
|
||||||
appEventBus.emit({name: "cluster", action: "stop"});
|
appEventBus.emit({name: "cluster", action: "stop"});
|
||||||
const cluster = clusterStore.getById(clusterId);
|
const cluster = ClusterStore.getInstance().getById(clusterId);
|
||||||
|
|
||||||
if (cluster) {
|
if (cluster) {
|
||||||
cluster.disconnect();
|
cluster.disconnect();
|
||||||
@ -49,7 +48,7 @@ if (ipcMain) {
|
|||||||
|
|
||||||
handleRequest(clusterKubectlApplyAllHandler, (event, clusterId: ClusterId, resources: string[]) => {
|
handleRequest(clusterKubectlApplyAllHandler, (event, clusterId: ClusterId, resources: string[]) => {
|
||||||
appEventBus.emit({name: "cluster", action: "kubectl-apply-all"});
|
appEventBus.emit({name: "cluster", action: "kubectl-apply-all"});
|
||||||
const cluster = clusterStore.getById(clusterId);
|
const cluster = ClusterStore.getInstance().getById(clusterId);
|
||||||
|
|
||||||
if (cluster) {
|
if (cluster) {
|
||||||
const applier = new ResourceApplier(cluster);
|
const applier = new ResourceApplier(cluster);
|
||||||
|
|||||||
@ -112,7 +112,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
|
|
||||||
private static stateRequestChannel = "cluster:states";
|
private static stateRequestChannel = "cluster:states";
|
||||||
|
|
||||||
private constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
configName: "lens-cluster-store",
|
configName: "lens-cluster-store",
|
||||||
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
||||||
@ -337,8 +337,6 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const clusterStore = ClusterStore.getInstance<ClusterStore>();
|
|
||||||
|
|
||||||
export function getClusterIdFromHost(host: string): ClusterId | undefined {
|
export function getClusterIdFromHost(host: string): ClusterId | undefined {
|
||||||
// e.g host == "%clusterId.localhost:45345"
|
// e.g host == "%clusterId.localhost:45345"
|
||||||
const subDomains = host.split(":")[0].split(".");
|
const subDomains = host.split(":")[0].split(".");
|
||||||
@ -355,5 +353,5 @@ export function getHostedClusterId() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getHostedCluster(): Cluster {
|
export function getHostedCluster(): Cluster {
|
||||||
return clusterStore.getById(getHostedClusterId());
|
return ClusterStore.getInstance().getById(getHostedClusterId());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,7 +23,7 @@ export interface HotbarStoreModel {
|
|||||||
export class HotbarStore extends BaseStore<HotbarStoreModel> {
|
export class HotbarStore extends BaseStore<HotbarStoreModel> {
|
||||||
@observable hotbars: Hotbar[] = [];
|
@observable hotbars: Hotbar[] = [];
|
||||||
|
|
||||||
private constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
configName: "lens-hotbar-store",
|
configName: "lens-hotbar-store",
|
||||||
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
||||||
@ -67,5 +67,3 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const hotbarStore = HotbarStore.getInstance<HotbarStore>();
|
|
||||||
|
|||||||
@ -5,8 +5,8 @@ import { pathToRegexp } from "path-to-regexp";
|
|||||||
import logger from "../../main/logger";
|
import logger from "../../main/logger";
|
||||||
import Url from "url-parse";
|
import Url from "url-parse";
|
||||||
import { RoutingError, RoutingErrorType } from "./error";
|
import { RoutingError, RoutingErrorType } from "./error";
|
||||||
import { extensionsStore } from "../../extensions/extensions-store";
|
import { ExtensionsStore } from "../../extensions/extensions-store";
|
||||||
import { extensionLoader } from "../../extensions/extension-loader";
|
import { ExtensionLoader } from "../../extensions/extension-loader";
|
||||||
import { LensExtension } from "../../extensions/lens-extension";
|
import { LensExtension } from "../../extensions/lens-extension";
|
||||||
import { RouteHandler, RouteParams } from "../../extensions/registries/protocol-handler-registry";
|
import { RouteHandler, RouteParams } from "../../extensions/registries/protocol-handler-registry";
|
||||||
|
|
||||||
@ -72,7 +72,7 @@ export abstract class LensProtocolRouter extends Singleton {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* find the most specific matching handler and call it
|
* find the most specific matching handler and call it
|
||||||
* @param routes the array of (path schemas, handler) paris to match against
|
* @param routes the array of (path schemas, handler) pairs to match against
|
||||||
* @param url the url (in its current state)
|
* @param url the url (in its current state)
|
||||||
*/
|
*/
|
||||||
protected _route(routes: [string, RouteHandler][], url: Url, extensionName?: string): void {
|
protected _route(routes: [string, RouteHandler][], url: Url, extensionName?: string): void {
|
||||||
@ -124,7 +124,7 @@ export abstract class LensProtocolRouter extends Singleton {
|
|||||||
const { [EXTENSION_PUBLISHER_MATCH]: publisher, [EXTENSION_NAME_MATCH]: partialName } = match.params;
|
const { [EXTENSION_PUBLISHER_MATCH]: publisher, [EXTENSION_NAME_MATCH]: partialName } = match.params;
|
||||||
const name = [publisher, partialName].filter(Boolean).join("/");
|
const name = [publisher, partialName].filter(Boolean).join("/");
|
||||||
|
|
||||||
const extension = extensionLoader.userExtensionsByName.get(name);
|
const extension = ExtensionLoader.getInstance().userExtensionsByName.get(name);
|
||||||
|
|
||||||
if (!extension) {
|
if (!extension) {
|
||||||
logger.info(`${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched, but not installed`);
|
logger.info(`${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched, but not installed`);
|
||||||
@ -132,7 +132,7 @@ export abstract class LensProtocolRouter extends Singleton {
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!extensionsStore.isEnabled(extension.id)) {
|
if (!ExtensionsStore.getInstance().isEnabled(extension.id)) {
|
||||||
logger.info(`${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched, but not enabled`);
|
logger.info(`${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched, but not enabled`);
|
||||||
|
|
||||||
return name;
|
return name;
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import request from "request";
|
import request from "request";
|
||||||
import requestPromise from "request-promise-native";
|
import requestPromise from "request-promise-native";
|
||||||
import { userStore } from "./user-store";
|
import { UserStore } from "./user-store";
|
||||||
|
|
||||||
// todo: get rid of "request" (deprecated)
|
// todo: get rid of "request" (deprecated)
|
||||||
// https://github.com/lensapp/lens/issues/459
|
// https://github.com/lensapp/lens/issues/459
|
||||||
|
|
||||||
function getDefaultRequestOpts(): Partial<request.Options> {
|
function getDefaultRequestOpts(): Partial<request.Options> {
|
||||||
const { httpsProxy, allowUntrustedCAs } = userStore.preferences;
|
const { httpsProxy, allowUntrustedCAs } = UserStore.getInstance().preferences;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
proxy: httpsProxy || undefined,
|
proxy: httpsProxy || undefined,
|
||||||
|
|||||||
@ -38,7 +38,7 @@ export interface UserPreferences {
|
|||||||
export class UserStore extends BaseStore<UserStoreModel> {
|
export class UserStore extends BaseStore<UserStoreModel> {
|
||||||
static readonly defaultTheme: ThemeId = "lens-dark";
|
static readonly defaultTheme: ThemeId = "lens-dark";
|
||||||
|
|
||||||
private constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
configName: "lens-user-store",
|
configName: "lens-user-store",
|
||||||
migrations,
|
migrations,
|
||||||
@ -163,14 +163,6 @@ export class UserStore extends BaseStore<UserStoreModel> {
|
|||||||
this.newContexts.clear();
|
this.newContexts.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Getting default directory to download kubectl binaries
|
|
||||||
* @returns string
|
|
||||||
*/
|
|
||||||
getDefaultKubectlPath(): string {
|
|
||||||
return path.join((app || remote.app).getPath("userData"), "binaries");
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
protected async fromStore(data: Partial<UserStoreModel> = {}) {
|
protected async fromStore(data: Partial<UserStoreModel> = {}) {
|
||||||
const { lastSeenAppVersion, seenContexts = [], preferences, kubeConfigPath } = data;
|
const { lastSeenAppVersion, seenContexts = [], preferences, kubeConfigPath } = data;
|
||||||
@ -200,4 +192,10 @@ export class UserStore extends BaseStore<UserStoreModel> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const userStore = UserStore.getInstance<UserStore>();
|
/**
|
||||||
|
* Getting default directory to download kubectl binaries
|
||||||
|
* @returns string
|
||||||
|
*/
|
||||||
|
export function getDefaultKubectlPath(): string {
|
||||||
|
return path.join((app || remote.app).getPath("userData"), "binaries");
|
||||||
|
}
|
||||||
|
|||||||
@ -3,8 +3,8 @@
|
|||||||
export function debouncePromise<T, F extends any[]>(func: (...args: F) => T | Promise<T>, timeout = 0): (...args: F) => Promise<T> {
|
export function debouncePromise<T, F extends any[]>(func: (...args: F) => T | Promise<T>, timeout = 0): (...args: F) => Promise<T> {
|
||||||
let timer: NodeJS.Timeout;
|
let timer: NodeJS.Timeout;
|
||||||
|
|
||||||
return (...params: any[]) => new Promise(resolve => {
|
return (...params: F) => new Promise(resolve => {
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
timer = global.setTimeout(() => resolve(func.apply(this, params)), timeout);
|
timer = global.setTimeout(() => resolve(func(...params)), timeout);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,25 +5,39 @@
|
|||||||
* @example
|
* @example
|
||||||
* const usersStore: UsersStore = UsersStore.getInstance();
|
* const usersStore: UsersStore = UsersStore.getInstance();
|
||||||
*/
|
*/
|
||||||
|
type StaticThis<T, R extends any[]> = { new(...args: R): T };
|
||||||
|
|
||||||
type Constructor<T = {}> = new (...args: any[]) => T;
|
export class Singleton {
|
||||||
|
|
||||||
class Singleton {
|
|
||||||
private static instances = new WeakMap<object, Singleton>();
|
private static instances = new WeakMap<object, Singleton>();
|
||||||
|
private static creating = "";
|
||||||
|
|
||||||
// todo: improve types inferring
|
constructor() {
|
||||||
static getInstance<T>(...args: ConstructorParameters<Constructor<T>>): T {
|
if (Singleton.creating.length === 0) {
|
||||||
|
throw new TypeError("A singleton class must be created by getInstanceOrCreate()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static getInstanceOrCreate<T, R extends any[]>(this: StaticThis<T, R>, ...args: R): T {
|
||||||
if (!Singleton.instances.has(this)) {
|
if (!Singleton.instances.has(this)) {
|
||||||
Singleton.instances.set(this, Reflect.construct(this, args));
|
Singleton.creating = this.name;
|
||||||
|
Singleton.instances.set(this, new this(...args));
|
||||||
|
Singleton.creating = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
return Singleton.instances.get(this) as T;
|
return Singleton.instances.get(this) as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getInstance<T, R extends any[]>(this: StaticThis<T, R>, strict = true): T | undefined {
|
||||||
|
if (!Singleton.instances.has(this) && strict) {
|
||||||
|
throw new TypeError(`instance of ${this.name} is not created`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Singleton.instances.get(this) as (T | undefined);
|
||||||
|
}
|
||||||
|
|
||||||
static resetInstance() {
|
static resetInstance() {
|
||||||
Singleton.instances.delete(this);
|
Singleton.instances.delete(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Singleton };
|
|
||||||
export default Singleton;
|
export default Singleton;
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { watch } from "chokidar";
|
import { watch } from "chokidar";
|
||||||
import { join, normalize } from "path";
|
import { join, normalize } from "path";
|
||||||
import { ExtensionDiscovery, InstalledExtension } from "../extension-discovery";
|
import { ExtensionDiscovery, InstalledExtension } from "../extension-discovery";
|
||||||
|
import { ExtensionsStore } from "../extensions-store";
|
||||||
|
|
||||||
jest.mock("../../common/ipc");
|
jest.mock("../../common/ipc");
|
||||||
jest.mock("fs-extra");
|
jest.mock("fs-extra");
|
||||||
@ -17,6 +18,12 @@ jest.mock("../extension-installer", () => ({
|
|||||||
const mockedWatch = watch as jest.MockedFunction<typeof watch>;
|
const mockedWatch = watch as jest.MockedFunction<typeof watch>;
|
||||||
|
|
||||||
describe("ExtensionDiscovery", () => {
|
describe("ExtensionDiscovery", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
ExtensionDiscovery.resetInstance();
|
||||||
|
ExtensionsStore.resetInstance();
|
||||||
|
ExtensionsStore.getInstanceOrCreate();
|
||||||
|
});
|
||||||
|
|
||||||
it("emits add for added extension", async done => {
|
it("emits add for added extension", async done => {
|
||||||
globalThis.__non_webpack_require__.mockImplementation(() => ({
|
globalThis.__non_webpack_require__.mockImplementation(() => ({
|
||||||
name: "my-extension"
|
name: "my-extension"
|
||||||
@ -36,7 +43,7 @@ describe("ExtensionDiscovery", () => {
|
|||||||
mockedWatch.mockImplementationOnce(() =>
|
mockedWatch.mockImplementationOnce(() =>
|
||||||
(mockWatchInstance) as any
|
(mockWatchInstance) as any
|
||||||
);
|
);
|
||||||
const extensionDiscovery = new ExtensionDiscovery();
|
const extensionDiscovery = ExtensionDiscovery.getInstanceOrCreate();
|
||||||
|
|
||||||
// Need to force isLoaded to be true so that the file watching is started
|
// Need to force isLoaded to be true so that the file watching is started
|
||||||
extensionDiscovery.isLoaded = true;
|
extensionDiscovery.isLoaded = true;
|
||||||
@ -76,7 +83,7 @@ describe("ExtensionDiscovery", () => {
|
|||||||
mockedWatch.mockImplementationOnce(() =>
|
mockedWatch.mockImplementationOnce(() =>
|
||||||
(mockWatchInstance) as any
|
(mockWatchInstance) as any
|
||||||
);
|
);
|
||||||
const extensionDiscovery = new ExtensionDiscovery();
|
const extensionDiscovery = ExtensionDiscovery.getInstanceOrCreate();
|
||||||
|
|
||||||
// Need to force isLoaded to be true so that the file watching is started
|
// Need to force isLoaded to be true so that the file watching is started
|
||||||
extensionDiscovery.isLoaded = true;
|
extensionDiscovery.isLoaded = true;
|
||||||
|
|||||||
@ -1,15 +1,21 @@
|
|||||||
import { ExtensionLoader } from "../extension-loader";
|
import { ExtensionLoader } from "../extension-loader";
|
||||||
import { ipcRenderer } from "electron";
|
import { ipcRenderer } from "electron";
|
||||||
import { extensionsStore } from "../extensions-store";
|
import { ExtensionsStore } from "../extensions-store";
|
||||||
|
import { Console } from "console";
|
||||||
|
import { stdout, stderr } from "process";
|
||||||
|
|
||||||
|
console = new Console(stdout, stderr);
|
||||||
|
|
||||||
const manifestPath = "manifest/path";
|
const manifestPath = "manifest/path";
|
||||||
const manifestPath2 = "manifest/path2";
|
const manifestPath2 = "manifest/path2";
|
||||||
const manifestPath3 = "manifest/path3";
|
const manifestPath3 = "manifest/path3";
|
||||||
|
|
||||||
jest.mock("../extensions-store", () => ({
|
jest.mock("../extensions-store", () => ({
|
||||||
extensionsStore: {
|
ExtensionsStore: {
|
||||||
|
getInstance: () => ({
|
||||||
whenLoaded: Promise.resolve(true),
|
whenLoaded: Promise.resolve(true),
|
||||||
mergeState: jest.fn()
|
mergeState: jest.fn()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -99,8 +105,12 @@ jest.mock(
|
|||||||
);
|
);
|
||||||
|
|
||||||
describe("ExtensionLoader", () => {
|
describe("ExtensionLoader", () => {
|
||||||
it("renderer updates extension after ipc broadcast", async (done) => {
|
beforeEach(() => {
|
||||||
const extensionLoader = new ExtensionLoader();
|
ExtensionLoader.resetInstance();
|
||||||
|
});
|
||||||
|
|
||||||
|
it.only("renderer updates extension after ipc broadcast", async (done) => {
|
||||||
|
const extensionLoader = ExtensionLoader.getInstanceOrCreate();
|
||||||
|
|
||||||
expect(extensionLoader.userExtensions).toMatchInlineSnapshot(`Map {}`);
|
expect(extensionLoader.userExtensions).toMatchInlineSnapshot(`Map {}`);
|
||||||
|
|
||||||
@ -140,20 +150,20 @@ describe("ExtensionLoader", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("updates ExtensionsStore after isEnabled is changed", async () => {
|
it("updates ExtensionsStore after isEnabled is changed", async () => {
|
||||||
(extensionsStore.mergeState as any).mockClear();
|
(ExtensionsStore.getInstance().mergeState as any).mockClear();
|
||||||
|
|
||||||
// Disable sending events in this test
|
// Disable sending events in this test
|
||||||
(ipcRenderer.on as any).mockImplementation();
|
(ipcRenderer.on as any).mockImplementation();
|
||||||
|
|
||||||
const extensionLoader = new ExtensionLoader();
|
const extensionLoader = ExtensionLoader.getInstanceOrCreate();
|
||||||
|
|
||||||
await extensionLoader.init();
|
await extensionLoader.init();
|
||||||
|
|
||||||
expect(extensionsStore.mergeState).not.toHaveBeenCalled();
|
expect(ExtensionsStore.getInstance().mergeState).not.toHaveBeenCalled();
|
||||||
|
|
||||||
Array.from(extensionLoader.userExtensions.values())[0].isEnabled = false;
|
Array.from(extensionLoader.userExtensions.values())[0].isEnabled = false;
|
||||||
|
|
||||||
expect(extensionsStore.mergeState).toHaveBeenCalledWith({
|
expect(ExtensionsStore.getInstance().mergeState).toHaveBeenCalledWith({
|
||||||
"manifest/path": {
|
"manifest/path": {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
name: "TestExtension"
|
name: "TestExtension"
|
||||||
|
|||||||
@ -1,4 +1,8 @@
|
|||||||
import { LensExtension } from "../lens-extension";
|
import { LensExtension } from "../lens-extension";
|
||||||
|
import { Console } from "console";
|
||||||
|
import { stdout, stderr } from "process";
|
||||||
|
|
||||||
|
console = new Console(stdout, stderr);
|
||||||
|
|
||||||
let ext: LensExtension = null;
|
let ext: LensExtension = null;
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import logger from "../main/logger";
|
|||||||
import { app } from "electron";
|
import { app } from "electron";
|
||||||
import { requestMain } from "../common/ipc";
|
import { requestMain } from "../common/ipc";
|
||||||
import { clusterKubectlApplyAllHandler } from "../common/cluster-ipc";
|
import { clusterKubectlApplyAllHandler } from "../common/cluster-ipc";
|
||||||
import { clusterStore } from "../common/cluster-store";
|
import { ClusterStore } from "../common/cluster-store";
|
||||||
|
|
||||||
export interface ClusterFeatureStatus {
|
export interface ClusterFeatureStatus {
|
||||||
/** feature's current version, as set by the implementation */
|
/** feature's current version, as set by the implementation */
|
||||||
@ -86,7 +86,7 @@ export abstract class ClusterFeature {
|
|||||||
protected async applyResources(cluster: KubernetesCluster, resourceSpec: string | string[]) {
|
protected async applyResources(cluster: KubernetesCluster, resourceSpec: string | string[]) {
|
||||||
let resources: string[];
|
let resources: string[];
|
||||||
|
|
||||||
const clusterModel = clusterStore.getById(cluster.metadata.uid);
|
const clusterModel = ClusterStore.getInstance().getById(cluster.metadata.uid);
|
||||||
|
|
||||||
if (!clusterModel) {
|
if (!clusterModel) {
|
||||||
throw new Error(`cluster not found`);
|
throw new Error(`cluster not found`);
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { getAppVersion } from "../../common/utils";
|
import { getAppVersion } from "../../common/utils";
|
||||||
import { extensionsStore } from "../extensions-store";
|
import { ExtensionsStore } from "../extensions-store";
|
||||||
|
|
||||||
export const version = getAppVersion();
|
export const version = getAppVersion();
|
||||||
export { isSnap, isWindows, isMac, isLinux, appName, slackUrl, issuesTrackerUrl } from "../../common/vars";
|
export { isSnap, isWindows, isMac, isLinux, appName, slackUrl, issuesTrackerUrl } from "../../common/vars";
|
||||||
|
|
||||||
export function getEnabledExtensions(): string[] {
|
export function getEnabledExtensions(): string[] {
|
||||||
return extensionsStore.enabledExtensions;
|
return ExtensionsStore.getInstance().enabledExtensions;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
|
|
||||||
import { computed } from "mobx";
|
|
||||||
import { CatalogEntity } from "../../common/catalog-entity";
|
import { CatalogEntity } from "../../common/catalog-entity";
|
||||||
import { catalogEntityRegistry as registry } from "../../common/catalog-entity-registry";
|
import { catalogEntityRegistry as registry } from "../../common/catalog-entity-registry";
|
||||||
|
|
||||||
@ -7,7 +6,7 @@ export { catalogCategoryRegistry as catalogCategories } from "../../common/catal
|
|||||||
export * from "../../common/catalog-entities";
|
export * from "../../common/catalog-entities";
|
||||||
|
|
||||||
export class CatalogEntityRegistry {
|
export class CatalogEntityRegistry {
|
||||||
@computed getItemsForApiKind<T extends CatalogEntity>(apiVersion: string, kind: string): T[] {
|
getItemsForApiKind<T extends CatalogEntity>(apiVersion: string, kind: string): T[] {
|
||||||
return registry.getItemsForApiKind<T>(apiVersion, kind);
|
return registry.getItemsForApiKind<T>(apiVersion, kind);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,9 +6,10 @@ import { observable, reaction, toJS, when } from "mobx";
|
|||||||
import os from "os";
|
import os from "os";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { broadcastMessage, handleRequest, requestMain, subscribeToBroadcast } from "../common/ipc";
|
import { broadcastMessage, handleRequest, requestMain, subscribeToBroadcast } from "../common/ipc";
|
||||||
|
import { Singleton } from "../common/utils";
|
||||||
import logger from "../main/logger";
|
import logger from "../main/logger";
|
||||||
import { extensionInstaller, PackageJson } from "./extension-installer";
|
import { extensionInstaller, PackageJson } from "./extension-installer";
|
||||||
import { extensionsStore } from "./extensions-store";
|
import { ExtensionsStore } from "./extensions-store";
|
||||||
import type { LensExtensionId, LensExtensionManifest } from "./lens-extension";
|
import type { LensExtensionId, LensExtensionManifest } from "./lens-extension";
|
||||||
|
|
||||||
export interface InstalledExtension {
|
export interface InstalledExtension {
|
||||||
@ -50,7 +51,7 @@ const isDirectoryLike = (lstat: fs.Stats) => lstat.isDirectory() || lstat.isSymb
|
|||||||
* - "add": When extension is added. The event is of type InstalledExtension
|
* - "add": When extension is added. The event is of type InstalledExtension
|
||||||
* - "remove": When extension is removed. The event is of type LensExtensionId
|
* - "remove": When extension is removed. The event is of type LensExtensionId
|
||||||
*/
|
*/
|
||||||
export class ExtensionDiscovery {
|
export class ExtensionDiscovery extends Singleton {
|
||||||
protected bundledFolderPath: string;
|
protected bundledFolderPath: string;
|
||||||
|
|
||||||
private loadStarted = false;
|
private loadStarted = false;
|
||||||
@ -66,6 +67,7 @@ export class ExtensionDiscovery {
|
|||||||
public events: EventEmitter;
|
public events: EventEmitter;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
super();
|
||||||
this.events = new EventEmitter();
|
this.events = new EventEmitter();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,7 +137,7 @@ export class ExtensionDiscovery {
|
|||||||
depth: 1,
|
depth: 1,
|
||||||
ignoreInitial: true,
|
ignoreInitial: true,
|
||||||
// Try to wait until the file has been completely copied.
|
// Try to wait until the file has been completely copied.
|
||||||
// The OS might emit an event for added file even it's not completely written to the filesysten.
|
// The OS might emit an event for added file even it's not completely written to the file-system.
|
||||||
awaitWriteFinish: {
|
awaitWriteFinish: {
|
||||||
// Wait 300ms until the file size doesn't change to consider the file written.
|
// Wait 300ms until the file size doesn't change to consider the file written.
|
||||||
// For a small file like package.json this should be plenty of time.
|
// For a small file like package.json this should be plenty of time.
|
||||||
@ -235,7 +237,7 @@ export class ExtensionDiscovery {
|
|||||||
/**
|
/**
|
||||||
* Uninstalls extension.
|
* Uninstalls extension.
|
||||||
* The application will detect the folder unlink and remove the extension from the UI automatically.
|
* The application will detect the folder unlink and remove the extension from the UI automatically.
|
||||||
* @param extension Extension to unistall.
|
* @param extension Extension to uninstall.
|
||||||
*/
|
*/
|
||||||
async uninstallExtension({ absolutePath, manifest }: InstalledExtension) {
|
async uninstallExtension({ absolutePath, manifest }: InstalledExtension) {
|
||||||
logger.info(`${logModule} Uninstalling ${manifest.name}`);
|
logger.info(`${logModule} Uninstalling ${manifest.name}`);
|
||||||
@ -325,7 +327,7 @@ export class ExtensionDiscovery {
|
|||||||
manifestJson = __non_webpack_require__(manifestPath);
|
manifestJson = __non_webpack_require__(manifestPath);
|
||||||
const installedManifestPath = this.getInstalledManifestPath(manifestJson.name);
|
const installedManifestPath = this.getInstalledManifestPath(manifestJson.name);
|
||||||
|
|
||||||
const isEnabled = isBundled || extensionsStore.isEnabled(installedManifestPath);
|
const isEnabled = isBundled || ExtensionsStore.getInstance().isEnabled(installedManifestPath);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: installedManifestPath,
|
id: installedManifestPath,
|
||||||
@ -455,5 +457,3 @@ export class ExtensionDiscovery {
|
|||||||
broadcastMessage(ExtensionDiscovery.extensionDiscoveryChannel, this.toJSON());
|
broadcastMessage(ExtensionDiscovery.extensionDiscoveryChannel, this.toJSON());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const extensionDiscovery = new ExtensionDiscovery();
|
|
||||||
|
|||||||
@ -5,9 +5,10 @@ import { action, computed, observable, reaction, toJS, when } from "mobx";
|
|||||||
import path from "path";
|
import path from "path";
|
||||||
import { getHostedCluster } from "../common/cluster-store";
|
import { getHostedCluster } from "../common/cluster-store";
|
||||||
import { broadcastMessage, handleRequest, requestMain, subscribeToBroadcast } from "../common/ipc";
|
import { broadcastMessage, handleRequest, requestMain, subscribeToBroadcast } from "../common/ipc";
|
||||||
|
import { Singleton } from "../common/utils";
|
||||||
import logger from "../main/logger";
|
import logger from "../main/logger";
|
||||||
import type { InstalledExtension } from "./extension-discovery";
|
import type { InstalledExtension } from "./extension-discovery";
|
||||||
import { extensionsStore } from "./extensions-store";
|
import { ExtensionsStore } from "./extensions-store";
|
||||||
import type { LensExtension, LensExtensionConstructor, LensExtensionId } from "./lens-extension";
|
import type { LensExtension, LensExtensionConstructor, LensExtensionId } from "./lens-extension";
|
||||||
import type { LensMainExtension } from "./lens-main-extension";
|
import type { LensMainExtension } from "./lens-main-extension";
|
||||||
import type { LensRendererExtension } from "./lens-renderer-extension";
|
import type { LensRendererExtension } from "./lens-renderer-extension";
|
||||||
@ -24,7 +25,7 @@ const logModule = "[EXTENSIONS-LOADER]";
|
|||||||
/**
|
/**
|
||||||
* Loads installed extensions to the Lens application
|
* Loads installed extensions to the Lens application
|
||||||
*/
|
*/
|
||||||
export class ExtensionLoader {
|
export class ExtensionLoader extends Singleton {
|
||||||
protected extensions = observable.map<LensExtensionId, InstalledExtension>();
|
protected extensions = observable.map<LensExtensionId, InstalledExtension>();
|
||||||
protected instances = observable.map<LensExtensionId, LensExtension>();
|
protected instances = observable.map<LensExtensionId, LensExtension>();
|
||||||
|
|
||||||
@ -95,11 +96,11 @@ export class ExtensionLoader {
|
|||||||
await this.initMain();
|
await this.initMain();
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all([this.whenLoaded, extensionsStore.whenLoaded]);
|
await Promise.all([this.whenLoaded, ExtensionsStore.getInstance().whenLoaded]);
|
||||||
|
|
||||||
// save state on change `extension.isEnabled`
|
// save state on change `extension.isEnabled`
|
||||||
reaction(() => this.storeState, extensionsState => {
|
reaction(() => this.storeState, extensionsState => {
|
||||||
extensionsStore.mergeState(extensionsState);
|
ExtensionsStore.getInstance().mergeState(extensionsState);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -329,5 +330,3 @@ export class ExtensionLoader {
|
|||||||
broadcastMessage(main ? ExtensionLoader.extensionsMainChannel : ExtensionLoader.extensionsRendererChannel, Array.from(this.toJSON()));
|
broadcastMessage(main ? ExtensionLoader.extensionsMainChannel : ExtensionLoader.extensionsRendererChannel, Array.from(this.toJSON()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const extensionLoader = new ExtensionLoader();
|
|
||||||
|
|||||||
@ -53,5 +53,3 @@ export class ExtensionsStore extends BaseStore<LensExtensionsStoreModel> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const extensionsStore = new ExtensionsStore();
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { InstalledExtension } from "./extension-discovery";
|
import type { InstalledExtension } from "./extension-discovery";
|
||||||
import { action, observable, reaction } from "mobx";
|
import { action, observable, reaction } from "mobx";
|
||||||
import { filesystemProvisionerStore } from "../main/extension-filesystem";
|
import { FilesystemProvisionerStore } from "../main/extension-filesystem";
|
||||||
import logger from "../main/logger";
|
import logger from "../main/logger";
|
||||||
import { ProtocolHandlerRegistration } from "./registries/protocol-handler-registry";
|
import { ProtocolHandlerRegistration } from "./registries/protocol-handler-registry";
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ export class LensExtension {
|
|||||||
* folder name.
|
* folder name.
|
||||||
*/
|
*/
|
||||||
async getExtensionFileFolder(): Promise<string> {
|
async getExtensionFileFolder(): Promise<string> {
|
||||||
return filesystemProvisionerStore.requestDirectory(this.id);
|
return FilesystemProvisionerStore.getInstance().requestDirectory(this.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
get description() {
|
get description() {
|
||||||
|
|||||||
@ -10,7 +10,7 @@ export class LensMainExtension extends LensExtension {
|
|||||||
appMenus: MenuRegistration[] = [];
|
appMenus: MenuRegistration[] = [];
|
||||||
|
|
||||||
async navigate<P extends object>(pageId?: string, params?: P, frameId?: number) {
|
async navigate<P extends object>(pageId?: string, params?: P, frameId?: number) {
|
||||||
const windowManager = WindowManager.getInstance<WindowManager>();
|
const windowManager = WindowManager.getInstance();
|
||||||
const pageUrl = getExtensionPageUrl({
|
const pageUrl = getExtensionPageUrl({
|
||||||
extensionId: this.name,
|
extensionId: this.name,
|
||||||
pageId,
|
pageId,
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
import { getExtensionPageUrl, globalPageRegistry, PageParams } from "../page-registry";
|
import { getExtensionPageUrl, globalPageRegistry, PageParams } from "../page-registry";
|
||||||
import { LensExtension } from "../../lens-extension";
|
import { LensExtension } from "../../lens-extension";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { Console } from "console";
|
||||||
|
import { stdout, stderr } from "process";
|
||||||
|
|
||||||
|
console = new Console(stdout, stderr);
|
||||||
|
|
||||||
let ext: LensExtension = null;
|
let ext: LensExtension = null;
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { themeStore } from "../../renderer/theme.store";
|
import { ThemeStore } from "../../renderer/theme.store";
|
||||||
|
|
||||||
export function getActiveTheme() {
|
export function getActiveTheme() {
|
||||||
return themeStore.activeTheme;
|
return ThemeStore.getInstance().activeTheme;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,7 +23,6 @@ jest.mock("winston", () => ({
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
jest.mock("../../common/ipc");
|
jest.mock("../../common/ipc");
|
||||||
jest.mock("../context-handler");
|
jest.mock("../context-handler");
|
||||||
jest.mock("request");
|
jest.mock("request");
|
||||||
|
|||||||
@ -36,6 +36,11 @@ import { bundledKubectlPath, Kubectl } from "../kubectl";
|
|||||||
import { mock, MockProxy } from "jest-mock-extended";
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
import { waitUntilUsed } from "tcp-port-used";
|
import { waitUntilUsed } from "tcp-port-used";
|
||||||
import { Readable } from "stream";
|
import { Readable } from "stream";
|
||||||
|
import { UserStore } from "../../common/user-store";
|
||||||
|
import { Console } from "console";
|
||||||
|
import { stdout, stderr } from "process";
|
||||||
|
|
||||||
|
console = new Console(stdout, stderr);
|
||||||
|
|
||||||
const mockBroadcastIpc = broadcastMessage as jest.MockedFunction<typeof broadcastMessage>;
|
const mockBroadcastIpc = broadcastMessage as jest.MockedFunction<typeof broadcastMessage>;
|
||||||
const mockSpawn = spawn as jest.MockedFunction<typeof spawn>;
|
const mockSpawn = spawn as jest.MockedFunction<typeof spawn>;
|
||||||
@ -44,6 +49,8 @@ const mockWaitUntilUsed = waitUntilUsed as jest.MockedFunction<typeof waitUntilU
|
|||||||
describe("kube auth proxy tests", () => {
|
describe("kube auth proxy tests", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
UserStore.resetInstance();
|
||||||
|
UserStore.getInstanceOrCreate();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("calling exit multiple times shouldn't throw", async () => {
|
it("calling exit multiple times shouldn't throw", async () => {
|
||||||
|
|||||||
@ -36,10 +36,6 @@ import * as path from "path";
|
|||||||
console = new Console(process.stdout, process.stderr); // fix mockFS
|
console = new Console(process.stdout, process.stderr); // fix mockFS
|
||||||
|
|
||||||
describe("kubeconfig manager tests", () => {
|
describe("kubeconfig manager tests", () => {
|
||||||
beforeEach(() => {
|
|
||||||
jest.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const mockOpts = {
|
const mockOpts = {
|
||||||
"minikube-config.yml": JSON.stringify({
|
"minikube-config.yml": JSON.stringify({
|
||||||
@ -76,7 +72,7 @@ describe("kubeconfig manager tests", () => {
|
|||||||
const cluster = new Cluster({
|
const cluster = new Cluster({
|
||||||
id: "foo",
|
id: "foo",
|
||||||
contextName: "minikube",
|
contextName: "minikube",
|
||||||
kubeConfigPath: "minikube-config.yml"
|
kubeConfigPath: "minikube-config.yml",
|
||||||
});
|
});
|
||||||
const contextHandler = new ContextHandler(cluster);
|
const contextHandler = new ContextHandler(cluster);
|
||||||
const port = await getFreePort();
|
const port = await getFreePort();
|
||||||
@ -98,7 +94,7 @@ describe("kubeconfig manager tests", () => {
|
|||||||
const cluster = new Cluster({
|
const cluster = new Cluster({
|
||||||
id: "foo",
|
id: "foo",
|
||||||
contextName: "minikube",
|
contextName: "minikube",
|
||||||
kubeConfigPath: "minikube-config.yml"
|
kubeConfigPath: "minikube-config.yml",
|
||||||
});
|
});
|
||||||
const contextHandler = new ContextHandler(cluster);
|
const contextHandler = new ContextHandler(cluster);
|
||||||
const port = await getFreePort();
|
const port = await getFreePort();
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import "../common/cluster-ipc";
|
|||||||
import type http from "http";
|
import type http from "http";
|
||||||
import { ipcMain } from "electron";
|
import { ipcMain } from "electron";
|
||||||
import { action, autorun, observable, reaction, toJS } from "mobx";
|
import { action, autorun, observable, reaction, toJS } from "mobx";
|
||||||
import { clusterStore, getClusterIdFromHost } from "../common/cluster-store";
|
import { ClusterStore, getClusterIdFromHost } from "../common/cluster-store";
|
||||||
import { Cluster } from "./cluster";
|
import { Cluster } from "./cluster";
|
||||||
import logger from "./logger";
|
import logger from "./logger";
|
||||||
import { apiKubePrefix } from "../common/vars";
|
import { apiKubePrefix } from "../common/vars";
|
||||||
@ -21,7 +21,7 @@ export class ClusterManager extends Singleton {
|
|||||||
|
|
||||||
catalogEntityRegistry.addSource("lens:kubernetes-clusters", this.catalogSource);
|
catalogEntityRegistry.addSource("lens:kubernetes-clusters", this.catalogSource);
|
||||||
// auto-init clusters
|
// auto-init clusters
|
||||||
reaction(() => clusterStore.enabledClustersList, (clusters) => {
|
reaction(() => ClusterStore.getInstance().enabledClustersList, (clusters) => {
|
||||||
clusters.forEach((cluster) => {
|
clusters.forEach((cluster) => {
|
||||||
if (!cluster.initialized && !cluster.initializing) {
|
if (!cluster.initialized && !cluster.initializing) {
|
||||||
logger.info(`[CLUSTER-MANAGER]: init cluster`, cluster.getMeta());
|
logger.info(`[CLUSTER-MANAGER]: init cluster`, cluster.getMeta());
|
||||||
@ -31,8 +31,8 @@ export class ClusterManager extends Singleton {
|
|||||||
|
|
||||||
}, { fireImmediately: true });
|
}, { fireImmediately: true });
|
||||||
|
|
||||||
reaction(() => toJS(clusterStore.enabledClustersList, { recurseEverything: true }), () => {
|
reaction(() => toJS(ClusterStore.getInstance().enabledClustersList, { recurseEverything: true }), () => {
|
||||||
this.updateCatalogSource(clusterStore.enabledClustersList);
|
this.updateCatalogSource(ClusterStore.getInstance().enabledClustersList);
|
||||||
}, { fireImmediately: true });
|
}, { fireImmediately: true });
|
||||||
|
|
||||||
reaction(() => catalogEntityRegistry.getItemsForApiKind<KubernetesCluster>("entity.k8slens.dev/v1alpha1", "KubernetesCluster"), (entities) => {
|
reaction(() => catalogEntityRegistry.getItemsForApiKind<KubernetesCluster>("entity.k8slens.dev/v1alpha1", "KubernetesCluster"), (entities) => {
|
||||||
@ -42,14 +42,14 @@ export class ClusterManager extends Singleton {
|
|||||||
|
|
||||||
// auto-stop removed clusters
|
// auto-stop removed clusters
|
||||||
autorun(() => {
|
autorun(() => {
|
||||||
const removedClusters = Array.from(clusterStore.removedClusters.values());
|
const removedClusters = Array.from(ClusterStore.getInstance().removedClusters.values());
|
||||||
|
|
||||||
if (removedClusters.length > 0) {
|
if (removedClusters.length > 0) {
|
||||||
const meta = removedClusters.map(cluster => cluster.getMeta());
|
const meta = removedClusters.map(cluster => cluster.getMeta());
|
||||||
|
|
||||||
logger.info(`[CLUSTER-MANAGER]: removing clusters`, meta);
|
logger.info(`[CLUSTER-MANAGER]: removing clusters`, meta);
|
||||||
removedClusters.forEach(cluster => cluster.disconnect());
|
removedClusters.forEach(cluster => cluster.disconnect());
|
||||||
clusterStore.removedClusters.clear();
|
ClusterStore.getInstance().removedClusters.clear();
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
delay: 250
|
delay: 250
|
||||||
@ -90,10 +90,10 @@ export class ClusterManager extends Singleton {
|
|||||||
|
|
||||||
@action syncClustersFromCatalog(entities: KubernetesCluster[]) {
|
@action syncClustersFromCatalog(entities: KubernetesCluster[]) {
|
||||||
entities.filter((entity) => entity.metadata.source !== "local").forEach((entity: KubernetesCluster) => {
|
entities.filter((entity) => entity.metadata.source !== "local").forEach((entity: KubernetesCluster) => {
|
||||||
const cluster = clusterStore.getById(entity.metadata.uid);
|
const cluster = ClusterStore.getInstance().getById(entity.metadata.uid);
|
||||||
|
|
||||||
if (!cluster) {
|
if (!cluster) {
|
||||||
clusterStore.addCluster({
|
ClusterStore.getInstance().addCluster({
|
||||||
id: entity.metadata.uid,
|
id: entity.metadata.uid,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
ownerRef: clusterOwnerRef,
|
ownerRef: clusterOwnerRef,
|
||||||
@ -145,7 +145,7 @@ export class ClusterManager extends Singleton {
|
|||||||
|
|
||||||
protected onNetworkOffline() {
|
protected onNetworkOffline() {
|
||||||
logger.info("[CLUSTER-MANAGER]: network is offline");
|
logger.info("[CLUSTER-MANAGER]: network is offline");
|
||||||
clusterStore.enabledClustersList.forEach((cluster) => {
|
ClusterStore.getInstance().enabledClustersList.forEach((cluster) => {
|
||||||
if (!cluster.disconnected) {
|
if (!cluster.disconnected) {
|
||||||
cluster.online = false;
|
cluster.online = false;
|
||||||
cluster.accessible = false;
|
cluster.accessible = false;
|
||||||
@ -156,7 +156,7 @@ export class ClusterManager extends Singleton {
|
|||||||
|
|
||||||
protected onNetworkOnline() {
|
protected onNetworkOnline() {
|
||||||
logger.info("[CLUSTER-MANAGER]: network is online");
|
logger.info("[CLUSTER-MANAGER]: network is online");
|
||||||
clusterStore.enabledClustersList.forEach((cluster) => {
|
ClusterStore.getInstance().enabledClustersList.forEach((cluster) => {
|
||||||
if (!cluster.disconnected) {
|
if (!cluster.disconnected) {
|
||||||
cluster.refreshConnectionStatus().catch((e) => e);
|
cluster.refreshConnectionStatus().catch((e) => e);
|
||||||
}
|
}
|
||||||
@ -164,7 +164,7 @@ export class ClusterManager extends Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
clusterStore.clusters.forEach((cluster: Cluster) => {
|
ClusterStore.getInstance().clusters.forEach((cluster: Cluster) => {
|
||||||
cluster.disconnect();
|
cluster.disconnect();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -176,18 +176,18 @@ export class ClusterManager extends Singleton {
|
|||||||
if (req.headers.host.startsWith("127.0.0.1")) {
|
if (req.headers.host.startsWith("127.0.0.1")) {
|
||||||
const clusterId = req.url.split("/")[1];
|
const clusterId = req.url.split("/")[1];
|
||||||
|
|
||||||
cluster = clusterStore.getById(clusterId);
|
cluster = ClusterStore.getInstance().getById(clusterId);
|
||||||
|
|
||||||
if (cluster) {
|
if (cluster) {
|
||||||
// we need to swap path prefix so that request is proxied to kube api
|
// we need to swap path prefix so that request is proxied to kube api
|
||||||
req.url = req.url.replace(`/${clusterId}`, apiKubePrefix);
|
req.url = req.url.replace(`/${clusterId}`, apiKubePrefix);
|
||||||
}
|
}
|
||||||
} else if (req.headers["x-cluster-id"]) {
|
} else if (req.headers["x-cluster-id"]) {
|
||||||
cluster = clusterStore.getById(req.headers["x-cluster-id"].toString());
|
cluster = ClusterStore.getInstance().getById(req.headers["x-cluster-id"].toString());
|
||||||
} else {
|
} else {
|
||||||
const clusterId = getClusterIdFromHost(req.headers.host);
|
const clusterId = getClusterIdFromHost(req.headers.host);
|
||||||
|
|
||||||
cluster = clusterStore.getById(clusterId);
|
cluster = ClusterStore.getInstance().getById(clusterId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return cluster;
|
return cluster;
|
||||||
|
|||||||
@ -4,11 +4,12 @@ import logger from "./logger";
|
|||||||
* Installs Electron developer tools in the development build.
|
* Installs Electron developer tools in the development build.
|
||||||
* The dependency is not bundled to the production build.
|
* The dependency is not bundled to the production build.
|
||||||
*/
|
*/
|
||||||
export const installDeveloperTools = async () => {
|
export const installDeveloperTools = () => {
|
||||||
if (process.env.NODE_ENV === "development") {
|
if (process.env.NODE_ENV === "development") {
|
||||||
logger.info("🤓 Installing developer tools");
|
logger.info("🤓 Installing developer tools");
|
||||||
const { default: devToolsInstaller, REACT_DEVELOPER_TOOLS } = await import("electron-devtools-installer");
|
import("electron-devtools-installer")
|
||||||
|
.then(({ default: devToolsInstaller, REACT_DEVELOPER_TOOLS }) => devToolsInstaller([REACT_DEVELOPER_TOOLS]))
|
||||||
return devToolsInstaller([REACT_DEVELOPER_TOOLS]);
|
.then((name) => logger.info(`[DEVTOOLS-INSTALLER]: installed ${name}`))
|
||||||
|
.catch(error => logger.error(`[DEVTOOLS-INSTALLER]: failed`, { error }));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,10 +4,14 @@ import { appEventBus } from "../common/event-bus";
|
|||||||
import { ClusterManager } from "./cluster-manager";
|
import { ClusterManager } from "./cluster-manager";
|
||||||
import logger from "./logger";
|
import logger from "./logger";
|
||||||
|
|
||||||
|
|
||||||
export function exitApp() {
|
export function exitApp() {
|
||||||
const windowManager = WindowManager.getInstance<WindowManager>();
|
console.log("before windowManager");
|
||||||
const clusterManager = ClusterManager.getInstance<ClusterManager>();
|
const windowManager = WindowManager.getInstance(false);
|
||||||
|
|
||||||
|
console.log("before clusterManager");
|
||||||
|
const clusterManager = ClusterManager.getInstance(false);
|
||||||
|
|
||||||
|
console.log("after clusterManager");
|
||||||
|
|
||||||
appEventBus.emit({ name: "service", action: "close" });
|
appEventBus.emit({ name: "service", action: "close" });
|
||||||
windowManager?.hide();
|
windowManager?.hide();
|
||||||
|
|||||||
@ -14,7 +14,7 @@ interface FSProvisionModel {
|
|||||||
export class FilesystemProvisionerStore extends BaseStore<FSProvisionModel> {
|
export class FilesystemProvisionerStore extends BaseStore<FSProvisionModel> {
|
||||||
@observable registeredExtensions = observable.map<LensExtensionId, string>();
|
@observable registeredExtensions = observable.map<LensExtensionId, string>();
|
||||||
|
|
||||||
private constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
configName: "lens-filesystem-provisioner-store",
|
configName: "lens-filesystem-provisioner-store",
|
||||||
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
||||||
@ -56,5 +56,3 @@ export class FilesystemProvisionerStore extends BaseStore<FSProvisionModel> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const filesystemProvisionerStore = FilesystemProvisionerStore.getInstance<FilesystemProvisionerStore>();
|
|
||||||
|
|||||||
@ -1,17 +1,24 @@
|
|||||||
import { helmService } from "../helm-service";
|
import { helmService } from "../helm-service";
|
||||||
import { repoManager } from "../helm-repo-manager";
|
import { HelmRepoManager } from "../helm-repo-manager";
|
||||||
|
|
||||||
jest.spyOn(repoManager, "init").mockImplementation();
|
const mockHelmRepoManager = jest.spyOn(HelmRepoManager, "getInstance").mockImplementation();
|
||||||
|
|
||||||
jest.mock("../helm-chart-manager");
|
jest.mock("../helm-chart-manager");
|
||||||
|
|
||||||
describe("Helm Service tests", () => {
|
describe("Helm Service tests", () => {
|
||||||
test("list charts without deprecated ones", async () => {
|
afterEach(() => {
|
||||||
jest.spyOn(repoManager, "repositories").mockImplementation(async () => {
|
jest.resetAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("list charts without deprecated ones", async () => {
|
||||||
|
mockHelmRepoManager.mockReturnValue({
|
||||||
|
init: jest.fn(),
|
||||||
|
repositories: jest.fn().mockImplementation(async () => {
|
||||||
return [
|
return [
|
||||||
{ name: "stable", url: "stableurl" },
|
{ name: "stable", url: "stableurl" },
|
||||||
{ name: "experiment", url: "experimenturl" }
|
{ name: "experiment", url: "experimenturl" },
|
||||||
];
|
];
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const charts = await helmService.listCharts();
|
const charts = await helmService.listCharts();
|
||||||
@ -55,11 +62,14 @@ describe("Helm Service tests", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("list charts sorted by version in descending order", async () => {
|
it("list charts sorted by version in descending order", async () => {
|
||||||
jest.spyOn(repoManager, "repositories").mockImplementation(async () => {
|
mockHelmRepoManager.mockReturnValue({
|
||||||
|
init: jest.fn(),
|
||||||
|
repositories: jest.fn().mockImplementation(async () => {
|
||||||
return [
|
return [
|
||||||
{ name: "bitnami", url: "bitnamiurl" }
|
{ name: "bitnami", url: "bitnamiurl" },
|
||||||
];
|
];
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const charts = await helmService.listCharts();
|
const charts = await helmService.listCharts();
|
||||||
|
|||||||
@ -1,17 +1,17 @@
|
|||||||
import * as tempy from "tempy";
|
import * as tempy from "tempy";
|
||||||
import fs from "fs";
|
import fse from "fs-extra";
|
||||||
import * as yaml from "js-yaml";
|
import * as yaml from "js-yaml";
|
||||||
import { promiseExec} from "../promise-exec";
|
import { promiseExec} from "../promise-exec";
|
||||||
import { helmCli } from "./helm-cli";
|
import { helmCli } from "./helm-cli";
|
||||||
import { Cluster } from "../cluster";
|
import { Cluster } from "../cluster";
|
||||||
import { toCamelCase } from "../../common/utils/camelCase";
|
import { toCamelCase } from "../../common/utils/camelCase";
|
||||||
|
|
||||||
export class HelmReleaseManager {
|
export async function listReleases(pathToKubeconfig: string, namespace?: string) {
|
||||||
|
|
||||||
public async listReleases(pathToKubeconfig: string, namespace?: string) {
|
|
||||||
const helm = await helmCli.binaryPath();
|
const helm = await helmCli.binaryPath();
|
||||||
const namespaceFlag = namespace ? `-n ${namespace}` : "--all-namespaces";
|
const namespaceFlag = namespace ? `-n ${namespace}` : "--all-namespaces";
|
||||||
const { stdout } = await promiseExec(`"${helm}" ls --output json ${namespaceFlag} --kubeconfig ${pathToKubeconfig}`).catch((error) => { throw(error.stderr);});
|
|
||||||
|
try {
|
||||||
|
const { stdout } = await promiseExec(`"${helm}" ls --output json ${namespaceFlag} --kubeconfig ${pathToKubeconfig}`);
|
||||||
const output = JSON.parse(stdout);
|
const output = JSON.parse(stdout);
|
||||||
|
|
||||||
if (output.length == 0) {
|
if (output.length == 0) {
|
||||||
@ -22,14 +22,17 @@ export class HelmReleaseManager {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
|
} catch ({ stderr }) {
|
||||||
|
throw stderr;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public async installChart(chart: string, values: any, name: string, namespace: string, version: string, pathToKubeconfig: string){
|
export async function installChart(chart: string, values: any, name: string | undefined, namespace: string, version: string, pathToKubeconfig: string){
|
||||||
const helm = await helmCli.binaryPath();
|
const helm = await helmCli.binaryPath();
|
||||||
const fileName = tempy.file({name: "values.yaml"});
|
const fileName = tempy.file({name: "values.yaml"});
|
||||||
|
|
||||||
await fs.promises.writeFile(fileName, yaml.safeDump(values));
|
await fse.writeFile(fileName, yaml.safeDump(values));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let generateName = "";
|
let generateName = "";
|
||||||
@ -38,7 +41,7 @@ export class HelmReleaseManager {
|
|||||||
generateName = "--generate-name";
|
generateName = "--generate-name";
|
||||||
name = "";
|
name = "";
|
||||||
}
|
}
|
||||||
const { stdout } = await promiseExec(`"${helm}" install ${name} ${chart} --version ${version} -f ${fileName} --namespace ${namespace} --kubeconfig ${pathToKubeconfig} ${generateName}`).catch((error) => { throw(error.stderr);});
|
const { stdout } = await promiseExec(`"${helm}" install ${name} ${chart} --version ${version} -f ${fileName} --namespace ${namespace} --kubeconfig ${pathToKubeconfig} ${generateName}`);
|
||||||
const releaseName = stdout.split("\n")[0].split(" ")[1].trim();
|
const releaseName = stdout.split("\n")[0].split(" ")[1].trim();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -48,80 +51,103 @@ export class HelmReleaseManager {
|
|||||||
namespace
|
namespace
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
} catch ({ stderr }) {
|
||||||
|
throw stderr;
|
||||||
} finally {
|
} finally {
|
||||||
await fs.promises.unlink(fileName);
|
await fse.unlink(fileName);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async upgradeRelease(name: string, chart: string, values: any, namespace: string, version: string, cluster: Cluster){
|
|
||||||
const helm = await helmCli.binaryPath();
|
|
||||||
const fileName = tempy.file({name: "values.yaml"});
|
|
||||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
|
||||||
|
|
||||||
await fs.promises.writeFile(fileName, yaml.safeDump(values));
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { stdout } = await promiseExec(`"${helm}" upgrade ${name} ${chart} --version ${version} -f ${fileName} --namespace ${namespace} --kubeconfig ${proxyKubeconfig}`).catch((error) => { throw(error.stderr);});
|
|
||||||
|
|
||||||
return {
|
|
||||||
log: stdout,
|
|
||||||
release: this.getRelease(name, namespace, cluster)
|
|
||||||
};
|
|
||||||
} finally {
|
|
||||||
await fs.promises.unlink(fileName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getRelease(name: string, namespace: string, cluster: Cluster) {
|
|
||||||
const helm = await helmCli.binaryPath();
|
|
||||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
|
||||||
|
|
||||||
const { stdout } = await promiseExec(`"${helm}" status ${name} --output json --namespace ${namespace} --kubeconfig ${proxyKubeconfig}`).catch((error) => { throw(error.stderr);});
|
|
||||||
const release = JSON.parse(stdout);
|
|
||||||
|
|
||||||
release.resources = await this.getResources(name, namespace, cluster);
|
|
||||||
|
|
||||||
return release;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async deleteRelease(name: string, namespace: string, pathToKubeconfig: string) {
|
|
||||||
const helm = await helmCli.binaryPath();
|
|
||||||
const { stdout } = await promiseExec(`"${helm}" delete ${name} --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`).catch((error) => { throw(error.stderr);});
|
|
||||||
|
|
||||||
return stdout;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getValues(name: string, namespace: string, all: boolean, pathToKubeconfig: string) {
|
|
||||||
const helm = await helmCli.binaryPath();
|
|
||||||
const { stdout, } = await promiseExec(`"${helm}" get values ${name} ${all? "--all": ""} --output yaml --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`).catch((error) => { throw(error.stderr);});
|
|
||||||
|
|
||||||
return stdout;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getHistory(name: string, namespace: string, pathToKubeconfig: string) {
|
|
||||||
const helm = await helmCli.binaryPath();
|
|
||||||
const { stdout } = await promiseExec(`"${helm}" history ${name} --output json --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`).catch((error) => { throw(error.stderr);});
|
|
||||||
|
|
||||||
return JSON.parse(stdout);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async rollback(name: string, namespace: string, revision: number, pathToKubeconfig: string) {
|
|
||||||
const helm = await helmCli.binaryPath();
|
|
||||||
const { stdout } = await promiseExec(`"${helm}" rollback ${name} ${revision} --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`).catch((error) => { throw(error.stderr);});
|
|
||||||
|
|
||||||
return stdout;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async getResources(name: string, namespace: string, cluster: Cluster) {
|
|
||||||
const helm = await helmCli.binaryPath();
|
|
||||||
const kubectl = await cluster.kubeCtl.getPath();
|
|
||||||
const pathToKubeconfig = await cluster.getProxyKubeconfigPath();
|
|
||||||
const { stdout } = await promiseExec(`"${helm}" get manifest ${name} --namespace ${namespace} --kubeconfig ${pathToKubeconfig} | "${kubectl}" get -n ${namespace} --kubeconfig ${pathToKubeconfig} -f - -o=json`).catch(() => {
|
|
||||||
return { stdout: JSON.stringify({items: []})};
|
|
||||||
});
|
|
||||||
|
|
||||||
return stdout;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const releaseManager = new HelmReleaseManager();
|
export async function upgradeRelease(name: string, chart: string, values: any, namespace: string, version: string, cluster: Cluster) {
|
||||||
|
const helm = await helmCli.binaryPath();
|
||||||
|
const fileName = tempy.file({name: "values.yaml"});
|
||||||
|
|
||||||
|
await fse.writeFile(fileName, yaml.safeDump(values));
|
||||||
|
|
||||||
|
try {
|
||||||
|
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||||
|
const { stdout } = await promiseExec(`"${helm}" upgrade ${name} ${chart} --version ${version} -f ${fileName} --namespace ${namespace} --kubeconfig ${proxyKubeconfig}`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
log: stdout,
|
||||||
|
release: getRelease(name, namespace, cluster)
|
||||||
|
};
|
||||||
|
} catch ({ stderr }) {
|
||||||
|
throw stderr;
|
||||||
|
} finally {
|
||||||
|
await fse.unlink(fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getRelease(name: string, namespace: string, cluster: Cluster) {
|
||||||
|
try {
|
||||||
|
const helm = await helmCli.binaryPath();
|
||||||
|
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||||
|
|
||||||
|
const { stdout } = await promiseExec(`"${helm}" status ${name} --output json --namespace ${namespace} --kubeconfig ${proxyKubeconfig}`);
|
||||||
|
const release = JSON.parse(stdout);
|
||||||
|
|
||||||
|
release.resources = await getResources(name, namespace, cluster);
|
||||||
|
|
||||||
|
return release;
|
||||||
|
} catch ({ stderr }) {
|
||||||
|
throw stderr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteRelease(name: string, namespace: string, pathToKubeconfig: string) {
|
||||||
|
try {
|
||||||
|
const helm = await helmCli.binaryPath();
|
||||||
|
const { stdout } = await promiseExec(`"${helm}" delete ${name} --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`);
|
||||||
|
|
||||||
|
return stdout;
|
||||||
|
} catch ({ stderr }) {
|
||||||
|
throw stderr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getValues(name: string, namespace: string, all: boolean, pathToKubeconfig: string) {
|
||||||
|
try {
|
||||||
|
const helm = await helmCli.binaryPath();
|
||||||
|
const { stdout, } = await promiseExec(`"${helm}" get values ${name} ${all ? "--all": ""} --output yaml --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`);
|
||||||
|
|
||||||
|
return stdout;
|
||||||
|
} catch ({ stderr }) {
|
||||||
|
throw stderr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getHistory(name: string, namespace: string, pathToKubeconfig: string) {
|
||||||
|
try {
|
||||||
|
const helm = await helmCli.binaryPath();
|
||||||
|
const { stdout } = await promiseExec(`"${helm}" history ${name} --output json --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`);
|
||||||
|
|
||||||
|
return JSON.parse(stdout);
|
||||||
|
} catch ({ stderr }) {
|
||||||
|
throw stderr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function rollback(name: string, namespace: string, revision: number, pathToKubeconfig: string) {
|
||||||
|
try {
|
||||||
|
const helm = await helmCli.binaryPath();
|
||||||
|
const { stdout } = await promiseExec(`"${helm}" rollback ${name} ${revision} --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`);
|
||||||
|
|
||||||
|
return stdout;
|
||||||
|
} catch ({ stderr }) {
|
||||||
|
throw stderr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getResources(name: string, namespace: string, cluster: Cluster) {
|
||||||
|
try {
|
||||||
|
const helm = await helmCli.binaryPath();
|
||||||
|
const kubectl = await cluster.kubeCtl.getPath();
|
||||||
|
const pathToKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||||
|
const { stdout } = await promiseExec(`"${helm}" get manifest ${name} --namespace ${namespace} --kubeconfig ${pathToKubeconfig} | "${kubectl}" get -n ${namespace} --kubeconfig ${pathToKubeconfig} -f - -o=json`);
|
||||||
|
|
||||||
|
return stdout;
|
||||||
|
} catch {
|
||||||
|
return { stdout: JSON.stringify({ items: [] }) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -77,10 +77,6 @@ export class HelmRepoManager extends Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async repositories(): Promise<HelmRepo[]> {
|
public async repositories(): Promise<HelmRepo[]> {
|
||||||
if (!this.initialized) {
|
|
||||||
await this.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const repoConfigFile = this.helmEnv.HELM_REPOSITORY_CONFIG;
|
const repoConfigFile = this.helmEnv.HELM_REPOSITORY_CONFIG;
|
||||||
const { repositories }: HelmRepoConfig = await readFile(repoConfigFile, "utf8")
|
const { repositories }: HelmRepoConfig = await readFile(repoConfigFile, "utf8")
|
||||||
@ -160,5 +156,3 @@ export class HelmRepoManager extends Singleton {
|
|||||||
return stdout;
|
return stdout;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const repoManager = HelmRepoManager.getInstance<HelmRepoManager>();
|
|
||||||
|
|||||||
@ -1,23 +1,21 @@
|
|||||||
import semver from "semver";
|
import semver from "semver";
|
||||||
import { Cluster } from "../cluster";
|
import { Cluster } from "../cluster";
|
||||||
import logger from "../logger";
|
import logger from "../logger";
|
||||||
import { repoManager } from "./helm-repo-manager";
|
import { HelmRepoManager } from "./helm-repo-manager";
|
||||||
import { HelmChartManager } from "./helm-chart-manager";
|
import { HelmChartManager } from "./helm-chart-manager";
|
||||||
import { releaseManager } from "./helm-release-manager";
|
|
||||||
import { HelmChartList, RepoHelmChartList } from "../../renderer/api/endpoints/helm-charts.api";
|
import { HelmChartList, RepoHelmChartList } from "../../renderer/api/endpoints/helm-charts.api";
|
||||||
|
import { deleteRelease, getHistory, getRelease, getValues, installChart, listReleases, rollback, upgradeRelease } from "./helm-release-manager";
|
||||||
|
|
||||||
class HelmService {
|
class HelmService {
|
||||||
public async installChart(cluster: Cluster, data: { chart: string; values: {}; name: string; namespace: string; version: string }) {
|
public async installChart(cluster: Cluster, data: { chart: string; values: {}; name: string; namespace: string; version: string }) {
|
||||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||||
|
|
||||||
return await releaseManager.installChart(data.chart, data.values, data.name, data.namespace, data.version, proxyKubeconfig);
|
return installChart(data.chart, data.values, data.name, data.namespace, data.version, proxyKubeconfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async listCharts() {
|
public async listCharts() {
|
||||||
const charts: HelmChartList = {};
|
const charts: HelmChartList = {};
|
||||||
|
const repositories = await HelmRepoManager.getInstance().repositories();
|
||||||
await repoManager.init();
|
|
||||||
const repositories = await repoManager.repositories();
|
|
||||||
|
|
||||||
for (const repo of repositories) {
|
for (const repo of repositories) {
|
||||||
charts[repo.name] = {};
|
charts[repo.name] = {};
|
||||||
@ -36,7 +34,7 @@ class HelmService {
|
|||||||
readme: "",
|
readme: "",
|
||||||
versions: {}
|
versions: {}
|
||||||
};
|
};
|
||||||
const repo = await repoManager.repository(repoName);
|
const repo = await HelmRepoManager.getInstance().repository(repoName);
|
||||||
const chartManager = new HelmChartManager(repo);
|
const chartManager = new HelmChartManager(repo);
|
||||||
const chart = await chartManager.chart(chartName);
|
const chart = await chartManager.chart(chartName);
|
||||||
|
|
||||||
@ -47,23 +45,22 @@ class HelmService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async getChartValues(repoName: string, chartName: string, version = "") {
|
public async getChartValues(repoName: string, chartName: string, version = "") {
|
||||||
const repo = await repoManager.repository(repoName);
|
const repo = await HelmRepoManager.getInstance().repository(repoName);
|
||||||
const chartManager = new HelmChartManager(repo);
|
const chartManager = new HelmChartManager(repo);
|
||||||
|
|
||||||
return chartManager.getValues(chartName, version);
|
return chartManager.getValues(chartName, version);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async listReleases(cluster: Cluster, namespace: string = null) {
|
public async listReleases(cluster: Cluster, namespace: string = null) {
|
||||||
await repoManager.init();
|
|
||||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||||
|
|
||||||
return await releaseManager.listReleases(proxyKubeconfig, namespace);
|
return listReleases(proxyKubeconfig, namespace);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getRelease(cluster: Cluster, releaseName: string, namespace: string) {
|
public async getRelease(cluster: Cluster, releaseName: string, namespace: string) {
|
||||||
logger.debug("Fetch release");
|
logger.debug("Fetch release");
|
||||||
|
|
||||||
return await releaseManager.getRelease(releaseName, namespace, cluster);
|
return getRelease(releaseName, namespace, cluster);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getReleaseValues(cluster: Cluster, releaseName: string, namespace: string, all: boolean) {
|
public async getReleaseValues(cluster: Cluster, releaseName: string, namespace: string, all: boolean) {
|
||||||
@ -71,7 +68,7 @@ class HelmService {
|
|||||||
|
|
||||||
logger.debug("Fetch release values");
|
logger.debug("Fetch release values");
|
||||||
|
|
||||||
return await releaseManager.getValues(releaseName, namespace, all, proxyKubeconfig);
|
return getValues(releaseName, namespace, all, proxyKubeconfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getReleaseHistory(cluster: Cluster, releaseName: string, namespace: string) {
|
public async getReleaseHistory(cluster: Cluster, releaseName: string, namespace: string) {
|
||||||
@ -79,7 +76,7 @@ class HelmService {
|
|||||||
|
|
||||||
logger.debug("Fetch release history");
|
logger.debug("Fetch release history");
|
||||||
|
|
||||||
return await releaseManager.getHistory(releaseName, namespace, proxyKubeconfig);
|
return getHistory(releaseName, namespace, proxyKubeconfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deleteRelease(cluster: Cluster, releaseName: string, namespace: string) {
|
public async deleteRelease(cluster: Cluster, releaseName: string, namespace: string) {
|
||||||
@ -87,20 +84,20 @@ class HelmService {
|
|||||||
|
|
||||||
logger.debug("Delete release");
|
logger.debug("Delete release");
|
||||||
|
|
||||||
return await releaseManager.deleteRelease(releaseName, namespace, proxyKubeconfig);
|
return deleteRelease(releaseName, namespace, proxyKubeconfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateRelease(cluster: Cluster, releaseName: string, namespace: string, data: { chart: string; values: {}; version: string }) {
|
public async updateRelease(cluster: Cluster, releaseName: string, namespace: string, data: { chart: string; values: {}; version: string }) {
|
||||||
logger.debug("Upgrade release");
|
logger.debug("Upgrade release");
|
||||||
|
|
||||||
return await releaseManager.upgradeRelease(releaseName, data.chart, data.values, namespace, data.version, cluster);
|
return upgradeRelease(releaseName, data.chart, data.values, namespace, data.version, cluster);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async rollback(cluster: Cluster, releaseName: string, namespace: string, revision: number) {
|
public async rollback(cluster: Cluster, releaseName: string, namespace: string, revision: number) {
|
||||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||||
|
|
||||||
logger.debug("Rollback release");
|
logger.debug("Rollback release");
|
||||||
const output = await releaseManager.rollback(releaseName, namespace, revision, proxyKubeconfig);
|
const output = rollback(releaseName, namespace, revision, proxyKubeconfig);
|
||||||
|
|
||||||
return { message: output };
|
return { message: output };
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,15 +15,15 @@ import { getFreePort } from "./port";
|
|||||||
import { mangleProxyEnv } from "./proxy-env";
|
import { mangleProxyEnv } from "./proxy-env";
|
||||||
import { registerFileProtocol } from "../common/register-protocol";
|
import { registerFileProtocol } from "../common/register-protocol";
|
||||||
import logger from "./logger";
|
import logger from "./logger";
|
||||||
import { clusterStore } from "../common/cluster-store";
|
import { ClusterStore } from "../common/cluster-store";
|
||||||
import { userStore } from "../common/user-store";
|
import { UserStore } from "../common/user-store";
|
||||||
import { appEventBus } from "../common/event-bus";
|
import { appEventBus } from "../common/event-bus";
|
||||||
import { extensionLoader } from "../extensions/extension-loader";
|
import { ExtensionLoader } from "../extensions/extension-loader";
|
||||||
import { extensionsStore } from "../extensions/extensions-store";
|
import { ExtensionsStore } from "../extensions/extensions-store";
|
||||||
import { InstalledExtension, extensionDiscovery } from "../extensions/extension-discovery";
|
import { InstalledExtension, ExtensionDiscovery } from "../extensions/extension-discovery";
|
||||||
import type { LensExtensionId } from "../extensions/lens-extension";
|
import type { LensExtensionId } from "../extensions/lens-extension";
|
||||||
|
import { FilesystemProvisionerStore } from "./extension-filesystem";
|
||||||
import { installDeveloperTools } from "./developer-tools";
|
import { installDeveloperTools } from "./developer-tools";
|
||||||
import { filesystemProvisionerStore } from "./extension-filesystem";
|
|
||||||
import { LensProtocolRouterMain } from "./protocol-handler";
|
import { LensProtocolRouterMain } from "./protocol-handler";
|
||||||
import { getAppVersion, getAppVersionFromProxyServer } from "../common/utils";
|
import { getAppVersion, getAppVersionFromProxyServer } from "../common/utils";
|
||||||
import { bindBroadcastHandlers } from "../common/ipc";
|
import { bindBroadcastHandlers } from "../common/ipc";
|
||||||
@ -31,13 +31,9 @@ import { startUpdateChecking } from "./app-updater";
|
|||||||
import { IpcRendererNavigationEvents } from "../renderer/navigation/events";
|
import { IpcRendererNavigationEvents } from "../renderer/navigation/events";
|
||||||
import { CatalogPusher } from "./catalog-pusher";
|
import { CatalogPusher } from "./catalog-pusher";
|
||||||
import { catalogEntityRegistry } from "../common/catalog-entity-registry";
|
import { catalogEntityRegistry } from "../common/catalog-entity-registry";
|
||||||
import { hotbarStore } from "../common/hotbar-store";
|
import { HotbarStore } from "../common/hotbar-store";
|
||||||
|
|
||||||
const workingDir = path.join(app.getPath("appData"), appName);
|
const workingDir = path.join(app.getPath("appData"), appName);
|
||||||
let proxyPort: number;
|
|
||||||
let proxyServer: LensProxy;
|
|
||||||
let clusterManager: ClusterManager;
|
|
||||||
let windowManager: WindowManager;
|
|
||||||
|
|
||||||
app.setName(appName);
|
app.setName(appName);
|
||||||
|
|
||||||
@ -66,7 +62,7 @@ if (app.commandLine.getSwitchValue("proxy-server") !== "") {
|
|||||||
if (!app.requestSingleInstanceLock()) {
|
if (!app.requestSingleInstanceLock()) {
|
||||||
app.exit();
|
app.exit();
|
||||||
} else {
|
} else {
|
||||||
const lprm = LensProtocolRouterMain.getInstance<LensProtocolRouterMain>();
|
const lprm = LensProtocolRouterMain.getInstanceOrCreate();
|
||||||
|
|
||||||
for (const arg of process.argv) {
|
for (const arg of process.argv) {
|
||||||
if (arg.toLowerCase().startsWith("lens://")) {
|
if (arg.toLowerCase().startsWith("lens://")) {
|
||||||
@ -77,7 +73,7 @@ if (!app.requestSingleInstanceLock()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
app.on("second-instance", (event, argv) => {
|
app.on("second-instance", (event, argv) => {
|
||||||
const lprm = LensProtocolRouterMain.getInstance<LensProtocolRouterMain>();
|
const lprm = LensProtocolRouterMain.getInstanceOrCreate();
|
||||||
|
|
||||||
for (const arg of argv) {
|
for (const arg of argv) {
|
||||||
if (arg.toLowerCase().startsWith("lens://")) {
|
if (arg.toLowerCase().startsWith("lens://")) {
|
||||||
@ -86,7 +82,7 @@ app.on("second-instance", (event, argv) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
windowManager?.ensureMainWindow();
|
WindowManager.getInstance(false)?.ensureMainWindow();
|
||||||
});
|
});
|
||||||
|
|
||||||
app.on("ready", async () => {
|
app.on("ready", async () => {
|
||||||
@ -102,7 +98,11 @@ app.on("ready", async () => {
|
|||||||
|
|
||||||
registerFileProtocol("static", __static);
|
registerFileProtocol("static", __static);
|
||||||
|
|
||||||
await installDeveloperTools();
|
const userStore = UserStore.getInstanceOrCreate();
|
||||||
|
const clusterStore = ClusterStore.getInstanceOrCreate();
|
||||||
|
const hotbarStore = HotbarStore.getInstanceOrCreate();
|
||||||
|
const extensionsStore = ExtensionsStore.getInstanceOrCreate();
|
||||||
|
const filesystemStore = FilesystemProvisionerStore.getInstanceOrCreate();
|
||||||
|
|
||||||
logger.info("💾 Loading stores");
|
logger.info("💾 Loading stores");
|
||||||
// preload
|
// preload
|
||||||
@ -111,10 +111,12 @@ app.on("ready", async () => {
|
|||||||
clusterStore.load(),
|
clusterStore.load(),
|
||||||
hotbarStore.load(),
|
hotbarStore.load(),
|
||||||
extensionsStore.load(),
|
extensionsStore.load(),
|
||||||
filesystemProvisionerStore.load(),
|
filesystemStore.load(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// find free port
|
// find free port
|
||||||
|
let proxyPort;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logger.info("🔑 Getting free port for LensProxy server");
|
logger.info("🔑 Getting free port for LensProxy server");
|
||||||
proxyPort = await getFreePort();
|
proxyPort = await getFreePort();
|
||||||
@ -125,13 +127,13 @@ app.on("ready", async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// create cluster manager
|
// create cluster manager
|
||||||
clusterManager = ClusterManager.getInstance<ClusterManager>(proxyPort);
|
ClusterManager.getInstanceOrCreate(proxyPort);
|
||||||
|
|
||||||
// run proxy
|
// run proxy
|
||||||
try {
|
try {
|
||||||
logger.info("🔌 Starting LensProxy");
|
logger.info("🔌 Starting LensProxy");
|
||||||
// eslint-disable-next-line unused-imports/no-unused-vars-ts
|
// eslint-disable-next-line unused-imports/no-unused-vars-ts
|
||||||
proxyServer = LensProxy.create(proxyPort, clusterManager);
|
LensProxy.getInstanceOrCreate(proxyPort).listen();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Could not start proxy (127.0.0:${proxyPort}): ${error?.message}`);
|
logger.error(`Could not start proxy (127.0.0:${proxyPort}): ${error?.message}`);
|
||||||
dialog.showErrorBox("Lens Error", `Could not start proxy (127.0.0:${proxyPort}): ${error?.message || "unknown error"}`);
|
dialog.showErrorBox("Lens Error", `Could not start proxy (127.0.0:${proxyPort}): ${error?.message || "unknown error"}`);
|
||||||
@ -151,7 +153,9 @@ app.on("ready", async () => {
|
|||||||
logger.error("Checking proxy server connection failed", error);
|
logger.error("Checking proxy server connection failed", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
extensionLoader.init();
|
const extensionDiscovery = ExtensionDiscovery.getInstanceOrCreate();
|
||||||
|
|
||||||
|
ExtensionLoader.getInstanceOrCreate().init();
|
||||||
extensionDiscovery.init();
|
extensionDiscovery.init();
|
||||||
|
|
||||||
// Start the app without showing the main window when auto starting on login
|
// Start the app without showing the main window when auto starting on login
|
||||||
@ -159,7 +163,9 @@ app.on("ready", async () => {
|
|||||||
const startHidden = process.argv.includes("--hidden") || (isMac && app.getLoginItemSettings().wasOpenedAsHidden);
|
const startHidden = process.argv.includes("--hidden") || (isMac && app.getLoginItemSettings().wasOpenedAsHidden);
|
||||||
|
|
||||||
logger.info("🖥️ Starting WindowManager");
|
logger.info("🖥️ Starting WindowManager");
|
||||||
windowManager = WindowManager.getInstance<WindowManager>(proxyPort);
|
const windowManager = WindowManager.getInstanceOrCreate(proxyPort);
|
||||||
|
|
||||||
|
installDeveloperTools();
|
||||||
|
|
||||||
if (!startHidden) {
|
if (!startHidden) {
|
||||||
windowManager.initMainWindow();
|
windowManager.initMainWindow();
|
||||||
@ -169,13 +175,13 @@ app.on("ready", async () => {
|
|||||||
CatalogPusher.init(catalogEntityRegistry);
|
CatalogPusher.init(catalogEntityRegistry);
|
||||||
startUpdateChecking();
|
startUpdateChecking();
|
||||||
LensProtocolRouterMain
|
LensProtocolRouterMain
|
||||||
.getInstance<LensProtocolRouterMain>()
|
.getInstance()
|
||||||
.rendererLoaded = true;
|
.rendererLoaded = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
extensionLoader.whenLoaded.then(() => {
|
ExtensionLoader.getInstance().whenLoaded.then(() => {
|
||||||
LensProtocolRouterMain
|
LensProtocolRouterMain
|
||||||
.getInstance<LensProtocolRouterMain>()
|
.getInstance()
|
||||||
.extensionsLoaded = true;
|
.extensionsLoaded = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -189,14 +195,15 @@ app.on("ready", async () => {
|
|||||||
extensionDiscovery.watchExtensions();
|
extensionDiscovery.watchExtensions();
|
||||||
|
|
||||||
// Subscribe to extensions that are copied or deleted to/from the extensions folder
|
// Subscribe to extensions that are copied or deleted to/from the extensions folder
|
||||||
extensionDiscovery.events.on("add", (extension: InstalledExtension) => {
|
extensionDiscovery.events
|
||||||
extensionLoader.addExtension(extension);
|
.on("add", (extension: InstalledExtension) => {
|
||||||
});
|
ExtensionLoader.getInstance().addExtension(extension);
|
||||||
extensionDiscovery.events.on("remove", (lensExtensionId: LensExtensionId) => {
|
})
|
||||||
extensionLoader.removeExtension(lensExtensionId);
|
.on("remove", (lensExtensionId: LensExtensionId) => {
|
||||||
|
ExtensionLoader.getInstance().removeExtension(lensExtensionId);
|
||||||
});
|
});
|
||||||
|
|
||||||
extensionLoader.initExtensions(extensions);
|
ExtensionLoader.getInstance().initExtensions(extensions);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
dialog.showErrorBox("Lens Error", `Could not load extensions${error?.message ? `: ${error.message}` : ""}`);
|
dialog.showErrorBox("Lens Error", `Could not load extensions${error?.message ? `: ${error.message}` : ""}`);
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@ -212,7 +219,7 @@ app.on("activate", (event, hasVisibleWindows) => {
|
|||||||
logger.info("APP:ACTIVATE", { hasVisibleWindows });
|
logger.info("APP:ACTIVATE", { hasVisibleWindows });
|
||||||
|
|
||||||
if (!hasVisibleWindows) {
|
if (!hasVisibleWindows) {
|
||||||
windowManager?.initMainWindow(false);
|
WindowManager.getInstance(false)?.initMainWindow(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -227,8 +234,7 @@ app.on("will-quit", (event) => {
|
|||||||
// Quit app on Cmd+Q (MacOS)
|
// Quit app on Cmd+Q (MacOS)
|
||||||
logger.info("APP:QUIT");
|
logger.info("APP:QUIT");
|
||||||
appEventBus.emit({name: "app", action: "close"});
|
appEventBus.emit({name: "app", action: "close"});
|
||||||
|
ClusterManager.getInstance(false)?.stop(); // close cluster connections
|
||||||
clusterManager?.stop(); // close cluster connections
|
|
||||||
|
|
||||||
if (blockQuit) {
|
if (blockQuit) {
|
||||||
event.preventDefault(); // prevent app's default shutdown (e.g. required for telemetry, etc.)
|
event.preventDefault(); // prevent app's default shutdown (e.g. required for telemetry, etc.)
|
||||||
@ -242,7 +248,7 @@ app.on("open-url", (event, rawUrl) => {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
LensProtocolRouterMain
|
LensProtocolRouterMain
|
||||||
.getInstance<LensProtocolRouterMain>()
|
.getInstance()
|
||||||
.route(rawUrl)
|
.route(rawUrl)
|
||||||
.catch(error => logger.error(`${LensProtocolRouterMain.LoggingPrefix}: an error occured`, { error, rawUrl }));
|
.catch(error => logger.error(`${LensProtocolRouterMain.LoggingPrefix}: an error occured`, { error, rawUrl }));
|
||||||
});
|
});
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import logger from "./logger";
|
|||||||
import { ensureDir, pathExists } from "fs-extra";
|
import { ensureDir, pathExists } from "fs-extra";
|
||||||
import * as lockFile from "proper-lockfile";
|
import * as lockFile from "proper-lockfile";
|
||||||
import { helmCli } from "./helm/helm-cli";
|
import { helmCli } from "./helm/helm-cli";
|
||||||
import { userStore } from "../common/user-store";
|
import { UserStore } from "../common/user-store";
|
||||||
import { customRequest } from "../common/request";
|
import { customRequest } from "../common/request";
|
||||||
import { getBundledKubectlVersion } from "../common/utils/app-version";
|
import { getBundledKubectlVersion } from "../common/utils/app-version";
|
||||||
import { isDevelopment, isWindows, isTestEnv } from "../common/vars";
|
import { isDevelopment, isWindows, isTestEnv } from "../common/vars";
|
||||||
@ -113,12 +113,12 @@ export class Kubectl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getPathFromPreferences() {
|
public getPathFromPreferences() {
|
||||||
return userStore.preferences?.kubectlBinariesPath || this.getBundledPath();
|
return UserStore.getInstance().preferences?.kubectlBinariesPath || this.getBundledPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getDownloadDir() {
|
protected getDownloadDir() {
|
||||||
if (userStore.preferences?.downloadBinariesPath) {
|
if (UserStore.getInstance().preferences?.downloadBinariesPath) {
|
||||||
return path.join(userStore.preferences.downloadBinariesPath, "kubectl");
|
return path.join(UserStore.getInstance().preferences.downloadBinariesPath, "kubectl");
|
||||||
}
|
}
|
||||||
|
|
||||||
return Kubectl.kubectlDir;
|
return Kubectl.kubectlDir;
|
||||||
@ -129,7 +129,7 @@ export class Kubectl {
|
|||||||
return this.getBundledPath();
|
return this.getBundledPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userStore.preferences?.downloadKubectlBinaries === false) {
|
if (UserStore.getInstance().preferences?.downloadKubectlBinaries === false) {
|
||||||
return this.getPathFromPreferences();
|
return this.getPathFromPreferences();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,7 +223,7 @@ export class Kubectl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async ensureKubectl(): Promise<boolean> {
|
public async ensureKubectl(): Promise<boolean> {
|
||||||
if (userStore.preferences?.downloadKubectlBinaries === false) {
|
if (UserStore.getInstance().preferences?.downloadKubectlBinaries === false) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,7 +273,7 @@ export class Kubectl {
|
|||||||
|
|
||||||
logger.info(`Downloading kubectl ${this.kubectlVersion} from ${this.url} to ${this.path}`);
|
logger.info(`Downloading kubectl ${this.kubectlVersion} from ${this.url} to ${this.path}`);
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
const stream = customRequest({
|
const stream = customRequest({
|
||||||
url: this.url,
|
url: this.url,
|
||||||
gzip: true,
|
gzip: true,
|
||||||
@ -303,7 +303,7 @@ export class Kubectl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async writeInitScripts() {
|
protected async writeInitScripts() {
|
||||||
const kubectlPath = userStore.preferences?.downloadKubectlBinaries ? this.dirname : path.dirname(this.getPathFromPreferences());
|
const kubectlPath = UserStore.getInstance().preferences?.downloadKubectlBinaries ? this.dirname : path.dirname(this.getPathFromPreferences());
|
||||||
const helmPath = helmCli.getBinaryDir();
|
const helmPath = helmCli.getBinaryDir();
|
||||||
const fsPromises = fs.promises;
|
const fsPromises = fs.promises;
|
||||||
const bashScriptPath = path.join(this.dirname, ".bash_set_path");
|
const bashScriptPath = path.join(this.dirname, ".bash_set_path");
|
||||||
@ -361,7 +361,7 @@ export class Kubectl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected getDownloadMirror() {
|
protected getDownloadMirror() {
|
||||||
const mirror = packageMirrors.get(userStore.preferences?.downloadMirror);
|
const mirror = packageMirrors.get(UserStore.getInstance().preferences?.downloadMirror);
|
||||||
|
|
||||||
if (mirror) {
|
if (mirror) {
|
||||||
return mirror;
|
return mirror;
|
||||||
|
|||||||
@ -6,23 +6,22 @@ import url from "url";
|
|||||||
import * as WebSocket from "ws";
|
import * as WebSocket from "ws";
|
||||||
import { apiPrefix, apiKubePrefix } from "../common/vars";
|
import { apiPrefix, apiKubePrefix } from "../common/vars";
|
||||||
import { Router } from "./router";
|
import { Router } from "./router";
|
||||||
import { ClusterManager } from "./cluster-manager";
|
|
||||||
import { ContextHandler } from "./context-handler";
|
import { ContextHandler } from "./context-handler";
|
||||||
import logger from "./logger";
|
import logger from "./logger";
|
||||||
import { NodeShellSession, LocalShellSession } from "./shell-session";
|
import { NodeShellSession, LocalShellSession } from "./shell-session";
|
||||||
|
import { Singleton } from "../common/utils";
|
||||||
|
import { ClusterManager } from "./cluster-manager";
|
||||||
|
|
||||||
export class LensProxy {
|
export class LensProxy extends Singleton {
|
||||||
protected origin: string;
|
protected origin: string;
|
||||||
protected proxyServer: http.Server;
|
protected proxyServer: http.Server;
|
||||||
protected router: Router;
|
protected router: Router;
|
||||||
protected closed = false;
|
protected closed = false;
|
||||||
protected retryCounters = new Map<string, number>();
|
protected retryCounters = new Map<string, number>();
|
||||||
|
|
||||||
static create(port: number, clusterManager: ClusterManager) {
|
constructor(protected port: number) {
|
||||||
return new LensProxy(port, clusterManager).listen();
|
super();
|
||||||
}
|
|
||||||
|
|
||||||
private constructor(protected port: number, protected clusterManager: ClusterManager) {
|
|
||||||
this.origin = `http://localhost:${port}`;
|
this.origin = `http://localhost:${port}`;
|
||||||
this.router = new Router();
|
this.router = new Router();
|
||||||
}
|
}
|
||||||
@ -66,7 +65,7 @@ export class LensProxy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async handleProxyUpgrade(proxy: httpProxy, req: http.IncomingMessage, socket: net.Socket, head: Buffer) {
|
protected async handleProxyUpgrade(proxy: httpProxy, req: http.IncomingMessage, socket: net.Socket, head: Buffer) {
|
||||||
const cluster = this.clusterManager.getClusterForRequest(req);
|
const cluster = ClusterManager.getInstance().getClusterForRequest(req);
|
||||||
|
|
||||||
if (cluster) {
|
if (cluster) {
|
||||||
const proxyUrl = await cluster.contextHandler.resolveAuthProxyUrl() + req.url.replace(apiKubePrefix, "");
|
const proxyUrl = await cluster.contextHandler.resolveAuthProxyUrl() + req.url.replace(apiKubePrefix, "");
|
||||||
@ -171,7 +170,7 @@ export class LensProxy {
|
|||||||
const ws = new WebSocket.Server({ noServer: true });
|
const ws = new WebSocket.Server({ noServer: true });
|
||||||
|
|
||||||
return ws.on("connection", ((socket: WebSocket, req: http.IncomingMessage) => {
|
return ws.on("connection", ((socket: WebSocket, req: http.IncomingMessage) => {
|
||||||
const cluster = this.clusterManager.getClusterForRequest(req);
|
const cluster = ClusterManager.getInstance().getClusterForRequest(req);
|
||||||
const nodeParam = url.parse(req.url, true).query["node"]?.toString();
|
const nodeParam = url.parse(req.url, true).query["node"]?.toString();
|
||||||
const shell = nodeParam
|
const shell = nodeParam
|
||||||
? new NodeShellSession(socket, cluster, nodeParam)
|
? new NodeShellSession(socket, cluster, nodeParam)
|
||||||
@ -197,7 +196,7 @@ export class LensProxy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async handleRequest(proxy: httpProxy, req: http.IncomingMessage, res: http.ServerResponse) {
|
protected async handleRequest(proxy: httpProxy, req: http.IncomingMessage, res: http.ServerResponse) {
|
||||||
const cluster = this.clusterManager.getClusterForRequest(req);
|
const cluster = ClusterManager.getInstance().getClusterForRequest(req);
|
||||||
|
|
||||||
if (cluster) {
|
if (cluster) {
|
||||||
const proxyTarget = await this.getProxyTarget(req, cluster.contextHandler);
|
const proxyTarget = await this.getProxyTarget(req, cluster.contextHandler);
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { LensProtocolRouterMain } from "../router";
|
import { LensProtocolRouterMain } from "../router";
|
||||||
import { noop } from "../../../common/utils";
|
import { noop } from "../../../common/utils";
|
||||||
import { extensionsStore } from "../../../extensions/extensions-store";
|
import { ExtensionsStore } from "../../../extensions/extensions-store";
|
||||||
import { extensionLoader } from "../../../extensions/extension-loader";
|
import { ExtensionLoader } from "../../../extensions/extension-loader";
|
||||||
import * as uuid from "uuid";
|
import * as uuid from "uuid";
|
||||||
import { LensMainExtension } from "../../../extensions/core-api";
|
import { LensMainExtension } from "../../../extensions/core-api";
|
||||||
import { broadcastMessage } from "../../../common/ipc";
|
import { broadcastMessage } from "../../../common/ipc";
|
||||||
@ -16,20 +16,28 @@ function throwIfDefined(val: any): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("protocol router tests", () => {
|
describe("protocol router tests", () => {
|
||||||
let lpr: LensProtocolRouterMain;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
ExtensionsStore.getInstanceOrCreate();
|
||||||
(extensionsStore as any).state.clear();
|
ExtensionLoader.getInstanceOrCreate();
|
||||||
(extensionLoader as any).instances.clear();
|
|
||||||
LensProtocolRouterMain.resetInstance();
|
const lpr = LensProtocolRouterMain.getInstanceOrCreate();
|
||||||
lpr = LensProtocolRouterMain.getInstance<LensProtocolRouterMain>();
|
|
||||||
lpr.extensionsLoaded = true;
|
lpr.extensionsLoaded = true;
|
||||||
lpr.rendererLoaded = true;
|
lpr.rendererLoaded = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
|
||||||
|
ExtensionsStore.resetInstance();
|
||||||
|
ExtensionLoader.resetInstance();
|
||||||
|
LensProtocolRouterMain.resetInstance();
|
||||||
|
});
|
||||||
|
|
||||||
it("should throw on non-lens URLS", async () => {
|
it("should throw on non-lens URLS", async () => {
|
||||||
try {
|
try {
|
||||||
|
const lpr = LensProtocolRouterMain.getInstance();
|
||||||
|
|
||||||
expect(await lpr.route("https://google.ca")).toBeUndefined();
|
expect(await lpr.route("https://google.ca")).toBeUndefined();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
expect(error).toBeInstanceOf(Error);
|
expect(error).toBeInstanceOf(Error);
|
||||||
@ -38,6 +46,8 @@ describe("protocol router tests", () => {
|
|||||||
|
|
||||||
it("should throw when host not internal or extension", async () => {
|
it("should throw when host not internal or extension", async () => {
|
||||||
try {
|
try {
|
||||||
|
const lpr = LensProtocolRouterMain.getInstance();
|
||||||
|
|
||||||
expect(await lpr.route("lens://foobar")).toBeUndefined();
|
expect(await lpr.route("lens://foobar")).toBeUndefined();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
expect(error).toBeInstanceOf(Error);
|
expect(error).toBeInstanceOf(Error);
|
||||||
@ -57,14 +67,15 @@ describe("protocol router tests", () => {
|
|||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
absolutePath: "/foo/bar",
|
absolutePath: "/foo/bar",
|
||||||
});
|
});
|
||||||
|
const lpr = LensProtocolRouterMain.getInstance();
|
||||||
|
|
||||||
ext.protocolHandlers.push({
|
ext.protocolHandlers.push({
|
||||||
pathSchema: "/",
|
pathSchema: "/",
|
||||||
handler: noop,
|
handler: noop,
|
||||||
});
|
});
|
||||||
|
|
||||||
(extensionLoader as any).instances.set(extId, ext);
|
(ExtensionLoader.getInstance() as any).instances.set(extId, ext);
|
||||||
(extensionsStore as any).state.set(extId, { enabled: true, name: "@mirantis/minikube" });
|
(ExtensionsStore.getInstance() as any).state.set(extId, { enabled: true, name: "@mirantis/minikube" });
|
||||||
|
|
||||||
lpr.addInternalHandler("/", noop);
|
lpr.addInternalHandler("/", noop);
|
||||||
|
|
||||||
@ -86,6 +97,7 @@ describe("protocol router tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should call handler if matches", async () => {
|
it("should call handler if matches", async () => {
|
||||||
|
const lpr = LensProtocolRouterMain.getInstance();
|
||||||
let called = false;
|
let called = false;
|
||||||
|
|
||||||
lpr.addInternalHandler("/page", () => { called = true; });
|
lpr.addInternalHandler("/page", () => { called = true; });
|
||||||
@ -101,6 +113,7 @@ describe("protocol router tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should call most exact handler", async () => {
|
it("should call most exact handler", async () => {
|
||||||
|
const lpr = LensProtocolRouterMain.getInstance();
|
||||||
let called: any = 0;
|
let called: any = 0;
|
||||||
|
|
||||||
lpr.addInternalHandler("/page", () => { called = 1; });
|
lpr.addInternalHandler("/page", () => { called = 1; });
|
||||||
@ -119,6 +132,7 @@ describe("protocol router tests", () => {
|
|||||||
it("should call most exact handler for an extension", async () => {
|
it("should call most exact handler for an extension", async () => {
|
||||||
let called: any = 0;
|
let called: any = 0;
|
||||||
|
|
||||||
|
const lpr = LensProtocolRouterMain.getInstance();
|
||||||
const extId = uuid.v4();
|
const extId = uuid.v4();
|
||||||
const ext = new LensMainExtension({
|
const ext = new LensMainExtension({
|
||||||
id: extId,
|
id: extId,
|
||||||
@ -141,8 +155,8 @@ describe("protocol router tests", () => {
|
|||||||
handler: params => { called = params.pathname.id; },
|
handler: params => { called = params.pathname.id; },
|
||||||
});
|
});
|
||||||
|
|
||||||
(extensionLoader as any).instances.set(extId, ext);
|
(ExtensionLoader.getInstance() as any).instances.set(extId, ext);
|
||||||
(extensionsStore as any).state.set(extId, { enabled: true, name: "@foobar/icecream" });
|
(ExtensionsStore.getInstance() as any).state.set(extId, { enabled: true, name: "@foobar/icecream" });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
expect(await lpr.route("lens://extension/@foobar/icecream/page/foob")).toBeUndefined();
|
expect(await lpr.route("lens://extension/@foobar/icecream/page/foob")).toBeUndefined();
|
||||||
@ -155,6 +169,7 @@ describe("protocol router tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should work with non-org extensions", async () => {
|
it("should work with non-org extensions", async () => {
|
||||||
|
const lpr = LensProtocolRouterMain.getInstance();
|
||||||
let called: any = 0;
|
let called: any = 0;
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -177,8 +192,8 @@ describe("protocol router tests", () => {
|
|||||||
handler: params => { called = params.pathname.id; },
|
handler: params => { called = params.pathname.id; },
|
||||||
});
|
});
|
||||||
|
|
||||||
(extensionLoader as any).instances.set(extId, ext);
|
(ExtensionLoader.getInstance() as any).instances.set(extId, ext);
|
||||||
(extensionsStore as any).state.set(extId, { enabled: true, name: "@foobar/icecream" });
|
(ExtensionsStore.getInstance() as any).state.set(extId, { enabled: true, name: "@foobar/icecream" });
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -201,12 +216,12 @@ describe("protocol router tests", () => {
|
|||||||
handler: () => { called = 1; },
|
handler: () => { called = 1; },
|
||||||
});
|
});
|
||||||
|
|
||||||
(extensionLoader as any).instances.set(extId, ext);
|
(ExtensionLoader.getInstance() as any).instances.set(extId, ext);
|
||||||
(extensionsStore as any).state.set(extId, { enabled: true, name: "icecream" });
|
(ExtensionsStore.getInstance() as any).state.set(extId, { enabled: true, name: "icecream" });
|
||||||
}
|
}
|
||||||
|
|
||||||
(extensionsStore as any).state.set("@foobar/icecream", { enabled: true, name: "@foobar/icecream" });
|
(ExtensionsStore.getInstance() as any).state.set("@foobar/icecream", { enabled: true, name: "@foobar/icecream" });
|
||||||
(extensionsStore as any).state.set("icecream", { enabled: true, name: "icecream" });
|
(ExtensionsStore.getInstance() as any).state.set("icecream", { enabled: true, name: "icecream" });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
expect(await lpr.route("lens://extension/icecream/page")).toBeUndefined();
|
expect(await lpr.route("lens://extension/icecream/page")).toBeUndefined();
|
||||||
@ -219,10 +234,13 @@ describe("protocol router tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should throw if urlSchema is invalid", () => {
|
it("should throw if urlSchema is invalid", () => {
|
||||||
|
const lpr = LensProtocolRouterMain.getInstance();
|
||||||
|
|
||||||
expect(() => lpr.addInternalHandler("/:@", noop)).toThrowError();
|
expect(() => lpr.addInternalHandler("/:@", noop)).toThrowError();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should call most exact handler with 3 found handlers", async () => {
|
it("should call most exact handler with 3 found handlers", async () => {
|
||||||
|
const lpr = LensProtocolRouterMain.getInstance();
|
||||||
let called: any = 0;
|
let called: any = 0;
|
||||||
|
|
||||||
lpr.addInternalHandler("/", () => { called = 2; });
|
lpr.addInternalHandler("/", () => { called = 2; });
|
||||||
@ -241,6 +259,7 @@ describe("protocol router tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should call most exact handler with 2 found handlers", async () => {
|
it("should call most exact handler with 2 found handlers", async () => {
|
||||||
|
const lpr = LensProtocolRouterMain.getInstance();
|
||||||
let called: any = 0;
|
let called: any = 0;
|
||||||
|
|
||||||
lpr.addInternalHandler("/", () => { called = 2; });
|
lpr.addInternalHandler("/", () => { called = 2; });
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import path from "path";
|
import path from "path";
|
||||||
import { helmCli } from "../helm/helm-cli";
|
import { helmCli } from "../helm/helm-cli";
|
||||||
import { userStore } from "../../common/user-store";
|
import { UserStore } from "../../common/user-store";
|
||||||
import { ShellSession } from "./shell-session";
|
import { ShellSession } from "./shell-session";
|
||||||
|
|
||||||
export class LocalShellSession extends ShellSession {
|
export class LocalShellSession extends ShellSession {
|
||||||
@ -21,8 +21,8 @@ export class LocalShellSession extends ShellSession {
|
|||||||
|
|
||||||
protected async getShellArgs(shell: string): Promise<string[]> {
|
protected async getShellArgs(shell: string): Promise<string[]> {
|
||||||
const helmpath = helmCli.getBinaryDir();
|
const helmpath = helmCli.getBinaryDir();
|
||||||
const pathFromPreferences = userStore.preferences.kubectlBinariesPath || this.kubectl.getBundledPath();
|
const pathFromPreferences = UserStore.getInstance().preferences.kubectlBinariesPath || this.kubectl.getBundledPath();
|
||||||
const kubectlPathDir = userStore.preferences.downloadKubectlBinaries ? await this.kubectlBinDirP : path.dirname(pathFromPreferences);
|
const kubectlPathDir = UserStore.getInstance().preferences.downloadKubectlBinaries ? await this.kubectlBinDirP : path.dirname(pathFromPreferences);
|
||||||
|
|
||||||
switch(path.basename(shell)) {
|
switch(path.basename(shell)) {
|
||||||
case "powershell.exe":
|
case "powershell.exe":
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { app } from "electron";
|
|||||||
import { clearKubeconfigEnvVars } from "../utils/clear-kube-env-vars";
|
import { clearKubeconfigEnvVars } from "../utils/clear-kube-env-vars";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { isWindows } from "../../common/vars";
|
import { isWindows } from "../../common/vars";
|
||||||
import { userStore } from "../../common/user-store";
|
import { UserStore } from "../../common/user-store";
|
||||||
import * as pty from "node-pty";
|
import * as pty from "node-pty";
|
||||||
import { appEventBus } from "../../common/event-bus";
|
import { appEventBus } from "../../common/event-bus";
|
||||||
|
|
||||||
@ -119,7 +119,7 @@ export abstract class ShellSession {
|
|||||||
protected async getShellEnv() {
|
protected async getShellEnv() {
|
||||||
const env = clearKubeconfigEnvVars(JSON.parse(JSON.stringify(await shellEnv())));
|
const env = clearKubeconfigEnvVars(JSON.parse(JSON.stringify(await shellEnv())));
|
||||||
const pathStr = [...this.getPathEntries(), await this.kubectlBinDirP, process.env.PATH].join(path.delimiter);
|
const pathStr = [...this.getPathEntries(), await this.kubectlBinDirP, process.env.PATH].join(path.delimiter);
|
||||||
const shell = userStore.preferences.shell || process.env.SHELL || process.env.PTYSHELL;
|
const shell = UserStore.getInstance().preferences.shell || process.env.SHELL || process.env.PTYSHELL;
|
||||||
|
|
||||||
delete env.DEBUG; // don't pass DEBUG into shells
|
delete env.DEBUG; // don't pass DEBUG into shells
|
||||||
|
|
||||||
|
|||||||
@ -61,33 +61,32 @@ export class WindowManager extends Singleton {
|
|||||||
this.windowState.manage(this.mainWindow);
|
this.windowState.manage(this.mainWindow);
|
||||||
|
|
||||||
// open external links in default browser (target=_blank, window.open)
|
// open external links in default browser (target=_blank, window.open)
|
||||||
this.mainWindow.webContents.on("new-window", (event, url) => {
|
this.mainWindow
|
||||||
event.preventDefault();
|
.on("focus", () => {
|
||||||
shell.openExternal(url);
|
|
||||||
});
|
|
||||||
this.mainWindow.webContents.on("dom-ready", () => {
|
|
||||||
appEventBus.emit({ name: "app", action: "dom-ready" });
|
|
||||||
});
|
|
||||||
this.mainWindow.on("focus", () => {
|
|
||||||
appEventBus.emit({ name: "app", action: "focus" });
|
appEventBus.emit({ name: "app", action: "focus" });
|
||||||
});
|
})
|
||||||
this.mainWindow.on("blur", () => {
|
.on("blur", () => {
|
||||||
appEventBus.emit({ name: "app", action: "blur" });
|
appEventBus.emit({ name: "app", action: "blur" });
|
||||||
});
|
})
|
||||||
|
.on("closed", () => {
|
||||||
// clean up
|
// clean up
|
||||||
this.mainWindow.on("closed", () => {
|
|
||||||
this.windowState.unmanage();
|
this.windowState.unmanage();
|
||||||
this.mainWindow = null;
|
this.mainWindow = null;
|
||||||
this.splashWindow = null;
|
this.splashWindow = null;
|
||||||
app.dock?.hide(); // hide icon in dock (mac-os)
|
app.dock?.hide(); // hide icon in dock (mac-os)
|
||||||
});
|
})
|
||||||
|
.webContents
|
||||||
this.mainWindow.webContents.on("did-fail-load", (_event, code, desc) => {
|
.on("new-window", (event, url) => {
|
||||||
|
event.preventDefault();
|
||||||
|
shell.openExternal(url);
|
||||||
|
})
|
||||||
|
.on("dom-ready", () => {
|
||||||
|
appEventBus.emit({ name: "app", action: "dom-ready" });
|
||||||
|
})
|
||||||
|
.on("did-fail-load", (_event, code, desc) => {
|
||||||
logger.error(`[WINDOW-MANAGER]: Failed to load Main window`, { code, desc });
|
logger.error(`[WINDOW-MANAGER]: Failed to load Main window`, { code, desc });
|
||||||
});
|
})
|
||||||
|
.on("did-finish-load", () => {
|
||||||
this.mainWindow.webContents.on("did-finish-load", () => {
|
|
||||||
logger.info("[WINDOW-MANAGER]: Main window loaded");
|
logger.info("[WINDOW-MANAGER]: Main window loaded");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -101,8 +100,9 @@ export class WindowManager extends Singleton {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
appEventBus.emit({ name: "app", action: "start" });
|
appEventBus.emit({ name: "app", action: "start" });
|
||||||
}, 1000);
|
}, 1000);
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
dialog.showErrorBox("ERROR!", err.toString());
|
logger.error("Showing main window failed", { error });
|
||||||
|
dialog.showErrorBox("ERROR!", error.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// Cleans up a store that had the state related data stored
|
// Cleans up a store that had the state related data stored
|
||||||
import { Hotbar } from "../../common/hotbar-store";
|
import { Hotbar } from "../../common/hotbar-store";
|
||||||
import { clusterStore } from "../../common/cluster-store";
|
import { ClusterStore } from "../../common/cluster-store";
|
||||||
import { migration } from "../migration-wrapper";
|
import { migration } from "../migration-wrapper";
|
||||||
|
|
||||||
export default migration({
|
export default migration({
|
||||||
@ -8,7 +8,7 @@ export default migration({
|
|||||||
run(store) {
|
run(store) {
|
||||||
const hotbars: Hotbar[] = [];
|
const hotbars: Hotbar[] = [];
|
||||||
|
|
||||||
clusterStore.enabledClustersList.forEach((cluster: any) => {
|
ClusterStore.getInstance().enabledClustersList.forEach((cluster: any) => {
|
||||||
const name = cluster.workspace || "default";
|
const name = cluster.workspace || "default";
|
||||||
let hotbar = hotbars.find((h) => h.name === name);
|
let hotbar = hotbars.find((h) => h.name === name);
|
||||||
|
|
||||||
|
|||||||
@ -6,19 +6,20 @@ import * as MobxReact from "mobx-react";
|
|||||||
import * as ReactRouter from "react-router";
|
import * as ReactRouter from "react-router";
|
||||||
import * as ReactRouterDom from "react-router-dom";
|
import * as ReactRouterDom from "react-router-dom";
|
||||||
import { render, unmountComponentAtNode } from "react-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 { delay } from "../common/utils";
|
||||||
import { isMac, isDevelopment } from "../common/vars";
|
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 * as LensExtensions from "../extensions/extension-api";
|
||||||
import { extensionDiscovery } from "../extensions/extension-discovery";
|
import { ExtensionDiscovery } from "../extensions/extension-discovery";
|
||||||
import { extensionLoader } from "../extensions/extension-loader";
|
import { ExtensionLoader } from "../extensions/extension-loader";
|
||||||
import { extensionsStore } from "../extensions/extensions-store";
|
import { ExtensionsStore } from "../extensions/extensions-store";
|
||||||
import { hotbarStore } from "../common/hotbar-store";
|
import { FilesystemProvisionerStore } from "../main/extension-filesystem";
|
||||||
import { filesystemProvisionerStore } from "../main/extension-filesystem";
|
|
||||||
import { App } from "./components/app";
|
import { App } from "./components/app";
|
||||||
import { LensApp } from "./lens-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
|
* If this is a development buid, wait a second to attach
|
||||||
@ -50,8 +51,16 @@ export async function bootstrap(App: AppComponent) {
|
|||||||
await attachChromeDebugger();
|
await attachChromeDebugger();
|
||||||
rootElem.classList.toggle("is-mac", isMac);
|
rootElem.classList.toggle("is-mac", isMac);
|
||||||
|
|
||||||
extensionLoader.init();
|
ExtensionLoader.getInstanceOrCreate().init();
|
||||||
extensionDiscovery.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
|
// preload common stores
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
@ -59,8 +68,9 @@ export async function bootstrap(App: AppComponent) {
|
|||||||
hotbarStore.load(),
|
hotbarStore.load(),
|
||||||
clusterStore.load(),
|
clusterStore.load(),
|
||||||
extensionsStore.load(),
|
extensionsStore.load(),
|
||||||
filesystemProvisionerStore.load(),
|
filesystemStore.load(),
|
||||||
themeStore.init(),
|
themeStore.init(),
|
||||||
|
helmRepoManager.init(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Register additional store listeners
|
// Register additional store listeners
|
||||||
@ -72,8 +82,8 @@ export async function bootstrap(App: AppComponent) {
|
|||||||
}
|
}
|
||||||
window.addEventListener("message", (ev: MessageEvent) => {
|
window.addEventListener("message", (ev: MessageEvent) => {
|
||||||
if (ev.data === "teardown") {
|
if (ev.data === "teardown") {
|
||||||
userStore.unregisterIpcListener();
|
UserStore.getInstance(false)?.unregisterIpcListener();
|
||||||
clusterStore.unregisterIpcListener();
|
ClusterStore.getInstance(false)?.unregisterIpcListener();
|
||||||
unmountComponentAtNode(rootElem);
|
unmountComponentAtNode(rootElem);
|
||||||
window.location.href = "about:blank";
|
window.location.href = "about:blank";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,10 +11,10 @@ import { AceEditor } from "../ace-editor";
|
|||||||
import { Button } from "../button";
|
import { Button } from "../button";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { kubeConfigDefaultPath, loadConfig, splitConfig, validateConfig, validateKubeConfig } from "../../../common/kube-helpers";
|
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 { v4 as uuid } from "uuid";
|
||||||
import { navigate } from "../../navigation";
|
import { navigate } from "../../navigation";
|
||||||
import { userStore } from "../../../common/user-store";
|
import { UserStore } from "../../../common/user-store";
|
||||||
import { cssNames } from "../../utils";
|
import { cssNames } from "../../utils";
|
||||||
import { Notifications } from "../notifications";
|
import { Notifications } from "../notifications";
|
||||||
import { Tab, Tabs } from "../tabs";
|
import { Tab, Tabs } from "../tabs";
|
||||||
@ -44,13 +44,13 @@ export class AddCluster extends React.Component {
|
|||||||
@observable showSettings = false;
|
@observable showSettings = false;
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
clusterStore.setActive(null);
|
ClusterStore.getInstance().setActive(null);
|
||||||
this.setKubeConfig(userStore.kubeConfigPath);
|
this.setKubeConfig(UserStore.getInstance().kubeConfigPath);
|
||||||
appEventBus.emit({ name: "cluster-add", action: "start" });
|
appEventBus.emit({ name: "cluster-add", action: "start" });
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
userStore.markNewContextsAsSeen();
|
UserStore.getInstance().markNewContextsAsSeen();
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
@ -60,9 +60,9 @@ export class AddCluster extends React.Component {
|
|||||||
validateConfig(this.kubeConfigLocal);
|
validateConfig(this.kubeConfigLocal);
|
||||||
this.refreshContexts();
|
this.refreshContexts();
|
||||||
this.kubeConfigPath = filePath;
|
this.kubeConfigPath = filePath;
|
||||||
userStore.kubeConfigPath = filePath; // save to store
|
UserStore.getInstance().kubeConfigPath = filePath; // save to store
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!userStore.isDefaultKubeConfigPath) {
|
if (!UserStore.getInstance().isDefaultKubeConfigPath) {
|
||||||
Notifications.error(
|
Notifications.error(
|
||||||
<div>Can't setup <code>{filePath}</code> as kubeconfig: {String(err)}</div>
|
<div>Can't setup <code>{filePath}</code> as kubeconfig: {String(err)}</div>
|
||||||
);
|
);
|
||||||
@ -181,7 +181,7 @@ export class AddCluster extends React.Component {
|
|||||||
});
|
});
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
clusterStore.addClusters(...newClusters);
|
ClusterStore.getInstance().addClusters(...newClusters);
|
||||||
|
|
||||||
Notifications.ok(
|
Notifications.ok(
|
||||||
<>Successfully imported <b>{newClusters.length}</b> cluster(s)</>
|
<>Successfully imported <b>{newClusters.length}</b> cluster(s)</>
|
||||||
@ -308,7 +308,7 @@ export class AddCluster extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onKubeConfigInputBlur = () => {
|
onKubeConfigInputBlur = () => {
|
||||||
const isChanged = this.kubeConfigPath !== userStore.kubeConfigPath;
|
const isChanged = this.kubeConfigPath !== UserStore.getInstance().kubeConfigPath;
|
||||||
|
|
||||||
if (isChanged) {
|
if (isChanged) {
|
||||||
this.kubeConfigPath = this.kubeConfigPath.replace("~", os.homedir());
|
this.kubeConfigPath = this.kubeConfigPath.replace("~", os.homedir());
|
||||||
@ -316,7 +316,7 @@ export class AddCluster extends React.Component {
|
|||||||
try {
|
try {
|
||||||
this.setKubeConfig(this.kubeConfigPath, { throwError: true });
|
this.setKubeConfig(this.kubeConfigPath, { throwError: true });
|
||||||
} catch (err) {
|
} 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>) => {
|
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);
|
const isSelected = this.selectedContexts.includes(context);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -19,7 +19,7 @@ import { Button } from "../button";
|
|||||||
import { releaseStore } from "./release.store";
|
import { releaseStore } from "./release.store";
|
||||||
import { Notifications } from "../notifications";
|
import { Notifications } from "../notifications";
|
||||||
import { createUpgradeChartTab } from "../dock/upgrade-chart.store";
|
import { createUpgradeChartTab } from "../dock/upgrade-chart.store";
|
||||||
import { themeStore } from "../../theme.store";
|
import { ThemeStore } from "../../theme.store";
|
||||||
import { apiManager } from "../../api/api-manager";
|
import { apiManager } from "../../api/api-manager";
|
||||||
import { SubTitle } from "../layout/sub-title";
|
import { SubTitle } from "../layout/sub-title";
|
||||||
import { secretsStore } from "../+config-secrets/secrets.store";
|
import { secretsStore } from "../+config-secrets/secrets.store";
|
||||||
@ -259,7 +259,7 @@ export class ReleaseDetails extends Component<Props> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
className={cssNames("ReleaseDetails", themeStore.activeTheme.type)}
|
className={cssNames("ReleaseDetails", ThemeStore.getInstance().activeTheme.type)}
|
||||||
usePortal={true}
|
usePortal={true}
|
||||||
open={!!release}
|
open={!!release}
|
||||||
title={title}
|
title={title}
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import { MenuItem, MenuActions } from "../menu";
|
|||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { CatalogEntityContextMenu, CatalogEntityContextMenuContext, catalogEntityRunContext } from "../../api/catalog-entity";
|
import { CatalogEntityContextMenu, CatalogEntityContextMenuContext, catalogEntityRunContext } from "../../api/catalog-entity";
|
||||||
import { Badge } from "../badge";
|
import { Badge } from "../badge";
|
||||||
import { hotbarStore } from "../../../common/hotbar-store";
|
import { HotbarStore } from "../../../common/hotbar-store";
|
||||||
import { autobind } from "../../utils";
|
import { autobind } from "../../utils";
|
||||||
import { Notifications } from "../notifications";
|
import { Notifications } from "../notifications";
|
||||||
import { ConfirmDialog } from "../confirm-dialog";
|
import { ConfirmDialog } from "../confirm-dialog";
|
||||||
@ -57,7 +57,7 @@ export class Catalog extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addToHotbar(item: CatalogEntityItem) {
|
addToHotbar(item: CatalogEntityItem) {
|
||||||
const hotbar = hotbarStore.getByName("default"); // FIXME
|
const hotbar = HotbarStore.getInstance().getByName("default"); // FIXME
|
||||||
|
|
||||||
if (!hotbar) {
|
if (!hotbar) {
|
||||||
return;
|
return;
|
||||||
@ -67,7 +67,7 @@ export class Catalog extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
removeFromHotbar(item: CatalogEntityItem) {
|
removeFromHotbar(item: CatalogEntityItem) {
|
||||||
const hotbar = hotbarStore.getByName("default"); // FIXME
|
const hotbar = HotbarStore.getInstance().getByName("default"); // FIXME
|
||||||
|
|
||||||
if (!hotbar) {
|
if (!hotbar) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import { eventStore } from "../+events/event.store";
|
|||||||
import { autobind, cssNames, prevDefault } from "../../utils";
|
import { autobind, cssNames, prevDefault } from "../../utils";
|
||||||
import { ItemObject } from "../../item.store";
|
import { ItemObject } from "../../item.store";
|
||||||
import { Spinner } from "../spinner";
|
import { Spinner } from "../spinner";
|
||||||
import { themeStore } from "../../theme.store";
|
import { ThemeStore } from "../../theme.store";
|
||||||
import { lookupApiLink } from "../../api/kube-api";
|
import { lookupApiLink } from "../../api/kube-api";
|
||||||
import { kubeSelectedUrlParam, showDetails } from "../kube-object";
|
import { kubeSelectedUrlParam, showDetails } from "../kube-object";
|
||||||
|
|
||||||
@ -145,7 +145,7 @@ export class ClusterIssues extends React.Component<Props> {
|
|||||||
sortByDefault={{ sortBy: sortBy.object, orderBy: "asc" }}
|
sortByDefault={{ sortBy: sortBy.object, orderBy: "asc" }}
|
||||||
sortSyncWithUrl={false}
|
sortSyncWithUrl={false}
|
||||||
getTableRow={this.getTableRow}
|
getTableRow={this.getTableRow}
|
||||||
className={cssNames("box grow", themeStore.activeTheme.type)}
|
className={cssNames("box grow", ThemeStore.getInstance().activeTheme.type)}
|
||||||
>
|
>
|
||||||
<TableHead nowrap>
|
<TableHead nowrap>
|
||||||
<TableCell className="message">Message</TableCell>
|
<TableCell className="message">Message</TableCell>
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { reaction } from "mobx";
|
|||||||
import { disposeOnUnmount, observer } from "mobx-react";
|
import { disposeOnUnmount, observer } from "mobx-react";
|
||||||
import { nodesStore } from "../+nodes/nodes.store";
|
import { nodesStore } from "../+nodes/nodes.store";
|
||||||
import { podsStore } from "../+workloads-pods/pods.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 { interval } from "../../utils";
|
||||||
import { TabLayout } from "../layout/tab-layout";
|
import { TabLayout } from "../layout/tab-layout";
|
||||||
import { Spinner } from "../spinner";
|
import { Spinner } from "../spinner";
|
||||||
@ -66,7 +66,7 @@ export class ClusterOverview extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const isLoaded = nodesStore.isLoaded && podsStore.isLoaded;
|
const isLoaded = nodesStore.isLoaded && podsStore.isLoaded;
|
||||||
const isMetricsHidden = clusterStore.isMetricHidden(ResourceType.Cluster);
|
const isMetricsHidden = ClusterStore.getInstance().isMetricHidden(ResourceType.Cluster);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TabLayout>
|
<TabLayout>
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import { nodesStore } from "../+nodes/nodes.store";
|
|||||||
import { ChartData, PieChart } from "../chart";
|
import { ChartData, PieChart } from "../chart";
|
||||||
import { ClusterNoMetrics } from "./cluster-no-metrics";
|
import { ClusterNoMetrics } from "./cluster-no-metrics";
|
||||||
import { bytesToUnits } from "../../utils";
|
import { bytesToUnits } from "../../utils";
|
||||||
import { themeStore } from "../../theme.store";
|
import { ThemeStore } from "../../theme.store";
|
||||||
import { getMetricLastPoints } from "../../api/endpoints/metrics.api";
|
import { getMetricLastPoints } from "../../api/endpoints/metrics.api";
|
||||||
|
|
||||||
export const ClusterPieCharts = observer(() => {
|
export const ClusterPieCharts = observer(() => {
|
||||||
@ -29,7 +29,7 @@ export const ClusterPieCharts = observer(() => {
|
|||||||
const { podUsage, podCapacity } = data;
|
const { podUsage, podCapacity } = data;
|
||||||
const cpuLimitsOverload = cpuLimits > cpuCapacity;
|
const cpuLimitsOverload = cpuLimits > cpuCapacity;
|
||||||
const memoryLimitsOverload = memoryLimits > memoryCapacity;
|
const memoryLimitsOverload = memoryLimits > memoryCapacity;
|
||||||
const defaultColor = themeStore.activeTheme.colors.pieChartDefaultColor;
|
const defaultColor = ThemeStore.getInstance().activeTheme.colors.pieChartDefaultColor;
|
||||||
|
|
||||||
if (!memoryCapacity || !cpuCapacity || !podCapacity) return null;
|
if (!memoryCapacity || !cpuCapacity || !podCapacity) return null;
|
||||||
const cpuData: ChartData = {
|
const cpuData: ChartData = {
|
||||||
|
|||||||
@ -2,10 +2,12 @@ import "@testing-library/jest-dom/extend-expect";
|
|||||||
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||||
import fse from "fs-extra";
|
import fse from "fs-extra";
|
||||||
import React from "react";
|
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 { ConfirmDialog } from "../../confirm-dialog";
|
||||||
import { Notifications } from "../../notifications";
|
import { Notifications } from "../../notifications";
|
||||||
import { ExtensionStateStore } from "../extension-install.store";
|
|
||||||
import { Extensions } from "../extensions";
|
import { Extensions } from "../extensions";
|
||||||
|
|
||||||
jest.mock("fs-extra");
|
jest.mock("fs-extra");
|
||||||
@ -18,20 +20,38 @@ jest.mock("../../../../common/utils", () => ({
|
|||||||
extractTar: jest.fn(() => Promise.resolve())
|
extractTar: jest.fn(() => Promise.resolve())
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock("../../../../extensions/extension-discovery", () => ({
|
jest.mock("../../notifications", () => ({
|
||||||
...jest.requireActual("../../../../extensions/extension-discovery"),
|
ok: jest.fn(),
|
||||||
extensionDiscovery: {
|
error: jest.fn(),
|
||||||
localFolderPath: "/fake/path",
|
info: jest.fn()
|
||||||
uninstallExtension: jest.fn(() => Promise.resolve()),
|
|
||||||
isLoaded: true
|
|
||||||
}
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock("../../../../extensions/extension-loader", () => ({
|
jest.mock("electron", () => {
|
||||||
...jest.requireActual("../../../../extensions/extension-loader"),
|
return {
|
||||||
extensionLoader: {
|
app: {
|
||||||
userExtensions: new Map([
|
getVersion: () => "99.99.99",
|
||||||
["extensionId", {
|
getPath: () => "tmp",
|
||||||
|
getLocale: () => "en",
|
||||||
|
setLoginItemSettings: (): void => void 0,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Extensions", () => {
|
||||||
|
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",
|
id: "extensionId",
|
||||||
manifest: {
|
manifest: {
|
||||||
name: "test",
|
name: "test",
|
||||||
@ -41,23 +61,11 @@ jest.mock("../../../../extensions/extension-loader", () => ({
|
|||||||
manifestPath: "/symlinked/path/package.json",
|
manifestPath: "/symlinked/path/package.json",
|
||||||
isBundled: false,
|
isBundled: false,
|
||||||
isEnabled: true
|
isEnabled: true
|
||||||
}]
|
});
|
||||||
])
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock("../../notifications", () => ({
|
|
||||||
ok: jest.fn(),
|
|
||||||
error: jest.fn(),
|
|
||||||
info: jest.fn()
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe("Extensions", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
ExtensionStateStore.resetInstance();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("disables uninstall and disable buttons while uninstalling", async () => {
|
it("disables uninstall and disable buttons while uninstalling", async () => {
|
||||||
|
ExtensionDiscovery.getInstance().isLoaded = true;
|
||||||
render(<><Extensions /><ConfirmDialog/></>);
|
render(<><Extensions /><ConfirmDialog/></>);
|
||||||
|
|
||||||
expect(screen.getByText("Disable").closest("button")).not.toBeDisabled();
|
expect(screen.getByText("Disable").closest("button")).not.toBeDisabled();
|
||||||
@ -68,13 +76,14 @@ describe("Extensions", () => {
|
|||||||
// Approve confirm dialog
|
// Approve confirm dialog
|
||||||
fireEvent.click(screen.getByText("Yes"));
|
fireEvent.click(screen.getByText("Yes"));
|
||||||
|
|
||||||
expect(extensionDiscovery.uninstallExtension).toHaveBeenCalled();
|
expect(ExtensionDiscovery.getInstance().uninstallExtension).toHaveBeenCalled();
|
||||||
expect(screen.getByText("Disable").closest("button")).toBeDisabled();
|
expect(screen.getByText("Disable").closest("button")).toBeDisabled();
|
||||||
expect(screen.getByText("Uninstall").closest("button")).toBeDisabled();
|
expect(screen.getByText("Uninstall").closest("button")).toBeDisabled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("displays error notification on uninstall error", () => {
|
it("displays error notification on uninstall error", () => {
|
||||||
(extensionDiscovery.uninstallExtension as any).mockImplementationOnce(() =>
|
ExtensionDiscovery.getInstance().isLoaded = true;
|
||||||
|
(ExtensionDiscovery.getInstance().uninstallExtension as any).mockImplementationOnce(() =>
|
||||||
Promise.reject()
|
Promise.reject()
|
||||||
);
|
);
|
||||||
render(<><Extensions /><ConfirmDialog/></>);
|
render(<><Extensions /><ConfirmDialog/></>);
|
||||||
@ -115,12 +124,12 @@ describe("Extensions", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("displays spinner while extensions are loading", () => {
|
it("displays spinner while extensions are loading", () => {
|
||||||
extensionDiscovery.isLoaded = false;
|
ExtensionDiscovery.getInstance().isLoaded = false;
|
||||||
const { container } = render(<Extensions />);
|
const { container } = render(<Extensions />);
|
||||||
|
|
||||||
expect(container.querySelector(".Spinner")).toBeInTheDocument();
|
expect(container.querySelector(".Spinner")).toBeInTheDocument();
|
||||||
|
|
||||||
extensionDiscovery.isLoaded = true;
|
ExtensionDiscovery.getInstance().isLoaded = true;
|
||||||
|
|
||||||
waitFor(() =>
|
waitFor(() =>
|
||||||
expect(container.querySelector(".Spinner")).not.toBeInTheDocument()
|
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 React from "react";
|
||||||
import { downloadFile, extractTar, listTarEntries, readFileFromTar } from "../../../common/utils";
|
import { downloadFile, extractTar, listTarEntries, readFileFromTar } from "../../../common/utils";
|
||||||
import { docsUrl } from "../../../common/vars";
|
import { docsUrl } from "../../../common/vars";
|
||||||
import { extensionDiscovery, InstalledExtension, manifestFilename } from "../../../extensions/extension-discovery";
|
import { ExtensionDiscovery, InstalledExtension, manifestFilename } from "../../../extensions/extension-discovery";
|
||||||
import { extensionLoader } from "../../../extensions/extension-loader";
|
import { ExtensionLoader } from "../../../extensions/extension-loader";
|
||||||
import { extensionDisplayName, LensExtensionManifest, sanitizeExtensionName } from "../../../extensions/lens-extension";
|
import { extensionDisplayName, LensExtensionManifest, sanitizeExtensionName } from "../../../extensions/lens-extension";
|
||||||
import logger from "../../../main/logger";
|
import logger from "../../../main/logger";
|
||||||
import { prevDefault } from "../../utils";
|
import { prevDefault } from "../../utils";
|
||||||
@ -21,7 +21,6 @@ import { SubTitle } from "../layout/sub-title";
|
|||||||
import { Notifications } from "../notifications";
|
import { Notifications } from "../notifications";
|
||||||
import { Spinner } from "../spinner/spinner";
|
import { Spinner } from "../spinner/spinner";
|
||||||
import { TooltipPosition } from "../tooltip";
|
import { TooltipPosition } from "../tooltip";
|
||||||
import { ExtensionStateStore } from "./extension-install.store";
|
|
||||||
import "./extensions.scss";
|
import "./extensions.scss";
|
||||||
|
|
||||||
interface InstallRequest {
|
interface InstallRequest {
|
||||||
@ -39,6 +38,12 @@ interface InstallRequestValidated extends InstallRequestPreloaded {
|
|||||||
tempFile: string; // temp system path to packed extension for unpacking
|
tempFile: string; // temp system path to packed extension for unpacking
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ExtensionState {
|
||||||
|
displayName: string;
|
||||||
|
// Possible states the extension can be
|
||||||
|
state: "installing" | "uninstalling";
|
||||||
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class Extensions extends React.Component {
|
export class Extensions extends React.Component {
|
||||||
private static supportedFormats = ["tar", "tgz"];
|
private static supportedFormats = ["tar", "tgz"];
|
||||||
@ -50,9 +55,7 @@ export class Extensions extends React.Component {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
get extensionStateStore() {
|
static installStates = observable.map<string, ExtensionState>();
|
||||||
return ExtensionStateStore.getInstance<ExtensionStateStore>();
|
|
||||||
}
|
|
||||||
|
|
||||||
@observable search = "";
|
@observable search = "";
|
||||||
@observable installPath = "";
|
@observable installPath = "";
|
||||||
@ -64,7 +67,7 @@ export class Extensions extends React.Component {
|
|||||||
* Extensions that were removed from extensions but are still in "uninstalling" state
|
* Extensions that were removed from extensions but are still in "uninstalling" state
|
||||||
*/
|
*/
|
||||||
@computed get removedUninstalling() {
|
@computed get removedUninstalling() {
|
||||||
return Array.from(this.extensionStateStore.extensionState.entries())
|
return Array.from(Extensions.installStates.entries())
|
||||||
.filter(([id, extension]) =>
|
.filter(([id, extension]) =>
|
||||||
extension.state === "uninstalling"
|
extension.state === "uninstalling"
|
||||||
&& !this.extensions.find(extension => extension.id === id)
|
&& !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
|
* Extensions that were added to extensions but are still in "installing" state
|
||||||
*/
|
*/
|
||||||
@computed get addedInstalling() {
|
@computed get addedInstalling() {
|
||||||
return Array.from(this.extensionStateStore.extensionState.entries())
|
return Array.from(Extensions.installStates.entries())
|
||||||
.filter(([id, extension]) =>
|
.filter(([id, extension]) =>
|
||||||
extension.state === "installing"
|
extension.state === "installing"
|
||||||
&& this.extensions.find(extension => extension.id === id)
|
&& this.extensions.find(extension => extension.id === id)
|
||||||
@ -91,7 +94,7 @@ export class Extensions extends React.Component {
|
|||||||
Notifications.ok(
|
Notifications.ok(
|
||||||
<p>Extension <b>{displayName}</b> successfully uninstalled!</p>
|
<p>Extension <b>{displayName}</b> successfully uninstalled!</p>
|
||||||
);
|
);
|
||||||
this.extensionStateStore.extensionState.delete(id);
|
Extensions.installStates.delete(id);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.addedInstalling.forEach(({ id, displayName }) => {
|
this.addedInstalling.forEach(({ id, displayName }) => {
|
||||||
@ -104,7 +107,7 @@ export class Extensions extends React.Component {
|
|||||||
Notifications.ok(
|
Notifications.ok(
|
||||||
<p>Extension <b>{displayName}</b> successfully installed!</p>
|
<p>Extension <b>{displayName}</b> successfully installed!</p>
|
||||||
);
|
);
|
||||||
this.extensionStateStore.extensionState.delete(id);
|
Extensions.installStates.delete(id);
|
||||||
this.installPath = "";
|
this.installPath = "";
|
||||||
|
|
||||||
// Enable installed extensions by default.
|
// Enable installed extensions by default.
|
||||||
@ -117,7 +120,7 @@ export class Extensions extends React.Component {
|
|||||||
@computed get extensions() {
|
@computed get extensions() {
|
||||||
const searchText = this.search.toLowerCase();
|
const searchText = this.search.toLowerCase();
|
||||||
|
|
||||||
return Array.from(extensionLoader.userExtensions.values())
|
return Array.from(ExtensionLoader.getInstance().userExtensions.values())
|
||||||
.filter(({ manifest: { name, description }}) => (
|
.filter(({ manifest: { name, description }}) => (
|
||||||
name.toLowerCase().includes(searchText)
|
name.toLowerCase().includes(searchText)
|
||||||
|| description?.toLowerCase().includes(searchText)
|
|| description?.toLowerCase().includes(searchText)
|
||||||
@ -125,7 +128,7 @@ export class Extensions extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get extensionsPath() {
|
get extensionsPath() {
|
||||||
return extensionDiscovery.localFolderPath;
|
return ExtensionDiscovery.getInstance().localFolderPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
getExtensionPackageTemp(fileName = "") {
|
getExtensionPackageTemp(fileName = "") {
|
||||||
@ -342,11 +345,11 @@ export class Extensions extends React.Component {
|
|||||||
|
|
||||||
async unpackExtension({ fileName, tempFile, manifest: { name, version } }: InstallRequestValidated) {
|
async unpackExtension({ fileName, tempFile, manifest: { name, version } }: InstallRequestValidated) {
|
||||||
const displayName = extensionDisplayName(name, version);
|
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 });
|
logger.info(`Unpacking extension ${displayName}`, { fileName, tempFile });
|
||||||
|
|
||||||
this.extensionStateStore.extensionState.set(extensionId, {
|
Extensions.installStates.set(extensionId, {
|
||||||
state: "installing",
|
state: "installing",
|
||||||
displayName
|
displayName
|
||||||
});
|
});
|
||||||
@ -381,8 +384,8 @@ export class Extensions extends React.Component {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Remove install state on install failure
|
// Remove install state on install failure
|
||||||
if (this.extensionStateStore.extensionState.get(extensionId)?.state === "installing") {
|
if (Extensions.installStates.get(extensionId)?.state === "installing") {
|
||||||
this.extensionStateStore.extensionState.delete(extensionId);
|
Extensions.installStates.delete(extensionId);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
// clean up
|
// clean up
|
||||||
@ -406,20 +409,20 @@ export class Extensions extends React.Component {
|
|||||||
const displayName = extensionDisplayName(extension.manifest.name, extension.manifest.version);
|
const displayName = extensionDisplayName(extension.manifest.name, extension.manifest.version);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.extensionStateStore.extensionState.set(extension.id, {
|
Extensions.installStates.set(extension.id, {
|
||||||
state: "uninstalling",
|
state: "uninstalling",
|
||||||
displayName
|
displayName
|
||||||
});
|
});
|
||||||
|
|
||||||
await extensionDiscovery.uninstallExtension(extension);
|
await ExtensionDiscovery.getInstance().uninstallExtension(extension);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Notifications.error(
|
Notifications.error(
|
||||||
<p>Uninstalling extension <b>{displayName}</b> has failed: <em>{error?.message ?? ""}</em></p>
|
<p>Uninstalling extension <b>{displayName}</b> has failed: <em>{error?.message ?? ""}</em></p>
|
||||||
);
|
);
|
||||||
|
|
||||||
// Remove uninstall state on uninstall failure
|
// Remove uninstall state on uninstall failure
|
||||||
if (this.extensionStateStore.extensionState.get(extension.id)?.state === "uninstalling") {
|
if (Extensions.installStates.get(extension.id)?.state === "uninstalling") {
|
||||||
this.extensionStateStore.extensionState.delete(extension.id);
|
Extensions.installStates.delete(extension.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -445,7 +448,7 @@ export class Extensions extends React.Component {
|
|||||||
return extensions.map(extension => {
|
return extensions.map(extension => {
|
||||||
const { id, isEnabled, manifest } = extension;
|
const { id, isEnabled, manifest } = extension;
|
||||||
const { name, description, version } = manifest;
|
const { name, description, version } = manifest;
|
||||||
const isUninstalling = this.extensionStateStore.extensionState.get(id)?.state === "uninstalling";
|
const isUninstalling = Extensions.installStates.get(id)?.state === "uninstalling";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={id} className="extension flex gaps align-center">
|
<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
|
* True if at least one extension is in installing state
|
||||||
*/
|
*/
|
||||||
@computed get isInstalling() {
|
@computed get isInstalling() {
|
||||||
return [...this.extensionStateStore.extensionState.values()].some(extension => extension.state === "installing");
|
return [...Extensions.installStates.values()].some(extension => extension.state === "installing");
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -536,7 +539,11 @@ export class Extensions extends React.Component {
|
|||||||
value={this.search}
|
value={this.search}
|
||||||
onChange={(value) => this.search = value}
|
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>
|
</div>
|
||||||
</PageLayout>
|
</PageLayout>
|
||||||
</DropFileInput>
|
</DropFileInput>
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
|||||||
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||||
import { getBackendServiceNamePort } from "../../api/endpoints/ingress.api";
|
import { getBackendServiceNamePort } from "../../api/endpoints/ingress.api";
|
||||||
import { ResourceType } from "../cluster-settings/components/cluster-metrics-setting";
|
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> {
|
interface Props extends KubeObjectDetailsProps<Ingress> {
|
||||||
}
|
}
|
||||||
@ -101,6 +101,7 @@ export class IngressDetails extends React.Component<Props> {
|
|||||||
if (!ingress) {
|
if (!ingress) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { spec, status } = ingress;
|
const { spec, status } = ingress;
|
||||||
const ingressPoints = status?.loadBalancer?.ingress;
|
const ingressPoints = status?.loadBalancer?.ingress;
|
||||||
const { metrics } = ingressStore;
|
const { metrics } = ingressStore;
|
||||||
@ -108,8 +109,7 @@ export class IngressDetails extends React.Component<Props> {
|
|||||||
"Network",
|
"Network",
|
||||||
"Duration",
|
"Duration",
|
||||||
];
|
];
|
||||||
const isMetricHidden = clusterStore.isMetricHidden(ResourceType.Ingress);
|
const isMetricHidden = ClusterStore.getInstance().isMetricHidden(ResourceType.Ingress);
|
||||||
|
|
||||||
const { serviceName, servicePort } = ingress.getServiceNamePort();
|
const { serviceName, servicePort } = ingress.getServiceNamePort();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { NoMetrics } from "../resource-metrics/no-metrics";
|
|||||||
import { IResourceMetricsValue, ResourceMetricsContext } from "../resource-metrics";
|
import { IResourceMetricsValue, ResourceMetricsContext } from "../resource-metrics";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { ChartOptions, ChartPoint } from "chart.js";
|
import { ChartOptions, ChartPoint } from "chart.js";
|
||||||
import { themeStore } from "../../theme.store";
|
import { ThemeStore } from "../../theme.store";
|
||||||
import { mapValues } from "lodash";
|
import { mapValues } from "lodash";
|
||||||
|
|
||||||
type IContext = IResourceMetricsValue<Node, { metrics: IClusterMetrics }>;
|
type IContext = IResourceMetricsValue<Node, { metrics: IClusterMetrics }>;
|
||||||
@ -14,7 +14,7 @@ type IContext = IResourceMetricsValue<Node, { metrics: IClusterMetrics }>;
|
|||||||
export const NodeCharts = observer(() => {
|
export const NodeCharts = observer(() => {
|
||||||
const { params: { metrics }, tabId, object } = useContext<IContext>(ResourceMetricsContext);
|
const { params: { metrics }, tabId, object } = useContext<IContext>(ResourceMetricsContext);
|
||||||
const id = object.getId();
|
const id = object.getId();
|
||||||
const { chartCapacityColor } = themeStore.activeTheme.colors;
|
const { chartCapacityColor } = ThemeStore.getInstance().activeTheme.colors;
|
||||||
|
|
||||||
if (!metrics) {
|
if (!metrics) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
|||||||
import { KubeEventDetails } from "../+events/kube-event-details";
|
import { KubeEventDetails } from "../+events/kube-event-details";
|
||||||
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||||
import { ResourceType } from "../cluster-settings/components/cluster-metrics-setting";
|
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> {
|
interface Props extends KubeObjectDetailsProps<Node> {
|
||||||
}
|
}
|
||||||
@ -54,7 +54,7 @@ export class NodeDetails extends React.Component<Props> {
|
|||||||
"Disk",
|
"Disk",
|
||||||
"Pods",
|
"Pods",
|
||||||
];
|
];
|
||||||
const isMetricHidden = clusterStore.isMetricHidden(ResourceType.Node);
|
const isMetricHidden = ClusterStore.getInstance().isMetricHidden(ResourceType.Node);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="NodeDetails">
|
<div className="NodeDetails">
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import { systemName, isUrl, isPath } from "../input/input_validators";
|
|||||||
import { SubTitle } from "../layout/sub-title";
|
import { SubTitle } from "../layout/sub-title";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { Notifications } from "../notifications";
|
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> {
|
interface Props extends Partial<DialogProps> {
|
||||||
onAddRepo: Function
|
onAddRepo: Function
|
||||||
@ -79,7 +79,7 @@ export class AddHelmRepoDialog extends React.Component<Props> {
|
|||||||
|
|
||||||
async addCustomRepo() {
|
async addCustomRepo() {
|
||||||
try {
|
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</>);
|
Notifications.ok(<>Helm repository <b>{this.helmRepo.name}</b> has added</>);
|
||||||
this.props.onAddRepo();
|
this.props.onAddRepo();
|
||||||
this.close();
|
this.close();
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import "./helm-charts.scss";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { action, computed, observable } from "mobx";
|
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 { Button } from "../button";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { Notifications } from "../notifications";
|
import { Notifications } from "../notifications";
|
||||||
@ -34,9 +34,9 @@ export class HelmCharts extends React.Component {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (!this.repos.length) {
|
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();
|
this.addedRepos.clear();
|
||||||
repos.forEach(repo => this.addedRepos.set(repo.name, repo));
|
repos.forEach(repo => this.addedRepos.set(repo.name, repo));
|
||||||
@ -49,7 +49,7 @@ export class HelmCharts extends React.Component {
|
|||||||
|
|
||||||
async addRepo(repo: HelmRepo) {
|
async addRepo(repo: HelmRepo) {
|
||||||
try {
|
try {
|
||||||
await repoManager.addRepo(repo);
|
await HelmRepoManager.getInstance().addRepo(repo);
|
||||||
this.addedRepos.set(repo.name, repo);
|
this.addedRepos.set(repo.name, repo);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Notifications.error(<>Adding helm branch <b>{repo.name}</b> has failed: {String(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) {
|
async removeRepo(repo: HelmRepo) {
|
||||||
try {
|
try {
|
||||||
await repoManager.removeRepo(repo);
|
await HelmRepoManager.getInstance().removeRepo(repo);
|
||||||
this.addedRepos.delete(repo.name);
|
this.addedRepos.delete(repo.name);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Notifications.error(
|
Notifications.error(
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Input, InputValidators } from "../input";
|
import { Input, InputValidators } from "../input";
|
||||||
import { SubTitle } from "../layout/sub-title";
|
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 { observer } from "mobx-react";
|
||||||
import { bundledKubectlPath } from "../../../main/kubectl";
|
import { bundledKubectlPath } from "../../../main/kubectl";
|
||||||
import { SelectOption, Select } from "../select";
|
import { SelectOption, Select } from "../select";
|
||||||
@ -59,7 +59,7 @@ export const KubectlBinaries = observer(({ preferences }: { preferences: UserPre
|
|||||||
<Input
|
<Input
|
||||||
theme="round-black"
|
theme="round-black"
|
||||||
value={downloadPath}
|
value={downloadPath}
|
||||||
placeholder={userStore.getDefaultKubectlPath()}
|
placeholder={getDefaultKubectlPath()}
|
||||||
validators={pathValidator}
|
validators={pathValidator}
|
||||||
onChange={setDownloadPath}
|
onChange={setDownloadPath}
|
||||||
onBlur={save}
|
onBlur={save}
|
||||||
|
|||||||
@ -5,10 +5,10 @@ import moment from "moment-timezone";
|
|||||||
import { computed, observable, reaction } from "mobx";
|
import { computed, observable, reaction } from "mobx";
|
||||||
import { disposeOnUnmount, observer } from "mobx-react";
|
import { disposeOnUnmount, observer } from "mobx-react";
|
||||||
|
|
||||||
import { userStore } from "../../../common/user-store";
|
|
||||||
import { isWindows } from "../../../common/vars";
|
import { isWindows } from "../../../common/vars";
|
||||||
import { appPreferenceRegistry, RegisteredAppPreference } from "../../../extensions/registries/app-preference-registry";
|
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 { Input } from "../input";
|
||||||
import { PageLayout } from "../layout/page-layout";
|
import { PageLayout } from "../layout/page-layout";
|
||||||
import { SubTitle } from "../layout/sub-title";
|
import { SubTitle } from "../layout/sub-title";
|
||||||
@ -30,12 +30,12 @@ enum Pages {
|
|||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class Preferences extends React.Component {
|
export class Preferences extends React.Component {
|
||||||
@observable httpProxy = userStore.preferences.httpsProxy || "";
|
@observable httpProxy = UserStore.getInstance().preferences.httpsProxy || "";
|
||||||
@observable shell = userStore.preferences.shell || "";
|
@observable shell = UserStore.getInstance().preferences.shell || "";
|
||||||
@observable activeTab = Pages.Application;
|
@observable activeTab = Pages.Application;
|
||||||
|
|
||||||
@computed get themeOptions(): SelectOption<string>[] {
|
@computed get themeOptions(): SelectOption<string>[] {
|
||||||
return themeStore.themes.map(theme => ({
|
return ThemeStore.getInstance().themes.map(theme => ({
|
||||||
label: theme.name,
|
label: theme.name,
|
||||||
value: theme.id,
|
value: theme.id,
|
||||||
}));
|
}));
|
||||||
@ -98,18 +98,16 @@ export class Preferences extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { preferences } = userStore;
|
|
||||||
const extensions = appPreferenceRegistry.getItems();
|
const extensions = appPreferenceRegistry.getItems();
|
||||||
const telemetryExtensions = extensions.filter(e => e.showInPreferencesTab == Pages.Telemetry);
|
const telemetryExtensions = extensions.filter(e => e.showInPreferencesTab == Pages.Telemetry);
|
||||||
let defaultShell = process.env.SHELL || process.env.PTYSHELL;
|
const { preferences } = UserStore.getInstance();
|
||||||
|
const defaultShell = process.env.SHELL
|
||||||
if (!defaultShell) {
|
|| process.env.PTYSHELL
|
||||||
if (isWindows) {
|
|| (
|
||||||
defaultShell = "powershell.exe";
|
isWindows
|
||||||
} else {
|
? "powershell.exe"
|
||||||
defaultShell = "System default shell";
|
: "System default shell"
|
||||||
}
|
);
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageLayout
|
<PageLayout
|
||||||
@ -167,7 +165,7 @@ export class Preferences extends React.Component {
|
|||||||
<Select
|
<Select
|
||||||
options={this.timezoneOptions}
|
options={this.timezoneOptions}
|
||||||
value={preferences.localeTimezone}
|
value={preferences.localeTimezone}
|
||||||
onChange={({ value }: SelectOption) => userStore.setLocaleTimezone(value)}
|
onChange={({ value }: SelectOption) => UserStore.getInstance().setLocaleTimezone(value)}
|
||||||
themeName="lens"
|
themeName="lens"
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import { getDetailsUrl, KubeObjectDetailsProps, KubeObjectMeta } from "../kube-o
|
|||||||
import { PersistentVolumeClaim } from "../../api/endpoints";
|
import { PersistentVolumeClaim } from "../../api/endpoints";
|
||||||
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||||
import { ResourceType } from "../cluster-settings/components/cluster-metrics-setting";
|
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> {
|
interface Props extends KubeObjectDetailsProps<PersistentVolumeClaim> {
|
||||||
}
|
}
|
||||||
@ -43,7 +43,7 @@ export class PersistentVolumeClaimDetails extends React.Component<Props> {
|
|||||||
const metricTabs = [
|
const metricTabs = [
|
||||||
"Disk"
|
"Disk"
|
||||||
];
|
];
|
||||||
const isMetricHidden = clusterStore.isMetricHidden(ResourceType.VolumeClaim);
|
const isMetricHidden = ClusterStore.getInstance().isMetricHidden(ResourceType.VolumeClaim);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="PersistentVolumeClaimDetails">
|
<div className="PersistentVolumeClaimDetails">
|
||||||
|
|||||||
@ -5,13 +5,13 @@ import { BarChart, ChartDataSets, memoryOptions } from "../chart";
|
|||||||
import { isMetricsEmpty, normalizeMetrics } from "../../api/endpoints/metrics.api";
|
import { isMetricsEmpty, normalizeMetrics } from "../../api/endpoints/metrics.api";
|
||||||
import { NoMetrics } from "../resource-metrics/no-metrics";
|
import { NoMetrics } from "../resource-metrics/no-metrics";
|
||||||
import { IResourceMetricsValue, ResourceMetricsContext } from "../resource-metrics";
|
import { IResourceMetricsValue, ResourceMetricsContext } from "../resource-metrics";
|
||||||
import { themeStore } from "../../theme.store";
|
import { ThemeStore } from "../../theme.store";
|
||||||
|
|
||||||
type IContext = IResourceMetricsValue<PersistentVolumeClaim, { metrics: IPvcMetrics }>;
|
type IContext = IResourceMetricsValue<PersistentVolumeClaim, { metrics: IPvcMetrics }>;
|
||||||
|
|
||||||
export const VolumeClaimDiskChart = observer(() => {
|
export const VolumeClaimDiskChart = observer(() => {
|
||||||
const { params: { metrics }, object } = useContext<IContext>(ResourceMetricsContext);
|
const { params: { metrics }, object } = useContext<IContext>(ResourceMetricsContext);
|
||||||
const { chartCapacityColor } = themeStore.activeTheme.colors;
|
const { chartCapacityColor } = ThemeStore.getInstance().activeTheme.colors;
|
||||||
const id = object.getId();
|
const id = object.getId();
|
||||||
|
|
||||||
if (!metrics) return null;
|
if (!metrics) return null;
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import fs from "fs";
|
|||||||
import path from "path";
|
import path from "path";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { userStore } from "../../../common/user-store";
|
import { UserStore } from "../../../common/user-store";
|
||||||
import { navigate } from "../../navigation";
|
import { navigate } from "../../navigation";
|
||||||
import { Button } from "../button";
|
import { Button } from "../button";
|
||||||
import marked from "marked";
|
import marked from "marked";
|
||||||
@ -14,7 +14,7 @@ export class WhatsNew extends React.Component {
|
|||||||
|
|
||||||
ok = () => {
|
ok = () => {
|
||||||
navigate("/");
|
navigate("/");
|
||||||
userStore.saveLastSeenAppVersion();
|
UserStore.getInstance().saveLastSeenAppVersion();
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
@ -19,7 +19,7 @@ import { PodDetailsList } from "../+workloads-pods/pod-details-list";
|
|||||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||||
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||||
import { ResourceType } from "../cluster-settings/components/cluster-metrics-setting";
|
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> {
|
interface Props extends KubeObjectDetailsProps<DaemonSet> {
|
||||||
}
|
}
|
||||||
@ -49,7 +49,7 @@ export class DaemonSetDetails extends React.Component<Props> {
|
|||||||
const nodeSelector = daemonSet.getNodeSelectors();
|
const nodeSelector = daemonSet.getNodeSelectors();
|
||||||
const childPods = daemonSetStore.getChildPods(daemonSet);
|
const childPods = daemonSetStore.getChildPods(daemonSet);
|
||||||
const metrics = daemonSetStore.metrics;
|
const metrics = daemonSetStore.metrics;
|
||||||
const isMetricHidden = clusterStore.isMetricHidden(ResourceType.DaemonSet);
|
const isMetricHidden = ClusterStore.getInstance().isMetricHidden(ResourceType.DaemonSet);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="DaemonSetDetails">
|
<div className="DaemonSetDetails">
|
||||||
|
|||||||
@ -20,7 +20,7 @@ import { PodDetailsList } from "../+workloads-pods/pod-details-list";
|
|||||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||||
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||||
import { ResourceType } from "../cluster-settings/components/cluster-metrics-setting";
|
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> {
|
interface Props extends KubeObjectDetailsProps<Deployment> {
|
||||||
}
|
}
|
||||||
@ -49,7 +49,7 @@ export class DeploymentDetails extends React.Component<Props> {
|
|||||||
const selectors = deployment.getSelectors();
|
const selectors = deployment.getSelectors();
|
||||||
const childPods = deploymentStore.getChildPods(deployment);
|
const childPods = deploymentStore.getChildPods(deployment);
|
||||||
const metrics = deploymentStore.metrics;
|
const metrics = deploymentStore.metrics;
|
||||||
const isMetricHidden = clusterStore.isMetricHidden(ResourceType.Deployment);
|
const isMetricHidden = ClusterStore.getInstance().isMetricHidden(ResourceType.Deployment);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="DeploymentDetails">
|
<div className="DeploymentDetails">
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { observer } from "mobx-react";
|
|||||||
import { PieChart } from "../chart";
|
import { PieChart } from "../chart";
|
||||||
import { cssVar } from "../../utils";
|
import { cssVar } from "../../utils";
|
||||||
import { ChartData, ChartDataSets } from "chart.js";
|
import { ChartData, ChartDataSets } from "chart.js";
|
||||||
import { themeStore } from "../../theme.store";
|
import { ThemeStore } from "../../theme.store";
|
||||||
|
|
||||||
interface SimpleChartDataSets extends ChartDataSets {
|
interface SimpleChartDataSets extends ChartDataSets {
|
||||||
backgroundColor?: string[];
|
backgroundColor?: string[];
|
||||||
@ -41,7 +41,7 @@ export class OverviewWorkloadStatus extends React.Component<Props> {
|
|||||||
labels: [] as string[],
|
labels: [] as string[],
|
||||||
datasets: [{
|
datasets: [{
|
||||||
data: [1],
|
data: [1],
|
||||||
backgroundColor: [themeStore.activeTheme.colors.pieChartDefaultColor],
|
backgroundColor: [ThemeStore.getInstance().activeTheme.colors.pieChartDefaultColor],
|
||||||
label: "Empty"
|
label: "Empty"
|
||||||
}]
|
}]
|
||||||
};
|
};
|
||||||
|
|||||||
@ -5,13 +5,13 @@ import { BarChart, cpuOptions, memoryOptions } from "../chart";
|
|||||||
import { isMetricsEmpty, normalizeMetrics } from "../../api/endpoints/metrics.api";
|
import { isMetricsEmpty, normalizeMetrics } from "../../api/endpoints/metrics.api";
|
||||||
import { NoMetrics } from "../resource-metrics/no-metrics";
|
import { NoMetrics } from "../resource-metrics/no-metrics";
|
||||||
import { IResourceMetricsValue, ResourceMetricsContext } from "../resource-metrics";
|
import { IResourceMetricsValue, ResourceMetricsContext } from "../resource-metrics";
|
||||||
import { themeStore } from "../../theme.store";
|
import { ThemeStore } from "../../theme.store";
|
||||||
|
|
||||||
type IContext = IResourceMetricsValue<any, { metrics: IPodMetrics }>;
|
type IContext = IResourceMetricsValue<any, { metrics: IPodMetrics }>;
|
||||||
|
|
||||||
export const ContainerCharts = observer(() => {
|
export const ContainerCharts = observer(() => {
|
||||||
const { params: { metrics }, tabId } = useContext<IContext>(ResourceMetricsContext);
|
const { params: { metrics }, tabId } = useContext<IContext>(ResourceMetricsContext);
|
||||||
const { chartCapacityColor } = themeStore.activeTheme.colors;
|
const { chartCapacityColor } = ThemeStore.getInstance().activeTheme.colors;
|
||||||
|
|
||||||
if (!metrics) return null;
|
if (!metrics) return null;
|
||||||
if (isMetricsEmpty(metrics)) return <NoMetrics/>;
|
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 { NoMetrics } from "../resource-metrics/no-metrics";
|
||||||
import { IResourceMetricsValue, ResourceMetricsContext } from "../resource-metrics";
|
import { IResourceMetricsValue, ResourceMetricsContext } from "../resource-metrics";
|
||||||
import { WorkloadKubeObject } from "../../api/workload-kube-object";
|
import { WorkloadKubeObject } from "../../api/workload-kube-object";
|
||||||
import { themeStore } from "../../theme.store";
|
import { ThemeStore } from "../../theme.store";
|
||||||
|
|
||||||
export const podMetricTabs = [
|
export const podMetricTabs = [
|
||||||
"CPU",
|
"CPU",
|
||||||
@ -19,7 +19,7 @@ type IContext = IResourceMetricsValue<WorkloadKubeObject, { metrics: IPodMetrics
|
|||||||
|
|
||||||
export const PodCharts = observer(() => {
|
export const PodCharts = observer(() => {
|
||||||
const { params: { metrics }, tabId, object } = useContext<IContext>(ResourceMetricsContext);
|
const { params: { metrics }, tabId, object } = useContext<IContext>(ResourceMetricsContext);
|
||||||
const { chartCapacityColor } = themeStore.activeTheme.colors;
|
const { chartCapacityColor } = ThemeStore.getInstance().activeTheme.colors;
|
||||||
const id = object.getId();
|
const id = object.getId();
|
||||||
|
|
||||||
if (!metrics) return null;
|
if (!metrics) return null;
|
||||||
|
|||||||
@ -12,8 +12,8 @@ import { ResourceMetrics } from "../resource-metrics";
|
|||||||
import { IMetrics } from "../../api/endpoints/metrics.api";
|
import { IMetrics } from "../../api/endpoints/metrics.api";
|
||||||
import { ContainerCharts } from "./container-charts";
|
import { ContainerCharts } from "./container-charts";
|
||||||
import { ResourceType } from "../cluster-settings/components/cluster-metrics-setting";
|
import { ResourceType } from "../cluster-settings/components/cluster-metrics-setting";
|
||||||
import { clusterStore } from "../../../common/cluster-store";
|
|
||||||
import { LocaleDate } from "../locale-date";
|
import { LocaleDate } from "../locale-date";
|
||||||
|
import { ClusterStore } from "../../../common/cluster-store";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
pod: Pod;
|
pod: Pod;
|
||||||
@ -66,7 +66,7 @@ export class PodDetailsContainer extends React.Component<Props> {
|
|||||||
"Memory",
|
"Memory",
|
||||||
"Filesystem",
|
"Filesystem",
|
||||||
];
|
];
|
||||||
const isMetricHidden = clusterStore.isMetricHidden(ResourceType.Container);
|
const isMetricHidden = ClusterStore.getInstance().isMetricHidden(ResourceType.Container);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="PodDetailsContainer">
|
<div className="PodDetailsContainer">
|
||||||
|
|||||||
@ -23,7 +23,7 @@ import { PodCharts, podMetricTabs } from "./pod-charts";
|
|||||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||||
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||||
import { ResourceType } from "../cluster-settings/components/cluster-metrics-setting";
|
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> {
|
interface Props extends KubeObjectDetailsProps<Pod> {
|
||||||
}
|
}
|
||||||
@ -68,7 +68,7 @@ export class PodDetails extends React.Component<Props> {
|
|||||||
const nodeSelector = pod.getNodeSelectors();
|
const nodeSelector = pod.getNodeSelectors();
|
||||||
const volumes = pod.getVolumes();
|
const volumes = pod.getVolumes();
|
||||||
const metrics = podsStore.metrics;
|
const metrics = podsStore.metrics;
|
||||||
const isMetricHidden = clusterStore.isMetricHidden(ResourceType.Pod);
|
const isMetricHidden = ClusterStore.getInstance().isMetricHidden(ResourceType.Pod);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="PodDetails">
|
<div className="PodDetails">
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import { PodDetailsList } from "../+workloads-pods/pod-details-list";
|
|||||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||||
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||||
import { ResourceType } from "../cluster-settings/components/cluster-metrics-setting";
|
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> {
|
interface Props extends KubeObjectDetailsProps<ReplicaSet> {
|
||||||
}
|
}
|
||||||
@ -49,7 +49,7 @@ export class ReplicaSetDetails extends React.Component<Props> {
|
|||||||
const nodeSelector = replicaSet.getNodeSelectors();
|
const nodeSelector = replicaSet.getNodeSelectors();
|
||||||
const images = replicaSet.getImages();
|
const images = replicaSet.getImages();
|
||||||
const childPods = replicaSetStore.getChildPods(replicaSet);
|
const childPods = replicaSetStore.getChildPods(replicaSet);
|
||||||
const isMetricHidden = clusterStore.isMetricHidden(ResourceType.ReplicaSet);
|
const isMetricHidden = ClusterStore.getInstance().isMetricHidden(ResourceType.ReplicaSet);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="ReplicaSetDetails">
|
<div className="ReplicaSetDetails">
|
||||||
|
|||||||
@ -19,7 +19,7 @@ import { PodDetailsList } from "../+workloads-pods/pod-details-list";
|
|||||||
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
|
||||||
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
|
||||||
import { ResourceType } from "../cluster-settings/components/cluster-metrics-setting";
|
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> {
|
interface Props extends KubeObjectDetailsProps<StatefulSet> {
|
||||||
}
|
}
|
||||||
@ -48,7 +48,7 @@ export class StatefulSetDetails extends React.Component<Props> {
|
|||||||
const nodeSelector = statefulSet.getNodeSelectors();
|
const nodeSelector = statefulSet.getNodeSelectors();
|
||||||
const childPods = statefulSetStore.getChildPods(statefulSet);
|
const childPods = statefulSetStore.getChildPods(statefulSet);
|
||||||
const metrics = statefulSetStore.metrics;
|
const metrics = statefulSetStore.metrics;
|
||||||
const isMetricHidden = clusterStore.isMetricHidden(ResourceType.StatefulSet);
|
const isMetricHidden = ClusterStore.getInstance().isMetricHidden(ResourceType.StatefulSet);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="StatefulSetDetails">
|
<div className="StatefulSetDetails">
|
||||||
|
|||||||
@ -34,7 +34,7 @@ import { getHostedCluster, getHostedClusterId } from "../../common/cluster-store
|
|||||||
import logger from "../../main/logger";
|
import logger from "../../main/logger";
|
||||||
import { webFrame } from "electron";
|
import { webFrame } from "electron";
|
||||||
import { clusterPageRegistry, getExtensionPageUrl } from "../../extensions/registries/page-registry";
|
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 { appEventBus } from "../../common/event-bus";
|
||||||
import { requestMain } from "../../common/ipc";
|
import { requestMain } from "../../common/ipc";
|
||||||
import whatInput from "what-input";
|
import whatInput from "what-input";
|
||||||
@ -62,7 +62,7 @@ export class App extends React.Component {
|
|||||||
|
|
||||||
await requestMain(clusterSetFrameIdHandler, clusterId);
|
await requestMain(clusterSetFrameIdHandler, clusterId);
|
||||||
await getHostedCluster().whenReady; // cluster.activate() is done at this point
|
await getHostedCluster().whenReady; // cluster.activate() is done at this point
|
||||||
extensionLoader.loadOnClusterRenderer();
|
ExtensionLoader.getInstance().loadOnClusterRenderer();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
appEventBus.emit({
|
appEventBus.emit({
|
||||||
name: "cluster",
|
name: "cluster",
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { ChartData, ChartOptions, ChartPoint, ChartTooltipItem, Scriptable } fro
|
|||||||
import { Chart, ChartKind, ChartProps } from "./chart";
|
import { Chart, ChartKind, ChartProps } from "./chart";
|
||||||
import { bytesToUnits, cssNames } from "../../utils";
|
import { bytesToUnits, cssNames } from "../../utils";
|
||||||
import { ZebraStripes } from "./zebra-stripes.plugin";
|
import { ZebraStripes } from "./zebra-stripes.plugin";
|
||||||
import { themeStore } from "../../theme.store";
|
import { ThemeStore } from "../../theme.store";
|
||||||
import { NoMetrics } from "../resource-metrics/no-metrics";
|
import { NoMetrics } from "../resource-metrics/no-metrics";
|
||||||
|
|
||||||
interface Props extends ChartProps {
|
interface Props extends ChartProps {
|
||||||
@ -26,7 +26,7 @@ export class BarChart extends React.Component<Props> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { name, data, className, timeLabelStep, plugins, options: customOptions, ...settings } = this.props;
|
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 getBarColor: Scriptable<string> = ({ dataset }) => {
|
||||||
const color = dataset.borderColor;
|
const color = dataset.borderColor;
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { observer } from "mobx-react";
|
|||||||
import ChartJS, { ChartOptions } from "chart.js";
|
import ChartJS, { ChartOptions } from "chart.js";
|
||||||
import { Chart, ChartProps } from "./chart";
|
import { Chart, ChartProps } from "./chart";
|
||||||
import { cssNames } from "../../utils";
|
import { cssNames } from "../../utils";
|
||||||
import { themeStore } from "../../theme.store";
|
import { ThemeStore } from "../../theme.store";
|
||||||
|
|
||||||
interface Props extends ChartProps {
|
interface Props extends ChartProps {
|
||||||
}
|
}
|
||||||
@ -13,7 +13,7 @@ interface Props extends ChartProps {
|
|||||||
export class PieChart extends React.Component<Props> {
|
export class PieChart extends React.Component<Props> {
|
||||||
render() {
|
render() {
|
||||||
const { data, className, options, ...chartProps } = this.props;
|
const { data, className, options, ...chartProps } = this.props;
|
||||||
const { contentColor } = themeStore.activeTheme.colors;
|
const { contentColor } = ThemeStore.getInstance().activeTheme.colors;
|
||||||
const cutouts = [88, 76, 63];
|
const cutouts = [88, 76, 63];
|
||||||
const opts: ChartOptions = this.props.showChart === false ? {} : {
|
const opts: ChartOptions = this.props.showChart === false ? {} : {
|
||||||
maintainAspectRatio: false,
|
maintainAspectRatio: false,
|
||||||
|
|||||||
@ -7,7 +7,6 @@ jest.mock("../../../extensions/registries");
|
|||||||
import { statusBarRegistry } from "../../../extensions/registries";
|
import { statusBarRegistry } from "../../../extensions/registries";
|
||||||
|
|
||||||
describe("<BottomBar />", () => {
|
describe("<BottomBar />", () => {
|
||||||
|
|
||||||
it("renders w/o errors", () => {
|
it("renders w/o errors", () => {
|
||||||
const { container } = render(<BottomBar />);
|
const { container } = render(<BottomBar />);
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import { Preferences, preferencesRoute } from "../+preferences";
|
|||||||
import { AddCluster, addClusterRoute } from "../+add-cluster";
|
import { AddCluster, addClusterRoute } from "../+add-cluster";
|
||||||
import { ClusterView } from "./cluster-view";
|
import { ClusterView } from "./cluster-view";
|
||||||
import { clusterViewRoute } from "./cluster-view.route";
|
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 { hasLoadedView, initView, lensViews, refreshViews } from "./lens-views";
|
||||||
import { globalPageRegistry } from "../../../extensions/registries/page-registry";
|
import { globalPageRegistry } from "../../../extensions/registries/page-registry";
|
||||||
import { Extensions, extensionsRoute } from "../+extensions";
|
import { Extensions, extensionsRoute } from "../+extensions";
|
||||||
@ -21,7 +21,7 @@ import { EntitySettings, entitySettingsRoute } from "../+entity-settings";
|
|||||||
@observer
|
@observer
|
||||||
export class ClusterManager extends React.Component {
|
export class ClusterManager extends React.Component {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const getMatchedCluster = () => clusterStore.getById(getMatchedClusterId());
|
const getMatchedCluster = () => ClusterStore.getInstance().getById(getMatchedClusterId());
|
||||||
|
|
||||||
disposeOnUnmount(this, [
|
disposeOnUnmount(this, [
|
||||||
reaction(getMatchedClusterId, initView, {
|
reaction(getMatchedClusterId, initView, {
|
||||||
@ -59,9 +59,12 @@ export class ClusterManager extends React.Component {
|
|||||||
<Route component={AddCluster} {...addClusterRoute} />
|
<Route component={AddCluster} {...addClusterRoute} />
|
||||||
<Route component={ClusterView} {...clusterViewRoute} />
|
<Route component={ClusterView} {...clusterViewRoute} />
|
||||||
<Route component={EntitySettings} {...entitySettingsRoute} />
|
<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}/>
|
<Redirect exact to={this.startUrl}/>
|
||||||
</Switch>
|
</Switch>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import { Icon } from "../icon";
|
|||||||
import { Button } from "../button";
|
import { Button } from "../button";
|
||||||
import { cssNames, IClassName } from "../../utils";
|
import { cssNames, IClassName } from "../../utils";
|
||||||
import { Cluster } from "../../../main/cluster";
|
import { Cluster } from "../../../main/cluster";
|
||||||
import { ClusterId, clusterStore } from "../../../common/cluster-store";
|
import { ClusterId, ClusterStore } from "../../../common/cluster-store";
|
||||||
import { CubeSpinner } from "../spinner";
|
import { CubeSpinner } from "../spinner";
|
||||||
import { clusterActivateHandler } from "../../../common/cluster-ipc";
|
import { clusterActivateHandler } from "../../../common/cluster-ipc";
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ export class ClusterStatus extends React.Component<Props> {
|
|||||||
@observable isReconnecting = false;
|
@observable isReconnecting = false;
|
||||||
|
|
||||||
get cluster(): Cluster {
|
get cluster(): Cluster {
|
||||||
return clusterStore.getById(this.props.clusterId);
|
return ClusterStore.getInstance().getById(this.props.clusterId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed get hasErrors(): boolean {
|
@computed get hasErrors(): boolean {
|
||||||
|
|||||||
@ -7,9 +7,9 @@ import { IClusterViewRouteParams } from "./cluster-view.route";
|
|||||||
import { ClusterStatus } from "./cluster-status";
|
import { ClusterStatus } from "./cluster-status";
|
||||||
import { hasLoadedView } from "./lens-views";
|
import { hasLoadedView } from "./lens-views";
|
||||||
import { Cluster } from "../../../main/cluster";
|
import { Cluster } from "../../../main/cluster";
|
||||||
import { clusterStore } from "../../../common/cluster-store";
|
|
||||||
import { navigate } from "../../navigation";
|
import { navigate } from "../../navigation";
|
||||||
import { catalogURL } from "../+catalog";
|
import { catalogURL } from "../+catalog";
|
||||||
|
import { ClusterStore } from "../../../common/cluster-store";
|
||||||
|
|
||||||
interface Props extends RouteComponentProps<IClusterViewRouteParams> {
|
interface Props extends RouteComponentProps<IClusterViewRouteParams> {
|
||||||
}
|
}
|
||||||
@ -21,12 +21,12 @@ export class ClusterView extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get cluster(): Cluster {
|
get cluster(): Cluster {
|
||||||
return clusterStore.getById(this.clusterId);
|
return ClusterStore.getInstance().getById(this.clusterId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
disposeOnUnmount(this, [
|
disposeOnUnmount(this, [
|
||||||
reaction(() => this.clusterId, clusterId => clusterStore.setActive(clusterId), {
|
reaction(() => this.clusterId, clusterId => ClusterStore.getInstance().setActive(clusterId), {
|
||||||
fireImmediately: true,
|
fireImmediately: true,
|
||||||
}),
|
}),
|
||||||
reaction(() => this.cluster.online, (online) => {
|
reaction(() => this.cluster.online, (online) => {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { observable, when } from "mobx";
|
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 { getMatchedClusterId } from "../../navigation";
|
||||||
import logger from "../../../main/logger";
|
import logger from "../../../main/logger";
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ export async function initView(clusterId: ClusterId) {
|
|||||||
if (!clusterId || lensViews.has(clusterId)) {
|
if (!clusterId || lensViews.has(clusterId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const cluster = clusterStore.getById(clusterId);
|
const cluster = ClusterStore.getInstance().getById(clusterId);
|
||||||
|
|
||||||
if (!cluster) {
|
if (!cluster) {
|
||||||
return;
|
return;
|
||||||
@ -44,13 +44,9 @@ export async function initView(clusterId: ClusterId) {
|
|||||||
|
|
||||||
export async function autoCleanOnRemove(clusterId: ClusterId, iframe: HTMLIFrameElement) {
|
export async function autoCleanOnRemove(clusterId: ClusterId, iframe: HTMLIFrameElement) {
|
||||||
await when(() => {
|
await when(() => {
|
||||||
const cluster = clusterStore.getById(clusterId);
|
const cluster = ClusterStore.getInstance().getById(clusterId);
|
||||||
|
|
||||||
if (!cluster) return true;
|
return !cluster || (cluster.disconnected && lensViews.get(clusterId)?.isLoaded);
|
||||||
|
|
||||||
const view = lensViews.get(clusterId);
|
|
||||||
|
|
||||||
return cluster.disconnected && view?.isLoaded;
|
|
||||||
});
|
});
|
||||||
logger.info(`[LENS-VIEW]: remove dashboard, clusterId=${clusterId}`);
|
logger.info(`[LENS-VIEW]: remove dashboard, clusterId=${clusterId}`);
|
||||||
lensViews.delete(clusterId);
|
lensViews.delete(clusterId);
|
||||||
@ -64,7 +60,7 @@ export async function autoCleanOnRemove(clusterId: ClusterId, iframe: HTMLIFrame
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function refreshViews() {
|
export function refreshViews() {
|
||||||
const cluster = clusterStore.getById(getMatchedClusterId());
|
const cluster = ClusterStore.getInstance().getById(getMatchedClusterId());
|
||||||
|
|
||||||
lensViews.forEach(({ clusterId, view, isLoaded }) => {
|
lensViews.forEach(({ clusterId, view, isLoaded }) => {
|
||||||
const isCurrent = clusterId === cluster?.id;
|
const isCurrent = clusterId === cluster?.id;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { navigate } from "../../navigation";
|
import { navigate } from "../../navigation";
|
||||||
import { commandRegistry } from "../../../extensions/registries/command-registry";
|
import { commandRegistry } from "../../../extensions/registries/command-registry";
|
||||||
import { clusterStore } from "../../../common/cluster-store";
|
|
||||||
import { entitySettingsURL } from "../+entity-settings";
|
import { entitySettingsURL } from "../+entity-settings";
|
||||||
|
import { ClusterStore } from "../../../common/cluster-store";
|
||||||
|
|
||||||
commandRegistry.add({
|
commandRegistry.add({
|
||||||
id: "cluster.viewCurrentClusterSettings",
|
id: "cluster.viewCurrentClusterSettings",
|
||||||
@ -9,7 +9,7 @@ commandRegistry.add({
|
|||||||
scope: "global",
|
scope: "global",
|
||||||
action: () => navigate(entitySettingsURL({
|
action: () => navigate(entitySettingsURL({
|
||||||
params: {
|
params: {
|
||||||
entityId: clusterStore.active.id
|
entityId: ClusterStore.getInstance().active.id
|
||||||
}
|
}
|
||||||
})),
|
})),
|
||||||
isActive: (context) => !!context.entity
|
isActive: (context) => !!context.entity
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { clusterStore } from "../../../common/cluster-store";
|
import { ClusterStore } from "../../../common/cluster-store";
|
||||||
import { ClusterProxySetting } from "./components/cluster-proxy-setting";
|
import { ClusterProxySetting } from "./components/cluster-proxy-setting";
|
||||||
import { ClusterNameSetting } from "./components/cluster-name-setting";
|
import { ClusterNameSetting } from "./components/cluster-name-setting";
|
||||||
import { ClusterHomeDirSetting } from "./components/cluster-home-dir-setting";
|
import { ClusterHomeDirSetting } from "./components/cluster-home-dir-setting";
|
||||||
@ -13,7 +13,7 @@ import { CatalogEntity } from "../../api/catalog-entity";
|
|||||||
|
|
||||||
|
|
||||||
function getClusterForEntity(entity: CatalogEntity) {
|
function getClusterForEntity(entity: CatalogEntity) {
|
||||||
const cluster = clusterStore.getById(entity.metadata.uid);
|
const cluster = ClusterStore.getInstance().getById(entity.metadata.uid);
|
||||||
|
|
||||||
if (!cluster?.enabled) {
|
if (!cluster?.enabled) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { clusterStore } from "../../../../common/cluster-store";
|
import { ClusterStore } from "../../../../common/cluster-store";
|
||||||
import { Cluster } from "../../../../main/cluster";
|
import { Cluster } from "../../../../main/cluster";
|
||||||
import { autobind } from "../../../utils";
|
import { autobind } from "../../../utils";
|
||||||
import { Button } from "../../button";
|
import { Button } from "../../button";
|
||||||
@ -21,7 +21,7 @@ export class RemoveClusterButton extends React.Component<Props> {
|
|||||||
labelOk: "Yes",
|
labelOk: "Yes",
|
||||||
labelCancel: "No",
|
labelCancel: "No",
|
||||||
ok: async () => {
|
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 { observer } from "mobx-react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { commandRegistry } from "../../../extensions/registries/command-registry";
|
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 { CommandOverlay } from "./command-container";
|
||||||
import { broadcastMessage } from "../../../common/ipc";
|
import { broadcastMessage } from "../../../common/ipc";
|
||||||
import { navigate } from "../../navigation";
|
import { navigate } from "../../navigation";
|
||||||
@ -20,7 +20,7 @@ export class CommandDialog extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return commandRegistry.getItems().filter((command) => {
|
return commandRegistry.getItems().filter((command) => {
|
||||||
if (command.scope === "entity" && !clusterStore.active) {
|
if (command.scope === "entity" && !ClusterStore.getInstance().active) {
|
||||||
return false;
|
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 renderTabs = () => render(getComponent());
|
||||||
|
|
||||||
const getTabKinds = () => dockStore.tabs.map(tab => tab.kind);
|
const getTabKinds = () => dockStore.tabs.map(tab => tab.kind);
|
||||||
|
|||||||
@ -7,6 +7,8 @@ import { Pod } from "../../../api/endpoints";
|
|||||||
import { LogResourceSelector } from "../log-resource-selector";
|
import { LogResourceSelector } from "../log-resource-selector";
|
||||||
import { LogTabData } from "../log-tab.store";
|
import { LogTabData } from "../log-tab.store";
|
||||||
import { dockerPod, deploymentPod1 } from "./pod.mock";
|
import { dockerPod, deploymentPod1 } from "./pod.mock";
|
||||||
|
import { ThemeStore } from "../../../theme.store";
|
||||||
|
import { UserStore } from "../../../../common/user-store";
|
||||||
|
|
||||||
const getComponent = (tabData: LogTabData) => {
|
const getComponent = (tabData: LogTabData) => {
|
||||||
return (
|
return (
|
||||||
@ -41,6 +43,16 @@ const getFewPodsTabData = (): LogTabData => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
describe("<LogResourceSelector />", () => {
|
describe("<LogResourceSelector />", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
UserStore.getInstanceOrCreate();
|
||||||
|
ThemeStore.getInstanceOrCreate();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
UserStore.resetInstance();
|
||||||
|
ThemeStore.resetInstance();
|
||||||
|
});
|
||||||
|
|
||||||
it("renders w/o errors", () => {
|
it("renders w/o errors", () => {
|
||||||
const tabData = getOnePodTabData();
|
const tabData = getOnePodTabData();
|
||||||
const { container } = render(getComponent(tabData));
|
const { container } = render(getComponent(tabData));
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import moment from "moment-timezone";
|
|||||||
import { Align, ListOnScrollProps } from "react-window";
|
import { Align, ListOnScrollProps } from "react-window";
|
||||||
|
|
||||||
import { SearchStore, searchStore } from "../../../common/search-store";
|
import { SearchStore, searchStore } from "../../../common/search-store";
|
||||||
import { userStore } from "../../../common/user-store";
|
import { UserStore } from "../../../common/user-store";
|
||||||
import { cssNames } from "../../utils";
|
import { cssNames } from "../../utils";
|
||||||
import { Button } from "../button";
|
import { Button } from "../button";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
@ -81,7 +81,7 @@ export class LogList extends React.Component<Props> {
|
|||||||
@computed
|
@computed
|
||||||
get logs() {
|
get logs() {
|
||||||
const showTimestamps = logTabStore.getData(this.props.id).showTimestamps;
|
const showTimestamps = logTabStore.getData(this.props.id).showTimestamps;
|
||||||
const { preferences } = userStore;
|
const { preferences } = UserStore.getInstance();
|
||||||
|
|
||||||
if (!showTimestamps) {
|
if (!showTimestamps) {
|
||||||
return logStore.logsWithoutTimestamps;
|
return logStore.logsWithoutTimestamps;
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { cssNames } from "../../utils";
|
|||||||
import { IDockTab } from "./dock.store";
|
import { IDockTab } from "./dock.store";
|
||||||
import { Terminal } from "./terminal";
|
import { Terminal } from "./terminal";
|
||||||
import { terminalStore } from "./terminal.store";
|
import { terminalStore } from "./terminal.store";
|
||||||
import { themeStore } from "../../theme.store";
|
import { ThemeStore } from "../../theme.store";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -38,7 +38,7 @@ export class TerminalWindow extends React.Component<Props> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cssNames("TerminalWindow", className, themeStore.activeTheme.type)}
|
className={cssNames("TerminalWindow", className, ThemeStore.getInstance().activeTheme.type)}
|
||||||
ref={e => this.elem = e}
|
ref={e => this.elem = e}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -4,9 +4,10 @@ import { Terminal as XTerm } from "xterm";
|
|||||||
import { FitAddon } from "xterm-addon-fit";
|
import { FitAddon } from "xterm-addon-fit";
|
||||||
import { dockStore, TabId } from "./dock.store";
|
import { dockStore, TabId } from "./dock.store";
|
||||||
import { TerminalApi } from "../../api/terminal-api";
|
import { TerminalApi } from "../../api/terminal-api";
|
||||||
import { themeStore } from "../../theme.store";
|
import { ThemeStore } from "../../theme.store";
|
||||||
import { autobind } from "../../utils";
|
import { autobind } from "../../utils";
|
||||||
import { isMac } from "../../../common/vars";
|
import { isMac } from "../../../common/vars";
|
||||||
|
import { camelCase } from "lodash";
|
||||||
|
|
||||||
export class Terminal {
|
export class Terminal {
|
||||||
static spawningPool: HTMLElement;
|
static spawningPool: HTMLElement;
|
||||||
@ -40,16 +41,10 @@ export class Terminal {
|
|||||||
// Replacing keys stored in styles to format accepted by terminal
|
// Replacing keys stored in styles to format accepted by terminal
|
||||||
// E.g. terminalBrightBlack -> brightBlack
|
// E.g. terminalBrightBlack -> brightBlack
|
||||||
const colorPrefix = "terminal";
|
const colorPrefix = "terminal";
|
||||||
const terminalColors = Object.entries(colors)
|
const terminalColorEntries = Object.entries(colors)
|
||||||
.filter(([name]) => name.startsWith(colorPrefix))
|
.filter(([name]) => name.startsWith(colorPrefix))
|
||||||
.reduce<any>((colors, [name, color]) => {
|
.map(([name, color]) => [camelCase(name.slice(colorPrefix.length)), color]);
|
||||||
const colorName = name.split("").slice(colorPrefix.length);
|
const terminalColors = Object.fromEntries(terminalColorEntries);
|
||||||
|
|
||||||
colorName[0] = colorName[0].toLowerCase();
|
|
||||||
colors[colorName.join("")] = color;
|
|
||||||
|
|
||||||
return colors;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
this.xterm.setOption("theme", terminalColors);
|
this.xterm.setOption("theme", terminalColors);
|
||||||
}
|
}
|
||||||
@ -109,7 +104,7 @@ export class Terminal {
|
|||||||
window.addEventListener("resize", this.onResize);
|
window.addEventListener("resize", this.onResize);
|
||||||
|
|
||||||
this.disposers.push(
|
this.disposers.push(
|
||||||
reaction(() => toJS(themeStore.activeTheme.colors), this.setTheme, {
|
reaction(() => toJS(ThemeStore.getInstance().activeTheme.colors), this.setTheme, {
|
||||||
fireImmediately: true
|
fireImmediately: true
|
||||||
}),
|
}),
|
||||||
dockStore.onResize(this.onResize),
|
dockStore.onResize(this.onResize),
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import { Menu, MenuItem } from "../menu";
|
|||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { observable } from "mobx";
|
import { observable } from "mobx";
|
||||||
import { navigate } from "../../navigation";
|
import { navigate } from "../../navigation";
|
||||||
import { hotbarStore } from "../../../common/hotbar-store";
|
import { HotbarStore } from "../../../common/hotbar-store";
|
||||||
import { ConfirmDialog } from "../confirm-dialog";
|
import { ConfirmDialog } from "../confirm-dialog";
|
||||||
|
|
||||||
interface Props extends DOMAttributes<HTMLElement> {
|
interface Props extends DOMAttributes<HTMLElement> {
|
||||||
@ -60,7 +60,7 @@ export class HotbarIcon extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
removeFromHotbar(item: CatalogEntity) {
|
removeFromHotbar(item: CatalogEntity) {
|
||||||
const hotbar = hotbarStore.getByName("default"); // FIXME
|
const hotbar = HotbarStore.getInstance().getByName("default"); // FIXME
|
||||||
|
|
||||||
if (!hotbar) {
|
if (!hotbar) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user