fix: fix
This commit is contained in:
parent
5be5b9f634
commit
81bf27ed28
38
Cargo.lock
generated
38
Cargo.lock
generated
@ -551,7 +551,7 @@ dependencies = [
|
|||||||
"derive_builder",
|
"derive_builder",
|
||||||
"diligent-date-parser",
|
"diligent-date-parser",
|
||||||
"never",
|
"never",
|
||||||
"quick-xml",
|
"quick-xml 0.37.5",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1922,6 +1922,17 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derivative"
|
||||||
|
version = "2.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 1.0.109",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_builder"
|
name = "derive_builder"
|
||||||
version = "0.20.2"
|
version = "0.20.2"
|
||||||
@ -2332,11 +2343,12 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fancy-regex"
|
name = "fancy-regex"
|
||||||
version = "0.14.0"
|
version = "0.15.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298"
|
checksum = "d6215aee357f8c7c989ebb4b8466ca4d7dc93b3957039f2fc3ea2ade8ea5f279"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bit-set",
|
"bit-set",
|
||||||
|
"derivative",
|
||||||
"regex-automata 0.4.9",
|
"regex-automata 0.4.9",
|
||||||
"regex-syntax 0.8.5",
|
"regex-syntax 0.8.5",
|
||||||
]
|
]
|
||||||
@ -4394,7 +4406,7 @@ dependencies = [
|
|||||||
"futures",
|
"futures",
|
||||||
"httparse",
|
"httparse",
|
||||||
"network-interface",
|
"network-interface",
|
||||||
"quick-xml",
|
"quick-xml 0.37.5",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"tokio",
|
"tokio",
|
||||||
@ -5166,7 +5178,7 @@ dependencies = [
|
|||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
"parking_lot 0.12.4",
|
"parking_lot 0.12.4",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"quick-xml",
|
"quick-xml 0.37.5",
|
||||||
"rand 0.9.1",
|
"rand 0.9.1",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"ring",
|
"ring",
|
||||||
@ -5219,7 +5231,7 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"md-5",
|
"md-5",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"quick-xml",
|
"quick-xml 0.37.5",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@ -6505,6 +6517,16 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quick-xml"
|
||||||
|
version = "0.38.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8927b0664f5c5a98265138b7e3f90aa19a6b21353182469ace36d4ac527b7b1b"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quinn"
|
name = "quinn"
|
||||||
version = "0.11.8"
|
version = "0.11.8"
|
||||||
@ -6798,7 +6820,7 @@ dependencies = [
|
|||||||
"paste",
|
"paste",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"polars",
|
"polars",
|
||||||
"quick-xml",
|
"quick-xml 0.38.0",
|
||||||
"quirks_path",
|
"quirks_path",
|
||||||
"rand 0.9.1",
|
"rand 0.9.1",
|
||||||
"regex",
|
"regex",
|
||||||
@ -7233,7 +7255,7 @@ dependencies = [
|
|||||||
"atom_syndication",
|
"atom_syndication",
|
||||||
"derive_builder",
|
"derive_builder",
|
||||||
"never",
|
"never",
|
||||||
"quick-xml",
|
"quick-xml 0.37.5",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -109,7 +109,7 @@ sea-orm = { version = "1.1", features = [
|
|||||||
figment = { version = "0.10", features = ["toml", "json", "env", "yaml"] }
|
figment = { version = "0.10", features = ["toml", "json", "env", "yaml"] }
|
||||||
sea-orm-migration = { version = "1.1", features = ["runtime-tokio"] }
|
sea-orm-migration = { version = "1.1", features = ["runtime-tokio"] }
|
||||||
rss = { version = "2", features = ["builders", "with-serde"] }
|
rss = { version = "2", features = ["builders", "with-serde"] }
|
||||||
fancy-regex = "0.14"
|
fancy-regex = "0.15"
|
||||||
lightningcss = "1.0.0-alpha.66"
|
lightningcss = "1.0.0-alpha.66"
|
||||||
html-escape = "0.2.13"
|
html-escape = "0.2.13"
|
||||||
opendal = { version = "0.53", features = ["default", "services-fs"] }
|
opendal = { version = "0.53", features = ["default", "services-fs"] }
|
||||||
@ -126,6 +126,7 @@ seaography = { version = "1.1", features = [
|
|||||||
"with-postgres-array",
|
"with-postgres-array",
|
||||||
"with-json-as-scalar",
|
"with-json-as-scalar",
|
||||||
"with-custom-as-json",
|
"with-custom-as-json",
|
||||||
|
"with-chrono-datetime-utc-as-timestamp",
|
||||||
] }
|
] }
|
||||||
tower = { version = "0.5.2", features = ["util"] }
|
tower = { version = "0.5.2", features = ["util"] }
|
||||||
tower-http = { version = "0.6", features = [
|
tower-http = { version = "0.6", features = [
|
||||||
@ -160,7 +161,7 @@ polars = { version = "0.49.1", features = [
|
|||||||
"lazy",
|
"lazy",
|
||||||
"diagonal_concat",
|
"diagonal_concat",
|
||||||
], optional = true }
|
], optional = true }
|
||||||
quick-xml = { version = "0.37.5", features = [
|
quick-xml = { version = "0.38", features = [
|
||||||
"serialize",
|
"serialize",
|
||||||
"serde-types",
|
"serde-types",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { getFutureMatches } from '@datasert/cronjs-matcher';
|
import { getFutureMatches } from "@datasert/cronjs-matcher";
|
||||||
import { Calendar, Clock, Info, Settings, Zap } from 'lucide-react';
|
import { Calendar, Clock, Info, Settings, Zap } from "lucide-react";
|
||||||
import {
|
import {
|
||||||
type CSSProperties,
|
type CSSProperties,
|
||||||
type FC,
|
type FC,
|
||||||
@ -8,109 +8,109 @@ import {
|
|||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from "react";
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardDescription,
|
CardDescription,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from '@/components/ui/card';
|
} from "@/components/ui/card";
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from "@/components/ui/label";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/components/ui/select';
|
} from "@/components/ui/select";
|
||||||
import { Separator } from '@/components/ui/separator';
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
|
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
|
||||||
import { cn } from '@/presentation/utils';
|
import { cn } from "@/presentation/utils";
|
||||||
import {
|
import {
|
||||||
type CronBuilderProps,
|
type CronBuilderProps,
|
||||||
CronField,
|
CronField,
|
||||||
type CronFieldConfig,
|
type CronFieldConfig,
|
||||||
CronPeriod,
|
CronPeriod,
|
||||||
type CronPreset,
|
type CronPreset,
|
||||||
} from './types';
|
} from "./types.js";
|
||||||
|
|
||||||
const CRON_PRESETS: CronPreset[] = [
|
const CRON_PRESETS: CronPreset[] = [
|
||||||
{
|
{
|
||||||
label: 'Every minute',
|
label: "Every minute",
|
||||||
value: '0 * * * * *',
|
value: "0 * * * * *",
|
||||||
description: 'Runs every minute',
|
description: "Runs every minute",
|
||||||
category: 'common',
|
category: "common",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Every 5 minutes',
|
label: "Every 5 minutes",
|
||||||
value: '0 */5 * * * *',
|
value: "0 */5 * * * *",
|
||||||
description: 'Runs every 5 minutes',
|
description: "Runs every 5 minutes",
|
||||||
category: 'common',
|
category: "common",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Every 15 minutes',
|
label: "Every 15 minutes",
|
||||||
value: '0 */15 * * * *',
|
value: "0 */15 * * * *",
|
||||||
description: 'Runs every 15 minutes',
|
description: "Runs every 15 minutes",
|
||||||
category: 'common',
|
category: "common",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Every 30 minutes',
|
label: "Every 30 minutes",
|
||||||
value: '0 */30 * * * *',
|
value: "0 */30 * * * *",
|
||||||
description: 'Runs every 30 minutes',
|
description: "Runs every 30 minutes",
|
||||||
category: 'common',
|
category: "common",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Every hour',
|
label: "Every hour",
|
||||||
value: '0 0 * * * *',
|
value: "0 0 * * * *",
|
||||||
description: 'Runs at the top of every hour',
|
description: "Runs at the top of every hour",
|
||||||
category: 'common',
|
category: "common",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Every 6 hours',
|
label: "Every 6 hours",
|
||||||
value: '0 0 */6 * * *',
|
value: "0 0 */6 * * *",
|
||||||
description: 'Runs every 6 hours',
|
description: "Runs every 6 hours",
|
||||||
category: 'common',
|
category: "common",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Daily at midnight',
|
label: "Daily at midnight",
|
||||||
value: '0 0 0 * * *',
|
value: "0 0 0 * * *",
|
||||||
description: 'Runs once daily at 00:00',
|
description: "Runs once daily at 00:00",
|
||||||
category: 'daily',
|
category: "daily",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Daily at 9 AM',
|
label: "Daily at 9 AM",
|
||||||
value: '0 0 9 * * *',
|
value: "0 0 9 * * *",
|
||||||
description: 'Runs daily at 9:00 AM',
|
description: "Runs daily at 9:00 AM",
|
||||||
category: 'daily',
|
category: "daily",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Weekdays at 9 AM',
|
label: "Weekdays at 9 AM",
|
||||||
value: '0 0 9 * * 1-5',
|
value: "0 0 9 * * 1-5",
|
||||||
description: 'Runs Monday to Friday at 9:00 AM',
|
description: "Runs Monday to Friday at 9:00 AM",
|
||||||
category: 'weekly',
|
category: "weekly",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Every Sunday',
|
label: "Every Sunday",
|
||||||
value: '0 0 0 * * 0',
|
value: "0 0 0 * * 0",
|
||||||
description: 'Runs every Sunday at midnight',
|
description: "Runs every Sunday at midnight",
|
||||||
category: 'weekly',
|
category: "weekly",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'First day of month',
|
label: "First day of month",
|
||||||
value: '0 0 0 1 * *',
|
value: "0 0 0 1 * *",
|
||||||
description: 'Runs on the 1st day of every month',
|
description: "Runs on the 1st day of every month",
|
||||||
category: 'monthly',
|
category: "monthly",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Every year',
|
label: "Every year",
|
||||||
value: '0 0 0 1 1 *',
|
value: "0 0 0 1 1 *",
|
||||||
description: 'Runs on January 1st every year',
|
description: "Runs on January 1st every year",
|
||||||
category: 'yearly',
|
category: "yearly",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -119,98 +119,98 @@ const FIELD_CONFIGS: Record<CronField, CronFieldConfig> = {
|
|||||||
min: 0,
|
min: 0,
|
||||||
max: 59,
|
max: 59,
|
||||||
step: 1,
|
step: 1,
|
||||||
allowSpecial: ['*', '?'],
|
allowSpecial: ["*", "?"],
|
||||||
},
|
},
|
||||||
minutes: {
|
minutes: {
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 59,
|
max: 59,
|
||||||
step: 1,
|
step: 1,
|
||||||
allowSpecial: ['*', '?'],
|
allowSpecial: ["*", "?"],
|
||||||
},
|
},
|
||||||
hours: {
|
hours: {
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 23,
|
max: 23,
|
||||||
step: 1,
|
step: 1,
|
||||||
allowSpecial: ['*', '?'],
|
allowSpecial: ["*", "?"],
|
||||||
},
|
},
|
||||||
dayOfMonth: {
|
dayOfMonth: {
|
||||||
min: 1,
|
min: 1,
|
||||||
max: 31,
|
max: 31,
|
||||||
step: 1,
|
step: 1,
|
||||||
allowSpecial: ['*', '?', 'L', 'W'],
|
allowSpecial: ["*", "?", "L", "W"],
|
||||||
options: [
|
options: [
|
||||||
{ label: 'Any day', value: '*' },
|
{ label: "Any day", value: "*" },
|
||||||
{ label: 'No specific day', value: '?' },
|
{ label: "No specific day", value: "?" },
|
||||||
{ label: 'Last day', value: 'L' },
|
{ label: "Last day", value: "L" },
|
||||||
{ label: 'Weekday', value: 'W' },
|
{ label: "Weekday", value: "W" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
month: {
|
month: {
|
||||||
min: 1,
|
min: 1,
|
||||||
max: 12,
|
max: 12,
|
||||||
step: 1,
|
step: 1,
|
||||||
allowSpecial: ['*'],
|
allowSpecial: ["*"],
|
||||||
options: [
|
options: [
|
||||||
{ label: 'January', value: 1 },
|
{ label: "January", value: 1 },
|
||||||
{ label: 'February', value: 2 },
|
{ label: "February", value: 2 },
|
||||||
{ label: 'March', value: 3 },
|
{ label: "March", value: 3 },
|
||||||
{ label: 'April', value: 4 },
|
{ label: "April", value: 4 },
|
||||||
{ label: 'May', value: 5 },
|
{ label: "May", value: 5 },
|
||||||
{ label: 'June', value: 6 },
|
{ label: "June", value: 6 },
|
||||||
{ label: 'July', value: 7 },
|
{ label: "July", value: 7 },
|
||||||
{ label: 'August', value: 8 },
|
{ label: "August", value: 8 },
|
||||||
{ label: 'September', value: 9 },
|
{ label: "September", value: 9 },
|
||||||
{ label: 'October', value: 10 },
|
{ label: "October", value: 10 },
|
||||||
{ label: 'November', value: 11 },
|
{ label: "November", value: 11 },
|
||||||
{ label: 'December', value: 12 },
|
{ label: "December", value: 12 },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
dayOfWeek: {
|
dayOfWeek: {
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 6,
|
max: 6,
|
||||||
step: 1,
|
step: 1,
|
||||||
allowSpecial: ['*', '?'],
|
allowSpecial: ["*", "?"],
|
||||||
options: [
|
options: [
|
||||||
{ label: 'Sunday', value: 0 },
|
{ label: "Sunday", value: 0 },
|
||||||
{ label: 'Monday', value: 1 },
|
{ label: "Monday", value: 1 },
|
||||||
{ label: 'Tuesday', value: 2 },
|
{ label: "Tuesday", value: 2 },
|
||||||
{ label: 'Wednesday', value: 3 },
|
{ label: "Wednesday", value: 3 },
|
||||||
{ label: 'Thursday', value: 4 },
|
{ label: "Thursday", value: 4 },
|
||||||
{ label: 'Friday', value: 5 },
|
{ label: "Friday", value: 5 },
|
||||||
{ label: 'Saturday', value: 6 },
|
{ label: "Saturday", value: 6 },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
year: {
|
year: {
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 9999,
|
max: 9999,
|
||||||
step: 1,
|
step: 1,
|
||||||
allowSpecial: ['*', '?'],
|
allowSpecial: ["*", "?"],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const PERIOD_CONFIGS = {
|
const PERIOD_CONFIGS = {
|
||||||
minute: {
|
minute: {
|
||||||
label: CronPeriod.Minute,
|
label: CronPeriod.Minute,
|
||||||
description: 'Run every minute',
|
description: "Run every minute",
|
||||||
template: '0 * * * * *',
|
template: "0 * * * * *",
|
||||||
fields: [CronField.Minutes],
|
fields: [CronField.Minutes],
|
||||||
},
|
},
|
||||||
hourly: {
|
hourly: {
|
||||||
label: CronPeriod.Hourly,
|
label: CronPeriod.Hourly,
|
||||||
description: 'Run every hour',
|
description: "Run every hour",
|
||||||
template: '0 0 * * * *',
|
template: "0 0 * * * *",
|
||||||
fields: [CronField.Minutes, CronField.Hours],
|
fields: [CronField.Minutes, CronField.Hours],
|
||||||
},
|
},
|
||||||
daily: {
|
daily: {
|
||||||
label: CronPeriod.Daily,
|
label: CronPeriod.Daily,
|
||||||
description: 'Run every day',
|
description: "Run every day",
|
||||||
template: '0 0 0 * * *',
|
template: "0 0 0 * * *",
|
||||||
fields: [CronField.Seconds, CronField.Minutes, CronField.Hours],
|
fields: [CronField.Seconds, CronField.Minutes, CronField.Hours],
|
||||||
},
|
},
|
||||||
weekly: {
|
weekly: {
|
||||||
label: CronPeriod.Weekly,
|
label: CronPeriod.Weekly,
|
||||||
description: 'Run every week',
|
description: "Run every week",
|
||||||
template: '0 0 0 * * 0',
|
template: "0 0 0 * * 0",
|
||||||
fields: [
|
fields: [
|
||||||
CronField.Seconds,
|
CronField.Seconds,
|
||||||
CronField.Minutes,
|
CronField.Minutes,
|
||||||
@ -220,8 +220,8 @@ const PERIOD_CONFIGS = {
|
|||||||
},
|
},
|
||||||
monthly: {
|
monthly: {
|
||||||
label: CronPeriod.Monthly,
|
label: CronPeriod.Monthly,
|
||||||
description: 'Run every month',
|
description: "Run every month",
|
||||||
template: '0 0 0 1 * *',
|
template: "0 0 0 1 * *",
|
||||||
fields: [
|
fields: [
|
||||||
CronField.Seconds,
|
CronField.Seconds,
|
||||||
CronField.Minutes,
|
CronField.Minutes,
|
||||||
@ -231,8 +231,8 @@ const PERIOD_CONFIGS = {
|
|||||||
},
|
},
|
||||||
yearly: {
|
yearly: {
|
||||||
label: CronPeriod.Yearly,
|
label: CronPeriod.Yearly,
|
||||||
description: 'Run every year',
|
description: "Run every year",
|
||||||
template: '0 0 0 1 1 *',
|
template: "0 0 0 1 1 *",
|
||||||
fields: [
|
fields: [
|
||||||
CronField.Seconds,
|
CronField.Seconds,
|
||||||
CronField.Minutes,
|
CronField.Minutes,
|
||||||
@ -243,8 +243,8 @@ const PERIOD_CONFIGS = {
|
|||||||
},
|
},
|
||||||
custom: {
|
custom: {
|
||||||
label: CronPeriod.Custom,
|
label: CronPeriod.Custom,
|
||||||
description: 'Custom expression',
|
description: "Custom expression",
|
||||||
template: '0 0 * * * *',
|
template: "0 0 * * * *",
|
||||||
fields: [
|
fields: [
|
||||||
CronField.Seconds,
|
CronField.Seconds,
|
||||||
CronField.Minutes,
|
CronField.Minutes,
|
||||||
@ -257,8 +257,8 @@ const PERIOD_CONFIGS = {
|
|||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const CronBuilder: FC<CronBuilderProps> = ({
|
const CronBuilder: FC<CronBuilderProps> = ({
|
||||||
timezone = 'UTC',
|
timezone = "UTC",
|
||||||
value = '0 0 * * * *',
|
value = "0 0 * * * *",
|
||||||
onChange,
|
onChange,
|
||||||
className,
|
className,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
@ -301,7 +301,7 @@ const CronBuilder: FC<CronBuilderProps> = ({
|
|||||||
});
|
});
|
||||||
return matches.map((match) => new Date(match));
|
return matches.map((match) => new Date(match));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to get future matched runs', error);
|
console.error("Failed to get future matched runs", error);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}, [currentExpression, showPreview, timezone]);
|
}, [currentExpression, showPreview, timezone]);
|
||||||
@ -327,7 +327,7 @@ const CronBuilder: FC<CronBuilderProps> = ({
|
|||||||
|
|
||||||
const handlePeriodChange = useCallback((period: CronPeriod) => {
|
const handlePeriodChange = useCallback((period: CronPeriod) => {
|
||||||
setActiveTab(period);
|
setActiveTab(period);
|
||||||
if (period !== 'custom') {
|
if (period !== "custom") {
|
||||||
const config = PERIOD_CONFIGS[period];
|
const config = PERIOD_CONFIGS[period];
|
||||||
setCronFields(parseCronExpression(config.template));
|
setCronFields(parseCronExpression(config.template));
|
||||||
}
|
}
|
||||||
@ -335,7 +335,7 @@ const CronBuilder: FC<CronBuilderProps> = ({
|
|||||||
|
|
||||||
const filteredPresets = useMemo(() => {
|
const filteredPresets = useMemo(() => {
|
||||||
return presets.filter((preset) => {
|
return presets.filter((preset) => {
|
||||||
if (activeTab === 'custom') {
|
if (activeTab === "custom") {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return preset.category === activeTab;
|
return preset.category === activeTab;
|
||||||
@ -343,7 +343,7 @@ const CronBuilder: FC<CronBuilderProps> = ({
|
|||||||
}, [presets, activeTab]);
|
}, [presets, activeTab]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn(withCard && 'space-y-6', className)}>
|
<div className={cn(withCard && "space-y-6", className)}>
|
||||||
<Tabs
|
<Tabs
|
||||||
value={activeTab}
|
value={activeTab}
|
||||||
onValueChange={(v) => handlePeriodChange(v as CronPeriod)}
|
onValueChange={(v) => handlePeriodChange(v as CronPeriod)}
|
||||||
@ -353,11 +353,11 @@ const CronBuilder: FC<CronBuilderProps> = ({
|
|||||||
className="grid w-(--all-grids-width) grid-cols-7 whitespace-nowrap lg:w-full"
|
className="grid w-(--all-grids-width) grid-cols-7 whitespace-nowrap lg:w-full"
|
||||||
style={
|
style={
|
||||||
{
|
{
|
||||||
'--my-grid-cols': `grid-template-columns: repeat(${displayPeriods.length}, minmax(0, 1fr))`,
|
"--my-grid-cols": `grid-template-columns: repeat(${displayPeriods.length}, minmax(0, 1fr))`,
|
||||||
'--all-grids-width':
|
"--all-grids-width":
|
||||||
displayPeriods.length > 4
|
displayPeriods.length > 4
|
||||||
? `${displayPeriods.length * 25 - 20}%`
|
? `${displayPeriods.length * 25 - 20}%`
|
||||||
: '100%',
|
: "100%",
|
||||||
} as CSSProperties
|
} as CSSProperties
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@ -377,10 +377,10 @@ const CronBuilder: FC<CronBuilderProps> = ({
|
|||||||
<TabsContent
|
<TabsContent
|
||||||
key={period}
|
key={period}
|
||||||
value={period}
|
value={period}
|
||||||
className={cn(withCard ? 'space-y-4' : 'px-0')}
|
className={cn(withCard ? "space-y-4" : "px-0")}
|
||||||
>
|
>
|
||||||
<Card className={cn(!withCard && 'border-none shadow-none')}>
|
<Card className={cn(!withCard && "border-none shadow-none")}>
|
||||||
<CardHeader className={cn('pb-1', !withCard && 'px-0')}>
|
<CardHeader className={cn("pb-1", !withCard && "px-0")}>
|
||||||
<CardTitle className="flex items-center gap-2 text-base">
|
<CardTitle className="flex items-center gap-2 text-base">
|
||||||
<Settings className="h-4 w-4" />
|
<Settings className="h-4 w-4" />
|
||||||
<span className="capitalize">
|
<span className="capitalize">
|
||||||
@ -391,7 +391,7 @@ const CronBuilder: FC<CronBuilderProps> = ({
|
|||||||
{PERIOD_CONFIGS[period].description}
|
{PERIOD_CONFIGS[period].description}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className={cn('space-y-4', !withCard && 'px-0')}>
|
<CardContent className={cn("space-y-4", !withCard && "px-0")}>
|
||||||
<CronFieldEditor
|
<CronFieldEditor
|
||||||
period={period}
|
period={period}
|
||||||
fields={cronFields}
|
fields={cronFields}
|
||||||
@ -402,8 +402,8 @@ const CronBuilder: FC<CronBuilderProps> = ({
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{showPresets && filteredPresets.length > 0 && (
|
{showPresets && filteredPresets.length > 0 && (
|
||||||
<Card className={cn(!withCard && 'border-none shadow-none')}>
|
<Card className={cn(!withCard && "border-none shadow-none")}>
|
||||||
<CardHeader className={cn(!withCard && 'px-0')}>
|
<CardHeader className={cn(!withCard && "px-0")}>
|
||||||
<CardTitle className="flex items-center gap-2 text-base">
|
<CardTitle className="flex items-center gap-2 text-base">
|
||||||
<Zap className="h-4 w-4" />
|
<Zap className="h-4 w-4" />
|
||||||
Quick Presets
|
Quick Presets
|
||||||
@ -412,7 +412,7 @@ const CronBuilder: FC<CronBuilderProps> = ({
|
|||||||
Common cron expressions for quick setup
|
Common cron expressions for quick setup
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className={cn(!withCard && 'px-0')}>
|
<CardContent className={cn(!withCard && "px-0")}>
|
||||||
<div className="grid gap-3 sm:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3">
|
<div className="grid gap-3 sm:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3">
|
||||||
{filteredPresets.map((preset, index) => (
|
{filteredPresets.map((preset, index) => (
|
||||||
<Button
|
<Button
|
||||||
@ -447,14 +447,14 @@ const CronBuilder: FC<CronBuilderProps> = ({
|
|||||||
</Tabs>
|
</Tabs>
|
||||||
{/* Current Expression & Preview */}
|
{/* Current Expression & Preview */}
|
||||||
{showGeneratedExpression && (
|
{showGeneratedExpression && (
|
||||||
<Card className={cn(!withCard && 'border-none shadow-none')}>
|
<Card className={cn(!withCard && "border-none shadow-none")}>
|
||||||
<CardHeader className={cn(!withCard && 'px-0')}>
|
<CardHeader className={cn(!withCard && "px-0")}>
|
||||||
<CardTitle className="flex items-center gap-2 text-base">
|
<CardTitle className="flex items-center gap-2 text-base">
|
||||||
<Clock className="h-4 w-4" />
|
<Clock className="h-4 w-4" />
|
||||||
Generated Expression
|
Generated Expression
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className={cn('space-y-4', !withCard && 'px-0')}>
|
<CardContent className={cn("space-y-4", !withCard && "px-0")}>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Badge variant="outline" className="px-3 py-1 font-mono text-sm">
|
<Badge variant="outline" className="px-3 py-1 font-mono text-sm">
|
||||||
{currentExpression}
|
{currentExpression}
|
||||||
@ -532,8 +532,8 @@ const CronFieldEditor: FC<CronFieldEditorProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const CronFieldItemAnyOrSpecificOption = {
|
const CronFieldItemAnyOrSpecificOption = {
|
||||||
Any: 'any',
|
Any: "any",
|
||||||
Specific: 'specific',
|
Specific: "specific",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
type CronFieldItemAnyOrSpecificOption =
|
type CronFieldItemAnyOrSpecificOption =
|
||||||
@ -548,11 +548,11 @@ interface CronFieldItemEditorProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function encodeCronFieldItem(value: string): string {
|
function encodeCronFieldItem(value: string): string {
|
||||||
if (value === '') {
|
if (value === "") {
|
||||||
return '<meta:empty>';
|
return "<meta:empty>";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value.includes(' ')) {
|
if (value.includes(" ")) {
|
||||||
return `<meta:contains-space:${encodeURIComponent(value)}>`;
|
return `<meta:contains-space:${encodeURIComponent(value)}>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -560,15 +560,15 @@ function encodeCronFieldItem(value: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function decodeCronFieldItem(value: string): string {
|
function decodeCronFieldItem(value: string): string {
|
||||||
if (value.startsWith('<meta:contains')) {
|
if (value.startsWith("<meta:contains")) {
|
||||||
return decodeURIComponent(
|
return decodeURIComponent(
|
||||||
// biome-ignore lint/performance/useTopLevelRegex: false
|
// biome-ignore lint/performance/useTopLevelRegex: false
|
||||||
value.replace(/^<meta:contains-space:([^>]+)>$/, '$1')
|
value.replace(/^<meta:contains-space:([^>]+)>$/, "$1")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value === '<meta:empty>') {
|
if (value === "<meta:empty>") {
|
||||||
return '';
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
@ -582,7 +582,7 @@ export const CronFieldItemEditor: FC<CronFieldItemEditorProps> = memo(
|
|||||||
|
|
||||||
const [anyOrSpecificOption, _setAnyOrSpecificOption] =
|
const [anyOrSpecificOption, _setAnyOrSpecificOption] =
|
||||||
useState<CronFieldItemAnyOrSpecificOption>(() =>
|
useState<CronFieldItemAnyOrSpecificOption>(() =>
|
||||||
innerValue === '*'
|
innerValue === "*"
|
||||||
? CronFieldItemAnyOrSpecificOption.Any
|
? CronFieldItemAnyOrSpecificOption.Any
|
||||||
: CronFieldItemAnyOrSpecificOption.Specific
|
: CronFieldItemAnyOrSpecificOption.Specific
|
||||||
);
|
);
|
||||||
@ -607,9 +607,9 @@ export const CronFieldItemEditor: FC<CronFieldItemEditorProps> = memo(
|
|||||||
(v: CronFieldItemAnyOrSpecificOption) => {
|
(v: CronFieldItemAnyOrSpecificOption) => {
|
||||||
_setAnyOrSpecificOption(v);
|
_setAnyOrSpecificOption(v);
|
||||||
if (v === CronFieldItemAnyOrSpecificOption.Any) {
|
if (v === CronFieldItemAnyOrSpecificOption.Any) {
|
||||||
handleChange('*');
|
handleChange("*");
|
||||||
} else if (v === CronFieldItemAnyOrSpecificOption.Specific) {
|
} else if (v === CronFieldItemAnyOrSpecificOption.Specific) {
|
||||||
handleChange('0');
|
handleChange("0");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[handleChange]
|
[handleChange]
|
||||||
@ -618,10 +618,10 @@ export const CronFieldItemEditor: FC<CronFieldItemEditorProps> = memo(
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="font-medium text-sm capitalize">
|
<Label className="font-medium text-sm capitalize">
|
||||||
{field.replace(/([A-Z])/g, ' $1').toLowerCase()}
|
{field.replace(/([A-Z])/g, " $1").toLowerCase()}
|
||||||
</Label>
|
</Label>
|
||||||
|
|
||||||
{(field === 'month' || field === 'dayOfWeek') && (
|
{(field === "month" || field === "dayOfWeek") && (
|
||||||
<Select
|
<Select
|
||||||
value={innerValue}
|
value={innerValue}
|
||||||
onValueChange={handleChange}
|
onValueChange={handleChange}
|
||||||
@ -640,7 +640,7 @@ export const CronFieldItemEditor: FC<CronFieldItemEditorProps> = memo(
|
|||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
)}
|
)}
|
||||||
{field === 'dayOfMonth' && (
|
{field === "dayOfMonth" && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Select
|
<Select
|
||||||
value={innerValue}
|
value={innerValue}
|
||||||
@ -666,9 +666,9 @@ export const CronFieldItemEditor: FC<CronFieldItemEditorProps> = memo(
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!(
|
{!(
|
||||||
field === 'month' ||
|
field === "month" ||
|
||||||
field === 'dayOfWeek' ||
|
field === "dayOfWeek" ||
|
||||||
field === 'dayOfMonth'
|
field === "dayOfMonth"
|
||||||
) && (
|
) && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<ToggleGroup
|
<ToggleGroup
|
||||||
@ -722,21 +722,21 @@ export const CronFieldItemEditor: FC<CronFieldItemEditorProps> = memo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
function parseCronExpression(expression: string): Record<CronField, string> {
|
function parseCronExpression(expression: string): Record<CronField, string> {
|
||||||
const parts = expression.split(' ');
|
const parts = expression.split(" ");
|
||||||
|
|
||||||
// Ensure we have 6 parts, pad with defaults if needed
|
// Ensure we have 6 parts, pad with defaults if needed
|
||||||
while (parts.length < 6) {
|
while (parts.length < 6) {
|
||||||
parts.push('*');
|
parts.push("*");
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
seconds: parts[0] || '0',
|
seconds: parts[0] || "0",
|
||||||
minutes: parts[1] || '*',
|
minutes: parts[1] || "*",
|
||||||
hours: parts[2] || '*',
|
hours: parts[2] || "*",
|
||||||
dayOfMonth: parts[3] || '*',
|
dayOfMonth: parts[3] || "*",
|
||||||
month: parts[4] || '*',
|
month: parts[4] || "*",
|
||||||
dayOfWeek: parts[5] || '*',
|
dayOfWeek: parts[5] || "*",
|
||||||
year: parts[6] || '*',
|
year: parts[6] || "*",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -1,67 +1,67 @@
|
|||||||
import { Badge } from '@/components/ui/badge';
|
import { Code2, Play, Settings, Type } from "lucide-react";
|
||||||
import { Button } from '@/components/ui/button';
|
import { type FC, useCallback, useState } from "react";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardDescription,
|
CardDescription,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from '@/components/ui/card';
|
} from "@/components/ui/card";
|
||||||
import { Separator } from '@/components/ui/separator';
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { Code2, Play, Settings, Type } from 'lucide-react';
|
import { Cron } from "./cron.jsx";
|
||||||
import { type FC, useCallback, useState } from 'react';
|
import { CronBuilder } from "./cron-builder.jsx";
|
||||||
import { CronBuilder } from './cron-builder.js';
|
import { CronDisplay } from "./cron-display.jsx";
|
||||||
import { CronDisplay } from './cron-display.js';
|
import { CronInput } from "./cron-input.jsx";
|
||||||
import { CronInput } from './cron-input.js';
|
|
||||||
import { Cron } from './cron.js';
|
|
||||||
|
|
||||||
const CronExample: FC = () => {
|
const CronExample: FC = () => {
|
||||||
const [inputValue, setInputValue] = useState('0 30 9 * * 1-5');
|
const [inputValue, setInputValue] = useState("0 30 9 * * 1-5");
|
||||||
const [builderValue, setBuilderValue] = useState('0 0 */6 * * *');
|
const [builderValue, setBuilderValue] = useState("0 0 */6 * * *");
|
||||||
const [fullValue, setFullValue] = useState('0 */15 * * * *');
|
const [fullValue, setFullValue] = useState("0 */15 * * * *");
|
||||||
const [displayValue] = useState('0 0 0 * * 0');
|
const [displayValue] = useState("0 0 0 * * 0");
|
||||||
|
|
||||||
const examples = [
|
const examples = [
|
||||||
{
|
{
|
||||||
label: 'Every minute',
|
label: "Every minute",
|
||||||
expression: '0 * * * * *',
|
expression: "0 * * * * *",
|
||||||
description: 'Runs at the start of every minute',
|
description: "Runs at the start of every minute",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Every 5 minutes',
|
label: "Every 5 minutes",
|
||||||
expression: '0 */5 * * * *',
|
expression: "0 */5 * * * *",
|
||||||
description: 'Runs every 5 minutes',
|
description: "Runs every 5 minutes",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Every hour',
|
label: "Every hour",
|
||||||
expression: '0 0 * * * *',
|
expression: "0 0 * * * *",
|
||||||
description: 'Runs at the start of every hour',
|
description: "Runs at the start of every hour",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Daily at 9 AM',
|
label: "Daily at 9 AM",
|
||||||
expression: '0 0 9 * * *',
|
expression: "0 0 9 * * *",
|
||||||
description: 'Runs every day at 9:00 AM',
|
description: "Runs every day at 9:00 AM",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Weekdays at 9:30 AM',
|
label: "Weekdays at 9:30 AM",
|
||||||
expression: '0 30 9 * * 1-5',
|
expression: "0 30 9 * * 1-5",
|
||||||
description: 'Runs Monday through Friday at 9:30 AM',
|
description: "Runs Monday through Friday at 9:30 AM",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Every Sunday',
|
label: "Every Sunday",
|
||||||
expression: '0 0 0 * * 0',
|
expression: "0 0 0 * * 0",
|
||||||
description: 'Runs every Sunday at midnight',
|
description: "Runs every Sunday at midnight",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'First day of month',
|
label: "First day of month",
|
||||||
expression: '0 0 0 1 * *',
|
expression: "0 0 0 1 * *",
|
||||||
description: 'Runs on the 1st day of every month',
|
description: "Runs on the 1st day of every month",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Every quarter',
|
label: "Every quarter",
|
||||||
expression: '0 0 0 1 */3 *',
|
expression: "0 0 0 1 */3 *",
|
||||||
description: 'Runs on the 1st day of every quarter',
|
description: "Runs on the 1st day of every quarter",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ const CronExample: FC = () => {
|
|||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(expression);
|
await navigator.clipboard.writeText(expression);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Failed to copy to clipboard:', error);
|
console.warn("Failed to copy to clipboard:", error);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -165,7 +165,7 @@ const CronExample: FC = () => {
|
|||||||
<div className="rounded bg-muted p-4">
|
<div className="rounded bg-muted p-4">
|
||||||
<h4 className="mb-2 font-medium text-sm">Current Value:</h4>
|
<h4 className="mb-2 font-medium text-sm">Current Value:</h4>
|
||||||
<Badge variant="outline" className="font-mono">
|
<Badge variant="outline" className="font-mono">
|
||||||
{fullValue || 'No expression set'}
|
{fullValue || "No expression set"}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -196,7 +196,7 @@ const CronExample: FC = () => {
|
|||||||
<div className="rounded bg-muted p-4">
|
<div className="rounded bg-muted p-4">
|
||||||
<h4 className="mb-2 font-medium text-sm">Current Value:</h4>
|
<h4 className="mb-2 font-medium text-sm">Current Value:</h4>
|
||||||
<Badge variant="outline" className="font-mono">
|
<Badge variant="outline" className="font-mono">
|
||||||
{inputValue || 'No expression set'}
|
{inputValue || "No expression set"}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -244,7 +244,7 @@ const CronExample: FC = () => {
|
|||||||
<div className="rounded bg-muted p-4">
|
<div className="rounded bg-muted p-4">
|
||||||
<h4 className="mb-2 font-medium text-sm">Current Value:</h4>
|
<h4 className="mb-2 font-medium text-sm">Current Value:</h4>
|
||||||
<Badge variant="outline" className="font-mono">
|
<Badge variant="outline" className="font-mono">
|
||||||
{builderValue || 'No expression set'}
|
{builderValue || "No expression set"}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@ -1,4 +1,4 @@
|
|||||||
import { parse } from '@datasert/cronjs-parser';
|
import { parse } from "@datasert/cronjs-parser";
|
||||||
import {
|
import {
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
Bolt,
|
Bolt,
|
||||||
@ -7,45 +7,45 @@ import {
|
|||||||
Copy,
|
Copy,
|
||||||
Settings,
|
Settings,
|
||||||
Type,
|
Type,
|
||||||
} from 'lucide-react';
|
} from "lucide-react";
|
||||||
import { type FC, useCallback, useEffect, useMemo, useState } from 'react';
|
import { type FC, useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardDescription,
|
CardDescription,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from '@/components/ui/card';
|
} from "@/components/ui/card";
|
||||||
import { Separator } from '@/components/ui/separator';
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { cn } from '@/presentation/utils';
|
import { cn } from "@/presentation/utils";
|
||||||
import { CronBuilder } from './cron-builder';
|
import { CronBuilder } from "./cron-builder.js";
|
||||||
import { CronDisplay } from './cron-display';
|
import { CronDisplay } from "./cron-display.js";
|
||||||
import { CronInput } from './cron-input';
|
import { CronInput } from "./cron-input.js";
|
||||||
import {
|
import {
|
||||||
CronMode,
|
CronMode,
|
||||||
type CronPrimitiveMode,
|
type CronPrimitiveMode,
|
||||||
type CronProps,
|
type CronProps,
|
||||||
type CronValidationResult,
|
type CronValidationResult,
|
||||||
} from './types';
|
} from "./types.js";
|
||||||
|
|
||||||
const PLACEHOLDER = '0 0 * * * *';
|
const PLACEHOLDER = "0 0 * * * *";
|
||||||
|
|
||||||
const Cron: FC<CronProps> = ({
|
const Cron: FC<CronProps> = ({
|
||||||
value = '',
|
value = "",
|
||||||
onChange,
|
onChange,
|
||||||
activeMode = 'input',
|
activeMode = "input",
|
||||||
onActiveModeChange,
|
onActiveModeChange,
|
||||||
onValidate,
|
onValidate,
|
||||||
className,
|
className,
|
||||||
mode = 'both',
|
mode = "both",
|
||||||
disabled = false,
|
disabled = false,
|
||||||
placeholder = PLACEHOLDER,
|
placeholder = PLACEHOLDER,
|
||||||
showPreview = true,
|
showPreview = true,
|
||||||
showDescription = true,
|
showDescription = true,
|
||||||
timezone = 'UTC',
|
timezone = "UTC",
|
||||||
error,
|
error,
|
||||||
children,
|
children,
|
||||||
showHelp = true,
|
showHelp = true,
|
||||||
@ -57,7 +57,7 @@ const Cron: FC<CronProps> = ({
|
|||||||
isFirstSibling = false,
|
isFirstSibling = false,
|
||||||
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: false
|
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: false
|
||||||
}) => {
|
}) => {
|
||||||
const [internalValue, setInternalValue] = useState(value || '');
|
const [internalValue, setInternalValue] = useState(value || "");
|
||||||
const [internalActiveMode, setInternalActiveMode] =
|
const [internalActiveMode, setInternalActiveMode] =
|
||||||
useState<CronPrimitiveMode>(
|
useState<CronPrimitiveMode>(
|
||||||
mode === CronMode.Both ? activeMode : (mode as CronPrimitiveMode)
|
mode === CronMode.Both ? activeMode : (mode as CronPrimitiveMode)
|
||||||
@ -66,7 +66,7 @@ const Cron: FC<CronProps> = ({
|
|||||||
|
|
||||||
const validationResult = useMemo((): CronValidationResult => {
|
const validationResult = useMemo((): CronValidationResult => {
|
||||||
if (!internalValue.trim()) {
|
if (!internalValue.trim()) {
|
||||||
return { isValid: false, error: 'Expression is required', isEmpty: true };
|
return { isValid: false, error: "Expression is required", isEmpty: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -78,13 +78,13 @@ const Cron: FC<CronProps> = ({
|
|||||||
error:
|
error:
|
||||||
parseError instanceof Error
|
parseError instanceof Error
|
||||||
? parseError.message
|
? parseError.message
|
||||||
: 'Invalid cron expression',
|
: "Invalid cron expression",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, [internalValue]);
|
}, [internalValue]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setInternalValue(value || '');
|
setInternalValue(value || "");
|
||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -92,7 +92,7 @@ const Cron: FC<CronProps> = ({
|
|||||||
}, [validationResult.isValid, onValidate]);
|
}, [validationResult.isValid, onValidate]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (mode === 'both') {
|
if (mode === "both") {
|
||||||
setInternalActiveMode(activeMode);
|
setInternalActiveMode(activeMode);
|
||||||
}
|
}
|
||||||
}, [activeMode, mode]);
|
}, [activeMode, mode]);
|
||||||
@ -123,16 +123,16 @@ const Cron: FC<CronProps> = ({
|
|||||||
setCopied(true);
|
setCopied(true);
|
||||||
setTimeout(() => setCopied(false), 2000);
|
setTimeout(() => setCopied(false), 2000);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('Failed to copy to clipboard:', e);
|
console.warn("Failed to copy to clipboard:", e);
|
||||||
}
|
}
|
||||||
}, [internalValue]);
|
}, [internalValue]);
|
||||||
|
|
||||||
const hasError =
|
const hasError =
|
||||||
!!error || !!(!validationResult.isValid && internalValue.trim());
|
!!error || !!(!validationResult.isValid && internalValue.trim());
|
||||||
|
|
||||||
if (mode === 'input') {
|
if (mode === "input") {
|
||||||
return (
|
return (
|
||||||
<div className={cn(withCard && 'space-y-4', className)}>
|
<div className={cn(withCard && "space-y-4", className)}>
|
||||||
<CronInput
|
<CronInput
|
||||||
value={internalValue}
|
value={internalValue}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
@ -161,9 +161,9 @@ const Cron: FC<CronProps> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode === 'builder') {
|
if (mode === "builder") {
|
||||||
return (
|
return (
|
||||||
<div className={cn(withCard && 'space-y-4', className)}>
|
<div className={cn(withCard && "space-y-4", className)}>
|
||||||
<CronBuilder
|
<CronBuilder
|
||||||
value={internalValue}
|
value={internalValue}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
@ -184,14 +184,14 @@ const Cron: FC<CronProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn(withCard && 'space-y-6', className)}>
|
<div className={cn(withCard && "space-y-6", className)}>
|
||||||
<Card
|
<Card
|
||||||
className={cn(
|
className={cn(
|
||||||
!withCard && 'border-none shadow-none',
|
!withCard && "border-none shadow-none",
|
||||||
!withCard && isFirstSibling && 'pt-0'
|
!withCard && isFirstSibling && "pt-0"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<CardHeader className={cn(!withCard && 'px-0')}>
|
<CardHeader className={cn(!withCard && "px-0")}>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<CardTitle className="flex items-center gap-2 text-base">
|
<CardTitle className="flex items-center gap-2 text-base">
|
||||||
@ -207,7 +207,7 @@ const Cron: FC<CronProps> = ({
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Badge
|
<Badge
|
||||||
variant={
|
variant={
|
||||||
validationResult.isValid ? 'secondary' : 'destructive'
|
validationResult.isValid ? "secondary" : "destructive"
|
||||||
}
|
}
|
||||||
className="font-mono text-sm"
|
className="font-mono text-sm"
|
||||||
>
|
>
|
||||||
@ -238,11 +238,11 @@ const Cron: FC<CronProps> = ({
|
|||||||
)}
|
)}
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<CardContent className={cn(!withCard && 'px-0')}>
|
<CardContent className={cn(!withCard && "px-0")}>
|
||||||
<Tabs
|
<Tabs
|
||||||
value={internalActiveMode}
|
value={internalActiveMode}
|
||||||
onValueChange={(v) =>
|
onValueChange={(v) =>
|
||||||
handleActiveModeChange(v as 'input' | 'builder')
|
handleActiveModeChange(v as "input" | "builder")
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<TabsList className="grid w-full grid-cols-2">
|
<TabsList className="grid w-full grid-cols-2">
|
||||||
@ -314,14 +314,14 @@ const Cron: FC<CronProps> = ({
|
|||||||
{showHelp && (
|
{showHelp && (
|
||||||
<>
|
<>
|
||||||
{!withCard && <Separator />}
|
{!withCard && <Separator />}
|
||||||
<Card className={cn(!withCard && 'border-none shadow-none')}>
|
<Card className={cn(!withCard && "border-none shadow-none")}>
|
||||||
<CardHeader className={cn(!withCard && 'px-0')}>
|
<CardHeader className={cn(!withCard && "px-0")}>
|
||||||
<CardTitle className="flex items-center gap-2 text-base">
|
<CardTitle className="flex items-center gap-2 text-base">
|
||||||
<Code2 className="h-4 w-4" />
|
<Code2 className="h-4 w-4" />
|
||||||
Cron Expression Format
|
Cron Expression Format
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className={cn(!withCard && 'px-0')}>
|
<CardContent className={cn(!withCard && "px-0")}>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="grid grid-cols-6 gap-2 text-center text-sm">
|
<div className="grid grid-cols-6 gap-2 text-center text-sm">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
@ -1,8 +1,8 @@
|
|||||||
export { Cron } from './cron';
|
export { Cron } from "./cron.js";
|
||||||
export { CronBuilder } from './cron-builder';
|
export { CronBuilder } from "./cron-builder.js";
|
||||||
export { CronDisplay } from './cron-display';
|
export { CronDisplay } from "./cron-display.js";
|
||||||
export { CronExample } from './cron-example';
|
export { CronExample } from "./cron-example.js";
|
||||||
export { CronInput } from './cron-input';
|
export { CronInput } from "./cron-input.js";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
type CronBuilderProps,
|
type CronBuilderProps,
|
||||||
@ -17,4 +17,4 @@ export {
|
|||||||
type CronProps,
|
type CronProps,
|
||||||
type CronValidationResult,
|
type CronValidationResult,
|
||||||
type PeriodConfig,
|
type PeriodConfig,
|
||||||
} from './types';
|
} from "./types.js";
|
File diff suppressed because it is too large
Load Diff
@ -4,8 +4,12 @@ import { DOCUMENT } from '../platform/injection';
|
|||||||
export class IntlService {
|
export class IntlService {
|
||||||
document = inject(DOCUMENT);
|
document = inject(DOCUMENT);
|
||||||
|
|
||||||
|
get Intl(): typeof Intl {
|
||||||
|
return this.document.defaultView?.Intl as typeof Intl;
|
||||||
|
}
|
||||||
|
|
||||||
get timezone() {
|
get timezone() {
|
||||||
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
return this.Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||||
}
|
}
|
||||||
|
|
||||||
formatTimestamp(timestamp: number, options?: Intl.DateTimeFormatOptions) {
|
formatTimestamp(timestamp: number, options?: Intl.DateTimeFormatOptions) {
|
||||||
@ -20,7 +24,7 @@ export class IntlService {
|
|||||||
...options,
|
...options,
|
||||||
};
|
};
|
||||||
|
|
||||||
return new Intl.DateTimeFormat(
|
return new this.Intl.DateTimeFormat(
|
||||||
this.document.defaultView?.navigator.language,
|
this.document.defaultView?.navigator.language,
|
||||||
{
|
{
|
||||||
...defaultOptions,
|
...defaultOptions,
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
import { Cron } from '@/components/domains/cron';
|
import { useMutation } from '@apollo/client';
|
||||||
import { CronMode } from '@/components/domains/cron/types';
|
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 { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@ -9,6 +12,8 @@ import {
|
|||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from '@/components/ui/card';
|
} from '@/components/ui/card';
|
||||||
|
import { Cron } from '@/components/ui/cron';
|
||||||
|
import { CronMode } from '@/components/ui/cron/types';
|
||||||
import {
|
import {
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogDescription,
|
DialogDescription,
|
||||||
@ -26,11 +31,6 @@ import {
|
|||||||
SubscriberTaskTypeEnum,
|
SubscriberTaskTypeEnum,
|
||||||
} from '@/infra/graphql/gql/graphql';
|
} from '@/infra/graphql/gql/graphql';
|
||||||
import { IntlService } from '@/infra/intl/intl.service';
|
import { IntlService } from '@/infra/intl/intl.service';
|
||||||
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';
|
|
||||||
|
|
||||||
const SUBSCRIPTION_TASK_CRON_PRESETS = [
|
const SUBSCRIPTION_TASK_CRON_PRESETS = [
|
||||||
{
|
{
|
||||||
@ -162,7 +162,6 @@ export const SubscriptionCronCreationView = memo(
|
|||||||
const loading = loadingInsert;
|
const loading = loadingInsert;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<Tabs
|
<Tabs
|
||||||
defaultValue={CRON_TABS[0].tab}
|
defaultValue={CRON_TABS[0].tab}
|
||||||
className="flex min-h-0 flex-1 flex-col"
|
className="flex min-h-0 flex-1 flex-col"
|
||||||
@ -170,11 +169,7 @@ export const SubscriptionCronCreationView = memo(
|
|||||||
<div className="flex w-full shrink-0 overflow-x-auto">
|
<div className="flex w-full shrink-0 overflow-x-auto">
|
||||||
<TabsList className="flex items-center justify-center whitespace-nowrap">
|
<TabsList className="flex items-center justify-center whitespace-nowrap">
|
||||||
{CRON_TABS.map((tab) => (
|
{CRON_TABS.map((tab) => (
|
||||||
<TabsTrigger
|
<TabsTrigger key={tab.tab} value={tab.tab} className="w-fit px-4">
|
||||||
key={tab.tab}
|
|
||||||
value={tab.tab}
|
|
||||||
className="w-fit px-4"
|
|
||||||
>
|
|
||||||
{tab.label}
|
{tab.label}
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
))}
|
))}
|
||||||
@ -214,7 +209,6 @@ export const SubscriptionCronCreationView = memo(
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -3,8 +3,6 @@ import { createFileRoute } from '@tanstack/react-router';
|
|||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import { RefreshCw } from 'lucide-react';
|
import { RefreshCw } from 'lucide-react';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
import { CronDisplay } from '@/components/domains/cron';
|
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
@ -15,17 +13,20 @@ import {
|
|||||||
CardTitle,
|
CardTitle,
|
||||||
} from '@/components/ui/card';
|
} from '@/components/ui/card';
|
||||||
import { ContainerHeader } from '@/components/ui/container-header';
|
import { ContainerHeader } from '@/components/ui/container-header';
|
||||||
|
import { CronDisplay } from '@/components/ui/cron';
|
||||||
import { DetailCardSkeleton } from '@/components/ui/detail-card-skeleton';
|
import { DetailCardSkeleton } from '@/components/ui/detail-card-skeleton';
|
||||||
import { DetailEmptyView } from '@/components/ui/detail-empty-view';
|
import { DetailEmptyView } from '@/components/ui/detail-empty-view';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { QueryErrorView } from '@/components/ui/query-error-view';
|
import { QueryErrorView } from '@/components/ui/query-error-view';
|
||||||
import { Separator } from '@/components/ui/separator';
|
import { Separator } from '@/components/ui/separator';
|
||||||
import { GET_CRONS } from '@/domains/recorder/schema/cron';
|
import { GET_CRONS } from '@/domains/recorder/schema/cron';
|
||||||
|
import { useInject } from '@/infra/di/inject';
|
||||||
import {
|
import {
|
||||||
CronStatusEnum,
|
CronStatusEnum,
|
||||||
type GetCronsQuery,
|
type GetCronsQuery,
|
||||||
type GetCronsQueryVariables,
|
type GetCronsQueryVariables,
|
||||||
} from '@/infra/graphql/gql/graphql';
|
} from '@/infra/graphql/gql/graphql';
|
||||||
|
import { IntlService } from '@/infra/intl/intl.service';
|
||||||
import type { RouteStateDataOption } from '@/infra/routes/traits';
|
import type { RouteStateDataOption } from '@/infra/routes/traits';
|
||||||
import { getStatusBadge } from './-status-badge';
|
import { getStatusBadge } from './-status-badge';
|
||||||
|
|
||||||
@ -38,6 +39,7 @@ export const Route = createFileRoute('/_app/tasks/cron/detail/$id')({
|
|||||||
|
|
||||||
function CronDetailRouteComponent() {
|
function CronDetailRouteComponent() {
|
||||||
const { id } = Route.useParams();
|
const { id } = Route.useParams();
|
||||||
|
const intlService = useInject(IntlService);
|
||||||
|
|
||||||
const { data, loading, error, refetch } = useQuery<
|
const { data, loading, error, refetch } = useQuery<
|
||||||
GetCronsQuery,
|
GetCronsQuery,
|
||||||
@ -115,7 +117,7 @@ function CronDetailRouteComponent() {
|
|||||||
{/* Basic Information */}
|
{/* Basic Information */}
|
||||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="font-medium text-sm">Cron ID</Label>
|
<Label className="font-medium text-sm">ID</Label>
|
||||||
<div className="rounded-md bg-muted p-3">
|
<div className="rounded-md bg-muted p-3">
|
||||||
<code className="text-sm">{cron.id}</code>
|
<code className="text-sm">{cron.id}</code>
|
||||||
</div>
|
</div>
|
||||||
@ -129,7 +131,7 @@ function CronDetailRouteComponent() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="font-medium text-sm">Retry count</Label>
|
<Label className="font-medium text-sm">Attemps</Label>
|
||||||
<div className="rounded-md bg-muted p-3">
|
<div className="rounded-md bg-muted p-3">
|
||||||
<span className="text-sm">
|
<span className="text-sm">
|
||||||
{cron.attempts} / {cron.maxAttempts}
|
{cron.attempts} / {cron.maxAttempts}
|
||||||
@ -199,7 +201,10 @@ function CronDetailRouteComponent() {
|
|||||||
<Label className="font-medium text-sm">Created at</Label>
|
<Label className="font-medium text-sm">Created at</Label>
|
||||||
<div className="rounded-md bg-muted p-3">
|
<div className="rounded-md bg-muted p-3">
|
||||||
<span className="text-sm">
|
<span className="text-sm">
|
||||||
{format(new Date(cron.createdAt), 'yyyy-MM-dd HH:mm:ss')}
|
{intlService.formatTimestamp(
|
||||||
|
cron.createdAt,
|
||||||
|
'yyyy-MM-dd HH:mm:ss'
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -41,9 +41,13 @@
|
|||||||
"noBannedTypes": "off"
|
"noBannedTypes": "off"
|
||||||
},
|
},
|
||||||
"correctness": {
|
"correctness": {
|
||||||
|
"noUnusedVariables": {
|
||||||
|
"fix": "none",
|
||||||
|
"level": "error"
|
||||||
|
},
|
||||||
"noUnusedImports": {
|
"noUnusedImports": {
|
||||||
"fix": "none",
|
"fix": "none",
|
||||||
"level": "warn"
|
"level": "error"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user