304 lines
9.0 KiB
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>
|