docs(admin): rewrite + extend WHP super-admin section from real UI
All checks were successful
Build and deploy / deploy (push) Successful in 24s
Verified every page against the live admin panel on whp01 (read-only). Five existing articles rewritten; one new article added; customer-facing backups article updated to match server reality. Article changes - overview: super admin = the root user only (no UI to add another); WHMCS portal route doesn't apply for admin; accurate sidebar map of every admin-only section; customer backups don't cover server config (multiple locations, not just /etc — full-server backup is the right safety net). - server-settings: walked all six tabs (System / Services / Mail / DNS / Network & SSL / Security); clarified that host Apache + PHP-FPM serve the WHP control panel, not customer sites; that MySQL runs as a container so host MySQL config is client-facing; that custom container needs are met by publishing a custom Docker image (linked to repo.anhonesthost.net/cloud-hosting-platform/ for examples). - coraza-waf: real Firing rules / CRS catalog / Activity tabs; global WAF mode pill (off/detect/enforce); per-rule + per-host overrides; Ask AI link; security.db source-of-truth + SIGHUP reload note. - site-monitoring: split into the three actual admin pages — AI Monitor dashboard, Issues, Ignore Rules — with stat tiles + health-check timeline + ignore-rule AND-semantics. - user-management: account types corrected to full / domain_dns / mail_dns (verified in web-files/pages/user-management.php:26); system users are protected against deletion (verified is_protected_user in web-files/libs/usermgmt.php:697); delegated users are admin-editable (not read-only); suspension page is served by haproxy's 503 errorfile (verified in haproxy-manager-base/haproxy_tarpit_config.txt:31) so troubleshooting points at haproxy reload / container logs. - new admin/backups: customer-data backups vs full-server backups; auto-backups only run with a default target; how to add global vs per-customer targets; how to fire on-demand backups for any user; troubleshooting around missing targets / failed test / disk pressure. - how-to/backups (customer): aside about default-target requirement; new section explaining what full-server backups cover vs customer backups (managed plans + VDS covered by AnHonestHost; elsewhere is the server operator's responsibility). New components / tooling - admin-signin partial: 'sign in directly at :8443 as root'. - Head.astro override + medium-zoom: click-to-zoom lightbox on every article image; auto-reattaches after Starlight client navigation. - capture-admin.ts: read-only Playwright capture for admin docs with multi-pass redaction (server hostnames, mail server, customer domains, customer usernames in table cells, IPs except RFC1918 and public resolvers, password/key/token/secret/api input values, plus LiteLLM URLs, model names, JWT/sk-prefix API keys, root → admin).
3
.gitignore
vendored
@@ -30,3 +30,6 @@ GEMINI.md
|
||||
|
||||
# local-only screenshot creds
|
||||
tools/screenshots/.env
|
||||
|
||||
# Admin capture-script discovery artifacts (scratch only)
|
||||
tools/screenshots/_admin-*-links.json
|
||||
|
||||
@@ -24,6 +24,9 @@ export default defineConfig({
|
||||
// Inline-SVG brand mark + "Knowledge Base" label.
|
||||
// Inlining lets the SVG's currentColor follow the active theme.
|
||||
SiteTitle: './src/components/SiteTitle.astro',
|
||||
// Wraps Starlight's default <Head> to add a click-to-zoom lightbox
|
||||
// (medium-zoom) that targets article content images.
|
||||
Head: './src/components/Head.astro',
|
||||
},
|
||||
customCss: [
|
||||
'@fontsource-variable/inter',
|
||||
@@ -60,7 +63,7 @@ export default defineConfig({
|
||||
},
|
||||
{
|
||||
label: 'Admin',
|
||||
badge: { text: 'Draft', variant: 'caution' },
|
||||
// badge removed once content was verified against the real UI
|
||||
items: [{ autogenerate: { directory: 'whp/admin' } }],
|
||||
},
|
||||
],
|
||||
|
||||
7
package-lock.json
generated
@@ -13,6 +13,7 @@
|
||||
"@fontsource-variable/inter": "^5.2.8",
|
||||
"@fontsource-variable/jetbrains-mono": "^5.2.8",
|
||||
"astro": "^6.3.1",
|
||||
"medium-zoom": "^1.1.0",
|
||||
"sharp": "^0.34.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -4359,6 +4360,12 @@
|
||||
"integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==",
|
||||
"license": "CC0-1.0"
|
||||
},
|
||||
"node_modules/medium-zoom": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/medium-zoom/-/medium-zoom-1.1.0.tgz",
|
||||
"integrity": "sha512-ewyDsp7k4InCUp3jRmwHBRFGyjBimKps/AJLjRSox+2q/2H4p/PNpQf+pwONWlJiOudkBXtbdmVbFjqyybfTmQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/micromark": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz",
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"@fontsource-variable/inter": "^5.2.8",
|
||||
"@fontsource-variable/jetbrains-mono": "^5.2.8",
|
||||
"astro": "^6.3.1",
|
||||
"medium-zoom": "^1.1.0",
|
||||
"sharp": "^0.34.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
BIN
src/assets/screenshots/whp/admin-coraza.png
Normal file
|
After Width: | Height: | Size: 406 KiB |
BIN
src/assets/screenshots/whp/admin-ignore-rules.png
Normal file
|
After Width: | Height: | Size: 154 KiB |
BIN
src/assets/screenshots/whp/admin-issues.png
Normal file
|
After Width: | Height: | Size: 272 KiB |
BIN
src/assets/screenshots/whp/admin-monitor-admin.png
Normal file
|
After Width: | Height: | Size: 340 KiB |
BIN
src/assets/screenshots/whp/admin-srvset-system.png
Normal file
|
After Width: | Height: | Size: 209 KiB |
BIN
src/assets/screenshots/whp/admin-user-mgmt.png
Normal file
|
After Width: | Height: | Size: 200 KiB |
BIN
src/assets/screenshots/whp/admin-user-resources.png
Normal file
|
After Width: | Height: | Size: 304 KiB |
34
src/components/Head.astro
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
import Default from '@astrojs/starlight/components/Head.astro';
|
||||
---
|
||||
|
||||
<Default><slot /></Default>
|
||||
|
||||
<script>
|
||||
// Lightbox: click-to-zoom on article images. Loaded once per page; auto-reattaches
|
||||
// after Starlight client-side navigation.
|
||||
import mediumZoom from 'medium-zoom';
|
||||
import 'medium-zoom/dist/style.css';
|
||||
|
||||
const SELECTOR = '.sl-markdown-content img:not(.no-zoom)';
|
||||
let zoom: ReturnType<typeof mediumZoom> | null = null;
|
||||
|
||||
function refresh() {
|
||||
if (!zoom) {
|
||||
zoom = mediumZoom(SELECTOR, {
|
||||
background: 'rgba(10, 22, 40, 0.92)',
|
||||
margin: 32,
|
||||
});
|
||||
} else {
|
||||
zoom.detach();
|
||||
zoom.attach(SELECTOR);
|
||||
}
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', refresh, { once: true });
|
||||
} else {
|
||||
refresh();
|
||||
}
|
||||
document.addEventListener('astro:after-swap', refresh);
|
||||
</script>
|
||||
120
src/content/docs/whp/admin/backups.mdx
Normal file
@@ -0,0 +1,120 @@
|
||||
---
|
||||
title: Backups
|
||||
description: How WHP's automatic backups work, the default-target requirement, full-server backups vs customer data backups, and managing backup targets.
|
||||
sidebar:
|
||||
order: 6
|
||||
---
|
||||
|
||||
import { Aside } from '@astrojs/starlight/components';
|
||||
import SuperAdmin from '~/content/partials/super-admin-callout.mdx';
|
||||
import AdminSignIn from '~/content/partials/admin-signin.mdx';
|
||||
import Support from '~/content/partials/support-link.mdx';
|
||||
|
||||
<SuperAdmin />
|
||||
|
||||
WHP backs up **customer data** (sites and databases) when an admin has configured a default backup target. WHP does **not** back up the full server — that's a separate concern.
|
||||
|
||||
## Two different things called "backup"
|
||||
|
||||
It's worth being precise:
|
||||
|
||||
- **Customer data backups** — what WHP does. Site files (per user, per domain) and databases. Configured per server via backup targets. These start automatically once a default target exists.
|
||||
- **Full server backups** — backing up the host OS, `/etc`, container images, the WHP install itself, etc. **WHP does not do this.**
|
||||
|
||||
### Full server backups
|
||||
|
||||
| Where WHP runs | Who owns full-server backups |
|
||||
|---|---|
|
||||
| **Our Virtual Dedicated Server (VDS)** plans | Included. AnHonestHost snapshots the VDS at the platform level. |
|
||||
| **Anywhere else** | The server operator. WHP doesn't ship a full-server backup mechanism — you'll need to set up something at the OS / hypervisor level. |
|
||||
|
||||
If you're running WHP on your own infrastructure, plan accordingly. WHP's configuration isn't all in `/etc` — there are config and state files in the WHP install directory, in Docker volumes for the platform containers (HAProxy, MySQL, Postgres, Valkey, the WAF), and in service-specific paths elsewhere on the host. The safest approach is a **full-server backup** (image snapshot or filesystem-level backup) rather than trying to enumerate paths.
|
||||
|
||||
## Sign in as super admin
|
||||
|
||||
<AdminSignIn />
|
||||
|
||||
## How customer auto-backups start
|
||||
|
||||
**Customer auto-backups don't run until at least one default backup target exists.** A fresh WHP install has no targets and no schedule — every user's backup status is "no targets available" until an admin sets one up.
|
||||
|
||||
Once a default target is configured, the platform begins automatic daily backups for every customer with sites or databases. Customers see their own backup history under the [Backups](/whp/how-to/backups/) page in their account.
|
||||
|
||||
## The Backup Management page
|
||||
|
||||
Sidebar → **Backups**. The admin view of this page mirrors the customer view, with extra controls:
|
||||
|
||||
- **Stat tiles** — Total Backups, Total Size, Sites, Databases. Server-wide totals across every account.
|
||||
- **Create New Backup** — fires an on-demand backup. Admin form adds a **User** dropdown so you can backup any customer's account, not just your own.
|
||||
- **Backup Targets** — the table of destinations available on the server. The **Global** column distinguishes shared targets from per-account targets.
|
||||
|
||||
### Backup targets
|
||||
|
||||
Each target row has:
|
||||
|
||||
- **Name** — your label for the destination.
|
||||
- **Type** — S3 (and other supported types) — built-in support for S3-compatible storage (AWS S3, Cloudflare R2, MinIO, etc.).
|
||||
- **Connection** — the endpoint URL and bucket / path.
|
||||
- **Retention** — how long backups are kept (default 5 days).
|
||||
- **Max Backups** — cap on the number of snapshots retained (default 10).
|
||||
- **Global** — `Yes` if every account can use this target, `No` if it's bound to a single account.
|
||||
- **Actions** — **Test** (verify credentials and write a probe object), **Edit**, **Delete**.
|
||||
|
||||
### Adding a global backup target
|
||||
|
||||
Click **+ Add Backup Target** and fill in:
|
||||
|
||||
1. **Name** — descriptive label.
|
||||
2. **Type** — pick S3 (or whichever storage backend you want).
|
||||
3. **Endpoint URL** — for non-AWS S3 (Cloudflare R2, MinIO, Wasabi, etc.), point at the provider's endpoint.
|
||||
4. **Bucket / path**.
|
||||
5. **Access key / Secret** — the credentials WHP will use.
|
||||
6. **Retention / Max Backups** — server-wide defaults for any account that uses this target.
|
||||
7. **Global** — leave **on** so every account can use it as their default destination.
|
||||
|
||||
Click **Test** before saving to confirm WHP can reach the bucket. A good target round-trips a probe object in under a second.
|
||||
|
||||
<Aside type="tip">
|
||||
Keep one global target as the default. Customers who want their own offsite (e.g. their own S3 bucket) can add a per-account, non-global target — visible in the same table for the admin, but only usable by that one customer.
|
||||
</Aside>
|
||||
|
||||
### Per-customer (non-global) targets
|
||||
|
||||
Customers can add their own backup targets from the customer-side Backups page. Those show up here with **Global: No** plus a note linking them to the owning account. Admins can edit or delete those on the customer's behalf when they need help.
|
||||
|
||||
### Triggering an on-demand backup for a user
|
||||
|
||||
In the **Create New Backup** form:
|
||||
|
||||
1. Pick the customer in the **User** dropdown.
|
||||
2. Pick a **Backup Type** (Sites / Databases / both).
|
||||
3. Pick a **Backup Target**.
|
||||
4. **Start Backup**. Progress is visible in the run history below.
|
||||
|
||||
This is the right path when a customer asks for a fresh backup right before a risky migration.
|
||||
|
||||
## Verifying customer backups are succeeding
|
||||
|
||||
The Total Backups and Total Size tiles climb over time on a healthy server. If they sit flat:
|
||||
|
||||
- Confirm at least one target exists with **Global: Yes**.
|
||||
- Open the run history (lower on the page) and look for failed entries — the error message usually points at credentials or quota.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**No backups are running for any customer.** Confirm at least one **Global: Yes** target exists and that its **Test** button returns success. Without a global default, the scheduler doesn't fire.
|
||||
|
||||
**One target is failing.** Click **Test** on the target row. The most common causes are rotated credentials, an incorrect endpoint URL (R2 / MinIO often need an explicit endpoint different from AWS's default), or the bucket lifecycle policy deleting backups before the retention window.
|
||||
|
||||
**Customer says their backup is too old.** Check **Max Backups** on the target — if it's lower than their backup cadence × retention window, older backups get pruned.
|
||||
|
||||
**Backup ran but `tar` step failed mid-stream.** Disk pressure on the host is a common cause. Check **Disk Usage** (admin sidebar) and consider raising the target's retention so fewer backups stack up on the host before upload.
|
||||
|
||||
## Related
|
||||
|
||||
- [Backups](/whp/how-to/backups/) — the customer-facing side.
|
||||
- [Server settings & services](/whp/admin/server-settings/) — including Backup Upload host service.
|
||||
|
||||
## Still stuck?
|
||||
|
||||
<Support />
|
||||
@@ -1,95 +1,108 @@
|
||||
---
|
||||
title: Coraza WAF rules
|
||||
description: Tune the Coraza web-application firewall rules running in front of your sites — toggle modes, mute false positives, audit blocks.
|
||||
description: Set the global WAF mode, tune individual rules, audit blocked requests, and add per-host overrides.
|
||||
sidebar:
|
||||
order: 3
|
||||
badge:
|
||||
text: Draft
|
||||
variant: caution
|
||||
---
|
||||
|
||||
import { Aside } from '@astrojs/starlight/components';
|
||||
import SuperAdmin from '~/content/partials/super-admin-callout.mdx';
|
||||
import Draft from '~/content/partials/draft-callout.mdx';
|
||||
import SignIn from '~/content/partials/signing-in.mdx';
|
||||
import AdminSignIn from '~/content/partials/admin-signin.mdx';
|
||||
import Support from '~/content/partials/support-link.mdx';
|
||||
|
||||
<SuperAdmin />
|
||||
|
||||
<Draft />
|
||||
[Coraza](https://coraza.io/) is an open-source web-application firewall (WAF) that runs as a sidecar in front of your sites. The **Coraza Rules** page in the admin sidebar gives you a UI to set the global mode, tune individual rules, and audit blocked requests — all without rebuilding the sidecar image.
|
||||
|
||||
[Coraza](https://coraza.io/) is an open-source web-application firewall (WAF). It runs as a sidecar in front of your sites and inspects incoming requests against rule families like OWASP Core Rule Set v4 (CRS). The admin WHP gives you a UI to manage the rules and audit what's been blocked.
|
||||

|
||||
|
||||
## Three operating modes
|
||||
## Global WAF mode
|
||||
|
||||
The WAF runs in one of three modes, set per-site or server-wide:
|
||||
A coloured pill at the top of the page shows the **Global WAF mode** with a **change** link:
|
||||
|
||||
- **Off.** No inspection. Requests pass through untouched.
|
||||
- **Detect-only.** Inspect every request and log matches, but pass them through. Use this when rolling out the WAF for the first time or when validating a rule change.
|
||||
- **Enforce.** Inspect every request and **block** any that match an enforcing rule. This is the production setting once you've validated detect-only.
|
||||
- **Off** — no inspection. Requests pass untouched.
|
||||
- **Detect** — inspect and log matches; do not block. Use during a roll-out or while validating a rule change.
|
||||
- **Enforce** — inspect and block anything that matches an enforcing rule.
|
||||
|
||||
The WAF is fail-open: if the Coraza sidecar itself is unhealthy, traffic still flows.
|
||||
The WAF is **fail-open**: if the Coraza sidecar is itself unhealthy, traffic still flows.
|
||||
|
||||
## Sign in to WHP
|
||||
## Sign in as super admin
|
||||
|
||||
<SignIn />
|
||||
<AdminSignIn />
|
||||
|
||||
## Where it lives
|
||||
## The three tabs
|
||||
|
||||
Sidebar → **Security → Coraza Rules**. The page lists rule families (CRS 901, 911, 913, 920–922, 930–934, 941–944, 949, 950–956, 959, 980) and per-rule controls.
|
||||
### Firing rules
|
||||
|
||||
The default view. Each row is a rule that has matched at least one request in the selected time window (toggle **Last 24h** or **Last 7d** at top right).
|
||||
|
||||
Columns:
|
||||
|
||||
- **Rule ID** — the CRS or custom rule identifier. **view** opens the rule definition. **Ask AI** opens an explanation of what this rule catches.
|
||||
- **Hits** — how many times the rule fired in the time window.
|
||||
- **Top hosts** — the top customer domains that triggered this rule (with per-host hit counts).
|
||||
- **Top URIs** — the most common request paths that triggered it (helpful for distinguishing scans from real traffic).
|
||||
- **State** — per-rule override of the global mode. Options: `(default → enforce)`, `(default → detect)`, `(default → score)`, `off`. Picking anything other than `(default → …)` overrides the global mode for that rule.
|
||||
- **Per-host** — opens a **Hosts (N)** drawer to set per-host overrides for that rule.
|
||||
|
||||
<Aside type="tip">
|
||||
Rule changes apply on the next request. The page reloads the sidecar via SIGHUP after every save (~10ms — no traffic blip).
|
||||
</Aside>
|
||||
|
||||
### CRS catalog
|
||||
|
||||
The full OWASP Core Rule Set catalogue (v4 families: 901, 905, 911, 913, 920–922, 930–934, 941–944, 949, 950–956, 959, 980). Use this when you want to look up a rule that *hasn't* fired yet — for example, to pre-mute a rule you know will produce false positives on a specific app.
|
||||
|
||||
### Activity
|
||||
|
||||
A timeline / log of every WAF block in the audit window. Use it to:
|
||||
|
||||
- Confirm a customer report (cross-reference the **X-Request-Reference** UUID on the visitor-facing 403 page).
|
||||
- Spot bursts of activity from the same source IP.
|
||||
- Tune source-of-truth back-end queries — the audit is read from `security.db`.
|
||||
|
||||
## Common tasks
|
||||
|
||||
### Roll a new site onto the WAF
|
||||
|
||||
<Aside type="tip">
|
||||
Always start in **Detect-only** for a new site. Move to **Enforce** only after you've watched the logs and confirmed no legitimate traffic is being matched.
|
||||
</Aside>
|
||||
|
||||
1. Open **Security → Coraza Rules**.
|
||||
2. Find the site and set mode to **Detect-only**.
|
||||
3. Drive normal traffic for at least 24 hours.
|
||||
4. Open the **Audit log** and filter to that site. Confirm no legitimate request is matching an enforcing rule.
|
||||
5. Switch the site to **Enforce**.
|
||||
1. Set the rule's **State** to `(default → detect)` for that site via the Per-host drawer.
|
||||
2. Drive normal traffic for at least 24 hours.
|
||||
3. Open the **Activity** tab and filter to that host. Confirm no legitimate request is matching an enforcing rule.
|
||||
4. Flip the host to **enforce**.
|
||||
|
||||
### Mute a noisy rule
|
||||
|
||||
When a rule is firing on legitimate traffic for one site:
|
||||
1. Find the rule in **Firing rules**.
|
||||
2. Click **Per-host → Hosts (N)** to add a per-site mute, OR change the rule's **State** column to mute it globally.
|
||||
|
||||
1. Click the audit-log row to see the rule_id and the matched request.
|
||||
2. From the **Coraza Rules** page, find the rule by ID.
|
||||
3. Pick **Ignore for this site** (per-site mute) or **Ignore globally** (server-wide mute).
|
||||
4. Save. The rule stops firing on the next request.
|
||||
|
||||
Per-site is almost always the right scope. Use global mute sparingly — it weakens the WAF for every site.
|
||||
Prefer per-host. Global mute weakens the WAF for every site.
|
||||
|
||||
### Audit a block
|
||||
|
||||
Customer reports a request was wrongly blocked? The branded 403 page that visitors see includes an **X-Request-Reference** UUID. Cross-reference it:
|
||||
Customer reports a request was wrongly blocked? The branded 403 page includes an **X-Request-Reference** UUID. Cross-reference it:
|
||||
|
||||
1. In the **Audit log**, search for the UUID.
|
||||
2. The audit row shows the matched rule_id, the source IP, the URL, and the offending parameter.
|
||||
1. In the **Activity** tab, search for the UUID.
|
||||
2. The row shows the matched rule ID, source IP, URL, and offending parameter.
|
||||
3. Decide whether to mute the rule (see above) or leave it — many "false positives" turn out to be real attempts.
|
||||
|
||||
## Things to know
|
||||
## Implementation notes
|
||||
|
||||
- **Rule changes apply on the next request.** No service restart needed for tuning.
|
||||
- **Adding or removing rules requires a full reload** of `coraza-spoa`, not just a SIGHUP. The panel handles this for you; if you edit rule files by hand, `docker restart coraza-spoa`.
|
||||
- **Real source IPs are in the audit log.** Even though haproxy fronts the WAF, we propagate the real client IP through the SPOE messages.
|
||||
- **`SecRuleRemoveById` plus a new rule needs a full restart**, not just a config reload. Again, the panel handles this when you change rules through the UI.
|
||||
- **Source of truth: `security.db`.** All rule edits go through the panel and write to that SQLite file; manual file edits won't survive a regeneration.
|
||||
- **Reload mechanism.** Rule edits apply via SIGHUP to `coraza-spoa`; adding or removing whole rule files requires a full container restart, which the panel performs when the change needs it.
|
||||
- **Real source IPs are in the audit log** — even with HAProxy in front of the WAF, the real client IP is propagated through the SPOE messages.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**A rule shows enabled but doesn't fire.** Check that the site is in **Detect-only** or **Enforce** mode. A site in **Off** mode bypasses every rule, including enabled ones.
|
||||
**A rule shows enabled but doesn't fire.** Check that the host is in **detect** or **enforce** mode. A host in **off** bypasses every rule.
|
||||
|
||||
**The audit log is empty.** Confirm `coraza-spoa` is healthy on the **Services** page. If it's restarting in a loop, check the container logs — most often a malformed rule file or a missing include.
|
||||
**The Activity log is empty.** Confirm `coraza-spoa` is healthy on the **Services** tab of Server Settings. If it's restarting in a loop, check the container logs — usually a malformed rule file or a missing include.
|
||||
|
||||
**Edits revert on restart.** Make sure you're editing through the panel; manual edits to files outside the panel-managed path are overwritten by config regeneration.
|
||||
**Edits revert on restart.** Make sure you're editing through the panel; manual edits outside the panel-managed path are overwritten.
|
||||
|
||||
## Related
|
||||
|
||||
- [Server settings & services](/whp/admin/server-settings/)
|
||||
- [Site Monitoring rules](/whp/admin/site-monitoring/)
|
||||
- [AI Monitor, Issues & Ignore Rules](/whp/admin/site-monitoring/)
|
||||
|
||||
## Still stuck?
|
||||
|
||||
|
||||
@@ -1,43 +1,67 @@
|
||||
---
|
||||
title: Admin overview
|
||||
description: What WHP super admin unlocks — server-wide controls for services, security rules, monitoring, and users.
|
||||
description: What WHP super admin unlocks — server-wide controls for services, mail, DNS, security, monitoring, and users.
|
||||
sidebar:
|
||||
order: 1
|
||||
badge:
|
||||
text: Draft
|
||||
variant: caution
|
||||
---
|
||||
|
||||
import SuperAdmin from '~/content/partials/super-admin-callout.mdx';
|
||||
import Draft from '~/content/partials/draft-callout.mdx';
|
||||
import Support from '~/content/partials/support-link.mdx';
|
||||
|
||||
<SuperAdmin />
|
||||
|
||||
<Draft />
|
||||
|
||||
## What super admin unlocks
|
||||
|
||||
The same WHP panel you'd use on a customer account scales up: customers with **super admin** access also see server-wide pages for managing services, firewall rules, monitoring policy, and users. The customer-facing sections (Sites, Domains, Email, etc.) work the same way; the admin sections sit alongside them, gated to admins.
|
||||
WHP's super admin role exposes server-wide pages alongside the customer-facing nav. The customer pages (Sites, Domains, Email, etc.) work identically for everyone; the admin pages sit alongside them and are gated to the super admin only.
|
||||
|
||||
Today, super admin is most commonly handed to customers running a [Virtual Dedicated Server](https://anhonesthost.com/vds) — they get full server control as part of the plan.
|
||||
**Today, the only super admin is the `root` user on the server.** There's no UI to add another super admin — if you need additional people to have super admin, share the root credentials via your usual secret-sharing flow (or [open a ticket](https://secure.anhonesthost.com/submitticket.php) if you need a different model).
|
||||
|
||||
This typically applies to customers running a [Virtual Dedicated Server](https://anhonesthost.com/vds) — they get full server control as part of the plan and sign in to WHP as `root`.
|
||||
|
||||
### Signing in as super admin
|
||||
|
||||
import AdminSignIn from '~/content/partials/admin-signin.mdx';
|
||||
|
||||
<AdminSignIn />
|
||||
|
||||
## Admin-only sidebar sections
|
||||
|
||||
When you sign in as a super admin, these sections appear in addition to the customer-facing nav:
|
||||
|
||||
- **AI Monitor** — the admin dashboard, plus **Issues**, **Site Reports**, and **Ignore Rules**.
|
||||
- **Security Management** — security policy across the server.
|
||||
- **Coraza Rules** — Web-application firewall (WAF) rule tuning, audit, and global mode.
|
||||
- **User Management** — create WHP users, set account types, manage existing users.
|
||||
- **User Resources** — per-user CPU/RAM/disk allowances and current usage.
|
||||
- **Delegated Users** — list of contractor / sub-account access grants.
|
||||
- **Active Sessions** — every signed-in browser across the server.
|
||||
- **Server Settings** — System, Services, Mail, DNS, Network & SSL, Security tabs.
|
||||
- **Disk Usage** — server-wide disk consumption breakdown.
|
||||
- **Announcements Management** — edit the announcements that appear on every customer's dashboard.
|
||||
- **Update Management** — apply WHP platform updates.
|
||||
- **Docker Management** — see and manage every container on the host.
|
||||
- **Valkey Admin** — server-wide Valkey configuration.
|
||||
- **Container Boot & Health** — boot-order and per-container health.
|
||||
- **Site Disable Audit** — record of sites disabled / re-enabled.
|
||||
- **Account Suspensions** — suspended customer accounts.
|
||||
|
||||
## What's in this section
|
||||
|
||||
- **[Server settings & services](/whp/admin/server-settings/)** — restart services, manage modules and runtimes, edit server-wide configuration.
|
||||
- **[Coraza WAF rules](/whp/admin/coraza-waf/)** — view, tune, and tune out web-application-firewall rules across all your sites.
|
||||
- **[Site Monitoring rules](/whp/admin/site-monitoring/)** — manage the rules that drive Site Monitoring alerts.
|
||||
- **[Users & delegated access](/whp/admin/user-management/)** — create sub-users, delegate panel access, manage SFTP/SSH users.
|
||||
- **[Server settings & services](/whp/admin/server-settings/)** — the six tabs under Server Settings: system info, restart services, mail-server config, DNS / nameservers, HAProxy + SSL, and integration API keys.
|
||||
- **[Coraza WAF rules](/whp/admin/coraza-waf/)** — set the global WAF mode, tune individual rules, and audit blocked requests.
|
||||
- **[AI Monitor, Issues & Ignore Rules](/whp/admin/site-monitoring/)** — the three pages that drive the Site Monitoring add-on.
|
||||
- **[Users & delegated access](/whp/admin/user-management/)** — create accounts, set account types, delegate access, and handle suspensions.
|
||||
- **[Backups](/whp/admin/backups/)** — configure the default backup target so customer auto-backups start running. Full-server backups are a separate, plan-dependent concern.
|
||||
|
||||
## Things to know before you change server-wide settings
|
||||
|
||||
- **One change can affect every site on the server.** Where customer-side pages scope changes to one site, admin pages typically scope to the whole server.
|
||||
- **Service restarts are visible to live traffic.** Restart Apache or PHP-FPM during a quiet window when possible.
|
||||
- **Backups still apply.** Server-level changes don't bypass the [backups](/whp/how-to/backups/) you have configured; you can roll back the data side, but service-config changes you made by hand aren't snapshotted unless you back up `/etc` somewhere on your own.
|
||||
- **Customer backups don't cover server config.** The [customer backups](/whp/how-to/backups/) you've configured snapshot site files and databases, not server-level changes you make by hand. Server config lives in several places — not just `/etc` — so the right safety net is a **full-server backup**. On AnHonestHost-managed plans and VDS we handle that for you; running WHP elsewhere, the operator is responsible. See [Backups](/whp/admin/backups/) for the full picture.
|
||||
|
||||
## Related
|
||||
|
||||
- [What is containerized hosting?](/whp/getting-started/what-is-containerized-hosting/) — the differences between container plans and full server access.
|
||||
- [What is containerized hosting?](/whp/getting-started/what-is-containerized-hosting/) — differences between container plans and full server access.
|
||||
|
||||
## Still stuck?
|
||||
|
||||
|
||||
@@ -1,74 +1,115 @@
|
||||
---
|
||||
title: Server settings & services
|
||||
description: Restart Apache, PHP-FPM, MySQL; manage PHP modules and runtimes; edit server-wide configuration.
|
||||
description: Restart services, configure mail server, manage DNS / nameservers, HAProxy + system SSL certificates, and integration API keys.
|
||||
sidebar:
|
||||
order: 2
|
||||
badge:
|
||||
text: Draft
|
||||
variant: caution
|
||||
---
|
||||
|
||||
import { Aside } from '@astrojs/starlight/components';
|
||||
import SuperAdmin from '~/content/partials/super-admin-callout.mdx';
|
||||
import Draft from '~/content/partials/draft-callout.mdx';
|
||||
import SignIn from '~/content/partials/signing-in.mdx';
|
||||
import AdminSignIn from '~/content/partials/admin-signin.mdx';
|
||||
import Support from '~/content/partials/support-link.mdx';
|
||||
|
||||
<SuperAdmin />
|
||||
|
||||
<Draft />
|
||||
The **Server Settings** page lives in the admin sidebar and has six tabs along the left rail. Each tab is a different surface area of the server you can inspect or change.
|
||||
|
||||
This page covers the server-wide controls available in the WHP admin sections — restarting services, managing PHP modules and versions, and adjusting server-level configuration.
|
||||

|
||||
|
||||
## Sign in to WHP
|
||||
## Sign in as super admin
|
||||
|
||||
<SignIn />
|
||||
<AdminSignIn />
|
||||
|
||||
## Restarting services
|
||||
## The six tabs
|
||||
|
||||
The admin **Services** page lists the long-running services that run on the server: Apache (front-end web), PHP-FPM (one or more pools), MySQL or MariaDB, the mail stack, and any add-on services like Valkey or PostgreSQL if you've enabled them.
|
||||
### System
|
||||
|
||||
Each service has a status indicator and **Restart**, **Stop**, and **Start** controls. Restart is the safe default for picking up new configuration.
|
||||
Read-only system summary plus two simple change controls:
|
||||
|
||||
- **System Information** — Hostname, Operating System, Kernel, Timezone, Uptime, Load Average, Disk Usage, Memory Usage.
|
||||
- **Hostname Settings** — change the server's FQDN. Restart the relevant services after a hostname change.
|
||||
- **Timezone Settings** — change the system timezone (affects cron timing, backup schedules, log timestamps).
|
||||
|
||||
<Aside type="caution">
|
||||
Restarting a service drops in-flight connections. For Apache and PHP-FPM, this means active web requests will fail to complete; for MySQL, in-flight transactions roll back. Pick a low-traffic window if you can.
|
||||
Changing the hostname affects the certificates and DNS records that reference it. Plan for the related re-issuance and DNS propagation before changing it on a live server.
|
||||
</Aside>
|
||||
|
||||
## Managing PHP modules and runtimes
|
||||
### Services
|
||||
|
||||
WHP can run multiple PHP runtimes side-by-side (PHP 8.3, 8.4, etc.). With super admin you can:
|
||||
Service status and restart controls.
|
||||
|
||||
- Install additional PHP runtimes via the **PHP Versions** admin page.
|
||||
- Add or remove extensions per runtime — common ones (mbstring, intl, opcache, imagick, redis, etc.) are toggles; less common ones may require a [support ticket](https://secure.anhonesthost.com/submitticket.php).
|
||||
- Edit a runtime's `php.ini` from the **PHP Configuration** sub-page, then reload PHP-FPM to pick it up.
|
||||
- **Service Status** — health pills for the host-side services:
|
||||
- **Apache** and **PHP-FPM** on the host serve the **WHP control panel itself**, not customer sites. Customer sites run inside their own per-site containers, separate from these host services.
|
||||
- **Docker** — the host's Docker daemon. If this is down, no customer container will start.
|
||||
- **ProFTPD** — host FTP service (used by FTP-enabled customer accounts).
|
||||
- **Backup Upload** — the host-side uploader that streams backups to your configured backup targets.
|
||||
- **Restart Services** — checkboxes per service plus **Restart Selected Services**.
|
||||
- **Docker Container Management** — status of the core platform containers (Mysql, Haproxy manager, Memcache, Postgresql) and a per-container **Execute Operation** picker (e.g. restart a single container).
|
||||
|
||||
Switching a site to a different PHP version is done on the customer side — open **Sites → your site** and pick from the **Container Type / PHP Version** dropdown. The runtimes available there come from this admin list.
|
||||
<Aside type="caution">
|
||||
Restarting host Apache or PHP-FPM kicks the **control panel** offline briefly — customers can't sign in to WHP for a few seconds. It does **not** affect customer sites (those are in their own containers). For MySQL, in-flight transactions in the MySQL container roll back. Pick a quiet window when you can.
|
||||
</Aside>
|
||||
|
||||
### Mail
|
||||
|
||||
Two panels:
|
||||
|
||||
- **Mail Server** — set the **Mail Server Hostname** (used for MX records on new domains and as the IMAP host for archival). Configure the Mail Server API (URL, Username, Password) that WHP uses to provision mailboxes. Toggle **Enable Mailserver API Debug Logging** when troubleshooting; it writes mailserver API requests/responses to the PHP error log.
|
||||
- **Outbound Email (SMTP)** — configure SMTP for outbound system alerts and customer AI Monitor notifications. Toggle **Enable Outbound Email** and provide the relay's credentials.
|
||||
|
||||
### DNS
|
||||
|
||||
Two panels:
|
||||
|
||||
- **WHP Nameserver Configuration** — set the primary and secondary nameserver hostnames and IPs. These are baked into every customer's DNS zone, so changing them affects every domain you host.
|
||||
- **Network DNS Settings** — set the upstream resolvers the server uses (defaults to Cloudflare `1.1.1.1` and Google `8.8.8.8`).
|
||||
- **DNS Configuration Settings** — default TTL for new DNS records (60–86400 seconds).
|
||||
|
||||
<Aside type="caution">
|
||||
Changing nameserver hostnames or IPs invalidates the zone delegation for every customer domain pointed at the old values. Migrate slowly; expect lag in the order of TTL × propagation window.
|
||||
</Aside>
|
||||
|
||||
### Network & SSL
|
||||
|
||||
Operational controls for HAProxy and system-service certs:
|
||||
|
||||
- **HAProxy Configuration Management** — **Regenerate** (rebuild config for every active site), **Reload** (apply config without restart), **Health Check** (probe HAProxy).
|
||||
- **HAProxy API Key** — Bearer token used to authenticate against the HAProxy Manager API. After rotating, restart the HAProxy container.
|
||||
- **System Service SSL Certificates** — request a Let's Encrypt cert for system-level services like the WHP panel itself and FTP, by picking the service and the domain name.
|
||||
|
||||
### Security
|
||||
|
||||
API keys for **external integrations** like WHMCS. Not customer-facing.
|
||||
|
||||
- **Create New API Key** — Key Name, Rate Limit (requests per hour), Permissions (User Management, Resource Management, SSO Access, System Statistics, AI Monitor Management), an optional IP whitelist, and Notes.
|
||||
- **Existing API Keys** — list of issued keys with their permissions and rate limit. Revoke by removing the row.
|
||||
|
||||
## Server-wide configuration files
|
||||
|
||||
For settings that aren't exposed in the panel, you can SSH to the server and edit configuration directly:
|
||||
The host runs the WHP control panel and orchestrates customer containers. Most customer-affecting configuration lives **inside** containers, not on the host. A short map:
|
||||
|
||||
- **Apache:** `/etc/httpd/conf.d/` for per-app drop-ins; per-site vhosts are generated from WHP and live in a directory the panel manages. **Don't** edit generated vhosts by hand — they'll be overwritten on the next config regeneration.
|
||||
- **PHP-FPM pools:** `/etc/php-fpm.d/` for runtime pool tweaks. Pool defaults are templated by WHP; edits to per-site pools are overwritten on regeneration.
|
||||
- **MySQL/MariaDB:** `/etc/my.cnf.d/`. The defaults are tuned for the server's resource profile; large-tweak changes are usually best left to a ticket so we can advise.
|
||||
- **Control-panel Apache:** `/etc/httpd/conf.d/` on the host configures the WHP panel's own Apache. Editing here changes how the panel serves; it doesn't change how customer sites serve.
|
||||
- **Control-panel PHP-FPM:** `/etc/php-fpm.d/` on the host configures the panel's PHP. Same scope.
|
||||
- **MySQL:** the MySQL instance runs as a container. Files under `/etc/my.cnf.d/` on the host are surfaced to customer database connections — they're effectively client-facing settings, not host settings.
|
||||
- **HAProxy:** runs as a container with its own volume. Reload via the **Network & SSL** tab; don't hand-edit files in the container.
|
||||
|
||||
After editing, reload the relevant service from the WHP **Services** page (or via systemctl on the box).
|
||||
### Customer-side container customisation
|
||||
|
||||
## Common admin tasks
|
||||
If a customer needs a non-standard runtime, library, or service inside their site's container — that's done by **building a custom Docker image** and adding it as a container type option in WHP, not by editing host-level config.
|
||||
|
||||
**Drop a hot file cache.** From the **Services** page, click **Reload** on PHP-FPM. This is the right move after editing `php.ini` or an extension list.
|
||||
The pattern is documented in our cloud-container repos. See <a href="https://repo.anhonesthost.net/cloud-hosting-platform/">repo.anhonesthost.net/cloud-hosting-platform/</a> for the cloud-apache-container and cloud-node-container examples — they show the layout, build, and how to publish an image so it appears in the **Container Type** dropdown on the Sites page.
|
||||
|
||||
**Free disk space on a full server.** Check `/var/log/` first — log rotation may be lagging. WHP rotates app logs into the per-site `logs/` directory; server-level logs in `/var/log` are yours to rotate via `logrotate` config in `/etc/logrotate.d/`.
|
||||
|
||||
**See what's eating resources.** The admin **Resource usage** page shows aggregate CPU, RAM, disk I/O, and per-process drilldowns. For deeper inspection, SSH in and use `top`, `htop`, or `iotop`.
|
||||
<Aside type="tip">
|
||||
Cloud container images are a one-time setup per stack you want to support. Once published, every site using that container type benefits automatically — no per-site shell access required.
|
||||
</Aside>
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Service won't restart.** The Services page surfaces the systemd error; if it's `failed (exit-code)`, check `journalctl -u <service>` on the box for the underlying message. The most common cause is a syntax error in a config file you just edited.
|
||||
**A service won't restart.** Check `journalctl -u <service>` (for systemd-managed services) or `docker logs <container>` (for containerized ones). The most common cause is a syntax error in a config file you just edited.
|
||||
|
||||
**PHP module toggle has no effect.** PHP modules need a PHP-FPM **reload** to be picked up. The toggle should do this automatically; if it doesn't, click Reload manually.
|
||||
**Edits to a generated vhost keep disappearing.** That file is generated. Put your customisation in a per-app drop-in under `/etc/httpd/conf.d/`, or open a ticket about adding a stable include hook.
|
||||
|
||||
**Edits to a generated vhost keep disappearing.** That file is generated. Put your customisation in a per-app drop-in under `/etc/httpd/conf.d/` instead, or open a ticket about adding a stable include hook.
|
||||
**Mailserver API debug log too noisy.** Toggle **Enable Mailserver API Debug Logging** off on the **Mail** tab once you've finished diagnosing.
|
||||
|
||||
## Related
|
||||
|
||||
|
||||
@@ -1,94 +1,112 @@
|
||||
---
|
||||
title: Site Monitoring rules
|
||||
description: Configure Site Monitoring across every site on the server — global ignore lists, alert routing, and severity tuning.
|
||||
title: AI Monitor, Issues & Ignore Rules
|
||||
description: The three admin pages that drive the Site Monitoring add-on — AI Monitor dashboard, Issues, and Ignore Rules.
|
||||
sidebar:
|
||||
order: 4
|
||||
badge:
|
||||
text: Draft
|
||||
variant: caution
|
||||
---
|
||||
|
||||
import { Aside } from '@astrojs/starlight/components';
|
||||
import SuperAdmin from '~/content/partials/super-admin-callout.mdx';
|
||||
import Draft from '~/content/partials/draft-callout.mdx';
|
||||
import SignIn from '~/content/partials/signing-in.mdx';
|
||||
import AdminSignIn from '~/content/partials/admin-signin.mdx';
|
||||
import Support from '~/content/partials/support-link.mdx';
|
||||
|
||||
<SuperAdmin />
|
||||
|
||||
<Draft />
|
||||
[Site Monitoring](/whp/add-ons/monitoring/) is the customer-facing alerting add-on. The admin side exposes three pages — together they let you tune what gets monitored, what surfaces as a customer-visible issue, and what gets suppressed.
|
||||
|
||||
[Site Monitoring](/whp/add-ons/monitoring/) is the customer-facing alerting product. With super admin access, you can manage the rules that drive those alerts at the server level — including a server-wide ignore list, alert routing, and severity tuning.
|
||||
## Sign in as super admin
|
||||
|
||||
## Sign in to WHP
|
||||
<AdminSignIn />
|
||||
|
||||
<SignIn />
|
||||
## AI Monitor (admin dashboard)
|
||||
|
||||
## Where it lives
|
||||
Sidebar → **AI Monitor → Dashboard**. The operational heartbeat of the whole monitoring pipeline.
|
||||
|
||||
Sidebar → **Site Monitoring** (admin view). The page shows two perspectives:
|
||||

|
||||
|
||||
- **Customer feed** — what your site owners see when they sign in.
|
||||
- **Admin tools** — the rule library, global ignore list, and alert configuration.
|
||||
Panels:
|
||||
|
||||
- **AI Log Monitor Status** — overall on/off plus three sub-statuses:
|
||||
- **Minute-cadence poll** — the cron that scans logs every minute.
|
||||
- **Health API** — the internal API that exposes per-container health.
|
||||
- **HAProxy stats** — the proxy stats feed used for error-rate tracking.
|
||||
- **Stat tiles** — Last Run, Errors Tracked, Remediations, API Calls Today (with a rate-limit denominator).
|
||||
- **Health Check Timeline (last 7d)** — every state transition (`cpu`, `swap`, `haproxy`, etc.) with its severity and an **AI Diagnosis** explanation.
|
||||
|
||||
Use this page to confirm the pipeline is healthy and to drill into recent state changes. The AI Diagnosis text gives a plain-language summary of *why* a transition happened — useful for context before you act.
|
||||
|
||||
## Issues
|
||||
|
||||
Sidebar → **AI Monitor → Issues**. The customer-visible findings, server-wide.
|
||||
|
||||

|
||||
|
||||
Four stat tiles at the top:
|
||||
|
||||
- **Critical** — open critical-severity issues.
|
||||
- **Warning** — open warning-severity issues.
|
||||
- **Auto-resolved (review)** — issues the monitor closed on its own that may still need a human glance.
|
||||
- **Active suppressions** — issues currently muted by an Ignore Rule.
|
||||
|
||||
Filter row: Scope (All / specific user), Status (Open / Closed / All), Severity, Source, Signature prefix.
|
||||
|
||||
Bulk actions: **Mark Fixed**, **Ignore**, **Delete**.
|
||||
|
||||
Each row has its own quick-actions: **Fix** (close it) and **Ignore** (create an ignore rule from this row's match criteria).
|
||||
|
||||
## Ignore Rules
|
||||
|
||||
Sidebar → **AI Monitor → Ignore Rules**. Match criteria that prevent matching findings from becoming customer-visible issues.
|
||||
|
||||

|
||||
|
||||
Each rule has these fields:
|
||||
|
||||
- **Scope** — `user` (just one customer) or `global` (every customer).
|
||||
- **Target** — the user or domain the rule applies to.
|
||||
- **Match** — a comma-separated set of `field=value` predicates (e.g. `cat=degraded & title~"Beaver Builder cache files missing"`). Match fields combine with **AND** semantics — every predicate must match.
|
||||
- **Reason** — a short note for future-you explaining why the rule exists.
|
||||
- **Hits / Last hit** — how often the rule has matched, and when.
|
||||
- **Enabled** — toggle without deleting.
|
||||
|
||||
<Aside type="tip">
|
||||
Prefer **per-user** ignores over global. Global rules silence the finding for everyone on the server.
|
||||
</Aside>
|
||||
|
||||
## Common tasks
|
||||
|
||||
### Add a global ignore rule
|
||||
### Mute a known-noisy finding for one customer
|
||||
|
||||
When a signature is noisy for every site (for example, a known scanner you allow on your own infrastructure):
|
||||
1. Open **Issues**, find the row.
|
||||
2. Click the row's **Ignore** action — that pre-fills an Ignore Rule with the matching criteria scoped to that user.
|
||||
3. Add a **Reason** so future-you (or someone else on the team) understands why it exists.
|
||||
4. Save. Future matching findings will be suppressed; the **Active suppressions** tile will tick up.
|
||||
|
||||
1. Open **Site Monitoring → Global Ignore Rules**.
|
||||
2. Click **Add Rule**.
|
||||
3. Define the match condition (rule_id, source IP/CIDR, URL pattern, or a combination).
|
||||
4. Add a short note so future-you remembers why this exists.
|
||||
5. Save. Matching events stop firing alerts immediately.
|
||||
### Investigate an "Auto-resolved (review)" issue
|
||||
|
||||
<Aside type="caution">
|
||||
Global ignore rules silence every site on the server. Prefer per-site ignore (set in the customer-facing Site Monitoring page) when the noise is site-specific.
|
||||
</Aside>
|
||||
These are issues where the underlying signal recovered before a human looked at them. Open the row to see the AI Diagnosis and the original detection. If the resolution looks legitimate, click **Mark Fixed**; if you're suspicious, leave it open and add a comment for context.
|
||||
|
||||
### Tune severity for a rule
|
||||
### Tune brute-force detection
|
||||
|
||||
If a rule is set to **critical** but you've decided it's really informational in your environment:
|
||||
Brute-force rules live in **Coraza Rules** (rule families CRS-913 / 921 / 942 / 949) rather than AI Monitor. AI Monitor surfaces the *effects* (error spikes) once a brute-force pattern fires, but the matching itself is in the WAF — see [Coraza WAF rules](/whp/admin/coraza-waf/).
|
||||
|
||||
1. Find the rule in **Site Monitoring → Rules**.
|
||||
2. Open the rule and change its severity to one of: informational / warning / critical.
|
||||
3. Save. The severity change applies to new events from that rule.
|
||||
## Routing alerts
|
||||
|
||||
Severity matters because **SMS notifications fire only on critical**. Downgrading from critical to warning silences SMS without silencing the rule.
|
||||
|
||||
### Route alerts somewhere other than the default
|
||||
|
||||
Default routing: alerts go to the contact email on the account. You can:
|
||||
|
||||
- **Add additional email recipients** — useful for a shared ops alias.
|
||||
- **Enable SMS for critical alerts** — wire your phone number on the **Alert Routing** page.
|
||||
- **Forward to a webhook** — for integration with Slack, PagerDuty, or your own incident pipeline.
|
||||
|
||||
## Brute-force detection
|
||||
|
||||
Brute-force detection is a separate rule family and has its own per-site sensitivity. From the admin view, you can adjust:
|
||||
|
||||
- The window in which repeated failures count.
|
||||
- The threshold at which the rule fires.
|
||||
- Whether the rule auto-blocks the source IP (recommended) or only alerts.
|
||||
|
||||
## Things to know
|
||||
|
||||
- **Ignore lists don't stop logging.** They only suppress alerts. The events still appear in the audit feed so you can see what's actually happening.
|
||||
- **Rules apply on the next log scan**, typically within a minute.
|
||||
- **A muted rule for one customer doesn't affect others.** Per-site ignore is scoped tightly.
|
||||
Customer email alerts are sent via the SMTP relay configured on **Server Settings → Mail → Outbound Email (SMTP)**. Toggle **Enable Outbound Email** off there if you need to silence outbound notifications for a maintenance window.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**No alerts arriving for a known event.** Check the **Global Ignore Rules** list and any per-site ignores. Also confirm the rule's severity isn't set to informational (no email or SMS by default).
|
||||
**Pipeline shows "stale" — Last Run more than a few minutes ago.** Check the cron's container on **Server Settings → Services → Docker Container Management**. The monitor poll is `whp-monitor-poll`.
|
||||
|
||||
**SMS not firing on critical.** Confirm SMS is enabled in **Alert Routing** and the phone number is verified.
|
||||
**Issues appearing for a known noisy site.** Add an Ignore Rule scoped to that user.
|
||||
|
||||
**No customer alerts arriving.** Confirm Outbound Email is enabled and the SMTP relay is reachable from the server.
|
||||
|
||||
## Related
|
||||
|
||||
- [Site Monitoring add-on](/whp/add-ons/monitoring/) — what the customer sees.
|
||||
- [Coraza WAF rules](/whp/admin/coraza-waf/) — request-level firewall, complements monitoring.
|
||||
- [Site Monitoring add-on](/whp/add-ons/monitoring/) — the customer-facing side.
|
||||
- [Coraza WAF rules](/whp/admin/coraza-waf/) — request-level firewall.
|
||||
- [Server settings & services](/whp/admin/server-settings/) — SMTP relay config for outbound alerts.
|
||||
|
||||
## Still stuck?
|
||||
|
||||
|
||||
@@ -1,95 +1,144 @@
|
||||
---
|
||||
title: Users & delegated access
|
||||
description: Create sub-users, delegate panel access, and manage SFTP/SSH users at the server level.
|
||||
description: Create WHP users, set account types, change passwords, plus delegated user access and account suspensions.
|
||||
sidebar:
|
||||
order: 5
|
||||
badge:
|
||||
text: Draft
|
||||
variant: caution
|
||||
---
|
||||
|
||||
import { Aside } from '@astrojs/starlight/components';
|
||||
import SuperAdmin from '~/content/partials/super-admin-callout.mdx';
|
||||
import Draft from '~/content/partials/draft-callout.mdx';
|
||||
import SignIn from '~/content/partials/signing-in.mdx';
|
||||
import AdminSignIn from '~/content/partials/admin-signin.mdx';
|
||||
import Support from '~/content/partials/support-link.mdx';
|
||||
|
||||
<SuperAdmin />
|
||||
|
||||
<Draft />
|
||||
Four admin pages collectively control who can sign in and what they can do on the server:
|
||||
|
||||
WHP super admin lets you give other people scoped access to the server — your dev team, a contractor, or a junior admin — without sharing your own credentials.
|
||||
- **User Management** — create / change-password / delete WHP users.
|
||||
- **User Resources** — set CPU / RAM / disk allowances per user.
|
||||
- **Delegated Users** — list of contractor / sub-account grants on customer sites.
|
||||
- **Account Suspensions** — suspended accounts.
|
||||
|
||||
## Three kinds of access
|
||||
## Sign in as super admin
|
||||
|
||||
| Type | What they can do | Where they sign in |
|
||||
| ------------------- | -------------------------------------------------------------------------------- | --------------------------------------- |
|
||||
| **WHP sub-user** | Sign in to WHP with their own credentials. You control which sections they see. | Same `:8443` URL as you. |
|
||||
| **Delegated panel access** | A read-only or scoped-write view onto a specific site, for a contractor. | Same WHP, but scoped to that one site. |
|
||||
| **SFTP / SSH user** | File access (and optionally SSH) without WHP at all. | SFTP client / SSH terminal. |
|
||||
<AdminSignIn />
|
||||
|
||||
## Sign in to WHP
|
||||
## User Management
|
||||
|
||||
<SignIn />
|
||||
Sidebar → **User Management**.
|
||||
|
||||
## Create a WHP sub-user
|
||||

|
||||
|
||||
### Create New User
|
||||
|
||||
Every user created here is a **customer account**, not a super admin. (Super admin is the `root` user on the server, and there's no UI to add another.)
|
||||
|
||||
Fields:
|
||||
|
||||
- **Username** — UNIX-safe username; also becomes the SFTP user and home-directory name (`/docker/users/<username>`).
|
||||
- **Password** — strong password. The user can change it later from the panel.
|
||||
- **Account Type** — pick the scope of features this customer should see:
|
||||
- **Full Hosting** — sites, databases, domains, DNS, email. The default for normal customers.
|
||||
- **Domain/DNS Only** — domains and DNS records only (no sites, databases, or email).
|
||||
- **Mail/DNS Only** — email plus domains/DNS (no sites or databases).
|
||||
|
||||
Click **Create User** to provision the account. The user's home directory and SFTP credentials are set up immediately.
|
||||
|
||||
<Aside type="tip">
|
||||
WHP sub-users are the right model when someone needs to manage parts of the server alongside you. Use **delegated access** instead if they only need one site, or **SFTP/SSH** if they only need files.
|
||||
Pick the smallest account type that does the job. You can change it later from the **Actions** column in the user list.
|
||||
</Aside>
|
||||
|
||||
1. Open **Users → WHP Users → Add User** in the admin sidebar.
|
||||
2. Set a username, a strong password, and an email (used for password reset and 2FA).
|
||||
3. Choose a **role**: pick from the predefined roles (Admin, Site Manager, Read-only, etc.) or build a custom role with specific pages enabled.
|
||||
4. (Optional) Enable **Require 2FA** so they have to set up an authenticator app on first login.
|
||||
5. Save. Share the credentials with them out-of-band; don't email passwords.
|
||||
### Change User Password
|
||||
|
||||
## Delegated access for a single site
|
||||
Pick the user from the dropdown, enter a new password, click **Change Password**. The user is forced to sign in again on next visit; any in-flight panel sessions are still live until you also revoke them via **Active Sessions**.
|
||||
|
||||
Use the customer-facing **Delegated Users** page (sidebar → **Delegated Users**, available on every account) when a contractor only needs to work on one site:
|
||||
### User Accounts table
|
||||
|
||||
1. Open **Delegated Users → Add**.
|
||||
2. Pick the site they should have access to.
|
||||
3. Set their permission scope: view-only, manage-files, manage-DNS, etc.
|
||||
4. Send them the panel URL. They sign in with their own credentials and see only that site.
|
||||
Columns: **Username**, **UID** (UNIX uid), **Account Type**, **Home Directory**, **Actions** (Account-Type dropdown + Delete).
|
||||
|
||||
This is the right path for, say, a freelance designer who needs to upload assets but shouldn't see your other sites or your DNS.
|
||||
To change a user's account type, change the dropdown in the row and the change applies immediately. The **System** badge on a row marks an internal/system user (such as `root`, `whp`, `daemon`, `www-data`, `nobody`, the various `systemd-*` users, etc.).
|
||||
|
||||
## SFTP / SSH users
|
||||
|
||||
Pure file access without WHP. Created from the admin **Users → SFTP/SSH** page:
|
||||
|
||||
1. Open **Users → SFTP/SSH → Add**.
|
||||
2. Set the username, password (or paste their public SSH key), and which directories they have access to.
|
||||
3. Pick whether to grant interactive SSH or restrict to SFTP only.
|
||||
4. Save. They can now connect with their preferred SFTP/SSH client.
|
||||
**System users are protected.** The panel refuses to delete any user on the protected list — `delete_user` in `web-files/libs/usermgmt.php` checks `is_protected_user($username)` first and returns *"Cannot delete protected system user"* without touching the OS user. Password changes are blocked the same way, with one exception: `root`'s password can be changed (the rest of the protected list cannot).
|
||||
|
||||
<Aside type="caution">
|
||||
Interactive SSH is powerful — anyone with it can run arbitrary commands inside the container or VM. Restrict to SFTP-only unless they specifically need a shell.
|
||||
Deleting a non-system user removes their home directory and every site, database, and mailbox associated with them. There is no undo. Use **Account Suspensions** instead for temporary disablement.
|
||||
</Aside>
|
||||
|
||||
## Managing existing users
|
||||
## User Resources
|
||||
|
||||
The user list shows last sign-in, role, and 2FA status. Common actions from each user's row:
|
||||
Sidebar → **User Resources**. Configure CPU and memory allowances per user.
|
||||
|
||||
- **Disable** — keeps the user but blocks sign-in.
|
||||
- **Delete** — removes the user.
|
||||
- **Force password reset** — invalidates their current password; they receive an email link.
|
||||
- **Revoke sessions** — kicks them out of any active panel sessions immediately.
|
||||

|
||||
|
||||
When someone leaves your team, **revoke sessions first** (so they're out *now*), then disable or delete the user.
|
||||
Each row shows current allocation vs. usage:
|
||||
|
||||
- **Max CPU** / **CPU Used** — in 0.25-core increments.
|
||||
- **Max Mem** / **Mem Used** — in 256 MB increments.
|
||||
- **Disk** / **Used** — total disk allocation and current consumption (with %).
|
||||
- **Email** — mailbox slot count.
|
||||
- **Mail MB** — total mail storage cap.
|
||||
- **Arch.** — archival email slots used / total.
|
||||
- **Cont.** — currently-running container count.
|
||||
|
||||
Use the **Actions** column to edit a user's caps. Changes apply on the next container restart for that user's sites.
|
||||
|
||||
<Aside type="note">
|
||||
Memory-usage tracking isn't available in some Docker environments. The **Active sites** count reflects running containers.
|
||||
</Aside>
|
||||
|
||||
## Delegated Users
|
||||
|
||||
Sidebar → **Delegated Users**. Customers can use the Delegated Users page on their own account to grant a contractor scoped access to one of their sites. The admin view shows every active delegation across the server.
|
||||
|
||||
From the admin view you can:
|
||||
|
||||
- **Audit** — see who has cross-account access at a glance.
|
||||
- **Edit** — modify scope, permissions, or expiry on any delegation for an independent customer. Useful when a customer asks support to fix a grant they set up incorrectly.
|
||||
- **Revoke** — remove a delegation outright.
|
||||
|
||||
If a customer reports a delegation issue, this page is where you confirm the grant exists, inspect its scope, and adjust it on their behalf.
|
||||
|
||||
## Account Suspensions
|
||||
|
||||
Sidebar → **Account Suspensions**. The list of suspended customer accounts.
|
||||
|
||||
A suspension takes a customer's sites offline without deleting any data — the customer can be reinstated by removing the suspension. Useful for non-payment, terms-of-service issues, or maintenance hold.
|
||||
|
||||
The page lists who's suspended, when, by whom, and why. Reinstate from the action button on each row.
|
||||
|
||||
### How the suspension page is served
|
||||
|
||||
The "site suspended" page is served by **HAProxy** itself, not by a separate backend. When you suspend an account, WHP rewrites the HAProxy config to point that account's frontends at a 503 errorfile (`/usr/local/etc/haproxy/errors/503.http`) and reloads HAProxy.
|
||||
|
||||
**If a suspended site is still serving the real content** (or is throwing a network error instead of the suspended page), it almost always means HAProxy didn't pick up the config reload. Check, in order:
|
||||
|
||||
1. **HAProxy container is running.** **Server Settings → Services → Docker Container Management** → confirm `Haproxy manager` shows **Running**.
|
||||
2. **HAProxy reload succeeded.** Either re-trigger from **Server Settings → Network & SSL → Reload Configuration**, or check the HAProxy container logs (`docker logs haproxy-manager`) for a reload error — usually a syntax error in the generated config from the suspension action.
|
||||
3. **Errorfile is in place.** The 503 page lives at `/usr/local/etc/haproxy/errors/503.http` inside the container.
|
||||
|
||||
## Active Sessions
|
||||
|
||||
Sidebar → **Active Sessions**. Every active panel session across the server, with last activity time, IP, and a **Terminate** button. Use this when offboarding someone — kick them out of any active sessions *first*, then change their password or delete the user.
|
||||
|
||||
## When someone leaves your team
|
||||
|
||||
1. **Revoke active sessions** for that user via **Active Sessions**.
|
||||
2. **Change their password** in **User Management** (locks them out even if they save their cookies).
|
||||
3. **Downgrade or delete** the user in **User Management**.
|
||||
4. Audit **Delegated Users** for any cross-account delegations that should also be revoked.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Sub-user can sign in but the page they expect is missing.** Their role doesn't include that section. Edit the role and tick the right page.
|
||||
**"Cannot delete protected system user".** Expected — system users (root, daemon, www-data, mail, the `systemd-*` users, etc.) are blocked at the panel level to prevent breaking the host. If you really need to remove a user, confirm it's a customer account first.
|
||||
|
||||
**SFTP user can connect but uploads land in the wrong directory.** Check their **Home directory** in the SFTP/SSH user page — it determines what they see as `/`.
|
||||
**Created user can't sign in.** Confirm the password meets the strength rules. If the user is signing in for the first time, they may be hitting the password-change-on-first-login flow.
|
||||
|
||||
**A delegated user can't see DNS records.** Delegated access defaults to file-only. Edit their permissions to include DNS Management.
|
||||
**Suspended customer's sites are still serving real traffic.** The suspension page is served by HAProxy — see "How the suspension page is served" above. Most often it's an HAProxy reload that didn't happen; check the container logs.
|
||||
|
||||
## Related
|
||||
|
||||
- [Server settings & services](/whp/admin/server-settings/)
|
||||
- [Server settings & services](/whp/admin/server-settings/) — including the suspension backend service.
|
||||
- [AI Monitor, Issues & Ignore Rules](/whp/admin/site-monitoring/)
|
||||
|
||||
## Still stuck?
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Steps, Aside } from '@astrojs/starlight/components';
|
||||
import SignIn from '~/content/partials/signing-in.mdx';
|
||||
import Support from '~/content/partials/support-link.mdx';
|
||||
|
||||
WHP keeps automatic backups of your sites and databases. The **Backup Management** page lets you trigger an on-demand backup, add a scheduled backup, manage where backups are sent, and review history.
|
||||
The **Backup Management** page lets you trigger an on-demand backup, add a scheduled backup, manage where backups are sent, and review history.
|
||||
|
||||
## What's backed up
|
||||
|
||||
@@ -18,6 +18,10 @@ WHP keeps automatic backups of your sites and databases. The **Backup Management
|
||||
|
||||
Default retention on built-in backup targets is **5 days, up to 10 backups**.
|
||||
|
||||
<Aside type="note">
|
||||
Automatic backups only run once the server admin has configured at least one **default backup target**. On our managed shared plans this is set up for you. On a VDS, the server operator picks the target — until they do, the **Backup Targets** table on your Backups page will be empty and you won't see scheduled backups firing.
|
||||
</Aside>
|
||||
|
||||
## Sign in to WHP
|
||||
|
||||
<SignIn />
|
||||
@@ -83,6 +87,14 @@ Don't do a full restore unless you genuinely need to — it rewrites your live s
|
||||
|
||||
**"No targets available" when starting a backup.** Your account has no backup targets attached. Open a ticket; we'll get one wired up.
|
||||
|
||||
## What's *not* in customer backups
|
||||
|
||||
Customer backups cover your sites and databases — they don't cover the underlying server, the OS, or system-level config. Full-server backups are a separate concern:
|
||||
|
||||
- On our managed plans, **AnHonestHost handles full-server backups** for the host.
|
||||
- On a Virtual Dedicated Server (VDS) we provide, **full-server snapshots are included** at the platform level.
|
||||
- If WHP is running somewhere else (your own infrastructure), full-server backups are the server operator's responsibility — WHP itself doesn't provide a host-level backup tool.
|
||||
|
||||
## Related
|
||||
|
||||
- [Archival email add-on](/whp/add-ons/archival-email/)
|
||||
|
||||
3
src/content/partials/admin-signin.mdx
Normal file
@@ -0,0 +1,3 @@
|
||||
Super admin access is granted to the `root` user only. Sign in **directly** at `https://<your-server-hostname>:8443` with the root credentials.
|
||||
|
||||
The WHMCS client portal route doesn't apply for super admin — it signs you in as the linked customer, not as root.
|
||||
229
tools/screenshots/capture-admin.ts
Normal file
@@ -0,0 +1,229 @@
|
||||
/**
|
||||
* v2 admin capture — stricter redaction and deeper navigation.
|
||||
*
|
||||
* - Masks customer domains (anything not in the brand allowlist).
|
||||
* - Masks customer-shaped usernames (anything not in the system allowlist).
|
||||
* - Masks input value attributes (the v1 only walked text nodes).
|
||||
* - Captures Settings sub-tabs (System, Services, Mail, DNS, Network & SSL,
|
||||
* Security) since those are where LiteLLM URL / model / key likely live.
|
||||
*
|
||||
* Read-only. Never clicks save/apply/restart/delete.
|
||||
*/
|
||||
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_ADMIN_USER');
|
||||
const PASS = need('WHP_ADMIN_PASS');
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
async function redact(page: Page) {
|
||||
await page.addStyleTag({ content: HIDE_CSS });
|
||||
|
||||
await page.evaluate(() => {
|
||||
// ---- Mask all secret-shaped input values ----
|
||||
const secretInputSel = [
|
||||
'input[type="password"]',
|
||||
'input[name*="key" i]',
|
||||
'input[name*="token" i]',
|
||||
'input[name*="secret" i]',
|
||||
'input[name*="api" i]',
|
||||
];
|
||||
for (const sel of secretInputSel) {
|
||||
document.querySelectorAll<HTMLInputElement>(sel).forEach((el) => {
|
||||
if (el.value) el.value = '████████████████';
|
||||
});
|
||||
}
|
||||
|
||||
// ---- Brand allowlist ----
|
||||
const BRAND_SUFFIXES = [
|
||||
'anhonesthost.com', 'anhonesthost.net', 'anhonesthost.io',
|
||||
'anhh.co',
|
||||
'cloud-hosting.io',
|
||||
'example.com', 'example.org', 'example.net',
|
||||
];
|
||||
|
||||
// ---- System users to keep visible (others get masked) ----
|
||||
const SYSTEM_USERS_ARR = ['root', 'admin', 'whp', 'haproxy', 'apache', 'nginx', 'newuser'];
|
||||
|
||||
// ---- Text-node swaps ----
|
||||
const swaps: [RegExp, string][] = [
|
||||
// Server / mail / nameserver hostnames in our infra
|
||||
[/whp\d+(-[a-z0-9]+)?\.cloud-hosting\.io/gi, '<your-server>.cloud-hosting.io'],
|
||||
[/mail\d+\.cloud-hosting\.io/gi, '<your-mail-server>.cloud-hosting.io'],
|
||||
[/whp\d+(-[a-z0-9]+)?\b/gi, '<your-server>'],
|
||||
[/WHP\d+(-[A-Z0-9]+)?\b/g, '<YOUR-SERVER>'],
|
||||
// IPs
|
||||
[/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, '<server-IP>'],
|
||||
// Home dirs
|
||||
[/\/docker\/users\/[a-z0-9-]+/g, '/docker/users/<user>'],
|
||||
// Common secret shapes
|
||||
[/sk-[A-Za-z0-9_-]{20,}/g, '<API-KEY>'],
|
||||
[/sk_(test|live)_[A-Za-z0-9]{20,}/g, '<API-KEY>'],
|
||||
[/Bearer\s+[A-Za-z0-9._-]{20,}/g, 'Bearer <API-KEY>'],
|
||||
[/eyJ[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}/g, '<JWT>'],
|
||||
// AI provider URLs
|
||||
[/https?:\/\/[^\s"'<>]*litellm[^\s"'<>]*/gi, '<litellm-endpoint>'],
|
||||
[/https?:\/\/[^\s"'<>]*\.anhonesthost\.(net|com|io)[^\s"'<>]*/gi, '<internal-endpoint>'],
|
||||
// Model family identifiers
|
||||
[/(claude|gpt|llama|mistral|gemini)[a-z0-9._-]*-\d[a-z0-9.\-]*/gi, '<model-name>'],
|
||||
// Bichon / Coraza / haproxy internal endpoints
|
||||
[/https?:\/\/[^\s"'<>]*\b(bichon|coraza-spoa|haproxy-manager)[^\s"'<>]*/gi, '<internal-service>'],
|
||||
// root → admin (we don't expose which UNIX user has super admin)
|
||||
[/Welcome, root\b/g, 'Welcome, admin'],
|
||||
[/(User:\s*)root\b/g, '$1admin'],
|
||||
[/(Home Directory:\s*)\/root\b/g, '$1/<admin-home>'],
|
||||
// Standalone 'root' (whole-word, not preceded by / or . — so paths like
|
||||
// /root/foo and references like .root stay untouched).
|
||||
[/(^|[^/.\w])root\b/g, '$1admin'],
|
||||
];
|
||||
|
||||
// ---- Walk 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 ?? '';
|
||||
|
||||
// Mask non-brand domain names (basic pattern: word.tld)
|
||||
v = v.replace(/\b([a-z0-9][a-z0-9-]{0,62}\.)+[a-z]{2,24}\b/gi, function (m) {
|
||||
const h = m.toLowerCase();
|
||||
let brand = false;
|
||||
for (const s of BRAND_SUFFIXES) { if (h === s || h.endsWith('.' + s)) { brand = true; break; } }
|
||||
return brand ? m : '<customer-domain>';
|
||||
});
|
||||
|
||||
// Apply other swaps
|
||||
for (const [re, replacement] of swaps) v = v.replace(re, replacement);
|
||||
|
||||
if (v !== node.nodeValue) node.nodeValue = v;
|
||||
}
|
||||
|
||||
// ---- Mask sensitive content in input values ----
|
||||
document.querySelectorAll<HTMLInputElement>('input[type="text"], input[type="url"], input[type="email"], input:not([type])').forEach((el) => {
|
||||
if (!el.value) return;
|
||||
const v = el.value;
|
||||
let nv = v.replace(/\b([a-z0-9][a-z0-9-]{0,62}\.)+[a-z]{2,24}\b/gi, function (m) {
|
||||
const h = m.toLowerCase();
|
||||
let brand = false;
|
||||
for (const s of BRAND_SUFFIXES) { if (h === s || h.endsWith('.' + s)) { brand = true; break; } }
|
||||
return brand ? m : '<customer-domain>';
|
||||
});
|
||||
// Server / mail / NS hostnames
|
||||
nv = nv.replace(/whp\d+(-[a-z0-9]+)?\.cloud-hosting\.io/gi, '<your-server>.cloud-hosting.io');
|
||||
nv = nv.replace(/mail\d+\.cloud-hosting\.io/gi, '<your-mail-server>.cloud-hosting.io');
|
||||
nv = nv.replace(/ns[12]\.whp\d+\.cloud-hosting\.io/gi, 'ns<n>.<your-server>.cloud-hosting.io');
|
||||
// IPv4 — skip the well-known public resolvers / RFC1918 examples
|
||||
nv = nv.replace(/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, function (ip) {
|
||||
const pub = new Set(['1.1.1.1', '1.0.0.1', '8.8.8.8', '8.8.4.4', '9.9.9.9', '149.112.112.112', '208.67.222.222', '208.67.220.220']);
|
||||
if (pub.has(ip)) return ip;
|
||||
if (/^(10\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.)/.test(ip)) return ip; // private nets, fine
|
||||
return '<server-IP>';
|
||||
});
|
||||
if (nv !== v) el.value = nv;
|
||||
});
|
||||
|
||||
// ---- Mask customer usernames in table cells ----
|
||||
// Heuristic: find table cells under a header containing 'user', 'username', or 'target' (case-insensitive)
|
||||
document.querySelectorAll('table').forEach((tbl) => {
|
||||
const headers = Array.from(tbl.querySelectorAll('thead th, thead td')).map(th => (th.textContent || '').trim().toLowerCase());
|
||||
const userCols: number[] = [];
|
||||
for (let i = 0; i < headers.length; i++) {
|
||||
if (/^(user(name)?|target|owner|account)$/.test(headers[i])) userCols.push(i);
|
||||
}
|
||||
if (userCols.length === 0) return;
|
||||
const rows = tbl.querySelectorAll('tbody tr');
|
||||
for (let r = 0; r < rows.length; r++) {
|
||||
const cells = rows[r].querySelectorAll('td');
|
||||
for (const idx of userCols) {
|
||||
const cell = cells[idx];
|
||||
if (!cell) continue;
|
||||
const txt = (cell.textContent || '').trim();
|
||||
if (!txt) continue;
|
||||
let isSystem = false;
|
||||
for (const u of SYSTEM_USERS_ARR) { if (u === txt) { isSystem = true; break; } }
|
||||
if (!isSystem) cell.textContent = '<user>';
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function shot(page: Page, id: string) {
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForTimeout(500);
|
||||
await redact(page);
|
||||
const path = resolve(OUT_DIR, `${id}.png`);
|
||||
await page.screenshot({ path, fullPage: false });
|
||||
console.log(`captured ${id}`);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// Server Settings — click each tab
|
||||
await page.goto(`${BASE}/index.php?page=server-settings`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
await shot(page, 'admin-srvset-system');
|
||||
for (const tab of ['services', 'mail', 'dns', 'network', 'security']) {
|
||||
const trigger = page.locator(`[data-bs-target="#tab-${tab}"]`).first();
|
||||
if (await trigger.count() === 0) { console.log('tab trigger not found:', tab); continue; }
|
||||
await trigger.click().catch(()=>{});
|
||||
await page.waitForTimeout(1200);
|
||||
await shot(page, `admin-srvset-${tab}`);
|
||||
}
|
||||
|
||||
// Re-shoot the previously PII-heavy pages with v2 redaction
|
||||
const rerun = [
|
||||
{ id: 'admin-coraza', path: '/index.php?page=coraza-rules' },
|
||||
{ id: 'admin-monitor-admin', path: '/index.php?page=ai-monitor' },
|
||||
{ id: 'admin-ignore-rules', path: '/index.php?page=ai-monitor-ignore-rules' },
|
||||
{ id: 'admin-user-mgmt', path: '/index.php?page=user-management' },
|
||||
{ id: 'admin-user-resources', path: '/index.php?page=user-resources' },
|
||||
{ id: 'admin-issues', path: '/index.php?page=issues' },
|
||||
{ id: 'admin-suspensions', path: '/index.php?page=account-suspensions' },
|
||||
{ id: 'admin-disk-usage', path: '/index.php?page=disk-usage' },
|
||||
{ id: 'admin-docker', path: '/index.php?page=docker-management' },
|
||||
{ id: 'admin-valkey', path: '/index.php?page=valkey-admin' },
|
||||
{ id: 'admin-updates', path: '/index.php?page=update-management' },
|
||||
{ id: 'admin-container-boot', path: '/index.php?page=container-boot' },
|
||||
{ id: 'admin-site-audit', path: '/index.php?page=site-audit' },
|
||||
{ id: 'admin-delegated', path: '/index.php?page=delegated-users' },
|
||||
];
|
||||
for (const r of rerun) {
|
||||
await page.goto(`${BASE}${r.path}`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
await shot(page, r.id);
|
||||
}
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(e => { console.error(e); process.exit(1); });
|
||||