88cf6458d0
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>
186 lines
5.4 KiB
Markdown
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.
|