Files
urlshort-qr-gen/templates/links/index.php
T
florian.wilde fabaef1f2b 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.
2026-08-03 18:11:40 +02:00

141 lines
6.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* @var list<array<string,mixed>> $links
* @var int $total
* @var string $search
* @var string $sort
* @var list<array<string,mixed>> $domains
* @var int|null $domainFilter
* @var App\Config $config
* @var App\Security\Csrf $csrf
*/
use App\Repository\LinkRepository;
use App\Support\ShortDomain;
$fallbackBase = rtrim($config->baseUrl(), '/');
$multiDomain = count($domains) > 1;
$sortOptions = [
'created_desc' => 'Neueste zuerst',
'created_asc' => 'Älteste zuerst',
'clicks_desc' => 'Meiste Klicks',
'clicks_asc' => 'Wenigste Klicks',
'slug_asc' => 'Slug (A–Z)',
];
?>
<div class="page-head">
<div>
<h1>Kurzlinks</h1>
<p class="muted"><?= (int) $total ?> Eintrag/Einträge<?= $search !== '' ? ' für „' . $e($search) . '“' : '' ?>.</p>
</div>
<a href="<?= $e($url('/admin/links/new')) ?>" class="btn btn--primary">Neuer Kurzlink</a>
</div>
<form class="toolbar" method="get" action="<?= $e($url('/admin/links')) ?>">
<input type="search" name="q" value="<?= $e($search) ?>" placeholder="Slug, Titel oder Ziel-URL suchen…"
aria-label="Suche">
<?php if ($multiDomain): ?>
<select name="domain" aria-label="Domain">
<option value="0">Alle Domains</option>
<?php foreach ($domains as $domain): ?>
<option value="<?= (int) $domain['id'] ?>" <?= $domainFilter === (int) $domain['id'] ? 'selected' : '' ?>>
<?= $e(ShortDomain::displayName((string) $domain['base_url'])) ?>
</option>
<?php endforeach; ?>
</select>
<?php endif; ?>
<select name="sort" aria-label="Sortierung">
<?php foreach ($sortOptions as $value => $label): ?>
<option value="<?= $e($value) ?>" <?= $sort === $value ? 'selected' : '' ?>><?= $e($label) ?></option>
<?php endforeach; ?>
</select>
<button type="submit" class="btn">Anwenden</button>
<?php if ($search !== '' || $sort !== 'created_desc' || $domainFilter !== null): ?>
<a class="btn btn--ghost" href="<?= $e($url('/admin/links')) ?>">Zurücksetzen</a>
<?php endif; ?>
</form>
<?php if ($links === []): ?>
<div class="card empty">
<h2>Noch nichts hier</h2>
<p class="muted">Lege deinen ersten Kurzlink an – anschließend kannst du direkt einen QR-Code dazu gestalten.</p>
<a href="<?= $e($url('/admin/links/new')) ?>" class="btn btn--primary">Kurzlink anlegen</a>
</div>
<?php else: ?>
<div class="card card--flush">
<div class="table-wrap">
<table class="table">
<thead>
<tr>
<th scope="col">Kurzlink</th>
<th scope="col">Ziel</th>
<th scope="col" class="num">Klicks</th>
<th scope="col">Erstellt</th>
<th scope="col"><span class="sr-only">Aktionen</span></th>
</tr>
</thead>
<tbody>
<?php foreach ($links as $link):
$shortUrl = LinkRepository::shortUrlOf($link, $fallbackBase);
$active = (int) $link['is_active'] === 1;
$domainName = $link['domain_base_url'] !== null
? ShortDomain::displayName((string) $link['domain_base_url'])
: '';
?>
<tr class="<?= $active ? '' : 'is-inactive' ?>">
<td>
<div class="cell-stack">
<button type="button" class="linkcopy mono" data-copy="<?= $e($shortUrl) ?>"
title="In die Zwischenablage kopieren"><?php
if ($multiDomain && $domainName !== ''): ?><span class="linkcopy__domain"><?= $e($domainName) ?></span><?php
endif; ?>/<?= $e($link['slug']) ?></button>
<?php if (($link['title'] ?? '') !== ''): ?>
<span class="muted small"><?= $e($link['title']) ?></span>
<?php endif; ?>
<?php if (!$active): ?>
<span class="badge badge--muted">deaktiviert</span>
<?php endif; ?>
</div>
</td>
<td class="truncate-cell">
<a href="<?= $e($link['target_url']) ?>" rel="noopener noreferrer nofollow" target="_blank"
class="muted small"><?= $e($link['target_url']) ?></a>
</td>
<td class="num">
<a href="<?= $e($url('/admin/links/' . $link['id'] . '/stats')) ?>">
<?= number_format((int) $link['click_count'], 0, ',', '.') ?>
</a>
</td>
<td class="muted small nowrap">
<?= $e(date('d.m.Y', strtotime((string) $link['created_at'] . ' UTC') ?: time())) ?>
</td>
<td class="actions">
<a class="btn btn--sm" href="<?= $e($url('/admin/links/' . $link['id'] . '/qr')) ?>">QR-Code</a>
<a class="btn btn--sm btn--ghost" href="<?= $e($url('/admin/links/' . $link['id'] . '/stats')) ?>">Statistik</a>
<a class="btn btn--sm btn--ghost" href="<?= $e($url('/admin/links/' . $link['id'] . '/edit')) ?>">Bearbeiten</a>
<form method="post" action="<?= $e($url('/admin/links/' . $link['id'] . '/toggle')) ?>" class="inline">
<?= $csrf->field() ?>
<button type="submit" class="btn btn--sm btn--ghost">
<?= $active ? 'Deaktivieren' : 'Aktivieren' ?>
</button>
</form>
<form method="post" action="<?= $e($url('/admin/links/' . $link['id'] . '/delete')) ?>" class="inline"
data-confirm="Kurzlink <?= $e($shortUrl) ?> wirklich löschen? Die Statistik geht dabei verloren.">
<?= $csrf->field() ?>
<button type="submit" class="btn btn--sm btn--danger">Löschen</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<?php endif; ?>