mirror of
https://github.com/luckfox-eng29/kvm.git
synced 2026-01-18 03:28:19 +01:00
Update App version to 0.0.2
This commit is contained in:
@@ -109,6 +109,11 @@ export function Dialog({ onClose }: { onClose: () => void }) {
|
||||
function handleStorageMount(fileName: string, mode: RemoteVirtualMediaState["mode"]) {
|
||||
console.log(`Mounting ${fileName} as ${mode}`);
|
||||
|
||||
if (!fileName.endsWith(".iso") && !fileName.endsWith(".img")) {
|
||||
triggerError("Only ISO and IMG files are supported");
|
||||
return;
|
||||
}
|
||||
|
||||
setMountInProgress(true);
|
||||
send("mountWithStorage", { filename: fileName, mode }, async resp => {
|
||||
if ("error" in resp) triggerError(resp.error.message);
|
||||
@@ -136,6 +141,11 @@ export function Dialog({ onClose }: { onClose: () => void }) {
|
||||
function handleSDStorageMount(fileName: string, mode: RemoteVirtualMediaState["mode"]) {
|
||||
console.log(`Mounting ${fileName} as ${mode}`);
|
||||
|
||||
if (!fileName.endsWith(".iso") && !fileName.endsWith(".img")) {
|
||||
triggerError("Only ISO and IMG files are supported");
|
||||
return;
|
||||
}
|
||||
|
||||
setMountInProgress(true);
|
||||
send("mountWithSDStorage", { filename: fileName, mode }, async resp => {
|
||||
if ("error" in resp) triggerError(resp.error.message);
|
||||
@@ -407,7 +417,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 "browser" | "url" | "device" | "sd")
|
||||
}
|
||||
>
|
||||
<div>
|
||||
@@ -1069,6 +1079,7 @@ function SDFileView({
|
||||
const [selected, setSelected] = useState<string | null>(null);
|
||||
const [usbMode, setUsbMode] = useState<RemoteVirtualMediaState["mode"]>("CDROM");
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const filesPerPage = 5;
|
||||
|
||||
const [send] = useJsonRpc();
|
||||
@@ -1195,17 +1206,37 @@ function SDFileView({
|
||||
const handleNextPage = () => {
|
||||
setCurrentPage(prev => Math.min(prev + 1, totalPages));
|
||||
};
|
||||
|
||||
function handleResetSDStorage() {
|
||||
|
||||
async function handleResetSDStorage() {
|
||||
setLoading(true);
|
||||
send("resetSDStorage", {}, res => {
|
||||
console.log("Reset SD storage response:", res);
|
||||
if ("error" in res) {
|
||||
notifications.error(`Failed to reset SD card`);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
setLoading(false);
|
||||
syncStorage();
|
||||
}
|
||||
|
||||
async function handleUnmountSDStorage() {
|
||||
setLoading(true);
|
||||
send("unmountSDStorage", {}, res => {
|
||||
console.log("Unmount SD response:", res);
|
||||
if ("error" in res) {
|
||||
notifications.error(`Failed to unmount SD card`);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
});
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
setLoading(false);
|
||||
syncStorage();
|
||||
}
|
||||
|
||||
|
||||
if (sdMountStatus && sdMountStatus !== "ok") {
|
||||
return (
|
||||
@@ -1223,6 +1254,7 @@ function SDFileView({
|
||||
: "SD card mount failed"}
|
||||
<Button
|
||||
size="XS"
|
||||
disabled={loading}
|
||||
theme="light"
|
||||
LeadingIcon={LuRefreshCw}
|
||||
onClick={handleResetSDStorage}
|
||||
@@ -1268,6 +1300,7 @@ function SDFileView({
|
||||
<div>
|
||||
<Button
|
||||
size="SM"
|
||||
disabled={loading}
|
||||
theme="primary"
|
||||
text="Upload a new image"
|
||||
onClick={() => onNewImageClick()}
|
||||
@@ -1312,14 +1345,14 @@ function SDFileView({
|
||||
theme="light"
|
||||
text="Previous"
|
||||
onClick={handlePreviousPage}
|
||||
disabled={currentPage === 1}
|
||||
disabled={currentPage === 1 || loading}
|
||||
/>
|
||||
<Button
|
||||
size="XS"
|
||||
theme="light"
|
||||
text="Next"
|
||||
onClick={handleNextPage}
|
||||
disabled={currentPage === totalPages}
|
||||
disabled={currentPage === totalPages || loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1344,7 +1377,7 @@ function SDFileView({
|
||||
<Button size="MD" theme="blank" text="Back" onClick={() => onBack()} />
|
||||
<Button
|
||||
size="MD"
|
||||
disabled={selected === null || mountInProgress}
|
||||
disabled={selected === null || mountInProgress || loading}
|
||||
theme="primary"
|
||||
text="Mount File"
|
||||
loading={mountInProgress}
|
||||
@@ -1412,6 +1445,7 @@ function SDFileView({
|
||||
>
|
||||
<Button
|
||||
size="MD"
|
||||
disabled={loading}
|
||||
theme="light"
|
||||
fullWidth
|
||||
text="Upload a new image"
|
||||
@@ -1419,6 +1453,24 @@ function SDFileView({
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
className="w-full animate-fadeIn opacity-0"
|
||||
style={{
|
||||
animationDuration: "0.7s",
|
||||
animationDelay: "0.25s",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
size="MD"
|
||||
disabled={loading}
|
||||
theme="light"
|
||||
fullWidth
|
||||
text="Unmount SD Card"
|
||||
onClick={() => handleUnmountSDStorage()}
|
||||
className="text-red-500 dark:text-red-400"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
1626
ui/src/routes/devices.$id.mtp.tsx
Normal file
1626
ui/src/routes/devices.$id.mtp.tsx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -22,6 +22,8 @@ import { CloudState } from "./adopt";
|
||||
import { useVpnStore } from "@/hooks/stores";
|
||||
import Checkbox from "../components/Checkbox";
|
||||
|
||||
import { LogDialog } from "../components/LogDialog";
|
||||
|
||||
export interface TailScaleResponse {
|
||||
state: string;
|
||||
loginUrl: string;
|
||||
@@ -35,6 +37,11 @@ export interface ZeroTierResponse {
|
||||
ip: string;
|
||||
}
|
||||
|
||||
export interface FrpcResponse {
|
||||
running: boolean;
|
||||
}
|
||||
|
||||
|
||||
export interface TLSState {
|
||||
mode: "self-signed" | "custom" | "disabled";
|
||||
certificate?: string;
|
||||
@@ -83,6 +90,11 @@ export default function SettingsAccessIndexRoute() {
|
||||
|
||||
const [tempNetworkID, setTempNetworkID] = useState("");
|
||||
const [isDisconnecting, setIsDisconnecting] = useState(false);
|
||||
|
||||
const [frpcToml, setFrpcToml] = useState<string>("");
|
||||
const [frpcLog, setFrpcLog] = useState<string>("");
|
||||
const [showFrpcLogModal, setShowFrpcLogModal] = useState(false);
|
||||
const [frpcStatus, setFrpcRunningStatus] = useState<FrpcResponse>({ running: false });
|
||||
|
||||
const getTLSState = useCallback(() => {
|
||||
send("getTLSState", {}, resp => {
|
||||
@@ -262,6 +274,77 @@ export default function SettingsAccessIndexRoute() {
|
||||
});
|
||||
},[send, zeroTierNetworkID]);
|
||||
|
||||
const handleStartFrpc = useCallback(() => {
|
||||
send("startFrpc", { frpcToml }, resp => {
|
||||
if ("error" in resp) {
|
||||
notifications.error(
|
||||
`Failed to start frpc: ${resp.error.data || "Unknown error"}`,
|
||||
);
|
||||
setFrpcRunningStatus({ running: false });
|
||||
return;
|
||||
}
|
||||
notifications.success("frpc started");
|
||||
setFrpcRunningStatus({ running: true });
|
||||
});
|
||||
}, [send, frpcToml]);
|
||||
|
||||
const handleStopFrpc = useCallback(() => {
|
||||
send("stopFrpc", { frpcToml }, resp => {
|
||||
if ("error" in resp) {
|
||||
notifications.error(
|
||||
`Failed to stop frpc: ${resp.error.data || "Unknown error"}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
notifications.success("frpc stopped");
|
||||
setFrpcRunningStatus({ running: false });
|
||||
});
|
||||
}, [send]);
|
||||
|
||||
const handleGetFrpcLog = useCallback(() => {
|
||||
send("getFrpcLog", {}, resp => {
|
||||
if ("error" in resp) {
|
||||
notifications.error(
|
||||
`Failed to get frpc log: ${resp.error.data || "Unknown error"}`,
|
||||
);
|
||||
setFrpcLog("");
|
||||
return;
|
||||
}
|
||||
setFrpcLog(resp.result as string);
|
||||
setShowFrpcLogModal(true);
|
||||
});
|
||||
}, [send]);
|
||||
|
||||
const getFrpcToml = useCallback(() => {
|
||||
send("getFrpcToml", {}, resp => {
|
||||
if ("error" in resp) {
|
||||
notifications.error(
|
||||
`Failed to get frpc toml: ${resp.error.data || "Unknown error"}`,
|
||||
);
|
||||
setFrpcToml("");
|
||||
return;
|
||||
}
|
||||
setFrpcToml(resp.result as string);
|
||||
});
|
||||
}, [send]);
|
||||
|
||||
const getFrpcStatus = useCallback(() => {
|
||||
send("getFrpcStatus", {}, resp => {
|
||||
if ("error" in resp) {
|
||||
notifications.error(
|
||||
`Failed to get frpc status: ${resp.error.data || "Unknown error"}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
setFrpcRunningStatus(resp.result as FrpcResponse);
|
||||
});
|
||||
}, [send]);
|
||||
|
||||
useEffect(() => {
|
||||
getFrpcStatus();
|
||||
getFrpcToml();
|
||||
}, [getFrpcStatus, getFrpcToml]);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<SettingsPageHeader
|
||||
@@ -399,16 +482,6 @@ export default function SettingsAccessIndexRoute() {
|
||||
badge="Experimental"
|
||||
description="Connect to TailScale VPN network"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
{ ((tailScaleConnectionState === "disconnected") || (tailScaleConnectionState === "closed")) && (
|
||||
<Button
|
||||
size="SM"
|
||||
theme="light"
|
||||
text="Enable TailScale"
|
||||
onClick={handleTailScaleLogin}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</SettingsItem>
|
||||
<SettingsItem
|
||||
title=""
|
||||
@@ -425,6 +498,21 @@ export default function SettingsAccessIndexRoute() {
|
||||
}}
|
||||
/>
|
||||
</SettingsItem>
|
||||
<SettingsItem
|
||||
title=""
|
||||
description=""
|
||||
>
|
||||
<div className="space-y-4">
|
||||
{ ((tailScaleConnectionState === "disconnected") || (tailScaleConnectionState === "closed")) && (
|
||||
<Button
|
||||
size="SM"
|
||||
theme="light"
|
||||
text="Enable"
|
||||
onClick={handleTailScaleLogin}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</SettingsItem>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
@@ -558,7 +646,58 @@ export default function SettingsAccessIndexRoute() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<SettingsItem
|
||||
title="Frp"
|
||||
description="Connect to Frp Server"
|
||||
/>
|
||||
<div className="space-y-4">
|
||||
<TextAreaWithLabel
|
||||
label="Edit frpc.toml"
|
||||
placeholder="Enter frpc settings"
|
||||
value={frpcToml || ""}
|
||||
rows={3}
|
||||
onChange={e => setFrpcToml(e.target.value)}
|
||||
/>
|
||||
<div className="flex items-center gap-x-2">
|
||||
{frpcStatus.running ? (
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Button
|
||||
size="SM"
|
||||
theme="danger"
|
||||
text="Stop frpc"
|
||||
onClick={handleStopFrpc}
|
||||
/>
|
||||
<Button
|
||||
size="SM"
|
||||
theme="light"
|
||||
text="Log"
|
||||
onClick={handleGetFrpcLog}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
size="SM"
|
||||
theme="primary"
|
||||
text="Start frpc"
|
||||
onClick={handleStartFrpc}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<LogDialog
|
||||
open={showFrpcLogModal}
|
||||
onClose={() => {
|
||||
setShowFrpcLogModal(false);
|
||||
}}
|
||||
title="Frpc Log"
|
||||
description={frpcLog}
|
||||
/>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,13 +5,11 @@ import { SettingsItem } from "@routes/devices.$id.settings";
|
||||
import { BacklightSettings, useSettingsStore } from "@/hooks/stores";
|
||||
import { useJsonRpc } from "@/hooks/useJsonRpc";
|
||||
import { SelectMenuBasic } from "@components/SelectMenuBasic";
|
||||
import { UsbDeviceSetting } from "@components/UsbDeviceSetting";
|
||||
import { InputField } from "@/components/InputField";
|
||||
import { Button, LinkButton } from "@/components/Button";
|
||||
|
||||
import notifications from "../notifications";
|
||||
import { UsbInfoSetting } from "../components/UsbInfoSetting";
|
||||
import { FeatureFlag } from "../components/FeatureFlag";
|
||||
import { UsbEpModeSetting } from "@components/UsbEpModeSetting";
|
||||
|
||||
export default function SettingsHardwareRoute() {
|
||||
const [send] = useJsonRpc();
|
||||
@@ -333,13 +331,9 @@ export default function SettingsHardwareRoute() {
|
||||
|
||||
</div>
|
||||
|
||||
<FeatureFlag minAppVersion="0.3.8">
|
||||
<UsbDeviceSetting />
|
||||
</FeatureFlag>
|
||||
|
||||
<FeatureFlag minAppVersion="0.3.8">
|
||||
<UsbInfoSetting />
|
||||
</FeatureFlag>
|
||||
<UsbEpModeSetting />
|
||||
{/*<UsbDeviceSetting /> */}
|
||||
{/*<UsbInfoSetting /> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -40,16 +40,9 @@ const streamQualityOptions = [
|
||||
{ value: "0.1", label: "Low" },
|
||||
];
|
||||
|
||||
const audioModeOptions = [
|
||||
{ value: "disabled", label: "Disabled"},
|
||||
{ value: "usb", label: "USB"},
|
||||
//{ value: "hdmi", label: "HDMI"},
|
||||
]
|
||||
|
||||
export default function SettingsVideoRoute() {
|
||||
const [send] = useJsonRpc();
|
||||
const [streamQuality, setStreamQuality] = useState("1");
|
||||
const [audioMode, setAudioMode] = useState("disabled");
|
||||
const [customEdidValue, setCustomEdidValue] = useState<string | null>(null);
|
||||
const [edid, setEdid] = useState<string | null>(null);
|
||||
|
||||
@@ -62,11 +55,6 @@ export default function SettingsVideoRoute() {
|
||||
const setVideoContrast = useSettingsStore(state => state.setVideoContrast);
|
||||
|
||||
useEffect(() => {
|
||||
send("getAudioMode", {}, resp => {
|
||||
if ("error" in resp) return;
|
||||
setAudioMode(String(resp.result));
|
||||
});
|
||||
|
||||
send("getStreamQualityFactor", {}, resp => {
|
||||
if ("error" in resp) return;
|
||||
setStreamQuality(String(resp.result));
|
||||
@@ -95,21 +83,7 @@ export default function SettingsVideoRoute() {
|
||||
}
|
||||
});
|
||||
}, [send]);
|
||||
|
||||
const handleAudioModeChange = (mode: string) => {
|
||||
send("setAudioMode", { mode }, resp => {
|
||||
if ("error" in resp) {
|
||||
notifications.error(
|
||||
`Failed to set Audio Mode: ${resp.error.data || "Unknown error"}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
notifications.success(`Audio Mode set to ${audioModeOptions.find(x => x.value === mode )?.label}.It takes effect after refreshing the page`);
|
||||
setAudioMode(mode);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const handleStreamQualityChange = (factor: string) => {
|
||||
send("setStreamQualityFactor", { factor: Number(factor) }, resp => {
|
||||
if ("error" in resp) {
|
||||
@@ -149,20 +123,6 @@ export default function SettingsVideoRoute() {
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-4">
|
||||
<SettingsItem
|
||||
title="Audio Mode"
|
||||
badge="Experimental"
|
||||
description="Set the working mode of the audio"
|
||||
>
|
||||
<SelectMenuBasic
|
||||
size="SM"
|
||||
label=""
|
||||
value={audioMode}
|
||||
options={audioModeOptions}
|
||||
onChange={e => handleAudioModeChange(e.target.value)}
|
||||
/>
|
||||
</SettingsItem>
|
||||
|
||||
<SettingsItem
|
||||
title="Stream Quality"
|
||||
description="Adjust the quality of the video stream"
|
||||
|
||||
@@ -233,7 +233,8 @@ export default function KvmIdRoute() {
|
||||
const wsProtocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
||||
|
||||
const { sendMessage, getWebSocket } = useWebSocket(
|
||||
`${wsProtocol}//${window.location.host}/webrtc/signaling/client?id=${params.id}`,
|
||||
//`${wsProtocol}//${window.location.host}/webrtc/signaling/client?id=${params.id}`,
|
||||
`${wsProtocol}//${window.location.host}/webrtc/signaling/client`,
|
||||
{
|
||||
heartbeat: true,
|
||||
retryOnError: true,
|
||||
@@ -362,9 +363,18 @@ export default function KvmIdRoute() {
|
||||
setLoadingMessage("Creating peer connection...");
|
||||
pc = new RTCPeerConnection({
|
||||
// We only use STUN or TURN servers if we're in the cloud
|
||||
...(isInCloud && iceConfig?.iceServers
|
||||
? { iceServers: [iceConfig?.iceServers] }
|
||||
: {}),
|
||||
//...(isInCloud && iceConfig?.iceServers
|
||||
// ? { iceServers: [iceConfig?.iceServers] }
|
||||
// : {}),
|
||||
...(iceConfig?.iceServers
|
||||
? { iceServers: [iceConfig?.iceServers] }
|
||||
: {
|
||||
iceServers: [
|
||||
{
|
||||
urls: ['stun:stun.l.google.com:19302']
|
||||
}
|
||||
]
|
||||
}),
|
||||
});
|
||||
|
||||
setPeerConnectionState(pc.connectionState);
|
||||
|
||||
Reference in New Issue
Block a user