9
0
mirror of https://github.com/donlon/cloudflare-error-page.git synced 2025-12-24 09:19:23 +00:00

7 Commits

Author SHA1 Message Date
Anthony Donlon
5a5311212e python: bump package version to 0.2.0 2025-12-22 22:48:41 +08:00
Anthony Donlon
9fbcba1a34 python: separate css from html template 2025-12-22 22:28:46 +08:00
Anthony Donlon
c0e576478a editor/web: fix generating Python code 2025-12-22 22:27:36 +08:00
Anthony Donlon
6233cec91f editor/web: support exporting generated webpage 2025-12-22 22:01:58 +08:00
Anthony Donlon
728ce52529 editor: support ok/error icon in shared page 2025-12-22 21:52:50 +08:00
Anthony Donlon
d0a05329d5 editor/web: open preview in about:blank tab 2025-12-22 21:44:14 +08:00
Anthony Donlon
51b61a8a2d Merge pull request #8 from donlon/save-as-dialog
Add Save As dialog to provide language examples
2025-12-22 21:34:48 +08:00
18 changed files with 80 additions and 146 deletions

View File

@@ -19,6 +19,10 @@ Here's an online editor to create customized error pages. Try it out [here](http
Install `cloudflare-error-page` with pip. Install `cloudflare-error-page` with pip.
``` Bash ``` Bash
# Install from PyPI
pip install cloudflare-error-page
# Or, install the latest version from this repo
pip install git+https://github.com/donlon/cloudflare-error-page.git pip install git+https://github.com/donlon/cloudflare-error-page.git
``` ```

View File

@@ -13,14 +13,14 @@ else:
from jinja2 import Environment, PackageLoader, Template, select_autoescape from jinja2 import Environment, PackageLoader, Template, select_autoescape
env = Environment( jinja_env = Environment(
loader=PackageLoader(__name__), loader=PackageLoader(__name__),
autoescape=select_autoescape(), autoescape=select_autoescape(),
trim_blocks=True, trim_blocks=True,
lstrip_blocks=True, lstrip_blocks=True,
) )
base_template: Template = env.get_template("error.html") base_template: Template = jinja_env.get_template('template.html')
class ErrorPageParams(TypedDict): class ErrorPageParams(TypedDict):
@@ -31,7 +31,7 @@ class ErrorPageParams(TypedDict):
for_text: NotRequired[str] # renamed to avoid Python keyword conflict for_text: NotRequired[str] # renamed to avoid Python keyword conflict
class StatusItem(TypedDict): class StatusItem(TypedDict):
status: NotRequired[Literal["ok", "error"]] status: NotRequired[Literal['ok', 'error']]
location: NotRequired[str] location: NotRequired[str]
name: NotRequired[str] name: NotRequired[str]
status_text: NotRequired[str] status_text: NotRequired[str]
@@ -57,7 +57,7 @@ class ErrorPageParams(TypedDict):
cloudflare_status: NotRequired[StatusItem] cloudflare_status: NotRequired[StatusItem]
host_status: NotRequired[StatusItem] host_status: NotRequired[StatusItem]
error_source: NotRequired[Literal["browser", "cloudflare", "host"]] error_source: NotRequired[Literal['browser', 'cloudflare', 'host']]
what_happened: NotRequired[str] what_happened: NotRequired[str]
what_can_i_do: NotRequired[str] what_can_i_do: NotRequired[str]
@@ -97,7 +97,7 @@ def render(params: ErrorPageParams,
if not params.get('time'): if not params.get('time'):
utc_now = datetime.now(timezone.utc) utc_now = datetime.now(timezone.utc)
params['time'] = utc_now.strftime("%Y-%m-%d %H:%M:%S UTC") params['time'] = utc_now.strftime('%Y-%m-%d %H:%M:%S UTC')
if not params.get('ray_id'): if not params.get('ray_id'):
params['ray_id'] = secrets.token_hex(8) params['ray_id'] = secrets.token_hex(8)
if not allow_html: if not allow_html:
@@ -106,5 +106,5 @@ def render(params: ErrorPageParams,
return template.render(params=params, *args, **kwargs) return template.render(params=params, *args, **kwargs)
__version__ = "0.1.0" __version__ = '0.2.0'
__all__ = ['base_template', 'render'] __all__ = ['jinja_env', 'base_template', 'render']

File diff suppressed because one or more lines are too long

View File

@@ -14,7 +14,15 @@
<meta name="robots" content="noindex, nofollow" /> <meta name="robots" content="noindex, nofollow" />
<meta name="viewport" content="width=device-width,initial-scale=1" /> <meta name="viewport" content="width=device-width,initial-scale=1" />
{% block html_head %}{% endblock %} {% block html_head %}{% endblock %}
<!-- @INLINE_CSS_HERE@ --> <style>
{% if html_style %}
{# Support custom stylesheet #}
{{ html_style | safe }}
{% else %}
{# Default stylesheet #}
{% include 'main.css' %}
{% endif %}
</style>
</head> </head>
<body> <body>
<div id="cf-wrapper"> <div id="cf-wrapper">

View File

@@ -8,11 +8,14 @@ SHARE_LINK_DIGITS = 7
SHORT_SHARE_URL = false SHORT_SHARE_URL = false
# Icon URL for rendered pages # Icon URL for rendered pages
PAGE_ICON_URL = '' PAGE_ICON_URL = 'https://virt.moe/cferr/editor/assets/icon-{status}-32x32.png'
# MIME type of page icon # MIME type of page icon
PAGE_ICON_TYPE = 'image/png' PAGE_ICON_TYPE = 'image/png'
# Image URL for rendered pages
PAGE_IMAGE_URL = 'https://virt.moe/cferr/editor/assets/icon-{status}-large-white.png'
# Set to true if trust X-Forwarded-For/X-Forwarded-Proto header # Set to true if trust X-Forwarded-For/X-Forwarded-Proto header
BEHIND_PROXY = true BEHIND_PROXY = true

View File

@@ -122,21 +122,23 @@ def render_extended_template(params: ErrorPageParams,
*args: Any, *args: Any,
**kwargs: Any) -> str: **kwargs: Any) -> str:
fill_cf_template_params(params) fill_cf_template_params(params)
description = params.get('what_happened') or 'Cloudflare error page' description = params.get('what_happened') or 'There is an internal server error on Cloudflare\'s network.'
description = re.sub(r'</?.*?>', '', description).strip() description = re.sub(r'<\/?.*?>', '', description).strip()
page_image_id = 'ok' status = 'ok'
cf_status_obj = params.get('cloudflare_status') cf_status_obj = params.get('cloudflare_status')
if cf_status_obj: if cf_status_obj:
cf_status = cf_status_obj.get('status') cf_status = cf_status_obj.get('status')
if cf_status == 'error': if cf_status == 'error':
page_image_id = 'error' status = 'error'
page_image_url = f'https://virt.moe/cferr/editor/assets/icon-{page_image_id}-large.png' page_icon_url = current_app.config.get('PAGE_ICON_URL', '').replace('{status}', status)
page_icon_type = current_app.config.get('PAGE_ICON_TYPE')
page_image_url = current_app.config.get('PAGE_IMAGE_URL', '').replace('{status}', status)
return render_cf_error_page(params=params, return render_cf_error_page(params=params,
template=template, template=template,
base=base_template, base=base_template,
page_icon_url=current_app.config.get('PAGE_ICON_URL'), page_icon_url=page_icon_url,
page_icon_type=current_app.config.get('PAGE_ICON_TYPE'), page_icon_type=page_icon_type,
page_url=request.url, page_url=request.url,
page_description=description, page_description=description,
page_image_url=page_image_url, page_image_url=page_image_url,

Binary file not shown.

After

Width:  |  Height:  |  Size: 676 B

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 708 B

After

Width:  |  Height:  |  Size: 708 B

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -14,7 +14,7 @@
<meta name="description" content="Online editor to create customized Cloudflare-style error pages."> <meta name="description" content="Online editor to create customized Cloudflare-style error pages.">
<meta name="keywords" content="cloudflare,error,page,editor"> <meta name="keywords" content="cloudflare,error,page,editor">
<link rel="canonical" href="https://virt.moe/cferr/editor/" /> <link rel="canonical" href="https://virt.moe/cferr/editor/" />
<link rel="icon" type="image/png" href="assets/icon-32x32.png"> <link rel="icon" type="image/png" href="assets/icon-ok-32x32.png">
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:site_name" content="moe::virt" /> <meta property="og:site_name" content="moe::virt" />
@@ -532,6 +532,9 @@
<button type="button" data-type="js" class="list-group-item list-group-item-action"> <button type="button" data-type="js" class="list-group-item list-group-item-action">
NodeJS Example NodeJS Example
</button> </button>
<button type="button" data-type="static" class="list-group-item list-group-item-action">
Static Page
</button>
</div> </div>
<div class="d-flex gap-1 save-as-dialog__buttons"> <div class="d-flex gap-1 save-as-dialog__buttons">
<button type="button" class="btn btn-success" id="saveAsDialogCopyBtn" data-bs-toggle="popover" <button type="button" class="btn btn-success" id="saveAsDialogCopyBtn" data-bs-toggle="popover"

View File

@@ -23,7 +23,7 @@ app = Flask(__name__)
@app.route('/') @app.route('/')
def index(): def index():
# Render the error page # Render the error page
return render_cf_error_page(<%=paramsArg.replaceAll('\n', '\n ')%>), 500 return render_cf_error_page(<%- paramsArg.replaceAll('\n', '\n ') %>), 500
if __name__ == '__main__': if __name__ == '__main__':
app.run(debug=True, port=5000) app.run(debug=True, port=5000)

View File

@@ -305,10 +305,8 @@ function render() {
let lastRenderedHtml = ''; let lastRenderedHtml = '';
function openInNewTab() { function openInNewTab() {
if (!lastRenderedHtml) render(); if (!lastRenderedHtml) render();
const blob = new Blob([lastRenderedHtml], { type: 'text/html' }); const wnd = window.open()
const url = URL.createObjectURL(blob); wnd.document.documentElement.innerHTML = lastRenderedHtml
window.open(url, '_blank', 'noopener');
// note that this url won't be revoked
} }
function createShareableLink() { function createShareableLink() {
@@ -447,7 +445,15 @@ function updateSaveAsDialog(e) {
} }
const params = { ...lastCfg }; const params = { ...lastCfg };
delete params.time; delete params.time;
$('saveAsDialogCode').innerHTML = saveAsContent = codegen.generate(params); if (codegen) {
saveAsContent = codegen.generate(params);
} else if (saveAsType == 'static') {
render() // rerender the page
saveAsContent = lastRenderedHtml;
} else {
throw new Error('unexpected saveAsType=' + saveAsType)
}
$('saveAsDialogCode').value = saveAsContent;
$('saveAsDialogCode').scrollTop = 0; $('saveAsDialogCode').scrollTop = 0;
document.querySelectorAll('#saveAsDialogTypes button').forEach((element) => { document.querySelectorAll('#saveAsDialogTypes button').forEach((element) => {
@@ -491,6 +497,10 @@ $('saveAsDialogSaveBtn').addEventListener('click', (e) => {
case 'python': case 'python':
saveName = 'cf_error_page_example.py'; saveName = 'cf_error_page_example.py';
break; break;
case 'static':
saveName = 'cf_error_page.html';
break;
// TODO: name output files using page title
} }
saveFile(saveAsContent, saveName); saveFile(saveAsContent, saveName);
}); });

View File

@@ -23,5 +23,8 @@ include = [
"cloudflare_error_page/templates/*", "cloudflare_error_page/templates/*",
] ]
[tool.hatch.build.targets.wheel.hooks.custom]
path = "scripts/hatch_build.py"
[tool.hatch.version] [tool.hatch.version]
path = "cloudflare_error_page/__init__.py" path = "cloudflare_error_page/__init__.py"

File diff suppressed because one or more lines are too long

17
scripts/hatch_build.py Normal file
View File

@@ -0,0 +1,17 @@
import os
import sys
import shutil
from pathlib import Path
from typing import Any
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
sys.path.append(os.path.dirname(__file__))
from inline_resources import generate_inlined_css
class CustomBuildHook(BuildHookInterface):
def initialize(self, version: str, build_data: dict[str, Any]):
generate_inlined_css()
src = Path(self.root) / 'resources' / 'styles' / 'main.css'
dst = Path(self.root) / 'cloudflare_error_page' / 'templates'
shutil.copy(src, dst)

View File

@@ -4,7 +4,7 @@ from urllib.parse import quote
root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
resources_folder = os.path.join(root,'resources') resources_folder = os.path.join(root, 'resources')
def read_file(path: str) -> str: def read_file(path: str) -> str:
@@ -49,7 +49,7 @@ def inline_css_resource(original_file: str, css_file: str, output_file: str):
write_file(output_file, original_data) write_file(output_file, original_data)
if __name__ == '__main__': def generate_inlined_css():
inline_svg_resources( inline_svg_resources(
os.path.join(resources_folder, 'styles/main-original.css'), os.path.join(resources_folder, 'styles/main-original.css'),
[ [
@@ -61,11 +61,10 @@ if __name__ == '__main__':
], ],
os.path.join(resources_folder, 'styles/main.css'), os.path.join(resources_folder, 'styles/main.css'),
) )
inline_css_resource(
os.path.join(resources_folder, 'templates/error.html'),
os.path.join(resources_folder, 'styles/main.css'), if __name__ == '__main__':
os.path.join(root, 'cloudflare_error_page/templates/error.html'), generate_inlined_css()
)
inline_css_resource( inline_css_resource(
os.path.join(resources_folder, 'templates/error.ejs'), os.path.join(resources_folder, 'templates/error.ejs'),
os.path.join(resources_folder, 'styles/main.css'), os.path.join(resources_folder, 'styles/main.css'),