diff --git a/VERSION b/VERSION index 6232e7e..f5301e2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.3.46 +1.3.47 diff --git a/app/Livewire/Admin/Overview.php b/app/Livewire/Admin/Overview.php index 7987e00..87435ca 100644 --- a/app/Livewire/Admin/Overview.php +++ b/app/Livewire/Admin/Overview.php @@ -6,6 +6,8 @@ use App\Livewire\Concerns\BuildsRunSteps; use App\Models\Customer; use App\Models\Host; use App\Services\Provisioning\HostCapacity; +use App\Support\AdminArea; +use App\Support\Settings; use App\Models\Instance; use App\Models\MonitoringTarget; use App\Models\ProvisioningRun; @@ -367,6 +369,40 @@ class Overview extends Component ])]; } + // The site being switched off. It is a deliberate state, not a fault — + // but it answers 503 to every visitor who is not on the VPN, and the + // only place that says so is the switch itself, three clicks away on + // another page. Asked as "ich komme nicht auf www.dev", which is exactly + // what it looks like from outside. + if (! Settings::bool('site.public', true)) { + $notices[] = [ + 'level' => 'warning', + 'text' => __('admin.notice.site_hidden'), + 'route' => 'admin.settings', + ]; + } + + // No SITE_HOST. The marketing site is then registered without a + // hostname, so it answers on the PORTAL's host as well — and every + // route() it generates points at APP_URL, which is why a link on the + // site lands on the portal's domain. + // + // Only where the installation is host-separated at all. On a fresh + // checkout nothing is bound to a hostname by design (see + // RestrictAdminHost: "upgrading must not lock anyone out of a system + // that was working"), and a warning there would be furniture — the same + // mistake the capacity notice made before it learned to ask whether + // there is a host at all. + if (AdminArea::isHostBound() && config('admin_access.site_hosts') === []) { + $notices[] = [ + 'level' => 'warning', + 'text' => __('admin.notice.no_site_host', [ + 'app' => (string) parse_url((string) config('app.url'), PHP_URL_HOST), + ]), + 'route' => 'admin.integrations', + ]; + } + return $notices; } diff --git a/lang/de/admin.php b/lang/de/admin.php index 78b708f..74e1715 100644 --- a/lang/de/admin.php +++ b/lang/de/admin.php @@ -88,6 +88,8 @@ return [ ], 'notice' => [ + 'site_hidden' => 'Website und Kundenbereich sind ausgeblendet — Besucher außerhalb des VPN bekommen die Platzhalterseite (503). Umschalten unter Einstellungen → Installation.', + 'no_site_host' => 'SITE_HOST ist nicht gesetzt. Die Website antwortet dadurch auch auf :app, und Links auf ihr zeigen dorthin statt auf den eigenen Namen.', 'no_capacity' => 'Kein Host hat noch Platz für „:plan“ — das Paket braucht :needs GB, der freieste Host hat :largest GB. Eine bezahlte Bestellung dafür würde bei der Reservierung scheitern.', 'failed_runs' => ':n fehlgeschlagene Bereitstellung(en).', 'host_error' => 'Host :host meldet einen Fehler.', diff --git a/lang/en/admin.php b/lang/en/admin.php index 5841b5c..a2098cd 100644 --- a/lang/en/admin.php +++ b/lang/en/admin.php @@ -88,6 +88,8 @@ return [ ], 'notice' => [ + 'site_hidden' => 'The website and the customer portal are hidden — a visitor outside the VPN gets the placeholder page (503). The switch is under Settings → Installation.', + 'no_site_host' => 'SITE_HOST is not set. The website therefore also answers on :app, and links on it point there instead of at its own name.', 'no_capacity' => 'No host has room for “:plan” any more — the package needs :needs GB and the roomiest host has :largest GB. A paid order for it would fail at reservation.', 'failed_runs' => ':n failed provisioning run(s).', 'host_error' => 'Host :host reports an error.', diff --git a/tests/Feature/Admin/MailRegisterTest.php b/tests/Feature/Admin/MailRegisterTest.php index 49662b1..25b4572 100644 --- a/tests/Feature/Admin/MailRegisterTest.php +++ b/tests/Feature/Admin/MailRegisterTest.php @@ -256,3 +256,47 @@ it('names the status in words rather than printing the key at it', function () { ->assertDontSee('customers.status.') ->assertDontSee('support.status.'); }); + +// ---- Two things the console owed the operator ---- + +it('says on the front page that the site is switched off', function () { + // Asked as "ich komme nicht auf www.dev". The site answers 503 to every + // visitor who is not on the VPN, which is exactly what a broken deployment + // looks like from outside — and the only place that said so was the switch + // itself, on another page. + App\Support\Settings::set('site.public', false); + + Livewire::actingAs(operator('Owner'), 'operator') + ->test(Overview::class) + ->assertSee(__('admin.notice.site_hidden')) + // And it leads to the switch. + ->assertSee(route('admin.settings'), false); + + App\Support\Settings::set('site.public', true); + + Livewire::actingAs(operator('Owner'), 'operator') + ->test(Overview::class) + ->assertDontSee(__('admin.notice.site_hidden')); +}); + +it('says when the website has no hostname of its own', function () { + // Without SITE_HOST the marketing site is registered without a hostname, so + // it answers on the PORTAL's host too — and every route() it generates + // points at APP_URL. That is why a link on the site lands on app.…, which + // was the other half of the same report. + // Only fires where the installation is host-separated at all: on a fresh + // checkout nothing is bound, and a warning there would be furniture. + config()->set('admin_access.hosts', ['admin.dev.example.test']); + config()->set('admin_access.site_hosts', []); + config()->set('app.url', 'https://app.dev.example.test'); + + Livewire::actingAs(operator('Owner'), 'operator') + ->test(Overview::class) + ->assertSee('app.dev.example.test'); + + config()->set('admin_access.site_hosts', ['www.dev.example.test']); + + Livewire::actingAs(operator('Owner'), 'operator') + ->test(Overview::class) + ->assertDontSee(__('admin.notice.no_site_host', ['app' => 'app.dev.example.test'])); +});