websockets and network interface datat added
This commit is contained in:
@@ -82,6 +82,13 @@ export const fetchFullState = async (): Promise<FullState> => {
|
||||
return res.data;
|
||||
};
|
||||
|
||||
export const getNetworkStateWebSocketUrl = (): string => {
|
||||
const url = new URL(BASE);
|
||||
url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
url.pathname = `${url.pathname.replace(/\/$/, '')}/network/ws/state`;
|
||||
return url.toString();
|
||||
};
|
||||
|
||||
export const createBridge = async (req: BridgeCreateRequest) => {
|
||||
const res = await api.post('/network/bridge/create', req);
|
||||
return res.data;
|
||||
@@ -92,11 +99,6 @@ export const removeBridge = async (req: BridgeRemoveRequest) => {
|
||||
return res.data;
|
||||
};
|
||||
|
||||
export const fetchBridgeLinkStateWatchers = async (): Promise<BridgeLinkStateWatcherStatus[]> => {
|
||||
const res = await api.get<BridgeLinkStateWatcherStatus[]>('/network/bridge/link-state-watchers');
|
||||
return res.data;
|
||||
};
|
||||
|
||||
export const enableBridgeLinkStateWatcher = async (
|
||||
bridgeName: string,
|
||||
req: BridgeLinkStateEnableRequest,
|
||||
|
||||
@@ -24,11 +24,12 @@ import {
|
||||
createBridge,
|
||||
disableBridgeLinkStateWatcher,
|
||||
enableBridgeLinkStateWatcher,
|
||||
fetchBridgeLinkStateWatchers,
|
||||
fetchFullState,
|
||||
getNetworkStateWebSocketUrl,
|
||||
removeBridge,
|
||||
} from '../api/apiClient';
|
||||
import type {
|
||||
EthernetProfile,
|
||||
BridgeInfo,
|
||||
BridgeLinkStateWatcherStatus,
|
||||
FullState,
|
||||
@@ -48,15 +49,10 @@ export default function Network() {
|
||||
const [watcherBusyBridge, setWatcherBusyBridge] = useState<string>();
|
||||
|
||||
const getNetworkPageState = (silent = false) => {
|
||||
Promise.all([fetchFullState(), fetchBridgeLinkStateWatchers()])
|
||||
.then(([state, watchers]) => {
|
||||
fetchFullState()
|
||||
.then((state) => {
|
||||
setNetworkState(state);
|
||||
setWatcherStatuses(
|
||||
watchers.reduce<Record<string, BridgeLinkStateWatcherStatus>>((acc, watcher) => {
|
||||
acc[watcher.bridge] = watcher;
|
||||
return acc;
|
||||
}, {}),
|
||||
);
|
||||
setWatcherStatuses(indexWatchers(state.watchers ?? []));
|
||||
if (!silent) {
|
||||
notification.success({
|
||||
message: 'Success',
|
||||
@@ -73,26 +69,70 @@ export default function Network() {
|
||||
});
|
||||
};
|
||||
|
||||
function indexWatchers(watchers: BridgeLinkStateWatcherStatus[]) {
|
||||
return watchers.reduce<Record<string, BridgeLinkStateWatcherStatus>>((acc, watcher) => {
|
||||
acc[watcher.bridge] = watcher;
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function applySnapshot(state: FullState) {
|
||||
setNetworkState(state);
|
||||
setWatcherStatuses(indexWatchers(state.watchers ?? []));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getNetworkPageState(true);
|
||||
}, []);
|
||||
|
||||
const hasActiveWatchers = useMemo(
|
||||
() => Object.values(watcherStatuses).some((watcher) => watcher.active),
|
||||
[watcherStatuses],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasActiveWatchers) {
|
||||
return undefined;
|
||||
let reconnectTimer: number | undefined;
|
||||
let socket: WebSocket | undefined;
|
||||
let cancelled = false;
|
||||
|
||||
const connect = () => {
|
||||
socket = new WebSocket(getNetworkStateWebSocketUrl());
|
||||
|
||||
socket.onmessage = (event) => {
|
||||
try {
|
||||
const payload = JSON.parse(event.data);
|
||||
if (payload?.type === 'network_state' && payload.snapshot) {
|
||||
applySnapshot(payload.snapshot as FullState);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to parse network websocket payload:', error);
|
||||
}
|
||||
};
|
||||
|
||||
socket.onclose = () => {
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
reconnectTimer = window.setTimeout(connect, 2000);
|
||||
};
|
||||
};
|
||||
|
||||
connect();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
if (reconnectTimer) {
|
||||
window.clearTimeout(reconnectTimer);
|
||||
}
|
||||
socket?.close();
|
||||
};
|
||||
}, []);
|
||||
|
||||
function renderEthernetProfile(profile?: EthernetProfile | null) {
|
||||
if (!profile) {
|
||||
return '—';
|
||||
}
|
||||
|
||||
const intervalId = window.setInterval(() => {
|
||||
getNetworkPageState(true);
|
||||
}, 2000);
|
||||
|
||||
return () => window.clearInterval(intervalId);
|
||||
}, [hasActiveWatchers]);
|
||||
const speed = profile.speed_mbps ? `${profile.speed_mbps} Mb/s` : 'speed ?';
|
||||
const duplex = profile.duplex ?? 'duplex ?';
|
||||
const autoneg = typeof profile.autoneg === 'boolean' ? (profile.autoneg ? 'autoneg on' : 'autoneg off') : 'autoneg ?';
|
||||
return `${speed}, ${duplex}, ${autoneg}`;
|
||||
}
|
||||
|
||||
const interfaceOptions = useMemo(
|
||||
() =>
|
||||
@@ -129,6 +169,13 @@ export default function Network() {
|
||||
width: 100,
|
||||
render: (value: boolean | null | undefined) => <Tag color={value ? 'green' : 'default'}>{value ? 'up' : 'down'}</Tag>,
|
||||
},
|
||||
{ title: 'MTU', dataIndex: 'mtu', key: 'mtu', width: 90, render: (value: number | null | undefined) => value ?? '—' },
|
||||
{
|
||||
title: 'Link',
|
||||
dataIndex: 'ethernet_profile',
|
||||
key: 'ethernet_profile',
|
||||
render: (value: EthernetProfile | null | undefined) => renderEthernetProfile(value),
|
||||
},
|
||||
{
|
||||
title: 'Operstate',
|
||||
dataIndex: 'operstate',
|
||||
@@ -157,6 +204,12 @@ export default function Network() {
|
||||
{ title: 'State', dataIndex: 'state', key: 'state', render: (state) => <Tag>{state}</Tag> },
|
||||
{ title: 'MAC', dataIndex: 'mac', key: 'mac', render: (mac) => mac ?? '—' },
|
||||
{ title: 'MTU', dataIndex: 'mtu', key: 'mtu', width: 90 },
|
||||
{
|
||||
title: 'Link',
|
||||
dataIndex: 'ethernet_profile',
|
||||
key: 'ethernet_profile',
|
||||
render: (value: EthernetProfile | null | undefined) => renderEthernetProfile(value),
|
||||
},
|
||||
{
|
||||
title: 'Addresses',
|
||||
dataIndex: 'addresses',
|
||||
@@ -435,6 +488,9 @@ export default function Network() {
|
||||
<Descriptions size="small" column={4} style={{ marginBottom: members.length ? 12 : 0 }}>
|
||||
<Descriptions.Item label="Members">{bridge.members.map((member) => member.ifname).join(', ') || '—'}</Descriptions.Item>
|
||||
<Descriptions.Item label="Mode">{watcher?.event_driven ? 'netlink events' : '—'}</Descriptions.Item>
|
||||
<Descriptions.Item label="Failure holdoff">
|
||||
{watcher?.failure_holdoff_seconds ? `${watcher.failure_holdoff_seconds}s` : '—'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="Recovery holdoff">
|
||||
{watcher?.recovery_holdoff_seconds ? `${watcher.recovery_holdoff_seconds}s` : '—'}
|
||||
</Descriptions.Item>
|
||||
|
||||
@@ -4,6 +4,12 @@ export interface InterfaceAddress {
|
||||
prefixlen: number;
|
||||
}
|
||||
|
||||
export interface EthernetProfile {
|
||||
speed_mbps?: number | null;
|
||||
duplex?: string | null;
|
||||
autoneg?: boolean | null;
|
||||
}
|
||||
|
||||
export interface InterfaceInfo {
|
||||
ifindex: number;
|
||||
name: string;
|
||||
@@ -11,6 +17,7 @@ export interface InterfaceInfo {
|
||||
mac?: string | null;
|
||||
mtu: number;
|
||||
flags: string[];
|
||||
ethernet_profile?: EthernetProfile | null;
|
||||
addresses: InterfaceAddress[];
|
||||
}
|
||||
|
||||
@@ -31,6 +38,7 @@ export interface BridgeInterfaceInfo {
|
||||
ifname: string;
|
||||
state?: string | null;
|
||||
mtu?: number | null;
|
||||
ethernet_profile?: EthernetProfile | null;
|
||||
}
|
||||
|
||||
export interface BridgeInfo {
|
||||
@@ -61,11 +69,7 @@ export interface BridgeMemberLinkStateInfo {
|
||||
carrier_up?: boolean | null;
|
||||
operstate?: string | null;
|
||||
mtu?: number | null;
|
||||
ethernet_profile?: {
|
||||
speed_mbps?: number | null;
|
||||
duplex?: string | null;
|
||||
autoneg?: boolean | null;
|
||||
} | null;
|
||||
ethernet_profile?: EthernetProfile | null;
|
||||
link_ready: boolean;
|
||||
suppressed: boolean;
|
||||
}
|
||||
@@ -78,6 +82,7 @@ export interface BridgeLinkStateWatcherStatus {
|
||||
last_error?: string | null;
|
||||
last_action?: string | null;
|
||||
suppressed_members: string[];
|
||||
failure_holdoff_seconds?: number | null;
|
||||
recovery_holdoff_seconds?: number | null;
|
||||
degraded_recheck_seconds?: number | null;
|
||||
members: Record<string, BridgeMemberLinkStateInfo>;
|
||||
@@ -89,4 +94,5 @@ export interface FullState {
|
||||
interfaces: InterfaceInfo[];
|
||||
routes: RouteInfo[];
|
||||
bridges: BridgeInfo[];
|
||||
watchers: BridgeLinkStateWatcherStatus[];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user