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,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { Switch } from "@/components/ui/switch";
const commonCronExpressions = [
{ label: "Every minute", value: "* * * * *" },
@ -42,6 +43,7 @@ const formSchema = z.object({
name: z.string().min(1, "Name is required"),
cronExpression: z.string().min(1, "Cron expression is required"),
command: z.string().min(1, "Command is required"),
enabled: z.boolean().default(true),
});
interface Props {
@ -68,6 +70,7 @@ export const HandleSchedules = ({
name: "",
cronExpression: "",
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">
{isLoading ? (
<>

View File

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

View File

@ -1,5 +1,5 @@
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 { nanoid } from "nanoid";
import { z } from "zod";
@ -18,6 +18,7 @@ export const schedules = pgTable("schedule", {
.references(() => applications.applicationId, {
onDelete: "cascade",
}),
enabled: boolean("enabled").notNull().default(true),
createdAt: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),