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 */