feat(ui): add network settings tab

This commit is contained in:
Siyuan Miao 2025-04-13 03:53:20 +02:00
parent d9eae340bf
commit fd3a8cb553
6 changed files with 187 additions and 0 deletions

View File

@ -143,3 +143,7 @@ func (c *DHCPClient) loadLeaseFile() error {
return nil
}
func (c *DHCPClient) GetLease() *Lease {
return c.lease
}

View File

@ -960,6 +960,8 @@ var rpcHandlers = map[string]RPCHandler{
"getDeviceID": {Func: rpcGetDeviceID},
"deregisterDevice": {Func: rpcDeregisterDevice},
"getCloudState": {Func: rpcGetCloudState},
"getNetworkState": {Func: rpcGetNetworkState},
"renewDHCPLease": {Func: rpcRenewDHCPLease},
"keyboardReport": {Func: rpcKeyboardReport, Params: []string{"modifier", "keys"}},
"absMouseReport": {Func: rpcAbsMouseReport, Params: []string{"x", "y", "buttons"}},
"relMouseReport": {Func: rpcRelMouseReport, Params: []string{"dx", "dy", "buttons"}},

View File

@ -47,6 +47,14 @@ type NetworkInterfaceState struct {
checked bool
}
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"`
}
func (s *NetworkInterfaceState) IsUp() bool {
return s.interfaceUp
}
@ -305,6 +313,27 @@ func (s *NetworkInterfaceState) HandleLinkUpdate(update netlink.LinkUpdate) {
}
}
func rpcGetNetworkState() RpcNetworkState {
return RpcNetworkState{
InterfaceName: networkState.interfaceName,
MacAddress: networkState.macAddr.String(),
IPv4: networkState.ipv4Addr.String(),
IPv6: networkState.ipv6Addr.String(),
DHCPLease: networkState.dhcpClient.GetLease(),
}
}
func rpcRenewDHCPLease() error {
if networkState == nil {
return fmt.Errorf("network state not initialized")
}
if networkState.dhcpClient == nil {
return fmt.Errorf("dhcp client not initialized")
}
return networkState.dhcpClient.Renew()
}
func initNetwork() {
ensureConfigLoaded()

View File

@ -42,6 +42,7 @@ import SettingsVideoRoute from "./routes/devices.$id.settings.video";
import SettingsAppearanceRoute from "./routes/devices.$id.settings.appearance";
import * as SettingsGeneralIndexRoute from "./routes/devices.$id.settings.general._index";
import SettingsGeneralUpdateRoute from "./routes/devices.$id.settings.general.update";
import SettingsNetworkRoute from "./routes/devices.$id.settings.network";
import SecurityAccessLocalAuthRoute from "./routes/devices.$id.settings.access.local-auth";
import SettingsMacrosRoute from "./routes/devices.$id.settings.macros";
import SettingsMacrosAddRoute from "./routes/devices.$id.settings.macros.add";
@ -156,6 +157,10 @@ if (isOnDevice) {
path: "hardware",
element: <SettingsHardwareRoute />,
},
{
path: "network",
element: <SettingsNetworkRoute />,
},
{
path: "access",
children: [

View File

@ -0,0 +1,135 @@
import { useCallback, useEffect, useState } from "react";
import { SettingsPageHeader } from "../components/SettingsPageheader";
import { SelectMenuBasic } from "../components/SelectMenuBasic";
import { SettingsItem } from "./devices.$id.settings";
import { useJsonRpc } from "@/hooks/useJsonRpc";
import { Button } from "@components/Button";
import notifications from "@/notifications";
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;
}
interface NetworkState {
interface_name?: string;
mac_address?: string;
ipv4?: string;
ipv6?: string;
dhcp_lease?: DhcpLease;
}
export default function SettingsNetworkRoute() {
const [send] = useJsonRpc();
const [networkState, setNetworkState] = useState<NetworkState | null>(null);
const getNetworkState = useCallback(() => {
send("getNetworkState", {}, resp => {
if ("error" in resp) return;
setNetworkState(resp.result as NetworkState);
});
}, [send]);
const handleRenewLease = useCallback(() => {
send("renewDHCPLease", {}, resp => {
if ("error" in resp) {
notifications.error("Failed to renew lease: " + resp.error.message);
} else {
notifications.success("DHCP lease renewed");
getNetworkState();
}
});
}, [send, getNetworkState]);
useEffect(() => {
getNetworkState();
}, [getNetworkState]);
return (
<div className="space-y-4">
<SettingsPageHeader
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>}
/>
</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>
</>}
>
<Button
size="SM"
theme="light"
text="Renew lease"
onClick={() => {
handleRenewLease();
}}
/>
</SettingsItem>
</div>
</div>
);
}

View File

@ -9,6 +9,7 @@ import {
LuArrowLeft,
LuPalette,
LuCommand,
LuNetwork,
} from "react-icons/lu";
import React, { useEffect, useRef, useState } from "react";
@ -207,6 +208,17 @@ export default function SettingsRoute() {
</div>
</NavLink>
</div>
<div className="shrink-0">
<NavLink
to="network"
className={({ isActive }) => (isActive ? "active" : "")}
>
<div className="flex items-center gap-x-2 rounded-md px-2.5 py-2.5 text-sm transition-colors hover:bg-slate-100 dark:hover:bg-slate-700 [.active_&]:bg-blue-50 [.active_&]:!text-blue-700 md:[.active_&]:bg-transparent dark:[.active_&]:bg-blue-900 dark:[.active_&]:!text-blue-200 dark:md:[.active_&]:bg-transparent">
<LuNetwork className="h-4 w-4 shrink-0" />
<h1>Network</h1>
</div>
</NavLink>
</div>
<div className="shrink-0">
<NavLink
to="advanced"