A user change can sever the manager's own path to a device with no warning and no
way back. Most recently, deleting an *unused* bond on a switch took the whole
switch offline. This adds a safety net, and fixes the bug that caused that
specific incident.
Root cause of the bond incident (DeviceCollector.deleteBond):
- Slaves were re-added to the bridge ONLY when the bond itself was a bridge
member. For an unbridged bond the restore loop never ran, so the member ports
were released from the bond and left in no bridge at all — the ports stopped
forwarding and the switch dropped off the network.
- The restore in both deleteBond and createBond was wrapped in `.catch(() => {})`,
so a failed restore was swallowed and the route still returned 200 OK.
- Restores also dropped PVID, silently returning tagged ports to VLAN 1.
Now: port settings (PVID, frame-types, ingress-filtering, edge, horizon) are
captured and carried over, and restore failures are reported instead of hidden.
Change Guard (services/changeGuard/ChangeGuard.ts):
Before a risky change the device saves a restore point and arms a scheduler that
restores it after N seconds. The change is applied, then reachability is proven
on a NEW connection; success disarms the scheduler, failure leaves the device to
restore itself and come back. Because the restore point is created before the
scheduler, a binary restore rolls back to a config containing neither the
scheduler nor the change — self-cleaning, with no revert loop.
- Works entirely over the RouterOS API. This matters: every recovery artifact
today requires SSH, so devices without SSH credentials had no rollback target
at all (cf. #86).
- Modes: `binary` (exact restore incl. deleted interfaces, reboots) or `script`
(/import, no reboot but additive). Configurable via change_guard_* settings.
- Per-device Redis lock, `device_change_guards` audit table, and startup
reconciliation for guards left pending by a manager restart.
- Degrades gracefully: if protection can't be armed the change still runs and the
response says it was unprotected.
Wired into bond create/delete, bridge vlan-filtering, and /ip/service. Disabling
the service the manager is connected through is now refused outright (409) — the
mgmt-service signal already existed but was confined to the read-only posture
endpoint.
Also: replaces the bare confirm() on "Destroy Bond" with a ChangeGuardDialog that
explains the risk and the protection, and reports the outcome (including "the
device is restoring itself"). Fixes getAppSettings omitting
config_snapshot_enabled/interval, which made the v0.17.4 snapshot toggle a no-op.
Verified on real hardware (hAP-class switch): capability probe confirmed
/system/backup/save and scheduler add/remove work over the API; a guarded change
armed, applied, verified and committed leaving zero schedulers and zero files on
the device; disabling the api service was refused with 409. 133 backend tests
pass, including 7 guard state-machine tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
50 lines
1.3 KiB
JSON
50 lines
1.3 KiB
JSON
{
|
|
"name": "mikrotik-manager-frontend",
|
|
"version": "0.18.0-beta",
|
|
"private": true,
|
|
"type": "module",
|
|
"scripts": {
|
|
"dev": "vite",
|
|
"build": "tsc && vite build",
|
|
"preview": "vite preview",
|
|
"lint": "eslint src",
|
|
"test": "vitest run"
|
|
},
|
|
"dependencies": {
|
|
"@tanstack/react-query": "^5.101.0",
|
|
"@types/leaflet": "^1.9.21",
|
|
"@xterm/addon-fit": "^0.11.0",
|
|
"@xterm/xterm": "^6.0.0",
|
|
"@xyflow/react": "^12.11.1",
|
|
"axios": "^1.18.1",
|
|
"clsx": "^2.1.0",
|
|
"date-fns": "^3.2.0",
|
|
"leaflet": "^1.9.4",
|
|
"lucide-react": "^1.23.0",
|
|
"react": "^19.2.7",
|
|
"react-dom": "^19.2.7",
|
|
"react-router-dom": "^7.18.1",
|
|
"recharts": "^2.10.3",
|
|
"socket.io-client": "^4.7.3",
|
|
"tailwind-merge": "^3.5.0",
|
|
"zustand": "^4.4.7"
|
|
},
|
|
"devDependencies": {
|
|
"@eslint/js": "^9.39.4",
|
|
"@types/react": "^19.2.14",
|
|
"@types/react-dom": "^19.2.3",
|
|
"@vitejs/plugin-react": "^6.0.3",
|
|
"autoprefixer": "^10.4.17",
|
|
"eslint": "^9.39.4",
|
|
"eslint-plugin-react": "^7.37.5",
|
|
"eslint-plugin-react-hooks": "^7.0.1",
|
|
"eslint-plugin-security": "^4.0.0",
|
|
"postcss": "^8.5.10",
|
|
"tailwindcss": "^3.4.1",
|
|
"typescript": "^5.9.3",
|
|
"typescript-eslint": "^8.58.2",
|
|
"vite": "^8.0.16",
|
|
"vitest": "^4.1.8"
|
|
}
|
|
}
|