diff --git a/config/clusev.php b/config/clusev.php
index a9d35aa..37fb1e1 100644
--- a/config/clusev.php
+++ b/config/clusev.php
@@ -19,6 +19,11 @@ return [
// is the fallback so a domain set during install still gets automatic TLS.
'domain' => env('APP_DOMAIN'),
+ // Public documentation site. The dashboard's "Hilfe" entry (sidebar + command palette +
+ // the `g h` shortcut) opens this instead of duplicating docs in-panel. Canonical for every
+ // install; env-overridable for a private/mirrored docs host.
+ 'docs_url' => env('CLUSEV_DOCS_URL', 'https://docs.clusev.com'),
+
// Update source. Defaults to the PUBLIC repo (safe to ship — it is public). install.sh overrides
// it via CLUSEV_REPOSITORY, derived from the clone origin: dev points at Gitea, staging at the
// GitHub-private mirror, public users at the public repo. Private (Gitea/GitHub-private) URLs live
diff --git a/resources/js/app.js b/resources/js/app.js
index 91af45f..e0b5a94 100644
--- a/resources/js/app.js
+++ b/resources/js/app.js
@@ -525,7 +525,9 @@ document.addEventListener('alpine:init', () => {
// translated via __()), so the palette stays in sync with the active locale.
// CMDK_GO maps leader-key mnemonics to routes (i=dIenste, l=Log,
// e=Einstellungen, y=sYstem — d/s already taken) and stays in JS.
- const CMDK_GO = { d: '/', s: '/servers', i: '/services', f: '/files', l: '/audit', e: '/settings', y: '/system', v: '/versions', h: '/help', t: '/terminal' };
+ // `h` (Help) is intentionally absent — it resolves via the config-driven nav list in go()
+ // so it can point at the external docs URL instead of an in-app route.
+ const CMDK_GO = { d: '/', s: '/servers', i: '/services', f: '/files', l: '/audit', e: '/settings', y: '/system', v: '/versions', t: '/terminal' };
window.Alpine.data('cmdk', (nav = [], actions = [], servers = [], failMsg = '') => ({
nav,
@@ -613,6 +615,8 @@ document.addEventListener('alpine:init', () => {
navigate(href) {
this.close();
+ // Absolute URLs (e.g. the online docs) open in a new tab; in-app routes use SPA nav.
+ if (/^https?:\/\//i.test(href)) { window.open(href, '_blank', 'noopener'); return; }
if (window.Livewire?.navigate) window.Livewire.navigate(href);
else window.location.href = href;
},
@@ -629,7 +633,11 @@ document.addEventListener('alpine:init', () => {
},
go(key) {
- if (CMDK_GO[key]) this.navigate(CMDK_GO[key]);
+ if (CMDK_GO[key]) { this.navigate(CMDK_GO[key]); return; }
+ // Keys not in the static map resolve via the (translated, config-driven) nav list —
+ // e.g. "g h" → the Help entry, whose href points at the online docs.
+ const item = this.nav.find((n) => n.hint === `g ${key}`);
+ if (item?.href) this.navigate(item.href);
},
onKey(e) {
diff --git a/resources/views/components/command-palette.blade.php b/resources/views/components/command-palette.blade.php
index 6185b51..57150fe 100644
--- a/resources/views/components/command-palette.blade.php
+++ b/resources/views/components/command-palette.blade.php
@@ -28,7 +28,7 @@
['label' => __('shell.nav_settings'), 'href' => '/settings', 'hint' => 'g e'],
['label' => __('shell.nav_system'), 'href' => '/system', 'hint' => 'g y'],
['label' => __('shell.nav_versions'), 'href' => '/versions', 'hint' => 'g v'],
- ['label' => __('shell.nav_help'), 'href' => '/help', 'hint' => 'g h'],
+ ['label' => __('shell.nav_help'), 'href' => config('clusev.docs_url'), 'hint' => 'g h'],
];
$actions = [
diff --git a/resources/views/components/sidebar.blade.php b/resources/views/components/sidebar.blade.php
index 674957a..99d4c18 100644
--- a/resources/views/components/sidebar.blade.php
+++ b/resources/views/components/sidebar.blade.php
@@ -45,7 +45,8 @@
{{ __('release.nav') }}
@endif
- {{ __('shell.nav_help') }}
+ {{-- Help opens the online docs (docs.clusev.com) in a new tab — no in-panel duplication. --}}
+ {{ __('shell.nav_help') }}
{{-- User --}}