176 lines
9.5 KiB
PHP
176 lines
9.5 KiB
PHP
<?php
|
|
|
|
use App\Models\Server;
|
|
use Livewire\Attributes\Layout;
|
|
use Livewire\Attributes\Url;
|
|
use Livewire\Volt\Component;
|
|
|
|
new #[Layout('layouts.app')] class extends Component {
|
|
#[Url(as: 'env')]
|
|
public string $envFilter = 'all'; // all | prod | edge | staging
|
|
|
|
#[Url(as: 'region')]
|
|
public string $regionFilter = '';
|
|
|
|
public function setEnv(string $env): void
|
|
{
|
|
$this->envFilter = $env;
|
|
}
|
|
|
|
public function with(): array
|
|
{
|
|
$query = Server::query()->withCount('sites');
|
|
|
|
if ($this->envFilter !== 'all') {
|
|
$query->where('env', $this->envFilter);
|
|
}
|
|
if ($this->regionFilter !== '') {
|
|
$query->where('region', $this->regionFilter);
|
|
}
|
|
|
|
return [
|
|
'servers' => $query->orderBy('name')->get(),
|
|
'totalServers' => Server::count(),
|
|
'okCount' => Server::where('status', 'ok')->count(),
|
|
'avgCpu' => (int) Server::avg('cpu_load_pct'),
|
|
'avgRam' => (int) Server::avg('ram_used_pct'),
|
|
'maintenance' => Server::where('status', 'maintenance')->count(),
|
|
'regions' => Server::query()->select('region')->distinct()->pluck('region'),
|
|
'prodCount' => Server::where('env', 'prod')->count(),
|
|
'edgeCount' => Server::where('env', 'edge')->count(),
|
|
'stagingCount' => Server::where('env', 'staging')->count(),
|
|
];
|
|
}
|
|
}; ?>
|
|
|
|
<div>
|
|
{{-- ───── Topbar ───── --}}
|
|
<div class="clu-topbar">
|
|
<x-clu.burger />
|
|
<div>
|
|
<h1 class="clu-page-title">Server</h1>
|
|
<div class="clu-page-sub">{{ $totalServers }} nodes · cluster acme</div>
|
|
</div>
|
|
<span class="clu-status-chip"><span class="clu-pulse-dot"></span> {{ $okCount }}/{{ $totalServers }} aktiv</span>
|
|
<div class="spacer"></div>
|
|
<button class="clu-btn-primary" style="height:36px;padding:0 14px;">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true">
|
|
<path d="M12 5v14M5 12h14"/>
|
|
</svg>
|
|
Cluster anlegen
|
|
</button>
|
|
</div>
|
|
|
|
{{-- ───── Metric tiles ───── --}}
|
|
<div class="grid gap-[14px] mb-[18px]" style="grid-template-columns:repeat(auto-fit,minmax(220px,1fr));">
|
|
<div class="clu-metric success">
|
|
<div class="clu-metric-row">
|
|
<span class="clu-metric-label">Aktive Nodes</span>
|
|
<span class="clu-metric-ico"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="7" rx="1.5"/><rect x="3" y="13" width="18" height="7" rx="1.5"/></svg></span>
|
|
</div>
|
|
<div class="clu-metric-value">{{ $okCount }}<span class="unit">/{{ $totalServers }}</span></div>
|
|
<div class="clu-metric-delta">{{ $maintenance }} im Maintenance</div>
|
|
</div>
|
|
<div class="clu-metric @if($avgCpu > 70) warning @endif">
|
|
<div class="clu-metric-row">
|
|
<span class="clu-metric-label">Ø CPU Load</span>
|
|
<span class="clu-metric-ico"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 12h4l3-9 4 18 3-9h4"/></svg></span>
|
|
</div>
|
|
<div class="clu-metric-value">{{ $avgCpu }}<span class="unit">%</span></div>
|
|
<div class="clu-metric-delta @if($avgCpu > 70) warn @endif">across {{ $totalServers }} nodes</div>
|
|
</div>
|
|
<div class="clu-metric @if($avgRam > 70) warning @endif">
|
|
<div class="clu-metric-row">
|
|
<span class="clu-metric-label">Ø RAM</span>
|
|
<span class="clu-metric-ico"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="7" width="18" height="10" rx="2"/><path d="M7 7v10M11 7v10M15 7v10M19 7v10"/></svg></span>
|
|
</div>
|
|
<div class="clu-metric-value">{{ $avgRam }}<span class="unit">%</span></div>
|
|
<div class="clu-metric-delta">used</div>
|
|
</div>
|
|
<div class="clu-metric">
|
|
<div class="clu-metric-row">
|
|
<span class="clu-metric-label">Maintenance</span>
|
|
<span class="clu-metric-ico"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M12 1v6M12 17v6M4.2 4.2l4.3 4.3M15.5 15.5l4.3 4.3M1 12h6M17 12h6M4.2 19.8l4.3-4.3M15.5 8.5l4.3-4.3"/></svg></span>
|
|
</div>
|
|
<div class="clu-metric-value">{{ $maintenance }}</div>
|
|
<div class="clu-metric-delta">geplant</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- ───── Toolbar ───── --}}
|
|
<div class="clu-card mb-[18px]">
|
|
<div class="clu-toolbar">
|
|
<div class="clu-seg">
|
|
<button wire:click="setEnv('all')" class="@if($envFilter==='all') active @endif">Alle <span class="count">{{ $totalServers }}</span></button>
|
|
<button wire:click="setEnv('prod')" class="@if($envFilter==='prod') active @endif">Prod <span class="count">{{ $prodCount }}</span></button>
|
|
<button wire:click="setEnv('edge')" class="@if($envFilter==='edge') active @endif">Edge <span class="count">{{ $edgeCount }}</span></button>
|
|
<button wire:click="setEnv('staging')" class="@if($envFilter==='staging') active @endif">Staging <span class="count">{{ $stagingCount }}</span></button>
|
|
</div>
|
|
<div>
|
|
<select wire:model.live="regionFilter" class="clu-ghost-btn" style="appearance:auto;">
|
|
<option value="">Region: Alle</option>
|
|
@foreach ($regions as $r)
|
|
<option value="{{ $r }}">{{ $r }}</option>
|
|
@endforeach
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- ───── Server cards grid ───── --}}
|
|
<div class="grid gap-[14px] mb-[24px]" style="grid-template-columns:repeat(auto-fit,minmax(320px,1fr));">
|
|
@forelse ($servers as $server)
|
|
<div class="clu-server-card">
|
|
<div class="clu-server-head">
|
|
<div class="clu-server-flag">{{ $server->country_flag }}</div>
|
|
<div class="flex-1 min-w-0">
|
|
<div class="clu-server-name">{{ $server->name }}</div>
|
|
<div class="clu-server-meta">{{ $server->region }} · {{ $server->env }}</div>
|
|
</div>
|
|
<span class="clu-pill @if($server->status === 'ok') success @elseif($server->status === 'warn') warning @else danger @endif">{{ strtoupper($server->status) }}</span>
|
|
</div>
|
|
|
|
<div class="clu-server-spec">
|
|
<span>{{ $server->cpu_cores }} vCPU</span>
|
|
<span>·</span>
|
|
<span>{{ $server->ram_gb }} GB RAM</span>
|
|
<span>·</span>
|
|
<span>{{ $server->disk_gb }} GB SSD</span>
|
|
</div>
|
|
|
|
<div class="clu-gauge-row">
|
|
<div class="clu-gauge">
|
|
<span class="clu-gauge-label">CPU</span>
|
|
<div class="clu-gauge-bar"><span style="width:{{ $server->cpu_load_pct }}%;" class="@if($server->cpu_load_pct > 70) warn @endif @if($server->cpu_load_pct > 90) bad @endif"></span></div>
|
|
<span class="clu-gauge-val">{{ $server->cpu_load_pct }}%</span>
|
|
</div>
|
|
<div class="clu-gauge">
|
|
<span class="clu-gauge-label">RAM</span>
|
|
<div class="clu-gauge-bar"><span style="width:{{ $server->ram_used_pct }}%;" class="@if($server->ram_used_pct > 70) warn @endif @if($server->ram_used_pct > 90) bad @endif"></span></div>
|
|
<span class="clu-gauge-val">{{ $server->ram_used_pct }}%</span>
|
|
</div>
|
|
<div class="clu-gauge">
|
|
<span class="clu-gauge-label">DISK</span>
|
|
<div class="clu-gauge-bar"><span style="width:{{ $server->disk_used_pct }}%;" class="@if($server->disk_used_pct > 70) warn @endif @if($server->disk_used_pct > 90) bad @endif"></span></div>
|
|
<span class="clu-gauge-val">{{ $server->disk_used_pct }}%</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="clu-server-foot">
|
|
<span class="clu-server-meta">{{ $server->sites_count }} sites</span>
|
|
<div class="clu-row-actions">
|
|
<button title="SSH" type="button">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 17l5-5-5-5M13 19h7"/></svg>
|
|
</button>
|
|
<button title="Mehr" type="button">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="5" cy="12" r="1.5"/><circle cx="12" cy="12" r="1.5"/><circle cx="19" cy="12" r="1.5"/></svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@empty
|
|
<div class="clu-card"><div class="clu-card-body" style="text-align:center;padding:40px;color:var(--color-muted);">Keine Server gefunden.</div></div>
|
|
@endforelse
|
|
</div>
|
|
</div>
|