rov-autonomy/widgets/w3_abort_button.html

304 lines
9.0 KiB
HTML

<!DOCTYPE html>
<!--
============================================================
ARGONAUT 3 — ABORT BUTTON
Widget 3
============================================================
Purpose : Send an abort command to the mission executor via
a confirm dialog → POST to the FastAPI backend.
Safety : The confirm step is non-negotiable. A single press
shows a dialog. A second press (confirm) fires the
POST. The dialog auto-dismisses after 10 seconds
if not confirmed, to prevent accidental arming.
Endpoint: POST http://<FASTAPI_HOST>/abort
(FastAPI publishes true to /rov/mission/abort)
Config : Edit FASTAPI_HOST below to match your deployment.
During dev: the BlueOS VM IP + port 8081
In field: http://blueos.local:8081
Import : Cockpit → Edit mode → Add DIY Widget →
gear icon → Import → select this file
Version : 1.0
============================================================
-->
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #09111a;
--bg2: #160a0f; /* red-tinted background */
--border: #3a1020;
--text0: #d8eeff;
--text1: #8fb3d4;
--red: #ff3a5a;
--red-dim: #7a1a28;
--green: #00e09a;
--amber: #ffb830;
--mono: 'Courier New', monospace;
}
body {
background: var(--bg);
color: var(--text0);
font-family: var(--mono);
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
overflow: hidden;
padding: 12px;
gap: 10px;
}
/* ── Main abort button ───────────────────────────────────── */
#abort-btn {
width: 100%;
padding: 14px 0;
background: var(--red-dim);
border: 2px solid var(--red);
border-radius: 6px;
color: var(--red);
font-family: var(--mono);
font-size: 15px;
font-weight: bold;
letter-spacing: 0.15em;
text-transform: uppercase;
cursor: pointer;
transition: background 0.15s, box-shadow 0.15s;
}
#abort-btn:hover {
background: #2a0c18;
box-shadow: 0 0 16px var(--red);
}
#abort-btn:active {
background: var(--red);
color: #fff;
}
/* Button disabled state (after abort sent) */
#abort-btn:disabled {
background: #1a0a10;
border-color: #3a1020;
color: var(--red-dim);
cursor: not-allowed;
box-shadow: none;
}
/* ── Confirm dialog overlay ──────────────────────────────── */
#confirm-overlay {
display: none; /* shown when confirm needed */
position: fixed;
inset: 0;
background: rgba(0,0,0,0.7);
align-items: center;
justify-content: center;
z-index: 10;
}
/* Show when active */
#confirm-overlay.active { display: flex; }
/* Dialog box */
#confirm-box {
background: #100510;
border: 2px solid var(--red);
border-radius: 8px;
padding: 18px;
max-width: 220px;
text-align: center;
box-shadow: 0 0 30px var(--red);
}
#confirm-box p {
font-size: 12px;
color: var(--text0);
line-height: 1.5;
margin-bottom: 14px;
}
#confirm-box .btn-row {
display: flex;
gap: 8px;
justify-content: center;
}
/* Confirm (yes) button */
#confirm-yes {
flex: 1;
padding: 8px;
background: var(--red);
border: none;
border-radius: 4px;
color: #fff;
font-family: var(--mono);
font-size: 12px;
font-weight: bold;
cursor: pointer;
letter-spacing: 0.08em;
}
#confirm-yes:hover { background: #ff6080; }
/* Cancel (no) button */
#confirm-no {
flex: 1;
padding: 8px;
background: transparent;
border: 1px solid var(--text1);
border-radius: 4px;
color: var(--text1);
font-family: var(--mono);
font-size: 12px;
cursor: pointer;
}
#confirm-no:hover { border-color: var(--text0); color: var(--text0); }
/* Auto-dismiss countdown */
#countdown {
font-size: 10px;
color: var(--red-dim);
margin-top: 8px;
}
/* ── Status message ──────────────────────────────────────── */
#status {
font-size: 10px;
color: var(--text1);
text-align: center;
min-height: 14px;
}
#status.ok { color: var(--green); }
#status.err { color: var(--red); }
</style>
<body>
<!-- Main button -->
<button id="abort-btn">⚠ ABORT MISSION</button>
<!-- Status message below the button -->
<div id="status">Standby</div>
<!-- Confirm dialog overlay (hidden until button pressed) -->
<div id="confirm-overlay">
<div id="confirm-box">
<p>Abort mission?<br>Vehicle will hold position<br>and await instruction.</p>
<div class="btn-row">
<button id="confirm-yes">ABORT</button>
<button id="confirm-no">Cancel</button>
</div>
<div id="countdown"></div>
</div>
</div>
</body>
<script>
// ── Config ────────────────────────────────────────────────
// Change this to match your FastAPI deployment:
// Dev: http://192.168.122.89:8081 (BlueOS VM IP)
// Field: http://blueos.local:8081
const FASTAPI_HOST = 'http://blueos.local:8081';
// Seconds before the confirm dialog auto-dismisses
const AUTO_DISMISS_SEC = 10;
// ── DOM references ────────────────────────────────────────
const abortBtn = document.getElementById('abort-btn');
const overlay = document.getElementById('confirm-overlay');
const confirmYes = document.getElementById('confirm-yes');
const confirmNo = document.getElementById('confirm-no');
const countdownEl = document.getElementById('countdown');
const statusEl = document.getElementById('status');
// ── Auto-dismiss timer handle ─────────────────────────────
let dismissTimer = null;
let countdownVal = AUTO_DISMISS_SEC;
// ── Show the confirm dialog ───────────────────────────────
function showConfirm() {
overlay.classList.add('active');
countdownVal = AUTO_DISMISS_SEC;
countdownEl.textContent = `Auto-cancel in ${countdownVal}s`;
// Countdown tick — dismiss automatically if ignored
dismissTimer = setInterval(() => {
countdownVal--;
countdownEl.textContent = `Auto-cancel in ${countdownVal}s`;
if (countdownVal <= 0) hideConfirm();
}, 1000);
}
// ── Hide the confirm dialog ───────────────────────────────
function hideConfirm() {
overlay.classList.remove('active');
clearInterval(dismissTimer);
dismissTimer = null;
countdownEl.textContent = '';
}
// ── Send the abort command ────────────────────────────────
async function sendAbort() {
hideConfirm();
abortBtn.disabled = true;
statusEl.className = '';
statusEl.textContent = 'Sending abort…';
try {
const res = await fetch(`${FASTAPI_HOST}/abort`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ abort: true }),
// Short timeout — we don't want to hang
signal: AbortSignal.timeout(5000),
});
if (res.ok) {
statusEl.className = 'ok';
statusEl.textContent = 'Abort sent — vehicle holding';
} else {
statusEl.className = 'err';
statusEl.textContent = `Server error: ${res.status}`;
abortBtn.disabled = false; // allow retry
}
} catch (err) {
statusEl.className = 'err';
statusEl.textContent = 'Network error — check connection';
abortBtn.disabled = false; // allow retry
console.error('[Abort Widget]', err);
}
}
// ── Event listeners ───────────────────────────────────────
// First press — show dialog
abortBtn.addEventListener('click', () => {
if (!abortBtn.disabled) showConfirm();
});
// Confirm — fire abort
confirmYes.addEventListener('click', sendAbort);
// Cancel — dismiss dialog
confirmNo.addEventListener('click', hideConfirm);
// ── Reset button after 30s (allows re-abort if needed) ───
// In real operations the button stays disabled until the
// vehicle is verified safe and a manual reset is done.
// Uncomment the block below to allow automatic re-enable:
/*
function resetButton() {
setTimeout(() => {
abortBtn.disabled = false;
statusEl.className = '';
statusEl.textContent = 'Standby';
}, 30000);
}
*/
</script>