261 lines
14 KiB
PHP
261 lines
14 KiB
PHP
<?php
|
|
|
|
use App\Models\Site;
|
|
use Livewire\Attributes\Layout;
|
|
use Livewire\Attributes\Url;
|
|
use Livewire\Volt\Component;
|
|
|
|
new #[Layout('layouts.app')] class extends Component {
|
|
public Site $site;
|
|
|
|
#[Url(as: 'tab')]
|
|
public string $tab = 'overview';
|
|
|
|
public function mount(Site $site): void
|
|
{
|
|
$this->site = $site->load(['server', 'certificate', 'crons', 'backups' => fn ($q) => $q->latest('completed_at')->limit(5)]);
|
|
}
|
|
|
|
public function setTab(string $t): void
|
|
{
|
|
$this->tab = $t;
|
|
}
|
|
|
|
public function toggleMaintenance(): void
|
|
{
|
|
$this->site->maintenance_mode = ! $this->site->maintenance_mode;
|
|
$this->site->save();
|
|
}
|
|
|
|
public function toggleCache(): void
|
|
{
|
|
$this->site->cache_enabled = ! $this->site->cache_enabled;
|
|
$this->site->save();
|
|
}
|
|
|
|
public function toggleCdn(): void
|
|
{
|
|
$this->site->cdn_enabled = ! $this->site->cdn_enabled;
|
|
$this->site->save();
|
|
}
|
|
|
|
public function with(): array
|
|
{
|
|
$logs = [
|
|
['09:42:18', 'info', 'GET /wp-admin → 200 (74ms)'],
|
|
['09:42:21', 'info', 'POST /wp-admin/admin-ajax.php → 200 (118ms)'],
|
|
['09:42:24', 'warn', 'PHP Notice: Undefined index "session_id" in /wp-content/plugins/foo/bar.php:42'],
|
|
['09:42:26', 'info', 'Cache HIT /produkte/'],
|
|
['09:42:28', 'error', 'PHP Fatal error: Allowed memory size exhausted (tried to allocate 8192 bytes)'],
|
|
['09:42:30', 'info', 'WP cron: wp_scheduled_delete completed in 23ms'],
|
|
['09:42:33', 'warn', 'Slow query > 2s: SELECT * FROM wp_posts WHERE post_status="publish"'],
|
|
];
|
|
|
|
$files = [
|
|
'📁 wp-content',
|
|
' 📁 plugins',
|
|
' 📁 woocommerce',
|
|
' 📁 yoast-seo',
|
|
' 📁 themes',
|
|
' 📁 storefront',
|
|
' 📁 uploads',
|
|
'📁 wp-includes',
|
|
'📁 wp-admin',
|
|
'📄 wp-config.php',
|
|
'📄 .htaccess',
|
|
'📄 index.php',
|
|
];
|
|
|
|
return compact('logs', 'files');
|
|
}
|
|
}; ?>
|
|
|
|
<div>
|
|
<div class="clu-topbar">
|
|
<x-clu.burger />
|
|
<div class="flex items-center gap-[12px]">
|
|
<a href="{{ route('sites.index') }}" wire:navigate class="clu-icon-btn" aria-label="Zurück">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 12H5M12 5l-7 7 7 7"/></svg>
|
|
</a>
|
|
<div>
|
|
<h1 class="clu-page-title">{{ $site->domain }}</h1>
|
|
<div class="clu-page-sub">{{ $site->server->name }} · WP {{ $site->wp_version }} · PHP {{ $site->php_version }}</div>
|
|
</div>
|
|
</div>
|
|
<span class="clu-status-chip"><span class="clu-pulse-dot"></span> {{ ucfirst($site->status) }}</span>
|
|
<div class="spacer"></div>
|
|
<a href="https://{{ $site->domain }}" target="_blank" rel="noopener" class="clu-ghost-btn">
|
|
Live ansehen
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 3h7v7M21 3l-9 9M19 14v5a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h5"/></svg>
|
|
</a>
|
|
</div>
|
|
|
|
{{-- Tab bar --}}
|
|
<div class="clu-detail-tabs mb-[18px]" role="tablist">
|
|
@foreach (['overview' => 'Übersicht', 'wordpress' => 'WordPress', 'php' => 'PHP', 'files' => 'Files', 'cron' => 'Cron', 'logs' => 'Logs'] as $key => $label)
|
|
<button type="button" role="tab"
|
|
wire:click="setTab('{{ $key }}')"
|
|
class="@if($tab === $key) active @endif">{{ $label }}</button>
|
|
@endforeach
|
|
</div>
|
|
|
|
{{-- Tab content --}}
|
|
@if ($tab === 'overview')
|
|
<div class="grid gap-[14px] mb-[18px]" style="grid-template-columns:repeat(auto-fit,minmax(220px,1fr));">
|
|
<div class="clu-metric"><div class="clu-metric-row"><span class="clu-metric-label">Besucher / 24h</span></div><div class="clu-metric-value">{{ number_format($site->visitors_24h, 0, ',', '.') }}</div></div>
|
|
<div class="clu-metric"><div class="clu-metric-row"><span class="clu-metric-label">Response</span></div><div class="clu-metric-value">{{ $site->response_ms }}<span class="unit">ms</span></div></div>
|
|
<div class="clu-metric @if($site->updates_pending > 0) warning @endif"><div class="clu-metric-row"><span class="clu-metric-label">Updates</span></div><div class="clu-metric-value">{{ $site->updates_pending }}</div></div>
|
|
<div class="clu-metric"><div class="clu-metric-row"><span class="clu-metric-label">PageSpeed</span></div><div class="clu-metric-value">{{ $site->pagespeed_score }}<span class="unit">/100</span></div></div>
|
|
</div>
|
|
<div class="grid gap-[14px]" style="grid-template-columns:1fr 1fr;">
|
|
<div class="clu-card">
|
|
<div class="clu-card-head"><h3>Installation</h3></div>
|
|
<div class="clu-card-body" style="display:flex;flex-direction:column;gap:8px;">
|
|
<div class="flex justify-between"><span class="text-(--color-muted)">Domain</span><span class="font-mono">{{ $site->domain }}</span></div>
|
|
<div class="flex justify-between"><span class="text-(--color-muted)">Server</span><span class="font-mono">{{ $site->server->name }}</span></div>
|
|
<div class="flex justify-between"><span class="text-(--color-muted)">WordPress</span><span class="clu-pill @if($site->updates_pending > 0) warning @else success @endif">{{ $site->wp_version }}</span></div>
|
|
<div class="flex justify-between"><span class="text-(--color-muted)">PHP</span><span class="clu-pill muted">{{ $site->php_version }}</span></div>
|
|
<div class="flex justify-between"><span class="text-(--color-muted)">SSL</span><span class="clu-pill success">Let's Encrypt</span></div>
|
|
</div>
|
|
</div>
|
|
<div class="clu-card">
|
|
<div class="clu-card-head"><h3>Quick Actions</h3></div>
|
|
<div class="clu-card-body" style="display:flex;flex-direction:column;gap:12px;">
|
|
<label class="clu-checkbox" style="justify-content:space-between;width:100%;">
|
|
<span>Maintenance Mode</span>
|
|
<input type="checkbox" wire:click="toggleMaintenance" @checked($site->maintenance_mode) />
|
|
<span class="box"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3.5"><path d="M5 12l5 5L20 7"/></svg></span>
|
|
</label>
|
|
<label class="clu-checkbox" style="justify-content:space-between;width:100%;">
|
|
<span>Page Cache</span>
|
|
<input type="checkbox" wire:click="toggleCache" @checked($site->cache_enabled) />
|
|
<span class="box"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3.5"><path d="M5 12l5 5L20 7"/></svg></span>
|
|
</label>
|
|
<label class="clu-checkbox" style="justify-content:space-between;width:100%;">
|
|
<span>CDN</span>
|
|
<input type="checkbox" wire:click="toggleCdn" @checked($site->cdn_enabled) />
|
|
<span class="box"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3.5"><path d="M5 12l5 5L20 7"/></svg></span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@elseif ($tab === 'wordpress')
|
|
<div class="clu-card">
|
|
<div class="clu-card-head"><h3>WordPress Settings</h3></div>
|
|
<div class="clu-card-body" style="display:flex;flex-direction:column;gap:14px;">
|
|
<div class="clu-field-row">
|
|
<div class="flex flex-col gap-[6px]">
|
|
<label class="text-[11.5px] text-(--color-muted) font-semibold">Site URL</label>
|
|
<div class="clu-input"><input value="https://{{ $site->domain }}" type="text" /></div>
|
|
</div>
|
|
<div class="flex flex-col gap-[6px]">
|
|
<label class="text-[11.5px] text-(--color-muted) font-semibold">Admin Email</label>
|
|
<div class="clu-input"><input value="admin@{{ $site->domain }}" type="email" /></div>
|
|
</div>
|
|
</div>
|
|
<div class="clu-field-row">
|
|
<div class="flex flex-col gap-[6px]">
|
|
<label class="text-[11.5px] text-(--color-muted) font-semibold">DB Name</label>
|
|
<div class="clu-input"><input value="wp_{{ str_replace('.', '_', $site->domain) }}" type="text" /></div>
|
|
</div>
|
|
<div class="flex flex-col gap-[6px]">
|
|
<label class="text-[11.5px] text-(--color-muted) font-semibold">DB Prefix</label>
|
|
<div class="clu-input"><input value="wp_" type="text" /></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@elseif ($tab === 'php')
|
|
<div class="clu-card">
|
|
<div class="clu-card-head"><h3>PHP {{ $site->php_version }} · php.ini</h3></div>
|
|
<div class="clu-card-body" style="display:flex;flex-direction:column;gap:14px;">
|
|
<div class="clu-field-row">
|
|
<div class="flex flex-col gap-[6px]"><label class="text-[11.5px] text-(--color-muted) font-semibold">memory_limit</label><div class="clu-input"><input value="256M" type="text" /></div></div>
|
|
<div class="flex flex-col gap-[6px]"><label class="text-[11.5px] text-(--color-muted) font-semibold">max_execution_time</label><div class="clu-input"><input value="120" type="text" /></div></div>
|
|
</div>
|
|
<div class="clu-field-row">
|
|
<div class="flex flex-col gap-[6px]"><label class="text-[11.5px] text-(--color-muted) font-semibold">upload_max_filesize</label><div class="clu-input"><input value="64M" type="text" /></div></div>
|
|
<div class="flex flex-col gap-[6px]"><label class="text-[11.5px] text-(--color-muted) font-semibold">post_max_size</label><div class="clu-input"><input value="64M" type="text" /></div></div>
|
|
</div>
|
|
<div>
|
|
<div class="clu-stat-label mb-2">Aktive Extensions</div>
|
|
<div class="flex flex-wrap gap-[6px]">
|
|
@foreach (['mysqli', 'gd', 'curl', 'mbstring', 'zip', 'xml', 'intl', 'imagick', 'redis', 'opcache'] as $ext)
|
|
<span class="clu-pill muted">{{ $ext }}</span>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@elseif ($tab === 'files')
|
|
<div class="grid gap-[14px]" style="grid-template-columns:280px 1fr;">
|
|
<div class="clu-card">
|
|
<div class="clu-card-head"><h3>Dateien</h3></div>
|
|
<div class="clu-card-body">
|
|
<div class="clu-file-tree">
|
|
@foreach ($files as $f)
|
|
<div class="clu-file-row">{{ $f }}</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="clu-card">
|
|
<div class="clu-card-head"><h3>wp-config.php</h3><span class="meta">readonly preview</span></div>
|
|
<div class="clu-card-body">
|
|
<pre class="clu-log-console" style="white-space:pre-wrap;"><?php /* keep template literal */ ?>
|
|
<?php
|
|
define('DB_NAME', 'wp_{{ str_replace('.', '_', $site->domain) }}');
|
|
define('DB_USER', 'wp_user');
|
|
define('DB_HOST', 'localhost');
|
|
define('WP_DEBUG', false);
|
|
define('WP_CACHE', {{ $site->cache_enabled ? 'true' : 'false' }});
|
|
$table_prefix = 'wp_';
|
|
require_once ABSPATH . 'wp-settings.php';</pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@elseif ($tab === 'cron')
|
|
<div class="clu-card">
|
|
<div class="clu-card-head"><h3>Cronjobs</h3><span class="meta">{{ $site->crons->count() }} aktiv</span></div>
|
|
<div class="clu-card-body tight" style="overflow-x:auto;">
|
|
<table class="clu-table">
|
|
<thead><tr><th>Name</th><th>Expression</th><th>Command</th><th>Last Run</th><th>Status</th></tr></thead>
|
|
<tbody>
|
|
@forelse ($site->crons as $c)
|
|
<tr>
|
|
<td class="font-semibold">{{ $c->name }}</td>
|
|
<td class="font-mono">{{ $c->expression }}</td>
|
|
<td class="font-mono text-[11.5px] text-(--color-muted)">{{ $c->command }}</td>
|
|
<td class="font-mono">{{ $c->last_run_at?->diffForHumans() ?? '—' }}</td>
|
|
<td>
|
|
@if ($c->status === 'ok')<span class="clu-health">ok</span>
|
|
@elseif ($c->status === 'failing')<span class="clu-health bad">failing</span>
|
|
@else <span class="clu-health warn">{{ $c->status }}</span>
|
|
@endif
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr><td colspan="5" style="text-align:center;padding:30px;color:var(--color-muted);">Keine Cronjobs.</td></tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
@elseif ($tab === 'logs')
|
|
<div class="clu-card">
|
|
<div class="clu-card-head"><h3>Logs</h3><span class="meta">live tail · access / error / php</span></div>
|
|
<div class="clu-card-body">
|
|
<div class="clu-log-console">
|
|
@foreach ($logs as [$time, $level, $msg])
|
|
<div class="clu-log-line">
|
|
<span class="clu-log-time">{{ $time }}</span>
|
|
<span class="clu-log-level {{ $level }}">{{ strtoupper($level) }}</span>
|
|
<span class="clu-log-msg">{{ $msg }}</span>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
</div>
|