318 lines
23 KiB
PHP
318 lines
23 KiB
PHP
<?php
|
||
declare(strict_types=1);
|
||
/** Verwaltung der Webseite: Anmelden, Pakete veröffentlichen, Versionen entfernen. */
|
||
require_once __DIR__ . '/inc/layout.php';
|
||
require_once __DIR__ . '/inc/push.php';
|
||
|
||
session_set_cookie_params(['lifetime' => 0, 'path' => '/', 'secure' => !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off', 'httponly' => true, 'samesite' => 'Strict']);
|
||
session_name('plugin_site_admin');
|
||
session_start();
|
||
|
||
$cfg = site_config();
|
||
$error = null;
|
||
$message = null;
|
||
|
||
function admin_csrf(): string
|
||
{
|
||
return $_SESSION['csrf'] ??= bin2hex(random_bytes(24));
|
||
}
|
||
function admin_check_csrf(): bool
|
||
{
|
||
return hash_equals($_SESSION['csrf'] ?? '', (string) ($_POST['csrf'] ?? ''));
|
||
}
|
||
/** Höchstens 5 Fehlversuche pro IP in 15 Minuten. */
|
||
function admin_throttled(bool $record = false): bool
|
||
{
|
||
$file = site_storage() . '/login_attempts.json';
|
||
$data = is_file($file) ? (json_decode((string) file_get_contents($file), true) ?: []) : [];
|
||
$ip = $_SERVER['REMOTE_ADDR'] ?? '?';
|
||
$now = time();
|
||
$data[$ip] = array_values(array_filter($data[$ip] ?? [], fn($t) => $t > $now - 900));
|
||
if ($record) { $data[$ip][] = $now; file_put_contents($file, json_encode($data), LOCK_EX); }
|
||
return count($data[$ip]) >= 5;
|
||
}
|
||
|
||
$loggedIn = !empty($_SESSION['admin']);
|
||
|
||
/** PHP verwirft bei zu großem Upload das ganze Formular (kein $_POST, kein Token) – das ist keine Abmeldung. */
|
||
$tooBig = $_SERVER['REQUEST_METHOD'] === 'POST' && !$_POST && !$_FILES && (int) ($_SERVER['CONTENT_LENGTH'] ?? 0) > 0;
|
||
if ($tooBig) {
|
||
$error = 'Die Datei ist größer als die Servergrenze (post_max_size ' . ini_get('post_max_size') . ', upload_max_filesize ' . ini_get('upload_max_filesize') . '). Bitte die Grenzen erhöhen bzw. das Docker-Image neu bauen. Du bleibst angemeldet.';
|
||
}
|
||
|
||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !$tooBig) {
|
||
$action = $_POST['action'] ?? '';
|
||
if ($action === 'login') {
|
||
if ($cfg['admin_password_hash'] === '') {
|
||
$error = 'Die Verwaltung ist nicht eingerichtet: In config.php fehlt admin_password_hash.';
|
||
} elseif (admin_throttled()) {
|
||
$error = 'Zu viele Fehlversuche. Bitte in 15 Minuten erneut versuchen.';
|
||
} elseif (hash_equals((string) $cfg['admin_user'], (string) ($_POST['user'] ?? '')) && password_verify((string) ($_POST['password'] ?? ''), (string) $cfg['admin_password_hash'])) {
|
||
session_regenerate_id(true);
|
||
$_SESSION['admin'] = true;
|
||
header('Location: admin.php');
|
||
exit;
|
||
} else {
|
||
admin_throttled(true);
|
||
usleep(400000);
|
||
$error = 'Benutzername oder Passwort ist falsch.';
|
||
}
|
||
} elseif (!$loggedIn || !admin_check_csrf()) {
|
||
http_response_code(403);
|
||
$error = 'Ungültige Anfrage. Bitte neu anmelden.';
|
||
$loggedIn = false;
|
||
} elseif ($action === 'logout') {
|
||
session_destroy();
|
||
header('Location: admin.php');
|
||
exit;
|
||
} elseif ($action === 'publish') {
|
||
$zip = $_FILES['package'] ?? [];
|
||
if (($zip['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
|
||
$error = 'Upload-Fehler (Code ' . ($zip['error'] ?? '?') . '). Prüft upload_max_filesize und post_max_size.';
|
||
} elseif (strtolower(pathinfo((string) $zip['name'], PATHINFO_EXTENSION)) !== 'zip') {
|
||
$error = 'Bitte eine ZIP-Datei hochladen.';
|
||
} else {
|
||
$prev = $_FILES['preview'] ?? [];
|
||
$hasPreview = ($prev['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_OK;
|
||
$res = site_publish((string) ($_POST['type'] ?? ''), $zip['tmp_name'], $_POST, $hasPreview ? $prev['tmp_name'] : null, $hasPreview ? (string) $prev['name'] : null);
|
||
if ($res['ok']) $message = "Veröffentlicht: {$res['slug']} {$res['version']} (" . ($res['signed'] ? 'signiert' : 'NICHT signiert, kein Signierschlüssel eingerichtet') . ').';
|
||
else $error = $res['error'];
|
||
}
|
||
} elseif ($action === 'publish_app') {
|
||
$upload = $_FILES['app_file'] ?? [];
|
||
if (($upload['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
|
||
$error = 'App-Upload fehlgeschlagen (Code ' . ($upload['error'] ?? '?') . '). Prüft upload_max_filesize, post_max_size und die Servergrenze.';
|
||
} else {
|
||
$res = site_publish_app((string) ($_POST['platform'] ?? ''), (string) $upload['tmp_name'], trim((string) ($_POST['version'] ?? '')), (string) ($_POST['notes'] ?? ''));
|
||
if ($res['ok']) $message = 'App-Version ' . $res['version'] . ' veröffentlicht.';
|
||
else $error = $res['error'];
|
||
}
|
||
} elseif ($action === 'delete_app') {
|
||
$id = (int) ($_POST['id'] ?? 0);
|
||
$stmt = site_db()->prepare('SELECT platform,file FROM app_releases WHERE id = ?');
|
||
$stmt->execute([$id]);
|
||
$release = $stmt->fetch();
|
||
if ($release) {
|
||
site_db()->prepare('DELETE FROM app_releases WHERE id = ?')->execute([$id]);
|
||
@unlink(site_storage() . '/apps/' . $release['platform'] . '/' . basename($release['file']));
|
||
$message = 'App-Version entfernt.';
|
||
} else $error = 'App-Version wurde nicht gefunden.';
|
||
} elseif (in_array($action, ['push_block', 'push_unblock', 'push_delete'], true)) {
|
||
$pid = (string) ($_POST['id'] ?? '');
|
||
if (preg_match('/^[a-f0-9]{24}$/', $pid)) {
|
||
$pp = push_db();
|
||
if ($action === 'push_delete') $pp->prepare('DELETE FROM push_installs WHERE id = ?')->execute([$pid]);
|
||
else $pp->prepare('UPDATE push_installs SET blocked = ? WHERE id = ?')->execute([$action === 'push_block' ? 1 : 0, $pid]);
|
||
$message = ['push_block' => 'Installation gesperrt.', 'push_unblock' => 'Installation entsperrt.', 'push_delete' => 'Installation gelöscht (sie meldet sich beim nächsten Senden von selbst neu an).'][$action];
|
||
} else {
|
||
$error = 'Ungültige Installation.';
|
||
}
|
||
} elseif ($action === 'set_preview' || $action === 'remove_preview') {
|
||
$img = $_FILES['preview'] ?? [];
|
||
if ($action === 'set_preview' && ($img['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
|
||
$error = 'Bitte ein Bild auswählen (PNG, JPG oder WebP, höchstens 1 MB).';
|
||
} else {
|
||
$res = site_set_preview((int) ($_POST['id'] ?? 0), $action === 'set_preview' ? (string) $img['tmp_name'] : null, $action === 'set_preview' ? (string) $img['name'] : null);
|
||
if ($res['ok']) $message = $action === 'set_preview' ? 'Vorschaubild geändert. Es bleibt bei weiteren Versionen erhalten.' : 'Vorschaubild entfernt.';
|
||
else $error = $res['error'];
|
||
}
|
||
} elseif ($action === 'feature' || $action === 'unfeature') {
|
||
$message = site_set_featured((int) ($_POST['id'] ?? 0), $action === 'feature') ? ($action === 'feature' ? 'Als „Empfohlen“ markiert. DeskHub zeigt es nach der nächsten Katalog-Aktualisierung (spätestens in wenigen Minuten).' : 'Empfehlung entfernt.') : null;
|
||
$error = $message ? null : 'Eintrag nicht gefunden.';
|
||
} elseif ($action === 'delete') {
|
||
$message = site_delete_version((int) ($_POST['id'] ?? 0)) ? 'Version entfernt.' : null;
|
||
$error = $message ? null : 'Version nicht gefunden.';
|
||
}
|
||
}
|
||
|
||
$tabs = site_tabs();
|
||
$tab = (string) ($_GET['tab'] ?? 'overview');
|
||
if (!isset($tabs[$tab])) $tab = 'overview';
|
||
$csrf = $loggedIn ? admin_csrf() : '';
|
||
/** Kleines POST-Formular mit nur einem Knopf. */
|
||
function admin_btn(string $action, array $fields, string $label, string $class = 'link', string $confirm = ''): string
|
||
{
|
||
$h = '<form method="post" class="inline"' . ($confirm !== '' ? ' data-confirm="' . site_h($confirm) . '"' : '') . '><input type="hidden" name="action" value="' . site_h($action) . '"><input type="hidden" name="csrf" value="' . site_h(admin_csrf()) . '">';
|
||
foreach ($fields as $k => $v) $h .= '<input type="hidden" name="' . site_h($k) . '" value="' . site_h((string) $v) . '">';
|
||
return $h . '<button type="submit" class="' . site_h($class) . '">' . site_h($label) . '</button></form>';
|
||
}
|
||
|
||
site_header($loggedIn ? $tabs[$tab][0] . ' · Verwaltung' : 'Anmelden', $tab, $loggedIn);
|
||
?>
|
||
<?php if ($error): ?><div class="alert err"><?= site_h($error) ?></div><?php endif; ?>
|
||
<?php if ($message): ?><div class="alert okbox"><?= site_h($message) ?></div><?php endif; ?>
|
||
|
||
<?php if (!$loggedIn): ?>
|
||
<form method="post" class="card form narrow">
|
||
<div><h1 class="h1-sm">Anmelden</h1><p class="sub">Verwaltung der DeskHub-Plugins, Themes und Apps.</p></div>
|
||
<input type="hidden" name="action" value="login">
|
||
<label>Benutzername<input type="text" name="user" autocomplete="username" required autofocus></label>
|
||
<label>Passwort<input type="password" name="password" autocomplete="current-password" required></label>
|
||
<button type="submit">Anmelden</button>
|
||
</form>
|
||
<?php else:
|
||
$signed = site_public_key() !== '';
|
||
$appReleases = site_app_releases();
|
||
$latestApp = array_fill_keys(array_keys(site_app_platforms()), null);
|
||
foreach ($appReleases as $r) if (array_key_exists($r['platform'], $latestApp) && $latestApp[$r['platform']] === null) $latestApp[$r['platform']] = $r;
|
||
$pushSa = push_service_account();
|
||
$pushRows = push_db()->query('SELECT * FROM push_installs ORDER BY COALESCE(last_used_at, created_at) DESC LIMIT 200')->fetchAll();
|
||
$plugins = site_items('plugin');
|
||
$themes = site_items('theme');
|
||
?>
|
||
|
||
<?php if ($tab === 'overview'): ?>
|
||
<div class="pagehead"><div><h1>Übersicht</h1><p class="sub">Alles Wichtige auf einen Blick: veröffentlichte Pakete, aktuelle App-Versionen und der Zustand der Dienste.</p></div></div>
|
||
<div class="stats">
|
||
<a class="stat" href="admin.php?tab=packages"><div class="k">Plugins</div><div class="v"><?= count($plugins) ?></div><div class="s">veröffentlicht</div></a>
|
||
<a class="stat" href="admin.php?tab=packages"><div class="k">Themes</div><div class="v"><?= count($themes) ?></div><div class="s">veröffentlicht</div></a>
|
||
<a class="stat" href="admin.php?tab=apps"><div class="k">Windows</div><div class="v"><?= $latestApp['windows'] ? site_h($latestApp['windows']['version']) : '–' ?></div><div class="s">aktuelle App-Version</div></a>
|
||
<a class="stat" href="admin.php?tab=apps"><div class="k">Android</div><div class="v"><?= $latestApp['android'] ? site_h($latestApp['android']['version']) : '–' ?></div><div class="s">aktuelle App-Version</div></a>
|
||
<a class="stat" href="admin.php?tab=push"><div class="k">Push-Geräte</div><div class="v"><?= count($pushRows) ?></div><div class="s">angemeldete Installationen</div></a>
|
||
</div>
|
||
<div class="cols">
|
||
<section class="card">
|
||
<header><div><h2>Systemstatus</h2><p>Einrichtung dieser Webseite</p></div></header>
|
||
<div class="status <?= $signed ? 'ok' : 'warn' ?>"><i class="dot"></i><div><strong>Signierschlüssel</strong><span><?= $signed ? 'Eingerichtet – neue Pakete werden automatisch signiert.' : 'Fehlt (signing_key_file in config.php) – Pakete werden unsigniert veröffentlicht.' ?></span></div></div>
|
||
<div class="status <?= $pushSa ? 'ok' : 'warn' ?>"><i class="dot"></i><div><strong>Push-Dienst</strong><span><?= $pushSa ? 'Firebase-Projekt ' . site_h($pushSa['project_id']) . ' eingebaut.' : 'Kein Firebase-Dienstkonto eingebaut – Push-Nachrichten sind nicht möglich.' ?></span></div></div>
|
||
<?php $openAccess = (string) site_config()['access_key'] === ''; ?>
|
||
<div class="status <?= $openAccess ? 'warn' : 'ok' ?>"><i class="dot"></i><div><strong>Zugangsschlüssel für Katalog und Downloads</strong><span><?= $openAccess ? 'Nicht gesetzt – Katalog, Pakete und App-Dateien sind für jeden abrufbar. Image mit Zugangsschlüssel bauen (docker-build.ps1 erzeugt ihn).' : 'Gesetzt – nur DeskHub-Server mit dem Schlüssel dürfen Pakete und Apps laden.' ?></span></div></div>
|
||
</section>
|
||
<section class="card">
|
||
<header><div><h2>Schnellzugriff</h2><p>Häufige Aufgaben</p></div></header>
|
||
<p class="actions-row">
|
||
<a class="btn" href="admin.php?tab=packages">Plugin oder Theme hochladen</a>
|
||
<a class="btn ghost" href="admin.php?tab=apps">App-Version veröffentlichen</a>
|
||
<a class="btn ghost" href="admin.php?tab=settings">DeskHub-Einstellungen</a>
|
||
</p>
|
||
</section>
|
||
</div>
|
||
|
||
<?php elseif ($tab === 'packages'): ?>
|
||
<div class="pagehead"><div><h1>Pakete</h1><p class="sub">Plugins und Themes für den DeskHub-Katalog hochladen und verwalten.</p></div></div>
|
||
<section class="card">
|
||
<header><div><h2>Neues Paket veröffentlichen</h2><p>ZIP-Datei mit <code>plugin.json</code> bzw. Theme-CSS</p></div></header>
|
||
<form method="post" enctype="multipart/form-data" class="form">
|
||
<input type="hidden" name="action" value="publish"><input type="hidden" name="csrf" value="<?= site_h($csrf) ?>">
|
||
<div class="row2">
|
||
<label>Art<select name="type" id="type"><option value="plugin">Plugin</option><option value="theme">Theme</option></select></label>
|
||
<label>ZIP-Paket<input type="file" name="package" accept=".zip" required></label>
|
||
</div>
|
||
<fieldset class="theme-only"><legend>Nur für Themes (bei Plugins steht alles in der plugin.json)</legend>
|
||
<div class="row2">
|
||
<label>Kennung (Slug)<input type="text" name="slug" placeholder="nordlicht" pattern="[a-z0-9][a-z0-9_\-]{1,40}"></label>
|
||
<label>Name<input type="text" name="name" placeholder="Nordlicht"></label>
|
||
</div>
|
||
<div class="row2">
|
||
<label>Version<input type="text" name="version" placeholder="1.0.0 (sonst aus dem CSS-Kopf)"></label>
|
||
<label>Autor<input type="text" name="author"></label>
|
||
</div>
|
||
<label>Beschreibung<textarea name="description" rows="3"></textarea></label>
|
||
</fieldset>
|
||
<label>Vorschaubild (nur beim ersten Mal nötig, PNG/JPG/WebP, höchstens 1 MB)<input type="file" name="preview" accept="image/png,image/jpeg,image/webp"></label>
|
||
<p class="muted small">Bei einem Update von Plugin oder Theme bleibt das vorhandene Bild erhalten: einfach weglassen. Ändern oder entfernen kannst du es unten bei dem Eintrag.</p>
|
||
<div><button type="submit">Veröffentlichen</button></div>
|
||
</form>
|
||
</section>
|
||
|
||
<div class="cols two">
|
||
<?php foreach (['plugin' => ['Plugins', $plugins], 'theme' => ['Themes', $themes]] as $t => [$label, $items]): ?>
|
||
<section class="card">
|
||
<header><div><h2><?= site_h($label) ?></h2><p><?= count($items) ?> veröffentlicht</p></div></header>
|
||
<?php if (!$items): ?><div class="empty">Noch keine <?= site_h($label) ?> veröffentlicht.</div><?php endif; ?>
|
||
<?php foreach ($items as $it): ?>
|
||
<div class="pkg">
|
||
<div class="head">
|
||
<div class="grow"><span class="name"><?= site_h($it['name']) ?></span> <span class="muted small"><?= site_h($it['slug']) ?></span></div>
|
||
<?php if (!empty($it['featured'])): ?><span class="badge brand">★ Empfohlen</span><?php endif; ?>
|
||
<?php if (!empty($it['language']) && is_array($__l = json_decode((string) $it['language'], true))): ?><span class="badge">🌐 <?= site_h((string) $__l['name']) ?> (<?= site_h((string) $__l['code']) ?>)</span><?php endif; ?>
|
||
<?= admin_btn(!empty($it['featured']) ? 'unfeature' : 'feature', ['id' => (int) $it['id']], !empty($it['featured']) ? 'Empfehlung entfernen' : 'Als Empfehlung markieren', 'ghost sm') ?>
|
||
</div>
|
||
<div class="previewrow">
|
||
<?php if (!empty($it['preview'])): ?><img class="thumb-sm" src="preview.php?file=<?= site_h(rawurlencode((string) $it['preview'])) ?>" alt="Vorschaubild von <?= site_h($it['name']) ?>"><?php else: ?><span class="thumb-sm none">kein Bild</span><?php endif; ?>
|
||
<form method="post" enctype="multipart/form-data" class="inline"><input type="hidden" name="action" value="set_preview"><input type="hidden" name="csrf" value="<?= site_h($csrf) ?>"><input type="hidden" name="id" value="<?= (int) $it['id'] ?>">
|
||
<input type="file" name="preview" accept="image/png,image/jpeg,image/webp" required> <button type="submit" class="ghost sm"><?= !empty($it['preview']) ? 'Bild ersetzen' : 'Bild hinzufügen' ?></button></form>
|
||
<?php if (!empty($it['preview'])) echo admin_btn('remove_preview', ['id' => (int) $it['id']], 'Bild entfernen', 'link danger', 'Vorschaubild wirklich entfernen?'); ?>
|
||
</div>
|
||
<div class="tablewrap flat"><table>
|
||
<?php foreach (site_versions((int) $it['id']) as $v): ?>
|
||
<tr><td><strong><?= site_h($v['version']) ?></strong></td><td class="muted"><?= site_h(substr($v['created_at'], 0, 10)) ?></td><td><?= (int) $v['downloads'] ?> Downloads</td>
|
||
<td><?= $v['signature'] !== '' ? '<span class="badge ok">signiert</span>' : '<span class="badge warn">nicht signiert</span>' ?></td>
|
||
<td class="actions"><?= admin_btn('delete', ['id' => (int) $v['id']], 'Entfernen', 'link danger', 'Version wirklich entfernen?') ?></td></tr>
|
||
<?php endforeach; ?>
|
||
</table></div>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</section>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
|
||
<?php elseif ($tab === 'apps'): ?>
|
||
<div class="pagehead"><div><h1>Apps</h1><p class="sub">Windows-, Android-App, Minecraft-Plugin, WordPress-Plugin und Chrome-Erweiterung veröffentlichen. Die Apps zeigen ihren Nutzern automatisch „Update verfügbar“, sobald hier eine höhere Version liegt.</p></div></div>
|
||
<section class="card">
|
||
<header><div><h2>Neue App-Version hochladen</h2><p>Windows-Setups dürfen bis 300 MB groß sein (PHP-Grenzen <code>upload_max_filesize</code> / <code>post_max_size</code> beachten).</p></div></header>
|
||
<form method="post" enctype="multipart/form-data" class="form">
|
||
<input type="hidden" name="action" value="publish_app"><input type="hidden" name="csrf" value="<?= site_h($csrf) ?>">
|
||
<div class="row2">
|
||
<label>Plattform<select name="platform" id="platform"><?php foreach (site_app_platforms() as $pk => $pv): ?><option value="<?= site_h($pk) ?>" data-zip="<?= $pv['ext'] === 'zip' ? '1' : '0' ?>"><?= site_h($pv['label'] . ' – ' . $pv['what']) ?></option><?php endforeach; ?></select></label>
|
||
<label>Versionsnummer <span class="muted small" id="versionhint" hidden>(leer lassen: wird aus dem Paket gelesen)</span><input type="text" id="appversion" name="version" placeholder="3.4.0" pattern="[0-9]+[.][0-9]+[.][0-9]+([+-][A-Za-z0-9.-]+)?" required></label>
|
||
</div>
|
||
<label>Installationsdatei<input type="file" name="app_file" accept=".apk,.exe,.jar,.zip,application/vnd.android.package-archive,application/vnd.microsoft.portable-executable,application/zip" required></label>
|
||
<label>Änderungen (optional, erscheint im Update-Hinweis)<textarea name="notes" rows="2" maxlength="2000"></textarea></label>
|
||
<div><button type="submit">App-Version hochladen</button></div>
|
||
</form>
|
||
</section>
|
||
<div class="cols">
|
||
<?php foreach (array_map(fn($p) => $p['label'], site_app_platforms()) as $pf => $pl): $mine = array_values(array_filter($appReleases, fn($r) => $r['platform'] === $pf)); $cur = $latestApp[$pf]; ?>
|
||
<section class="card">
|
||
<header><div><h2><?= site_h($pl) ?></h2><p><?= site_h(site_app_platforms()[$pf]['what']) ?></p></div>
|
||
<?= $cur ? '<span class="badge ok">aktuell ' . site_h($cur['version']) . '</span>' : '<span class="badge">noch nichts veröffentlicht</span>' ?></header>
|
||
<?php if (!$mine): ?><div class="empty">Noch keine Version veröffentlicht.</div><?php else: ?>
|
||
<div class="tablewrap"><table>
|
||
<thead><tr><th>Version</th><th>Datum</th><th>Größe</th><th></th></tr></thead>
|
||
<?php foreach ($mine as $release): $isLatest = $cur && (int) $cur['id'] === (int) $release['id']; ?><tr>
|
||
<td><strong><?= site_h($release['version']) ?></strong> <?= $isLatest ? '<span class="badge ok">aktuell</span>' : '' ?></td>
|
||
<td class="muted"><?= site_h(substr($release['created_at'], 0, 16)) ?></td><td><?= number_format((int) $release['size'] / 1048576, 1, ',', '.') ?> MB</td>
|
||
<td class="actions"><?= admin_btn('delete_app', ['id' => (int) $release['id']], 'Entfernen', 'link danger', 'Diese App-Version wirklich entfernen?') ?></td>
|
||
</tr><?php endforeach; ?>
|
||
</table></div>
|
||
<?php endif; ?>
|
||
</section>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
|
||
|
||
<?php elseif ($tab === 'push'): ?>
|
||
<div class="pagehead"><div><h1>Push-Dienst</h1><p class="sub">Zentraler Push-Dienst der Android-App. Hier siehst du die angemeldeten DeskHub-Installationen.</p></div></div>
|
||
<div class="status <?= $pushSa ? 'ok' : 'warn' ?>"><i class="dot"></i><div><strong><?= $pushSa ? 'Firebase-Dienstkonto eingebaut' : 'Kein Firebase-Dienstkonto' ?></strong><span><?= $pushSa ? 'Projekt ' . site_h($pushSa['project_id']) : 'schluessel/firebase-dienstkonto.json fehlte beim Bauen – Push-Nachrichten sind nicht möglich.' ?></span></div></div>
|
||
<section class="card mt">
|
||
<header><div><h2>Angemeldete Installationen</h2><p><?= count($pushRows) ?> Einträge</p></div></header>
|
||
<?php if (!$pushRows): ?><div class="empty">Noch keine DeskHub-Installation angemeldet.</div><?php else: ?>
|
||
<div class="tablewrap"><table>
|
||
<thead><tr><th>Name</th><th>Angemeldet</th><th>Zuletzt genutzt</th><th>Gesendet</th><th>Status</th><th></th></tr></thead>
|
||
<?php foreach ($pushRows as $p): $blocked = (int) $p['blocked'] === 1; ?>
|
||
<tr><td><?= site_h($p['name'] !== '' ? $p['name'] : $p['id']) ?></td><td class="muted"><?= site_h(substr((string) $p['created_at'], 0, 16)) ?></td>
|
||
<td class="muted"><?= $p['last_used_at'] ? site_h(substr((string) $p['last_used_at'], 0, 16)) : 'noch nie' ?></td><td><?= (int) $p['sent_total'] ?></td>
|
||
<td><?= $blocked ? '<span class="badge warn">gesperrt</span>' : '<span class="badge ok">aktiv</span>' ?></td>
|
||
<td class="actions"><?= admin_btn($blocked ? 'push_unblock' : 'push_block', ['id' => $p['id']], $blocked ? 'Entsperren' : 'Sperren') ?> <?= admin_btn('push_delete', ['id' => $p['id']], 'Löschen', 'link danger', 'Installation wirklich löschen?') ?></td></tr>
|
||
<?php endforeach; ?>
|
||
</table></div>
|
||
<?php endif; ?>
|
||
</section>
|
||
|
||
<?php else: ?>
|
||
<div class="pagehead"><div><h1>Einstellungen</h1><p class="sub">Einrichtung für DeskHub und Konto.</p></div></div>
|
||
<div class="status <?= $signed ? 'ok' : 'warn' ?>"><i class="dot"></i><div><strong>Signierschlüssel</strong><span><?= $signed ? 'Eingerichtet – neue Pakete werden automatisch signiert.' : 'Nicht eingerichtet (<code>php einrichten.php</code>) – ohne Schlüssel ist der geprüfte Modus nicht nutzbar.' ?></span></div></div>
|
||
<section class="card mt">
|
||
<header><div><h2>Einstellungen für DeskHub (offizieller Katalog)</h2><p>Diesen Inhalt in <code>includes/catalog_defaults.php</code> von DeskHub einsetzen, bevor ihr ein Release baut. Dann installieren eure Kunden Plugins nur aus diesem Katalog, und nur signierte.</p></div></header>
|
||
<pre class="key"><?= site_h(site_deskhub_defaults_snippet()) ?></pre>
|
||
</section>
|
||
<section class="card">
|
||
<header><div><h2>Konto</h2><p>Du bist als Administrator angemeldet.</p></div></header>
|
||
<?= admin_btn('logout', [], 'Abmelden', 'ghost') ?>
|
||
</section>
|
||
<?php endif; ?>
|
||
<script src="assets/admin.js"></script>
|
||
<?php endif; ?>
|
||
<?php site_footer();
|