Add maintenance pages #16

Merged
RHPZAdmin merged 3 commits from dev into master 2026-06-29 10:39:07 +00:00
12 changed files with 740 additions and 14 deletions

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Attributes\Description;
use Illuminate\Console\Attributes\Signature;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Cache;
#[Signature('submissions:down')]
#[Description('Disable submissions')]
class SubmissionsDown extends Command
{
/**
* Execute the console command.
*/
public function handle()
{
Cache::put('submissions_maintenance', true );
$this->info("Submissions disabled.");
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Attributes\Description;
use Illuminate\Console\Attributes\Signature;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Cache;
#[Signature('submissions:up')]
#[Description('Enable submissions')]
class SubmissionsUp extends Command
{
/**
* Execute the console command.
*/
public function handle()
{
Cache::forget('submissions_maintenance');
$this->info('Submissions enabled.');
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Symfony\Component\HttpFoundation\Response;
class SubmissionsEnabled
{
/**
* Handle an incoming request.
*
* @param Closure(Request): (Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
if( Cache::get('submissions_maintenance', false ) ){
if( $request->expectsJson() ){
return response()->json([
'message' => 'The submissions are currently in maintenance mode.',
], 503 );
}
return back()->withErrors([
'maintenance' => "The submissions are currently in maintenance mode.",
])->withInput();
}
return $next($request);
}
}

View File

@@ -14,6 +14,10 @@ class ErrorBlock extends Component
'icon' => 'ban',
'message' => '%s'
],
'not-found' => [
'icon' => 'sticky-note-off',
'message' => "404: This page does not exist."
],
'page-not-allowed' => [
'icon' => 'shield-ban',
'message' => "You do not have permission to access this page.\nRequired permission: %s"
@@ -21,6 +25,10 @@ class ErrorBlock extends Component
'user-state-not-valid' => [
'icon' => 'shield-ban',
'message' => "You do not have permission to access this page.\nYour user profile is incomplete: %s\nGo back to the forum for more details."
],
'server-error' => [
'icon' => 'bomb',
'message' => "500: Internal Server Error."
]
];

View File

@@ -16,6 +16,7 @@ return Application::configure(basePath: dirname(__DIR__))
$middleware->encryptCookies(except: ['xf_session','xf_user','xf_csrf','xf_style_variation','activity_filters']);
$middleware->alias([
'xf.auth' => \App\Http\Middleware\CheckXenForoPermissions::class,
'submissions.maintenance' => \App\Http\Middleware\SubmissionsEnabled::class,
]);
$middleware->redirectGuestsTo(function(Request $request): void {
if( $request->is('manage*'))
@@ -24,5 +25,20 @@ return Application::configure(basePath: dirname(__DIR__))
$middleware->append(\App\Http\Middleware\CheckXenForoUserState::class);
})
->withExceptions(function (Exceptions $exceptions): void {
//
$exceptions->render(function(Throwable $e, $request) {
$statusCode = method_exists($e, 'getStatusCode') ? $e->getStatusCode() : 500;
if( app()->environment('production') && $statusCode >= 500 && !$request->expectsJson() ){
$errorId = (string) Str::uuid();
\Illuminate\Support\Facades\Log::error($e->getMessage(), [
'error_id' => $errorId,
'exception' => $e,
'url' => $request->fullUrl(),
'user_id' => (optional($request->user())->user_id ?? 0),
]);
return response()->view('errors.500', ['errorId' => $errorId], 500 );
}
});
})->create();

View File

@@ -0,0 +1,6 @@
@extends('layouts.app')
@section('content')
<x-error-block error-type="page-not-allowed" message="Unknown" />
<a class="btn" href="javascript:history.go(-1)">Go back to the previous page</a>
@endsection

View File

@@ -0,0 +1,6 @@
@extends('layouts.app')
@section('content')
<x-error-block error-type="not-found" />
<a class="btn" href="javascript:history.go(-1)">Go back to the previous page</a>
@endsection

View File

@@ -0,0 +1,6 @@
@extends('layouts.app')
@section('content')
<x-error-block error-type="custom" message="{{ $exception->getMessage() }}" />
<a class="btn" href="javascript:history.go(-1)">Go back to the previous page</a>
@endsection

View File

@@ -0,0 +1,305 @@
<html>
<head>
<title>500 - Internal server error - Romhack Plaza</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
:root {
/* RHPZ color */
--rhpz-orange: #ff7300;
--rhpz-orange-hover: #e56700;
/* Background colors */
--bg: #1e1e1e;
--bg2: #252526;
--bg3: #2d2d30;
--bg4: #3e3e42;
/* Text */
--text: #f1f1f1;
--text2: #a1a1aa;
--text3: #111111;
/* Elements */
--border: #3f3f46;
--error: #e57373;
--info: #1976d2;
--success: #81c784;
--success2: #388e3c;
/* Typo */
--typography: 'Segoe UI', 'San Francisco', 'Helvetica Neue', sans-serif;
/* Menu settings */
--menu-size: 260px;
--menu-user-avatar-bg: #555;
/* Gap */
--gap: 15px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--typography);
background-color: var(--bg);
color: var(--text);
display: flex;
height: 100vh;
overflow: hidden;
}
a {
color: var(--rhpz-orange);
text-decoration: none;
transition: color 0.2s;
}
a:hover {
color: var(--rhpz-orange-hover);
text-decoration: underline;
}
#app {
display: flex;
width: 100%;
height: 100%;
}
ul {
margin-left: 20px;
margin-bottom: 20px;
list-style-type: square;
}
[x-cloak] {display: none !important;}
/* BUTTONS */
.btn {
background-color: var(--bg3);
border: 1px solid var(--border);
color: var(--text);
padding: 8px 16px;
font-size: 0.9rem;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 8px;
transition: all 0.1s;
}
.btn:hover {
background-color: var(--bg4);
border-color: var(--menu-user-avatar-bg);
}
.btn.primary {
background-color: var(--rhpz-orange);
color: var(--text3);
border-color: var(--rhpz-orange);
font-weight: 600;
}
.btn.primary:hover {
background-color: var(--rhpz-orange-hover);
border-color: var(--rhpz-orange-hover);
}
.btn.danger {
background-color: transparent;
color: var(--error);
border-color: var(--error);
}
.btn.danger:hover {
background-color: rgba(229, 115, 115, 0.1);
}
.btn.success {
background-color: transparent;
color: var(--success);
border-color: var(--success);
}
.btn.success:hover {
background-color: rgba(129, 199, 132, 0.1);
}
/* BLOCK */
.block {
background-color: var(--bg2);
border: 1px solid var(--border);
padding: 20px;
margin-bottom: 20px;
}
.block.featured {
border-left: 3px solid var(--rhpz-orange);
}
.block-header {
font-size: 1.2rem;
color: var(--text);
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 10px;
border-bottom: 1px solid var(--border);
padding-bottom: 10px;
}
.block-success {
background-color: var(--success);
border: 1px solid var(--success);
color: var(--text);
padding: 20px;
margin-bottom: 20px;
}
.block-error {
background-color: var(--error);
border: 1px solid var(--error);
color: var(--text);
padding: 20px;
margin-bottom: 20px;
}
/* PAGE */
.page-title {
font-size: 1.8rem;
font-weight: 300;
margin-bottom: 20px;
color: var(--text);
flex-shrink: 0;
}
/* TEXTS */
.whisper {
color: var(--text2);
margin-bottom: 15px;
}
.content-title {
color: var(--text);
margin: 30px 0 15px 0;
border-left: 3px solid var(--rhpz-orange);
padding-left: 10px;
}
.quote {
background-color: var(--bg);
border-left: 4px solid #1976d2;
padding: 15px;
margin-top: 30px;
font-style: italic;
}
@media (max-width: 768px) {
.btn {
padding: 7px 12px;
font-size: 0.85rem;
gap: 6px;
}
.block {
padding: 15px;
margin-bottom: 15px;
}
.block-header {
font-size: 1.05rem;
margin-bottom: 12px;
padding-bottom: 8px;
}
.page-title {
font-size: 1.5rem;
margin-bottom: 15px;
}
.content-title {
margin: 20px 0 12px 0;
padding-left: 8px;
}
.quote {
padding: 12px;
margin-top: 20px;
font-size: 0.95rem;
}
.whisper {
margin-bottom: 12px;
font-size: 0.9rem;
}
.breadcrumb {
font-size: 0.85rem;
}
}
@media (max-width: 600px) {
.btn {
padding: 6px 10px;
font-size: 0.8rem;
gap: 4px;
justify-content: center;
}
.btn.primary, .btn.danger, .btn.success {
width: 100%;
}
.block {
padding: 12px;
margin-bottom: 12px;
}
.block-header {
font-size: 0.95rem;
margin-bottom: 10px;
padding-bottom: 6px;
}
.page-title {
font-size: 1.2rem;
margin-bottom: 12px;
}
.badge {
padding: 2px 6px;
font-size: 0.7rem;
}
.topbar-badge {
min-width: 16px;
height: 16px;
padding: 0 3px;
font-size: 0.6rem;
}
}
</style>
</head>
<body>
<div id="app" style="display:flex;flex-direction: column;align-items: center;justify-content: center;">
<main id="main-wrapper">
<main id="content">
<div class="block">
<div class="page-title">500 - Internal server error</div>
<p>To resolve the issue quickly, please report it on Gitea, Discord, or the forum with the following information.</p>
<ul>
<li>Your error code: {{ $errorId ?? "" }}</li>
<li>What you were doing before the error occurred.</li>
</ul>
</div>
<div>
<a href="https://code.romhackplaza.org/RHPZAdmin/RomhackPlaza/issues" class="btn primary">Report the problem on Gitea</a>
<a href="https://community.romhackplaza.org/forums/bug-reports.6/" class="btn primary">Report the problem on the forum</a>
<a href="https://community.romhackplaza.org/misc/contact/" class="btn">Report the problem with the contact form</a>
<a href="https://discord.gg/5CKzeWmZZU" class="btn">Report the problem on Discord</a>
</div>
</main>
</main>
</div>
</body>
</html>

View File

@@ -24,6 +24,9 @@
@if(session('error'))
<x-error-block error-type="custom" :message="session('error')" />
@endif
@if($errors->has('maintenance'))
<x-error-block error-type="custom" :message="$errors->get('maintenance')[0]" />
@endif
@yield('content')
</div>
</main>

View File

@@ -0,0 +1,299 @@
<html>
<head>
<title>500 - Internal server error - Romhack Plaza</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
:root {
/* RHPZ color */
--rhpz-orange: #ff7300;
--rhpz-orange-hover: #e56700;
/* Background colors */
--bg: #1e1e1e;
--bg2: #252526;
--bg3: #2d2d30;
--bg4: #3e3e42;
/* Text */
--text: #f1f1f1;
--text2: #a1a1aa;
--text3: #111111;
/* Elements */
--border: #3f3f46;
--error: #e57373;
--info: #1976d2;
--success: #81c784;
--success2: #388e3c;
/* Typo */
--typography: 'Segoe UI', 'San Francisco', 'Helvetica Neue', sans-serif;
/* Menu settings */
--menu-size: 260px;
--menu-user-avatar-bg: #555;
/* Gap */
--gap: 15px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--typography);
background-color: var(--bg);
color: var(--text);
display: flex;
height: 100vh;
overflow: hidden;
}
a {
color: var(--rhpz-orange);
text-decoration: none;
transition: color 0.2s;
}
a:hover {
color: var(--rhpz-orange-hover);
text-decoration: underline;
}
#app {
display: flex;
width: 100%;
height: 100%;
}
ul {
margin-left: 20px;
margin-bottom: 20px;
list-style-type: square;
}
[x-cloak] {display: none !important;}
/* BUTTONS */
.btn {
background-color: var(--bg3);
border: 1px solid var(--border);
color: var(--text);
padding: 8px 16px;
font-size: 0.9rem;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 8px;
transition: all 0.1s;
}
.btn:hover {
background-color: var(--bg4);
border-color: var(--menu-user-avatar-bg);
}
.btn.primary {
background-color: var(--rhpz-orange);
color: var(--text3);
border-color: var(--rhpz-orange);
font-weight: 600;
}
.btn.primary:hover {
background-color: var(--rhpz-orange-hover);
border-color: var(--rhpz-orange-hover);
}
.btn.danger {
background-color: transparent;
color: var(--error);
border-color: var(--error);
}
.btn.danger:hover {
background-color: rgba(229, 115, 115, 0.1);
}
.btn.success {
background-color: transparent;
color: var(--success);
border-color: var(--success);
}
.btn.success:hover {
background-color: rgba(129, 199, 132, 0.1);
}
/* BLOCK */
.block {
background-color: var(--bg2);
border: 1px solid var(--border);
padding: 20px;
margin-bottom: 20px;
}
.block.featured {
border-left: 3px solid var(--rhpz-orange);
}
.block-header {
font-size: 1.2rem;
color: var(--text);
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 10px;
border-bottom: 1px solid var(--border);
padding-bottom: 10px;
}
.block-success {
background-color: var(--success);
border: 1px solid var(--success);
color: var(--text);
padding: 20px;
margin-bottom: 20px;
}
.block-error {
background-color: var(--error);
border: 1px solid var(--error);
color: var(--text);
padding: 20px;
margin-bottom: 20px;
}
/* PAGE */
.page-title {
font-size: 1.8rem;
font-weight: 300;
margin-bottom: 20px;
color: var(--text);
flex-shrink: 0;
}
/* TEXTS */
.whisper {
color: var(--text2);
margin-bottom: 15px;
}
.content-title {
color: var(--text);
margin: 30px 0 15px 0;
border-left: 3px solid var(--rhpz-orange);
padding-left: 10px;
}
.quote {
background-color: var(--bg);
border-left: 4px solid #1976d2;
padding: 15px;
margin-top: 30px;
font-style: italic;
}
@media (max-width: 768px) {
.btn {
padding: 7px 12px;
font-size: 0.85rem;
gap: 6px;
}
.block {
padding: 15px;
margin-bottom: 15px;
}
.block-header {
font-size: 1.05rem;
margin-bottom: 12px;
padding-bottom: 8px;
}
.page-title {
font-size: 1.5rem;
margin-bottom: 15px;
}
.content-title {
margin: 20px 0 12px 0;
padding-left: 8px;
}
.quote {
padding: 12px;
margin-top: 20px;
font-size: 0.95rem;
}
.whisper {
margin-bottom: 12px;
font-size: 0.9rem;
}
.breadcrumb {
font-size: 0.85rem;
}
}
@media (max-width: 600px) {
.btn {
padding: 6px 10px;
font-size: 0.8rem;
gap: 4px;
justify-content: center;
}
.btn.primary, .btn.danger, .btn.success {
width: 100%;
}
.block {
padding: 12px;
margin-bottom: 12px;
}
.block-header {
font-size: 0.95rem;
margin-bottom: 10px;
padding-bottom: 6px;
}
.page-title {
font-size: 1.2rem;
margin-bottom: 12px;
}
.badge {
padding: 2px 6px;
font-size: 0.7rem;
}
.topbar-badge {
min-width: 16px;
height: 16px;
padding: 0 3px;
font-size: 0.6rem;
}
}
</style>
</head>
<body>
<div id="app" style="display:flex;flex-direction: column;align-items: center;justify-content: center;">
<main id="main-wrapper">
<main id="content">
<div class="block">
<div class="page-title">Maintenance is in progress.</div>
<p>Please wait until maintenance is complete. Follow the Discord server or the forum if this is unscheduled maintenance.</p>
</div>
<div>
<a href="https://community.romhackplaza.org/" class="btn primary">Go to the forum</a>
<a href="https://discord.gg/5CKzeWmZZU" class="btn">Go to Discord</a>
</div>
</main>
</main>
</div>
</body>
</html>

View File

@@ -33,14 +33,14 @@ Route::name('reviews.')->controller(ReviewController::class)->group(function ()
});
// SubmissionController.
Route::name('submit.')->prefix('/submit')->controller(\App\Http\Controllers\SubmissionController::class)->middleware(['xf.auth', 'can:create,\App\Models\Entry'])->group(function () {
Route::name('submit.')->prefix('/submit')->controller(\App\Http\Controllers\SubmissionController::class)->middleware(['submissions.maintenance','xf.auth', 'can:create,\App\Models\Entry'])->group(function () {
Route::get('/', 'index')->name('index');
Route::get('/{section}', 'create' )->name('create')
->where([ 'section' => 'translations|romhacks|homebrew|utilities|documents|lua-scripts' ]);
Route::post('/{section}', 'store' )->name('store')
->where([ 'section' => 'translations|romhacks|homebrew|utilities|documents|lua-scripts' ]);
});
Route::name('submit.')->prefix('/edit')->controller(\App\Http\Controllers\SubmissionController::class)->middleware(['xf.auth', 'can:update,entry'])->group(function () {
Route::name('submit.')->prefix('/edit')->controller(\App\Http\Controllers\SubmissionController::class)->middleware(['submissions.maintenance','xf.auth', 'can:update,entry'])->group(function () {
Route::get('/{section}/{entry:id}', 'edit' )->name('edit')
->where([ 'section' => 'translations|romhacks|homebrew|utilities|documents|lua-scripts', 'entry' => '[0-9\-]+' ]);
Route::post('/{section}/{entry:id}', 'update' )->name('update')
@@ -54,12 +54,12 @@ Route::name('news.')->controller(\App\Http\Controllers\NewsController::class)->g
Route::get('/news/', 'index' )->name('index');
Route::get('/news/{news:slug}', 'show' )->name('show')->where([ 'news' => '[a-zA-Z0-9\-_]+']);
Route::get('/submit/news', 'create' )->name('create')->middleware(['xf.auth','can:create,\App\Models\News']);
Route::post('/submit/news', 'store' )->name('store')->middleware(['xf.auth','can:create,\App\Models\News']);
Route::get('/submit/news', 'create' )->name('create')->middleware(['submissions.maintenance','xf.auth','can:create,\App\Models\News']);
Route::post('/submit/news', 'store' )->name('store')->middleware(['submissions.maintenance','xf.auth','can:create,\App\Models\News']);
Route::get('/edit/news/{news:id}', 'edit')->name('edit')->where(['news' => '[0-9\-]+'])->middleware(['xf.auth','can:update,news']);
Route::post('/edit/news/{news:id}', 'update')->name('update')->where(['news' => '[0-9\-]+'])->middleware(['xf.auth','can:update,news']);
Route::delete('/edit/news/{news:id}', 'destroy')->name('destroy')->where(['news' => '[0-9\-]+'])->middleware(['xf.auth','can:update,news']);
Route::get('/edit/news/{news:id}', 'edit')->name('edit')->where(['news' => '[0-9\-]+'])->middleware(['submissions.maintenance','xf.auth','can:update,news']);
Route::post('/edit/news/{news:id}', 'update')->name('update')->where(['news' => '[0-9\-]+'])->middleware(['submissions.maintenance','xf.auth','can:update,news']);
Route::delete('/edit/news/{news:id}', 'destroy')->name('destroy')->where(['news' => '[0-9\-]+'])->middleware(['submissions.maintenance','xf.auth','can:update,news']);
});
// QueueController
@@ -67,32 +67,32 @@ Route::name('queue.')->prefix('/queue')->controller(\App\Http\Controllers\QueueC
Route::get('/', 'index' )->name('index');
Route::patch('/{entry:id}/comment', 'updateComment' )
->middleware(['xf.auth', 'can:updateComment,entry' ] )
->middleware(['submissions.maintenance','xf.auth', 'can:updateComment,entry' ] )
->where([ 'entry' => '[0-9\-]+' ])
->name('comment');
Route::patch('/{entry:id}/approve', 'approve' )
->middleware(['xf.auth', 'can:approve,entry' ] )
->middleware(['submissions.maintenance','xf.auth', 'can:approve,entry' ] )
->where([ 'entry' => '[0-9\-]+' ])
->name('approve');
Route::patch('/{entry:id}/reject', 'reject' )
->middleware(['xf.auth', 'can:reject,entry' ] )
->middleware(['submissions.maintenance','xf.auth', 'can:reject,entry' ] )
->where([ 'entry' => '[0-9\-]+' ])
->name('reject');
Route::patch('/news/{news:id}/comment', 'updateComment_news' )
->middleware(['xf.auth', 'can:updateComment,news' ] )
->middleware(['submissions.maintenance','xf.auth', 'can:updateComment,news' ] )
->where([ 'news' => '[0-9\-]+' ])
->name('news.comment');
Route::patch('/news/{news:id}/approve', 'approve_news' )
->middleware(['xf.auth', 'can:approve,news' ] )
->middleware(['submissions.maintenance','xf.auth', 'can:approve,news' ] )
->where([ 'news' => '[0-9\-]+' ])
->name('news.approve');
Route::patch('/news/{news:id}/reject', 'reject_news' )
->middleware(['xf.auth', 'can:reject,news' ] )
->middleware(['submissions.maintenance','xf.auth', 'can:reject,news' ] )
->where([ 'news' => '[0-9\-]+' ])
->name('news.reject');
});