# 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 `