refactor(ui): Improve device access settings with conditional rendering and loader data handling (#204)

This commit is contained in:
Adam Shiervani 2025-02-27 17:18:56 +01:00 committed by GitHub
parent d49a567a38
commit a4863f6999
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 167 additions and 164 deletions

View File

@ -6,7 +6,6 @@ import { CLOUD_APP, DEVICE_API } from "../ui.config";
import api from "../api"; import api from "../api";
import { LocalDevice } from "./devices.$id"; import { LocalDevice } from "./devices.$id";
import { useDeviceUiNavigation } from "../hooks/useAppNavigation"; import { useDeviceUiNavigation } from "../hooks/useAppNavigation";
import { isOnDevice } from "../main";
import { GridCard } from "../components/Card"; import { GridCard } from "../components/Card";
import { ShieldCheckIcon } from "@heroicons/react/24/outline"; import { ShieldCheckIcon } from "@heroicons/react/24/outline";
import notifications from "../notifications"; import notifications from "../notifications";
@ -15,16 +14,21 @@ import { useJsonRpc } from "../hooks/useJsonRpc";
import { InputFieldWithLabel } from "../components/InputField"; import { InputFieldWithLabel } from "../components/InputField";
import { SelectMenuBasic } from "../components/SelectMenuBasic"; import { SelectMenuBasic } from "../components/SelectMenuBasic";
import { SettingsSectionHeader } from "../components/SettingsSectionHeader"; import { SettingsSectionHeader } from "../components/SettingsSectionHeader";
import { isOnDevice } from "../main";
export const loader = async () => { export const loader = async () => {
const status = await api if (isOnDevice) {
.GET(`${DEVICE_API}/device`) const status = await api
.then(res => res.json() as Promise<LocalDevice>); .GET(`${DEVICE_API}/device`)
return status; .then(res => res.json() as Promise<LocalDevice>);
return status;
}
return null;
}; };
export default function SettingsAccessIndexRoute() { export default function SettingsAccessIndexRoute() {
const { authMode } = useLoaderData() as LocalDevice; const loaderData = useLoaderData() as LocalDevice | null;
const { navigateTo } = useDeviceUiNavigation(); const { navigateTo } = useDeviceUiNavigation();
const [send] = useJsonRpc(); const [send] = useJsonRpc();
@ -137,8 +141,6 @@ export default function SettingsAccessIndexRoute() {
syncCloudUrl(); syncCloudUrl();
}, [cloudProviders, syncCloudUrl]); }, [cloudProviders, syncCloudUrl]);
console.log("is adopted:", isAdopted);
return ( return (
<div className="space-y-4"> <div className="space-y-4">
<SettingsPageHeader <SettingsPageHeader
@ -146,185 +148,186 @@ export default function SettingsAccessIndexRoute() {
description="Manage the Access Control of the device" description="Manage the Access Control of the device"
/> />
<div className="space-y-4"> {loaderData?.authMode && (
<SettingsSectionHeader <>
title="Local" <div className="space-y-4">
description="Manage the mode of local access to the device" <SettingsSectionHeader
/> title="Local"
<SettingsItem description="Manage the mode of local access to the device"
title="Authentication Mode"
description={`Current mode: ${authMode === "password" ? "Password protected" : "No password"}`}
>
{authMode === "password" ? (
<Button
size="SM"
theme="light"
text="Disable Protection"
onClick={() => {
navigateTo("./local-auth", { state: { init: "deletePassword" } });
}}
/> />
) : ( <SettingsItem
<Button title="Authentication Mode"
size="SM" description={`Current mode: ${loaderData.authMode === "password" ? "Password protected" : "No password"}`}
theme="light" >
text="Enable Password" {loaderData.authMode === "password" ? (
onClick={() => { <Button
navigateTo("./local-auth", { state: { init: "createPassword" } }); size="SM"
}} theme="light"
/> text="Disable Protection"
)} onClick={() => {
</SettingsItem> navigateTo("./local-auth", { state: { init: "deletePassword" } });
}}
/>
) : (
<Button
size="SM"
theme="light"
text="Enable Password"
onClick={() => {
navigateTo("./local-auth", { state: { init: "createPassword" } });
}}
/>
)}
</SettingsItem>
{loaderData.authMode === "password" && (
<SettingsItem
title="Change Password"
description="Update your device access password"
>
<Button
size="SM"
theme="light"
text="Change Password"
onClick={() => {
navigateTo("./local-auth", { state: { init: "updatePassword" } });
}}
/>
</SettingsItem>
)}
</div>
<div className="h-px w-full bg-slate-800/10 dark:bg-slate-300/20" />
</>
)}
{authMode === "password" && (
<SettingsItem
title="Change Password"
description="Update your device access password"
>
<Button
size="SM"
theme="light"
text="Change Password"
onClick={() => {
navigateTo("./local-auth", { state: { init: "updatePassword" } });
}}
/>
</SettingsItem>
)}
</div>
<div className="h-px w-full bg-slate-800/10 dark:bg-slate-300/20" />
<div className="space-y-4"> <div className="space-y-4">
<SettingsSectionHeader <SettingsSectionHeader
title="Remote" title="Remote"
description="Manage the mode of Remote access to the device" description="Manage the mode of Remote access to the device"
/> />
{isOnDevice && ( <div className="space-y-4">
<> {!isAdopted && (
<div className="space-y-4"> <>
{!isAdopted && ( <SettingsItem
<> title="Cloud Provider"
<SettingsItem description="Select the cloud provider for your device"
title="Cloud Provider" >
description="Select the cloud provider for your device" <SelectMenuBasic
> size="SM"
<SelectMenuBasic value={selectedUrlOption}
size="SM" onChange={e => {
value={selectedUrlOption} const value = e.target.value;
onChange={e => { setSelectedUrlOption(value);
const value = e.target.value; }}
setSelectedUrlOption(value); options={cloudProviders ?? []}
}} />
options={cloudProviders ?? []} </SettingsItem>
/>
</SettingsItem>
{selectedUrlOption === "custom" && ( {selectedUrlOption === "custom" && (
<div className="mt-4 flex items-end gap-x-2 space-y-4"> <div className="mt-4 flex items-end gap-x-2 space-y-4">
<InputFieldWithLabel <InputFieldWithLabel
size="SM" size="SM"
label="Custom Cloud URL" label="Custom Cloud URL"
value={cloudUrl} value={cloudUrl}
onChange={e => setCloudUrl(e.target.value)} onChange={e => setCloudUrl(e.target.value)}
placeholder="https://api.example.com" placeholder="https://api.example.com"
/> />
</div> </div>
)}
</>
)} )}
</>
)}
{/* {/*
We do the harcoding here to avoid flickering when the default Cloud URL being fetched. We do the harcoding here to avoid flickering when the default Cloud URL being fetched.
I've tried to avoid harcoding api.jetkvm.com, but it's the only reasonable way I could think of to avoid flickering for now. I've tried to avoid harcoding api.jetkvm.com, but it's the only reasonable way I could think of to avoid flickering for now.
*/} */}
{selectedUrlOption === (defaultCloudUrl || "https://api.jetkvm.com") && ( {selectedUrlOption === (defaultCloudUrl || "https://api.jetkvm.com") && (
<GridCard> <GridCard>
<div className="flex items-start gap-x-4 p-4"> <div className="flex items-start gap-x-4 p-4">
<ShieldCheckIcon className="mt-1 h-8 w-8 shrink-0 text-blue-600 dark:text-blue-500" /> <ShieldCheckIcon className="mt-1 h-8 w-8 shrink-0 text-blue-600 dark:text-blue-500" />
<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">
Cloud Security Cloud Security
</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>End-to-end encryption using WebRTC (DTLS and SRTP)</li> <li>End-to-end encryption using WebRTC (DTLS and SRTP)</li>
<li>Zero Trust security model</li> <li>Zero Trust security model</li>
<li>OIDC (OpenID Connect) authentication</li> <li>OIDC (OpenID Connect) authentication</li>
<li>All streams encrypted in transit</li> <li>All streams encrypted in transit</li>
</ul> </ul>
</div> </div>
<div className="text-xs text-slate-700 dark:text-slate-300"> <div className="text-xs text-slate-700 dark:text-slate-300">
All cloud components are open-source and available on{" "} All cloud components are open-source and available on{" "}
<a <a
href="https://github.com/jetkvm" href="https://github.com/jetkvm"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="font-medium text-blue-600 hover:text-blue-800 dark:text-blue-500 dark:hover:text-blue-400" className="font-medium text-blue-600 hover:text-blue-800 dark:text-blue-500 dark:hover:text-blue-400"
> >
GitHub GitHub
</a> </a>
. .
</div>
</div>
<hr className="block w-full dark:border-slate-600" />
<div>
<LinkButton
to="https://jetkvm.com/docs/networking/remote-access"
size="SM"
theme="light"
text="Learn about our cloud security"
/>
</div>
</div> </div>
</div> </div>
</GridCard> <hr className="block w-full dark:border-slate-600" />
)}
{!isAdopted ? ( <div>
<div className="flex items-end gap-x-2"> <LinkButton
to="https://jetkvm.com/docs/networking/remote-access"
size="SM"
theme="light"
text="Learn about our cloud security"
/>
</div>
</div>
</div>
</GridCard>
)}
{!isAdopted ? (
<div className="flex items-end gap-x-2">
<Button
onClick={() => onCloudAdoptClick(cloudUrl)}
size="SM"
theme="primary"
text="Adopt KVM to Cloud"
/>
</div>
) : (
<div>
<div className="space-y-2">
<p className="text-sm text-slate-600 dark:text-slate-300">
Your device is adopted to JetKVM Cloud
</p>
<div>
<Button <Button
onClick={() => onCloudAdoptClick(cloudUrl)}
size="SM" size="SM"
theme="primary" theme="light"
text="Adopt KVM to Cloud" text="De-register from Cloud"
className="text-red-600"
onClick={() => {
if (deviceId) {
if (
window.confirm(
"Are you sure you want to de-register this device?",
)
) {
deregisterDevice();
}
} else {
notifications.error("No device ID available");
}
}}
/> />
</div> </div>
) : ( </div>
<div>
<div className="space-y-2">
<p className="text-sm text-slate-600 dark:text-slate-300">
Your device is adopted to JetKVM Cloud
</p>
<div>
<Button
size="SM"
theme="light"
text="De-register from Cloud"
className="text-red-600"
onClick={() => {
if (deviceId) {
if (
window.confirm(
"Are you sure you want to de-register this device?",
)
) {
deregisterDevice();
}
} else {
notifications.error("No device ID available");
}
}}
/>
</div>
</div>
</div>
)}
</div> </div>
</> )}
)} </div>
</div> </div>
</div> </div>
); );