Undocumented apps: per-device filter and wider device column; presence for Skip USMT

- Source Devices in the undocumented-apps list filters by device name, like
  the software columns; Devices and the action column take less room.
- Drop the one-click documented pick (Documented + Only ticked does it).
- Stage legend underline spans label and number.
- Device details title stays on one line; the name truncates instead.
- Presence sweep also pings devices under Skip USMT tags.
This commit is contained in:
Johan Sandoval committed 2026-10-06 20:49:58 +00:00
1 parent f72e39892c
commit 9c21567f6d
7 files changed
+31 -35

No files matched your search

+3 -2
View File
@@ -52,8 +52,9 @@
# A task-sequence log modified within this window counts as "active"
ActiveLogHours = 24
# Completed pairs skip network checks in the sweep, so they're pinged this often to keep
# "last online" / "days offline" current (every other device is pinged every cycle).
# Completed pairs and Skip USMT devices skip network checks in the sweep, so they're
# pinged this often to keep "last online" / "days offline" current (every other device
# is pinged every cycle).
# Cycles only run in business hours plus the midnight pitstop: 60 = hourly, plus midnight.
PresenceSweepMinutes = 60
}
+2 -2
View File
@@ -53,8 +53,8 @@ a 1,300-device scope.
*Not in SCCM*, *No DNS record*, or *Waiting for worker...* if it hasn't been looked up yet.
The dialog refreshes in place when the worker finishes a cycle.
- **Presence**: every ping is recorded, so each device shows when it was last online and how
many days it has been offline. Completed pairs, which skip network checks in the sweep, are
pinged hourly during business hours and at midnight to keep theirs current.
many days it has been offline. Completed pairs and Skip USMT devices, which skip network checks
in the sweep, are pinged hourly during business hours and at midnight to keep theirs current.
- **Snapshots**: the last free space and scanstate / loadstate lines seen while a device was
reachable are kept with their timestamps, and shown for completed migrations and offline
devices instead of blanks.
+8 -7
View File
@@ -735,27 +735,28 @@ while ($true) {
$sweepSeconds = $phaseWatch.Elapsed.TotalSeconds
$phaseWatch.Restart()
# --- PRESENCE SWEEP: completed pairs ---
# Every other device is pinged by the sweep itself on every cycle. Completed pairs skip
# their network checks, so they're pinged here: hourly during business hours (cycles only
# run then) and on the midnight pitstop.
# --- PRESENCE SWEEP: completed pairs and Skip USMT devices ---
# Every other device is pinged by the sweep itself on every cycle. Completed pairs and
# devices under a Skip USMT tag skip network checks, so they're pinged here: hourly during
# business hours (cycles only run then) and on the midnight pitstop.
$presenceCount = 0
$presenceDue = ((Get-Date) - $global:LastPresenceSweep).TotalMinutes -ge ($presenceMinutes - 0.5) -or $runFullSoftwareSweep
if ($presenceDue) {
$completedDevs = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
foreach ($r in $deviceTable) {
if ($r.Pairing -ne "Cached") { continue }
if ($r.Pairing -ne "Cached" -and $r.Pairing -ne "Skipped") { continue }
foreach ($n in @($r."Source Device", $r."Destination Device")) { if ($n -and $n -ne "N/A") { $null = $completedDevs.Add($n) } }
}
$toPing = @($completedDevs | Where-Object { -not $global:PingedThisCycle.Contains($_) } | Sort-Object)
Write-Host "`n[Presence Sweep: pinging $($toPing.Count) device(s) from completed migrations]" -ForegroundColor Cyan
Write-Host "`n[Presence Sweep: pinging $($toPing.Count) completed / Skip USMT device(s)]" -ForegroundColor Cyan
$i = 0
foreach ($n in $toPing) {
$i++
Update-WorkerState "Presence Sweep..." $i $toPing.Count $globalLastDuration "Calculating..."
$null = Test-Device $n -Session $session
}
# Cached / skipped rows pick up the fresh result
# Completed rows pick up the fresh result (Skip USMT rows keep "Skipped" in the ping
# column; their presence shows in the device details and Custom View)
foreach ($r in $deviceTable) {
if ($r.Pairing -eq "Cached") { Set-RowPresence $r -Source -Destination }
}
+1 -15
View File
@@ -22,7 +22,7 @@
const cardsQuery = window.matchMedia('(max-width: 1100px)');
const sheetQuery = window.matchMedia('(max-width: 600px)');
const LIST_COLUMNS = /^(tags|missing software|specialty software)$/i;
const LIST_COLUMNS = /^(tags|missing software|specialty software|source devices)$/i;
const SKIP_COLUMNS = /(^|\s)apps$/i; // "Apps" button columns have nothing to filter
const BLANK = '(Blanks)';
const NO_READING = '(No reading)'; // space column: offline / cached / not checked
@@ -596,20 +596,6 @@
};
pick('Documented', isDocumented);
pick('Undocumented', v => !isDocumented(v));
// One click: devices whose apps ALL have an article ("easy" devices)
if (setMode) {
const b = el('button', 'xf-pick xf-pick-main'); b.type = 'button';
b.textContent = 'Devices with only documented apps';
b.title = 'Show devices where every app in this column has a documentation article (and devices with none)';
b.onclick = () => {
search.value = '';
opts.forEach(o => { o.row.hidden = false; o.cb.checked = o.v === 'None' || (o.v !== BLANK && isDocumented(o.v)); });
setMode('subset');
sync();
ok.click();
};
pickBar.appendChild(b);
}
}
search.oninput = () => {
const q = search.value.trim().toLowerCase();
+7 -6
View File
@@ -476,16 +476,17 @@ a.nav-brand:hover h2 { color: #d6eaf8; }
a.nav-brand:focus-visible { outline: 2px solid #3498db; outline-offset: 3px; }
/* Device details title: name on the left, Simple / Advanced View toggle on the right, one line */
#detailsTitle { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; padding-right: 10px; min-width: 0; }
#detailsTitle .dt-name { min-width: 0; overflow-wrap: anywhere; }
#detailsTitle { display: flex; align-items: center; gap: 12px; flex-wrap: nowrap; padding-right: 10px; min-width: 0; }
/* the name gives way (ellipsis) so the toggle never drops to a second line */
#detailsTitle .dt-name { flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#detailsTitle .dt-name .copy-row-btn { vertical-align: middle; }
#detailsTitle .dt-toggle { flex: 0 0 auto; }
#detailsTitle .dt-toggle {
margin-left: auto; cursor: pointer; padding: 6px 12px; border-radius: 4px; background: #34495e; color: #fff;
border: none; font-size: 0.55em; font-weight: bold; white-space: nowrap; transition: background 0.2s;
}
#detailsTitle .dt-toggle:hover { background: #3498db; }
@media (max-width: 700px) { #detailsTitle { padding-right: 32px; } #detailsTitle .dt-toggle { margin-left: 0; } }
@media (max-width: 700px) { #detailsTitle { font-size: 1.15em; gap: 8px; } }
/* Excel filter: three list modes, and the one-click "only documented" pick */
/* Excel filter: three list modes */
.xf-seg3 .xf-seg-btn { font-size: 0.78em; padding-left: 4px; padding-right: 4px; }
.xf-pick.xf-pick-main { flex-basis: 100%; background: #27ae60; border-color: #27ae60; color: #fff; font-weight: 600; }
.xf-pick.xf-pick-main:hover { background: #219150; border-color: #219150; }
+5 -1
View File
@@ -24,7 +24,8 @@
.ov-tile.ov-selected { border: 2px solid #3498db !important; background: rgba(52,152,219,0.12); box-shadow: 0 0 0 3px rgba(52,152,219,0.25); transform: none; position: relative; }
.ov-showing { display: inline-block; margin-top: 6px; font-size: 0.72em; font-weight: 700; color: #fff; background: #3498db; border-radius: 999px; padding: 2px 8px; }
.ov-bar-row.ov-selected { background: rgba(52,152,219,0.16); box-shadow: inset 3px 0 0 #3498db; }
.ov-legend-item.ov-selected { color: var(--text-main); text-decoration: underline; text-decoration-color: #3498db; text-decoration-thickness: 2px; text-underline-offset: 4px; }
.ov-legend-item.ov-selected { color: var(--text-main); box-shadow: inset 0 -2px 0 #3498db; }
.ov-legend-item.ov-selected .ov-key { box-shadow: none; }
.ov-heat-cell.ov-selected, .ov-heat-sum.ov-selected { box-shadow: 0 0 0 2px var(--bg-card), 0 0 0 5px #3498db !important; }
.ov-heat-sum.ov-selected { background: rgba(52,152,219,0.16); }
@@ -160,3 +161,6 @@ table.ov-heat td { padding: 0; text-align: center; border: 0; background: none;
.ov-bar-row { grid-template-columns: 128px 1fr 32px; }
.ov-bar-label { font-size: 0.82em; }
}
/* Undocumented apps list: room goes to the device names */
#ov-table td.ov-src-list { white-space: normal; overflow-wrap: anywhere; }
+5 -2
View File
@@ -604,14 +604,17 @@
const table = el('table', 'master-table ov-table');
table.id = 'ov-table';
const thead = el('thead'), tr = el('tr');
['App', 'Devices', 'Sources with it', ''].forEach(h => tr.appendChild(el('th', null, h)));
// Source Devices is a list column: its filter lists every device name, like the software columns
[['App', '26%'], ['Devices', '90px'], ['Source Devices', ''], ['', '130px']].forEach(([h, w]) => { const th = el('th', null, h); if (w) th.style.width = w; tr.appendChild(th); });
thead.appendChild(tr); table.appendChild(thead);
const tb = el('tbody');
items.forEach(({ app, devs }) => {
const row = el('tr');
const a = el('td'); a.appendChild(el('b', null, app)); row.appendChild(a);
row.appendChild(el('td', null, String(devs.length)));
const d = el('td', null, devs.slice(0, 6).join(', ') + (devs.length > 6 ? ` +${devs.length - 6} more` : '')); d.title = devs.join(', ');
const d = el('td', 'ov-src-list', devs.slice(0, 8).join(', ') + (devs.length > 8 ? ` +${devs.length - 8} more` : ''));
d.title = devs.join(', ');
d.setAttribute('data-val', devs.join(', ')); // the filter and copy see every name
row.appendChild(d);
const act = el('td', 'no-copy');
const btn = el('button', 'ov-btn', 'Write article'); btn.type = 'button';