restructure frontend and add network settings
All checks were successful
Build and Deploy MITM Webserver / build (push) Successful in 1m41s

This commit is contained in:
2025-12-18 20:38:27 +01:00
parent b0de61fd9d
commit 9afe49857a
15 changed files with 1479 additions and 696 deletions

View File

@@ -0,0 +1,227 @@
// src/hooks/useNetwork.ts
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { useCallback, useState } from "react";
import * as api from "../api/apiClient";
import type {
BridgeCreateRequest,
BridgeInfo,
BridgeRemoveRequest,
FullState,
InterfaceInfo,
RouteInfo,
} from "../types/network";
/**
* useNetwork
*
* - queries start disabled (no automatic network calls)
* - calling fetchInterfaces()/fetchBridges()/... will:
* 1) fetch and cache the data right away (queryClient.fetchQuery)
* 2) enable the corresponding useQuery so it becomes "active" and will
* auto-refetch based on the query options
*
* This gives "no initial auto-fetch" but "once fetched, auto-updates".
*/
export function useNetwork() {
const qc = useQueryClient();
// per-query enabled flags (start false => no automatic fetch)
const [interfacesEnabled, setInterfacesEnabled] = useState(false);
const [linksEnabled, setLinksEnabled] = useState(false);
const [routesEnabled, setRoutesEnabled] = useState(false);
const [bridgesEnabled, setBridgesEnabled] = useState(false);
const [fullStateEnabled, setFullStateEnabled] = useState(false);
// common query options once enabled
const commonOptions = {
refetchOnWindowFocus: true,
staleTime: 1000 * 10, // 10s
};
// Queries (disabled initially)
const interfacesQuery = useQuery<InterfaceInfo[]>({
queryKey: ["interfaces"],
queryFn: api.fetchInterfaces,
enabled: interfacesEnabled,
...commonOptions,
});
const linksQuery = useQuery<InterfaceInfo[]>({
queryKey: ["links"],
queryFn: api.fetchLinks,
enabled: linksEnabled,
...commonOptions,
});
const routesQuery = useQuery<RouteInfo[]>({
queryKey: ["routes"],
queryFn: api.fetchRoutes,
enabled: routesEnabled,
...commonOptions,
});
const bridgesQuery = useQuery<BridgeInfo[]>({
queryKey: ["bridges"],
queryFn: api.fetchBridges,
enabled: bridgesEnabled,
...commonOptions,
});
const fullStateQuery = useQuery<FullState>({
queryKey: ["full-state"],
queryFn: api.fetchFullState,
enabled: fullStateEnabled,
...commonOptions,
});
// Imperative fetch helpers that also enable auto-refetch behavior
const fetchInterfaces = useCallback(async () => {
const res = await qc.fetchQuery<InterfaceInfo[]>({
queryKey: ["interfaces"],
queryFn: api.fetchInterfaces,
staleTime: 1000 * 10
});
setInterfacesEnabled(true);
return res;
}, [qc]);
const fetchLinks = useCallback(async () => {
const res = await qc.fetchQuery<InterfaceInfo[]>({
queryKey: ["links"],
queryFn: api.fetchLinks,
staleTime: 1000 * 10
});
setLinksEnabled(true);
return res;
}, [qc]);
const fetchRoutes = useCallback(async () => {
const res = await qc.fetchQuery<RouteInfo[]>({
queryKey: ["routes"],
queryFn: api.fetchRoutes,
staleTime: 1000 * 10
});
setRoutesEnabled(true);
return res;
}, [qc]);
const fetchBridges = useCallback(async () => {
const res = await qc.fetchQuery<BridgeInfo[]>({
queryKey: ["bridges"],
queryFn: api.fetchBridges,
staleTime: 1000 * 10
});
setBridgesEnabled(true);
return res;
}, [qc]);
const fetchFullState = useCallback(async () => {
const res = await qc.fetchQuery<FullState>({
queryKey: ["full-state"],
queryFn: api.fetchFullState,
staleTime: 1000 * 5
});
setFullStateEnabled(true);
return res;
}, [qc]);
// Local loading state for simple UI feedback
const [isCreating, setIsCreating] = useState(false);
const [isRemoving, setIsRemoving] = useState(false);
// Simple imperative functions that call the API and invalidate queries
async function createBridge(payload: BridgeCreateRequest) {
setIsCreating(true);
try {
await api.createBridge(payload);
// If the query is enabled it will refetch automatically after invalidation.
await qc.invalidateQueries({ queryKey: ["bridges"] });
await qc.invalidateQueries({ queryKey: ["full-state"] });
await qc.invalidateQueries({ queryKey: ["interfaces"] });
} catch (err) {
const message =
err instanceof Error ? err.message : typeof err === "string" ? err : "Create bridge failed";
throw new Error(message);
} finally {
setIsCreating(false);
}
}
async function removeBridge(payload: BridgeRemoveRequest) {
setIsRemoving(true);
try {
await api.removeBridge(payload);
await qc.invalidateQueries({ queryKey: ["bridges"] });
await qc.invalidateQueries({ queryKey: ["full-state"] });
await qc.invalidateQueries({ queryKey: ["interfaces"] });
} catch (err) {
const message =
err instanceof Error ? err.message : typeof err === "string" ? err : "Remove bridge failed";
throw new Error(message);
} finally {
setIsRemoving(false);
}
}
// Convenience: invalidate helpers
function refreshInterfaces() {
return qc.invalidateQueries({ queryKey: ["interfaces"] });
}
function refreshLinks() {
return qc.invalidateQueries({ queryKey: ["links"] });
}
function refreshRoutes() {
return qc.invalidateQueries({ queryKey: ["routes"] });
}
function refreshBridges() {
return qc.invalidateQueries({ queryKey: ["bridges"] });
}
function refreshFullState() {
return qc.invalidateQueries({ queryKey: ["full-state"] });
}
// Convenience: refresh all queries
function refreshAll() {
refreshInterfaces();
refreshLinks();
refreshRoutes();
refreshBridges();
refreshFullState();
}
return {
// queries
interfacesQuery,
linksQuery,
routesQuery,
bridgesQuery,
fullStateQuery,
// manual fetchers (fetch+enable auto-updates)
fetchInterfaces,
fetchLinks,
fetchRoutes,
fetchBridges,
fetchFullState,
// simple mutation functions (imperative)
createBridge,
removeBridge,
// local loading flags
isCreating,
isRemoving,
// invalidate helpers
refreshInterfaces,
refreshLinks,
refreshRoutes,
refreshBridges,
refreshFullState,
refreshAll,
};
}