import { SettingsPageHeader } from "@components/SettingsPageheader"; import { SettingsItem } from "./devices.$id.settings"; import { useLoaderData, useNavigate } from "react-router-dom"; import { Button, LinkButton } from "../components/Button"; import { DEVICE_API } from "../ui.config"; import api from "../api"; import { LocalDevice } from "./devices.$id"; import { useDeviceUiNavigation } from "../hooks/useAppNavigation"; import { GridCard } from "../components/Card"; import { ShieldCheckIcon } from "@heroicons/react/24/outline"; import notifications from "../notifications"; import { useCallback, useEffect, useState } from "react"; import { useJsonRpc } from "../hooks/useJsonRpc"; import { InputFieldWithLabel } from "../components/InputField"; import { SelectMenuBasic } from "../components/SelectMenuBasic"; import { SettingsSectionHeader } from "../components/SettingsSectionHeader"; import { isOnDevice } from "../main"; import { CloudState } from "./adopt"; export const loader = async () => { if (isOnDevice) { const status = await api .GET(`${DEVICE_API}/device`) .then(res => res.json() as Promise); return status; } return null; }; // Define hardcoded cloud providers with both API and app URLs const CLOUD_PROVIDERS = [ { value: "jetkvm", apiUrl: "https://api.jetkvm.com", appUrl: "https://app.jetkvm.com", label: "JetKVM Cloud", }, { value: "custom", apiUrl: "", appUrl: "", label: "Custom", }, ]; 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 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 matchingProvider = CLOUD_PROVIDERS.find(p => p.apiUrl === cloudState.url); if (matchingProvider && matchingProvider.value !== "custom") { setSelectedProvider(matchingProvider.value); } else { setSelectedProvider("custom"); } }); }, [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 const provider = CLOUD_PROVIDERS.find(p => p.value === value); if (provider && value !== "custom") { setCloudApiUrl(provider.apiUrl); setCloudAppUrl(provider.appUrl); } }; // Fetch device ID and cloud state on component mount useEffect(() => { getCloudState(); send("getDeviceID", {}, async resp => { if ("error" in resp) return console.error(resp.error); setDeviceId(resp.result as string); }); }, [send, getCloudState]); return (
{loaderData?.authMode && ( <>
{loaderData.authMode === "password" ? (
)}
{!isAdopted && ( <> handleProviderChange(e.target.value)} options={CLOUD_PROVIDERS.map(p => ({ value: p.value, label: p.label }))} /> {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 JetKVM Cloud

)}
); }