feat: enhance configuration and templates for HTML output
This commit is contained in:
Vendored
+7
-6
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"1yib.rust-bundle",
|
||||
"streetsidesoftware.code-spell-checker",
|
||||
"esbenp.prettier-vscode"
|
||||
]
|
||||
}
|
||||
"recommendations": [
|
||||
"1yib.rust-bundle",
|
||||
"streetsidesoftware.code-spell-checker",
|
||||
"esbenp.prettier-vscode",
|
||||
"karunamurti.tera"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -73,14 +73,14 @@ Pass `--verbose` if you want the CLI to print the selected configuration file pa
|
||||
Configure at least these values:
|
||||
|
||||
- `digitemp` and `digitemp_config`
|
||||
- `database_dir` and `images_dir`
|
||||
- `templates_dir`
|
||||
- `database_dir`, `html_dir`, and `images_dir`
|
||||
- `templates_dir` and `html_template`
|
||||
- `room_name`
|
||||
- SMTP server settings
|
||||
- mail recipients, subjects, and alert thresholds
|
||||
|
||||
`room_name` is required. It is used in mail templates, graph titles, and the generated top page.
|
||||
The `update` command also regenerates `html/index.html` from the template so the page title and heading stay aligned with `room_name`.
|
||||
The `update` command renders `html_template` and writes it under `html_dir` using the same relative path with the `.tera` suffix removed. For example, `index.html.tera` becomes `html/index.html`.
|
||||
|
||||
SMTP settings support these combinations:
|
||||
|
||||
@@ -140,7 +140,8 @@ See `etc/crontab.sample` for a sample schedule.
|
||||
|
||||
## Templates
|
||||
|
||||
Mail templates are stored in `templates/` and rendered with [Tera](https://tera.netlify.app/).
|
||||
Templates are stored in `templates/` and rendered with [Tera](https://tera.netlify.app/).
|
||||
The top page uses `html_template`, while the mail bodies use `mail_info_template` and `mail_alert_template`.
|
||||
|
||||
## Development Notes
|
||||
|
||||
|
||||
@@ -15,14 +15,21 @@ fn default_mail_smtp_auth() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn default_html_template() -> String {
|
||||
"index.html.tera".to_string()
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Config {
|
||||
pub url: String,
|
||||
pub digitemp: String,
|
||||
pub digitemp_config: String,
|
||||
pub database_dir: String,
|
||||
pub html_dir: String,
|
||||
pub images_dir: String,
|
||||
pub templates_dir: String,
|
||||
#[serde(default = "default_html_template")]
|
||||
pub html_template: String,
|
||||
pub room_name: String,
|
||||
pub mail_alert_upper: f64,
|
||||
pub mail_alert_lower: f64,
|
||||
@@ -57,6 +64,46 @@ mod tests {
|
||||
"digitemp": "/bin/digitemp_DS9097U",
|
||||
"digitemp_config": "etc/digitemp.conf",
|
||||
"database_dir": "db",
|
||||
"html_dir": "html",
|
||||
"images_dir": "html/images",
|
||||
"templates_dir": "templates",
|
||||
"html_template": "index.html.tera",
|
||||
"room_name": "S108",
|
||||
"mail_alert_upper": 30,
|
||||
"mail_alert_lower": 17,
|
||||
"mail_from": "from@example.com",
|
||||
"mail_from_name": "Temperature Monitor",
|
||||
"mail_info_to": "info@example.com",
|
||||
"mail_info_subject": "Info",
|
||||
"mail_info_template": "mail_info.tera",
|
||||
"mail_alert_to": "alert@example.com",
|
||||
"mail_alert_subject": "Alert",
|
||||
"mail_alert_template": "mail_alert.tera",
|
||||
"mail_smtp_server": "localhost",
|
||||
"mail_smtp_port": 587,
|
||||
"mail_smtp_username": "user@example.com",
|
||||
"mail_smtp_password": "password"
|
||||
}"#;
|
||||
|
||||
let config: Config = serde_json::from_str(json).unwrap();
|
||||
|
||||
assert!(matches!(
|
||||
config.mail_smtp_security,
|
||||
MailSmtpSecurity::Starttls
|
||||
));
|
||||
assert!(config.mail_smtp_auth);
|
||||
assert_eq!(config.html_template, "index.html.tera");
|
||||
assert_eq!(config.room_name().unwrap(), "S108");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_defaults_html_template() {
|
||||
let json = r#"{
|
||||
"url": "https://example.com/temperature/",
|
||||
"digitemp": "/bin/digitemp_DS9097U",
|
||||
"digitemp_config": "etc/digitemp.conf",
|
||||
"database_dir": "db",
|
||||
"html_dir": "html",
|
||||
"images_dir": "html/images",
|
||||
"templates_dir": "templates",
|
||||
"room_name": "S108",
|
||||
@@ -78,9 +125,7 @@ mod tests {
|
||||
|
||||
let config: Config = serde_json::from_str(json).unwrap();
|
||||
|
||||
assert!(matches!(config.mail_smtp_security, MailSmtpSecurity::Starttls));
|
||||
assert!(config.mail_smtp_auth);
|
||||
assert_eq!(config.room_name().unwrap(), "S108");
|
||||
assert_eq!(config.html_template, "index.html.tera");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -90,8 +135,10 @@ mod tests {
|
||||
"digitemp": "/bin/digitemp_DS9097U",
|
||||
"digitemp_config": "etc/digitemp.conf",
|
||||
"database_dir": "db",
|
||||
"html_dir": "html",
|
||||
"images_dir": "html/images",
|
||||
"templates_dir": "templates",
|
||||
"html_template": "index.html.tera",
|
||||
"room_name": "S108",
|
||||
"mail_alert_upper": 30,
|
||||
"mail_alert_lower": 17,
|
||||
@@ -125,8 +172,10 @@ mod tests {
|
||||
"digitemp": "/bin/digitemp_DS9097U",
|
||||
"digitemp_config": "etc/digitemp.conf",
|
||||
"database_dir": "db",
|
||||
"html_dir": "html",
|
||||
"images_dir": "html/images",
|
||||
"templates_dir": "templates",
|
||||
"html_template": "index.html.tera",
|
||||
"room_name": " ",
|
||||
"mail_alert_upper": 30,
|
||||
"mail_alert_lower": 17,
|
||||
@@ -156,8 +205,10 @@ mod tests {
|
||||
"digitemp": "/bin/digitemp_DS9097U",
|
||||
"digitemp_config": "etc/digitemp.conf",
|
||||
"database_dir": "db",
|
||||
"html_dir": "html",
|
||||
"images_dir": "html/images",
|
||||
"templates_dir": "templates",
|
||||
"html_template": "index.html.tera",
|
||||
"mail_alert_upper": 30,
|
||||
"mail_alert_lower": 17,
|
||||
"mail_from": "from@example.com",
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
"digitemp": "/bin/digitemp_DS9097U",
|
||||
"digitemp_config": "/opt/digitemp-rrdgraph/etc/digitemp.conf",
|
||||
"database_dir": "/opt/digitemp-rrdgraph/db",
|
||||
"html_dir": "/opt/digitemp-rrdgraph/html",
|
||||
"images_dir": "/opt/digitemp-rrdgraph/html/images",
|
||||
"templates_dir": "/opt/digitemp-rrdgraph/templates",
|
||||
"html_template": "index.html.tera",
|
||||
"room_name": "C407",
|
||||
"mail_alert_upper": 30,
|
||||
"mail_alert_lower": 17,
|
||||
|
||||
+13
-5
@@ -7,6 +7,14 @@ use std::fs;
|
||||
use std::path::Path;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
fn output_path_for_template(config: &Config) -> Result<std::path::PathBuf, Box<dyn Error>> {
|
||||
let output_name = config
|
||||
.html_template
|
||||
.strip_suffix(".tera")
|
||||
.ok_or("html_template must end with .tera")?;
|
||||
Ok(Path::new(&config.html_dir).join(output_name))
|
||||
}
|
||||
|
||||
pub fn run(config: &Config) -> Result<(), Box<dyn Error>> {
|
||||
let room_name = config.room_name()?;
|
||||
let digitemp = DigiTemp::new(config.digitemp.clone(), config.digitemp_config.clone())?;
|
||||
@@ -41,11 +49,11 @@ pub fn run(config: &Config) -> Result<(), Box<dyn Error>> {
|
||||
let engine = TemplateEngine::new(&config.templates_dir)?;
|
||||
let mut ctx = TemplateContext::new();
|
||||
ctx.insert("room_name", &room_name);
|
||||
let html = engine.render("index.tera", &ctx)?;
|
||||
let index_path = Path::new(&config.images_dir)
|
||||
.parent()
|
||||
.unwrap_or_else(|| Path::new("html"))
|
||||
.join("index.html");
|
||||
let html = engine.render(&config.html_template, &ctx)?;
|
||||
let index_path = output_path_for_template(config)?;
|
||||
if let Some(parent) = index_path.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
fs::write(index_path, html)?;
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Temperature Monitor - {{ room_name }}</title>
|
||||
<meta name="description" content="Temperature Monitor - {{ room_name }}">
|
||||
<meta name="author" content="Neuroinformatics Unit, RIKEN CBS">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<meta http-equiv="refresh" content="300">
|
||||
<link rel="stylesheet" href="css/default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Temperature Monitor - {{ room_name }}</h1>
|
||||
|
||||
<main>
|
||||
<div class="graph">
|
||||
<div class="range-select">
|
||||
Hour:
|
||||
<span onclick="changeGraph('g1', 'hour', 1)">1</span> ...
|
||||
<span onclick="changeGraph('g1', 'hour', 3)">3</span> ...
|
||||
<span onclick="changeGraph('g1', 'hour', 6)">6</span> ...
|
||||
<span onclick="changeGraph('g1', 'hour', 12)">12</span>
|
||||
</div>
|
||||
<div><img id="g1" src="./images/graph-hour.png" alt="Hourly Graph" /></div>
|
||||
</div>
|
||||
<div class="graph">
|
||||
<div class="range-select">
|
||||
Day:
|
||||
<span onclick="changeGraph('g2', 'day', 1)">1</span> ...
|
||||
<span onclick="changeGraph('g2', 'day', 3)">3</span>
|
||||
</div>
|
||||
<div><img id="g2" src="./images/graph-day.png" alt="Daily Graph" /></div>
|
||||
</div>
|
||||
<div class="graph">
|
||||
<div class="range-select">
|
||||
Week:
|
||||
<span onclick="changeGraph('g3', 'week', 1)">1</span> ...
|
||||
<span onclick="changeGraph('g3', 'week', 2)">2</span>
|
||||
</div>
|
||||
<div><img id="g3" src="./images/graph-week.png" alt="Weekly Graph" /></div>
|
||||
</div>
|
||||
<div class="graph">
|
||||
<div class="range-select">
|
||||
Month:
|
||||
<span onclick="changeGraph('g4', 'month', 1)">1</span> ...
|
||||
<span onclick="changeGraph('g4', 'month', 3)">3</span> ...
|
||||
<span onclick="changeGraph('g4', 'month', 6)">6</span>
|
||||
</div>
|
||||
<div><img id="g4" src="./images/graph-month.png" alt="Monthly Graph" /></div>
|
||||
</div>
|
||||
<div class="graph">
|
||||
<div class="range-select">
|
||||
Year:
|
||||
<span onclick="changeGraph('g5', 'year', 1)">1</span> ...
|
||||
<span onclick="changeGraph('g5', 'year', 3)">3</span> ...
|
||||
<span onclick="changeGraph('g5', 'year', 5)">5</span> ...
|
||||
<span onclick="changeGraph('g5', 'year', 10)">10</span>
|
||||
</div>
|
||||
<div><img id="g5" src="./images/graph-year.png" alt="Yearly Graph" /></div>
|
||||
</div>
|
||||
</main>
|
||||
<script>
|
||||
function changeGraph(id, type, num) {
|
||||
const img = document.getElementById(id);
|
||||
if (img) {
|
||||
img.src = './images/graph-' + (num === 1 ? '' : num) + type + '.png';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,72 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Temperature Monitor - {{ room_name }}</title>
|
||||
<meta name="description" content="Temperature Monitor - {{ room_name }}">
|
||||
<meta name="author" content="Neuroinformatics Unit, RIKEN CBS">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<meta http-equiv="refresh" content="300">
|
||||
<link rel="stylesheet" href="css/default.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Temperature Monitor - {{ room_name }}</h1>
|
||||
|
||||
<main>
|
||||
<div class="graph">
|
||||
<div class="range-select">
|
||||
Hour:
|
||||
<span onclick="changeGraph('g1', 'hour', 1)">1</span> ...
|
||||
<span onclick="changeGraph('g1', 'hour', 3)">3</span> ...
|
||||
<span onclick="changeGraph('g1', 'hour', 6)">6</span> ...
|
||||
<span onclick="changeGraph('g1', 'hour', 12)">12</span>
|
||||
</div>
|
||||
<div><img id="g1" src="./images/graph-hour.png" alt="Hourly Graph" /></div>
|
||||
</div>
|
||||
<div class="graph">
|
||||
<div class="range-select">
|
||||
Day:
|
||||
<span onclick="changeGraph('g2', 'day', 1)">1</span> ...
|
||||
<span onclick="changeGraph('g2', 'day', 3)">3</span>
|
||||
</div>
|
||||
<div><img id="g2" src="./images/graph-day.png" alt="Daily Graph" /></div>
|
||||
</div>
|
||||
<div class="graph">
|
||||
<div class="range-select">
|
||||
Week:
|
||||
<span onclick="changeGraph('g3', 'week', 1)">1</span> ...
|
||||
<span onclick="changeGraph('g3', 'week', 2)">2</span>
|
||||
</div>
|
||||
<div><img id="g3" src="./images/graph-week.png" alt="Weekly Graph" /></div>
|
||||
</div>
|
||||
<div class="graph">
|
||||
<div class="range-select">
|
||||
Month:
|
||||
<span onclick="changeGraph('g4', 'month', 1)">1</span> ...
|
||||
<span onclick="changeGraph('g4', 'month', 3)">3</span> ...
|
||||
<span onclick="changeGraph('g4', 'month', 6)">6</span>
|
||||
</div>
|
||||
<div><img id="g4" src="./images/graph-month.png" alt="Monthly Graph" /></div>
|
||||
</div>
|
||||
<div class="graph">
|
||||
<div class="range-select">
|
||||
Year:
|
||||
<span onclick="changeGraph('g5', 'year', 1)">1</span> ...
|
||||
<span onclick="changeGraph('g5', 'year', 3)">3</span> ...
|
||||
<span onclick="changeGraph('g5', 'year', 5)">5</span> ...
|
||||
<span onclick="changeGraph('g5', 'year', 10)">10</span>
|
||||
</div>
|
||||
<div><img id="g5" src="./images/graph-year.png" alt="Yearly Graph" /></div>
|
||||
</div>
|
||||
</main>
|
||||
<script>
|
||||
function changeGraph(id, type, num) {
|
||||
const img = document.getElementById(id);
|
||||
if (img) {
|
||||
img.src = './images/graph-' + (num === 1 ? '' : num) + type + '.png';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user