diff --git a/.vscode/settings.json b/.vscode/settings.json index a610159c..fbda890e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -24,5 +24,6 @@ ], "url": "./internal/ota/testdata/ota.schema.json" } - ] + ], + "typescript.tsdk": "./ui/node_modules/typescript/lib" } \ No newline at end of file diff --git a/ui/src/components/UpdateInProgressStatusCard.tsx b/ui/src/components/UpdateInProgressStatusCard.tsx index b61752f2..ba536fb2 100644 --- a/ui/src/components/UpdateInProgressStatusCard.tsx +++ b/ui/src/components/UpdateInProgressStatusCard.tsx @@ -1,11 +1,10 @@ +import { useDeviceUiNavigation } from "@hooks/useAppNavigation"; + +import { Button } from "@components/Button"; +import { GridCard } from "@components/Card"; +import LoadingSpinner from "@components/LoadingSpinner"; import { cx } from "@/cva.config"; -import { useDeviceUiNavigation } from "../hooks/useAppNavigation"; - -import { Button } from "./Button"; -import { GridCard } from "./Card"; -import LoadingSpinner from "./LoadingSpinner"; - export default function UpdateInProgressStatusCard() { const { navigateTo } = useDeviceUiNavigation(); @@ -22,7 +21,7 @@ export default function UpdateInProgressStatusCard() {
- Please don{"'"}t turn off your device... + Please don't turn off your device…
diff --git a/ui/src/components/UpdatingStatusCard.tsx b/ui/src/components/UpdatingStatusCard.tsx new file mode 100644 index 00000000..766a9294 --- /dev/null +++ b/ui/src/components/UpdatingStatusCard.tsx @@ -0,0 +1,48 @@ +import { CheckCircleIcon } from "@heroicons/react/24/solid"; + +import LoadingSpinner from "@components/LoadingSpinner"; + +export interface UpdatePart { + pending: boolean; + status: string; + progress: number; + complete: boolean; +} + +export default function UpdatingStatusCard({ + label, + part, +}: { + label: string; + part: UpdatePart; +}) { + return ( +
+
+

{label}

+ {part.progress < 100 ? ( + + ) : ( + + )} +
+
+
+
+
+ {part.status} + {part.progress < 100 ? {`${Math.round(part.progress)}%`} : null} +
+
+ ); +} diff --git a/ui/src/routes/devices.$id.settings.access._index.tsx b/ui/src/routes/devices.$id.settings.access._index.tsx index 9b2d3cd3..8617a2bb 100644 --- a/ui/src/routes/devices.$id.settings.access._index.tsx +++ b/ui/src/routes/devices.$id.settings.access._index.tsx @@ -1,9 +1,9 @@ import { useCallback, useEffect, useState } from "react"; import { useLoaderData, useNavigate, type LoaderFunction } from "react-router"; import { ShieldCheckIcon } from "@heroicons/react/24/outline"; - import { useDeviceUiNavigation } from "@hooks/useAppNavigation"; import { JsonRpcResponse, useJsonRpc } from "@hooks/useJsonRpc"; + import { GridCard } from "@components/Card"; import { Button, LinkButton } from "@components/Button"; import { InputFieldWithLabel } from "@components/InputField"; @@ -17,7 +17,6 @@ import api from "@/api"; import notifications from "@/notifications"; import { DEVICE_API } from "@/ui.config"; import { isOnDevice } from "@/main"; -import { m } from "@localizations/messages.js"; import { LocalDevice } from "./devices.$id"; import { CloudState } from "./adopt"; @@ -93,7 +92,7 @@ export default function SettingsAccessIndexRoute() { send("deregisterDevice", {}, (resp: JsonRpcResponse) => { if ("error" in resp) { notifications.error( - m.access_failed_deregister({ error: resp.error.data || m.unknown_error() }), + `Failed to deregister device: ${resp.error.data || "Unknown error"}`, ); return; } @@ -108,14 +107,14 @@ export default function SettingsAccessIndexRoute() { const onCloudAdoptClick = useCallback( (cloudApiUrl: string, cloudAppUrl: string) => { if (!deviceId) { - notifications.error(m.access_no_device_id()); + notifications.error("No device ID found"); return; } send("setCloudUrl", { apiUrl: cloudApiUrl, appUrl: cloudAppUrl }, (resp: JsonRpcResponse) => { if ("error" in resp) { notifications.error( - m.access_failed_update_cloud_url({ error: resp.error.data || m.unknown_error() }), + `Failed to update cloud URL: ${resp.error.data || "Unknown error"}`, ); return; } @@ -161,12 +160,12 @@ export default function SettingsAccessIndexRoute() { send("setTLSState", { state }, (resp: JsonRpcResponse) => { if ("error" in resp) { notifications.error( - m.access_failed_update_tls({ error: resp.error.data || m.unknown_error() }), + `Failed to update TLS state: ${resp.error.data || "Unknown error"}`, ); return; } - notifications.success(m.access_tls_updated()); + notifications.success("TLS state updated successfully"); }); }, [send]); @@ -207,22 +206,22 @@ export default function SettingsAccessIndexRoute() { return (
{loaderData?.authMode && ( <>
<> handleTlsModeChange(e.target.value)} disabled={tlsMode === "unknown"} options={[ - { value: "disabled", label: m.access_tls_disabled() }, - { value: "self-signed", label: m.access_tls_self_signed() }, - { value: "custom", label: m.access_tls_custom() }, + { value: "disabled", label: "Disabled" }, + { value: "self-signed", label: "Self-signed" }, + { value: "custom", label: "Custom" }, ]} /> @@ -240,11 +239,11 @@ export default function SettingsAccessIndexRoute() { {tlsMode === "custom" && ( handleTlsCertChange(e.target.value)} />
@@ -274,14 +273,14 @@ export default function SettingsAccessIndexRoute() { )} {loaderData.authMode === "password" ? (
@@ -308,24 +308,24 @@ export default function SettingsAdvancedRoute() { {isOnDevice && (
setSSHKey(e.target.value)} - placeholder={m.advanced_ssh_public_key_placeholder()} + placeholder="Enter your SSH public key" />

- {m.advanced_ssh_default_user()}root. + The default SSH user isroot.

@@ -335,16 +335,16 @@ export default function SettingsAdvancedRoute() {
setUpdateTarget(e.target.value)} @@ -352,7 +352,7 @@ export default function SettingsAdvancedRoute() { {(updateTarget === "app" || updateTarget === "both") && ( setAppVersion(e.target.value)} @@ -361,7 +361,7 @@ export default function SettingsAdvancedRoute() { {(updateTarget === "system" || updateTarget === "both") && ( setSystemVersion(e.target.value)} @@ -369,21 +369,21 @@ export default function SettingsAdvancedRoute() { )}

- {m.advanced_version_update_helper()}{" "} + Find available versions on the{" "} - {m.advanced_version_update_github_link()} + JetKVM releases page

setResetConfig(e.target.checked)} /> @@ -400,7 +400,7 @@ export default function SettingsAdvancedRoute() {
); -} +} \ No newline at end of file diff --git a/ui/src/routes/devices.$id.settings.general.reboot.tsx b/ui/src/routes/devices.$id.settings.general.reboot.tsx index 63b10f61..4c61f3c4 100644 --- a/ui/src/routes/devices.$id.settings.general.reboot.tsx +++ b/ui/src/routes/devices.$id.settings.general.reboot.tsx @@ -1,7 +1,7 @@ -import { useNavigate } from "react-router"; import { useCallback, useState } from "react"; +import { useNavigate } from "react-router"; +import { useJsonRpc } from "@hooks/useJsonRpc"; -import { useJsonRpc } from "@/hooks/useJsonRpc"; import { Button } from "@components/Button"; import { useFailsafeModeStore } from "@/hooks/stores"; import { sleep } from "@/utils"; @@ -29,29 +29,25 @@ export default function SettingsGeneralRebootRoute() { const onConfirmUpdate = useCallback(async () => { setIsRebooting(true); - // This is where we send the RPC to the golang binary - send("reboot", { force: true }); + send("reboot", { force: true }); await new Promise(resolve => setTimeout(resolve, REBOOT_REDIRECT_DELAY_MS)); setFailsafeMode(false, ""); navigateTo("/"); }, [navigateTo, send, setFailsafeMode]); - { - /* TODO: Migrate to using URLs instead of the global state. To simplify the refactoring, we'll keep the global state for now. */ - } - return navigate("..")} onConfirmUpdate={onConfirmUpdate} />; + return ; } export function Dialog({ isRebooting, onClose, onConfirmUpdate, -}: { +}: Readonly<{ isRebooting: boolean; onClose: () => void; onConfirmUpdate: () => void; -}) { +}>) { return (
@@ -97,4 +93,4 @@ function ConfirmationBox({
); -} +} \ No newline at end of file diff --git a/ui/src/routes/devices.$id.settings.general.update.tsx b/ui/src/routes/devices.$id.settings.general.update.tsx index 1e489be3..84d5377e 100644 --- a/ui/src/routes/devices.$id.settings.general.update.tsx +++ b/ui/src/routes/devices.$id.settings.general.update.tsx @@ -1,15 +1,14 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useLocation, useNavigate, useSearchParams } from "react-router"; - import { useJsonRpc } from "@hooks/useJsonRpc"; import { UpdateState, useUpdateStore } from "@hooks/stores"; import { useDeviceUiNavigation } from "@hooks/useAppNavigation"; import { useVersion } from "@hooks/useVersion"; +import UpdatingStatusCard, { type UpdatePart } from "@components/UpdatingStatusCard"; + import { Button } from "@components/Button"; import Card from "@components/Card"; import LoadingSpinner from "@components/LoadingSpinner"; -import UpdatingStatusCard, { type UpdatePart } from "@components/UpdatingStatusCard"; -import { m } from "@localizations/messages.js"; import { sleep } from "@/utils"; import { checkUpdateComponents, SystemVersionInfo, UpdateComponents, updateParams } from "@/utils/jsonrpc"; @@ -236,10 +235,10 @@ function LoadingState({

- Checking for updates... + Checking for updates…

- We{"'"}re ensuring your device has the latest features and improvements. + We're ensuring your device has the latest features and improvements.

@@ -302,17 +301,18 @@ function UpdatingDeviceState({ const verifiedAt = otaState[`${type}VerifiedAt`]; const updatedAt = otaState[`${type}UpdatedAt`]; + const update_type = () => (type === "system" ? "System" : "App"); if (!otaState.metadataFetchedAt) { - return "Fetching update information..."; + return "Fetching update information…"; } else if (!downloadFinishedAt) { - return `Downloading ${type} update...`; + return `Downloading ${update_type()} update…`; } else if (!verifiedAt) { - return `Verifying ${type} update...`; + return `Verifying ${update_type()} update…`; } else if (!updatedAt) { - return `Installing ${type} update...`; + return `Installing ${update_type()} update…`; } else { - return `Awaiting reboot`; + return "Awaiting reboot"; } }; @@ -352,7 +352,6 @@ function UpdatingDeviceState({ }; }, [otaState]); - return (
@@ -361,7 +360,7 @@ function UpdatingDeviceState({ Updating your device

- Please don{"'"}t turn off your device. This process may take a few minutes. + Please don't turn off your device. This process may take a few minutes.

@@ -370,7 +369,7 @@ function UpdatingDeviceState({
- Rebooting to complete the update... + Rebooting to complete the update…
@@ -449,27 +448,26 @@ function UpdateAvailableState({

- Update Available + Update available

- A new update is available to enhance system performance and improve - compatibility. We recommend updating to ensure everything runs smoothly. + A new update is available to enhance system performance and improve compatibility. We recommend updating to ensure everything runs smoothly.

{versionInfo?.systemUpdateAvailable ? ( <> - Linux System Update: {versionInfo?.local?.systemVersion} {versionInfo?.remote?.systemVersion} + System: {versionInfo?.local?.systemVersion} {versionInfo?.remote?.systemVersion}
) : null} {versionInfo?.appUpdateAvailable ? ( <> - App Update: {versionInfo?.local?.appVersion} {versionInfo?.remote?.appVersion} + App: {versionInfo?.local?.appVersion} {versionInfo?.remote?.appVersion} ) : null} {versionInfo?.willDisableAutoUpdate ? (

- You{"'"}re about to manually change your device version. Auto-update will be disabled after the update is completed to prevent accidental updates. + You're about to manually change your device version. Auto-update will be disabled after the update is completed to prevent accidental updates.

) : null}

@@ -490,8 +488,7 @@ function UpdateCompletedState({ onClose }: { onClose: () => void }) { Update Completed Successfully

- Your device has been successfully updated to the latest version. Enjoy the new - features and improvements! + Your device has been successfully updated to the latest version. Enjoy the new features and improvements!

); -} +} \ No newline at end of file diff --git a/ui/src/routes/devices.$id.settings.hardware.tsx b/ui/src/routes/devices.$id.settings.hardware.tsx index 5c625bb5..62e05c27 100644 --- a/ui/src/routes/devices.$id.settings.hardware.tsx +++ b/ui/src/routes/devices.$id.settings.hardware.tsx @@ -1,502 +1,237 @@ -import { useCallback, useEffect, useState } from "react"; -import { useSettingsStore } from "@hooks/stores"; -import { JsonRpcError, JsonRpcResponse, useJsonRpc } from "@hooks/useJsonRpc"; -import { useDeviceUiNavigation } from "@hooks/useAppNavigation"; -import { SystemVersionInfo } from "@hooks/useVersion"; +import { useEffect, useState } from "react"; +import { BacklightSettings, useSettingsStore } from "@hooks/stores"; +import { JsonRpcResponse, useJsonRpc } from "@hooks/useJsonRpc"; -import { Button } from "@components/Button"; -import Checkbox, { CheckboxWithLabel } from "@components/Checkbox"; -import { ConfirmDialog } from "@components/ConfirmDialog"; -import { GridCard } from "@components/Card"; +import { Checkbox } from "@components/Checkbox"; +import { FeatureFlag } from "@components/FeatureFlag"; +import { SelectMenuBasic } from "@components/SelectMenuBasic"; import { SettingsItem } from "@components/SettingsItem"; import { SettingsPageHeader } from "@components/SettingsPageheader"; +import { SettingsSectionHeader } from "@components/SettingsSectionHeader"; import { NestedSettingsGroup } from "@components/NestedSettingsGroup"; -import { TextAreaWithLabel } from "@components/TextArea"; -import { InputFieldWithLabel } from "@components/InputField"; -import { SelectMenuBasic } from "@components/SelectMenuBasic"; -import { isOnDevice } from "@/main"; +import { UsbDeviceSetting } from "@components/UsbDeviceSetting"; +import { UsbInfoSetting } from "@components/UsbInfoSetting"; import notifications from "@/notifications"; -import { sleep } from "@/utils"; -import { checkUpdateComponents, UpdateComponents } from "@/utils/jsonrpc"; - -import { FeatureFlag } from "../components/FeatureFlag"; - -export default function SettingsAdvancedRoute() { +export default function SettingsHardwareRoute() { const { send } = useJsonRpc(); - const { navigateTo } = useDeviceUiNavigation(); - - const [sshKey, setSSHKey] = useState(""); - const { setDeveloperMode } = useSettingsStore(); - const [devChannel, setDevChannel] = useState(false); - const [usbEmulationEnabled, setUsbEmulationEnabled] = useState(false); - const [showLoopbackWarning, setShowLoopbackWarning] = useState(false); - const [localLoopbackOnly, setLocalLoopbackOnly] = useState(false); - const [updateTarget, setUpdateTarget] = useState("app"); - const [appVersion, setAppVersion] = useState(""); - const [systemVersion, setSystemVersion] = useState(""); - const [resetConfig, setResetConfig] = useState(false); - const [versionChangeAcknowledged, setVersionChangeAcknowledged] = useState(false); - const [customVersionUpdateLoading, setCustomVersionUpdateLoading] = useState(false); const settings = useSettingsStore(); + const { displayRotation, setDisplayRotation } = useSettingsStore(); + const [powerSavingEnabled, setPowerSavingEnabled] = useState(false); + + const handleDisplayRotationChange = (rotation: string) => { + setDisplayRotation(rotation); + handleDisplayRotationSave(); + }; + + const handleDisplayRotationSave = () => { + send("setDisplayRotation", { params: { rotation: displayRotation } }, (resp: JsonRpcResponse) => { + if ("error" in resp) { + notifications.error( + `Failed to set display orientation: ${resp.error.data || "Unknown error"}`, + ); + return; + } + notifications.success("Display orientation updated successfully"); + }); + }; + + const { backlightSettings, setBacklightSettings } = useSettingsStore(); + + const handleBacklightSettingsChange = (settings: BacklightSettings) => { + // If the user has set the display to dim after it turns off, set the dim_after + // value to never. + if (settings.dim_after > settings.off_after && settings.off_after != 0) { + settings.dim_after = 0; + } + + setBacklightSettings(settings); + handleBacklightSettingsSave(settings); + }; + + const handleBacklightSettingsSave = (backlightSettings: BacklightSettings) => { + send("setBacklightSettings", { params: backlightSettings }, (resp: JsonRpcResponse) => { + if ("error" in resp) { + notifications.error( + `Failed to set backlight settings: ${resp.error.data || "Unknown error"}`, + ); + return; + } + notifications.success("Backlight settings updated successfully"); + }); + }; + + const handleBacklightMaxBrightnessChange = (max_brightness: number) => { + const settings = { ...backlightSettings, max_brightness }; + handleBacklightSettingsChange(settings); + }; + + const handleBacklightDimAfterChange = (dim_after: number) => { + const settings = { ...backlightSettings, dim_after }; + handleBacklightSettingsChange(settings); + }; + + const handleBacklightOffAfterChange = (off_after: number) => { + const settings = { ...backlightSettings, off_after }; + handleBacklightSettingsChange(settings); + }; + + const handlePowerSavingChange = (enabled: boolean) => { + setPowerSavingEnabled(enabled); + const duration = enabled ? 90 : -1; + send("setVideoSleepMode", { duration }, (resp: JsonRpcResponse) => { + if ("error" in resp) { + notifications.error(`Failed to set power saving mode: ${resp.error.data || "Unknown error"}`); + setPowerSavingEnabled(!enabled); // Attempt to revert on error + return; + } + notifications.success(enabled ? 'Power saving mode enabled' : 'Power saving mode disabled'); + }); + }; useEffect(() => { - send("getDevModeState", {}, (resp: JsonRpcResponse) => { - if ("error" in resp) return; - const result = resp.result as { enabled: boolean }; - setDeveloperMode(result.enabled); - }); - - send("getSSHKeyState", {}, (resp: JsonRpcResponse) => { - if ("error" in resp) return; - setSSHKey(resp.result as string); - }); - - send("getUsbEmulationState", {}, (resp: JsonRpcResponse) => { - if ("error" in resp) return; - setUsbEmulationEnabled(resp.result as boolean); - }); - - send("getDevChannelState", {}, (resp: JsonRpcResponse) => { - if ("error" in resp) return; - setDevChannel(resp.result as boolean); - }); - - send("getLocalLoopbackOnly", {}, (resp: JsonRpcResponse) => { - if ("error" in resp) return; - setLocalLoopbackOnly(resp.result as boolean); - }); - }, [send, setDeveloperMode]); - - const getUsbEmulationState = useCallback(() => { - send("getUsbEmulationState", {}, (resp: JsonRpcResponse) => { - if ("error" in resp) return; - setUsbEmulationEnabled(resp.result as boolean); - }); - }, [send]); - - const handleUsbEmulationToggle = useCallback( - (enabled: boolean) => { - send("setUsbEmulationState", { enabled: enabled }, (resp: JsonRpcResponse) => { - 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 handleResetConfig = useCallback(() => { - send("resetConfig", {}, (resp: JsonRpcResponse) => { + send("getBacklightSettings", {}, (resp: JsonRpcResponse) => { if ("error" in resp) { - notifications.error( - `Failed to reset configuration: ${resp.error.data || "Unknown error"}`, + return notifications.error( + `Failed to get backlight settings: ${resp.error.data || "Unknown error"}`, ); + } + const result = resp.result as BacklightSettings; + setBacklightSettings(result); + }); + }, [send, setBacklightSettings]); + + useEffect(() => { + send("getVideoSleepMode", {}, (resp: JsonRpcResponse) => { + if ("error" in resp) { + console.error("Failed to get power saving mode:", resp.error); return; } - notifications.success("Configuration reset to default successfully"); + const result = resp.result as { enabled: boolean; duration: number }; + setPowerSavingEnabled(result.duration >= 0); }); }, [send]); - const handleUpdateSSHKey = useCallback(() => { - send("setSSHKeyState", { sshKey }, (resp: JsonRpcResponse) => { - 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 handleDevModeChange = useCallback( - (developerMode: boolean) => { - send("setDevModeState", { enabled: developerMode }, (resp: JsonRpcResponse) => { - if ("error" in resp) { - notifications.error( - `Failed to set dev mode: ${resp.error.data || "Unknown error"}`, - ); - return; - } - setDeveloperMode(developerMode); - }); - }, - [send, setDeveloperMode], - ); - - const handleDevChannelChange = useCallback( - (enabled: boolean) => { - send("setDevChannelState", { enabled }, (resp: JsonRpcResponse) => { - if ("error" in resp) { - notifications.error( - `Failed to set dev channel state: ${resp.error.data || "Unknown error"}`, - ); - return; - } - setDevChannel(enabled); - }); - }, - [send, setDevChannel], - ); - - const applyLoopbackOnlyMode = useCallback( - (enabled: boolean) => { - send("setLocalLoopbackOnly", { enabled }, (resp: JsonRpcResponse) => { - if ("error" in resp) { - notifications.error( - `Failed to ${enabled ? "enable" : "disable"} loopback-only mode: ${resp.error.data || "Unknown error"}`, - ); - return; - } - setLocalLoopbackOnly(enabled); - if (enabled) { - notifications.success( - "Loopback-only mode enabled. Restart your device to apply.", - ); - } else { - notifications.success( - "Loopback-only mode disabled. Restart your device to apply.", - ); - } - }); - }, - [send, setLocalLoopbackOnly], - ); - - const handleLoopbackOnlyModeChange = useCallback( - (enabled: boolean) => { - // If trying to enable loopback-only mode, show warning first - if (enabled) { - setShowLoopbackWarning(true); - } else { - // If disabling, just proceed - applyLoopbackOnlyMode(false); - } - }, - [applyLoopbackOnlyMode, setShowLoopbackWarning], - ); - - const confirmLoopbackModeEnable = useCallback(() => { - applyLoopbackOnlyMode(true); - setShowLoopbackWarning(false); - }, [applyLoopbackOnlyMode, setShowLoopbackWarning]); - - const handleVersionUpdateError = useCallback((error?: JsonRpcError | string) => { - notifications.error( - m.advanced_error_version_update({ - error: typeof error === "string" ? error : (error?.data ?? error?.message ?? m.unknown_error()) - }), - { duration: 1000 * 15 } // 15 seconds - ); - setCustomVersionUpdateLoading(false); - }, []); - - const handleCustomVersionUpdate = useCallback(async () => { - const components: UpdateComponents = {}; - if (["app", "both"].includes(updateTarget) && appVersion) components.app = appVersion; - if (["system", "both"].includes(updateTarget) && systemVersion) components.system = systemVersion; - let versionInfo: SystemVersionInfo | undefined; - - try { - // we do not need to set it to false if check succeeds, - // because it will be redirected to the update page later - setCustomVersionUpdateLoading(true); - versionInfo = await checkUpdateComponents({ - components, - }, devChannel); - } catch (error: unknown) { - const jsonRpcError = error as JsonRpcError; - handleVersionUpdateError(jsonRpcError); - return; - } - - let hasUpdate = false; - - const pageParams = new URLSearchParams(); - if (components.app && versionInfo?.remote?.appVersion && versionInfo?.appUpdateAvailable) { - hasUpdate = true; - pageParams.set("custom_app_version", versionInfo.remote?.appVersion); - } - if (components.system && versionInfo?.remote?.systemVersion && versionInfo?.systemUpdateAvailable) { - hasUpdate = true; - pageParams.set("custom_system_version", versionInfo.remote?.systemVersion); - } - pageParams.set("reset_config", resetConfig.toString()); - - if (!hasUpdate) { - handleVersionUpdateError("No update available"); - return; - } - - // Navigate to update page - navigateTo(`/settings/general/update?${pageParams.toString()}`); - }, [ - updateTarget, appVersion, systemVersion, devChannel, - navigateTo, resetConfig, handleVersionUpdateError, - setCustomVersionUpdateLoading - ]); - return (
-
- { - handleDevChannelChange(e.target.checked); + handleDisplayRotationChange(e.target.value); }} /> - handleDevModeChange(e.target.checked)} - /> - - {settings.developerMode ? ( - - -
- - - -
-
-

- {m.advanced_developer_mode_enabled_title()} -

-
-
    -
  • {m.advanced_developer_mode_warning_security()}
  • -
  • {m.advanced_developer_mode_warning_risks()}
  • -
-
-
-
- {m.advanced_developer_mode_warning_advanced()} -
-
-
-
- - {isOnDevice && ( -
- - setSSHKey(e.target.value)} - placeholder={m.advanced_ssh_public_key_placeholder()} - /> -

- {m.advanced_ssh_default_user()}root. -

-
-
-
- )} - - -
- - - setUpdateTarget(e.target.value)} - /> - - {(updateTarget === "app" || updateTarget === "both") && ( - setAppVersion(e.target.value)} - /> - )} - - {(updateTarget === "system" || updateTarget === "both") && ( - setSystemVersion(e.target.value)} - /> - )} - -

- {m.advanced_version_update_helper()}{" "} - - {m.advanced_version_update_github_link()} - -

- -
- setResetConfig(e.target.checked)} - /> -
- -
- setVersionChangeAcknowledged(e.target.checked)} - /> -
- -
-
-
- ) : null} - - - handleLoopbackOnlyModeChange(e.target.checked)} - /> - - - - - - { - settings.setDebugMode(e.target.checked); + handleBacklightMaxBrightnessChange(Number.parseInt(e.target.value)); }} /> - - {settings.debugMode && ( + {backlightSettings.max_brightness != 0 && ( -
- { - setShowLoopbackWarning(false); - }} - title="Enable Loopback-Only Mode?" - description={ - <> -

- WARNING: This will restrict web interface access to localhost (127.0.0.1) - only. -

-

Before enabling this feature, make sure you have either:

-
    -
  • SSH access configured and tested
  • -
  • Cloud access enabled and working
  • -
- - } - variant="warning" - confirmText="I Understand, Enable Anyway" - onConfirm={confirmLoopbackModeEnable} - /> + +
+
+ + + handlePowerSavingChange(e.target.checked)} + /> + +
+ + + + + + + + +
); } \ No newline at end of file