diff --git a/config.go b/config.go index 4119a0c..38a2b5b 100644 --- a/config.go +++ b/config.go @@ -53,7 +53,7 @@ var defaultConfig = &Config{ ProductId: "0x0104", //Multifunction Composite Gadget SerialNumber: "", Manufacturer: "JetKVM", - Product: "JetKVM USB Emulation Device", + Product: "USB Emulation Device", }, } diff --git a/ui/src/components/USBConfigDialog.tsx b/ui/src/components/USBConfigDialog.tsx index cb12447..5ecbb8d 100644 --- a/ui/src/components/USBConfigDialog.tsx +++ b/ui/src/components/USBConfigDialog.tsx @@ -1,83 +1,25 @@ -import { GridCard } from "@/components/Card"; -import {useCallback, useEffect, useState} from "react"; import { Button } from "@components/Button"; -import LogoBlueIcon from "@/assets/logo-blue.svg"; -import LogoWhiteIcon from "@/assets/logo-white.svg"; -import Modal from "@components/Modal"; import { InputFieldWithLabel } from "./InputField"; -import { useJsonRpc } from "@/hooks/useJsonRpc"; -import { useUsbConfigModalStore } from "@/hooks/stores"; -import ExtLink from "@components/ExtLink"; -import { UsbConfigState } from "@/hooks/stores" +import { UsbConfigState } from "@/hooks/stores"; +import { useEffect, useCallback, useState } from "react"; +import { useJsonRpc } from "../hooks/useJsonRpc"; +import { USBConfig } from "../routes/devices.$id.settings.hardware"; -export default function USBConfigDialog({ - open, - setOpen, -}: { - open: boolean; - setOpen: (open: boolean) => void; -}) { - return ( - setOpen(false)}> - - - ); -} - -export function Dialog({ setOpen }: { setOpen: (open: boolean) => void }) { - const { modalView, setModalView } = useUsbConfigModalStore(); - const [error, setError] = useState(null); - - const [send] = useJsonRpc(); - - const handleUsbConfigChange = useCallback((usbConfig: object) => { - send("setUsbConfig", { usbConfig }, resp => { - if ("error" in resp) { - setError(`Failed to update USB Config: ${resp.error.data || "Unknown error"}`); - return; - } - setModalView("updateUsbConfigSuccess"); - }); - }, [send, setModalView]); - - return ( - -
- {modalView === "updateUsbConfig" && ( - setOpen(false)} - error={error} - /> - )} - {modalView === "updateUsbConfigSuccess" && ( - setOpen(false)} - /> - )} -
-
- ); -} - -function UpdateUsbConfigModal({ +export default function UpdateUsbConfigModal({ onSetUsbConfig, - onCancel, - error, + onRestoreToDefault, }: { - onSetUsbConfig: (usb_config: object) => void; - onCancel: () => void; - error: string | null; + onSetUsbConfig: (usbConfig: USBConfig) => void; + onRestoreToDefault: () => void; }) { - const [usbConfigState, setUsbConfigState] = useState({ - vendor_id: '', - product_id: '', - serial_number: '', - manufacturer: '', - product: '' + const [usbConfigState, setUsbConfigState] = useState({ + vendor_id: "", + product_id: "", + serial_number: "", + manufacturer: "", + product: "", }); + const [send] = useJsonRpc(); const syncUsbConfig = useCallback(() => { @@ -90,53 +32,34 @@ function UpdateUsbConfigModal({ }); }, [send, setUsbConfigState]); - // Load stored usb config from the backend + // Load stored usb config from the backend useEffect(() => { syncUsbConfig(); }, [syncUsbConfig]); const handleUsbVendorIdChange = (value: string) => { - setUsbConfigState({... usbConfigState, vendor_id: value}) + setUsbConfigState({ ...usbConfigState, vendor_id: value }); }; const handleUsbProductIdChange = (value: string) => { - setUsbConfigState({... usbConfigState, product_id: value}) + setUsbConfigState({ ...usbConfigState, product_id: value }); }; const handleUsbSerialChange = (value: string) => { - setUsbConfigState({... usbConfigState, serial_number: value}) + setUsbConfigState({ ...usbConfigState, serial_number: value }); }; const handleUsbManufacturer = (value: string) => { - setUsbConfigState({... usbConfigState, manufacturer: value}) + setUsbConfigState({ ...usbConfigState, manufacturer: value }); }; const handleUsbProduct = (value: string) => { - setUsbConfigState({... usbConfigState, product: value}) + setUsbConfigState({ ...usbConfigState, product: value }); }; return ( -
-
- - -
-
-
-

USB Emulation Configuration

-

- Set custom USB parameters to control how the USB device is emulated. - The device will rebind once the parameters are updated. -

-
- - Look up USB Device IDs here - -
-
+
+
handleUsbProduct(e.target.value)} /> -
-
- {error &&

{error}

} +
+
+
); } - -function SuccessModal({ - headline, - description, - onClose, -}: { - headline: string; - description: string; - onClose: () => void; -}) { - return ( -
-
- - -
-
-
-

{headline}

-

{description}

-
-
-
- ); -} \ No newline at end of file diff --git a/ui/src/components/sidebar/settings.tsx b/ui/src/components/sidebar/settings.tsx deleted file mode 100644 index fb19f89..0000000 --- a/ui/src/components/sidebar/settings.tsx +++ /dev/null @@ -1,1254 +0,0 @@ -import SidebarHeader from "@components/SidebarHeader"; -import { - BacklightSettings, - useLocalAuthModalStore, - useSettingsStore, - useUiStore, - useUpdateStore, - useUsbConfigModalStore -} from "@/hooks/stores"; -import { Checkbox } from "@components/Checkbox"; -import { Button, LinkButton } from "@components/Button"; -import { TextAreaWithLabel } from "@components/TextArea"; -import { SectionHeader } from "@components/SectionHeader"; -import { GridCard } from "@components/Card"; -import { CheckCircleIcon } from "@heroicons/react/20/solid"; -import { cx } from "@/cva.config"; -import React, {useCallback, useEffect, useMemo, useRef, useState} from "react"; -import { isOnDevice } from "@/main"; -import PointingFinger from "@/assets/pointing-finger.svg"; -import MouseIcon from "@/assets/mouse-icon.svg"; -import { useJsonRpc } from "@/hooks/useJsonRpc"; -import { SelectMenuBasic } from "../SelectMenuBasic"; -import { SystemVersionInfo } from "@components/UpdateDialog"; -import notifications from "@/notifications"; -import api from "../../api"; -import LocalAuthPasswordDialog from "@/components/LocalAuthPasswordDialog"; -import { LocalDevice } from "@routes/devices.$id"; -import { useRevalidator } from "react-router-dom"; -import { ShieldCheckIcon } from "@heroicons/react/20/solid"; -import USBConfigDialog from "@components/USBConfigDialog"; -import { UsbConfigState } from "@/hooks/stores" -import { CLOUD_APP, DEVICE_API } from "@/ui.config"; -import { InputFieldWithLabel } from "../InputField"; - -export function SettingsItem({ - title, - description, - children, - className, -}: { - title: string; - description: string | React.ReactNode; - children?: React.ReactNode; - className?: string; - name?: string; -}) { - return ( - - ); -} - - -const generatedSerialNumber = [generateNumber(1,9), generateHex(7,7), 0, 1].join("&"); - -function generateNumber(min: number, max: number) { - return Math.floor(Math.random() * (max - min + 1) + min); -} - -function generateHex(min: number, max: number) { - const len = generateNumber(min, max); - const n = (Math.random() * 0xfffff * 1000000).toString(16); - return n.slice(0, len); -} - -const defaultEdid = - "00ffffffffffff0052620188008888881c150103800000780a0dc9a05747982712484c00000001010101010101010101010101010101023a801871382d40582c4500c48e2100001e011d007251d01e206e285500c48e2100001e000000fc00543734392d6648443732300a20000000fd00147801ff1d000a202020202020017b"; -const edids = [ - { - value: defaultEdid, - label: "JetKVM Default", - }, - { - value: - "00FFFFFFFFFFFF00047265058A3F6101101E0104A53420783FC125A8554EA0260D5054BFEF80714F8140818081C081008B009500B300283C80A070B023403020360006442100001A000000FD00304C575716010A202020202020000000FC0042323436574C0A202020202020000000FF0054384E4545303033383532320A01F802031CF14F90020304050607011112131415161F2309070783010000011D8018711C1620582C250006442100009E011D007251D01E206E28550006442100001E8C0AD08A20E02D10103E9600064421000018C344806E70B028401720A80406442100001E00000000000000000000000000000000000000000000000000000096", - label: "Acer B246WL, 1920x1200", - }, - { - value: - "00FFFFFFFFFFFF0006B3872401010101021F010380342078EA6DB5A7564EA0250D5054BF6F00714F8180814081C0A9409500B300D1C0283C80A070B023403020360006442100001A000000FD00314B1E5F19000A202020202020000000FC00504132343851560A2020202020000000FF004D314C4D51533035323135370A014D02032AF14B900504030201111213141F230907078301000065030C001000681A00000101314BE6E2006A023A801871382D40582C450006442100001ECD5F80B072B0374088D0360006442100001C011D007251D01E206E28550006442100001E8C0AD08A20E02D10103E960006442100001800000000000000000000000000DC", - label: "ASUS PA248QV, 1920x1200", - }, - { - value: - "00FFFFFFFFFFFF0010AC132045393639201E0103803C22782ACD25A3574B9F270D5054A54B00714F8180A9C0D1C00101010101010101023A801871382D40582C450056502100001E000000FF00335335475132330A2020202020000000FC0044454C4C204432373231480A20000000FD00384C1E5311000A202020202020018102031AB14F90050403020716010611121513141F65030C001000023A801871382D40582C450056502100001E011D8018711C1620582C250056502100009E011D007251D01E206E28550056502100001E8C0AD08A20E02D10103E960056502100001800000000000000000000000000000000000000000000000000000000004F", - label: "DELL D2721H, 1920x1080", - }, -]; - -export default function SettingsSidebar() { - const setSidebarView = useUiStore(state => state.setSidebarView); - const settings = useSettingsStore(); - const [send] = useJsonRpc(); - const [streamQuality, setStreamQuality] = useState("1"); - const [autoUpdate, setAutoUpdate] = useState(true); - const [devChannel, setDevChannel] = useState(false); - const [jiggler, setJiggler] = useState(false); - const [edid, setEdid] = useState(null); - const [customEdidValue, setCustomEdidValue] = useState(null); - const [usbConfigProduct, setUsbConfigProduct] = useState(""); - - const [isAdopted, setAdopted] = useState(false); - const [deviceId, setDeviceId] = useState(null); - - const [sshKey, setSSHKey] = useState(""); - const [localDevice, setLocalDevice] = useState(null); - - const sidebarRef = useRef(null); - - const hideCursor = useSettingsStore(state => state.isCursorHidden); - const setHideCursor = useSettingsStore(state => state.setCursorVisibility); - const setDeveloperMode = useSettingsStore(state => state.setDeveloperMode); - const setBacklightSettings = useSettingsStore(state => state.setBacklightSettings); - - const [currentVersions, setCurrentVersions] = useState<{ - appVersion: string; - systemVersion: string; - } | null>(null); - - const [usbEmulationEnabled, setUsbEmulationEnabled] = useState(false); - const getUsbEmulationState = useCallback(() => { - send("getUsbEmulationState", {}, resp => { - if ("error" in resp) return; - setUsbEmulationEnabled(resp.result as boolean); - }); - }, [send]); - - const usbConfigs = useMemo(() => [ - { - label: "JetKVM Default", - value: "JetKVM USB Emulation Device" - }, - { - label: "Logitech Universal Adapter", - value: "Logitech USB Input Device" - }, - { - label: "Microsoft Wireless MultiMedia Keyboard", - value: "Wireless MultiMedia Keyboard" - }, - { - label: "Dell Multimedia Pro Keyboard", - value: "Multimedia Pro Keyboard" - } - ], []); - - - interface USBConfig { - vendor_id: string; - product_id: string; - serial_number: string | null; - manufacturer: string; - product: string; - } - - type UsbConfigMap = Record; - - - const usbConfigData: UsbConfigMap = { - "JetKVM USB Emulation Device": { - vendor_id: "0x1d6b", - product_id: "0x0104", - serial_number: deviceId, - manufacturer: "JetKVM", - product: "JetKVM USB Emulation Device", - }, - "Logitech USB Input Device": { - vendor_id: "0x046d", - product_id: "0xc52b", - serial_number: generatedSerialNumber, - manufacturer: "Logitech (x64)", - product: "Logitech USB Input Device", - }, - "Wireless MultiMedia Keyboard": { - vendor_id: "0x045e", - product_id: "0x005f", - serial_number: generatedSerialNumber, - manufacturer: "Microsoft", - product: "Wireless MultiMedia Keyboard", - }, - "Multimedia Pro Keyboard": { - vendor_id: "0x413c", - product_id: "0x2011", - serial_number: generatedSerialNumber, - manufacturer: "Dell Inc.", - product: "Multimedia Pro Keyboard", - } - } - - const syncUsbConfigProduct = useCallback(() => { - send("getUsbConfig", {}, resp => { - if ("error" in resp) { - console.error("Failed to load USB Config:", resp.error); - } else { - console.log("syncUsbConfigProduct#getUsbConfig result:", resp.result); - const usbConfigState = resp.result as UsbConfigState - const product = usbConfigs.map(u => u.value).includes(usbConfigState.product) ? usbConfigState.product : "custom" - setUsbConfigProduct(product); - } - }); - }, [send, usbConfigs]); - - // Load stored usb config product from the backend - useEffect(() => { - syncUsbConfigProduct(); - }, [syncUsbConfigProduct]); - - const handleUsbEmulationToggle = useCallback( - (enabled: boolean) => { - send("setUsbEmulationState", { enabled: enabled }, resp => { - if ("error" in resp) { - notifications.error( - `Failed to ${enabled ? "enable" : "disable"} USB emulation: ${resp.error.data || "Unknown error"}`, - ); - return; - } - setUsbEmulationEnabled(enabled); - getUsbEmulationState(); - }); - }, - [getUsbEmulationState, send], - ); - - const getCloudState = useCallback(() => { - send("getCloudState", {}, resp => { - if ("error" in resp) return console.error(resp.error); - const cloudState = resp.result as { connected: boolean }; - setAdopted(cloudState.connected); - }); - }, [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(); - return; - }); - }; - - const handleStreamQualityChange = (factor: string) => { - send("setStreamQualityFactor", { factor: Number(factor) }, resp => { - if ("error" in resp) { - notifications.error( - `Failed to set stream quality: ${resp.error.data || "Unknown error"}`, - ); - return; - } - setStreamQuality(factor); - }); - }; - - const handleAutoUpdateChange = (enabled: boolean) => { - send("setAutoUpdateState", { enabled }, resp => { - if ("error" in resp) { - notifications.error( - `Failed to set auto-update: ${resp.error.data || "Unknown error"}`, - ); - return; - } - setAutoUpdate(enabled); - }); - }; - - const handleDevChannelChange = (enabled: boolean) => { - send("setDevChannelState", { enabled }, resp => { - if ("error" in resp) { - notifications.error( - `Failed to set dev channel state: ${resp.error.data || "Unknown error"}`, - ); - return; - } - setDevChannel(enabled); - }); - }; - - const handleUsbConfigChange = (product: string) => { - const usbConfig = usbConfigData[product]; - console.info(`USB config: ${JSON.stringify(usbConfig)}`) - send("setUsbConfig", { usbConfig }, resp => { - if ("error" in resp) { - notifications.error( - `Failed to set usb config: ${resp.error.data || "Unknown error"}`, - ); - return; - } - setUsbConfigProduct(usbConfig.product); - notifications.success(`USB Config set to ${usbConfig.manufacturer} ${usbConfig.product}`); - }); - }; - - const handleJigglerChange = (enabled: boolean) => { - send("setJigglerState", { enabled }, resp => { - if ("error" in resp) { - notifications.error( - `Failed to set jiggler state: ${resp.error.data || "Unknown error"}`, - ); - return; - } - setJiggler(enabled); - }); - }; - - const handleEDIDChange = (newEdid: string) => { - send("setEDID", { edid: newEdid }, resp => { - if ("error" in resp) { - notifications.error(`Failed to set EDID: ${resp.error.data || "Unknown error"}`); - return; - } - - // Update the EDID value in the UI - setEdid(newEdid); - }); - }; - - const handleSSHKeyChange = (newKey: string) => { - setSSHKey(newKey); - }; - - const handleDevModeChange = useCallback( - (developerMode: boolean) => { - send("setDevModeState", { enabled: developerMode }, resp => { - if ("error" in resp) { - notifications.error( - `Failed to set dev mode: ${resp.error.data || "Unknown error"}`, - ); - return; - } - setDeveloperMode(developerMode); - setTimeout(() => { - sidebarRef.current?.scrollTo({ top: 5000, behavior: "smooth" }); - }, 0); - }); - }, - [send, setDeveloperMode], - ); - - const handleBacklightSettingsChange = (settings: BacklightSettings) => { - // If the user has set the display to dim after it turns off, set the dim_after - // value to never. - if (settings.dim_after > settings.off_after && settings.off_after != 0) { - settings.dim_after = 0; - } - - setBacklightSettings(settings); - handleBacklightSettingsSave(); - } - - const handleBacklightSettingsSave = () => { - send("setBacklightSettings", { params: settings.backlightSettings }, resp => { - if ("error" in resp) { - notifications.error( - `Failed to set backlight settings: ${resp.error.data || "Unknown error"}`, - ); - return; - } - notifications.success("Backlight settings updated successfully"); - }); - }; - - const handleUpdateSSHKey = useCallback(() => { - send("setSSHKeyState", { sshKey }, resp => { - if ("error" in resp) { - notifications.error( - `Failed to update SSH key: ${resp.error.data || "Unknown error"}`, - ); - return; - } - notifications.success("SSH key updated successfully"); - }); - }, [send, sshKey]); - - const { setIsUpdateDialogOpen, setModalView, otaState } = useUpdateStore(); - const handleCheckForUpdates = () => { - if (otaState.updating) { - setModalView("updating"); - setIsUpdateDialogOpen(true); - } else { - setModalView("loading"); - setIsUpdateDialogOpen(true); - } - }; - - const [cloudUrl, setCloudUrl] = useState(""); - - useEffect(() => { - send("getCloudUrl", {}, resp => { - if ("error" in resp) return; - setCloudUrl(resp.result as string); - }); - }, [send]); - - const getCloudUrl = useCallback(() => { - send("getCloudUrl", {}, resp => { - if ("error" in resp) return; - setCloudUrl(resp.result as string); - }); - }, [send]); - - const handleCloudUrlChange = useCallback( - (url: string) => { - send("setCloudUrl", { url }, resp => { - if ("error" in resp) { - notifications.error( - `Failed to update cloud URL: ${resp.error.data || "Unknown error"}`, - ); - return; - } - getCloudUrl(); - notifications.success("Cloud URL updated successfully"); - }); - }, - [send, getCloudUrl], - ); - - const handleResetCloudUrl = useCallback(() => { - send("resetCloudUrl", {}, resp => { - if ("error" in resp) { - notifications.error( - `Failed to reset cloud URL: ${resp.error.data || "Unknown error"}`, - ); - return; - } - getCloudUrl(); - notifications.success("Cloud URL reset to default successfully"); - }); - }, [send, getCloudUrl]); - - useEffect(() => { - getCloudState(); - - send("getDeviceID", {}, async resp => { - if ("error" in resp) return console.error(resp.error); - setDeviceId(resp.result as string); - }); - - send("getJigglerState", {}, resp => { - if ("error" in resp) return; - setJiggler(resp.result as boolean); - }); - - send("getAutoUpdateState", {}, resp => { - if ("error" in resp) return; - setAutoUpdate(resp.result as boolean); - }); - - send("getDevChannelState", {}, resp => { - if ("error" in resp) return; - setDevChannel(resp.result as boolean); - }); - - send("getStreamQualityFactor", {}, resp => { - if ("error" in resp) return; - setStreamQuality(String(resp.result)); - }); - - send("getEDID", {}, resp => { - if ("error" in resp) { - notifications.error(`Failed to get EDID: ${resp.error.data || "Unknown error"}`); - return; - } - - const receivedEdid = resp.result as string; - - const matchingEdid = edids.find( - x => x.value.toLowerCase() === receivedEdid.toLowerCase(), - ); - - if (matchingEdid) { - // EDID is stored in uppercase in the UI - setEdid(matchingEdid.value.toUpperCase()); - // Reset custom EDID value - setCustomEdidValue(null); - } else { - setEdid("custom"); - setCustomEdidValue(receivedEdid); - } - }); - - send("getBacklightSettings", {}, resp => { - if ("error" in resp) { - notifications.error( - `Failed to get backlight settings: ${resp.error.data || "Unknown error"}`, - ); - return; - } - const result = resp.result as BacklightSettings; - setBacklightSettings(result); - }) - - send("getDevModeState", {}, resp => { - if ("error" in resp) return; - const result = resp.result as { enabled: boolean }; - setDeveloperMode(result.enabled); - }); - - send("getSSHKeyState", {}, resp => { - if ("error" in resp) return; - setSSHKey(resp.result as string); - }); - - send("getUpdateStatus", {}, resp => { - if ("error" in resp) return; - const result = resp.result as SystemVersionInfo; - setCurrentVersions({ - appVersion: result.local.appVersion, - systemVersion: result.local.systemVersion, - }); - }); - - send("getUsbEmulationState", {}, resp => { - if ("error" in resp) return; - setUsbEmulationEnabled(resp.result as boolean); - }); - }, [ - getCloudState, - send, - setBacklightSettings, - setDeveloperMode, - setHideCursor, - setJiggler, - ]); - - const getDevice = useCallback(async () => { - try { - const status = await api - .GET(`${DEVICE_API}/device`) - .then(res => res.json() as Promise); - setLocalDevice(status); - } catch (error) { - notifications.error("Failed to get authentication status"); - } - }, []); - - const { setModalView: setLocalAuthModalView } = useLocalAuthModalStore(); - const { setModalView: setUsbConfigModalView } = useUsbConfigModalStore(); - const [isLocalAuthDialogOpen, setIsLocalAuthDialogOpen] = useState(false); - const [isUsbConfigDialogOpen, setIsUsbConfigDialogOpen] = useState(false); - - useEffect(() => { - if (isOnDevice) getDevice(); - }, [getDevice]); - - useEffect(() => { - if (!isOnDevice) return; - // Refresh device status when the local auth dialog is closed - if (!isLocalAuthDialogOpen) { - getDevice(); - } - }, [getDevice, isLocalAuthDialogOpen]); - - useEffect(() => { - if (!isOnDevice) return; - // Refresh device status when the local usb config dialog is closed - if (!isUsbConfigDialogOpen) { - getDevice(); - } - }, [getDevice, isUsbConfigDialogOpen]); - - const revalidator = useRevalidator(); - - const [currentTheme, setCurrentTheme] = useState(() => { - return localStorage.theme || "system"; - }); - - const handleThemeChange = useCallback((value: string) => { - const root = document.documentElement; - - if (value === "system") { - localStorage.removeItem("theme"); - // Check system preference - const systemTheme = window.matchMedia("(prefers-color-scheme: dark)").matches - ? "dark" - : "light"; - root.classList.remove("light", "dark"); - root.classList.add(systemTheme); - } else { - localStorage.theme = value; - root.classList.remove("light", "dark"); - root.classList.add(value); - } - }, []); - - const handleResetConfig = useCallback(() => { - send("resetConfig", {}, resp => { - if ("error" in resp) { - notifications.error( - `Failed to reset configuration: ${resp.error.data || "Unknown error"}`, - ); - return; - } - notifications.success("Configuration reset to default successfully"); - }); - }, [send]); - - return ( -
e.stopPropagation()} - onKeyUp={e => e.stopPropagation()} - > - -
-
-
- - App: {currentVersions.appVersion} -
- System: {currentVersions.systemVersion} - - ) : ( - "Loading current versions..." - ) - } - /> -
-
-
-
- - -
- - { - setHideCursor(e.target.checked); - }} - /> - - - { - handleJigglerChange(e.target.checked); - }} - /> - -
- -
- - -
-
-
-
-
-
- -
- - handleStreamQualityChange(e.target.value)} - /> - - - { - if (e.target.value === "custom") { - setEdid("custom"); - setCustomEdidValue(""); - } else { - handleEDIDChange(e.target.value as string); - } - }} - options={[...edids, { value: "custom", label: "Custom" }]} - /> - - {customEdidValue !== null && ( - <> - - setCustomEdidValue(e.target.value)} - /> -
-
- - )} -
-
- {isOnDevice && ( - <> -
-
- - - -
- -
-
-

- 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 -

-
-
-
-
- )} -
- - )} -
- {isOnDevice ? ( - <> -
- - -
- - {localDevice?.authMode === "password" ? ( -
-
-
- - ) : null} -
- - -
- - { - handleAutoUpdateChange(e.target.checked); - }} - /> - - - { - handleDevChannelChange(e.target.checked); - }} - /> - -
-
-
- - - - { - setCurrentTheme(e.target.value); - handleThemeChange(e.target.value); - }} - /> - -
-
- -
- - { - settings.backlightSettings.max_brightness = parseInt(e.target.value) - handleBacklightSettingsChange(settings.backlightSettings); - }} - /> - - {settings.backlightSettings.max_brightness != 0 && ( - <> - - { - settings.backlightSettings.dim_after = parseInt(e.target.value) - handleBacklightSettingsChange(settings.backlightSettings); - }} - /> - - - { - settings.backlightSettings.off_after = parseInt(e.target.value) - handleBacklightSettingsChange(settings.backlightSettings); - }} - /> - - - )} -

- The display will wake up when the connection state changes, or when touched. -

- - { - if (e.target.value === "custom") { - setUsbConfigProduct(e.target.value); - } else { - handleUsbConfigChange(e.target.value as string); - } - }} - options={[...usbConfigs, { value: "custom", label: "Custom" }]} - /> - - {(usbConfigProduct === "custom") && ( - -
-
- {isOnDevice && ( -
- - - setCloudUrl(e.target.value)} - placeholder="https://api.jetkvm.com" - /> - -
-
-
- )} -
- )} - - { - settings.setDebugMode(e.target.checked); - }} - /> - - {settings.debugMode && ( - <> - -
-
-
- { - // Revalidate the current route to refresh the local device status and dependent UI components - revalidator.revalidate(); - setIsUsbConfigDialogOpen(x); - }} - /> - { - // Revalidate the current route to refresh the local device status and dependent UI components - revalidator.revalidate(); - setIsLocalAuthDialogOpen(x); - }} - /> -
- ); -} diff --git a/ui/src/hooks/useJsonRpc.ts b/ui/src/hooks/useJsonRpc.ts index da53e04..f3390f7 100644 --- a/ui/src/hooks/useJsonRpc.ts +++ b/ui/src/hooks/useJsonRpc.ts @@ -8,17 +8,25 @@ export interface JsonRpcRequest { id: number | string; } -type JsonRpcResponse = - | { - jsonrpc: string; - result: boolean | number | object | string | []; - id: string | number; - } - | { - jsonrpc: string; - error: { code: number; data?: string; message: string }; - id: string | number; - }; +export interface JsonRpcError { + code: number; + data?: string; + message: string; +} + +export interface JsonRpcSuccessResponse { + jsonrpc: string; + result: boolean | number | object | string | []; + id: string | number; +} + +export interface JsonRpcErrorResponse { + jsonrpc: string; + error: JsonRpcError; + id: string | number; +} + +export type JsonRpcResponse = JsonRpcSuccessResponse | JsonRpcErrorResponse; const callbackStore = new Map void>(); let requestCounter = 0; diff --git a/ui/src/routes/devices.$id.settings.advanced.tsx b/ui/src/routes/devices.$id.settings.advanced.tsx index 71ae244..ae8996e 100644 --- a/ui/src/routes/devices.$id.settings.advanced.tsx +++ b/ui/src/routes/devices.$id.settings.advanced.tsx @@ -11,7 +11,7 @@ import { isOnDevice } from "../main"; import { InputFieldWithLabel } from "../components/InputField"; import { Button } from "../components/Button"; import { useSettingsStore } from "../hooks/stores"; -import { GridCard } from "@/components/Card"; +import { GridCard } from "@components/Card"; export default function SettingsAdvancedRoute() { const [send] = useJsonRpc(); @@ -152,6 +152,52 @@ export default function SettingsAdvancedRoute() { />
+ + { + settings.setDebugMode(e.target.checked); + }} + /> + + + {settings.debugMode && ( + <> + +
+
+ )} + {isOnDevice && settings.developerMode && ( +
+
- {isOnDevice && ( -
- - - setCloudUrl(e.target.value)} - placeholder="https://api.jetkvm.com" - /> - -
-
-
- )}
)} - - { - settings.setDebugMode(e.target.checked); - }} - /> - - - {settings.debugMode && ( - <> - -
); diff --git a/ui/src/routes/devices.$id.settings.general._index.tsx b/ui/src/routes/devices.$id.settings.general._index.tsx index 3ad3756..7c7b61c 100644 --- a/ui/src/routes/devices.$id.settings.general._index.tsx +++ b/ui/src/routes/devices.$id.settings.general._index.tsx @@ -169,101 +169,105 @@ export default function SettingsGeneralRoute() { {isOnDevice && ( -
- + <> +
+ +
+ + + +
+ +
+
+

+ 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 + + . +
+
+
- -
- -
-
-

- 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 -

-
-
-
- )} -
+ )} +
+ )}
diff --git a/ui/src/routes/devices.$id.settings.hardware.tsx b/ui/src/routes/devices.$id.settings.hardware.tsx index d87813d..44c7848 100644 --- a/ui/src/routes/devices.$id.settings.hardware.tsx +++ b/ui/src/routes/devices.$id.settings.hardware.tsx @@ -1,17 +1,97 @@ -import { SectionHeader } from "../components/SectionHeader"; - -import { SettingsItem } from "./devices.$id.settings"; -import { BacklightSettings, useSettingsStore } from "../hooks/stores"; -import { useEffect } from "react"; +import { SectionHeader } from "@components/SectionHeader"; +import { SettingsItem } from "@routes/devices.$id.settings"; +import { BacklightSettings, UsbConfigState, useSettingsStore } from "@/hooks/stores"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { useJsonRpc } from "@/hooks/useJsonRpc"; import notifications from "../notifications"; -import { SelectMenuBasic } from "../components/SelectMenuBasic"; +import { SelectMenuBasic } from "@components/SelectMenuBasic"; +import USBConfigDialog from "@components/USBConfigDialog"; + +const generatedSerialNumber = [generateNumber(1, 9), generateHex(7, 7), 0, 1].join("&"); + +function generateNumber(min: number, max: number) { + return Math.floor(Math.random() * (max - min + 1) + min); +} + +function generateHex(min: number, max: number) { + const len = generateNumber(min, max); + const n = (Math.random() * 0xfffff * 1000000).toString(16); + return n.slice(0, len); +} + +export interface USBConfig { + vendor_id: string; + product_id: string; + serial_number: string; + manufacturer: string; + product: string; +} + +const usbConfigs = [ + { + label: "JetKVM Default", + value: "USB Emulation Device", + }, + { + label: "Logitech Universal Adapter", + value: "Logitech USB Input Device", + }, + { + label: "Microsoft Wireless MultiMedia Keyboard", + value: "Wireless MultiMedia Keyboard", + }, + { + label: "Dell Multimedia Pro Keyboard", + value: "Multimedia Pro Keyboard", + }, +]; + +type UsbConfigMap = Record; export default function SettingsHardwareRoute() { - const setBacklightSettings = useSettingsStore(state => state.setBacklightSettings); + const [send] = useJsonRpc(); const settings = useSettingsStore(); + const [usbConfigProduct, setUsbConfigProduct] = useState(""); + const [deviceId, setDeviceId] = useState(""); + + const setBacklightSettings = useSettingsStore(state => state.setBacklightSettings); + + const usbConfigData: UsbConfigMap = useMemo( + () => ({ + "USB Emulation Device": { + vendor_id: "0x1d6b", + product_id: "0x0104", + serial_number: deviceId, + manufacturer: "JetKVM", + product: "USB Emulation Device", + }, + "Logitech USB Input Device": { + vendor_id: "0x046d", + product_id: "0xc52b", + serial_number: generatedSerialNumber, + manufacturer: "Logitech (x64)", + product: "Logitech USB Input Device", + }, + "Wireless MultiMedia Keyboard": { + vendor_id: "0x045e", + product_id: "0x005f", + serial_number: generatedSerialNumber, + manufacturer: "Microsoft", + product: "Wireless MultiMedia Keyboard", + }, + "Multimedia Pro Keyboard": { + vendor_id: "0x413c", + product_id: "0x2011", + serial_number: generatedSerialNumber, + manufacturer: "Dell Inc.", + product: "Multimedia Pro Keyboard", + }, + }), + [deviceId], + ); + const handleBacklightSettingsChange = (settings: BacklightSettings) => { // If the user has set the display to dim after it turns off, set the dim_after // value to never. @@ -34,24 +114,72 @@ export default function SettingsHardwareRoute() { notifications.success("Backlight settings updated successfully"); }); }; - const [send] = useJsonRpc(); + const syncUsbConfigProduct = useCallback(() => { + send("getUsbConfig", {}, resp => { + if ("error" in resp) { + console.error("Failed to load USB Config:", resp.error); + notifications.error( + `Failed to load USB Config: ${resp.error.data || "Unknown error"}`, + ); + } else { + console.log("syncUsbConfigProduct#getUsbConfig result:", resp.result); + const usbConfigState = resp.result as UsbConfigState; + const product = usbConfigs.map(u => u.value).includes(usbConfigState.product) + ? usbConfigState.product + : "custom"; + setUsbConfigProduct(product); + } + }); + }, [send]); + + const handleUsbConfigChange = useCallback( + (usbConfig: USBConfig) => { + send("setUsbConfig", { usbConfig }, resp => { + if ("error" in resp) { + notifications.error( + `Failed to set usb config: ${resp.error.data || "Unknown error"}`, + ); + return; + } + // setUsbConfigProduct(usbConfig.product); + notifications.success( + `USB Config set to ${usbConfig.manufacturer} ${usbConfig.product}`, + ); + syncUsbConfigProduct(); + }); + }, + [send, syncUsbConfigProduct], + ); useEffect(() => { send("getBacklightSettings", {}, resp => { if ("error" in resp) { - notifications.error( + return notifications.error( `Failed to get backlight settings: ${resp.error.data || "Unknown error"}`, ); - return; } const result = resp.result as BacklightSettings; setBacklightSettings(result); }); - }, [send, setBacklightSettings]); + + send("getDeviceID", {}, async resp => { + if ("error" in resp) { + return notifications.error( + `Failed to get device ID: ${resp.error.data || "Unknown error"}`, + ); + } + setDeviceId(resp.result as string); + }); + + syncUsbConfigProduct(); + }, [send, setBacklightSettings, syncUsbConfigProduct]); return (
- +
+ +
+ + + { + if (e.target.value === "custom") { + setUsbConfigProduct(e.target.value); + } else { + const usbConfig = usbConfigData[e.target.value]; + handleUsbConfigChange(usbConfig); + } + }} + options={[...usbConfigs, { value: "custom", label: "Custom" }]} + /> + + {usbConfigProduct === "custom" && ( + handleUsbConfigChange(usbConfig)} + onRestoreToDefault={() => + handleUsbConfigChange(usbConfigData[usbConfigs[0].value]) + } + /> + )}
); } diff --git a/ui/src/routes/devices.$id.settings.video.tsx b/ui/src/routes/devices.$id.settings.video.tsx index dd54851..a77ba61 100644 --- a/ui/src/routes/devices.$id.settings.video.tsx +++ b/ui/src/routes/devices.$id.settings.video.tsx @@ -160,13 +160,13 @@ export default function SettingsVideoRoute() { />