Files
artifactapi/ui/src/pages/RemoteDetail.tsx
T
unkin-agent e5313342ba Serve stale virtual members while they refresh (#167)
Virtual merges expire after 60s and the next request blocks on every member. One slow member (a large github_rpm remote) makes the re-merge outlast dnf's 30s timeout.

- add `serve_stale` to remotes (model, v2 API, migration 0008, UI, TUI), default false
- serve a flagged member's last good fetch to rpm/deb/alpine merges at once
- refresh it in the background, single-flighted, detached from the request
- block on the member again once its copy is over an hour old, or the flag is off
- prune copies with their merge cache; drop refreshes for evicted entries

Reviewed-on: #167
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
2026-10-25 15:44:16 +11:00

135 lines
4.6 KiB
TypeScript

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 { UsageInstructions } from '../components/UsageInstructions';
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">&larr; 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>
<dt>Serve Stale</dt>
<dd>{remote.serve_stale ? '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>
<UsageInstructions packageType={remote.package_type} repoClass="remote" name={remote.name} />
<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>
);
}