import { useCallback, useEffect, useRef, useState } from "react"; import dayjs from "dayjs"; import relativeTime from "dayjs/plugin/relativeTime"; import { ArrowPathIcon } from "@heroicons/react/24/outline"; import { IPv4Mode, IPv6Mode, LLDPMode, mDNSMode, NetworkSettings, NetworkState, TimeSyncMode, useNetworkStateStore, } from "@/hooks/stores"; import { useJsonRpc } from "@/hooks/useJsonRpc"; import { Button } from "@components/Button"; import { GridCard } from "@components/Card"; import InputField from "@components/InputField"; import { SelectMenuBasic } from "@/components/SelectMenuBasic"; import { SettingsPageHeader } from "@/components/SettingsPageheader"; import Fieldset from "@/components/Fieldset"; import { ConfirmDialog } from "@/components/ConfirmDialog"; import notifications from "@/notifications"; import { SettingsItem } from "./devices.$id.settings"; dayjs.extend(relativeTime); const defaultNetworkSettings: NetworkSettings = { hostname: "", domain: "", 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 }) { const [remaining, setRemaining] = useState(null); useEffect(() => { setRemaining(dayjs(lifetime).fromNow()); const interval = setInterval(() => { setRemaining(dayjs(lifetime).fromNow()); }, 1000 * 30); return () => clearInterval(interval); }, [lifetime]); if (lifetime == "") { return N/A; } return ( <> {dayjs(lifetime).format("YYYY-MM-DD HH:mm")} {remaining && ( <> {" "} ({remaining}) )} ); } export default function SettingsNetworkRoute() { const [send] = useJsonRpc(); const [networkState, setNetworkState] = useNetworkStateStore(state => [ state, state.setNetworkState, ]); const [networkSettings, setNetworkSettings] = useState(defaultNetworkSettings); // We use this to determine whether the settings have changed const firstNetworkSettings = useRef(undefined); const [networkSettingsLoaded, setNetworkSettingsLoaded] = useState(false); const [customDomain, setCustomDomain] = useState(""); const [selectedDomainOption, setSelectedDomainOption] = useState("dhcp"); useEffect(() => { if (networkSettings.domain && networkSettingsLoaded) { // Check if the domain is one of the predefined options const predefinedOptions = ["dhcp", "local"]; if (predefinedOptions.includes(networkSettings.domain)) { setSelectedDomainOption(networkSettings.domain); } else { setSelectedDomainOption("custom"); setCustomDomain(networkSettings.domain); } } }, [networkSettings.domain, networkSettingsLoaded]); const getNetworkSettings = useCallback(() => { setNetworkSettingsLoaded(false); send("getNetworkSettings", {}, resp => { if ("error" in resp) return; console.log(resp.result); setNetworkSettings(resp.result as NetworkSettings); if (!firstNetworkSettings.current) { firstNetworkSettings.current = resp.result as NetworkSettings; } setNetworkSettingsLoaded(true); }); }, [send]); const setNetworkSettingsRemote = useCallback( (settings: NetworkSettings) => { setNetworkSettingsLoaded(false); send("setNetworkSettings", { settings }, resp => { if ("error" in resp) { notifications.error( "Failed to save network settings: " + (resp.error.data ? resp.error.data : resp.error.message), ); setNetworkSettingsLoaded(true); return; } // 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); setNetworkSettingsLoaded(true); notifications.success("Network settings saved"); }); }, [send], ); const getNetworkState = useCallback(() => { send("getNetworkState", {}, resp => { if ("error" in resp) return; console.log(resp.result); setNetworkState(resp.result as NetworkState); }); }, [send, setNetworkState]); 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"); } }); }, [send]); useEffect(() => { 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 handleHostnameChange = (value: string) => { setNetworkSettings({ ...networkSettings, hostname: value }); }; const handleDomainChange = (value: string) => { setNetworkSettings({ ...networkSettings, domain: value }); }; const handleDomainOptionChange = (value: string) => { setSelectedDomainOption(value); if (value !== "custom") { handleDomainChange(value); } }; const handleCustomDomainChange = (value: string) => { setCustomDomain(value); handleDomainChange(value); }; const filterUnknown = useCallback( (options: { value: string; label: string }[]) => { if (!networkSettingsLoaded) return options; return options.filter(option => option.value !== "unknown"); }, [networkSettingsLoaded], ); const [showRenewLeaseConfirm, setShowRenewLeaseConfirm] = useState(false); return ( <>
{ handleHostnameChange(e.target.value); }} />
handleDomainOptionChange(e.target.value)} options={[ { value: "dhcp", label: "DHCP provided" }, { value: "local", label: ".local" }, { value: "custom", label: "Custom" }, ]} />
{selectedDomainOption === "custom" && (
setCustomDomain(e.target.value)} />
)}
handleMdnsModeChange(e.target.value)} options={filterUnknown([ { value: "disabled", label: "Disabled" }, { value: "auto", label: "Auto" }, { value: "ipv4_only", label: "IPv4 only" }, { value: "ipv6_only", label: "IPv6 only" }, ])} />
{ handleTimeSyncModeChange(e.target.value); }} 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" }, ])} />
handleIpv4ModeChange(e.target.value)} options={filterUnknown([ { value: "dhcp", label: "DHCP" }, // { value: "static", label: "Static" }, ])} /> {networkState?.dhcp_lease && (

DHCP Lease

{networkState?.dhcp_lease?.ip && (
IP Address {networkState?.dhcp_lease?.ip}
)} {networkState?.dhcp_lease?.netmask && (
Subnet Mask {networkState?.dhcp_lease?.netmask}
)} {networkState?.dhcp_lease?.dns && (
DNS Servers {networkState?.dhcp_lease?.dns.map(dns => (
{dns}
))}
)} {networkState?.dhcp_lease?.broadcast && (
Broadcast {networkState?.dhcp_lease?.broadcast}
)} {networkState?.dhcp_lease?.domain && (
Domain {networkState?.dhcp_lease?.domain}
)} {networkState?.dhcp_lease?.ntp_servers && networkState?.dhcp_lease?.ntp_servers.length > 0 && (
NTP Servers
{networkState?.dhcp_lease?.ntp_servers.map(server => (
{server}
))}
)} {networkState?.dhcp_lease?.hostname && (
Hostname {networkState?.dhcp_lease?.hostname}
)}
{networkState?.dhcp_lease?.routers && networkState?.dhcp_lease?.routers.length > 0 && (
Gateway {networkState?.dhcp_lease?.routers.map(router => (
{router}
))}
)} {networkState?.dhcp_lease?.server_id && (
DHCP Server {networkState?.dhcp_lease?.server_id}
)} {networkState?.dhcp_lease?.lease_expiry && (
Lease Expires
)} {networkState?.dhcp_lease?.mtu && (
MTU {networkState?.dhcp_lease?.mtu}
)} {networkState?.dhcp_lease?.ttl && (
TTL {networkState?.dhcp_lease?.ttl}
)} {networkState?.dhcp_lease?.bootp_next_server && (
Boot Next Server {networkState?.dhcp_lease?.bootp_next_server}
)} {networkState?.dhcp_lease?.bootp_server_name && (
Boot Server Name {networkState?.dhcp_lease?.bootp_server_name}
)} {networkState?.dhcp_lease?.bootp_file && (
Boot File {networkState?.dhcp_lease?.bootp_file}
)}
)}
handleIpv6ModeChange(e.target.value)} 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" }, ])} /> {networkState?.ipv6_addresses && (

IPv6 Information

{networkState?.dhcp_lease?.ip && (
Link-local {networkState?.ipv6_link_local}
)}
{networkState?.ipv6_addresses && networkState?.ipv6_addresses.length > 0 && (

IPv6 Addresses

{networkState.ipv6_addresses.map(addr => (
Address {addr.address}
{addr.valid_lifetime && (
Valid Lifetime {addr.valid_lifetime === "" ? ( N/A ) : ( )}
)} {addr.preferred_lifetime && (
Preferred Lifetime {addr.preferred_lifetime === "" ? ( N/A ) : ( )}
)}
))}
)}
)}
handleLldpModeChange(e.target.value)} options={filterUnknown([ { value: "disabled", label: "Disabled" }, { value: "basic", label: "Basic" }, { value: "all", label: "All" }, ])} />
setShowRenewLeaseConfirm(false)} title="Renew DHCP Lease" description="This will request a new IP address from your DHCP server. Your device may temporarily lose network connectivity during this process." variant="danger" confirmText="Renew Lease" onConfirm={() => { handleRenewLease(); setShowRenewLeaseConfirm(false); }} /> ); }