diff --git a/bin/src/daemon.rs b/bin/src/daemon.rs index 3e08f18..8abea30 100644 --- a/bin/src/daemon.rs +++ b/bin/src/daemon.rs @@ -20,7 +20,7 @@ use crate::framebuffer::Framebuffer; use analysis::{get_analysis_status, run_analysis_thread, start_analysis, AnalysisCtrlMessage, AnalysisStatus}; use axum::response::Redirect; -use diag::{get_analysis_report, start_recording, stop_recording, DiagDeviceCtrlMessage}; +use diag::{delete_recording, get_analysis_report, start_recording, stop_recording, DiagDeviceCtrlMessage}; use log::{info, error}; use qmdl_store::RecordingStoreError; use rayhunter::diag_device::DiagDevice; @@ -56,6 +56,7 @@ async fn run_server( .route("/api/qmdl-manifest", get(get_qmdl_manifest)) .route("/api/start-recording", post(start_recording)) .route("/api/stop-recording", post(stop_recording)) + .route("/api/delete-recording/*name", post(delete_recording)) .route("/api/analysis-report/*name", get(get_analysis_report)) .route("/api/analysis", get(get_analysis_status)) .route("/api/analysis/*name", post(start_analysis)) diff --git a/bin/src/diag.rs b/bin/src/diag.rs index 9aca91a..61a7931 100644 --- a/bin/src/diag.rs +++ b/bin/src/diag.rs @@ -18,7 +18,7 @@ use tokio_util::task::TaskTracker; use futures::{StreamExt, TryStreamExt}; use crate::framebuffer; -use crate::qmdl_store::RecordingStore; +use crate::qmdl_store::{RecordingStore, RecordingStoreError}; use crate::server::ServerState; use crate::analysis::AnalysisWriter; @@ -155,6 +155,26 @@ pub async fn stop_recording(State(state): State>) -> Result<(St Ok((StatusCode::ACCEPTED, "ok".to_string())) } +pub async fn delete_recording( + State(state): State>, + Path(qmdl_name): Path, +) -> Result<(StatusCode, String), (StatusCode, String)> { + if state.debug_mode { + return Err((StatusCode::FORBIDDEN, "server is in debug mode".to_string())); + } + let mut qmdl_store = state.qmdl_store_lock.write().await; + match qmdl_store.delete_entry(&qmdl_name).await { + Err(RecordingStoreError::NoSuchEntryError) => return Err((StatusCode::BAD_REQUEST, format!("no recording with name {qmdl_name}"))), + Err(e) => return Err((StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't delete recording: {e}"))), + Ok(_) => {}, + } + state.diag_device_ctrl_sender.send(DiagDeviceCtrlMessage::StopRecording).await + .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't send stop recording message: {}", e)))?; + state.ui_update_sender.send(framebuffer::DisplayState::Paused).await + .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't send ui update message: {}", e)))?; + Ok((StatusCode::ACCEPTED, "ok".to_string())) +} + pub async fn get_analysis_report(State(state): State>, Path(qmdl_name): Path) -> Result { let qmdl_store = state.qmdl_store_lock.read().await; let (entry_index, _) = if qmdl_name == "live" { diff --git a/bin/src/qmdl_store.rs b/bin/src/qmdl_store.rs index e7f8970..9e1bca4 100644 --- a/bin/src/qmdl_store.rs +++ b/bin/src/qmdl_store.rs @@ -5,17 +5,21 @@ use std::path::{Path, PathBuf}; use thiserror::Error; use tokio::{ fs::{self, try_exists, File, OpenOptions}, - io::AsyncWriteExt, + io::AsyncWriteExt }; #[derive(Debug, Error)] pub enum RecordingStoreError { #[error("Can't close an entry when there's no current entry")] NoCurrentEntry, + #[error("An entry with that name doesn't exist")] + NoSuchEntryError, #[error("Couldn't create file: {0}")] CreateFileError(tokio::io::Error), #[error("Couldn't read file: {0}")] ReadFileError(tokio::io::Error), + #[error("Couldn't delete file: {0}")] + DeleteFileError(tokio::io::Error), #[error("Couldn't open directory at path: {0}")] OpenDirError(tokio::io::Error), #[error("Couldn't read manifest file: {0}")] @@ -268,6 +272,32 @@ impl RecordingStore { let entry_index = self.current_entry?; Some((entry_index, &self.manifest.entries[entry_index])) } + + pub async fn delete_entry(&mut self, name: &str) -> Result { + let entry_to_delete_idx = self.manifest + .entries + .iter() + .position(|entry| entry.name == name) + .ok_or(RecordingStoreError::NoSuchEntryError)?; + if let Some(current_entry) = self.current_entry { + if current_entry == entry_to_delete_idx { + self.close_current_entry().await?; + } else { + self.current_entry = Some(current_entry - 1); + } + } + let entry_to_delete = self.manifest.entries.remove(entry_to_delete_idx); + self.write_manifest().await?; + let qmdl_filepath = entry_to_delete.get_qmdl_filepath(&self.path); + let analysis_filepath = entry_to_delete.get_analysis_filepath(&self.path); + tokio::fs::remove_file(qmdl_filepath) + .await + .map_err(RecordingStoreError::DeleteFileError)?; + tokio::fs::remove_file(analysis_filepath) + .await + .map_err(RecordingStoreError::DeleteFileError)?; + Ok(entry_to_delete) + } } #[cfg(test)] diff --git a/bin/static/index.html b/bin/static/index.html index f1c42bc..7bb37c0 100644 --- a/bin/static/index.html +++ b/bin/static/index.html @@ -29,6 +29,7 @@ PCAP QMDL Analysis Result + Actions diff --git a/bin/static/js/main.js b/bin/static/js/main.js index 18e1937..4f7af7d 100644 --- a/bin/static/js/main.js +++ b/bin/static/js/main.js @@ -125,6 +125,16 @@ function createLink(uri, text) { return link; } +function createButton(uri, text) { + const link = document.createElement('button'); + link.innerText = text; + link.onclick = async () => { + await req('POST', uri); + populateDivs(); + }; + return link; +} + function createEntryRow(entry, isCurrent) { const row = document.createElement('tr'); const name = document.createElement('th'); @@ -153,6 +163,10 @@ function createEntryRow(entry, isCurrent) { } row.appendChild(analysisResult); + const actionsButtons = document.createElement('td'); + actionsButtons.appendChild(createButton(`/api/delete-recording/${entry.name}`, 'Delete')); + row.appendChild(actionsButtons); + return row; }