mirror of
https://git.alemi.dev/dashboard.git
synced 2024-11-22 23:44:55 +01:00
chore: removed leftovers, version bump
This commit is contained in:
parent
ba59b62b2b
commit
0a0ee7077d
5 changed files with 51 additions and 252 deletions
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "dashboard"
|
name = "dashboard"
|
||||||
version = "0.1.2"
|
version = "0.1.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
// pub mod source;
|
pub mod source;
|
||||||
pub mod store;
|
pub mod store;
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::{RwLock, Mutex};
|
use std::sync::{RwLock, Mutex};
|
||||||
use std::num::ParseFloatError;
|
use std::num::ParseFloatError;
|
||||||
use chrono::{DateTime, Utc};
|
|
||||||
use eframe::egui::plot::{Values, Value};
|
|
||||||
use eframe::epaint::Color32;
|
use eframe::epaint::Color32;
|
||||||
|
|
||||||
use self::store::SQLiteDataStore;
|
use self::store::SQLiteDataStore;
|
||||||
|
use self::source::{Panel, Source};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum FetchError {
|
pub enum FetchError {
|
||||||
|
@ -75,62 +73,3 @@ impl ApplicationState {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Panel {
|
|
||||||
pub(crate) id: i32,
|
|
||||||
pub name: String,
|
|
||||||
pub view_scroll: bool,
|
|
||||||
pub view_size: i32,
|
|
||||||
pub timeserie: bool,
|
|
||||||
pub(crate) width: i32,
|
|
||||||
pub(crate) height: i32,
|
|
||||||
pub limit: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Panel {
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Source {
|
|
||||||
pub(crate) id: i32,
|
|
||||||
pub name: String,
|
|
||||||
pub url: String,
|
|
||||||
pub interval: i32,
|
|
||||||
pub color: Color32,
|
|
||||||
pub visible: bool,
|
|
||||||
pub(crate) last_fetch: RwLock<DateTime<Utc>>,
|
|
||||||
pub query_x: String,
|
|
||||||
// pub(crate) compiled_query_x: Arc<Mutex<jq_rs::JqProgram>>,
|
|
||||||
pub query_y: String,
|
|
||||||
// pub(crate) compiled_query_y: Arc<Mutex<jq_rs::JqProgram>>,
|
|
||||||
pub(crate) panel_id: i32,
|
|
||||||
pub(crate) data: RwLock<Vec<Value>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Source {
|
|
||||||
pub fn valid(&self) -> bool {
|
|
||||||
let last_fetch = self.last_fetch.read().expect("LastFetch RwLock poisoned");
|
|
||||||
return (Utc::now() - *last_fetch).num_seconds() < self.interval as i64;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn values(&self) -> Values {
|
|
||||||
Values::from_values(self.data.read().expect("Values RwLock poisoned").clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn values_filter(&self, min_x:f64) -> Values {
|
|
||||||
let mut values = self.data.read().expect("Values RwLock poisoned").clone();
|
|
||||||
values.retain(|x| x.x > min_x);
|
|
||||||
Values::from_values(values)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fetch(url:&str, query_x:&str, query_y:&str) -> Result<Value, FetchError> {
|
|
||||||
let res = ureq::get(url).call()?.into_json()?;
|
|
||||||
let x : f64;
|
|
||||||
if query_x.len() > 0 {
|
|
||||||
x = jql::walker(&res, query_x)?.as_f64().unwrap(); // TODO what if it's given to us as a string?
|
|
||||||
} else {
|
|
||||||
x = Utc::now().timestamp() as f64;
|
|
||||||
}
|
|
||||||
let y = jql::walker(&res, query_y)?.as_f64().unwrap();
|
|
||||||
return Ok( Value { x, y } );
|
|
||||||
}
|
|
|
@ -1,201 +1,61 @@
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::RwLock;
|
||||||
use rand::Rng;
|
|
||||||
use std::io::{Write, Read};
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize, de::{DeserializeOwned}};
|
use eframe::egui::plot::{Values, Value};
|
||||||
use eframe::egui::{plot::Value, Context};
|
use eframe::epaint::Color32;
|
||||||
|
use super::FetchError;
|
||||||
|
|
||||||
pub fn native_save(name: &str, data:String) -> std::io::Result<()> {
|
pub struct Panel {
|
||||||
let mut file = std::fs::File::create(name)?;
|
pub(crate) id: i32,
|
||||||
file.write_all(data.as_bytes())?;
|
pub name: String,
|
||||||
return Ok(());
|
pub view_scroll: bool,
|
||||||
}
|
pub view_size: i32,
|
||||||
pub struct DataSource {
|
pub timeserie: bool,
|
||||||
data : Arc<Mutex<Vec<Value>>>,
|
pub(crate) width: i32,
|
||||||
|
pub(crate) height: i32,
|
||||||
|
pub limit: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
pub struct Source {
|
||||||
struct SerializableValue {
|
pub(crate) id: i32,
|
||||||
x : f64,
|
pub name: String,
|
||||||
y : f64,
|
pub url: String,
|
||||||
|
pub interval: i32,
|
||||||
|
pub color: Color32,
|
||||||
|
pub visible: bool,
|
||||||
|
pub(crate) last_fetch: RwLock<DateTime<Utc>>,
|
||||||
|
pub query_x: String,
|
||||||
|
// pub(crate) compiled_query_x: Arc<Mutex<jq_rs::JqProgram>>,
|
||||||
|
pub query_y: String,
|
||||||
|
// pub(crate) compiled_query_y: Arc<Mutex<jq_rs::JqProgram>>,
|
||||||
|
pub(crate) panel_id: i32,
|
||||||
|
pub(crate) data: RwLock<Vec<Value>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DataSource {
|
impl Source {
|
||||||
pub fn new() -> Self {
|
pub fn valid(&self) -> bool {
|
||||||
Self{ data: Arc::new(Mutex::new(Vec::new())) }
|
let last_fetch = self.last_fetch.read().expect("LastFetch RwLock poisoned");
|
||||||
|
return (Utc::now() - *last_fetch).num_seconds() < self.interval as i64;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn view(&self) -> Vec<Value> { // TODO handle errors
|
pub fn values(&self) -> Values {
|
||||||
return self.data.lock().unwrap().clone();
|
Values::from_values(self.data.read().expect("Values RwLock poisoned").clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn serialize(&self) -> String {
|
pub fn values_filter(&self, min_x:f64) -> Values {
|
||||||
let mut out : Vec<SerializableValue> = Vec::new();
|
let mut values = self.data.read().expect("Values RwLock poisoned").clone();
|
||||||
for value in self.view() {
|
values.retain(|x| x.x > min_x);
|
||||||
out.push(SerializableValue { x: value.x, y: value.y });
|
Values::from_values(values)
|
||||||
}
|
|
||||||
return serde_json::to_string(&out).unwrap();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait PlotValue {
|
pub fn fetch(url:&str, query_x:&str, query_y:&str) -> Result<Value, FetchError> {
|
||||||
fn as_value(&self) -> Value;
|
let res = ureq::get(url).call()?.into_json()?;
|
||||||
|
let x : f64;
|
||||||
|
if query_x.len() > 0 {
|
||||||
|
x = jql::walker(&res, query_x)?.as_f64().unwrap(); // TODO what if it's given to us as a string?
|
||||||
|
} else {
|
||||||
|
x = Utc::now().timestamp() as f64;
|
||||||
}
|
}
|
||||||
|
let y = jql::walker(&res, query_y)?.as_f64().unwrap();
|
||||||
pub trait Data {
|
return Ok( Value { x, y } );
|
||||||
fn load_remote(&mut self, url:&str, ctx:Context);
|
|
||||||
fn load_local(&mut self, file:&str, ctx:Context);
|
|
||||||
|
|
||||||
fn read(&mut self, file:&str, storage:Arc<Mutex<Vec<Value>>>, ctx:Context) -> std::io::Result<()> {
|
|
||||||
let mut file = std::fs::File::open(file)?;
|
|
||||||
let mut contents = String::new();
|
|
||||||
file.read_to_string(&mut contents)?;
|
|
||||||
let data : Vec<SerializableValue> = serde_json::from_str(contents.as_str())?;
|
|
||||||
for v in data {
|
|
||||||
storage.lock().unwrap().push(Value { x: v.x, y: v.y });
|
|
||||||
}
|
|
||||||
ctx.request_repaint();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fetch<T>(&mut self, base:&str, endpoint:&str, storage:Arc<Mutex<Vec<Value>>>, ctx:Context)
|
|
||||||
where T : DeserializeOwned + PlotValue {
|
|
||||||
let request = ehttp::Request::get(format!("{}/{}", base, endpoint));
|
|
||||||
ehttp::fetch(request, move |result: ehttp::Result<ehttp::Response>| {
|
|
||||||
let data : T = serde_json::from_slice(result.unwrap().bytes.as_slice()).unwrap();
|
|
||||||
storage.lock().unwrap().push(data.as_value());
|
|
||||||
ctx.request_repaint();
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TpsData {
|
|
||||||
pub ds: DataSource,
|
|
||||||
load_interval : i64,
|
|
||||||
last_load : DateTime<Utc>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
struct TpsResponseData {
|
|
||||||
tps: f64
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PlotValue for TpsResponseData {
|
|
||||||
fn as_value(&self) -> Value {
|
|
||||||
Value { x: Utc::now().timestamp() as f64, y: self.tps }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TpsData {
|
|
||||||
pub fn new(load_interval:i64) -> Self {
|
|
||||||
Self { ds: DataSource::new() , last_load: Utc::now(), load_interval }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Data for TpsData{
|
|
||||||
fn load_remote(&mut self, url:&str, ctx:Context) {
|
|
||||||
if (Utc::now() - self.last_load).num_seconds() < self.load_interval { return; }
|
|
||||||
self.last_load = Utc::now();
|
|
||||||
self.fetch::<TpsResponseData>(url, "tps", self.ds.data.clone(), ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_local(&mut self, file:&str, ctx:Context) {
|
|
||||||
self.read(file, self.ds.data.clone(), ctx).unwrap_or_else(|_err| println!("Could not load {}", file));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ChatData {
|
|
||||||
pub ds : DataSource,
|
|
||||||
load_interval : i64,
|
|
||||||
last_load : DateTime<Utc>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
struct ChatResponseData {
|
|
||||||
volume: f64
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PlotValue for ChatResponseData {
|
|
||||||
fn as_value(&self) -> Value {
|
|
||||||
Value { x:Utc::now().timestamp() as f64, y: self.volume }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ChatData {
|
|
||||||
pub fn new(load_interval:i64) -> Self {
|
|
||||||
Self { ds: DataSource::new() , last_load: Utc::now(), load_interval }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Data for ChatData{
|
|
||||||
fn load_remote(&mut self, url:&str, ctx:Context) {
|
|
||||||
if (Utc::now() - self.last_load).num_seconds() < self.load_interval { return; }
|
|
||||||
self.last_load = Utc::now();
|
|
||||||
self.fetch::<ChatResponseData>(url, "chat_activity", self.ds.data.clone(), ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_local(&mut self, file:&str, ctx:Context) {
|
|
||||||
self.read(file, self.ds.data.clone(), ctx).unwrap_or_else(|_err| println!("Could not load {}", file));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PlayerCountData {
|
|
||||||
pub ds : DataSource,
|
|
||||||
load_interval : i64,
|
|
||||||
last_load : DateTime<Utc>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
struct PlayerCountResponseData {
|
|
||||||
count: i32
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PlotValue for PlayerCountResponseData {
|
|
||||||
fn as_value(&self) -> Value {
|
|
||||||
Value { x:Utc::now().timestamp() as f64, y: self.count as f64 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PlayerCountData {
|
|
||||||
pub fn new(load_interval:i64) -> Self {
|
|
||||||
Self { ds: DataSource::new() , last_load: Utc::now(), load_interval }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Data for PlayerCountData{
|
|
||||||
fn load_remote(&mut self, url:&str, ctx:Context) {
|
|
||||||
if (Utc::now() - self.last_load).num_seconds() < self.load_interval { return; }
|
|
||||||
self.last_load = Utc::now();
|
|
||||||
self.fetch::<PlayerCountResponseData>(url, "player_count", self.ds.data.clone(), ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_local(&mut self, file:&str, ctx:Context) {
|
|
||||||
self.read(file, self.ds.data.clone(), ctx).unwrap_or_else(|_err| println!("Could not load {}", file));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct RandomData {
|
|
||||||
pub ds : DataSource,
|
|
||||||
load_interval : i64,
|
|
||||||
last_load : DateTime<Utc>,
|
|
||||||
rng: rand::rngs::ThreadRng,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RandomData {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn new(load_interval:i64) -> Self {
|
|
||||||
Self { ds: DataSource::new() , last_load: Utc::now(), load_interval, rng : rand::thread_rng() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Data for RandomData{
|
|
||||||
fn load_remote(&mut self, _url:&str, ctx:Context) {
|
|
||||||
if (Utc::now() - self.last_load).num_seconds() < self.load_interval { return; }
|
|
||||||
self.last_load = Utc::now();
|
|
||||||
self.ds.data.lock().unwrap().push(Value {x:Utc::now().timestamp() as f64, y:self.rng.gen()});
|
|
||||||
ctx.request_repaint();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_local(&mut self, _file:&str, _ctx:Context) {}
|
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::app::{data::{Panel, Source}, util::repack_color};
|
use crate::app::{data::source::{Panel, Source}, util::repack_color};
|
||||||
use chrono::{TimeZone, Utc};
|
use chrono::{TimeZone, Utc};
|
||||||
use eframe::egui::{Color32, plot::Value};
|
use eframe::egui::{Color32, plot::Value};
|
||||||
use rusqlite::{params, Connection};
|
use rusqlite::{params, Connection};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use eframe::egui::Context;
|
use eframe::egui::Context;
|
||||||
use crate::app::data::{fetch, ApplicationState};
|
use crate::app::data::{ApplicationState, source::fetch};
|
||||||
|
|
||||||
pub fn native_save(state:Arc<ApplicationState>) {
|
pub fn native_save(state:Arc<ApplicationState>) {
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
|
|
Loading…
Reference in a new issue