mirror of
https://github.com/sourcebot-dev/sourcebot.git
synced 2025-12-11 20:05:25 +00:00
206 lines
No EOL
8.7 KiB
TypeScript
206 lines
No EOL
8.7 KiB
TypeScript
import { sew } from "@/actions"
|
|
import { Badge } from "@/components/ui/badge"
|
|
import { Button } from "@/components/ui/button"
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
|
import { Skeleton } from "@/components/ui/skeleton"
|
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
|
|
import { env } from "@sourcebot/shared"
|
|
import { SINGLE_TENANT_ORG_DOMAIN } from "@/lib/constants"
|
|
import { ServiceErrorException } from "@/lib/serviceError"
|
|
import { cn, getCodeHostInfoForRepo, isServiceError } from "@/lib/utils"
|
|
import { withOptionalAuthV2 } from "@/withAuthV2"
|
|
import { getConfigSettings, repoMetadataSchema } from "@sourcebot/shared"
|
|
import { ExternalLink, Info } from "lucide-react"
|
|
import Image from "next/image"
|
|
import Link from "next/link"
|
|
import { notFound } from "next/navigation"
|
|
import { Suspense } from "react"
|
|
import { BackButton } from "../../components/backButton"
|
|
import { DisplayDate } from "../../components/DisplayDate"
|
|
import { RepoBranchesTable } from "../components/repoBranchesTable"
|
|
import { RepoJobsTable } from "../components/repoJobsTable"
|
|
|
|
export default async function RepoDetailPage({ params }: { params: Promise<{ id: string }> }) {
|
|
const { id } = await params
|
|
const repo = await getRepoWithJobs(Number.parseInt(id))
|
|
if (isServiceError(repo)) {
|
|
throw new ServiceErrorException(repo);
|
|
}
|
|
|
|
const codeHostInfo = getCodeHostInfoForRepo({
|
|
codeHostType: repo.external_codeHostType,
|
|
name: repo.name,
|
|
displayName: repo.displayName ?? undefined,
|
|
webUrl: repo.webUrl ?? undefined,
|
|
});
|
|
|
|
const configSettings = await getConfigSettings(env.CONFIG_PATH);
|
|
|
|
const nextIndexAttempt = (() => {
|
|
const latestJob = repo.jobs.length > 0 ? repo.jobs[0] : null;
|
|
if (!latestJob) {
|
|
return undefined;
|
|
}
|
|
|
|
if (latestJob.completedAt) {
|
|
return new Date(latestJob.completedAt.getTime() + configSettings.reindexIntervalMs);
|
|
}
|
|
|
|
return undefined;
|
|
})();
|
|
|
|
const repoMetadata = repoMetadataSchema.parse(repo.metadata);
|
|
|
|
return (
|
|
<>
|
|
<div className="mb-6">
|
|
<BackButton
|
|
href={`/${SINGLE_TENANT_ORG_DOMAIN}/repos`}
|
|
name="Back to repositories"
|
|
className="mb-2"
|
|
/>
|
|
|
|
<div className="flex items-start justify-between">
|
|
<div>
|
|
<h1 className="text-3xl font-semibold">{repo.displayName || repo.name}</h1>
|
|
<p className="text-muted-foreground mt-2">{repo.name}</p>
|
|
</div>
|
|
{codeHostInfo.repoLink && (
|
|
<Button variant="outline" asChild>
|
|
<Link href={codeHostInfo.repoLink} target="_blank" rel="noopener noreferrer" className="flex items-center">
|
|
<Image
|
|
src={codeHostInfo.icon}
|
|
alt={codeHostInfo.codeHostName}
|
|
className={cn("w-4 h-4 flex-shrink-0", codeHostInfo.iconClassName)}
|
|
/>
|
|
Open in {codeHostInfo.codeHostName}
|
|
<ExternalLink className="ml-2 h-4 w-4" />
|
|
</Link>
|
|
</Button>
|
|
)}
|
|
</div>
|
|
|
|
<div className="flex gap-2 mt-4">
|
|
{repo.isArchived && <Badge variant="secondary">Archived</Badge>}
|
|
{repo.isPublic && <Badge variant="outline">Public</Badge>}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid gap-4 md:grid-cols-3 mb-8">
|
|
<Card>
|
|
<CardHeader className="pb-3">
|
|
<CardTitle className="text-sm font-medium flex items-center gap-1.5">
|
|
Created
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<Info className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
|
|
</TooltipTrigger>
|
|
<TooltipContent>
|
|
<p>When this repository was first added to Sourcebot</p>
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<span className="text-2xl font-semibold"><DisplayDate date={repo.createdAt} /></span>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader className="pb-3">
|
|
<CardTitle className="text-sm font-medium flex items-center gap-1.5">
|
|
Last indexed
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<Info className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
|
|
</TooltipTrigger>
|
|
<TooltipContent>
|
|
<p>The last time this repository was successfully indexed</p>
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<span className="text-2xl font-semibold">{repo.indexedAt ? <DisplayDate date={repo.indexedAt} /> : "Never"}</span>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader className="pb-3">
|
|
<CardTitle className="text-sm font-medium flex items-center gap-1.5">
|
|
Scheduled
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<Info className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
|
|
</TooltipTrigger>
|
|
<TooltipContent>
|
|
<p>When the next indexing job is scheduled to run</p>
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<span className="text-2xl font-semibold">{nextIndexAttempt ? <DisplayDate date={nextIndexAttempt} /> : "-"}</span>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{repoMetadata.indexedRevisions && (
|
|
<Card className="mb-8">
|
|
<CardHeader>
|
|
<div className="flex items-center gap-2">
|
|
<CardTitle>Indexed Branches</CardTitle>
|
|
</div>
|
|
<CardDescription>Branches that have been indexed for this repository. <Link href="https://docs.sourcebot.dev/docs/features/search/multi-branch-indexing" target="_blank" className="text-link hover:underline">Docs</Link></CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Suspense fallback={<Skeleton className="h-64 w-full" />}>
|
|
<RepoBranchesTable
|
|
indexRevisions={repoMetadata.indexedRevisions}
|
|
repoWebUrl={repo.webUrl}
|
|
repoCodeHostType={repo.external_codeHostType}
|
|
/>
|
|
</Suspense>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Indexing History</CardTitle>
|
|
<CardDescription>History of all indexing and cleanup jobs for this repository.</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Suspense fallback={<Skeleton className="h-96 w-full" />}>
|
|
<RepoJobsTable data={repo.jobs} />
|
|
</Suspense>
|
|
</CardContent>
|
|
</Card>
|
|
</>
|
|
)
|
|
}
|
|
|
|
const getRepoWithJobs = async (repoId: number) => sew(() =>
|
|
withOptionalAuthV2(async ({ prisma, org }) => {
|
|
|
|
const repo = await prisma.repo.findUnique({
|
|
where: {
|
|
id: repoId,
|
|
orgId: org.id,
|
|
},
|
|
include: {
|
|
jobs: {
|
|
orderBy: {
|
|
createdAt: 'desc'
|
|
},
|
|
}
|
|
},
|
|
});
|
|
|
|
if (!repo) {
|
|
return notFound();
|
|
}
|
|
|
|
return repo;
|
|
})
|
|
); |