95 lines
3.8 KiB
PHP
95 lines
3.8 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\Admin;
|
|
|
|
use App\Models\Host;
|
|
use App\Models\Operator;
|
|
use App\Services\Terminal\TerminalTicket;
|
|
use Livewire\Attributes\Layout;
|
|
use Livewire\Component;
|
|
|
|
/**
|
|
* Die Seite im eigenen Fenster.
|
|
*
|
|
* Sie hat bewusst kein Layout der Konsole: hier steht ein Terminal, das die
|
|
* volle Fläche bekommt, und eine Seitenleiste daneben wäre ein Rahmen um eine
|
|
* Sache, die keinen braucht.
|
|
*
|
|
* Das Ticket entsteht beim Aufruf und lebt dreißig Sekunden — also genau so
|
|
* lange, wie das Fenster zum Verbinden braucht. Wer die Seite offen liegen
|
|
* lässt und später neu lädt, bekommt ein frisches.
|
|
*
|
|
* Berechtigung ZUERST, Host-Suche DANACH: wer `hosts.manage` nicht hat, soll
|
|
* auch nicht per Antwortzeit oder 404-gegen-403 lernen können, ob eine UUID zu
|
|
* einem echten Host gehört.
|
|
*
|
|
* Die Eigenschaft heißt bewusst NICHT `$host` wie der Routenparameter, obwohl
|
|
* das näherliegend gewesen wäre und die Ansicht genau diesen Namen erwartet
|
|
* (siehe render() unten, das ihn dort wieder einsetzt). Livewires eigene
|
|
* `ImplicitRouteBinding` löst JEDE öffentliche, typisierte Eigenschaft aUTOMATISCH
|
|
* gegen einen gleichnamigen Routenparameter auf — unabhängig vom `mount()`
|
|
* -Rumpf. Mit `public Host $host` UND einem Parameter `{host}` in der Route
|
|
* bekam `mount(string $host)` beim Testlauf nicht die UUID, sondern ein
|
|
* bereits (und ungeprüft!) aufgelöstes `Host`-Objekt, von PHP wegen der
|
|
* Typangabe `string` über `__toString()` in JSON verwandelt — und genau die
|
|
* Prüf-Reihenfolge oben ausgehebelt, weil die Auflösung dann VOR jedem
|
|
* `authorize()`-Aufruf passiert. Ein anderer Name für die Eigenschaft nimmt
|
|
* dem Mechanismus die Angriffsfläche, ohne die Route umzubenennen (die
|
|
* Schnittstelle — `route('admin.hosts.terminal', ['host' => …])` — bleibt
|
|
* unverändert, das betrifft nur, wie diese Klasse den Wert intern hält).
|
|
*/
|
|
#[Layout('layouts.bare')]
|
|
class HostTerminal extends Component
|
|
{
|
|
public Host $record;
|
|
|
|
public string $ticket = '';
|
|
|
|
/**
|
|
* Warum hier kein Terminal entstehen kann — ein Merkwort aus
|
|
* `TerminalTicket::blocker()`, oder leer, wenn nichts im Weg steht.
|
|
*/
|
|
public string $problem = '';
|
|
|
|
public function mount(string $host): void
|
|
{
|
|
$this->authorize('hosts.manage');
|
|
|
|
$this->record = Host::query()->where('uuid', $host)->firstOrFail();
|
|
|
|
/** @var Operator $operator */
|
|
$operator = auth('operator')->user();
|
|
|
|
// Diese Seite wirft nicht. Ein Terminalfenster, das mit Laravels
|
|
// Fehlerseite aufgeht — Klassenname, Dateipfad, Zeilennummer,
|
|
// Quelltextauszug —, ist ein Bruch mitten im Produkt: der Betreiber hat
|
|
// einen Knopf gedrückt und bekommt einen Stacktrace. Was schiefgehen
|
|
// kann, steht in `blocker()` und wird unten in der Sprache dieses
|
|
// Fensters hingeschrieben.
|
|
$this->problem = TerminalTicket::blocker($this->record) ?? '';
|
|
|
|
if ($this->problem !== '') {
|
|
return;
|
|
}
|
|
|
|
// Der Fangzaun dahinter: `blocker()` kennt die Gründe, mit denen wir
|
|
// rechnen. Reißt `issue()` aus einem, mit dem niemand gerechnet hat
|
|
// (Redis weg, Tresor kaputt), ist die Antwort dieselbe — eine gestaltete
|
|
// Seite statt einer Fehlerseite —, nur ohne Versprechen, woran es lag.
|
|
try {
|
|
$this->ticket = TerminalTicket::issue($this->record, $operator);
|
|
} catch (\Throwable $e) {
|
|
report($e);
|
|
$this->problem = 'unknown';
|
|
}
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
// 'host' ist der Name, den die Ansicht erwartet — die Eigenschaft
|
|
// heißt aus dem oben erklärten Grund anders.
|
|
return view('livewire.admin.host-terminal', ['host' => $this->record])
|
|
->title($this->record->name.' — Terminal');
|
|
}
|
|
}
|