diff --git a/app/Livewire/Services/Index.php b/app/Livewire/Services/Index.php index 86754c4..842a81d 100644 --- a/app/Livewire/Services/Index.php +++ b/app/Livewire/Services/Index.php @@ -79,14 +79,32 @@ class Index extends Component ); } - /** Applies the confirmed service state change (optimistic until SSH exec lands). */ + /** Performs the confirmed systemctl action over SSH, then reloads real state. */ #[On('serviceConfirmed')] - public function applyService(string $op, string $name): void + public function applyService(string $op, string $name, FleetService $fleet): void { - foreach ($this->services as $i => $svc) { - if ($svc['name'] === $name) { - $this->services[$i]['status'] = $op === 'stop' ? 'offline' : 'online'; - } + $active = $this->activeServer(); + + if (! $active || ! $active->credential_exists) { + return; + } + + try { + $res = $fleet->serviceAction($active, $op, $name); + } catch (Throwable $e) { + $this->dispatch('notify', message: "{$name}: ".$e->getMessage()); + + return; + } + + $this->dispatch('notify', message: $res['ok'] + ? "{$name}: {$op} ausgeführt." + : "{$name}: fehlgeschlagen — ".\Illuminate\Support\Str::limit($res['output'] ?: 'keine Rechte', 90)); + + try { + $this->services = $fleet->systemd($active)['services']; + } catch (Throwable) { + // keep current list on reload failure } } diff --git a/app/Services/FleetService.php b/app/Services/FleetService.php index 4933400..e071776 100644 --- a/app/Services/FleetService.php +++ b/app/Services/FleetService.php @@ -211,7 +211,7 @@ class FleetService $cmd = 'export LC_ALL=C; ' .'echo '.self::MARK.'units===; systemctl list-units --type=service --all --plain --no-legend --no-pager; ' .'echo '.self::MARK.'files===; systemctl list-unit-files --type=service --no-legend --no-pager; ' - .'echo '.self::MARK.'journal===; journalctl -n '.(int) $journalLines.' --no-pager -o short-iso 2>&1'; + .'echo '.self::MARK.'journal===; if [ "$(id -u)" = 0 ]; then SUDO=""; else SUDO="sudo -n"; fi; $SUDO journalctl -n '.(int) $journalLines.' --no-pager -o short-iso 2>&1'; try { $s = $this->sections($ssh->exec($cmd)); @@ -373,6 +373,35 @@ class FleetService } } + /** + * systemctl start/stop/restart. Runs directly as root, else via `sudo -n` + * (needs a root or nopasswd-sudo credential). + * + * @return array{ok: bool, output: string} + */ + public function serviceAction(Server $server, string $op, string $unit): array + { + if (! in_array($op, ['start', 'stop', 'restart'], true)) { + throw new InvalidArgumentException('Unbekannte Aktion.'); + } + if (! preg_match('/^[\w@.:-]+$/', $unit)) { + throw new InvalidArgumentException('Ungueltige Unit.'); + } + + $ssh = $this->client($server); + + try { + [$out, $code] = $ssh->run( + 'export LC_ALL=C; if [ "$(id -u)" = 0 ]; then SUDO=""; else SUDO="sudo -n"; fi; ' + ."\$SUDO systemctl {$op} {$unit} 2>&1" + ); + + return ['ok' => $code === 0, 'output' => trim($out)]; + } finally { + $ssh->disconnect(); + } + } + // ── parsers ───────────────────────────────────────────────────────── private function lines(string $body): array