The Card carried Mantine's `w="100%"` prop, whose style props land in the
style attribute after anything passed via `style`, so it overrode the pixel
width dnd measures at lift time. On a `position: fixed` element `100%`
resolves against the viewport rather than the list, so grabbing a rule
snapped the card from 734px to 798px and pushed it past the right edge.
Moving the width into `style`, ahead of the dnd spread, leaves normal flow
untouched and lets the measured width win while dragging.
Three separate defects made the rule list impossible to reorder:
- The Card's own `style` was placed after `{...provided.draggableProps}`,
clobbering the `position`/`transform` dnd sets while dragging. The drag
did start, but nothing moved on screen. Styles are now merged with dnd's
winning, and the dragged card is highlighted.
- `Draggable index` used the position in the full `state` array and the
rules of the other table were filtered out afterwards, leaving gaps.
dnd requires contiguous indexes from 0 inside a Droppable, so a single
rule in the other table broke the visible tab entirely. Visible rules
are now re-indexed and mapped back to their `state` position on drop.
- `destination?.index || 0` reordered to the top instead of doing nothing
when a rule was dropped outside the list.
The real frontend, built against an in-memory backend, so the UI can be tried
without a Linux host, NET_ADMIN or a running firewall. A visitor can create,
edit, start, stop and delete services across all five modules and watch the UI
react as it would against a real instance; a reload restores the seed.
- frontend/src/js/demo.ts holds the mock. genericapi() and the socket.io client
are the frontend's only two ways out to the network, so routing both through
it was enough - no component knows the difference. It also emits the same
"update" events the backend does, and ticks the packet counters of running
services so the page looks alive.
- The demo lives behind IS_DEMO, which vite inlines as a literal, so a normal
`bun run build` tree-shakes the module and its seed data away. Verified: no
demo string survives in the production bundle.
- The workflow rebuilds on every push touching frontend/ or docs/ and deploys
to demo.firegex.pwnzer0tt1.it, copying index.html to 404.html so deep links
resolve on Pages.
Rename the flag to --unsafe-disable-auth so the risk is visible at the call
site, and close the ways the mode could outlive itself:
- Keep an explicit -P/--startup-psw instead of dropping it. Without a password
in the database, --no-unsafe-disable-auth brought firegex up in its
initial-setup state, where anyone able to reach it could choose the password.
- Refuse /api/login, /api/set-password and /api/change-password while
authentication is disabled: an anonymous caller could otherwise plant a
credential that kept working once authentication was turned back on.
run.py config --password still works, writing to the database from the host.
- Print a banner on every start/restart with authentication disabled. The flag
is persisted in the config file, so a later bare `run.py start` inherits it.
- Report the change in `run.py config --unsafe-disable-auth`, which claimed
"No configuration changes specified" while persisting it, and stop that
command from nulling the stored port and host on the way.
- Expose the state as auth_disabled in /api/status and hide the logout button
and the change-password entry, which were silent no-ops in this mode.
Tests cover both directions of the toggle, the socket.io handler, the password
endpoints and the -P handling.