Static generated error pages.
This commit is contained in:
parent
c52c8c00a5
commit
cad2d4c1c5
24 changed files with 242 additions and 235 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -9,3 +9,6 @@
|
|||
/vendor
|
||||
/node_modules
|
||||
/hanyuu.cfg
|
||||
/public/assets
|
||||
/assets/current.json
|
||||
/public/error-*.html
|
||||
|
|
39
assets/errors.css/main.css
Normal file
39
assets/errors.css/main.css
Normal file
|
@ -0,0 +1,39 @@
|
|||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #111;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
body {
|
||||
background-color: #ddd;
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
.http-err {
|
||||
padding: 40px 60px;
|
||||
}
|
||||
.http-err-title {
|
||||
font-size: 3em;
|
||||
line-height: 1.5em;
|
||||
font-weight: 700;
|
||||
}
|
||||
.http-err-paragraph {
|
||||
font-size: 1.2em;
|
||||
line-height: 1.5em;
|
||||
}
|
|
@ -1,46 +1,3 @@
|
|||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #111;
|
||||
color: #fff;
|
||||
font: 12px/20px Verdana, Geneva, Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
body {
|
||||
background-color: #ddd;
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
[hidden],
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.http-err {
|
||||
padding: 40px 60px;
|
||||
}
|
||||
.http-err-title {
|
||||
font-size: 3em;
|
||||
line-height: 1.5em;
|
||||
font-weight: 700;
|
||||
}
|
||||
.http-err-paragraph {
|
||||
font-size: 1.2em;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
.auth {
|
||||
margin: 0 auto;
|
||||
padding: 10px;
|
31
assets/hanyuu.css/main.css
Normal file
31
assets/hanyuu.css/main.css
Normal file
|
@ -0,0 +1,31 @@
|
|||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #111;
|
||||
color: #fff;
|
||||
font: 12px/20px Verdana, Geneva, Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
body {
|
||||
background-color: #ddd;
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
[hidden],
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@include auth.css;
|
1
assets/hanyuu.js/main.js
Normal file
1
assets/hanyuu.js/main.js
Normal file
|
@ -0,0 +1 @@
|
|||
/* beans */
|
39
build.js
Normal file
39
build.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
const assproc = require('@railcomm/assproc');
|
||||
const { join: pathJoin } = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
(async () => {
|
||||
const isDebug = fs.existsSync(pathJoin(__dirname, '.debug'));
|
||||
|
||||
const env = {
|
||||
root: __dirname,
|
||||
source: pathJoin(__dirname, 'assets'),
|
||||
public: pathJoin(__dirname, 'public'),
|
||||
debug: isDebug,
|
||||
swc: {
|
||||
es: 'es2021',
|
||||
},
|
||||
};
|
||||
|
||||
const tasks = {
|
||||
js: [
|
||||
{ source: 'hanyuu.js', target: '/assets', name: 'hanyuu.{hash}.js', },
|
||||
],
|
||||
css: [
|
||||
{ source: 'errors.css', target: '/', name: 'errors.css', },
|
||||
{ source: 'hanyuu.css', target: '/assets', name: 'hanyuu.{hash}.css', },
|
||||
],
|
||||
twig: [
|
||||
{ source: 'errors/400', target: '/', name: 'error-400.html', },
|
||||
{ source: 'errors/401', target: '/', name: 'error-401.html', },
|
||||
{ source: 'errors/403', target: '/', name: 'error-403.html', },
|
||||
{ source: 'errors/404', target: '/', name: 'error-404.html', },
|
||||
{ source: 'errors/500', target: '/', name: 'error-500.html', },
|
||||
{ source: 'errors/503', target: '/', name: 'error-503.html', },
|
||||
],
|
||||
};
|
||||
|
||||
const files = await assproc.process(env, tasks);
|
||||
|
||||
fs.writeFileSync(pathJoin(__dirname, 'assets/current.json'), JSON.stringify(files));
|
||||
})();
|
|
@ -10,6 +10,7 @@ define('HAU_STARTUP', microtime(true));
|
|||
define('HAU_ROOT', __DIR__);
|
||||
define('HAU_CLI', PHP_SAPI === 'cli');
|
||||
define('HAU_DEBUG', is_file(HAU_ROOT . '/.debug'));
|
||||
define('HAU_DIR_ASSETS', HAU_ROOT . '/assets');
|
||||
define('HAU_DIR_PUBLIC', HAU_ROOT . '/public');
|
||||
define('HAU_DIR_SOURCE', HAU_ROOT . '/src');
|
||||
define('HAU_DIR_MIGRATIONS', HAU_ROOT . '/database');
|
||||
|
|
35
public/errors.css
Normal file
35
public/errors.css
Normal file
|
@ -0,0 +1,35 @@
|
|||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
}
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
body {
|
||||
background-color: #111;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;
|
||||
}
|
||||
@media (prefers-color-scheme: light) {
|
||||
body {
|
||||
background-color: #ddd;
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
.http-err {
|
||||
padding: 40px 60px;
|
||||
}
|
||||
.http-err-title {
|
||||
font-size: 3em;
|
||||
line-height: 1.5em;
|
||||
font-weight: 700;
|
||||
}
|
||||
.http-err-paragraph {
|
||||
font-size: 1.2em;
|
||||
line-height: 1.5em;
|
||||
}
|
23
src/GitInfo.php
Normal file
23
src/GitInfo.php
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
namespace Hanyuu;
|
||||
|
||||
final class GitInfo {
|
||||
private const FORMAT_HASH_SHORT = '%h';
|
||||
private const FORMAT_HASH_LONG = '%H';
|
||||
|
||||
public static function log(string $format, string $args = ''): string {
|
||||
return trim(shell_exec(sprintf('git log --pretty="%s" %s -n1 HEAD', $format, $args)));
|
||||
}
|
||||
|
||||
public static function hash(bool $long = false): string {
|
||||
return self::log($long ? self::FORMAT_HASH_LONG : self::FORMAT_HASH_SHORT);
|
||||
}
|
||||
|
||||
public static function branch(): string {
|
||||
return trim(`git rev-parse --abbrev-ref HEAD`);
|
||||
}
|
||||
|
||||
public static function tag(): string {
|
||||
return trim(`git describe --abbrev=0 --tags`);
|
||||
}
|
||||
}
|
|
@ -16,7 +16,6 @@ class HanyuuContext {
|
|||
public function __construct(IConfig $config, IDbConnection $dbConn) {
|
||||
$this->config = $config;
|
||||
$this->dbConn = $dbConn;
|
||||
|
||||
$this->siteInfo = new SiteInfo($config->scopeTo('site'));
|
||||
}
|
||||
|
||||
|
@ -37,6 +36,10 @@ class HanyuuContext {
|
|||
return new FsDbMigrationRepo(HAU_DIR_MIGRATIONS);
|
||||
}
|
||||
|
||||
public function getWebAssetInfo(): ?object {
|
||||
return json_decode(file_get_contents(HAU_DIR_ASSETS . '/current.json'));
|
||||
}
|
||||
|
||||
public function getTemplating(): SasaeEnvironment {
|
||||
if($this->templating === null) {
|
||||
$isDebug = Environment::isDebug();
|
||||
|
@ -46,10 +49,10 @@ class HanyuuContext {
|
|||
cache: null,//$isDebug ? null : ['Hanyuu', GitInfo::hash(true)],
|
||||
debug: $isDebug,
|
||||
);
|
||||
$this->templating->addExtension(new HanyuuSasaeExtension($this));
|
||||
|
||||
$this->templating->addGlobal('globals', [
|
||||
'siteInfo' => $this->siteInfo,
|
||||
//'assetsInfo' => AssetsInfo::fromCurrent(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
30
src/HanyuuSasaeExtension.php
Normal file
30
src/HanyuuSasaeExtension.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
namespace Hanyuu;
|
||||
|
||||
use Twig\Extension\AbstractExtension;
|
||||
use Twig\TwigFunction;
|
||||
|
||||
final class HanyuuSasaeExtension extends AbstractExtension {
|
||||
private HanyuuContext $ctx;
|
||||
private ?object $assets;
|
||||
|
||||
public function __construct(HanyuuContext $ctx) {
|
||||
$this->ctx = $ctx;
|
||||
$this->assets = $ctx->getWebAssetInfo();
|
||||
}
|
||||
|
||||
public function getFunctions() {
|
||||
return [
|
||||
new TwigFunction('asset', $this->getAssetPath(...)),
|
||||
new TwigFunction('git_commit_hash', GitInfo::hash(...)),
|
||||
new TwigFunction('git_tag', GitInfo::tag(...)),
|
||||
new TwigFunction('git_branch', GitInfo::branch(...)),
|
||||
new TwigFunction('startup_time', fn(float $time = HAU_STARTUP) => microtime(true) - $time),
|
||||
new TwigFunction('sql_query_count', $this->ctx->getDbQueryCount(...)),
|
||||
];
|
||||
}
|
||||
|
||||
public function getAssetPath(string $name): string {
|
||||
return $this->assets?->{$name} ?? '';
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@ class RoutingContext {
|
|||
|
||||
public function __construct(SasaeEnvironment $templating) {
|
||||
$this->templating = $templating;
|
||||
$this->router = new HttpRouter(errorHandler: new RoutingErrorHandler($templating));
|
||||
$this->router = new HttpRouter(errorHandler: new RoutingErrorHandler);
|
||||
$this->router->use('/', fn($resp) => $resp->setPoweredBy('Hanyuu'));
|
||||
}
|
||||
|
||||
|
|
|
@ -3,24 +3,14 @@ namespace Hanyuu;
|
|||
|
||||
use Index\Http\{HttpResponseBuilder,HttpRequest};
|
||||
use Index\Http\ErrorHandling\HtmlErrorHandler;
|
||||
use Sasae\SasaeEnvironment;
|
||||
|
||||
class RoutingErrorHandler extends HtmlErrorHandler {
|
||||
public function __construct(
|
||||
private SasaeEnvironment $templating
|
||||
) {}
|
||||
|
||||
#[\Override]
|
||||
public function handle(HttpResponseBuilder $response, HttpRequest $request, int $code, string $message): void {
|
||||
if($code === 500 || $code === 503) {
|
||||
$path = HAU_DIR_PUBLIC . sprintf('/error-%03d.html', $code);
|
||||
if(is_file($path)) {
|
||||
$response->setTypeHTML();
|
||||
$response->setContent(file_get_contents(sprintf('%s/errors/%s.html', HAU_DIR_TEMPLATES, $code)));
|
||||
return;
|
||||
}
|
||||
|
||||
if($code == 401 || $code === 403 || $code === 400) {
|
||||
$response->setTypeHTML();
|
||||
$response->setContent($this->templating->render(sprintf('errors/%s', $code)));
|
||||
$response->setContent(file_get_contents($path));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
{% extends 'auth/master.twig' %}
|
||||
|
||||
{% block content %}
|
||||
<form class="auth-login" method="post" action="/login/tfa">
|
||||
<div class="auth-avatar">
|
||||
<div class="auth-avatar-image">
|
||||
<img src="//flashii.net/assets/avatar/{{ user_info.id }}?res=200" alt="">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="auth-fields">
|
||||
<label class="auth-input">
|
||||
<div class="auth-input-title">
|
||||
Username
|
||||
</div>
|
||||
<div class="auth-input-value">
|
||||
<input type="text" value="{{ user_info.name }}" readonly>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="auth-buttons">
|
||||
{% if 'totp' in auth_method_names %}
|
||||
<a href="/login/tfa/totp" class="auth-button auth-button-primary">Authenticator app (TOTP)</a>
|
||||
{% endif %}
|
||||
{% if 'u2f' in auth_method_names %}
|
||||
<a href="/login/tfa/u2f" class="auth-button auth-button-primary">Security key (U2F)</a>
|
||||
{% endif %}
|
||||
{% if 'backup' in auth_method_names %}
|
||||
<a href="/login/tfa/backup" class="auth-button auth-button-primary">Backup code</a>
|
||||
{% endif %}
|
||||
<a href="/login" class="auth-button">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -1,36 +0,0 @@
|
|||
{% extends 'auth/master.twig' %}
|
||||
|
||||
{% block content %}
|
||||
<form class="auth-login" method="post" action="/login/tfa/totp">
|
||||
<div class="auth-avatar">
|
||||
<div class="auth-avatar-image">
|
||||
<img src="//flashii.net/assets/avatar/{{ user_info.id }}?res=200" alt="">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="auth-fields">
|
||||
<label class="auth-input">
|
||||
<div class="auth-input-title">
|
||||
Username
|
||||
</div>
|
||||
<div class="auth-input-value">
|
||||
<input type="text" value="{{ user_info.name }}" readonly>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="auth-input">
|
||||
<div class="auth-input-title">
|
||||
Authenticator code
|
||||
</div>
|
||||
<div class="auth-input-value">
|
||||
<input type="text" name="code" value="" maxlength="6" inputmode="numeric" placeholder="------">
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="auth-buttons">
|
||||
<input type="submit" value="Next" class="auth-button auth-button-primary">
|
||||
<a href="/login/tfa" class="auth-button">Back</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -1,51 +0,0 @@
|
|||
{% extends 'auth/master.twig' %}
|
||||
|
||||
{% set register_suffix = '' %}
|
||||
{% set forgot_suffix = '' %}
|
||||
|
||||
{% if user_name is not empty %}
|
||||
{% set suffix = '?username=' ~ user_name %}
|
||||
|
||||
{% if error_name == 'user_not_found' %}
|
||||
{% set register_suffix = suffix %}
|
||||
{% else %}
|
||||
{% set forgot_suffix = suffix %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% block content %}
|
||||
<form class="auth-login" method="post" action="/login">
|
||||
<div class="auth-avatar">
|
||||
<div class="auth-avatar-image">
|
||||
<img src="//flashii.net/assets/avatar/0?res=200" alt="">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="auth-fields">
|
||||
<label class="auth-input">
|
||||
<div class="auth-input-title">
|
||||
Username
|
||||
</div>
|
||||
<div class="auth-input-value">
|
||||
<input type="text" name="username" value="{{ user_name }}" placeholder="example123">
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="auth-input">
|
||||
<div class="auth-input-title">
|
||||
Password
|
||||
</div>
|
||||
<div class="auth-input-value">
|
||||
<input type="password" name="password" value="">
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="auth-buttons">
|
||||
<input type="submit" value="Next" class="auth-button auth-button-primary">
|
||||
<a href="/forgot-username{{ forgot_suffix }}" class="auth-button">Forgot username?</a>
|
||||
<a href="/forgot-password{{ forgot_suffix }}" class="auth-button">Forgot password?</a>
|
||||
<a href="/register{{ register_suffix }}" class="auth-button">Create account</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -1,10 +0,0 @@
|
|||
{% extends 'master.twig' %}
|
||||
|
||||
{% block body %}
|
||||
<div class="auth">
|
||||
<div class="auth-header">
|
||||
{{ globals.siteInfo.name }}
|
||||
</div>
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,18 +0,0 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Error 500</title>
|
||||
<style type="text/css">
|
||||
body {
|
||||
font: 12px/20px Verdana, Geneva, Arial, Helvetica, sans-serif;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<center>
|
||||
<h1>Error 500</h1>
|
||||
<p>Something went horrendously wrong! Please <a href="//fii.moe/bugs">report</a> this if the error persists.</p>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
4
templates/errors/500.twig
Normal file
4
templates/errors/500.twig
Normal file
|
@ -0,0 +1,4 @@
|
|||
{% extends 'errors/master.twig' %}
|
||||
|
||||
{% set http_error_title = 'Error 500' %}
|
||||
{% set http_error_desc = 'Something went horrendously wrong! Please <a href="//fii.moe/bugs">report</a> this if the error persists.' %}
|
|
@ -1,18 +0,0 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Error 503</title>
|
||||
<style type="text/css">
|
||||
body {
|
||||
font: 12px/20px Verdana, Geneva, Arial, Helvetica, sans-serif;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<center>
|
||||
<h1>Under Construction</h1>
|
||||
<img src="//static.flash.moe/images/me-tan-2.png" alt="">
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
4
templates/errors/503.twig
Normal file
4
templates/errors/503.twig
Normal file
|
@ -0,0 +1,4 @@
|
|||
{% extends 'errors/master.twig' %}
|
||||
|
||||
{% set http_error_title = 'Under Construction' %}
|
||||
{% set http_error_desc = '<img src="//static.flash.moe/images/me-tan-2.png" alt="">' %}
|
|
@ -1,8 +1,15 @@
|
|||
{% extends 'master.twig' %}
|
||||
|
||||
{% block body %}
|
||||
<div class="http-err">
|
||||
<h1 class="http-err-title">{{ http_error_title|default('Unknown Error') }}</h1>
|
||||
<p class="http-err-paragraph">{{ http_error_title|default('No additional information is available.') }}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<title>{{ http_error_title }} :: {{ globals.siteInfo.name }}</title>
|
||||
<link href="{{ asset('errors.css') }}" type="text/css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div class="http-err">
|
||||
<h1 class="http-err-title">{{ http_error_title }}</h1>
|
||||
<p class="http-err-paragraph">{{ http_error_desc|raw }}</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
8
tools/render-tpl
Executable file
8
tools/render-tpl
Executable file
|
@ -0,0 +1,8 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
require_once __DIR__ . '/../hanyuu.php';
|
||||
|
||||
$templating = $hau->getTemplating();
|
||||
$path = implode(' ', array_slice($argv, 1));
|
||||
|
||||
echo $templating->render($path);
|
Loading…
Reference in a new issue