improver error handling
All checks were successful
Build and Deploy MITM Webserver / build (push) Successful in 8s

This commit is contained in:
2025-12-18 21:45:43 +01:00
parent 9afe49857a
commit a2a017f900
7 changed files with 52 additions and 38 deletions

View File

@@ -353,7 +353,7 @@ def full_state(
@router.post("/bridge/create") @router.post("/bridge/create")
def create_bridge(req: BridgeCreateRequest, ip: IPRoute = Depends(get_iproute)): def create_bridge(req: BridgeCreateRequest, ip: IPRoute = Depends(get_iproute)):
if bridge_exists(req.name, ip): if bridge_exists(req.name, ip):
raise HTTPException(400, f"Bridge {req.name} already exists") raise HTTPException(400, detail=f"Bridge {req.name} already exists")
# Bridge erzeugen # Bridge erzeugen
ip.link("add", ifname=req.name, kind="bridge") ip.link("add", ifname=req.name, kind="bridge")

View File

@@ -4,10 +4,3 @@
padding: 2rem; padding: 2rem;
text-align: center; text-align: center;
} }
.drawer-pages {
display: flex;
justify-content: flex-start;
align-items: center;
gap: 10px;
}

View File

@@ -9,10 +9,10 @@ import '../theme/layout.less';
const { Sider } = Layout; const { Sider } = Layout;
const menuItems = [ const menuItems = [
{ key: PATHS.HOME, icon: <HomeOutlined />, label: 'Home' }, { key: PATHS.HOME, icon: <HomeOutlined style={{ fontSize: '18px' }} />, label: 'Home' },
{ key: PATHS.NETWORK, icon: <ApartmentOutlined />, label: 'Network' }, { key: PATHS.NETWORK, icon: <ApartmentOutlined style={{ fontSize: '18px' }} />, label: 'Network' },
{ key: '/about', icon: <InfoCircleOutlined />, label: 'About' }, { key: '/about', icon: <InfoCircleOutlined style={{ fontSize: '18px' }} />, label: 'About' },
{ key: '/settings', icon: <SettingOutlined />, label: 'Settings' }, { key: '/settings', icon: <SettingOutlined style={{ fontSize: '18px' }} />, label: 'Settings' },
]; ];
interface SidebarProps { interface SidebarProps {

View File

@@ -17,6 +17,22 @@ export const api = axios.create({
timeout: 10000, timeout: 10000,
}); });
// 🔹 Normalize FastAPI errors here
api.interceptors.response.use(
(response) => response,
(error) => {
// FastAPI HTTPException format
const detail =
error?.response?.data?.detail ??
error?.response?.data?.message ??
error.message ??
"Unknown error";
// Always reject with a standard Error
return Promise.reject(new Error(detail));
}
);
// Queries // Queries
export const fetchInterfaces = async (): Promise<InterfaceInfo[]> => { export const fetchInterfaces = async (): Promise<InterfaceInfo[]> => {
const res = await api.get<InterfaceInfo[]>("/network/interfaces"); const res = await api.get<InterfaceInfo[]>("/network/interfaces");

View File

@@ -1,8 +1,8 @@
// src/AppRouter.tsx // src/AppRouter.tsx
import { Navigate, Route, Routes } from 'react-router-dom'; import { Navigate, Route, Routes } from 'react-router-dom';
import App from './App'; // your layout component (has <Outlet />) import App from './App'; // your layout component (has <Outlet />)
import Bridges from './pages/Bridges';
import Home from './pages/Home'; import Home from './pages/Home';
import Network from './pages/Network';
import { PATHS } from './routes'; import { PATHS } from './routes';
export default function AppRouter() { export default function AppRouter() {
@@ -15,7 +15,7 @@ export default function AppRouter() {
{/* Child routes - these render inside App's <Outlet /> */} {/* Child routes - these render inside App's <Outlet /> */}
<Route path={PATHS.HOME.slice(1)} element={<Home />} /> <Route path={PATHS.HOME.slice(1)} element={<Home />} />
<Route path={PATHS.NETWORK.slice(1)} element={<Bridges />} /> <Route path={PATHS.NETWORK.slice(1)} element={<Network />} />
{/* Fallback (renders inside layout too) */} {/* Fallback (renders inside layout too) */}
<Route path="*" element={<NotFound />} /> <Route path="*" element={<NotFound />} />

View File

@@ -8,7 +8,7 @@ import type { BridgeInfo, InterfaceInfo } from '../types/network';
const { Title, Paragraph } = Typography; const { Title, Paragraph } = Typography;
export default function NetworkManager() { export default function Network() {
const { const {
interfacesQuery, interfacesQuery,
bridgesQuery, bridgesQuery,
@@ -59,7 +59,7 @@ export default function NetworkManager() {
key: 'addresses', key: 'addresses',
render: (addrs: any[]) => render: (addrs: any[]) =>
addrs?.length ? ( addrs?.length ? (
<Space direction="vertical"> <Space orientation="vertical">
{addrs.map((a) => ( {addrs.map((a) => (
<span key={`${a.address}/${a.prefixlen}`}> <span key={`${a.address}/${a.prefixlen}`}>
{a.address}/{a.prefixlen} ({a.family}) {a.address}/{a.prefixlen} ({a.family})
@@ -79,7 +79,7 @@ export default function NetworkManager() {
() => [ () => [
{ title: 'IfIndex', dataIndex: 'ifindex', key: 'ifindex', width: 90 }, { title: 'IfIndex', dataIndex: 'ifindex', key: 'ifindex', width: 90 },
{ title: 'Name', dataIndex: 'ifname', key: 'ifname' }, { title: 'Name', dataIndex: 'ifname', key: 'ifname' },
{ title: 'State', dataIndex: 'state', key: 'state', render: (s) => s ?? '—' }, { title: 'State', dataIndex: 'state', key: 'state', render: (s) => <Tag>{s ?? '—'}</Tag> },
{ {
title: 'Members', title: 'Members',
dataIndex: 'members', dataIndex: 'members',
@@ -106,36 +106,36 @@ export default function NetworkManager() {
], ],
[isRemoving], [isRemoving],
); );
async function handleCreateBridge(values: { name: string; interfaces?: string[] }) { async function handleCreateBridge(values: { name: string; interfaces?: string[] }) {
const ifaceList = values.interfaces ?? []; const ifaceList = values.interfaces ?? [];
createBridge({ name: values.name, interfaces: ifaceList })
try { .then(() => {
await createBridge({ name: values.name, interfaces: ifaceList }); message.success(`Bridge ${values.name} created`);
message.success(`Bridge ${values.name} created`); setBridgeModalVisible(false);
setBridgeModalVisible(false); bridgeForm.resetFields();
bridgeForm.resetFields(); })
} catch (err) { .catch((err) => {
console.error(err); console.error(err);
message.error((err as Error)?.message ?? 'Failed to create bridge'); message.error((err as Error).message ?? 'Failed to create bridge');
} });
} }
async function handleRemoveBridge(name: string) { function handleRemoveBridge(name: string) {
try { removeBridge({ name })
await removeBridge({ name }); .then(() => {
message.success(`Bridge ${name} removed`); message.success(`Bridge ${name} removed`);
} catch (err) { })
console.error(err); .catch((err) => {
message.error((err as Error)?.message ?? 'Failed to remove bridge'); console.error(err);
} message.error((err as Error).message ?? 'Failed to remove bridge');
});
} }
return ( return (
<div style={{ padding: 16 }}> <div style={{ padding: 16 }}>
<Row justify="space-between" align="middle" style={{ marginBottom: 12 }}> <Row justify="space-between" align="middle" style={{ marginBottom: 12 }}>
<Col> <Col>
<Title level={4}>Bridges & Interfaces</Title> <Title level={2}> Network Management</Title>
<Paragraph type="secondary">View system interfaces and manage network bridges.</Paragraph> <Paragraph type="secondary">View system interfaces and manage network bridges.</Paragraph>
</Col> </Col>
<Col> <Col>

View File

@@ -46,6 +46,10 @@ body {
li span { li span {
font-size: @font-size-slider; font-size: @font-size-slider;
} }
.ant-menu-title-content{
text-align: left;
padding-left: 10px;
}
} }
/* Inner layout (app-main) is a column flex that occupies the remaining width */ /* Inner layout (app-main) is a column flex that occupies the remaining width */
@@ -79,6 +83,7 @@ body {
/* this makes the main content scrollable */ /* this makes the main content scrollable */
background: @component-background; background: @component-background;
padding: 24px; padding: 24px;
text-align: left;
} }
/* Footer: fixed at bottom of app-main */ /* Footer: fixed at bottom of app-main */