mirror of https://github.com/jetkvm/kvm.git
feat: add network settings tab
This commit is contained in:
parent
eb8bac4013
commit
73b83557af
5
cloud.go
5
cloud.go
|
@ -165,9 +165,12 @@ func setCloudConnectionState(state CloudConnectionState) {
|
|||
state = CloudConnectionStateNotConfigured
|
||||
}
|
||||
|
||||
previousState := cloudConnectionState
|
||||
cloudConnectionState = state
|
||||
|
||||
go waitCtrlAndRequestDisplayUpdate()
|
||||
go waitCtrlAndRequestDisplayUpdate(
|
||||
previousState != state,
|
||||
)
|
||||
}
|
||||
|
||||
func wsResetMetrics(established bool, sourceType string, source string) {
|
||||
|
|
12
display.go
12
display.go
|
@ -179,7 +179,7 @@ var (
|
|||
waitDisplayUpdate = sync.Mutex{}
|
||||
)
|
||||
|
||||
func requestDisplayUpdate() {
|
||||
func requestDisplayUpdate(shouldWakeDisplay bool) {
|
||||
displayUpdateLock.Lock()
|
||||
defer displayUpdateLock.Unlock()
|
||||
|
||||
|
@ -188,19 +188,21 @@ func requestDisplayUpdate() {
|
|||
return
|
||||
}
|
||||
go func() {
|
||||
wakeDisplay(false)
|
||||
if shouldWakeDisplay {
|
||||
wakeDisplay(false)
|
||||
}
|
||||
displayLogger.Debug().Msg("display updating")
|
||||
//TODO: only run once regardless how many pending updates
|
||||
updateDisplay()
|
||||
}()
|
||||
}
|
||||
|
||||
func waitCtrlAndRequestDisplayUpdate() {
|
||||
func waitCtrlAndRequestDisplayUpdate(shouldWakeDisplay bool) {
|
||||
waitDisplayUpdate.Lock()
|
||||
defer waitDisplayUpdate.Unlock()
|
||||
|
||||
waitCtrlClientConnected()
|
||||
requestDisplayUpdate()
|
||||
requestDisplayUpdate(shouldWakeDisplay)
|
||||
}
|
||||
|
||||
func updateStaticContents() {
|
||||
|
@ -376,7 +378,7 @@ func init() {
|
|||
displayLogger.Info().Msg("display inited")
|
||||
startBacklightTickers()
|
||||
wakeDisplay(true)
|
||||
requestDisplayUpdate()
|
||||
requestDisplayUpdate(true)
|
||||
}()
|
||||
|
||||
go watchTsEvents()
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package udhcpc
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -41,6 +43,8 @@ type Lease struct {
|
|||
Message string `env:"message" json:"reason,omitempty"` // Reason for a DHCPNAK
|
||||
TFTPServerName string `env:"tftp" json:"tftp,omitempty"` // The TFTP server name
|
||||
BootFileName string `env:"bootfile" json:"bootfile,omitempty"` // The boot file name
|
||||
Uptime time.Duration `env:"uptime" json:"uptime,omitempty"` // The uptime of the device when the lease was obtained, in seconds
|
||||
LeaseExpiry *time.Time `json:"lease_expiry,omitempty"` // The expiry time of the lease
|
||||
isEmpty map[string]bool
|
||||
}
|
||||
|
||||
|
@ -60,6 +64,40 @@ func (l *Lease) ToJSON() string {
|
|||
return string(json)
|
||||
}
|
||||
|
||||
func (l *Lease) SetLeaseExpiry() (time.Time, error) {
|
||||
if l.Uptime == 0 || l.LeaseTime == 0 {
|
||||
return time.Time{}, fmt.Errorf("uptime or lease time isn't set")
|
||||
}
|
||||
|
||||
// get the uptime of the device
|
||||
|
||||
file, err := os.Open("/proc/uptime")
|
||||
if err != nil {
|
||||
return time.Time{}, fmt.Errorf("failed to open uptime file: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var uptime time.Duration
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
text := scanner.Text()
|
||||
parts := strings.Split(text, " ")
|
||||
uptime, err = time.ParseDuration(parts[0] + "s")
|
||||
|
||||
if err != nil {
|
||||
return time.Time{}, fmt.Errorf("failed to parse uptime: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
relativeLeaseRemaining := (l.Uptime + l.LeaseTime) - uptime
|
||||
leaseExpiry := time.Now().Add(relativeLeaseRemaining)
|
||||
|
||||
l.LeaseExpiry = &leaseExpiry
|
||||
|
||||
return leaseExpiry, nil
|
||||
}
|
||||
|
||||
func UnmarshalDHCPCLease(lease *Lease, str string) error {
|
||||
// parse the lease file as a map
|
||||
data := make(map[string]string)
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/rs/zerolog"
|
||||
|
@ -140,6 +141,17 @@ func (c *DHCPClient) loadLeaseFile() error {
|
|||
msg = "udhcpc lease loaded"
|
||||
}
|
||||
|
||||
leaseExpiry, err := lease.SetLeaseExpiry()
|
||||
if err != nil {
|
||||
c.logger.Error().Err(err).Msg("failed to get dhcp lease expiry")
|
||||
} else {
|
||||
expiresIn := leaseExpiry.Sub(time.Now())
|
||||
c.logger.Info().
|
||||
Interface("expiry", leaseExpiry).
|
||||
Str("expiresIn", expiresIn.String()).
|
||||
Msg("current dhcp lease expiry time calculated")
|
||||
}
|
||||
|
||||
c.onLeaseChange(lease)
|
||||
|
||||
c.logger.Info().
|
||||
|
|
|
@ -961,6 +961,7 @@ var rpcHandlers = map[string]RPCHandler{
|
|||
"deregisterDevice": {Func: rpcDeregisterDevice},
|
||||
"getCloudState": {Func: rpcGetCloudState},
|
||||
"getNetworkState": {Func: rpcGetNetworkState},
|
||||
"getNetworkSettings": {Func: rpcGetNetworkSettings},
|
||||
"renewDHCPLease": {Func: rpcRenewDHCPLease},
|
||||
"keyboardReport": {Func: rpcKeyboardReport, Params: []string{"modifier", "keys"}},
|
||||
"absMouseReport": {Func: rpcAbsMouseReport, Params: []string{"x", "y", "buttons"}},
|
||||
|
|
159
network.go
159
network.go
|
@ -29,11 +29,22 @@ const (
|
|||
DhcpTargetStateRelease
|
||||
)
|
||||
|
||||
type IPv6Address struct {
|
||||
Address net.IP `json:"address"`
|
||||
Prefix net.IPNet `json:"prefix"`
|
||||
ValidLifetime *time.Time `json:"valid_lifetime"`
|
||||
PreferredLifetime *time.Time `json:"preferred_lifetime"`
|
||||
Scope int `json:"scope"`
|
||||
}
|
||||
|
||||
type NetworkInterfaceState struct {
|
||||
interfaceName string
|
||||
interfaceUp bool
|
||||
ipv4Addr *net.IP
|
||||
ipv4Addresses []string
|
||||
ipv6Addr *net.IP
|
||||
ipv6Addresses []IPv6Address
|
||||
ipv6LinkLocal *net.IP
|
||||
macAddr *net.HardwareAddr
|
||||
|
||||
l *zerolog.Logger
|
||||
|
@ -47,12 +58,65 @@ type NetworkInterfaceState struct {
|
|||
checked bool
|
||||
}
|
||||
|
||||
type NetworkConfig struct {
|
||||
Hostname string `json:"hostname,omitempty"`
|
||||
Domain string `json:"domain,omitempty"`
|
||||
|
||||
IPv4Mode string `json:"ipv4_mode" one_of:"dhcp,static,disabled" default:"dhcp"`
|
||||
IPv4Static struct {
|
||||
Address string `json:"address" validate_type:"ipv4"`
|
||||
Netmask string `json:"netmask" validate_type:"ipv4"`
|
||||
Gateway string `json:"gateway" validate_type:"ipv4"`
|
||||
DNS []string `json:"dns" validate_type:"ipv4"`
|
||||
} `json:"ipv4_static,omitempty" required_if:"ipv4_mode,static"`
|
||||
|
||||
IPv6Mode string `json:"ipv6_mode" one_of:"slaac,dhcpv6,slaac_and_dhcpv6,static,link_local,disabled" default:"slaac"`
|
||||
IPv6Static struct {
|
||||
Address string `json:"address" validate_type:"ipv6"`
|
||||
Netmask string `json:"netmask" validate_type:"ipv6"`
|
||||
Gateway string `json:"gateway" validate_type:"ipv6"`
|
||||
DNS []string `json:"dns" validate_type:"ipv6"`
|
||||
} `json:"ipv6_static,omitempty" required_if:"ipv6_mode,static"`
|
||||
|
||||
LLDPMode string `json:"lldp_mode,omitempty" one_of:"disabled,basic,all" default:"basic"`
|
||||
LLDPTxTLVs []string `json:"lldp_tx_tlvs,omitempty" one_of:"chassis,port,system,vlan" default:"chassis,port,system,vlan"`
|
||||
MDNSMode string `json:"mdns_mode,omitempty" one_of:"disabled,auto,ipv4_only,ipv6_only" default:"auto"`
|
||||
TimeSyncMode string `json:"time_sync_mode,omitempty" one_of:"ntp_only,ntp_and_http,http_only,custom" default:"ntp_and_http"`
|
||||
}
|
||||
|
||||
type RpcIPv6Address struct {
|
||||
Address string `json:"address"`
|
||||
ValidLifetime *time.Time `json:"valid_lifetime,omitempty"`
|
||||
PreferredLifetime *time.Time `json:"preferred_lifetime,omitempty"`
|
||||
Scope int `json:"scope"`
|
||||
}
|
||||
|
||||
type RpcNetworkState struct {
|
||||
InterfaceName string `json:"interface_name"`
|
||||
MacAddress string `json:"mac_address"`
|
||||
IPv4 string `json:"ipv4,omitempty"`
|
||||
IPv6 string `json:"ipv6,omitempty"`
|
||||
DHCPLease *udhcpc.Lease `json:"dhcp_lease,omitempty"`
|
||||
InterfaceName string `json:"interface_name"`
|
||||
MacAddress string `json:"mac_address"`
|
||||
IPv4 string `json:"ipv4,omitempty"`
|
||||
IPv6 string `json:"ipv6,omitempty"`
|
||||
IPv6LinkLocal string `json:"ipv6_link_local,omitempty"`
|
||||
IPv4Addresses []string `json:"ipv4_addresses,omitempty"`
|
||||
IPv6Addresses []RpcIPv6Address `json:"ipv6_addresses,omitempty"`
|
||||
DHCPLease *udhcpc.Lease `json:"dhcp_lease,omitempty"`
|
||||
}
|
||||
|
||||
type RpcNetworkSettings struct {
|
||||
IPv4Mode string `json:"ipv4_mode,omitempty"`
|
||||
IPv6Mode string `json:"ipv6_mode,omitempty"`
|
||||
LLDPMode string `json:"lldp_mode,omitempty"`
|
||||
LLDPTxTLVs []string `json:"lldp_tx_tlvs,omitempty"`
|
||||
MDNSMode string `json:"mdns_mode,omitempty"`
|
||||
TimeSyncMode string `json:"time_sync_mode,omitempty"`
|
||||
}
|
||||
|
||||
func lifetimeToTime(lifetime int) *time.Time {
|
||||
if lifetime == 0 {
|
||||
return nil
|
||||
}
|
||||
t := time.Now().Add(time.Duration(lifetime) * time.Second)
|
||||
return &t
|
||||
}
|
||||
|
||||
func (s *NetworkInterfaceState) IsUp() bool {
|
||||
|
@ -115,13 +179,13 @@ func NewNetworkInterfaceState(ifname string) *NetworkInterfaceState {
|
|||
onStateChange: func(state *NetworkInterfaceState) {
|
||||
go func() {
|
||||
waitCtrlClientConnected()
|
||||
requestDisplayUpdate()
|
||||
requestDisplayUpdate(true)
|
||||
}()
|
||||
},
|
||||
onInitialCheck: func(state *NetworkInterfaceState) {
|
||||
go func() {
|
||||
waitCtrlClientConnected()
|
||||
requestDisplayUpdate()
|
||||
requestDisplayUpdate(true)
|
||||
}()
|
||||
},
|
||||
}
|
||||
|
@ -140,7 +204,18 @@ func NewNetworkInterfaceState(ifname string) *NetworkInterfaceState {
|
|||
PidFile: dhcpPidFile,
|
||||
Logger: &logger,
|
||||
OnLeaseChange: func(lease *udhcpc.Lease) {
|
||||
_, _ = s.update()
|
||||
_, err := s.update()
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("failed to update network state")
|
||||
return
|
||||
}
|
||||
|
||||
if currentSession == nil {
|
||||
logger.Info().Msg("No active RPC session, skipping network state update")
|
||||
return
|
||||
}
|
||||
|
||||
writeJSONRPCEvent("networkState", rpcGetNetworkState(), currentSession)
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -196,8 +271,11 @@ func (s *NetworkInterfaceState) update() (DhcpTargetState, error) {
|
|||
}
|
||||
|
||||
var (
|
||||
ipv4Addresses = make([]net.IP, 0)
|
||||
ipv6Addresses = make([]net.IP, 0)
|
||||
ipv4Addresses = make([]net.IP, 0)
|
||||
ipv4AddressesString = make([]string, 0)
|
||||
ipv6Addresses = make([]IPv6Address, 0)
|
||||
ipv6AddressesString = make([]string, 0)
|
||||
ipv6LinkLocal *net.IP
|
||||
)
|
||||
|
||||
for _, addr := range addrs {
|
||||
|
@ -215,9 +293,15 @@ func (s *NetworkInterfaceState) update() (DhcpTargetState, error) {
|
|||
continue
|
||||
}
|
||||
ipv4Addresses = append(ipv4Addresses, addr.IP)
|
||||
ipv4AddressesString = append(ipv4AddressesString, addr.IPNet.String())
|
||||
} else if addr.IP.To16() != nil {
|
||||
scopedLogger := s.l.With().Str("ipv6", addr.IP.String()).Logger()
|
||||
// check if it's a link local address
|
||||
if addr.IP.IsLinkLocalUnicast() {
|
||||
ipv6LinkLocal = &addr.IP
|
||||
continue
|
||||
}
|
||||
|
||||
if !addr.IP.IsGlobalUnicast() {
|
||||
scopedLogger.Trace().Msg("not a global unicast address, skipping")
|
||||
continue
|
||||
|
@ -231,7 +315,14 @@ func (s *NetworkInterfaceState) update() (DhcpTargetState, error) {
|
|||
}
|
||||
continue
|
||||
}
|
||||
ipv6Addresses = append(ipv6Addresses, addr.IP)
|
||||
ipv6Addresses = append(ipv6Addresses, IPv6Address{
|
||||
Address: addr.IP,
|
||||
Prefix: *addr.IPNet,
|
||||
ValidLifetime: lifetimeToTime(addr.ValidLft),
|
||||
PreferredLifetime: lifetimeToTime(addr.PreferedLft),
|
||||
Scope: addr.Scope,
|
||||
})
|
||||
ipv6AddressesString = append(ipv6AddressesString, addr.IPNet.String())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -250,11 +341,28 @@ func (s *NetworkInterfaceState) update() (DhcpTargetState, error) {
|
|||
changed = true
|
||||
}
|
||||
}
|
||||
s.ipv4Addresses = ipv4AddressesString
|
||||
|
||||
if ipv6LinkLocal != nil {
|
||||
if s.ipv6LinkLocal == nil || s.ipv6LinkLocal.String() != ipv6LinkLocal.String() {
|
||||
scopedLogger := s.l.With().Str("ipv6", ipv6LinkLocal.String()).Logger()
|
||||
if s.ipv6LinkLocal != nil {
|
||||
scopedLogger.Info().
|
||||
Str("old_ipv6", s.ipv6LinkLocal.String()).
|
||||
Msg("IPv6 link local address changed")
|
||||
} else {
|
||||
scopedLogger.Info().Msg("IPv6 link local address found")
|
||||
}
|
||||
s.ipv6LinkLocal = ipv6LinkLocal
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
s.ipv6Addresses = ipv6Addresses
|
||||
|
||||
if len(ipv6Addresses) > 0 {
|
||||
// compare the addresses to see if there's a change
|
||||
if s.ipv6Addr == nil || s.ipv6Addr.String() != ipv6Addresses[0].String() {
|
||||
scopedLogger := s.l.With().Str("ipv6", ipv6Addresses[0].String()).Logger()
|
||||
if s.ipv6Addr == nil || s.ipv6Addr.String() != ipv6Addresses[0].Address.String() {
|
||||
scopedLogger := s.l.With().Str("ipv6", ipv6Addresses[0].Address.String()).Logger()
|
||||
if s.ipv6Addr != nil {
|
||||
scopedLogger.Info().
|
||||
Str("old_ipv6", s.ipv6Addr.String()).
|
||||
|
@ -262,7 +370,7 @@ func (s *NetworkInterfaceState) update() (DhcpTargetState, error) {
|
|||
} else {
|
||||
scopedLogger.Info().Msg("IPv6 address found")
|
||||
}
|
||||
s.ipv6Addr = &ipv6Addresses[0]
|
||||
s.ipv6Addr = &ipv6Addresses[0].Address
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
|
@ -313,15 +421,38 @@ func (s *NetworkInterfaceState) HandleLinkUpdate(update netlink.LinkUpdate) {
|
|||
}
|
||||
|
||||
func rpcGetNetworkState() RpcNetworkState {
|
||||
ipv6Addresses := make([]RpcIPv6Address, 0)
|
||||
for _, addr := range networkState.ipv6Addresses {
|
||||
ipv6Addresses = append(ipv6Addresses, RpcIPv6Address{
|
||||
Address: addr.Prefix.String(),
|
||||
ValidLifetime: addr.ValidLifetime,
|
||||
PreferredLifetime: addr.PreferredLifetime,
|
||||
Scope: addr.Scope,
|
||||
})
|
||||
}
|
||||
return RpcNetworkState{
|
||||
InterfaceName: networkState.interfaceName,
|
||||
MacAddress: networkState.macAddr.String(),
|
||||
IPv4: networkState.ipv4Addr.String(),
|
||||
IPv6: networkState.ipv6Addr.String(),
|
||||
IPv6LinkLocal: networkState.ipv6LinkLocal.String(),
|
||||
IPv4Addresses: networkState.ipv4Addresses,
|
||||
IPv6Addresses: ipv6Addresses,
|
||||
DHCPLease: networkState.dhcpClient.GetLease(),
|
||||
}
|
||||
}
|
||||
|
||||
func rpcGetNetworkSettings() RpcNetworkSettings {
|
||||
return RpcNetworkSettings{
|
||||
IPv4Mode: "dhcp",
|
||||
IPv6Mode: "slaac",
|
||||
LLDPMode: "basic",
|
||||
LLDPTxTLVs: []string{"chassis", "port", "system", "vlan"},
|
||||
MDNSMode: "auto",
|
||||
TimeSyncMode: "ntp_and_http",
|
||||
}
|
||||
}
|
||||
|
||||
func rpcRenewDHCPLease() error {
|
||||
if networkState == nil {
|
||||
return fmt.Errorf("network state not initialized")
|
||||
|
|
|
@ -15,5 +15,15 @@ echo "└───────────────────────
|
|||
|
||||
# Set the environment variable and run Vite
|
||||
echo "Starting development server with JetKVM device at: $ip_address"
|
||||
|
||||
# Check if pwd is the current directory of the script
|
||||
if [ "$(pwd)" != "$(dirname "$0")" ]; then
|
||||
pushd "$(dirname "$0")" > /dev/null
|
||||
echo "Changed directory to: $(pwd)"
|
||||
fi
|
||||
|
||||
sleep 1
|
||||
|
||||
JETKVM_PROXY_URL="ws://$ip_address" npx vite dev --mode=device
|
||||
|
||||
popd > /dev/null
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
"@xterm/addon-webgl": "^0.18.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
"cva": "^1.0.0-beta.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"eslint-import-resolver-alias": "^1.1.2",
|
||||
"focus-trap-react": "^10.2.3",
|
||||
"framer-motion": "^11.15.0",
|
||||
|
@ -2460,6 +2461,11 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/dayjs": {
|
||||
"version": "1.11.13",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
|
||||
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
"@xterm/addon-webgl": "^0.18.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
"cva": "^1.0.0-beta.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"eslint-import-resolver-alias": "^1.1.2",
|
||||
"focus-trap-react": "^10.2.3",
|
||||
"framer-motion": "^11.15.0",
|
||||
|
|
|
@ -663,6 +663,93 @@ export const useDeviceStore = create<DeviceState>(set => ({
|
|||
setSystemVersion: version => set({ systemVersion: version }),
|
||||
}));
|
||||
|
||||
export interface DhcpLease {
|
||||
ip?: string;
|
||||
netmask?: string;
|
||||
broadcast?: string;
|
||||
ttl?: string;
|
||||
mtu?: string;
|
||||
hostname?: string;
|
||||
domain?: string;
|
||||
bootp_next_server?: string;
|
||||
bootp_server_name?: string;
|
||||
bootp_file?: string;
|
||||
timezone?: string;
|
||||
routers?: string[];
|
||||
dns?: string[];
|
||||
ntp_servers?: string[];
|
||||
lpr_servers?: string[];
|
||||
_time_servers?: string[];
|
||||
_name_servers?: string[];
|
||||
_log_servers?: string[];
|
||||
_cookie_servers?: string[];
|
||||
_wins_servers?: string[];
|
||||
_swap_server?: string;
|
||||
boot_size?: string;
|
||||
root_path?: string;
|
||||
lease?: string;
|
||||
lease_expiry?: Date;
|
||||
dhcp_type?: string;
|
||||
server_id?: string;
|
||||
message?: string;
|
||||
tftp?: string;
|
||||
bootfile?: string;
|
||||
}
|
||||
|
||||
export interface IPv6Address {
|
||||
address: string;
|
||||
prefix: string;
|
||||
valid_lifetime: string;
|
||||
preferred_lifetime: string;
|
||||
scope: string;
|
||||
}
|
||||
|
||||
export interface NetworkState {
|
||||
interface_name?: string;
|
||||
mac_address?: string;
|
||||
ipv4?: string;
|
||||
ipv4_addresses?: string[];
|
||||
ipv6?: string;
|
||||
ipv6_addresses?: IPv6Address[];
|
||||
ipv6_link_local?: string;
|
||||
dhcp_lease?: DhcpLease;
|
||||
|
||||
setNetworkState: (state: NetworkState) => void;
|
||||
setDhcpLease: (lease: NetworkState["dhcp_lease"]) => void;
|
||||
setDhcpLeaseExpiry: (expiry: Date) => void;
|
||||
}
|
||||
|
||||
|
||||
export type IPv6Mode = "disabled" | "slaac" | "dhcpv6" | "slaac_and_dhcpv6" | "static" | "link_local" | "unknown";
|
||||
export type IPv4Mode = "disabled" | "static" | "dhcp" | "unknown";
|
||||
export type LLDPMode = "disabled" | "basic" | "all" | "unknown";
|
||||
export type mDNSMode = "disabled" | "auto" | "ipv4_only" | "ipv6_only" | "unknown";
|
||||
export type TimeSyncMode = "ntp_only" | "ntp_and_http" | "http_only" | "custom" | "unknown";
|
||||
|
||||
export interface NetworkSettings {
|
||||
ipv4_mode: IPv4Mode;
|
||||
ipv6_mode: IPv6Mode;
|
||||
lldp_mode: LLDPMode;
|
||||
lldp_tx_tlvs: string[];
|
||||
mdns_mode: mDNSMode;
|
||||
time_sync_mode: TimeSyncMode;
|
||||
}
|
||||
|
||||
export const useNetworkStateStore = create<NetworkState>((set, get) => ({
|
||||
setNetworkState: (state: NetworkState) => set(state),
|
||||
setDhcpLease: (lease: NetworkState["dhcp_lease"]) => set({ dhcp_lease: lease }),
|
||||
setDhcpLeaseExpiry: (expiry: Date) => {
|
||||
const lease = get().dhcp_lease;
|
||||
if (!lease) {
|
||||
console.warn("No lease found");
|
||||
return;
|
||||
}
|
||||
|
||||
lease.lease_expiry = expiry.toISOString();
|
||||
set({ dhcp_lease: lease });
|
||||
}
|
||||
}));
|
||||
|
||||
export interface KeySequenceStep {
|
||||
keys: string[];
|
||||
modifiers: string[];
|
||||
|
@ -767,8 +854,8 @@ export const useMacrosStore = create<MacrosState>((set, get) => ({
|
|||
for (let i = 0; i < macro.steps.length; i++) {
|
||||
const step = macro.steps[i];
|
||||
if (step.keys && step.keys.length > MAX_KEYS_PER_STEP) {
|
||||
console.error(`Cannot save: macro "${macro.name}" step ${i+1} exceeds maximum of ${MAX_KEYS_PER_STEP} keys`);
|
||||
throw new Error(`Cannot save: macro "${macro.name}" step ${i+1} exceeds maximum of ${MAX_KEYS_PER_STEP} keys`);
|
||||
console.error(`Cannot save: macro "${macro.name}" step ${i + 1} exceeds maximum of ${MAX_KEYS_PER_STEP} keys`);
|
||||
throw new Error(`Cannot save: macro "${macro.name}" step ${i + 1} exceeds maximum of ${MAX_KEYS_PER_STEP} keys`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,62 +1,79 @@
|
|||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
import { SettingsPageHeader } from "../components/SettingsPageheader";
|
||||
import { SelectMenuBasic } from "../components/SelectMenuBasic";
|
||||
import { SettingsPageHeader } from "../components/SettingsPageheader";
|
||||
|
||||
import { SettingsItem } from "./devices.$id.settings";
|
||||
import { IPv4Mode, IPv6Mode, LLDPMode, mDNSMode, NetworkSettings, NetworkState, TimeSyncMode, useNetworkStateStore } from "@/hooks/stores";
|
||||
import { useJsonRpc } from "@/hooks/useJsonRpc";
|
||||
import { Button } from "@components/Button";
|
||||
import notifications from "@/notifications";
|
||||
import { Button } from "@components/Button";
|
||||
import { GridCard } from "@components/Card";
|
||||
import InputField from "@components/InputField";
|
||||
import { SettingsItem } from "./devices.$id.settings";
|
||||
|
||||
interface DhcpLease {
|
||||
ip?: string;
|
||||
netmask?: string;
|
||||
broadcast?: string;
|
||||
ttl?: string;
|
||||
mtu?: string;
|
||||
hostname?: string;
|
||||
domain?: string;
|
||||
bootp_next_server?: string;
|
||||
bootp_server_name?: string;
|
||||
bootp_file?: string;
|
||||
timezone?: string;
|
||||
routers?: string[];
|
||||
dns?: string[];
|
||||
ntp_servers?: string[];
|
||||
lpr_servers?: string[];
|
||||
_time_servers?: string[];
|
||||
_name_servers?: string[];
|
||||
_log_servers?: string[];
|
||||
_cookie_servers?: string[];
|
||||
_wins_servers?: string[];
|
||||
_swap_server?: string;
|
||||
boot_size?: string;
|
||||
root_path?: string;
|
||||
lease?: string;
|
||||
dhcp_type?: string;
|
||||
server_id?: string;
|
||||
message?: string;
|
||||
tftp?: string;
|
||||
bootfile?: string;
|
||||
import dayjs from 'dayjs';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
const defaultNetworkSettings: NetworkSettings = {
|
||||
ipv4_mode: "unknown",
|
||||
ipv6_mode: "unknown",
|
||||
lldp_mode: "unknown",
|
||||
lldp_tx_tlvs: [],
|
||||
mdns_mode: "unknown",
|
||||
time_sync_mode: "unknown",
|
||||
}
|
||||
|
||||
export function LifeTimeLabel({ lifetime }: { lifetime: string }) {
|
||||
if (lifetime == "") {
|
||||
return <strong>N/A</strong>;
|
||||
}
|
||||
|
||||
interface NetworkState {
|
||||
interface_name?: string;
|
||||
mac_address?: string;
|
||||
ipv4?: string;
|
||||
ipv6?: string;
|
||||
dhcp_lease?: DhcpLease;
|
||||
const [remaining, setRemaining] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setRemaining(dayjs(lifetime).fromNow());
|
||||
|
||||
const interval = setInterval(() => {
|
||||
setRemaining(dayjs(lifetime).fromNow());
|
||||
}, 1000 * 30);
|
||||
return () => clearInterval(interval);
|
||||
}, [lifetime]);
|
||||
|
||||
return <>
|
||||
<strong>{dayjs(lifetime).format()}</strong>
|
||||
{remaining && <>
|
||||
{" "}<span className="text-xs text-slate-700 dark:text-slate-300">
|
||||
({remaining})
|
||||
</span>
|
||||
</>}
|
||||
</>
|
||||
}
|
||||
|
||||
export default function SettingsNetworkRoute() {
|
||||
const [send] = useJsonRpc();
|
||||
const [networkState, setNetworkState] = useState<NetworkState | null>(null);
|
||||
const [networkState, setNetworkState] = useNetworkStateStore(state => [state, state.setNetworkState]);
|
||||
|
||||
const [networkSettings, setNetworkSettings] = useState<NetworkSettings>(defaultNetworkSettings);
|
||||
const [networkSettingsLoaded, setNetworkSettingsLoaded] = useState(false);
|
||||
|
||||
const [dhcpLeaseExpiry, setDhcpLeaseExpiry] = useState<Date | null>(null);
|
||||
const [dhcpLeaseExpiryRemaining, setDhcpLeaseExpiryRemaining] = useState<string | null>(null);
|
||||
|
||||
const getNetworkSettings = useCallback(() => {
|
||||
setNetworkSettingsLoaded(false);
|
||||
send("getNetworkSettings", {}, resp => {
|
||||
if ("error" in resp) return;
|
||||
setNetworkSettings(resp.result as NetworkSettings);
|
||||
setNetworkSettingsLoaded(true);
|
||||
});
|
||||
}, [send]);
|
||||
|
||||
const getNetworkState = useCallback(() => {
|
||||
send("getNetworkState", {}, resp => {
|
||||
if ("error" in resp) return;
|
||||
console.log(resp.result);
|
||||
setNetworkState(resp.result as NetworkState);
|
||||
});
|
||||
}, [send]);
|
||||
|
@ -74,7 +91,37 @@ export default function SettingsNetworkRoute() {
|
|||
|
||||
useEffect(() => {
|
||||
getNetworkState();
|
||||
}, [getNetworkState]);
|
||||
getNetworkSettings();
|
||||
}, [getNetworkState, getNetworkSettings]);
|
||||
|
||||
const handleIpv4ModeChange = (value: IPv4Mode | string) => {
|
||||
setNetworkSettings({ ...networkSettings, ipv4_mode: value as IPv4Mode });
|
||||
};
|
||||
|
||||
const handleIpv6ModeChange = (value: IPv6Mode | string) => {
|
||||
setNetworkSettings({ ...networkSettings, ipv6_mode: value as IPv6Mode });
|
||||
};
|
||||
|
||||
const handleLldpModeChange = (value: LLDPMode | string) => {
|
||||
setNetworkSettings({ ...networkSettings, lldp_mode: value as LLDPMode });
|
||||
};
|
||||
|
||||
const handleLldpTxTlvsChange = (value: string[]) => {
|
||||
setNetworkSettings({ ...networkSettings, lldp_tx_tlvs: value });
|
||||
};
|
||||
|
||||
const handleMdnsModeChange = (value: mDNSMode | string) => {
|
||||
setNetworkSettings({ ...networkSettings, mdns_mode: value as mDNSMode });
|
||||
};
|
||||
|
||||
const handleTimeSyncModeChange = (value: TimeSyncMode | string) => {
|
||||
setNetworkSettings({ ...networkSettings, time_sync_mode: value as TimeSyncMode });
|
||||
};
|
||||
|
||||
const filterUnknown = useCallback((options: { value: string; label: string; }[]) => {
|
||||
if (!networkSettingsLoaded) return options;
|
||||
return options.filter(option => option.value !== "unknown");
|
||||
}, [networkSettingsLoaded]);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
|
@ -82,54 +129,265 @@ export default function SettingsNetworkRoute() {
|
|||
title="Network"
|
||||
description="Configure your network settings"
|
||||
/>
|
||||
<div className="space-y-4">
|
||||
<SettingsItem
|
||||
title="IPv4 Address"
|
||||
description={
|
||||
<span className="select-text font-mono">{networkState?.ipv4}</span>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<SettingsItem
|
||||
title="IPv6 Address"
|
||||
description={<span className="select-text font-mono">{networkState?.ipv6}</span>}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<SettingsItem
|
||||
title="MAC Address"
|
||||
description={<span className="select-auto font-mono">{networkState?.mac_address}</span>}
|
||||
/>
|
||||
description={<></>}
|
||||
>
|
||||
<span className="select-auto font-mono text-xs text-slate-700 dark:text-slate-300">
|
||||
{networkState?.mac_address}
|
||||
</span>
|
||||
</SettingsItem>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<SettingsItem
|
||||
title="DHCP Lease"
|
||||
description={<>
|
||||
<ul>
|
||||
{networkState?.dhcp_lease?.ip && <li>IP: <strong>{networkState?.dhcp_lease?.ip}</strong></li>}
|
||||
{networkState?.dhcp_lease?.netmask && <li>Subnet: <strong>{networkState?.dhcp_lease?.netmask}</strong></li>}
|
||||
{networkState?.dhcp_lease?.broadcast && <li>Broadcast: <strong>{networkState?.dhcp_lease?.broadcast}</strong></li>}
|
||||
{networkState?.dhcp_lease?.ttl && <li>TTL: <strong>{networkState?.dhcp_lease?.ttl}</strong></li>}
|
||||
{networkState?.dhcp_lease?.mtu && <li>MTU: <strong>{networkState?.dhcp_lease?.mtu}</strong></li>}
|
||||
{networkState?.dhcp_lease?.hostname && <li>Hostname: <strong>{networkState?.dhcp_lease?.hostname}</strong></li>}
|
||||
{networkState?.dhcp_lease?.domain && <li>Domain: <strong>{networkState?.dhcp_lease?.domain}</strong></li>}
|
||||
{networkState?.dhcp_lease?.routers && <li>Gateway: <strong>{networkState?.dhcp_lease?.routers.join(", ")}</strong></li>}
|
||||
{networkState?.dhcp_lease?.dns && <li>DNS: <strong>{networkState?.dhcp_lease?.dns.join(", ")}</strong></li>}
|
||||
{networkState?.dhcp_lease?.ntp_servers && <li>NTP Servers: <strong>{networkState?.dhcp_lease?.ntp_servers.join(", ")}</strong></li>}
|
||||
</ul>
|
||||
</>}
|
||||
title="Hostname"
|
||||
description={
|
||||
<>
|
||||
Hostname for the device
|
||||
<br />
|
||||
<span className="text-xs text-slate-700 dark:text-slate-300">
|
||||
Leave blank for default
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
size="SM"
|
||||
theme="light"
|
||||
text="Renew lease"
|
||||
onClick={() => {
|
||||
handleRenewLease();
|
||||
}}
|
||||
<InputField
|
||||
type="text"
|
||||
placeholder="jetkvm"
|
||||
value={""}
|
||||
error={""}
|
||||
onChange={e => {
|
||||
console.log(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</SettingsItem>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<SettingsItem
|
||||
title="Domain"
|
||||
description={
|
||||
<>
|
||||
Domain for the device
|
||||
<br />
|
||||
<span className="text-xs text-slate-700 dark:text-slate-300">
|
||||
Leave blank to use DHCP provided domain, if there is no domain, use <span className="font-mono">local</span>
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<InputField
|
||||
type="text"
|
||||
placeholder="local"
|
||||
value={""}
|
||||
error={""}
|
||||
onChange={e => {
|
||||
console.log(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</SettingsItem>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<SettingsItem
|
||||
title="IPv4 Mode"
|
||||
description="Configure the IPv4 mode"
|
||||
>
|
||||
<SelectMenuBasic
|
||||
size="SM"
|
||||
value={networkSettings.ipv4_mode}
|
||||
onChange={e => handleIpv4ModeChange(e.target.value)}
|
||||
disabled={!networkSettingsLoaded}
|
||||
options={filterUnknown([
|
||||
{ value: "dhcp", label: "DHCP" },
|
||||
// { value: "static", label: "Static" },
|
||||
])}
|
||||
/>
|
||||
</SettingsItem>
|
||||
{networkState?.dhcp_lease && (
|
||||
<GridCard>
|
||||
<div className="flex items-start gap-x-4 p-4">
|
||||
<div className="space-y-3 w-full">
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-base font-bold text-slate-900 dark:text-white">
|
||||
Current DHCP Lease
|
||||
</h3>
|
||||
<div>
|
||||
<ul className="list-none space-y-1 text-xs text-slate-700 dark:text-slate-300">
|
||||
{networkState?.dhcp_lease?.ip && <li>IP: <strong>{networkState?.dhcp_lease?.ip}</strong></li>}
|
||||
{networkState?.dhcp_lease?.netmask && <li>Subnet: <strong>{networkState?.dhcp_lease?.netmask}</strong></li>}
|
||||
{networkState?.dhcp_lease?.broadcast && <li>Broadcast: <strong>{networkState?.dhcp_lease?.broadcast}</strong></li>}
|
||||
{networkState?.dhcp_lease?.ttl && <li>TTL: <strong>{networkState?.dhcp_lease?.ttl}</strong></li>}
|
||||
{networkState?.dhcp_lease?.mtu && <li>MTU: <strong>{networkState?.dhcp_lease?.mtu}</strong></li>}
|
||||
{networkState?.dhcp_lease?.hostname && <li>Hostname: <strong>{networkState?.dhcp_lease?.hostname}</strong></li>}
|
||||
{networkState?.dhcp_lease?.domain && <li>Domain: <strong>{networkState?.dhcp_lease?.domain}</strong></li>}
|
||||
{networkState?.dhcp_lease?.routers && <li>Gateway: <strong>{networkState?.dhcp_lease?.routers.join(", ")}</strong></li>}
|
||||
{networkState?.dhcp_lease?.dns && <li>DNS: <strong>{networkState?.dhcp_lease?.dns.join(", ")}</strong></li>}
|
||||
{networkState?.dhcp_lease?.ntp_servers && <li>NTP Servers: <strong>{networkState?.dhcp_lease?.ntp_servers.join(", ")}</strong></li>}
|
||||
{networkState?.dhcp_lease?.server_id && <li>Server ID: <strong>{networkState?.dhcp_lease?.server_id}</strong></li>}
|
||||
{networkState?.dhcp_lease?.bootp_next_server && <li>BootP Next Server: <strong>{networkState?.dhcp_lease?.bootp_next_server}</strong></li>}
|
||||
{networkState?.dhcp_lease?.bootp_server_name && <li>BootP Server Name: <strong>{networkState?.dhcp_lease?.bootp_server_name}</strong></li>}
|
||||
{networkState?.dhcp_lease?.bootp_file && <li>Boot File: <strong>{networkState?.dhcp_lease?.bootp_file}</strong></li>}
|
||||
{networkState?.dhcp_lease?.lease_expiry && <li>
|
||||
Lease Expiry: <LifeTimeLabel lifetime={`${networkState?.dhcp_lease?.lease_expiry}`} />
|
||||
</li>}
|
||||
{/* {JSON.stringify(networkState?.dhcp_lease)} */}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<hr className="block w-full dark:border-slate-600" />
|
||||
<div>
|
||||
<Button
|
||||
size="SM"
|
||||
theme="danger"
|
||||
text="Renew lease"
|
||||
onClick={() => {
|
||||
handleRenewLease();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</GridCard>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<SettingsItem
|
||||
title="IPv6 Mode"
|
||||
description="Configure the IPv6 mode"
|
||||
>
|
||||
<SelectMenuBasic
|
||||
size="SM"
|
||||
value={networkSettings.ipv6_mode}
|
||||
onChange={e => handleIpv6ModeChange(e.target.value)}
|
||||
disabled={!networkSettingsLoaded}
|
||||
options={filterUnknown([
|
||||
// { value: "disabled", label: "Disabled" },
|
||||
{ value: "slaac", label: "SLAAC" },
|
||||
// { value: "dhcpv6", label: "DHCPv6" },
|
||||
// { value: "slaac_and_dhcpv6", label: "SLAAC and DHCPv6" },
|
||||
// { value: "static", label: "Static" },
|
||||
// { value: "link_local", label: "Link-local only" },
|
||||
])}
|
||||
/>
|
||||
</SettingsItem>
|
||||
{networkState?.ipv6_addresses && (
|
||||
<GridCard>
|
||||
<div className="flex items-start gap-x-4 p-4">
|
||||
<div className="space-y-3 w-full">
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-base font-bold text-slate-900 dark:text-white">
|
||||
IPv6 Information
|
||||
</h3>
|
||||
<div className="space-y-2">
|
||||
<div>
|
||||
<h4 className="text-sm font-bold text-slate-900 dark:text-white">
|
||||
IPv6 Link-local
|
||||
</h4>
|
||||
<p className="text-xs text-slate-700 dark:text-slate-300">
|
||||
{networkState?.ipv6_link_local}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-sm font-bold text-slate-900 dark:text-white">
|
||||
IPv6 Addresses
|
||||
</h4>
|
||||
<ul className="list-none space-y-1 text-xs text-slate-700 dark:text-slate-300">
|
||||
{networkState?.ipv6_addresses && networkState?.ipv6_addresses.map(addr => (
|
||||
<li key={addr.address}>
|
||||
{addr.address}
|
||||
{addr.valid_lifetime && <>
|
||||
<br />
|
||||
- valid_lft: {" "}
|
||||
<span className="text-xs text-slate-700 dark:text-slate-300">
|
||||
<LifeTimeLabel lifetime={`${addr.valid_lifetime}`} />
|
||||
</span>
|
||||
</>}
|
||||
{addr.preferred_lifetime && <>
|
||||
<br />
|
||||
- pref_lft: {" "}
|
||||
<span className="text-xs text-slate-700 dark:text-slate-300">
|
||||
<LifeTimeLabel lifetime={`${addr.preferred_lifetime}`} />
|
||||
</span>
|
||||
</>}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</GridCard>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<SettingsItem
|
||||
title="LLDP"
|
||||
description="Control which TLVs will be sent over Link Layer Discovery Protocol"
|
||||
>
|
||||
<SelectMenuBasic
|
||||
size="SM"
|
||||
value={networkSettings.lldp_mode}
|
||||
onChange={e => handleLldpModeChange(e.target.value)}
|
||||
disabled={!networkSettingsLoaded}
|
||||
options={filterUnknown([
|
||||
{ value: "disabled", label: "Disabled" },
|
||||
{ value: "basic", label: "Basic" },
|
||||
{ value: "all", label: "All" },
|
||||
])}
|
||||
/>
|
||||
</SettingsItem>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<SettingsItem
|
||||
title="mDNS"
|
||||
description="Control mDNS (multicast DNS) operational mode"
|
||||
>
|
||||
<SelectMenuBasic
|
||||
size="SM"
|
||||
value={networkSettings.mdns_mode}
|
||||
onChange={e => handleMdnsModeChange(e.target.value)}
|
||||
disabled={!networkSettingsLoaded}
|
||||
options={filterUnknown([
|
||||
{ value: "disabled", label: "Disabled" },
|
||||
{ value: "auto", label: "Auto" },
|
||||
{ value: "ipv4_only", label: "IPv4 only" },
|
||||
{ value: "ipv6_only", label: "IPv6 only" },
|
||||
])}
|
||||
/>
|
||||
</SettingsItem>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<SettingsItem
|
||||
title="Time synchronization"
|
||||
description="Configure time synchronization settings"
|
||||
>
|
||||
<SelectMenuBasic
|
||||
size="SM"
|
||||
value={networkSettings.time_sync_mode}
|
||||
onChange={e => handleTimeSyncModeChange(e.target.value)}
|
||||
disabled={!networkSettingsLoaded}
|
||||
options={filterUnknown([
|
||||
{ value: "unknown", label: "..." },
|
||||
{ value: "auto", label: "Auto" },
|
||||
{ value: "ntp_only", label: "NTP only" },
|
||||
{ value: "ntp_and_http", label: "NTP and HTTP" },
|
||||
{ value: "http_only", label: "HTTP only" },
|
||||
{ value: "custom", label: "Custom" },
|
||||
])}
|
||||
/>
|
||||
</SettingsItem>
|
||||
</div>
|
||||
<div className="flex items-end gap-x-2">
|
||||
<Button
|
||||
onClick={() => {
|
||||
console.log("save settings");
|
||||
}}
|
||||
size="SM"
|
||||
theme="light"
|
||||
text="Save Settings"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -20,11 +20,13 @@ import { cx } from "@/cva.config";
|
|||
import {
|
||||
DeviceSettingsState,
|
||||
HidState,
|
||||
NetworkState,
|
||||
UpdateState,
|
||||
useDeviceSettingsStore,
|
||||
useDeviceStore,
|
||||
useHidStore,
|
||||
useMountMediaStore,
|
||||
useNetworkStateStore,
|
||||
User,
|
||||
useRTCStore,
|
||||
useUiStore,
|
||||
|
@ -581,6 +583,8 @@ export default function KvmIdRoute() {
|
|||
});
|
||||
}, 10000);
|
||||
|
||||
const setNetworkState = useNetworkStateStore(state => state.setNetworkState);
|
||||
|
||||
const setUsbState = useHidStore(state => state.setUsbState);
|
||||
const setHdmiState = useVideoStore(state => state.setHdmiState);
|
||||
|
||||
|
@ -600,6 +604,11 @@ export default function KvmIdRoute() {
|
|||
setHdmiState(resp.params as Parameters<VideoState["setHdmiState"]>[0]);
|
||||
}
|
||||
|
||||
if (resp.method === "networkState") {
|
||||
console.log("Setting network state", resp.params);
|
||||
setNetworkState(resp.params as NetworkState);
|
||||
}
|
||||
|
||||
if (resp.method === "otaState") {
|
||||
const otaState = resp.params as UpdateState["otaState"];
|
||||
setOtaState(otaState);
|
||||
|
|
2
usb.go
2
usb.go
|
@ -66,6 +66,6 @@ func checkUSBState() {
|
|||
usbState = newState
|
||||
|
||||
usbLogger.Info().Str("from", usbState).Str("to", newState).Msg("USB state changed")
|
||||
requestDisplayUpdate()
|
||||
requestDisplayUpdate(true)
|
||||
triggerUSBStateUpdate()
|
||||
}
|
||||
|
|
2
video.go
2
video.go
|
@ -43,7 +43,7 @@ func HandleVideoStateMessage(event CtrlResponse) {
|
|||
}
|
||||
lastVideoState = videoState
|
||||
triggerVideoStateUpdate()
|
||||
requestDisplayUpdate()
|
||||
requestDisplayUpdate(true)
|
||||
}
|
||||
|
||||
func rpcGetVideoState() (VideoInputState, error) {
|
||||
|
|
Loading…
Reference in New Issue