Skip to content

Commit

Permalink
feat: add basic Leptos UI
Browse files Browse the repository at this point in the history
  • Loading branch information
Alex committed Dec 30, 2023
1 parent ccc8d73 commit d057692
Show file tree
Hide file tree
Showing 9 changed files with 1,270 additions and 56 deletions.
985 changes: 982 additions & 3 deletions Cargo.lock

Large diffs are not rendered by default.

57 changes: 57 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,73 @@ version = "0.1.0"
edition = "2021"

[dependencies]
actix-files = "0.6.2"
actix-web = "4.3.1"
anyhow = "1.0.77"
chrono = { version = "0.4.31", features = ["serde"] }
env_logger = "0.10.1"
lazy_static = "1.4.0"
leptos = { version = "0.5.4", features = ["ssr"] }
leptos_actix = "0.5.4"
leptos_config = "0.5.4"
leptos_meta = { version = "0.5.4", features = ["ssr"] }
leptos_router = { version = "0.5.4", features = ["ssr"] }
log = "0.4.20"
reqwest = "0.11.23"
serde = { version = "1.0.193", features = ["derive"] }
serde_json = "1.0.108"
shuttle-actix-web = "0.35.0"
shuttle-runtime = "0.35.0"
thaw = { version = "0.1.5", default-features = false, features = ["ssr"] }
thiserror = "1.0.52"
tokio = { version = "1.26.0", features = ["full"] }
toml = "0.8.8"
url = { version = "2.5.0", features = ["serde"] }
wasm-bindgen = { version = "0.2.89", features = ["serde", "serde-serialize", "serde_json"] }

[package.metadata.leptos]
# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name
output-name = "pingy"
# The site root folder is where cargo-leptos generate all output. WARNING: all content of this folder will be erased on a rebuild. Use it in your server setup.
site-root = "target/site"
# The site-root relative folder where all compiled output (JS, WASM and CSS) is written
# Defaults to pkg
site-pkg-dir = "pkg"
# [Optional] The source CSS file. If it ends with .sass or .scss then it will be compiled by dart-sass into CSS. The CSS is optimized by Lightning CSS before being written to <site-root>/<site-pkg>/app.css
style-file = "style/main.scss"
# Assets source dir. All files found here will be copied and synchronized to site-root.
# The assets-dir cannot have a sub directory with the same name/path as site-pkg-dir.
#
# Optional. Env: LEPTOS_ASSETS_DIR.
assets-dir = "assets"
# The IP and port (ex: 127.0.0.1:3000) where the server serves the content. Use it in your server setup.
site-addr = "127.0.0.1:3000"
# The port to use for automatic reload monitoring
reload-port = 3001
# [Optional] Command to use when running end2end tests. It will run in the end2end dir.
# [Windows] for non-WSL use "npx.cmd playwright test"
# This binary name can be checked in Powershell with Get-Command npx
# The browserlist query used for optimizing the CSS.
browserquery = "defaults"
# Set by cargo-leptos watch when building with that tool. Controls whether autoreload JS will be included in the head
watch = false
# The environment Leptos will run in, usually either "DEV" or "PROD"
env = "DEV"
# The features to use when compiling the bin target
#
# Optional. Can be over-ridden with the command line parameter --bin-features
bin-features = ["ssr"]

# If the --no-default-features flag should be used when compiling the bin target
#
# Optional. Defaults to false.
bin-default-features = false

# The features to use when compiling the lib target
#
# Optional. Can be over-ridden with the command line parameter --lib-features

# If the --no-default-features flag should be used when compiling the lib target
#
# Optional. Defaults to false.
lib-default-features = false
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
# pingy
A fast &amp; simple website monitor running in [Shuttle](https://shuttle.rs).

Pingy is designed to be an easy to use, low-footprint monitor for HTTP(S) services. It is built using Leptos and deployed using
Shuttle.

See a [live demo](https://pingy.shuttle.rs/).

## Development

### Setup
Expand Down
7 changes: 7 additions & 0 deletions config.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
[[monitors]]
title = "Shuttle"
url = "https://shuttle.rs"
method = "GET"
check_interval = 30000

[[monitors]]
title = "Rust"
url = "https://www.rust-lang.org"
method = "GET"
check_interval = 30000
71 changes: 70 additions & 1 deletion flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
inherit system overlays;
};
naersk-lib = pkgs.callPackage naersk { };
rust = pkgs.rust-bin.nightly.latest.default.override {
targets = [ "wasm32-unknown-unknown" ];
};
in
{
defaultPackage = naersk-lib.buildPackage {
Expand All @@ -24,7 +27,7 @@
devShell = with pkgs; mkShell {
nativeBuildInputs = [
cargo
rust-bin.nightly.latest.default
rust
pkg-config
];
buildInputs = [ rustfmt pre-commit rustPackages.clippy bacon openssl ];
Expand Down
61 changes: 61 additions & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use std::collections::HashMap;

use leptos::*;
use leptos_meta::*;
use leptos_router::*;

use crate::{AppState, MonitorResult, UrlConfig};
use thaw::*;

#[component]
pub fn App(app_state: AppState) -> impl IntoView {
// Provides context that manages stylesheets, titles, meta tags, etc.
provide_meta_context();

view! {
<Stylesheet id="leptos" href="/pkg/ssr_modes.css"/>
<Title text="Welcome to Leptos"/>

<Router>
<main>
<Routes>
<Route path="" view=move || view! {
<HomePage results=app_state
.get_results()
/>
}/>
</Routes>
</main>
</Router>
}
}

#[component]
fn HomePage(results: HashMap<UrlConfig, Vec<MonitorResult>>) -> impl IntoView {
view! {
<h1>"Pingy!"</h1>

<Table>
<thead>
<tr>
<th>"Title"</th>
<th>"URL"</th>
<th>"Status Code"</th>
<th>"Latency (ms)"</th>
</tr>
</thead>
<tbody>
{results.into_iter()
.map(|n| view! {
<tr>
<td>{n.0.title.to_string()}</td>
<td>{n.0.url.to_string()}</td>
<td>{n.1.iter().last().unwrap().status_code}</td>
<td>{n.1.iter().last().unwrap().latency}</td>
</tr>
})
.collect::<Vec<_>>()}
</tbody>
</Table>
}
}
53 changes: 47 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
pub mod app;
use chrono::prelude::*;
use log::info;
use log::{debug, error, info};
use std::sync::{Arc, RwLock};
use std::{collections::HashMap, fs::File, io::Read};

#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
#[derive(Debug, Clone, PartialEq, Hash, Eq, serde::Deserialize, serde::Serialize)]
pub struct UrlConfig {
pub title: String,
pub url: url::Url,
pub method: Option<String>,
pub check_interval: u64,
Expand All @@ -24,7 +26,7 @@ pub struct MonitorResult {
#[derive(Clone, Debug)]
pub struct AppState {
pub monitors: Vec<UrlConfig>,
pub results: Arc<RwLock<HashMap<url::Url, Vec<MonitorResult>>>>,
pub results: Arc<RwLock<HashMap<UrlConfig, Vec<MonitorResult>>>>,
}

#[derive(serde::Deserialize)]
Expand All @@ -43,14 +45,53 @@ impl Config {
}

impl AppState {
pub async fn get_results(&self) -> HashMap<url::Url, Vec<MonitorResult>> {
pub fn get_results(&self) -> HashMap<UrlConfig, Vec<MonitorResult>> {
let lock = self.results.read().expect("Couldn't lock results");
info!("Got results {:?}", lock);
lock.clone()
}

pub fn record(&mut self, url: url::Url, result: MonitorResult) -> () {
pub fn record(&mut self, url: UrlConfig, result: MonitorResult) -> () {
let mut map = self.results.write().expect("Couldn't unlock results");
map.entry(url).or_insert(Vec::new()).push(result);
}
}

/// An async task to request the given URL every `url.check_interval` ms
pub async fn monitor(
url: UrlConfig,
tx: tokio::sync::mpsc::Sender<(UrlConfig, MonitorResult)>,
) -> Result<(), String> {
info!("Processor initialised for {}", url.title);
loop {
debug!("Checking {}", url.title);
let ts = Utc::now();
let res = reqwest::get(url.url.clone())
.await
.map_err(|e| format!("Error {}", e))?;

match tx
.send((
url.clone(),
MonitorResult {
timestamp: ts,
status_code: res.status().into(),
latency: (Utc::now().time() - ts.time()).num_milliseconds(),
},
))
.await
{
Err(e) => {
error!("Couldn't send result for {}: {e}", url.url);
break;
}
_ => (),
}
debug!(
"{} monitor sleeping for {}ms",
url.title, url.check_interval
);
let _ =
tokio::time::sleep(std::time::Duration::from_millis(url.check_interval.into())).await;
}
Ok(())
}
Loading

0 comments on commit d057692

Please sign in to comment.