Files
shadowdao 5fdf55de7f docs: link Bichon to its open-source project
The archival-email page linked to https://anhonesthost.com/bichon/, which
does not exist. Point readers at the upstream open-source project instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 18:29:33 -07:00

117 lines
4.7 KiB
TypeScript

/**
* Traffic analytics capture — for the June 2026 platform-updates blog post.
*
* Captures, as the demo customer:
* - traffic-analytics-overview.png View Traffic landing: Yesterday's Snapshot,
* Top URLs / Bandwidth Consumers, Daily Totals
* - traffic-analytics-day-detail.png the per-day drill-down: hourly request graph,
* top pages by views, top pages by bandwidth
*
* Viewport 1440x900, deviceScaleFactor 2, fullPage:false. Redacts the fleet
* server strip ("WHP-01" / "Welcome, ...") and the version-number footer; keeps
* the brand demo domain (whp-demo.anhh.co) visible on purpose. Read-only.
*
* Output goes to /workspace/blog-assets (this is a blog image, not a KB page).
*/
import { chromium, type Page } from 'playwright';
import { mkdir } from 'node:fs/promises';
import { resolve } from 'node:path';
const OUT_DIR = '/workspace/blog-assets';
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');
// Hide the server-identifying navbar strip and the version footer.
const HIDE_CSS = `.navbar-text, .brand-full, .navbar-brand { visibility: hidden !important; }`;
async function login(page: Page) {
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 redact(page: Page) {
await page.addStyleTag({ content: HIDE_CSS });
await page.evaluate(() => {
const swaps: [RegExp, string][] = [
[/whp\d+(-[a-z0-9]+)?\.cloud-hosting\.io/gi, '<your-server>.cloud-hosting.io'],
[/WHP\d+(-[A-Z0-9]+)?\b/g, '<YOUR-SERVER>'],
[/whp\d+(-[a-z0-9]+)?\b/gi, '<your-server>'],
[/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, '<server-IP>'],
[/demo-user/g, 'your-username'],
// Strip the release/version identifier from the footer.
[/Web Hosting Panel\s*-\s*\d{4}\.\d{2}\.\d+/gi, 'Web Hosting Panel'],
[/\b\d{4}\.\d{2}\.\d+\b/g, ''],
];
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
const nodes: Text[] = [];
let n: Node | null = walker.nextNode();
while (n) { nodes.push(n as Text); n = walker.nextNode(); }
for (const node of nodes) {
let v = node.nodeValue ?? '';
for (const [re, rep] of swaps) v = v.replace(re, rep);
if (v !== node.nodeValue) node.nodeValue = v;
}
});
}
async function main() {
await mkdir(OUT_DIR, { recursive: true });
const browser = await chromium.launch({ headless: true });
const ctx = await browser.newContext({
ignoreHTTPSErrors: true,
viewport: { width: 1440, height: 900 },
deviceScaleFactor: 2,
});
const page = await ctx.newPage();
try {
await login(page);
// Resolve the demo site's traffic view via the "View Traffic" button.
await page.goto(`${BASE}/index.php?page=site-traffic`, { waitUntil: 'networkidle' });
await page.locator('a:has-text("View Traffic"), button:has-text("View Traffic")').first().click();
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
const trafficUrl = page.url();
// 1. Overview — clip to the main content card so the snapshot + daily totals
// frame nicely without the empty side gutters.
await redact(page);
await page.evaluate(() => window.scrollTo(0, 0));
await page.waitForTimeout(300);
await page.screenshot({ path: resolve(OUT_DIR, 'traffic-analytics-overview.png'), fullPage: false });
console.log('captured traffic-analytics-overview');
// 2. Day drill-down — click the 23rd (richest demo data), then clip to the
// "Breakdown for ..." card (hourly graph + top pages + bandwidth).
await page.locator('a:has-text("2026-06-23")').first().click();
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1500);
await redact(page);
const card = page.locator('#day-detail, .card:has-text("Breakdown for")').first();
await card.scrollIntoViewIfNeeded().catch(() => {});
await page.waitForTimeout(400);
if (await card.count()) {
await card.screenshot({ path: resolve(OUT_DIR, 'traffic-analytics-day-detail.png') });
} else {
await page.screenshot({ path: resolve(OUT_DIR, 'traffic-analytics-day-detail.png'), fullPage: false });
}
console.log('captured traffic-analytics-day-detail');
console.log('traffic url was', trafficUrl);
} finally {
await browser.close();
}
}
main().catch((err) => { console.error(err); process.exit(1); });