feat: added most beacon DS and listener CRUD
This commit is contained in:
parent
0d6b2b4c16
commit
b381261cea
33
Cargo.lock
generated
33
Cargo.lock
generated
@ -2191,6 +2191,16 @@ dependencies = [
|
|||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pem"
|
||||||
|
version = "3.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pem-rfc7468"
|
name = "pem-rfc7468"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
@ -2430,6 +2440,19 @@ dependencies = [
|
|||||||
"getrandom",
|
"getrandom",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rcgen"
|
||||||
|
version = "0.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2"
|
||||||
|
dependencies = [
|
||||||
|
"pem",
|
||||||
|
"ring",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"time",
|
||||||
|
"yasna",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reactive_graph"
|
name = "reactive_graph"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
@ -2971,6 +2994,7 @@ dependencies = [
|
|||||||
"leptos_meta",
|
"leptos_meta",
|
||||||
"leptos_router",
|
"leptos_router",
|
||||||
"pbkdf2",
|
"pbkdf2",
|
||||||
|
"rcgen",
|
||||||
"rpassword",
|
"rpassword",
|
||||||
"serde",
|
"serde",
|
||||||
"sha2",
|
"sha2",
|
||||||
@ -4291,6 +4315,15 @@ version = "1.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yasna"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd"
|
||||||
|
dependencies = [
|
||||||
|
"time",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yoke"
|
name = "yoke"
|
||||||
version = "0.7.5"
|
version = "0.7.5"
|
||||||
|
|||||||
@ -39,6 +39,7 @@ sha2 = { version = "0.10", optional = true }
|
|||||||
hex = { version = "0.4", optional = true }
|
hex = { version = "0.4", optional = true }
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
cfg-if = "1.0.0"
|
cfg-if = "1.0.0"
|
||||||
|
rcgen = { version = "0.13.2", optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
hydrate = ["leptos/hydrate", "chrono/wasmbind"]
|
hydrate = ["leptos/hydrate", "chrono/wasmbind"]
|
||||||
@ -60,6 +61,7 @@ ssr = [
|
|||||||
"dep:pbkdf2",
|
"dep:pbkdf2",
|
||||||
"dep:sha2",
|
"dep:sha2",
|
||||||
"dep:hex",
|
"dep:hex",
|
||||||
|
"dep:rcgen",
|
||||||
"leptos/ssr",
|
"leptos/ssr",
|
||||||
"leptos_meta/ssr",
|
"leptos_meta/ssr",
|
||||||
"leptos_router/ssr",
|
"leptos_router/ssr",
|
||||||
|
|||||||
121
sparse-server/migrations/20250130040842_add_beacons.sql
Normal file
121
sparse-server/migrations/20250130040842_add_beacons.sql
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
CREATE TABLE beacon_listener (
|
||||||
|
listener_id integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
|
||||||
|
port int NOT NULL,
|
||||||
|
public_ip varchar NOT NULL,
|
||||||
|
domain_name varchar NOT NULL,
|
||||||
|
certificate varchar NOT NULL,
|
||||||
|
privkey varchar NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE beacon_config (
|
||||||
|
config_id integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
|
||||||
|
mode text check (mode in ('single', 'regular', 'random', 'cron')),
|
||||||
|
|
||||||
|
regular_interval int,
|
||||||
|
|
||||||
|
random_min_time int,
|
||||||
|
random_max_time int,
|
||||||
|
|
||||||
|
cron_schedule varchar
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE beacon_category (
|
||||||
|
category_id integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
category_name varchar NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE beacon_template (
|
||||||
|
template_id integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
template_name varchar NOT NULL,
|
||||||
|
operating_system varchar NOT NULL,
|
||||||
|
config_id int NOT NULL,
|
||||||
|
listener_id int NOT NULL,
|
||||||
|
|
||||||
|
source_ip varchar NOT NULL,
|
||||||
|
source_mac varchar,
|
||||||
|
source_mode text check (source_mode in ('host', 'custom')),
|
||||||
|
source_netmask int,
|
||||||
|
source_gateway varchar,
|
||||||
|
|
||||||
|
FOREIGN KEY (config_id) REFERENCES beacon_config
|
||||||
|
FOREIGN KEY (listener_id) REFERENCES beacon_listener
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE beacon_instance (
|
||||||
|
beacon_id varchar PRIMARY KEY NOT NULL,
|
||||||
|
template_id int NOT NULL,
|
||||||
|
peer_ip varchar NOT NULL,
|
||||||
|
nickname varchar NOT NULL,
|
||||||
|
|
||||||
|
cwd varchar NOT NULL,
|
||||||
|
operating_system varchar NOT NULL,
|
||||||
|
beacon_userent varchar NOT NULL,
|
||||||
|
hostname varchar NOT NULL,
|
||||||
|
|
||||||
|
FOREIGN KEY (template_id) REFERENCES beacon_template
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE beacon_category_assignment (
|
||||||
|
category_id int,
|
||||||
|
beacon_id varchar,
|
||||||
|
|
||||||
|
PRIMARY KEY (category_id, beacon_id),
|
||||||
|
|
||||||
|
FOREIGN KEY (category_id) REFERENCES beacon_category,
|
||||||
|
FOREIGN KEY (beacon_id) REFERENCES beacon_instance
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE beacon_config_assignment (
|
||||||
|
config_id int,
|
||||||
|
beacon_id varchar,
|
||||||
|
|
||||||
|
PRIMARY KEY (config_id, beacon_id),
|
||||||
|
|
||||||
|
FOREIGN KEY (config_id) REFERENCES beacon_config,
|
||||||
|
FOREIGN KEY (beacon_id) REFERENCES beacon_instance
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE beacon_command (
|
||||||
|
command_id integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
|
||||||
|
cmd_type text check (cmd_type in ('update', 'exec', 'install', 'upload', 'download', 'reload_config', 'chdir', 'ls')),
|
||||||
|
|
||||||
|
exec_command varchar,
|
||||||
|
|
||||||
|
install_target varchar,
|
||||||
|
|
||||||
|
upload_src varchar,
|
||||||
|
upload_dest varchar,
|
||||||
|
|
||||||
|
download_path varchar,
|
||||||
|
|
||||||
|
config_id int,
|
||||||
|
|
||||||
|
FOREIGN KEY (config_id) REFERENCES beacon_config
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE beacon_command_invocation (
|
||||||
|
beacon_id varchar NOT NULL,
|
||||||
|
command_id int NOT NULL,
|
||||||
|
|
||||||
|
invoker_id int NOT NULL,
|
||||||
|
|
||||||
|
invocation_date int,
|
||||||
|
invocation_result varchar,
|
||||||
|
|
||||||
|
PRIMARY KEY (beacon_id, command_id),
|
||||||
|
|
||||||
|
FOREIGN KEY (command_id) REFERENCES beacon_command,
|
||||||
|
FOREIGN KEY (beacon_id) REFERENCES beacon_instance,
|
||||||
|
FOREIGN KEY (invoker_id) REFERENCES users
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE beacon_checkin (
|
||||||
|
beacon_id varchar NOT NULL,
|
||||||
|
|
||||||
|
checkin_date int,
|
||||||
|
|
||||||
|
FOREIGN KEY (beacon_id) REFERENCES beacon_instance
|
||||||
|
);
|
||||||
@ -1,7 +1,7 @@
|
|||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use leptos_meta::{provide_meta_context, MetaTags, Stylesheet, Title};
|
use leptos_meta::{provide_meta_context, MetaTags, Stylesheet, Title};
|
||||||
use leptos_router::{
|
use leptos_router::{
|
||||||
components::{A, Route, Router, Routes},
|
components::{A, ParentRoute, Route, Router, Routes},
|
||||||
hooks::use_query_map,
|
hooks::use_query_map,
|
||||||
path
|
path
|
||||||
};
|
};
|
||||||
@ -30,12 +30,8 @@ pub struct User {
|
|||||||
|
|
||||||
#[server]
|
#[server]
|
||||||
pub async fn me() -> Result<Option<User>, ServerFnError> {
|
pub async fn me() -> Result<Option<User>, ServerFnError> {
|
||||||
tracing::info!("I'm being checked!");
|
|
||||||
|
|
||||||
let user = crate::db::user::get_auth_session().await?;
|
let user = crate::db::user::get_auth_session().await?;
|
||||||
|
|
||||||
tracing::debug!("User returned: {:?}", user);
|
|
||||||
|
|
||||||
Ok(user.map(|user| User {
|
Ok(user.map(|user| User {
|
||||||
user_id: user.user_id,
|
user_id: user.user_id,
|
||||||
user_name: user.user_name
|
user_name: user.user_name
|
||||||
@ -149,12 +145,22 @@ pub fn App() -> impl IntoView {
|
|||||||
</Suspense>
|
</Suspense>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<aside class="beacons">
|
<crate::beacons::BeaconSidebar />
|
||||||
</aside>
|
|
||||||
|
|
||||||
<Routes fallback=|| "Page not found.".into_view()>
|
<Routes fallback=|| "Page not found.".into_view()>
|
||||||
<Route path=path!("users") view=crate::users::UserView />
|
<Route path=path!("users") view=crate::users::UserView />
|
||||||
<Route path=path!("login") view=move || view! { <LoginPage login/> }/>
|
<Route path=path!("login") view=move || view! { <LoginPage login/> }/>
|
||||||
|
<ParentRoute path=path!("beacons") view=crate::beacons::BeaconView>
|
||||||
|
<Route path=path!("categories") view=crate::beacons::CategoriesView/>
|
||||||
|
<Route path=path!("commands") view=crate::beacons::CommandsView/>
|
||||||
|
<Route path=path!("configs") view=crate::beacons::ConfigsView/>
|
||||||
|
<Route path=path!("templates") view=crate::beacons::TemplatesView/>
|
||||||
|
<Route path=path!("instances") view=crate::beacons::InstancesView/>
|
||||||
|
<Route path=path!("listeners") view=crate::beacons::ListenersView/>
|
||||||
|
<Route path=path!("") view=|| view! {
|
||||||
|
<p>"Select a menu item on the left to get started"</p>
|
||||||
|
}/>
|
||||||
|
</ParentRoute>
|
||||||
<Route path=path!("") view=HomePage/>
|
<Route path=path!("") view=HomePage/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</Router>
|
</Router>
|
||||||
|
|||||||
10
sparse-server/src/beacon_handler.rs
Normal file
10
sparse-server/src/beacon_handler.rs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
sync::{Arc, RwLock},
|
||||||
|
};
|
||||||
|
|
||||||
|
use tokio::task::JoinHandle;
|
||||||
|
|
||||||
|
pub type BeaconListenerHandle = JoinHandle<()>;
|
||||||
|
|
||||||
|
pub type BeaconListenerMap = Arc<RwLock<HashMap<i64, BeaconListenerHandle>>>;
|
||||||
54
sparse-server/src/beacons.rs
Normal file
54
sparse-server/src/beacons.rs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
use leptos::prelude::*;
|
||||||
|
use leptos_router::{components::A, nested_router::Outlet};
|
||||||
|
|
||||||
|
mod categories;
|
||||||
|
mod commands;
|
||||||
|
mod configs;
|
||||||
|
mod instances;
|
||||||
|
mod listeners;
|
||||||
|
mod templates;
|
||||||
|
|
||||||
|
pub use categories::CategoriesView;
|
||||||
|
pub use commands::CommandsView;
|
||||||
|
pub use configs::ConfigsView;
|
||||||
|
pub use instances::InstancesView;
|
||||||
|
pub use listeners::ListenersView;
|
||||||
|
pub use templates::TemplatesView;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn BeaconView() -> impl IntoView {
|
||||||
|
#[cfg(feature = "hydrate")]
|
||||||
|
Effect::new(move || {
|
||||||
|
let user = expect_context::<ReadSignal<Option<crate::app::User>>>();
|
||||||
|
if user.get().is_none() {
|
||||||
|
let navigate = leptos_router::hooks::use_navigate();
|
||||||
|
navigate("/login?next=beacons", Default::default());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<main class="beacons">
|
||||||
|
<aside class="beacon-menu">
|
||||||
|
<ul>
|
||||||
|
<li><A href="/beacons/listeners">"Listeners"</A></li>
|
||||||
|
<li><A href="/beacons/configs">"Configs"</A></li>
|
||||||
|
<li><A href="/beacons/categories">"Categories"</A></li>
|
||||||
|
<li><A href="/beacons/templates">"Templates"</A></li>
|
||||||
|
<li><A href="/beacons/instances">"Instances"</A></li>
|
||||||
|
<li><A href="/beacons/commands">"Commands"</A></li>
|
||||||
|
</ul>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<Outlet />
|
||||||
|
</main>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn BeaconSidebar() -> impl IntoView {
|
||||||
|
view! {
|
||||||
|
<aside class="beacons">
|
||||||
|
|
||||||
|
</aside>
|
||||||
|
}
|
||||||
|
}
|
||||||
13
sparse-server/src/beacons/categories.rs
Normal file
13
sparse-server/src/beacons/categories.rs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
use leptos::prelude::*;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn CategoriesView() -> impl IntoView {
|
||||||
|
view! {
|
||||||
|
<div>
|
||||||
|
<p>"Categories"</p>
|
||||||
|
<ul>
|
||||||
|
<li>"Windows"</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
10
sparse-server/src/beacons/commands.rs
Normal file
10
sparse-server/src/beacons/commands.rs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
use leptos::prelude::*;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn CommandsView() -> impl IntoView {
|
||||||
|
view! {
|
||||||
|
<div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
10
sparse-server/src/beacons/configs.rs
Normal file
10
sparse-server/src/beacons/configs.rs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
use leptos::prelude::*;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn ConfigsView() -> impl IntoView {
|
||||||
|
view! {
|
||||||
|
<div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
10
sparse-server/src/beacons/instances.rs
Normal file
10
sparse-server/src/beacons/instances.rs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
use leptos::prelude::*;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn InstancesView() -> impl IntoView {
|
||||||
|
view! {
|
||||||
|
<div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
226
sparse-server/src/beacons/listeners.rs
Normal file
226
sparse-server/src/beacons/listeners.rs
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
use std::net::Ipv4Addr;
|
||||||
|
|
||||||
|
use leptos::{either::Either, prelude::*};
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
use {
|
||||||
|
sqlx::SqlitePool,
|
||||||
|
leptos::server_fn::error::NoCustomError,
|
||||||
|
crate::{db::user, beacon_handler::BeaconListenerMap},
|
||||||
|
rcgen::{generate_simple_self_signed, CertifiedKey},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
struct DbListener {
|
||||||
|
listener_id: i64,
|
||||||
|
port: i64,
|
||||||
|
public_ip: String,
|
||||||
|
domain_name: String
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
|
pub struct PubListener {
|
||||||
|
listener_id: i64,
|
||||||
|
port: i64,
|
||||||
|
public_ip: String,
|
||||||
|
domain_name: String,
|
||||||
|
active: bool
|
||||||
|
}
|
||||||
|
|
||||||
|
#[server]
|
||||||
|
pub async fn get_listeners() -> Result<Vec<PubListener>, ServerFnError> {
|
||||||
|
let user = user::get_auth_session().await?;
|
||||||
|
|
||||||
|
if user.is_none() {
|
||||||
|
return Err(ServerFnError::<NoCustomError>::ServerError("You are not signed in!".to_owned()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let db = expect_context::<SqlitePool>();
|
||||||
|
let beacon_handles = expect_context::<BeaconListenerMap>();
|
||||||
|
|
||||||
|
let listeners = sqlx::query_as!(
|
||||||
|
DbListener,
|
||||||
|
"SELECT listener_id, port, public_ip, domain_name FROM beacon_listener"
|
||||||
|
)
|
||||||
|
.fetch_all(&db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let Ok(beacon_handles_handle) = beacon_handles.read() else {
|
||||||
|
return Err(ServerFnError::<NoCustomError>::ServerError("".to_string()));
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(listeners
|
||||||
|
.into_iter()
|
||||||
|
.map(|b| PubListener {
|
||||||
|
listener_id: b.listener_id,
|
||||||
|
port: b.port,
|
||||||
|
public_ip: b.public_ip,
|
||||||
|
domain_name: b.domain_name,
|
||||||
|
active: beacon_handles_handle
|
||||||
|
.get(&b.listener_id)
|
||||||
|
.map(|h| !h.is_finished())
|
||||||
|
.unwrap_or(false)
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[server]
|
||||||
|
pub async fn add_listener(public_ip: String, port: i16, domain_name: String) -> Result<(), ServerFnError> {
|
||||||
|
let user = user::get_auth_session().await?;
|
||||||
|
|
||||||
|
if user.is_none() {
|
||||||
|
return Err(ServerFnError::<NoCustomError>::ServerError("You are not signed in!".to_owned()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if public_ip.parse::<Ipv4Addr>().is_err() {
|
||||||
|
return Err(ServerFnError::<NoCustomError>::ServerError("Unable to parse public IP address".to_owned()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let subject_alt_names = vec![public_ip.to_string(), domain_name.clone()];
|
||||||
|
let CertifiedKey { cert, key_pair } = tokio::task::spawn_blocking(|| {
|
||||||
|
generate_simple_self_signed(subject_alt_names)
|
||||||
|
}).await??;
|
||||||
|
|
||||||
|
let db = expect_context::<SqlitePool>();
|
||||||
|
|
||||||
|
let public_ip = public_ip.to_string();
|
||||||
|
let cert = cert.pem().to_string();
|
||||||
|
let key_pair = key_pair.serialize_pem().to_string();
|
||||||
|
|
||||||
|
sqlx::query!(
|
||||||
|
"INSERT INTO beacon_listener (port, public_ip, domain_name, certificate, privkey) VALUES (?, ?, ?, ?, ?)",
|
||||||
|
port,
|
||||||
|
public_ip,
|
||||||
|
domain_name,
|
||||||
|
cert,
|
||||||
|
key_pair
|
||||||
|
)
|
||||||
|
.execute(&db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[server]
|
||||||
|
pub async fn start_listener(listener_id: i64) -> Result<(), ServerFnError> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn ListenersView() -> impl IntoView {
|
||||||
|
let add_listener = ServerAction::<AddListener>::new();
|
||||||
|
|
||||||
|
let listeners = Resource::new(
|
||||||
|
move || add_listener.version().get(),
|
||||||
|
|_| async { get_listeners().await }
|
||||||
|
);
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<div class="listeners">
|
||||||
|
<ActionForm action=add_listener>
|
||||||
|
<fieldset>
|
||||||
|
{move || match add_listener.value().get() {
|
||||||
|
Some(Ok(_)) => Either::Right(()),
|
||||||
|
None => Either::Right(()),
|
||||||
|
Some(Err(e)) => Either::Left(view! {
|
||||||
|
<p>"Error creating listener:"</p>
|
||||||
|
<p>{format!("{e:?}")}</p>
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
<legend>"Add a new listener"</legend>
|
||||||
|
<label>"Public IP address"</label>
|
||||||
|
<input name="public_ip"/>
|
||||||
|
<label>"Port"</label>
|
||||||
|
<input name="port" type="number"/>
|
||||||
|
<label>"Domain name (for HTTPS)"</label>
|
||||||
|
<input name="domain_name"/>
|
||||||
|
<div></div>
|
||||||
|
<input type="submit" value="Submit"/>
|
||||||
|
</fieldset>
|
||||||
|
</ActionForm>
|
||||||
|
|
||||||
|
<Suspense fallback=|| view! { <p>"Loading..."</p> }>
|
||||||
|
{ move || match listeners.get() {
|
||||||
|
Some(inner) => Either::Right(match inner {
|
||||||
|
Err(e) => Either::Left(view! {
|
||||||
|
<p>"There was an error loading listeners:"</p>
|
||||||
|
<p>{format!("error: {}", e)}</p>
|
||||||
|
}),
|
||||||
|
Ok(ls) => Either::Right(view! {
|
||||||
|
<DisplayListeners
|
||||||
|
listener_resource=listeners
|
||||||
|
listeners=ls
|
||||||
|
/>
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
None => Either::Left(view! {
|
||||||
|
<p>"Loading..."</p>
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</Suspense>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn DisplayListeners(listener_resource: Resource<Result<Vec<PubListener>, ServerFnError>>, listeners: Vec<PubListener>) -> impl IntoView {
|
||||||
|
let (error_msg, set_error_msg) = signal(None);
|
||||||
|
let start_listener_action = Action::new(move |&id: &i64| async move {
|
||||||
|
match start_listener(id).await {
|
||||||
|
Ok(()) => {
|
||||||
|
listener_resource.refetch();
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
set_error_msg(Some(e.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let listeners_view = listeners
|
||||||
|
.iter()
|
||||||
|
.map(|listener| view! {
|
||||||
|
<li>
|
||||||
|
{listener.listener_id}
|
||||||
|
": "
|
||||||
|
{listener.domain_name.clone()}
|
||||||
|
" ("
|
||||||
|
{listener.public_ip.clone()}
|
||||||
|
":"
|
||||||
|
{listener.port}
|
||||||
|
")"
|
||||||
|
{match listener.active {
|
||||||
|
true => Either::Left(view! {
|
||||||
|
<span>"active!"</span>
|
||||||
|
}),
|
||||||
|
false => Either::Right(view! {
|
||||||
|
<button
|
||||||
|
on:click={
|
||||||
|
let id = listener.listener_id;
|
||||||
|
move |e| {
|
||||||
|
let _ = e.prevent_default();
|
||||||
|
start_listener_action.dispatch(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
"activate"
|
||||||
|
</button>
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</li>
|
||||||
|
})
|
||||||
|
.collect_view();
|
||||||
|
|
||||||
|
view! {
|
||||||
|
{move || error_msg
|
||||||
|
.get()
|
||||||
|
.map(|err| view! {
|
||||||
|
<p>
|
||||||
|
"Error starting listener: "
|
||||||
|
{err}
|
||||||
|
</p>
|
||||||
|
})}
|
||||||
|
<ul>
|
||||||
|
{listeners_view}
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
}
|
||||||
10
sparse-server/src/beacons/templates.rs
Normal file
10
sparse-server/src/beacons/templates.rs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
use leptos::prelude::*;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn TemplatesView() -> impl IntoView {
|
||||||
|
view! {
|
||||||
|
<div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,10 +1,10 @@
|
|||||||
pub mod app;
|
pub mod app;
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
pub mod users;
|
pub mod beacon_handler;
|
||||||
|
pub mod beacons;
|
||||||
pub mod error;
|
|
||||||
|
|
||||||
pub mod db;
|
pub mod db;
|
||||||
|
pub mod error;
|
||||||
|
pub mod users;
|
||||||
|
|
||||||
#[cfg(feature = "hydrate")]
|
#[cfg(feature = "hydrate")]
|
||||||
#[wasm_bindgen::prelude::wasm_bindgen]
|
#[wasm_bindgen::prelude::wasm_bindgen]
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
pub(crate) mod beacons {
|
pub(crate) mod beacon_binaries {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub const LINUX_BEACON: &'static [u8] = include_bytes!(std::env!("SPARSE_BEACON_LINUX"));
|
pub const LINUX_BEACON: &'static [u8] = include_bytes!(std::env!("SPARSE_BEACON_LINUX"));
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@ -15,16 +15,15 @@ pub(crate) mod beacons {
|
|||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
mod cli;
|
mod cli;
|
||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
mod webserver;
|
mod webserver;
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
mod beacons;
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
pub mod users;
|
pub mod users;
|
||||||
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
|
||||||
pub mod db;
|
pub mod db;
|
||||||
|
pub mod beacon_handler;
|
||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
@ -56,7 +55,7 @@ async fn main() -> anyhow::Result<std::process::ExitCode> {
|
|||||||
|
|
||||||
let db_exists = std::fs::metadata(&db_location);
|
let db_exists = std::fs::metadata(&db_location);
|
||||||
|
|
||||||
let run_init = if let Err(e) = db_exists {
|
if let Err(e) = db_exists {
|
||||||
if !options.init_ok {
|
if !options.init_ok {
|
||||||
tracing::error!("Database doesn't exist, and initialization not allowed!");
|
tracing::error!("Database doesn't exist, and initialization not allowed!");
|
||||||
tracing::error!("{:?}", e);
|
tracing::error!("{:?}", e);
|
||||||
@ -64,10 +63,7 @@ async fn main() -> anyhow::Result<std::process::ExitCode> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tracing::info!("Database doesn't exist, readying initialization");
|
tracing::info!("Database doesn't exist, readying initialization");
|
||||||
true
|
}
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
let pool = SqlitePool::connect_with(
|
let pool = SqlitePool::connect_with(
|
||||||
SqliteConnectOptions::from_str(&format!("sqlite://{}", db_location.to_string_lossy()))?
|
SqliteConnectOptions::from_str(&format!("sqlite://{}", db_location.to_string_lossy()))?
|
||||||
|
|||||||
@ -255,7 +255,7 @@ pub fn UserView() -> impl IntoView {
|
|||||||
let user = expect_context::<ReadSignal<Option<crate::app::User>>>();
|
let user = expect_context::<ReadSignal<Option<crate::app::User>>>();
|
||||||
if user.get().is_none() {
|
if user.get().is_none() {
|
||||||
let navigate = leptos_router::hooks::use_navigate();
|
let navigate = leptos_router::hooks::use_navigate();
|
||||||
navigate("/login?next=/users", Default::default());
|
navigate("/login?next=users", Default::default());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -12,6 +12,7 @@ pub async fn serve_web(management_address: SocketAddrV4, _bind_address: SocketAd
|
|||||||
let conf = get_configuration(None).unwrap();
|
let conf = get_configuration(None).unwrap();
|
||||||
let leptos_options = conf.leptos_options;
|
let leptos_options = conf.leptos_options;
|
||||||
let routes = generate_route_list(App);
|
let routes = generate_route_list(App);
|
||||||
|
let beacon_listeners = crate::beacon_handler::BeaconListenerMap::default();
|
||||||
|
|
||||||
let compression_layer = tower_http::compression::CompressionLayer::new()
|
let compression_layer = tower_http::compression::CompressionLayer::new()
|
||||||
.gzip(true)
|
.gzip(true)
|
||||||
@ -23,7 +24,10 @@ pub async fn serve_web(management_address: SocketAddrV4, _bind_address: SocketAd
|
|||||||
.leptos_routes_with_context(
|
.leptos_routes_with_context(
|
||||||
&leptos_options,
|
&leptos_options,
|
||||||
routes,
|
routes,
|
||||||
move || provide_context(db.clone()),
|
move || {
|
||||||
|
provide_context(std::sync::Arc::clone(&beacon_listeners));
|
||||||
|
provide_context(db.clone())
|
||||||
|
},
|
||||||
{
|
{
|
||||||
let leptos_options = leptos_options.clone();
|
let leptos_options = leptos_options.clone();
|
||||||
move || shell(leptos_options.clone())
|
move || shell(leptos_options.clone())
|
||||||
|
|||||||
38
sparse-server/style/_beacons.scss
Normal file
38
sparse-server/style/_beacons.scss
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
@use 'beacons/listeners';
|
||||||
|
@use 'beacons/configs';
|
||||||
|
@use 'beacons/categories';
|
||||||
|
@use 'beacons/templates';
|
||||||
|
@use 'beacons/instances';
|
||||||
|
@use 'beacons/commands';
|
||||||
|
|
||||||
|
main.beacons {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 120px 1fr;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
aside.beacon-menu {
|
||||||
|
border-right: 1px solid #2e2e59;
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style-type: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
li a, li a:visited {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aside.beacons {
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,3 +1,14 @@
|
|||||||
main.main {
|
main.main {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
main.login {
|
||||||
|
form {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 125px 200px;
|
||||||
|
|
||||||
|
input, label {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
0
sparse-server/style/beacons/_categories.scss
Normal file
0
sparse-server/style/beacons/_categories.scss
Normal file
0
sparse-server/style/beacons/_commands.scss
Normal file
0
sparse-server/style/beacons/_commands.scss
Normal file
0
sparse-server/style/beacons/_configs.scss
Normal file
0
sparse-server/style/beacons/_configs.scss
Normal file
0
sparse-server/style/beacons/_instances.scss
Normal file
0
sparse-server/style/beacons/_instances.scss
Normal file
14
sparse-server/style/beacons/_listeners.scss
Normal file
14
sparse-server/style/beacons/_listeners.scss
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
main.beacons div.listeners {
|
||||||
|
form {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 300px 200px;
|
||||||
|
|
||||||
|
input, label {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
0
sparse-server/style/beacons/_templates.scss
Normal file
0
sparse-server/style/beacons/_templates.scss
Normal file
@ -1,5 +1,10 @@
|
|||||||
@use '_users';
|
@use '_users';
|
||||||
@use '_main';
|
@use '_main';
|
||||||
|
@use '_beacons';
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
html, body {
|
html, body {
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
|
|||||||
@ -10,10 +10,12 @@ typedef struct {
|
|||||||
typedef union SourceIp {
|
typedef union SourceIp {
|
||||||
struct {
|
struct {
|
||||||
char mode; // set to 0
|
char mode; // set to 0
|
||||||
|
char source_mac[6];
|
||||||
ipaddr_t source_ip;
|
ipaddr_t source_ip;
|
||||||
} use_host_networking;
|
} use_host_networking;
|
||||||
struct {
|
struct {
|
||||||
char mode; // set to 1
|
char mode; // set to 1
|
||||||
|
char source_mac[6];
|
||||||
unsigned short netmask;
|
unsigned short netmask;
|
||||||
ipaddr_t source_ip;
|
ipaddr_t source_ip;
|
||||||
ipaddr_t gateway;
|
ipaddr_t gateway;
|
||||||
@ -25,8 +27,6 @@ typedef struct Parameters {
|
|||||||
SourceIp_t source_ip;
|
SourceIp_t source_ip;
|
||||||
unsigned short destination_port;
|
unsigned short destination_port;
|
||||||
unsigned short pubkey_cert_size;
|
unsigned short pubkey_cert_size;
|
||||||
unsigned short privkey_size;
|
|
||||||
unsigned short privkey_cert_size;
|
|
||||||
unsigned short beacon_name_length;
|
unsigned short beacon_name_length;
|
||||||
unsigned short domain_name_length;
|
unsigned short domain_name_length;
|
||||||
char pubkey_cert[1024];
|
char pubkey_cert[1024];
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user