mirror of https://github.com/jetkvm/kvm.git
Compare commits
5 Commits
a1e9b85eb1
...
c2b4c5b243
Author | SHA1 | Date |
---|---|---|
|
c2b4c5b243 | |
|
54af0ab0fb | |
|
4ab11a6af1 | |
|
7e64a529f8 | |
|
1b5062c504 |
|
@ -9,6 +9,19 @@
|
||||||
},
|
},
|
||||||
"mounts": [
|
"mounts": [
|
||||||
"source=${localEnv:HOME}/.ssh,target=/home/vscode/.ssh,type=bind,consistency=cached"
|
"source=${localEnv:HOME}/.ssh,target=/home/vscode/.ssh,type=bind,consistency=cached"
|
||||||
|
],
|
||||||
|
"customizations": {
|
||||||
|
"vscode": {
|
||||||
|
"extensions": [
|
||||||
|
"bradlc.vscode-tailwindcss",
|
||||||
|
"GitHub.vscode-pull-request-github",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"golang.go",
|
||||||
|
"ms-vscode.makefile-tools",
|
||||||
|
"esbenp.prettier-vscode",
|
||||||
|
"github.vscode-github-actions"
|
||||||
]
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"tailwindCSS.classFunctions": ["cva", "cx"]
|
||||||
|
}
|
|
@ -85,6 +85,7 @@ type Config struct {
|
||||||
HashedPassword string `json:"hashed_password"`
|
HashedPassword string `json:"hashed_password"`
|
||||||
LocalAuthToken string `json:"local_auth_token"`
|
LocalAuthToken string `json:"local_auth_token"`
|
||||||
LocalAuthMode string `json:"localAuthMode"` //TODO: fix it with migration
|
LocalAuthMode string `json:"localAuthMode"` //TODO: fix it with migration
|
||||||
|
LocalLoopbackOnly bool `json:"local_loopback_only"`
|
||||||
WakeOnLanDevices []WakeOnLanDevice `json:"wake_on_lan_devices"`
|
WakeOnLanDevices []WakeOnLanDevice `json:"wake_on_lan_devices"`
|
||||||
KeyboardMacros []KeyboardMacro `json:"keyboard_macros"`
|
KeyboardMacros []KeyboardMacro `json:"keyboard_macros"`
|
||||||
KeyboardLayout string `json:"keyboard_layout"`
|
KeyboardLayout string `json:"keyboard_layout"`
|
||||||
|
@ -95,7 +96,6 @@ type Config struct {
|
||||||
DisplayDimAfterSec int `json:"display_dim_after_sec"`
|
DisplayDimAfterSec int `json:"display_dim_after_sec"`
|
||||||
DisplayOffAfterSec int `json:"display_off_after_sec"`
|
DisplayOffAfterSec int `json:"display_off_after_sec"`
|
||||||
TLSMode string `json:"tls_mode"` // options: "self-signed", "user-defined", ""
|
TLSMode string `json:"tls_mode"` // options: "self-signed", "user-defined", ""
|
||||||
LocalWebServerLoopbackOnly bool `json:"local_web_server_loopback_only"`
|
|
||||||
UsbConfig *usbgadget.Config `json:"usb_config"`
|
UsbConfig *usbgadget.Config `json:"usb_config"`
|
||||||
UsbDevices *usbgadget.Devices `json:"usb_devices"`
|
UsbDevices *usbgadget.Devices `json:"usb_devices"`
|
||||||
NetworkConfig *network.NetworkConfig `json:"network_config"`
|
NetworkConfig *network.NetworkConfig `json:"network_config"`
|
||||||
|
@ -116,7 +116,6 @@ var defaultConfig = &Config{
|
||||||
DisplayDimAfterSec: 120, // 2 minutes
|
DisplayDimAfterSec: 120, // 2 minutes
|
||||||
DisplayOffAfterSec: 1800, // 30 minutes
|
DisplayOffAfterSec: 1800, // 30 minutes
|
||||||
TLSMode: "",
|
TLSMode: "",
|
||||||
LocalWebServerLoopbackOnly: false, // Allow access from any network interface by default
|
|
||||||
UsbConfig: &usbgadget.Config{
|
UsbConfig: &usbgadget.Config{
|
||||||
VendorId: "0x1d6b", //The Linux Foundation
|
VendorId: "0x1d6b", //The Linux Foundation
|
||||||
ProductId: "0x0104", //Multifunction Composite Gadget
|
ProductId: "0x0104", //Multifunction Composite Gadget
|
||||||
|
|
14
jsonrpc.go
14
jsonrpc.go
|
@ -1006,18 +1006,18 @@ func setKeyboardMacros(params KeyboardMacrosParams) (interface{}, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func rpcGetLocalWebServerLoopbackOnly() (bool, error) {
|
func rpcGetLocalLoopbackOnly() (bool, error) {
|
||||||
return config.LocalWebServerLoopbackOnly, nil
|
return config.LocalLoopbackOnly, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func rpcSetLocalWebServerLoopbackOnly(enabled bool) error {
|
func rpcSetLocalLoopbackOnly(enabled bool) error {
|
||||||
// Check if the setting is actually changing
|
// Check if the setting is actually changing
|
||||||
if config.LocalWebServerLoopbackOnly == enabled {
|
if config.LocalLoopbackOnly == enabled {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the setting
|
// Update the setting
|
||||||
config.LocalWebServerLoopbackOnly = enabled
|
config.LocalLoopbackOnly = enabled
|
||||||
if err := SaveConfig(); err != nil {
|
if err := SaveConfig(); err != nil {
|
||||||
return fmt.Errorf("failed to save config: %w", err)
|
return fmt.Errorf("failed to save config: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -1102,6 +1102,6 @@ var rpcHandlers = map[string]RPCHandler{
|
||||||
"setKeyboardLayout": {Func: rpcSetKeyboardLayout, Params: []string{"layout"}},
|
"setKeyboardLayout": {Func: rpcSetKeyboardLayout, Params: []string{"layout"}},
|
||||||
"getKeyboardMacros": {Func: getKeyboardMacros},
|
"getKeyboardMacros": {Func: getKeyboardMacros},
|
||||||
"setKeyboardMacros": {Func: setKeyboardMacros, Params: []string{"params"}},
|
"setKeyboardMacros": {Func: setKeyboardMacros, Params: []string{"params"}},
|
||||||
"getLocalWebServerLoopbackOnly": {Func: rpcGetLocalWebServerLoopbackOnly},
|
"getLocalLoopbackOnly": {Func: rpcGetLocalLoopbackOnly},
|
||||||
"setLocalWebServerLoopbackOnly": {Func: rpcSetLocalWebServerLoopbackOnly, Params: []string{"enabled"}},
|
"setLocalLoopbackOnly": {Func: rpcSetLocalLoopbackOnly, Params: []string{"enabled"}},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { LuCornerDownLeft } from "react-icons/lu";
|
import { LuCornerDownLeft } from "react-icons/lu";
|
||||||
import { ExclamationCircleIcon } from "@heroicons/react/16/solid";
|
import { ExclamationCircleIcon } from "@heroicons/react/16/solid";
|
||||||
import { useClose } from "@headlessui/react";
|
import { useClose } from "@headlessui/react";
|
||||||
|
@ -39,6 +39,13 @@ export default function PasteModal() {
|
||||||
state => state.setKeyboardLayout,
|
state => state.setKeyboardLayout,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// this ensures we always get the original en-US if it hasn't been set yet
|
||||||
|
const safeKeyboardLayout = useMemo(() => {
|
||||||
|
if (keyboardLayout && keyboardLayout.length > 0)
|
||||||
|
return keyboardLayout;
|
||||||
|
return "en-US";
|
||||||
|
}, [keyboardLayout]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
send("getKeyboardLayout", {}, resp => {
|
send("getKeyboardLayout", {}, resp => {
|
||||||
if ("error" in resp) return;
|
if ("error" in resp) return;
|
||||||
|
@ -56,14 +63,13 @@ export default function PasteModal() {
|
||||||
setPasteMode(false);
|
setPasteMode(false);
|
||||||
setDisableVideoFocusTrap(false);
|
setDisableVideoFocusTrap(false);
|
||||||
if (rpcDataChannel?.readyState !== "open" || !TextAreaRef.current) return;
|
if (rpcDataChannel?.readyState !== "open" || !TextAreaRef.current) return;
|
||||||
if (!keyboardLayout) return;
|
if (!safeKeyboardLayout) return;
|
||||||
if (!chars[keyboardLayout]) return;
|
if (!chars[safeKeyboardLayout]) return;
|
||||||
|
|
||||||
const text = TextAreaRef.current.value;
|
const text = TextAreaRef.current.value;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (const char of text) {
|
for (const char of text) {
|
||||||
const { key, shift, altRight, deadKey, accentKey } = chars[keyboardLayout][char]
|
const { key, shift, altRight, deadKey, accentKey } = chars[safeKeyboardLayout][char]
|
||||||
if (!key) continue;
|
if (!key) continue;
|
||||||
|
|
||||||
const keyz = [ keys[key] ];
|
const keyz = [ keys[key] ];
|
||||||
|
@ -98,7 +104,7 @@ export default function PasteModal() {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
notifications.error("Failed to paste text");
|
notifications.error("Failed to paste text");
|
||||||
}
|
}
|
||||||
}, [rpcDataChannel?.readyState, send, setDisableVideoFocusTrap, setPasteMode, keyboardLayout]);
|
}, [rpcDataChannel?.readyState, send, setDisableVideoFocusTrap, setPasteMode, safeKeyboardLayout]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (TextAreaRef.current) {
|
if (TextAreaRef.current) {
|
||||||
|
@ -148,7 +154,7 @@ export default function PasteModal() {
|
||||||
// @ts-expect-error TS doesn't recognize Intl.Segmenter in some environments
|
// @ts-expect-error TS doesn't recognize Intl.Segmenter in some environments
|
||||||
[...new Intl.Segmenter().segment(value)]
|
[...new Intl.Segmenter().segment(value)]
|
||||||
.map(x => x.segment)
|
.map(x => x.segment)
|
||||||
.filter(char => !chars[keyboardLayout][char]),
|
.filter(char => !chars[safeKeyboardLayout][char]),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -169,7 +175,7 @@ export default function PasteModal() {
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<p className="text-xs text-slate-600 dark:text-slate-400">
|
<p className="text-xs text-slate-600 dark:text-slate-400">
|
||||||
Sending text using keyboard layout: {layouts[keyboardLayout]}
|
Sending text using keyboard layout: {layouts[safeKeyboardLayout]}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { useCallback, useEffect } from "react";
|
import { useCallback, useEffect, useMemo } from "react";
|
||||||
|
|
||||||
import { KeyboardLedSync, useSettingsStore } from "@/hooks/stores";
|
import { KeyboardLedSync, useSettingsStore } from "@/hooks/stores";
|
||||||
import { useJsonRpc } from "@/hooks/useJsonRpc";
|
import { useJsonRpc } from "@/hooks/useJsonRpc";
|
||||||
|
@ -20,6 +20,13 @@ export default function SettingsKeyboardRoute() {
|
||||||
state => state.setKeyboardLedSync,
|
state => state.setKeyboardLedSync,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// this ensures we always get the original en-US if it hasn't been set yet
|
||||||
|
const safeKeyboardLayout = useMemo(() => {
|
||||||
|
if (keyboardLayout && keyboardLayout.length > 0)
|
||||||
|
return keyboardLayout;
|
||||||
|
return "en-US";
|
||||||
|
}, [keyboardLayout]);
|
||||||
|
|
||||||
const layoutOptions = Object.entries(layouts).map(([code, language]) => { return { value: code, label: language } })
|
const layoutOptions = Object.entries(layouts).map(([code, language]) => { return { value: code, label: language } })
|
||||||
const ledSyncOptions = [
|
const ledSyncOptions = [
|
||||||
{ value: "auto", label: "Automatic" },
|
{ value: "auto", label: "Automatic" },
|
||||||
|
@ -69,7 +76,7 @@ export default function SettingsKeyboardRoute() {
|
||||||
size="SM"
|
size="SM"
|
||||||
label=""
|
label=""
|
||||||
fullWidth
|
fullWidth
|
||||||
value={keyboardLayout}
|
value={safeKeyboardLayout}
|
||||||
onChange={onKeyboardLayoutChange}
|
onChange={onKeyboardLayoutChange}
|
||||||
options={layoutOptions}
|
options={layoutOptions}
|
||||||
/>
|
/>
|
||||||
|
|
8
web.go
8
web.go
|
@ -54,7 +54,7 @@ type ChangePasswordRequest struct {
|
||||||
type LocalDevice struct {
|
type LocalDevice struct {
|
||||||
AuthMode *string `json:"authMode"`
|
AuthMode *string `json:"authMode"`
|
||||||
DeviceID string `json:"deviceId"`
|
DeviceID string `json:"deviceId"`
|
||||||
LocalWebServerLoopbackOnly bool `json:"localWebServerLoopbackOnly"`
|
LoopbackOnly bool `json:"loopbackOnly"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DeviceStatus struct {
|
type DeviceStatus struct {
|
||||||
|
@ -536,11 +536,11 @@ func RunWebServer() {
|
||||||
|
|
||||||
// Determine the binding address based on the config
|
// Determine the binding address based on the config
|
||||||
bindAddress := ":80" // Default to all interfaces
|
bindAddress := ":80" // Default to all interfaces
|
||||||
if config.LocalWebServerLoopbackOnly {
|
if config.LocalLoopbackOnly {
|
||||||
bindAddress = "localhost:80" // Loopback only (both IPv4 and IPv6)
|
bindAddress = "localhost:80" // Loopback only (both IPv4 and IPv6)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info().Str("bindAddress", bindAddress).Bool("loopbackOnly", config.LocalWebServerLoopbackOnly).Msg("Starting web server")
|
logger.Info().Str("bindAddress", bindAddress).Bool("loopbackOnly", config.LocalLoopbackOnly).Msg("Starting web server")
|
||||||
err := r.Run(bindAddress)
|
err := r.Run(bindAddress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -551,7 +551,7 @@ func handleDevice(c *gin.Context) {
|
||||||
response := LocalDevice{
|
response := LocalDevice{
|
||||||
AuthMode: &config.LocalAuthMode,
|
AuthMode: &config.LocalAuthMode,
|
||||||
DeviceID: GetDeviceID(),
|
DeviceID: GetDeviceID(),
|
||||||
LocalWebServerLoopbackOnly: config.LocalWebServerLoopbackOnly,
|
LoopbackOnly: config.LocalLoopbackOnly,
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, response)
|
c.JSON(http.StatusOK, response)
|
||||||
|
|
Loading…
Reference in New Issue