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:
parent
8a0cf91ab4
commit
4ac0c7c899
@ -2,7 +2,7 @@
|
|||||||
"name": "kontena-lens",
|
"name": "kontena-lens",
|
||||||
"productName": "Lens",
|
"productName": "Lens",
|
||||||
"description": "Lens - The Kubernetes IDE",
|
"description": "Lens - The Kubernetes IDE",
|
||||||
"version": "4.2.0-rc.2",
|
"version": "5.0.0-alpha.0",
|
||||||
"main": "static/build/main.js",
|
"main": "static/build/main.js",
|
||||||
"copyright": "© 2021, Mirantis, Inc.",
|
"copyright": "© 2021, Mirantis, Inc.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import { observable } from "mobx";
|
import { observable } from "mobx";
|
||||||
import { catalogCategoryRegistry } from "../catalog-category-registry";
|
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 { clusterStore } from "../cluster-store";
|
||||||
|
import { requestMain } from "../ipc";
|
||||||
|
|
||||||
export type KubernetesClusterSpec = {
|
export type KubernetesClusterSpec = {
|
||||||
kubeconfigPath: string;
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -40,8 +40,14 @@ export interface CatalogEntityActionContext {
|
|||||||
navigate: (url: string) => void;
|
navigate: (url: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type CatalogEntityContextMenu = {
|
||||||
|
icon: string;
|
||||||
|
title: string;
|
||||||
|
onClick: () => Promise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
export interface CatalogEntityContextMenuContext extends CatalogEntityActionContext {
|
export interface CatalogEntityContextMenuContext extends CatalogEntityActionContext {
|
||||||
menu: any
|
menuItems: CatalogEntityContextMenu[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CatalogEntityData = {
|
export type CatalogEntityData = {
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import { appEventBus } from "./event-bus";
|
|||||||
import { ResourceApplier } from "../main/resource-applier";
|
import { ResourceApplier } from "../main/resource-applier";
|
||||||
import { ipcMain, IpcMainInvokeEvent } from "electron";
|
import { ipcMain, IpcMainInvokeEvent } from "electron";
|
||||||
import { clusterFrameMap } from "./cluster-frames";
|
import { clusterFrameMap } from "./cluster-frames";
|
||||||
import { catalogEntityRegistry } from "./catalog-entity-registry";
|
|
||||||
|
|
||||||
export const clusterActivateHandler = "cluster:activate";
|
export const clusterActivateHandler = "cluster:activate";
|
||||||
export const clusterSetFrameIdHandler = "cluster:set-frame-id";
|
export const clusterSetFrameIdHandler = "cluster:set-frame-id";
|
||||||
@ -18,10 +17,6 @@ if (ipcMain) {
|
|||||||
const cluster = clusterStore.getById(clusterId);
|
const cluster = clusterStore.getById(clusterId);
|
||||||
|
|
||||||
if (cluster) {
|
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);
|
return cluster.activate(force);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -48,9 +43,6 @@ if (ipcMain) {
|
|||||||
|
|
||||||
if (cluster) {
|
if (cluster) {
|
||||||
cluster.disconnect();
|
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);
|
clusterFrameMap.delete(cluster.id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -104,7 +104,6 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
@observable removedClusters = observable.map<ClusterId, Cluster>();
|
@observable removedClusters = observable.map<ClusterId, Cluster>();
|
||||||
@observable clusters = observable.map<ClusterId, Cluster>();
|
@observable clusters = observable.map<ClusterId, Cluster>();
|
||||||
|
|
||||||
|
|
||||||
private static stateRequestChannel = "cluster:states";
|
private static stateRequestChannel = "cluster:states";
|
||||||
|
|
||||||
private constructor() {
|
private constructor() {
|
||||||
|
|||||||
64
src/common/hotbar-store.ts
Normal file
64
src/common/hotbar-store.ts
Normal 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>();
|
||||||
@ -21,6 +21,7 @@ export class CatalogPusher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
broadcast() {
|
broadcast() {
|
||||||
|
console.log("BROADCAST");
|
||||||
broadcastMessage("catalog:items", toJS(this.catalog.items, { recurseEverything: true }));
|
broadcastMessage("catalog:items", toJS(this.catalog.items, { recurseEverything: true }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -69,9 +69,7 @@ export class ClusterManager extends Singleton {
|
|||||||
if (entityIndex === -1) {
|
if (entityIndex === -1) {
|
||||||
this.catalogSource.push(newEntity);
|
this.catalogSource.push(newEntity);
|
||||||
} else {
|
} else {
|
||||||
const entity = this.catalogSource[entityIndex] as KubernetesCluster;
|
newEntity.status.active = !cluster.disconnected;
|
||||||
|
|
||||||
newEntity.status.active = entity.status.active;
|
|
||||||
this.catalogSource.splice(entityIndex, 1, newEntity);
|
this.catalogSource.splice(entityIndex, 1, newEntity);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -86,7 +84,7 @@ export class ClusterManager extends Singleton {
|
|||||||
name: cluster.name,
|
name: cluster.name,
|
||||||
source: "local",
|
source: "local",
|
||||||
labels: {
|
labels: {
|
||||||
"distro": cluster.metadata["distro"]?.toString()
|
"distro": (cluster.metadata["distribution"] || "unknown").toString()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
spec: {
|
spec: {
|
||||||
|
|||||||
@ -31,6 +31,7 @@ import { startUpdateChecking } from "./app-updater";
|
|||||||
import { IpcRendererNavigationEvents } from "../renderer/navigation/events";
|
import { IpcRendererNavigationEvents } from "../renderer/navigation/events";
|
||||||
import { CatalogPusher } from "./catalog-pusher";
|
import { CatalogPusher } from "./catalog-pusher";
|
||||||
import { catalogEntityRegistry } from "../common/catalog-entity-registry";
|
import { catalogEntityRegistry } from "../common/catalog-entity-registry";
|
||||||
|
import { hotbarStore } from "../common/hotbar-store";
|
||||||
|
|
||||||
const workingDir = path.join(app.getPath("appData"), appName);
|
const workingDir = path.join(app.getPath("appData"), appName);
|
||||||
let proxyPort: number;
|
let proxyPort: number;
|
||||||
@ -108,6 +109,7 @@ app.on("ready", async () => {
|
|||||||
await Promise.all([
|
await Promise.all([
|
||||||
userStore.load(),
|
userStore.load(),
|
||||||
clusterStore.load(),
|
clusterStore.load(),
|
||||||
|
hotbarStore.load(),
|
||||||
extensionsStore.load(),
|
extensionsStore.load(),
|
||||||
filesystemProvisionerStore.load(),
|
filesystemProvisionerStore.load(),
|
||||||
]);
|
]);
|
||||||
|
|||||||
28
src/migrations/hotbar-store/5.0.0-alpha.0.ts
Normal file
28
src/migrations/hotbar-store/5.0.0-alpha.0.ts
Normal 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
7
src/migrations/hotbar-store/index.ts
Normal file
7
src/migrations/hotbar-store/index.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// Hotbar store migrations
|
||||||
|
|
||||||
|
import version500alpha0 from "./5.0.0-alpha.0";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
...version500alpha0,
|
||||||
|
};
|
||||||
@ -4,7 +4,7 @@ import { CatalogEntity, CatalogEntityData } from "../../common/catalog-entity";
|
|||||||
import { catalogCategoryRegistry, CatalogCategoryRegistry } from "../../common/catalog-category-registry";
|
import { catalogCategoryRegistry, CatalogCategoryRegistry } from "../../common/catalog-category-registry";
|
||||||
import "../../common/catalog-entities";
|
import "../../common/catalog-entities";
|
||||||
|
|
||||||
export { CatalogEntity, CatalogEntityData } from "../../common/catalog-entity";
|
export { CatalogEntity, CatalogEntityData, CatalogEntityContextMenuContext } from "../../common/catalog-entity";
|
||||||
|
|
||||||
export class CatalogEntityRegistry {
|
export class CatalogEntityRegistry {
|
||||||
@observable protected _items: CatalogEntity[] = observable.array([], { deep: true });
|
@observable protected _items: CatalogEntity[] = observable.array([], { deep: true });
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import * as LensExtensions from "../extensions/extension-api";
|
|||||||
import { extensionDiscovery } from "../extensions/extension-discovery";
|
import { extensionDiscovery } from "../extensions/extension-discovery";
|
||||||
import { extensionLoader } from "../extensions/extension-loader";
|
import { extensionLoader } from "../extensions/extension-loader";
|
||||||
import { extensionsStore } from "../extensions/extensions-store";
|
import { extensionsStore } from "../extensions/extensions-store";
|
||||||
|
import { hotbarStore } from "../common/hotbar-store";
|
||||||
import { filesystemProvisionerStore } from "../main/extension-filesystem";
|
import { filesystemProvisionerStore } from "../main/extension-filesystem";
|
||||||
import { App } from "./components/app";
|
import { App } from "./components/app";
|
||||||
import { LensApp } from "./lens-app";
|
import { LensApp } from "./lens-app";
|
||||||
@ -55,6 +56,7 @@ export async function bootstrap(App: AppComponent) {
|
|||||||
// preload common stores
|
// preload common stores
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
userStore.load(),
|
userStore.load(),
|
||||||
|
hotbarStore.load(),
|
||||||
clusterStore.load(),
|
clusterStore.load(),
|
||||||
extensionsStore.load(),
|
extensionsStore.load(),
|
||||||
filesystemProvisionerStore.load(),
|
filesystemProvisionerStore.load(),
|
||||||
|
|||||||
@ -26,9 +26,25 @@ export class CatalogEntityItem implements ItemObject {
|
|||||||
return this.entity.status.phase;
|
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) {
|
onRun(ctx: any) {
|
||||||
this.entity.onRun(ctx);
|
this.entity.onRun(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onContextMenuOpen(ctx: any) {
|
||||||
|
this.entity.onContextMenuOpen(ctx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind()
|
@autobind()
|
||||||
|
|||||||
@ -7,6 +7,10 @@ import { CatalogEntityItem, CatalogEntityStore } from "./catalog-entity.store";
|
|||||||
import { navigate } from "../../navigation";
|
import { navigate } from "../../navigation";
|
||||||
import { kebabCase } from "lodash";
|
import { kebabCase } from "lodash";
|
||||||
import { PageLayout } from "../layout/page-layout";
|
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 {
|
enum sortBy {
|
||||||
name = "name",
|
name = "name",
|
||||||
@ -47,13 +51,35 @@ export class LandingPage extends React.Component {
|
|||||||
}}
|
}}
|
||||||
renderTableHeader={[
|
renderTableHeader={[
|
||||||
{ title: "Name", className: "name", sortBy: sortBy.name },
|
{ title: "Name", className: "name", sortBy: sortBy.name },
|
||||||
|
{ title: "Labels", className: "labels" },
|
||||||
{ title: "Status", className: "status", sortBy: sortBy.status },
|
{ title: "Status", className: "status", sortBy: sortBy.status },
|
||||||
]}
|
]}
|
||||||
renderTableContents={(item: CatalogEntityItem) => [
|
renderTableContents={(item: CatalogEntityItem) => [
|
||||||
item.name,
|
item.name,
|
||||||
|
item.labels.map((label) => <Badge key={label} label={label} title={label} />),
|
||||||
{ title: item.phase, className: kebabCase(item.phase) }
|
{ title: item.phase, className: kebabCase(item.phase) }
|
||||||
]}
|
]}
|
||||||
onDetails={(item: CatalogEntityItem) => item.onRun({ navigate: (url: string) => navigate(url)})}
|
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>
|
</PageLayout>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -5,8 +5,8 @@ import { observer } from "mobx-react";
|
|||||||
import { HotbarIcon } from "./hotbar-icon";
|
import { HotbarIcon } from "./hotbar-icon";
|
||||||
import { cssNames, IClassName } from "../../utils";
|
import { cssNames, IClassName } from "../../utils";
|
||||||
import { catalogEntityRegistry } from "../../api/catalog-entity-registry";
|
import { catalogEntityRegistry } from "../../api/catalog-entity-registry";
|
||||||
import { KubernetesCluster } from "../../../common/catalog-entities/kubernetes-cluster";
|
|
||||||
import { navigate } from "../../navigation";
|
import { navigate } from "../../navigation";
|
||||||
|
import { hotbarStore } from "../../../common/hotbar-store";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: IClassName;
|
className?: IClassName;
|
||||||
@ -16,7 +16,8 @@ interface Props {
|
|||||||
export class HotbarMenu extends React.Component<Props> {
|
export class HotbarMenu extends React.Component<Props> {
|
||||||
render() {
|
render() {
|
||||||
const { className } = this.props;
|
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 = {
|
const runContext = {
|
||||||
navigate: (url: string) => navigate(url)
|
navigate: (url: string) => navigate(url)
|
||||||
};
|
};
|
||||||
@ -31,7 +32,6 @@ export class HotbarMenu extends React.Component<Props> {
|
|||||||
entity={entity}
|
entity={entity}
|
||||||
isActive={entity.status.active}
|
isActive={entity.status.active}
|
||||||
onClick={() => entity.onRun(runContext)}
|
onClick={() => entity.onRun(runContext)}
|
||||||
onContextMenu={() => entity.onContextMenuOpen()}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user