164 lines
9.9 KiB
PHP
164 lines
9.9 KiB
PHP
@props(['active' => null])
|
|
|
|
@php
|
|
$user = auth()->user();
|
|
$ws = $user?->currentWorkspace;
|
|
$wsInitials = $ws ? mb_strtoupper(mb_substr(preg_replace('/[^A-Za-z\s]/', '', $ws->name), 0, 2)) : '—';
|
|
$userInit = $user ? mb_strtoupper(mb_substr($user->name, 0, 1) . mb_substr(explode(' ', $user->name)[1] ?? '', 0, 1)) : '?';
|
|
|
|
// All workspaces the user can access: owned + via TeamMember.
|
|
$workspaces = collect();
|
|
if ($user) {
|
|
$workspaces = $workspaces->merge($user->ownedWorkspaces);
|
|
if (method_exists($user, 'teamMemberships')) {
|
|
$workspaces = $workspaces->merge($user->teamMemberships->pluck('workspace'));
|
|
} else {
|
|
// Fallback via TeamMember model
|
|
$memberWsIds = \App\Models\TeamMember::where('user_id', $user->id)->pluck('workspace_id');
|
|
$workspaces = $workspaces->merge(\App\Models\Workspace::whereIn('id', $memberWsIds)->get());
|
|
}
|
|
$workspaces = $workspaces->unique('id')->values();
|
|
// Fallback: at least the currently-active workspace.
|
|
if ($workspaces->isEmpty() && $ws) {
|
|
$workspaces = collect([$ws]);
|
|
}
|
|
}
|
|
|
|
$sitesUsed = $ws ? \App\Models\Site::count() : 0;
|
|
$sitesQuota = $ws?->quotas['sites'] ?? 0;
|
|
$planPct = $sitesQuota ? min(100, round($sitesUsed / $sitesQuota * 100)) : 0;
|
|
|
|
$items = [
|
|
['name' => 'dashboard', 'label' => 'Dashboard', 'route' => 'dashboard',
|
|
'svg' => '<path d="M3 13h8V3H3v10zm10 8h8V11h-8v10zM3 21h8v-6H3v6zm10-18v6h8V3h-8z"/>'],
|
|
['name' => 'sites', 'label' => 'Sites', 'route' => 'sites.index',
|
|
'svg' => '<circle cx="12" cy="12" r="10"/><path d="M2 12h20M12 2a15 15 0 0 1 0 20M12 2a15 15 0 0 0 0 20"/>',
|
|
'count' => \App\Models\Site::count() ?: null],
|
|
['name' => 'servers', 'label' => 'Server', 'route' => 'servers.index',
|
|
'svg' => '<rect x="3" y="4" width="18" height="7" rx="1.5"/><rect x="3" y="13" width="18" height="7" rx="1.5"/><circle cx="7" cy="7.5" r="0.7" fill="currentColor"/><circle cx="7" cy="16.5" r="0.7" fill="currentColor"/>',
|
|
'count' => \App\Models\Server::count() ?: null],
|
|
['name' => 'backups', 'label' => 'Backups', 'route' => 'backups.index',
|
|
'svg' => '<path d="M21 12c0 5-9 9-9 9s-9-4-9-9V5l9-3 9 3v7z"/>'],
|
|
['name' => 'security', 'label' => 'Sicherheit','route' => 'security.index',
|
|
'svg' => '<path d="M12 3l8 4v5c0 5-3.5 8-8 9-4.5-1-8-4-8-9V7l8-4z"/>'],
|
|
['name' => 'team', 'label' => 'Team', 'route' => 'team.index',
|
|
'svg' => '<circle cx="9" cy="8" r="3.5"/><path d="M3 20v-1c0-3 3-5 6-5s6 2 6 5v1"/><circle cx="17" cy="9" r="2.5"/>'],
|
|
['name' => 'settings', 'label' => 'Einstellungen','route' => 'settings.index',
|
|
'svg' => '<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>'],
|
|
];
|
|
@endphp
|
|
|
|
<aside class="clu-sidebar" :class="{ 'open': sidebarOpen }">
|
|
{{-- Fixed header --}}
|
|
<div class="clu-sidebar-head">
|
|
<div class="clu-brand-row">
|
|
<div class="clu-brand-mark" aria-hidden="true"></div>
|
|
<div class="clu-brand-name">CluPilot<span>v2.4</span></div>
|
|
</div>
|
|
|
|
{{-- Workspace switcher (Alpine dropdown) --}}
|
|
<div class="relative" x-data="{ wsOpen: false }" @click.outside="wsOpen = false" @keydown.escape.window="wsOpen = false">
|
|
<button type="button" class="clu-ws-switch w-full" @click="wsOpen = !wsOpen">
|
|
<div class="clu-ws-avatar">{{ $wsInitials }}</div>
|
|
<div class="clu-ws-meta">
|
|
<div class="clu-ws-name">{{ $ws?->name ?? 'Workspace wählen' }}</div>
|
|
<div class="clu-ws-plan">{{ strtoupper($ws?->plan ?? 'free') }} · {{ $sitesUsed }}/{{ $sitesQuota }} sites</div>
|
|
</div>
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="text-(--color-muted-2) flex-none" aria-hidden="true">
|
|
<path d="M7 9l5-5 5 5M7 15l5 5 5-5"/>
|
|
</svg>
|
|
</button>
|
|
|
|
<div x-show="wsOpen" x-cloak
|
|
x-transition:enter="transition ease-out duration-150"
|
|
x-transition:enter-start="opacity-0 translate-y-[-4px]"
|
|
x-transition:enter-end="opacity-100 translate-y-0"
|
|
class="clu-ws-dropdown" style="display:none;">
|
|
|
|
<div class="clu-ws-section">WORKSPACES</div>
|
|
@foreach ($workspaces as $w)
|
|
<a href="{{ url('/w/' . $w->public_id . '/dashboard') }}"
|
|
class="clu-ws-item @if($ws && $ws->id === $w->id) active @endif">
|
|
<div class="clu-ws-avatar" style="width:20px;height:20px;font-size:9px;border-radius:5px;">
|
|
{{ mb_strtoupper(mb_substr(preg_replace('/[^A-Za-z\s]/', '', $w->name), 0, 2)) }}
|
|
</div>
|
|
<div class="flex-1 min-w-0">
|
|
<div class="text-[12.5px] font-semibold truncate">{{ $w->name }}</div>
|
|
<div class="text-[10.5px] text-(--color-muted) font-mono">{{ strtoupper($w->plan) }} · {{ $w->sites()->count() }} sites</div>
|
|
</div>
|
|
@if ($ws && $ws->id === $w->id)
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" class="text-(--color-accent) flex-none"><path d="M5 12l5 5L20 7"/></svg>
|
|
@endif
|
|
</a>
|
|
@endforeach
|
|
|
|
<a href="#" class="clu-ws-item">
|
|
<div class="clu-ws-avatar" style="width:20px;height:20px;font-size:14px;border-radius:5px;background:rgba(30,35,60,0.08);color:var(--color-muted);">+</div>
|
|
<span class="text-[12.5px] font-medium">Neuer Workspace</span>
|
|
</a>
|
|
|
|
<div class="clu-ws-divider"></div>
|
|
<div class="clu-ws-section">{{ $user?->name ?? 'Konto' }}</div>
|
|
<div class="text-[11px] text-(--color-muted) font-mono px-[10px] pb-[6px] truncate">{{ $user?->email }}</div>
|
|
|
|
<a href="{{ route('profile') }}" wire:navigate class="clu-ws-item">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="text-(--color-muted) flex-none"><circle cx="12" cy="8" r="4"/><path d="M4 21v-2c0-3 3-5 8-5s8 2 8 5v2"/></svg>
|
|
<span class="text-[12.5px]">Profil</span>
|
|
</a>
|
|
<a href="{{ route('settings.index') }}" wire:navigate class="clu-ws-item">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="text-(--color-muted) flex-none"><circle cx="12" cy="12" r="3"/><path d="M12 2v3M12 19v3M2 12h3M19 12h3"/></svg>
|
|
<span class="text-[12.5px]">Einstellungen</span>
|
|
</a>
|
|
|
|
<div class="clu-ws-divider"></div>
|
|
<form method="POST" action="{{ route('logout') }}" class="block">
|
|
@csrf
|
|
<button type="submit" class="clu-ws-item w-full" style="color:var(--color-danger);">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="flex-none"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4M16 17l5-5-5-5M21 12H9"/></svg>
|
|
<span class="text-[12.5px]">Abmelden</span>
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Scrollable nav --}}
|
|
<div class="clu-sidebar-scroll">
|
|
<nav class="clu-nav">
|
|
<div class="clu-nav-title">{{ $ws?->name ?? 'Cluster' }}</div>
|
|
@foreach ($items as $item)
|
|
@php
|
|
$isActive = ($active === $item['route']) || ($active === $item['name']);
|
|
$href = \Illuminate\Support\Facades\Route::has($item['route']) ? route($item['route']) : '#';
|
|
@endphp
|
|
<a href="{{ $href }}" wire:navigate
|
|
class="clu-nav-item @if($isActive) active @endif"
|
|
@click="sidebarOpen = false">
|
|
<svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
{!! $item['svg'] !!}
|
|
</svg>
|
|
{{ $item['label'] }}
|
|
@if (! empty($item['count']))
|
|
<span class="count">{{ $item['count'] }}</span>
|
|
@endif
|
|
</a>
|
|
@endforeach
|
|
</nav>
|
|
</div>
|
|
|
|
{{-- Fixed footer --}}
|
|
<div class="clu-sidebar-foot">
|
|
<div class="clu-plan-box">
|
|
<div class="clu-plan-row">
|
|
<span class="clu-plan-name">{{ strtoupper($ws?->plan ?? 'FREE') }}</span>
|
|
<span class="clu-plan-tag">{{ $planPct }}% USED</span>
|
|
</div>
|
|
<div class="clu-plan-meter"><span style="width:{{ $planPct }}%;"></span></div>
|
|
<div class="clu-plan-stats">
|
|
<span>{{ $sitesUsed }} / {{ $sitesQuota }} sites</span>
|
|
<a href="{{ route('settings.index') }}" wire:navigate class="text-(--color-accent) hover:underline">Upgrade →</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</aside>
|