Polish: nav fits at every desktop width, Custom View list values, phone docs, README
This commit is contained in:
1 parent
a5c0a5210e
commit
e2125c760a
7 files changed
+69
-16
No files matched your search
@@ -13,7 +13,7 @@ beyond PowerShell.
|
|||||||
> simulated 200-device fleet. Open the **Terminals** panel at the bottom of the page to watch
|
> simulated 200-device fleet. Open the **Terminals** panel at the bottom of the page to watch
|
||||||
> them work.
|
> them work.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Why it exists
|
## Why it exists
|
||||||
|
|
||||||
@@ -28,6 +28,11 @@ a 1,300-device scope.
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
- **Overview**: the landing page, built to drive a refresh's open issues to zero. Counters for
|
||||||
|
offline sources, not started, *Bad Client* and *Broken WMI* tags, devices missing from AD /
|
||||||
|
SCCM / DNS, low disk, backup errors and undocumented apps; overall progress; devices per
|
||||||
|
Backup Status; a building x floor map of what's left; and a calendar of the deployment-date
|
||||||
|
tags showing how far each day's devices have got. Every number opens the devices behind it.
|
||||||
- **Tracked Devices**: technicians register devices under tags (a name, a date batch, a
|
- **Tracked Devices**: technicians register devices under tags (a name, a date batch, a
|
||||||
department). The worker only sweeps tagged devices. Tags can be flagged *Skip USMT* for
|
department). The worker only sweeps tagged devices. Tags can be flagged *Skip USMT* for
|
||||||
inventory-only tracking, and `SOURCE, DEST` lines create manual pairs for hardware exceptions.
|
inventory-only tracking, and `SOURCE, DEST` lines create manual pairs for hardware exceptions.
|
||||||
@@ -43,21 +48,35 @@ a 1,300-device scope.
|
|||||||
- **Missing software**: compares installed software on source and destination, filters out the
|
- **Missing software**: compares installed software on source and destination, filters out the
|
||||||
baseline image using a pattern list, groups multi-part products into suites, and lists the
|
baseline image using a pattern list, groups multi-part products into suites, and lists the
|
||||||
specialty apps not yet on the new device.
|
specialty apps not yet on the new device.
|
||||||
- **Device details**: AD OU and groups, hardware, disk, RAM, client health, location,
|
- **Device details**: AD OU and groups, hardware, disk, RAM, monitors, client health,
|
||||||
department, users. Shown for both sides of a migration.
|
location, department, users, for both sides of a migration. A blank says why: *Not in AD*,
|
||||||
- **Custom View**: build your own table from any field, with drag-to-reorder, resizable and
|
*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 tracked device (completed pairs and destinations included) is pinged
|
||||||
|
hourly during business hours and at midnight, so each one shows when it was last online and
|
||||||
|
how many days it has been offline.
|
||||||
|
- **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.
|
||||||
|
- **Custom View**: build your own table from any field (including days offline, last online,
|
||||||
|
found in AD/SCCM/DNS and monitors), with drag-to-reorder, resizable and Excel-style
|
||||||
filterable columns. Saved per browser.
|
filterable columns. Saved per browser.
|
||||||
- **Workbench**: personal "stacks" of devices for the cart in front of you.
|
- **Workbench**: personal "stacks" of devices for the cart in front of you.
|
||||||
- **Knowledge base**: Markdown articles with an editor, image manager, PDF export, badges and
|
- **Knowledge base**: Markdown articles with an editor, image manager, badges, alerts and
|
||||||
click-to-copy links. Renaming an image rewrites every article that links to it. Articles
|
click-to-copy links. Each article has its own address (`/docs/Adobe Acrobat Pro`); the list
|
||||||
named after an app are linked from that app in the software view.
|
is searchable, counted and split into *Applications* and *Troubleshooting & Guides*; PDF
|
||||||
|
export and print. Renaming an image rewrites every article that links to it. Articles named
|
||||||
|
after an app are linked from that app in the software view.
|
||||||
- **Business-hours scheduling**: the worker cycles every few minutes during working hours,
|
- **Business-hours scheduling**: the worker cycles every few minutes during working hours,
|
||||||
sleeps overnight, and makes a midnight "pitstop" for the daily software refresh.
|
sleeps overnight, and makes a midnight "pitstop" for the daily software refresh.
|
||||||
*Force Update* and *Force Full Refresh* skip the wait.
|
*Force Update* and *Force Full Refresh* skip the wait. Newly tracked devices are swept and
|
||||||
|
published first; the sync dialog shows how long each kind of device (SCCM association,
|
||||||
|
manual pair, completed snapshot...) took.
|
||||||
|
|
||||||
| | |
|
| | |
|
||||||
|---|---|
|
|---|---|
|
||||||
|  |  |
|
|  |  |
|
||||||
|
|  |  |
|
||||||
|  |  |
|
|  |  |
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
@@ -78,12 +97,16 @@ refresh" buttons drop flag files that the worker watches for.
|
|||||||
| `Config\usmt-dash.psd1` | Every environment-specific setting |
|
| `Config\usmt-dash.psd1` | Every environment-specific setting |
|
||||||
| `web\index.html` | The dashboard (single-file HTML/JS, about 4,500 lines) |
|
| `web\index.html` | The dashboard (single-file HTML/JS, about 4,500 lines) |
|
||||||
| `web\demo\demo.js` | Terminal panel, plus the demo-mode browser sandbox |
|
| `web\demo\demo.js` | Terminal panel, plus the demo-mode browser sandbox |
|
||||||
|
| `web\overview.js` | Overview page (issue counters, progress, locations, deployment calendar) |
|
||||||
|
| `web\device-info.js` | Shared device helpers: missing-data reasons, presence, snapshot markers |
|
||||||
|
| `web\docs.js` | Knowledge base: permalinks, search, categories, PDF / print |
|
||||||
|
|
||||||
### API
|
### API
|
||||||
|
|
||||||
| Method & path | Purpose |
|
| Method & path | Purpose |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `GET /` and static files | Dashboard, assets, `Data\` state files, `Documentation\` |
|
| `GET /` and static files | Dashboard, assets, `Data\` state files, `Documentation\` |
|
||||||
|
| `GET /docs/<title>` | The dashboard, opened on that knowledge-base article |
|
||||||
| `GET /get-config` | Mode flags for the front end |
|
| `GET /get-config` | Mode flags for the front end |
|
||||||
| `GET /get-logs?source=server\|worker&after=<id>` | Tail of the service console output (terminal panel) |
|
| `GET /get-logs?source=server\|worker&after=<id>` | Tail of the service console output (terminal panel) |
|
||||||
| `POST /force-update` · `POST /force-full` | Wake the worker now (full = also refresh software/inventory caches) |
|
| `POST /force-update` · `POST /force-full` | Wake the worker now (full = also refresh software/inventory caches) |
|
||||||
@@ -172,8 +195,9 @@ different CMDB, PDQ, ...), add `Providers\<Name>.ps1`, implement these functions
|
|||||||
| `Get-DiskSpace <name>` | `@{ Total = '238 GB'; Free = '80 GB' }` |
|
| `Get-DiskSpace <name>` | `@{ Total = '238 GB'; Free = '80 GB' }` |
|
||||||
| `Get-LogInfo <name> <logName>` | Latest USMT progress log line (via the shared parser) |
|
| `Get-LogInfo <name> <logName>` | Latest USMT progress log line (via the shared parser) |
|
||||||
| `Get-DeviceAssociations <name>` | Objects with `SourceComputer`, `DestinationComputer`, `MigrationStatus` (0/1/2) |
|
| `Get-DeviceAssociations <name>` | Objects with `SourceComputer`, `DestinationComputer`, `MigrationStatus` (0/1/2) |
|
||||||
| `Get-DeviceInventory <name> <session>` | Hashtable of detail fields (OU, Groups, Model, RAM, Location, ...) |
|
| `Get-DeviceInventory <name> <session>` | Hashtable of detail fields (OU, Groups, Model, RAM, MonitorCount, Monitors, Location, ...), plus `InAD` / `InSCCM` (`$true` / `$false`) |
|
||||||
| `Get-InstalledSoftware <name>` | Rows of `Name`, `Version`, `Publisher`, `Source`, `InstallDate` |
|
| `Get-InstalledSoftware <name>` | Rows of `Name`, `Version`, `Publisher`, `Source`, `InstallDate` |
|
||||||
|
| `Get-DeviceLastSeen <name> <session>` *(optional)* | When the device was last active, used to backfill "last online" the first time it's tracked |
|
||||||
|
|
||||||
`Providers\Demo.ps1` is a complete working example that needs no infrastructure.
|
`Providers\Demo.ps1` is a complete working example that needs no infrastructure.
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 186 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 138 KiB |
+1
-1
@@ -125,7 +125,7 @@
|
|||||||
const d = daysOffline(name);
|
const d = daysOffline(name);
|
||||||
if (d === undefined) return { text: WAITING, state: 'waiting' };
|
if (d === undefined) return { text: WAITING, state: 'waiting' };
|
||||||
if (d === null) return { text: 'Never seen', state: 'value' };
|
if (d === null) return { text: 'Never seen', state: 'value' };
|
||||||
if (d === 0) return { text: presence(name).online ? 'Online' : '0 days', state: 'value' };
|
if (d === 0) return { text: presence(name).online ? 'Online' : '< 1 day', state: 'value' };
|
||||||
return { text: d + (d === 1 ? ' day' : ' days'), state: 'value' };
|
return { text: d + (d === 1 ? ' day' : ' days'), state: 'value' };
|
||||||
}
|
}
|
||||||
if (key === 'LastOnline') {
|
if (key === 'LastOnline') {
|
||||||
|
|||||||
+8
-1
@@ -385,7 +385,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
wrap('openDoc', () => { renderSidebar(); decorate(); }, name => { setUrl(permalink(name), true); });
|
wrap('openDoc', () => {
|
||||||
|
renderSidebar(); decorate();
|
||||||
|
// Phones and tablets stack the list above the article: bring the article into view
|
||||||
|
if (window.matchMedia('(max-width: 1100px)').matches) {
|
||||||
|
const bar = document.getElementById('docToolbar');
|
||||||
|
if (bar) window.scrollTo({ top: window.scrollY + bar.getBoundingClientRect().top - (parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--nav-h')) || 60) - 10, behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
}, name => { setUrl(permalink(name), true); });
|
||||||
wrap('formatCodeBlocks', decorate);
|
wrap('formatCodeBlocks', decorate);
|
||||||
wrap('setDocMode', mode => {
|
wrap('setDocMode', mode => {
|
||||||
const sel = document.getElementById('docCategory');
|
const sel = document.getElementById('docCategory');
|
||||||
|
|||||||
+5
-4
@@ -3451,8 +3451,9 @@
|
|||||||
function getCustomCellValue(row, col) {
|
function getCustomCellValue(row, col) {
|
||||||
const blank = v => (v === undefined || v === null || v === "") ? "N/A" : v;
|
const blank = v => (v === undefined || v === null || v === "") ? "N/A" : v;
|
||||||
if (col.kind === "apiKey") return blank(row[col.key]);
|
if (col.kind === "apiKey") return blank(row[col.key]);
|
||||||
if (col.kind === "adv") return DeviceInfo.field(row["Source Device"], col.key).text;
|
// multi-line inventory values (groups, monitors, disk) read as a comma list in a cell
|
||||||
if (col.kind === "advDst") return DeviceInfo.field(row["Destination Device"], col.key).text;
|
if (col.kind === "adv") return DeviceInfo.field(row["Source Device"], col.key).text.replace(/<br\s*\/?>/gi, ", ");
|
||||||
|
if (col.kind === "advDst") return DeviceInfo.field(row["Destination Device"], col.key).text.replace(/<br\s*\/?>/gi, ", ");
|
||||||
if (col.kind === "special") return getCustomSpecialty(row["Source Device"]);
|
if (col.kind === "special") return getCustomSpecialty(row["Source Device"]);
|
||||||
if (col.kind === "migration") {
|
if (col.kind === "migration") {
|
||||||
if (col.fn === "space") return (row["Source Free Space"] && row["Source Free Space"] !== "N/A") ? row["Source Free Space"] + " / " + row["Source Total Space"] : "N/A";
|
if (col.fn === "space") return (row["Source Free Space"] && row["Source Free Space"] !== "N/A") ? row["Source Free Space"] + " / " + row["Source Total Space"] : "N/A";
|
||||||
@@ -4315,7 +4316,7 @@
|
|||||||
const forceBtn = document.getElementById('forceSyncBtn');
|
const forceBtn = document.getElementById('forceSyncBtn');
|
||||||
const forceFullBtn = document.getElementById('forceFullBtn');
|
const forceFullBtn = document.getElementById('forceFullBtn');
|
||||||
|
|
||||||
btn.innerText = `Data Updated At: ${globalDataLastUpdated}`;
|
btn.innerHTML = `<span class="st-pre">Data Updated At: </span>${globalDataLastUpdated}`;
|
||||||
|
|
||||||
if (state.Status === "Cooldown") {
|
if (state.Status === "Cooldown") {
|
||||||
document.getElementById('syncStatusText').innerText = "System is Idle (On Cooldown)";
|
document.getElementById('syncStatusText').innerText = "System is Idle (On Cooldown)";
|
||||||
@@ -4376,7 +4377,7 @@
|
|||||||
if (payload.LastUpdated && payload.Devices) {
|
if (payload.LastUpdated && payload.Devices) {
|
||||||
apiData = payload.Devices;
|
apiData = payload.Devices;
|
||||||
globalDataLastUpdated = payload.LastUpdated;
|
globalDataLastUpdated = payload.LastUpdated;
|
||||||
btn.innerText = `Data Updated At: ${globalDataLastUpdated}`;
|
btn.innerHTML = `<span class="st-pre">Data Updated At: </span>${globalDataLastUpdated}`;
|
||||||
} else {
|
} else {
|
||||||
apiData = payload;
|
apiData = payload;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -449,3 +449,24 @@ table.sync-bd th { color: var(--text-muted); font-weight: 600; }
|
|||||||
table.sync-bd tbody tr { cursor: help; }
|
table.sync-bd tbody tr { cursor: help; }
|
||||||
.sync-bd-phases { margin-top: 6px; color: var(--text-muted); line-height: 1.4; }
|
.sync-bd-phases { margin-top: 6px; color: var(--text-muted); line-height: 1.4; }
|
||||||
#syncModal .modal-content { max-width: 460px !important; }
|
#syncModal .modal-content { max-width: 460px !important; }
|
||||||
|
|
||||||
|
/* Desktop nav between the hamburger breakpoint and wide screens: tighten so the links,
|
||||||
|
DEMO pill, theme toggle and sync status all fit on one line */
|
||||||
|
@media (min-width: 1101px) and (max-width: 1600px) {
|
||||||
|
.top-nav .nav-btn { padding: 8px 7px; font-size: 0.9em; }
|
||||||
|
.top-nav h2 { margin-right: 8px; font-size: 1.25em; }
|
||||||
|
.status-btn { padding: 8px 10px; }
|
||||||
|
}
|
||||||
|
@media (min-width: 1101px) and (max-width: 1450px) {
|
||||||
|
.st-pre { display: none; }
|
||||||
|
.status-btn::before { content: 'Updated '; color: rgba(255,255,255,0.65); }
|
||||||
|
}
|
||||||
|
@media (min-width: 1101px) and (max-width: 1250px) {
|
||||||
|
.status-btn::before { content: none; }
|
||||||
|
.top-nav .nav-btn { padding: 8px 5px; font-size: 0.86em; }
|
||||||
|
.nav-logo { margin-right: 6px; }
|
||||||
|
}
|
||||||
|
@media (min-width: 1101px) and (max-width: 1180px) {
|
||||||
|
.nav-logo { display: none; }
|
||||||
|
.top-nav .nav-btn { padding: 8px 4px; }
|
||||||
|
}
|
||||||
Reference in new issue
Block a user