feat: support URL routes to open terminal directly (#156)

- Add /terminal/{hostNameOrId} route for new format
- Keep /hosts/{id}/terminal for backward compatibility
- Smart detection: numeric IDs for ID lookup, otherwise name lookup
- Clean URL after opening to prevent duplicate on refresh
- Show toast error when host not found
This commit is contained in:
ZacharyZcR
2026-01-13 08:15:12 +08:00
parent 3922a82dfc
commit 74bcd1f2bd

View File

@@ -13,6 +13,7 @@ import { AdminSettings } from "@/ui/desktop/apps/admin/AdminSettings.tsx";
import { UserProfile } from "@/ui/desktop/user/UserProfile.tsx"; import { UserProfile } from "@/ui/desktop/user/UserProfile.tsx";
import { NetworkGraphView } from "@/ui/desktop/dashboard/network-graph"; import { NetworkGraphView } from "@/ui/desktop/dashboard/network-graph";
import { Toaster } from "@/components/ui/sonner.tsx"; import { Toaster } from "@/components/ui/sonner.tsx";
import { toast } from "sonner";
import { CommandPalette } from "@/ui/desktop/apps/command-palette/CommandPalette.tsx"; import { CommandPalette } from "@/ui/desktop/apps/command-palette/CommandPalette.tsx";
import { getUserInfo, logoutUser, isElectron } from "@/ui/main-axios.ts"; import { getUserInfo, logoutUser, isElectron } from "@/ui/main-axios.ts";
import { useTheme } from "@/components/theme-provider"; import { useTheme } from "@/components/theme-provider";
@@ -89,30 +90,44 @@ function AppContent() {
useEffect(() => { useEffect(() => {
const path = window.location.pathname; const path = window.location.pathname;
const match = path.match(/^\/hosts\/([a-zA-Z0-9_-]+)\/terminal$/); // New format: /terminal/{hostNameOrId}
if (match) { const terminalMatch = path.match(/^\/terminal\/([a-zA-Z0-9_-]+)$/);
const hostId = match[1]; // Legacy format: /hosts/{id}/terminal (backward compatible)
const legacyMatch = path.match(/^\/hosts\/([a-zA-Z0-9_-]+)\/terminal$/);
const openTerminalForHost = async () => { const hostIdentifier = terminalMatch?.[1] || legacyMatch?.[1];
if (hostIdentifier) {
const openTerminal = async () => {
try { try {
const { getSSHHostById } = await import("@/ui/main-axios.ts"); const { getSSHHostById, getSSHHosts } = await import("@/ui/main-axios.ts");
const host = await getSSHHostById(parseInt(hostId, 10)); let host = null;
// Pure numeric → lookup by ID
if (/^\d+$/.test(hostIdentifier)) {
host = await getSSHHostById(parseInt(hostIdentifier, 10));
} else {
// Non-numeric → lookup by name (first match)
const hosts = await getSSHHosts();
host = hosts.find((h: { name?: string }) => h.name === hostIdentifier) || null;
}
if (host) { if (host) {
addTab({ addTab({
type: "terminal", type: "terminal",
title: host.name || host.ip, title: host.name || host.ip,
data: { data: { host, initialCommand: "" },
host,
initialCommand: "",
},
}); });
// Clean URL to prevent re-opening on refresh
window.history.replaceState({}, "", "/");
} else {
toast.error(`Host "${hostIdentifier}" not found`);
} }
} catch (error) { } catch (error) {
console.error("Failed to open terminal for host:", error); console.error("Failed to open terminal:", error);
toast.error("Failed to open terminal for host");
} }
}; };
openTerminal();
openTerminalForHost();
} }
}, [addTab]); }, [addTab]);