mirror of
https://github.com/YuzuZensai/Termix.git
synced 2026-11-12 10:02:03 +00:00
* Add Helm and GitOps deployment setup * fix: build better-sqlite3 from source in Docker (#1267) * fix: preserve runtime SSL settings (#1268) * fix: support forwarding from the memory SSH agent (#1269) * fix: support forwarding from the memory agent * style: format memory agent test * fix: prompt for encrypted SFTP key passphrases (#1270) * fix: prompt for SFTP key passphrases * style: format SSH key utility test * fix: include host context in automation notifications (#1271) * fix: include host context in automation notifications * style: format automation notification changes * fix: reserve sidebar height for host tags (#1272) * fix: keep host action rows stable at large font sizes (#1273) * fix: honor certificate setting during server probe (#1274) * fix: package standard Linux icon sizes (#1275) * fix: avoid duplicate Docker HTTPS listener (#1276) * Fix host status without metrics collection (#1277) * fix: allow eight-digit secure auth codes (#1263) Allow TOTP prompts to accept secure auth codes longer than six digits without blocking valid authentication attempts. Generated with Codebuff 🤖 Co-authored-by: Chetan <chetan.development@gmail.com> Co-authored-by: Codebuff <noreply@codebuff.com> * Harden Helm deployment defaults * Update Helm workflow action * Exclude Helm templates from Prettier * Fix browser RDP file drops (#1279) * Fix Proxmox guest credential usernames (#1280) * Add WSL local terminal option (#1281) * refactor: split the transfer engine into focused modules (#1282) * refactor: extract SFTP promisify helpers into sftp-promisify module * refactor: extract transfer timing and rate stats into transfer-stats module * refactor: extract transfer error classes and recovery checks into transfer-errors module * refactor: extract host/path utility helpers into transfer-host-utils module * refactor: extract SFTP directory tree helpers into transfer-sftp-dir module * refactor: extract segment copy job builder into transfer-segment-copy module * refactor: extract file scan and sample helpers into transfer-scan module * refactor: move throttled progress helper into transfer-stats module * style: format transfer modules * perf: optimize tmux monitor aggregation (#1283) * fix: reserve credential tag row height (#1284) * feat: edit AI provider model settings (#1285) * fix: clarify click-to-expand host setting (#1286) * fix: allow portable imports on remote databases (#1287) * fix: allow HTTPS to share the configured port (#1288) * fix: resolve synced jump hosts on the server (#1289) * fix: make terminal clipboard shortcuts layout independent (#1290) * fix: use compatible fetch dispatcher for Tailscale (#1291) * fix: add OIDC environment recovery override (#1292) * fix: coalesce rapid mobile terminal input (#1293) * fix: coalesce rapid mobile terminal input * fix: support clean xterm patch installs * fix: resolve synced remote desktop host IDs (#1295) * feat: make the SFTP file manager path bar editable (#1294) Co-authored-by: Maxime Bonillo <257463937+dropafterfree@users.noreply.github.com> Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com> * feat: add passkey sign in to the login screen * fix: remove rounded corners from the host list search bar * fix: stop image storage settings text wrapping to one word per line * fix: prevent malformed websocket messages from crashing the server * chore: increment version * fix: remove gaps between host rows in the sidebar list Keep sub-pixel row measurements and stop wiping the size cache on hover. * fix: Failed to connect through jump hosts (#1180) https://github.com/Termix-SSH/Support/issues/1180 * feat: Progress bar for file downloads in the file manager (#1158) https://github.com/Termix-SSH/Support/issues/1158 * feat: Allow setting Silent OIDC Login via ENV var (#1174) https://github.com/Termix-SSH/Support/issues/1174 * feat: `IdentityFile` to limit the number of attempts by agents (#1165) https://github.com/Termix-SSH/Support/issues/1165 * feat: Credentials clone (#1159) https://github.com/Termix-SSH/Support/issues/1159 * chore: update release notes * docs: move helm setup guide to the docs site * fix: type errors in FilteredAgent agent identity handling * fix: remove stale better-sqlite3 prebuilds so the source build is used * fix: actually build better-sqlite3 from source so arm64 docker images work * fix: credential edit pencil in host editor and add clone action to credential list * fix: clear editingHost so the credential pencil actually opens the editor * chore: run format and lint * fix: folder drag and drop upload failing in the file manager * chore: sync Crowdin translations for 2.7.1 --------- Co-authored-by: alex-ctms <alex-ctms@users.noreply.github.com> Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com> Co-authored-by: Chetan Kumar <74929596+ckloop@users.noreply.github.com> Co-authored-by: Chetan <chetan.development@gmail.com> Co-authored-by: Codebuff <noreply@codebuff.com> Co-authored-by: ZacharyZcR <payasonorahc@protonmail.com> Co-authored-by: dropafterfree <maxime.bonillo@gmail.com> Co-authored-by: Maxime Bonillo <257463937+dropafterfree@users.noreply.github.com>
226 lines
6.5 KiB
TypeScript
226 lines
6.5 KiB
TypeScript
import {
|
|
authApi,
|
|
getRemoteGuacamoleApi,
|
|
handleApiError,
|
|
isElectron,
|
|
} from "@/main-axios";
|
|
import type { AxiosInstance } from "axios";
|
|
import type { GuacamoleConfig } from "@/types/guacamole-config";
|
|
import { resolveRemoteHostId } from "@/lib/remote-server-api";
|
|
|
|
/**
|
|
* The embedded desktop backend does not bundle guacd, which is why
|
|
* resolveConnectionOrigin() pins RDP/VNC/Telnet to "remote". These calls have to
|
|
* follow: asking the embedded backend reports the guacd *it* cannot reach,
|
|
* rather than the one on the connected server that serves the session.
|
|
*/
|
|
function guacamoleApi(): AxiosInstance {
|
|
return isElectron() ? getRemoteGuacamoleApi() : authApi;
|
|
}
|
|
|
|
export interface GuacamoleTokenRequest {
|
|
protocol: "rdp" | "vnc" | "telnet";
|
|
hostname: string;
|
|
port?: number;
|
|
username?: string;
|
|
password?: string;
|
|
domain?: string;
|
|
security?: string;
|
|
ignoreCert?: boolean;
|
|
guacamoleConfig?: GuacamoleConfig;
|
|
}
|
|
|
|
export interface GuacamoleTokenResponse {
|
|
token: string;
|
|
guacamoleConnectionId?: string | null;
|
|
}
|
|
|
|
type GuacamoleConfigSource = {
|
|
guacamoleConfig?: string | Record<string, unknown> | null;
|
|
};
|
|
|
|
export function parseGuacamoleConfig(
|
|
config?: string | GuacamoleConfig | null,
|
|
): GuacamoleConfig {
|
|
if (!config) return {};
|
|
if (typeof config !== "string") return config;
|
|
try {
|
|
return JSON.parse(config) as GuacamoleConfig;
|
|
} catch {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
export function getGuacamoleDpi(
|
|
source?: GuacamoleConfigSource,
|
|
): number | undefined {
|
|
const config = source?.guacamoleConfig;
|
|
if (!config) return undefined;
|
|
|
|
let dpi: unknown;
|
|
if (typeof config === "string") {
|
|
try {
|
|
dpi = JSON.parse(config).dpi;
|
|
} catch {
|
|
return undefined;
|
|
}
|
|
} else {
|
|
dpi = config.dpi;
|
|
}
|
|
|
|
const parsedDpi = typeof dpi === "string" ? Number(dpi) : dpi;
|
|
if (
|
|
typeof parsedDpi !== "number" ||
|
|
!Number.isFinite(parsedDpi) ||
|
|
parsedDpi <= 0
|
|
) {
|
|
return undefined;
|
|
}
|
|
|
|
return Math.trunc(parsedDpi);
|
|
}
|
|
|
|
function toGuacamoleParams(
|
|
config: GuacamoleTokenRequest["guacamoleConfig"],
|
|
): Record<string, unknown> {
|
|
if (!config) return {};
|
|
|
|
const params: Record<string, unknown> = {};
|
|
|
|
const mappings: Record<string, string> = {
|
|
colorDepth: "color-depth",
|
|
resizeMethod: "resize-method",
|
|
forceLossless: "force-lossless",
|
|
disableAudio: "disable-audio",
|
|
enableAudioInput: "enable-audio-input",
|
|
enableWallpaper: "enable-wallpaper",
|
|
enableTheming: "enable-theming",
|
|
enableFontSmoothing: "enable-font-smoothing",
|
|
enableFullWindowDrag: "enable-full-window-drag",
|
|
enableDesktopComposition: "enable-desktop-composition",
|
|
enableMenuAnimations: "enable-menu-animations",
|
|
disableBitmapCaching: "disable-bitmap-caching",
|
|
disableOffscreenCaching: "disable-offscreen-caching",
|
|
disableGlyphCaching: "disable-glyph-caching",
|
|
disableGfx: "disable-gfx",
|
|
enablePrinting: "enable-printing",
|
|
printerName: "printer-name",
|
|
enableDrive: "enable-drive",
|
|
driveName: "drive-name",
|
|
drivePath: "drive-path",
|
|
createDrivePath: "create-drive-path",
|
|
disableDownload: "disable-download",
|
|
disableUpload: "disable-upload",
|
|
enableTouch: "enable-touch",
|
|
clientName: "client-name",
|
|
initialProgram: "initial-program",
|
|
serverLayout: "server-layout",
|
|
gatewayHostname: "gateway-hostname",
|
|
gatewayPort: "gateway-port",
|
|
gatewayUsername: "gateway-username",
|
|
gatewayPassword: "gateway-password",
|
|
gatewayDomain: "gateway-domain",
|
|
remoteApp: "remote-app",
|
|
remoteAppDir: "remote-app-dir",
|
|
remoteAppArgs: "remote-app-args",
|
|
normalizeClipboard: "normalize-clipboard",
|
|
disableCopy: "disable-copy",
|
|
disablePaste: "disable-paste",
|
|
swapRedBlue: "swap-red-blue",
|
|
readOnly: "read-only",
|
|
recordingPath: "recording-path",
|
|
recordingName: "recording-name",
|
|
createRecordingPath: "create-recording-path",
|
|
recordingExcludeOutput: "recording-exclude-output",
|
|
recordingExcludeMouse: "recording-exclude-mouse",
|
|
recordingIncludeKeys: "recording-include-keys",
|
|
wolSendPacket: "wol-send-packet",
|
|
wolMacAddr: "wol-mac-addr",
|
|
wolBroadcastAddr: "wol-broadcast-addr",
|
|
wolUdpPort: "wol-udp-port",
|
|
wolWaitTime: "wol-wait-time",
|
|
};
|
|
|
|
for (const [key, value] of Object.entries(config)) {
|
|
if (value !== undefined && value !== null && value !== "") {
|
|
const paramName = mappings[key] || key;
|
|
if (typeof value === "boolean") {
|
|
params[paramName] = value ? "true" : "false";
|
|
} else {
|
|
params[paramName] = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
return params;
|
|
}
|
|
|
|
export async function getGuacamoleToken(
|
|
request: GuacamoleTokenRequest,
|
|
): Promise<GuacamoleTokenResponse> {
|
|
try {
|
|
const guacParams = toGuacamoleParams(request.guacamoleConfig);
|
|
|
|
const response = await guacamoleApi().post("/guacamole/token", {
|
|
type: request.protocol,
|
|
hostname: request.hostname,
|
|
port: request.port,
|
|
username: request.username,
|
|
password: request.password,
|
|
domain: request.domain,
|
|
security: request.security,
|
|
"ignore-cert": request.ignoreCert,
|
|
...guacParams,
|
|
});
|
|
return response.data;
|
|
} catch (error) {
|
|
throw handleApiError(error, "get guacamole token");
|
|
}
|
|
}
|
|
|
|
export async function getGuacamoleTokenFromHost(
|
|
hostId: number,
|
|
protocol?: "rdp" | "vnc" | "telnet",
|
|
promptedCredentials?: {
|
|
username?: string;
|
|
password?: string;
|
|
domain?: string;
|
|
},
|
|
syncId?: string | null,
|
|
): Promise<GuacamoleTokenResponse> {
|
|
try {
|
|
const remoteHostId = isElectron()
|
|
? await resolveRemoteHostId(syncId)
|
|
: null;
|
|
if (isElectron() && syncId && remoteHostId === null) {
|
|
throw new Error("The synced host does not exist on the remote server");
|
|
}
|
|
const targetHostId = remoteHostId ?? hostId;
|
|
const response = await guacamoleApi().post(
|
|
`/guacamole/connect-host/${targetHostId}`,
|
|
{
|
|
...(protocol ? { protocol } : {}),
|
|
...(promptedCredentials?.username
|
|
? { promptedUsername: promptedCredentials.username }
|
|
: {}),
|
|
...(promptedCredentials?.password
|
|
? { promptedPassword: promptedCredentials.password }
|
|
: {}),
|
|
...(promptedCredentials
|
|
? { promptedDomain: promptedCredentials.domain ?? "" }
|
|
: {}),
|
|
},
|
|
);
|
|
return response.data;
|
|
} catch (error) {
|
|
throw handleApiError(error, "get guacamole token from host");
|
|
}
|
|
}
|
|
|
|
export async function getGuacdStatus(): Promise<{
|
|
guacd: { status: string };
|
|
}> {
|
|
const response = await guacamoleApi().get("/guacamole/status");
|
|
return response.data;
|
|
}
|