# Extending the Application ## Add a Field to `Device` Every new Device field requires changes in four places. Missing any one of them will cause data loss or silent failures. ### 1. `backend/models.py` Add the column to the `Device` class: ```python my_field = Column(String, nullable=True) ``` ### 2. `backend/main.py` Add an idempotent migration function and call it before `Base.metadata.create_all`: ```python def _migrate_device_my_field(): 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 'my_field' not in cols: conn.execute(text("ALTER TABLE devices ADD COLUMN my_field VARCHAR")) conn.commit() # Add the call at the bottom of the startup sequence: _migrate_device_my_field() ``` ### 3. `backend/routers/devices.py` Add to both Pydantic models: ```python class DeviceCreate(BaseModel): my_field: Optional[str] = None class DeviceOut(BaseModel): my_field: Optional[str] = None ``` Then assign explicitly in both `create_device` and `update_device`: ```python db_device = models.Device( ... my_field=device.my_field, ) ``` Do **not** use `**device.model_dump()` to populate the ORM object — it silently ignores any field not present in the constructor keyword args. ### 4. Frontend Add the field to the device form in `DeviceManager.vue` and display it where needed (chip in `TopologyGraph.vue`, card in `DeviceManager.vue`). Add any new i18n keys to all three locales in `i18n.js`. --- ## Add a New API Route Group 1. Create `backend/routers/myrouter.py` following the pattern of `vlans.py` or `devices.py`. 2. Import and register in `backend/main.py`: ```python from routers import myrouter app.include_router(myrouter.router, prefix="/api/myroutes", tags=["myroutes"], dependencies=[Depends(get_current_user)]) ``` 3. Add the corresponding API object in `frontend/src/api.js`: ```javascript export const myApi = { list: () => http.get('/myroutes/'), create: (data) => http.post('/myroutes/', data), remove: (id) => http.delete(`/myroutes/${id}`), } ``` --- ## Add a New i18n Key 1. Open `frontend/src/i18n.js`. 2. Add the key to all three locale objects (`fr`, `en`, `es`). 3. Use `t('myKey')` in the template. For interpolation: ```javascript // i18n.js fr: { myMsg: "Il y a {0} équipements dans {1}" } // template {{ tFmt('myMsg', devices.length, vlan.name) }} ``` --- ## Add a Language 1. In `frontend/src/i18n.js`, add a new locale object with all existing keys translated. 2. Add the language code to the valid values type/comment. 3. In `App.vue`, add a pill button to the language switcher: ```html ``` --- ## Add a Brand Icon Supported brands are detected by keyword-matching device name and description. 1. Find the Simple Icons export name: ```bash cd frontend node -e "const si = require('./node_modules/simple-icons'); console.log(Object.keys(si).filter(k => k.toLowerCase().includes('yourterm')))" ``` 2. In `frontend/src/brandIcons.js`, import the icon: ```javascript import { siYourbrand } from 'simple-icons' ``` 3. Add an entry to the `BRANDS` array: ```javascript { id: 'yourbrand', name: 'YourBrand', keywords: ['yourbrand', 'alternate-name'], hex: siYourbrand.hex, path: siYourbrand.path, } ``` `keywords` are matched case-insensitively against the device `name` and `description` fields. --- ## Add a Vue Component 1. Create `frontend/src/components/MyComponent.vue` using `