Discovery against the demo account on whp01 surfaced several inaccuracies: - Cache is Valkey (Redis wire-compatible), not Redis or Memcached. No Memcached is offered as a separate service. - Site Monitoring is the sidebar label (not 'AI Monitor'). - 'Add a domain' has no Primary/Add-on distinction. - Sites form: 'Container Type' (not 'Site type'), Number of Containers (1-10 for horizontal scaling), CPU per Container (default 0.25), Memory per Container (default 256MB), SSL inline on the same form. - Backups: default retention 5 days / 10 backups; on-demand + scheduled; S3 backup targets are visible and configurable. - Email: per-domain settings live behind 'Setup Instructions' on the Email page; mail server hostname is on the Dashboard (per-server, e.g. mail01.cloud-hosting.io), not per-domain. Also reworked the screenshot pipeline: - New shots.config.ts targets the real index.php?page=... URLs - Added redactSensitive() step that runs before each screenshot to swap server names, IPs, mail hostnames, and demo-user-isms with neutral placeholders. This keeps docs portable across the fleet. - Hides .brand-full and .navbar-text (top-bar server identifier and Welcome greeting). - Captured 9 real WHP screenshots; removed stale placeholders.
54 lines
1.7 KiB
TypeScript
54 lines
1.7 KiB
TypeScript
/**
|
|
* One-shot peek script: log in, navigate to a specific URL, screenshot it.
|
|
* Usage: PEEK_URL=... npx tsx tools/screenshots/peek.ts
|
|
*/
|
|
import { chromium, type Page } from 'playwright';
|
|
import { mkdir } from 'node:fs/promises';
|
|
import { resolve, dirname } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const OUT_DIR = resolve(__dirname, '../../src/assets/screenshots/whp');
|
|
|
|
function need(name: string): string {
|
|
const v = process.env[name];
|
|
if (!v) throw new Error(`missing env: ${name}`);
|
|
return v;
|
|
}
|
|
|
|
const BASE = need('WHP_BASE');
|
|
const USER = need('WHP_USER');
|
|
const PASS = need('WHP_PASS');
|
|
const PEEK = need('PEEK_URL');
|
|
const ID = process.env.PEEK_ID ?? '_peek';
|
|
|
|
async function login(page: Page): Promise<void> {
|
|
await page.goto(`${BASE}/login.php`, { waitUntil: 'domcontentloaded' });
|
|
await page.fill('input[name="user"]', USER);
|
|
await page.fill('input[name="password"]', PASS);
|
|
await page.click('button[type="submit"]');
|
|
await page.waitForLoadState('networkidle');
|
|
}
|
|
|
|
async function main(): Promise<void> {
|
|
const browser = await chromium.launch({ headless: true });
|
|
const ctx = await browser.newContext({ ignoreHTTPSErrors: true, viewport: { width: 1440, height: 900 } });
|
|
const page = await ctx.newPage();
|
|
try {
|
|
await login(page);
|
|
await page.goto(`${BASE}${PEEK}`);
|
|
await page.waitForLoadState('networkidle');
|
|
await mkdir(OUT_DIR, { recursive: true });
|
|
const out = resolve(OUT_DIR, `${ID}.png`);
|
|
await page.screenshot({ path: out, fullPage: true });
|
|
console.log(`captured ${PEEK} -> ${out}`);
|
|
} finally {
|
|
await browser.close();
|
|
}
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error(err);
|
|
process.exit(1);
|
|
});
|