2026-08-06 14:41:39 -05:00
|
|
|
import type { AuthOverrideProtocol } from "../../../types/auth-protocols.js";
|
|
|
|
|
|
2026-06-04 15:16:53 -04:00
|
|
|
export function isNonEmptyString(value: unknown): value is string {
|
|
|
|
|
return typeof value === "string" && value.trim().length > 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function isValidPort(port: unknown): port is number {
|
|
|
|
|
return typeof port === "number" && port > 0 && port <= 65535;
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-06 14:41:39 -05:00
|
|
|
export function isOptionalBoolean(
|
|
|
|
|
value: unknown,
|
|
|
|
|
): value is boolean | undefined {
|
|
|
|
|
return value === undefined || typeof value === "boolean";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const OWNER_PRIVATE_AUTH_FIELDS = {
|
|
|
|
|
ssh: [
|
|
|
|
|
"authType",
|
|
|
|
|
"authMethod",
|
|
|
|
|
"credentialId",
|
|
|
|
|
"vaultProfileId",
|
|
|
|
|
"overrideCredentialUsername",
|
|
|
|
|
"shareSshAuth",
|
|
|
|
|
"password",
|
|
|
|
|
"key",
|
|
|
|
|
"keyPassword",
|
|
|
|
|
"keyType",
|
|
|
|
|
"sudoPassword",
|
|
|
|
|
],
|
|
|
|
|
rdp: [
|
|
|
|
|
"rdpAuthType",
|
|
|
|
|
"rdpCredentialId",
|
|
|
|
|
"rdpUser",
|
|
|
|
|
"rdpPassword",
|
|
|
|
|
"rdpDomain",
|
|
|
|
|
],
|
|
|
|
|
vnc: ["vncAuthType", "vncCredentialId", "vncUser", "vncPassword"],
|
|
|
|
|
telnet: [
|
|
|
|
|
"telnetAuthType",
|
|
|
|
|
"telnetCredentialId",
|
|
|
|
|
"telnetUser",
|
|
|
|
|
"telnetPassword",
|
|
|
|
|
],
|
|
|
|
|
} as const satisfies Record<AuthOverrideProtocol, readonly string[]>;
|
|
|
|
|
|
|
|
|
|
export const OWNER_PRIVATE_TERMINAL_CONFIG_FIELDS = [
|
|
|
|
|
"sudoPassword",
|
|
|
|
|
"agentSocketPath",
|
|
|
|
|
] as const;
|
|
|
|
|
|
|
|
|
|
export function containsOwnerPrivateAuthUpdate(
|
|
|
|
|
hostData: Record<string, unknown>,
|
|
|
|
|
protocol: AuthOverrideProtocol,
|
|
|
|
|
): boolean {
|
|
|
|
|
return OWNER_PRIVATE_AUTH_FIELDS[protocol].some((field) =>
|
|
|
|
|
Object.prototype.hasOwnProperty.call(hostData, field),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-16 15:59:53 -05:00
|
|
|
export const FOLDER_PATH_SEPARATOR = " / ";
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Re-paths a folder string when its ancestor folder is renamed. Returns the new
|
|
|
|
|
* path for an exact match or any nested child, or null when the path is unrelated.
|
|
|
|
|
* Mirrors the SQL CASE expression used in the folder rename route.
|
|
|
|
|
*/
|
|
|
|
|
export function renameFolderPath(
|
|
|
|
|
folderPath: string,
|
|
|
|
|
oldName: string,
|
|
|
|
|
newName: string,
|
|
|
|
|
): string | null {
|
|
|
|
|
if (folderPath === oldName) return newName;
|
|
|
|
|
const prefix = `${oldName}${FOLDER_PATH_SEPARATOR}`;
|
|
|
|
|
if (folderPath.startsWith(prefix)) {
|
|
|
|
|
return `${newName}${FOLDER_PATH_SEPARATOR}${folderPath.slice(prefix.length)}`;
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-04 15:16:53 -04:00
|
|
|
function asString(value: unknown): string | undefined {
|
|
|
|
|
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function asPort(value: unknown): number | undefined {
|
|
|
|
|
const port =
|
|
|
|
|
typeof value === "number"
|
|
|
|
|
? value
|
|
|
|
|
: typeof value === "string"
|
|
|
|
|
? Number.parseInt(value, 10)
|
|
|
|
|
: NaN;
|
|
|
|
|
|
|
|
|
|
return isValidPort(port) ? port : undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function asInteger(value: unknown): number | undefined {
|
|
|
|
|
const number =
|
|
|
|
|
typeof value === "number"
|
|
|
|
|
? value
|
|
|
|
|
: typeof value === "string"
|
|
|
|
|
? Number.parseInt(value, 10)
|
|
|
|
|
: NaN;
|
|
|
|
|
|
|
|
|
|
return Number.isInteger(number) ? number : undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function asBoolean(value: unknown, fallback = false): boolean {
|
|
|
|
|
if (typeof value === "boolean") return value;
|
|
|
|
|
if (typeof value === "number") return value !== 0;
|
|
|
|
|
if (typeof value === "string") {
|
|
|
|
|
const normalized = value.trim().toLowerCase();
|
|
|
|
|
if (["true", "1", "yes", "on"].includes(normalized)) return true;
|
|
|
|
|
if (["false", "0", "no", "off"].includes(normalized)) return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return fallback;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function normalizeImportTags(value: unknown): string[] {
|
|
|
|
|
if (Array.isArray(value)) {
|
|
|
|
|
return value
|
|
|
|
|
.map((tag) => asString(tag))
|
|
|
|
|
.filter((tag): tag is string => !!tag);
|
|
|
|
|
}
|
|
|
|
|
if (typeof value === "string") {
|
|
|
|
|
return value
|
|
|
|
|
.split(",")
|
|
|
|
|
.map((tag) => tag.trim())
|
|
|
|
|
.filter(Boolean);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type NormalizedImportedHost = Record<string, unknown> & {
|
|
|
|
|
connectionType: string;
|
|
|
|
|
name?: string;
|
|
|
|
|
ip?: string;
|
|
|
|
|
port: number;
|
|
|
|
|
username?: string;
|
|
|
|
|
folder?: string;
|
|
|
|
|
tags: string[];
|
|
|
|
|
authType?: string;
|
|
|
|
|
password?: string;
|
|
|
|
|
key?: string;
|
|
|
|
|
keyPassword?: string;
|
|
|
|
|
keyType?: string;
|
|
|
|
|
credentialId?: number;
|
2026-06-29 13:28:26 -05:00
|
|
|
credentialAlias?: string;
|
2026-06-04 15:16:53 -04:00
|
|
|
pin?: unknown;
|
|
|
|
|
enableTerminal?: unknown;
|
|
|
|
|
enableTunnel?: unknown;
|
|
|
|
|
enableFileManager?: unknown;
|
|
|
|
|
enableDocker?: unknown;
|
2026-06-16 15:59:53 -05:00
|
|
|
enableProxmox?: unknown;
|
|
|
|
|
enableTmuxMonitor?: unknown;
|
2026-06-04 15:16:53 -04:00
|
|
|
showTerminalInSidebar?: unknown;
|
|
|
|
|
showFileManagerInSidebar?: unknown;
|
|
|
|
|
showTunnelInSidebar?: unknown;
|
|
|
|
|
showDockerInSidebar?: unknown;
|
|
|
|
|
showServerStatsInSidebar?: unknown;
|
|
|
|
|
defaultPath?: unknown;
|
|
|
|
|
sudoPassword?: unknown;
|
|
|
|
|
tunnelConnections?: unknown;
|
|
|
|
|
jumpHosts?: unknown;
|
|
|
|
|
quickActions?: unknown;
|
|
|
|
|
statsConfig?: unknown;
|
|
|
|
|
dockerConfig?: unknown;
|
2026-06-16 15:59:53 -05:00
|
|
|
proxmoxConfig?: unknown;
|
2026-06-04 15:16:53 -04:00
|
|
|
terminalConfig?: unknown;
|
|
|
|
|
forceKeyboardInteractive?: unknown;
|
|
|
|
|
notes?: unknown;
|
|
|
|
|
useSocks5?: unknown;
|
|
|
|
|
socks5Host?: unknown;
|
|
|
|
|
socks5Port?: unknown;
|
|
|
|
|
socks5Username?: unknown;
|
|
|
|
|
socks5Password?: unknown;
|
|
|
|
|
socks5ProxyChain?: unknown;
|
|
|
|
|
portKnockSequence?: unknown;
|
|
|
|
|
overrideCredentialUsername?: unknown;
|
|
|
|
|
domain?: unknown;
|
|
|
|
|
security?: unknown;
|
|
|
|
|
ignoreCert?: unknown;
|
|
|
|
|
guacamoleConfig?: unknown;
|
|
|
|
|
enableSsh: boolean;
|
|
|
|
|
enableRdp: boolean;
|
|
|
|
|
enableVnc: boolean;
|
|
|
|
|
enableTelnet: boolean;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export function normalizeImportedHost(
|
|
|
|
|
hostData: Record<string, unknown>,
|
|
|
|
|
): NormalizedImportedHost {
|
2026-06-29 13:28:26 -05:00
|
|
|
const credentialAlias =
|
|
|
|
|
asString(hostData.credentialAlias) || asString(hostData.credentialName);
|
2026-06-04 15:16:53 -04:00
|
|
|
const connectionType =
|
|
|
|
|
asString(hostData.connectionType) ||
|
|
|
|
|
(asBoolean(hostData.enableRdp)
|
|
|
|
|
? "rdp"
|
|
|
|
|
: asBoolean(hostData.enableVnc)
|
|
|
|
|
? "vnc"
|
|
|
|
|
: asBoolean(hostData.enableTelnet)
|
|
|
|
|
? "telnet"
|
|
|
|
|
: "ssh");
|
|
|
|
|
|
|
|
|
|
const port =
|
|
|
|
|
asPort(hostData.port) ||
|
|
|
|
|
(connectionType === "rdp"
|
|
|
|
|
? asPort(hostData.rdpPort) || 3389
|
|
|
|
|
: connectionType === "vnc"
|
|
|
|
|
? asPort(hostData.vncPort) || 5900
|
|
|
|
|
: connectionType === "telnet"
|
|
|
|
|
? asPort(hostData.telnetPort) || 23
|
|
|
|
|
: asPort(hostData.sshPort) || 22);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...hostData,
|
|
|
|
|
connectionType,
|
|
|
|
|
name: asString(hostData.name) || asString(hostData.label),
|
|
|
|
|
ip:
|
|
|
|
|
asString(hostData.ip) ||
|
|
|
|
|
asString(hostData.address) ||
|
|
|
|
|
asString(hostData.host) ||
|
|
|
|
|
asString(hostData.hostname),
|
|
|
|
|
port,
|
|
|
|
|
username: asString(hostData.username) || asString(hostData.user),
|
|
|
|
|
folder: asString(hostData.folder) || asString(hostData.group),
|
|
|
|
|
tags: normalizeImportTags(hostData.tags),
|
|
|
|
|
credentialId: asInteger(hostData.credentialId),
|
2026-06-29 13:28:26 -05:00
|
|
|
credentialAlias,
|
2026-06-04 15:16:53 -04:00
|
|
|
authType:
|
|
|
|
|
asString(hostData.authType) ||
|
|
|
|
|
asString(hostData.authMethod) ||
|
2026-06-29 13:28:26 -05:00
|
|
|
(hostData.credentialId || credentialAlias
|
|
|
|
|
? "credential"
|
|
|
|
|
: hostData.key
|
|
|
|
|
? "key"
|
|
|
|
|
: undefined),
|
2026-06-04 15:16:53 -04:00
|
|
|
enableSsh:
|
|
|
|
|
hostData.enableSsh === undefined
|
|
|
|
|
? connectionType === "ssh"
|
|
|
|
|
: asBoolean(hostData.enableSsh),
|
|
|
|
|
enableRdp:
|
|
|
|
|
hostData.enableRdp === undefined
|
|
|
|
|
? connectionType === "rdp"
|
|
|
|
|
: asBoolean(hostData.enableRdp),
|
|
|
|
|
enableVnc:
|
|
|
|
|
hostData.enableVnc === undefined
|
|
|
|
|
? connectionType === "vnc"
|
|
|
|
|
: asBoolean(hostData.enableVnc),
|
|
|
|
|
enableTelnet:
|
|
|
|
|
hostData.enableTelnet === undefined
|
|
|
|
|
? connectionType === "telnet"
|
|
|
|
|
: asBoolean(hostData.enableTelnet),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const SENSITIVE_FIELDS = [
|
|
|
|
|
"key",
|
|
|
|
|
"keyPassword",
|
|
|
|
|
"autostartKey",
|
|
|
|
|
"autostartKeyPassword",
|
|
|
|
|
"password",
|
|
|
|
|
"sudoPassword",
|
|
|
|
|
"socks5Password",
|
|
|
|
|
"rdpPassword",
|
|
|
|
|
"vncPassword",
|
|
|
|
|
"telnetPassword",
|
|
|
|
|
"autostartPassword",
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
export function stripSensitiveFields(
|
|
|
|
|
host: Record<string, unknown>,
|
|
|
|
|
): Record<string, unknown> {
|
|
|
|
|
const result = { ...host };
|
|
|
|
|
result.hasKey = !!host.key;
|
|
|
|
|
result.hasKeyPassword = !!host.keyPassword;
|
|
|
|
|
result.hasPassword = !!host.password;
|
|
|
|
|
result.hasSudoPassword = !!host.sudoPassword;
|
2026-07-26 18:47:27 -05:00
|
|
|
result.hasRdpPassword = !!host.rdpPassword;
|
|
|
|
|
result.hasVncPassword = !!host.vncPassword;
|
|
|
|
|
result.hasTelnetPassword = !!host.telnetPassword;
|
2026-06-04 15:16:53 -04:00
|
|
|
for (const field of SENSITIVE_FIELDS) {
|
|
|
|
|
delete result[field];
|
|
|
|
|
}
|
2026-08-06 14:41:39 -05:00
|
|
|
if (
|
|
|
|
|
result.terminalConfig &&
|
|
|
|
|
typeof result.terminalConfig === "object" &&
|
|
|
|
|
!Array.isArray(result.terminalConfig)
|
|
|
|
|
) {
|
|
|
|
|
const terminalConfig = {
|
|
|
|
|
...(result.terminalConfig as Record<string, unknown>),
|
|
|
|
|
};
|
|
|
|
|
delete terminalConfig.sudoPassword;
|
|
|
|
|
result.terminalConfig = terminalConfig;
|
|
|
|
|
}
|
2026-06-04 15:16:53 -04:00
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-19 12:29:52 -05:00
|
|
|
// Connection essentials a connect-level recipient is allowed to see.
|
|
|
|
|
const CONNECT_LEVEL_FIELDS = new Set([
|
|
|
|
|
"id",
|
|
|
|
|
"userId",
|
|
|
|
|
"ownerId",
|
|
|
|
|
"ownerUsername",
|
|
|
|
|
"isShared",
|
|
|
|
|
"permissionLevel",
|
|
|
|
|
"sharedExpiresAt",
|
|
|
|
|
"name",
|
|
|
|
|
"ip",
|
|
|
|
|
"port",
|
|
|
|
|
"username",
|
|
|
|
|
"folder",
|
|
|
|
|
"tags",
|
|
|
|
|
"pin",
|
|
|
|
|
"authType",
|
2026-08-06 14:41:39 -05:00
|
|
|
"shareSshAuth",
|
|
|
|
|
"authOverrides",
|
2026-07-19 12:29:52 -05:00
|
|
|
"connectionType",
|
|
|
|
|
"enableTerminal",
|
|
|
|
|
"enableTunnel",
|
|
|
|
|
"enableFileManager",
|
|
|
|
|
"enableDocker",
|
|
|
|
|
"enableProxmox",
|
|
|
|
|
"enableTmuxMonitor",
|
|
|
|
|
"showTerminalInSidebar",
|
|
|
|
|
"showFileManagerInSidebar",
|
|
|
|
|
"showTunnelInSidebar",
|
|
|
|
|
"showDockerInSidebar",
|
|
|
|
|
"showServerStatsInSidebar",
|
|
|
|
|
"enableSsh",
|
|
|
|
|
"enableRdp",
|
|
|
|
|
"enableVnc",
|
|
|
|
|
"enableTelnet",
|
|
|
|
|
"sshPort",
|
|
|
|
|
"rdpPort",
|
|
|
|
|
"vncPort",
|
|
|
|
|
"telnetPort",
|
|
|
|
|
"defaultPath",
|
|
|
|
|
"scpLegacy",
|
|
|
|
|
"tunnelConnections",
|
|
|
|
|
"jumpHosts",
|
|
|
|
|
"createdAt",
|
|
|
|
|
"updatedAt",
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Shapes a shared host row for its recipient. Secrets are always stripped
|
|
|
|
|
* (all levels); connect-level recipients are additionally reduced to
|
|
|
|
|
* connection essentials since they may not view the host's configuration.
|
|
|
|
|
*/
|
|
|
|
|
export function sanitizeHostForRecipient(
|
|
|
|
|
host: Record<string, unknown>,
|
|
|
|
|
permissionLevel: string | undefined,
|
|
|
|
|
): Record<string, unknown> {
|
|
|
|
|
const stripped = stripSensitiveFields(host);
|
2026-08-06 14:41:39 -05:00
|
|
|
delete stripped.credentialId;
|
|
|
|
|
delete stripped.overrideCredentialUsername;
|
|
|
|
|
if (
|
|
|
|
|
stripped.terminalConfig &&
|
|
|
|
|
typeof stripped.terminalConfig === "object" &&
|
|
|
|
|
!Array.isArray(stripped.terminalConfig)
|
|
|
|
|
) {
|
|
|
|
|
const terminalConfig = {
|
|
|
|
|
...(stripped.terminalConfig as Record<string, unknown>),
|
|
|
|
|
};
|
|
|
|
|
delete terminalConfig.agentSocketPath;
|
|
|
|
|
stripped.terminalConfig = terminalConfig;
|
|
|
|
|
}
|
|
|
|
|
const authOverrides =
|
|
|
|
|
stripped.authOverrides &&
|
|
|
|
|
typeof stripped.authOverrides === "object" &&
|
|
|
|
|
!Array.isArray(stripped.authOverrides)
|
|
|
|
|
? (stripped.authOverrides as Record<string, unknown>)
|
|
|
|
|
: undefined;
|
|
|
|
|
const sshOverride =
|
|
|
|
|
authOverrides?.ssh &&
|
|
|
|
|
typeof authOverrides.ssh === "object" &&
|
|
|
|
|
!Array.isArray(authOverrides.ssh)
|
|
|
|
|
? (authOverrides.ssh as Record<string, unknown>)
|
|
|
|
|
: undefined;
|
|
|
|
|
if (!sshOverride?.credentialId) {
|
|
|
|
|
stripped.hasPassword = false;
|
|
|
|
|
stripped.hasKey = false;
|
|
|
|
|
stripped.hasKeyPassword = false;
|
|
|
|
|
stripped.hasSudoPassword = false;
|
|
|
|
|
}
|
2026-07-19 12:29:52 -05:00
|
|
|
|
|
|
|
|
if (permissionLevel !== "connect") {
|
|
|
|
|
return stripped;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const reduced: Record<string, unknown> = {};
|
|
|
|
|
for (const [key, value] of Object.entries(stripped)) {
|
|
|
|
|
if (CONNECT_LEVEL_FIELDS.has(key)) {
|
|
|
|
|
reduced[key] = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return reduced;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-04 15:16:53 -04:00
|
|
|
export function transformHostResponse(
|
|
|
|
|
host: Record<string, unknown>,
|
|
|
|
|
): Record<string, unknown> {
|
|
|
|
|
return {
|
|
|
|
|
...host,
|
|
|
|
|
tags:
|
|
|
|
|
typeof host.tags === "string"
|
|
|
|
|
? host.tags
|
|
|
|
|
? host.tags.split(",").filter(Boolean)
|
|
|
|
|
: []
|
|
|
|
|
: [],
|
|
|
|
|
pin: !!host.pin,
|
2026-08-06 14:41:39 -05:00
|
|
|
shareSshAuth: !!host.shareSshAuth,
|
2026-06-04 15:16:53 -04:00
|
|
|
enableTerminal: !!host.enableTerminal,
|
|
|
|
|
enableTunnel: !!host.enableTunnel,
|
2026-06-21 15:55:41 -05:00
|
|
|
enableFileManager: host.enableFileManager !== false,
|
2026-06-04 15:16:53 -04:00
|
|
|
enableDocker: !!host.enableDocker,
|
2026-06-16 15:59:53 -05:00
|
|
|
enableProxmox: !!host.enableProxmox,
|
|
|
|
|
enableTmuxMonitor: !!host.enableTmuxMonitor,
|
2026-06-04 15:16:53 -04:00
|
|
|
showTerminalInSidebar: !!host.showTerminalInSidebar,
|
|
|
|
|
showFileManagerInSidebar: !!host.showFileManagerInSidebar,
|
|
|
|
|
showTunnelInSidebar: !!host.showTunnelInSidebar,
|
|
|
|
|
showDockerInSidebar: !!host.showDockerInSidebar,
|
|
|
|
|
showServerStatsInSidebar: !!host.showServerStatsInSidebar,
|
|
|
|
|
// Old hosts only had connection_type set; the per-protocol enable flags didn't exist yet.
|
|
|
|
|
// The schema defaults (enableSsh=true, others=false) wrongly mark every old host as SSH.
|
|
|
|
|
// Detect this migration case: if no non-SSH protocol is explicitly enabled AND
|
|
|
|
|
// connectionType is set to a non-SSH value, fall back to inferring from connectionType.
|
|
|
|
|
...(() => {
|
|
|
|
|
const ct = host.connectionType;
|
|
|
|
|
const rdp = !!host.enableRdp;
|
|
|
|
|
const vnc = !!host.enableVnc;
|
|
|
|
|
const tel = !!host.enableTelnet;
|
|
|
|
|
const isMigratedNonSsh = !rdp && !vnc && !tel && ct && ct !== "ssh";
|
|
|
|
|
return {
|
|
|
|
|
enableSsh: isMigratedNonSsh ? false : !!host.enableSsh,
|
|
|
|
|
enableRdp: isMigratedNonSsh ? ct === "rdp" : rdp,
|
|
|
|
|
enableVnc: isMigratedNonSsh ? ct === "vnc" : vnc,
|
|
|
|
|
enableTelnet: isMigratedNonSsh ? ct === "telnet" : tel,
|
|
|
|
|
};
|
|
|
|
|
})(),
|
|
|
|
|
sshPort: host.sshPort ?? host.port ?? 22,
|
|
|
|
|
rdpPort: host.rdpPort ?? 3389,
|
|
|
|
|
vncPort: host.vncPort ?? 5900,
|
|
|
|
|
telnetPort: host.telnetPort ?? 23,
|
|
|
|
|
rdpUser: host.rdpUser || undefined,
|
|
|
|
|
rdpDomain: host.rdpDomain || undefined,
|
|
|
|
|
rdpSecurity: host.rdpSecurity || undefined,
|
|
|
|
|
rdpIgnoreCert: !!host.rdpIgnoreCert,
|
|
|
|
|
vncUser: host.vncUser || undefined,
|
|
|
|
|
telnetUser: host.telnetUser || undefined,
|
|
|
|
|
tunnelConnections: host.tunnelConnections
|
|
|
|
|
? JSON.parse(host.tunnelConnections as string)
|
|
|
|
|
: [],
|
|
|
|
|
jumpHosts: host.jumpHosts ? JSON.parse(host.jumpHosts as string) : [],
|
|
|
|
|
quickActions: host.quickActions
|
|
|
|
|
? JSON.parse(host.quickActions as string)
|
|
|
|
|
: [],
|
|
|
|
|
statsConfig: host.statsConfig
|
|
|
|
|
? JSON.parse(host.statsConfig as string)
|
|
|
|
|
: undefined,
|
|
|
|
|
terminalConfig: host.terminalConfig
|
|
|
|
|
? JSON.parse(host.terminalConfig as string)
|
|
|
|
|
: undefined,
|
|
|
|
|
dockerConfig: host.dockerConfig
|
|
|
|
|
? JSON.parse(host.dockerConfig as string)
|
|
|
|
|
: undefined,
|
2026-06-16 15:59:53 -05:00
|
|
|
proxmoxConfig: host.proxmoxConfig
|
|
|
|
|
? JSON.parse(host.proxmoxConfig as string)
|
|
|
|
|
: undefined,
|
2026-06-04 15:16:53 -04:00
|
|
|
forceKeyboardInteractive: host.forceKeyboardInteractive === "true",
|
2026-06-21 15:55:41 -05:00
|
|
|
useWarpgate: !!host.useWarpgate,
|
2026-06-04 15:16:53 -04:00
|
|
|
socks5ProxyChain: host.socks5ProxyChain
|
|
|
|
|
? JSON.parse(host.socks5ProxyChain as string)
|
|
|
|
|
: [],
|
|
|
|
|
portKnockSequence: host.portKnockSequence
|
|
|
|
|
? JSON.parse(host.portKnockSequence as string)
|
|
|
|
|
: [],
|
|
|
|
|
domain: host.domain || undefined,
|
|
|
|
|
security: host.security || undefined,
|
|
|
|
|
ignoreCert: !!host.ignoreCert,
|
|
|
|
|
guacamoleConfig: host.guacamoleConfig
|
|
|
|
|
? JSON.parse(host.guacamoleConfig as string)
|
|
|
|
|
: undefined,
|
|
|
|
|
};
|
|
|
|
|
}
|