mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge branch 'master' into font-settings-for-editor-and-ternimal
# Conflicts: # src/renderer/components/+preferences/application.tsx # src/renderer/components/+preferences/preferences.tsx
This commit is contained in:
commit
3fd2d6121d
@ -16,6 +16,12 @@ module.exports = {
|
|||||||
react: {
|
react: {
|
||||||
version: packageJson.devDependencies.react || "detect",
|
version: packageJson.devDependencies.react || "detect",
|
||||||
},
|
},
|
||||||
|
// the package eslint-import-resolver-typescript is required for this line which fixes errors when using .d.ts files
|
||||||
|
"import/resolver": {
|
||||||
|
"typescript": {
|
||||||
|
"alwaysTryTypes": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -20,6 +20,7 @@ Each guide or code sample includes the following:
|
|||||||
| [Main process extension](main-extension.md) | Main.LensExtension |
|
| [Main process extension](main-extension.md) | Main.LensExtension |
|
||||||
| [Renderer process extension](renderer-extension.md) | Renderer.LensExtension |
|
| [Renderer process extension](renderer-extension.md) | Renderer.LensExtension |
|
||||||
| [Resource stack (cluster feature)](resource-stack.md) | |
|
| [Resource stack (cluster feature)](resource-stack.md) | |
|
||||||
|
| [Extending KubernetesCluster)](extending-kubernetes-cluster.md) | |
|
||||||
| [Stores](stores.md) | |
|
| [Stores](stores.md) | |
|
||||||
| [Components](components.md) | |
|
| [Components](components.md) | |
|
||||||
| [KubeObjectListLayout](kube-object-list-layout.md) | |
|
| [KubeObjectListLayout](kube-object-list-layout.md) | |
|
||||||
|
|||||||
69
docs/extensions/guides/extending-kubernetes-cluster.md
Normal file
69
docs/extensions/guides/extending-kubernetes-cluster.md
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
# Extending KubernetesCluster
|
||||||
|
|
||||||
|
Extension can specify it's own subclass of Common.Catalog.KubernetesCluster. Extension can also specify a new Category for it in the Catalog.
|
||||||
|
|
||||||
|
## Extending Common.Catalog.KubernetesCluster
|
||||||
|
|
||||||
|
``` typescript
|
||||||
|
import { Common } from "@k8slens/extensions";
|
||||||
|
|
||||||
|
// The kind must be different from KubernetesCluster's kind
|
||||||
|
export const kind = "ManagedDevCluster";
|
||||||
|
|
||||||
|
export class ManagedDevCluster extends Common.Catalog.KubernetesCluster {
|
||||||
|
public static readonly kind = kind;
|
||||||
|
|
||||||
|
public readonly kind = kind;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Extending Common.Catalog.CatalogCategory
|
||||||
|
|
||||||
|
These custom Catalog entities can be added a new Category in the Catalog.
|
||||||
|
|
||||||
|
``` typescript
|
||||||
|
import { Common } from "@k8slens/extensions";
|
||||||
|
import { kind, ManagedDevCluster } from "../entities/ManagedDevCluster";
|
||||||
|
|
||||||
|
class ManagedDevClusterCategory extends Common.Catalog.CatalogCategory {
|
||||||
|
public readonly apiVersion = "catalog.k8slens.dev/v1alpha1";
|
||||||
|
public readonly kind = "CatalogCategory";
|
||||||
|
public metadata = {
|
||||||
|
name: "Managed Dev Clusters",
|
||||||
|
icon: ""
|
||||||
|
};
|
||||||
|
public spec: Common.Catalog.CatalogCategorySpec = {
|
||||||
|
group: "entity.k8slens.dev",
|
||||||
|
versions: [
|
||||||
|
{
|
||||||
|
name: "v1alpha1",
|
||||||
|
entityClass: ManagedDevCluster as any,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
names: {
|
||||||
|
kind
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export { ManagedDevClusterCategory };
|
||||||
|
export type { ManagedDevClusterCategory as ManagedDevClusterCategoryType };
|
||||||
|
```
|
||||||
|
|
||||||
|
The category needs to be registered in the `onActivate()` method both in main and renderer
|
||||||
|
|
||||||
|
``` typescript
|
||||||
|
// in main's on onActivate
|
||||||
|
Main.Catalog.catalogCategories.add(new ManagedDevClusterCategory());
|
||||||
|
```
|
||||||
|
|
||||||
|
``` typescript
|
||||||
|
// in renderer's on onActivate
|
||||||
|
Renderer.Catalog.catalogCategories.add(new ManagedDevClusterCategory());
|
||||||
|
```
|
||||||
|
|
||||||
|
You can then add the entities to the Catalog as a new source:
|
||||||
|
|
||||||
|
``` typescript
|
||||||
|
this.addCatalogSource("managedDevClusters", this.managedDevClusters);
|
||||||
|
```
|
||||||
@ -24,6 +24,7 @@ nav:
|
|||||||
- Renderer Extension: extensions/guides/renderer-extension.md
|
- Renderer Extension: extensions/guides/renderer-extension.md
|
||||||
- Catalog: extensions/guides/catalog.md
|
- Catalog: extensions/guides/catalog.md
|
||||||
- Resource Stack: extensions/guides/resource-stack.md
|
- Resource Stack: extensions/guides/resource-stack.md
|
||||||
|
- Extending KubernetesCluster: extensions/guides/extending-kubernetes-cluster.md
|
||||||
- Stores: extensions/guides/stores.md
|
- Stores: extensions/guides/stores.md
|
||||||
- Working with MobX: extensions/guides/working-with-mobx.md
|
- Working with MobX: extensions/guides/working-with-mobx.md
|
||||||
- Protocol Handlers: extensions/guides/protocol-handlers.md
|
- Protocol Handlers: extensions/guides/protocol-handlers.md
|
||||||
|
|||||||
@ -340,6 +340,7 @@
|
|||||||
"esbuild": "^0.13.15",
|
"esbuild": "^0.13.15",
|
||||||
"esbuild-loader": "^2.16.0",
|
"esbuild-loader": "^2.16.0",
|
||||||
"eslint": "^7.32.0",
|
"eslint": "^7.32.0",
|
||||||
|
"eslint-import-resolver-typescript": "^2.5.0",
|
||||||
"eslint-plugin-header": "^3.1.1",
|
"eslint-plugin-header": "^3.1.1",
|
||||||
"eslint-plugin-import": "^2.25.3",
|
"eslint-plugin-import": "^2.25.3",
|
||||||
"eslint-plugin-react": "^7.27.1",
|
"eslint-plugin-react": "^7.27.1",
|
||||||
|
|||||||
@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import mockFs from "mock-fs";
|
import mockFs from "mock-fs";
|
||||||
import yaml from "js-yaml";
|
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import fse from "fs-extra";
|
import fse from "fs-extra";
|
||||||
import type { Cluster } from "../cluster/cluster";
|
import type { Cluster } from "../cluster/cluster";
|
||||||
@ -334,159 +333,6 @@ users:
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("pre 2.0 config with an existing cluster", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
ClusterStore.resetInstance();
|
|
||||||
|
|
||||||
const mockOpts = {
|
|
||||||
"some-directory-for-user-data": {
|
|
||||||
"lens-cluster-store.json": JSON.stringify({
|
|
||||||
__internal__: {
|
|
||||||
migrations: {
|
|
||||||
version: "1.0.0",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
cluster1: minimalValidKubeConfig,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
mockFs(mockOpts);
|
|
||||||
|
|
||||||
clusterStore = mainDi.inject(clusterStoreInjectable);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
mockFs.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("migrates to modern format with kubeconfig in a file", async () => {
|
|
||||||
const config = clusterStore.clustersList[0].kubeConfigPath;
|
|
||||||
|
|
||||||
expect(fs.readFileSync(config, "utf8")).toContain(`"contexts":[`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("pre 2.6.0 config with a cluster that has arrays in auth config", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
ClusterStore.resetInstance();
|
|
||||||
const mockOpts = {
|
|
||||||
"some-directory-for-user-data": {
|
|
||||||
"lens-cluster-store.json": JSON.stringify({
|
|
||||||
__internal__: {
|
|
||||||
migrations: {
|
|
||||||
version: "2.4.1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
cluster1: {
|
|
||||||
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);
|
|
||||||
|
|
||||||
clusterStore = mainDi.inject(clusterStoreInjectable);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
mockFs.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("replaces array format access token and expiry into string", async () => {
|
|
||||||
const file = clusterStore.clustersList[0].kubeConfigPath;
|
|
||||||
const config = fs.readFileSync(file, "utf8");
|
|
||||||
const kc = yaml.load(config) as Record<string, any>;
|
|
||||||
|
|
||||||
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",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("pre 2.6.0 config with a cluster icon", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
ClusterStore.resetInstance();
|
|
||||||
const mockOpts = {
|
|
||||||
"some-directory-for-user-data": {
|
|
||||||
"lens-cluster-store.json": JSON.stringify({
|
|
||||||
__internal__: {
|
|
||||||
migrations: {
|
|
||||||
version: "2.4.1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
cluster1: {
|
|
||||||
kubeConfig: minimalValidKubeConfig,
|
|
||||||
icon: "icon_path",
|
|
||||||
preferences: {
|
|
||||||
terminalCWD: "/some-directory-for-user-data",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
icon_path: testDataIcon,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
mockFs(mockOpts);
|
|
||||||
|
|
||||||
clusterStore = mainDi.inject(clusterStoreInjectable);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
mockFs.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("moves the icon into preferences", async () => {
|
|
||||||
const storedClusterData = clusterStore.clustersList[0];
|
|
||||||
|
|
||||||
expect(Object.prototype.hasOwnProperty.call(storedClusterData, "icon")).toBe(false);
|
|
||||||
expect(Object.prototype.hasOwnProperty.call(storedClusterData.preferences, "icon")).toBe(true);
|
|
||||||
expect(storedClusterData.preferences.icon.startsWith("data:;base64,")).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
|
describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ClusterStore.resetInstance();
|
ClusterStore.resetInstance();
|
||||||
|
|||||||
@ -59,8 +59,8 @@ export interface KubernetesClusterStatus extends CatalogEntityStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class KubernetesCluster extends CatalogEntity<KubernetesClusterMetadata, KubernetesClusterStatus, KubernetesClusterSpec> {
|
export class KubernetesCluster extends CatalogEntity<KubernetesClusterMetadata, KubernetesClusterStatus, KubernetesClusterSpec> {
|
||||||
public static readonly apiVersion = "entity.k8slens.dev/v1alpha1";
|
public static readonly apiVersion: string = "entity.k8slens.dev/v1alpha1";
|
||||||
public static readonly kind = "KubernetesCluster";
|
public static readonly kind: string = "KubernetesCluster";
|
||||||
|
|
||||||
public readonly apiVersion = KubernetesCluster.apiVersion;
|
public readonly apiVersion = KubernetesCluster.apiVersion;
|
||||||
public readonly kind = KubernetesCluster.kind;
|
public readonly kind = KubernetesCluster.kind;
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export type { AppPreferenceRegistration, AppPreferenceComponents } from "../registries/app-preference-registry";
|
export type { AppPreferenceRegistration, AppPreferenceComponents } from "../../renderer/components/+preferences/app-preferences/app-preference-registration";
|
||||||
export type { KubeObjectDetailRegistration, KubeObjectDetailComponents } from "../registries/kube-object-detail-registry";
|
export type { KubeObjectDetailRegistration, KubeObjectDetailComponents } from "../registries/kube-object-detail-registry";
|
||||||
export type { KubeObjectMenuRegistration, KubeObjectMenuComponents } from "../registries/kube-object-menu-registry";
|
export type { KubeObjectMenuRegistration, KubeObjectMenuComponents } from "../registries/kube-object-menu-registry";
|
||||||
export type { KubeObjectStatusRegistration } from "../registries/kube-object-status-registry";
|
export type { KubeObjectStatusRegistration } from "../registries/kube-object-status-registry";
|
||||||
|
|||||||
@ -252,7 +252,6 @@ export class ExtensionLoader {
|
|||||||
return this.autoInitExtensions(async (extension: LensRendererExtension) => {
|
return this.autoInitExtensions(async (extension: LensRendererExtension) => {
|
||||||
const removeItems = [
|
const removeItems = [
|
||||||
registries.GlobalPageRegistry.getInstance().add(extension.globalPages, extension),
|
registries.GlobalPageRegistry.getInstance().add(extension.globalPages, extension),
|
||||||
registries.AppPreferenceRegistry.getInstance().add(extension.appPreferences),
|
|
||||||
registries.EntitySettingRegistry.getInstance().add(extension.entitySettings),
|
registries.EntitySettingRegistry.getInstance().add(extension.entitySettings),
|
||||||
registries.StatusBarRegistry.getInstance().add(extension.statusBarItems),
|
registries.StatusBarRegistry.getInstance().add(extension.statusBarItems),
|
||||||
registries.CatalogEntityDetailRegistry.getInstance().add(extension.catalogEntityDetailItems),
|
registries.CatalogEntityDetailRegistry.getInstance().add(extension.catalogEntityDetailItems),
|
||||||
@ -270,11 +269,12 @@ export class ExtensionLoader {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
loadOnClusterRenderer = (entity: KubernetesCluster) => {
|
loadOnClusterRenderer = (getCluster: () => KubernetesCluster) => {
|
||||||
logger.debug(`${logModule}: load on cluster renderer (dashboard)`);
|
logger.debug(`${logModule}: load on cluster renderer (dashboard)`);
|
||||||
|
|
||||||
this.autoInitExtensions(async (extension: LensRendererExtension) => {
|
this.autoInitExtensions(async (extension: LensRendererExtension) => {
|
||||||
if ((await extension.isEnabledForCluster(entity)) === false) {
|
// getCluster must be a callback, as the entity might be available only after an extension has been loaded
|
||||||
|
if ((await extension.isEnabledForCluster(getCluster())) === false) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -299,11 +299,15 @@ export class ExtensionLoader {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
protected autoInitExtensions(register: (ext: LensExtension) => Promise<Disposer[]>) {
|
protected async loadExtensions(installedExtensions: Map<string, InstalledExtension>, register: (ext: LensExtension) => Promise<Disposer[]>) {
|
||||||
const loadingExtensions: ExtensionLoading[] = [];
|
// Steps of the function:
|
||||||
|
// 1. require and call .activate for each Extension
|
||||||
|
// 2. Wait until every extension's onActivate has been resolved
|
||||||
|
// 3. Call .enable for each extension
|
||||||
|
// 4. Return ExtensionLoading[]
|
||||||
|
|
||||||
reaction(() => this.toJSON(), async installedExtensions => {
|
const extensions = [...installedExtensions.entries()]
|
||||||
for (const [extId, extension] of installedExtensions) {
|
.map(([extId, extension]) => {
|
||||||
const alreadyInit = this.instances.has(extId) || this.nonInstancesByName.has(extension.manifest.name);
|
const alreadyInit = this.instances.has(extId) || this.nonInstancesByName.has(extension.manifest.name);
|
||||||
|
|
||||||
if (extension.isCompatible && extension.isEnabled && !alreadyInit) {
|
if (extension.isCompatible && extension.isEnabled && !alreadyInit) {
|
||||||
@ -312,7 +316,8 @@ export class ExtensionLoader {
|
|||||||
|
|
||||||
if (!LensExtensionClass) {
|
if (!LensExtensionClass) {
|
||||||
this.nonInstancesByName.add(extension.manifest.name);
|
this.nonInstancesByName.add(extension.manifest.name);
|
||||||
continue;
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const instance = this.dependencies.createExtensionInstance(
|
const instance = this.dependencies.createExtensionInstance(
|
||||||
@ -320,27 +325,49 @@ export class ExtensionLoader {
|
|||||||
extension,
|
extension,
|
||||||
);
|
);
|
||||||
|
|
||||||
const loaded = instance.enable(register).catch((err) => {
|
return {
|
||||||
logger.error(`${logModule}: failed to enable`, { ext: extension, err });
|
extId,
|
||||||
});
|
instance,
|
||||||
|
|
||||||
loadingExtensions.push({
|
|
||||||
isBundled: extension.isBundled,
|
isBundled: extension.isBundled,
|
||||||
loaded,
|
activated: instance.activate(),
|
||||||
});
|
};
|
||||||
this.instances.set(extId, instance);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(`${logModule}: activation extension error`, { ext: extension, err });
|
logger.error(`${logModule}: activation extension error`, { ext: extension, err });
|
||||||
}
|
}
|
||||||
} else if (!extension.isEnabled && alreadyInit) {
|
} else if (!extension.isEnabled && alreadyInit) {
|
||||||
this.removeInstance(extId);
|
this.removeInstance(extId);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}, {
|
|
||||||
fireImmediately: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
return loadingExtensions;
|
return null;
|
||||||
|
})
|
||||||
|
// Remove null values
|
||||||
|
.filter(extension => Boolean(extension));
|
||||||
|
|
||||||
|
// We first need to wait until each extension's `onActivate` is resolved,
|
||||||
|
// as this might register new catalog categories. Afterwards we can safely .enable the extension.
|
||||||
|
await Promise.all(extensions.map(extension => extension.activated));
|
||||||
|
|
||||||
|
// Return ExtensionLoading[]
|
||||||
|
return extensions.map(extension => {
|
||||||
|
const loaded = extension.instance.enable(register).catch((err) => {
|
||||||
|
logger.error(`${logModule}: failed to enable`, { ext: extension, err });
|
||||||
|
});
|
||||||
|
|
||||||
|
this.instances.set(extension.extId, extension.instance);
|
||||||
|
|
||||||
|
return {
|
||||||
|
isBundled: extension.isBundled,
|
||||||
|
loaded,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected autoInitExtensions(register: (ext: LensExtension) => Promise<Disposer[]>) {
|
||||||
|
// Setup reaction to load extensions on JSON changes
|
||||||
|
reaction(() => this.toJSON(), installedExtensions => this.loadExtensions(installedExtensions, register));
|
||||||
|
|
||||||
|
// Load initial extensions
|
||||||
|
return this.loadExtensions(this.toJSON(), register);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected requireExtension(extension: InstalledExtension): LensExtensionConstructor | null {
|
protected requireExtension(extension: InstalledExtension): LensExtensionConstructor | null {
|
||||||
|
|||||||
@ -86,7 +86,6 @@ export class LensExtension {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.onActivate();
|
|
||||||
this._isEnabled = true;
|
this._isEnabled = true;
|
||||||
|
|
||||||
this[Disposers].push(...await register(this));
|
this[Disposers].push(...await register(this));
|
||||||
@ -113,6 +112,11 @@ export class LensExtension {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
activate() {
|
||||||
|
return this.onActivate();
|
||||||
|
}
|
||||||
|
|
||||||
protected onActivate(): Promise<void> | void {
|
protected onActivate(): Promise<void> | void {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,13 +15,14 @@ import type { KubernetesCluster } from "../common/catalog-entities";
|
|||||||
import type { WelcomeMenuRegistration } from "../renderer/components/+welcome/welcome-menu-items/welcome-menu-registration";
|
import type { WelcomeMenuRegistration } from "../renderer/components/+welcome/welcome-menu-items/welcome-menu-registration";
|
||||||
import type { WelcomeBannerRegistration } from "../renderer/components/+welcome/welcome-banner-items/welcome-banner-registration";
|
import type { WelcomeBannerRegistration } from "../renderer/components/+welcome/welcome-banner-items/welcome-banner-registration";
|
||||||
import type { CommandRegistration } from "../renderer/components/command-palette/registered-commands/commands";
|
import type { CommandRegistration } from "../renderer/components/command-palette/registered-commands/commands";
|
||||||
|
import type { AppPreferenceRegistration } from "../renderer/components/+preferences/app-preferences/app-preference-registration";
|
||||||
|
|
||||||
export class LensRendererExtension extends LensExtension {
|
export class LensRendererExtension extends LensExtension {
|
||||||
globalPages: registries.PageRegistration[] = [];
|
globalPages: registries.PageRegistration[] = [];
|
||||||
clusterPages: registries.PageRegistration[] = [];
|
clusterPages: registries.PageRegistration[] = [];
|
||||||
clusterPageMenus: registries.ClusterPageMenuRegistration[] = [];
|
clusterPageMenus: registries.ClusterPageMenuRegistration[] = [];
|
||||||
kubeObjectStatusTexts: registries.KubeObjectStatusRegistration[] = [];
|
kubeObjectStatusTexts: registries.KubeObjectStatusRegistration[] = [];
|
||||||
appPreferences: registries.AppPreferenceRegistration[] = [];
|
appPreferences: AppPreferenceRegistration[] = [];
|
||||||
entitySettings: registries.EntitySettingRegistration[] = [];
|
entitySettings: registries.EntitySettingRegistration[] = [];
|
||||||
statusBarItems: registries.StatusBarRegistration[] = [];
|
statusBarItems: registries.StatusBarRegistration[] = [];
|
||||||
kubeObjectDetailItems: registries.KubeObjectDetailRegistration[] = [];
|
kubeObjectDetailItems: registries.KubeObjectDetailRegistration[] = [];
|
||||||
|
|||||||
@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
export * from "./page-registry";
|
export * from "./page-registry";
|
||||||
export * from "./page-menu-registry";
|
export * from "./page-menu-registry";
|
||||||
export * from "./app-preference-registry";
|
|
||||||
export * from "./status-bar-registry";
|
export * from "./status-bar-registry";
|
||||||
export * from "./kube-object-detail-registry";
|
export * from "./kube-object-detail-registry";
|
||||||
export * from "./kube-object-menu-registry";
|
export * from "./kube-object-menu-registry";
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { action, computed, IComputedValue, IObservableArray, makeObservable, observable } from "mobx";
|
import { action, computed, IComputedValue, IObservableArray, makeObservable, observable } from "mobx";
|
||||||
import { CatalogCategoryRegistry, catalogCategoryRegistry, CatalogEntity, CatalogEntityConstructor, CatalogEntityKindData } from "../../common/catalog";
|
import { CatalogCategoryRegistry, catalogCategoryRegistry, CatalogEntity, CatalogEntityConstructor } from "../../common/catalog";
|
||||||
import { iter } from "../../common/utils";
|
import { iter } from "../../common/utils";
|
||||||
|
|
||||||
export class CatalogEntityRegistry {
|
export class CatalogEntityRegistry {
|
||||||
@ -43,8 +43,8 @@ export class CatalogEntityRegistry {
|
|||||||
return this.items.filter((item) => item.apiVersion === apiVersion && item.kind === kind) as T[];
|
return this.items.filter((item) => item.apiVersion === apiVersion && item.kind === kind) as T[];
|
||||||
}
|
}
|
||||||
|
|
||||||
getItemsByEntityClass<T extends CatalogEntity>({ apiVersion, kind }: CatalogEntityKindData & CatalogEntityConstructor<T>): T[] {
|
getItemsByEntityClass<T extends CatalogEntity>(constructor: CatalogEntityConstructor<T>): T[] {
|
||||||
return this.getItemsForApiKind(apiVersion, kind);
|
return this.items.filter((item) => item instanceof constructor) as T[];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,24 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { MigrationDeclaration } from "../helpers";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Early store format had the kubeconfig directly under context name, this moves
|
|
||||||
* it under the kubeConfig key
|
|
||||||
*/
|
|
||||||
|
|
||||||
export default {
|
|
||||||
version: "2.0.0-beta.2",
|
|
||||||
run(store) {
|
|
||||||
for (const value of store) {
|
|
||||||
const contextName = value[0];
|
|
||||||
|
|
||||||
// Looping all the keys gives out the store internal stuff too...
|
|
||||||
if (contextName === "__internal__" || Object.prototype.hasOwnProperty.call(value[1], "kubeConfig")) continue;
|
|
||||||
store.set(contextName, { kubeConfig: value[1] });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
} as MigrationDeclaration;
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { MigrationDeclaration } from "../helpers";
|
|
||||||
|
|
||||||
// Cleans up a store that had the state related data stored
|
|
||||||
|
|
||||||
export default {
|
|
||||||
version: "2.4.1",
|
|
||||||
run(store) {
|
|
||||||
for (const value of store) {
|
|
||||||
const contextName = value[0];
|
|
||||||
|
|
||||||
if (contextName === "__internal__") continue;
|
|
||||||
const cluster = value[1];
|
|
||||||
|
|
||||||
store.set(contextName, { kubeConfig: cluster.kubeConfig, icon: cluster.icon || null, preferences: cluster.preferences || {}});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
} as MigrationDeclaration;
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Move cluster icon from root to preferences
|
|
||||||
import type { MigrationDeclaration } from "../helpers";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
version: "2.6.0-beta.2",
|
|
||||||
run(store) {
|
|
||||||
for (const value of store) {
|
|
||||||
const clusterKey = value[0];
|
|
||||||
|
|
||||||
if (clusterKey === "__internal__") continue;
|
|
||||||
const cluster = value[1];
|
|
||||||
|
|
||||||
if (!cluster.preferences) cluster.preferences = {};
|
|
||||||
|
|
||||||
if (cluster.icon) {
|
|
||||||
cluster.preferences.icon = cluster.icon;
|
|
||||||
delete (cluster["icon"]);
|
|
||||||
}
|
|
||||||
store.set(clusterKey, { contextName: clusterKey, kubeConfig: value[1].kubeConfig, preferences: value[1].preferences });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
} as MigrationDeclaration;
|
|
||||||
@ -1,53 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import yaml from "js-yaml";
|
|
||||||
import { MigrationDeclaration, migrationLog } from "../helpers";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
version: "2.6.0-beta.3",
|
|
||||||
run(store) {
|
|
||||||
for (const value of store) {
|
|
||||||
const clusterKey = value[0];
|
|
||||||
|
|
||||||
if (clusterKey === "__internal__") continue;
|
|
||||||
const cluster = value[1];
|
|
||||||
|
|
||||||
if (!cluster.kubeConfig) continue;
|
|
||||||
const config = yaml.load(cluster.kubeConfig);
|
|
||||||
|
|
||||||
if (!config || typeof config !== "object" || !Object.prototype.hasOwnProperty.call(config, "users")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const kubeConfig = config as Record<string, any>;
|
|
||||||
const userObj = kubeConfig.users[0];
|
|
||||||
|
|
||||||
if (userObj) {
|
|
||||||
const user = userObj.user;
|
|
||||||
|
|
||||||
if (user["auth-provider"] && user["auth-provider"].config) {
|
|
||||||
const authConfig = user["auth-provider"].config;
|
|
||||||
|
|
||||||
if (authConfig["access-token"]) {
|
|
||||||
authConfig["access-token"] = `${authConfig["access-token"]}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authConfig.expiry) {
|
|
||||||
authConfig.expiry = `${authConfig.expiry}`;
|
|
||||||
}
|
|
||||||
migrationLog(authConfig);
|
|
||||||
user["auth-provider"].config = authConfig;
|
|
||||||
kubeConfig.users = [{
|
|
||||||
name: userObj.name,
|
|
||||||
user,
|
|
||||||
}];
|
|
||||||
cluster.kubeConfig = yaml.dump(kubeConfig);
|
|
||||||
store.set(clusterKey, cluster);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
} as MigrationDeclaration;
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Add existing clusters to "default" workspace
|
|
||||||
import type { MigrationDeclaration } from "../helpers";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
version: "2.7.0-beta.0",
|
|
||||||
run(store) {
|
|
||||||
for (const value of store) {
|
|
||||||
const clusterKey = value[0];
|
|
||||||
|
|
||||||
if (clusterKey === "__internal__") continue;
|
|
||||||
const cluster = value[1];
|
|
||||||
|
|
||||||
cluster.workspace = "default";
|
|
||||||
store.set(clusterKey, cluster);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
} as MigrationDeclaration;
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Add id for clusters and store them to array
|
|
||||||
import { v4 as uuid } from "uuid";
|
|
||||||
import type { MigrationDeclaration } from "../helpers";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
version: "2.7.0-beta.1",
|
|
||||||
run(store) {
|
|
||||||
const clusters: any[] = [];
|
|
||||||
|
|
||||||
for (const value of store) {
|
|
||||||
const clusterKey = value[0];
|
|
||||||
|
|
||||||
if (clusterKey === "__internal__") continue;
|
|
||||||
if (clusterKey === "clusters") continue;
|
|
||||||
const cluster = value[1];
|
|
||||||
|
|
||||||
cluster.id = uuid();
|
|
||||||
|
|
||||||
if (!cluster.preferences.clusterName) {
|
|
||||||
cluster.preferences.clusterName = clusterKey;
|
|
||||||
}
|
|
||||||
clusters.push(cluster);
|
|
||||||
store.delete(clusterKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clusters.length > 0) {
|
|
||||||
store.set("clusters", clusters);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
} as MigrationDeclaration;
|
|
||||||
@ -7,24 +7,12 @@
|
|||||||
|
|
||||||
import { joinMigrations } from "../helpers";
|
import { joinMigrations } from "../helpers";
|
||||||
|
|
||||||
import version200Beta2 from "./2.0.0-beta.2";
|
|
||||||
import version241 from "./2.4.1";
|
|
||||||
import version260Beta2 from "./2.6.0-beta.2";
|
|
||||||
import version260Beta3 from "./2.6.0-beta.3";
|
|
||||||
import version270Beta0 from "./2.7.0-beta.0";
|
|
||||||
import version270Beta1 from "./2.7.0-beta.1";
|
|
||||||
import version360Beta1 from "./3.6.0-beta.1";
|
import version360Beta1 from "./3.6.0-beta.1";
|
||||||
import version500Beta10 from "./5.0.0-beta.10";
|
import version500Beta10 from "./5.0.0-beta.10";
|
||||||
import version500Beta13 from "./5.0.0-beta.13";
|
import version500Beta13 from "./5.0.0-beta.13";
|
||||||
import snap from "./snap";
|
import snap from "./snap";
|
||||||
|
|
||||||
export default joinMigrations(
|
export default joinMigrations(
|
||||||
version200Beta2,
|
|
||||||
version241,
|
|
||||||
version260Beta2,
|
|
||||||
version260Beta3,
|
|
||||||
version270Beta0,
|
|
||||||
version270Beta1,
|
|
||||||
version360Beta1,
|
version360Beta1,
|
||||||
version500Beta10,
|
version500Beta10,
|
||||||
version500Beta13,
|
version500Beta13,
|
||||||
|
|||||||
@ -47,10 +47,25 @@ export class CatalogEntityRegistry {
|
|||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
get activeEntity(): CatalogEntity | null {
|
protected getActiveEntityById() {
|
||||||
return this._entities.get(this.activeEntityId) || null;
|
return this._entities.get(this.activeEntityId) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get activeEntity(): CatalogEntity | null {
|
||||||
|
const entity = this.getActiveEntityById();
|
||||||
|
|
||||||
|
// If the entity was not found but there are rawEntities to be processed,
|
||||||
|
// try to process them and return the entity.
|
||||||
|
// This might happen if an extension registered a new Catalog category.
|
||||||
|
if (this.activeEntityId && !entity && this.rawEntities.length > 0) {
|
||||||
|
this.processRawEntities();
|
||||||
|
|
||||||
|
return this.getActiveEntityById();
|
||||||
|
}
|
||||||
|
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
set activeEntity(raw: CatalogEntity | string | null) {
|
set activeEntity(raw: CatalogEntity | string | null) {
|
||||||
if (raw) {
|
if (raw) {
|
||||||
const id = typeof raw === "string"
|
const id = typeof raw === "string"
|
||||||
|
|||||||
@ -98,9 +98,6 @@ export async function bootstrap(di: DependencyInjectionContainer) {
|
|||||||
logger.info(`${logPrefix} initializing IpcRendererListeners`);
|
logger.info(`${logPrefix} initializing IpcRendererListeners`);
|
||||||
initializers.initIpcRendererListeners(extensionLoader);
|
initializers.initIpcRendererListeners(extensionLoader);
|
||||||
|
|
||||||
logger.info(`${logPrefix} initializing StatusBarRegistry`);
|
|
||||||
initializers.initStatusBarRegistry();
|
|
||||||
|
|
||||||
extensionLoader.init();
|
extensionLoader.init();
|
||||||
|
|
||||||
const extensionDiscovery = di.inject(extensionDiscoveryInjectable);
|
const extensionDiscovery = di.inject(extensionDiscoveryInjectable);
|
||||||
|
|||||||
@ -4,7 +4,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { BaseRegistry } from "./base-registry";
|
|
||||||
|
|
||||||
export interface AppPreferenceComponents {
|
export interface AppPreferenceComponents {
|
||||||
Hint: React.ComponentType<any>;
|
Hint: React.ComponentType<any>;
|
||||||
@ -22,11 +21,3 @@ export interface RegisteredAppPreference extends AppPreferenceRegistration {
|
|||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AppPreferenceRegistry extends BaseRegistry<AppPreferenceRegistration, RegisteredAppPreference> {
|
|
||||||
getRegisteredItem(item: AppPreferenceRegistration): RegisteredAppPreference {
|
|
||||||
return {
|
|
||||||
id: item.id || item.title.toLowerCase().replace(/[^0-9a-zA-Z]+/g, "-"),
|
|
||||||
...item,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import rendererExtensionsInjectable from "../../../../extensions/renderer-extensions.injectable";
|
||||||
|
import { getAppPreferences } from "./get-app-preferences";
|
||||||
|
|
||||||
|
const appPreferencesInjectable = getInjectable({
|
||||||
|
instantiate: (di) =>
|
||||||
|
getAppPreferences({
|
||||||
|
extensions: di.inject(rendererExtensionsInjectable),
|
||||||
|
}),
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default appPreferencesInjectable;
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { computed, IComputedValue } from "mobx";
|
||||||
|
import type { LensRendererExtension } from "../../../../extensions/lens-renderer-extension";
|
||||||
|
import type { AppPreferenceRegistration, RegisteredAppPreference } from "./app-preference-registration";
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
extensions: IComputedValue<LensRendererExtension[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRegisteredItem(item: AppPreferenceRegistration): RegisteredAppPreference {
|
||||||
|
return {
|
||||||
|
id: item.id || item.title.toLowerCase().replace(/[^0-9a-zA-Z]+/g, "-"),
|
||||||
|
...item,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const getAppPreferences = ({ extensions }: Dependencies) => {
|
||||||
|
return computed(() => (
|
||||||
|
extensions.get()
|
||||||
|
.flatMap((extension) => extension.appPreferences)
|
||||||
|
.map(getRegisteredItem)
|
||||||
|
));
|
||||||
|
};
|
||||||
@ -13,10 +13,12 @@ import { Input } from "../input";
|
|||||||
import { Switch } from "../switch";
|
import { Switch } from "../switch";
|
||||||
import moment from "moment-timezone";
|
import moment from "moment-timezone";
|
||||||
import { CONSTANTS, defaultExtensionRegistryUrl, ExtensionRegistryLocation } from "../../../common/user-store/preferences-helpers";
|
import { CONSTANTS, defaultExtensionRegistryUrl, ExtensionRegistryLocation } from "../../../common/user-store/preferences-helpers";
|
||||||
import { action } from "mobx";
|
import { action, IComputedValue } from "mobx";
|
||||||
import { isUrl } from "../input/input_validators";
|
import { isUrl } from "../input/input_validators";
|
||||||
import { AppPreferenceRegistry } from "../../../extensions/registries";
|
|
||||||
import { ExtensionSettings } from "./extension-settings";
|
import { ExtensionSettings } from "./extension-settings";
|
||||||
|
import type { RegisteredAppPreference } from "./app-preferences/app-preference-registration";
|
||||||
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
|
import appPreferencesInjectable from "./app-preferences/app-preferences.injectable";
|
||||||
|
|
||||||
const timezoneOptions: SelectOption<string>[] = moment.tz.names().map(zone => ({
|
const timezoneOptions: SelectOption<string>[] = moment.tz.names().map(zone => ({
|
||||||
label: zone,
|
label: zone,
|
||||||
@ -27,18 +29,23 @@ const updateChannelOptions: SelectOption<string>[] = Array.from(
|
|||||||
([value, { label }]) => ({ value, label }),
|
([value, { label }]) => ({ value, label }),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const Application = observer(() => {
|
interface Dependencies {
|
||||||
|
appPreferenceItems: IComputedValue<RegisteredAppPreference[]>
|
||||||
|
}
|
||||||
|
|
||||||
|
const NonInjectedApplication: React.FC<Dependencies> = ({ appPreferenceItems }) => {
|
||||||
const userStore = UserStore.getInstance();
|
const userStore = UserStore.getInstance();
|
||||||
const [customUrl, setCustomUrl] = React.useState(userStore.extensionRegistryUrl.customUrl || "");
|
const [customUrl, setCustomUrl] = React.useState(userStore.extensionRegistryUrl.customUrl || "");
|
||||||
const extensionSettings = AppPreferenceRegistry.getInstance().getItems().filter((preference) => preference.showInPreferencesTab === "application");
|
const extensionSettings = appPreferenceItems.get().filter((preference) => preference.showInPreferencesTab === "application");
|
||||||
|
const themeStore = ThemeStore.getInstance();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section id="application">
|
<section id="application">
|
||||||
<h2 data-testid="application-header">Application</h2>
|
<h2 data-testid="application-header">Application</h2>
|
||||||
<section id="appearance">
|
<section id="appearance">
|
||||||
<SubTitle title="Theme"/>
|
<SubTitle title="Theme" />
|
||||||
<Select
|
<Select
|
||||||
options={ThemeStore.getInstance().themeOptions}
|
options={themeStore.themeOptions}
|
||||||
value={userStore.colorTheme}
|
value={userStore.colorTheme}
|
||||||
onChange={({ value }) => userStore.colorTheme = value}
|
onChange={({ value }) => userStore.colorTheme = value}
|
||||||
themeName="lens"
|
themeName="lens"
|
||||||
@ -78,10 +85,10 @@ export const Application = observer(() => {
|
|||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<hr/>
|
<hr />
|
||||||
|
|
||||||
<section id="other">
|
<section id="other">
|
||||||
<SubTitle title="Start-up"/>
|
<SubTitle title="Start-up" />
|
||||||
<Switch checked={userStore.openAtLogin} onChange={() => userStore.openAtLogin = !userStore.openAtLogin}>
|
<Switch checked={userStore.openAtLogin} onChange={() => userStore.openAtLogin = !userStore.openAtLogin}>
|
||||||
Automatically start Lens on login
|
Automatically start Lens on login
|
||||||
</Switch>
|
</Switch>
|
||||||
@ -94,7 +101,7 @@ export const Application = observer(() => {
|
|||||||
))}
|
))}
|
||||||
|
|
||||||
<section id="update-channel">
|
<section id="update-channel">
|
||||||
<SubTitle title="Update Channel"/>
|
<SubTitle title="Update Channel" />
|
||||||
<Select
|
<Select
|
||||||
options={updateChannelOptions}
|
options={updateChannelOptions}
|
||||||
value={userStore.updateChannel}
|
value={userStore.updateChannel}
|
||||||
@ -116,4 +123,14 @@ export const Application = observer(() => {
|
|||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
});
|
};
|
||||||
|
|
||||||
|
export const Application = withInjectables<Dependencies>(
|
||||||
|
observer(NonInjectedApplication),
|
||||||
|
|
||||||
|
{
|
||||||
|
getProps: (di) => ({
|
||||||
|
appPreferenceItems: di.inject(appPreferencesInjectable),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|||||||
@ -3,12 +3,12 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { SubTitle } from "../layout/sub-title";
|
import { SubTitle } from "../layout/sub-title";
|
||||||
import type { RegisteredAppPreference } from "../../../extensions/registries/app-preference-registry";
|
import type { AppPreferenceRegistration } from "./app-preferences/app-preference-registration";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { cssNames } from "../../../renderer/utils";
|
import { cssNames } from "../../../renderer/utils";
|
||||||
|
|
||||||
interface ExtensionSettingsProps {
|
interface ExtensionSettingsProps {
|
||||||
setting: RegisteredAppPreference;
|
setting: AppPreferenceRegistration;
|
||||||
size: "small" | "normal"
|
size: "small" | "normal"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,13 +3,21 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
|
import type { IComputedValue } from "mobx";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { AppPreferenceRegistry } from "../../../extensions/registries";
|
import type { RegisteredAppPreference } from "./app-preferences/app-preference-registration";
|
||||||
|
import appPreferencesInjectable from "./app-preferences/app-preferences.injectable";
|
||||||
import { ExtensionSettings } from "./extension-settings";
|
import { ExtensionSettings } from "./extension-settings";
|
||||||
|
|
||||||
export const Extensions = observer(() => {
|
interface Dependencies {
|
||||||
const settings = AppPreferenceRegistry.getInstance().getItems();
|
appPreferenceItems: IComputedValue<RegisteredAppPreference[]>
|
||||||
|
}
|
||||||
|
|
||||||
|
const NonInjectedExtensions: React.FC<Dependencies> = ({ appPreferenceItems }) => {
|
||||||
|
|
||||||
|
const settings = appPreferenceItems.get();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section id="extensions">
|
<section id="extensions">
|
||||||
@ -19,4 +27,14 @@ export const Extensions = observer(() => {
|
|||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
});
|
};
|
||||||
|
|
||||||
|
export const Extensions = withInjectables<Dependencies>(
|
||||||
|
observer(NonInjectedExtensions),
|
||||||
|
|
||||||
|
{
|
||||||
|
getProps: (di) => ({
|
||||||
|
appPreferenceItems: di.inject(appPreferencesInjectable),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
import "./preferences.scss";
|
import "./preferences.scss";
|
||||||
|
|
||||||
import { makeObservable, observable } from "mobx";
|
import type { IComputedValue } from "mobx";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { matchPath, Redirect, Route, RouteProps, Switch } from "react-router";
|
import { matchPath, Redirect, Route, RouteProps, Switch } from "react-router";
|
||||||
@ -25,7 +25,6 @@ import {
|
|||||||
terminalRoute,
|
terminalRoute,
|
||||||
terminalURL,
|
terminalURL,
|
||||||
} from "../../../common/routes";
|
} from "../../../common/routes";
|
||||||
import { AppPreferenceRegistry } from "../../../extensions/registries/app-preference-registry";
|
|
||||||
import { navigateWithoutHistoryChange, navigation } from "../../navigation";
|
import { navigateWithoutHistoryChange, navigation } from "../../navigation";
|
||||||
import { SettingLayout } from "../layout/setting-layout";
|
import { SettingLayout } from "../layout/setting-layout";
|
||||||
import { Tab, Tabs } from "../tabs";
|
import { Tab, Tabs } from "../tabs";
|
||||||
@ -37,18 +36,18 @@ import { LensProxy } from "./proxy";
|
|||||||
import { Telemetry } from "./telemetry";
|
import { Telemetry } from "./telemetry";
|
||||||
import { Extensions } from "./extensions";
|
import { Extensions } from "./extensions";
|
||||||
import { sentryDsn } from "../../../common/vars";
|
import { sentryDsn } from "../../../common/vars";
|
||||||
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
|
import type { RegisteredAppPreference } from "./app-preferences/app-preference-registration";
|
||||||
|
import appPreferencesInjectable from "./app-preferences/app-preferences.injectable";
|
||||||
|
|
||||||
@observer
|
interface Dependencies {
|
||||||
export class Preferences extends React.Component {
|
appPreferenceItems: IComputedValue<RegisteredAppPreference[]>
|
||||||
@observable historyLength: number | undefined;
|
}
|
||||||
|
|
||||||
constructor(props: {}) {
|
const NonInjectedPreferences: React.FC<Dependencies> = ({ appPreferenceItems }) => {
|
||||||
super(props);
|
|
||||||
makeObservable(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderNavigation() {
|
function renderNavigation() {
|
||||||
const extensions = AppPreferenceRegistry.getInstance().getItems();
|
const extensions = appPreferenceItems.get();
|
||||||
const telemetryExtensions = extensions.filter(e => e.showInPreferencesTab == "telemetry");
|
const telemetryExtensions = extensions.filter(e => e.showInPreferencesTab == "telemetry");
|
||||||
const currentLocation = navigation.location.pathname;
|
const currentLocation = navigation.location.pathname;
|
||||||
const isActive = (route: RouteProps) => !!matchPath(currentLocation, { path: route.path, exact: route.exact });
|
const isActive = (route: RouteProps) => !!matchPath(currentLocation, { path: route.path, exact: route.exact });
|
||||||
@ -71,24 +70,32 @@ export class Preferences extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
return (
|
||||||
return (
|
<SettingLayout
|
||||||
<SettingLayout
|
navigation={renderNavigation()}
|
||||||
navigation={this.renderNavigation()}
|
className="Preferences"
|
||||||
className="Preferences"
|
contentGaps={false}
|
||||||
contentGaps={false}
|
>
|
||||||
>
|
<Switch>
|
||||||
<Switch>
|
<Route path={appURL()} component={Application}/>
|
||||||
<Route path={appURL()} component={Application}/>
|
<Route path={proxyURL()} component={LensProxy}/>
|
||||||
<Route path={proxyURL()} component={LensProxy}/>
|
<Route path={kubernetesURL()} component={Kubernetes}/>
|
||||||
<Route path={kubernetesURL()} component={Kubernetes}/>
|
<Route path={editorURL()} component={Editor}/>
|
||||||
<Route path={editorURL()} component={Editor}/>
|
<Route path={terminalURL()} component={Terminal}/>
|
||||||
<Route path={terminalURL()} component={Terminal}/>
|
<Route path={telemetryURL()} component={Telemetry}/>
|
||||||
<Route path={telemetryURL()} component={Telemetry}/>
|
<Route path={extensionURL()} component={Extensions}/>
|
||||||
<Route path={extensionURL()} component={Extensions}/>
|
<Redirect exact from={`${preferencesURL()}/`} to={appURL()}/>
|
||||||
<Redirect exact from={`${preferencesURL()}/`} to={appURL()}/>
|
</Switch>
|
||||||
</Switch>
|
</SettingLayout>
|
||||||
</SettingLayout>
|
);
|
||||||
);
|
};
|
||||||
}
|
|
||||||
}
|
export const Preferences = withInjectables<Dependencies>(
|
||||||
|
observer(NonInjectedPreferences),
|
||||||
|
|
||||||
|
{
|
||||||
|
getProps: (di) => ({
|
||||||
|
appPreferenceItems: di.inject(appPreferencesInjectable),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|||||||
@ -6,13 +6,20 @@ import { observer } from "mobx-react";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { UserStore } from "../../../common/user-store";
|
import { UserStore } from "../../../common/user-store";
|
||||||
import { sentryDsn } from "../../../common/vars";
|
import { sentryDsn } from "../../../common/vars";
|
||||||
import { AppPreferenceRegistry } from "../../../extensions/registries";
|
|
||||||
import { Checkbox } from "../checkbox";
|
import { Checkbox } from "../checkbox";
|
||||||
import { SubTitle } from "../layout/sub-title";
|
import { SubTitle } from "../layout/sub-title";
|
||||||
import { ExtensionSettings } from "./extension-settings";
|
import { ExtensionSettings } from "./extension-settings";
|
||||||
|
import type { RegisteredAppPreference } from "./app-preferences/app-preference-registration";
|
||||||
|
import appPreferencesInjectable from "./app-preferences/app-preferences.injectable";
|
||||||
|
import type { IComputedValue } from "mobx";
|
||||||
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
|
|
||||||
export const Telemetry = observer(() => {
|
interface Dependencies {
|
||||||
const extensions = AppPreferenceRegistry.getInstance().getItems();
|
appPreferenceItems: IComputedValue<RegisteredAppPreference[]>
|
||||||
|
}
|
||||||
|
|
||||||
|
const NonInjectedTelemetry: React.FC<Dependencies> = ({ appPreferenceItems }) => {
|
||||||
|
const extensions = appPreferenceItems.get();
|
||||||
const telemetryExtensions = extensions.filter(e => e.showInPreferencesTab == "telemetry");
|
const telemetryExtensions = extensions.filter(e => e.showInPreferencesTab == "telemetry");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -44,4 +51,14 @@ export const Telemetry = observer(() => {
|
|||||||
}
|
}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
});
|
};
|
||||||
|
|
||||||
|
export const Telemetry = withInjectables<Dependencies>(
|
||||||
|
observer(NonInjectedTelemetry),
|
||||||
|
|
||||||
|
{
|
||||||
|
getProps: (di) => ({
|
||||||
|
appPreferenceItems: di.inject(appPreferencesInjectable),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|||||||
@ -1,36 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
import { observer } from "mobx-react";
|
|
||||||
import { Icon } from "../icon";
|
|
||||||
import hotbarManagerInjectable from "../../../common/hotbar-store.injectable";
|
|
||||||
import { HotbarSwitchCommand } from "../hotbar/hotbar-switch-command";
|
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
|
||||||
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
|
|
||||||
|
|
||||||
interface Dependencies {
|
|
||||||
openCommandOverlay: (component: React.ReactElement) => void;
|
|
||||||
activeHotbarName: () => string | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const NonInjectedActiveHotbarName = observer(({ openCommandOverlay, activeHotbarName }: Dependencies) => (
|
|
||||||
<div
|
|
||||||
className="flex items-center"
|
|
||||||
data-testid="current-hotbar-name"
|
|
||||||
onClick={() => openCommandOverlay(<HotbarSwitchCommand />)}
|
|
||||||
>
|
|
||||||
<Icon material="bookmarks" className="mr-2" size={14} />
|
|
||||||
{activeHotbarName()}
|
|
||||||
</div>
|
|
||||||
));
|
|
||||||
|
|
||||||
export const ActiveHotbarName = withInjectables<Dependencies>(NonInjectedActiveHotbarName, {
|
|
||||||
getProps: (di, props) => ({
|
|
||||||
activeHotbarName: () => di.inject(hotbarManagerInjectable).getActive()?.name,
|
|
||||||
openCommandOverlay: di.inject(commandOverlayInjectable).open,
|
|
||||||
...props,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@ -4,68 +4,24 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import mockFs from "mock-fs";
|
import { render } from "@testing-library/react";
|
||||||
import { fireEvent } from "@testing-library/react";
|
|
||||||
import "@testing-library/jest-dom/extend-expect";
|
import "@testing-library/jest-dom/extend-expect";
|
||||||
import { BottomBar } from "./bottom-bar";
|
import { BottomBar } from "./bottom-bar";
|
||||||
import { StatusBarRegistry } from "../../../extensions/registries";
|
import { StatusBarRegistry } from "../../../extensions/registries";
|
||||||
import hotbarManagerInjectable from "../../../common/hotbar-store.injectable";
|
|
||||||
import { HotbarSwitchCommand } from "../hotbar/hotbar-switch-command";
|
|
||||||
import { ActiveHotbarName } from "./active-hotbar-name";
|
|
||||||
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
|
||||||
import { DiRender, renderFor } from "../test-utils/renderFor";
|
|
||||||
import type { DependencyInjectionContainer } from "@ogre-tools/injectable";
|
|
||||||
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
|
|
||||||
import { getEmptyHotbar } from "../../../common/hotbar-types";
|
|
||||||
|
|
||||||
|
|
||||||
jest.mock("electron", () => ({
|
jest.mock("electron", () => ({
|
||||||
app: {
|
app: {
|
||||||
getName: () => "lens",
|
getPath: () => "/foo",
|
||||||
setName: jest.fn(),
|
|
||||||
setPath: jest.fn(),
|
|
||||||
getPath: () => "tmp",
|
|
||||||
},
|
|
||||||
ipcMain: {
|
|
||||||
handle: jest.fn(),
|
|
||||||
on: jest.fn(),
|
|
||||||
removeAllListeners: jest.fn(),
|
|
||||||
off: jest.fn(),
|
|
||||||
send: jest.fn(),
|
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const foobarHotbar = getEmptyHotbar("foobar");
|
|
||||||
|
|
||||||
describe("<BottomBar />", () => {
|
describe("<BottomBar />", () => {
|
||||||
let di: DependencyInjectionContainer;
|
beforeEach(() => {
|
||||||
let render: DiRender;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const mockOpts = {
|
|
||||||
"tmp": {
|
|
||||||
"test-store.json": JSON.stringify({}),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
di = getDiForUnitTesting({ doGeneralOverrides: true });
|
|
||||||
|
|
||||||
mockFs(mockOpts);
|
|
||||||
|
|
||||||
render = renderFor(di);
|
|
||||||
|
|
||||||
di.override(hotbarManagerInjectable, () => ({
|
|
||||||
getActive: () => foobarHotbar,
|
|
||||||
} as any));
|
|
||||||
|
|
||||||
await di.runSetups();
|
|
||||||
|
|
||||||
StatusBarRegistry.createInstance();
|
StatusBarRegistry.createInstance();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
StatusBarRegistry.resetInstance();
|
StatusBarRegistry.resetInstance();
|
||||||
mockFs.restore();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders w/o errors", () => {
|
it("renders w/o errors", () => {
|
||||||
@ -111,33 +67,6 @@ describe("<BottomBar />", () => {
|
|||||||
expect(getByTestId(testId)).toHaveTextContent(text);
|
expect(getByTestId(testId)).toHaveTextContent(text);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows active hotbar name", () => {
|
|
||||||
StatusBarRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => [
|
|
||||||
{ item: () => <ActiveHotbarName/> },
|
|
||||||
]);
|
|
||||||
const { getByTestId } = render(<BottomBar />);
|
|
||||||
|
|
||||||
expect(getByTestId("current-hotbar-name")).toHaveTextContent("foobar");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("opens command palette on click", () => {
|
|
||||||
const mockOpen = jest.fn();
|
|
||||||
|
|
||||||
di.override(commandOverlayInjectable, () => ({
|
|
||||||
open: mockOpen,
|
|
||||||
}) as any);
|
|
||||||
|
|
||||||
StatusBarRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => [
|
|
||||||
{ item: () => <ActiveHotbarName/> },
|
|
||||||
]);
|
|
||||||
const { getByTestId } = render(<BottomBar />);
|
|
||||||
const activeHotbar = getByTestId("current-hotbar-name");
|
|
||||||
|
|
||||||
fireEvent.click(activeHotbar);
|
|
||||||
|
|
||||||
|
|
||||||
expect(mockOpen).toHaveBeenCalledWith(<HotbarSwitchCommand />);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sort positioned items properly", () => {
|
it("sort positioned items properly", () => {
|
||||||
StatusBarRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => [
|
StatusBarRegistry.getInstance().getItems = jest.fn().mockImplementationOnce(() => [
|
||||||
|
|||||||
@ -54,6 +54,7 @@ export class EditableList<T> extends React.Component<Props<T>> {
|
|||||||
onSubmit={this.onSubmit}
|
onSubmit={this.onSubmit}
|
||||||
validators={validators}
|
validators={validators}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
|
blurOnEnter={false}
|
||||||
iconRight={({ isDirty }) => isDirty ? <Icon material="keyboard_return" size={16} /> : null}
|
iconRight={({ isDirty }) => isDirty ? <Icon material="keyboard_return" size={16} /> : null}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -4,6 +4,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
.HotbarSelector {
|
.HotbarSelector {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
height: 26px;
|
height: 26px;
|
||||||
background-color: var(--layoutBackground);
|
background-color: var(--layoutBackground);
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -17,7 +19,13 @@
|
|||||||
top: -20px;
|
top: -20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.SelectorIndex {
|
.HotbarIndex {
|
||||||
|
display: flex;
|
||||||
|
flex-grow: 1;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Badge {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background: var(--secondaryBackground);
|
background: var(--secondaryBackground);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -3,21 +3,18 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import "./hotbar-selector.scss";
|
import styles from "./hotbar-selector.module.scss";
|
||||||
import React from "react";
|
import React, { useRef, useState } from "react";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { Badge } from "../badge";
|
import { Badge } from "../badge";
|
||||||
import hotbarManagerInjectable from "../../../common/hotbar-store.injectable";
|
import hotbarManagerInjectable from "../../../common/hotbar-store.injectable";
|
||||||
import { HotbarSwitchCommand } from "./hotbar-switch-command";
|
import { HotbarSwitchCommand } from "./hotbar-switch-command";
|
||||||
import { TooltipPosition } from "../tooltip";
|
import { Tooltip, TooltipPosition } from "../tooltip";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import type { Hotbar } from "../../../common/hotbar-types";
|
import type { Hotbar } from "../../../common/hotbar-types";
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
|
import commandOverlayInjectable from "../command-palette/command-overlay.injectable";
|
||||||
|
import { cssNames } from "../../utils";
|
||||||
export interface HotbarSelectorProps {
|
|
||||||
hotbar: Hotbar;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
hotbarManager: {
|
hotbarManager: {
|
||||||
@ -29,25 +26,63 @@ interface Dependencies {
|
|||||||
openCommandOverlay: (component: React.ReactElement) => void;
|
openCommandOverlay: (component: React.ReactElement) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NonInjectedHotbarSelector = observer(({ hotbar, hotbarManager, openCommandOverlay }: HotbarSelectorProps & Dependencies) => (
|
export interface HotbarSelectorProps extends Partial<Dependencies> {
|
||||||
<div className="HotbarSelector flex align-center">
|
hotbar: Hotbar;
|
||||||
<Icon material="play_arrow" className="previous box" onClick={() => hotbarManager.switchToPrevious()} />
|
}
|
||||||
<div className="box grow flex align-center">
|
|
||||||
<Badge
|
const NonInjectedHotbarSelector = observer(({ hotbar, hotbarManager, openCommandOverlay }: HotbarSelectorProps & Dependencies) => {
|
||||||
id="hotbarIndex"
|
const [tooltipVisible, setTooltipVisible] = useState(false);
|
||||||
small
|
const tooltipTimeout = useRef<NodeJS.Timeout>();
|
||||||
label={hotbarManager.getDisplayIndex(hotbarManager.getActive())}
|
|
||||||
onClick={() => openCommandOverlay(<HotbarSwitchCommand />)}
|
function clearTimer() {
|
||||||
tooltip={{
|
clearTimeout(tooltipTimeout.current);
|
||||||
preferredPositions: [TooltipPosition.TOP, TooltipPosition.TOP_LEFT],
|
}
|
||||||
children: hotbar.name,
|
|
||||||
}}
|
function onTooltipShow() {
|
||||||
className="SelectorIndex"
|
setTooltipVisible(true);
|
||||||
|
clearTimer();
|
||||||
|
tooltipTimeout.current = setTimeout(() => setTooltipVisible(false), 1500);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onArrowClick(switchTo: () => void) {
|
||||||
|
onTooltipShow();
|
||||||
|
switchTo();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMouseEvent(event: React.MouseEvent) {
|
||||||
|
clearTimer();
|
||||||
|
setTooltipVisible(event.type == "mouseenter");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.HotbarSelector}>
|
||||||
|
<Icon
|
||||||
|
material="play_arrow"
|
||||||
|
className={cssNames(styles.Icon, styles.previous)}
|
||||||
|
onClick={() => onArrowClick(hotbarManager.switchToPrevious)}
|
||||||
/>
|
/>
|
||||||
|
<div className={styles.HotbarIndex}>
|
||||||
|
<Badge
|
||||||
|
id="hotbarIndex"
|
||||||
|
small
|
||||||
|
label={hotbarManager.getDisplayIndex(hotbarManager.getActive())}
|
||||||
|
onClick={() => openCommandOverlay(<HotbarSwitchCommand />)}
|
||||||
|
className={styles.Badge}
|
||||||
|
onMouseEnter={onMouseEvent}
|
||||||
|
onMouseLeave={onMouseEvent}
|
||||||
|
/>
|
||||||
|
<Tooltip
|
||||||
|
visible={tooltipVisible}
|
||||||
|
targetId="hotbarIndex"
|
||||||
|
preferredPositions={[TooltipPosition.TOP, TooltipPosition.TOP_LEFT]}
|
||||||
|
>
|
||||||
|
{hotbar.name}
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<Icon material="play_arrow" className={styles.Icon} onClick={() => onArrowClick(hotbarManager.switchToNext)} />
|
||||||
</div>
|
</div>
|
||||||
<Icon material="play_arrow" className="next box" onClick={() => hotbarManager.switchToNext()} />
|
);
|
||||||
</div>
|
});
|
||||||
));
|
|
||||||
|
|
||||||
export const HotbarSelector = withInjectables<Dependencies, HotbarSelectorProps>(NonInjectedHotbarSelector, {
|
export const HotbarSelector = withInjectables<Dependencies, HotbarSelectorProps>(NonInjectedHotbarSelector, {
|
||||||
getProps: (di, props) => ({
|
getProps: (di, props) => ({
|
||||||
|
|||||||
@ -52,6 +52,7 @@ export type InputProps = Omit<InputElementProps, "onChange" | "onSubmit"> & {
|
|||||||
iconRight?: IconData;
|
iconRight?: IconData;
|
||||||
contentRight?: string | React.ReactNode; // Any component of string goes after iconRight
|
contentRight?: string | React.ReactNode; // Any component of string goes after iconRight
|
||||||
validators?: InputValidator | InputValidator[];
|
validators?: InputValidator | InputValidator[];
|
||||||
|
blurOnEnter?: boolean;
|
||||||
onChange?(value: string, evt: React.ChangeEvent<InputElement>): void;
|
onChange?(value: string, evt: React.ChangeEvent<InputElement>): void;
|
||||||
onSubmit?(value: string, evt: React.KeyboardEvent<InputElement>): void;
|
onSubmit?(value: string, evt: React.KeyboardEvent<InputElement>): void;
|
||||||
};
|
};
|
||||||
@ -70,6 +71,7 @@ const defaultProps: Partial<InputProps> = {
|
|||||||
maxRows: 10000,
|
maxRows: 10000,
|
||||||
showValidationLine: true,
|
showValidationLine: true,
|
||||||
validators: [],
|
validators: [],
|
||||||
|
blurOnEnter: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Input extends React.Component<InputProps, State> {
|
export class Input extends React.Component<InputProps, State> {
|
||||||
@ -267,6 +269,11 @@ export class Input extends React.Component<InputProps, State> {
|
|||||||
} else {
|
} else {
|
||||||
this.setDirty();
|
this.setDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(this.props.blurOnEnter){
|
||||||
|
//pressing enter indicates that the edit is complete, we can unfocus now
|
||||||
|
this.blur();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -25,14 +25,15 @@
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
transition: opacity 150ms 150ms ease-in-out;
|
transition: opacity 150ms 150ms ease-in-out;
|
||||||
z-index: 100000;
|
z-index: 100000;
|
||||||
opacity: 1;
|
|
||||||
box-shadow: 0 8px 16px rgba(0,0,0,0.24);
|
box-shadow: 0 8px 16px rgba(0,0,0,0.24);
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
|
||||||
&.invisible {
|
&.visible {
|
||||||
left: 0;
|
opacity: 1;
|
||||||
top: 0;
|
visibility: visible;
|
||||||
opacity: 0;
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:empty {
|
&:empty {
|
||||||
|
|||||||
@ -54,7 +54,7 @@ export class Tooltip extends React.Component<TooltipProps> {
|
|||||||
|
|
||||||
@observable.ref elem: HTMLElement;
|
@observable.ref elem: HTMLElement;
|
||||||
@observable activePosition: TooltipPosition;
|
@observable activePosition: TooltipPosition;
|
||||||
@observable isVisible = !!this.props.visible;
|
@observable isVisible = false;
|
||||||
|
|
||||||
constructor(props: TooltipProps) {
|
constructor(props: TooltipProps) {
|
||||||
super(props);
|
super(props);
|
||||||
@ -78,6 +78,10 @@ export class Tooltip extends React.Component<TooltipProps> {
|
|||||||
this.hoverTarget.addEventListener("mouseleave", this.onLeaveTarget);
|
this.hoverTarget.addEventListener("mouseleave", this.onLeaveTarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
this.refreshPosition();
|
||||||
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.hoverTarget.removeEventListener("mouseenter", this.onEnterTarget);
|
this.hoverTarget.removeEventListener("mouseenter", this.onEnterTarget);
|
||||||
this.hoverTarget.removeEventListener("mouseleave", this.onLeaveTarget);
|
this.hoverTarget.removeEventListener("mouseleave", this.onLeaveTarget);
|
||||||
@ -210,9 +214,9 @@ export class Tooltip extends React.Component<TooltipProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { style, formatters, usePortal, children } = this.props;
|
const { style, formatters, usePortal, children, visible } = this.props;
|
||||||
const className = cssNames("Tooltip", this.props.className, formatters, this.activePosition, {
|
const className = cssNames("Tooltip", this.props.className, formatters, this.activePosition, {
|
||||||
invisible: !this.isVisible,
|
visible: visible ?? this.isVisible,
|
||||||
formatter: !!formatters,
|
formatter: !!formatters,
|
||||||
});
|
});
|
||||||
const tooltip = (
|
const tooltip = (
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { Button } from "../button";
|
|||||||
import { Stepper } from "../stepper";
|
import { Stepper } from "../stepper";
|
||||||
import { SubTitle } from "../layout/sub-title";
|
import { SubTitle } from "../layout/sub-title";
|
||||||
import { Spinner } from "../spinner";
|
import { Spinner } from "../spinner";
|
||||||
|
import { debounce } from "lodash";
|
||||||
|
|
||||||
interface WizardCommonProps<D = any> {
|
interface WizardCommonProps<D = any> {
|
||||||
data?: Partial<D>;
|
data?: Partial<D>;
|
||||||
@ -179,14 +180,16 @@ export class WizardStep extends React.Component<WizardStepProps, WizardStepState
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
submit = () => {
|
//because submit MIGHT be called through pressing enter, it might be fired twice.
|
||||||
|
//we'll debounce it to ensure it isn't
|
||||||
|
submit = debounce(() => {
|
||||||
if (!this.form.noValidate) {
|
if (!this.form.noValidate) {
|
||||||
const valid = this.form.checkValidity();
|
const valid = this.form.checkValidity();
|
||||||
|
|
||||||
if (!valid) return;
|
if (!valid) return;
|
||||||
}
|
}
|
||||||
this.next();
|
this.next();
|
||||||
};
|
}, 100);
|
||||||
|
|
||||||
renderLoading() {
|
renderLoading() {
|
||||||
return (
|
return (
|
||||||
@ -196,6 +199,17 @@ export class WizardStep extends React.Component<WizardStepProps, WizardStepState
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//make sure we call submit if the "enter" keypress doesn't trigger the events
|
||||||
|
keyDown(evt: React.KeyboardEvent<HTMLElement>) {
|
||||||
|
if (evt.shiftKey || evt.metaKey || evt.altKey || evt.ctrlKey || evt.repeat) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(evt.key === "Enter"){
|
||||||
|
this.submit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
step, isFirst, isLast, children,
|
step, isFirst, isLast, children,
|
||||||
@ -216,6 +230,7 @@ export class WizardStep extends React.Component<WizardStepProps, WizardStepState
|
|||||||
return (
|
return (
|
||||||
<form className={className}
|
<form className={className}
|
||||||
onSubmit={prevDefault(this.submit)} noValidate={noValidate}
|
onSubmit={prevDefault(this.submit)} noValidate={noValidate}
|
||||||
|
onKeyDown={(evt) => this.keyDown(evt)}
|
||||||
ref={e => this.form = e}>
|
ref={e => this.form = e}>
|
||||||
{beforeContent}
|
{beforeContent}
|
||||||
<div className={contentClass}>
|
<div className={contentClass}>
|
||||||
|
|||||||
@ -19,7 +19,7 @@ import { KubeObjectStore } from "../../../../common/k8s-api/kube-object.store";
|
|||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
hostedCluster: Cluster;
|
hostedCluster: Cluster;
|
||||||
loadExtensions: (entity: CatalogEntity) => void;
|
loadExtensions: (getCluster: () => CatalogEntity) => void;
|
||||||
catalogEntityRegistry: CatalogEntityRegistry;
|
catalogEntityRegistry: CatalogEntityRegistry;
|
||||||
frameRoutingId: number;
|
frameRoutingId: number;
|
||||||
emitEvent: (event: AppEvent) => void;
|
emitEvent: (event: AppEvent) => void;
|
||||||
@ -47,11 +47,12 @@ export const initClusterFrame =
|
|||||||
|
|
||||||
catalogEntityRegistry.activeEntity = hostedCluster.id;
|
catalogEntityRegistry.activeEntity = hostedCluster.id;
|
||||||
|
|
||||||
// Only load the extensions once the catalog has been populated
|
// Only load the extensions once the catalog has been populated.
|
||||||
|
// Note that the Catalog might still have unprocessed entities until the extensions are fully loaded.
|
||||||
when(
|
when(
|
||||||
() => Boolean(catalogEntityRegistry.activeEntity),
|
() => catalogEntityRegistry.items.length > 0,
|
||||||
() =>
|
() =>
|
||||||
loadExtensions(catalogEntityRegistry.activeEntity as KubernetesCluster),
|
loadExtensions(() => catalogEntityRegistry.activeEntity as KubernetesCluster),
|
||||||
{
|
{
|
||||||
timeout: 15_000,
|
timeout: 15_000,
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
|
|||||||
@ -11,15 +11,15 @@ import type { ExtensionLoading } from "../../../../extensions/extension-loader";
|
|||||||
import type { CatalogEntityRegistry } from "../../../api/catalog-entity-registry";
|
import type { CatalogEntityRegistry } from "../../../api/catalog-entity-registry";
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
loadExtensions: () => ExtensionLoading[]
|
loadExtensions: () => Promise<ExtensionLoading[]>;
|
||||||
|
|
||||||
// TODO: Move usages of third party library behind abstraction
|
// TODO: Move usages of third party library behind abstraction
|
||||||
ipcRenderer: { send: (name: string) => void }
|
ipcRenderer: { send: (name: string) => void };
|
||||||
|
|
||||||
// TODO: Remove dependencies being here only for correct timing of initialization
|
// TODO: Remove dependencies being here only for correct timing of initialization
|
||||||
bindProtocolAddRouteHandlers: () => void;
|
bindProtocolAddRouteHandlers: () => void;
|
||||||
lensProtocolRouterRenderer: { init: () => void };
|
lensProtocolRouterRenderer: { init: () => void };
|
||||||
catalogEntityRegistry: CatalogEntityRegistry
|
catalogEntityRegistry: CatalogEntityRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
const logPrefix = "[ROOT-FRAME]:";
|
const logPrefix = "[ROOT-FRAME]:";
|
||||||
@ -40,7 +40,7 @@ export const initRootFrame =
|
|||||||
// maximum time to let bundled extensions finish loading
|
// maximum time to let bundled extensions finish loading
|
||||||
const timeout = delay(10000);
|
const timeout = delay(10000);
|
||||||
|
|
||||||
const loadingExtensions = loadExtensions();
|
const loadingExtensions = await loadExtensions();
|
||||||
|
|
||||||
const loadingBundledExtensions = loadingExtensions
|
const loadingBundledExtensions = loadingExtensions
|
||||||
.filter((e) => e.isBundled)
|
.filter((e) => e.isBundled)
|
||||||
|
|||||||
@ -12,4 +12,3 @@ export * from "./kube-object-menu-registry";
|
|||||||
export * from "./registries";
|
export * from "./registries";
|
||||||
export * from "./workloads-overview-detail-registry";
|
export * from "./workloads-overview-detail-registry";
|
||||||
export * from "./catalog-category-registry";
|
export * from "./catalog-category-registry";
|
||||||
export * from "./status-bar-registry";
|
|
||||||
|
|||||||
@ -6,7 +6,6 @@
|
|||||||
import * as registries from "../../extensions/registries";
|
import * as registries from "../../extensions/registries";
|
||||||
|
|
||||||
export function initRegistries() {
|
export function initRegistries() {
|
||||||
registries.AppPreferenceRegistry.createInstance();
|
|
||||||
registries.CatalogEntityDetailRegistry.createInstance();
|
registries.CatalogEntityDetailRegistry.createInstance();
|
||||||
registries.ClusterPageMenuRegistry.createInstance();
|
registries.ClusterPageMenuRegistry.createInstance();
|
||||||
registries.ClusterPageRegistry.createInstance();
|
registries.ClusterPageRegistry.createInstance();
|
||||||
|
|||||||
@ -1,19 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
import { StatusBarRegistry } from "../../extensions/registries";
|
|
||||||
import { ActiveHotbarName } from "../components/cluster-manager/active-hotbar-name";
|
|
||||||
|
|
||||||
export function initStatusBarRegistry() {
|
|
||||||
StatusBarRegistry.getInstance().add([
|
|
||||||
{
|
|
||||||
components: {
|
|
||||||
Item: () => <ActiveHotbarName/>,
|
|
||||||
position: "left",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
13
yarn.lock
13
yarn.lock
@ -5460,6 +5460,17 @@ eslint-import-resolver-node@^0.3.6:
|
|||||||
debug "^3.2.7"
|
debug "^3.2.7"
|
||||||
resolve "^1.20.0"
|
resolve "^1.20.0"
|
||||||
|
|
||||||
|
eslint-import-resolver-typescript@^2.5.0:
|
||||||
|
version "2.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.5.0.tgz#07661966b272d14ba97f597b51e1a588f9722f0a"
|
||||||
|
integrity sha512-qZ6e5CFr+I7K4VVhQu3M/9xGv9/YmwsEXrsm3nimw8vWaVHRDrQRp26BgCypTxBp3vUp4o5aVEJRiy0F2DFddQ==
|
||||||
|
dependencies:
|
||||||
|
debug "^4.3.1"
|
||||||
|
glob "^7.1.7"
|
||||||
|
is-glob "^4.0.1"
|
||||||
|
resolve "^1.20.0"
|
||||||
|
tsconfig-paths "^3.9.0"
|
||||||
|
|
||||||
eslint-module-utils@^2.7.1:
|
eslint-module-utils@^2.7.1:
|
||||||
version "2.7.1"
|
version "2.7.1"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.1.tgz#b435001c9f8dd4ab7f6d0efcae4b9696d4c24b7c"
|
resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.1.tgz#b435001c9f8dd4ab7f6d0efcae4b9696d4c24b7c"
|
||||||
@ -6516,7 +6527,7 @@ glob-to-regexp@^0.4.1:
|
|||||||
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
|
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
|
||||||
integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
|
integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
|
||||||
|
|
||||||
glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.0:
|
glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.1.7, glob@^7.2.0:
|
||||||
version "7.2.0"
|
version "7.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
|
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
|
||||||
integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
|
integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user