diff --git a/README.md b/README.md index 0cd91e1..344818c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,28 @@ -# ospfcost +This project was generated from [create.xyz](https://create.xyz/). -OSPF cost webapp simulator \ No newline at end of file +It is a [Next.js](https://nextjs.org/) project built on React and TailwindCSS. + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the code in `src`. The page auto-updates as you edit the file. + +To learn more, take a look at the following resources: + +- [React Documentation](https://react.dev/) - learn about React +- [TailwindCSS Documentation](https://tailwindcss.com/) - learn about TailwindCSS +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. \ No newline at end of file diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..e8b3494 --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "paths": { + "@/*": ["./src/*"] + } + } +} \ No newline at end of file diff --git a/next.config.js b/next.config.js new file mode 100644 index 0000000..2e5f142 --- /dev/null +++ b/next.config.js @@ -0,0 +1,12 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + experimental: { + esmExternals: 'loose' + }, + webpack: (config) => { + config.externals = [...config.externals, { canvas: "canvas" }]; // required to make pdfjs work + return config; + }, +}; + +module.exports = nextConfig; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..6edec10 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "create-project", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + + "react": "^18", + "react-dom": "^18", + "next": "14.0.4" + }, + "devDependencies": { + "autoprefixer": "^10.0.1", + "postcss": "^8", + "tailwindcss": "^3.3.0" + } +} \ No newline at end of file diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..96bb01e --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} \ No newline at end of file diff --git a/src/app/globals.css b/src/app/globals.css new file mode 100644 index 0000000..bd6213e --- /dev/null +++ b/src/app/globals.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; \ No newline at end of file diff --git a/src/app/layout.js b/src/app/layout.js new file mode 100644 index 0000000..96c6a87 --- /dev/null +++ b/src/app/layout.js @@ -0,0 +1,17 @@ +import { Inter } from 'next/font/google' +import './globals.css' + +const inter = Inter({ subsets: ['latin'] }) + +export const metadata = { + title: 'Create.xyz App', + description: 'Generated by create.xyz', +} + +export default function RootLayout({ children }) { + return ( + +
{children} + + ) +} \ No newline at end of file diff --git a/src/app/page.jsx b/src/app/page.jsx new file mode 100644 index 0000000..c2edc5a --- /dev/null +++ b/src/app/page.jsx @@ -0,0 +1,561 @@ +"use client"; +import React from "react"; + +function MainComponent() { + const [networkData, setNetworkData] = useState(null); + const [links, setLinks] = useState([]); + const [nodes, setNodes] = useState([]); + const [trafficDistribution, setTrafficDistribution] = useState({}); + const [selectedLink, setSelectedLink] = useState(null); + const [newCost, setNewCost] = useState(""); + const [error, setError] = useState(""); + const [utilizationPercentage, setUtilizationPercentage] = useState(50); + + // Parse uploaded file + const parseNetworkFile = (content) => { + try { + const lines = content.trim().split("\n"); + const header = lines[0]; + + if ( + !header.toLowerCase().includes("switch a") || + !header.toLowerCase().includes("switch b") + ) { + throw new Error('File must contain "Switch A" and "Switch B" columns'); + } + + const parsedLinks = []; + const nodeSet = new Set(); + + for (let i = 1; i < lines.length; i++) { + const parts = lines[i].split(/[,\t]/).map((p) => p.trim()); + if (parts.length >= 4) { + const switchA = parts[0]; + const switchB = parts[1]; + const cost = parseFloat(parts[2]); + const bandwidth = parseFloat(parts[3]); + + if (switchA && switchB && !isNaN(cost) && !isNaN(bandwidth)) { + parsedLinks.push({ + id: `${switchA}-${switchB}`, + switchA, + switchB, + cost, + originalCost: cost, + bandwidth, + }); + nodeSet.add(switchA); + nodeSet.add(switchB); + } + } + } + + const nodeArray = Array.from(nodeSet).map((name) => ({ id: name, name })); + + setLinks(parsedLinks); + setNodes(nodeArray); + calculateTrafficDistribution(parsedLinks, nodeArray); + setError(""); + } catch (err) { + setError(`Error parsing file: ${err.message}`); + } + }; + + // Calculate OSPF shortest paths and traffic distribution + const calculateTrafficDistribution = (linkData, nodeData) => { + if (linkData.length === 0 || nodeData.length === 0) return; + + // Build adjacency list with costs + const graph = {}; + nodeData.forEach((node) => { + graph[node.id] = {}; + }); + + linkData.forEach((link) => { + graph[link.switchA][link.switchB] = link.cost; + graph[link.switchB][link.switchA] = link.cost; + }); + + // Calculate traffic for each link based on shortest paths + const linkTraffic = {}; + linkData.forEach((link) => { + linkTraffic[link.id] = 0; + }); + + // For each pair of nodes, find shortest path and add traffic + nodeData.forEach((source) => { + nodeData.forEach((destination) => { + if (source.id !== destination.id) { + const path = dijkstra(graph, source.id, destination.id); + if (path.length > 1) { + // Add traffic to each link in the path + for (let i = 0; i < path.length - 1; i++) { + const linkId1 = `${path[i]}-${path[i + 1]}`; + const linkId2 = `${path[i + 1]}-${path[i]}`; + + if (linkTraffic.hasOwnProperty(linkId1)) { + linkTraffic[linkId1] += 1; + } else if (linkTraffic.hasOwnProperty(linkId2)) { + linkTraffic[linkId2] += 1; + } + } + } + } + }); + }); + + // Apply utilization percentage scaling + const maxTraffic = Math.max(...Object.values(linkTraffic)); + const trafficPercentages = {}; + Object.keys(linkTraffic).forEach((linkId) => { + const baseTraffic = + maxTraffic > 0 ? (linkTraffic[linkId] / maxTraffic) * 100 : 0; + trafficPercentages[linkId] = (baseTraffic * utilizationPercentage) / 100; + }); + + setTrafficDistribution(trafficPercentages); + }; + + // Dijkstra's algorithm for shortest path + const dijkstra = (graph, start, end) => { + const distances = {}; + const previous = {}; + const unvisited = new Set(); + + Object.keys(graph).forEach((node) => { + distances[node] = Infinity; + previous[node] = null; + unvisited.add(node); + }); + + distances[start] = 0; + + while (unvisited.size > 0) { + let current = null; + unvisited.forEach((node) => { + if (current === null || distances[node] < distances[current]) { + current = node; + } + }); + + if (current === end) break; + + unvisited.delete(current); + + Object.keys(graph[current]).forEach((neighbor) => { + if (unvisited.has(neighbor)) { + const alt = distances[current] + graph[current][neighbor]; + if (alt < distances[neighbor]) { + distances[neighbor] = alt; + previous[neighbor] = current; + } + } + }); + } + + // Reconstruct path + const path = []; + let current = end; + while (current !== null) { + path.unshift(current); + current = previous[current]; + } + + return distances[end] === Infinity ? [] : path; + }; + + // Handle file upload + const handleFileUpload = (event) => { + const file = event.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (e) => { + parseNetworkFile(e.target.result); + }; + reader.readAsText(file); + } + }; + + // Update link cost + const updateLinkCost = () => { + if (selectedLink && newCost !== "") { + const cost = parseFloat(newCost); + if (!isNaN(cost) && cost > 0) { + const updatedLinks = links.map((link) => + link.id === selectedLink.id ? { ...link, cost } : link + ); + setLinks(updatedLinks); + calculateTrafficDistribution(updatedLinks, nodes); + setSelectedLink(null); + setNewCost(""); + } + } + }; + + // Reset all costs to original + const resetCosts = () => { + const resetLinks = links.map((link) => ({ + ...link, + cost: link.originalCost, + })); + setLinks(resetLinks); + calculateTrafficDistribution(resetLinks, nodes); + }; + + // Handle utilization slider change + const handleUtilizationChange = (event) => { + const newUtilization = parseInt(event.target.value); + setUtilizationPercentage(newUtilization); + // Recalculate traffic distribution with new utilization + calculateTrafficDistribution(links, nodes); + }; + + // Network visualization component + const NetworkVisualization = () => { + if (nodes.length === 0) return null; + + const svgWidth = 800; + const svgHeight = 600; + const nodeRadius = 30; + + // Position nodes in a circle for better visualization + const centerX = svgWidth / 2; + const centerY = svgHeight / 2; + const radius = Math.min(svgWidth, svgHeight) / 3; + + const positionedNodes = nodes.map((node, index) => { + const angle = (2 * Math.PI * index) / nodes.length; + return { + ...node, + x: centerX + radius * Math.cos(angle), + y: centerY + radius * Math.sin(angle), + }; + }); + + return ( ++ Upload a text file with columns: Switch A, Switch B, Cost, Bandwidth + (Gb) +
+ {error && ( +• Adjust the overall network traffic utilization level
++ • Higher utilization may affect link behavior and performance +
++ • Green: Low + utilization (<40%) | + Orange: + Medium (40-70%) | + Red: High + (>70%) +
+• Click on links to modify OSPF costs
++ • Line thickness and color represent traffic utilization level +
+• Percentages show current traffic distribution
++ Link: {selectedLink.switchA} ↔{" "} + {selectedLink.switchB} +
++ Current Cost: {selectedLink.cost} +
++ Original Cost: {selectedLink.originalCost} +
++ Bandwidth: {selectedLink.bandwidth} Gb +
++ Current Traffic:{" "} + 70 + ? "#dc2626" + : trafficDistribution[selectedLink.id] > 40 + ? "#d97706" + : "#059669", + }} + > + {(trafficDistribution[selectedLink.id] || 0).toFixed(1)}% + +
+| Switch A | +Switch B | +Current Cost | +Original Cost | +Bandwidth (Gb) | +Traffic % | +Actions | +
|---|---|---|---|---|---|---|
| {link.switchA} | +{link.switchB} | +{link.cost} | ++ {link.originalCost} + | +{link.bandwidth} | ++ 70 + ? "#dc2626" + : traffic > 40 + ? "#d97706" + : "#059669", + }} + > + {traffic.toFixed(1)}% + + | ++ + | +