This commit is contained in:
legop3
2025-11-17 14:29:15 -05:00
parent 922f0b1188
commit fb04103593
9 changed files with 102 additions and 13 deletions
File diff suppressed because one or more lines are too long
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
@@ -5,8 +5,8 @@
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>webui</title>
<script type="module" crossorigin src="/assets/index-we2n3Zya.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-C9u9txZ3.css">
<script type="module" crossorigin src="/assets/index-BvgUoEqO.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DwQ38q3M.css">
</head>
<body>
<div id="root"></div>
+5 -2
View File
@@ -146,11 +146,14 @@ function pickRover() {
}
function describeAssignment(socketId) {
const roverId = assignments.get(socketId) || null;
const assignedRoverId = assignments.get(socketId) || null;
const adminRoverId = assignedRoverId ? null : roverManager.getPrimaryRoverForSocket(socketId);
const waitingIndex = waiting.has(socketId) ? Array.from(waiting).indexOf(socketId) : -1;
const roverId = assignedRoverId || adminRoverId || null;
const waitingStatus = waiting.has(socketId);
return {
roverId,
status: roverId ? 'assigned' : waiting.has(socketId) ? 'waiting' : null,
status: assignedRoverId ? 'assigned' : adminRoverId ? 'admin' : waitingStatus ? 'waiting' : null,
queuePosition: waitingIndex >= 0 ? waitingIndex + 1 : null,
};
}
+23
View File
@@ -115,6 +115,7 @@ function removeSocket(socket) {
record.drivers.delete(socket.id);
}
turnService.driverRemoved(roverId, socket.id);
managerEvents.emit('driver', { socketId: socket.id, roverId, action: 'remove' });
}
socketToRovers.delete(socket.id);
disableSpectator(socket);
@@ -147,6 +148,7 @@ function requestControl(roverId, socket, options = {}) {
socket.join(record.room);
turnService.driverAdded(roverId, socket.id, force && isAdmin(socket));
socket.emit('controlGranted', { roverId });
managerEvents.emit('driver', { socketId: socket.id, roverId, action: 'add' });
sendAlert({
color: COLORS.success,
title: 'Control Granted',
@@ -168,6 +170,7 @@ function releaseControl(roverId, socket) {
}
socket.leave(record.room);
turnService.driverRemoved(roverId, socket.id);
managerEvents.emit('driver', { socketId: socket.id, roverId, action: 'remove' });
}
function isDriver(roverId, socket) {
@@ -180,6 +183,24 @@ function canDrive(roverId, socket) {
return turnService.canDrive(roverId, socket) || isAdmin(socket);
}
function getRoversForSocket(socketId) {
const joined = socketToRovers.get(socketId);
if (!joined || joined.size === 0) {
return [];
}
return Array.from(joined);
}
function getPrimaryRoverForSocket(socketId) {
const joined = socketToRovers.get(socketId);
if (!joined || joined.size === 0) {
return null;
}
const iterator = joined.values();
const first = iterator.next();
return first.done ? null : first.value;
}
module.exports = {
upsertRover,
removeRover,
@@ -196,6 +217,8 @@ module.exports = {
disableSpectator,
rovers,
managerEvents,
getRoversForSocket,
getPrimaryRoverForSocket,
};
roleEvents.on('change', ({ socket, role }) => {
+9
View File
@@ -64,6 +64,15 @@ managerEvents.on('lock', ({ roverId, locked }) => {
syncAll();
});
managerEvents.on('driver', ({ socketId }) => {
if (!socketId) return;
const socket = io.sockets.sockets.get(socketId);
if (socket) {
logger.info('Driver assignment change; syncing session', socketId);
syncSocket(socket);
}
});
turnEvents.on('activeDriver', () => {
logger.info('Active driver change; syncing all clients');
syncAll();
+2
View File
@@ -10,6 +10,7 @@ import LogPanel from './components/LogPanel.jsx';
import AuthPanel from './components/AuthPanel.jsx';
import DriverVideoPanel from './components/DriverVideoPanel.jsx';
import RightPaneTabs from './components/RightPaneTabs.jsx';
import ModeGateOverlay from './components/ModeGateOverlay.jsx';
import SessionSnapshot from './components/SessionSnapshot.jsx';
function useLayoutMode() {
@@ -115,6 +116,7 @@ function App() {
<DriveControlProvider>
<main className="flex w-full flex-col gap-1 px-1 py-1 text-base">{renderedLayout}</main>
<AlertFeed />
<ModeGateOverlay />
</DriveControlProvider>
</div>
);
+52
View File
@@ -0,0 +1,52 @@
import AuthPanel from './AuthPanel.jsx';
import { useSession } from '../context/SessionContext.jsx';
const PRIVILEGED_ROLES = new Set(['admin', 'lockdown', 'lockdown-admin']);
const RESTRICTED_MODES = new Set(['admin', 'lockdown']);
function getModeDetails(mode = 'admin') {
if (mode === 'lockdown') {
return {
title: 'Lockdown in effect',
description:
'Only the lockdown admin can remain connected right now. If you have credentials, log in below. Otherwise please stand by until the server re-opens.',
};
}
return {
title: 'Admin mode active',
description:
'The server is temporarily restricted to administrators. Log in below if you are authorized, or wait for admin mode to end.',
};
}
export default function ModeGateOverlay() {
const { session } = useSession();
const mode = session?.mode;
const role = session?.role;
const restricted = RESTRICTED_MODES.has(mode);
const privileged = PRIVILEGED_ROLES.has(role);
if (!restricted || privileged) {
return null;
}
const details = getModeDetails(mode);
return (
<div className="pointer-events-auto fixed inset-0 z-50 flex items-center justify-center bg-black/85 px-2 py-2">
<div className="w-full max-w-md space-y-2 rounded-lg bg-[#141821] p-2 text-slate-100 shadow-2xl">
<div className="space-y-1">
<p className="text-lg font-semibold">{details.title}</p>
<p className="text-sm text-slate-300">{details.description}</p>
</div>
<div className="rounded-md bg-black/40 p-1">
<AuthPanel />
</div>
<p className="text-xs text-slate-500">
Your controls are paused until access is granted. You will automatically regain the interface once the mode
changes or after a successful login.
</p>
</div>
</div>
);
}