Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion aim/web/api/runs/views.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from typing import Optional, Tuple
import os
import pathlib

from aim.sdk.types import QueryReportMode
from aim.web.api.runs.object_views import (
Expand Down Expand Up @@ -49,7 +51,7 @@
object_factory,
)
from fastapi import Depends, Header, HTTPException, Query
from fastapi.responses import JSONResponse, StreamingResponse
from fastapi.responses import JSONResponse, StreamingResponse, FileResponse
from starlette import status


Expand Down Expand Up @@ -369,6 +371,38 @@ async def get_log_records_api(run_id: str, record_range: Optional[str] = ''):
return StreamingResponse(run_log_records_streamer(run, record_range))


@runs_router.get('/{run_id}/artifacts/{artifact_name}/')
async def get_artifact_file_api(run_id: str, artifact_name: str):
"""Serve artifact files for a specific run."""
repo = get_project_repo()
run = get_run_or_404(run_id, repo=repo)

# Find the artifact by name
artifacts = run.artifacts
if artifact_name not in artifacts:
raise HTTPException(status_code=404, detail=f"Artifact '{artifact_name}' not found")

artifact = artifacts[artifact_name]
file_path = artifact.path

# Verify the file exists and is accessible
if not os.path.exists(file_path):
raise HTTPException(status_code=404, detail=f"Artifact file not found at path: {file_path}")

# Security check: ensure the file is within reasonable bounds
try:
file_path = os.path.abspath(file_path)
except Exception:
raise HTTPException(status_code=400, detail="Invalid file path")

# Return the file
return FileResponse(
path=file_path,
filename=artifact_name,
media_type='application/octet-stream'
)


def add_api_routes():
ImageApiConfig.register_endpoints(runs_router)
TextApiConfig.register_endpoints(runs_router)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import useRunMetricsBatch from '../hooks/useRunMetricsBatch';
import GitInfoCard from './components/GitInfoCard';
import RunOverviewTabMetricsCard from './components/MetricsCard/RunOverviewTabMetricsCard';
import RunOverviewTabArtifactsCard from './components/ArtifactsCard/RunOverviewTabArtifactsCard';
import RunOverviewTabCSVTablesCard from './components/CSVTablesCard/RunOverviewTabCSVTablesCard';
import RunOverviewTabPackagesCard from './components/Packages/RunOverviewTabPackagesCard';
import RunOverviewTabParamsCard from './components/ParamsCard/RunOverviewTabParamsCard';
import RunOverviewSidebar from './components/RunOverviewSidebar/RunOverviewSidebar';
Expand Down Expand Up @@ -118,6 +119,14 @@ function RunOverviewTab({ runData, runHash }: IRunOverviewTabProps) {
/>
</ErrorBoundary>
)}
{_.isEmpty(cardsData.artifacts) ? null : (
<ErrorBoundary>
<RunOverviewTabCSVTablesCard
artifacts={cardsData.artifacts}
isRunInfoLoading={runData?.isRunInfoLoading}
/>
</ErrorBoundary>
)}
{_.isEmpty(cardsData.artifacts) ? null : (
<ErrorBoundary>
<RunOverviewTabArtifactsCard
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export interface IRunOverviewTabCSVTablesCardProps {
artifacts: Array<{ name: string; path: string; uri: string }>;
isRunInfoLoading: boolean;
}

export interface CSVData {
name: string;
uri: string;
data: Array<Record<string, any>>;
columns: string[];
error?: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
@use 'src/styles/abstracts' as *;

.RunOverviewTabCSVTablesCard {
.IllustrationBlock {
margin-bottom: 1.75rem;
}

&__csvTable {
margin-bottom: 2rem;

&:last-child {
margin-bottom: 0;
}

&__header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid $cuddle-50;

&__title {
display: flex;
align-items: center;
gap: 0.5rem;
}

&__actions {
display: flex;
gap: 0.5rem;
}
}

&__tableContainer {
max-height: 400px;
overflow: auto;
border: 1px solid $cuddle-50;
border-radius: $border-radius-sm;

.DataList {
.IllustrationBlock {
height: 200px;
}
}
}

&__error {
padding: 1rem;
background-color: #fef2f2;
border: 1px solid #fecaca;
border-radius: $border-radius-sm;
color: #dc2626;
font-size: 0.875rem;
}

&__loading {
display: flex;
justify-content: center;
align-items: center;
height: 200px;
border: 1px solid $cuddle-50;
border-radius: $border-radius-sm;
}
}
}
Loading
Loading