import { useLoaderData, useNavigate } from "react-router-dom"; import { ShieldCheckIcon } from "@heroicons/react/24/outline"; import { useCallback, useEffect, useState } from "react"; import api from "@/api"; import { SettingsPageHeader } from "@components/SettingsPageheader"; import { GridCard } from "@/components/Card"; import { Button, LinkButton } from "@/components/Button"; import { InputFieldWithLabel } from "@/components/InputField"; import { SelectMenuBasic } from "@/components/SelectMenuBasic"; import { SettingsSectionHeader } from "@/components/SettingsSectionHeader"; import { useDeviceUiNavigation } from "@/hooks/useAppNavigation"; import notifications from "@/notifications"; import { DEVICE_API } from "@/ui.config"; import { useJsonRpc } from "@/hooks/useJsonRpc"; import { isOnDevice } from "@/main"; import { LocalDevice } from "./devices.$id"; import { SettingsItem } from "./devices.$id.settings"; import { CloudState } from "./adopt"; import { TextAreaWithLabel } from "@components/TextArea"; export interface TLSState { mode: "self-signed" | "custom" | "disabled"; certificate?: string; privateKey?: string; }; export const loader = async () => { if (isOnDevice) { const status = await api .GET(`${DEVICE_API}/device`) .then(res => res.json() as Promise); return status; } return null; }; export default function SettingsAccessIndexRoute() { const loaderData = useLoaderData() as LocalDevice | null; const { navigateTo } = useDeviceUiNavigation(); const navigate = useNavigate(); const [send] = useJsonRpc(); const [isAdopted, setAdopted] = useState(false); const [deviceId, setDeviceId] = useState(null); const [cloudApiUrl, setCloudApiUrl] = useState(""); const [cloudAppUrl, setCloudAppUrl] = useState(""); // Use a simple string identifier for the selected provider const [selectedProvider, setSelectedProvider] = useState("jetkvm"); const [tlsMode, setTlsMode] = useState("self-signed"); const [tlsCert, setTlsCert] = useState(""); const [tlsKey, setTlsKey] = useState(""); const getCloudState = useCallback(() => { send("getCloudState", {}, resp => { if ("error" in resp) return console.error(resp.error); const cloudState = resp.result as CloudState; setAdopted(cloudState.connected); setCloudApiUrl(cloudState.url); if (cloudState.appUrl) setCloudAppUrl(cloudState.appUrl); // Find if the API URL matches any of our predefined providers const isAPIJetKVMProd = cloudState.url === "https://api.jetkvm.com"; const isAppJetKVMProd = cloudState.appUrl === "https://app.jetkvm.com"; if (isAPIJetKVMProd && isAppJetKVMProd) { setSelectedProvider("jetkvm"); } else { setSelectedProvider("custom"); } }); }, [send]); const getTLSState = useCallback(() => { send("getTLSState", {}, resp => { if ("error" in resp) return console.error(resp.error); const tlsState = resp.result as TLSState; setTlsMode(tlsState.mode); if (tlsState.certificate) setTlsCert(tlsState.certificate); if (tlsState.privateKey) setTlsKey(tlsState.privateKey); }); }, [send]); const deregisterDevice = async () => { send("deregisterDevice", {}, resp => { if ("error" in resp) { notifications.error( `Failed to de-register device: ${resp.error.data || "Unknown error"}`, ); return; } getCloudState(); // In cloud mode, we need to navigate to the device overview page, as we don't a connection anymore if (!isOnDevice) navigate("/"); return; }); }; const onCloudAdoptClick = useCallback( (cloudApiUrl: string, cloudAppUrl: string) => { if (!deviceId) { notifications.error("No device ID available"); return; } send("setCloudUrl", { apiUrl: cloudApiUrl, appUrl: cloudAppUrl }, resp => { if ("error" in resp) { notifications.error( `Failed to update cloud URL: ${resp.error.data || "Unknown error"}`, ); return; } const returnTo = new URL(window.location.href); returnTo.pathname = "/adopt"; returnTo.search = ""; returnTo.hash = ""; window.location.href = cloudAppUrl + "/signup?deviceId=" + deviceId + `&returnTo=${returnTo.toString()}`; }); }, [deviceId, send], ); // Handle provider selection change const handleProviderChange = (value: string) => { setSelectedProvider(value); // If selecting a predefined provider, update both URLs if (value === "jetkvm") { setCloudApiUrl("https://api.jetkvm.com"); setCloudAppUrl("https://app.jetkvm.com"); } else { if (cloudApiUrl || cloudAppUrl) return; setCloudApiUrl(""); setCloudAppUrl(""); } }; // Handle TLS mode change const handleTlsModeChange = (value: string) => { setTlsMode(value); }; const handleTlsCertChange = (value: string) => { setTlsCert(value); }; const handleTlsKeyChange = (value: string) => { setTlsKey(value); }; const handleTlsUpdate = useCallback(() => { const state = { mode: tlsMode } as TLSState; if (tlsMode !== "disabled") { state.certificate = tlsCert; state.privateKey = tlsKey; } send("setTLSState", { state }, resp => { if ("error" in resp) { notifications.error(`Failed to update TLS settings: ${resp.error.data || "Unknown error"}`); return; } notifications.success("TLS settings updated successfully"); }); }, [send, tlsMode, tlsCert, tlsKey]); // Fetch device ID and cloud state on component mount useEffect(() => { getCloudState(); getTLSState(); send("getDeviceID", {}, async resp => { if ("error" in resp) return console.error(resp.error); setDeviceId(resp.result as string); }); }, [send, getCloudState, getTLSState]); return (
{loaderData?.authMode && ( <>
<> Select the TLS mode for your device experimental
The feature might not work as expected, please report any issues if you encounter any. } > handleTlsModeChange(e.target.value)} options={[ { value: "self-signed", label: "Self-signed" }, { value: "custom", label: "Custom" }, { value: "disabled", label: "Disabled" }, ]} />
{tlsMode === "custom" && (
handleTlsCertChange(e.target.value)} />
handleTlsKeyChange(e.target.value)} />

Private key won't be shown again after saving.

)}
{loaderData.authMode === "password" ? (
)}
{!isAdopted && ( <> handleProviderChange(e.target.value)} options={[ { value: "jetkvm", label: "JetKVM Cloud" }, { value: "custom", label: "Custom" }, ]} /> {selectedProvider === "custom" && (
setCloudApiUrl(e.target.value)} placeholder="https://api.example.com" />
setCloudAppUrl(e.target.value)} placeholder="https://app.example.com" />
)} )} {/* Show security info for JetKVM Cloud */} {selectedProvider === "jetkvm" && (

Cloud Security

  • End-to-end encryption using WebRTC (DTLS and SRTP)
  • Zero Trust security model
  • OIDC (OpenID Connect) authentication
  • All streams encrypted in transit
All cloud components are open-source and available on{" "} GitHub .

)} {!isAdopted ? (
) : (

Your device is adopted to the Cloud

)}
); }