fix(ui): make modal cancel buttons work; serve the app over the HTTPS domains
The cancel buttons were dead: wire-elements registers Livewire.on('closeModal'),
but the buttons used Alpine's $dispatch, which fires a DOM event. Livewire
bridges its own events TO the DOM, not back, so nothing ever received them.
Proven in the browser both ways — before: modal stays open after Abbrechen;
after: it closes.
Domain operation behind Zoraxy:
- trustProxies for FOR/PORT/PROTO so Laravel sees https (otherwise it builds
http:// URLs into an https page). X_FORWARDED_HOST is deliberately NOT
trusted — the console is gated on the request host, and trusting it would let
anyone reach /admin through a public domain by forging the header.
- APP_URL + VITE_REVERB_* point at the dev domains (wss via ws.dev).
- Vite dev server is now opt-in (VITE_AUTOSTART): over https the browser cannot
load assets from http://<ip>:5173. Built assets are the default, and the
entrypoint clears a stale public/hot, which otherwise 404s every asset.
Verified in the browser over https: login, styled pages, zero console errors,
zero unencrypted requests, /admin reachable only on admin.dev.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat/portal-design
parent
8edb2a69e5
commit
3e82b51f89
|
|
@ -63,8 +63,8 @@ REVERB_SERVER_PORT=8080
|
|||
# browser-side (baked into built assets): the address a browser can reach
|
||||
VITE_REVERB_APP_KEY=change-me-app-key
|
||||
VITE_REVERB_HOST=localhost
|
||||
VITE_REVERB_PORT=8080
|
||||
VITE_REVERB_SCHEME=http
|
||||
VITE_REVERB_PORT=443
|
||||
VITE_REVERB_SCHEME=https
|
||||
VITE_APP_NAME=CluPilot
|
||||
|
||||
# Vite HMR host the browser connects to (dev). Set to the reachable VM address
|
||||
|
|
@ -174,6 +174,10 @@ ADMIN_HOSTS=admin.dev.clupilot.com,10.10.90.185,localhost,127.0.0.1
|
|||
HOST_UID=1000
|
||||
HOST_GID=1000
|
||||
APP_PORT=80
|
||||
# Vite dev server (HMR). Off by default: over the HTTPS domains the
|
||||
# browser cannot load assets from http://<ip>:5173. Set true only when
|
||||
# working over http://10.10.90.185, then run: docker compose up -d app
|
||||
VITE_AUTOSTART=false
|
||||
VITE_PORT=5173
|
||||
REVERB_HOST_PORT=8080
|
||||
DB_HOST_PORT=3306
|
||||
|
|
|
|||
|
|
@ -24,6 +24,22 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||
// that an operator console is hosted here. Self-scoped to /admin*.
|
||||
$middleware->prependToGroup('web', \App\Http\Middleware\RestrictAdminHost::class);
|
||||
|
||||
// TLS is terminated by the reverse proxy (Zoraxy on the private LAN), so
|
||||
// without this Laravel sees plain http and builds http:// URLs into an
|
||||
// https page — every asset and redirect breaks as mixed content.
|
||||
//
|
||||
// X_FORWARDED_HOST is deliberately NOT trusted: the console is gated on
|
||||
// the request host (ADMIN_HOSTS), and trusting that header would let
|
||||
// anyone reach /admin through a public domain by sending
|
||||
// "X-Forwarded-Host: admin.…". Zoraxy passes the real Host through, so
|
||||
// nothing needs it.
|
||||
$middleware->trustProxies(
|
||||
at: ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16', '127.0.0.1'],
|
||||
headers: Request::HEADER_X_FORWARDED_FOR
|
||||
| Request::HEADER_X_FORWARDED_PORT
|
||||
| Request::HEADER_X_FORWARDED_PROTO,
|
||||
);
|
||||
|
||||
// Stripe posts server-to-server with its own signature (no CSRF token).
|
||||
$middleware->validateCsrfTokens(except: ['webhooks/stripe']);
|
||||
})
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ services:
|
|||
environment:
|
||||
VITE_HMR_HOST: ${VITE_HMR_HOST:-localhost}
|
||||
VITE_PORT: ${VITE_PORT:-5173}
|
||||
# Vite dev server is opt-in — see docker/supervisor/clupilot.conf.
|
||||
VITE_AUTOSTART: ${VITE_AUTOSTART:-false}
|
||||
depends_on:
|
||||
mariadb:
|
||||
condition: service_healthy
|
||||
|
|
|
|||
|
|
@ -18,6 +18,10 @@ elif printf '%s ' "$@" | grep -qE 'reverb:start|queue:work|schedule:work'; then
|
|||
fi
|
||||
|
||||
if [ "$role" = "app" ]; then
|
||||
# A container that was killed leaves public/hot behind, and Laravel then
|
||||
# serves @vite from a dev server that is not running — every asset 404s.
|
||||
# Vite recreates the file when it actually starts.
|
||||
rm -f public/hot
|
||||
[ -f vendor/autoload.php ] || www "composer install --no-interaction --prefer-dist --no-progress"
|
||||
[ -x node_modules/.bin/vite ] || www "npm install --no-fund --no-audit"
|
||||
if [ -f .env ] && ! grep -qE '^APP_KEY=.+' .env; then
|
||||
|
|
|
|||
|
|
@ -20,6 +20,10 @@ stderr_logfile=/dev/stderr
|
|||
stderr_logfile_maxbytes=0
|
||||
|
||||
[program:vite]
|
||||
; Opt-in: over HTTPS domains the browser cannot load assets from
|
||||
; http://<ip>:5173 (mixed content + CORS), so the default is the built manifest.
|
||||
; Set VITE_AUTOSTART=true in .env for HMR while working over the IP.
|
||||
autostart=%(ENV_VITE_AUTOSTART)s
|
||||
command=npm run dev -- --host 0.0.0.0
|
||||
directory=/var/www/html
|
||||
user=www-data
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="mt-6 flex justify-end">
|
||||
<x-ui.button variant="secondary" x-on:click="$dispatch('closeModal')">{{ __('datacenters.cancel') }}</x-ui.button>
|
||||
<x-ui.button variant="secondary" x-on:click="Livewire.dispatch('closeModal')">{{ __('datacenters.cancel') }}</x-ui.button>
|
||||
</div>
|
||||
@else
|
||||
<div class="flex items-start gap-3">
|
||||
|
|
@ -23,7 +23,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="mt-6 flex justify-end gap-3">
|
||||
<x-ui.button variant="secondary" x-on:click="$dispatch('closeModal')">{{ __('datacenters.cancel') }}</x-ui.button>
|
||||
<x-ui.button variant="secondary" x-on:click="Livewire.dispatch('closeModal')">{{ __('datacenters.cancel') }}</x-ui.button>
|
||||
<x-ui.button variant="danger" wire:click="delete" wire:loading.attr="disabled">
|
||||
<x-ui.icon name="trash-2" class="size-4" />{{ __('datacenters.delete_confirm') }}
|
||||
</x-ui.button>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="mt-6 flex justify-end gap-3">
|
||||
<x-ui.button variant="secondary" x-on:click="$dispatch('closeModal')">{{ __('hosts.cancel') }}</x-ui.button>
|
||||
<x-ui.button variant="secondary" x-on:click="Livewire.dispatch('closeModal')">{{ __('hosts.cancel') }}</x-ui.button>
|
||||
<x-ui.button variant="danger" wire:click="remove" wire:loading.attr="disabled">
|
||||
<x-ui.icon name="trash-2" class="size-4" />{{ __('hosts.remove_confirm') }}
|
||||
</x-ui.button>
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
</div>
|
||||
|
||||
<div class="mt-6 flex justify-end gap-3">
|
||||
<x-ui.button variant="secondary" x-on:click="$dispatch('closeModal')">{{ __('datacenters.cancel') }}</x-ui.button>
|
||||
<x-ui.button variant="secondary" x-on:click="Livewire.dispatch('closeModal')">{{ __('datacenters.cancel') }}</x-ui.button>
|
||||
<x-ui.button variant="primary" wire:click="save" wire:loading.attr="disabled">{{ __('datacenters.save') }}</x-ui.button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
</div>
|
||||
|
||||
<div class="mt-6 flex justify-end gap-3">
|
||||
<x-ui.button variant="secondary" x-on:click="$dispatch('closeModal')">{{ __('settings.keep') }}</x-ui.button>
|
||||
<x-ui.button variant="secondary" x-on:click="Livewire.dispatch('closeModal')">{{ __('settings.keep') }}</x-ui.button>
|
||||
<x-ui.button variant="danger" wire:click="cancelPackage" wire:loading.attr="disabled">
|
||||
{{ __('settings.cancel_confirm') }}
|
||||
</x-ui.button>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="mt-6 flex justify-end">
|
||||
<x-ui.button variant="secondary" x-on:click="$dispatch('closeModal')">{{ __('settings.keep') }}</x-ui.button>
|
||||
<x-ui.button variant="secondary" x-on:click="Livewire.dispatch('closeModal')">{{ __('settings.keep') }}</x-ui.button>
|
||||
</div>
|
||||
@else
|
||||
<div class="flex items-start gap-3">
|
||||
|
|
@ -28,7 +28,7 @@
|
|||
@error('confirmWord')<p class="mt-1 text-xs text-danger">{{ $message }}</p>@enderror
|
||||
</div>
|
||||
<div class="mt-6 flex justify-end gap-3">
|
||||
<x-ui.button variant="secondary" x-on:click="$dispatch('closeModal')">{{ __('settings.keep') }}</x-ui.button>
|
||||
<x-ui.button variant="secondary" x-on:click="Livewire.dispatch('closeModal')">{{ __('settings.keep') }}</x-ui.button>
|
||||
<x-ui.button variant="danger" wire:click="closeAccount" wire:loading.attr="disabled">
|
||||
{{ __('settings.close_confirm') }}
|
||||
</x-ui.button>
|
||||
|
|
|
|||
Loading…
Reference in New Issue