Redirecting to Controller when main react app is down.

This commit is contained in:
Roland 2026-03-07 19:17:38 -07:00
parent 08982647ac
commit 8261df5bef
No known key found for this signature in database
GPG key ID: 1BA620CE6C9A4680
3 changed files with 34 additions and 31 deletions

View file

@ -1883,7 +1883,7 @@
// Poll main API to check if upgrade is complete
// Use absolute URL to main WROLPi API (HTTPS on port 443), not Controller
const mainApiUrl = `https://${window.location.hostname}/api/status`;
const mainApiUrl = `https://${window.location.hostname}${window.location.port === '8087' ? ':8443' : ''}/api/status`;
const checkUpgradeComplete = async () => {
try {
const resp = await fetch(mainApiUrl, {
@ -1900,7 +1900,7 @@
// Redirect to main app after 2 seconds
setTimeout(() => {
window.location.href = `https://${window.location.hostname}/`;
window.location.href = `https://${window.location.hostname}${window.location.port === '8087' ? ':8443' : ''}/`;
}, 2000);
}
} catch (e) {
@ -1923,16 +1923,28 @@
// Show fallback banner
document.getElementById('fallback-banner').classList.add('show');
// Poll main API to check if it's back up
// Use absolute URL to main WROLPi API (HTTPS on port 443), not Controller
const mainApiUrl = `https://${window.location.hostname}/api/status`;
// Poll main API and React app to check if they're back up
const checkMainApp = async () => {
try {
// Check main API status endpoint (not Controller)
const resp = await fetch(mainApiUrl, {method: 'HEAD'});
if (resp.ok) {
// Main app is back, redirect to main app
window.location.href = `https://${window.location.hostname}/`;
const httpsBase = `https://${window.location.hostname}${window.location.port === '8087' ? ':8443' : ''}`;
// Check both API and React app are up before redirecting.
// API check uses fetch; app check uses an Image probe to avoid CORS (http→https).
// When the app is down, Caddy redirects to the controller, so the image src won't
// resolve to a valid image and will error.
const appUp = new Promise((resolve) => {
const img = new Image();
img.onload = () => resolve(true);
img.onerror = () => resolve(false);
img.src = `${httpsBase}/favicon.ico?_=${Date.now()}`;
setTimeout(() => resolve(false), 5000);
});
const [apiResp, isAppUp] = await Promise.all([
fetch(`${httpsBase}/api/status`, {method: 'HEAD', signal: AbortSignal.timeout(5000)}),
appUp
]);
// API must respond OK; app must serve its favicon (not redirect to controller).
if (apiResp.ok && isAppUp) {
window.location.href = `${httpsBase}/`;
}
} catch (e) {
// Still down, continue polling

View file

@ -25,6 +25,10 @@
:443 {
tls /etc/ssl/caddy/cert.crt /etc/ssl/caddy/cert.key
header Access-Control-Allow-Origin "*"
header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
header Access-Control-Allow-Headers "Content-Type, Accept"
handle /favicon.ico {
root * /etc/caddy
rewrite * /favicon.ico
@ -84,13 +88,11 @@
# React app with fallback to Controller.
handle {
reverse_proxy app:3000 {
@bad status 502 503 504
handle_response @bad {
header Location /controller/?fallback=true
respond "" 302
}
}
reverse_proxy app:3000
}
handle_errors 502 503 504 {
redir * "http://{host}:8087/?fallback=true" 302
}
}

View file

@ -88,22 +88,11 @@
# React app with fallback to Controller.
handle {
reverse_proxy 0.0.0.0:3000 {
@bad status 502 503 504
handle_response @bad {
header Location /controller/?fallback=true
respond "" 302
}
}
reverse_proxy 0.0.0.0:3000
}
handle_errors {
header Access-Control-Allow-Origin "*"
header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
header Access-Control-Allow-Headers "Content-Type, Accept"
root * /var/www
rewrite * /50x.html
file_server
handle_errors 502 503 504 {
redir * "http://{host}:8087/?fallback=true" 302
}
}