Compare commits
14 Commits
416aea14d0
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4db5519341 | ||
|
|
5748a32b59 | ||
|
|
3f1eb33d6a | ||
|
|
3663562b8a | ||
|
|
10196cbbfb | ||
|
|
64e0168443 | ||
|
|
3bf77add88 | ||
|
|
e7a45dc46c | ||
|
|
876bcfe257 | ||
|
|
735abf577c | ||
|
|
4a8d22cb47 | ||
|
|
da99111317 | ||
|
|
31e23086ce | ||
|
|
01e95aa292 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,3 +4,4 @@ target/
|
|||||||
site/
|
site/
|
||||||
style/output.css
|
style/output.css
|
||||||
.env
|
.env
|
||||||
|
.env-prod
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ axum = { version = "0.7", optional = true }
|
|||||||
jsonwebtoken = { version = "9", optional = true }
|
jsonwebtoken = { version = "9", optional = true }
|
||||||
tokio = { version = "1.39", features = ["rt-multi-thread"], optional = true }
|
tokio = { version = "1.39", features = ["rt-multi-thread"], optional = true }
|
||||||
tower = { version = "0.4", optional = true }
|
tower = { version = "0.4", optional = true }
|
||||||
tower-http = { version = "0.5", features = ["fs", "trace"], optional = true }
|
tower-http = { version = "0.5", features = ["fs", "trace", "cors"], optional = true }
|
||||||
sqlx = { version = "0.8", features = [
|
sqlx = { version = "0.8", features = [
|
||||||
"runtime-tokio-rustls",
|
"runtime-tokio-rustls",
|
||||||
"sqlite",
|
"sqlite",
|
||||||
|
|||||||
20
Makefile
20
Makefile
@@ -1,18 +1,8 @@
|
|||||||
deploy:
|
|
||||||
npx tailwindcss -i ./input.css -o ./style/output.css --minify
|
|
||||||
tac Cargo.toml | sed '1s/^.//' | tac > fichier_temp.txt && mv fichier_temp.txt Cargo.toml
|
|
||||||
#cargo leptos build
|
|
||||||
cargo leptos build --release
|
|
||||||
tac Cargo.toml | sed '1s/^/#/' | tac > fichier_temp.txt && mv fichier_temp.txt Cargo.toml
|
|
||||||
ssh raspiwork "sudo systemctl stop rust_leptos.service"
|
|
||||||
scp target/aarch64-unknown-linux-gnu/release/rust_leptos raspiwork:/var/www/rust_leptos/rust_leptos
|
|
||||||
#scp target/aarch64-unknown-linux-gnu/debug/rust_leptos raspiwork:/var/www/rust_leptos/rust_leptos
|
|
||||||
scp -r target/site raspiwork:/var/www/rust_leptos/
|
|
||||||
ssh raspiwork "sudo systemctl start rust_leptos.service"
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
cargo leptos build --release
|
git pull
|
||||||
|
npx tailwindcss -i ./input.css -o ./style/output.css --minify
|
||||||
|
wasm-pack build --target=web --no-default-features --features=hydrate
|
||||||
sudo systemctl stop rust_leptos.service
|
sudo systemctl stop rust_leptos.service
|
||||||
cp target/release/rust_leptos /var/www/rust_leptos/rust_leptos
|
cargo sqlx migrate run
|
||||||
cp -r target/site /var/www/rust_leptos/
|
cargo leptos build --release
|
||||||
sudo systemctl start rust_leptos.service
|
sudo systemctl start rust_leptos.service
|
||||||
|
|||||||
21
README.md
21
README.md
@@ -1,24 +1,30 @@
|
|||||||
|
# install
|
||||||
|
```
|
||||||
|
cargo install sqlx-cli
|
||||||
wasm-pack build --target=web --debug --no-default-features --features=hydrate
|
wasm-pack build --target=web --debug --no-default-features --features=hydrate
|
||||||
cargo run --no-default-features --features=ssr
|
cargo run --no-default-features --features=ssr
|
||||||
|
```
|
||||||
|
|
||||||
# dev
|
# dev
|
||||||
|
```
|
||||||
source .env
|
source .env
|
||||||
cargo sqlx migrate run
|
cargo sqlx migrate run
|
||||||
cargo leptos watch
|
cargo leptos watch
|
||||||
|
```
|
||||||
|
|
||||||
npx tailwindcss -i ./input.css -o ./style/output.css --watch
|
```npx tailwindcss -i ./input.css -o ./style/output.css --watch```
|
||||||
|
|
||||||
# deploy
|
# deploy
|
||||||
|
|
||||||
example of systemd config
|
example of systemd config
|
||||||
```
|
```
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Running rust script
|
Description=Home website
|
||||||
After=multi-user.target
|
After=mysql.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
EnvironmentFile=/home/pi/Scripts/rust_mosquitto/target/release/.env
|
EnvironmentFile=/var/www/rust_leptos/.env
|
||||||
ExecStart=/home/pi/Scripts/rust_mosquitto/target/release/rust_mosquitto
|
ExecStart=/var/www/rust_leptos/rust_leptos
|
||||||
Type=simple
|
Type=simple
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
@@ -32,7 +38,10 @@ WantedBy=multi-user.target
|
|||||||
[x] router
|
[x] router
|
||||||
[ ] value gestion des erreurs
|
[ ] value gestion des erreurs
|
||||||
[x] deploy
|
[x] deploy
|
||||||
[ ] hydratation
|
[x] hydratation
|
||||||
|
[x] liens icones
|
||||||
|
[ ] liens position
|
||||||
|
[ ] liens edition
|
||||||
|
|
||||||
## pages
|
## pages
|
||||||
[x] liens
|
[x] liens
|
||||||
|
|||||||
2
migrations/20250604201415_add_icon_to_links.down.sql
Normal file
2
migrations/20250604201415_add_icon_to_links.down.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE links
|
||||||
|
DROP COLUMN icon;
|
||||||
2
migrations/20250604201415_add_icon_to_links.up.sql
Normal file
2
migrations/20250604201415_add_icon_to_links.up.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE links
|
||||||
|
ADD COLUMN icon TEXT NOT NULL DEFAULT "missing" AFTER link;
|
||||||
20
src/app.rs
20
src/app.rs
@@ -20,13 +20,21 @@ pub fn App() -> impl IntoView {
|
|||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" view=move || view! { <Home/> }/>
|
<Route path="/" view=move || view! { <Home/> }/>
|
||||||
<Route path="/liens" view=move || view! { <Links/> }/>
|
<Route path="/liens" view=move || view! { <Links/> }/>
|
||||||
<Route path="/formulaire" view=move || view! { <FormValues/> }/>
|
|
||||||
<Route path="/donnees" view=move || view! { <Data/> }/>
|
|
||||||
<Route path="/volets" view=move || view! { <Shutters/> }/>
|
<Route path="/volets" view=move || view! { <Shutters/> }/>
|
||||||
//<Route path="/*any" view=move || view! { <NotFound/> }/>
|
//<Route path="/*any" view=move || view! { <NotFound/> }/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</main>
|
</main>
|
||||||
<footer>Footer</footer>
|
<footer>
|
||||||
|
//<span>Footer</span>
|
||||||
|
//<ul>
|
||||||
|
// <li class="xs:block sm:hidden md:hidden lg:hidden xl:hidden">xs</li>
|
||||||
|
// <li class="sm:block xs:hidden md:hidden lg:hidden xl:hidden">sm</li>
|
||||||
|
// <li class="md:block xs:hidden sm:hidden lg:hidden xl:hidden">md</li>
|
||||||
|
// <li class="lg:block xs:hidden sm:hidden md:hidden xl:hidden">lg</li>
|
||||||
|
// <li class="xl:block xs:hidden sm:hidden mg:hidden lg:hidden">xl</li>
|
||||||
|
// <li class="md:hidden lg:hidden xl:hidden">sm</li>
|
||||||
|
//</ul>
|
||||||
|
</footer>
|
||||||
</Router>
|
</Router>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -44,12 +52,6 @@ pub fn Navigation() -> impl IntoView {
|
|||||||
<a href="/liens"
|
<a href="/liens"
|
||||||
class="hover:text-third hover:border-b border-third inline-block transition-colors"
|
class="hover:text-third hover:border-b border-third inline-block transition-colors"
|
||||||
class:active-link=move || location.pathname.get() == "/liens">Liens</a>
|
class:active-link=move || location.pathname.get() == "/liens">Liens</a>
|
||||||
<a href="/formulaire"
|
|
||||||
class="hover:text-third hover:border-b border-third inline-block transition-colors"
|
|
||||||
class:active-link=move || location.pathname.get() == "/formulaire">Formulaire</a>
|
|
||||||
<a href="/donnees"
|
|
||||||
class="hover:text-third hover:border-b border-third inline-block transition-colors"
|
|
||||||
class:active-link=move || location.pathname.get() == "/donnees">Données</a>
|
|
||||||
<a href="/volets"
|
<a href="/volets"
|
||||||
class="hover:text-third hover:border-b border-third inline-block transition-colors"
|
class="hover:text-third hover:border-b border-third inline-block transition-colors"
|
||||||
class:active-link=move || location.pathname.get() == "/volets">Volets</a>
|
class:active-link=move || location.pathname.get() == "/volets">Volets</a>
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ async fn create_pool() -> sqlx::MySqlPool {
|
|||||||
.await
|
.await
|
||||||
.expect("could not connect to database_url");
|
.expect("could not connect to database_url");
|
||||||
|
|
||||||
sqlx::migrate!()
|
/*sqlx::migrate!()
|
||||||
.run(&pool)
|
.run(&pool)
|
||||||
.await
|
.await
|
||||||
.expect("migrations failed");
|
.expect("migrations failed");*/
|
||||||
|
|
||||||
pool
|
pool
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ pub struct Link {
|
|||||||
pub id: u64,
|
pub id: u64,
|
||||||
pub link: String,
|
pub link: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
pub icon: String,
|
||||||
position: i64,
|
position: i64,
|
||||||
created_at: String,
|
created_at: String,
|
||||||
}
|
}
|
||||||
@@ -14,11 +15,13 @@ impl Link {
|
|||||||
pub async fn insert(
|
pub async fn insert(
|
||||||
name: String,
|
name: String,
|
||||||
link: String,
|
link: String,
|
||||||
|
icon: String,
|
||||||
) -> Result<sqlx::mysql::MySqlQueryResult, sqlx::Error> {
|
) -> Result<sqlx::mysql::MySqlQueryResult, sqlx::Error> {
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"INSERT INTO links (name, link, position, created_at) VALUES (?, ?, (SELECT COALESCE(MAX(position) + 1, 1) FROM links lin), ?)",
|
"INSERT INTO links (name, link, icon, position, created_at) VALUES (?, ?, ?, (SELECT COALESCE(MAX(position) + 1, 1) FROM links lin), ?)",
|
||||||
name,
|
name,
|
||||||
link,
|
link,
|
||||||
|
icon,
|
||||||
chrono::Local::now().naive_local(),
|
chrono::Local::now().naive_local(),
|
||||||
)
|
)
|
||||||
.execute(crate::database::get_db())
|
.execute(crate::database::get_db())
|
||||||
@@ -27,11 +30,12 @@ impl Link {
|
|||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
pub async fn get_all() -> Result<Vec<Self>, sqlx::Error> {
|
pub async fn get_all() -> Result<Vec<Self>, sqlx::Error> {
|
||||||
sqlx::query!("SELECT id, name, link, position, created_at FROM links ORDER BY position")
|
sqlx::query!("SELECT id, name, link, icon, position, created_at FROM links ORDER BY position")
|
||||||
.map(|x| Self {
|
.map(|x| Self {
|
||||||
id: x.id,
|
id: x.id,
|
||||||
name: x.name,
|
name: x.name,
|
||||||
link: x.link,
|
link: x.link,
|
||||||
|
icon: x.icon,
|
||||||
position: x.position,
|
position: x.position,
|
||||||
created_at: x.created_at.format("%d/%m/%Y %H:%M").to_string(),
|
created_at: x.created_at.format("%d/%m/%Y %H:%M").to_string(),
|
||||||
})
|
})
|
||||||
@@ -45,13 +49,14 @@ impl Link {
|
|||||||
direction: String,
|
direction: String,
|
||||||
) -> Result<sqlx::mysql::MySqlQueryResult, sqlx::Error> {
|
) -> Result<sqlx::mysql::MySqlQueryResult, sqlx::Error> {
|
||||||
let link = sqlx::query!(
|
let link = sqlx::query!(
|
||||||
"SELECT id, name, link, position, created_at FROM links WHERE id = ?",
|
"SELECT id, name, link, icon, position, created_at FROM links WHERE id = ?",
|
||||||
link_id
|
link_id
|
||||||
)
|
)
|
||||||
.map(|x| Self {
|
.map(|x| Self {
|
||||||
id: x.id,
|
id: x.id,
|
||||||
name: x.name,
|
name: x.name,
|
||||||
link: x.link,
|
link: x.link,
|
||||||
|
icon: x.icon,
|
||||||
position: x.position,
|
position: x.position,
|
||||||
created_at: x.created_at.format("%d/%m/%Y %H:%M").to_string(),
|
created_at: x.created_at.format("%d/%m/%Y %H:%M").to_string(),
|
||||||
})
|
})
|
||||||
@@ -89,6 +94,24 @@ impl Link {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
pub async fn update(
|
||||||
|
id: String,
|
||||||
|
name: String,
|
||||||
|
link: String,
|
||||||
|
icon: String,
|
||||||
|
) -> Result<sqlx::mysql::MySqlQueryResult, sqlx::Error> {
|
||||||
|
sqlx::query!(
|
||||||
|
"UPDATE links SET name = ?, link = ?, icon = ? WHERE id = ?",
|
||||||
|
name,
|
||||||
|
link,
|
||||||
|
icon,
|
||||||
|
id
|
||||||
|
)
|
||||||
|
.execute(crate::database::get_db())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
pub async fn destroy(id: String) -> Result<sqlx::mysql::MySqlQueryResult, sqlx::Error> {
|
pub async fn destroy(id: String) -> Result<sqlx::mysql::MySqlQueryResult, sqlx::Error> {
|
||||||
sqlx::query!("DELETE FROM links WHERE id = ? ", id,)
|
sqlx::query!("DELETE FROM links WHERE id = ? ", id,)
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
mod link;
|
mod link;
|
||||||
mod value;
|
|
||||||
pub use link::Link;
|
pub use link::Link;
|
||||||
pub use value::Value;
|
|
||||||
pub use value::OPTIONS;
|
|
||||||
|
|||||||
@@ -1,122 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Option {
|
|
||||||
pub value: &'static str,
|
|
||||||
pub name: &'static str,
|
|
||||||
service: &'static str,
|
|
||||||
device: &'static str,
|
|
||||||
r_type: &'static str,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const OPTIONS: &[Option] = &[
|
|
||||||
Option {
|
|
||||||
value: "citerne",
|
|
||||||
name: "Citerne",
|
|
||||||
service: "home",
|
|
||||||
device: "citerne",
|
|
||||||
r_type: "height",
|
|
||||||
},
|
|
||||||
Option {
|
|
||||||
value: "eau",
|
|
||||||
name: "Eau",
|
|
||||||
service: "home",
|
|
||||||
device: "eau",
|
|
||||||
r_type: "volume",
|
|
||||||
},
|
|
||||||
Option {
|
|
||||||
value: "elec-hight",
|
|
||||||
name: "Electricité heure pleine",
|
|
||||||
service: "home",
|
|
||||||
device: "elec",
|
|
||||||
r_type: "pleine",
|
|
||||||
},
|
|
||||||
Option {
|
|
||||||
value: "elec-low",
|
|
||||||
name: "Electricité heure creuse",
|
|
||||||
service: "home",
|
|
||||||
device: "elec",
|
|
||||||
r_type: "creuse",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
pub struct Value {
|
|
||||||
id: i64,
|
|
||||||
service: String,
|
|
||||||
capteur: String,
|
|
||||||
type_donnee: String,
|
|
||||||
pub donnee: String,
|
|
||||||
pub date_donnee: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Value {
|
|
||||||
#[cfg(feature = "ssr")]
|
|
||||||
pub async fn insert(
|
|
||||||
device: String,
|
|
||||||
value: String,
|
|
||||||
) -> Result<sqlx::mysql::MySqlQueryResult, sqlx::Error> {
|
|
||||||
let option = match find_option(&device) {
|
|
||||||
Ok(option) => option,
|
|
||||||
Err(err) => panic!("{}", err),
|
|
||||||
};
|
|
||||||
|
|
||||||
sqlx::query!(
|
|
||||||
"INSERT INTO donnees(service, capteur, type, donnee, date_donnee) VALUES (?, ?, ?, ?, ?)",
|
|
||||||
option.service,
|
|
||||||
option.device,
|
|
||||||
option.r_type,
|
|
||||||
value,
|
|
||||||
chrono::Local::now().naive_local(),
|
|
||||||
)
|
|
||||||
.execute(crate::database::get_db())
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
|
||||||
pub async fn get_one(topic: String) -> Result<Self, sqlx::Error> {
|
|
||||||
let split: Vec<&str> = topic.split("/").collect();
|
|
||||||
let unknow_value = Value {
|
|
||||||
id: 0,
|
|
||||||
service: "".to_string(),
|
|
||||||
capteur: "".to_string(),
|
|
||||||
type_donnee: "".to_string(),
|
|
||||||
donnee: "--".to_string(),
|
|
||||||
date_donnee: "--:--".to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if split.len() == 3 {
|
|
||||||
let res = sqlx::query!(
|
|
||||||
"SELECT * FROM donnees WHERE service = ? AND capteur = ? AND type = ? ORDER BY date_donnee DESC LIMIT 1",
|
|
||||||
split[0],
|
|
||||||
split[1],
|
|
||||||
split[2],
|
|
||||||
//chrono::Local::now().naive_local(),
|
|
||||||
)
|
|
||||||
.map(|x| Self {
|
|
||||||
id: x.id,
|
|
||||||
service: x.service,
|
|
||||||
capteur: x.capteur,
|
|
||||||
type_donnee: x.r#type,
|
|
||||||
donnee: x.donnee,
|
|
||||||
date_donnee: x.date_donnee.format("%H:%M").to_string(),
|
|
||||||
})
|
|
||||||
.fetch_one(crate::database::get_db())
|
|
||||||
.await;
|
|
||||||
|
|
||||||
match res {
|
|
||||||
Ok(res) => Ok(res),
|
|
||||||
Err(_) => Ok(unknow_value)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Ok(unknow_value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_option(value: &str) -> Result<&Option, &str> {
|
|
||||||
OPTIONS
|
|
||||||
.iter()
|
|
||||||
.find(|&option| option.value == value)
|
|
||||||
.ok_or("No option found with the given value")
|
|
||||||
}
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
use leptos::*;
|
|
||||||
use leptos_meta::*;
|
|
||||||
|
|
||||||
use crate::models::Value;
|
|
||||||
|
|
||||||
#[component]
|
|
||||||
pub fn Data() -> impl IntoView {
|
|
||||||
view! {
|
|
||||||
<Title text="Capteurs"/>
|
|
||||||
|
|
||||||
<ul class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 gap-3 m-5">
|
|
||||||
<ValueCard location="Bureau".to_string()
|
|
||||||
sensor="Température".to_string()
|
|
||||||
unity="°C".to_string()
|
|
||||||
color="text-third".to_string()
|
|
||||||
topic="maison/bureau/temperature".to_string() />
|
|
||||||
<ValueCard location="Bureau".to_string()
|
|
||||||
sensor="CO2".to_string()
|
|
||||||
unity="ppm".to_string()
|
|
||||||
color="text-green".to_string()
|
|
||||||
topic="maison/bureau/co2".to_string() />
|
|
||||||
<ValueCard location="Bureau".to_string()
|
|
||||||
sensor="Pression".to_string()
|
|
||||||
unity="hPa".to_string()
|
|
||||||
color="text-fourth".to_string()
|
|
||||||
topic="maison/bureau/pression".to_string() />
|
|
||||||
<ValueCard location="Extérieur".to_string()
|
|
||||||
sensor="Température".to_string()
|
|
||||||
unity="°C".to_string()
|
|
||||||
color="text-third".to_string()
|
|
||||||
topic="meteo/exterieur/temperature".to_string() />
|
|
||||||
<ValueCard location="Véranda".to_string()
|
|
||||||
sensor="Température".to_string()
|
|
||||||
unity="°C".to_string()
|
|
||||||
color="text-third".to_string()
|
|
||||||
topic="meteo/veranda/temperature".to_string() />
|
|
||||||
</ul>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[server(ValueAction, "/api")]
|
|
||||||
pub async fn get_value(topic: String) -> Result<crate::models::Value, ServerFnError> {
|
|
||||||
crate::models::Value::get_one(topic).await.map_err(|x| {
|
|
||||||
let err = format!("Error while posting a comment: {x:?}");
|
|
||||||
tracing::error!("{err}");
|
|
||||||
ServerFnError::ServerError("Could not post a comment, try again later".into())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[component]
|
|
||||||
pub fn ValueCard(topic: String, location: String, sensor: String, unity: String, color: String) -> impl IntoView {
|
|
||||||
let (sensor_props, _) = create_signal((location, sensor, unity, color));
|
|
||||||
let value = create_resource(
|
|
||||||
move || topic.clone(),
|
|
||||||
|topic| async { get_value(topic).await },
|
|
||||||
);
|
|
||||||
|
|
||||||
view! {
|
|
||||||
<li class="flex p-3 bg-prim-light rounded-lg justify-between">
|
|
||||||
<Suspense fallback=move || view! { <p>"Loading Value"</p> }>
|
|
||||||
<ErrorBoundary fallback=|_| {
|
|
||||||
view! { <p class="error-messages text-xs-center">"Something went wrong, please try again later."</p>}
|
|
||||||
}>
|
|
||||||
{move || value.get().map(move |x| {
|
|
||||||
x.map(move |result| {
|
|
||||||
view! {
|
|
||||||
<Card sensor_props value=result />
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</ErrorBoundary>
|
|
||||||
</Suspense>
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[component]
|
|
||||||
pub fn Card(sensor_props: ReadSignal<(String, String, String, String)>, value: Value) -> impl IntoView {
|
|
||||||
let (location, sensor, unity, color) = sensor_props.get();
|
|
||||||
let value_class = ["text-4xl flex ml-5 -my-2 text-center ", color.as_str()].join(" ");
|
|
||||||
view! {
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<div class="text-xl flex-1">{location}</div>
|
|
||||||
<div class="text-second-dark">{sensor}</div>
|
|
||||||
<div class="text-second-dark text-sm">{value.date_donnee}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2 class=value_class>
|
|
||||||
<strong>{value.donnee}</strong>
|
|
||||||
<span class="text-2xl mt-5">{unity}</span>
|
|
||||||
</h2>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,8 +13,8 @@ pub async fn get_links() -> Result<Vec<crate::models::Link>, ServerFnError> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[server(LinkAction, "/api")]
|
#[server(LinkAction, "/api")]
|
||||||
pub async fn add_value(name: String, link: String) -> Result<(), ServerFnError> {
|
pub async fn add_value(name: String, link: String, icon: String) -> Result<(), ServerFnError> {
|
||||||
crate::models::Link::insert(name, link)
|
crate::models::Link::insert(name, link, icon)
|
||||||
.await
|
.await
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
.map_err(|x| {
|
.map_err(|x| {
|
||||||
@@ -26,52 +26,28 @@ pub async fn add_value(name: String, link: String) -> Result<(), ServerFnError>
|
|||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Links() -> impl IntoView {
|
pub fn Links() -> impl IntoView {
|
||||||
let (show, set_show) = create_signal(false);
|
let (show_form, set_show_form) = create_signal(false);
|
||||||
|
let (edit_link, set_edit_link) = create_signal::<Option<crate::models::Link>>(None);
|
||||||
let (edit, set_edit) = create_signal(false);
|
let (edit, set_edit) = create_signal(false);
|
||||||
|
|
||||||
let link_action = create_server_action::<LinkAction>();
|
let link_action = create_server_action::<LinkAction>();
|
||||||
|
let update_action = create_server_action::<UpdateLinkAction>();
|
||||||
|
|
||||||
let result = link_action.version();
|
let result = link_action.version();
|
||||||
let reset_form = create_rw_signal("");
|
let update_result = update_action.version();
|
||||||
|
|
||||||
let links = create_resource(
|
let links = create_resource(
|
||||||
move || (result.get()),
|
move || (result.get(), update_result.get()),
|
||||||
move |_| async move {
|
move |_| async move {
|
||||||
reset_form.set("");
|
set_show_form.set(false);
|
||||||
set_show.set(false);
|
set_edit_link.set(None);
|
||||||
get_links().await
|
get_links().await
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let form = move || {
|
|
||||||
show.get().then(|| {
|
|
||||||
view! { <div class="my-0 mx-auto w-72 p-6 bg-prim-light rounded-lg">
|
|
||||||
<h2 class="pb-6 text-2xl text-center">"Ajout d'un lien"</h2>
|
|
||||||
<ActionForm action=link_action attr:class="">
|
|
||||||
<div>
|
|
||||||
<label class="block mt-3 mb-1">"Nom"</label>
|
|
||||||
<input type="text"
|
|
||||||
name="name"
|
|
||||||
prop:value=move || reset_form.get()
|
|
||||||
class="text-center bg-prim border border-transparent rounded-lg focus:border-third px-2 py-2 w-60" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label class="block mt-3 mb-1">"Lien"</label>
|
|
||||||
<input type="url"
|
|
||||||
name="link"
|
|
||||||
prop:value=move || reset_form.get()
|
|
||||||
class="text-center bg-prim border border-transparent rounded-lg focus:border-third px-2 py-2 w-60" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button type="submit" class="bg-third hover:bg-third-light rounded-lg transition-colors px-2 py-1 w-60 my-5">"Valider"</button>
|
|
||||||
</div>
|
|
||||||
</ActionForm>
|
|
||||||
</div> }
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<Title text="Liens"/>
|
<Title text="Liens"/>
|
||||||
<ul class="flex flex-wrap gap-5 m-5 justify-center">
|
<ul class="flex gap-5 m-5 flex-wrap justify-center">
|
||||||
<Suspense fallback=move || view! {<p>"Loading Comments from the article"</p> }>
|
<Suspense fallback=move || view! {<p>"Loading Comments from the article"</p> }>
|
||||||
<ErrorBoundary fallback=|_| {
|
<ErrorBoundary fallback=|_| {
|
||||||
view! { <p class="error-messages text-xs-center">"Something went wrong."</p>}
|
view! { <p class="error-messages text-xs-center">"Something went wrong."</p>}
|
||||||
@@ -83,7 +59,7 @@ pub fn Links() -> impl IntoView {
|
|||||||
children=move |(_, link)| {
|
children=move |(_, link)| {
|
||||||
let link = create_rw_signal(link);
|
let link = create_rw_signal(link);
|
||||||
view!{
|
view!{
|
||||||
<Link link edit links />
|
<Link link edit set_edit_link set_show_form links />
|
||||||
}
|
}
|
||||||
}/>
|
}/>
|
||||||
}
|
}
|
||||||
@@ -94,12 +70,22 @@ pub fn Links() -> impl IntoView {
|
|||||||
|
|
||||||
<div class="flex justify-end gap-5 mx-5">
|
<div class="flex justify-end gap-5 mx-5">
|
||||||
<button on:click=move |_| {set_edit.set(!edit.get())} class="bg-prim-light hover:bg-prim-lightest rounded-full px-5 py-3 text-second-dark hover:text-third transition-colors">"Modifier"</button>
|
<button on:click=move |_| {set_edit.set(!edit.get())} class="bg-prim-light hover:bg-prim-lightest rounded-full px-5 py-3 text-second-dark hover:text-third transition-colors">"Modifier"</button>
|
||||||
<button on:click=move |_| {set_show.set(true)} class="bg-prim-light hover:bg-prim-lightest rounded-full px-5 py-3 text-second-dark hover:text-third transition-colors">"Ajouter un lien +"</button>
|
<button on:click=move |_| {
|
||||||
|
set_edit_link.set(None);
|
||||||
|
set_show_form.set(true);
|
||||||
|
} class="bg-prim-light hover:bg-prim-lightest rounded-full px-5 py-3 text-second-dark hover:text-third transition-colors">"Ajouter un lien +"</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-center m-5">
|
{move || {
|
||||||
{form}
|
show_form.get().then(|| {
|
||||||
</div>
|
view! { <LinkFormModal
|
||||||
|
link=edit_link.get()
|
||||||
|
set_show=set_show_form
|
||||||
|
add_action=link_action
|
||||||
|
update_action=update_action
|
||||||
|
/> }
|
||||||
|
})
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,21 +105,30 @@ pub async fn change_position(link_id: String, direction: String) -> Result<(), S
|
|||||||
fn Link<T: 'static + Clone, S: 'static>(
|
fn Link<T: 'static + Clone, S: 'static>(
|
||||||
link: RwSignal<crate::models::Link>,
|
link: RwSignal<crate::models::Link>,
|
||||||
edit: ReadSignal<bool>,
|
edit: ReadSignal<bool>,
|
||||||
|
set_edit_link: WriteSignal<Option<crate::models::Link>>,
|
||||||
|
set_show_form: WriteSignal<bool>,
|
||||||
links: Resource<T, S>,
|
links: Resource<T, S>,
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
view! {
|
view! {
|
||||||
<li>
|
<li class="w-44 lg:w-fit">
|
||||||
<a class="bg-prim-light hover:bg-prim-lightest border-b hover:border-third border-transparent text-xl rounded-lg text-center hover:text-third transition-colors px-5 py-4 min-w-60 inline-block"
|
<a class="bg-prim-light container flex flex-col lg:gap-2 lg:flex-row w-full item-center hover:scale-110 transition hover:bg-prim-lightest border-b hover:border-third border-transparent rounded-lg text-center hover:text-third px-5 py-4"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href={move || link.with(|x| x.link.to_string())}>
|
href={move || link.with(|x| x.link.to_string())}>
|
||||||
{move || link.with(|x| x.name.to_string())}
|
<img src={move || link.with(|x| x.icon.to_string())}
|
||||||
|
alt={move || link.with(|x| x.name.to_string())}
|
||||||
|
class="size-32 lg:size-10 object-cover overflow-hidden" />
|
||||||
|
|
||||||
|
<span class="flex-1 text-2xl mt-2 lg:mt-0 flex justify-center items-center">{move || link.with(|x| x.name.to_string())}</span>
|
||||||
</a>
|
</a>
|
||||||
{move || {
|
{move || {
|
||||||
edit.get().then(|| {
|
edit.get().then(|| {
|
||||||
view! { <div class="bg-third border border-transparent rounded-b-lg flex justify-between">
|
view! { <div class="bg-third border border-transparent rounded-b-lg flex justify-between">
|
||||||
<MoveButton id=link.with(|x| x.id.to_string()) value="prev".to_string() label="<".to_string() links=links />
|
<MoveButton id=link.with(|x| x.id.to_string()) value="prev".to_string() label="<".to_string() links=links />
|
||||||
<button class="block px-2 py-1 flex-1 hover:bg-third-light"
|
<button class="block px-2 py-1 flex-1 hover:bg-third-light"
|
||||||
//on:click=move |_| {set_show.set(true)}
|
on:click=move |_| {
|
||||||
|
set_edit_link.set(Some(link.get()));
|
||||||
|
set_show_form.set(true);
|
||||||
|
}
|
||||||
>"Editer"</button>
|
>"Editer"</button>
|
||||||
<DeleteButton id=link.with(|x| x.id.to_string()) links=links />
|
<DeleteButton id=link.with(|x| x.id.to_string()) links=links />
|
||||||
<MoveButton id=link.with(|x| x.id.to_string()) value="next".to_string() label=">".to_string() links=links />
|
<MoveButton id=link.with(|x| x.id.to_string()) value="next".to_string() label=">".to_string() links=links />
|
||||||
@@ -198,3 +193,124 @@ fn DeleteButton<T: 'static + Clone, S: 'static>(
|
|||||||
</ActionForm>
|
</ActionForm>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[server(UpdateLinkAction, "/api")]
|
||||||
|
pub async fn update_link(id: String, name: String, link: String, icon: String) -> Result<(), ServerFnError> {
|
||||||
|
crate::models::Link::update(id, name, link, icon)
|
||||||
|
.await
|
||||||
|
.map(|_| ())
|
||||||
|
.map_err(|x| {
|
||||||
|
let err = format!("Error while updating a link: {x:?}");
|
||||||
|
tracing::error!("{err}");
|
||||||
|
ServerFnError::ServerError("Could not update the link, try again later".into())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn LinkFormModal(
|
||||||
|
link: Option<crate::models::Link>,
|
||||||
|
set_show: WriteSignal<bool>,
|
||||||
|
add_action: Action<LinkAction, Result<(), ServerFnError>>,
|
||||||
|
update_action: Action<UpdateLinkAction, Result<(), ServerFnError>>,
|
||||||
|
) -> impl IntoView {
|
||||||
|
let is_edit = link.is_some();
|
||||||
|
let (name, set_name) = create_signal(link.as_ref().map(|x| x.name.clone()).unwrap_or_default());
|
||||||
|
let (link_url, set_link_url) = create_signal(link.as_ref().map(|x| x.link.clone()).unwrap_or_default());
|
||||||
|
let (icon, set_icon) = create_signal(link.as_ref().map(|x| x.icon.clone()).unwrap_or_default());
|
||||||
|
let link_id = link.as_ref().map(|x| x.id.to_string());
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||||
|
<div class="bg-prim-light p-6 rounded-lg w-96">
|
||||||
|
<h2 class="pb-6 text-2xl text-center">
|
||||||
|
{if is_edit { "Édition du lien" } else { "Ajout d'un lien" }}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{if is_edit {
|
||||||
|
view! {
|
||||||
|
<ActionForm action=update_action>
|
||||||
|
<input name="id" type="hidden" value={link_id.unwrap_or_default()} />
|
||||||
|
<LinkFormFields
|
||||||
|
name=name
|
||||||
|
set_name=set_name
|
||||||
|
link_url=link_url
|
||||||
|
set_link_url=set_link_url
|
||||||
|
icon=icon
|
||||||
|
set_icon=set_icon
|
||||||
|
/>
|
||||||
|
<FormButtons set_show=set_show />
|
||||||
|
</ActionForm>
|
||||||
|
}.into_view()
|
||||||
|
} else {
|
||||||
|
view! {
|
||||||
|
<ActionForm action=add_action>
|
||||||
|
<LinkFormFields
|
||||||
|
name=name
|
||||||
|
set_name=set_name
|
||||||
|
link_url=link_url
|
||||||
|
set_link_url=set_link_url
|
||||||
|
icon=icon
|
||||||
|
set_icon=set_icon
|
||||||
|
/>
|
||||||
|
<FormButtons set_show=set_show />
|
||||||
|
</ActionForm>
|
||||||
|
}.into_view()
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn LinkFormFields(
|
||||||
|
name: ReadSignal<String>,
|
||||||
|
set_name: WriteSignal<String>,
|
||||||
|
link_url: ReadSignal<String>,
|
||||||
|
set_link_url: WriteSignal<String>,
|
||||||
|
icon: ReadSignal<String>,
|
||||||
|
set_icon: WriteSignal<String>,
|
||||||
|
) -> impl IntoView {
|
||||||
|
view! {
|
||||||
|
<div>
|
||||||
|
<label class="block mt-3 mb-1">"Nom"</label>
|
||||||
|
<input type="text"
|
||||||
|
name="name"
|
||||||
|
prop:value=move || name.get()
|
||||||
|
on:input=move |ev| set_name.set(event_target_value(&ev))
|
||||||
|
class="text-center bg-prim border border-transparent rounded-lg focus:border-third px-2 py-2 w-full" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block mt-3 mb-1">"Lien"</label>
|
||||||
|
<input type="url"
|
||||||
|
name="link"
|
||||||
|
prop:value=move || link_url.get()
|
||||||
|
on:input=move |ev| set_link_url.set(event_target_value(&ev))
|
||||||
|
class="text-center bg-prim border border-transparent rounded-lg focus:border-third px-2 py-2 w-full" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block mt-3 mb-1">"Icône"</label>
|
||||||
|
<input type="url"
|
||||||
|
name="icon"
|
||||||
|
placeholder="http..."
|
||||||
|
prop:value=move || icon.get()
|
||||||
|
on:input=move |ev| set_icon.set(event_target_value(&ev))
|
||||||
|
class="text-center bg-prim border border-transparent rounded-lg focus:border-third px-2 py-2 w-full" />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn FormButtons(set_show: WriteSignal<bool>) -> impl IntoView {
|
||||||
|
view! {
|
||||||
|
<div class="flex gap-2 mt-5">
|
||||||
|
<button type="button"
|
||||||
|
on:click=move |_| set_show.set(false)
|
||||||
|
class="bg-prim hover:bg-prim-light rounded-lg transition-colors px-2 py-1 flex-1">"Annuler"</button>
|
||||||
|
<button type="submit"
|
||||||
|
class="bg-third hover:bg-third-light rounded-lg transition-colors px-2 py-1 flex-1">"Valider"</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
mod data;
|
|
||||||
mod link;
|
mod link;
|
||||||
mod value;
|
|
||||||
mod shutters;
|
mod shutters;
|
||||||
|
|
||||||
pub use data::*;
|
|
||||||
pub use link::*;
|
pub use link::*;
|
||||||
pub use value::*;
|
|
||||||
pub use shutters::*;
|
pub use shutters::*;
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ pub fn Shutters() -> impl IntoView {
|
|||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Shutter(name: String, shade_id: i32) -> impl IntoView {
|
pub fn Shutter(name: String, shade_id: i32) -> impl IntoView {
|
||||||
let (response, set_response) = create_signal(None::<String>);
|
let (_response, set_response) = create_signal(None::<String>);
|
||||||
|
|
||||||
let move_up = create_action(move |_| async move {
|
let move_up = create_action(move |_| async move {
|
||||||
let client = Client::new();
|
let client = Client::new();
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
use leptos::*;
|
|
||||||
use leptos_meta::*;
|
|
||||||
use leptos_router::*;
|
|
||||||
|
|
||||||
use crate::models::OPTIONS;
|
|
||||||
|
|
||||||
#[server(ValueAction, "/api")]
|
|
||||||
pub async fn add_value(device: String, value: String) -> Result<(), ServerFnError> {
|
|
||||||
crate::models::Value::insert(device, value)
|
|
||||||
.await
|
|
||||||
.map(|_| ())
|
|
||||||
.map_err(|x| {
|
|
||||||
let err = format!("Error while posting a comment: {x:?}");
|
|
||||||
tracing::error!("{err}");
|
|
||||||
ServerFnError::ServerError("Could not post a comment, try again later".into())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[component]
|
|
||||||
pub fn FormValues() -> impl IntoView {
|
|
||||||
let value_action = create_server_action::<ValueAction>();
|
|
||||||
let result = value_action.version();
|
|
||||||
let reset_value = create_rw_signal("");
|
|
||||||
let _ = create_resource(
|
|
||||||
move || (result.get()),
|
|
||||||
move |_| async move {
|
|
||||||
reset_value.set("");
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
view! {
|
|
||||||
<Title text="Formulaire"/>
|
|
||||||
|
|
||||||
<div class="my-5 mx-auto w-72 p-6 bg-prim-light rounded-lg">
|
|
||||||
<h2 class="pb-6 text-2xl text-center">"Formulaire"</h2>
|
|
||||||
|
|
||||||
<ActionForm action=value_action attr:class="">
|
|
||||||
<div>
|
|
||||||
<label class="block mt-3 mb-1">Capteur</label>
|
|
||||||
<select name="device" class="text-center bg-prim border border-transparent rounded-lg focus:border-third px-2 py-2 w-60">
|
|
||||||
{OPTIONS.into_iter()
|
|
||||||
.map(|option| view! { <option value={option.value} class="px-2 py-1">{option.name}</option>})
|
|
||||||
.collect::<Vec<_>>()}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label class="block mt-3 mb-1">Valeur</label>
|
|
||||||
<input name="value"
|
|
||||||
type="number"
|
|
||||||
step="0.01"
|
|
||||||
prop:value=move || reset_value.get()
|
|
||||||
class="text-center bg-prim border border-transparent rounded-lg focus:border-third px-2 py-2 w-60" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button type="submit" class="bg-third hover:bg-third-light rounded-lg transition-colors px-2 py-1 w-60 my-5">Valider</button>
|
|
||||||
</div>
|
|
||||||
</ActionForm>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -21,10 +21,16 @@ pub async fn init_app(configuration_path: Option<&str>) {
|
|||||||
let leptos_options = conf.leptos_options;
|
let leptos_options = conf.leptos_options;
|
||||||
let serve_dir = tower_http::services::ServeDir::new(&leptos_options.site_root)
|
let serve_dir = tower_http::services::ServeDir::new(&leptos_options.site_root)
|
||||||
.append_index_html_on_directories(false);
|
.append_index_html_on_directories(false);
|
||||||
|
|
||||||
|
let cors = tower_http::cors::CorsLayer::new()
|
||||||
|
.allow_methods([axum::http::Method::GET, axum::http::Method::POST])
|
||||||
|
.allow_origin(tower_http::cors::Any);
|
||||||
|
|
||||||
logging::log!("listening on http://{}", &addr);
|
logging::log!("listening on http://{}", &addr);
|
||||||
let app = axum::Router::new()
|
let app = axum::Router::new()
|
||||||
.leptos_routes(&leptos_options, routes, || view! { <App/> })
|
.leptos_routes(&leptos_options, routes, || view! { <App/> })
|
||||||
.fallback_service(serve_dir)
|
.fallback_service(serve_dir)
|
||||||
|
.layer(cors)
|
||||||
.layer(
|
.layer(
|
||||||
tower_http::trace::TraceLayer::new_for_http()
|
tower_http::trace::TraceLayer::new_for_http()
|
||||||
.make_span_with(
|
.make_span_with(
|
||||||
|
|||||||
Reference in New Issue
Block a user