mirror of
https://git.alemi.dev/dashboard.git
synced 2024-11-22 15:34:54 +01:00
chore: boilerplate over async stuff
This commit is contained in:
parent
cbca9f99b3
commit
bc94398e49
12 changed files with 695 additions and 456 deletions
|
@ -2,8 +2,6 @@ pub mod entities;
|
||||||
|
|
||||||
use std::num::ParseFloatError;
|
use std::num::ParseFloatError;
|
||||||
|
|
||||||
use sea_orm::{DatabaseConnection, EntityTrait};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum FetchError {
|
pub enum FetchError {
|
||||||
ReqwestError(reqwest::Error),
|
ReqwestError(reqwest::Error),
|
||||||
|
@ -39,39 +37,3 @@ impl From<sea_orm::DbErr> for FetchError {
|
||||||
FetchError::DbError(e)
|
FetchError::DbError(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub struct ApplicationState {
|
|
||||||
// pub run: bool,
|
|
||||||
db: DatabaseConnection,
|
|
||||||
pub panels: Vec<entities::panels::Model>,
|
|
||||||
pub sources: Vec<entities::sources::Model>,
|
|
||||||
pub metrics: Vec<entities::metrics::Model>,
|
|
||||||
last_fetch: i64,
|
|
||||||
// pub diagnostics: RwLock<Vec<String>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
impl ApplicationState {
|
|
||||||
pub fn new(db: DatabaseConnection) -> Result<ApplicationState, FetchError> {
|
|
||||||
return Ok(ApplicationState {
|
|
||||||
db,
|
|
||||||
panels: vec![],
|
|
||||||
sources: vec![],
|
|
||||||
metrics: vec![],
|
|
||||||
last_fetch: 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn fetch(&mut self) -> Result<(), sea_orm::DbErr> {
|
|
||||||
self.panels = entities::panels::Entity::find().all(&self.db).await?;
|
|
||||||
self.sources = entities::sources::Entity::find().all(&self.db).await?;
|
|
||||||
self.metrics = entities::metrics::Entity::find().all(&self.db).await?;
|
|
||||||
self.last_fetch = chrono::Utc::now().timestamp();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn age(&self) -> i64 {
|
|
||||||
chrono::Utc::now().timestamp() - self.last_fetch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use eframe::{egui::{Ui, Layout, Sense, color_picker::show_color_at}, emath::Align, epaint::Color32};
|
use eframe::{egui::{Ui, Layout, Sense, color_picker::show_color_at, ComboBox, TextEdit}, emath::Align, epaint::Color32};
|
||||||
|
|
||||||
use crate::{data::entities, util::unpack_color};
|
use crate::{data::entities, util::unpack_color};
|
||||||
|
|
||||||
fn color_square(ui: &mut Ui, color:Color32) {
|
fn _color_square(ui: &mut Ui, color:Color32) {
|
||||||
let size = ui.spacing().interact_size;
|
let size = ui.spacing().interact_size;
|
||||||
let (rect, response) = ui.allocate_exact_size(size, Sense::click());
|
let (rect, response) = ui.allocate_exact_size(size, Sense::click());
|
||||||
if ui.is_rect_visible(rect) {
|
if ui.is_rect_visible(rect) {
|
||||||
|
@ -17,9 +17,9 @@ fn color_square(ui: &mut Ui, color:Color32) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn metric_display_ui(ui: &mut Ui, metric: &entities::metrics::Model, _width: f32) {
|
pub fn _metric_display_ui(ui: &mut Ui, metric: &entities::metrics::Model, _width: f32) {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
color_square(ui, unpack_color(metric.color));
|
_color_square(ui, unpack_color(metric.color));
|
||||||
ui.label(&metric.name);
|
ui.label(&metric.name);
|
||||||
ui.with_layout(Layout::top_down(Align::RIGHT), |ui| {
|
ui.with_layout(Layout::top_down(Align::RIGHT), |ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
|
@ -34,32 +34,42 @@ pub fn metric_display_ui(ui: &mut Ui, metric: &entities::metrics::Model, _width:
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn metric_edit_ui(ui: &mut Ui, metric: &entities::metrics::Model, panels: Option<&Vec<entities::panels::Model>>, width: f32) {
|
pub fn metric_edit_ui(ui: &mut Ui, metric: &entities::metrics::Model, panels: Option<&Vec<entities::panels::Model>>, width: f32) {
|
||||||
let _text_width = width - 195.0;
|
let text_width = width - 195.0;
|
||||||
|
let mut name = metric.name.clone();
|
||||||
|
let def_str = "".into();
|
||||||
|
let mut query_x = metric.query_x.as_ref().unwrap_or(&def_str).clone();
|
||||||
|
let mut query_y = metric.query_y.clone();
|
||||||
|
let mut panel_id = 0;
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.color_edit_button_srgba(&mut unpack_color(metric.color));
|
ui.color_edit_button_srgba(&mut unpack_color(metric.color));
|
||||||
// TextEdit::singleline(&mut metric.name)
|
TextEdit::singleline(&mut name)
|
||||||
// .desired_width(text_width / 2.0)
|
.interactive(false)
|
||||||
// .hint_text("name")
|
.desired_width(text_width / 2.0)
|
||||||
// .show(ui);
|
.hint_text("name")
|
||||||
|
.show(ui);
|
||||||
ui.separator();
|
ui.separator();
|
||||||
// TextEdit::singleline(&mut metric.query_x.unwrap_or("".into()))
|
if query_x.len() > 0 {
|
||||||
// .desired_width(text_width / 4.0)
|
TextEdit::singleline(&mut query_x)
|
||||||
// .hint_text("x")
|
.interactive(false)
|
||||||
// .show(ui);
|
.desired_width(text_width / 4.0)
|
||||||
// TextEdit::singleline(&mut metric.query_y)
|
.hint_text("x")
|
||||||
// .desired_width(text_width / 4.0)
|
.show(ui);
|
||||||
// .hint_text("y")
|
}
|
||||||
// .show(ui);
|
TextEdit::singleline(&mut query_y)
|
||||||
if let Some(_panels) = panels {
|
.interactive(false)
|
||||||
// ComboBox::from_id_source(format!("panel-selector-{}", metric.id))
|
.desired_width(if query_x.len() > 0 { 0.0 } else { 15.0 } + (text_width / if query_x.len() > 0 { 4.0 } else { 2.0 }))
|
||||||
// .width(60.0)
|
.hint_text("y")
|
||||||
// .selected_text(format!("panel: {:02}", metric.panel_id))
|
.show(ui);
|
||||||
// .show_ui(ui, |ui| {
|
if let Some(panels) = panels {
|
||||||
// ui.selectable_value(&mut metric.panel_id, -1, "None");
|
ComboBox::from_id_source(format!("panel-selector-{}", metric.id))
|
||||||
// for p in panels {
|
.width(60.0)
|
||||||
// ui.selectable_value(&mut metric.panel_id, p.id, p.name.as_str());
|
.selected_text(format!("panel: {:02}", metric.panel_id))
|
||||||
// }
|
.show_ui(ui, |ui| {
|
||||||
// });
|
ui.selectable_value(&mut panel_id, -1, "None");
|
||||||
|
for p in panels {
|
||||||
|
ui.selectable_value(&mut panel_id, p.id, p.name.as_str());
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,11 @@ pub mod metric;
|
||||||
|
|
||||||
mod scaffold;
|
mod scaffold;
|
||||||
|
|
||||||
|
use chrono::Utc;
|
||||||
use eframe::egui::{CentralPanel, Context, SidePanel, TopBottomPanel};
|
use eframe::egui::{CentralPanel, Context, SidePanel, TopBottomPanel};
|
||||||
use tokio::sync::watch;
|
use tokio::sync::watch;
|
||||||
|
|
||||||
use crate::data::entities;
|
use crate::{data::entities, worker::visualizer::AppStateView};
|
||||||
use panel::main_content;
|
use panel::main_content;
|
||||||
use scaffold::{
|
use scaffold::{
|
||||||
// confirmation_popup_delete_metric, confirmation_popup_delete_source, footer,
|
// confirmation_popup_delete_metric, confirmation_popup_delete_source, footer,
|
||||||
|
@ -15,14 +16,17 @@ use scaffold::{
|
||||||
};
|
};
|
||||||
use source::source_panel;
|
use source::source_panel;
|
||||||
|
|
||||||
pub struct App {
|
use self::scaffold::footer;
|
||||||
panels_rx: watch::Receiver<Vec<entities::panels::Model>>,
|
|
||||||
panels: Vec<entities::panels::Model>,
|
|
||||||
view_tx: watch::Sender<i64>,
|
|
||||||
|
|
||||||
sources: watch::Receiver<Vec<entities::sources::Model>>,
|
pub struct App {
|
||||||
metrics: watch::Receiver<Vec<entities::metrics::Model>>,
|
view: AppStateView,
|
||||||
points: watch::Receiver<Vec<entities::points::Model>>,
|
db_path: String,
|
||||||
|
interval: i64,
|
||||||
|
last_redraw: i64,
|
||||||
|
|
||||||
|
panels: Vec<entities::panels::Model>,
|
||||||
|
width_tx: watch::Sender<i64>,
|
||||||
|
logger_view: watch::Receiver<Vec<String>>,
|
||||||
|
|
||||||
buffer_panel: entities::panels::Model,
|
buffer_panel: entities::panels::Model,
|
||||||
buffer_source: entities::sources::Model,
|
buffer_source: entities::sources::Model,
|
||||||
|
@ -36,24 +40,34 @@ pub struct App {
|
||||||
impl App {
|
impl App {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
_cc: &eframe::CreationContext,
|
_cc: &eframe::CreationContext,
|
||||||
panels_rx: watch::Receiver<Vec<entities::panels::Model>>,
|
db_path: String,
|
||||||
sources: watch::Receiver<Vec<entities::sources::Model>>,
|
interval: i64,
|
||||||
metrics: watch::Receiver<Vec<entities::metrics::Model>>,
|
view: AppStateView,
|
||||||
points: watch::Receiver<Vec<entities::points::Model>>,
|
width_tx: watch::Sender<i64>,
|
||||||
view_tx: watch::Sender<i64>,
|
logger_view: watch::Receiver<Vec<String>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let panels = panels_rx.borrow().clone();
|
let panels = view.panels.borrow().clone();
|
||||||
Self {
|
Self {
|
||||||
panels_rx, panels, view_tx,
|
db_path, interval, panels, width_tx, view, logger_view,
|
||||||
sources, metrics, points,
|
|
||||||
buffer_panel: entities::panels::Model::default(),
|
buffer_panel: entities::panels::Model::default(),
|
||||||
buffer_source: entities::sources::Model::default(),
|
buffer_source: entities::sources::Model::default(),
|
||||||
buffer_metric: entities::metrics::Model::default(),
|
buffer_metric: entities::metrics::Model::default(),
|
||||||
|
last_redraw: 0,
|
||||||
edit: false,
|
edit: false,
|
||||||
sidebar: true,
|
sidebar: true,
|
||||||
padding: false,
|
padding: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn save_all_panels(&self) {
|
||||||
|
self.view.op.blocking_send(
|
||||||
|
crate::worker::visualizer::BackgroundAction::UpdateAllPanels { panels: self.panels.clone() }
|
||||||
|
).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn refresh_data(&self) {
|
||||||
|
self.view.flush.blocking_send(()).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl eframe::App for App {
|
impl eframe::App for App {
|
||||||
|
@ -62,8 +76,8 @@ impl eframe::App for App {
|
||||||
header(self, ui, frame);
|
header(self, ui, frame);
|
||||||
});
|
});
|
||||||
|
|
||||||
TopBottomPanel::bottom("footer").show(ctx, |_ui| {
|
TopBottomPanel::bottom("footer").show(ctx, |ui| {
|
||||||
// footer(self.data.clone(), ctx, ui);
|
footer(ctx, ui, self.logger_view.clone(), self.db_path.clone(), self.view.points.borrow().len());
|
||||||
});
|
});
|
||||||
|
|
||||||
// if let Some(index) = self.deleting_metric {
|
// if let Some(index) = self.deleting_metric {
|
||||||
|
@ -75,6 +89,10 @@ impl eframe::App for App {
|
||||||
// .show(ctx, |ui| confirmation_popup_delete_source(self, ui, index));
|
// .show(ctx, |ui| confirmation_popup_delete_source(self, ui, index));
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
// for window in self.windows {
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
if self.sidebar {
|
if self.sidebar {
|
||||||
SidePanel::left("sources-bar")
|
SidePanel::left("sources-bar")
|
||||||
.width_range(if self.edit { 400.0..=1000.0 } else { 280.0..=680.0 })
|
.width_range(if self.edit { 400.0..=1000.0 } else { 280.0..=680.0 })
|
||||||
|
@ -87,7 +105,12 @@ impl eframe::App for App {
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(viewsize) = self.panels.iter().map(|p| p.view_size).max() {
|
if let Some(viewsize) = self.panels.iter().map(|p| p.view_size).max() {
|
||||||
self.view_tx.send(viewsize as i64).unwrap();
|
self.width_tx.send(viewsize as i64).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
if Utc::now().timestamp() > self.last_redraw + self.interval {
|
||||||
|
ctx.request_repaint();
|
||||||
|
self.last_redraw = Utc::now().timestamp();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use chrono::{Local, Utc};
|
use chrono::{Local, Utc};
|
||||||
use eframe::{egui::{
|
use eframe::{egui::{
|
||||||
plot::{Corner, GridMark, Legend, Line, Plot, PlotPoints, PlotPoint},
|
plot::{Corner, GridMark, Legend, Line, Plot},
|
||||||
Ui, ScrollArea, collapsing_header::CollapsingState, Context, Layout, Slider, DragValue,
|
Ui, ScrollArea, collapsing_header::CollapsingState, Context, Layout, Slider, DragValue,
|
||||||
}, emath::Vec2};
|
}, emath::Vec2};
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ pub fn main_content(app: &mut App, ctx: &Context, ui: &mut Ui) {
|
||||||
ScrollArea::vertical().show(ui, |ui| {
|
ScrollArea::vertical().show(ui, |ui| {
|
||||||
let panels = &mut app.panels;
|
let panels = &mut app.panels;
|
||||||
let _panels_count = panels.len();
|
let _panels_count = panels.len();
|
||||||
let metrics = app.metrics.borrow();
|
let metrics = app.view.metrics.borrow();
|
||||||
for (index, panel) in panels.iter_mut().enumerate() {
|
for (index, panel) in panels.iter_mut().enumerate() {
|
||||||
if index > 0 {
|
if index > 0 {
|
||||||
ui.separator(); // only show this if there is at least one panel
|
ui.separator(); // only show this if there is at least one panel
|
||||||
|
@ -41,7 +41,7 @@ pub fn main_content(app: &mut App, ctx: &Context, ui: &mut Ui) {
|
||||||
// ui.separator();
|
// ui.separator();
|
||||||
panel_title_ui(ui, panel, app.edit);
|
panel_title_ui(ui, panel, app.edit);
|
||||||
})
|
})
|
||||||
.body(|ui| panel_body_ui(ui, panel, &metrics, &app.points.borrow()));
|
.body(|ui| panel_body_ui(ui, panel, &metrics, &app.view.points.borrow()));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use eframe::{Frame, egui::{collapsing_header::CollapsingState, Context, Ui, Layout, ScrollArea, global_dark_light_mode_switch}, emath::Align};
|
use eframe::{Frame, egui::{collapsing_header::CollapsingState, Context, Ui, Layout, ScrollArea, global_dark_light_mode_switch}, emath::Align};
|
||||||
|
use tokio::sync::watch;
|
||||||
|
|
||||||
use crate::data::ApplicationState;
|
|
||||||
use crate::gui::App;
|
use crate::gui::App;
|
||||||
|
|
||||||
use super::panel::panel_edit_inline_ui;
|
use super::panel::panel_edit_inline_ui;
|
||||||
|
@ -68,16 +66,19 @@ pub fn header(app: &mut App, ui: &mut Ui, frame: &mut Frame) {
|
||||||
ui.checkbox(&mut app.sidebar, "sources");
|
ui.checkbox(&mut app.sidebar, "sources");
|
||||||
ui.separator();
|
ui.separator();
|
||||||
if ui.button("reset").clicked() {
|
if ui.button("reset").clicked() {
|
||||||
app.panels = app.panels_rx.borrow().clone();
|
app.panels = app.view.panels.borrow().clone();
|
||||||
|
}
|
||||||
|
ui.separator();
|
||||||
|
if ui.button("save").clicked() {
|
||||||
|
app.save_all_panels();
|
||||||
|
}
|
||||||
|
ui.separator();
|
||||||
|
if ui.button("refresh").clicked() {
|
||||||
|
app.refresh_data();
|
||||||
}
|
}
|
||||||
ui.separator();
|
ui.separator();
|
||||||
ui.checkbox(&mut app.edit, "edit");
|
ui.checkbox(&mut app.edit, "edit");
|
||||||
if app.edit {
|
if app.edit {
|
||||||
if ui.button("save").clicked() {
|
|
||||||
// native_save(app.data.clone());
|
|
||||||
app.edit = false;
|
|
||||||
}
|
|
||||||
ui.separator();
|
|
||||||
ui.label("+ panel");
|
ui.label("+ panel");
|
||||||
panel_edit_inline_ui(ui, &mut app.buffer_panel);
|
panel_edit_inline_ui(ui, &mut app.buffer_panel);
|
||||||
if ui.button("add").clicked() {
|
if ui.button("add").clicked() {
|
||||||
|
@ -97,7 +98,7 @@ pub fn header(app: &mut App, ui: &mut Ui, frame: &mut Frame) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _footer(_data: Arc<ApplicationState>, ctx: &Context, ui: &mut Ui) {
|
pub fn footer(ctx: &Context, ui: &mut Ui, diagnostics: watch::Receiver<Vec<String>>, db_path: String, records: usize) {
|
||||||
CollapsingState::load_with_default_open(
|
CollapsingState::load_with_default_open(
|
||||||
ctx,
|
ctx,
|
||||||
ui.make_persistent_id("footer-logs"),
|
ui.make_persistent_id("footer-logs"),
|
||||||
|
@ -106,8 +107,9 @@ pub fn _footer(_data: Arc<ApplicationState>, ctx: &Context, ui: &mut Ui) {
|
||||||
.show_header(ui, |ui| {
|
.show_header(ui, |ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.separator();
|
ui.separator();
|
||||||
// ui.label(data.file_path.to_str().unwrap()); // TODO maybe calculate it just once?
|
ui.label(db_path); // TODO maybe calculate it just once?
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
ui.label(format!("{} records loaded", records)); // TODO put thousands separator
|
||||||
// ui.label(human_size(
|
// ui.label(human_size(
|
||||||
// *data
|
// *data
|
||||||
// .file_size
|
// .file_size
|
||||||
|
@ -131,14 +133,10 @@ pub fn _footer(_data: Arc<ApplicationState>, ctx: &Context, ui: &mut Ui) {
|
||||||
.body(|ui| {
|
.body(|ui| {
|
||||||
ui.set_height(200.0);
|
ui.set_height(200.0);
|
||||||
ScrollArea::vertical().show(ui, |ui| {
|
ScrollArea::vertical().show(ui, |ui| {
|
||||||
// let msgs = data
|
|
||||||
// .diagnostics
|
|
||||||
// .read()
|
|
||||||
// .expect("Diagnostics RwLock poisoned");
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
//for msg in msgs.iter() {
|
for msg in diagnostics.borrow().iter() {
|
||||||
// ui.label(msg);
|
ui.label(msg);
|
||||||
//}
|
}
|
||||||
ui.separator();
|
ui.separator();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use eframe::{
|
use eframe::{
|
||||||
egui::{Layout, ScrollArea, Ui},
|
egui::{Layout, ScrollArea, Ui, DragValue, TextEdit, Checkbox},
|
||||||
emath::Align,
|
emath::Align,
|
||||||
};
|
};
|
||||||
use rfd::FileDialog;
|
use rfd::FileDialog;
|
||||||
|
@ -9,7 +9,7 @@ use crate::util::deserialize_values;
|
||||||
use crate::gui::App;
|
use crate::gui::App;
|
||||||
use crate::data::entities;
|
use crate::data::entities;
|
||||||
|
|
||||||
use super::metric::{metric_display_ui, metric_edit_ui};
|
use super::metric::metric_edit_ui;
|
||||||
|
|
||||||
pub fn source_panel(app: &mut App, ui: &mut Ui) {
|
pub fn source_panel(app: &mut App, ui: &mut Ui) {
|
||||||
let mut source_to_put_metric_on : Option<i32> = None;
|
let mut source_to_put_metric_on : Option<i32> = None;
|
||||||
|
@ -23,138 +23,126 @@ pub fn source_panel(app: &mut App, ui: &mut Ui) {
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
// TODO only vertical!
|
// TODO only vertical!
|
||||||
{
|
{
|
||||||
let sources = app.sources.borrow();
|
let sources = app.view.sources.borrow();
|
||||||
let sources_count = sources.len();
|
let sources_count = sources.len();
|
||||||
ui.heading("Sources");
|
ui.heading("Sources");
|
||||||
ui.separator();
|
ui.separator();
|
||||||
for (i, source) in sources.iter().enumerate() {
|
for (i, source) in sources.iter().enumerate() {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
if app.edit { // show buttons to move sources up and down
|
ui.vertical(|ui| {
|
||||||
ui.vertical(|ui| {
|
ui.add_space(10.0);
|
||||||
ui.add_space(10.0);
|
if ui.small_button("+").clicked() {
|
||||||
if ui.small_button("+").clicked() {
|
if i > 0 {
|
||||||
if i > 0 {
|
to_swap = Some(i); // TODO kinda jank but is there a better way?
|
||||||
to_swap = Some(i); // TODO kinda jank but is there a better way?
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if ui.small_button("−").clicked() {
|
}
|
||||||
if i < sources_count - 1 {
|
if ui.small_button("−").clicked() {
|
||||||
to_swap = Some(i + 1); // TODO kinda jank but is there a better way?
|
if i < sources_count - 1 {
|
||||||
}
|
to_swap = Some(i + 1); // TODO kinda jank but is there a better way?
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
});
|
||||||
ui.vertical(|ui| { // actual sources list container
|
ui.vertical(|ui| { // actual sources list container
|
||||||
let remaining_width = ui.available_width();
|
let remaining_width = ui.available_width();
|
||||||
if app.edit {
|
ui.group(|ui| {
|
||||||
ui.group(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.horizontal(|ui| {
|
source_edit_ui(ui, source, remaining_width - 34.0);
|
||||||
source_edit_ui(ui, source, remaining_width - 34.0);
|
if ui.small_button("×").clicked() {
|
||||||
if ui.small_button("×").clicked() {
|
// app.deleting_metric = None;
|
||||||
// app.deleting_metric = None;
|
// app.deleting_source = Some(i);
|
||||||
// app.deleting_source = Some(i);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let metrics = app
|
|
||||||
.metrics
|
|
||||||
.borrow();
|
|
||||||
for (_j, metric) in metrics.iter().enumerate() {
|
|
||||||
if metric.source_id == source.id {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
metric_edit_ui(
|
|
||||||
ui,
|
|
||||||
metric,
|
|
||||||
Some(&panels),
|
|
||||||
remaining_width - 53.0,
|
|
||||||
);
|
|
||||||
if ui.small_button("s").clicked() {
|
|
||||||
let path = FileDialog::new()
|
|
||||||
.add_filter("csv", &["csv"])
|
|
||||||
.set_file_name(format!("{}-{}.csv", source.name, metric.name).as_str())
|
|
||||||
.save_file();
|
|
||||||
if let Some(_path) = path {
|
|
||||||
// serialize_values(
|
|
||||||
// &*metric
|
|
||||||
// .data
|
|
||||||
// .read()
|
|
||||||
// .expect("Values RwLock poisoned"),
|
|
||||||
// metric,
|
|
||||||
// path,
|
|
||||||
// )
|
|
||||||
// .expect("Could not serialize data");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ui.small_button("×").clicked() {
|
|
||||||
// app.deleting_source = None;
|
|
||||||
// app.deleting_metric = Some(j);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ui.horizontal(|ui| {
|
});
|
||||||
metric_edit_ui(
|
let metrics = app
|
||||||
ui,
|
.view
|
||||||
&mut app.buffer_metric,
|
.metrics
|
||||||
None,
|
.borrow();
|
||||||
remaining_width - 53.0,
|
for (_j, metric) in metrics.iter().enumerate() {
|
||||||
);
|
if metric.source_id == source.id {
|
||||||
ui.add_space(2.0);
|
ui.horizontal(|ui| {
|
||||||
if ui.small_button(" + ").clicked() {
|
metric_edit_ui(
|
||||||
source_to_put_metric_on = Some(source.id);
|
ui,
|
||||||
}
|
metric,
|
||||||
ui.add_space(1.0); // DAMN!
|
Some(&panels),
|
||||||
if ui.small_button("o").clicked() {
|
remaining_width - 53.0,
|
||||||
let path = FileDialog::new()
|
);
|
||||||
.add_filter("csv", &["csv"])
|
if ui.small_button("s").clicked() {
|
||||||
.pick_file();
|
let path = FileDialog::new()
|
||||||
if let Some(path) = path {
|
.add_filter("csv", &["csv"])
|
||||||
match deserialize_values(path) {
|
.set_file_name(format!("{}-{}.csv", source.name, metric.name).as_str())
|
||||||
Ok((_name, _query_x, _query_y, _data)) => {
|
.save_file();
|
||||||
// let mut store = app
|
if let Some(_path) = path {
|
||||||
// .data
|
// serialize_values(
|
||||||
// .storage
|
// &*metric
|
||||||
// .lock()
|
// .data
|
||||||
// .expect("Storage Mutex poisoned");
|
// .read()
|
||||||
// match store.new_metric(
|
// .expect("Values RwLock poisoned"),
|
||||||
// name.as_str(),
|
// metric,
|
||||||
// source.id,
|
// path,
|
||||||
// query_x.as_str(),
|
// )
|
||||||
// query_y.as_str(),
|
// .expect("Could not serialize data");
|
||||||
// -1,
|
}
|
||||||
// Color32::TRANSPARENT,
|
}
|
||||||
// metrics.len() as i32,
|
if ui.small_button("×").clicked() {
|
||||||
// ) {
|
// app.deleting_source = None;
|
||||||
// Ok(verified_metric) => {
|
// app.deleting_metric = Some(j);
|
||||||
// store.put_values(verified_metric.id, &data).unwrap();
|
}
|
||||||
// *verified_metric.data.write().expect("Values RwLock poisoned") = data;
|
});
|
||||||
// to_insert.push(verified_metric);
|
}
|
||||||
// }
|
}
|
||||||
// Err(e) => {
|
ui.horizontal(|ui| {
|
||||||
// error!(target: "ui", "could not save metric into archive : {:?}", e);
|
metric_edit_ui(
|
||||||
// }
|
ui,
|
||||||
// }
|
&mut app.buffer_metric,
|
||||||
}
|
None,
|
||||||
Err(e) => {
|
remaining_width - 53.0,
|
||||||
error!(target: "ui", "Could not deserialize metric from file : {:?}", e);
|
);
|
||||||
}
|
ui.add_space(2.0);
|
||||||
|
if ui.small_button(" + ").clicked() {
|
||||||
|
source_to_put_metric_on = Some(source.id);
|
||||||
|
}
|
||||||
|
ui.add_space(1.0); // DAMN!
|
||||||
|
if ui.small_button("o").clicked() {
|
||||||
|
let path = FileDialog::new()
|
||||||
|
.add_filter("csv", &["csv"])
|
||||||
|
.pick_file();
|
||||||
|
if let Some(path) = path {
|
||||||
|
match deserialize_values(path) {
|
||||||
|
Ok((_name, _query_x, _query_y, _data)) => {
|
||||||
|
// let mut store = app
|
||||||
|
// .data
|
||||||
|
// .storage
|
||||||
|
// .lock()
|
||||||
|
// .expect("Storage Mutex poisoned");
|
||||||
|
// match store.new_metric(
|
||||||
|
// name.as_str(),
|
||||||
|
// source.id,
|
||||||
|
// query_x.as_str(),
|
||||||
|
// query_y.as_str(),
|
||||||
|
// -1,
|
||||||
|
// Color32::TRANSPARENT,
|
||||||
|
// metrics.len() as i32,
|
||||||
|
// ) {
|
||||||
|
// Ok(verified_metric) => {
|
||||||
|
// store.put_values(verified_metric.id, &data).unwrap();
|
||||||
|
// *verified_metric.data.write().expect("Values RwLock poisoned") = data;
|
||||||
|
// to_insert.push(verified_metric);
|
||||||
|
// }
|
||||||
|
// Err(e) => {
|
||||||
|
// error!(target: "ui", "could not save metric into archive : {:?}", e);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!(target: "ui", "Could not deserialize metric from file : {:?}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ui.small_button("×").clicked() {
|
|
||||||
app.buffer_metric = entities::metrics::Model::default();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
let metrics = app.metrics.borrow();
|
|
||||||
source_display_ui(ui, source, remaining_width);
|
|
||||||
for metric in metrics.iter() {
|
|
||||||
if metric.source_id == source.id {
|
|
||||||
metric_display_ui(ui, metric, ui.available_width());
|
|
||||||
}
|
}
|
||||||
}
|
if ui.small_button("×").clicked() {
|
||||||
ui.separator();
|
app.buffer_metric = entities::metrics::Model::default();
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -200,7 +188,7 @@ pub fn source_panel(app: &mut App, ui: &mut Ui) {
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
if let Some(s) = source_to_put_metric_on {
|
if let Some(s) = source_to_put_metric_on {
|
||||||
for source in app.sources.borrow().iter() {
|
for source in app.view.sources.borrow().iter() {
|
||||||
if source.id == s {
|
if source.id == s {
|
||||||
// if let Err(e) =
|
// if let Err(e) =
|
||||||
// app.data.add_metric(&app.input_metric, &source)
|
// app.data.add_metric(&app.input_metric, &source)
|
||||||
|
@ -212,29 +200,31 @@ pub fn source_panel(app: &mut App, ui: &mut Ui) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn source_display_ui(_ui: &mut Ui, _source: &entities::sources::Model, _width: f32) {
|
pub fn _source_display_ui(ui: &mut Ui, source: &entities::sources::Model, _width: f32) {
|
||||||
// ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
|
ui.heading(&source.name).on_hover_text(&source.url);
|
||||||
// ui.add_enabled(false, Checkbox::new(&mut source.enabled, ""));
|
// ui.add_enabled(false, Checkbox::new(&mut source.enabled, ""));
|
||||||
// ui.add_enabled(
|
});
|
||||||
// false,
|
|
||||||
// DragValue::new(&mut source.interval).clamp_range(1..=120),
|
|
||||||
// );
|
|
||||||
// ui.heading(&source.name).on_hover_text(&source.url);
|
|
||||||
// });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn source_edit_ui(_ui: &mut Ui, _source: &entities::sources::Model, _width: f32) {
|
pub fn source_edit_ui(ui: &mut Ui, source: &entities::sources::Model, width: f32) {
|
||||||
// ui.horizontal(|ui| {
|
let mut interval = source.interval.clone();
|
||||||
// let text_width = width - 100.0;
|
let mut name = source.name.clone();
|
||||||
// ui.checkbox(&mut source.enabled, "");
|
let mut url = source.url.clone();
|
||||||
// ui.add(DragValue::new(&mut source.interval).clamp_range(1..=3600));
|
let mut enabled = source.enabled.clone();
|
||||||
// TextEdit::singleline(&mut source.name)
|
ui.horizontal(|ui| {
|
||||||
// .desired_width(text_width / 4.0)
|
let text_width = width - 100.0;
|
||||||
// .hint_text("name")
|
ui.add_enabled(false, Checkbox::new(&mut enabled, ""));
|
||||||
// .show(ui);
|
ui.add_enabled(false, DragValue::new(&mut interval).clamp_range(1..=3600));
|
||||||
// TextEdit::singleline(&mut source.url)
|
TextEdit::singleline(&mut name)
|
||||||
// .desired_width(text_width * 3.0 / 4.0)
|
.interactive(false)
|
||||||
// .hint_text("url")
|
.desired_width(text_width / 4.0)
|
||||||
// .show(ui);
|
.hint_text("name")
|
||||||
// });
|
.show(ui);
|
||||||
|
TextEdit::singleline(&mut url)
|
||||||
|
.interactive(false)
|
||||||
|
.desired_width(text_width * 3.0 / 4.0)
|
||||||
|
.hint_text("url")
|
||||||
|
.show(ui);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
105
src/main.rs
105
src/main.rs
|
@ -5,14 +5,17 @@ mod worker;
|
||||||
|
|
||||||
use tracing::metadata::LevelFilter;
|
use tracing::metadata::LevelFilter;
|
||||||
use tracing_subscriber::prelude::*;
|
use tracing_subscriber::prelude::*;
|
||||||
use tracing::info;
|
use tracing::{info, error};
|
||||||
use tracing_subscriber::filter::filter_fn;
|
use tracing_subscriber::filter::filter_fn;
|
||||||
|
|
||||||
|
use eframe::egui::Context;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use tokio::sync::watch;
|
use tokio::sync::watch;
|
||||||
use sea_orm::Database;
|
use sea_orm::Database;
|
||||||
|
|
||||||
use worker::{surveyor_loop, visualizer_loop};
|
use worker::visualizer::AppState;
|
||||||
|
use worker::surveyor_loop;
|
||||||
|
use util::InternalLogger;
|
||||||
use gui::{
|
use gui::{
|
||||||
// util::InternalLogger,
|
// util::InternalLogger,
|
||||||
App
|
App
|
||||||
|
@ -34,12 +37,16 @@ struct CliArgs {
|
||||||
gui: bool,
|
gui: bool,
|
||||||
|
|
||||||
/// Check interval for background worker
|
/// Check interval for background worker
|
||||||
#[arg(short, long, default_value_t = 5)]
|
#[arg(short, long, default_value_t = 10)]
|
||||||
interval: u64,
|
interval: u64,
|
||||||
|
|
||||||
/// How often sources and metrics are refreshed
|
/// How often sources and metrics are refreshed
|
||||||
#[arg(short, long, default_value_t = 300)]
|
#[arg(short, long, default_value_t = 300)]
|
||||||
cache_time: u64,
|
cache_time: u64,
|
||||||
|
|
||||||
|
/// How many log lines to keep in memory
|
||||||
|
#[arg(short, long, default_value_t = 1000)]
|
||||||
|
log_size: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
// When compiling natively:
|
// When compiling natively:
|
||||||
|
@ -47,25 +54,31 @@ struct CliArgs {
|
||||||
fn main() {
|
fn main() {
|
||||||
let args = CliArgs::parse();
|
let args = CliArgs::parse();
|
||||||
|
|
||||||
|
// TODO is there an alternative to this ugly botch?
|
||||||
|
let (ctx_tx, ctx_rx) = watch::channel::<Option<Context>>(None);
|
||||||
|
|
||||||
|
let (width_tx, width_rx) = watch::channel(0);
|
||||||
|
let (run_tx, run_rx) = watch::channel(true);
|
||||||
|
|
||||||
|
let logger = InternalLogger::new(args.log_size as usize);
|
||||||
|
let logger_view = logger.view();
|
||||||
|
|
||||||
tracing_subscriber::registry()
|
tracing_subscriber::registry()
|
||||||
.with(LevelFilter::INFO)
|
.with(LevelFilter::INFO)
|
||||||
.with(filter_fn(|x| x.target() != "sqlx::query"))
|
.with(filter_fn(|x| x.target() != "sqlx::query"))
|
||||||
.with(tracing_subscriber::fmt::Layer::new())
|
.with(tracing_subscriber::fmt::Layer::new())
|
||||||
// .with(InternalLogger::new(store.clone()))
|
.with(logger.layer())
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
// // Set default file location
|
let state = AppState::new(
|
||||||
// let mut store_path = dirs::data_dir().unwrap_or(PathBuf::from(".")); // TODO get cwd more consistently?
|
width_rx,
|
||||||
// store_path.push("dashboard.db");
|
args.interval as i64,
|
||||||
// let store =
|
args.cache_time as i64,
|
||||||
// Arc::new(ApplicationState::new(store_path).expect("Failed creating application state"));
|
).unwrap();
|
||||||
|
|
||||||
|
let view = state.view();
|
||||||
let (panel_tx, panel_rx) = watch::channel(vec![]);
|
let run_rx_clone = run_rx.clone();
|
||||||
let (source_tx, source_rx) = watch::channel(vec![]);
|
let db_uri = args.db.clone();
|
||||||
let (metric_tx, metric_rx) = watch::channel(vec![]);
|
|
||||||
let (point_tx, point_rx) = watch::channel(vec![]);
|
|
||||||
let (view_tx, view_rx) = watch::channel(1440);
|
|
||||||
|
|
||||||
let worker = std::thread::spawn(move || {
|
let worker = std::thread::spawn(move || {
|
||||||
tokio::runtime::Builder::new_current_thread()
|
tokio::runtime::Builder::new_current_thread()
|
||||||
|
@ -73,11 +86,28 @@ fn main() {
|
||||||
.build()
|
.build()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.block_on(async {
|
.block_on(async {
|
||||||
let db = Database::connect(args.db.clone()).await.unwrap();
|
let db = Database::connect(db_uri.clone()).await.unwrap();
|
||||||
info!(target: "launcher", "Connected to '{}'", args.db);
|
info!(target: "launcher", "Connected to '{}'", db_uri);
|
||||||
|
|
||||||
let mut jobs = vec![];
|
let mut jobs = vec![];
|
||||||
|
|
||||||
|
let run_rx_clone_clone = run_rx_clone.clone();
|
||||||
|
|
||||||
|
jobs.push(
|
||||||
|
tokio::spawn(async move {
|
||||||
|
while *run_rx_clone_clone.borrow() {
|
||||||
|
if let Some(ctx) = &*ctx_rx.borrow() {
|
||||||
|
ctx.request_repaint();
|
||||||
|
}
|
||||||
|
tokio::time::sleep(std::time::Duration::from_secs(args.interval)).await;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
jobs.push(
|
||||||
|
tokio::spawn(logger.worker(run_rx_clone.clone()))
|
||||||
|
);
|
||||||
|
|
||||||
if args.worker {
|
if args.worker {
|
||||||
jobs.push(
|
jobs.push(
|
||||||
tokio::spawn(
|
tokio::spawn(
|
||||||
|
@ -85,6 +115,7 @@ fn main() {
|
||||||
db.clone(),
|
db.clone(),
|
||||||
args.interval as i64,
|
args.interval as i64,
|
||||||
args.cache_time as i64,
|
args.cache_time as i64,
|
||||||
|
run_rx_clone.clone(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -93,16 +124,7 @@ fn main() {
|
||||||
if args.gui {
|
if args.gui {
|
||||||
jobs.push(
|
jobs.push(
|
||||||
tokio::spawn(
|
tokio::spawn(
|
||||||
visualizer_loop(
|
state.worker(db, run_rx_clone.clone())
|
||||||
db.clone(),
|
|
||||||
args.interval,
|
|
||||||
args.cache_time as i64,
|
|
||||||
panel_tx,
|
|
||||||
source_tx,
|
|
||||||
metric_tx,
|
|
||||||
point_tx,
|
|
||||||
view_rx,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -116,23 +138,34 @@ fn main() {
|
||||||
if args.gui {
|
if args.gui {
|
||||||
let native_options = eframe::NativeOptions::default();
|
let native_options = eframe::NativeOptions::default();
|
||||||
|
|
||||||
|
info!(target: "launcher", "Starting native GUI");
|
||||||
|
|
||||||
eframe::run_native(
|
eframe::run_native(
|
||||||
// TODO replace this with a loop that ends so we can cleanly exit the background worker
|
// TODO replace this with a loop that ends so we can cleanly exit the background worker
|
||||||
"dashboard",
|
"dashboard",
|
||||||
native_options,
|
native_options,
|
||||||
Box::new(
|
Box::new(
|
||||||
move |cc| Box::new(
|
move |cc| {
|
||||||
App::new(
|
ctx_tx.send(Some(cc.egui_ctx.clone())).unwrap_or_else(|_| {
|
||||||
cc,
|
error!(target: "launcher", "Could not share reference to egui context (won't be able to periodically refresh window)");
|
||||||
panel_rx,
|
});
|
||||||
source_rx,
|
Box::new(
|
||||||
metric_rx,
|
App::new(
|
||||||
point_rx,
|
cc,
|
||||||
view_tx,
|
args.db,
|
||||||
|
args.interval as i64,
|
||||||
|
view,
|
||||||
|
width_tx,
|
||||||
|
logger_view,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
info!(target: "launcher", "Stopping native GUI");
|
||||||
|
|
||||||
|
run_tx.send(false).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
worker.join().unwrap();
|
worker.join().unwrap();
|
||||||
|
|
71
src/util.rs
71
src/util.rs
|
@ -1,9 +1,10 @@
|
||||||
use chrono::{DateTime, Local, NaiveDateTime, Utc};
|
use chrono::{DateTime, Local, NaiveDateTime, Utc};
|
||||||
use eframe::egui::{Color32, plot::PlotPoint};
|
use eframe::egui::{Color32, plot::PlotPoint};
|
||||||
use std::{sync::Arc, error::Error, path::PathBuf};
|
use tokio::sync::{watch, mpsc};
|
||||||
|
use std::{error::Error, path::PathBuf, collections::VecDeque};
|
||||||
use tracing_subscriber::Layer;
|
use tracing_subscriber::Layer;
|
||||||
|
|
||||||
use super::data::{ApplicationState, entities};
|
use super::data::entities;
|
||||||
|
|
||||||
// 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!
|
||||||
const _PREFIXES: &'static [&'static str] = &["", "k", "M", "G", "T"];
|
const _PREFIXES: &'static [&'static str] = &["", "k", "M", "G", "T"];
|
||||||
|
@ -51,7 +52,8 @@ pub fn deserialize_values(path: PathBuf) -> Result<(String, String, String, Vec<
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _human_size(size: u64) -> String {
|
#[allow(dead_code)]
|
||||||
|
pub fn human_size(size: u64) -> String {
|
||||||
let mut buf: f64 = size as f64;
|
let mut buf: f64 = size as f64;
|
||||||
let mut prefix: usize = 0;
|
let mut prefix: usize = 0;
|
||||||
while buf > 1024.0 && prefix < _PREFIXES.len() - 1 {
|
while buf > 1024.0 && prefix < _PREFIXES.len() - 1 {
|
||||||
|
@ -101,16 +103,62 @@ pub fn repack_color(c: Color32) -> u32 {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct InternalLogger {
|
pub struct InternalLogger {
|
||||||
_state: Arc<ApplicationState>,
|
size: usize,
|
||||||
|
view_tx: watch::Sender<Vec<String>>,
|
||||||
|
view_rx: watch::Receiver<Vec<String>>,
|
||||||
|
msg_tx : mpsc::UnboundedSender<String>,
|
||||||
|
msg_rx : mpsc::UnboundedReceiver<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InternalLogger {
|
impl InternalLogger {
|
||||||
pub fn _new(state: Arc<ApplicationState>) -> Self {
|
pub fn new(size: usize) -> Self {
|
||||||
InternalLogger { _state: state }
|
let (view_tx, view_rx) = watch::channel(vec![]);
|
||||||
|
let (msg_tx, msg_rx) = mpsc::unbounded_channel();
|
||||||
|
InternalLogger { size, view_tx, view_rx, msg_tx, msg_rx }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view(&self) -> watch::Receiver<Vec<String>> {
|
||||||
|
self.view_rx.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn layer(&self) -> InternalLoggerLayer {
|
||||||
|
InternalLoggerLayer::new(self.msg_tx.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn worker(mut self, run: watch::Receiver<bool>) {
|
||||||
|
let mut messages = VecDeque::new();
|
||||||
|
while *run.borrow() {
|
||||||
|
tokio::select!{
|
||||||
|
msg = self.msg_rx.recv() => {
|
||||||
|
match msg {
|
||||||
|
Some(msg) => {
|
||||||
|
messages.push_back(msg);
|
||||||
|
while messages.len() > self.size {
|
||||||
|
messages.pop_front();
|
||||||
|
}
|
||||||
|
self.view_tx.send(messages.clone().into()).unwrap();
|
||||||
|
},
|
||||||
|
None => break,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ = tokio::time::sleep(std::time::Duration::from_secs(1)) => {},
|
||||||
|
// unblock so it checks again run and exits cleanly
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> Layer<S> for InternalLogger
|
pub struct InternalLoggerLayer {
|
||||||
|
msg_tx: mpsc::UnboundedSender<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InternalLoggerLayer {
|
||||||
|
pub fn new(msg_tx: mpsc::UnboundedSender<String>) -> Self {
|
||||||
|
InternalLoggerLayer { msg_tx }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Layer<S> for InternalLoggerLayer
|
||||||
where
|
where
|
||||||
S: tracing::Subscriber,
|
S: tracing::Subscriber,
|
||||||
{
|
{
|
||||||
|
@ -123,18 +171,15 @@ where
|
||||||
msg: "".to_string(),
|
msg: "".to_string(),
|
||||||
};
|
};
|
||||||
event.record(&mut msg_visitor);
|
event.record(&mut msg_visitor);
|
||||||
let _out = format!(
|
let out = format!(
|
||||||
"{} [{}] {}: {}",
|
"{} [{}] {}: {}",
|
||||||
Local::now().format("%H:%M:%S"),
|
Local::now().format("%H:%M:%S"),
|
||||||
event.metadata().level(),
|
event.metadata().level(),
|
||||||
event.metadata().target(),
|
event.metadata().target(),
|
||||||
msg_visitor.msg
|
msg_visitor.msg
|
||||||
);
|
);
|
||||||
// self.state
|
|
||||||
// .diagnostics
|
self.msg_tx.send(out).unwrap_or_default();
|
||||||
// .write()
|
|
||||||
// .expect("Diagnostics RwLock poisoned")
|
|
||||||
// .push(out);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
159
src/worker.rs
159
src/worker.rs
|
@ -1,159 +0,0 @@
|
||||||
use chrono::Utc;
|
|
||||||
use sea_orm::{DatabaseConnection, ActiveValue::NotSet, Set, EntityTrait, Condition, ColumnTrait, QueryFilter};
|
|
||||||
use tokio::sync::watch;
|
|
||||||
use tracing::{error, info};
|
|
||||||
use std::collections::VecDeque;
|
|
||||||
|
|
||||||
use super::data::{entities, FetchError};
|
|
||||||
|
|
||||||
async fn fetch(url: &str) -> Result<serde_json::Value, FetchError> {
|
|
||||||
Ok(reqwest::get(url).await?.json().await?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn surveyor_loop(
|
|
||||||
db: DatabaseConnection,
|
|
||||||
interval:i64,
|
|
||||||
cache_time:i64,
|
|
||||||
) {
|
|
||||||
let mut last_check = Utc::now().timestamp();
|
|
||||||
let mut last_fetch = 0;
|
|
||||||
let mut sources = vec![];
|
|
||||||
let mut metrics = vec![];
|
|
||||||
loop {
|
|
||||||
// sleep until next activation
|
|
||||||
let delta_time = (interval as i64) - (Utc::now().timestamp() - last_check);
|
|
||||||
if delta_time > 0 {
|
|
||||||
std::thread::sleep(std::time::Duration::from_secs(delta_time as u64));
|
|
||||||
}
|
|
||||||
last_check = Utc::now().timestamp();
|
|
||||||
|
|
||||||
if Utc::now().timestamp() - last_fetch > cache_time {
|
|
||||||
// TODO do both concurrently
|
|
||||||
let res = tokio::join!(
|
|
||||||
entities::sources::Entity::find().all(&db),
|
|
||||||
entities::metrics::Entity::find().all(&db)
|
|
||||||
);
|
|
||||||
sources = res.0.unwrap();
|
|
||||||
metrics = res.1.unwrap();
|
|
||||||
last_fetch = Utc::now().timestamp();
|
|
||||||
}
|
|
||||||
|
|
||||||
for source in sources.iter_mut() {
|
|
||||||
if !source.enabled || !source.ready() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// source.last_fetch = Utc::now(); // TODO! do it in background threads again!
|
|
||||||
// tokio::spawn(async move {
|
|
||||||
match fetch(&source.url).await {
|
|
||||||
Ok(res) => {
|
|
||||||
let now = Utc::now().timestamp();
|
|
||||||
entities::sources::Entity::update(
|
|
||||||
entities::sources::ActiveModel{id: Set(source.id), last_update: Set(now), ..Default::default()}
|
|
||||||
).exec(&db).await.unwrap();
|
|
||||||
source.last_update = now;
|
|
||||||
for metric in metrics.iter().filter(|x| source.id == x.source_id) {
|
|
||||||
match metric.extract(&res) {
|
|
||||||
Ok(v) => {
|
|
||||||
entities::points::Entity::insert(
|
|
||||||
entities::points::ActiveModel {
|
|
||||||
id: NotSet, metric_id: Set(metric.id), x: Set(v.x), y: Set(v.y),
|
|
||||||
}).exec(&db).await.unwrap();
|
|
||||||
},
|
|
||||||
Err(e) => error!(target: "worker", "Failed extracting '{}' from {}: {:?}", metric.name, source.name, e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => error!(target: "worker", "Failed fetching {}: {:?}", source.name, e),
|
|
||||||
}
|
|
||||||
// source.last_fetch = Utc::now(); // TODO!
|
|
||||||
// });
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// if let Ok(meta) = std::fs::metadata(state.file_path.clone()) {
|
|
||||||
// let mut fsize = state.file_size.write().expect("File Size RwLock poisoned");
|
|
||||||
// *fsize = meta.len();
|
|
||||||
// } // ignore errors
|
|
||||||
|
|
||||||
// ctx.request_repaint();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn visualizer_loop(
|
|
||||||
db: DatabaseConnection,
|
|
||||||
interval: u64,
|
|
||||||
cache_time: i64,
|
|
||||||
panels_tx: watch::Sender<Vec<entities::panels::Model>>,
|
|
||||||
sources_tx: watch::Sender<Vec<entities::sources::Model>>,
|
|
||||||
metrics_tx: watch::Sender<Vec<entities::metrics::Model>>,
|
|
||||||
points_tx: watch::Sender<Vec<entities::points::Model>>,
|
|
||||||
view_rx: watch::Receiver<i64>,
|
|
||||||
) {
|
|
||||||
let mut points : VecDeque<entities::points::Model> = VecDeque::new();
|
|
||||||
let mut last_fetch = 0;
|
|
||||||
|
|
||||||
let mut width = *view_rx.borrow() * 60; // TODO it's in minutes somewhere...
|
|
||||||
let mut lower = Utc::now().timestamp() - width;
|
|
||||||
|
|
||||||
let mut changes;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
if Utc::now().timestamp() - last_fetch >= cache_time {
|
|
||||||
panels_tx.send(entities::panels::Entity::find().all(&db).await.unwrap()).unwrap();
|
|
||||||
sources_tx.send(entities::sources::Entity::find().all(&db).await.unwrap()).unwrap();
|
|
||||||
metrics_tx.send(entities::metrics::Entity::find().all(&db).await.unwrap()).unwrap();
|
|
||||||
last_fetch = Utc::now().timestamp();
|
|
||||||
info!(target: "worker", "Updated panels, sources and metrics");
|
|
||||||
}
|
|
||||||
|
|
||||||
changes = false;
|
|
||||||
let now = Utc::now().timestamp();
|
|
||||||
let new_width = *view_rx.borrow() * 60; // TODO it's in minutes somewhere...
|
|
||||||
|
|
||||||
if new_width != width {
|
|
||||||
let mut lower_points = entities::points::Entity::find()
|
|
||||||
.filter(
|
|
||||||
Condition::all()
|
|
||||||
.add(entities::points::Column::X.gte(now - new_width))
|
|
||||||
.add(entities::points::Column::X.lte(now - width))
|
|
||||||
)
|
|
||||||
.all(&db)
|
|
||||||
.await.unwrap();
|
|
||||||
lower_points.reverse(); // TODO wasteful!
|
|
||||||
for p in lower_points {
|
|
||||||
points.push_front(p);
|
|
||||||
changes = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
width = new_width;
|
|
||||||
|
|
||||||
let new_points = entities::points::Entity::find()
|
|
||||||
.filter(
|
|
||||||
Condition::all()
|
|
||||||
.add(entities::points::Column::X.gte(lower as f64))
|
|
||||||
)
|
|
||||||
.all(&db)
|
|
||||||
.await.unwrap();
|
|
||||||
|
|
||||||
lower = Utc::now().timestamp();
|
|
||||||
while let Some(p) = points.get(0) {
|
|
||||||
if (p.x as i64) >= lower - (*view_rx.borrow() * 60) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
points.pop_front();
|
|
||||||
changes = true;
|
|
||||||
}
|
|
||||||
for p in new_points {
|
|
||||||
points.push_back(p);
|
|
||||||
changes = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if changes {
|
|
||||||
points_tx.send(points.clone().into()).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
tokio::time::sleep(std::time::Duration::from_secs(interval)).await;
|
|
||||||
}
|
|
||||||
}
|
|
5
src/worker/mod.rs
Normal file
5
src/worker/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
pub mod surveyor;
|
||||||
|
pub mod visualizer;
|
||||||
|
|
||||||
|
pub use surveyor::surveyor_loop;
|
||||||
|
pub use visualizer::{AppState, AppStateView, BackgroundAction};
|
81
src/worker/surveyor.rs
Normal file
81
src/worker/surveyor.rs
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
use chrono::Utc;
|
||||||
|
use sea_orm::{DatabaseConnection, ActiveValue::NotSet, Set, EntityTrait};
|
||||||
|
use tokio::sync::watch;
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
|
use crate::data::{entities, FetchError};
|
||||||
|
|
||||||
|
async fn fetch(url: &str) -> Result<serde_json::Value, FetchError> {
|
||||||
|
Ok(reqwest::get(url).await?.json().await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn surveyor_loop(
|
||||||
|
db: DatabaseConnection,
|
||||||
|
interval:i64,
|
||||||
|
cache_time:i64,
|
||||||
|
run: watch::Receiver<bool>,
|
||||||
|
) {
|
||||||
|
let mut last_check = Utc::now().timestamp();
|
||||||
|
let mut last_fetch = 0;
|
||||||
|
let mut sources = vec![];
|
||||||
|
let mut metrics = vec![];
|
||||||
|
while *run.borrow() {
|
||||||
|
// sleep until next activation
|
||||||
|
let delta_time = (interval as i64) - (Utc::now().timestamp() - last_check);
|
||||||
|
if delta_time > 0 {
|
||||||
|
tokio::time::sleep(std::time::Duration::from_secs(delta_time as u64)).await;
|
||||||
|
}
|
||||||
|
last_check = Utc::now().timestamp();
|
||||||
|
|
||||||
|
if Utc::now().timestamp() - last_fetch > cache_time {
|
||||||
|
// TODO do both concurrently
|
||||||
|
let res = tokio::join!(
|
||||||
|
entities::sources::Entity::find().all(&db),
|
||||||
|
entities::metrics::Entity::find().all(&db)
|
||||||
|
);
|
||||||
|
sources = res.0.unwrap();
|
||||||
|
metrics = res.1.unwrap();
|
||||||
|
last_fetch = Utc::now().timestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
for source in sources.iter_mut() {
|
||||||
|
if !source.enabled || !source.ready() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// source.last_fetch = Utc::now(); // TODO! do it in background threads again!
|
||||||
|
// tokio::spawn(async move {
|
||||||
|
match fetch(&source.url).await {
|
||||||
|
Ok(res) => {
|
||||||
|
let now = Utc::now().timestamp();
|
||||||
|
entities::sources::Entity::update(
|
||||||
|
entities::sources::ActiveModel{id: Set(source.id), last_update: Set(now), ..Default::default()}
|
||||||
|
).exec(&db).await.unwrap();
|
||||||
|
source.last_update = now;
|
||||||
|
for metric in metrics.iter().filter(|x| source.id == x.source_id) {
|
||||||
|
match metric.extract(&res) {
|
||||||
|
Ok(v) => {
|
||||||
|
entities::points::Entity::insert(
|
||||||
|
entities::points::ActiveModel {
|
||||||
|
id: NotSet, metric_id: Set(metric.id), x: Set(v.x), y: Set(v.y),
|
||||||
|
}).exec(&db).await.unwrap();
|
||||||
|
},
|
||||||
|
Err(e) => error!(target: "worker", "Failed extracting '{}' from {}: {:?}", metric.name, source.name, e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => error!(target: "worker", "Failed fetching {}: {:?}", source.name, e),
|
||||||
|
}
|
||||||
|
// source.last_fetch = Utc::now(); // TODO!
|
||||||
|
// });
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// if let Ok(meta) = std::fs::metadata(state.file_path.clone()) {
|
||||||
|
// let mut fsize = state.file_size.write().expect("File Size RwLock poisoned");
|
||||||
|
// *fsize = meta.len();
|
||||||
|
// } // ignore errors
|
||||||
|
|
||||||
|
// ctx.request_repaint();
|
||||||
|
}
|
||||||
|
}
|
251
src/worker/visualizer.rs
Normal file
251
src/worker/visualizer.rs
Normal file
|
@ -0,0 +1,251 @@
|
||||||
|
use chrono::Utc;
|
||||||
|
use sea_orm::{DatabaseConnection, EntityTrait, Condition, ColumnTrait, QueryFilter, Set};
|
||||||
|
use tokio::sync::{watch, mpsc};
|
||||||
|
use tracing::info;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
use crate::data::{entities, FetchError};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AppStateView {
|
||||||
|
pub panels: watch::Receiver<Vec<entities::panels::Model>>,
|
||||||
|
pub sources: watch::Receiver<Vec<entities::sources::Model>>,
|
||||||
|
pub metrics: watch::Receiver<Vec<entities::metrics::Model>>,
|
||||||
|
pub points: watch::Receiver<Vec<entities::points::Model>>,
|
||||||
|
pub flush: mpsc::Sender<()>,
|
||||||
|
pub op: mpsc::Sender<BackgroundAction>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppStateView {
|
||||||
|
pub async fn _request_flush(&self) -> bool {
|
||||||
|
match self.flush.send(()).await {
|
||||||
|
Ok(_) => true,
|
||||||
|
Err(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AppStateTransmitters {
|
||||||
|
panels: watch::Sender<Vec<entities::panels::Model>>,
|
||||||
|
sources: watch::Sender<Vec<entities::sources::Model>>,
|
||||||
|
metrics: watch::Sender<Vec<entities::metrics::Model>>,
|
||||||
|
points: watch::Sender<Vec<entities::points::Model>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AppState {
|
||||||
|
tx: AppStateTransmitters,
|
||||||
|
|
||||||
|
panels: Vec<entities::panels::Model>,
|
||||||
|
sources: Vec<entities::sources::Model>,
|
||||||
|
metrics: Vec<entities::metrics::Model>,
|
||||||
|
last_refresh: i64,
|
||||||
|
|
||||||
|
points: VecDeque<entities::points::Model>,
|
||||||
|
last_check: i64,
|
||||||
|
|
||||||
|
flush: mpsc::Receiver<()>,
|
||||||
|
op: mpsc::Receiver<BackgroundAction>,
|
||||||
|
|
||||||
|
interval: i64,
|
||||||
|
cache_age: i64,
|
||||||
|
|
||||||
|
width: watch::Receiver<i64>,
|
||||||
|
|
||||||
|
view: AppStateView,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn sleep(t:i64) {
|
||||||
|
if t > 0 {
|
||||||
|
tokio::time::sleep(std::time::Duration::from_secs(t as u64)).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppState {
|
||||||
|
pub fn new(
|
||||||
|
width: watch::Receiver<i64>,
|
||||||
|
interval: i64,
|
||||||
|
cache_age: i64,
|
||||||
|
) -> Result<AppState, FetchError> {
|
||||||
|
let (panel_tx, panel_rx) = watch::channel(vec![]);
|
||||||
|
let (source_tx, source_rx) = watch::channel(vec![]);
|
||||||
|
let (metric_tx, metric_rx) = watch::channel(vec![]);
|
||||||
|
let (point_tx, point_rx) = watch::channel(vec![]);
|
||||||
|
// let (view_tx, view_rx) = watch::channel(0);
|
||||||
|
let (flush_tx, flush_rx) = mpsc::channel(10);
|
||||||
|
let (op_tx, op_rx) = mpsc::channel(100);
|
||||||
|
|
||||||
|
Ok(AppState {
|
||||||
|
panels: vec![],
|
||||||
|
sources: vec![],
|
||||||
|
metrics: vec![],
|
||||||
|
last_refresh: 0,
|
||||||
|
points: VecDeque::new(),
|
||||||
|
last_check: 0,
|
||||||
|
flush: flush_rx,
|
||||||
|
op: op_rx,
|
||||||
|
view: AppStateView {
|
||||||
|
panels: panel_rx,
|
||||||
|
sources: source_rx,
|
||||||
|
metrics: metric_rx,
|
||||||
|
points: point_rx,
|
||||||
|
flush: flush_tx,
|
||||||
|
op: op_tx,
|
||||||
|
},
|
||||||
|
tx: AppStateTransmitters {
|
||||||
|
panels: panel_tx,
|
||||||
|
sources: source_tx,
|
||||||
|
metrics: metric_tx,
|
||||||
|
points: point_tx,
|
||||||
|
},
|
||||||
|
width,
|
||||||
|
interval,
|
||||||
|
cache_age,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view(&self) -> AppStateView {
|
||||||
|
self.view.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn fetch(&mut self, db: &DatabaseConnection) -> Result<(), sea_orm::DbErr> {
|
||||||
|
// TODO parallelize all this stuff
|
||||||
|
self.panels = entities::panels::Entity::find().all(db).await?;
|
||||||
|
self.tx.panels.send(self.panels.clone()).unwrap();
|
||||||
|
self.sources = entities::sources::Entity::find().all(db).await?;
|
||||||
|
self.tx.sources.send(self.sources.clone()).unwrap();
|
||||||
|
self.metrics = entities::metrics::Entity::find().all(db).await?;
|
||||||
|
self.tx.metrics.send(self.metrics.clone()).unwrap();
|
||||||
|
info!(target: "worker", "Updated panels, sources and metrics");
|
||||||
|
self.last_refresh = chrono::Utc::now().timestamp();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _cache_age(&self) -> i64 {
|
||||||
|
chrono::Utc::now().timestamp() - self.last_refresh
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn worker(mut self, db: DatabaseConnection, run:watch::Receiver<bool>) {
|
||||||
|
let mut width = *self.width.borrow() * 60; // TODO it's in minutes somewhere...
|
||||||
|
let mut last = Utc::now().timestamp() - width;
|
||||||
|
|
||||||
|
while *run.borrow() {
|
||||||
|
let now = Utc::now().timestamp();
|
||||||
|
tokio::select!{
|
||||||
|
op = self.op.recv() => {
|
||||||
|
match op {
|
||||||
|
Some(op) => {
|
||||||
|
match op {
|
||||||
|
BackgroundAction::UpdateAllPanels { panels } => {
|
||||||
|
// TODO this is kinda rough, can it be done better?
|
||||||
|
entities::panels::Entity::delete_many().exec(&db).await.unwrap();
|
||||||
|
entities::panels::Entity::insert_many(
|
||||||
|
panels.iter().map(|v| entities::panels::ActiveModel{
|
||||||
|
id: Set(v.id),
|
||||||
|
name: Set(v.name.clone()),
|
||||||
|
view_scroll: Set(v.view_scroll),
|
||||||
|
view_size: Set(v.view_size),
|
||||||
|
timeserie: Set(v.timeserie),
|
||||||
|
height: Set(v.height),
|
||||||
|
limit_view: Set(v.limit_view),
|
||||||
|
position: Set(v.position),
|
||||||
|
reduce_view: Set(v.reduce_view),
|
||||||
|
view_chunks: Set(v.view_chunks),
|
||||||
|
shift_view: Set(v.shift_view),
|
||||||
|
view_offset: Set(v.view_offset),
|
||||||
|
average_view: Set(v.average_view),
|
||||||
|
}).collect::<Vec<entities::panels::ActiveModel>>()
|
||||||
|
).exec(&db).await.unwrap();
|
||||||
|
self.tx.panels.send(panels.clone()).unwrap();
|
||||||
|
self.panels = panels;
|
||||||
|
},
|
||||||
|
// _ => todo!(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ = self.flush.recv() => {
|
||||||
|
let now = Utc::now().timestamp();
|
||||||
|
self.fetch(&db).await.unwrap();
|
||||||
|
let new_width = *self.width.borrow() * 60; // TODO it's in minutes somewhere...
|
||||||
|
self.points = entities::points::Entity::find()
|
||||||
|
.filter(
|
||||||
|
Condition::all()
|
||||||
|
.add(entities::points::Column::X.gte((now - new_width) as f64))
|
||||||
|
.add(entities::points::Column::X.lte(now as f64))
|
||||||
|
)
|
||||||
|
.all(&db)
|
||||||
|
.await.unwrap().into();
|
||||||
|
self.tx.points.send(self.points.clone().into()).unwrap();
|
||||||
|
last = Utc::now().timestamp();
|
||||||
|
info!(target: "worker", "Reloaded points");
|
||||||
|
},
|
||||||
|
_ = sleep(self.cache_age - (now - self.last_refresh)) => self.fetch(&db).await.unwrap(),
|
||||||
|
_ = sleep(self.interval - (now - self.last_check)) => {
|
||||||
|
let mut changes = false;
|
||||||
|
let now = Utc::now().timestamp();
|
||||||
|
let new_width = *self.width.borrow() * 60; // TODO it's in minutes somewhere...
|
||||||
|
|
||||||
|
// fetch previous points
|
||||||
|
if new_width != width {
|
||||||
|
let mut previous_points = entities::points::Entity::find()
|
||||||
|
.filter(
|
||||||
|
Condition::all()
|
||||||
|
.add(entities::points::Column::X.gte(now - new_width))
|
||||||
|
.add(entities::points::Column::X.lte(now - width))
|
||||||
|
)
|
||||||
|
.all(&db)
|
||||||
|
.await.unwrap();
|
||||||
|
info!(target: "worker", "Fetched {} previous points", previous_points.len());
|
||||||
|
previous_points.reverse(); // TODO wasteful!
|
||||||
|
for p in previous_points {
|
||||||
|
self.points.push_front(p);
|
||||||
|
changes = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch new points
|
||||||
|
let new_points = entities::points::Entity::find()
|
||||||
|
.filter(
|
||||||
|
Condition::all()
|
||||||
|
.add(entities::points::Column::X.gte(last as f64))
|
||||||
|
.add(entities::points::Column::X.lte(now as f64))
|
||||||
|
)
|
||||||
|
.all(&db)
|
||||||
|
.await.unwrap();
|
||||||
|
info!(target: "worker", "Fetched {} new points", new_points.len());
|
||||||
|
|
||||||
|
for p in new_points {
|
||||||
|
self.points.push_back(p);
|
||||||
|
changes = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove old points
|
||||||
|
while let Some(p) = self.points.get(0) {
|
||||||
|
if (p.x as i64) >= now - (*self.width.borrow() * 60) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
self.points.pop_front();
|
||||||
|
changes = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update
|
||||||
|
last = now;
|
||||||
|
width = new_width;
|
||||||
|
self.last_check = now;
|
||||||
|
if changes {
|
||||||
|
self.tx.points.send(self.points.clone().into()).unwrap();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum BackgroundAction {
|
||||||
|
UpdateAllPanels { panels: Vec<entities::panels::Model> },
|
||||||
|
// UpdatePanel { panel : entities::panels::ActiveModel },
|
||||||
|
// UpdateSource { source: entities::sources::ActiveModel },
|
||||||
|
// UpdateMetric { metric: entities::metrics::ActiveModel },
|
||||||
|
}
|
Loading…
Reference in a new issue