[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
This commit is contained in:
162
components/host/HostTreeContextMenus.tsx
Normal file
162
components/host/HostTreeContextMenus.tsx
Normal file
@@ -0,0 +1,162 @@
|
||||
import { Copy, FileSymlink, Files, Folder, FolderOpen, Monitor, Pencil, Plus, Server, Settings2 } from 'lucide-react';
|
||||
import React from 'react';
|
||||
|
||||
import { useI18n } from '../../application/i18n/I18nProvider';
|
||||
import { requestOpenDualPaneSftp } from '../../application/state/sftp/sftpDualPaneOpenStore';
|
||||
import { useSettingsChromeStore } from '../../application/state/settingsChromeStore';
|
||||
import { sanitizeHost } from '../../domain/host';
|
||||
import { isPluginHostProtocol } from '../../domain/pluginConnection';
|
||||
import { canOpenDualPaneSftp } from '../../domain/sftpDualPaneOpen';
|
||||
import type { Host } from '../../types';
|
||||
import { ContextMenuContent, ContextMenuItem, ContextMenuShortcut } from '../ui/context-menu';
|
||||
import { collectOwnedPluginMenus, comparePluginMenus, usePluginContributions } from '../../application/state/usePluginContributions';
|
||||
import { PluginContributionIcon } from '../plugins/PluginContributionIcon';
|
||||
|
||||
export interface HostTreeHostContextMenuHandlers {
|
||||
onConnect: (host: Host) => void;
|
||||
onEditHost?: (host: Host) => void;
|
||||
onRenameHost?: (host: Host) => void;
|
||||
onDuplicateHost: (host: Host) => void;
|
||||
onCopyHostname?: (host: Host) => void;
|
||||
onCopyCredentials: (host: Host) => void;
|
||||
onDeleteHost: (host: Host) => void;
|
||||
}
|
||||
|
||||
export const OpenDualPaneSftpMenuItem: React.FC<{ host: Host }> = ({ host }) => {
|
||||
const { t } = useI18n();
|
||||
const { showSftpTab } = useSettingsChromeStore();
|
||||
if (!showSftpTab || !canOpenDualPaneSftp(host)) return null;
|
||||
return (
|
||||
<ContextMenuItem onClick={() => requestOpenDualPaneSftp(host.id)}>
|
||||
<Files className="mr-2 h-4 w-4" /> {t('vault.hosts.openSftp')}
|
||||
</ContextMenuItem>
|
||||
);
|
||||
};
|
||||
|
||||
export const HostTreeHostContextMenuContent: React.FC<
|
||||
HostTreeHostContextMenuHandlers & { host: Host }
|
||||
> = ({
|
||||
host,
|
||||
onConnect,
|
||||
onEditHost,
|
||||
onRenameHost,
|
||||
onDuplicateHost,
|
||||
onCopyHostname,
|
||||
onCopyCredentials,
|
||||
onDeleteHost,
|
||||
}) => {
|
||||
const { t } = useI18n();
|
||||
const safeHost = sanitizeHost(host);
|
||||
const pluginContributions = usePluginContributions({
|
||||
context: {
|
||||
'netcatty.surface': 'host/context',
|
||||
'host.id': safeHost.id,
|
||||
'host.protocol': safeHost.protocol ?? 'ssh',
|
||||
},
|
||||
});
|
||||
const pluginMenus = collectOwnedPluginMenus(pluginContributions.snapshot.plugins)
|
||||
.filter((menu) => menu.location === 'host/context' && menu.visible)
|
||||
.sort(comparePluginMenus);
|
||||
const canCopyHostname = Boolean(onCopyHostname) && !isPluginHostProtocol(safeHost.protocol);
|
||||
|
||||
return (
|
||||
<ContextMenuContent>
|
||||
<ContextMenuItem onClick={() => onConnect(safeHost)}>
|
||||
<Monitor className="mr-2 h-4 w-4" /> {t('vault.hosts.connect')}
|
||||
</ContextMenuItem>
|
||||
<OpenDualPaneSftpMenuItem host={safeHost} />
|
||||
{onEditHost && (
|
||||
<ContextMenuItem onClick={() => onEditHost(host)}>
|
||||
<Settings2 className="mr-2 h-4 w-4" /> {t('terminal.layer.hostTree.editHost')}
|
||||
</ContextMenuItem>
|
||||
)}
|
||||
{onRenameHost && (
|
||||
<ContextMenuItem onClick={() => onRenameHost(host)}>
|
||||
<Pencil className="mr-2 h-4 w-4" /> {t('common.rename')}
|
||||
</ContextMenuItem>
|
||||
)}
|
||||
<ContextMenuItem onClick={() => onDuplicateHost(host)}>
|
||||
<Copy className="mr-2 h-4 w-4" /> {t('action.duplicate')}
|
||||
</ContextMenuItem>
|
||||
{canCopyHostname ? (
|
||||
<ContextMenuItem onClick={() => onCopyHostname?.(host)}>
|
||||
<Copy className="mr-2 h-4 w-4" /> {t('terminal.statusbar.copyHostname.label')}
|
||||
</ContextMenuItem>
|
||||
) : null}
|
||||
<ContextMenuItem onClick={() => onCopyCredentials(host)}>
|
||||
<Server className="mr-2 h-4 w-4" /> {t('vault.hosts.copyCredentials')}
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem
|
||||
onClick={() => onDeleteHost(host)}
|
||||
className="text-destructive focus:text-destructive"
|
||||
>
|
||||
<Server className="mr-2 h-4 w-4" /> {t('action.delete')}
|
||||
</ContextMenuItem>
|
||||
{pluginMenus.map((menu) => (
|
||||
<ContextMenuItem
|
||||
key={menu.id}
|
||||
disabled={!menu.enabled}
|
||||
onClick={(event) => void pluginContributions.executeCommand(event.altKey && menu.alt ? menu.alt : menu.command, { hostId: safeHost.id }, {
|
||||
'netcatty.surface': 'host/context',
|
||||
'host.id': safeHost.id,
|
||||
'host.protocol': safeHost.protocol ?? 'ssh',
|
||||
}).catch(() => {})}
|
||||
>
|
||||
<PluginContributionIcon pluginId={menu.pluginId} icon={menu.icon} className="mr-2" />
|
||||
{menu.title}
|
||||
{menu.checked && <span className="ml-auto pl-4" aria-hidden="true">✓</span>}
|
||||
{menu.shortcut && <ContextMenuShortcut>{menu.shortcut}</ContextMenuShortcut>}
|
||||
</ContextMenuItem>
|
||||
))}
|
||||
</ContextMenuContent>
|
||||
);
|
||||
};
|
||||
|
||||
export interface HostTreeGroupContextMenuHandlers {
|
||||
onNewHost?: (groupPath: string) => void;
|
||||
onNewGroup: (parentPath?: string) => void;
|
||||
onRenameGroup: (groupPath: string) => void;
|
||||
onDeleteGroup: (groupPath: string) => void;
|
||||
onUnmanageGroup?: (groupPath: string) => void;
|
||||
}
|
||||
|
||||
export const HostTreeGroupContextMenuContent: React.FC<
|
||||
HostTreeGroupContextMenuHandlers & { groupPath: string; isManaged: boolean }
|
||||
> = ({
|
||||
groupPath,
|
||||
isManaged,
|
||||
onNewHost,
|
||||
onNewGroup,
|
||||
onRenameGroup,
|
||||
onDeleteGroup,
|
||||
onUnmanageGroup,
|
||||
}) => {
|
||||
const { t } = useI18n();
|
||||
|
||||
return (
|
||||
<ContextMenuContent>
|
||||
{onNewHost && (
|
||||
<ContextMenuItem onClick={() => onNewHost(groupPath)}>
|
||||
<Plus className="mr-2 h-4 w-4" /> {t('terminal.layer.hostTree.newHostInGroup')}
|
||||
</ContextMenuItem>
|
||||
)}
|
||||
<ContextMenuItem onClick={() => onNewGroup(groupPath)}>
|
||||
<Folder className="mr-2 h-4 w-4" /> {t('vault.hosts.newGroup')}
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem onClick={() => onRenameGroup(groupPath)}>
|
||||
<FolderOpen className="mr-2 h-4 w-4" /> {t('vault.groups.rename')}
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem
|
||||
onClick={() => onDeleteGroup(groupPath)}
|
||||
className="text-destructive focus:text-destructive"
|
||||
>
|
||||
<FolderOpen className="mr-2 h-4 w-4" /> {t('vault.groups.delete')}
|
||||
</ContextMenuItem>
|
||||
{isManaged && onUnmanageGroup && (
|
||||
<ContextMenuItem onClick={() => onUnmanageGroup(groupPath)}>
|
||||
<FileSymlink className="mr-2 h-4 w-4" /> {t('vault.managedSource.unmanage')}
|
||||
</ContextMenuItem>
|
||||
)}
|
||||
</ContextMenuContent>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user