diff --git a/app/resources/css/app.css b/app/resources/css/app.css index 8139a13..a711432 100644 --- a/app/resources/css/app.css +++ b/app/resources/css/app.css @@ -1350,6 +1350,100 @@ .clu-section-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; } .clu-section-head h3 { margin: 0; font-size: 14px; font-weight: 600; letter-spacing: -0.005em; } + /* Toggle row (template's
) */ + .clu-toggle-row { + display: flex; align-items: center; justify-content: space-between; gap: 14px; + padding: 11px 0; + border-bottom: 1px solid var(--color-hairline); + } + .clu-toggle-row:last-child { border-bottom: 0; } + .clu-tr-body { flex: 1; min-width: 0; } + .clu-tr-title { font-size: 13px; font-weight: 600; color: var(--color-fg); } + .clu-tr-desc { font-size: 11.5px; color: var(--color-muted); margin-top: 2px; line-height: 1.4; } + + /* Pure switch toggle (no checkbox) */ + .clu-toggle { + position: relative; flex: none; + width: 36px; height: 20px; + background: rgba(30,35,60,0.20); + border-radius: 999px; + cursor: pointer; + transition: background .18s; + } + .clu-toggle::after { + content: ""; + position: absolute; + top: 2px; left: 2px; + width: 16px; height: 16px; + border-radius: 50%; + background: #fff; + box-shadow: 0 1px 3px rgba(0,0,0,0.25); + transition: transform .18s; + } + .clu-toggle.on { background: var(--color-accent); } + .clu-toggle.on::after { transform: translateX(16px); } + + /* Input-group with suffix (numeric unit) */ + .clu-input-group { + display: flex; align-items: center; + border: 1px solid var(--color-hairline); + border-radius: var(--radius-xs); + background: rgba(255,255,255,0.5); + height: 42px; + overflow: hidden; + box-shadow: inset 0 1px 0 rgba(255,255,255,0.7); + } + .clu-input-group:focus-within { border-color: rgba(59,111,242,0.5); background: rgba(255,255,255,0.85); box-shadow: 0 0 0 3px rgba(59,111,242,0.12); } + .clu-input-group input { + flex: 1; border: 0; outline: 0; + background: none; + padding: 0 13px; + font-family: var(--font-mono); + font-size: 13px; + color: var(--color-fg); + height: 100%; + min-width: 0; + } + .clu-input-group .suffix { + padding: 0 13px; + border-left: 1px solid var(--color-hairline); + color: var(--color-muted-2); + font-family: var(--font-mono); + font-size: 11.5px; + background: rgba(255,255,255,0.35); + height: 100%; + display: flex; align-items: center; + } + + /* Monospace input variant */ + .clu-input.mono input { font-family: var(--font-mono); } + + /* Key-value row */ + .clu-kv { + display: flex; align-items: center; justify-content: space-between; gap: 12px; + padding: 10px 0; + border-bottom: 1px solid var(--color-hairline); + font-size: 12.5px; + } + .clu-kv:last-child { border-bottom: 0; } + .clu-kv .k { color: var(--color-muted); } + .clu-kv .v { color: var(--color-fg); font-family: var(--font-mono); } + + /* Field wrapper */ + .clu-field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; } + .clu-field label { font-size: 11.5px; font-weight: 600; color: var(--color-muted); text-transform: uppercase; letter-spacing: 0.04em; } + .clu-field:last-child { margin-bottom: 0; } + + /* Form actions row */ + .clu-form-actions { + display: flex; align-items: center; justify-content: flex-end; gap: 8px; + padding: 12px 18px; + border-top: 1px solid var(--color-hairline); + } + + /* Hico (card-head icon) */ + .clu-hico { color: var(--color-muted); flex: none; } + /* File tree */ .clu-file-tree { font-family: var(--font-mono); diff --git a/app/resources/views/livewire/pages/sites/show.blade.php b/app/resources/views/livewire/pages/sites/show.blade.php index 2dd8119..54ab9ed 100644 --- a/app/resources/views/livewire/pages/sites/show.blade.php +++ b/app/resources/views/livewire/pages/sites/show.blade.php @@ -15,6 +15,9 @@ new #[Layout('layouts.app')] class extends Component { public bool $logFollow = true; public string $currentLog = 'access'; + /** Set of dir paths currently expanded in file tree. */ + public array $openDirs = ['htdocs', 'htdocs/wp-content', 'htdocs/wp-content/themes', 'htdocs/wp-content/themes/storefront-child', 'logs']; + public function mount(Site $site): void { $this->site = $site->load(['server', 'certificate', 'crons']); @@ -26,6 +29,15 @@ new #[Layout('layouts.app')] class extends Component { public function setLog(string $type): void { $this->currentLog = $type; } + public function toggleDir(string $path): void + { + if (in_array($path, $this->openDirs, true)) { + $this->openDirs = array_values(array_diff($this->openDirs, [$path])); + } else { + $this->openDirs[] = $path; + } + } + public function toggleMaintenance(): void { $this->site->maintenance_mode = ! $this->site->maintenance_mode; @@ -245,16 +257,19 @@ new #[Layout('layouts.app')] class extends Component { }; ?> @php - /** Recursive file tree renderer. */ + /** Recursive file tree renderer (folder toggle via Livewire). */ if (! function_exists('clu_render_tree')) { - function clu_render_tree(array $items, string $activeFile): string { + function clu_render_tree(array $items, string $activeFile, array $openDirs, string $base = ''): string { $out = ''; foreach ($items as $node) { - $isDir = ($node['type'] ?? '') === 'dir'; - $isOpen = $node['open'] ?? false; + $isDir = ($node['type'] ?? '') === 'dir'; + $myPath = ltrim($base . '/' . $node['name'], '/'); + $isOpen = $isDir && in_array($myPath, $openDirs, true); $isActive = ! $isDir && ($node['path'] ?? null) === $activeFile; $rowClass = 'clu-tree-item' . ($isDir ? ' dir' : '') . ($isOpen ? ' open' : '') . ($isActive ? ' active' : ''); - $click = $isDir ? '' : 'wire:click="setActiveFile(\''.addslashes($node['path']).'\')" wire:navigate.hover'; + $click = $isDir + ? 'wire:click="toggleDir(\''.addslashes($myPath).'\')"' + : 'wire:click="setActiveFile(\''.addslashes($node['path']).'\')"'; $ico = $isDir ? '' : ''; @@ -264,7 +279,7 @@ new #[Layout('layouts.app')] class extends Component { $out .= '
'.$chev.$ico.''.htmlspecialchars($node['name']).'
'; if ($isDir && ! empty($node['children'])) { $hidden = $isOpen ? '' : ' collapsed'; - $out .= '
'.clu_render_tree($node['children'], $activeFile).'
'; + $out .= '
'.clu_render_tree($node['children'], $activeFile, $openDirs, $myPath).'
'; } } return $out; @@ -343,27 +358,23 @@ new #[Layout('layouts.app')] class extends Component {

Schnellaktionen

-
- - - - +
+
+
Wartungsmodus
site für besucher sperren
+ +
+
+
Objekt-Cache (Redis)
datenbankabfragen zwischenspeichern
+ +
+
+
Edge-CDN
statische inhalte global cachen
+ +
+
+
HTTPS erzwingen
alle anfragen auf ssl umleiten
+ +
@@ -386,7 +397,7 @@ new #[Layout('layouts.app')] class extends Component { /var/www/bergmann - {!! clu_render_tree($this->fileTree, $activeFile) !!} + {!! clu_render_tree($this->fileTree, $activeFile, $openDirs) !!}
@@ -523,60 +534,145 @@ new #[Layout('layouts.app')] class extends Component { {{-- ────── WP + PHP (kompakte Hardcoded-Konfig nach Template) ────── --}} @if ($tab === 'wp')
+ {{-- wp-config.php card --}}
-

wp-config.php

WordPress core
-
-
- @foreach (['Tabellen-Präfix' => 'wp_bg_', 'WP_MEMORY_LIMIT' => '256M', 'AUTOSAVE_INTERVAL' => '60', 'WP_POST_REVISIONS' => '10', 'WP_DEBUG_LOG' => '/var/www/bergmann/logs/wp-debug.log', 'WP_HOME' => 'https://shop.bergmann-coffee.com'] as $label => $val) -
- @endforeach +
+

+ + wp-config.php +

+ /htdocs/wp-config.php +
+
+
+
+
MB
+
MB
+
sek
+
+
- @foreach (['WP_DEBUG' => false, 'WP_CACHE' => true, 'DISALLOW_FILE_EDIT' => true, 'FORCE_SSL_ADMIN' => true, 'AUTOMATIC_UPDATER_DISABLED' => false] as $k => $v) - - @endforeach +
+
WP_DEBUG
debug-modus & fehlerprotokoll
+
WP_CACHE
persistentes caching aktiv
+
DISALLOW_FILE_EDIT
datei-editor im wp-admin sperren
+
AUTOMATIC_UPDATER_DISABLED
automatische core-updates deaktiviert
+
FORCE_SSL_ADMIN
wp-admin nur über https
+
+
+
+ +
+ + {{-- Datenbank + Salts side-by-side --}}
-

Datenbank

- @foreach (['DB_NAME' => 'bergmann_prod', 'DB_USER' => 'bergmann_wp', 'DB_HOST' => '127.0.0.1:3306', 'DB_CHARSET' => 'utf8mb4'] as $k => $v) -
- @endforeach -
-

Security Keys

-
AUTH_KEYenv
-
SECURE_AUTH_KEYenv
-
Rotationvor 11 Wochen
- -
+
+

Datenbank

verbunden
+
+
+
+
+
anzeigen
+
+
+
+

Sicherheits-Schlüssel (Salts)

+
+

AUTH_KEY, SECURE_AUTH_KEY, LOGGED_IN_KEY, NONCE_KEY & zugehörige Salts. Neu generieren meldet alle Benutzer ab.

+
Zuletzt rotiert14.03.2026
+
Schlüssel-Länge64 zeichen
+
Statusstark
+
+
+ +
+
+
+ + {{-- Allgemeine WP-Einstellungen --}} +
+

Allgemeine WordPress-Einstellungen

+
+
+
+
+
+
+
+
+
Suchmaschinen-Sichtbarkeit
von google & co. indexieren lassen
+
Registrierung erlauben
neue benutzer können sich registrieren
+
+
+
+ +
@endif @if ($tab === 'php')
+ {{-- php.ini main card --}}
-

php.ini · PHP {{ $site->php_version }}

-
-
- @foreach (['memory_limit' => '256M', 'max_execution_time' => '60', 'max_input_time' => '60', 'upload_max_filesize' => '64M', 'post_max_size' => '64M', 'date.timezone' => 'Europe/Berlin', 'opcache.memory_consumption' => '256', 'opcache.max_accelerated_files' => '20000'] as $k => $v) -
- @endforeach +
+

+ + php.ini +

+ php {{ $site->php_version }} · fpm pool: bergmann +
+
+
+
M
+
sek
+
M
+
M
+
+
sek
+
+
- @foreach (['display_errors' => false, 'opcache.enable' => true, 'log_errors' => true, 'allow_url_fopen' => false] as $k => $v) - - @endforeach +
+
display_errors
php-fehler im browser anzeigen (nur dev)
+
opcache.enable
bytecode-cache aktiv
+
log_errors
fehler in error.log schreiben
+
allow_url_fopen
externe urls als datei öffnen
+
+
+
+ +
+ + {{-- OPcache + Extensions --}}
-

OPcache

-
Hit-Rate99.2%
-
Scripts4 218
-
Memory187 / 256 MB
- -
-

Aktive Erweiterungen

- @foreach (['curl', 'gd', 'imagick', 'mbstring', 'mysqli', 'opcache', 'redis', 'xml', 'zip', 'intl', 'json', 'soap', 'sodium', 'exif', 'fileinfo', 'iconv'] as $ext){{ $ext }}@endforeach -
+
+

OPcache

aktiv
+
+
Trefferquote99,2%
+
Speicher142 / 256 MB
+
Zwischengesp. Skripte4.218
+
opcache.revalidate_freq60 s
+
+
+ +
+
+
+

Aktive Erweiterungen

28 module
+
+
+ @foreach (['curl', 'gd', 'imagick', 'intl', 'mbstring', 'mysqli', 'opcache', 'redis', 'soap', 'zip', 'bcmath', 'exif'] as $ext) + {{ $ext }} + @endforeach + + 16 +
+
+
@endif