import type { Request } from "express"; import type { RefObject } from "react"; import type { HostAuthOverrides } from "./auth-protocols.js"; export type { AuthOverrideProtocol, HostAuthOverrideState, HostAuthOverrides, } from "./auth-protocols.js"; // ============================================================================ // HOST TYPES (SSH, RDP, VNC, Telnet) // ============================================================================ export type ConnectionType = "ssh" | "rdp" | "vnc" | "telnet"; export type SSHAuthType = | "password" | "key" | "credential" | "none" | "opkssh" | "stepca" | "tailscale"; export type GuacamoleAuthType = "password" | "credential"; export interface ProxmoxStatsConfig { nodeName?: string | null; pollInterval?: number; enabledCards?: string[]; } export interface ProxmoxConfig { defaultCredentialId: number | null; defaultAuthType?: string; windowsPatterns: string; dockerPatterns: string; preferredPrefixes: string; autoSyncEnabled?: boolean; syncIntervalMinutes?: number; markMissingGuests?: boolean; lastSyncAt?: string; lastSyncStatus?: "success" | "error"; lastSyncError?: string | null; lastSyncResult?: { created: number; updated: number; markedMissing: number; skipped: number; errors: string[]; }; } export type WebEndpointAccess = "direct" | "tunnel"; export type WebEndpointRender = "external" | "embedded"; /** One web UI a host serves, declared in the host's settings. */ export interface WebEndpoint { /** * Stable identifier. Must NOT be derived from the port: it keys both the * tunnel name and the tab identity, so editing a port has to leave a live * tunnel findable under the same name. */ id: string; label: string; scheme: "http" | "https"; port: number; /** Defaults to "/". Normalized at the storage boundary, never here. */ path?: string; access: WebEndpointAccess; render: WebEndpointRender; /** * Direct endpoints only. Allows an invalid TLS certificate for this * endpoint's exact origin. A no-op for tunnel access, whose host component * is loopback and therefore already exempt. */ ignoreCert?: boolean; /** * Tunnel endpoints only. Where the backend binds the forward, exactly as * the server tunnels feature exposes it. Defaults to 127.0.0.1, reachable * only from the machine running the backend. A web deployment runs the * backend on a server, so reaching the forward from a browser needs an * address that machine answers on -- which also exposes the target's web UI * to anyone who can reach the port, with no login in front of it. */ bindHost?: string; /** * Tunnel endpoints only. Which port the forward listens on, as the server * tunnels feature's Source Port does. Left unset the kernel picks a free * one, which is fine when backend and browser share a machine -- but a * container can only publish ports it knows in advance. */ localPort?: number; } export interface WebUiConfig { endpoints: WebEndpoint[]; } /** A host may declare at most this many web endpoints. */ export const MAX_WEB_ENDPOINTS = 16; /** Endpoint labels are truncated to this length. */ export const MAX_WEB_ENDPOINT_LABEL_LENGTH = 64; export interface JumpHost { hostId: number; } export interface QuickAction { name: string; snippetId: number; } export type Host = { id: number; name: string; ip: string; port: number; username: string; folder: string; tags: string[]; pin: boolean; authType: | "password" | "key" | "credential" | "none" | "opkssh" | "stepca" | "tailscale" | "agent" | "vault"; shareSshAuth?: boolean; password?: string; key?: string; keyPassword?: string; keyType?: string; sudoPassword?: string; forceKeyboardInteractive?: boolean; autostartPassword?: string; autostartKey?: string; autostartKeyPassword?: string; credentialId?: number; overrideCredentialUsername?: boolean; userId?: string; jumpHosts?: JumpHost[]; quickActions?: QuickAction[]; statusCheckEnabled?: boolean; /** Seconds between status checks; null follows the global setting. */ statusCheckInterval?: number | null; terminalConfig?: Partial; notes?: string; useSocks5?: boolean; socks5Host?: string; socks5Port?: number; socks5Username?: string; socks5Password?: string; socks5ProxyChain?: ProxyNode[]; portKnockSequence?: Array<{ port: number; protocol?: "tcp" | "udp"; delay?: number; }>; /** "ssh", or the id of the plugin protocol a host without SSH uses. */ connectionType?: string; domain?: string; enableSsh?: boolean; sshPort?: number; rdpCredentialId?: number | null; rdpUser?: string; rdpPassword?: string; rdpDomain?: string; vncCredentialId?: number | null; vncPassword?: string; vncUser?: string; telnetUser?: string; telnetPassword?: string; telnetCredentialId?: number | null; rdpAuthType?: "direct" | "credential" | "none" | null; vncAuthType?: "direct" | "credential" | null; telnetAuthType?: "direct" | "credential" | null; /** * Stable identity across a desktop/server sync pair. `id` is an * autoincrement local to whichever database produced the row, so it cannot * name the same host on both sides; this can. Absent on hosts that have * never been part of a sync. */ syncId?: string | null; createdAt: string; updatedAt: string; sortOrder?: number | null; connectionOrigin?: "local" | "remote" | null; /** Assigned when a host is opened in a tab; distinguishes duplicate tabs. */ instanceId?: string; hasKey?: boolean; hasKeyPassword?: boolean; // Set by formatHostOutput() alongside hasKey/hasKeyPassword so the UI can // tell a stored secret from an empty one without receiving it. hasPassword?: boolean; hasSudoPassword?: boolean; hasRdpPassword?: boolean; hasVncPassword?: boolean; hasTelnetPassword?: boolean; isShared?: boolean; authOverrides?: HostAuthOverrides; permissionLevel?: "connect" | "view" | "edit" | "manage"; sharedExpiresAt?: string; ownerUsername?: string; /** Enabled plugins' host-scope settings, keyed by plugin id. Secrets redacted. */ pluginSettings?: Record>; }; export interface JumpHostData { hostId: number; } export interface QuickActionData { name: string; snippetId: number; } export interface ProxyNode { host: string; port: number; /** * The host editor writes "socks4"/"socks5"/"http", while proxy-helper.ts * tests for "http" and casts everything else to 4|5 before handing it to the * socks client. The two spellings have never agreed; typed as the union of * what is actually stored rather than pretending one side is right. */ type: 4 | 5 | "http" | "socks4" | "socks5"; username?: string; password?: string; } export interface HostData { name?: string; ip: string; port: number; username: string; folder?: string; /** Sub-host nesting: mutually exclusive with folder. */ parentHostId?: number | string | null; tags?: string[]; pin?: boolean; authType: | "password" | "key" | "credential" | "none" | "opkssh" | "stepca" | "tailscale" | "agent" | "vault"; shareSshAuth?: boolean; password?: string; key?: File | string | null; keyPassword?: string; keyType?: string; sudoPassword?: string; credentialId?: number | null; connectionOrigin?: "local" | "remote" | null; overrideCredentialUsername?: boolean; forceKeyboardInteractive?: boolean; jumpHosts?: JumpHostData[]; quickActions?: QuickActionData[]; statusCheckEnabled?: boolean; /** Seconds between status checks; null follows the global setting. */ statusCheckInterval?: number | null; terminalConfig?: Partial; notes?: string; useSocks5?: boolean; socks5Host?: string; socks5Port?: number; socks5Username?: string; socks5Password?: string; socks5ProxyChain?: ProxyNode[]; portKnockSequence?: Array<{ port: number; protocol?: "tcp" | "udp"; delay?: number; }>; /** "ssh", or the id of the plugin protocol a host without SSH uses. */ connectionType?: string; domain?: string; enableSsh?: boolean; sshPort?: number; rdpCredentialId?: number | null; rdpUser?: string; rdpPassword?: string; rdpDomain?: string; vncCredentialId?: number | null; vncPassword?: string; vncUser?: string; telnetUser?: string; telnetPassword?: string; telnetCredentialId?: number | null; rdpAuthType?: "direct" | "credential" | "none" | null; vncAuthType?: "direct" | "credential" | null; telnetAuthType?: "direct" | "credential" | null; } export type SSHHost = Host; export type SSHHostData = HostData; export interface SSHFolder { id: number; userId: string; name: string; color?: string; icon?: string; credentialId?: number | null; sortOrder?: number | null; createdAt: string; updatedAt: string; } // ============================================================================ // CREDENTIAL TYPES // ============================================================================ export interface Credential { id: number; name: string; description?: string; folder?: string; tags: string[]; authType: "password" | "key"; username?: string; password?: string; key?: string; publicKey?: string; /** CA-signed certificate file content (e.g. id_ed25519-cert.pub) */ certPublicKey?: string; /** True when a cert is stored but certPublicKey content is redacted in list responses */ hasCertPublicKey?: boolean; keyPassword?: string; keyType?: string; usageCount: number; lastUsed?: string; createdAt: string; updatedAt: string; } export interface CredentialBackend { id: number; userId: string; name: string; description: string | null; folder: string | null; tags: string; authType: "password" | "key"; username: string | null; password: string | null; key: string; privateKey?: string; publicKey?: string; /** CA-signed certificate file content (e.g. id_ed25519-cert.pub) */ certPublicKey?: string; keyPassword: string | null; keyType?: string; detectedKeyType: string; usageCount: number; lastUsed: string | null; createdAt: string; updatedAt: string; } export interface CredentialData { name: string; description?: string; folder?: string; tags: string[]; authType: "password" | "key"; username?: string; password?: string; key?: string; publicKey?: string; /** CA-signed certificate file content (e.g. id_ed25519-cert.pub) */ certPublicKey?: string | null; keyPassword?: string; keyType?: string; } // ============================================================================ // TUNNEL TYPES // ============================================================================ export type TunnelScope = "s2s" | "c2s"; export type TunnelMode = "local" | "remote" | "dynamic"; export interface TunnelConnection { scope?: TunnelScope; mode?: TunnelMode; tunnelType?: "local" | "remote"; localAddress?: string; remoteAddress?: string; bindHost?: string; sourceHostId?: number; sourceHostSyncId?: string; sourceHostName?: string; sourcePort: number; endpointPort: number; endpointHost?: string; targetHost?: string; endpointPassword?: string; endpointKey?: string; endpointKeyPassword?: string; endpointAuthType?: string; endpointKeyType?: string; maxRetries: number; retryInterval: number; autoStart: boolean; } // ============================================================================ // FILE MANAGER TYPES // ============================================================================ export interface Tab { id: string | number; title: string; fileName: string; content: string; isSSH?: boolean; sshSessionId?: string; filePath?: string; loading?: boolean; dirty?: boolean; } export interface FileManagerFile { name: string; path: string; type?: "file" | "directory"; isSSH?: boolean; sshSessionId?: string; } export interface FileManagerShortcut { name: string; path: string; } export interface FileItem { name: string; path: string; isPinned?: boolean; type: "file" | "directory" | "link"; sshSessionId?: string; size?: number; modified?: string; modifiedTimestamp?: number; permissions?: string; owner?: string; group?: string; linkTarget?: string; executable?: boolean; } export interface ShortcutItem { name: string; path: string; } export interface SSHConnection { id: number; name: string; ip: string; port: number; username: string; isPinned?: boolean; } // ============================================================================ // HOST INFO TYPES // ============================================================================ export interface HostInfo { id: number; name?: string; ip: string; port: number; createdAt: string; } // ============================================================================ // ALERT TYPES // ============================================================================ export interface TermixAlert { id: string; title: string; message: string; expiresAt: string; priority?: "low" | "medium" | "high" | "critical"; type?: "info" | "warning" | "error" | "success"; actionUrl?: string; actionText?: string; } // ============================================================================ // TERMINAL CONFIGURATION TYPES // ============================================================================ export interface TerminalConfig { localEcho?: "default" | "off" | "auto" | "on"; cursorBlink: boolean; cursorStyle: "block" | "underline" | "bar"; fontSize: number; fontFamily: string; letterSpacing: number; lineHeight: number; theme: string; scrollback: number; bellStyle: "none" | "sound" | "visual" | "both"; rightClickSelectsWord: boolean; macOptionIsMeta: boolean; fastScrollModifier: "alt" | "ctrl" | "shift"; fastScrollSensitivity: number; minimumContrastRatio: number; backspaceMode: "normal" | "control-h"; agentForwarding: boolean; environmentVariables: Array<{ key: string; value: string }>; startupSnippetId: number | null; autoMosh: boolean; moshCommand: string; sudoPasswordAutoFill: boolean; sudoPassword?: string | null; keepaliveInterval?: number; keepaliveCountMax?: number; autoTmux: boolean; syntaxHighlighting: boolean; syntaxHighlightingOptions?: { logLevels: boolean; paths: boolean; timestamps: boolean; ipAddresses: boolean; urls: boolean; numbers: boolean; }; backgroundImage?: string; backgroundImageOpacity?: number; allowLegacyAlgorithms?: boolean; linkClickBehavior?: "confirm" | "direct"; useSSHTitle?: boolean; agentSocketPath?: string; agentIdentity?: string; customThemeColors?: { background: string; foreground: string; cursor?: string; cursorAccent?: string; selectionBackground?: string; selectionForeground?: string; black: string; red: string; green: string; yellow: string; blue: string; magenta: string; cyan: string; white: string; brightBlack: string; brightRed: string; brightGreen: string; brightYellow: string; brightBlue: string; brightMagenta: string; brightCyan: string; brightWhite: string; }; } // ============================================================================ // TAB TYPES // ============================================================================ export interface TabContextTab { id: number; instanceId?: string; type: | "home" | "terminal" | "ssh_manager" | "server_stats" | "admin" | "file_manager" | "user_profile" | "docker" | "tunnel" | "network_graph" | "tmux_monitor" // --- tmux-monitor --- | "rdp" | "vnc" | "telnet"; title: string; hostConfig?: SSHHost; terminalRef?: RefObject; initialTab?: string; _updateTimestamp?: number; connectionConfig?: Record; } export interface TerminalRefHandle { disconnect?: () => void; reconnect?: () => void; isConnected?: () => boolean; fit?: () => void; sendInput?: (data: string) => void; subscribeOutput?: (listener: (data: string) => void) => () => void; notifyResize?: () => void; refresh?: () => void; openFileManager?: () => void; } export type SplitLayout = "2h" | "2v" | "3l" | "3r" | "3t" | "4grid"; export interface SplitConfiguration { layout: SplitLayout; positions: Map; } export interface SplitLayoutOption { id: SplitLayout; name: string; description: string; cellCount: number; icon: string; } // ============================================================================ // CONNECTION STATES // ============================================================================ export const CONNECTION_STATES = { DISCONNECTED: "disconnected", CONNECTING: "connecting", CONNECTED: "connected", VERIFYING: "verifying", FAILED: "failed", UNSTABLE: "unstable", RETRYING: "retrying", WAITING: "waiting", DISCONNECTING: "disconnecting", } as const; export type ConnectionState = (typeof CONNECTION_STATES)[keyof typeof CONNECTION_STATES]; export type ErrorType = | "CONNECTION_FAILED" | "AUTHENTICATION_FAILED" | "TIMEOUT" | "NETWORK_ERROR" | "UNKNOWN"; // ============================================================================ // AUTHENTICATION TYPES // ============================================================================ export type AuthType = | "password" | "key" | "credential" | "none" | "opkssh" | "stepca" | "tailscale"; export type KeyType = "rsa" | "ecdsa" | "ed25519"; // ============================================================================ // API RESPONSE TYPES // ============================================================================ export interface ApiResponse { data?: T; error?: string; message?: string; status?: number; } // ============================================================================ // COMPONENT PROP TYPES // ============================================================================ export interface CredentialsManagerProps { onEditCredential?: (credential: Credential) => void; onAddCredential?: () => void; } export interface CredentialEditorProps { editingCredential?: Credential | null; onFormSubmit?: () => void; onBack?: () => void; } export interface CredentialViewerProps { credential: Credential; onClose: () => void; onEdit: () => void; } export interface CredentialSelectorProps { value?: number | null; onValueChange: (value: number | null) => void; } export interface HostManagerProps { onSelectView?: (view: string) => void; isTopbarOpen?: boolean; initialTab?: string; hostConfig?: SSHHost; _updateTimestamp?: number; rightSidebarOpen?: boolean; rightSidebarWidth?: number; currentTabId?: number; updateTab?: (tabId: number, updates: Partial>) => void; } export interface SSHManagerHostEditorProps { editingHost?: SSHHost | null; onFormSubmit?: () => void; } export interface SSHManagerHostViewerProps { onEditHost?: (host: SSHHost) => void; onAddHost?: () => void; } export interface HostProps { host: SSHHost; onHostConnect?: () => void; } export interface FileManagerProps { onSelectView?: (view: string) => void; embedded?: boolean; initialHost?: SSHHost | null; } export interface AlertCardProps { alert: TermixAlert; onDismiss: (alertId: string) => void; } export interface AlertManagerProps { alerts: TermixAlert[]; onDismiss: (alertId: string) => void; loggedIn: boolean; } export interface FolderStats { totalHosts: number; hostsByType: Array<{ type: string; count: number; }>; } // Snippet, SnippetFolder types live in ui-types.ts (the shape actually used // by SnippetsPanel.tsx); this file's older definitions were unused and removed. // ============================================================================ // UTILITY TYPES // ============================================================================ export type Optional = Omit & Partial>; export type RequiredFields = T & Required>; export type PartialExcept = Partial & Pick; // ============================================================================ // EXPRESS REQUEST TYPES // ============================================================================ export interface AuthenticatedRequest extends Request { userId: string; sessionId?: string; apiKeyId?: string; actingAdminUserId?: string; user?: { id: string; username: string; isAdmin: boolean; }; } // ============================================================================ // GITHUB API TYPES // ============================================================================ export interface GitHubAsset { id: number; name: string; size: number; download_count: number; browser_download_url: string; } export interface GitHubRelease { id: number; tag_name: string; name: string; body: string; published_at: string; html_url: string; assets: GitHubAsset[]; prerelease: boolean; draft: boolean; } export interface GitHubAPIResponse { data: T; cached: boolean; cache_age?: number; timestamp?: number; } // ============================================================================ // CACHE TYPES // ============================================================================ export interface CacheEntry { data: T; timestamp: number; expiresAt: number; } // ============================================================================ // DATABASE EXPORT/IMPORT TYPES // ============================================================================ export interface ExportSummary { sshHostsImported: number; sshCredentialsImported: number; fileManagerItemsImported: number; dismissedAlertsImported: number; credentialUsageImported: number; settingsImported: number; skippedItems: number; errors: string[]; } export interface ImportResult { success: boolean; summary: ExportSummary; } export interface ExportRequestBody { password: string; } export interface ImportRequestBody { password: string; } export interface ExportPreviewBody { scope?: string; includeCredentials?: boolean; } export interface RestoreRequestBody { backupPath: string; targetPath?: string; } // ============================================================================ // DOCKER TYPES // ============================================================================