feat: reset sélection batch au chargement + style toast warning
- initApp() appelle deselectAll() au démarrage (sécurité) - Toast warning : fond jaune transparent + icône colorée - Ajout classe toast-icon pour colorer l'icône selon le type - Correction broadcast WebSocket batch delete (type mismatch) - Correction compilation Linux : service install/uninstall gated
This commit is contained in:
@@ -100,6 +100,7 @@ pub fn api_routes(state: AppState) -> Router {
|
||||
.route("/api/images/{id}", get(get_image).delete(delete_image))
|
||||
.route("/api/images/{id}/tags", put(update_tags))
|
||||
.route("/api/images/{id}/thumbnail", get(get_thumbnail))
|
||||
.route("/api/images/batch-delete", post(batch_delete))
|
||||
.route("/api/stats", get(stats))
|
||||
.route("/api/version", get(version))
|
||||
.with_state(state)
|
||||
@@ -497,6 +498,95 @@ pub struct TagsUpdate {
|
||||
pub tags: Vec<String>,
|
||||
}
|
||||
|
||||
/// Requête de suppression multiple
|
||||
#[derive(Deserialize)]
|
||||
pub struct BatchDeleteRequest {
|
||||
pub ids: Vec<i64>,
|
||||
}
|
||||
|
||||
/// Réponse de suppression multiple
|
||||
#[derive(Serialize)]
|
||||
pub struct BatchDeleteResponse {
|
||||
pub deleted: Vec<i64>,
|
||||
pub failed: Vec<serde_json::Value>,
|
||||
pub total_requested: usize,
|
||||
pub total_deleted: usize,
|
||||
}
|
||||
|
||||
/// Suppression en lot : supprime plusieurs images en une seule requête
|
||||
async fn batch_delete(
|
||||
State(state): State<Arc<AppState>>,
|
||||
headers: HeaderMap,
|
||||
Json(body): Json<BatchDeleteRequest>,
|
||||
) -> impl IntoResponse {
|
||||
if body.ids.is_empty() {
|
||||
return error_response(400, "Aucun ID fourni");
|
||||
}
|
||||
if body.ids.len() > 100 {
|
||||
return error_response(400, "Maximum 100 images par lot");
|
||||
}
|
||||
|
||||
let total_requested = body.ids.len();
|
||||
let mut deleted: Vec<i64> = Vec::new();
|
||||
let mut failed: Vec<serde_json::Value> = Vec::new();
|
||||
|
||||
let client = match get_client(&state, &headers) {
|
||||
Ok(c) => c,
|
||||
Err(e) => return error_response(500, &format!("Erreur client : {:#}", e)),
|
||||
};
|
||||
|
||||
tracing::info!("Suppression batch de {} images...", total_requested);
|
||||
|
||||
for &id in &body.ids {
|
||||
match client.delete_image(id).await {
|
||||
Ok(()) => {
|
||||
deleted.push(id);
|
||||
tracing::debug!(" ✅ Image {} supprimée", id);
|
||||
}
|
||||
Err(e) => {
|
||||
failed.push(serde_json::json!({
|
||||
"id": id,
|
||||
"error": format!("{:#}", e)
|
||||
}));
|
||||
tracing::warn!(" ❌ Échec suppression image {}: {:#}", id, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tracing::info!(
|
||||
"Batch delete terminé: {}/{} supprimées, {} échecs",
|
||||
deleted.len(),
|
||||
total_requested,
|
||||
failed.len()
|
||||
);
|
||||
|
||||
// Notifier les clients WebSocket
|
||||
if !deleted.is_empty() {
|
||||
let _ = state.ws_hub.broadcast(&serde_json::json!({
|
||||
"type": "images_deleted",
|
||||
"ids": &deleted
|
||||
}).to_string());
|
||||
}
|
||||
|
||||
let status = if failed.is_empty() {
|
||||
axum::http::StatusCode::OK
|
||||
} else if deleted.is_empty() {
|
||||
axum::http::StatusCode::INTERNAL_SERVER_ERROR
|
||||
} else {
|
||||
axum::http::StatusCode::MULTI_STATUS
|
||||
};
|
||||
|
||||
(
|
||||
status,
|
||||
Json(serde_json::json!({
|
||||
"deleted": deleted,
|
||||
"failed": failed,
|
||||
"total_requested": total_requested,
|
||||
"total_deleted": deleted.len()
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
async fn update_tags(
|
||||
State(state): State<Arc<AppState>>,
|
||||
headers: HeaderMap,
|
||||
|
||||
Reference in New Issue
Block a user