Contrat API — Équipements du client, accès à distance
Pour l’équipe application mobile. Comment permettre à un client d’ajouter ses points d’accès, antennes, CPE, caméras et switches, puis d’ouvrir leur interface web depuis n’importe où — à travers le tunnel, sans ouvrir un seul port public.
Base : https://live.jmoai.net/api/v1/clients/topology
État de preuve : prouvé dans le code et par tests automatisés. Avant activation chez un client, le parcours doit être validé sur le pilote autorisé : routeur, tunnel, relais, AP réel, expiration du lien et suppression de l’accès. Une documentation ou un test unitaire ne prouve pas cet accès en production.
1. Ce qui existe déjà, et qu’il ne faut pas refaire
Tout le chemin réseau est construit, déployé et prouvé de bout en bout :
| brique | état |
|---|---|
| balayage du LAN, tri du bruit | livré |
| réservation d’un port privé par équipement | livré |
règle dst-nat posée sur le routeur |
livré (posée par le backend) |
| relais 443 authentifié, isolation par locataire | livré |
| vérification et plan de réparation | livré |
| épinglage du bail DHCP en statique | livré |
| sonde de surveillance posée sur le routeur | livré (posée par le backend) |
| notification push à la chute et au retour | livré |
Ce qui manquait n’était pas du réseau : c’était l’entrée. Au 23/08, un seul équipement était déclaré sur tout le parc en un mois. Depuis, le backend pose la règle lui-même — il ne reste plus qu’un écran à brancher.
2. Le problème que l’utilisateur doit résoudre — et l’outil qui le résout
Un routeur hotspot voit des centaines d’adresses. Sur un routeur réel du parc, le balayage brut remonte 493 adresses. Le client, lui, ne veut ajouter qu’un point d’accès.
Ne pas lui montrer la liste brute. Le tri est déjà fait côté backend :
GET /api/v1/clients/topology/scan?router_serial=HH40AF7YVNP
{
"discovered": [
{
"lan_ip": "10.100.4.1",
"interface": "ether1-TP-LinkGiga",
"mac": "3C:6A:D2:7C:11:18",
"hostname": "TendaAP",
"already_added": false
}
],
"hotspot_clients_hidden": 492
}
493 → 1. Sur ce routeur, l’équipement à ajouter est le seul de la liste.
Comment le tri fonctionne, pour que l’écran sache quoi dire
Le backend croise quatre lectures du routeur — table ARP, baux DHCP, adresses du routeur, serveurs hotspot — puis écarte :
- tout ce qui est branché sur une interface portant un serveur hotspot. C’est le critère
décisif : les clients d’un hotspot sont des passants, jamais des équipements. Ils sont
comptés dans
hotspot_clients_hidden, pas listés ; - les interfaces
container/docker; - les adresses du routeur lui-même ;
- tout ce qui n’est pas une adresse privée valide.
Ce qui reste est enrichi du nom d’hôte DHCP (hostname) et de la MAC. C’est ce nom
qui permet à l’utilisateur de reconnaître son matériel sans connaître les adresses IP —
affichez-le en premier, l’adresse en second.
La marque, l’application n’a rien à envoyer
Chaque ligne du balayage porte déjà :
{
"lan_ip": "10.100.4.1",
"mac": "3C:6A:D2:7C:11:18",
"vendor": "TP-Link",
"vendor_logo": "tp-link",
"vendor_reason": null
}
vendor est déduit de la MAC — ses trois premiers octets sont attribués au constructeur
par l’IEEE. Demander la marque à l’utilisateur reviendrait à lui demander de savoir : il se
tromperait, ou laisserait vide. vendor_logo est l’identifiant stable auquel accrocher le
SVG (tp-link, ubiquiti, cudy, mikrotik, hikvision…) — séparé du nom affiché, pour
que renommer l’un ne casse pas l’autre.
Le même champ est présent sur chaque équipement de la topologie (§5).
⚠️
vendor: nullarrive souvent, et ce n’est pas un défaut
vendor_reasonce que ça veut dire MAC_ALEATOIREMAC tirée au hasard par le téléphone — aucune marque n’existe CONSTRUCTEUR_INCONNUpréfixe hors de notre table MAC_ABSENTEpas de MAC relevée Les téléphones modernes changent de MAC par réseau pour ne pas être suivis. Sur le parc mesuré, plusieurs préfixes sont dans ce cas. Leur chercher une marque produirait une correspondance fausse, puisque le préfixe n’appartient à personne.
Afficher une icône générique. Jamais un logo approchant : un mauvais logo fait douter de tout le reste de l’écran.
already_added: true signale un équipement déjà enregistré : le griser plutôt que de laisser
l’utilisateur créer un doublon.
?all=true rend la liste brute. À réserver au diagnostic — ce n’est pas un écran client.
3. Ajouter un équipement
POST /api/v1/clients/topology/devices
Authorization: Bearer <jeton du client>
{
"router_serial": "HH40AF7YVNP",
"name": "AP Salle 2",
"device_type": "AP",
"lan_ip": "10.100.4.1",
"service_protocol": "http",
"service_port": 80,
"mac_address": "3C:6A:D2:7C:11:18"
}
router_serial suffit — l’application n’a pas besoin de connaître l’allocation_id. Le
backend résout le numéro de série vers son propriétaire actuel et refuse si le registre et
l’allocation divergent. Un numéro inconnu et le numéro d’un tiers rendent la même erreur
404 ROUTER_NOT_OWNED : la réponse ne dit jamais si le matériel existe ailleurs.
Envoyez la mac_address du balayage. Elle sert à retrouver l’équipement le jour où son
adresse change, et c’est elle qui rend la réparation automatique possible.
La réponse, dans le cas normal
{
"id": "b6347768-…",
"status": "ACTIVE",
"access_endpoint": {
"dstnat_port": 34001,
"nat_managed_by": "backend",
"proxy_path": "/api/v1/clients/topology/access/b6347768-…"
},
"nat_rule": {
"_": "rendu quand même : c'est le plan de secours si le routeur était injoignable",
"mikrotik_commands": [
"/ip dhcp-server lease make-static [find where address=\"10.100.4.1\"]",
"/ip firewall nat remove [find comment=TOPO_b6347768]",
"/ip firewall nat add chain=dstnat dst-address=10.255.0.15 protocol=tcp dst-port=34001 action=dst-nat to-addresses=10.100.4.1 to-ports=80 comment=TOPO_b6347768"
],
"remove_command": "/ip firewall nat remove [find comment=TOPO_b6347768]"
}
}
Depuis le 23/08, le backend met TOUT en service lui-même quand il joint le routeur :
- la règle d’accès à distance sur le routeur ;
- la sonde de surveillance sur le port de service de l’équipement ;
- un push au client —
✅ <nom> ajouté · Accès à distance et surveillance en service.
L’utilisateur nomme son équipement, et il apparaît. L’application n’a rien à exécuter, rien à enchaîner, et aucun appel supplémentaire pour activer la surveillance — c’était jusqu’ici un appel séparé que personne n’effectuait.
La réponse le dit sans ambiguïté :
status |
nat_managed_by |
ce qui s’est passé | ce que l’application fait |
|---|---|---|---|
ACTIVE |
backend |
la règle est sur le routeur | rien, afficher « Ouvrir » |
PENDING |
app |
routeur injoignable à cet instant | exécuter nat_rule.mikrotik_commands si elle est sur le LAN, sinon laisser la vérification reprendre plus tard |
Un routeur hors ligne ne fait pas échouer l’ajout : l’équipement est enregistré, la place est réservée, et la saisie de l’utilisateur n’est pas perdue pour une raison qui n’a rien à voir avec lui.
pushNat: false dans le corps force l’ancien comportement — au cas où l’application préfère
poser depuis le LAN. Ce n’est plus nécessaire pour que la fonctionnalité marche.
⚠️ La première commande n’est pas optionnelle
make-staticépingle le bail DHCP de l’équipement. Sans elle, un point d’accès en adresse dynamique change d’IP au premier redémarrage, la règledst-natpointe dans le vide, et l’accès casse sans aucun message. La commande est idempotente et sans effet sur un équipement déjà en adresse fixe.
4. Confirmer que l’accès existe vraiment
POST /api/v1/clients/topology/devices/:id/verify
À appeler seulement si l’application a posé la règle elle-même (status: "PENDING").
C’est ce qui fait alors passer l’équipement en ACTIVE. Utile aussi comme diagnostic à tout
moment : c’est le seul appel qui va vraiment LIRE le routeur.
{
"ok": true,
"reason": null,
"checks": {
"router_reachable": true,
"nat_rule_present": true,
"nat_rule_enabled": true,
"nat_rule_matches": true,
"lease_static": true,
"mac_matches_ip": true
},
"repair": { "repairable": false }
}
⚠️
ACTIVEveut dire « le routeur porte la règle », pas « c’est enregistré »Un équipement créé est
PENDING: la place est réservée, le routeur n’en sait rien encore. Seule la vérification, qui va lire la règle sur le routeur, promeut enACTIVE.Constaté le 23/08 avant correction : deux équipements
ACTIVEen base, zéro règle sur le routeur. L’écran aurait montré deux accès prêts qui échouaient au clic.N’affichez un bouton « Ouvrir » que sur un équipement
ACTIVE.
Réparer en un appel
POST /api/v1/clients/topology/devices/:deviceId/repair
Un seul appel : le backend diagnostique, pose ce qui manque sur le routeur, puis revérifie. « J’ai lancé la réparation » n’est pas « c’est réparé ».
{
"repaired": true,
"reason_before": "NAT_RULE_MISSING",
"reason_after": null,
"status": "ACTIVE",
"verify": { "ok": true, "…": "…" }
}
Trois réponses possibles, et l’écran doit les distinguer :
| réponse | sens | ce que l’écran affiche |
|---|---|---|
already_healthy: true |
rien à réparer | « tout va bien » — ne pas proposer le bouton |
repaired: true |
c’est réparé, et revérifié | « accès rétabli » |
needs_human: true |
la correction demande une décision | le champ note, tel quel |
⚠️
needs_humann’est pas un échecLe cas type est
DEVICE_IP_CHANGED: l’équipement ne vit plus à l’adresse enregistrée. Le backend pourrait poser une règle vers la nouvelle adresse — mais rien ne prouve que la machine trouvée là est celle que l’utilisateur avait choisie. Réparer, ici, ce serait ouvrir un accès vers un appareil que personne n’a désigné.La
noteest écrite pour être affichée sans reformulation. Exemple réel :L’équipement répond maintenant sur 10.100.90.125. Faites confirmer la nouvelle adresse à l’utilisateur, puis ré-enregistrez-le : rejouer les commandes d’origine recréerait la redirection vers l’ancienne adresse.
L’appel rend 200 dans les trois cas. Ce n’est pas une erreur d’appel, c’est un diagnostic — et l’écran a de quoi expliquer plutôt que d’afficher « échec ».
Les motifs, et ce que l’écran doit en faire
reason |
sens | action proposée |
|---|---|---|
NAT_RULE_MISSING |
la commande n’a pas été exécutée | appeler /repair |
NAT_RULE_DISABLED |
règle présente mais désactivée | appeler /repair |
NAT_RULE_MISMATCH |
la règle vise une autre adresse ou un autre port | appeler /repair |
DEVICE_IP_CHANGED |
l’équipement vit à une autre adresse | afficher note, puis ré-enregistrer |
DEVICE_NOT_SEEN |
plus vu dans la table ARP | équipement éteint ou débranché |
ROUTER_UNREACHABLE |
tunnel tombé | ne rien conclure sur l’équipement |
ROUTER_CHANGED_OWNER · ROUTER_REALLOCATED |
le routeur a changé de main | revalidation nécessaire |
repair.repairable: false n’est pas un échec : c’est le cas où la correction demande une
décision humaine. Un changement d’adresse en fait partie — le backend ne réécrit pas tout seul
une donnée que l’utilisateur a saisie.
⚠️ Un routeur injoignable ne rétrograde rien. Une coupure de trois minutes ferait sinon
basculer toute une installation en erreur. « Je n’ai pas pu regarder » n’est pas « c’est
cassé ». N’affichez pas d’alarme sur ROUTER_UNREACHABLE.
5. Où l’utilisateur les retrouve
Depuis le 23/08, les équipements apparaissent dans la topologie RADIUS, sous leur routeur.
Ils sont aussi fournis dans GET /api/v1/clients/dashboard, sous
radius.items[].devices, avec la même forme et le même rattachement. Le dashboard et la
topologie RADIUS ne sont pas mis en cache : l’application affiche les données reçues pour
le compte connecté.
Un seul appel suffit à peindre l’écran complet :
GET /api/v1/radius/topology
{
"primary": {
"nasname": "10.255.0.15",
"shortname": "J+Pro Ax3",
"model": "hAP ax^3",
"status": "ACTIVE",
"tunnel_status": "CONNECTED",
"web_access": {
"webfig": "http://vpn.mikhmoai.com:17012",
"winbox": "vpn.mikhmoai.com:19012",
"api": "vpn.mikhmoai.com:22012",
"ssh": "vpn.mikhmoai.com:23012",
"reachable": true
},
"devices": [
{
"id": "c58f77aa-…",
"name": "Ubiquiti Lite AP 120",
"type": "AP",
"lan_ip": "10.100.88.16",
"mac_address": "24:5A:4C:B2:AB:53",
"vendor": "Ubiquiti",
"vendor_logo": "ubiquiti",
"protocol": "https",
"port": 443,
"status": "ACTIVE",
"pending_reason": null,
"online": true,
"online_changed_at": "2026-08-23T21:38:42Z",
"monitored": true,
"access_path": "/api/v1/clients/topology/access/c58f77aa-…",
"access_session_path": "/api/v1/clients/topology/devices/c58f77aa-…/access-session"
}
]
},
"members": [],
"warnings": [],
"capabilities": {
"can_add_devices": true,
"reason": null,
"enforced": false,
"max_devices": 10,
"devices_used": 2,
"devices_remaining": 8
}
}
capabilities — l’accès aux équipements est un abonnement à part
L’accès et la surveillance des équipements du LAN forment un abonnement mensuel vendu
séparément (suivi_pro). Il ne vient ni avec la licence RADIUS, ni avec le VPN.
| champ | à quoi il sert |
|---|---|
can_add_devices |
le client a-t-il l’abonnement ? null = droits illisibles |
reason |
SUIVI_PRO_REQUIRED quand il ne l’a pas |
enforced |
la restriction est-elle RÉELLEMENT appliquée aujourd’hui ? |
max_devices |
plafond du palier souscrit |
devices_used |
équipements déjà déclarés |
devices_remaining |
ce qu’il reste avant le refus |
Lire les deux champs, jamais un seul. Tant que enforced vaut false, l’appel d’ajout
passe quand même — la restriction est en observation. Griser le bouton sur le seul
can_add_devices cacherait une fonction qui marche.
enforced: false → bouton actif
enforced: true + can_add_devices: false → bouton grisé + proposer l'abonnement
can_add_devices: null → bouton actif (on ne prive pas sur un doute)
Un refus au clic ne vend rien. Un bouton grisé qui explique pourquoi devient une proposition.
Les deux paliers
| palier | prix | équipements |
|---|---|---|
| Essentiel | 3 100 XOF / mois | 10 |
| Étendu | 10 000 XOF / mois | 30 |
Afficher le compteur en permanence, pas seulement le blocage. « 7 sur 10 » prévient ;
« limite atteinte » ne fait que constater. C’est devices_used / max_devices.
Au-delà du plafond, l’ajout rend :
402 Payment Required
{ "code": "DEVICE_QUOTA_REACHED",
"error": "Votre abonnement couvre 10 équipements et vous en avez 10. Passez au palier supérieur pour en ajouter davantage.",
"details": { "max_devices": 10, "devices_used": 10 } }
402 et non 403 : ce n’est pas un droit manquant, c’est un palier atteint — deux
situations qui n’appellent pas le même écran. Le message porte les deux nombres et la sortie ;
l’afficher tel quel suffit.
web_access concerne le routeur lui-même : ces quatre services existent nativement sur
tout MikroTik, les ports sont déjà alloués et servis. reachable suit l’état du tunnel —
grisez le bouton quand il est faux plutôt que d’ouvrir une page morte.
⚠️ Le schéma de l’URL WebFig n’est pas toujours
httpsLe port public est un flux TCP brut, sans terminaison TLS côté serveur. Le schéma est déjà calculé par le backend à partir du port réel du routeur (80 →
http, 8081 →https). Ouvrez l’URL telle qu’elle est renvoyée. La forcer enhttpsproduit une page noire — le bug a déjà été vécu côté Desktop.
devices[] concerne ce qu’il y a derrière le routeur.
Deux états à ne surtout pas confondre
| champ | ce qu’il dit |
|---|---|
status |
l’ACCÈS est-il en place ? ACTIVE = la règle est sur le routeur |
online |
l’ÉQUIPEMENT répond-il ? mesuré par la sonde, sur son port |
Un équipement peut être ACTIVE et online: false : l’accès est prêt, mais l’appareil est
éteint. C’est le cas le plus fréquent, et l’écran doit le dire ainsi — « accès configuré,
appareil hors ligne », jamais « erreur ».
⚠️
online: nullveut dire « pas surveillé », pas « hors ligne »Personne n’a encore regardé. À afficher en gris, jamais en rouge. Un point rouge sur un équipement qui fonctionne parfaitement fait perdre confiance dans tous les autres points rouges.
online_changed_at porte l’instant du dernier changement : c’est ce qui permet d’afficher
« hors ligne depuis 2 h 15 » sur la fiche, sans attendre une notification.
6. Ouvrir l’interface web d’un équipement
GET /api/v1/clients/topology/access/:deviceId
Authorization: Bearer <jeton du client>
C’est un relais, pas une redirection. La requête traverse : backend → tunnel du routeur →
règle dst-nat → équipement. Le locataire est revérifié à chaque appel.
Le bouton « Ouvrir » — deux appels, pas un
access_path exige l’en-tête d’authentification, qu’un navigateur ou une WebView ne portera
pas. Pour ouvrir l’équipement, demandez d’abord une URL signée :
POST /api/v1/clients/topology/devices/:deviceId/access-session
{
"access_url": "https://dev-<id>.topo.jmoai.net/?st=<signature>",
"access_path": "/api/v1/clients/topology/access/<id>?st=<signature>",
"ttl_seconds": 300
}
Ouvrez access_url : c’est le sous-domaine, qui sert l’interface complète de l’équipement
(les images, le style et les scripts se chargent, ce qui n’est pas le cas d’un accès par
chemin). La topologie donne ce chemin dans access_session_path — pas besoin de le composer.
Demandez le lien au CLIC, jamais à l’affichage de la liste. Il vaut 5 minutes. En émettre un par équipement à chaque lecture de la topologie sèmerait des URL signées dans les journaux et les caches pour un seul clic réel.
Pourquoi pas
vpn.mikhmoai.com:PORT, comme pour le routeur ?Les quatre services du routeur (WebFig, Winbox, API, SSH) passent par un flux TCP nginx sur un port public. C’est délibérément l’inverse pour les équipements du client, et pour trois raisons :
- un port public, c’est Internet entier. La caméra ou le point d’accès d’un client serait joignable par n’importe quel scanner, avec pour seule défense son mot de passe d’usine ;
- un flux TCP brut ne sait pas qui parle. Aucune vérification de locataire n’est possible dessus. Le relais 443, lui, revérifie le propriétaire à chaque requête ;
- ça ne tient pas à l’échelle. Quatre ports par routeur, c’est gérable. Autant de ports que d’équipements sur tout le parc ne l’est pas.
Le prix à payer est l’URL signée à durée de vie courte. C’est ce qui permet de garder zéro port public pour le matériel des clients.
N’exposez jamais tunnel_ip:dstnat_port dans l’application. C’est une adresse interne ;
la publier contournerait le contrôle d’accès, et quiconque l’aurait vue atteindrait
l’équipement.
Aucun port public n’est ouvert pour cette fonctionnalité. C’est le point de conception : un équipement domestique — un AP, une caméra — n’est jamais joignable depuis Internet.
7. Les notifications que l’application recevra
Le client n’a rien à configurer — ni Telegram, ni webhook, ni seuil. La surveillance part avec l’ajout, et les notifications arrivent en push et dans la liste in-app.
data.type |
quand | exemple |
|---|---|---|
EQUIPMENT_ADDED |
l’équipement vient d’être mis en service | ✅ Ubiquiti Lite AP 120 ajouté |
EQUIPMENT_MONITOR |
l’équipement tombe | 🔴 Ubiquiti Lite AP 120 hors ligne |
EQUIPMENT_MONITOR |
il est toujours tombé (15 min, 2 h) | 🔴 … toujours hors ligne · est OFF depuis 17 min |
EQUIPMENT_MONITOR |
il revient | ✅ … de nouveau en ligne après 12 min d'interruption |
Chaque message porte device_id : ouvrez la fiche de l’équipement au tap, pas la liste.
Ce que l’application ne doit PAS reconstruire
La durée. Elle est déjà dans le texte, et elle est mesurée, pas estimée. Au retour, le backend connaît l’instant exact de la chute. À la chute, il ne sait pas depuis combien de temps l’appareil est muet — la sonde vient de le constater — et il ne l’invente donc pas. Recalculer une durée côté application produirait un chiffre différent de celui du push, pour le même incident.
La relance. Deux paliers seulement, 15 minutes puis 2 heures, puis plus rien. Ce n’est pas un oubli : un rappel toutes les dix minutes sur un équipement débranché pour l’hiver ferait couper toutes les notifications par le client — y compris celles qui comptent. Ne pas y ajouter de rappel local.
Le filtre anti-flapping (micro-coupures). Les sondes routeur interrogent par cycle de 60s.
Une coupure transitoire ou un paquet perdu ne doit pas réveiller l’exploitant : la notification
push 🔴 hors ligne applique un délai de confirmation de 2 minutes.
- Si l’appareil revient en ligne avant 2 minutes, la micro-coupure est absorbée en silence : aucun push DOWN ni push UP n’est émis.
- Si la panne persiste au-delà de 2 minutes, l’alerte
🔴 hors ligneest envoyée. Le push de retour✅ de nouveau en lignesera alors émis au rétablissement avec la durée exacte. - L’état technique dans l’API (
GET /topology/devices) et sur la fiche reste mis à jour immédiatement en base dès la première détection.
La déduplication. Elle est faite côté serveur, par transition. Deux pushs identiques à une seconde d’intervalle ne remonteront pas.
8. Les autres appels
| appel | usage |
|---|---|
GET /topology/devices |
la liste du client, avec vpn_health et hardware{anchored, needs_revalidation, reason} |
POST /topology/sync |
déclarer plusieurs équipements en une fois, après un balayage |
DELETE /topology/devices/:id |
retire l’équipement et rend la remove_command à exécuter |
POST /topology/devices/:id/monitor |
surveillance netwatch sur le routeur |
hardware.needs_revalidation signale qu’un routeur a changé de propriétaire ou d’allocation
depuis l’enregistrement. Un routeur se vend ; les points d’accès, eux, restent sur le site.
Un équipement dans cet état ne doit plus offrir d’accès tant que l’utilisateur n’a pas
reconfirmé.
9. Préconditions d’exploitation
Le serveur refuse d’émettre ou de relayer un accès si l’une de ces configurations manque ou est invalide :
TOPOLOGY_ACCESS_SECRET: secret dédié aux liens courts, différent de la clé de chiffrement ;VPN_BRIDGE_URLetVPN_BRIDGE_TOKEN: relais privé vers l’overlay, jamais vers Internet ;TOPOLOGY_ACCESS_DOMAIN: domaine TLS qui reçoitdev-<deviceId>.…;TOPOLOGY_ACCESS_TTL_SEC: entre 30 et 900 secondes, 300 par défaut.
Le relais ne transmet jamais les cookies renvoyés par l’AP/CPE au domaine MikhmoAI. Un équipement est du contenu non fiable : il ne peut pas installer de session sur le domaine API.
Avant le pilote, exécuter le runner de migrations en lecture (npm run migrate -- --dry-run),
puis avec l’autorisation de déploiement. La migration versionnée
2026-08-03_topology_devices_bootstrap.sql crée le schéma nécessaire sur une installation
neuve ; elle est idempotente pour les installations existantes.
Après application, npm run topology:preflight est le contrôle bloquant, en lecture seule :
il vérifie les secrets/configurations sans les afficher, la présence des colonnes utilisées et
la migration enregistrée. Un échec interdit le test pilote ; ce script ne contacte aucun
routeur et ne crée aucune règle NAT.
10. Le parcours complet, dans l’ordre
GET /topology/scan— l’utilisateur voit une liste courte, pas 493 lignes.- Il choisit un équipement et le nomme (« Ubiquiti Lite AP 120 »).
POST /topology/devices— le backend réserve le port, pose la règle d’accès, pose la sonde, et envoie le push✅ … ajouté.- Il apparaît sous son routeur dans la topologie, avec
statusETonline. - Un bouton Ouvrir demande l’URL signée puis l’ouvre.
- À la moindre chute, le client reçoit un push. Sans avoir rien configuré.
Pour l’utilisateur, c’est un seul geste : il nomme son équipement, il apparaît.
Les deux étapes de secours, quand le routeur n’était pas joignable à cet instant
(status: "PENDING") : exécuter nat_rule.mikrotik_commands, puis appeler
POST /topology/devices/:id/verify.
Validation pilote obligatoire
balayage l'AP réel est vu et les clients hotspot sont exclus
ajout règle `TOPO_…` confirmée sur le routeur et `status=ACTIVE`
accès lien signé fonctionne, puis est refusé à expiration
isolation un autre client et une IP hors overlay sont refusés
retrait suppression de l'équipement retire NAT et surveillance, puis l'URL échoue
11. Les pièges déjà payés
N’affichez jamais la liste brute du balayage. 493 lignes dont 492 passants : l’utilisateur ne trouvera pas son point d’accès, et il conclura que la fonctionnalité ne marche pas.
N’affichez pas « Ouvrir » sur un équipement PENDING. Le clic échouera, et rien à
l’écran n’expliquera pourquoi. Affichez le motif et le bouton « Réparer ».
Ne peignez pas online: null en rouge. C’est « pas surveillé », pas « hors ligne ». Un
point rouge sur un équipement qui marche fait perdre confiance dans tous les autres.
Ne confondez pas status et online. Un équipement ACTIVE avec online: false n’est
pas en erreur : son accès est prêt et l’appareil est éteint. Le dire comme une erreur amène
l’utilisateur à « réparer » ce qui n’est pas cassé.
N’oubliez pas verify quand VOUS avez posé la règle. Dans le cas PENDING, sans cet
appel l’équipement reste en attente pour toujours, même quand l’accès fonctionne
parfaitement. Quand le backend a posé la règle (nat_managed_by: "backend"), l’équipement
est déjà ACTIVE — l’appel est inutile.