File uploads external source

This commit is contained in:
sriram
2026-08-28 08:54:00 +05:30
parent e073d68d60
commit 8f8f370566
4 changed files with 402 additions and 3 deletions

View File

@@ -1,9 +1,10 @@
import React, { useEffect, useState } from 'react';
import { Wrench, Database, UploadCloud, Layers } from 'lucide-react';
import { Wrench, Database, UploadCloud, Layers, Inbox } from 'lucide-react';
import { api } from '../api/client';
import { NavigationHeader } from '../components/NavigationHeader';
import { StoreCatalogPanel } from './StoreCatalogPanel';
import { BatchCatalogPanel } from './BatchCatalogPanel';
import { InboxPanel } from './InboxPanel';
/*
* The admin panel carries two views: the read-only project overview, and the
@@ -20,12 +21,22 @@ const TABS = [
{ id: 'project', label: 'Project Overview Details', icon: Database },
{ id: 'ingest', label: 'Store Catalog Ingestion', icon: UploadCloud },
{ id: 'batch', label: 'Batch Catalog Ingestion', icon: Layers },
{ id: 'inbox', label: 'Review Inbox', icon: Inbox },
];
// How often the pending count is refreshed. Slower than the 3s a running batch
// polls at, because this is a "has anything arrived?" question, not progress on
// something already in flight - and it runs on whichever tab is open, so it is
// the one poll in the app that never stops while the page is up.
const INBOX_POLL_MS = 15000;
export function AdminPage() {
const [activeTab, setActiveTab] = useState('project');
const [projectDetails, setProjectDetails] = useState(null);
const [loadingProject, setLoadingProject] = useState(true);
const [pendingUploads, setPendingUploads] = useState(0);
// Handed to BatchCatalogPanel when a run is started from the inbox.
const [adoptedBatch, setAdoptedBatch] = useState(null);
const fetchProjectDetails = async () => {
try {
@@ -43,6 +54,29 @@ export function AdminPage() {
fetchProjectDetails();
}, []);
// The badge. Polled here rather than inside InboxPanel so the count is live
// whichever tab is open - the whole point is finding out that a colleague
// sent something while you were looking at a different screen. A failed poll
// is swallowed: a transient network blip must not replace the panel with an
// error, and the next tick retries.
useEffect(() => {
let alive = true;
const check = async () => {
try {
const { pending_count: count } = await api.listInbox();
if (alive) setPendingUploads(count || 0);
} catch {
/* next tick retries */
}
};
check();
const timer = setInterval(check, INBOX_POLL_MS);
return () => {
alive = false;
clearInterval(timer);
};
}, []);
return (
<div className="flex h-screen w-full flex-col overflow-y-auto bg-paper-100 text-ink-950 font-sans">
<NavigationHeader
@@ -64,13 +98,34 @@ export function AdminPage() {
}`}
>
<Icon className="h-4 w-4" /> {label}
{id === 'inbox' && pendingUploads > 0 && (
<span
className={`ml-1 min-w-5 px-1.5 py-0.5 rounded-full text-[10px] font-extrabold leading-none ${
activeTab === id
? 'bg-slate-950 text-amber-400'
: 'bg-amber-500 text-slate-950'
}`}
>
{pendingUploads}
</span>
)}
</button>
))}
</div>
{activeTab === 'ingest' && <StoreCatalogPanel />}
{activeTab === 'batch' && <BatchCatalogPanel />}
{activeTab === 'batch' && <BatchCatalogPanel adoptBatch={adoptedBatch} />}
{activeTab === 'inbox' && (
<InboxPanel
onBatchStarted={(batch) => {
setAdoptedBatch(batch);
setActiveTab('batch');
setPendingUploads((n) => Math.max(0, n - batch.files_total));
}}
/>
)}
{activeTab === 'project' && (
<div className="space-y-6">