mirror of https://github.com/jetkvm/kvm.git
feat(lldp): show neighbors in UI
This commit is contained in:
parent
748bfe5477
commit
cb7da61ab4
|
@ -39,7 +39,7 @@ type testNetworkConfig struct {
|
||||||
IPv6Mode null.String `json:"ipv6_mode" one_of:"slaac,dhcpv6,slaac_and_dhcpv6,static,link_local,disabled" default:"slaac"`
|
IPv6Mode null.String `json:"ipv6_mode" one_of:"slaac,dhcpv6,slaac_and_dhcpv6,static,link_local,disabled" default:"slaac"`
|
||||||
IPv6Static *testIPv6StaticConfig `json:"ipv6_static,omitempty" required_if:"IPv6Mode=static"`
|
IPv6Static *testIPv6StaticConfig `json:"ipv6_static,omitempty" required_if:"IPv6Mode=static"`
|
||||||
|
|
||||||
LLDPMode null.String `json:"lldp_mode,omitempty" one_of:"disabled,basic,all" default:"basic"`
|
LLDPMode null.String `json:"lldp_mode,omitempty" one_of:"disabled,rx_only,tx_only,enabled" default:"enabled"`
|
||||||
LLDPTxTLVs []string `json:"lldp_tx_tlvs,omitempty" one_of:"chassis,port,system,vlan" default:"chassis,port,system,vlan"`
|
LLDPTxTLVs []string `json:"lldp_tx_tlvs,omitempty" one_of:"chassis,port,system,vlan" default:"chassis,port,system,vlan"`
|
||||||
MDNSMode null.String `json:"mdns_mode,omitempty" one_of:"disabled,auto,ipv4_only,ipv6_only" default:"auto"`
|
MDNSMode null.String `json:"mdns_mode,omitempty" one_of:"disabled,auto,ipv4_only,ipv6_only" default:"auto"`
|
||||||
TimeSyncMode null.String `json:"time_sync_mode,omitempty" one_of:"ntp_only,ntp_and_http,http_only,custom" default:"ntp_and_http"`
|
TimeSyncMode null.String `json:"time_sync_mode,omitempty" one_of:"ntp_only,ntp_and_http,http_only,custom" default:"ntp_and_http"`
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package lldp
|
package lldp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/gopacket"
|
"github.com/google/gopacket"
|
||||||
|
@ -16,6 +18,9 @@ type LLDP struct {
|
||||||
l *zerolog.Logger
|
l *zerolog.Logger
|
||||||
tPacket *afpacket.TPacket
|
tPacket *afpacket.TPacket
|
||||||
pktSource *gopacket.PacketSource
|
pktSource *gopacket.PacketSource
|
||||||
|
rxCtx context.Context
|
||||||
|
rxCancel context.CancelFunc
|
||||||
|
rxLock sync.Mutex
|
||||||
|
|
||||||
enableRx bool
|
enableRx bool
|
||||||
enableTx bool
|
enableTx bool
|
||||||
|
@ -53,6 +58,16 @@ func NewLLDP(opts *LLDPOptions) *LLDP {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *LLDP) Start() error {
|
func (l *LLDP) Start() error {
|
||||||
|
l.rxLock.Lock()
|
||||||
|
defer l.rxLock.Unlock()
|
||||||
|
|
||||||
|
if l.rxCtx != nil {
|
||||||
|
l.l.Info().Msg("LLDP already started")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
l.rxCtx, l.rxCancel = context.WithCancel(context.Background())
|
||||||
|
|
||||||
if l.enableRx {
|
if l.enableRx {
|
||||||
l.l.Info().Msg("setting up AF_PACKET")
|
l.l.Info().Msg("setting up AF_PACKET")
|
||||||
if err := l.setUpCapture(); err != nil {
|
if err := l.setUpCapture(); err != nil {
|
||||||
|
@ -66,3 +81,23 @@ func (l *LLDP) Start() error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *LLDP) Stop() error {
|
||||||
|
l.rxLock.Lock()
|
||||||
|
defer l.rxLock.Unlock()
|
||||||
|
|
||||||
|
if l.rxCancel != nil {
|
||||||
|
l.rxCancel()
|
||||||
|
l.rxCancel = nil
|
||||||
|
l.rxCtx = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.enableRx {
|
||||||
|
l.shutdownCapture()
|
||||||
|
}
|
||||||
|
|
||||||
|
l.neighbors.Stop()
|
||||||
|
l.neighbors.DeleteAll()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -51,5 +51,7 @@ func (l *LLDP) GetNeighbors() []Neighbor {
|
||||||
neighbors = append(neighbors, item.Value())
|
neighbors = append(neighbors, item.Value())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
l.l.Info().Interface("neighbors", neighbors).Msg("neighbors")
|
||||||
|
|
||||||
return neighbors
|
return neighbors
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,11 +99,18 @@ func (l *LLDP) startCapture() error {
|
||||||
go func() {
|
go func() {
|
||||||
logger.Info().Msg("starting capture LLDP ethernet frames")
|
logger.Info().Msg("starting capture LLDP ethernet frames")
|
||||||
|
|
||||||
for packet := range l.pktSource.Packets() {
|
for {
|
||||||
|
select {
|
||||||
|
case <-l.rxCtx.Done():
|
||||||
|
logger.Info().Msg("shutting down LLDP capture")
|
||||||
|
return
|
||||||
|
case packet := <-l.pktSource.Packets():
|
||||||
if err := l.handlePacket(packet, &logger); err != nil {
|
if err := l.handlePacket(packet, &logger); err != nil {
|
||||||
logger.Error().Msgf("error handling packet: %s", err)
|
logger.Error().Msgf("error handling packet: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -242,11 +249,13 @@ func (l *LLDP) handlePacketCDP(mac string, raw *layers.CiscoDiscovery, info *lay
|
||||||
|
|
||||||
func (l *LLDP) shutdownCapture() error {
|
func (l *LLDP) shutdownCapture() error {
|
||||||
if l.tPacket != nil {
|
if l.tPacket != nil {
|
||||||
|
l.l.Info().Msg("closing TPacket")
|
||||||
l.tPacket.Close()
|
l.tPacket.Close()
|
||||||
l.tPacket = nil
|
l.tPacket = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if l.pktSource != nil {
|
if l.pktSource != nil {
|
||||||
|
l.l.Info().Msg("closing packet source")
|
||||||
l.pktSource = nil
|
l.pktSource = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ type NetworkConfig struct {
|
||||||
IPv6Mode null.String `json:"ipv6_mode,omitempty" one_of:"slaac,dhcpv6,slaac_and_dhcpv6,static,link_local,disabled" default:"slaac"`
|
IPv6Mode null.String `json:"ipv6_mode,omitempty" one_of:"slaac,dhcpv6,slaac_and_dhcpv6,static,link_local,disabled" default:"slaac"`
|
||||||
IPv6Static *IPv6StaticConfig `json:"ipv6_static,omitempty" required_if:"IPv6Mode=static"`
|
IPv6Static *IPv6StaticConfig `json:"ipv6_static,omitempty" required_if:"IPv6Mode=static"`
|
||||||
|
|
||||||
LLDPMode null.String `json:"lldp_mode,omitempty" one_of:"disabled,basic,all" default:"basic"`
|
LLDPMode null.String `json:"lldp_mode,omitempty" one_of:"disabled,rx_only,tx_only,basic,all,enabled" default:"enabled"`
|
||||||
LLDPTxTLVs []string `json:"lldp_tx_tlvs,omitempty" one_of:"chassis,port,system,vlan" default:"chassis,port,system,vlan"`
|
LLDPTxTLVs []string `json:"lldp_tx_tlvs,omitempty" one_of:"chassis,port,system,vlan" default:"chassis,port,system,vlan"`
|
||||||
MDNSMode null.String `json:"mdns_mode,omitempty" one_of:"disabled,auto,ipv4_only,ipv6_only" default:"auto"`
|
MDNSMode null.String `json:"mdns_mode,omitempty" one_of:"disabled,auto,ipv4_only,ipv6_only" default:"auto"`
|
||||||
TimeSyncMode null.String `json:"time_sync_mode,omitempty" one_of:"ntp_only,ntp_and_http,http_only,custom" default:"ntp_and_http"`
|
TimeSyncMode null.String `json:"time_sync_mode,omitempty" one_of:"ntp_only,ntp_and_http,http_only,custom" default:"ntp_and_http"`
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
package network
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/jetkvm/kvm/internal/lldp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *NetworkInterfaceState) shouldStartLLDP() bool {
|
||||||
|
if s.lldp == nil {
|
||||||
|
s.l.Trace().Msg("LLDP not initialized")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
s.l.Trace().Msgf("LLDP mode: %s", s.config.LLDPMode.String)
|
||||||
|
|
||||||
|
if s.config.LLDPMode.String == "disabled" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *NetworkInterfaceState) startLLDP() {
|
||||||
|
if !s.shouldStartLLDP() || s.lldp == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.l.Trace().Msg("starting LLDP")
|
||||||
|
s.lldp.Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *NetworkInterfaceState) stopLLDP() {
|
||||||
|
if s.lldp == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.l.Trace().Msg("stopping LLDP")
|
||||||
|
s.lldp.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *NetworkInterfaceState) GetLLDPNeighbors() ([]lldp.Neighbor, error) {
|
||||||
|
if s.lldp == nil {
|
||||||
|
return nil, errors.New("lldp not initialized")
|
||||||
|
}
|
||||||
|
return s.lldp.GetNeighbors(), nil
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/jetkvm/kvm/internal/confparser"
|
"github.com/jetkvm/kvm/internal/confparser"
|
||||||
|
"github.com/jetkvm/kvm/internal/lldp"
|
||||||
"github.com/jetkvm/kvm/internal/logging"
|
"github.com/jetkvm/kvm/internal/logging"
|
||||||
"github.com/jetkvm/kvm/internal/udhcpc"
|
"github.com/jetkvm/kvm/internal/udhcpc"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
|
@ -29,6 +30,8 @@ type NetworkInterfaceState struct {
|
||||||
config *NetworkConfig
|
config *NetworkConfig
|
||||||
dhcpClient *udhcpc.DHCPClient
|
dhcpClient *udhcpc.DHCPClient
|
||||||
|
|
||||||
|
lldp *lldp.LLDP
|
||||||
|
|
||||||
defaultHostname string
|
defaultHostname string
|
||||||
currentHostname string
|
currentHostname string
|
||||||
currentFqdn string
|
currentFqdn string
|
||||||
|
@ -96,8 +99,16 @@ func NewNetworkInterfaceState(opts *NetworkInterfaceOptions) (*NetworkInterfaceS
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
s.dhcpClient = dhcpClient
|
// create the lldp service
|
||||||
|
lldpClient := lldp.NewLLDP(&lldp.LLDPOptions{
|
||||||
|
InterfaceName: opts.InterfaceName,
|
||||||
|
EnableRx: true,
|
||||||
|
EnableTx: true,
|
||||||
|
Logger: l,
|
||||||
|
})
|
||||||
|
|
||||||
|
s.dhcpClient = dhcpClient
|
||||||
|
s.lldp = lldpClient
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -310,14 +321,30 @@ func (s *NetworkInterfaceState) update() (DhcpTargetState, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if initialCheck {
|
if initialCheck {
|
||||||
s.onInitialCheck(s)
|
s.handleInitialCheck()
|
||||||
} else if changed {
|
} else if changed {
|
||||||
s.onStateChange(s)
|
s.handleStateChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
return dhcpTargetState, nil
|
return dhcpTargetState, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *NetworkInterfaceState) handleInitialCheck() {
|
||||||
|
if s.IsUp() {
|
||||||
|
s.startLLDP()
|
||||||
|
}
|
||||||
|
s.onInitialCheck(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *NetworkInterfaceState) handleStateChange() {
|
||||||
|
if s.IsUp() {
|
||||||
|
s.startLLDP()
|
||||||
|
} else {
|
||||||
|
s.stopLLDP()
|
||||||
|
}
|
||||||
|
s.onStateChange(s)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *NetworkInterfaceState) CheckAndUpdateDhcp() error {
|
func (s *NetworkInterfaceState) CheckAndUpdateDhcp() error {
|
||||||
dhcpTargetState, err := s.update()
|
dhcpTargetState, err := s.update()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1104,4 +1104,5 @@ var rpcHandlers = map[string]RPCHandler{
|
||||||
"setKeyboardMacros": {Func: setKeyboardMacros, Params: []string{"params"}},
|
"setKeyboardMacros": {Func: setKeyboardMacros, Params: []string{"params"}},
|
||||||
"getLocalLoopbackOnly": {Func: rpcGetLocalLoopbackOnly},
|
"getLocalLoopbackOnly": {Func: rpcGetLocalLoopbackOnly},
|
||||||
"setLocalLoopbackOnly": {Func: rpcSetLocalLoopbackOnly, Params: []string{"enabled"}},
|
"setLocalLoopbackOnly": {Func: rpcSetLocalLoopbackOnly, Params: []string{"enabled"}},
|
||||||
|
"getLLDPNeighbors": {Func: rpcGetLLDPNeighbors},
|
||||||
}
|
}
|
||||||
|
|
14
network.go
14
network.go
|
@ -32,16 +32,6 @@ func networkStateChanged() {
|
||||||
func initNetwork() error {
|
func initNetwork() error {
|
||||||
ensureConfigLoaded()
|
ensureConfigLoaded()
|
||||||
|
|
||||||
lldp := lldp.NewLLDP(&lldp.LLDPOptions{
|
|
||||||
InterfaceName: NetIfName,
|
|
||||||
EnableRx: true,
|
|
||||||
EnableTx: true,
|
|
||||||
Logger: networkLogger,
|
|
||||||
})
|
|
||||||
if err := lldp.Start(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
state, err := network.NewNetworkInterfaceState(&network.NetworkInterfaceOptions{
|
state, err := network.NewNetworkInterfaceState(&network.NetworkInterfaceOptions{
|
||||||
DefaultHostname: GetDefaultHostname(),
|
DefaultHostname: GetDefaultHostname(),
|
||||||
InterfaceName: NetIfName,
|
InterfaceName: NetIfName,
|
||||||
|
@ -116,3 +106,7 @@ func rpcSetNetworkSettings(settings network.RpcNetworkSettings) (*network.RpcNet
|
||||||
func rpcRenewDHCPLease() error {
|
func rpcRenewDHCPLease() error {
|
||||||
return networkState.RpcRenewDHCPLease()
|
return networkState.RpcRenewDHCPLease()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func rpcGetLLDPNeighbors() ([]lldp.Neighbor, error) {
|
||||||
|
return networkState.GetLLDPNeighbors()
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
import { LLDPNeighbor } from "../hooks/stores";
|
||||||
|
import { LifeTimeLabel } from "../routes/devices.$id.settings.network";
|
||||||
|
|
||||||
|
import { GridCard } from "./Card";
|
||||||
|
|
||||||
|
export default function LLDPNeighCard({
|
||||||
|
neighbors,
|
||||||
|
}: {
|
||||||
|
neighbors: LLDPNeighbor[];
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<GridCard>
|
||||||
|
<div className="animate-fadeIn p-4 text-black opacity-0 animation-duration-500 dark:text-white">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="text-base font-bold text-slate-900 dark:text-white">
|
||||||
|
LLDP Neighbors
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div className="space-y-3 pt-2">
|
||||||
|
{neighbors.map(neighbor => (
|
||||||
|
<div className="space-y-3" key={neighbor.mac}>
|
||||||
|
<h4 className="text-sm font-semibold font-mono">{neighbor.mac}</h4>
|
||||||
|
<div
|
||||||
|
className="rounded-md rounded-l-none border border-slate-500/10 border-l-blue-700/50 bg-white p-4 pl-4 backdrop-blur-sm dark:bg-transparent"
|
||||||
|
>
|
||||||
|
<div className="grid grid-cols-2 gap-x-8 gap-y-4">
|
||||||
|
<div className="col-span-2 flex flex-col justify-between">
|
||||||
|
<span className="text-sm text-slate-600 dark:text-slate-400">
|
||||||
|
Interface
|
||||||
|
</span>
|
||||||
|
<span className="text-sm font-medium">{neighbor.port_description}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{neighbor.system_name && (
|
||||||
|
<div className="flex flex-col justify-between">
|
||||||
|
<span className="text-sm text-slate-600 dark:text-slate-400">
|
||||||
|
System Name
|
||||||
|
</span>
|
||||||
|
<span className="text-sm font-medium">{neighbor.system_name}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{neighbor.system_description && (
|
||||||
|
<div className="col-span-2 flex flex-col justify-between">
|
||||||
|
<span className="text-sm text-slate-600 dark:text-slate-400">
|
||||||
|
System Description
|
||||||
|
</span>
|
||||||
|
<span className="text-sm font-medium">{neighbor.system_description}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
{neighbor.port_id && (
|
||||||
|
<div className="flex flex-col justify-between">
|
||||||
|
<span className="text-sm text-slate-600 dark:text-slate-400">
|
||||||
|
Port ID
|
||||||
|
</span>
|
||||||
|
<span className="text-sm font-medium">
|
||||||
|
{neighbor.port_id}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
{neighbor.port_description && (
|
||||||
|
<div className="flex flex-col justify-between">
|
||||||
|
<span className="text-sm text-slate-600 dark:text-slate-400">
|
||||||
|
Port Description
|
||||||
|
</span>
|
||||||
|
<span className="text-sm font-medium">
|
||||||
|
{neighbor.port_description}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</GridCard>
|
||||||
|
);
|
||||||
|
}
|
|
@ -741,7 +741,7 @@ export type IPv6Mode =
|
||||||
| "link_local"
|
| "link_local"
|
||||||
| "unknown";
|
| "unknown";
|
||||||
export type IPv4Mode = "disabled" | "static" | "dhcp" | "unknown";
|
export type IPv4Mode = "disabled" | "static" | "dhcp" | "unknown";
|
||||||
export type LLDPMode = "disabled" | "basic" | "all" | "unknown";
|
export type LLDPMode = "disabled" | "basic" | "all" | "tx_only" | "rx_only" | "unknown";
|
||||||
export type mDNSMode = "disabled" | "auto" | "ipv4_only" | "ipv6_only" | "unknown";
|
export type mDNSMode = "disabled" | "auto" | "ipv4_only" | "ipv6_only" | "unknown";
|
||||||
export type TimeSyncMode =
|
export type TimeSyncMode =
|
||||||
| "ntp_only"
|
| "ntp_only"
|
||||||
|
@ -761,6 +761,19 @@ export interface NetworkSettings {
|
||||||
time_sync_mode: TimeSyncMode;
|
time_sync_mode: TimeSyncMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface LLDPNeighbor {
|
||||||
|
mac: string;
|
||||||
|
source: string;
|
||||||
|
chassis_id: string;
|
||||||
|
port_id: string;
|
||||||
|
port_description: string;
|
||||||
|
system_name: string;
|
||||||
|
system_description: string;
|
||||||
|
ttl: number | null;
|
||||||
|
management_address: string | null;
|
||||||
|
values: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
export const useNetworkStateStore = create<NetworkState>((set, get) => ({
|
export const useNetworkStateStore = create<NetworkState>((set, get) => ({
|
||||||
setNetworkState: (state: NetworkState) => set(state),
|
setNetworkState: (state: NetworkState) => set(state),
|
||||||
setDhcpLease: (lease: NetworkState["dhcp_lease"]) => set({ dhcp_lease: lease }),
|
setDhcpLease: (lease: NetworkState["dhcp_lease"]) => set({ dhcp_lease: lease }),
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
IPv4Mode,
|
IPv4Mode,
|
||||||
IPv6Mode,
|
IPv6Mode,
|
||||||
LLDPMode,
|
LLDPMode,
|
||||||
|
LLDPNeighbor,
|
||||||
mDNSMode,
|
mDNSMode,
|
||||||
NetworkSettings,
|
NetworkSettings,
|
||||||
NetworkState,
|
NetworkState,
|
||||||
|
@ -29,6 +30,7 @@ import AutoHeight from "../components/AutoHeight";
|
||||||
import DhcpLeaseCard from "../components/DhcpLeaseCard";
|
import DhcpLeaseCard from "../components/DhcpLeaseCard";
|
||||||
|
|
||||||
import { SettingsItem } from "./devices.$id.settings";
|
import { SettingsItem } from "./devices.$id.settings";
|
||||||
|
import LLDPNeighCard from "../components/LLDPNeighCard";
|
||||||
|
|
||||||
dayjs.extend(relativeTime);
|
dayjs.extend(relativeTime);
|
||||||
|
|
||||||
|
@ -88,6 +90,14 @@ export default function SettingsNetworkRoute() {
|
||||||
const [customDomain, setCustomDomain] = useState<string>("");
|
const [customDomain, setCustomDomain] = useState<string>("");
|
||||||
const [selectedDomainOption, setSelectedDomainOption] = useState<string>("dhcp");
|
const [selectedDomainOption, setSelectedDomainOption] = useState<string>("dhcp");
|
||||||
|
|
||||||
|
const [lldpNeighbors, setLldpNeighbors] = useState<LLDPNeighbor[] | undefined>(undefined);
|
||||||
|
useEffect(() => {
|
||||||
|
send("getLLDPNeighbors", {}, resp => {
|
||||||
|
if ("error" in resp) return;
|
||||||
|
setLldpNeighbors(resp.result as LLDPNeighbor[]);
|
||||||
|
});
|
||||||
|
}, [send]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (networkSettings.domain && networkSettingsLoaded) {
|
if (networkSettings.domain && networkSettingsLoaded) {
|
||||||
// Check if the domain is one of the predefined options
|
// Check if the domain is one of the predefined options
|
||||||
|
@ -428,22 +438,49 @@ export default function SettingsNetworkRoute() {
|
||||||
)}
|
)}
|
||||||
</AutoHeight>
|
</AutoHeight>
|
||||||
</div>
|
</div>
|
||||||
<div className="hidden space-y-4">
|
|
||||||
<SettingsItem
|
<div className="space-y-4">
|
||||||
title="LLDP"
|
<SettingsItem title="LLDP" description="Configure the LLDP mode">
|
||||||
description="Control which TLVs will be sent over Link Layer Discovery Protocol"
|
|
||||||
>
|
|
||||||
<SelectMenuBasic
|
<SelectMenuBasic
|
||||||
size="SM"
|
size="SM"
|
||||||
value={networkSettings.lldp_mode}
|
value={networkSettings.lldp_mode}
|
||||||
onChange={e => handleLldpModeChange(e.target.value)}
|
onChange={e => handleLldpModeChange(e.target.value)}
|
||||||
options={filterUnknown([
|
options={filterUnknown([
|
||||||
{ value: "disabled", label: "Disabled" },
|
{ value: "disabled", label: "Disabled" },
|
||||||
{ value: "basic", label: "Basic" },
|
{ value: "tx_only", label: "Tx only" },
|
||||||
{ value: "all", label: "All" },
|
{ value: "rx_only", label: "Rx only" },
|
||||||
|
{ value: "basic", label: "Tx Minimal + Rx" },
|
||||||
|
{ value: "all", label: "Tx Detailed + Rx" },
|
||||||
|
{ value: "enabled", label: "Enabled" },
|
||||||
])}
|
])}
|
||||||
/>
|
/>
|
||||||
</SettingsItem>
|
</SettingsItem>
|
||||||
|
<AutoHeight>
|
||||||
|
{lldpNeighbors === undefined ? (
|
||||||
|
<GridCard>
|
||||||
|
<div className="p-4">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="text-base font-bold text-slate-900 dark:text-white">
|
||||||
|
LLDP Neighbors
|
||||||
|
</h3>
|
||||||
|
<div className="animate-pulse space-y-3">
|
||||||
|
<div className="h-4 w-1/3 rounded bg-slate-200 dark:bg-slate-700" />
|
||||||
|
<div className="h-4 w-1/2 rounded bg-slate-200 dark:bg-slate-700" />
|
||||||
|
<div className="h-4 w-1/3 rounded bg-slate-200 dark:bg-slate-700" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</GridCard>
|
||||||
|
) : lldpNeighbors.length > 0 ? (
|
||||||
|
<LLDPNeighCard neighbors={lldpNeighbors} />
|
||||||
|
) : (
|
||||||
|
<EmptyCard
|
||||||
|
IconElm={LuEthernetPort}
|
||||||
|
headline="LLDP Neighbors"
|
||||||
|
description="No LLDP neighbors found"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</AutoHeight>
|
||||||
</div>
|
</div>
|
||||||
</Fieldset>
|
</Fieldset>
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
|
|
Loading…
Reference in New Issue