✨ 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",
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "axum-macros"
|
||||
version = "0.5.0"
|
||||
|
@ -831,6 +854,31 @@ version = "0.8.21"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
|
@ -968,6 +1016,27 @@ dependencies = [
|
|||
"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]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
|
@ -994,6 +1063,12 @@ version = "0.15.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
|
@ -1344,6 +1419,24 @@ dependencies = [
|
|||
"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]]
|
||||
name = "generator"
|
||||
version = "0.7.5"
|
||||
|
@ -1496,6 +1589,30 @@ dependencies = [
|
|||
"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]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
|
@ -1967,6 +2084,23 @@ dependencies = [
|
|||
"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]]
|
||||
name = "intl-memoizer"
|
||||
version = "0.5.2"
|
||||
|
@ -2232,6 +2366,18 @@ dependencies = [
|
|||
"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]]
|
||||
name = "mio"
|
||||
version = "1.0.3"
|
||||
|
@ -2279,6 +2425,15 @@ dependencies = [
|
|||
"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]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
|
@ -2433,6 +2588,12 @@ dependencies = [
|
|||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
|
@ -2854,6 +3015,17 @@ dependencies = [
|
|||
"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]]
|
||||
name = "ref-cast"
|
||||
version = "1.0.24"
|
||||
|
@ -3581,11 +3753,14 @@ dependencies = [
|
|||
"argh",
|
||||
"axum",
|
||||
"axum-client-ip",
|
||||
"axum-extra",
|
||||
"based",
|
||||
"chrono",
|
||||
"dashmap",
|
||||
"directories",
|
||||
"hex",
|
||||
"http2",
|
||||
"inquire",
|
||||
"log",
|
||||
"owl",
|
||||
"rand 0.9.1",
|
||||
|
@ -3607,6 +3782,27 @@ version = "1.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.5"
|
||||
|
@ -4116,7 +4312,7 @@ dependencies = [
|
|||
"backtrace",
|
||||
"bytes",
|
||||
"libc",
|
||||
"mio",
|
||||
"mio 1.0.3",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
|
@ -4461,6 +4657,12 @@ version = "1.12.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.6"
|
||||
|
|
11
Cargo.toml
11
Cargo.toml
|
@ -3,10 +3,18 @@ name = "sheepd"
|
|||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
name = "sheepd"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "sheepd"
|
||||
path = "src/sheepd.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "sheepctl"
|
||||
path = "src/sheepctl.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "herd"
|
||||
path = "src/herd.rs"
|
||||
|
@ -38,3 +46,6 @@ sage = { git = "https://git.hydrar.de/jmarya/sage" }
|
|||
dashmap = "6.1.0"
|
||||
ulid = { version = "1.2.1", features = ["serde"] }
|
||||
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 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)]
|
||||
/// Join Request
|
||||
pub struct JoinParams {
|
||||
|
@ -89,20 +103,57 @@ where
|
|||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
/// Generic JSON API result
|
||||
pub struct Result {
|
||||
pub ok: u32,
|
||||
pub struct DeviceList {
|
||||
pub devices: Vec<DeviceEntry>,
|
||||
}
|
||||
|
||||
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)]
|
||||
pub fn Ok() -> Self {
|
||||
Self { ok: 1 }
|
||||
Self {
|
||||
ok: Some(1),
|
||||
err: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn Err() -> Self {
|
||||
Self { ok: 0 }
|
||||
pub fn Err(msg: &str) -> Self {
|
||||
Self {
|
||||
ok: None,
|
||||
err: Some(msg.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,11 +11,11 @@ use std::{net::SocketAddr, path::PathBuf};
|
|||
mod api;
|
||||
mod herd_core;
|
||||
use crate::herd_core::mqtt::{handle_mqtt, listen_to_devices};
|
||||
use herd_core::model::Machine;
|
||||
use herd_core::{
|
||||
config::Config,
|
||||
route::{join_device, login_user},
|
||||
};
|
||||
use herd_core::{model::Machine, route::devices_list};
|
||||
use sage::Identity;
|
||||
use tokio::sync::OnceCell;
|
||||
|
||||
|
@ -59,7 +59,9 @@ async fn main() {
|
|||
.layer(ClientIpSource::ConnectInfo.into_extension()); // Direct IP
|
||||
// .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);
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ use owl::{Serialize, get, query};
|
|||
use rumqttc::AsyncClient;
|
||||
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();
|
||||
now.signed_duration_since(time).num_seconds() <= 80
|
||||
}
|
||||
|
|
|
@ -1,19 +1,55 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use crate::api;
|
||||
use crate::api::JoinResponse;
|
||||
use crate::api::LoginParam;
|
||||
use crate::herd_core::model::Machine;
|
||||
use axum::Json;
|
||||
use axum::extract::FromRequestParts;
|
||||
use axum::http::StatusCode;
|
||||
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::User;
|
||||
use owl::prelude::Model;
|
||||
use owl::query;
|
||||
use owl::save;
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
use sheepd::DeviceEntry;
|
||||
use sheepd::DeviceList;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct LoginParam {
|
||||
username: String,
|
||||
password: String,
|
||||
use super::mqtt::is_within_80_seconds;
|
||||
|
||||
pub async fn devices_list(
|
||||
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>) {
|
||||
|
@ -21,9 +57,15 @@ pub async fn login_user(Json(payload): Json<LoginParam>) -> (StatusCode, Json<se
|
|||
let u = User::find(&payload.username).await.unwrap();
|
||||
if u.read().verify_pw(&payload.password) {
|
||||
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 {
|
||||
(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;
|
||||
|
||||
fn domain(host: &str) -> String {
|
||||
if host.starts_with("http") {
|
||||
return host.to_string();
|
||||
} else {
|
||||
format!("https://{host}")
|
||||
}
|
||||
}
|
||||
use crate::api::domain;
|
||||
|
||||
/// Join a herd as client
|
||||
pub fn join(conf: JoinCommand) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue