mirror of
https://github.com/donlon/cloudflare-error-page.git
synced 2025-12-23 08:49:25 +00:00
Compare commits
3 Commits
javascript
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8be87415fe | ||
|
|
fb52f6d523 | ||
|
|
5a633b5958 |
15
.gitignore
vendored
15
.gitignore
vendored
@@ -1,14 +1,13 @@
|
|||||||
.vscode/
|
.vscode/
|
||||||
|
.DS_Store
|
||||||
build/
|
|
||||||
*.egg-info/
|
|
||||||
__pycache__/
|
|
||||||
dist/
|
|
||||||
|
|
||||||
node_modules/
|
|
||||||
|
|
||||||
.venv/
|
.venv/
|
||||||
venv/
|
venv/
|
||||||
ttt/
|
|
||||||
|
node_modules/
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
*.egg-info/
|
||||||
|
__pycache__/
|
||||||
|
|
||||||
instance/
|
instance/
|
||||||
|
|||||||
48
README.md
48
README.md
@@ -66,12 +66,52 @@ 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).
|
A demo server using Flask is also available in [flask_demo.py](examples/flask_demo.py).
|
||||||
|
|
||||||
### Node.js/NPM
|
### JavaScript/NodeJS
|
||||||
|
|
||||||
A Node.js package is available in [nodejs](nodejs) folder. However currently it supports only Node.js but not web browsers,
|
Install the `cloudflare-error-page` package using npm:
|
||||||
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.)
|
``` Bash
|
||||||
|
npm install cloudflare-error-page
|
||||||
|
```
|
||||||
|
|
||||||
|
The following example demonstrates how to create a simple Express server and return the error page to visitors.
|
||||||
|
|
||||||
|
``` JavaScript
|
||||||
|
import express from 'express';
|
||||||
|
import { render as render_cf_error_page } from 'cloudflare-error-page';
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const port = 3000;
|
||||||
|
|
||||||
|
// Define a route for GET requests to the root URL
|
||||||
|
app.get('/', (req, res) => {
|
||||||
|
res.status(500).send(render_cf_error_page({
|
||||||
|
title: "Internal server error",
|
||||||
|
// 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',
|
||||||
|
},
|
||||||
|
error_source: "cloudflare",
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start the server and listen on the specified port
|
||||||
|
app.listen(port, () => {
|
||||||
|
console.log(`Example app listening at http://localhost:${port}`);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
(Thanks [@junduck](https://github.com/junduck) for creating the original NodeJS version.)
|
||||||
|
|
||||||
### PHP
|
### PHP
|
||||||
|
|
||||||
|
|||||||
@@ -170,8 +170,10 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-height: 700px !important;
|
min-height: 700px !important;
|
||||||
|
max-height: 1200px !important;
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
font-size: 0.8em !important;
|
font-size: 0.8em !important;
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.save-as-dialog__buttons {
|
.save-as-dialog__buttons {
|
||||||
@@ -545,8 +547,7 @@
|
|||||||
Save
|
Save
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<textarea id="saveAsDialogCode" class="form-control save-as-dialog__code" spellcheck="false"
|
<pre id="saveAsDialogCode" class="border px-2 py-1 save-as-dialog__code"></pre>
|
||||||
readonly></textarea>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"@types/ejs": "^3.1.5",
|
"@types/ejs": "^3.1.5",
|
||||||
"@types/html-minifier-terser": "^7.0.2",
|
"@types/html-minifier-terser": "^7.0.2",
|
||||||
"@types/node": "^24.10.2",
|
"@types/node": "^24.10.2",
|
||||||
|
"@types/prismjs": "^1.26.5",
|
||||||
"html-minifier-terser": "^7.2.0",
|
"html-minifier-terser": "^7.2.0",
|
||||||
"prettier": "3.7.4",
|
"prettier": "3.7.4",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
@@ -22,6 +23,8 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bootstrap": "^5.3.8",
|
"bootstrap": "^5.3.8",
|
||||||
"ejs": "^3.1.10"
|
"cloudflare-error-page": "../../javascript",
|
||||||
|
"ejs": "^3.1.10",
|
||||||
|
"prismjs": "^1.30.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,14 +6,17 @@ import pythonTemplate from './python.ejs?raw';
|
|||||||
|
|
||||||
interface CodeGen {
|
interface CodeGen {
|
||||||
name: string;
|
name: string;
|
||||||
|
language: string;
|
||||||
generate(params: any): string;
|
generate(params: any): string;
|
||||||
}
|
}
|
||||||
|
|
||||||
class EjsCodeGen implements CodeGen {
|
class EjsCodeGen implements CodeGen {
|
||||||
name: string;
|
name: string;
|
||||||
|
language: string;
|
||||||
private template: ejs.TemplateFunction;
|
private template: ejs.TemplateFunction;
|
||||||
constructor(name: string, templateContent: any) {
|
constructor(name: string, language: string, templateContent: any) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
this.language = language;
|
||||||
this.template = ejs.compile(templateContent);
|
this.template = ejs.compile(templateContent);
|
||||||
}
|
}
|
||||||
generate(params: any): string {
|
generate(params: any): string {
|
||||||
@@ -21,6 +24,6 @@ class EjsCodeGen implements CodeGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const jsCodeGen = new EjsCodeGen('NodeJS Example', jsTemplate);
|
export const jsCodeGen = new EjsCodeGen('NodeJS Example', 'javascript', jsTemplate);
|
||||||
export const jsonCodeGen = new EjsCodeGen('JSON', jsonTemplate);
|
export const jsonCodeGen = new EjsCodeGen('JSON', 'json', jsonTemplate);
|
||||||
export const pythonCodeGen = new EjsCodeGen('Python Example', pythonTemplate);
|
export const pythonCodeGen = new EjsCodeGen('Python Example', 'python', pythonTemplate);
|
||||||
|
|||||||
@@ -5,17 +5,19 @@
|
|||||||
- inputs call render() on change
|
- inputs call render() on change
|
||||||
- "Open in new tab" opens the rendered HTML in a new window using a blob URL
|
- "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';
|
|
||||||
|
|
||||||
import 'bootstrap/js/src/modal.js';
|
import 'bootstrap/js/src/modal.js';
|
||||||
import Popover from 'bootstrap/js/src/popover.js';
|
import Popover from 'bootstrap/js/src/popover.js';
|
||||||
|
import Prism from 'prismjs';
|
||||||
|
import 'prismjs/components/prism-json.js';
|
||||||
|
import 'prismjs/components/prism-python.js';
|
||||||
|
import { render as render_cf_error_page } from 'cloudflare-error-page';
|
||||||
|
|
||||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||||
|
import 'prismjs/themes/prism.css';
|
||||||
|
|
||||||
import { jsCodeGen, jsonCodeGen, pythonCodeGen } from './codegen';
|
import { jsCodeGen, jsonCodeGen, pythonCodeGen } from './codegen';
|
||||||
|
|
||||||
let template = ejs.compile(templateContent);
|
|
||||||
|
|
||||||
// can be changed if specified by '?from=<name>'
|
// can be changed if specified by '?from=<name>'
|
||||||
let initialConfig = {
|
let initialConfig = {
|
||||||
title: 'Internal server error',
|
title: 'Internal server error',
|
||||||
@@ -255,24 +257,9 @@ function readConfig() {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
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) {
|
function renderEjs(params) {
|
||||||
return template({
|
return render_cf_error_page(params);
|
||||||
params: params,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Basic render: build HTML string from config and put into iframe.srcdoc */
|
/* Basic render: build HTML string from config and put into iframe.srcdoc */
|
||||||
@@ -280,9 +267,8 @@ function render() {
|
|||||||
const cfg = readConfig();
|
const cfg = readConfig();
|
||||||
window.lastCfg = cfg;
|
window.lastCfg = cfg;
|
||||||
|
|
||||||
cfg.time = formatUtcTimestamp();
|
// TODO: input box for Ray ID / Client IP?
|
||||||
cfg.ray_id = '0123456789abcdef';
|
cfg.ray_id = '0123456789abcdef';
|
||||||
cfg.client_ip = '1.1.1.1';
|
|
||||||
if (Number.isNaN(Number(cfg.error_code))) {
|
if (Number.isNaN(Number(cfg.error_code))) {
|
||||||
cfg.html_title = cfg.title || 'Internal server error';
|
cfg.html_title = cfg.title || 'Internal server error';
|
||||||
}
|
}
|
||||||
@@ -305,8 +291,8 @@ function render() {
|
|||||||
let lastRenderedHtml = '';
|
let lastRenderedHtml = '';
|
||||||
function openInNewTab() {
|
function openInNewTab() {
|
||||||
if (!lastRenderedHtml) render();
|
if (!lastRenderedHtml) render();
|
||||||
const wnd = window.open()
|
const wnd = window.open();
|
||||||
wnd.document.documentElement.innerHTML = lastRenderedHtml
|
wnd.document.documentElement.innerHTML = lastRenderedHtml;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createShareableLink() {
|
function createShareableLink() {
|
||||||
@@ -445,16 +431,20 @@ function updateSaveAsDialog(e) {
|
|||||||
}
|
}
|
||||||
const params = { ...lastCfg };
|
const params = { ...lastCfg };
|
||||||
delete params.time;
|
delete params.time;
|
||||||
|
let language;
|
||||||
if (codegen) {
|
if (codegen) {
|
||||||
saveAsContent = codegen.generate(params);
|
saveAsContent = codegen.generate(params);
|
||||||
|
language = codegen.language;
|
||||||
} else if (saveAsType == 'static') {
|
} else if (saveAsType == 'static') {
|
||||||
render() // rerender the page
|
render(); // rerender the page
|
||||||
saveAsContent = lastRenderedHtml;
|
saveAsContent = lastRenderedHtml;
|
||||||
|
language = 'html';
|
||||||
} else {
|
} else {
|
||||||
throw new Error('unexpected saveAsType=' + saveAsType)
|
throw new Error('unexpected saveAsType=' + saveAsType);
|
||||||
}
|
}
|
||||||
$('saveAsDialogCode').value = saveAsContent;
|
const html = Prism.highlight(saveAsContent, Prism.languages[language], language);
|
||||||
$('saveAsDialogCode').scrollTop = 0;
|
|
||||||
|
$('saveAsDialogCode').innerHTML = html;
|
||||||
|
|
||||||
document.querySelectorAll('#saveAsDialogTypes button').forEach((element) => {
|
document.querySelectorAll('#saveAsDialogTypes button').forEach((element) => {
|
||||||
const isCurrent = element.dataset.type == saveAsType;
|
const isCurrent = element.dataset.type == saveAsType;
|
||||||
@@ -475,10 +465,7 @@ document.querySelectorAll('#saveAsDialogTypes button').forEach((element) => {
|
|||||||
|
|
||||||
const saveAsDialogCopyPopover = new Popover($('saveAsDialogCopyBtn'));
|
const saveAsDialogCopyPopover = new Popover($('saveAsDialogCopyBtn'));
|
||||||
$('saveAsDialogCopyBtn').addEventListener('click', (e) => {
|
$('saveAsDialogCopyBtn').addEventListener('click', (e) => {
|
||||||
const field = $('saveAsDialogCode');
|
navigator.clipboard.writeText(saveAsContent).then(() => {
|
||||||
field.select();
|
|
||||||
// field.setSelectionRange(0, field.value.length);
|
|
||||||
navigator.clipboard.writeText(field.value).then(() => {
|
|
||||||
saveAsDialogCopyPopover.show();
|
saveAsDialogCopyPopover.show();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
saveAsDialogCopyPopover.hide();
|
saveAsDialogCopyPopover.hide();
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -13,6 +13,9 @@ export default defineConfig(({ mode }) => {
|
|||||||
minify: true,
|
minify: true,
|
||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
},
|
},
|
||||||
|
resolve: {
|
||||||
|
dedupe: ['ejs'],
|
||||||
|
},
|
||||||
server: {
|
server: {
|
||||||
port: 3000,
|
port: 3000,
|
||||||
proxy: {
|
proxy: {
|
||||||
|
|||||||
@@ -310,6 +310,11 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
undici-types "~7.16.0"
|
undici-types "~7.16.0"
|
||||||
|
|
||||||
|
"@types/prismjs@^1.26.5":
|
||||||
|
version "1.26.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/prismjs/-/prismjs-1.26.5.tgz#72499abbb4c4ec9982446509d2f14fb8483869d6"
|
||||||
|
integrity sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==
|
||||||
|
|
||||||
acorn@^8.15.0:
|
acorn@^8.15.0:
|
||||||
version "8.15.0"
|
version "8.15.0"
|
||||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816"
|
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816"
|
||||||
@@ -392,6 +397,11 @@ clean-css@~5.3.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
source-map "~0.6.0"
|
source-map "~0.6.0"
|
||||||
|
|
||||||
|
cloudflare-error-page@../../javascript:
|
||||||
|
version "0.1.0"
|
||||||
|
dependencies:
|
||||||
|
ejs "^3.1.10"
|
||||||
|
|
||||||
commander@^10.0.0:
|
commander@^10.0.0:
|
||||||
version "10.0.1"
|
version "10.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06"
|
||||||
@@ -613,6 +623,11 @@ prettier@3.7.4:
|
|||||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.7.4.tgz#d2f8335d4b1cec47e1c8098645411b0c9dff9c0f"
|
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.7.4.tgz#d2f8335d4b1cec47e1c8098645411b0c9dff9c0f"
|
||||||
integrity sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==
|
integrity sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==
|
||||||
|
|
||||||
|
prismjs@^1.30.0:
|
||||||
|
version "1.30.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.30.0.tgz#d9709969d9d4e16403f6f348c63553b19f0975a9"
|
||||||
|
integrity sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==
|
||||||
|
|
||||||
readdirp@~3.6.0:
|
readdirp@~3.6.0:
|
||||||
version "3.6.0"
|
version "3.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
|
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ npm install cloudflare-error-page
|
|||||||
Or install from GitHub:
|
Or install from GitHub:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install git+https://github.com/donlon/cloudflare-error-page.git#main:nodejs
|
npm install git+https://github.com/donlon/cloudflare-error-page.git#javascriptnodejs
|
||||||
```
|
```
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
@@ -34,7 +34,7 @@ fs.writeFileSync('error.html', errorPage);
|
|||||||
|
|
||||||
## API Reference
|
## API Reference
|
||||||
|
|
||||||
### `render(params: ErrorPageParams, allowHtml?: boolean): string`
|
### `render(params: ErrorPageParams, allowHtml?: boolean, moreArgs?: { [name: string]: any; }): string`
|
||||||
|
|
||||||
Generates an HTML error page based on the provided parameters.
|
Generates an HTML error page based on the provided parameters.
|
||||||
|
|
||||||
@@ -42,6 +42,7 @@ Generates an HTML error page based on the provided parameters.
|
|||||||
|
|
||||||
- `params`: An object containing error page configuration
|
- `params`: An object containing error page configuration
|
||||||
- `allowHtml` (optional): Whether to allow HTML in `what_happened` and `what_can_i_do` fields. Default: `true`
|
- `allowHtml` (optional): Whether to allow HTML in `what_happened` and `what_can_i_do` fields. Default: `true`
|
||||||
|
- `moreArgs` (optional): More arguments passed to the ejs template
|
||||||
|
|
||||||
#### ErrorPageParams Interface
|
#### ErrorPageParams Interface
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ function genHexString(digits: number): string {
|
|||||||
* Render a customized Cloudflare error page
|
* Render a customized Cloudflare error page
|
||||||
* @param params - The parameters for the 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)
|
* @param allowHtml - Whether to allow HTML in what_happened and what_can_i_do fields (default: true)
|
||||||
* @param moreArgs - More arguments passed to ejs template
|
* @param moreArgs - More arguments passed to the ejs template
|
||||||
* @returns The rendered HTML string
|
* @returns The rendered HTML string
|
||||||
*/
|
*/
|
||||||
export function render(
|
export function render(
|
||||||
|
|||||||
Reference in New Issue
Block a user