Toggles: visual fix (inline-block) + wire:click bindings via settings array
Bug 1 (visual): .clu-toggle is a <span> which is inline by default;
width:36px height:20px were being ignored so toggles rendered as zero-
width inline bars. Added display:inline-block + vertical-align:middle.
Bug 2 (no state): toggle-rows in vhost / ssl / access / plugins /
redirects / staging tabs were pure static markup with no wire:click.
Clicking did nothing.
Fix: generic $settings array on the Volt component (one key per toggle)
+ toggleSetting(key) action + isOn(key) helper. All toggle-rows now
emit wire:click=toggleSetting('namespace.key') and read the live state
via @if($this->isOn('namespace.key')) on @endif.
Per-plugin auto-update toggles read settings['plugin.<slug>.auto'] so
each row flips independently.
Tabs covered: vHost (server_tokens, http2_push), SSL (ocsp/auto_renew/
caa_check/ct_log), SSH-FTP access policy (password_auth/no_root/
fail2ban/ip_allowlist/ssh_2fa), Plugins (per-plugin auto + 5 policy
toggles), Redirects (keep_qs/case_insens), Staging (robots_block/
email_sandbox/payments_test).
Browser-verified: clicking Yoast SEO auto-update toggle flips from
blue ON to grey OFF instantly via Livewire. 0 console errors. 5/5
SiteDetailsTest still green.
master
parent
0615e0969d
commit
a30d5a711a
|
|
@ -1404,12 +1404,14 @@
|
|||
|
||||
/* Pure switch toggle (no checkbox) */
|
||||
.clu-toggle {
|
||||
display: inline-block;
|
||||
position: relative; flex: none;
|
||||
width: 36px; height: 20px;
|
||||
background: rgba(30,35,60,0.20);
|
||||
border-radius: 999px;
|
||||
cursor: pointer;
|
||||
transition: background .18s;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.clu-toggle::after {
|
||||
content: "";
|
||||
|
|
|
|||
|
|
@ -34,6 +34,56 @@ new #[Layout('layouts.app')] class extends Component {
|
|||
public bool $stagingExcludeMedia = false;
|
||||
public string $stagingLastSync = '2026-05-31 14:22';
|
||||
|
||||
/** Generic settings bag for all toggle-rows across tabs. */
|
||||
public array $settings = [
|
||||
// vHost
|
||||
'vhost.server_tokens' => true,
|
||||
'vhost.http2_push' => false,
|
||||
// SSL
|
||||
'ssl.ocsp' => true,
|
||||
'ssl.auto_renew' => true,
|
||||
'ssl.caa_check' => true,
|
||||
'ssl.ct_log' => true,
|
||||
// SSH/FTP
|
||||
'access.password_auth' => true, // password-auth DISABLED toggle
|
||||
'access.no_root' => true,
|
||||
'access.fail2ban' => true,
|
||||
'access.ip_allowlist' => false,
|
||||
'access.ssh_2fa' => false,
|
||||
// Plugins
|
||||
'wp.auto.security' => true,
|
||||
'wp.auto.minor' => true,
|
||||
'wp.auto.major' => false,
|
||||
'wp.auto.snapshot' => true,
|
||||
'wp.auto.staging_first' => false,
|
||||
// Redirects new-rule form
|
||||
'redirect.keep_qs' => true,
|
||||
'redirect.case_insens' => false,
|
||||
// Staging
|
||||
'staging.robots_block' => true,
|
||||
'staging.email_sandbox' => true,
|
||||
'staging.payments_test' => true,
|
||||
// Plugin auto-update flags (per slug)
|
||||
'plugin.wordpress-seo.auto' => true,
|
||||
'plugin.woocommerce.auto' => false,
|
||||
'plugin.wp-rocket.auto' => true,
|
||||
'plugin.wordfence.auto' => true,
|
||||
'plugin.advanced-custom-fields.auto' => false,
|
||||
'plugin.contact-form-7.auto' => true,
|
||||
'plugin.akismet.auto' => true,
|
||||
'plugin.updraftplus.auto' => false,
|
||||
];
|
||||
|
||||
public function toggleSetting(string $key): void
|
||||
{
|
||||
$this->settings[$key] = ! ($this->settings[$key] ?? false);
|
||||
}
|
||||
|
||||
public function isOn(string $key): bool
|
||||
{
|
||||
return (bool) ($this->settings[$key] ?? false);
|
||||
}
|
||||
|
||||
/** Plugin / theme registry — mocked, would come from WP-API in prod. */
|
||||
public function getPluginsProperty(): array
|
||||
{
|
||||
|
|
@ -640,7 +690,9 @@ new #[Layout('layouts.app')] class extends Component {
|
|||
@else <span class="clu-health warn">inaktiv</span>@endif
|
||||
</td>
|
||||
<td>
|
||||
<span class="clu-toggle @if($p['auto']) on @endif" style="cursor:pointer;"></span>
|
||||
<span class="clu-toggle @if($this->isOn('plugin.'.$p['slug'].'.auto')) on @endif"
|
||||
wire:click="toggleSetting('plugin.{{ $p['slug'] }}.auto')"
|
||||
style="cursor:pointer;"></span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="clu-row-actions">
|
||||
|
|
@ -698,25 +750,25 @@ new #[Layout('layouts.app')] class extends Component {
|
|||
<div class="clu-card">
|
||||
<div class="clu-card-head"><h3>Update-Policy</h3></div>
|
||||
<div class="clu-card-body">
|
||||
<div class="clu-toggle-row" style="cursor:pointer;">
|
||||
<div class="clu-toggle-row" wire:click="toggleSetting('wp.auto.security')" style="cursor:pointer;">
|
||||
<div class="clu-tr-body"><div class="clu-tr-title">Auto-Update: Security-Patches</div><div class="clu-tr-desc">kritische sicherheits-updates automatisch einspielen</div></div>
|
||||
<span class="clu-toggle on"></span>
|
||||
<span class="clu-toggle @if($this->isOn('wp.auto.security')) on @endif"></span>
|
||||
</div>
|
||||
<div class="clu-toggle-row" style="cursor:pointer;">
|
||||
<div class="clu-toggle-row" wire:click="toggleSetting('wp.auto.minor')" style="cursor:pointer;">
|
||||
<div class="clu-tr-body"><div class="clu-tr-title">Auto-Update: Minor-Versionen</div><div class="clu-tr-desc">8.9.2 → 8.9.4 automatisch (gleiche major)</div></div>
|
||||
<span class="clu-toggle on"></span>
|
||||
<span class="clu-toggle @if($this->isOn('wp.auto.minor')) on @endif"></span>
|
||||
</div>
|
||||
<div class="clu-toggle-row" style="cursor:pointer;">
|
||||
<div class="clu-toggle-row" wire:click="toggleSetting('wp.auto.major')" style="cursor:pointer;">
|
||||
<div class="clu-tr-body"><div class="clu-tr-title">Auto-Update: Major-Versionen</div><div class="clu-tr-desc">8.x → 9.x — manuell empfohlen, breaking changes möglich</div></div>
|
||||
<span class="clu-toggle"></span>
|
||||
<span class="clu-toggle @if($this->isOn('wp.auto.major')) on @endif"></span>
|
||||
</div>
|
||||
<div class="clu-toggle-row" style="cursor:pointer;">
|
||||
<div class="clu-toggle-row" wire:click="toggleSetting('wp.auto.snapshot')" style="cursor:pointer;">
|
||||
<div class="clu-tr-body"><div class="clu-tr-title">Snapshot vor jedem Update</div><div class="clu-tr-desc">automatisches backup vor plugin-update, rollback bei fehler</div></div>
|
||||
<span class="clu-toggle on"></span>
|
||||
<span class="clu-toggle @if($this->isOn('wp.auto.snapshot')) on @endif"></span>
|
||||
</div>
|
||||
<div class="clu-toggle-row" style="cursor:pointer;">
|
||||
<div class="clu-toggle-row" wire:click="toggleSetting('wp.auto.staging_first')" style="cursor:pointer;">
|
||||
<div class="clu-tr-body"><div class="clu-tr-title">Staging-Test vor Production</div><div class="clu-tr-desc">update zuerst auf staging, smoke-test, dann auf prod</div></div>
|
||||
<span class="clu-toggle"></span>
|
||||
<span class="clu-toggle @if($this->isOn('wp.auto.staging_first')) on @endif"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -776,13 +828,13 @@ new #[Layout('layouts.app')] class extends Component {
|
|||
</div>
|
||||
</div>
|
||||
<div style="margin-top:8px;">
|
||||
<div class="clu-toggle-row" style="cursor:pointer;">
|
||||
<div class="clu-toggle-row" wire:click="toggleSetting('redirect.keep_qs')" style="cursor:pointer;">
|
||||
<div class="clu-tr-body"><div class="clu-tr-title">Query-String beibehalten</div><div class="clu-tr-desc">?utm_source=… überträgt sich auf target</div></div>
|
||||
<span class="clu-toggle on"></span>
|
||||
<span class="clu-toggle @if($this->isOn('redirect.keep_qs')) on @endif"></span>
|
||||
</div>
|
||||
<div class="clu-toggle-row" style="cursor:pointer;">
|
||||
<div class="clu-toggle-row" wire:click="toggleSetting('redirect.case_insens')" style="cursor:pointer;">
|
||||
<div class="clu-tr-body"><div class="clu-tr-title">Case-insensitive Match</div><div class="clu-tr-desc">/PRODUKT und /produkt matchen gleich</div></div>
|
||||
<span class="clu-toggle"></span>
|
||||
<span class="clu-toggle @if($this->isOn('redirect.case_insens')) on @endif"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -873,21 +925,21 @@ new #[Layout('layouts.app')] class extends Component {
|
|||
<div class="clu-tr-body"><div class="clu-tr-title">HTTP-Basic-Auth</div><div class="clu-tr-desc">staging-zugriff per username + passwort schützen (verhindert google-indexierung)</div></div>
|
||||
<span class="clu-toggle @if($stagingPasswordProtect) on @endif"></span>
|
||||
</div>
|
||||
<div class="clu-toggle-row" style="cursor:pointer;">
|
||||
<div class="clu-toggle-row" wire:click="toggleSetting('staging.robots_block')" style="cursor:pointer;">
|
||||
<div class="clu-tr-body"><div class="clu-tr-title">robots.txt blockt alle Crawler</div><div class="clu-tr-desc">User-agent: * / Disallow: /</div></div>
|
||||
<span class="clu-toggle on"></span>
|
||||
<span class="clu-toggle @if($this->isOn('staging.robots_block')) on @endif"></span>
|
||||
</div>
|
||||
<div class="clu-toggle-row" wire:click="$toggle('stagingExcludeMedia')" style="cursor:pointer;">
|
||||
<div class="clu-tr-body"><div class="clu-tr-title">Media-Dateien beim Sync ausschließen</div><div class="clu-tr-desc">spart bandwidth, lädt /uploads lazy on-demand</div></div>
|
||||
<span class="clu-toggle @if($stagingExcludeMedia) on @endif"></span>
|
||||
</div>
|
||||
<div class="clu-toggle-row" style="cursor:pointer;">
|
||||
<div class="clu-toggle-row" wire:click="toggleSetting('staging.email_sandbox')" style="cursor:pointer;">
|
||||
<div class="clu-tr-body"><div class="clu-tr-title">Email-Sandbox (DISALLOW_OUTGOING_MAIL)</div><div class="clu-tr-desc">keine kunden-mails von staging versenden</div></div>
|
||||
<span class="clu-toggle on"></span>
|
||||
<span class="clu-toggle @if($this->isOn('staging.email_sandbox')) on @endif"></span>
|
||||
</div>
|
||||
<div class="clu-toggle-row" style="cursor:pointer;">
|
||||
<div class="clu-toggle-row" wire:click="toggleSetting('staging.payments_test')" style="cursor:pointer;">
|
||||
<div class="clu-tr-body"><div class="clu-tr-title">Payment-Gateways auf Test-Mode</div><div class="clu-tr-desc">stripe + paypal automatisch im sandbox-mode</div></div>
|
||||
<span class="clu-toggle on"></span>
|
||||
<span class="clu-toggle @if($this->isOn('staging.payments_test')) on @endif"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1014,13 +1066,13 @@ new #[Layout('layouts.app')] class extends Component {
|
|||
<div class="clu-tr-body"><div class="clu-tr-title">Brotli + gzip Kompression</div><div class="clu-tr-desc">text-assets vor auslieferung komprimieren</div></div>
|
||||
<span class="clu-toggle @if($brotli) on @endif"></span>
|
||||
</div>
|
||||
<div class="clu-toggle-row" style="cursor:pointer;">
|
||||
<div class="clu-toggle-row" wire:click="toggleSetting('vhost.server_tokens')" style="cursor:pointer;">
|
||||
<div class="clu-tr-body"><div class="clu-tr-title">server_tokens off</div><div class="clu-tr-desc">nginx-version aus header entfernen</div></div>
|
||||
<span class="clu-toggle on"></span>
|
||||
<span class="clu-toggle @if($this->isOn('vhost.server_tokens')) on @endif"></span>
|
||||
</div>
|
||||
<div class="clu-toggle-row" style="cursor:pointer;">
|
||||
<div class="clu-toggle-row" wire:click="toggleSetting('vhost.http2_push')" style="cursor:pointer;">
|
||||
<div class="clu-tr-body"><div class="clu-tr-title">HTTP/2 Server Push</div><div class="clu-tr-desc">kritische assets im voraus an client streamen</div></div>
|
||||
<span class="clu-toggle"></span>
|
||||
<span class="clu-toggle @if($this->isOn('vhost.http2_push')) on @endif"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1111,21 +1163,21 @@ new #[Layout('layouts.app')] class extends Component {
|
|||
</div>
|
||||
|
||||
<div style="margin-top:8px;">
|
||||
<div class="clu-toggle-row" style="cursor:pointer;">
|
||||
<div class="clu-toggle-row" wire:click="toggleSetting('ssl.ocsp')" style="cursor:pointer;">
|
||||
<div class="clu-tr-body"><div class="clu-tr-title">OCSP Stapling</div><div class="clu-tr-desc">cert-revocation-status mit response cachen</div></div>
|
||||
<span class="clu-toggle on"></span>
|
||||
<span class="clu-toggle @if($this->isOn('ssl.ocsp')) on @endif"></span>
|
||||
</div>
|
||||
<div class="clu-toggle-row" style="cursor:pointer;">
|
||||
<div class="clu-toggle-row" wire:click="toggleSetting('ssl.auto_renew')" style="cursor:pointer;">
|
||||
<div class="clu-tr-body"><div class="clu-tr-title">Auto-Renewal</div><div class="clu-tr-desc">erneuerung 30 tage vor ablauf</div></div>
|
||||
<span class="clu-toggle on"></span>
|
||||
<span class="clu-toggle @if($this->isOn('ssl.auto_renew')) on @endif"></span>
|
||||
</div>
|
||||
<div class="clu-toggle-row" style="cursor:pointer;">
|
||||
<div class="clu-toggle-row" wire:click="toggleSetting('ssl.caa_check')" style="cursor:pointer;">
|
||||
<div class="clu-tr-body"><div class="clu-tr-title">CAA-Record-Check</div><div class="clu-tr-desc">vor neuausstellung dns-caa prüfen</div></div>
|
||||
<span class="clu-toggle on"></span>
|
||||
<span class="clu-toggle @if($this->isOn('ssl.caa_check')) on @endif"></span>
|
||||
</div>
|
||||
<div class="clu-toggle-row" style="cursor:pointer;">
|
||||
<div class="clu-toggle-row" wire:click="toggleSetting('ssl.ct_log')" style="cursor:pointer;">
|
||||
<div class="clu-tr-body"><div class="clu-tr-title">Certificate Transparency Log</div><div class="clu-tr-desc">cert in öffentliches ct-log eintragen</div></div>
|
||||
<span class="clu-toggle on"></span>
|
||||
<span class="clu-toggle @if($this->isOn('ssl.ct_log')) on @endif"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1223,25 +1275,25 @@ new #[Layout('layouts.app')] class extends Component {
|
|||
<div class="clu-card">
|
||||
<div class="clu-card-head"><h3>Zugriffs-Policy</h3></div>
|
||||
<div class="clu-card-body">
|
||||
<div class="clu-toggle-row" style="cursor:pointer;">
|
||||
<div class="clu-toggle-row" wire:click="toggleSetting('access.password_auth')" style="cursor:pointer;">
|
||||
<div class="clu-tr-body"><div class="clu-tr-title">Password-Auth deaktiviert</div><div class="clu-tr-desc">SSH nur über Public-Key zulassen</div></div>
|
||||
<span class="clu-toggle on"></span>
|
||||
<span class="clu-toggle @if($this->isOn('access.password_auth')) on @endif"></span>
|
||||
</div>
|
||||
<div class="clu-toggle-row" style="cursor:pointer;">
|
||||
<div class="clu-toggle-row" wire:click="toggleSetting('access.no_root')" style="cursor:pointer;">
|
||||
<div class="clu-tr-body"><div class="clu-tr-title">Root-Login verbieten</div><div class="clu-tr-desc">PermitRootLogin no</div></div>
|
||||
<span class="clu-toggle on"></span>
|
||||
<span class="clu-toggle @if($this->isOn('access.no_root')) on @endif"></span>
|
||||
</div>
|
||||
<div class="clu-toggle-row" style="cursor:pointer;">
|
||||
<div class="clu-toggle-row" wire:click="toggleSetting('access.fail2ban')" style="cursor:pointer;">
|
||||
<div class="clu-tr-body"><div class="clu-tr-title">fail2ban (3 Versuche → 1h Ban)</div><div class="clu-tr-desc">automatische IP-Sperre nach fehlversuchen</div></div>
|
||||
<span class="clu-toggle on"></span>
|
||||
<span class="clu-toggle @if($this->isOn('access.fail2ban')) on @endif"></span>
|
||||
</div>
|
||||
<div class="clu-toggle-row" style="cursor:pointer;">
|
||||
<div class="clu-toggle-row" wire:click="toggleSetting('access.ip_allowlist')" style="cursor:pointer;">
|
||||
<div class="clu-tr-body"><div class="clu-tr-title">IP-Allowlist</div><div class="clu-tr-desc">SSH nur aus 10.10.0.0/16 + büro-WAN</div></div>
|
||||
<span class="clu-toggle"></span>
|
||||
<span class="clu-toggle @if($this->isOn('access.ip_allowlist')) on @endif"></span>
|
||||
</div>
|
||||
<div class="clu-toggle-row" style="cursor:pointer;">
|
||||
<div class="clu-toggle-row" wire:click="toggleSetting('access.ssh_2fa')" style="cursor:pointer;">
|
||||
<div class="clu-tr-body"><div class="clu-tr-title">2FA für SSH (Google Authenticator)</div><div class="clu-tr-desc">zusätzlich zum public key</div></div>
|
||||
<span class="clu-toggle"></span>
|
||||
<span class="clu-toggle @if($this->isOn('access.ssh_2fa')) on @endif"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clu-form-actions">
|
||||
|
|
|
|||
Loading…
Reference in New Issue