Architecture
This page shows the panel at the levels of the C4 model: the system context, the containers and the components of the web application. Two model-view-controller diagrams then show which view classes read which data and render which templates.
The system context and container diagrams are written by hand: they show the
deployment boundaries, which panel/nix/ and core/ define. The component and
model-view-controller diagrams are generated from the sources in panel/src/
when the documentation builds, by panel/nix/architecture-diagrams.py. A new
module, route, view or template shows up there with no edit to this page.
In all diagrams, a dashed border marks a system or container outside the scope of the diagram. Click a diagram to show it at its full size.
System context
flowchart TB operator(["<b>Operator</b><br/>[Person]<br/>Runs Fediversity applications<br/>for their organization."]) admin(["<b>Hosting provider admin</b><br/>[Person]<br/>Manages operator accounts."]) panel["<b>Fediversity Panel</b><br/>[Software system]<br/>Lets operators configure,<br/>deploy and remove applications."] api["<b>Fediversity API</b><br/>[Software system]<br/>Serves the configuration schema,<br/>queues and runs deployments,<br/>publishes their state."] idp["<b>Identity provider</b><br/>[Software system: OpenID Connect]<br/>Authenticates operators."] openbao["<b>OpenBao</b><br/>[Software system]<br/>Holds and rotates secrets."] hosting["<b>Hosting infrastructure</b><br/>[Software system]<br/>Runs the deployed applications."] operator -->|"configures and deploys<br/>applications, HTTPS"| panel admin -->|"manages accounts, HTTPS"| panel operator -.->|"signs in"| idp panel -->|"signs operators in"| idp panel -->|"submits configurations,<br/>reads deployment state"| api api -->|"asks to authorize operators"| panel panel -->|"reads database credentials<br/>and the callback token"| openbao api -->|"deploys applications"| hosting class api,idp,openbao,hosting external classDef external stroke-dasharray: 5 5
Containers
The panel is one Django application. The database is PostgreSQL in a Fediversity deployment, where OpenBao rotates its credentials, and SQLite when the NixOS module of the panel runs on its own.
flowchart TB
operator(["<b>Operator</b><br/>[Person]"])
admin(["<b>Hosting provider admin</b><br/>[Person]"])
subgraph panel ["Fediversity Panel [Software system]"]
browser["<b>Web browser</b><br/>[Container: HTML, htmx, forms island]<br/>Renders the pages, the configuration<br/>form and live deployment state."]
nginx["<b>Reverse proxy</b><br/>[Container: nginx]<br/>Terminates TLS, serves static files,<br/>limits the rate of password logins."]
web["<b>Web application</b><br/>[Container: Django on uvicorn, ASGI]<br/>Serves the pages, mints bearer tokens,<br/>authorizes event subscriptions."]
db[("<b>Database</b><br/>[Container: PostgreSQL, or SQLite standalone]<br/>Users, deployments, operator settings.")]
end
subgraph apisystem ["Fediversity API [Software system]"]
api["<b>API</b><br/>[Container: HTTP]<br/>Configuration schema,<br/>deployments, audit events."]
centrifugo["<b>Centrifugo</b><br/>[Container: pub/sub]<br/>Pushes deployment state<br/>to browsers."]
end
idp["<b>Identity provider</b><br/>[Software system: OpenID Connect]"]
openbao["<b>OpenBao</b><br/>[Software system]"]
operator -->|uses| browser
admin -->|"uses /admin/"| browser
browser -->|HTTPS| nginx
nginx -->|"HTTP, ASGI"| web
web -->|"reads and writes, SQL"| db
web -->|"HTTP, through api_client"| api
web -->|"OpenID Connect"| idp
browser -->|"HTTPS, bearer token"| api
browser -->|"WebSocket, events.domain"| centrifugo
centrifugo -->|"connect, subscribe and<br/>publish callbacks"| nginx
api -->|"authorization callback"| nginx
openbao -.->|"database credentials,<br/>callback token"| web
class api,centrifugo,idp,openbao external
classDef external stroke-dasharray: 5 5
Components
The components of the web application are the modules of the panel package.
An arrow is an import, or a module path in a setting, and its label names what
the module uses. A dotted arrow leaves the web application. A line with no
arrowhead is a call from a system that the panel also calls, labelled with the
routes that the system calls.
flowchart TB
subgraph web ["Web application [Container: Django on uvicorn]"]
m_admin["<b>admin</b><br/>[Component: Python module]<br/>The Django admin pages, where an<br/>admin manages the accounts."]
m_api_client["<b>api_client</b><br/>[Component: Python module]<br/>Thin facade over the generated<br/>fediversity_api_client OpenAPI<br/>client."]
m_asgi["<b>asgi</b><br/>[Component: Python module]<br/>ASGI config for panel project."]
m_context_processors["<b>context_processors</b><br/>[Component: Python module]<br/>Template context processors."]
m_forms["<b>forms</b><br/>[Component: Python module]<br/>The forms the deployment pages post."]
m_models["<b>models</b><br/>[Component: Python module]<br/>The data the panel stores: the<br/>deployments and the settings of each<br/>operator."]
m_oidc["<b>oidc</b><br/>[Component: Python module]<br/>Single sign-on (OpenID Connect)<br/>settings for the panel."]
m_settings["<b>settings</b><br/>[Component: Python module]<br/>Django settings for panel project."]
m_templatetags_custom_tags["<b>templatetags.custom_tags</b><br/>[Component: Python module]<br/>Template tags the pages load."]
m_tokens["<b>tokens</b><br/>[Component: Python module]<br/>Bearer tokens the browser mints to<br/>call api/'s HTTP surface directly."]
m_urls["<b>urls</b><br/>[Component: Python module]<br/>URL configuration for panel project."]
m_views["<b>views</b><br/>[Component: Python module]<br/>The view classes that serve the<br/>pages and the endpoints of the<br/>panel."]
m_wsgi["<b>wsgi</b><br/>[Component: Python module]<br/>WSGI config for panel project."]
t_templates["<b>templates</b><br/>[Component: Django templates]<br/>15 templates"]
end
x_api["<b>Fediversity API</b><br/>[Software system]"]
class x_api external
x_browser["<b>Web browser</b><br/>[Container: HTML, htmx, forms island]"]
class x_browser external
x_centrifugo["<b>Centrifugo</b><br/>[Container of the Fediversity API]"]
class x_centrifugo external
x_database[("<b>Database</b><br/>[Container: PostgreSQL, or SQLite standalone]")]
class x_database external
x_idp["<b>Identity provider</b><br/>[Software system: OpenID Connect]"]
class x_idp external
x_nginx["<b>Reverse proxy</b><br/>[Container: nginx]"]
class x_nginx external
x_browser -->|HTTPS| x_nginx
x_nginx -->|HTTP, ASGI| m_asgi
x_nginx ---|"Fediversity API calls api_authz"| x_api
x_centrifugo -->|"centrifugo_connect, centrifugo_publish, centrifugo_subscribe"| x_nginx
m_admin -->|"DeploymentDestroyError"| m_models
m_admin -.->|"User, transaction"| x_database
m_api_client -.->|"AuditEventListResponse, AuthenticatedClient, Client, +22"| x_api
m_asgi --> m_settings
m_context_processors -->|"OperatorSettings"| m_models
m_forms -->|"Deployment, OperatorSettings"| m_models
m_models -->|"OUTAGE_ERRORS, deployment_delete_call"| m_api_client
m_models -->|"mint"| m_tokens
m_models -.->|"UnexpectedStatus"| x_api
m_models -.->|"User, models, pre_delete"| x_database
m_oidc -.->|"DEFAULT_AUTH_PIPELINE"| x_idp
m_settings -->|"centrifugo, oidc, update_notice"| m_context_processors
m_settings -->|"OIDC_BACKEND, enabled, social_auth_settings"| m_oidc
m_settings --> m_urls
m_settings -->|"application"| m_wsgi
m_urls -->|"AccountDetail, AccountHistory, ApiToken, +22"| m_views
m_views -->|"OUTAGE_ERRORS, audit_event_list_call, configuration_sections_call, +7"| m_api_client
m_views -->|"DeploymentModelForm, OperatorSettingsForm, UpdatePolicyForm"| m_forms
m_views -->|"Deployment, OperatorSettings"| m_models
m_views -->|"mint, mint_reconcile, verify, +1"| m_tokens
m_views -.->|"UnexpectedStatus"| x_api
m_views -.->|"User, UserSocialAuth"| x_database
m_wsgi --> m_settings
m_views -->|renders| t_templates
t_templates -->|loads| m_templatetags_custom_tags
m_urls -->|"admin.site.urls, autodiscover"| m_admin
classDef external stroke-dasharray: 5 5
Model-view-controller
Django calls this structure model-template-view: the "view" of Django is the
controller here, and its templates are the view. The model holds the Django
models and the data of other systems that the panel reads: the API, through
api_client, and the bearer tokens of tokens.
Pages
The view classes that render a template. A dotted arrow from a template is a form or a script that sends a request to a controller.
flowchart LR
subgraph view ["View: templates"]
t_account_detail_html["<b>account_detail.html</b><br/>scripts: update_policy.js,<br/>api_token.js, api_token_copy.js"]
t_account_history_html["<b>account_history.html</b><br/>scripts: relative_time.js"]
t_base_html["<b>base.html</b><br/>scripts: htmx.min.js, notice.js,<br/>logo_fallback.js, update_notice.js,<br/>theme.js"]
t_deployment_form_html["<b>deployment_form.html</b><br/>scripts: centrifuge.js,<br/>centrifuge_session.js, api_token.js,<br/>deployment_delete.js,<br/>deployment_submit.js,<br/>migration_export.js,<br/>deployment_action.js,<br/>update_policy.js, relative_time.js,<br/>forms-island.js"]
t_deployment_list_html["<b>deployment_list.html</b><br/>scripts: api_token.js,<br/>migration_import.js,<br/>relative_time.js, centrifuge.js,<br/>centrifuge_session.js,<br/>deployment_delete.js,<br/>migration_export.js"]
t_deployment_sbom_html["<b>deployment_sbom.html</b>"]
t_deployment_section_form_html["<b>deployment_section_form.html</b><br/>scripts: centrifuge.js,<br/>centrifuge_session.js, api_token.js,<br/>deployment_delete.js,<br/>deployment_submit.js,<br/>forms-island.js"]
t_index_html["<b>index.html</b>"]
t_instance_list_html["<b>instance_list.html</b>"]
t_partials__api_unreachable_html["<b>partials/_api_unreachable.html</b>"]
t_partials__deployment_actions_html["<b>partials/_deployment_actions.html</b>"]
t_partials__island_skeleton_html["<b>partials/_island_skeleton.html</b>"]
t_partials__messages_html["<b>partials/_messages.html</b>"]
t_partials__update_policy_fields_html["<b>partials/_update_policy_fields.html</b>"]
t_registration_login_html["<b>registration/login.html</b>"]
end
subgraph controller ["Controller: view classes"]
inc_admin_site_urls["<b>admin.site.urls</b><br/>[included URLconf]<br/>/admin/"]
c_Index["<b>Index</b><br/>[page]<br/>/"]
inc_django_contrib_auth_urls["<b>django.contrib.auth.urls</b><br/>[included URLconf]<br/>/"]
inc_social_django_urls["<b>social_django.urls</b><br/>[included URLconf]<br/>/oauth/"]
c_AccountDetail["<b>AccountDetail</b><br/>[page]<br/>/account/"]
c_AccountHistory["<b>AccountHistory</b><br/>[page]<br/>/account/history/"]
c_InstanceList["<b>InstanceList</b><br/>[page]<br/>/instances/"]
c_DeploymentList["<b>DeploymentList</b><br/>[page]<br/>/deployments/"]
c_DeploymentForm["<b>DeploymentForm</b><br/>[page]<br/>/deployments/{pk}/"]
c_DeploymentSbom["<b>DeploymentSbom</b><br/>[page]<br/>/deployments/{pk}/sbom/"]
c_DeploymentSectionForm["<b>DeploymentSectionForm</b><br/>[page]<br/>/deployments/{pk}/section/{section}/"]
inc_debug_toolbar_urls["<b>debug_toolbar.urls</b><br/>[included URLconf, only if settings.DEBUG]<br/>/debug/"]
f_DeploymentModelForm["<b>DeploymentModelForm</b><br/>[form]"]
end
subgraph model ["Model"]
model_Deployment[("<b>Deployment</b><br/>[Django model]")]
model_OperatorSettings[("<b>OperatorSettings</b><br/>[Django model]")]
model_User[("<b>User</b><br/>[Django model]")]
model_UserSocialAuth[("<b>UserSocialAuth</b><br/>[Django model]")]
subgraph api_group ["api/, through api_client"]
api_audit_event_list_call["<b>audit_event_list_call</b>"]
api_configuration_sections_call["<b>configuration_sections_call</b>"]
api_deployment_get_status["<b>deployment_get_status</b>"]
api_domain_list_call["<b>domain_list_call</b>"]
api_sbom_list_call["<b>sbom_list_call</b>"]
end
subgraph tokens_group ["tokens"]
tokens_mint["<b>mint</b>"]
end
end
t_index_html ---|rendered by| c_Index
t_registration_login_html ---|rendered by| inc_django_contrib_auth_urls
t_account_detail_html ---|rendered by| c_AccountDetail
c_AccountDetail --> model_OperatorSettings
c_AccountDetail --> model_User
t_account_history_html ---|rendered by| c_AccountHistory
c_AccountHistory --> model_Deployment
c_AccountHistory --> model_User
c_AccountHistory --> model_UserSocialAuth
c_AccountHistory --> api_audit_event_list_call
c_AccountHistory --> tokens_mint
t_instance_list_html ---|rendered by| c_InstanceList
c_InstanceList --> model_Deployment
c_InstanceList --> api_configuration_sections_call
c_InstanceList --> api_deployment_get_status
c_InstanceList --> tokens_mint
t_deployment_list_html ---|rendered by| c_DeploymentList
c_DeploymentList --> model_Deployment
t_deployment_form_html ---|rendered by| c_DeploymentForm
c_DeploymentForm -->|validates with| f_DeploymentModelForm
c_DeploymentForm --> model_Deployment
c_DeploymentForm --> model_OperatorSettings
c_DeploymentForm --> model_User
c_DeploymentForm --> model_UserSocialAuth
c_DeploymentForm --> api_deployment_get_status
c_DeploymentForm --> api_domain_list_call
c_DeploymentForm --> tokens_mint
t_deployment_sbom_html ---|rendered by| c_DeploymentSbom
c_DeploymentSbom --> model_Deployment
c_DeploymentSbom --> api_sbom_list_call
c_DeploymentSbom --> tokens_mint
t_deployment_section_form_html ---|rendered by| c_DeploymentSectionForm
c_DeploymentSectionForm -->|validates with| f_DeploymentModelForm
c_DeploymentSectionForm --> model_Deployment
c_DeploymentSectionForm --> model_OperatorSettings
c_DeploymentSectionForm --> model_User
c_DeploymentSectionForm --> model_UserSocialAuth
c_DeploymentSectionForm --> api_configuration_sections_call
c_DeploymentSectionForm --> api_deployment_get_status
c_DeploymentSectionForm --> api_domain_list_call
c_DeploymentSectionForm --> tokens_mint
t_account_detail_html -.->|submits to| c_AccountDetail
t_account_history_html -.->|submits to| c_AccountHistory
t_base_html -.->|submits to| inc_django_contrib_auth_urls
t_deployment_form_html -.->|submits to| c_DeploymentForm
t_deployment_section_form_html -.->|submits to| c_DeploymentSectionForm
t_registration_login_html -.->|submits to| inc_social_django_urls
t_partials__update_policy_fields_html ---|included by| t_account_detail_html
t_base_html -.-|extended by| t_account_detail_html
t_base_html -.-|extended by| t_account_history_html
t_partials__messages_html ---|included by| t_base_html
t_partials__api_unreachable_html ---|included by| t_deployment_form_html
t_partials__island_skeleton_html ---|included by| t_deployment_form_html
t_partials__deployment_actions_html ---|included by| t_deployment_form_html
t_partials__update_policy_fields_html ---|included by| t_deployment_form_html
t_base_html -.-|extended by| t_deployment_form_html
t_base_html -.-|extended by| t_deployment_list_html
t_base_html -.-|extended by| t_deployment_sbom_html
t_partials__api_unreachable_html ---|included by| t_deployment_section_form_html
t_partials__island_skeleton_html ---|included by| t_deployment_section_form_html
t_partials__deployment_actions_html ---|included by| t_deployment_section_form_html
t_base_html -.-|extended by| t_deployment_section_form_html
t_base_html -.-|extended by| t_index_html
t_partials__api_unreachable_html ---|included by| t_instance_list_html
t_base_html -.-|extended by| t_instance_list_html
t_base_html -.-|extended by| t_registration_login_html
f_DeploymentModelForm -->|binds| model_Deployment
classDef external stroke-dasharray: 5 5
Endpoints
The view classes that return JSON, a redirect or a plain response, and what calls them: the scripts and templates of the pages, and the systems outside the panel.
flowchart LR
subgraph callers ["Callers"]
t_base_html["<b>base.html</b><br/>[template]"]
t_deployment_form_html["<b>deployment_form.html</b><br/>[template]"]
t_deployment_list_html["<b>deployment_list.html</b><br/>[template]"]
t_partials__deployment_actions_html["<b>partials/_deployment_actions.html</b><br/>[template]"]
js_api_token_js["<b>api_token.js</b><br/>[script]"]
x_api["<b>Fediversity API</b><br/>[Software system]"]
class x_api external
x_centrifugo["<b>Centrifugo</b><br/>[Container of the Fediversity API]"]
class x_centrifugo external
end
subgraph controller ["Controller: view classes"]
c_UpdatesSeen["<b>UpdatesSeen</b><br/>[plain]<br/>/account/updates-seen/"]
c_DeploymentCreate["<b>DeploymentCreate</b><br/>[redirect]<br/>/deployments/new/"]
c_DeploymentDuplicate["<b>DeploymentDuplicate</b><br/>[redirect]<br/>/deployments/{pk}/duplicate/"]
c_DeploymentDestroyConfirm["<b>DeploymentDestroyConfirm</b><br/>[plain]<br/>/deployments/{pk}/delete/confirm/"]
c_DeploymentReconcile["<b>DeploymentReconcile</b><br/>[plain]<br/>/deployments/{pk}/delete/reconcile/"]
c_DeploymentStateProxy["<b>DeploymentStateProxy</b><br/>[JSON]<br/>/deployments/{pk}/state/"]
c_DeploymentSbomDownload["<b>DeploymentSbomDownload</b><br/>[JSON, plain]<br/>/deployments/{pk}/sbom/{node}/"]
c_DeploymentUpdatePolicy["<b>DeploymentUpdatePolicy</b><br/>[redirect]<br/>/deployments/{pk}/update-policy/"]
c_DeploymentDomainClaim["<b>DeploymentDomainClaim</b><br/>[redirect]<br/>/deployments/{pk}/domain-claim/"]
r__deployments__uuid_pk___slug_section__["<b>RedirectView to deployment_section_form</b><br/>[redirect]<br/>/deployments/{pk}/{section}/"]
c_ApiToken["<b>ApiToken</b><br/>[JSON]<br/>/api/token/"]
c_ReconcileToken["<b>ReconcileToken</b><br/>[JSON]<br/>/api/reconcile-token/{pk}/"]
c_DeploymentListProxy["<b>DeploymentListProxy</b><br/>[JSON]<br/>/api/deployments/"]
c_UpdateNotice["<b>UpdateNotice</b><br/>[JSON]<br/>/api/update-notice/"]
c_AuthzCallback["<b>AuthzCallback</b><br/>[JSON, plain]<br/>/api/authz/"]
c_CentrifugoConnect["<b>CentrifugoConnect</b><br/>[JSON]<br/>/api/centrifugo/connect/"]
c_CentrifugoSubscribe["<b>CentrifugoSubscribe</b><br/>[JSON, plain]<br/>/api/centrifugo/subscribe/"]
c_CentrifugoPublish["<b>CentrifugoPublish</b><br/>[JSON]<br/>/api/centrifugo/publish/"]
end
subgraph model ["Model"]
model_Deployment[("<b>Deployment</b><br/>[Django model]")]
model_OperatorSettings[("<b>OperatorSettings</b><br/>[Django model]")]
model_User[("<b>User</b><br/>[Django model]")]
model_UserSocialAuth[("<b>UserSocialAuth</b><br/>[Django model]")]
subgraph api_group ["api/, through api_client"]
api_deployment_get_status["<b>deployment_get_status</b>"]
api_deployment_list_call["<b>deployment_list_call</b>"]
api_deployment_update_policy_call["<b>deployment_update_policy_call</b>"]
api_domain_claim_call["<b>domain_claim_call</b>"]
api_sbom_get_call["<b>sbom_get_call</b>"]
end
subgraph tokens_group ["tokens"]
tokens_mint["<b>mint</b>"]
tokens_mint_reconcile["<b>mint_reconcile</b>"]
tokens_verify["<b>verify</b>"]
tokens_verify_reconcile["<b>verify_reconcile</b>"]
end
end
c_DeploymentSectionForm["<b>DeploymentSectionForm</b><br/>[page]<br/>/deployments/{pk}/section/{section}/"]
c_UpdatesSeen --> model_OperatorSettings
c_DeploymentCreate --> model_Deployment
c_DeploymentDuplicate --> model_Deployment
c_DeploymentDestroyConfirm --> model_Deployment
c_DeploymentReconcile --> model_Deployment
c_DeploymentReconcile --> tokens_verify_reconcile
c_DeploymentStateProxy --> model_Deployment
c_DeploymentStateProxy --> api_deployment_get_status
c_DeploymentStateProxy --> tokens_mint
c_DeploymentSbomDownload --> model_Deployment
c_DeploymentSbomDownload --> api_sbom_get_call
c_DeploymentSbomDownload --> tokens_mint
c_DeploymentUpdatePolicy --> model_Deployment
c_DeploymentUpdatePolicy --> api_deployment_update_policy_call
c_DeploymentUpdatePolicy --> tokens_mint
c_DeploymentDomainClaim --> model_Deployment
c_DeploymentDomainClaim --> api_domain_claim_call
c_DeploymentDomainClaim --> tokens_mint
c_ApiToken --> tokens_mint
c_ReconcileToken --> model_Deployment
c_ReconcileToken --> tokens_mint_reconcile
c_DeploymentListProxy --> model_Deployment
c_DeploymentListProxy --> api_deployment_list_call
c_DeploymentListProxy --> tokens_mint
c_UpdateNotice --> model_Deployment
c_UpdateNotice --> model_OperatorSettings
c_UpdateNotice --> api_deployment_list_call
c_UpdateNotice --> tokens_mint
c_AuthzCallback --> model_Deployment
c_AuthzCallback --> model_User
c_AuthzCallback --> model_UserSocialAuth
c_AuthzCallback --> tokens_verify
c_CentrifugoSubscribe --> model_Deployment
t_base_html -.->|calls| c_UpdateNotice
t_base_html -.->|calls| c_UpdatesSeen
t_deployment_form_html -.->|calls| c_DeploymentDomainClaim
t_deployment_form_html -.->|calls| c_DeploymentUpdatePolicy
t_deployment_list_html -.->|calls| c_DeploymentCreate
t_deployment_list_html -.->|calls| c_DeploymentDestroyConfirm
t_deployment_list_html -.->|calls| c_DeploymentDuplicate
t_deployment_list_html -.->|calls| c_DeploymentStateProxy
t_deployment_list_html -.->|calls| c_ReconcileToken
t_partials__deployment_actions_html -.->|calls| c_DeploymentDestroyConfirm
t_partials__deployment_actions_html -.->|calls| c_DeploymentStateProxy
t_partials__deployment_actions_html -.->|calls| c_ReconcileToken
js_api_token_js -.->|fetches| c_ApiToken
x_api -.->|calls| c_AuthzCallback
x_centrifugo -.->|calls| c_CentrifugoConnect
x_centrifugo -.->|calls| c_CentrifugoSubscribe
x_centrifugo -.->|calls| c_CentrifugoPublish
r__deployments__uuid_pk___slug_section__ -.->|redirects to| c_DeploymentSectionForm
classDef external stroke-dasharray: 5 5
Notation
The C4 diagrams on this page are Mermaid flowcharts in the C4 style, not the
C4Context, C4Container and C4Component diagram types of Mermaid. Those
types draw with fixed colors, so they ignore the light and dark themes of this
site, and they place elements in fixed rows rather than by their relations.
Change to them when both of these are true:
- They take their colors from the theme variables of Mermaid, so they follow the theme of the site, as flowcharts do.
- Mermaid no longer marks them as experimental, and their layout keeps a
diagram of this size readable without manual
UpdateLayoutConfigtuning.
The generator then writes Component, Container_Ext and Rel elements in
place of flowchart nodes and edges; the analysis of the sources stays the same.