This commit is contained in:
jackislanding 2025-03-10 11:16:20 +01:00 committed by GitHub
commit 5b45f6b1e4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 131 additions and 3 deletions

View File

@ -12,6 +12,11 @@ type WakeOnLanDevice struct {
MacAddress string `json:"macAddress"` MacAddress string `json:"macAddress"`
} }
type NameConfig struct {
Name string `json:"name"`
DNS string `json:"dns"`
}
type UsbConfig struct { type UsbConfig struct {
VendorId string `json:"vendor_id"` VendorId string `json:"vendor_id"`
ProductId string `json:"product_id"` ProductId string `json:"product_id"`
@ -34,6 +39,7 @@ type Config struct {
WakeOnLanDevices []WakeOnLanDevice `json:"wake_on_lan_devices"` WakeOnLanDevices []WakeOnLanDevice `json:"wake_on_lan_devices"`
EdidString string `json:"hdmi_edid_string"` EdidString string `json:"hdmi_edid_string"`
ActiveExtension string `json:"active_extension"` ActiveExtension string `json:"active_extension"`
NameConfig NameConfig `json:"name_config"`
DisplayMaxBrightness int `json:"display_max_brightness"` DisplayMaxBrightness int `json:"display_max_brightness"`
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"`
@ -57,6 +63,10 @@ var defaultConfig = &Config{
Manufacturer: "JetKVM", Manufacturer: "JetKVM",
Product: "USB Emulation Device", Product: "USB Emulation Device",
}, },
NameConfig: NameConfig{
Name: "JetKVM",
DNS: "jetkvm.local",
},
} }
var ( var (

2
go.mod
View File

@ -12,6 +12,7 @@ require (
github.com/creack/pty v1.1.23 github.com/creack/pty v1.1.23
github.com/gin-gonic/gin v1.9.1 github.com/gin-gonic/gin v1.9.1
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/gosimple/slug v1.15.0
github.com/gwatts/rootcerts v0.0.0-20240401182218-3ab9db955caf github.com/gwatts/rootcerts v0.0.0-20240401182218-3ab9db955caf
github.com/hanwen/go-fuse/v2 v2.5.1 github.com/hanwen/go-fuse/v2 v2.5.1
github.com/hashicorp/go-envparse v0.1.0 github.com/hashicorp/go-envparse v0.1.0
@ -46,6 +47,7 @@ require (
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-json v0.10.2 // indirect
github.com/gosimple/unidecode v1.0.1 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/compress v1.17.11 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect

4
go.sum
View File

@ -50,6 +50,10 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gosimple/slug v1.15.0 h1:wRZHsRrRcs6b0XnxMUBM6WK1U1Vg5B0R7VkIf1Xzobo=
github.com/gosimple/slug v1.15.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ=
github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o=
github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc=
github.com/gwatts/rootcerts v0.0.0-20240401182218-3ab9db955caf h1:JO6ISZIvEUitto5zjQ3/VEnDM5rPbqIFuOhS0U0ByeA= github.com/gwatts/rootcerts v0.0.0-20240401182218-3ab9db955caf h1:JO6ISZIvEUitto5zjQ3/VEnDM5rPbqIFuOhS0U0ByeA=
github.com/gwatts/rootcerts v0.0.0-20240401182218-3ab9db955caf/go.mod h1:5Kt9XkWvkGi2OHOq0QsGxebHmhCcqJ8KCbNg/a6+n+g= github.com/gwatts/rootcerts v0.0.0-20240401182218-3ab9db955caf/go.mod h1:5Kt9XkWvkGi2OHOq0QsGxebHmhCcqJ8KCbNg/a6+n+g=
github.com/hanwen/go-fuse/v2 v2.5.1 h1:OQBE8zVemSocRxA4OaFJbjJ5hlpCmIWbGr7r0M4uoQQ= github.com/hanwen/go-fuse/v2 v2.5.1 h1:OQBE8zVemSocRxA4OaFJbjJ5hlpCmIWbGr7r0M4uoQQ=

View File

@ -13,6 +13,7 @@ import (
"strconv" "strconv"
"time" "time"
"github.com/gosimple/slug"
"github.com/pion/webrtc/v4" "github.com/pion/webrtc/v4"
"go.bug.st/serial" "go.bug.st/serial"
) )
@ -43,6 +44,11 @@ type BacklightSettings struct {
OffAfter int `json:"off_after"` OffAfter int `json:"off_after"`
} }
type NameSettings struct {
Name string `json:"name"`
DNS string `json:"dns"`
}
func writeJSONRPCResponse(response JSONRPCResponse, session *Session) { func writeJSONRPCResponse(response JSONRPCResponse, session *Session) {
responseBytes, err := json.Marshal(response) responseBytes, err := json.Marshal(response)
if err != nil { if err != nil {
@ -293,6 +299,28 @@ type SSHKeyState struct {
SSHKey string `json:"sshKey"` SSHKey string `json:"sshKey"`
} }
func rpcGetNameConfig() (NameConfig, error) {
return config.NameConfig, nil
}
func rpcSetNameConfig(deviceName string) (NameConfig, error) {
config.NameConfig = NameConfig{
Name: deviceName,
DNS: slug.Make(deviceName) + ".local",
}
RestartMDNS()
err := SaveConfig()
if err != nil {
return NameConfig{}, fmt.Errorf("failed to save device name: %w", err)
}
nameConfig := config.NameConfig
log.Printf("[jsonrpc.go:rpcSetNameConfig] device name set to %s, dns name set to %s", nameConfig.Name, nameConfig.DNS)
return nameConfig, nil
}
func rpcGetDevModeState() (DevModeState, error) { func rpcGetDevModeState() (DevModeState, error) {
devModeEnabled := false devModeEnabled := false
if _, err := os.Stat(devModeFile); err != nil { if _, err := os.Stat(devModeFile); err != nil {
@ -798,6 +826,8 @@ var rpcHandlers = map[string]RPCHandler{
"setDevChannelState": {Func: rpcSetDevChannelState, Params: []string{"enabled"}}, "setDevChannelState": {Func: rpcSetDevChannelState, Params: []string{"enabled"}},
"getUpdateStatus": {Func: rpcGetUpdateStatus}, "getUpdateStatus": {Func: rpcGetUpdateStatus},
"tryUpdate": {Func: rpcTryUpdate}, "tryUpdate": {Func: rpcTryUpdate},
"setNameConfig": {Func: rpcSetNameConfig, Params: []string{"deviceName"}},
"getNameConfig": {Func: rpcGetNameConfig},
"getDevModeState": {Func: rpcGetDevModeState}, "getDevModeState": {Func: rpcGetDevModeState},
"setDevModeState": {Func: rpcSetDevModeState, Params: []string{"enabled"}}, "setDevModeState": {Func: rpcSetDevModeState, Params: []string{"enabled"}},
"getSSHKeyState": {Func: rpcGetSSHKeyState}, "getSSHKeyState": {Func: rpcGetSSHKeyState},

View File

@ -113,6 +113,13 @@ func checkNetworkState() {
} }
} }
func RestartMDNS() {
err := startMDNS()
if err != nil {
return
}
}
func startMDNS() error { func startMDNS() error {
// If server was previously running, stop it // If server was previously running, stop it
if mDNSConn != nil { if mDNSConn != nil {
@ -124,7 +131,7 @@ func startMDNS() error {
} }
// Start a new server // Start a new server
fmt.Printf("Starting mDNS server on jetkvm.local\n") fmt.Printf("Starting mDNS server on %v\n", config.NameConfig.DNS)
addr4, err := net.ResolveUDPAddr("udp4", mdns.DefaultAddressIPv4) addr4, err := net.ResolveUDPAddr("udp4", mdns.DefaultAddressIPv4)
if err != nil { if err != nil {
return err return err
@ -146,7 +153,7 @@ func startMDNS() error {
} }
mDNSConn, err = mdns.Server(ipv4.NewPacketConn(l4), ipv6.NewPacketConn(l6), &mdns.Config{ mDNSConn, err = mdns.Server(ipv4.NewPacketConn(l4), ipv6.NewPacketConn(l6), &mdns.Config{
LocalNames: []string{"jetkvm.local"}, //TODO: make it configurable LocalNames: []string{config.NameConfig.DNS},
}) })
if err != nil { if err != nil {
mDNSConn = nil mDNSConn = nil

View File

@ -23,6 +23,11 @@ const appendStatToMap = <T extends { timestamp: number }>(
export type AvailableSidebarViews = "connection-stats"; export type AvailableSidebarViews = "connection-stats";
export type AvailableTerminalTypes = "kvm" | "serial" | "none"; export type AvailableTerminalTypes = "kvm" | "serial" | "none";
export interface NameConfig {
name: string;
dns: string;
}
export interface User { export interface User {
sub: string; sub: string;
email?: string; email?: string;
@ -273,6 +278,9 @@ interface SettingsState {
backlightSettings: BacklightSettings; backlightSettings: BacklightSettings;
setBacklightSettings: (settings: BacklightSettings) => void; setBacklightSettings: (settings: BacklightSettings) => void;
nameConfig: NameConfig;
setNameConfig: (config: NameConfig) => void;
} }
export const useSettingsStore = create( export const useSettingsStore = create(
@ -298,6 +306,12 @@ export const useSettingsStore = create(
}, },
setBacklightSettings: (settings: BacklightSettings) => setBacklightSettings: (settings: BacklightSettings) =>
set({ backlightSettings: settings }), set({ backlightSettings: settings }),
nameConfig: {
name: "JetKVM",
dns: "jetkvm.local"
},
setNameConfig: (config: NameConfig) => set({ nameConfig: config }),
}), }),
{ {
name: "settings", name: "settings",

View File

@ -1,12 +1,22 @@
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { Button } from "@components/Button";
import { InputFieldWithLabel } from "@components/InputField";
import { SettingsPageHeader } from "../components/SettingsPageheader"; import { SettingsPageHeader } from "../components/SettingsPageheader";
import { SelectMenuBasic } from "../components/SelectMenuBasic"; import { SelectMenuBasic } from "../components/SelectMenuBasic";
import { SettingsItem } from "./devices.$id.settings"; import { SettingsItem } from "./devices.$id.settings";
import {NameConfig, useSettingsStore} from "@/hooks/stores";
import { useJsonRpc } from "@/hooks/useJsonRpc";
import notifications from "@/notifications";
export default function SettingsAppearanceRoute() { export default function SettingsAppearanceRoute() {
const [currentTheme, setCurrentTheme] = useState(() => { const [currentTheme, setCurrentTheme] = useState(() => {
return localStorage.theme || "system"; return localStorage.theme || "system";
}); });
const [send] = useJsonRpc();
const [name, setName] = useState("");
const nameConfigSettings = useSettingsStore(state => state.nameConfig);
const setNameConfigSettings = useSettingsStore(state => state.setNameConfig);
const handleThemeChange = useCallback((value: string) => { const handleThemeChange = useCallback((value: string) => {
const root = document.documentElement; const root = document.documentElement;
@ -26,6 +36,25 @@ export default function SettingsAppearanceRoute() {
} }
}, []); }, []);
const handleNameChange = (value: string) => {
setName(value);
};
const handleNameSave = useCallback(() => {
send("setNameConfig", { deviceName: name }, resp => {
if ("error" in resp) {
notifications.error(`Failed to set name config: ${resp.error.data || "Unknown error"}`);
return;
}
const nameConfig = resp.result as NameConfig;
setNameConfigSettings(nameConfig);
document.title = nameConfig.name;
notifications.success(
`Device name set to "${nameConfig.name}" successfully.\nDNS Name set to "${nameConfig.dns}"`
);
});
}, [send, name, setNameConfigSettings]);
return ( return (
<div className="space-y-4"> <div className="space-y-4">
<SettingsPageHeader <SettingsPageHeader
@ -48,6 +77,24 @@ export default function SettingsAppearanceRoute() {
}} }}
/> />
</SettingsItem> </SettingsItem>
<SettingsItem title="Device Name" description="Set your device name">
<InputFieldWithLabel
required
label=""
placeholder="Enter Device Name"
description={`DNS: ${nameConfigSettings.dns}`}
defaultValue={nameConfigSettings.name}
onChange={e => handleNameChange(e.target.value)}
/>
</SettingsItem>
<div className="flex items-center gap-x-2">
<Button
size="SM"
theme="primary"
text="Update Device Name"
onClick={() => {handleNameSave()}}
/>
</div>
</div> </div>
); );
} }

View File

@ -3,6 +3,7 @@ import { cx } from "@/cva.config";
import { import {
DeviceSettingsState, DeviceSettingsState,
HidState, HidState,
NameConfig,
UpdateState, UpdateState,
useDeviceSettingsStore, useDeviceSettingsStore,
useDeviceStore, useDeviceStore,
@ -10,6 +11,7 @@ import {
useMountMediaStore, useMountMediaStore,
User, User,
useRTCStore, useRTCStore,
useSettingsStore,
useUiStore, useUiStore,
useUpdateStore, useUpdateStore,
useVideoStore, useVideoStore,
@ -131,6 +133,7 @@ export default function KvmIdRoute() {
const setRpcDataChannel = useRTCStore(state => state.setRpcDataChannel); const setRpcDataChannel = useRTCStore(state => state.setRpcDataChannel);
const setTransceiver = useRTCStore(state => state.setTransceiver); const setTransceiver = useRTCStore(state => state.setTransceiver);
const navigate = useNavigate(); const navigate = useNavigate();
const { otaState, setOtaState, setModalView } = useUpdateStore(); const { otaState, setOtaState, setModalView } = useUpdateStore();
@ -380,6 +383,17 @@ export default function KvmIdRoute() {
}); });
}, [rpcDataChannel?.readyState, send, setHdmiState]); }, [rpcDataChannel?.readyState, send, setHdmiState]);
const setNameConfig = useSettingsStore(state => state.setNameConfig);
useEffect(() => {
send("getNameConfig", {}, resp => {
if ("error" in resp) return;
const results = resp.result as NameConfig;
setNameConfig(results)
document.title = results.name;
});
}, [send, setNameConfig])
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error // @ts-expect-error
window.send = send; window.send = send;