diff --git a/crates/app/src/assets.rs b/crates/app/src/assets.rs
index e140d79..bf92db7 100644
--- a/crates/app/src/assets.rs
+++ b/crates/app/src/assets.rs
@@ -90,6 +90,7 @@ pub const MOD_FILE_REPORT: &str = include_str!("./public/html/mod/file_report.ht
pub const MOD_IP_BANS: &str = include_str!("./public/html/mod/ip_bans.html");
pub const MOD_PROFILE: &str = include_str!("./public/html/mod/profile.html");
pub const MOD_WARNINGS: &str = include_str!("./public/html/mod/warnings.html");
+pub const MOD_STATS: &str = include_str!("./public/html/mod/stats.html");
pub const CHATS_APP: &str = include_str!("./public/html/chats/app.html");
pub const CHATS_STREAM: &str = include_str!("./public/html/chats/stream.html");
@@ -258,6 +259,7 @@ pub(crate) async fn write_assets(config: &Config) -> PathBufD {
write_template!(html_path->"mod/ip_bans.html"(crate::assets::MOD_IP_BANS) --config=config);
write_template!(html_path->"mod/profile.html"(crate::assets::MOD_PROFILE) --config=config);
write_template!(html_path->"mod/warnings.html"(crate::assets::MOD_WARNINGS) --config=config);
+ write_template!(html_path->"mod/stats.html"(crate::assets::MOD_STATS) --config=config);
write_template!(html_path->"chats/app.html"(crate::assets::CHATS_APP) -d "chats" --config=config);
write_template!(html_path->"chats/stream.html"(crate::assets::CHATS_STREAM) --config=config);
diff --git a/crates/app/src/langs/en-US.toml b/crates/app/src/langs/en-US.toml
index 7adc6d8..c3e7e7a 100644
--- a/crates/app/src/langs/en-US.toml
+++ b/crates/app/src/langs/en-US.toml
@@ -14,6 +14,7 @@ version = "1.0.0"
"general:link.audit_log" = "Audit log"
"general:link.reports" = "Reports"
"general:link.ip_bans" = "IP bans"
+"general:link.stats" = "Stats"
"general:action.save" = "Save"
"general:action.delete" = "Delete"
"general:action.accept" = "Accept"
diff --git a/crates/app/src/public/html/macros.html b/crates/app/src/public/html/macros.html
index 95a651b..50968fa 100644
--- a/crates/app/src/public/html/macros.html
+++ b/crates/app/src/public/html/macros.html
@@ -113,6 +113,11 @@
{{ icon "ban" }}
{{ text "general:link.ip_bans" }}
+
+
+ {{ icon "chart-line" }}
+ {{ text "general:link.stats" }}
+
{% endif %}
{{ config.name }}
diff --git a/crates/app/src/public/html/mod/stats.html b/crates/app/src/public/html/mod/stats.html
new file mode 100644
index 0000000..57e5df4
--- /dev/null
+++ b/crates/app/src/public/html/mod/stats.html
@@ -0,0 +1,31 @@
+{% extends "root.html" %} {% block head %}
+
Audit log - {{ config.name }}
+{% endblock %} {% block body %} {{ macros::nav() }}
+
+
+
+ {{ icon "chart-line" }}
+ {{ text "general:link.stats" }}
+
+
+
+
+ -
+ Active user streams:
+ {{ active_users }}
+
+
+ -
+ Active chat subscriptions:
+ {{ active_users_chats }}
+
+
+ -
+ Socket threads:
+ {{ (active_users_chats + active_users) * 3 }}
+
+
+
+
+
+{% endblock %}
diff --git a/crates/app/src/routes/api/v1/auth/profile.rs b/crates/app/src/routes/api/v1/auth/profile.rs
index 47d703c..444e043 100644
--- a/crates/app/src/routes/api/v1/auth/profile.rs
+++ b/crates/app/src/routes/api/v1/auth/profile.rs
@@ -524,12 +524,14 @@ pub async fn handle_socket(socket: WebSocket, db: DataManager, user_id: String,
}
});
+ db.2.incr("atto.active_connections:user".to_string()).await;
tokio::select! {
_ = (&mut recv_task) => redis_task.abort(),
_ = (&mut redis_task) => recv_task.abort()
}
heartbeat_task.abort(); // kill
+ db.2.decr("atto.active_connections:user".to_string()).await;
tracing::info!("socket terminate");
}
diff --git a/crates/app/src/routes/api/v1/channels/messages.rs b/crates/app/src/routes/api/v1/channels/messages.rs
index 0124e6e..5d57167 100644
--- a/crates/app/src/routes/api/v1/channels/messages.rs
+++ b/crates/app/src/routes/api/v1/channels/messages.rs
@@ -258,12 +258,15 @@ pub async fn handle_socket(socket: WebSocket, db: DataManager, community_id: Str
}
});
+ db.2.incr("atto.active_connections:chats".to_string()).await;
+
tokio::select! {
_ = (&mut recv_task) => redis_task.abort(),
_ = (&mut redis_task) => recv_task.abort()
}
heartbeat_task.abort(); // kill
+ db.2.decr("atto.active_connections:chats".to_string()).await;
tracing::info!("socket terminate");
}
diff --git a/crates/app/src/routes/pages/mod.rs b/crates/app/src/routes/pages/mod.rs
index 9986e7c..2334f2e 100644
--- a/crates/app/src/routes/pages/mod.rs
+++ b/crates/app/src/routes/pages/mod.rs
@@ -56,6 +56,7 @@ pub fn routes() -> Router {
"/mod_panel/profile/{id}/warnings",
get(mod_panel::manage_profile_warnings_request),
)
+ .route("/mod_panel/stats", get(mod_panel::stats_request))
// auth
.route("/auth/register", get(auth::register_request))
.route("/auth/login", get(auth::login_request))
diff --git a/crates/app/src/routes/pages/mod_panel.rs b/crates/app/src/routes/pages/mod_panel.rs
index f9d21c1..9ee1e16 100644
--- a/crates/app/src/routes/pages/mod_panel.rs
+++ b/crates/app/src/routes/pages/mod_panel.rs
@@ -7,7 +7,10 @@ use axum::{
};
use axum_extra::extract::CookieJar;
use serde::Deserialize;
-use tetratto_core::model::{Error, permissions::FinePermission, reactions::AssetType};
+use tetratto_core::{
+ cache::Cache,
+ model::{permissions::FinePermission, reactions::AssetType, Error},
+};
/// `/mod_panel/audit_log`
pub async fn audit_log_request(
@@ -232,3 +235,51 @@ pub async fn manage_profile_warnings_request(
// return
Ok(Html(data.1.render("mod/warnings.html", &context).unwrap()))
}
+
+/// `/mod_panel/stats`
+pub async fn stats_request(jar: CookieJar, Extension(data): Extension) -> impl IntoResponse {
+ let data = data.read().await;
+ let user = match get_user_from_token!(jar, data.0) {
+ Some(ua) => ua,
+ None => {
+ return Err(Html(
+ render_error(Error::NotAllowed, &jar, &data, &None).await,
+ ));
+ }
+ };
+
+ if !user.permissions.check(FinePermission::VIEW_AUDIT_LOG) {
+ return Err(Html(
+ render_error(Error::NotAllowed, &jar, &data, &None).await,
+ ));
+ }
+
+ let lang = get_lang!(jar, data.0);
+ let mut context = initial_context(&data.0.0, lang, &Some(user)).await;
+
+ context.insert(
+ "active_users_chats",
+ &data
+ .0
+ .2
+ .get("atto.active_connections:chats".to_string())
+ .await
+ .unwrap_or("0".to_string())
+ .parse::()
+ .unwrap(),
+ );
+ context.insert(
+ "active_users",
+ &data
+ .0
+ .2
+ .get("atto.active_connections:users".to_string())
+ .await
+ .unwrap_or("0".to_string())
+ .parse::()
+ .unwrap(),
+ );
+
+ // return
+ Ok(Html(data.1.render("mod/stats.html", &context).unwrap()))
+}