Complete rewrite of ArtifactAPI from Python/FastAPI to Go as a single binary. Core engine: - 10 package providers: generic, docker, helm, pypi, npm, rpm, alpine, puppet, terraform, goproxy — each with built-in mutable patterns - Content-addressable storage (SHA256 dedup across all remotes) - Three-tier caching: Redis (TTL/locks) → S3/MinIO (blobs) → upstream - Classifier with allowlist/blocklist per-remote (empty = allow all) - Circuit breaker, conditional revalidation, stale-on-error - Background garbage collection for orphaned blobs - Access logging to PostgreSQL API: - v1 proxy endpoints (backwards compatible) - v2 management API: CRUD remotes/virtuals, object browser, stats, health, SSE events, probe/test endpoint - Virtual repos with index merging (Helm YAML + PyPI HTML) Frontend (React + Vite, separate Dockerfile): - Dashboard with stats, health indicators, top remotes - Remotes list with type filter, remote detail with config/patterns - Object browser with pagination and evict - Test Remote page: probe any remote path, see headers/size/timing - Virtuals page with expandable member lists TUI (Bubble Tea): - Dashboard, remotes list/detail, object browser, virtuals - Vim-style navigation, artifactapi tui --endpoint <url> Infrastructure: - S3 client supports MinIO, Ceph RGW, AWS S3 (minio-go) - PostgreSQL schema with migrations - Docker Compose: API + UI + Postgres 17 + Redis 7 + MinIO - Makefile with Go version check, build/test/lint/fmt/e2e targets - Distroless Docker image (~15MB) Testing: - Unit tests for models, classifier, providers, mergers - E2E tests with testcontainers-go (real Postgres/Redis/MinIO) Terraform config: - All 40 production remotes + helm virtual as HCL - Provider repo: terraform-provider-artifactapi v0.0.1 (separate) --------- Co-authored-by: Ben Vincent <ben@unkin.net> Reviewed-on: #47
This commit was merged in pull request #47.
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useParams, Link } from 'react-router-dom';
|
||||
import { api } from '../api/client';
|
||||
import type { Remote } from '../api/types';
|
||||
import { Badge } from '../components/Badge';
|
||||
import './RemoteDetail.css';
|
||||
|
||||
export function RemoteDetail() {
|
||||
const { name } = useParams<{ name: string }>();
|
||||
const [remote, setRemote] = useState<Remote | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!name) return;
|
||||
api.getRemote(name)
|
||||
.then(setRemote)
|
||||
.catch(e => setError(e.message));
|
||||
}, [name]);
|
||||
|
||||
if (error) return <div className="error-banner">{error}</div>;
|
||||
if (!remote) return <div className="loading">Loading...</div>;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="detail-header">
|
||||
<Link to="/remotes" className="back-link">← Remotes</Link>
|
||||
<h1 className="page-title">{remote.name}</h1>
|
||||
<div className="detail-badges">
|
||||
<Badge variant="blue">{remote.package_type}</Badge>
|
||||
{remote.managed_by && <Badge variant="green">managed by {remote.managed_by}</Badge>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{remote.description && (
|
||||
<p className="detail-description">{remote.description}</p>
|
||||
)}
|
||||
|
||||
<div className="detail-grid">
|
||||
<div className="detail-section">
|
||||
<h3 className="section-label">Configuration</h3>
|
||||
<dl className="detail-dl">
|
||||
<dt>Base URL</dt>
|
||||
<dd className="mono">{remote.base_url}</dd>
|
||||
<dt>Immutable TTL</dt>
|
||||
<dd className="mono">{remote.immutable_ttl === 0 ? 'forever' : `${remote.immutable_ttl}s`}</dd>
|
||||
<dt>Mutable TTL</dt>
|
||||
<dd className="mono">{remote.mutable_ttl}s</dd>
|
||||
<dt>Conditional Revalidation</dt>
|
||||
<dd>{remote.check_mutable ? 'enabled' : 'disabled'}</dd>
|
||||
<dt>Stale on Error</dt>
|
||||
<dd>{remote.stale_on_error ? 'enabled' : 'disabled'}</dd>
|
||||
{remote.releases_remote && (
|
||||
<>
|
||||
<dt>Releases Remote</dt>
|
||||
<dd>
|
||||
<Link to={`/remotes/${remote.releases_remote}`} className="mono">
|
||||
{remote.releases_remote}
|
||||
</Link>
|
||||
</dd>
|
||||
</>
|
||||
)}
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div className="detail-section">
|
||||
<h3 className="section-label">Access Control</h3>
|
||||
<dl className="detail-dl">
|
||||
<dt>Patterns</dt>
|
||||
<dd>
|
||||
{remote.patterns?.length
|
||||
? <PatternList patterns={remote.patterns} />
|
||||
: <span className="text-muted">none (proxy all)</span>}
|
||||
</dd>
|
||||
<dt>Blocklist</dt>
|
||||
<dd>
|
||||
{remote.blocklist?.length
|
||||
? <PatternList patterns={remote.blocklist} />
|
||||
: <span className="text-muted">none</span>}
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div className="detail-section">
|
||||
<h3 className="section-label">Classification Overrides</h3>
|
||||
<dl className="detail-dl">
|
||||
<dt>Mutable</dt>
|
||||
<dd>
|
||||
{remote.mutable_patterns?.length
|
||||
? <PatternList patterns={remote.mutable_patterns} />
|
||||
: <span className="text-muted">provider defaults only</span>}
|
||||
</dd>
|
||||
<dt>Immutable</dt>
|
||||
<dd>
|
||||
{remote.immutable_patterns?.length
|
||||
? <PatternList patterns={remote.immutable_patterns} />
|
||||
: <span className="text-muted">provider defaults only</span>}
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
{remote.ban_tags_enabled && (
|
||||
<div className="detail-section">
|
||||
<h3 className="section-label">Tag Banning</h3>
|
||||
<dl className="detail-dl">
|
||||
<dt>Banned Tags</dt>
|
||||
<dd><PatternList patterns={remote.ban_tags || []} /></dd>
|
||||
</dl>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="detail-actions">
|
||||
<Link to={`/remotes/${remote.name}/objects`} className="btn btn-primary">
|
||||
Browse Objects
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PatternList({ patterns }: { patterns: string[] }) {
|
||||
return (
|
||||
<ul className="pattern-list">
|
||||
{patterns.map((p, i) => (
|
||||
<li key={i} className="mono">{p}</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user