This commit is contained in:
Stefan Schwarz 2020-08-15 09:10:47 +02:00
parent 7100621f5e
commit 09a3f096c7
8 changed files with 154 additions and 115 deletions

1
Cargo.lock generated
View file

@ -1054,6 +1054,7 @@ dependencies = [
name = "macnickenson"
version = "0.1.0"
dependencies = [
"anyhow",
"argh",
"askama",
"askama_tide",

View file

@ -5,9 +5,10 @@ authors = ["Stefan Schwarz <stefan@f2o.io>"]
edition = "2018"
[dependencies]
anyhow = "1.0"
argh = "0.1.3"
askama = { version = "0.10", features = ["with-tide"]}
askama_tide = "0.10"
askama = { version = "0.10", features = ["with-tide"]}
async-std = { version = "1.6", features = ["attributes"] }
chrono = "0.4"
sqlx = { version = "0.4.0-beta.1", features = ["mysql", "chrono", "macros"] }

100
src/db.rs
View file

@ -1,11 +1,99 @@
use chrono::{DateTime, Utc};
use sqlx::database::HasArguments;
use std::net::IpAddr;
type QueryAs<'q, T> =
sqlx::query::QueryAs<'q, sqlx::MySql, T, <sqlx::MySql as HasArguments<'q>>::Arguments>;
#[derive(sqlx::FromRow, Debug)]
pub struct Entry {
id: i32,
macaddr: String,
nickname: String,
descr: String,
privacy: i8,
created: DateTime<Utc>,
pub id: i32,
pub macaddr: String,
pub nickname: String,
pub descr: String,
pub privacy: PrivacyLevel,
pub created: DateTime<Utc>,
pub present: bool,
}
#[derive(sqlx::Type, Debug, Clone, Copy)]
#[repr(i8)]
pub enum PrivacyLevel {
ShowUserAndDevice = 0,
ShowUser = 1,
ShowAnonymous = 2,
HideUser = 3,
DontLog = 4,
}
impl<'q> Entry {
pub fn all() -> QueryAs<'q, Self> {
sqlx::query_as("SELECT * FROM mac_to_nick")
}
pub fn for_user(user: &'q str) -> QueryAs<'q, Self> {
sqlx::query_as(
"
SELECT
mtn.*,
IF(al.iplong != NULL, TRUE, FALSE) present
FROM
mac_to_nick mtn,
alive_hosts al
WHERE
al.macaddr = mtn.macaddr
AND nickname = ?
GROUP BY
mtn.macaddr
",
)
.bind(user)
}
}
impl PrivacyLevel {
pub fn as_u8(&self) -> u8 {
*self as u8
}
pub fn selected(&self, level: &PrivacyLevel) -> &'static str {
if *self as u8 == *level as u8 {
"selected"
} else {
""
}
}
}
#[derive(sqlx::FromRow, Debug)]
pub struct AliveDevice {
pub macaddr: String,
pub iplong: i32,
}
impl<'q> AliveDevice {
pub fn unassinged() -> QueryAs<'q, Self> {
sqlx::query_as(
"
SELECT DISTINCT
al.macaddr macaddr,
al.iplong iplong
FROM
alive_hosts al,
mac_to_nick mtn
WHERE
mtn.nickname IS NULL
AND al.erfda > NOW() - INTERVAL 24 DAY
",
)
}
pub fn ip(&self) -> IpAddr {
IpAddr::from([
(self.iplong >> 24 & 0xff) as u8,
(self.iplong >> 16 & 0xff) as u8,
(self.iplong >> 8 & 0xff) as u8,
(self.iplong & 0xff) as u8,
])
}
}

View file

@ -11,6 +11,13 @@ struct Config {
/// listen address
#[argh(option, default = "\"[::1]:8080\".to_string()")]
listen: String,
/// database dsn
#[argh(
option,
default = "\"mysql://administration:foosinn123@127.0.0.1/administration\".to_string()"
)]
dsn: String,
}
#[derive(Clone)]
@ -24,12 +31,7 @@ pub type Request = tide::Request<State>;
async fn main() -> Result<(), io::Error> {
let config: Config = argh::from_env();
let dsn = "mysql://administration:foosinn123@127.0.0.1/administration";
let pool = MySqlPool::connect(dsn)
.await
.map_err(|err| io::Error::new(io::ErrorKind::Other, format!("{:?}", err)))?;
let mut conn = pool
.acquire()
let pool = MySqlPool::connect(&config.dsn)
.await
.map_err(|err| io::Error::new(io::ErrorKind::Other, format!("{:?}", err)))?;
@ -38,11 +40,5 @@ async fn main() -> Result<(), io::Error> {
app.at("/healthz").get(routes::healthz);
app.at("/static").serve_dir("static/")?;
let entries: Vec<db::Entry> = sqlx::query_as("select * from mac_to_nick")
.fetch_all(&mut conn)
.await
.map_err(|err| io::Error::new(io::ErrorKind::Other, format!("{:?}", err)))?;
entries.into_iter().for_each(|e| println!("{:?}", e));
app.listen(config.listen).await
}

18
src/routes.rs Normal file
View file

@ -0,0 +1,18 @@
use crate::db;
use crate::templates;
pub async fn healthz(_request: crate::Request) -> tide::Result {
Ok("ok".into())
}
pub async fn index(request: crate::Request) -> tide::Result {
let my = db::Entry::for_user("foosinn")
.fetch_all(&request.state().pool)
.await
.map_err(|err| dbg!(err))?;
let unassinged = db::AliveDevice::unassinged()
.fetch_all(&request.state().pool)
.await
.map_err(|err| dbg!(err))?;
Ok(templates::IndexTemplate::new(my, unassinged).into())
}

View file

@ -1,9 +0,0 @@
use crate::templates;
pub async fn healthz(_request: crate::Request) -> tide::Result {
Ok("ok".into())
}
pub async fn index(request: crate::Request) -> tide::Result {
Ok(templates::IndexTemplate::new().into())
}

View file

@ -3,26 +3,13 @@ use askama::Template;
#[derive(Template, Default)]
#[template(path = "index.html")]
pub struct IndexTemplate<'a> {
devices: Vec<Device<'a>>,
pub struct IndexTemplate {
my: Vec<db::Entry>,
unassinged: Vec<db::AliveDevice>,
}
pub struct Device<'a> {
pub macaddr: &'a str,
pub nickname: &'a str,
pub descr: &'a str,
pub privacy: PrivacyLevel,
}
#[repr(u8)]
pub enum PrivacyLevel {
Public = 1,
Private = 2,
Internal = 3,
}
impl<'a> IndexTemplate<'a> {
pub fn new() -> Self {
Self::default()
impl IndexTemplate {
pub fn new(my: Vec<db::Entry>, unassinged: Vec<db::AliveDevice>) -> Self {
Self { my, unassinged }
}
}

View file

@ -18,15 +18,28 @@
<div class="p-2">
<h2 class="underline">Your Devices:</h2>
<div class="grid grid-cols-3 grid-gap-1">
<div>macaddr: "00:18:e7:34:11:1c"</div>
{% for device in my %}
<div class="grid grid-cols-3 grid-gap-1 p-1">
<div class="font-mono">{{ device.macaddr }}</div>
<div>
<input class="focus:shadow-outline border border-gray-300 px-2"
value="Thinkpad T30 Wifi"/>
value="{{ device.descr }}"/>
<select class="focus:shadow-outline border border-gray-300 px-2">
<option value="1">Public</option>
<option value="2">Private</option>
<option value="3">Internal</option>
<option value="0"
{{ device.privacy.selected(crate::db::PrivacyLevel::ShowUserAndDevice) }}
>Show User and Device</option>
<option value="1"
{{ device.privacy.selected(crate::db::PrivacyLevel::ShowUser) }}
>Show User</option>
<option value="2"
{{ device.privacy.selected(crate::db::PrivacyLevel::ShowAnonymous) }}
>Show Anonymous</option>
<option value="3"
{{ device.privacy.selected(crate::db::PrivacyLevel::HideUser) }}
>Hide User</option>
<option value="4"
{{ device.privacy.selected(crate::db::PrivacyLevel::DontLog) }}
>Dont Log</option>
</select>
</div>
<div>
@ -34,23 +47,14 @@
<button class="rounded bg-gray-300 font-bold px-2 bg-red-500">Delete</button>
</div>
</div>
<div class="grid grid-cols-3 grid-gap-1">
<div>macaddr: "90:78:41:bd:10:23"</div>
<div>nickname: "Neptun"</div>
<div>descr: "Neptun"</div>
</div>
<div class="grid grid-cols-3 grid-gap-1">
<div>macaddr: "10:0b:a9:ad:9f:30"</div>
<div>nickname: "Wu"</div>
<div>descr: "x220"</div>
</div>
{% endfor %}
</div>
<div class="p-2">
<h2 class="underline">Unregistred Devices:</h2>
<div class="grid grid-cols-3 grid-gap-1">
<div>macaddr: "f0:d5:bf:cc:a2:f9"</div>
{% for device in unassinged %}
<div class="grid grid-cols-4 grid-gap-1 p-1">
<div class="font-mono">{{ device.macaddr }}</div>
<div class="font-mono">{{ device.ip() }}</div>
<div>
<input class="focus:shadow-outline border border-gray-300 px-2"
placeholder="awesome new device"/>
@ -59,54 +63,7 @@
<button class="rounded bg-gray-300 font-bold px-2 bg-blue-300">Register</button>
</div>
</div>
<div class="grid grid-cols-3 grid-gap-1">
<div>macaddr: "6a:f4:a2:c4:e2:2e"</div>
<div>nickname: "Mondschauer93"</div>
<div>descr: "Smartphone"</div>
</div>
<div class="grid grid-cols-3 grid-gap-1">
<div>macaddr: "f8:ff:c2:20:b6:0d"</div>
<div>nickname: "idefix"</div>
<div>descr: "MacBook"</div>
</div>
<div class="grid grid-cols-3 grid-gap-1">
<div>macaddr: "30:57:14:db:34:d3"</div>
<div>nickname: "idefix"</div>
<div>descr: "IPhone"</div>
</div>
<div class="grid grid-cols-3 grid-gap-1">
<div>macaddr: "98:01:a7:b6:48:5b"</div>
<div>nickname: "idefix"</div>
<div>descr: "UbuntuBook"</div>
</div>
<div class="grid grid-cols-3 grid-gap-1">
<div>macaddr: "f4:db:e3:3f:7e:18"</div>
<div>nickname: "raphii"</div>
<div>descr: "iPhone 11"</div>
</div>
<div class="grid grid-cols-3 grid-gap-1">
<div>macaddr: "70:48:0f:98:de:23"</div>
<div>nickname: "raphii"</div>
<div>descr: "iPad Pro"</div>
</div>
<div class="grid grid-cols-3 grid-gap-1">
<div>macaddr: "e0:cc:f8:6a:d1:d4"</div>
<div>nickname: "stoneos"</div>
<div>descr: "xiaomi mi10"</div>
</div>
<div class="grid grid-cols-3 grid-gap-1">
<div>macaddr: "74:e5:f9:86:98:8b"</div>
<div>nickname: "ptflea"</div>
<div>descr: "computer"</div>
</div>
{% endfor %}
</div>
<div class="shadow-lg p-2">
footer