VPS and dedicated server ordering, managed panels, SSO, billing, and launcher integration — forked from hosting infrastructure with server-focused routes and dashboard. Co-authored-by: Cursor <cursoragent@cursor.com>
221 lines
14 KiB
PHP
221 lines
14 KiB
PHP
@php
|
|
$currentRoute = request()->route()->getName();
|
|
$panelRuntime = app(\App\Services\Hosting\PanelRuntimeResolver::class)->forSubject($account);
|
|
$currentUser = auth()->user();
|
|
|
|
$navGroups = [
|
|
'main' => [
|
|
[
|
|
'name' => 'Overview',
|
|
'route' => route('hosting.panel.index', $account),
|
|
'active' => $currentRoute === 'hosting.panel.index',
|
|
'capability' => null,
|
|
'permission' => 'viewPanel',
|
|
'icon' => '<path stroke-linecap="round" stroke-linejoin="round" d="m2.25 12 8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" />',
|
|
],
|
|
[
|
|
'name' => 'File Manager',
|
|
'route' => route('hosting.panel.files', $account),
|
|
'active' => str_starts_with($currentRoute, 'hosting.panel.files'),
|
|
'capability' => 'files',
|
|
'permission' => 'manageFiles',
|
|
'icon' => '<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 9.776c.112-.017.227-.026.344-.026h15.812c.117 0 .232.009.344.026m-16.5 0a2.25 2.25 0 0 0-1.883 2.542l.857 6a2.25 2.25 0 0 0 2.227 1.932H19.05a2.25 2.25 0 0 0 2.227-1.932l.857-6a2.25 2.25 0 0 0-1.883-2.542m-16.5 0V6A2.25 2.25 0 0 1 6 3.75h3.879a1.5 1.5 0 0 1 1.06.44l2.122 2.12a1.5 1.5 0 0 0 1.06.44H18A2.25 2.25 0 0 1 20.25 9v.776" />',
|
|
],
|
|
[
|
|
'name' => 'Databases',
|
|
'route' => route('hosting.panel.databases', $account),
|
|
'active' => str_starts_with($currentRoute, 'hosting.panel.databases'),
|
|
'capability' => 'databases',
|
|
'permission' => 'manageDatabases',
|
|
'icon' => '<path stroke-linecap="round" stroke-linejoin="round" d="M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375" />',
|
|
],
|
|
[
|
|
'name' => 'Domains',
|
|
'route' => route('hosting.panel.domains', $account),
|
|
'active' => str_starts_with($currentRoute, 'hosting.panel.domains'),
|
|
'capability' => 'domains',
|
|
'permission' => 'viewDomains',
|
|
'domain_icon' => true,
|
|
],
|
|
],
|
|
'software' => [
|
|
[
|
|
'name' => 'Terminal',
|
|
'route' => route('hosting.panel.terminal', $account),
|
|
'active' => str_starts_with($currentRoute, 'hosting.panel.terminal'),
|
|
'capability' => 'terminal',
|
|
'permission' => 'useTerminal',
|
|
'icon' => '<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 7.5 10.5 12l-3.75 4.5m6-9h4.5" />',
|
|
],
|
|
[
|
|
'name' => 'PHP Settings',
|
|
'route' => route('hosting.panel.php', $account),
|
|
'active' => str_starts_with($currentRoute, 'hosting.panel.php'),
|
|
'capability' => 'php',
|
|
'permission' => 'managePhp',
|
|
'icon' => '<path stroke-linecap="round" stroke-linejoin="round" d="M17.25 6.75L22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3l-4.5 16.5" />',
|
|
],
|
|
[
|
|
'name' => 'SSL Certificates',
|
|
'route' => route('hosting.panel.ssl', $account),
|
|
'active' => str_starts_with($currentRoute, 'hosting.panel.ssl'),
|
|
'capability' => 'ssl',
|
|
'permission' => 'manageSsl',
|
|
'icon' => '<path stroke-linecap="round" stroke-linejoin="round" d="M16.5 10.5V6.75a4.5 4.5 0 10-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H6.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z" />',
|
|
],
|
|
[
|
|
'name' => 'App Installer',
|
|
'route' => route('hosting.panel.apps', $account),
|
|
'active' => str_starts_with($currentRoute, 'hosting.panel.apps'),
|
|
'capability' => 'apps',
|
|
'permission' => 'manageApps',
|
|
'icon' => '<path stroke-linecap="round" stroke-linejoin="round" d="M20.25 7.5l-.625 10.632a2.25 2.25 0 01-2.247 2.118H6.622a2.25 2.25 0 01-2.247-2.118L3.75 7.5M10 11.25h4M3.375 7.5h17.25c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125z" />',
|
|
],
|
|
],
|
|
'advanced' => [
|
|
[
|
|
'name' => 'Cron Jobs',
|
|
'route' => route('hosting.panel.cron', $account),
|
|
'active' => str_starts_with($currentRoute, 'hosting.panel.cron'),
|
|
'capability' => 'cron',
|
|
'permission' => 'manageCron',
|
|
'icon' => '<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z" />',
|
|
],
|
|
[
|
|
'name' => 'Error Logs',
|
|
'route' => route('hosting.panel.logs', $account),
|
|
'active' => str_starts_with($currentRoute, 'hosting.panel.logs'),
|
|
'capability' => 'logs',
|
|
'permission' => 'viewLogs',
|
|
'icon' => '<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" />',
|
|
],
|
|
[
|
|
'name' => 'Settings',
|
|
'route' => route('hosting.panel.settings', $account),
|
|
'active' => str_starts_with($currentRoute, 'hosting.panel.settings'),
|
|
'capability' => 'settings',
|
|
'permission' => 'viewSettings',
|
|
'icon' => '<path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.43.992a6.759 6.759 0 010 .255c-.008.378.137.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z" /><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />',
|
|
],
|
|
],
|
|
];
|
|
|
|
foreach ($navGroups as $group => $items) {
|
|
$navGroups[$group] = array_values(array_filter($items, static function (array $item) use ($panelRuntime, $currentUser, $account): bool {
|
|
$capability = $item['capability'] ?? null;
|
|
$permission = $item['permission'] ?? null;
|
|
|
|
return ($capability === null || $panelRuntime->supports($capability))
|
|
&& ($permission === null || $currentUser?->can($permission, $account));
|
|
}));
|
|
}
|
|
@endphp
|
|
|
|
<div class="flex h-full flex-col">
|
|
{{-- Logo/Header --}}
|
|
<div class="flex h-14 items-center gap-3 border-b border-slate-200 px-4">
|
|
<div class="flex h-8 w-8 items-center justify-center rounded-lg bg-indigo-600">
|
|
<svg class="h-4 w-4 text-white" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M5.25 14.25h13.5m-13.5 0a3 3 0 0 1-3-3m3 3a3 3 0 1 0 0 6h13.5a3 3 0 1 0 0-6m-16.5-3a3 3 0 0 1 3-3h13.5a3 3 0 0 1 3 3m-19.5 0a4.5 4.5 0 0 1 .9-2.7L5.737 5.1a3.375 3.375 0 0 1 2.7-1.35h7.126c1.062 0 2.062.5 2.7 1.35l2.587 3.45a4.5 4.5 0 0 1 .9 2.7m0 0a3 3 0 0 1-3 3m0 3h.008v.008h-.008v-.008Zm0-6h.008v.008h-.008v-.008Zm-3 6h.008v.008h-.008v-.008Zm0-6h.008v.008h-.008v-.008Z" />
|
|
</svg>
|
|
</div>
|
|
<div class="min-w-0 flex-1">
|
|
<p class="truncate text-sm font-semibold text-slate-900">{{ $panelRuntime->label() }}</p>
|
|
<p class="truncate text-xs text-slate-500">{{ $account->username }}</p>
|
|
</div>
|
|
<button type="button" @click="sidebarOpen = false" class="lg:hidden p-1 text-slate-400 hover:text-slate-600">
|
|
<svg class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg>
|
|
</button>
|
|
</div>
|
|
|
|
{{-- Navigation --}}
|
|
<nav class="flex-1 overflow-y-auto p-3">
|
|
{{-- Main Section --}}
|
|
<ul class="space-y-1">
|
|
@foreach($navGroups['main'] as $item)
|
|
<li>
|
|
<a href="{{ $item['route'] }}"
|
|
class="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition {{ $item['active'] ? 'bg-indigo-50 text-indigo-700' : 'text-slate-600 hover:bg-slate-50 hover:text-slate-900' }}">
|
|
@if (! empty($item['domain_icon']))
|
|
{!! \App\Support\DomainGlobeIcon::svg('h-5 w-5 shrink-0 ' . ($item['active'] ? 'text-indigo-600' : 'text-slate-400')) !!}
|
|
@else
|
|
<svg class="h-5 w-5 flex-shrink-0 {{ $item['active'] ? 'text-indigo-600' : 'text-slate-400' }}" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
|
|
{!! $item['icon'] !!}
|
|
</svg>
|
|
@endif
|
|
{{ $item['name'] }}
|
|
</a>
|
|
</li>
|
|
@endforeach
|
|
</ul>
|
|
|
|
{{-- Software Section --}}
|
|
@if (! empty($navGroups['software']))
|
|
<div class="mt-6">
|
|
<p class="px-3 text-[10px] font-semibold uppercase tracking-wider text-slate-400">Software</p>
|
|
<ul class="mt-2 space-y-1">
|
|
@foreach($navGroups['software'] as $item)
|
|
<li>
|
|
<a href="{{ $item['route'] }}"
|
|
class="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition {{ $item['active'] ? 'bg-indigo-50 text-indigo-700' : 'text-slate-600 hover:bg-slate-50 hover:text-slate-900' }}">
|
|
@if (! empty($item['domain_icon']))
|
|
{!! \App\Support\DomainGlobeIcon::svg('h-5 w-5 shrink-0 ' . ($item['active'] ? 'text-indigo-600' : 'text-slate-400')) !!}
|
|
@else
|
|
<svg class="h-5 w-5 flex-shrink-0 {{ $item['active'] ? 'text-indigo-600' : 'text-slate-400' }}" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
|
|
{!! $item['icon'] !!}
|
|
</svg>
|
|
@endif
|
|
{{ $item['name'] }}
|
|
</a>
|
|
</li>
|
|
@endforeach
|
|
</ul>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- Advanced Section --}}
|
|
@if (! empty($navGroups['advanced']))
|
|
<div class="mt-6">
|
|
<p class="px-3 text-[10px] font-semibold uppercase tracking-wider text-slate-400">Advanced</p>
|
|
<ul class="mt-2 space-y-1">
|
|
@foreach($navGroups['advanced'] as $item)
|
|
<li>
|
|
<a href="{{ $item['route'] }}"
|
|
class="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition {{ $item['active'] ? 'bg-indigo-50 text-indigo-700' : 'text-slate-600 hover:bg-slate-50 hover:text-slate-900' }}">
|
|
@if (! empty($item['domain_icon']))
|
|
{!! \App\Support\DomainGlobeIcon::svg('h-5 w-5 shrink-0 ' . ($item['active'] ? 'text-indigo-600' : 'text-slate-400')) !!}
|
|
@else
|
|
<svg class="h-5 w-5 flex-shrink-0 {{ $item['active'] ? 'text-indigo-600' : 'text-slate-400' }}" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
|
|
{!! $item['icon'] !!}
|
|
</svg>
|
|
@endif
|
|
{{ $item['name'] }}
|
|
</a>
|
|
</li>
|
|
@endforeach
|
|
</ul>
|
|
</div>
|
|
@endif
|
|
</nav>
|
|
|
|
{{-- Account Info Footer --}}
|
|
<div class="border-t border-slate-200 p-3">
|
|
<div class="rounded-lg bg-slate-50 p-3">
|
|
<div class="flex items-center gap-2 text-xs text-slate-500">
|
|
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M21.75 17.25v-.228a4.5 4.5 0 0 0-.12-1.03l-2.268-9.64a3.375 3.375 0 0 0-3.285-2.602H7.923a3.375 3.375 0 0 0-3.285 2.602l-2.268 9.64a4.5 4.5 0 0 0-.12 1.03v.228m19.5 0a3 3 0 0 1-3 3H5.25a3 3 0 0 1-3-3m19.5 0a3 3 0 0 0-3-3H5.25a3 3 0 0 0-3 3m16.5 0h.008v.008h-.008v-.008Zm-3 0h.008v.008h-.008v-.008Z" />
|
|
</svg>
|
|
<span>{{ $account->node->hostname ?? $account->node->ip_address ?? 'Server' }}</span>
|
|
</div>
|
|
<div class="mt-2 flex items-center gap-2">
|
|
<span class="inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium {{ $account->status === 'active' ? 'bg-emerald-100 text-emerald-700' : 'bg-slate-200 text-slate-600' }}">
|
|
{{ ucfirst($account->status) }}
|
|
</span>
|
|
@if($account->product)
|
|
<span class="text-[10px] text-slate-400">{{ $account->product->name }}</span>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|