mirror of https://github.com/jetkvm/kvm.git
feat(ui): Add dedicated update route and refactor update dialog state management
This commit is contained in:
parent
a2652c5265
commit
bb4ee2a6c7
|
@ -2,25 +2,21 @@ import { cx } from "@/cva.config";
|
|||
import { Button } from "./Button";
|
||||
import { GridCard } from "./Card";
|
||||
import LoadingSpinner from "./LoadingSpinner";
|
||||
import { UpdateState } from "@/hooks/stores";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useUpdateStore } from "@/hooks/stores";
|
||||
|
||||
interface UpdateInProgressStatusCardProps {
|
||||
setIsUpdateDialogOpen: (isOpen: boolean) => void;
|
||||
setModalView: (view: UpdateState["modalView"]) => void;
|
||||
}
|
||||
export default function UpdateInProgressStatusCard() {
|
||||
const navigate = useNavigate();
|
||||
const { setModalView } = useUpdateStore();
|
||||
|
||||
export default function UpdateInProgressStatusCard({
|
||||
setIsUpdateDialogOpen,
|
||||
setModalView,
|
||||
}: UpdateInProgressStatusCardProps) {
|
||||
return (
|
||||
<div className="w-full transition-all duration-300 ease-in-out opacity-100 select-none">
|
||||
<div className="w-full select-none opacity-100 transition-all duration-300 ease-in-out">
|
||||
<GridCard cardClassName="!shadow-xl">
|
||||
<div className="flex items-center justify-between gap-x-3 px-2.5 py-2.5 text-black dark:text-white">
|
||||
<div className="flex items-center gap-x-3">
|
||||
<LoadingSpinner className={cx("h-5 w-5", "shrink-0 text-blue-700")} />
|
||||
<div className="space-y-1">
|
||||
<div className="text-sm font-semibold leading-none transition text-ellipsis">
|
||||
<div className="text-ellipsis text-sm font-semibold leading-none transition">
|
||||
Update in Progress
|
||||
</div>
|
||||
<div className="text-sm leading-none">
|
||||
|
@ -39,7 +35,7 @@ export default function UpdateInProgressStatusCard({
|
|||
text="View Details"
|
||||
onClick={() => {
|
||||
setModalView("updating");
|
||||
setIsUpdateDialogOpen(true);
|
||||
navigate("update");
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -24,7 +24,7 @@ import notifications from "@/notifications";
|
|||
import api from "../../api";
|
||||
import LocalAuthPasswordDialog from "@/components/LocalAuthPasswordDialog";
|
||||
import { LocalDevice } from "@routes/devices.$id";
|
||||
import { useRevalidator } from "react-router-dom";
|
||||
import { useRevalidator, useNavigate } from "react-router-dom";
|
||||
import { ShieldCheckIcon } from "@heroicons/react/20/solid";
|
||||
import { CLOUD_APP, DEVICE_API } from "@/ui.config";
|
||||
import { InputFieldWithLabel } from "../InputField";
|
||||
|
@ -267,14 +267,16 @@ export default function SettingsSidebar() {
|
|||
});
|
||||
}, [send, sshKey]);
|
||||
|
||||
const { setIsUpdateDialogOpen, setModalView, otaState } = useUpdateStore();
|
||||
const { setModalView, otaState } = useUpdateStore();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleCheckForUpdates = () => {
|
||||
if (otaState.updating) {
|
||||
setModalView("updating");
|
||||
setIsUpdateDialogOpen(true);
|
||||
navigate("update");
|
||||
} else {
|
||||
setModalView("loading");
|
||||
setIsUpdateDialogOpen(true);
|
||||
navigate("update");
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -303,7 +303,8 @@ export const useSettingsStore = create(
|
|||
dim_after: 10000,
|
||||
off_after: 50000,
|
||||
},
|
||||
setBacklightSettings: (settings: BacklightSettings) => set({ backlightSettings: settings }),
|
||||
setBacklightSettings: (settings: BacklightSettings) =>
|
||||
set({ backlightSettings: settings }),
|
||||
}),
|
||||
{
|
||||
name: "settings",
|
||||
|
@ -484,8 +485,6 @@ export interface UpdateState {
|
|||
| "updateCompleted"
|
||||
| "error";
|
||||
setModalView: (view: UpdateState["modalView"]) => void;
|
||||
isUpdateDialogOpen: boolean;
|
||||
setIsUpdateDialogOpen: (isOpen: boolean) => void;
|
||||
setUpdateErrorMessage: (errorMessage: string) => void;
|
||||
updateErrorMessage: string | null;
|
||||
}
|
||||
|
@ -520,8 +519,6 @@ export const useUpdateStore = create<UpdateState>(set => ({
|
|||
set({ updateDialogHasBeenMinimized: hasBeenMinimized }),
|
||||
modalView: "loading",
|
||||
setModalView: view => set({ modalView: view }),
|
||||
isUpdateDialogOpen: false,
|
||||
setIsUpdateDialogOpen: isOpen => set({ isUpdateDialogOpen: isOpen }),
|
||||
updateErrorMessage: null,
|
||||
setUpdateErrorMessage: errorMessage => set({ updateErrorMessage: errorMessage }),
|
||||
}));
|
||||
|
|
|
@ -29,6 +29,7 @@ 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";
|
||||
import UpdateRoute from "./routes/devices.$id.update";
|
||||
|
||||
export const isOnDevice = import.meta.env.MODE === "device";
|
||||
export const isInCloud = !isOnDevice;
|
||||
|
@ -81,6 +82,10 @@ if (isOnDevice) {
|
|||
path: "other-session",
|
||||
element: <OtherSessionRoute />,
|
||||
},
|
||||
{
|
||||
path: "update",
|
||||
element: <UpdateRoute />,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
|
@ -129,6 +134,10 @@ if (isOnDevice) {
|
|||
path: "other-session",
|
||||
element: <OtherSessionRoute />,
|
||||
},
|
||||
{
|
||||
path: "update",
|
||||
element: <UpdateRoute />,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
@ -125,13 +125,7 @@ export default function KvmIdRoute() {
|
|||
const setTransceiver = useRTCStore(state => state.setTransceiver);
|
||||
|
||||
const navigate = useNavigate();
|
||||
const {
|
||||
otaState,
|
||||
setOtaState,
|
||||
isUpdateDialogOpen,
|
||||
setIsUpdateDialogOpen,
|
||||
setModalView,
|
||||
} = useUpdateStore();
|
||||
const { otaState, setOtaState, setModalView } = useUpdateStore();
|
||||
|
||||
const sdp = useCallback(
|
||||
async (event: RTCPeerConnectionIceEvent, pc: RTCPeerConnection) => {
|
||||
|
@ -356,7 +350,7 @@ export default function KvmIdRoute() {
|
|||
|
||||
if (otaState.error) {
|
||||
setModalView("error");
|
||||
setIsUpdateDialogOpen(true);
|
||||
navigate("update");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -387,10 +381,10 @@ export default function KvmIdRoute() {
|
|||
useEffect(() => {
|
||||
if (queryParams.get("updateSuccess")) {
|
||||
setModalView("updateCompleted");
|
||||
setIsUpdateDialogOpen(true);
|
||||
navigate("update");
|
||||
setQueryParams({});
|
||||
}
|
||||
}, [queryParams, setIsUpdateDialogOpen, setModalView, setQueryParams]);
|
||||
}, [navigate, queryParams, setModalView, setQueryParams]);
|
||||
|
||||
const diskChannel = useRTCStore(state => state.diskChannel)!;
|
||||
const file = useMountMediaStore(state => state.localFile)!;
|
||||
|
@ -444,16 +438,13 @@ export default function KvmIdRoute() {
|
|||
}, [kvmTerminal]);
|
||||
|
||||
const outlet = useOutlet();
|
||||
|
||||
const isUpdateDialogOpen = location.pathname.includes("/update");
|
||||
return (
|
||||
<>
|
||||
<Transition show={!isUpdateDialogOpen && otaState.updating}>
|
||||
<div className="pointer-events-none fixed inset-0 z-10 mx-auto flex h-full w-full max-w-xl translate-y-8 items-start justify-center">
|
||||
<div className="transition duration-1000 ease-in data-[closed]:opacity-0">
|
||||
<UpdateInProgressStatusCard
|
||||
setIsUpdateDialogOpen={setIsUpdateDialogOpen}
|
||||
setModalView={setModalView}
|
||||
/>
|
||||
<UpdateInProgressStatusCard />
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
|
@ -494,8 +485,6 @@ export default function KvmIdRoute() {
|
|||
<Outlet context={{ connectWebRTC }} />
|
||||
</Modal>
|
||||
|
||||
<UpdateDialog open={isUpdateDialogOpen} setOpen={setIsUpdateDialogOpen} />
|
||||
|
||||
{kvmTerminal && (
|
||||
<Terminal type="kvm" dataChannel={kvmTerminal} title="KVM Terminal" />
|
||||
)}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
import { useNavigate } from "react-router-dom";
|
||||
import { GridCard } from "@/components/Card";
|
||||
import { useUpdateStore } from "@/hooks/stores";
|
||||
import { Dialog } from "@/components/UpdateDialog";
|
||||
import { useJsonRpc } from "@/hooks/useJsonRpc";
|
||||
import { useCallback } from "react";
|
||||
|
||||
export default function UpdateRoute() {
|
||||
const navigate = useNavigate();
|
||||
const { setModalView } = useUpdateStore();
|
||||
const [send] = useJsonRpc();
|
||||
|
||||
const onConfirmUpdate = useCallback(() => {
|
||||
send("tryUpdate", {});
|
||||
setModalView("updating");
|
||||
}, [send, setModalView]);
|
||||
|
||||
return (
|
||||
<GridCard cardClassName="relative mx-auto max-w-md text-left pointer-events-auto">
|
||||
{/* TODO: Migrate to using URLs instead of the global state. To simplify the refactoring, we'll keep the global state for now. */}
|
||||
<Dialog
|
||||
setOpen={open => {
|
||||
if (!open) {
|
||||
navigate("..");
|
||||
}
|
||||
}}
|
||||
onConfirmUpdate={onConfirmUpdate}
|
||||
/>
|
||||
</GridCard>
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue