Skip to main content

axum_admin/
app.rs

1use crate::{auth::AdminAuth, entity::{EntityAdmin, EntityGroupAdmin}, render::AdminRenderer};
2use std::{path::PathBuf, sync::Arc};
3
4pub enum AdminEntry {
5    Entity(EntityAdmin),
6    Group(EntityGroupAdmin),
7}
8
9impl From<EntityAdmin> for AdminEntry {
10    fn from(e: EntityAdmin) -> Self { AdminEntry::Entity(e) }
11}
12
13impl From<EntityGroupAdmin> for AdminEntry {
14    fn from(g: EntityGroupAdmin) -> Self { AdminEntry::Group(g) }
15}
16
17pub struct AdminAppState {
18    pub title: String,
19    pub icon: String,
20    pub entities: Vec<EntityAdmin>,
21    pub renderer: AdminRenderer,
22    #[cfg(feature = "seaorm")]
23    pub enforcer: Option<std::sync::Arc<tokio::sync::RwLock<casbin::Enforcer>>>,
24    #[cfg(not(feature = "seaorm"))]
25    pub enforcer: Option<()>,
26    #[cfg(feature = "seaorm")]
27    pub seaorm_auth: Option<std::sync::Arc<crate::adapters::seaorm_auth::SeaOrmAdminAuth>>,
28    pub show_auth_nav: bool,
29}
30
31pub struct AdminApp {
32    pub title: String,
33    pub icon: String,
34    pub prefix: String,
35    pub entities: Vec<EntityAdmin>,
36    pub auth: Option<Arc<dyn AdminAuth>>,
37    pub(crate) templates: Vec<(String, String)>,
38    pub(crate) template_dirs: Vec<PathBuf>,
39    /// Maximum multipart body size in bytes. Defaults to 10 MiB.
40    pub upload_limit: usize,
41    #[cfg(feature = "seaorm")]
42    pub(crate) enforcer: Option<std::sync::Arc<tokio::sync::RwLock<casbin::Enforcer>>>,
43    #[cfg(not(feature = "seaorm"))]
44    pub(crate) enforcer: Option<()>,
45    #[cfg(feature = "seaorm")]
46    pub(crate) seaorm_auth: Option<std::sync::Arc<crate::adapters::seaorm_auth::SeaOrmAdminAuth>>,
47}
48
49impl AdminApp {
50    pub fn new() -> Self {
51        Self {
52            title: "Admin".to_string(),
53            icon: "fa-solid fa-bolt".to_string(),
54            prefix: "/admin".to_string(),
55            entities: Vec::new(),
56            auth: None,
57            templates: Vec::new(),
58            template_dirs: Vec::new(),
59            upload_limit: 10 * 1024 * 1024, // 10 MiB
60            enforcer: None,
61            #[cfg(feature = "seaorm")]
62            seaorm_auth: None,
63        }
64    }
65
66    /// Set the maximum allowed multipart upload size in bytes.
67    /// Defaults to 10 MiB (10 * 1024 * 1024).
68    pub fn upload_limit(mut self, bytes: usize) -> Self {
69        self.upload_limit = bytes;
70        self
71    }
72
73    pub fn title(mut self, title: &str) -> Self {
74        self.title = title.to_string();
75        self
76    }
77
78    /// Set the Font Awesome icon class for the app logo in the sidebar.
79    /// Defaults to `"fa-solid fa-bolt"`.
80    pub fn icon(mut self, icon: &str) -> Self {
81        self.icon = icon.to_string();
82        self
83    }
84
85    pub fn prefix(mut self, prefix: &str) -> Self {
86        self.prefix = prefix.to_string();
87        self
88    }
89
90    pub fn register(mut self, entry: impl Into<AdminEntry>) -> Self {
91        match entry.into() {
92            AdminEntry::Entity(e) => self.entities.push(e),
93            AdminEntry::Group(g) => self.entities.extend(g.into_entities()),
94        }
95        self
96    }
97
98    pub fn auth(mut self, auth: Box<dyn AdminAuth>) -> Self {
99        self.auth = Some(Arc::from(auth));
100        self
101    }
102
103    #[cfg(feature = "seaorm")]
104    pub fn seaorm_auth(mut self, auth: crate::adapters::seaorm_auth::SeaOrmAdminAuth) -> Self {
105        let arc = std::sync::Arc::new(auth);
106        self.enforcer = Some(arc.enforcer());
107        self.auth = Some(arc.clone() as std::sync::Arc<dyn crate::auth::AdminAuth>);
108        self.seaorm_auth = Some(arc);
109        self
110    }
111
112    /// Override or add a template by name. The name must match what the router
113    /// uses (e.g. `"home.html"`, `"layout.html"`, `"form.html"`).
114    /// Custom templates can also be added and rendered via custom routes.
115    pub fn template(mut self, name: &str, content: &str) -> Self {
116        self.templates.push((name.to_string(), content.to_string()));
117        self
118    }
119
120    /// Load templates from a directory on disk. Any `.html` file whose name
121    /// matches a built-in template will override it; unknown names are added
122    /// as new templates. Files are loaded at startup — no runtime reloading.
123    ///
124    /// Multiple directories can be registered; later calls take precedence.
125    pub fn template_dir(mut self, path: impl Into<PathBuf>) -> Self {
126        self.template_dirs.push(path.into());
127        self
128    }
129
130    pub(crate) fn into_state(self) -> (Arc<dyn AdminAuth>, Arc<AdminAppState>, usize) {
131        let auth = self
132            .auth
133            .expect("AdminApp requires .auth() to be configured before calling into_router()");
134
135        // Collect directory templates before inline overrides so that
136        // .template() always wins over .template_dir().
137        let mut all_templates: Vec<(String, String)> = Vec::new();
138        for dir in &self.template_dirs {
139            if let Ok(entries) = std::fs::read_dir(dir) {
140                for entry in entries.flatten() {
141                    let path = entry.path();
142                    if path.extension().and_then(|e| e.to_str()) == Some("html") {
143                        if let (Some(name), Ok(content)) = (
144                            path.file_name().and_then(|n| n.to_str()).map(str::to_string),
145                            std::fs::read_to_string(&path),
146                        ) {
147                            all_templates.push((name, content));
148                        }
149                    }
150                }
151            }
152        }
153        all_templates.extend(self.templates);
154
155        let upload_limit = self.upload_limit;
156        let show_auth_nav = {
157            #[cfg(feature = "seaorm")]
158            { self.seaorm_auth.is_some() }
159            #[cfg(not(feature = "seaorm"))]
160            { false }
161        };
162        let state = Arc::new(AdminAppState {
163            title: self.title,
164            icon: self.icon,
165            entities: self.entities,
166            renderer: AdminRenderer::with_overrides(all_templates),
167            enforcer: self.enforcer,
168            #[cfg(feature = "seaorm")]
169            seaorm_auth: self.seaorm_auth,
170            show_auth_nav,
171        });
172        (auth, state, upload_limit)
173    }
174}
175
176impl Default for AdminApp {
177    fn default() -> Self {
178        Self::new()
179    }
180}