9
0
mirror of https://github.com/donlon/cloudflare-error-page.git synced 2026-01-06 15:41:45 +00:00

misc updates

This commit is contained in:
Anthony Donlon
2025-12-27 01:39:06 +08:00
parent e2226ff5bb
commit f2f4a8223d
11 changed files with 69 additions and 52 deletions

View File

@@ -4,7 +4,7 @@
## What does this project do? ## What does this project do?
This project creates customized error pages that mimics the well-known Cloudflare error page. You can also embed it into your website. This project creates customized error pages that mimic the well-known Cloudflare error page. You can also embed it into your own website.
## Online Editor ## Online Editor
@@ -16,7 +16,7 @@ Here's an online editor to create customized error pages and example server apps
### Python ### Python
Install `cloudflare-error-page` with pip. Install `cloudflare-error-page` using pip.
``` Bash ``` Bash
# Install from PyPI # Install from PyPI
@@ -26,7 +26,7 @@ pip install cloudflare-error-page
pip install git+https://github.com/donlon/cloudflare-error-page.git pip install git+https://github.com/donlon/cloudflare-error-page.git
``` ```
Then you can generate an error page using the `render` function. ([example.py](examples/example.py)) Then an error page can be generated using the `render` function provided by the package. ([example.py](examples/example.py))
``` Python ``` Python
import webbrowser import webbrowser
@@ -48,7 +48,7 @@ error_page = render_cf_error_page({
"status": 'ok', "status": 'ok',
"location": 'example.com', "location": 'example.com',
}, },
# can be 'browser', 'cloudflare', or 'host' # Position of the error indicator, valid options are 'browser', 'cloudflare', and 'host'
'error_source': 'cloudflare', 'error_source': 'cloudflare',
# Texts shown in the bottom of the page # Texts shown in the bottom of the page
@@ -56,9 +56,11 @@ error_page = render_cf_error_page({
'what_can_i_do': '<p>Please try again in a few minutes.</p>', 'what_can_i_do': '<p>Please try again in a few minutes.</p>',
}) })
# Write generated webpage to file
with open('error.html', 'w') as f: with open('error.html', 'w') as f:
f.write(error_page) f.write(error_page)
# Open the generated page in browser
webbrowser.open('error.html') webbrowser.open('error.html')
``` ```
@@ -83,14 +85,14 @@ import { render as render_cf_error_page } from 'cloudflare-error-page';
const app = express(); const app = express();
app.get('/', (req, res) => { app.get('/', (req, res) => {
/* Some code that break prod. Recently pushed by a new employee. */ /* Some code that break prod. Pushed by a new employee recently. */
let [feature_values, _] = features let [feature_values, _] = features
.append_with_names(self.config.feature_names) .append_with_names(self.config.feature_names)
.unwrap(); .unwrap();
} }
/* Handle the error intelligently by using a custom error handler */
app.use((err, req, res) => { app.use((err, req, res) => {
/* Handle the error intelligently by using a custom handler */
res.status(500).send(render_cf_error_page({ res.status(500).send(render_cf_error_page({
"title": "Internal server error", "title": "Internal server error",
"error_code": "500", "error_code": "500",
@@ -182,15 +184,15 @@ params = {
Ray ID and user IP field in the error page can be set by `ray_id` and `client_ip` properties in the `params` argument passed to the render function. The real Cloudflare Ray ID and the data center location of current request can be extracted from the `Cf-Ray` request header (e.g. `Cf-Ray: 230b030023ae2822-SJC`). Detailed description of this header can be found at [Cloudflare documentation](https://developers.cloudflare.com/fundamentals/reference/http-headers/#cf-ray). Ray ID and user IP field in the error page can be set by `ray_id` and `client_ip` properties in the `params` argument passed to the render function. The real Cloudflare Ray ID and the data center location of current request can be extracted from the `Cf-Ray` request header (e.g. `Cf-Ray: 230b030023ae2822-SJC`). Detailed description of this header can be found at [Cloudflare documentation](https://developers.cloudflare.com/fundamentals/reference/http-headers/#cf-ray).
To lookup the city name of the data center corresponding to the three letter code in the header, you can use a location list from [here](https://github.com/Netrvin/cloudflare-colo-list/blob/main/DC-Colos.json) To lookup the city name of the data center corresponding to the three letter code in the header, you can use a location list [here](https://github.com/Netrvin/cloudflare-colo-list/blob/main/DC-Colos.json)
The demo server runs in our website did handle these. Take a look at [this file](https://github.com/donlon/cloudflare-error-page/blob/94c3c4ddbe521dee0c9a880ef33fa7a9f0720cbe/editor/server/utils.py#L34) for reference. The demo server runs in our website did handle these. Take a look at [this file](https://github.com/donlon/cloudflare-error-page/blob/e2226ff5bb7a877c9fe3ac09deadccdc58b0c1c7/editor/server/utils.py#L78) for reference.
## See also ## See also
- [cloudflare-error-page-3th.pages.dev](https://cloudflare-error-page-3th.pages.dev/) - [cloudflare-error-page-3th.pages.dev](https://cloudflare-error-page-3th.pages.dev/)
Error page of every HTTP status code (reload to show random page). Error page of every HTTP status code (reload to show random pages).
- [oftx/cloudflare-error-page](https://github.com/oftx/cloudflare-error-page) - [oftx/cloudflare-error-page](https://github.com/oftx/cloudflare-error-page)
@@ -205,7 +207,7 @@ The demo server runs in our website did handle these. Take a look at [this file]
"error_code": "500", "error_code": "500",
"time": "2025-11-18 12:34:56 UTC", // Current UTC time will be shown if empty "time": "2025-11-18 12:34:56 UTC", // Current UTC time will be shown if empty
// Configuration of "Visit ... for more information" line // Configuration of "Visit ... for more information"
"more_information": { "more_information": {
"hidden": false, "hidden": false,
"text": "cloudflare.com", "text": "cloudflare.com",
@@ -213,7 +215,7 @@ The demo server runs in our website did handle these. Take a look at [this file]
"for": "more information", "for": "more information",
}, },
// Configuration of the Browser/Cloudflare/Host status // Configuration of the Browser/Cloudflare/Host status block
"browser_status": { "browser_status": {
"status": "ok", // "ok" or "error" "status": "ok", // "ok" or "error"
"location": "You", "location": "You",
@@ -235,7 +237,7 @@ The demo server runs in our website did handle these. Take a look at [this file]
"status_text": "Working", "status_text": "Working",
"status_text_color": "#9bca3e", "status_text_color": "#9bca3e",
}, },
// Position of the error indicator, can be "browser", "cloudflare", or "host" // Position of the error indicator, valid options are 'browser', 'cloudflare', and 'host'
"error_source": "host", "error_source": "host",
"what_happened": "<p>There is an internal server error on Cloudflare's network.</p>", "what_happened": "<p>There is an internal server error on Cloudflare's network.</p>",

View File

@@ -169,7 +169,6 @@
grid-area: code; grid-area: code;
width: 100%; width: 100%;
height: 100%; height: 100%;
min-height: 700px !important;
max-height: 1200px !important; max-height: 1200px !important;
font-family: monospace; font-family: monospace;
font-size: 0.8em !important; font-size: 0.8em !important;
@@ -235,7 +234,7 @@
<label for="presetSelect">Preset</label> <label for="presetSelect">Preset</label>
<select id="presetSelect" class="form-select form-select-sm"> <select id="presetSelect" class="form-select form-select-sm">
<option value="default">Internal server error (Default)</option> <option value="default">Internal server error (Default)</option>
<option value="empty">Empty</option> <option value="empty">(Empty)</option>
<option value="catastrophic">Catastrophic failure</option> <option value="catastrophic">Catastrophic failure</option>
<option value="working">Server working</option> <option value="working">Server working</option>
<option value="teapot">Teapot</option> <option value="teapot">Teapot</option>

View File

@@ -29,9 +29,9 @@ class EjsCodeGen implements CodeGen {
} }
} }
function getErrorCode(error_code?: string | number) { function getErrorCode(error_code?: string | number): string {
const errorCode = error_code || ''; const errorCode = error_code ?? '';
return /\d{3}/.test(errorCode + '') ? errorCode : 500; return /\d{3}/.test(errorCode + '') ? errorCode : '500';
} }
class JSCodeGen extends EjsCodeGen { class JSCodeGen extends EjsCodeGen {
@@ -72,7 +72,7 @@ class PythonCodeGen extends EjsCodeGen {
params.client_ip = clientIpKey; params.client_ip = clientIpKey;
const paramsArg = JSON.stringify( const paramsArg = JSON.stringify(
params, params,
(key, value) => { (_, value) => {
if (typeof value === 'boolean') { if (typeof value === 'boolean') {
return randomKey + value.toString(); return randomKey + value.toString();
} else if (value === null) { } else if (value === null) {
@@ -93,7 +93,6 @@ class PythonCodeGen extends EjsCodeGen {
protected prepareTemplateArgs(params: ErrorPageParams): Record<string, any> { protected prepareTemplateArgs(params: ErrorPageParams): Record<string, any> {
return { return {
errorCode: getErrorCode(params.error_code), errorCode: getErrorCode(params.error_code),
// TODO: format to JS-style object (key w/o parens)
indentedParams: this.formatPythonArgs(params).replaceAll('\n', '\n '), indentedParams: this.formatPythonArgs(params).replaceAll('\n', '\n '),
}; };
} }

View File

@@ -2,12 +2,12 @@
/* /*
* JavaScript Example: Render and serve the Cloudflare error page using Express server * JavaScript Example: Render and serve the Cloudflare error page using Express server
* *
* Prerequisits: * Prerequisites:
* npm install express cloudflare-error-page * npm install express cloudflare-error-page
*/ */
import express from 'express'; import express from 'express';
import { render as render_cf_error_page } from 'cloudflare-error-page'; import { render } from 'cloudflare-error-page';
const app = express(); const app = express();
const port = 3000; const port = 3000;
@@ -15,7 +15,8 @@ const port = 3000;
<%# TODO: use the library as middleware? _%> <%# TODO: use the library as middleware? _%>
// Define a route for GET requests to the root URL // Define a route for GET requests to the root URL
app.get('/', (req, res) => { app.get('/', (req, res) => {
res.status(<%= errorCode %>).send(render_cf_error_page(<%- indentedParams %>)); // Render the error page and send to client
res.status(<%= errorCode %>).send(render(<%- indentedParams %>));
}); });
// Start the server and listen on the specified port // Start the server and listen on the specified port

View File

@@ -2,7 +2,7 @@
# #
# Python Example: Render and serve the Cloudflare error page using Flask server # Python Example: Render and serve the Cloudflare error page using Flask server
# #
# Prerequisits: # Prerequisites:
# pip install flask cloudflare-error-page # pip install flask cloudflare-error-page
# #
@@ -10,13 +10,15 @@ from flask import Flask, request
from cloudflare_error_page import render as render_cf_error_page from cloudflare_error_page import render as render_cf_error_page
app = Flask(__name__) app = Flask(__name__)
port = 5000
<%# TODO: use the library as middleware? _%> <%# TODO: use the library as middleware? _%>
# Define a route for GET requests to the root URL # Define a route for GET requests to the root URL
@app.route("/") @app.route("/")
def index(): def index():
# Render the error page # Render the error page and send to client
return render_cf_error_page(<%- indentedParams %>), <%= errorCode %> return render_cf_error_page(<%- indentedParams %>), <%= errorCode %>
if __name__ == "__main__": if __name__ == "__main__":
app.run(debug=True, port=5000) # Start the server and listen on the specified port
app.run(debug=True, port=port)

View File

@@ -53,9 +53,7 @@ let initialConfig = {
// Demo presets (content brief — replace or expand as needed) // Demo presets (content brief — replace or expand as needed)
const PRESETS = { const PRESETS = {
default: structuredClone(initialConfig), default: structuredClone(initialConfig),
empty: { empty: {},
error_code: '500',
},
catastrophic: { catastrophic: {
title: 'Catastrophic infrastructure failure', title: 'Catastrophic infrastructure failure',
error_code: '500', error_code: '500',
@@ -104,7 +102,7 @@ const PRESETS = {
what_can_i_do: 'Visit the site before it crashes someday.', what_can_i_do: 'Visit the site before it crashes someday.',
}, },
teapot: { teapot: {
title: "I'm a teapot", title: "I'm a Teapot",
error_code: '418', error_code: '418',
more_information: { more_information: {
text: 'rfc2324', text: 'rfc2324',
@@ -112,20 +110,16 @@ const PRESETS = {
}, },
browser_status: { browser_status: {
status: 'ok', status: 'ok',
location: 'You',
status_text: 'Working',
}, },
cloudflare_status: { cloudflare_status: {
status: 'ok', status: 'ok',
status_text: 'Working',
}, },
host_status: { host_status: {
status: 'ok', status: 'ok',
location: 'Teapot', location: 'Teapot',
status_text: 'Working',
}, },
error_source: 'host', error_source: 'host',
what_happened: "The server can not brew coffee for it is a teapot.", what_happened: 'The server can not brew coffee for it is a teapot.',
what_can_i_do: 'Please try a different coffee machine.', what_can_i_do: 'Please try a different coffee machine.',
}, },
consensual: { consensual: {
@@ -306,7 +300,7 @@ function render() {
doc.write(pageHtml); doc.write(pageHtml);
doc.close(); doc.close();
updateStatusBlockStyles(); updateStatusBlock();
// store last rendered HTML for "open in new tab" // store last rendered HTML for "open in new tab"
lastRenderedHtml = pageHtml; lastRenderedHtml = pageHtml;
@@ -321,7 +315,7 @@ function openInNewTab() {
} }
function createShareableLink() { function createShareableLink() {
$('shareLink').value = ''; $('shareLink').value = 'Creating...';
fetch('../s/create', { fetch('../s/create', {
method: 'POST', method: 'POST',
headers: { headers: {
@@ -333,16 +327,19 @@ function createShareableLink() {
}) })
.then((response) => { .then((response) => {
if (!response.ok) { if (!response.ok) {
alert('failed to create link'); throw new Error('failed to create link');
} }
return response.json(); return response.json();
}) })
.then((result) => { .then((result) => {
if (result.status != 'ok') { if (result.status != 'ok') {
alert('failed to create link'); throw new Error('failed to create link');
return;
} }
$('shareLink').value = result.url; $('shareLink').value = result.url;
})
.catch((e) => {
alert(e);
$('shareLink').value = '';
}); });
} }
function resizePreviewFrame() { function resizePreviewFrame() {
@@ -352,7 +349,7 @@ function resizePreviewFrame() {
} }
/* Update status block colors based on selected status and error_source */ /* Update status block colors based on selected status and error_source */
function updateStatusBlockStyles() { function updateStatusBlock() {
const browserOk = $('browser_status').value === 'ok'; const browserOk = $('browser_status').value === 'ok';
const cfOk = $('cloudflare_status').value === 'ok'; const cfOk = $('cloudflare_status').value === 'ok';
const hostOk = $('host_status').value === 'ok'; const hostOk = $('host_status').value === 'ok';
@@ -360,6 +357,10 @@ function updateStatusBlockStyles() {
setBlockClass('block_browser', browserOk ? 'status-ok' : 'status-error'); setBlockClass('block_browser', browserOk ? 'status-ok' : 'status-error');
setBlockClass('block_cloudflare', cfOk ? 'status-ok' : 'status-error'); setBlockClass('block_cloudflare', cfOk ? 'status-ok' : 'status-error');
setBlockClass('block_host', hostOk ? 'status-ok' : 'status-error'); setBlockClass('block_host', hostOk ? 'status-ok' : 'status-error');
$('browser_status_text').placeholder = browserOk ? 'Working' : 'Error';
$('cloudflare_status_text').placeholder = cfOk ? 'Working' : 'Error';
$('host_status_text').placeholder = hostOk ? 'Working' : 'Error';
} }
function setBlockClass(id, cls) { function setBlockClass(id, cls) {

View File

@@ -34,7 +34,7 @@ fs.writeFileSync('error.html', errorPage);
## API Reference ## API Reference
### `render(params: ErrorPageParams, allowHtml?: boolean, moreArgs?: { [name: string]: any; }): string` ### `render(params: ErrorPageParams, allowHtml?: boolean, moreArgs?: Record<string, any>): string`
Generates an HTML error page based on the provided parameters. Generates an HTML error page based on the provided parameters.

View File

@@ -42,7 +42,6 @@
}, },
"files": [ "files": [
"dist/**/*", "dist/**/*",
"templates/**/*",
"README.md", "README.md",
"LICENSE" "LICENSE"
] ]

View File

@@ -4,10 +4,8 @@ import { createFilter } from "@rollup/pluginutils";
function createRawImportPlugin(include: string) { function createRawImportPlugin(include: string) {
const rawFilter = createFilter(include); const rawFilter = createFilter(include);
return { return {
name: "raw-import", name: "raw-import",
transform(code: string, id: string): any { transform(code: string, id: string): any {
if (rawFilter(id)) { if (rawFilter(id)) {
return { return {

View File

@@ -75,9 +75,7 @@ function genHexString(digits: number): string {
export function render( export function render(
params: ErrorPageParams, params: ErrorPageParams,
allowHtml: boolean = true, allowHtml: boolean = true,
moreArgs: { moreArgs: Record<string, any> = {}
[name: string]: any;
} = {}
): string { ): string {
params = { ...params }; params = { ...params };

View File

@@ -4,23 +4,41 @@ build-backend = "hatchling.build"
[project] [project]
name = "cloudflare-error-page" name = "cloudflare-error-page"
dynamic = [ "version" ]
description = "A customizable Cloudflare error page generator" description = "A customizable Cloudflare error page generator"
authors = [{ name = "Anthony Donlon" }]
license = { text = "MIT" }
readme = "README.md" readme = "README.md"
requires-python = ">=3.10" requires-python = ">=3.10"
license = "MIT"
authors = [{ name = "Anthony Donlon" }]
classifiers = [
"Development Status :: 4 - Beta",
"Programming Language :: Python",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
]
dependencies = [ dependencies = [
"jinja2>=3.0" "jinja2>=3.0"
] ]
dynamic = [ "version" ]
[project.urls] [project.urls]
Homepage = "https://github.com/donlon/cloudflare-error-page" Homepage = "https://github.com/donlon/cloudflare-error-page"
[tool.hatch.build] [tool.hatch.build]
include = [ include = [
"cloudflare_error_page/*.py", "cloudflare_error_page/**/*",
"cloudflare_error_page/templates/*", ]
exclude = [".gitignore"]
[tool.hatch.build.targets.sdist]
include = [
"cloudflare_error_page/**/*",
"resources/**/*",
"scripts/**/*",
] ]
[tool.hatch.build.targets.wheel.hooks.custom] [tool.hatch.build.targets.wheel.hooks.custom]