The homepage showed seven hand-built JSX imitations of the netOrk UI. They are gone; every image is now a screenshot of netOrk v0.28.0 itself, taken from an anonymized copy of a production database (scripts/demo) with scripts/screenshots/capture.py and published as WebP (~630 KB for all eight). - Hero: the device inventory. Walkthrough: device detail, VLANs, the Security tab, the vulnerability triage queue (replacing the config-diff row), the dashboard and service checks (new row 6). NIS2: the audit log, filtered to what people did. - Copy follows the images: row 3 describes the security assessment, row 4 the triage queue; row 2 no longer claims corrections are always automatic; 18 widgets. Alt texts in both languages. - Also fixed on the homepage: the NIS2 teaser for Art. 21 (2e) and the container list of a deployment (three worker pools, plus Flower, registry, APT cache and the Signal gateway). - Demo tooling hardened on the real dump: secrets inside JSON (Wi-Fi keys), reverse DNS zones, glued identifiers, tens of thousands of CrowdSec addresses, a schema newer than the release (anonymize, then downgrade), MFA-enforcing roles, and click steps for view filters. - DESIGN.md: real screenshots only. PAGES.md: the six rows as they are. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
272 lines
12 KiB
TypeScript
272 lines
12 KiB
TypeScript
import { Link } from 'react-router-dom'
|
|
import {
|
|
MagnifyingGlassIcon,
|
|
ChartBarIcon,
|
|
WrenchScrewdriverIcon,
|
|
PuzzlePieceIcon,
|
|
ShieldCheckIcon,
|
|
} from '@heroicons/react/24/outline'
|
|
import { useLang } from '../context/LangContext'
|
|
import { linkify } from '../lib/glossary'
|
|
|
|
const DRIVERS = [
|
|
'Fritz!Box', 'HPE OfficeConnect', 'HPE ProCurve / Aruba', 'Linux', 'Netgear',
|
|
'OpenMediaVault', 'OpenWrt', 'OPNsense', 'Proxmox VE', 'QNAP QTS', 'Sonos',
|
|
'TP-Link JetStream', 'Yealink', 'Zyxel VMG',
|
|
]
|
|
|
|
function Screenshot({ src, label, alt }: { src: string; label: string; alt: string }) {
|
|
return (
|
|
<div className="rounded-xl border border-slate-700 overflow-hidden shadow-2xl">
|
|
<div className="flex items-center gap-1.5 border-b border-slate-700 bg-slate-800 px-4 py-2.5">
|
|
<span className="h-2.5 w-2.5 rounded-full bg-red-500/70" />
|
|
<span className="h-2.5 w-2.5 rounded-full bg-yellow-500/70" />
|
|
<span className="h-2.5 w-2.5 rounded-full bg-green-500/70" />
|
|
<span className="ml-4 text-xs text-slate-500 font-mono">{label}</span>
|
|
</div>
|
|
<img src={src} alt={alt} loading="lazy" className="block w-full" width={1600} height={1000} />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default function Home() {
|
|
const { t } = useLang()
|
|
const h = t.home
|
|
|
|
return (
|
|
<>
|
|
{/* 1 — Hero */}
|
|
<section className="py-24 md:py-32">
|
|
<div className="max-w-7xl mx-auto px-6 text-center">
|
|
<h1 className="text-4xl md:text-6xl font-bold text-slate-100 leading-tight mb-6">
|
|
{h.hero.line1}<br />
|
|
<span className="text-sky-400">{h.hero.line2}</span>
|
|
</h1>
|
|
<p className="text-base text-slate-400 leading-relaxed max-w-2xl mx-auto mb-10">
|
|
{linkify(h.hero.sub)}
|
|
</p>
|
|
<div className="flex flex-wrap gap-4 justify-center">
|
|
<a
|
|
href="mailto:christian@manivong.de"
|
|
className="inline-flex items-center gap-2 px-6 py-3 rounded-lg bg-sky-600 hover:bg-sky-500 text-white font-medium transition-colors"
|
|
>
|
|
{h.hero.cta1}
|
|
</a>
|
|
<Link
|
|
to="/features"
|
|
className="inline-flex items-center gap-2 px-6 py-3 rounded-lg border border-slate-700 hover:border-slate-500 text-slate-300 hover:text-slate-100 transition-colors"
|
|
>
|
|
{h.hero.cta2}
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
<div className="max-w-7xl mx-auto px-6 mt-16">
|
|
<Screenshot src="/screenshots/devices.webp" label="netork / devices" alt={h.shotAlt.devices} />
|
|
</div>
|
|
</section>
|
|
|
|
<div className="border-t border-slate-800" />
|
|
|
|
{/* 2 — Problem Statement */}
|
|
<section className="py-16">
|
|
<div className="max-w-3xl mx-auto px-6 text-center">
|
|
<p className="text-base text-slate-400 leading-relaxed">{linkify(h.problem)}</p>
|
|
</div>
|
|
</section>
|
|
|
|
<div className="border-t border-slate-800" />
|
|
|
|
{/* 3 — Core Capabilities */}
|
|
<section className="py-24">
|
|
<div className="max-w-7xl mx-auto px-6">
|
|
<div className="grid md:grid-cols-3 gap-6">
|
|
<div className="rounded-xl border border-slate-800 bg-slate-900 p-6">
|
|
<div className="mb-4 flex h-10 w-10 items-center justify-center rounded-lg bg-sky-600/10">
|
|
<MagnifyingGlassIcon className="h-5 w-5 text-sky-400" />
|
|
</div>
|
|
<h3 className="mb-2 text-lg font-semibold text-slate-100">{h.cap1.title}</h3>
|
|
<p className="text-sm text-slate-400 leading-relaxed">{linkify(h.cap1.body)}</p>
|
|
</div>
|
|
<div className="rounded-xl border border-slate-800 bg-slate-900 p-6">
|
|
<div className="mb-4 flex h-10 w-10 items-center justify-center rounded-lg bg-sky-600/10">
|
|
<ChartBarIcon className="h-5 w-5 text-sky-400" />
|
|
</div>
|
|
<h3 className="mb-2 text-lg font-semibold text-slate-100">{h.cap2.title}</h3>
|
|
<p className="text-sm text-slate-400 leading-relaxed">{linkify(h.cap2.body)}</p>
|
|
</div>
|
|
<div className="rounded-xl border border-slate-800 bg-slate-900 p-6">
|
|
<div className="mb-4 flex h-10 w-10 items-center justify-center rounded-lg bg-sky-600/10">
|
|
<WrenchScrewdriverIcon className="h-5 w-5 text-sky-400" />
|
|
</div>
|
|
<h3 className="mb-2 text-lg font-semibold text-slate-100">{h.cap3.title}</h3>
|
|
<p className="text-sm text-slate-400 leading-relaxed">{linkify(h.cap3.body)}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<div className="border-t border-slate-800" />
|
|
|
|
{/* 4 — Driver Grid */}
|
|
<section className="py-24">
|
|
<div className="max-w-7xl mx-auto px-6 text-center">
|
|
<h2 className="text-2xl md:text-3xl font-bold text-slate-100 mb-4">{h.driversHeading}</h2>
|
|
<p className="text-base text-slate-400 leading-relaxed max-w-xl mx-auto mb-10">{linkify(h.driversSub)}</p>
|
|
<div className="flex flex-wrap justify-center gap-3">
|
|
{DRIVERS.map((d) => (
|
|
<span key={d} className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full border border-slate-700 bg-slate-900 text-xs font-medium text-slate-300">
|
|
{d}
|
|
</span>
|
|
))}
|
|
</div>
|
|
<div className="mt-8">
|
|
<Link to="/drivers" className="text-sky-400 hover:text-sky-300 transition-colors text-sm font-medium">
|
|
{h.driversLink}
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<div className="border-t border-slate-800" />
|
|
|
|
{/* 5 — Screenshot Walkthrough */}
|
|
<section className="py-24">
|
|
<div className="max-w-7xl mx-auto px-6 space-y-24">
|
|
<div className="grid md:grid-cols-2 gap-12 items-center">
|
|
<div>
|
|
<h2 className="text-2xl md:text-3xl font-bold text-slate-100 mb-4">{h.screenshot1.heading}</h2>
|
|
<p className="text-base text-slate-400 leading-relaxed">{linkify(h.screenshot1.body)}</p>
|
|
</div>
|
|
<Screenshot src="/screenshots/device-detail.webp" label="netork / devices / ap-floor1" alt={h.shotAlt.deviceDetail} />
|
|
</div>
|
|
<div className="grid md:grid-cols-2 gap-12 items-center">
|
|
<div className="md:order-2">
|
|
<h2 className="text-2xl md:text-3xl font-bold text-slate-100 mb-4">{h.screenshot2.heading}</h2>
|
|
<p className="text-base text-slate-400 leading-relaxed">{linkify(h.screenshot2.body)}</p>
|
|
</div>
|
|
<Screenshot src="/screenshots/vlans.webp" label="netork / vlans" alt={h.shotAlt.vlans} />
|
|
</div>
|
|
<div className="grid md:grid-cols-2 gap-12 items-center">
|
|
<div>
|
|
<h2 className="text-2xl md:text-3xl font-bold text-slate-100 mb-4">{h.screenshot3.heading}</h2>
|
|
<p className="text-base text-slate-400 leading-relaxed">{linkify(h.screenshot3.body)}</p>
|
|
</div>
|
|
<Screenshot src="/screenshots/device-security.webp" label="netork / devices / proxy-01 / security" alt={h.shotAlt.deviceSecurity} />
|
|
</div>
|
|
<div className="grid md:grid-cols-2 gap-12 items-center">
|
|
<div className="md:order-2">
|
|
<h2 className="text-2xl md:text-3xl font-bold text-slate-100 mb-4">{h.screenshot4.heading}</h2>
|
|
<p className="text-base text-slate-400 leading-relaxed">{linkify(h.screenshot4.body)}</p>
|
|
</div>
|
|
<Screenshot src="/screenshots/vulnerabilities.webp" label="netork / vulnerabilities" alt={h.shotAlt.vulnerabilities} />
|
|
</div>
|
|
<div className="grid md:grid-cols-2 gap-12 items-center">
|
|
<div>
|
|
<h2 className="text-2xl md:text-3xl font-bold text-slate-100 mb-4">{h.screenshot5.heading}</h2>
|
|
<p className="text-base text-slate-400 leading-relaxed">{linkify(h.screenshot5.body)}</p>
|
|
</div>
|
|
<Screenshot src="/screenshots/dashboard.webp" label="netork / dashboard" alt={h.shotAlt.dashboard} />
|
|
</div>
|
|
<div className="grid md:grid-cols-2 gap-12 items-center">
|
|
<div className="md:order-2">
|
|
<h2 className="text-2xl md:text-3xl font-bold text-slate-100 mb-4">{h.screenshot6.heading}</h2>
|
|
<p className="text-base text-slate-400 leading-relaxed">{linkify(h.screenshot6.body)}</p>
|
|
</div>
|
|
<Screenshot src="/screenshots/service-checks.webp" label="netork / monitoring / checks" alt={h.shotAlt.serviceChecks} />
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<div className="border-t border-slate-800" />
|
|
|
|
{/* 5b — NIS2 */}
|
|
<section className="py-24">
|
|
<div className="max-w-7xl mx-auto px-6">
|
|
<div className="grid md:grid-cols-2 gap-12 items-center">
|
|
<div>
|
|
<p className="text-xs font-semibold tracking-widest uppercase text-sky-500 mb-3">{h.nis2Label}</p>
|
|
<h2 className="text-2xl md:text-3xl font-bold text-slate-100 mb-4">{h.nis2Heading}</h2>
|
|
<p className="text-base text-slate-400 leading-relaxed mb-8">{linkify(h.nis2Body)}</p>
|
|
<div className="mb-8">
|
|
<Link to="/nis2" className="inline-flex items-center gap-2 text-sky-400 hover:text-sky-300 transition-colors text-sm font-medium">
|
|
<ShieldCheckIcon className="h-4 w-4" />
|
|
{h.nis2Link}
|
|
</Link>
|
|
</div>
|
|
<div className="space-y-4">
|
|
{h.nis2Items.map((m) => (
|
|
<div key={m.art} className="flex gap-4">
|
|
<span className="mt-0.5 shrink-0 w-28 text-xs font-mono text-sky-500">{m.art}</span>
|
|
<div>
|
|
<p className="text-sm font-medium text-slate-200">{m.label}</p>
|
|
<p className="text-xs text-slate-500 mt-0.5">{linkify(m.detail)}</p>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
<Screenshot src="/screenshots/audit-log.webp" label="netork / audit-log" alt={h.shotAlt.auditLog} />
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<div className="border-t border-slate-800" />
|
|
|
|
{/* 6 — Plugin System */}
|
|
<section className="py-24">
|
|
<div className="max-w-7xl mx-auto px-6">
|
|
<div className="rounded-xl border border-slate-800 bg-slate-900 p-8 md:p-12">
|
|
<div className="mb-4 flex h-10 w-10 items-center justify-center rounded-lg bg-sky-600/10">
|
|
<PuzzlePieceIcon className="h-5 w-5 text-sky-400" />
|
|
</div>
|
|
<h2 className="text-2xl md:text-3xl font-bold text-slate-100 mb-4">{h.pluginsHeading}</h2>
|
|
<p className="text-base text-slate-400 leading-relaxed max-w-2xl mb-6">{linkify(h.pluginsBody)}</p>
|
|
<Link to="/plugins" className="text-sky-400 hover:text-sky-300 transition-colors text-sm font-medium">
|
|
{h.pluginsLink}
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<div className="border-t border-slate-800" />
|
|
|
|
{/* 7 — Deployment */}
|
|
<section className="py-24">
|
|
<div className="max-w-7xl mx-auto px-6">
|
|
<div className="grid md:grid-cols-2 gap-12 items-start">
|
|
<div>
|
|
<h2 className="text-2xl md:text-3xl font-bold text-slate-100 mb-4">{h.deployHeading}</h2>
|
|
<p className="text-base text-slate-400 leading-relaxed">{linkify(h.deployBody)}</p>
|
|
</div>
|
|
<pre className="rounded-xl border border-slate-800 bg-slate-900 p-6 font-mono text-sm text-slate-300 overflow-x-auto">
|
|
<code>{`# Clone + configure
|
|
git clone https://gitea.example.com/netork/netork.git
|
|
cp .env.example .env
|
|
# edit .env (DB URL, Redis password, secret key)
|
|
|
|
# Deploy
|
|
bash scripts/deploy.sh 192.168.1.10`}</code>
|
|
</pre>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<div className="border-t border-slate-800" />
|
|
|
|
{/* 8 — CTA */}
|
|
<section className="py-24">
|
|
<div className="max-w-7xl mx-auto px-6 text-center">
|
|
<h2 className="text-2xl md:text-3xl font-bold text-slate-100 mb-4">{h.ctaHeading}</h2>
|
|
<p className="text-base text-slate-400 leading-relaxed mb-8">{h.ctaBody}</p>
|
|
<a
|
|
href="mailto:christian@manivong.de"
|
|
className="inline-flex items-center gap-2 px-6 py-3 rounded-lg bg-sky-600 hover:bg-sky-500 text-white font-medium transition-colors"
|
|
>
|
|
{h.ctaButton}
|
|
</a>
|
|
</div>
|
|
</section>
|
|
</>
|
|
)
|
|
}
|