From c5d3de6e0b32d5409d782fb244a8a3c08b2956d5 Mon Sep 17 00:00:00 2001 From: boban Date: Sun, 14 Jun 2026 15:27:49 +0200 Subject: [PATCH] feat(branding): favicons + PWA manifest, and custom branded error pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Branding assets (public/, served directly): - favicon.svg (scalable, primary), favicon.ico (16/32/48 PNG-embedded), apple-touch-icon.png (180), icon-192/512.png, site.webmanifest. The mark is the in-app brand glyph (orange Lucide "server" on dark). Wired into both layouts via a shared partials/head-icons (rel=icon svg+ico, apple-touch, manifest, theme-color #06080A). nginx serves the manifest as application/manifest+json. Never show raw error text (custom messages only): - Custom branded resources/views/errors/{403,404,419,429,500,503}.blade.php on a self-contained error layout (design tokens, brand mark, bilingual via lang/errors.php) — replaces Laravel's default Symfony/whoops pages. - docker-compose.prod.yml pins APP_DEBUG=false so a prod error can never render a stack trace (the custom pages show instead). - Files download error dispatched a bare $e->getMessage(); wrap it in the localized files.download_failed (consistent with upload/delete). Verified in-browser: icon links present + all assets 200; custom 404 renders the branded German message (no raw "Not Found"/trace), responsive at 375, 0 console. Co-Authored-By: Claude Opus 4.8 (1M context) --- app/Livewire/Files/Index.php | 2 +- docker-compose.prod.yml | 5 +++ docker/nginx/nginx.conf | 3 ++ lang/de/errors.php | 26 ++++++++++++++ lang/de/files.php | 1 + lang/en/errors.php | 26 ++++++++++++++ lang/en/files.php | 1 + public/apple-touch-icon.png | Bin 0 -> 2945 bytes public/favicon.ico | Bin 0 -> 1360 bytes public/favicon.svg | 9 +++++ public/icon-192.png | Bin 0 -> 3246 bytes public/icon-512.png | Bin 0 -> 8916 bytes public/site.webmanifest | 14 ++++++++ resources/views/errors/403.blade.php | 4 +++ resources/views/errors/404.blade.php | 4 +++ resources/views/errors/419.blade.php | 4 +++ resources/views/errors/429.blade.php | 4 +++ resources/views/errors/500.blade.php | 4 +++ resources/views/errors/503.blade.php | 4 +++ resources/views/errors/layout.blade.php | 32 ++++++++++++++++++ resources/views/layouts/app.blade.php | 1 + resources/views/layouts/auth.blade.php | 1 + resources/views/partials/head-icons.blade.php | 8 +++++ 23 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 lang/de/errors.php create mode 100644 lang/en/errors.php create mode 100644 public/apple-touch-icon.png create mode 100644 public/favicon.svg create mode 100644 public/icon-192.png create mode 100644 public/icon-512.png create mode 100644 public/site.webmanifest create mode 100644 resources/views/errors/403.blade.php create mode 100644 resources/views/errors/404.blade.php create mode 100644 resources/views/errors/419.blade.php create mode 100644 resources/views/errors/429.blade.php create mode 100644 resources/views/errors/500.blade.php create mode 100644 resources/views/errors/503.blade.php create mode 100644 resources/views/errors/layout.blade.php create mode 100644 resources/views/partials/head-icons.blade.php diff --git a/app/Livewire/Files/Index.php b/app/Livewire/Files/Index.php index 0dbd4d4..b299851 100644 --- a/app/Livewire/Files/Index.php +++ b/app/Livewire/Files/Index.php @@ -107,7 +107,7 @@ class Index extends Component try { $content = $fleet->getFile($active, rtrim($this->path, '/').'/'.basename($name)); } catch (Throwable $e) { - $this->dispatch('notify', message: $e->getMessage()); + $this->dispatch('notify', message: __('files.download_failed', ['error' => $e->getMessage()])); return null; } diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index e7285b2..73e697c 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -16,6 +16,11 @@ x-image: &image restart: unless-stopped env_file: - .env + # Safety net: force debug OFF in prod so an uncaught error can never render a + # stack trace to the user (the custom resources/views/errors/* pages show + # instead). `environment` overrides any APP_DEBUG left in the prod .env. + environment: + APP_DEBUG: "false" networks: - clusev diff --git a/docker/nginx/nginx.conf b/docker/nginx/nginx.conf index 3648424..c7d1597 100644 --- a/docker/nginx/nginx.conf +++ b/docker/nginx/nginx.conf @@ -47,6 +47,9 @@ http { location = /favicon.ico { access_log off; log_not_found off; } location = /robots.txt { access_log off; log_not_found off; } + # Correct MIME for the PWA manifest (the bundled mime.types maps .webmanifest + # to octet-stream). Per-file default_type avoids replacing the global types map. + location = /site.webmanifest { default_type application/manifest+json; access_log off; } error_page 404 /index.php; diff --git a/lang/de/errors.php b/lang/de/errors.php new file mode 100644 index 0000000..d378e87 --- /dev/null +++ b/lang/de/errors.php @@ -0,0 +1,26 @@ + 'Zurück zum Dashboard', + 'generic_title' => 'Fehler', + + '403_title' => 'Kein Zugriff', + '403_body' => 'Du hast keine Berechtigung, diese Seite zu sehen.', + + '404_title' => 'Seite nicht gefunden', + '404_body' => 'Diese Seite existiert nicht oder wurde verschoben.', + + '419_title' => 'Sitzung abgelaufen', + '419_body' => 'Deine Sitzung ist abgelaufen. Bitte lade die Seite neu und melde dich erneut an.', + + '429_title' => 'Zu viele Anfragen', + '429_body' => 'Zu viele Anfragen in kurzer Zeit. Bitte einen Moment warten und erneut versuchen.', + + '500_title' => 'Unerwarteter Fehler', + '500_body' => 'Auf dem Server ist ein unerwarteter Fehler aufgetreten. Bitte später erneut versuchen.', + + '503_title' => 'Wartungsmodus', + '503_body' => 'Clusev wird gerade gewartet. Bitte in Kürze erneut versuchen.', +]; diff --git a/lang/de/files.php b/lang/de/files.php index 8f31d8d..45deda3 100644 --- a/lang/de/files.php +++ b/lang/de/files.php @@ -24,6 +24,7 @@ return [ // Upload / notifications 'uploaded' => '„:name“ hochgeladen.', 'upload_failed' => 'Upload fehlgeschlagen: :error', + 'download_failed' => 'Download fehlgeschlagen: :error', // Delete (confirm modal) 'delete_heading' => 'Datei löschen', diff --git a/lang/en/errors.php b/lang/en/errors.php new file mode 100644 index 0000000..d16b2da --- /dev/null +++ b/lang/en/errors.php @@ -0,0 +1,26 @@ + 'Back to dashboard', + 'generic_title' => 'Error', + + '403_title' => 'Access denied', + '403_body' => 'You do not have permission to view this page.', + + '404_title' => 'Page not found', + '404_body' => 'This page does not exist or has moved.', + + '419_title' => 'Session expired', + '419_body' => 'Your session has expired. Please reload the page and sign in again.', + + '429_title' => 'Too many requests', + '429_body' => 'Too many requests in a short time. Please wait a moment and try again.', + + '500_title' => 'Unexpected error', + '500_body' => 'An unexpected error occurred on the server. Please try again later.', + + '503_title' => 'Down for maintenance', + '503_body' => 'Clusev is undergoing maintenance. Please check back shortly.', +]; diff --git a/lang/en/files.php b/lang/en/files.php index 5022668..9677c1e 100644 --- a/lang/en/files.php +++ b/lang/en/files.php @@ -24,6 +24,7 @@ return [ // Upload / notifications 'uploaded' => '“:name” uploaded.', 'upload_failed' => 'Upload failed: :error', + 'download_failed' => 'Download failed: :error', // Delete (confirm modal) 'delete_heading' => 'Delete file', diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..743a2c23bff3b9f2a78d901ecf0533d54f6b50e2 GIT binary patch literal 2945 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 b&>VnDUnd@U4UzWW8-Rt06|&Ok)}#Lbr38?6 literal 0 HcmV?d00001 diff --git a/public/favicon.ico b/public/favicon.ico index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..d086ff457b7e211558525d71b44aabe8721a442d 100644 GIT binary patch literal 1360 zcmZQzU}Rus5D;Jh(h3a5j0_BB3=9kk3K0HfAU_j`4GbWB5hex(8)gQE&Hz7mUM?vv zpi*8>50@Yy4K#s4fCDJcFkxZ_$cz`BE{-7;bIt_qUHozI^Iop{O? z=D^UsV9KK8XqOeA}kX{#Ubf%kgXTl0Hli zZ?gY?O&jRd|Nqz5_(}p9Y#=`~GgQtDk2rK!6cj8Bp00i_>zoprumqU`P+9>TWPc>y zE(V5h2PDYeOtbes>>zRMzV|FM-~TL2H*O8u$Z_eQuYh^D_(Bf7UyLq)&G_nA1Y=no z%?~d1YifQVFW|CfN?7*VrOTGRtenJ`c_vcdv_sY2sbZ(f_d8E3KMTZbSaIYXYCQd? zo^k$JiDSZ>1o$^)+OPa|)LWSOm6CzXlWpnj%cd}F2oCzXHRh?oeFJ_aZ*kePN0*7( zTRaWCdU-eNXFIDhhiUm|_EkpS-XGl9`ewzBNu0-B>@+|AtU4X1p!m^kV`;Fxw)x~s z%X#^i?cTre+7Xuqk**UnSy%YEv6Re{-4)g0&iaCN6$6vcxhU@XTpqQWE!?5JYTvtD z{)Zm^5Y5!OD^JAxaga;K>^Xf0{1w!skAALhPhYov<<;h=k3VIuWb9Lrzm>Hv=_Ak0 zBc{hBBLgHBFZyw9*Sf5`i<=H878yvN4wl=c$To5P(aldV}Drs#OJRwjRo+mxZ=a$f1?{kYxJUDfvJY`V}N*|Mkpvc32ZV7&bQ zpEvhlK9B*8((BCobPepfLx3Xqq7;}<7!1HsdLtq40x(KXLZftsuQ!vU#PRg)k>2j% zyhmR6ZV;~)v?_U}T*CS3!qV)-l9v}YdQZ1=SpG|-RbGkz+Cn~2L5T>-qzU<}AFhk; zUm<9+i~H;)w$IkKXKc>=nVJ4=rhRv(O~;#-jvO}e6c+K6mbWYq<%*MCdfZPeVXv0W zDG`7DtJc$cSMa9Lz0rLa*76^9`g`2FZu*gb9IIFDo2jE>^!~)21p+U<*RpVITeYBT znZDX5|EtH&YgU~!TNWFiHE*iht~H8cEw&XcUmO{_4~zYto|+OAa7BG9(~4hjGX4HL z_z19m*V!CitTH~+s4G{J8YeorRaE@ZIG{L zE$_KwIIGF}m|ns06HM_StWt!BPS?Lc$_p}iAWO; PXL + + + + + + + + diff --git a/public/icon-192.png b/public/icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..53e6382d80dc1b674ef7d20ddfd927b382cb8d96 GIT binary patch literal 3246 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 __('errors.403_title')]) +@section('code', '403') +@section('heading', __('errors.403_title')) +@section('message', __('errors.403_body')) diff --git a/resources/views/errors/404.blade.php b/resources/views/errors/404.blade.php new file mode 100644 index 0000000..f583f82 --- /dev/null +++ b/resources/views/errors/404.blade.php @@ -0,0 +1,4 @@ +@extends('errors.layout', ['title' => __('errors.404_title')]) +@section('code', '404') +@section('heading', __('errors.404_title')) +@section('message', __('errors.404_body')) diff --git a/resources/views/errors/419.blade.php b/resources/views/errors/419.blade.php new file mode 100644 index 0000000..a66726b --- /dev/null +++ b/resources/views/errors/419.blade.php @@ -0,0 +1,4 @@ +@extends('errors.layout', ['title' => __('errors.419_title')]) +@section('code', '419') +@section('heading', __('errors.419_title')) +@section('message', __('errors.419_body')) diff --git a/resources/views/errors/429.blade.php b/resources/views/errors/429.blade.php new file mode 100644 index 0000000..ade8e84 --- /dev/null +++ b/resources/views/errors/429.blade.php @@ -0,0 +1,4 @@ +@extends('errors.layout', ['title' => __('errors.429_title')]) +@section('code', '429') +@section('heading', __('errors.429_title')) +@section('message', __('errors.429_body')) diff --git a/resources/views/errors/500.blade.php b/resources/views/errors/500.blade.php new file mode 100644 index 0000000..a5699b5 --- /dev/null +++ b/resources/views/errors/500.blade.php @@ -0,0 +1,4 @@ +@extends('errors.layout', ['title' => __('errors.500_title')]) +@section('code', '500') +@section('heading', __('errors.500_title')) +@section('message', __('errors.500_body')) diff --git a/resources/views/errors/503.blade.php b/resources/views/errors/503.blade.php new file mode 100644 index 0000000..5d4a8e1 --- /dev/null +++ b/resources/views/errors/503.blade.php @@ -0,0 +1,4 @@ +@extends('errors.layout', ['title' => __('errors.503_title')]) +@section('code', '503') +@section('heading', __('errors.503_title')) +@section('message', __('errors.503_body')) diff --git a/resources/views/errors/layout.blade.php b/resources/views/errors/layout.blade.php new file mode 100644 index 0000000..c0c4032 --- /dev/null +++ b/resources/views/errors/layout.blade.php @@ -0,0 +1,32 @@ + + + + + + {{ ($title ?? __('errors.generic_title')).' — Clusev' }} + @include('partials.head-icons') + @vite(['resources/css/app.css']) + +{{-- Self-contained: no Livewire/session/auth deps, so it renders even when those + are the thing that broke. Design tokens come from the bundled CSS (R3). --}} + +
+
+ + + + Clusev +
+ +

@yield('code')

+

@yield('heading')

+

@yield('message')

+ +
+ + {{ __('errors.back_home') }} + +
+
+ + diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index 49fc126..b921e5a 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -11,6 +11,7 @@ {{ $title ?? 'Clusev' }} + @include('partials.head-icons')