diff --git a/backend/src/api/network_api.py b/backend/src/api/network_api.py index a99b9de..2ae3c29 100644 --- a/backend/src/api/network_api.py +++ b/backend/src/api/network_api.py @@ -353,7 +353,7 @@ def full_state( @router.post("/bridge/create") def create_bridge(req: BridgeCreateRequest, ip: IPRoute = Depends(get_iproute)): 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 ip.link("add", ifname=req.name, kind="bridge") diff --git a/frontend/src/App.css b/frontend/src/App.css index 4039e55..027945e 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -3,11 +3,4 @@ margin: 0 auto; padding: 2rem; text-align: center; -} - -.drawer-pages { - display: flex; - justify-content: flex-start; - align-items: center; - gap: 10px; -} +} \ No newline at end of file diff --git a/frontend/src/Layout/Sidebar.tsx b/frontend/src/Layout/Sidebar.tsx index 1ede8dc..f1ed6bd 100644 --- a/frontend/src/Layout/Sidebar.tsx +++ b/frontend/src/Layout/Sidebar.tsx @@ -9,10 +9,10 @@ import '../theme/layout.less'; const { Sider } = Layout; const menuItems = [ - { key: PATHS.HOME, icon: , label: 'Home' }, - { key: PATHS.NETWORK, icon: , label: 'Network' }, - { key: '/about', icon: , label: 'About' }, - { key: '/settings', icon: , label: 'Settings' }, + { key: PATHS.HOME, icon: , label: 'Home' }, + { key: PATHS.NETWORK, icon: , label: 'Network' }, + { key: '/about', icon: , label: 'About' }, + { key: '/settings', icon: , label: 'Settings' }, ]; interface SidebarProps { diff --git a/frontend/src/api/apiClient.ts b/frontend/src/api/apiClient.ts index da4e226..f5c9078 100644 --- a/frontend/src/api/apiClient.ts +++ b/frontend/src/api/apiClient.ts @@ -17,6 +17,22 @@ export const api = axios.create({ 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 export const fetchInterfaces = async (): Promise => { const res = await api.get("/network/interfaces"); diff --git a/frontend/src/appRouter.tsx b/frontend/src/appRouter.tsx index 657dad6..c411d2f 100644 --- a/frontend/src/appRouter.tsx +++ b/frontend/src/appRouter.tsx @@ -1,8 +1,8 @@ // src/AppRouter.tsx import { Navigate, Route, Routes } from 'react-router-dom'; import App from './App'; // your layout component (has ) -import Bridges from './pages/Bridges'; import Home from './pages/Home'; +import Network from './pages/Network'; import { PATHS } from './routes'; export default function AppRouter() { @@ -15,7 +15,7 @@ export default function AppRouter() { {/* Child routes - these render inside App's */} } /> - } /> + } /> {/* Fallback (renders inside layout too) */} } /> diff --git a/frontend/src/pages/Bridges.tsx b/frontend/src/pages/Network.tsx similarity index 89% rename from frontend/src/pages/Bridges.tsx rename to frontend/src/pages/Network.tsx index 1a9530c..80c9b0c 100644 --- a/frontend/src/pages/Bridges.tsx +++ b/frontend/src/pages/Network.tsx @@ -8,7 +8,7 @@ import type { BridgeInfo, InterfaceInfo } from '../types/network'; const { Title, Paragraph } = Typography; -export default function NetworkManager() { +export default function Network() { const { interfacesQuery, bridgesQuery, @@ -59,7 +59,7 @@ export default function NetworkManager() { key: 'addresses', render: (addrs: any[]) => addrs?.length ? ( - + {addrs.map((a) => ( {a.address}/{a.prefixlen} ({a.family}) @@ -79,7 +79,7 @@ export default function NetworkManager() { () => [ { title: 'IfIndex', dataIndex: 'ifindex', key: 'ifindex', width: 90 }, { title: 'Name', dataIndex: 'ifname', key: 'ifname' }, - { title: 'State', dataIndex: 'state', key: 'state', render: (s) => s ?? '—' }, + { title: 'State', dataIndex: 'state', key: 'state', render: (s) => {s ?? '—'} }, { title: 'Members', dataIndex: 'members', @@ -106,36 +106,36 @@ export default function NetworkManager() { ], [isRemoving], ); - async function handleCreateBridge(values: { name: string; interfaces?: string[] }) { const ifaceList = values.interfaces ?? []; - - try { - await createBridge({ name: values.name, interfaces: ifaceList }); - message.success(`Bridge ${values.name} created`); - setBridgeModalVisible(false); - bridgeForm.resetFields(); - } catch (err) { - console.error(err); - message.error((err as Error)?.message ?? 'Failed to create bridge'); - } + createBridge({ name: values.name, interfaces: ifaceList }) + .then(() => { + message.success(`Bridge ${values.name} created`); + setBridgeModalVisible(false); + bridgeForm.resetFields(); + }) + .catch((err) => { + console.error(err); + message.error((err as Error).message ?? 'Failed to create bridge'); + }); } - async function handleRemoveBridge(name: string) { - try { - await removeBridge({ name }); - message.success(`Bridge ${name} removed`); - } catch (err) { - console.error(err); - message.error((err as Error)?.message ?? 'Failed to remove bridge'); - } + function handleRemoveBridge(name: string) { + removeBridge({ name }) + .then(() => { + message.success(`Bridge ${name} removed`); + }) + .catch((err) => { + console.error(err); + message.error((err as Error).message ?? 'Failed to remove bridge'); + }); } return (
- Bridges & Interfaces + Network Management View system interfaces and manage network bridges. diff --git a/frontend/src/theme/layout.less b/frontend/src/theme/layout.less index aef7a0c..b2c8ca4 100644 --- a/frontend/src/theme/layout.less +++ b/frontend/src/theme/layout.less @@ -46,6 +46,10 @@ body { li span { 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 */ @@ -79,6 +83,7 @@ body { /* this makes the main content scrollable */ background: @component-background; padding: 24px; + text-align: left; } /* Footer: fixed at bottom of app-main */