Merge pull request #406 from AppFlowy-IO/admin-frontend/leave-workspace
admin portal: leave workspace
This commit is contained in:
commit
aa3f0fd86e
|
|
@ -238,6 +238,26 @@ pub async fn invite_user_to_workspace(
|
||||||
check_response(resp).await
|
check_response(resp).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn leave_workspace(
|
||||||
|
access_token: &str,
|
||||||
|
workspace_id: &str,
|
||||||
|
appflowy_cloud_base_url: &str,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let http_client = reqwest::Client::new();
|
||||||
|
let url = format!(
|
||||||
|
"{}/api/workspace/{}/leave",
|
||||||
|
appflowy_cloud_base_url, workspace_id
|
||||||
|
);
|
||||||
|
let resp = http_client
|
||||||
|
.post(url)
|
||||||
|
.header("Authorization", format!("Bearer {}", access_token))
|
||||||
|
.json(&())
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
check_response(resp).await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn accept_workspace_invitation(
|
pub async fn accept_workspace_invitation(
|
||||||
access_token: &str,
|
access_token: &str,
|
||||||
invite_id: &str,
|
invite_id: &str,
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,12 @@ pub struct Invite {
|
||||||
pub pending_workspace_invitations: Vec<AFWorkspaceInvitation>,
|
pub pending_workspace_invitations: Vec<AFWorkspaceInvitation>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "components/shared_workspaces.html")]
|
||||||
|
pub struct SharedWorkspaces {
|
||||||
|
pub shared_workspaces: Vec<AFWorkspace>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "components/admin_navigate.html")]
|
#[template(path = "components/admin_navigate.html")]
|
||||||
pub struct AdminNavigate;
|
pub struct AdminNavigate;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
use crate::error::WebApiError;
|
use crate::error::WebApiError;
|
||||||
use crate::ext::api::{accept_workspace_invitation, invite_user_to_workspace, verify_token_cloud};
|
use crate::ext::api::{
|
||||||
|
accept_workspace_invitation, invite_user_to_workspace, leave_workspace, verify_token_cloud,
|
||||||
|
};
|
||||||
use crate::models::{
|
use crate::models::{
|
||||||
WebApiAdminCreateUserRequest, WebApiChangePasswordRequest, WebApiCreateSSOProviderRequest,
|
WebApiAdminCreateUserRequest, WebApiChangePasswordRequest, WebApiCreateSSOProviderRequest,
|
||||||
WebApiInviteUserRequest, WebApiPutUserRequest,
|
WebApiInviteUserRequest, WebApiPutUserRequest,
|
||||||
|
|
@ -34,6 +36,7 @@ pub fn router() -> Router<AppState> {
|
||||||
.route("/oauth_login/:provider", post(post_oauth_login_handler))
|
.route("/oauth_login/:provider", post(post_oauth_login_handler))
|
||||||
.route("/invite", post(invite_handler))
|
.route("/invite", post(invite_handler))
|
||||||
.route("/workspace/:workspace_id/invite", post(workspace_invite_handler))
|
.route("/workspace/:workspace_id/invite", post(workspace_invite_handler))
|
||||||
|
.route("/workspace/:workspace_id/leave", post(leave_workspace_handler))
|
||||||
.route("/invite/:invite_id/accept", post(invite_accept_handler))
|
.route("/invite/:invite_id/accept", post(invite_accept_handler))
|
||||||
.route("/open_app", post(open_app_handler))
|
.route("/open_app", post(open_app_handler))
|
||||||
|
|
||||||
|
|
@ -148,11 +151,26 @@ pub async fn workspace_invite_handler(
|
||||||
Ok(WebApiResponse::<()>::from_str("Invitation sent".into()))
|
Ok(WebApiResponse::<()>::from_str("Invitation sent".into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn leave_workspace_handler(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
session: UserSession,
|
||||||
|
Path(workspace_id): Path<String>,
|
||||||
|
) -> Result<WebApiResponse<()>, WebApiError<'static>> {
|
||||||
|
leave_workspace(
|
||||||
|
&session.token.access_token,
|
||||||
|
&workspace_id,
|
||||||
|
&state.appflowy_cloud_url,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(WebApiResponse::<()>::from_str("Left workspace".into()))
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn invite_accept_handler(
|
pub async fn invite_accept_handler(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
session: UserSession,
|
session: UserSession,
|
||||||
Path(invite_id): Path<String>,
|
Path(invite_id): Path<String>,
|
||||||
) -> Result<WebApiResponse<()>, WebApiError<'static>> {
|
) -> Result<HeaderMap, WebApiError<'static>> {
|
||||||
accept_workspace_invitation(
|
accept_workspace_invitation(
|
||||||
&session.token.access_token,
|
&session.token.access_token,
|
||||||
&invite_id,
|
&invite_id,
|
||||||
|
|
@ -160,9 +178,7 @@ pub async fn invite_accept_handler(
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(WebApiResponse::<()>::from_str(
|
Ok(htmx_trigger("workspaceInvitationAccepted"))
|
||||||
"Invitation accepted.\nPlease refresh page to see changes".into(),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn change_password_handler(
|
pub async fn change_password_handler(
|
||||||
|
|
@ -300,6 +316,12 @@ pub async fn login_refresh_handler(
|
||||||
))
|
))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
verify_token_cloud(
|
||||||
|
token.access_token.as_str(),
|
||||||
|
state.appflowy_cloud_url.as_str(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let new_session_id = uuid::Uuid::new_v4();
|
let new_session_id = uuid::Uuid::new_v4();
|
||||||
let new_session = session::UserSession::new(new_session_id.to_string(), token);
|
let new_session = session::UserSession::new(new_session_id.to_string(), token);
|
||||||
state.session_store.put_user_session(&new_session).await?;
|
state.session_store.put_user_session(&new_session).await?;
|
||||||
|
|
@ -390,6 +412,12 @@ fn htmx_redirect(url: &str) -> HeaderMap {
|
||||||
h
|
h
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn htmx_trigger(trigger: &str) -> HeaderMap {
|
||||||
|
let mut h = HeaderMap::new();
|
||||||
|
h.insert("HX-Trigger", trigger.parse().unwrap());
|
||||||
|
h
|
||||||
|
}
|
||||||
|
|
||||||
fn new_session_cookie(id: uuid::Uuid) -> Cookie<'static> {
|
fn new_session_cookie(id: uuid::Uuid) -> Cookie<'static> {
|
||||||
let mut cookie = Cookie::new("session_id", id.to_string());
|
let mut cookie = Cookie::new("session_id", id.to_string());
|
||||||
cookie.set_path("/");
|
cookie.set_path("/");
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,9 @@ pub fn component_router() -> Router<AppState> {
|
||||||
// User actions
|
// User actions
|
||||||
.route("/user/navigate", get(user_navigate_handler))
|
.route("/user/navigate", get(user_navigate_handler))
|
||||||
.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/shared-workspaces", get(shared_workspaces_handler))
|
||||||
.route("/user/user-usage", get(user_usage_handler))
|
.route("/user/user-usage", get(user_usage_handler))
|
||||||
.route("/user/workspace-usage", get(workspace_usage_handler))
|
.route("/user/workspace-usage", get(workspace_usage_handler))
|
||||||
|
|
||||||
|
|
@ -93,14 +94,34 @@ pub async fn admin_navigate_handler() -> Result<Html<String>, WebAppError> {
|
||||||
render_template(templates::AdminNavigate)
|
render_template(templates::AdminNavigate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn shared_workspaces_handler(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
session: UserSession,
|
||||||
|
) -> Result<Html<String>, WebAppError> {
|
||||||
|
let user_workspaces =
|
||||||
|
get_user_workspaces(&session.token.access_token, &state.appflowy_cloud_url).await?;
|
||||||
|
|
||||||
|
let profile = get_user_profile(
|
||||||
|
session.token.access_token.as_str(),
|
||||||
|
state.appflowy_cloud_url.as_str(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let shared_workspaces = user_workspaces
|
||||||
|
.into_iter()
|
||||||
|
.filter(|workspace| workspace.owner_uid != profile.uid)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
render_template(templates::SharedWorkspaces { shared_workspaces })
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn user_invite_handler(
|
pub async fn user_invite_handler(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
session: UserSession,
|
session: UserSession,
|
||||||
) -> Result<Html<String>, WebAppError> {
|
) -> Result<Html<String>, WebAppError> {
|
||||||
let user_workspaces =
|
let user_workspaces =
|
||||||
get_user_workspaces(&session.token.access_token, &state.appflowy_cloud_url).await;
|
get_user_workspaces(&session.token.access_token, &state.appflowy_cloud_url).await?;
|
||||||
|
|
||||||
let user_workspaces = user_workspaces?;
|
|
||||||
let profile = get_user_profile(
|
let profile = get_user_profile(
|
||||||
session.token.access_token.as_str(),
|
session.token.access_token.as_str(),
|
||||||
state.appflowy_cloud_url.as_str(),
|
state.appflowy_cloud_url.as_str(),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<div>
|
<div>
|
||||||
<h3>Password Change</h3>
|
<h3>Password Change</h3>
|
||||||
<form hx-post="/web-api/change_password" hx-target="#none">
|
<form hx-post="/web-api/change-password" hx-target="#none">
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td>New Password:</td>
|
<td>New Password:</td>
|
||||||
|
|
|
||||||
|
|
@ -24,20 +24,13 @@
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
<h4>Workspaces shared with you</h4>
|
<h4>Workspaces shared with you</h4>
|
||||||
<table class="cyan-table table">
|
<div
|
||||||
<thead>
|
hx-get="/web/components/user/shared-workspaces"
|
||||||
<tr>
|
hx-trigger="workspaceInvitationAccepted from:body"
|
||||||
<th>Workspace Name</th>
|
hx-swap="innerHTML"
|
||||||
<th>Owner Name</th>
|
>
|
||||||
</tr>
|
{% include "shared_workspaces.html" %}
|
||||||
</thead>
|
</div>
|
||||||
{% for shared_workspace in shared_workspaces %}
|
|
||||||
<tr>
|
|
||||||
<td> {{ shared_workspace.workspace_name|escape }} </td>
|
|
||||||
<td> {{ shared_workspace.owner_name|escape }} </td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
<h4>Invite another user to your workspace</h4>
|
<h4>Invite another user to your workspace</h4>
|
||||||
|
|
@ -87,7 +80,11 @@
|
||||||
<td> {{ pending_workspace_invitation.workspace_name|default("")|escape }} </td>
|
<td> {{ pending_workspace_invitation.workspace_name|default("")|escape }} </td>
|
||||||
<td> {{ pending_workspace_invitation.inviter_email|default("")|escape }} </td>
|
<td> {{ pending_workspace_invitation.inviter_email|default("")|escape }} </td>
|
||||||
<td>
|
<td>
|
||||||
<form hx-post="/web-api/invite/{{ pending_workspace_invitation.invite_id|escape }}/accept" hx-target="#none">
|
<form
|
||||||
|
hx-post="/web-api/invite/{{ pending_workspace_invitation.invite_id|escape }}/accept"
|
||||||
|
hx-target="closest tr"
|
||||||
|
hx-swap="delete"
|
||||||
|
>
|
||||||
<button class="button cyan" type="submit">Accept</button>
|
<button class="button cyan" type="submit">Accept</button>
|
||||||
</form>
|
</form>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
<table class="cyan-table table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Workspace Name</th>
|
||||||
|
<th>Owner Name</th>
|
||||||
|
<th>Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
{% for shared_workspace in shared_workspaces %}
|
||||||
|
<tr>
|
||||||
|
<td> {{ shared_workspace.workspace_name|escape }} </td>
|
||||||
|
<td> {{ shared_workspace.owner_name|escape }} </td>
|
||||||
|
<td>
|
||||||
|
<button
|
||||||
|
class="button red"
|
||||||
|
hx-post="/web-api/workspace/{{ shared_workspace.workspace_id|escape }}/leave"
|
||||||
|
hx-confirm="Are you sure?"
|
||||||
|
hx-target="closest tr"
|
||||||
|
hx-swap="delete"
|
||||||
|
>
|
||||||
|
Leave
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
<div
|
<div
|
||||||
class="sidebar-item"
|
class="sidebar-item"
|
||||||
hx-target="#sidebar-content"
|
hx-target="#sidebar-content"
|
||||||
hx-get="/web/components/user/change_password"
|
hx-get="/web/components/user/change-password"
|
||||||
>
|
>
|
||||||
Change Password
|
Change Password
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,7 @@ services:
|
||||||
- RUST_LOG=${RUST_LOG:-info}
|
- RUST_LOG=${RUST_LOG:-info}
|
||||||
- ADMIN_FRONTEND_REDIS_URL=${ADMIN_FRONTEND_REDIS_URL:-redis://redis:6379}
|
- ADMIN_FRONTEND_REDIS_URL=${ADMIN_FRONTEND_REDIS_URL:-redis://redis:6379}
|
||||||
- ADMIN_FRONTEND_GOTRUE_URL=${ADMIN_FRONTEND_GOTRUE_URL:-http://gotrue:9999}
|
- ADMIN_FRONTEND_GOTRUE_URL=${ADMIN_FRONTEND_GOTRUE_URL:-http://gotrue:9999}
|
||||||
|
- ADMIN_FRONTEND_APPFLOWY_CLOUD_URL=${ADMIN_FRONTEND_APPFLOWY_CLOUD_URL:-http://appflowy_cloud:8000}
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue