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

Implement the Filesystem page in seed #1072

Merged
merged 1 commit into from
Jul 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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