import { useEffect, useState } from "react"; import { UserGroupIcon, } from "@heroicons/react/16/solid"; import { useJsonRpc, JsonRpcResponse } from "@/hooks/useJsonRpc"; import { usePermissions } from "@/hooks/usePermissions"; import { Permission } from "@/types/permissions"; import { useSettingsStore } from "@/hooks/stores"; import { notify } from "@/notifications"; import Card from "@/components/Card"; import Checkbox from "@/components/Checkbox"; import { SettingsPageHeader } from "@/components/SettingsPageheader"; import { SettingsItem } from "@/components/SettingsItem"; export default function SessionsSettings() { const { send } = useJsonRpc(); const { hasPermission } = usePermissions(); const canModifySettings = hasPermission(Permission.SETTINGS_WRITE); const { requireSessionNickname, setRequireSessionNickname, requireSessionApproval, setRequireSessionApproval, maxRejectionAttempts, setMaxRejectionAttempts } = useSettingsStore(); const [reconnectGrace, setReconnectGrace] = useState(10); const [primaryTimeout, setPrimaryTimeout] = useState(300); const [privateKeystrokes, setPrivateKeystrokes] = useState(false); const [maxSessions, setMaxSessions] = useState(10); const [observerTimeout, setObserverTimeout] = useState(120); useEffect(() => { send("getSessionSettings", {}, (response: JsonRpcResponse) => { if ("error" in response) { console.error("Failed to get session settings:", response.error); } else { const settings = response.result as { requireApproval: boolean; requireNickname: boolean; reconnectGrace?: number; primaryTimeout?: number; privateKeystrokes?: boolean; maxRejectionAttempts?: number; maxSessions?: number; observerTimeout?: number; }; setRequireSessionApproval(settings.requireApproval); setRequireSessionNickname(settings.requireNickname); if (settings.reconnectGrace !== undefined) { setReconnectGrace(settings.reconnectGrace); } if (settings.primaryTimeout !== undefined) { setPrimaryTimeout(settings.primaryTimeout); } if (settings.privateKeystrokes !== undefined) { setPrivateKeystrokes(settings.privateKeystrokes); } if (settings.maxRejectionAttempts !== undefined) { setMaxRejectionAttempts(settings.maxRejectionAttempts); } if (settings.maxSessions !== undefined) { setMaxSessions(settings.maxSessions); } if (settings.observerTimeout !== undefined) { setObserverTimeout(settings.observerTimeout); } } }); }, [send, setRequireSessionApproval, setRequireSessionNickname, setMaxRejectionAttempts]); const updateSessionSettings = (updates: Partial<{ requireApproval: boolean; requireNickname: boolean; reconnectGrace: number; primaryTimeout: number; privateKeystrokes: boolean; maxRejectionAttempts: number; maxSessions: number; observerTimeout: number; }>) => { if (!canModifySettings) { notify.error("Only the primary session can change this setting"); return; } send("setSessionSettings", { settings: { requireApproval: requireSessionApproval, requireNickname: requireSessionNickname, reconnectGrace: reconnectGrace, primaryTimeout: primaryTimeout, privateKeystrokes: privateKeystrokes, maxRejectionAttempts: maxRejectionAttempts, maxSessions: maxSessions, observerTimeout: observerTimeout, ...updates } }, (response: JsonRpcResponse) => { if ("error" in response) { console.error("Failed to update session settings:", response.error); notify.error("Failed to update session settings"); } }); }; return (
{!canModifySettings && (
Note: Only the primary session can modify these settings. Request primary control to change settings.
)}

Access Control

{ const newValue = e.target.checked; setRequireSessionApproval(newValue); updateSessionSettings({ requireApproval: newValue }); notify.success( newValue ? "New sessions will require approval" : "New sessions will be automatically approved" ); }} /> { const newValue = e.target.checked; setRequireSessionNickname(newValue); updateSessionSettings({ requireNickname: newValue }); notify.success( newValue ? "Session nicknames are now required" : "Session nicknames are now optional" ); }} />
{ const newValue = parseInt(e.target.value) || 3; if (newValue < 1 || newValue > 10) { notify.error("Maximum attempts must be between 1 and 10"); return; } setMaxRejectionAttempts(newValue); updateSessionSettings({ maxRejectionAttempts: newValue }); notify.success( `Denied sessions can now retry up to ${newValue} time${newValue === 1 ? '' : 's'}` ); }} className="w-20 px-2 py-1.5 border rounded-md bg-white dark:bg-slate-800 border-slate-300 dark:border-slate-600 text-slate-900 dark:text-white disabled:opacity-50 disabled:cursor-not-allowed text-sm" /> attempts
{ const newValue = parseInt(e.target.value) || 10; if (newValue < 5 || newValue > 60) { notify.error("Grace period must be between 5 and 60 seconds"); return; } setReconnectGrace(newValue); updateSessionSettings({ reconnectGrace: newValue }); notify.success( `Session will have ${newValue} seconds to reconnect` ); }} className="w-20 px-2 py-1.5 border rounded-md bg-white dark:bg-slate-800 border-slate-300 dark:border-slate-600 text-slate-900 dark:text-white disabled:opacity-50 disabled:cursor-not-allowed text-sm" /> seconds
{ const newValue = parseInt(e.target.value) || 0; if (newValue < 0 || newValue > 3600) { notify.error("Timeout must be between 0 and 3600 seconds"); return; } setPrimaryTimeout(newValue); updateSessionSettings({ primaryTimeout: newValue }); notify.success( newValue === 0 ? "Primary session timeout disabled" : `Primary session will timeout after ${Math.round(newValue / 60)} minutes of inactivity` ); }} className="w-24 px-2 py-1.5 border rounded-md bg-white dark:bg-slate-800 border-slate-300 dark:border-slate-600 text-slate-900 dark:text-white disabled:opacity-50 disabled:cursor-not-allowed text-sm" /> seconds
{ const newValue = parseInt(e.target.value) || 10; if (newValue < 1 || newValue > 20) { notify.error("Max sessions must be between 1 and 20"); return; } setMaxSessions(newValue); updateSessionSettings({ maxSessions: newValue }); notify.success( `Maximum concurrent sessions set to ${newValue}` ); }} className="w-20 px-2 py-1.5 border rounded-md bg-white dark:bg-slate-800 border-slate-300 dark:border-slate-600 text-slate-900 dark:text-white disabled:opacity-50 disabled:cursor-not-allowed text-sm" /> sessions
{ const newValue = parseInt(e.target.value) || 120; if (newValue < 30 || newValue > 600) { notify.error("Timeout must be between 30 and 600 seconds"); return; } setObserverTimeout(newValue); updateSessionSettings({ observerTimeout: newValue }); notify.success( `Observer cleanup timeout set to ${Math.round(newValue / 60)} minute${Math.round(newValue / 60) === 1 ? '' : 's'}` ); }} className="w-20 px-2 py-1.5 border rounded-md bg-white dark:bg-slate-800 border-slate-300 dark:border-slate-600 text-slate-900 dark:text-white disabled:opacity-50 disabled:cursor-not-allowed text-sm" /> seconds
{ const newValue = e.target.checked; setPrivateKeystrokes(newValue); updateSessionSettings({ privateKeystrokes: newValue }); notify.success( newValue ? "Keystrokes are now private to primary session" : "Keystrokes are visible to all authorized sessions" ); }} />

How Multi-Session Access Works

Primary: Full control over the KVM device including keyboard, mouse, and settings
Observer: View-only access to monitor activity without control capabilities
Pending: Awaiting approval from the primary session (when approval is required)
Use the Sessions panel in the top navigation bar to view and manage active sessions.
); }