file structure and comments unified
Some checks failed
Build and Deploy MITM Webserver / build (push) Failing after 3s

This commit is contained in:
2026-03-06 19:03:26 +01:00
parent 7b9a7d3a4b
commit b60a1d3118
20 changed files with 697 additions and 1823 deletions

View File

@@ -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"