1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

hotbar store

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
Jari Kolehmainen 2021-03-31 14:42:46 +03:00
parent 8a0cf91ab4
commit 4ac0c7c899
16 changed files with 187 additions and 22 deletions

View File

@ -2,7 +2,7 @@
"name": "kontena-lens",
"productName": "Lens",
"description": "Lens - The Kubernetes IDE",
"version": "4.2.0-rc.2",
"version": "5.0.0-alpha.0",
"main": "static/build/main.js",
"copyright": "© 2021, Mirantis, Inc.",
"license": "MIT",

View File

@ -1,7 +1,9 @@
import { observable } from "mobx";
import { catalogCategoryRegistry } from "../catalog-category-registry";
import { CatalogCategory, CatalogEntity, CatalogEntityActionContext, CatalogEntityData, CatalogEntityMetadata, CatalogEntityStatus } from "../catalog-entity";
import { CatalogCategory, CatalogEntity, CatalogEntityActionContext, CatalogEntityContextMenuContext, CatalogEntityData, CatalogEntityMetadata, CatalogEntityStatus } from "../catalog-entity";
import { clusterDisconnectHandler } from "../cluster-ipc";
import { clusterStore } from "../cluster-store";
import { requestMain } from "../ipc";
export type KubernetesClusterSpec = {
kubeconfigPath: string;
@ -45,8 +47,30 @@ export class KubernetesCluster implements CatalogEntity {
//
}
async onContextMenuOpen() {
//
async onContextMenuOpen(context: CatalogEntityContextMenuContext) {
context.menuItems = [
{
icon: "settings",
title: "Settings",
onClick: async () => context.navigate(`/cluster/${this.metadata.uid}/settings`)
},
{
icon: "delete",
title: "Delete",
onClick: async () => clusterStore.removeById(this.metadata.uid)
},
];
if (this.status.active) {
context.menuItems.unshift({
icon: "link_off",
title: "Disconnect",
onClick: async () => {
clusterStore.deactivate(this.metadata.uid);
requestMain(clusterDisconnectHandler, this.metadata.uid);
}
});
}
}
}

View File

@ -40,8 +40,14 @@ export interface CatalogEntityActionContext {
navigate: (url: string) => void;
}
export type CatalogEntityContextMenu = {
icon: string;
title: string;
onClick: () => Promise<void>;
};
export interface CatalogEntityContextMenuContext extends CatalogEntityActionContext {
menu: any
menuItems: CatalogEntityContextMenu[];
}
export type CatalogEntityData = {

View File

@ -4,7 +4,6 @@ import { appEventBus } from "./event-bus";
import { ResourceApplier } from "../main/resource-applier";
import { ipcMain, IpcMainInvokeEvent } from "electron";
import { clusterFrameMap } from "./cluster-frames";
import { catalogEntityRegistry } from "./catalog-entity-registry";
export const clusterActivateHandler = "cluster:activate";
export const clusterSetFrameIdHandler = "cluster:set-frame-id";
@ -18,10 +17,6 @@ if (ipcMain) {
const cluster = clusterStore.getById(clusterId);
if (cluster) {
catalogEntityRegistry.getItemsForApiKind("entity.k8slens.dev/v1alpha1", "KubernetesCluster").forEach((item) => {
if (item.metadata.uid === cluster.id) item.status.active = true;
});
return cluster.activate(force);
}
});
@ -48,9 +43,6 @@ if (ipcMain) {
if (cluster) {
cluster.disconnect();
catalogEntityRegistry.getItemsForApiKind("entity.k8slens.dev/v1alpha1", "KubernetesCluster").forEach((item) => {
if (item.metadata.uid === cluster.id) item.status.active = false;
});
clusterFrameMap.delete(cluster.id);
}
});

View File

@ -104,7 +104,6 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
@observable removedClusters = observable.map<ClusterId, Cluster>();
@observable clusters = observable.map<ClusterId, Cluster>();
private static stateRequestChannel = "cluster:states";
private constructor() {

View File

@ -0,0 +1,64 @@
import { action, comparer, observable, toJS } from "mobx";
import { BaseStore } from "./base-store";
import migrations from "../migrations/hotbar-store";
export interface HotbarItem {
entity: {
uid: string;
};
params?: {
[key: string]: string;
}
}
export interface Hotbar {
name: string;
items: HotbarItem[];
}
export interface HotbarStoreModel {
hotbars: Hotbar[];
}
export class HotbarStore extends BaseStore<HotbarStoreModel> {
@observable hotbars: Hotbar[] = [];
private constructor() {
super({
configName: "lens-hotbar-store",
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
syncOptions: {
equals: comparer.structural,
},
migrations,
});
}
@action protected async fromStore(data: Partial<HotbarStoreModel> = {}) {
this.hotbars = data.hotbars || [];
}
getByName(name: string) {
return this.hotbars.find((hotbar) => hotbar.name === name);
}
add(hotbar: Hotbar) {
this.hotbars.push(hotbar);
}
remove(hotbar: Hotbar) {
this.hotbars = this.hotbars.filter((h) => h !== hotbar);
}
toJSON(): HotbarStoreModel {
const model: HotbarStoreModel = {
hotbars: this.hotbars
};
return toJS(model, {
recurseEverything: true,
});
}
}
export const hotbarStore = HotbarStore.getInstance<HotbarStore>();

View File

@ -21,6 +21,7 @@ export class CatalogPusher {
}
broadcast() {
console.log("BROADCAST");
broadcastMessage("catalog:items", toJS(this.catalog.items, { recurseEverything: true }));
}
}

View File

@ -69,9 +69,7 @@ export class ClusterManager extends Singleton {
if (entityIndex === -1) {
this.catalogSource.push(newEntity);
} else {
const entity = this.catalogSource[entityIndex] as KubernetesCluster;
newEntity.status.active = entity.status.active;
newEntity.status.active = !cluster.disconnected;
this.catalogSource.splice(entityIndex, 1, newEntity);
}
});
@ -86,7 +84,7 @@ export class ClusterManager extends Singleton {
name: cluster.name,
source: "local",
labels: {
"distro": cluster.metadata["distro"]?.toString()
"distro": (cluster.metadata["distribution"] || "unknown").toString()
}
},
spec: {

View File

@ -31,6 +31,7 @@ import { startUpdateChecking } from "./app-updater";
import { IpcRendererNavigationEvents } from "../renderer/navigation/events";
import { CatalogPusher } from "./catalog-pusher";
import { catalogEntityRegistry } from "../common/catalog-entity-registry";
import { hotbarStore } from "../common/hotbar-store";
const workingDir = path.join(app.getPath("appData"), appName);
let proxyPort: number;
@ -108,6 +109,7 @@ app.on("ready", async () => {
await Promise.all([
userStore.load(),
clusterStore.load(),
hotbarStore.load(),
extensionsStore.load(),
filesystemProvisionerStore.load(),
]);

View File

@ -0,0 +1,28 @@
// Cleans up a store that had the state related data stored
import { Hotbar } from "../../common/hotbar-store";
import { clusterStore } from "../../common/cluster-store";
import { migration } from "../migration-wrapper";
export default migration({
version: "5.0.0-alpha.0",
run(store) {
const hotbars: Hotbar[] = [];
clusterStore.enabledClustersList.forEach((cluster: any) => {
const name = cluster.workspace || "default";
let hotbar = hotbars.find((h) => h.name === name);
if (!hotbar) {
hotbar = { name, items: [] };
hotbars.push(hotbar);
}
hotbar.items.push({
entity: { uid: cluster.id },
params: {}
});
});
store.set("hotbars", hotbars);
}
});

View File

@ -0,0 +1,7 @@
// Hotbar store migrations
import version500alpha0 from "./5.0.0-alpha.0";
export default {
...version500alpha0,
};

View File

@ -4,7 +4,7 @@ import { CatalogEntity, CatalogEntityData } from "../../common/catalog-entity";
import { catalogCategoryRegistry, CatalogCategoryRegistry } from "../../common/catalog-category-registry";
import "../../common/catalog-entities";
export { CatalogEntity, CatalogEntityData } from "../../common/catalog-entity";
export { CatalogEntity, CatalogEntityData, CatalogEntityContextMenuContext } from "../../common/catalog-entity";
export class CatalogEntityRegistry {
@observable protected _items: CatalogEntity[] = observable.array([], { deep: true });

View File

@ -14,6 +14,7 @@ import * as LensExtensions from "../extensions/extension-api";
import { extensionDiscovery } from "../extensions/extension-discovery";
import { extensionLoader } from "../extensions/extension-loader";
import { extensionsStore } from "../extensions/extensions-store";
import { hotbarStore } from "../common/hotbar-store";
import { filesystemProvisionerStore } from "../main/extension-filesystem";
import { App } from "./components/app";
import { LensApp } from "./lens-app";
@ -55,6 +56,7 @@ export async function bootstrap(App: AppComponent) {
// preload common stores
await Promise.all([
userStore.load(),
hotbarStore.load(),
clusterStore.load(),
extensionsStore.load(),
filesystemProvisionerStore.load(),

View File

@ -26,9 +26,25 @@ export class CatalogEntityItem implements ItemObject {
return this.entity.status.phase;
}
get labels() {
const labels: string[] = [];
Object.keys(this.entity.metadata.labels).forEach((key) => {
const value = this.entity.metadata.labels[key];
labels.push(`${key}=${value}`);
});
return labels;
}
onRun(ctx: any) {
this.entity.onRun(ctx);
}
onContextMenuOpen(ctx: any) {
this.entity.onContextMenuOpen(ctx);
}
}
@autobind()

View File

@ -7,6 +7,10 @@ import { CatalogEntityItem, CatalogEntityStore } from "./catalog-entity.store";
import { navigate } from "../../navigation";
import { kebabCase } from "lodash";
import { PageLayout } from "../layout/page-layout";
import { MenuItem, MenuActions } from "../menu";
import { Icon } from "../icon";
import { CatalogEntityContextMenuContext } from "../../api/catalog-entity-registry";
import { Badge } from "../badge";
enum sortBy {
name = "name",
@ -47,13 +51,35 @@ export class LandingPage extends React.Component {
}}
renderTableHeader={[
{ title: "Name", className: "name", sortBy: sortBy.name },
{ title: "Labels", className: "labels" },
{ title: "Status", className: "status", sortBy: sortBy.status },
]}
renderTableContents={(item: CatalogEntityItem) => [
item.name,
item.labels.map((label) => <Badge key={label} label={label} title={label} />),
{ title: item.phase, className: kebabCase(item.phase) }
]}
onDetails={(item: CatalogEntityItem) => item.onRun({ navigate: (url: string) => navigate(url)})}
renderItemMenu={(item: CatalogEntityItem) => {
const menuOpenContext: CatalogEntityContextMenuContext = {
menuItems: [],
navigate: (url: string) => navigate(url)
};
item.onContextMenuOpen(menuOpenContext);
return (
<MenuActions>
{ menuOpenContext.menuItems.map((menuItem) => {
return (
<MenuItem key={menuItem.title} onClick={() => menuItem.onClick()}>
<Icon material={menuItem.icon} interactive={true} title={menuItem.title}/> {menuItem.title}
</MenuItem>
);
})}
</MenuActions>
);
}}
/>
</PageLayout>
);

View File

@ -5,8 +5,8 @@ import { observer } from "mobx-react";
import { HotbarIcon } from "./hotbar-icon";
import { cssNames, IClassName } from "../../utils";
import { catalogEntityRegistry } from "../../api/catalog-entity-registry";
import { KubernetesCluster } from "../../../common/catalog-entities/kubernetes-cluster";
import { navigate } from "../../navigation";
import { hotbarStore } from "../../../common/hotbar-store";
interface Props {
className?: IClassName;
@ -16,7 +16,8 @@ interface Props {
export class HotbarMenu extends React.Component<Props> {
render() {
const { className } = this.props;
const items = catalogEntityRegistry.getItemsForApiKind<KubernetesCluster>("entity.k8slens.dev/v1alpha1", "KubernetesCluster");
const hotbar = hotbarStore.getByName("default"); // FIXME
const items = hotbar.items.map((item) => catalogEntityRegistry.items.find((entity) => entity.metadata.uid === item.entity.uid)).filter(Boolean);
const runContext = {
navigate: (url: string) => navigate(url)
};
@ -31,7 +32,6 @@ export class HotbarMenu extends React.Component<Props> {
entity={entity}
isActive={entity.status.active}
onClick={() => entity.onRun(runContext)}
onContextMenu={() => entity.onContextMenuOpen()}
/>
);
})}