Files
olivier 88cf6458d0 Initial commit — Stupid Simple Network Inventory
Application web d'inventaire réseau manuel avec FastAPI, Vue 3 et Docker.
Inclut l'authentification JWT, la découverte ICMP, et la topologie en cards CSS.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 09:19:19 +02:00

186 lines
5.4 KiB
Markdown

# 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
<button :class="{ active: locale === 'de' }" @click="setLocale('de')">de</button>
```
---
## 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 `<script setup>` syntax.
2. Use CSS custom properties for all colors (see `docs/frontend.md` for the variable list).
3. Use `t('key')` for all visible strings — never hardcode text.
4. For dark-mode scoped overrides, put the full selector inside `:global()`:
```css
:global(html.dark .my-component) { background: #1E293B; }
```
5. Import and use in `App.vue` or the relevant parent component.
---
## Change the Default Admin Password
The admin account is seeded by `_migrate_users()` only when the `users` table is empty. To reset credentials on a running instance, use the account settings modal in the UI (sidebar footer → account icon).
To reset programmatically (e.g., locked out):
```bash
docker compose exec backend python3 -c "
from database import engine
from sqlalchemy import text
from passlib.context import CryptContext
pwd = CryptContext(schemes=['bcrypt'], deprecated='auto')
with engine.connect() as conn:
conn.execute(text(\"UPDATE users SET hashed_password=:h WHERE username='admin'\"), {'h': pwd.hash('newpassword')})
conn.commit()
"
```
---
## Upgrade Dependencies
### Backend Python packages
Check for passlib/bcrypt compatibility before upgrading:
- `passlib 1.7.4` requires `bcrypt < 4.0` — see `requirements.txt` for the pin.
- If upgrading passlib, verify the new version supports the installed bcrypt before removing the pin.
### Frontend npm packages
`simple-icons` has breaking changes between major versions (icon names and SVG paths change). Pin the major version in `package.json` and test brand detection after upgrading.
`lucide-vue-next` is generally backwards-compatible but icons are occasionally renamed or removed.