mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-15 17:12:59 -04:00
update all rovers buttone
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
File diff suppressed because one or more lines are too long
@@ -78,7 +78,7 @@
|
|||||||
<script defer src="https://analytics.otter.land/script.js" data-website-id="82dd56a5-db44-4279-bd1e-a4d9fee39af7" data-domains="rover.otter.land"></script>
|
<script defer src="https://analytics.otter.land/script.js" data-website-id="82dd56a5-db44-4279-bd1e-a4d9fee39af7" data-domains="rover.otter.land"></script>
|
||||||
<script defer src="https://analytics.otter.land/recorder.js" data-website-id="82dd56a5-db44-4279-bd1e-a4d9fee39af7" data-domains="rover.otter.land" data-sample-rate="0.15" data-mask-level="moderate" data-max-duration="300000"></script>
|
<script defer src="https://analytics.otter.land/recorder.js" data-website-id="82dd56a5-db44-4279-bd1e-a4d9fee39af7" data-domains="rover.otter.land" data-sample-rate="0.15" data-mask-level="moderate" data-max-duration="300000"></script>
|
||||||
<title>Roomba Rover</title>
|
<title>Roomba Rover</title>
|
||||||
<script type="module" crossorigin src="/assets/index-D5vPzVhj.js"></script>
|
<script type="module" crossorigin src="/assets/index-DrMv7S5j.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-BN3kEVFL.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-BN3kEVFL.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -106,6 +106,28 @@ function handleAck(msg) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function issueUpdateToAllRovers() {
|
||||||
|
const updated = [];
|
||||||
|
const failed = [];
|
||||||
|
|
||||||
|
roverManager.rovers.forEach((record) => {
|
||||||
|
if (!record?.ws) return;
|
||||||
|
|
||||||
|
const roverId = String(record.id);
|
||||||
|
try {
|
||||||
|
// Use the same narrow update payload as the per-rover admin action. The
|
||||||
|
// browser only asks for "update all"; the Pi still owns the privileged
|
||||||
|
// pull/install/reboot sequence through its fixed self-update helper.
|
||||||
|
issueCommand(roverId, { type: 'update', update: {} });
|
||||||
|
updated.push(roverId);
|
||||||
|
} catch (err) {
|
||||||
|
failed.push({ roverId, error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { updated, failed };
|
||||||
|
}
|
||||||
|
|
||||||
function getRecentDriveActivity(windowMs, options = {}) {
|
function getRecentDriveActivity(windowMs, options = {}) {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const results = [];
|
const results = [];
|
||||||
@@ -288,6 +310,26 @@ io.on('connection', (socket) => {
|
|||||||
|
|
||||||
socket.on('command', handleCommand);
|
socket.on('command', handleCommand);
|
||||||
socket.on('command:issue', handleCommand);
|
socket.on('command:issue', handleCommand);
|
||||||
|
|
||||||
|
socket.on('command:updateAllRovers', (_payload = {}, cb) => {
|
||||||
|
const reply = typeof cb === 'function' ? cb : () => {};
|
||||||
|
try {
|
||||||
|
if (!isAdmin(socket)) {
|
||||||
|
throw new Error('Not authorized');
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = issueUpdateToAllRovers();
|
||||||
|
logger.warn('Admin requested update for all online rovers', {
|
||||||
|
socketId: socket.id,
|
||||||
|
updated: result.updated,
|
||||||
|
failed: result.failed,
|
||||||
|
});
|
||||||
|
reply(result);
|
||||||
|
} catch (err) {
|
||||||
|
logger.warn('Update-all rovers rejected', socket.id, err.message);
|
||||||
|
reply({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
homeAssistantService.homeAssistantEvents.on('update', () => {
|
homeAssistantService.homeAssistantEvents.on('update', () => {
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ export default function AdminPanelContent() {
|
|||||||
setAdminReason,
|
setAdminReason,
|
||||||
rebootRover,
|
rebootRover,
|
||||||
updateRover,
|
updateRover,
|
||||||
|
updateAllRovers,
|
||||||
rebootServer,
|
rebootServer,
|
||||||
setAudioLevels,
|
setAudioLevels,
|
||||||
setPrivateSafety,
|
setPrivateSafety,
|
||||||
@@ -181,6 +182,35 @@ export default function AdminPanelContent() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleUpdateAll = async () => {
|
||||||
|
const roverCount = roster.filter((rover) => rover?.id).length;
|
||||||
|
if (roverCount === 0) return;
|
||||||
|
|
||||||
|
const ok = window.confirm(
|
||||||
|
`Update all ${roverCount} rover${roverCount === 1 ? '' : 's'} now? Each rover will run its self-update and reboot if the update starts successfully.`,
|
||||||
|
);
|
||||||
|
if (!ok) return;
|
||||||
|
|
||||||
|
trackAnalyticsEvent('rover_update_all_click', { roverCount });
|
||||||
|
try {
|
||||||
|
// The server owns the fan-out because it has the authoritative online
|
||||||
|
// rover map and can enforce admin privileges once before issuing the
|
||||||
|
// existing per-rover update command to every connected rover.
|
||||||
|
const result = await updateAllRovers();
|
||||||
|
trackAnalyticsEvent('rover_update_all_result', {
|
||||||
|
status: 'accepted',
|
||||||
|
updated: result?.updated?.length || 0,
|
||||||
|
failed: result?.failed?.length || 0,
|
||||||
|
});
|
||||||
|
if (result?.failed?.length) {
|
||||||
|
alert(`Update requested for ${result.updated?.length || 0} rover(s). ${result.failed.length} rover(s) failed to queue.`);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
trackAnalyticsEvent('rover_update_all_result', { status: 'failed', reason: err?.message || 'unknown' });
|
||||||
|
alert(err.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleServerReboot = async () => {
|
const handleServerReboot = async () => {
|
||||||
const ok = window.confirm('Reboot the server host now? This will disconnect all users.');
|
const ok = window.confirm('Reboot the server host now? This will disconnect all users.');
|
||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
@@ -487,6 +517,17 @@ export default function AdminPanelContent() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between gap-0.5 text-xs">
|
||||||
|
<span className="text-slate-400">Rovers</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleUpdateAll}
|
||||||
|
disabled={roster.length === 0}
|
||||||
|
className="button-danger disabled:cursor-not-allowed disabled:opacity-60"
|
||||||
|
>
|
||||||
|
Update All
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<RoverRoster
|
<RoverRoster
|
||||||
roster={roster}
|
roster={roster}
|
||||||
renderActions={(rover) => (
|
renderActions={(rover) => (
|
||||||
|
|||||||
@@ -410,6 +410,10 @@ export function SessionProvider({ children }) {
|
|||||||
// maintenance without becoming a remote shell.
|
// maintenance without becoming a remote shell.
|
||||||
updateRover: (roverId) =>
|
updateRover: (roverId) =>
|
||||||
emitWithAck('command', { roverId, type: 'update', data: { update: {} } }),
|
emitWithAck('command', { roverId, type: 'update', data: { update: {} } }),
|
||||||
|
// Bulk rover updates stay server-owned so the admin browser sends one
|
||||||
|
// intent, then the server checks privileges and fans out the same fixed
|
||||||
|
// update command to every currently connected rover.
|
||||||
|
updateAllRovers: () => emitWithAck('command:updateAllRovers'),
|
||||||
rebootServer: () => emitWithAck('server:reboot'),
|
rebootServer: () => emitWithAck('server:reboot'),
|
||||||
playUploadedAudio: ({ roverId, name, mime, dataBase64 }) =>
|
playUploadedAudio: ({ roverId, name, mime, dataBase64 }) =>
|
||||||
emitWithAck('audio:uploadPlay', { roverId, name, mime, dataBase64 }),
|
emitWithAck('audio:uploadPlay', { roverId, name, mime, dataBase64 }),
|
||||||
|
|||||||
Reference in New Issue
Block a user