Files
harmony/fleet/harmony-fleet-operator/vendor/app.js
Jean-Gabriel Gill-Couture 6893813f48 feat(fleet): device exec + container log tail over NATS
Operator→agent request/reply gains an `exec` verb (the protocol's
second, after `ping`). The dashboard's "Run command" tab and the
device log panels now reach a real device instead of stubs.

- contracts: `Verb::Exec`, `CommandRequest::Exec`, `ExecReply`
  (capped output + truncated flag). Reuses the existing
  `device-commands.<id>.>` subject — no callout permission change.
- agent: command server runs `sh -c` with a 25s deadline
  (kill_on_drop), captures stdout/stderr capped at 256 KiB.
- operator: `FleetCommandsClient::exec` (30s timeout); thread the
  authenticated NATS client into `RealFleetService` via
  `spawn_dashboard`. `run_command` and a new `device_logs`
  (`podman logs --tail 200` of the device's deployment container)
  compose exec — no separate logs verb at n=1.
- frontend: replace the fake SSE log stream with a one-shot
  `/devices/{id}/logs/tail` fetch (load + Refresh), CSP-safe;
  auto-scroll to the latest line.

serve-web (no NATS) returns a clear "not configured" error for these
two actions; the deployed operator wires them.
2026-06-02 06:38:54 -04:00

29 lines
1.0 KiB
JavaScript

document.body.addEventListener('htmx:configRequest', (event) => {
event.detail.headers['x-csrf-token'] = '1';
});
// Open a modal dialog swapped into #modal-root. Lives here (not inline)
// because the production CSP forbids inline scripts/handlers.
document.body.addEventListener('htmx:afterSwap', (event) => {
if (!event.target || event.target.id !== 'modal-root') return;
const dialog = event.target.querySelector('dialog');
if (!dialog || typeof dialog.showModal !== 'function') return;
dialog.showModal();
// Backdrop click closes; closing clears the root so it can re-open.
dialog.addEventListener('click', (e) => {
if (e.target === dialog) dialog.close();
});
dialog.addEventListener('close', () => {
event.target.innerHTML = '';
});
});
// After a log panel loads its tail, scroll to the latest line.
document.body.addEventListener('htmx:afterSwap', (event) => {
const el = event.target;
if (el && el.classList && el.classList.contains('logfeed')) {
el.scrollTop = el.scrollHeight;
}
});