change index to templates using handlebars

using a handlebars instance in ApiConfig, to cache the templates
as long as possible, this is currently ok, as the index template
can only change when the whole package changes

if we split this in the future, we have to trigger a reload of
the daemon on gui package upgrade (so that the template gets reloaded)

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
This commit is contained in:
Dominik Csapak 2020-04-29 11:59:31 +02:00 committed by Dietmar Maurer
parent d9d3da2b68
commit d0815e5e71
2 changed files with 38 additions and 46 deletions

View File

@ -1,7 +1,9 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::path::{PathBuf}; use std::path::{PathBuf};
use anyhow::Error;
use hyper::Method; use hyper::Method;
use handlebars::Handlebars;
use proxmox::api::{ApiMethod, Router, RpcEnvironmentType}; use proxmox::api::{ApiMethod, Router, RpcEnvironmentType};
@ -10,17 +12,22 @@ pub struct ApiConfig {
router: &'static Router, router: &'static Router,
aliases: HashMap<String, PathBuf>, aliases: HashMap<String, PathBuf>,
env_type: RpcEnvironmentType, env_type: RpcEnvironmentType,
pub templates: Handlebars<'static>,
} }
impl ApiConfig { impl ApiConfig {
pub fn new<B: Into<PathBuf>>(basedir: B, router: &'static Router, env_type: RpcEnvironmentType) -> Self { pub fn new<B: Into<PathBuf>>(basedir: B, router: &'static Router, env_type: RpcEnvironmentType) -> Result<Self, Error> {
Self { let mut templates = Handlebars::new();
basedir: basedir.into(), let basedir = basedir.into();
templates.register_template_file("index", basedir.join("index.hbs"))?;
Ok(Self {
basedir,
router, router,
aliases: HashMap::new(), aliases: HashMap::new(),
env_type, env_type,
} templates
})
} }
pub fn find_method( pub fn find_method(

View File

@ -16,6 +16,7 @@ use serde_json::{json, Value};
use tokio::fs::File; use tokio::fs::File;
use tokio::time::Instant; use tokio::time::Instant;
use url::form_urlencoded; use url::form_urlencoded;
use handlebars::Handlebars;
use proxmox::http_err; use proxmox::http_err;
use proxmox::api::{ApiHandler, ApiMethod, HttpError}; use proxmox::api::{ApiHandler, ApiMethod, HttpError};
@ -311,59 +312,43 @@ pub async fn handle_api_request<Env: RpcEnvironment, S: 'static + BuildHasher +
Ok(resp) Ok(resp)
} }
fn get_index(username: Option<String>, token: Option<String>) -> Response<Body> { fn get_index(username: Option<String>, token: Option<String>, template: &Handlebars, parts: Parts) -> Response<Body> {
let nodename = proxmox::tools::nodename(); let nodename = proxmox::tools::nodename();
let username = username.unwrap_or_else(|| String::from("")); let username = username.unwrap_or_else(|| String::from(""));
let token = token.unwrap_or_else(|| String::from("")); let token = token.unwrap_or_else(|| String::from(""));
let setup = json!({ let mut debug = false;
"Setup": { "auth_cookie_name": "PBSAuthCookie" },
if let Some(query_str) = parts.uri.query() {
for (k, v) in form_urlencoded::parse(query_str.as_bytes()).into_owned() {
if k == "debug" && v == "1" || v == "true" {
debug = true;
}
}
}
let data = json!({
"NodeName": nodename, "NodeName": nodename,
"UserName": username, "UserName": username,
"CSRFPreventionToken": token, "CSRFPreventionToken": token,
"debug": debug,
}); });
let index = format!(r###" let mut ct = "text/html";
<!DOCTYPE html>
<html> let index = match template.render("index", &data) {
<head> Ok(index) => index,
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> Err(err) => {
<meta http-equiv="X-UA-Compatible" content="IE=edge"> ct = "text/plain";
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> format!("Error rendering template: {}", err.desc)
<title>Proxmox Backup Server</title> },
<link rel="icon" sizes="128x128" href="/images/logo-128.png" /> };
<link rel="apple-touch-icon" sizes="128x128" href="/pve2/images/logo-128.png" />
<link rel="stylesheet" type="text/css" href="/extjs/theme-crisp/resources/theme-crisp-all.css" />
<link rel="stylesheet" type="text/css" href="/extjs/crisp/resources/charts-all.css" />
<link rel="stylesheet" type="text/css" href="/fontawesome/css/font-awesome.css" />
<link rel="stylesheet" type="text/css" href="/css/ext6-pbs.css" />
<script type='text/javascript'> function gettext(buf) {{ return buf; }} </script>
<script type="text/javascript" src="/extjs/ext-all-debug.js"></script>
<script type="text/javascript" src="/extjs/charts-debug.js"></script>
<script type="text/javascript">
Proxmox = {};
</script>
<script type="text/javascript" src="/widgettoolkit/proxmoxlib.js"></script>
<script type="text/javascript" src="/extjs/locale/locale-en.js"></script>
<script type="text/javascript">
Ext.History.fieldid = 'x-history-field';
</script>
<script type="text/javascript" src="/js/proxmox-backup-gui.js"></script>
</head>
<body>
<!-- Fields required for history management -->
<form id="history-form" class="x-hidden">
<input type="hidden" id="x-history-field"/>
</form>
</body>
</html>
"###, setup.to_string());
Response::builder() Response::builder()
.status(StatusCode::OK) .status(StatusCode::OK)
.header(header::CONTENT_TYPE, "text/html") .header(header::CONTENT_TYPE, ct)
.body(index.into()) .body(index.into())
.unwrap() .unwrap()
} }
@ -595,15 +580,15 @@ pub async fn handle_request(api: Arc<ApiConfig>, req: Request<Body>) -> Result<R
match check_auth(&method, &ticket, &token, &user_info) { match check_auth(&method, &ticket, &token, &user_info) {
Ok(username) => { Ok(username) => {
let new_token = assemble_csrf_prevention_token(csrf_secret(), &username); let new_token = assemble_csrf_prevention_token(csrf_secret(), &username);
return Ok(get_index(Some(username), Some(new_token))); return Ok(get_index(Some(username), Some(new_token), &api.templates, parts));
} }
_ => { _ => {
tokio::time::delay_until(Instant::from_std(delay_unauth_time)).await; tokio::time::delay_until(Instant::from_std(delay_unauth_time)).await;
return Ok(get_index(None, None)); return Ok(get_index(None, None, &api.templates, parts));
} }
} }
} else { } else {
return Ok(get_index(None, None)); return Ok(get_index(None, None, &api.templates, parts));
} }
} else { } else {
let filename = api.find_alias(&components); let filename = api.find_alias(&components);