talk support + prometheus

This commit is contained in:
Cadey Ratio 2020-07-14 09:10:12 -04:00
parent c1a620c040
commit d722ea9ede
9 changed files with 344 additions and 11 deletions

77
Cargo.lock generated
View File

@ -21,6 +21,12 @@ dependencies = [
"pretty", "pretty",
] ]
[[package]]
name = "adler32"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b077b825e468cc974f0020d4082ee6e03132512f207ef1a02fd5d00d1f32d"
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "0.7.13" version = "0.7.13"
@ -255,6 +261,15 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac"
[[package]]
name = "crc32fast"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "dhall" name = "dhall"
version = "0.5.3" version = "0.5.3"
@ -776,6 +791,24 @@ version = "0.2.72"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9f8082297d534141b30c8d39e9b1773713ab50fdbe4ff30f750d063b3bfd701" checksum = "a9f8082297d534141b30c8d39e9b1773713ab50fdbe4ff30f750d063b3bfd701"
[[package]]
name = "libflate"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9bac9023e1db29c084f9f8cd9d3852e5e8fddf98fb47c4964a0ea4663d95949"
dependencies = [
"adler32",
"crc32fast",
"libflate_lz77",
"rle-decode-fast",
]
[[package]]
name = "libflate_lz77"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3286f09f7d4926fc486334f28d8d2e6ebe4f7f9994494b6dab27ddfad2c9b11b"
[[package]] [[package]]
name = "linked-hash-map" name = "linked-hash-map"
version = "0.5.3" version = "0.5.3"
@ -1237,6 +1270,35 @@ dependencies = [
"unicode-xid", "unicode-xid",
] ]
[[package]]
name = "procfs"
version = "0.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c434e93ef69c216e68e4f417c927b4f31502c3560b72cfdb6827e2321c5c6b3e"
dependencies = [
"bitflags",
"byteorder",
"hex",
"lazy_static",
"libc",
"libflate",
]
[[package]]
name = "prometheus"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd0ced56dee39a6e960c15c74dc48849d614586db2eaada6497477af7c7811cd"
dependencies = [
"cfg-if",
"fnv",
"lazy_static",
"libc",
"procfs",
"spin",
"thiserror",
]
[[package]] [[package]]
name = "quick-error" name = "quick-error"
version = "1.2.3" version = "1.2.3"
@ -1475,6 +1537,12 @@ dependencies = [
"winreg", "winreg",
] ]
[[package]]
name = "rle-decode-fast"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cabe4fa914dec5870285fa7f71f602645da47c486e68486d2b4ceb4a343e90ac"
[[package]] [[package]]
name = "ructe" name = "ructe"
version = "0.11.4" version = "0.11.4"
@ -1712,6 +1780,12 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "spin"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]] [[package]]
name = "static_assertions" name = "static_assertions"
version = "1.1.0" version = "1.1.0"
@ -2262,9 +2336,12 @@ dependencies = [
"comrak", "comrak",
"envy", "envy",
"glob", "glob",
"hyper",
"lazy_static",
"log 0.4.8", "log 0.4.8",
"mime 0.3.16", "mime 0.3.16",
"pretty_env_logger", "pretty_env_logger",
"prometheus",
"rand 0.7.3", "rand 0.7.3",
"ructe", "ructe",
"serde", "serde",

View File

@ -13,9 +13,12 @@ chrono = "0.4"
comrak = "0.8" comrak = "0.8"
envy = "0.4" envy = "0.4"
glob = "0.3" glob = "0.3"
hyper = "0.13"
lazy_static = "1.4"
log = "0" log = "0"
mime = "0.3.0" mime = "0.3.0"
pretty_env_logger = "0" pretty_env_logger = "0"
prometheus = { version = "0.9", default-features = false, features = ["process"] }
rand = "0" rand = "0"
ructe = "0.11" ructe = "0.11"
serde_dhall = "0.5.3" serde_dhall = "0.5.3"

View File

@ -4,9 +4,17 @@ use crate::{
post::Post, post::Post,
templates::{self, Html, RenderRucte}, templates::{self, Html, RenderRucte},
}; };
use lazy_static::lazy_static;
use prometheus::{IntCounterVec, register_int_counter_vec, opts};
use std::sync::Arc; use std::sync::Arc;
use warp::{http::Response, Rejection, Reply}; use warp::{http::Response, Rejection, Reply};
lazy_static! {
static ref HIT_COUNTER: IntCounterVec =
register_int_counter_vec!(opts!("blogpost_hits", "Number of hits to blogposts"), &["name"])
.unwrap();
}
pub async fn index(state: Arc<State>) -> Result<impl Reply, Rejection> { pub async fn index(state: Arc<State>) -> Result<impl Reply, Rejection> {
let state = state.clone(); let state = state.clone();
Response::builder().html(|o| templates::blogindex_html(o, state.blog.clone())) Response::builder().html(|o| templates::blogindex_html(o, state.blog.clone()))
@ -61,6 +69,7 @@ pub async fn post_view(name: String, state: Arc<State>) -> Result<impl Reply, Re
match want { match want {
None => Err(PostNotFound("blog".into(), name).into()), None => Err(PostNotFound("blog".into(), name).into()),
Some(post) => { Some(post) => {
HIT_COUNTER.with_label_values(&[name.clone().as_str()]).inc();
let body = Html(post.body_html.clone()); let body = Html(post.body_html.clone());
Response::builder().html(|o| templates::blogpost_html(o, post, body)) Response::builder().html(|o| templates::blogpost_html(o, post, body))
} }

View File

@ -4,9 +4,17 @@ use crate::{
post::Post, post::Post,
templates::{self, Html, RenderRucte}, templates::{self, Html, RenderRucte},
}; };
use lazy_static::lazy_static;
use prometheus::{IntCounterVec, register_int_counter_vec, opts};
use std::sync::Arc; use std::sync::Arc;
use warp::{http::Response, Rejection, Reply}; use warp::{http::Response, Rejection, Reply};
lazy_static! {
static ref HIT_COUNTER: IntCounterVec =
register_int_counter_vec!(opts!("gallery_hits", "Number of hits to gallery images"), &["name"])
.unwrap();
}
pub async fn index(state: Arc<State>) -> Result<impl Reply, Rejection> { pub async fn index(state: Arc<State>) -> Result<impl Reply, Rejection> {
let state = state.clone(); let state = state.clone();
Response::builder().html(|o| templates::galleryindex_html(o, state.gallery.clone())) Response::builder().html(|o| templates::galleryindex_html(o, state.gallery.clone()))
@ -22,8 +30,9 @@ pub async fn post_view(name: String, state: Arc<State>) -> Result<impl Reply, Re
} }
match want { match want {
None => Err(PostNotFound("blog".into(), name).into()), None => Err(PostNotFound("gallery".into(), name).into()),
Some(post) => { Some(post) => {
HIT_COUNTER.with_label_values(&[name.clone().as_str()]).inc();
let body = Html(post.body_html.clone()); let body = Html(post.body_html.clone());
Response::builder().html(|o| templates::gallerypost_html(o, post, body)) Response::builder().html(|o| templates::gallerypost_html(o, post, body))
} }

View File

@ -2,40 +2,55 @@ use crate::{
app::State, app::State,
templates::{self, Html, RenderRucte}, templates::{self, Html, RenderRucte},
}; };
use lazy_static::lazy_static;
use prometheus::{IntCounterVec, register_int_counter_vec, opts};
use std::{convert::Infallible, fmt, sync::Arc}; use std::{convert::Infallible, fmt, sync::Arc};
use warp::{ use warp::{
http::{Response, StatusCode}, http::{Response, StatusCode},
Rejection, Reply, Rejection, Reply,
}; };
lazy_static! {
static ref HIT_COUNTER: IntCounterVec =
register_int_counter_vec!(opts!("hits", "Number of hits to various pages"), &["page"])
.unwrap();
}
pub async fn index() -> Result<impl Reply, Rejection> { pub async fn index() -> Result<impl Reply, Rejection> {
HIT_COUNTER.with_label_values(&["index"]).inc();
Response::builder().html(|o| templates::index_html(o)) Response::builder().html(|o| templates::index_html(o))
} }
pub async fn contact() -> Result<impl Reply, Rejection> { pub async fn contact() -> Result<impl Reply, Rejection> {
HIT_COUNTER.with_label_values(&["contact"]).inc();
Response::builder().html(|o| templates::contact_html(o)) Response::builder().html(|o| templates::contact_html(o))
} }
pub async fn feeds() -> Result<impl Reply, Rejection> { pub async fn feeds() -> Result<impl Reply, Rejection> {
HIT_COUNTER.with_label_values(&["feeds"]).inc();
Response::builder().html(|o| templates::feeds_html(o)) Response::builder().html(|o| templates::feeds_html(o))
} }
pub async fn resume(state: Arc<State>) -> Result<impl Reply, Rejection> { pub async fn resume(state: Arc<State>) -> Result<impl Reply, Rejection> {
HIT_COUNTER.with_label_values(&["resume"]).inc();
let state = state.clone(); let state = state.clone();
Response::builder().html(|o| templates::resume_html(o, Html(state.resume.clone()))) Response::builder().html(|o| templates::resume_html(o, Html(state.resume.clone())))
} }
pub async fn signalboost(state: Arc<State>) -> Result<impl Reply, Rejection> { pub async fn signalboost(state: Arc<State>) -> Result<impl Reply, Rejection> {
HIT_COUNTER.with_label_values(&["signalboost"]).inc();
let state = state.clone(); let state = state.clone();
Response::builder().html(|o| templates::signalboost_html(o, state.signalboost.clone())) Response::builder().html(|o| templates::signalboost_html(o, state.signalboost.clone()))
} }
pub async fn not_found() -> Result<impl Reply, Rejection> { pub async fn not_found() -> Result<impl Reply, Rejection> {
HIT_COUNTER.with_label_values(&["not_found"]).inc();
Response::builder().html(|o| templates::notfound_html(o, "some path".into())) Response::builder().html(|o| templates::notfound_html(o, "some path".into()))
} }
pub mod blog; pub mod blog;
pub mod gallery; pub mod gallery;
pub mod talks;
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
struct PostNotFound(String, String); struct PostNotFound(String, String);
@ -71,24 +86,34 @@ impl From<SeriesNotFound> for warp::reject::Rejection {
} }
} }
lazy_static! {
static ref REJECTION_COUNTER: IntCounterVec =
register_int_counter_vec!(opts!("rejections", "Number of rejections by kind"), &["kind"])
.unwrap();
}
pub async fn rejection(err: Rejection) -> Result<impl Reply, Infallible> { pub async fn rejection(err: Rejection) -> Result<impl Reply, Infallible> {
let path: String; let path: String;
let code; let code;
if err.is_not_found() { if err.is_not_found() {
REJECTION_COUNTER.with_label_values(&["404"]).inc();
path = "".into(); path = "".into();
code = StatusCode::NOT_FOUND; code = StatusCode::NOT_FOUND;
} else if let Some(SeriesNotFound(series)) = err.find() { } else if let Some(SeriesNotFound(series)) = err.find() {
REJECTION_COUNTER.with_label_values(&["SeriesNotFound"]).inc();
log::error!("invalid series {}", series); log::error!("invalid series {}", series);
path = format!("/blog/series/{}", series); path = format!("/blog/series/{}", series);
code = StatusCode::NOT_FOUND; code = StatusCode::NOT_FOUND;
} else if let Some(PostNotFound(kind, name)) = err.find() { } else if let Some(PostNotFound(kind, name)) = err.find() {
REJECTION_COUNTER.with_label_values(&["PostNotFound"]).inc();
log::error!("unknown post {}/{}", kind, name); log::error!("unknown post {}/{}", kind, name);
path = format!("/{}/{}", kind, name); path = format!("/{}/{}", kind, name);
code = StatusCode::NOT_FOUND; code = StatusCode::NOT_FOUND;
} else { } else {
REJECTION_COUNTER.with_label_values(&["Other"]).inc();
log::error!("unhandled rejection: {:?}", err); log::error!("unhandled rejection: {:?}", err);
path = "wut".into(); path = format!("weird rejection: {:?}", err);
code = StatusCode::INTERNAL_SERVER_ERROR; code = StatusCode::INTERNAL_SERVER_ERROR;
} }

40
src/handlers/talks.rs Normal file
View File

@ -0,0 +1,40 @@
use super::PostNotFound;
use crate::{
app::State,
post::Post,
templates::{self, Html, RenderRucte},
};
use lazy_static::lazy_static;
use prometheus::{IntCounterVec, register_int_counter_vec, opts};
use std::sync::Arc;
use warp::{http::Response, Rejection, Reply};
lazy_static! {
static ref HIT_COUNTER: IntCounterVec =
register_int_counter_vec!(opts!("talks_hits", "Number of hits to talks images"), &["name"])
.unwrap();
}
pub async fn index(state: Arc<State>) -> Result<impl Reply, Rejection> {
let state = state.clone();
Response::builder().html(|o| templates::talkindex_html(o, state.talks.clone()))
}
pub async fn post_view(name: String, state: Arc<State>) -> Result<impl Reply, Rejection> {
let mut want: Option<Post> = None;
for post in &state.talks {
if post.link == format!("talks/{}", name) {
want = Some(post.clone());
}
}
match want {
None => Err(PostNotFound("talks".into(), name).into()),
Some(post) => {
HIT_COUNTER.with_label_values(&[name.clone().as_str()]).inc();
let body = Html(post.body_html.clone());
Response::builder().html(|o| templates::talkpost_html(o, post, body))
}
}
}

View File

@ -1,4 +1,6 @@
use anyhow::Result; use anyhow::Result;
use hyper::{header::CONTENT_TYPE, Body, Response};
use prometheus::{Encoder, TextEncoder};
use std::sync::Arc; use std::sync::Arc;
use warp::{path, Filter}; use warp::{path, Filter};
@ -72,6 +74,22 @@ async fn main() -> Result<()> {
index.or(post_view) index.or(post_view)
}; };
let talks = {
let base = warp::path!("talks" / ..);
let index = base
.and(warp::path::end())
.and(with_state(state.clone()))
.and_then(handlers::talks::index);
let post_view = base.and(
warp::path!(String)
.and(with_state(state.clone()))
.and(warp::get())
.and_then(handlers::talks::post_view),
);
index.or(post_view)
};
let static_pages = { let static_pages = {
let contact = warp::path!("contact").and_then(handlers::contact); let contact = warp::path!("contact").and_then(handlers::contact);
let feeds = warp::path!("feeds").and_then(handlers::feeds); let feeds = warp::path!("feeds").and_then(handlers::feeds);
@ -82,15 +100,9 @@ async fn main() -> Result<()> {
.and(with_state(state.clone())) .and(with_state(state.clone()))
.and_then(handlers::signalboost); .and_then(handlers::signalboost);
contact.or(feeds.or(resume.or(signalboost))) contact.or(feeds).or(resume).or(signalboost)
}; };
let routes = warp::get()
.and(path::end().and_then(handlers::index))
.or(static_pages)
.or(blog)
.or(gallery);
let files = { let files = {
let files = warp::path("static").and(warp::fs::dir("./static")); let files = warp::path("static").and(warp::fs::dir("./static"));
let css = warp::path("css").and(warp::fs::dir("./css")); let css = warp::path("css").and(warp::fs::dir("./css"));
@ -100,8 +112,26 @@ async fn main() -> Result<()> {
files.or(css).or(sw).or(robots) files.or(css).or(sw).or(robots)
}; };
let metrics_endpoint = warp::path("metrics").and(warp::path::end()).map(move || {
let encoder = TextEncoder::new();
let metric_families = prometheus::gather();
let mut buffer = vec![];
encoder.encode(&metric_families, &mut buffer).unwrap();
Response::builder()
.status(200)
.header(CONTENT_TYPE, encoder.format_type())
.body(Body::from(buffer))
.unwrap()
});
let site = files let site = files
.or(routes) .or(warp::get().and(path::end().and_then(handlers::index)))
.or(static_pages)
.or(blog)
.or(gallery)
.or(talks)
.or(healthcheck)
.or(metrics_endpoint)
.map(|reply| { .map(|reply| {
warp::reply::with_header( warp::reply::with_header(
reply, reply,
@ -109,7 +139,6 @@ async fn main() -> Result<()> {
"If you are reading this, check out /signalboost to find people for your team", "If you are reading this, check out /signalboost to find people for your team",
) )
}) })
.or(healthcheck)
.map(|reply| warp::reply::with_header(reply, "X-Clacks-Overhead", "GNU Ashlynn")) .map(|reply| warp::reply::with_header(reply, "X-Clacks-Overhead", "GNU Ashlynn"))
.with(warp::log(APPLICATION_NAME)) .with(warp::log(APPLICATION_NAME))
.recover(handlers::rejection); .recover(handlers::rejection);

View File

@ -0,0 +1,23 @@
@use crate::post::Post;
@use super::{header_html, footer_html};
@(posts: Vec<Post>)
@:header_html(Some("Talks"), None)
<h1>Talks</h1>
<p>Here is a link to all of the talks I have done at conferences. Each of these will have links to the slides (PDF) as well as some brief information about them.</p>
<p>If you have a compatible reader, be sure to check out my <a href="/blog.rss">RSS Feed</a> for automatic updates. Also check out the <a href="/blog.json">JSONFeed</a>.</p>
<p>
<ul>
@for post in posts {
<li>@post.date - <a href="@post.link">@post.front_matter.title</a></li>
}
</ul>
</p>
@:footer_html()

118
templates/talkpost.rs.html Normal file
View File

@ -0,0 +1,118 @@
@use super::{header_html, footer_html};
@use crate::post::Post;
@(post: Post, body: impl ToHtml)
@:header_html(Some(&post.front_matter.title.clone()), None)
<!-- Twitter -->
<meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@@theprincessxena" />
<meta name="twitter:title" content="@post.front_matter.title" />
<meta name="twitter:description" content="Posted on @post.date" />
<!-- Facebook -->
<meta property="og:type" content="website" />
<meta property="og:title" content="@post.front_matter.title" />
<meta property="og:site_name" content="Christine Dodrill's Blog" />
<!-- Description -->
<meta name="description" content="@post.front_matter.title - Christine Dodrill's Blog" />
<meta name="author" content="Christine Dodrill">
<link rel="canonical" href="https://christine.website/@post.link">
<script type="application/ld+json">
@{
"@@context": "http://schema.org",
"@@type": "Article",
"headline": "@post.front_matter.title",
"image": "https://christine.website/static/img/avatar.png",
"url": "https://christine.website/@post.link",
"datePublished": "@post.date",
"mainEntityOfPage": @{
"@@type": "WebPage",
"@@id": "https://christine.website/@post.link"
@},
"author": @{
"@@type": "Person",
"name": "Christine Dodrill"
@},
"publisher": @{
"@@type": "Person",
"name": "Christine Dodrill"
@}
@}
</script>
@body
<a href="@post.front_matter.slides_link.as_ref().unwrap()">Link to the slides</a>
<hr />
<!-- The button that should be clicked. -->
<button onclick="share_on_mastodon()">Share on Mastodon</button>
@if post.front_matter.series.is_some() {
<p>Series: <a href="/blog/series/@post.front_matter.series.as_ref().unwrap()">@post.front_matter.series.as_ref().unwrap()</a></p>
}
@if post.front_matter.tags.is_some() {
<p>Tags: @for tag in post.front_matter.tags.as_ref().unwrap() { <code>@tag</code> }</p>
}
<script>
// The actual function. Set this as an onclick function for your "Share on Mastodon" button
function share_on_mastodon() @{
// Prefill the form with the user's previously-specified Mastodon instance, if applicable
var default_url = localStorage['mastodon_instance'];
// If there is no cached instance/domain, then insert a "https://" with no domain at the start of the prompt.
if (!default_url)
default_url = "https://";
var instance = prompt("Enter your instance's address: (ex: https://linuxrocks.online)", default_url);
if (instance) @{
// Handle URL formats
if ( !instance.startsWith("https://") && !instance.startsWith("http://") )
instance = "https://" + instance;
// get the current page's url
var url = window.location.href;
// get the page title from the og:title meta tag, if it exists.
var title = document.querySelectorAll('meta[property="og:title"]')[0].getAttribute("content");
// Otherwise, use the <title> tag as the title
if (!title) var title = document.getElementsByTagName("title")[0].innerHTML;
// Handle slash
if ( !instance.endsWith("/") )
instance = instance + "/";
// Cache the instance/domain for future requests
localStorage['mastodon_instance'] = instance;
// Hashtags
var hashtags = "#talk";
@if post.front_matter.tags.is_some() {
hashtags += "@for tag in post.front_matter.tags.as_ref().unwrap() { #@tag}";
}
// Tagging users, such as offical accounts or the author of the post
var author = "@@cadey@@mst3k.interlinked.me";
// Create the Share URL
// https://someinstance.tld/share?text=URL%20encoded%20text
mastodon_url = instance + "share?text=" + encodeURIComponent(title + "\n\n" + url + "\n\n" + hashtags + " " + author);
// Open a new window at the share location
window.open(mastodon_url, '_blank');
@}
@}
</script>
@:footer_html()