Compare commits

..

No commits in common. "a69f7c9c504a286acaba43d03be190aee064112d" and "325de4a33a842ddf51c46914d18ea18087c983c6" have entirely different histories.

8 changed files with 113 additions and 213 deletions

View File

@ -100,18 +100,6 @@
"advanced_update_ssh_key_button": "Update SSH Key",
"advanced_usb_emulation_description": "Control the USB emulation state",
"advanced_usb_emulation_title": "USB Emulation",
"advanced_version_update_app_label": "App Version",
"advanced_version_update_button": "Update to Version",
"advanced_version_update_description": "Install a specific version from GitHub releases",
"advanced_version_update_github_link": "JetKVM releases page",
"advanced_version_update_helper": "Find available versions on the",
"advanced_version_update_system_label": "System Version",
"advanced_version_update_target_app": "App only",
"advanced_version_update_target_both": "Both App and System",
"advanced_version_update_target_label": "What to update",
"advanced_version_update_target_system": "System only",
"advanced_version_update_title": "Update to Specific Version",
"advanced_error_version_update": "Failed to initiate version update: {error}",
"already_adopted_new_owner": "If you're the new owner, please ask the previous owner to de-register the device from their account in the cloud dashboard. If you believe this is an error, contact our support team for assistance.",
"already_adopted_other_user": "This device is currently registered to another user in our cloud dashboard.",
"already_adopted_return_to_dashboard": "Return to Dashboard",

View File

@ -1,22 +0,0 @@
import { cx } from "@/cva.config";
interface NestedSettingsGroupProps {
readonly children: React.ReactNode;
readonly className?: string;
}
export function NestedSettingsGroup(props: NestedSettingsGroupProps) {
const { children, className } = props;
return (
<div
className={cx(
"space-y-4 border-l-2 border-slate-200 ml-2 pl-4 dark:border-slate-700",
className,
)}
>
{children}
</div>
);
}

View File

@ -11,7 +11,6 @@ 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 api from "@/api";
import notifications from "@/notifications";
@ -238,30 +237,39 @@ export default function SettingsAccessIndexRoute() {
</SettingsItem>
{tlsMode === "custom" && (
<NestedSettingsGroup className="mt-4">
<SettingsItem
title={m.access_tls_certificate_title()}
description={m.access_tls_certificate_description()}
/>
<TextAreaWithLabel
label={m.access_certificate_label()}
rows={3}
placeholder={
"-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----"
}
value={tlsCert}
onChange={e => handleTlsCertChange(e.target.value)}
/>
<TextAreaWithLabel
label={m.access_private_key_label()}
description={m.access_private_key_description()}
rows={3}
placeholder={
"-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
}
value={tlsKey}
onChange={e => handleTlsKeyChange(e.target.value)}
/>
<div className="mt-4 space-y-4">
<div className="space-y-4">
<SettingsItem
title={m.access_tls_certificate_title()}
description={m.access_tls_certificate_description()}
/>
<div className="space-y-4">
<TextAreaWithLabel
label={m.access_certificate_label()}
rows={3}
placeholder={
"-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----"
}
value={tlsCert}
onChange={e => handleTlsCertChange(e.target.value)}
/>
</div>
<div className="space-y-4">
<div className="space-y-4">
<TextAreaWithLabel
label={m.access_private_key_label()}
description={m.access_private_key_description()}
rows={3}
placeholder={
"-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
}
value={tlsKey}
onChange={e => handleTlsKeyChange(e.target.value)}
/>
</div>
</div>
</div>
<div className="flex items-center gap-x-2">
<Button
size="SM"
@ -270,7 +278,7 @@ export default function SettingsAccessIndexRoute() {
onClick={handleCustomTlsUpdate}
/>
</div>
</NestedSettingsGroup>
</div>
)}
<SettingsItem
@ -344,7 +352,7 @@ export default function SettingsAccessIndexRoute() {
</SettingsItem>
{selectedProvider === "custom" && (
<NestedSettingsGroup className="mt-4">
<div className="mt-4 space-y-4">
<div className="flex items-end gap-x-2">
<InputFieldWithLabel
size="SM"
@ -363,7 +371,7 @@ export default function SettingsAccessIndexRoute() {
placeholder="https://app.example.com"
/>
</div>
</NestedSettingsGroup>
</div>
)}
</>
)}

View File

@ -2,24 +2,19 @@ import { useCallback, useEffect, useState } from "react";
import { useSettingsStore } from "@hooks/stores";
import { JsonRpcResponse, useJsonRpc } from "@hooks/useJsonRpc";
import { useDeviceUiNavigation } from "@hooks/useAppNavigation";
import { Button } from "@components/Button";
import Checkbox from "@components/Checkbox";
import { ConfirmDialog } from "@components/ConfirmDialog";
import { GridCard } from "@components/Card";
import { SettingsItem } from "@components/SettingsItem";
import { SettingsPageHeader } from "@components/SettingsPageheader";
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 notifications from "@/notifications";
import { m } from "@localizations/messages.js";
export default function SettingsAdvancedRoute() {
const { send } = useJsonRpc();
const { navigateTo } = useDeviceUiNavigation();
const [sshKey, setSSHKey] = useState<string>("");
const { setDeveloperMode } = useSettingsStore();
@ -27,9 +22,6 @@ export default function SettingsAdvancedRoute() {
const [usbEmulationEnabled, setUsbEmulationEnabled] = useState(false);
const [showLoopbackWarning, setShowLoopbackWarning] = useState(false);
const [localLoopbackOnly, setLocalLoopbackOnly] = useState(false);
const [updateTarget, setUpdateTarget] = useState<string>("app");
const [appVersion, setAppVersion] = useState<string>("");
const [systemVersion, setSystemVersion] = useState<string>("");
const settings = useSettingsStore();
@ -180,21 +172,6 @@ export default function SettingsAdvancedRoute() {
setShowLoopbackWarning(false);
}, [applyLoopbackOnlyMode, setShowLoopbackWarning]);
const handleVersionUpdate = useCallback(() => {
// TODO: Add version params to tryUpdate
console.log("tryUpdate", updateTarget, appVersion, systemVersion);
send("tryUpdate", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(
m.advanced_error_version_update({ error: resp.error.data || m.unknown_error() })
);
return;
}
// Navigate to update page
navigateTo("/settings/general/update");
});
}, [updateTarget, appVersion, systemVersion, send, navigateTo]);
return (
<div className="space-y-4">
<SettingsPageHeader
@ -223,129 +200,41 @@ export default function SettingsAdvancedRoute() {
onChange={e => handleDevModeChange(e.target.checked)}
/>
</SettingsItem>
{settings.developerMode ? (
<NestedSettingsGroup>
<GridCard>
<div className="flex items-start gap-x-4 p-4 select-none">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
className="mt-1 h-8 w-8 shrink-0 text-amber-600 dark:text-amber-500"
>
<path
fillRule="evenodd"
d="M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z"
clipRule="evenodd"
/>
</svg>
<div className="space-y-3">
<div className="space-y-2">
<h3 className="text-base font-bold text-slate-900 dark:text-white">
{m.advanced_developer_mode_enabled_title()}
</h3>
<div>
<ul className="list-disc space-y-1 pl-5 text-xs text-slate-700 dark:text-slate-300">
<li>{m.advanced_developer_mode_warning_security()}</li>
<li>{m.advanced_developer_mode_warning_risks()}</li>
</ul>
</div>
</div>
<div className="text-xs text-slate-700 dark:text-slate-300">
{m.advanced_developer_mode_warning_advanced()}
{settings.developerMode && (
<GridCard>
<div className="flex items-start gap-x-4 p-4 select-none">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
className="mt-1 h-8 w-8 shrink-0 text-amber-600 dark:text-amber-500"
>
<path
fillRule="evenodd"
d="M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z"
clipRule="evenodd"
/>
</svg>
<div className="space-y-3">
<div className="space-y-2">
<h3 className="text-base font-bold text-slate-900 dark:text-white">
{m.advanced_developer_mode_enabled_title()}
</h3>
<div>
<ul className="list-disc space-y-1 pl-5 text-xs text-slate-700 dark:text-slate-300">
<li>{m.advanced_developer_mode_warning_security()}</li>
<li>{m.advanced_developer_mode_warning_risks()}</li>
</ul>
</div>
</div>
</div>
</GridCard>
{isOnDevice && (
<div className="space-y-4">
<SettingsItem
title={m.advanced_ssh_access_title()}
description={m.advanced_ssh_access_description()}
/>
<TextAreaWithLabel
label={m.advanced_ssh_public_key_label()}
value={sshKey || ""}
rows={3}
onChange={e => setSSHKey(e.target.value)}
placeholder={m.advanced_ssh_public_key_placeholder()}
/>
<p className="text-xs text-slate-600 dark:text-slate-400">
{m.advanced_ssh_default_user()}<strong>root</strong>.
</p>
<div className="flex items-center gap-x-2">
<Button
size="SM"
theme="primary"
text={m.advanced_update_ssh_key_button()}
onClick={handleUpdateSSHKey}
/>
<div className="text-xs text-slate-700 dark:text-slate-300">
{m.advanced_developer_mode_warning_advanced()}
</div>
</div>
)}
<div className="space-y-4">
<SettingsItem
title={m.advanced_version_update_title()}
description={m.advanced_version_update_description()}
/>
<SelectMenuBasic
label={m.advanced_version_update_target_label()}
options={[
{ value: "app", label: m.advanced_version_update_target_app() },
{ value: "system", label: m.advanced_version_update_target_system() },
{ value: "both", label: m.advanced_version_update_target_both() },
]}
value={updateTarget}
onChange={e => setUpdateTarget(e.target.value)}
/>
{(updateTarget === "app" || updateTarget === "both") && (
<InputFieldWithLabel
label={m.advanced_version_update_app_label()}
placeholder="0.4.9"
value={appVersion}
onChange={e => setAppVersion(e.target.value)}
/>
)}
{(updateTarget === "system" || updateTarget === "both") && (
<InputFieldWithLabel
label={m.advanced_version_update_system_label()}
placeholder="0.4.9"
value={systemVersion}
onChange={e => setSystemVersion(e.target.value)}
/>
)}
<p className="text-xs text-slate-600 dark:text-slate-400">
{m.advanced_version_update_helper()}{" "}
<a
href="https://github.com/jetkvm/kvm/releases"
target="_blank"
rel="noopener noreferrer"
className="font-medium text-blue-700 hover:underline dark:text-blue-500"
>
{m.advanced_version_update_github_link()}
</a>
</p>
<Button
size="SM"
theme="primary"
text={m.advanced_version_update_button()}
disabled={
(updateTarget === "app" && !appVersion) ||
(updateTarget === "system" && !systemVersion) ||
(updateTarget === "both" && (!appVersion || !systemVersion))
}
onClick={handleVersionUpdate}
/>
</div>
</NestedSettingsGroup>
) : null}
</GridCard>
)}
<SettingsItem
title={m.advanced_loopback_only_title()}
@ -357,7 +246,34 @@ export default function SettingsAdvancedRoute() {
/>
</SettingsItem>
{isOnDevice && settings.developerMode && (
<div className="space-y-4">
<SettingsItem
title={m.advanced_ssh_access_title()}
description={m.advanced_ssh_access_description()}
/>
<div className="space-y-4">
<TextAreaWithLabel
label={m.advanced_ssh_public_key_label()}
value={sshKey || ""}
rows={3}
onChange={e => setSSHKey(e.target.value)}
placeholder={m.advanced_ssh_public_key_placeholder()}
/>
<p className="text-xs text-slate-600 dark:text-slate-400">
{m.advanced_ssh_default_user()}<strong>root</strong>.
</p>
<div className="flex items-center gap-x-2">
<Button
size="SM"
theme="primary"
text={m.advanced_update_ssh_key_button()}
onClick={handleUpdateSSHKey}
/>
</div>
</div>
</div>
)}
<SettingsItem
title={m.advanced_troubleshooting_mode_title()}
@ -372,7 +288,7 @@ export default function SettingsAdvancedRoute() {
</SettingsItem>
{settings.debugMode && (
<NestedSettingsGroup>
<>
<SettingsItem
title={m.advanced_usb_emulation_title()}
description={m.advanced_usb_emulation_description()}
@ -401,7 +317,7 @@ export default function SettingsAdvancedRoute() {
}}
/>
</SettingsItem>
</NestedSettingsGroup>
</>
)}
</div>

View File

@ -12,11 +12,13 @@ import notifications from "@/notifications";
import { getLocale, setLocale, locales, baseLocale } from '@localizations/runtime.js';
import { m } from "@localizations/messages.js";
import { deleteCookie, map_locale_code_to_name } from "@/utils";
import { useVersion } from "@hooks/useVersion";
export default function SettingsGeneralRoute() {
const { send } = useJsonRpc();
const { navigateTo } = useDeviceUiNavigation();
const [autoUpdate, setAutoUpdate] = useState(true);
const { isOnDevVersion } = useVersion();
const currentVersions = useDeviceStore(state => {
const { appVersion, systemVersion } = state;
if (!appVersion || !systemVersion) return null;
@ -73,6 +75,10 @@ export default function SettingsGeneralRoute() {
notifications.success(m.locale_change_success({ locale: validLocale || m.locale_auto() }));
};
const downgradeAvailable = useMemo(() => {
return isOnDevVersion;
}, [isOnDevVersion]);
return (
<div className="space-y-4">
<SettingsPageHeader
@ -108,6 +114,12 @@ export default function SettingsGeneralRoute() {
}
/>
<div className="flex items-center justify-start gap-x-2">
{downgradeAvailable && <Button
size="SM"
theme="danger"
text={m.general_check_for_stable_updates()}
onClick={() => navigateTo("./update?channel=stable")}
/>}
<Button
size="SM"
theme="light"

View File

@ -1,5 +1,5 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useLocation, useNavigate } from "react-router";
import { useLocation, useNavigate, useSearchParams } from "react-router";
import { useJsonRpc } from "@hooks/useJsonRpc";
import { UpdateState, useUpdateStore } from "@hooks/stores";

View File

@ -8,7 +8,6 @@ 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 { UsbDeviceSetting } from "@components/UsbDeviceSetting";
import { UsbInfoSetting } from "@components/UsbInfoSetting";
import notifications from "@/notifications";
@ -157,7 +156,7 @@ export default function SettingsHardwareRoute() {
/>
</SettingsItem>
{backlightSettings.max_brightness != 0 && (
<NestedSettingsGroup>
<>
<SettingsItem
title={m.hardware_dim_display_after_title()}
description={m.hardware_dim_display_after_description()}
@ -199,7 +198,7 @@ export default function SettingsHardwareRoute() {
}}
/>
</SettingsItem>
</NestedSettingsGroup>
</>
)}
<p className="text-xs text-slate-600 dark:text-slate-400">
{m.hardware_display_wake_up_note()}

View File

@ -7,7 +7,6 @@ import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
import { SettingsItem } from "@components/SettingsItem";
import { SettingsPageHeader } from "@components/SettingsPageheader";
import { SelectMenuBasic } from "@components/SelectMenuBasic";
import { NestedSettingsGroup } from "@components/NestedSettingsGroup";
import Fieldset from "@components/Fieldset";
import notifications from "@/notifications";
import { m } from "@localizations/messages.js";
@ -175,7 +174,7 @@ export default function SettingsVideoRoute() {
description={m.video_enhancement_description()}
/>
<NestedSettingsGroup>
<div className="space-y-4 pl-4">
<SettingsItem
title={m.video_saturation_title()}
description={m.video_saturation_description({ value: videoSaturation.toFixed(1) })}
@ -233,7 +232,7 @@ export default function SettingsVideoRoute() {
}}
/>
</div>
</NestedSettingsGroup>
</div>
<Fieldset disabled={edidLoading} className="space-y-2">
<SettingsItem
title={m.video_edid_title()}