mirror of
https://github.com/logos-storage/das-research.git
synced 2026-01-03 13:43:11 +00:00
Merge 7ceee1af13a117ba8d79cc5f4aa30d52b63e3e87 into f36c3c85ba31ed0fd27c3650794a511ad994a661
This commit is contained in:
commit
f82abbeaf2
6
.gitignore
vendored
6
.gitignore
vendored
@ -4,4 +4,8 @@ results/*
|
||||
myenv*/
|
||||
doc/_build
|
||||
!results/plots.py
|
||||
Frontend/
|
||||
das-simulator-visualizer/node_modules
|
||||
das-simulator-visualizer/build
|
||||
das-simulator-visualizer/.next
|
||||
.DS_Store
|
||||
|
||||
|
||||
91
das-simulator-visualizer/README.md
Normal file
91
das-simulator-visualizer/README.md
Normal file
@ -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
|
||||
```
|
||||
BIN
das-simulator-visualizer/app/favicon.ico
Normal file
BIN
das-simulator-visualizer/app/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
87
das-simulator-visualizer/app/globals.css
Normal file
87
das-simulator-visualizer/app/globals.css
Normal file
@ -0,0 +1,87 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 84% 4.9%;
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 84% 4.9%;
|
||||
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
--ring: 222.2 84% 4.9%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
|
||||
--chart-1: 222.2 47.4% 11.2%;
|
||||
--chart-2: 215 25% 27%;
|
||||
--chart-3: 0 84.2% 60.2%;
|
||||
--chart-4: 24.6 95% 53.1%;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
|
||||
--card: 222.2 84% 4.9%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
|
||||
--popover: 222.2 84% 4.9%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
|
||||
--primary: 210 40% 98%;
|
||||
--primary-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--secondary: 217.2 32.6% 17.5%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
|
||||
--accent: 217.2 32.6% 17.5%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--ring: 212.7 26.8% 83.9%;
|
||||
|
||||
--chart-1: 210 40% 98%;
|
||||
--chart-2: 215 20.2% 65.1%;
|
||||
--chart-3: 0 62.8% 30.6%;
|
||||
--chart-4: 20.5 90% 48.2%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
27
das-simulator-visualizer/app/layout.tsx
Normal file
27
das-simulator-visualizer/app/layout.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import type React from "react"
|
||||
import { SimulationProvider } from "@/components/simulation-provider"
|
||||
import type { Metadata } from "next"
|
||||
import { Inter } from "next/font/google"
|
||||
import "./globals.css"
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] })
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "DAS Simulator Visualizer",
|
||||
description: "Visualize Data Availability Sampling simulation results",
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>
|
||||
<SimulationProvider>{children}</SimulationProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
|
||||
22
das-simulator-visualizer/app/page.tsx
Normal file
22
das-simulator-visualizer/app/page.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import { SimulationList } from "@/components/simulation-list"
|
||||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<div className="flex flex-col min-h-screen">
|
||||
<header className="border-b bg-background">
|
||||
<div className="container flex h-16 items-center px-4 md:px-6">
|
||||
<h1 className="text-lg font-semibold">DAS Simulator Visualizer</h1>
|
||||
</div>
|
||||
</header>
|
||||
<main className="flex-1 container py-6 px-4 md:px-6">
|
||||
<SimulationList />
|
||||
</main>
|
||||
<footer className="border-t py-4 bg-background">
|
||||
<div className="container flex flex-col items-center justify-between gap-4 px-4 md:flex-row md:px-6">
|
||||
<p className="text-sm text-muted-foreground">© {new Date().getFullYear()} DAS Simulator Visualizer</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
229
das-simulator-visualizer/app/simulations/[id]/page.tsx
Normal file
229
das-simulator-visualizer/app/simulations/[id]/page.tsx
Normal file
@ -0,0 +1,229 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect, useState } from "react"
|
||||
import { useParams, useRouter } from "next/navigation"
|
||||
import { ArrowLeft, Download, Maximize } from "lucide-react"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { SimulationMetadata } from "@/components/simulation-metadata"
|
||||
import { ParameterSelector } from "@/components/parameter-selector"
|
||||
import { GraphViewer } from "@/components/graph-viewer"
|
||||
import { HeatmapViewer } from "@/components/heatmap-viewer"
|
||||
import { StatisticsSummary } from "@/components/statistics-summary"
|
||||
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<any>(null)
|
||||
const [selectedParams, setSelectedParams] = useState<SimulationParams>({
|
||||
numberNodes: 128,
|
||||
failureRate: 40,
|
||||
blockSize: 64,
|
||||
netDegree: 8,
|
||||
chi: 2,
|
||||
maliciousNodes: 0,
|
||||
run: 0,
|
||||
})
|
||||
const [graphType, setGraphType] = useState("missingSegments")
|
||||
|
||||
useEffect(() => {
|
||||
const fetchSimulation = async () => {
|
||||
try {
|
||||
if (params.id) {
|
||||
const sim = getSimulationById(params.id as string)
|
||||
setSimulation(sim)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching simulation:", error)
|
||||
setSimulation(null)
|
||||
}
|
||||
}
|
||||
|
||||
fetchSimulation()
|
||||
}, [params.id, getSimulationById])
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="container py-6 px-4 md:px-6">
|
||||
<div className="flex items-center mb-6">
|
||||
<Button variant="ghost" size="icon" onClick={() => router.back()}>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
<Skeleton className="h-8 w-64 ml-2" />
|
||||
</div>
|
||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
<Skeleton className="h-[200px] w-full" />
|
||||
<Skeleton className="h-[200px] w-full" />
|
||||
<Skeleton className="h-[200px] w-full" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!simulation) {
|
||||
return (
|
||||
<div className="container py-6 px-4 md:px-6">
|
||||
<div className="flex items-center mb-6">
|
||||
<Button variant="ghost" size="icon" onClick={() => router.back()}>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
<h1 className="text-xl font-semibold ml-2">Simulation not found</h1>
|
||||
</div>
|
||||
<p>The requested simulation could not be found.</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const handleParamChange = (param: string, value: any) => {
|
||||
setSelectedParams((prev: SimulationParams) => ({
|
||||
...prev,
|
||||
[param]: value,
|
||||
}))
|
||||
}
|
||||
|
||||
const handleGraphTypeChange = (type: string) => {
|
||||
setGraphType(type)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container py-6 px-4 md:px-6">
|
||||
<div className="flex items-center mb-6">
|
||||
<Button variant="ghost" size="icon" onClick={() => router.back()}>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
<h1 className="text-xl font-semibold ml-2">Simulation: {simulation.id}</h1>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 md:grid-cols-12">
|
||||
{/* Sidebar with metadata and parameter selection */}
|
||||
<div className="md:col-span-4 lg:col-span-3 space-y-6">
|
||||
<SimulationMetadata simulation={simulation} />
|
||||
<ParameterSelector
|
||||
parameters={simulation.parameters}
|
||||
selectedParams={selectedParams}
|
||||
onParamChange={handleParamChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Main content area */}
|
||||
<div className="md:col-span-8 lg:col-span-9 space-y-6">
|
||||
<Tabs defaultValue="graphs" className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-3">
|
||||
<TabsTrigger value="graphs">Graphs</TabsTrigger>
|
||||
<TabsTrigger value="heatmaps">Heatmaps</TabsTrigger>
|
||||
<TabsTrigger value="statistics">Statistics</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="graphs" className="space-y-4">
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<CardTitle>Graph Viewer</CardTitle>
|
||||
<CardDescription>View detailed graphs for selected parameters</CardDescription>
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline" size="icon">
|
||||
<Maximize className="h-4 w-4" />
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-4xl">
|
||||
<GraphViewer
|
||||
simulation={simulation}
|
||||
parameters={selectedParams}
|
||||
graphType={graphType}
|
||||
fullscreen
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<Button variant="outline" size="icon">
|
||||
<Download className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="mb-4">
|
||||
<Tabs value={graphType} onValueChange={handleGraphTypeChange} className="w-full">
|
||||
<TabsList className="grid grid-cols-3 md:grid-cols-7 w-full">
|
||||
<TabsTrigger value="missingSegments">Missing</TabsTrigger>
|
||||
<TabsTrigger value="nodesReady">Ready</TabsTrigger>
|
||||
<TabsTrigger value="sentData">Sent</TabsTrigger>
|
||||
<TabsTrigger value="recvData">Received</TabsTrigger>
|
||||
<TabsTrigger value="dupData">Duplicate</TabsTrigger>
|
||||
<TabsTrigger value="RowColDist">Distribution</TabsTrigger>
|
||||
<TabsTrigger value="ecdf_restoreRowCount">Row Count</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</div>
|
||||
<GraphViewer simulation={simulation} parameters={selectedParams} graphType={graphType} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="heatmaps" className="space-y-4">
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<CardTitle>Heatmap Viewer</CardTitle>
|
||||
<CardDescription>Explore parameter relationships through heatmaps</CardDescription>
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline" size="icon">
|
||||
<Maximize className="h-4 w-4" />
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-4xl">
|
||||
<HeatmapViewer simulation={simulation} fullscreen />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<Button variant="outline" size="icon">
|
||||
<Download className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<HeatmapViewer simulation={simulation} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="statistics" className="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Statistics Summary</CardTitle>
|
||||
<CardDescription>View aggregated statistics across all parameter combinations</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<StatisticsSummary simulation={simulation} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
21
das-simulator-visualizer/components.json
Normal file
21
das-simulator-visualizer/components.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": true,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.ts",
|
||||
"css": "app/globals.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"iconLibrary": "lucide"
|
||||
}
|
||||
77
das-simulator-visualizer/components/graph-viewer.tsx
Normal file
77
das-simulator-visualizer/components/graph-viewer.tsx
Normal file
@ -0,0 +1,77 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { AlertCircle } from "lucide-react";
|
||||
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { getGraphUrl } from "@/lib/simulation-service";
|
||||
|
||||
interface GraphViewerProps {
|
||||
simulation: any;
|
||||
parameters: any;
|
||||
graphType: string;
|
||||
fullscreen?: boolean;
|
||||
}
|
||||
|
||||
export function GraphViewer({ simulation, parameters, graphType, fullscreen = false }: GraphViewerProps) {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [graphUrl, setGraphUrl] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchGraph = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
const url = await getGraphUrl(
|
||||
simulation.id,
|
||||
parameters.numberNodes,
|
||||
parameters.failureRate,
|
||||
parameters.blockSize,
|
||||
parameters.netDegree,
|
||||
parameters.chi,
|
||||
parameters.run,
|
||||
graphType,
|
||||
);
|
||||
|
||||
setGraphUrl(url);
|
||||
} catch (err) {
|
||||
console.error("Error loading graph:", err);
|
||||
setError("Failed to load graph. Please try again later.");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchGraph();
|
||||
}, [simulation.id, parameters, graphType]);
|
||||
|
||||
return (
|
||||
<div className={`w-full ${fullscreen ? "h-[600px]" : "h-[300px]"} relative`}>
|
||||
{loading ? (
|
||||
<Skeleton className={`w-full ${fullscreen ? "h-[600px]" : "h-[300px]"}`} />
|
||||
) : error ? (
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertTitle>Error</AlertTitle>
|
||||
<AlertDescription>{error}</AlertDescription>
|
||||
</Alert>
|
||||
) : !graphUrl ? (
|
||||
<Alert>
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertTitle>No data available</AlertTitle>
|
||||
<AlertDescription>No graph data available for the selected parameters.</AlertDescription>
|
||||
</Alert>
|
||||
) : (
|
||||
<Image
|
||||
src={graphUrl}
|
||||
alt={`${graphType} graph for the selected parameters`}
|
||||
fill
|
||||
className="object-contain"
|
||||
onError={() => setError("Failed to load graph image.")}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
89
das-simulator-visualizer/components/heatmap-viewer.tsx
Normal file
89
das-simulator-visualizer/components/heatmap-viewer.tsx
Normal file
@ -0,0 +1,89 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { AlertCircle } from "lucide-react";
|
||||
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { getHeatmapUrl } from "@/lib/simulation-service";
|
||||
|
||||
interface HeatmapViewerProps {
|
||||
simulation: any;
|
||||
fullscreen?: boolean;
|
||||
}
|
||||
|
||||
export function HeatmapViewer({ simulation, fullscreen = false }: HeatmapViewerProps) {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [heatmapType, setHeatmapType] = useState("nodesVsFailure");
|
||||
const [heatmapUrl, setHeatmapUrl] = useState<string | null>(null);
|
||||
|
||||
const heatmapTypes = [
|
||||
{ id: "nodesVsFailure", label: "Nodes vs Failure" },
|
||||
{ id: "nodesVsChi", label: "Nodes vs Chi" },
|
||||
{ id: "failureVsChi", label: "Failure vs Chi" },
|
||||
{ id: "failureVsNetDegree", label: "Failure vs Net Degree" },
|
||||
{ id: "NWDegVsNodeOnRuntime", label: "Network Degree vs Nodes" },
|
||||
{ id: "NWDegVsMalNodeOnMissingSamples", label: "Net Degree vs Malicious Nodes" },
|
||||
{ id: "NWDegVsFailureRateOnMissingSamples", label: "Net Degree vs Failure Rate" },
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
const fetchHeatmap = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
const url = await getHeatmapUrl(simulation.id, heatmapType);
|
||||
setHeatmapUrl(url);
|
||||
} catch (err) {
|
||||
console.error("Error loading heatmap:", err);
|
||||
setError("Failed to load heatmap. Please try again later.");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchHeatmap();
|
||||
}, [simulation.id, heatmapType]);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<Tabs value={heatmapType} onValueChange={setHeatmapType} className="w-full">
|
||||
<TabsList className="grid grid-cols-2 md:grid-cols-4 w-full">
|
||||
{heatmapTypes.map((type) => (
|
||||
<TabsTrigger key={type.id} value={type.id}>
|
||||
{type.label}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
|
||||
<div className={`w-full ${fullscreen ? "h-[600px]" : "h-[300px]"} relative`}>
|
||||
{loading ? (
|
||||
<Skeleton className={`w-full ${fullscreen ? "h-[600px]" : "h-[300px]"}`} />
|
||||
) : error ? (
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertTitle>Error</AlertTitle>
|
||||
<AlertDescription>{error}</AlertDescription>
|
||||
</Alert>
|
||||
) : !heatmapUrl ? (
|
||||
<Alert>
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertTitle>No data available</AlertTitle>
|
||||
<AlertDescription>No heatmap data available for the selected parameters.</AlertDescription>
|
||||
</Alert>
|
||||
) : (
|
||||
<Image
|
||||
src={heatmapUrl}
|
||||
alt={`${heatmapType} heatmap`}
|
||||
fill
|
||||
className="object-contain"
|
||||
onError={() => setError("Failed to load heatmap image.")}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
146
das-simulator-visualizer/components/parameter-selector.tsx
Normal file
146
das-simulator-visualizer/components/parameter-selector.tsx
Normal file
@ -0,0 +1,146 @@
|
||||
import React from "react";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Slider } from "@/components/ui/slider";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
|
||||
interface ParameterSelectorProps {
|
||||
parameters: any;
|
||||
selectedParams: any;
|
||||
onParamChange: (param: string, value: any) => void;
|
||||
}
|
||||
|
||||
export function ParameterSelector({ parameters, selectedParams, onParamChange }: ParameterSelectorProps) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Parameter Selection</CardTitle>
|
||||
<CardDescription>Adjust parameters to view different simulation results</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<Label htmlFor="numberNodes">Number of Nodes</Label>
|
||||
<span className="text-sm">{selectedParams.numberNodes}</span>
|
||||
</div>
|
||||
<Slider
|
||||
id="numberNodes"
|
||||
min={parameters.numberNodes.min}
|
||||
max={parameters.numberNodes.max}
|
||||
step={parameters.numberNodes.step || 128}
|
||||
value={[selectedParams.numberNodes]}
|
||||
onValueChange={(value) => onParamChange("numberNodes", value[0])}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<Label htmlFor="failureRate">Failure Rate (%)</Label>
|
||||
<span className="text-sm">{selectedParams.failureRate}%</span>
|
||||
</div>
|
||||
<Slider
|
||||
id="failureRate"
|
||||
min={parameters.failureRate.min}
|
||||
max={parameters.failureRate.max}
|
||||
step={parameters.failureRate.step || 10}
|
||||
value={[selectedParams.failureRate]}
|
||||
onValueChange={(value) => onParamChange("failureRate", value[0])}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{parameters.maliciousNodes && (
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<Label htmlFor="maliciousNodes">Malicious Nodes (%)</Label>
|
||||
<span className="text-sm">{selectedParams.maliciousNodes || 0}%</span>
|
||||
</div>
|
||||
<Slider
|
||||
id="maliciousNodes"
|
||||
min={0}
|
||||
max={100}
|
||||
step={20}
|
||||
value={[selectedParams.maliciousNodes || 0]}
|
||||
onValueChange={(value) => onParamChange("maliciousNodes", value[0])}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="blockSize">Block Size</Label>
|
||||
<Select
|
||||
value={selectedParams.blockSize.toString()}
|
||||
onValueChange={(value) => onParamChange("blockSize", Number.parseInt(value))}
|
||||
>
|
||||
<SelectTrigger id="blockSize">
|
||||
<SelectValue placeholder="Select block size" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{parameters.blockSize.options?.map((option: number) => (
|
||||
<SelectItem key={option} value={option.toString()}>
|
||||
{option}
|
||||
</SelectItem>
|
||||
)) || <SelectItem value={parameters.blockSize.value.toString()}>{parameters.blockSize.value}</SelectItem>}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="netDegree">Network Degree</Label>
|
||||
<Select
|
||||
value={selectedParams.netDegree.toString()}
|
||||
onValueChange={(value) => onParamChange("netDegree", Number.parseInt(value))}
|
||||
>
|
||||
<SelectTrigger id="netDegree">
|
||||
<SelectValue placeholder="Select network degree" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{parameters.netDegree.options?.map((option: number) => (
|
||||
<SelectItem key={option} value={option.toString()}>
|
||||
{option}
|
||||
</SelectItem>
|
||||
)) || <SelectItem value={parameters.netDegree.value.toString()}>{parameters.netDegree.value}</SelectItem>}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="chi">Custody Size</Label>
|
||||
<Select
|
||||
value={selectedParams.chi.toString()}
|
||||
onValueChange={(value) => onParamChange("chi", Number.parseInt(value))}
|
||||
>
|
||||
<SelectTrigger id="chi">
|
||||
<SelectValue placeholder="Select custody size" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{parameters.chi.options?.map((option: number) => (
|
||||
<SelectItem key={option} value={option.toString()}>
|
||||
{option}
|
||||
</SelectItem>
|
||||
)) || <SelectItem value={parameters.chi.value.toString()}>{parameters.chi.value}</SelectItem>}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="run">Run</Label>
|
||||
<Select
|
||||
value={selectedParams.run.toString()}
|
||||
onValueChange={(value) => onParamChange("run", Number.parseInt(value))}
|
||||
>
|
||||
<SelectTrigger id="run">
|
||||
<SelectValue placeholder="Select run" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{Array.from({ length: parameters.run.max + 1 }, (_, i) => (
|
||||
<SelectItem key={i} value={i.toString()}>
|
||||
Run {i}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
274
das-simulator-visualizer/components/simulation-list.tsx
Normal file
274
das-simulator-visualizer/components/simulation-list.tsx
Normal file
@ -0,0 +1,274 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { format, parseISO } from "date-fns"
|
||||
import { Calendar, ChevronRight, Filter, Search } from "lucide-react"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Skeleton } from "@/components/ui/skeleton"
|
||||
import { useSimulation } from "./simulation-provider"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
|
||||
import { Calendar as CalendarComponent } from "@/components/ui/calendar"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
|
||||
export function SimulationList() {
|
||||
const router = useRouter()
|
||||
const { simulations, loading, error } = useSimulation()
|
||||
const [searchTerm, setSearchTerm] = useState("")
|
||||
const [dateRange, setDateRange] = useState<{ from: Date | undefined; to: Date | undefined }>({
|
||||
from: undefined,
|
||||
to: undefined,
|
||||
})
|
||||
const [showFilters, setShowFilters] = useState(false)
|
||||
const [successFilter, setSuccessFilter] = useState<string | null>(null)
|
||||
|
||||
const handleSimulationSelect = (id: string) => {
|
||||
router.push(`/simulations/${id}`)
|
||||
}
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
try {
|
||||
if (dateString.includes('T') && dateString.endsWith(':00Z')) {
|
||||
dateString = dateString.replace(':00Z', 'Z');
|
||||
}
|
||||
|
||||
const date = parseISO(dateString);
|
||||
return format(date, "PPP");
|
||||
} catch (error) {
|
||||
console.error("Error formatting date:", dateString, error);
|
||||
|
||||
try {
|
||||
if (dateString.includes("_")) {
|
||||
const parts = dateString.split("_");
|
||||
if (parts.length >= 2) {
|
||||
const datePart = parts[0];
|
||||
const timePart = parts[1].replace(/-/g, ":");
|
||||
return `${datePart} ${timePart}`;
|
||||
}
|
||||
}
|
||||
|
||||
return new Date(dateString).toLocaleDateString();
|
||||
} catch (e) {
|
||||
return "Date unavailable";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const filteredSimulations = simulations.filter((sim) => {
|
||||
if (searchTerm && !sim.id.toLowerCase().includes(searchTerm.toLowerCase())) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (dateRange.from) {
|
||||
try {
|
||||
const simDate = new Date(sim.date);
|
||||
if (simDate < dateRange.from) return false;
|
||||
} catch (e) {
|
||||
console.warn("Could not parse date for filtering:", sim.date);
|
||||
}
|
||||
}
|
||||
if (dateRange.to) {
|
||||
try {
|
||||
const simDate = new Date(sim.date);
|
||||
if (simDate > dateRange.to) return false;
|
||||
} catch (e) {
|
||||
console.warn("Could not parse date for filtering:", sim.date);
|
||||
}
|
||||
}
|
||||
|
||||
if (successFilter === "high" && sim.successRate < 75) {
|
||||
return false
|
||||
}
|
||||
if (successFilter === "medium" && (sim.successRate < 50 || sim.successRate >= 75)) {
|
||||
return false
|
||||
}
|
||||
if (successFilter === "low" && sim.successRate >= 50) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center p-8">
|
||||
<h2 className="text-xl font-semibold text-red-500 mb-2">Error</h2>
|
||||
<p className="text-muted-foreground">{error}</p>
|
||||
<Button className="mt-4" onClick={() => window.location.reload()}>
|
||||
Try Again
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex flex-col sm:flex-row gap-4 items-start sm:items-center justify-between">
|
||||
<h2 className="text-2xl font-bold tracking-tight">Simulation Runs</h2>
|
||||
<div className="flex gap-2 w-full sm:w-auto">
|
||||
<div className="relative w-full sm:w-auto">
|
||||
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
type="search"
|
||||
placeholder="Search simulations..."
|
||||
className="w-full sm:w-[250px] pl-8"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<Popover open={showFilters} onOpenChange={setShowFilters}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="outline" size="icon">
|
||||
<Filter className="h-4 w-4" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80">
|
||||
<div className="grid gap-4">
|
||||
<div className="space-y-2">
|
||||
<h4 className="font-medium leading-none">Filters</h4>
|
||||
<p className="text-sm text-muted-foreground">Filter simulation results by date and success rate</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Date Range</Label>
|
||||
<CalendarComponent
|
||||
mode="range"
|
||||
selected={dateRange}
|
||||
onSelect={setDateRange as any}
|
||||
className="rounded-md border"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Success Rate</Label>
|
||||
<div className="grid gap-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="success-high"
|
||||
checked={successFilter === "high"}
|
||||
onCheckedChange={() => setSuccessFilter(successFilter === "high" ? null : "high")}
|
||||
/>
|
||||
<label htmlFor="success-high" className="text-sm">
|
||||
High (75-100%)
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="success-medium"
|
||||
checked={successFilter === "medium"}
|
||||
onCheckedChange={() => setSuccessFilter(successFilter === "medium" ? null : "medium")}
|
||||
/>
|
||||
<label htmlFor="success-medium" className="text-sm">
|
||||
Medium (50-75%)
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="success-low"
|
||||
checked={successFilter === "low"}
|
||||
onCheckedChange={() => setSuccessFilter(successFilter === "low" ? null : "low")}
|
||||
/>
|
||||
<label htmlFor="success-low" className="text-sm">
|
||||
Low (0-50%)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setDateRange({ from: undefined, to: undefined })
|
||||
setSuccessFilter(null)
|
||||
}}
|
||||
>
|
||||
Reset Filters
|
||||
</Button>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{[1, 2, 3, 4, 5, 6].map((i) => (
|
||||
<Card key={i} className="overflow-hidden">
|
||||
<CardHeader className="pb-2">
|
||||
<Skeleton className="h-5 w-1/2 mb-2" />
|
||||
<Skeleton className="h-4 w-3/4" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<Skeleton className="h-4 w-full" />
|
||||
<Skeleton className="h-4 w-full" />
|
||||
<Skeleton className="h-4 w-full" />
|
||||
<Skeleton className="h-4 w-full" />
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<Skeleton className="h-9 w-full" />
|
||||
</CardFooter>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
) : filteredSimulations.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center p-8 border rounded-lg">
|
||||
<h3 className="text-lg font-semibold mb-2">No simulations found</h3>
|
||||
<p className="text-muted-foreground text-center">
|
||||
{searchTerm || dateRange.from || dateRange.to || successFilter
|
||||
? "Try adjusting your filters to see more results."
|
||||
: "No simulation runs are available. Run a simulation to get started."}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{filteredSimulations.map((simulation) => (
|
||||
<Card key={simulation.id} className="overflow-hidden">
|
||||
<CardHeader className="pb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar className="h-4 w-4 text-muted-foreground" />
|
||||
<CardDescription>{formatDate(simulation.date || simulation.id)}</CardDescription>
|
||||
</div>
|
||||
<CardTitle className="text-lg">{simulation.id}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-2 gap-y-2 text-sm">
|
||||
<div className="text-muted-foreground">Nodes:</div>
|
||||
<div>
|
||||
{simulation.parameters.numberNodes.min}-{simulation.parameters.numberNodes.max}
|
||||
</div>
|
||||
<div className="text-muted-foreground">Failure Rate:</div>
|
||||
<div>
|
||||
{simulation.parameters.failureRate.min}%-{simulation.parameters.failureRate.max}%
|
||||
</div>
|
||||
<div className="text-muted-foreground">Success Rate:</div>
|
||||
<div className="flex items-center">
|
||||
<Badge
|
||||
className={
|
||||
simulation.successRate >= 75
|
||||
? "bg-green-500"
|
||||
: simulation.successRate >= 50
|
||||
? "bg-yellow-500"
|
||||
: "bg-red-500"
|
||||
}
|
||||
>
|
||||
{simulation.successRate}%
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<Button className="w-full" onClick={() => handleSimulationSelect(simulation.id)}>
|
||||
View Details
|
||||
<ChevronRight className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
96
das-simulator-visualizer/components/simulation-metadata.tsx
Normal file
96
das-simulator-visualizer/components/simulation-metadata.tsx
Normal file
@ -0,0 +1,96 @@
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { format, parseISO } from "date-fns"
|
||||
import { Badge } from "./ui/badge"
|
||||
|
||||
interface SimulationMetadataProps {
|
||||
simulation: any
|
||||
}
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
try {
|
||||
if (dateString && dateString.includes('T') && dateString.endsWith(':00Z')) {
|
||||
dateString = dateString.replace(':00Z', 'Z');
|
||||
}
|
||||
|
||||
const date = parseISO(dateString);
|
||||
return format(date, "PPP 'at' p");
|
||||
} catch (error) {
|
||||
console.error("Error formatting date:", dateString, error);
|
||||
|
||||
try {
|
||||
if (dateString && dateString.includes("_")) {
|
||||
const parts = dateString.split("_");
|
||||
if (parts.length >= 2) {
|
||||
const datePart = parts[0];
|
||||
const timePart = parts[1].replace(/-/g, ":");
|
||||
return `${datePart} at ${timePart}`;
|
||||
}
|
||||
}
|
||||
|
||||
return dateString || "Date unavailable";
|
||||
} catch (e) {
|
||||
return "Date unavailable";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export function SimulationMetadata({ simulation }: SimulationMetadataProps) {
|
||||
if (!simulation) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Simulation Details</CardTitle>
|
||||
<CardDescription>Run on {formatDate(simulation.date || simulation.id)}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div>
|
||||
<h4 className="text-sm font-medium mb-2">Parameter Ranges</h4>
|
||||
<div className="grid grid-cols-2 gap-y-2 text-sm">
|
||||
<div className="text-muted-foreground">Nodes:</div>
|
||||
<div>
|
||||
{simulation.parameters.numberNodes.min}-{simulation.parameters.numberNodes.max}
|
||||
</div>
|
||||
<div className="text-muted-foreground">Failure Rate:</div>
|
||||
<div>
|
||||
{simulation.parameters.failureRate.min}%-{simulation.parameters.failureRate.max}%
|
||||
</div>
|
||||
<div className="text-muted-foreground">Block Size:</div>
|
||||
<div>{simulation.parameters.blockSize.value}</div>
|
||||
<div className="text-muted-foreground">Net Degree:</div>
|
||||
<div>{simulation.parameters.netDegree.value}</div>
|
||||
<div className="text-muted-foreground">Chi:</div>
|
||||
<div>{simulation.parameters.chi.value}</div>
|
||||
<div className="text-muted-foreground">Runs:</div>
|
||||
<div>{simulation.parameters.run.max + 1}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-sm font-medium mb-2">Overall Performance</h4>
|
||||
<div className="grid grid-cols-2 gap-y-2 text-sm">
|
||||
<div className="text-muted-foreground">Success Rate:</div>
|
||||
<div>
|
||||
<Badge
|
||||
className={
|
||||
simulation.successRate >= 75
|
||||
? "bg-green-500"
|
||||
: simulation.successRate >= 50
|
||||
? "bg-yellow-500"
|
||||
: "bg-red-500"
|
||||
}
|
||||
>
|
||||
{simulation.successRate}%
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="text-muted-foreground">Avg. Missing Samples:</div>
|
||||
<div>{simulation.avgMissingSamples.toFixed(2)}%</div>
|
||||
<div className="text-muted-foreground">Avg. Nodes Ready:</div>
|
||||
<div>{simulation.avgNodesReady.toFixed(2)}%</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
101
das-simulator-visualizer/components/simulation-provider.tsx
Normal file
101
das-simulator-visualizer/components/simulation-provider.tsx
Normal file
@ -0,0 +1,101 @@
|
||||
"use client"
|
||||
|
||||
import { createContext, useContext, useState, useEffect, type ReactNode } from "react"
|
||||
import { fetchSimulations } from "@/lib/simulation-service"
|
||||
|
||||
type SimulationContextType = {
|
||||
simulations: any[]
|
||||
loading: boolean
|
||||
error: string | null
|
||||
getSimulationById: (id: string) => any
|
||||
refreshSimulations: () => Promise<void>
|
||||
}
|
||||
|
||||
const SimulationContext = createContext<SimulationContextType | undefined>(undefined)
|
||||
|
||||
export function SimulationProvider({ children }: { children: ReactNode }) {
|
||||
const [simulations, setSimulations] = useState<any[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
const refreshSimulations = async () => {
|
||||
try {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
const data = await fetchSimulations()
|
||||
setSimulations(data)
|
||||
} catch (err) {
|
||||
setError("Failed to fetch simulations")
|
||||
console.error(err)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const getSimulationById = (id: string) => {
|
||||
try {
|
||||
const cachedSimulation = simulations.find((sim) => sim.id === id)
|
||||
if (cachedSimulation) return cachedSimulation
|
||||
|
||||
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] },
|
||||
run: { max: 2 },
|
||||
},
|
||||
successRate: 75,
|
||||
avgMissingSamples: 20,
|
||||
avgNodesReady: 80,
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error getting simulation by ID:", err)
|
||||
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] },
|
||||
run: { max: 2 },
|
||||
},
|
||||
successRate: 0,
|
||||
avgMissingSamples: 0,
|
||||
avgNodesReady: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
refreshSimulations()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<SimulationContext.Provider
|
||||
value={{
|
||||
simulations,
|
||||
loading,
|
||||
error,
|
||||
getSimulationById,
|
||||
refreshSimulations,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</SimulationContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useSimulation() {
|
||||
const context = useContext(SimulationContext)
|
||||
if (context === undefined) {
|
||||
throw new Error("useSimulation must be used within a SimulationProvider")
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
172
das-simulator-visualizer/components/statistics-summary.tsx
Normal file
172
das-simulator-visualizer/components/statistics-summary.tsx
Normal file
@ -0,0 +1,172 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect, useState } from "react"
|
||||
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from "recharts"
|
||||
import { AlertCircle } from "lucide-react"
|
||||
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
|
||||
import { Skeleton } from "@/components/ui/skeleton"
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { getSimulationStats } from "@/lib/simulation-service"
|
||||
|
||||
interface StatisticsSummaryProps {
|
||||
simulation: any
|
||||
}
|
||||
|
||||
export function StatisticsSummary({ simulation }: StatisticsSummaryProps) {
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [stats, setStats] = useState<any>(null)
|
||||
const [statType, setStatType] = useState("byNodes")
|
||||
|
||||
useEffect(() => {
|
||||
const fetchStats = async () => {
|
||||
try {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
|
||||
const data = await getSimulationStats(simulation.id)
|
||||
setStats(data)
|
||||
} catch (err) {
|
||||
console.error("Error loading statistics:", err)
|
||||
setError("Failed to load statistics. Please try again later.")
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
fetchStats()
|
||||
}, [simulation.id])
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<Skeleton className="h-8 w-full" />
|
||||
<Skeleton className="h-[300px] w-full" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertTitle>Error</AlertTitle>
|
||||
<AlertDescription>{error}</AlertDescription>
|
||||
</Alert>
|
||||
)
|
||||
}
|
||||
|
||||
if (!stats) {
|
||||
return (
|
||||
<Alert>
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertTitle>No data available</AlertTitle>
|
||||
<AlertDescription>No statistics available for this simulation.</AlertDescription>
|
||||
</Alert>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<Tabs value={statType} onValueChange={setStatType} className="w-full">
|
||||
<TabsList className="grid grid-cols-3 w-full">
|
||||
<TabsTrigger value="byNodes">By Nodes</TabsTrigger>
|
||||
<TabsTrigger value="byFailureRate">By Failure Rate</TabsTrigger>
|
||||
<TabsTrigger value="byChi">By Chi</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Missing Samples</CardTitle>
|
||||
<CardDescription>Average percentage of missing samples</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="h-[200px]">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<BarChart data={stats[statType].missingSamples}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="name" />
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
<Bar dataKey="value" fill="#8884d8" name="Missing Samples (%)" />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Nodes Ready</CardTitle>
|
||||
<CardDescription>Average percentage of nodes ready</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="h-[200px]">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<BarChart data={stats[statType].nodesReady}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="name" />
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
<Bar dataKey="value" fill="#82ca9d" name="Nodes Ready (%)" />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Data Sent</CardTitle>
|
||||
<CardDescription>Average amount of data sent</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="h-[200px]">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<BarChart data={stats[statType].sentData}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="name" />
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
<Bar dataKey="value" fill="#ffc658" name="Data Sent (KB)" />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Performance Comparison</CardTitle>
|
||||
<CardDescription>
|
||||
Comparison of key metrics across different{" "}
|
||||
{statType === "byNodes" ? "node counts" : statType === "byFailureRate" ? "failure rates" : "chi values"}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="h-[300px]">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<BarChart data={stats[statType].comparison}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="name" />
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Bar dataKey="missingSamples" fill="#8884d8" name="Missing Samples (%)" />
|
||||
<Bar dataKey="nodesReady" fill="#82ca9d" name="Nodes Ready (%)" />
|
||||
<Bar dataKey="sentData" fill="#ffc658" name="Data Sent (KB)" />
|
||||
<Bar dataKey="recvData" fill="#ff8042" name="Data Received (KB)" />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
59
das-simulator-visualizer/components/ui/alert.tsx
Normal file
59
das-simulator-visualizer/components/ui/alert.tsx
Normal file
@ -0,0 +1,59 @@
|
||||
import * as React from "react"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const alertVariants = cva(
|
||||
"relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-background text-foreground",
|
||||
destructive:
|
||||
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const Alert = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
|
||||
>(({ className, variant, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
role="alert"
|
||||
className={cn(alertVariants({ variant }), className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Alert.displayName = "Alert"
|
||||
|
||||
const AlertTitle = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLHeadingElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<h5
|
||||
ref={ref}
|
||||
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AlertTitle.displayName = "AlertTitle"
|
||||
|
||||
const AlertDescription = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("text-sm [&_p]:leading-relaxed", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AlertDescription.displayName = "AlertDescription"
|
||||
|
||||
export { Alert, AlertTitle, AlertDescription }
|
||||
36
das-simulator-visualizer/components/ui/badge.tsx
Normal file
36
das-simulator-visualizer/components/ui/badge.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import * as React from "react"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const badgeVariants = cva(
|
||||
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
|
||||
secondary:
|
||||
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
destructive:
|
||||
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
|
||||
outline: "text-foreground",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
export interface BadgeProps
|
||||
extends React.HTMLAttributes<HTMLDivElement>,
|
||||
VariantProps<typeof badgeVariants> {}
|
||||
|
||||
function Badge({ className, variant, ...props }: BadgeProps) {
|
||||
return (
|
||||
<div className={cn(badgeVariants({ variant }), className)} {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
export { Badge, badgeVariants }
|
||||
57
das-simulator-visualizer/components/ui/button.tsx
Normal file
57
das-simulator-visualizer/components/ui/button.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
||||
destructive:
|
||||
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
|
||||
outline:
|
||||
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: "h-9 px-4 py-2",
|
||||
sm: "h-8 rounded-md px-3 text-xs",
|
||||
lg: "h-10 rounded-md px-8",
|
||||
icon: "h-9 w-9",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean
|
||||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "button"
|
||||
return (
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Button.displayName = "Button"
|
||||
|
||||
export { Button, buttonVariants }
|
||||
76
das-simulator-visualizer/components/ui/calendar.tsx
Normal file
76
das-simulator-visualizer/components/ui/calendar.tsx
Normal file
@ -0,0 +1,76 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react"
|
||||
import { DayPicker } from "react-day-picker"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { buttonVariants } from "@/components/ui/button"
|
||||
|
||||
export type CalendarProps = React.ComponentProps<typeof DayPicker>
|
||||
|
||||
function Calendar({
|
||||
className,
|
||||
classNames,
|
||||
showOutsideDays = true,
|
||||
...props
|
||||
}: CalendarProps) {
|
||||
return (
|
||||
<DayPicker
|
||||
showOutsideDays={showOutsideDays}
|
||||
className={cn("p-3", className)}
|
||||
classNames={{
|
||||
months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
|
||||
month: "space-y-4",
|
||||
caption: "flex justify-center pt-1 relative items-center",
|
||||
caption_label: "text-sm font-medium",
|
||||
nav: "space-x-1 flex items-center",
|
||||
nav_button: cn(
|
||||
buttonVariants({ variant: "outline" }),
|
||||
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
|
||||
),
|
||||
nav_button_previous: "absolute left-1",
|
||||
nav_button_next: "absolute right-1",
|
||||
table: "w-full border-collapse space-y-1",
|
||||
head_row: "flex",
|
||||
head_cell:
|
||||
"text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]",
|
||||
row: "flex w-full mt-2",
|
||||
cell: cn(
|
||||
"relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected].day-range-end)]:rounded-r-md",
|
||||
props.mode === "range"
|
||||
? "[&:has(>.day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md"
|
||||
: "[&:has([aria-selected])]:rounded-md"
|
||||
),
|
||||
day: cn(
|
||||
buttonVariants({ variant: "ghost" }),
|
||||
"h-8 w-8 p-0 font-normal aria-selected:opacity-100"
|
||||
),
|
||||
day_range_start: "day-range-start",
|
||||
day_range_end: "day-range-end",
|
||||
day_selected:
|
||||
"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
|
||||
day_today: "bg-accent text-accent-foreground",
|
||||
day_outside:
|
||||
"day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground",
|
||||
day_disabled: "text-muted-foreground opacity-50",
|
||||
day_range_middle:
|
||||
"aria-selected:bg-accent aria-selected:text-accent-foreground",
|
||||
day_hidden: "invisible",
|
||||
...classNames,
|
||||
}}
|
||||
components={{
|
||||
IconLeft: ({ className, ...props }) => (
|
||||
<ChevronLeft className={cn("h-4 w-4", className)} {...props} />
|
||||
),
|
||||
IconRight: ({ className, ...props }) => (
|
||||
<ChevronRight className={cn("h-4 w-4", className)} {...props} />
|
||||
),
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
Calendar.displayName = "Calendar"
|
||||
|
||||
export { Calendar }
|
||||
76
das-simulator-visualizer/components/ui/card.tsx
Normal file
76
das-simulator-visualizer/components/ui/card.tsx
Normal file
@ -0,0 +1,76 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Card = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"rounded-xl border bg-card text-card-foreground shadow",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Card.displayName = "Card"
|
||||
|
||||
const CardHeader = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardHeader.displayName = "CardHeader"
|
||||
|
||||
const CardTitle = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("font-semibold leading-none tracking-tight", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardTitle.displayName = "CardTitle"
|
||||
|
||||
const CardDescription = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardDescription.displayName = "CardDescription"
|
||||
|
||||
const CardContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
||||
))
|
||||
CardContent.displayName = "CardContent"
|
||||
|
||||
const CardFooter = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("flex items-center p-6 pt-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardFooter.displayName = "CardFooter"
|
||||
|
||||
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
||||
30
das-simulator-visualizer/components/ui/checkbox.tsx
Normal file
30
das-simulator-visualizer/components/ui/checkbox.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
||||
import { Check } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Checkbox = React.forwardRef<
|
||||
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CheckboxPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<CheckboxPrimitive.Indicator
|
||||
className={cn("flex items-center justify-center text-current")}
|
||||
>
|
||||
<Check className="h-4 w-4" />
|
||||
</CheckboxPrimitive.Indicator>
|
||||
</CheckboxPrimitive.Root>
|
||||
))
|
||||
Checkbox.displayName = CheckboxPrimitive.Root.displayName
|
||||
|
||||
export { Checkbox }
|
||||
122
das-simulator-visualizer/components/ui/dialog.tsx
Normal file
122
das-simulator-visualizer/components/ui/dialog.tsx
Normal file
@ -0,0 +1,122 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
||||
import { X } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Dialog = DialogPrimitive.Root
|
||||
|
||||
const DialogTrigger = DialogPrimitive.Trigger
|
||||
|
||||
const DialogPortal = DialogPrimitive.Portal
|
||||
|
||||
const DialogClose = DialogPrimitive.Close
|
||||
|
||||
const DialogOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
|
||||
|
||||
const DialogContent = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
))
|
||||
DialogContent.displayName = DialogPrimitive.Content.displayName
|
||||
|
||||
const DialogHeader = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col space-y-1.5 text-center sm:text-left",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
DialogHeader.displayName = "DialogHeader"
|
||||
|
||||
const DialogFooter = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
DialogFooter.displayName = "DialogFooter"
|
||||
|
||||
const DialogTitle = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"text-lg font-semibold leading-none tracking-tight",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DialogTitle.displayName = DialogPrimitive.Title.displayName
|
||||
|
||||
const DialogDescription = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DialogDescription.displayName = DialogPrimitive.Description.displayName
|
||||
|
||||
export {
|
||||
Dialog,
|
||||
DialogPortal,
|
||||
DialogOverlay,
|
||||
DialogTrigger,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogFooter,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
}
|
||||
22
das-simulator-visualizer/components/ui/input.tsx
Normal file
22
das-simulator-visualizer/components/ui/input.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
|
||||
({ className, type, ...props }, ref) => {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Input.displayName = "Input"
|
||||
|
||||
export { Input }
|
||||
26
das-simulator-visualizer/components/ui/label.tsx
Normal file
26
das-simulator-visualizer/components/ui/label.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const labelVariants = cva(
|
||||
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
)
|
||||
|
||||
const Label = React.forwardRef<
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
||||
VariantProps<typeof labelVariants>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<LabelPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(labelVariants(), className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Label.displayName = LabelPrimitive.Root.displayName
|
||||
|
||||
export { Label }
|
||||
33
das-simulator-visualizer/components/ui/popover.tsx
Normal file
33
das-simulator-visualizer/components/ui/popover.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Popover = PopoverPrimitive.Root
|
||||
|
||||
const PopoverTrigger = PopoverPrimitive.Trigger
|
||||
|
||||
const PopoverAnchor = PopoverPrimitive.Anchor
|
||||
|
||||
const PopoverContent = React.forwardRef<
|
||||
React.ElementRef<typeof PopoverPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
||||
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
|
||||
<PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Content
|
||||
ref={ref}
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</PopoverPrimitive.Portal>
|
||||
))
|
||||
PopoverContent.displayName = PopoverPrimitive.Content.displayName
|
||||
|
||||
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
|
||||
159
das-simulator-visualizer/components/ui/select.tsx
Normal file
159
das-simulator-visualizer/components/ui/select.tsx
Normal file
@ -0,0 +1,159 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as SelectPrimitive from "@radix-ui/react-select"
|
||||
import { Check, ChevronDown, ChevronUp } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Select = SelectPrimitive.Root
|
||||
|
||||
const SelectGroup = SelectPrimitive.Group
|
||||
|
||||
const SelectValue = SelectPrimitive.Value
|
||||
|
||||
const SelectTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SelectPrimitive.Icon asChild>
|
||||
<ChevronDown className="h-4 w-4 opacity-50" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
))
|
||||
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
|
||||
|
||||
const SelectScrollUpButton = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.ScrollUpButton
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default items-center justify-center py-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronUp className="h-4 w-4" />
|
||||
</SelectPrimitive.ScrollUpButton>
|
||||
))
|
||||
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
|
||||
|
||||
const SelectScrollDownButton = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.ScrollDownButton
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default items-center justify-center py-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
</SelectPrimitive.ScrollDownButton>
|
||||
))
|
||||
SelectScrollDownButton.displayName =
|
||||
SelectPrimitive.ScrollDownButton.displayName
|
||||
|
||||
const SelectContent = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
||||
>(({ className, children, position = "popper", ...props }, ref) => (
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
position === "popper" &&
|
||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||
className
|
||||
)}
|
||||
position={position}
|
||||
{...props}
|
||||
>
|
||||
<SelectScrollUpButton />
|
||||
<SelectPrimitive.Viewport
|
||||
className={cn(
|
||||
"p-1",
|
||||
position === "popper" &&
|
||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</SelectPrimitive.Viewport>
|
||||
<SelectScrollDownButton />
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
))
|
||||
SelectContent.displayName = SelectPrimitive.Content.displayName
|
||||
|
||||
const SelectLabel = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn("px-2 py-1.5 text-sm font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SelectLabel.displayName = SelectPrimitive.Label.displayName
|
||||
|
||||
const SelectItem = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<SelectPrimitive.ItemIndicator>
|
||||
<Check className="h-4 w-4" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
))
|
||||
SelectItem.displayName = SelectPrimitive.Item.displayName
|
||||
|
||||
const SelectSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
|
||||
|
||||
export {
|
||||
Select,
|
||||
SelectGroup,
|
||||
SelectValue,
|
||||
SelectTrigger,
|
||||
SelectContent,
|
||||
SelectLabel,
|
||||
SelectItem,
|
||||
SelectSeparator,
|
||||
SelectScrollUpButton,
|
||||
SelectScrollDownButton,
|
||||
}
|
||||
15
das-simulator-visualizer/components/ui/skeleton.tsx
Normal file
15
das-simulator-visualizer/components/ui/skeleton.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Skeleton({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return (
|
||||
<div
|
||||
className={cn("animate-pulse rounded-md bg-primary/10", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Skeleton }
|
||||
28
das-simulator-visualizer/components/ui/slider.tsx
Normal file
28
das-simulator-visualizer/components/ui/slider.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as SliderPrimitive from "@radix-ui/react-slider"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Slider = React.forwardRef<
|
||||
React.ElementRef<typeof SliderPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SliderPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex w-full touch-none select-none items-center",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<SliderPrimitive.Track className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-primary/20">
|
||||
<SliderPrimitive.Range className="absolute h-full bg-primary" />
|
||||
</SliderPrimitive.Track>
|
||||
<SliderPrimitive.Thumb className="block h-4 w-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" />
|
||||
</SliderPrimitive.Root>
|
||||
))
|
||||
Slider.displayName = SliderPrimitive.Root.displayName
|
||||
|
||||
export { Slider }
|
||||
55
das-simulator-visualizer/components/ui/tabs.tsx
Normal file
55
das-simulator-visualizer/components/ui/tabs.tsx
Normal file
@ -0,0 +1,55 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Tabs = TabsPrimitive.Root
|
||||
|
||||
const TabsList = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.List>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.List
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TabsList.displayName = TabsPrimitive.List.displayName
|
||||
|
||||
const TabsTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
|
||||
|
||||
const TabsContent = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TabsContent.displayName = TabsPrimitive.Content.displayName
|
||||
|
||||
export { Tabs, TabsList, TabsTrigger, TabsContent }
|
||||
16
das-simulator-visualizer/eslint.config.mjs
Normal file
16
das-simulator-visualizer/eslint.config.mjs
Normal file
@ -0,0 +1,16 @@
|
||||
import { dirname } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { FlatCompat } from "@eslint/eslintrc";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
});
|
||||
|
||||
const eslintConfig = [
|
||||
...compat.extends("next/core-web-vitals", "next/typescript"),
|
||||
];
|
||||
|
||||
export default eslintConfig;
|
||||
153
das-simulator-visualizer/lib/simulation-service.ts
Normal file
153
das-simulator-visualizer/lib/simulation-service.ts
Normal file
@ -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<Simulation[]> => {
|
||||
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<Simulation> => {
|
||||
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<string> => {
|
||||
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<string, string> = {
|
||||
'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<string> => {
|
||||
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<any> => {
|
||||
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;
|
||||
}
|
||||
};
|
||||
6
das-simulator-visualizer/lib/utils.ts
Normal file
6
das-simulator-visualizer/lib/utils.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
5
das-simulator-visualizer/next-env.d.ts
vendored
Normal file
5
das-simulator-visualizer/next-env.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
10
das-simulator-visualizer/next.config.ts
Normal file
10
das-simulator-visualizer/next.config.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
images: {
|
||||
domains: ['localhost'],
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
7267
das-simulator-visualizer/package-lock.json
generated
Normal file
7267
das-simulator-visualizer/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
43
das-simulator-visualizer/package.json
Normal file
43
das-simulator-visualizer/package.json
Normal file
@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "my-app",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --turbopack",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-checkbox": "^1.1.4",
|
||||
"@radix-ui/react-dialog": "^1.1.6",
|
||||
"@radix-ui/react-label": "^2.1.2",
|
||||
"@radix-ui/react-popover": "^1.1.6",
|
||||
"@radix-ui/react-select": "^2.1.6",
|
||||
"@radix-ui/react-slider": "^1.2.3",
|
||||
"@radix-ui/react-slot": "^1.1.2",
|
||||
"@radix-ui/react-tabs": "^1.1.3",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"lucide-react": "^0.479.0",
|
||||
"next": "15.1.0",
|
||||
"react": "^19.0.0",
|
||||
"react-day-picker": "^8.10.1",
|
||||
"react-dom": "^19.0.0",
|
||||
"recharts": "^2.15.1",
|
||||
"tailwind-merge": "^3.0.2",
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.1.0",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
8
das-simulator-visualizer/postcss.config.mjs
Normal file
8
das-simulator-visualizer/postcss.config.mjs
Normal file
@ -0,0 +1,8 @@
|
||||
/** @type {import('postcss-load-config').Config} */
|
||||
const config = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
62
das-simulator-visualizer/tailwind.config.ts
Normal file
62
das-simulator-visualizer/tailwind.config.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import type { Config } from "tailwindcss";
|
||||
|
||||
export default {
|
||||
darkMode: ["class"],
|
||||
content: [
|
||||
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
background: 'hsl(var(--background))',
|
||||
foreground: 'hsl(var(--foreground))',
|
||||
card: {
|
||||
DEFAULT: 'hsl(var(--card))',
|
||||
foreground: 'hsl(var(--card-foreground))'
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: 'hsl(var(--popover))',
|
||||
foreground: 'hsl(var(--popover-foreground))'
|
||||
},
|
||||
primary: {
|
||||
DEFAULT: 'hsl(var(--primary))',
|
||||
foreground: 'hsl(var(--primary-foreground))'
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: 'hsl(var(--secondary))',
|
||||
foreground: 'hsl(var(--secondary-foreground))'
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: 'hsl(var(--muted))',
|
||||
foreground: 'hsl(var(--muted-foreground))'
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: 'hsl(var(--accent))',
|
||||
foreground: 'hsl(var(--accent-foreground))'
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: 'hsl(var(--destructive))',
|
||||
foreground: 'hsl(var(--destructive-foreground))'
|
||||
},
|
||||
border: 'hsl(var(--border))',
|
||||
input: 'hsl(var(--input))',
|
||||
ring: 'hsl(var(--ring))',
|
||||
chart: {
|
||||
'1': 'hsl(var(--chart-1))',
|
||||
'2': 'hsl(var(--chart-2))',
|
||||
'3': 'hsl(var(--chart-3))',
|
||||
'4': 'hsl(var(--chart-4))',
|
||||
'5': 'hsl(var(--chart-5))'
|
||||
}
|
||||
},
|
||||
borderRadius: {
|
||||
lg: 'var(--radius)',
|
||||
md: 'calc(var(--radius) - 2px)',
|
||||
sm: 'calc(var(--radius) - 4px)'
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [require("tailwindcss-animate")],
|
||||
} satisfies Config;
|
||||
27
das-simulator-visualizer/tsconfig.json
Normal file
27
das-simulator-visualizer/tsconfig.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
375
server.py
Normal file
375
server.py
Normal file
@ -0,0 +1,375 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import json
|
||||
import glob
|
||||
import base64
|
||||
from typing import List, Dict, Optional, Any
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import FileResponse
|
||||
from pydantic import BaseModel
|
||||
|
||||
RESULTS_DIR = "results"
|
||||
|
||||
app = FastAPI(title="DAS Simulator API")
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
class SimulationInfo(BaseModel):
|
||||
id: str
|
||||
date: str
|
||||
parameters: Dict[str, Any]
|
||||
successRate: float
|
||||
avgMissingSamples: float
|
||||
avgNodesReady: float
|
||||
|
||||
def parse_shape_string(shape_str: str) -> Dict[str, Any]:
|
||||
"""Parse a shape string to extract the parameters."""
|
||||
params = {}
|
||||
parts = shape_str.split("-")
|
||||
|
||||
for i in range(0, len(parts), 2):
|
||||
if i+1 < len(parts):
|
||||
key = parts[i]
|
||||
value = parts[i+1]
|
||||
try:
|
||||
params[key] = int(value)
|
||||
except ValueError:
|
||||
try:
|
||||
params[key] = float(value)
|
||||
except ValueError:
|
||||
params[key] = value
|
||||
|
||||
return params
|
||||
|
||||
def calculate_success_rate(sim_dir: str) -> float:
|
||||
"""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
|
||||
|
||||
success = 0
|
||||
for xml_file in xml_files:
|
||||
success += 1
|
||||
|
||||
return (success / total) * 100.0
|
||||
|
||||
def extract_parameters(sim_dir: str) -> Dict[str, Any]:
|
||||
"""Extract the parameter ranges used in the simulation."""
|
||||
xml_files = glob.glob(f"{sim_dir}/*.xml")
|
||||
nn_values = set()
|
||||
fr_values = set()
|
||||
bs_values = set()
|
||||
nd_values = set()
|
||||
chi_values = set()
|
||||
mn_values = set()
|
||||
run_values = set()
|
||||
|
||||
for xml_file in xml_files:
|
||||
base_name = os.path.basename(xml_file).replace(".xml", "")
|
||||
params = parse_shape_string(base_name)
|
||||
|
||||
if "nn" in params:
|
||||
nn_values.add(params["nn"])
|
||||
if "fr" in params:
|
||||
fr_values.add(params["fr"])
|
||||
if "bsrn" in params:
|
||||
bs_values.add(params["bsrn"])
|
||||
if "nd" in params:
|
||||
nd_values.add(params["nd"])
|
||||
if "cusr" in params:
|
||||
chi_values.add(params["cusr"])
|
||||
if "mn" in params:
|
||||
mn_values.add(params["mn"])
|
||||
if "r" in params:
|
||||
run_values.add(params["r"])
|
||||
|
||||
parameters = {
|
||||
"numberNodes": {
|
||||
"min": min(nn_values) if nn_values else 128,
|
||||
"max": max(nn_values) if nn_values else 512,
|
||||
"step": 128
|
||||
},
|
||||
"failureRate": {
|
||||
"min": min(fr_values) if fr_values else 40,
|
||||
"max": max(fr_values) if fr_values else 80,
|
||||
"step": 20
|
||||
},
|
||||
"blockSize": {
|
||||
"value": list(bs_values)[0] if bs_values else 64,
|
||||
"options": list(bs_values) if bs_values else [64]
|
||||
},
|
||||
"netDegree": {
|
||||
"value": list(nd_values)[0] if nd_values else 8,
|
||||
"options": list(nd_values) if nd_values else [8]
|
||||
},
|
||||
"chi": {
|
||||
"value": list(chi_values)[0] if chi_values else 2,
|
||||
"options": list(chi_values) if chi_values else [2]
|
||||
},
|
||||
"maliciousNodes": {
|
||||
"value": list(mn_values)[0] if mn_values else 0,
|
||||
"options": list(mn_values) if mn_values else [0]
|
||||
},
|
||||
"run": {
|
||||
"max": max(run_values) if run_values else 2
|
||||
}
|
||||
}
|
||||
|
||||
return parameters
|
||||
|
||||
@app.get("/api/simulations", response_model=List[SimulationInfo])
|
||||
async def get_simulations():
|
||||
"""Get the list of all available simulations."""
|
||||
simulations = []
|
||||
|
||||
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"Results directory not found: {RESULTS_DIR}")
|
||||
|
||||
for sim_id in sim_dirs:
|
||||
sim_dir = os.path.join(RESULTS_DIR, sim_id)
|
||||
|
||||
date_str = sim_id.split("_")[0] + "T" + sim_id.split("_")[1].replace("-", ":") + ":00Z"
|
||||
|
||||
success_rate = calculate_success_rate(sim_dir)
|
||||
parameters = extract_parameters(sim_dir)
|
||||
|
||||
avg_missing_samples = 15.0
|
||||
avg_nodes_ready = 85.0
|
||||
|
||||
sim_info = SimulationInfo(
|
||||
id=sim_id,
|
||||
date=date_str,
|
||||
parameters=parameters,
|
||||
successRate=success_rate,
|
||||
avgMissingSamples=avg_missing_samples,
|
||||
avgNodesReady=avg_nodes_ready
|
||||
)
|
||||
|
||||
simulations.append(sim_info)
|
||||
|
||||
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):
|
||||
"""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"Simulation not found: {sim_id}")
|
||||
|
||||
date_str = sim_id.split("_")[0] + "T" + sim_id.split("_")[1].replace("-", ":") + ":00Z"
|
||||
|
||||
success_rate = calculate_success_rate(sim_dir)
|
||||
parameters = extract_parameters(sim_dir)
|
||||
|
||||
avg_missing_samples = 15.0
|
||||
avg_nodes_ready = 85.0
|
||||
|
||||
sim_info = {
|
||||
"id": sim_id,
|
||||
"date": date_str,
|
||||
"parameters": parameters,
|
||||
"successRate": success_rate,
|
||||
"avgMissingSamples": avg_missing_samples,
|
||||
"avgNodesReady": avg_nodes_ready
|
||||
}
|
||||
|
||||
return sim_info
|
||||
|
||||
@app.get("/api/graph/{sim_id}/{nn}/{fr}/{bs}/{nd}/{chi}/{run}/{graph_type}")
|
||||
async def get_graph(
|
||||
sim_id: str,
|
||||
nn: int,
|
||||
fr: int,
|
||||
bs: int,
|
||||
nd: int,
|
||||
chi: int,
|
||||
run: int,
|
||||
graph_type: str
|
||||
):
|
||||
"""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"Simulation not found: {sim_id}")
|
||||
|
||||
if graph_type.endswith('.png'):
|
||||
graph_type = graph_type.replace('.png', '')
|
||||
|
||||
plots_dir = os.path.join(sim_dir, "plots")
|
||||
|
||||
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:
|
||||
graph_file = os.path.join(matching_dirs[0], f"{graph_type}.png")
|
||||
if os.path.exists(graph_file):
|
||||
return FileResponse(graph_file)
|
||||
|
||||
specific_patterns = [
|
||||
f"{graph_type}.png",
|
||||
f"boxen_{graph_type}.png",
|
||||
f"ecdf_{graph_type}.png",
|
||||
f"box_{graph_type}.png"
|
||||
]
|
||||
|
||||
for pattern in specific_patterns:
|
||||
for root, dirs, files in os.walk(plots_dir):
|
||||
if pattern in files:
|
||||
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)
|
||||
|
||||
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))
|
||||
|
||||
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"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):
|
||||
"""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"Simulation not found: {sim_id}")
|
||||
|
||||
heatmap_mapping = {
|
||||
"nodesVsFailure": ["nnVsfr", "nodeVsFailure", "failureRateVsnumberNodes"],
|
||||
"nodesVsChi": ["nnVschir", "nodeVsChi", "nnVscusr"],
|
||||
"failureVsChi": ["frVschir", "failureVsChi", "frVscusr"],
|
||||
"failureVsNetDegree": ["frVsnd", "failureVsNetDegree"],
|
||||
"NWDegVsNodeOnRuntime": ["NWDegVsNodeOnRuntime"],
|
||||
"NWDegVsMalNodeOnMissingSamples": ["NWDegVsMalNodeOnMissingSamples"],
|
||||
"NWDegVsFailureRateOnMissingSamples": ["NWDegVsFailureRateOnMissingSamples"]
|
||||
}
|
||||
|
||||
if heatmap_type not in heatmap_mapping:
|
||||
raise HTTPException(status_code=400, detail=f"Invalid heatmap type: {heatmap_type}")
|
||||
|
||||
heatmap_dir = os.path.join(sim_dir, "heatmaps")
|
||||
if not os.path.exists(heatmap_dir):
|
||||
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])
|
||||
|
||||
all_images = []
|
||||
for root, dirs, files in os.walk(sim_dir):
|
||||
for file in files:
|
||||
if file.endswith(".png"):
|
||||
all_images.append(os.path.join(root, file))
|
||||
|
||||
if all_images:
|
||||
return FileResponse(all_images[0], media_type="image/png")
|
||||
|
||||
raise HTTPException(status_code=404, detail=f"No heatmaps found for the simulation")
|
||||
|
||||
if heatmap_type in ["NWDegVsNodeOnRuntime", "NWDegVsMalNodeOnMissingSamples", "NWDegVsFailureRateOnMissingSamples"]:
|
||||
specific_dir = os.path.join(heatmap_dir, heatmap_type)
|
||||
if os.path.exists(specific_dir):
|
||||
png_files = glob.glob(os.path.join(specific_dir, "*.png"))
|
||||
if png_files:
|
||||
return FileResponse(png_files[0])
|
||||
|
||||
possible_names = heatmap_mapping[heatmap_type]
|
||||
|
||||
for pattern in possible_names:
|
||||
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:
|
||||
png_files = glob.glob(os.path.join(heatmap_dir, subdir, "*.png"))
|
||||
if png_files:
|
||||
return FileResponse(png_files[0])
|
||||
|
||||
for root, dirs, files in os.walk(heatmap_dir):
|
||||
for file in files:
|
||||
if file.endswith(".png"):
|
||||
return FileResponse(os.path.join(root, file))
|
||||
|
||||
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 not found, showing another image"})
|
||||
|
||||
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):
|
||||
"""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"Simulation not found: {sim_id}")
|
||||
|
||||
def generate_stat_data(prefix, count, min_val=10, max_val=90):
|
||||
import random
|
||||
return [{"name": f"{prefix}{i * 128 + 128}", "value": random.randint(min_val, max_val)}
|
||||
for i in range(count)]
|
||||
|
||||
def generate_comparison_data(prefix, count):
|
||||
import random
|
||||
return [
|
||||
{
|
||||
"name": f"{prefix}{i * 128 + 128}",
|
||||
"missingSamples": random.randint(5, 40),
|
||||
"nodesReady": random.randint(60, 95),
|
||||
"sentData": random.randint(20, 100),
|
||||
"recvData": random.randint(15, 90)
|
||||
}
|
||||
for i in range(count)
|
||||
]
|
||||
|
||||
stats = {
|
||||
"byNodes": {
|
||||
"missingSamples": generate_stat_data("Nodes: ", 4),
|
||||
"nodesReady": generate_stat_data("Nodes: ", 4),
|
||||
"sentData": generate_stat_data("Nodes: ", 4),
|
||||
"comparison": generate_comparison_data("Nodes: ", 4)
|
||||
},
|
||||
"byFailureRate": {
|
||||
"missingSamples": generate_stat_data("Failure: ", 5),
|
||||
"nodesReady": generate_stat_data("Failure: ", 5),
|
||||
"sentData": generate_stat_data("Failure: ", 5),
|
||||
"comparison": generate_comparison_data("Failure: ", 5)
|
||||
},
|
||||
"byChi": {
|
||||
"missingSamples": generate_stat_data("Chi: ", 4),
|
||||
"nodesReady": generate_stat_data("Chi: ", 4),
|
||||
"sentData": generate_stat_data("Chi: ", 4),
|
||||
"comparison": generate_comparison_data("Chi: ", 4)
|
||||
}
|
||||
}
|
||||
|
||||
return stats
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||
Loading…
x
Reference in New Issue
Block a user