harden(update): drop inline spinner style (R4), server-side return-path sanitise
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>feat/v1-foundation
parent
d2f70900ff
commit
df5d9e2112
|
|
@ -32,14 +32,13 @@
|
|||
{{-- Spinner + heading --}}
|
||||
<div class="flex flex-col items-center gap-5 text-center">
|
||||
<div id="js-spinner" class="relative h-14 w-14" aria-hidden="true">
|
||||
<svg class="h-14 w-14 -rotate-90" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg class="h-14 w-14 -rotate-90 animate-spin origin-center" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="28" cy="28" r="24" stroke-width="3" class="stroke-line"/>
|
||||
<circle id="js-spinner-arc" cx="28" cy="28" r="24" stroke-width="3"
|
||||
stroke-linecap="round"
|
||||
stroke-dasharray="150.796"
|
||||
stroke-dashoffset="37.699"
|
||||
class="stroke-accent origin-center"
|
||||
style="animation: clusev-spin 1.4s linear infinite; transform-origin: 50% 50%;" />
|
||||
class="stroke-accent origin-center" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
|
|
@ -100,11 +99,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Inline CSS for the spinner animation (no Alpine/external deps) --}}
|
||||
<style>
|
||||
@keyframes clusev-spin { to { transform: rotate(360deg); } }
|
||||
</style>
|
||||
|
||||
{{-- Inline JS — no external dependencies. Reads ?return= for the redirect target.
|
||||
Polls /up every 2500 ms; requires 2 consecutive 200s before redirecting (flap guard).
|
||||
Times out after 10 minutes and shows a manual-reload prompt. --}}
|
||||
|
|
@ -113,13 +107,8 @@
|
|||
'use strict';
|
||||
|
||||
// ── Return URL ─────────────────────────────────────────────────────────
|
||||
// Sanitise to a same-origin relative path: must start with a single /,
|
||||
// must NOT start with //, http, or contain backslashes.
|
||||
var raw = new URLSearchParams(window.location.search).get('return') || '';
|
||||
var returnUrl = '/';
|
||||
if (raw && /^\/[^\/\\]/.test(raw) && !/^\/\//g.test(raw) && !/^https?:/i.test(raw)) {
|
||||
returnUrl = raw;
|
||||
}
|
||||
// Server-validated path from the route handler (defence-in-depth guard).
|
||||
var returnUrl = {{ \Illuminate\Support\Js::from($returnPath) }};
|
||||
|
||||
// ── Phase timing heuristic (purely visual) ─────────────────────────────
|
||||
// Approx durations (seconds) per phase before advancing the highlight.
|
||||
|
|
|
|||
|
|
@ -110,6 +110,11 @@ Route::middleware('auth')->group(function () {
|
|||
|
||||
// Plain Blade view — deliberately NOT a Livewire component so it survives the
|
||||
// container teardown that follows an update request (R1/R2 exception, by design).
|
||||
Route::get('/update-progress', fn () => view('update-progress'))->name('update.progress');
|
||||
Route::get('/update-progress', function (Request $request) {
|
||||
$parsed = parse_url((string) $request->query('return', ''), PHP_URL_PATH);
|
||||
$return = (is_string($parsed) && str_starts_with($parsed, '/') && ! str_starts_with($parsed, '//')) ? $parsed : '/';
|
||||
|
||||
return view('update-progress', ['returnPath' => $return]);
|
||||
})->name('update.progress');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue