feat(ui): Serverseitig gerenderte Oberflaeche ohne Build-Schritt

Eigenes CSS statt Framework, Vanilla-JavaScript, heller und dunkler
Modus. Ohne JavaScript bleibt alles bedienbar; das Liniendiagramm
faellt auf eine selbst gezeichnete Balkendarstellung zurueck, wenn das
CDN nicht erreichbar ist.

Chart.js ist auf eine Version festgelegt und per Subresource Integrity
an seinen Hash gebunden.

Die Domain-Seite enthaelt eine Schritt-fuer-Schritt-Anleitung fuer DNS,
Hosting-Panel und Zertifikat samt Fehlersuche.
This commit is contained in:
2026-08-03 18:11:40 +02:00
parent 7238e7a937
commit fabaef1f2b
19 changed files with 2378 additions and 0 deletions
+103
View File
@@ -0,0 +1,103 @@
<?php
/**
* @var array{links:int,clicks:int,active:int} $summary
* @var list<array<string,mixed>> $recentLinks
* @var list<array<string,mixed>> $topLinks
* @var list<array{date:string,clicks:int}> $series
* @var int $domainCount
* @var App\Config $config
*/
use App\Repository\LinkRepository;
use App\Support\ShortDomain;
$fallbackBase = rtrim($config->baseUrl(), '/');
?>
<div class="page-head">
<div>
<h1>Übersicht</h1>
<p class="muted">Alle Kurzlinks und Klicks auf einen Blick.</p>
</div>
<a href="<?= $e($url('/admin/links/new')) ?>" class="btn btn--primary">Neuer Kurzlink</a>
</div>
<div class="stats-grid">
<div class="stat">
<span class="stat__label">Kurzlinks</span>
<span class="stat__value"><?= (int) $summary['links'] ?></span>
</div>
<div class="stat">
<span class="stat__label">davon aktiv</span>
<span class="stat__value"><?= (int) $summary['active'] ?></span>
</div>
<div class="stat">
<span class="stat__label">Klicks gesamt</span>
<span class="stat__value"><?= number_format((int) $summary['clicks'], 0, ',', '.') ?></span>
</div>
<div class="stat">
<span class="stat__label">Klicks (30 Tage)</span>
<span class="stat__value"><?= number_format(array_sum(array_column($series, 'clicks')), 0, ',', '.') ?></span>
</div>
<div class="stat">
<span class="stat__label">Domains</span>
<span class="stat__value">
<a href="<?= $e($url('/admin/domains')) ?>"><?= (int) $domainCount ?></a>
</span>
</div>
</div>
<section class="card">
<h2>Klicks der letzten 30 Tage</h2>
<div class="chart" data-chart="line" data-series="<?= $e(json_encode($series, JSON_UNESCAPED_SLASHES)) ?>">
<canvas height="120" aria-label="Liniendiagramm der Klicks pro Tag"></canvas>
<noscript><p class="muted">Für das Diagramm wird JavaScript benötigt.</p></noscript>
</div>
</section>
<div class="grid grid--2">
<section class="card">
<h2>Zuletzt angelegt</h2>
<?php if ($recentLinks === []): ?>
<p class="muted">Noch keine Kurzlinks vorhanden.
<a href="<?= $e($url('/admin/links/new')) ?>">Jetzt den ersten anlegen.</a></p>
<?php else: ?>
<ul class="list">
<?php foreach ($recentLinks as $link): ?>
<li class="list__item">
<div class="list__main">
<a class="mono" href="<?= $e($url('/admin/links/' . $link['id'] . '/stats')) ?>"><?php
if ($domainCount > 1 && $link['domain_base_url'] !== null): ?><span class="linkcopy__domain"><?=
$e(ShortDomain::displayName((string) $link['domain_base_url'])) ?></span><?php
endif; ?>/<?= $e($link['slug']) ?></a>
<span class="muted small truncate"><?= $e($link['target_url']) ?></span>
</div>
<span class="badge"><?= (int) $link['click_count'] ?> Klicks</span>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</section>
<section class="card">
<h2>Meistgeklickt</h2>
<?php if ($topLinks === [] || (int) $summary['clicks'] === 0): ?>
<p class="muted">Noch keine Klicks erfasst.</p>
<?php else: ?>
<ul class="list">
<?php foreach ($topLinks as $link): ?>
<li class="list__item">
<div class="list__main">
<a class="mono" href="<?= $e($url('/admin/links/' . $link['id'] . '/stats')) ?>"><?php
if ($domainCount > 1 && $link['domain_base_url'] !== null): ?><span class="linkcopy__domain"><?=
$e(ShortDomain::displayName((string) $link['domain_base_url'])) ?></span><?php
endif; ?>/<?= $e($link['slug']) ?></a>
<span class="muted small truncate"><?= $e($link['target_url']) ?></span>
</div>
<span class="badge"><?= (int) $link['click_count'] ?> Klicks</span>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</section>
</div>