[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:
207
application/app/AppHostEditorLayer.tsx
Normal file
207
application/app/AppHostEditorLayer.tsx
Normal file
@@ -0,0 +1,207 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
|
||||
import type { WorkSurfaceHostEditorTarget } from '../state/useWorkSurfaceHostEditor';
|
||||
import type { EditorTabChrome } from '../state/editorTabStore';
|
||||
import type { LogView } from '../state/logViewState';
|
||||
import { useI18n } from '../i18n/I18nProvider';
|
||||
import HostDetailsPanel from '../../components/HostDetailsPanel';
|
||||
import SerialHostDetailsPanel from '../../components/SerialHostDetailsPanel';
|
||||
import { PortalContainerProvider } from '../../components/ui/portal-container';
|
||||
import { resolveGroupDefaults } from '../../domain/groupConfig';
|
||||
import { STORAGE_KEY_VAULT_HOST_PANEL_WIDTH } from '@/infrastructure/config/storageKeys';
|
||||
import type {
|
||||
GroupConfig,
|
||||
Host,
|
||||
Identity,
|
||||
ManagedSource,
|
||||
ProxyProfile,
|
||||
Snippet,
|
||||
SSHKey,
|
||||
TerminalSession,
|
||||
Workspace,
|
||||
} from '../../types';
|
||||
import { useWorkSurfaceVisible } from './AppHostEditorSurface';
|
||||
|
||||
export type WorkSurfaceHostEditorKind = 'standard' | 'serial';
|
||||
|
||||
export function resolveWorkSurfaceHostEditorKind(
|
||||
target: WorkSurfaceHostEditorTarget,
|
||||
): WorkSurfaceHostEditorKind {
|
||||
return target.mode === 'edit' && target.openedHost.protocol === 'serial'
|
||||
? 'serial'
|
||||
: 'standard';
|
||||
}
|
||||
|
||||
function addGroupAndAncestors(groups: Set<string>, path: string | null | undefined) {
|
||||
const segments = path?.split('/').filter(Boolean) ?? [];
|
||||
for (let index = 1; index <= segments.length; index += 1) {
|
||||
groups.add(segments.slice(0, index).join('/'));
|
||||
}
|
||||
}
|
||||
|
||||
export function collectWorkSurfaceHostGroups(
|
||||
hosts: Host[],
|
||||
customGroups: string[],
|
||||
groupConfigs: GroupConfig[],
|
||||
): string[] {
|
||||
const groups = new Set<string>();
|
||||
for (const path of customGroups) addGroupAndAncestors(groups, path);
|
||||
for (const config of groupConfigs) addGroupAndAncestors(groups, config.path);
|
||||
for (const host of hosts) addGroupAndAncestors(groups, host.group);
|
||||
return Array.from(groups).sort((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
export function collectWorkSurfaceHostTags(hosts: Host[]): string[] {
|
||||
const tags = new Set<string>();
|
||||
for (const host of hosts) {
|
||||
for (const tag of host.tags ?? []) tags.add(tag);
|
||||
}
|
||||
return Array.from(tags).sort((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
export function getAppHostEditorLayerStyle(surfaceVisible: boolean): React.CSSProperties {
|
||||
return {
|
||||
display: surfaceVisible ? undefined : 'none',
|
||||
pointerEvents: surfaceVisible ? undefined : 'none',
|
||||
};
|
||||
}
|
||||
|
||||
interface AppHostEditorLayerProps {
|
||||
/** When omitted, surface visibility is derived from activeTabId in this leaf. */
|
||||
surfaceVisible?: boolean;
|
||||
target: WorkSurfaceHostEditorTarget | null;
|
||||
editorKey: string | null;
|
||||
hosts: Host[];
|
||||
customGroups: string[];
|
||||
groupConfigs: GroupConfig[];
|
||||
keys: SSHKey[];
|
||||
identities: Identity[];
|
||||
proxyProfiles: ProxyProfile[];
|
||||
managedSources: ManagedSource[];
|
||||
snippets: Snippet[];
|
||||
terminalThemeId: string;
|
||||
terminalFontSize: number;
|
||||
/** Required when surfaceVisible is not passed (leaf active-tab subscription). */
|
||||
sessions?: TerminalSession[];
|
||||
workspaces?: Workspace[];
|
||||
logViews?: readonly LogView[];
|
||||
orderedTabs?: readonly string[];
|
||||
editorTabs?: readonly EditorTabChrome[];
|
||||
onSave: (host: Host) => void;
|
||||
onCancel: () => void;
|
||||
onCreateGroup: (groupPath: string) => void;
|
||||
onImportOrReuseKey: (draft: Partial<SSHKey>) => SSHKey;
|
||||
onUpdateSnippets: (snippets: Snippet[]) => void;
|
||||
onUpdateHosts?: (hosts: Host[] | ((prev: Host[]) => Host[])) => void;
|
||||
}
|
||||
|
||||
export const AppHostEditorLayer: React.FC<AppHostEditorLayerProps> = ({
|
||||
surfaceVisible: surfaceVisibleProp,
|
||||
target,
|
||||
editorKey,
|
||||
hosts,
|
||||
customGroups,
|
||||
groupConfigs,
|
||||
keys,
|
||||
identities,
|
||||
proxyProfiles,
|
||||
managedSources,
|
||||
snippets,
|
||||
terminalThemeId,
|
||||
terminalFontSize,
|
||||
sessions = [],
|
||||
workspaces = [],
|
||||
logViews = [],
|
||||
orderedTabs = [],
|
||||
onSave,
|
||||
onCancel,
|
||||
onCreateGroup,
|
||||
onImportOrReuseKey,
|
||||
onUpdateSnippets,
|
||||
onUpdateHosts,
|
||||
}) => {
|
||||
const { t } = useI18n();
|
||||
const derivedSurfaceVisible = useWorkSurfaceVisible({
|
||||
enabled: true,
|
||||
sessions,
|
||||
workspaces,
|
||||
logViews,
|
||||
orderedTabs,
|
||||
});
|
||||
// Prefer explicit prop only when provided, so existing tests keep control.
|
||||
const surfaceVisible = surfaceVisibleProp ?? derivedSurfaceVisible;
|
||||
const [portalContainer, setPortalContainer] = useState<HTMLDivElement | null>(null);
|
||||
const groups = useMemo(
|
||||
() => collectWorkSurfaceHostGroups(hosts, customGroups, groupConfigs),
|
||||
[customGroups, groupConfigs, hosts],
|
||||
);
|
||||
const allTags = useMemo(() => collectWorkSurfaceHostTags(hosts), [hosts]);
|
||||
const groupPath = target?.mode === 'edit'
|
||||
? target.openedHost.group
|
||||
: target?.defaultGroup;
|
||||
const groupDefaults = useMemo(
|
||||
() => (groupPath ? resolveGroupDefaults(groupPath, groupConfigs) : undefined),
|
||||
[groupConfigs, groupPath],
|
||||
);
|
||||
// Share width persistence with Vault host details so both entry points feel consistent.
|
||||
const hostPanelResizeProps = {
|
||||
resizable: true as const,
|
||||
persistWidthStorageKey: STORAGE_KEY_VAULT_HOST_PANEL_WIDTH,
|
||||
resizeAriaLabel: t('vault.panel.resizeWidth'),
|
||||
};
|
||||
|
||||
if (!target || !editorKey) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={setPortalContainer}
|
||||
className="pointer-events-none absolute inset-0 z-40 [&>*]:pointer-events-auto"
|
||||
data-section="app-host-editor-layer"
|
||||
style={getAppHostEditorLayerStyle(surfaceVisible)}
|
||||
>
|
||||
<PortalContainerProvider container={portalContainer}>
|
||||
{target.mode === 'edit' && target.openedHost.protocol === 'serial' ? (
|
||||
<SerialHostDetailsPanel
|
||||
key={editorKey}
|
||||
initialData={target.openedHost}
|
||||
allTags={allTags}
|
||||
groups={groups}
|
||||
groupDefaults={groupDefaults}
|
||||
onSave={onSave}
|
||||
onCancel={onCancel}
|
||||
layout="overlay"
|
||||
className="pointer-events-auto"
|
||||
{...hostPanelResizeProps}
|
||||
/>
|
||||
) : (
|
||||
<HostDetailsPanel
|
||||
key={editorKey}
|
||||
initialData={target.mode === 'edit' ? target.openedHost : null}
|
||||
availableKeys={keys}
|
||||
identities={identities}
|
||||
proxyProfiles={proxyProfiles}
|
||||
groups={groups}
|
||||
managedSources={managedSources}
|
||||
allTags={allTags}
|
||||
allHosts={hosts}
|
||||
defaultGroup={target.mode === 'new' ? target.defaultGroup : undefined}
|
||||
terminalThemeId={terminalThemeId}
|
||||
terminalFontSize={terminalFontSize}
|
||||
groupDefaults={groupDefaults}
|
||||
groupConfigs={groupConfigs}
|
||||
snippets={snippets}
|
||||
onSnippetsChange={onUpdateSnippets}
|
||||
onHostsChange={onUpdateHosts}
|
||||
onImportKey={onImportOrReuseKey}
|
||||
onSave={onSave}
|
||||
onCancel={onCancel}
|
||||
onCreateGroup={onCreateGroup}
|
||||
layout="overlay"
|
||||
className="pointer-events-auto"
|
||||
{...hostPanelResizeProps}
|
||||
/>
|
||||
)}
|
||||
</PortalContainerProvider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user