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 {