chore/Deprecate browser mount

No longer supported.
This commit is contained in:
Marc Brooks 2025-08-26 21:40:20 -05:00
parent 3ec243255b
commit 3d770e73c8
No known key found for this signature in database
GPG Key ID: 583A6AF2D6AE1DC6
5 changed files with 13 additions and 249 deletions

8
ui/package-lock.json generated
View File

@ -49,7 +49,7 @@
"@tailwindcss/typography": "^0.5.16",
"@tailwindcss/vite": "^4.1.12",
"@types/react": "^19.1.11",
"@types/react-dom": "^19.1.7",
"@types/react-dom": "^19.1.8",
"@types/semver": "^7.7.0",
"@types/validator": "^13.15.2",
"@typescript-eslint/eslint-plugin": "^8.41.0",
@ -1970,9 +1970,9 @@
}
},
"node_modules/@types/react-dom": {
"version": "19.1.7",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.7.tgz",
"integrity": "sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==",
"version": "19.1.8",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.8.tgz",
"integrity": "sha512-xG7xaBMJCpcK0RpN8jDbAACQo54ycO6h4dSSmgv8+fu6ZIAdANkx/WsawASUjVXYfy+J9AbUpRMNNEsXCDfDBQ==",
"license": "MIT",
"peerDependencies": {
"@types/react": "^19.0.0"

View File

@ -60,7 +60,7 @@
"@tailwindcss/typography": "^0.5.16",
"@tailwindcss/vite": "^4.1.12",
"@types/react": "^19.1.11",
"@types/react-dom": "^19.1.7",
"@types/react-dom": "^19.1.8",
"@types/semver": "^7.7.0",
"@types/validator": "^13.15.2",
"@typescript-eslint/eslint-plugin": "^8.41.0",

View File

@ -1,9 +1,6 @@
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
import { PlusCircleIcon } from "@heroicons/react/20/solid";
import { useMemo, forwardRef, useEffect, useCallback } from "react";
import { forwardRef, useEffect, useCallback } from "react";
import {
LuArrowUpFromLine,
LuCheckCheck,
LuLink,
LuPlus,
LuRadioReceiver,
@ -14,38 +11,17 @@ import { useLocation } from "react-router-dom";
import { Button } from "@components/Button";
import Card, { GridCard } from "@components/Card";
import { formatters } from "@/utils";
import { RemoteVirtualMediaState, useMountMediaStore, useRTCStore } from "@/hooks/stores";
import { RemoteVirtualMediaState, useMountMediaStore } from "@/hooks/stores";
import { SettingsPageHeader } from "@components/SettingsPageheader";
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
import { useDeviceUiNavigation } from "@/hooks/useAppNavigation";
import notifications from "@/notifications";
const MountPopopover = forwardRef<HTMLDivElement, object>((_props, ref) => {
const { diskDataChannelStats } = useRTCStore();
const { send } = useJsonRpc();
const { remoteVirtualMediaState, setModalView, setRemoteVirtualMediaState } =
useMountMediaStore();
const bytesSentPerSecond = useMemo(() => {
if (diskDataChannelStats.size < 2) return null;
const secondLastItem =
Array.from(diskDataChannelStats)[diskDataChannelStats.size - 2];
const lastItem = Array.from(diskDataChannelStats)[diskDataChannelStats.size - 1];
if (!secondLastItem || !lastItem) return 0;
const lastTime = lastItem[0];
const secondLastTime = secondLastItem[0];
const timeDelta = lastTime - secondLastTime;
const lastBytesSent = lastItem[1].bytesSent;
const secondLastBytesSent = secondLastItem[1].bytesSent;
const bytesDelta = lastBytesSent - secondLastBytesSent;
return bytesDelta / timeDelta;
}, [diskDataChannelStats]);
const syncRemoteVirtualMediaState = useCallback(() => {
send("getVirtualMediaState", {}, (response: JsonRpcResponse) => {
if ("error" in response) {
@ -94,42 +70,6 @@ const MountPopopover = forwardRef<HTMLDivElement, object>((_props, ref) => {
const { source, filename, size, url, path } = remoteVirtualMediaState;
switch (source) {
case "WebRTC":
return (
<>
<div className="space-y-1">
<div className="flex items-center gap-x-2">
<LuCheckCheck className="h-5 text-green-500" />
<h3 className="text-base font-semibold text-black dark:text-white">
Streaming from Browser
</h3>
</div>
<Card className="w-auto px-2 py-1">
<div className="w-full truncate text-sm text-black dark:text-white">
{formatters.truncateMiddle(filename, 50)}
</div>
</Card>
</div>
<div className="my-2 flex flex-col items-center gap-y-2">
<div className="w-full text-sm text-slate-900 dark:text-slate-100">
<div className="flex items-center justify-between">
<span>{formatters.bytes(size ?? 0)}</span>
<div className="flex items-center gap-x-1">
<LuArrowUpFromLine
className="h-4 text-blue-700 dark:text-blue-500"
strokeWidth={2}
/>
<span>
{bytesSentPerSecond !== null
? `${formatters.bytes(bytesSentPerSecond)}/s`
: "N/A"}
</span>
</div>
</div>
</div>
</div>
</>
);
case "HTTP":
return (
<div className="">
@ -202,18 +142,7 @@ const MountPopopover = forwardRef<HTMLDivElement, object>((_props, ref) => {
description="Mount an image to boot from or install an operating system."
/>
{remoteVirtualMediaState?.source === "WebRTC" ? (
<Card>
<div className="flex items-center gap-x-1.5 px-2.5 py-2 text-sm">
<ExclamationTriangleIcon className="h-4 text-yellow-500" />
<div className="flex w-full items-center text-black">
<div>Closing this tab will unmount the image</div>
</div>
</div>
</Card>
) : null}
<div
<div
className="animate-fadeIn opacity-0 space-y-2"
style={{
animationDuration: "0.7s",

View File

@ -396,7 +396,7 @@ export interface MountMediaState {
remoteVirtualMediaState: RemoteVirtualMediaState | null;
setRemoteVirtualMediaState: (state: MountMediaState["remoteVirtualMediaState"]) => void;
modalView: "mode" | "browser" | "url" | "device" | "upload" | "error" | null;
modalView: "mode" | "url" | "device" | "upload" | "error" | null;
setModalView: (view: MountMediaState["modalView"]) => void;
isMountMediaDialogOpen: boolean;

View File

@ -1,9 +1,7 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
LuGlobe,
LuLink,
LuRadioReceiver,
LuHardDrive,
LuCheck,
LuUpload,
} from "react-icons/lu";
@ -131,35 +129,7 @@ export function Dialog({ onClose }: { onClose: () => void }) {
clearMountMediaState();
}
function handleBrowserMount(file: File, mode: RemoteVirtualMediaState["mode"]) {
console.log(`Mounting ${file.name} as ${mode}`);
setMountInProgress(true);
send(
"mountWithWebRTC",
{ filename: file.name, size: file.size, mode },
async resp => {
if ("error" in resp) triggerError(resp.error.message);
clearMountMediaState();
syncRemoteVirtualMediaState()
.then(() => {
// We need to keep the local file in the store so that the browser can
// continue to stream the file to the device
setLocalFile(file);
navigate("..");
})
.catch(err => {
triggerError(err instanceof Error ? err.message : String(err));
})
.finally(() => {
setMountInProgress(false);
});
},
);
}
const [selectedMode, setSelectedMode] = useState<"browser" | "url" | "device">("url");
const [selectedMode, setSelectedMode] = useState<"url" | "device">("url");
return (
<AutoHeight>
<div
@ -167,7 +137,6 @@ export function Dialog({ onClose }: { onClose: () => void }) {
"max-w-4xl": modalView === "mode",
"max-w-2xl": modalView === "device",
"max-w-xl":
modalView === "browser" ||
modalView === "url" ||
modalView === "upload" ||
modalView === "error",
@ -194,19 +163,6 @@ export function Dialog({ onClose }: { onClose: () => void }) {
/>
)}
{modalView === "browser" && (
<BrowserFileView
mountInProgress={mountInProgress}
onMountFile={(file, mode) => {
handleBrowserMount(file, mode);
}}
onBack={() => {
setMountInProgress(false);
setModalView("mode");
}}
/>
)}
{modalView === "url" && (
<UrlView
mountInProgress={mountInProgress}
@ -275,8 +231,8 @@ function ModeSelectionView({
setSelectedMode,
}: {
onClose: () => void;
selectedMode: "browser" | "url" | "device";
setSelectedMode: (mode: "browser" | "url" | "device") => void;
selectedMode: "url" | "device";
setSelectedMode: (mode: "url" | "device") => void;
}) {
const { setModalView } = useMountMediaStore();
@ -292,14 +248,6 @@ function ModeSelectionView({
</div>
<div className="grid gap-4 md:grid-cols-3">
{[
{
label: "Browser Mount",
value: "browser",
description: "Stream files directly from your browser",
icon: LuGlobe,
tag: "Coming Soon",
disabled: true,
},
{
label: "URL Mount",
value: "url",
@ -338,7 +286,7 @@ function ModeSelectionView({
<div
className="relative z-50 flex flex-col items-start p-4 select-none"
onClick={() =>
disabled ? null : setSelectedMode(mode as "browser" | "url" | "device")
disabled ? null : setSelectedMode(mode as "url" | "device")
}
>
<div>
@ -394,119 +342,6 @@ function ModeSelectionView({
);
}
function BrowserFileView({
onMountFile,
onBack,
mountInProgress,
}: {
onBack: () => void;
onMountFile: (file: File, mode: RemoteVirtualMediaState["mode"]) => void;
mountInProgress: boolean;
}) {
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const [usbMode, setUsbMode] = useState<RemoteVirtualMediaState["mode"]>("CDROM");
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0] || null;
setSelectedFile(file);
if (file?.name.endsWith(".iso")) {
setUsbMode("CDROM");
} else if (file?.name.endsWith(".img")) {
setUsbMode("Disk");
}
};
const handleMount = () => {
if (selectedFile) {
console.log(`Mounting ${selectedFile.name} as ${setUsbMode}`);
onMountFile(selectedFile, usbMode);
}
};
return (
<div className="w-full space-y-4">
<ViewHeader
title="Mount from Browser"
description="Select an image file to mount"
/>
<div className="space-y-2">
<div
onClick={() => document.getElementById("file-upload")?.click()}
className="block cursor-pointer select-none"
>
<div
className="group animate-fadeIn opacity-0"
style={{
animationDuration: "0.7s",
}}
>
<Card className="transition-all duration-300 outline-dashed">
<div className="w-full px-4 py-12">
<div className="flex h-full flex-col items-center justify-center text-center">
{selectedFile ? (
<>
<div className="space-y-1">
<LuHardDrive className="mx-auto h-6 w-6 text-blue-700" />
<h3 className="text-sm leading-none font-semibold">
{formatters.truncateMiddle(selectedFile.name, 40)}
</h3>
<p className="text-xs leading-none text-slate-700">
{formatters.bytes(selectedFile.size)}
</p>
</div>
</>
) : (
<div className="space-y-1">
<PlusCircleIcon className="mx-auto h-6 w-6 text-blue-700" />
<h3 className="text-sm leading-none font-semibold">
Click to select a file
</h3>
<p className="text-xs leading-none text-slate-700">
Supported formats: ISO, IMG
</p>
</div>
)}
</div>
</div>
</Card>
</div>
</div>
<input
id="file-upload"
type="file"
onChange={handleFileChange}
className="hidden"
accept=".iso, .img"
/>
</div>
<div
className="flex w-full animate-fadeIn items-end justify-between opacity-0"
style={{
animationDuration: "0.7s",
animationDelay: "0.1s",
}}
>
<Fieldset disabled={!selectedFile}>
<UsbModeSelector usbMode={usbMode} setUsbMode={setUsbMode} />
</Fieldset>
<div className="flex space-x-2">
<Button size="MD" theme="blank" text="Back" onClick={onBack} />
<Button
size="MD"
theme="primary"
text="Mount File"
onClick={handleMount}
disabled={!selectedFile || mountInProgress}
loading={mountInProgress}
/>
</div>
</div>
</div>
);
}
function UrlView({
onBack,
onMount,