19 KiB
Dossier technique sur Glance et l’ajout d’un onglet IP réseau
Périmètre de l’analyse
Le dépôt que tu as donné est bien glanceapp/glance, c’est-à-dire Glance, le dashboard self-hosted de l’organisation glanceapp. Ce n’est pas Glances de nicolargo, qui est un outil de monitoring système différent. La nuance compte beaucoup pour ton projet, parce que Glance est d’abord un framework de tableau de bord configurable en YAML, pensé pour agréger et afficher des données, pas un moteur natif de supervision réseau temps réel. citeturn2view0turn26search2turn19view0
La conséquence la plus importante pour ton besoin est celle-ci : Glance doit être traité comme la couche d’interface, tandis que le scan de ton réseau 10.0.0.0/22 et l’inventaire détaillé des IP doivent vivre dans un service séparé. Le README officiel précise en effet qu’une actualisation de page est nécessaire pour mettre à jour les informations et qu’aucune requête périodique n’est effectuée en arrière-plan par les widgets ; les données sont chargées au rendu puis mises en cache suivant la durée définie pour chaque widget. citeturn19view0
Autre point utile : le widget natif monitor existe déjà, mais il est conçu pour une liste de sites/URLs avec title, url, éventuel check-url, icon, timeouts et variantes d’affichage. Il ne correspond pas à un inventaire CIDR, à un découpage par sous-réseaux, ni à un volet détaillé par IP. Ton besoin dépasse donc nettement le périmètre des widgets intégrés actuels. citeturn12view2
Langages et technologies employés
D’après la répartition des langages affichée sur le dépôt GitHub, Glance est majoritairement écrit en Go, avec une part significative de HTML, JavaScript et CSS, puis un peu de Dockerfile. La page du dépôt affiche précisément Go 55,5 %, HTML 16,2 %, JavaScript 15,0 %, CSS 13,2 % et Dockerfile 0,1 %. Cette répartition correspond bien à ce qu’on voit dans l’arborescence : un backend Go, des templates HTML embarqués, des assets CSS/JS locaux et un packaging Docker très léger. citeturn19view0
Le cœur applicatif est un module Go nommé github.com/glanceapp/glance, et le go.mod déclare aujourd’hui Go 1.24.3. Les dépendances directes sont révélatrices : fsnotify pour la surveillance et le rechargement de config, gofeed pour les flux, gopsutil/v4 pour les métriques système, gjson pour le parsing JSON dans les widgets custom, x/crypto pour l’authentification et yaml.v3 pour toute la configuration. À noter qu’il y a un léger écart documentaire : le README parle encore d’un build avec Go >= 1.23, mais le go.mod du dépôt main pointe déjà sur 1.24.3 ; si tu recompiles, c’est le go.mod qu’il faut considérer comme source de vérité. citeturn4view0turn19view0
Le point d’entrée est volontairement minuscule : main.go ne fait qu’appeler glance.Main() depuis internal/glance. Le Dockerfile confirme le modèle de distribution : build en Go sur Alpine, avec CGO_ENABLED=0, puis copie du binaire dans une image Alpine finale. En pratique, Glance est donc pensé comme un binaire Go autonome, sans runtime Node, sans bundle frontend externe et avec une surface de build très simple. citeturn4view2turn4view1
Le frontend n’emploie pas de framework type React/Vue/Svelte. Le README insiste sur du “minimal vanilla JS”, et les guidelines de contribution ajoutent même explicitement “No package.json” et la consigne d’éviter les nouvelles dépendances. Pour un développeur, c’est un signal très clair : Glance privilégie du server-side rendering avec templates Go, des assets statiques maison, et un JavaScript utilitaire limité. citeturn19view0
Techniquement, les assets sont embarqués dans le binaire via //go:embed pour static et templates. Le code montre aussi que le CSS est bundlé au runtime à partir de internal/glance/static/css, tandis que internal/glance/static/js contient des scripts comme animations.js, calendar.js, login.js, masonry.js, page.js, popover.js, templating.js, todo.js et utils.js. Côté templates, on trouve les vues page/document/footer ainsi qu’un template HTML dédié par widget ou style de widget. citeturn9view4turn13view0turn13view1turn14view0
Structure interne de l’application
L’arborescence de haut niveau est sobre et lisible : à la racine, on a surtout docs/, internal/glance/, pkg/sysinfo/, main.go, go.mod, Dockerfile et la config de release. Pour une lecture développeur, tout le produit est concentré dans internal/glance, tandis que docs/ sert de documentation fonctionnelle et pkg/sysinfo/ isole une partie des métriques système. citeturn2view0
La structure de configuration est également très claire dans config.go. On y voit les blocs top-level server, auth, document, theme, branding et surtout pages. Chaque page possède un name, un slug, une largeur (width), des head-widgets et des columns; chaque colonne porte une size et une liste de widgets. La documentation confirme que la première page devient la page d’accueil, que les pages apparaissent automatiquement dans la navigation dans l’ordre de déclaration, et qu’une page peut avoir jusqu’à 3 colonnes, avec 1 ou 2 colonnes full au minimum. citeturn16view0turn11view0
Autrement dit, dans le vocabulaire de Glance, ton futur “tab” n’est pas une entité spéciale : c’est simplement une nouvelle page Glance. C’est une bonne nouvelle, parce que ton onglet réseau peut donc être ajouté proprement comme page dédiée, avec une largeur wide et un ou plusieurs widgets de présentation. La doc donne d’ailleurs des largeurs default, slim et wide, et associe wide à 1920 px max, ce qui est pertinent pour ton souhait d’afficher un très grand nombre de petites cases IP. citeturn11view0
Le système de widgets natifs est, lui aussi, très explicite. Dans internal/glance, on voit une série de fichiers widget-*.go et, côté templates, des fichiers *.html correspondants. La fonction newWidget dans widget.go enregistre les types connus comme calendar, clock, weather, monitor, extension, custom-api, iframe, html, server-stats, etc. Le même fichier définit une interface widget avec notamment Render(), initialize(), requiresUpdate(), update(), handleRequest() et les méthodes d’identification/configuration. En pratique, cela veut dire qu’un widget natif Glance est un type Go compilé, pas un plugin chargé dynamiquement. citeturn3view0turn24view0
Le couplage avec le rendu HTML est fort, mais propre : les widgets vivent en Go, le rendu passe par des templates de html/template, et les assets/template sont embarqués dans le binaire. J’en déduis qu’ajouter un widget natif implique toujours des modifications du code source Go, un template HTML et souvent une recompilation complète de l’application. Ce n’est pas une architecture à plugins dynamiques. citeturn24view0turn9view4turn4view1
Les options réalistes pour ajouter ton module
Officiellement, Glance documente quatre façons de créer des widgets personnalisés : iframe, html, extension et custom-api. Pour ton cas, les deux plus sérieuses sont custom-api, extension et, très franchement, iframe si tu veux une vraie mini-application interactive. Le mode html reste trop statique pour un module IPAM réseau un peu avancé. citeturn19view0turn12view0turn12view1turn12view4
Le widget custom-api est la voie la plus “Glance-compatible” quand on dispose déjà d’une API JSON. La documentation officielle dit explicitement que sa configuration demande des notions de programmation, HTML, CSS, langage de templates Go et concepts propres à Glance. Elle précise aussi que le rendu repose sur html/template et gjson. C’est très puissant si ton backend de scan publie un JSON structuré et si tu acceptes un modèle d’affichage plutôt déclaratif. Le dépôt glanceapp/community-widgets est d’ailleurs un excellent indicateur de la stratégie réelle du projet : il précise que la plupart des widgets communautaires sont faits avec custom-api, et que cette approche est beaucoup plus simple à déployer qu’une extension. citeturn12view0turn9view1turn18search2turn20search0
Le widget extension est plus souple côté langage, parce qu’il ne demande qu’un serveur HTTP renvoyant du contenu et quelques en-têtes spéciaux comme Widget-Title, Widget-Title-URL, Widget-Content-Type et Widget-Content-Frameless. Mais la doc officielle prévient de deux choses importantes : l’API d’extension est WIP et seul le type de contenu html est officiellement supporté pour l’instant. Le dépôt communautaire confirme aussi que les extensions sont plus impliquées, car elles demandent un serveur ou conteneur séparé. Autrement dit : oui, c’est faisable et élégant pour du Rust, mais c’est moins stable et plus coûteux à maintenir qu’un custom-api. citeturn9view0turn12view1turn18search2
Le widget iframe est paradoxalement très intéressant pour toi. La doc officielle le décrit simplement comme un widget capable d’embarquer une source externe avec une hauteur configurable. Ça paraît basique, mais pour un plan IP cliquable, avec volet de détails, rafraîchissement indépendant et éventuellement un peu de JS applicatif, c’est en pratique l’intégration la plus simple et la plus robuste : tu gardes Glance comme shell de navigation et tu exécutes ton mini-dashboard réseau comme une petite app web autonome. citeturn12view4turn19view0
Enfin, si tu veux modifier le cœur de Glance pour ajouter un widget “officiel” à ton fork, les technologies à employer sont sans ambiguïté : Go pour la logique, templates HTML Go pour le rendu, éventuellement un peu de JS/CSS statiques dans internal/glance/static, et la documentation YAML dans docs/configuration.md. C’est la voie la plus intégrée, mais aussi celle qui te lie le plus au codebase et aux conventions du projet. citeturn3view0turn13view1turn14view0turn24view0
Glance fournit néanmoins quelques points d’extension utiles pour “hybrider” une approche custom : le serveur peut exposer un répertoire /assets/, le document global peut recevoir du HTML custom dans <head>, et le thème supporte un fichier CSS custom. En plus, chaque widget peut recevoir un css-class, et la doc explique que Glance utilise beaucoup de classes utilitaires, avec une classe widget-type-{name} pour cibler précisément un widget. En clair, tu peux très bien injecter ton propre CSS et même ton propre JS global si tu veux enrichir un custom-api avec un comportement plus dynamique. citeturn23view0turn23view2
Architecture recommandée pour ton onglet IP
Pour ton besoin précis, je recommande une page Glance dédiée nommée par exemple “Réseau”, en width: wide, avec une seule colonne full. La raison est simple : une page Glance ne peut avoir que trois colonnes maximum, alors que ton découpage logique contient déjà quatre sous-réseaux (10.0.0.x, 10.0.1.x, 10.0.2.x, 10.0.3.x), sans compter le volet de détail. En plus, un bloc /22 représente 1024 adresses IPv4 ; à cette échelle, il vaut mieux afficher un seul grand module full-width avec quatre sections internes, plutôt que disperser l’UI dans les colonnes natives de Glance. citeturn11view0turn25calculator0
Je déconseille fortement d’embarquer le moteur de ping directement dans Glance. Le produit n’est pas conçu pour exécuter des sondes réseau en tâche de fond : il rafraîchit ses données au chargement de page, puis s’appuie sur le cache de widget. Ton service de scan doit donc être un daemon séparé qui fait le travail lourd à la fréquence de ton choix et qui publie un état matérialisé que Glance ne fait qu’afficher. C’est le découpage le plus propre techniquement, mais aussi celui qui colle le mieux à l’architecture officielle du projet. citeturn19view0
Je te conseille aussi de ne pas assimiler “IP libre” à “ne répond pas au ping”. La documentation officielle de Nmap rappelle qu’un hôte actif peut être manqué si un firewall filtre les probes ou leurs réponses, et qu’un échec de découverte ICMP ne permet pas de conclure qu’une machine est réellement absente. Pour ton dashboard, il vaut mieux distinguer au moins alive, seen/used, reserved et free, où free ne devient vrai qu’en absence de réponse et en absence d’occupation connue dans ton inventaire/DHCP/ARP. citeturn29search0turn29search1
Si tu veux le faire en Rust, le stack le plus cohérent pour le service externe est un backend de type Axum + Tokio, avec SQLite via SQLx si tu veux persister tes métadonnées riches (hardware, icon, services, link, location, parent, notes, dernières vues, etc.). Axum se présente comme une bibliothèque de routage HTTP et de handling ergonomique, Tokio comme le runtime asynchrone de référence pour I/O, timers et scheduling, et SQLx comme un toolkit SQL async “pure Rust” compatible SQLite. Pour ton besoin, c’est un trio très logique. citeturn27search0turn27search5turn27search2
L’API de ce service peut rester simple. Je te recommande un endpoint de synthèse pour le rendu global, et éventuellement un endpoint fin pour le détail d’une IP. Par exemple :
{
"last_scan_at": "2026-05-19T12:34:56Z",
"sections": [
{
"name": "VM",
"cidr": "10.0.0.0/24",
"items": [
{
"ip": "10.0.0.12",
"state": "alive",
"hostname": "vm-proxy-01",
"icon": "mdi:server",
"link": "https://vm-proxy-01.local",
"services": ["ssh", "https"],
"location": "rack-a",
"parent": "pve01"
}
]
}
]
}
Ce schéma n’est pas imposé par Glance ; c’est le bon niveau d’abstraction pour séparer proprement scan, inventaire et présentation.
Pour l’intégration visuelle dans Glance, tu as à mon avis deux architectures vraiment pertinentes. La première, que je considère comme la meilleure pour ton cas, est une page Glance + un widget iframe plein écran pointant vers une petite app web Rust. Tu gagnes tout de suite la liberté de faire une vraie grille, un volet latéral au clic, un polling indépendant, du tri, du filtrage, voire de la navigation clavier, sans dépendre du cycle de rendu de Glance. C’est la voie la plus simple si tu veux une UX riche. citeturn12view4turn19view0
La seconde est une approche plus native visuellement : un widget custom-api qui tire un JSON de ton service Rust, rend les cases via template HTML, puis s’appuie sur css-class, custom-css-file, /assets/ et éventuellement un script injecté dans document.head pour animer un panneau de détails ou appliquer un peu de comportement côté client. Cette voie demande plus de bricolage frontend, mais elle garde un rendu plus “Glance”. Elle a aussi des précédents crédibles : le dépôt communautaire propose déjà des widgets Tailscale devices et NetAlertX Device Status qui affichent justement des équipements, leur statut et leurs IP à partir d’une API externe. citeturn12view0turn23view0turn23view2turn21search0turn22search0turn22search2
Comme ton futur widget risque vite d’être long, ne mets pas tout en vrac dans glance.yml. La documentation Glance supporte les includes YAML avec $include, et le dépôt communautaire recommande justement de sortir les widgets complexes dans des fichiers dédiés. Pour ton cas, un fichier ipam.yml séparé est la bonne pratique. citeturn11view0turn20search0
Voici le squelette Glance que je recommanderais pour la solution la plus robuste, c’est-à-dire une page dédiée + iframe :
pages:
- name: Réseau
width: wide
columns:
- size: full
widgets:
- type: iframe
title: Plan IP
source: http://ipam-ui:8088/
height: 1200
Et si tu veux tenter le mode “plus natif Glance”, voici le principe hybride :
server:
assets-path: /app/assets
document:
head: |
<script defer src="/assets/ipam.js"></script>
pages:
- name: Réseau
width: wide
columns:
- size: full
widgets:
- type: custom-api
title: Plan IP
cache: 15s
css-class: ipam-grid
url: http://ipam-api:8088/api/network/summary
template: |
<!-- grille + sous-sections + drawer -->
Le second modèle demande plus de finesse, mais il reste compatible avec les mécanismes officiellement documentés par Glance. citeturn12view0turn23view0turn23view2
Peut-on ajouter des widgets en Rust
Oui, mais pas de la même manière selon ce que tu appelles “ajouter un widget”. Si tu entends par là fournir les données et/ou le rendu depuis un service externe, alors oui, Rust convient très bien. C’est même une très bonne option, parce que Glance supporte officiellement un widget custom-api qui consomme du JSON, un widget extension basé sur un serveur HTTP externe, et un widget iframe qui peut embarquer n’importe quelle application web. Ces trois voies sont compatibles avec un backend écrit en Rust. citeturn12view0turn12view1turn12view4turn9view0
En revanche, si tu veux dire “ajouter un widget natif à Glance lui-même, dans le code de l’application”, alors la réponse réaliste est non, pas en Rust dans le cadre normal du projet. Le code source montre que les widgets natifs sont des types Go enregistrés dans newWidget, que l’application est construite comme un binaire Go unique, et que les templates/assets sont embarqués par go:embed. Le dépôt ne montre pas de système de plugins dynamiques ou de pont officiel vers Rust. Tu pourrais toujours bricoler une solution maison via FFI, sidecar ou fork très personnalisé, mais ce ne serait ni idiomatique pour Glance, ni dans l’esprit “peu de dépendances / binaire simple” affiché par le projet. citeturn24view0turn4view1turn9view4turn19view0
Ma conclusion est donc nette : si ton vrai objectif est d’obtenir rapidement un onglet IP réseau puissant et maintenable, fais-le en Rust à l’extérieur de Glance, puis intègre-le dans Glance via iframe si tu veux la meilleure interactivité, ou via custom-api si tu veux un rendu plus natif et que tu acceptes davantage de contraintes côté UI. Si ton objectif est d’ajouter un widget “first-class” au codebase Glance, alors il faut le faire en Go, avec les templates et assets du projet. citeturn12view4turn12view0turn9view0turn24view0turn4view1