mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 05:25:40 +09:00
feat(admin): require a resolution and fix panel layout and copy (#3335)
This commit is contained in:
1 parent
4db13b0375
commit
6534ee4300
57 files changed
+1186
-255
No files matched your search
@@ -4,6 +4,7 @@ use crate::api::client::{AdminApiClient, ApiResult};
|
||||
use crate::api::types::{FlashLevel, FlashMessage};
|
||||
use crate::middleware::auth::AuthContext;
|
||||
use crate::templates;
|
||||
use crate::utils::timestamps::format_admin_timestamp;
|
||||
use axum::response::{Html, IntoResponse, Response};
|
||||
use serde::Deserialize;
|
||||
|
||||
@@ -201,7 +202,13 @@ async fn execute_check(
|
||||
};
|
||||
match result {
|
||||
Ok(r) if r.banned => match r.expires_at {
|
||||
Some(expires_at) => ("info", format!("{value} is banned until {expires_at}")),
|
||||
Some(expires_at) => (
|
||||
"info",
|
||||
format!(
|
||||
"{value} is banned until {}",
|
||||
format_admin_timestamp(&expires_at)
|
||||
),
|
||||
),
|
||||
None => ("info", format!("{value} is banned")),
|
||||
},
|
||||
Ok(_) => ("info", format!("{value} is NOT banned")),
|
||||
|
||||
@@ -150,7 +150,10 @@ fn build_admin_csp(config: &AdminConfig) -> String {
|
||||
"default-src 'self'".to_owned(),
|
||||
"script-src 'self' 'unsafe-inline'".to_owned(),
|
||||
"style-src 'self' 'unsafe-inline'".to_owned(),
|
||||
format!("img-src 'self' data: blob: {static_cdn} {media} https://fluxer-reports.ewr1.vultrobjects.com"),
|
||||
format!(
|
||||
"img-src 'self' data: blob: {static_cdn} {media} {}",
|
||||
config.reports_bucket_origin
|
||||
),
|
||||
"font-src 'self'".to_owned(),
|
||||
"connect-src 'self'".to_owned(),
|
||||
"object-src 'none'".to_owned(),
|
||||
|
||||
@@ -22,7 +22,6 @@ use axum::{
|
||||
Form, Router,
|
||||
extract::{FromRequest, Path, Query, Request, State},
|
||||
http::HeaderMap,
|
||||
http::StatusCode,
|
||||
response::{Html, IntoResponse, Redirect, Response},
|
||||
routing::get,
|
||||
};
|
||||
@@ -69,6 +68,8 @@ struct ResolveForm {
|
||||
#[serde(default)]
|
||||
resolution: Option<String>,
|
||||
#[serde(default)]
|
||||
public_comment: Option<String>,
|
||||
#[serde(default)]
|
||||
notify_reporter: Option<String>,
|
||||
#[serde(default)]
|
||||
notify_reporter_present: Option<String>,
|
||||
@@ -287,53 +288,52 @@ async fn report_resolve(
|
||||
) -> Response {
|
||||
let config = state.config();
|
||||
let base = &config.base_path;
|
||||
let is_background = request
|
||||
.uri()
|
||||
.query()
|
||||
.is_some_and(|query| query.split('&').any(|part| part == "background=1"));
|
||||
let back = format!("{base}/reports/{report_id}");
|
||||
let form: ResolveForm = match Form::from_request(request, &state).await {
|
||||
Ok(Form(f)) => f,
|
||||
Err(error) => {
|
||||
tracing::warn!(%error, report_id, "failed to parse report resolve form");
|
||||
return flash::redirect_with_flash(
|
||||
&format!("{base}/reports/{report_id}"),
|
||||
&back,
|
||||
FlashData::error("Invalid form data"),
|
||||
config.secure_cookies(),
|
||||
);
|
||||
}
|
||||
};
|
||||
let Some((resolution, label)) =
|
||||
templates::pages::report_detail::resolution_choice(form.resolution.as_deref())
|
||||
else {
|
||||
return flash::redirect_with_flash(
|
||||
&back,
|
||||
FlashData::error(RESOLUTION_REQUIRED),
|
||||
config.secure_cookies(),
|
||||
);
|
||||
};
|
||||
let client = AdminApiClient::new(state.http_client(), config, &auth.0.session);
|
||||
let public_comment = clean_string(form.resolution.as_deref().unwrap_or(""));
|
||||
let public_comment = clean_string(form.public_comment.as_deref().unwrap_or(""));
|
||||
let notify_reporter =
|
||||
form.notify_reporter_present.is_none() || form.notify_reporter.as_deref() == Some("true");
|
||||
let result = client
|
||||
.resolve_report(&report_id, public_comment.as_deref(), notify_reporter, None)
|
||||
.await;
|
||||
match result {
|
||||
Ok(_) => {
|
||||
if is_background {
|
||||
return StatusCode::NO_CONTENT.into_response();
|
||||
}
|
||||
flash::redirect_with_flash(
|
||||
&format!("{base}/reports/{report_id}"),
|
||||
FlashData::success("Report resolved"),
|
||||
config.secure_cookies(),
|
||||
)
|
||||
}
|
||||
let flash = match client
|
||||
.resolve_report(
|
||||
&report_id,
|
||||
resolution,
|
||||
public_comment.as_deref(),
|
||||
notify_reporter,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => FlashData::success(format!("Report resolved: {label}")),
|
||||
Err(error) => {
|
||||
tracing::warn!(%error, report_id, "admin API request failed: resolve report");
|
||||
if is_background {
|
||||
return StatusCode::BAD_GATEWAY.into_response();
|
||||
}
|
||||
flash::redirect_with_flash(
|
||||
&format!("{base}/reports/{report_id}"),
|
||||
FlashData::error("Failed to resolve report"),
|
||||
config.secure_cookies(),
|
||||
)
|
||||
FlashData::error("Failed to resolve report")
|
||||
}
|
||||
}
|
||||
};
|
||||
flash::redirect_with_flash(&back, flash, config.secure_cookies())
|
||||
}
|
||||
|
||||
const RESOLUTION_REQUIRED: &str = "Choose a resolution";
|
||||
|
||||
async fn report_legal_hold(
|
||||
State(state): State<AppState>,
|
||||
auth: axum::Extension<AuthContext>,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use crate::utils::plural::count_noun;
|
||||
use crate::{
|
||||
admin_flags,
|
||||
api::client::{AdminApiClient, ApiError},
|
||||
@@ -311,7 +312,7 @@ pub async fn dispatch(
|
||||
};
|
||||
if !form.bool_value("confirm") {
|
||||
return DispatchOutcome::error(
|
||||
"Confirm whose deletion you are cancelling before submitting",
|
||||
"Confirm whose deletion you are canceling before submitting",
|
||||
);
|
||||
}
|
||||
let Some(private_reason) = get("private_reason") else {
|
||||
@@ -322,7 +323,7 @@ pub async fn dispatch(
|
||||
.cancel_deletion(user_id, &expected, notify_user, Some(&private_reason))
|
||||
.await
|
||||
{
|
||||
Ok(_) => DispatchOutcome::success("User deletion cancelled successfully"),
|
||||
Ok(_) => DispatchOutcome::success("User deletion canceled successfully"),
|
||||
Err(ApiError::Http { status: 409, .. }) => DispatchOutcome::error(
|
||||
"The pending deletion changed since this page loaded. Reload and review.",
|
||||
),
|
||||
@@ -435,8 +436,9 @@ pub async fn dispatch(
|
||||
match client.delete_all_user_messages(user_id, dry_run).await {
|
||||
Ok(response) if dry_run => DispatchOutcome {
|
||||
flash: FlashData::success(format!(
|
||||
"Dry run found {} messages across {} channels. Confirm to delete them permanently.",
|
||||
response.message_count, response.channel_count
|
||||
"Dry run found {} across {}. Confirm to delete them permanently.",
|
||||
count_noun(response.message_count, "message", "messages"),
|
||||
count_noun(response.channel_count, "channel", "channels")
|
||||
)),
|
||||
redirect_params: vec![
|
||||
("delete_all_messages_dry_run".to_owned(), "true".to_owned()),
|
||||
@@ -470,7 +472,7 @@ pub async fn dispatch(
|
||||
}
|
||||
"cancel_bulk_message_deletion" => DispatchOutcome::from_result(
|
||||
client.cancel_bulk_message_deletion(user_id).await,
|
||||
"Bulk message deletion cancelled successfully",
|
||||
"Bulk message deletion canceled successfully",
|
||||
"Failed to cancel bulk message deletion",
|
||||
),
|
||||
"message_shred" => {
|
||||
|
||||
Reference in new issue
Block a user