Skip to content
4 changes: 1 addition & 3 deletions packages/core/src/backends/duplicated-job-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import { JobData } from "../schema";
*/
export class DuplicatedJobError extends Error {
constructor(job: JobData) {
super(
`Job ${job.class} is duplicated, constructor args: ${JSON.stringify(job.constructor_args)} args: ${JSON.stringify(job.args)}`,
);
super(`Job #${job.id} - ${job.class} is duplicated`);
}
}
2 changes: 2 additions & 0 deletions packages/core/src/transitions/cancel-transition.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { logger } from "../logger";
import { JobData } from "../schema";
import { JobTransition } from "./transition";

Expand All @@ -15,6 +16,7 @@ import { JobTransition } from "./transition";
*/
export class CancelTransition extends JobTransition {
apply(job: JobData): JobData {
logger("Core").info(`Cancelling job #${job.id} - ${job.class}`);
job.state = "canceled";
job.canceled_at = new Date();
return job;
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/transitions/complete-transition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class CompleteTransition extends JobTransition {
}

apply(job: JobData): JobData {
logger("Core").info(`Job ${job.class} has completed with args: ${JSON.stringify(job.args)}`);
logger("Core").info(`Job #${job.id} - ${job.class} has completed`);
job.completed_at = new Date();
job.state = "completed";
job.result = this.result ?? null;
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/transitions/rerun-transition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { JobTransition } from "./transition";
*/
export class RerunTransition extends JobTransition {
apply(job: JobData): JobData {
logger("Core").debug(`Re-running job ${job.class} with args: ${JSON.stringify(job.args)}`);
logger("Core").info(`Re-running job #${job.id} - ${job.class}`);

// Reset job state to waiting
job.state = "waiting";
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/transitions/retry-transition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export class RetryTransition extends JobTransition {
const reason = toErrorData(this.reason);

const delay = this.delay ?? this.calculateBackoff(job.attempt);
logger("Core").info(`Retrying failed job ${job.class} in ${delay}ms`);
logger("Core").info(`Retrying failed job #${job.id} - ${job.class} in ${delay}ms`);

const errData = {
...reason,
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/transitions/run-transition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { JobTransition } from "./transition";
*/
export class RunTransition extends JobTransition {
apply(job: JobData): JobData {
logger("Core").info(`Running job #${job.id} - ${job.class} with args: ${JSON.stringify(job.args)}`);
logger("Core").info(`Running job #${job.id} - ${job.class}`);
job.state = "running";
job.attempted_at = new Date();
job.attempt = job.attempt + 1;
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/transitions/snooze-transition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export class SnoozeTransition extends JobTransition {
}

apply(job: JobData): JobData {
logger("Core").info(`Job ${job.class} snoozed by ${this.delay}ms`);
logger("Core").info(`Job #${job.id} - ${job.class} snoozed by ${this.delay}ms`);

// Attempts are only decremented if the job is running and has already been attempted
// This means that the job will not consider the current run as an attempt
Expand Down
31 changes: 12 additions & 19 deletions packages/dashboard/src/public/js/job.js
Original file line number Diff line number Diff line change
@@ -1,43 +1,36 @@
// Global variable to store details state
let globalDetailsStates = {};

function applySeeMore() {
document.querySelectorAll(".sq-code").forEach((container) => {
const content = container.querySelector(".code-content");
const btn = container.querySelector(".toggle-btn");

const maxHeight = 240;
let expanded = false;

if (content.scrollHeight <= maxHeight) {
btn.classList.add("hidden");
}

btn.addEventListener("click", () => {
expanded = !expanded;

function toggleDetails(expanded) {
if (expanded) {
content.classList.remove("max-h-[15rem]", "overflow-hidden");
btn.textContent = "See less";
globalDetailsStates[container.id] = true; // Save state as expanded
} else {
content.classList.add("max-h-[15rem]", "overflow-hidden");
btn.textContent = "See more";
globalDetailsStates[container.id] = false; // Save state as not expanded
}
}

toggleDetails(globalDetailsStates[container.id] ?? false);

btn.addEventListener("click", () => {
toggleDetails(!globalDetailsStates[container.id] ?? true);
});
});
}

document.addEventListener("DOMContentLoaded", applySeeMore);
document.addEventListener("htmx:afterSwap", applySeeMore);

document.addEventListener("htmx:beforeSwap", (evt) => {
const detailsStates = Array.from(document.querySelectorAll("#job-details details")).map((d) => d.open);

evt.detail.target.dataset.detailsStates = JSON.stringify(detailsStates);
});

document.addEventListener("htmx:afterSwap", (evt) => {
const detailsStates = JSON.parse(evt.detail.target.dataset.detailsStates || "[]");

const details = evt.target.querySelectorAll("details");
detailsStates.forEach((isOpen, idx) => {
if (isOpen) details[idx].open = true;
});
});
10 changes: 0 additions & 10 deletions packages/dashboard/src/public/js/jobs.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,3 @@ document.addEventListener("htmx:configRequest", (evt) => {
evt.detail.parameters.end = endDate.toISOString();
}
});

document.addEventListener("htmx:afterRequest", (evt) => {
const form = document.getElementById("filter-form");
if (!form) return;

const params = new URLSearchParams(new FormData(form)).toString();

const url = `/jobs?${params}`;
window.history.pushState({}, "", url);
});
12 changes: 12 additions & 0 deletions packages/dashboard/src/public/js/scroll.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
let savedScrollY = 0;

// Save and restore scroll position around HTMX swaps
document.addEventListener("DOMContentLoaded", () => {
document.addEventListener("htmx:beforeSwap", () => {
savedScrollY = document.getElementById("main-section").scrollTop;
});

document.addEventListener("htmx:afterSwap", () => {
document.getElementById("main-section").scrollTo({ top: savedScrollY });
});
});
9 changes: 4 additions & 5 deletions packages/dashboard/src/views/layout.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<script src="/public/js/feather-icons.js"></script>
<script src="/public/js/highlight.js"></script>
<script src="/public/js/chart.js"></script>
<script src="/public/js/scroll.js"></script>
</head>
<body class="h-full bg-base-100 text-base-content">
<div class="flex h-full">
Expand All @@ -30,18 +31,16 @@


<!-- Main content -->
<main class="flex-1 p-6 overflow-y-auto">
<%- body %>
</main>
<main id="main-section" class="flex-1 p-6 overflow-y-auto"><%- body %></main>
</div>

<script>
function loadLibs(){
function loadLibs() {
feather.replace();
hljs.highlightAll();
}

document.addEventListener('htmx:afterSwap', loadLibs);
document.addEventListener("htmx:afterSwap", loadLibs);
loadLibs();
</script>
</body>
Expand Down
2 changes: 1 addition & 1 deletion packages/dashboard/src/views/pages/jobs.ejs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<section class="space-y-6">
<form method="GET" id="filter-form" hx-get="/jobs" hx-target="#jobs-table"
hx-trigger="change from:* delay:300ms, every 3s, jobChanged">
hx-trigger="change from:* delay:300ms, every 3s, jobChanged" hx-push-url="true">
<div class="flex flex-wrap items-end gap-4 text-base mb-4">
<div class="form-control w-48">
<label class="label label-text">Status</label>
Expand Down
6 changes: 3 additions & 3 deletions packages/dashboard/src/views/partials/job-view.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@
<h4 class="text-sm font-semibold mb-1">Constructor Arguments</h4>

<% if (Array.isArray(job.constructor_args) && job.constructor_args.length > 0) { %>
<div class="sq-code bg-neutral-900 rounded p-3 text-xs font-mono">
<div id="constructor-details" class="sq-code bg-neutral-900 rounded p-3 text-xs font-mono">
<div class="code-content overflow-x-auto max-h-[15rem] overflow-hidden transition-all">
<pre><code class="language-json"><%= JSON.stringify(job.constructor_args, null, 2) %></code></pre>
</div>
Expand All @@ -130,7 +130,7 @@
<div class="mt-5">
<h4 class="text-sm font-semibold mb-1">Run Arguments</h4>
<% if (Array.isArray(job.args) && job.args.length > 0) { %>
<div class="sq-code bg-neutral-900 rounded p-3 text-xs font-mono">
<div id="args-details" class="sq-code bg-neutral-900 rounded p-3 text-xs font-mono">
<div class="code-content overflow-x-auto max-h-[15rem] overflow-hidden transition-all">
<pre><code class="language-json"><%= JSON.stringify(job.args, null, 2) %></code></pre>
</div>
Expand All @@ -147,7 +147,7 @@
<% if (job.result) { %>
<div>
<h4 class="text-sm font-semibold mb-1">Result</h4>
<div class="sq-code bg-neutral-900 rounded p-3 text-xs font-mono">
<div id="result-details" class="sq-code bg-neutral-900 rounded p-3 text-xs font-mono">
<div class="code-content overflow-x-auto max-h-[15rem] overflow-hidden transition-all">
<pre><code class="language-json"><%= JSON.stringify(job.result, null, 2) %></code></pre>
</div>
Expand Down
4 changes: 2 additions & 2 deletions packages/dashboard/src/views/partials/jobs-table.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@
<td class="px-2 py-3 text-base-content"><%= job.claimed_by || '-' %></td>
<td class="px-2 py-3 flex flex-nowrap gap-2">
<% if (job.available_at > new Date()) { %>
<button type="button" class="btn btn-sm" hx-patch="/jobs/<%= job.id %>/run"><i data-feather="play" class="w-4 h-4"></i>Run</button>
<button type="button" class="btn btn-sm" hx-patch="/jobs/<%= job.id %>/run" hx-push-url="false" hx-target="this"><i data-feather="play" class="w-4 h-4"></i>Run</button>
<% } %>
<% if (!['canceled', 'failed', 'completed'].includes(job.state)){ %>
<button type="button" class="btn btn-sm" hx-patch="/jobs/<%= job.id %>/cancel"><i data-feather="x" class="w-4 h-4"></i>Cancel</button>
<button type="button" class="btn btn-sm" hx-patch="/jobs/<%= job.id %>/cancel" hx-push-url="false" hx-target="this"><i data-feather="x" class="w-4 h-4"></i>Cancel</button>
<% } %>
</td>
</tr>
Expand Down
2 changes: 1 addition & 1 deletion packages/dashboard/src/views/partials/queues-table.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
<td><%= queue.jobs.filter(job => job.state === "failed").length %></td>
<td>
<button class="btn btn-sm"
hx-patch="/queues/<%= queue.name %>/toggle">
hx-patch="/queues/<%= queue.name %>/toggle" hx-push-url="false" hx-target="this">
<% if(queue.state==="active") { %>
<i data-feather="pause" class="w-4 h-4"></i>
<% } else { %>
Expand Down
Loading