Dans cet article
J’ai commencé DasherControl parce que mes services homelab finissaient dispersés dans mes onglets. L’idée était de regrouper des applets dans un espace de travail : favoris et applications intégrées au tableau de bord, avec quelques commandes Docker à proximité.

Deux parcours derrière le même tableau de bord
Vue générale des composants et des échanges vérifiés dans le dépôt.

flowchart LR
subgraph group_dashboard["Dashboard UI"]
node_main_ui["Dashboard Shell<br/>[Main.vue]"]
node_grid["Workspace Grid<br/>[GridLayoutMain.vue]"]
node_grid_item["Grid Items<br/>[GridItemApplet.vue]"]
node_applet_router["Applet Selector<br/>[Applet.vue]"]
node_applet_authoring["Applet Creation<br/>[CreateNew.vue]"]
node_editor["Applet Editor<br/>[Editor.vue]"]
node_iframe["Embedded Sites"]
node_shortcut["Shortcuts<br/>[Shortcut.vue]"]
node_canvas["Canvas Applet<br/>[Canvas.vue]"]
node_login_ui["Login Screen<br/>[Login.vue]"]
end
subgraph group_identity["Tenant Identity"]
node_tenant_api["Tenant Endpoints<br/>[handler.rs]"]
node_tenant_model["Tenant Accounts<br/>[model.rs]"]
node_token_auth["Token Validation<br/>[token.rs]"]
end
subgraph group_workspace["Workspace Data"]
node_workspace_api["Workspace Endpoints<br/>[handler.rs]"]
node_workspace_model["Workspace Records<br/>[model.rs]"]
node_database[("PostgreSQL Access<br/>[db.rs]")]
node_schema["Database Schema<br/>[schema.rs]"]
end
subgraph group_integrations["Runtime Integrations"]
node_docker_ui["Docker Manager<br/>[DockerManager.vue]"]
node_container_api["Container Endpoints<br/>[handler.rs]"]
node_docker_engine["Docker Engine<br/>[docker.rs]"]
node_terminal_ui["Terminal Applet<br/>[Terminal.vue]"]
node_terminal_ws["Terminal WebSocket<br/>[websocket.rs]"]
node_server["Rocket Server<br/>[main.rs]"]
end
node_user(("Dashboard User"))
node_user -->|"uses"| node_main_ui
node_main_ui -->|"renders"| node_grid
node_grid -->|"renders"| node_grid_item
node_grid_item -->|"renders"| node_applet_router
node_applet_router -->|"selects"| node_applet_authoring
node_applet_router -->|"selects"| node_editor
node_applet_router -->|"selects"| node_iframe
node_applet_router -->|"selects"| node_shortcut
node_applet_authoring -->|"configures"| node_applet_authoring
node_grid -->|"requests"| node_workspace_api
node_tenant_api -->|"authenticates"| node_tenant_model
node_tenant_api -->|"issues tokens"| node_token_auth
node_token_auth -->|"checks session"| node_tenant_model
node_workspace_api -->|"queries"| node_workspace_model
node_workspace_api -->|"validates"| node_token_auth
node_tenant_model -->|"queries"| node_schema
node_workspace_model -->|"queries"| node_schema
node_tenant_api -->|"uses connection"| node_database
node_workspace_api -->|"uses connection"| node_database
node_docker_ui -->|"requests"| node_container_api
node_container_api -->|"controls"| node_docker_engine
node_applet_router -->|"selects"| node_docker_ui
node_applet_router -->|"selects"| node_terminal_ui
node_server -->|"mounts routes"| node_tenant_api
node_server -->|"mounts routes"| node_workspace_api
node_server -->|"mounts routes"| node_container_api
node_server -->|"references"| node_terminal_ws
node_user -.->|"may use"| node_canvas
node_terminal_ui -.->|"SSH terminal"| node_terminal_ws
class node_main_ui,node_grid,node_grid_item,node_applet_router,node_applet_authoring,node_editor,node_iframe,node_shortcut,node_canvas,node_login_ui,node_user toneBlue
class node_tenant_api,node_tenant_model,node_token_auth toneAmber
class node_workspace_api,node_workspace_model,node_database,node_schema toneMint
class node_docker_ui,node_container_api,node_docker_engine,node_terminal_ui,node_terminal_ws,node_server toneRoseUn tableau de bord homelab configurable où les utilisateurs organisent des espaces de travail composés d’applets. Vue appelle des routes Rocket pour la persistance, les contrôles de locataire et les actions Docker; PostgreSQL conserve les espaces et un adaptateur séparé communique avec Docker.
- Composants
- 24
- Connexions
- 29
- Liens appuyés par le code ou le README
- 26/29
Version texte du diagramme
- Interface Vue → Routes Rocket
- Routes Rocket → Diesel et PostgreSQL
- Routes Rocket → Adaptateur Shiplift
- Adaptateur Shiplift → Démon Docker
Sauvegarder une disposition passe par les routes des espaces et les données persistantes. Redémarrer un conteneur passe par l’adaptateur Shiplift vers Docker. PostgreSQL n’exécute pas l’action Docker et une applet n’est pas un conteneur. Cette séparation aide à comprendre les permissions et les erreurs.
Suivre les routes et la frontière Docker dans la note d’architecture.
Un espace de travail a un propriétaire
Le frontend utilise Vue et le backend repose sur Rust, Rocket et Diesel. Dans src/workspaces/handler.rs, la liste des espaces utilise l’identifiant du compte présent dans un jeton. La route qui retourne les applets vérifie le propriétaire de l’espace avant de renvoyer son contenu.
La création d’un espace avec ses applets récupère également cet identifiant depuis le jeton. C’est le modèle prévu : une disposition sauvegardée appartient à un utilisateur, et chaque applet appartient à un espace.
Le fichier contient aussi d’anciennes routes de création et de suppression avec des vérifications différentes. Il faudrait revoir les autorisations de chaque opération avant d’en faire un service partagé. Cet article décrit une expérience, sans constituer un audit d’un système déployé.
Docker a sa propre interface
src/containers/docker.rs regroupe les appels Shiplift pour lister les conteneurs et les images, télécharger des images, créer des conteneurs, les arrêter ou les redémarrer. Les opérations Docker restent ainsi séparées du modèle des espaces de travail.
Le code révèle aussi un problème d’interface : les fonctions de liste retournent un vecteur vide après une erreur Docker. Un écran vide peut donc signifier qu’aucun conteneur n’existe ou que le service est inaccessible. Dans une nouvelle version, je distinguerais ces deux états à l’écran.
Ce que le navigateur accepte d’intégrer
Le README mentionne des échecs de connexion dans les iframes. Certaines applications interdisent l’intégration, et l’authentification dans un cadre peut différer de celle d’un onglet normal. Le tableau de bord doit proposer un lien externe pour ces services.
Ce projet est ancien. La capture et le GIF optionnel conservent son interface d’époque ; ils ne démontrent pas sa compatibilité avec les dépendances actuelles.
Lire le GIF de démonstration d’origine

Voir le code et le README d’origine. Les deux illustrations viennent de son dossier images.
