Compare commits
7 Commits
save-as-di
...
python-v0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a5311212e | ||
|
|
9fbcba1a34 | ||
|
|
c0e576478a | ||
|
|
6233cec91f | ||
|
|
728ce52529 | ||
|
|
d0a05329d5 | ||
|
|
51b61a8a2d |
@@ -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
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -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']
|
||||||
|
|||||||
@@ -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">
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
BIN
editor/web/assets/icon-error-32x32.png
Normal file
|
After Width: | Height: | Size: 676 B |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 708 B After Width: | Height: | Size: 708 B |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
@@ -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"
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
1
resources/styles/main.css
Normal file
17
scripts/hatch_build.py
Normal 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)
|
||||||
@@ -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'),
|
||||||
|
|||||||