{{ t("cron.history.title") }}
-
@@ -376,7 +575,7 @@ onMounted(() => {
{{ statusLabel(run.status) }}
|
- {{ formatDate(run.started_at) }} |
+ {{ runStartedLabel(run) }} |
{{ formatDuration(run.duration_ms) }} |
@@ -427,6 +626,7 @@ onMounted(() => {
display: block;
font-size: 1rem;
margin-top: 0.25rem;
+ overflow-wrap: anywhere;
}
.cron-table-container {
@@ -456,7 +656,8 @@ onMounted(() => {
white-space: nowrap;
}
-.cron-history h2 {
+.cron-history h2,
+.cron-worker-panel h2 {
font-size: 1.1rem;
font-weight: 700;
margin: 0;
diff --git a/tests/e2e/superuser-cron.spec.ts b/tests/e2e/superuser-cron.spec.ts
index 74d1f96d..a67d4a24 100644
--- a/tests/e2e/superuser-cron.spec.ts
+++ b/tests/e2e/superuser-cron.spec.ts
@@ -64,11 +64,21 @@ test.describe("Superuser cron operations", () => {
await primeMockSession(page, { token: "superuser-cron-token", bootPath: null });
});
- test("superusers can inspect, run, toggle, and reschedule cron tasks", async ({ page }) => {
+ test("superusers can inspect, run, toggle, and reschedule cron tasks", async ({ page }, testInfo) => {
const patchPayloads: Array> = [];
const runPayloads: Array> = [];
const deployPayloads: Array> = [];
let currentTasks = cronTasks.map((task) => ({ ...task, schedule: { ...task.schedule } }));
+ let runHistory = [
+ {
+ id: 41,
+ task_id: "economic.transfer_queue",
+ source: "automatic",
+ status: "succeeded",
+ started_at: "2026-07-09 12:00:00",
+ duration_ms: 2100,
+ },
+ ];
await page.route(apiPathPattern("/superuser/cron"), async (route) => {
if (route.request().method() !== "GET") {
@@ -89,16 +99,7 @@ test.describe("Superuser cron operations", () => {
body: JSON.stringify({
success: true,
data: {
- runs: [
- {
- id: 41,
- task_id: "economic.transfer_queue",
- source: "automatic",
- status: "succeeded",
- started_at: "2026-07-09 12:00:00",
- duration_ms: 2100,
- },
- ],
+ runs: runHistory,
},
meta: {},
includes: {},
@@ -180,8 +181,20 @@ test.describe("Superuser cron operations", () => {
await page.route(apiPathPattern("/superuser/cron/run"), async (route) => {
runPayloads.push(route.request().postDataJSON() as Record);
+ runHistory = [
+ {
+ id: 42,
+ task_id: "economic.transfer_queue",
+ source: "manual",
+ status: "queued",
+ scheduled_for: "2026-07-09 12:02:00",
+ created_at: "2026-07-09 12:02:00",
+ duration_ms: null,
+ },
+ ...runHistory,
+ ];
await route.fulfill({
- status: 200,
+ status: 202,
contentType: "application/json",
body: JSON.stringify({
success: true,
@@ -189,9 +202,10 @@ test.describe("Superuser cron operations", () => {
id: 42,
task_id: "economic.transfer_queue",
source: "manual",
- status: "succeeded",
- started_at: "2026-07-09 12:02:00",
- duration_ms: 1800,
+ status: "queued",
+ scheduled_for: "2026-07-09 12:02:00",
+ created_at: "2026-07-09 12:02:00",
+ duration_ms: null,
},
meta: {},
includes: {},
@@ -226,18 +240,22 @@ test.describe("Superuser cron operations", () => {
"Process e-conomic transfer queue"
);
await expect(page.getByTestId("cron-total-tasks")).toContainText("2");
- await expect(page.getByTestId("cron-worker-panel")).toContainText("release-stable-cron-worker");
- await expect(page.getByTestId("cron-worker-running")).toContainText("1");
- await expect(page.getByTestId("cron-worker-target")).toContainText("cron-worker-uuid");
+ if (!/mobile/i.test(testInfo.project.name)) {
+ await expect(page.getByTestId("cron-worker-panel")).toContainText("release-stable-cron-worker");
+ await expect(page.getByTestId("cron-worker-running")).toContainText("1");
+ await expect(page.getByTestId("cron-worker-target")).toContainText("cron-worker-uuid");
+ await expect(page.getByTestId("cron-worker-deploy")).toHaveText("Update Coolify deployment");
- await Promise.all([
- page.waitForResponse(
- (response) =>
- response.url().includes("/superuser/cron/workers/deploy") && response.request().method() === "POST"
- ),
- page.getByTestId("cron-worker-deploy").click(),
- ]);
- expect(deployPayloads).toEqual([{}]);
+ await Promise.all([
+ page.waitForResponse(
+ (response) =>
+ response.url().includes("/superuser/cron/workers/deploy") && response.request().method() === "POST"
+ ),
+ page.getByTestId("cron-worker-deploy").click(),
+ ]);
+ expect(deployPayloads).toEqual([{}]);
+ await expect(page.getByTestId("cron-run-queued")).toContainText("deployment update");
+ }
await Promise.all([
page.waitForResponse(
@@ -248,6 +266,8 @@ test.describe("Superuser cron operations", () => {
expect(runPayloads).toEqual([{ task_id: "economic.transfer_queue", force: false }]);
await expect(page.getByTestId("cron-run-history")).toContainText("manual");
+ await expect(page.getByTestId("cron-run-history")).toContainText("Queued");
+ await expect(page.getByTestId("cron-run-queued")).toContainText("was queued");
await page.getByTestId("cron-task-toggle-system.sync_logs").click();
expect(patchPayloads.at(-1)).toMatchObject({ task_id: "system.sync_logs", enabled: true });
@@ -259,4 +279,106 @@ test.describe("Superuser cron operations", () => {
schedule: { type: "interval", seconds: 120 },
});
});
+
+ test("superusers can deploy the cron worker to Coolify when no deployment target exists", async ({ page }) => {
+ const deployPayloads: Array> = [];
+ let deploymentTarget: Record | null = null;
+
+ await page.route(apiPathPattern("/superuser/cron"), async (route) => {
+ if (route.request().method() !== "GET") {
+ await route.fallback();
+ return;
+ }
+ await route.fulfill({
+ status: 200,
+ contentType: "application/json",
+ body: JSON.stringify(cronListPayload()),
+ });
+ });
+
+ await page.route(apiPathPattern("/superuser/cron/runs"), async (route) => {
+ await route.fulfill({
+ status: 200,
+ contentType: "application/json",
+ body: JSON.stringify({
+ success: true,
+ data: { runs: [] },
+ meta: {},
+ includes: {},
+ }),
+ });
+ });
+
+ await page.route(apiPathPattern("/superuser/cron/workers"), async (route) => {
+ if (route.request().method() !== "GET") {
+ await route.fallback();
+ return;
+ }
+ await route.fulfill({
+ status: 200,
+ contentType: "application/json",
+ body: JSON.stringify({
+ success: true,
+ data: {
+ workers: [],
+ summary: {
+ total: 0,
+ running: 0,
+ stale: 0,
+ },
+ deployment: {
+ ok: true,
+ channel: {
+ id: 1,
+ slug: "stable",
+ name: "Stable",
+ },
+ target: deploymentTarget,
+ },
+ },
+ meta: {},
+ includes: {},
+ }),
+ });
+ });
+
+ await page.route(apiPathPattern("/superuser/cron/workers/deploy"), async (route) => {
+ deployPayloads.push(route.request().postDataJSON() as Record);
+ deploymentTarget = {
+ id: 71,
+ app: "cron",
+ coolify_service_uuid: "cron-worker-uuid",
+ auto_deploy: false,
+ };
+ await route.fulfill({
+ status: 200,
+ contentType: "application/json",
+ body: JSON.stringify({
+ success: true,
+ data: {
+ ok: true,
+ target: deploymentTarget,
+ },
+ meta: {},
+ includes: {},
+ }),
+ });
+ });
+
+ await page.goto("/superuser/system/cron", { waitUntil: "domcontentloaded" });
+
+ await expect(page.getByTestId("cron-worker-deploy")).toHaveText("Deploy to Coolify");
+ await Promise.all([
+ page.waitForResponse(
+ (response) =>
+ response.url().includes("/superuser/cron/workers/deploy") && response.request().method() === "POST"
+ ),
+ page.getByTestId("cron-worker-deploy").click(),
+ ]);
+
+ expect(deployPayloads).toEqual([{}]);
+ await expect(page.getByTestId("cron-run-queued")).toContainText("deployment was queued");
+ await expect(page.getByTestId("cron-worker-target")).toContainText("cron-worker-uuid");
+ await expect(page.getByTestId("cron-worker-deploy")).toHaveText("Update Coolify deployment");
+ });
});
diff --git a/tests/e2e/superuser-security.spec.ts b/tests/e2e/superuser-security.spec.ts
index d0a518d3..036b6e24 100644
--- a/tests/e2e/superuser-security.spec.ts
+++ b/tests/e2e/superuser-security.spec.ts
@@ -92,7 +92,7 @@ test.describe("Superuser system security", () => {
await primeMockSession(page, { token: "superuser-security-token", bootPath: null });
});
- test("supports overview, firewall, settings, and incident management", async ({ page }) => {
+ test("supports overview, firewall, settings, and incident management", async ({ page }, testInfo) => {
const settingsPayloads: Array> = [];
const firewallCreatePayloads: Array> = [];
const incidentPatchPayloads: Array> = [];
@@ -210,9 +210,11 @@ test.describe("Superuser system security", () => {
await page.goto("/superuser/system/security/overview");
await expect(page.getByTestId("superuser-security-page")).toBeVisible();
- const desktopNavigation = page.getByTestId("desktop-buefy-navigation");
- await expect(desktopNavigation.getByText("System", { exact: true })).toBeVisible();
- await expect(desktopNavigation.getByText("Security", { exact: true })).toBeVisible();
+ if (!/mobile/i.test(testInfo.project.name)) {
+ const desktopNavigation = page.getByTestId("desktop-buefy-navigation");
+ await expect(desktopNavigation.getByText("System", { exact: true })).toBeVisible();
+ await expect(desktopNavigation.getByText("Security", { exact: true })).toBeVisible();
+ }
await expect(page.getByText("Security overview")).toBeVisible();
await expect(page.getByRole("link", { name: "Open incidents" })).toBeVisible();
await expect(page.getByText("Block firewall rule matched: ip 203.0.113.9")).toBeVisible();
diff --git a/tests/e2e/superuser-users.spec.ts b/tests/e2e/superuser-users.spec.ts
index 9811e858..261cbe5d 100644
--- a/tests/e2e/superuser-users.spec.ts
+++ b/tests/e2e/superuser-users.spec.ts
@@ -1356,9 +1356,12 @@ test.describe("Superuser user overview", () => {
await expect(page.getByTestId("superuser-user-vehicles-summary")).toContainText("1");
await expect(page.getByTestId("superuser-user-vehicles-subscription-invoicing")).toContainText("694");
await expect(page.getByTestId("superuser-user-vehicles-mass-insert")).toBeVisible();
- await expect(page.getByTestId("superuser-user-vehicles-table-panel")).toContainText("AA11223");
- await expect(page.getByTestId("superuser-user-vehicles-table-panel")).toContainText("BB44556");
- await expect(page.getByTestId("superuser-user-vehicles-table-panel")).not.toContainText(/Anna Transport/);
+ const vehiclesPanel = page.getByTestId("superuser-user-vehicles-table-panel");
+ await expect(vehiclesPanel).toContainText("AA11223");
+ await expect(vehiclesPanel).toContainText("BB44556");
+ if (isDesktopProject(testInfo)) {
+ await expect(vehiclesPanel).not.toContainText(/Anna Transport/);
+ }
await page.goto("/superuser/users/11/subusers", { waitUntil: "domcontentloaded" });
await expectUserDetailShell(page, "subusers", "superuser-user-subusers-page");
@@ -1375,8 +1378,11 @@ test.describe("Superuser user overview", () => {
.toBe(true);
await expectTileGridLayout(page, "superuser-user-subusers-metrics", expectMultipleMetricColumns);
await expect(page.getByTestId("superuser-user-subusers-summary")).toContainText("2");
- await expect(page.getByTestId("superuser-user-subusers-table-panel")).toContainText("Driver One");
- await expect(page.getByTestId("superuser-user-subusers-table-panel")).not.toContainText(/Anna Transport/);
+ const subusersPanel = page.getByTestId("superuser-user-subusers-table-panel");
+ await expect(subusersPanel).toContainText("Driver One");
+ if (!isDesktopProject(testInfo)) {
+ await expect(subusersPanel).toContainText(/Anna Transport/);
+ }
await page.goto("/superuser/users/11/xlvask", { waitUntil: "domcontentloaded" });
await expectUserDetailShell(page, "xlvask");
@@ -1408,7 +1414,8 @@ test.describe("Superuser user overview", () => {
});
await page.getByRole("button", { name: /Send invitation|Send invitation|Send invitation/i }).click();
const inviteRequest = await inviteRequestPromise;
- await expect(page.getByText(/Invitationen blev sendt på SMS|SMS/i)).toBeVisible();
+ const inviteDialog = page.locator(".swal2-popup").filter({ hasText: "Chauffør oprettet" });
+ await expect(inviteDialog.locator(".swal2-html-container")).toContainText(/Invitationen blev sendt på SMS/i);
expect(inviteRequest.postDataJSON()).toMatchObject({
name: "Scoped Invite Driver",