← back to Homesonspec

apps/admin/src/app/sources/page.tsx

50 lines

import { prisma } from "@homesonspec/database";
import { CreatedChip } from "@homesonspec/shared-ui";

export const dynamic = "force-dynamic";

export default async function SourcesPage() {
  const sources = await prisma.sourceRegistry.findMany({
    orderBy: { key: "asc" },
    include: { _count: { select: { snapshots: true, staged: true, homes: true } } },
  });

  return (
    <div>
      <h1 className="text-2xl font-bold">Source registry</h1>
      <p className="mt-1 text-sm text-neutral-500">
        Every source records its collection method, permissions, media rights, rate limits, and health.
        Publishing stops automatically when a source degrades.
      </p>
      <div className="mt-4 grid gap-4 md:grid-cols-2">
        {sources.map((source) => (
          <div key={source.id} className="rounded-xl border border-neutral-200 bg-white p-4 shadow-sm" data-testid="source-card">
            <div className="flex items-center justify-between">
              <h2 className="font-semibold">{source.name}</h2>
              <span className={`rounded-full px-2 py-0.5 text-xs font-medium ${
                source.health === "HEALTHY" ? "bg-emerald-100 text-emerald-800" :
                source.health === "DEGRADED" ? "bg-amber-100 text-amber-800" :
                source.health === "PAUSED" ? "bg-neutral-200 text-neutral-600" :
                "bg-red-100 text-red-800"}`}>
                {source.health}
              </span>
            </div>
            <dl className="mt-2 grid grid-cols-2 gap-x-4 gap-y-1 text-xs">
              <div><dt className="text-neutral-500">Key</dt><dd className="font-mono">{source.key}</dd></div>
              <div><dt className="text-neutral-500">Method</dt><dd>{source.collectionMethod}</dd></div>
              <div><dt className="text-neutral-500">Media rights</dt><dd>{source.mediaRights}</dd></div>
              <div><dt className="text-neutral-500">Rate limit</dt><dd>{source.rateLimitRpm ? `${source.rateLimitRpm}/min` : "n/a"}</dd></div>
              <div><dt className="text-neutral-500">Fresh interval</dt><dd>{source.freshIntervalHours}h</dd></div>
              <div><dt className="text-neutral-500">Aging interval</dt><dd>{source.agingIntervalHours}h</dd></div>
              <div><dt className="text-neutral-500">Snapshots</dt><dd>{source._count.snapshots}</dd></div>
              <div><dt className="text-neutral-500">Staged / homes</dt><dd>{source._count.staged} / {source._count.homes}</dd></div>
            </dl>
            {source.notes && <p className="mt-2 text-xs text-neutral-500">{source.notes}</p>}
            <div className="mt-2"><CreatedChip date={source.createdAt} /></div>
          </div>
        ))}
      </div>
    </div>
  );
}