216 lines
15 KiB
PHP
216 lines
15 KiB
PHP
<div class="space-y-4" x-data="{ msgs: @js(['addon' => __('dashboard.modules.add_toast'), 'restore' => __('dashboard.backups.restore_toast')]) }">
|
||
{{-- Header --}}
|
||
<div class="flex flex-wrap items-center gap-x-4 gap-y-1 animate-rise">
|
||
<h1 class="text-2xl font-semibold tracking-tight text-ink">{{ __('dashboard.greeting', ['name' => auth()->user()->name]) }}</h1>
|
||
<span class="ml-auto inline-flex items-center gap-2 rounded-pill bg-success-bg px-3.5 py-1.5 text-xs font-semibold text-success">
|
||
<span class="size-2 rounded-pill bg-success-bright" aria-hidden="true"></span>{{ __('dashboard.system_ok') }}
|
||
</span>
|
||
<p class="w-full text-sm text-muted">{{ __('dashboard.as_of', ['time' => '08:42']) }}</p>
|
||
</div>
|
||
|
||
{{-- Live provisioning progress (only while a run is in flight) --}}
|
||
<livewire:customer-provisioning />
|
||
|
||
{{-- KPI row --}}
|
||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4">
|
||
{{-- Storage ring --}}
|
||
<div class="flex items-center gap-4 rounded-xl border border-line bg-surface p-5 shadow-xs animate-rise [animation-delay:60ms]">
|
||
<div class="relative size-24 shrink-0" wire:ignore>
|
||
<x-ui.chart :config="$storageChart" class="size-24" :label="__('dashboard.kpi.storage')" />
|
||
<div class="pointer-events-none absolute inset-0 grid place-items-center text-center">
|
||
<div>
|
||
<span class="font-mono text-lg font-semibold text-ink">{{ $storagePercent }}%</span>
|
||
<span class="block text-[0.62rem] text-muted">{{ __('dashboard.occupied') }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<p class="text-xs font-semibold uppercase tracking-wide text-faint">{{ __('dashboard.kpi.storage') }}</p>
|
||
<p class="mt-1 font-mono text-xl font-semibold text-ink">{{ $storageUsed }}<span class="ml-1 text-sm font-normal text-muted">/ {{ $storageQuota }} GB</span></p>
|
||
<p class="mt-0.5 text-xs text-muted">{{ __('dashboard.storage_week') }}</p>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- Users --}}
|
||
<div class="rounded-xl border border-line bg-surface p-5 shadow-xs animate-rise [animation-delay:120ms]">
|
||
<p class="text-xs font-semibold uppercase tracking-wide text-faint">{{ __('dashboard.kpi.users') }}</p>
|
||
<p class="mt-2 font-mono text-2xl font-semibold text-ink">{{ $usersActive }}<span class="ml-1 text-sm font-normal text-muted">/ {{ $usersQuota }}</span></p>
|
||
<p class="mt-1 text-xs text-muted">{{ __('dashboard.users_detail') }}</p>
|
||
</div>
|
||
|
||
{{-- Last backup --}}
|
||
<div class="rounded-xl border border-line bg-surface p-5 shadow-xs animate-rise [animation-delay:180ms]">
|
||
<p class="text-xs font-semibold uppercase tracking-wide text-faint">{{ __('dashboard.kpi.backup') }}</p>
|
||
<p class="mt-2 flex items-center gap-1.5 font-mono text-2xl font-semibold text-success">
|
||
{{ $lastBackup }}<x-ui.icon name="check" class="size-5" />
|
||
</p>
|
||
<p class="mt-1 text-xs text-muted">{{ __('dashboard.backup_verified') }}</p>
|
||
</div>
|
||
|
||
{{-- Availability sparkline --}}
|
||
<div class="rounded-xl border border-line bg-surface p-5 shadow-xs animate-rise [animation-delay:240ms]">
|
||
<p class="text-xs font-semibold uppercase tracking-wide text-faint">{{ __('dashboard.kpi.availability') }}</p>
|
||
<p class="mt-2 font-mono text-2xl font-semibold text-ink">{{ $availability }}<span class="ml-1 text-sm font-normal text-muted">%</span></p>
|
||
<div class="mt-2 h-8" wire:ignore>
|
||
<x-ui.chart :config="$availabilityChart" class="h-8" :label="__('dashboard.kpi.availability')" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- Upsell --}}
|
||
<div class="flex flex-wrap items-center gap-4 rounded-xl border border-accent-border bg-accent-subtle px-5 py-4 animate-rise [animation-delay:300ms]">
|
||
<x-ui.icon name="alert-triangle" class="size-6 shrink-0 text-accent-active" />
|
||
<div class="min-w-0">
|
||
<p class="text-sm font-semibold text-ink">{{ __('dashboard.upsell.title', ['percent' => $storagePercent]) }}</p>
|
||
<p class="text-sm text-muted">{{ __('dashboard.upsell.body') }}</p>
|
||
</div>
|
||
<x-ui.button variant="secondary" size="sm" class="ml-auto" @click="$dispatch('notify', { message: msgs.addon })">
|
||
{{ __('dashboard.upsell.cta') }}
|
||
</x-ui.button>
|
||
</div>
|
||
|
||
<div class="grid grid-cols-1 gap-4 lg:grid-cols-[1.35fr_1fr] lg:items-start">
|
||
{{-- Left column --}}
|
||
<div class="flex flex-col gap-4">
|
||
{{-- Cloud card --}}
|
||
<div class="rounded-xl border border-line bg-surface p-5 shadow-xs animate-rise [animation-delay:360ms]">
|
||
<div class="flex flex-wrap items-center gap-3">
|
||
<span class="grid size-11 place-items-center rounded-lg bg-ink font-semibold text-on-accent">B</span>
|
||
<div class="min-w-0">
|
||
<p class="font-semibold text-ink">{{ $cloud['name'] }}</p>
|
||
<a href="https://{{ $cloud['domain'] }}" target="_blank" rel="noopener noreferrer" class="font-mono text-sm text-accent-text hover:underline">{{ $cloud['domain'] }}</a>
|
||
</div>
|
||
<a href="https://{{ $cloud['domain'] }}" target="_blank" rel="noopener noreferrer"
|
||
class="ml-auto inline-flex items-center gap-2 rounded-pill bg-accent-active px-4 py-2 text-sm font-semibold text-on-accent transition hover:bg-accent-press">
|
||
<x-ui.icon name="external-link" class="size-4" />{{ __('dashboard.cloud.open') }}
|
||
</a>
|
||
</div>
|
||
<div class="mt-4 grid grid-cols-1 gap-3 border-t border-line pt-4 sm:grid-cols-2">
|
||
<div><p class="text-xs font-semibold uppercase tracking-wide text-faint">{{ __('dashboard.cloud.package') }}</p><p class="mt-1 text-sm text-body">{{ $cloud['package'] }}</p></div>
|
||
<div><p class="text-xs font-semibold uppercase tracking-wide text-faint">{{ __('dashboard.cloud.status') }}</p><p class="mt-1 text-sm text-success">{{ __('dashboard.cloud.active_since', ['date' => $cloud['since']]) }}</p></div>
|
||
<div><p class="text-xs font-semibold uppercase tracking-wide text-faint">{{ __('dashboard.cloud.location') }}</p><p class="mt-1 text-sm text-body">{{ $cloud['location'] }}</p></div>
|
||
<div><p class="text-xs font-semibold uppercase tracking-wide text-faint">{{ __('dashboard.cloud.version') }}</p><p class="mt-1 font-mono text-sm text-body">{{ $cloud['version'] }} · {{ __('dashboard.cloud.current') }}</p></div>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- Onboarding checklist (Alpine) --}}
|
||
<div
|
||
class="rounded-xl border border-line bg-surface p-5 shadow-xs animate-rise [animation-delay:420ms]"
|
||
x-data="{
|
||
tasks: @js(collect($onboarding)->map(fn ($t) => ['label' => $t['label'], 'done' => $t['done'], 'date' => $t['date']])),
|
||
total: {{ count($onboarding) }},
|
||
get doneCount() { return this.tasks.filter(t => t.done).length; },
|
||
get percent() { return Math.round(this.doneCount / this.total * 100); },
|
||
complete(i) {
|
||
if (this.tasks[i].done) return;
|
||
this.tasks[i].done = true;
|
||
$dispatch('notify', { message: @js(__('dashboard.onboarding.done_toast')) });
|
||
},
|
||
nextIndex() { return this.tasks.findIndex(t => !t.done); },
|
||
}"
|
||
>
|
||
<div class="flex items-baseline gap-3">
|
||
<h2 class="font-semibold text-ink">{{ __('dashboard.onboarding.title') }}</h2>
|
||
<span class="ml-auto font-mono text-sm font-semibold text-muted" x-text="doneCount + ' / ' + total"></span>
|
||
</div>
|
||
<div class="mt-3 h-1.5 overflow-hidden rounded-pill bg-surface-2">
|
||
<div class="h-full rounded-pill bg-accent transition-all duration-700" :style="`width: ${percent}%`"></div>
|
||
</div>
|
||
<ul class="mt-2">
|
||
<template x-for="(task, i) in tasks" :key="i">
|
||
<li>
|
||
<button
|
||
type="button"
|
||
class="flex w-full items-center gap-3 rounded-lg px-2.5 py-3 text-left text-sm transition hover:bg-surface-hover"
|
||
:class="!task.done && i === nextIndex() ? 'bg-accent-subtle' : ''"
|
||
:disabled="task.done"
|
||
@click="complete(i)"
|
||
>
|
||
<span
|
||
class="grid size-6 shrink-0 place-items-center rounded-pill border"
|
||
:class="task.done ? 'border-success-bright bg-success-bright text-on-accent' : (i === nextIndex() ? 'border-accent' : 'border-line-strong')"
|
||
>
|
||
<svg x-show="task.done" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" class="size-3.5" aria-hidden="true"><polyline points="20 6 9 17 4 12"/></svg>
|
||
</span>
|
||
<span class="min-w-0 flex-1" :class="task.done ? 'text-muted line-through' : 'text-body'" x-text="task.label"></span>
|
||
<span x-show="task.date" class="text-xs text-faint" x-text="task.date"></span>
|
||
<span x-show="!task.done && i === nextIndex()" class="text-xs font-semibold text-accent-text whitespace-nowrap">{{ __('dashboard.onboarding.review') }} ›</span>
|
||
</button>
|
||
</li>
|
||
</template>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- Right column --}}
|
||
<div class="flex flex-col gap-4">
|
||
{{-- Backups --}}
|
||
<div class="rounded-xl border border-line bg-surface p-5 shadow-xs animate-rise [animation-delay:360ms]">
|
||
<div class="flex items-center">
|
||
<h2 class="font-semibold text-ink">{{ __('dashboard.backups.title') }}</h2>
|
||
<span class="ml-auto rounded-pill bg-success-bg px-2.5 py-1 text-xs font-semibold text-success">{{ __('dashboard.backups.schedule') }}</span>
|
||
</div>
|
||
<ul class="mt-2 divide-y divide-line">
|
||
@foreach ($backups as $b)
|
||
<li class="flex items-center gap-3 py-2.5 text-sm">
|
||
<x-ui.icon name="check" class="size-4 text-success" />
|
||
<span class="font-mono text-xs text-muted">{{ $b['when'] }}</span>
|
||
<span class="ml-auto font-mono text-xs text-muted">{{ $b['size'] }}</span>
|
||
<x-ui.button variant="secondary" size="sm" @click="$dispatch('notify', { message: msgs.restore })">{{ __('dashboard.backups.restore') }}</x-ui.button>
|
||
</li>
|
||
@endforeach
|
||
</ul>
|
||
<p class="mt-3 flex gap-2 text-xs leading-relaxed text-muted">
|
||
<x-ui.icon name="shield" class="mt-px size-4 shrink-0 text-accent-active" />
|
||
<span>{{ __('dashboard.backups.note') }}</span>
|
||
</p>
|
||
</div>
|
||
|
||
{{-- Modules --}}
|
||
<div class="rounded-xl border border-line bg-surface p-5 shadow-xs animate-rise [animation-delay:420ms]">
|
||
<div class="flex items-center">
|
||
<h2 class="font-semibold text-ink">{{ __('dashboard.modules.title') }}</h2>
|
||
<span class="ml-auto rounded-pill bg-surface-2 px-2.5 py-1 text-xs font-semibold text-muted">{{ __('dashboard.modules.addon') }}</span>
|
||
</div>
|
||
<ul class="mt-2 divide-y divide-line">
|
||
@foreach ($modules as $m)
|
||
<li class="flex items-center gap-3 py-2.5 text-sm">
|
||
<span class="grid size-9 shrink-0 place-items-center rounded-lg bg-surface-2 text-muted"><x-ui.icon :name="$m['icon']" class="size-5" /></span>
|
||
<div class="min-w-0">
|
||
<p class="font-medium text-ink">{{ $m['name'] }}</p>
|
||
<p class="text-xs text-muted">{{ $m['desc'] }}</p>
|
||
</div>
|
||
@if ($m['status'] === 'active')
|
||
<span class="ml-auto rounded-pill bg-success-bg px-2.5 py-1 text-xs font-semibold text-success">{{ __('dashboard.modules.active') }}</span>
|
||
@elseif ($m['status'] === 'available')
|
||
<x-ui.button variant="secondary" size="sm" class="ml-auto" @click="$dispatch('notify', { message: msgs.addon })">{{ __('dashboard.modules.add') }}</x-ui.button>
|
||
@else
|
||
<span class="ml-auto rounded-pill bg-surface-2 px-2.5 py-1 text-xs font-semibold text-muted">{{ __('dashboard.modules.soon') }}</span>
|
||
@endif
|
||
</li>
|
||
@endforeach
|
||
</ul>
|
||
</div>
|
||
|
||
{{-- Activity feed --}}
|
||
<div class="rounded-xl border border-line bg-surface p-5 shadow-xs animate-rise [animation-delay:480ms]">
|
||
<div class="flex items-center">
|
||
<h2 class="font-semibold text-ink">{{ __('dashboard.activity') }}</h2>
|
||
<span class="ml-auto rounded-pill bg-surface-2 px-2.5 py-1 text-xs font-semibold text-muted">{{ __('dashboard.activity_live') }}</span>
|
||
</div>
|
||
<ul class="mt-2 divide-y divide-line">
|
||
@foreach ($activity as $a)
|
||
<li class="flex items-start gap-3 py-2.5 text-sm">
|
||
<span class="grid size-7 shrink-0 place-items-center rounded-lg bg-surface-2 text-accent-active"><x-ui.icon :name="$a['icon']" class="size-4" /></span>
|
||
<div class="min-w-0 flex-1">
|
||
<p class="text-body">{{ $a['text'] }}</p>
|
||
<p class="font-mono text-xs text-faint">{{ $a['time'] }}</p>
|
||
</div>
|
||
</li>
|
||
@endforeach
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|