rebuild
This commit is contained in:
@@ -1,39 +0,0 @@
|
||||
<?php
|
||||
$app = app();
|
||||
|
||||
// Example: register assets from inside a landing template
|
||||
$app->assets()->addStyle('/assets/css/app.css', 'early');
|
||||
$app->assets()->addScript('/assets/js/app.js', 'footer', true);
|
||||
|
||||
$flash = $app->flash()->get();
|
||||
?>
|
||||
<div class="card">
|
||||
<div class="pill">env: <?= htmlspecialchars(defined('APP_ENV') ? APP_ENV : 'local', ENT_QUOTES) ?></div>
|
||||
<h1 style="margin-top: .75rem;"><?= htmlspecialchars(t('common.title'), ENT_QUOTES) ?></h1>
|
||||
|
||||
<p class="muted"><?= htmlspecialchars(t('common.intro'), ENT_QUOTES) ?></p>
|
||||
|
||||
<?php if ($flash): ?>
|
||||
<div style="margin: 1rem 0; padding: .75rem 1rem; border: 1px solid #ddd; border-radius: 12px;">
|
||||
<strong><?= htmlspecialchars($flash['type'], ENT_QUOTES) ?>:</strong>
|
||||
<?= htmlspecialchars($flash['message'], ENT_QUOTES) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="grid" style="margin-top: 1rem;">
|
||||
<div>
|
||||
<h3 style="margin: 0 0 .5rem 0;">Runtime</h3>
|
||||
<div><strong>Current URL:</strong> <?= htmlspecialchars($app->request()->currentUrl(), ENT_QUOTES) ?></div>
|
||||
<div><strong>Client-ID:</strong> <code><?= htmlspecialchars($GLOBALS['client_id'] ?? '', ENT_QUOTES) ?></code></div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 style="margin: 0 0 .5rem 0;">Actions</h3>
|
||||
<form method="post" action="/action/flash">
|
||||
<button type="submit" style="padding:.6rem 1rem; border-radius: 12px; border: 1px solid #ddd; background: white; cursor:pointer;">
|
||||
Set flash message
|
||||
</button>
|
||||
</form>
|
||||
<p class="muted" style="margin-top:.5rem;">Flash uses SessionManager, no direct globals.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
80
partials/landingpages/auth/callback.php
Normal file
80
partials/landingpages/auth/callback.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
use App\OidcClient;
|
||||
|
||||
$config = app()->config();
|
||||
$session = app()->session();
|
||||
$session->start();
|
||||
|
||||
if (!$config->authEnabled) {
|
||||
echo '<div class="card">Auth ist deaktiviert.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
$code = (string)($_GET['code'] ?? '');
|
||||
$state = (string)($_GET['state'] ?? '');
|
||||
$expectedState = (string)($_SESSION['oidc_state'] ?? '');
|
||||
$nonce = (string)($_SESSION['oidc_nonce'] ?? '');
|
||||
|
||||
if ($code === '' || $state === '' || $expectedState === '' || !hash_equals($expectedState, $state)) {
|
||||
echo '<div class="card">Ungültiger Login-Status.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
unset($_SESSION['oidc_state']);
|
||||
|
||||
$client = new OidcClient($config);
|
||||
$token = $client->exchangeCode($code);
|
||||
|
||||
$idToken = (string)($token['id_token'] ?? '');
|
||||
$accessToken = (string)($token['access_token'] ?? '');
|
||||
if ($idToken === '') {
|
||||
echo '<div class="card">Kein ID Token erhalten.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
$claims = $client->decodeJwt($idToken);
|
||||
$client->validateIdToken($claims, $nonce);
|
||||
unset($_SESSION['oidc_nonce']);
|
||||
|
||||
$groups = $client->groupsFromClaims($claims);
|
||||
$accessClaims = null;
|
||||
if (!$groups && $accessToken !== '') {
|
||||
try {
|
||||
$accessClaims = $client->decodeJwt($accessToken);
|
||||
$groups = $client->groupsFromClaims($accessClaims);
|
||||
} catch (\Throwable $e) {
|
||||
// ignore access token decoding errors
|
||||
}
|
||||
}
|
||||
$user = [
|
||||
'sub' => (string)($claims['sub'] ?? ''),
|
||||
'email' => (string)($claims['email'] ?? ''),
|
||||
'name' => (string)($claims['name'] ?? ($claims['preferred_username'] ?? '')),
|
||||
'groups' => $groups,
|
||||
'id_token' => $idToken,
|
||||
];
|
||||
|
||||
$_SESSION['auth_user'] = $user;
|
||||
|
||||
if (defined('APP_AUTH_DEBUG') && APP_AUTH_DEBUG) {
|
||||
$log = [
|
||||
'ts' => date('c'),
|
||||
'sub' => $user['sub'],
|
||||
'email' => $user['email'],
|
||||
'name' => $user['name'],
|
||||
'groups' => $groups,
|
||||
'id_token_claims' => $claims,
|
||||
'access_token_claims' => $accessToken ? ($accessClaims ?? null) : null,
|
||||
'token_meta' => [
|
||||
'has_id_token' => $idToken !== '',
|
||||
'has_access_token' => $accessToken !== '',
|
||||
'expires_in' => $token['expires_in'] ?? null,
|
||||
'refresh_expires_in' => $token['refresh_expires_in'] ?? null,
|
||||
'scope' => $token['scope'] ?? null,
|
||||
],
|
||||
'claim_source' => !empty($groups) ? 'id_token_or_access_token' : 'none',
|
||||
];
|
||||
@file_put_contents(__DIR__ . '/../../../debug/oidc_login.log', json_encode($log) . PHP_EOL, FILE_APPEND);
|
||||
}
|
||||
|
||||
redirect('/');
|
||||
19
partials/landingpages/auth/login.php
Normal file
19
partials/landingpages/auth/login.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
use App\OidcClient;
|
||||
|
||||
$config = app()->config();
|
||||
if (!$config->authEnabled) {
|
||||
echo '<div class="card">Auth ist deaktiviert.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
$session = app()->session();
|
||||
$session->start();
|
||||
|
||||
$state = bin2hex(random_bytes(16));
|
||||
$nonce = bin2hex(random_bytes(16));
|
||||
$_SESSION['oidc_state'] = $state;
|
||||
$_SESSION['oidc_nonce'] = $nonce;
|
||||
|
||||
$client = new OidcClient($config);
|
||||
redirect($client->authUrl($state, $nonce));
|
||||
23
partials/landingpages/auth/logout.php
Normal file
23
partials/landingpages/auth/logout.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
use App\OidcClient;
|
||||
|
||||
$config = app()->config();
|
||||
$session = app()->session();
|
||||
$session->start();
|
||||
|
||||
$idToken = null;
|
||||
if (!empty($_SESSION['auth_user']['id_token'])) {
|
||||
$idToken = (string)$_SESSION['auth_user']['id_token'];
|
||||
}
|
||||
|
||||
unset($_SESSION['auth_user']);
|
||||
|
||||
if ($config->authEnabled) {
|
||||
$client = new OidcClient($config);
|
||||
$url = $client->logoutUrl($idToken);
|
||||
if ($url) {
|
||||
redirect($url);
|
||||
}
|
||||
}
|
||||
|
||||
redirect('/');
|
||||
11
partials/landingpages/errorpages/404.php
Normal file
11
partials/landingpages/errorpages/404.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
http_response_code(404);
|
||||
?>
|
||||
<div class="card">
|
||||
<div class="pill">404</div>
|
||||
<h1 style="margin-top:.75rem;">Seite nicht gefunden</h1>
|
||||
<p class="muted">Die angeforderte Seite existiert nicht oder wurde verschoben.</p>
|
||||
<div style="margin-top:1rem;">
|
||||
<a class="nav-link" href="/">Zur Startseite</a>
|
||||
</div>
|
||||
</div>
|
||||
34
partials/landingpages/index.php
Executable file
34
partials/landingpages/index.php
Executable file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
$modules = modules()->all();
|
||||
?>
|
||||
<div class="card">
|
||||
<div class="pill">Core</div>
|
||||
<h1 style="margin-top:.75rem;">Nexus Basis-System</h1>
|
||||
<p class="muted">Aktive Module verwalten und neue Module initialisieren.</p>
|
||||
|
||||
<div style="margin-top:1rem;">
|
||||
<a class="nav-link" href="/modules">Module verwalten</a>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:1.5rem;" class="grid">
|
||||
<?php foreach ($modules as $module): ?>
|
||||
<div class="card" style="background:var(--panel-2);">
|
||||
<div style="display:flex; align-items:center; justify-content:space-between; gap:12px;">
|
||||
<div>
|
||||
<strong><?= e($module['title']) ?></strong>
|
||||
<div class="muted" style="font-size:.85rem;"><?= e($module['description'] ?? '') ?></div>
|
||||
</div>
|
||||
<?php if (!empty($module['enabled'])): ?>
|
||||
<span class="pill" style="border-color:var(--accent-2); color:var(--accent-2);">aktiv</span>
|
||||
<?php else: ?>
|
||||
<span class="pill">inaktiv</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div style="margin-top:.75rem; display:flex; gap:10px; flex-wrap:wrap;">
|
||||
<a class="nav-link" href="/module/<?= e($module['name']) ?>">Öffnen</a>
|
||||
<a class="nav-link" href="/modules/setup/<?= e($module['name']) ?>">Setup</a>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
63
partials/landingpages/modules/index.php
Normal file
63
partials/landingpages/modules/index.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
$modules = modules()->all();
|
||||
$error = null;
|
||||
$notice = null;
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
require_admin();
|
||||
$name = (string)($_POST['module'] ?? '');
|
||||
$action = (string)($_POST['action'] ?? '');
|
||||
|
||||
if ($name !== '' && ($action === 'enable' || $action === 'disable')) {
|
||||
$enabled = $action === 'enable';
|
||||
modules()->setEnabled($name, $enabled);
|
||||
$notice = $enabled ? 'Modul aktiviert.' : 'Modul deaktiviert.';
|
||||
$modules = modules()->all();
|
||||
} else {
|
||||
$error = 'Ungültige Aktion.';
|
||||
}
|
||||
}
|
||||
?>
|
||||
<div class="card">
|
||||
<?php require_auth(); ?>
|
||||
<div class="pill">Module</div>
|
||||
<h1 style="margin-top:.75rem;">Module verwalten</h1>
|
||||
<p class="muted">Hier siehst du nur aktive Module. Installierte Module kannst du unten verwalten.</p>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div class="bg-red-900 border-l-4 border-red-500 text-red-100 p-4 mb-6" role="alert">
|
||||
<?= e($error) ?>
|
||||
</div>
|
||||
<?php elseif ($notice): ?>
|
||||
<div class="card" style="margin-top:1rem; border-color:var(--accent-2);">
|
||||
<?= e($notice) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div style="margin-top:1rem;" class="grid">
|
||||
<?php foreach ($modules as $module): ?>
|
||||
<?php if (empty($module['enabled'])) { continue; } ?>
|
||||
<div class="card" style="background:var(--panel-2);">
|
||||
<div style="display:flex; align-items:center; justify-content:space-between; gap:12px;">
|
||||
<div>
|
||||
<strong><?= e($module['title']) ?></strong>
|
||||
<div class="muted" style="font-size:.85rem;"><?= e($module['description'] ?? '') ?></div>
|
||||
</div>
|
||||
<span class="pill" style="border-color:var(--accent-2); color:var(--accent-2);">aktiv</span>
|
||||
</div>
|
||||
<div style="margin-top:.75rem; display:flex; gap:10px; flex-wrap:wrap;">
|
||||
<a class="nav-link" href="/module/<?= e($module['name']) ?>">Öffnen</a>
|
||||
<a class="nav-link" href="/modules/setup/<?= e($module['name']) ?>">Setup</a>
|
||||
<form method="post" style="margin:0;">
|
||||
<input type="hidden" name="module" value="<?= e($module['name']) ?>">
|
||||
<button class="cta-button" name="action" value="disable" style="background:var(--panel); color:var(--text);">Deaktivieren</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:1.5rem;">
|
||||
<a class="nav-link" href="/modules/install">Modul installieren/aktivieren</a>
|
||||
</div>
|
||||
</div>
|
||||
78
partials/landingpages/modules/install.php
Normal file
78
partials/landingpages/modules/install.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
$modules = modules()->all();
|
||||
$error = null;
|
||||
$notice = null;
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
require_admin();
|
||||
$name = (string)($_POST['module'] ?? '');
|
||||
$action = (string)($_POST['action'] ?? '');
|
||||
if ($name !== '' && ($action === 'enable' || $action === 'disable')) {
|
||||
modules()->setEnabled($name, $action === 'enable');
|
||||
$notice = $action === 'enable' ? 'Modul aktiviert.' : 'Modul deaktiviert.';
|
||||
$modules = modules()->all();
|
||||
} else {
|
||||
$error = 'Ungültige Aktion.';
|
||||
}
|
||||
}
|
||||
|
||||
$active = [];
|
||||
$inactive = [];
|
||||
foreach ($modules as $m) {
|
||||
if (!empty($m['enabled'])) {
|
||||
$active[] = $m;
|
||||
} else {
|
||||
$inactive[] = $m;
|
||||
}
|
||||
}
|
||||
?>
|
||||
<?php require_auth(); ?>
|
||||
<div class="card">
|
||||
<div class="pill">Module</div>
|
||||
<h1 style="margin-top:.75rem;">Module installieren/aktivieren</h1>
|
||||
<p class="muted">Erkannte Module basieren auf Ordnern in <code>modules/</code>.</p>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div class="bg-red-900 border-l-4 border-red-500 text-red-100 p-4 mb-6" role="alert">
|
||||
<?= e($error) ?>
|
||||
</div>
|
||||
<?php elseif ($notice): ?>
|
||||
<div class="card" style="margin-top:1rem; border-color:var(--accent-2);">
|
||||
<?= e($notice) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<h3 style="margin-top:1.25rem;">Aktive Module</h3>
|
||||
<div style="margin-top:.5rem;" class="grid">
|
||||
<?php foreach ($active as $module): ?>
|
||||
<div class="card" style="background:var(--panel-2);">
|
||||
<strong><?= e($module['title']) ?></strong>
|
||||
<div class="muted" style="font-size:.85rem;"><?= e($module['description'] ?? '') ?></div>
|
||||
<div style="margin-top:.75rem; display:flex; gap:10px;">
|
||||
<a class="nav-link" href="/module/<?= e($module['name']) ?>">Öffnen</a>
|
||||
<form method="post" style="margin:0;">
|
||||
<input type="hidden" name="module" value="<?= e($module['name']) ?>">
|
||||
<button class="cta-button" name="action" value="disable" style="background:var(--panel); color:var(--text);">Deaktivieren</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<h3 style="margin-top:1.5rem;">Deaktivierte Module</h3>
|
||||
<div style="margin-top:.5rem;" class="grid">
|
||||
<?php foreach ($inactive as $module): ?>
|
||||
<div class="card" style="background:var(--panel-2);">
|
||||
<strong><?= e($module['title']) ?></strong>
|
||||
<div class="muted" style="font-size:.85rem;"><?= e($module['description'] ?? '') ?></div>
|
||||
<div style="margin-top:.75rem; display:flex; gap:10px;">
|
||||
<a class="nav-link" href="/modules/setup/<?= e($module['name']) ?>">Setup</a>
|
||||
<form method="post" style="margin:0;">
|
||||
<input type="hidden" name="module" value="<?= e($module['name']) ?>">
|
||||
<button class="cta-button" name="action" value="enable">Aktivieren</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
110
partials/landingpages/modules/setup.php
Normal file
110
partials/landingpages/modules/setup.php
Normal file
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
$moduleName = (string)($_GET['module'] ?? '');
|
||||
$module = modules()->get($moduleName);
|
||||
$error = null;
|
||||
$notice = null;
|
||||
|
||||
require_admin();
|
||||
|
||||
if (!$module) {
|
||||
http_response_code(404);
|
||||
echo '<div class="card">Modul nicht gefunden.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
$fields = (array)($module['setup']['fields'] ?? []);
|
||||
$current = modules()->settings($moduleName);
|
||||
$defaults = $module['db_defaults'] ?? [];
|
||||
if (empty($current['db']) && is_array($defaults)) {
|
||||
$current['db'] = $defaults;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$payload = [];
|
||||
$db = $current['db'] ?? [];
|
||||
|
||||
foreach ($fields as $field) {
|
||||
$name = (string)($field['name'] ?? '');
|
||||
if ($name === '') {
|
||||
continue;
|
||||
}
|
||||
$value = $_POST[$name] ?? null;
|
||||
if (is_array($value)) {
|
||||
continue;
|
||||
}
|
||||
$value = is_string($value) ? trim($value) : $value;
|
||||
|
||||
if ($name === 'kea_auto_init') {
|
||||
$payload[$name] = $value === '1';
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str_starts_with($name, 'db.')) {
|
||||
$key = substr($name, 3);
|
||||
$db[$key] = $value;
|
||||
continue;
|
||||
}
|
||||
|
||||
$payload[$name] = $value;
|
||||
}
|
||||
|
||||
if (!empty($db)) {
|
||||
$payload['db'] = $db;
|
||||
}
|
||||
|
||||
modules()->saveSettings($moduleName, $payload);
|
||||
$notice = 'Setup gespeichert.';
|
||||
$current = modules()->settings($moduleName);
|
||||
}
|
||||
?>
|
||||
<div class="card">
|
||||
<div class="pill">Setup</div>
|
||||
<h1 style="margin-top:.75rem;"><?= e($module['title']) ?> – Einrichtung</h1>
|
||||
<p class="muted">Trage die benötigten Informationen für das Modul ein.</p>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div class="bg-red-900 border-l-4 border-red-500 text-red-100 p-4 mb-6" role="alert">
|
||||
<?= e($error) ?>
|
||||
</div>
|
||||
<?php elseif ($notice): ?>
|
||||
<div class="card" style="margin-top:1rem; border-color:var(--accent-2);">
|
||||
<?= e($notice) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post" style="margin-top:1rem; display:grid; gap:14px; max-width:520px;">
|
||||
<?php foreach ($fields as $field): ?>
|
||||
<?php
|
||||
$name = (string)($field['name'] ?? '');
|
||||
$label = (string)($field['label'] ?? $name);
|
||||
$type = (string)($field['type'] ?? 'text');
|
||||
$required = !empty($field['required']);
|
||||
|
||||
$value = '';
|
||||
if ($name === 'kea_auto_init') {
|
||||
$value = !empty($current[$name]) ? '1' : '0';
|
||||
} elseif (str_starts_with($name, 'db.')) {
|
||||
$key = substr($name, 3);
|
||||
$value = (string)($current['db'][$key] ?? '');
|
||||
} else {
|
||||
$value = (string)($current[$name] ?? '');
|
||||
}
|
||||
?>
|
||||
<label class="muted" style="display:grid; gap:6px;">
|
||||
<span><?= e($label) ?></span>
|
||||
<?php if ($type === 'textarea'): ?>
|
||||
<textarea name="<?= e($name) ?>" rows="3" <?= $required ? 'required' : '' ?>><?= e($value) ?></textarea>
|
||||
<?php elseif ($type === 'checkbox'): ?>
|
||||
<input type="checkbox" name="<?= e($name) ?>" value="1" <?= $value === '1' ? 'checked' : '' ?>>
|
||||
<?php else: ?>
|
||||
<input type="<?= e($type) ?>" name="<?= e($name) ?>" value="<?= e($value) ?>" <?= $required ? 'required' : '' ?>>
|
||||
<?php endif; ?>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<div style="display:flex; gap:10px;">
|
||||
<button class="cta-button" type="submit">Speichern</button>
|
||||
<a class="nav-link" href="/modules">Zurück</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
123
partials/landingpages/retool/debug.php
Normal file
123
partials/landingpages/retool/debug.php
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
$isList = (isset($_GET['list']) && $_GET['list'] === '1');
|
||||
$isRaw = (isset($_GET['raw']) && $_GET['raw'] === '1');
|
||||
|
||||
if ($isList || $isRaw) {
|
||||
if (!auth_is_admin()) {
|
||||
http_response_code(403);
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'forbidden';
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
require_admin();
|
||||
|
||||
if (!defined('APP_DEBUG_TOOL') || !APP_DEBUG_TOOL) {
|
||||
echo '<div class="card">Debug-Tool ist deaktiviert.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
$debugDir = __DIR__ . '/../../../debug';
|
||||
if (!is_dir($debugDir)) {
|
||||
if ($isList || $isRaw) {
|
||||
http_response_code(404);
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'debug_dir_missing';
|
||||
return;
|
||||
}
|
||||
echo '<div class="card">Debug-Verzeichnis fehlt.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
$files = array_values(array_filter(scandir($debugDir) ?: [], function ($f) use ($debugDir) {
|
||||
if ($f === '.' || $f === '..') return false;
|
||||
$path = $debugDir . '/' . $f;
|
||||
return is_file($path);
|
||||
}));
|
||||
|
||||
$selected = (string)($_GET['file'] ?? '');
|
||||
$content = null;
|
||||
|
||||
if ($selected !== '' && preg_match('/^[a-zA-Z0-9._-]+$/', $selected)) {
|
||||
$path = $debugDir . '/' . $selected;
|
||||
if (is_file($path)) {
|
||||
$content = file_get_contents($path);
|
||||
}
|
||||
}
|
||||
|
||||
if ($isList) {
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode($files);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($isRaw) {
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
$tail = isset($_GET['tail']) ? (int)$_GET['tail'] : 0;
|
||||
if ($tail > 0 && $content !== null) {
|
||||
$lines = preg_split('/\\R/', $content) ?: [];
|
||||
$content = implode(PHP_EOL, array_slice($lines, -$tail));
|
||||
}
|
||||
echo $content ?? '';
|
||||
return;
|
||||
}
|
||||
?>
|
||||
<div class="card">
|
||||
<div class="pill">Debug</div>
|
||||
<h1 style="margin-top:.75rem;">Debug Logs</h1>
|
||||
<p class="muted">Hier kannst du temporäre Log-Files aus dem <code>debug/</code>-Ordner ansehen.</p>
|
||||
|
||||
<div style="margin-top:.5rem;">
|
||||
<a class="nav-link" href="/debug?file=oidc_login.log">OIDC Login</a>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:1rem;" class="grid">
|
||||
<div class="card" style="background:var(--panel-2);">
|
||||
<strong>Logs</strong>
|
||||
<ul style="margin-top:.5rem;">
|
||||
<?php if (!$files): ?>
|
||||
<li class="muted">Keine Logs vorhanden.</li>
|
||||
<?php endif; ?>
|
||||
<?php foreach ($files as $f): ?>
|
||||
<li>
|
||||
<a class="nav-link" href="/debug?file=<?= e($f) ?>"><?= e($f) ?></a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card" style="background:var(--panel-2);">
|
||||
<strong>Inhalt</strong>
|
||||
<?php if ($content === null): ?>
|
||||
<p class="muted" style="margin-top:.5rem;">Wähle eine Datei.</p>
|
||||
<?php else: ?>
|
||||
<pre id="debug-content" style="margin-top:.5rem; white-space:pre-wrap; font-family:monospace;"><?= e($content) ?></pre>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($selected !== ''): ?>
|
||||
<script>
|
||||
(() => {
|
||||
const el = document.getElementById('debug-content');
|
||||
if (!el) return;
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set('raw', '1');
|
||||
let last = '';
|
||||
async function tick() {
|
||||
try {
|
||||
const res = await fetch(url.toString(), { cache: 'no-store' });
|
||||
if (!res.ok) return;
|
||||
const text = await res.text();
|
||||
if (text !== last) {
|
||||
el.textContent = text;
|
||||
last = text;
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
tick();
|
||||
setInterval(tick, 3000);
|
||||
})();
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
128
partials/landingpages/users/index.php
Normal file
128
partials/landingpages/users/index.php
Normal file
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
$pdo = app()->basePdo();
|
||||
$error = null;
|
||||
$notice = null;
|
||||
|
||||
require_admin();
|
||||
|
||||
if (!$pdo) {
|
||||
echo '<div class="card">Base-DB nicht aktiviert.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$action = (string)($_POST['action'] ?? '');
|
||||
|
||||
if ($action === 'add_role') {
|
||||
$role = trim((string)($_POST['role'] ?? ''));
|
||||
$desc = trim((string)($_POST['description'] ?? ''));
|
||||
if ($role === '') {
|
||||
$error = 'Rollenname fehlt.';
|
||||
} else {
|
||||
$stmt = $pdo->prepare(
|
||||
"INSERT INTO nexus_roles (name, description)
|
||||
VALUES (:name, :description)
|
||||
ON CONFLICT(name) DO UPDATE SET description = excluded.description"
|
||||
);
|
||||
$stmt->execute(['name' => $role, 'description' => $desc]);
|
||||
$notice = 'Rolle gespeichert.';
|
||||
}
|
||||
} elseif ($action === 'add_user') {
|
||||
$email = trim((string)($_POST['email'] ?? ''));
|
||||
$password = (string)($_POST['password'] ?? '');
|
||||
$role = trim((string)($_POST['role'] ?? 'user'));
|
||||
|
||||
if ($email === '' || $password === '') {
|
||||
$error = 'E-Mail und Passwort sind erforderlich.';
|
||||
} else {
|
||||
$hash = password_hash($password, PASSWORD_DEFAULT);
|
||||
$pdo->prepare(
|
||||
"INSERT INTO nexus_users (email, password_hash, role, is_active)
|
||||
VALUES (:email, :hash, :role, 1)"
|
||||
)->execute([
|
||||
'email' => $email,
|
||||
'hash' => $hash,
|
||||
'role' => $role !== '' ? $role : 'user',
|
||||
]);
|
||||
|
||||
$pdo->prepare(
|
||||
"INSERT INTO nexus_roles (name) VALUES (:name)
|
||||
ON CONFLICT(name) DO NOTHING"
|
||||
)->execute(['name' => $role !== '' ? $role : 'user']);
|
||||
|
||||
$notice = 'User angelegt.';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$roles = $pdo->query("SELECT name, description FROM nexus_roles ORDER BY name")->fetchAll(PDO::FETCH_ASSOC) ?: [];
|
||||
$users = $pdo->query("SELECT id, email, role, is_active, created_at FROM nexus_users ORDER BY id DESC")->fetchAll(PDO::FETCH_ASSOC) ?: [];
|
||||
?>
|
||||
<div class="card">
|
||||
<div class="pill">Userverwaltung</div>
|
||||
<h1 style="margin-top:.75rem;">User & Rollen</h1>
|
||||
<p class="muted">Admin kann Module aktivieren/deaktivieren, Benutzer können Module nutzen.</p>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div class="bg-red-900 border-l-4 border-red-500 text-red-100 p-4 mb-6" role="alert">
|
||||
<?= e($error) ?>
|
||||
</div>
|
||||
<?php elseif ($notice): ?>
|
||||
<div class="card" style="margin-top:1rem; border-color:var(--accent-2);">
|
||||
<?= e($notice) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div style="margin-top:1.5rem;" class="grid">
|
||||
<div class="card" style="background:var(--panel-2);">
|
||||
<strong>Rollen</strong>
|
||||
<ul style="margin-top:.5rem;">
|
||||
<?php foreach ($roles as $r): ?>
|
||||
<li><?= e($r['name']) ?> <span class="muted"><?= e($r['description'] ?? '') ?></span></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
|
||||
<form method="post" style="margin-top:1rem; display:grid; gap:10px;">
|
||||
<input type="hidden" name="action" value="add_role">
|
||||
<input type="text" name="role" placeholder="Rollenname (z. B. admin)">
|
||||
<input type="text" name="description" placeholder="Beschreibung">
|
||||
<button class="cta-button" type="submit">Rolle hinzufügen</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card" style="background:var(--panel-2);">
|
||||
<strong>User anlegen</strong>
|
||||
<form method="post" style="margin-top:1rem; display:grid; gap:10px;">
|
||||
<input type="hidden" name="action" value="add_user">
|
||||
<input type="email" name="email" placeholder="E-Mail">
|
||||
<input type="password" name="password" placeholder="Passwort">
|
||||
<input type="text" name="role" placeholder="Rolle (admin|user|...)">
|
||||
<button class="cta-button" type="submit">User anlegen</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 style="margin-top:1.5rem;">Userliste</h3>
|
||||
<div style="margin-top:.5rem; background:var(--panel-2);" class="card">
|
||||
<table class="min-w-full divide-y divide-gray-700">
|
||||
<thead class="bg-gray-900">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">E-Mail</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">Rolle</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">Aktiv</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">Erstellt</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-gray-800 divide-y divide-gray-700">
|
||||
<?php foreach ($users as $u): ?>
|
||||
<tr>
|
||||
<td class="px-6 py-4 text-sm"><?= e($u['email']) ?></td>
|
||||
<td class="px-6 py-4 text-sm"><?= e($u['role']) ?></td>
|
||||
<td class="px-6 py-4 text-sm"><?= !empty($u['is_active']) ? 'Ja' : 'Nein' ?></td>
|
||||
<td class="px-6 py-4 text-sm"><?= e((string)$u['created_at']) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
47
partials/landingpages/users/settings.php
Normal file
47
partials/landingpages/users/settings.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
$themes = [
|
||||
'light' => 'Light',
|
||||
'ocean' => 'Ocean',
|
||||
'graphite' => 'Graphite',
|
||||
];
|
||||
|
||||
require_auth();
|
||||
|
||||
$current = user_theme();
|
||||
$notice = null;
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$theme = (string)($_POST['theme'] ?? 'light');
|
||||
if (!isset($themes[$theme])) {
|
||||
$theme = 'light';
|
||||
}
|
||||
set_user_theme($theme);
|
||||
$current = $theme;
|
||||
$notice = 'Theme gespeichert.';
|
||||
}
|
||||
?>
|
||||
<div class="card">
|
||||
<div class="pill">Einstellungen</div>
|
||||
<h1 style="margin-top:.75rem;">User-Design</h1>
|
||||
<p class="muted">Wähle deine persönliche Farbpalette.</p>
|
||||
|
||||
<?php if ($notice): ?>
|
||||
<div class="card" style="margin-top:1rem; border-color:var(--accent-2);">
|
||||
<?= e($notice) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post" style="margin-top:1rem; display:grid; gap:12px; max-width:360px;">
|
||||
<label class="muted" style="display:grid; gap:6px;">
|
||||
<span>Farbpalette</span>
|
||||
<select name="theme">
|
||||
<?php foreach ($themes as $key => $label): ?>
|
||||
<option value="<?= e($key) ?>" <?= $current === $key ? 'selected' : '' ?>>
|
||||
<?= e($label) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
<button class="cta-button" type="submit">Speichern</button>
|
||||
</form>
|
||||
</div>
|
||||
Reference in New Issue
Block a user