259 lines
7.0 KiB
TypeScript
259 lines
7.0 KiB
TypeScript
import { useMutation } from '@apollo/client';
|
|
import { useNavigate } from '@tanstack/react-router';
|
|
import { CalendarIcon } from 'lucide-react';
|
|
import { memo, useCallback, useState } from 'react';
|
|
import { toast } from 'sonner';
|
|
import { Button } from '@/components/ui/button';
|
|
import {
|
|
Card,
|
|
CardAction,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from '@/components/ui/card';
|
|
import { Cron } from '@/components/ui/cron';
|
|
import { CronMode } from '@/components/ui/cron/types';
|
|
import {
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from '@/components/ui/dialog';
|
|
import { Separator } from '@/components/ui/separator';
|
|
import { Spinner } from '@/components/ui/spinner';
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|
import { INSERT_CRON } from '@/domains/recorder/schema/cron';
|
|
import { useInject } from '@/infra/di/inject';
|
|
import {
|
|
type InsertCronMutation,
|
|
type InsertCronMutationVariables,
|
|
SubscriberTaskTypeEnum,
|
|
} from '@/infra/graphql/gql/graphql';
|
|
import { IntlService } from '@/infra/intl/intl.service';
|
|
|
|
const SUBSCRIPTION_TASK_CRON_PRESETS = [
|
|
{
|
|
label: 'Every hour',
|
|
value: '0 0 * * * *',
|
|
description: 'Runs at the top of every hour',
|
|
category: 'common',
|
|
},
|
|
{
|
|
label: 'Daily at midnight',
|
|
value: '0 0 0 * * *',
|
|
description: 'Runs once daily at 00:00',
|
|
category: 'daily',
|
|
},
|
|
{
|
|
label: 'Daily at 9 AM',
|
|
value: '0 0 9 * * *',
|
|
description: 'Runs daily at 9:00 AM',
|
|
category: 'daily',
|
|
},
|
|
{
|
|
label: 'Every Sunday',
|
|
value: '0 0 0 * * 0',
|
|
description: 'Runs every Sunday at midnight',
|
|
category: 'weekly',
|
|
},
|
|
{
|
|
label: 'First day of month',
|
|
value: '0 0 0 1 * *',
|
|
description: 'Runs on the 1st day of every month',
|
|
category: 'monthly',
|
|
},
|
|
{
|
|
label: 'Every year',
|
|
value: '0 0 0 1 1 *',
|
|
description: 'Runs on January 1st every year',
|
|
category: 'yearly',
|
|
},
|
|
];
|
|
|
|
const CRON_TABS = [
|
|
{
|
|
tab: SubscriberTaskTypeEnum.SyncOneSubscriptionSources,
|
|
label: 'Sync sources',
|
|
description: 'Syncs subscription sources',
|
|
},
|
|
{
|
|
tab: SubscriberTaskTypeEnum.SyncOneSubscriptionFeedsIncremental,
|
|
label: 'Feeds incremental',
|
|
description: 'Syncs incremental subscription feeds',
|
|
},
|
|
{
|
|
tab: SubscriberTaskTypeEnum.SyncOneSubscriptionFeedsFull,
|
|
label: 'Feeds full',
|
|
description: 'Syncs all subscription feeds',
|
|
},
|
|
];
|
|
|
|
export type SubscriptionCronCreationViewCompletePayload = {
|
|
id: number;
|
|
};
|
|
|
|
export interface SubscriptionCronCreationViewProps {
|
|
subscriptionId: number;
|
|
onComplete: (payload: SubscriptionCronCreationViewCompletePayload) => void;
|
|
}
|
|
|
|
export interface SubscriptionCronFormProps {
|
|
tab: (typeof CRON_TABS)[number];
|
|
timezone: string;
|
|
onComplete: (payload: SubscriptionCronFormPayload) => void;
|
|
}
|
|
|
|
export interface SubscriptionCronFormPayload {
|
|
cronExpr: string;
|
|
}
|
|
|
|
export const SubscriptionCronForm = memo(
|
|
({ tab, timezone, onComplete }: SubscriptionCronFormProps) => {
|
|
const [cronExpr, setCronExpr] = useState<string>('');
|
|
return (
|
|
<Card className="overflow-y-auto">
|
|
<CardHeader>
|
|
<CardTitle>{tab.label}</CardTitle>
|
|
<CardDescription>{tab.description}</CardDescription>
|
|
<CardAction>
|
|
<Button variant="default" onClick={() => onComplete({ cronExpr })}>
|
|
<CalendarIcon className="size-4" />
|
|
Create
|
|
</Button>
|
|
</CardAction>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Separator />
|
|
<Cron
|
|
mode={CronMode.Both}
|
|
withCard={false}
|
|
showPresets={false}
|
|
presets={SUBSCRIPTION_TASK_CRON_PRESETS}
|
|
timezone={timezone}
|
|
onChange={setCronExpr}
|
|
value={cronExpr}
|
|
/>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
);
|
|
|
|
export const SubscriptionCronCreationView = memo(
|
|
({ subscriptionId, onComplete }: SubscriptionCronCreationViewProps) => {
|
|
const intlService = useInject(IntlService);
|
|
|
|
const [insertCron, { loading: loadingInsert }] = useMutation<
|
|
InsertCronMutation,
|
|
InsertCronMutationVariables
|
|
>(INSERT_CRON, {
|
|
onCompleted: (data) => {
|
|
toast.success('Cron created');
|
|
onComplete(data.cronCreateOne);
|
|
},
|
|
onError: (error) => {
|
|
toast.error('Failed to sync subscription', {
|
|
description: error.message,
|
|
});
|
|
},
|
|
});
|
|
|
|
const loading = loadingInsert;
|
|
|
|
return (
|
|
<Tabs
|
|
defaultValue={CRON_TABS[0].tab}
|
|
className="flex min-h-0 flex-1 flex-col"
|
|
>
|
|
<div className="flex w-full shrink-0 overflow-x-auto">
|
|
<TabsList className="flex items-center justify-center whitespace-nowrap">
|
|
{CRON_TABS.map((tab) => (
|
|
<TabsTrigger key={tab.tab} value={tab.tab} className="w-fit px-4">
|
|
{tab.label}
|
|
</TabsTrigger>
|
|
))}
|
|
</TabsList>
|
|
</div>
|
|
{CRON_TABS.map((tab) => (
|
|
<TabsContent
|
|
key={tab.tab}
|
|
value={tab.tab}
|
|
className="flex-1 space-y-2"
|
|
asChild
|
|
>
|
|
<SubscriptionCronForm
|
|
tab={tab}
|
|
onComplete={(payload) => {
|
|
insertCron({
|
|
variables: {
|
|
data: {
|
|
cronExpr: payload.cronExpr,
|
|
cronTimezone: intlService.timezone,
|
|
subscriberTaskCron: {
|
|
subscriptionId,
|
|
taskType: tab.tab,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
}}
|
|
timezone={intlService.timezone}
|
|
/>
|
|
</TabsContent>
|
|
))}
|
|
{loading && (
|
|
<div className="absolute inset-0 flex flex-row items-center justify-center gap-2">
|
|
<Spinner variant="circle-filled" size="16" />
|
|
<span>Creating cron...</span>
|
|
</div>
|
|
)}
|
|
</Tabs>
|
|
);
|
|
}
|
|
);
|
|
|
|
export interface SubscriptionCronCreationDialogContentProps {
|
|
subscriptionId: number;
|
|
onCancel?: VoidFunction;
|
|
}
|
|
|
|
export const SubscriptionCronCreationDialogContent = memo(
|
|
({
|
|
subscriptionId,
|
|
onCancel,
|
|
}: SubscriptionCronCreationDialogContentProps) => {
|
|
const navigate = useNavigate();
|
|
|
|
const handleCreationComplete = useCallback(
|
|
(payload: SubscriptionCronCreationViewCompletePayload) => {
|
|
navigate({
|
|
to: '/tasks/cron/detail/$id',
|
|
params: {
|
|
id: `${payload.id}`,
|
|
},
|
|
});
|
|
},
|
|
[navigate]
|
|
);
|
|
|
|
return (
|
|
<DialogContent
|
|
onAbort={onCancel}
|
|
className="flex max-h-[80vh] flex-col xl:max-w-2xl"
|
|
>
|
|
<DialogHeader>
|
|
<DialogTitle>Create Cron</DialogTitle>
|
|
<DialogDescription>
|
|
Create a cron to execute the subscription.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<SubscriptionCronCreationView
|
|
subscriptionId={subscriptionId}
|
|
onComplete={handleCreationComplete}
|
|
/>
|
|
</DialogContent>
|
|
);
|
|
}
|
|
);
|