✨ cli + user api
This commit is contained in:
parent
46cf2f4572
commit
b010027549
12 changed files with 504 additions and 25 deletions
204
Cargo.lock
generated
204
Cargo.lock
generated
|
@ -397,6 +397,29 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "axum-extra"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "45bf463831f5131b7d3c756525b305d40f1185b688565648a92e1392ca35713d"
|
||||||
|
dependencies = [
|
||||||
|
"axum",
|
||||||
|
"axum-core",
|
||||||
|
"bytes",
|
||||||
|
"futures-util",
|
||||||
|
"headers",
|
||||||
|
"http 1.3.1",
|
||||||
|
"http-body 1.0.1",
|
||||||
|
"http-body-util",
|
||||||
|
"mime",
|
||||||
|
"pin-project-lite",
|
||||||
|
"rustversion",
|
||||||
|
"serde",
|
||||||
|
"tower",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum-macros"
|
name = "axum-macros"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
@ -831,6 +854,31 @@ version = "0.8.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossterm"
|
||||||
|
version = "0.25.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"crossterm_winapi",
|
||||||
|
"libc",
|
||||||
|
"mio 0.8.11",
|
||||||
|
"parking_lot",
|
||||||
|
"signal-hook",
|
||||||
|
"signal-hook-mio",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossterm_winapi"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crypto-common"
|
name = "crypto-common"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
|
@ -968,6 +1016,27 @@ dependencies = [
|
||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "directories"
|
||||||
|
version = "6.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d"
|
||||||
|
dependencies = [
|
||||||
|
"dirs-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dirs-sys"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"option-ext",
|
||||||
|
"redox_users",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "displaydoc"
|
name = "displaydoc"
|
||||||
version = "0.2.5"
|
version = "0.2.5"
|
||||||
|
@ -994,6 +1063,12 @@ version = "0.15.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dyn-clone"
|
||||||
|
version = "1.0.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.15.0"
|
version = "1.15.0"
|
||||||
|
@ -1344,6 +1419,24 @@ dependencies = [
|
||||||
"slab",
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fuzzy-matcher"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94"
|
||||||
|
dependencies = [
|
||||||
|
"thread_local",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fxhash"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "generator"
|
name = "generator"
|
||||||
version = "0.7.5"
|
version = "0.7.5"
|
||||||
|
@ -1496,6 +1589,30 @@ dependencies = [
|
||||||
"hashbrown 0.15.2",
|
"hashbrown 0.15.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "headers"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.21.7",
|
||||||
|
"bytes",
|
||||||
|
"headers-core",
|
||||||
|
"http 1.3.1",
|
||||||
|
"httpdate",
|
||||||
|
"mime",
|
||||||
|
"sha1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "headers-core"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4"
|
||||||
|
dependencies = [
|
||||||
|
"http 1.3.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
@ -1967,6 +2084,23 @@ dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inquire"
|
||||||
|
version = "0.7.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.9.0",
|
||||||
|
"crossterm",
|
||||||
|
"dyn-clone",
|
||||||
|
"fuzzy-matcher",
|
||||||
|
"fxhash",
|
||||||
|
"newline-converter",
|
||||||
|
"once_cell",
|
||||||
|
"unicode-segmentation",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "intl-memoizer"
|
name = "intl-memoizer"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
|
@ -2232,6 +2366,18 @@ dependencies = [
|
||||||
"adler2",
|
"adler2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio"
|
||||||
|
version = "0.8.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||||
|
"windows-sys 0.48.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
|
@ -2279,6 +2425,15 @@ dependencies = [
|
||||||
"tempfile",
|
"tempfile",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "newline-converter"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "47b6b097ecb1cbfed438542d16e84fd7ad9b0c76c8a65b7f9039212a3d14dc7f"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-segmentation",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "7.1.3"
|
version = "7.1.3"
|
||||||
|
@ -2433,6 +2588,12 @@ dependencies = [
|
||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "option-ext"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "overload"
|
name = "overload"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
|
@ -2854,6 +3015,17 @@ dependencies = [
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_users"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.2.16",
|
||||||
|
"libredox",
|
||||||
|
"thiserror 2.0.12",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ref-cast"
|
name = "ref-cast"
|
||||||
version = "1.0.24"
|
version = "1.0.24"
|
||||||
|
@ -3581,11 +3753,14 @@ dependencies = [
|
||||||
"argh",
|
"argh",
|
||||||
"axum",
|
"axum",
|
||||||
"axum-client-ip",
|
"axum-client-ip",
|
||||||
|
"axum-extra",
|
||||||
"based",
|
"based",
|
||||||
"chrono",
|
"chrono",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
|
"directories",
|
||||||
"hex",
|
"hex",
|
||||||
"http2",
|
"http2",
|
||||||
|
"inquire",
|
||||||
"log",
|
"log",
|
||||||
"owl",
|
"owl",
|
||||||
"rand 0.9.1",
|
"rand 0.9.1",
|
||||||
|
@ -3607,6 +3782,27 @@ version = "1.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook"
|
||||||
|
version = "0.3.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"signal-hook-registry",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-mio"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"mio 0.8.11",
|
||||||
|
"signal-hook",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signal-hook-registry"
|
name = "signal-hook-registry"
|
||||||
version = "1.4.5"
|
version = "1.4.5"
|
||||||
|
@ -4116,7 +4312,7 @@ dependencies = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"bytes",
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
"mio",
|
"mio 1.0.3",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
|
@ -4461,6 +4657,12 @@ version = "1.12.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.1.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
version = "0.2.6"
|
version = "0.2.6"
|
||||||
|
|
11
Cargo.toml
11
Cargo.toml
|
@ -3,10 +3,18 @@ name = "sheepd"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "sheepd"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "sheepd"
|
name = "sheepd"
|
||||||
path = "src/sheepd.rs"
|
path = "src/sheepd.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "sheepctl"
|
||||||
|
path = "src/sheepctl.rs"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "herd"
|
name = "herd"
|
||||||
path = "src/herd.rs"
|
path = "src/herd.rs"
|
||||||
|
@ -38,3 +46,6 @@ sage = { git = "https://git.hydrar.de/jmarya/sage" }
|
||||||
dashmap = "6.1.0"
|
dashmap = "6.1.0"
|
||||||
ulid = { version = "1.2.1", features = ["serde"] }
|
ulid = { version = "1.2.1", features = ["serde"] }
|
||||||
chrono = "0.4.41"
|
chrono = "0.4.41"
|
||||||
|
directories = "6.0.0"
|
||||||
|
inquire = "0.7.5"
|
||||||
|
axum-extra = { version = "0.10.1", features = ["typed-header"] }
|
||||||
|
|
65
src/api.rs
65
src/api.rs
|
@ -3,6 +3,20 @@ use rumqttc::{AsyncClient, Event, EventLoop, MqttOptions, Packet, Transport};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tokio::time::sleep;
|
use tokio::time::sleep;
|
||||||
|
|
||||||
|
pub fn domain(host: &str) -> String {
|
||||||
|
if host.starts_with("http") {
|
||||||
|
return host.to_string();
|
||||||
|
} else {
|
||||||
|
format!("https://{host}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
|
pub struct LoginParam {
|
||||||
|
pub username: String,
|
||||||
|
pub password: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
/// Join Request
|
/// Join Request
|
||||||
pub struct JoinParams {
|
pub struct JoinParams {
|
||||||
|
@ -89,20 +103,57 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
/// Generic JSON API result
|
pub struct DeviceList {
|
||||||
pub struct Result {
|
pub devices: Vec<DeviceEntry>,
|
||||||
pub ok: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Result {
|
#[derive(Deserialize, Serialize)]
|
||||||
|
pub struct DeviceEntry {
|
||||||
|
pub id: String,
|
||||||
|
pub hostname: String,
|
||||||
|
pub online: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
|
/// Generic JSON API result
|
||||||
|
pub struct Result<T: Serialize> {
|
||||||
|
pub ok: Option<T>,
|
||||||
|
pub err: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Serialize> Result<T> {
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn OkVal(val: T) -> Self {
|
||||||
|
Self {
|
||||||
|
ok: Some(val),
|
||||||
|
err: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_result(self) -> std::result::Result<T, String> {
|
||||||
|
if let Some(ok) = self.ok {
|
||||||
|
Ok(ok)
|
||||||
|
} else {
|
||||||
|
Err(self.err.unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Result<i32> {
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn Ok() -> Self {
|
pub fn Ok() -> Self {
|
||||||
Self { ok: 1 }
|
Self {
|
||||||
|
ok: Some(1),
|
||||||
|
err: None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn Err() -> Self {
|
pub fn Err(msg: &str) -> Self {
|
||||||
Self { ok: 0 }
|
Self {
|
||||||
|
ok: None,
|
||||||
|
err: Some(msg.to_string()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,11 +11,11 @@ use std::{net::SocketAddr, path::PathBuf};
|
||||||
mod api;
|
mod api;
|
||||||
mod herd_core;
|
mod herd_core;
|
||||||
use crate::herd_core::mqtt::{handle_mqtt, listen_to_devices};
|
use crate::herd_core::mqtt::{handle_mqtt, listen_to_devices};
|
||||||
use herd_core::model::Machine;
|
|
||||||
use herd_core::{
|
use herd_core::{
|
||||||
config::Config,
|
config::Config,
|
||||||
route::{join_device, login_user},
|
route::{join_device, login_user},
|
||||||
};
|
};
|
||||||
|
use herd_core::{model::Machine, route::devices_list};
|
||||||
use sage::Identity;
|
use sage::Identity;
|
||||||
use tokio::sync::OnceCell;
|
use tokio::sync::OnceCell;
|
||||||
|
|
||||||
|
@ -59,7 +59,9 @@ async fn main() {
|
||||||
.layer(ClientIpSource::ConnectInfo.into_extension()); // Direct IP
|
.layer(ClientIpSource::ConnectInfo.into_extension()); // Direct IP
|
||||||
// .layer(ClientIpSource::XRealIp.into_extension()) // Proxy
|
// .layer(ClientIpSource::XRealIp.into_extension()) // Proxy
|
||||||
|
|
||||||
let user = Router::new().route("/login", post(login_user));
|
let user = Router::new()
|
||||||
|
.route("/login", post(login_user))
|
||||||
|
.route("/devices", get(devices_list));
|
||||||
|
|
||||||
let app = Router::new().merge(device).merge(user);
|
let app = Router::new().merge(device).merge(user);
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ use owl::{Serialize, get, query};
|
||||||
use rumqttc::AsyncClient;
|
use rumqttc::AsyncClient;
|
||||||
use sage::PersonaIdentity;
|
use sage::PersonaIdentity;
|
||||||
|
|
||||||
fn is_within_80_seconds(time: chrono::DateTime<chrono::Utc>) -> bool {
|
pub fn is_within_80_seconds(time: chrono::DateTime<chrono::Utc>) -> bool {
|
||||||
let now = chrono::Utc::now();
|
let now = chrono::Utc::now();
|
||||||
now.signed_duration_since(time).num_seconds() <= 80
|
now.signed_duration_since(time).num_seconds() <= 80
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,55 @@
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
use crate::api;
|
use crate::api;
|
||||||
use crate::api::JoinResponse;
|
use crate::api::JoinResponse;
|
||||||
|
use crate::api::LoginParam;
|
||||||
use crate::herd_core::model::Machine;
|
use crate::herd_core::model::Machine;
|
||||||
use axum::Json;
|
use axum::Json;
|
||||||
|
use axum::extract::FromRequestParts;
|
||||||
use axum::http::StatusCode;
|
use axum::http::StatusCode;
|
||||||
use axum_client_ip::ClientIp;
|
use axum_client_ip::ClientIp;
|
||||||
|
use axum_extra::TypedHeader;
|
||||||
|
use axum_extra::headers::Authorization;
|
||||||
|
use axum_extra::headers::authorization::Bearer;
|
||||||
use based::auth::Sessions;
|
use based::auth::Sessions;
|
||||||
use based::auth::User;
|
use based::auth::User;
|
||||||
|
use owl::prelude::Model;
|
||||||
|
use owl::query;
|
||||||
use owl::save;
|
use owl::save;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
use sheepd::DeviceEntry;
|
||||||
|
use sheepd::DeviceList;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
use super::mqtt::is_within_80_seconds;
|
||||||
pub struct LoginParam {
|
|
||||||
username: String,
|
pub async fn devices_list(
|
||||||
password: String,
|
session: TypedHeader<Authorization<Bearer>>,
|
||||||
|
) -> (StatusCode, Json<api::Result<DeviceList>>) {
|
||||||
|
let machines: Vec<Model<Machine>> = query!(|_| true);
|
||||||
|
|
||||||
|
let mut ret = vec![];
|
||||||
|
|
||||||
|
for mac in machines {
|
||||||
|
let id = mac.read().id.to_string().replace("-", "");
|
||||||
|
let online_state = crate::ONLINE
|
||||||
|
.get()
|
||||||
|
.unwrap()
|
||||||
|
.get(&id)
|
||||||
|
.map(|x| is_within_80_seconds(*x.deref()))
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
ret.push(DeviceEntry {
|
||||||
|
id: id,
|
||||||
|
hostname: mac.read().hostname.clone(),
|
||||||
|
online: online_state,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
(
|
||||||
|
StatusCode::OK,
|
||||||
|
Json(api::Result::OkVal(DeviceList { devices: ret })),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn login_user(Json(payload): Json<LoginParam>) -> (StatusCode, Json<serde_json::Value>) {
|
pub async fn login_user(Json(payload): Json<LoginParam>) -> (StatusCode, Json<serde_json::Value>) {
|
||||||
|
@ -21,9 +57,15 @@ pub async fn login_user(Json(payload): Json<LoginParam>) -> (StatusCode, Json<se
|
||||||
let u = User::find(&payload.username).await.unwrap();
|
let u = User::find(&payload.username).await.unwrap();
|
||||||
if u.read().verify_pw(&payload.password) {
|
if u.read().verify_pw(&payload.password) {
|
||||||
let ses = u.read().session().await;
|
let ses = u.read().session().await;
|
||||||
(StatusCode::OK, Json(json!({"token": ses.read().token})))
|
(
|
||||||
|
StatusCode::OK,
|
||||||
|
Json(json!(api::Result::OkVal(ses.read().token.as_str()))),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
(StatusCode::FORBIDDEN, Json(json!({"error": "invalid"})))
|
(
|
||||||
|
StatusCode::FORBIDDEN,
|
||||||
|
Json(json!(api::Result::Err("invalid"))),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
2
src/lib.rs
Normal file
2
src/lib.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod api;
|
||||||
|
pub use api::*;
|
18
src/sheepctl.rs
Normal file
18
src/sheepctl.rs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
use sheepctl_core::{
|
||||||
|
args::{DeviceCommands, SheepctlArgs, SheepctlCommand},
|
||||||
|
cmd::{list_devices, login},
|
||||||
|
};
|
||||||
|
|
||||||
|
mod api;
|
||||||
|
mod sheepctl_core;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args: SheepctlArgs = argh::from_env();
|
||||||
|
|
||||||
|
match args.command {
|
||||||
|
SheepctlCommand::Device(device_command) => match device_command.command {
|
||||||
|
DeviceCommands::List(list_devices_command) => list_devices(list_devices_command),
|
||||||
|
},
|
||||||
|
SheepctlCommand::Login(login_command) => login(login_command),
|
||||||
|
}
|
||||||
|
}
|
51
src/sheepctl_core/args.rs
Normal file
51
src/sheepctl_core/args.rs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
use argh::FromArgs;
|
||||||
|
|
||||||
|
#[derive(FromArgs)]
|
||||||
|
/// Control your herd
|
||||||
|
pub struct SheepctlArgs {
|
||||||
|
#[argh(subcommand)]
|
||||||
|
pub command: SheepctlCommand,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromArgs, PartialEq, Debug)]
|
||||||
|
#[argh(subcommand)]
|
||||||
|
pub enum SheepctlCommand {
|
||||||
|
Login(LoginCommand),
|
||||||
|
Device(DeviceCommand),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromArgs, PartialEq, Debug)]
|
||||||
|
/// Login to a homeserver
|
||||||
|
#[argh(subcommand, name = "login")]
|
||||||
|
pub struct LoginCommand {
|
||||||
|
#[argh(positional)]
|
||||||
|
/// homeserver
|
||||||
|
pub home: String,
|
||||||
|
|
||||||
|
#[argh(positional)]
|
||||||
|
/// username
|
||||||
|
pub username: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromArgs, PartialEq, Debug)]
|
||||||
|
#[argh(subcommand, name = "device")]
|
||||||
|
/// Commands for devices
|
||||||
|
pub struct DeviceCommand {
|
||||||
|
#[argh(subcommand)]
|
||||||
|
pub command: DeviceCommands,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromArgs, PartialEq, Debug)]
|
||||||
|
#[argh(subcommand)]
|
||||||
|
pub enum DeviceCommands {
|
||||||
|
List(ListDevicesCommand),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromArgs, PartialEq, Debug)]
|
||||||
|
/// List devices
|
||||||
|
#[argh(subcommand, name = "ls")]
|
||||||
|
pub struct ListDevicesCommand {
|
||||||
|
#[argh(switch)]
|
||||||
|
/// only show online devices
|
||||||
|
pub online: bool,
|
||||||
|
}
|
105
src/sheepctl_core/cmd.rs
Normal file
105
src/sheepctl_core/cmd.rs
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use owl::{Deserialize, Serialize};
|
||||||
|
use sheepd::{DeviceList, LoginParam};
|
||||||
|
|
||||||
|
use super::args::{ListDevicesCommand, LoginCommand};
|
||||||
|
use crate::api::domain;
|
||||||
|
|
||||||
|
pub fn api_call<T: Serialize + for<'a> Deserialize<'a>, I: Serialize>(
|
||||||
|
server: &str,
|
||||||
|
path: &str,
|
||||||
|
data: I,
|
||||||
|
) -> crate::api::Result<T> {
|
||||||
|
let url = format!("{}/{path}", domain(server));
|
||||||
|
let mut res = ureq::post(url).send_json(data).unwrap();
|
||||||
|
let res: crate::api::Result<T> = res.body_mut().read_json().unwrap();
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn api_call_get<T: Serialize + for<'a> Deserialize<'a>>(
|
||||||
|
server: &str,
|
||||||
|
path: &str,
|
||||||
|
token: &str,
|
||||||
|
) -> crate::api::Result<T> {
|
||||||
|
let url = format!("{}/{path}", domain(server));
|
||||||
|
let mut res = ureq::get(url)
|
||||||
|
.header("Authorization", format!("Bearer {token}"))
|
||||||
|
.force_send_body()
|
||||||
|
.send_empty()
|
||||||
|
.unwrap();
|
||||||
|
let res: crate::api::Result<T> = res.body_mut().read_json().unwrap();
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_config_path() -> Option<PathBuf> {
|
||||||
|
directories::ProjectDirs::from("de", "Hydrar", "sheepd")
|
||||||
|
.map(|proj_dirs| proj_dirs.config_dir().join("config.toml"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct CtlConfig {
|
||||||
|
pub home: String,
|
||||||
|
pub token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CtlConfig {
|
||||||
|
pub fn load() -> Option<Self> {
|
||||||
|
let c = std::fs::read_to_string(get_config_path()?).ok()?;
|
||||||
|
toml::from_str(&c).ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save(&self) {
|
||||||
|
let s = toml::to_string(self).unwrap();
|
||||||
|
let config = get_config_path().unwrap();
|
||||||
|
let _ = std::fs::create_dir_all(config.parent().unwrap());
|
||||||
|
std::fs::write(get_config_path().unwrap(), s).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list_devices(arg: ListDevicesCommand) {
|
||||||
|
let conf = CtlConfig::load().unwrap();
|
||||||
|
|
||||||
|
if let Ok(devices) = api_call_get::<DeviceList>(&conf.home, "devices", &conf.token).as_result()
|
||||||
|
{
|
||||||
|
println!("Hosts:");
|
||||||
|
for d in devices.devices {
|
||||||
|
println!(
|
||||||
|
"- {} [{}]{}",
|
||||||
|
d.hostname,
|
||||||
|
d.id,
|
||||||
|
if d.online { " [ONLINE]" } else { "" }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn login(arg: LoginCommand) {
|
||||||
|
if let Some(conf) = CtlConfig::load() {
|
||||||
|
println!("You are already logged in to {}", conf.home);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let password = inquire::prompt_secret("Password: ").unwrap();
|
||||||
|
|
||||||
|
// login request
|
||||||
|
if let Result::Ok(token) = api_call::<String, _>(
|
||||||
|
&arg.home,
|
||||||
|
"login",
|
||||||
|
LoginParam {
|
||||||
|
username: arg.username,
|
||||||
|
password: password,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.as_result()
|
||||||
|
{
|
||||||
|
// save token to config
|
||||||
|
CtlConfig {
|
||||||
|
home: arg.home,
|
||||||
|
token,
|
||||||
|
}
|
||||||
|
.save();
|
||||||
|
} else {
|
||||||
|
println!("Login failed");
|
||||||
|
}
|
||||||
|
}
|
2
src/sheepctl_core/mod.rs
Normal file
2
src/sheepctl_core/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod args;
|
||||||
|
pub mod cmd;
|
|
@ -8,14 +8,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::args::JoinCommand;
|
use super::args::JoinCommand;
|
||||||
|
use crate::api::domain;
|
||||||
fn domain(host: &str) -> String {
|
|
||||||
if host.starts_with("http") {
|
|
||||||
return host.to_string();
|
|
||||||
} else {
|
|
||||||
format!("https://{host}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Join a herd as client
|
/// Join a herd as client
|
||||||
pub fn join(conf: JoinCommand) {
|
pub fn join(conf: JoinCommand) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue