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 { Workspace } from "../../common/workspace-store";
import { BaseRegistry } from "./base-registry";
import { action } from "mobx";
import { LensExtension } from "../lens-extension";
export type CommandContext = {
cluster?: Cluster;
@ -18,6 +20,18 @@ export interface 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();

View File

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

View File

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

View File

@ -1,7 +1,9 @@
import type { RouteProps } from "react-router";
import { buildURL, IURLParams } from "../../../common/utils/buildUrl";
import { KubeResource } from "../../../common/rbac";
import { isAllowedResource, KubeResource } from "../../../common/rbac";
import { Workloads } from "./workloads";
import { navigate } from "../../navigation";
import { commandRegistry } from "../../../extensions/registries/command-registry";
export const workloadsRoute: RouteProps = {
get path() {
@ -80,3 +82,31 @@ export const workloadURL: Partial<Record<KubeResource, ReturnType<typeof buildUR
"jobs": jobsURL,
"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({
id: "workspace.removeWorkspace",
title: "Workspace: Remove ...",
title: "Workspace: Remove workspace ...",
scope: "global",
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 { observer } from "mobx-react";
import { computed} from "mobx";
@ -65,7 +64,7 @@ export class ChooseWorkspace extends React.Component {
commandRegistry.add({
id: "workspace.chooseWorkspace",
title: "Workspace: Choose...",
title: "Workspace: Switch to workspace ...",
scope: "global",
action: () => openCommandDialog(<ChooseWorkspace />)
});

View File

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

View File

@ -22,6 +22,10 @@ import { ConfirmDialog } from "../confirm-dialog";
import { clusterViewURL } from "./cluster-view.route";
import { getExtensionPageUrl, globalPageMenuRegistry, globalPageRegistry } from "../../../extensions/registries";
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 {
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 { subscribeToBroadcast } from "../../../common/ipc";
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 = {
component: React.ReactElement
@ -23,7 +27,7 @@ export function closeCommandDialog() {
}
@observer
export class CommandContainer extends React.Component<{listenPaletteOpen: boolean}> {
export class CommandContainer extends React.Component<{cluster?: Cluster}> {
@observable visible = false;
@observable commandComponent: React.ReactElement;
@ -39,22 +43,49 @@ export class CommandContainer extends React.Component<{listenPaletteOpen: boolea
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() {
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", () => {
openCommandDialog(<CommandDialog />);
});
}
window.addEventListener("keyup", (e) => this.escHandler(e), true);
commandDialogBus.addListener((event) => {
console.log(event);
this.commandComponent = event.component;
});
}
render() {
return (
<Dialog isOpen={!!this.commandComponent} animated={false}>
<Dialog isOpen={!!this.commandComponent} animated={false} onClose={() => this.commandComponent = null}>
<div id="command-container">
{this.commandComponent}
</div>

View File

@ -7,15 +7,37 @@ import { commandRegistry } from "../../../extensions/registries/command-registry
import { clusterStore } from "../../../common/cluster-store";
import { workspaceStore } from "../../../common/workspace-store";
import { closeCommandDialog } from "./command-container";
import { broadcastMessage } from "../../../common/ipc";
@observer
export class CommandDialog extends React.Component {
@observable menuIsOpen = true;
@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 };
});
}).sort((a, b) => a.label > b.label ? 1 : -1);
}
private onChange(value: string) {
@ -29,10 +51,15 @@ export class CommandDialog extends React.Component {
try {
closeCommandDialog();
action({
cluster: clusterStore.active,
workspace: workspaceStore.currentWorkspace
});
if (command.scope === "global") {
action({
cluster: clusterStore.active,
workspace: workspaceStore.currentWorkspace
});
} else if(clusterStore.active) {
broadcastMessage(`command-palette:run-action:${clusterStore.active.id}`, command.id);
}
} catch(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 { UpgradeChart } from "./upgrade-chart";
import { isUpgradeChartTab } from "./upgrade-chart.store";
import { commandRegistry } from "../../../extensions/registries/command-registry";
interface Props {
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>
<Notifications/>
<ConfirmDialog/>
<CommandContainer listenPaletteOpen={true} />
<CommandContainer />
</Router>
);
}