1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00
Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
Jari Kolehmainen 2021-01-22 15:05:03 +02:00
parent 3f158f4a4b
commit c1a3f6f280
13 changed files with 174 additions and 36 deletions

View File

@ -3,6 +3,8 @@
import type { Cluster } from "../../main/cluster"; import type { Cluster } from "../../main/cluster";
import type { Workspace } from "../../common/workspace-store"; import type { Workspace } from "../../common/workspace-store";
import { BaseRegistry } from "./base-registry"; import { BaseRegistry } from "./base-registry";
import { action } from "mobx";
import { LensExtension } from "../lens-extension";
export type CommandContext = { export type CommandContext = {
cluster?: Cluster; cluster?: Cluster;
@ -18,6 +20,18 @@ export interface CommandRegistration {
} }
export class CommandRegistry extends BaseRegistry<CommandRegistration> { export class CommandRegistry extends BaseRegistry<CommandRegistration> {
@action
add(items: CommandRegistration | CommandRegistration[], extension?: LensExtension) {
const itemArray = [items].flat() as CommandRegistration[];
const newIds = itemArray.map((item) => item.id);
const currentIds = this.getItems().map((item) => item.id);
const filteredIds = newIds.filter((id) => !currentIds.includes(id));
const filteredItems = itemArray.filter((item) => filteredIds.includes(item.id));
return super.add(filteredItems, extension);
}
} }
export const commandRegistry = new CommandRegistry(); export const commandRegistry = new CommandRegistry();

View File

@ -26,6 +26,7 @@ import { InstalledExtension, extensionDiscovery } from "../extensions/extension-
import type { LensExtensionId } from "../extensions/lens-extension"; import type { LensExtensionId } from "../extensions/lens-extension";
import { installDeveloperTools } from "./developer-tools"; import { installDeveloperTools } from "./developer-tools";
import { filesystemProvisionerStore } from "./extension-filesystem"; import { filesystemProvisionerStore } from "./extension-filesystem";
import { bindBroadcastHandlers } from "../common/ipc";
const workingDir = path.join(app.getPath("appData"), appName); const workingDir = path.join(app.getPath("appData"), appName);
let proxyPort: number; let proxyPort: number;
@ -63,6 +64,8 @@ app.on("ready", async () => {
logger.info(`🚀 Starting Lens from "${workingDir}"`); logger.info(`🚀 Starting Lens from "${workingDir}"`);
await shellSync(); await shellSync();
bindBroadcastHandlers();
powerMonitor.on("shutdown", () => { powerMonitor.on("shutdown", () => {
app.exit(); app.exit();
}); });

View File

@ -1,7 +1,5 @@
import React from "react"; import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { Link } from "react-router-dom";
import { workspacesURL } from "../../+workspaces";
import { workspaceStore } from "../../../../common/workspace-store"; import { workspaceStore } from "../../../../common/workspace-store";
import { Cluster } from "../../../../main/cluster"; import { Cluster } from "../../../../main/cluster";
import { Select } from "../../../components/select"; import { Select } from "../../../components/select";
@ -18,10 +16,7 @@ export class ClusterWorkspaceSetting extends React.Component<Props> {
<> <>
<SubTitle title="Cluster Workspace"/> <SubTitle title="Cluster Workspace"/>
<p> <p>
Define cluster{" "} Define cluster workspace.
<Link to={workspacesURL()}>
workspace
</Link>.
</p> </p>
<Select <Select
value={this.props.cluster.workspace} value={this.props.cluster.workspace}

View File

@ -1,7 +1,9 @@
import type { RouteProps } from "react-router"; import type { RouteProps } from "react-router";
import { buildURL, IURLParams } from "../../../common/utils/buildUrl"; import { buildURL, IURLParams } from "../../../common/utils/buildUrl";
import { KubeResource } from "../../../common/rbac"; import { isAllowedResource, KubeResource } from "../../../common/rbac";
import { Workloads } from "./workloads"; import { Workloads } from "./workloads";
import { navigate } from "../../navigation";
import { commandRegistry } from "../../../extensions/registries/command-registry";
export const workloadsRoute: RouteProps = { export const workloadsRoute: RouteProps = {
get path() { get path() {
@ -80,3 +82,31 @@ export const workloadURL: Partial<Record<KubeResource, ReturnType<typeof buildUR
"jobs": jobsURL, "jobs": jobsURL,
"cronjobs": cronJobsURL, "cronjobs": cronJobsURL,
}; };
commandRegistry.add({
id: "cluster.viewPods",
title: "Cluster: View Pods",
scope: "cluster",
action: () => navigate(podsURL())
});
commandRegistry.add({
id: "cluster.viewDeployments",
title: "Cluster: View Deployments",
scope: "cluster",
action: () => navigate(deploymentsURL())
});
commandRegistry.add({
id: "cluster.viewDaemonSets",
title: "Cluster: View DaemonSets",
scope: "cluster",
action: () => navigate(daemonSetsURL())
});
commandRegistry.add({
id: "cluster.viewStatefulSets",
title: "Cluster: View StatefulSets",
scope: "cluster",
action: () => navigate(statefulSetsURL())
});

View File

@ -65,7 +65,7 @@ export class RemoveWorkspace extends React.Component {
commandRegistry.add({ commandRegistry.add({
id: "workspace.removeWorkspace", id: "workspace.removeWorkspace",
title: "Workspace: Remove ...", title: "Workspace: Remove workspace ...",
scope: "global", scope: "global",
action: () => openCommandDialog(<RemoveWorkspace />) action: () => openCommandDialog(<RemoveWorkspace />)
}); });

View File

@ -1,14 +0,0 @@
.Workspaces {
.workspace {
--flex-gap: #{$padding};
padding: $padding / 2;
&.default {
font-style: italic;
}
> .description {
flex: 1;
}
}
}

View File

@ -1,4 +1,3 @@
import "./workspaces.scss";
import React from "react"; import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { computed} from "mobx"; import { computed} from "mobx";
@ -65,7 +64,7 @@ export class ChooseWorkspace extends React.Component {
commandRegistry.add({ commandRegistry.add({
id: "workspace.chooseWorkspace", id: "workspace.chooseWorkspace",
title: "Workspace: Choose...", title: "Workspace: Switch to workspace ...",
scope: "global", scope: "global",
action: () => openCommandDialog(<ChooseWorkspace />) action: () => openCommandDialog(<ChooseWorkspace />)
}); });

View File

@ -173,6 +173,8 @@ export class App extends React.Component {
} }
render() { render() {
const cluster = getHostedCluster();
return ( return (
<Router history={history}> <Router history={history}>
<ErrorBoundary> <ErrorBoundary>
@ -204,7 +206,7 @@ export class App extends React.Component {
<StatefulSetScaleDialog/> <StatefulSetScaleDialog/>
<ReplicaSetScaleDialog/> <ReplicaSetScaleDialog/>
<CronJobTriggerDialog/> <CronJobTriggerDialog/>
<CommandContainer listenPaletteOpen={false} /> <CommandContainer cluster={cluster} />
</ErrorBoundary> </ErrorBoundary>
</Router> </Router>
); );

View File

@ -22,6 +22,10 @@ import { ConfirmDialog } from "../confirm-dialog";
import { clusterViewURL } from "./cluster-view.route"; import { clusterViewURL } from "./cluster-view.route";
import { getExtensionPageUrl, globalPageMenuRegistry, globalPageRegistry } from "../../../extensions/registries"; import { getExtensionPageUrl, globalPageMenuRegistry, globalPageRegistry } from "../../../extensions/registries";
import { clusterDisconnectHandler } from "../../../common/cluster-ipc"; import { clusterDisconnectHandler } from "../../../common/cluster-ipc";
import { commandRegistry } from "../../../extensions/registries/command-registry";
import { closeCommandDialog, openCommandDialog } from "../command-palette/command-container";
import { computed } from "mobx";
import { Select } from "../select";
interface Props { interface Props {
className?: IClassName; className?: IClassName;
@ -178,3 +182,41 @@ export class ClustersMenu extends React.Component<Props> {
); );
} }
} }
@observer
export class ChooseCluster extends React.Component {
@computed get options() {
const clusters = clusterStore.getByWorkspaceId(workspaceStore.currentWorkspaceId).filter(cluster => cluster.enabled);
const options = clusters.map((cluster) => {
return { value: cluster.id, label: cluster.name };
});
return options;
}
onChange(clusterId: string) {
navigate(clusterViewURL({ params: { clusterId } }));
closeCommandDialog();
}
render() {
return (
<Select
onChange={(v) => this.onChange(v.value)}
components={{ DropdownIndicator: null, IndicatorSeparator: null }}
menuIsOpen={true}
options={this.options}
autoFocus={true}
escapeClearsValue={false}
placeholder="Switch to cluster" />
);
}
}
commandRegistry.add({
id: "workspace.chooseCluster",
title: "Workspace: Switch to cluster ...",
scope: "global",
action: () => openCommandDialog(<ChooseCluster />)
});

View File

@ -7,6 +7,10 @@ import { Dialog } from "../dialog";
import { EventEmitter } from "../../../common/event-emitter"; import { EventEmitter } from "../../../common/event-emitter";
import { subscribeToBroadcast } from "../../../common/ipc"; import { subscribeToBroadcast } from "../../../common/ipc";
import { CommandDialog } from "./command-dialog"; import { CommandDialog } from "./command-dialog";
import { CommandRegistration, commandRegistry } from "../../../extensions/registries/command-registry";
import { clusterStore } from "../../../common/cluster-store";
import { workspaceStore } from "../../../common/workspace-store";
import { Cluster } from "../../../main/cluster";
export type CommandDialogEvent = { export type CommandDialogEvent = {
component: React.ReactElement component: React.ReactElement
@ -23,7 +27,7 @@ export function closeCommandDialog() {
} }
@observer @observer
export class CommandContainer extends React.Component<{listenPaletteOpen: boolean}> { export class CommandContainer extends React.Component<{cluster?: Cluster}> {
@observable visible = false; @observable visible = false;
@observable commandComponent: React.ReactElement; @observable commandComponent: React.ReactElement;
@ -39,22 +43,49 @@ export class CommandContainer extends React.Component<{listenPaletteOpen: boolea
this.commandComponent = null; this.commandComponent = null;
} }
private findCommandById(commandId: string) {
return commandRegistry.getItems().find((command) => command.id === commandId);
}
private runCommand(command: CommandRegistration) {
command.action({
cluster: clusterStore.active,
workspace: workspaceStore.currentWorkspace
});
}
onClusterAction(commandId: string) {
const command = this.findCommandById(commandId);
if (command) {
this.runCommand(command);
}
}
componentDidMount() { componentDidMount() {
if (this.props.listenPaletteOpen) { if (this.props.cluster) {
subscribeToBroadcast(`command-palette:run-action:${this.props.cluster.id}`, (event, commandId: string) => {
console.log("run-action", commandId);
const command = this.findCommandById(commandId);
if (command) {
this.runCommand(command);
}
});
} else {
subscribeToBroadcast("command-palette:open", () => { subscribeToBroadcast("command-palette:open", () => {
openCommandDialog(<CommandDialog />); openCommandDialog(<CommandDialog />);
}); });
} }
window.addEventListener("keyup", (e) => this.escHandler(e), true); window.addEventListener("keyup", (e) => this.escHandler(e), true);
commandDialogBus.addListener((event) => { commandDialogBus.addListener((event) => {
console.log(event);
this.commandComponent = event.component; this.commandComponent = event.component;
}); });
} }
render() { render() {
return ( return (
<Dialog isOpen={!!this.commandComponent} animated={false}> <Dialog isOpen={!!this.commandComponent} animated={false} onClose={() => this.commandComponent = null}>
<div id="command-container"> <div id="command-container">
{this.commandComponent} {this.commandComponent}
</div> </div>

View File

@ -7,15 +7,37 @@ import { commandRegistry } from "../../../extensions/registries/command-registry
import { clusterStore } from "../../../common/cluster-store"; import { clusterStore } from "../../../common/cluster-store";
import { workspaceStore } from "../../../common/workspace-store"; import { workspaceStore } from "../../../common/workspace-store";
import { closeCommandDialog } from "./command-container"; import { closeCommandDialog } from "./command-container";
import { broadcastMessage } from "../../../common/ipc";
@observer @observer
export class CommandDialog extends React.Component { export class CommandDialog extends React.Component {
@observable menuIsOpen = true; @observable menuIsOpen = true;
@computed get options() { @computed get options() {
return commandRegistry.getItems().map((command) => { const context = {
cluster: clusterStore.active,
workspace: workspaceStore.currentWorkspace
};
return commandRegistry.getItems().filter((command) => {
if (command.scope === "cluster" && !clusterStore.active) {
return false;
}
if (!command.isActive) {
return true;
}
try {
return command.isActive(context);
} catch(e) {
console.error(e);
return false;
}
}).map((command) => {
return { value: command.id, label: command.title }; return { value: command.id, label: command.title };
}); }).sort((a, b) => a.label > b.label ? 1 : -1);
} }
private onChange(value: string) { private onChange(value: string) {
@ -29,10 +51,15 @@ export class CommandDialog extends React.Component {
try { try {
closeCommandDialog(); closeCommandDialog();
action({
cluster: clusterStore.active, if (command.scope === "global") {
workspace: workspaceStore.currentWorkspace action({
}); cluster: clusterStore.active,
workspace: workspaceStore.currentWorkspace
});
} else if(clusterStore.active) {
broadcastMessage(`command-palette:run-action:${clusterStore.active.id}`, command.id);
}
} catch(error) { } catch(error) {
console.error("failed to execute command", command.id, error); console.error("failed to execute command", command.id, error);
} }

View File

@ -22,6 +22,7 @@ import { TerminalWindow } from "./terminal-window";
import { createTerminalTab, isTerminalTab } from "./terminal.store"; import { createTerminalTab, isTerminalTab } from "./terminal.store";
import { UpgradeChart } from "./upgrade-chart"; import { UpgradeChart } from "./upgrade-chart";
import { isUpgradeChartTab } from "./upgrade-chart.store"; import { isUpgradeChartTab } from "./upgrade-chart.store";
import { commandRegistry } from "../../../extensions/registries/command-registry";
interface Props { interface Props {
className?: string; className?: string;
@ -131,3 +132,11 @@ export class Dock extends React.Component<Props> {
); );
} }
} }
commandRegistry.add({
id: "cluster.openTerminal",
title: "Cluster: Open terminal",
scope: "cluster",
action: () => createTerminalTab(),
isActive: (context) => !!context.cluster
});

View File

@ -38,7 +38,7 @@ export class LensApp extends React.Component {
</ErrorBoundary> </ErrorBoundary>
<Notifications/> <Notifications/>
<ConfirmDialog/> <ConfirmDialog/>
<CommandContainer listenPaletteOpen={true} /> <CommandContainer />
</Router> </Router>
); );
} }