Improve WebCRT UX: real login transcript, session tools, and first-input sync.

Capture Netmiko session_log for login replay, open NE terminals in a fresh tab, tidy the device list UI with reconnect/log/clear/copy, and paint a live prompt after attach so the first keystrokes behave normally.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-07-29 19:53:41 +08:00
parent 30f2db9708
commit 89ff0a7212
11 changed files with 593 additions and 183 deletions

View file

@ -30,8 +30,12 @@ function moduleAckKey(moduleId: string): string {
}
export function registerModuleWindow(moduleId: string): () => void {
const sharedName = moduleWindowName(moduleId);
try {
window.name = moduleWindowName(moduleId);
// Keep unique names from openNewModuleWindow; only claim the singleton if unnamed.
if (!window.name) {
window.name = sharedName;
}
} catch {
/* ignore */
}
@ -40,9 +44,29 @@ export function registerModuleWindow(moduleId: string): () => void {
MODULE_CHANNEL,
moduleAckKey(moduleId),
(data) => {
// One-shot session windows (unique name) must not merge into the singleton module tab.
if (window.name && window.name !== sharedName) {
return;
}
const targetPath = String(data.path || "").trim();
if (targetPath && window.location.pathname !== targetPath) {
window.location.assign(targetPath);
if (targetPath) {
// Compare pathname only for hard navigation. Same route + new query
// must soft-navigate so React state (open tabs) survives.
try {
const url = new URL(targetPath, window.location.origin);
const next = `${url.pathname}${url.search}${url.hash}`;
const cur = `${window.location.pathname}${window.location.search}${window.location.hash}`;
if (window.location.pathname !== url.pathname) {
window.location.assign(next);
} else if (cur !== next) {
window.history.pushState({}, "", next);
window.dispatchEvent(new PopStateEvent("popstate"));
}
} catch {
if (window.location.pathname + window.location.search !== targetPath) {
window.location.assign(targetPath);
}
}
}
window.focus();
},
@ -74,4 +98,16 @@ export function openOrFocusModule({ moduleId, path }: ModuleWindowSpec): void {
});
}
/** Always open a fresh browser tab/window; never focus or merge with an existing module tab. */
export function openNewModuleWindow({ moduleId, path }: ModuleWindowSpec): void {
const mod = getModuleById(moduleId);
const base = mod?.path ?? "/";
const targetPath = path && (path === base || path.startsWith(`${base}?`) || path.startsWith(`${base}/`))
? path
: base;
const uniqueName = `${moduleWindowName(moduleId)}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
focusWindowSafe(window.open(targetPath, uniqueName));
}
export { moduleIdFromPath } from "../config/modules";