Files
sencho/docs/tutorials/schedule-an-operation.mdx
Anso b6766572df feat(navigation): make Compact Launcher the default desktop style (#1890)
* feat(navigation): make Compact Launcher the default desktop style

Compact Launcher replaces Smart bar as the default desktop navigation
style; Classic bar is fully retired (rendering, settings option, and
persisted-value migration to Compact). Quick-link capacity increases
from 7 to 8, and the recommended default set becomes Home, Fleet,
Resources, Security, Update, and Schedules.

Quick-link seeding and Reset now use a settled, role-aware eligibility
computation distinct from the existing current-context display
filtering, so an operator's recommended defaults reflect their role
rather than which node happens to be active, and a Reset produces the
same result regardless of node context.

Also fixes two issues surfaced while touching this code: a disabled
quick-link control showing a duplicate tooltip (native title plus the
Radix tooltip), and the Navigate launcher panel being unable to scroll
at constrained viewport heights (now uses the shared ScrollArea
component). The launcher hamburger icon animates into an X on open,
respecting Reduced motion.

* test(e2e): fix the launcher morph and panel scroll navigation assertions

Three assertions in the new navigation specs were wrong against a real
browser, all in the tests rather than the product:

- The morph check read getComputedStyle().transform, which Tailwind v4
  no longer writes: rotate-45 compiles to `rotate:45deg` and the
  translate utilities to the standalone `translate` property, so both
  the open and closed reads returned "none". It now snapshots translate
  and rotate alongside transform. The animation itself was always
  correct, since Tailwind's transition-transform covers translate,
  scale, and rotate.
- The reduced-motion check assumed motion starts enabled. A fresh
  install defaults to the Calm visual style, which turns Reduced motion
  on, so the clamp was already active and the pre-toggle duration
  assertion could never hold. It now drives the toggle in both
  directions from a known state.
- The panel scroll check asserted a specific overflow measurement,
  which depends on Radix having applied its available-height variable
  at read time and on how many destinations the account can reach. It
  now asserts the property the fix actually guarantees: the ScrollArea
  viewport owns vertical scrolling while the outer menu only clips,
  with no horizontal overflow and the panel inside the viewport.

Verified by running the spec against live dev servers: 7 passed, twice.

* fix(nav): make the Navigate launcher panel actually scroll with the mouse

Live QA found that the panel only reached destinations below the fold
by keyboard; a real mouse wheel did nothing. The Radix ScrollArea
viewport is sized with h-full, and a percentage height only resolves
against a containing block with a definite height. The popper content
is height:auto clamped by max-height, which is not definite, so
nothing sized from it is either, so the viewport fell back to its full
content height with no internal overflow, no scrollbar, and no
response to wheel input, while an ancestor's overflow-hidden silently
clipped everything past the fold.

Moves the available-height cap onto the viewport itself and moves the
masthead inside the scroll region so the cap needs no masthead-height
arithmetic. Verified live: the previous structure measured
scrollHeight === clientHeight === 594 with zero wheel movement; the
fix measures 646/357 with the wheel reaching the true maximum.

Also hardens the panel's e2e coverage, which passed on the broken
structure because it asserted only computed overflow properties and
never drove a real scroll: adds a keyboard-reach assertion using End
rather than ArrowDown (ArrowDown landing on the first item is stock
roving focus and proves nothing about scrolling), a genuine
mouse-wheel assertion that scrolls to the true bottom rather than
assuming one gesture covers the range, and a companion test proving
the cap tracks the popper's available height rather than a hardcoded
pixel value. Confirmed the rewritten test fails on the previous
structure and passes on the fix.
2026-09-05 20:03:48 -04:00

117 lines
9.7 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: Schedule an Operation
sidebarTitle: Schedule an operation
description: Create a recurring stack restart in Sencho, verify it with a manual run, and read its execution history.
---
Say you run a small web app that slowly accumulates stale connections as the week goes on, and the fix is a restart every night while nobody is using it. You could log in at 3:00 AM, or you could let Sencho do it for you. This walks through creating a scheduled task that restarts the `my-website` stack (an nginx web tier with a postgres database) every day at 3:00 AM, running it once by hand to confirm it works, and checking the execution history.
By the end, the task is running on its own schedule, and you have a green run in the history that proves it works.
This tutorial covers one recurring **Restart Stack** task on the hub. It does not cover the other scheduled actions (auto-update, vulnerability scans, prunes, fleet snapshots), advanced cron expressions, per-service restart scoping, one-shot tasks, or scheduling actions against a remote node. See the [Scheduled Operations](/features/scheduled-operations) feature page for the full action list and the [cron expression reference](/features/scheduled-operations#cron-expression-reference).
## Prerequisites
- An **admin** account on the Sencho hub. Creating and managing schedules requires admin.
- A running stack on the hub. This tutorial schedules a restart of `my-website`, deployed as an nginx service with a postgres service behind it.
- The hub (Local) selected as the active node. Schedules is a hub-level view: it is not available while a remote node is the active selection. If you manage remote nodes, switch back to **Local** before continuing.
<Note>
A scheduled restart restarts the stack's containers in place. It does not recreate them, so the compose configuration and any bind-mounted data are left untouched.
</Note>
<Steps>
<Step title="Open the Schedules page">
Open the navigation launcher (the top-left menu icon), then under **Operations** pick **Schedules**. If you use Smart bar instead, click **More** and find Schedules there under the same **Operations** group.
<Frame>
<img src="/images/tutorials/schedule-an-operation/more-menu-schedules.png" alt="The navigation launcher's Navigate panel with Audit under Security & review, Logs, Update, and Schedules under Operations, and Console under Tools." />
</Frame>
The page opens on the **Timeline** view: a 24-hour strip with five lanes, one per operation category (Lifecycle, Updates, Security, Upkeep, Backups). With no tasks yet, it shows the empty-state message `Nothing scheduled in the next 24 hours`.
<Frame>
<img src="/images/tutorials/schedule-an-operation/schedules-timeline-empty.png" alt="The Scheduled Operations page on the Timeline view, showing the Next 24 hours header, an empty Next readout, the five category lanes, and the message 'Nothing scheduled in the next 24 hours. Toggle to All tasks to see every schedule, or create a new one.'" />
</Frame>
</Step>
<Step title="Open the New Schedule form">
Click **New Schedule** in the top right of the page. The **New scheduled task** modal opens.
Open the **Action** picker. The actions are grouped by category, each with a risk badge and one-line helper text so you can see the blast radius before you commit: **Lifecycle** holds the stack and container actions, **Updates** the auto-update variants, **Security** the vulnerability scan, **Upkeep** the resource prune, and **Backups** the fleet snapshot.
<Frame>
<img src="/images/tutorials/schedule-an-operation/new-schedule-action-picker.png" alt="The New scheduled task modal with the Action picker open, showing the Lifecycle, Updates, Security, Upkeep, and Backups groups with their actions listed." />
</Frame>
</Step>
<Step title="Configure the nightly restart">
Fill in the form:
- **Name**: `Nightly website restart`
- **Action**: **Restart Stack** (the default, in the Lifecycle group). The **Interruptive** badge and the helper text "Restarts containers in place" describe exactly what a run will do: stop and start the containers on the same configuration.
- **Node**: **Local**.
- **Stack**: `my-website`. The stack defines two services, so a **Services (leave empty for all)** grid appears with `web` and `db` checkboxes. Leave both unchecked to restart the entire stack.
- **Schedule**: leave **Simple** mode selected, with **Daily**, **Hour** `03`, and **Minute** `00`. The preview below the controls reads `At 03:00 AM · 0 3 * * *`, and the note under it says the schedule runs in the node's local timezone. This is where you pick the off-peak window: 3:00 AM, when nobody is hitting the site.
- **Enabled**: leave ON.
- **Delete after successful run**: leave unchecked. This task is meant to fire every night, not remove itself after one success.
<Frame>
<img src="/images/tutorials/schedule-an-operation/create-task-filled.png" alt="The New scheduled task modal configured with Name 'Nightly website restart', Action Restart Stack with the Interruptive badge, Node Local, Stack my-website with the web and db service checkboxes, a Daily schedule at 03:00 with the preview 'At 03:00 AM · 0 3 * * *', and the Enabled switch on." />
</Frame>
Click **Create**. The modal closes and a toast confirms the task was created.
</Step>
<Step title="Confirm the task in the timeline and the table">
The timeline now shows a blue pill on the **Lifecycle** lane at the next firing, and the **Next** readout shows the task name with a countdown.
<Frame>
<img src="/images/tutorials/schedule-an-operation/timeline-pill.png" alt="The Scheduled Operations timeline with a blue pill labelled 23:00 my-website on the Lifecycle lane, and the Next readout showing 'Nightly website restart · in 20h 21m'." />
</Frame>
Note the time: the schedule fires at 3:00 AM **in the node's timezone**, and the timeline displays firing times **in your browser's timezone**, so the pill time may differ from the form's preview. Here the node is on UTC, the browser on Eastern Daylight Time, and 03:00 UTC shows as 23:00.
Toggle to **All tasks** for the tabular view. The new row shows the name, the **Restart Stack** action badge, the `my-website` target, the human-readable schedule over its raw cron `0 3 * * *`, **Status** `Never run`, the next firing in your local time, and the **Enabled** switch set to ON.
<Frame>
<img src="/images/tutorials/schedule-an-operation/all-tasks-row.png" alt="The All tasks table with one row: Nightly website restart, Restart Stack, my-website, At 03:00 AM / 0 3 * * *, Status Never run, Next Run 8/6/2026 11:00:00 PM, Enabled ON, and the Run now, Execution history, Edit, and Delete buttons." />
</Frame>
</Step>
<Step title="Run the task now to prove it works">
Click the **Run now** button (play icon) on the row. The task executes immediately instead of waiting for the next firing. After a few seconds, click **Refresh**; the row's **Status** flips to **Success** (green badge).
<Frame>
<img src="/images/tutorials/schedule-an-operation/all-tasks-success.png" alt="The All tasks table with the Nightly website restart row now showing a green Success badge in the Status column." />
</Frame>
</Step>
</Steps>
## Verify it worked
Check from two places, since either alone could be misleading (a table that has not refreshed, or a run that updated the badge but left an error behind).
**The Status badge.** In **All tasks**, the row shows the green **Success** badge, and the task is still **Enabled**, so the next firing is still scheduled.
**The Execution history.** Click the **Execution history** button (clock icon) on the row. The sheet opens with the breadcrumb `Schedules Nightly website restart Runs`, a `1 run` count, and the run's row: **Source** `Manual` (it was triggered by Run now, not by the cron), **Status** `Success`, a short **Duration**, and the details `Restarted 2 container(s) in stack "my-website"`: both services, exactly as configured.
<Frame>
<img src="/images/tutorials/schedule-an-operation/run-history.png" alt="The Execution history sheet for Nightly website restart, showing 1 run with Source Manual, Status Success, Duration 0.7s, and Details 'Restarted 2 container(s) in stack my-website'. The footer shows the next scheduled run." />
</Frame>
The footer confirms the schedule is alive: the next run time is shown there too, matching the row's **Next Run** in the table. You can now rely on the nightly restart without being at the console at 3:00 AM.
## If something goes wrong
**You don't see Schedules in the launcher or More menu.** Schedules is a hub-level view. If a remote node is the active selection, the hub-only views (Schedules, Audit, Logs, Update) are missing. Click the node switcher next to the Sencho logo, pick **Local**, and reopen the launcher (or **More**): Schedules is back under **Operations**.
A run that fails shows a red **Failed** badge in the row and an error-level notification; the failure notification carries the task name and the error so you can diagnose without opening the run history. The task stays enabled and fires again at its next cron tick, or you can click **Run now** to retry immediately. See [Scheduled Operations · Troubleshooting](/features/scheduled-operations#troubleshooting) for the full failure list.
## Related
<CardGroup cols={2}>
<Card title="Scheduled Operations" icon="clock" href="/features/scheduled-operations">
Every supported action, the cron expression reference, and failure notifications.
</Card>
<Card title="Auto-Update Policies" icon="arrows-rotate" href="/features/auto-update-policies">
Schedule image updates the same way, then review what changed on the companion board.
</Card>
</CardGroup>