cleanup frontend notifications
All checks were successful
Build and Deploy MITM Webserver / build (push) Successful in 1m39s
All checks were successful
Build and Deploy MITM Webserver / build (push) Successful in 1m39s
This commit is contained in:
@@ -1,48 +1,67 @@
|
||||
// src/pages/BridgesManager.tsx
|
||||
import { DeleteOutlined, PlusOutlined, ReloadOutlined } from '@ant-design/icons';
|
||||
import { Button, Col, Form, Input, message, Modal, Popconfirm, Row, Select, Space, Table, Tag, Typography } from 'antd';
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Form,
|
||||
Input,
|
||||
Modal,
|
||||
notification,
|
||||
Popconfirm,
|
||||
Row,
|
||||
Select,
|
||||
Space,
|
||||
Table,
|
||||
Tag,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useBackendAPI } from '../hooks/useBackendAPI';
|
||||
import { createBridge, fetchFullState, removeBridge } from '../api/apiClient';
|
||||
import type { BridgeInfo, InterfaceInfo } from '../types/network';
|
||||
import { FullState } from '../types/network';
|
||||
|
||||
const { Title, Paragraph } = Typography;
|
||||
|
||||
export default function Network() {
|
||||
const {
|
||||
interfacesQuery,
|
||||
bridgesQuery,
|
||||
createBridge,
|
||||
removeBridge,
|
||||
fetchBridges,
|
||||
fetchInterfaces,
|
||||
refreshBridges,
|
||||
refreshInterfaces,
|
||||
isCreating,
|
||||
isRemoving,
|
||||
} = useBackendAPI();
|
||||
|
||||
const [bridgeModalVisible, setBridgeModalVisible] = useState(false);
|
||||
const [bridgeForm] = Form.useForm();
|
||||
const [networkState, setNetworkState] = useState<FullState>();
|
||||
|
||||
const getFullState = (auto: boolean = false) => {
|
||||
fetchFullState()
|
||||
.then((interfaces) => {
|
||||
setNetworkState(interfaces);
|
||||
if (!auto) {
|
||||
notification.success({
|
||||
message: 'Success',
|
||||
description: 'Network state updated.',
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Failed to fetch interfaces:', err);
|
||||
notification.error({
|
||||
message: 'Error',
|
||||
description: 'Failed to fetch network state.',
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// fetch on mount (explicit, since queries are disabled by default)
|
||||
useEffect(() => {
|
||||
fetchInterfaces().catch(() => {});
|
||||
fetchBridges().catch(() => {});
|
||||
getFullState(true);
|
||||
}, []);
|
||||
|
||||
// Table data
|
||||
const interfaces = interfacesQuery.data ?? [];
|
||||
const bridges = bridgesQuery.data ?? [];
|
||||
|
||||
// build select options from interfaces list
|
||||
const interfaceOptions = useMemo(
|
||||
() =>
|
||||
interfaces.map((it: InterfaceInfo) => ({
|
||||
networkState?.interfaces.map((it: InterfaceInfo) => ({
|
||||
label: it.name,
|
||||
value: it.name,
|
||||
})),
|
||||
[interfaces],
|
||||
})) ?? [],
|
||||
[networkState],
|
||||
);
|
||||
|
||||
// Columns for interfaces table (read-only)
|
||||
@@ -97,37 +116,49 @@ export default function Network() {
|
||||
okText="Remove"
|
||||
cancelText="Cancel"
|
||||
>
|
||||
<Button danger icon={<DeleteOutlined />} loading={isRemoving} size="small">
|
||||
Remove
|
||||
</Button>
|
||||
<Button danger icon={<DeleteOutlined />} />
|
||||
</Popconfirm>
|
||||
),
|
||||
},
|
||||
],
|
||||
[isRemoving],
|
||||
[networkState],
|
||||
);
|
||||
async function handleCreateBridge(values: { name: string; interfaces?: string[] }) {
|
||||
const ifaceList = values.interfaces ?? [];
|
||||
createBridge({ name: values.name, interfaces: ifaceList })
|
||||
.then(() => {
|
||||
message.success(`Bridge ${values.name} created`);
|
||||
notification.success({
|
||||
message: 'Success',
|
||||
description: `Bridge ${values.name} created`,
|
||||
});
|
||||
setBridgeModalVisible(false);
|
||||
getFullState(true);
|
||||
bridgeForm.resetFields();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
message.error((err as Error).message ?? 'Failed to create bridge');
|
||||
notification.error({
|
||||
message: 'Error',
|
||||
description: (err as Error).message ?? 'Failed to create bridge',
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function handleRemoveBridge(name: string) {
|
||||
removeBridge({ name })
|
||||
.then(() => {
|
||||
message.success(`Bridge ${name} removed`);
|
||||
notification.success({
|
||||
message: 'Success',
|
||||
description: `Bridge ${name} removed`,
|
||||
});
|
||||
getFullState(true);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
message.error((err as Error).message ?? 'Failed to remove bridge');
|
||||
notification.error({
|
||||
message: 'Error',
|
||||
description: (err as Error).message ?? 'Failed to remove bridge',
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -140,23 +171,10 @@ export default function Network() {
|
||||
</Col>
|
||||
<Col>
|
||||
<Space>
|
||||
<Button
|
||||
icon={<PlusOutlined />}
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
// ensure up-to-date interface list when opening modal
|
||||
refreshInterfaces();
|
||||
setBridgeModalVisible(true);
|
||||
}}
|
||||
>
|
||||
Create bridge
|
||||
</Button>
|
||||
<Button
|
||||
icon={<ReloadOutlined />}
|
||||
onClick={() => {
|
||||
refreshBridges();
|
||||
refreshInterfaces();
|
||||
message.success('Refreshing…');
|
||||
getFullState();
|
||||
}}
|
||||
>
|
||||
Refresh
|
||||
@@ -167,35 +185,41 @@ export default function Network() {
|
||||
|
||||
<Row gutter={16}>
|
||||
<Col span={14}>
|
||||
<div style={{ background: '#fff', padding: 12, borderRadius: 6, minHeight: 220 }}>
|
||||
<Title level={5} style={{ marginBottom: 12 }}>
|
||||
Interfaces ({interfaces.length})
|
||||
</Title>
|
||||
<Card title={`Interfaces (${networkState?.interfaces.length})`} style={{ overflow: 'auto' }}>
|
||||
<Table
|
||||
rowKey={(r: InterfaceInfo) => r.ifindex}
|
||||
dataSource={interfaces}
|
||||
dataSource={networkState?.interfaces ?? []}
|
||||
columns={interfaceColumns}
|
||||
pagination={{ pageSize: 8 }}
|
||||
size="small"
|
||||
loading={interfacesQuery.isFetching || interfacesQuery.isLoading}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
<Col span={10}>
|
||||
<div style={{ background: '#fff', padding: 12, borderRadius: 6, minHeight: 220 }}>
|
||||
<Title level={5} style={{ marginBottom: 12 }}>
|
||||
Bridges ({bridges.length})
|
||||
</Title>
|
||||
<Card
|
||||
title={`Bridges (${networkState?.bridges.length})`}
|
||||
style={{ overflow: 'auto' }}
|
||||
extra={
|
||||
<Button
|
||||
icon={<PlusOutlined />}
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
// ensure up-to-date interface list when opening modal
|
||||
getFullState(true);
|
||||
setBridgeModalVisible(true);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Table
|
||||
rowKey={(r: BridgeInfo) => String(r.ifindex)}
|
||||
dataSource={bridges}
|
||||
dataSource={networkState?.bridges ?? []}
|
||||
columns={bridgeColumns}
|
||||
pagination={{ pageSize: 6 }}
|
||||
size="small"
|
||||
loading={bridgesQuery.isFetching || bridgesQuery.isLoading}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
@@ -207,7 +231,6 @@ export default function Network() {
|
||||
setBridgeModalVisible(false);
|
||||
bridgeForm.resetFields();
|
||||
}}
|
||||
okButtonProps={{ loading: isCreating }}
|
||||
onOk={() => bridgeForm.submit()}
|
||||
>
|
||||
<Form form={bridgeForm} layout="vertical" onFinish={handleCreateBridge}>
|
||||
@@ -219,12 +242,11 @@ export default function Network() {
|
||||
<Form.Item name="interfaces" label="Interfaces (select one or more)">
|
||||
<Select
|
||||
mode="multiple"
|
||||
placeholder={interfaces.length ? 'Select interfaces...' : 'No interfaces available'}
|
||||
placeholder={networkState?.interfaces.length ? 'Select interfaces...' : 'No interfaces available'}
|
||||
options={interfaceOptions}
|
||||
showSearch
|
||||
allowClear
|
||||
loading={interfacesQuery.isFetching || interfacesQuery.isLoading}
|
||||
disabled={interfacesQuery.isFetching || interfacesQuery.isLoading || interfaceOptions.length === 0}
|
||||
disabled={interfaceOptions.length === 0}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
Reference in New Issue
Block a user