feat: help tab-URL + Commands/CLI topic + short command copy
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>feat/v1-foundation
parent
580c72a59d
commit
437a22592b
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Livewire\Help;
|
||||
|
||||
use Livewire\Attributes\Layout;
|
||||
use Livewire\Attributes\Url;
|
||||
use Livewire\Component;
|
||||
|
||||
/**
|
||||
|
|
@ -15,12 +16,16 @@ class Index extends Component
|
|||
{
|
||||
/** Help topics in display order. */
|
||||
private const TOPICS = [
|
||||
'overview', 'domain-tls', 'security', 'updates',
|
||||
'overview', 'domain-tls', 'security', 'updates', 'commands',
|
||||
'servers', 'sessions', 'email', 'audit', 'recovery',
|
||||
];
|
||||
|
||||
#[Url]
|
||||
public string $topic = 'overview';
|
||||
|
||||
// mount() handles the test/parameter-injection path (Livewire::test($class, ['topic' => …])).
|
||||
// Browser deep links populate $topic via the #[Url] attribute above and bypass mount on
|
||||
// hydration. Both paths are validated by the render() clamp below.
|
||||
public function mount(?string $topic = null): void
|
||||
{
|
||||
if ($topic !== null) {
|
||||
|
|
@ -41,6 +46,7 @@ class Index extends Component
|
|||
'domain-tls' => __('help.topic_domain_tls'),
|
||||
'security' => __('help.topic_security'),
|
||||
'updates' => __('help.topic_updates'),
|
||||
'commands' => __('help.topic_commands'),
|
||||
'servers' => __('help.topic_servers'),
|
||||
'sessions' => __('help.topic_sessions'),
|
||||
'email' => __('help.topic_email'),
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ return [
|
|||
'topic_domain_tls' => 'Domain, TLS & Reverse-Proxy',
|
||||
'topic_security' => 'Sicherheit & 2FA',
|
||||
'topic_updates' => 'Updates & Versionen',
|
||||
'topic_commands' => 'Befehle / CLI',
|
||||
'topic_servers' => 'Server & SSH',
|
||||
'topic_sessions' => 'Sitzungen & Benutzer',
|
||||
'topic_email' => 'E-Mail (SMTP)',
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ return [
|
|||
'topic_domain_tls' => 'Domain, TLS & reverse proxy',
|
||||
'topic_security' => 'Security & 2FA',
|
||||
'topic_updates' => 'Updates & versions',
|
||||
'topic_commands' => 'Commands / CLI',
|
||||
'topic_servers' => 'Servers & SSH',
|
||||
'topic_sessions' => 'Sessions & users',
|
||||
'topic_email' => 'Email (SMTP)',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
@php
|
||||
$h = 'font-display text-base font-semibold text-ink';
|
||||
$p = 'text-sm leading-relaxed text-ink-2';
|
||||
$code = 'rounded bg-inset px-1.5 py-0.5 font-mono text-[12px] text-accent-text';
|
||||
$cmd = 'block w-full overflow-x-auto rounded-md border border-line bg-void px-3 py-2 font-mono text-[12px] text-accent-text';
|
||||
$real = 'mt-1 font-mono text-[11px] leading-relaxed text-ink-3';
|
||||
@endphp
|
||||
|
||||
<div class="space-y-3">
|
||||
<h3 class="{{ $h }}">Befehle / CLI</h3>
|
||||
<p class="{{ $p }}">Auf dem Host richtet der Installer den Befehl <code class="{{ $code }}">clusev</code> ein. Er kapselt die langen Docker-Kommandos — du musst dir weder Compose-Datei noch Container-Namen merken. Per SSH einloggen und von überall <code class="{{ $code }}">clusev <befehl></code> tippen. <code class="{{ $code }}">clusev help</code> zeigt die Übersicht.</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-1">
|
||||
<pre class="{{ $cmd }}">sudo clusev update</pre>
|
||||
<p class="{{ $p }}">Holt die neueste Version, baut das Image neu (inkl. CSS/JS) und wendet Datenbank-Migrationen an. Braucht root (<code class="{{ $code }}">sudo</code>). Secrets und die konfigurierte Domain bleiben erhalten; das Panel ist ein bis zwei Minuten kurz nicht erreichbar.</p>
|
||||
<p class="{{ $real }}">→ führt <span class="text-ink-2">update.sh</span> aus (git pull + Image-Rebuild + migrate)</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<pre class="{{ $cmd }}">clusev reset-admin</pre>
|
||||
<p class="{{ $p }}">Setzt den Admin-Zugang zurück: entfernt den zweiten Faktor (2FA), sodass beim nächsten Login ein neues Passwort gesetzt werden kann. Letzter Ausweg, wenn Passwort und 2FA verloren sind (siehe <span class="text-ink">Konto-Wiederherstellung</span>).</p>
|
||||
<p class="{{ $real }}">→ <span class="text-ink-2">docker compose exec app php artisan clusev:reset-admin</span></p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<pre class="{{ $cmd }}">clusev restart</pre>
|
||||
<p class="{{ $p }}">Startet den Stack neu (alle Container) und wendet Konfigurationsänderungen an, ohne neu zu bauen.</p>
|
||||
<p class="{{ $real }}">→ <span class="text-ink-2">docker compose up -d</span></p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<pre class="{{ $cmd }}">clusev logs</pre>
|
||||
<p class="{{ $p }}">Folgt den Live-Logs des Stacks (z. B. zur Fehlersuche). Mit einem Dienstnamen dahinter — etwa <code class="{{ $code }}">clusev logs app</code> — nur dessen Logs. Strg-C beendet.</p>
|
||||
<p class="{{ $real }}">→ <span class="text-ink-2">docker compose logs -f</span></p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<pre class="{{ $cmd }}">clusev ps</pre>
|
||||
<p class="{{ $p }}">Zeigt, welche Dienste laufen (Alias: <code class="{{ $code }}">clusev status</code>). Schneller Gesundheits-Check des Stacks.</p>
|
||||
<p class="{{ $real }}">→ <span class="text-ink-2">docker compose ps</span></p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<pre class="{{ $cmd }}">clusev migrate</pre>
|
||||
<p class="{{ $p }}">Wendet ausstehende Datenbank-Migrationen an. Normalerweise erledigt das <code class="{{ $code }}">clusev update</code> automatisch — dieser Befehl ist für den seltenen manuellen Fall.</p>
|
||||
<p class="{{ $real }}">→ <span class="text-ink-2">docker compose exec app php artisan migrate --force</span></p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<pre class="{{ $cmd }}">clusev artisan <...></pre>
|
||||
<p class="{{ $p }}">Für Fortgeschrittene: reicht ein beliebiges artisan-Kommando in den app-Container durch, etwa <code class="{{ $code }}">clusev artisan about</code>.</p>
|
||||
<p class="{{ $real }}">→ <span class="text-ink-2">docker compose exec app php artisan <...></span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -17,8 +17,7 @@
|
|||
|
||||
<div class="space-y-3">
|
||||
<h3 class="{{ $h }}">Komplett ausgesperrt (letzter Ausweg)</h3>
|
||||
<p class="{{ $p }}">Passwort und 2FA verloren, kein SMTP? Vom Host wiederherstellen — per SSH auf den Server einloggen und im Projektverzeichnis ausführen:</p>
|
||||
<pre class="{{ $pre }}">cd clusev
|
||||
docker compose -f docker-compose.prod.yml exec app php artisan clusev:reset-admin</pre>
|
||||
<p class="{{ $p }}"><code class="{{ $code }}">clusev:reset-admin</code> entfernt den zweiten Faktor, sodass du beim nächsten Login ein neues Passwort setzen kannst.</p>
|
||||
<p class="{{ $p }}">Passwort und 2FA verloren, kein SMTP? Vom Host wiederherstellen — per SSH auf den Server einloggen und ausführen:</p>
|
||||
<pre class="{{ $pre }}">clusev reset-admin</pre>
|
||||
<p class="{{ $p }}"><code class="{{ $code }}">clusev reset-admin</code> entfernt den zweiten Faktor, sodass du beim nächsten Login ein neues Passwort setzen kannst. (Dahinter läuft <code class="{{ $code }}">docker compose exec app php artisan clusev:reset-admin</code> — siehe „Befehle / CLI".)</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
<p class="{{ $p }}">Unter <span class="text-ink">Version & Releases</span> prüft das Panel beim Öffnen automatisch, ob eine neuere Version im Kanal verfügbar ist (Vergleich mit dem neuesten getaggten Release). Ist eines verfügbar, erscheint der Knopf <span class="text-ink">„Jetzt aktualisieren"</span>.</p>
|
||||
<ul class="ml-4 list-disc space-y-1.5">
|
||||
<li class="{{ $li }}"><span class="text-ink">Per Knopf:</span> „Jetzt aktualisieren" anstoßen. Das Panel ist während des Neubaus ein bis zwei Minuten kurz nicht erreichbar; danach springt die Anzeige selbst auf „Aktuell".</li>
|
||||
<li class="{{ $li }}"><span class="text-ink">Per SSH:</span> auf dem Host <code class="{{ $code }}">cd clusev</code> und <code class="{{ $code }}">sudo ./update.sh</code> ausführen.</li>
|
||||
<li class="{{ $li }}"><span class="text-ink">Per SSH:</span> auf dem Host <code class="{{ $code }}">sudo clusev update</code> ausführen (von überall). Mehr unter „Befehle / CLI".</li>
|
||||
</ul>
|
||||
<p class="{{ $p }}">Beim Update wird das Image neu gebaut (inkl. <code class="{{ $code }}">npm run build</code> für CSS/JS) und es werden <span class="text-ink">Datenbank-Migrationen</span> angewendet. Stil-Änderungen und Schema-Updates greifen also automatisch. Secrets und die konfigurierte Domain bleiben erhalten.</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
@php
|
||||
$h = 'font-display text-base font-semibold text-ink';
|
||||
$p = 'text-sm leading-relaxed text-ink-2';
|
||||
$code = 'rounded bg-inset px-1.5 py-0.5 font-mono text-[12px] text-accent-text';
|
||||
$cmd = 'block w-full overflow-x-auto rounded-md border border-line bg-void px-3 py-2 font-mono text-[12px] text-accent-text';
|
||||
$real = 'mt-1 font-mono text-[11px] leading-relaxed text-ink-3';
|
||||
@endphp
|
||||
|
||||
<div class="space-y-3">
|
||||
<h3 class="{{ $h }}">Commands / CLI</h3>
|
||||
<p class="{{ $p }}">The installer sets up the <code class="{{ $code }}">clusev</code> command on the host. It wraps the long Docker invocations — you never need to remember the compose file or container names. SSH in and run <code class="{{ $code }}">clusev <command></code> from anywhere. <code class="{{ $code }}">clusev help</code> prints the overview.</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-1">
|
||||
<pre class="{{ $cmd }}">sudo clusev update</pre>
|
||||
<p class="{{ $p }}">Pulls the latest release, rebuilds the image (including CSS/JS) and applies database migrations. Requires root (<code class="{{ $code }}">sudo</code>). Secrets and the configured domain are preserved; the panel is briefly unreachable for a minute or two.</p>
|
||||
<p class="{{ $real }}">→ runs <span class="text-ink-2">update.sh</span> (git pull + image rebuild + migrate)</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<pre class="{{ $cmd }}">clusev reset-admin</pre>
|
||||
<p class="{{ $p }}">Resets dashboard access: clears the second factor (2FA) so you can set a new password on the next login. Last resort when password and 2FA are both lost (see <span class="text-ink">Account recovery</span>).</p>
|
||||
<p class="{{ $real }}">→ <span class="text-ink-2">docker compose exec app php artisan clusev:reset-admin</span></p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<pre class="{{ $cmd }}">clusev restart</pre>
|
||||
<p class="{{ $p }}">Restarts the stack (all containers) and applies configuration changes without rebuilding.</p>
|
||||
<p class="{{ $real }}">→ <span class="text-ink-2">docker compose up -d</span></p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<pre class="{{ $cmd }}">clusev logs</pre>
|
||||
<p class="{{ $p }}">Follows the stack's live logs (handy for troubleshooting). Add a service name — e.g. <code class="{{ $code }}">clusev logs app</code> — for just that one. Ctrl-C stops.</p>
|
||||
<p class="{{ $real }}">→ <span class="text-ink-2">docker compose logs -f</span></p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<pre class="{{ $cmd }}">clusev ps</pre>
|
||||
<p class="{{ $p }}">Shows which services are running (alias: <code class="{{ $code }}">clusev status</code>). A quick stack health check.</p>
|
||||
<p class="{{ $real }}">→ <span class="text-ink-2">docker compose ps</span></p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<pre class="{{ $cmd }}">clusev migrate</pre>
|
||||
<p class="{{ $p }}">Applies pending database migrations. Normally <code class="{{ $code }}">clusev update</code> does this for you — this is for the rare manual case.</p>
|
||||
<p class="{{ $real }}">→ <span class="text-ink-2">docker compose exec app php artisan migrate --force</span></p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<pre class="{{ $cmd }}">clusev artisan <...></pre>
|
||||
<p class="{{ $p }}">For advanced use: passes any artisan command through to the app container, e.g. <code class="{{ $code }}">clusev artisan about</code>.</p>
|
||||
<p class="{{ $real }}">→ <span class="text-ink-2">docker compose exec app php artisan <...></span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -17,8 +17,7 @@
|
|||
|
||||
<div class="space-y-3">
|
||||
<h3 class="{{ $h }}">Completely locked out (last resort)</h3>
|
||||
<p class="{{ $p }}">Lost password and 2FA, no SMTP? Recover from the host — SSH into the server and run in the project directory:</p>
|
||||
<pre class="{{ $pre }}">cd clusev
|
||||
docker compose -f docker-compose.prod.yml exec app php artisan clusev:reset-admin</pre>
|
||||
<p class="{{ $p }}"><code class="{{ $code }}">clusev:reset-admin</code> clears the second factor so you can set a new password on the next login.</p>
|
||||
<p class="{{ $p }}">Lost password and 2FA, no SMTP? Recover from the host — SSH into the server and run:</p>
|
||||
<pre class="{{ $pre }}">clusev reset-admin</pre>
|
||||
<p class="{{ $p }}"><code class="{{ $code }}">clusev reset-admin</code> clears the second factor so you can set a new password on the next login. (Under the hood it runs <code class="{{ $code }}">docker compose exec app php artisan clusev:reset-admin</code> — see "Commands / CLI".)</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
<p class="{{ $p }}">Under <span class="text-ink">Version & Releases</span> the panel checks on open whether a newer release is available in the channel (compared against the newest tagged release). When one is available, the <span class="text-ink">"Update now"</span> button appears.</p>
|
||||
<ul class="ml-4 list-disc space-y-1.5">
|
||||
<li class="{{ $li }}"><span class="text-ink">From the button:</span> trigger "Update now". The panel is briefly unreachable for a minute or two during the rebuild; afterwards the card flips to "Current" on its own.</li>
|
||||
<li class="{{ $li }}"><span class="text-ink">Over SSH:</span> on the host run <code class="{{ $code }}">cd clusev</code> and <code class="{{ $code }}">sudo ./update.sh</code>.</li>
|
||||
<li class="{{ $li }}"><span class="text-ink">Over SSH:</span> on the host run <code class="{{ $code }}">sudo clusev update</code> (from anywhere). More under "Commands / CLI".</li>
|
||||
</ul>
|
||||
<p class="{{ $p }}">An update rebuilds the image (including <code class="{{ $code }}">npm run build</code> for CSS/JS) and applies <span class="text-ink">database migrations</span>. So style changes and schema updates take effect automatically. Secrets and the configured domain are preserved.</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ namespace Tests\Feature;
|
|||
use App\Livewire\Help\Index;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Livewire\Attributes\Url;
|
||||
use Livewire\Livewire;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use Tests\TestCase;
|
||||
|
|
@ -57,6 +58,31 @@ class HelpPageTest extends TestCase
|
|||
->assertSee('TOTP');
|
||||
}
|
||||
|
||||
public function test_topic_property_is_url_bound(): void
|
||||
{
|
||||
$attrs = (new \ReflectionProperty(Index::class, 'topic'))->getAttributes(Url::class);
|
||||
$this->assertNotEmpty($attrs, 'Help topic must be #[Url]-bound for deep links / reload');
|
||||
}
|
||||
|
||||
public function test_recovery_shows_the_short_host_command_not_the_long_compose_one(): void
|
||||
{
|
||||
$this->actAsAdmin();
|
||||
Livewire::test(Index::class)
|
||||
->set('topic', 'recovery')
|
||||
->assertSee('clusev reset-admin')
|
||||
->assertDontSee('docker-compose.prod.yml');
|
||||
}
|
||||
|
||||
public function test_commands_topic_lists_the_cli(): void
|
||||
{
|
||||
$this->actAsAdmin();
|
||||
Livewire::test(Index::class)
|
||||
->set('topic', 'commands')
|
||||
->assertSee('clusev reset-admin')
|
||||
->assertSee('sudo clusev update')
|
||||
->assertDontSee('docker-compose.prod.yml');
|
||||
}
|
||||
|
||||
#[DataProvider('topicProvider')]
|
||||
public function test_each_topic_renders_its_own_partial(string $topic, string $marker): void
|
||||
{
|
||||
|
|
@ -67,12 +93,13 @@ class HelpPageTest extends TestCase
|
|||
public static function topicProvider(): array
|
||||
{
|
||||
return [
|
||||
['updates', 'update.sh'],
|
||||
['updates', 'clusev update'],
|
||||
['servers', 'SSH'],
|
||||
['sessions', 'Sitzung'],
|
||||
['email', 'SMTP'],
|
||||
['audit', 'Audit'],
|
||||
['recovery', 'clusev:reset-admin'],
|
||||
['recovery', 'clusev reset-admin'],
|
||||
['commands', 'clusev ps'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue