From 7361289b68d822d18ee459f9f65747920aac9fc5 Mon Sep 17 00:00:00 2001 From: Adrian Date: Thu, 23 Jan 2025 18:55:25 -0600 Subject: [PATCH 01/39] created feature branch --- config.go | 9 + display.go | 8 +- jsonrpc.go | 21 + ui/src/components/sidebar/settings.tsx | 1184 +++++++++++++----------- usb.go | 113 ++- 5 files changed, 758 insertions(+), 577 deletions(-) diff --git a/config.go b/config.go index 1636434..89c2715 100644 --- a/config.go +++ b/config.go @@ -11,6 +11,14 @@ type WakeOnLanDevice struct { MacAddress string `json:"macAddress"` } +type UsbConfig struct { + UsbVendorId string `json:"usb_vendor_id"` + UsbProductId string `json:"usb_product_id"` + UsbSerialNumber string `json:"usb_serial_number"` + UsbName string `json:"usb_name"` + UsbManufacturer string `json:"usb_manufacturer"` +} + type Config struct { CloudURL string `json:"cloud_url"` CloudToken string `json:"cloud_token"` @@ -22,6 +30,7 @@ type Config struct { LocalAuthToken string `json:"local_auth_token"` LocalAuthMode string `json:"localAuthMode"` //TODO: fix it with migration WakeOnLanDevices []WakeOnLanDevice `json:"wake_on_lan_devices"` + UsbConfig UsbConfig `json:"usb_config"` } const configPath = "/userdata/kvm_config.json" diff --git a/display.go b/display.go index f312eb6..701e3c6 100644 --- a/display.go +++ b/display.go @@ -36,17 +36,17 @@ func switchToScreenIfDifferent(screenName string) { func updateDisplay() { updateLabelIfChanged("ui_Home_Content_Ip", networkState.IPv4) if usbState == "configured" { - updateLabelIfChanged("ui_Home_Footer_Usb_Status_Label", "Connected") + updateLabelIfChanged("ui_Home_Footer_Usb_Status_Label", "Connected*") _, _ = CallCtrlAction("lv_obj_set_state", map[string]interface{}{"obj": "ui_Home_Footer_Usb_Status_Label", "state": "LV_STATE_DEFAULT"}) } else { - updateLabelIfChanged("ui_Home_Footer_Usb_Status_Label", "Disconnected") + updateLabelIfChanged("ui_Home_Footer_Usb_Status_Label", "Disconnected*") _, _ = CallCtrlAction("lv_obj_set_state", map[string]interface{}{"obj": "ui_Home_Footer_Usb_Status_Label", "state": "LV_STATE_USER_2"}) } if lastVideoState.Ready { - updateLabelIfChanged("ui_Home_Footer_Hdmi_Status_Label", "Connected") + updateLabelIfChanged("ui_Home_Footer_Hdmi_Status_Label", "Connected*") _, _ = CallCtrlAction("lv_obj_set_state", map[string]interface{}{"obj": "ui_Home_Footer_Hdmi_Status_Label", "state": "LV_STATE_DEFAULT"}) } else { - updateLabelIfChanged("ui_Home_Footer_Hdmi_Status_Label", "Disconnected") + updateLabelIfChanged("ui_Home_Footer_Hdmi_Status_Label", "Disconnected*") _, _ = CallCtrlAction("lv_obj_set_state", map[string]interface{}{"obj": "ui_Home_Footer_Hdmi_Status_Label", "state": "LV_STATE_USER_2"}) } updateLabelIfChanged("ui_Home_Header_Cloud_Status_Label", fmt.Sprintf("%d active", actionSessions)) diff --git a/jsonrpc.go b/jsonrpc.go index 2ce5f18..5885d70 100644 --- a/jsonrpc.go +++ b/jsonrpc.go @@ -478,6 +478,26 @@ func rpcSetUsbEmulationState(enabled bool) error { } } +func rpcSetUsbConfig(usbConfig UsbConfig) error { + log.Printf("[jsonrpc.go:rpcSetUsbConfig] called") + + LoadConfig() + config.UsbConfig = usbConfig + + err2 := UpdateGadgetConfig() + if err2 != nil { + return fmt.Errorf("failed to write gadget config: %w", err2) + } + + err := SaveConfig() + if err != nil { + return fmt.Errorf("failed to save usb config: %w", err) + } + + log.Printf("[jsonrpc.go:rpcSetUsbConfig] usb config set to %s", usbConfig) + return nil +} + func rpcGetWakeOnLanDevices() ([]WakeOnLanDevice, error) { LoadConfig() if config.WakeOnLanDevices == nil { @@ -542,6 +562,7 @@ var rpcHandlers = map[string]RPCHandler{ "isUpdatePending": {Func: rpcIsUpdatePending}, "getUsbEmulationState": {Func: rpcGetUsbEmulationState}, "setUsbEmulationState": {Func: rpcSetUsbEmulationState, Params: []string{"enabled"}}, + "setUsbConfig": {Func: rpcSetUsbConfig, Params: []string{"usbConfig"}}, "checkMountUrl": {Func: rpcCheckMountUrl, Params: []string{"url"}}, "getVirtualMediaState": {Func: rpcGetVirtualMediaState}, "getStorageSpace": {Func: rpcGetStorageSpace}, diff --git a/ui/src/components/sidebar/settings.tsx b/ui/src/components/sidebar/settings.tsx index ec606a6..8f5d962 100644 --- a/ui/src/components/sidebar/settings.tsx +++ b/ui/src/components/sidebar/settings.tsx @@ -10,6 +10,7 @@ import { Button, LinkButton } from "@components/Button"; import { TextAreaWithLabel } from "@components/TextArea"; import { SectionHeader } from "@components/SectionHeader"; import { GridCard } from "@components/Card"; +import { InputFieldWithLabel } from "@components/InputField"; import { CheckCircleIcon } from "@heroicons/react/20/solid"; import { cx } from "@/cva.config"; import React, { useCallback, useEffect, useRef, useState } from "react"; @@ -27,11 +28,11 @@ import { useRevalidator } from "react-router-dom"; import { ShieldCheckIcon } from "@heroicons/react/20/solid"; export function SettingsItem({ - title, - description, - children, - className, -}: { + title, + description, + children, + className, + }: { title: string; description: string | React.ReactNode; children?: React.ReactNode; @@ -39,18 +40,18 @@ export function SettingsItem({ name?: string; }) { return ( - + ); } const defaultEdid = - "00ffffffffffff0052620188008888881c150103800000780a0dc9a05747982712484c00000001010101010101010101010101010101023a801871382d40582c4500c48e2100001e011d007251d01e206e285500c48e2100001e000000fc00543734392d6648443732300a20000000fd00147801ff1d000a202020202020017b"; + "00ffffffffffff0052620188008888881c150103800000780a0dc9a05747982712484c00000001010101010101010101010101010101023a801871382d40582c4500c48e2100001e011d007251d01e206e285500c48e2100001e000000fc00543734392d6648443732300a20000000fd00147801ff1d000a202020202020017b"; const edids = [ { value: defaultEdid, @@ -58,17 +59,17 @@ const edids = [ }, { value: - "00FFFFFFFFFFFF00047265058A3F6101101E0104A53420783FC125A8554EA0260D5054BFEF80714F8140818081C081008B009500B300283C80A070B023403020360006442100001A000000FD00304C575716010A202020202020000000FC0042323436574C0A202020202020000000FF0054384E4545303033383532320A01F802031CF14F90020304050607011112131415161F2309070783010000011D8018711C1620582C250006442100009E011D007251D01E206E28550006442100001E8C0AD08A20E02D10103E9600064421000018C344806E70B028401720A80406442100001E00000000000000000000000000000000000000000000000000000096", + "00FFFFFFFFFFFF00047265058A3F6101101E0104A53420783FC125A8554EA0260D5054BFEF80714F8140818081C081008B009500B300283C80A070B023403020360006442100001A000000FD00304C575716010A202020202020000000FC0042323436574C0A202020202020000000FF0054384E4545303033383532320A01F802031CF14F90020304050607011112131415161F2309070783010000011D8018711C1620582C250006442100009E011D007251D01E206E28550006442100001E8C0AD08A20E02D10103E9600064421000018C344806E70B028401720A80406442100001E00000000000000000000000000000000000000000000000000000096", label: "Acer B246WL, 1920x1200", }, { value: - "00FFFFFFFFFFFF0006B3872401010101021F010380342078EA6DB5A7564EA0250D5054BF6F00714F8180814081C0A9409500B300D1C0283C80A070B023403020360006442100001A000000FD00314B1E5F19000A202020202020000000FC00504132343851560A2020202020000000FF004D314C4D51533035323135370A014D02032AF14B900504030201111213141F230907078301000065030C001000681A00000101314BE6E2006A023A801871382D40582C450006442100001ECD5F80B072B0374088D0360006442100001C011D007251D01E206E28550006442100001E8C0AD08A20E02D10103E960006442100001800000000000000000000000000DC", + "00FFFFFFFFFFFF0006B3872401010101021F010380342078EA6DB5A7564EA0250D5054BF6F00714F8180814081C0A9409500B300D1C0283C80A070B023403020360006442100001A000000FD00314B1E5F19000A202020202020000000FC00504132343851560A2020202020000000FF004D314C4D51533035323135370A014D02032AF14B900504030201111213141F230907078301000065030C001000681A00000101314BE6E2006A023A801871382D40582C450006442100001ECD5F80B072B0374088D0360006442100001C011D007251D01E206E28550006442100001E8C0AD08A20E02D10103E960006442100001800000000000000000000000000DC", label: "ASUS PA248QV, 1920x1200", }, { value: - "00FFFFFFFFFFFF0010AC132045393639201E0103803C22782ACD25A3574B9F270D5054A54B00714F8180A9C0D1C00101010101010101023A801871382D40582C450056502100001E000000FF00335335475132330A2020202020000000FC0044454C4C204432373231480A20000000FD00384C1E5311000A202020202020018102031AB14F90050403020716010611121513141F65030C001000023A801871382D40582C450056502100001E011D8018711C1620582C250056502100009E011D007251D01E206E28550056502100001E8C0AD08A20E02D10103E960056502100001800000000000000000000000000000000000000000000000000000000004F", + "00FFFFFFFFFFFF0010AC132045393639201E0103803C22782ACD25A3574B9F270D5054A54B00714F8180A9C0D1C00101010101010101023A801871382D40582C450056502100001E000000FF00335335475132330A2020202020000000FC0044454C4C204432373231480A20000000FD00384C1E5311000A202020202020018102031AB14F90050403020716010611121513141F65030C001000023A801871382D40582C450056502100001E011D8018711C1620582C250056502100009E011D007251D01E206E28550056502100001E8C0AD08A20E02D10103E960056502100001800000000000000000000000000000000000000000000000000000000004F", label: "DELL D2721H, 1920x1080", }, ]; @@ -102,6 +103,15 @@ export default function SettingsSidebar() { } | null>(null); const [usbEmulationEnabled, setUsbEmulationEnabled] = useState(false); + + const [usbConfig, setUsbConfig] = useState({ + usb_product_id: '', + usb_vendor_id: '', + usb_serial_number: '', + usb_manufacturer: '', + usb_name: '', + }) + const getUsbEmulationState = useCallback(() => { send("getUsbEmulationState", {}, resp => { if ("error" in resp) return; @@ -110,19 +120,19 @@ export default function SettingsSidebar() { }, [send]); 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], + (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(() => { @@ -137,7 +147,7 @@ export default function SettingsSidebar() { send("deregisterDevice", {}, resp => { if ("error" in resp) { notifications.error( - `Failed to de-register device: ${resp.error.data || "Unknown error"}`, + `Failed to de-register device: ${resp.error.data || "Unknown error"}`, ); return; } @@ -150,7 +160,7 @@ export default function SettingsSidebar() { send("setStreamQualityFactor", { factor: Number(factor) }, resp => { if ("error" in resp) { notifications.error( - `Failed to set stream quality: ${resp.error.data || "Unknown error"}`, + `Failed to set stream quality: ${resp.error.data || "Unknown error"}`, ); return; } @@ -162,7 +172,7 @@ export default function SettingsSidebar() { send("setAutoUpdateState", { enabled }, resp => { if ("error" in resp) { notifications.error( - `Failed to set auto-update: ${resp.error.data || "Unknown error"}`, + `Failed to set auto-update: ${resp.error.data || "Unknown error"}`, ); return; } @@ -174,7 +184,7 @@ export default function SettingsSidebar() { send("setDevChannelState", { enabled }, resp => { if ("error" in resp) { notifications.error( - `Failed to set dev channel state: ${resp.error.data || "Unknown error"}`, + `Failed to set dev channel state: ${resp.error.data || "Unknown error"}`, ); return; } @@ -186,7 +196,7 @@ export default function SettingsSidebar() { send("setJigglerState", { enabled }, resp => { if ("error" in resp) { notifications.error( - `Failed to set jiggler state: ${resp.error.data || "Unknown error"}`, + `Failed to set jiggler state: ${resp.error.data || "Unknown error"}`, ); return; } @@ -206,33 +216,44 @@ export default function SettingsSidebar() { }); }; + const handleUsbConfigChange = useCallback((usbConfig: object) => { + send("setUsbConfig", { usbConfig }, resp => { + if ("error" in resp) { + notifications.error( + `Failed to update USB Config: ${resp.error.data || "Unknown error"}`, + ); + return; + } + }); + }, [send, usbConfig]); + 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], + (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 handleUpdateSSHKey = useCallback(() => { send("setSSHKeyState", { sshKey }, resp => { if ("error" in resp) { notifications.error( - `Failed to update SSH key: ${resp.error.data || "Unknown error"}`, + `Failed to update SSH key: ${resp.error.data || "Unknown error"}`, ); return; } @@ -240,6 +261,26 @@ export default function SettingsSidebar() { }); }, [send, sshKey]); + const handleUsbProductIdChange = (productId: string) => { + setUsbConfig({... usbConfig, usb_product_id: productId}) + }; + + const handleUsbVendorIdChange = (vendorId: string) => { + setUsbConfig({... usbConfig, usb_vendor_id: vendorId}) + }; + + const handleUsbSerialChange = (serialNumber: string) => { + setUsbConfig({... usbConfig, usb_serial_number: serialNumber}) + }; + + const handleUsbName = (name: string) => { + setUsbConfig({... usbConfig, usb_name: name}) + }; + + const handleUsbManufacturer = (manufacturer: string) => { + setUsbConfig({... usbConfig, usb_manufacturer: manufacturer}) + }; + const { setIsUpdateDialogOpen, setModalView, otaState } = useUpdateStore(); const handleCheckForUpdates = () => { if (otaState.updating) { @@ -288,7 +329,7 @@ export default function SettingsSidebar() { const receivedEdid = resp.result as string; const matchingEdid = edids.find( - x => x.value.toLowerCase() === receivedEdid.toLowerCase(), + x => x.value.toLowerCase() === receivedEdid.toLowerCase(), ); if (matchingEdid) { @@ -331,8 +372,8 @@ export default function SettingsSidebar() { const getDevice = useCallback(async () => { try { const status = await api - .GET(`${import.meta.env.VITE_SIGNAL_API}/device`) - .then(res => res.json() as Promise); + .GET(`${import.meta.env.VITE_SIGNAL_API}/device`) + .then(res => res.json() as Promise); setLocalDevice(status); } catch (error) { notifications.error("Failed to get authentication status"); @@ -367,8 +408,8 @@ export default function SettingsSidebar() { localStorage.removeItem("theme"); // Check system preference const systemTheme = window.matchMedia("(prefers-color-scheme: dark)").matches - ? "dark" - : "light"; + ? "dark" + : "light"; root.classList.remove("light", "dark"); root.classList.add(systemTheme); } else { @@ -382,7 +423,7 @@ export default function SettingsSidebar() { send("resetConfig", {}, resp => { if ("error" in resp) { notifications.error( - `Failed to reset configuration: ${resp.error.data || "Unknown error"}`, + `Failed to reset configuration: ${resp.error.data || "Unknown error"}`, ); return; } @@ -391,509 +432,558 @@ export default function SettingsSidebar() { }, [send]); return ( -
e.stopPropagation()} - onKeyUp={e => e.stopPropagation()} - > -
e.stopPropagation()} + onKeyUp={e => e.stopPropagation()} > -
-
- - App: {currentVersions.appVersion} -
- System: {currentVersions.systemVersion} - - ) : ( - "Loading current versions..." - ) - } - /> -
-
+
+
+ + +
+ + { + setHideCursor(e.target.checked); + }} + /> + + + { + handleJigglerChange(e.target.checked); + }} + /> + +
+ +
+ + +
+
- - -
- - { - 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); - }} - /> - -
-
- - -
- - handleDevModeChange(e.target.checked)} - /> - - - {settings.developerMode && ( -
- handleSSHKeyChange(e.target.value)} - placeholder="Enter your SSH public key" - /> -

- The default SSH user is root. -

-
-
-
- )} - - { - settings.setDebugMode(e.target.checked); - }} - /> - - - {settings.debugMode && ( - <> - -
+ + )} +
+
+ {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); + }} + /> + +
+
+ + +
+ + handleDevModeChange(e.target.checked)} + /> + + + {settings.developerMode && ( +
+ handleSSHKeyChange(e.target.value)} + placeholder="Enter your SSH public key" + /> +

+ The default SSH user is root. +

+
+
+
+ )} + {settings.developerMode && ( +
+ handleUsbVendorIdChange(e.target.value)} + placeholder="Enter USB Vendor Id" + /> + handleUsbProductIdChange(e.target.value)} + placeholder="Enter USB Product Id" + /> + handleUsbSerialChange(e.target.value)} + placeholder="Enter USB Serial Number" + /> + handleUsbName(e.target.value)} + placeholder="Enter USB Name" + /> + handleUsbManufacturer(e.target.value)} + placeholder="Enter USB Manufacturer" + /> +
+
+
+ )} + + { + settings.setDebugMode(e.target.checked); + }} + /> + + + {settings.debugMode && ( + <> + +
+ { + // Revalidate the current route to refresh the local device status and dependent UI components + revalidator.revalidate(); + setIsLocalAuthDialogOpen(x); + }} + />
- { - // Revalidate the current route to refresh the local device status and dependent UI components - revalidator.revalidate(); - setIsLocalAuthDialogOpen(x); - }} - /> -
); -} +} \ No newline at end of file diff --git a/usb.go b/usb.go index 075409a..18bf874 100644 --- a/usb.go +++ b/usb.go @@ -3,6 +3,7 @@ package kvm import ( "errors" "fmt" + gadget "github.com/openstadia/go-usb-gadget" "log" "os" "os/exec" @@ -11,8 +12,6 @@ import ( "strings" "sync" "time" - - gadget "github.com/openstadia/go-usb-gadget" ) const configFSPath = "/sys/kernel/config" @@ -20,19 +19,6 @@ const gadgetPath = "/sys/kernel/config/usb_gadget" const kvmGadgetPath = "/sys/kernel/config/usb_gadget/jetkvm" const configC1Path = "/sys/kernel/config/usb_gadget/jetkvm/configs/c.1" -func mountConfigFS() error { - _, err := os.Stat(gadgetPath) - if os.IsNotExist(err) { - err = exec.Command("mount", "-t", "configfs", "none", configFSPath).Run() - if err != nil { - return fmt.Errorf("failed to mount configfs: %w", err) - } - } else { - return fmt.Errorf("unable to access usb gadget path: %w", err) - } - return nil -} - func init() { _ = os.MkdirAll(imagesFolder, 0755) udcs := gadget.GetUdcs() @@ -58,6 +44,60 @@ func init() { //TODO: read hid reports(capslock, numlock, etc) from keyboardHidFile } +func UpdateGadgetConfig() error { + LoadConfig() + gadgetAttrs := [][]string{ + {"idVendor", config.UsbConfig.UsbVendorId}, + {"idProduct", config.UsbConfig.UsbProductId}, + } + err := writeGadgetAttrs(kvmGadgetPath, gadgetAttrs) + if err != nil { + return err + } + + log.Printf("Successfully updated usb gadget attributes: %v", gadgetAttrs) + + strAttrs := [][]string{ + {"serialnumber", config.UsbConfig.UsbSerialNumber}, + {"manufacturer", config.UsbConfig.UsbManufacturer}, + {"product", config.UsbConfig.UsbName}, + } + gadgetStringsPath := filepath.Join(kvmGadgetPath, "strings", "0x409") + err = os.MkdirAll(gadgetStringsPath, 0755) + if err != nil { + return err + } + err = writeGadgetAttrs(gadgetStringsPath, strAttrs) + if err != nil { + return err + } + + log.Printf("Successfully updated string attributes: %s", strAttrs) + + err = rebindUsb() + if err != nil { + return err + } + + return nil +} + +func mountConfigFS() error { + logger.Infof("Checking for gadgetPath: %s ...", gadgetPath) + _, err := os.Stat(gadgetPath) + if os.IsNotExist(err) { + logger.Infof("running mount command...") + err = exec.Command("mount", "-t", "configfs", "none", configFSPath).Run() + if err != nil { + return fmt.Errorf("failed to mount configfs: %w", err) + } + } else { + return fmt.Errorf("unable to access usb gadget path: %w", err) + } + logger.Infof("Successfully mounted usb gadget at %s", gadgetPath) + return nil +} + func writeGadgetAttrs(basePath string, attrs [][]string) error { for _, item := range attrs { filePath := filepath.Join(basePath, item[0]) @@ -79,30 +119,35 @@ func writeGadgetConfig() error { return err } - err = writeGadgetAttrs(kvmGadgetPath, [][]string{ - {"bcdUSB", "0x0200"}, //USB 2.0 - {"idVendor", "0x1d6b"}, //The Linux Foundation - {"idProduct", "0104"}, //Multifunction Composite Gadget¬ + LoadConfig() + gadgetAttrs := [][]string{ + {"bcdUSB", "0x0200"}, //USB 2.0 + {"idVendor", config.UsbConfig.UsbVendorId}, + {"idProduct", config.UsbConfig.UsbProductId}, {"bcdDevice", "0100"}, - }) + } + err = writeGadgetAttrs(kvmGadgetPath, gadgetAttrs) if err != nil { return err } + logger.Infof("Successfully wrote gadget attributes: %s", gadgetAttrs) gadgetStringsPath := filepath.Join(kvmGadgetPath, "strings", "0x409") err = os.MkdirAll(gadgetStringsPath, 0755) if err != nil { return err } - err = writeGadgetAttrs(gadgetStringsPath, [][]string{ - {"serialnumber", GetDeviceID()}, - {"manufacturer", "JetKVM"}, - {"product", "JetKVM USB Emulation Device"}, - }) + strAttrs := [][]string{ + {"serialnumber", config.UsbConfig.UsbSerialNumber}, + {"manufacturer", config.UsbConfig.UsbManufacturer}, + {"product", config.UsbConfig.UsbName}, + } + err = writeGadgetAttrs(gadgetStringsPath, strAttrs) if err != nil { return err } + logger.Infof("Successfully wrote string attributes: %s", strAttrs) configC1StringsPath := path.Join(configC1Path, "strings", "0x409") err = os.MkdirAll(configC1StringsPath, 0755) @@ -216,11 +261,27 @@ func writeGadgetConfig() error { } func rebindUsb() error { + unbindErr := unbindUsb() + if unbindErr != nil { + return unbindErr + } + bindErr := bindUsb() + if bindErr != nil { + return bindErr + } + return nil +} + +func unbindUsb() error { err := os.WriteFile("/sys/bus/platform/drivers/dwc3/unbind", []byte(udc), 0644) if err != nil { return err } - err = os.WriteFile("/sys/bus/platform/drivers/dwc3/bind", []byte(udc), 0644) + return nil +} + +func bindUsb() error { + err := os.WriteFile("/sys/bus/platform/drivers/dwc3/bind", []byte(udc), 0644) if err != nil { return err } From 391de747a68eaccaf80153784893fb82386e3ca0 Mon Sep 17 00:00:00 2001 From: Adrian Date: Thu, 23 Jan 2025 19:11:51 -0600 Subject: [PATCH 02/39] removed debug asterisks --- display.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/display.go b/display.go index 701e3c6..f312eb6 100644 --- a/display.go +++ b/display.go @@ -36,17 +36,17 @@ func switchToScreenIfDifferent(screenName string) { func updateDisplay() { updateLabelIfChanged("ui_Home_Content_Ip", networkState.IPv4) if usbState == "configured" { - updateLabelIfChanged("ui_Home_Footer_Usb_Status_Label", "Connected*") + updateLabelIfChanged("ui_Home_Footer_Usb_Status_Label", "Connected") _, _ = CallCtrlAction("lv_obj_set_state", map[string]interface{}{"obj": "ui_Home_Footer_Usb_Status_Label", "state": "LV_STATE_DEFAULT"}) } else { - updateLabelIfChanged("ui_Home_Footer_Usb_Status_Label", "Disconnected*") + updateLabelIfChanged("ui_Home_Footer_Usb_Status_Label", "Disconnected") _, _ = CallCtrlAction("lv_obj_set_state", map[string]interface{}{"obj": "ui_Home_Footer_Usb_Status_Label", "state": "LV_STATE_USER_2"}) } if lastVideoState.Ready { - updateLabelIfChanged("ui_Home_Footer_Hdmi_Status_Label", "Connected*") + updateLabelIfChanged("ui_Home_Footer_Hdmi_Status_Label", "Connected") _, _ = CallCtrlAction("lv_obj_set_state", map[string]interface{}{"obj": "ui_Home_Footer_Hdmi_Status_Label", "state": "LV_STATE_DEFAULT"}) } else { - updateLabelIfChanged("ui_Home_Footer_Hdmi_Status_Label", "Disconnected*") + updateLabelIfChanged("ui_Home_Footer_Hdmi_Status_Label", "Disconnected") _, _ = CallCtrlAction("lv_obj_set_state", map[string]interface{}{"obj": "ui_Home_Footer_Hdmi_Status_Label", "state": "LV_STATE_USER_2"}) } updateLabelIfChanged("ui_Home_Header_Cloud_Status_Label", fmt.Sprintf("%d active", actionSessions)) From 8329137bae6da8a33b437b5c0550d345ff3b7b45 Mon Sep 17 00:00:00 2001 From: Adrian Date: Fri, 24 Jan 2025 18:49:08 -0600 Subject: [PATCH 03/39] cleanup --- usb.go | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/usb.go b/usb.go index 18bf874..00ff80f 100644 --- a/usb.go +++ b/usb.go @@ -261,27 +261,11 @@ func writeGadgetConfig() error { } func rebindUsb() error { - unbindErr := unbindUsb() - if unbindErr != nil { - return unbindErr - } - bindErr := bindUsb() - if bindErr != nil { - return bindErr - } - return nil -} - -func unbindUsb() error { err := os.WriteFile("/sys/bus/platform/drivers/dwc3/unbind", []byte(udc), 0644) if err != nil { return err } - return nil -} - -func bindUsb() error { - err := os.WriteFile("/sys/bus/platform/drivers/dwc3/bind", []byte(udc), 0644) + err = os.WriteFile("/sys/bus/platform/drivers/dwc3/bind", []byte(udc), 0644) if err != nil { return err } From f8e668349b3a2fcd7c3398f0f46fb66b7e316747 Mon Sep 17 00:00:00 2001 From: Adrian Date: Fri, 24 Jan 2025 19:16:09 -0600 Subject: [PATCH 04/39] cleanup --- usb.go | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/usb.go b/usb.go index 00ff80f..1550ca3 100644 --- a/usb.go +++ b/usb.go @@ -19,6 +19,22 @@ const gadgetPath = "/sys/kernel/config/usb_gadget" const kvmGadgetPath = "/sys/kernel/config/usb_gadget/jetkvm" const configC1Path = "/sys/kernel/config/usb_gadget/jetkvm/configs/c.1" +func mountConfigFS() error { + logger.Infof("Checking for gadgetPath: %s ...", gadgetPath) + _, err := os.Stat(gadgetPath) + if os.IsNotExist(err) { + logger.Infof("running mount command...") + err = exec.Command("mount", "-t", "configfs", "none", configFSPath).Run() + if err != nil { + return fmt.Errorf("failed to mount configfs: %w", err) + } + } else { + return fmt.Errorf("unable to access usb gadget path: %w", err) + } + logger.Infof("Successfully mounted usb gadget at %s", gadgetPath) + return nil +} + func init() { _ = os.MkdirAll(imagesFolder, 0755) udcs := gadget.GetUdcs() @@ -82,22 +98,6 @@ func UpdateGadgetConfig() error { return nil } -func mountConfigFS() error { - logger.Infof("Checking for gadgetPath: %s ...", gadgetPath) - _, err := os.Stat(gadgetPath) - if os.IsNotExist(err) { - logger.Infof("running mount command...") - err = exec.Command("mount", "-t", "configfs", "none", configFSPath).Run() - if err != nil { - return fmt.Errorf("failed to mount configfs: %w", err) - } - } else { - return fmt.Errorf("unable to access usb gadget path: %w", err) - } - logger.Infof("Successfully mounted usb gadget at %s", gadgetPath) - return nil -} - func writeGadgetAttrs(basePath string, attrs [][]string) error { for _, item := range attrs { filePath := filepath.Join(basePath, item[0]) @@ -122,8 +122,8 @@ func writeGadgetConfig() error { LoadConfig() gadgetAttrs := [][]string{ {"bcdUSB", "0x0200"}, //USB 2.0 - {"idVendor", config.UsbConfig.UsbVendorId}, - {"idProduct", config.UsbConfig.UsbProductId}, + {"idVendor", "0x1d6b"}, + {"idProduct", "0104"}, {"bcdDevice", "0100"}, } err = writeGadgetAttrs(kvmGadgetPath, gadgetAttrs) @@ -139,9 +139,9 @@ func writeGadgetConfig() error { } strAttrs := [][]string{ - {"serialnumber", config.UsbConfig.UsbSerialNumber}, - {"manufacturer", config.UsbConfig.UsbManufacturer}, - {"product", config.UsbConfig.UsbName}, + {"serialnumber", GetDeviceID()}, + {"manufacturer", "JetKVM"}, + {"product", "JetKVM USB Emulation Device"}, } err = writeGadgetAttrs(gadgetStringsPath, strAttrs) if err != nil { From 789f2bef6531045833a9cb64f4e07854225db265 Mon Sep 17 00:00:00 2001 From: Adrian Date: Fri, 24 Jan 2025 19:17:45 -0600 Subject: [PATCH 05/39] cleanup --- jsonrpc.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jsonrpc.go b/jsonrpc.go index 5885d70..6325a9a 100644 --- a/jsonrpc.go +++ b/jsonrpc.go @@ -484,12 +484,12 @@ func rpcSetUsbConfig(usbConfig UsbConfig) error { LoadConfig() config.UsbConfig = usbConfig - err2 := UpdateGadgetConfig() - if err2 != nil { - return fmt.Errorf("failed to write gadget config: %w", err2) + err := UpdateGadgetConfig() + if err != nil { + return fmt.Errorf("failed to write gadget config: %w", err) } - err := SaveConfig() + err = SaveConfig() if err != nil { return fmt.Errorf("failed to save usb config: %w", err) } From fd6e2fa7df23576e41f17f71ca60363171c1a9b6 Mon Sep 17 00:00:00 2001 From: Adrian Date: Fri, 24 Jan 2025 19:19:37 -0600 Subject: [PATCH 06/39] cleanup --- usb.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/usb.go b/usb.go index 1550ca3..0d362e9 100644 --- a/usb.go +++ b/usb.go @@ -3,7 +3,6 @@ package kvm import ( "errors" "fmt" - gadget "github.com/openstadia/go-usb-gadget" "log" "os" "os/exec" @@ -12,6 +11,8 @@ import ( "strings" "sync" "time" + + gadget "github.com/openstadia/go-usb-gadget" ) const configFSPath = "/sys/kernel/config" From 8c54eac1675f71892207bc398d5211ef37384f95 Mon Sep 17 00:00:00 2001 From: Adrian Date: Fri, 24 Jan 2025 19:34:35 -0600 Subject: [PATCH 07/39] cleanup --- usb.go | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/usb.go b/usb.go index 0d362e9..35147c4 100644 --- a/usb.go +++ b/usb.go @@ -89,7 +89,7 @@ func UpdateGadgetConfig() error { return err } - log.Printf("Successfully updated string attributes: %s", strAttrs) + log.Printf("Successfully updated usb string attributes: %s", strAttrs) err = rebindUsb() if err != nil { @@ -120,35 +120,30 @@ func writeGadgetConfig() error { return err } - LoadConfig() - gadgetAttrs := [][]string{ - {"bcdUSB", "0x0200"}, //USB 2.0 - {"idVendor", "0x1d6b"}, - {"idProduct", "0104"}, + err = writeGadgetAttrs(kvmGadgetPath, [][]string{ + {"bcdUSB", "0x0200"}, //USB 2.0 + {"idVendor", "0x1d6b"}, //The Linux Foundation + {"idProduct", "0104"}, //Multifunction Composite Gadget¬ {"bcdDevice", "0100"}, - } - err = writeGadgetAttrs(kvmGadgetPath, gadgetAttrs) + }) if err != nil { return err } - logger.Infof("Successfully wrote gadget attributes: %s", gadgetAttrs) gadgetStringsPath := filepath.Join(kvmGadgetPath, "strings", "0x409") err = os.MkdirAll(gadgetStringsPath, 0755) if err != nil { return err } - strAttrs := [][]string{ + err = writeGadgetAttrs(gadgetStringsPath, [][]string{ {"serialnumber", GetDeviceID()}, {"manufacturer", "JetKVM"}, {"product", "JetKVM USB Emulation Device"}, - } - err = writeGadgetAttrs(gadgetStringsPath, strAttrs) + }) if err != nil { return err } - logger.Infof("Successfully wrote string attributes: %s", strAttrs) configC1StringsPath := path.Join(configC1Path, "strings", "0x409") err = os.MkdirAll(configC1StringsPath, 0755) From 30385f8ae3f5439ede1eff2ee1f198b2adcff8f4 Mon Sep 17 00:00:00 2001 From: Adrian Date: Fri, 24 Jan 2025 19:35:43 -0600 Subject: [PATCH 08/39] cleanup --- usb.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/usb.go b/usb.go index 35147c4..96cbc68 100644 --- a/usb.go +++ b/usb.go @@ -21,10 +21,8 @@ const kvmGadgetPath = "/sys/kernel/config/usb_gadget/jetkvm" const configC1Path = "/sys/kernel/config/usb_gadget/jetkvm/configs/c.1" func mountConfigFS() error { - logger.Infof("Checking for gadgetPath: %s ...", gadgetPath) _, err := os.Stat(gadgetPath) if os.IsNotExist(err) { - logger.Infof("running mount command...") err = exec.Command("mount", "-t", "configfs", "none", configFSPath).Run() if err != nil { return fmt.Errorf("failed to mount configfs: %w", err) @@ -32,7 +30,6 @@ func mountConfigFS() error { } else { return fmt.Errorf("unable to access usb gadget path: %w", err) } - logger.Infof("Successfully mounted usb gadget at %s", gadgetPath) return nil } From 3be51a106f91d782a850d629d6523ac182310869 Mon Sep 17 00:00:00 2001 From: Adrian Date: Fri, 24 Jan 2025 20:44:52 -0600 Subject: [PATCH 09/39] cleanup --- jsonrpc.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/jsonrpc.go b/jsonrpc.go index 6325a9a..2a82607 100644 --- a/jsonrpc.go +++ b/jsonrpc.go @@ -479,8 +479,6 @@ func rpcSetUsbEmulationState(enabled bool) error { } func rpcSetUsbConfig(usbConfig UsbConfig) error { - log.Printf("[jsonrpc.go:rpcSetUsbConfig] called") - LoadConfig() config.UsbConfig = usbConfig From e9b68f8131b158b549e9843bc1c0e808b90bb7ae Mon Sep 17 00:00:00 2001 From: Adrian Date: Fri, 24 Jan 2025 21:01:17 -0600 Subject: [PATCH 10/39] cleanup --- ui/src/components/sidebar/settings.tsx | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/ui/src/components/sidebar/settings.tsx b/ui/src/components/sidebar/settings.tsx index 8f5d962..e612c4f 100644 --- a/ui/src/components/sidebar/settings.tsx +++ b/ui/src/components/sidebar/settings.tsx @@ -28,11 +28,11 @@ import { useRevalidator } from "react-router-dom"; import { ShieldCheckIcon } from "@heroicons/react/20/solid"; export function SettingsItem({ - title, - description, - children, - className, - }: { + title, + description, + children, + className, +}: { title: string; description: string | React.ReactNode; children?: React.ReactNode; @@ -40,18 +40,18 @@ export function SettingsItem({ name?: string; }) { return ( - + ); } const defaultEdid = - "00ffffffffffff0052620188008888881c150103800000780a0dc9a05747982712484c00000001010101010101010101010101010101023a801871382d40582c4500c48e2100001e011d007251d01e206e285500c48e2100001e000000fc00543734392d6648443732300a20000000fd00147801ff1d000a202020202020017b"; + "00ffffffffffff0052620188008888881c150103800000780a0dc9a05747982712484c00000001010101010101010101010101010101023a801871382d40582c4500c48e2100001e011d007251d01e206e285500c48e2100001e000000fc00543734392d6648443732300a20000000fd00147801ff1d000a202020202020017b"; const edids = [ { value: defaultEdid, From ac377c49537ce50ed1e97d3bae093cf7f5678979 Mon Sep 17 00:00:00 2001 From: Adrian Date: Fri, 24 Jan 2025 21:02:09 -0600 Subject: [PATCH 11/39] cleanup --- ui/src/components/sidebar/settings.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ui/src/components/sidebar/settings.tsx b/ui/src/components/sidebar/settings.tsx index e612c4f..f393774 100644 --- a/ui/src/components/sidebar/settings.tsx +++ b/ui/src/components/sidebar/settings.tsx @@ -28,10 +28,10 @@ import { useRevalidator } from "react-router-dom"; import { ShieldCheckIcon } from "@heroicons/react/20/solid"; export function SettingsItem({ - title, - description, - children, - className, + title, + description, + children, + className, }: { title: string; description: string | React.ReactNode; From 61477e1795e92640e3f4187e7c6271c8b6d7c5eb Mon Sep 17 00:00:00 2001 From: Adrian Date: Fri, 24 Jan 2025 21:04:17 -0600 Subject: [PATCH 12/39] cleanup --- ui/src/components/sidebar/settings.tsx | 32 +++++++++++++------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/ui/src/components/sidebar/settings.tsx b/ui/src/components/sidebar/settings.tsx index f393774..91d4b29 100644 --- a/ui/src/components/sidebar/settings.tsx +++ b/ui/src/components/sidebar/settings.tsx @@ -59,17 +59,17 @@ const edids = [ }, { value: - "00FFFFFFFFFFFF00047265058A3F6101101E0104A53420783FC125A8554EA0260D5054BFEF80714F8140818081C081008B009500B300283C80A070B023403020360006442100001A000000FD00304C575716010A202020202020000000FC0042323436574C0A202020202020000000FF0054384E4545303033383532320A01F802031CF14F90020304050607011112131415161F2309070783010000011D8018711C1620582C250006442100009E011D007251D01E206E28550006442100001E8C0AD08A20E02D10103E9600064421000018C344806E70B028401720A80406442100001E00000000000000000000000000000000000000000000000000000096", + "00FFFFFFFFFFFF00047265058A3F6101101E0104A53420783FC125A8554EA0260D5054BFEF80714F8140818081C081008B009500B300283C80A070B023403020360006442100001A000000FD00304C575716010A202020202020000000FC0042323436574C0A202020202020000000FF0054384E4545303033383532320A01F802031CF14F90020304050607011112131415161F2309070783010000011D8018711C1620582C250006442100009E011D007251D01E206E28550006442100001E8C0AD08A20E02D10103E9600064421000018C344806E70B028401720A80406442100001E00000000000000000000000000000000000000000000000000000096", label: "Acer B246WL, 1920x1200", }, { value: - "00FFFFFFFFFFFF0006B3872401010101021F010380342078EA6DB5A7564EA0250D5054BF6F00714F8180814081C0A9409500B300D1C0283C80A070B023403020360006442100001A000000FD00314B1E5F19000A202020202020000000FC00504132343851560A2020202020000000FF004D314C4D51533035323135370A014D02032AF14B900504030201111213141F230907078301000065030C001000681A00000101314BE6E2006A023A801871382D40582C450006442100001ECD5F80B072B0374088D0360006442100001C011D007251D01E206E28550006442100001E8C0AD08A20E02D10103E960006442100001800000000000000000000000000DC", + "00FFFFFFFFFFFF0006B3872401010101021F010380342078EA6DB5A7564EA0250D5054BF6F00714F8180814081C0A9409500B300D1C0283C80A070B023403020360006442100001A000000FD00314B1E5F19000A202020202020000000FC00504132343851560A2020202020000000FF004D314C4D51533035323135370A014D02032AF14B900504030201111213141F230907078301000065030C001000681A00000101314BE6E2006A023A801871382D40582C450006442100001ECD5F80B072B0374088D0360006442100001C011D007251D01E206E28550006442100001E8C0AD08A20E02D10103E960006442100001800000000000000000000000000DC", label: "ASUS PA248QV, 1920x1200", }, { value: - "00FFFFFFFFFFFF0010AC132045393639201E0103803C22782ACD25A3574B9F270D5054A54B00714F8180A9C0D1C00101010101010101023A801871382D40582C450056502100001E000000FF00335335475132330A2020202020000000FC0044454C4C204432373231480A20000000FD00384C1E5311000A202020202020018102031AB14F90050403020716010611121513141F65030C001000023A801871382D40582C450056502100001E011D8018711C1620582C250056502100009E011D007251D01E206E28550056502100001E8C0AD08A20E02D10103E960056502100001800000000000000000000000000000000000000000000000000000000004F", + "00FFFFFFFFFFFF0010AC132045393639201E0103803C22782ACD25A3574B9F270D5054A54B00714F8180A9C0D1C00101010101010101023A801871382D40582C450056502100001E000000FF00335335475132330A2020202020000000FC0044454C4C204432373231480A20000000FD00384C1E5311000A202020202020018102031AB14F90050403020716010611121513141F65030C001000023A801871382D40582C450056502100001E011D8018711C1620582C250056502100009E011D007251D01E206E28550056502100001E8C0AD08A20E02D10103E960056502100001800000000000000000000000000000000000000000000000000000000004F", label: "DELL D2721H, 1920x1080", }, ]; @@ -120,19 +120,19 @@ export default function SettingsSidebar() { }, [send]); 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], + (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(() => { From 81085977405ad0292dd2a59b371c11ce4e1eba48 Mon Sep 17 00:00:00 2001 From: Adrian Date: Fri, 24 Jan 2025 21:07:32 -0600 Subject: [PATCH 13/39] cleanup --- ui/src/components/sidebar/settings.tsx | 40 +++++++++++++------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/ui/src/components/sidebar/settings.tsx b/ui/src/components/sidebar/settings.tsx index 91d4b29..2426b82 100644 --- a/ui/src/components/sidebar/settings.tsx +++ b/ui/src/components/sidebar/settings.tsx @@ -147,7 +147,7 @@ export default function SettingsSidebar() { send("deregisterDevice", {}, resp => { if ("error" in resp) { notifications.error( - `Failed to de-register device: ${resp.error.data || "Unknown error"}`, + `Failed to de-register device: ${resp.error.data || "Unknown error"}`, ); return; } @@ -160,7 +160,7 @@ export default function SettingsSidebar() { send("setStreamQualityFactor", { factor: Number(factor) }, resp => { if ("error" in resp) { notifications.error( - `Failed to set stream quality: ${resp.error.data || "Unknown error"}`, + `Failed to set stream quality: ${resp.error.data || "Unknown error"}`, ); return; } @@ -172,7 +172,7 @@ export default function SettingsSidebar() { send("setAutoUpdateState", { enabled }, resp => { if ("error" in resp) { notifications.error( - `Failed to set auto-update: ${resp.error.data || "Unknown error"}`, + `Failed to set auto-update: ${resp.error.data || "Unknown error"}`, ); return; } @@ -184,7 +184,7 @@ export default function SettingsSidebar() { send("setDevChannelState", { enabled }, resp => { if ("error" in resp) { notifications.error( - `Failed to set dev channel state: ${resp.error.data || "Unknown error"}`, + `Failed to set dev channel state: ${resp.error.data || "Unknown error"}`, ); return; } @@ -196,7 +196,7 @@ export default function SettingsSidebar() { send("setJigglerState", { enabled }, resp => { if ("error" in resp) { notifications.error( - `Failed to set jiggler state: ${resp.error.data || "Unknown error"}`, + `Failed to set jiggler state: ${resp.error.data || "Unknown error"}`, ); return; } @@ -232,21 +232,21 @@ export default function SettingsSidebar() { }; 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], + (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 handleUpdateSSHKey = useCallback(() => { From afebc2dd1e27cc4c8a20b3d1f77ce3013a91fad1 Mon Sep 17 00:00:00 2001 From: Adrian Date: Fri, 24 Jan 2025 21:10:04 -0600 Subject: [PATCH 14/39] cleanup --- ui/src/components/sidebar/settings.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ui/src/components/sidebar/settings.tsx b/ui/src/components/sidebar/settings.tsx index 2426b82..3ea67dc 100644 --- a/ui/src/components/sidebar/settings.tsx +++ b/ui/src/components/sidebar/settings.tsx @@ -253,7 +253,7 @@ export default function SettingsSidebar() { send("setSSHKeyState", { sshKey }, resp => { if ("error" in resp) { notifications.error( - `Failed to update SSH key: ${resp.error.data || "Unknown error"}`, + `Failed to update SSH key: ${resp.error.data || "Unknown error"}`, ); return; } @@ -329,7 +329,7 @@ export default function SettingsSidebar() { const receivedEdid = resp.result as string; const matchingEdid = edids.find( - x => x.value.toLowerCase() === receivedEdid.toLowerCase(), + x => x.value.toLowerCase() === receivedEdid.toLowerCase(), ); if (matchingEdid) { @@ -372,8 +372,8 @@ export default function SettingsSidebar() { const getDevice = useCallback(async () => { try { const status = await api - .GET(`${import.meta.env.VITE_SIGNAL_API}/device`) - .then(res => res.json() as Promise); + .GET(`${import.meta.env.VITE_SIGNAL_API}/device`) + .then(res => res.json() as Promise); setLocalDevice(status); } catch (error) { notifications.error("Failed to get authentication status"); @@ -408,8 +408,8 @@ export default function SettingsSidebar() { localStorage.removeItem("theme"); // Check system preference const systemTheme = window.matchMedia("(prefers-color-scheme: dark)").matches - ? "dark" - : "light"; + ? "dark" + : "light"; root.classList.remove("light", "dark"); root.classList.add(systemTheme); } else { @@ -423,7 +423,7 @@ export default function SettingsSidebar() { send("resetConfig", {}, resp => { if ("error" in resp) { notifications.error( - `Failed to reset configuration: ${resp.error.data || "Unknown error"}`, + `Failed to reset configuration: ${resp.error.data || "Unknown error"}`, ); return; } From c6ba93c46f58fbb05b4ecb4f1a1e656c51e93fd4 Mon Sep 17 00:00:00 2001 From: Adrian Date: Fri, 24 Jan 2025 21:22:14 -0600 Subject: [PATCH 15/39] cleaned up settings file --- ui/src/components/sidebar/settings.tsx | 125 ++++++++++++------------- 1 file changed, 62 insertions(+), 63 deletions(-) diff --git a/ui/src/components/sidebar/settings.tsx b/ui/src/components/sidebar/settings.tsx index 3ea67dc..5f58338 100644 --- a/ui/src/components/sidebar/settings.tsx +++ b/ui/src/components/sidebar/settings.tsx @@ -28,11 +28,11 @@ import { useRevalidator } from "react-router-dom"; import { ShieldCheckIcon } from "@heroicons/react/20/solid"; export function SettingsItem({ - title, - description, - children, - className, -}: { + title, + description, + children, + className, + }: { title: string; description: string | React.ReactNode; children?: React.ReactNode; @@ -40,18 +40,18 @@ export function SettingsItem({ name?: string; }) { return ( - + ); } const defaultEdid = - "00ffffffffffff0052620188008888881c150103800000780a0dc9a05747982712484c00000001010101010101010101010101010101023a801871382d40582c4500c48e2100001e011d007251d01e206e285500c48e2100001e000000fc00543734392d6648443732300a20000000fd00147801ff1d000a202020202020017b"; + "00ffffffffffff0052620188008888881c150103800000780a0dc9a05747982712484c00000001010101010101010101010101010101023a801871382d40582c4500c48e2100001e011d007251d01e206e285500c48e2100001e000000fc00543734392d6648443732300a20000000fd00147801ff1d000a202020202020017b"; const edids = [ { value: defaultEdid, @@ -59,17 +59,17 @@ const edids = [ }, { value: - "00FFFFFFFFFFFF00047265058A3F6101101E0104A53420783FC125A8554EA0260D5054BFEF80714F8140818081C081008B009500B300283C80A070B023403020360006442100001A000000FD00304C575716010A202020202020000000FC0042323436574C0A202020202020000000FF0054384E4545303033383532320A01F802031CF14F90020304050607011112131415161F2309070783010000011D8018711C1620582C250006442100009E011D007251D01E206E28550006442100001E8C0AD08A20E02D10103E9600064421000018C344806E70B028401720A80406442100001E00000000000000000000000000000000000000000000000000000096", + "00FFFFFFFFFFFF00047265058A3F6101101E0104A53420783FC125A8554EA0260D5054BFEF80714F8140818081C081008B009500B300283C80A070B023403020360006442100001A000000FD00304C575716010A202020202020000000FC0042323436574C0A202020202020000000FF0054384E4545303033383532320A01F802031CF14F90020304050607011112131415161F2309070783010000011D8018711C1620582C250006442100009E011D007251D01E206E28550006442100001E8C0AD08A20E02D10103E9600064421000018C344806E70B028401720A80406442100001E00000000000000000000000000000000000000000000000000000096", label: "Acer B246WL, 1920x1200", }, { value: - "00FFFFFFFFFFFF0006B3872401010101021F010380342078EA6DB5A7564EA0250D5054BF6F00714F8180814081C0A9409500B300D1C0283C80A070B023403020360006442100001A000000FD00314B1E5F19000A202020202020000000FC00504132343851560A2020202020000000FF004D314C4D51533035323135370A014D02032AF14B900504030201111213141F230907078301000065030C001000681A00000101314BE6E2006A023A801871382D40582C450006442100001ECD5F80B072B0374088D0360006442100001C011D007251D01E206E28550006442100001E8C0AD08A20E02D10103E960006442100001800000000000000000000000000DC", + "00FFFFFFFFFFFF0006B3872401010101021F010380342078EA6DB5A7564EA0250D5054BF6F00714F8180814081C0A9409500B300D1C0283C80A070B023403020360006442100001A000000FD00314B1E5F19000A202020202020000000FC00504132343851560A2020202020000000FF004D314C4D51533035323135370A014D02032AF14B900504030201111213141F230907078301000065030C001000681A00000101314BE6E2006A023A801871382D40582C450006442100001ECD5F80B072B0374088D0360006442100001C011D007251D01E206E28550006442100001E8C0AD08A20E02D10103E960006442100001800000000000000000000000000DC", label: "ASUS PA248QV, 1920x1200", }, { value: - "00FFFFFFFFFFFF0010AC132045393639201E0103803C22782ACD25A3574B9F270D5054A54B00714F8180A9C0D1C00101010101010101023A801871382D40582C450056502100001E000000FF00335335475132330A2020202020000000FC0044454C4C204432373231480A20000000FD00384C1E5311000A202020202020018102031AB14F90050403020716010611121513141F65030C001000023A801871382D40582C450056502100001E011D8018711C1620582C250056502100009E011D007251D01E206E28550056502100001E8C0AD08A20E02D10103E960056502100001800000000000000000000000000000000000000000000000000000000004F", + "00FFFFFFFFFFFF0010AC132045393639201E0103803C22782ACD25A3574B9F270D5054A54B00714F8180A9C0D1C00101010101010101023A801871382D40582C450056502100001E000000FF00335335475132330A2020202020000000FC0044454C4C204432373231480A20000000FD00384C1E5311000A202020202020018102031AB14F90050403020716010611121513141F65030C001000023A801871382D40582C450056502100001E011D8018711C1620582C250056502100009E011D007251D01E206E28550056502100001E8C0AD08A20E02D10103E960056502100001800000000000000000000000000000000000000000000000000000000004F", label: "DELL D2721H, 1920x1080", }, ]; @@ -103,6 +103,12 @@ export default function SettingsSidebar() { } | 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 [usbConfig, setUsbConfig] = useState({ usb_product_id: '', @@ -112,27 +118,20 @@ export default function SettingsSidebar() { usb_name: '', }) - const getUsbEmulationState = useCallback(() => { - send("getUsbEmulationState", {}, resp => { - if ("error" in resp) return; - setUsbEmulationEnabled(resp.result as boolean); - }); - }, [send]); - 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], + (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(() => { @@ -147,7 +146,7 @@ export default function SettingsSidebar() { send("deregisterDevice", {}, resp => { if ("error" in resp) { notifications.error( - `Failed to de-register device: ${resp.error.data || "Unknown error"}`, + `Failed to de-register device: ${resp.error.data || "Unknown error"}`, ); return; } @@ -160,7 +159,7 @@ export default function SettingsSidebar() { send("setStreamQualityFactor", { factor: Number(factor) }, resp => { if ("error" in resp) { notifications.error( - `Failed to set stream quality: ${resp.error.data || "Unknown error"}`, + `Failed to set stream quality: ${resp.error.data || "Unknown error"}`, ); return; } @@ -172,7 +171,7 @@ export default function SettingsSidebar() { send("setAutoUpdateState", { enabled }, resp => { if ("error" in resp) { notifications.error( - `Failed to set auto-update: ${resp.error.data || "Unknown error"}`, + `Failed to set auto-update: ${resp.error.data || "Unknown error"}`, ); return; } @@ -184,7 +183,7 @@ export default function SettingsSidebar() { send("setDevChannelState", { enabled }, resp => { if ("error" in resp) { notifications.error( - `Failed to set dev channel state: ${resp.error.data || "Unknown error"}`, + `Failed to set dev channel state: ${resp.error.data || "Unknown error"}`, ); return; } @@ -196,7 +195,7 @@ export default function SettingsSidebar() { send("setJigglerState", { enabled }, resp => { if ("error" in resp) { notifications.error( - `Failed to set jiggler state: ${resp.error.data || "Unknown error"}`, + `Failed to set jiggler state: ${resp.error.data || "Unknown error"}`, ); return; } @@ -232,28 +231,28 @@ export default function SettingsSidebar() { }; 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], + (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 handleUpdateSSHKey = useCallback(() => { send("setSSHKeyState", { sshKey }, resp => { if ("error" in resp) { notifications.error( - `Failed to update SSH key: ${resp.error.data || "Unknown error"}`, + `Failed to update SSH key: ${resp.error.data || "Unknown error"}`, ); return; } @@ -329,7 +328,7 @@ export default function SettingsSidebar() { const receivedEdid = resp.result as string; const matchingEdid = edids.find( - x => x.value.toLowerCase() === receivedEdid.toLowerCase(), + x => x.value.toLowerCase() === receivedEdid.toLowerCase(), ); if (matchingEdid) { @@ -372,8 +371,8 @@ export default function SettingsSidebar() { const getDevice = useCallback(async () => { try { const status = await api - .GET(`${import.meta.env.VITE_SIGNAL_API}/device`) - .then(res => res.json() as Promise); + .GET(`${import.meta.env.VITE_SIGNAL_API}/device`) + .then(res => res.json() as Promise); setLocalDevice(status); } catch (error) { notifications.error("Failed to get authentication status"); @@ -408,8 +407,8 @@ export default function SettingsSidebar() { localStorage.removeItem("theme"); // Check system preference const systemTheme = window.matchMedia("(prefers-color-scheme: dark)").matches - ? "dark" - : "light"; + ? "dark" + : "light"; root.classList.remove("light", "dark"); root.classList.add(systemTheme); } else { @@ -423,7 +422,7 @@ export default function SettingsSidebar() { send("resetConfig", {}, resp => { if ("error" in resp) { notifications.error( - `Failed to reset configuration: ${resp.error.data || "Unknown error"}`, + `Failed to reset configuration: ${resp.error.data || "Unknown error"}`, ); return; } From e9096c36f7fe47a27a1797975b4a42797380f331 Mon Sep 17 00:00:00 2001 From: Adrian Date: Fri, 24 Jan 2025 21:34:44 -0600 Subject: [PATCH 16/39] removed unused dep --- ui/src/components/sidebar/settings.tsx | 1853 ++++++++++++------------ 1 file changed, 928 insertions(+), 925 deletions(-) diff --git a/ui/src/components/sidebar/settings.tsx b/ui/src/components/sidebar/settings.tsx index 5f58338..c3a216c 100644 --- a/ui/src/components/sidebar/settings.tsx +++ b/ui/src/components/sidebar/settings.tsx @@ -1,988 +1,991 @@ import SidebarHeader from "@components/SidebarHeader"; import { - useLocalAuthModalStore, - useSettingsStore, - useUiStore, - useUpdateStore, + useLocalAuthModalStore, + useSettingsStore, + useUiStore, + useUpdateStore, } 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 { InputFieldWithLabel } from "@components/InputField"; -import { CheckCircleIcon } from "@heroicons/react/20/solid"; -import { cx } from "@/cva.config"; -import React, { useCallback, useEffect, useRef, useState } from "react"; -import { isOnDevice } from "@/main"; +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 {InputFieldWithLabel} from "@components/InputField"; +import {CheckCircleIcon} from "@heroicons/react/20/solid"; +import {cx} from "@/cva.config"; +import React, {useCallback, useEffect, 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 {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 {LocalDevice} from "@routes/devices.$id"; +import {useRevalidator} from "react-router-dom"; +import {ShieldCheckIcon} from "@heroicons/react/20/solid"; export function SettingsItem({ - title, - description, - children, - className, - }: { - title: string; - description: string | React.ReactNode; - children?: React.ReactNode; - className?: string; - name?: string; + title, + description, + children, + className, +}: { + title: string; + description: string | React.ReactNode; + children?: React.ReactNode; + className?: string; + name?: string; }) { - return ( - - ); + return ( + + ); } 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", - }, + { + 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 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 [isAdopted, setAdopted] = useState(false); - const [deviceId, setDeviceId] = useState(null); + const [isAdopted, setAdopted] = useState(false); + const [deviceId, setDeviceId] = useState(null); - const [sshKey, setSSHKey] = useState(""); - const [localDevice, setLocalDevice] = useState(null); + const [sshKey, setSSHKey] = useState(""); + const [localDevice, setLocalDevice] = useState(null); - const sidebarRef = useRef(null); + const sidebarRef = useRef(null); - const hideCursor = useSettingsStore(state => state.isCursorHidden); - const setHideCursor = useSettingsStore(state => state.setCursorVisibility); - const setDeveloperMode = useSettingsStore(state => state.setDeveloperMode); + const hideCursor = useSettingsStore(state => state.isCursorHidden); + const setHideCursor = useSettingsStore(state => state.setCursorVisibility); + const setDeveloperMode = useSettingsStore(state => state.setDeveloperMode); - const [currentVersions, setCurrentVersions] = useState<{ - appVersion: string; - systemVersion: string; - } | null>(null); + 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 [usbConfig, setUsbConfig] = useState({ - usb_product_id: '', - usb_vendor_id: '', - usb_serial_number: '', - usb_manufacturer: '', - usb_name: '', - }) - - 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(); + const [usbEmulationEnabled, setUsbEmulationEnabled] = useState(false); + const getUsbEmulationState = useCallback(() => { + send("getUsbEmulationState", {}, resp => { + if ("error" in resp) return; + setUsbEmulationEnabled(resp.result as boolean); }); - }, - [getUsbEmulationState, send], - ); + }, [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 [usbConfig, setUsbConfig] = useState({ + usb_product_id: '', + usb_vendor_id: '', + usb_serial_number: '', + usb_manufacturer: '', + usb_name: '', + }) - 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 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 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 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 handleUsbConfigChange = useCallback((usbConfig: object) => { - send("setUsbConfig", { usbConfig }, resp => { - if ("error" in resp) { - notifications.error( - `Failed to update USB Config: ${resp.error.data || "Unknown error"}`, - ); - return; - } - }); - }, [send, usbConfig]); - - 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); + 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, setDeveloperMode], - ); + }, [send]); - 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 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 handleUsbProductIdChange = (productId: string) => { - setUsbConfig({... usbConfig, usb_product_id: productId}) - }; + 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 handleUsbVendorIdChange = (vendorId: string) => { - setUsbConfig({... usbConfig, usb_vendor_id: vendorId}) - }; + 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 handleUsbSerialChange = (serialNumber: string) => { - setUsbConfig({... usbConfig, usb_serial_number: serialNumber}) - }; + 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 handleUsbName = (name: string) => { - setUsbConfig({... usbConfig, usb_name: name}) - }; + 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 handleUsbManufacturer = (manufacturer: string) => { - setUsbConfig({... usbConfig, usb_manufacturer: manufacturer}) - }; + 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; + } - const { setIsUpdateDialogOpen, setModalView, otaState } = useUpdateStore(); - const handleCheckForUpdates = () => { - if (otaState.updating) { - setModalView("updating"); - setIsUpdateDialogOpen(true); - } else { - setModalView("loading"); - setIsUpdateDialogOpen(true); - } - }; + // Update the EDID value in the UI + setEdid(newEdid); + }); + }; - useEffect(() => { - getCloudState(); + const handleUsbConfigChange = useCallback((usbConfig: object) => { + send("setUsbConfig", {usbConfig}, resp => { + if ("error" in resp) { + notifications.error( + `Failed to update USB Config: ${resp.error.data || "Unknown error"}`, + ); + return; + } + }); + }, [send]); - send("getDeviceID", {}, async resp => { - if ("error" in resp) return console.error(resp.error); - setDeviceId(resp.result as string); + 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 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 handleUsbProductIdChange = (productId: string) => { + setUsbConfig({...usbConfig, usb_product_id: productId}) + }; + + const handleUsbVendorIdChange = (vendorId: string) => { + setUsbConfig({...usbConfig, usb_vendor_id: vendorId}) + }; + + const handleUsbSerialChange = (serialNumber: string) => { + setUsbConfig({...usbConfig, usb_serial_number: serialNumber}) + }; + + const handleUsbName = (name: string) => { + setUsbConfig({...usbConfig, usb_name: name}) + }; + + const handleUsbManufacturer = (manufacturer: string) => { + setUsbConfig({...usbConfig, usb_manufacturer: manufacturer}) + }; + + const {setIsUpdateDialogOpen, setModalView, otaState} = useUpdateStore(); + const handleCheckForUpdates = () => { + if (otaState.updating) { + setModalView("updating"); + setIsUpdateDialogOpen(true); + } else { + setModalView("loading"); + setIsUpdateDialogOpen(true); + } + }; + + 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("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, setDeveloperMode, setHideCursor, setJiggler]); + + const getDevice = useCallback(async () => { + try { + const status = await api + .GET(`${import.meta.env.VITE_SIGNAL_API}/device`) + .then(res => res.json() as Promise); + setLocalDevice(status); + } catch (error) { + notifications.error("Failed to get authentication status"); + } + }, []); + + const {setModalView: setLocalAuthModalView} = useLocalAuthModalStore(); + const [isLocalAuthDialogOpen, setIsLocalAuthDialogOpen] = 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]); + + const revalidator = useRevalidator(); + + const [currentTheme, setCurrentTheme] = useState(() => { + return localStorage.theme || "system"; }); - send("getJigglerState", {}, resp => { - if ("error" in resp) return; - setJiggler(resp.result as boolean); - }); + const handleThemeChange = useCallback((value: string) => { + const root = document.documentElement; - send("getAutoUpdateState", {}, resp => { - if ("error" in resp) return; - setAutoUpdate(resp.result as boolean); - }); + 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); + } + }, []); - send("getDevChannelState", {}, resp => { - if ("error" in resp) return; - setDevChannel(resp.result as boolean); - }); + 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]); - 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("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, setDeveloperMode, setHideCursor, setJiggler]); - - const getDevice = useCallback(async () => { - try { - const status = await api - .GET(`${import.meta.env.VITE_SIGNAL_API}/device`) - .then(res => res.json() as Promise); - setLocalDevice(status); - } catch (error) { - notifications.error("Failed to get authentication status"); - } - }, []); - - const { setModalView: setLocalAuthModalView } = useLocalAuthModalStore(); - const [isLocalAuthDialogOpen, setIsLocalAuthDialogOpen] = 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]); - - 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()} - > - + 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 ? ( -
- +
+
+
+ + App: {currentVersions.appVersion} +
+ System: {currentVersions.systemVersion} + + ) : ( + "Loading current versions..." + ) } - size="MD" - theme="primary" - text="Adopt KVM to Cloud account" /> -
- ) : ( -
-
-

- Your device is adopted to JetKVM Cloud -

-
+
+
+
+ + +
+ + { + setHideCursor(e.target.checked); }} /> -
-
-
- )} -
- - )} -
- {isOnDevice ? ( - <> -
- - -
- - {localDevice?.authMode === "password" ? ( -
+ + { + handleJigglerChange(e.target.checked); + }} + /> + +
+ +
+ + +
+
+
-
- - ) : null} -
- - -
- - { - handleAutoUpdateChange(e.target.checked); - }} - /> - - - { - handleDevChannelChange(e.target.checked); - }} - /> - -
-
-
- - - - { - setCurrentTheme(e.target.value); - handleThemeChange(e.target.value); - }} - /> - -
-
- - -
- - handleDevModeChange(e.target.checked)} - /> - - - {settings.developerMode && ( -
- handleSSHKeyChange(e.target.value)} - placeholder="Enter your SSH public key" +
+
+ -

- The default SSH user is root. -

-
-
+ + )}
-
- )} - {settings.developerMode && ( -
- handleUsbVendorIdChange(e.target.value)} - placeholder="Enter USB Vendor Id" - /> - handleUsbProductIdChange(e.target.value)} - placeholder="Enter USB Product Id" - /> - handleUsbSerialChange(e.target.value)} - placeholder="Enter USB Serial Number" - /> - handleUsbName(e.target.value)} - placeholder="Enter USB Name" - /> - handleUsbManufacturer(e.target.value)} - placeholder="Enter USB Manufacturer" - /> -
-
-
- )} - - { - settings.setDebugMode(e.target.checked); - }} - /> - +
+ {isOnDevice && ( + <> +
+
+ - {settings.debugMode && ( - <> - -
+
+
+ )} +
+ + )} +
+ {isOnDevice ? ( + <> +
+ + +
+ + {localDevice?.authMode === "password" ? ( +
+
+
+ + ) : null} +
+ + +
+ + { + handleAutoUpdateChange(e.target.checked); + }} + /> + + + { + handleDevChannelChange(e.target.checked); + }} + /> + +
+
+
+ + + + { - handleResetConfig(); - window.location.reload(); + label="" + value={currentTheme} + options={[ + {value: "system", label: "System"}, + {value: "light", label: "Light"}, + {value: "dark", label: "Dark"}, + ]} + onChange={e => { + setCurrentTheme(e.target.value); + handleThemeChange(e.target.value); }} /> - - )} + +
+
+ + +
+ + handleDevModeChange(e.target.checked)} + /> + + + {settings.developerMode && ( +
+ handleSSHKeyChange(e.target.value)} + placeholder="Enter your SSH public key" + /> +

+ The default SSH user is root. +

+
+
+
+ )} + {settings.developerMode && ( +
+ handleUsbVendorIdChange(e.target.value)} + placeholder="Enter USB Vendor Id" + /> + handleUsbProductIdChange(e.target.value)} + placeholder="Enter USB Product Id" + /> + handleUsbSerialChange(e.target.value)} + placeholder="Enter USB Serial Number" + /> + handleUsbName(e.target.value)} + placeholder="Enter USB Name" + /> + handleUsbManufacturer(e.target.value)} + placeholder="Enter USB Manufacturer" + /> +
+
+
+ )} + + { + settings.setDebugMode(e.target.checked); + }} + /> + + + {settings.debugMode && ( + <> + +
+
-
+ { + // Revalidate the current route to refresh the local device status and dependent UI components + revalidator.revalidate(); + setIsLocalAuthDialogOpen(x); + }} + />
- { - // Revalidate the current route to refresh the local device status and dependent UI components - revalidator.revalidate(); - setIsLocalAuthDialogOpen(x); - }} - /> -
- ); + ); } \ No newline at end of file From 03fd7508de3f8743146e68a77ff78993f74dddf5 Mon Sep 17 00:00:00 2001 From: Adrian Date: Fri, 24 Jan 2025 21:35:56 -0600 Subject: [PATCH 17/39] prettifying --- ui/src/components/sidebar/settings.tsx | 1861 ++++++++++++------------ 1 file changed, 929 insertions(+), 932 deletions(-) diff --git a/ui/src/components/sidebar/settings.tsx b/ui/src/components/sidebar/settings.tsx index c3a216c..12b8c3e 100644 --- a/ui/src/components/sidebar/settings.tsx +++ b/ui/src/components/sidebar/settings.tsx @@ -1,991 +1,988 @@ import SidebarHeader from "@components/SidebarHeader"; import { - useLocalAuthModalStore, - useSettingsStore, - useUiStore, - useUpdateStore, + useLocalAuthModalStore, + useSettingsStore, + useUiStore, + useUpdateStore, } 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 {InputFieldWithLabel} from "@components/InputField"; -import {CheckCircleIcon} from "@heroicons/react/20/solid"; -import {cx} from "@/cva.config"; -import React, {useCallback, useEffect, useRef, useState} from "react"; -import {isOnDevice} from "@/main"; +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 { InputFieldWithLabel } from "@components/InputField"; +import { CheckCircleIcon } from "@heroicons/react/20/solid"; +import { cx } from "@/cva.config"; +import React, { useCallback, useEffect, 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 { 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 { LocalDevice } from "@routes/devices.$id"; +import { useRevalidator } from "react-router-dom"; +import { ShieldCheckIcon } from "@heroicons/react/20/solid"; export function SettingsItem({ - title, - description, - children, - className, + title, + description, + children, + className, }: { - title: string; - description: string | React.ReactNode; - children?: React.ReactNode; - className?: string; - name?: string; + title: string; + description: string | React.ReactNode; + children?: React.ReactNode; + className?: string; + name?: string; }) { - return ( - - ); + return ( + + ); } 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", - }, + { + 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 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 [isAdopted, setAdopted] = useState(false); - const [deviceId, setDeviceId] = useState(null); + const [isAdopted, setAdopted] = useState(false); + const [deviceId, setDeviceId] = useState(null); - const [sshKey, setSSHKey] = useState(""); - const [localDevice, setLocalDevice] = useState(null); + const [sshKey, setSSHKey] = useState(""); + const [localDevice, setLocalDevice] = useState(null); - const sidebarRef = useRef(null); + const sidebarRef = useRef(null); - const hideCursor = useSettingsStore(state => state.isCursorHidden); - const setHideCursor = useSettingsStore(state => state.setCursorVisibility); - const setDeveloperMode = useSettingsStore(state => state.setDeveloperMode); + const hideCursor = useSettingsStore(state => state.isCursorHidden); + const setHideCursor = useSettingsStore(state => state.setCursorVisibility); + const setDeveloperMode = useSettingsStore(state => state.setDeveloperMode); - const [currentVersions, setCurrentVersions] = useState<{ - appVersion: string; - systemVersion: string; - } | null>(null); + 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 [usbEmulationEnabled, setUsbEmulationEnabled] = useState(false); + const getUsbEmulationState = useCallback(() => { + send("getUsbEmulationState", {}, resp => { + if ("error" in resp) return; + setUsbEmulationEnabled(resp.result as boolean); + }); + }, [send]); - const [usbConfig, setUsbConfig] = useState({ - usb_product_id: '', - usb_vendor_id: '', - usb_serial_number: '', - usb_manufacturer: '', - usb_name: '', - }) + const [usbConfig, setUsbConfig] = useState({ + usb_product_id: '', + usb_vendor_id: '', + usb_serial_number: '', + usb_manufacturer: '', + usb_name: '', + }) - 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 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 handleUsbConfigChange = useCallback((usbConfig: object) => { - send("setUsbConfig", {usbConfig}, resp => { - if ("error" in resp) { - notifications.error( - `Failed to update USB Config: ${resp.error.data || "Unknown error"}`, - ); - return; - } - }); - }, [send]); - - 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 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 handleUsbProductIdChange = (productId: string) => { - setUsbConfig({...usbConfig, usb_product_id: productId}) - }; - - const handleUsbVendorIdChange = (vendorId: string) => { - setUsbConfig({...usbConfig, usb_vendor_id: vendorId}) - }; - - const handleUsbSerialChange = (serialNumber: string) => { - setUsbConfig({...usbConfig, usb_serial_number: serialNumber}) - }; - - const handleUsbName = (name: string) => { - setUsbConfig({...usbConfig, usb_name: name}) - }; - - const handleUsbManufacturer = (manufacturer: string) => { - setUsbConfig({...usbConfig, usb_manufacturer: manufacturer}) - }; - - const {setIsUpdateDialogOpen, setModalView, otaState} = useUpdateStore(); - const handleCheckForUpdates = () => { - if (otaState.updating) { - setModalView("updating"); - setIsUpdateDialogOpen(true); - } else { - setModalView("loading"); - setIsUpdateDialogOpen(true); - } - }; - - 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(), + 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"}`, ); - - 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); - } + return; + } + setUsbEmulationEnabled(enabled); + getUsbEmulationState(); }); + }, + [getUsbEmulationState, send], + ); - send("getDevModeState", {}, resp => { - if ("error" in resp) return; - const result = resp.result as { enabled: boolean }; - setDeveloperMode(result.enabled); + 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 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 handleUsbConfigChange = useCallback((usbConfig: object) => { + send("setUsbConfig", { usbConfig }, resp => { + if ("error" in resp) { + notifications.error( + `Failed to update USB Config: ${resp.error.data || "Unknown error"}`, + ); + return; + } + }); + }, [send]); + + 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], + ); - send("getSSHKeyState", {}, resp => { - if ("error" in resp) return; - setSSHKey(resp.result as string); - }); + 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]); - send("getUpdateStatus", {}, resp => { - if ("error" in resp) return; - const result = resp.result as SystemVersionInfo; - setCurrentVersions({ - appVersion: result.local.appVersion, - systemVersion: result.local.systemVersion, - }); - }); + const handleUsbProductIdChange = (productId: string) => { + setUsbConfig({... usbConfig, usb_product_id: productId}) + }; - send("getUsbEmulationState", {}, resp => { - if ("error" in resp) return; - setUsbEmulationEnabled(resp.result as boolean); - }); - }, [getCloudState, send, setDeveloperMode, setHideCursor, setJiggler]); + const handleUsbVendorIdChange = (vendorId: string) => { + setUsbConfig({... usbConfig, usb_vendor_id: vendorId}) + }; - const getDevice = useCallback(async () => { - try { - const status = await api - .GET(`${import.meta.env.VITE_SIGNAL_API}/device`) - .then(res => res.json() as Promise); - setLocalDevice(status); - } catch (error) { - notifications.error("Failed to get authentication status"); - } - }, []); + const handleUsbSerialChange = (serialNumber: string) => { + setUsbConfig({... usbConfig, usb_serial_number: serialNumber}) + }; - const {setModalView: setLocalAuthModalView} = useLocalAuthModalStore(); - const [isLocalAuthDialogOpen, setIsLocalAuthDialogOpen] = useState(false); + const handleUsbName = (name: string) => { + setUsbConfig({... usbConfig, usb_name: name}) + }; - useEffect(() => { - if (isOnDevice) getDevice(); - }, [getDevice]); + const handleUsbManufacturer = (manufacturer: string) => { + setUsbConfig({... usbConfig, usb_manufacturer: manufacturer}) + }; - useEffect(() => { - if (!isOnDevice) return; - // Refresh device status when the local auth dialog is closed - if (!isLocalAuthDialogOpen) { - getDevice(); - } - }, [getDevice, isLocalAuthDialogOpen]); + const { setIsUpdateDialogOpen, setModalView, otaState } = useUpdateStore(); + const handleCheckForUpdates = () => { + if (otaState.updating) { + setModalView("updating"); + setIsUpdateDialogOpen(true); + } else { + setModalView("loading"); + setIsUpdateDialogOpen(true); + } + }; - const revalidator = useRevalidator(); + useEffect(() => { + getCloudState(); - const [currentTheme, setCurrentTheme] = useState(() => { - return localStorage.theme || "system"; + send("getDeviceID", {}, async resp => { + if ("error" in resp) return console.error(resp.error); + setDeviceId(resp.result as string); }); - const handleThemeChange = useCallback((value: string) => { - const root = document.documentElement; + send("getJigglerState", {}, resp => { + if ("error" in resp) return; + setJiggler(resp.result as boolean); + }); - 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); - } - }, []); + send("getAutoUpdateState", {}, resp => { + if ("error" in resp) return; + setAutoUpdate(resp.result as boolean); + }); - 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]); + send("getDevChannelState", {}, resp => { + if ("error" in resp) return; + setDevChannel(resp.result as boolean); + }); - return ( + 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("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, setDeveloperMode, setHideCursor, setJiggler]); + + const getDevice = useCallback(async () => { + try { + const status = await api + .GET(`${import.meta.env.VITE_SIGNAL_API}/device`) + .then(res => res.json() as Promise); + setLocalDevice(status); + } catch (error) { + notifications.error("Failed to get authentication status"); + } + }, []); + + const { setModalView: setLocalAuthModalView } = useLocalAuthModalStore(); + const [isLocalAuthDialogOpen, setIsLocalAuthDialogOpen] = 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]); + + 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()} + > +
e.stopPropagation()} - onKeyUp={e => e.stopPropagation()} + className="h-full px-4 py-2 space-y-4 overflow-y-scroll bg-white dark:bg-slate-900" + ref={sidebarRef} > - -
-
-
- - 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); - }} - /> - -
-
-
- - +
+ + App: {currentVersions.appVersion} +
+ System: {currentVersions.systemVersion} + + ) : ( + "Loading current versions..." + ) + } + /> +
+ + +
+
+
+
+
+
+ +
+ + 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 && ( + <> +
+
+ - {settings.developerMode && ( -
- handleSSHKeyChange(e.target.value)} - placeholder="Enter your SSH public key" - /> -

- The default SSH user is root. -

-
-
-
- )} - {settings.developerMode && ( -
- handleUsbVendorIdChange(e.target.value)} - placeholder="Enter USB Vendor Id" - /> - handleUsbProductIdChange(e.target.value)} - placeholder="Enter USB Product Id" - /> - handleUsbSerialChange(e.target.value)} - placeholder="Enter USB Serial Number" - /> - handleUsbName(e.target.value)} - placeholder="Enter USB Name" - /> - handleUsbManufacturer(e.target.value)} - placeholder="Enter USB Manufacturer" - /> -
-
-
- )} - - { - settings.setDebugMode(e.target.checked); + +
+ +
+
+

+ 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); + }} + /> +
- { - // Revalidate the current route to refresh the local device status and dependent UI components - revalidator.revalidate(); - setIsLocalAuthDialogOpen(x); +
+
+ + + + { + setCurrentTheme(e.target.value); + handleThemeChange(e.target.value); }} /> + +
+
+ + +
+ + handleDevModeChange(e.target.checked)} + /> + + + {settings.developerMode && ( +
+ handleSSHKeyChange(e.target.value)} + placeholder="Enter your SSH public key" + /> +

+ The default SSH user is root. +

+
+
+
+ )} + {settings.developerMode && ( +
+ handleUsbVendorIdChange(e.target.value)} + placeholder="Enter USB Vendor Id" + /> + handleUsbProductIdChange(e.target.value)} + placeholder="Enter USB Product Id" + /> + handleUsbSerialChange(e.target.value)} + placeholder="Enter USB Serial Number" + /> + handleUsbName(e.target.value)} + placeholder="Enter USB Name" + /> + handleUsbManufacturer(e.target.value)} + placeholder="Enter USB Manufacturer" + /> +
+
+
+ )} + + { + settings.setDebugMode(e.target.checked); + }} + /> + + + {settings.debugMode && ( + <> + +
+
- ); + { + // Revalidate the current route to refresh the local device status and dependent UI components + revalidator.revalidate(); + setIsLocalAuthDialogOpen(x); + }} + /> +
+ ); } \ No newline at end of file From 0cd406f35e6a0795e622d7075b3b92b4eab4bd86 Mon Sep 17 00:00:00 2001 From: Adrian Date: Sat, 25 Jan 2025 14:52:23 -0600 Subject: [PATCH 18/39] linted modules converted input fields to use a modal to save space in settings updated descriptions --- cloud.go | 4 +- ui/src/components/UsbConfigDialog.tsx | 187 +++++++++++++++++++++++++ ui/src/components/sidebar/settings.tsx | 122 +++++----------- ui/src/hooks/stores.ts | 16 +++ 4 files changed, 239 insertions(+), 90 deletions(-) create mode 100644 ui/src/components/UsbConfigDialog.tsx diff --git a/cloud.go b/cloud.go index db47727..c2cd554 100644 --- a/cloud.go +++ b/cloud.go @@ -5,15 +5,15 @@ import ( "context" "encoding/json" "fmt" + "github.com/coder/websocket/wsjson" "net/http" "net/url" - "github.com/coder/websocket/wsjson" "time" "github.com/coreos/go-oidc/v3/oidc" - "github.com/gin-gonic/gin" "github.com/coder/websocket" + "github.com/gin-gonic/gin" ) type CloudRegisterRequest struct { diff --git a/ui/src/components/UsbConfigDialog.tsx b/ui/src/components/UsbConfigDialog.tsx new file mode 100644 index 0000000..3ea5fd6 --- /dev/null +++ b/ui/src/components/UsbConfigDialog.tsx @@ -0,0 +1,187 @@ +import { GridCard } from "@/components/Card"; +import {useCallback, 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"; + +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({ + onSetUsbConfig, + onCancel, + error, +}: { + onSetUsbConfig: (usb_config: object) => void; + onCancel: () => void; + error: string | null; +}) { + const [usbConfig, setUsbConfig] = useState({ + usb_vendor_id: '', + usb_product_id: '', + usb_serial_number: '', + usb_manufacturer: '', + usb_name: '', + }) + const handleUsbVendorIdChange = (vendorId: string) => { + setUsbConfig({... usbConfig, usb_vendor_id: vendorId}) + }; + + const handleUsbProductIdChange = (productId: string) => { + setUsbConfig({... usbConfig, usb_product_id: productId}) + }; + + const handleUsbSerialChange = (serialNumber: string) => { + setUsbConfig({... usbConfig, usb_serial_number: serialNumber}) + }; + + const handleUsbManufacturer = (manufacturer: string) => { + setUsbConfig({... usbConfig, usb_manufacturer: manufacturer}) + }; + + const handleUsbName = (name: string) => { + setUsbConfig({... usbConfig, usb_name: name}) + }; + + return ( +
+
+ + +
+
+
+

USB Emulation Configuration

+

+ Set custom USB parameters to control the device USB emulation. The device will rebind once the parameters are updated. +

+
+ handleUsbVendorIdChange(e.target.value)} + /> + handleUsbProductIdChange(e.target.value)} + /> + handleUsbSerialChange(e.target.value)} + /> + handleUsbManufacturer(e.target.value)} + /> + handleUsbName(e.target.value)} + /> +
+
+ {error &&

{error}

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

{headline}

+

{description}

+
+
+
+ ); +} diff --git a/ui/src/components/sidebar/settings.tsx b/ui/src/components/sidebar/settings.tsx index 12b8c3e..e0cedd4 100644 --- a/ui/src/components/sidebar/settings.tsx +++ b/ui/src/components/sidebar/settings.tsx @@ -3,14 +3,13 @@ import { useLocalAuthModalStore, useSettingsStore, useUiStore, - useUpdateStore, + 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 { InputFieldWithLabel } from "@components/InputField"; import { CheckCircleIcon } from "@heroicons/react/20/solid"; import { cx } from "@/cva.config"; import React, { useCallback, useEffect, useRef, useState } from "react"; @@ -26,6 +25,7 @@ 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"; export function SettingsItem({ title, @@ -110,14 +110,6 @@ export default function SettingsSidebar() { }); }, [send]); - const [usbConfig, setUsbConfig] = useState({ - usb_product_id: '', - usb_vendor_id: '', - usb_serial_number: '', - usb_manufacturer: '', - usb_name: '', - }) - const handleUsbEmulationToggle = useCallback( (enabled: boolean) => { send("setUsbEmulationState", { enabled: enabled }, resp => { @@ -215,17 +207,6 @@ export default function SettingsSidebar() { }); }; - const handleUsbConfigChange = useCallback((usbConfig: object) => { - send("setUsbConfig", { usbConfig }, resp => { - if ("error" in resp) { - notifications.error( - `Failed to update USB Config: ${resp.error.data || "Unknown error"}`, - ); - return; - } - }); - }, [send]); - const handleSSHKeyChange = (newKey: string) => { setSSHKey(newKey); }; @@ -260,26 +241,6 @@ export default function SettingsSidebar() { }); }, [send, sshKey]); - const handleUsbProductIdChange = (productId: string) => { - setUsbConfig({... usbConfig, usb_product_id: productId}) - }; - - const handleUsbVendorIdChange = (vendorId: string) => { - setUsbConfig({... usbConfig, usb_vendor_id: vendorId}) - }; - - const handleUsbSerialChange = (serialNumber: string) => { - setUsbConfig({... usbConfig, usb_serial_number: serialNumber}) - }; - - const handleUsbName = (name: string) => { - setUsbConfig({... usbConfig, usb_name: name}) - }; - - const handleUsbManufacturer = (manufacturer: string) => { - setUsbConfig({... usbConfig, usb_manufacturer: manufacturer}) - }; - const { setIsUpdateDialogOpen, setModalView, otaState } = useUpdateStore(); const handleCheckForUpdates = () => { if (otaState.updating) { @@ -380,7 +341,9 @@ export default function SettingsSidebar() { }, []); const { setModalView: setLocalAuthModalView } = useLocalAuthModalStore(); + const { setModalView: setUsbConfigModalView } = useUsbConfigModalStore(); const [isLocalAuthDialogOpen, setIsLocalAuthDialogOpen] = useState(false); + const [isUsbConfigDialogOpen, setIsUsbConfigDialogOpen] = useState(false); useEffect(() => { if (isOnDevice) getDevice(); @@ -394,6 +357,14 @@ export default function SettingsSidebar() { } }, [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(() => { @@ -877,53 +848,20 @@ export default function SettingsSidebar() {
)} {settings.developerMode && ( -
- handleUsbVendorIdChange(e.target.value)} - placeholder="Enter USB Vendor Id" - /> - handleUsbProductIdChange(e.target.value)} - placeholder="Enter USB Product Id" - /> - handleUsbSerialChange(e.target.value)} - placeholder="Enter USB Serial Number" - /> - handleUsbName(e.target.value)} - placeholder="Enter USB Name" - /> - handleUsbManufacturer(e.target.value)} - placeholder="Enter USB Manufacturer" - /> -
-
-
+ +
); } \ No newline at end of file diff --git a/ui/src/hooks/stores.ts b/ui/src/hooks/stores.ts index b4cfbec..a6b4451 100644 --- a/ui/src/hooks/stores.ts +++ b/ui/src/hooks/stores.ts @@ -528,3 +528,19 @@ export const useLocalAuthModalStore = create(set => ({ setModalView: view => set({ modalView: view }), setErrorMessage: message => set({ errorMessage: message }), })); + +interface UsbConfigModalState { + modalView: + | "updateUsbConfig" + | "updateUsbConfigSuccess"; + errorMessage: string | null; + setModalView: (view: UsbConfigModalState["modalView"]) => void; + setErrorMessage: (message: string | null) => void; +} + +export const useUsbConfigModalStore = create(set => ({ + modalView: "updateUsbConfig", + errorMessage: null, + setModalView: view => set({ modalView: view }), + setErrorMessage: message => set({ errorMessage: message }), +})); \ No newline at end of file From 2e7493c9d1a260299268ba4835125a133aaad2dd Mon Sep 17 00:00:00 2001 From: Adrian Date: Sat, 25 Jan 2025 14:55:01 -0600 Subject: [PATCH 19/39] updated descriptions --- ui/src/components/UsbConfigDialog.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/ui/src/components/UsbConfigDialog.tsx b/ui/src/components/UsbConfigDialog.tsx index 3ea5fd6..d25a568 100644 --- a/ui/src/components/UsbConfigDialog.tsx +++ b/ui/src/components/UsbConfigDialog.tsx @@ -107,41 +107,42 @@ function UpdateUsbConfigModal({

USB Emulation Configuration

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

handleUsbVendorIdChange(e.target.value)} /> handleUsbProductIdChange(e.target.value)} /> handleUsbSerialChange(e.target.value)} /> handleUsbManufacturer(e.target.value)} /> handleUsbName(e.target.value)} /> From ac1403defcc403de60b40694ae9a1d074743a2c7 Mon Sep 17 00:00:00 2001 From: Adrian Date: Sat, 25 Jan 2025 15:20:44 -0600 Subject: [PATCH 20/39] renamed module to match convention --- .../components/{UsbConfigDialog.tsx => USBConfigDialog.tsx} | 2 +- ui/src/components/sidebar/settings.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename ui/src/components/{UsbConfigDialog.tsx => USBConfigDialog.tsx} (99%) diff --git a/ui/src/components/UsbConfigDialog.tsx b/ui/src/components/USBConfigDialog.tsx similarity index 99% rename from ui/src/components/UsbConfigDialog.tsx rename to ui/src/components/USBConfigDialog.tsx index d25a568..67f576a 100644 --- a/ui/src/components/UsbConfigDialog.tsx +++ b/ui/src/components/USBConfigDialog.tsx @@ -8,7 +8,7 @@ import { InputFieldWithLabel } from "./InputField"; import { useJsonRpc } from "@/hooks/useJsonRpc"; import { useUsbConfigModalStore } from "@/hooks/stores"; -export default function UsbConfigDialog({ +export default function USBConfigDialog({ open, setOpen, }: { diff --git a/ui/src/components/sidebar/settings.tsx b/ui/src/components/sidebar/settings.tsx index e0cedd4..e504332 100644 --- a/ui/src/components/sidebar/settings.tsx +++ b/ui/src/components/sidebar/settings.tsx @@ -25,7 +25,7 @@ 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 USBConfigDialog from "@components/USBConfigDialog"; export function SettingsItem({ title, @@ -921,7 +921,7 @@ export default function SettingsSidebar() { setIsLocalAuthDialogOpen(x); }} /> - { // Revalidate the current route to refresh the local device status and dependent UI components From 21b458b59a8890e225af45436051f216e6b37632 Mon Sep 17 00:00:00 2001 From: Adrian Date: Sat, 25 Jan 2025 15:35:35 -0600 Subject: [PATCH 21/39] renamed usb name to usb product to match convention added usb config defaults --- config.go | 9 ++++++++- ui/src/components/USBConfigDialog.tsx | 14 +++++++------- usb.go | 2 +- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/config.go b/config.go index 89c2715..5537834 100644 --- a/config.go +++ b/config.go @@ -15,8 +15,8 @@ type UsbConfig struct { UsbVendorId string `json:"usb_vendor_id"` UsbProductId string `json:"usb_product_id"` UsbSerialNumber string `json:"usb_serial_number"` - UsbName string `json:"usb_name"` UsbManufacturer string `json:"usb_manufacturer"` + UsbProduct string `json:"usb_product"` } type Config struct { @@ -38,6 +38,13 @@ const configPath = "/userdata/kvm_config.json" var defaultConfig = &Config{ CloudURL: "https://api.jetkvm.com", AutoUpdateEnabled: true, // Set a default value + UsbConfig: UsbConfig{ + UsbVendorId: "0x1d6b", + UsbProductId: "0104", + UsbSerialNumber: "", + UsbManufacturer: "JetKVM", + UsbProduct: "JetKVM USB Emulation Device", + }, } var config *Config diff --git a/ui/src/components/USBConfigDialog.tsx b/ui/src/components/USBConfigDialog.tsx index 67f576a..0fb58e7 100644 --- a/ui/src/components/USBConfigDialog.tsx +++ b/ui/src/components/USBConfigDialog.tsx @@ -75,7 +75,7 @@ function UpdateUsbConfigModal({ usb_product_id: '', usb_serial_number: '', usb_manufacturer: '', - usb_name: '', + usb_product: '', }) const handleUsbVendorIdChange = (vendorId: string) => { setUsbConfig({... usbConfig, usb_vendor_id: vendorId}) @@ -93,8 +93,8 @@ function UpdateUsbConfigModal({ setUsbConfig({... usbConfig, usb_manufacturer: manufacturer}) }; - const handleUsbName = (name: string) => { - setUsbConfig({... usbConfig, usb_name: name}) + const handleUsbProduct = (name: string) => { + setUsbConfig({... usbConfig, usb_product: name}) }; return ( @@ -141,10 +141,10 @@ function UpdateUsbConfigModal({ /> handleUsbName(e.target.value)} + label="Product Name" + placeholder="Enter Product Name" + value={usbConfig.usb_product || ""} + onChange={e => handleUsbProduct(e.target.value)} />
)} - {settings.developerMode && ( - -
From 194fad972e9d339c6d92583b173d8e05855df541 Mon Sep 17 00:00:00 2001 From: Adrian Date: Wed, 29 Jan 2025 23:00:24 -0600 Subject: [PATCH 37/39] Revert "fix for initial values being empty" This reverts commit 065f1bd21beadff9983f734e5bda6b47461ba4a3. --- ui/src/components/USBConfigDialog.tsx | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/ui/src/components/USBConfigDialog.tsx b/ui/src/components/USBConfigDialog.tsx index b21a75e..e9ba5f2 100644 --- a/ui/src/components/USBConfigDialog.tsx +++ b/ui/src/components/USBConfigDialog.tsx @@ -78,13 +78,15 @@ function UpdateUsbConfigModal({ onCancel: () => void; error: string | null; }) { - const [usbConfigState, setUsbConfigState] = useState({ + const [usbConfig, setUsbConfig] = useState({ vendor_id: '', product_id: '', serial_number: '', manufacturer: '', - product: '' - }); + product: '', + }) + + const [usbConfigState, setUsbConfigState] = useState(); const [send] = useJsonRpc(); const syncUsbConfig = useCallback(() => { @@ -103,23 +105,23 @@ function UpdateUsbConfigModal({ }, [syncUsbConfig]); const handleUsbVendorIdChange = (value: string) => { - setUsbConfigState({... usbConfigState, vendor_id: value}) + setUsbConfig({... usbConfig, vendor_id: value}) }; const handleUsbProductIdChange = (value: string) => { - setUsbConfigState({... usbConfigState, product_id: value}) + setUsbConfig({... usbConfig, product_id: value}) }; const handleUsbSerialChange = (value: string) => { - setUsbConfigState({... usbConfigState, serial_number: value}) + setUsbConfig({... usbConfig, serial_number: value}) }; const handleUsbManufacturer = (value: string) => { - setUsbConfigState({... usbConfigState, manufacturer: value}) + setUsbConfig({... usbConfig, manufacturer: value}) }; const handleUsbProduct = (value: string) => { - setUsbConfigState({... usbConfigState, product: value}) + setUsbConfig({... usbConfig, product: value}) }; return ( @@ -178,7 +180,7 @@ function UpdateUsbConfigModal({ size="SM" theme="primary" text="Update USB Config" - onClick={() => onSetUsbConfig(usbConfigState)} + onClick={() => onSetUsbConfig(usbConfig)} />
From da1a57eb93d07756020f5f7efae749853ae5947e Mon Sep 17 00:00:00 2001 From: Adrian Date: Wed, 29 Jan 2025 23:05:28 -0600 Subject: [PATCH 38/39] Revert "Revert "fix for initial values being empty"" This reverts commit 194fad972e9d339c6d92583b173d8e05855df541. --- ui/src/components/USBConfigDialog.tsx | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/ui/src/components/USBConfigDialog.tsx b/ui/src/components/USBConfigDialog.tsx index e9ba5f2..b21a75e 100644 --- a/ui/src/components/USBConfigDialog.tsx +++ b/ui/src/components/USBConfigDialog.tsx @@ -78,15 +78,13 @@ function UpdateUsbConfigModal({ onCancel: () => void; error: string | null; }) { - const [usbConfig, setUsbConfig] = useState({ + const [usbConfigState, setUsbConfigState] = useState({ vendor_id: '', product_id: '', serial_number: '', manufacturer: '', - product: '', - }) - - const [usbConfigState, setUsbConfigState] = useState(); + product: '' + }); const [send] = useJsonRpc(); const syncUsbConfig = useCallback(() => { @@ -105,23 +103,23 @@ function UpdateUsbConfigModal({ }, [syncUsbConfig]); const handleUsbVendorIdChange = (value: string) => { - setUsbConfig({... usbConfig, vendor_id: value}) + setUsbConfigState({... usbConfigState, vendor_id: value}) }; const handleUsbProductIdChange = (value: string) => { - setUsbConfig({... usbConfig, product_id: value}) + setUsbConfigState({... usbConfigState, product_id: value}) }; const handleUsbSerialChange = (value: string) => { - setUsbConfig({... usbConfig, serial_number: value}) + setUsbConfigState({... usbConfigState, serial_number: value}) }; const handleUsbManufacturer = (value: string) => { - setUsbConfig({... usbConfig, manufacturer: value}) + setUsbConfigState({... usbConfigState, manufacturer: value}) }; const handleUsbProduct = (value: string) => { - setUsbConfig({... usbConfig, product: value}) + setUsbConfigState({... usbConfigState, product: value}) }; return ( @@ -180,7 +178,7 @@ function UpdateUsbConfigModal({ size="SM" theme="primary" text="Update USB Config" - onClick={() => onSetUsbConfig(usbConfig)} + onClick={() => onSetUsbConfig(usbConfigState)} />
From 1870144d20e0477ee9a4543409af2947beb491ba Mon Sep 17 00:00:00 2001 From: JackTheRooster Date: Thu, 30 Jan 2025 18:10:36 -0600 Subject: [PATCH 39/39] squash! cleanup --- cloud.go | 4 ++-- ui/src/components/USBConfigDialog.tsx | 20 +++++++++----------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/cloud.go b/cloud.go index c2cd554..db47727 100644 --- a/cloud.go +++ b/cloud.go @@ -5,15 +5,15 @@ import ( "context" "encoding/json" "fmt" - "github.com/coder/websocket/wsjson" "net/http" "net/url" + "github.com/coder/websocket/wsjson" "time" "github.com/coreos/go-oidc/v3/oidc" - "github.com/coder/websocket" "github.com/gin-gonic/gin" + "github.com/coder/websocket" ) type CloudRegisterRequest struct { diff --git a/ui/src/components/USBConfigDialog.tsx b/ui/src/components/USBConfigDialog.tsx index e9ba5f2..b21a75e 100644 --- a/ui/src/components/USBConfigDialog.tsx +++ b/ui/src/components/USBConfigDialog.tsx @@ -78,15 +78,13 @@ function UpdateUsbConfigModal({ onCancel: () => void; error: string | null; }) { - const [usbConfig, setUsbConfig] = useState({ + const [usbConfigState, setUsbConfigState] = useState({ vendor_id: '', product_id: '', serial_number: '', manufacturer: '', - product: '', - }) - - const [usbConfigState, setUsbConfigState] = useState(); + product: '' + }); const [send] = useJsonRpc(); const syncUsbConfig = useCallback(() => { @@ -105,23 +103,23 @@ function UpdateUsbConfigModal({ }, [syncUsbConfig]); const handleUsbVendorIdChange = (value: string) => { - setUsbConfig({... usbConfig, vendor_id: value}) + setUsbConfigState({... usbConfigState, vendor_id: value}) }; const handleUsbProductIdChange = (value: string) => { - setUsbConfig({... usbConfig, product_id: value}) + setUsbConfigState({... usbConfigState, product_id: value}) }; const handleUsbSerialChange = (value: string) => { - setUsbConfig({... usbConfig, serial_number: value}) + setUsbConfigState({... usbConfigState, serial_number: value}) }; const handleUsbManufacturer = (value: string) => { - setUsbConfig({... usbConfig, manufacturer: value}) + setUsbConfigState({... usbConfigState, manufacturer: value}) }; const handleUsbProduct = (value: string) => { - setUsbConfig({... usbConfig, product: value}) + setUsbConfigState({... usbConfigState, product: value}) }; return ( @@ -180,7 +178,7 @@ function UpdateUsbConfigModal({ size="SM" theme="primary" text="Update USB Config" - onClick={() => onSetUsbConfig(usbConfig)} + onClick={() => onSetUsbConfig(usbConfigState)} />