9
0
mirror of https://github.com/donlon/cloudflare-error-page.git synced 2025-12-20 07:19:30 +00:00

4 Commits

Author SHA1 Message Date
Anthony Donlon
914fefcbde update 2025-12-19 01:55:55 +08:00
Anthony Donlon
72fa5a5c23 update 2025-11-22 14:09:48 +08:00
Anthony Donlon
b158d350b0 update 2025-11-22 14:01:08 +08:00
Anthony Donlon
5584ff70cd update 2025-11-21 14:00:55 +08:00
53 changed files with 0 additions and 6623 deletions

14
.gitignore vendored
View File

@@ -1,14 +0,0 @@
.vscode/
build/
*.egg-info/
__pycache__/
dist/
node_modules/
.venv/
venv/
ttt/
instance/

View File

@@ -1,9 +0,0 @@
MIT License
Copyright (c) 2025 Anthony Donlon
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

218
README.md
View File

@@ -1,218 +0,0 @@
# Cloudflare Error Page Generator
📢 **Update (2025/12/09)**: All icons used in the error page have been fully redrawn as vector assets. These icons along with the stylesheet are also inlined into a single file of the error page, eliminating any need of hosting additional resources, and ensuring better experience for you and your end users.
## 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.
## Online Editor
Here's an online editor to create customized error pages. Try it out [here](https://virt.moe/cferr/editor/).
![Editor](https://github.com/donlon/cloudflare-error-page/blob/images/editor.png?raw=true)
## Quickstart for Programmers
### Python
Install `cloudflare-error-page` with pip.
``` Bash
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))
``` Python
import webbrowser
from cloudflare_error_page import render as render_cf_error_page
# This function renders an error page based on the input parameters
error_page = render_cf_error_page({
# Browser status is ok
'browser_status': {
"status": 'ok',
},
# Cloudflare status is error
'cloudflare_status': {
"status": 'error',
"status_text": 'Error',
},
# Host status is also ok
'host_status': {
"status": 'ok',
"location": 'example.com',
},
# can be 'browser', 'cloudflare', or 'host'
'error_source': 'cloudflare',
# Texts shown in the bottom of the page
'what_happened': '<p>There is an internal server error on Cloudflare\'s network.</p>',
'what_can_i_do': '<p>Please try again in a few minutes.</p>',
})
with open('error.html', 'w') as f:
f.write(error_page)
webbrowser.open('error.html')
```
You can also see live demo [here](https://virt.moe/cferr/examples/default).
A demo server using Flask is also available in [flask_demo.py](examples/flask_demo.py).
### Node.js/NPM
A Node.js package is available in [nodejs](nodejs) folder. However currently it supports only Node.js but not web browsers,
and we plan to refactor it into a shared package, so it can work in both environments.
(Thanks [@junduck](https://github.com/junduck) for creating this.)
### PHP
``` PHP
/* Coming soon! */
```
## More Examples
### Catastrophic infrastructure failure
``` Python
params = {
"title": "Catastrophic infrastructure failure",
"more_information": {
"for": "no information",
},
"browser_status": {
"status": "error",
"status_text": "Out of Memory",
},
"cloudflare_status": {
"status": "error",
"location": "Everywhere",
"status_text": "Error",
},
"host_status": {
"status": "error",
"location": "example.com",
"status_text": "On Fire",
},
"error_source": "cloudflare",
"what_happened": "<p>There is a catastrophic failure.</p>",
"what_can_i_do": "<p>Please try again in a few years.</p>",
}
```
![Catastrophic infrastructure failure](https://github.com/donlon/cloudflare-error-page/blob/images/example.png?raw=true)
[Demo](https://virt.moe/cferr/examples/catastrophic)
### Web server is working
``` Python
params = {
"title": "Web server is working",
"error_code": "200",
"more_information": {
"hidden": True,
},
"browser_status": {
"status": "ok",
"status_text": "Seems Working",
},
"cloudflare_status": {
"status": "ok",
"status_text": "Often Working",
},
"host_status": {
"status": "ok",
"location": "example.com",
"status_text": "Almost Working",
},
"error_source": "host",
"what_happened": "<p>This site is still working. And it looks great.</p>",
"what_can_i_do": "<p>Visit the site before it crashes someday.</p>",
}
```
![Web server is working](https://github.com/donlon/cloudflare-error-page/blob/images/example2.png?raw=true)
[Demo](https://virt.moe/cferr/examples/working)
## FAQ
### How to show real user IP / Cloudflare Ray ID / data center location in the error page so that it looks more realistic?
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)
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.
## See also
- [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).
- [oftx/cloudflare-error-page](https://github.com/oftx/cloudflare-error-page)
React reimplementation of the original page, and can be deployed directly to Cloudflare Pages.
## Full Parameter Reference
``` JavaScript
{
"html_title": "cloudflare.com | 500: Internal server error",
"title": "Internal server error",
"error_code": "500",
"time": "2025-11-18 12:34:56 UTC", // Current UTC time will be shown if empty
// Configuration of "Visit ... for more information" line
"more_information": {
"hidden": false,
"text": "cloudflare.com",
"link": "https://www.cloudflare.com/",
"for": "more information",
},
// Configuration of the Browser/Cloudflare/Host status
"browser_status": {
"status": "ok", // "ok" or "error"
"location": "You",
"name": "Browser",
"status_text": "Working",
"status_text_color": "#9bca3e",
},
"cloudflare_status": {
"status": "error",
"location": "Cloud",
"name": "Cloudflare",
"status_text": "Error",
"status_text_color": "#bd2426",
},
"host_status": {
"status": "ok",
"location": "Website",
"name": "Host",
"status_text": "Working",
"status_text_color": "#9bca3e",
},
// Position of the error indicator, can be "browser", "cloudflare", or "host"
"error_source": "host",
"what_happened": "<p>There is an internal server error on Cloudflare's network.</p>",
"what_can_i_do": "<p>Please try again in a few minutes.</p>",
"ray_id": '0123456789abcdef', // Random hex string will be shown if empty
"client_ip": '1.1.1.1',
// Configuration of 'Performance & security by ...' in the footer
"perf_sec_by": {
"text": "Cloudflare",
"link": "https://www.cloudflare.com/",
},
}
```

View File

@@ -1,50 +0,0 @@
import html
import secrets
from datetime import datetime, timezone
from typing import Any
from jinja2 import Environment, PackageLoader, Template, select_autoescape
env = Environment(
loader=PackageLoader(__name__),
autoescape=select_autoescape(),
trim_blocks=True,
lstrip_blocks=True,
)
default_template: Template = env.get_template("error.html")
def render(params: dict,
allow_html: bool = True,
template: Template | None = None,
*args: Any,
**kwargs: Any) -> str:
'''Render a customized Cloudflare error page.
:param params: Parameters passed to the template. Refer to the project homepage for more information.
:param allow_html: Allow output raw HTML content from parameters. Set to False if you don't trust the source of the params.
:param template: Jinja template used to render the error page. Default template will be used if ``template`` is None.
Override this to extend or customize the base template.
:param args: Additional positional arguments passed to ``Template.render`` function.
:param kwargs: Additional keyword arguments passed to ``Template.render`` function.
:return: The rendered error page as a string.
'''
if not template:
template = default_template
params = {**params}
if not params.get('time'):
utc_now = datetime.now(timezone.utc)
params['time'] = utc_now.strftime("%Y-%m-%d %H:%M:%S UTC")
if not params.get('ray_id'):
params['ray_id'] = secrets.token_hex(8)
if not allow_html:
params['what_happened'] = html.escape(params.get('what_happened', ''))
params['what_can_i_do'] = html.escape(params.get('what_can_i_do', ''))
return template.render(params=params, *args, **kwargs)
__all__ = ['default_template', 'render']

File diff suppressed because one or more lines are too long

BIN
editor.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

View File

@@ -1 +0,0 @@
cf-colos.json

View File

@@ -1,102 +0,0 @@
# SPDX-License-Identifier: MIT
import json
import os
import secrets
import string
import sys
import tomllib
from flask import Flask, redirect, request, url_for
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm import DeclarativeBase
from werkzeug.middleware.proxy_fix import ProxyFix
root_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../')
sys.path.append(root_dir)
class Base(DeclarativeBase):
pass
db: SQLAlchemy = SQLAlchemy(model_class=Base, session_options={
# 'autobegin': False,
# 'expire_on_commit': False,
})
limiter: Limiter = Limiter(
key_func=get_remote_address, # Uses client's IP address by default
)
def _generate_secret(length=32) -> str:
characters = string.ascii_letters + string.digits # A-Z, a-z, 0-9
return ''.join(secrets.choice(characters) for _ in range(length))
def _initialize_app_config(app: Flask):
if app.config.get('BEHIND_PROXY', True):
app.wsgi_app = ProxyFix(
app.wsgi_app, x_for=1, x_proto=1
)
app.json.ensure_ascii = False
app.json.mimetype = "application/json; charset=utf-8"
secret_key = app.config.get('SECRET_KEY', '')
if secret_key:
app.secret_key = secret_key
else:
app.logger.info('Using generated secret')
app.secret_key = _generate_secret()
app.config["SQLALCHEMY_DATABASE_URI"] = app.config.get('SQLALCHEMY_DATABASE_URI', 'sqlite:///example.db')
if app.config["SQLALCHEMY_DATABASE_URI"].startswith('sqlite'):
app.config["SQLALCHEMY_ENGINE_OPTIONS"] = {
'isolation_level': 'SERIALIZABLE',
# "execution_options": {"autobegin": False}
}
def create_app(test_config=None) -> Flask:
instance_path = os.getenv('INSTANCE_PATH')
if instance_path is not None:
instance_path = os.path.abspath(instance_path)
os.makedirs(instance_path, exist_ok=True)
print(f'App instance path: {instance_path}')
app = Flask(__name__,
instance_path=instance_path,
instance_relative_config=True
)
app.config.from_file("config.toml", load=tomllib.load, text=False)
_initialize_app_config(app)
from . import utils
from . import models
from . import examples
from . import editor
from . import share
db.init_app(app)
limiter.init_app(app)
with app.app_context():
db.create_all()
@app.route('/')
def index():
return redirect(url_for('editor.index'))
@app.route('/health')
def health():
return '', 204
url_prefix = app.config.get('URL_PREFIX', '')
app.register_blueprint(editor.bp, url_prefix=f'{url_prefix}/editor')
app.register_blueprint(examples.bp, url_prefix=f'{url_prefix}/examples')
app.register_blueprint(share.bp, url_prefix=f'{url_prefix}/s')
return app
__all__ = ['create_app']

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +0,0 @@
# Url prefix for app urls
URL_PREFIX = ''
# Set to true if trust X-Forwarded-For/X-Forwarded-Proto header
BEHIND_PROXY = true
# Some random secret, will be auto-generated if empty
SECRET_KEY = ''
# Main database URI
SQLALCHEMY_DATABASE_URI = 'sqlite:///database.db'
# Rate limit storage for Flask-Limiter
RATELIMIT_STORAGE_URI = 'memory://'

View File

@@ -1,19 +0,0 @@
# SPDX-License-Identifier: MIT
import os
from flask import (
Blueprint,
send_from_directory,
)
root_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../')
res_folder = os.path.join(root_dir, 'editor/web/dist')
bp = Blueprint('editor', __name__, url_prefix='/')
@bp.route('/', defaults={'path': 'index.html'})
@bp.route('/<path:path>')
def index(path: str):
return send_from_directory(res_folder, path)

View File

@@ -1,55 +0,0 @@
# SPDX-License-Identifier: MIT
import copy
import os
import re
from flask import (
Blueprint,
json,
abort,
redirect,
)
from cloudflare_error_page import render as render_cf_error_page
from .utils import fill_cf_template_params
root_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../')
examples_dir = os.path.join(root_dir, 'examples')
bp = Blueprint('examples', __name__, url_prefix='/')
param_cache: dict[str, dict] = {}
def get_page_params(name: str) -> dict:
name = re.sub(r'[^\w]', '', name)
params = param_cache.get(name)
if params is not None:
return copy.deepcopy(params)
try:
with open(os.path.join(examples_dir, f'{name}.json')) as f:
params = json.load(f)
param_cache[name] = params
return copy.deepcopy(params)
except Exception as _:
return None
@bp.route('/', defaults={'name': 'default'})
@bp.route('/<path:name>')
def index(name: str):
lower_name = name.lower()
if name != lower_name:
return redirect(lower_name)
else:
name = lower_name
params = get_page_params(name)
if params is None:
abort(404)
fill_cf_template_params(params)
# Render the error page
return render_cf_error_page(params)

View File

@@ -1,19 +0,0 @@
# SPDX-License-Identifier: MIT
from sqlalchemy import (
Column,
DateTime,
Integer,
JSON,
String,
func,
)
from . import db
class Item(db.Model):
id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
name = Column(String(255), unique=True, nullable=False, index=True)
params = Column(JSON, nullable=False)
time_created = Column(DateTime(timezone=True), server_default=func.now())

View File

@@ -1,3 +0,0 @@
Flask
Flask-Limiter
Flask-SqlAlchemy

View File

@@ -1,138 +0,0 @@
# SPDX-License-Identifier: MIT
import html
import random
import string
from flask import (
Blueprint,
request,
abort,
jsonify,
url_for,
)
from jinja2 import Environment, select_autoescape
from cloudflare_error_page import (
default_template as cf_template,
render as render_cf_error_page,
)
from . import (
db,
limiter,
models
)
from .utils import fill_cf_template_params, sanitize_page_param_links
# root_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../')
# examples_dir = os.path.join(root_dir, 'examples')
env = Environment(
autoescape=select_autoescape(),
trim_blocks=True,
lstrip_blocks=True,
)
template = env.from_string('''
{% extends base %}
{% block header %}
<meta property="og:type" content="website" />
<meta property="og:site_name" content="moe::virt" />
<meta property="og:title" content="{{ html_title }}" />
<meta property="og:url" content="{{ url }}" />
<meta property="og:description" content="{{ description }}" />
<meta property="twitter:card" content="summary" />
<meta property="twitter:site" content="moe::virt" />
<meta property="twitter:title" content="{{ html_title }}" />
<meta property="twitter:description" content="{{ description }}" />
{% endblock %}
''')
bp = Blueprint('share', __name__, url_prefix='/')
rand_charset = string.ascii_lowercase + string.digits
def get_rand_name(digits=8):
return ''.join(random.choice(rand_charset) for _ in range(digits))
@bp.post('/create')
@limiter.limit("20 per minute")
@limiter.limit("500 per hour")
def create():
if len(request.data) > 4096:
abort(413)
# Simple CSRF check
sec_fetch_site = request.headers.get('Sec-Fetch-Site')
if sec_fetch_site is not None and sec_fetch_site != 'same-origin':
return jsonify({
'status': 'failed',
'message': 'CSRF check failed (Sec-Fetch-Site)',
}), 403
# Accessing request.json raises 415 error if Content-Type is not application/json. This also prevents CSRF requests.
# See https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/CSRF#avoiding_simple_requests
params = request.json['parameters'] # throws KeyError
# TODO: strip unused params
try:
item = models.Item()
item.name = get_rand_name()
item.params = params
db.session.add(item)
db.session.commit()
except:
db.session.rollback()
return jsonify({
'status': 'failed',
})
return jsonify({
'status': 'ok',
'name': item.name,
'url': request.host_url[:-1] + url_for('share.get', name=item.name),
# TODO: better way to handle this
})
@bp.get('/<name>')
def get(name: str):
accept = request.headers.get('Accept', '')
is_json = 'application/json' in accept
item = db.session.query(models.Item).filter_by(name=name).first()
if not item:
if is_json:
return jsonify({
'status': 'notfound'
})
else:
return abort(404)
params: dict = item.params
params.pop('time', None)
params.pop('ray_id', None)
params.pop('client_ip', None)
if is_json:
return jsonify({
'status': 'ok',
'parameters': params,
})
else:
params['creator_info'] = {
'hidden': False,
'text': 'CF Error Page Editor',
'link': request.host_url[:-1] + url_for('editor.index') + f'#from={name}',
}
fill_cf_template_params(params)
sanitize_page_param_links(params)
return render_cf_error_page(params=params,
allow_html=False,
template=template,
base=cf_template,
url=request.url,
description='Cloudflare error page')

View File

@@ -1,74 +0,0 @@
import json
import os
from flask import request
from . import root_dir
loc_data: dict = None
def read_loc_file(path: str):
try:
with open(os.path.join(root_dir, path)) as f:
return json.load(f)
except:
return
def get_cf_location(loc: str):
global loc_data
loc = loc.upper()
if loc_data is None:
loc_data = read_loc_file('editor/server/cf-colos.json')
if loc_data is None:
# From https://github.com/Netrvin/cloudflare-colo-list/blob/main/DC-Colos.json
loc_data = read_loc_file('editor/server/cf-colos.bundled.json')
if loc_data is None:
return
data: dict = loc_data.get(loc)
if not data:
return
return data.get('city')
def fill_cf_template_params(params: dict):
# Get the real Ray ID / data center location from Cloudflare header
ray_id_loc = request.headers.get('Cf-Ray')
if ray_id_loc:
params['ray_id'] = ray_id_loc[:16]
cf_status: dict = params.get('cloudflare_status')
if cf_status is None:
cf_status = params['cloudflare_status'] = {}
if not cf_status.get('location'):
loc = get_cf_location(ray_id_loc[-3:])
if loc:
cf_status['location'] = loc
# Get the real client ip from remote_addr
# If this server is behind proxies (e.g CF CDN / Nginx), make sure to set 'BEHIND_PROXY'=True in app config. Then ProxyFix will fix this variable
# using X-Forwarded-For header from the proxy.
params['client_ip'] = request.remote_addr
def sanitize_user_link(link: str):
link = link.strip()
link_lower = link
if link_lower.startswith('http://') or link_lower.startswith('https://'):
return link
if '.' in link or '/' in link:
return 'https://' + link
return '#' + link
def sanitize_page_param_links(param: dict):
more_info = param.get('more_information')
if more_info:
link = more_info.get('link')
if link:
more_info['link'] = sanitize_user_link(link)
perf_sec_by = param.get('perf_sec_by')
if perf_sec_by:
link = perf_sec_by.get('link')
if link:
perf_sec_by['link'] = sanitize_user_link(link)

View File

@@ -1,9 +0,0 @@
{
"endOfLine": "lf",
"printWidth": 120,
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"useTabs": false
}

View File

@@ -1,429 +0,0 @@
<!-- SPDX-License-Identifier: MIT -->
<!--
!!!
!!! Note: This file is vibely generated, and could be very hard to maintain.
!!!
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Cloudflare Error Page Editor</title>
<meta name="description" content="Online editor to create customized Cloudflare-style error pages.">
<meta name="keywords" content="cloudflare,error,page,editor">
<link rel="canonical" href="https://virt.moe/cferr/editor/" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="moe::virt" />
<meta property="og:title" content="Cloudflare error page editor" />
<meta property="og:url" content="https://virt.moe/cferr/editor/" />
<meta property="og:description" content="Online editor to create customized Cloudflare-style error pages" />
<meta property="twitter:card" content="summary" />
<meta property="twitter:site" content="moe::virt" />
<meta property="twitter:title" content="Cloudflare error page editor" />
<meta property="twitter:description" content="Online editor to create customized Cloudflare-style error pages" />
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1" />
<link href="https://virt.moe/assets/cloudflare-error-page/bootstrap.min.css" rel="stylesheet">
<script type="module" src="src/index.js"></script>
<style>
/* Layout: editor + preview */
.app {
display: flex;
flex-direction: column;
gap: 1rem;
height: 100vh;
/* padding: 0.8rem; */
}
/* On md and up, arrange horizontally: editor left, preview right */
@media (min-width: 768px) {
.app {
flex-direction: row;
align-items: stretch;
}
.editor {
flex: 0 0;
min-width: 380px;
overflow-y: auto;
}
.preview {
flex: 1 1 48%;
/* max-width: 48%; */
display: flex;
flex-direction: column;
}
iframe.preview-frame {
height: 100% !important;
}
}
/* On small screens use stacked layout: editor then iframe */
.editor {
background: #fff;
border: 1px solid #e3e6ea;
border-radius: .5rem;
padding: 0.8rem;
}
.preview {
background: #fff;
border: 1px solid #e3e6ea;
border-radius: .5rem;
padding: 0.8rem 0 0 0;
}
/* Compact form: label and control same row */
.form-row {
display: flex;
gap: .3rem;
align-items: center;
margin-bottom: .6rem;
}
.form-row>label {
min-width: 6rem;
max-width: 12rem;
margin-bottom: 0;
font-weight: 600;
}
.form-row>.control {
flex: 1;
}
/* Status block styling */
.status-block {
border: 1px solid #cfeadd;
border-radius: .375rem;
padding: .75rem;
margin-bottom: .5rem;
}
.status-ok {
background: #e9f7ef;
border: 1px solid #cfeadd;
}
.status-error {
background: #fff5f5;
border: 1px solid #f3c2c2;
}
/* Iframe styling */
iframe.preview-frame {
--expanded-height: 100%;
width: 100%;
height: var(--expanded-height);
border: 1px solid #ddd;
flex: 1 1 auto;
min-height: 360px;
border-radius: .375rem;
box-shadow: 0 1px 4px rgba(0, 0, 0, .06);
}
/* Controls toolbar */
.toolbar {
display: flex;
gap: .5rem;
align-items: center;
justify-content: space-between;
margin-bottom: .75rem;
}
/* Compact textarea resizing */
textarea.compact {
min-height: 80px;
resize: vertical;
}
</style>
</head>
<body class="bg-light">
<div class="container-fluid h-100">
<div class="app">
<!-- Editor column -->
<div class="editor">
<h5 class="form-row">Cloudflare Error Page Editor</h5>
<hr>
<div class="form-row mb-3">
<label for="presetSelect">Preset</label>
<select id="presetSelect" class="form-select form-select-sm">
<option value="default">Internal server error (Default)</option>
<option value="empty">Empty</option>
<option value="catastrophic">Catastrophic failure</option>
<option value="working">Server working</option>
<option value="consensual">Myth of consensual</option>
</select>
</div>
<hr>
<form id="editorForm" class="needs-validation" novalidate>
<!-- Basic properties -->
<div class="mb-3">
<!-- <h6 class="mb-2">Page</h6> -->
<div class="form-row">
<label for="title">Title</label>
<div class="control">
<input id="title" class="form-control form-control-sm" placeholder="Internal server error" />
</div>
</div>
<div class="form-row">
<label for="error_code">Error Code</label>
<div class="control">
<input id="error_code" class="form-control form-control-sm" placeholder="500" />
</div>
</div>
</div>
<hr>
<!-- Status blocks -->
<div class="mb-3">
<h6 class="mb-2">Status</h6>
<!-- Browser -->
<div id="block_browser" class="status-block status-ok">
<div class="d-flex justify-content-between align-items-start mb-2">
<strong>Browser</strong>
<div>
<input class="form-check-input" type="radio" name="error_source" id="err_browser" value="browser" />
<label for="err_browser" class="ms-1 small">Error here</label>
</div>
</div>
<div class="form-row">
<label for="browser_status">Status</label>
<div class="control">
<select id="browser_status" class="form-select form-select-sm">
<option value="ok">Ok</option>
<option value="error">Error</option>
</select>
</div>
</div>
<div class="form-row">
<label for="browser_location">Location</label>
<div class="control">
<input id="browser_location" class="form-control form-control-sm" placeholder="You" />
</div>
</div>
<div class="form-row">
<label for="browser_name">Name</label>
<div class="control">
<input id="browser_name" class="form-control form-control-sm" placeholder="Browser" />
</div>
</div>
<div class="form-row">
<label for="browser_status_text">Status Text</label>
<div class="control">
<input id="browser_status_text" class="form-control form-control-sm" />
</div>
</div>
</div>
<!-- Cloudflare -->
<div id="block_cloudflare" class="status-block status-error">
<div class="d-flex justify-content-between align-items-start mb-2">
<strong>Cloudflare</strong>
<div>
<input class="form-check-input" type="radio" name="error_source" id="err_cloudflare"
value="cloudflare" />
<label for="err_cloudflare" class="ms-1 small">Error here</label>
</div>
</div>
<div class="form-row">
<label for="cloudflare_status">Status</label>
<div class="control">
<select id="cloudflare_status" class="form-select form-select-sm">
<option value="ok">Ok</option>
<option value="error">Error</option>
</select>
</div>
</div>
<div class="form-row">
<label for="cloudflare_location">Location</label>
<div class="control">
<input id="cloudflare_location" class="form-control form-control-sm" placeholder="San Francisco" />
</div>
</div>
<div class="form-row">
<label for="cloudflare_name">Name</label>
<div class="control">
<input id="cloudflare_name" class="form-control form-control-sm" placeholder="Cloudflare" />
</div>
</div>
<div class="form-row">
<label for="cloudflare_status_text">Status Text</label>
<div class="control">
<input id="cloudflare_status_text" class="form-control form-control-sm" />
</div>
</div>
</div>
<!-- Host -->
<div id="block_host" class="status-block status-ok">
<div class="d-flex justify-content-between align-items-start mb-2">
<strong>Host</strong>
<div>
<input class="form-check-input" type="radio" name="error_source" id="err_host" value="host" />
<label for="err_host" class="ms-1 small">Error here</label>
</div>
</div>
<div class="form-row">
<label for="host_status">Status</label>
<div class="control">
<select id="host_status" class="form-select form-select-sm">
<option value="ok">Ok</option>
<option value="error">Error</option>
</select>
</div>
</div>
<div class="form-row">
<label for="host_location">Location</label>
<div class="control">
<input id="host_location" class="form-control form-control-sm" placeholder="Website" />
</div>
</div>
<div class="form-row">
<label for="host_name">Name</label>
<div class="control">
<input id="host_name" class="form-control form-control-sm" placeholder="Host" />
</div>
</div>
<div class="form-row">
<label for="host_status_text">Status Text</label>
<div class="control">
<input id="host_status_text" class="form-control form-control-sm" />
</div>
</div>
</div>
</div>
<div class="status-block mt-3 mb-3">
<div class="d-flex justify-content-between align-items-start mb-2">
<strong>Visit ...</strong>
<div>
<input id="more_hidden" class="form-check-input" type="checkbox" />
<label for="more_hidden" class="ms-1 small">Hidden</label>
</div>
</div>
<div class="form-row">
<label for="more_text">Text</label>
<div class="control">
<input id="more_text" class="form-control form-control-sm" placeholder="cloudflare.com" />
</div>
</div>
<div class="form-row">
<label for="more_link">Link</label>
<div class="control">
<input id="more_link" class="form-control form-control-sm" placeholder="https://www.cloudflare.com/" />
</div>
</div>
<div class="form-row">
<label for="more_for">For</label>
<div class="control">
<input id="more_for" class="form-control form-control-sm" placeholder="more information" />
</div>
</div>
</div>
<label for="what_happened" class="fw-semibold">What happened?</label>
<div class="control">
<textarea id="what_happened" class="form-control compact"
placeholder="There is an internal server error on Cloudflare's network."></textarea>
</div>
<!-- </div> -->
<!-- <div class=""> -->
<label for="what_can_i_do" class="fw-semibold mt-2">What can I do?</label>
<div class="control">
<textarea id="what_can_i_do" class="form-control compact"
placeholder="Please try again in a few minutes."></textarea>
</div>
<hr>
<h6 class="form-row">Performance & security by ...</h6>
<div class="form-row">
<label for="perf_text">Text</label>
<div class="control">
<input id="perf_text" class="form-control form-control-sm" placeholder="Cloudflare" />
</div>
</div>
<div class="form-row">
<label for="perf_link">Link</label>
<div class="control">
<input id="perf_link" class="form-control form-control-sm" placeholder="https://www.cloudflare.com/" />
</div>
</div>
<!-- </div> -->
<div class="d-flex gap-2 mt-2 mb-2">
<!-- <button id="btnRender" class="btn btn-sm btn-primary">Render</button> -->
<button id="btnOpen" class="btn btn-sm btn-primary">Preview in new tab</button>
<button id="btnExport" class="btn btn-sm btn-primary">Export JSON</button>
</div>
<button id="btnShare" class="btn btn-sm btn-primary">Create shareable link</button>
<div class="mt-2">
<div class="input-group input-group-sm">
<input id="shareLink" class="form-control" readonly />
<button id="btnCopyLink" class="btn btn-outline-secondary" type="button">Copy</button>
</div>
</div>
<div class="mt-2 text-center">&gt;&gt; Star this project on
<a href="https://github.com/donlon/cloudflare-error-page" target="_blank">GitHub</a>
</div>
<div class="mt-2" style="font-size: 0.9em;">You can also embed this error page into your own website. See
<a href="https://github.com/donlon/cloudflare-error-page#quickstart-for-programmers"
target="_blank">Quickstart</a> in the
homepage for steps.
</div>
</form>
</div>
<!-- Preview column -->
<div class="preview">
<div class="d-flex justify-content-between align-items-center mb-1" style="padding: 0 0.8em;">
<h6><strong>Preview</strong></h6>
</div>
<!-- TODO: An iframe which has both allow-scripts and allow-same-origin for its sandbox attribute can escape its sandboxing. -->
<iframe id="previewFrame" class="preview-frame" sandbox="allow-scripts allow-same-origin"></iframe>
</div>
</div>
</div>
</body>
</html>

View File

@@ -1,23 +0,0 @@
{
"name": "cloudflare-error-page-editor",
"version": "0.0.1",
"license": "MIT",
"scripts": {
"dev": "vite",
"build": "vite build",
"format": "prettier src --write",
"lint": "eslint .",
"preview": "npm run build && vite preview"
},
"devDependencies": {
"@types/node": "^24.10.2",
"html-minifier-terser": "^7.2.0",
"prettier": "3.7.4",
"typescript": "^5.9.3",
"vite": "^7.2.6"
},
"dependencies": {
"bootstrap": "^5.3.8",
"ejs": "^3.1.10"
}
}

View File

@@ -1,441 +0,0 @@
/*
Editor logic
- initialConfig: provided default config (from your JSON)
- render(): placeholder that generates HTML and writes to iframe.srcdoc
- inputs call render() on change
- "Open in new tab" opens the rendered HTML in a new window using a blob URL
*/
import ejs from 'ejs'
import templateContent from './template.ejs?raw'
let template = ejs.compile(templateContent);
// can be changed if specified by '?from=<name>'
let initialConfig = {
title: 'Internal server error',
error_code: '500',
more_information: {
hidden: false,
text: 'cloudflare.com',
for: 'more information',
},
browser_status: {
status: 'ok',
location: 'You',
name: 'Browser',
status_text: 'Working',
},
cloudflare_status: {
status: 'error',
location: 'San Francisco',
name: 'Cloudflare',
status_text: 'Error',
},
host_status: {
status: 'ok',
location: 'Website',
name: 'Host',
status_text: 'Working',
},
error_source: 'cloudflare',
what_happened: "There is an internal server error on Cloudflare's network.",
what_can_i_do: 'Please try again in a few minutes.',
};
// Demo presets (content brief — replace or expand as needed)
const PRESETS = {
default: structuredClone(initialConfig),
empty: {
error_code: '500',
},
catastrophic: {
title: 'Catastrophic infrastructure failure',
error_code: '500',
more_information: {
for: 'no help at all',
},
browser_status: {
status: 'error',
status_text: 'Out of Memory',
},
cloudflare_status: {
status: 'error',
location: 'Everywhere',
status_text: 'Error',
},
host_status: {
status: 'error',
location: 'example.com',
status_text: 'On Fire',
},
error_source: 'cloudflare',
what_happened: 'There is a catastrophic failure.',
what_can_i_do: 'Please try again in a few years.',
},
working: {
title: 'Web server is working',
error_code: '200',
more_information: {
hidden: true,
},
browser_status: {
status: 'ok',
status_text: 'Seems Working',
},
cloudflare_status: {
status: 'ok',
status_text: 'Often Working',
},
host_status: {
status: 'ok',
location: 'example.com',
status_text: 'Almost Working',
},
error_source: 'host',
what_happened: 'This site is still working. And it looks great.',
what_can_i_do: 'Visit the site before it crashes someday.',
},
consensual: {
title: 'The Myth Of "Consensual" Internet',
error_code: 'lmao',
more_information: {
hidden: false,
text: 'r/ProgrammerHumor',
link: 'https://redd.it/1p2yola',
},
browser_status: {
status: 'ok',
location: 'You',
name: 'Browser',
status_text: 'I Consent',
},
cloudflare_status: {
status: 'error',
location: 'F***ing Everywhere',
name: 'Cloudflare',
status_text: "I Don't!",
},
host_status: {
status: 'ok',
location: 'Remote',
name: 'Host',
status_text: 'I Consent',
},
error_source: 'cloudflare',
what_happened: "Isn't There Someone You Forgot To Ask?",
what_can_i_do: 'Kill Yourself',
},
};
function extractUrlParam(str, key) {
const urlParams = new URLSearchParams(str);
return urlParams.get(key);
}
function getDefaultPresetName() {
const key = 'from';
let name = extractUrlParam(window.location.search, key);
if (!name) {
name = extractUrlParam(window.location.hash.substring(1), key);
}
if (name) {
name = name.replace(/[^\w\d]/g, '')
}
return name;
}
const defaultPresetName = getDefaultPresetName();
if (defaultPresetName && defaultPresetName.indexOf('/') < 0) {
fetch(`../s/${defaultPresetName}`, {
headers: {
Accept: 'application/json',
},
})
.then((response) => {
if (!response.ok) {
throw new Error('failed to get preset');
}
return response.json();
})
.then((result) => {
if (result.status != 'ok') {
return;
}
console.log(result.parameters);
initialConfig = result.parameters;
loadConfig(initialConfig);
render();
});
}
/* Utilities */
function $(id) {
return document.getElementById(id);
}
/* Fill form from config */
function loadConfig(cfg) {
$('title').value = cfg.title ?? '';
$('error_code').value = cfg.error_code ?? '';
$('more_hidden').checked = !!(cfg.more_information && cfg.more_information.hidden);
$('more_text').value = cfg.more_information?.text ?? '';
$('more_link').value = cfg.more_information?.link ?? '';
$('more_for').value = cfg.more_information?.for ?? '';
$('browser_status').value = cfg.browser_status?.status ?? 'ok';
$('browser_location').value = cfg.browser_status?.location ?? '';
$('browser_name').value = cfg.browser_status?.name ?? '';
$('browser_status_text').value = cfg.browser_status?.status_text ?? '';
$('cloudflare_status').value = cfg.cloudflare_status?.status ?? 'ok';
$('cloudflare_location').value = cfg.cloudflare_status?.location ?? '';
$('cloudflare_name').value = cfg.cloudflare_status?.name ?? '';
$('cloudflare_status_text').value = cfg.cloudflare_status?.status_text ?? '';
$('host_status').value = cfg.host_status?.status ?? 'ok';
$('host_location').value = cfg.host_status?.location ?? '';
$('host_name').value = cfg.host_status?.name ?? '';
$('host_status_text').value = cfg.host_status?.status_text ?? '';
if (cfg.error_source === 'browser') $('err_browser').checked = true;
else if (cfg.error_source === 'cloudflare') $('err_cloudflare').checked = true;
else $('err_host').checked = true;
$('what_happened').value = cfg.what_happened ?? '';
$('what_can_i_do').value = cfg.what_can_i_do ?? '';
$('perf_text').value = cfg.perf_sec_by?.text ?? '';
$('perf_link').value = cfg.perf_sec_by?.link ?? '';
}
/* Read config from form inputs */
function readConfig() {
return {
title: $('title').value,
error_code: $('error_code').value,
more_information: {
hidden: !!$('more_hidden').checked,
text: $('more_text').value,
link: $('more_link').value,
for: $('more_for').value,
},
browser_status: {
status: $('browser_status').value,
location: $('browser_location').value,
name: $('browser_name').value,
status_text: $('browser_status_text').value,
},
cloudflare_status: {
status: $('cloudflare_status').value,
location: $('cloudflare_location').value,
name: $('cloudflare_name').value,
status_text: $('cloudflare_status_text').value,
},
host_status: {
status: $('host_status').value,
location: $('host_location').value,
name: $('host_name').value,
status_text: $('host_status_text').value,
},
error_source: (
document.querySelector('input[name="error_source"]:checked') || {
value: 'host',
}
).value,
what_happened: $('what_happened').value,
what_can_i_do: $('what_can_i_do').value,
perf_sec_by: {
text: $('perf_text').value,
link: $('perf_link').value,
},
};
}
function formatUtcTimestamp() {
const d = new Date();
const year = d.getUTCFullYear();
const month = String(d.getUTCMonth() + 1).padStart(2, '0');
const day = String(d.getUTCDate()).padStart(2, '0');
const hours = String(d.getUTCHours()).padStart(2, '0');
const minutes = String(d.getUTCMinutes()).padStart(2, '0');
const seconds = String(d.getUTCSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds} UTC`;
}
function renderEjs(params) {
return template({
params: params,
});
}
/* Basic render: build HTML string from config and put into iframe.srcdoc */
function render() {
const cfg = readConfig();
window.lastCfg = cfg;
cfg.time = formatUtcTimestamp();
cfg.ray_id = '0123456789abcdef';
cfg.client_ip = '1.1.1.1';
if (Number.isNaN(Number(cfg.error_code))) {
cfg.html_title = cfg.title || 'Internal server error';
}
let pageHtml = renderEjs(cfg);
// Write into iframe
const iframe = $('previewFrame');
let doc = iframe.contentDocument;
doc.open();
doc.write(pageHtml);
doc.close();
updateStatusBlockStyles();
// store last rendered HTML for "open in new tab"
lastRenderedHtml = pageHtml;
}
/* Open in new tab: create blob and open */
let lastRenderedHtml = '';
function openInNewTab() {
if (!lastRenderedHtml) render();
const blob = new Blob([lastRenderedHtml], { type: 'text/html' });
const url = URL.createObjectURL(blob);
window.open(url, '_blank', 'noopener');
// note that this url won't be revoked
}
function createShareableLink() {
$('shareLink').value = '';
fetch('../s/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
parameters: window.lastCfg,
}),
})
.then((response) => {
if (!response.ok) {
alert('failed to create link');
}
return response.json();
})
.then((result) => {
if (result.status != 'ok') {
alert('failed to create link');
return;
}
$('shareLink').value = result.url;
});
}
function exportJSON() {
let content = JSON.stringify(lastCfg, null, 4);
const file = new File([content], 'cloudflare-error-page-params.json', {
type: 'text/plain',
});
const url = URL.createObjectURL(file);
const link = document.createElement('a');
link.href = url;
link.download = file.name;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
}
function resizePreviewFrame() {
const iframe = $('previewFrame');
const height = iframe.contentWindow.document.body.scrollHeight + 2;
iframe.style.setProperty('--expanded-height', height + 'px');
}
/* Update status block colors based on selected status and error_source */
function updateStatusBlockStyles() {
const browserOk = $('browser_status').value === 'ok';
const cfOk = $('cloudflare_status').value === 'ok';
const hostOk = $('host_status').value === 'ok';
setBlockClass('block_browser', browserOk ? 'status-ok' : 'status-error');
setBlockClass('block_cloudflare', cfOk ? 'status-ok' : 'status-error');
setBlockClass('block_host', hostOk ? 'status-ok' : 'status-error');
}
function setBlockClass(id, cls) {
const el = $(id);
if (!el) return;
el.classList.remove('status-ok', 'status-error');
el.classList.add(cls);
}
/* Simple debounce */
function debounce(fn, wait) {
let t;
return (...args) => {
clearTimeout(t);
t = setTimeout(() => fn(...args), wait);
};
}
/* Wire up events */
// initialize form values from initialConfig
loadConfig(initialConfig);
render();
// On preset change, load preset and render
$('presetSelect').addEventListener('change', (e) => {
const p = e.target.value;
if (PRESETS[p]) loadConfig(PRESETS[p]);
render();
});
// Render / Open button handlers
// $('btnRender').addEventListener('click', e => { e.preventDefault(); render(); });
$('btnOpen').addEventListener('click', (e) => {
e.preventDefault();
openInNewTab();
});
$('btnShare').addEventListener('click', (e) => {
e.preventDefault();
createShareableLink();
});
$('btnExport').addEventListener('click', (e) => {
e.preventDefault();
exportJSON();
});
$('btnCopyLink').addEventListener('click', () => {
const field = $('shareLink');
field.select();
field.setSelectionRange(0, field.value.length);
navigator.clipboard.writeText(field.value).then(() => {
// No notification required unless you want one
});
});
// Input change -> render
const inputs = document.querySelectorAll('#editorForm input, #editorForm textarea, #editorForm select');
inputs.forEach((inp) => {
inp.addEventListener(
'input',
debounce(() => {
// Update status block color classes for quick visual feedback in the editor
render();
}, 200)
);
// for radio change events (error_source)
if (inp.type === 'radio')
inp.addEventListener('change', () => {
render();
});
});
// Automatically update frame height
const observer = new ResizeObserver((entries) => resizePreviewFrame());
const iframe = $('previewFrame');
observer.observe(iframe.contentWindow.document.body);
// resizePreviewFrame()
setInterval(resizePreviewFrame, 1000); // TODO...

File diff suppressed because one or more lines are too long

View File

@@ -1,45 +0,0 @@
{
// Visit https://aka.ms/tsconfig to read more about this file
"include": ["src/**/*.ts"],
"compilerOptions": {
// File Layout
// "rootDir": "./src",
"outDir": "./dist",
// Environment Settings
// See also https://aka.ms/tsconfig/module
"module": "esnext",
"target": "esnext",
"types": [],
// For nodejs:
// "lib": ["esnext"],
// "types": ["node"],
// and npm install -D @types/node
// Other Outputs
"sourceMap": true,
"declaration": true,
"declarationMap": true,
// Stricter Typechecking Options
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
// Style Options
// "noImplicitReturns": true,
// "noImplicitOverride": true,
// "noUnusedLocals": true,
// "noUnusedParameters": true,
// "noFallthroughCasesInSwitch": true,
// "noPropertyAccessFromIndexSignature": true,
"strict": true,
"jsx": "react-jsx",
// "verbatimModuleSyntax": true,
"isolatedModules": true,
"noUncheckedSideEffectImports": true,
"moduleResolution": "bundler",
"moduleDetection": "force",
"skipLibCheck": true
}
}

View File

@@ -1,43 +0,0 @@
/// <reference types="vite/types/importMeta.d.ts" />
import { defineConfig, loadEnv } from 'vite';
import { minify as htmlMinify } from 'html-minifier-terser';
import process from 'node:process';
export default defineConfig(({ mode }) => {
const baseUrl = mode === 'production' ? '' : '/editor/';
return {
appType: 'mpa',
base: baseUrl,
build: {
minify: true,
sourcemap: true,
},
server: {
port: 3000,
proxy: {
'/s': {
target: 'http://localhost:5000',
},
},
},
plugins: [
{
name: 'html-minifier',
transformIndexHtml: {
order: 'post',
handler(html) {
return htmlMinify(html, {
collapseWhitespace: true,
removeComments: true,
minifyCSS: true,
minifyJS: true,
});
},
},
},
],
};
});

View File

@@ -1,605 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@esbuild/aix-ppc64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz#80fcbe36130e58b7670511e888b8e88a259ed76c"
integrity sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==
"@esbuild/android-arm64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz#8aa4965f8d0a7982dc21734bf6601323a66da752"
integrity sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==
"@esbuild/android-arm@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.12.tgz#300712101f7f50f1d2627a162e6e09b109b6767a"
integrity sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==
"@esbuild/android-x64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.12.tgz#87dfb27161202bdc958ef48bb61b09c758faee16"
integrity sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==
"@esbuild/darwin-arm64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz#79197898ec1ff745d21c071e1c7cc3c802f0c1fd"
integrity sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==
"@esbuild/darwin-x64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz#146400a8562133f45c4d2eadcf37ddd09718079e"
integrity sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==
"@esbuild/freebsd-arm64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz#1c5f9ba7206e158fd2b24c59fa2d2c8bb47ca0fe"
integrity sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==
"@esbuild/freebsd-x64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz#ea631f4a36beaac4b9279fa0fcc6ca29eaeeb2b3"
integrity sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==
"@esbuild/linux-arm64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz#e1066bce58394f1b1141deec8557a5f0a22f5977"
integrity sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==
"@esbuild/linux-arm@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz#452cd66b20932d08bdc53a8b61c0e30baf4348b9"
integrity sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==
"@esbuild/linux-ia32@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz#b24f8acc45bcf54192c7f2f3be1b53e6551eafe0"
integrity sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==
"@esbuild/linux-loong64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz#f9cfffa7fc8322571fbc4c8b3268caf15bd81ad0"
integrity sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==
"@esbuild/linux-mips64el@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz#575a14bd74644ffab891adc7d7e60d275296f2cd"
integrity sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==
"@esbuild/linux-ppc64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz#75b99c70a95fbd5f7739d7692befe60601591869"
integrity sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==
"@esbuild/linux-riscv64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz#2e3259440321a44e79ddf7535c325057da875cd6"
integrity sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==
"@esbuild/linux-s390x@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz#17676cabbfe5928da5b2a0d6df5d58cd08db2663"
integrity sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==
"@esbuild/linux-x64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz#0583775685ca82066d04c3507f09524d3cd7a306"
integrity sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==
"@esbuild/netbsd-arm64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz#f04c4049cb2e252fe96b16fed90f70746b13f4a4"
integrity sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==
"@esbuild/netbsd-x64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz#77da0d0a0d826d7c921eea3d40292548b258a076"
integrity sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==
"@esbuild/openbsd-arm64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz#6296f5867aedef28a81b22ab2009c786a952dccd"
integrity sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==
"@esbuild/openbsd-x64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz#f8d23303360e27b16cf065b23bbff43c14142679"
integrity sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==
"@esbuild/openharmony-arm64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz#49e0b768744a3924be0d7fd97dd6ce9b2923d88d"
integrity sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==
"@esbuild/sunos-x64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz#a6ed7d6778d67e528c81fb165b23f4911b9b13d6"
integrity sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==
"@esbuild/win32-arm64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz#9ac14c378e1b653af17d08e7d3ce34caef587323"
integrity sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==
"@esbuild/win32-ia32@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz#918942dcbbb35cc14fca39afb91b5e6a3d127267"
integrity sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==
"@esbuild/win32-x64@0.25.12":
version "0.25.12"
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz#9bdad8176be7811ad148d1f8772359041f46c6c5"
integrity sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==
"@jridgewell/gen-mapping@^0.3.5":
version "0.3.13"
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f"
integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==
dependencies:
"@jridgewell/sourcemap-codec" "^1.5.0"
"@jridgewell/trace-mapping" "^0.3.24"
"@jridgewell/resolve-uri@^3.1.0":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6"
integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==
"@jridgewell/source-map@^0.3.3":
version "0.3.11"
resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.11.tgz#b21835cbd36db656b857c2ad02ebd413cc13a9ba"
integrity sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==
dependencies:
"@jridgewell/gen-mapping" "^0.3.5"
"@jridgewell/trace-mapping" "^0.3.25"
"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0":
version "1.5.5"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba"
integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==
"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25":
version "0.3.31"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0"
integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==
dependencies:
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
"@rollup/rollup-android-arm-eabi@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz#7e478b66180c5330429dd161bf84dad66b59c8eb"
integrity sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==
"@rollup/rollup-android-arm64@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz#2b025510c53a5e3962d3edade91fba9368c9d71c"
integrity sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==
"@rollup/rollup-darwin-arm64@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz#3577c38af68ccf34c03e84f476bfd526abca10a0"
integrity sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==
"@rollup/rollup-darwin-x64@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz#2bf5f2520a1f3b551723d274b9669ba5b75ed69c"
integrity sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==
"@rollup/rollup-freebsd-arm64@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz#4bb9cc80252564c158efc0710153c71633f1927c"
integrity sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==
"@rollup/rollup-freebsd-x64@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz#2301289094d49415a380cf942219ae9d8b127440"
integrity sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==
"@rollup/rollup-linux-arm-gnueabihf@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz#1d03d776f2065e09fc141df7d143476e94acca88"
integrity sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==
"@rollup/rollup-linux-arm-musleabihf@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz#8623de0e040b2fd52a541c602688228f51f96701"
integrity sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==
"@rollup/rollup-linux-arm64-gnu@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz#ce2d1999bc166277935dde0301cde3dd0417fb6e"
integrity sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==
"@rollup/rollup-linux-arm64-musl@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz#88c2523778444da952651a2219026416564a4899"
integrity sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==
"@rollup/rollup-linux-loong64-gnu@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz#578ca2220a200ac4226c536c10c8cc6e4f276714"
integrity sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==
"@rollup/rollup-linux-ppc64-gnu@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz#aa338d3effd4168a20a5023834a74ba2c3081293"
integrity sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==
"@rollup/rollup-linux-riscv64-gnu@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz#16ba582f9f6cff58119aa242782209b1557a1508"
integrity sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==
"@rollup/rollup-linux-riscv64-musl@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz#e404a77ebd6378483888b8064c703adb011340ab"
integrity sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==
"@rollup/rollup-linux-s390x-gnu@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz#92ad52d306227c56bec43d96ad2164495437ffe6"
integrity sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==
"@rollup/rollup-linux-x64-gnu@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz#fd0dea3bb9aa07e7083579f25e1c2285a46cb9fa"
integrity sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==
"@rollup/rollup-linux-x64-musl@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz#37a3efb09f18d555f8afc490e1f0444885de8951"
integrity sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==
"@rollup/rollup-openharmony-arm64@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz#c489bec9f4f8320d42c9b324cca220c90091c1f7"
integrity sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==
"@rollup/rollup-win32-arm64-msvc@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz#152832b5f79dc22d1606fac3db946283601b7080"
integrity sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==
"@rollup/rollup-win32-ia32-msvc@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz#54d91b2bb3bf3e9f30d32b72065a4e52b3a172a5"
integrity sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==
"@rollup/rollup-win32-x64-gnu@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz#df9df03e61a003873efec8decd2034e7f135c71e"
integrity sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==
"@rollup/rollup-win32-x64-msvc@4.53.3":
version "4.53.3"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz#38ae84f4c04226c1d56a3b17296ef1e0460ecdfe"
integrity sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==
"@types/estree@1.0.8":
version "1.0.8"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e"
integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
"@types/node@^24.10.2":
version "24.10.2"
resolved "https://registry.yarnpkg.com/@types/node/-/node-24.10.2.tgz#82a57476a19647d8f2c7750d0924788245e39b26"
integrity sha512-WOhQTZ4G8xZ1tjJTvKOpyEVSGgOTvJAfDK3FNFgELyaTpzhdgHVHeqW8V+UJvzF5BT+/B54T/1S2K6gd9c7bbA==
dependencies:
undici-types "~7.16.0"
acorn@^8.15.0:
version "8.15.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816"
integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==
async@^3.2.6:
version "3.2.6"
resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce"
integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==
balanced-match@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
bootstrap@^5.3.8:
version "5.3.8"
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.3.8.tgz#6401a10057a22752d21f4e19055508980656aeed"
integrity sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==
brace-expansion@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.2.tgz#54fc53237a613d854c7bd37463aad17df87214e7"
integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==
dependencies:
balanced-match "^1.0.0"
buffer-from@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
camel-case@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a"
integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==
dependencies:
pascal-case "^3.1.2"
tslib "^2.0.3"
clean-css@~5.3.2:
version "5.3.3"
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.3.3.tgz#b330653cd3bd6b75009cc25c714cae7b93351ccd"
integrity sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==
dependencies:
source-map "~0.6.0"
commander@^10.0.0:
version "10.0.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06"
integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==
commander@^2.20.0:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
dot-case@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751"
integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==
dependencies:
no-case "^3.0.4"
tslib "^2.0.3"
ejs@^3.1.10:
version "3.1.10"
resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b"
integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==
dependencies:
jake "^10.8.5"
entities@^4.4.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
esbuild@^0.25.0:
version "0.25.12"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.12.tgz#97a1d041f4ab00c2fce2f838d2b9969a2d2a97a5"
integrity sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==
optionalDependencies:
"@esbuild/aix-ppc64" "0.25.12"
"@esbuild/android-arm" "0.25.12"
"@esbuild/android-arm64" "0.25.12"
"@esbuild/android-x64" "0.25.12"
"@esbuild/darwin-arm64" "0.25.12"
"@esbuild/darwin-x64" "0.25.12"
"@esbuild/freebsd-arm64" "0.25.12"
"@esbuild/freebsd-x64" "0.25.12"
"@esbuild/linux-arm" "0.25.12"
"@esbuild/linux-arm64" "0.25.12"
"@esbuild/linux-ia32" "0.25.12"
"@esbuild/linux-loong64" "0.25.12"
"@esbuild/linux-mips64el" "0.25.12"
"@esbuild/linux-ppc64" "0.25.12"
"@esbuild/linux-riscv64" "0.25.12"
"@esbuild/linux-s390x" "0.25.12"
"@esbuild/linux-x64" "0.25.12"
"@esbuild/netbsd-arm64" "0.25.12"
"@esbuild/netbsd-x64" "0.25.12"
"@esbuild/openbsd-arm64" "0.25.12"
"@esbuild/openbsd-x64" "0.25.12"
"@esbuild/openharmony-arm64" "0.25.12"
"@esbuild/sunos-x64" "0.25.12"
"@esbuild/win32-arm64" "0.25.12"
"@esbuild/win32-ia32" "0.25.12"
"@esbuild/win32-x64" "0.25.12"
fdir@^6.5.0:
version "6.5.0"
resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350"
integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==
filelist@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5"
integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==
dependencies:
minimatch "^5.0.1"
fsevents@~2.3.2, fsevents@~2.3.3:
version "2.3.3"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
html-minifier-terser@^7.2.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz#18752e23a2f0ed4b0f550f217bb41693e975b942"
integrity sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==
dependencies:
camel-case "^4.1.2"
clean-css "~5.3.2"
commander "^10.0.0"
entities "^4.4.0"
param-case "^3.0.4"
relateurl "^0.2.7"
terser "^5.15.1"
jake@^10.8.5:
version "10.9.4"
resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.4.tgz#d626da108c63d5cfb00ab5c25fadc7e0084af8e6"
integrity sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==
dependencies:
async "^3.2.6"
filelist "^1.0.4"
picocolors "^1.1.1"
lower-case@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28"
integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==
dependencies:
tslib "^2.0.3"
minimatch@^5.0.1:
version "5.1.6"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96"
integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==
dependencies:
brace-expansion "^2.0.1"
nanoid@^3.3.11:
version "3.3.11"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b"
integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==
no-case@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d"
integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==
dependencies:
lower-case "^2.0.2"
tslib "^2.0.3"
param-case@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5"
integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==
dependencies:
dot-case "^3.0.4"
tslib "^2.0.3"
pascal-case@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb"
integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==
dependencies:
no-case "^3.0.4"
tslib "^2.0.3"
picocolors@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
picomatch@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042"
integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==
postcss@^8.5.6:
version "8.5.6"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c"
integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==
dependencies:
nanoid "^3.3.11"
picocolors "^1.1.1"
source-map-js "^1.2.1"
prettier@3.7.4:
version "3.7.4"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.7.4.tgz#d2f8335d4b1cec47e1c8098645411b0c9dff9c0f"
integrity sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==
relateurl@^0.2.7:
version "0.2.7"
resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==
rollup@^4.43.0:
version "4.53.3"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.53.3.tgz#dbc8cd8743b38710019fb8297e8d7a76e3faa406"
integrity sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==
dependencies:
"@types/estree" "1.0.8"
optionalDependencies:
"@rollup/rollup-android-arm-eabi" "4.53.3"
"@rollup/rollup-android-arm64" "4.53.3"
"@rollup/rollup-darwin-arm64" "4.53.3"
"@rollup/rollup-darwin-x64" "4.53.3"
"@rollup/rollup-freebsd-arm64" "4.53.3"
"@rollup/rollup-freebsd-x64" "4.53.3"
"@rollup/rollup-linux-arm-gnueabihf" "4.53.3"
"@rollup/rollup-linux-arm-musleabihf" "4.53.3"
"@rollup/rollup-linux-arm64-gnu" "4.53.3"
"@rollup/rollup-linux-arm64-musl" "4.53.3"
"@rollup/rollup-linux-loong64-gnu" "4.53.3"
"@rollup/rollup-linux-ppc64-gnu" "4.53.3"
"@rollup/rollup-linux-riscv64-gnu" "4.53.3"
"@rollup/rollup-linux-riscv64-musl" "4.53.3"
"@rollup/rollup-linux-s390x-gnu" "4.53.3"
"@rollup/rollup-linux-x64-gnu" "4.53.3"
"@rollup/rollup-linux-x64-musl" "4.53.3"
"@rollup/rollup-openharmony-arm64" "4.53.3"
"@rollup/rollup-win32-arm64-msvc" "4.53.3"
"@rollup/rollup-win32-ia32-msvc" "4.53.3"
"@rollup/rollup-win32-x64-gnu" "4.53.3"
"@rollup/rollup-win32-x64-msvc" "4.53.3"
fsevents "~2.3.2"
source-map-js@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
source-map-support@~0.5.20:
version "0.5.21"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
dependencies:
buffer-from "^1.0.0"
source-map "^0.6.0"
source-map@^0.6.0, source-map@~0.6.0:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
terser@^5.15.1:
version "5.44.1"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.44.1.tgz#e391e92175c299b8c284ad6ded609e37303b0a9c"
integrity sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==
dependencies:
"@jridgewell/source-map" "^0.3.3"
acorn "^8.15.0"
commander "^2.20.0"
source-map-support "~0.5.20"
tinyglobby@^0.2.15:
version "0.2.15"
resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2"
integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==
dependencies:
fdir "^6.5.0"
picomatch "^4.0.3"
tslib@^2.0.3:
version "2.8.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
typescript@^5.9.3:
version "5.9.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f"
integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==
undici-types@~7.16.0:
version "7.16.0"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.16.0.tgz#ffccdff36aea4884cbfce9a750a0580224f58a46"
integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==
vite@^7.2.6:
version "7.2.7"
resolved "https://registry.yarnpkg.com/vite/-/vite-7.2.7.tgz#0789a4c3206081699f34a9ecca2dda594a07478e"
integrity sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==
dependencies:
esbuild "^0.25.0"
fdir "^6.5.0"
picomatch "^4.0.3"
postcss "^8.5.6"
rollup "^4.43.0"
tinyglobby "^0.2.15"
optionalDependencies:
fsevents "~2.3.3"

BIN
example.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

BIN
example2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

View File

@@ -1,23 +0,0 @@
{
"title": "Catastrophic infrastructure failure",
"more_information": {
"for": "no help at all"
},
"browser_status": {
"status": "error",
"status_text": "Out of Memory"
},
"cloudflare_status": {
"status": "error",
"location": "Everywhere",
"status_text": "Error"
},
"host_status": {
"status": "error",
"location": "example.com",
"status_text": "On Fire"
},
"error_source": "cloudflare",
"what_happened": "<p>There is a catastrophic failure.</p>",
"what_can_i_do": "<p>Please try again in a few years.</p>"
}

View File

@@ -1,18 +0,0 @@
{
"title": "Internal server error",
"error_code": 500,
"browser_status": {
"status": "ok"
},
"cloudflare_status": {
"status": "error",
"status_text": "Error"
},
"host_status": {
"status": "ok",
"location": "example.com"
},
"error_source": "cloudflare",
"what_happened": "<p>There is an internal server error on Cloudflare\"s network.</p>",
"what_can_i_do": "<p>Please try again in a few minutes.</p>"
}

View File

@@ -1,31 +0,0 @@
#!/usr/bin/env python3
import os
import sys
import webbrowser
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from cloudflare_error_page import render as render_cf_error_page
error_page = render_cf_error_page({
'browser_status': {
"status": 'ok',
},
'cloudflare_status': {
"status": 'error',
"status_text": 'Error',
},
'host_status': {
"status": 'ok',
"location": 'example.com',
},
'error_source': 'cloudflare', # 'browser', 'cloudflare', or 'host'
'what_happened': '<p>There is an internal server error on Cloudflare\'s network.</p>',
'what_can_i_do': '<p>Please try again in a few minutes.</p>',
})
with open('error.html', 'w') as f:
f.write(error_page)
webbrowser.open('error.html')

View File

@@ -1,64 +0,0 @@
#!/usr/bin/env python3
import os
import re
import sys
from flask import (
Flask,
request,
send_from_directory
)
# Append this directory to sys.path is not required if the package is already installed
examples_dir = os.path.dirname(os.path.abspath(__file__))
sys.path.append(os.path.dirname(examples_dir))
from cloudflare_error_page import render as render_cf_error_page
app = Flask(__name__)
@app.route('/')
def index():
params = {
"title": "Internal server error",
"error_code": 500,
"browser_status": {
"status": "ok"
},
"cloudflare_status": {
"status": "error",
"status_text": "Error"
},
"host_status": {
"status": "ok",
"location": "example.com"
},
"error_source": "cloudflare",
"what_happened": "<p>There is an internal server error on Cloudflare\"s network.</p>",
"what_can_i_do": "<p>Please try again in a few minutes.</p>"
}
# Get the real Ray ID from Cloudflare header
ray_id = request.headers.get('Cf-Ray', '')[:16]
# Get the real client ip from Cloudflare header or request.remote_addr
client_ip = request.headers.get('X-Forwarded-For')
if not client_ip:
client_ip = request.remote_addr
params.update({
'ray_id': ray_id,
'client_ip': client_ip,
})
# Render the error page
return render_cf_error_page(params), 500
if __name__ == '__main__':
host = sys.argv[1] if len(sys.argv) > 1 else None
port = int(sys.argv[2]) if len(sys.argv) > 2 else None
app.run(debug=True, host=host, port=port)

View File

@@ -1,23 +0,0 @@
{
"title": "Web server is working",
"error_code": 200,
"more_information": {
"hidden": true
},
"browser_status": {
"status": "ok",
"status_text": "Seems Working"
},
"cloudflare_status": {
"status": "ok",
"status_text": "Often Working"
},
"host_status": {
"status": "ok",
"location": "example.com",
"status_text": "Almost Working"
},
"error_source": "host",
"what_happened": "<p>This site is still working. And it looks great.</p>",
"what_can_i_do": "<p>Visit the site before it crashes someday.</p>"
}

6
nodejs/.gitignore vendored
View File

@@ -1,6 +0,0 @@
node_modules/
dist/
*.log
*.html
.DS_Store
examples/*.html

View File

@@ -1,8 +0,0 @@
src/
tsconfig.json
*.ts
!*.d.ts
examples/
node_modules/
*.log
.DS_Store

View File

@@ -1,9 +0,0 @@
MIT License
Copyright (c) 2025 Anthony Donlon
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,141 +0,0 @@
# Cloudflare Error Page Generator (Node.js/TypeScript)
Carbon copy of the original Python version.
## Installation
```bash
npm install cloudflare-error-page
```
Or install from GitHub:
```bash
npm install git+https://github.com/donlon/cloudflare-error-page.git#main:nodejs
```
## Quick Start
```typescript
import { render } from 'cloudflare-error-page';
import * as fs from 'fs';
const errorPage = render({
browser_status: { status: 'ok' },
cloudflare_status: { status: 'error', status_text: 'Error' },
host_status: { status: 'ok', location: 'example.com' },
error_source: 'cloudflare',
what_happened: '<p>There is an internal server error on Cloudflare\'s network.</p>',
what_can_i_do: '<p>Please try again in a few minutes.</p>',
});
fs.writeFileSync('error.html', errorPage);
```
## API Reference
### `render(params: ErrorPageParams, allowHtml?: boolean): string`
Generates an HTML error page based on the provided parameters.
#### Parameters
- `params`: An object containing error page configuration
- `allowHtml` (optional): Whether to allow HTML in `what_happened` and `what_can_i_do` fields. Default: `true`
#### ErrorPageParams Interface
```typescript
interface ErrorPageParams {
// Basic information
error_code?: number; // Default: 500
title?: string; // Default: 'Internal server error'
html_title?: string; // Default: '{error_code}: {title}'
time?: string; // Auto-generated if not provided
ray_id?: string; // Auto-generated if not provided
client_ip?: string; // Default: '1.1.1.1'
// Status for each component
browser_status?: StatusItem;
cloudflare_status?: StatusItem;
host_status?: StatusItem;
// Error source indicator
error_source?: 'browser' | 'cloudflare' | 'host';
// Content sections
what_happened?: string; // HTML content
what_can_i_do?: string; // HTML content
// Optional customization
more_information?: MoreInformation;
perf_sec_by?: PerfSecBy;
creator_info?: CreatorInfo;
}
interface StatusItem {
status?: 'ok' | 'error';
status_text?: string; // Default: 'Working' or 'Error'
status_text_color?: string; // CSS color
location?: string;
name?: string;
}
```
## Examples
### Basic Error Page
```typescript
import { render } from 'cloudflare-error-page';
const html = render({
cloudflare_status: { status: 'error' },
error_source: 'cloudflare',
what_happened: '<p>Something went wrong.</p>',
what_can_i_do: '<p>Try again later.</p>',
});
```
### Express.js Integration
```typescript
import express from 'express';
import { render } from 'cloudflare-error-page';
const app = express();
app.use((err, req, res, next) => {
const errorPage = render({
error_code: err.status || 500,
title: err.message || 'Internal server error',
cloudflare_status: { status: 'ok' },
host_status: {
status: 'error',
location: req.hostname
},
error_source: 'host',
what_happened: `<p>${err.message}</p>`,
what_can_i_do: '<p>Please try again or contact support.</p>',
});
res.status(err.status || 500).send(errorPage);
});
```
## TypeScript Support
This package includes full TypeScript type definitions. Import types as needed:
```typescript
import { render, ErrorPageParams, StatusItem } from 'cloudflare-error-page';
```
## License
MIT
## Related
- [Python version](https://github.com/donlon/cloudflare-error-page)
- [Online Editor](https://virt.moe/cloudflare-error-page/editor/)

View File

@@ -1,32 +0,0 @@
import { render } from '../dist/index.js';
import * as fs from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Generate an error page
const errorPage = render({
browser_status: {
status: 'ok',
},
cloudflare_status: {
status: 'error',
status_text: 'Error',
},
host_status: {
status: 'ok',
location: 'example.com',
},
error_source: 'cloudflare',
what_happened: '<p>There is an internal server error on Cloudflare\'s network.</p>',
what_can_i_do: '<p>Please try again in a few minutes.</p>',
});
const outputPath = join(__dirname, 'error.html');
fs.writeFileSync(outputPath, errorPage);
console.log(`Error page generated: ${outputPath}`);
console.log('Open the file in your browser to view it.');

View File

@@ -1,32 +0,0 @@
import { render } from '../dist/index.js';
import * as fs from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Test all JSON configs from oringinal examples directory
const testConfigs = [
{ file: '../../examples/default.json', output: 'test-default.html' },
{ file: '../../examples/working.json', output: 'test-working.html' },
{ file: '../../examples/catastrophic.json', output: 'test-catastrophic.html' }
];
testConfigs.forEach(({ file, output }) => {
try {
const configPath = join(__dirname, file);
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
console.log(`Testing: ${file}`);
console.log(`Config keys: ${Object.keys(config).join(', ')}`);
const html = render(config);
const outputPath = join(__dirname, output);
fs.writeFileSync(outputPath, html);
} catch (error) {
console.error(`Something went wrong: ${file}`);
console.error(`Error: ${error.message}\n`);
}
});

142
nodejs/package-lock.json generated
View File

@@ -1,142 +0,0 @@
{
"name": "cloudflare-error-page",
"version": "0.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "cloudflare-error-page",
"version": "0.0.1",
"license": "MIT",
"dependencies": {
"ejs": "^3.1.10"
},
"devDependencies": {
"@types/ejs": "^3.1.5",
"@types/node": "^20.0.0",
"typescript": "^5.3.0"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@types/ejs": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.5.tgz",
"integrity": "sha512-nv+GSx77ZtXiJzwKdsASqi+YQ5Z7vwHsTP0JY2SiQgjGckkBRKZnk8nIM+7oUZ1VCtuTz0+By4qVR7fqzp/Dfg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": {
"version": "20.19.26",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.26.tgz",
"integrity": "sha512-0l6cjgF0XnihUpndDhk+nyD3exio3iKaYROSgvh/qSevPXax3L8p5DBRFjbvalnwatGgHEQn2R88y2fA3g4irg==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
}
},
"node_modules/async": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
"license": "MIT"
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
"node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/ejs": {
"version": "3.1.10",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
"integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
"license": "Apache-2.0",
"dependencies": {
"jake": "^10.8.5"
},
"bin": {
"ejs": "bin/cli.js"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/filelist": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
"integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==",
"license": "Apache-2.0",
"dependencies": {
"minimatch": "^5.0.1"
}
},
"node_modules/jake": {
"version": "10.9.4",
"resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz",
"integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==",
"license": "Apache-2.0",
"dependencies": {
"async": "^3.2.6",
"filelist": "^1.0.4",
"picocolors": "^1.1.1"
},
"bin": {
"jake": "bin/cli.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/minimatch": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=10"
}
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"license": "ISC"
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true,
"license": "MIT"
}
}
}

View File

@@ -1,45 +0,0 @@
{
"name": "cloudflare-error-page",
"version": "0.0.1",
"description": "Cloudflare Error Page Generator",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"type": "module",
"scripts": {
"build": "tsc",
"prepublishOnly": "pnpm run build",
"example": "node examples/example.js"
},
"keywords": [
"cloudflare",
"error-page",
"error",
"http",
"html",
"template"
],
"author": "Anthony Donlon",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/donlon/cloudflare-error-page.git",
"directory": "nodejs"
},
"engines": {
"node": ">=18.0.0"
},
"dependencies": {
"ejs": "^3.1.10"
},
"devDependencies": {
"@types/ejs": "^3.1.5",
"@types/node": "^20.0.0",
"typescript": "^5.3.0"
},
"files": [
"dist/**/*",
"templates/**/*",
"README.md",
"LICENSE"
]
}

View File

@@ -1,123 +0,0 @@
import * as fs from "fs";
import * as path from "path";
import { fileURLToPath } from "url";
import * as ejs from "ejs";
import * as crypto from "crypto";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export interface StatusItem {
status?: "ok" | "error";
status_text?: string;
status_text_color?: string;
location?: string;
name?: string;
}
export interface MoreInformation {
hidden?: boolean;
link?: string;
text?: string;
for?: string;
}
export interface PerfSecBy {
link?: string;
text?: string;
}
export interface CreatorInfo {
hidden?: boolean;
link?: string;
text?: string;
}
export interface ErrorPageParams {
error_code?: number;
title?: string;
html_title?: string;
time?: string;
ray_id?: string;
client_ip?: string;
browser_status?: StatusItem;
cloudflare_status?: StatusItem;
host_status?: StatusItem;
error_source?: "browser" | "cloudflare" | "host";
what_happened?: string;
what_can_i_do?: string;
more_information?: MoreInformation;
perf_sec_by?: PerfSecBy;
creator_info?: CreatorInfo;
}
/**
* Fill default parameters if not provided
*/
function fillParams(params: ErrorPageParams): ErrorPageParams {
const filledParams = { ...params };
if (!filledParams.time) {
const now = new Date();
filledParams.time =
now.toISOString().replace("T", " ").substring(0, 19) + " UTC";
}
if (!filledParams.ray_id) {
filledParams.ray_id = crypto.randomBytes(8).toString("hex");
}
return filledParams;
}
/**
* Escape HTML special characters
*/
function escapeHtml(text: string): string {
const htmlEscapeMap: Record<string, string> = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#39;",
};
return text.replace(/[&<>"']/g, (char) => htmlEscapeMap[char] || char);
}
/**
* Render a customized Cloudflare error page
* @param params - The parameters for the error page
* @param allowHtml - Whether to allow HTML in what_happened and what_can_i_do fields (default: true)
* @returns The rendered HTML string
*/
export function render(
params: ErrorPageParams,
allowHtml: boolean = true
): string {
let processedParams = fillParams(params);
if (!allowHtml) {
processedParams = { ...processedParams };
if (processedParams.what_happened) {
processedParams.what_happened = escapeHtml(processedParams.what_happened);
}
if (processedParams.what_can_i_do) {
processedParams.what_can_i_do = escapeHtml(processedParams.what_can_i_do);
}
}
// Load EJS template
const templatePath = path.join(__dirname, "..", "templates", "error.ejs");
const template = fs.readFileSync(templatePath, "utf-8");
const rendered = ejs.render(template, { params: processedParams });
return rendered;
}
export default render;

File diff suppressed because one or more lines are too long

View File

@@ -1,20 +0,0 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020"],
"moduleResolution": "node",
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "examples"]
}

View File

@@ -1,24 +0,0 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "cloudflare-error-page"
version = "0.1.0"
description = "A customizable Cloudflare error page generator"
authors = [{ name = "Anthony Donlon" }]
license = { text = "MIT" }
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"jinja2>=3.0"
]
[project.urls]
Homepage = "https://github.com/donlon/cloudflare-error-page"
[tool.hatch.build]
include = [
"cloudflare_error_page/*.py",
"cloudflare_error_page/templates/*",
]

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 80.7362">
<path d="M89.8358.1636H10.1642C4.6398.1636.1614,4.6421.1614,10.1664v60.4033c0,5.5244,4.4784,10.0028,10.0028,10.0028h79.6716c5.5244,0,10.0027-4.4784,10.0027-10.0028V10.1664c0-5.5244-4.4784-10.0028-10.0027-10.0028ZM22.8323,9.6103c1.9618,0,3.5522,1.5903,3.5522,3.5521s-1.5904,3.5522-3.5522,3.5522-3.5521-1.5904-3.5521-3.5522,1.5903-3.5521,3.5521-3.5521ZM12.8936,9.6103c1.9618,0,3.5522,1.5903,3.5522,3.5521s-1.5904,3.5522-3.5522,3.5522-3.5521-1.5904-3.5521-3.5522,1.5903-3.5521,3.5521-3.5521ZM89.8293,70.137H9.7312V24.1983h80.0981v45.9387ZM89.8293,16.1619H29.8524v-5.999h59.977v5.999Z" style="fill: #999;"/>
</svg>

Before

Width:  |  Height:  |  Size: 725 B

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 152 78.9141">
<path d="M132.2996,77.9927v-.0261c10.5477-.2357,19.0305-8.8754,19.0305-19.52,0-10.7928-8.7161-19.5422-19.4678-19.5422-2.9027,0-5.6471.6553-8.1216,1.7987C123.3261,18.6624,105.3419.9198,83.202.9198c-17.8255,0-32.9539,11.5047-38.3939,27.4899-3.0292-2.2755-6.7818-3.6403-10.8622-3.6403-10.0098,0-18.1243,8.1145-18.1243,18.1243,0,1.7331.258,3.4033.7122,4.9905-.2899-.0168-.5769-.0442-.871-.0442-8.2805,0-14.993,6.7503-14.993,15.0772,0,8.2795,6.6381,14.994,14.8536,15.0701v.0054h.1069c.0109,0,.0215.0016.0325.0016s.0215-.0016.0325-.0016" style="fill: #999;"/>
</svg>

Before

Width:  |  Height:  |  Size: 675 B

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 47.9145 47.9641">
<circle cx="23.9572" cy="23.982" r="23.4815" style="fill: #bd2426;"/>
<line x1="19.0487" y1="19.0768" x2="27.8154" y2="28.8853" style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: 3px;"/>
<line x1="27.8154" y1="19.0768" x2="19.0487" y2="28.8853" style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: 3px;"/>
</svg>

Before

Width:  |  Height:  |  Size: 519 B

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
<circle cx="24" cy="24" r="23.4815" style="fill: #9bca3e;"/>
<polyline points="17.453 24.9841 21.7183 30.4504 30.2076 16.8537" style="fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round; stroke-width: 4px;"/>
</svg>

Before

Width:  |  Height:  |  Size: 346 B

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 95 75">
<path d="M94.0103,45.0775l-12.9885-38.4986c-1.2828-3.8024-4.8488-6.3624-8.8618-6.3619l-49.91.0065c-3.9995.0005-7.556,2.5446-8.8483,6.3295L1.0128,42.8363c-.3315.971-.501,1.9899-.5016,3.0159l-.0121,19.5737c-.0032,5.1667,4.1844,9.3569,9.3513,9.3569h75.2994c5.1646,0,9.3512-4.1866,9.3512-9.3512v-17.3649c0-1.0165-.1657-2.0262-.4907-2.9893ZM86.7988,65.3097c0,1.2909-1.0465,2.3374-2.3374,2.3374H9.9767c-1.2909,0-2.3374-1.0465-2.3374-2.3374v-18.1288c0-1.2909,1.0465-2.3374,2.3374-2.3374h74.4847c1.2909,0,2.3374,1.0465,2.3374,2.3374v18.1288Z" style="fill: #999;"/>
<circle cx="74.6349" cy="56.1889" r="4.7318" style="fill: #999;"/>
<circle cx="59.1472" cy="56.1889" r="4.7318" style="fill: #999;"/>
</svg>

Before

Width:  |  Height:  |  Size: 810 B

View File

@@ -1 +0,0 @@
main.css

File diff suppressed because one or more lines are too long

View File

@@ -1,123 +0,0 @@
<!DOCTYPE html>
<!--[if lt IE 7]> <html class="no-js ie6 oldie" lang="en-US"> <![endif]-->
<!--[if IE 7]> <html class="no-js ie7 oldie" lang="en-US"> <![endif]-->
<!--[if IE 8]> <html class="no-js ie8 oldie" lang="en-US"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js" lang="en-US"> <!--<![endif]-->
<head>
<%
let error_code = params.error_code || 500;
let title = params.title || 'Internal server error';
let html_title_output = params.html_title || (error_code + ': ' + title);
%>
<title><%= html_title_output %></title>
<meta charset="UTF-8" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
<meta name="robots" content="noindex, nofollow" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<!-- @INLINE_CSS_HERE@ -->
</head>
<body>
<div id="cf-wrapper">
<div id="cf-error-details" class="p-0">
<header class="mx-auto pt-10 lg:pt-6 lg:px-8 w-240 lg:w-full mb-8">
<h1 class="inline-block sm:block sm:mb-2 font-light text-60 lg:text-4xl text-black-dark leading-tight mr-2">
<span class="inline-block"><%= title %></span>
<span class="code-label">Error code <%= error_code %></span>
</h1>
<% let more_info = params.more_information || {}; %>
<% if (!more_info.hidden) { %>
<div>
Visit <a href="<%= more_info.link || 'https://www.cloudflare.com/' %>" target="_blank" rel="noopener noreferrer"><%= more_info.text || 'cloudflare.com' %></a> for <%= more_info.for || "more information" %>.
</div>
<% } %>
<div class="<%= more_info.hidden ? '' : 'mt-3' %>"><%= params.time %></div>
</header>
<div class="my-8 bg-gradient-gray">
<div class="w-240 lg:w-full mx-auto">
<div class="clearfix md:px-8">
<% 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');
%>
<div id="cf-<%= item_id %>-status" class="<% if (params.error_source === item_id) { %>cf-error-source<% } %> relative w-1/3 md:w-full py-15 md:p-0 md:py-8 md:text-left md:border-solid md:border-0 md:border-b md:border-gray-400 overflow-hidden float-left md:float-none text-center">
<div class="relative mb-10 md:m-0">
<span class="cf-icon-<%= icon %> block md:hidden h-20 bg-center bg-no-repeat"></span>
<span class="cf-icon-<%= status %> w-12 h-12 absolute left-1/2 md:left-auto md:right-0 md:top-0 -ml-6 -bottom-4"></span>
</div>
<span class="md:block w-full truncate"><%= item.location || default_location %></span>
<%
let _name_style;
if ((item.name || default_name) === 'Cloudflare') {
_name_style = 'style="color: #2f7bbf;"'
} else{
_name_style = ''
}
%>
<h3 class="md:inline-block mt-3 md:mt-0 text-2xl text-gray-600 font-light leading-1.3" <%-_name_style %>><%= item.name || default_name %></h3>
<span class="leading-1.3 text-2xl" style="color: <%= text_color %>"><%= status_text %></span>
</div>
<% } %>
</div>
</div>
</div>
<div class="w-240 lg:w-full mx-auto mb-8 lg:px-8">
<div class="clearfix">
<div class="w-1/2 md:w-full float-left pr-6 md:pb-10 md:pr-0 leading-relaxed">
<h2 class="text-3xl font-normal leading-1.3 mb-4">What happened?</h2>
<%= (params.what_happened || 'There is an internal server error on Cloudflare\'s network.') %>
</div>
<div class="w-1/2 md:w-full float-left leading-relaxed">
<h2 class="text-3xl font-normal leading-1.3 mb-4">What can I do?</h2>
<%= (params.what_can_i_do || 'Please try again in a few minutes.') %>
</div>
</div>
</div>
<div class="cf-error-footer cf-wrapper w-240 lg:w-full py-10 sm:py-4 sm:px-8 mx-auto text-center sm:text-left border-solid border-0 border-t border-gray-300">
<p class="text-13">
<span class="cf-footer-item sm:block sm:mb-1">Ray ID: <strong class="font-semibold"><%= params.ray_id %></strong></span>
<span class="cf-footer-separator sm:hidden">&bull;</span>
<span id="cf-footer-item-ip" class="cf-footer-item hidden sm:block sm:mb-1">
Your IP:
<button type="button" id="cf-footer-ip-reveal" class="cf-footer-ip-reveal-btn">Click to reveal</button>
<span class="hidden" id="cf-footer-ip"><%= params.client_ip || '1.1.1.1' %></span>
<span class="cf-footer-separator sm:hidden">&bull;</span>
</span>
<% let perf_sec_by = params.perf_sec_by || {}; %>
<span class="cf-footer-item sm:block sm:mb-1"><span>Performance &amp; security by</span> <a rel="noopener noreferrer" href="<%= perf_sec_by.link || 'https://www.cloudflare.com/' %>" id="brand_link" target="_blank"><%= perf_sec_by.text || 'Cloudflare' %></a></span>
</p>
</div><!-- /.error-footer -->
</div>
</div>
<script>(function(){function d(){var b=a.getElementById("cf-footer-item-ip"),c=a.getElementById("cf-footer-ip-reveal");b&&"classList"in b&&(b.classList.remove("hidden"),c.addEventListener("click",function(){c.classList.add("hidden");a.getElementById("cf-footer-ip").classList.remove("hidden")}))}var a=document;document.addEventListener&&a.addEventListener("DOMContentLoaded",d)})();</script>
</body>
</html>

View File

@@ -1,114 +0,0 @@
<!DOCTYPE html>
<!--[if lt IE 7]> <html class="no-js ie6 oldie" lang="en-US"> <![endif]-->
<!--[if IE 7]> <html class="no-js ie7 oldie" lang="en-US"> <![endif]-->
<!--[if IE 8]> <html class="no-js ie8 oldie" lang="en-US"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js" lang="en-US"> <!--<![endif]-->
<head>
{% 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) %}
<title>{{ html_title }}</title>
<meta charset="UTF-8" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
<meta name="robots" content="noindex, nofollow" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
{% block header %}{% endblock %}
<!-- @INLINE_CSS_HERE@ -->
</head>
<body>
<div id="cf-wrapper">
<div id="cf-error-details" class="p-0">
<header class="mx-auto pt-10 lg:pt-6 lg:px-8 w-240 lg:w-full mb-8">
<h1 class="inline-block sm:block sm:mb-2 font-light text-60 lg:text-4xl text-black-dark leading-tight mr-2">
<span class="inline-block">{{ title }}</span>
<span class="code-label">Error code {{ error_code }}</span>
</h1>
{% set more_info = params.more_information or {} %}
{% if not more_info.hidden or false %}{# default: shown #}
<div>
Visit <a href="{{more_info.link or 'https://www.cloudflare.com/'}}" target="_blank" rel="noopener noreferrer">{{more_info.text or 'cloudflare.com'}}</a> for {{more_info.for or 'more information'}}.
</div>
{% endif %}
<div class="mt-3">{{ params.time }}</div>
</header>
<div class="my-8 bg-gradient-gray">
<div class="w-240 lg:w-full mx-auto">
<div class="clearfix md:px-8">
{% 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') %}
<div id="cf-{{item_id}}-status" class="{{'cf-error-source' if params.error_source == item_id else ''}} relative w-1/3 md:w-full py-15 md:p-0 md:py-8 md:text-left md:border-solid md:border-0 md:border-b md:border-gray-400 overflow-hidden float-left md:float-none text-center">
<div class="relative mb-10 md:m-0">
<span class="cf-icon-{{icon}} block md:hidden h-20 bg-center bg-no-repeat"></span>
<span class="cf-icon-{{status}} w-12 h-12 absolute left-1/2 md:left-auto md:right-0 md:top-0 -ml-6 -bottom-4"></span>
</div>
<span class="md:block w-full truncate">{{item.location or default_location}}</span>
{% set _name_style = 'style="color: #2f7bbf;"' if ((item.name or default_name) == 'Cloudflare') else '' %}
<h3 class="md:inline-block mt-3 md:mt-0 text-2xl text-gray-600 font-light leading-1.3" {{_name_style | safe}}>{{item.name or default_name}}</h3>
<span class="leading-1.3 text-2xl" style="color: {{text_color}}">{{status_text}}</span>
</div>
{% endfor %}
</div>
</div>
</div>
<div class="w-240 lg:w-full mx-auto mb-8 lg:px-8">
<div class="clearfix">
<div class="w-1/2 md:w-full float-left pr-6 md:pb-10 md:pr-0 leading-relaxed">
<h2 class="text-3xl font-normal leading-1.3 mb-4">What happened?</h2>
{{ (params.what_happened or '<p>There is an internal server error on Cloudflare\'s network.</p>') | safe }}
</div>
<div class="w-1/2 md:w-full float-left leading-relaxed">
<h2 class="text-3xl font-normal leading-1.3 mb-4">What can I do?</h2>
{{ (params.what_can_i_do or '<p>Please try again in a few minutes.</p>') | safe }}
</div>
</div>
</div>
<div class="cf-error-footer cf-wrapper w-240 lg:w-full py-10 sm:py-4 sm:px-8 mx-auto text-center sm:text-left border-solid border-0 border-t border-gray-300">
<p class="text-13">
<span class="cf-footer-item sm:block sm:mb-1">Ray ID: <strong class="font-semibold">{{ params.ray_id }}</strong></span>
<span class="cf-footer-separator sm:hidden">&bull;</span>
<span id="cf-footer-item-ip" class="cf-footer-item hidden sm:block sm:mb-1">
Your IP:
<button type="button" id="cf-footer-ip-reveal" class="cf-footer-ip-reveal-btn">Click to reveal</button>
<span class="hidden" id="cf-footer-ip">{{ params.client_ip or '1.1.1.1' }}</span>
<span class="cf-footer-separator sm:hidden">&bull;</span>
</span>
{% set perf_sec_by = params.perf_sec_by or {} %}
<span class="cf-footer-item sm:block sm:mb-1"><span>Performance &amp; security by</span> <a rel="noopener noreferrer" href="{{perf_sec_by.link or 'https://www.cloudflare.com/'}}" id="brand_link" target="_blank">{{perf_sec_by.text or 'Cloudflare'}}</a></span>
{% set creator_info = params.creator_info or {} %}
{% if not creator_info.get('hidden', True) %}{# default: hidden #}
<span class="cf-footer-separator sm:hidden">&bull;</span>
<span class="cf-footer-item sm:block sm:mb-1">Created with <a href="{{creator_info.link}}" target="_blank">{{creator_info.text}}</a></span>
{% endif %}
</p>
</div><!-- /.error-footer -->
</div>
</div>
<script>(function(){function d(){var b=a.getElementById("cf-footer-item-ip"),c=a.getElementById("cf-footer-ip-reveal");b&&"classList"in b&&(b.classList.remove("hidden"),c.addEventListener("click",function(){c.classList.add("hidden");a.getElementById("cf-footer-ip").classList.remove("hidden")}))}var a=document;document.addEventListener&&a.addEventListener("DOMContentLoaded",d)})();</script>
</body>
</html>

View File

@@ -1,73 +0,0 @@
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('<?xml version="1.0" encoding="UTF-8"?>', '')
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('<!-- @INLINE_CSS_HERE@ -->',
f'<style>{css_data}</style>')
note = 'Note: This file is generated with scripts/inline_resources.py. Please do not edit 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/web/src/template.ejs'),
)