mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
General catalog category (#3106)
* Adding General Entities and General Category Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Usign material icons for general entities Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Setting background for hotbar icon explicitly Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Adding Catalog initially to first hotbar Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Tuning hotbar store tests Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Changing type from GeneralEntity to General Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Search for catalog hotbar item in tests Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Removing Catalog link from bottom bar Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Waiting for hotbar catalog entity Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Opening cluster list by data-testId Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Replacing types on interfaces Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Fixing integration tests Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Adding general entities throught initializers Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com> * Removing icon refs from CatalogEntitySpec Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>
This commit is contained in:
parent
48e278c71b
commit
d9ceb8fa08
@ -45,7 +45,6 @@ describe("Lens cluster pages", () => {
|
|||||||
utils.describeIf(ready)("test common pages", () => {
|
utils.describeIf(ready)("test common pages", () => {
|
||||||
let clusterAdded = false;
|
let clusterAdded = false;
|
||||||
const addCluster = async () => {
|
const addCluster = async () => {
|
||||||
await app.client.waitUntilTextExists("div", "Catalog");
|
|
||||||
await waitForMinikubeDashboard(app);
|
await waitForMinikubeDashboard(app);
|
||||||
await app.client.click('a[href="/nodes"]');
|
await app.client.click('a[href="/nodes"]');
|
||||||
await app.client.waitUntilTextExists("div.TableCell", "Ready");
|
await app.client.waitUntilTextExists("div.TableCell", "Ready");
|
||||||
|
|||||||
@ -65,6 +65,7 @@ export async function waitForMinikubeDashboard(app: Application) {
|
|||||||
await app.client.waitUntilTextExists("div.TableCell", "minikube");
|
await app.client.waitUntilTextExists("div.TableCell", "minikube");
|
||||||
await app.client.click("div.TableRow");
|
await app.client.click("div.TableRow");
|
||||||
await app.client.waitUntilTextExists("div.drawer-title-text", "KubernetesCluster: minikube");
|
await app.client.waitUntilTextExists("div.drawer-title-text", "KubernetesCluster: minikube");
|
||||||
|
await app.client.waitForExist("div.EntityIcon div.HotbarIcon div div.MuiAvatar-root");
|
||||||
await app.client.click("div.EntityIcon div.HotbarIcon div div.MuiAvatar-root");
|
await app.client.click("div.EntityIcon div.HotbarIcon div div.MuiAvatar-root");
|
||||||
await app.client.waitUntilTextExists("pre.kube-auth-out", "Authentication proxy started");
|
await app.client.waitUntilTextExists("pre.kube-auth-out", "Authentication proxy started");
|
||||||
await app.client.waitForExist(`iframe[name="minikube"]`);
|
await app.client.waitForExist(`iframe[name="minikube"]`);
|
||||||
|
|||||||
@ -99,8 +99,8 @@ export async function appStart() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function showCatalog(app: Application) {
|
export async function showCatalog(app: Application) {
|
||||||
await app.client.waitUntilTextExists("[data-test-id=catalog-link]", "Catalog");
|
await app.client.waitForExist("#hotbarIcon-catalog-entity .Icon");
|
||||||
await app.client.click("[data-test-id=catalog-link]");
|
await app.client.click("#hotbarIcon-catalog-entity .Icon");
|
||||||
}
|
}
|
||||||
|
|
||||||
type AsyncPidGetter = () => Promise<number>;
|
type AsyncPidGetter = () => Promise<number>;
|
||||||
|
|||||||
@ -156,6 +156,13 @@ describe("HotbarStore", () => {
|
|||||||
expect(hotbarStore.getActive().items.length).toEqual(12);
|
expect(hotbarStore.getActive().items.length).toEqual(12);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("initially adds catalog entity as first item", () => {
|
||||||
|
const hotbarStore = HotbarStore.createInstance();
|
||||||
|
|
||||||
|
hotbarStore.load();
|
||||||
|
expect(hotbarStore.getActive().items[0].entity.name).toEqual("Catalog");
|
||||||
|
});
|
||||||
|
|
||||||
it("adds items", () => {
|
it("adds items", () => {
|
||||||
const hotbarStore = HotbarStore.createInstance();
|
const hotbarStore = HotbarStore.createInstance();
|
||||||
|
|
||||||
@ -163,7 +170,7 @@ describe("HotbarStore", () => {
|
|||||||
hotbarStore.addToHotbar(testCluster);
|
hotbarStore.addToHotbar(testCluster);
|
||||||
const items = hotbarStore.getActive().items.filter(Boolean);
|
const items = hotbarStore.getActive().items.filter(Boolean);
|
||||||
|
|
||||||
expect(items.length).toEqual(1);
|
expect(items.length).toEqual(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("removes items", () => {
|
it("removes items", () => {
|
||||||
@ -172,6 +179,7 @@ describe("HotbarStore", () => {
|
|||||||
hotbarStore.load();
|
hotbarStore.load();
|
||||||
hotbarStore.addToHotbar(testCluster);
|
hotbarStore.addToHotbar(testCluster);
|
||||||
hotbarStore.removeFromHotbar("test");
|
hotbarStore.removeFromHotbar("test");
|
||||||
|
hotbarStore.removeFromHotbar("catalog-entity");
|
||||||
const items = hotbarStore.getActive().items.filter(Boolean);
|
const items = hotbarStore.getActive().items.filter(Boolean);
|
||||||
|
|
||||||
expect(items.length).toEqual(0);
|
expect(items.length).toEqual(0);
|
||||||
@ -185,7 +193,7 @@ describe("HotbarStore", () => {
|
|||||||
hotbarStore.removeFromHotbar("invalid uid");
|
hotbarStore.removeFromHotbar("invalid uid");
|
||||||
const items = hotbarStore.getActive().items.filter(Boolean);
|
const items = hotbarStore.getActive().items.filter(Boolean);
|
||||||
|
|
||||||
expect(items.length).toEqual(1);
|
expect(items.length).toEqual(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("moves item to empty cell", () => {
|
it("moves item to empty cell", () => {
|
||||||
@ -196,12 +204,12 @@ describe("HotbarStore", () => {
|
|||||||
hotbarStore.addToHotbar(minikubeCluster);
|
hotbarStore.addToHotbar(minikubeCluster);
|
||||||
hotbarStore.addToHotbar(awsCluster);
|
hotbarStore.addToHotbar(awsCluster);
|
||||||
|
|
||||||
expect(hotbarStore.getActive().items[5]).toBeNull();
|
expect(hotbarStore.getActive().items[6]).toBeNull();
|
||||||
|
|
||||||
hotbarStore.restackItems(1, 5);
|
hotbarStore.restackItems(1, 5);
|
||||||
|
|
||||||
expect(hotbarStore.getActive().items[5]).toBeTruthy();
|
expect(hotbarStore.getActive().items[5]).toBeTruthy();
|
||||||
expect(hotbarStore.getActive().items[5].entity.uid).toEqual("minikube");
|
expect(hotbarStore.getActive().items[5].entity.uid).toEqual("test");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("moves items down", () => {
|
it("moves items down", () => {
|
||||||
@ -212,12 +220,12 @@ describe("HotbarStore", () => {
|
|||||||
hotbarStore.addToHotbar(minikubeCluster);
|
hotbarStore.addToHotbar(minikubeCluster);
|
||||||
hotbarStore.addToHotbar(awsCluster);
|
hotbarStore.addToHotbar(awsCluster);
|
||||||
|
|
||||||
// aws -> test
|
// aws -> catalog
|
||||||
hotbarStore.restackItems(2, 0);
|
hotbarStore.restackItems(3, 0);
|
||||||
|
|
||||||
const items = hotbarStore.getActive().items.map(item => item?.entity.uid || null);
|
const items = hotbarStore.getActive().items.map(item => item?.entity.uid || null);
|
||||||
|
|
||||||
expect(items.slice(0, 4)).toEqual(["aws", "test", "minikube", null]);
|
expect(items.slice(0, 4)).toEqual(["aws", "catalog-entity", "test", "minikube"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("moves items up", () => {
|
it("moves items up", () => {
|
||||||
@ -229,11 +237,11 @@ describe("HotbarStore", () => {
|
|||||||
hotbarStore.addToHotbar(awsCluster);
|
hotbarStore.addToHotbar(awsCluster);
|
||||||
|
|
||||||
// test -> aws
|
// test -> aws
|
||||||
hotbarStore.restackItems(0, 2);
|
hotbarStore.restackItems(1, 3);
|
||||||
|
|
||||||
const items = hotbarStore.getActive().items.map(item => item?.entity.uid || null);
|
const items = hotbarStore.getActive().items.map(item => item?.entity.uid || null);
|
||||||
|
|
||||||
expect(items.slice(0, 4)).toEqual(["minikube", "aws", "test", null]);
|
expect(items.slice(0, 4)).toEqual(["catalog-entity", "minikube", "aws", "test"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does nothing when item moved to same cell", () => {
|
it("does nothing when item moved to same cell", () => {
|
||||||
@ -241,9 +249,9 @@ describe("HotbarStore", () => {
|
|||||||
|
|
||||||
hotbarStore.load();
|
hotbarStore.load();
|
||||||
hotbarStore.addToHotbar(testCluster);
|
hotbarStore.addToHotbar(testCluster);
|
||||||
hotbarStore.restackItems(0, 0);
|
hotbarStore.restackItems(1, 1);
|
||||||
|
|
||||||
expect(hotbarStore.getActive().items[0].entity.uid).toEqual("test");
|
expect(hotbarStore.getActive().items[1].entity.uid).toEqual("test");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("new items takes first empty cell", () => {
|
it("new items takes first empty cell", () => {
|
||||||
|
|||||||
76
src/common/catalog-entities/general.ts
Normal file
76
src/common/catalog-entities/general.ts
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { navigate } from "../../renderer/navigation";
|
||||||
|
import { CatalogCategory, CatalogEntity, CatalogEntityMetadata, CatalogEntitySpec, CatalogEntityStatus } from "../catalog";
|
||||||
|
import { catalogCategoryRegistry } from "../catalog/catalog-category-registry";
|
||||||
|
|
||||||
|
interface GeneralEntitySpec extends CatalogEntitySpec {
|
||||||
|
path: string;
|
||||||
|
icon?: {
|
||||||
|
material?: string;
|
||||||
|
background?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GeneralEntity extends CatalogEntity<CatalogEntityMetadata, CatalogEntityStatus, GeneralEntitySpec> {
|
||||||
|
public readonly apiVersion = "entity.k8slens.dev/v1alpha1";
|
||||||
|
public readonly kind = "General";
|
||||||
|
|
||||||
|
async onRun() {
|
||||||
|
navigate(this.spec.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public onSettingsOpen(): void {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
public onDetailsOpen(): void {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
public onContextMenuOpen(): void {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GeneralCategory extends CatalogCategory {
|
||||||
|
public readonly apiVersion = "catalog.k8slens.dev/v1alpha1";
|
||||||
|
public readonly kind = "CatalogCategory";
|
||||||
|
public metadata = {
|
||||||
|
name: "General",
|
||||||
|
icon: "settings"
|
||||||
|
};
|
||||||
|
public spec = {
|
||||||
|
group: "entity.k8slens.dev",
|
||||||
|
versions: [
|
||||||
|
{
|
||||||
|
name: "v1alpha1",
|
||||||
|
entityClass: GeneralEntity
|
||||||
|
}
|
||||||
|
],
|
||||||
|
names: {
|
||||||
|
kind: "General"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
catalogCategoryRegistry.add(new GeneralCategory());
|
||||||
@ -19,5 +19,6 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export * from "./general";
|
||||||
export * from "./kubernetes-cluster";
|
export * from "./kubernetes-cluster";
|
||||||
export * from "./web-link";
|
export * from "./web-link";
|
||||||
|
|||||||
@ -27,9 +27,10 @@ import { requestMain } from "../ipc";
|
|||||||
import { CatalogCategory, CatalogCategorySpec } from "../catalog";
|
import { CatalogCategory, CatalogCategorySpec } from "../catalog";
|
||||||
import { addClusterURL } from "../routes";
|
import { addClusterURL } from "../routes";
|
||||||
import { app } from "electron";
|
import { app } from "electron";
|
||||||
|
import type { CatalogEntitySpec } from "../catalog/catalog-entity";
|
||||||
import { HotbarStore } from "../hotbar-store";
|
import { HotbarStore } from "../hotbar-store";
|
||||||
|
|
||||||
export type KubernetesClusterPrometheusMetrics = {
|
export interface KubernetesClusterPrometheusMetrics {
|
||||||
address?: {
|
address?: {
|
||||||
namespace: string;
|
namespace: string;
|
||||||
service: string;
|
service: string;
|
||||||
@ -37,17 +38,19 @@ export type KubernetesClusterPrometheusMetrics = {
|
|||||||
prefix: string;
|
prefix: string;
|
||||||
};
|
};
|
||||||
type?: string;
|
type?: string;
|
||||||
};
|
icon?: {
|
||||||
|
src?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export type KubernetesClusterSpec = {
|
export interface KubernetesClusterSpec extends CatalogEntitySpec {
|
||||||
kubeconfigPath: string;
|
kubeconfigPath: string;
|
||||||
kubeconfigContext: string;
|
kubeconfigContext: string;
|
||||||
iconData?: string;
|
|
||||||
metrics?: {
|
metrics?: {
|
||||||
source: string;
|
source: string;
|
||||||
prometheus?: KubernetesClusterPrometheusMetrics;
|
prometheus?: KubernetesClusterPrometheusMetrics;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export interface KubernetesClusterStatus extends CatalogEntityStatus {
|
export interface KubernetesClusterStatus extends CatalogEntityStatus {
|
||||||
phase: "connected" | "disconnected" | "deleting";
|
phase: "connected" | "disconnected" | "deleting";
|
||||||
|
|||||||
@ -149,6 +149,7 @@ export interface CatalogEntityAddMenuContext {
|
|||||||
|
|
||||||
export type CatalogEntitySpec = Record<string, any>;
|
export type CatalogEntitySpec = Record<string, any>;
|
||||||
|
|
||||||
|
|
||||||
export interface CatalogEntityData<
|
export interface CatalogEntityData<
|
||||||
Metadata extends CatalogEntityMetadata = CatalogEntityMetadata,
|
Metadata extends CatalogEntityMetadata = CatalogEntityMetadata,
|
||||||
Status extends CatalogEntityStatus = CatalogEntityStatus,
|
Status extends CatalogEntityStatus = CatalogEntityStatus,
|
||||||
|
|||||||
@ -26,6 +26,7 @@ import * as uuid from "uuid";
|
|||||||
import isNull from "lodash/isNull";
|
import isNull from "lodash/isNull";
|
||||||
import { toJS } from "./utils";
|
import { toJS } from "./utils";
|
||||||
import { CatalogEntity } from "./catalog";
|
import { CatalogEntity } from "./catalog";
|
||||||
|
import { catalogEntity } from "../main/catalog-sources/general";
|
||||||
|
|
||||||
export interface HotbarItem {
|
export interface HotbarItem {
|
||||||
entity: {
|
entity: {
|
||||||
@ -91,6 +92,16 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
|
|||||||
return this.hotbarIndex(this.activeHotbarId);
|
return this.hotbarIndex(this.activeHotbarId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get defaultHotbarInitialItems() {
|
||||||
|
const { metadata: { uid, name, source } } = catalogEntity;
|
||||||
|
const initialItem = { entity: { uid, name, source }};
|
||||||
|
|
||||||
|
return [
|
||||||
|
initialItem,
|
||||||
|
...Array.from(Array(defaultHotbarCells - 1).fill(null))
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
get initialItems() {
|
get initialItems() {
|
||||||
return [...Array.from(Array(defaultHotbarCells).fill(null))];
|
return [...Array.from(Array(defaultHotbarCells).fill(null))];
|
||||||
}
|
}
|
||||||
@ -100,7 +111,7 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
|
|||||||
this.hotbars = [{
|
this.hotbars = [{
|
||||||
id: uuid.v4(),
|
id: uuid.v4(),
|
||||||
name: "Default",
|
name: "Default",
|
||||||
items: this.initialItems,
|
items: this.defaultHotbarInitialItems,
|
||||||
}];
|
}];
|
||||||
} else {
|
} else {
|
||||||
this.hotbars = data.hotbars;
|
this.hotbars = data.hotbars;
|
||||||
|
|||||||
@ -25,6 +25,7 @@ export interface WelcomeMenuRegistration {
|
|||||||
title: string | (() => string);
|
title: string | (() => string);
|
||||||
icon: string;
|
icon: string;
|
||||||
click: () => void | Promise<void>;
|
click: () => void | Promise<void>;
|
||||||
|
testId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WelcomeMenuRegistry extends BaseRegistry<WelcomeMenuRegistration> {}
|
export class WelcomeMenuRegistry extends BaseRegistry<WelcomeMenuRegistration> {}
|
||||||
|
|||||||
72
src/main/catalog-sources/general.ts
Normal file
72
src/main/catalog-sources/general.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { observable } from "mobx";
|
||||||
|
import { GeneralEntity } from "../../common/catalog-entities/general";
|
||||||
|
import { catalogURL, preferencesURL } from "../../common/routes";
|
||||||
|
import { catalogEntityRegistry } from "../catalog";
|
||||||
|
|
||||||
|
export const catalogEntity = new GeneralEntity({
|
||||||
|
metadata: {
|
||||||
|
uid: "catalog-entity",
|
||||||
|
name: "Catalog",
|
||||||
|
source: "app",
|
||||||
|
labels: {}
|
||||||
|
},
|
||||||
|
spec: {
|
||||||
|
path: catalogURL(),
|
||||||
|
icon: {
|
||||||
|
material: "view_list",
|
||||||
|
background: "#3d90ce"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
phase: "active",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const preferencesEntity = new GeneralEntity({
|
||||||
|
metadata: {
|
||||||
|
uid: "preferences-entity",
|
||||||
|
name: "Preferences",
|
||||||
|
source: "app",
|
||||||
|
labels: {}
|
||||||
|
},
|
||||||
|
spec: {
|
||||||
|
path: preferencesURL(),
|
||||||
|
icon: {
|
||||||
|
material: "settings",
|
||||||
|
background: "#3d90ce"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
phase: "active",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const generalEntities = observable([
|
||||||
|
catalogEntity,
|
||||||
|
preferencesEntity
|
||||||
|
]);
|
||||||
|
|
||||||
|
export function initializeGeneralEntities() {
|
||||||
|
catalogEntityRegistry.addObservableSource("lens:general", generalEntities);
|
||||||
|
}
|
||||||
@ -21,3 +21,4 @@
|
|||||||
|
|
||||||
export { initializeWeblinks } from "./weblinks";
|
export { initializeWeblinks } from "./weblinks";
|
||||||
export { KubeconfigSyncManager } from "./kubeconfig-sync";
|
export { KubeconfigSyncManager } from "./kubeconfig-sync";
|
||||||
|
export { initializeGeneralEntities } from "./general";
|
||||||
|
|||||||
@ -119,7 +119,7 @@ export class ClusterManager extends Singleton {
|
|||||||
entity.spec.metrics.prometheus = prometheus;
|
entity.spec.metrics.prometheus = prometheus;
|
||||||
}
|
}
|
||||||
|
|
||||||
entity.spec.iconData = cluster.preferences.icon;
|
entity.spec.icon.src = cluster.preferences.icon;
|
||||||
|
|
||||||
catalogEntityRegistry.items.splice(index, 1, entity);
|
catalogEntityRegistry.items.splice(index, 1, entity);
|
||||||
}
|
}
|
||||||
@ -220,7 +220,8 @@ export function catalogEntityFromCluster(cluster: Cluster) {
|
|||||||
},
|
},
|
||||||
spec: {
|
spec: {
|
||||||
kubeconfigPath: cluster.kubeConfigPath,
|
kubeconfigPath: cluster.kubeConfigPath,
|
||||||
kubeconfigContext: cluster.contextName
|
kubeconfigContext: cluster.contextName,
|
||||||
|
icon: {}
|
||||||
},
|
},
|
||||||
status: {
|
status: {
|
||||||
phase: cluster.disconnected ? "disconnected" : "connected",
|
phase: cluster.disconnected ? "disconnected" : "connected",
|
||||||
|
|||||||
@ -132,7 +132,7 @@ app.on("ready", async () => {
|
|||||||
handleWsUpgrade,
|
handleWsUpgrade,
|
||||||
req => ClusterManager.getInstance().getClusterForRequest(req),
|
req => ClusterManager.getInstance().getClusterForRequest(req),
|
||||||
);
|
);
|
||||||
|
|
||||||
ClusterManager.createInstance().init();
|
ClusterManager.createInstance().init();
|
||||||
KubeconfigSyncManager.createInstance();
|
KubeconfigSyncManager.createInstance();
|
||||||
|
|
||||||
@ -182,6 +182,7 @@ app.on("ready", async () => {
|
|||||||
ipcMainOn(IpcRendererNavigationEvents.LOADED, () => {
|
ipcMainOn(IpcRendererNavigationEvents.LOADED, () => {
|
||||||
cleanup.push(pushCatalogToRenderer(catalogEntityRegistry));
|
cleanup.push(pushCatalogToRenderer(catalogEntityRegistry));
|
||||||
KubeconfigSyncManager.getInstance().startSync();
|
KubeconfigSyncManager.getInstance().startSync();
|
||||||
|
initializers.initializeGeneralEntities();
|
||||||
startUpdateChecking();
|
startUpdateChecking();
|
||||||
LensProtocolRouterMain.getInstance().rendererLoaded = true;
|
LensProtocolRouterMain.getInstance().rendererLoaded = true;
|
||||||
});
|
});
|
||||||
|
|||||||
22
src/main/initializers/general-entities.ts
Normal file
22
src/main/initializers/general-entities.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { initializeGeneralEntities } from "../catalog-sources";
|
||||||
@ -24,3 +24,4 @@ export * from "./metrics-providers";
|
|||||||
export * from "./ipc";
|
export * from "./ipc";
|
||||||
export * from "./weblinks";
|
export * from "./weblinks";
|
||||||
export * from "./stores";
|
export * from "./stores";
|
||||||
|
export * from "./general-entities";
|
||||||
|
|||||||
@ -63,7 +63,9 @@ export class CatalogEntityDetails<T extends CatalogEntity> extends Component<Pro
|
|||||||
uid={item.id}
|
uid={item.id}
|
||||||
title={item.name}
|
title={item.name}
|
||||||
source={item.source}
|
source={item.source}
|
||||||
icon={item.entity.spec.iconData}
|
src={item.entity.spec.icon?.src}
|
||||||
|
material={item.entity.spec.icon?.material}
|
||||||
|
background={item.entity.spec.icon?.background}
|
||||||
disabled={!item?.enabled}
|
disabled={!item?.enabled}
|
||||||
onClick={() => item.onRun(catalogEntityRunContext)}
|
onClick={() => item.onRun(catalogEntityRunContext)}
|
||||||
size={128} />
|
size={128} />
|
||||||
|
|||||||
@ -169,9 +169,12 @@ export class Catalog extends React.Component<Props> {
|
|||||||
uid={item.getId()}
|
uid={item.getId()}
|
||||||
title={item.getName()}
|
title={item.getName()}
|
||||||
source={item.source}
|
source={item.source}
|
||||||
icon={item.entity.spec.iconData}
|
src={item.entity.spec.icon?.src}
|
||||||
|
material={item.entity.spec.icon?.material}
|
||||||
|
background={item.entity.spec.icon?.background}
|
||||||
onClick={() => this.onDetails(item)}
|
onClick={() => this.onDetails(item)}
|
||||||
size={24} />
|
size={24}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -44,7 +44,7 @@ export class Welcome extends React.Component {
|
|||||||
|
|
||||||
<ul className="box">
|
<ul className="box">
|
||||||
{WelcomeMenuRegistry.getInstance().getItems().map((item, index) => (
|
{WelcomeMenuRegistry.getInstance().getItems().map((item, index) => (
|
||||||
<li key={index} className="flex grid-12" onClick={() => item.click()}>
|
<li key={index} className="flex grid-12" onClick={() => item.click()} data-testId={item.testId}>
|
||||||
<Icon material={item.icon} className="box col-1" /> <a className="box col-10">{typeof item.title === "string" ? item.title : item.title()}</a> <Icon material="navigate_next" className="box col-1" />
|
<Icon material={item.icon} className="box col-1" /> <a className="box col-10">{typeof item.title === "string" ? item.title : item.title()}</a> <Icon material="navigate_next" className="box col-1" />
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -32,6 +32,7 @@ interface Props extends DOMAttributes<HTMLElement>, Partial<AvatarTypeMap> {
|
|||||||
height?: number;
|
height?: number;
|
||||||
src?: string;
|
src?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
background?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNameParts(name: string): string[] {
|
function getNameParts(name: string): string[] {
|
||||||
@ -69,11 +70,11 @@ function getIconString(title: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Avatar(props: Props) {
|
export function Avatar(props: Props) {
|
||||||
const { title, src, width = 32, height = 32, colorHash, ...settings } = props;
|
const { title, width = 32, height = 32, colorHash, children, background, ...settings } = props;
|
||||||
|
|
||||||
const generateAvatarStyle = (): React.CSSProperties => {
|
const generateAvatarStyle = (): React.CSSProperties => {
|
||||||
return {
|
return {
|
||||||
backgroundColor: randomColor({ seed: colorHash, luminosity: "dark" }),
|
backgroundColor: background || randomColor({ seed: colorHash, luminosity: "dark" }),
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
textTransform: "uppercase"
|
textTransform: "uppercase"
|
||||||
@ -86,7 +87,7 @@ export function Avatar(props: Props) {
|
|||||||
style={generateAvatarStyle()}
|
style={generateAvatarStyle()}
|
||||||
{...settings}
|
{...settings}
|
||||||
>
|
>
|
||||||
{getIconString(title)}
|
{children || getIconString(title)}
|
||||||
</MaterialAvatar>
|
</MaterialAvatar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,18 +27,6 @@
|
|||||||
padding: 0 2px;
|
padding: 0 2px;
|
||||||
height: var(--bottom-bar-height);
|
height: var(--bottom-bar-height);
|
||||||
|
|
||||||
#catalog-link {
|
|
||||||
font-size: var(--font-size-small);
|
|
||||||
color: white;
|
|
||||||
padding: $padding / 4 $padding / 2;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: #ffffff33;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.extensions {
|
.extensions {
|
||||||
font-size: var(--font-size-small);
|
font-size: var(--font-size-small);
|
||||||
color: white;
|
color: white;
|
||||||
|
|||||||
@ -24,9 +24,6 @@ import "./bottom-bar.scss";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { StatusBarRegistration, StatusBarRegistry } from "../../../extensions/registries";
|
import { StatusBarRegistration, StatusBarRegistry } from "../../../extensions/registries";
|
||||||
import { navigate } from "../../navigation";
|
|
||||||
import { Icon } from "../icon";
|
|
||||||
import { catalogURL } from "../../../common/routes";
|
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class BottomBar extends React.Component {
|
export class BottomBar extends React.Component {
|
||||||
@ -67,10 +64,6 @@ export class BottomBar extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="BottomBar flex gaps">
|
<div className="BottomBar flex gaps">
|
||||||
<div id="catalog-link" data-test-id="catalog-link" className="flex gaps align-center" onClick={() => navigate(catalogURL())}>
|
|
||||||
<Icon smallest material="view_list"/>
|
|
||||||
<span className="catalog-link" data-test-id="catalog-link">Catalog</span>
|
|
||||||
</div>
|
|
||||||
{this.renderRegisteredItems()}
|
{this.renderRegisteredItems()}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -86,7 +86,7 @@ export class ClusterIconSetting extends React.Component<Props> {
|
|||||||
uid={entity.metadata.uid}
|
uid={entity.metadata.uid}
|
||||||
title={entity.metadata.name}
|
title={entity.metadata.name}
|
||||||
source={entity.metadata.source}
|
source={entity.metadata.source}
|
||||||
icon={entity.spec.iconData}
|
src={entity.spec.icon?.src}
|
||||||
/>
|
/>
|
||||||
<span style={{marginRight: "var(--unit)"}}>Browse for new icon...</span>
|
<span style={{marginRight: "var(--unit)"}}>Browse for new icon...</span>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -129,7 +129,9 @@ export class HotbarEntityIcon extends React.Component<Props> {
|
|||||||
uid={entity.metadata.uid}
|
uid={entity.metadata.uid}
|
||||||
title={entity.metadata.name}
|
title={entity.metadata.name}
|
||||||
source={entity.metadata.source}
|
source={entity.metadata.source}
|
||||||
icon={entity.spec.iconData}
|
src={entity.spec.icon?.src}
|
||||||
|
material={entity.spec.icon?.material}
|
||||||
|
background={entity.spec.icon?.background}
|
||||||
className={className}
|
className={className}
|
||||||
active={isActive}
|
active={isActive}
|
||||||
onMenuOpen={onOpen}
|
onMenuOpen={onOpen}
|
||||||
|
|||||||
@ -20,7 +20,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
.HotbarMenu {
|
.HotbarMenu {
|
||||||
|
|
||||||
.HotbarIconMenu {
|
.HotbarIconMenu {
|
||||||
left: 30px;
|
left: 30px;
|
||||||
min-width: 250px;
|
min-width: 250px;
|
||||||
@ -126,4 +125,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.materialIcon {
|
||||||
|
margin-left: 1px;
|
||||||
|
margin-top: 1px;
|
||||||
|
text-shadow: none;
|
||||||
|
font-size: 180%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,18 +30,21 @@ import { Menu, MenuItem } from "../menu";
|
|||||||
import { MaterialTooltip } from "../material-tooltip/material-tooltip";
|
import { MaterialTooltip } from "../material-tooltip/material-tooltip";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { Avatar } from "../avatar/avatar";
|
import { Avatar } from "../avatar/avatar";
|
||||||
|
import { Icon } from "../icon";
|
||||||
|
|
||||||
export interface HotbarIconProps extends DOMAttributes<HTMLElement> {
|
export interface HotbarIconProps extends DOMAttributes<HTMLElement> {
|
||||||
uid: string;
|
uid: string;
|
||||||
title: string;
|
title: string;
|
||||||
source: string;
|
source: string;
|
||||||
icon?: string;
|
src?: string;
|
||||||
|
material?: string;
|
||||||
onMenuOpen?: () => void;
|
onMenuOpen?: () => void;
|
||||||
className?: IClassName;
|
className?: IClassName;
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
menuItems?: CatalogEntityContextMenu[];
|
menuItems?: CatalogEntityContextMenu[];
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
size?: number;
|
size?: number;
|
||||||
|
background?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMenuItemClick(menuItem: CatalogEntityContextMenu) {
|
function onMenuItemClick(menuItem: CatalogEntityContextMenu) {
|
||||||
@ -62,7 +65,7 @@ function onMenuItemClick(menuItem: CatalogEntityContextMenu) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const HotbarIcon = observer(({menuItems = [], size = 40, ...props}: HotbarIconProps) => {
|
export const HotbarIcon = observer(({menuItems = [], size = 40, ...props}: HotbarIconProps) => {
|
||||||
const { uid, title, icon, active, className, source, disabled, onMenuOpen, onClick, children, ...rest } = props;
|
const { uid, title, src, material, active, className, source, disabled, onMenuOpen, onClick, children, ...rest } = props;
|
||||||
const id = `hotbarIcon-${uid}`;
|
const id = `hotbarIcon-${uid}`;
|
||||||
const [menuOpen, setMenuOpen] = useState(false);
|
const [menuOpen, setMenuOpen] = useState(false);
|
||||||
|
|
||||||
@ -71,38 +74,28 @@ export const HotbarIcon = observer(({menuItems = [], size = 40, ...props}: Hotba
|
|||||||
};
|
};
|
||||||
|
|
||||||
const renderIcon = () => {
|
const renderIcon = () => {
|
||||||
if (icon) {
|
return (
|
||||||
return <img
|
<Avatar
|
||||||
{...rest}
|
|
||||||
src={icon}
|
|
||||||
className={active ? "active" : "default"}
|
|
||||||
width={size}
|
|
||||||
height={size}
|
|
||||||
onClick={(event) => {
|
|
||||||
if (!disabled) {
|
|
||||||
onClick?.(event);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>;
|
|
||||||
} else {
|
|
||||||
return <Avatar
|
|
||||||
{...rest}
|
{...rest}
|
||||||
title={title}
|
title={title}
|
||||||
colorHash={`${title}-${source}`}
|
colorHash={`${title}-${source}`}
|
||||||
className={active ? "active" : "default"}
|
className={active ? "active" : "default"}
|
||||||
width={size}
|
width={size}
|
||||||
height={size}
|
height={size}
|
||||||
|
src={src}
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
if (!disabled) {
|
if (!disabled) {
|
||||||
onClick?.(event);
|
onClick?.(event);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>;
|
>
|
||||||
}
|
{material && <Icon className="materialIcon" material={material}/>}
|
||||||
|
</Avatar>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cssNames("HotbarIcon flex inline", className, { disabled })}>
|
<div className={cssNames("HotbarIcon flex", className, { disabled })}>
|
||||||
<MaterialTooltip title={`${title || "unknown"} (${source || "unknown"})`} placement="right">
|
<MaterialTooltip title={`${title || "unknown"} (${source || "unknown"})`} placement="right">
|
||||||
<div id={id}>
|
<div id={id}>
|
||||||
{renderIcon()}
|
{renderIcon()}
|
||||||
|
|||||||
@ -29,7 +29,8 @@ export function initWelcomeMenuRegistry() {
|
|||||||
{
|
{
|
||||||
title: "Browse Clusters",
|
title: "Browse Clusters",
|
||||||
icon: "view_list",
|
icon: "view_list",
|
||||||
click: () => navigate(catalogURL({ params: { group: "entity.k8slens.dev", kind: "KubernetesCluster" } } ))
|
click: () => navigate(catalogURL({ params: { group: "entity.k8slens.dev", kind: "KubernetesCluster" } } )),
|
||||||
|
testId: "browseClustersButton"
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user