From 9c7e1ce4dbfe2f2f21aab90f6a63e266f542e7d0 Mon Sep 17 00:00:00 2001 From: dschlueter Date: Wed, 7 Oct 2026 09:55:02 +0200 Subject: [PATCH] Feature: Fehlerseiten 401, 403, 500, 502, 503, 504 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gemeinsames Layout in components/error-page.tsx, 404 darauf umgestellt. Jede Route app//page.tsx erzeugt landingpage/.html; die Deploy-Skripte kopieren alle Fehlerseiten in den Webroot. README dokumentiert die nötigen ErrorDocument-Zeilen (Server-Konfig unverändert). Co-Authored-By: Claude Opus 5.5 --- README.md | 35 +++++++++ app/401/page.tsx | 37 +++++++++ app/403/page.tsx | 35 +++++++++ app/500/page.tsx | 34 +++++++++ app/502/page.tsx | 33 ++++++++ app/503/page.tsx | 31 ++++++++ app/504/page.tsx | 32 ++++++++ app/not-found.tsx | 139 ++++++--------------------------- components/error-page.tsx | 157 ++++++++++++++++++++++++++++++++++++++ deploy_to_localhost.sh | 9 ++- deploy_to_production.sh | 5 +- 11 files changed, 427 insertions(+), 120 deletions(-) create mode 100644 app/401/page.tsx create mode 100644 app/403/page.tsx create mode 100644 app/500/page.tsx create mode 100644 app/502/page.tsx create mode 100644 app/503/page.tsx create mode 100644 app/504/page.tsx create mode 100644 components/error-page.tsx diff --git a/README.md b/README.md index c78a177..151b592 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,7 @@ Die E-Mail im Footer (`footer.tsx`) wird als Prop von `app/page.tsx` übergeben: - Öffnet eine SSH-Master-Verbindung (Passwort nur einmal eingeben) - rsync nach `jamulix.de:/var/www/jamulix.de/html/landingpage/` - Setzt Symlink: `/var/www/jamulix.de/html/index.html` → `landingpage/index.html` +- Kopiert die Fehlerseiten `landingpage/.html` in den Webroot ### Localhost (zum Testen) @@ -107,6 +108,7 @@ Die E-Mail im Footer (`footer.tsx`) wird als Prop von `app/page.tsx` übergeben: - rsync nach `/var/www/html/landingpage/` - Symlink: `/var/www/html/index.html` → `landingpage/index.html` +- Kopiert die Fehlerseiten in den Webroot - `sudo chown -R www-data:www-data` ### Warum `/landingpage/` als Unterverzeichnis? @@ -115,6 +117,39 @@ Die E-Mail im Footer (`footer.tsx`) wird als Prop von `app/page.tsx` übergeben: unter `jamulix.de/` zu betreiben ohne Konflikte. rsync mit `--delete` löscht nur Dateien innerhalb von `landingpage/`, nicht andere Verzeichnisse im Webroot. +### Fehlerseiten + +| Code | Quelle | Thema | +|------|--------|-------| +| 401 | `app/401/page.tsx` | Autorisierung erforderlich | +| 403 | `app/403/page.tsx` | Zugriff verweigert | +| 404 | `app/not-found.tsx` | Seite nicht gefunden | +| 500 | `app/500/page.tsx` | Interner Serverfehler | +| 502 | `app/502/page.tsx` | Bad Gateway | +| 503 | `app/503/page.tsx` | Wartungsarbeiten | +| 504 | `app/504/page.tsx` | Gateway Timeout | + +Gemeinsames Layout: `components/error-page.tsx`. Der Build erzeugt +`landingpage/.html`; die Deploy-Skripte kopieren sie zusätzlich nach +`/.html`. Alle Asset-Pfade sind absolut (`/landingpage/_next/…`), +daher funktionieren die Kopien im Webroot genauso. + +Damit Apache sie ausliefert, braucht die vHost-Konfiguration (nicht Teil +dieses Repos) z. B.: + +```apache +ErrorDocument 401 /401.html +ErrorDocument 403 /403.html +ErrorDocument 404 /404.html +ErrorDocument 500 /500.html +ErrorDocument 502 /502.html +ErrorDocument 503 /503.html +ErrorDocument 504 /504.html +``` + +- `ErrorDocument 401` muss ein lokaler Pfad sein, keine vollständige URL. +- Die 401-Seite erscheint erst, wenn der Login-Dialog des Browsers abgebrochen wird. + --- ## Design diff --git a/app/401/page.tsx b/app/401/page.tsx new file mode 100644 index 0000000..0ff8b06 --- /dev/null +++ b/app/401/page.tsx @@ -0,0 +1,37 @@ +import { ErrorPage, Cmd, Out, Err } from '@/components/error-page' + +export const metadata = { + title: '401 – Autorisierung erforderlich | Jamulix', + description: 'Parole? Dieser Bereich ist passwortgeschützt.', + robots: { index: false, follow: false }, +} + +export default function Unauthorized() { + return ( + + ssh gast@jamulix.de + gast@jamulix.de: Permission denied (publickey,password). + sudo !! + [sudo] Passwort für gast: ******** + Sorry, try again. + [sudo] Passwort für gast: geheim + Sorry, try again. + [sudo] Passwort für gast: 123456 + sudo: 3 incorrect password attempts + echo "Sesam, öffne dich" + Sesam, öffne dich + + } + tip={<>Tipp: 123456 ist es auch diesmal nicht.} + > + Dieser Bereich ist passwortgeschützt. Wer Zugangsdaten hat, lädt die + Seite einfach neu und meldet sich an – alle anderen bekommen immerhin + diese hübsche Fehlerseite. + + ) +} diff --git a/app/403/page.tsx b/app/403/page.tsx new file mode 100644 index 0000000..f7fabdf --- /dev/null +++ b/app/403/page.tsx @@ -0,0 +1,35 @@ +import { ErrorPage, Cmd, Out, Err } from '@/components/error-page' +import { RequestedPath } from '@/components/requested-path' + +export const metadata = { + title: '403 – Zugriff verweigert | Jamulix', + description: 'Hier gilt chmod 000.', + robots: { index: false, follow: false }, +} + +export default function Forbidden() { + return ( + + ls -l + ls: Zugriff auf '' nicht möglich: Keine Berechtigung + chmod 777 + chmod: Ändern der Zugriffsrechte nicht möglich: Operation nicht erlaubt + sudo chmod 777 + gast ist nicht in der sudoers-Datei. Dieser Vorfall wird gemeldet. + whoami + gast + + } + tip={<>Tipp: Auch root war einmal gast.} + > + Die Seite gibt es – nur eben nicht für dich. Manche Verzeichnisse + bleiben aus gutem Grund verschlossen, selbst nach 50 Jahren + Unix-Erfahrung und mit freundlichem Fragen. + + ) +} diff --git a/app/500/page.tsx b/app/500/page.tsx new file mode 100644 index 0000000..b64d092 --- /dev/null +++ b/app/500/page.tsx @@ -0,0 +1,34 @@ +import { ErrorPage, Cmd, Out, Err, Code } from '@/components/error-page' + +export const metadata = { + title: '500 – Interner Serverfehler | Jamulix', + description: 'Der Server hat sich verschluckt.', + robots: { index: false, follow: false }, +} + +export default function InternalServerError() { + return ( + + ./server --seite-ausliefern + Segmentation fault (core dumped) + gdb ./server core + #0 0x00000000 in ?? () + #1 0x0000dead in seite_ausliefern (zeiger=NULL) + cat notfall.f + C WENN ALLES SCHIEFGEHT: + STOP 500 + + } + tip={<>Tipp: Kurz warten, dann F5 – das hilft öfter, als man denkt.} + > + Irgendwo im Maschinenraum ist ein Zeiger ins Leere gelaufen. Das ist + nicht deine Schuld. Der Fehler wird untersucht – vermutlich bei einer + Tasse Tee. + + ) +} diff --git a/app/502/page.tsx b/app/502/page.tsx new file mode 100644 index 0000000..29c9580 --- /dev/null +++ b/app/502/page.tsx @@ -0,0 +1,33 @@ +import { ErrorPage, Cmd, Out, Err } from '@/components/error-page' + +export const metadata = { + title: '502 – Bad Gateway | Jamulix', + description: 'Der Server hinter dem Server schweigt.', + robots: { index: false, follow: false }, +} + +export default function BadGateway() { + return ( + + ping -c 3 backend + --- backend ping statistics --- + 3 packets transmitted, 0 received, 100% packet loss + curl http://localhost:8080/ + curl: (7) Failed to connect to localhost port 8080: Connection refused + echo "Hallo? Jemand da?" > /dev/backend + … + + } + tip={<>Tipp: Der Dienst startet meist von selbst neu – in ein paar Minuten nochmal versuchen.} + > + Der Webserver ist da und hat brav weitergefragt – aber die Anwendung + dahinter antwortet gerade nicht. Wahrscheinlich startet sie neu oder + denkt über ihr Leben nach. + + ) +} diff --git a/app/503/page.tsx b/app/503/page.tsx new file mode 100644 index 0000000..88a0fa7 --- /dev/null +++ b/app/503/page.tsx @@ -0,0 +1,31 @@ +import { ErrorPage, Cmd, Out, Code } from '@/components/error-page' + +export const metadata = { + title: '503 – Wartungsarbeiten | Jamulix', + description: 'Der Server macht gerade Teepause.', + robots: { index: false, follow: false }, +} + +export default function ServiceUnavailable() { + return ( + + systemctl status jamulix + ● jamulix.service – Jamulix Webdienst + Active: maintenance (Teepause) since vorhin + Tasks: 1 (Wasser kocht) + sleep 300 && tee tasse.txt < kanne + Earl Grey, heiß. + + } + tip={<>Tipp: Ein guter Tee zieht 3–5 Minuten – genau wie diese Wartung.} + > + Hier wird gerade gewartet, aktualisiert und ein wenig aufgeräumt. + Gleich ist alles wieder da – frisch gepatcht und gut gelaunt. + + ) +} diff --git a/app/504/page.tsx b/app/504/page.tsx new file mode 100644 index 0000000..ea0014e --- /dev/null +++ b/app/504/page.tsx @@ -0,0 +1,32 @@ +import { ErrorPage, Cmd, Out, Err, Code } from '@/components/error-page' + +export const metadata = { + title: '504 – Gateway Timeout | Jamulix', + description: 'Der Server denkt noch nach.', + robots: { index: false, follow: false }, +} + +export default function GatewayTimeout() { + return ( + + curl --max-time 60 http://backend/ + curl: (28) Operation timed out after 60000 milliseconds + cat jobkarte.jcl + //JOB504 JOB (1976),'SEITE',CLASS=A + JOB 504 IN WARTESCHLANGE – POSITION 42 + VORAUSSICHTLICHE AUSGABE: MORGEN FRÜH AM SCHALTER + + } + tip={<>Tipp: 1976 kam das Ergebnis am nächsten Morgen – heute reicht meist ein Neuladen.} + > + Die Anwendung hinter dem Webserver braucht gerade länger als erlaubt. + Früher gab man seinen Lochkartenstapel ab und holte das Ergebnis am + nächsten Tag – so lange musst du hoffentlich nicht warten. + + ) +} diff --git a/app/not-found.tsx b/app/not-found.tsx index 6939106..b5b0518 100644 --- a/app/not-found.tsx +++ b/app/not-found.tsx @@ -1,6 +1,4 @@ -import Link from 'next/link' -import { ArrowLeft, FolderGit2 } from 'lucide-react' -import { Button } from '@/components/ui/button' +import { ErrorPage, Cmd, Out, Err, Code } from '@/components/error-page' import { RequestedPath } from '@/components/requested-path' export const metadata = { @@ -11,117 +9,28 @@ export const metadata = { export default function NotFound() { return ( -
- {/* Fly.io-style gradient mesh background (dark mode only) */} -
- - {/* Subtle grid overlay */} -
- - {/* Logo */} -
-
- - Jamulix - -
-
- -
-
-
- {/* Text Content */} -
-
- - Fehler 404 · Seite nicht gefunden - - - - -

- Diese Seite ist vom Lochkartenstapel gefallen. -

-
- -

- Seit 50 Jahren Code – und trotzdem verlegt man gelegentlich - etwas. Die gesuchte Seite existiert nicht, nicht mehr oder - noch nicht. Vielleicht war auch nur ein übereifriges{' '} - rm -rf{' '} - am Werk. -

- -
- - -
-
- - {/* Terminal */} -
-
-
- - - - - jamulix@jamulix.de: ~ - -
- -
-                  $ cd {'\n'}
-                  
-                    bash: cd: : Datei oder Verzeichnis nicht gefunden
-                  {'\n'}
-                  $ find / -name "seite" 2>/dev/null{'\n'}
-                  $ cat seite.f{'\n'}
-                  C     GESTANZT 1976 – STAPEL LEIDER RUNTERGEFALLEN{'\n'}
-                        GO TO 404{'\n'}
-                  $ sudo !!{'\n'}
-                  
-                    jamulix ist nicht in der sudoers-Datei. Dieser Vorfall wird gemeldet.
-                  {'\n'}
-                  ${' '}
-                  
-                
-
- -

- Tipp: Auch nach 50 Jahren hilft ls meistens weiter. -

-
-
-
-
-
+ + cd + bash: cd: : Datei oder Verzeichnis nicht gefunden + find / -name "seite" 2>/dev/null + cat seite.f + C GESTANZT 1976 – STAPEL LEIDER RUNTERGEFALLEN + GO TO 404 + sudo !! + jamulix ist nicht in der sudoers-Datei. Dieser Vorfall wird gemeldet. + + } + tip={<>Tipp: Auch nach 50 Jahren hilft ls meistens weiter.} + > + Seit 50 Jahren Code – und trotzdem verlegt man gelegentlich etwas. + Die gesuchte Seite existiert nicht, nicht mehr oder noch nicht. + Vielleicht war auch nur ein übereifriges{' '} + rm -rf am Werk. + ) } diff --git a/components/error-page.tsx b/components/error-page.tsx new file mode 100644 index 0000000..e6ecc22 --- /dev/null +++ b/components/error-page.tsx @@ -0,0 +1,157 @@ +import type { ReactNode } from 'react' +import Link from 'next/link' +import { ArrowLeft, FolderGit2 } from 'lucide-react' +import { Button } from '@/components/ui/button' + +/* Terminal lines — each renders one line incl. trailing newline inside
 */
+
+export function Cmd({ children }: { children: ReactNode }) {
+  return (
+    <>
+      $ {children}{'\n'}
+    
+  )
+}
+
+export function Out({ children }: { children: ReactNode }) {
+  return (
+    <>
+      {children}{'\n'}
+    
+  )
+}
+
+export function Err({ children }: { children: ReactNode }) {
+  return (
+    <>
+      {children}{'\n'}
+    
+  )
+}
+
+export function Code({ children }: { children: ReactNode }) {
+  return (
+    <>
+      {children}{'\n'}
+    
+  )
+}
+
+export function Cursor() {
+  return (
+    <>
+      ${' '}
+      
+    
+  )
+}
+
+type ErrorPageProps = {
+  code: number
+  label: string
+  title: string
+  children: ReactNode
+  terminal: ReactNode
+  tip: ReactNode
+}
+
+export function ErrorPage({ code, label, title, children, terminal, tip }: ErrorPageProps) {
+  return (
+    
+ {/* Fly.io-style gradient mesh background (dark mode only) */} +
+ + {/* Subtle grid overlay */} +
+ + {/* Logo */} +
+
+ + Jamulix + +
+
+ +
+
+
+ {/* Text Content */} +
+
+ + Fehler {code} · {label} + + + + +

+ {title} +

+
+ +

+ {children} +

+ +
+ + +
+
+ + {/* Terminal */} +
+
+
+ + + + + jamulix@jamulix.de: ~ + +
+ +
+                  {terminal}
+                  
+                
+
+ +

+ {tip} +

+
+
+
+
+
+ ) +} diff --git a/deploy_to_localhost.sh b/deploy_to_localhost.sh index c8cc3d3..b9f023d 100755 --- a/deploy_to_localhost.sh +++ b/deploy_to_localhost.sh @@ -4,6 +4,7 @@ set -e LOCAL_DIR="/home/dschlueter/neue_jamulix.de_landing_page/jamulix.de_webpage_2" +ERROR_PAGES="401 403 404 500 502 503 504" cd "$LOCAL_DIR" @@ -17,9 +18,11 @@ echo "Setze symbolischen Link: index.html → landingpage/index.html" sudo rm -f /var/www/html/index.html sudo ln -s landingpage/index.html /var/www/html/index.html -echo "Kopiere 404-Seite: landingpage/404.html → 404.html" -sudo cp landingpage/404.html /var/www/html/404.html -sudo chown www-data:www-data /var/www/html/404.html +echo "Kopiere Fehlerseiten: landingpage/.html → .html" +for c in $ERROR_PAGES; do + sudo cp "landingpage/$c.html" "/var/www/html/$c.html" + sudo chown www-data:www-data "/var/www/html/$c.html" +done sudo chown -R www-data:www-data /var/www/html/landingpage/ diff --git a/deploy_to_production.sh b/deploy_to_production.sh index 9daad67..e6ea84a 100755 --- a/deploy_to_production.sh +++ b/deploy_to_production.sh @@ -8,6 +8,7 @@ REMOTE_HOST="jamulix.de" REMOTE_DIR="/var/www/jamulix.de/html" LOCAL_DIR="/home/dschlueter/neue_jamulix.de_landing_page/jamulix.de_webpage_2" SSH_SOCKET="/tmp/ssh-jamulix-deploy-$$" +ERROR_PAGES="401 403 404 500 502 503 504" cleanup() { ssh -S "$SSH_SOCKET" -O exit "$REMOTE_USER@$REMOTE_HOST" 2>/dev/null || true @@ -30,8 +31,8 @@ rsync -av --delete -e "ssh -S $SSH_SOCKET" landingpage/ "$REMOTE_USER@$REMOTE_HO echo "Setze symbolischen Link: index.html → landingpage/index.html" ssh -S "$SSH_SOCKET" "$REMOTE_USER@$REMOTE_HOST" "rm -f '$REMOTE_DIR/index.html' && ln -s landingpage/index.html '$REMOTE_DIR/index.html'" -echo "Kopiere 404-Seite: landingpage/404.html → 404.html" -ssh -S "$SSH_SOCKET" "$REMOTE_USER@$REMOTE_HOST" "cp '$REMOTE_DIR/landingpage/404.html' '$REMOTE_DIR/404.html'" +echo "Kopiere Fehlerseiten: landingpage/.html → .html" +ssh -S "$SSH_SOCKET" "$REMOTE_USER@$REMOTE_HOST" "for c in $ERROR_PAGES; do cp '$REMOTE_DIR'/landingpage/\$c.html '$REMOTE_DIR'/\$c.html; done" echo "" echo "Deployment abgeschlossen: https://jamulix.de"