tangled
alpha
login
or
join now
vielle.dev
/
wol
1
fork
atom
[WIP] A simple wake-on-lan service
1
fork
atom
overview
issues
pulls
pipelines
add ping route, utils module, and misc html tweaks
vielle.dev
1 week ago
9d5a4967
4b3aee7c
verified
This commit was signed with the committer's
known signature
.
vielle.dev
SSH Key Fingerprint:
SHA256:EoUuRRBFQKUfYh74C568g83i9g4fVi5OTtOENMSfa+0=
+105
-36
8 changed files
expand all
collapse all
unified
split
src
config.rs
mac.rs
main.rs
server.rs
utils.rs
web
src
App.svelte
lib
Power.svelte
api.ts
+2
-2
src/config.rs
···
35
35
#[derive(Deserialize, Serialize, Debug, Clone)]
36
36
pub struct Target {
37
37
#[serde(
38
38
-
deserialize_with = "crate::mac::deserialize_mac",
39
39
-
serialize_with = "crate::mac::serialize_mac"
38
38
+
deserialize_with = "crate::utils::deserialize_mac",
39
39
+
serialize_with = "crate::utils::serialize_mac"
40
40
)]
41
41
pub mac: crate::mac::MacAddress,
42
42
pub ip: Option<String>,
-15
src/mac.rs
···
1
1
use std::{fmt::Display, net::UdpSocket, num::ParseIntError, str::FromStr};
2
2
3
3
-
use serde::{Deserialize, Deserializer, Serializer, de::Error};
4
3
use thiserror::Error;
5
4
6
5
#[derive(Clone, Debug)]
···
71
70
]))
72
71
}
73
72
}
74
74
-
75
75
-
pub fn deserialize_mac<'de, D>(de: D) -> Result<MacAddress, D::Error>
76
76
-
where
77
77
-
D: Deserializer<'de>,
78
78
-
{
79
79
-
MacAddress::from_str(&String::deserialize(de)?).map_err(Error::custom)
80
80
-
}
81
81
-
82
82
-
pub fn serialize_mac<S>(mac: &MacAddress, se: S) -> Result<S::Ok, S::Error>
83
83
-
where
84
84
-
S: Serializer,
85
85
-
{
86
86
-
se.serialize_str(&mac.to_string())
87
87
-
}
+1
src/main.rs
···
3
3
mod config;
4
4
mod mac;
5
5
mod server;
6
6
+
mod utils;
6
7
7
8
#[derive(thiserror::Error, Debug)]
8
9
enum Error {
+18
-3
src/server.rs
···
1
1
-
use std::{sync::Arc, time::Instant};
1
1
+
use std::{net::IpAddr, pin::Pin, sync::Arc, time::Instant};
2
2
3
3
use axum::{
4
4
Json, Router,
5
5
-
extract::{Request, State},
5
5
+
body::Body,
6
6
+
extract::{Query, Request, State},
7
7
+
handler::Handler,
6
8
http::Response,
7
9
middleware::{self},
8
10
routing::{get, post},
···
15
17
16
18
#[derive(Deserialize)]
17
19
struct WakeRequest {
18
18
-
#[serde(deserialize_with = "crate::mac::deserialize_mac")]
20
20
+
#[serde(deserialize_with = "crate::utils::deserialize_mac")]
19
21
mac: MacAddress,
20
22
}
21
23
···
30
32
})
31
33
}
32
34
35
35
+
#[derive(Deserialize)]
36
36
+
struct PingRequest {
37
37
+
#[serde(deserialize_with = "crate::utils::deserialize_ip")]
38
38
+
ip: IpAddr,
39
39
+
}
40
40
+
41
41
+
async fn ping(Query(req): Query<PingRequest>) -> Result<Json<bool>, Response<String>> {
42
42
+
println!("Pinging {}", req.ip);
43
43
+
44
44
+
Ok(Json(false))
45
45
+
}
46
46
+
33
47
async fn config(State(conf): State<Arc<Config>>) -> Json<Config> {
34
48
Json((*conf).clone())
35
49
}
···
48
62
Router::new()
49
63
.route("/wake", post(wake))
50
64
.route("/config", get(config))
65
65
+
.route("/ping", get(ping))
51
66
.with_state(Arc::new(conf))
52
67
.merge(dist::main())
53
68
.layer(middleware::from_fn(log))
+33
src/utils.rs
···
1
1
+
use std::{net::IpAddr, str::FromStr};
2
2
+
3
3
+
use serde::{Deserialize, Deserializer, Serializer, de::Error};
4
4
+
5
5
+
use crate::mac::MacAddress;
6
6
+
7
7
+
pub fn deserialize_ip<'de, D>(de: D) -> Result<IpAddr, D::Error>
8
8
+
where
9
9
+
D: Deserializer<'de>,
10
10
+
{
11
11
+
IpAddr::from_str(&String::deserialize(de)?).map_err(Error::custom)
12
12
+
}
13
13
+
14
14
+
pub fn serialize_ip<S>(ip: &IpAddr, se: S) -> Result<S::Ok, S::Error>
15
15
+
where
16
16
+
S: Serializer,
17
17
+
{
18
18
+
se.serialize_str(ip.to_string().as_str())
19
19
+
}
20
20
+
21
21
+
pub fn deserialize_mac<'de, D>(de: D) -> Result<MacAddress, D::Error>
22
22
+
where
23
23
+
D: Deserializer<'de>,
24
24
+
{
25
25
+
MacAddress::from_str(&String::deserialize(de)?).map_err(Error::custom)
26
26
+
}
27
27
+
28
28
+
pub fn serialize_mac<S>(mac: &MacAddress, se: S) -> Result<S::Ok, S::Error>
29
29
+
where
30
30
+
S: Serializer,
31
31
+
{
32
32
+
se.serialize_str(&mac.to_string())
33
33
+
}
+10
-4
web/src/App.svelte
···
19
19
</script>
20
20
21
21
<ThemeProvider />
22
22
-
<h1>Wake on Lan</h1>
23
23
-
{#each targets as [name, { mac, ip, url }]}
24
24
-
<Power {name} {mac} {ip} {url} />
25
25
-
{/each}
22
22
+
<main>
23
23
+
<h1>Wake on Lan</h1>
24
24
+
<ul>
25
25
+
{#each targets as [name, { mac, ip, url }]}
26
26
+
<li>
27
27
+
<Power {name} {mac} {ip} {url} />
28
28
+
</li>
29
29
+
{/each}
30
30
+
</ul>
31
31
+
</main>
+31
-11
web/src/lib/Power.svelte
···
12
12
ip: string | null;
13
13
url: string | null;
14
14
} = $props();
15
15
+
16
16
+
let online: boolean | undefined = $state(undefined);
17
17
+
const checkStatus: () => Promise<void> = async () => {
18
18
+
if (!ip) return;
19
19
+
const active = await Api.ping({ ip });
20
20
+
online = active;
21
21
+
// check every 30s if online, or every 5 seconds if offline
22
22
+
setTimeout(checkStatus, (active ? 30 : 5) * 1000);
23
23
+
};
24
24
+
checkStatus();
15
25
</script>
16
26
17
27
<section>
···
22
32
Power On
23
33
</button>
24
34
25
25
-
{#if url}
26
26
-
<a href={url}>{url}</a>
27
27
-
•
28
28
-
{/if}
29
29
-
{name}
30
30
-
•
31
31
-
{mac}
32
32
-
{#if ip}
33
33
-
•
34
34
-
<a href={`http://${ip}/`}>{ip}</a>
35
35
-
{/if}
35
35
+
<div class="name">
36
36
+
{name} <span class="status" data-status={online}></span>
37
37
+
</div>
38
38
+
<div class="mac">{mac}</div>
39
39
+
{#if ip}<div class="ip"><a href={`http://${ip}/`}>{ip}</a></div>{/if}
40
40
+
{#if url}<div class="url"><a href={url}>{url}</a></div>{/if}
36
41
</section>
42
42
+
43
43
+
<style>
44
44
+
.status {
45
45
+
display: inline-block;
46
46
+
width: 0.5em;
47
47
+
height: 0.5em;
48
48
+
49
49
+
&[data-status="true"] {
50
50
+
background: green;
51
51
+
}
52
52
+
&[data-status="false"] {
53
53
+
background: red;
54
54
+
}
55
55
+
}
56
56
+
</style>
+10
-1
web/src/lib/api.ts
···
94
94
wake: route({
95
95
route: "/wake",
96
96
method: "POST",
97
97
+
input: z.object({
98
98
+
mac: z.string(),
99
99
+
}),
97
100
output: z.boolean(),
101
101
+
}),
102
102
+
103
103
+
ping: route({
104
104
+
route: "/ping",
105
105
+
method: "GET",
98
106
input: z.object({
99
99
-
mac: z.string(),
107
107
+
ip: z.string(),
100
108
}),
109
109
+
output: z.boolean(),
101
110
}),
102
111
} as const;
103
112