Skip to content
This repository has been archived by the owner on Jul 25, 2022. It is now read-only.

Commit

Permalink
Implement the Filesystem page in seed (#1072)
Browse files Browse the repository at this point in the history
Fixes #1053

Signed-off-by: Joe Grund <jgrund@whamcloud.io>
  • Loading branch information
jgrund authored Jul 17, 2019
1 parent b516153 commit d453648
Show file tree
Hide file tree
Showing 7 changed files with 528 additions and 167 deletions.
325 changes: 160 additions & 165 deletions iml-wasm-components/Cargo.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion iml-wasm-components/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ members = [
'iml-alert-indicator',
'iml-duration-picker',
'iml-environment',
'iml-fs-page',
'iml-lock-indicator',
'iml-pie-chart',
'iml-grafana-chart',
Expand All @@ -13,7 +14,7 @@ members = [
'iml-toggle',
'iml-tooltip',
'iml-utils',
'iml-wasm'
'iml-wasm',
]

[profile.release]
Expand Down
2 changes: 1 addition & 1 deletion iml-wasm-components/iml-alert-indicator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pub fn alert_indicator(
resource_uri: &str,
open: bool,
) -> El<AlertIndicatorPopoverState> {
log::debug!("Alerts {:#?}", alerts);
log::trace!("Alerts {:#?}", alerts);

let alerts: Vec<&Alert> = alerts
.iter()
Expand Down
29 changes: 29 additions & 0 deletions iml-wasm-components/iml-fs-page/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[package]
name = "iml-fs-page"
version = "0.1.0"
authors = ["IML Team <iml@whamcloud.com>"]
edition = "2018"

[dependencies]
futures = "0.1"
seed = "=0.3.7"
serde = { version = "1", features = ['derive'] }
cfg-if = "0.1"
log = "0.4"
wasm-bindgen = { version = "0.2", features = ["serde-serialize"]}
web-sys = "0.3"
console_log = { version = "0.1", optional = true }
iml-action-dropdown = { path = "../iml-action-dropdown", version = "0.1" }
iml-environment = { path = "../iml-environment", version = "0.1" }
iml-wire-types = "0.1"
iml-utils = { path = "../iml-utils", version = "0.1" }
iml-alert-indicator = { path = "../iml-alert-indicator", version = "0.1" }
iml-lock-indicator = { path = "../iml-lock-indicator", version = "0.1" }
iml-pie-chart = { path = "../iml-pie-chart", version = "0.1" }

[dev-dependencies]
wasm-bindgen-test = "0.2"
insta = "0.8.1"

[lib]
path = "fs-page.rs"
334 changes: 334 additions & 0 deletions iml-wasm-components/iml-fs-page/fs-page.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,334 @@
// Copyright (c) 2019 DDN. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

use cfg_if::cfg_if;
use iml_action_dropdown::{deferred_action_dropdown as dad, has_lock};
use iml_alert_indicator::{alert_indicator, AlertIndicatorPopoverState};
use iml_environment::ui_root;
use iml_lock_indicator::{lock_indicator, LockIndicatorState};
use iml_pie_chart::pie_chart;
use iml_utils::{extract_api, format_bytes, Locks, WatchState};
use iml_wire_types::{Alert, Filesystem, Target, TargetConfParam, ToCompositeId};
use seed::{a, attrs, class, div, h1, h4, i, prelude::*, span, table, tbody, td, th, thead, tr};
use std::collections::{HashMap, HashSet};
use wasm_bindgen::JsValue;
use web_sys::Element;

cfg_if! {
if #[cfg(feature = "console_log")] {
fn init_log() {
use log::Level;
match console_log::init_with_level(Level::Trace) {
Ok(_) => (),
Err(e) => log::info!("{:?}", e)
};
}
} else {
fn init_log() {}
}
}

pub struct Row {
pub fs: Filesystem,
pub dropdown: dad::Model,
pub alert_indicator: WatchState,
pub lock_indicator: WatchState,
}

type Rows = HashMap<u32, Row>;

#[derive(Default)]
struct Model {
pub destroyed: bool,
pub alerts: Vec<Alert>,
pub rows: Rows,
pub targets: HashMap<u32, Target<TargetConfParam>>,
pub locks: Locks,
}

impl Model {
fn has_fs(&self) -> bool {
!self.rows.is_empty()
}
fn get_mgt(&self, fs: &Filesystem) -> Option<&Target<TargetConfParam>> {
self.targets
.values()
.find(|x| x.kind == "MGT" && fs.mgt == x.resource_uri)
}
}

#[derive(Clone)]
enum Msg {
Destroy,
WindowClick,
Filesystems(HashMap<u32, Filesystem>),
FsRowPopoverState(AlertIndicatorPopoverState),
FsRowLockIndicatorState(LockIndicatorState),
Targets(HashMap<u32, Target<TargetConfParam>>),
Alerts(Vec<Alert>),
SetLocks(Locks),
ActionDropdown(dad::IdMsg<Filesystem>),
}

fn update(msg: Msg, model: &mut Model, orders: &mut Orders<Msg>) {
match msg {
Msg::Destroy => {
model.destroyed = true;

for (id, row) in &mut model.rows {
*orders = call_update(
dad::update,
dad::IdMsg(*id, dad::Msg::Destroy),
&mut row.dropdown,
)
.map_message(Msg::ActionDropdown);
}
}
Msg::WindowClick => {
for row in &mut model.rows.values_mut() {
if row.alert_indicator.should_update() {
row.alert_indicator.update()
}

if row.dropdown.watching.should_update() {
row.dropdown.watching.update()
}

if row.lock_indicator.should_update() {
row.lock_indicator.update()
}
}
}
Msg::Filesystems(mut filesystems) => {
let old_keys = model.rows.keys().cloned().collect::<HashSet<u32>>();
let new_keys = filesystems.keys().cloned().collect::<HashSet<u32>>();

let to_remove = old_keys.difference(&new_keys);
let to_add = new_keys.difference(&old_keys);
let to_change = new_keys.intersection(&old_keys);

log::info!("old keys {:?}, new keys {:?}", old_keys, new_keys);

for x in to_remove {
model.rows.remove(x);
}

for x in to_add {
let fs = filesystems.remove(&x).unwrap();

model.rows.insert(
*x,
Row {
dropdown: dad::Model {
composite_ids: vec![fs.composite_id()],
..dad::Model::default()
},
alert_indicator: WatchState::Close,
lock_indicator: WatchState::Close,
fs,
},
);
}

for x in to_change {
let mut r = model.rows.get_mut(&x).unwrap();

r.fs = filesystems.remove(&x).unwrap();
}
}
Msg::Targets(targets) => {
model.targets = targets;
}
Msg::Alerts(alerts) => {
model.alerts = alerts;
}
Msg::FsRowPopoverState(AlertIndicatorPopoverState((id, state))) => {
if let Some(x) = model.rows.get_mut(&id) {
x.alert_indicator = state;
}
}
Msg::FsRowLockIndicatorState(LockIndicatorState(id, state)) => {
if let Some(x) = model.rows.get_mut(&id) {
x.lock_indicator = state;
}
}
Msg::SetLocks(locks) => {
for row in &mut model.rows.values_mut() {
if has_lock(&locks, &row.fs) {
row.dropdown.is_locked = true;
} else {
row.dropdown.is_locked = false;
}
}

model.locks = locks;
}
Msg::ActionDropdown(dad::IdMsg(id, msg)) => {
if let Some(x) = model.rows.get_mut(&id) {
*orders = call_update(dad::update, dad::IdMsg(id, msg), &mut x.dropdown)
.map_message(Msg::ActionDropdown);
}
}
}
}

fn no_fs() -> El<Msg> {
div![
class!["no-fs", "well", "text-center"],
h1!["No File Systems are configured"],
a![
class!["btn", "btn-success", "btn-lg"],
attrs! { At::Href => format!("{}configure/filesystem/create/", ui_root()), At::Type => "button"},
i![class!["fa", "fa-plus-circle"]],
"Create File System"
]
]
}

fn link(href: &str, content: &str) -> El<Msg> {
a![attrs! { At::Href => href, At::Type => "button" }, content]
}

fn space_usage(used: Option<f64>, total: Option<f64>) -> El<Msg> {
div![match (used, total) {
(Some(used), Some(total)) => div![
pie_chart(used, total, "#aec7e8", "#1f77b4")
.add_style("width".into(), px(18))
.add_style("height".into(), px(18))
.add_style("vertical-align".into(), "bottom".into())
.add_style("margin-right".into(), px(3)),
format_bytes(used, Some(1)),
" / ",
format_bytes(total, Some(1)),
],
_ => span!["Calculating..."],
}]
}

fn fs_rows(model: &Model) -> Vec<El<Msg>> {
model
.rows
.values()
.map(|x| {
let fs = &x.fs;

let mgt = model.get_mgt(&fs);

tr![
td![link(
&format!("{}configure/filesystem/{}", ui_root(), fs.id),
&fs.name
)],
td![
lock_indicator(
fs.id,
x.lock_indicator.is_open(),
fs.composite_id(),
&model.locks
)
.add_style("margin-right".into(), px(5))
.map_message(Msg::FsRowLockIndicatorState),
alert_indicator(
&model.alerts,
fs.id,
&fs.resource_uri,
x.alert_indicator.is_open()
)
.map_message(Msg::FsRowPopoverState)
],
td![match mgt {
Some(mgt) => link(
&format!(
"{}configure/server/{}",
ui_root(),
extract_api(&mgt.primary_server).unwrap()
),
&mgt.primary_server_name
),
None => span!["---"],
}],
td![fs.mdts.len().to_string()],
td![fs.client_count.round().to_string()],
td![space_usage(
Some(fs.bytes_total.unwrap() - fs.bytes_free.unwrap()),
fs.bytes_total
)],
td![dad::render(fs.id, &x.dropdown, fs).map_message(Msg::ActionDropdown)],
]
})
.collect()
}

fn view(model: &Model) -> El<Msg> {
div![
class!["file-systems"],
if model.has_fs() {
div![
h4![class!["section-header"], "File Systems"],
table![
class!["table"],
thead![tr![
th!["File System"],
th!["Status"],
th!["Primary MGS"],
th!["Metadata Target Count"],
th!["Connected Clients"],
th!["Space Used / Total"],
th!["Actions"]
]],
tbody![fs_rows(&model)]
]
]
} else {
no_fs()
}
]
}

fn window_events(model: &Model) -> Vec<seed::events::Listener<Msg>> {
if model.destroyed {
return vec![];
}

vec![simple_ev(Ev::Click, Msg::WindowClick)]
}

#[wasm_bindgen]
pub struct FsPageCallbacks {
app: seed::App<Msg, Model, El<Msg>>,
}

#[wasm_bindgen]
impl FsPageCallbacks {
pub fn destroy(&self) {
self.app.update(Msg::Destroy);
}
pub fn set_filesystems(&self, filesystems: JsValue) {
self.app
.update(Msg::Filesystems(filesystems.into_serde().unwrap()));
}
pub fn set_targets(&self, targets: JsValue) {
self.app.update(Msg::Targets(targets.into_serde().unwrap()))
}
pub fn set_alerts(&self, alerts: JsValue) {
self.app.update(Msg::Alerts(alerts.into_serde().unwrap()))
}
pub fn set_locks(&self, locks: JsValue) {
let locks: Locks = locks.into_serde().unwrap();
self.app.update(Msg::SetLocks(locks));
}
}

#[wasm_bindgen]
pub fn render_fs_page(el: Element) -> FsPageCallbacks {
init_log();

let app = seed::App::build(Model::default(), update, view)
.mount(el)
.window_events(window_events)
.finish()
.run();

FsPageCallbacks { app: app.clone() }
}
1 change: 1 addition & 0 deletions iml-wasm-components/iml-wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ iml-action-dropdown = { path = "../iml-action-dropdown", version = "0.1" }
iml-tooltip = { path = "../iml-tooltip", version = "0.1" }
iml-utils = { path = "../iml-utils", version = "0.1" }
iml-wire-types = "0.1"
iml-fs-page = { path = "../iml-fs-page", version = "0.1" }


# [features]
Expand Down
Loading

0 comments on commit d453648

Please sign in to comment.