feat: added basic template management
This commit is contained in:
parent
71b2f70686
commit
ba5145c5ae
@ -5,7 +5,6 @@ use leptos_router::{
|
||||
hooks::use_query_map,
|
||||
path
|
||||
};
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use crate::users::User;
|
||||
|
||||
@ -200,6 +199,16 @@ fn HomePage() -> impl IntoView {
|
||||
view! {
|
||||
<main class="main">
|
||||
<h1>"Welcome to sparse!"</h1>
|
||||
<p>"To get started:"</p>
|
||||
<ol>
|
||||
<li>"Sign in"</li>
|
||||
<li>"Go to beacon management"</li>
|
||||
<li>"Create a listener"</li>
|
||||
<li>"(Optional) Create a category"</li>
|
||||
<li>"Create a template"</li>
|
||||
<li>"Download the installer"</li>
|
||||
<li>"Run the installer on a target system"</li>
|
||||
</ol>
|
||||
</main>
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,11 +8,17 @@ mod instances;
|
||||
mod listeners;
|
||||
mod templates;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub use categories::CategoriesView;
|
||||
#[allow(dead_code)]
|
||||
pub use commands::CommandsView;
|
||||
#[allow(dead_code)]
|
||||
pub use configs::ConfigsView;
|
||||
#[allow(dead_code)]
|
||||
pub use instances::InstancesView;
|
||||
#[allow(dead_code)]
|
||||
pub use listeners::ListenersView;
|
||||
#[allow(dead_code)]
|
||||
pub use templates::TemplatesView;
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -24,10 +30,13 @@ pub struct BeaconResources {
|
||||
rename_category: ServerAction<categories::RenameCategory>,
|
||||
add_beacon_config: ServerAction<configs::AddBeaconConfig>,
|
||||
remove_beacon_config: ServerAction<configs::RemoveBeaconConfig>,
|
||||
add_template: ServerAction<templates::AddTemplate>,
|
||||
remove_template: ServerAction<templates::RemoveTemplate>,
|
||||
|
||||
listeners: Resource<Result<Vec<listeners::PubListener>, ServerFnError>>,
|
||||
categories: Resource<Result<Vec<categories::Category>, ServerFnError>>,
|
||||
configs: Resource<Result<Vec<configs::BeaconConfig>, ServerFnError>>
|
||||
configs: Resource<Result<Vec<configs::BeaconConfig>, ServerFnError>>,
|
||||
templates: Resource<Result<Vec<templates::BeaconTemplate>, ServerFnError>>
|
||||
}
|
||||
|
||||
pub fn provide_beacon_resources() {
|
||||
@ -43,6 +52,9 @@ pub fn provide_beacon_resources() {
|
||||
let add_beacon_config = ServerAction::<configs::AddBeaconConfig>::new();
|
||||
let remove_beacon_config = ServerAction::<configs::RemoveBeaconConfig>::new();
|
||||
|
||||
let add_template = ServerAction::<templates::AddTemplate>::new();
|
||||
let remove_template = ServerAction::<templates::RemoveTemplate>::new();
|
||||
|
||||
let listeners = Resource::new(
|
||||
move || (
|
||||
user.get(),
|
||||
@ -71,6 +83,15 @@ pub fn provide_beacon_resources() {
|
||||
|_| async { configs::get_beacon_configs().await }
|
||||
);
|
||||
|
||||
let templates = Resource::new(
|
||||
move || (
|
||||
user.get(),
|
||||
add_template.version().get(),
|
||||
remove_template.version().get()
|
||||
),
|
||||
|_| async { templates::get_templates().await }
|
||||
);
|
||||
|
||||
provide_context(BeaconResources {
|
||||
add_listener,
|
||||
remove_listener,
|
||||
@ -79,10 +100,13 @@ pub fn provide_beacon_resources() {
|
||||
rename_category,
|
||||
add_beacon_config,
|
||||
remove_beacon_config,
|
||||
add_template,
|
||||
remove_template,
|
||||
|
||||
listeners,
|
||||
categories,
|
||||
configs
|
||||
configs,
|
||||
templates
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -11,8 +11,8 @@ use super::BeaconResources;
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct Category {
|
||||
category_id: i64,
|
||||
category_name: String
|
||||
pub category_id: i64,
|
||||
pub category_name: String
|
||||
}
|
||||
|
||||
#[server]
|
||||
|
||||
@ -44,10 +44,10 @@ impl FromRow<'_, SqliteRow> for BeaconConfigTypes {
|
||||
#[cfg_attr(feature = "ssr", derive(FromRow))]
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct BeaconConfig {
|
||||
config_id: i64,
|
||||
config_name: String,
|
||||
pub config_id: i64,
|
||||
pub config_name: String,
|
||||
#[cfg_attr(feature = "ssr", sqlx(flatten))]
|
||||
config_type: BeaconConfigTypes,
|
||||
pub config_type: BeaconConfigTypes,
|
||||
}
|
||||
|
||||
#[server]
|
||||
@ -135,7 +135,7 @@ pub async fn add_beacon_config(
|
||||
|
||||
match &*cron_mode {
|
||||
"local" | "utc" => {},
|
||||
other => {
|
||||
_ => {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError("Unrecognized timezone specifier for cron".to_string()))
|
||||
}
|
||||
}
|
||||
@ -159,7 +159,22 @@ pub async fn add_beacon_config(
|
||||
|
||||
#[server]
|
||||
pub async fn remove_beacon_config(id: i64) -> Result<(), ServerFnError> {
|
||||
unimplemented!()
|
||||
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>();
|
||||
|
||||
sqlx::query!(
|
||||
"DELETE FROM beacon_config WHERE config_id = ?",
|
||||
id
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[component]
|
||||
|
||||
@ -7,8 +7,8 @@ use serde::{Serialize, Deserialize};
|
||||
use {
|
||||
sqlx::SqlitePool,
|
||||
leptos::server_fn::error::NoCustomError,
|
||||
crate::{db::user, beacon_handler::BeaconListenerMap},
|
||||
rcgen::{generate_simple_self_signed, CertifiedKey},
|
||||
crate::{db::user, beacon_handler::BeaconListenerMap},
|
||||
};
|
||||
|
||||
use super::BeaconResources;
|
||||
@ -23,11 +23,11 @@ struct DbListener {
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct PubListener {
|
||||
listener_id: i64,
|
||||
port: i64,
|
||||
public_ip: String,
|
||||
domain_name: String,
|
||||
active: bool
|
||||
pub listener_id: i64,
|
||||
pub port: i64,
|
||||
pub public_ip: String,
|
||||
pub domain_name: String,
|
||||
pub active: bool
|
||||
}
|
||||
|
||||
#[server]
|
||||
|
||||
@ -1,10 +1,452 @@
|
||||
use leptos::prelude::*;
|
||||
use leptos::{either::Either, prelude::*};
|
||||
use serde::{Serialize, Deserialize};
|
||||
#[cfg(feature = "ssr")]
|
||||
use {
|
||||
std::net::Ipv4Addr,
|
||||
|
||||
sqlx::{sqlite::SqliteRow, FromRow, Row, SqlitePool},
|
||||
leptos::server_fn::error::NoCustomError,
|
||||
|
||||
crate::db::user
|
||||
};
|
||||
|
||||
use crate::beacons::BeaconResources;
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub enum BeaconSourceMode {
|
||||
Host,
|
||||
Custom(i64, String)
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
impl FromRow<'_, SqliteRow> for BeaconSourceMode {
|
||||
fn from_row(row: &SqliteRow) -> sqlx::Result<Self> {
|
||||
match row.try_get("source_mode")? {
|
||||
"host" => Ok(Self::Host),
|
||||
"custom" => Ok(Self::Custom(
|
||||
row.try_get("source_netmask")?,
|
||||
row.try_get("source_gateway")?
|
||||
)),
|
||||
type_name => Err(sqlx::Error::TypeNotFound {
|
||||
type_name: type_name.to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "ssr", derive(FromRow))]
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct BeaconTemplate {
|
||||
template_id: i64,
|
||||
template_name: String,
|
||||
operating_system: String,
|
||||
|
||||
source_ip: String,
|
||||
source_mac: Option<String>,
|
||||
#[cfg_attr(feature = "ssr", sqlx(flatten))]
|
||||
source_mode: BeaconSourceMode,
|
||||
|
||||
config_id: i64,
|
||||
listener_id: i64,
|
||||
default_category: Option<i64>
|
||||
}
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
macro_rules! srverr {
|
||||
($err:expr) => {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError($err.to_owned()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[server]
|
||||
pub async fn add_template(
|
||||
template_name: String,
|
||||
operating_system: String,
|
||||
listener_id: i64,
|
||||
config_id: i64,
|
||||
default_category: i64,
|
||||
source_ip: String,
|
||||
source_mac: String,
|
||||
source_mode: String,
|
||||
source_netmask: i64,
|
||||
source_gateway: String
|
||||
) -> Result<(), ServerFnError> {
|
||||
let user = user::get_auth_session().await?;
|
||||
|
||||
if user.is_none() {
|
||||
srverr!("You are not signed in!");
|
||||
}
|
||||
|
||||
if listener_id == 0 {
|
||||
srverr!("You must specify a listener");
|
||||
}
|
||||
|
||||
if config_id == 0 {
|
||||
srverr!("You must specify a configuration");
|
||||
}
|
||||
|
||||
if source_ip.parse::<Ipv4Addr>().is_err() {
|
||||
srverr!("Source IP address is formatted incorrectly");
|
||||
}
|
||||
|
||||
if &*source_mode == "custom" && source_gateway.parse::<Ipv4Addr>().is_err() {
|
||||
srverr!("Gateway address is formatted incorrectly");
|
||||
}
|
||||
|
||||
let mac_parts = source_mac.split(":").collect::<Vec<_>>();
|
||||
if mac_parts.len() != 6 || mac_parts.iter().any(|p| p.len() != 2 || u8::from_str_radix(p, 16).is_err()) {
|
||||
srverr!("Source MAC address is formatted incorrectly");
|
||||
}
|
||||
|
||||
let db = expect_context::<SqlitePool>();
|
||||
|
||||
match &*source_mode {
|
||||
"host" => {
|
||||
let source_mac = Some(source_mac).filter(|mac| mac != "00:00:00:00:00:00");
|
||||
let default_category = Some(default_category).filter(|dc| *dc != 0);
|
||||
|
||||
sqlx::query!(
|
||||
r"INSERT INTO beacon_template
|
||||
(template_name, operating_system, config_id, listener_id, source_ip, source_mac, source_mode, default_category)
|
||||
VALUES
|
||||
(?, ?, ?, ?, ?, ?, 'host', ?)",
|
||||
template_name,
|
||||
operating_system,
|
||||
config_id,
|
||||
listener_id,
|
||||
source_ip,
|
||||
source_mac,
|
||||
default_category
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
},
|
||||
"custom" => {
|
||||
let source_mac = Some(source_mac).filter(|mac| mac != "00:00:00:00:00:00");
|
||||
let default_category = Some(default_category).filter(|dc| *dc != 0);
|
||||
|
||||
sqlx::query!(
|
||||
r"INSERT INTO beacon_template
|
||||
(template_name, operating_system, config_id, listener_id, source_ip, source_mac, source_mode, source_netmask, source_gateway, default_category)
|
||||
VALUES
|
||||
(?, ?, ?, ?, ?, ?, 'host', ?, ?, ?)",
|
||||
template_name,
|
||||
operating_system,
|
||||
config_id,
|
||||
listener_id,
|
||||
source_ip,
|
||||
source_mac,
|
||||
source_netmask,
|
||||
source_gateway,
|
||||
default_category
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
},
|
||||
_other => {
|
||||
srverr!("Invalid type of source mode provided");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[server]
|
||||
pub async fn remove_template(template_id: i64) -> Result<(), ServerFnError> {
|
||||
let user = user::get_auth_session().await?;
|
||||
|
||||
if user.is_none() {
|
||||
srverr!("You are not signed in!");
|
||||
}
|
||||
|
||||
let db = expect_context::<SqlitePool>();
|
||||
|
||||
sqlx::query!("DELETE FROM beacon_template WHERE template_id = ?", template_id)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[server]
|
||||
pub async fn get_templates() -> Result<Vec<BeaconTemplate>, 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>();
|
||||
|
||||
Ok(sqlx::query_as("SELECT * FROM beacon_template")
|
||||
.fetch_all(&db)
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn TemplatesView() -> impl IntoView {
|
||||
view! {
|
||||
<div>
|
||||
let BeaconResources { configs, listeners, categories, templates, .. } = expect_context();
|
||||
|
||||
view! {
|
||||
<div class="templates">
|
||||
<h2>"Beacon templates"</h2>
|
||||
<p>
|
||||
"Beacon templates indicate the information needed to spin up a new beacon. "
|
||||
"Specifically, this is how to create a beacon for an operating system or network configuration."
|
||||
</p>
|
||||
|
||||
<p>
|
||||
"To create a new template, you will need:"
|
||||
</p>
|
||||
<ul>
|
||||
<li>"Operating system: the OS the beacon will run on"</li>
|
||||
<li>"Listener: where the beacon will call back to, and what cert will be used"</li>
|
||||
<li>"Configuration: what configuration the beacon will use to call back with"</li>
|
||||
<li>"Default category: when a new beacon calls back, place it in this category (can be left empty for no category)"</li>
|
||||
<li>"Source IP address: Sparse can't use any IP addresses already in use on the network, so choose one here"</li>
|
||||
<li>"Source MAC: Sparse can optionally use the host MAC address, or use one specified. Leave at 00:00:00:00:00:00 to use the host MAC address. Using a different MAC address will place the NIC in promiscuous mode, and hasn't been tested on Windows"</li>
|
||||
<li>"Source networking mode: choose host to gather the gateway and netmask from the host, or specify your own (maybe custom NAT is going on)"</li>
|
||||
</ul>
|
||||
|
||||
<Suspense fallback=|| view! { <p>"Loading..."</p> }>
|
||||
{ move || Suspend::new(async move {
|
||||
let configs = match configs.await {
|
||||
Ok(cs) => cs,
|
||||
Err(e) => return Either::Left(view! {
|
||||
<p>{"There was an error loading configs:".to_string()}</p>
|
||||
<p>{format!("error: {}", e)}</p>
|
||||
})
|
||||
};
|
||||
|
||||
let listeners = match listeners.await {
|
||||
Ok(cs) => cs,
|
||||
Err(e) => return Either::Left(view! {
|
||||
<p>{"There was an error loading listeners:".to_string()}</p>
|
||||
<p>{format!("error: {}", e)}</p>
|
||||
})
|
||||
};
|
||||
|
||||
let categories = match categories.await {
|
||||
Ok(cs) => cs,
|
||||
Err(e) => return Either::Left(view! {
|
||||
<p>{"There was an error loading categories:".to_string()}</p>
|
||||
<p>{format!("error: {}", e)}</p>
|
||||
})
|
||||
};
|
||||
|
||||
let templates = match templates.await {
|
||||
Ok(ts) => ts,
|
||||
Err(e) => return Either::Left(view! {
|
||||
<p>{"There was an error loading templates:".to_string()}</p>
|
||||
<p>{format!("error: {}", e)}</p>
|
||||
})
|
||||
};
|
||||
|
||||
Either::Right(view! {
|
||||
<AddTemplateForm
|
||||
configs=configs.clone()
|
||||
listeners=listeners.clone()
|
||||
categories=categories.clone()
|
||||
/>
|
||||
|
||||
<DisplayTemplates
|
||||
configs
|
||||
listeners
|
||||
categories
|
||||
templates
|
||||
/>
|
||||
})
|
||||
})}
|
||||
</Suspense>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn AddTemplateForm(
|
||||
configs: Vec<super::configs::BeaconConfig>,
|
||||
listeners: Vec<super::listeners::PubListener>,
|
||||
categories: Vec<super::categories::Category>,
|
||||
) -> impl IntoView {
|
||||
let BeaconResources { add_template, .. } = expect_context();
|
||||
|
||||
view! {
|
||||
{configs.is_empty()
|
||||
.then(|| view! {
|
||||
<span class="error">"Missing configurations! Cannot create a template without a configuration"</span>
|
||||
})}
|
||||
{listeners.is_empty()
|
||||
.then(|| view! {
|
||||
<span class="error">"Missing listeners! Cannot create a template without a listener"</span>
|
||||
})}
|
||||
|
||||
<ActionForm action=add_template>
|
||||
<fieldset>
|
||||
{move || match add_template.value().get() {
|
||||
Some(Ok(_)) => Either::Right(()),
|
||||
None => Either::Right(()),
|
||||
Some(Err(e)) => Either::Left(view! {
|
||||
<p>"Error creating template:"</p>
|
||||
<p>{format!("{e:?}")}</p>
|
||||
})
|
||||
}}
|
||||
<legend>"Create new template"</legend>
|
||||
<label>"Name"</label>
|
||||
<input name="template_name"/>
|
||||
<label>"Operating System"</label>
|
||||
<select name="operating_system">
|
||||
<option value="linux">"Linux"</option>
|
||||
<option value="windows">"Windows"</option>
|
||||
<option value="freebsd">"Freebsd"</option>
|
||||
</select>
|
||||
<label>"Listener"</label>
|
||||
<select name="listener_id">
|
||||
<option value="0">"---"</option>
|
||||
{listeners
|
||||
.iter()
|
||||
.map(|listener| view! {
|
||||
<option value=listener.listener_id.to_string()>{listener.domain_name.clone()}</option>
|
||||
})
|
||||
.collect_view()}
|
||||
</select>
|
||||
<label>"Configuration"</label>
|
||||
<select name="config_id">
|
||||
<option value="0">"---"</option>
|
||||
{configs
|
||||
.iter()
|
||||
.map(|config| view! {
|
||||
<option value=config.config_id.to_string()>{config.config_name.clone()}</option>
|
||||
})
|
||||
.collect_view()}
|
||||
</select>
|
||||
<label>"Default category for new instances"</label>
|
||||
<select name="default_category">
|
||||
<option value="0">"---"</option>
|
||||
{categories
|
||||
.iter()
|
||||
.map(|category| view! {
|
||||
<option value=category.category_id.to_string()>{category.category_name.clone()}</option>
|
||||
})
|
||||
.collect_view()}
|
||||
</select>
|
||||
<label>"Source IP address"</label>
|
||||
<input name="source_ip"/>
|
||||
<label>"Source MAC address (can be empty)"</label>
|
||||
<input name="source_mac" value="00:00:00:00:00:00"/>
|
||||
<label>"Source networking mode"</label>
|
||||
<select name="source_mode">
|
||||
<option value="host">"Host"</option>
|
||||
<option value="custom">"Custom"</option>
|
||||
</select>
|
||||
<label class="mode-custom">"Custom CIDR mask"</label>
|
||||
<input class="mode-custom" type="number" name="source_netmask" value="24"/>
|
||||
<label class="mode-custom">"Network gateway"</label>
|
||||
<input class="mode-custom" name="source_gateway"/>
|
||||
<div></div>
|
||||
<input type="submit" value="Submit" />
|
||||
</fieldset>
|
||||
</ActionForm>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn DisplayTemplates(
|
||||
configs: Vec<super::configs::BeaconConfig>,
|
||||
listeners: Vec<super::listeners::PubListener>,
|
||||
categories: Vec<super::categories::Category>,
|
||||
templates: Vec<BeaconTemplate>
|
||||
) -> impl IntoView {
|
||||
let BeaconResources { remove_template, .. } = expect_context();
|
||||
|
||||
let templates_view = templates
|
||||
.iter()
|
||||
.map(|template| view! {
|
||||
<li>
|
||||
<h4>
|
||||
{template.template_id}
|
||||
": "
|
||||
{template.template_name.clone()}
|
||||
" ("
|
||||
{template.operating_system.clone()}
|
||||
")"
|
||||
</h4>
|
||||
<div>
|
||||
<button>
|
||||
"Download installer"
|
||||
</button>
|
||||
<button
|
||||
on:click={
|
||||
let template_id = template.template_id;
|
||||
move |_| {
|
||||
remove_template.dispatch(RemoveTemplate { template_id });
|
||||
}
|
||||
}
|
||||
>
|
||||
"Delete template"
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<ul>
|
||||
<li>"Source IP: "{template.source_ip.clone()}</li>
|
||||
<li>"Source MAC: "{template.source_mac.clone().unwrap_or("00:00:00:00:00:00".to_owned())}</li>
|
||||
<li>
|
||||
"Source mode: "
|
||||
{match template.source_mode.clone() {
|
||||
BeaconSourceMode::Host => "Host".to_owned(),
|
||||
BeaconSourceMode::Custom(nm, gw) => format!("Custom; netmask: {nm}, gateway: {gw}")
|
||||
}}
|
||||
</li>
|
||||
<li>
|
||||
"Configuration: "
|
||||
{configs
|
||||
.iter()
|
||||
.find(|config| config.config_id == template.config_id)
|
||||
.map(|config| config.config_name.clone())}
|
||||
</li>
|
||||
<li>
|
||||
"Listener: "
|
||||
{listeners
|
||||
.iter()
|
||||
.find(|listener| listener.listener_id == template.listener_id)
|
||||
.map(|listener| listener.domain_name.clone())}
|
||||
</li>
|
||||
{template
|
||||
.default_category
|
||||
.map(|cat_id|
|
||||
categories
|
||||
.iter()
|
||||
.find(|cat| cat.category_id == cat_id)
|
||||
)
|
||||
.flatten()
|
||||
.map(|cat| view! {
|
||||
<li>
|
||||
"Default category: "
|
||||
{cat.category_name.clone()}
|
||||
</li>
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
})
|
||||
.collect_view();
|
||||
|
||||
view! {
|
||||
<h3>"Current beacon templates"</h3>
|
||||
{move || match remove_template.value().get() {
|
||||
Some(Ok(_)) => Either::Right(()),
|
||||
None => Either::Right(()),
|
||||
Some(Err(e)) => Either::Left(view! {
|
||||
<p>"Error deleting template:"</p>
|
||||
<p>{format!("{e:?}")}</p>
|
||||
})
|
||||
}}
|
||||
<ul>
|
||||
{templates_view}
|
||||
</ul>
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,26 @@
|
||||
main.beacons div.templates {
|
||||
padding: 10px;
|
||||
|
||||
fieldset {
|
||||
display: grid;
|
||||
grid-template-columns: 400px 200px;
|
||||
grid-row-gap: 10px;
|
||||
|
||||
input, label {
|
||||
margin: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.mode-host, .mode-custom {
|
||||
display: none;
|
||||
}
|
||||
|
||||
select[name="source_mode"]:has(> option[value="custom"]:checked) ~ .mode-custom {
|
||||
display: block;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin-bottom: 5px;
|
||||
border-bottom: 1px solid #2e2e59;
|
||||
}
|
||||
}
|
||||
@ -27,10 +27,10 @@ typedef struct Parameters {
|
||||
SourceIp_t source_ip;
|
||||
unsigned short destination_port;
|
||||
unsigned short pubkey_cert_size;
|
||||
unsigned short beacon_name_length;
|
||||
unsigned short template_name_length;
|
||||
unsigned short domain_name_length;
|
||||
char pubkey_cert[1024];
|
||||
char beacon_identifier[64];
|
||||
char beacon_name[128];
|
||||
char template_name[128];
|
||||
char domain_name[128];
|
||||
} Parameters_t;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user