new alert service colors

This commit is contained in:
legop3
2026-01-08 22:44:31 -05:00
parent 55782dabf0
commit acebcd7307
10 changed files with 54 additions and 45 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -11,8 +11,8 @@
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Multi Roomba Rover" />
<title>Multi Roomba Rover</title>
<script type="module" crossorigin src="/assets/index-D8hW3Mn4.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DCrU2sZz.css">
<script type="module" crossorigin src="/assets/index-BgRdubNv.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-aZHU9GG3.css">
</head>
<body>
<div id="root"></div>
+1 -9
View File
@@ -1,15 +1,8 @@
const io = require('../globals/io');
const COLORS = {
info: '#2196f3',
success: '#4caf50',
warn: '#f0b651',
error: '#e53935',
};
function sendAlert({ color, title, message, ts = Date.now() }) {
const payload = {
color: color || COLORS.info,
color: color || '#2196f3',
title,
message,
ts,
@@ -19,6 +12,5 @@ function sendAlert({ color, title, message, ts = Date.now() }) {
}
module.exports = {
COLORS,
sendAlert,
};
+4 -3
View File
@@ -1,6 +1,7 @@
const EventEmitter = require('events');
const io = require('../globals/io');
const { sendAlert, COLORS } = require('./alertService');
const { sendAlert } = require('./alertService');
const ALERT_COLOR = '#ff9800';
const { isAdmin, isLockdownAdmin } = require('./roleService');
const { publishEvent } = require('./eventBus');
@@ -34,7 +35,7 @@ function setMode(nextMode, socket, options = {}) {
}
currentMode = nextMode;
sendAlert({
color: COLORS.info,
color: ALERT_COLOR,
title: 'Mode Changed',
message: `Server mode set to ${nextMode}`,
});
@@ -65,7 +66,7 @@ io.on('connection', (socket) => {
try {
setMode(mode, socket);
} catch (err) {
sendAlert({ color: COLORS.error, title: 'Mode change failed', message: err.message });
sendAlert({ color: ALERT_COLOR, title: 'Mode change failed', message: err.message });
}
});
});
@@ -1,20 +1,21 @@
const roverWSS = require('../globals/ws');
const logger = require('../globals/logger').child('roverConnection');
const roverManager = require('./roverManager');
const { sendAlert, COLORS } = require('./alertService');
const { sendAlert } = require('./alertService');
const ALERT_COLOR = '#00bcd4';
const { handleAck } = require('./commandService');
function handleMessage(roverId, msg) {
switch (msg.type) {
case 'hello':
roverManager.upsertRover(msg, this);
sendAlert({ color: COLORS.info, title: 'Rover Connected', message: roverId });
sendAlert({ color: ALERT_COLOR, title: 'Rover Connected', message: roverId });
break;
case 'sensor':
roverManager.handleSensorFrame(roverId, msg);
break;
case 'event':
sendAlert({ color: COLORS.info, title: `${roverId} event`, message: msg.event });
sendAlert({ color: ALERT_COLOR, title: `${roverId} event`, message: msg.event });
break;
default:
break;
@@ -40,7 +41,7 @@ roverWSS.on('connection', (ws) => {
});
roverManager.upsertRover(msg, ws);
roverManager.broadcastRoster();
sendAlert({ color: COLORS.success, title: 'Rover Online', message: roverId });
sendAlert({ color: ALERT_COLOR, title: 'Rover Online', message: roverId });
return;
}
if (!roverId) return;
@@ -49,14 +50,14 @@ roverWSS.on('connection', (ws) => {
} else if (msg.type === 'ack') {
handleAck(msg);
} else if (msg.type === 'event') {
sendAlert({ color: COLORS.info, title: `${roverId}`, message: msg.event });
sendAlert({ color: ALERT_COLOR, title: `${roverId}`, message: msg.event });
}
});
ws.on('close', () => {
if (roverId) {
roverManager.removeRover(roverId);
sendAlert({ color: COLORS.warn, title: 'Rover Offline', message: roverId });
sendAlert({ color: ALERT_COLOR, title: 'Rover Offline', message: roverId });
}
});
});
+8 -7
View File
@@ -1,7 +1,8 @@
const EventEmitter = require('events');
const io = require('../globals/io');
const logger = require('../globals/logger').child('roverManager');
const { sendAlert, COLORS } = require('./alertService');
const { sendAlert } = require('./alertService');
const ALERT_COLOR = '#8bc34a';
const { parseSensorFrame } = require('../helpers/sensorDecoder');
const { MODES, getMode } = require('./modeManager');
const { isAdmin, roleEvents } = require('./roleService');
@@ -89,7 +90,7 @@ function lockRover(id, locked, options = {}) {
record.lockReason = reason;
if (!silent) {
sendAlert({
color: COLORS.warn,
color: ALERT_COLOR,
title: 'Rover Locked',
message: `${id} locked${record.lockReason ? ` (${record.lockReason})` : ''}.`,
});
@@ -103,7 +104,7 @@ function lockRover(id, locked, options = {}) {
record.locked = false;
record.lockReason = null;
if (!silent) {
sendAlert({ color: COLORS.success, title: 'Rover Unlocked', message: `${id} unlocked.` });
sendAlert({ color: ALERT_COLOR, title: 'Rover Unlocked', message: `${id} unlocked.` });
}
publishEvent({
source: 'roverManager',
@@ -218,7 +219,7 @@ function handleIdleUndock(undockedRecord) {
const bumpRecent =
suspectRecord.lastBumpAt && now - suspectRecord.lastBumpAt <= DOCK_GUARD_WINDOW_MS;
sendAlert({
color: COLORS.warn,
color: ALERT_COLOR,
title: 'Dock protection',
message: `${undockedRecord.id} undocked while idle; stopping ${suspect.roverId}.`,
});
@@ -309,7 +310,7 @@ function requestControl(roverId, socket, options = {}) {
socket.emit('controlGranted', { roverId });
managerEvents.emit('driver', { socketId: socket.id, roverId, action: 'add' });
sendAlert({
color: COLORS.success,
color: ALERT_COLOR,
title: 'Control Granted',
message: `${socket.id} now driving ${roverId}`,
});
@@ -464,7 +465,7 @@ io.on('connection', (socket) => {
cb({ success: true, roverId: targetId });
} catch (err) {
logger.warn('Request control failed', socket.id, err.message);
sendAlert({ color: COLORS.warn, title: 'Control denied', message: err.message });
sendAlert({ color: ALERT_COLOR, title: 'Control denied', message: err.message });
cb({ error: err.message });
}
}
@@ -490,7 +491,7 @@ io.on('connection', (socket) => {
cb({ success: true });
} catch (err) {
logger.warn('Lock change failed', roverId, err.message);
sendAlert({ color: COLORS.error, title: 'Lock failed', message: err.message });
sendAlert({ color: ALERT_COLOR, title: 'Lock failed', message: err.message });
cb({ error: err.message });
}
}
+5 -4
View File
@@ -1,6 +1,7 @@
const EventEmitter = require('events');
const io = require('../globals/io');
const { sendAlert, COLORS } = require('./alertService');
const { sendAlert } = require('./alertService');
const ALERT_COLOR = '#ff5722';
const { MODES, getMode, modeEvents } = require('./modeManager');
const driverQueues = new Map(); // roverId -> { queue: [], current: socketId, timer: Timeout | null }
@@ -159,7 +160,7 @@ function handleIdleTimeout(roverId, expectedDriver) {
stopRover(roverId);
if (skips >= MAX_IDLE_SKIPS) {
sendAlert({
color: COLORS.error,
color: ALERT_COLOR,
title: 'Driver removed',
message: `${expectedDriver} removed from ${roverId} after ${skips} idle skips`,
});
@@ -167,7 +168,7 @@ function handleIdleTimeout(roverId, expectedDriver) {
return;
}
sendAlert({
color: COLORS.warn,
color: ALERT_COLOR,
title: 'Turn skipped',
message: `${expectedDriver} skipped on ${roverId} (idle ${skips}/${MAX_IDLE_SKIPS})`,
});
@@ -207,7 +208,7 @@ function advanceTurn(roverId) {
queue.current = queue.queue[nextIdx];
setActiveDriver(roverId, queue.current);
idleDisarmed.set(roverId, false);
sendAlert({ color: COLORS.info, title: 'Turn switch', message: `${queue.current} now controls ${roverId}` });
sendAlert({ color: ALERT_COLOR, title: 'Turn switch', message: `${queue.current} now controls ${roverId}` });
stopRover(roverId);
scheduleNextTurn(roverId);
scheduleIdleTimer(roverId);
+20 -7
View File
@@ -2,6 +2,7 @@ import { useEffect, useMemo, useState } from 'react';
import { useSession } from '../context/SessionContext.jsx';
const LIFETIME_MS = 5000;
const DEFAULT_COLOR = '#2196f3';
function buildKey(alert) {
if (alert.id) return alert.id;
@@ -38,15 +39,27 @@ export default function AlertFeed() {
);
}
function hexToRgb(hex) {
const safe = typeof hex === 'string' ? hex.trim() : '';
const match = /^#?([0-9a-fA-F]{6})$/.exec(safe);
if (!match) return null;
const value = match[1];
return {
r: parseInt(value.slice(0, 2), 16),
g: parseInt(value.slice(2, 4), 16),
b: parseInt(value.slice(4, 6), 16),
};
}
function AlertToast({ alert }) {
const colorClasses =
alert.color === 'error'
? 'bg-red-500/30 text-red-100'
: alert.color === 'warn'
? 'bg-amber-500/30 text-amber-100'
: 'bg-emerald-500/30 text-emerald-100';
const rgb = hexToRgb(alert.color) || hexToRgb(DEFAULT_COLOR);
const backgroundColor = rgb ? `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.25)` : 'rgba(33, 150, 243, 0.25)';
const borderColor = rgb ? `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.7)` : 'rgba(33, 150, 243, 0.7)';
return (
<div className={`pointer-events-auto px-0.5 py-0.5 text-[0.85rem] ${colorClasses}`}>
<div
className="pointer-events-auto px-0.5 py-0.5 text-[0.85rem] text-slate-100"
style={{ backgroundColor, border: `1px solid ${borderColor}` }}
>
<p className="text-xs text-slate-200">{alert.title || 'Alert'}</p>
<p className="text-sm text-white">{alert.message}</p>
</div>
+1 -1
View File
@@ -45,7 +45,7 @@ export default function TurnAlertListener() {
pushAlert({
title: 'Your turn!',
message: `You now control ${roverName}.`,
color: 'success',
color: '#ff5722',
});
});
playSound();