Add analytics drill-down, visitor logs, exports and pagination
This commit is contained in:
1 parent
0e111d61ff
commit
84c48fa5c1
16 files changed
+1982
-8
No files matched your search
@@ -0,0 +1,108 @@
|
||||
use crate::models::{LandingPage, Url};
|
||||
use askama::Template;
|
||||
use axum::{
|
||||
http::StatusCode,
|
||||
response::{Html, IntoResponse, Response},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct VisitorLogEntry {
|
||||
pub sr: usize,
|
||||
pub timestamp: String,
|
||||
pub ip_address: String,
|
||||
pub country: String,
|
||||
pub referrer: String,
|
||||
pub browser: String,
|
||||
pub user_agent: String,
|
||||
pub utm_source: String,
|
||||
pub utm_campaign: String,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "url_analytics.html")]
|
||||
pub struct UrlAnalyticsTemplate {
|
||||
pub admin_username: String,
|
||||
pub url: Url,
|
||||
pub total_clicks: i64,
|
||||
pub unique_visitors: i64,
|
||||
pub qr_scans: i64,
|
||||
pub direct_clicks: i64,
|
||||
pub traffic_chart: String,
|
||||
pub monthly_chart: String,
|
||||
pub countries_chart: String,
|
||||
pub referrers_chart: String,
|
||||
pub browsers_chart: String,
|
||||
// Paginated visitor logs
|
||||
pub visits: Vec<VisitorLogEntry>,
|
||||
pub current_page: usize,
|
||||
pub total_pages: usize,
|
||||
pub visible_pages: Vec<usize>,
|
||||
pub total_records: i64,
|
||||
pub page_start: usize,
|
||||
pub page_end: usize,
|
||||
pub date_from: Option<String>,
|
||||
pub date_to: Option<String>,
|
||||
}
|
||||
|
||||
impl UrlAnalyticsTemplate {
|
||||
pub fn is_current(&self, page: &usize) -> bool {
|
||||
*page == self.current_page
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl IntoResponse for UrlAnalyticsTemplate {
|
||||
fn into_response(self) -> Response {
|
||||
match self.render() {
|
||||
Ok(html) => Html(html).into_response(),
|
||||
Err(e) => (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Render error: {}", e),
|
||||
)
|
||||
.into_response(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "page_analytics.html")]
|
||||
pub struct PageAnalyticsTemplate {
|
||||
pub admin_username: String,
|
||||
pub page: LandingPage,
|
||||
pub total_views: i64,
|
||||
pub unique_visitors: i64,
|
||||
pub traffic_chart: String,
|
||||
pub monthly_chart: String,
|
||||
pub countries_chart: String,
|
||||
pub referrers_chart: String,
|
||||
// Paginated visitor logs
|
||||
pub visits: Vec<VisitorLogEntry>,
|
||||
pub current_page: usize,
|
||||
pub total_pages: usize,
|
||||
pub visible_pages: Vec<usize>,
|
||||
pub total_records: i64,
|
||||
pub page_start: usize,
|
||||
pub page_end: usize,
|
||||
pub date_from: Option<String>,
|
||||
pub date_to: Option<String>,
|
||||
}
|
||||
|
||||
impl PageAnalyticsTemplate {
|
||||
pub fn is_current(&self, page: &usize) -> bool {
|
||||
*page == self.current_page
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl IntoResponse for PageAnalyticsTemplate {
|
||||
fn into_response(self) -> Response {
|
||||
match self.render() {
|
||||
Ok(html) => Html(html).into_response(),
|
||||
Err(e) => (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Render error: {}", e),
|
||||
)
|
||||
.into_response(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
pub mod analytics;
|
||||
pub mod dashboard;
|
||||
pub mod pages;
|
||||
pub mod settings;
|
||||
pub mod stats;
|
||||
pub mod urls;
|
||||
|
||||
pub use analytics::{PageAnalyticsTemplate, UrlAnalyticsTemplate, VisitorLogEntry};
|
||||
pub use dashboard::DashboardTemplate;
|
||||
pub use pages::PagesTemplate;
|
||||
pub use settings::SettingsTemplate;
|
||||
|
||||
@@ -12,8 +12,18 @@ pub struct PagesTemplate {
|
||||
pub pages: Vec<LandingPage>,
|
||||
pub csrf_token: String,
|
||||
pub error: Option<String>,
|
||||
pub current_page: usize,
|
||||
pub total_pages: usize,
|
||||
pub visible_pages: Vec<usize>,
|
||||
}
|
||||
|
||||
impl PagesTemplate {
|
||||
pub fn is_current(&self, page: &usize) -> bool {
|
||||
*page == self.current_page
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl IntoResponse for PagesTemplate {
|
||||
fn into_response(self) -> Response {
|
||||
match self.render() {
|
||||
|
||||
@@ -14,8 +14,18 @@ pub struct UrlsTemplate {
|
||||
pub error: Option<String>,
|
||||
pub tag_filter: Option<String>,
|
||||
pub base_url: String,
|
||||
pub current_page: usize,
|
||||
pub total_pages: usize,
|
||||
pub visible_pages: Vec<usize>,
|
||||
}
|
||||
|
||||
impl UrlsTemplate {
|
||||
pub fn is_current(&self, page: &usize) -> bool {
|
||||
*page == self.current_page
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl IntoResponse for UrlsTemplate {
|
||||
fn into_response(self) -> Response {
|
||||
match self.render() {
|
||||
|
||||
Reference in new issue
Block a user