clusev/resources/views/livewire/alerts/index.blade.php

177 lines
9.9 KiB
PHP

@php
$field = 'h-9 w-full rounded-md border border-line bg-inset px-3 text-sm text-ink focus:border-accent/40 focus:outline-none';
$label = 'mb-1 block font-mono text-[11px] uppercase tracking-wider text-ink-3';
$scopeName = function ($rule) use ($groupNames, $serverNames) {
return match ($rule->scope_type) {
'group' => $groupNames[$rule->scope_id] ?? __('alerts.scope_group'),
'server' => $serverNames[$rule->scope_id] ?? __('alerts.scope_server'),
default => __('alerts.scope_all'),
};
};
@endphp
<div class="space-y-5">
{{-- Header --}}
<div>
<p class="font-mono text-[11px] uppercase tracking-[0.2em] text-accent-text">{{ __('alerts.eyebrow') }}</p>
<h2 class="mt-0.5 font-display text-xl font-semibold text-ink sm:text-2xl">{{ __('alerts.title') }}</h2>
<p class="mt-1 text-sm text-ink-3">{{ __('alerts.subtitle') }}</p>
</div>
{{-- Active incidents --}}
<x-panel :title="__('alerts.incidents_title')" :subtitle="__('alerts.incidents_subtitle')" :padded="false">
@if ($incidents->isEmpty())
<div class="flex flex-col items-center gap-2 py-8 text-center">
<x-status-dot status="online" class="h-2.5 w-2.5" />
<p class="font-display text-sm font-semibold text-ink">{{ __('alerts.no_incidents_title') }}</p>
<p class="text-xs text-ink-3">{{ __('alerts.no_incidents') }}</p>
</div>
@else
<div class="divide-y divide-line">
@foreach ($incidents as $incident)
<div wire:key="inc-{{ $incident->id }}" class="flex flex-wrap items-center gap-3 px-4 py-3 sm:px-5">
<x-status-dot status="offline" :ping="true" class="h-2.5 w-2.5" />
<div class="min-w-0 flex-1">
<p class="truncate text-sm font-medium text-ink">{{ $incident->rule?->name ?? '—' }}</p>
<p class="truncate font-mono text-[11px] text-ink-3">{{ $incident->server?->name ?? '—' }} · {{ __('alerts.incident_value', ['value' => $incident->value]) }}</p>
</div>
<span class="shrink-0 font-mono text-[11px] text-ink-4">{{ __('alerts.incident_since', ['time' => $incident->started_at?->diffForHumans() ?? '—']) }}</span>
</div>
@endforeach
</div>
@endif
</x-panel>
{{-- New rule --}}
<x-panel :title="__('alerts.new_rule')">
<form wire:submit="createRule" class="space-y-4">
<div class="grid gap-4 sm:grid-cols-2">
<div>
<label class="{{ $label }}">{{ __('alerts.name_label') }}</label>
<input wire:model="name" type="text" maxlength="80" placeholder="{{ __('alerts.name_placeholder') }}" class="{{ $field }} placeholder:text-ink-4" />
@error('name') <p class="mt-1 flex items-center gap-1.5 font-mono text-[11px] text-offline"><x-icon name="alert" class="h-3.5 w-3.5 shrink-0" />{{ $message }}</p> @enderror
</div>
<div>
<label class="{{ $label }}">{{ __('alerts.metric_label') }}</label>
<select wire:model.live="metric" class="{{ $field }}">
@foreach ($metrics as $m)
<option value="{{ $m }}">{{ __('alerts.metric_'.$m) }}</option>
@endforeach
</select>
</div>
</div>
@if ($metric !== 'offline')
<div class="grid gap-4 sm:grid-cols-2">
<div>
<label class="{{ $label }}">{{ __('alerts.comparator_label') }}</label>
<select wire:model="comparator" class="{{ $field }}">
@foreach ($comparators as $c)
<option value="{{ $c }}">{{ __('alerts.comparator_'.$c) }}</option>
@endforeach
</select>
</div>
<div>
<label class="{{ $label }}">{{ __('alerts.threshold_label') }}</label>
<input wire:model="threshold" type="number" min="0" max="100" class="{{ $field }} font-mono" />
@error('threshold') <p class="mt-1 flex items-center gap-1.5 font-mono text-[11px] text-offline"><x-icon name="alert" class="h-3.5 w-3.5 shrink-0" />{{ $message }}</p> @enderror
</div>
</div>
@endif
<div class="grid gap-4 sm:grid-cols-2">
<div>
<label class="{{ $label }}">{{ __('alerts.scope_label') }}</label>
<select wire:model.live="scopeType" class="{{ $field }}">
@foreach ($scopes as $s)
<option value="{{ $s }}">{{ __('alerts.scope_'.$s) }}</option>
@endforeach
</select>
</div>
@if ($scopeType === 'group')
<div>
<label class="{{ $label }}">{{ __('alerts.scope_target_label') }}</label>
<select wire:model="scopeUuid" class="{{ $field }}">
<option value=""></option>
@foreach ($groups as $grp)
<option value="{{ $grp->uuid }}">{{ $grp->name }}</option>
@endforeach
</select>
</div>
@elseif ($scopeType === 'server')
<div>
<label class="{{ $label }}">{{ __('alerts.scope_target_label') }}</label>
<select wire:model="scopeUuid" class="{{ $field }}">
<option value=""></option>
@foreach ($servers as $srv)
<option value="{{ $srv->uuid }}">{{ $srv->name }}</option>
@endforeach
</select>
</div>
@endif
</div>
<div class="flex justify-end">
<x-btn variant="primary" type="submit">{{ __('alerts.create') }}</x-btn>
</div>
</form>
</x-panel>
{{-- Rules --}}
<x-panel :title="__('alerts.rules_title')" :padded="false">
@if ($rules->isEmpty())
<div class="px-4 py-8 text-center sm:px-5">
<p class="font-display text-sm font-semibold text-ink">{{ __('alerts.no_rules_title') }}</p>
<p class="mt-1 text-xs text-ink-3">{{ __('alerts.no_rules') }}</p>
</div>
@else
<div class="divide-y divide-line">
@foreach ($rules as $rule)
<div wire:key="rule-{{ $rule->uuid }}" class="flex flex-wrap items-center gap-3 px-4 py-3 sm:px-5">
<div class="min-w-0 flex-1">
<p class="truncate text-sm font-medium text-ink">{{ $rule->name }}</p>
<p class="truncate font-mono text-[11px] text-ink-3">
@if ($rule->metric === 'offline')
{{ __('alerts.rule_summary_offline', ['scope' => $scopeName($rule)]) }}
@else
{{ __('alerts.rule_summary', ['scope' => $scopeName($rule), 'metric' => __('alerts.metric_'.$rule->metric), 'comparator' => __('alerts.comparator_'.$rule->comparator), 'threshold' => $rule->threshold]) }}
@endif
</p>
</div>
<button type="button" wire:click="toggleRule('{{ $rule->uuid }}')"
@class([
'inline-flex min-h-9 shrink-0 items-center gap-1.5 rounded-md border px-2.5 text-xs transition-colors',
'border-online/20 bg-online/10 text-online' => $rule->enabled,
'border-line bg-inset text-ink-3 hover:bg-raised' => ! $rule->enabled,
])>
<x-status-dot :status="$rule->enabled ? 'online' : 'offline'" class="h-2 w-2" />
{{ $rule->enabled ? __('alerts.enabled') : __('alerts.disabled') }}
</button>
<x-modal-trigger variant="danger-soft" action="confirmDeleteRule('{{ $rule->uuid }}')">{{ __('common.delete') }}</x-modal-trigger>
</div>
@endforeach
</div>
@endif
</x-panel>
{{-- Channels --}}
<x-panel :title="__('alerts.channels_title')" :subtitle="__('alerts.channels_subtitle')">
<form wire:submit="saveChannels" class="space-y-4">
<div>
<label class="{{ $label }}">{{ __('alerts.email_label') }}</label>
<textarea wire:model="emailTo" rows="2" class="{{ $field }} h-auto py-2.5 font-mono" placeholder="ops@example.com"></textarea>
<p class="mt-1 font-mono text-[11px] text-ink-4">{{ __('alerts.email_hint') }}</p>
</div>
<div>
<label class="{{ $label }}">{{ __('alerts.webhooks_label') }}</label>
<textarea wire:model="webhooks" rows="2" class="{{ $field }} h-auto py-2.5 font-mono" placeholder="https://hooks.slack.com/…"></textarea>
@error('webhooks') <p class="mt-1 flex items-center gap-1.5 font-mono text-[11px] text-offline"><x-icon name="alert" class="h-3.5 w-3.5 shrink-0" />{{ $message }}</p> @enderror
<p class="mt-1 font-mono text-[11px] text-ink-4">{{ __('alerts.webhooks_hint') }}</p>
</div>
<div class="flex justify-end">
<x-btn variant="primary" type="submit">{{ __('alerts.save') }}</x-btn>
</div>
</form>
</x-panel>
</div>