diff --git a/src/assets/screenshots/whp/whp-dns-add-domain.png b/src/assets/screenshots/whp/whp-dns-add-domain.png new file mode 100644 index 0000000..9ffbe3f Binary files /dev/null and b/src/assets/screenshots/whp/whp-dns-add-domain.png differ diff --git a/src/assets/screenshots/whp/whp-dns-add-record.png b/src/assets/screenshots/whp/whp-dns-add-record.png new file mode 100644 index 0000000..7cd24fe Binary files /dev/null and b/src/assets/screenshots/whp/whp-dns-add-record.png differ diff --git a/src/assets/screenshots/whp/whp-dns-bulk-actions.png b/src/assets/screenshots/whp/whp-dns-bulk-actions.png new file mode 100644 index 0000000..c308d73 Binary files /dev/null and b/src/assets/screenshots/whp/whp-dns-bulk-actions.png differ diff --git a/src/assets/screenshots/whp/whp-dns-records.png b/src/assets/screenshots/whp/whp-dns-records.png new file mode 100644 index 0000000..32c9ba9 Binary files /dev/null and b/src/assets/screenshots/whp/whp-dns-records.png differ diff --git a/src/assets/screenshots/whp/whp-domains.png b/src/assets/screenshots/whp/whp-domains.png index 745e37d..a21072d 100644 Binary files a/src/assets/screenshots/whp/whp-domains.png and b/src/assets/screenshots/whp/whp-domains.png differ diff --git a/src/content/docs/whp/how-to/add-a-domain.mdx b/src/content/docs/whp/how-to/add-a-domain.mdx index a75db4c..6521aa4 100644 --- a/src/content/docs/whp/how-to/add-a-domain.mdx +++ b/src/content/docs/whp/how-to/add-a-domain.mdx @@ -27,24 +27,25 @@ import Support from '~/content/partials/support-link.mdx'; -1. In the sidebar, click **Domains**. - ![WHP Domains page](~/assets/screenshots/whp/whp-domains.png) +1. In the sidebar, click **Domains & DNS**. You'll see a searchable list of the domains on your account. + ![WHP Domains & DNS page](~/assets/screenshots/whp/whp-domains.png) -2. Under **Add New Domain** on the left, type your domain (for example, `example.com`). Don't include `http://` or `www.` — just the bare domain. +2. Click **Add Domain** in the top right. In the dialog, type your domain (for example, `example.com`) — just the bare domain, with no `http://` or `www.` + ![Add Domain dialog](~/assets/screenshots/whp/whp-dns-add-domain.png) -3. Click **Add Domain**. +3. Click **Add Domain** to confirm. The domain appears in the list with a green **Active** status. -WHP creates the standard set of DNS records automatically for the new domain: +WHP creates a DNS zone with the standard set of records automatically for the new domain: - **A record** for the apex domain → your server's IP - **CNAME** for `www` → the apex - **NS records** for the nameservers - **MX record** → the mail server -- **TXT record** for SPF +- **TXT records** for SPF and DKIM -You can review and tweak any of these from the **DNS Management** panel on the right side of the Domains page (select the domain from the dropdown). +You can review and tweak any of these — click **Manage DNS** next to the domain to open the records editor. See [Manage DNS records](/whp/how-to/manage-dns-records/) for the full walkthrough. ## Point your DNS at us @@ -54,7 +55,7 @@ There are two paths depending on where the domain is registered: **Registered elsewhere.** At your registrar, do one of the following: -- Set the **nameservers** to the values shown on the Domains page (recommended — gives us full DNS control, easier to support), **or** +- Set the **nameservers** to the values shown in **Manage DNS** (the **NS** records) — recommended, as it gives us full DNS control and is easier to support — **or** - Keep your existing nameservers and add an **A record** pointing the domain to the IP shown on the **Dashboard** page under Server Information. ## Verify it worked @@ -76,6 +77,7 @@ Once DNS resolves, visiting your domain in a browser will reach WHP — though y ## Related +- [Manage DNS records](/whp/how-to/manage-dns-records/) - [Create a site](/whp/how-to/create-a-site/) ## Still stuck? diff --git a/src/content/docs/whp/how-to/backups.mdx b/src/content/docs/whp/how-to/backups.mdx index de9cec6..44d105c 100644 --- a/src/content/docs/whp/how-to/backups.mdx +++ b/src/content/docs/whp/how-to/backups.mdx @@ -2,7 +2,7 @@ title: Backups description: Run on-demand and scheduled backups of your sites and databases, and confirm they're succeeding. sidebar: - order: 4 + order: 5 --- import { Steps, Aside } from '@astrojs/starlight/components'; diff --git a/src/content/docs/whp/how-to/create-a-site.mdx b/src/content/docs/whp/how-to/create-a-site.mdx index ddebef1..d15d7cf 100644 --- a/src/content/docs/whp/how-to/create-a-site.mdx +++ b/src/content/docs/whp/how-to/create-a-site.mdx @@ -2,7 +2,7 @@ title: Create a site description: Spin up a containerized site on a domain you've added to WHP. sidebar: - order: 2 + order: 3 --- import { Steps, Aside } from '@astrojs/starlight/components'; diff --git a/src/content/docs/whp/how-to/create-an-email-account.mdx b/src/content/docs/whp/how-to/create-an-email-account.mdx index 7b8ef4e..f4b7da4 100644 --- a/src/content/docs/whp/how-to/create-an-email-account.mdx +++ b/src/content/docs/whp/how-to/create-an-email-account.mdx @@ -2,7 +2,7 @@ title: Create an email account description: Add a mailbox on one of your domains and connect your email client. sidebar: - order: 3 + order: 4 --- import { Steps, Aside } from '@astrojs/starlight/components'; diff --git a/src/content/docs/whp/how-to/manage-dns-records.mdx b/src/content/docs/whp/how-to/manage-dns-records.mdx new file mode 100644 index 0000000..65ed41c --- /dev/null +++ b/src/content/docs/whp/how-to/manage-dns-records.mdx @@ -0,0 +1,113 @@ +--- +title: Manage DNS records +description: View, add, edit, and delete DNS records for your domains using the Domains & DNS records editor in WHP. +sidebar: + order: 2 +--- + +import { Steps, Aside } from '@astrojs/starlight/components'; +import SignIn from '~/content/partials/signing-in.mdx'; +import Support from '~/content/partials/support-link.mdx'; + +When we host your DNS, WHP gives you a full records editor — add, edit, or remove **A**, **CNAME**, **MX**, **TXT**, and other records yourself, no support ticket needed. + +## Before you start + +- A domain already added to your account. If you haven't done that yet, [add a domain](/whp/how-to/add-a-domain/) first. +- We must be running DNS for the domain (its nameservers point at us). If your DNS lives at another provider, make these changes there instead. +- A couple of minutes. Record changes apply quickly on our side, but can take up to 24 hours to propagate worldwide. + +## Sign in to WHP + + + +## Open the records editor + + + +1. In the sidebar, click **Domains & DNS**. + ![WHP Domains & DNS page](~/assets/screenshots/whp/whp-domains.png) + +2. Find your domain in the list and click **Manage DNS**. + ![DNS records editor for a domain](~/assets/screenshots/whp/whp-dns-records.png) + + + +Each domain starts with a standard zone created automatically when the domain was added: + +| Type | What it's for | +| --- | --- | +| **A** | Points the apex domain at your server's IP. | +| **CNAME** | Aliases like `www` and `autoconfig` to the right host. | +| **MX** | Routes mail for the domain to our mail server. | +| **TXT** | SPF and DKIM records that help your mail pass authentication. | +| **NS** | The nameservers that are authoritative for the domain. | +| **SRV** | Service records such as mail autodiscovery. | + + + +## Add a record + + + +1. Click **Add Record**. A new, editable row appears at the top of the table. + ![Inline Add Record row](~/assets/screenshots/whp/whp-dns-add-record.png) + +2. Fill in the row: + - **Type** — choose the record type (A, AAAA, CNAME, MX, TXT, NS, PTR, SRV, CAA, SSHFP, or TLSA). + - **Name** — the host the record applies to. Use `@` for the domain itself, or a subdomain like `blog` for `blog.example.com`. + - **Content** — the value: an IP for an A record, a hostname for a CNAME, and so on. + - **Prio** — only used by record types that need a priority (like MX). Leave it blank otherwise. + - **TTL** — how long resolvers may cache the record, in seconds. The default of `300` (5 minutes) is fine for most records. + +3. Click **Save**. The record joins the list immediately. (Click **Cancel** to discard the row without saving.) + + + +## Edit or delete a record + +In the **Actions** column on the right of each row: + +- The **pencil** icon opens the record for editing in place. Change any field, then save. +- The **trash** icon deletes the record. Deletions take effect right away, so double-check before you remove anything. + +## Find a record quickly + +If a domain has a lot of records, use the **All types** dropdown above the table to filter by a single record type — for example, show only **MX** records while you sort out mail. + +## Work with several records at once + +Tick the checkboxes on the left of one or more rows to reveal the bulk-action bar: + +![Bulk actions on selected DNS records](~/assets/screenshots/whp/whp-dns-bulk-actions.png) + +- **Change TTL** — set the same TTL on every selected record. +- **Enable/Disable** — toggle records on or off without deleting them (handy for temporarily parking a record). +- **Delete** — remove all selected records at once. +- **Clear** — clear your selection (this does *not* delete anything). + +## Verify it worked + +DNS changes apply on our side within moments, but resolvers elsewhere may keep serving the old answer until the record's TTL expires (up to 24 hours for unfamiliar records). + +- Run `dig example.com +short` (or `dig blog.example.com A +short`) from a terminal and confirm you see the value you just set. +- Or use a web tool like [whatsmydns.net](https://www.whatsmydns.net/) to watch propagation across regions. + +## Troubleshooting + +**My change isn't showing up yet.** Resolvers cache records for the length of their TTL. Wait for the TTL to pass, then clear your local DNS cache and check again with `dig`. + +**There's no Manage DNS button for my domain.** We're not running DNS for it — its nameservers point somewhere else. Make the change at your current DNS provider, or [point the domain's nameservers at us](/whp/how-to/add-a-domain/#point-your-dns-at-us) first. + +**Email stopped working after I edited records.** Restore the original **MX** and SPF/DKIM **TXT** records. If you're not sure what they should be, open a support ticket (see below) and we'll put them back. + +## Related + +- [Add a domain](/whp/how-to/add-a-domain/) +- [Create an email account](/whp/how-to/create-an-email-account/) + +## Still stuck? + + diff --git a/src/content/docs/whp/how-to/switching-site-backend.mdx b/src/content/docs/whp/how-to/switching-site-backend.mdx index 53e4681..0a27d61 100644 --- a/src/content/docs/whp/how-to/switching-site-backend.mdx +++ b/src/content/docs/whp/how-to/switching-site-backend.mdx @@ -2,7 +2,7 @@ title: Switching your site's backend description: Change the web engine (container type) running a site — standard PHP/FPM or the premium LiteSpeed/OpenLiteSpeed tier. sidebar: - order: 5 + order: 6 --- import { Steps, Aside } from '@astrojs/starlight/components'; diff --git a/tools/screenshots/capture-dns.ts b/tools/screenshots/capture-dns.ts new file mode 100644 index 0000000..2874b4c --- /dev/null +++ b/tools/screenshots/capture-dns.ts @@ -0,0 +1,140 @@ +/** + * DNS capture — the reworked "Domains & DNS" area. + * + * Captures, as the demo customer (so the demo domain's real zone shows): + * - whp-domains.png the Domains & DNS list (searchable table) + * - whp-dns-add-domain.png the Add Domain modal + * - whp-dns-records.png the per-domain DNS Records editor + * - whp-dns-add-record.png the inline "Add Record" editor row + * - whp-dns-bulk-actions.png the bulk-select toolbar + * + * Viewport-only (1440x900), redacted for our multi-server fleet: server / + * mail / nameserver hostnames and IPs become neutral placeholders, while the + * brand demo domain (whp-demo.anhh.co) is kept visible on purpose. + * + * Read-only: opens modals and ticks checkboxes for the screenshot, never + * saves, deletes, or submits anything. + */ +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 DOMAIN = process.env.WHP_DEMO_DOMAIN ?? 'whp-demo.anhh.co'; + +const HIDE_CSS = `.navbar-text, .brand-full { 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'); +} + +/** + * Neutralise fleet-identifying text before the screenshot. The brand demo + * domain (anhh.co) is intentionally preserved; everything that names a + * specific server, mail host, nameserver, or IP is swapped for a placeholder. + */ +async function redact(page: Page) { + await page.addStyleTag({ content: HIDE_CSS }); + await page.evaluate(() => { + const swaps: [RegExp, string][] = [ + [/ns[12]\.whp\d+(-[a-z0-9]+)?\.cloud-hosting\.io/gi, 'ns..cloud-hosting.io'], + [/whp\d+(-[a-z0-9]+)?\.cloud-hosting\.io/gi, '.cloud-hosting.io'], + [/mail\d+\.cloud-hosting\.io/gi, '.cloud-hosting.io'], + [/WHP\d+(-[A-Z0-9]+)?\b/g, ''], + [/whp\d+(-[a-z0-9]+)?\b/gi, ''], + // Public IPv4 (skip RFC1918 — those read fine as generic examples) + [/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, ''], + [/demo-user/g, 'your-username'], + ]; + // Text nodes + 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; + } + // Input values (the inline Add Record / TTL fields) + document.querySelectorAll('input').forEach((el) => { + if (el.type === 'password' || !el.value) return; + let v = el.value; + for (const [re, rep] of swaps) v = v.replace(re, rep); + if (v !== el.value) el.value = v; + }); + }); +} + +async function shot(page: Page, id: string) { + await page.waitForTimeout(400); + await redact(page); + const path = resolve(OUT_DIR, `${id}.png`); + await page.screenshot({ path, fullPage: false }); + console.log(`captured ${id} -> ${path}`); +} + +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); + + // 1. Domains & DNS list + await page.goto(`${BASE}/index.php?page=domains`, { waitUntil: 'networkidle' }); + await shot(page, 'whp-domains'); + + // 2. Add Domain modal + await page.locator('button:has-text("Add Domain"), a:has-text("Add Domain")').first().click(); + await page.waitForTimeout(600); + await shot(page, 'whp-dns-add-domain'); + await page.keyboard.press('Escape').catch(() => {}); + await page.waitForTimeout(300); + + // 3. DNS Records editor for the demo domain + await page.goto(`${BASE}/index.php?page=domains&domain=${DOMAIN}`, { waitUntil: 'networkidle' }); + await shot(page, 'whp-dns-records'); + + // 4. Inline Add Record row + await page.locator('button:has-text("Add Record"), a:has-text("Add Record")').first().click(); + await page.waitForTimeout(500); + await shot(page, 'whp-dns-add-record'); + // Cancel the inline add row so the next shot is clean + await page.locator('button:has-text("Cancel")').first().click().catch(() => {}); + await page.waitForTimeout(400); + + // 5. Bulk-select toolbar (tick two record rows) + const rowChecks = page.locator('table tbody input[type=checkbox]'); + const n = await rowChecks.count(); + if (n >= 2) { await rowChecks.nth(0).check(); await rowChecks.nth(1).check(); } + else if (n === 1) { await rowChecks.nth(0).check(); } + await page.waitForTimeout(500); + await shot(page, 'whp-dns-bulk-actions'); + } finally { + await browser.close(); + } +} + +main().catch((err) => { console.error(err); process.exit(1); });