# 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 `
`**, 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 : ```json { "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** : ```yaml 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 : ```yaml server: assets-path: /app/assets document: head: | 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: | ``` 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