file structure and comments unified
Some checks failed
Build and Deploy MITM Webserver / build (push) Failing after 3s
Some checks failed
Build and Deploy MITM Webserver / build (push) Failing after 3s
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
// src/pages/BridgesManager.tsx
|
||||
import { DeleteOutlined, PlusOutlined, ReloadOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
Button,
|
||||
@@ -18,9 +17,9 @@ import {
|
||||
} from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { createBridge, fetchFullState, removeBridge } from '../api/apiClient';
|
||||
import type { BridgeInfo, InterfaceInfo } from '../types/network';
|
||||
import { FullState } from '../types/network';
|
||||
import type { BridgeInfo, FullState, InterfaceInfo } from '../types/network';
|
||||
|
||||
const { Title, Paragraph } = Typography;
|
||||
|
||||
@@ -29,19 +28,19 @@ export default function Network() {
|
||||
const [bridgeForm] = Form.useForm();
|
||||
const [networkState, setNetworkState] = useState<FullState>();
|
||||
|
||||
const getFullState = (auto: boolean = false) => {
|
||||
const getFullState = (silent = false) => {
|
||||
fetchFullState()
|
||||
.then((interfaces) => {
|
||||
setNetworkState(interfaces);
|
||||
if (!auto) {
|
||||
.then((state) => {
|
||||
setNetworkState(state);
|
||||
if (!silent) {
|
||||
notification.success({
|
||||
message: 'Success',
|
||||
description: 'Network state updated.',
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Failed to fetch interfaces:', err);
|
||||
.catch((error) => {
|
||||
console.error('Failed to fetch network state:', error);
|
||||
notification.error({
|
||||
message: 'Error',
|
||||
description: 'Failed to fetch network state.',
|
||||
@@ -49,39 +48,36 @@ export default function Network() {
|
||||
});
|
||||
};
|
||||
|
||||
// fetch on mount (explicit, since queries are disabled by default)
|
||||
useEffect(() => {
|
||||
getFullState(true);
|
||||
}, []);
|
||||
|
||||
// build select options from interfaces list
|
||||
const interfaceOptions = useMemo(
|
||||
() =>
|
||||
networkState?.interfaces.map((it: InterfaceInfo) => ({
|
||||
label: it.name,
|
||||
value: it.name,
|
||||
networkState?.interfaces.map((iface: InterfaceInfo) => ({
|
||||
label: iface.name,
|
||||
value: iface.name,
|
||||
})) ?? [],
|
||||
[networkState],
|
||||
);
|
||||
|
||||
// Columns for interfaces table (read-only)
|
||||
const interfaceColumns: ColumnsType<InterfaceInfo> = useMemo(
|
||||
() => [
|
||||
{ title: 'IfIndex', dataIndex: 'ifindex', key: 'ifindex', width: 90 },
|
||||
{ title: 'Name', dataIndex: 'name', key: 'name' },
|
||||
{ title: 'State', dataIndex: 'state', key: 'state', render: (s) => <Tag>{s}</Tag> },
|
||||
{ title: 'MAC', dataIndex: 'mac', key: 'mac', render: (m) => m ?? '—' },
|
||||
{ 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: 'Addresses',
|
||||
dataIndex: 'addresses',
|
||||
key: 'addresses',
|
||||
render: (addrs: any[]) =>
|
||||
addrs?.length ? (
|
||||
<Space orientation="vertical">
|
||||
{addrs.map((a) => (
|
||||
<span key={`${a.address}/${a.prefixlen}`}>
|
||||
{a.address}/{a.prefixlen} ({a.family})
|
||||
render: (addresses: any[]) =>
|
||||
addresses?.length ? (
|
||||
<Space direction="vertical">
|
||||
{addresses.map((address) => (
|
||||
<span key={`${address.address}/${address.prefixlen}`}>
|
||||
{address.address}/{address.prefixlen} ({address.family})
|
||||
</span>
|
||||
))}
|
||||
</Space>
|
||||
@@ -93,23 +89,22 @@ export default function Network() {
|
||||
[],
|
||||
);
|
||||
|
||||
// Columns for bridges table (with remove action)
|
||||
const bridgeColumns: ColumnsType<BridgeInfo> = useMemo(
|
||||
() => [
|
||||
{ title: 'IfIndex', dataIndex: 'ifindex', key: 'ifindex', width: 90 },
|
||||
{ title: 'Name', dataIndex: 'ifname', key: 'ifname' },
|
||||
{ title: 'State', dataIndex: 'state', key: 'state', render: (s) => <Tag>{s ?? '—'}</Tag> },
|
||||
{ title: 'State', dataIndex: 'state', key: 'state', render: (state) => <Tag>{state ?? '—'}</Tag> },
|
||||
{
|
||||
title: 'Members',
|
||||
dataIndex: 'members',
|
||||
key: 'members',
|
||||
render: (members: any[]) => (members?.length ? members.map((m) => m.ifname).join(', ') : '—'),
|
||||
render: (members: any[]) => (members?.length ? members.map((member) => member.ifname).join(', ') : '—'),
|
||||
},
|
||||
{
|
||||
title: 'Actions',
|
||||
key: 'actions',
|
||||
width: 140,
|
||||
render: (_: any, record: BridgeInfo) => (
|
||||
render: (_, record: BridgeInfo) => (
|
||||
<Popconfirm
|
||||
title={`Remove bridge ${record.ifname}?`}
|
||||
onConfirm={() => handleRemoveBridge(record.ifname)}
|
||||
@@ -121,11 +116,12 @@ export default function Network() {
|
||||
),
|
||||
},
|
||||
],
|
||||
[networkState],
|
||||
[],
|
||||
);
|
||||
async function handleCreateBridge(values: { name: string; interfaces?: string[] }) {
|
||||
const ifaceList = values.interfaces ?? [];
|
||||
createBridge({ name: values.name, interfaces: ifaceList })
|
||||
|
||||
function handleCreateBridge(values: { name: string; interfaces?: string[] }) {
|
||||
const interfaces = values.interfaces ?? [];
|
||||
createBridge({ name: values.name, interfaces })
|
||||
.then(() => {
|
||||
notification.success({
|
||||
message: 'Success',
|
||||
@@ -135,11 +131,11 @@ export default function Network() {
|
||||
getFullState(true);
|
||||
bridgeForm.resetFields();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
notification.error({
|
||||
message: 'Error',
|
||||
description: (err as Error).message ?? 'Failed to create bridge',
|
||||
description: (error as Error).message ?? 'Failed to create bridge',
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -153,11 +149,11 @@ export default function Network() {
|
||||
});
|
||||
getFullState(true);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
notification.error({
|
||||
message: 'Error',
|
||||
description: (err as Error).message ?? 'Failed to remove bridge',
|
||||
description: (error as Error).message ?? 'Failed to remove bridge',
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -166,28 +162,21 @@ export default function Network() {
|
||||
<div style={{ padding: 16 }}>
|
||||
<Row justify="space-between" align="middle" style={{ marginBottom: 12 }}>
|
||||
<Col>
|
||||
<Title level={2}> Network Management</Title>
|
||||
<Title level={2}>Network Management</Title>
|
||||
<Paragraph type="secondary">View system interfaces and manage network bridges.</Paragraph>
|
||||
</Col>
|
||||
<Col>
|
||||
<Space>
|
||||
<Button
|
||||
icon={<ReloadOutlined />}
|
||||
onClick={() => {
|
||||
getFullState();
|
||||
}}
|
||||
>
|
||||
Refresh
|
||||
</Button>
|
||||
</Space>
|
||||
<Button icon={<ReloadOutlined />} onClick={() => getFullState()}>
|
||||
Refresh
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row gutter={16}>
|
||||
<Col span={14}>
|
||||
<Card title={`Interfaces (${networkState?.interfaces.length})`} style={{ overflow: 'auto' }}>
|
||||
<Card title={`Interfaces (${networkState?.interfaces.length ?? 0})`} style={{ overflow: 'auto' }}>
|
||||
<Table
|
||||
rowKey={(r: InterfaceInfo) => r.ifindex}
|
||||
rowKey={(row: InterfaceInfo) => row.ifindex}
|
||||
dataSource={networkState?.interfaces ?? []}
|
||||
columns={interfaceColumns}
|
||||
pagination={{ pageSize: 8 }}
|
||||
@@ -198,14 +187,13 @@ export default function Network() {
|
||||
|
||||
<Col span={10}>
|
||||
<Card
|
||||
title={`Bridges (${networkState?.bridges.length})`}
|
||||
title={`Bridges (${networkState?.bridges.length ?? 0})`}
|
||||
style={{ overflow: 'auto' }}
|
||||
extra={
|
||||
<Button
|
||||
icon={<PlusOutlined />}
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
// ensure up-to-date interface list when opening modal
|
||||
getFullState(true);
|
||||
setBridgeModalVisible(true);
|
||||
}}
|
||||
@@ -213,7 +201,7 @@ export default function Network() {
|
||||
}
|
||||
>
|
||||
<Table
|
||||
rowKey={(r: BridgeInfo) => String(r.ifindex)}
|
||||
rowKey={(row: BridgeInfo) => String(row.ifindex)}
|
||||
dataSource={networkState?.bridges ?? []}
|
||||
columns={bridgeColumns}
|
||||
pagination={{ pageSize: 6 }}
|
||||
@@ -223,7 +211,6 @@ export default function Network() {
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{/* Create Bridge Modal */}
|
||||
<Modal
|
||||
title="Create Bridge"
|
||||
open={bridgeModalVisible}
|
||||
@@ -238,7 +225,6 @@ export default function Network() {
|
||||
<Input placeholder="e.g. br0" />
|
||||
</Form.Item>
|
||||
|
||||
{/* Select field populated from interfaces endpoint */}
|
||||
<Form.Item name="interfaces" label="Interfaces (select one or more)">
|
||||
<Select
|
||||
mode="multiple"
|
||||
|
||||
Reference in New Issue
Block a user