Compare commits
	
		
			6 Commits
		
	
	
		
			218be2f0b1
			...
			jude/react
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 1a03c2471b | ||
| a476f43f28 | |||
| 17192b0f89 | |||
|  | 0419863afa | ||
|  | 827a982a40 | ||
|  | 6e435bfc2e | 
							
								
								
									
										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