3
0
mirror of https://github.com/snipe/snipe-it.git synced 2026-08-18 03:06:23 +00:00

Add specific tab to ad hoc

This commit is contained in:
snipe
2026-07-21 14:56:59 +01:00
parent d5388af0ee
commit b5d04ce1bc
2 changed files with 48 additions and 3 deletions

View File

@ -167,17 +167,22 @@ node .screenshotter/src/screenshotter.mjs --one /hardware/create --as assetmgr
# With a custom output name
node .screenshotter/src/screenshotter.mjs --one /licenses/5 --as licensemgr --name license-detail
# Shoot a specific tab on a view page
node .screenshotter/src/screenshotter.mjs --one /hardware/1 --tab licenses
node .screenshotter/src/screenshotter.mjs --one /hardware/1 --tab "components"
# Dark mode, no framing
COLOR_SCHEME=dark FRAME=false node .screenshotter/src/screenshotter.mjs --one /hardware --as admin
```
Ad-hoc shots land in `.screenshotter/screenshots/adhoc/{username}-{name}-{timestamp}.png`. The `adhoc/` directory is deliberately preserved across full walkthrough runs so historical one-off images stick around, and every ad-hoc shot carries a timestamp so repeated captures of the same URL never overwrite each other.
Ad-hoc shots land in `.screenshotter/screenshots/adhoc/{username}-{name}-{timestamp}.png`, or with `-tab-{slug}` in the filename when `--tab` is used. The `adhoc/` directory is deliberately preserved across full walkthrough runs so historical one-off images stick around, and every ad-hoc shot carries a timestamp so repeated captures of the same URL never overwrite each other.
Arguments:
- `--one <path>` (required) URL path to shoot, with or without a leading slash.
- `--as <username>` (default: `USERNAME` env, which defaults to `admin`) user to log in as. Any seeded user works: `admin`, `snipe`, `assetmgr`, `licensemgr`, `accessorymgr`, `consumablemgr`, `componentmgr`, `usermgr`, etc.
- `--name <slug>` (default: URL path with `/` replaced by `__`) filename slug. The timestamp is appended automatically.
- `--tab <label>` case-insensitive substring match against the visible Bootstrap tab labels on the target page. `--tab licenses` matches "Licenses" or "Licenses (5)" alike. If nothing matches, the run aborts with a list of available tabs so you can pick a valid one.
## What a full run produces

View File

@ -37,12 +37,13 @@
* Other sections' shots from prior runs are preserved (not wiped).
*
* Ad-hoc single-shot mode (skips the full walkthrough):
* node .screenshotter/src/screenshotter.mjs --one <path> [--as <username>] [--name <slug>]
* node .screenshotter/src/screenshotter.mjs --one <path> [--as <username>] [--name <slug>] [--tab <label>]
*
* Examples:
* node .screenshotter/src/screenshotter.mjs --one /hardware
* node .screenshotter/src/screenshotter.mjs --one /hardware/create --as assetmgr
* node .screenshotter/src/screenshotter.mjs --one /licenses/5 --as licensemgr --name license-detail
* node .screenshotter/src/screenshotter.mjs --one /hardware/1 --tab licenses
*
* DATA WARNING: this script captures whatever is currently rendering in
* the connected database, in full. Do not run against production,
@ -94,6 +95,7 @@ const {values: cli} = parseArgs({
as: {type: 'string'},
name: {type: 'string'},
section: {type: 'string'},
tab: {type: 'string'},
},
strict: false,
});
@ -659,7 +661,12 @@ if (ONE_SHOT) {
const asUsername = cli.as ?? USERNAME;
const uri = ONE_SHOT.replace(/^\//, '');
const baseName = cli.name ?? (uri === '' ? 'root' : uri.replace(/[/?&=]/g, '__'));
const outName = `adhoc/${asUsername}-${baseName}`;
// If --tab was requested, append its slug to the filename so the
// shot is self-identifying.
const tabSuffix = cli.tab
? '-tab-' + cli.tab.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')
: '';
const outName = `adhoc/${asUsername}-${baseName}${tabSuffix}`;
console.log(`→ logging in as ${asUsername}`);
await loginAs(asUsername, PASSWORD);
@ -668,6 +675,39 @@ if (ONE_SHOT) {
await page.goto(`${BASE_URL}/${uri}`, {waitUntil: 'domcontentloaded'});
await page.waitForLoadState('networkidle').catch(() => {});
await capPagination();
// If --tab was requested, find the matching Bootstrap tab and
// activate it before shooting. Case-insensitive substring match on
// the visible tab labels, so `--tab licenses` matches "Licenses"
// or "Licenses (5)". Fails loudly if nothing matches so the user
// isn't left staring at a "why did this shoot the wrong tab" shot.
if (cli.tab) {
const wanted = cli.tab.toLowerCase();
const tabs = await page.locator('.nav-tabs a[data-toggle="tab"]:visible').all();
let matched = false;
// Normalize labels so multiline tab text (icon + label + count
// wrapped across nodes) doesn't blow up the log line or trip
// the substring match on stray whitespace.
const norm = (s) => (s || '').replace(/\s+/g, ' ').trim();
for (const t of tabs) {
const label = norm(await t.textContent()).toLowerCase();
if (label.includes(wanted)) {
await t.click();
await page.waitForTimeout(400);
await capPagination();
matched = true;
console.log(` ↪ tab: "${label}"`);
break;
}
}
if (!matched) {
const labels = await Promise.all(tabs.map(async (t) => norm(await t.textContent())));
console.log(` ! tab "${cli.tab}" not found. Available: ${labels.filter(Boolean).join(', ') || '(none)'}`);
await browser.close();
process.exit(1);
}
}
await shot(outName);
const elapsedMs = performance.now() - RUN_STARTED_AT;