mirror of
https://github.com/supabase/supabase.git
synced 2026-10-17 15:19:08 +03:00
* chore: remove event enums * chore: remove more event enums * chore: remove unused HomepageHeroRequestDemoClickedEvent * chore: remove unused HomepageHeroRequestDemoClickedEvent * chore: rename custom_report events to use underscores * chore: remove telemetry enum * chore: remove unused imports * chore: remove enums from merged events * chore: remove unused imports * chore: add typedocs for module and hide exported types that are not events
158 lines
5.7 KiB
TypeScript
158 lines
5.7 KiB
TypeScript
import { Search } from 'lucide-react'
|
|
import { parseAsBoolean, parseAsString, useQueryState } from 'nuqs'
|
|
import { useState } from 'react'
|
|
|
|
import { CreateCronJobSheet } from 'components/interfaces/Integrations/CronJobs/CreateCronJobSheet'
|
|
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
|
|
import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
|
|
import { CronJob, useCronJobsQuery } from 'data/database-cron-jobs/database-cron-jobs-query'
|
|
import { useDatabaseExtensionsQuery } from 'data/database-extensions/database-extensions-query'
|
|
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
|
|
import { Button, Input, Sheet, SheetContent } from 'ui'
|
|
import { CronJobCard } from './CronJobCard'
|
|
import { DeleteCronJob } from './DeleteCronJob'
|
|
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
|
|
|
|
const EMPTY_CRON_JOB = {
|
|
jobname: '',
|
|
schedule: '',
|
|
active: true,
|
|
command: '',
|
|
}
|
|
|
|
export const CronjobsTab = () => {
|
|
const { project } = useProjectContext()
|
|
const org = useSelectedOrganization()
|
|
|
|
const [searchQuery, setSearchQuery] = useQueryState('search', parseAsString.withDefault(''))
|
|
const [createCronJobSheetShown, setCreateCronJobSheetShown] = useQueryState(
|
|
'dialog-shown',
|
|
parseAsBoolean.withDefault(false).withOptions({ clearOnDefault: true })
|
|
)
|
|
|
|
// used for confirmation prompt in the Create Cron Job Sheet
|
|
const [isClosingCreateCronJobSheet, setIsClosingCreateCronJobSheet] = useState(false)
|
|
const [cronJobForEditing, setCronJobForEditing] = useState<
|
|
Pick<CronJob, 'jobname' | 'schedule' | 'active' | 'command'> | undefined
|
|
>()
|
|
const [cronJobForDeletion, setCronJobForDeletion] = useState<CronJob | undefined>()
|
|
|
|
const { data: cronJobs, isLoading } = useCronJobsQuery({
|
|
projectRef: project?.ref,
|
|
connectionString: project?.connectionString,
|
|
})
|
|
|
|
const { data: extensions } = useDatabaseExtensionsQuery({
|
|
projectRef: project?.ref,
|
|
connectionString: project?.connectionString,
|
|
})
|
|
|
|
const { mutate: sendEvent } = useSendEventMutation()
|
|
|
|
// check pg_cron version to see if it supports seconds
|
|
const pgCronExtension = (extensions ?? []).find((ext) => ext.name === 'pg_cron')
|
|
const installedVersion = pgCronExtension?.installed_version
|
|
const supportsSeconds = installedVersion ? parseFloat(installedVersion) >= 1.5 : false
|
|
|
|
if (isLoading)
|
|
return (
|
|
<div className="p-10">
|
|
<GenericSkeletonLoader />
|
|
</div>
|
|
)
|
|
|
|
const filteredCronJobs =
|
|
searchQuery.length > 0
|
|
? (cronJobs ?? []).filter((cj) => cj?.jobname?.includes(searchQuery || ''))
|
|
: cronJobs ?? []
|
|
|
|
const onOpenCreateJobSheet = () => {
|
|
sendEvent({
|
|
action: 'cron_job_create_clicked',
|
|
groups: { project: project?.ref ?? 'Unknown', organization: org?.slug ?? 'Unknown' },
|
|
})
|
|
setCreateCronJobSheetShown(true)
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<div className="w-full space-y-4 p-4 md:p-10">
|
|
{(cronJobs ?? []).length == 0 ? (
|
|
<div className="border rounded border-default px-20 py-16 flex flex-col items-center justify-center space-y-4 border-dashed">
|
|
<p className="text-sm text-foreground">No cron jobs created yet</p>
|
|
<Button onClick={onOpenCreateJobSheet}>Create job</Button>
|
|
</div>
|
|
) : (
|
|
<div className="w-full space-y-4">
|
|
<div className="flex items-center justify-between flex-wrap">
|
|
<Input
|
|
placeholder="Search for a job"
|
|
size="small"
|
|
icon={<Search size={14} />}
|
|
value={searchQuery || ''}
|
|
className="w-64"
|
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
/>
|
|
|
|
<Button onClick={onOpenCreateJobSheet}>Create job</Button>
|
|
</div>
|
|
{filteredCronJobs.length === 0 ? (
|
|
<div
|
|
className={
|
|
'border rounded border-default px-20 py-16 flex flex-col items-center justify-center space-y-4 border-dashed'
|
|
}
|
|
>
|
|
<p className="text-sm text-foreground">No results found</p>
|
|
<p className="text-sm text-foreground-light">
|
|
Your search for "{searchQuery}" did not return any results
|
|
</p>
|
|
</div>
|
|
) : isLoading ? (
|
|
<div className="p-10">
|
|
<GenericSkeletonLoader />
|
|
</div>
|
|
) : (
|
|
filteredCronJobs.map((job) => (
|
|
<CronJobCard
|
|
key={job.jobid}
|
|
job={job}
|
|
onEditCronJob={(job) => {
|
|
setCronJobForEditing(job)
|
|
setCreateCronJobSheetShown(true)
|
|
}}
|
|
onDeleteCronJob={(job) => setCronJobForDeletion(job)}
|
|
/>
|
|
))
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<DeleteCronJob
|
|
visible={!!cronJobForDeletion}
|
|
onClose={() => setCronJobForDeletion(undefined)}
|
|
cronJob={cronJobForDeletion!}
|
|
/>
|
|
|
|
<Sheet
|
|
open={!!createCronJobSheetShown}
|
|
onOpenChange={() => setIsClosingCreateCronJobSheet(true)}
|
|
>
|
|
<SheetContent size="default" tabIndex={undefined}>
|
|
<CreateCronJobSheet
|
|
selectedCronJob={cronJobForEditing ?? EMPTY_CRON_JOB}
|
|
supportsSeconds={supportsSeconds}
|
|
onClose={() => {
|
|
setIsClosingCreateCronJobSheet(false)
|
|
setCronJobForEditing(undefined)
|
|
setCreateCronJobSheetShown(false)
|
|
}}
|
|
isClosing={isClosingCreateCronJobSheet}
|
|
setIsClosing={setIsClosingCreateCronJobSheet}
|
|
/>
|
|
</SheetContent>
|
|
</Sheet>
|
|
</>
|
|
)
|
|
}
|