changeset 56:649097da34ae

Surface recent sessions in UI
author Lewin Bormann <lbo@spheniscida.de>
date Fri, 22 Jul 2022 19:31:57 -0700
parents ced9006f0f91
children ad0625965942
files assets/index_dashboard.html.hbs assets/static/style.css src/logsdb.rs src/main.rs
diffstat 4 files changed, 130 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/assets/index_dashboard.html.hbs	Fri Jul 22 18:41:04 2022 -0700
+++ b/assets/index_dashboard.html.hbs	Fri Jul 22 19:31:57 2022 -0700
@@ -99,8 +99,8 @@
     <!-- Plots -- only shown when logged in. -->
     <div class="plotrow row1">
         <div class="plotframe fullwidth">
-        <div class="plottitle">Visits and Sessions</div>
-        <div class="chartcontainer1"><canvas id="visitsAndSessions" height="100"></canvas></div>
+          <div class="plottitle">Visits and Sessions</div>
+          <div class="chartcontainer1"><canvas id="visitsAndSessions" height="100"></canvas></div>
         </div>
     </div>
     <div class="plotrow row2">
@@ -115,6 +115,20 @@
         <div class="plotframe halfwidth">Top External Referers
             <div class="chartcontainer2"><canvas id="topRefer"></canvas></div> </div>
     </div>
+    <div class="plotrow row3">
+      <div class="plotframe fullwidth">Recent Sessions
+        <table align="center" id="sessionstable">
+          <tr>
+            <th>Start</th><th>Duration</th><th>Requests</th><th>Referer</th><th>Country/City</th><th>User Agent</th>
+          </tr>
+          {{ #each recentSessions }}
+          <tr>
+            <td>{{ start }}</td><td>{{ duration }}</td><td>{{ count }}</td><td>{{ refer }}</td><td>{{ origin_country }} {{ origin_city }}</td><td>{{ ua }}</td>
+          </tr>
+          {{ /each }}
+        </table>
+      </div>
+    </div>
 
     <script>
         let plots = {"visitsAndSessions": {{{ chartconfig.visitsAndSessions }}},
--- a/assets/static/style.css	Fri Jul 22 18:41:04 2022 -0700
+++ b/assets/static/style.css	Fri Jul 22 19:31:57 2022 -0700
@@ -27,6 +27,9 @@
 #botbox { position: relative; vertical-align: top; height: 80%; }
 #botboxlabel { position: relative; vertical-align: middle; }
 
+#sessionstable tr, td { border: 1px dotted gray; }
+#sessionstable tr td { text-align: left; }
+
 /* Login form */
 #loginformbox {  display: flex; justify-content: center; }
 #loginform { }
--- a/src/logsdb.rs	Fri Jul 22 18:41:04 2022 -0700
+++ b/src/logsdb.rs	Fri Jul 22 19:31:57 2022 -0700
@@ -6,6 +6,7 @@
 
 use rocket::futures::{future::ready, StreamExt};
 use rocket::http::hyper::uri::Uri;
+use rocket::serde::Serialize;
 use rocket_db_pools::sqlx::{Executor, Row, Sqlite, SqlitePool};
 use rocket_db_pools::{Connection, Database, Pool};
 use sqlx::prelude::FromRow;
@@ -26,6 +27,48 @@
     pub include_bots: bool,
 }
 
+#[derive(sqlx::FromRow, Serialize)]
+#[serde(crate = "rocket::serde")]
+pub struct RecentSessionsRow {
+    start: i64,
+    duration: i64,
+    count: i64,
+    refer: Option<String>,
+    origin_country: Option<String>,
+    origin_city: Option<String>,
+    ua: String,
+}
+
+#[derive(Serialize)]
+#[serde(crate = "rocket::serde")]
+pub struct RecentSessionsTableRow {
+    start: String,
+    duration: String,
+    count: i64,
+    refer: Option<String>,
+    origin_country: Option<String>,
+    origin_city: Option<String>,
+    ua: String,
+}
+
+impl RecentSessionsTableRow {
+    pub fn from_row(r: RecentSessionsRow, off: time::UtcOffset) -> RecentSessionsTableRow {
+        RecentSessionsTableRow {
+            start: time::OffsetDateTime::from_unix_timestamp(r.start)
+                .unwrap_or(time::OffsetDateTime::UNIX_EPOCH)
+                .to_offset(off)
+                .format(&time::format_description::well_known::Iso8601::DEFAULT)
+                .unwrap_or(String::new()),
+            duration: format!("{:.0}", time::Duration::seconds(r.duration)),
+            count: r.count,
+            refer: r.refer,
+            origin_country: r.origin_country,
+            origin_city: r.origin_city,
+            ua: r.ua,
+        }
+    }
+}
+
 impl<'p> LogsDBSession<'p, Sqlite> {
     pub async fn log_request<
         S1: AsRef<str>,
@@ -358,4 +401,49 @@
         by_origin_vec.sort_by_key(|(_, v)| -*v);
         Ok(by_origin_vec)
     }
+
+    pub async fn query_recent_sessions(
+        &mut self,
+        ctx: &LogsQueryContext,
+        n: i64,
+    ) -> Result<Vec<RecentSessionsRow>, Error> {
+        let include_bots = if ctx.include_bots { 1 } else { 0 };
+        let result = sqlx::query(
+            r#"
+SELECT Sessions.start AS start,
+    Sessions.last-Sessions.start AS duration,
+    COUNT(*) AS count,
+    RequestLog.Refer AS refer,
+    Sessions.origin_country AS origin_country,
+    Sessions.origin_city AS origin_city,
+    RequestLog.ua AS ua
+FROM RequestLog
+JOIN Sessions ON (RequestLog.session = Sessions.id)
+WHERE Sessions.is_bot <= ?
+GROUP BY Sessions.id
+ORDER BY Sessions.start DESC, RequestLog.atime DESC
+LIMIT ?;"#,
+        )
+        .bind(include_bots)
+        .bind(n)
+        .fetch(&mut *self.0)
+        .take_while(|r| {
+            if let Err(e) = r {
+                error!("Error fetching in query_last_visits: {}", e);
+            }
+            ready(r.is_ok())
+        })
+        .map(Result::unwrap)
+        .map(|r| RecentSessionsRow::from_row(&r))
+        .take_while(|r| {
+            if let Err(e) = r {
+                error!("Error parsing row in query_last_visits: {}", e);
+            }
+            ready(r.is_ok())
+        })
+        .map(Result::unwrap)
+        .collect::<Vec<RecentSessionsRow>>()
+        .await;
+        Ok(result)
+    }
 }
--- a/src/main.rs	Fri Jul 22 18:41:04 2022 -0700
+++ b/src/main.rs	Fri Jul 22 19:31:57 2022 -0700
@@ -337,6 +337,26 @@
             "undefined".to_string()
         }
     };
+    let recent_sessions = match LogsDBSession(&mut conn)
+        .query_recent_sessions(&ctx, 15)
+        .await
+    {
+        Ok(rs) => Some(
+            rs.into_iter()
+                .map(|r| {
+                    logsdb::RecentSessionsTableRow::from_row(
+                        r,
+                        time::UtcOffset::from_whole_seconds(tz_offset as i32)
+                            .unwrap_or(time::UtcOffset::UTC),
+                    )
+                })
+                .collect::<Vec<logsdb::RecentSessionsTableRow>>(),
+        ),
+        Err(e) => {
+            error!("Couldn't query recent sessions: {}", e);
+            None
+        }
+    };
 
     let ymd_format = time::format_description::parse("[year]-[month]-[day]").unwrap();
     let tmpl_today = begin.date().format(&ymd_format).unwrap();
@@ -358,7 +378,9 @@
             topPaths: toppaths,
             requestsBySession: reqbyses,
             sessionsByCountry: sesbycountry,
-            topRefer: toprefer, ]
+            topRefer: toprefer,
+        ],
+        recentSessions: recent_sessions,
         ],
     )
 }