CluPilotCloud/app/Livewire/Admin/HostTerminal.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');
}
}