CluPilotCloud/routes/admin.php

67 lines
3.2 KiB
PHP

<?php
use App\Http\Controllers\ImpersonationController;
use App\Livewire\Admin;
use Illuminate\Support\Facades\Route;
/*
| The operator console.
|
| Its own file, and registered BEFORE routes/web.php, because once the console
| has a hostname to itself it lives at the ROOT of that host — where `/` and
| `/settings` also exist in the customer portal, and the first matching route
| wins.
|
| Where it is mounted is AdminArea's decision, not this file's: `/` on the
| console hostname where the console has one to itself, `/admin` on any host
| otherwise. Route NAMES are `admin.*` in both cases, so every route('admin.…')
| in the views is unaffected by which mode an installation is in.
|
| RestrictAdminHost is listed here as well as prepended to the `web` group:
| Livewire records it on the component snapshot and re-applies it to
| /livewire/update, so a console action cannot be driven through another host.
*/
Route::get('/', Admin\Overview::class)->name('overview');
Route::get('/customers', Admin\Customers::class)->name('customers');
Route::get('/instances', Admin\Instances::class)->name('instances');
Route::get('/hosts', Admin\Hosts::class)->name('hosts');
Route::get('/hosts/create', Admin\HostCreate::class)->name('hosts.create');
Route::get('/hosts/{host}', Admin\HostDetail::class)->name('hosts.show');
Route::get('/datacenters', Admin\Datacenters::class)->name('datacenters');
Route::get('/plans', Admin\Plans::class)->name('plans');
Route::get('/plans/{uuid}', Admin\PlanVersions::class)->name('plans.versions');
// POST so the identity change is CSRF-protected (a GET could be forced cross-site).
Route::post('/impersonate/{customer}', [ImpersonationController::class, 'start'])->name('impersonate');
Route::get('/provisioning', Admin\Provisioning::class)->name('provisioning');
Route::get('/maintenance', Admin\Maintenance::class)->name('maintenance');
Route::get('/vpn', Admin\Vpn::class)->name('vpn');
Route::get('/revenue', Admin\Revenue::class)->name('revenue');
Route::get('/mail', Admin\Mail::class)->name('mail');
Route::get('/secrets', Admin\Secrets::class)->name('secrets');
Route::get('/settings', Admin\Settings::class)->name('settings');
/*
* A deployment restarts the very application that is watching it.
*
* The settings card polls itself with wire:poll, which is fine until the run
* reaches the restart: the requests fail, and what comes back afterwards is a
* new build being asked questions by the old page's JavaScript. So the card
* never learned that the update had finished and an operator had to reload to
* find out — reported exactly that way.
*
* This is the smallest thing that can answer "is it done, and is it still the
* same build" across that gap: no Livewire, no component state, no assets. A
* failed request here is expected (it IS the restart) and the poller keeps
* trying rather than giving up.
*/
Route::get('/update/state', function (\App\Services\Deployment\UpdateChannel $channel) {
$state = $channel->state();
return response()->json([
'running' => $state['running'],
'commit' => $state['commit'],
'behind' => $state['behind'],
'last_finished_at' => $state['last_finished_at']?->toIso8601String(),
])->header('Cache-Control', 'no-store');
})->name('update.state');