1use crate::error::CoreError;
8use crate::registry::get_registered_menus;
9use crate::request::ReqCtx;
10use crate::response::ApiResponse;
11use lazy_static::lazy_static;
12use serde_json::{json, Map, Value};
13use std::collections::HashMap;
14use tera::{Context, Tera};
15
16lazy_static! {
17 static ref TEMPLATES: Tera = {
18 let mut tera = Tera::default();
19 tera.add_raw_templates(vec![
20 ("layout.html", include_str!("templates/layout.html.tera")),
21 ("header.html", include_str!("templates/header.html.tera")),
22 ("footer.html", include_str!("templates/footer.html.tera")),
23 ("dashboard.html", include_str!("templates/dashboard.html.tera")),
24 ("list.html", include_str!("templates/list.html.tera")),
25 ("form.html", include_str!("templates/form.html.tera")),
26 ("view.html", include_str!("templates/view.html.tera")),
27 ("login.html", include_str!("templates/login.html.tera")),
28 ("mfa_setup.html", include_str!("templates/mfa_setup.html.tera")),
29 ("mfa_backup.html", include_str!("templates/mfa_backup.html.tera")),
30 ("mfa_verify.html", include_str!("templates/mfa_verify.html.tera")),
31 ])
32 .expect("adminx: failed to parse embedded templates");
33 tera.autoescape_on(vec![".html"]);
34 tera
35 };
36}
37
38pub fn render(name: &str, ctx: &Context) -> ApiResponse {
40 match TEMPLATES.render(name, ctx) {
41 Ok(html) => ApiResponse::html(200, html),
42 Err(e) => {
43 tracing::error!("adminx template render error [{name}]: {e}");
44 ApiResponse::error(CoreError::Internal(format!("template error: {e}")))
45 }
46 }
47}
48
49pub fn render_with_csrf(ctx: &ReqCtx, mut context: Context, template: &str) -> ApiResponse {
54 let (token, cookie) = crate::csrf::ensure(ctx);
55 context.insert("csrf_token", &token);
56 let resp = render(template, &context);
57 match cookie {
58 Some(v) => resp.with_header("Set-Cookie", v),
59 None => resp,
60 }
61}
62
63pub fn base_context(ctx: &ReqCtx, title: &str) -> Context {
65 let mut c = Context::new();
66 c.insert("title", title);
67 c.insert("mount", &ctx.mount);
68 c.insert("menus", &get_registered_menus());
69 c.insert("is_authenticated", &ctx.claims.is_some());
70 c
71}
72
73pub fn dashboard(ctx: &ReqCtx) -> ApiResponse {
75 let c = base_context(ctx, "Dashboard");
76 render("dashboard.html", &c)
77}
78
79pub fn derive_headers(rows: &[Value], pk: &str) -> Vec<String> {
82 let mut headers: Vec<String> = Vec::new();
83 if let Some(Value::Object(first)) = rows.first() {
84 if first.contains_key(pk) {
85 headers.push(pk.to_string());
86 }
87 for k in first.keys() {
88 if k != pk {
89 headers.push(k.clone());
90 }
91 }
92 }
93 headers
94}
95
96pub fn default_fields(columns: &[&str]) -> Vec<Value> {
99 columns
100 .iter()
101 .map(|name| {
102 let field_type = if *name == "deleted" { "checkbox" } else { "text" };
103 json!({
104 "name": name,
105 "label": humanize(name),
106 "field_type": field_type,
107 })
108 })
109 .collect()
110}
111
112pub fn fields_from_structure(structure: &Value) -> Vec<Value> {
115 if let Some(groups) = structure.get("groups").and_then(|g| g.as_array()) {
116 return groups
117 .iter()
118 .filter_map(|g| g.get("fields").and_then(|f| f.as_array()))
119 .flatten()
120 .cloned()
121 .collect();
122 }
123 if let Some(fields) = structure.get("fields").and_then(|f| f.as_array()) {
124 return fields.clone();
125 }
126 Vec::new()
127}
128
129pub fn form_to_json(form: HashMap<String, String>) -> Value {
133 let mut map = Map::new();
134 for (k, v) in form {
135 let value = if v == "true" {
136 Value::Bool(true)
137 } else if v == "false" {
138 Value::Bool(false)
139 } else if let Ok(i) = v.parse::<i64>() {
140 Value::from(i)
141 } else if let Ok(f) = v.parse::<f64>() {
142 Value::from(f)
143 } else {
144 Value::String(v)
145 };
146 map.insert(k, value);
147 }
148 Value::Object(map)
149}
150
151pub fn humanize(name: &str) -> String {
153 name.split('_')
154 .map(|w| {
155 let mut chars = w.chars();
156 match chars.next() {
157 Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
158 None => String::new(),
159 }
160 })
161 .collect::<Vec<_>>()
162 .join(" ")
163}