diff --git a/Config/usmt-dash.psd1 b/Config/usmt-dash.psd1
index 127726a..e0ac67c 100644
--- a/Config/usmt-dash.psd1
+++ b/Config/usmt-dash.psd1
@@ -133,6 +133,15 @@
LifecycleHours = 24 # how long one simulated migration takes, start to finish
PresenceSweepMinutes = 60 # completed / Skip USMT devices: hourly, like Live (the demo runs all day)
LivePublishSeconds = 4 # publish swept rows this often during a cycle
+
+ # When the demo worker runs (outside these hours it sleeps, with the midnight pitstop),
+ # same format as Worker.BusinessHours. Set Enabled = $false to run around the clock.
+ BusinessHours = @{
+ Enabled = $true
+ Start = '06:00'
+ End = '21:00'
+ Weekdays = @(0, 1, 2, 3, 4, 5, 6) # every day
+ }
Verbose = $true # run the worker with -v style tracing
}
diff --git a/Dockerfile b/Dockerfile
index 6187388..8ee49f0 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -12,7 +12,7 @@ ENV USMT_MODE=Demo \
POWERSHELL_TELEMETRY_OPTOUT=1 \
POWERSHELL_UPDATECHECK=Off \
DOTNET_CLI_TELEMETRY_OPTOUT=1 \
- TZ=UTC
+ TZ=America/Chicago
WORKDIR /app
COPY . /app
diff --git a/README.md b/README.md
index d230966..30f1373 100644
--- a/README.md
+++ b/README.md
@@ -160,8 +160,9 @@ docker compose up -d --build # http://localhost:8080
On **Coolify**, create a resource from this repository using the *Dockerfile* (or *Docker
Compose*) build pack, set the exposed port to **8080**, and attach a domain. The container
-reseeds the simulated fleet on every start and needs no volumes or secrets. Set `TZ` to show
-times in your timezone.
+reseeds the simulated fleet on every start and needs no volumes or secrets. Times are shown in
+`TZ` (default `America/Chicago`); set it to your own timezone. The demo worker runs
+6 AM to 9 PM every day (`Demo.BusinessHours` in the config) and sleeps overnight.
### Demo (local PowerShell 7)
diff --git a/USMT-Worker.ps1 b/USMT-Worker.ps1
index 74666c3..dcf4660 100755
--- a/USMT-Worker.ps1
+++ b/USMT-Worker.ps1
@@ -364,8 +364,8 @@ function Get-NextRunTime {
$cycleSeconds = if ($config.IsDemo) { $config.Demo.CycleSeconds } else { $config.Worker.CycleSeconds }
$nextRun = $now.AddSeconds($cycleSeconds)
- $bh = $config.Worker.BusinessHours
- if ($config.IsDemo -or -not $bh.Enabled) { return $nextRun }
+ $bh = if ($config.IsDemo) { $config.Demo.BusinessHours } else { $config.Worker.BusinessHours }
+ if (-not $bh -or -not $bh.Enabled) { return $nextRun }
$startTime = [timespan]::Parse($bh.Start)
$endTime = [timespan]::Parse($bh.End)
diff --git a/docker-compose.yml b/docker-compose.yml
index 234c07f..94ee94c 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -8,6 +8,6 @@ services:
restart: unless-stopped
environment:
- USMT_MODE=Demo
- - TZ=UTC # set your timezone, e.g. America/New_York
+ - TZ=America/Chicago # timezone for every time shown (any IANA name, e.g. America/New_York)
ports:
- "8080:8080"
diff --git a/web/demo/demo.js b/web/demo/demo.js
index fce7161..81edbf0 100644
--- a/web/demo/demo.js
+++ b/web/demo/demo.js
@@ -623,8 +623,10 @@
const secs = (new Date() - started) / 1000;
w([]);
w([[`Cycle Complete in ${secs.toFixed(1)}s.`, 'Gray']]);
- const next = new Date(Date.now() + 45000);
- w([[`Next Cycle Scheduled For: ${psStamp(next)}`, 'DarkGray']]);
+ // the real worker's schedule (it sleeps outside the demo's business hours)
+ const ws = await serverGet('/Worker-State.json');
+ const nextText = ws && ws.Status === 'Cooldown' && ws.NextCycle ? ws.NextCycle : psStamp(new Date(Date.now() + 45000));
+ w([[`Next Cycle Scheduled For: ${nextText}`, 'DarkGray']]);
w([]); w([]);
if (!s.tracked) await ensureOwnRoster();
diff --git a/web/jump.js b/web/jump.js
index 256eb06..d06dd8b 100644
--- a/web/jump.js
+++ b/web/jump.js
@@ -163,12 +163,19 @@
sheet.appendChild(list);
}
- const ends = el('div', 'xf-actions');
- const top = el('button', 'xf-cancel', '↑ Top'); top.type = 'button';
- top.onclick = () => { close(); window.scrollTo({ top: 0, behavior: 'smooth' }); };
- const bottom = el('button', 'xf-cancel', '↓ Bottom'); bottom.type = 'button';
- bottom.onclick = () => { close(); window.scrollTo({ top: document.documentElement.scrollHeight, behavior: 'smooth' }); };
- ends.appendChild(top); ends.appendChild(bottom);
+ // Top / Middle / Bottom (same bold arrows as Card fields)
+ const ends = el('div', 'xf-actions jump-ends');
+ const svg = d => '';
+ const end = (icon, label, y) => {
+ const b = el('button', 'xf-cancel jump-end'); b.type = 'button';
+ b.innerHTML = svg(icon); b.appendChild(el('span', null, label));
+ b.onclick = () => { close(); window.scrollTo({ top: y(), behavior: 'smooth' }); };
+ ends.appendChild(b);
+ };
+ const maxY = () => document.documentElement.scrollHeight - window.innerHeight;
+ end('', 'Top', () => 0);
+ end('', 'Middle', () => Math.round(maxY() / 2));
+ end('', 'Bottom', () => maxY());
sheet.appendChild(ends);
document.body.appendChild(backdrop);
@@ -190,7 +197,8 @@
function init() {
btn = el('button', 'jump-btn');
btn.type = 'button'; btn.title = 'Jump to a device or section'; btn.setAttribute('aria-label', 'Jump to a device or section');
- btn.innerHTML = 'Jump';
+ // up + down arrows, drawn like the Card fields arrows
+ btn.innerHTML = 'Jump';
btn.onclick = open;
btn.hidden = true;
document.body.appendChild(btn);
diff --git a/web/layout.css b/web/layout.css
index 3f02224..9b70d7c 100644
--- a/web/layout.css
+++ b/web/layout.css
@@ -567,7 +567,10 @@ a.nav-brand:focus-visible { outline: 2px solid #3498db; outline-offset: 3px; }
font: inherit; font-weight: 700; font-size: 0.9em; cursor: pointer; box-shadow: 0 6px 18px rgba(0,0,0,0.35);
}
.jump-btn[hidden] { display: none; }
-.jump-btn svg { width: 18px; height: 18px; }
+.jump-btn svg { width: 26px; height: 18px; }
+.jump-ends { gap: 8px; }
+.jump-end { display: inline-flex; align-items: center; justify-content: center; gap: 6px; font-weight: 600; }
+.jump-end svg { width: 17px; height: 17px; flex: none; }
.jump-sheet .jump-search { margin: 8px 12px; font-size: 16px; }
.jump-results, .jump-sections { overflow-y: auto; padding: 0 8px; }
.jump-results { max-height: 38vh; }