server reboot too

This commit is contained in:
legop3
2026-02-18 15:13:38 -05:00
parent 40e21d4ac5
commit 7e772ab607
6 changed files with 98 additions and 11 deletions
+1
View File
@@ -18,6 +18,7 @@ require('./src/services/assignmentService');
require('./src/services/nicknameService');
require('./src/services/chatService');
require('./src/services/communityGoalService');
require('./src/services/serverControlService');
require('./src/services/videoSessions');
require('./src/services/videoAuthService');
require('./src/services/videoSocketService');
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -11,7 +11,7 @@
<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-BIUOTGXU.js"></script>
<script type="module" crossorigin src="/assets/index-RGOkx1Qn.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Bz1ixYh7.css">
</head>
<body>
@@ -0,0 +1,52 @@
const { spawn } = require('child_process');
const io = require('../globals/io');
const logger = require('../globals/logger').child('serverControlService');
const { isAdmin } = require('./roleService');
const { sendAlert } = require('./alertService');
const ALERT_COLOR = '#ff5722';
let rebootPending = false;
function scheduleSystemReboot() {
if (rebootPending) {
throw new Error('Server reboot already pending');
}
rebootPending = true;
setTimeout(() => {
logger.warn('Issuing system reboot command');
try {
const child = spawn('systemctl', ['reboot'], {
detached: true,
stdio: 'ignore',
});
child.unref();
} catch (err) {
rebootPending = false;
logger.error('Server reboot command failed', err.message);
}
}, 400);
}
io.on('connection', (socket) => {
socket.on('server:reboot', (_, cb = () => {}) => {
if (!isAdmin(socket)) {
cb({ error: 'Not authorized' });
return;
}
try {
scheduleSystemReboot();
const who = socket?.data?.user?.username || socket.id;
logger.warn('Server reboot requested', { by: who });
sendAlert({
color: ALERT_COLOR,
title: 'Server Reboot',
message: `Reboot requested by ${who}`,
});
cb({ success: true });
} catch (err) {
cb({ error: err.message });
}
});
});
+34 -2
View File
@@ -10,11 +10,21 @@ const MODES = [
];
export default function AdminPanel() {
const { session, lockRover, setMode, requestControl, setCommunityGoal, setAdminReason, rebootRover, adminLogs } =
useSession();
const {
session,
lockRover,
setMode,
requestControl,
setCommunityGoal,
setAdminReason,
rebootRover,
rebootServer,
adminLogs,
} = useSession();
const roster = useMemo(() => session?.roster ?? [], [session?.roster]);
const [lockStates, setLockStates] = useState({});
const [rebootStates, setRebootStates] = useState({});
const [serverRebooting, setServerRebooting] = useState(false);
const health = session?.health || null;
const currentGoal = session?.communityGoal?.text || '';
const goalUpdatedAt = session?.communityGoal?.updatedAt || null;
@@ -70,6 +80,18 @@ export default function AdminPanel() {
}
};
const handleServerReboot = async () => {
const ok = window.confirm('Reboot the server host now? This will disconnect all users.');
if (!ok) return;
setServerRebooting(true);
try {
await rebootServer();
} catch (err) {
alert(err.message);
setServerRebooting(false);
}
};
const handleGoalSave = async () => {
try {
await setCommunityGoal(goalDraft);
@@ -132,6 +154,16 @@ export default function AdminPanel() {
))}
</select>
</div>
<div className="flex gap-0.5 text-xs">
<button
type="button"
onClick={handleServerReboot}
disabled={serverRebooting}
className="button-danger disabled:cursor-not-allowed disabled:opacity-60"
>
{serverRebooting ? 'Server rebooting...' : 'Reboot Server'}
</button>
</div>
<div className="space-y-0.5">
<div className="flex items-center justify-between text-xs text-slate-400">
<span>Community goal</span>
+2
View File
@@ -21,6 +21,7 @@ const SessionContext = createContext({
setCommunityGoal: async () => {},
setAdminReason: async () => {},
rebootRover: async () => {},
rebootServer: async () => {},
});
function useAckEmitter(socket) {
@@ -120,6 +121,7 @@ export function SessionProvider({ children }) {
setAdminReason: (text) => emitWithAck('adminReason:set', { text }),
rebootRover: (roverId) =>
emitWithAck('command', { roverId, type: 'reboot', data: { reboot: {} } }),
rebootServer: () => emitWithAck('server:reboot'),
pushAlert: (alert) =>
setAlerts((prev) => [
...prev.slice(-49),