180 lines
14 KiB
PHP
180 lines
14 KiB
PHP
<div class="space-y-6">
|
||
<div class="animate-rise">
|
||
<h1 class="text-2xl font-semibold tracking-tight text-ink">{{ __('maintenance.title') }}</h1>
|
||
<p class="mt-1 text-sm text-muted">{{ __('maintenance.subtitle') }}</p>
|
||
</div>
|
||
|
||
<div class="grid grid-cols-1 gap-6 lg:grid-cols-[1fr_420px] lg:items-start">
|
||
{{-- Windows list --}}
|
||
<div class="overflow-hidden rounded-xl border border-line bg-surface shadow-xs animate-rise [animation-delay:60ms]">
|
||
@if ($windows->isEmpty())
|
||
<p class="p-8 text-center text-sm text-muted">{{ __('maintenance.empty') }}</p>
|
||
@else
|
||
<div class="overflow-x-auto">
|
||
<table class="w-full text-sm">
|
||
<thead>
|
||
<tr class="border-b border-line bg-surface-2 text-left text-xs uppercase tracking-wide text-faint">
|
||
<th class="px-4 py-3 font-semibold">{{ __('maintenance.col_window') }}</th>
|
||
<th class="px-4 py-3 font-semibold">{{ __('maintenance.col_when') }}</th>
|
||
<th class="px-4 py-3 font-semibold">{{ __('maintenance.col_impact') }}</th>
|
||
<th class="px-4 py-3 font-semibold">{{ __('maintenance.col_state') }}</th>
|
||
<th class="px-4 py-3 text-right font-semibold">{{ __('maintenance.col_actions') }}</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@foreach ($windows as $w)
|
||
@php $tone = ['draft' => 'info', 'upcoming' => 'provisioning', 'active' => 'warning', 'completed' => 'active', 'cancelled' => 'suspended'][$w['state']] ?? 'info'; @endphp
|
||
<tr wire:key="mw-{{ $w['uuid'] }}" class="border-b border-line last:border-0 hover:bg-surface-hover">
|
||
<td class="px-4 py-3 font-medium text-ink">{{ $w['title'] }}</td>
|
||
<td class="px-4 py-3 text-xs text-body">{{ $w['starts_at']->isoFormat('DD.MM. HH:mm') }} – {{ $w['ends_at']->isoFormat('HH:mm') }}</td>
|
||
<td class="px-4 py-3 text-xs text-muted whitespace-nowrap">
|
||
{{ trans_choice('maintenance.impact_hosts', $w['hosts'], ['count' => $w['hosts']]) }}
|
||
<span class="text-faint">·</span>
|
||
{{ trans_choice('maintenance.impact_customers', $w['affected'], ['count' => $w['affected']]) }}
|
||
</td>
|
||
<td class="px-4 py-3"><x-ui.badge :status="$tone">{{ __('maintenance.state_'.$w['state']) }}</x-ui.badge></td>
|
||
<td class="px-4 py-3 text-right">
|
||
{{-- Icon buttons: the labels wrapped to two lines and made the
|
||
column unreadable. Same pattern as the other admin tables. --}}
|
||
<div class="inline-flex gap-1">
|
||
@if ($w['is_draft'])
|
||
<button type="button" wire:click="publishExisting('{{ $w['uuid'] }}')"
|
||
title="{{ __('maintenance.publish') }}" aria-label="{{ __('maintenance.publish') }}"
|
||
class="grid size-8 place-items-center rounded-md border border-line text-muted transition hover:border-accent-border hover:text-accent-text">
|
||
<x-ui.icon name="check" class="size-4" />
|
||
</button>
|
||
@endif
|
||
@if ($w['notifiable'])
|
||
<button type="button" wire:click="resend('{{ $w['uuid'] }}')"
|
||
title="{{ __('maintenance.notify_again') }}" aria-label="{{ __('maintenance.notify_again') }}"
|
||
class="grid size-8 place-items-center rounded-md border border-line text-muted transition hover:border-accent-border hover:text-accent-text">
|
||
<x-ui.icon name="bell" class="size-4" />
|
||
</button>
|
||
@endif
|
||
@if ($w['cancellable'])
|
||
<button type="button" wire:click="cancel('{{ $w['uuid'] }}')"
|
||
title="{{ __('maintenance.cancel') }}" aria-label="{{ __('maintenance.cancel') }}"
|
||
class="grid size-8 place-items-center rounded-md border border-line text-muted transition hover:border-danger hover:text-danger">
|
||
<x-ui.icon name="x" class="size-4" />
|
||
</button>
|
||
@endif
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
@endforeach
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
@endif
|
||
</div>
|
||
|
||
{{-- Create form --}}
|
||
<form wire:submit="publish" class="overflow-hidden rounded-xl border border-line bg-surface shadow-xs animate-rise [animation-delay:120ms]">
|
||
<div class="border-b border-line bg-surface-2 px-5 py-3.5">
|
||
<h2 class="font-semibold text-ink">{{ __('maintenance.new_title') }}</h2>
|
||
<p class="mt-0.5 text-xs text-muted">{{ __('maintenance.new_sub') }}</p>
|
||
</div>
|
||
|
||
<div class="space-y-5 p-5">
|
||
<x-ui.input name="title" wire:model="title" :label="__('maintenance.field_title')" placeholder="Netzwerk-Wartung Falkenstein" />
|
||
|
||
<div>
|
||
<label class="text-sm font-medium text-body" for="publicDescription">{{ __('maintenance.field_public') }}</label>
|
||
<textarea id="publicDescription" wire:model="publicDescription" rows="2" placeholder="{{ __('maintenance.field_public_ph') }}"
|
||
class="mt-1.5 w-full rounded-md border border-line-strong bg-surface px-3 py-2 text-sm text-ink placeholder:text-faint focus:border-accent focus:outline-none focus:ring-2 focus:ring-accent/30"></textarea>
|
||
<p class="mt-1 text-xs text-faint">{{ __('maintenance.field_public_hint') }}</p>
|
||
</div>
|
||
|
||
<div>
|
||
<label class="text-sm font-medium text-body" for="internalNotes">{{ __('maintenance.field_internal') }}</label>
|
||
<textarea id="internalNotes" wire:model="internalNotes" rows="2" placeholder="{{ __('maintenance.field_internal_ph') }}"
|
||
class="mt-1.5 w-full rounded-md border border-line-strong bg-surface px-3 py-2 text-sm text-ink placeholder:text-faint focus:border-accent focus:outline-none focus:ring-2 focus:ring-accent/30"></textarea>
|
||
</div>
|
||
|
||
<div class="space-y-3">
|
||
<div class="grid grid-cols-1 gap-3">
|
||
@foreach ([['startsAt', 'field_start'], ['endsAt', 'field_end']] as [$field, $key])
|
||
<div>
|
||
<label class="text-sm font-medium text-body" for="{{ $field }}">{{ __('maintenance.'.$key) }}</label>
|
||
<div class="relative mt-1.5">
|
||
<x-ui.icon name="calendar" class="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-faint" />
|
||
<input id="{{ $field }}" type="datetime-local" wire:model.live="{{ $field }}"
|
||
class="w-full rounded-md border border-line-strong bg-surface py-2 pl-9 pr-3 text-sm text-ink focus:border-accent focus:outline-none focus:ring-2 focus:ring-accent/30 [color-scheme:dark]" />
|
||
</div>
|
||
@error($field)<p class="mt-1 text-xs text-danger">{{ $message }}</p>@enderror
|
||
</div>
|
||
@endforeach
|
||
</div>
|
||
|
||
{{-- Typing an end time by hand is the fiddliest part of this form;
|
||
these set it from the start time. --}}
|
||
<div class="flex flex-wrap items-center gap-1.5">
|
||
<span class="text-xs text-faint">{{ __('maintenance.duration') }}</span>
|
||
@foreach ([30, 60, 120, 240] as $minutes)
|
||
<button type="button" wire:click="setDuration({{ $minutes }})"
|
||
class="rounded-pill border px-2.5 py-0.5 text-xs font-medium transition
|
||
{{ $durationMinutes === $minutes ? 'border-accent-border bg-accent-bg text-accent-text' : 'border-line text-muted hover:border-accent-border hover:text-accent-text' }}">
|
||
{{ $minutes < 60 ? $minutes.' min' : ($minutes / 60).' h' }}
|
||
</button>
|
||
@endforeach
|
||
@if ($durationMinutes !== null && ! in_array($durationMinutes, [30, 60, 120, 240], true))
|
||
<span class="rounded-pill border border-line-strong px-2.5 py-0.5 text-xs text-muted">{{ __('maintenance.duration_value', ['n' => $durationMinutes]) }}</span>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
|
||
{{-- Host picker, grouped by datacenter --}}
|
||
<div>
|
||
<div class="flex items-baseline justify-between">
|
||
<label class="text-sm font-medium text-body">{{ __('maintenance.field_hosts') }}</label>
|
||
<span class="font-mono text-xs {{ count($hostIds) > 0 ? 'text-accent-text' : 'text-faint' }}">{{ __('maintenance.selected', ['n' => count($hostIds)]) }}</span>
|
||
</div>
|
||
@error('hostIds')<p class="mt-1 text-xs text-danger">{{ $message }}</p>@enderror
|
||
<div class="mt-1.5 max-h-64 divide-y divide-line overflow-y-auto rounded-lg border border-line-strong bg-surface-2">
|
||
@foreach ($datacenters as $dc)
|
||
@php
|
||
$dcHosts = $hosts->where('datacenter', $dc->code);
|
||
$dcSelected = $dcHosts->whereIn('id', $hostIds)->count();
|
||
@endphp
|
||
@if ($dcHosts->isNotEmpty())
|
||
<div class="p-2.5">
|
||
<div class="flex items-center justify-between px-1.5 pb-1.5">
|
||
<p class="text-xs font-semibold uppercase tracking-wide text-faint">
|
||
{{ $dc->name }}
|
||
@if ($dcSelected)
|
||
<span class="ml-1 font-mono text-accent-text">{{ $dcSelected }}/{{ $dcHosts->count() }}</span>
|
||
@endif
|
||
</p>
|
||
<button type="button" wire:click="selectDatacenter('{{ $dc->code }}')"
|
||
class="rounded-pill border border-line px-2 py-0.5 text-[11px] font-semibold text-muted transition hover:border-accent-border hover:text-accent-text">
|
||
{{ $dcSelected === $dcHosts->count() ? __('maintenance.select_none') : __('maintenance.select_all') }}
|
||
</button>
|
||
</div>
|
||
<div class="space-y-1">
|
||
@foreach ($dcHosts as $host)
|
||
@php $checked = in_array($host->id, $hostIds); @endphp
|
||
<label class="flex cursor-pointer items-center gap-2.5 rounded-md border px-2.5 py-2 text-sm transition
|
||
{{ $checked ? 'border-accent-border bg-accent-bg text-ink' : 'border-transparent text-body hover:border-line hover:bg-surface-hover' }}">
|
||
<input type="checkbox" wire:model.live="hostIds" value="{{ $host->id }}"
|
||
class="size-4 rounded border-line-strong bg-surface text-accent-active focus:ring-2 focus:ring-accent/30" />
|
||
<span class="font-mono">{{ $host->name }}</span>
|
||
<span class="ml-auto font-mono text-xs {{ $checked ? 'text-accent-text' : 'text-faint' }}">{{ $host->public_ip }}</span>
|
||
</label>
|
||
@endforeach
|
||
</div>
|
||
</div>
|
||
@endif
|
||
@endforeach
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flex flex-wrap items-center justify-end gap-2 border-t border-line bg-surface-2 px-5 py-3.5">
|
||
<p class="mr-auto max-w-[14rem] text-xs text-faint">{{ __('maintenance.mail_note') }}</p>
|
||
<x-ui.button variant="secondary" type="button" size="sm" wire:click="saveDraft" wire:loading.attr="disabled" wire:target="saveDraft">{{ __('maintenance.save_draft') }}</x-ui.button>
|
||
<x-ui.button variant="primary" type="submit" size="sm" wire:loading.attr="disabled" wire:target="publish">{{ __('maintenance.publish_notify') }}</x-ui.button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|