The fleet has ~700 devices and no way to see them: the device console manages one machine from its own keyboard, and the question "which devices are installed, and which wave is each in" had no surface at all. This adds it as the first screen. Python standard library only -- http.server, html.escape, string.Template. No framework and no third-party import, because the fleet installs offline from a committed package pool that contains no web framework, so fastapi/uvicorn/flask would have made the panel uninstallable on the devices it manages. That was decided with the user over the FastAPI they first picked, and a test asserts it: the module imports only stdlib plus its own siblings. What it shows: the dark header with the target and the privilege level; a summary of counts by state with one loud figure; the device list with a state badge per row, monospace ids and hostnames so columns line up, non-ready devices sorted first, and a filter bar; the selected device's detail with its four-channel verification, since READY is refused until every channel is verified (docs/27). Nothing is colour-only: every badge carries its Turkish state word. Data has no live source yet, so a missing inventory file falls back to synthetic records that the page labels as demo in two places -- the panel never presents invented devices as real ones. Two defects found by reviewing a real render, both fixed here with a test that fails when the bug returns (proven by mutation: each assertion goes red against the reintroduced bug, green after): - --state-provisioned resolved to var(--info), and --info is --accent, so a KURULDU badge and the selected row were the same blue. The state now resolves to its own colour, and the test resolves var() aliases before comparing -- comparing the raw declarations does not detect this, which is how the first version of the test passed against the bug. - the wave cell printed the bare code and then a label that already contains it, so one device read "P2 P2 — PILOT-2". Each wave is named once, now. Also fixes two entries in docs/27 that had become untrue: it claimed a clone carries the packages and the installer downloads nothing, which stopped being the case when the pool moved to Git LFS and bf-bootstrap began cloning without smudge. The real flow -- light clone, payload from a local mirror or a targeted `git lfs pull` -- is documented instead. Tests: tests/test_web_static.py, 112 tests, collected by tests/run-all.sh (12/12 PASS overall).
107 lines
4.5 KiB
Markdown
107 lines
4.5 KiB
Markdown
# Blueforce Field OS — Filo Paneli (merkez web paneli)
|
||
|
||
Operatörün ofis tarayıcısından filoyu gördüğü yüzey: **hangi cihaz kuruldu,
|
||
hangi yayılım dalgasında ve sıradaki tek iş ne**. Cihaz başına operatör arayüzü
|
||
`sudo bf` (tkinter konsolu, `bfos/`) ile aynı filoyu iki uçtan anlatır; iki yüzey
|
||
tek durum sözlüğünü paylaşır (`docs/DESIGN.md`).
|
||
|
||
## Neden yalnız Python standart kütüphanesi
|
||
|
||
Filo çevrimdışı kurulur ve paketler commit'lenmiş havuzdan gelir; `fastapi`,
|
||
`uvicorn`, `flask`, `jinja2` bu havuzda **yok**. Panel bunları import etse
|
||
yönettiği cihazlara kurulamaz (bkz. `PRODUCT.md`, "Stack"; karar kullanıcıyla
|
||
birlikte alındı). Bu yüzden: `http.server` + `string.Template`, harici CDN veya
|
||
webfont yok, tüm varlıklar yerel.
|
||
|
||
## Çalıştırma
|
||
|
||
```bash
|
||
# 1) Hiçbir şey vermeden: 127.0.0.1:8787, etiketli DEMO VERİ, salt-okunur
|
||
python3 -m web.server
|
||
# (python3 web/server.py de aynı şekilde çalışır)
|
||
|
||
# 2) Gerçek envanter dosyasıyla
|
||
python3 -m web.server --data /var/lib/blueforce/inventory/fleet.json
|
||
|
||
# 3) Sunucuyu başlatmadan veri kaynağını denetle (CI/operatör kontrolü)
|
||
python3 -m web.server --check --data ./web/data/fleet.json
|
||
|
||
# 4) Tam yetki (bu sürümde yazma uç noktası yoktur; yalnız arayüz farkı)
|
||
python3 -m web.server --privilege full
|
||
```
|
||
|
||
Seçenekler: `--host` (varsayılan `127.0.0.1`), `--port` (varsayılan `8787`),
|
||
`--data`, `--privilege {read-only,full}`, `--check`, `--version`.
|
||
Panel varsayılan olarak **yalnız yerel arayüze** bağlanır.
|
||
|
||
## Uç noktalar (hepsi okuma)
|
||
|
||
| Yol | Ne döner |
|
||
|---|---|
|
||
| `/` (veya `/filo`) | İlk ekran: başlık bandı, durum özeti, cihaz listesi, seçili cihaz ayrıntısı |
|
||
| `/?q=&durum=&dalga=&hazir_degil=1&cihaz=` | Aynı ekran, sunucu tarafında filtrelenmiş (JavaScript yok) |
|
||
| `/hakkinda` | Panelin sürümü, veri kaynağı, durum sözlüğü, dalgalar, kanallar ve panel kuralları |
|
||
| `/static/tokens.css`, `/static/app.css` | Yerel stil dosyaları (allow-list; listeye eklenmeyen dosya 404) |
|
||
|
||
`GET` ve `HEAD` dışındaki yöntemler **405** ile ve Türkçe gerekçeyle reddedilir.
|
||
Hiçbir uç nokta komut çalıştırmaz, dosya yazmaz veya cihazda değişiklik yapmaz.
|
||
|
||
## Veri sözleşmesi (`schema_version: 1`)
|
||
|
||
```json
|
||
{
|
||
"schema_version": 1,
|
||
"generated_at": "2026-09-16T09:30:00+03:00",
|
||
"devices": [
|
||
{
|
||
"dealer_id": "12010193",
|
||
"wave": "P1",
|
||
"state": "READY",
|
||
"last_seen": "2026-09-16T09:05:00+03:00",
|
||
"channels": {
|
||
"ssh_over_wg": {"status": "verified", "checked_at": "2026-09-14T10:00:00+03:00"},
|
||
"xrdp": "pending",
|
||
"rustdesk": "verified",
|
||
"meshcentral": "verified"
|
||
}
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
- `dealer_id`: 8 hane (`^[0-9]{8}$`). Cihaz kimliği ve hostname **türetilir**:
|
||
`BF-<no>` / `bf-<no>` (`docs/02`). Dosyada `device_id`/`hostname` de varsa
|
||
yalnız kurala uyduğunda kabul edilir.
|
||
- `wave`: `LAB`, `P1`, `P2`, `W1`, `W2`, `PROD` (Ansible grup adları da kabul:
|
||
`lab`, `pilot_1`, `wave_1`, `production`).
|
||
- `state`: `PROVISIONED_OFFLINE`, `ENROLLED`, `READY`, `FAILED`, `ABSENT`
|
||
(Türkçe kelimeler de kabul: `KURULDU`, `KAYITLI`, `HAZIR`, `HATA`, `YOK`).
|
||
- `channels`: dört kanal (`ssh_over_wg`, `xrdp`, `rustdesk`, `meshcentral`),
|
||
durum `verified` / `pending` / `failed` / `unknown` / `missing`.
|
||
- Örnek dosya: `web/data/fleet.example.json`.
|
||
|
||
## Panelin uyguladığı kurallar (fail-closed)
|
||
|
||
1. **HAZIR kapısı:** `READY` iddiası dört kanal da `verified` olmadan reddedilir;
|
||
cihaz `HATA` olarak ve gerekçesiyle gösterilir (`docs/27`). Panel bir iddiayı
|
||
uyarıya yumuşatmaz.
|
||
2. **Başarısız kanal:** denetimi başarısız (`failed`) bir kanal varsa cihaz
|
||
`HATA` sayılır — cihaz bir insan bekliyor demektir.
|
||
3. **Yükseltme yok:** panel hiçbir cihazın durumunu ilerletmez; `KAYITLI`yı
|
||
kendiliğinden `HAZIR` yapmaz.
|
||
4. **Geçersiz kayıt görünür:** kimliği kurulamayan kayıt (hatalı bayi no,
|
||
yinelenen bayi no, bilinmeyen dalga) sessizce düşürülmez; sayfada
|
||
"Geçersiz kayıtlar" altında nedeniyle listelenir.
|
||
5. **Bozuk veri demo veriye dönüşmez:** `--data`/`BF_FLEET_DATA` ile verilen
|
||
dosya yoksa, JSON değilse veya şema sürümü desteklenmiyorsa panel `VERİ YOK`
|
||
der; uydurmaz.
|
||
|
||
## Test
|
||
|
||
```bash
|
||
python3 tests/test_web_static.py -v # statik + sözleşme testleri (port açmaz)
|
||
bash tests/run-all.sh # depo genelindeki tüm testler
|
||
```
|
||
|
||
Testler HTTP yüzeyini `web/panel.py:route()` (saf fonksiyon) üzerinden kapsar;
|
||
bu yüzden hiçbir test gerçek port bağlamaz, tarayıcı açmaz.
|