mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-17 01:50:47 -04:00
server reboot too
This commit is contained in:
@@ -18,6 +18,7 @@ require('./src/services/assignmentService');
|
|||||||
require('./src/services/nicknameService');
|
require('./src/services/nicknameService');
|
||||||
require('./src/services/chatService');
|
require('./src/services/chatService');
|
||||||
require('./src/services/communityGoalService');
|
require('./src/services/communityGoalService');
|
||||||
|
require('./src/services/serverControlService');
|
||||||
require('./src/services/videoSessions');
|
require('./src/services/videoSessions');
|
||||||
require('./src/services/videoAuthService');
|
require('./src/services/videoAuthService');
|
||||||
require('./src/services/videoSocketService');
|
require('./src/services/videoSocketService');
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -11,7 +11,7 @@
|
|||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||||
<meta name="apple-mobile-web-app-title" content="Multi Roomba Rover" />
|
<meta name="apple-mobile-web-app-title" content="Multi Roomba Rover" />
|
||||||
<title>Multi Roomba Rover</title>
|
<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">
|
<link rel="stylesheet" crossorigin href="/assets/index-Bz1ixYh7.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<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 });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
@@ -10,11 +10,21 @@ const MODES = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export default function AdminPanel() {
|
export default function AdminPanel() {
|
||||||
const { session, lockRover, setMode, requestControl, setCommunityGoal, setAdminReason, rebootRover, adminLogs } =
|
const {
|
||||||
useSession();
|
session,
|
||||||
|
lockRover,
|
||||||
|
setMode,
|
||||||
|
requestControl,
|
||||||
|
setCommunityGoal,
|
||||||
|
setAdminReason,
|
||||||
|
rebootRover,
|
||||||
|
rebootServer,
|
||||||
|
adminLogs,
|
||||||
|
} = useSession();
|
||||||
const roster = useMemo(() => session?.roster ?? [], [session?.roster]);
|
const roster = useMemo(() => session?.roster ?? [], [session?.roster]);
|
||||||
const [lockStates, setLockStates] = useState({});
|
const [lockStates, setLockStates] = useState({});
|
||||||
const [rebootStates, setRebootStates] = useState({});
|
const [rebootStates, setRebootStates] = useState({});
|
||||||
|
const [serverRebooting, setServerRebooting] = useState(false);
|
||||||
const health = session?.health || null;
|
const health = session?.health || null;
|
||||||
const currentGoal = session?.communityGoal?.text || '';
|
const currentGoal = session?.communityGoal?.text || '';
|
||||||
const goalUpdatedAt = session?.communityGoal?.updatedAt || null;
|
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 () => {
|
const handleGoalSave = async () => {
|
||||||
try {
|
try {
|
||||||
await setCommunityGoal(goalDraft);
|
await setCommunityGoal(goalDraft);
|
||||||
@@ -132,6 +154,16 @@ export default function AdminPanel() {
|
|||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</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="space-y-0.5">
|
||||||
<div className="flex items-center justify-between text-xs text-slate-400">
|
<div className="flex items-center justify-between text-xs text-slate-400">
|
||||||
<span>Community goal</span>
|
<span>Community goal</span>
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ const SessionContext = createContext({
|
|||||||
setCommunityGoal: async () => {},
|
setCommunityGoal: async () => {},
|
||||||
setAdminReason: async () => {},
|
setAdminReason: async () => {},
|
||||||
rebootRover: async () => {},
|
rebootRover: async () => {},
|
||||||
|
rebootServer: async () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
function useAckEmitter(socket) {
|
function useAckEmitter(socket) {
|
||||||
@@ -120,6 +121,7 @@ export function SessionProvider({ children }) {
|
|||||||
setAdminReason: (text) => emitWithAck('adminReason:set', { text }),
|
setAdminReason: (text) => emitWithAck('adminReason:set', { text }),
|
||||||
rebootRover: (roverId) =>
|
rebootRover: (roverId) =>
|
||||||
emitWithAck('command', { roverId, type: 'reboot', data: { reboot: {} } }),
|
emitWithAck('command', { roverId, type: 'reboot', data: { reboot: {} } }),
|
||||||
|
rebootServer: () => emitWithAck('server:reboot'),
|
||||||
pushAlert: (alert) =>
|
pushAlert: (alert) =>
|
||||||
setAlerts((prev) => [
|
setAlerts((prev) => [
|
||||||
...prev.slice(-49),
|
...prev.slice(-49),
|
||||||
|
|||||||
Reference in New Issue
Block a user