- Per-key IP/CIDR allowlist (
allowed_ips, default0.0.0.0/0) enforced on everyX-API-Keyrequest - Edit support for API keys (name, role, allowed IPs) — first PATCH endpoint/UI for API keys, previously create/list/revoke only
- Client IP resolution now honors
X-Forwarded-Forbehind Traefik, so restrictive allowlists work correctly in production
create_switch/update_switchtools: addedportparameter for non-default SSH portscreate_interface/update_interfacetools: addedallowed_vlan_addparameter for trunk VLAN config; clarified thatallowed_vlanis read-only device state, not an input- Fixed
.mcp.jsonto use environment variable interpolation for the API URL and key instead of a hardcoded value
- README rewrite for clearer presentation and visual showcase
- Windows environment variable syntax guides added to READMEs
- Postgres 17 → 18
- Traefik v2.11 → v3.7 (rule syntax updated:
Host()/PathPrefix()multi-arg lists →||-chained, removed in v3) - Adminer 4.8.1 → 5.4.2
- nginx 1.30.1 → 1.30.3 (frontend image)
- Backend: Python 3.12 → 3.13, Poetry 1.8.5 → 2.4.1 (
poetry lock --check→poetry check --lock) - Configure Netmiko
global_delay_factor/fast_clifor Arista cEOS to avoid prompt detection timeout
- Remove Delete Account (Danger zone) feature
- Fix intermittent
KeyErroron switch sync: nornir's inventory dir is gitignored and not a Docker volume, so a fresh/recreated backend container had nogroups.yamluntil someone happened to touch the Groups UI. Now regenerated from the DB at startup. - Fix 500 error on partial interface updates (e.g. updating just the description via the API/MCP): the update path was dumping every field including unset ones, clobbering
portwithNULLand tripping its NOT NULL constraint.
- API Keys management UI (sidebar) — mint read-write/read-only keys, auto-provisioned hidden service-account user, one-time key display
- New
mcp_server/— exposes REST API as MCP tools (Claude Desktop, Claude Code, Gemini CLI, etc.) for querying/operating switches, interfaces, MAC/ARP, credentials, groups - Project-level
.mcp.jsoncommitted for Claude Code
- Detect and surface wrong credentials/auth errors from switches instead of a generic failure
- App version now shown on login page
- Fixed Netmiko prompt-detection timeout on cEOS — tuned
global_delay_factorandfast_cli
- Upgraded backend to Python 3.12; base image
tiangolo→python:3.12-slim - Poetry install/build fixes (pin then upgrade to
>=2.0.0, pip-based install to dodge apt-get network failures, auto-regenerate stale lockfile) prestart.shnow runs automatically on container start (auto-migrate)- Vite 6 upgrade, TanStack route tree regenerated
- Dependency bumps: cryptography, pydantic-settings, starlette, pyjwt, python-multipart, joserfc, js-yaml, tar, form-data, esbuild
- Vietnamese README (
README.vi.md) + language switcher - New lab guide (
lab-guide.md,lab-guide-vi.md) for Multipass/Docker deployment, updated for Ubuntu 26.04
- mypy/ruff lint fixes, test mock corrections,
timezone.utc→datetime.UTCrefactor
- Interface mode parsing — mode was always stored as
accessduring sync; now correctly derived from device output (trunk,routed,access) - Trunk VLAN sync —
allowed_vlanin DB now updates after configuring trunk port (no longer requires a full metadata sync to see changes) - Edit Interface loading state — Save button now shows spinner for the full duration of the request
- Audit log ordering — log entry now written after device operation succeeds (not before)
- Interface update — fixed
__dict__→model_dump()causing stale field values on update
- Redesigned Interfaces, ARP, MAC Addresses, IP Interfaces, Group Config pages
- Redesigned Edit Interface modal — Current State card, mode-aware VLAN/Allowed column
- Switches page: card/list view toggle with localStorage persistence
- Dashboard stat cards smaller; timestamp format consistent (
MM/DD/YYYY, HH:MM:SS) across all pages
- EOS platform and device_type options added to Add/Edit Switch dropdowns
- README updated with EOS switch config requirements
- TCP connect health check (UP/DOWN) per switch — no credentials needed
- Batch Check Health button checks all switches in parallel (~5s)
- Periodic background health check every 5 min (configurable via
HEALTH_CHECK_INTERVAL_MINUTES) - Status displayed as colored badge (green UP / red DOWN / gray Unknown)
- Card view: shows vendor, model, serial number, OS version, description, groups as tags, last sync time
- List view: dense table with all key fields visible
- Toggle between views with grid/list icon buttons — preference saved to localStorage
- Per-switch Sync button replaces the old wide "Update Metadata" button
- Fixed stale records persisting after interfaces are removed from device
- Fixed duplicate records created when IP address changes on same interface
- Sync now matches by interface name, deletes removed interfaces, deduplicates existing records
- Frontend minor dependency updates (TanStack Router, Emotion, TypeScript 5.9)
- Alembic migration:
health_statuscolumn onswitchtable
- Upgraded 91 Python packages (fastapi 0.109→0.136, sqlmodel 0.0.16→0.0.38, alembic 1.13→1.18, sentry-sdk 1.41→2.60, cryptography 42→48, ruff 0.2→0.15, pytest 7→8, mypy 1.9→1.20, and more)
- Upgraded 15 npm packages (TanStack Query/Router, react-hook-form, react-icons, axios, @simplewebauthn/browser)
- Docker: node 22→24, nginx 1.28→1.30.1-alpine
- Pre-commit: ruff v0.15.13, pre-commit-hooks v5.0.0
- Fixed critical CVE in
form-data(4.0.0→4.0.5, CVE-2025-7312)
- New
TIMEZONEenv var (IANA format, e.g.Asia/Ho_Chi_Minh) - Server time displayed in Admin page
- Audit log timestamps converted to configured timezone
- Fixed duplicate route handler
update_switch_metadata(F811) - Fixed
crud/__init__.pymissing re-exports breaking test helpers - Fixed test teardown FK violation on
webauthncredentialtable - Fixed mypy strict mode compliance with updated type checker
- Fixed CI
.envgeneration with all required variables - Added
gibberish-detectorto security scan dependencies - Added
CLAUDE.mdwith project architecture documentation
- DB-backed audit log table (replaces fragile file-based parsing)
- All significant write operations now logged: login, OAuth, passkey, user CRUD, switch/interface/credential/group changes
- Logs page: action column, severity badges (INFO/WARNING/ERROR), search by username/message, severity filter, pagination
- Social login: Google OAuth2, Microsoft Azure AD, Keycloak OIDC (Authorization Code + PKCE)
- Passkey / WebAuthn registration and authentication
- Passkey management in user settings (add, delete credentials)
- Disable password login per user (
password_login_enabledflag) - User table shows authentication methods (password, OAuth providers, passkey) as badges
- New migration:
oauthaccountandwebauthncredentialtables - New migration:
password_login_enabledcolumn onusertable
- Credential encryption — device passwords encrypted at rest using Fernet (AES-128-CBC). Never stored or returned in plaintext.
- Command injection fix — all user input (port name, description, VLAN) validated before building device CLI commands
- Group config restricted to superusers — only admin accounts can push bulk config to devices
- JWT token lifetime reduced from 8 days to 1 day
.envremoved from git tracking — use.env.exampleas template
- Interface delete endpoint now actually deletes (was returning
True) - Fixed
switch.modelnull guard on Juniper platform detection - Removed dead code
switches2.py(was never imported)
- Added
detect-secretsandbanditpre-commit hooks - Added
securityjob to GitHub Actions (bandit SAST + secrets scan) - Added dependency review on PRs
- Added
mypytype-check step to test job - Added PR template with security checklist
- PostgreSQL 12 → 17
- Traefik v2.3 → v2.11
- Backend base image Python 3.10 → 3.11
- Node.js 20 → 22 LTS, nginx 1 → 1.28-alpine
adminerpinned to 4.8.1- Added
docker compose watchsupport for live development
- App version
v0.2.0displayed in sidebar docker compose watch— backend syncs instantly, frontend rebuilds on change- Updated README with full local and production deployment guide
Breaking:
CREDENTIAL_ENCRYPTION_KEYis now required in.env. Generate with:python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
Breaking: PostgreSQL 12 → 17 requires dropping the existing data volume:
docker compose down -v docker compose up -d