@nodefony/studio — l'administration web du framework
stable@nodefony/studiomis à jour 2026-07-19
Une application web qui montre le serveur en train de tourner : ses modules, ses routes, sa configuration, ses sessions, ses utilisateurs, ses journaux, ses canaux temps réel, ses bases de données et sa documentation. Studio ne porte aucune logique métier : il affiche ce que chaque module publie de lui-même sur le data plane d’administration. C’est aussi une pièce facultative — le framework démarre sans elle, et les données restent interrogeables sans écran.
📍 Documentation › @nodefony/studio
🧭 Par où commencer#
Trois parcours selon ce que tu viens faire. L’ordre compte : chaque étape suppose la précédente.
Je découvre Studio — voir l’intérieur du serveur avant de configurer quoi que ce soit.
- Démarrage rapide — déclarer le module, ouvrir la page, se connecter.
- Accéder à Studio — l’authentification passe par le pare-feu réel : il faut une identité qui existe vraiment, et les rôles décident de ce que tu vois.
- Le catalogue d’écrans ci-dessous — repérer la famille qui répond à ta question.
- Mon bureau — composer ton propre tableau de bord à partir des sondes existantes.
J’observe une application qui tourne — la santé, la charge, ce qui vient de se passer.
- Le temps réel de Studio — une seule connexion, des canaux qu’on ouvre à la demande : c’est ce qui rend les écrans vivants sans marteler le serveur.
- La famille Observation du catalogue — supervision, cluster, journaux, console temps réel.
@nodefony/realtime— la couche qui porte ces canaux, et ce qu’elle devient quand l’application passe à plusieurs répliques.@nodefony/security— le journal d’audit et le pare-feu, que Studio se contente de rendre lisibles.
Je publie mes propres écrans et données dans Studio — un module qui veut être administrable.
- La partition du namespace
/nodefony(section plus bas) — la règle d’architecture à respecter avant d’écrire la moindre route d’administration. - Publier ses écrans et ses données — le contrat
IAdminApi, le courtier qui monte les routes, et le catalogue qui rend ton module découvrable. @nodefony/framework— le courtier vit là, avec le routeur.@nodefony/frontend— si ton module apporte aussi sa propre interface : Studio en est le premier consommateur.
🗂️ Ce que Studio montre#
Le tableau pour trouver la bonne famille en cinq secondes ; les cards en dessous pour savoir ce
qu’on y trouve, et à qui c’est ouvert. La navigation réelle est déclarée d’un seul tenant
(NAV_GROUPS, navConfig.ts:106) — ajouter un écran, c’est ajouter une ligne.
| Famille | Ce qu’on y voit | Ouvert à |
|---|---|---|
| Mon espace | son bureau, son profil, ses sessions, ses clés d’API | tout compte authentifié |
| Observation | santé du processus, cluster, journaux, canaux temps réel | développeur · exploitant |
| Système & données | modules, configuration, routes, ORM, schéma, magasins | développeur |
| Sécurité | audit, pare-feu, utilisateurs, rôles, webhooks | administrateur de plateforme |
| IA | console de conversation et surfaces de gouvernance | développeur · administrateur |
| Documentation | ces pages, rendues depuis le dépôt | développeur · exploitant |
ℹ️ Note
La barre latérale montre plus que ce qui est monté. Une entrée de navigation peut porter le drapeau
wip: la barre la relègue alors en fin de groupe et l’atténue, et l’écran affiche une page d’attente au lieu de données. Une entréedevOnlydisparaît hors développement, parce que son data plane n’y est pas monté. Dans les deux cas c’est du confort d’affichage : la vraie garde reste le serveur, qui refuse l’appel.
🧠 Ce que Studio apporte#
Trois propriétés, toutes vérifiables dans le code — c’est ce qui distingue Studio d’un tableau de bord de plus.
Il n’invente aucune donnée. Chaque chiffre affiché vient d’un endpoint qu’un module a publié
lui-même, sous /nodefony/<module>/api/*. Studio découvre ces producteurs par le catalogue
/nodefony/framework/api/admin (FrameworkAdminApi.ts:203) et construit sa navigation avec.
Conséquence pratique : tout ce que montre l’écran est aussi lisible en curl, en script, ou depuis
un agent — l’interface n’est pas un passage obligé.
Il est facultatif, et le framework le sait. Le module se déclare non critique
(Studio.critical, index.ts:45) : un échec de son démarrage n’emporte jamais le processus. S’il
n’est pas chargé du tout, ce sont les pages qui disparaissent — le data plane de chaque module,
lui, reste monté et servi. On perd la vue, jamais la donnée.
Il se protège avec le pare-feu de l’application. Studio n’a pas d’authentification à lui : la
connexion passe par @nodefony/security (session serveur, cookie opaque), et chaque endpoint
d’administration porte un rôle minimum appliqué par le courtier (IAdminEndpoint.role,
IAdminApi.ts:152). Un administrateur de plateforme voit tout ; un développeur voit
l’introspection ; un simple compte ne voit que son espace.
🏛️ Place dans le framework#
Studio est le premier consommateur de @nodefony/frontend et du data plane admin. La flèche ne
part jamais dans l’autre sens : aucun module ne dépend de Studio pour fonctionner.
🚀 Démarrage rapide#
Vu depuis une application créée par nodefony create app. Studio se déclare comme n’importe quel
module, et n’a besoin de rien d’autre que du pare-feu pour être utile.
// nodefony.config.ts — l'orchestrateur de l'application
export default defineConfig(() => ({
modules: [
"@nodefony/http",
"@nodefony/framework",
// Sans pare-feu, aucune identité à présenter : Studio serait inaccessible.
"@nodefony/security",
// `auto` : l'interface vient des fichiers pré-construits livrés dans le paquet
// npm — ni Vite ni @nodefony/frontend requis. Dans le dépôt du framework, le
// même réglage bascule sur Vite (rechargement à chaud) parce que les sources
// sont là.
use("@nodefony/studio", { ui: "auto" }),
],
}));
Ce qu’on observe ensuite :
- Au démarrage, le module annonce le mode retenu et la raison de ce choix dans les journaux
(
Studio.onKernelBoot(),index.ts:66) — c’est le premier endroit à regarder si la page reste blanche. https://127.0.0.1:5152/nodefonysert la page ; toute URL à un segment sous/nodefonyrenvoie la même page React (StudioController.renderStudio(),StudioController.ts:53), pour que le rechargement d’un lien profond ne tombe pas sur une erreur.curl https://127.0.0.1:5152/nodefony/studio/api/healthrépond sans authentification : c’est la sonde de vie, volontairement placée hors du pare-feu et réduite à l’état, la durée de fonctionnement et l’identifiant de processus (StudioController.apiHealth(),StudioController.ts:159).
🔐 Accéder à Studio#
Il n’y a pas de compte de démonstration ni de connexion simulée : l’authentification est celle de
l’application. Le formulaire de connexion appelle le flux de session de @nodefony/security, qui
pose un cookie opaque ; le navigateur ne détient jamais de jeton exploitable.
Ce qu’il faut réunir pour entrer :
- Le module
@nodefony/securitychargé, avec une zone qui couvre/nodefonyet un authentificateur de session. - Un utilisateur qui existe dans le magasin d’identités — donc une source d’utilisateurs
branchée (voir
@nodefony/user). - Les rôles qui correspondent à ce qu’on veut voir. Ils ne changent pas seulement le menu : ils changent les réponses du serveur.
| Le compte porte… | Ce qu’il obtient |
|---|---|
| une session valide | son espace personnel : bureau, profil, ses sessions, ses clés d’API |
| un rôle développeur | modules, configuration, routes, ORM, schéma, magasins, documentation |
| un rôle d’exploitation | supervision, cluster, et les sondes de processus |
| l’administration | tout, dont sécurité, utilisateurs, rôles, audit et catalogue d’API |
❗ Important
Cacher un menu n’est pas une protection. Le filtrage par rôle dans la barre latérale ne sert qu’à ne pas proposer une porte fermée. La décision réelle est prise par le serveur : le courtier refuse l’appel si le rôle minimum de l’endpoint n’est pas là, et le pare-feu refuse la requête avant même le routage. Un écran vide dans Studio est donc, presque toujours, un refus légitime.
🏗️ La partition du namespace /nodefony#
/nodefony est réservé au framework : aucune application n’y monte ses propres routes. C’est ce
qui permet d’y loger l’administration sans jamais entrer en collision avec le métier — un /studio
ordinaire, lui, aurait fini par se heurter à une route applicative.
À l’intérieur de ce préfixe, deux espaces séparés par la profondeur :
| Espace | Forme | Porté par | Existe sans Studio |
|---|---|---|---|
| Interface humaine | /nodefony et /nodefony/<page> (1 segment) |
le module Studio | non |
| Data plane machine | /nodefony/<module>/api/* (≥ 3 segments) |
chaque module | oui |
La règle qui en découle vaut pour tout module, pas seulement pour Studio :
- ✅
/nodefony/http/api/sessions— trois segments, marqueur/api/, jamais ambigu. - ❌
/nodefony/security— un seul segment : entre en collision avec une page de l’interface.
Cette asymétrie est ce qui rend l’interface jetable. Le repli des liens profonds suit la même
prudence : chaque page à deux segments ou plus déclare son préfixe littéral
(modules/{name}, cluster/{pid}, users/{id}…). Un repli générique du type /{section}/{page}
masquerait les vraies routes que d’autres modules montent sous /nodefony/<x>/<y>.
🧩 Publier ses écrans et ses données#
Un module devient administrable en publiant un objet IAdminApi (IAdminApi.ts:212) — un
producteur de données, qui ne connaît ni le routeur, ni le contexte HTTP, ni la sérialisation.
import type { IAdminApi, IAdminRegistry } from "nodefony";
const inventoryAdminApi: IAdminApi = {
// Devient /nodefony/inventory/api/* — segment stable, les liens en dépendent.
adminNamespace: "inventory",
// Ce que la navigation de Studio affichera pour ce producteur.
adminDescriptor: () => ({ label: "Inventaire", icon: "box", order: 50 }),
adminEndpoints: () => [
{
path: "health",
summary: "État du magasin et âge du dernier inventaire",
// Rôle minimum appliqué par le courtier ; défaut : administrateur.
role: "ROLE_SUPERVISOR",
// Entrée → sortie. Le handler lit des données et rend des données.
handler: (request) => ({ ok: true, roles: request.roles }),
},
],
};
// Dans onKernelBoot : le courtier monte les routes plus tard, à onKernelReady.
const registry = this.kernel?.container?.get("adminBroker") as IAdminRegistry;
registry.register(inventoryAdminApi);
Ce que le framework fait ensuite, sans que le module s’en occupe :
AdminBroker.register()(AdminBroker.ts:45) mémorise le producteur et refuse un espace de nom déjà pris, ou un enregistrement arrivé après le montage.- Au montage, chaque endpoint devient une vraie route
/nodefony/<namespace>/api/<path>, avec son contrôle de rôle appliqué avant l’appel du handler. - Le producteur apparaît dans le catalogue
/nodefony/framework/api/admin, d’où Studio construit automatiquement un groupe de navigation — sans une ligne de code frontend.
💡 Astuce
Le contrat vit dans le cœur (
nodefony), pas dans le framework, exactement pour qu’un module bas niveau — un adaptateur de base de données, un service — puisse se rendre administrable sans dépendre du routeur. Le module ne déclare que quoi il expose ; le transport ne le regarde pas.
⚙️ Configuration#
Studio n’a presque rien à régler. La seule molette qui compte décide d’où vient l’interface :
| Valeur | Ce qui se passe | Quand |
|---|---|---|
auto |
Vite si tout est réuni, sinon les fichiers pré-construits du paquet | défaut — ne rien décider |
static |
force les fichiers pré-construits livrés avec le paquet npm | production, ou toute app installée depuis npm |
vite |
force le serveur de développement et le rechargement à chaud | contribution au framework lui-même |
En mode statique, l’administration fonctionne sans Vite ni @nodefony/frontend : c’est ce qui
la rend disponible d’emblée dans une application installée. En mode Vite, l’ordre de chargement
compte — Studio doit venir après @nodefony/frontend, dont le service doit exister au moment
où le module s’enregistre.
📡 Le temps réel de Studio#
Une seule connexion WebSocket permanente porte tout ce qui bouge : WS /nodefony/studio/api/realtime, en JSON-RPC 2.0. Elle ne pousse rien tant que personne ne
demande — le client s’abonne à un canal, le producteur démarre ; il se désabonne, le producteur
s’arrête, et la connexion reste ouverte.
Les canaux sont déclarés d’un seul endroit (CHANNELS, providers.ts:98) : flux des journaux,
sondes de processus pour la supervision et pour la barre de débogage, santé et flux de l’ORM, santé
de la socket elle-même. Beaucoup acceptent un suffixe de cadence (nodefony:orm:flow:5000) : c’est le
lecteur qui choisit sa granularité, dans des bornes que le serveur impose.
Deux autres formes de trafic circulent sur la même connexion :
- des actions en aller-retour (
StudioRealtimeController.realtimeActions(),StudioRealtimeController.ts:112) — mesurer la latence, déclencher un ramasse-miettes, piloter le générateur de code ; - le pont d’API (
StudioRealtimeController.realtimeApiRequest(),StudioRealtimeController.ts:202), qui permet d’appeler un endpoint du data plane par la socket plutôt qu’en HTTP, avec exactement la même réponse.
⚠️ Attention
Les sondes de processus décrivent un processus, pas un pod. En multi-travailleurs, la socket tombe sur un seul d’entre eux : ce qu’affiche la supervision est alors la vue de ce travailleur. Pour un verdict à l’échelle du pod, la source légitime est le canal de santé agrégé par le maître, pas un endpoint tiré au sort. C’est un modèle assumé, cohérent avec le cloud : chaque instance se rapporte, l’agrégation se fait au-dessus.
🧪 Tests & couverture#
Les compteurs sont régénérés depuis vitest, jamais figés dans cette prose. Ce qui mérite d’être dit ici, c’est ce que les suites prouvent — et la frontière volontaire de ce qu’elles ne couvrent pas.
| Type | Où | Ce qui est prouvé |
|---|---|---|
| Producteurs temps réel | nodefony/tests/unit/providers.test.ts |
agrégation des journaux en lots, cadence, arrêt propre |
| Agrégation cluster | nodefony/tests/unit/clusterSupervision.test.ts |
fusion des vues de plusieurs travailleurs en un verdict |
| Pont d’API | nodefony/tests/unit/apiClientSocketBridge.test.ts |
appeler le data plane par la socket rend la même réponse |
| Rendu et disposition | nodefony/tests/unit/{grid,jsonFormat}.test.ts |
grille du bureau composable, mise en forme des charges JSON |
La frontière est délibérée : le point d’entrée WebSocket relève de l’intégration sur serveur
vivant (il vit dans la suite WebSocket de @nodefony/http), et les
composants React ne sont pas instrumentés ici. Les tests portent sur la logique pure —
producteurs, agrégation, disposition — qui est là où les régressions se cachent.
cd src/packages/@nodefony/studio
npm test # suite unitaire, sans serveur
npm run coverage # + rapport lisible dans l'onglet Couverture de Studio
🔗 Pour aller plus loin#
- ⬆️ Remonter : Toute la documentation
- 📄 La page du module : Mon bureau — le tableau de bord composable
- 🧭 Modules voisins :
@nodefony/frontend(le build Vite qui sert cette interface) ·@nodefony/framework(le courtier et le routeur) ·@nodefony/security(identité, rôles, audit) ·@nodefony/realtime(les canaux) ·@nodefony/documentation(le portail qui rend ces pages) - 🏛️ Transverse : vue d’ensemble du framework · pipeline de requête · configuration
- 📖 Lexique général du framework.