mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 06:40:45 +08:00
Improve WebCRT new-session flow for SecureCRT-like SSH/Telnet use.
Add quick-connect sessions with credential retry, raw interactive drivers, bootstrap banner replay, Huawei telnet prompt cleanup, and session rename/delete in the tree. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
accd28c9a1
commit
f6b399e03e
23 changed files with 3682 additions and 225 deletions
|
|
@ -92,6 +92,46 @@ const parseApiResponse = async (res: Response): Promise<Record<string, unknown>>
|
|||
}
|
||||
};
|
||||
|
||||
/** Preserve FastAPI structured ``detail`` objects (e.g. connect_failed + ne). */
|
||||
export class ApiRequestError extends Error {
|
||||
status: number;
|
||||
detail: unknown;
|
||||
|
||||
constructor(status: number, detail: unknown) {
|
||||
super(formatApiDetail(detail) || `${status}`);
|
||||
this.name = "ApiRequestError";
|
||||
this.status = status;
|
||||
this.detail = detail;
|
||||
}
|
||||
}
|
||||
|
||||
export function formatApiDetail(detail: unknown): string {
|
||||
if (typeof detail === "string") return detail;
|
||||
if (Array.isArray(detail)) {
|
||||
return detail
|
||||
.map((item) => {
|
||||
if (typeof item === "string") return item;
|
||||
if (item && typeof item === "object" && "msg" in item) {
|
||||
return String((item as { msg?: unknown }).msg || "");
|
||||
}
|
||||
return "";
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join("; ");
|
||||
}
|
||||
if (detail && typeof detail === "object") {
|
||||
const obj = detail as Record<string, unknown>;
|
||||
if (typeof obj.message === "string" && obj.message.trim()) return obj.message;
|
||||
if (typeof obj.error === "string" && obj.error.trim()) return obj.error;
|
||||
try {
|
||||
return JSON.stringify(detail);
|
||||
} catch {
|
||||
/* fall through */
|
||||
}
|
||||
}
|
||||
return detail == null ? "" : String(detail);
|
||||
}
|
||||
|
||||
export const apiGet = async <T,>(path: string): Promise<T> => {
|
||||
const res = await fetch(path, { headers: authHeaders() });
|
||||
if (res.status === 401) {
|
||||
|
|
@ -111,9 +151,9 @@ export const apiPost = async <T,>(path: string, body: unknown): Promise<T> => {
|
|||
const data = await parseApiResponse(res);
|
||||
if (res.status === 401) {
|
||||
handleUnauthorized(path);
|
||||
throw new Error(String(data.detail || "unauthorized"));
|
||||
throw new ApiRequestError(401, data.detail || "unauthorized");
|
||||
}
|
||||
if (!res.ok) throw new Error(String(data.detail || `${res.status} ${path}`));
|
||||
if (!res.ok) throw new ApiRequestError(res.status, data.detail || `${res.status} ${path}`);
|
||||
return data as T;
|
||||
};
|
||||
|
||||
|
|
@ -126,9 +166,9 @@ export const apiPatch = async <T,>(path: string, body: unknown): Promise<T> => {
|
|||
const data = await parseApiResponse(res);
|
||||
if (res.status === 401) {
|
||||
handleUnauthorized(path);
|
||||
throw new Error(String(data.detail || "unauthorized"));
|
||||
throw new ApiRequestError(401, data.detail || "unauthorized");
|
||||
}
|
||||
if (!res.ok) throw new Error(String(data.detail || `${res.status} ${path}`));
|
||||
if (!res.ok) throw new ApiRequestError(res.status, data.detail || `${res.status} ${path}`);
|
||||
return data as T;
|
||||
};
|
||||
|
||||
|
|
@ -137,9 +177,9 @@ export const apiDelete = async <T,>(path: string): Promise<T> => {
|
|||
const data = await parseApiResponse(res);
|
||||
if (res.status === 401) {
|
||||
handleUnauthorized(path);
|
||||
throw new Error(String(data.detail || "unauthorized"));
|
||||
throw new ApiRequestError(401, data.detail || "unauthorized");
|
||||
}
|
||||
if (!res.ok) throw new Error(String(data.detail || `${res.status} ${path}`));
|
||||
if (!res.ok) throw new ApiRequestError(res.status, data.detail || `${res.status} ${path}`);
|
||||
return data as T;
|
||||
};
|
||||
|
||||
|
|
@ -152,9 +192,9 @@ export const apiPut = async <T,>(path: string, body: unknown): Promise<T> => {
|
|||
const data = await parseApiResponse(res);
|
||||
if (res.status === 401) {
|
||||
handleUnauthorized(path);
|
||||
throw new Error(String(data.detail || "unauthorized"));
|
||||
throw new ApiRequestError(401, data.detail || "unauthorized");
|
||||
}
|
||||
if (!res.ok) throw new Error(String(data.detail || `${res.status} ${path}`));
|
||||
if (!res.ok) throw new ApiRequestError(res.status, data.detail || `${res.status} ${path}`);
|
||||
return data as T;
|
||||
};
|
||||
|
||||
|
|
@ -471,10 +511,34 @@ export const createWebcrtSession = (body: {
|
|||
cols?: number;
|
||||
rows?: number;
|
||||
encoding?: string;
|
||||
keepalive_sec?: number;
|
||||
post_login_commands?: string[];
|
||||
async_connect?: boolean;
|
||||
username?: string;
|
||||
password?: string;
|
||||
}) => apiPost<WebcrtSessionCreateResult>("/v1/webcrt/sessions", body);
|
||||
|
||||
export type WebcrtQuickConnectResult = WebcrtSessionCreateResult & {
|
||||
ne: ManagedNeItem;
|
||||
ne_action: "created" | "updated" | "reused" | string;
|
||||
list_source: "webcrt" | "managed" | string;
|
||||
};
|
||||
|
||||
export const quickConnectWebcrtSession = (body: {
|
||||
name?: string;
|
||||
ip_address: string;
|
||||
port?: number;
|
||||
protocol?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
save_password?: boolean;
|
||||
cols?: number;
|
||||
rows?: number;
|
||||
encoding?: string;
|
||||
keepalive_sec?: number;
|
||||
async_connect?: boolean;
|
||||
}) => apiPost<WebcrtQuickConnectResult>("/v1/webcrt/sessions/quick-connect", body);
|
||||
|
||||
export const closeWebcrtSession = (sessionId: string) =>
|
||||
apiDelete<{ ok: boolean; session_id: string; closed: boolean }>(
|
||||
`/v1/webcrt/sessions/${encodeURIComponent(sessionId)}`,
|
||||
|
|
@ -565,7 +629,7 @@ export const fetchCliProfiles = () =>
|
|||
apiGet<{ items: CliConnectProfileItem[] }>("/v1/cli/profiles");
|
||||
|
||||
export const fetchCliTargets = (params: {
|
||||
source?: "managed" | "ume" | "all";
|
||||
source?: "managed" | "ume" | "webcrt" | "all";
|
||||
keyword?: string;
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue