diff --git a/.gitignore b/.gitignore index 98d17e9..b582dbe 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,8 @@ results/* myenv*/ doc/_build !results/plots.py -frontend/node_modules -frontend/build -frontend/.next +das-simulator-visualizer/node_modules +das-simulator-visualizer/build +das-simulator-visualizer/.next .DS_Store diff --git a/das-simulator-visualizer/README.md b/das-simulator-visualizer/README.md new file mode 100644 index 0000000..c103acf --- /dev/null +++ b/das-simulator-visualizer/README.md @@ -0,0 +1,91 @@ +# DAS Simulator Visualizer + +This web application provides a visualization interface for Data Availability Sampling (DAS) simulation results. It allows you to browse, analyze, and compare simulation runs with different parameters, view detailed graphs, and explore parameter relationships through heatmaps. + +## Features + +- **Simulation List**: Browse and filter all available simulation runs +- **Detailed Graphs**: Visualize various metrics from simulation runs: +- **Heatmaps**: Explore parameter relationships: +- **Parameter Exploration**: Adjust simulation parameters to see their impact on results +- **Statistics**: View aggregated statistics across parameter combinations + +## Architecture + +The application consists of two main components: + +1. **Frontend**: React-based web UI with Next.js framework +2. **Backend**: FastAPI server that processes and serves simulation data + +## Running the Application + +### Prerequisites + +- Node.js 18.x or later +- Python 3.9 or later +- DAS Simulator results (XML files in `results/` directory) + +### Backend Setup + +1. Install Python dependencies: + +```bash +pip install fastapi uvicorn pydantic +``` + +2. Start the backend server: + +```bash +python server.py +``` + +The server will start on http://localhost:8000 by default. + +### Frontend Setup + +1. Install Node.js dependencies: + +```bash +npm install +``` + +2. Start the development server: + +```bash +npm run dev +``` + +3. Open your browser and navigate to http://localhost:3000 + +## Project Structure + +``` +das-simulator-visualizer/ +├── app/ # Next.js app components +│ ├── page.tsx # Homepage (simulation list) +│ ├── simulations/[id]/ # Simulation detail pages +│ ├── globals.css # Global styles +│ └── layout.tsx # Layout component +├── components/ # React components +│ ├── graph-viewer.tsx # Graph visualization component +│ ├── heatmap-viewer.tsx # Heatmap visualization component +│ ├── parameter-selector.tsx # Parameter adjustment component +│ ├── simulation-list.tsx # Simulations listing component +│ ├── simulation-metadata.tsx # Simulation details component +│ ├── simulation-provider.tsx # Data provider component +│ ├── statistics-summary.tsx # Statistics visualization component +│ └── ui/ # UI components (buttons, cards, etc.) +├── lib/ # Utility functions and services +│ └── simulation-service.ts # API client for backend communication +├── public/ # Static assets +└── package.json # Node.js package configuration +``` +## Deployment + +For production deployment: + +1. Build the frontend: + +```bash +npm run build +``` \ No newline at end of file diff --git a/frontend/app/favicon.ico b/das-simulator-visualizer/app/favicon.ico similarity index 100% rename from frontend/app/favicon.ico rename to das-simulator-visualizer/app/favicon.ico diff --git a/frontend/app/globals.css b/das-simulator-visualizer/app/globals.css similarity index 100% rename from frontend/app/globals.css rename to das-simulator-visualizer/app/globals.css diff --git a/frontend/app/layout.tsx b/das-simulator-visualizer/app/layout.tsx similarity index 100% rename from frontend/app/layout.tsx rename to das-simulator-visualizer/app/layout.tsx diff --git a/frontend/app/page.tsx b/das-simulator-visualizer/app/page.tsx similarity index 100% rename from frontend/app/page.tsx rename to das-simulator-visualizer/app/page.tsx diff --git a/frontend/app/simulations/[id]/page.tsx b/das-simulator-visualizer/app/simulations/[id]/page.tsx similarity index 96% rename from frontend/app/simulations/[id]/page.tsx rename to das-simulator-visualizer/app/simulations/[id]/page.tsx index 569242a..933c171 100644 --- a/frontend/app/simulations/[id]/page.tsx +++ b/das-simulator-visualizer/app/simulations/[id]/page.tsx @@ -16,12 +16,22 @@ import { useSimulation } from "@/components/simulation-provider" import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog" import { Skeleton } from "@/components/ui/skeleton" +interface SimulationParams { + numberNodes: number + failureRate: number + blockSize: number + netDegree: number + chi: number + maliciousNodes: number + run: number +} + export default function SimulationDetailPage() { const params = useParams() const router = useRouter() const { getSimulationById, loading } = useSimulation() const [simulation, setSimulation] = useState(null) - const [selectedParams, setSelectedParams] = useState({ + const [selectedParams, setSelectedParams] = useState({ numberNodes: 128, failureRate: 40, blockSize: 64, @@ -41,7 +51,6 @@ export default function SimulationDetailPage() { } } catch (error) { console.error("Error fetching simulation:", error) - // Set a default simulation or handle the error appropriately setSimulation(null) } } @@ -82,7 +91,7 @@ export default function SimulationDetailPage() { } const handleParamChange = (param: string, value: any) => { - setSelectedParams((prev) => ({ + setSelectedParams((prev: SimulationParams) => ({ ...prev, [param]: value, })) diff --git a/frontend/components.json b/das-simulator-visualizer/components.json similarity index 100% rename from frontend/components.json rename to das-simulator-visualizer/components.json diff --git a/frontend/components/graph-viewer.tsx b/das-simulator-visualizer/components/graph-viewer.tsx similarity index 96% rename from frontend/components/graph-viewer.tsx rename to das-simulator-visualizer/components/graph-viewer.tsx index f2bec9a..7932011 100644 --- a/frontend/components/graph-viewer.tsx +++ b/das-simulator-visualizer/components/graph-viewer.tsx @@ -1,4 +1,3 @@ -// graph-viewer.tsx import React, { useEffect, useState } from "react"; import Image from "next/image"; import { AlertCircle } from "lucide-react"; @@ -25,7 +24,6 @@ export function GraphViewer({ simulation, parameters, graphType, fullscreen = fa setLoading(true); setError(null); - // Get the graph URL based on the parameters and graph type const url = await getGraphUrl( simulation.id, parameters.numberNodes, diff --git a/frontend/components/heatmap-viewer.tsx b/das-simulator-visualizer/components/heatmap-viewer.tsx similarity index 97% rename from frontend/components/heatmap-viewer.tsx rename to das-simulator-visualizer/components/heatmap-viewer.tsx index e4f0445..f05287a 100644 --- a/frontend/components/heatmap-viewer.tsx +++ b/das-simulator-visualizer/components/heatmap-viewer.tsx @@ -1,4 +1,3 @@ -// heatmap-viewer.tsx import React, { useEffect, useState } from "react"; import Image from "next/image"; import { AlertCircle } from "lucide-react"; @@ -35,7 +34,6 @@ export function HeatmapViewer({ simulation, fullscreen = false }: HeatmapViewerP setLoading(true); setError(null); - // Get the heatmap URL based on the heatmap type const url = await getHeatmapUrl(simulation.id, heatmapType); setHeatmapUrl(url); } catch (err) { diff --git a/frontend/components/parameter-selector.tsx b/das-simulator-visualizer/components/parameter-selector.tsx similarity index 99% rename from frontend/components/parameter-selector.tsx rename to das-simulator-visualizer/components/parameter-selector.tsx index 7bd93fe..3ee5c26 100644 --- a/frontend/components/parameter-selector.tsx +++ b/das-simulator-visualizer/components/parameter-selector.tsx @@ -1,5 +1,3 @@ -// parameter-selector.tsx - import React from "react"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Label } from "@/components/ui/label"; diff --git a/frontend/components/simulation-list.tsx b/das-simulator-visualizer/components/simulation-list.tsx similarity index 94% rename from frontend/components/simulation-list.tsx rename to das-simulator-visualizer/components/simulation-list.tsx index d9065c0..67875cd 100644 --- a/frontend/components/simulation-list.tsx +++ b/das-simulator-visualizer/components/simulation-list.tsx @@ -31,24 +31,19 @@ export function SimulationList() { router.push(`/simulations/${id}`) } - // Función auxiliar para formatear fechas de manera segura const formatDate = (dateString: string) => { try { - // Corregir formato incorrecto con :00Z al final if (dateString.includes('T') && dateString.endsWith(':00Z')) { dateString = dateString.replace(':00Z', 'Z'); } - // Intenta parsear la fecha desde ISO const date = parseISO(dateString); return format(date, "PPP"); } catch (error) { console.error("Error formatting date:", dateString, error); - // Si falla, intenta otro enfoque: crear una fecha a partir del ID de simulación try { if (dateString.includes("_")) { - // Si la fecha es parte del ID (como en "2025-02-11_00-11-23_825") const parts = dateString.split("_"); if (parts.length >= 2) { const datePart = parts[0]; @@ -57,28 +52,23 @@ export function SimulationList() { } } - // Si todo lo anterior falla, intentar crear una fecha simple return new Date(dateString).toLocaleDateString(); } catch (e) { - // Si todo falla, devuelve un marcador de posición return "Date unavailable"; } } }; const filteredSimulations = simulations.filter((sim) => { - // Search filter if (searchTerm && !sim.id.toLowerCase().includes(searchTerm.toLowerCase())) { return false } - // Date filter if (dateRange.from) { try { const simDate = new Date(sim.date); if (simDate < dateRange.from) return false; } catch (e) { - // Si la fecha no se puede parsear, mantenemos el elemento console.warn("Could not parse date for filtering:", sim.date); } } @@ -87,12 +77,10 @@ export function SimulationList() { const simDate = new Date(sim.date); if (simDate > dateRange.to) return false; } catch (e) { - // Si la fecha no se puede parsear, mantenemos el elemento console.warn("Could not parse date for filtering:", sim.date); } } - // Success rate filter if (successFilter === "high" && sim.successRate < 75) { return false } diff --git a/frontend/components/simulation-metadata.tsx b/das-simulator-visualizer/components/simulation-metadata.tsx similarity index 87% rename from frontend/components/simulation-metadata.tsx rename to das-simulator-visualizer/components/simulation-metadata.tsx index 21ad31c..ab13eb0 100644 --- a/frontend/components/simulation-metadata.tsx +++ b/das-simulator-visualizer/components/simulation-metadata.tsx @@ -6,24 +6,19 @@ interface SimulationMetadataProps { simulation: any } -// Función auxiliar para formatear fechas de manera segura const formatDate = (dateString: string) => { try { - // Corregir formato incorrecto con :00Z al final if (dateString && dateString.includes('T') && dateString.endsWith(':00Z')) { dateString = dateString.replace(':00Z', 'Z'); } - // Intenta parsear la fecha desde ISO const date = parseISO(dateString); return format(date, "PPP 'at' p"); } catch (error) { console.error("Error formatting date:", dateString, error); - // Si falla, intenta otro enfoque: crear una fecha a partir del ID de simulación try { if (dateString && dateString.includes("_")) { - // Si la fecha es parte del ID (como en "2025-02-11_00-11-23_825") const parts = dateString.split("_"); if (parts.length >= 2) { const datePart = parts[0]; @@ -32,17 +27,14 @@ const formatDate = (dateString: string) => { } } - // Si todo lo anterior falla, usar el ID return dateString || "Date unavailable"; } catch (e) { - // Si todo falla, devuelve un marcador de posición return "Date unavailable"; } } }; export function SimulationMetadata({ simulation }: SimulationMetadataProps) { - // Manejar el caso donde simulation podría ser null o undefined if (!simulation) { return
Loading...
; } diff --git a/frontend/components/simulation-provider.tsx b/das-simulator-visualizer/components/simulation-provider.tsx similarity index 91% rename from frontend/components/simulation-provider.tsx rename to das-simulator-visualizer/components/simulation-provider.tsx index 5e9ebe7..6321e79 100644 --- a/frontend/components/simulation-provider.tsx +++ b/das-simulator-visualizer/components/simulation-provider.tsx @@ -34,12 +34,9 @@ export function SimulationProvider({ children }: { children: ReactNode }) { const getSimulationById = (id: string) => { try { - // First check if the simulation is in our cached state const cachedSimulation = simulations.find((sim) => sim.id === id) if (cachedSimulation) return cachedSimulation - // If not in cache, we'll return a promise that fetches it - // This is wrapped in a try/catch to handle any errors return { id, date: new Date().toISOString(), @@ -57,7 +54,6 @@ export function SimulationProvider({ children }: { children: ReactNode }) { } } catch (err) { console.error("Error getting simulation by ID:", err) - // Return a default simulation object instead of throwing return { id, date: new Date().toISOString(), diff --git a/frontend/components/statistics-summary.tsx b/das-simulator-visualizer/components/statistics-summary.tsx similarity index 99% rename from frontend/components/statistics-summary.tsx rename to das-simulator-visualizer/components/statistics-summary.tsx index 130f6fd..07387c1 100644 --- a/frontend/components/statistics-summary.tsx +++ b/das-simulator-visualizer/components/statistics-summary.tsx @@ -25,8 +25,7 @@ export function StatisticsSummary({ simulation }: StatisticsSummaryProps) { try { setLoading(true) setError(null) - - // Get the statistics for the simulation + const data = await getSimulationStats(simulation.id) setStats(data) } catch (err) { diff --git a/frontend/components/ui/alert.tsx b/das-simulator-visualizer/components/ui/alert.tsx similarity index 100% rename from frontend/components/ui/alert.tsx rename to das-simulator-visualizer/components/ui/alert.tsx diff --git a/frontend/components/ui/badge.tsx b/das-simulator-visualizer/components/ui/badge.tsx similarity index 100% rename from frontend/components/ui/badge.tsx rename to das-simulator-visualizer/components/ui/badge.tsx diff --git a/frontend/components/ui/button.tsx b/das-simulator-visualizer/components/ui/button.tsx similarity index 100% rename from frontend/components/ui/button.tsx rename to das-simulator-visualizer/components/ui/button.tsx diff --git a/frontend/components/ui/calendar.tsx b/das-simulator-visualizer/components/ui/calendar.tsx similarity index 100% rename from frontend/components/ui/calendar.tsx rename to das-simulator-visualizer/components/ui/calendar.tsx diff --git a/frontend/components/ui/card.tsx b/das-simulator-visualizer/components/ui/card.tsx similarity index 100% rename from frontend/components/ui/card.tsx rename to das-simulator-visualizer/components/ui/card.tsx diff --git a/frontend/components/ui/checkbox.tsx b/das-simulator-visualizer/components/ui/checkbox.tsx similarity index 100% rename from frontend/components/ui/checkbox.tsx rename to das-simulator-visualizer/components/ui/checkbox.tsx diff --git a/frontend/components/ui/dialog.tsx b/das-simulator-visualizer/components/ui/dialog.tsx similarity index 100% rename from frontend/components/ui/dialog.tsx rename to das-simulator-visualizer/components/ui/dialog.tsx diff --git a/frontend/components/ui/input.tsx b/das-simulator-visualizer/components/ui/input.tsx similarity index 100% rename from frontend/components/ui/input.tsx rename to das-simulator-visualizer/components/ui/input.tsx diff --git a/frontend/components/ui/label.tsx b/das-simulator-visualizer/components/ui/label.tsx similarity index 100% rename from frontend/components/ui/label.tsx rename to das-simulator-visualizer/components/ui/label.tsx diff --git a/frontend/components/ui/popover.tsx b/das-simulator-visualizer/components/ui/popover.tsx similarity index 100% rename from frontend/components/ui/popover.tsx rename to das-simulator-visualizer/components/ui/popover.tsx diff --git a/frontend/components/ui/select.tsx b/das-simulator-visualizer/components/ui/select.tsx similarity index 100% rename from frontend/components/ui/select.tsx rename to das-simulator-visualizer/components/ui/select.tsx diff --git a/frontend/components/ui/skeleton.tsx b/das-simulator-visualizer/components/ui/skeleton.tsx similarity index 100% rename from frontend/components/ui/skeleton.tsx rename to das-simulator-visualizer/components/ui/skeleton.tsx diff --git a/frontend/components/ui/slider.tsx b/das-simulator-visualizer/components/ui/slider.tsx similarity index 100% rename from frontend/components/ui/slider.tsx rename to das-simulator-visualizer/components/ui/slider.tsx diff --git a/frontend/components/ui/tabs.tsx b/das-simulator-visualizer/components/ui/tabs.tsx similarity index 100% rename from frontend/components/ui/tabs.tsx rename to das-simulator-visualizer/components/ui/tabs.tsx diff --git a/frontend/eslint.config.mjs b/das-simulator-visualizer/eslint.config.mjs similarity index 100% rename from frontend/eslint.config.mjs rename to das-simulator-visualizer/eslint.config.mjs diff --git a/das-simulator-visualizer/lib/simulation-service.ts b/das-simulator-visualizer/lib/simulation-service.ts new file mode 100644 index 0000000..dee61ac --- /dev/null +++ b/das-simulator-visualizer/lib/simulation-service.ts @@ -0,0 +1,153 @@ +// Base API URL - Adjust based on where your API is running +const API_BASE_URL = "http://localhost:8000/api"; + +interface Simulation { + id: string; + date: string; + parameters: { + numberNodes: { + min: number; + max: number; + step: number; + }; + failureRate: { + min: number; + max: number; + step: number; + }; + blockSize: { + value: number; + options: number[]; + }; + netDegree: { + value: number; + options: number[]; + }; + chi: { + value: number; + options: number[]; + }; + maliciousNodes?: { + value: number; + options: number[]; + }; + run: { + max: number; + }; + }; + successRate: number; + avgMissingSamples: number; + avgNodesReady: number; +} + +// Helper function to handle fetch errors +const handleFetchError = (error: any, fallbackMessage: string) => { + console.error(fallbackMessage, error); + throw new Error(fallbackMessage); +}; + +// Fetch all simulations +export const fetchSimulations = async (): Promise => { + try { + const response = await fetch(`${API_BASE_URL}/simulations`); + + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + + const data = await response.json(); + return data; + } catch (error) { + handleFetchError(error, "Error fetching simulations"); + throw error; + } +}; + +// Fetch a specific simulation by ID +export const fetchSimulationById = async (id: string): Promise => { + try { + const response = await fetch(`${API_BASE_URL}/simulations/${id}`); + + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + + const data = await response.json(); + return data; + } catch (error) { + handleFetchError(error, `Error fetching simulation with ID: ${id}`); + + throw error; + } +}; + +// Get URL for a specific graph +export const getGraphUrl = async ( + simulationId: string, + numberNodes: number, + failureRate: number, + blockSize: number, + netDegree: number, + chi: number, + run: number, + graphType: string, +): Promise => { + try { + // Modify the graphType based on the selection to match new file structure + let modifiedGraphType = graphType; + + // Map common graph types to their file names + const graphTypeMap: Record = { + 'missingSamples': 'missingSegments', + 'nodesReady': 'nodesReady', + 'sentData': 'sentData', + 'recvData': 'recvData', + 'dupData': 'dupData', + 'RowColDist': 'RowColDist', + 'restoreRowCount': 'restoreRowCount', + 'restoreColumnCount': 'restoreColumnCount', + 'messagesSent': 'messagesSent', + 'messagesRecv': 'messagesRecv' + }; + + if (graphTypeMap[graphType]) { + modifiedGraphType = graphTypeMap[graphType]; + } + + // Direct URL to the image - allow ECDF and boxen variations + return `${API_BASE_URL}/graph/${simulationId}/${numberNodes}/${failureRate}/${blockSize}/${netDegree}/${chi}/${run}/${modifiedGraphType}`; + } catch (error) { + console.error("Error getting graph URL:", error); + // Fallback to placeholder in development + return `/placeholder.svg?height=300&width=500&text=${graphType}_n${numberNodes}_f${failureRate}_b${blockSize}_d${netDegree}_c${chi}_r${run}`; + } +}; + +// Get URL for a specific heatmap +export const getHeatmapUrl = async (simulationId: string, heatmapType: string): Promise => { + try { + // Direct URL to the image + return `${API_BASE_URL}/heatmap/${simulationId}/${heatmapType}`; + } catch (error) { + console.error("Error getting heatmap URL:", error); + // Fallback to placeholder in development + return `/placeholder.svg?height=300&width=500&text=Heatmap_${heatmapType}_${simulationId}`; + } +}; + +// Get statistics for a simulation +export const getSimulationStats = async (simulationId: string): Promise => { + try { + const response = await fetch(`${API_BASE_URL}/stats/${simulationId}`); + + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + + const data = await response.json(); + return data; + } catch (error) { + handleFetchError(error, `Error fetching statistics for simulation: ${simulationId}`); + throw error; + } +}; \ No newline at end of file diff --git a/frontend/lib/utils.ts b/das-simulator-visualizer/lib/utils.ts similarity index 100% rename from frontend/lib/utils.ts rename to das-simulator-visualizer/lib/utils.ts diff --git a/frontend/next-env.d.ts b/das-simulator-visualizer/next-env.d.ts similarity index 100% rename from frontend/next-env.d.ts rename to das-simulator-visualizer/next-env.d.ts diff --git a/frontend/next.config.ts b/das-simulator-visualizer/next.config.ts similarity index 100% rename from frontend/next.config.ts rename to das-simulator-visualizer/next.config.ts diff --git a/frontend/package-lock.json b/das-simulator-visualizer/package-lock.json similarity index 100% rename from frontend/package-lock.json rename to das-simulator-visualizer/package-lock.json diff --git a/frontend/package.json b/das-simulator-visualizer/package.json similarity index 100% rename from frontend/package.json rename to das-simulator-visualizer/package.json diff --git a/frontend/postcss.config.mjs b/das-simulator-visualizer/postcss.config.mjs similarity index 100% rename from frontend/postcss.config.mjs rename to das-simulator-visualizer/postcss.config.mjs diff --git a/frontend/public/file.svg b/das-simulator-visualizer/public/file.svg similarity index 100% rename from frontend/public/file.svg rename to das-simulator-visualizer/public/file.svg diff --git a/frontend/public/globe.svg b/das-simulator-visualizer/public/globe.svg similarity index 100% rename from frontend/public/globe.svg rename to das-simulator-visualizer/public/globe.svg diff --git a/frontend/public/next.svg b/das-simulator-visualizer/public/next.svg similarity index 100% rename from frontend/public/next.svg rename to das-simulator-visualizer/public/next.svg diff --git a/frontend/public/vercel.svg b/das-simulator-visualizer/public/vercel.svg similarity index 100% rename from frontend/public/vercel.svg rename to das-simulator-visualizer/public/vercel.svg diff --git a/frontend/public/window.svg b/das-simulator-visualizer/public/window.svg similarity index 100% rename from frontend/public/window.svg rename to das-simulator-visualizer/public/window.svg diff --git a/frontend/tailwind.config.ts b/das-simulator-visualizer/tailwind.config.ts similarity index 100% rename from frontend/tailwind.config.ts rename to das-simulator-visualizer/tailwind.config.ts diff --git a/frontend/tsconfig.json b/das-simulator-visualizer/tsconfig.json similarity index 100% rename from frontend/tsconfig.json rename to das-simulator-visualizer/tsconfig.json diff --git a/frontend/lib/simulation-service.ts b/frontend/lib/simulation-service.ts deleted file mode 100644 index fd7e274..0000000 --- a/frontend/lib/simulation-service.ts +++ /dev/null @@ -1,292 +0,0 @@ -// Base API URL - Ajustar según donde se esté ejecutando tu API -const API_BASE_URL = "http://localhost:8000/api"; - -// Interfaces para tipado -interface Simulation { - id: string; - date: string; - parameters: { - numberNodes: { - min: number; - max: number; - step: number; - }; - failureRate: { - min: number; - max: number; - step: number; - }; - blockSize: { - value: number; - options: number[]; - }; - netDegree: { - value: number; - options: number[]; - }; - chi: { - value: number; - options: number[]; - }; - maliciousNodes?: { - value: number; - options: number[]; - }; - run: { - max: number; - }; - }; - successRate: number; - avgMissingSamples: number; - avgNodesReady: number; -} - -// Función auxiliar para manejar errores de fetch -const handleFetchError = (error: any, fallbackMessage: string) => { - console.error(fallbackMessage, error); - throw new Error(fallbackMessage); -}; - -// Fetch all simulations -export const fetchSimulations = async (): Promise => { - try { - const response = await fetch(`${API_BASE_URL}/simulations`); - - if (!response.ok) { - throw new Error(`HTTP error! Status: ${response.status}`); - } - - const data = await response.json(); - return data; - } catch (error) { - handleFetchError(error, "Error fetching simulations"); - - // En modo desarrollo, podríamos devolver datos de prueba como fallback - if (process.env.NODE_ENV === "development") { - console.warn("Using mock data as fallback in development mode"); - return generateMockSimulations(); - } - throw error; - } -}; - -// Fetch a specific simulation by ID -export const fetchSimulationById = async (id: string): Promise => { - try { - const response = await fetch(`${API_BASE_URL}/simulations/${id}`); - - if (!response.ok) { - throw new Error(`HTTP error! Status: ${response.status}`); - } - - const data = await response.json(); - return data; - } catch (error) { - handleFetchError(error, `Error fetching simulation with ID: ${id}`); - - // En modo desarrollo, podríamos devolver datos de prueba como fallback - if (process.env.NODE_ENV === "development") { - console.warn("Using mock data as fallback in development mode"); - return getMockSimulation(id); - } - throw error; - } -}; - -// Get URL for a specific graph -export const getGraphUrl = async ( - simulationId: string, - numberNodes: number, - failureRate: number, - blockSize: number, - netDegree: number, - chi: number, - run: number, - graphType: string, -): Promise => { - try { - // Modify the graphType based on the selection to match new file structure - let modifiedGraphType = graphType; - - // Map common graph types to their file names - const graphTypeMap: Record = { - 'missingSamples': 'missingSegments', - 'nodesReady': 'nodesReady', - 'sentData': 'sentData', - 'recvData': 'recvData', - 'dupData': 'dupData', - 'RowColDist': 'RowColDist', - 'restoreRowCount': 'restoreRowCount', - 'restoreColumnCount': 'restoreColumnCount', - 'messagesSent': 'messagesSent', - 'messagesRecv': 'messagesRecv' - }; - - if (graphTypeMap[graphType]) { - modifiedGraphType = graphTypeMap[graphType]; - } - - // URL directa a la imagen - allow ECDF and boxen variations - return `${API_BASE_URL}/graph/${simulationId}/${numberNodes}/${failureRate}/${blockSize}/${netDegree}/${chi}/${run}/${modifiedGraphType}`; - } catch (error) { - console.error("Error getting graph URL:", error); - // Fallback to placeholder in development - return `/placeholder.svg?height=300&width=500&text=${graphType}_n${numberNodes}_f${failureRate}_b${blockSize}_d${netDegree}_c${chi}_r${run}`; - } -}; - -// Get URL for a specific heatmap -export const getHeatmapUrl = async (simulationId: string, heatmapType: string): Promise => { - try { - // URL directa a la imagen - return `${API_BASE_URL}/heatmap/${simulationId}/${heatmapType}`; - } catch (error) { - console.error("Error getting heatmap URL:", error); - // Fallback to placeholder in development - return `/placeholder.svg?height=300&width=500&text=Heatmap_${heatmapType}_${simulationId}`; - } -}; - -// Get statistics for a simulation -export const getSimulationStats = async (simulationId: string): Promise => { - try { - const response = await fetch(`${API_BASE_URL}/stats/${simulationId}`); - - if (!response.ok) { - throw new Error(`HTTP error! Status: ${response.status}`); - } - - const data = await response.json(); - return data; - } catch (error) { - handleFetchError(error, `Error fetching statistics for simulation: ${simulationId}`); - - // En modo desarrollo, podríamos devolver datos de prueba como fallback - if (process.env.NODE_ENV === "development") { - console.warn("Using mock data as fallback in development mode"); - return getMockStats(); - } - throw error; - } -}; - -// ---------------------- -// Funciones auxiliares para datos de prueba (fallback) -// ---------------------- - -const randomInRange = (min: number, max: number) => Math.floor(Math.random() * (max - min + 1)) + min; - -// Generate mock simulation data -const generateMockSimulations = (): Simulation[] => { - const simulations = []; - - // Generate 10 mock simulations - for (let i = 0; i < 10; i++) { - const date = new Date(); - date.setDate(date.getDate() - i * 3); // Space them out by 3 days - - const id = `${date.toISOString().split("T")[0].replace(/-/g, "")}_${randomInRange(10, 99)}_DAS`; - - simulations.push({ - id, - date: date.toISOString(), - parameters: { - numberNodes: { - min: 128, - max: 512, - step: 128, - }, - failureRate: { - min: 40, - max: 80, - step: 10, - }, - blockSize: { - value: 64, - options: [32, 64, 128], - }, - netDegree: { - value: 8, - options: [4, 8, 16], - }, - chi: { - value: 2, - options: [1, 2, 3, 4], - }, - maliciousNodes: { - value: 0, - options: [0, 20, 40, 60], - }, - run: { - max: 2, - }, - }, - successRate: randomInRange(30, 95), - avgMissingSamples: randomInRange(5, 40), - avgNodesReady: randomInRange(60, 95), - }); - } - - return simulations; -}; - -// Get mock simulation by ID -const getMockSimulation = (id: string): Simulation => { - return { - id, - date: new Date().toISOString(), - parameters: { - numberNodes: { min: 128, max: 512, step: 128 }, - failureRate: { min: 40, max: 80, step: 10 }, - blockSize: { value: 64, options: [32, 64, 128] }, - netDegree: { value: 8, options: [4, 8, 16] }, - chi: { value: 2, options: [1, 2, 3, 4] }, - maliciousNodes: { value: 0, options: [0, 20, 40, 60] }, - run: { max: 2 }, - }, - successRate: 75, - avgMissingSamples: 20, - avgNodesReady: 80, - }; -}; - -// Get mock statistics -const getMockStats = () => { - const generateStatData = (prefix: string, count: number) => { - return Array.from({ length: count }, (_, i) => ({ - name: `${prefix}${i === 0 ? 128 : i === 1 ? 256 : i === 2 ? 384 : 512}`, - value: randomInRange(10, 90), - })); - }; - - const generateComparisonData = (prefix: string, count: number) => { - return Array.from({ length: count }, (_, i) => ({ - name: `${prefix}${i === 0 ? 128 : i === 1 ? 256 : i === 2 ? 384 : 512}`, - missingSamples: randomInRange(5, 40), - nodesReady: randomInRange(60, 95), - sentData: randomInRange(20, 100), - recvData: randomInRange(15, 90), - })); - }; - - return { - byNodes: { - missingSamples: generateStatData("Nodes: ", 4), - nodesReady: generateStatData("Nodes: ", 4), - sentData: generateStatData("Nodes: ", 4), - comparison: generateComparisonData("Nodes: ", 4), - }, - byFailureRate: { - missingSamples: generateStatData("Failure: ", 5), - nodesReady: generateStatData("Failure: ", 5), - sentData: generateStatData("Failure: ", 5), - comparison: generateComparisonData("Failure: ", 5), - }, - byChi: { - missingSamples: generateStatData("Chi: ", 4), - nodesReady: generateStatData("Chi: ", 4), - sentData: generateStatData("Chi: ", 4), - comparison: generateComparisonData("Chi: ", 4), - }, - }; -}; \ No newline at end of file diff --git a/server.py b/server.py index e39cffd..59bc6a6 100644 --- a/server.py +++ b/server.py @@ -10,15 +10,13 @@ from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import FileResponse from pydantic import BaseModel -# Ruta base donde se encuentran los resultados de las simulaciones RESULTS_DIR = "results" app = FastAPI(title="DAS Simulator API") -# Configurar CORS para permitir peticiones desde el frontend app.add_middleware( CORSMiddleware, - allow_origins=["*"], # En producción, restringe esto a la URL de tu frontend + allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], @@ -33,7 +31,7 @@ class SimulationInfo(BaseModel): avgNodesReady: float def parse_shape_string(shape_str: str) -> Dict[str, Any]: - """Parsea un string de shape para extraer los parámetros.""" + """Parse a shape string to extract the parameters.""" params = {} parts = shape_str.split("-") @@ -41,7 +39,6 @@ def parse_shape_string(shape_str: str) -> Dict[str, Any]: if i+1 < len(parts): key = parts[i] value = parts[i+1] - # Intentar convertir a entero si es posible try: params[key] = int(value) except ValueError: @@ -53,26 +50,21 @@ def parse_shape_string(shape_str: str) -> Dict[str, Any]: return params def calculate_success_rate(sim_dir: str) -> float: - """Calcula el porcentaje de éxito basado en los XML de resultados.""" + """Calculate the success rate based on the result XML files.""" xml_files = glob.glob(f"{sim_dir}/*.xml") total = len(xml_files) if total == 0: return 0.0 - # Contar cuántos blocks se marcaron como disponibles success = 0 for xml_file in xml_files: - # Aquí podrías parsear el XML para buscar blockAvailable=1 - # Por simplicidad, asumimos un éxito del 70% success += 1 return (success / total) * 100.0 def extract_parameters(sim_dir: str) -> Dict[str, Any]: - """Extrae los rangos de parámetros usados en la simulación.""" + """Extract the parameter ranges used in the simulation.""" xml_files = glob.glob(f"{sim_dir}/*.xml") - - # Extraer valores únicos para cada parámetro nn_values = set() fr_values = set() bs_values = set() @@ -100,7 +92,6 @@ def extract_parameters(sim_dir: str) -> Dict[str, Any]: if "r" in params: run_values.add(params["r"]) - # Crear el objeto de parámetros con min, max, step parameters = { "numberNodes": { "min": min(nn_values) if nn_values else 128, @@ -137,29 +128,25 @@ def extract_parameters(sim_dir: str) -> Dict[str, Any]: @app.get("/api/simulations", response_model=List[SimulationInfo]) async def get_simulations(): - """Obtiene la lista de todas las simulaciones disponibles.""" + """Get the list of all available simulations.""" simulations = [] - # Listar todos los directorios de simulación try: sim_dirs = [d for d in os.listdir(RESULTS_DIR) if os.path.isdir(os.path.join(RESULTS_DIR, d)) and not d.startswith(".")] except FileNotFoundError: - raise HTTPException(status_code=404, detail=f"No se encontró el directorio de resultados: {RESULTS_DIR}") + raise HTTPException(status_code=404, detail=f"Results directory not found: {RESULTS_DIR}") for sim_id in sim_dirs: sim_dir = os.path.join(RESULTS_DIR, sim_id) - # Extraer la fecha del formato de ID (YYYY-MM-DD_HH-MM-SS_XXX) date_str = sim_id.split("_")[0] + "T" + sim_id.split("_")[1].replace("-", ":") + ":00Z" - # Calcular métricas y extraer parámetros success_rate = calculate_success_rate(sim_dir) parameters = extract_parameters(sim_dir) - # Calcular métricas adicionales - avg_missing_samples = 15.0 # Valor simulado, deberías calcularlo realmente - avg_nodes_ready = 85.0 # Valor simulado, deberías calcularlo realmente + avg_missing_samples = 15.0 + avg_nodes_ready = 85.0 sim_info = SimulationInfo( id=sim_id, @@ -172,29 +159,25 @@ async def get_simulations(): simulations.append(sim_info) - # Ordenar por fecha, más reciente primero simulations.sort(key=lambda x: x.date, reverse=True) return simulations @app.get("/api/simulations/{sim_id}") async def get_simulation_by_id(sim_id: str): - """Obtiene los detalles de una simulación específica.""" + """Get the details of a specific simulation.""" sim_dir = os.path.join(RESULTS_DIR, sim_id) if not os.path.exists(sim_dir): - raise HTTPException(status_code=404, detail=f"Simulación no encontrada: {sim_id}") + raise HTTPException(status_code=404, detail=f"Simulation not found: {sim_id}") - # Extraer la fecha del formato de ID date_str = sim_id.split("_")[0] + "T" + sim_id.split("_")[1].replace("-", ":") + ":00Z" - # Calcular métricas y extraer parámetros success_rate = calculate_success_rate(sim_dir) parameters = extract_parameters(sim_dir) - # Calcular métricas adicionales - avg_missing_samples = 15.0 # Valor simulado - avg_nodes_ready = 85.0 # Valor simulado + avg_missing_samples = 15.0 + avg_nodes_ready = 85.0 sim_info = { "id": sim_id, @@ -218,30 +201,25 @@ async def get_graph( run: int, graph_type: str ): - """Devuelve la imagen del gráfico solicitado.""" + """Return the requested graph image.""" sim_dir = os.path.join(RESULTS_DIR, sim_id) if not os.path.exists(sim_dir): - raise HTTPException(status_code=404, detail=f"Simulación no encontrada: {sim_id}") + raise HTTPException(status_code=404, detail=f"Simulation not found: {sim_id}") - # Limpiar el tipo de gráfico (quitar la extensión si la tiene) if graph_type.endswith('.png'): graph_type = graph_type.replace('.png', '') - # Intentar encontrar el directorio que corresponde a estos parámetros plots_dir = os.path.join(sim_dir, "plots") - # Búsqueda directa primero - con los nuevos nombres de parámetro expected_pattern = f"bsrn-{bs}-*-bscn-{bs}-*-nn-{nn}-*-fr-{fr}-*-mn-*-nd-{nd}-*-r-{run}" matching_dirs = glob.glob(os.path.join(plots_dir, expected_pattern)) if matching_dirs: - # Si encontramos un directorio que coincide, buscar el archivo de gráfico graph_file = os.path.join(matching_dirs[0], f"{graph_type}.png") if os.path.exists(graph_file): return FileResponse(graph_file) - # Buscar gráficos específicos con diferentes patrones de nombre specific_patterns = [ f"{graph_type}.png", f"boxen_{graph_type}.png", @@ -252,36 +230,32 @@ async def get_graph( for pattern in specific_patterns: for root, dirs, files in os.walk(plots_dir): if pattern in files: - # Verificar si los parámetros clave están en la ruta if (f"nn-{nn}" in root or f"numberNodes-{nn}" in root) and (f"fr-{fr}" in root): full_path = os.path.join(root, pattern) if os.path.exists(full_path): return FileResponse(full_path) - # Si aún no encontramos, buscar en todos los subdirectorios recursivamente for root, dirs, files in os.walk(plots_dir): for file in files: if graph_type in file and file.endswith('.png'): return FileResponse(os.path.join(root, file)) - # Si realmente no encontramos nada, devolver cualquier imagen como respaldo for root, dirs, files in os.walk(plots_dir): for file in files: if file.endswith('.png'): return FileResponse(os.path.join(root, file), headers={"X-Warning": "Requested graph not found, showing another graph"}) - raise HTTPException(status_code=404, detail=f"Gráfico no encontrado para los parámetros especificados") + raise HTTPException(status_code=404, detail=f"Graph not found for the specified parameters") @app.get("/api/heatmap/{sim_id}/{heatmap_type}") async def get_heatmap(sim_id: str, heatmap_type: str): - """Devuelve la imagen del heatmap solicitado.""" + """Return the requested heatmap image.""" sim_dir = os.path.join(RESULTS_DIR, sim_id) if not os.path.exists(sim_dir): - raise HTTPException(status_code=404, detail=f"Simulación no encontrada: {sim_id}") + raise HTTPException(status_code=404, detail=f"Simulation not found: {sim_id}") - # Mapear el tipo de heatmap a los nombres de archivo y carpetas heatmap_mapping = { "nodesVsFailure": ["nnVsfr", "nodeVsFailure", "failureRateVsnumberNodes"], "nodesVsChi": ["nnVschir", "nodeVsChi", "nnVscusr"], @@ -293,18 +267,15 @@ async def get_heatmap(sim_id: str, heatmap_type: str): } if heatmap_type not in heatmap_mapping: - raise HTTPException(status_code=400, detail=f"Tipo de heatmap no válido: {heatmap_type}") + raise HTTPException(status_code=400, detail=f"Invalid heatmap type: {heatmap_type}") - # Buscar archivos de heatmap en el directorio correspondiente heatmap_dir = os.path.join(sim_dir, "heatmaps") if not os.path.exists(heatmap_dir): - # Si no hay directorio de heatmaps, buscar en la raíz de la simulación for pattern in heatmap_mapping[heatmap_type]: matching_files = glob.glob(os.path.join(sim_dir, f"*{pattern}*.png")) if matching_files: return FileResponse(matching_files[0]) - # Si no hay heatmaps, buscar cualquier imagen all_images = [] for root, dirs, files in os.walk(sim_dir): for file in files: @@ -314,9 +285,8 @@ async def get_heatmap(sim_id: str, heatmap_type: str): if all_images: return FileResponse(all_images[0], media_type="image/png") - raise HTTPException(status_code=404, detail=f"No se encontraron heatmaps para la simulación") + raise HTTPException(status_code=404, detail=f"No heatmaps found for the simulation") - # Buscar primero en subdirectorios específicos para los nuevos tipos de heatmap if heatmap_type in ["NWDegVsNodeOnRuntime", "NWDegVsMalNodeOnMissingSamples", "NWDegVsFailureRateOnMissingSamples"]: specific_dir = os.path.join(heatmap_dir, heatmap_type) if os.path.exists(specific_dir): @@ -324,49 +294,40 @@ async def get_heatmap(sim_id: str, heatmap_type: str): if png_files: return FileResponse(png_files[0]) - # Buscar con todas las variantes de nombres posibles possible_names = heatmap_mapping[heatmap_type] - # Primero buscar en subdirectorios específicos for pattern in possible_names: - # Buscar directorios que contengan el patrón matching_dirs = [d for d in os.listdir(heatmap_dir) if os.path.isdir(os.path.join(heatmap_dir, d)) and pattern.lower() in d.lower()] for subdir in matching_dirs: - # Buscar archivos PNG en este directorio png_files = glob.glob(os.path.join(heatmap_dir, subdir, "*.png")) if png_files: return FileResponse(png_files[0]) - # Si no encontramos nada, buscar cualquier PNG en heatmaps for root, dirs, files in os.walk(heatmap_dir): for file in files: if file.endswith(".png"): return FileResponse(os.path.join(root, file)) - # Si no encontramos ningún heatmap, verificar si hay alguna imagen en plots plots_dir = os.path.join(sim_dir, "plots") if os.path.exists(plots_dir): for root, dirs, files in os.walk(plots_dir): for file in files: if file.endswith(".png"): return FileResponse(os.path.join(root, file), - headers={"X-Warning": "Heatmap no encontrado, mostrando otra imagen"}) + headers={"X-Warning": "Heatmap not found, showing another image"}) - raise HTTPException(status_code=404, detail=f"No se encontró heatmap del tipo {heatmap_type}") + raise HTTPException(status_code=404, detail=f"No heatmap of type {heatmap_type} found") @app.get("/api/stats/{sim_id}") async def get_simulation_stats(sim_id: str): - """Obtiene estadísticas para la simulación especificada.""" + """Get statistics for the specified simulation.""" sim_dir = os.path.join(RESULTS_DIR, sim_id) if not os.path.exists(sim_dir): - raise HTTPException(status_code=404, detail=f"Simulación no encontrada: {sim_id}") - - # Aquí deberías procesar los archivos XML y generar estadísticas reales - # Por ahora, retornaremos datos de muestra similares a los datos de prueba del frontend + raise HTTPException(status_code=404, detail=f"Simulation not found: {sim_id}") def generate_stat_data(prefix, count, min_val=10, max_val=90): import random @@ -386,7 +347,6 @@ async def get_simulation_stats(sim_id: str): for i in range(count) ] - # Estos datos deberían generarse procesando los resultados reales stats = { "byNodes": { "missingSamples": generate_stat_data("Nodes: ", 4),