modul layout
All checks were successful
Deploy / deploy-staging (push) Successful in 6s
Deploy / deploy-production (push) Has been skipped

This commit is contained in:
2026-05-03 02:02:32 +02:00
parent 2d56289477
commit 3b9940d030
3 changed files with 246 additions and 40 deletions

View File

@@ -28,8 +28,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
<h1 style="margin-top:.75rem;">Module verwalten</h1> <h1 style="margin-top:.75rem;">Module verwalten</h1>
<p class="muted">Hier siehst du nur aktive Module. Installierte Module kannst du unten verwalten.</p> <p class="muted">Hier siehst du nur aktive Module. Installierte Module kannst du unten verwalten.</p>
<p style="margin-top:.75rem;"> <p style="margin-top:.75rem;">
<a class="nav-link" href="/modules/sql-import">Zentralen SQL-Import oeffnen</a> <a class="nav-link" href="/modules/sql-import">Zentralen SQL-Import öffnen</a>
</p> </p>
<div class="module-admin-note">
<strong>Was bedeutet „Migrationen anwenden“?</strong>
<p>Damit werden neue Modulschritte aus <code>modules/&lt;name&gt;/migrations/</code> ausgeführt, meist für Datenbanktabellen, zusätzliche Spalten oder initiale Moduldaten. Bereits protokollierte Migrationen werden dabei nicht erneut ausgeführt.</p>
</div>
<?php if ($error): ?> <?php if ($error): ?>
<div class="bg-red-900 border-l-4 border-red-500 text-red-100 p-4 mb-6" role="alert"> <div class="bg-red-900 border-l-4 border-red-500 text-red-100 p-4 mb-6" role="alert">
@@ -41,7 +45,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
</div> </div>
<?php endif; ?> <?php endif; ?>
<div style="margin-top:1rem;" class="grid"> <div class="module-admin-grid">
<?php foreach ($modules as $module): ?> <?php foreach ($modules as $module): ?>
<?php if (empty($module['enabled'])) { continue; } ?> <?php if (empty($module['enabled'])) { continue; } ?>
<?php <?php
@@ -52,40 +56,46 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$hasSpecificAccess = $authRequired && ($authUsers !== [] || $authGroups !== []); $hasSpecificAccess = $authRequired && ($authUsers !== [] || $authGroups !== []);
$accessLabel = $hasSpecificAccess $accessLabel = $hasSpecificAccess
? 'Spezielle Zugriffsrechte' ? 'Spezielle Zugriffsrechte'
: ($authRequired ? 'Login erforderlich' : 'Kein Modulschutz'); : ($authRequired ? 'Login erforderlich' : 'Offen ohne Modulschutz');
$migrationStatus = modules()->migrationStatus($module['name']); $migrationStatus = modules()->migrationStatus($module['name']);
$pendingMigrations = $migrationStatus['pending'] ?? []; $pendingMigrations = $migrationStatus['pending'] ?? [];
$changedMigrations = $migrationStatus['changed'] ?? []; $changedMigrations = $migrationStatus['changed'] ?? [];
$migrationLabel = $pendingMigrations !== []
? count($pendingMigrations) . ' ausstehend'
: (($migrationStatus['available'] ?? 0) > 0 ? 'Schema aktuell' : 'Keine Migrationen');
?> ?>
<div class="card" style="background:var(--panel-2);"> <article class="module-admin-card">
<div style="display:flex; align-items:center; justify-content:space-between; gap:12px;"> <div class="module-admin-card__head">
<div> <div class="module-admin-card__title">
<strong><?= e($module['title']) ?></strong> <h2><?= e($module['title']) ?></h2>
<div class="muted" style="font-size:.85rem;"><?= e($module['description'] ?? '') ?></div> <p><?= e($module['description'] ?? '') ?></p>
</div> </div>
<div style="display:flex; gap:8px; flex-wrap:wrap; justify-content:flex-end;"> </div>
<span class="pill" style="border-color:var(--accent-2); color:var(--accent-2);">aktiv</span> <div class="module-admin-meta">
<span class="pill" title="<?= e($accessLabel) ?>" style="<?= $hasSpecificAccess ? 'border-color:#f59e0b; color:#fbbf24;' : '' ?>"> <div class="module-admin-meta__item">
<?= e($accessLabel) ?> <span class="module-admin-meta__label">Status</span>
</span> <strong class="module-admin-badge module-admin-badge--success">Aktiv</strong>
<?php if ($pendingMigrations !== []): ?> </div>
<span class="pill" style="border-color:var(--accent-orange); color:var(--accent-orange);"> <div class="module-admin-meta__item">
<?= e((string)count($pendingMigrations)) ?> Migration(en) <span class="module-admin-meta__label">Zugriff</span>
</span> <strong class="module-admin-badge<?= $hasSpecificAccess ? ' module-admin-badge--warning' : '' ?>"><?= e($accessLabel) ?></strong>
<?php elseif (($migrationStatus['available'] ?? 0) > 0): ?> </div>
<span class="pill" style="border-color:var(--accent-green); color:var(--accent-green);">Schema aktuell</span> <div class="module-admin-meta__item">
<?php endif; ?> <span class="module-admin-meta__label">Migrationen</span>
<strong class="module-admin-badge<?= $pendingMigrations !== [] ? ' module-admin-badge--accent' : (($migrationStatus['available'] ?? 0) > 0 ? ' module-admin-badge--success' : '') ?>"><?= e($migrationLabel) ?></strong>
</div> </div>
</div> </div>
<?php if ($changedMigrations !== []): ?> <?php if ($changedMigrations !== []): ?>
<div class="muted" style="margin-top:.65rem; font-size:.85rem; color:var(--accent-orange);"> <div class="module-admin-warning">
Achtung: <?= e((string)count($changedMigrations)) ?> bereits angewendete Migration(en) wurden veraendert. Achtung: <?= e((string)count($changedMigrations)) ?> bereits angewendete Migration(en) wurden verändert.
</div> </div>
<?php endif; ?> <?php endif; ?>
<?php if ($hasSpecificAccess): ?> <?php if ($hasSpecificAccess || $authRequired): ?>
<div class="muted" style="margin-top:.65rem; font-size:.85rem;"> <div class="module-admin-access">
<?php if ($authGroups !== []): ?> <?php if ($authGroups !== []): ?>
Gruppen: <?= e(implode(', ', $authGroups)) ?> Gruppen: <?= e(implode(', ', $authGroups)) ?>
<?php elseif ($authRequired): ?>
Anmeldung erforderlich
<?php endif; ?> <?php endif; ?>
<?php if ($authGroups !== [] && $authUsers !== []): ?> <?php if ($authGroups !== [] && $authUsers !== []): ?>
· ·
@@ -95,7 +105,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
<?php endif; ?> <?php endif; ?>
</div> </div>
<?php endif; ?> <?php endif; ?>
<div style="margin-top:.75rem; display:flex; gap:10px; flex-wrap:wrap;"> <div class="module-admin-actions">
<a class="nav-link" href="/module/<?= e($module['name']) ?>">Öffnen</a> <a class="nav-link" href="/module/<?= e($module['name']) ?>">Öffnen</a>
<a class="nav-link" href="/modules/setup/<?= e($module['name']) ?>">Setup</a> <a class="nav-link" href="/modules/setup/<?= e($module['name']) ?>">Setup</a>
<a class="nav-link" href="/modules/access/<?= e($module['name']) ?>">Zugriff</a> <a class="nav-link" href="/modules/access/<?= e($module['name']) ?>">Zugriff</a>
@@ -110,11 +120,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
<button class="cta-button" name="action" value="disable" style="background:var(--panel); color:var(--text);">Deaktivieren</button> <button class="cta-button" name="action" value="disable" style="background:var(--panel); color:var(--text);">Deaktivieren</button>
</form> </form>
</div> </div>
</div> </article>
<?php endforeach; ?> <?php endforeach; ?>
</div> </div>
<div style="margin-top:1.5rem;"> <div style="margin-top:1.5rem;">
<a class="nav-link" href="/modules/install">Modul installieren/aktivieren</a> <a class="nav-link" href="/modules/install">Module installieren/aktivieren</a>
</div> </div>
</div> </div>

View File

@@ -36,8 +36,12 @@ foreach ($modules as $m) {
<h1 style="margin-top:.75rem;">Module installieren/aktivieren</h1> <h1 style="margin-top:.75rem;">Module installieren/aktivieren</h1>
<p class="muted">Erkannte Module basieren auf Ordnern in <code>modules/</code>.</p> <p class="muted">Erkannte Module basieren auf Ordnern in <code>modules/</code>.</p>
<p style="margin-top:.75rem;"> <p style="margin-top:.75rem;">
<a class="nav-link" href="/modules/sql-import">Zentralen SQL-Import oeffnen</a> <a class="nav-link" href="/modules/sql-import">Zentralen SQL-Import öffnen</a>
</p> </p>
<div class="module-admin-note">
<strong>Migrationen</strong>
<p>Falls ein Modul neue Datenbankänderungen mitbringt, können sie hier einmalig angewendet werden, bevor das Modul genutzt oder aktualisiert wird.</p>
</div>
<?php if ($error): ?> <?php if ($error): ?>
<div class="bg-red-900 border-l-4 border-red-500 text-red-100 p-4 mb-6" role="alert"> <div class="bg-red-900 border-l-4 border-red-500 text-red-100 p-4 mb-6" role="alert">
@@ -50,16 +54,33 @@ foreach ($modules as $m) {
<?php endif; ?> <?php endif; ?>
<h3 style="margin-top:1.25rem;">Aktive Module</h3> <h3 style="margin-top:1.25rem;">Aktive Module</h3>
<div style="margin-top:.5rem;" class="grid"> <div class="module-admin-grid module-admin-grid--compact">
<?php foreach ($active as $module): ?> <?php foreach ($active as $module): ?>
<?php <?php
$migrationStatus = modules()->migrationStatus($module['name']); $migrationStatus = modules()->migrationStatus($module['name']);
$pendingMigrations = $migrationStatus['pending'] ?? []; $pendingMigrations = $migrationStatus['pending'] ?? [];
$migrationLabel = $pendingMigrations !== []
? count($pendingMigrations) . ' ausstehend'
: (($migrationStatus['available'] ?? 0) > 0 ? 'Schema aktuell' : 'Keine Migrationen');
?> ?>
<div class="card" style="background:var(--panel-2);"> <article class="module-admin-card">
<strong><?= e($module['title']) ?></strong> <div class="module-admin-card__head">
<div class="muted" style="font-size:.85rem;"><?= e($module['description'] ?? '') ?></div> <div class="module-admin-card__title">
<div style="margin-top:.75rem; display:flex; gap:10px;"> <h2><?= e($module['title']) ?></h2>
<p><?= e($module['description'] ?? '') ?></p>
</div>
</div>
<div class="module-admin-meta">
<div class="module-admin-meta__item">
<span class="module-admin-meta__label">Status</span>
<strong class="module-admin-badge module-admin-badge--success">Aktiv</strong>
</div>
<div class="module-admin-meta__item">
<span class="module-admin-meta__label">Migrationen</span>
<strong class="module-admin-badge<?= $pendingMigrations !== [] ? ' module-admin-badge--accent' : (($migrationStatus['available'] ?? 0) > 0 ? ' module-admin-badge--success' : '') ?>"><?= e($migrationLabel) ?></strong>
</div>
</div>
<div class="module-admin-actions">
<a class="nav-link" href="/module/<?= e($module['name']) ?>">Öffnen</a> <a class="nav-link" href="/module/<?= e($module['name']) ?>">Öffnen</a>
<?php if ($pendingMigrations !== []): ?> <?php if ($pendingMigrations !== []): ?>
<form method="post" style="margin:0;"> <form method="post" style="margin:0;">
@@ -72,21 +93,38 @@ foreach ($modules as $m) {
<button class="cta-button" name="action" value="disable" style="background:var(--panel); color:var(--text);">Deaktivieren</button> <button class="cta-button" name="action" value="disable" style="background:var(--panel); color:var(--text);">Deaktivieren</button>
</form> </form>
</div> </div>
</div> </article>
<?php endforeach; ?> <?php endforeach; ?>
</div> </div>
<h3 style="margin-top:1.5rem;">Deaktivierte Module</h3> <h3 style="margin-top:1.5rem;">Deaktivierte Module</h3>
<div style="margin-top:.5rem;" class="grid"> <div class="module-admin-grid module-admin-grid--compact">
<?php foreach ($inactive as $module): ?> <?php foreach ($inactive as $module): ?>
<?php <?php
$migrationStatus = modules()->migrationStatus($module['name']); $migrationStatus = modules()->migrationStatus($module['name']);
$pendingMigrations = $migrationStatus['pending'] ?? []; $pendingMigrations = $migrationStatus['pending'] ?? [];
$migrationLabel = $pendingMigrations !== []
? count($pendingMigrations) . ' ausstehend'
: (($migrationStatus['available'] ?? 0) > 0 ? 'Schema aktuell' : 'Keine Migrationen');
?> ?>
<div class="card" style="background:var(--panel-2);"> <article class="module-admin-card">
<strong><?= e($module['title']) ?></strong> <div class="module-admin-card__head">
<div class="muted" style="font-size:.85rem;"><?= e($module['description'] ?? '') ?></div> <div class="module-admin-card__title">
<div style="margin-top:.75rem; display:flex; gap:10px;"> <h2><?= e($module['title']) ?></h2>
<p><?= e($module['description'] ?? '') ?></p>
</div>
</div>
<div class="module-admin-meta">
<div class="module-admin-meta__item">
<span class="module-admin-meta__label">Status</span>
<strong class="module-admin-badge">Deaktiviert</strong>
</div>
<div class="module-admin-meta__item">
<span class="module-admin-meta__label">Migrationen</span>
<strong class="module-admin-badge<?= $pendingMigrations !== [] ? ' module-admin-badge--accent' : (($migrationStatus['available'] ?? 0) > 0 ? ' module-admin-badge--success' : '') ?>"><?= e($migrationLabel) ?></strong>
</div>
</div>
<div class="module-admin-actions">
<a class="nav-link" href="/modules/setup/<?= e($module['name']) ?>">Setup</a> <a class="nav-link" href="/modules/setup/<?= e($module['name']) ?>">Setup</a>
<?php if ($pendingMigrations !== []): ?> <?php if ($pendingMigrations !== []): ?>
<form method="post" style="margin:0;"> <form method="post" style="margin:0;">
@@ -99,7 +137,7 @@ foreach ($modules as $m) {
<button class="cta-button" name="action" value="enable">Aktivieren</button> <button class="cta-button" name="action" value="enable">Aktivieren</button>
</form> </form>
</div> </div>
</div> </article>
<?php endforeach; ?> <?php endforeach; ?>
</div> </div>
</div> </div>

View File

@@ -534,6 +534,20 @@ body.has-modal-open {
align-items: start; align-items: start;
flex-direction: column; flex-direction: column;
} }
.module-admin-grid,
.module-admin-grid--compact {
grid-template-columns: 1fr;
gap: 16px;
}
.module-admin-card {
padding: 18px;
}
.module-admin-meta {
grid-template-columns: 1fr;
}
} }
.card { .card {
@@ -623,6 +637,150 @@ body.has-modal-open {
gap: 14px; gap: 14px;
} }
.module-admin-note {
margin-top: 1rem;
padding: 14px 16px;
border: 1px solid var(--line);
border-radius: 16px;
background: color-mix(in srgb, var(--surface) 94%, transparent);
}
.module-admin-note strong {
display: block;
margin: 0;
}
.module-admin-note p {
margin: 0.45rem 0 0;
color: var(--muted);
line-height: 1.5;
}
.module-admin-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
gap: 20px;
margin-top: 1rem;
}
.module-admin-grid--compact {
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}
.module-admin-card {
display: grid;
gap: 16px;
min-height: 100%;
padding: 24px;
border: 1px solid var(--line);
border-radius: 22px;
background: color-mix(in srgb, var(--surface) 94%, transparent);
box-shadow: 0 16px 34px rgba(1, 22, 32, 0.08);
}
.module-admin-card__head {
display: flex;
justify-content: space-between;
gap: 12px;
}
.module-admin-card__title h2 {
margin: 0;
font-size: 1.55rem;
line-height: 1.1;
}
.module-admin-card__title p {
margin: 0.45rem 0 0;
color: var(--muted);
line-height: 1.5;
}
.module-admin-meta {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
}
.module-admin-meta__item {
display: grid;
gap: 8px;
padding: 12px 14px;
border: 1px solid var(--line);
border-radius: 16px;
background: color-mix(in srgb, var(--surface-strong) 92%, transparent);
}
.module-admin-meta__label {
color: var(--muted);
font-size: 0.74rem;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
}
.module-admin-badge {
display: inline-flex;
align-items: center;
width: fit-content;
min-height: 28px;
padding: 4px 10px;
border: 1px solid var(--line);
border-radius: 999px;
background: color-mix(in srgb, var(--surface) 92%, transparent);
font-size: 0.82rem;
font-weight: 800;
}
.module-admin-badge--success {
border-color: color-mix(in srgb, var(--accent-green) 60%, var(--line));
color: color-mix(in srgb, var(--accent-green) 78%, var(--text));
background: color-mix(in srgb, var(--accent-green) 12%, transparent);
}
.module-admin-badge--warning {
border-color: color-mix(in srgb, var(--accent-orange) 72%, var(--line));
color: color-mix(in srgb, var(--accent-orange) 88%, var(--text));
background: color-mix(in srgb, var(--accent-orange) 12%, transparent);
}
.module-admin-badge--accent {
border-color: color-mix(in srgb, var(--brand-accent) 64%, var(--line));
color: color-mix(in srgb, var(--brand-accent) 86%, var(--text));
background: color-mix(in srgb, var(--brand-accent) 12%, transparent);
}
.module-admin-access,
.module-admin-warning {
padding: 12px 14px;
border-radius: 14px;
font-size: 0.92rem;
line-height: 1.45;
}
.module-admin-access {
color: var(--muted);
background: color-mix(in srgb, var(--surface-strong) 90%, transparent);
border: 1px solid var(--line);
}
.module-admin-warning {
color: color-mix(in srgb, var(--accent-orange) 86%, var(--text));
background: color-mix(in srgb, var(--accent-orange) 10%, transparent);
border: 1px solid color-mix(in srgb, var(--accent-orange) 30%, var(--line));
}
.module-admin-actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: auto;
}
.module-admin-actions form {
margin: 0;
}
.setup-shell { .setup-shell {
display: grid; display: grid;
gap: 10px; gap: 10px;