mailwolt/app/Livewire/Ui/Nx/Mail/QueueList.php

189 lines
6.4 KiB
PHP

<?php
namespace App\Livewire\Ui\Nx\Mail;
use Illuminate\Pagination\LengthAwarePaginator;
use Livewire\Attributes\Layout;
use Livewire\Attributes\On;
use Livewire\Attributes\Title;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
#[Layout('layouts.dvx')]
#[Title('Mail-Queue · Mailwolt')]
class QueueList extends Component
{
use WithPagination;
#[Url(as: 'filter', keep: true)]
public string $filter = 'all';
#[Url(as: 'q', keep: true)]
public string $search = '';
#[Url(as: 'limit', keep: true)]
public int $perPage = 25;
public array $selected = [];
public bool $selectAll = false;
#[On('queue:updated')]
public function refresh(): void {}
public function updatedFilter(): void { $this->resetPage(); $this->selected = []; $this->selectAll = false; }
public function updatedSearch(): void { $this->resetPage(); }
public function updatedPerPage(): void { $this->resetPage(); $this->selected = []; $this->selectAll = false; }
public function updatedSelectAll(bool $val): void
{
$messages = $this->fetchQueue();
$this->selected = $val ? array_column($messages, 'id') : [];
}
public function flush(): void
{
@shell_exec('sudo postqueue -f >/dev/null 2>&1 &');
$this->dispatch('toast', type: 'info', title: 'Queue-Flush gestartet');
}
public function deleteSelected(): void
{
$count = count($this->selected);
foreach ($this->selected as $id) {
@shell_exec('sudo postsuper -d ' . escapeshellarg($id) . ' 2>&1');
}
$this->selected = [];
$this->selectAll = false;
$this->dispatch('toast', type: 'success', title: "{$count} Nachricht(en) gelöscht");
}
public function deleteAll(): void
{
@shell_exec('sudo postsuper -d ALL 2>&1');
$this->selected = [];
$this->selectAll = false;
$this->dispatch('toast', type: 'warning', title: 'Alle Queue-Nachrichten gelöscht');
}
public function openMessage(string $queueId): void
{
$this->dispatch('openModal',
component: 'ui.nx.mail.modal.queue-message-modal',
arguments: ['queueId' => $queueId]
);
}
public function render()
{
$all = $this->fetchQueue();
$counts = [
'all' => count($all),
'active' => count(array_filter($all, fn($m) => $m['queue'] === 'active')),
'deferred' => count(array_filter($all, fn($m) => $m['queue'] === 'deferred')),
'hold' => count(array_filter($all, fn($m) => $m['queue'] === 'hold')),
];
$messages = $all;
if ($this->filter !== 'all') {
$messages = array_values(array_filter($messages, fn($m) => $m['queue'] === $this->filter));
}
if ($this->search !== '') {
$s = strtolower($this->search);
$messages = array_values(array_filter($messages, fn($m) =>
str_contains(strtolower($m['sender'] ?? ''), $s) ||
str_contains(strtolower($m['recipient'] ?? ''), $s) ||
str_contains(strtolower($m['id'] ?? ''), $s)
));
}
$total = count($messages);
$currentPage = LengthAwarePaginator::resolveCurrentPage();
$paged = new LengthAwarePaginator(
array_slice($messages, ($currentPage - 1) * $this->perPage, $this->perPage),
$total,
$this->perPage,
$currentPage,
['path' => request()->url()]
);
return view('livewire.ui.nx.mail.queue-list', [
'messages' => $paged,
'counts' => $counts,
]);
}
// ── helpers ──────────────────────────────────────────────────────────────
public function fetchQueue(): array
{
$out = trim(@shell_exec('postqueue -j 2>/dev/null') ?? '');
if ($out !== '') {
return $this->parseJson($out);
}
return $this->parseText(trim(@shell_exec('postqueue -p 2>/dev/null') ?? ''));
}
private function parseJson(string $out): array
{
$rows = [];
foreach (explode("\n", $out) as $line) {
$line = trim($line);
if ($line === '') continue;
$m = json_decode($line, true);
if (!is_array($m)) continue;
$recipients = $m['recipients'] ?? [];
$rcptList = array_column($recipients, 'address');
$reason = $recipients[0]['delay_reason'] ?? '';
$rows[] = [
'id' => $m['queue_id'] ?? '',
'queue' => $m['queue_name'] ?? 'deferred',
'sender' => $m['sender'] ?? '',
'recipient' => implode(', ', $rcptList),
'size' => (int)($m['message_size'] ?? 0),
'arrival' => (int)($m['arrival_time'] ?? 0),
'reason' => $this->trimReason($reason),
];
}
return $rows;
}
private function parseText(string $out): array
{
$rows = [];
$current = null;
foreach (explode("\n", $out) as $line) {
if (preg_match('/^([A-F0-9]{9,})\*?\s+(\d+)\s+\S+\s+\S+\s+\d+\s+\d{1,2}:\d{2}:\d{2}\s+(.*)/i', $line, $m)) {
if ($current) $rows[] = $current;
$current = [
'id' => $m[1],
'queue' => 'active',
'sender' => trim($m[3]),
'recipient' => '',
'size' => (int)$m[2],
'arrival' => 0,
'reason' => '',
];
} elseif ($current && preg_match('/^\s+([\w.+%-]+@[\w.-]+)/', $line, $m)) {
$current['recipient'] = $m[1];
} elseif ($current && preg_match('/^\s+\((.+)\)/', $line, $m)) {
$current['reason'] = $this->trimReason($m[1]);
$current['queue'] = 'deferred';
}
}
if ($current) $rows[] = $current;
return $rows;
}
private function trimReason(string $r): string
{
$r = preg_replace('/^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\s+/', '', trim($r));
return mb_strlen($r) > 100 ? mb_substr($r, 0, 100) . '…' : $r;
}
}