feat: added beacon operation config editor
This commit is contained in:
parent
66b59531c5
commit
71b2f70686
12
Cargo.lock
generated
12
Cargo.lock
generated
@ -630,6 +630,17 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cron"
|
||||||
|
version = "0.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5877d3fbf742507b66bc2a1945106bd30dd8504019d596901ddd012a4dd01740"
|
||||||
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"once_cell",
|
||||||
|
"winnow",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-queue"
|
name = "crossbeam-queue"
|
||||||
version = "0.3.12"
|
version = "0.3.12"
|
||||||
@ -2984,6 +2995,7 @@ dependencies = [
|
|||||||
"chrono",
|
"chrono",
|
||||||
"codee",
|
"codee",
|
||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
|
"cron",
|
||||||
"futures",
|
"futures",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"hex",
|
"hex",
|
||||||
|
|||||||
@ -40,6 +40,7 @@ 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 }
|
rcgen = { version = "0.13.2", optional = true }
|
||||||
|
cron = { version = "0.15.0", optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
hydrate = ["leptos/hydrate", "chrono/wasmbind"]
|
hydrate = ["leptos/hydrate", "chrono/wasmbind"]
|
||||||
@ -62,6 +63,7 @@ ssr = [
|
|||||||
"dep:sha2",
|
"dep:sha2",
|
||||||
"dep:hex",
|
"dep:hex",
|
||||||
"dep:rcgen",
|
"dep:rcgen",
|
||||||
|
"dep:cron",
|
||||||
"leptos/ssr",
|
"leptos/ssr",
|
||||||
"leptos_meta/ssr",
|
"leptos_meta/ssr",
|
||||||
"leptos_router/ssr",
|
"leptos_router/ssr",
|
||||||
|
|||||||
@ -0,0 +1,15 @@
|
|||||||
|
DROP TABLE beacon_config;
|
||||||
|
CREATE TABLE beacon_config (
|
||||||
|
config_id integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
config_name varchar 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,
|
||||||
|
cron_mode text check (cron_mode in ('local', 'utc'))
|
||||||
|
);
|
||||||
@ -22,9 +22,12 @@ pub struct BeaconResources {
|
|||||||
add_category: ServerAction<categories::AddCategory>,
|
add_category: ServerAction<categories::AddCategory>,
|
||||||
remove_category: ServerAction<categories::RemoveCategory>,
|
remove_category: ServerAction<categories::RemoveCategory>,
|
||||||
rename_category: ServerAction<categories::RenameCategory>,
|
rename_category: ServerAction<categories::RenameCategory>,
|
||||||
|
add_beacon_config: ServerAction<configs::AddBeaconConfig>,
|
||||||
|
remove_beacon_config: ServerAction<configs::RemoveBeaconConfig>,
|
||||||
|
|
||||||
listeners: Resource<Result<Vec<listeners::PubListener>, ServerFnError>>,
|
listeners: Resource<Result<Vec<listeners::PubListener>, ServerFnError>>,
|
||||||
categories: Resource<Result<Vec<categories::Category>, ServerFnError>>,
|
categories: Resource<Result<Vec<categories::Category>, ServerFnError>>,
|
||||||
|
configs: Resource<Result<Vec<configs::BeaconConfig>, ServerFnError>>
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn provide_beacon_resources() {
|
pub fn provide_beacon_resources() {
|
||||||
@ -37,6 +40,9 @@ pub fn provide_beacon_resources() {
|
|||||||
let remove_category = ServerAction::<categories::RemoveCategory>::new();
|
let remove_category = ServerAction::<categories::RemoveCategory>::new();
|
||||||
let rename_category = ServerAction::<categories::RenameCategory>::new();
|
let rename_category = ServerAction::<categories::RenameCategory>::new();
|
||||||
|
|
||||||
|
let add_beacon_config = ServerAction::<configs::AddBeaconConfig>::new();
|
||||||
|
let remove_beacon_config = ServerAction::<configs::RemoveBeaconConfig>::new();
|
||||||
|
|
||||||
let listeners = Resource::new(
|
let listeners = Resource::new(
|
||||||
move || (
|
move || (
|
||||||
user.get(),
|
user.get(),
|
||||||
@ -56,15 +62,27 @@ pub fn provide_beacon_resources() {
|
|||||||
|_| async { categories::get_categories().await }
|
|_| async { categories::get_categories().await }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let configs = Resource::new(
|
||||||
|
move || (
|
||||||
|
user.get(),
|
||||||
|
add_beacon_config.version().get(),
|
||||||
|
remove_beacon_config.version().get(),
|
||||||
|
),
|
||||||
|
|_| async { configs::get_beacon_configs().await }
|
||||||
|
);
|
||||||
|
|
||||||
provide_context(BeaconResources {
|
provide_context(BeaconResources {
|
||||||
add_listener,
|
add_listener,
|
||||||
remove_listener,
|
remove_listener,
|
||||||
add_category,
|
add_category,
|
||||||
remove_category,
|
remove_category,
|
||||||
rename_category,
|
rename_category,
|
||||||
|
add_beacon_config,
|
||||||
|
remove_beacon_config,
|
||||||
|
|
||||||
listeners,
|
listeners,
|
||||||
categories
|
categories,
|
||||||
|
configs
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,7 +104,6 @@ pub fn BeaconView() -> impl IntoView {
|
|||||||
<li><A href="/beacons/configs">"Configs"</A></li>
|
<li><A href="/beacons/configs">"Configs"</A></li>
|
||||||
<li><A href="/beacons/categories">"Categories"</A></li>
|
<li><A href="/beacons/categories">"Categories"</A></li>
|
||||||
<li><A href="/beacons/templates">"Templates"</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>
|
<li><A href="/beacons/commands">"Commands"</A></li>
|
||||||
</ul>
|
</ul>
|
||||||
</aside>
|
</aside>
|
||||||
|
|||||||
@ -225,6 +225,7 @@ fn DisplayCategories(categories: Vec<Category>) -> impl IntoView {
|
|||||||
<label>"Name"</label>
|
<label>"Name"</label>
|
||||||
<input name="name" bind:value=target_rename_name />
|
<input name="name" bind:value=target_rename_name />
|
||||||
<input type="hidden" name="id" value=move||target_rename_id.get() />
|
<input type="hidden" name="id" value=move||target_rename_id.get() />
|
||||||
|
<div></div>
|
||||||
<input type="submit" value="Submit" disabled=move||rename_category.pending()/>
|
<input type="submit" value="Submit" disabled=move||rename_category.pending()/>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</ActionForm>
|
</ActionForm>
|
||||||
|
|||||||
@ -1,10 +1,300 @@
|
|||||||
use leptos::prelude::*;
|
use leptos::{either::Either, prelude::*};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
use {
|
||||||
|
std::str::FromStr,
|
||||||
|
|
||||||
|
sqlx::{sqlite::SqliteRow, FromRow, Row, SqlitePool},
|
||||||
|
leptos::server_fn::error::NoCustomError,
|
||||||
|
|
||||||
|
crate::db::user
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::BeaconResources;
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
|
pub enum BeaconConfigTypes {
|
||||||
|
Single,
|
||||||
|
Regular(i64),
|
||||||
|
Random(i64, i64),
|
||||||
|
CronSchedule(String, String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
impl FromRow<'_, SqliteRow> for BeaconConfigTypes {
|
||||||
|
fn from_row(row: &SqliteRow) -> sqlx::Result<Self> {
|
||||||
|
match row.try_get("mode")? {
|
||||||
|
"single" => Ok(Self::Single),
|
||||||
|
"regular" => Ok(Self::Regular(row.try_get("regular_interval")?)),
|
||||||
|
"random" => Ok(Self::Random(
|
||||||
|
row.try_get("random_min_time")?,
|
||||||
|
row.try_get("random_max_time")?,
|
||||||
|
)),
|
||||||
|
"cron" => Ok(Self::CronSchedule(
|
||||||
|
row.try_get("cron_schedule")?,
|
||||||
|
row.try_get("cron_mode")?
|
||||||
|
)),
|
||||||
|
type_name => Err(sqlx::Error::TypeNotFound {
|
||||||
|
type_name: type_name.to_string(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "ssr", derive(FromRow))]
|
||||||
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
|
pub struct BeaconConfig {
|
||||||
|
config_id: i64,
|
||||||
|
config_name: String,
|
||||||
|
#[cfg_attr(feature = "ssr", sqlx(flatten))]
|
||||||
|
config_type: BeaconConfigTypes,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[server]
|
||||||
|
pub async fn get_beacon_configs() -> Result<Vec<BeaconConfig>, 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_config")
|
||||||
|
.fetch_all(&db)
|
||||||
|
.await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[server]
|
||||||
|
pub async fn add_beacon_config(
|
||||||
|
name: String,
|
||||||
|
mode: String,
|
||||||
|
regular_interval: i64,
|
||||||
|
random_min_time: i64,
|
||||||
|
random_max_time: i64,
|
||||||
|
cron_schedule: String,
|
||||||
|
cron_mode: 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()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let db = expect_context::<SqlitePool>();
|
||||||
|
|
||||||
|
match &*mode {
|
||||||
|
"single" => {
|
||||||
|
sqlx::query!(
|
||||||
|
"INSERT INTO beacon_config (config_name, mode) VALUES (?, 'single')",
|
||||||
|
name
|
||||||
|
)
|
||||||
|
.execute(&db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
"regular" => {
|
||||||
|
if regular_interval < 1 {
|
||||||
|
return Err(ServerFnError::<NoCustomError>::ServerError("Invalid interval provided".to_owned()))
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlx::query!(
|
||||||
|
"INSERT INTO beacon_config (config_name, mode, regular_interval) VALUES (?, 'regular', ?)",
|
||||||
|
name,
|
||||||
|
regular_interval
|
||||||
|
)
|
||||||
|
.execute(&db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
"random" => {
|
||||||
|
if random_min_time < 1 || random_max_time < random_min_time {
|
||||||
|
return Err(ServerFnError::<NoCustomError>::ServerError("Invalid random interval provided".to_owned()))
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlx::query!(
|
||||||
|
"INSERT INTO beacon_config (config_name, mode, random_min_time, random_max_time) VALUES (?, 'random', ?, ?)",
|
||||||
|
name,
|
||||||
|
random_min_time,
|
||||||
|
random_max_time
|
||||||
|
)
|
||||||
|
.execute(&db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
"cron" => {
|
||||||
|
if let Err(e) = cron::Schedule::from_str(&cron_schedule) {
|
||||||
|
return Err(ServerFnError::<NoCustomError>::ServerError(format!(
|
||||||
|
"Could not parse cron expression: {}",
|
||||||
|
e
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
match &*cron_mode {
|
||||||
|
"local" | "utc" => {},
|
||||||
|
other => {
|
||||||
|
return Err(ServerFnError::<NoCustomError>::ServerError("Unrecognized timezone specifier for cron".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlx::query!(
|
||||||
|
"INSERT INTO beacon_config (config_name, mode, cron_schedule, cron_mode) VALUES (?, 'cron', ?, ?)",
|
||||||
|
name,
|
||||||
|
cron_schedule,
|
||||||
|
cron_mode
|
||||||
|
)
|
||||||
|
.execute(&db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
Err(ServerFnError::<NoCustomError>::ServerError("Invalid mode supplied".to_owned()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[server]
|
||||||
|
pub async fn remove_beacon_config(id: i64) -> Result<(), ServerFnError> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn ConfigsView() -> impl IntoView {
|
pub fn ConfigsView() -> impl IntoView {
|
||||||
view! {
|
let BeaconResources { add_beacon_config, configs, .. } = expect_context();
|
||||||
<div>
|
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<div class="config">
|
||||||
|
<h2>"Beacon configuration"</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
"Beacon configuration controls the mode of operation of a beacon. Currently, there are 4 modes:"
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>"Single: When the beacon runs, perform a single callback and exit"</li>
|
||||||
|
<li>"Regular: After launching, stay launched and perform a callback every X seconds"</li>
|
||||||
|
<li>"Random: After launching, stay launched and perform a callback a random amount of time every X seconds between the specified minimum and maximum"</li>
|
||||||
|
<li>
|
||||||
|
"Cron: Operate on a crontab schedule (as shown "
|
||||||
|
<a href="https://docs.rs/cron/0.15.0/cron/">"here"</a>
|
||||||
|
")"
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ActionForm action=add_beacon_config>
|
||||||
|
<fieldset>
|
||||||
|
{move || match add_beacon_config.value().get() {
|
||||||
|
Some(Ok(_)) => Either::Right(()),
|
||||||
|
None => Either::Right(()),
|
||||||
|
Some(Err(e)) => Either::Left(view! {
|
||||||
|
<p>"Error creating config:"</p>
|
||||||
|
<p>{format!("{e:?}")}</p>
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
<legend>"Add beacon configuration"</legend>
|
||||||
|
<label>"Configuration name"</label>
|
||||||
|
<input name="name" />
|
||||||
|
<label>"Configuration type"</label>
|
||||||
|
<select name="mode">
|
||||||
|
<option value="single">"Single"</option>
|
||||||
|
<option value="regular">"Regular"</option>
|
||||||
|
<option value="random">"Random"</option>
|
||||||
|
<option value="cron">"Cron"</option>
|
||||||
|
</select>
|
||||||
|
<label class="mode-regular">"Interval"</label>
|
||||||
|
<input class="mode-regular" name="regular_interval" type="number" value="0" />
|
||||||
|
<label class="mode-random">"Random interval (min)"</label>
|
||||||
|
<input class="mode-random" name="random_min_time" type="number" value="0" />
|
||||||
|
<label class="mode-random">"Random interval (max)"</label>
|
||||||
|
<input class="mode-random" name="random_max_time" type="number" value="0" />
|
||||||
|
<label class="mode-cron">"Schedule"</label>
|
||||||
|
<input class="mode-cron" name="cron_schedule" />
|
||||||
|
<label class="mode-cron">"Cron timezone"</label>
|
||||||
|
<select class="mode-cron" name="cron_mode">
|
||||||
|
<option value="local">"Local to target"</option>
|
||||||
|
<option value="utc">"UTC"</option>
|
||||||
|
</select>
|
||||||
|
<div></div>
|
||||||
|
<input type="submit" value="Submit" disabled=move||add_beacon_config.pending() />
|
||||||
|
</fieldset>
|
||||||
|
</ActionForm>
|
||||||
|
|
||||||
|
<Suspense fallback=|| view! { <p>"Loading..."</p> }>
|
||||||
|
{ move || match configs.get() {
|
||||||
|
Some(inner) => Either::Right(match inner {
|
||||||
|
Err(e) => Either::Left(view! {
|
||||||
|
<p>"There was an error loading configs:"</p>
|
||||||
|
<p>{format!("error: {}", e)}</p>
|
||||||
|
}),
|
||||||
|
Ok(cs) => Either::Right(view! {
|
||||||
|
<DisplayConfigs configs=cs />
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
None => Either::Left(view! {
|
||||||
|
<p>"Loading..."</p>
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn DisplayConfigs(configs: Vec<BeaconConfig>) -> impl IntoView {
|
||||||
|
let BeaconResources { remove_beacon_config, .. } = expect_context();
|
||||||
|
|
||||||
|
let configs_view = configs
|
||||||
|
.iter()
|
||||||
|
.map(|config| view! {
|
||||||
|
<li>
|
||||||
|
{config.config_id}
|
||||||
|
": "
|
||||||
|
{config.config_name.clone()}
|
||||||
|
" ("
|
||||||
|
{match &config.config_type {
|
||||||
|
BeaconConfigTypes::Single => {
|
||||||
|
format!("Single")
|
||||||
|
},
|
||||||
|
BeaconConfigTypes::Regular(int) => {
|
||||||
|
format!("Regular; every {int} seconds")
|
||||||
|
},
|
||||||
|
BeaconConfigTypes::Random(min, max) => {
|
||||||
|
format!("Random; regularly between {min} and {max} seconds")
|
||||||
|
},
|
||||||
|
BeaconConfigTypes::CronSchedule(sch, mode) => {
|
||||||
|
format!("{mode} cron; schedule: {sch}")
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
") "
|
||||||
|
<button
|
||||||
|
on:click={
|
||||||
|
let id = config.config_id;
|
||||||
|
move |_| {
|
||||||
|
remove_beacon_config.dispatch(RemoveBeaconConfig { id });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
"delete"
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
})
|
||||||
|
.collect_view();
|
||||||
|
|
||||||
|
view! {
|
||||||
|
{move || match remove_beacon_config.value().get() {
|
||||||
|
Some(Ok(_)) => Either::Right(()),
|
||||||
|
None => Either::Right(()),
|
||||||
|
Some(Err(e)) => Either::Left(view! {
|
||||||
|
<p>"Error deleting config:"</p>
|
||||||
|
<p>{format!("{e:?}")}</p>
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
<ul>
|
||||||
|
{configs_view}
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
#[cfg(feature = "ssr")]
|
||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
|
|
||||||
use leptos::{either::Either, prelude::*};
|
use leptos::{either::Either, prelude::*};
|
||||||
@ -196,17 +197,6 @@ fn DisplayListeners(listeners: Vec<PubListener>) -> impl IntoView {
|
|||||||
{match listener.active {
|
{match listener.active {
|
||||||
true => Either::Left(view! {
|
true => Either::Left(view! {
|
||||||
<span>"active!"</span>
|
<span>"active!"</span>
|
||||||
<button
|
|
||||||
on:click={
|
|
||||||
let id = listener.listener_id;
|
|
||||||
move |e| {
|
|
||||||
let _ = e.prevent_default();
|
|
||||||
remove_listener.dispatch(RemoveListener { listener_id: id });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
"delete"
|
|
||||||
</button>
|
|
||||||
}),
|
}),
|
||||||
false => Either::Right(view! {
|
false => Either::Right(view! {
|
||||||
<button
|
<button
|
||||||
@ -222,6 +212,17 @@ fn DisplayListeners(listeners: Vec<PubListener>) -> impl IntoView {
|
|||||||
</button>
|
</button>
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
|
<button
|
||||||
|
on:click={
|
||||||
|
let id = listener.listener_id;
|
||||||
|
move |e| {
|
||||||
|
let _ = e.prevent_default();
|
||||||
|
remove_listener.dispatch(RemoveListener { listener_id: id });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
"delete"
|
||||||
|
</button>
|
||||||
</li>
|
</li>
|
||||||
})
|
})
|
||||||
.collect_view();
|
.collect_view();
|
||||||
|
|||||||
@ -0,0 +1,12 @@
|
|||||||
|
main.beacons div.categories {
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 150px 200px;
|
||||||
|
|
||||||
|
input, label {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
main.beacons div.config {
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 300px 200px;
|
||||||
|
|
||||||
|
input, label {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-regular, .mode-random, .mode-cron {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
select:has(> option[value="regular"]:checked) ~ .mode-regular {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
select:has(> option[value="random"]:checked) ~ .mode-random {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
select:has(> option[value="cron"]:checked) ~ .mode-cron {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
main.beacons div.listeners {
|
main.beacons div.listeners {
|
||||||
form {
|
form, p, h2 {
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -72,11 +72,11 @@ input[type="submit"],
|
|||||||
input[type="button"],
|
input[type="button"],
|
||||||
button {
|
button {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border: 1px solid transparent;
|
border: 1px solid black;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin: 5px;
|
margin: 5px;
|
||||||
padding: 2px 4px;
|
padding: 5px 7px;
|
||||||
border-radius: 2px;
|
border-radius: 4px;
|
||||||
box-shadow: #fff7 0 1px 0 inset;
|
box-shadow: #fff7 0 1px 0 inset;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user