Jump to the middle; bold jump arrows; Chicago time; demo hours 6 AM-9 PM

- Jump sheet: Top / Middle / Bottom with the Card fields arrows; the Jump
  button uses the same bold arrows.
- Container timezone defaults to America/Chicago (TZ in Dockerfile/compose).
- Demo worker follows its own business hours (Demo.BusinessHours, default
  06:00-21:00 every day) with the midnight pitstop; the simulated cycle in
  the browser shows the real worker's next run.
This commit is contained in:
Johan Sandoval committed 2026-10-06 22:16:58 +00:00
1 parent b0f700928e
commit 4cc810adf5
8 files changed
+39 -16

No files matched your search

+9
View File
@@ -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
}
+1 -1
View File
@@ -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
+3 -2
View File
@@ -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)
+2 -2
View File
@@ -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)
+1 -1
View File
@@ -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"
+4 -2
View File
@@ -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();
+15 -7
View File
@@ -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 => '<svg viewBox="0 0 16 16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round">' + d + '</svg>';
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('<path d="M8 13V3M3.5 7.5 8 3l4.5 4.5"/>', 'Top', () => 0);
end('<path d="M4.5 1.8 8 5l3.5-3.2M4.5 14.2 8 11l3.5 3.2M1.8 8h12.4"/>', 'Middle', () => Math.round(maxY() / 2));
end('<path d="M8 3v10M3.5 8.5 8 13l4.5-4.5"/>', '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 = '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3v18M12 3 7.5 7.5M12 3l4.5 4.5M12 21l-4.5-4.5M12 21l4.5-4.5" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg><span>Jump</span>';
// up + down arrows, drawn like the Card fields arrows
btn.innerHTML = '<svg viewBox="0 0 24 16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M6 13V3M1.5 7.5 6 3l4.5 4.5"/><path d="M18 3v10M13.5 8.5 18 13l4.5-4.5"/></svg><span>Jump</span>';
btn.onclick = open;
btn.hidden = true;
document.body.appendChild(btn);
+4 -1
View File
@@ -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; }