| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 |
- import { useParams } from 'common'
- import { toString as CronToString } from 'cronstrue'
- import { Edit3, List } from 'lucide-react'
- import Link from 'next/link'
- import { useRouter } from 'next/router'
- import { useState } from 'react'
- import {
- BreadcrumbItem,
- BreadcrumbLink,
- BreadcrumbList,
- BreadcrumbPage,
- BreadcrumbSeparator,
- Button,
- cn,
- Tooltip,
- TooltipContent,
- TooltipTrigger,
- } from 'ui'
- import { CodeBlock } from 'ui-patterns/CodeBlock'
- import {
- PageHeader,
- PageHeaderAside,
- PageHeaderBreadcrumb,
- PageHeaderDescription,
- PageHeaderMeta,
- PageHeaderSummary,
- PageHeaderTitle,
- } from 'ui-patterns/PageHeader'
- import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
- import { CreateCronJobSheet } from './CreateCronJobSheet/CreateCronJobSheet'
- import { isSecondsFormat, parseCronJobCommand } from './CronJobs.utils'
- import { PreviousRunsTab } from './PreviousRunsTab'
- import { useCronJobQuery } from '@/data/database-cron-jobs/database-cron-job-query'
- import { useEdgeFunctionsQuery } from '@/data/edge-functions/edge-functions-query'
- import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
- export const CronJobPage = () => {
- const router = useRouter()
- const { ref, id, pageId, childId } = useParams()
- const childLabel = router?.query?.['child-label'] as string
- const { data: project } = useSelectedProjectQuery()
- const [isEditSheetOpen, setIsEditSheetOpen] = useState(false)
- const jobId = Number(childId)
- const { data: job, isPending: isLoading } = useCronJobQuery({
- projectRef: project?.ref,
- connectionString: project?.connectionString,
- id: jobId,
- })
- const { data: edgeFunctions = [] } = useEdgeFunctionsQuery({ projectRef: project?.ref })
- // Parse the cron job command to check if it's an edge function
- const cronJobValues = parseCronJobCommand(job?.command || '', project?.ref!)
- const edgeFunction =
- cronJobValues.type === 'edge_function' ? cronJobValues.edgeFunctionName : undefined
- const edgeFunctionSlug = edgeFunction?.split('/functions/v1/').pop()
- const isValidEdgeFunction = edgeFunctions.some((x) => x.slug === edgeFunctionSlug)
- const pageTitle = childLabel || childId || 'Cron Job'
- const pageSubtitle = job ? (
- <div className="text-sm text-foreground-light">
- Running{' '}
- <Tooltip>
- <TooltipTrigger asChild>
- <span className="cursor-pointer underline decoration-dotted lowercase">
- {isSecondsFormat(job.schedule)
- ? job.schedule.toLowerCase()
- : CronToString(job.schedule.toLowerCase().replace(/\$/g, 'L'))}
- </span>
- </TooltipTrigger>
- <TooltipContent side="bottom" align="center">
- <div className="text-xs">
- <p className="font-mono mb-1">{job.schedule.toLowerCase()}</p>
- {!isSecondsFormat(job.schedule) && (
- <p className="text-foreground-light">{CronToString(job.schedule.toLowerCase())}</p>
- )}
- </div>
- </TooltipContent>
- </Tooltip>{' '}
- with command{' '}
- <Tooltip>
- <TooltipTrigger asChild>
- <code className="text-code-inline max-w-[200px] inline-block truncate align-bottom cursor-pointer">
- {job.command}
- </code>
- </TooltipTrigger>
- <TooltipContent
- side="bottom"
- align="center"
- className="min-w-[200px] max-w-[400px] text-wrap p-0"
- >
- <p className="text-xs font-mono px-2 py-1 border-b bg-surface-100">Command</p>
- <CodeBlock
- hideLineNumbers
- language="sql"
- value={job.command.trim()}
- className={cn(
- 'py-0 px-3.5 max-w-full prose dark:prose-dark border-0 rounded-t-none',
- '[&>code]:m-0 [&>code>span]:flex [&>code>span]:flex-wrap min-h-11',
- '[&>code]:text-xs'
- )}
- />
- </TooltipContent>
- </Tooltip>
- </div>
- ) : null
- // Secondary actions
- const secondaryActions = [
- <Button
- key="edit"
- type="outline"
- icon={<Edit3 strokeWidth={1.5} size="14" />}
- onClick={() => setIsEditSheetOpen(true)}
- >
- Edit
- </Button>,
- <Button key="view-logs" asChild type="outline" icon={<List strokeWidth={1.5} size="14" />}>
- <Link
- target="_blank"
- rel="noopener noreferrer"
- href={`/project/${project?.ref}/logs/pgcron-logs/`}
- >
- View Cron logs
- </Link>
- </Button>,
- ...(isValidEdgeFunction
- ? [
- <Button key="view-edge-logs" asChild type="outline">
- <Link
- target="_blank"
- rel="noopener noreferrer"
- href={`/project/${project?.ref}/functions/${edgeFunctionSlug}/logs`}
- >
- View Edge Function logs
- </Link>
- </Button>,
- ]
- : []),
- ]
- return (
- <>
- <PageHeader size="full" className="pb-6">
- <PageHeaderBreadcrumb>
- <BreadcrumbList>
- <BreadcrumbItem>
- <BreadcrumbLink asChild>
- <Link href={`/project/${ref}/integrations`}>Integrations</Link>
- </BreadcrumbLink>
- </BreadcrumbItem>
- <BreadcrumbSeparator />
- <BreadcrumbItem>
- <BreadcrumbLink asChild>
- <Link href={`/project/${ref}/integrations/${id}/${pageId}`}>Cron</Link>
- </BreadcrumbLink>
- </BreadcrumbItem>
- <BreadcrumbSeparator />
- <BreadcrumbItem>
- <BreadcrumbPage>{childLabel ?? job?.jobname ?? 'Cron Job'}</BreadcrumbPage>
- </BreadcrumbItem>
- </BreadcrumbList>
- </PageHeaderBreadcrumb>
- <PageHeaderMeta>
- <PageHeaderSummary>
- <PageHeaderTitle>{pageTitle}</PageHeaderTitle>
- <PageHeaderDescription>
- {isLoading ? <ShimmeringLoader className="py-0 h-[20px] w-96" /> : pageSubtitle}
- </PageHeaderDescription>
- </PageHeaderSummary>
- {secondaryActions.length > 0 && <PageHeaderAside>{secondaryActions}</PageHeaderAside>}
- </PageHeaderMeta>
- </PageHeader>
- <PreviousRunsTab />
- {job && (
- <CreateCronJobSheet
- open={isEditSheetOpen}
- selectedCronJob={job}
- onClose={() => setIsEditSheetOpen(false)}
- />
- )}
- </>
- )
- }
|