feat(network): enhance network settings UI with domain management and improved layout

- Added custom domain input and selection options for DHCP and local domains.
- Improved layout for displaying network settings, including DHCP lease information and IPv6 addresses.
- Refactored state management for network settings and added handlers for hostname and domain changes.
- Updated the display of network settings to enhance user experience and accessibility.
This commit is contained in:
Adam Shiervani 2025-04-16 21:09:23 +02:00
parent d79f359c43
commit 8f488a3cb5
1 changed files with 489 additions and 228 deletions

View File

@ -1,18 +1,28 @@
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
import { SelectMenuBasic } from "../components/SelectMenuBasic"; import {
import { SettingsPageHeader } from "../components/SettingsPageheader"; IPv4Mode,
IPv6Mode,
import { IPv4Mode, IPv6Mode, LLDPMode, mDNSMode, NetworkSettings, NetworkState, TimeSyncMode, useNetworkStateStore } from "@/hooks/stores"; LLDPMode,
mDNSMode,
NetworkSettings,
NetworkState,
TimeSyncMode,
useNetworkStateStore,
} from "@/hooks/stores";
import { useJsonRpc } from "@/hooks/useJsonRpc"; import { useJsonRpc } from "@/hooks/useJsonRpc";
import notifications from "@/notifications"; import notifications from "@/notifications";
import { Button } from "@components/Button"; import { Button, LinkButton } from "@components/Button";
import { GridCard } from "@components/Card"; import { GridCard } from "@components/Card";
import InputField from "@components/InputField"; import InputField from "@components/InputField";
import { SettingsItem } from "./devices.$id.settings";
import dayjs from 'dayjs'; import { SettingsPageHeader } from "../components/SettingsPageheader";
import relativeTime from 'dayjs/plugin/relativeTime'; import { SelectMenuBasic } from "../components/SelectMenuBasic";
import { SettingsItem } from "./devices.$id.settings";
import Fieldset from "../components/Fieldset";
dayjs.extend(relativeTime); dayjs.extend(relativeTime);
@ -25,13 +35,9 @@ const defaultNetworkSettings: NetworkSettings = {
lldp_tx_tlvs: [], lldp_tx_tlvs: [],
mdns_mode: "unknown", mdns_mode: "unknown",
time_sync_mode: "unknown", time_sync_mode: "unknown",
} };
export function LifeTimeLabel({ lifetime }: { lifetime: string }) { export function LifeTimeLabel({ lifetime }: { lifetime: string }) {
if (lifetime == "") {
return <strong>N/A</strong>;
}
const [remaining, setRemaining] = useState<string | null>(null); const [remaining, setRemaining] = useState<string | null>(null);
useEffect(() => { useEffect(() => {
@ -43,23 +49,48 @@ export function LifeTimeLabel({ lifetime }: { lifetime: string }) {
return () => clearInterval(interval); return () => clearInterval(interval);
}, [lifetime]); }, [lifetime]);
return <> return (
<strong>{dayjs(lifetime).format()}</strong> <>
{remaining && <> <span>{dayjs(lifetime).format("YYYY-MM-DD HH:mm")}</span>
{" "}<span className="text-xs text-slate-700 dark:text-slate-300"> {remaining && (
({remaining}) <>
</span> {" "}
</>} <span className="text-xs text-slate-700 dark:text-slate-300">
</> ({remaining})
</span>
</>
)}
</>
);
} }
export default function SettingsNetworkRoute() { export default function SettingsNetworkRoute() {
const [send] = useJsonRpc(); const [send] = useJsonRpc();
const [networkState, setNetworkState] = useNetworkStateStore(state => [state, state.setNetworkState]); const [networkState, setNetworkState] = useNetworkStateStore(state => [
state,
state.setNetworkState,
]);
const [networkSettings, setNetworkSettings] = useState<NetworkSettings>(defaultNetworkSettings); const [networkSettings, setNetworkSettings] =
useState<NetworkSettings>(defaultNetworkSettings);
const [networkSettingsLoaded, setNetworkSettingsLoaded] = useState(false); const [networkSettingsLoaded, setNetworkSettingsLoaded] = useState(false);
const [customDomain, setCustomDomain] = useState<string>("");
const [selectedDomainOption, setSelectedDomainOption] = useState<string>("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(() => { const getNetworkSettings = useCallback(() => {
setNetworkSettingsLoaded(false); setNetworkSettingsLoaded(false);
send("getNetworkSettings", {}, resp => { send("getNetworkSettings", {}, resp => {
@ -70,19 +101,25 @@ export default function SettingsNetworkRoute() {
}); });
}, [send]); }, [send]);
const setNetworkSettingsRemote = useCallback((settings: NetworkSettings) => { const setNetworkSettingsRemote = useCallback(
setNetworkSettingsLoaded(false); (settings: NetworkSettings) => {
send("setNetworkSettings", { settings }, resp => { setNetworkSettingsLoaded(false);
if ("error" in resp) { send("setNetworkSettings", { settings }, resp => {
notifications.error("Failed to save network settings: " + (resp.error.data ? resp.error.data : resp.error.message)); if ("error" in resp) {
notifications.error(
"Failed to save network settings: " +
(resp.error.data ? resp.error.data : resp.error.message),
);
setNetworkSettingsLoaded(true);
return;
}
setNetworkSettings(resp.result as NetworkSettings);
setNetworkSettingsLoaded(true); setNetworkSettingsLoaded(true);
return; notifications.success("Network settings saved");
} });
setNetworkSettings(resp.result as NetworkSettings); },
setNetworkSettingsLoaded(true); [send],
notifications.success("Network settings saved"); );
});
}, [send]);
const getNetworkState = useCallback(() => { const getNetworkState = useCallback(() => {
send("getNetworkState", {}, resp => { send("getNetworkState", {}, resp => {
@ -90,7 +127,7 @@ export default function SettingsNetworkRoute() {
console.log(resp.result); console.log(resp.result);
setNetworkState(resp.result as NetworkState); setNetworkState(resp.result as NetworkState);
}); });
}, [send]); }, [send, setNetworkState]);
const handleRenewLease = useCallback(() => { const handleRenewLease = useCallback(() => {
send("renewDHCPLease", {}, resp => { send("renewDHCPLease", {}, resp => {
@ -116,7 +153,9 @@ export default function SettingsNetworkRoute() {
}; };
const handleLldpModeChange = (value: LLDPMode | string) => { const handleLldpModeChange = (value: LLDPMode | string) => {
setNetworkSettings({ ...networkSettings, lldp_mode: value as LLDPMode }); const newSettings = { ...networkSettings, lldp_mode: value as LLDPMode };
setNetworkSettings(newSettings);
setNetworkSettingsRemote(newSettings);
}; };
// const handleLldpTxTlvsChange = (value: string[]) => { // const handleLldpTxTlvsChange = (value: string[]) => {
@ -124,94 +163,175 @@ export default function SettingsNetworkRoute() {
// }; // };
const handleMdnsModeChange = (value: mDNSMode | string) => { const handleMdnsModeChange = (value: mDNSMode | string) => {
setNetworkSettings({ ...networkSettings, mdns_mode: value as mDNSMode }); const newSettings = { ...networkSettings, mdns_mode: value as mDNSMode };
setNetworkSettings(newSettings);
setNetworkSettingsRemote(newSettings);
}; };
const handleTimeSyncModeChange = (value: TimeSyncMode | string) => { const handleTimeSyncModeChange = (value: TimeSyncMode | string) => {
setNetworkSettings({ ...networkSettings, time_sync_mode: value as TimeSyncMode }); const newSettings = { ...networkSettings, time_sync_mode: value as TimeSyncMode };
setNetworkSettings(newSettings);
setNetworkSettingsRemote(newSettings);
}; };
const filterUnknown = useCallback((options: { value: string; label: string; }[]) => { const handleHostnameChange = (value: string) => {
if (!networkSettingsLoaded) return options; if (value === networkSettings.hostname) return;
return options.filter(option => option.value !== "unknown"); const newSettings = { ...networkSettings, hostname: value };
}, [networkSettingsLoaded]); setNetworkSettings(newSettings);
setNetworkSettingsRemote(newSettings);
};
const handleDomainChange = (value: string) => {
if (value === networkSettings.domain) return;
const newSettings = { ...networkSettings, domain: value };
setNetworkSettings(newSettings);
setNetworkSettingsRemote(newSettings);
};
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],
);
return ( return (
<div className="space-y-4"> <Fieldset disabled={!networkSettingsLoaded} className="space-y-4">
<SettingsPageHeader <SettingsPageHeader title="Network" description="Configure your network settings" />
title="Network"
description="Configure your network settings"
/>
<div className="space-y-4"> <div className="space-y-4">
<SettingsItem <SettingsItem
title="MAC Address" title="MAC Address"
description={<></>} description="Hardware identifier for the network interface"
> >
<span className="select-auto font-mono text-xs text-slate-700 dark:text-slate-300"> <InputField
{networkState?.mac_address} type="text"
</span> size="SM"
value={networkState?.mac_address}
error={""}
disabled={true}
readOnly={true}
className="dark:!text-opacity-60"
/>
</SettingsItem> </SettingsItem>
</div> </div>
<div className="space-y-4"> <div className="space-y-4">
<SettingsItem <SettingsItem
title="Hostname" title="Hostname"
description={ description="Device identifier on the network. Blank for system default"
<>
Hostname for the device
<br />
<span className="text-xs text-slate-700 dark:text-slate-300">
Leave blank for default
</span>
</>
}
> >
<InputField <div className="relative">
type="text" <div>
placeholder="jetkvm" <InputField
value={networkSettings.hostname} size="SM"
error={""} type="text"
onChange={e => { placeholder="jetkvm"
setNetworkSettings({ ...networkSettings, hostname: e.target.value }); defaultValue={networkSettings.hostname}
}} onBlur={e => {
disabled={!networkSettingsLoaded} handleHostnameChange(e.target.value);
/> }}
/>
</div>
</div>
</SettingsItem> </SettingsItem>
</div> </div>
<div className="space-y-4"> <div className="space-y-4">
<SettingsItem <div className="space-y-4">
title="Domain" <SettingsItem title="Domain" description="Network domain suffix for the device">
description={ <div className="space-y-2">
<> <SelectMenuBasic
Domain for the device size="SM"
<br /> value={selectedDomainOption}
<span className="text-xs text-slate-700 dark:text-slate-300"> onChange={e => handleDomainOptionChange(e.target.value)}
Leave blank to use DHCP provided domain, if there is no domain, use <span className="font-mono">local</span> options={[
</span> { value: "dhcp", label: "DHCP provided" },
</> { value: "local", label: ".local" },
} { value: "custom", label: "Custom" },
> ]}
<InputField />
type="text" </div>
placeholder="local" </SettingsItem>
value={networkSettings.domain} {selectedDomainOption === "custom" && (
error={""} <div className="flex items-center justify-between gap-x-2">
onChange={e => { <InputField
setNetworkSettings({ ...networkSettings, domain: e.target.value }); size="SM"
}} type="text"
disabled={!networkSettingsLoaded} placeholder="home"
/> value={customDomain}
</SettingsItem> onChange={e => setCustomDomain(e.target.value)}
/>
<Button
size="SM"
theme="primary"
text="Save Domain"
onClick={() => handleCustomDomainChange(customDomain)}
/>
</div>
)}
</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)}
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);
}}
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> </div>
<div className="h-[1px] w-full bg-slate-800/10 dark:bg-slate-300/20" />
<div className="space-y-4"> <div className="space-y-4">
<SettingsItem <SettingsItem title="IPv4 Mode" description="Configure the IPv4 mode">
title="IPv4 Mode"
description="Configure the IPv4 mode"
>
<SelectMenuBasic <SelectMenuBasic
size="SM" size="SM"
value={networkSettings.ipv4_mode} value={networkSettings.ipv4_mode}
onChange={e => handleIpv4ModeChange(e.target.value)} onChange={e => handleIpv4ModeChange(e.target.value)}
disabled={!networkSettingsLoaded}
options={filterUnknown([ options={filterUnknown([
{ value: "dhcp", label: "DHCP" }, { value: "dhcp", label: "DHCP" },
// { value: "static", label: "Static" }, // { value: "static", label: "Static" },
@ -220,44 +340,199 @@ export default function SettingsNetworkRoute() {
</SettingsItem> </SettingsItem>
{networkState?.dhcp_lease && ( {networkState?.dhcp_lease && (
<GridCard> <GridCard>
<div className="flex items-start gap-x-4 p-4"> <div className="p-4">
<div className="space-y-3 w-full"> <div className="space-y-4">
<div className="space-y-2"> <h3 className="text-base font-bold text-slate-900 dark:text-white">
<h3 className="text-base font-bold text-slate-900 dark:text-white"> Current DHCP Lease
Current DHCP Lease </h3>
</h3>
<div> <div className="flex gap-x-6 gap-y-2">
<ul className="list-none space-y-1 text-xs text-slate-700 dark:text-slate-300"> <div className="flex-1 space-y-2">
{networkState?.dhcp_lease?.ip && <li>IP: <strong>{networkState?.dhcp_lease?.ip}</strong></li>} {networkState?.dhcp_lease?.ip && (
{networkState?.dhcp_lease?.netmask && <li>Subnet: <strong>{networkState?.dhcp_lease?.netmask}</strong></li>} <div className="flex justify-between border-slate-800/10 pt-2 dark:border-slate-300/20">
{networkState?.dhcp_lease?.broadcast && <li>Broadcast: <strong>{networkState?.dhcp_lease?.broadcast}</strong></li>} <span className="text-sm text-slate-600 dark:text-slate-400">
{networkState?.dhcp_lease?.ttl && <li>TTL: <strong>{networkState?.dhcp_lease?.ttl}</strong></li>} IP Address
{networkState?.dhcp_lease?.mtu && <li>MTU: <strong>{networkState?.dhcp_lease?.mtu}</strong></li>} </span>
{networkState?.dhcp_lease?.hostname && <li>Hostname: <strong>{networkState?.dhcp_lease?.hostname}</strong></li>} <span className="text-sm font-medium">
{networkState?.dhcp_lease?.domain && <li>Domain: <strong>{networkState?.dhcp_lease?.domain}</strong></li>} {networkState?.dhcp_lease?.ip}
{networkState?.dhcp_lease?.routers && <li>Gateway: <strong>{networkState?.dhcp_lease?.routers.join(", ")}</strong></li>} </span>
{networkState?.dhcp_lease?.dns && <li>DNS: <strong>{networkState?.dhcp_lease?.dns.join(", ")}</strong></li>} </div>
{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?.netmask && (
{networkState?.dhcp_lease?.bootp_server_name && <li>BootP Server Name: <strong>{networkState?.dhcp_lease?.bootp_server_name}</strong></li>} <div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
{networkState?.dhcp_lease?.bootp_file && <li>Boot File: <strong>{networkState?.dhcp_lease?.bootp_file}</strong></li>} <span className="text-sm text-slate-600 dark:text-slate-400">
{networkState?.dhcp_lease?.lease_expiry && <li> Subnet Mask
Lease Expiry: <LifeTimeLabel lifetime={`${networkState?.dhcp_lease?.lease_expiry}`} /> </span>
</li>} <span className="text-sm font-medium">
{/* {JSON.stringify(networkState?.dhcp_lease)} */} {networkState?.dhcp_lease?.netmask}
</ul> </span>
</div>
)}
{networkState?.dhcp_lease?.dns && (
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
DNS Servers
</span>
<span className="text-right text-sm font-medium">
{networkState?.dhcp_lease?.dns.map(dns => (
<div key={dns}>{dns}</div>
))}
</span>
</div>
)}
{networkState?.dhcp_lease?.broadcast && (
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
Broadcast
</span>
<span className="text-sm font-medium">
{networkState?.dhcp_lease?.broadcast}
</span>
</div>
)}
{networkState?.dhcp_lease?.domain && (
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
Domain
</span>
<span className="text-sm font-medium">
{networkState?.dhcp_lease?.domain}
</span>
</div>
)}
{networkState?.dhcp_lease?.ntp_servers &&
networkState?.dhcp_lease?.ntp_servers.length > 0 && (
<div className="flex justify-between gap-x-8 border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<div className="w-full grow text-sm text-slate-600 dark:text-slate-400">
NTP Servers
</div>
<div className="shrink text-right text-sm font-medium">
{networkState?.dhcp_lease?.ntp_servers.map(server => (
<div key={server}>{server}</div>
))}
</div>
</div>
)}
{networkState?.dhcp_lease?.hostname && (
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
Hostname
</span>
<span className="text-sm font-medium">
{networkState?.dhcp_lease?.hostname}
</span>
</div>
)}
</div>
<div className="flex-1 space-y-2">
{networkState?.dhcp_lease?.routers &&
networkState?.dhcp_lease?.routers.length > 0 && (
<div className="flex justify-between pt-2">
<span className="text-sm text-slate-600 dark:text-slate-400">
Gateway
</span>
<span className="text-right text-sm font-medium">
{networkState?.dhcp_lease?.routers.map(router => (
<div key={router}>{router}</div>
))}
</span>
</div>
)}
{networkState?.dhcp_lease?.server_id && (
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
DHCP Server
</span>
<span className="text-sm font-medium">
{networkState?.dhcp_lease?.server_id}
</span>
</div>
)}
{networkState?.dhcp_lease?.lease_expiry && (
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
Lease Expires
</span>
<span className="text-sm font-medium">
<LifeTimeLabel
lifetime={`${networkState?.dhcp_lease?.lease_expiry}`}
/>
</span>
</div>
)}
{networkState?.dhcp_lease?.mtu && (
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
MTU
</span>
<span className="text-sm font-medium">
{networkState?.dhcp_lease?.mtu}
</span>
</div>
)}
{networkState?.dhcp_lease?.ttl && (
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
TTL
</span>
<span className="text-sm font-medium">
{networkState?.dhcp_lease?.ttl}
</span>
</div>
)}
{networkState?.dhcp_lease?.bootp_next_server && (
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
Boot Next Server
</span>
<span className="text-sm font-medium">
{networkState?.dhcp_lease?.bootp_next_server}
</span>
</div>
)}
{networkState?.dhcp_lease?.bootp_server_name && (
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
Boot Server Name
</span>
<span className="text-sm font-medium">
{networkState?.dhcp_lease?.bootp_server_name}
</span>
</div>
)}
{networkState?.dhcp_lease?.bootp_file && (
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
Boot File
</span>
<span className="text-sm font-medium">
{networkState?.dhcp_lease?.bootp_file}
</span>
</div>
)}
</div> </div>
</div> </div>
<hr className="block w-full dark:border-slate-600" />
<div> <div>
<Button <Button
size="SM" size="SM"
theme="danger" theme="primary"
text="Renew lease" text="Renew lease"
onClick={() => { onClick={handleRenewLease}
handleRenewLease();
}}
/> />
</div> </div>
</div> </div>
@ -266,15 +541,11 @@ export default function SettingsNetworkRoute() {
)} )}
</div> </div>
<div className="space-y-4"> <div className="space-y-4">
<SettingsItem <SettingsItem title="IPv6 Mode" description="Configure the IPv6 mode">
title="IPv6 Mode"
description="Configure the IPv6 mode"
>
<SelectMenuBasic <SelectMenuBasic
size="SM" size="SM"
value={networkSettings.ipv6_mode} value={networkSettings.ipv6_mode}
onChange={e => handleIpv6ModeChange(e.target.value)} onChange={e => handleIpv6ModeChange(e.target.value)}
disabled={!networkSettingsLoaded}
options={filterUnknown([ options={filterUnknown([
// { value: "disabled", label: "Disabled" }, // { value: "disabled", label: "Disabled" },
{ value: "slaac", label: "SLAAC" }, { value: "slaac", label: "SLAAC" },
@ -287,55 +558,96 @@ export default function SettingsNetworkRoute() {
</SettingsItem> </SettingsItem>
{networkState?.ipv6_addresses && ( {networkState?.ipv6_addresses && (
<GridCard> <GridCard>
<div className="flex items-start gap-x-4 p-4"> <div className="p-4">
<div className="space-y-3 w-full"> <div className="space-y-4">
<div className="space-y-2"> <h3 className="text-base font-bold text-slate-900 dark:text-white">
<h3 className="text-base font-bold text-slate-900 dark:text-white"> IPv6 Information
IPv6 Information </h3>
</h3>
<div className="space-y-2"> <div className="grid grid-cols-2 gap-x-6 gap-y-2">
<div> {networkState?.dhcp_lease?.ip && (
<h4 className="text-sm font-bold text-slate-900 dark:text-white"> <div className="flex flex-col justify-between">
IPv6 Link-local <span className="text-sm text-slate-600 dark:text-slate-400">
</h4> Link-local
<p className="text-xs text-slate-700 dark:text-slate-300"> </span>
<span className="text-sm font-medium">
{networkState?.ipv6_link_local} {networkState?.ipv6_link_local}
</p> </span>
</div> </div>
<div> )}
<h4 className="text-sm font-bold text-slate-900 dark:text-white"> </div>
IPv6 Addresses
</h4> <div className="space-y-3 pt-2">
<ul className="list-none space-y-1 text-xs text-slate-700 dark:text-slate-300"> {networkState?.ipv6_addresses &&
{networkState?.ipv6_addresses && networkState?.ipv6_addresses.map(addr => ( networkState?.ipv6_addresses.length > 0 && (
<li key={addr.address}> <div className="space-y-3">
{addr.address} <h4 className="text-sm font-semibold">IPv6 Addresses</h4>
{addr.valid_lifetime && <> {[
<br /> ...networkState.ipv6_addresses,
- valid_lft: {" "} ...networkState.ipv6_addresses,
<span className="text-xs text-slate-700 dark:text-slate-300"> ].map(addr => (
<LifeTimeLabel lifetime={`${addr.valid_lifetime}`} /> <div
</span> key={addr.address}
</>} className="rounded-md rounded-l-none border border-slate-500/10 border-l-blue-700/50 bg-slate-100/40 p-4 pl-4 dark:border-blue-500 dark:bg-slate-900"
{addr.preferred_lifetime && <> >
<br /> <div className="grid grid-cols-2 gap-x-8 gap-y-4">
- pref_lft: {" "} <div className="col-span-2 flex flex-col justify-between">
<span className="text-xs text-slate-700 dark:text-slate-300"> <span className="text-sm text-slate-600 dark:text-slate-400">
<LifeTimeLabel lifetime={`${addr.preferred_lifetime}`} /> Address
</span> </span>
</>} <span className="text-sm font-medium">
</li> {addr.address}
</span>
</div>
{addr.valid_lifetime && (
<div className="flex flex-col justify-between">
<span className="text-sm text-slate-600 dark:text-slate-400">
Valid Lifetime
</span>
<span className="text-sm font-medium">
{addr.valid_lifetime === "" ? (
<span className="text-slate-400 dark:text-slate-600">
N/A
</span>
) : (
<LifeTimeLabel
lifetime={`${addr.valid_lifetime}`}
/>
)}
</span>
</div>
)}
{addr.preferred_lifetime && (
<div className="flex flex-col justify-between">
<span className="text-sm text-slate-600 dark:text-slate-400">
Preferred Lifetime
</span>
<span className="text-sm font-medium">
{addr.preferred_lifetime === "" ? (
<span className="text-slate-400 dark:text-slate-600">
N/A
</span>
) : (
<LifeTimeLabel
lifetime={`${addr.preferred_lifetime}`}
/>
)}
</span>
</div>
)}
</div>
</div>
))} ))}
</ul> </div>
</div> )}
</div>
</div> </div>
</div> </div>
</div> </div>
</GridCard> </GridCard>
)} )}
</div> </div>
<div className="space-y-4 hidden"> <div className="hidden space-y-4">
<SettingsItem <SettingsItem
title="LLDP" title="LLDP"
description="Control which TLVs will be sent over Link Layer Discovery Protocol" description="Control which TLVs will be sent over Link Layer Discovery Protocol"
@ -344,7 +656,6 @@ export default function SettingsNetworkRoute() {
size="SM" size="SM"
value={networkSettings.lldp_mode} value={networkSettings.lldp_mode}
onChange={e => handleLldpModeChange(e.target.value)} onChange={e => handleLldpModeChange(e.target.value)}
disabled={!networkSettingsLoaded}
options={filterUnknown([ options={filterUnknown([
{ value: "disabled", label: "Disabled" }, { value: "disabled", label: "Disabled" },
{ value: "basic", label: "Basic" }, { value: "basic", label: "Basic" },
@ -353,56 +664,6 @@ export default function SettingsNetworkRoute() {
/> />
</SettingsItem> </SettingsItem>
</div> </div>
<div className="space-y-4"> </Fieldset>
<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={() => {
setNetworkSettingsRemote(networkSettings);
}}
size="SM"
theme="light"
text="Save Settings"
/>
</div>
</div>
); );
} }