-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement monitor page with basic info
Include error and logout endpoints
- Loading branch information
1 parent
8511ef1
commit 230cc46
Showing
10 changed files
with
344 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,6 @@ | ||
/// Module for `/` and `/health` entrypoints. | ||
/// Module for `/` and `/health` entrypoint. | ||
pub mod basics; | ||
/// Module for `/login`, `/logout` and `/error` entrypoints. | ||
/// Module for `/login`, `/logout` and `/error` entrypoint. | ||
pub mod auth; | ||
/// Module for `/monitor` entrypoint. | ||
pub mod monitor; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
use crate::{constant, routes, squire, system_info}; | ||
use actix_web::cookie::{Cookie, SameSite}; | ||
use actix_web::http::StatusCode; | ||
use actix_web::{web, HttpRequest, HttpResponse}; | ||
use fernet::Fernet; | ||
use std::sync::Arc; | ||
|
||
/// Handles the monitor endpoint and rendering the appropriate HTML page. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `request` - A reference to the Actix web `HttpRequest` object. | ||
/// * `fernet` - Fernet object to encrypt the auth payload that will be set as `session_token` cookie. | ||
/// * `session` - Session struct that holds the `session_mapping` to handle sessions. | ||
/// * `metadata` - Struct containing metadata of the application. | ||
/// * `config` - Configuration data for the application. | ||
/// * `template` - Configuration container for the loaded templates. | ||
/// | ||
/// # Returns | ||
/// | ||
/// Returns an `HTTPResponse` with the cookie for `session_token` reset if available. | ||
#[get("/monitor")] | ||
pub async fn monitor(request: HttpRequest, | ||
fernet: web::Data<Arc<Fernet>>, | ||
session: web::Data<Arc<constant::Session>>, | ||
metadata: web::Data<Arc<constant::MetaData>>, | ||
config: web::Data<Arc<squire::settings::Config>>, | ||
template: web::Data<Arc<minijinja::Environment<'static>>>) -> HttpResponse { | ||
let monitor_template = template.get_template("monitor").unwrap(); | ||
let mut response = HttpResponse::build(StatusCode::OK); | ||
response.content_type("text/html; charset=utf-8"); | ||
|
||
let auth_response = squire::authenticator::verify_token(&request, &config, &fernet, &session); | ||
if !auth_response.ok { | ||
return routes::auth::failed_auth(auth_response); | ||
} | ||
log::debug!("Session Validation Response: {}", auth_response.detail); | ||
|
||
let (sys_info_basic_struct, sys_info_mem_storage_struct) = system_info::sys_info::get_sys_info(); | ||
let sys_info_network_struct = system_info::network::get_network_info().await; | ||
|
||
let sys_info_basic = serde_json::to_value(&sys_info_basic_struct).unwrap(); | ||
let sys_info_mem_storage = serde_json::to_value(&sys_info_mem_storage_struct).unwrap(); | ||
let sys_info_network = serde_json::to_value(&sys_info_network_struct).unwrap(); | ||
|
||
let rendered = monitor_template.render(minijinja::context!( | ||
version => metadata.pkg_version, | ||
logout => "/logout", | ||
sys_info_basic => sys_info_basic, | ||
sys_info_mem_storage => sys_info_mem_storage, | ||
sys_info_network => sys_info_network, | ||
)).unwrap(); | ||
|
||
let mut cookie = Cookie::new("session_token", ""); | ||
cookie.set_same_site(SameSite::Strict); | ||
cookie.make_removal(); | ||
response.cookie(cookie); | ||
response.body(rendered) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
pub mod sys_info; | ||
pub mod network; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
use regex::Regex; | ||
use reqwest; | ||
use serde::{Deserialize, Serialize}; | ||
use std::net::{SocketAddr, UdpSocket}; | ||
|
||
// Define the IP regex pattern | ||
fn ip_regex() -> Regex { | ||
Regex::new( | ||
r"^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$" | ||
).unwrap() | ||
} | ||
|
||
// Synchronous function to retrieve the public IP address | ||
async fn public_ip_address() -> Option<String> { | ||
let ip_regex = ip_regex(); | ||
|
||
// Mapping URLs to their expected response types | ||
let mapping: Vec<(&str, bool)> = vec![ | ||
("https://checkip.amazonaws.com/", true), // expects text | ||
("https://api.ipify.org/", true), // expects text | ||
("https://ipinfo.io/ip/", true), // expects text | ||
("https://v4.ident.me/", true), // expects text | ||
("https://httpbin.org/ip", false), // expects JSON | ||
("https://myip.dnsomatic.com/", true), // expects text | ||
]; | ||
|
||
for (url, expects_text) in mapping { | ||
match reqwest::get(url).await { | ||
Ok(response) => { | ||
let extracted_ip = if expects_text { | ||
response.text().await.unwrap_or_default().trim().to_string() | ||
} else { | ||
response.json::<serde_json::Value>().await.ok() | ||
.and_then(|json| json["origin"].as_str().map(str::to_string)) | ||
.unwrap_or_default() | ||
}; | ||
|
||
if ip_regex.is_match(&extracted_ip) { | ||
return Some(extracted_ip); | ||
} | ||
} | ||
Err(e) => { | ||
eprintln!("Failed to fetch from {}: {}", url, e); | ||
continue; // Move on to the next URL | ||
} | ||
} | ||
} | ||
|
||
None | ||
} | ||
|
||
// Function to retrieve the private IP address | ||
fn private_ip_address() -> Option<String> { | ||
let socket = UdpSocket::bind("0.0.0.0:0").ok()?; | ||
socket.connect("8.8.8.8:80").ok()?; | ||
let local_addr: SocketAddr = socket.local_addr().ok()?; | ||
Some(local_addr.ip().to_string()) | ||
} | ||
|
||
#[derive(Serialize, Deserialize, Debug)] | ||
pub struct SystemInfoNetwork { | ||
private_ip_address: String, | ||
public_ip_address: String, | ||
} | ||
|
||
|
||
pub async fn get_network_info() -> SystemInfoNetwork { | ||
let private_ip = private_ip_address().unwrap_or_default(); | ||
let public_ip = public_ip_address().await.unwrap_or_default(); | ||
SystemInfoNetwork { | ||
private_ip_address: private_ip, | ||
public_ip_address: public_ip, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
use chrono::Utc; | ||
use serde::{Deserialize, Serialize}; | ||
use sysinfo::{DiskExt, System, SystemExt}; | ||
|
||
// Helper function to format the duration | ||
fn convert_seconds(seconds: i64) -> String { | ||
let days = seconds / 86_400; // 86,400 seconds in a day | ||
let hours = (seconds % 86_400) / 3_600; // 3,600 seconds in an hour | ||
let minutes = (seconds % 3_600) / 60; // 60 seconds in a minute | ||
let remaining_seconds = seconds % 60; | ||
|
||
let mut result = Vec::new(); | ||
|
||
if days > 0 { | ||
result.push(format!("{} day{}", days, if days > 1 { "s" } else { "" })); | ||
} | ||
if hours > 0 { | ||
result.push(format!("{} hour{}", hours, if hours > 1 { "s" } else { "" })); | ||
} | ||
if minutes > 0 && result.len() < 2 { | ||
result.push(format!("{} minute{}", minutes, if minutes > 1 { "s" } else { "" })); | ||
} | ||
if remaining_seconds > 0 && result.len() < 2 { | ||
result.push(format!("{} second{}", remaining_seconds, if remaining_seconds > 1 { "s" } else { "" })); | ||
} | ||
result.join(" and ") | ||
} | ||
|
||
// Helper function to convert size | ||
fn size_converter(byte_size: u64) -> String { | ||
let size_name = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; | ||
let mut index = 0; | ||
let mut size = byte_size as f64; | ||
|
||
while size >= 1024.0 && index < size_name.len() - 1 { | ||
size /= 1024.0; | ||
index += 1; | ||
} | ||
|
||
format!("{:.2} {}", size, size_name[index]) | ||
} | ||
|
||
// Struct to hold system information in a JSON-serializable format | ||
#[derive(Serialize, Deserialize, Debug)] | ||
pub struct SystemInfoBasic { | ||
node: String, | ||
system: String, | ||
architecture: String, | ||
cpu_cores: String, | ||
uptime: String, | ||
} | ||
|
||
#[derive(Serialize, Deserialize, Debug)] | ||
pub struct SystemInfoMemStorage { | ||
memory: String, | ||
swap: String, | ||
storage: String, | ||
} | ||
|
||
|
||
// Function to collect system information and return as a JSON-serializable struct | ||
pub fn get_sys_info() -> (SystemInfoBasic, SystemInfoMemStorage) { | ||
let mut sys = System::new_all(); | ||
sys.refresh_all(); | ||
|
||
// Uptime | ||
let boot_time = sys.boot_time(); | ||
let uptime_duration = Utc::now().timestamp() - boot_time as i64; | ||
let uptime = convert_seconds(uptime_duration); | ||
|
||
let total_memory = size_converter(sys.total_memory()); // in bytes | ||
let total_swap = size_converter(sys.total_swap()); // in bytes | ||
let total_storage = size_converter(sys.disks().iter().map(|disk| disk.total_space()).sum::<u64>()); | ||
|
||
// Basic and Memory/Storage Info | ||
let basic = SystemInfoBasic { | ||
node: sys.host_name().unwrap_or_else(|| "Unknown".to_string()), | ||
system: sys.name().unwrap_or_else(|| "Unknown".to_string()), | ||
architecture: std::env::consts::ARCH.to_string(), | ||
uptime, | ||
cpu_cores: sys.cpus().len().to_string(), | ||
}; | ||
let mem_storage = SystemInfoMemStorage { | ||
memory: total_memory, | ||
swap: total_swap, | ||
storage: total_storage, | ||
}; | ||
(basic, mem_storage) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
/// Get the HTML content to render the session unauthorized page. | ||
/// | ||
/// # See Also | ||
/// | ||
/// - This page is served as a response for all the entry points, | ||
/// when the user tries to access a page without valid authentication. | ||
/// | ||
/// # Returns | ||
/// | ||
/// A `String` version of the HTML, CSS and JS content. | ||
pub fn get_content() -> String { | ||
r###"<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> | ||
<title>RuStream - Self-hosted Streaming Engine - v{{ version }}</title> | ||
<meta property="og:type" content="MediaStreaming"> | ||
<meta name="keywords" content="Python, streaming, fastapi, JavaScript, HTML, CSS"> | ||
<meta name="author" content="Vignesh Rao"> | ||
<meta content="width=device-width, initial-scale=1" name="viewport"> | ||
<!-- Favicon.ico and Apple Touch Icon --> | ||
<link rel="icon" href="https://thevickypedia.github.io/open-source/images/logo/actix.ico"> | ||
<link rel="apple-touch-icon" href="https://thevickypedia.github.io/open-source/images/logo/actix.png"> | ||
<style> | ||
/* Google fonts with a backup alternative */ | ||
@import url('https://fonts.googleapis.com/css2?family=Ubuntu:wght@400;500;700&display=swap'); | ||
* { | ||
font-family: 'Ubuntu', 'PT Serif', sans-serif; | ||
} | ||
img { | ||
display: block; | ||
margin-left: auto; | ||
margin-right: auto; | ||
} | ||
:is(h1, h2, h3, h4, h5, h6) | ||
{ | ||
text-align: center; | ||
color: #F0F0F0; | ||
} | ||
button { | ||
width: 100px; | ||
margin: 0 auto; | ||
display: block; | ||
} | ||
body { | ||
background-color: #151515; | ||
} | ||
</style> | ||
<noscript> | ||
<style> | ||
body { | ||
width: 100%; | ||
height: 100%; | ||
overflow: hidden; | ||
} | ||
</style> | ||
<div style="position: fixed; text-align:center; height: 100%; width: 100%; background-color: #151515;"> | ||
<h2 style="margin-top:5%">This page requires JavaScript | ||
to be enabled. | ||
<br><br> | ||
Please refer <a href="https://www.enable-javascript.com/">enable-javascript</a> for how to. | ||
</h2> | ||
<form> | ||
<button type="submit" onClick="<meta httpEquiv='refresh' content='0'>">RETRY</button> | ||
</form> | ||
</div> | ||
</noscript> | ||
</head> | ||
<body> | ||
<h2 style="margin-top:5%">{{ title }}</h2> | ||
<h3>{{ description }}</h3> | ||
<p> | ||
<img src="https://thevickypedia.github.io/open-source/images/gif/lockscape.gif" | ||
onerror="this.src='https://vigneshrao.com/open-source/images/gif/lockscape.gif'" | ||
width="200" height="170" alt="Image" class="center"> | ||
</p> | ||
<button style="text-align:center" onClick="window.location.href = '{{ button_link }}';">{{ button_text }}</button> | ||
<br> | ||
<button style="text-align:center" onClick="alert('{{ help }}');">HELP | ||
</button> | ||
<h4>Click <a href="https://vigneshrao.com/contact">HERE</a> to reach out.</h4> | ||
</body> | ||
{% if block_navigation %} | ||
<!-- control the behavior of the browser's navigation without triggering a full page reload --> | ||
<script> | ||
document.addEventListener('DOMContentLoaded', function() { | ||
history.pushState(null, document.title, location.href); | ||
window.addEventListener('popstate', function (event) { | ||
history.pushState(null, document.title, location.href); | ||
}); | ||
}); | ||
</script> | ||
{% endif %} | ||
</html> | ||
"###.to_string() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.