import { useEffect, useMemo, useState } from "react"; import { AnimatePresence, motion } from "framer-motion"; import { AlertTriangle, Check, Database, FileJson, Globe2, Loader2, Plus, RefreshCcw, Route, Server, ShieldCheck, Zap, } from "lucide-react"; const API_BASE = import.meta.env.VITE_API_BASE_URL || "http://localhost:4000/api"; const emptyServer = { name: "", ipAddress: "", description: "" }; const emptyService = { serverId: "", name: "", port: "", endpoint: "", method: "POST", requestTemplate: "{}", notes: "", }; const sampleJson = JSON.stringify( { servers: [ { name: "Billing Server", ipAddress: "192.168.1.50", services: [ { name: "Invoice API", port: 8080, endpoint: "/api/invoices/sync", method: "POST", requestTemplate: { limit: 10 }, }, ], }, ], }, null, 2, ); async function api(path, options = {}) { const response = await fetch(`${API_BASE}${path}`, { headers: { "content-type": "application/json", ...(options.headers || {}) }, ...options, }); const data = await response.json().catch(() => null); if (!response.ok) { const error = new Error(data?.message || "Request failed."); error.data = data; throw error; } return data; } function parseJsonField(value) { const trimmed = value.trim(); if (!trimmed) return null; return JSON.parse(trimmed); } function Stat({ icon: Icon, label, value }) { return (

{label}

{value}

); } function Field({ label, children }) { return ( ); } function App() { const [servers, setServers] = useState([]); const [serverForm, setServerForm] = useState(emptyServer); const [serviceForm, setServiceForm] = useState(emptyService); const [jsonInput, setJsonInput] = useState(sampleJson); const [preview, setPreview] = useState(null); const [message, setMessage] = useState(""); const [error, setError] = useState(""); const [loading, setLoading] = useState(false); const [proxyResult, setProxyResult] = useState(null); const totals = useMemo(() => { const services = servers.reduce((sum, server) => sum + server.services.length, 0); const ports = new Set(servers.flatMap((server) => server.services.map((service) => service.port))); return { servers: servers.length, services, ports: ports.size }; }, [servers]); async function loadServers() { setLoading(true); setError(""); try { const data = await api("/servers"); setServers(data); if (data[0] && !serviceForm.serverId) { setServiceForm((current) => ({ ...current, serverId: data[0].id })); } } catch (err) { setError(err.message); } finally { setLoading(false); } } useEffect(() => { loadServers(); }, []); async function createServer(event) { event.preventDefault(); setError(""); setMessage(""); try { await api("/servers", { method: "POST", body: JSON.stringify(serverForm) }); setServerForm(emptyServer); setMessage("Server added."); await loadServers(); } catch (err) { setError(err.message); } } async function createService(event) { event.preventDefault(); setError(""); setMessage(""); try { await api("/services", { method: "POST", body: JSON.stringify({ ...serviceForm, requestTemplate: parseJsonField(serviceForm.requestTemplate), }), }); setServiceForm((current) => ({ ...emptyService, serverId: current.serverId, method: "POST", requestTemplate: "{}", })); setMessage("Service added."); await loadServers(); } catch (err) { setError(err.message); } } async function previewImport() { setError(""); setMessage(""); try { const payload = JSON.parse(jsonInput); const data = await api("/imports/preview", { method: "POST", body: JSON.stringify(payload) }); setPreview(data); setMessage( data.conflicts.length ? "Conflicts found. Review and confirm before overwrite." : "Import is ready. Exact duplicates will be skipped.", ); } catch (err) { setError(err.message); } } async function applyImport(mode = "skip") { setError(""); setMessage(""); try { const payload = JSON.parse(jsonInput); const data = await api("/imports/apply", { method: "POST", body: JSON.stringify({ payload, mode }), }); setPreview(null); setMessage( `Import applied: ${data.applied.serversCreated} servers and ${data.applied.servicesCreated} services created.`, ); await loadServers(); } catch (err) { setError(err.message); if (err.data) setPreview(err.data); } } async function reverseProxy(serviceId, body) { setError(""); setProxyResult(null); try { const data = await api(`/services/${serviceId}/reverse-proxy`, { method: "POST", body: JSON.stringify({ body }), }); setProxyResult(data); } catch (err) { setError(err.message); } } return (

MTC Server Reverse Proxy

Reverse Proxy Manager