116 lines
6.7 KiB
PHP
116 lines
6.7 KiB
PHP
@php
|
|
$svcLabel = ['online' => __('services.status_online'), 'warning' => __('services.status_warning'), 'offline' => __('services.status_offline')];
|
|
$list = $this->filteredServices;
|
|
$total = count($services);
|
|
$active = collect($services)->where('status', 'online')->count();
|
|
$failed = collect($services)->where('status', 'offline')->count();
|
|
$fleetTone = $failed > 0 ? 'offline' : (collect($services)->where('status', 'warning')->count() > 0 ? 'warning' : 'online');
|
|
|
|
// journal level → token color (status triad + neutral for info)
|
|
$logTone = ['info' => 'text-ink-3', 'warn' => 'text-warning', 'error' => 'text-offline'];
|
|
@endphp
|
|
|
|
<div class="space-y-4" wire:init="load">
|
|
{{-- Header --}}
|
|
<div class="flex flex-wrap items-end justify-between gap-3">
|
|
<div>
|
|
<p class="font-mono text-[11px] uppercase tracking-[0.2em] text-accent-text">{{ __('services.eyebrow') }}</p>
|
|
<h2 class="mt-0.5 font-display text-xl font-semibold text-ink sm:text-2xl">{{ __('services.heading') }}</h2>
|
|
<p class="mt-0.5 font-mono text-[11px] text-ink-3">{{ $server }}</p>
|
|
</div>
|
|
<x-status-pill :status="$fleetTone">{{ __('services.fleet_active', ['active' => $active, 'total' => $total]) }}</x-status-pill>
|
|
</div>
|
|
|
|
{{-- systemd services --}}
|
|
<x-panel :title="__('services.panel_title')" :subtitle="__('services.panel_subtitle', ['total' => $total, 'server' => $server])" :padded="false">
|
|
<x-slot:actions>
|
|
<label class="relative block">
|
|
<span class="sr-only">{{ __('services.search_label') }}</span>
|
|
<x-icon name="search" class="pointer-events-none absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-ink-4" />
|
|
<input
|
|
type="search"
|
|
data-page-search
|
|
wire:model.live.debounce.250ms="search"
|
|
placeholder="{{ __('services.search_placeholder') }}"
|
|
class="h-9 w-40 rounded-md border border-line bg-inset pl-8 pr-3 font-mono text-xs text-ink placeholder:text-ink-4 focus:border-accent/40 focus:outline-none sm:w-56"
|
|
/>
|
|
</label>
|
|
</x-slot:actions>
|
|
|
|
<div class="max-h-[32rem] divide-y divide-line overflow-y-auto">
|
|
@if (! $ready)
|
|
@for ($i = 0; $i < 7; $i++)
|
|
<div class="flex items-center gap-3 px-4 py-3.5 sm:px-5">
|
|
<x-skeleton class="h-2.5 w-2.5 rounded-full" />
|
|
<div class="flex-1 space-y-2"><x-skeleton class="h-3 w-44 max-w-full" /><x-skeleton class="h-2.5 w-64 max-w-full" /></div>
|
|
<x-skeleton class="hidden h-6 w-24 sm:block" />
|
|
</div>
|
|
@endfor
|
|
@else
|
|
@forelse ($list as $svc)
|
|
<div class="flex flex-col gap-3 px-4 py-3 sm:px-5 lg:flex-row lg:items-center">
|
|
{{-- identity --}}
|
|
<div class="flex min-w-0 flex-1 items-center gap-3">
|
|
<x-status-dot :status="$svc['status']" :ping="$svc['status'] === 'online'" class="mt-1 self-start lg:mt-0 lg:self-center" />
|
|
<div class="min-w-0 flex-1">
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
<p class="truncate font-mono text-sm text-ink">{{ $svc['name'] }}</p>
|
|
@if ($svc['enabled'])
|
|
<x-badge tone="neutral">enabled</x-badge>
|
|
@else
|
|
<x-badge tone="neutral">disabled</x-badge>
|
|
@endif
|
|
</div>
|
|
<p class="truncate text-[11px] text-ink-3">{{ $svc['desc'] }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- status + actions --}}
|
|
<div class="flex shrink-0 items-center justify-between gap-2 pl-5 lg:justify-end lg:pl-0">
|
|
<x-status-pill :status="$svc['status']">{{ $svcLabel[$svc['status']] }}</x-status-pill>
|
|
|
|
{{-- R5: wire to wire-elements/modal --}}
|
|
<div class="flex items-center gap-1.5">
|
|
<x-btn variant="secondary" wire:click="confirm('start', {{ $loop->index }})" wire:loading.attr="disabled" :disabled="$svc['status'] === 'online'">{{ __('services.start') }}</x-btn>
|
|
<x-btn variant="secondary" wire:click="confirm('stop', {{ $loop->index }})" wire:loading.attr="disabled" :disabled="$svc['status'] === 'offline'">{{ __('services.stop') }}</x-btn>
|
|
<x-btn variant="accent" wire:click="confirm('restart', {{ $loop->index }})" wire:loading.attr="disabled" :disabled="$svc['status'] === 'offline'">{{ __('services.restart') }}</x-btn>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@empty
|
|
<div class="px-4 py-10 text-center sm:px-5">
|
|
<p class="font-mono text-sm text-ink-3">{{ __('services.empty_title') }}</p>
|
|
<p class="mt-1 font-mono text-[11px] text-ink-4">{{ __('services.empty_hint', ['search' => $search]) }}</p>
|
|
</div>
|
|
@endforelse
|
|
@endif
|
|
</div>
|
|
</x-panel>
|
|
|
|
{{-- Journal --}}
|
|
<x-panel :title="__('services.journal_title')" :subtitle="__('services.journal_subtitle', ['server' => $server])" :padded="false">
|
|
<x-slot:actions>
|
|
<x-badge tone="cyan">{{ __('services.journal_live') }}</x-badge>
|
|
</x-slot:actions>
|
|
|
|
<div @if ($ready && $connected) wire:poll.5s="pollJournal" @endif class="overflow-x-auto">
|
|
<div class="min-w-[640px] divide-y divide-line-soft font-mono text-[11px] leading-relaxed sm:min-w-0">
|
|
@foreach ($journal as $line)
|
|
<div class="flex items-start gap-3 px-4 py-1.5 sm:px-5">
|
|
<span class="shrink-0 tabular text-ink-4">{{ $line['time'] }}</span>
|
|
<span class="w-28 shrink-0 truncate text-ink-2">{{ $line['unit'] }}[1]:</span>
|
|
<span class="min-w-0 flex-1 break-words {{ $logTone[$line['level']] ?? 'text-ink-3' }}">{{ $line['text'] }}</span>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
|
|
<div class="border-t border-line px-4 py-2 sm:px-5">
|
|
<p class="flex items-center gap-1.5 font-mono text-[11px] text-ink-4">
|
|
<span class="inline-flex h-1.5 w-1.5 rounded-full bg-online"></span>
|
|
{{ __('services.journal_follow', ['count' => count($journal)]) }}
|
|
</p>
|
|
</div>
|
|
</x-panel>
|
|
</div>
|