mirror of
https://github.com/luckfox-eng29/kvm.git
synced 2026-05-26 08:05:08 +02:00
Update App version to 0.1.1
Signed-off-by: luckfox-eng29 <eng29@luckfox.com>
This commit is contained in:
@@ -38,7 +38,8 @@ const appendStatToMap = <T extends { timestamp: number }>(
|
||||
};
|
||||
|
||||
// Constants and types
|
||||
export type AvailableSidebarViews = "connection-stats";
|
||||
export type AvailableSidebarViews ="ConsoleLogViewer"|"MacroMoreList"|"Fullscreen"|"TerminalTabsMobile"|"SettingsModal"|"ClipboardMobile"|"KeyboardPanel"|"MousePanel"|"SettingsVideo"
|
||||
|"connection-stats"|"Clipboard"|"PowerControl"|"Macros"|"VirtualMedia"|"SharedFolders"|null;
|
||||
export type AvailableTerminalTypes = "kvm" | "serial" | "none";
|
||||
|
||||
export interface User {
|
||||
@@ -56,6 +57,11 @@ interface UIState {
|
||||
sidebarView: AvailableSidebarViews | null;
|
||||
setSidebarView: (view: AvailableSidebarViews | null) => void;
|
||||
|
||||
topBarView: AvailableSidebarViews | null;
|
||||
setTopBarView: (view: AvailableSidebarViews | null) => void;
|
||||
isAnimationComplete: boolean;
|
||||
setIsAnimationComplete: (enabled: boolean) => void;
|
||||
|
||||
disableVideoFocusTrap: boolean;
|
||||
setDisableVideoFocusTrap: (enabled: boolean) => void;
|
||||
|
||||
@@ -63,12 +69,15 @@ interface UIState {
|
||||
setWakeOnLanModalVisibility: (enabled: boolean) => void;
|
||||
|
||||
toggleSidebarView: (view: AvailableSidebarViews) => void;
|
||||
toggleTopBarView: (view: AvailableSidebarViews) => void;
|
||||
|
||||
isAttachedVirtualKeyboardVisible: boolean;
|
||||
setAttachedVirtualKeyboardVisibility: (enabled: boolean) => void;
|
||||
|
||||
terminalType: AvailableTerminalTypes;
|
||||
setTerminalType: (enabled: UIState["terminalType"]) => void;
|
||||
otherSession:boolean;
|
||||
setOtherSession: (enabled: boolean) => void;
|
||||
}
|
||||
|
||||
export const useUiStore = create<UIState>(set => ({
|
||||
@@ -77,9 +86,13 @@ export const useUiStore = create<UIState>(set => ({
|
||||
|
||||
sidebarView: null,
|
||||
setSidebarView: view => set({ sidebarView: view }),
|
||||
topBarView: null,
|
||||
setTopBarView: view => set({ topBarView: view }),
|
||||
|
||||
disableVideoFocusTrap: false,
|
||||
setDisableVideoFocusTrap: enabled => set({ disableVideoFocusTrap: enabled }),
|
||||
isAnimationComplete: false,
|
||||
setIsAnimationComplete: enabled => set({ isAnimationComplete: enabled }),
|
||||
|
||||
isWakeOnLanModalVisible: false,
|
||||
setWakeOnLanModalVisibility: enabled => set({ isWakeOnLanModalVisible: enabled }),
|
||||
@@ -87,16 +100,30 @@ export const useUiStore = create<UIState>(set => ({
|
||||
toggleSidebarView: view =>
|
||||
set(state => {
|
||||
if (state.sidebarView === view) {
|
||||
return { sidebarView: null };
|
||||
return { sidebarView: null , topBarView: null };
|
||||
} else {
|
||||
return { sidebarView: view };
|
||||
return { sidebarView: view , topBarView: null };
|
||||
}
|
||||
}),
|
||||
toggleTopBarView: view =>
|
||||
set(state => {
|
||||
|
||||
if (state.topBarView === view) {
|
||||
return { topBarView: null,sidebarView: null };
|
||||
} else {
|
||||
return { topBarView: view ,sidebarView: null };
|
||||
}
|
||||
}),
|
||||
otherSession:false,
|
||||
setOtherSession: enabled => set({ otherSession: enabled }),
|
||||
|
||||
isAttachedVirtualKeyboardVisible: true,
|
||||
setAttachedVirtualKeyboardVisibility: enabled =>
|
||||
set({ isAttachedVirtualKeyboardVisible: enabled }),
|
||||
}));
|
||||
}),
|
||||
|
||||
|
||||
);
|
||||
|
||||
interface RTCState {
|
||||
peerConnection: RTCPeerConnection | null;
|
||||
@@ -151,7 +178,13 @@ interface RTCState {
|
||||
appendDiskDataChannelStats: (stat: RTCDataChannelStats) => void;
|
||||
|
||||
terminalChannel: RTCDataChannel | null;
|
||||
setTerminalChannel: (channel: RTCDataChannel) => void;
|
||||
setTerminalChannel: (channel: RTCDataChannel | null) => void;
|
||||
|
||||
kvmTerminal: RTCDataChannel | null;
|
||||
setKvmTerminal: (channel: RTCDataChannel | null) => void;
|
||||
|
||||
serialConsole: RTCDataChannel | null;
|
||||
setSerialConsole: (channel: RTCDataChannel | null) => void;
|
||||
}
|
||||
|
||||
export const useRTCStore = create<RTCState>(set => ({
|
||||
@@ -227,6 +260,11 @@ export const useRTCStore = create<RTCState>(set => ({
|
||||
// Add these new properties to the store implementation
|
||||
terminalChannel: null,
|
||||
setTerminalChannel: channel => set({ terminalChannel: channel }),
|
||||
kvmTerminal: null,
|
||||
setKvmTerminal: channel => set({ kvmTerminal: channel }),
|
||||
|
||||
serialConsole: null,
|
||||
setSerialConsole: channel => set({ serialConsole: channel }),
|
||||
}));
|
||||
|
||||
interface MouseMove {
|
||||
@@ -356,6 +394,9 @@ interface SettingsState {
|
||||
setVideoBrightness: (value: number) => void;
|
||||
videoContrast: number;
|
||||
setVideoContrast: (value: number) => void;
|
||||
|
||||
forceHttp: boolean;
|
||||
setForceHttp: (enabled: boolean) => void;
|
||||
}
|
||||
|
||||
export const useSettingsStore = create(
|
||||
@@ -422,6 +463,9 @@ export const useSettingsStore = create(
|
||||
setVideoBrightness: value => set({ videoBrightness: value }),
|
||||
videoContrast: 1.0,
|
||||
setVideoContrast: value => set({ videoContrast: value }),
|
||||
|
||||
forceHttp: false,
|
||||
setForceHttp: enabled => set({ forceHttp: enabled }),
|
||||
}),
|
||||
{
|
||||
name: "settings",
|
||||
@@ -530,11 +574,21 @@ export interface HidState {
|
||||
|
||||
usbState: "configured" | "attached" | "not attached" | "suspended" | "addressed" | "default";
|
||||
setUsbState: (state: HidState["usbState"]) => void;
|
||||
|
||||
|
||||
isReinitializingGadget: boolean;
|
||||
setIsReinitializingGadget: (reinitializing: boolean) => void;
|
||||
}
|
||||
|
||||
export interface SerialState {
|
||||
isConnected: boolean;
|
||||
setIsConnected: (connected: boolean) => void;
|
||||
}
|
||||
|
||||
export const useSerialStore = create<SerialState>(set => ({
|
||||
isConnected: false,
|
||||
setIsConnected: connected => set({ isConnected: connected }),
|
||||
}));
|
||||
|
||||
export const useHidStore = create<HidState>((set, get) => ({
|
||||
activeKeys: [],
|
||||
activeModifiers: [],
|
||||
@@ -580,7 +634,7 @@ export const useHidStore = create<HidState>((set, get) => ({
|
||||
// Add these new properties for USB state
|
||||
usbState: "not attached",
|
||||
setUsbState: state => set({ usbState: state }),
|
||||
|
||||
|
||||
isReinitializingGadget: false,
|
||||
setIsReinitializingGadget: reinitializing => set({ isReinitializingGadget: reinitializing }),
|
||||
}));
|
||||
@@ -662,7 +716,15 @@ export const useUpdateStore = create<UpdateState>(set => ({
|
||||
isUpdatePending: false,
|
||||
setIsUpdatePending: isPending => set({ isUpdatePending: isPending }),
|
||||
|
||||
setOtaState: state => set({ otaState: state }),
|
||||
setOtaState: state =>
|
||||
set(current => {
|
||||
const definedEntries = Object.entries(state).filter(([, value]) => value !== undefined);
|
||||
const merged = {
|
||||
...current.otaState,
|
||||
...Object.fromEntries(definedEntries),
|
||||
} as UpdateState["otaState"];
|
||||
return { otaState: merged };
|
||||
}),
|
||||
otaState: {
|
||||
updating: false,
|
||||
error: null,
|
||||
@@ -714,7 +776,7 @@ export const useUsbConfigModalStore = create<UsbConfigModalState>(set => ({
|
||||
setErrorMessage: message => set({ errorMessage: message }),
|
||||
}));
|
||||
|
||||
interface LocalAuthModalState {
|
||||
export interface LocalAuthModalState {
|
||||
modalView:
|
||||
| "createPassword"
|
||||
| "deletePassword"
|
||||
@@ -1065,3 +1127,19 @@ export const useVpnStore = create<VpnState>(set => ({
|
||||
setZeroTierIP: networkID => set({ zeroTierIP: networkID }),
|
||||
}));
|
||||
|
||||
export interface MacrosSideState {
|
||||
sideTitle: string;
|
||||
setSideTitle: (title: string) => void;
|
||||
};
|
||||
|
||||
export const useMacrosSideTitleState = create<MacrosSideState>(set => ({
|
||||
sideTitle: "Keyboard Macros",
|
||||
setSideTitle: title => set({ sideTitle: title }),
|
||||
}));
|
||||
|
||||
export interface LogEntry {
|
||||
timestamp: string;
|
||||
level: 'log' | 'error' | 'warn' | 'info';
|
||||
message: string;
|
||||
originalArgs: any[];
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useEffect } from "react";
|
||||
|
||||
import { useRTCStore } from "@/hooks/stores";
|
||||
import { useRTCStore, useSettingsStore } from "@/hooks/stores";
|
||||
|
||||
export interface JsonRpcRequest {
|
||||
jsonrpc: string;
|
||||
@@ -31,12 +31,111 @@ export type JsonRpcResponse = JsonRpcSuccessResponse | JsonRpcErrorResponse;
|
||||
|
||||
const callbackStore = new Map<number | string, (resp: JsonRpcResponse) => void>();
|
||||
let requestCounter = 0;
|
||||
let httpSessionId: string | null = null;
|
||||
let httpSessionInvalidated = false;
|
||||
|
||||
function getHttpSessionId() {
|
||||
if (httpSessionId) return httpSessionId;
|
||||
try {
|
||||
const existing = window.sessionStorage.getItem("httpSessionId");
|
||||
if (existing) {
|
||||
httpSessionId = existing;
|
||||
return httpSessionId;
|
||||
}
|
||||
const generated = typeof crypto !== "undefined" && "randomUUID" in crypto
|
||||
? crypto.randomUUID()
|
||||
: `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
||||
window.sessionStorage.setItem("httpSessionId", generated);
|
||||
httpSessionId = generated;
|
||||
return httpSessionId;
|
||||
} catch {
|
||||
const fallback = `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
||||
httpSessionId = fallback;
|
||||
return httpSessionId;
|
||||
}
|
||||
}
|
||||
|
||||
export function resetHttpSessionId() {
|
||||
try {
|
||||
window.sessionStorage.removeItem("httpSessionId");
|
||||
} catch {
|
||||
void 0;
|
||||
}
|
||||
httpSessionId = null;
|
||||
httpSessionInvalidated = true;
|
||||
}
|
||||
|
||||
export function useJsonRpc(onRequest?: (payload: JsonRpcRequest) => void) {
|
||||
const rpcDataChannel = useRTCStore(state => state.rpcDataChannel);
|
||||
const forceHttp = useSettingsStore(state => state.forceHttp);
|
||||
|
||||
const send = useCallback(
|
||||
(method: string, params: unknown, callback?: (resp: JsonRpcResponse) => void) => {
|
||||
if (forceHttp) {
|
||||
if (httpSessionInvalidated) {
|
||||
requestCounter++;
|
||||
const payloadId = requestCounter;
|
||||
if (callback) {
|
||||
callback({
|
||||
jsonrpc: "2.0",
|
||||
error: { code: -32002, message: "HTTP session invalidated on client" },
|
||||
id: payloadId,
|
||||
} as JsonRpcErrorResponse);
|
||||
}
|
||||
return;
|
||||
}
|
||||
requestCounter++;
|
||||
const payload = { jsonrpc: "2.0", method, params, id: requestCounter };
|
||||
|
||||
fetch("/api/rpc", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Session-ID': getHttpSessionId(),
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then((data: unknown) => {
|
||||
const handleEvent = (event: JsonRpcRequest) => {
|
||||
if (event.method === "refreshPage") {
|
||||
const currentUrl = new URL(window.location.href);
|
||||
currentUrl.searchParams.set("networkChanged", "true");
|
||||
window.location.href = currentUrl.toString();
|
||||
return;
|
||||
}
|
||||
if (onRequest) onRequest(event);
|
||||
};
|
||||
|
||||
if (data && typeof data === "object" && ("response" in data || "event" in data)) {
|
||||
const wrapper = data as { response: JsonRpcResponse; event?: JsonRpcRequest };
|
||||
if (wrapper.event) {
|
||||
handleEvent(wrapper.event);
|
||||
}
|
||||
if (callback) callback(wrapper.response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data && typeof data === "object" && "method" in data) {
|
||||
handleEvent(data as JsonRpcRequest);
|
||||
return;
|
||||
}
|
||||
|
||||
if (callback) callback(data as JsonRpcResponse);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error("RPC over HTTP failed", err);
|
||||
if (callback) {
|
||||
callback({
|
||||
jsonrpc: "2.0",
|
||||
error: { code: -32000, message: "HTTP RPC failed", data: err.toString() },
|
||||
id: payload.id
|
||||
});
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (rpcDataChannel?.readyState !== "open") return;
|
||||
requestCounter++;
|
||||
const payload = { jsonrpc: "2.0", method, params, id: requestCounter };
|
||||
@@ -45,7 +144,7 @@ export function useJsonRpc(onRequest?: (payload: JsonRpcRequest) => void) {
|
||||
|
||||
rpcDataChannel.send(JSON.stringify(payload));
|
||||
},
|
||||
[rpcDataChannel],
|
||||
[rpcDataChannel, forceHttp, onRequest],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback } from "react";
|
||||
import notifications from "@/notifications";
|
||||
|
||||
import { useHidStore, useRTCStore } from "@/hooks/stores";
|
||||
import notifications from "@/notifications";
|
||||
import { useHidStore, useRTCStore, useSettingsStore } from "@/hooks/stores";
|
||||
import { useJsonRpc } from "@/hooks/useJsonRpc";
|
||||
import { keys, modifiers } from "@/keyboardMappings";
|
||||
|
||||
@@ -9,6 +9,7 @@ export default function useKeyboard() {
|
||||
const [send] = useJsonRpc();
|
||||
|
||||
const rpcDataChannel = useRTCStore(state => state.rpcDataChannel);
|
||||
const forceHttp = useSettingsStore(state => state.forceHttp);
|
||||
const updateActiveKeysAndModifiers = useHidStore(
|
||||
state => state.updateActiveKeysAndModifiers,
|
||||
);
|
||||
@@ -17,11 +18,10 @@ export default function useKeyboard() {
|
||||
|
||||
const sendKeyboardEvent = useCallback(
|
||||
(keys: number[], modifiers: number[]) => {
|
||||
if (rpcDataChannel?.readyState !== "open") return;
|
||||
if (!forceHttp && rpcDataChannel?.readyState !== "open") return;
|
||||
// Don't send keyboard events while reinitializing gadget
|
||||
if (isReinitializingGadget) return;
|
||||
if (usbState !== "configured") return;
|
||||
|
||||
const accModifier = modifiers.reduce((acc, val) => acc + val, 0);
|
||||
|
||||
send("keyboardReport", { keys, modifier: accModifier }, resp => {
|
||||
@@ -36,7 +36,7 @@ export default function useKeyboard() {
|
||||
// We do this for the info bar to display the currently pressed keys for the user
|
||||
updateActiveKeysAndModifiers({ keys: keys, modifiers: modifiers });
|
||||
},
|
||||
[rpcDataChannel?.readyState, send, updateActiveKeysAndModifiers, isReinitializingGadget, usbState],
|
||||
[forceHttp, rpcDataChannel?.readyState, send, updateActiveKeysAndModifiers, isReinitializingGadget, usbState],
|
||||
);
|
||||
|
||||
const resetKeyboardState = useCallback(() => {
|
||||
|
||||
Reference in New Issue
Block a user