Feature: Fehlerseiten 401, 403, 500, 502, 503, 504
Gemeinsames Layout in components/error-page.tsx, 404 darauf umgestellt. Jede Route app/<code>/page.tsx erzeugt landingpage/<code>.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 <noreply@anthropic.com>
This commit is contained in:
parent
ea2bcfbd27
commit
9c7e1ce4db
11 changed files with 427 additions and 120 deletions
35
README.md
35
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)
|
- Öffnet eine SSH-Master-Verbindung (Passwort nur einmal eingeben)
|
||||||
- rsync nach `jamulix.de:/var/www/jamulix.de/html/landingpage/`
|
- rsync nach `jamulix.de:/var/www/jamulix.de/html/landingpage/`
|
||||||
- Setzt Symlink: `/var/www/jamulix.de/html/index.html` → `landingpage/index.html`
|
- Setzt Symlink: `/var/www/jamulix.de/html/index.html` → `landingpage/index.html`
|
||||||
|
- Kopiert die Fehlerseiten `landingpage/<code>.html` in den Webroot
|
||||||
|
|
||||||
### Localhost (zum Testen)
|
### 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/`
|
- rsync nach `/var/www/html/landingpage/`
|
||||||
- Symlink: `/var/www/html/index.html` → `landingpage/index.html`
|
- Symlink: `/var/www/html/index.html` → `landingpage/index.html`
|
||||||
|
- Kopiert die Fehlerseiten in den Webroot
|
||||||
- `sudo chown -R www-data:www-data`
|
- `sudo chown -R www-data:www-data`
|
||||||
|
|
||||||
### Warum `/landingpage/` als Unterverzeichnis?
|
### 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
|
unter `jamulix.de/` zu betreiben ohne Konflikte. rsync mit `--delete` löscht
|
||||||
nur Dateien innerhalb von `landingpage/`, nicht andere Verzeichnisse im Webroot.
|
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/<code>.html`; die Deploy-Skripte kopieren sie zusätzlich nach
|
||||||
|
`<webroot>/<code>.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
|
## Design
|
||||||
|
|
|
||||||
37
app/401/page.tsx
Normal file
37
app/401/page.tsx
Normal file
|
|
@ -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 (
|
||||||
|
<ErrorPage
|
||||||
|
code={401}
|
||||||
|
label="Autorisierung erforderlich"
|
||||||
|
title="Parole?"
|
||||||
|
terminal={
|
||||||
|
<>
|
||||||
|
<Cmd>ssh gast@jamulix.de</Cmd>
|
||||||
|
<Err>gast@jamulix.de: Permission denied (publickey,password).</Err>
|
||||||
|
<Cmd>sudo !!</Cmd>
|
||||||
|
<Out>[sudo] Passwort für gast: ********</Out>
|
||||||
|
<Out>Sorry, try again.</Out>
|
||||||
|
<Out>[sudo] Passwort für gast: geheim</Out>
|
||||||
|
<Out>Sorry, try again.</Out>
|
||||||
|
<Out>[sudo] Passwort für gast: 123456</Out>
|
||||||
|
<Err>sudo: 3 incorrect password attempts</Err>
|
||||||
|
<Cmd>echo "Sesam, öffne dich"</Cmd>
|
||||||
|
<Out>Sesam, öffne dich</Out>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
tip={<>Tipp: <span className="text-accent">123456</span> 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.
|
||||||
|
</ErrorPage>
|
||||||
|
)
|
||||||
|
}
|
||||||
35
app/403/page.tsx
Normal file
35
app/403/page.tsx
Normal file
|
|
@ -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 (
|
||||||
|
<ErrorPage
|
||||||
|
code={403}
|
||||||
|
label="Zugriff verweigert"
|
||||||
|
title="Hier gilt chmod 000."
|
||||||
|
terminal={
|
||||||
|
<>
|
||||||
|
<Cmd>ls -l <RequestedPath /></Cmd>
|
||||||
|
<Err>ls: Zugriff auf '<RequestedPath />' nicht möglich: Keine Berechtigung</Err>
|
||||||
|
<Cmd>chmod 777 <RequestedPath /></Cmd>
|
||||||
|
<Err>chmod: Ändern der Zugriffsrechte nicht möglich: Operation nicht erlaubt</Err>
|
||||||
|
<Cmd>sudo chmod 777 <RequestedPath /></Cmd>
|
||||||
|
<Err>gast ist nicht in der sudoers-Datei. Dieser Vorfall wird gemeldet.</Err>
|
||||||
|
<Cmd>whoami</Cmd>
|
||||||
|
<Out>gast</Out>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
tip={<>Tipp: Auch root war einmal <span className="text-accent">gast</span>.</>}
|
||||||
|
>
|
||||||
|
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.
|
||||||
|
</ErrorPage>
|
||||||
|
)
|
||||||
|
}
|
||||||
34
app/500/page.tsx
Normal file
34
app/500/page.tsx
Normal file
|
|
@ -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 (
|
||||||
|
<ErrorPage
|
||||||
|
code={500}
|
||||||
|
label="Interner Serverfehler"
|
||||||
|
title="Der Server hat sich verschluckt."
|
||||||
|
terminal={
|
||||||
|
<>
|
||||||
|
<Cmd>./server --seite-ausliefern</Cmd>
|
||||||
|
<Err>Segmentation fault (core dumped)</Err>
|
||||||
|
<Cmd>gdb ./server core</Cmd>
|
||||||
|
<Out>#0 0x00000000 in ?? ()</Out>
|
||||||
|
<Out>#1 0x0000dead in seite_ausliefern (zeiger=NULL)</Out>
|
||||||
|
<Cmd>cat notfall.f</Cmd>
|
||||||
|
<Out>C WENN ALLES SCHIEFGEHT:</Out>
|
||||||
|
<Code> STOP 500</Code>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
tip={<>Tipp: Kurz warten, dann <span className="text-accent">F5</span> – 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.
|
||||||
|
</ErrorPage>
|
||||||
|
)
|
||||||
|
}
|
||||||
33
app/502/page.tsx
Normal file
33
app/502/page.tsx
Normal file
|
|
@ -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 (
|
||||||
|
<ErrorPage
|
||||||
|
code={502}
|
||||||
|
label="Bad Gateway"
|
||||||
|
title="Der Server hinter dem Server schweigt."
|
||||||
|
terminal={
|
||||||
|
<>
|
||||||
|
<Cmd>ping -c 3 backend</Cmd>
|
||||||
|
<Out>--- backend ping statistics ---</Out>
|
||||||
|
<Err>3 packets transmitted, 0 received, 100% packet loss</Err>
|
||||||
|
<Cmd>curl http://localhost:8080/</Cmd>
|
||||||
|
<Err>curl: (7) Failed to connect to localhost port 8080: Connection refused</Err>
|
||||||
|
<Cmd>echo "Hallo? Jemand da?" > /dev/backend</Cmd>
|
||||||
|
<Out>…</Out>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
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.
|
||||||
|
</ErrorPage>
|
||||||
|
)
|
||||||
|
}
|
||||||
31
app/503/page.tsx
Normal file
31
app/503/page.tsx
Normal file
|
|
@ -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 (
|
||||||
|
<ErrorPage
|
||||||
|
code={503}
|
||||||
|
label="Wartungsarbeiten"
|
||||||
|
title="Der Server macht gerade Teepause."
|
||||||
|
terminal={
|
||||||
|
<>
|
||||||
|
<Cmd>systemctl status jamulix</Cmd>
|
||||||
|
<Out>● jamulix.service – Jamulix Webdienst</Out>
|
||||||
|
<Code> Active: maintenance (Teepause) since vorhin</Code>
|
||||||
|
<Out> Tasks: 1 (Wasser kocht)</Out>
|
||||||
|
<Cmd>sleep 300 && tee tasse.txt < kanne</Cmd>
|
||||||
|
<Out>Earl Grey, heiß.</Out>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
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.
|
||||||
|
</ErrorPage>
|
||||||
|
)
|
||||||
|
}
|
||||||
32
app/504/page.tsx
Normal file
32
app/504/page.tsx
Normal file
|
|
@ -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 (
|
||||||
|
<ErrorPage
|
||||||
|
code={504}
|
||||||
|
label="Gateway Timeout"
|
||||||
|
title="Der Server denkt noch nach."
|
||||||
|
terminal={
|
||||||
|
<>
|
||||||
|
<Cmd>curl --max-time 60 http://backend/</Cmd>
|
||||||
|
<Err>curl: (28) Operation timed out after 60000 milliseconds</Err>
|
||||||
|
<Cmd>cat jobkarte.jcl</Cmd>
|
||||||
|
<Code>//JOB504 JOB (1976),'SEITE',CLASS=A</Code>
|
||||||
|
<Out>JOB 504 IN WARTESCHLANGE – POSITION 42</Out>
|
||||||
|
<Out>VORAUSSICHTLICHE AUSGABE: MORGEN FRÜH AM SCHALTER</Out>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
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.
|
||||||
|
</ErrorPage>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
import Link from 'next/link'
|
import { ErrorPage, Cmd, Out, Err, Code } from '@/components/error-page'
|
||||||
import { ArrowLeft, FolderGit2 } from 'lucide-react'
|
|
||||||
import { Button } from '@/components/ui/button'
|
|
||||||
import { RequestedPath } from '@/components/requested-path'
|
import { RequestedPath } from '@/components/requested-path'
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
|
|
@ -11,117 +9,28 @@ export const metadata = {
|
||||||
|
|
||||||
export default function NotFound() {
|
export default function NotFound() {
|
||||||
return (
|
return (
|
||||||
<main className="relative min-h-screen flex flex-col overflow-hidden">
|
<ErrorPage
|
||||||
{/* Fly.io-style gradient mesh background (dark mode only) */}
|
code={404}
|
||||||
<div className="absolute inset-0 bg-gradient-mesh hidden dark:block" />
|
label="Seite nicht gefunden"
|
||||||
|
title="Diese Seite ist vom Lochkartenstapel gefallen."
|
||||||
{/* Subtle grid overlay */}
|
terminal={
|
||||||
<div
|
<>
|
||||||
className="absolute inset-0 opacity-[0.02] dark:opacity-[0.04]"
|
<Cmd>cd <RequestedPath /></Cmd>
|
||||||
style={{
|
<Out>bash: cd: <RequestedPath />: Datei oder Verzeichnis nicht gefunden</Out>
|
||||||
backgroundImage: `
|
<Cmd>find / -name "seite" 2>/dev/null</Cmd>
|
||||||
linear-gradient(to right, currentColor 1px, transparent 1px),
|
<Cmd>cat seite.f</Cmd>
|
||||||
linear-gradient(to bottom, currentColor 1px, transparent 1px)
|
<Out>C GESTANZT 1976 – STAPEL LEIDER RUNTERGEFALLEN</Out>
|
||||||
`,
|
<Code> GO TO 404</Code>
|
||||||
backgroundSize: '60px 60px',
|
<Cmd>sudo !!</Cmd>
|
||||||
}}
|
<Err>jamulix ist nicht in der sudoers-Datei. Dieser Vorfall wird gemeldet.</Err>
|
||||||
/>
|
</>
|
||||||
|
}
|
||||||
{/* Logo */}
|
tip={<>Tipp: Auch nach 50 Jahren hilft <span className="text-accent">ls</span> meistens weiter.</>}
|
||||||
<div className="relative max-w-6xl w-full mx-auto px-6 lg:px-8">
|
|
||||||
<div className="flex items-center h-16 lg:h-20">
|
|
||||||
<Link
|
|
||||||
href="/"
|
|
||||||
className="font-mono text-lg tracking-tight text-foreground hover:text-accent transition-colors"
|
|
||||||
>
|
>
|
||||||
Jamulix
|
Seit 50 Jahren Code – und trotzdem verlegt man gelegentlich etwas.
|
||||||
</Link>
|
Die gesuchte Seite existiert nicht, nicht mehr oder noch nicht.
|
||||||
</div>
|
Vielleicht war auch nur ein übereifriges{' '}
|
||||||
</div>
|
<code className="font-mono text-sm text-accent">rm -rf</code> am Werk.
|
||||||
|
</ErrorPage>
|
||||||
<div className="relative flex-1 flex items-center">
|
|
||||||
<div className="max-w-6xl w-full mx-auto px-6 lg:px-8 py-12 lg:py-20">
|
|
||||||
<div className="grid lg:grid-cols-12 gap-12 lg:gap-16 items-center">
|
|
||||||
{/* Text Content */}
|
|
||||||
<div className="lg:col-span-6 space-y-8">
|
|
||||||
<div>
|
|
||||||
<span className="inline-block font-mono text-xs tracking-wider text-accent uppercase mb-4">
|
|
||||||
Fehler 404 · Seite nicht gefunden
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<p
|
|
||||||
aria-hidden="true"
|
|
||||||
className="font-serif font-bold text-8xl sm:text-9xl leading-none tracking-tighter text-foreground/90 dark:bg-gradient-to-r dark:from-cyan-400 dark:to-violet-400 dark:bg-clip-text dark:text-transparent"
|
|
||||||
>
|
|
||||||
404
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h1 className="mt-6 font-serif text-3xl sm:text-4xl lg:text-5xl leading-[1.1] tracking-tight text-balance">
|
|
||||||
Diese Seite ist vom Lochkartenstapel gefallen.
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p className="text-lg text-muted-foreground leading-relaxed max-w-xl">
|
|
||||||
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{' '}
|
|
||||||
<code className="font-mono text-sm text-accent">rm -rf</code>{' '}
|
|
||||||
am Werk.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="flex flex-col sm:flex-row gap-4">
|
|
||||||
<Button asChild size="lg" className="group">
|
|
||||||
<Link href="/">
|
|
||||||
<ArrowLeft className="mr-2 size-4 transition-transform group-hover:-translate-x-0.5" />
|
|
||||||
cd ~ — Zur Startseite
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
<Button asChild variant="outline" size="lg" className="group">
|
|
||||||
<Link href="/#projekte">
|
|
||||||
<FolderGit2 className="mr-2 size-4" />
|
|
||||||
Projekte ansehen
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Terminal */}
|
|
||||||
<div className="lg:col-span-6">
|
|
||||||
<div className="rounded-lg border border-border bg-card shadow-xl dark:shadow-[0_0_40px_oklch(0.70_0.22_280/0.3)] overflow-hidden">
|
|
||||||
<div className="flex items-center gap-2 px-4 py-3 border-b border-border bg-muted/50">
|
|
||||||
<span className="size-3 rounded-full bg-red-500/80" />
|
|
||||||
<span className="size-3 rounded-full bg-yellow-500/80" />
|
|
||||||
<span className="size-3 rounded-full bg-green-500/80" />
|
|
||||||
<span className="ml-3 font-mono text-xs text-muted-foreground">
|
|
||||||
jamulix@jamulix.de: ~
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<pre className="p-5 sm:p-6 font-mono text-xs sm:text-sm leading-relaxed whitespace-pre-wrap text-card-foreground">
|
|
||||||
<span className="text-accent">$</span> cd <RequestedPath />{'\n'}
|
|
||||||
<span className="text-muted-foreground">
|
|
||||||
bash: cd: <RequestedPath />: Datei oder Verzeichnis nicht gefunden
|
|
||||||
</span>{'\n'}
|
|
||||||
<span className="text-accent">$</span> find / -name "seite" 2>/dev/null{'\n'}
|
|
||||||
<span className="text-accent">$</span> cat seite.f{'\n'}
|
|
||||||
<span className="text-muted-foreground">C GESTANZT 1976 – STAPEL LEIDER RUNTERGEFALLEN</span>{'\n'}
|
|
||||||
<span className="text-cyan-600 dark:text-cyan-400"> GO TO 404</span>{'\n'}
|
|
||||||
<span className="text-accent">$</span> sudo !!{'\n'}
|
|
||||||
<span className="text-destructive dark:text-red-400">
|
|
||||||
jamulix ist nicht in der sudoers-Datei. Dieser Vorfall wird gemeldet.
|
|
||||||
</span>{'\n'}
|
|
||||||
<span className="text-accent">$</span>{' '}
|
|
||||||
<span className="inline-block w-2 h-4 align-middle bg-foreground animate-pulse" />
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p className="mt-4 font-mono text-xs text-muted-foreground text-center lg:text-left">
|
|
||||||
Tipp: Auch nach 50 Jahren hilft <span className="text-accent">ls</span> meistens weiter.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
157
components/error-page.tsx
Normal file
157
components/error-page.tsx
Normal file
|
|
@ -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 <pre> */
|
||||||
|
|
||||||
|
export function Cmd({ children }: { children: ReactNode }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span className="text-accent">$</span> {children}{'\n'}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Out({ children }: { children: ReactNode }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span className="text-muted-foreground">{children}</span>{'\n'}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Err({ children }: { children: ReactNode }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span className="text-destructive dark:text-red-400">{children}</span>{'\n'}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Code({ children }: { children: ReactNode }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span className="text-cyan-600 dark:text-cyan-400">{children}</span>{'\n'}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Cursor() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span className="text-accent">$</span>{' '}
|
||||||
|
<span className="inline-block w-2 h-4 align-middle bg-foreground animate-pulse" />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<main className="relative min-h-screen flex flex-col overflow-hidden">
|
||||||
|
{/* Fly.io-style gradient mesh background (dark mode only) */}
|
||||||
|
<div className="absolute inset-0 bg-gradient-mesh hidden dark:block" />
|
||||||
|
|
||||||
|
{/* Subtle grid overlay */}
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 opacity-[0.02] dark:opacity-[0.04]"
|
||||||
|
style={{
|
||||||
|
backgroundImage: `
|
||||||
|
linear-gradient(to right, currentColor 1px, transparent 1px),
|
||||||
|
linear-gradient(to bottom, currentColor 1px, transparent 1px)
|
||||||
|
`,
|
||||||
|
backgroundSize: '60px 60px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Logo */}
|
||||||
|
<div className="relative max-w-6xl w-full mx-auto px-6 lg:px-8">
|
||||||
|
<div className="flex items-center h-16 lg:h-20">
|
||||||
|
<Link
|
||||||
|
href="/"
|
||||||
|
className="font-mono text-lg tracking-tight text-foreground hover:text-accent transition-colors"
|
||||||
|
>
|
||||||
|
Jamulix
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative flex-1 flex items-center">
|
||||||
|
<div className="max-w-6xl w-full mx-auto px-6 lg:px-8 py-12 lg:py-20">
|
||||||
|
<div className="grid lg:grid-cols-12 gap-12 lg:gap-16 items-center">
|
||||||
|
{/* Text Content */}
|
||||||
|
<div className="lg:col-span-6 space-y-8">
|
||||||
|
<div>
|
||||||
|
<span className="inline-block font-mono text-xs tracking-wider text-accent uppercase mb-4">
|
||||||
|
Fehler {code} · {label}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<p
|
||||||
|
aria-hidden="true"
|
||||||
|
className="font-serif font-bold text-8xl sm:text-9xl leading-none tracking-tighter text-foreground/90 dark:bg-gradient-to-r dark:from-cyan-400 dark:to-violet-400 dark:bg-clip-text dark:text-transparent"
|
||||||
|
>
|
||||||
|
{code}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h1 className="mt-6 font-serif text-3xl sm:text-4xl lg:text-5xl leading-[1.1] tracking-tight text-balance">
|
||||||
|
{title}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-lg text-muted-foreground leading-relaxed max-w-xl">
|
||||||
|
{children}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex flex-col sm:flex-row gap-4">
|
||||||
|
<Button asChild size="lg" className="group">
|
||||||
|
<Link href="/">
|
||||||
|
<ArrowLeft className="mr-2 size-4 transition-transform group-hover:-translate-x-0.5" />
|
||||||
|
cd ~ — Zur Startseite
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
<Button asChild variant="outline" size="lg" className="group">
|
||||||
|
<Link href="/#projekte">
|
||||||
|
<FolderGit2 className="mr-2 size-4" />
|
||||||
|
Projekte ansehen
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Terminal */}
|
||||||
|
<div className="lg:col-span-6">
|
||||||
|
<div className="rounded-lg border border-border bg-card shadow-xl dark:shadow-[0_0_40px_oklch(0.70_0.22_280/0.3)] overflow-hidden">
|
||||||
|
<div className="flex items-center gap-2 px-4 py-3 border-b border-border bg-muted/50">
|
||||||
|
<span className="size-3 rounded-full bg-red-500/80" />
|
||||||
|
<span className="size-3 rounded-full bg-yellow-500/80" />
|
||||||
|
<span className="size-3 rounded-full bg-green-500/80" />
|
||||||
|
<span className="ml-3 font-mono text-xs text-muted-foreground">
|
||||||
|
jamulix@jamulix.de: ~
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<pre className="p-5 sm:p-6 font-mono text-xs sm:text-sm leading-relaxed whitespace-pre-wrap text-card-foreground">
|
||||||
|
{terminal}
|
||||||
|
<Cursor />
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="mt-4 font-mono text-xs text-muted-foreground text-center lg:text-left">
|
||||||
|
{tip}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
LOCAL_DIR="/home/dschlueter/neue_jamulix.de_landing_page/jamulix.de_webpage_2"
|
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"
|
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 rm -f /var/www/html/index.html
|
||||||
sudo ln -s landingpage/index.html /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"
|
echo "Kopiere Fehlerseiten: landingpage/<code>.html → <code>.html"
|
||||||
sudo cp landingpage/404.html /var/www/html/404.html
|
for c in $ERROR_PAGES; do
|
||||||
sudo chown www-data:www-data /var/www/html/404.html
|
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/
|
sudo chown -R www-data:www-data /var/www/html/landingpage/
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ REMOTE_HOST="jamulix.de"
|
||||||
REMOTE_DIR="/var/www/jamulix.de/html"
|
REMOTE_DIR="/var/www/jamulix.de/html"
|
||||||
LOCAL_DIR="/home/dschlueter/neue_jamulix.de_landing_page/jamulix.de_webpage_2"
|
LOCAL_DIR="/home/dschlueter/neue_jamulix.de_landing_page/jamulix.de_webpage_2"
|
||||||
SSH_SOCKET="/tmp/ssh-jamulix-deploy-$$"
|
SSH_SOCKET="/tmp/ssh-jamulix-deploy-$$"
|
||||||
|
ERROR_PAGES="401 403 404 500 502 503 504"
|
||||||
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
ssh -S "$SSH_SOCKET" -O exit "$REMOTE_USER@$REMOTE_HOST" 2>/dev/null || true
|
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"
|
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'"
|
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"
|
echo "Kopiere Fehlerseiten: landingpage/<code>.html → <code>.html"
|
||||||
ssh -S "$SSH_SOCKET" "$REMOTE_USER@$REMOTE_HOST" "cp '$REMOTE_DIR/landingpage/404.html' '$REMOTE_DIR/404.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 ""
|
||||||
echo "Deployment abgeschlossen: https://jamulix.de"
|
echo "Deployment abgeschlossen: https://jamulix.de"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue