mirror of https://github.com/jetkvm/kvm.git
refactor: mprove UI settings structure with NestedSettingsGroup
This commit is contained in:
parent
85f7f60618
commit
b0e659b76e
|
|
@ -0,0 +1,22 @@
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -11,6 +11,7 @@ import { SelectMenuBasic } from "@components/SelectMenuBasic";
|
||||||
import { SettingsItem } from "@components/SettingsItem";
|
import { SettingsItem } from "@components/SettingsItem";
|
||||||
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
||||||
import { SettingsSectionHeader } from "@components/SettingsSectionHeader";
|
import { SettingsSectionHeader } from "@components/SettingsSectionHeader";
|
||||||
|
import { NestedSettingsGroup } from "@components/NestedSettingsGroup";
|
||||||
import { TextAreaWithLabel } from "@components/TextArea";
|
import { TextAreaWithLabel } from "@components/TextArea";
|
||||||
import api from "@/api";
|
import api from "@/api";
|
||||||
import notifications from "@/notifications";
|
import notifications from "@/notifications";
|
||||||
|
|
@ -237,39 +238,30 @@ export default function SettingsAccessIndexRoute() {
|
||||||
</SettingsItem>
|
</SettingsItem>
|
||||||
|
|
||||||
{tlsMode === "custom" && (
|
{tlsMode === "custom" && (
|
||||||
<div className="mt-4 space-y-4">
|
<NestedSettingsGroup className="mt-4">
|
||||||
<div className="space-y-4">
|
<SettingsItem
|
||||||
<SettingsItem
|
title={m.access_tls_certificate_title()}
|
||||||
title={m.access_tls_certificate_title()}
|
description={m.access_tls_certificate_description()}
|
||||||
description={m.access_tls_certificate_description()}
|
/>
|
||||||
/>
|
<TextAreaWithLabel
|
||||||
<div className="space-y-4">
|
label={m.access_certificate_label()}
|
||||||
<TextAreaWithLabel
|
rows={3}
|
||||||
label={m.access_certificate_label()}
|
placeholder={
|
||||||
rows={3}
|
"-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----"
|
||||||
placeholder={
|
}
|
||||||
"-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----"
|
value={tlsCert}
|
||||||
}
|
onChange={e => handleTlsCertChange(e.target.value)}
|
||||||
value={tlsCert}
|
/>
|
||||||
onChange={e => handleTlsCertChange(e.target.value)}
|
<TextAreaWithLabel
|
||||||
/>
|
label={m.access_private_key_label()}
|
||||||
</div>
|
description={m.access_private_key_description()}
|
||||||
|
rows={3}
|
||||||
<div className="space-y-4">
|
placeholder={
|
||||||
<div className="space-y-4">
|
"-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
|
||||||
<TextAreaWithLabel
|
}
|
||||||
label={m.access_private_key_label()}
|
value={tlsKey}
|
||||||
description={m.access_private_key_description()}
|
onChange={e => handleTlsKeyChange(e.target.value)}
|
||||||
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">
|
<div className="flex items-center gap-x-2">
|
||||||
<Button
|
<Button
|
||||||
size="SM"
|
size="SM"
|
||||||
|
|
@ -278,7 +270,7 @@ export default function SettingsAccessIndexRoute() {
|
||||||
onClick={handleCustomTlsUpdate}
|
onClick={handleCustomTlsUpdate}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</NestedSettingsGroup>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<SettingsItem
|
<SettingsItem
|
||||||
|
|
@ -352,7 +344,7 @@ export default function SettingsAccessIndexRoute() {
|
||||||
</SettingsItem>
|
</SettingsItem>
|
||||||
|
|
||||||
{selectedProvider === "custom" && (
|
{selectedProvider === "custom" && (
|
||||||
<div className="mt-4 space-y-4">
|
<NestedSettingsGroup className="mt-4">
|
||||||
<div className="flex items-end gap-x-2">
|
<div className="flex items-end gap-x-2">
|
||||||
<InputFieldWithLabel
|
<InputFieldWithLabel
|
||||||
size="SM"
|
size="SM"
|
||||||
|
|
@ -371,7 +363,7 @@ export default function SettingsAccessIndexRoute() {
|
||||||
placeholder="https://app.example.com"
|
placeholder="https://app.example.com"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</NestedSettingsGroup>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { ConfirmDialog } from "@components/ConfirmDialog";
|
||||||
import { GridCard } from "@components/Card";
|
import { GridCard } from "@components/Card";
|
||||||
import { SettingsItem } from "@components/SettingsItem";
|
import { SettingsItem } from "@components/SettingsItem";
|
||||||
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
||||||
|
import { NestedSettingsGroup } from "@components/NestedSettingsGroup";
|
||||||
import { TextAreaWithLabel } from "@components/TextArea";
|
import { TextAreaWithLabel } from "@components/TextArea";
|
||||||
import { isOnDevice } from "@/main";
|
import { isOnDevice } from "@/main";
|
||||||
import notifications from "@/notifications";
|
import notifications from "@/notifications";
|
||||||
|
|
@ -201,41 +202,69 @@ export default function SettingsAdvancedRoute() {
|
||||||
onChange={e => handleDevModeChange(e.target.checked)}
|
onChange={e => handleDevModeChange(e.target.checked)}
|
||||||
/>
|
/>
|
||||||
</SettingsItem>
|
</SettingsItem>
|
||||||
|
{settings.developerMode ? (
|
||||||
{settings.developerMode && (
|
<NestedSettingsGroup>
|
||||||
<GridCard>
|
<GridCard>
|
||||||
<div className="flex items-start gap-x-4 p-4 select-none">
|
<div className="flex items-start gap-x-4 p-4 select-none">
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
className="mt-1 h-8 w-8 shrink-0 text-amber-600 dark:text-amber-500"
|
className="mt-1 h-8 w-8 shrink-0 text-amber-600 dark:text-amber-500"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fillRule="evenodd"
|
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"
|
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"
|
clipRule="evenodd"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h3 className="text-base font-bold text-slate-900 dark:text-white">
|
<h3 className="text-base font-bold text-slate-900 dark:text-white">
|
||||||
{m.advanced_developer_mode_enabled_title()}
|
{m.advanced_developer_mode_enabled_title()}
|
||||||
</h3>
|
</h3>
|
||||||
<div>
|
<div>
|
||||||
<ul className="list-disc space-y-1 pl-5 text-xs text-slate-700 dark:text-slate-300">
|
<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_security()}</li>
|
||||||
<li>{m.advanced_developer_mode_warning_risks()}</li>
|
<li>{m.advanced_developer_mode_warning_risks()}</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-slate-700 dark:text-slate-300">
|
||||||
|
{m.advanced_developer_mode_warning_advanced()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-slate-700 dark:text-slate-300">
|
</div>
|
||||||
{m.advanced_developer_mode_warning_advanced()}
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
</GridCard>
|
</NestedSettingsGroup>
|
||||||
)}
|
) : null}
|
||||||
|
|
||||||
<SettingsItem
|
<SettingsItem
|
||||||
title={m.advanced_loopback_only_title()}
|
title={m.advanced_loopback_only_title()}
|
||||||
|
|
@ -247,34 +276,7 @@ export default function SettingsAdvancedRoute() {
|
||||||
/>
|
/>
|
||||||
</SettingsItem>
|
</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
|
<SettingsItem
|
||||||
title={m.advanced_troubleshooting_mode_title()}
|
title={m.advanced_troubleshooting_mode_title()}
|
||||||
|
|
@ -289,7 +291,7 @@ export default function SettingsAdvancedRoute() {
|
||||||
</SettingsItem>
|
</SettingsItem>
|
||||||
|
|
||||||
{settings.debugMode && (
|
{settings.debugMode && (
|
||||||
<>
|
<NestedSettingsGroup>
|
||||||
<SettingsItem
|
<SettingsItem
|
||||||
title={m.advanced_usb_emulation_title()}
|
title={m.advanced_usb_emulation_title()}
|
||||||
description={m.advanced_usb_emulation_description()}
|
description={m.advanced_usb_emulation_description()}
|
||||||
|
|
@ -320,7 +322,7 @@ export default function SettingsAdvancedRoute() {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</SettingsItem>
|
</SettingsItem>
|
||||||
</>
|
</NestedSettingsGroup>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,13 +12,11 @@ import notifications from "@/notifications";
|
||||||
import { getLocale, setLocale, locales, baseLocale } from '@localizations/runtime.js';
|
import { getLocale, setLocale, locales, baseLocale } from '@localizations/runtime.js';
|
||||||
import { m } from "@localizations/messages.js";
|
import { m } from "@localizations/messages.js";
|
||||||
import { deleteCookie, map_locale_code_to_name } from "@/utils";
|
import { deleteCookie, map_locale_code_to_name } from "@/utils";
|
||||||
import { useVersion } from "@hooks/useVersion";
|
|
||||||
|
|
||||||
export default function SettingsGeneralRoute() {
|
export default function SettingsGeneralRoute() {
|
||||||
const { send } = useJsonRpc();
|
const { send } = useJsonRpc();
|
||||||
const { navigateTo } = useDeviceUiNavigation();
|
const { navigateTo } = useDeviceUiNavigation();
|
||||||
const [autoUpdate, setAutoUpdate] = useState(true);
|
const [autoUpdate, setAutoUpdate] = useState(true);
|
||||||
const { isOnDevVersion } = useVersion();
|
|
||||||
const currentVersions = useDeviceStore(state => {
|
const currentVersions = useDeviceStore(state => {
|
||||||
const { appVersion, systemVersion } = state;
|
const { appVersion, systemVersion } = state;
|
||||||
if (!appVersion || !systemVersion) return null;
|
if (!appVersion || !systemVersion) return null;
|
||||||
|
|
@ -75,10 +73,6 @@ export default function SettingsGeneralRoute() {
|
||||||
notifications.success(m.locale_change_success({ locale: validLocale || m.locale_auto() }));
|
notifications.success(m.locale_change_success({ locale: validLocale || m.locale_auto() }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const downgradeAvailable = useMemo(() => {
|
|
||||||
return isOnDevVersion;
|
|
||||||
}, [isOnDevVersion]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<SettingsPageHeader
|
<SettingsPageHeader
|
||||||
|
|
@ -114,12 +108,6 @@ export default function SettingsGeneralRoute() {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<div className="flex items-center justify-start gap-x-2">
|
<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
|
<Button
|
||||||
size="SM"
|
size="SM"
|
||||||
theme="light"
|
theme="light"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useLocation, useNavigate, useSearchParams } from "react-router";
|
import { useLocation, useNavigate } from "react-router";
|
||||||
|
|
||||||
import { useJsonRpc } from "@hooks/useJsonRpc";
|
import { useJsonRpc } from "@hooks/useJsonRpc";
|
||||||
import { UpdateState, useUpdateStore } from "@hooks/stores";
|
import { UpdateState, useUpdateStore } from "@hooks/stores";
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { SelectMenuBasic } from "@components/SelectMenuBasic";
|
||||||
import { SettingsItem } from "@components/SettingsItem";
|
import { SettingsItem } from "@components/SettingsItem";
|
||||||
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
||||||
import { SettingsSectionHeader } from "@components/SettingsSectionHeader";
|
import { SettingsSectionHeader } from "@components/SettingsSectionHeader";
|
||||||
|
import { NestedSettingsGroup } from "@components/NestedSettingsGroup";
|
||||||
import { UsbDeviceSetting } from "@components/UsbDeviceSetting";
|
import { UsbDeviceSetting } from "@components/UsbDeviceSetting";
|
||||||
import { UsbInfoSetting } from "@components/UsbInfoSetting";
|
import { UsbInfoSetting } from "@components/UsbInfoSetting";
|
||||||
import notifications from "@/notifications";
|
import notifications from "@/notifications";
|
||||||
|
|
@ -156,7 +157,7 @@ export default function SettingsHardwareRoute() {
|
||||||
/>
|
/>
|
||||||
</SettingsItem>
|
</SettingsItem>
|
||||||
{backlightSettings.max_brightness != 0 && (
|
{backlightSettings.max_brightness != 0 && (
|
||||||
<>
|
<NestedSettingsGroup>
|
||||||
<SettingsItem
|
<SettingsItem
|
||||||
title={m.hardware_dim_display_after_title()}
|
title={m.hardware_dim_display_after_title()}
|
||||||
description={m.hardware_dim_display_after_description()}
|
description={m.hardware_dim_display_after_description()}
|
||||||
|
|
@ -198,7 +199,7 @@ export default function SettingsHardwareRoute() {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</SettingsItem>
|
</SettingsItem>
|
||||||
</>
|
</NestedSettingsGroup>
|
||||||
)}
|
)}
|
||||||
<p className="text-xs text-slate-600 dark:text-slate-400">
|
<p className="text-xs text-slate-600 dark:text-slate-400">
|
||||||
{m.hardware_display_wake_up_note()}
|
{m.hardware_display_wake_up_note()}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
|
||||||
import { SettingsItem } from "@components/SettingsItem";
|
import { SettingsItem } from "@components/SettingsItem";
|
||||||
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
||||||
import { SelectMenuBasic } from "@components/SelectMenuBasic";
|
import { SelectMenuBasic } from "@components/SelectMenuBasic";
|
||||||
|
import { NestedSettingsGroup } from "@components/NestedSettingsGroup";
|
||||||
import Fieldset from "@components/Fieldset";
|
import Fieldset from "@components/Fieldset";
|
||||||
import notifications from "@/notifications";
|
import notifications from "@/notifications";
|
||||||
import { m } from "@localizations/messages.js";
|
import { m } from "@localizations/messages.js";
|
||||||
|
|
@ -174,7 +175,7 @@ export default function SettingsVideoRoute() {
|
||||||
description={m.video_enhancement_description()}
|
description={m.video_enhancement_description()}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="space-y-4 pl-4">
|
<NestedSettingsGroup>
|
||||||
<SettingsItem
|
<SettingsItem
|
||||||
title={m.video_saturation_title()}
|
title={m.video_saturation_title()}
|
||||||
description={m.video_saturation_description({ value: videoSaturation.toFixed(1) })}
|
description={m.video_saturation_description({ value: videoSaturation.toFixed(1) })}
|
||||||
|
|
@ -232,7 +233,7 @@ export default function SettingsVideoRoute() {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</NestedSettingsGroup>
|
||||||
<Fieldset disabled={edidLoading} className="space-y-2">
|
<Fieldset disabled={edidLoading} className="space-y-2">
|
||||||
<SettingsItem
|
<SettingsItem
|
||||||
title={m.video_edid_title()}
|
title={m.video_edid_title()}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue