Live updates during a cycle; Filter & sort dropdown on phones; layout polish

- Worker publishes the rows swept so far every few seconds (LivePublishSeconds)
  with a DataVersion stamp; the dashboard reloads on each new version, so
  tables, the Overview and open dialogs update mid-cycle. Shared files are
  written to a temp file and swapped in, so readers never see half a file.
- Phones / tablets: one Filter & sort button opens a column list instead of a
  row of chips, on every table.
- Details: a wrapped Simple / Advanced toggle sits on the left.
- Card fields: fixed-height dialog; field list and add list share it evenly.
This commit is contained in:
Johan Sandoval committed 2026-10-06 21:33:29 +00:00
1 parent 7242f8f8af
commit 9dc2d88726
8 files changed
+149 -31

No files matched your search

+43 -12
View File
@@ -31,6 +31,10 @@ $loadLogName = if ($config.SCCM.LoadStateLog) { $config.SCCM.LoadStateLog
# How often every tracked device (cached and destination devices included) is pinged to keep
# "last online" current. Cycles only run in business hours plus the midnight pitstop, so this
# works out to an hourly presence sweep during the day and one at midnight.
# While a cycle runs, rows are published this often (merged with last cycle's rows for devices
# not swept yet), so the dashboard updates live instead of only when the cycle ends
$livePublishSeconds = if ($config.IsDemo) { $config.Demo.LivePublishSeconds } else { $config.Worker.LivePublishSeconds }
if (-not $livePublishSeconds) { $livePublishSeconds = 10 }
$presenceMinutes = if ($config.IsDemo) { $config.Demo.PresenceSweepMinutes } else { $config.Worker.PresenceSweepMinutes }
if (-not $presenceMinutes) { $presenceMinutes = 60 }
@@ -50,6 +54,7 @@ $globalLastUpdated = "Never"
$globalNextRun = "Calculating..."
$globalLastDuration = "N/A"
$global:LastBreakdown = $null
$global:DataVersion = ""
$global:LastPresenceSweep = [datetime]::MinValue
# --- FUNCTIONS ---
@@ -64,9 +69,10 @@ function Update-WorkerState {
NextCycle = $nextRun
LastDuration = $duration
LastCycle = $global:LastBreakdown
DataVersion = $global:DataVersion
} | ConvertTo-Json -Compress -Depth 5
[System.IO.File]::WriteAllText($statePath, $json, [System.Text.Encoding]::UTF8)
Write-SharedText $statePath $json
} catch {}
}
@@ -91,6 +97,21 @@ function Set-LogInfoLine {
function Get-IsoNow { return (Get-Date).ToString("o") }
# Files the dashboard reads while the worker writes them: write a temp file, then swap it in,
# so a reader never sees a half-written file. Falls back to a direct write if the swap fails
# (e.g. Windows refusing to replace a file another process has open).
function Write-SharedText([string]$path, [string]$text) {
$tmp = "$path.tmp"
try {
[System.IO.File]::WriteAllText($tmp, $text, [System.Text.Encoding]::UTF8)
if (Test-Path -LiteralPath $path) { [System.IO.File]::Replace($tmp, $path, $null) }
else { [System.IO.File]::Move($tmp, $path) }
} catch {
[System.IO.File]::WriteAllText($path, $text, [System.Text.Encoding]::UTF8)
if (Test-Path -LiteralPath $tmp) { Remove-Item -LiteralPath $tmp -Force -ErrorAction SilentlyContinue }
}
}
function ConvertTo-Hashtable($obj) {
$h = @{}
if ($obj -is [hashtable]) { foreach ($k in $obj.Keys) { $h[$k] = $obj[$k] } }
@@ -111,8 +132,7 @@ function Read-JsonTable([string]$path) {
}
function Write-JsonFile([string]$path, $obj, [int]$depth = 6) {
$json = ConvertTo-Json -InputObject $obj -Depth $depth -Compress
[System.IO.File]::WriteAllText($path, $json, [System.Text.Encoding]::UTF8)
Write-SharedText $path (ConvertTo-Json -InputObject $obj -Depth $depth -Compress)
}
# --- PROVIDER (data source adapter) ---
@@ -216,6 +236,14 @@ function Use-Snapshot {
}
}
# Data.json + a version stamp the dashboard compares to know there's something new to load
function Publish-Data {
param($rows)
$global:DataVersion = (Get-Date).ToString("o")
$payload = [PSCustomObject]@{ LastUpdated = (Get-Date).ToString("MM/dd/yy hh:mm tt"); DataVersion = $global:DataVersion; Devices = $rows }
Write-SharedText $jsonPath ($payload | ConvertTo-Json -Depth 6)
}
# Every row has the same columns; these are filled in as the device is processed
function New-DeviceRow {
param([string]$sName, [string]$dName, [string]$tags, [string]$pairing)
@@ -499,6 +527,7 @@ while ($true) {
if ($newSources.Count -gt 0) { Write-Host "--- Commencing Data Sweep ($totalDevices Devices, $($newSources.Count) new first) ---" -ForegroundColor White }
else { Write-Host "--- Commencing Data Sweep ($totalDevices Devices) ---" -ForegroundColor White }
$publishWatch = [System.Diagnostics.Stopwatch]::StartNew()
foreach ($deviceName in $sourceDevices) {
$currentDeviceCount++
$deviceWatch = [System.Diagnostics.Stopwatch]::StartNew()
@@ -722,14 +751,19 @@ while ($true) {
$timing[$category].Count++
$timing[$category].Ms += $deviceWatch.Elapsed.TotalMilliseconds
# Newly tracked devices are done: publish them now rather than at the end of the cycle
if ($newSources.Count -gt 0 -and $knownSources.Count -gt 0 -and $currentDeviceCount -eq $newSources.Count) {
Write-Host " -> [PRIORITY] $($newSources.Count) new device(s) swept. Publishing early..." -ForegroundColor Green
# Live updates: newly tracked devices are published as soon as they're done, then the
# rows swept so far every few seconds (devices not reached yet keep last cycle's row)
$newDone = ($newSources.Count -gt 0 -and $currentDeviceCount -eq $newSources.Count)
$remaining = $totalDevices - $currentDeviceCount
if ($remaining -gt 0 -and ($newDone -or $publishWatch.Elapsed.TotalSeconds -ge $livePublishSeconds)) {
if ($newDone) { Write-Host " -> [PRIORITY] $($newSources.Count) new device(s) swept. Publishing early..." -ForegroundColor Green }
elseif ($v) { Write-Host " -> [LIVE] Publishing $currentDeviceCount of $totalDevices swept device(s)..." -ForegroundColor DarkGray }
$interim = [System.Collections.Generic.List[object]]::new()
foreach ($r in $deviceTable) { $interim.Add($r) }
foreach ($dev in $knownSources) { if ($previousRows.ContainsKey($dev)) { $interim.Add($previousRows[$dev]) } }
foreach ($dev in $sourceDevices) { if (-not $processedSources.Contains($dev) -and $previousRows.ContainsKey($dev)) { $interim.Add($previousRows[$dev]) } }
Save-AdvancedCache
Write-JsonFile $jsonPath ([PSCustomObject]@{ LastUpdated = (Get-Date).ToString("MM/dd/yy hh:mm tt"); Devices = $interim })
Publish-Data $interim
$publishWatch.Restart()
}
}
$sweepSeconds = $phaseWatch.Elapsed.TotalSeconds
@@ -806,10 +840,7 @@ while ($true) {
if ($v) { Write-Host " -> Writing payload to Data.json..." -ForegroundColor DarkGray }
$globalLastUpdated = (Get-Date).ToString("MM/dd/yy hh:mm tt")
$exportPayload = [PSCustomObject]@{ LastUpdated = $globalLastUpdated; Devices = $deviceTable }
$jsonFinal = $exportPayload | ConvertTo-Json -Depth 6
[System.IO.File]::WriteAllText($jsonPath, $jsonFinal, [System.Text.Encoding]::UTF8)
Publish-Data $deviceTable
$saveSeconds = $phaseWatch.Elapsed.TotalSeconds
$cycleEnd = Get-Date