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; }