diff --git a/backend/main.py b/backend/main.py
index ccd7728..5032167 100644
--- a/backend/main.py
+++ b/backend/main.py
@@ -146,6 +146,19 @@ def _migrate_users():
conn.commit()
+def _migrate_device_os():
+ """Ajoute la colonne os sur devices si absente."""
+ with engine.connect() as conn:
+ if not conn.execute(text(
+ "SELECT name FROM sqlite_master WHERE type='table' AND name='devices'"
+ )).fetchone():
+ return
+ cols = [row[1] for row in conn.execute(text("PRAGMA table_info(devices)")).fetchall()]
+ if 'os' not in cols:
+ conn.execute(text("ALTER TABLE devices ADD COLUMN os VARCHAR"))
+ conn.commit()
+
+
def _migrate_drop_links_table():
"""Supprime la table links (fonctionnalité retirée en phase 3). Idempotent."""
with engine.connect() as conn:
@@ -162,6 +175,7 @@ def _migrate_drop_links_table():
_migrate_vlan_nullable()
_migrate_device_virt_type()
_migrate_device_url()
+_migrate_device_os()
_migrate_users_must_change_password()
_migrate_users_token_version()
_migrate_force_admin_password_change()
diff --git a/backend/models.py b/backend/models.py
index 264dd55..86aa1c7 100644
--- a/backend/models.py
+++ b/backend/models.py
@@ -36,6 +36,7 @@ class Device(Base):
is_livebox = Column(Boolean, default=False)
virt_type = Column(String, nullable=True)
url = Column(String, nullable=True)
+ os = Column(String, nullable=True)
interfaces = relationship(
"DeviceInterface", back_populates="device", cascade="all, delete-orphan"
diff --git a/backend/routers/devices.py b/backend/routers/devices.py
index 7673563..560808e 100644
--- a/backend/routers/devices.py
+++ b/backend/routers/devices.py
@@ -91,6 +91,8 @@ class DeviceCreate(BaseModel):
@field_validator("virt_type")
@classmethod
def _virt_type(cls, v: Optional[str]) -> Optional[str]:
+ if not v:
+ return None
if v not in _VALID_VIRT_TYPES:
raise ValueError(f"Invalid virt_type: {v!r}. Must be one of: baremetal, lxc, qemu")
return v
@@ -104,7 +106,6 @@ class DeviceCreate(BaseModel):
raise ValueError("url must be a valid http or https URL")
return v
-
class DeviceOut(BaseModel):
id: int
name: str
diff --git a/frontend/package.json b/frontend/package.json
index e832067..299d153 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,6 +1,6 @@
{
"name": "network-topology",
- "version": "1.0.1",
+ "version": "1.1.0",
"scripts": {
"dev": "vite",
"build": "vite build",
diff --git a/frontend/src/App.vue b/frontend/src/App.vue
index da49c76..3bf2fcc 100644
--- a/frontend/src/App.vue
+++ b/frontend/src/App.vue
@@ -110,6 +110,7 @@
/>
+
@@ -124,6 +125,7 @@ import { isAuthenticated, currentUsername, mustChangePassword, setAuth, clearAut
import TopologyGraph from './components/TopologyGraph.vue'
import VlanManager from './components/VlanManager.vue'
import DeviceManager from './components/DeviceManager.vue'
+import IpAddressing from './components/IpAddressing.vue'
import DiscoveryModal from './components/DiscoveryModal.vue'
import LoginPage from './components/LoginPage.vue'
import AccountModal from './components/AccountModal.vue'
@@ -139,9 +141,10 @@ const showAccount = ref(false)
const langs = ['fr', 'en', 'es']
const tabs = computed(() => [
- { id: 'topology', label: t('tabTopology'), icon: '■' },
- { id: 'vlans', label: t('tabNetworks'), icon: '◆' },
- { id: 'devices', label: t('tabDevices'), icon: '▣' },
+ { id: 'topology', label: t('tabTopology'), icon: '■' },
+ { id: 'vlans', label: t('tabNetworks'), icon: '◆' },
+ { id: 'devices', label: t('tabDevices'), icon: '▣' },
+ { id: 'addressing', label: t('tabAddressing'), icon: '⊞' },
])
function onLogin({ token, username, mustChangePassword: mcp }) {
diff --git a/frontend/src/brandIcons.js b/frontend/src/brandIcons.js
index bdabddc..2e58183 100644
--- a/frontend/src/brandIcons.js
+++ b/frontend/src/brandIcons.js
@@ -15,6 +15,15 @@ import {
siElastic, siKibana, siLogstash, siSplunk, siGraylog, siJaeger, siOpentelemetry,
siApple,
siDebian, siUbuntu, siFirefox,
+ siCentos, siRedhat, siFedora, siAlpinelinux, siArchlinux, siOpensuse, siFreebsd,
+ siAlmalinux, siRockylinux, siNixos, siGentoo, siVoidlinux, siSlackware,
+ siSuse, siManjaro, siLinuxmint, siZorin, siPopos, siDeepin, siElementary,
+ siMxlinux, siSolus, siEndeavouros, siArtixlinux, siAsahilinux,
+ siKubuntu, siLubuntu, siXubuntu, siUbuntumate,
+ siBsd, siOpenbsd, siNetbsd,
+ siKalilinux, siParrotsecurity, siTails, siQubesos, siReactos,
+ siMacos, siIos, siLinux,
+ siAndroid,
siAnsible,
siDell, siHp,
siRaspberrypi, siArduino,
@@ -22,7 +31,7 @@ import {
siWordpress, siGhost, siGrav, siJekyll, siHugo, siHexo, siDrupal, siJoomla, siTypo3, siOctobercms, siTextpattern,
siMatomo, siPlausibleanalytics,
siSamsung, siLg, siSony, siPanasonic, siSharp, siToshiba, siVestel,
- siChromecast, siAndroid, siAppletv, siAmazonfiretv, siRoku, siKodi,
+ siChromecast, siAppletv, siAmazonfiretv, siRoku, siKodi,
siJellyfin, siHomeassistant, siPhilipshue, siXiaomi,
siRadarr, siSonarr, siTransmission,
siExcalidraw,
@@ -295,6 +304,84 @@ const BRANDS = [
{ kw: ['xiaomi', 'mi home', 'yeelight'], icon: siXiaomi },
]
+export const OS_LIST = [
+ // Infrastructure
+ { value: 'proxmox', label: 'Proxmox VE', kw: ['proxmox', 'pve'], icon: siProxmox },
+ { value: 'truenas', label: 'TrueNAS', kw: ['truenas', 'freenas'], icon: siTruenas },
+ { value: 'synology', label: 'Synology DSM', kw: ['synology', 'dsm'], icon: siSynology },
+ // Réseau / pare-feu
+ { value: 'openwrt', label: 'OpenWrt', kw: ['openwrt'], icon: siOpenwrt },
+ { value: 'pfsense', label: 'pfSense', kw: ['pfsense'], icon: siPfsense },
+ { value: 'opnsense', label: 'OPNsense', kw: ['opnsense'], icon: siOpnsense },
+ // SBC
+ { value: 'raspbian', label: 'Raspberry Pi OS', kw: ['raspbian', 'raspberry pi os', 'pi os'], icon: siRaspberrypi },
+ // Debian / Ubuntu
+ { value: 'ubuntu', label: 'Ubuntu', kw: ['ubuntu'], icon: siUbuntu },
+ { value: 'kubuntu', label: 'Kubuntu', kw: ['kubuntu'], icon: siKubuntu },
+ { value: 'lubuntu', label: 'Lubuntu', kw: ['lubuntu'], icon: siLubuntu },
+ { value: 'xubuntu', label: 'Xubuntu', kw: ['xubuntu'], icon: siXubuntu },
+ { value: 'ubuntumate', label: 'Ubuntu MATE', kw: ['ubuntu mate', 'ubuntumate'], icon: siUbuntumate },
+ { value: 'debian', label: 'Debian', kw: ['debian'], icon: siDebian },
+ // Red Hat / CentOS
+ { value: 'redhat', label: 'Red Hat', kw: ['red hat', 'redhat', 'rhel'], icon: siRedhat },
+ { value: 'centos', label: 'CentOS', kw: ['centos'], icon: siCentos },
+ { value: 'almalinux', label: 'AlmaLinux', kw: ['almalinux', 'alma linux'], icon: { ...siAlmalinux, hex: '4d4d4d' } },
+ { value: 'rockylinux', label: 'Rocky Linux', kw: ['rockylinux', 'rocky linux', 'rocky'], icon: siRockylinux },
+ { value: 'fedora', label: 'Fedora', kw: ['fedora'], icon: siFedora },
+ // SUSE
+ { value: 'opensuse', label: 'openSUSE', kw: ['opensuse', 'open suse'], icon: siOpensuse },
+ { value: 'suse', label: 'SUSE Linux Enterprise', kw: ['sles', 'suse linux enterprise'], icon: { ...siSuse, hex: '73BA25' } },
+ // Arch
+ { value: 'arch', label: 'Arch Linux', kw: ['arch linux', 'archlinux'], icon: siArchlinux },
+ { value: 'manjaro', label: 'Manjaro', kw: ['manjaro'], icon: siManjaro },
+ { value: 'endeavouros', label: 'EndeavourOS', kw: ['endeavouros', 'endeavour os'], icon: siEndeavouros },
+ { value: 'artixlinux', label: 'Artix Linux', kw: ['artixlinux', 'artix linux', 'artix'], icon: siArtixlinux },
+ // Alpine
+ { value: 'alpine', label: 'Alpine Linux', kw: ['alpine linux', 'alpine'], icon: siAlpinelinux },
+ // Divers
+ { value: 'nixos', label: 'NixOS', kw: ['nixos', 'nix os'], icon: siNixos },
+ { value: 'gentoo', label: 'Gentoo', kw: ['gentoo'], icon: siGentoo },
+ { value: 'voidlinux', label: 'Void Linux', kw: ['voidlinux', 'void linux'], icon: siVoidlinux },
+ { value: 'slackware', label: 'Slackware', kw: ['slackware'], icon: { ...siSlackware, hex: '4d4d4d' } },
+ { value: 'asahilinux', label: 'Asahi Linux', kw: ['asahi linux', 'asahi'], icon: siAsahilinux },
+ // Bureau
+ { value: 'linuxmint', label: 'Linux Mint', kw: ['linux mint', 'linuxmint'], icon: siLinuxmint },
+ { value: 'popos', label: 'Pop!_OS', kw: ["pop!_os", 'popos', 'pop os'], icon: siPopos },
+ { value: 'zorin', label: 'Zorin OS', kw: ['zorin'], icon: siZorin },
+ { value: 'elementary', label: 'elementary OS', kw: ['elementary os', 'elementaryos'], icon: siElementary },
+ { value: 'deepin', label: 'deepin', kw: ['deepin'], icon: siDeepin },
+ { value: 'mxlinux', label: 'MX Linux', kw: ['mx linux', 'mxlinux'], icon: { ...siMxlinux, hex: '4d4d4d' } },
+ { value: 'solus', label: 'Solus', kw: ['solus'], icon: siSolus },
+ // BSD
+ { value: 'freebsd', label: 'FreeBSD', kw: ['freebsd', 'free bsd'], icon: siFreebsd },
+ { value: 'openbsd', label: 'OpenBSD', kw: ['openbsd'], icon: siOpenbsd },
+ { value: 'netbsd', label: 'NetBSD', kw: ['netbsd'], icon: siNetbsd },
+ // Sécurité
+ { value: 'kali', label: 'Kali Linux', kw: ['kali linux', 'kalilinux'], icon: siKalilinux },
+ { value: 'parrot', label: 'Parrot Security', kw: ['parrot security', 'parrotsec', 'parrot os'], icon: siParrotsecurity },
+ { value: 'tails', label: 'Tails', kw: ['tails os', 'tails linux'], icon: siTails },
+ { value: 'qubes', label: 'Qubes OS', kw: ['qubes os', 'qubesos'], icon: siQubesos },
+ // Windows / macOS / mobile
+ { value: 'windows', label: 'Windows', kw: ['windows', 'win10', 'win11', 'winserver', 'windows server'], icon: ICON_WINDOWS },
+ { value: 'macos', label: 'macOS', kw: ['macos', 'mac os'], icon: { ...siMacos, hex: '555555' } },
+ { value: 'ios', label: 'iOS / iPadOS', kw: ['ios', 'ipados'], icon: { ...siIos, hex: '555555' } },
+ { value: 'android', label: 'Android', kw: ['android'], icon: siAndroid },
+ // Autres
+ { value: 'reactos', label: 'ReactOS', kw: ['reactos'], icon: siReactos },
+ { value: 'linux', label: 'Linux (générique)', kw: ['linux'], icon: siLinux },
+ { value: 'bsd', label: 'BSD (générique)', kw: ['bsd'], icon: siBsd },
+]
+
+export function osIcon(value) {
+ return OS_LIST.find(o => o.value === value) ?? null
+}
+
+export function detectOs(name, description) {
+ const text = ((name || '') + ' ' + (description || '')).toLowerCase()
+ if (!text.trim()) return null
+ return OS_LIST.find(o => o.kw.some(kw => text.includes(kw))) ?? null
+}
+
export function detectBrands(name, description) {
const text = ((name || '') + ' ' + (description || '')).toLowerCase()
if (!text.trim()) return []
diff --git a/frontend/src/components/DeviceManager.vue b/frontend/src/components/DeviceManager.vue
index 573f595..989f38d 100644
--- a/frontend/src/components/DeviceManager.vue
+++ b/frontend/src/components/DeviceManager.vue
@@ -149,90 +149,21 @@
-
-
-
{{ editing ? t('editDevice') : t('newDevice') }}
-
-
-
+