Pages

This page documents the page components in frontend/src/pages/. Each page corresponds to a route in the application.


RunListPage

Location:

src/pages/RunListPage.tsx

Route:

/ or /runs

The main dashboard showing all simulation runs.

Features

  • Lists all runs with status indicators

  • Automatic refresh (polls every 5 seconds)

  • Filter by status (completed, running, failed)

  • Click to navigate to run details

  • “New Run” button to create a simulation

Implementation

function RunListPage() {
  const navigate = useNavigate()

  const { data, isLoading } = useQuery({
    queryKey: ['runs'],
    queryFn: () => runsApi.list(),
    refetchInterval: 5000,
  })

  return (
    <div className="p-6">
      <div className="flex justify-between items-center mb-6">
        <h1 className="text-2xl font-bold">Simulation Runs</h1>
        <Button onClick={() => navigate('/runs/new')}>New Run</Button>
      </div>

      {isLoading ? (
        <LoadingSpinner />
      ) : (
        <div className="grid gap-4">
          {data?.runs.map((run) => (
            <RunCard
              key={run.id}
              run={run}
              onClick={() => navigate(`/runs/${run.id}`)}
            />
          ))}
        </div>
      )}
    </div>
  )
}

NewRunPage

Location:

src/pages/NewRunPage.tsx

Route:

/runs/new

Form for creating a new simulation run.

Features

  • Run ID input (auto-generated if empty)

  • Optional display name

  • Template selection dropdown

  • Configuration editor (Monaco) for customization

  • Validation before submission

Implementation

function NewRunPage() {
  const navigate = useNavigate()
  const [runId, setRunId] = useState('')
  const [template, setTemplate] = useState('minimal.ini')
  const [configContent, setConfigContent] = useState('')

  const { data: templates } = useQuery({
    queryKey: ['templates'],
    queryFn: configsApi.listTemplates,
  })

  const createMutation = useMutation({
    mutationFn: runsApi.create,
    onSuccess: (run) => navigate(`/runs/${run.id}`),
  })

  const handleSubmit = () => {
    createMutation.mutate({
      run_id: runId || undefined,
      template,
      config_content: configContent,
    })
  }

  return (
    <div className="p-6 max-w-3xl">
      <h1 className="text-2xl font-bold mb-6">New Simulation Run</h1>

      <form onSubmit={handleSubmit}>
        <Input label="Run ID" value={runId} onChange={setRunId} />
        <Select label="Template" options={templates} value={template} onChange={setTemplate} />
        <Editor value={configContent} onChange={setConfigContent} language="ini" />
        <Button type="submit" loading={createMutation.isLoading}>
          Start Run
        </Button>
      </form>
    </div>
  )
}

RunDetailPage

Location:

src/pages/RunDetailPage.tsx

Route:

/runs/:runId

Detailed view of a single run with tabs for different information.

Features

  • Run metadata (status, timestamps, template)

  • Progress indicator for running simulations

  • Logs tab with real-time streaming

  • Artifacts tab with file browser

  • Cancel button for running simulations

Tabs

Logs Tab

  • Displays simulation output in a scrollable terminal-like view

  • Uses Server-Sent Events for real-time updates

  • Auto-scrolls to bottom for new content

  • Preserves scroll position when manually scrolled up

Artifacts Tab

  • File browser for simulation output files

  • Navigate folders (breadcrumb navigation)

  • Click files to download

  • Preview JSON files inline

Implementation (Logs)

function LogsPanel({ runId }: { runId: string }) {
  const [logs, setLogs] = useState<string[]>([])
  const logsEndRef = useRef<HTMLDivElement>(null)

  useEffect(() => {
    const eventSource = new EventSource(runsApi.getLogsUrl(runId))

    eventSource.addEventListener('log', (e) => {
      const data = JSON.parse(e.data)
      setLogs((prev) => [...prev, data.line])
    })

    eventSource.addEventListener('done', () => {
      eventSource.close()
    })

    return () => eventSource.close()
  }, [runId])

  // Auto-scroll effect
  useEffect(() => {
    logsEndRef.current?.scrollIntoView({ behavior: 'smooth' })
  }, [logs])

  return (
    <div className="font-mono text-sm bg-black text-green-400 p-4 h-96 overflow-auto">
      {logs.map((line, i) => (
        <div key={i}>{line}</div>
      ))}
      <div ref={logsEndRef} />
    </div>
  )
}

TopologyPage

Location:

src/pages/TopologyPage.tsx

Route:

/topology

Interactive network topology visualization.

Features

  • Dropdown to select topology (NSFNet, USNet, etc.)

  • Force-directed graph visualization

  • Click nodes to see details

  • Zoom and pan controls

  • Node degree information on selection

Implementation

function TopologyPage() {
  const [selectedTopology, setSelectedTopology] = useState('NSFNet')
  const [selectedNode, setSelectedNode] = useState<number | null>(null)

  const { data: topologies } = useQuery({
    queryKey: ['topologies'],
    queryFn: topologyApi.list,
  })

  const { data: topology } = useQuery({
    queryKey: ['topology', selectedTopology],
    queryFn: () => topologyApi.get(selectedTopology),
    enabled: !!selectedTopology,
  })

  return (
    <div className="p-6 h-full flex flex-col">
      <div className="flex items-center gap-4 mb-4">
        <Select
          value={selectedTopology}
          onChange={setSelectedTopology}
          options={topologies?.topologies ?? []}
        />
        {selectedNode !== null && (
          <NodeInfo node={topology?.nodes[selectedNode]} />
        )}
      </div>

      <div className="flex-1 border rounded">
        {topology && (
          <NetworkGraph
            topology={topology}
            selectedNode={selectedNode}
            onNodeClick={setSelectedNode}
          />
        )}
      </div>
    </div>
  )
}

ConfigEditorPage

Location:

src/pages/ConfigEditorPage.tsx

Route:

/config

INI configuration file editor.

Features

  • Template selection dropdown

  • Monaco editor with INI syntax highlighting

  • Dark/light theme support

  • Copy to clipboard

  • Use with new run (navigates to NewRunPage with config)

Implementation

function ConfigEditorPage() {
  const [template, setTemplate] = useState<string | null>(null)
  const [content, setContent] = useState('')
  const { theme } = useTheme()

  const { data: templates } = useQuery({
    queryKey: ['templates'],
    queryFn: configsApi.listTemplates,
  })

  const { data: templateData } = useQuery({
    queryKey: ['template', template],
    queryFn: () => configsApi.getTemplate(template!),
    enabled: !!template,
  })

  useEffect(() => {
    if (templateData) {
      setContent(templateData.content)
    }
  }, [templateData])

  return (
    <div className="p-6 h-full flex flex-col">
      <div className="flex items-center gap-4 mb-4">
        <Select
          label="Template"
          value={template}
          onChange={setTemplate}
          options={templates?.templates.map(t => t.name) ?? []}
        />
      </div>

      <div className="flex-1 border rounded overflow-hidden">
        <Editor
          value={content}
          onChange={(value) => setContent(value ?? '')}
          language="ini"
          theme={theme === 'dark' ? 'vs-dark' : 'light'}
        />
      </div>
    </div>
  )
}

CodebaseExplorerPage

Location:

src/pages/CodebaseExplorerPage.tsx

Route:

/codebase

Browse and explore the FUSION codebase.

Features

  • Architecture view with module cards

  • Guided tour for newcomers

  • File tree navigation

  • Code viewer with syntax highlighting

  • File search

Views

Architecture View

Grid of cards showing high-level modules (core, modules, configs, etc.). Each card shows the module name, description, and key files.

Code View

Split panel with file tree on left and code viewer on right. Supports syntax highlighting for Python, INI, TypeScript, etc.

Tour Mode

Step-by-step walkthrough of the codebase structure with highlighted sections and explanatory text.

Implementation

function CodebaseExplorerPage() {
  const [view, setView] = useState<'architecture' | 'code'>('architecture')
  const [selectedFile, setSelectedFile] = useState<string | null>(null)
  const [tourActive, setTourActive] = useState(false)

  const { data: tree } = useQuery({
    queryKey: ['codebase-tree'],
    queryFn: codebaseApi.getTree,
  })

  const { data: fileContent } = useQuery({
    queryKey: ['codebase-file', selectedFile],
    queryFn: () => codebaseApi.getFile(selectedFile!),
    enabled: !!selectedFile,
  })

  if (view === 'architecture') {
    return <ArchitectureView onSelectModule={...} onStartTour={...} />
  }

  return (
    <div className="h-full flex">
      <FileTree tree={tree} onSelect={setSelectedFile} />
      <CodeViewer content={fileContent?.content} language={...} />
    </div>
  )
}

SettingsPage

Location:

src/pages/SettingsPage.tsx

Route:

/settings

User preferences and settings.

Features

  • Theme selection (light, dark, system)

  • Settings persisted to localStorage

Implementation

function SettingsPage() {
  const { theme, setTheme } = useTheme()

  return (
    <div className="p-6 max-w-2xl">
      <h1 className="text-2xl font-bold mb-6">Settings</h1>

      <Card>
        <CardHeader>Appearance</CardHeader>
        <CardContent>
          <label className="block mb-2">Theme</label>
          <div className="flex gap-2">
            {['light', 'dark', 'system'].map((t) => (
              <Button
                key={t}
                variant={theme === t ? 'primary' : 'outline'}
                onClick={() => setTheme(t)}
              >
                {t.charAt(0).toUpperCase() + t.slice(1)}
              </Button>
            ))}
          </div>
        </CardContent>
      </Card>
    </div>
  )
}