diff --git a/Cargo.lock b/Cargo.lock index e4940166..2fc8e46a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -321,6 +321,7 @@ dependencies = [ "askama", "axum", "axum-extra", + "database-entity", "dotenvy", "gotrue", "gotrue-entity", diff --git a/admin_frontend/Cargo.toml b/admin_frontend/Cargo.toml index d3443983..b237d1b9 100644 --- a/admin_frontend/Cargo.toml +++ b/admin_frontend/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" # local dependencies gotrue = { path = "../libs/gotrue" } gotrue-entity = { path = "../libs/gotrue-entity" } +database-entity = { path = "../libs/database-entity" } anyhow = "1.0.79" axum = {version = "0.7", features = ["json"]} diff --git a/admin_frontend/assets/base.css b/admin_frontend/assets/base.css index 83a109ff..ba5a04a6 100644 --- a/admin_frontend/assets/base.css +++ b/admin_frontend/assets/base.css @@ -23,6 +23,32 @@ body { cursor: pointer; } +.table { + border-collapse: collapse; /* Collapse borders so there's no double borders */ +} + +.table th, .table td { + padding: 8px; +} + +.yellow-table th { + border: 1px solid var(--af-dark-yellow); + background-color: var(--af-yellow); +} + +.yellow-table td { + border: 1px solid var(--af-dark-yellow); +} + +.red-table th { + border: 1px solid var(--af-dark-red); + background-color: var(--af-red); +} + +.red-table td { + border: 1px solid var(--af-dark-red); +} + .purple:hover { background-color: var(--af-dark-purple); } diff --git a/admin_frontend/src/config.rs b/admin_frontend/src/config.rs index ab1c325c..7068db1c 100644 --- a/admin_frontend/src/config.rs +++ b/admin_frontend/src/config.rs @@ -4,6 +4,8 @@ use tracing::warn; pub struct Config { pub redis_url: String, pub gotrue_url: String, + pub appflowy_cloud_url: String, + pub appflowy_cloud_gateway_url: String, } impl Config { @@ -11,6 +13,15 @@ impl Config { Config { redis_url: get_or_default("ADMIN_FRONTEND_REDIS_URL", "redis://localhost:6379"), gotrue_url: get_or_default("ADMIN_FRONTEND_GOTRUE_URL", "http://localhost:9999"), + appflowy_cloud_url: get_or_default( + "ADMIN_FRONTEND_APPFLOWY_CLOUD_URL", + "http://localhost:8000", + ), + + appflowy_cloud_gateway_url: get_or_default( + "ADMIN_FRONTEND_APPFLOWY_CLOUD_GATEWAY_URL", + "http://localhost:8100", + ), } } } diff --git a/admin_frontend/src/ext.rs b/admin_frontend/src/ext.rs new file mode 100644 index 00000000..fc361ab9 --- /dev/null +++ b/admin_frontend/src/ext.rs @@ -0,0 +1,48 @@ +use database_entity::dto::AFWorkspace; +use serde::Deserialize; + +pub async fn get_user_workspace_count( + auth_header: &str, + appflowy_cloud_base_url: &str, +) -> Result { + let http_client = reqwest::Client::new(); + let resp = http_client + .get(format!("{}/api/workspace", appflowy_cloud_base_url)) + .header("Authorization", format!("Bearer {}", auth_header)) + .send() + .await?; + + let res = resp.json::>>().await?; + Ok(res.data.len() as u32) +} + +pub async fn get_user_workspace_limit( + auth_header: &str, + appflowy_cloud_base_url: &str, +) -> Result { + let http_client = reqwest::Client::new(); + let resp = http_client + .get(format!("{}/api/user/limit", appflowy_cloud_base_url)) + .header("Authorization", format!("Bearer {}", auth_header)) + .send() + .await?; + + let res = resp.json::>().await?; + let b = res.data; + let c = b.workspace_count.unwrap_or({ + tracing::warn!("workspace_count is None, return 0"); + 0 + }); + Ok(c as u32) +} + +#[derive(Debug, Deserialize)] +pub struct JsonResponse { + pub code: u16, + pub data: T, +} + +#[derive(Deserialize)] +pub struct UserUsageLimit { + pub workspace_count: Option, +} diff --git a/admin_frontend/src/main.rs b/admin_frontend/src/main.rs index 7cd436d1..4ba6244f 100644 --- a/admin_frontend/src/main.rs +++ b/admin_frontend/src/main.rs @@ -1,5 +1,6 @@ mod config; mod error; +mod ext; mod models; mod response; mod session; @@ -50,6 +51,8 @@ async fn main() { let session_store = session::SessionStorage::new(redis_client); let state = AppState { + appflowy_cloud_url: config.appflowy_cloud_url, + appflowy_cloud_gateway_url: config.appflowy_cloud_gateway_url, gotrue_client, session_store, }; @@ -85,6 +88,8 @@ async fn main() { #[derive(Clone)] pub struct AppState { + pub appflowy_cloud_url: String, + pub appflowy_cloud_gateway_url: String, pub gotrue_client: gotrue::api::Client, pub session_store: session::SessionStorage, } diff --git a/admin_frontend/src/templates.rs b/admin_frontend/src/templates.rs index b2414b01..c4045d54 100644 --- a/admin_frontend/src/templates.rs +++ b/admin_frontend/src/templates.rs @@ -1,6 +1,23 @@ use askama::Template; use gotrue_entity::{dto::User, sso::SSOProvider}; +#[derive(Template)] +#[template(path = "components/user_usage.html")] +pub struct UserUsage { + pub workspace_count: u32, + pub workspace_limit: u32, +} + +// #[derive(Template)] +// #[template(path = "components/workspace_usage.html")] +// pub struct WorkspaceUsage { +// pub name: String, +// pub member_count: u32, +// pub member_limit: u32, +// pub total_object_size: u64, +// pub total_object_limit: u64, +// } + #[derive(Template)] #[template(path = "components/admin_sso_detail.html")] pub struct SsoDetail { diff --git a/admin_frontend/src/web_app.rs b/admin_frontend/src/web_app.rs index d8873f63..1d14cff7 100644 --- a/admin_frontend/src/web_app.rs +++ b/admin_frontend/src/web_app.rs @@ -1,4 +1,5 @@ use crate::error::WebAppError; +use crate::ext::{get_user_workspace_count, get_user_workspace_limit}; use crate::session::UserSession; use askama::Template; use axum::extract::{Path, State}; @@ -29,6 +30,8 @@ pub fn component_router() -> Router { .route("/user/user", get(user_user_handler)) .route("/user/change_password", get(user_change_password_handler)) .route("/user/invite", get(user_invite_handler)) + .route("/user/user-usage", get(user_usage_handler)) + .route("/user/workspace-usage", get(workspace_usage_handler)) // Admin actions .route("/admin/navigate", get(admin_navigate_handler)) @@ -87,9 +90,45 @@ pub async fn admin_navigate_handler() -> Result, WebAppError> { } pub async fn user_invite_handler() -> Result, WebAppError> { + println!("----- user_invite_handler"); + render_template(templates::Invite) } +pub async fn user_usage_handler( + State(state): State, + session: UserSession, +) -> Result, WebAppError> { + println!("----- user_usage_handler"); + + let workspace_count = + get_user_workspace_count(&session.token.access_token, &state.appflowy_cloud_url) + .await + .unwrap_or_else(|err| { + tracing::error!("Error getting user workspace count: {:?}", err); + 0 + }); + + let workspace_limit = get_user_workspace_limit( + &session.token.access_token, + &state.appflowy_cloud_gateway_url, + ) + .await; + println!("workspace count: {:?}", workspace_count); + + render_template(templates::Invite) + + // todo!(); + // render_template(templates::UserUsage { + // workspace_count: 500, + // workspace_limit: 1000, + // }) +} + +pub async fn workspace_usage_handler(session: UserSession) -> Result, WebAppError> { + todo!() +} + pub async fn admin_users_create_handler() -> Result, WebAppError> { render_template(templates::CreateUser) } diff --git a/admin_frontend/templates/components/sidebar.html b/admin_frontend/templates/components/sidebar.html index 91cc44e8..fae7afa6 100644 --- a/admin_frontend/templates/components/sidebar.html +++ b/admin_frontend/templates/components/sidebar.html @@ -20,4 +20,18 @@ > Invite + + diff --git a/admin_frontend/templates/components/user_usage.html b/admin_frontend/templates/components/user_usage.html new file mode 100644 index 00000000..6bf5f975 --- /dev/null +++ b/admin_frontend/templates/components/user_usage.html @@ -0,0 +1,22 @@ +
+ + + + + + + + + + + + + +
TypeCurrentLimit
+ Workspaces + + {{ workspace_count|escape }} + + {{ workspace_limit|escape }} +
+
diff --git a/admin_frontend/templates/components/workspace_usage.html b/admin_frontend/templates/components/workspace_usage.html new file mode 100644 index 00000000..464e63ed --- /dev/null +++ b/admin_frontend/templates/components/workspace_usage.html @@ -0,0 +1,20 @@ +
+ + + + + + + + + + + + + + + + + +
Workspace NameMembersMembers LimitStorage SpaceStorage Space Limit
{{ name|escape }} {{ member_count|escape }} {{ member_limit|escape }} {{ total_blob_size|escape }} {{ total_blob_size_limit|escape }}
+