diff --git a/ui/package-lock.json b/ui/package-lock.json
index ab6f459..2126943 100644
--- a/ui/package-lock.json
+++ b/ui/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "kvm-ui",
- "version": "2025.08.07.001",
+ "version": "2025.08.15.001",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "kvm-ui",
- "version": "2025.08.07.001",
+ "version": "2025.08.15.001",
"dependencies": {
"@headlessui/react": "^2.2.7",
"@headlessui/tailwindcss": "^0.2.2",
@@ -45,9 +45,9 @@
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.33.0",
"@tailwindcss/forms": "^0.5.10",
- "@tailwindcss/postcss": "^4.1.11",
+ "@tailwindcss/postcss": "^4.1.12",
"@tailwindcss/typography": "^0.5.16",
- "@tailwindcss/vite": "^4.1.11",
+ "@tailwindcss/vite": "^4.1.12",
"@types/react": "^19.1.10",
"@types/react-dom": "^19.1.7",
"@types/semver": "^7.7.0",
@@ -66,7 +66,7 @@
"postcss": "^8.5.6",
"prettier": "^3.6.2",
"prettier-plugin-tailwindcss": "^0.6.14",
- "tailwindcss": "^4.1.11",
+ "tailwindcss": "^4.1.12",
"typescript": "^5.9.2",
"vite": "^6.3.5",
"vite-tsconfig-paths": "^5.1.4"
@@ -88,24 +88,10 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/@ampproject/remapping": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
- "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.24"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
"node_modules/@babel/runtime": {
- "version": "7.28.2",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.2.tgz",
- "integrity": "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==",
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz",
+ "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -855,6 +841,17 @@
"@jridgewell/trace-mapping": "^0.3.24"
}
},
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
@@ -1549,25 +1546,25 @@
}
},
"node_modules/@tailwindcss/node": {
- "version": "4.1.11",
- "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz",
- "integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==",
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.12.tgz",
+ "integrity": "sha512-3hm9brwvQkZFe++SBt+oLjo4OLDtkvlE8q2WalaD/7QWaeM7KEJbAiY/LJZUaCs7Xa8aUu4xy3uoyX4q54UVdQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@ampproject/remapping": "^2.3.0",
- "enhanced-resolve": "^5.18.1",
- "jiti": "^2.4.2",
+ "@jridgewell/remapping": "^2.3.4",
+ "enhanced-resolve": "^5.18.3",
+ "jiti": "^2.5.1",
"lightningcss": "1.30.1",
"magic-string": "^0.30.17",
"source-map-js": "^1.2.1",
- "tailwindcss": "4.1.11"
+ "tailwindcss": "4.1.12"
}
},
"node_modules/@tailwindcss/oxide": {
- "version": "4.1.11",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz",
- "integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==",
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.12.tgz",
+ "integrity": "sha512-gM5EoKHW/ukmlEtphNwaGx45fGoEmP10v51t9unv55voWh6WrOL19hfuIdo2FjxIaZzw776/BUQg7Pck++cIVw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -1579,24 +1576,24 @@
"node": ">= 10"
},
"optionalDependencies": {
- "@tailwindcss/oxide-android-arm64": "4.1.11",
- "@tailwindcss/oxide-darwin-arm64": "4.1.11",
- "@tailwindcss/oxide-darwin-x64": "4.1.11",
- "@tailwindcss/oxide-freebsd-x64": "4.1.11",
- "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11",
- "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11",
- "@tailwindcss/oxide-linux-arm64-musl": "4.1.11",
- "@tailwindcss/oxide-linux-x64-gnu": "4.1.11",
- "@tailwindcss/oxide-linux-x64-musl": "4.1.11",
- "@tailwindcss/oxide-wasm32-wasi": "4.1.11",
- "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11",
- "@tailwindcss/oxide-win32-x64-msvc": "4.1.11"
+ "@tailwindcss/oxide-android-arm64": "4.1.12",
+ "@tailwindcss/oxide-darwin-arm64": "4.1.12",
+ "@tailwindcss/oxide-darwin-x64": "4.1.12",
+ "@tailwindcss/oxide-freebsd-x64": "4.1.12",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.12",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.1.12",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.1.12",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.1.12",
+ "@tailwindcss/oxide-linux-x64-musl": "4.1.12",
+ "@tailwindcss/oxide-wasm32-wasi": "4.1.12",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.1.12",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.1.12"
}
},
"node_modules/@tailwindcss/oxide-android-arm64": {
- "version": "4.1.11",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.11.tgz",
- "integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==",
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.12.tgz",
+ "integrity": "sha512-oNY5pq+1gc4T6QVTsZKwZaGpBb2N1H1fsc1GD4o7yinFySqIuRZ2E4NvGasWc6PhYJwGK2+5YT1f9Tp80zUQZQ==",
"cpu": [
"arm64"
],
@@ -1611,9 +1608,9 @@
}
},
"node_modules/@tailwindcss/oxide-darwin-arm64": {
- "version": "4.1.11",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.11.tgz",
- "integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==",
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.12.tgz",
+ "integrity": "sha512-cq1qmq2HEtDV9HvZlTtrj671mCdGB93bVY6J29mwCyaMYCP/JaUBXxrQQQm7Qn33AXXASPUb2HFZlWiiHWFytw==",
"cpu": [
"arm64"
],
@@ -1628,9 +1625,9 @@
}
},
"node_modules/@tailwindcss/oxide-darwin-x64": {
- "version": "4.1.11",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.11.tgz",
- "integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==",
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.12.tgz",
+ "integrity": "sha512-6UCsIeFUcBfpangqlXay9Ffty9XhFH1QuUFn0WV83W8lGdX8cD5/+2ONLluALJD5+yJ7k8mVtwy3zMZmzEfbLg==",
"cpu": [
"x64"
],
@@ -1645,9 +1642,9 @@
}
},
"node_modules/@tailwindcss/oxide-freebsd-x64": {
- "version": "4.1.11",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.11.tgz",
- "integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==",
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.12.tgz",
+ "integrity": "sha512-JOH/f7j6+nYXIrHobRYCtoArJdMJh5zy5lr0FV0Qu47MID/vqJAY3r/OElPzx1C/wdT1uS7cPq+xdYYelny1ww==",
"cpu": [
"x64"
],
@@ -1662,9 +1659,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
- "version": "4.1.11",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.11.tgz",
- "integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==",
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.12.tgz",
+ "integrity": "sha512-v4Ghvi9AU1SYgGr3/j38PD8PEe6bRfTnNSUE3YCMIRrrNigCFtHZ2TCm8142X8fcSqHBZBceDx+JlFJEfNg5zQ==",
"cpu": [
"arm"
],
@@ -1679,9 +1676,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
- "version": "4.1.11",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.11.tgz",
- "integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==",
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.12.tgz",
+ "integrity": "sha512-YP5s1LmetL9UsvVAKusHSyPlzSRqYyRB0f+Kl/xcYQSPLEw/BvGfxzbH+ihUciePDjiXwHh+p+qbSP3SlJw+6g==",
"cpu": [
"arm64"
],
@@ -1696,9 +1693,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
- "version": "4.1.11",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.11.tgz",
- "integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==",
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.12.tgz",
+ "integrity": "sha512-V8pAM3s8gsrXcCv6kCHSuwyb/gPsd863iT+v1PGXC4fSL/OJqsKhfK//v8P+w9ThKIoqNbEnsZqNy+WDnwQqCA==",
"cpu": [
"arm64"
],
@@ -1713,9 +1710,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
- "version": "4.1.11",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.11.tgz",
- "integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==",
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.12.tgz",
+ "integrity": "sha512-xYfqYLjvm2UQ3TZggTGrwxjYaLB62b1Wiysw/YE3Yqbh86sOMoTn0feF98PonP7LtjsWOWcXEbGqDL7zv0uW8Q==",
"cpu": [
"x64"
],
@@ -1730,9 +1727,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
- "version": "4.1.11",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.11.tgz",
- "integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==",
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.12.tgz",
+ "integrity": "sha512-ha0pHPamN+fWZY7GCzz5rKunlv9L5R8kdh+YNvP5awe3LtuXb5nRi/H27GeL2U+TdhDOptU7T6Is7mdwh5Ar3A==",
"cpu": [
"x64"
],
@@ -1747,9 +1744,9 @@
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi": {
- "version": "4.1.11",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.11.tgz",
- "integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==",
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.12.tgz",
+ "integrity": "sha512-4tSyu3dW+ktzdEpuk6g49KdEangu3eCYoqPhWNsZgUhyegEda3M9rG0/j1GV/JjVVsj+lG7jWAyrTlLzd/WEBg==",
"bundleDependencies": [
"@napi-rs/wasm-runtime",
"@emnapi/core",
@@ -1765,11 +1762,11 @@
"license": "MIT",
"optional": true,
"dependencies": {
- "@emnapi/core": "^1.4.3",
- "@emnapi/runtime": "^1.4.3",
- "@emnapi/wasi-threads": "^1.0.2",
- "@napi-rs/wasm-runtime": "^0.2.11",
- "@tybys/wasm-util": "^0.9.0",
+ "@emnapi/core": "^1.4.5",
+ "@emnapi/runtime": "^1.4.5",
+ "@emnapi/wasi-threads": "^1.0.4",
+ "@napi-rs/wasm-runtime": "^0.2.12",
+ "@tybys/wasm-util": "^0.10.0",
"tslib": "^2.8.0"
},
"engines": {
@@ -1777,9 +1774,9 @@
}
},
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
- "version": "4.1.11",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz",
- "integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==",
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.12.tgz",
+ "integrity": "sha512-iGLyD/cVP724+FGtMWslhcFyg4xyYyM+5F4hGvKA7eifPkXHRAUDFaimu53fpNg9X8dfP75pXx/zFt/jlNF+lg==",
"cpu": [
"arm64"
],
@@ -1794,9 +1791,9 @@
}
},
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
- "version": "4.1.11",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz",
- "integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==",
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.12.tgz",
+ "integrity": "sha512-NKIh5rzw6CpEodv/++r0hGLlfgT/gFN+5WNdZtvh6wpU2BpGNgdjvj6H2oFc8nCM839QM1YOhjpgbAONUb4IxA==",
"cpu": [
"x64"
],
@@ -1811,17 +1808,17 @@
}
},
"node_modules/@tailwindcss/postcss": {
- "version": "4.1.11",
- "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.11.tgz",
- "integrity": "sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA==",
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.12.tgz",
+ "integrity": "sha512-5PpLYhCAwf9SJEeIsSmCDLgyVfdBhdBpzX1OJ87anT9IVR0Z9pjM0FNixCAUAHGnMBGB8K99SwAheXrT0Kh6QQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
- "@tailwindcss/node": "4.1.11",
- "@tailwindcss/oxide": "4.1.11",
+ "@tailwindcss/node": "4.1.12",
+ "@tailwindcss/oxide": "4.1.12",
"postcss": "^8.4.41",
- "tailwindcss": "4.1.11"
+ "tailwindcss": "4.1.12"
}
},
"node_modules/@tailwindcss/typography": {
@@ -1841,15 +1838,15 @@
}
},
"node_modules/@tailwindcss/vite": {
- "version": "4.1.11",
- "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.11.tgz",
- "integrity": "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==",
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.12.tgz",
+ "integrity": "sha512-4pt0AMFDx7gzIrAOIYgYP0KCBuKWqyW8ayrdiLEjoJTT4pKTjrzG/e4uzWtTLDziC+66R9wbUqZBccJalSE5vQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@tailwindcss/node": "4.1.11",
- "@tailwindcss/oxide": "4.1.11",
- "tailwindcss": "4.1.11"
+ "@tailwindcss/node": "4.1.12",
+ "@tailwindcss/oxide": "4.1.12",
+ "tailwindcss": "4.1.12"
},
"peerDependencies": {
"vite": "^5.2.0 || ^6 || ^7"
@@ -2739,9 +2736,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001734",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001734.tgz",
- "integrity": "sha512-uhE1Ye5vgqju6OI71HTQqcBCZrvHugk0MjLak7Q+HfoBgoq5Bi+5YnwjP4fjDgrtYr/l8MVRBvzz9dPD4KyK0A==",
+ "version": "1.0.30001735",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001735.tgz",
+ "integrity": "sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==",
"dev": true,
"funding": [
{
@@ -3159,9 +3156,9 @@
}
},
"node_modules/electron-to-chromium": {
- "version": "1.5.200",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.200.tgz",
- "integrity": "sha512-rFCxROw7aOe4uPTfIAx+rXv9cEcGx+buAF4npnhtTqCJk5KDFRnh3+KYj7rdVh6lsFt5/aPs+Irj9rZ33WMA7w==",
+ "version": "1.5.201",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.201.tgz",
+ "integrity": "sha512-ZG65vsrLClodGqywuigc+7m0gr4ISoTQttfVh7nfpLv0M7SIwF4WbFNEOywcqTiujs12AUeeXbFyQieDICAIxg==",
"dev": true,
"license": "ISC"
},
@@ -6478,9 +6475,9 @@
}
},
"node_modules/tailwindcss": {
- "version": "4.1.11",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz",
- "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==",
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.12.tgz",
+ "integrity": "sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==",
"license": "MIT"
},
"node_modules/tapable": {
@@ -6534,10 +6531,13 @@
}
},
"node_modules/tinyglobby/node_modules/fdir": {
- "version": "6.4.6",
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
- "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
"peerDependencies": {
"picomatch": "^3 || ^4"
},
@@ -6939,10 +6939,13 @@
}
},
"node_modules/vite/node_modules/fdir": {
- "version": "6.4.6",
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
- "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
"peerDependencies": {
"picomatch": "^3 || ^4"
},
diff --git a/ui/package.json b/ui/package.json
index 4c929f0..f10341f 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -1,7 +1,7 @@
{
"name": "kvm-ui",
"private": true,
- "version": "2025.08.07.001",
+ "version": "2025.08.15.001",
"type": "module",
"engines": {
"node": "22.15.0"
@@ -56,9 +56,9 @@
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.33.0",
"@tailwindcss/forms": "^0.5.10",
- "@tailwindcss/postcss": "^4.1.11",
+ "@tailwindcss/postcss": "^4.1.12",
"@tailwindcss/typography": "^0.5.16",
- "@tailwindcss/vite": "^4.1.11",
+ "@tailwindcss/vite": "^4.1.12",
"@types/react": "^19.1.10",
"@types/react-dom": "^19.1.7",
"@types/semver": "^7.7.0",
@@ -77,7 +77,7 @@
"postcss": "^8.5.6",
"prettier": "^3.6.2",
"prettier-plugin-tailwindcss": "^0.6.14",
- "tailwindcss": "^4.1.11",
+ "tailwindcss": "^4.1.12",
"typescript": "^5.9.2",
"vite": "^6.3.5",
"vite-tsconfig-paths": "^5.1.4"
diff --git a/ui/src/components/ActionBar.tsx b/ui/src/components/ActionBar.tsx
index 801cc7a..c8a214b 100644
--- a/ui/src/components/ActionBar.tsx
+++ b/ui/src/components/ActionBar.tsx
@@ -48,7 +48,7 @@ export default function Actionbar({
if (!open) {
setTimeout(() => {
setDisableFocusTrap(false);
- console.log("Popover is closing. Returning focus trap to video");
+ console.debug("Popover is closing. Returning focus trap to video");
}, 0);
}
}
diff --git a/ui/src/components/InfoBar.tsx b/ui/src/components/InfoBar.tsx
index bc123ac..eafc380 100644
--- a/ui/src/components/InfoBar.tsx
+++ b/ui/src/components/InfoBar.tsx
@@ -3,7 +3,6 @@ import { useEffect, useMemo } from "react";
import { cx } from "@/cva.config";
import {
HidState,
- KeysDownState,
MouseState,
RTCState,
SettingsState,
@@ -39,7 +38,7 @@ export default function InfoBar() {
if (!rpcDataChannel) return;
rpcDataChannel.onclose = () => console.log("rpcDataChannel has closed");
rpcDataChannel.onerror = (e: Event) =>
- console.log(`Error on DataChannel '${rpcDataChannel.label}': ${e}`);
+ console.error(`Error on DataChannel '${rpcDataChannel.label}': ${e}`);
}, [rpcDataChannel]);
const keyboardLedState = useHidStore((state: HidState) => state.keyboardLedState);
@@ -49,12 +48,11 @@ export default function InfoBar() {
const hdmiState = useVideoStore((state: VideoState) => state.hdmiState);
const displayKeys = useMemo(() => {
- if (!showPressedKeys || !keysDownState)
+ if (!showPressedKeys)
return "";
- const state = keysDownState as KeysDownState;
- const activeModifierMask = state.modifier || 0;
- const keysDown = state.keys || [];
+ const activeModifierMask = keysDownState.modifier || 0;
+ const keysDown = keysDownState.keys || [];
const modifierNames = Object.entries(modifiers).filter(([_, mask]) => (activeModifierMask & mask) !== 0).map(([name, _]) => name);
const keyNames = Object.entries(keys).filter(([_, value]) => keysDown.includes(value)).map(([name, _]) => name);
diff --git a/ui/src/components/USBStateStatus.tsx b/ui/src/components/USBStateStatus.tsx
index f0b2cb2..618a9c3 100644
--- a/ui/src/components/USBStateStatus.tsx
+++ b/ui/src/components/USBStateStatus.tsx
@@ -67,7 +67,7 @@ export default function USBStateStatus({
};
const props = StatusCardProps[state];
if (!props) {
- console.log("Unsupported USB state: ", state);
+ console.warn("Unsupported USB state: ", state);
return;
}
diff --git a/ui/src/components/UsbInfoSetting.tsx b/ui/src/components/UsbInfoSetting.tsx
index 198335c..4f63697 100644
--- a/ui/src/components/UsbInfoSetting.tsx
+++ b/ui/src/components/UsbInfoSetting.tsx
@@ -101,8 +101,8 @@ export function UsbInfoSetting() {
`Failed to load USB Config: ${resp.error.data || "Unknown error"}`,
);
} else {
- console.log("syncUsbConfigProduct#getUsbConfig result:", resp.result);
const usbConfigState = resp.result as UsbConfigState;
+ console.log("syncUsbConfigProduct#getUsbConfig result:", usbConfigState);
const product = usbConfigs.map(u => u.value).includes(usbConfigState.product)
? usbConfigState.product
: "custom";
diff --git a/ui/src/components/VirtualKeyboard.tsx b/ui/src/components/VirtualKeyboard.tsx
index e08cee7..fa7857d 100644
--- a/ui/src/components/VirtualKeyboard.tsx
+++ b/ui/src/components/VirtualKeyboard.tsx
@@ -14,7 +14,7 @@ import DetachIconRaw from "@/assets/detach-icon.svg";
import { cx } from "@/cva.config";
import { HidState, useHidStore, useUiStore } from "@/hooks/stores";
import useKeyboard from "@/hooks/useKeyboard";
-import { keyDisplayMap, keys, modifiers } from "@/keyboardMappings";
+import { keyDisplayMap, keys } from "@/keyboardMappings";
export const DetachIcon = ({ className }: { className?: string }) => {
return
;
@@ -39,7 +39,7 @@ function KeyboardWrapper() {
const setVirtualKeyboard = useHidStore(state => state.setVirtualKeyboardEnabled);
const keysDownState = useHidStore((state: HidState) => state.keysDownState);
- const { handleKeyPress, sendKeyboardEvent, resetKeyboardState } = useKeyboard();
+ const { handleKeyPress, executeMacro } = useKeyboard();
const [isDragging, setIsDragging] = useState(false);
const [position, setPosition] = useState({ x: 0, y: 0 });
@@ -133,26 +133,25 @@ function KeyboardWrapper() {
const latchingKeys = ["CapsLock", "ScrollLock", "NumLock", "Meta", "Compose", "Kana"];
const dynamicKeys = ["ShiftLeft", "ShiftRight", "ControlLeft", "ControlRight", "AltLeft", "AltRight", "MetaLeft", "MetaRight"];
+ // handle the fake key-macros we have defined for common combinations
if (key === "CtrlAltDelete") {
- sendKeyboardEvent({ keys: [keys.Delete], modifier: modifiers.ControlLeft | modifiers.AltLeft });
- setTimeout(resetKeyboardState, 100);
+ executeMacro([ { keys: ["Delete"], modifiers: ["ControlLeft", "AltLeft"], delay: 100 } ]);
return;
}
if (key === "AltMetaEscape") {
- sendKeyboardEvent({ keys: [keys.Escape], modifier: modifiers.AltLeft | modifiers.MetaLeft });
- setTimeout(resetKeyboardState, 100);
+ executeMacro([ { keys: ["Escape"], modifiers: ["AltLeft", "MetaLeft"], delay: 100 } ]);
return;
}
if (key === "CtrlAltBackspace") {
- sendKeyboardEvent({ keys: [keys.Backspace], modifier: modifiers.ControlLeft | modifiers.AltLeft });
- setTimeout(resetKeyboardState, 100);
+ executeMacro([ { keys: ["Backspace"], modifiers: ["ControlLeft", "AltLeft"], delay: 100 } ]);
return;
}
// if they press any of the latching keys, we send a keypress down event and the release it automatically (on timer)
if (latchingKeys.includes(key)) {
+ console.debug(`Latching key pressed: ${key} sending down and delayed up pair`);
handleKeyPress(keys[key], true)
setTimeout(() => handleKeyPress(keys[key], false), 100);
return;
@@ -161,16 +160,18 @@ function KeyboardWrapper() {
// if they press any of the dynamic keys, we send a keypress down event but we don't release it until they click it again
if (dynamicKeys.includes(key)) {
const currentlyDown = keysDownState.keys.includes(keys[key]);
+ console.debug(`Dynamic key pressed: ${key} was currently down: ${currentlyDown}, toggling state`);
handleKeyPress(keys[key], !currentlyDown)
return;
}
// otherwise, just treat it as a down+up pair
const cleanKey = key.replace(/[()]/g, "");
+ console.debug(`Regular key pressed: ${cleanKey} sending down and up pair`);
handleKeyPress(keys[cleanKey], true);
setTimeout(() => handleKeyPress(keys[cleanKey], false), 50);
},
- [handleKeyPress, sendKeyboardEvent, resetKeyboardState, keysDownState],
+ [executeMacro, handleKeyPress, keysDownState],
);
// TODO handle the display of down keys and the layout change for shift/caps lock
diff --git a/ui/src/components/WebRTCVideo.tsx b/ui/src/components/WebRTCVideo.tsx
index 33c3dfc..6e273f5 100644
--- a/ui/src/components/WebRTCVideo.tsx
+++ b/ui/src/components/WebRTCVideo.tsx
@@ -35,6 +35,7 @@ export default function WebRTCVideo() {
const [isPlaying, setIsPlaying] = useState(false);
const peerConnectionState = useRTCStore((state: RTCState) => state.peerConnectionState);
const [isPointerLockActive, setIsPointerLockActive] = useState(false);
+ const [isKeyboardLockActive, setIsKeyboardLockActive] = useState(false);
// Store hooks
const settings = useSettingsStore();
const { handleKeyPress, resetKeyboardState } = useKeyboard();
@@ -69,16 +70,19 @@ export default function WebRTCVideo() {
const [send] = useJsonRpc();
// Video-related
+ const handleResize = useCallback(
+ ({ width, height }: { width: number; height: number }) => {
+ if (!videoElm.current) return;
+ // Do something with width and height, e.g.:
+ setVideoClientSize(width, height);
+ setVideoSize(videoElm.current.videoWidth, videoElm.current.videoHeight);
+ },
+ [setVideoClientSize, setVideoSize]
+ );
+
useResizeObserver({
ref: videoElm as React.RefObject,
- onResize: ({ width, height }) => {
- // This is actually client size, not videoSize
- if (width && height) {
- if (!videoElm.current) return;
- setVideoClientSize(width, height);
- setVideoSize(videoElm.current.videoWidth, videoElm.current.videoHeight);
- }
- },
+ onResize: handleResize,
});
const updateVideoSizeStore = useCallback(
@@ -107,7 +111,7 @@ export default function WebRTCVideo() {
const isFullscreenEnabled = document.fullscreenEnabled;
const checkNavigatorPermissions = useCallback(async (permissionName: string) => {
- if (!navigator.permissions || !navigator.permissions.query) {
+ if (!navigator || !navigator.permissions || !navigator.permissions.query) {
return false; // if can't query permissions, assume NOT granted
}
@@ -142,28 +146,30 @@ export default function WebRTCVideo() {
const isKeyboardLockGranted = await checkNavigatorPermissions("keyboard-lock");
- if (isKeyboardLockGranted && "keyboard" in navigator) {
+ if (isKeyboardLockGranted && navigator && "keyboard" in navigator) {
try {
// @ts-expect-error - keyboard lock is not supported in all browsers
await navigator.keyboard.lock();
+ setIsKeyboardLockActive(true);
} catch {
// ignore errors
}
}
- }, [checkNavigatorPermissions]);
+ }, [checkNavigatorPermissions, setIsKeyboardLockActive]);
const releaseKeyboardLock = useCallback(async () => {
if (videoElm.current === null || document.fullscreenElement !== videoElm.current) return;
- if ("keyboard" in navigator) {
+ if (navigator && "keyboard" in navigator) {
try {
// @ts-expect-error - keyboard unlock is not supported in all browsers
await navigator.keyboard.unlock();
} catch {
// ignore errors
}
+ setIsKeyboardLockActive(false);
}
- }, []);
+ }, [setIsKeyboardLockActive]);
useEffect(() => {
if (!isPointerLockPossible || !videoElm.current) return;
@@ -353,12 +359,24 @@ export default function WebRTCVideo() {
// https://bugzilla.mozilla.org/show_bug.cgi?id=1299553
if (e.metaKey && hidKey < 0xE0) {
setTimeout(() => {
+ console.debug(`Forcing the meta key release of associated key: ${hidKey}`);
handleKeyPress(hidKey, false);
}, 10);
}
+ console.debug(`Key down: ${hidKey}`);
handleKeyPress(hidKey, true);
+
+ if (!isKeyboardLockActive && hidKey === keys.MetaLeft) {
+ // If the left meta key was just pressed and we're not keyboard locked
+ // we'll never see the keyup event because the browser is going to lose
+ // focus so set a deferred keyup after a short delay
+ setTimeout(() => {
+ console.debug(`Forcing the left meta key release`);
+ handleKeyPress(hidKey, false);
+ }, 100);
+ }
},
- [handleKeyPress],
+ [handleKeyPress, isKeyboardLockActive],
);
const keyUpHandler = useCallback(
@@ -372,6 +390,7 @@ export default function WebRTCVideo() {
return;
}
+ console.debug(`Key up: ${hidKey}`);
handleKeyPress(hidKey, false);
},
[handleKeyPress],
@@ -385,7 +404,7 @@ export default function WebRTCVideo() {
// Fix only works in chrome based browsers.
if (e.code === "Space") {
if (videoElm.current.paused) {
- console.log("Force playing video");
+ console.debug("Force playing video");
videoElm.current.play();
}
}
diff --git a/ui/src/hooks/useKeyboard.ts b/ui/src/hooks/useKeyboard.ts
index f98066d..f9d9b04 100644
--- a/ui/src/hooks/useKeyboard.ts
+++ b/ui/src/hooks/useKeyboard.ts
@@ -19,19 +19,33 @@ export default function useKeyboard() {
(state: KeysDownState) => {
if (rpcDataChannel?.readyState !== "open") return;
- //TODO would be nice if the keyboardReport rpc call returned the current state like keypressReport does
- send("keyboardReport", { keys: state.keys, modifier: state.modifier });
+ console.debug(`Send keyboardReport keys: ${state.keys}, modifier: ${state.modifier}`);
+ send("keyboardReport", { keys: state.keys, modifier: state.modifier }, (resp: JsonRpcResponse) => {
+ if ("error" in resp) {
+ console.error(`Failed to send keyboard report ${state}`, resp.error);
+ } else {
+ const keysDownState = resp.result as KeysDownState;
- // We do this for the info bar to display the currently pressed keys for the user
- setKeysDownState(state);
+ if (keysDownState) {
+ // new devices return the keyDownState, so we can use it to update the state
+ setKeysDownState(keysDownState);
+ setKeyPressAvailable(true); // if they returned a keysDownState, we know they also support keyPressReport
+ } else {
+ // old devices do not return the keyDownState, so we just pretend they accepted what we sent
+ setKeysDownState(state);
+ // and we shouldn't set keyPressAvailable here because we don't know if they support it
+ }
+ }
+ });
},
- [rpcDataChannel?.readyState, send, setKeysDownState],
+ [rpcDataChannel?.readyState, send, setKeyPressAvailable, setKeysDownState],
);
const sendKeypressEvent = useCallback(
(key: number, press: boolean) => {
if (rpcDataChannel?.readyState !== "open") return;
+ console.debug(`Send keypressEvent key: ${key}, press: ${press}`);
send("keypressReport", { key, press }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
// -32601 means the method is not supported
@@ -43,9 +57,12 @@ export default function useKeyboard() {
console.error(`Failed to send key ${key} press: ${press}`, resp.error);
}
} else {
- const keyDownState = resp.result as KeysDownState;
- // We do this for the info bar to display the currently pressed keys for the user
- setKeysDownState(keyDownState);
+ const keysDownState = resp.result as KeysDownState;
+
+ if (keysDownState) {
+ setKeysDownState(keysDownState);
+ // we don't need to set keyPressAvailable here, because it's already true or we never landed here
+ }
}
});
},
@@ -53,8 +70,11 @@ export default function useKeyboard() {
);
const resetKeyboardState = useCallback(() => {
- sendKeyboardEvent({ keys: [], modifier: 0 });
- }, [sendKeyboardEvent]);
+ console.debug("Resetting keyboard state");
+ keysDownState.keys.fill(0); // Reset the keys buffer to zeros
+ keysDownState.modifier = 0; // Reset the modifier state to zero
+ sendKeyboardEvent(keysDownState);
+ }, [keysDownState, sendKeyboardEvent]);
const executeMacro = async (steps: { keys: string[] | null; modifiers: string[] | null; delay: number }[]) => {
for (const [index, step] of steps.entries()) {
@@ -87,6 +107,7 @@ export default function useKeyboard() {
const modifierMask = hidKeyToModifierMask[key] || 0;
if (modifierMask !== 0) {
+ console.debug(`Handling modifier key: ${key}, press: ${press}, current modifiers: ${modifiers}, modifier mask: ${modifierMask}`);
if (press) {
modifiers |= modifierMask;
} else {
@@ -132,22 +153,24 @@ export default function useKeyboard() {
const handleKeyPress = useCallback(
(key: number, press: boolean) => {
+ if (rpcDataChannel?.readyState !== "open") return;
+
if (keyPressAvailable) {
// if the keyPress api is available, we can just send the key press event
sendKeypressEvent(key, press);
-
- // TODO handle the case where the keyPress api is not available and we need to handle the key locally now...
- } else {
- // if the keyPress api is not available, we need to handle the key locally
- const newKeysDownState = handleKeyLocally(keysDownState, key, press);
- setKeysDownState(newKeysDownState);
-
- // then we send the full state
- sendKeyboardEvent(newKeysDownState);
+ // if keyPress api is STILL available, we don't need to handle the key locally
+ if (keyPressAvailable) return;
}
+
+ // if the keyPress api is not available, we need to handle the key locally
+ const downState = handleKeyLocally(keysDownState, key, press);
+ setKeysDownState(downState);
+
+ // then we send the full state
+ sendKeyboardEvent(downState);
},
- [keyPressAvailable, keysDownState, sendKeyboardEvent, sendKeypressEvent, setKeysDownState],
+ [keyPressAvailable, keysDownState, rpcDataChannel?.readyState, sendKeyboardEvent, sendKeypressEvent, setKeysDownState],
);
- return { handleKeyPress, sendKeyboardEvent, sendKeypressEvent, resetKeyboardState, executeMacro };
+ return { handleKeyPress, resetKeyboardState, executeMacro };
}
diff --git a/ui/src/routes/devices.$id.settings.network.tsx b/ui/src/routes/devices.$id.settings.network.tsx
index 6fcd588..1df380f 100644
--- a/ui/src/routes/devices.$id.settings.network.tsx
+++ b/ui/src/routes/devices.$id.settings.network.tsx
@@ -106,11 +106,12 @@ export default function SettingsNetworkRoute() {
setNetworkSettingsLoaded(false);
send("getNetworkSettings", {}, resp => {
if ("error" in resp) return;
- console.log(resp.result);
- setNetworkSettings(resp.result as NetworkSettings);
+ const networkSettings = resp.result as NetworkSettings;
+ console.debug("Network settings: ", networkSettings);
+ setNetworkSettings(networkSettings);
if (!firstNetworkSettings.current) {
- firstNetworkSettings.current = resp.result as NetworkSettings;
+ firstNetworkSettings.current = networkSettings;
}
setNetworkSettingsLoaded(true);
});
@@ -119,8 +120,9 @@ export default function SettingsNetworkRoute() {
const getNetworkState = useCallback(() => {
send("getNetworkState", {}, resp => {
if ("error" in resp) return;
- console.log(resp.result);
- setNetworkState(resp.result as NetworkState);
+ const networkState = resp.result as NetworkState;
+ console.debug("Network state:", networkState);
+ setNetworkState(networkState);
});
}, [send, setNetworkState]);
@@ -136,9 +138,10 @@ export default function SettingsNetworkRoute() {
setNetworkSettingsLoaded(true);
return;
}
+ const networkSettings = resp.result as NetworkSettings;
// We need to update the firstNetworkSettings ref to the new settings so we can use it to determine if the settings have changed
- firstNetworkSettings.current = resp.result as NetworkSettings;
- setNetworkSettings(resp.result as NetworkSettings);
+ firstNetworkSettings.current = networkSettings;
+ setNetworkSettings(networkSettings);
getNetworkState();
setNetworkSettingsLoaded(true);
notifications.success("Network settings saved");
diff --git a/ui/src/routes/devices.$id.settings.tsx b/ui/src/routes/devices.$id.settings.tsx
index 09703c3..2a7e190 100644
--- a/ui/src/routes/devices.$id.settings.tsx
+++ b/ui/src/routes/devices.$id.settings.tsx
@@ -73,7 +73,7 @@ export default function SettingsRoute() {
// For some reason, the focus trap is not disabled immediately
// so we need to blur the active element
(document.activeElement as HTMLElement)?.blur();
- console.log("Just disabled focus trap");
+ console.debug("Just disabled focus trap");
}, 300);
return () => {
diff --git a/ui/src/routes/devices.$id.tsx b/ui/src/routes/devices.$id.tsx
index efc52df..81ad5fd 100644
--- a/ui/src/routes/devices.$id.tsx
+++ b/ui/src/routes/devices.$id.tsx
@@ -216,7 +216,7 @@ export default function KvmIdRoute() {
clearInterval(checkInterval);
setLoadingMessage("Connection established");
} else if (attempts >= 10) {
- console.log(
+ console.warn(
"[setRemoteSessionDescription] Failed to establish connection after 10 attempts",
{
connectionState: pc.connectionState,
@@ -444,7 +444,7 @@ export default function KvmIdRoute() {
if (isNewSignalingEnabled) {
sendWebRTCSignal("offer", { sd: sd });
} else {
- console.log("Legacy signanling. Waiting for ICE Gathering to complete...");
+ console.log("Legacy signaling. Waiting for ICE Gathering to complete...");
}
} catch (e) {
console.error(
@@ -511,7 +511,7 @@ export default function KvmIdRoute() {
useEffect(() => {
if (peerConnectionState === "failed") {
- console.log("Connection failed, closing peer connection");
+ console.warn("Connection failed, closing peer connection");
cleanupAndStopReconnecting();
}
}, [peerConnectionState, cleanupAndStopReconnecting]);
@@ -616,20 +616,24 @@ export default function KvmIdRoute() {
}
if (resp.method === "networkState") {
- console.log("Setting network state", resp.params);
+ console.debug("Setting network state", resp.params);
setNetworkState(resp.params as NetworkState);
}
if (resp.method === "keyboardLedState") {
const ledState = resp.params as KeyboardLedState;
- console.log("Setting keyboard led state", ledState);
+ console.debug("Setting keyboard led state", ledState);
setKeyboardLedState(ledState);
}
if (resp.method === "keysDownState") {
const downState = resp.params as KeysDownState;
- console.log("Setting key down state", downState);
- setKeysDownState(downState);
+
+ if (downState) {
+ console.debug("Setting key down state:", downState);
+ setKeysDownState(downState);
+ setKeyPressAvailable(true); // if they returned a keyDownState, we know they also support keyPressReport
+ }
}
if (resp.method === "otaState") {
@@ -681,8 +685,12 @@ export default function KvmIdRoute() {
console.error("Failed to get keyboard led state", resp.error);
return;
}
- console.log("Keyboard led state", resp.result);
- setKeyboardLedState(resp.result as KeyboardLedState);
+ const ledState = resp.result as KeyboardLedState;
+
+ if (ledState) {
+ console.debug("Keyboard led state: ", resp.result);
+ setKeyboardLedState(resp.result as KeyboardLedState);
+ }
setNeedLedState(false);
});
}, [rpcDataChannel?.readyState, send, setKeyboardLedState, keyboardLedState, needLedState]);
@@ -705,10 +713,15 @@ export default function KvmIdRoute() {
} else {
console.error("Failed to get key down state", resp.error);
}
- return;
+ } else {
+ const downState = resp.result as KeysDownState;
+
+ if (downState) {
+ console.debug("Keyboard key down state", downState);
+ setKeysDownState(downState);
+ setKeyPressAvailable(true); // if they returned a keyDownState, we know they also support keyPressReport
+ }
}
- console.log("Keyboard key down state", resp.result);
- setKeysDownState(resp.result as KeysDownState);
setNeedKeyDownState(false);
});
}, [keysDownState, needKeyDownState, rpcDataChannel?.readyState, send, setKeyPressAvailable, setKeysDownState]);
@@ -725,7 +738,7 @@ export default function KvmIdRoute() {
useEffect(() => {
if (!diskChannel || !file) return;
diskChannel.onmessage = async e => {
- console.log("Received", e.data);
+ console.debug("Received", e.data);
const data = JSON.parse(e.data);
const blob = file.slice(data.start, data.end);
const buf = await blob.arrayBuffer();