Compare commits

...

4 Commits

Author SHA1 Message Date
Marc Brooks 0a4a35a3cd
Merge 34dff0a3aa into 66cccfe9e1 2025-08-28 18:31:54 +00:00
Marc Brooks 34dff0a3aa
Removed diskChannel and localFile 2025-08-28 13:26:05 -05:00
Marc Brooks 66cccfe9e1
Add application icon for Safari and saved-bookmarks (#749) 2025-08-28 12:01:04 +02:00
Adam Shiervani a42384fed6
enhancement: add new EDID for DELL iDRAC (#693) 2025-08-27 10:16:17 +02:00
15 changed files with 38 additions and 45 deletions

View File

@ -1,7 +1,7 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- These are the fonts used in the app --> <!-- These are the fonts used in the app -->
<link <link
@ -27,7 +27,14 @@
/> />
<title>JetKVM</title> <title>JetKVM</title>
<link rel="stylesheet" href="/fonts/fonts.css" /> <link rel="stylesheet" href="/fonts/fonts.css" />
<link rel="icon" href="/favicon.png" /> <link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<meta name="apple-mobile-web-app-title" content="JetKVM" />
<link rel="manifest" href="/site.webmanifest" />
<meta name="theme-color" content="#051946" />
<meta name="description" content="A web-based KVM console for managing remote servers." />
<script> <script>
// Initial theme setup // Initial theme setup
document.documentElement.classList.toggle( document.documentElement.classList.toggle(

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
ui/public/favicon-96x96.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 972 B

BIN
ui/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

1
ui/public/favicon.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#1D4ED8" d="M0 6a6 6 0 0 1 6-6h12a6 6 0 0 1 6 6v12a6 6 0 0 1-6 6H6a6 6 0 0 1-6-6V6Z"/><path fill="#fff" d="M13.885 12a1.895 1.895 0 1 1-3.79 0 1.895 1.895 0 0 1 3.79 0Z"/><path fill="#fff" fill-rule="evenodd" d="M7.59 9.363c.49.182.74.727.558 1.218A4.103 4.103 0 0 0 12 16.105a4.103 4.103 0 0 0 3.852-5.526.947.947 0 0 1 1.777-.658A5.998 5.998 0 0 1 12 18a5.998 5.998 0 0 1-5.628-8.078.947.947 0 0 1 1.218-.56ZM11.993 7.895c-.628 0-1.22.14-1.75.39a.947.947 0 1 1-.808-1.714A5.985 5.985 0 0 1 11.993 6c.913 0 1.78.204 2.557.57a.947.947 0 1 1-.808 1.715 4.09 4.09 0 0 0-1.75-.39Z" clip-rule="evenodd"/><path fill="#1D4ED8" d="M0 6a6 6 0 0 1 6-6h12a6 6 0 0 1 6 6v12a6 6 0 0 1-6 6H6a6 6 0 0 1-6-6V6Z"/><path fill="#fff" d="M13.885 12a1.895 1.895 0 1 1-3.79 0 1.895 1.895 0 0 1 3.79 0Z"/><path fill="#fff" fill-rule="evenodd" d="M7.59 9.363c.49.182.74.727.558 1.218A4.103 4.103 0 0 0 12 16.105a4.103 4.103 0 0 0 3.852-5.526.947.947 0 0 1 1.777-.658A5.998 5.998 0 0 1 12 18a5.998 5.998 0 0 1-5.628-8.078.947.947 0 0 1 1.218-.56ZM11.993 7.895c-.628 0-1.22.14-1.75.39a.947.947 0 1 1-.808-1.714A5.985 5.985 0 0 1 11.993 6c.913 0 1.78.204 2.557.57a.947.947 0 1 1-.808 1.715 4.09 4.09 0 0 0-1.75-.39Z" clip-rule="evenodd"/></svg><style>@media (prefers-color-scheme:light){:root{filter:none}}@media (prefers-color-scheme:dark){:root{filter:none}}</style></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

1
ui/public/jetkvm.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#1D4ED8" d="M0 6a6 6 0 0 1 6-6h12a6 6 0 0 1 6 6v12a6 6 0 0 1-6 6H6a6 6 0 0 1-6-6V6Z"/><path fill="#fff" d="M13.885 12a1.895 1.895 0 1 1-3.79 0 1.895 1.895 0 0 1 3.79 0Z"/><path fill="#fff" fill-rule="evenodd" d="M7.59 9.363c.49.182.74.727.558 1.218A4.103 4.103 0 0 0 12 16.105a4.103 4.103 0 0 0 3.852-5.526.947.947 0 0 1 1.777-.658A5.998 5.998 0 0 1 12 18a5.998 5.998 0 0 1-5.628-8.078.947.947 0 0 1 1.218-.56ZM11.993 7.895c-.628 0-1.22.14-1.75.39a.947.947 0 1 1-.808-1.714A5.985 5.985 0 0 1 11.993 6c.913 0 1.78.204 2.557.57a.947.947 0 1 1-.808 1.715 4.09 4.09 0 0 0-1.75-.39Z" clip-rule="evenodd"/><path fill="#1D4ED8" d="M0 6a6 6 0 0 1 6-6h12a6 6 0 0 1 6 6v12a6 6 0 0 1-6 6H6a6 6 0 0 1-6-6V6Z"/><path fill="#fff" d="M13.885 12a1.895 1.895 0 1 1-3.79 0 1.895 1.895 0 0 1 3.79 0Z"/><path fill="#fff" fill-rule="evenodd" d="M7.59 9.363c.49.182.74.727.558 1.218A4.103 4.103 0 0 0 12 16.105a4.103 4.103 0 0 0 3.852-5.526.947.947 0 0 1 1.777-.658A5.998 5.998 0 0 1 12 18a5.998 5.998 0 0 1-5.628-8.078.947.947 0 0 1 1.218-.56ZM11.993 7.895c-.628 0-1.22.14-1.75.39a.947.947 0 1 1-.808-1.714A5.985 5.985 0 0 1 11.993 6c.913 0 1.78.204 2.557.57a.947.947 0 1 1-.808 1.715 4.09 4.09 0 0 0-1.75-.39Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,21 @@
{
"name": "JetKVM",
"short_name": "JetKVM",
"icons": [
{
"src": "/web-app-manifest-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/web-app-manifest-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"theme_color": "#002b36",
"background_color": "#051946",
"display": "standalone"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

@ -105,9 +105,6 @@ export interface RTCState {
setRpcDataChannel: (channel: RTCDataChannel) => void; setRpcDataChannel: (channel: RTCDataChannel) => void;
rpcDataChannel: RTCDataChannel | null; rpcDataChannel: RTCDataChannel | null;
diskChannel: RTCDataChannel | null;
setDiskChannel: (channel: RTCDataChannel) => void;
peerConnectionState: RTCPeerConnectionState | null; peerConnectionState: RTCPeerConnectionState | null;
setPeerConnectionState: (state: RTCPeerConnectionState) => void; setPeerConnectionState: (state: RTCPeerConnectionState) => void;
@ -160,9 +157,6 @@ export const useRTCStore = create<RTCState>(set => ({
peerConnectionState: null, peerConnectionState: null,
setPeerConnectionState: (state: RTCPeerConnectionState) => set({ peerConnectionState: state }), setPeerConnectionState: (state: RTCPeerConnectionState) => set({ peerConnectionState: state }),
diskChannel: null,
setDiskChannel: (channel: RTCDataChannel) => set({ diskChannel: channel }),
mediaStream: null, mediaStream: null,
setMediaStream: (stream: MediaStream) => set({ mediaStream: stream }), setMediaStream: (stream: MediaStream) => set({ mediaStream: stream }),
@ -390,9 +384,6 @@ export interface RemoteVirtualMediaState {
} }
export interface MountMediaState { export interface MountMediaState {
localFile: File | null;
setLocalFile: (file: MountMediaState["localFile"]) => void;
remoteVirtualMediaState: RemoteVirtualMediaState | null; remoteVirtualMediaState: RemoteVirtualMediaState | null;
setRemoteVirtualMediaState: (state: MountMediaState["remoteVirtualMediaState"]) => void; setRemoteVirtualMediaState: (state: MountMediaState["remoteVirtualMediaState"]) => void;
@ -410,9 +401,6 @@ export interface MountMediaState {
} }
export const useMountMediaStore = create<MountMediaState>(set => ({ export const useMountMediaStore = create<MountMediaState>(set => ({
localFile: null,
setLocalFile: (file: MountMediaState["localFile"]) => set({ localFile: file }),
remoteVirtualMediaState: null, remoteVirtualMediaState: null,
setRemoteVirtualMediaState: (state: MountMediaState["remoteVirtualMediaState"]) => set({ remoteVirtualMediaState: state }), setRemoteVirtualMediaState: (state: MountMediaState["remoteVirtualMediaState"]) => set({ remoteVirtualMediaState: state }),

View File

@ -48,7 +48,6 @@ export function Dialog({ onClose }: { onClose: () => void }) {
const { const {
modalView, modalView,
setModalView, setModalView,
setLocalFile,
setRemoteVirtualMediaState, setRemoteVirtualMediaState,
errorMessage, errorMessage,
setErrorMessage, setErrorMessage,
@ -58,7 +57,6 @@ export function Dialog({ onClose }: { onClose: () => void }) {
const [incompleteFileName, setIncompleteFileName] = useState<string | null>(null); const [incompleteFileName, setIncompleteFileName] = useState<string | null>(null);
const [mountInProgress, setMountInProgress] = useState(false); const [mountInProgress, setMountInProgress] = useState(false);
function clearMountMediaState() { function clearMountMediaState() {
setLocalFile(null);
setRemoteVirtualMediaState(null); setRemoteVirtualMediaState(null);
} }

View File

@ -32,6 +32,11 @@ const edids = [
"00FFFFFFFFFFFF0010AC132045393639201E0103803C22782ACD25A3574B9F270D5054A54B00714F8180A9C0D1C00101010101010101023A801871382D40582C450056502100001E000000FF00335335475132330A2020202020000000FC0044454C4C204432373231480A20000000FD00384C1E5311000A202020202020018102031AB14F90050403020716010611121513141F65030C001000023A801871382D40582C450056502100001E011D8018711C1620582C250056502100009E011D007251D01E206E28550056502100001E8C0AD08A20E02D10103E960056502100001800000000000000000000000000000000000000000000000000000000004F", "00FFFFFFFFFFFF0010AC132045393639201E0103803C22782ACD25A3574B9F270D5054A54B00714F8180A9C0D1C00101010101010101023A801871382D40582C450056502100001E000000FF00335335475132330A2020202020000000FC0044454C4C204432373231480A20000000FD00384C1E5311000A202020202020018102031AB14F90050403020716010611121513141F65030C001000023A801871382D40582C450056502100001E011D8018711C1620582C250056502100009E011D007251D01E206E28550056502100001E8C0AD08A20E02D10103E960056502100001800000000000000000000000000000000000000000000000000000000004F",
label: "DELL D2721H, 1920x1080", label: "DELL D2721H, 1920x1080",
}, },
{
value:
"00ffffffffffff0010ac0100020000000111010380221bff0a00000000000000000000adce0781800101010101010101010101010101000000ff0030303030303030303030303030000000ff0030303030303030303030303030000000fd00384c1f530b000a000000000000000000fc0044454c4c2049445241430a2020000a",
label: "DELL IDRAC EDID, 1280x1024",
},
]; ];
const streamQualityOptions = [ const streamQualityOptions = [
@ -140,7 +145,7 @@ export default function SettingsVideoRoute() {
title="Video Enhancement" title="Video Enhancement"
description="Adjust color settings to make the video output more vibrant and colorful" description="Adjust color settings to make the video output more vibrant and colorful"
/> />
<div className="space-y-4 pl-4"> <div className="space-y-4 pl-4">
<SettingsItem <SettingsItem
title="Saturation" title="Saturation"

View File

@ -29,7 +29,6 @@ import {
USBStates, USBStates,
useDeviceStore, useDeviceStore,
useHidStore, useHidStore,
useMountMediaStore,
useNetworkStateStore, useNetworkStateStore,
User, User,
useRTCStore, useRTCStore,
@ -132,7 +131,6 @@ export default function KvmIdRoute() {
const { const {
peerConnection, setPeerConnection, peerConnection, setPeerConnection,
peerConnectionState, setPeerConnectionState, peerConnectionState, setPeerConnectionState,
diskChannel, setDiskChannel,
setMediaStream, setMediaStream,
setRpcDataChannel, setRpcDataChannel,
isTurnServerInUse, setTurnServerInUse, isTurnServerInUse, setTurnServerInUse,
@ -484,18 +482,12 @@ export default function KvmIdRoute() {
setRpcDataChannel(rpcDataChannel); setRpcDataChannel(rpcDataChannel);
}; };
const diskDataChannel = pc.createDataChannel("disk");
diskDataChannel.onopen = () => {
setDiskChannel(diskDataChannel);
};
setPeerConnection(pc); setPeerConnection(pc);
}, [ }, [
cleanupAndStopReconnecting, cleanupAndStopReconnecting,
iceConfig?.iceServers, iceConfig?.iceServers,
legacyHTTPSignaling, legacyHTTPSignaling,
sendWebRTCSignal, sendWebRTCSignal,
setDiskChannel,
setMediaStream, setMediaStream,
setPeerConnection, setPeerConnection,
setPeerConnectionState, setPeerConnectionState,
@ -719,25 +711,6 @@ export default function KvmIdRoute() {
} }
}, [navigate, navigateTo, queryParams, setModalView, setQueryParams]); }, [navigate, navigateTo, queryParams, setModalView, setQueryParams]);
const { localFile } = useMountMediaStore();
useEffect(() => {
if (!diskChannel || !localFile) return;
diskChannel.onmessage = async e => {
console.debug("Received", e.data);
const data = JSON.parse(e.data);
const blob = localFile.slice(data.start, data.end);
const buf = await blob.arrayBuffer();
const header = new ArrayBuffer(16);
const headerView = new DataView(header);
headerView.setBigUint64(0, BigInt(data.start), false); // start offset, big-endian
headerView.setBigUint64(8, BigInt(buf.byteLength), false); // length, big-endian
const fullData = new Uint8Array(header.byteLength + buf.byteLength);
fullData.set(new Uint8Array(header), 0);
fullData.set(new Uint8Array(buf), header.byteLength);
diskChannel.send(fullData);
};
}, [diskChannel, localFile]);
// System update // System update
const [kvmTerminal, setKvmTerminal] = useState<RTCDataChannel | null>(null); const [kvmTerminal, setKvmTerminal] = useState<RTCDataChannel | null>(null);
const [serialConsole, setSerialConsole] = useState<RTCDataChannel | null>(null); const [serialConsole, setSerialConsole] = useState<RTCDataChannel | null>(null);

View File

@ -21,7 +21,6 @@ type Session struct {
ControlChannel *webrtc.DataChannel ControlChannel *webrtc.DataChannel
RPCChannel *webrtc.DataChannel RPCChannel *webrtc.DataChannel
HidChannel *webrtc.DataChannel HidChannel *webrtc.DataChannel
DiskChannel *webrtc.DataChannel
shouldUmountVirtualMedia bool shouldUmountVirtualMedia bool
rpcQueue chan webrtc.DataChannelMessage rpcQueue chan webrtc.DataChannelMessage
} }