diff --git a/app/api/flowchart/route.ts b/app/api/flowchart/route.ts new file mode 100644 index 0000000..fcccd1e --- /dev/null +++ b/app/api/flowchart/route.ts @@ -0,0 +1,142 @@ +import { NextResponse, NextRequest } from "next/server"; +import { PrismaClient } from "@/lib/generated/prisma"; + +const prisma = new PrismaClient(); + +const NODE_WIDTH = 220; +const NODE_HEIGHT = 60; +const HORIZONTAL_SPACING = 280; +const VERTICAL_SPACING = 80; +const START_Y = 120; +const ROOT_NODE_WIDTH = 300; + +export async function GET() { + try { + const [servers, applications] = await Promise.all([ + prisma.server.findMany({ + orderBy: { id: "asc" }, + }), + prisma.application.findMany({ + orderBy: { serverId: "asc" }, + }), + ]); + + const rootNode = { + id: "root", + type: "infrastructure", + data: { label: "My Infrastructure" }, + position: { x: 0, y: 20 }, + style: { + background: "#ffffff", + color: "#0f0f0f", + border: "2px solid #e6e4e1", + borderRadius: "8px", + padding: "16px", + width: ROOT_NODE_WIDTH, + height: NODE_HEIGHT, + fontSize: "1.2rem", + fontWeight: "bold", + }, + }; + + const serverNodes = servers.map((server, index) => { + const xPos = + index * HORIZONTAL_SPACING - + ((servers.length - 1) * HORIZONTAL_SPACING) / 2; + + return { + id: `server-${server.id}`, + type: "server", + data: { + label: `${server.name}\n${server.ip}`, + ...server, + }, + position: { x: xPos, y: START_Y }, + style: { + background: "#ffffff", + color: "#0f0f0f", + border: "2px solid #e6e4e1", + borderRadius: "4px", + padding: "8px", + width: NODE_WIDTH, + height: NODE_HEIGHT, + fontSize: "0.9rem", + lineHeight: "1.2", + whiteSpace: "pre-wrap", + }, + }; + }); + + const appNodes: any[] = []; + servers.forEach((server) => { + const serverX = + serverNodes.find((n) => n.id === `server-${server.id}`)?.position.x || + 0; + const serverY = START_Y; + + applications + .filter((app) => app.serverId === server.id) + .forEach((app, appIndex) => { + appNodes.push({ + id: `app-${app.id}`, + type: "application", + data: { + label: `${app.name}\n${app.localURL}`, + ...app, + }, + position: { + x: serverX, + y: serverY + NODE_HEIGHT + 40 + appIndex * VERTICAL_SPACING, + }, + style: { + background: "#ffffff", + color: "#0f0f0f", + border: "2px solid #e6e4e1", + borderRadius: "4px", + padding: "8px", + width: NODE_WIDTH, + height: NODE_HEIGHT, + fontSize: "0.9rem", + lineHeight: "1.2", + whiteSpace: "pre-wrap", + }, + }); + }); + }); + + const connections = [ + ...servers.map((server) => ({ + id: `conn-root-${server.id}`, + source: "root", + target: `server-${server.id}`, + type: "straight", + style: { + stroke: "#94a3b8", + strokeWidth: 2, + }, + })), + ...applications.map((app) => ({ + id: `conn-${app.serverId}-${app.id}`, + source: `server-${app.serverId}`, + target: `app-${app.id}`, + type: "straight", + style: { + stroke: "#60a5fa", + strokeWidth: 2, + }, + })), + ]; + + return NextResponse.json({ + nodes: [rootNode, ...serverNodes, ...appNodes], + edges: connections, + }); + } catch (error: any) { + return NextResponse.json( + { + error: `Error fetching flowchart: ${error.message}`, + }, + { status: 500 } + ); + } +} diff --git a/app/dashboard/network/Networks.tsx b/app/dashboard/network/Networks.tsx new file mode 100644 index 0000000..29a7b55 --- /dev/null +++ b/app/dashboard/network/Networks.tsx @@ -0,0 +1,99 @@ +import { AppSidebar } from "@/components/app-sidebar"; +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbPage, + BreadcrumbSeparator, +} from "@/components/ui/breadcrumb"; +import { Separator } from "@/components/ui/separator"; +import { + SidebarInset, + SidebarProvider, + SidebarTrigger, +} from "@/components/ui/sidebar"; +import { ReactFlow, Controls, Background } from "@xyflow/react"; +import "@xyflow/react/dist/style.css"; +import { useEffect, useState } from "react"; + +export default function Dashboard() { + const [nodes, setNodes] = useState([]); + const [edges, setEdges] = useState([]); + + useEffect(() => { + const fetchFlowData = async () => { + try { + const response = await fetch("/api/flowchart"); + const data = await response.json(); + + setNodes(data.nodes); + setEdges(data.edges); + } catch (error) { + console.error("Error loading flowchart:", error); + } + }; + + fetchFlowData(); + }, []); + + return ( + + + +
+
+ + + + + + + / + + + + + + My Infrastructure + + + + + + Network + + + + +
+
+ +
+
+ + + +
+
+
+
+ ); +} diff --git a/app/dashboard/network/page.tsx b/app/dashboard/network/page.tsx new file mode 100644 index 0000000..ccedc3d --- /dev/null +++ b/app/dashboard/network/page.tsx @@ -0,0 +1,59 @@ +"use client"; + +import { useEffect, useState } from "react"; +import Cookies from "js-cookie"; +import { useRouter } from "next/navigation"; +import Networks from "./Networks" +import axios from "axios"; + +export default function DashboardPage() { + const router = useRouter(); + const [isAuthChecked, setIsAuthChecked] = useState(false); + const [isValid, setIsValid] = useState(false); + + useEffect(() => { + const token = Cookies.get("token"); + if (!token) { + router.push("/"); + } else { + const checkToken = async () => { + try { + const response = await axios.post("/api/auth/validate", { + token: token, + }); + + if (response.status === 200) { + setIsValid(true); + } + } catch (error: any) { + Cookies.remove("token"); + router.push("/"); + } + } + checkToken(); + } + setIsAuthChecked(true); + }, [router]); + + if (!isAuthChecked) { + return ( +
+
+ + + + + + + + + + + Loading... +
+
+ ) + } + + return isValid ? : null; +} \ No newline at end of file diff --git a/components/app-sidebar.tsx b/components/app-sidebar.tsx index f2a601d..a705cad 100644 --- a/components/app-sidebar.tsx +++ b/components/app-sidebar.tsx @@ -1,7 +1,7 @@ import * as React from "react" import Image from "next/image" -import { AppWindow, Settings, LayoutDashboardIcon, Briefcase, Server } from "lucide-react" +import { AppWindow, Settings, LayoutDashboardIcon, Briefcase, Server, Network } from "lucide-react" import { Sidebar, SidebarContent, @@ -43,7 +43,11 @@ const data = { icon: AppWindow, url: "/dashboard/applications", }, - + { + title: "Network", + icon: Network, + url: "/dashboard/network", + }, ], }, @@ -67,7 +71,7 @@ export function AppSidebar({ ...props }: React.ComponentProps) { - + Logo
CoreControl diff --git a/package-lock.json b/package-lock.json index 911c507..c2e2234 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "@types/axios": "^0.9.36", "@types/js-cookie": "^3.0.6", "@types/jsonwebtoken": "^9.0.9", + "@xyflow/react": "^12.5.5", "axios": "^1.8.4", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -2170,6 +2171,55 @@ "integrity": "sha512-NLOpedx9o+rxo/X5ChbdiX6mS1atE4WHmEEIcR9NLenRVa5HoVjAvjafwU3FPTqnZEstpoqCaW7fagqSoTDNeg==", "license": "MIT" }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, "node_modules/@types/js-cookie": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz", @@ -2221,6 +2271,36 @@ "@types/react": "^19.0.0" } }, + "node_modules/@xyflow/react": { + "version": "12.5.5", + "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.5.5.tgz", + "integrity": "sha512-mAtHuS4ktYBL1ph5AJt7X/VmpzzlmQBN3+OXxyT/1PzxwrVto6AKc3caerfxzwBsg3cA4J8lB63F3WLAuPMmHw==", + "license": "MIT", + "dependencies": { + "@xyflow/system": "0.0.55", + "classcat": "^5.0.3", + "zustand": "^4.4.0" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@xyflow/system": { + "version": "0.0.55", + "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.55.tgz", + "integrity": "sha512-6cngWlE4oMXm+zrsbJxerP3wUNUFJcv/cE5kDfu0qO55OWK3fAeSOLW9td3xEVQlomjIW5knds1MzeMnBeCfqw==", + "license": "MIT", + "dependencies": { + "@types/d3-drag": "^3.0.7", + "@types/d3-selection": "^3.0.10", + "@types/d3-transition": "^3.0.8", + "@types/d3-zoom": "^3.0.8", + "d3-drag": "^3.0.0", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0" + } + }, "node_modules/aria-hidden": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz", @@ -2312,6 +2392,12 @@ "url": "https://polar.sh/cva" } }, + "node_modules/classcat": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", + "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==", + "license": "MIT" + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -2391,6 +2477,111 @@ "devOptional": true, "license": "MIT" }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -3674,6 +3865,43 @@ "optional": true } } + }, + "node_modules/use-sync-external-store": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/zustand": { + "version": "4.5.6", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.6.tgz", + "integrity": "sha512-ibr/n1hBzLLj5Y+yUcU7dYw8p6WnIVzdJbnX+1YpaScvZVF2ziugqHs+LAmHw4lWO9c/zRj+K1ncgWDQuthEdQ==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index f87c061..dfd5d61 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@types/axios": "^0.9.36", "@types/js-cookie": "^3.0.6", "@types/jsonwebtoken": "^9.0.9", + "@xyflow/react": "^12.5.5", "axios": "^1.8.4", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1",