mirror of
https://git.alemi.dev/dashboard.git
synced 2024-11-26 01:14:50 +01:00
feat: made lines color customizable
This commit is contained in:
parent
cdb7d23695
commit
a0d33811b9
5 changed files with 99 additions and 41 deletions
|
@ -6,6 +6,7 @@ use std::sync::{RwLock, Mutex};
|
||||||
use std::num::ParseFloatError;
|
use std::num::ParseFloatError;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use eframe::egui::plot::{Values, Value};
|
use eframe::egui::plot::{Values, Value};
|
||||||
|
use eframe::epaint::Color32;
|
||||||
|
|
||||||
use self::store::SQLiteDataStore;
|
use self::store::SQLiteDataStore;
|
||||||
|
|
||||||
|
@ -64,8 +65,9 @@ impl ApplicationState {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_source(&self, panel_id:i32, name:&str, url:&str, query_x:&str, query_y:&str) -> Result<(), FetchError> {
|
pub fn add_source(&self, panel_id:i32, name:&str, url:&str, query_x:&str, query_y:&str, color:Color32, visible:bool) -> Result<(), FetchError> {
|
||||||
let source = self.storage.lock().expect("Storage Mutex poisoned").new_source(panel_id, name, url, query_x, query_y)?;
|
let source = self.storage.lock().expect("Storage Mutex poisoned")
|
||||||
|
.new_source(panel_id, name, url, query_x, query_y, color, visible)?;
|
||||||
let panels = self.panels.read().expect("Panels RwLock poisoned");
|
let panels = self.panels.read().expect("Panels RwLock poisoned");
|
||||||
for panel in &*panels {
|
for panel in &*panels {
|
||||||
if panel.id == panel_id {
|
if panel.id == panel_id {
|
||||||
|
@ -97,6 +99,8 @@ pub struct Source {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub url: String,
|
pub url: String,
|
||||||
pub interval: i32,
|
pub interval: i32,
|
||||||
|
pub color: Color32,
|
||||||
|
pub visible: bool,
|
||||||
pub(crate) last_fetch: RwLock<DateTime<Utc>>,
|
pub(crate) last_fetch: RwLock<DateTime<Utc>>,
|
||||||
pub query_x: String,
|
pub query_x: String,
|
||||||
// pub(crate) compiled_query_x: Arc<Mutex<jq_rs::JqProgram>>,
|
// pub(crate) compiled_query_x: Arc<Mutex<jq_rs::JqProgram>>,
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
use crate::app::data::{Panel, Source};
|
use crate::app::{data::{Panel, Source}, util::repack_color};
|
||||||
use chrono::{TimeZone, Utc};
|
use chrono::{TimeZone, Utc};
|
||||||
use eframe::egui::plot::Value;
|
use eframe::egui::{Color32, plot::Value};
|
||||||
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);
|
||||||
|
@ -19,12 +20,12 @@ impl SQLiteDataStore {
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"CREATE TABLE IF NOT EXISTS panels (
|
"CREATE TABLE IF NOT EXISTS panels (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
name TEXT UNIQUE,
|
name TEXT UNIQUE NOT NULL,
|
||||||
view_scroll BOOL,
|
view_scroll BOOL NOT NULL,
|
||||||
view_size INT,
|
view_size INT NOT NULL,
|
||||||
timeserie BOOL,
|
timeserie BOOL NOT NULL,
|
||||||
width INT,
|
width INT NOT NULL,
|
||||||
height INT
|
height INT NOT NULL
|
||||||
);",
|
);",
|
||||||
[],
|
[],
|
||||||
)?;
|
)?;
|
||||||
|
@ -32,12 +33,14 @@ impl SQLiteDataStore {
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"CREATE TABLE IF NOT EXISTS sources (
|
"CREATE TABLE IF NOT EXISTS sources (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
name TEXT,
|
name TEXT NOT NULL,
|
||||||
url TEXT,
|
url TEXT NOT NULL,
|
||||||
interval INT,
|
interval INT NOT NULL,
|
||||||
query_x TEXT,
|
query_x TEXT NOT NULL,
|
||||||
query_y TEXT,
|
query_y TEXT NOT NULL,
|
||||||
panel_id INT
|
panel_id INT NOT NULL,
|
||||||
|
color INT NULL,
|
||||||
|
visible BOOL NOT NULL
|
||||||
);",
|
);",
|
||||||
[],
|
[],
|
||||||
)?;
|
)?;
|
||||||
|
@ -45,10 +48,10 @@ impl SQLiteDataStore {
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"CREATE TABLE IF NOT EXISTS points (
|
"CREATE TABLE IF NOT EXISTS points (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
panel_id INT,
|
panel_id INT NOT NULL,
|
||||||
source_id INT,
|
source_id INT NOT NULL,
|
||||||
x FLOAT,
|
x FLOAT NOT NULL,
|
||||||
y FLOAT
|
y FLOAT NOT NULL
|
||||||
);",
|
);",
|
||||||
[],
|
[],
|
||||||
)?;
|
)?;
|
||||||
|
@ -105,6 +108,8 @@ impl SQLiteDataStore {
|
||||||
query_y: row.get(5)?,
|
query_y: row.get(5)?,
|
||||||
// compiled_query_y: Arc::new(Mutex::new(jq_rs::compile(row.get::<usize, String>(5)?.as_str()).unwrap())),
|
// compiled_query_y: Arc::new(Mutex::new(jq_rs::compile(row.get::<usize, String>(5)?.as_str()).unwrap())),
|
||||||
// panel_id: row.get(6)?,
|
// panel_id: row.get(6)?,
|
||||||
|
color: unpack_color(row.get(7).unwrap_or(0)),
|
||||||
|
visible: row.get(8)?,
|
||||||
data: RwLock::new(Vec::new()),
|
data: RwLock::new(Vec::new()),
|
||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
|
@ -127,10 +132,13 @@ impl SQLiteDataStore {
|
||||||
url: &str,
|
url: &str,
|
||||||
query_x: &str,
|
query_x: &str,
|
||||||
query_y: &str,
|
query_y: &str,
|
||||||
|
color: Color32,
|
||||||
|
visible: bool,
|
||||||
) -> rusqlite::Result<Source> {
|
) -> rusqlite::Result<Source> {
|
||||||
|
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) VALUES (?, ?, ?, ?, ?, ?)",
|
"INSERT INTO sources(name, url, interval, query_x, query_y, panel_id, color, visible) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||||
params![name, url, 60, query_x, query_y, panel_id],
|
params![name, url, 60i32, query_x, query_y, panel_id, color_u32, visible],
|
||||||
)?;
|
)?;
|
||||||
let mut statement = self
|
let mut statement = self
|
||||||
.conn
|
.conn
|
||||||
|
@ -141,17 +149,19 @@ impl SQLiteDataStore {
|
||||||
name: row.get(1)?,
|
name: row.get(1)?,
|
||||||
url: row.get(2)?,
|
url: row.get(2)?,
|
||||||
interval: row.get(3)?,
|
interval: row.get(3)?,
|
||||||
query_x: row.get(4)?,
|
|
||||||
query_y: row.get(5)?,
|
|
||||||
// panel_id: row.get(6)?,
|
|
||||||
last_fetch: RwLock::new(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)),
|
last_fetch: RwLock::new(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)),
|
||||||
|
query_x: row.get(4)?,
|
||||||
|
// compiled_query_x: Arc::new(Mutex::new(jq_rs::compile(row.get::<usize, String>(4)?.as_str()).unwrap())),
|
||||||
|
query_y: row.get(5)?,
|
||||||
|
// compiled_query_y: Arc::new(Mutex::new(jq_rs::compile(row.get::<usize, String>(5)?.as_str()).unwrap())),
|
||||||
|
// panel_id: row.get(6)?,
|
||||||
|
color: unpack_color(row.get(7).unwrap_or(0)),
|
||||||
|
visible: row.get(8)?,
|
||||||
data: RwLock::new(Vec::new()),
|
data: RwLock::new(Vec::new()),
|
||||||
})
|
})
|
||||||
})? {
|
})? {
|
||||||
if let Ok(p) = panel {
|
if let Ok(p) = panel {
|
||||||
return Ok(p);
|
return Ok(p);
|
||||||
} else {
|
|
||||||
println!("WTF");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,10 +176,13 @@ impl SQLiteDataStore {
|
||||||
interval: i32,
|
interval: i32,
|
||||||
query_x: &str,
|
query_x: &str,
|
||||||
query_y: &str,
|
query_y: &str,
|
||||||
|
color: Color32,
|
||||||
|
visible: bool,
|
||||||
) -> rusqlite::Result<usize> {
|
) -> rusqlite::Result<usize> {
|
||||||
|
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 = ? WHERE id = ?",
|
"UPDATE sources SET name = ?, url = ?, interval = ?, query_x = ?, query_y = ?, color = ?, visible = ? WHERE id = ?",
|
||||||
params![name, url, interval, query_x, query_y, source_id],
|
params![name, url, interval, query_x, query_y, color_u32, visible, source_id],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ pub mod util;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
use eframe::egui::{plot::{Line, Plot}};
|
use eframe::egui::{RichText, plot::{Line, Plot}, Color32};
|
||||||
|
|
||||||
use self::data::ApplicationState;
|
use self::data::ApplicationState;
|
||||||
use self::worker::native_save;
|
use self::worker::native_save;
|
||||||
|
@ -18,6 +18,8 @@ struct InputBuffer {
|
||||||
interval: i32,
|
interval: i32,
|
||||||
query_x: String,
|
query_x: String,
|
||||||
query_y: String,
|
query_y: String,
|
||||||
|
color: Color32,
|
||||||
|
visible: bool,
|
||||||
panel_id: i32,
|
panel_id: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +32,8 @@ impl Default for InputBuffer {
|
||||||
interval: 60,
|
interval: 60,
|
||||||
query_x: "".to_string(),
|
query_x: "".to_string(),
|
||||||
query_y: "".to_string(),
|
query_y: "".to_string(),
|
||||||
|
color: Color32::TRANSPARENT,
|
||||||
|
visible: true,
|
||||||
panel_id: 0,
|
panel_id: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,7 +88,9 @@ impl eframe::App for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
ui.checkbox(&mut self.input.visible, "visible");
|
||||||
ui.add(egui::Slider::new(&mut self.input.interval, 1..=60));
|
ui.add(egui::Slider::new(&mut self.input.interval, 1..=60));
|
||||||
|
ui.color_edit_button_srgba(&mut self.input.color);
|
||||||
if ui.button("add").clicked() {
|
if ui.button("add").clicked() {
|
||||||
self.data.add_source(
|
self.data.add_source(
|
||||||
self.input.panel_id,
|
self.input.panel_id,
|
||||||
|
@ -92,6 +98,8 @@ impl eframe::App for App {
|
||||||
self.input.url.as_str(),
|
self.input.url.as_str(),
|
||||||
self.input.query_x.as_str(),
|
self.input.query_x.as_str(),
|
||||||
self.input.query_y.as_str(),
|
self.input.query_y.as_str(),
|
||||||
|
self.input.color,
|
||||||
|
self.input.visible,
|
||||||
).unwrap();
|
).unwrap();
|
||||||
}
|
}
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
@ -128,11 +136,19 @@ impl eframe::App for App {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.heading(panel.name.as_str());
|
ui.heading(panel.name.as_str());
|
||||||
ui.separator();
|
ui.separator();
|
||||||
if !self.edit {
|
for source in &mut *sources {
|
||||||
for source in &*sources {
|
if self.edit {
|
||||||
ui.label(source.name.as_str());
|
ui.checkbox(&mut source.visible, "");
|
||||||
ui.separator();
|
|
||||||
}
|
}
|
||||||
|
if source.visible {
|
||||||
|
ui.label(
|
||||||
|
RichText::new(source.name.as_str())
|
||||||
|
.color(if source.color == Color32::TRANSPARENT { Color32::GRAY } else { source.color })
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ui.label(RichText::new(source.name.as_str()).color(Color32::BLACK));
|
||||||
|
}
|
||||||
|
ui.separator();
|
||||||
}
|
}
|
||||||
ui.checkbox(&mut panel.view_scroll, "autoscroll");
|
ui.checkbox(&mut panel.view_scroll, "autoscroll");
|
||||||
ui.checkbox(&mut panel.timeserie, "timeserie");
|
ui.checkbox(&mut panel.timeserie, "timeserie");
|
||||||
|
@ -150,11 +166,12 @@ impl eframe::App for App {
|
||||||
for source in &mut *sources {
|
for source in &mut *sources {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.add(egui::Slider::new(&mut source.interval, 1..=60));
|
ui.add(egui::Slider::new(&mut source.interval, 1..=60));
|
||||||
eframe::egui::TextEdit::singleline(&mut source.url).hint_text("url").desired_width(250.0).show(ui);
|
eframe::egui::TextEdit::singleline(&mut source.url).hint_text("url").desired_width(300.0).show(ui);
|
||||||
if !panel.timeserie {
|
if !panel.timeserie {
|
||||||
eframe::egui::TextEdit::singleline(&mut source.query_x).hint_text("x").desired_width(25.0).show(ui);
|
eframe::egui::TextEdit::singleline(&mut source.query_x).hint_text("x").desired_width(50.0).show(ui);
|
||||||
}
|
}
|
||||||
eframe::egui::TextEdit::singleline(&mut source.query_y).hint_text("y").desired_width(25.0).show(ui);
|
eframe::egui::TextEdit::singleline(&mut source.query_y).hint_text("y").desired_width(50.0).show(ui);
|
||||||
|
ui.color_edit_button_srgba(&mut source.color);
|
||||||
ui.separator();
|
ui.separator();
|
||||||
ui.label(source.name.as_str());
|
ui.label(source.name.as_str());
|
||||||
});
|
});
|
||||||
|
@ -185,10 +202,13 @@ impl eframe::App for App {
|
||||||
|
|
||||||
p.show(ui, |plot_ui| {
|
p.show(ui, |plot_ui| {
|
||||||
for source in &mut *sources {
|
for source in &mut *sources {
|
||||||
if self.filter {
|
if source.visible {
|
||||||
plot_ui.line(Line::new(source.values_filter((Utc::now().timestamp() - (panel.view_size as i64 * 60)) as f64)).name(source.name.as_str()));
|
let line = if self.filter {
|
||||||
} else {
|
Line::new(source.values_filter((Utc::now().timestamp() - (panel.view_size as i64 * 60)) as f64)).name(source.name.as_str())
|
||||||
plot_ui.line(Line::new(source.values()).name(source.name.as_str()));
|
} else {
|
||||||
|
Line::new(source.values()).name(source.name.as_str())
|
||||||
|
};
|
||||||
|
plot_ui.line(line.color(source.color));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// if you're handling more than terabytes of data, it's the future and you ought to update this code!
|
// if you're handling more than terabytes of data, it's the future and you ought to update this code!
|
||||||
use chrono::{DateTime, Utc, NaiveDateTime};
|
use chrono::{DateTime, Utc, NaiveDateTime};
|
||||||
|
use eframe::egui::Color32;
|
||||||
|
|
||||||
const PREFIXES: &'static [&'static str] = &["", "k", "M", "G", "T"];
|
const PREFIXES: &'static [&'static str] = &["", "k", "M", "G", "T"];
|
||||||
|
|
||||||
|
@ -20,4 +21,22 @@ pub fn timestamp_to_str(t:i64) -> String {
|
||||||
DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(t, 0), Utc)
|
DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(t, 0), Utc)
|
||||||
.format("%Y/%m/%d %H:%M:%S")
|
.format("%Y/%m/%d %H:%M:%S")
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unpack_color(c: u32) -> Color32 {
|
||||||
|
let r : u8 = (c >> 0) as u8;
|
||||||
|
let g : u8 = (c >> 8) as u8;
|
||||||
|
let b : u8 = (c >> 16) as u8;
|
||||||
|
let a : u8 = (c >> 24) as u8;
|
||||||
|
return Color32::from_rgba_unmultiplied(r, g, b, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn repack_color(c: Color32) -> u32 {
|
||||||
|
let mut out : u32 = 0;
|
||||||
|
let mut offset = 0;
|
||||||
|
for el in c.to_array() {
|
||||||
|
out |= ((el & 0xFF) as u32) << offset;
|
||||||
|
offset += 8;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
}
|
}
|
|
@ -26,6 +26,8 @@ pub fn native_save(state:Arc<ApplicationState>) {
|
||||||
source.interval,
|
source.interval,
|
||||||
source.query_x.as_str(),
|
source.query_x.as_str(),
|
||||||
source.query_y.as_str(),
|
source.query_y.as_str(),
|
||||||
|
source.color,
|
||||||
|
source.visible,
|
||||||
).unwrap();
|
).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue