mirror of
https://git.alemi.dev/dashboard.git
synced 2024-11-12 19:29:18 +01:00
feat: added relations, added window uis to edit them all
This commit is contained in:
parent
76772465a3
commit
32d68691a1
16 changed files with 435 additions and 78 deletions
|
@ -2,6 +2,8 @@ pub use sea_orm_migration::prelude::*;
|
|||
|
||||
mod m20220101_000001_create_table;
|
||||
mod m20221030_192706_add_last_update;
|
||||
mod m20221102_232244_add_join_table;
|
||||
mod m20221102_232858_remove_unused_columns;
|
||||
|
||||
pub struct Migrator;
|
||||
|
||||
|
@ -9,8 +11,10 @@ pub struct Migrator;
|
|||
impl MigratorTrait for Migrator {
|
||||
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
|
||||
vec![
|
||||
Box::new(m20220101_000001_create_table::Migration),
|
||||
Box::new(m20221030_192706_add_last_update::Migration),
|
||||
]
|
||||
Box::new(m20220101_000001_create_table::Migration),
|
||||
Box::new(m20221030_192706_add_last_update::Migration),
|
||||
Box::new(m20221102_232244_add_join_table::Migration),
|
||||
Box::new(m20221102_232858_remove_unused_columns::Migration),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
40
migration/src/m20221102_232244_add_join_table.rs
Normal file
40
migration/src/m20221102_232244_add_join_table.rs
Normal file
|
@ -0,0 +1,40 @@
|
|||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(PanelMetric::Table)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(PanelMetric::Id)
|
||||
.big_integer()
|
||||
.not_null()
|
||||
.auto_increment()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(PanelMetric::PanelId).big_integer().not_null())
|
||||
.col(ColumnDef::new(PanelMetric::MetricId).big_integer().not_null())
|
||||
.to_owned(),
|
||||
).await
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(PanelMetric::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Iden)]
|
||||
enum PanelMetric {
|
||||
Table,
|
||||
Id,
|
||||
PanelId,
|
||||
MetricId,
|
||||
}
|
85
migration/src/m20221102_232858_remove_unused_columns.rs
Normal file
85
migration/src/m20221102_232858_remove_unused_columns.rs
Normal file
|
@ -0,0 +1,85 @@
|
|||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager.
|
||||
alter_table(
|
||||
Table::alter()
|
||||
.table(Panels::Table)
|
||||
.drop_column(Panels::Width)
|
||||
.drop_column(Panels::LimitView)
|
||||
.drop_column(Panels::ShiftView)
|
||||
.to_owned()
|
||||
)
|
||||
.await?;
|
||||
manager.
|
||||
alter_table(
|
||||
Table::alter()
|
||||
.table(Metrics::Table)
|
||||
.drop_column(Metrics::PanelId)
|
||||
.to_owned()
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(Panels::Table)
|
||||
.add_column(
|
||||
ColumnDef::new(Panels::Width)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(100)
|
||||
)
|
||||
.add_column(
|
||||
ColumnDef::new(Panels::LimitView)
|
||||
.boolean()
|
||||
.not_null()
|
||||
.default(true)
|
||||
)
|
||||
.add_column(
|
||||
ColumnDef::new(Panels::ShiftView)
|
||||
.boolean()
|
||||
.not_null()
|
||||
.default(false)
|
||||
)
|
||||
.to_owned()
|
||||
)
|
||||
.await?;
|
||||
manager
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(Metrics::Table)
|
||||
.add_column(
|
||||
ColumnDef::new(Metrics::PanelId)
|
||||
.big_integer()
|
||||
.not_null()
|
||||
.default(0)
|
||||
)
|
||||
.to_owned()
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Iden)]
|
||||
enum Panels {
|
||||
Table,
|
||||
Width,
|
||||
LimitView,
|
||||
ShiftView,
|
||||
}
|
||||
|
||||
#[derive(Iden)]
|
||||
enum Metrics {
|
||||
Table,
|
||||
PanelId,
|
||||
}
|
|
@ -15,17 +15,43 @@ pub struct Model {
|
|||
pub source_id: i64,
|
||||
pub query_x: String,
|
||||
pub query_y: String,
|
||||
pub panel_id: i64,
|
||||
pub color: i32,
|
||||
pub position: i32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {}
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::sources::Entity",
|
||||
from = "Column::SourceId",
|
||||
to = "super::sources::Column::Id"
|
||||
)]
|
||||
Source,
|
||||
|
||||
#[sea_orm(has_many = "super::points::Entity")]
|
||||
Point,
|
||||
}
|
||||
|
||||
impl Related<super::sources::Entity> for Entity {
|
||||
fn to() -> RelationDef { Relation::Source.def() }
|
||||
}
|
||||
|
||||
impl Related<super::points::Entity> for Entity {
|
||||
fn to() -> RelationDef { Relation::Point.def() }
|
||||
}
|
||||
|
||||
impl Related<super::panels::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
super::panel_metric::Relation::Panel.def()
|
||||
}
|
||||
|
||||
fn via() -> Option<RelationDef> {
|
||||
Some(super::panel_metric::Relation::Metric.def().rev())
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
||||
|
||||
impl Model {
|
||||
pub fn extract(&self, value: &serde_json::Value) -> Result<PlotPoint, FetchError> {
|
||||
let x: f64;
|
||||
|
@ -51,7 +77,6 @@ impl Default for Model {
|
|||
source_id: 0,
|
||||
query_x: "".into(),
|
||||
query_y: "".into(),
|
||||
panel_id: 0,
|
||||
color: 0,
|
||||
position: 0,
|
||||
}
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
pub mod prelude;
|
||||
|
||||
pub mod metrics;
|
||||
pub mod panels;
|
||||
pub mod panel_metric;
|
||||
pub mod metrics;
|
||||
pub mod points;
|
||||
pub mod sources;
|
||||
|
|
29
src/data/entities/panel_metric.rs
Normal file
29
src/data/entities/panel_metric.rs
Normal file
|
@ -0,0 +1,29 @@
|
|||
use sea_orm::entity::prelude::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||
#[sea_orm(table_name = "panel_metric")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = true)]
|
||||
pub id: i64,
|
||||
pub panel_id: i64,
|
||||
pub metric_id: i64,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::panels::Entity",
|
||||
from = "Column::PanelId",
|
||||
to = "super::panels::Column::Id"
|
||||
)]
|
||||
Panel,
|
||||
|
||||
#[sea_orm(
|
||||
belongs_to = "super::metrics::Entity",
|
||||
from = "Column::MetricId",
|
||||
to = "super::metrics::Column::Id"
|
||||
)]
|
||||
Metric,
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
|
@ -12,11 +12,9 @@ pub struct Model {
|
|||
pub view_size: i32,
|
||||
pub timeserie: bool,
|
||||
pub height: i32,
|
||||
pub limit_view: bool,
|
||||
pub position: i32,
|
||||
pub reduce_view: bool,
|
||||
pub view_chunks: i32,
|
||||
pub shift_view: bool,
|
||||
pub view_offset: i32,
|
||||
pub average_view: bool,
|
||||
}
|
||||
|
@ -24,6 +22,16 @@ pub struct Model {
|
|||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {}
|
||||
|
||||
impl Related<super::metrics::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
super::panel_metric::Relation::Metric.def()
|
||||
}
|
||||
|
||||
fn via() -> Option<RelationDef> {
|
||||
Some(super::panel_metric::Relation::Panel.def().rev())
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
||||
impl Default for Model {
|
||||
|
@ -35,11 +43,9 @@ impl Default for Model {
|
|||
view_size: 1000,
|
||||
timeserie: true,
|
||||
height: 100,
|
||||
limit_view: true,
|
||||
position: 0,
|
||||
reduce_view: false,
|
||||
view_chunks: 10,
|
||||
shift_view: false,
|
||||
view_offset: 0,
|
||||
average_view: true,
|
||||
}
|
||||
|
|
|
@ -13,7 +13,18 @@ pub struct Model {
|
|||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {}
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::metrics::Entity",
|
||||
from = "Column::MetricId",
|
||||
to = "super::metrics::Column::Id"
|
||||
)]
|
||||
Metric,
|
||||
}
|
||||
|
||||
impl Related<super::metrics::Entity> for Entity {
|
||||
fn to() -> RelationDef { Relation::Metric.def() }
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
||||
|
|
|
@ -4,3 +4,4 @@ pub use super::metrics::Entity as Metrics;
|
|||
pub use super::panels::Entity as Panels;
|
||||
pub use super::points::Entity as Points;
|
||||
pub use super::sources::Entity as Sources;
|
||||
pub use super::panel_metric::Entity as PanelMetric;
|
||||
|
|
|
@ -15,7 +15,16 @@ pub struct Model {
|
|||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {}
|
||||
pub enum Relation {
|
||||
#[sea_orm(has_many = "super::metrics::Entity")]
|
||||
Metric,
|
||||
}
|
||||
|
||||
impl Related<super::metrics::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Metric.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ use eframe::{egui::{Ui, Layout, Sense, color_picker::show_color_at, ComboBox, Te
|
|||
|
||||
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 (rect, response) = ui.allocate_exact_size(size, Sense::click());
|
||||
if ui.is_rect_visible(rect) {
|
||||
|
@ -19,11 +19,11 @@ fn _color_square(ui: &mut Ui, color:Color32) {
|
|||
|
||||
pub fn _metric_display_ui(ui: &mut Ui, metric: &entities::metrics::Model, _width: f32) {
|
||||
ui.horizontal(|ui| {
|
||||
_color_square(ui, unpack_color(metric.color));
|
||||
color_square(ui, unpack_color(metric.color));
|
||||
ui.label(&metric.name);
|
||||
ui.with_layout(Layout::top_down(Align::RIGHT), |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(format!("panel: {}", metric.panel_id));
|
||||
ui.label("panel: ???");
|
||||
ui.label(format!("y: {}", metric.query_y));
|
||||
// if let Some(query_x) = metric.query_x {
|
||||
// ui.label(format!("x: {}", query_x));
|
||||
|
@ -40,7 +40,8 @@ pub fn metric_edit_ui(ui: &mut Ui, metric: &entities::metrics::Model, panels: Op
|
|||
let mut query_y = metric.query_y.clone();
|
||||
let mut panel_id = 0;
|
||||
ui.horizontal(|ui| {
|
||||
ui.color_edit_button_srgba(&mut unpack_color(metric.color));
|
||||
// ui.color_edit_button_srgba(&mut unpack_color(metric.color));
|
||||
color_square(ui, unpack_color(metric.color));
|
||||
TextEdit::singleline(&mut name)
|
||||
.interactive(false)
|
||||
.desired_width(text_width / 2.0)
|
||||
|
@ -62,7 +63,7 @@ pub fn metric_edit_ui(ui: &mut Ui, metric: &entities::metrics::Model, panels: Op
|
|||
if let Some(panels) = panels {
|
||||
ComboBox::from_id_source(format!("panel-selector-{}", metric.id))
|
||||
.width(60.0)
|
||||
.selected_text(format!("panel: {:02}", metric.panel_id))
|
||||
.selected_text("panel: ???")
|
||||
.show_ui(ui, |ui| {
|
||||
ui.selectable_value(&mut panel_id, -1, "None");
|
||||
for p in panels {
|
||||
|
|
|
@ -17,7 +17,7 @@ use scaffold::{
|
|||
};
|
||||
use source::source_panel;
|
||||
|
||||
use self::scaffold::{footer, popup_edit_ui, EditingModel};
|
||||
use self::scaffold::{footer, EditingModel, popup_edit_ui};
|
||||
|
||||
pub struct App {
|
||||
view: AppStateView,
|
||||
|
@ -94,7 +94,9 @@ impl eframe::App for App {
|
|||
});
|
||||
|
||||
for m in self.editing.iter_mut() {
|
||||
Window::new(m.id_repr()).show(ctx, |ui| popup_edit_ui(ui, m));
|
||||
Window::new(m.id_repr())
|
||||
.default_width(150.0)
|
||||
.show(ctx, |ui| popup_edit_ui(ui, m, &self.view.sources.borrow(), &self.view.metrics.borrow()));
|
||||
}
|
||||
|
||||
if self.sidebar {
|
||||
|
@ -121,7 +123,7 @@ impl eframe::App for App {
|
|||
|
||||
for m in self.editing.iter() {
|
||||
if m.should_fetch() {
|
||||
self.op(m.to_msg());
|
||||
self.op(m.to_msg(self.view.clone())); // TODO cloning is super wasteful
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,8 @@ use crate::util::{timestamp_to_str, unpack_color};
|
|||
use crate::gui::App;
|
||||
use crate::data::entities;
|
||||
|
||||
use super::scaffold::EditingModel;
|
||||
|
||||
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;
|
||||
|
@ -39,9 +41,9 @@ pub fn main_content(app: &mut App, ctx: &Context, ui: &mut Ui) {
|
|||
// to_delete = Some(index); // TODO kinda jank but is there a better way?
|
||||
// }
|
||||
// ui.separator();
|
||||
panel_title_ui(ui, panel, app.edit);
|
||||
panel_title_ui(ui, panel, &mut app.editing, &app.view.metrics.borrow(), &app.view.panel_metric.borrow());
|
||||
})
|
||||
.body(|ui| panel_body_ui(ui, panel, &metrics, &app.view.points.borrow()));
|
||||
.body(|ui| panel_body_ui(ui, panel, &metrics, &app.view.points.borrow(), &app.view.panel_metric.borrow()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -53,10 +55,23 @@ pub fn _panel_edit_inline_ui(_ui: &mut Ui, _panel: &entities::panels::Model) {
|
|||
// .show(ui);
|
||||
}
|
||||
|
||||
pub fn panel_title_ui(ui: &mut Ui, panel: &mut entities::panels::Model, _edit: bool) { // TODO make edit UI in separate func
|
||||
pub fn panel_title_ui(
|
||||
ui: &mut Ui,
|
||||
panel: &mut entities::panels::Model,
|
||||
editing: &mut Vec<EditingModel>,
|
||||
metrics: &Vec<entities::metrics::Model>,
|
||||
panel_metric: &Vec<entities::panel_metric::Model>,
|
||||
) { // TODO make edit UI in separate func
|
||||
ui.horizontal(|ui| {
|
||||
ui.heading(panel.name.as_str());
|
||||
ui.separator();
|
||||
if ui.small_button("#").clicked() {
|
||||
// TODO don't add duplicates
|
||||
editing.push(
|
||||
EditingModel::make_edit_panel(panel.clone(), metrics, panel_metric)
|
||||
);
|
||||
}
|
||||
ui.separator();
|
||||
ui.add(Slider::new(&mut panel.height, 0..=500).text("height"));
|
||||
//ui.separator();
|
||||
//ui.checkbox(&mut panel.timeserie, "timeserie");
|
||||
|
@ -93,26 +108,29 @@ pub fn panel_title_ui(ui: &mut Ui, panel: &mut entities::panels::Model, _edit: b
|
|||
});
|
||||
}
|
||||
|
||||
pub fn panel_body_ui(ui: &mut Ui, panel: &entities::panels::Model, metrics: &Vec<entities::metrics::Model>, points: &Vec<entities::points::Model>) {
|
||||
pub fn panel_body_ui(
|
||||
ui: &mut Ui,
|
||||
panel: &entities::panels::Model,
|
||||
metrics: &Vec<entities::metrics::Model>,
|
||||
points: &Vec<entities::points::Model>,
|
||||
panel_metric: &Vec<entities::panel_metric::Model>,
|
||||
) {
|
||||
let mut p = Plot::new(format!("plot-{}", panel.name))
|
||||
.height(panel.height as f32)
|
||||
.allow_scroll(false)
|
||||
.legend(Legend::default().position(Corner::LeftTop));
|
||||
|
||||
if panel.limit_view {
|
||||
if panel.view_scroll {
|
||||
p = p.set_margin_fraction(Vec2 { x: 0.0, y: 0.1 });
|
||||
}
|
||||
|
||||
|
||||
if panel.timeserie {
|
||||
if panel.view_scroll {
|
||||
let _now = (Utc::now().timestamp() as f64) - (60.0 * panel.view_offset as f64);
|
||||
p = p.include_x(_now);
|
||||
if panel.limit_view {
|
||||
p = p
|
||||
.include_x(_now + (panel.view_size as f64 * 3.0))
|
||||
.include_x(_now - (panel.view_size as f64 * 60.0)); // ??? TODO
|
||||
}
|
||||
let now = (Utc::now().timestamp() as f64) - (60.0 * panel.view_offset as f64);
|
||||
p = p.include_x(now)
|
||||
.include_x(now + (panel.view_size as f64 * 3.0))
|
||||
.include_x(now - (panel.view_size as f64 * 60.0)); // ??? TODO
|
||||
}
|
||||
p = p
|
||||
.x_axis_formatter(|x, _range| timestamp_to_str(x as i64, true, false))
|
||||
|
@ -165,8 +183,9 @@ pub fn panel_body_ui(ui: &mut Ui, panel: &entities::panels::Model, metrics: &Vec
|
|||
let min_x = now - size - off;
|
||||
let max_x = now - off;
|
||||
let chunk_size = if panel.reduce_view { Some(panel.view_chunks) } else { None };
|
||||
let metric_ids : Vec<i64> = panel_metric.iter().filter(|x| x.panel_id == panel.id).map(|x| x.metric_id).collect();
|
||||
for metric in metrics {
|
||||
if metric.panel_id == panel.id {
|
||||
if metric_ids.contains(&metric.id) {
|
||||
// let values = metric.values(min_x, max_x, chunk_size, panel.average_view);
|
||||
let mut values : Vec<[f64;2]> = points
|
||||
.iter()
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use eframe::{Frame, egui::{collapsing_header::CollapsingState, Context, Ui, Layout, ScrollArea, global_dark_light_mode_switch, TextEdit, Checkbox, Slider}, emath::Align};
|
||||
use eframe::{Frame, egui::{collapsing_header::CollapsingState, Context, Ui, Layout, ScrollArea, global_dark_light_mode_switch, TextEdit, Checkbox, Slider, ComboBox}, emath::Align};
|
||||
use sea_orm::{Set, Unchanged, ActiveValue::NotSet};
|
||||
use tokio::sync::watch;
|
||||
|
||||
use crate::{gui::App, data::entities, util::unpack_color, worker::BackgroundAction};
|
||||
use crate::{gui::App, data::entities, util::{unpack_color, repack_color}, worker::{BackgroundAction, AppStateView}};
|
||||
|
||||
// TODO make this not super specific!
|
||||
pub fn _confirmation_popup_delete_metric(_app: &mut App, ui: &mut Ui, _metric_index: usize) {
|
||||
|
@ -68,7 +68,7 @@ pub struct EditingModel {
|
|||
impl EditingModel {
|
||||
pub fn id_repr(&self) -> String {
|
||||
let prefix = match self.m {
|
||||
EditingModelType::EditingPanel { panel: _ } => "panel",
|
||||
EditingModelType::EditingPanel { panel: _, opts: _ } => "panel",
|
||||
EditingModelType::EditingSource { source: _ } => "source",
|
||||
EditingModelType::EditingMetric { metric: _ } => "metric",
|
||||
};
|
||||
|
@ -83,9 +83,30 @@ impl EditingModel {
|
|||
return !self.ready;
|
||||
}
|
||||
|
||||
pub fn to_msg(&self) -> BackgroundAction {
|
||||
pub fn make_edit_panel(
|
||||
panel: entities::panels::Model,
|
||||
metrics: &Vec<entities::metrics::Model>,
|
||||
panel_metric: &Vec<entities::panel_metric::Model>
|
||||
) -> EditingModel {
|
||||
let metric_ids : Vec<i64> = panel_metric.iter().filter(|x| x.panel_id == panel.id).map(|x| x.metric_id).collect();
|
||||
let mut opts = vec![false; metrics.len()];
|
||||
for i in 0..metrics.len() {
|
||||
if metric_ids.contains(&metrics[i].id) {
|
||||
opts[i] = true;
|
||||
}
|
||||
}
|
||||
EditingModel {
|
||||
id: panel.id,
|
||||
new: if panel.id > 0 { false } else { true },
|
||||
m: EditingModelType::EditingPanel { panel, opts },
|
||||
valid: false,
|
||||
ready: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_msg(&self, view:AppStateView) -> BackgroundAction {
|
||||
match &self.m {
|
||||
EditingModelType::EditingPanel { panel } =>
|
||||
EditingModelType::EditingPanel { panel, opts: metrics } =>
|
||||
BackgroundAction::UpdatePanel {
|
||||
panel: entities::panels::ActiveModel {
|
||||
id: if self.new { NotSet } else { Unchanged(panel.id) },
|
||||
|
@ -94,14 +115,21 @@ impl EditingModel {
|
|||
view_size: Set(panel.view_size),
|
||||
timeserie: Set(panel.timeserie),
|
||||
height: Set(panel.height),
|
||||
limit_view: Set(panel.limit_view),
|
||||
position: Set(panel.position),
|
||||
reduce_view: Set(panel.reduce_view),
|
||||
view_chunks: Set(panel.view_chunks),
|
||||
shift_view: Set(panel.shift_view),
|
||||
view_offset: Set(panel.view_offset),
|
||||
average_view: Set(panel.average_view),
|
||||
}
|
||||
},
|
||||
metrics: view.metrics.borrow().iter()
|
||||
.enumerate()
|
||||
.filter(|(i,x)| *metrics.get(*i).unwrap_or(&false))
|
||||
.map(|(i,m)| entities::panel_metric::ActiveModel {
|
||||
id: NotSet,
|
||||
panel_id: Set(panel.id),
|
||||
metric_id: Set(m.id),
|
||||
})
|
||||
.collect(),
|
||||
},
|
||||
EditingModelType::EditingSource { source } =>
|
||||
BackgroundAction::UpdateSource {
|
||||
|
@ -122,7 +150,6 @@ impl EditingModel {
|
|||
name: Set(metric.name.clone()),
|
||||
source_id: Set(metric.source_id),
|
||||
color: Set(metric.color),
|
||||
panel_id: Set(metric.panel_id),
|
||||
query_x: Set(metric.query_x.clone()),
|
||||
query_y: Set(metric.query_y.clone()),
|
||||
position: Set(metric.position),
|
||||
|
@ -154,24 +181,35 @@ impl From<entities::panels::Model> for EditingModel {
|
|||
fn from(p: entities::panels::Model) -> Self {
|
||||
EditingModel {
|
||||
new: if p.id == 0 { true } else { false },
|
||||
id: p.id, m: EditingModelType::EditingPanel { panel: p }, valid: false, ready: false,
|
||||
id: p.id, m: EditingModelType::EditingPanel { panel: p , opts: vec![] }, valid: false, ready: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum EditingModelType {
|
||||
EditingPanel { panel : entities::panels::Model },
|
||||
EditingPanel { panel : entities::panels::Model, opts: Vec<bool> },
|
||||
EditingSource { source: entities::sources::Model },
|
||||
EditingMetric { metric: entities::metrics::Model },
|
||||
}
|
||||
|
||||
pub fn popup_edit_ui(ui: &mut Ui, model: &mut EditingModel) {
|
||||
pub fn popup_edit_ui(
|
||||
ui: &mut Ui,
|
||||
model: &mut EditingModel,
|
||||
sources: &Vec<entities::sources::Model>,
|
||||
metrics: &Vec<entities::metrics::Model>
|
||||
) {
|
||||
match &mut model.m {
|
||||
EditingModelType::EditingPanel { panel } => {
|
||||
EditingModelType::EditingPanel { panel, opts } => {
|
||||
ui.heading(format!("Edit panel #{}", panel.id));
|
||||
TextEdit::singleline(&mut panel.name)
|
||||
.hint_text("name")
|
||||
.show(ui);
|
||||
for (i, metric) in metrics.iter().enumerate() {
|
||||
if i >= opts.len() { // TODO safe but jank: always starts with all off
|
||||
opts.push(false);
|
||||
}
|
||||
ui.checkbox(&mut opts[i], &metric.name);
|
||||
}
|
||||
},
|
||||
EditingModelType::EditingSource { source } => {
|
||||
ui.heading(format!("Edit source #{}", source.id));
|
||||
|
@ -189,11 +227,21 @@ pub fn popup_edit_ui(ui: &mut Ui, model: &mut EditingModel) {
|
|||
EditingModelType::EditingMetric { metric } => {
|
||||
ui.heading(format!("Edit metric #{}", metric.id));
|
||||
ui.horizontal(|ui| {
|
||||
ui.color_edit_button_srgba(&mut unpack_color(metric.color));
|
||||
let mut color_buf = unpack_color(metric.color);
|
||||
ui.color_edit_button_srgba(&mut color_buf);
|
||||
metric.color = repack_color(color_buf);
|
||||
TextEdit::singleline(&mut metric.name)
|
||||
.hint_text("name")
|
||||
.show(ui);
|
||||
});
|
||||
ComboBox::from_id_source(format!("source-selector-{}", metric.id))
|
||||
.selected_text(format!("source: {:02}", metric.source_id))
|
||||
.show_ui(ui, |ui| {
|
||||
ui.selectable_value(&mut metric.source_id, -1, "None");
|
||||
for s in sources.iter() {
|
||||
ui.selectable_value(&mut metric.source_id, s.id, s.name.as_str());
|
||||
}
|
||||
});
|
||||
TextEdit::singleline(&mut metric.query_x)
|
||||
.hint_text("x")
|
||||
.show(ui);
|
||||
|
|
|
@ -16,6 +16,7 @@ pub fn source_panel(app: &mut App, ui: &mut Ui) {
|
|||
// let mut to_delete: Option<usize> = None;
|
||||
let panels = &app.panels;
|
||||
let panel_width = ui.available_width();
|
||||
let mut orphaned_metrics = app.view.metrics.borrow().clone();
|
||||
ScrollArea::vertical()
|
||||
.max_width(panel_width)
|
||||
.show(ui, |ui| {
|
||||
|
@ -29,16 +30,8 @@ pub fn source_panel(app: &mut App, ui: &mut Ui) {
|
|||
ui.horizontal(|ui| {
|
||||
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?
|
||||
}
|
||||
}
|
||||
if ui.small_button("+").clicked() { }
|
||||
if ui.small_button("−").clicked() { }
|
||||
});
|
||||
ui.vertical(|ui| { // actual sources list container
|
||||
let remaining_width = ui.available_width();
|
||||
|
@ -56,6 +49,7 @@ pub fn source_panel(app: &mut App, ui: &mut Ui) {
|
|||
.borrow();
|
||||
for (_j, metric) in metrics.iter().enumerate() {
|
||||
if metric.source_id == source.id {
|
||||
orphaned_metrics.retain(|m| m.id != metric.id);
|
||||
ui.horizontal(|ui| {
|
||||
metric_edit_ui(
|
||||
ui,
|
||||
|
@ -91,6 +85,55 @@ pub fn source_panel(app: &mut App, ui: &mut Ui) {
|
|||
});
|
||||
});
|
||||
}
|
||||
ui.horizontal(|ui| { // 1 more for uncategorized sources
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(10.0);
|
||||
if ui.small_button("+").clicked() { }
|
||||
if ui.small_button("−").clicked() { }
|
||||
});
|
||||
ui.vertical(|ui| { // actual sources list container
|
||||
let remaining_width = ui.available_width();
|
||||
ui.group(|ui| {
|
||||
ui.horizontal(|ui| {
|
||||
source_edit_ui(ui, &app.buffer_source, remaining_width - 34.0);
|
||||
if ui.small_button("×").clicked() {
|
||||
app.buffer_source = entities::sources::Model::default();
|
||||
}
|
||||
});
|
||||
for metric in orphaned_metrics.iter() {
|
||||
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() {
|
||||
// TODO don't add duplicates
|
||||
app.editing.push(metric.clone().into());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
if app.edit {
|
||||
ui.separator();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use chrono::Utc;
|
||||
use sea_orm::{TransactionTrait, DatabaseConnection, EntityTrait, Condition, ColumnTrait, QueryFilter, Set, QueryOrder, Order, ActiveModelTrait, ActiveValue::NotSet};
|
||||
use sea_orm::{TransactionTrait, DatabaseConnection, EntityTrait, Condition, ColumnTrait, QueryFilter, Set, QueryOrder, Order, ActiveModelTrait, ActiveValue::{NotSet, self}};
|
||||
use tokio::sync::{watch, mpsc};
|
||||
use tracing::{info, error};
|
||||
use std::collections::VecDeque;
|
||||
|
@ -8,12 +8,13 @@ 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>,
|
||||
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 panel_metric: watch::Receiver<Vec<entities::panel_metric::Model>>,
|
||||
pub points: watch::Receiver<Vec<entities::points::Model>>,
|
||||
pub flush: mpsc::Sender<()>,
|
||||
pub op: mpsc::Sender<BackgroundAction>,
|
||||
}
|
||||
|
||||
impl AppStateView {
|
||||
|
@ -26,18 +27,20 @@ impl AppStateView {
|
|||
}
|
||||
|
||||
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>>,
|
||||
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>>,
|
||||
panel_metric: watch::Sender<Vec<entities::panel_metric::Model>>,
|
||||
}
|
||||
|
||||
pub struct AppState {
|
||||
tx: AppStateTransmitters,
|
||||
|
||||
panels: Vec<entities::panels::Model>,
|
||||
sources: Vec<entities::sources::Model>,
|
||||
metrics: Vec<entities::metrics::Model>,
|
||||
panels: Vec<entities::panels::Model>,
|
||||
sources: Vec<entities::sources::Model>,
|
||||
metrics: Vec<entities::metrics::Model>,
|
||||
panel_metric: Vec<entities::panel_metric::Model>,
|
||||
last_refresh: i64,
|
||||
|
||||
points: VecDeque<entities::points::Model>,
|
||||
|
@ -70,6 +73,7 @@ impl AppState {
|
|||
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 (panel_metric_tx, panel_metric_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);
|
||||
|
@ -78,6 +82,7 @@ impl AppState {
|
|||
panels: vec![],
|
||||
sources: vec![],
|
||||
metrics: vec![],
|
||||
panel_metric: vec![],
|
||||
last_refresh: 0,
|
||||
points: VecDeque::new(),
|
||||
last_check: 0,
|
||||
|
@ -88,6 +93,7 @@ impl AppState {
|
|||
sources: source_rx,
|
||||
metrics: metric_rx,
|
||||
points: point_rx,
|
||||
panel_metric: panel_metric_rx,
|
||||
flush: flush_tx,
|
||||
op: op_tx,
|
||||
},
|
||||
|
@ -96,6 +102,7 @@ impl AppState {
|
|||
sources: source_tx,
|
||||
metrics: metric_tx,
|
||||
points: point_tx,
|
||||
panel_metric: panel_metric_tx,
|
||||
},
|
||||
width,
|
||||
interval,
|
||||
|
@ -130,6 +137,12 @@ impl AppState {
|
|||
error!(target: "worker", "Could not send metrics update: {:?}", e);
|
||||
}
|
||||
|
||||
self.panel_metric = entities::panel_metric::Entity::find()
|
||||
.all(db).await?;
|
||||
if let Err(e) = self.tx.panel_metric.send(self.panel_metric.clone()) {
|
||||
error!(target: "worker", "Could not send panel-metric update: {:?}", e);
|
||||
}
|
||||
|
||||
info!(target: "worker", "Updated panels, sources and metrics");
|
||||
self.last_refresh = chrono::Utc::now().timestamp();
|
||||
Ok(())
|
||||
|
@ -164,11 +177,9 @@ impl AppState {
|
|||
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>>()
|
||||
|
@ -184,12 +195,34 @@ impl AppState {
|
|||
self.panels = panels;
|
||||
}
|
||||
},
|
||||
BackgroundAction::UpdatePanel { panel } => {
|
||||
BackgroundAction::UpdatePanel { panel, metrics } => {
|
||||
let panel_id = match panel.id {
|
||||
ActiveValue::Unchanged(pid) => Some(pid),
|
||||
_ => None,
|
||||
};
|
||||
let op = if panel.id == NotSet { panel.insert(&db) } else { panel.update(&db) };
|
||||
// TODO chained if is trashy
|
||||
if let Err(e) = op.await {
|
||||
error!(target: "worker", "Could not update panel: {:?}", e);
|
||||
} else {
|
||||
self.view.request_flush().await;
|
||||
if let Some(panel_id) = panel_id {
|
||||
if let Err(e) = db.transaction::<_, (), sea_orm::DbErr>(|txn| {
|
||||
Box::pin(async move {
|
||||
entities::panel_metric::Entity::delete_many()
|
||||
.filter(
|
||||
Condition::all()
|
||||
.add(entities::panel_metric::Column::PanelId.eq(panel_id))
|
||||
)
|
||||
.exec(txn).await?;
|
||||
entities::panel_metric::Entity::insert_many(metrics).exec(txn).await?;
|
||||
Ok(())
|
||||
})
|
||||
}).await {
|
||||
error!(target: "worker", "Could not update panels on database: {:?}", e);
|
||||
}
|
||||
} else {
|
||||
self.view.request_flush().await;
|
||||
}
|
||||
}
|
||||
},
|
||||
BackgroundAction::UpdateSource { source } => {
|
||||
|
@ -330,7 +363,7 @@ impl AppState {
|
|||
#[derive(Debug)]
|
||||
pub enum BackgroundAction {
|
||||
UpdateAllPanels { panels: Vec<entities::panels::Model> },
|
||||
UpdatePanel { panel : entities::panels::ActiveModel },
|
||||
UpdatePanel { panel : entities::panels::ActiveModel, metrics: Vec<entities::panel_metric::ActiveModel> },
|
||||
UpdateSource { source: entities::sources::ActiveModel },
|
||||
UpdateMetric { metric: entities::metrics::ActiveModel },
|
||||
// InsertPanel { panel : entities::panels::ActiveModel },
|
||||
|
|
Loading…
Reference in a new issue