- {statusIcon}
+ const columns = useMemo(
+ () => [
+ {
+ title: "Job",
+ dataIndex: "task_name",
+ key: "task_name",
+ sorter: (a, b) =>
+ (a.task_name || "").localeCompare(b.task_name || ""),
+ render: (text, record) => {
+ const isFailed = [
+ "FAILED",
+ "FAILED PERMANENTLY",
+ "FAILURE",
+ ].includes(record.task_status);
+ const isSuccess = record.task_status === "SUCCESS";
+ const isRunning = ["RUNNING", "STARTED", "PENDING"].includes(
+ record.task_status
+ );
+ const isPaused = !record.periodic_task_details?.enabled;
+ let statusIcon;
+ let statusClass;
+ let tooltipText;
+ if (isPaused) {
+ statusIcon =
;
+ statusClass = "paused";
+ tooltipText = "Paused — will not run";
+ } else if (isFailed) {
+ statusIcon =
;
+ statusClass = "failed";
+ tooltipText = "Last run failed — needs attention";
+ } else if (isSuccess) {
+ statusIcon =
;
+ statusClass = "success";
+ tooltipText = "Healthy — last run succeeded";
+ } else if (isRunning) {
+ statusIcon =
;
+ statusClass = "running";
+ tooltipText = "Running";
+ } else {
+ statusIcon =
;
+ statusClass = "paused";
+ tooltipText = "Scheduled — has not run yet";
+ }
+ return (
+
+
+
+ {statusIcon}
+
+
+
+
goToRunHistory(record.user_task_id)}
+ >
+ {text}
+
+ {record.description && (
+
+
+ {record.description}
+
+
+ )}
-
-
-
goToRunHistory(record.user_task_id)}>
- {text}
-
- {record.description && (
-
{record.description}
- )}
-
- );
+ );
+ },
+ },
+ {
+ title: "Project",
+ dataIndex: "project",
+ key: "project",
+ render: (project) =>
{project?.name} ,
},
- },
- {
- title: "Project",
- dataIndex: "project",
- key: "project",
- render: (project) =>
{project?.name} ,
- },
- {
- title: "Environment",
- dataIndex: "environment",
- key: "environment",
- render: (env) => env ?
:
— ,
- },
- {
- title: "Schedule",
- key: "schedule",
- render: (_, record) => (
-
- ),
- },
- {
- title: "Last Run",
- key: "last_run",
- sorter: (a, b) => new Date(a.task_completion_time || 0) - new Date(b.task_completion_time || 0),
- render: (_, record) => {
- if (!record.task_status) return
— ;
- const isFailed = ["FAILED", "FAILED PERMANENTLY", "FAILURE"].includes(record.task_status);
- const isSuccess = record.task_status === "SUCCESS";
- const isRunning = ["RUNNING", "STARTED", "PENDING"].includes(record.task_status);
+ {
+ title: "Environment",
+ dataIndex: "environment",
+ key: "environment",
+ render: (env) =>
+ env ? (
+
+ ) : (
+
—
+ ),
+ },
+ {
+ title: "Schedule",
+ key: "schedule",
+ render: (_, record) => (
+
+ ),
+ },
+ {
+ title: "Last Run",
+ key: "last_run",
+ sorter: (a, b) =>
+ new Date(a.task_completion_time || 0) -
+ new Date(b.task_completion_time || 0),
+ render: (_, record) => {
+ if (!record.task_status) return
— ;
+ const isFailed = [
+ "FAILED",
+ "FAILED PERMANENTLY",
+ "FAILURE",
+ ].includes(record.task_status);
+ const isSuccess = record.task_status === "SUCCESS";
+ const isRunning = ["RUNNING", "STARTED", "PENDING"].includes(
+ record.task_status
+ );
- // Compute duration if both times available
- let duration = null;
- if (record.task_run_time && record.task_completion_time) {
- const ms = new Date(record.task_completion_time) - new Date(record.task_run_time);
- if (ms > 0) duration = formatDurationMs(ms);
- }
+ // Compute duration if both times available
+ let duration = null;
+ if (record.task_run_time && record.task_completion_time) {
+ const ms =
+ new Date(record.task_completion_time) -
+ new Date(record.task_run_time);
+ if (ms > 0) duration = formatDurationMs(ms);
+ }
- return (
-
- : isFailed ? : isRunning ? : null}
- color={isSuccess ? "success" : isFailed ? "error" : isRunning ? "processing" : "default"}
- >
- {record.task_status === "FAILURE" ? "FAILED" : record.task_status}
-
- {record.task_completion_time && (
-
- {formatDateTime(record.task_completion_time)}
-
- )}
-
- {record.task_completion_time ? getRelativeTime(record.task_completion_time) : ""}
- {duration ? ` · ${duration}` : ""}
-
-
- );
+ return (
+
+
+ ) : isFailed ? (
+
+ ) : isRunning ? (
+
+ ) : null
+ }
+ color={
+ isSuccess
+ ? "success"
+ : isFailed
+ ? "error"
+ : isRunning
+ ? "processing"
+ : "default"
+ }
+ >
+ {record.task_status === "FAILURE"
+ ? "FAILED"
+ : record.task_status}
+
+ {record.task_completion_time && (
+
+
+ {formatDateTime(record.task_completion_time)}
+
+
+ )}
+
+ {record.task_completion_time
+ ? getRelativeTime(record.task_completion_time)
+ : ""}
+ {duration ? ` · ${duration}` : ""}
+
+
+ );
+ },
},
- },
- {
- title: "Next Run",
- dataIndex: "next_run_time",
- key: "next_run",
- sorter: (a, b) => new Date(a.next_run_time || 0) - new Date(b.next_run_time || 0),
- render: (text, record) => {
- if (!text) {
- if (!record.periodic_task_details?.enabled) {
- return
Paused ;
+ {
+ title: "Next Run",
+ dataIndex: "next_run_time",
+ key: "next_run",
+ sorter: (a, b) =>
+ new Date(a.next_run_time || 0) - new Date(b.next_run_time || 0),
+ render: (text, record) => {
+ if (!text) {
+ if (!record.periodic_task_details?.enabled) {
+ return (
+
+
+ Paused
+
+ );
+ }
+ return
— ;
}
- return
— ;
- }
- // Compute "in Xm" countdown
- const diff = new Date(text) - new Date();
- let countdown = null;
- if (diff > 0) {
- const mins = Math.floor(diff / 60000);
- if (mins < 60) countdown = `in ${mins}m`;
- else if (mins < 1440) countdown = `in ${Math.floor(mins / 60)}h ${mins % 60}m`;
- else countdown = `in ${Math.floor(mins / 1440)}d`;
- }
- return (
-
- {formatDateTime(text)}
- {countdown && (
-
- {countdown}
-
- )}
-
- );
+ // Compute "in Xm" countdown
+ const diff = new Date(text) - new Date();
+ let countdown = null;
+ if (diff > 0) {
+ const mins = Math.floor(diff / 60000);
+ if (mins < 60) countdown = `in ${mins}m`;
+ else if (mins < 1440)
+ countdown = `in ${Math.floor(mins / 60)}h ${mins % 60}m`;
+ else countdown = `in ${Math.floor(mins / 1440)}d`;
+ }
+ return (
+
+ {formatDateTime(text)}
+ {countdown && (
+
+ {countdown}
+
+ )}
+
+ );
+ },
+ },
+ {
+ title: "Status",
+ key: "status",
+ render: (_, record) => {
+ const enabled = record.periodic_task_details?.enabled;
+ return (
+
+ handleSwitchSchedular(record, checked)}
+ size="small"
+ />
+
+ {enabled ? "Enabled" : "Paused"}
+
+
+ );
+ },
},
- },
- {
- title: "Status",
- key: "status",
- render: (_, record) => {
- const enabled = record.periodic_task_details?.enabled;
- return (
-
- handleSwitchSchedular(record, checked)}
- size="small"
- />
- {enabled ? "Enabled" : "Paused"}
+ {
+ title: "Actions",
+ key: "actions",
+ width: 140,
+ render: (_, record) => (
+
+
+
+ ) : (
+
+ )
+ }
+ onClick={() =>
+ handleRun(record.project?.id, record.user_task_id)
+ }
+ disabled={loading[record.user_task_id]}
+ />
+
+
+ }
+ onClick={() => goToRunHistory(record.user_task_id)}
+ />
+
+
+ }
+ onClick={() => onRowClick(record.user_task_id)}
+ />
+
+
+ }
+ onClick={() => handleDelete(record)}
+ />
+
- );
+ ),
},
- },
- {
- title: "Actions",
- key: "actions",
- width: 140,
- render: (_, record) => (
-
-
- : }
- onClick={() => handleRun(record.project?.id, record.user_task_id)}
- disabled={loading[record.user_task_id]}
- />
-
-
- }
- onClick={() => goToRunHistory(record.user_task_id)}
- />
-
-
- }
- onClick={() => onRowClick(record.user_task_id)}
- />
-
-
- }
- onClick={() => handleDelete(record)}
- />
-
-
- ),
- },
- ], [token, loading, onRowClick]);
+ ],
+ [token, loading, onRowClick]
+ );
return (
,
+ emptyText: (
+
+ ),
}}
/>
);
From ed1e03751f23750f44fa2c599f1d58463c3f432b Mon Sep 17 00:00:00 2001
From: wicky <130177258+wicky-zipstack@users.noreply.github.com>
Date: Wed, 29 Apr 2026 07:28:27 +0530
Subject: [PATCH 6/8] fix: deleted jobs reappear after clearing filters
onDelete was removing the job from jobList but not from backup.
When filters were cleared, the useEffect re-derived jobList from
the stale backup, re-populating the deleted job.
---
frontend/src/ide/scheduler/JobList.jsx | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/frontend/src/ide/scheduler/JobList.jsx b/frontend/src/ide/scheduler/JobList.jsx
index 3a677a97..aacb7bf6 100644
--- a/frontend/src/ide/scheduler/JobList.jsx
+++ b/frontend/src/ide/scheduler/JobList.jsx
@@ -237,8 +237,12 @@ const JobList = () => {
await deleteTask(delTaskDetail.projectId, delTaskDetail.taskId);
setIsDeleteModalOpen(false);
notify({ type: "success", message: "Job deleted successfully" });
- setJobList(
- jobList.filter(
+ const remaining = jobList.filter(
+ (el) => el.periodic_task_details.id !== delTaskDetail.taskId
+ );
+ setJobList(remaining);
+ setBackup((prev) =>
+ prev.filter(
(el) => el.periodic_task_details.id !== delTaskDetail.taskId
)
);
From 84b4722a5df088fe39798e29179141ca507400cd Mon Sep 17 00:00:00 2001
From: wicky <130177258+wicky-zipstack@users.noreply.github.com>
Date: Wed, 29 Apr 2026 10:14:50 +0530
Subject: [PATCH 7/8] fix: update totalCount on delete so pagination reflects
correctly
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
totalCount was stale after deleting jobs — pagination footer still
showed the old count. Now decremented on each successful delete.
Stats cards already re-compute from backup (fixed earlier).
---
frontend/src/ide/scheduler/JobList.jsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/frontend/src/ide/scheduler/JobList.jsx b/frontend/src/ide/scheduler/JobList.jsx
index aacb7bf6..a44b488f 100644
--- a/frontend/src/ide/scheduler/JobList.jsx
+++ b/frontend/src/ide/scheduler/JobList.jsx
@@ -246,6 +246,7 @@ const JobList = () => {
(el) => el.periodic_task_details.id !== delTaskDetail.taskId
)
);
+ setTotalCount((prev) => Math.max(0, prev - 1));
} catch (error) {
notify({ error });
}
From faf8a6892d06aa6fbbacfb37ddae92190f6a11a3 Mon Sep 17 00:00:00 2001
From: wicky <130177258+wicky-zipstack@users.noreply.github.com>
Date: Wed, 29 Apr 2026 10:38:09 +0530
Subject: [PATCH 8/8] fix: append (UTC) to cron schedule description
Cron schedules are stored and executed in UTC. The description text
now shows "(UTC)" so users understand the schedule timezone context
(e.g., "At 30 minutes past the hour (UTC)").
---
frontend/src/ide/scheduler/JobListTable.jsx | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/frontend/src/ide/scheduler/JobListTable.jsx b/frontend/src/ide/scheduler/JobListTable.jsx
index b5ece7b3..973bef46 100644
--- a/frontend/src/ide/scheduler/JobListTable.jsx
+++ b/frontend/src/ide/scheduler/JobListTable.jsx
@@ -81,7 +81,8 @@ const ScheduleBadge = ({ type, details }) => {
let description = "";
try {
if (isCron && expression) {
- description = getTooltipText({ cron_expression: expression }, type);
+ description =
+ getTooltipText({ cron_expression: expression }, type) + " (UTC)";
} else if (!isCron && details?.interval) {
description = getTooltipText(details.interval, type);
}