Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
331 changes: 167 additions & 164 deletions ui/src/routes/devices.$id.settings.access._index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { CLOUD_APP, DEVICE_API } from "../ui.config";
import api from "../api";
import { LocalDevice } from "./devices.$id";
import { useDeviceUiNavigation } from "../hooks/useAppNavigation";
import { isOnDevice } from "../main";
import { GridCard } from "../components/Card";
import { ShieldCheckIcon } from "@heroicons/react/24/outline";
import notifications from "../notifications";
Expand All @@ -15,16 +14,21 @@ import { useJsonRpc } from "../hooks/useJsonRpc";
import { InputFieldWithLabel } from "../components/InputField";
import { SelectMenuBasic } from "../components/SelectMenuBasic";
import { SettingsSectionHeader } from "../components/SettingsSectionHeader";
import { isOnDevice } from "../main";

export const loader = async () => {
const status = await api
.GET(`${DEVICE_API}/device`)
.then(res => res.json() as Promise<LocalDevice>);
return status;
if (isOnDevice) {
const status = await api
.GET(`${DEVICE_API}/device`)
.then(res => res.json() as Promise<LocalDevice>);
return status;
}
return null;
};

export default function SettingsAccessIndexRoute() {
const { authMode } = useLoaderData() as LocalDevice;
const loaderData = useLoaderData() as LocalDevice | null;

const { navigateTo } = useDeviceUiNavigation();

const [send] = useJsonRpc();
Expand Down Expand Up @@ -137,194 +141,193 @@ export default function SettingsAccessIndexRoute() {
syncCloudUrl();
}, [cloudProviders, syncCloudUrl]);

console.log("is adopted:", isAdopted);

return (
<div className="space-y-4">
<SettingsPageHeader
title="Access"
description="Manage the Access Control of the device"
/>

<div className="space-y-4">
<SettingsSectionHeader
title="Local"
description="Manage the mode of local access to the device"
/>
<SettingsItem
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" } });
}}
/>
) : (
<Button
size="SM"
theme="light"
text="Enable Password"
onClick={() => {
navigateTo("./local-auth", { state: { init: "createPassword" } });
}}
{loaderData?.authMode && (
<>
<div className="space-y-4">
<SettingsSectionHeader
title="Local"
description="Manage the mode of local access to the device"
/>
)}
</SettingsItem>
<SettingsItem
title="Authentication Mode"
description={`Current mode: ${loaderData.authMode === "password" ? "Password protected" : "No password"}`}
>
{loaderData.authMode === "password" ? (
<Button
size="SM"
theme="light"
text="Disable Protection"
onClick={() => {
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">
<SettingsSectionHeader
title="Remote"
description="Manage the mode of Remote access to the device"
/>

{isOnDevice && (
<>
<div className="space-y-4">
{!isAdopted && (
<>
<SettingsItem
title="Cloud Provider"
description="Select the cloud provider for your device"
>
<SelectMenuBasic
size="SM"
value={selectedUrlOption}
onChange={e => {
const value = e.target.value;
setSelectedUrlOption(value);
}}
options={cloudProviders ?? []}
/>
</SettingsItem>
<div className="space-y-4">
{!isAdopted && (
<>
<SettingsItem
title="Cloud Provider"
description="Select the cloud provider for your device"
>
<SelectMenuBasic
size="SM"
value={selectedUrlOption}
onChange={e => {
const value = e.target.value;
setSelectedUrlOption(value);
}}
options={cloudProviders ?? []}
/>
</SettingsItem>

{selectedUrlOption === "custom" && (
<div className="mt-4 flex items-end gap-x-2 space-y-4">
<InputFieldWithLabel
size="SM"
label="Custom Cloud URL"
value={cloudUrl}
onChange={e => setCloudUrl(e.target.value)}
placeholder="https://api.example.com"
/>
</div>
)}
</>
{selectedUrlOption === "custom" && (
<div className="mt-4 flex items-end gap-x-2 space-y-4">
<InputFieldWithLabel
size="SM"
label="Custom Cloud URL"
value={cloudUrl}
onChange={e => setCloudUrl(e.target.value)}
placeholder="https://api.example.com"
/>
</div>
)}
</>
)}

{/*
{/*
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.
*/}
{selectedUrlOption === (defaultCloudUrl || "https://api.jetkvm.com") && (
<GridCard>
<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" />
<div className="space-y-3">
<div className="space-y-2">
<h3 className="text-base font-bold text-slate-900 dark:text-white">
Cloud Security
</h3>
<div>
<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>Zero Trust security model</li>
<li>OIDC (OpenID Connect) authentication</li>
<li>All streams encrypted in transit</li>
</ul>
</div>

<div className="text-xs text-slate-700 dark:text-slate-300">
All cloud components are open-source and available on{" "}
<a
href="https://github.com/jetkvm"
target="_blank"
rel="noopener noreferrer"
className="font-medium text-blue-600 hover:text-blue-800 dark:text-blue-500 dark:hover:text-blue-400"
>
GitHub
</a>
.
</div>
</div>
<hr className="block w-full dark:border-slate-600" />
{selectedUrlOption === (defaultCloudUrl || "https://api.jetkvm.com") && (
<GridCard>
<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" />
<div className="space-y-3">
<div className="space-y-2">
<h3 className="text-base font-bold text-slate-900 dark:text-white">
Cloud Security
</h3>
<div>
<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>Zero Trust security model</li>
<li>OIDC (OpenID Connect) authentication</li>
<li>All streams encrypted in transit</li>
</ul>
</div>

<div>
<LinkButton
to="https://jetkvm.com/docs/networking/remote-access"
size="SM"
theme="light"
text="Learn about our cloud security"
/>
</div>
<div className="text-xs text-slate-700 dark:text-slate-300">
All cloud components are open-source and available on{" "}
<a
href="https://github.com/jetkvm"
target="_blank"
rel="noopener noreferrer"
className="font-medium text-blue-600 hover:text-blue-800 dark:text-blue-500 dark:hover:text-blue-400"
>
GitHub
</a>
.
</div>
</div>
</GridCard>
)}
<hr className="block w-full dark:border-slate-600" />

{!isAdopted ? (
<div className="flex items-end gap-x-2">
<div>
<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
onClick={() => onCloudAdoptClick(cloudUrl)}
size="SM"
theme="primary"
text="Adopt KVM to Cloud"
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 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>
);
Expand Down