feat: wip user usage indicator
This commit is contained in:
parent
e4934fbf2a
commit
af32cd62d0
|
|
@ -321,6 +321,7 @@ dependencies = [
|
||||||
"askama",
|
"askama",
|
||||||
"axum",
|
"axum",
|
||||||
"axum-extra",
|
"axum-extra",
|
||||||
|
"database-entity",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
"gotrue",
|
"gotrue",
|
||||||
"gotrue-entity",
|
"gotrue-entity",
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ edition = "2021"
|
||||||
# local dependencies
|
# local dependencies
|
||||||
gotrue = { path = "../libs/gotrue" }
|
gotrue = { path = "../libs/gotrue" }
|
||||||
gotrue-entity = { path = "../libs/gotrue-entity" }
|
gotrue-entity = { path = "../libs/gotrue-entity" }
|
||||||
|
database-entity = { path = "../libs/database-entity" }
|
||||||
|
|
||||||
anyhow = "1.0.79"
|
anyhow = "1.0.79"
|
||||||
axum = {version = "0.7", features = ["json"]}
|
axum = {version = "0.7", features = ["json"]}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,32 @@ body {
|
||||||
cursor: pointer;
|
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 {
|
.purple:hover {
|
||||||
background-color: var(--af-dark-purple);
|
background-color: var(--af-dark-purple);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ use tracing::warn;
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub redis_url: String,
|
pub redis_url: String,
|
||||||
pub gotrue_url: String,
|
pub gotrue_url: String,
|
||||||
|
pub appflowy_cloud_url: String,
|
||||||
|
pub appflowy_cloud_gateway_url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
|
|
@ -11,6 +13,15 @@ impl Config {
|
||||||
Config {
|
Config {
|
||||||
redis_url: get_or_default("ADMIN_FRONTEND_REDIS_URL", "redis://localhost:6379"),
|
redis_url: get_or_default("ADMIN_FRONTEND_REDIS_URL", "redis://localhost:6379"),
|
||||||
gotrue_url: get_or_default("ADMIN_FRONTEND_GOTRUE_URL", "http://localhost:9999"),
|
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",
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>,
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
mod config;
|
mod config;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod ext;
|
||||||
mod models;
|
mod models;
|
||||||
mod response;
|
mod response;
|
||||||
mod session;
|
mod session;
|
||||||
|
|
@ -50,6 +51,8 @@ async fn main() {
|
||||||
let session_store = session::SessionStorage::new(redis_client);
|
let session_store = session::SessionStorage::new(redis_client);
|
||||||
|
|
||||||
let state = AppState {
|
let state = AppState {
|
||||||
|
appflowy_cloud_url: config.appflowy_cloud_url,
|
||||||
|
appflowy_cloud_gateway_url: config.appflowy_cloud_gateway_url,
|
||||||
gotrue_client,
|
gotrue_client,
|
||||||
session_store,
|
session_store,
|
||||||
};
|
};
|
||||||
|
|
@ -85,6 +88,8 @@ async fn main() {
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
|
pub appflowy_cloud_url: String,
|
||||||
|
pub appflowy_cloud_gateway_url: String,
|
||||||
pub gotrue_client: gotrue::api::Client,
|
pub gotrue_client: gotrue::api::Client,
|
||||||
pub session_store: session::SessionStorage,
|
pub session_store: session::SessionStorage,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,23 @@
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
use gotrue_entity::{dto::User, sso::SSOProvider};
|
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)]
|
#[derive(Template)]
|
||||||
#[template(path = "components/admin_sso_detail.html")]
|
#[template(path = "components/admin_sso_detail.html")]
|
||||||
pub struct SsoDetail {
|
pub struct SsoDetail {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::error::WebAppError;
|
use crate::error::WebAppError;
|
||||||
|
use crate::ext::{get_user_workspace_count, get_user_workspace_limit};
|
||||||
use crate::session::UserSession;
|
use crate::session::UserSession;
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
use axum::extract::{Path, State};
|
use axum::extract::{Path, State};
|
||||||
|
|
@ -29,6 +30,8 @@ pub fn component_router() -> Router<AppState> {
|
||||||
.route("/user/user", get(user_user_handler))
|
.route("/user/user", get(user_user_handler))
|
||||||
.route("/user/change_password", get(user_change_password_handler))
|
.route("/user/change_password", get(user_change_password_handler))
|
||||||
.route("/user/invite", get(user_invite_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
|
// Admin actions
|
||||||
.route("/admin/navigate", get(admin_navigate_handler))
|
.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> {
|
pub async fn user_invite_handler() -> Result<Html<String>, WebAppError> {
|
||||||
|
println!("----- user_invite_handler");
|
||||||
|
|
||||||
render_template(templates::Invite)
|
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> {
|
pub async fn admin_users_create_handler() -> Result<Html<String>, WebAppError> {
|
||||||
render_template(templates::CreateUser)
|
render_template(templates::CreateUser)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,4 +20,18 @@
|
||||||
>
|
>
|
||||||
Invite
|
Invite
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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>
|
||||||
Loading…
Reference in New Issue