Mercurial > lbo > hg > analyrics
changeset 30:24f2baa54870
Show top pages
author | Lewin Bormann <lbo@spheniscida.de> |
---|---|
date | Fri, 15 Jul 2022 08:19:13 -0700 |
parents | 8ad244c1b061 |
children | c25ab9d90bb2 |
files | assets/index.html.hbs src/logsdb.rs src/main.rs |
diffstat | 3 files changed, 94 insertions(+), 34 deletions(-) [+] |
line wrap: on
line diff
--- a/assets/index.html.hbs Thu Jul 14 22:37:16 2022 -0700 +++ b/assets/index.html.hbs Fri Jul 15 08:19:13 2022 -0700 @@ -54,14 +54,17 @@ </div> </div> <div class="plotrow row2"> - <div class="plotframe halfwidth"> </div> + <div class="plotframe halfwidth"><canvas id="topPaths" height="100"></canvas> </div> <div class="plotframe halfwidth"> </div> </div> <script> - let plots = {"visitsAndSessions": {{{ chartconfig.visitsAndSessions }}} }; + let plots = {"visitsAndSessions": {{{ chartconfig.visitsAndSessions }}}, "topPaths": {{{ chartconfig.topPaths }}} }; Object.keys(plots).forEach((cv) => { + if (plots[cv] == undefined) { + return; + } const ctx = document.getElementById(cv).getContext('2d'); const myChart = new Chart(ctx, plots[cv]); });
--- a/src/logsdb.rs Thu Jul 14 22:37:16 2022 -0700 +++ b/src/logsdb.rs Fri Jul 15 08:19:13 2022 -0700 @@ -4,7 +4,7 @@ use log::{debug, error, info, warn, Level}; use time::{Duration, OffsetDateTime}; -use rocket::futures::StreamExt; +use rocket::futures::{future::ready, StreamExt}; use rocket_db_pools::sqlx::{Executor, Row, Sqlite, SqlitePool}; use rocket_db_pools::{Connection, Database, Pool}; use sqlx::prelude::FromRow; @@ -140,4 +140,38 @@ Ok((dates, visits, sessions)) } + + pub async fn query_top_paths( + &mut self, + from: OffsetDateTime, + to: OffsetDateTime, + tz_offset: Option<i64>, + domainpattern: Option<&str>, + n: i64, + ) -> Result<Vec<(String, i64)>, Error> { + let tz_offset = tz_offset.unwrap_or(0); + let result = sqlx::query( + r#" +SELECT path, COUNT(*) AS pathcount +FROM RequestLog +WHERE atime+? > ? AND atime+? < ? AND domain LIKE ? +GROUP BY path +ORDER BY pathcount DESC +LIMIT ?;"#, + ) + .bind(tz_offset) + .bind(from.unix_timestamp()) + .bind(tz_offset) + .bind(to.unix_timestamp()) + .bind(domainpattern.unwrap_or("%")) + .bind(n) + .fetch(&mut *self.0); + + Ok(result + .map(|r| r.map(|e| (e.get(0), e.get(1)))) + .filter(|e| ready(e.is_ok())) + .map(|e| e.unwrap()) + .collect() + .await) + } }
--- a/src/main.rs Thu Jul 14 22:37:16 2022 -0700 +++ b/src/main.rs Fri Jul 15 08:19:13 2022 -0700 @@ -119,10 +119,17 @@ } } +#[derive(Default)] +struct ChartOptions { + typ: String, // bar/line/etc. + indexAxis: Option<String>, // x/y + stack: Option<String>, +} + fn create_chart<S1: Serialize, S2: Serialize>( labels: Vec<S1>, values: Vec<(String, Vec<S2>)>, - typ: String, + opt: &ChartOptions, ) -> Value { let colors = vec![ "red", "blue", "orange", "green", "gray", "purple", "black", "brown", @@ -133,16 +140,25 @@ let datasets: Vec<Value> = values .iter() .zip(colors) - .map(|((name, val), col)| json!({"label": name, "data": val, "borderColor": col})) + .map(|((name, val), col)| { + json!({ + "label": name, + "data": val, + "borderColor": col, + "backgroundColor": col, + }) + }) .collect(); let inner = json!({ - "type": typ, + "type": &opt.typ, "data": { "labels": labels, "datasets": datasets, }, "options": { "scales": { "y": { "beginAtZero": true }}, + "indexAxis": opt.indexAxis, + "stack": opt.stack.as_ref().map(String::as_str), }, }); inner @@ -213,7 +229,7 @@ 0 } }; - match LogsDBSession(&mut conn) + let vissess = match LogsDBSession(&mut conn) .query_visits_sessions_counts(begin, end, Some(tz_offset), domain) .await { @@ -222,27 +238,43 @@ "Successfully queried visits/sessions: {:?}, {:?}", visits, sessions ); - let vissess = create_chart( + create_chart( dates, vec![("Visits".into(), visits), ("Sessions".into(), sessions)], - "line".into(), + &ChartOptions { + typ: "line".into(), + ..ChartOptions::default() + }, ) - .to_string(); - Template::render( - "index", - context![ - loggedin: true, - domain: domain, - username: lig.0, - flash: f, - chartconfig: context![ visitsAndSessions: vissess ]], - ) + .to_string() } - Err(e) => Template::render( - "index", - context![loggedin: true, domain: domain, username: lig.0, flash: f, chartconfig: context![], error: format!("{:?}", e)], - ), - } + Err(e) => "undefined".to_string(), //return Template::render("index", context![loggedin: true, domain: domain, username: lig.0, flash: f, chartconfig: context![], error: format!("{:?}", e)],), + }; + let toppaths = match LogsDBSession(&mut conn) + .query_top_paths(begin, end, Some(tz_offset), domain, 10) + .await + { + Ok(tp) => create_chart( + tp.iter().map(|(p, c)| p).collect(), + vec![("Top Pages".into(), tp.iter().map(|(p, c)| c).collect())], + &ChartOptions { + typ: "bar".into(), + indexAxis: Some("y".into()), + ..ChartOptions::default() + }, + ) + .to_string(), + Err(e) => "undefined".to_string(), + }; + Template::render( + "index", + context![ + loggedin: true, + domain: domain, + username: lig.0, + flash: f, + chartconfig: context![ visitsAndSessions: vissess, topPaths: toppaths ]], + ) } #[rocket::get("/", rank = 2)] @@ -310,7 +342,7 @@ .0 .get("host") .take(1) - .map(|s| s.to_string()) + .map(str::to_string) .collect::<Vec<String>>() .pop() .unwrap_or(String::new()), @@ -343,14 +375,6 @@ } } -#[rocket::get("/testchart")] -async fn route_testchart() -> Template { - Template::render( - "testchart", - context![ chartconfig: create_chart(vec!["1", "2", "3", "4"], vec![("Series A".into(), vec![1,4,3,8]), ("Series B".into(), vec![5,2,3,9])], "line".into()).to_string() ], - ) -} - #[derive(rocket::serde::Deserialize)] #[serde(crate = "rocket::serde")] struct CustomConfig { @@ -389,7 +413,6 @@ route_login_form, route_login_post, route_log, - route_testchart, ], ); r