mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 13:05:32 +09:00
fix: admin API key page fixes (#1360)
This commit is contained in:
1 parent
6064bdf0e7
commit
097fa9d9ce
13 files changed
+188
-126
No files matched your search
@@ -1,12 +1,15 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use super::ActionQuery;
|
||||
use crate::{
|
||||
acl,
|
||||
api::client::{AdminApiClient, ApiResultExt},
|
||||
config::AdminConfig,
|
||||
middleware::{
|
||||
auth::AuthContext,
|
||||
csrf::CsrfToken,
|
||||
flash::{self, FlashData},
|
||||
htmx,
|
||||
},
|
||||
state::AppState,
|
||||
templates,
|
||||
@@ -19,8 +22,6 @@ use axum::{
|
||||
routing::get,
|
||||
};
|
||||
|
||||
use super::ActionQuery;
|
||||
|
||||
pub fn router() -> Router<AppState> {
|
||||
Router::new().route(
|
||||
"/admin-api-keys",
|
||||
@@ -32,6 +33,7 @@ async fn admin_api_keys_page(
|
||||
State(state): State<AppState>,
|
||||
auth: axum::Extension<AuthContext>,
|
||||
csrf: axum::Extension<CsrfToken>,
|
||||
flash: Option<axum::Extension<FlashData>>,
|
||||
) -> Response {
|
||||
let config = state.config();
|
||||
let client = AdminApiClient::new(state.http_client(), config, &auth.0.session);
|
||||
@@ -40,10 +42,12 @@ async fn admin_api_keys_page(
|
||||
.await
|
||||
.log_error("load admin api keys");
|
||||
let available_acls = available_acls(&auth.0);
|
||||
let flash = flash.map(|flash| flash.0.to_flash_message());
|
||||
let markup = templates::pages::admin_api_keys::admin_api_keys_page(
|
||||
config,
|
||||
&auth.0,
|
||||
&csrf.0.0,
|
||||
flash.as_ref(),
|
||||
None,
|
||||
keys.as_deref(),
|
||||
&available_acls,
|
||||
@@ -60,13 +64,14 @@ async fn admin_api_keys_post(
|
||||
) -> Response {
|
||||
let config = state.config();
|
||||
let base = &config.base_path;
|
||||
let is_htmx = htmx::is_htmx_request(request.headers());
|
||||
let form = match MultiValueForm::from_request(request).await {
|
||||
Some(form) => form,
|
||||
None => {
|
||||
return flash::redirect_with_flash(
|
||||
&format!("{base}/admin-api-keys"),
|
||||
return admin_api_key_flash_response(
|
||||
config,
|
||||
FlashData::error("Invalid form data"),
|
||||
config.is_production(),
|
||||
is_htmx,
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -76,40 +81,39 @@ async fn admin_api_keys_post(
|
||||
"create" => {
|
||||
let name = form.clean("name").unwrap_or_default();
|
||||
let acls = form.list_values_any(&["acls[]", "acls"]);
|
||||
if !name.is_empty() {
|
||||
return match client.create_api_key(&name, &acls).await {
|
||||
Ok(created) => {
|
||||
let keys = client
|
||||
.list_api_keys()
|
||||
.await
|
||||
.log_error("reload admin api keys after create");
|
||||
let available_acls = available_acls(&auth.0);
|
||||
let markup = templates::pages::admin_api_keys::admin_api_keys_page(
|
||||
config,
|
||||
&auth.0,
|
||||
&csrf.0.0,
|
||||
Some(&created),
|
||||
keys.as_deref(),
|
||||
&available_acls,
|
||||
);
|
||||
Html(markup.into_string()).into_response()
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::warn!(%error, "admin API request failed: create API key");
|
||||
flash::redirect_with_flash(
|
||||
&format!("{base}/admin-api-keys"),
|
||||
FlashData::error("Failed to create API key"),
|
||||
config.is_production(),
|
||||
)
|
||||
}
|
||||
};
|
||||
}
|
||||
return match client.create_api_key(&name, &acls).await {
|
||||
Ok(created) => {
|
||||
let keys = client
|
||||
.list_api_keys()
|
||||
.await
|
||||
.log_error("reload admin api keys after create");
|
||||
let available_acls = available_acls(&auth.0);
|
||||
let markup = templates::pages::admin_api_keys::admin_api_keys_page(
|
||||
config,
|
||||
&auth.0,
|
||||
&csrf.0.0,
|
||||
None,
|
||||
Some(&created),
|
||||
keys.as_deref(),
|
||||
&available_acls,
|
||||
);
|
||||
Html(markup.into_string()).into_response()
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::warn!(%error, "admin API request failed: create API key");
|
||||
admin_api_key_flash_response(
|
||||
config,
|
||||
FlashData::error("Failed to create API key."),
|
||||
is_htmx,
|
||||
)
|
||||
}
|
||||
};
|
||||
}
|
||||
"revoke" => {
|
||||
if let Some(key_id) = form.clean("key_id") {
|
||||
let result = client.revoke_api_key(&key_id).await;
|
||||
let flash = match result.log_error("revoke API key") {
|
||||
Some(_) => FlashData::success("API key revoked"),
|
||||
Some(_) => FlashData::success("API key revoked."),
|
||||
None => FlashData::error("Failed to revoke API key"),
|
||||
};
|
||||
return flash::redirect_with_flash(
|
||||
@@ -123,11 +127,27 @@ async fn admin_api_keys_post(
|
||||
}
|
||||
flash::redirect_with_flash(
|
||||
&format!("{base}/admin-api-keys"),
|
||||
FlashData::success(format!("API key action '{action}' completed")),
|
||||
FlashData::success(format!("API key action '{action}' completed.")),
|
||||
config.is_production(),
|
||||
)
|
||||
}
|
||||
|
||||
fn admin_api_key_flash_response(
|
||||
config: &AdminConfig,
|
||||
flash_data: FlashData,
|
||||
is_htmx: bool,
|
||||
) -> Response {
|
||||
if is_htmx {
|
||||
htmx::toast_response(&flash_data)
|
||||
} else {
|
||||
flash::redirect_with_flash(
|
||||
&format!("{}/admin-api-keys", config.base_path),
|
||||
flash_data,
|
||||
config.is_production(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn available_acls(auth: &AuthContext) -> Vec<&'static str> {
|
||||
let admin_acls = auth
|
||||
.admin_user
|
||||
|
||||
@@ -352,6 +352,7 @@ pub const HTMX_FLASH_SCRIPT: &str = r#"
|
||||
window.clearTimeout(hideTimer);
|
||||
hideTimer = 0;
|
||||
}
|
||||
if (activeToast && !activeToast.isConnected) activeToast = null;
|
||||
if (!activeToast) {
|
||||
activeToast = document.createElement('div');
|
||||
activeToast.setAttribute('role', 'status');
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use crate::{
|
||||
api::types::{CreateAdminApiKeyResponse, ListAdminApiKeyEntry},
|
||||
api::types::{CreateAdminApiKeyResponse, FlashMessage, ListAdminApiKeyEntry},
|
||||
config::AdminConfig,
|
||||
middleware::auth::AuthContext,
|
||||
templates::{
|
||||
@@ -23,7 +23,7 @@ fn created_key_banner(key: &CreateAdminApiKeyResponse) -> Markup {
|
||||
let key_id = &key.key_id;
|
||||
alert(
|
||||
AlertVariant::Success,
|
||||
Some("API Key Created Successfully"),
|
||||
Some("API Key created successfully."),
|
||||
html! {
|
||||
div class="flex flex-col gap-2" {
|
||||
p class="text-sm text-green-700" {
|
||||
@@ -62,7 +62,8 @@ fn create_form(base: &str, csrf_token: &str, acls: &[&str]) -> Markup {
|
||||
}
|
||||
}
|
||||
form id="create-key-form" method="post"
|
||||
action={(base) "/admin-api-keys?action=create"} {
|
||||
action={(base) "/admin-api-keys?action=create"}
|
||||
data-admin-result-form="true" hx-push-url="false" {
|
||||
(csrf_input(csrf_token))
|
||||
div class="flex flex-col gap-4" {
|
||||
div class="flex flex-col gap-2" {
|
||||
@@ -94,7 +95,7 @@ fn create_form(base: &str, csrf_token: &str, acls: &[&str]) -> Markup {
|
||||
}
|
||||
div class="grid grid-cols-1 gap-3 md:grid-cols-2" {
|
||||
@for acl in acls {
|
||||
(checkbox("acls", acl, acl, true, true))
|
||||
(checkbox("acls", acl, acl, false, true))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -151,7 +152,8 @@ fn api_key_item(base: &str, csrf_token: &str, key: &ListAdminApiKeyEntry) -> Mar
|
||||
}
|
||||
form method="post"
|
||||
action={(base) "/admin-api-keys?action=revoke"}
|
||||
class="flex-shrink-0 self-stretch sm:self-start" {
|
||||
class="flex-shrink-0 self-stretch sm:self-start"
|
||||
data-admin-result-form="true" hx-push-url="false" {
|
||||
(csrf_input(csrf_token))
|
||||
input type="hidden" name="key_id" value=(key_id);
|
||||
button type="submit"
|
||||
@@ -194,6 +196,7 @@ pub fn admin_api_keys_page(
|
||||
config: &AdminConfig,
|
||||
auth: &AuthContext,
|
||||
csrf_token: &str,
|
||||
flash: Option<&FlashMessage>,
|
||||
created_key: Option<&CreateAdminApiKeyResponse>,
|
||||
keys: Option<&[ListAdminApiKeyEntry]>,
|
||||
available_acls: &[&str],
|
||||
@@ -201,7 +204,7 @@ pub fn admin_api_keys_page(
|
||||
let base = &config.base_path;
|
||||
let content = html! {
|
||||
(page_header("Admin API Keys", Some("Create and manage API keys for admin access")))
|
||||
div class="space-y-6" {
|
||||
div class="space-y-6" hx-history=[created_key.is_some().then_some("false")] {
|
||||
@if let Some(ck) = created_key {
|
||||
(created_key_banner(ck))
|
||||
}
|
||||
@@ -214,7 +217,7 @@ pub fn admin_api_keys_page(
|
||||
auth,
|
||||
"Admin API Keys",
|
||||
"admin-api-keys",
|
||||
None,
|
||||
flash,
|
||||
content,
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user