feat(M3): SSH productization — Terminal/SSH split + Wave-style server monitoring
- Sidebar split into 本地终端 / SSH 服务器 sections + recent sessions quick reconnect - SSH tabs carry server badge (icon + green edge); local tabs unchanged - SSH session panels mount a collapsible live monitor panel (CPU/mem/disk/net/load/uptime) - src/main/sysinfo.ts polls remote via ssh2 exec (/proc + df), 3s interval, error-tolerant (3 consecutive failures auto-stop), auto-stops on session close - tests: sysinfo-e2e (loopback exec with canned /proc output) — ALL PASSED
This commit is contained in:
1 parent
f97edbb016
commit
06fa4ca023
19 files changed
+1632
-30
No files matched your search
+19
-1
@@ -6,6 +6,11 @@ import type { SessionOpenOptions, HostKeyPromptEvent, SshConnection } from '../s
|
||||
import { broadcast } from './broadcast'
|
||||
import { connectSsh, type SshSessionHandle } from './ssh'
|
||||
import type { HostKeyCheckResult } from './knownHosts'
|
||||
import { configureSysinfo, registerSysinfoClient, stopPolling } from './sysinfo'
|
||||
|
||||
// Re-export so the session-layer loopback bundle (tests/*-e2e.mjs) can drive the
|
||||
// M3 polling engine without importing src/main/sysinfo.ts separately.
|
||||
export { startPolling, stopPolling } from './sysinfo'
|
||||
|
||||
/**
|
||||
* Session routing table. A session is either a local pty or an established ssh
|
||||
@@ -64,6 +69,14 @@ let runtimeDeps: SessionRuntimeDeps | undefined
|
||||
/** Wire the real stores + renderer prompt. Called once during app startup. */
|
||||
export function configureSessionRuntime(deps: SessionRuntimeDeps): void {
|
||||
runtimeDeps = deps
|
||||
configureSysinfo({
|
||||
broadcast: (channel, ...args) => deps.broadcast(channel, ...args)
|
||||
})
|
||||
registerSysinfoClient((id) => {
|
||||
const session = sessions.get(id)
|
||||
if (session?.kind === 'ssh') return session.ssh.client
|
||||
return undefined
|
||||
})
|
||||
}
|
||||
|
||||
function defaultShell(): string {
|
||||
@@ -156,6 +169,7 @@ export async function openSession(opts: SessionOpenOptions): Promise<{ id: strin
|
||||
|
||||
handle.stream.on('close', () => {
|
||||
try {
|
||||
stopPolling(handle.id)
|
||||
sessions.delete(handle.id)
|
||||
deps.broadcast(Ipc.PTY_EXIT, { id: handle.id, exitCode: 0 })
|
||||
} catch {
|
||||
@@ -190,6 +204,7 @@ export function resizePty(id: string, cols: number, rows: number): void {
|
||||
}
|
||||
|
||||
export function killPty(id: string): void {
|
||||
stopPolling(id)
|
||||
replayBuffers.delete(id)
|
||||
const session = sessions.get(id)
|
||||
if (!session) return
|
||||
@@ -215,7 +230,10 @@ export function killPty(id: string): void {
|
||||
}
|
||||
|
||||
export function killAllPtys(): void {
|
||||
for (const session of sessions.values()) {
|
||||
for (const id of sessions.keys()) {
|
||||
stopPolling(id)
|
||||
const session = sessions.get(id)
|
||||
if (!session) continue
|
||||
if (session.kind === 'ssh') {
|
||||
try {
|
||||
session.ssh.stream.close()
|
||||
|
||||
Reference in new issue
Block a user