mirror of
https://git.alemi.dev/dashboard.git
synced 2024-11-14 03:49:19 +01:00
style: rustfmt whole project
This commit is contained in:
parent
c53abb2eed
commit
8df10ec5ed
9 changed files with 184 additions and 137 deletions
|
@ -1,12 +1,11 @@
|
||||||
pub mod source;
|
pub mod source;
|
||||||
pub mod store;
|
pub mod store;
|
||||||
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::sync::{RwLock, Mutex};
|
|
||||||
use std::num::ParseFloatError;
|
|
||||||
use eframe::epaint::Color32;
|
|
||||||
use self::store::SQLiteDataStore;
|
|
||||||
use self::source::{Panel, Source};
|
use self::source::{Panel, Source};
|
||||||
|
use self::store::SQLiteDataStore;
|
||||||
|
use std::num::ParseFloatError;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::{Mutex, RwLock};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum FetchError {
|
pub enum FetchError {
|
||||||
|
@ -18,20 +17,31 @@ pub enum FetchError {
|
||||||
ParseFloatError(ParseFloatError),
|
ParseFloatError(ParseFloatError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From::<ureq::Error> for FetchError {
|
impl From<ureq::Error> for FetchError {
|
||||||
fn from(e: ureq::Error) -> Self { FetchError::UreqError(e) }
|
fn from(e: ureq::Error) -> Self {
|
||||||
|
FetchError::UreqError(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl From::<std::io::Error> for FetchError {
|
impl From<std::io::Error> for FetchError {
|
||||||
fn from(e: std::io::Error) -> Self { FetchError::IoError(e) }
|
fn from(e: std::io::Error) -> Self {
|
||||||
|
FetchError::IoError(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl From::<String> for FetchError { // TODO wtf? why does JQL error as a String?
|
impl From<String> for FetchError {
|
||||||
fn from(e: String) -> Self { FetchError::JQLError(e) }
|
// TODO wtf? why does JQL error as a String?
|
||||||
|
fn from(e: String) -> Self {
|
||||||
|
FetchError::JQLError(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl From::<ParseFloatError> for FetchError {
|
impl From<ParseFloatError> for FetchError {
|
||||||
fn from(e: ParseFloatError) -> Self { FetchError::ParseFloatError(e) }
|
fn from(e: ParseFloatError) -> Self {
|
||||||
|
FetchError::ParseFloatError(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl From::<rusqlite::Error> for FetchError {
|
impl From<rusqlite::Error> for FetchError {
|
||||||
fn from(e: rusqlite::Error) -> Self { FetchError::RusqliteError(e) }
|
fn from(e: rusqlite::Error) -> Self {
|
||||||
|
FetchError::RusqliteError(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ApplicationState {
|
pub struct ApplicationState {
|
||||||
|
@ -45,13 +55,13 @@ pub struct ApplicationState {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ApplicationState {
|
impl ApplicationState {
|
||||||
pub fn new(path:PathBuf) -> Result<ApplicationState, FetchError> {
|
pub fn new(path: PathBuf) -> Result<ApplicationState, FetchError> {
|
||||||
let storage = SQLiteDataStore::new(path.clone())?;
|
let storage = SQLiteDataStore::new(path.clone())?;
|
||||||
|
|
||||||
let panels = storage.load_panels()?;
|
let panels = storage.load_panels()?;
|
||||||
let sources = storage.load_sources()?;
|
let sources = storage.load_sources()?;
|
||||||
|
|
||||||
return Ok(ApplicationState{
|
return Ok(ApplicationState {
|
||||||
run: true,
|
run: true,
|
||||||
file_size: RwLock::new(std::fs::metadata(path.clone())?.len()),
|
file_size: RwLock::new(std::fs::metadata(path.clone())?.len()),
|
||||||
file_path: path,
|
file_path: path,
|
||||||
|
@ -63,22 +73,42 @@ impl ApplicationState {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_panel(&self, panel: &Panel) -> Result<(), FetchError> {
|
pub fn add_panel(&self, panel: &Panel) -> Result<(), FetchError> {
|
||||||
let verified_panel = self.storage.lock().expect("Storage Mutex poisoned")
|
let verified_panel = self
|
||||||
|
.storage
|
||||||
|
.lock()
|
||||||
|
.expect("Storage Mutex poisoned")
|
||||||
.new_panel(
|
.new_panel(
|
||||||
panel.name.as_str(),
|
panel.name.as_str(),
|
||||||
panel.view_size,
|
panel.view_size,
|
||||||
panel.width,
|
panel.width,
|
||||||
panel.height,
|
panel.height,
|
||||||
self.panels.read().expect("Panels RwLock poisoned").len() as i32 // todo can this be made more compact and without acquisition?
|
self.panels.read().expect("Panels RwLock poisoned").len() as i32, // todo can this be made more compact and without acquisition?
|
||||||
)?; // TODO make values customizable and useful
|
)?; // TODO make values customizable and useful
|
||||||
self.panels.write().expect("Panels RwLock poisoned").push(verified_panel);
|
self.panels
|
||||||
|
.write()
|
||||||
|
.expect("Panels RwLock poisoned")
|
||||||
|
.push(verified_panel);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_source(&self, source: &Source) -> Result<(), FetchError> {
|
pub fn add_source(&self, source: &Source) -> Result<(), FetchError> {
|
||||||
let verified_source = self.storage.lock().expect("Storage Mutex poisoned")
|
let verified_source = self
|
||||||
.new_source(source.panel_id, source.name.as_str(), source.url.as_str(), source.query_x.as_str(), source.query_y.as_str(), source.color, source.visible)?;
|
.storage
|
||||||
self.sources.write().expect("Sources RwLock poisoned").push(verified_source);
|
.lock()
|
||||||
|
.expect("Storage Mutex poisoned")
|
||||||
|
.new_source(
|
||||||
|
source.panel_id,
|
||||||
|
source.name.as_str(),
|
||||||
|
source.url.as_str(),
|
||||||
|
source.query_x.as_str(),
|
||||||
|
source.query_y.as_str(),
|
||||||
|
source.color,
|
||||||
|
source.visible,
|
||||||
|
)?;
|
||||||
|
self.sources
|
||||||
|
.write()
|
||||||
|
.expect("Sources RwLock poisoned")
|
||||||
|
.push(verified_source);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use std::sync::RwLock;
|
|
||||||
use chrono::{DateTime, Utc};
|
|
||||||
use eframe::egui::plot::{Values, Value};
|
|
||||||
use eframe::epaint::Color32;
|
|
||||||
use super::FetchError;
|
use super::FetchError;
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use eframe::egui::plot::{Value, Values};
|
||||||
|
use eframe::epaint::Color32;
|
||||||
|
use std::sync::RwLock;
|
||||||
|
|
||||||
pub struct Panel {
|
pub struct Panel {
|
||||||
pub(crate) id: i32,
|
pub(crate) id: i32,
|
||||||
|
@ -59,7 +59,7 @@ impl Default for Source {
|
||||||
query_x: "".to_string(),
|
query_x: "".to_string(),
|
||||||
query_y: "".to_string(),
|
query_y: "".to_string(),
|
||||||
panel_id: -1,
|
panel_id: -1,
|
||||||
data: RwLock::new(Vec::new())
|
data: RwLock::new(Vec::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,21 +74,25 @@ impl Source {
|
||||||
Values::from_values(self.data.read().expect("Values RwLock poisoned").clone())
|
Values::from_values(self.data.read().expect("Values RwLock poisoned").clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn values_filter(&self, min_x:f64) -> Values {
|
pub fn values_filter(&self, min_x: f64) -> Values {
|
||||||
let mut values = self.data.read().expect("Values RwLock poisoned").clone();
|
let mut values = self.data.read().expect("Values RwLock poisoned").clone();
|
||||||
values.retain(|x| x.x > min_x);
|
values.retain(|x| x.x > min_x);
|
||||||
Values::from_values(values)
|
Values::from_values(values)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch(url:&str, query_x:&str, query_y:&str) -> Result<Value, FetchError> {
|
pub fn fetch(url: &str, query_x: &str, query_y: &str) -> Result<Value, FetchError> {
|
||||||
let res = ureq::get(url).call()?.into_json()?;
|
let res = ureq::get(url).call()?.into_json()?;
|
||||||
let x : f64;
|
let x: f64;
|
||||||
if query_x.len() > 0 {
|
if query_x.len() > 0 {
|
||||||
x = jql::walker(&res, query_x)?.as_f64().ok_or(FetchError::JQLError("X query is null".to_string()))?; // TODO what if it's given to us as a string?
|
x = jql::walker(&res, query_x)?
|
||||||
|
.as_f64()
|
||||||
|
.ok_or(FetchError::JQLError("X query is null".to_string()))?; // TODO what if it's given to us as a string?
|
||||||
} else {
|
} else {
|
||||||
x = Utc::now().timestamp() as f64;
|
x = Utc::now().timestamp() as f64;
|
||||||
}
|
}
|
||||||
let y = jql::walker(&res, query_y)?.as_f64().ok_or(FetchError::JQLError("Y query is null".to_string()))?;
|
let y = jql::walker(&res, query_y)?
|
||||||
return Ok( Value { x, y } );
|
.as_f64()
|
||||||
|
.ok_or(FetchError::JQLError("Y query is null".to_string()))?;
|
||||||
|
return Ok(Value { x, y });
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
use crate::app::{data::source::{Panel, Source}, util::repack_color};
|
use crate::app::util::unpack_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::{plot::Value, Color32};
|
||||||
use rusqlite::{params, Connection};
|
use rusqlite::{params, Connection};
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
use crate::app::util::unpack_color;
|
|
||||||
|
|
||||||
pub trait DataStorage {
|
pub trait DataStorage {
|
||||||
fn add_panel(&self, name: &str);
|
fn add_panel(&self, name: &str);
|
||||||
|
@ -60,8 +63,6 @@ impl SQLiteDataStore {
|
||||||
Ok(SQLiteDataStore { conn })
|
Ok(SQLiteDataStore { conn })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
pub fn load_values(&self, source_id: i32) -> rusqlite::Result<Vec<Value>> {
|
pub fn load_values(&self, source_id: i32) -> rusqlite::Result<Vec<Value>> {
|
||||||
let mut values: Vec<Value> = Vec::new();
|
let mut values: Vec<Value> = Vec::new();
|
||||||
let mut statement = self
|
let mut statement = self
|
||||||
|
@ -90,13 +91,9 @@ impl SQLiteDataStore {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
pub fn load_sources(&self) -> rusqlite::Result<Vec<Source>> {
|
pub fn load_sources(&self) -> rusqlite::Result<Vec<Source>> {
|
||||||
let mut sources: Vec<Source> = Vec::new();
|
let mut sources: Vec<Source> = Vec::new();
|
||||||
let mut statement = self
|
let mut statement = self.conn.prepare("SELECT * FROM sources")?;
|
||||||
.conn
|
|
||||||
.prepare("SELECT * FROM sources")?;
|
|
||||||
let sources_iter = statement.query_map([], |row| {
|
let sources_iter = statement.query_map([], |row| {
|
||||||
Ok(Source {
|
Ok(Source {
|
||||||
id: row.get(0)?,
|
id: row.get(0)?,
|
||||||
|
@ -134,7 +131,11 @@ impl SQLiteDataStore {
|
||||||
color: Color32,
|
color: Color32,
|
||||||
visible: bool,
|
visible: bool,
|
||||||
) -> rusqlite::Result<Source> {
|
) -> rusqlite::Result<Source> {
|
||||||
let color_u32 : Option<u32> = if color == Color32::TRANSPARENT { None } else { Some(repack_color(color)) };
|
let color_u32: Option<u32> = if color == Color32::TRANSPARENT {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(repack_color(color))
|
||||||
|
};
|
||||||
self.conn.execute(
|
self.conn.execute(
|
||||||
"INSERT INTO sources(name, url, interval, query_x, query_y, panel_id, color, visible) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
"INSERT INTO sources(name, url, interval, query_x, query_y, panel_id, color, visible) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||||
params![name, url, 60i32, query_x, query_y, panel_id, color_u32, visible],
|
params![name, url, 60i32, query_x, query_y, panel_id, color_u32, visible],
|
||||||
|
@ -177,7 +178,11 @@ impl SQLiteDataStore {
|
||||||
color: Color32,
|
color: Color32,
|
||||||
visible: bool,
|
visible: bool,
|
||||||
) -> rusqlite::Result<usize> {
|
) -> rusqlite::Result<usize> {
|
||||||
let color_u32 : Option<u32> = if color == Color32::TRANSPARENT { None } else { Some(repack_color(color)) };
|
let color_u32: Option<u32> = if color == Color32::TRANSPARENT {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(repack_color(color))
|
||||||
|
};
|
||||||
self.conn.execute(
|
self.conn.execute(
|
||||||
"UPDATE sources SET name = ?, url = ?, interval = ?, query_x = ?, query_y = ?, panel_id = ?, color = ?, visible = ? WHERE id = ?",
|
"UPDATE sources SET name = ?, url = ?, interval = ?, query_x = ?, query_y = ?, panel_id = ?, color = ?, visible = ? WHERE id = ?",
|
||||||
params![name, url, interval, query_x, query_y, panel_id, color_u32, visible, source_id],
|
params![name, url, interval, query_x, query_y, panel_id, color_u32, visible, source_id],
|
||||||
|
@ -190,7 +195,9 @@ impl SQLiteDataStore {
|
||||||
|
|
||||||
pub fn load_panels(&self) -> rusqlite::Result<Vec<Panel>> {
|
pub fn load_panels(&self) -> rusqlite::Result<Vec<Panel>> {
|
||||||
let mut panels: Vec<Panel> = Vec::new();
|
let mut panels: Vec<Panel> = Vec::new();
|
||||||
let mut statement = self.conn.prepare("SELECT * FROM panels ORDER BY position")?;
|
let mut statement = self
|
||||||
|
.conn
|
||||||
|
.prepare("SELECT * FROM panels ORDER BY position")?;
|
||||||
let panels_iter = statement.query_map([], |row| {
|
let panels_iter = statement.query_map([], |row| {
|
||||||
Ok(Panel {
|
Ok(Panel {
|
||||||
id: row.get(0)?,
|
id: row.get(0)?,
|
||||||
|
@ -214,7 +221,14 @@ impl SQLiteDataStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
// jank! TODO make it not jank!
|
// jank! TODO make it not jank!
|
||||||
pub fn new_panel(&self, name: &str, view_size:i32, width: i32, height: i32, position: i32) -> rusqlite::Result<Panel> {
|
pub fn new_panel(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
view_size: i32,
|
||||||
|
width: i32,
|
||||||
|
height: i32,
|
||||||
|
position: i32,
|
||||||
|
) -> rusqlite::Result<Panel> {
|
||||||
self.conn.execute(
|
self.conn.execute(
|
||||||
"INSERT INTO panels (name, view_scroll, view_size, timeserie, width, height, limit_view, position) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
"INSERT INTO panels (name, view_scroll, view_size, timeserie, width, height, limit_view, position) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||||
params![name, true, view_size, true, width, height, false, position]
|
params![name, true, view_size, true, width, height, false, position]
|
||||||
|
@ -260,7 +274,4 @@ impl SQLiteDataStore {
|
||||||
// pub fn delete_panel(&self, id:i32) -> rusqlite::Result<usize> {
|
// pub fn delete_panel(&self, id:i32) -> rusqlite::Result<usize> {
|
||||||
// self.conn.execute("DELETE FROM panels WHERE id = ?", params![id])
|
// self.conn.execute("DELETE FROM panels WHERE id = ?", params![id])
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
pub mod source;
|
|
||||||
pub mod panel;
|
pub mod panel;
|
||||||
|
pub mod source;
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
use chrono::{Utc, Local};
|
use chrono::{Local, Utc};
|
||||||
use eframe::egui::{Ui, Layout, Align, plot::{Plot, Legend, Corner, Line, GridMark}, Slider, DragValue};
|
use eframe::egui::{
|
||||||
|
plot::{Corner, GridMark, Legend, Line, Plot},
|
||||||
|
DragValue, Layout, Slider, Ui,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::app::{data::source::{Panel, Source}, util::timestamp_to_str};
|
use crate::app::{
|
||||||
|
data::source::{Panel, Source},
|
||||||
|
util::timestamp_to_str,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn panel_edit_inline_ui(ui: &mut Ui, panel: &mut Panel) {
|
pub fn panel_edit_inline_ui(ui: &mut Ui, panel: &mut Panel) {
|
||||||
eframe::egui::TextEdit::singleline(&mut panel.name)
|
eframe::egui::TextEdit::singleline(&mut panel.name)
|
||||||
|
@ -27,16 +33,13 @@ pub fn panel_title_ui(ui: &mut Ui, panel: &mut Panel) {
|
||||||
ui.separator();
|
ui.separator();
|
||||||
ui.checkbox(&mut panel.timeserie, "timeserie");
|
ui.checkbox(&mut panel.timeserie, "timeserie");
|
||||||
ui.separator();
|
ui.separator();
|
||||||
ui.add(
|
ui.add(Slider::new(&mut panel.height, 0..=500).text("height"));
|
||||||
Slider::new(&mut panel.height, 0..=500).text("height"),
|
|
||||||
);
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn panel_body_ui(ui: &mut Ui, panel: &mut Panel, sources: &Vec<Source>) {
|
pub fn panel_body_ui(ui: &mut Ui, panel: &mut Panel, sources: &Vec<Source>) {
|
||||||
let mut p = Plot::new(format!("plot-{}", panel.name))
|
let mut p = Plot::new(format!("plot-{}", panel.name))
|
||||||
.height(panel.height as f32)
|
.height(panel.height as f32)
|
||||||
|
@ -44,26 +47,20 @@ pub fn panel_body_ui(ui: &mut Ui, panel: &mut Panel, sources: &Vec<Source>) {
|
||||||
.legend(Legend::default().position(Corner::LeftTop));
|
.legend(Legend::default().position(Corner::LeftTop));
|
||||||
|
|
||||||
if panel.view_scroll {
|
if panel.view_scroll {
|
||||||
p = p
|
p = p.include_x(Utc::now().timestamp() as f64);
|
||||||
.include_x(Utc::now().timestamp() as f64);
|
|
||||||
if panel.limit {
|
if panel.limit {
|
||||||
p = p
|
p = p
|
||||||
.set_margin_fraction(eframe::emath::Vec2{x:0.0, y:0.1})
|
.set_margin_fraction(eframe::emath::Vec2 { x: 0.0, y: 0.1 })
|
||||||
.include_x((Utc::now().timestamp() + ( panel.view_size as i64 * 3)) as f64);
|
.include_x((Utc::now().timestamp() + (panel.view_size as i64 * 3)) as f64);
|
||||||
}
|
}
|
||||||
if panel.limit {
|
if panel.limit {
|
||||||
p = p.include_x(
|
p = p.include_x((Utc::now().timestamp() - (panel.view_size as i64 * 60)) as f64);
|
||||||
(Utc::now().timestamp() - (panel.view_size as i64 * 60))
|
|
||||||
as f64,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if panel.timeserie {
|
if panel.timeserie {
|
||||||
p = p
|
p = p
|
||||||
.x_axis_formatter(|x, _range| {
|
.x_axis_formatter(|x, _range| timestamp_to_str(x as i64, true, false))
|
||||||
timestamp_to_str(x as i64, true, false)
|
|
||||||
})
|
|
||||||
.label_formatter(|name, value| {
|
.label_formatter(|name, value| {
|
||||||
if !name.is_empty() {
|
if !name.is_empty() {
|
||||||
return format!(
|
return format!(
|
||||||
|
@ -111,8 +108,7 @@ pub fn panel_body_ui(ui: &mut Ui, panel: &mut Panel, sources: &Vec<Source>) {
|
||||||
if source.visible && source.panel_id == panel.id {
|
if source.visible && source.panel_id == panel.id {
|
||||||
let line = if panel.limit {
|
let line = if panel.limit {
|
||||||
Line::new(source.values_filter(
|
Line::new(source.values_filter(
|
||||||
(Utc::now().timestamp()
|
(Utc::now().timestamp() - (panel.view_size as i64 * 60)) as f64,
|
||||||
- (panel.view_size as i64 * 60)) as f64,
|
|
||||||
))
|
))
|
||||||
.name(source.name.as_str())
|
.name(source.name.as_str())
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
use eframe::egui::Ui;
|
use eframe::egui::Ui;
|
||||||
|
|
||||||
use crate::app::data::source::{Source, Panel};
|
use crate::app::data::source::{Panel, Source};
|
||||||
|
|
||||||
pub fn source_edit_inline_ui(ui: &mut Ui, source: &mut Source, panels: &Vec<Panel>) {
|
pub fn source_edit_inline_ui(ui: &mut Ui, source: &mut Source, panels: &Vec<Panel>) {
|
||||||
eframe::egui::TextEdit::singleline(&mut source.name)
|
eframe::egui::TextEdit::singleline(&mut source.name)
|
||||||
|
@ -25,11 +25,7 @@ pub fn source_edit_inline_ui(ui: &mut Ui, source: &mut Source, panels: &Vec<Pane
|
||||||
.width(70.0)
|
.width(70.0)
|
||||||
.show_ui(ui, |ui| {
|
.show_ui(ui, |ui| {
|
||||||
for p in panels {
|
for p in panels {
|
||||||
ui.selectable_value(
|
ui.selectable_value(&mut source.panel_id, p.id, p.name.as_str());
|
||||||
&mut source.panel_id,
|
|
||||||
p.id,
|
|
||||||
p.name.as_str(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ui.checkbox(&mut source.visible, "visible");
|
ui.checkbox(&mut source.visible, "visible");
|
||||||
|
@ -37,7 +33,6 @@ pub fn source_edit_inline_ui(ui: &mut Ui, source: &mut Source, panels: &Vec<Pane
|
||||||
ui.color_edit_button_srgba(&mut source.color);
|
ui.color_edit_button_srgba(&mut source.color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn source_ui(ui: &mut Ui, source: &mut Source, panels: &Vec<Panel>) {
|
pub fn source_ui(ui: &mut Ui, source: &mut Source, panels: &Vec<Panel>) {
|
||||||
ui.group(|ui| {
|
ui.group(|ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
|
@ -66,11 +61,7 @@ pub fn source_ui(ui: &mut Ui, source: &mut Source, panels: &Vec<Panel>) {
|
||||||
.width(70.0)
|
.width(70.0)
|
||||||
.show_ui(ui, |ui| {
|
.show_ui(ui, |ui| {
|
||||||
for p in panels {
|
for p in panels {
|
||||||
ui.selectable_value(
|
ui.selectable_value(&mut source.panel_id, p.id, p.name.as_str());
|
||||||
&mut source.panel_id,
|
|
||||||
p.id,
|
|
||||||
p.name.as_str(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ui.color_edit_button_srgba(&mut source.color);
|
ui.color_edit_button_srgba(&mut source.color);
|
||||||
|
|
|
@ -3,21 +3,15 @@ pub mod gui;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
pub mod worker;
|
pub mod worker;
|
||||||
|
|
||||||
use chrono::{Local, Utc};
|
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
use eframe::egui::plot::GridMark;
|
|
||||||
use eframe::egui::{
|
|
||||||
plot::{Line, Plot},
|
|
||||||
Color32,
|
|
||||||
};
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
|
use self::data::source::{Panel, Source};
|
||||||
use self::data::ApplicationState;
|
use self::data::ApplicationState;
|
||||||
use self::data::source::{Panel,Source};
|
use self::gui::panel::{panel_body_ui, panel_edit_inline_ui, panel_title_ui};
|
||||||
use self::gui::panel::{panel_edit_inline_ui, panel_title_ui, panel_body_ui};
|
use self::gui::source::{source_edit_inline_ui, source_ui};
|
||||||
use self::gui::source::{source_ui, source_edit_inline_ui};
|
use self::util::human_size;
|
||||||
use self::util::{human_size, timestamp_to_str};
|
|
||||||
use self::worker::native_save;
|
use self::worker::native_save;
|
||||||
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
|
@ -61,7 +55,11 @@ impl eframe::App for App {
|
||||||
}
|
}
|
||||||
ui.separator();
|
ui.separator();
|
||||||
ui.label("+ source");
|
ui.label("+ source");
|
||||||
source_edit_inline_ui(ui, &mut self.input_source, &self.data.panels.read().expect("Panels RwLock poisoned"));
|
source_edit_inline_ui(
|
||||||
|
ui,
|
||||||
|
&mut self.input_source,
|
||||||
|
&self.data.panels.read().expect("Panels RwLock poisoned"),
|
||||||
|
);
|
||||||
if ui.button("add").clicked() {
|
if ui.button("add").clicked() {
|
||||||
if let Err(e) = self.data.add_source(&self.input_source) {
|
if let Err(e) = self.data.add_source(&self.input_source) {
|
||||||
error!(target: "ui", "Error adding souce : {:?}", e);
|
error!(target: "ui", "Error adding souce : {:?}", e);
|
||||||
|
@ -154,22 +152,22 @@ impl eframe::App for App {
|
||||||
ui.make_persistent_id(format!("panel-{}-compressable", panel.id)),
|
ui.make_persistent_id(format!("panel-{}-compressable", panel.id)),
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
.show_header(ui, |ui| {
|
.show_header(ui, |ui| {
|
||||||
if self.edit {
|
if self.edit {
|
||||||
if ui.small_button(" + ").clicked() {
|
if ui.small_button(" + ").clicked() {
|
||||||
if index > 0 {
|
if index > 0 {
|
||||||
to_swap.push(index); // TODO kinda jank but is there a better way?
|
to_swap.push(index); // TODO kinda jank but is there a better way?
|
||||||
}
|
|
||||||
}
|
|
||||||
if ui.small_button(" - ").clicked() {
|
|
||||||
if index < panels_count - 1 {
|
|
||||||
to_swap.push(index + 1); // TODO kinda jank but is there a better way?
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
panel_title_ui(ui, panel);
|
if ui.small_button(" - ").clicked() {
|
||||||
})
|
if index < panels_count - 1 {
|
||||||
.body(|ui| panel_body_ui(ui, panel, &sources));
|
to_swap.push(index + 1); // TODO kinda jank but is there a better way?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panel_title_ui(ui, panel);
|
||||||
|
})
|
||||||
|
.body(|ui| panel_body_ui(ui, panel, &sources));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use std::sync::Arc;
|
use crate::app::data::{source::fetch, ApplicationState};
|
||||||
use tracing::warn;
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use eframe::egui::Context;
|
use eframe::egui::Context;
|
||||||
use crate::app::data::{ApplicationState, source::fetch};
|
use std::sync::Arc;
|
||||||
|
use tracing::warn;
|
||||||
|
|
||||||
pub fn native_save(state:Arc<ApplicationState>) {
|
pub fn native_save(state: Arc<ApplicationState>) {
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
let storage = state.storage.lock().expect("Storage Mutex poisoned");
|
let storage = state.storage.lock().expect("Storage Mutex poisoned");
|
||||||
let panels = state.panels.read().expect("Panels RwLock poisoned");
|
let panels = state.panels.read().expect("Panels RwLock poisoned");
|
||||||
|
@ -43,16 +43,16 @@ pub fn native_save(state:Arc<ApplicationState>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait BackgroundWorker {
|
pub(crate) trait BackgroundWorker {
|
||||||
fn start(state:Arc<ApplicationState>, ctx:Context) -> Self; // TODO make it return an error? Can we even do anything without a background worker
|
fn start(state: Arc<ApplicationState>, ctx: Context) -> Self; // TODO make it return an error? Can we even do anything without a background worker
|
||||||
fn stop(self); // TODO make it return an error? Can we even do anything without a background worker
|
fn stop(self); // TODO make it return an error? Can we even do anything without a background worker
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct NativeBackgroundWorker {
|
pub(crate) struct NativeBackgroundWorker {
|
||||||
worker : std::thread::JoinHandle<()>,
|
worker: std::thread::JoinHandle<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BackgroundWorker for NativeBackgroundWorker {
|
impl BackgroundWorker for NativeBackgroundWorker {
|
||||||
fn start(state:Arc<ApplicationState>, ctx:Context) -> Self {
|
fn start(state: Arc<ApplicationState>, ctx: Context) -> Self {
|
||||||
let worker = std::thread::spawn(move || {
|
let worker = std::thread::spawn(move || {
|
||||||
let mut last_check = 0;
|
let mut last_check = 0;
|
||||||
while state.run {
|
while state.run {
|
||||||
|
@ -66,25 +66,38 @@ impl BackgroundWorker for NativeBackgroundWorker {
|
||||||
for j in 0..sources.len() {
|
for j in 0..sources.len() {
|
||||||
let s_id = sources[j].id;
|
let s_id = sources[j].id;
|
||||||
if !sources[j].valid() {
|
if !sources[j].valid() {
|
||||||
let mut last_update = sources[j].last_fetch.write().expect("Sources RwLock poisoned");
|
let mut last_update = sources[j]
|
||||||
|
.last_fetch
|
||||||
|
.write()
|
||||||
|
.expect("Sources RwLock poisoned");
|
||||||
*last_update = Utc::now();
|
*last_update = Utc::now();
|
||||||
let state2 = state.clone();
|
let state2 = state.clone();
|
||||||
let url = sources[j].url.clone();
|
let url = sources[j].url.clone();
|
||||||
let query_x = sources[j].query_x.clone();
|
let query_x = sources[j].query_x.clone();
|
||||||
let query_y = sources[j].query_y.clone();
|
let query_y = sources[j].query_y.clone();
|
||||||
std::thread::spawn(move || { // TODO this can overspawn if a request takes longer than the refresh interval!
|
std::thread::spawn(move || {
|
||||||
|
// TODO this can overspawn if a request takes longer than the refresh interval!
|
||||||
match fetch(url.as_str(), query_x.as_str(), query_y.as_str()) {
|
match fetch(url.as_str(), query_x.as_str(), query_y.as_str()) {
|
||||||
Ok(v) => {
|
Ok(v) => {
|
||||||
let store = state2.storage.lock().expect("Storage mutex poisoned");
|
let store =
|
||||||
|
state2.storage.lock().expect("Storage mutex poisoned");
|
||||||
if let Err(e) = store.put_value(s_id, v) {
|
if let Err(e) = store.put_value(s_id, v) {
|
||||||
warn!(target:"background-worker", "Could not put sample for source #{} in db: {:?}", s_id, e);
|
warn!(target:"background-worker", "Could not put sample for source #{} in db: {:?}", s_id, e);
|
||||||
} else {
|
} else {
|
||||||
let sources = state2.sources.read().expect("Sources RwLock poisoned");
|
let sources =
|
||||||
sources[j].data.write().expect("Source data RwLock poisoned").push(v);
|
state2.sources.read().expect("Sources RwLock poisoned");
|
||||||
let mut last_update = sources[j].last_fetch.write().expect("Source last update RwLock poisoned");
|
sources[j]
|
||||||
|
.data
|
||||||
|
.write()
|
||||||
|
.expect("Source data RwLock poisoned")
|
||||||
|
.push(v);
|
||||||
|
let mut last_update = sources[j]
|
||||||
|
.last_fetch
|
||||||
|
.write()
|
||||||
|
.expect("Source last update RwLock poisoned");
|
||||||
*last_update = Utc::now(); // overwrite it so fetches comply with API slowdowns and get desynched among them
|
*last_update = Utc::now(); // overwrite it so fetches comply with API slowdowns and get desynched among them
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!(target:"background-worker", "Could not fetch value from {} : {:?}", url, e);
|
warn!(target:"background-worker", "Could not fetch value from {} : {:?}", url, e);
|
||||||
}
|
}
|
||||||
|
@ -102,12 +115,12 @@ impl BackgroundWorker for NativeBackgroundWorker {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return NativeBackgroundWorker {
|
return NativeBackgroundWorker { worker };
|
||||||
worker
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stop(self) {
|
fn stop(self) {
|
||||||
self.worker.join().expect("Failed joining main worker thread");
|
self.worker
|
||||||
|
.join()
|
||||||
|
.expect("Failed joining main worker thread");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
20
src/main.rs
20
src/main.rs
|
@ -1,23 +1,26 @@
|
||||||
mod app;
|
mod app;
|
||||||
|
|
||||||
|
use crate::app::{
|
||||||
|
data::ApplicationState,
|
||||||
|
util::InternalLogger,
|
||||||
|
worker::{BackgroundWorker, NativeBackgroundWorker},
|
||||||
|
App,
|
||||||
|
};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tracing_subscriber::prelude::*;
|
use tracing_subscriber::prelude::*;
|
||||||
use crate::app::{App, util::InternalLogger, data::ApplicationState, worker::{BackgroundWorker, NativeBackgroundWorker}};
|
|
||||||
|
|
||||||
// When compiling natively:
|
// When compiling natively:
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
fn main() -> ! {
|
fn main() -> ! {
|
||||||
use tracing::metadata::LevelFilter;
|
use tracing::metadata::LevelFilter;
|
||||||
|
|
||||||
|
|
||||||
let native_options = eframe::NativeOptions::default();
|
let native_options = eframe::NativeOptions::default();
|
||||||
|
|
||||||
let mut store_path = dirs::data_dir().unwrap_or(std::path::PathBuf::from(".")); // TODO get cwd more consistently?
|
let mut store_path = dirs::data_dir().unwrap_or(std::path::PathBuf::from(".")); // TODO get cwd more consistently?
|
||||||
store_path.push("dashboard.db");
|
store_path.push("dashboard.db");
|
||||||
|
|
||||||
let store = Arc::new(
|
let store =
|
||||||
ApplicationState::new(store_path).expect("Failed creating application state")
|
Arc::new(ApplicationState::new(store_path).expect("Failed creating application state"));
|
||||||
);
|
|
||||||
|
|
||||||
tracing_subscriber::registry()
|
tracing_subscriber::registry()
|
||||||
.with(LevelFilter::INFO)
|
.with(LevelFilter::INFO)
|
||||||
|
@ -25,7 +28,8 @@ fn main() -> ! {
|
||||||
.with(InternalLogger::new(store.clone()))
|
.with(InternalLogger::new(store.clone()))
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
eframe::run_native( // TODO replace this with a loop that ends so we can cleanly exit the background worker
|
eframe::run_native(
|
||||||
|
// TODO replace this with a loop that ends so we can cleanly exit the background worker
|
||||||
"dashboard",
|
"dashboard",
|
||||||
native_options,
|
native_options,
|
||||||
Box::new(move |cc| {
|
Box::new(move |cc| {
|
||||||
|
|
Loading…
Reference in a new issue