mirror of
https://git.alemi.dev/dashboard.git
synced 2024-11-22 23:44:55 +01:00
chore: moved most of the UI in separate modules
Some things still need to be better thought and designed: the confirmation popups for example are pretty jank and super specific!
This commit is contained in:
parent
7b830a6cef
commit
78989daaae
5 changed files with 377 additions and 344 deletions
|
@ -1,3 +1,4 @@
|
||||||
pub mod panel;
|
pub mod panel;
|
||||||
pub mod source;
|
pub mod source;
|
||||||
pub mod metric;
|
pub mod metric;
|
||||||
|
pub mod scaffold;
|
||||||
|
|
|
@ -1,14 +1,87 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use chrono::{Local, Utc};
|
use chrono::{Local, Utc};
|
||||||
use eframe::{egui::{
|
use eframe::{egui::{
|
||||||
plot::{Corner, GridMark, Legend, Line, Plot, Values},
|
plot::{Corner, GridMark, Legend, Line, Plot, Values},
|
||||||
DragValue, Layout, Ui, Slider, TextEdit,
|
DragValue, Layout, Ui, Slider, TextEdit, ScrollArea, collapsing_header::CollapsingState, Context,
|
||||||
}, emath::Vec2};
|
}, emath::Vec2};
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
use crate::app::{
|
use crate::app::{
|
||||||
data::source::{Panel, Metric},
|
data::source::{Panel, Metric},
|
||||||
util::timestamp_to_str,
|
util::timestamp_to_str, App,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub fn main_content(app: &mut App, ctx: &Context, ui: &mut Ui) {
|
||||||
|
let mut to_swap: Option<usize> = None;
|
||||||
|
let mut to_delete: Option<usize> = None;
|
||||||
|
ScrollArea::vertical().show(ui, |ui| {
|
||||||
|
let mut panels = app.data.panels.write().expect("Panels RwLock poisoned"); // TODO only lock as write when editing
|
||||||
|
let panels_count = panels.len();
|
||||||
|
let metrics = app.data.metrics.read().expect("Metrics RwLock poisoned"); // TODO only lock as write when editing
|
||||||
|
for (index, panel) in panels.iter_mut().enumerate() {
|
||||||
|
if index > 0 {
|
||||||
|
ui.separator();
|
||||||
|
}
|
||||||
|
CollapsingState::load_with_default_open(
|
||||||
|
ctx,
|
||||||
|
ui.make_persistent_id(format!("panel-{}-compressable", panel.id)),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.show_header(ui, |ui| {
|
||||||
|
if app.edit {
|
||||||
|
if ui.small_button(" + ").clicked() {
|
||||||
|
if index > 0 {
|
||||||
|
to_swap = Some(index); // TODO kinda jank but is there a better way?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ui.small_button(" − ").clicked() {
|
||||||
|
if index < panels_count - 1 {
|
||||||
|
to_swap = Some(index + 1); // TODO kinda jank but is there a better way?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ui.small_button(" × ").clicked() {
|
||||||
|
to_delete = Some(index); // TODO kinda jank but is there a better way?
|
||||||
|
}
|
||||||
|
ui.separator();
|
||||||
|
}
|
||||||
|
panel_title_ui(ui, panel, app.edit);
|
||||||
|
})
|
||||||
|
.body(|ui| panel_body_ui(ui, panel, &metrics));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if let Some(i) = to_delete {
|
||||||
|
// TODO can this be done in background? idk
|
||||||
|
let mut panels = app.data.panels.write().expect("Panels RwLock poisoned");
|
||||||
|
if let Err(e) = app
|
||||||
|
.data
|
||||||
|
.storage
|
||||||
|
.lock()
|
||||||
|
.expect("Storage Mutex poisoned")
|
||||||
|
.delete_panel(panels[i].id)
|
||||||
|
{
|
||||||
|
error!(target: "ui", "Could not delete panel : {:?}", e);
|
||||||
|
} else {
|
||||||
|
for metric in app
|
||||||
|
.data
|
||||||
|
.metrics
|
||||||
|
.write()
|
||||||
|
.expect("Sources RwLock poisoned")
|
||||||
|
.iter_mut()
|
||||||
|
{
|
||||||
|
if metric.panel_id == panels[i].id {
|
||||||
|
metric.panel_id = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panels.remove(i);
|
||||||
|
}
|
||||||
|
} else if let Some(i) = to_swap {
|
||||||
|
// TODO can this be done in background? idk
|
||||||
|
let mut panels = app.data.panels.write().expect("Panels RwLock poisoned");
|
||||||
|
panels.swap(i - 1, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn panel_edit_inline_ui(ui: &mut Ui, panel: &mut Panel) {
|
pub fn panel_edit_inline_ui(ui: &mut Ui, panel: &mut Panel) {
|
||||||
TextEdit::singleline(&mut panel.name)
|
TextEdit::singleline(&mut panel.name)
|
||||||
.hint_text("name")
|
.hint_text("name")
|
||||||
|
@ -61,6 +134,7 @@ pub fn panel_title_ui(ui: &mut Ui, panel: &mut Panel, edit: bool) { // TODO make
|
||||||
.prefix("x")
|
.prefix("x")
|
||||||
.clamp_range(1..=1000), // TODO allow to average larger spans maybe?
|
.clamp_range(1..=1000), // TODO allow to average larger spans maybe?
|
||||||
);
|
);
|
||||||
|
ui.checkbox(&mut panel.average, "avg");
|
||||||
}
|
}
|
||||||
ui.toggle_value(&mut panel.reduce, "reduce");
|
ui.toggle_value(&mut panel.reduce, "reduce");
|
||||||
});
|
});
|
||||||
|
|
141
src/app/gui/scaffold.rs
Normal file
141
src/app/gui/scaffold.rs
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use eframe::{Frame, egui::{collapsing_header::CollapsingState, Context, Ui, Layout, ScrollArea, global_dark_light_mode_switch}, emath::Align};
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
|
use crate::app::{data::ApplicationState, util::human_size, App, worker::native_save};
|
||||||
|
|
||||||
|
use super::panel::panel_edit_inline_ui;
|
||||||
|
|
||||||
|
// TODO make this not super specific!
|
||||||
|
pub fn confirmation_popup_delete_metric(app: &mut App, ui: &mut Ui, metric_index: usize) {
|
||||||
|
ui.heading("Are you sure you want to delete this metric?");
|
||||||
|
ui.label("This will remove all its metrics and delete all points from archive. This action CANNOT BE UNDONE!");
|
||||||
|
ui.with_layout(Layout::top_down(Align::RIGHT), |ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if ui.button("yes").clicked() {
|
||||||
|
let store = app.data.storage.lock().expect("Storage Mutex poisoned");
|
||||||
|
let mut metrics = app.data.metrics.write().expect("Metrics RwLock poisoned");
|
||||||
|
store.delete_metric(metrics[metric_index].id).expect("Failed deleting metric");
|
||||||
|
store.delete_values(metrics[metric_index].id).expect("Failed deleting values");
|
||||||
|
metrics.remove(metric_index);
|
||||||
|
app.deleting_metric = None;
|
||||||
|
}
|
||||||
|
if ui.button(" no ").clicked() {
|
||||||
|
app.deleting_metric = None;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO make this not super specific!
|
||||||
|
pub fn confirmation_popup_delete_source(app: &mut App, ui: &mut Ui, source_index: usize) {
|
||||||
|
ui.heading("Are you sure you want to delete this source?");
|
||||||
|
ui.label("This will remove all its metrics and delete all points from archive. This action CANNOT BE UNDONE!");
|
||||||
|
ui.with_layout(Layout::top_down(Align::RIGHT), |ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if ui.button("YEAH").clicked() {
|
||||||
|
let store = app.data.storage.lock().expect("Storage Mutex poisoned");
|
||||||
|
let mut sources = app.data.sources.write().expect("sources RwLock poisoned");
|
||||||
|
let mut metrics = app.data.metrics.write().expect("Metrics RwLock poisoned");
|
||||||
|
let mut to_remove = Vec::new();
|
||||||
|
for j in 0..metrics.len() {
|
||||||
|
if metrics[j].source_id == app.input_source.id {
|
||||||
|
store.delete_values(metrics[j].id).expect("Failed deleting values");
|
||||||
|
store.delete_metric(metrics[j].id).expect("Failed deleting Metric");
|
||||||
|
to_remove.push(j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for index in to_remove {
|
||||||
|
metrics.remove(index);
|
||||||
|
}
|
||||||
|
store.delete_source(sources[source_index].id).expect("Failed deleting source");
|
||||||
|
sources.remove(source_index);
|
||||||
|
app.deleting_source = None;
|
||||||
|
}
|
||||||
|
if ui.button(" NO WAY ").clicked() {
|
||||||
|
app.deleting_source = None;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn header(app: &mut App, ui: &mut Ui, frame: &mut Frame) {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
global_dark_light_mode_switch(ui);
|
||||||
|
ui.heading("dashboard");
|
||||||
|
ui.separator();
|
||||||
|
ui.checkbox(&mut app.sources, "sources");
|
||||||
|
ui.separator();
|
||||||
|
ui.checkbox(&mut app.edit, "edit");
|
||||||
|
if app.edit {
|
||||||
|
if ui.button("save").clicked() {
|
||||||
|
native_save(app.data.clone());
|
||||||
|
app.edit = false;
|
||||||
|
}
|
||||||
|
ui.separator();
|
||||||
|
ui.label("+ panel");
|
||||||
|
panel_edit_inline_ui(ui, &mut app.input_panel);
|
||||||
|
if ui.button("add").clicked() {
|
||||||
|
if let Err(e) = app.data.add_panel(&app.input_panel) {
|
||||||
|
error!(target: "ui", "Failed to add panel: {:?}", e);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
ui.separator();
|
||||||
|
}
|
||||||
|
ui.with_layout(Layout::top_down(Align::RIGHT), |ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if ui.small_button("×").clicked() {
|
||||||
|
frame.quit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn footer(data: Arc<ApplicationState>, ctx: &Context, ui: &mut Ui) {
|
||||||
|
CollapsingState::load_with_default_open(
|
||||||
|
ctx,
|
||||||
|
ui.make_persistent_id("footer-logs"),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.show_header(ui, |ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.separator();
|
||||||
|
ui.label(data.file_path.to_str().unwrap()); // TODO maybe calculate it just once?
|
||||||
|
ui.separator();
|
||||||
|
ui.label(human_size(
|
||||||
|
*data
|
||||||
|
.file_size
|
||||||
|
.read()
|
||||||
|
.expect("Filesize RwLock poisoned"),
|
||||||
|
));
|
||||||
|
ui.with_layout(Layout::top_down(Align::RIGHT), |ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label(format!(
|
||||||
|
"v{}-{}",
|
||||||
|
env!("CARGO_PKG_VERSION"),
|
||||||
|
git_version::git_version!()
|
||||||
|
));
|
||||||
|
ui.separator();
|
||||||
|
ui.hyperlink_to("<me@alemi.dev>", "mailto:me@alemi.dev");
|
||||||
|
ui.label("alemi");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.body(|ui| {
|
||||||
|
ui.set_height(200.0);
|
||||||
|
ScrollArea::vertical().show(ui, |ui| {
|
||||||
|
let msgs = data
|
||||||
|
.diagnostics
|
||||||
|
.read()
|
||||||
|
.expect("Diagnostics RwLock poisoned");
|
||||||
|
ui.separator();
|
||||||
|
for msg in msgs.iter() {
|
||||||
|
ui.label(msg);
|
||||||
|
}
|
||||||
|
ui.separator();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,6 +1,145 @@
|
||||||
use eframe::{egui::{Ui, TextEdit, DragValue, Checkbox}};
|
use eframe::{egui::{Ui, TextEdit, DragValue, Checkbox, ScrollArea, Context, Layout}, emath::Align};
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
use crate::app::data::source::Source;
|
use crate::app::{data::source::{Source, Metric}, App};
|
||||||
|
|
||||||
|
use super::metric::{metric_edit_ui, metric_display_ui};
|
||||||
|
|
||||||
|
pub fn source_panel(app: &mut App, ctx: &Context, ui: &mut Ui) {
|
||||||
|
let mut to_swap: Option<usize> = None;
|
||||||
|
// let mut to_delete: Option<usize> = None;
|
||||||
|
let panels = app.data.panels.read().expect("Panels RwLock poisoned");
|
||||||
|
let panel_width = ui.available_width();
|
||||||
|
ScrollArea::both().max_width(panel_width).show(ui, |ui| {
|
||||||
|
// TODO only vertical!
|
||||||
|
{
|
||||||
|
let mut sources =
|
||||||
|
app.data.sources.write().expect("Sources RwLock poisoned");
|
||||||
|
let sources_count = sources.len();
|
||||||
|
ui.heading("Sources");
|
||||||
|
ui.separator();
|
||||||
|
for (i, source) in sources.iter_mut().enumerate() {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if app.edit {
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
ui.add_space(10.0);
|
||||||
|
if ui.small_button("+").clicked() {
|
||||||
|
if i > 0 {
|
||||||
|
to_swap = Some(i); // TODO kinda jank but is there a better way?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ui.small_button("−").clicked() {
|
||||||
|
if i < sources_count - 1 {
|
||||||
|
to_swap = Some(i + 1); // TODO kinda jank but is there a better way?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
let remaining_width = ui.available_width();
|
||||||
|
if app.edit {
|
||||||
|
ui.group(|ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
source_edit_ui(
|
||||||
|
ui,
|
||||||
|
source,
|
||||||
|
remaining_width - 34.0,
|
||||||
|
);
|
||||||
|
if ui.small_button("×").clicked() {
|
||||||
|
app.deleting_metric = None;
|
||||||
|
app.deleting_source = Some(i);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
for (j, metric) in app.data.metrics.write().expect("Metrics RwLock poisoned").iter_mut().enumerate() {
|
||||||
|
if metric.source_id == source.id {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
metric_edit_ui(ui, metric, Some(&panels), remaining_width - 31.0);
|
||||||
|
if ui.small_button("×").clicked() {
|
||||||
|
app.deleting_source = None;
|
||||||
|
app.deleting_metric = Some(j);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
metric_edit_ui(
|
||||||
|
ui,
|
||||||
|
&mut app.input_metric,
|
||||||
|
None,
|
||||||
|
remaining_width - 30.0,
|
||||||
|
);
|
||||||
|
if ui.small_button(" + ").clicked() { // TODO find a better
|
||||||
|
if let Err(e) = app
|
||||||
|
.data
|
||||||
|
.add_metric(&app.input_metric, source)
|
||||||
|
{
|
||||||
|
error!(target: "ui", "Error adding metric : {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ui.add_space(1.0); // DAMN!
|
||||||
|
if ui.small_button("×").clicked() {
|
||||||
|
app.input_metric = Metric::default();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let metrics =
|
||||||
|
app.data.metrics.read().expect("Metrics RwLock poisoned");
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ui.separator();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if app.edit {
|
||||||
|
ui.separator();
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.heading("new source");
|
||||||
|
ui.with_layout(Layout::top_down(Align::RIGHT), |ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if ui.button("add").clicked() {
|
||||||
|
if let Err(e) = app.data.add_source(&app.input_source) {
|
||||||
|
error!(target: "ui", "Error adding source : {:?}", e);
|
||||||
|
} else {
|
||||||
|
app.input_source.id += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ui.toggle_value(&mut app.padding, "#");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
source_edit_ui(
|
||||||
|
ui,
|
||||||
|
&mut app.input_source,
|
||||||
|
panel_width - 10.0,
|
||||||
|
);
|
||||||
|
ui.add_space(5.0);
|
||||||
|
if app.padding {
|
||||||
|
ui.add_space(300.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//if let Some(i) = to_delete {
|
||||||
|
// // TODO can this be done in background? idk
|
||||||
|
// let mut panels = app.data.panels.write().expect("Panels RwLock poisoned");
|
||||||
|
// panels.remove(i);
|
||||||
|
// } else
|
||||||
|
if let Some(i) = to_swap {
|
||||||
|
// TODO can this be done in background? idk
|
||||||
|
let mut sources = app.data.sources.write().expect("Sources RwLock poisoned");
|
||||||
|
sources.swap(i - 1, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn source_display_ui(ui: &mut Ui, source: &mut Source, _width: f32) {
|
pub fn source_display_ui(ui: &mut Ui, source: &mut Source, _width: f32) {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
|
|
358
src/app/mod.rs
358
src/app/mod.rs
|
@ -4,22 +4,16 @@ pub mod util;
|
||||||
pub mod worker;
|
pub mod worker;
|
||||||
|
|
||||||
use eframe::egui::Window;
|
use eframe::egui::Window;
|
||||||
use eframe::egui::{
|
use eframe::egui::{CentralPanel, Context, SidePanel, TopBottomPanel};
|
||||||
collapsing_header::CollapsingState, global_dark_light_mode_switch, CentralPanel, Context,
|
|
||||||
Layout, ScrollArea, SidePanel, TopBottomPanel,
|
|
||||||
};
|
|
||||||
use eframe::emath::{Align, Pos2};
|
|
||||||
use std::ops::Index;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tracing::error;
|
|
||||||
|
|
||||||
use self::data::source::{Metric, Panel, Source};
|
use self::data::source::{Metric, Panel, Source};
|
||||||
use self::data::ApplicationState;
|
use self::data::ApplicationState;
|
||||||
use self::gui::metric::{metric_edit_ui, metric_display_ui};
|
use self::gui::panel::main_content;
|
||||||
use self::gui::panel::{panel_body_ui, panel_edit_inline_ui, panel_title_ui};
|
use self::gui::scaffold::{
|
||||||
use self::gui::source::{source_display_ui, source_edit_ui};
|
confirmation_popup_delete_metric, confirmation_popup_delete_source, footer, header,
|
||||||
use self::util::human_size;
|
};
|
||||||
use self::worker::native_save;
|
use self::gui::source::source_panel;
|
||||||
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
data: Arc<ApplicationState>,
|
data: Arc<ApplicationState>,
|
||||||
|
@ -51,348 +45,32 @@ impl App {
|
||||||
|
|
||||||
impl eframe::App for App {
|
impl eframe::App for App {
|
||||||
fn update(&mut self, ctx: &Context, frame: &mut eframe::Frame) {
|
fn update(&mut self, ctx: &Context, frame: &mut eframe::Frame) {
|
||||||
TopBottomPanel::top("heading").show(ctx, |ui| {
|
TopBottomPanel::top("header").show(ctx, |ui| {
|
||||||
ui.horizontal(|ui| {
|
header(self, ui, frame);
|
||||||
global_dark_light_mode_switch(ui);
|
|
||||||
ui.heading("dashboard");
|
|
||||||
ui.separator();
|
|
||||||
ui.checkbox(&mut self.sources, "sources");
|
|
||||||
ui.separator();
|
|
||||||
ui.checkbox(&mut self.edit, "edit");
|
|
||||||
if self.edit {
|
|
||||||
if ui.button("save").clicked() {
|
|
||||||
native_save(self.data.clone());
|
|
||||||
self.edit = false;
|
|
||||||
}
|
|
||||||
ui.separator();
|
|
||||||
ui.label("+ panel");
|
|
||||||
panel_edit_inline_ui(ui, &mut self.input_panel);
|
|
||||||
if ui.button("add").clicked() {
|
|
||||||
if let Err(e) = self.data.add_panel(&self.input_panel) {
|
|
||||||
error!(target: "ui", "Failed to add panel: {:?}", e);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
ui.separator();
|
|
||||||
}
|
|
||||||
ui.with_layout(Layout::top_down(Align::RIGHT), |ui| {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
if ui.small_button("×").clicked() {
|
|
||||||
frame.quit();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
TopBottomPanel::bottom("footer").show(ctx, |ui| {
|
TopBottomPanel::bottom("footer").show(ctx, |ui| {
|
||||||
CollapsingState::load_with_default_open(
|
footer(self.data.clone(), ctx, ui);
|
||||||
ctx,
|
|
||||||
ui.make_persistent_id("footer-logs"),
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.show_header(ui, |ui| {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.separator();
|
|
||||||
ui.label(self.data.file_path.to_str().unwrap()); // TODO maybe calculate it just once?
|
|
||||||
ui.separator();
|
|
||||||
ui.label(human_size(
|
|
||||||
*self
|
|
||||||
.data
|
|
||||||
.file_size
|
|
||||||
.read()
|
|
||||||
.expect("Filesize RwLock poisoned"),
|
|
||||||
));
|
|
||||||
ui.with_layout(Layout::top_down(Align::RIGHT), |ui| {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.label(format!(
|
|
||||||
"v{}-{}",
|
|
||||||
env!("CARGO_PKG_VERSION"),
|
|
||||||
git_version::git_version!()
|
|
||||||
));
|
|
||||||
ui.separator();
|
|
||||||
ui.hyperlink_to("<me@alemi.dev>", "mailto:me@alemi.dev");
|
|
||||||
ui.label("alemi");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.body(|ui| {
|
|
||||||
ui.set_height(200.0);
|
|
||||||
ScrollArea::vertical().show(ui, |ui| {
|
|
||||||
let msgs = self
|
|
||||||
.data
|
|
||||||
.diagnostics
|
|
||||||
.read()
|
|
||||||
.expect("Diagnostics RwLock poisoned");
|
|
||||||
ui.separator();
|
|
||||||
for msg in msgs.iter() {
|
|
||||||
ui.label(msg);
|
|
||||||
}
|
|
||||||
ui.separator();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(index) = self.deleting_metric {
|
if let Some(index) = self.deleting_metric {
|
||||||
Window::new(format!("Delete Metric #{}", index))
|
Window::new(format!("Delete Metric #{}", index))
|
||||||
.show(ctx, |ui| {
|
.show(ctx, |ui| confirmation_popup_delete_metric(self, ui, index));
|
||||||
ui.heading("Are you sure you want to delete this metric?");
|
|
||||||
ui.label("This will remove all its metrics and delete all points from archive. This action CANNOT BE UNDONE!");
|
|
||||||
ui.with_layout(Layout::top_down(Align::RIGHT), |ui| {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
if ui.button("yes").clicked() {
|
|
||||||
let store = self.data.storage.lock().expect("Storage Mutex poisoned");
|
|
||||||
let mut metrics = self.data.metrics.write().expect("Metrics RwLock poisoned");
|
|
||||||
store.delete_metric(metrics[index].id).expect("Failed deleting metric");
|
|
||||||
store.delete_values(metrics[index].id).expect("Failed deleting values");
|
|
||||||
metrics.remove(index);
|
|
||||||
self.deleting_metric = None;
|
|
||||||
}
|
|
||||||
if ui.button(" no ").clicked() {
|
|
||||||
self.deleting_metric = None;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if let Some(index) = self.deleting_source {
|
if let Some(index) = self.deleting_source {
|
||||||
Window::new(format!("Delete Source #{}", index)).show(ctx, |ui| {
|
Window::new(format!("Delete Source #{}", index))
|
||||||
ui.heading("Are you sure you want to delete this source?");
|
.show(ctx, |ui| confirmation_popup_delete_source(self, ui, index));
|
||||||
ui.label("This will remove all its metrics and delete all points from archive. This action CANNOT BE UNDONE!");
|
|
||||||
ui.with_layout(Layout::top_down(Align::RIGHT), |ui| {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
if ui.button("YEAH").clicked() {
|
|
||||||
let store = self.data.storage.lock().expect("Storage Mutex poisoned");
|
|
||||||
let mut sources = self.data.sources.write().expect("sources RwLock poisoned");
|
|
||||||
let mut metrics = self.data.metrics.write().expect("Metrics RwLock poisoned");
|
|
||||||
let mut to_remove = Vec::new();
|
|
||||||
for j in 0..metrics.len() {
|
|
||||||
if metrics[j].source_id == self.input_source.id {
|
|
||||||
store.delete_values(metrics[j].id).expect("Failed deleting values");
|
|
||||||
store.delete_metric(metrics[j].id).expect("Failed deleting Metric");
|
|
||||||
to_remove.push(j);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for index in to_remove {
|
|
||||||
metrics.remove(index);
|
|
||||||
}
|
|
||||||
store.delete_source(sources[index].id).expect("Failed deleting source");
|
|
||||||
sources.remove(index);
|
|
||||||
self.deleting_source = None;
|
|
||||||
}
|
|
||||||
if ui.button(" NO WAY ").clicked() {
|
|
||||||
self.deleting_source = None;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.sources {
|
if self.sources {
|
||||||
let mut to_swap: Option<usize> = None;
|
|
||||||
// let mut to_delete: Option<usize> = None;
|
|
||||||
SidePanel::left("sources-bar")
|
SidePanel::left("sources-bar")
|
||||||
.width_range(280.0..=800.0)
|
.width_range(280.0..=800.0)
|
||||||
.default_width(330.0)
|
.default_width(330.0)
|
||||||
.show(ctx, |ui| {
|
.show(ctx, |ui| source_panel(self, ctx, ui));
|
||||||
let panels = self.data.panels.read().expect("Panels RwLock poisoned");
|
|
||||||
let panel_width = ui.available_width();
|
|
||||||
ScrollArea::both().max_width(panel_width).show(ui, |ui| {
|
|
||||||
// TODO only vertical!
|
|
||||||
{
|
|
||||||
let mut sources =
|
|
||||||
self.data.sources.write().expect("Sources RwLock poisoned");
|
|
||||||
let sources_count = sources.len();
|
|
||||||
ui.heading("Sources");
|
|
||||||
ui.separator();
|
|
||||||
for (i, source) in sources.iter_mut().enumerate() {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
if self.edit {
|
|
||||||
ui.vertical(|ui| {
|
|
||||||
ui.add_space(10.0);
|
|
||||||
if ui.small_button("+").clicked() {
|
|
||||||
if i > 0 {
|
|
||||||
to_swap = Some(i); // TODO kinda jank but is there a better way?
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if ui.small_button("−").clicked() {
|
|
||||||
if i < sources_count - 1 {
|
|
||||||
to_swap = Some(i + 1); // TODO kinda jank but is there a better way?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
ui.vertical(|ui| {
|
|
||||||
let remaining_width = ui.available_width();
|
|
||||||
if self.edit {
|
|
||||||
ui.group(|ui| {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
source_edit_ui(
|
|
||||||
ui,
|
|
||||||
source,
|
|
||||||
remaining_width - 34.0,
|
|
||||||
);
|
|
||||||
if ui.small_button("×").clicked() {
|
|
||||||
self.deleting_metric = None;
|
|
||||||
self.deleting_source = Some(i);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
for (j, metric) in self.data.metrics.write().expect("Metrics RwLock poisoned").iter_mut().enumerate() {
|
|
||||||
if metric.source_id == source.id {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
metric_edit_ui(ui, metric, Some(&panels), remaining_width - 31.0);
|
|
||||||
if ui.small_button("×").clicked() {
|
|
||||||
self.deleting_source = None;
|
|
||||||
self.deleting_metric = Some(j);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
metric_edit_ui(
|
|
||||||
ui,
|
|
||||||
&mut self.input_metric,
|
|
||||||
None,
|
|
||||||
remaining_width - 30.0,
|
|
||||||
);
|
|
||||||
if ui.small_button(" + ").clicked() { // TODO find a better
|
|
||||||
if let Err(e) = self
|
|
||||||
.data
|
|
||||||
.add_metric(&self.input_metric, source)
|
|
||||||
{
|
|
||||||
error!(target: "ui", "Error adding metric : {:?}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ui.add_space(1.0); // DAMN!
|
|
||||||
if ui.small_button("×").clicked() {
|
|
||||||
self.input_metric = Metric::default();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
let metrics =
|
|
||||||
self.data.metrics.read().expect("Metrics RwLock poisoned");
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ui.separator();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if self.edit {
|
|
||||||
ui.separator();
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.heading("new source");
|
|
||||||
ui.with_layout(Layout::top_down(Align::RIGHT), |ui| {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
if ui.button("add").clicked() {
|
|
||||||
if let Err(e) = self.data.add_source(&self.input_source) {
|
|
||||||
error!(target: "ui", "Error adding source : {:?}", e);
|
|
||||||
} else {
|
|
||||||
self.input_source.id += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ui.toggle_value(&mut self.padding, "#");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
source_edit_ui(
|
|
||||||
ui,
|
|
||||||
&mut self.input_source,
|
|
||||||
panel_width - 10.0,
|
|
||||||
);
|
|
||||||
ui.add_space(5.0);
|
|
||||||
if self.padding {
|
|
||||||
ui.add_space(300.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
//if let Some(i) = to_delete {
|
|
||||||
// // TODO can this be done in background? idk
|
|
||||||
// let mut panels = self.data.panels.write().expect("Panels RwLock poisoned");
|
|
||||||
// panels.remove(i);
|
|
||||||
// } else
|
|
||||||
if let Some(i) = to_swap {
|
|
||||||
// TODO can this be done in background? idk
|
|
||||||
let mut sources = self.data.sources.write().expect("Sources RwLock poisoned");
|
|
||||||
sources.swap(i - 1, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mut to_swap: Option<usize> = None;
|
|
||||||
let mut to_delete: Option<usize> = None;
|
|
||||||
CentralPanel::default().show(ctx, |ui| {
|
CentralPanel::default().show(ctx, |ui| {
|
||||||
ScrollArea::vertical().show(ui, |ui| {
|
main_content(self, ctx, ui);
|
||||||
let mut panels = self.data.panels.write().expect("Panels RwLock poisoned"); // TODO only lock as write when editing
|
|
||||||
let panels_count = panels.len();
|
|
||||||
let metrics = self.data.metrics.read().expect("Metrics RwLock poisoned"); // TODO only lock as write when editing
|
|
||||||
for (index, panel) in panels.iter_mut().enumerate() {
|
|
||||||
if index > 0 {
|
|
||||||
ui.separator();
|
|
||||||
}
|
|
||||||
CollapsingState::load_with_default_open(
|
|
||||||
ctx,
|
|
||||||
ui.make_persistent_id(format!("panel-{}-compressable", panel.id)),
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.show_header(ui, |ui| {
|
|
||||||
if self.edit {
|
|
||||||
if ui.small_button(" + ").clicked() {
|
|
||||||
if index > 0 {
|
|
||||||
to_swap = Some(index); // TODO kinda jank but is there a better way?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ui.small_button(" − ").clicked() {
|
|
||||||
if index < panels_count - 1 {
|
|
||||||
to_swap = Some(index + 1); // TODO kinda jank but is there a better way?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ui.small_button(" × ").clicked() {
|
|
||||||
to_delete = Some(index); // TODO kinda jank but is there a better way?
|
|
||||||
}
|
|
||||||
ui.separator();
|
|
||||||
}
|
|
||||||
panel_title_ui(ui, panel, self.edit);
|
|
||||||
})
|
|
||||||
.body(|ui| panel_body_ui(ui, panel, &metrics));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
if let Some(i) = to_delete {
|
|
||||||
// TODO can this be done in background? idk
|
|
||||||
let mut panels = self.data.panels.write().expect("Panels RwLock poisoned");
|
|
||||||
if let Err(e) = self
|
|
||||||
.data
|
|
||||||
.storage
|
|
||||||
.lock()
|
|
||||||
.expect("Storage Mutex poisoned")
|
|
||||||
.delete_panel(panels[i].id)
|
|
||||||
{
|
|
||||||
error!(target: "ui", "Could not delete panel : {:?}", e);
|
|
||||||
} else {
|
|
||||||
for metric in self
|
|
||||||
.data
|
|
||||||
.metrics
|
|
||||||
.write()
|
|
||||||
.expect("Sources RwLock poisoned")
|
|
||||||
.iter_mut()
|
|
||||||
{
|
|
||||||
if metric.panel_id == panels[i].id {
|
|
||||||
metric.panel_id = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
panels.remove(i);
|
|
||||||
}
|
|
||||||
} else if let Some(i) = to_swap {
|
|
||||||
// TODO can this be done in background? idk
|
|
||||||
let mut panels = self.data.panels.write().expect("Panels RwLock poisoned");
|
|
||||||
panels.swap(i - 1, i);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue