163 lines
8.4 KiB
PHP
163 lines
8.4 KiB
PHP
<?php
|
|
|
|
namespace App\Support;
|
|
|
|
/**
|
|
* The navigation of both shells, in one place.
|
|
*
|
|
* The sidebar and the breadcrumb describe the same structure, and when each
|
|
* built its own copy the breadcrumb quietly said "Übersicht" on every page —
|
|
* the layout had no way to know which entry was current, so it fell back to the
|
|
* first one. One definition, two readers.
|
|
*
|
|
* Each entry is [route name, icon, translation key, capability or null].
|
|
*/
|
|
final class Navigation
|
|
{
|
|
/** @return array<int, array{label: ?string, items: array<int, array{0:string,1:string,2:string,3:?string}>}> */
|
|
public static function portal(): array
|
|
{
|
|
return [
|
|
['label' => null, 'items' => [
|
|
['dashboard', 'gauge', 'overview', null],
|
|
['cloud', 'cloud', 'cloud', null],
|
|
// The only portal entry with a condition. Start carries no
|
|
// custom domain and cannot buy one, so the tab would lead
|
|
// straight to the 403 its page raises.
|
|
['domain', 'globe', 'domain', 'use-custom-domain'],
|
|
['users', 'users', 'users', null],
|
|
['backups', 'database', 'backups', null],
|
|
]],
|
|
['label' => __('dashboard.nav_group.contract'), 'items' => [
|
|
['billing', 'box', 'billing', null],
|
|
['invoices', 'receipt', 'invoices', null],
|
|
['settings', 'settings', 'settings', null],
|
|
['support', 'life-buoy', 'support', null],
|
|
]],
|
|
];
|
|
}
|
|
|
|
/** @return array<int, array{label: ?string, items: array<int, array{0:string,1:string,2:string,3:?string}>}> */
|
|
public static function console(): array
|
|
{
|
|
return [
|
|
// Allein und ohne Überschrift: die Seite, auf der man landet.
|
|
['label' => null, 'items' => [
|
|
['admin.overview', 'gauge', 'overview', null],
|
|
]],
|
|
// Wer bei uns ist und was er hat.
|
|
['label' => __('admin.nav_group.customers'), 'items' => [
|
|
['admin.customers', 'users', 'customers', null],
|
|
['admin.instances', 'box', 'instances', null],
|
|
['admin.plans', 'tag', 'plans', 'plans.manage'],
|
|
]],
|
|
// Die Maschinen und ihre Adressen — der Bestand, den man ansieht.
|
|
['label' => __('admin.nav_group.fleet'), 'items' => [
|
|
['admin.hosts', 'server', 'hosts', null],
|
|
['admin.datacenters', 'database', 'datacenters', null],
|
|
['admin.capacity', 'database', 'capacity', 'hosts.manage'],
|
|
// Hostnamen und ihre Zertifikate: was hier steht, entscheidet,
|
|
// ob eine Adresse ANTWORTET. Kein Einrichten, sondern Bestand.
|
|
['admin.proxy-hosts', 'globe', 'proxy_hosts', 'site.manage'],
|
|
// Stand vorher unter System, mit der Begründung, der Tunnel
|
|
// müsse stehen, bevor irgendetwas geht. Das stimmt — gilt aber
|
|
// genauso für Integrationen und Bereitschaft, die dort bleiben.
|
|
// Der Unterschied: das hier ist eine LISTE VON GEGENSTELLEN,
|
|
// die man beim Arbeiten ansieht, so wie die Hostliste. Dieselbe
|
|
// Begründung, mit der Hostnamen schon aus System herauskamen.
|
|
['admin.vpn', 'shield', 'vpn', null],
|
|
]],
|
|
// Was gerade läuft oder klemmt. Vorgänge, kein Bestand.
|
|
//
|
|
// Diese Gruppe trug bis hierher zwölf Einträge — Maschinen, Geld und
|
|
// Post unter einem Wort, das auf alle drei zutrifft und deshalb
|
|
// keinem hilft. Sie heißt weiter „Betrieb" und meint jetzt nur noch
|
|
// das eine.
|
|
['label' => __('admin.nav_group.operations'), 'items' => [
|
|
['admin.provisioning', 'activity', 'provisioning', null],
|
|
['admin.maintenance', 'alert-triangle', 'maintenance', null],
|
|
['admin.incidents', 'bell', 'incidents', null],
|
|
]],
|
|
// Alles, wo Geld drinsteht.
|
|
['label' => __('admin.nav_group.billing'), 'items' => [
|
|
['admin.revenue', 'trending-up', 'revenue', null],
|
|
['admin.invoices', 'file-text', 'invoices', 'site.manage'],
|
|
['admin.payment-problems', 'alert-triangle', 'payment_problems', 'billing.manage'],
|
|
// Eigener Eintrag, kein Abschnitt der Einstellungen: was hier
|
|
// gesetzt wird, steht auf einem Rechtsdokument, und neben dem
|
|
// Sichtbarkeitsschalter ändert das jemand im Vorbeigehen.
|
|
['admin.finance', 'receipt', 'finance', 'site.manage'],
|
|
]],
|
|
// Was geschrieben wird — gelesen beim Antworten, nicht beim
|
|
// Einrichten.
|
|
['label' => __('admin.nav_group.post'), 'items' => [
|
|
['admin.inbox', 'mail', 'inbox', 'customers.manage'],
|
|
['admin.mail-log', 'send', 'mail_log', 'customers.manage'],
|
|
['admin.templates', 'file-text', 'templates', 'customers.manage'],
|
|
// Stand vorher unter System, neben den Postfächern. Sie zeigt
|
|
// aber VORLAGEN, keine Postfächer — und gehört deshalb neben
|
|
// die Vorlagen, die sie darstellt.
|
|
['admin.mail.preview', 'send', 'mail_preview', 'mail.manage'],
|
|
]],
|
|
// Was einmal eingerichtet wird und danach gilt.
|
|
['label' => __('admin.nav_group.system'), 'items' => [
|
|
// Die Postfächer und der Server, über den Mail dieses Haus
|
|
// verlässt. Einrichtung, anders als die vier Seiten unter Post.
|
|
['admin.mail', 'mail', 'mail', 'mail.manage'],
|
|
// Merged from the former admin.secrets + admin.infrastructure
|
|
// pages, which split the same subject by storage mechanism
|
|
// instead of by what it configures. Reachable with EITHER
|
|
// capability — see App\Livewire\Admin\Integrations — so the
|
|
// entry stays visible to exactly who could reach at least one
|
|
// of the two before: an array here means "any of these",
|
|
// never "all of these" (contrast a single string elsewhere in
|
|
// this file, which x-shell.nav checks with plain ->can()).
|
|
['admin.integrations', 'plug', 'integrations', ['hosts.manage', 'secrets.manage']],
|
|
// Every check App\Support\Readiness knows about, gathered on
|
|
// one page — same gate as Integrations just above, because
|
|
// both halves it configures come back together here.
|
|
['admin.readiness', 'shield-check', 'readiness', ['hosts.manage', 'secrets.manage']],
|
|
// Der AV-Vertrag: gilt für die ganze Installation, nicht für
|
|
// einen Kunden.
|
|
['admin.dpa', 'file-text', 'dpa', 'dpa.manage'],
|
|
// Two-factor enrolment used to sit here as an entry of its
|
|
// own. It is a tab of Settings now — it is a setting, and it
|
|
// was the one place an operator had to leave the settings page
|
|
// to change something about their own access. The standalone
|
|
// page stays reachable at its route, because that is where
|
|
// RequireOperatorTwoFactor sends somebody who may not open
|
|
// anything else yet, Settings included.
|
|
['admin.settings', 'settings', 'settings', null],
|
|
['admin.roles', 'users', 'roles', 'staff.manage'],
|
|
]],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* The label of the entry the current request belongs to.
|
|
*
|
|
* Matched on the route rather than the path, because the console's path
|
|
* changes between host-bound and fallback mode while its route names do
|
|
* not. Null when the page is not in the navigation at all — a detail view,
|
|
* a modal — and the caller then supplies its own title.
|
|
*/
|
|
public static function currentLabel(bool $console = false): ?string
|
|
{
|
|
$prefix = $console ? 'admin.nav.' : 'dashboard.nav.';
|
|
|
|
foreach ($console ? self::console() : self::portal() as $group) {
|
|
foreach ($group['items'] as [$route, , $key]) {
|
|
$matches = $console
|
|
? AdminArea::routeIs($route)
|
|
: request()->routeIs($route);
|
|
|
|
if ($matches) {
|
|
return __($prefix.$key);
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|