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:
1 parent
7242f8f8af
commit
9dc2d88726
8 files changed
+149
-31
No files matched your search
+43
-12
@@ -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
|
||||
|
||||
Reference in new issue
Block a user