feat(ui): Add other session handling route and modal

This commit is contained in:
Adam Shiervani 2025-02-25 23:34:25 +01:00
parent ba0c937e2a
commit a2652c5265
4 changed files with 84 additions and 24 deletions

View File

@ -17,11 +17,11 @@ export default function Modal({
<Dialog open={open} onClose={onClose} className="relative z-10">
<DialogBackdrop
transition
className="fixed inset-0 bg-gray-500/75 dark:bg-slate-900/90 transition-opacity data-[closed]:opacity-0 data-[enter]:duration-300 data-[leave]:duration-200 data-[enter]:ease-out data-[leave]:ease-in"
className="fixed inset-0 bg-gray-500/75 transition-opacity data-[closed]:opacity-0 data-[enter]:duration-300 data-[leave]:duration-200 data-[enter]:ease-out data-[leave]:ease-in dark:bg-slate-900/90"
/>
<div className="fixed inset-0 z-10 w-screen overflow-y-auto">
<div className="flex items-end justify-center min-h-full p-4 text-center sm:items-center sm:p-0">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<DialogPanel
transition
className={cx(
@ -30,9 +30,12 @@ export default function Modal({
className,
)}
>
<div className="inline-block w-full text-left pointer-events-auto">
<div className="pointer-events-auto inline-block w-full text-left">
<div className="flex justify-center" onClick={onClose}>
<div className="w-full pointer-events-none" onClick={e => e.stopPropagation()}>
<div
className="pointer-events-none w-full"
onClick={e => e.stopPropagation()}
>
{children}
</div>
</div>

View File

@ -28,6 +28,7 @@ import WelcomeLocalModeRoute from "./routes/welcome-local.mode";
import WelcomeRoute from "./routes/welcome-local";
import WelcomeLocalPasswordRoute from "./routes/welcome-local.password";
import { CLOUD_API } from "./ui.config";
import OtherSessionRoute from "./routes/devices.$id.other-session";
export const isOnDevice = import.meta.env.MODE === "device";
export const isInCloud = !isOnDevice;
@ -75,7 +76,14 @@ if (isOnDevice) {
errorElement: <ErrorBoundary />,
element: <DeviceRoute />,
loader: DeviceRoute.loader,
children: [
{
path: "other-session",
element: <OtherSessionRoute />,
},
],
},
{
path: "/adopt",
element: <AdoptRoute />,
@ -116,6 +124,12 @@ if (isOnDevice) {
path: "devices/:id",
element: <DeviceRoute />,
loader: DeviceRoute.loader,
children: [
{
path: "other-session",
element: <OtherSessionRoute />,
},
],
},
{
path: "devices/:id/deregister",
@ -164,8 +178,8 @@ function ErrorBoundary() {
}
return (
<div className="w-full h-full">
<div className="flex items-center justify-center h-full">
<div className="h-full w-full">
<div className="flex h-full items-center justify-center">
<div className="w-full max-w-2xl">
<EmptyCard
IconElm={ExclamationTriangleIcon}

View File

@ -0,0 +1,45 @@
import { useNavigate, useOutletContext } from "react-router-dom";
import { GridCard } from "@/components/Card";
import { Button } from "@components/Button";
import LogoBlue from "@/assets/logo-blue.svg";
import LogoWhite from "@/assets/logo-white.svg";
interface ContextType {
connectWebRTC: () => Promise<void>;
}
export default function OtherSessionRoute() {
const outletContext = useOutletContext<ContextType>();
const navigate = useNavigate();
// Function to handle closing the modal
const handleClose = () => {
outletContext?.connectWebRTC().then(() => navigate(".."));
};
return (
<GridCard cardClassName="relative mx-auto max-w-md text-left pointer-events-auto">
<div className="p-10">
<div className="flex min-h-[140px] flex-col items-start justify-start space-y-4 text-left">
<div className="h-[24px]">
<img src={LogoBlue} alt="" className="h-full dark:hidden" />
<img src={LogoWhite} alt="" className="hidden h-full dark:block" />
</div>
<div className="text-left">
<p className="text-base font-semibold dark:text-white">
Another Active Session Detected
</p>
<p className="mb-4 text-sm text-slate-600 dark:text-slate-400">
Only one active session is supported at a time. Would you like to take over
this session?
</p>
<div className="flex items-center justify-start space-x-4">
<Button size="SM" theme="primary" text="Use Here" onClick={handleClose} />
</div>
</div>
</div>
</div>
</GridCard>
);
}

View File

@ -16,10 +16,12 @@ import {
import WebRTCVideo from "@components/WebRTCVideo";
import {
LoaderFunctionArgs,
Outlet,
Params,
redirect,
useLoaderData,
useNavigate,
useOutlet,
useParams,
useSearchParams,
} from "react-router-dom";
@ -34,9 +36,9 @@ import UpdateInProgressStatusCard from "../components/UpdateInProgressStatusCard
import api from "../api";
import { DeviceStatus } from "./welcome-local";
import FocusTrap from "focus-trap-react";
import OtherSessionConnectedModal from "@/components/OtherSessionConnectedModal";
import Terminal from "@components/Terminal";
import { CLOUD_API, DEVICE_API } from "@/ui.config";
import Modal from "../components/Modal";
interface LocalLoaderResp {
authMode: "password" | "noPassword" | null;
@ -131,9 +133,6 @@ export default function KvmIdRoute() {
setModalView,
} = useUpdateStore();
const [isOtherSessionConnectedModalOpen, setIsOtherSessionConnectedModalOpen] =
useState(false);
const sdp = useCallback(
async (event: RTCPeerConnectionIceEvent, pc: RTCPeerConnection) => {
if (!pc) return;
@ -243,8 +242,7 @@ export default function KvmIdRoute() {
) {
return;
}
// We don't want to connect if another session is connected
if (isOtherSessionConnectedModalOpen) return;
if (location.pathname.includes("other-session")) return;
connectWebRTC();
}, 3000);
@ -334,7 +332,7 @@ export default function KvmIdRoute() {
function onJsonRpcRequest(resp: JsonRpcRequest) {
if (resp.method === "otherSessionConnected") {
console.log("otherSessionConnected", resp.params);
setIsOtherSessionConnectedModalOpen(true);
navigate("other-session");
}
if (resp.method === "usbState") {
@ -445,6 +443,8 @@ export default function KvmIdRoute() {
};
}, [kvmTerminal]);
const outlet = useOutlet();
return (
<>
<Transition show={!isUpdateDialogOpen && otaState.updating}>
@ -486,18 +486,16 @@ export default function KvmIdRoute() {
</div>
</div>
</div>
<UpdateDialog open={isUpdateDialogOpen} setOpen={setIsUpdateDialogOpen} />
<OtherSessionConnectedModal
open={isOtherSessionConnectedModalOpen}
setOpen={state => {
if (!state) connectWebRTC().then(r => r);
// It takes some time for the WebRTC connection to be established, so we wait a bit before closing the modal
setTimeout(() => {
setIsOtherSessionConnectedModalOpen(state);
}, 1000);
}}
/>
<Modal
open={outlet !== null}
onClose={() => location.pathname !== "/other-session" && navigate("..")}
>
<Outlet context={{ connectWebRTC }} />
</Modal>
<UpdateDialog open={isUpdateDialogOpen} setOpen={setIsUpdateDialogOpen} />
{kvmTerminal && (
<Terminal type="kvm" dataChannel={kvmTerminal} title="KVM Terminal" />
)}