Add 'enabled' field to schedule management

- Introduced a new boolean field `enabled` in the `schedule` schema to indicate the active status of schedules.
- Updated the `HandleSchedules` component to include a toggle switch for enabling/disabling schedules.
- Enhanced the `ShowSchedules` component to display the status of each schedule with a badge indicating whether it is enabled or disabled.
- Added a new API mutation to run schedules manually, ensuring proper error handling for non-existent schedules.
- Updated database schema to reflect the new `enabled` field with a default value of true.
This commit is contained in:
Mauricio Siu
2025-05-02 03:45:07 -06:00
parent e84ce38994
commit 442f051457
7 changed files with 5567 additions and 7 deletions

View File

@@ -27,6 +27,7 @@ import {
TooltipProvider, TooltipProvider,
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import { Switch } from "@/components/ui/switch";
const commonCronExpressions = [ const commonCronExpressions = [
{ label: "Every minute", value: "* * * * *" }, { label: "Every minute", value: "* * * * *" },
@@ -42,6 +43,7 @@ const formSchema = z.object({
name: z.string().min(1, "Name is required"), name: z.string().min(1, "Name is required"),
cronExpression: z.string().min(1, "Cron expression is required"), cronExpression: z.string().min(1, "Cron expression is required"),
command: z.string().min(1, "Command is required"), command: z.string().min(1, "Command is required"),
enabled: z.boolean().default(true),
}); });
interface Props { interface Props {
@@ -68,6 +70,7 @@ export const HandleSchedules = ({
name: "", name: "",
cronExpression: "", cronExpression: "",
command: "", command: "",
enabled: true,
}, },
}); });
@@ -205,6 +208,21 @@ export const HandleSchedules = ({
)} )}
/> />
<FormField
control={form.control}
name="enabled"
render={({ field }) => (
<FormItem>
<FormLabel className="flex items-center gap-2">
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
Enabled
</FormLabel>
</FormItem>
)}
/>
<Button type="submit" disabled={isLoading} className="w-full"> <Button type="submit" disabled={isLoading} className="w-full">
{isLoading ? ( {isLoading ? (
<> <>

View File

@@ -18,7 +18,13 @@ import { api } from "@/utils/api";
import { useState } from "react"; import { useState } from "react";
import { HandleSchedules } from "./handle-schedules"; import { HandleSchedules } from "./handle-schedules";
import { PlusCircle, Clock, Terminal, Trash2, Edit2 } from "lucide-react"; import { PlusCircle, Clock, Terminal, Trash2, Edit2 } from "lucide-react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
interface Props { interface Props {
@@ -54,11 +60,16 @@ export const ShowSchedules = ({ applicationId }: Props) => {
return ( return (
<Card className="border px-4 shadow-none bg-transparent"> <Card className="border px-4 shadow-none bg-transparent">
<CardHeader className="px-0"> <CardHeader className="px-0">
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<CardTitle className="text-xl font-bold flex items-center gap-2"> <div className="flex flex-col gap-2">
<Clock className="size-4 text-muted-foreground" /> <CardTitle className="text-xl font-bold flex items-center gap-2">
Scheduled Tasks Scheduled Tasks
</CardTitle> </CardTitle>
<CardDescription>
Schedule tasks to run automatically at specified intervals.
</CardDescription>
</div>
<Dialog open={isOpen} onOpenChange={setIsOpen}> <Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button className="gap-2"> <Button className="gap-2">
@@ -91,6 +102,7 @@ export const ShowSchedules = ({ applicationId }: Props) => {
<TableHead>Task Name</TableHead> <TableHead>Task Name</TableHead>
<TableHead>Schedule</TableHead> <TableHead>Schedule</TableHead>
<TableHead>Command</TableHead> <TableHead>Command</TableHead>
<TableHead>Status</TableHead>
<TableHead className="text-right">Actions</TableHead> <TableHead className="text-right">Actions</TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
@@ -113,6 +125,13 @@ export const ShowSchedules = ({ applicationId }: Props) => {
</code> </code>
</div> </div>
</TableCell> </TableCell>
<TableCell>
<Badge
variant={schedule.enabled ? "default" : "secondary"}
>
{schedule.enabled ? "Enabled" : "Disabled"}
</Badge>
</TableCell>
<TableCell className="text-right"> <TableCell className="text-right">
<div className="flex justify-end gap-2"> <div className="flex justify-end gap-2">
<Button <Button

View File

@@ -0,0 +1 @@
ALTER TABLE "schedule" ADD COLUMN "enabled" boolean DEFAULT true NOT NULL;

File diff suppressed because it is too large Load Diff

View File

@@ -631,6 +631,13 @@
"when": 1746178027816, "when": 1746178027816,
"tag": "0089_fearless_morlun", "tag": "0089_fearless_morlun",
"breakpoints": true "breakpoints": true
},
{
"idx": 90,
"version": "7",
"when": 1746178996842,
"tag": "0090_colossal_azazel",
"breakpoints": true
} }
] ]
} }

View File

@@ -80,6 +80,24 @@ export const scheduleRouter = createTRPCRouter({
}); });
} }
return schedule;
}),
runManually: protectedProcedure
.input(z.object({ scheduleId: z.string() }))
.mutation(async ({ ctx, input }) => {
const schedule = await ctx.db
.select()
.from(schedules)
.where(eq(schedules.scheduleId, input.scheduleId));
if (!schedule) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Schedule not found",
});
}
return schedule; return schedule;
}), }),
}); });

View File

@@ -1,5 +1,5 @@
import { relations } from "drizzle-orm"; import { relations } from "drizzle-orm";
import { pgTable, text } from "drizzle-orm/pg-core"; import { boolean, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod"; import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { z } from "zod"; import { z } from "zod";
@@ -18,6 +18,7 @@ export const schedules = pgTable("schedule", {
.references(() => applications.applicationId, { .references(() => applications.applicationId, {
onDelete: "cascade", onDelete: "cascade",
}), }),
enabled: boolean("enabled").notNull().default(true),
createdAt: text("createdAt") createdAt: text("createdAt")
.notNull() .notNull()
.$defaultFn(() => new Date().toISOString()), .$defaultFn(() => new Date().toISOString()),