import { useCallback, useEffect, useMemo, useState } from "react"; import dayjs from "dayjs"; import relativeTime from "dayjs/plugin/relativeTime"; import { LuEthernetPort } from "react-icons/lu"; import { IPv4Mode, IPv6Mode, LLDPMode, mDNSMode, NetworkSettings, NetworkState, TimeSyncMode, useNetworkStateStore, } from "@hooks/stores"; import { JsonRpcResponse, useJsonRpc } from "@hooks/useJsonRpc"; import AutoHeight from "@components/AutoHeight"; import { Button } from "@components/Button"; import { ConfirmDialog } from "@components/ConfirmDialog"; import EmptyCard from "@components/EmptyCard"; import Fieldset from "@components/Fieldset"; import { GridCard } from "@components/Card"; import InputField, { InputFieldWithLabel } from "@components/InputField"; import Ipv6NetworkCard from "@components/Ipv6NetworkCard"; import DhcpLeaseCard from "@components/DhcpLeaseCard"; import { SelectMenuBasic } from "@components/SelectMenuBasic"; import { SettingsItem } from "@components/SettingsItem"; import { SettingsPageHeader } from "@components/SettingsPageheader"; import notifications from "@/notifications"; dayjs.extend(relativeTime); const defaultNetworkSettings: NetworkSettings = { hostname: "", http_proxy: "", 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); const updateRemaining = useCallback(() => { setRemaining(dayjs(lifetime).fromNow()); }, [lifetime]); useEffect(() => { setTimeout(() => updateRemaining(), 0); const interval = setInterval(() => { updateRemaining(); }, 1000 * 30); return () => clearInterval(interval); }, [updateRemaining]); if (lifetime == "") { return N/A; } return ( <> {remaining && <> {remaining}} {" "} ({dayjs(lifetime).format("YYYY-MM-DD HH:mm")}) ); } 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, setFirstNetworkSettings] = useState(undefined); const [networkSettingsLoaded, setNetworkSettingsLoaded] = useState(false); const selectedDomainOption = useMemo(() => { if (!networkSettingsLoaded) return "dhcp"; const predefinedOptions = ["dhcp", "local"]; return predefinedOptions.includes(networkSettings.domain) ? networkSettings.domain : "custom"; }, [networkSettings.domain, networkSettingsLoaded]); const customDomain = useMemo(() => { if (!networkSettingsLoaded) return ""; const predefinedOptions = ["dhcp", "local"]; return predefinedOptions.includes(networkSettings.domain) ? "" : networkSettings.domain; }, [networkSettings.domain, networkSettingsLoaded]); const getNetworkSettings = useCallback(() => { setNetworkSettingsLoaded(false); send("getNetworkSettings", {}, (resp: JsonRpcResponse) => { if ("error" in resp) return; const networkSettings = resp.result as NetworkSettings; console.debug("Network settings: ", networkSettings); setNetworkSettings(networkSettings); if (!firstNetworkSettings) { setFirstNetworkSettings(networkSettings); } setNetworkSettingsLoaded(true); }); }, [send, firstNetworkSettings]); const getNetworkState = useCallback(() => { send("getNetworkState", {}, (resp: JsonRpcResponse) => { if ("error" in resp) return; const networkState = resp.result as NetworkState; console.debug("Network state:", networkState); setNetworkState(networkState); }); }, [send, setNetworkState]); const setNetworkSettingsRemote = useCallback( (settings: NetworkSettings) => { setNetworkSettingsLoaded(false); send("setNetworkSettings", { settings }, (resp: JsonRpcResponse) => { if ("error" in resp) { notifications.error( "Failed to save network settings: " + (resp.error.data ? resp.error.data : resp.error.message), ); setNetworkSettingsLoaded(true); return; } const networkSettings = resp.result as NetworkSettings; setFirstNetworkSettings(networkSettings); setNetworkSettings(networkSettings); getNetworkState(); setNetworkSettingsLoaded(true); notifications.success("Network settings saved"); }); }, [getNetworkState, send], ); const handleRenewLease = useCallback(() => { send("renewDHCPLease", {}, (resp: JsonRpcResponse) => { if ("error" in resp) { notifications.error("Failed to renew lease: " + resp.error.message); } else { notifications.success("DHCP lease renewed"); } }); }, [send]); useEffect(() => { setTimeout(() => { getNetworkState(); getNetworkSettings(); }, 0); }, [getNetworkState, getNetworkSettings]); const handleIpv4ModeChange = (value: IPv4Mode | string) => { setNetworkSettingsRemote({ ...networkSettings, ipv4_mode: value as IPv4Mode }); }; const handleIpv6ModeChange = (value: IPv6Mode | string) => { setNetworkSettingsRemote({ ...networkSettings, ipv6_mode: value as IPv6Mode }); }; const handleLldpModeChange = (value: LLDPMode | string) => { setNetworkSettings({ ...networkSettings, lldp_mode: value as LLDPMode }); }; 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 handleProxyChange = (value: string) => { setNetworkSettings({ ...networkSettings, http_proxy: value }); }; const handleDomainChange = (value: string) => { setNetworkSettings({ ...networkSettings, domain: value }); }; const handleDomainOptionChange = (value: string) => { if (value !== "custom") { handleDomainChange(value); } }; const handleCustomDomainChange = (value: string) => { 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); }} />
{ handleProxyChange(e.target.value); }} />
handleDomainOptionChange(e.target.value)} options={[ { value: "dhcp", label: "DHCP provided" }, { value: "local", label: ".local" }, { value: "custom", label: "Custom" }, ]} />
{selectedDomainOption === "custom" && (
{ handleCustomDomainChange(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" }, ])} /> {!networkSettingsLoaded && !networkState?.dhcp_lease ? (

DHCP Lease Information

) : networkState?.dhcp_lease && networkState.dhcp_lease.ip ? ( ) : ( )}
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" }, ])} /> {!networkSettingsLoaded && !(networkState?.ipv6_addresses && networkState.ipv6_addresses.length > 0) ? (

IPv6 Information

) : networkState?.ipv6_addresses && networkState.ipv6_addresses.length > 0 ? ( ) : ( )}
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); }} /> ); }