jude/react-dashboard #3
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -3,3 +3,5 @@
 | 
				
			|||||||
/venv
 | 
					/venv
 | 
				
			||||||
.cargo
 | 
					.cargo
 | 
				
			||||||
/.idea
 | 
					/.idea
 | 
				
			||||||
 | 
					web/static/index.html
 | 
				
			||||||
 | 
					web/static/assets
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										22
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										22
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							@@ -2051,6 +2051,27 @@ dependencies = [
 | 
				
			|||||||
 "yansi",
 | 
					 "yansi",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "prometheus"
 | 
				
			||||||
 | 
					version = "0.13.3"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "449811d15fbdf5ceb5c1144416066429cf82316e2ec8ce0c1f6f8a02e7bbcf8c"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "cfg-if",
 | 
				
			||||||
 | 
					 "fnv",
 | 
				
			||||||
 | 
					 "lazy_static",
 | 
				
			||||||
 | 
					 "memchr",
 | 
				
			||||||
 | 
					 "parking_lot",
 | 
				
			||||||
 | 
					 "protobuf",
 | 
				
			||||||
 | 
					 "thiserror",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "protobuf"
 | 
				
			||||||
 | 
					version = "2.28.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "quote"
 | 
					name = "quote"
 | 
				
			||||||
version = "1.0.33"
 | 
					version = "1.0.33"
 | 
				
			||||||
@@ -2211,6 +2232,7 @@ dependencies = [
 | 
				
			|||||||
 "lazy_static",
 | 
					 "lazy_static",
 | 
				
			||||||
 "log",
 | 
					 "log",
 | 
				
			||||||
 "oauth2",
 | 
					 "oauth2",
 | 
				
			||||||
 | 
					 "prometheus",
 | 
				
			||||||
 "rand",
 | 
					 "rand",
 | 
				
			||||||
 "reqwest",
 | 
					 "reqwest",
 | 
				
			||||||
 "rocket",
 | 
					 "rocket",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -43,6 +43,7 @@ assets = [
 | 
				
			|||||||
    ["conf/default.env", "etc/reminder-rs/config.env", "600"],
 | 
					    ["conf/default.env", "etc/reminder-rs/config.env", "600"],
 | 
				
			||||||
    ["conf/Rocket.toml", "etc/reminder-rs/Rocket.toml", "600"],
 | 
					    ["conf/Rocket.toml", "etc/reminder-rs/Rocket.toml", "600"],
 | 
				
			||||||
    ["web/static/**/*", "lib/reminder-rs/static", "644"],
 | 
					    ["web/static/**/*", "lib/reminder-rs/static", "644"],
 | 
				
			||||||
 | 
					    ["reminder-dashboard/dist/static/**/*", "lib/reminder-rs/static", "644"],
 | 
				
			||||||
    ["web/templates/**/*", "lib/reminder-rs/templates", "644"],
 | 
					    ["web/templates/**/*", "lib/reminder-rs/templates", "644"],
 | 
				
			||||||
    ["healthcheck", "lib/reminder-rs/healthcheck", "755"],
 | 
					    ["healthcheck", "lib/reminder-rs/healthcheck", "755"],
 | 
				
			||||||
    ["cron.d/reminder_health", "etc/cron.d/reminder_health", "644"],
 | 
					    ["cron.d/reminder_health", "etc/cron.d/reminder_health", "644"],
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,3 +19,4 @@ lazy_static = "1.4.0"
 | 
				
			|||||||
rand = "0.8"
 | 
					rand = "0.8"
 | 
				
			||||||
base64 = "0.13"
 | 
					base64 = "0.13"
 | 
				
			||||||
csv = "1.2"
 | 
					csv = "1.2"
 | 
				
			||||||
 | 
					prometheus = "0.13.3"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,7 @@ mod consts;
 | 
				
			|||||||
mod macros;
 | 
					mod macros;
 | 
				
			||||||
mod catchers;
 | 
					mod catchers;
 | 
				
			||||||
mod guards;
 | 
					mod guards;
 | 
				
			||||||
 | 
					mod metrics;
 | 
				
			||||||
mod routes;
 | 
					mod routes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use std::{env, path::Path};
 | 
					use std::{env, path::Path};
 | 
				
			||||||
@@ -25,7 +26,10 @@ use serenity::{
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
use sqlx::{MySql, Pool};
 | 
					use sqlx::{MySql, Pool};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::consts::{CNC_GUILD, DISCORD_OAUTH_AUTHORIZE, DISCORD_OAUTH_TOKEN, SUBSCRIPTION_ROLES};
 | 
					use crate::{
 | 
				
			||||||
 | 
					    consts::{CNC_GUILD, DISCORD_OAUTH_AUTHORIZE, DISCORD_OAUTH_TOKEN, SUBSCRIPTION_ROLES},
 | 
				
			||||||
 | 
					    metrics::{init_metrics, MetricProducer},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Database = MySql;
 | 
					type Database = MySql;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -64,7 +68,10 @@ pub async fn initialize(
 | 
				
			|||||||
    let static_path =
 | 
					    let static_path =
 | 
				
			||||||
        if Path::new("web/static").exists() { "web/static" } else { "/lib/reminder-rs/static" };
 | 
					        if Path::new("web/static").exists() { "web/static" } else { "/lib/reminder-rs/static" };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    init_metrics();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    rocket::build()
 | 
					    rocket::build()
 | 
				
			||||||
 | 
					        .attach(MetricProducer)
 | 
				
			||||||
        .attach(Template::fairing())
 | 
					        .attach(Template::fairing())
 | 
				
			||||||
        .register(
 | 
					        .register(
 | 
				
			||||||
            "/",
 | 
					            "/",
 | 
				
			||||||
@@ -85,12 +92,13 @@ pub async fn initialize(
 | 
				
			|||||||
        .mount(
 | 
					        .mount(
 | 
				
			||||||
            "/",
 | 
					            "/",
 | 
				
			||||||
            routes![
 | 
					            routes![
 | 
				
			||||||
                routes::index,
 | 
					 | 
				
			||||||
                routes::cookies,
 | 
					                routes::cookies,
 | 
				
			||||||
 | 
					                routes::index,
 | 
				
			||||||
 | 
					                routes::metrics::metrics,
 | 
				
			||||||
                routes::privacy,
 | 
					                routes::privacy,
 | 
				
			||||||
                routes::terms,
 | 
					 | 
				
			||||||
                routes::return_to_same_site,
 | 
					 | 
				
			||||||
                routes::report::report_error,
 | 
					                routes::report::report_error,
 | 
				
			||||||
 | 
					                routes::return_to_same_site,
 | 
				
			||||||
 | 
					                routes::terms,
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        .mount(
 | 
					        .mount(
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										43
									
								
								web/src/metrics.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								web/src/metrics.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
				
			|||||||
 | 
					use lazy_static::lazy_static;
 | 
				
			||||||
 | 
					use prometheus::{IntCounterVec, Opts, Registry};
 | 
				
			||||||
 | 
					use rocket::{
 | 
				
			||||||
 | 
					    fairing::{Fairing, Info, Kind},
 | 
				
			||||||
 | 
					    Data, Request, Response,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					lazy_static! {
 | 
				
			||||||
 | 
					    pub static ref REGISTRY: Registry = Registry::new();
 | 
				
			||||||
 | 
					    static ref REQUEST_COUNTER: IntCounterVec =
 | 
				
			||||||
 | 
					        IntCounterVec::new(Opts::new("requests", "Requests"), &["method", "route"]).unwrap();
 | 
				
			||||||
 | 
					    static ref RESPONSE_COUNTER: IntCounterVec =
 | 
				
			||||||
 | 
					        IntCounterVec::new(Opts::new("responses", "Responses"), &["status", "route"]).unwrap();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn init_metrics() {
 | 
				
			||||||
 | 
					    REGISTRY.register(Box::new(REQUEST_COUNTER.clone())).unwrap();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct MetricProducer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[rocket::async_trait]
 | 
				
			||||||
 | 
					impl Fairing for MetricProducer {
 | 
				
			||||||
 | 
					    fn info(&self) -> Info {
 | 
				
			||||||
 | 
					        Info { name: "Metrics fairing", kind: Kind::Request }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async fn on_request(&self, req: &mut Request<'_>, _data: &mut Data<'_>) {
 | 
				
			||||||
 | 
					        if let Some(route) = req.route() {
 | 
				
			||||||
 | 
					            REQUEST_COUNTER
 | 
				
			||||||
 | 
					                .with_label_values(&[req.method().as_str(), &route.uri.to_string()])
 | 
				
			||||||
 | 
					                .inc();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async fn on_response<'r>(&self, req: &'r Request<'_>, resp: &mut Response<'r>) {
 | 
				
			||||||
 | 
					        if let Some(route) = req.route() {
 | 
				
			||||||
 | 
					            RESPONSE_COUNTER
 | 
				
			||||||
 | 
					                .with_label_values(&[&resp.status().code.to_string(), &route.uri.to_string()])
 | 
				
			||||||
 | 
					                .inc();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -235,6 +235,21 @@ pub async fn edit_reminder(
 | 
				
			|||||||
                ]);
 | 
					                ]);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        sqlx::query!(
 | 
				
			||||||
 | 
					            "
 | 
				
			||||||
 | 
					            UPDATE reminders
 | 
				
			||||||
 | 
					            SET interval_seconds = NULL, interval_days = NULL, interval_months = NULL
 | 
				
			||||||
 | 
					            WHERE uid = ?
 | 
				
			||||||
 | 
					            ",
 | 
				
			||||||
 | 
					            reminder.uid
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .execute(transaction.executor())
 | 
				
			||||||
 | 
					        .await
 | 
				
			||||||
 | 
					        .map_err(|e| {
 | 
				
			||||||
 | 
					            warn!("Error updating reminder interval: {:?}", e);
 | 
				
			||||||
 | 
					            json!({ "reminder": Option::<Reminder>::None, "errors": vec!["Unknown error"] })
 | 
				
			||||||
 | 
					        })?;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if reminder.channel > 0 {
 | 
					    if reminder.channel > 0 {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1 +0,0 @@
 | 
				
			|||||||
 | 
					 | 
				
			||||||
@@ -1,9 +1,13 @@
 | 
				
			|||||||
use std::collections::HashMap;
 | 
					use std::path::Path;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use chrono::{naive::NaiveDateTime, Utc};
 | 
					use chrono::{naive::NaiveDateTime, Utc};
 | 
				
			||||||
use rand::{rngs::OsRng, seq::IteratorRandom};
 | 
					use rand::{rngs::OsRng, seq::IteratorRandom};
 | 
				
			||||||
use rocket::{http::CookieJar, response::Redirect, serde::json::json};
 | 
					use rocket::{
 | 
				
			||||||
use rocket_dyn_templates::Template;
 | 
					    fs::{relative, NamedFile},
 | 
				
			||||||
 | 
					    http::CookieJar,
 | 
				
			||||||
 | 
					    response::Redirect,
 | 
				
			||||||
 | 
					    serde::json::json,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
 | 
					use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
 | 
				
			||||||
use serenity::{
 | 
					use serenity::{
 | 
				
			||||||
    client::Context,
 | 
					    client::Context,
 | 
				
			||||||
@@ -27,7 +31,6 @@ use crate::{
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
pub mod api;
 | 
					pub mod api;
 | 
				
			||||||
pub mod export;
 | 
					pub mod export;
 | 
				
			||||||
pub mod guild;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Unset<T> = Option<T>;
 | 
					type Unset<T> = Option<T>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -655,22 +658,26 @@ async fn create_database_channel(
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[get("/")]
 | 
					#[get("/")]
 | 
				
			||||||
pub async fn dashboard_home(cookies: &CookieJar<'_>) -> Result<Template, Redirect> {
 | 
					pub async fn dashboard_home(cookies: &CookieJar<'_>) -> Result<NamedFile, Redirect> {
 | 
				
			||||||
    if cookies.get_private("userid").is_some() {
 | 
					    if cookies.get_private("userid").is_some() {
 | 
				
			||||||
        let mut map = HashMap::new();
 | 
					        NamedFile::open(Path::new(relative!("static/index.html"))).await.map_err(|e| {
 | 
				
			||||||
        map.insert("version", env!("CARGO_PKG_VERSION"));
 | 
					            warn!("Couldn't render dashboard: {:?}", e);
 | 
				
			||||||
        Ok(Template::render("dashboard", &map))
 | 
					
 | 
				
			||||||
 | 
					            Redirect::to("/login/discord")
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        Err(Redirect::to("/login/discord"))
 | 
					        Err(Redirect::to("/login/discord"))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[get("/<_..>")]
 | 
					#[get("/<_..>")]
 | 
				
			||||||
pub async fn dashboard(cookies: &CookieJar<'_>) -> Result<Template, Redirect> {
 | 
					pub async fn dashboard(cookies: &CookieJar<'_>) -> Result<NamedFile, Redirect> {
 | 
				
			||||||
    if cookies.get_private("userid").is_some() {
 | 
					    if cookies.get_private("userid").is_some() {
 | 
				
			||||||
        let mut map = HashMap::new();
 | 
					        NamedFile::open(Path::new(relative!("static/index.html"))).await.map_err(|e| {
 | 
				
			||||||
        map.insert("version", env!("CARGO_PKG_VERSION"));
 | 
					            warn!("Couldn't render dashboard: {:?}", e);
 | 
				
			||||||
        Ok(Template::render("dashboard", &map))
 | 
					
 | 
				
			||||||
 | 
					            Redirect::to("/login/discord")
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        Err(Redirect::to("/login/discord"))
 | 
					        Err(Redirect::to("/login/discord"))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										18
									
								
								web/src/routes/metrics.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								web/src/routes/metrics.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					use prometheus;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::metrics::REGISTRY;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[get("/metrics")]
 | 
				
			||||||
 | 
					pub async fn metrics() -> String {
 | 
				
			||||||
 | 
					    let encoder = prometheus::TextEncoder::new();
 | 
				
			||||||
 | 
					    let res_custom = encoder.encode_to_string(®ISTRY.gather());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    match res_custom {
 | 
				
			||||||
 | 
					        Ok(s) => s,
 | 
				
			||||||
 | 
					        Err(e) => {
 | 
				
			||||||
 | 
					            warn!("Error encoding metrics: {:?}", e);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            String::new()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
pub mod admin;
 | 
					pub mod admin;
 | 
				
			||||||
pub mod dashboard;
 | 
					pub mod dashboard;
 | 
				
			||||||
pub mod login;
 | 
					pub mod login;
 | 
				
			||||||
 | 
					pub mod metrics;
 | 
				
			||||||
pub mod report;
 | 
					pub mod report;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use std::collections::HashMap;
 | 
					use std::collections::HashMap;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -218,17 +218,16 @@ div.inset-content {
 | 
				
			|||||||
    margin-right: 10%;
 | 
					    margin-right: 10%;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
div.flash-message {
 | 
					div.flash-container {
 | 
				
			||||||
    position: fixed;
 | 
					    position: fixed;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    bottom: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.flash-message {
 | 
				
			||||||
    width: calc(100% - 32px);
 | 
					    width: calc(100% - 32px);
 | 
				
			||||||
    margin: 16px !important;
 | 
					    margin: 16px !important;
 | 
				
			||||||
    z-index: 99;
 | 
					    z-index: 99;
 | 
				
			||||||
    bottom: 0;
 | 
					 | 
				
			||||||
    display: none;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
div.flash-message.is-active {
 | 
					 | 
				
			||||||
    display: block;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
body {
 | 
					body {
 | 
				
			||||||
@@ -633,6 +632,10 @@ li.highlight {
 | 
				
			|||||||
    display: flex;
 | 
					    display: flex;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.button-row-edit > button {
 | 
				
			||||||
 | 
					    margin-right: 4px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.button-row .button-row-reminder {
 | 
					.button-row .button-row-reminder {
 | 
				
			||||||
    flex-grow: 0;
 | 
					    flex-grow: 0;
 | 
				
			||||||
    padding: 2px;
 | 
					    padding: 2px;
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user