From 14de657deb48250edb91212d48041a531d7e6299 Mon Sep 17 00:00:00 2001 From: Olivier Date: Mon, 18 May 2026 10:48:36 +0200 Subject: [PATCH] feat: cascade-delete hosts when removing a network When a VLAN/LAN is deleted, all non-gateway, non-livebox devices with an interface in that network are deleted automatically. Gateway and livebox devices are preserved; their interface is unlinked (vlan_id set to NULL). The confirmation dialog now shows the exact count of devices that will be deleted (all three locales: fr/en/es). Co-Authored-By: Claude Sonnet 4.6 --- backend/routers/vlans.py | 15 +++++++++++++++ frontend/src/App.vue | 2 +- frontend/src/components/VlanManager.vue | 13 +++++++++++-- frontend/src/i18n.js | 3 +++ 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/backend/routers/vlans.py b/backend/routers/vlans.py index db343ef..6edf041 100644 --- a/backend/routers/vlans.py +++ b/backend/routers/vlans.py @@ -97,6 +97,21 @@ def delete_vlan(vlan_pk: int, db: Session = Depends(get_db)): db_vlan = db.query(models.Vlan).filter(models.Vlan.id == vlan_pk).first() if not db_vlan: raise HTTPException(status_code=404, detail="VLAN introuvable") + + # Collect devices with an interface in this VLAN + ifaces = ( + db.query(models.DeviceInterface) + .filter(models.DeviceInterface.vlan_id == vlan_pk) + .all() + ) + device_ids = {i.device_id for i in ifaces} + + for device_id in device_ids: + device = db.query(models.Device).filter(models.Device.id == device_id).first() + if device and not device.is_gateway and not device.is_livebox: + db.delete(device) # cascade deletes all its interfaces + + # Gateway/livebox interfaces in this VLAN will be SET NULL by SQLAlchemy db.delete(db_vlan) db.commit() return {"ok": True} diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 7120695..beb5c48 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -107,7 +107,7 @@ :devices="devices" :vlans="vlans" /> - + diff --git a/frontend/src/components/VlanManager.vue b/frontend/src/components/VlanManager.vue index 240e19a..b366f27 100644 --- a/frontend/src/components/VlanManager.vue +++ b/frontend/src/components/VlanManager.vue @@ -90,7 +90,7 @@ import { ref, reactive } from 'vue' import { vlansApi } from '../api.js' import { t, tFmt } from '../i18n.js' -const props = defineProps({ vlans: Array }) +const props = defineProps({ vlans: Array, devices: { type: Array, default: () => [] } }) const emit = defineEmits(['refresh']) const showForm = ref(false) @@ -145,9 +145,18 @@ async function save() { } } +function _affectedCount(vlan) { + return props.devices.filter( + d => !d.is_gateway && !d.is_livebox && d.interfaces.some(i => i.vlan_id === vlan.id) + ).length +} + async function remove(vlan) { const label = vlan.vlan_id != null ? `VLAN ${vlan.vlan_id} — ${vlan.name}` : `LAN ${vlan.name}` - if (!confirm(`Supprimer ${tFmt('confirmDeleteNetwork', label)}`)) return + const count = _affectedCount(vlan) + let msg = `Supprimer ${tFmt('confirmDeleteNetwork', label)}` + if (count > 0) msg += '\n' + tFmt('confirmDeleteNetworkHosts', count) + if (!confirm(msg)) return try { await vlansApi.remove(vlan.id) emit('refresh') diff --git a/frontend/src/i18n.js b/frontend/src/i18n.js index b4ca8a1..4988b55 100644 --- a/frontend/src/i18n.js +++ b/frontend/src/i18n.js @@ -70,6 +70,7 @@ const LANGS = { badgeLivebox: 'Livebox', confirmDeleteDevice: '{0} et tous ses liens ?', confirmDeleteNetwork: '{0} ?', + confirmDeleteNetworkHosts: '{0} équipement(s) hors passerelle seront également supprimés.', saveError: 'Erreur lors de la sauvegarde', deleteError: 'Erreur lors de la suppression', descPlaceholder: 'Rôle, OS, notes…', @@ -229,6 +230,7 @@ const LANGS = { badgeLivebox: 'ISP Box', confirmDeleteDevice: '{0} and all its links?', confirmDeleteNetwork: '{0}?', + confirmDeleteNetworkHosts: '{0} non-gateway device(s) will also be deleted.', saveError: 'Error while saving', deleteError: 'Error while deleting', descPlaceholder: 'Role, OS, notes…', @@ -384,6 +386,7 @@ const LANGS = { badgeLivebox: 'Router ISP', confirmDeleteDevice: '{0} y todos sus enlaces?', confirmDeleteNetwork: '{0}?', + confirmDeleteNetworkHosts: '{0} equipo(s) no gateway también serán eliminados.', saveError: 'Error al guardar', deleteError: 'Error al eliminar', descPlaceholder: 'Rol, SO, notas…',