adasdadad
All checks were successful
Deploy / deploy-staging (push) Successful in 26s
Deploy / deploy-production (push) Has been skipped

This commit is contained in:
2026-06-26 00:54:30 +02:00
parent a70704bc0a
commit 5a488260b9
17 changed files with 678 additions and 76 deletions

View File

@@ -195,6 +195,37 @@
gap: 8px;
}
#admin-apps-app .aa-access-card {
gap: 16px;
}
#admin-apps-app .aa-access-groups {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 10px;
}
#admin-apps-app .aa-group-item {
display: grid;
grid-template-columns: auto minmax(0, 1fr);
gap: 10px;
align-items: start;
padding: 12px 14px;
border: 1px solid rgba(148, 163, 184, 0.22);
border-radius: 14px;
background: rgba(255, 255, 255, 0.92);
}
#admin-apps-app .aa-group-item input {
margin-top: 4px;
}
#admin-apps-app .aa-group-main strong {
display: block;
margin-bottom: 4px;
font-size: 14px;
}
#admin-apps-app .aa-message {
padding: 14px 16px;
border-radius: 14px;

View File

@@ -53,7 +53,7 @@ $service = new AdminAppsService($projectRoot);
$messages = [];
$errors = [];
$section = trim((string) ($_GET['section'] ?? $_POST['active_section'] ?? 'overview'));
$allowedSections = ['overview', 'widgets', 'integrations'];
$allowedSections = ['overview', 'installation', 'access', 'widgets', 'integrations'];
if (!in_array($section, $allowedSections, true)) {
$section = 'overview';
}
@@ -78,6 +78,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
]);
$messages[] = 'Gitea-Deploy-Status-Konfiguration gespeichert.';
$section = 'integrations';
} elseif ($action === 'save-app-access') {
$service->saveAppAccess(
(string) ($_POST['app_id'] ?? ''),
array_values(array_map('strval', (array) ($_POST['required_groups'] ?? [])))
);
$messages[] = 'LDAP-Gruppen fuer die App wurden gespeichert.';
$section = 'access';
}
} catch (\Throwable $exception) {
$errors[] = $exception->getMessage();
@@ -90,6 +97,7 @@ $apps = is_array($bootstrap['apps'] ?? null) ? $bootstrap['apps'] : [];
$widgets = is_array($bootstrap['widgets'] ?? null) ? $bootstrap['widgets'] : [];
$stats = is_array($bootstrap['stats'] ?? null) ? $bootstrap['stats'] : [];
$meta = is_array($bootstrap['meta'] ?? null) ? $bootstrap['meta'] : [];
$availableGroups = is_array($meta['available_groups'] ?? null) ? $meta['available_groups'] : [];
$gitea = is_array($bootstrap['gitea'] ?? null) ? $bootstrap['gitea'] : [];
$giteaConfig = is_array($gitea['config'] ?? null) ? $gitea['config'] : [];
$giteaDiagnostics = is_array($gitea['diagnostics'] ?? null) ? $gitea['diagnostics'] : [];
@@ -99,15 +107,27 @@ $sectionUrl = static fn (string $targetSection): string => '/admin/apps/?section
$sectionMeta = [
'overview' => [
'label' => 'Apps',
'title' => 'App-Bestand und Scope',
'description' => 'Installierbare Fach-Apps, Systemtools und Core-Eintraege der Desktop-Shell im aktuellen Registry-Stand.',
'nav' => 'Registry und Freigaben pruefen.',
'title' => 'Apps und Bereitstellung',
'description' => 'Apps sind allgemeine Anwendungen. Sie koennen als Menue-App, Tray-App, Widget-Quelle oder mit optionalem Desktop-Icon auftreten.',
'nav' => 'Bestand und Installationsweg pruefen.',
],
'installation' => [
'label' => 'Installation',
'title' => 'Installationswege fuer Apps',
'description' => 'Installierbare Fach-Apps liegen unter custom/apps/. Upload- und Importwege werden hier als naechster Ausbauschritt vorbereitet.',
'nav' => 'ZIP-, Verzeichnis- und Server-Workflow beschreiben.',
],
'access' => [
'label' => 'Berechtigungen',
'title' => 'LDAP-Gruppen pro App',
'description' => 'Admins steuern hier, welche LDAP-Gruppen eine App benutzen duerfen. Leere Auswahl bedeutet: fuer alle sichtbaren Benutzer.',
'nav' => 'LDAP-Gruppen verwalten.',
],
'widgets' => [
'label' => 'Widgets',
'title' => 'Widget-Bereich und Quellen',
'description' => 'Widget-Inventar aus globaler Registry und Modul-Manifesten mit Quellen, Launch-App und Sichtbarkeit.',
'nav' => 'Widget-Quellen und Startpunkte pruefen.',
'title' => 'Widgets und Quellen',
'description' => 'Widgets sind kleine Desktop-Bereiche ohne klassische Fensterfunktionen. Sie koennen eigenstaendig oder Teil einer App sein.',
'nav' => 'Widget-Quellen und Bedeutung pruefen.',
],
'integrations' => [
'label' => 'Integrationen',
@@ -230,6 +250,7 @@ ob_start();
<th>Titel</th>
<th>Scope</th>
<th>Typ</th>
<th>LDAP-Gruppen</th>
<th>Route</th>
<th>Flags</th>
</tr>
@@ -243,6 +264,7 @@ ob_start();
</td>
<td><?= $h((string) ($app['app_scope'] ?? '')) ?></td>
<td><?= $h(!empty($app['installable']) ? 'Custom App' : 'System') ?></td>
<td><?= $h((string) ($app['required_groups_display'] ?? '')) ?></td>
<td><code><?= $h((string) ($app['entry_route'] ?? '')) ?></code></td>
<td>
<div class="aa-flag-list">
@@ -259,6 +281,108 @@ ob_start();
</table>
</div>
</section>
<section class="window-app-card aa-card">
<div class="aa-section-head">
<div>
<p class="window-app-kicker aa-kicker">Installation</p>
<h3 class="aa-section-title">Aktueller Installationsweg</h3>
<p class="aa-copy">Neue Apps koennen aktuell als Modul-Verzeichnis unter <code>custom/apps/</code> abgelegt werden. Ein eigener Upload-Bereich fuer ZIP-Dateien oder Verzeichnisse ist noch offen und wird als naechster Ausbauschritt vorbereitet.</p>
</div>
</div>
</section>
<?php endif; ?>
<?php if ($section === 'installation'): ?>
<section class="aa-grid aa-grid--stats">
<article class="window-app-card aa-card aa-stat-card">
<span>Installierbare Apps</span>
<strong><?= (int) ($stats['installable_apps'] ?? 0) ?></strong>
</article>
<article class="window-app-card aa-card aa-stat-card">
<span>Systemtools</span>
<strong><?= (int) ($stats['system_tools'] ?? 0) ?></strong>
</article>
</section>
<section class="window-app-card aa-card">
<div class="aa-section-head">
<div>
<p class="window-app-kicker aa-kicker">Installationswege</p>
<h3 class="aa-section-title">Aktueller und geplanter Ablauf</h3>
<p class="aa-copy">Installierbare Apps liegen im Projekt unter <code>custom/apps/</code>. Der Desktop erkennt neue Module automatisch ueber deren Desktop-Metadaten.</p>
</div>
</div>
<div class="aa-grid">
<article class="window-app-card aa-card aa-inline-card">
<strong>1. Manuell ueber den Server</strong>
<p>Ein Modul-Verzeichnis wird nach <code>custom/apps/&lt;app&gt;/</code> kopiert. Das ist der aktuell gueltige produktive Weg.</p>
</article>
<article class="window-app-card aa-card aa-inline-card">
<strong>2. Verzeichnis-Upload</strong>
<p>Ein Browser-Upload fuer ein vorbereitetes Modul-Verzeichnis ist fachlich vorgesehen, aber noch nicht implementiert.</p>
</article>
<article class="window-app-card aa-card aa-inline-card">
<strong>3. ZIP-Upload</strong>
<p>Ein ZIP-Import mit Entpacken und Validierung ist vorgesehen, benoetigt aber noch einen dedizierten Installations-Workflow.</p>
</article>
</div>
</section>
<?php endif; ?>
<?php if ($section === 'access'): ?>
<section class="window-app-card aa-card">
<div class="aa-section-head">
<div>
<p class="window-app-kicker aa-kicker">Access Control</p>
<h3 class="aa-section-title">LDAP-Gruppen pro App</h3>
<p class="aa-copy">Gespeichert wird in <?= $h((string) ($meta['app_access_config_path'] ?? '')) ?>. Die kurzen Gruppennamen entsprechen den LDAP-/Auth-Gruppen im Desktop.</p>
</div>
</div>
<div class="aa-grid">
<?php foreach ($apps as $app): ?>
<form class="window-app-card aa-card aa-access-card" method="post" action="/admin/apps/">
<input type="hidden" name="csrf_token" value="<?= $h($csrfToken) ?>">
<input type="hidden" name="action" value="save-app-access">
<input type="hidden" name="active_section" value="access">
<input type="hidden" name="app_id" value="<?= $h((string) ($app['app_id'] ?? '')) ?>">
<div>
<strong><?= $h((string) ($app['title'] ?? '')) ?></strong>
<p class="aa-table-copy"><?= $h((string) ($app['summary'] ?? '')) ?></p>
<p class="aa-table-copy">Aktuell: <?= $h((string) ($app['required_groups_display'] ?? '')) ?></p>
</div>
<div class="aa-access-groups">
<?php if ($availableGroups === []): ?>
<p class="aa-copy">Keine LDAP-Gruppen aus der Registration-Konfiguration gefunden.</p>
<?php else: ?>
<?php foreach ($availableGroups as $group): ?>
<label class="window-app-item aa-group-item">
<input
type="checkbox"
name="required_groups[]"
value="<?= $h((string) ($group['id'] ?? '')) ?>"
<?= in_array((string) ($group['id'] ?? ''), array_values(array_map('strval', (array) ($app['required_groups'] ?? []))), true) ? 'checked' : '' ?>
>
<span class="window-app-item-main aa-group-main">
<strong><?= $h((string) ($group['label'] ?? '')) ?></strong>
<span class="aa-meta"><?= $h((string) ($group['id'] ?? '')) ?></span>
</span>
</label>
<?php endforeach; ?>
<?php endif; ?>
</div>
<div class="aa-actions">
<button class="window-app-button aa-primary" type="submit">Berechtigungen speichern</button>
</div>
</form>
<?php endforeach; ?>
</div>
</section>
<?php endif; ?>
<?php if ($section === 'widgets'): ?>
@@ -267,7 +391,7 @@ ob_start();
<div>
<p class="window-app-kicker aa-kicker">Widget Registry</p>
<h3 class="aa-section-title">Alle Widgets</h3>
<p class="aa-copy">Globale Widgets und modulbasierte Widgets werden hier gemeinsam sichtbar. Modul-Widgets kommen bevorzugt aus `module.json`.</p>
<p class="aa-copy">Globale Widgets und modulbasierte Widgets werden hier gemeinsam sichtbar. Fachlich sind Widgets kleine Desktop-Elemente; technisch werden sie aktuell noch uebergangsweise im rechten Infobereich gerendert.</p>
</div>
</div>