diff --git a/dashboard/src/components/StatesByRunId.tsx b/dashboard/src/components/StatesByRunId.tsx index 654a17b6..1250aaeb 100644 --- a/dashboard/src/components/StatesByRunId.tsx +++ b/dashboard/src/components/StatesByRunId.tsx @@ -1,11 +1,13 @@ 'use client'; import React, { useState, useEffect, useMemo } from 'react'; +import { formatDistanceToNow } from 'date-fns'; import { apiService } from '@/services/api'; import { CurrentStatesResponse, StatesByRunIdResponse, - StateListItem + StateListItem, + RunSummary } from '@/types/state-manager'; import { GraphVisualization } from './GraphVisualization'; import { @@ -25,7 +27,7 @@ interface StatesByRunIdProps { namespace: string; apiKey: string; } - +// my new function export const StatesByRunId: React.FC = ({ namespace, apiKey @@ -43,6 +45,13 @@ export const StatesByRunId: React.FC = ({ return m; }, [currentStates]); +const sortedRunIds = React.useMemo(() => { + if (!currentStates?.run_ids) return []; + return [...currentStates.run_ids].sort((a, b) => + new Date(b.created_at).getTime() - new Date(a.created_at).getTime() + ); +}, [currentStates?.run_ids]); + const loadCurrentStates = async () => { setIsLoading(true); setError(null); @@ -53,7 +62,7 @@ export const StatesByRunId: React.FC = ({ // Auto-select the first run ID if available if (data.run_ids.length > 0 && !selectedRunId) { - setSelectedRunId(data.run_ids[0]); + setSelectedRunId(data.run_ids[0].run_id); } } catch (err) { setError(err instanceof Error ? err.message : 'Failed to load current states'); @@ -177,6 +186,28 @@ export const StatesByRunId: React.FC = ({ +{/* my react component for latest run id's */} +
+

Latest Run ID

+
+ {sortedRunIds.length > 0 ? ( +
    + {sortedRunIds.map((run) => ( +
  • +
    + {run.run_id} + + {formatDistanceToNow(new Date(run.created_at), { addSuffix: true })} + +
    +
  • + ))} +
+ ) : ( +

No run IDs found

+ )} +
+
{/* Run ID Selector */} {currentStates && currentStates.run_ids.length > 0 && ( @@ -189,19 +220,19 @@ export const StatesByRunId: React.FC = ({
{currentStates.run_ids.map((runId) => ( ))} diff --git a/dashboard/src/services/api.ts b/dashboard/src/services/api.ts index 54cb54c3..7822c4e7 100644 --- a/dashboard/src/services/api.ts +++ b/dashboard/src/services/api.ts @@ -18,7 +18,7 @@ import { } from '@/types/state-manager'; const API_BASE_URL = process.env.NEXT_PUBLIC_EXOSPHERE_STATE_MANAGER_URL || 'http://localhost:8000'; - +const DEFAULT_API_KEY = process.env.NEXT_PUBLIC_EXOSPHERE_API_KEY || ''; class ApiService { private async makeRequest( endpoint: string, @@ -26,18 +26,26 @@ class ApiService { ): Promise { const url = `${API_BASE_URL}${endpoint}`; + const headers = new Headers(options.headers); + headers.set('Content-Type', 'application/json'); + headers.set('x-api-key', process.env.NEXT_PUBLIC_EXOSPHERE_API_KEY || ''); + const response = await fetch(url, { - headers: { - 'Content-Type': 'application/json', - ...options.headers, - }, ...options, + headers, }); - + if (!response.ok) { - throw new Error(`API request failed: ${response.status} ${response.statusText}`); + const errorData = await response.json().catch(() => ({})); + console.error('API Error:', { + status: response.status, + statusText: response.statusText, + url, + errorData + }); + throw new Error(errorData.detail || `API request failed: ${response.statusText}`); } - + return response.json(); } @@ -52,7 +60,7 @@ class ApiService { { method: 'PUT', headers: { - 'X-API-Key': apiKey, + 'X-API-Key': DEFAULT_API_KEY, }, body: JSON.stringify(request), } @@ -88,7 +96,7 @@ class ApiService { { method: 'GET', headers: { - 'X-API-Key': apiKey, + 'X-API-Key': DEFAULT_API_KEY, }, } ); @@ -106,7 +114,7 @@ class ApiService { { method: 'POST', headers: { - 'X-API-Key': apiKey, + 'X-API-Key': DEFAULT_API_KEY, }, body: JSON.stringify(request), } @@ -123,7 +131,7 @@ class ApiService { { method: 'POST', headers: { - 'X-API-Key': apiKey, + 'X-API-Key': DEFAULT_API_KEY, }, body: JSON.stringify(request), } @@ -141,7 +149,7 @@ class ApiService { { method: 'POST', headers: { - 'X-API-Key': apiKey, + 'X-API-Key': DEFAULT_API_KEY, }, body: JSON.stringify(request), } @@ -158,7 +166,7 @@ class ApiService { { method: 'GET', headers: { - 'X-API-Key': apiKey, + 'X-API-Key': DEFAULT_API_KEY, }, } ); @@ -174,7 +182,7 @@ class ApiService { { method: 'GET', headers: { - 'X-API-Key': apiKey, + 'X-API-Key': DEFAULT_API_KEY, }, } ); @@ -189,7 +197,7 @@ class ApiService { { method: 'GET', headers: { - 'X-API-Key': apiKey, + 'X-API-Key': DEFAULT_API_KEY, }, } ); @@ -205,7 +213,7 @@ class ApiService { { method: 'GET', headers: { - 'X-API-Key': apiKey, + 'X-API-Key': DEFAULT_API_KEY, }, } ); @@ -221,7 +229,7 @@ class ApiService { { method: 'GET', headers: { - 'X-API-Key': apiKey, + 'X-API-Key': DEFAULT_API_KEY, }, } ); @@ -237,7 +245,7 @@ class ApiService { { method: 'GET', headers: { - 'X-API-Key': apiKey, + 'X-API-Key': DEFAULT_API_KEY, }, } ); diff --git a/dashboard/src/types/state-manager.ts b/dashboard/src/types/state-manager.ts index ad02bf8e..107dadaa 100644 --- a/dashboard/src/types/state-manager.ts +++ b/dashboard/src/types/state-manager.ts @@ -146,11 +146,15 @@ export interface StatesByRunIdResponse { states: StateListItem[]; } +export interface RunSummary { + run_id: string; + created_at: string; +} export interface CurrentStatesResponse { namespace: string; count: number; states: StateListItem[]; - run_ids: string[]; + run_ids: RunSummary[]; } export interface WorkflowStep { diff --git a/state-manager/Dockerfile b/state-manager/Dockerfile index 0f9cb814..51806d87 100644 --- a/state-manager/Dockerfile +++ b/state-manager/Dockerfile @@ -1,14 +1,34 @@ FROM python:3.12-slim-bookworm -COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ + +# Install curl for downloading uv +RUN apt-get update && apt-get install -y curl ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# Download uv binary based on target architecture +ARG TARGETARCH +RUN case "$TARGETARCH" in \ + amd64) ARCH=x86_64 ;; \ + arm64) ARCH=aarch64 ;; \ + *) echo "Unsupported architecture: $TARGETARCH" && exit 1 ;; \ + esac && \ + curl -L "https://github.com/astral-sh/uv/releases/latest/download/uv-${ARCH}-unknown-linux-musl.tar.gz" \ + | tar -xz && \ + mv uv-${ARCH}-unknown-linux-musl/uv /usr/local/bin/ && \ + mv uv-${ARCH}-unknown-linux-musl/uvx /usr/local/bin/ && \ + rm -rf uv-${ARCH}-unknown-linux-musl WORKDIR /api-server +# Copy dependency files first (for caching) COPY pyproject.toml uv.lock ./ +# Install dependencies using uv RUN uv sync --locked +# Copy app source code COPY . . EXPOSE 8000 -CMD ["uv", "run", "run.py", "--mode", "production", "--workers", "4"] \ No newline at end of file +# Start the app +CMD ["uv", "run", "run.py", "--mode", "production", "--workers", "4"]