From 38d19fa12df5f505e22e2300e0e01fcd122b3fb3 Mon Sep 17 00:00:00 2001 From: Anthony Donlon Date: Tue, 9 Dec 2025 23:56:28 +0800 Subject: [PATCH] inline css/svg files to avoid the use of external resources --- cloudflare_error_page/__init__.py | 11 +- cloudflare_error_page/templates/error.html | 6 +- editor/resources/index.html | 3 +- editor/resources/template.ejs | 7 +- editor/server/examples.py | 2 +- editor/server/share.py | 3 +- examples/flask_demo.py | 16 +-- pyproject.toml | 1 - .../images/cf-icon-browser.svg | 0 .../images/cf-icon-cloud.svg | 0 .../images/cf-icon-error.svg | 0 .../images/cf-icon-ok.svg | 0 .../images/cf-icon-server.svg | 0 resources/styles/.gitignore | 1 + .../styles/main-original.css | 0 resources/templates/error.ejs | 123 ++++++++++++++++++ resources/templates/error.html | 114 ++++++++++++++++ scripts/inline_resources.py | 73 +++++++++++ 18 files changed, 324 insertions(+), 36 deletions(-) rename {cloudflare_error_page/resources => resources}/images/cf-icon-browser.svg (100%) rename {cloudflare_error_page/resources => resources}/images/cf-icon-cloud.svg (100%) rename {cloudflare_error_page/resources => resources}/images/cf-icon-error.svg (100%) rename {cloudflare_error_page/resources => resources}/images/cf-icon-ok.svg (100%) rename {cloudflare_error_page/resources => resources}/images/cf-icon-server.svg (100%) create mode 100644 resources/styles/.gitignore rename cloudflare_error_page/resources/styles/main.css => resources/styles/main-original.css (100%) create mode 100644 resources/templates/error.ejs create mode 100644 resources/templates/error.html create mode 100644 scripts/inline_resources.py diff --git a/cloudflare_error_page/__init__.py b/cloudflare_error_page/__init__.py index db47b0f..6f6d0c3 100644 --- a/cloudflare_error_page/__init__.py +++ b/cloudflare_error_page/__init__.py @@ -14,13 +14,6 @@ env = Environment( default_template = env.get_template("error.html") -def get_resources_folder() -> str: - """ - Get resources folder that contains stylesheet and icons for the error page. - If you pass resources_use_cdn=True to render(), local resources are not used. - """ - return os.path.join(os.path.dirname(os.path.abspath(__file__)), 'resources') - def fill_params(params: dict): if not params.get('time'): @@ -30,7 +23,7 @@ def fill_params(params: dict): params['ray_id'] = secrets.token_hex(8) -def render(params: dict, allow_html: bool=True, use_cdn: bool=True) -> str: +def render(params: dict, allow_html: bool=True) -> str: """ Render a customized Cloudflare error page. """ @@ -40,4 +33,4 @@ def render(params: dict, allow_html: bool=True, use_cdn: bool=True) -> str: params['what_happened'] = html.escape(params.get('what_happened', '')) params['what_can_i_do'] = html.escape(params.get('what_can_i_do', '')) - return default_template.render(params=params, resources_use_cdn=use_cdn) + return default_template.render(params=params) diff --git a/cloudflare_error_page/templates/error.html b/cloudflare_error_page/templates/error.html index 22953ec..7cb6511 100644 --- a/cloudflare_error_page/templates/error.html +++ b/cloudflare_error_page/templates/error.html @@ -1,5 +1,4 @@ -{% set resources_cdn = 'https://cloudflare.com' if resources_use_cdn else '' %} - +{# Note: This is generated with scripts/inline_resources.py. Please do not edit this file manually. #} @@ -16,7 +15,8 @@ {% block header %}{% endblock %} - +
diff --git a/editor/resources/index.html b/editor/resources/index.html index be4e41d..c81cbc4 100644 --- a/editor/resources/index.html +++ b/editor/resources/index.html @@ -299,10 +299,9 @@ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds} UTC`; } - function renderEjs(params, use_cdn = true) { + function renderEjs(params) { return template({ params: params, - resources_use_cdn: use_cdn }) } diff --git a/editor/resources/template.ejs b/editor/resources/template.ejs index 84f0f30..5346a48 100644 --- a/editor/resources/template.ejs +++ b/editor/resources/template.ejs @@ -1,6 +1,4 @@ -<% -let resources_cdn = resources_use_cdn ? 'https://cloudflare.com' : ''; -%> +<%# Note: This is generated with scripts/inline_resources.py. Please do not edit this file manually. %> @@ -18,7 +16,8 @@ let html_title_output = params.html_title || (error_code + ': ' + title); - +
diff --git a/editor/server/examples.py b/editor/server/examples.py index bc3f80f..e98c41e 100644 --- a/editor/server/examples.py +++ b/editor/server/examples.py @@ -53,4 +53,4 @@ def index(name: str): fill_cf_template_params(params) # Render the error page - return render_cf_error_page(params, use_cdn=True) + return render_cf_error_page(params) diff --git a/editor/server/share.py b/editor/server/share.py index f13253e..42df085 100644 --- a/editor/server/share.py +++ b/editor/server/share.py @@ -125,5 +125,4 @@ def get(name: str): return template.render(base=cf_template, params=params, url=request.url, - description='Cloudflare error page', - resources_use_cdn=True) + description='Cloudflare error page') diff --git a/examples/flask_demo.py b/examples/flask_demo.py index 0dd9727..e05367f 100755 --- a/examples/flask_demo.py +++ b/examples/flask_demo.py @@ -14,22 +14,10 @@ from flask import ( examples_dir = os.path.dirname(os.path.abspath(__file__)) sys.path.append(os.path.dirname(examples_dir)) -from cloudflare_error_page import get_resources_folder, render as render_cf_error_page +from cloudflare_error_page import render as render_cf_error_page app = Flask(__name__) -# Resources required for the error page can be loaded from Cloudflare CDN. But in case of changes, you can set use_cdn = False to use bundled resources. -use_cdn = True - -if not use_cdn: - res_folder = get_resources_folder() - - # This handler is used to provide stylesheet and icon resources for the error page. If you pass use_cdn=True to render_cf_error_page - # or if your site is under proxy of Cloudflare (the cdn-cgi folder is already provided by Cloudflare), this handler can be removed. - @app.route('/cdn-cgi/') - def cdn_cgi(path: str): - return send_from_directory(res_folder, path) - @app.route('/') def index(): @@ -66,7 +54,7 @@ def index(): }) # Render the error page - return render_cf_error_page(params, use_cdn=use_cdn), 500 + return render_cf_error_page(params), 500 if __name__ == '__main__': diff --git a/pyproject.toml b/pyproject.toml index b837fdf..81aa0c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,5 @@ Homepage = "https://github.com/donlon/cloudflare-error-page" [tool.hatch.build] include = [ "cloudflare_error_page/*.py", - "cloudflare_error_page/resources/*", "cloudflare_error_page/templates/*", ] diff --git a/cloudflare_error_page/resources/images/cf-icon-browser.svg b/resources/images/cf-icon-browser.svg similarity index 100% rename from cloudflare_error_page/resources/images/cf-icon-browser.svg rename to resources/images/cf-icon-browser.svg diff --git a/cloudflare_error_page/resources/images/cf-icon-cloud.svg b/resources/images/cf-icon-cloud.svg similarity index 100% rename from cloudflare_error_page/resources/images/cf-icon-cloud.svg rename to resources/images/cf-icon-cloud.svg diff --git a/cloudflare_error_page/resources/images/cf-icon-error.svg b/resources/images/cf-icon-error.svg similarity index 100% rename from cloudflare_error_page/resources/images/cf-icon-error.svg rename to resources/images/cf-icon-error.svg diff --git a/cloudflare_error_page/resources/images/cf-icon-ok.svg b/resources/images/cf-icon-ok.svg similarity index 100% rename from cloudflare_error_page/resources/images/cf-icon-ok.svg rename to resources/images/cf-icon-ok.svg diff --git a/cloudflare_error_page/resources/images/cf-icon-server.svg b/resources/images/cf-icon-server.svg similarity index 100% rename from cloudflare_error_page/resources/images/cf-icon-server.svg rename to resources/images/cf-icon-server.svg diff --git a/resources/styles/.gitignore b/resources/styles/.gitignore new file mode 100644 index 0000000..5f93228 --- /dev/null +++ b/resources/styles/.gitignore @@ -0,0 +1 @@ +main.css \ No newline at end of file diff --git a/cloudflare_error_page/resources/styles/main.css b/resources/styles/main-original.css similarity index 100% rename from cloudflare_error_page/resources/styles/main.css rename to resources/styles/main-original.css diff --git a/resources/templates/error.ejs b/resources/templates/error.ejs new file mode 100644 index 0000000..af806b4 --- /dev/null +++ b/resources/templates/error.ejs @@ -0,0 +1,123 @@ + + + + + + +<% +let error_code = params.error_code || 500; +let title = params.title || 'Internal server error'; +let html_title_output = params.html_title || (error_code + ': ' + title); +%> +<%= html_title_output %> + + + + + + + + +
+
+
+

+ <%= title %> + Error code <%= error_code %> +

+ <% let more_info = params.more_information || {}; %> + <% if (!more_info.hidden) { %> +
+ Visit <%= more_info.text || 'cloudflare.com' %> for <%= more_info.for || "more information" %>. +
+ <% } %> +
<%= params.time %>
+
+
+
+
+ <% for (let item_id of ['browser', 'cloudflare', 'host']) { %> + <% + let icon, default_location, default_name, text_color, status_text; + + if (item_id === 'browser') { + icon = 'browser'; + default_location = 'You'; + default_name = 'Browser'; + } else if (item_id === 'cloudflare') { + icon = 'cloud'; + default_location = 'San Francisco'; + default_name = 'Cloudflare'; + } else { + icon = 'server'; + default_location = 'Website'; + default_name = 'Host'; + } + + let item = params[item_id + '_status'] || {}; + let status = item.status || 'ok'; + + if (item.status_text_color) { + text_color = item.status_text_color; + } else if (status === 'ok') { + text_color = '#9bca3e'; // text-green-success + } else if (status === 'error') { + text_color = '#bd2426'; // text-red-error + } + + status_text = item.status_text || (status === 'ok' ? 'Working' : 'Error'); + %> +
+
+ + +
+ <%= item.location || default_location %> + <% + let _name_style; + if ((item.name || default_name) === 'Cloudflare') { + _name_style = 'style="color: #2f7bbf;"' + } else{ + _name_style = '' + } + %> +

><%= item.name || default_name %>

+ <%= status_text %> +
+ <% } %> +
+
+
+ +
+
+
+

What happened?

+ <%= (params.what_happened || 'There is an internal server error on Cloudflare\'s network.') %> +
+
+

What can I do?

+ <%= (params.what_can_i_do || 'Please try again in a few minutes.') %> +
+
+
+ + +
+
+ + + \ No newline at end of file diff --git a/resources/templates/error.html b/resources/templates/error.html new file mode 100644 index 0000000..2f4d37e --- /dev/null +++ b/resources/templates/error.html @@ -0,0 +1,114 @@ + + + + + + +{% set error_code = params.error_code or 500 %} +{% set title = params.title or 'Internal server error' %} +{% set html_title = params.html_title or ((error_code | string) + ': ' + title) %} +{{ html_title }} + + + + + +{% block header %}{% endblock %} + + + +
+
+
+

+ {{ title }} + Error code {{ error_code }} +

+ {% set more_info = params.more_information or {} %} + {% if not more_info.hidden or false %}{# default: shown #} +
+ Visit {{more_info.text or 'cloudflare.com'}} for {{more_info.for or 'more information'}}. +
+ {% endif %} +
{{ params.time }}
+
+
+
+
+ {% for item_id in ['browser', 'cloudflare', 'host'] %} + {% if item_id == 'browser' %} + {% set icon = 'browser' %} + {% set default_location = 'You' %} + {% set default_name = 'Browser' %} + {% elif item_id == 'cloudflare' %} + {% set icon = 'cloud' %} + {% set default_location = 'San Francisco' %} + {% set default_name = 'Cloudflare' %} + {% else %} + {% set icon = 'server' %} + {% set default_location = 'Website' %} + {% set default_name = 'Host' %} + {% endif %} + {% set item = params.get(item_id + '_status', {}) %} + {% set status = item.status or 'ok' %} + {% if item.status_text_color %} + {% set text_color = item.status_text_color %} + {% elif status == 'ok' %} + {% set text_color = '#9bca3e' %}{# text-green-success #} + {% elif status == 'error' %} + {% set text_color = '#bd2426' %}{# text-red-error #} + {% endif %} + {% set status_text = item.status_text or ('Working' if status == 'ok' else 'Error') %} +
+
+ + +
+ {{item.location or default_location}} + {% set _name_style = 'style="color: #2f7bbf;"' if ((item.name or default_name) == 'Cloudflare') else '' %} +

{{item.name or default_name}}

+ {{status_text}} +
+ {% endfor %} +
+
+
+ +
+
+
+

What happened?

+ {{ (params.what_happened or '

There is an internal server error on Cloudflare\'s network.

') | safe }} +
+
+

What can I do?

+ {{ (params.what_can_i_do or '

Please try again in a few minutes.

') | safe }} +
+
+
+ + +
+
+ + + \ No newline at end of file diff --git a/scripts/inline_resources.py b/scripts/inline_resources.py new file mode 100644 index 0000000..b6a5823 --- /dev/null +++ b/scripts/inline_resources.py @@ -0,0 +1,73 @@ +import os +import re +from urllib.parse import quote + + +root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +resources_folder = os.path.join(root,'resources') + + +def read_file(path: str) -> str: + with open(path, 'r', encoding='utf-8') as f: + return f.read() + + +def write_file(path: str, data: str): + with open(path, 'w', encoding='utf-8') as f: + f.write(data) + + +def convert_svg_to_data_uri(data: str) -> str: + data = data.replace('', '') + data = re.sub(r'\n\s*', '', data, flags=re.DOTALL) + uri = 'data:image/svg+xml;utf8,' + uri += quote(data) + return uri + + +def inline_svg_resources(css_file: str, svg_files: list[str], output_file: str): + css_data = read_file(css_file) + for svg_file in svg_files: + svg_data = read_file(os.path.join(os.path.dirname(css_file), svg_file)) + svg_uri = convert_svg_to_data_uri(svg_data) + css_data = css_data.replace(svg_file, svg_uri) + print(f'inline_svg_resources writing to {output_file}') + write_file(output_file, css_data) + + +def inline_css_resource(original_file: str, css_file: str, output_file: str): + css_data = read_file(css_file) + original_data = read_file(original_file) + original_data = original_data.replace('', + f'') + note = 'Note: This is generated with scripts/inline_resources.py. Please do not edit this file manually.' + if original_file.endswith('.ejs'): + original_data = f'<%# {note} %>\n' + original_data + else: + original_data = f'{{# {note} #}}\n' + original_data + print(f'inline_css_resource writing to {output_file}') + write_file(output_file, original_data) + + +if __name__ == '__main__': + inline_svg_resources( + os.path.join(resources_folder, 'styles/main-original.css'), + [ + '../images/cf-icon-browser.svg', + '../images/cf-icon-cloud.svg', + '../images/cf-icon-server.svg', + '../images/cf-icon-ok.svg', + '../images/cf-icon-error.svg', + ], + 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'), + os.path.join(root, 'cloudflare_error_page/templates/error.html'), + ) + inline_css_resource( + os.path.join(resources_folder, 'templates/error.ejs'), + os.path.join(resources_folder, 'styles/main.css'), + os.path.join(root, 'editor/resources/template.ejs'), + )