mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-15 17:12:59 -04:00
admin
This commit is contained in:
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
@@ -5,8 +5,8 @@
|
|||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>webui</title>
|
<title>webui</title>
|
||||||
<script type="module" crossorigin src="/assets/index-we2n3Zya.js"></script>
|
<script type="module" crossorigin src="/assets/index-BvgUoEqO.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-C9u9txZ3.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-DwQ38q3M.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -146,11 +146,14 @@ function pickRover() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function describeAssignment(socketId) {
|
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 waitingIndex = waiting.has(socketId) ? Array.from(waiting).indexOf(socketId) : -1;
|
||||||
|
const roverId = assignedRoverId || adminRoverId || null;
|
||||||
|
const waitingStatus = waiting.has(socketId);
|
||||||
return {
|
return {
|
||||||
roverId,
|
roverId,
|
||||||
status: roverId ? 'assigned' : waiting.has(socketId) ? 'waiting' : null,
|
status: assignedRoverId ? 'assigned' : adminRoverId ? 'admin' : waitingStatus ? 'waiting' : null,
|
||||||
queuePosition: waitingIndex >= 0 ? waitingIndex + 1 : null,
|
queuePosition: waitingIndex >= 0 ? waitingIndex + 1 : null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ function removeSocket(socket) {
|
|||||||
record.drivers.delete(socket.id);
|
record.drivers.delete(socket.id);
|
||||||
}
|
}
|
||||||
turnService.driverRemoved(roverId, socket.id);
|
turnService.driverRemoved(roverId, socket.id);
|
||||||
|
managerEvents.emit('driver', { socketId: socket.id, roverId, action: 'remove' });
|
||||||
}
|
}
|
||||||
socketToRovers.delete(socket.id);
|
socketToRovers.delete(socket.id);
|
||||||
disableSpectator(socket);
|
disableSpectator(socket);
|
||||||
@@ -147,6 +148,7 @@ function requestControl(roverId, socket, options = {}) {
|
|||||||
socket.join(record.room);
|
socket.join(record.room);
|
||||||
turnService.driverAdded(roverId, socket.id, force && isAdmin(socket));
|
turnService.driverAdded(roverId, socket.id, force && isAdmin(socket));
|
||||||
socket.emit('controlGranted', { roverId });
|
socket.emit('controlGranted', { roverId });
|
||||||
|
managerEvents.emit('driver', { socketId: socket.id, roverId, action: 'add' });
|
||||||
sendAlert({
|
sendAlert({
|
||||||
color: COLORS.success,
|
color: COLORS.success,
|
||||||
title: 'Control Granted',
|
title: 'Control Granted',
|
||||||
@@ -168,6 +170,7 @@ function releaseControl(roverId, socket) {
|
|||||||
}
|
}
|
||||||
socket.leave(record.room);
|
socket.leave(record.room);
|
||||||
turnService.driverRemoved(roverId, socket.id);
|
turnService.driverRemoved(roverId, socket.id);
|
||||||
|
managerEvents.emit('driver', { socketId: socket.id, roverId, action: 'remove' });
|
||||||
}
|
}
|
||||||
|
|
||||||
function isDriver(roverId, socket) {
|
function isDriver(roverId, socket) {
|
||||||
@@ -180,6 +183,24 @@ function canDrive(roverId, socket) {
|
|||||||
return turnService.canDrive(roverId, socket) || isAdmin(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 = {
|
module.exports = {
|
||||||
upsertRover,
|
upsertRover,
|
||||||
removeRover,
|
removeRover,
|
||||||
@@ -196,6 +217,8 @@ module.exports = {
|
|||||||
disableSpectator,
|
disableSpectator,
|
||||||
rovers,
|
rovers,
|
||||||
managerEvents,
|
managerEvents,
|
||||||
|
getRoversForSocket,
|
||||||
|
getPrimaryRoverForSocket,
|
||||||
};
|
};
|
||||||
|
|
||||||
roleEvents.on('change', ({ socket, role }) => {
|
roleEvents.on('change', ({ socket, role }) => {
|
||||||
|
|||||||
@@ -64,6 +64,15 @@ managerEvents.on('lock', ({ roverId, locked }) => {
|
|||||||
syncAll();
|
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', () => {
|
turnEvents.on('activeDriver', () => {
|
||||||
logger.info('Active driver change; syncing all clients');
|
logger.info('Active driver change; syncing all clients');
|
||||||
syncAll();
|
syncAll();
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import LogPanel from './components/LogPanel.jsx';
|
|||||||
import AuthPanel from './components/AuthPanel.jsx';
|
import AuthPanel from './components/AuthPanel.jsx';
|
||||||
import DriverVideoPanel from './components/DriverVideoPanel.jsx';
|
import DriverVideoPanel from './components/DriverVideoPanel.jsx';
|
||||||
import RightPaneTabs from './components/RightPaneTabs.jsx';
|
import RightPaneTabs from './components/RightPaneTabs.jsx';
|
||||||
|
import ModeGateOverlay from './components/ModeGateOverlay.jsx';
|
||||||
import SessionSnapshot from './components/SessionSnapshot.jsx';
|
import SessionSnapshot from './components/SessionSnapshot.jsx';
|
||||||
|
|
||||||
function useLayoutMode() {
|
function useLayoutMode() {
|
||||||
@@ -115,6 +116,7 @@ function App() {
|
|||||||
<DriveControlProvider>
|
<DriveControlProvider>
|
||||||
<main className="flex w-full flex-col gap-1 px-1 py-1 text-base">{renderedLayout}</main>
|
<main className="flex w-full flex-col gap-1 px-1 py-1 text-base">{renderedLayout}</main>
|
||||||
<AlertFeed />
|
<AlertFeed />
|
||||||
|
<ModeGateOverlay />
|
||||||
</DriveControlProvider>
|
</DriveControlProvider>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user