feat: wip user usage indicator

This commit is contained in:
Zack Fu Zi Xiang 2024-03-13 17:53:34 +08:00
parent e4934fbf2a
commit af32cd62d0
No known key found for this signature in database
11 changed files with 204 additions and 0 deletions

1
Cargo.lock generated
View File

@ -321,6 +321,7 @@ dependencies = [
"askama",
"axum",
"axum-extra",
"database-entity",
"dotenvy",
"gotrue",
"gotrue-entity",

View File

@ -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"]}

View File

@ -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);
}

View File

@ -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",
),
}
}
}

48
admin_frontend/src/ext.rs Normal file
View File

@ -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<u32, reqwest::Error> {
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::<JsonResponse<Vec<AFWorkspace>>>().await?;
Ok(res.data.len() as u32)
}
pub async fn get_user_workspace_limit(
auth_header: &str,
appflowy_cloud_base_url: &str,
) -> Result<u32, reqwest::Error> {
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::<JsonResponse<UserUsageLimit>>().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<T> {
pub code: u16,
pub data: T,
}
#[derive(Deserialize)]
pub struct UserUsageLimit {
pub workspace_count: Option<i64>,
}

View File

@ -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,
}

View File

@ -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 {

View File

@ -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<AppState> {
.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<Html<String>, WebAppError> {
}
pub async fn user_invite_handler() -> Result<Html<String>, WebAppError> {
println!("----- user_invite_handler");
render_template(templates::Invite)
}
pub async fn user_usage_handler(
State(state): State<AppState>,
session: UserSession,
) -> Result<Html<String>, 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<Html<String>, WebAppError> {
todo!()
}
pub async fn admin_users_create_handler() -> Result<Html<String>, WebAppError> {
render_template(templates::CreateUser)
}

View File

@ -20,4 +20,18 @@
>
Invite
</div>
<div
class="sidebar-item"
hx-target="#sidebar-content"
hx-get="/web/components/user/user-usage"
>
User Usage
</div>
<div
class="sidebar-item"
hx-target="#sidebar-content"
hx-get="/web/components/user/workspace-usage"
>
Workspace Usage
</div>
</div>

View File

@ -0,0 +1,22 @@
<div>
<table class="yellow-table table">
<thead>
<tr>
<th>Type</th>
<th>Current</th>
<th>Limit</th>
</tr>
</thead>
<tr>
<td>
Workspaces
</td>
<td>
{{ workspace_count|escape }}
</td>
<td>
{{ workspace_limit|escape }}
</td>
</tr>
</table>
</div>

View File

@ -0,0 +1,20 @@
<div>
<table class="red-table table">
<thead>
<tr>
<th>Workspace Name</th>
<th>Members</th>
<th>Members Limit</th>
<th>Storage Space</th>
<th>Storage Space Limit</th>
</tr>
</thead>
<tr>
<td> {{ name|escape }} </td>
<td> {{ member_count|escape }} </td>
<td> {{ member_limit|escape }} </td>
<td> {{ total_blob_size|escape }} </td>
<td> {{ total_blob_size_limit|escape }} </td>
</tr>
</table>
</div>