Static generated error pages.

This commit is contained in:
flash 2024-06-11 22:50:55 +00:00
parent c52c8c00a5
commit cad2d4c1c5
24 changed files with 242 additions and 235 deletions

3
.gitignore vendored
View file

@ -9,3 +9,6 @@
/vendor /vendor
/node_modules /node_modules
/hanyuu.cfg /hanyuu.cfg
/public/assets
/assets/current.json
/public/error-*.html

View 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;
}

View file

@ -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 { .auth {
margin: 0 auto; margin: 0 auto;
padding: 10px; padding: 10px;

View 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
View file

@ -0,0 +1 @@
/* beans */

39
build.js Normal file
View 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));
})();

View file

@ -10,6 +10,7 @@ define('HAU_STARTUP', microtime(true));
define('HAU_ROOT', __DIR__); define('HAU_ROOT', __DIR__);
define('HAU_CLI', PHP_SAPI === 'cli'); define('HAU_CLI', PHP_SAPI === 'cli');
define('HAU_DEBUG', is_file(HAU_ROOT . '/.debug')); 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_PUBLIC', HAU_ROOT . '/public');
define('HAU_DIR_SOURCE', HAU_ROOT . '/src'); define('HAU_DIR_SOURCE', HAU_ROOT . '/src');
define('HAU_DIR_MIGRATIONS', HAU_ROOT . '/database'); define('HAU_DIR_MIGRATIONS', HAU_ROOT . '/database');

35
public/errors.css Normal file
View 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
View 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`);
}
}

View file

@ -16,7 +16,6 @@ class HanyuuContext {
public function __construct(IConfig $config, IDbConnection $dbConn) { public function __construct(IConfig $config, IDbConnection $dbConn) {
$this->config = $config; $this->config = $config;
$this->dbConn = $dbConn; $this->dbConn = $dbConn;
$this->siteInfo = new SiteInfo($config->scopeTo('site')); $this->siteInfo = new SiteInfo($config->scopeTo('site'));
} }
@ -37,6 +36,10 @@ class HanyuuContext {
return new FsDbMigrationRepo(HAU_DIR_MIGRATIONS); 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 { public function getTemplating(): SasaeEnvironment {
if($this->templating === null) { if($this->templating === null) {
$isDebug = Environment::isDebug(); $isDebug = Environment::isDebug();
@ -46,10 +49,10 @@ class HanyuuContext {
cache: null,//$isDebug ? null : ['Hanyuu', GitInfo::hash(true)], cache: null,//$isDebug ? null : ['Hanyuu', GitInfo::hash(true)],
debug: $isDebug, debug: $isDebug,
); );
$this->templating->addExtension(new HanyuuSasaeExtension($this));
$this->templating->addGlobal('globals', [ $this->templating->addGlobal('globals', [
'siteInfo' => $this->siteInfo, 'siteInfo' => $this->siteInfo,
//'assetsInfo' => AssetsInfo::fromCurrent(),
]); ]);
} }

View 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} ?? '';
}
}

View file

@ -10,7 +10,7 @@ class RoutingContext {
public function __construct(SasaeEnvironment $templating) { public function __construct(SasaeEnvironment $templating) {
$this->templating = $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')); $this->router->use('/', fn($resp) => $resp->setPoweredBy('Hanyuu'));
} }

View file

@ -3,24 +3,14 @@ namespace Hanyuu;
use Index\Http\{HttpResponseBuilder,HttpRequest}; use Index\Http\{HttpResponseBuilder,HttpRequest};
use Index\Http\ErrorHandling\HtmlErrorHandler; use Index\Http\ErrorHandling\HtmlErrorHandler;
use Sasae\SasaeEnvironment;
class RoutingErrorHandler extends HtmlErrorHandler { class RoutingErrorHandler extends HtmlErrorHandler {
public function __construct(
private SasaeEnvironment $templating
) {}
#[\Override] #[\Override]
public function handle(HttpResponseBuilder $response, HttpRequest $request, int $code, string $message): void { 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->setTypeHTML();
$response->setContent(file_get_contents(sprintf('%s/errors/%s.html', HAU_DIR_TEMPLATES, $code))); $response->setContent(file_get_contents($path));
return;
}
if($code == 401 || $code === 403 || $code === 400) {
$response->setTypeHTML();
$response->setContent($this->templating->render(sprintf('errors/%s', $code)));
return; return;
} }

View file

@ -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 %}

View file

@ -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 %}

View file

@ -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 %}

View file

@ -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 %}

View file

@ -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>

View 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.' %}

View file

@ -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>

View 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="">' %}

View file

@ -1,8 +1,15 @@
{% extends 'master.twig' %} <!doctype html>
<html>
{% block body %} <head>
<div class="http-err"> <meta charset="utf-8">
<h1 class="http-err-title">{{ http_error_title|default('Unknown Error') }}</h1> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<p class="http-err-paragraph">{{ http_error_title|default('No additional information is available.') }}</p> <title>{{ http_error_title }} :: {{ globals.siteInfo.name }}</title>
</div> <link href="{{ asset('errors.css') }}" type="text/css" rel="stylesheet">
{% endblock %} </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
View 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);