One IPv4 or IPv6 visitor address per line. These skip maintenance on every site, including sites with their own setting on. For Cloudflare sites, enter the visitor IP.
Visitors receive HTTP 503 with a five-minute Retry-After header. ACME certificate challenges and bypassed IPs remain live.
-
IP bypasses
One IPv4 or IPv6 visitor address per line. For Cloudflare sites, enter the visitor IP. Global bypasses from the overview also apply here.
-
-
${currentIp ? `` : ""}
-
+ ${bypassCard("bypass-ips", "IP bypasses", "These skip maintenance on this site, as do the global bypasses.", site.bypasses, currentIp, `saveBypasses('${escJs(site.domain)}')`)}
Maintenance page
Custom HTML and CSS are stored for this site. Active scripts and form controls are removed.
diff --git a/docs/decisions/maintenance.md b/docs/decisions/maintenance.md
index fa3b08a..8652d85 100644
--- a/docs/decisions/maintenance.md
+++ b/docs/decisions/maintenance.md
@@ -17,10 +17,11 @@ how many have maintenance saved off. While it is on, every site serves 503 and a
site whose own setting is off, or could not be read, shows a "Maintenance
(Global)" badge and is counted as in maintenance; turning it off returns each
site to its saved setting. Turning it on or off purges Varnish
-cache across the fleet. The card also saves up to 64 global IP bypasses. Each
+cache across the fleet. A card below it saves up to 64 global IP bypasses. Each
one applies to every site, including sites with their own maintenance setting
on, and remains saved when the global override is off. Site bypasses continue
-to apply to their own site.
+to apply to their own site. Both bypass lists keep Save off until the list
+differs from the saved one.
A domain query opens the focused editor for that site. That page is drawn in the
shell's site mode, so CloudPanel's site information and site tabs stay above it
diff --git a/tests/test-maintenance.test.ts b/tests/test-maintenance.test.ts
index dcd6422..cb09fa9 100644
--- a/tests/test-maintenance.test.ts
+++ b/tests/test-maintenance.test.ts
@@ -319,6 +319,19 @@ test("fleet overview renders the global override and badges according to global
])).not.toContain('id="global-toggle" disabled');
});
+test("both bypass lists offer the operator's IP and hold Save until the list changes", () => {
+ const fleet = fleetView([], { global: false, bypasses: ["203.0.113.8"] }, "198.51.100.4");
+ expect(fleet).toContain(`onclick="addCurrentIp('global-bypass-ips', '198.51.100.4')"`);
+ expect(fleet).toContain('data-bypass-save="global-bypass-ips" disabled');
+ const site = siteView(
+ { domain: "example.com", type: "php", user: "one", enabled: false, customTemplate: false, bypasses: [] },
+ { domain: "example.com", custom: false, html: "" },
+ "198.51.100.4",
+ );
+ expect(site).toContain(`onclick="addCurrentIp('bypass-ips', '198.51.100.4')"`);
+ expect(site).toContain('data-bypass-save="bypass-ips" disabled');
+});
+
test("siteView explains that the global override outranks this site's saved setting", () => {
const site = { domain: "one.example.com", type: "php", user: "one", enabled: false, customTemplate: false, bypasses: [] };
const template = { domain: "one.example.com", custom: false, html: "