diff --git a/server/Cargo.lock b/server/Cargo.lock index 16eea90..e61c9aa 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -174,11 +174,13 @@ dependencies = [ "env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)", "iron 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-core 8.0.1 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.11)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "postgres 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)", "primitives 0.4.0 (git+https://github.com/CodeChain-io/rust-codechain-primitives.git)", "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "sendgrid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/server/Cargo.toml b/server/Cargo.toml index 653b1d9..8488504 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -10,11 +10,13 @@ colored = "1.6" env_logger = "0.5.7" iron = "*" jsonrpc-core = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.11" } +lazy_static = "1.3.0" log = "0.4.1" parking_lot = "0.7.1" postgres = { version = "0.15", features = ["with-chrono"] } primitives = { git = "https://github.com/CodeChain-io/rust-codechain-primitives.git", version = "0.4.0" } rand = "0.5.5" +regex = "1" sendgrid = "0.8.1" serde = "1.0" serde_derive = "1.0" diff --git a/server/src/agent/agent.rs b/server/src/agent/agent.rs index 62c210a..ef27961 100644 --- a/server/src/agent/agent.rs +++ b/server/src/agent/agent.rs @@ -1,3 +1,4 @@ +use std::convert::TryFrom; use std::net::SocketAddr; use std::ops::Drop; use std::sync::Arc; @@ -316,6 +317,7 @@ impl Agent { let network_id = self.codechain_rpc.get_network_id(info.status)?; let whitelist = self.codechain_rpc.get_whitelist(info.status)?; let blacklist = self.codechain_rpc.get_blacklist(info.status)?; + let network_usage = self.codechain_rpc.get_network_usage(info.status)?; let hardware = self.sender.hardware_get().map_err(|err| format!("Agent Update {}", err))?; ctrace!("Update state from {:?} to {:?}", *state, new_state); @@ -335,6 +337,16 @@ impl Agent { }); *state = new_state; + let now = chrono::Utc::now(); + if let Some(network_usage) = network_usage { + self.db_service.write_network_usage(info.name.clone(), network_usage, now); + self.db_service.write_peer_count( + info.name.clone(), + i32::try_from(number_of_peers).map_err(|err| err.to_string())?, + now, + ); + } + let logs = self.codechain_rpc.get_logs(info.status)?; self.db_service.write_logs(info.name, logs); diff --git a/server/src/agent/codechain_rpc.rs b/server/src/agent/codechain_rpc.rs index 2b223ff..3322b38 100644 --- a/server/src/agent/codechain_rpc.rs +++ b/server/src/agent/codechain_rpc.rs @@ -5,7 +5,9 @@ use serde::de::DeserializeOwned; use serde_json; use serde_json::Value; -use super::super::common_rpc_types::{BlackList, BlockId, NodeStatus, PendingTransaction, StructuredLog, WhiteList}; +use super::super::common_rpc_types::{ + BlackList, BlockId, NetworkUsage, NodeStatus, PendingTransaction, StructuredLog, WhiteList, +}; use super::agent::{AgentSender, SendAgentRPC}; use super::types::ChainGetBestBlockIdResponse; @@ -59,6 +61,10 @@ impl CodeChainRPC { self.call_rpc(status, "net_getBlacklist", Vec::new()) } + pub fn get_network_usage(&self, status: NodeStatus) -> Result, String> { + self.call_rpc(status, "net_recentNetworkUsage", Vec::new()) + } + pub fn get_logs(&self, status: NodeStatus) -> Result, String> { if status != NodeStatus::Run { return Ok(Default::default()) diff --git a/server/src/bin/generate-schema.rs b/server/src/bin/generate-schema.rs index 201d9f4..54b1c13 100644 --- a/server/src/bin/generate-schema.rs +++ b/server/src/bin/generate-schema.rs @@ -18,6 +18,8 @@ fn main() { create_agent_extra_schema(&conn); create_logs_schema(&conn); + create_network_usage_schema(&conn); + create_peer_count_schema(&conn) } fn create_agent_extra_schema(conn: &Connection) { @@ -56,3 +58,39 @@ fn create_logs_schema(conn: &Connection) { cinfo!("Create logs_target index"); conn.execute("CREATE INDEX IF NOT EXISTS logs_targets ON logs (target)", &[]).unwrap(); } + +fn create_network_usage_schema(conn: &Connection) { + cinfo!("Create network_usage table"); + conn.execute( + "CREATE TABLE IF NOT EXISTS network_usage ( + id BIGSERIAL PRIMARY KEY, + time TIMESTAMP WITH TIME ZONE NOT NULL, + name VARCHAR NOT NULL, + extension VARCHAR NOT NULL, + target_ip VARCHAR NOT NULL, + bytes INTEGER NOT NULL + )", + &[], + ) + .unwrap(); + + cinfo!("Create network_usage_time_index"); + conn.execute("CREATE INDEX IF NOT EXISTS network_usage_time_index ON network_usage (time)", &[]).unwrap(); +} + +fn create_peer_count_schema(conn: &Connection) { + cinfo!("Create peer_count table"); + conn.execute( + "CREATE TABLE IF NOT EXISTS peer_count ( + id BIGSERIAL PRIMARY KEY, + time TIMESTAMP WITH TIME ZONE NOT NULL, + name VARCHAR NOT NULL, + peer_count INTEGER NOT NULL + )", + &[], + ) + .unwrap(); + + cinfo!("Create peer_count_time_index"); + conn.execute("CREATE INDEX IF NOT EXISTS peer_count_time_index ON peer_count (time)", &[]).unwrap(); +} diff --git a/server/src/common_rpc_types.rs b/server/src/common_rpc_types.rs index 98fedf3..5bbd6b8 100644 --- a/server/src/common_rpc_types.rs +++ b/server/src/common_rpc_types.rs @@ -1,5 +1,7 @@ +use std::collections::HashMap; use std::net::IpAddr; +use chrono::{DateTime, Utc}; use cprimitives::H256; use serde_json; @@ -60,6 +62,8 @@ pub struct WhiteList { pub type BlackList = WhiteList; +pub type NetworkUsage = HashMap; + #[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)] #[serde(rename_all = "camelCase")] pub struct HardwareUsage { @@ -101,3 +105,51 @@ pub enum UpdateCodeChainRequest { binary_checksum: String, }, } + + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum GraphPeriod { + Minutes5, + Hour, + Day, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct GraphCommonArgs { + pub from: DateTime, + pub to: DateTime, + pub period: GraphPeriod, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GraphNetworkOutAllRow { + pub node_name: String, + pub time: DateTime, + pub value: f32, +} + +pub type GraphNetworkOutAllAVGRow = GraphNetworkOutAllRow; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn serialize_day() { + let period = GraphPeriod::Day; + assert_eq!("\"day\"", &serde_json::to_string(&period).unwrap()); + } + #[test] + fn serialize_minutes5() { + let period = GraphPeriod::Minutes5; + assert_eq!("\"minutes5\"", &serde_json::to_string(&period).unwrap()); + } + #[test] + fn serialize_hour() { + let period = GraphPeriod::Hour; + assert_eq!("\"hour\"", &serde_json::to_string(&period).unwrap()); + } +} diff --git a/server/src/db/queries/mod.rs b/server/src/db/queries/mod.rs index 441f5fd..aeff2a9 100644 --- a/server/src/db/queries/mod.rs +++ b/server/src/db/queries/mod.rs @@ -1,3 +1,6 @@ pub mod agent_extra; pub mod config; pub mod logs; +pub mod network_usage; +pub mod network_usage_graph; +pub mod peer_count; diff --git a/server/src/db/queries/network_usage.rs b/server/src/db/queries/network_usage.rs new file mode 100644 index 0000000..7f68597 --- /dev/null +++ b/server/src/db/queries/network_usage.rs @@ -0,0 +1,47 @@ +use chrono; +use postgres; +use regex::{Captures, Regex}; + +use common_rpc_types::NetworkUsage; + +pub fn insert( + conn: &postgres::Connection, + node_name: &str, + network_usage: NetworkUsage, + time: chrono::DateTime, +) -> postgres::Result<()> { + ctrace!("Add network usage of {}", node_name); + + if network_usage.is_empty() { + return Ok(()) + } + + let stmt = conn + .prepare("INSERT INTO network_usage (time, name, extension, target_ip, bytes) VALUES ($1, $2, $3, $4, $5)")?; + for key in network_usage.keys() { + let parse_result = parse_network_usage_key(key); + let (extension, ip) = match parse_result { + Ok((extension, ip)) => (extension, ip), + Err(err) => { + cerror!("Network Usage Parse Failed {:?}", err); + // FIXME: propagate the error + return Ok(()) + } + }; + let bytes = network_usage[key]; + stmt.execute(&[&time, &node_name, &extension, &ip, &bytes])?; + } + + Ok(()) +} + +fn parse_network_usage_key(key: &str) -> Result<(String, String), String> { + // Ex) ::block-propagation@54.180.74.243:3485 + lazy_static! { + static ref KEY_REGEX: Regex = Regex::new(r"::(?P[a-zA-Z\-]*)@(?P[0-9\.]*)").unwrap(); + } + + let reg_result: Captures = KEY_REGEX.captures(key).ok_or_else(|| "Parse Error".to_string())?; + + Ok((reg_result["extension"].to_string(), reg_result["ip"].to_string())) +} diff --git a/server/src/db/queries/network_usage_graph.rs b/server/src/db/queries/network_usage_graph.rs new file mode 100644 index 0000000..6c9eb01 --- /dev/null +++ b/server/src/db/queries/network_usage_graph.rs @@ -0,0 +1,71 @@ +use common_rpc_types::{GraphCommonArgs, GraphNetworkOutAllRow, GraphPeriod}; +use postgres; + +pub fn query_network_out_all( + conn: &postgres::Connection, + graph_args: GraphCommonArgs, +) -> postgres::Result> { + let query_stmt = format!( + "\ + SELECT \ + name, \ + {}, \ + CAST (AVG(bytes) AS REAL) as value \ + FROM \"network_usage\" \ + WHERE \"time\"<$1 and \"time\">$2 \ + GROUP BY \"name\", \"rounded_time\" \ + ORDER BY \"name\", \"rounded_time\" ASC", + get_sql_round_period_expression(graph_args.period) + ); + + let rows = conn.query(&query_stmt, &[&graph_args.to, &graph_args.from])?; + + Ok(rows + .into_iter() + .map(|row| GraphNetworkOutAllRow { + node_name: row.get("name"), + time: row.get("rounded_time"), + value: row.get("value"), + }) + .collect()) +} + +fn get_sql_round_period_expression(period: GraphPeriod) -> &'static str { + match period { + GraphPeriod::Minutes5 => { + "date_trunc('hour', \"network_usage\".time) + INTERVAL '5 min' * ROUND(date_part('minute', \"network_usage\".time) / 5.0) as \"rounded_time\"" + } + GraphPeriod::Hour => "date_trunc('hour', \"network_usage\".time) as \"rounded_time\"", + GraphPeriod::Day => "date_trunc('day', \"network_usage\".time) as \"rounded_time\"", + } +} + +pub fn query_network_out_all_avg( + conn: &postgres::Connection, + graph_args: GraphCommonArgs, +) -> postgres::Result> { + let query_stmt = format!( + "\ + SELECT \ + \"network_usage\".name, \ + {}, \ + CAST (AVG(bytes/\"peer_count\".\"peer_count\") AS REAL) as value \ + FROM \"network_usage\" \ + LEFT JOIN peer_count ON (\"network_usage\".\"time\"=\"peer_count\".\"time\") \ + WHERE \"network_usage\".\"time\"<$1 and \"network_usage\".\"time\">$2 \ + GROUP BY \"network_usage\".\"name\", \"rounded_time\" \ + ORDER BY \"network_usage\".\"name\", \"rounded_time\" ASC", + get_sql_round_period_expression(graph_args.period) + ); + + let rows = conn.query(&query_stmt, &[&graph_args.to, &graph_args.from])?; + + Ok(rows + .into_iter() + .map(|row| GraphNetworkOutAllRow { + node_name: row.get("name"), + time: row.get("rounded_time"), + value: row.get("value"), + }) + .collect()) +} diff --git a/server/src/db/queries/peer_count.rs b/server/src/db/queries/peer_count.rs new file mode 100644 index 0000000..cbf4794 --- /dev/null +++ b/server/src/db/queries/peer_count.rs @@ -0,0 +1,18 @@ +use chrono; +use postgres; + +pub fn insert( + conn: &postgres::Connection, + node_name: &str, + peer_count: i32, + time: chrono::DateTime, +) -> postgres::Result<()> { + ctrace!("Add peer count of {}", node_name); + + conn.execute("INSERT INTO peer_count (time, name, peer_count) VALUES ($1, $2, $3)", &[ + &time, + &node_name, + &peer_count, + ])?; + Ok(()) +} diff --git a/server/src/db/service.rs b/server/src/db/service.rs index 2df7d13..1f3cd1e 100644 --- a/server/src/db/service.rs +++ b/server/src/db/service.rs @@ -13,6 +13,7 @@ use super::super::common_rpc_types::{NodeName, NodeStatus, StructuredLog}; use super::event::{Event, EventSubscriber}; use super::queries; use super::types::{AgentExtra, AgentQueryResult, Connection, Connections, Error as DBError, Log, LogQueryParams}; +use common_rpc_types::{GraphCommonArgs, GraphNetworkOutAllAVGRow, GraphNetworkOutAllRow, NetworkUsage}; use util; #[derive(Debug, Clone)] @@ -27,6 +28,10 @@ pub enum Message { GetLogs(LogQueryParams, Sender>), WriteLogs(NodeName, Vec), GetLogTargets(Sender>), + WriteNetworkUsage(NodeName, NetworkUsage, chrono::DateTime), + WritePeerCount(NodeName, i32, chrono::DateTime), + GetGraphNetworkOutAll(GraphCommonArgs, Sender, DBError>>), + GetGraphNetworkOutAllAVG(GraphCommonArgs, Sender, DBError>>), } #[derive(Clone)] @@ -122,6 +127,28 @@ impl Service { cerror!("Error at {}", err); } } + Message::WriteNetworkUsage(node_name, network_usage, time) => { + util::log_error(&node_name, service.write_network_usage(&node_name, network_usage, time)); + } + Message::WritePeerCount(node_name, peer_count, time) => { + util::log_error(&node_name, service.write_peer_count(&node_name, peer_count, time)); + } + Message::GetGraphNetworkOutAll(args, callback) => { + let result = service + .get_network_out_all_graph(args) + .map_err(|err| DBError::Internal(err.to_string())); + if let Err(callback_err) = callback.send(result) { + cerror!("Error at {}", callback_err); + } + } + Message::GetGraphNetworkOutAllAVG(args, callback) => { + let result = service + .get_network_out_all_avg_graph(args) + .map_err(|err| DBError::Internal(err.to_string())); + if let Err(callback_err) = callback.send(result) { + cerror!("Error at {}", callback_err); + } + } } } }) @@ -276,6 +303,42 @@ impl Service { callback.send(targets)?; Ok(()) } + + fn write_network_usage( + &self, + node_name: &str, + network_usage: NetworkUsage, + time: chrono::DateTime, + ) -> Result<(), Box> { + queries::network_usage::insert(&self.db_conn, node_name, network_usage, time)?; + Ok(()) + } + + fn write_peer_count( + &self, + node_name: &str, + peer_count: i32, + time: chrono::DateTime, + ) -> Result<(), Box> { + queries::peer_count::insert(&self.db_conn, node_name, peer_count, time)?; + Ok(()) + } + + fn get_network_out_all_graph( + &self, + args: GraphCommonArgs, + ) -> Result, Box> { + let rows = queries::network_usage_graph::query_network_out_all(&self.db_conn, args)?; + Ok(rows) + } + + fn get_network_out_all_avg_graph( + &self, + args: GraphCommonArgs, + ) -> Result, Box> { + let rows = queries::network_usage_graph::query_network_out_all_avg(&self.db_conn, args)?; + Ok(rows) + } } impl ServiceSender { @@ -288,7 +351,7 @@ impl ServiceSender { pub fn initialize_agent_query_result(&self, agent_query_result: AgentQueryResult) -> Result { let (tx, rx) = channel(); self.sender.send(Message::InitializeAgent(agent_query_result.into(), tx)).expect("Should success update agent"); - let result = rx.recv().map_err(|_| DBError::Timeout)?; + let result = rx.recv()?; Ok(result) } @@ -299,21 +362,21 @@ impl ServiceSender { pub fn get_agent_query_result(&self, name: &str) -> Result, DBError> { let (tx, rx) = channel(); self.sender.send(Message::GetAgent(name.to_string(), tx)).expect("Should success send request"); - let agent_query_result = rx.recv().map_err(|_| DBError::Timeout)?; + let agent_query_result = rx.recv()?; Ok(agent_query_result) } pub fn get_agents_state(&self) -> Result, DBError> { let (tx, rx) = channel(); self.sender.send(Message::GetAgents(tx)).expect("Should success send request"); - let agents_state = rx.recv().map_err(|_| DBError::Timeout)?; + let agents_state = rx.recv()?; Ok(agents_state) } pub fn get_connections(&self) -> Result, DBError> { let (tx, rx) = channel(); self.sender.send(Message::GetConnections(tx)).expect("Should success send request"); - let connections = rx.recv().map_err(|_| DBError::Timeout)?; + let connections = rx.recv()?; Ok(connections) } @@ -326,14 +389,14 @@ impl ServiceSender { pub fn get_agent_extra(&self, node_name: NodeName) -> Result, DBError> { let (tx, rx) = channel(); self.sender.send(Message::GetAgentExtra(node_name, tx)).expect("Should success send request"); - let agent_extra = rx.recv().map_err(|_| DBError::Timeout)?; + let agent_extra = rx.recv()?; Ok(agent_extra) } pub fn get_logs(&self, params: LogQueryParams) -> Result, DBError> { let (tx, rx) = channel(); self.sender.send(Message::GetLogs(params, tx)).expect("Should success send request"); - let logs = rx.recv().map_err(|_| DBError::Timeout)?; + let logs = rx.recv()?; Ok(logs) } @@ -344,7 +407,37 @@ impl ServiceSender { pub fn get_log_targets(&self) -> Result, DBError> { let (tx, rx) = channel(); self.sender.send(Message::GetLogTargets(tx)).expect("Should success"); - let targets = rx.recv().map_err(|_| DBError::Timeout)?; + let targets = rx.recv()?; Ok(targets) } + + pub fn write_network_usage( + &self, + node_name: NodeName, + network_usage: NetworkUsage, + time: chrono::DateTime, + ) { + self.sender + .send(Message::WriteNetworkUsage(node_name, network_usage, time)) + .expect("Should success send request"); + } + + pub fn write_peer_count(&self, node_name: NodeName, peer_count: i32, time: chrono::DateTime) { + self.sender.send(Message::WritePeerCount(node_name, peer_count, time)).expect("Should success send request"); + } + + pub fn get_network_out_all_graph(&self, args: GraphCommonArgs) -> Result, DBError> { + let (tx, rx) = channel(); + self.sender.send(Message::GetGraphNetworkOutAll(args, tx)).expect("Should success send request"); + rx.recv()? + } + + pub fn get_network_out_all_avg_graph( + &self, + args: GraphCommonArgs, + ) -> Result, DBError> { + let (tx, rx) = channel(); + self.sender.send(Message::GetGraphNetworkOutAllAVG(args, tx)).expect("Should success send request"); + rx.recv()? + } } diff --git a/server/src/db/types.rs b/server/src/db/types.rs index 6cdac10..39f9015 100644 --- a/server/src/db/types.rs +++ b/server/src/db/types.rs @@ -3,12 +3,12 @@ use std::collections::HashSet; use std::fmt; use std::hash::{Hash, Hasher}; use std::net::SocketAddr; +use std::sync::mpsc::RecvError; use super::super::common_rpc_types::{ BlackList, BlockId, HardwareInfo, NodeName, NodeStatus, NodeVersion, PendingTransaction, WhiteList, }; - #[derive(PartialEq, Clone, Debug, Default)] pub struct AgentQueryResult { pub name: NodeName, @@ -176,5 +176,11 @@ pub struct Log { #[derive(Debug, Clone)] pub enum Error { - Timeout, + Internal(String), +} + +impl From for Error { + fn from(error: RecvError) -> Self { + Error::Internal(error.to_string()) + } } diff --git a/server/src/frontend/api.rs b/server/src/frontend/api.rs index 20a1584..466159e 100644 --- a/server/src/frontend/api.rs +++ b/server/src/frontend/api.rs @@ -3,10 +3,10 @@ use super::super::common_rpc_types::{NodeName, ShellStartCodeChainRequest}; use super::super::router::Router; use super::super::rpc::{response, RPCError, RPCResponse}; use super::types::{ - Context, DashboardGetNetworkResponse, DashboardNode, LogGetRequest, LogGetResponse, LogGetTargetsResponse, - NodeConnection, NodeGetInfoResponse, + Context, DashboardGetNetworkResponse, DashboardNode, GraphNetworkOutAllAVGResponse, GraphNetworkOutAllResponse, + LogGetRequest, LogGetResponse, LogGetTargetsResponse, NodeConnection, NodeGetInfoResponse, }; -use common_rpc_types::UpdateCodeChainRequest; +use common_rpc_types::{GraphCommonArgs, UpdateCodeChainRequest}; pub fn add_routing(router: &mut Router) { router.add_route("ping", Box::new(ping as fn(Context) -> RPCResponse)); @@ -29,6 +29,19 @@ pub fn add_routing(router: &mut Router) { ); router.add_route("log_getTargets", Box::new(log_get_targets as fn(Context) -> RPCResponse)); router.add_route("log_get", Box::new(log_get as fn(Context, (LogGetRequest,)) -> RPCResponse)); + router.add_route( + "graph_network_out_all_node", + Box::new( + graph_network_out_all_node as fn(Context, (GraphCommonArgs,)) -> RPCResponse, + ), + ); + router.add_route( + "graph_network_out_all_node_avg", + Box::new( + graph_network_out_all_node_avg + as fn(Context, (GraphCommonArgs,)) -> RPCResponse, + ), + ); } fn ping(_: Context) -> RPCResponse { @@ -112,3 +125,24 @@ fn log_get(context: Context, args: (LogGetRequest,)) -> RPCResponse RPCResponse { + let (graph_args,) = args; + + let rows = context.db_service.get_network_out_all_graph(graph_args)?; + response(GraphNetworkOutAllResponse { + rows, + }) +} + +fn graph_network_out_all_node_avg( + context: Context, + args: (GraphCommonArgs,), +) -> RPCResponse { + let (graph_args,) = args; + + let rows = context.db_service.get_network_out_all_avg_graph(graph_args)?; + response(GraphNetworkOutAllAVGResponse { + rows, + }) +} diff --git a/server/src/frontend/types.rs b/server/src/frontend/types.rs index dee55a1..6987a4e 100644 --- a/server/src/frontend/types.rs +++ b/server/src/frontend/types.rs @@ -3,7 +3,8 @@ use std::net::SocketAddr; use super::super::agent; use super::super::common_rpc_types; use super::super::common_rpc_types::{ - BlackList, BlockId, HardwareInfo, HardwareUsage, NodeName, NodeStatus, NodeVersion, PendingTransaction, WhiteList, + BlackList, BlockId, GraphNetworkOutAllAVGRow, GraphNetworkOutAllRow, HardwareInfo, HardwareUsage, NodeName, + NodeStatus, NodeVersion, PendingTransaction, WhiteList, }; use super::super::db; @@ -173,3 +174,15 @@ pub type LogGetRequest = db::LogQueryParams; pub struct LogGetResponse { pub logs: Vec, } + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GraphNetworkOutAllResponse { + pub rows: Vec, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GraphNetworkOutAllAVGResponse { + pub rows: Vec, +} diff --git a/server/src/main.rs b/server/src/main.rs index 49ce134..d8cff3c 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -4,10 +4,13 @@ extern crate log; extern crate chrono; extern crate iron; extern crate jsonrpc_core; +#[macro_use] +extern crate lazy_static; extern crate parking_lot; extern crate postgres; extern crate primitives as cprimitives; extern crate rand; +extern crate regex; extern crate sendgrid; extern crate serde; #[macro_use] diff --git a/ui/images.d.ts b/ui/images.d.ts deleted file mode 100644 index 397cc9b..0000000 --- a/ui/images.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -declare module '*.svg' -declare module '*.png' -declare module '*.jpg' diff --git a/ui/package.json b/ui/package.json index ed8165c..416a9c5 100644 --- a/ui/package.json +++ b/ui/package.json @@ -18,7 +18,8 @@ "moment": "^2.22.2", "node-sass-chokidar": "^1.3.3", "npm-run-all": "^4.1.5", - "react": "^16.5.0", + "plotly.js": "^1.47.4", + "react": "^16.8.6", "react-chartjs-2": "^2.7.4", "react-color": "^2.14.1", "react-confirm-alert": "^2.0.5", @@ -26,9 +27,10 @@ "react-datepicker": "^1.8.0", "react-dom": "^16.5.0", "react-modal": "^3.5.1", + "react-plotly.js": "^2.3.0", "react-redux": "^5.0.7", "react-router-dom": "^4.3.1", - "react-scripts-ts": "2.17.0", + "react-scripts": "^3.0.1", "react-toastify": "^4.3.2", "react-vis-force": "^0.3.1", "react-widgets": "^4.4.10", @@ -42,12 +44,12 @@ "scripts": { "build-css": "node-sass-chokidar --include-path ./src --include-path ./node_modules src/ -o src/", "watch-css": "npm run build-css && node-sass-chokidar --include-path ./src --include-path ./node_modules src/ -o src/ --watch --recursive", - "start-js": "react-scripts-ts start", + "start-js": "react-scripts --max_old_space_size=4096 start", "start": "npm-run-all -p watch-css start-js", - "build-js": "react-scripts-ts build", + "build-js": "react-scripts --max_old_space_size=4096 build", "build": "npm-run-all build-css build-js", - "test": "react-scripts-ts test --env=jsdom", - "eject": "react-scripts-ts eject", + "test": "react-scripts test", + "eject": "react-scripts eject", "lint": "tslint -p tsconfig.json && prettier 'src/**/*.{ts,tsx,scss,json,html,js,jsx}' -l", "fmt": "tslint -p tsconfig.json --fix && prettier 'src/**/*.{ts,tsx,scss,json,html,js,jsx}' --write" }, @@ -61,14 +63,28 @@ "@types/react-copy-to-clipboard": "^4.2.6", "@types/react-datepicker": "^1.1.7", "@types/react-dom": "^16.0.7", - "@types/react-modal": "^3.2.1", + "@types/react-modal": "^3.8.2", + "@types/react-plotly.js": "^2.2.2", "@types/react-redux": "^6.0.7", "@types/react-router-dom": "^4.3.0", "@types/redux-logger": "^3.0.6", + "jest-canvas-mock": "^2.1.0", "prettier": "^1.14.2", "tslint": "^5.11.0", "tslint-config-prettier": "^1.15.0", "tslint-react": "^3.6.0", - "typescript": "^3.0.3" + "typescript": "^3.4.5" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] } } diff --git a/ui/src/RequestAgent.ts b/ui/src/RequestAgent.ts index dc90abb..e5513ca 100644 --- a/ui/src/RequestAgent.ts +++ b/ui/src/RequestAgent.ts @@ -1,12 +1,12 @@ +import { toast } from "react-toastify"; +import { updateChainNetworks } from "./actions/chainNetworks"; +import { updateNodeInfo } from "./actions/nodeInfo"; import { ChainNetworksUpdate, CommonError, NodeUpdateInfo } from "./requests/types"; const WebSocket = require("rpc-websockets").Client; -import { toast } from "react-toastify"; -import { updateChainNetworks } from "./actions/chainNetworks"; -import { updateNodeInfo } from "./actions/nodeInfo"; export interface JsonRPCError { code: number; diff --git a/ui/src/actions/graph.ts b/ui/src/actions/graph.ts new file mode 100644 index 0000000..4b57e62 --- /dev/null +++ b/ui/src/actions/graph.ts @@ -0,0 +1,125 @@ +import moment from "moment"; +import { ReducerConfigure } from "../reducers"; +import RequestAgent from "../RequestAgent"; +import { + GraphNetworkOutAllAVGRow, + GraphNetworkOutAllRow +} from "../requests/types"; + +export type GraphAction = + | SetNetworkOutAllGraph + | ChangeNetworkOutAllFilters + | SetNetworkOutAllAVGGraph + | ChangeNetworkOutAllAVGFilters; + +export interface SetNetworkOutAllGraph { + type: "SetNetworkOutAllGraph"; + data: GraphNetworkOutAllRow[]; +} + +const setNetworkOutAllGraph = (data: GraphNetworkOutAllRow[]) => ({ + type: "SetNetworkOutAllGraph", + data +}); + +export interface ChangeNetworkOutAllFilters { + type: "ChangeNetworkOutAllFilters"; + data: { + time: { + fromTime: number; + toTime: number; + }; + }; +} + +export const changeNetworkOutAllFilters = (params: { + time: { + fromTime: number; + toTime: number; + }; +}) => { + return async (dispatch: any, getState: () => ReducerConfigure) => { + dispatch({ + type: "ChangeNetworkOutAllFilters", + data: { + time: params.time + } + }); + dispatch(fetchNetworkOutAllGraph()); + }; +}; + +export const fetchNetworkOutAllGraph = () => { + return async (dispatch: any, getState: () => ReducerConfigure) => { + const response = await RequestAgent.getInstance().call<{ + rows: GraphNetworkOutAllRow[]; + }>("graph_network_out_all_node", [ + { + from: moment + .unix(getState().graphReducer.networkOutAllGraph.time.fromTime) + .toISOString(), + to: moment + .unix(getState().graphReducer.networkOutAllGraph.time.toTime) + .toISOString(), + period: "minutes5" + } + ]); + dispatch(setNetworkOutAllGraph(response.rows)); + }; +}; + +export interface SetNetworkOutAllAVGGraph { + type: "SetNetworkOutAllAVGGraph"; + data: GraphNetworkOutAllAVGRow[]; +} + +const setNetworkOutAllAVGGraph = (data: GraphNetworkOutAllAVGRow[]) => ({ + type: "SetNetworkOutAllAVGGraph", + data +}); + +export interface ChangeNetworkOutAllAVGFilters { + type: "ChangeNetworkOutAllAVGFilters"; + data: { + time: { + fromTime: number; + toTime: number; + }; + }; +} + +export const changeNetworkOutAllAVGFilters = (params: { + time: { + fromTime: number; + toTime: number; + }; +}) => { + return async (dispatch: any, getState: () => ReducerConfigure) => { + dispatch({ + type: "ChangeNetworkOutAllAVGFilters", + data: { + time: params.time + } + }); + dispatch(fetchNetworkOutAllAVGGraph()); + }; +}; + +export const fetchNetworkOutAllAVGGraph = () => { + return async (dispatch: any, getState: () => ReducerConfigure) => { + const response = await RequestAgent.getInstance().call<{ + rows: GraphNetworkOutAllAVGRow[]; + }>("graph_network_out_all_node_avg", [ + { + from: moment + .unix(getState().graphReducer.networkOutAllAVGGraph.time.fromTime) + .toISOString(), + to: moment + .unix(getState().graphReducer.networkOutAllAVGGraph.time.toTime) + .toISOString(), + period: "minutes5" + } + ]); + dispatch(setNetworkOutAllAVGGraph(response.rows)); + }; +}; diff --git a/ui/src/actions/log.ts b/ui/src/actions/log.ts index 28d9706..4003c53 100644 --- a/ui/src/actions/log.ts +++ b/ui/src/actions/log.ts @@ -1,4 +1,4 @@ -import * as moment from "moment"; +import moment from "moment"; import Log from "../components/Log/Log"; import { ReducerConfigure } from "../reducers"; import { LogState } from "../reducers/log"; @@ -189,7 +189,7 @@ export const setAutoRefresh = (isOn: boolean) => { changeFilters({ time: { fromTime: logReducer.time.fromTime, - toTime: moment().unix() + toTime: moment.now() }, orderBy: "DESC", setToTime: true diff --git a/ui/src/components/App/App.test.tsx b/ui/src/components/App/App.test.tsx index bd7e464..2be993f 100644 --- a/ui/src/components/App/App.test.tsx +++ b/ui/src/components/App/App.test.tsx @@ -1,9 +1,24 @@ import * as React from "react"; import * as ReactDOM from "react-dom"; +import { Provider } from "react-redux"; +import { applyMiddleware, createStore } from "redux"; +import { composeWithDevTools } from "redux-devtools-extension"; +import thunkMiddleware from "redux-thunk"; +import appReducer from "../../reducers"; import App from "./App"; it("renders without crashing", () => { const div = document.createElement("div"); - ReactDOM.render(, div); + const composeEnhancers = composeWithDevTools({}); + const store = createStore( + appReducer, + composeEnhancers(applyMiddleware(thunkMiddleware)) + ); + ReactDOM.render( + + + , + div + ); ReactDOM.unmountComponentAtNode(div); }); diff --git a/ui/src/components/App/App.tsx b/ui/src/components/App/App.tsx index ee07f41..0f6426c 100644 --- a/ui/src/components/App/App.tsx +++ b/ui/src/components/App/App.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import * as ReactModal from "react-modal"; +import ReactModal from "react-modal"; import { connect, DispatchProp } from "react-redux"; import { BrowserRouter as Router, Route } from "react-router-dom"; import { ToastContainer } from "react-toastify"; @@ -7,6 +7,7 @@ import "react-toastify/dist/ReactToastify.css"; import RequestAgent from "../../RequestAgent"; import Dashboard from "../Dashboard/Dashboard"; import { GlobalNavigationBar } from "../GlobalNavigationBar/GlobalNavigationBar"; +import Graph from "../Graph/Graph"; import { Header } from "../Header/Header"; import Log from "../Log/Log"; import NodeList from "../NodeList/NodeList"; @@ -15,7 +16,9 @@ import "./App.css"; class App extends React.Component { public componentDidMount() { - ReactModal.setAppElement("#app"); + if (process.env.NODE_ENV !== "test") { + ReactModal.setAppElement("#app"); + } } public componentWillMount() { RequestAgent.getInstance().setDispatch(this.props.dispatch); @@ -34,6 +37,7 @@ class App extends React.Component { + diff --git a/ui/src/components/Dashboard/ConnectGraphContainer/ConnectionGraphContainer.tsx b/ui/src/components/Dashboard/ConnectGraphContainer/ConnectionGraphContainer.tsx index f226998..3335e96 100644 --- a/ui/src/components/Dashboard/ConnectGraphContainer/ConnectionGraphContainer.tsx +++ b/ui/src/components/Dashboard/ConnectGraphContainer/ConnectionGraphContainer.tsx @@ -13,10 +13,6 @@ interface Props { onDeselect: () => void; } export class ConnectionGraphContainer extends React.Component { - constructor(props: Props) { - super(props); - } - public render() { const { className, chainNetworks, onSelectNode, onDeselect } = this.props; return ( diff --git a/ui/src/components/Dashboard/Dashboard.tsx b/ui/src/components/Dashboard/Dashboard.tsx index 2eeb155..ce3320d 100644 --- a/ui/src/components/Dashboard/Dashboard.tsx +++ b/ui/src/components/Dashboard/Dashboard.tsx @@ -306,6 +306,7 @@ class Dashboard extends React.Component { Install agent diff --git a/ui/src/components/GlobalNavigationBar/GlobalNavigationBar.tsx b/ui/src/components/GlobalNavigationBar/GlobalNavigationBar.tsx index e73db08..f8f6d7e 100644 --- a/ui/src/components/GlobalNavigationBar/GlobalNavigationBar.tsx +++ b/ui/src/components/GlobalNavigationBar/GlobalNavigationBar.tsx @@ -1,4 +1,5 @@ import { + faChartLine, faCoins, faHistory, faRetweet, @@ -9,7 +10,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import * as React from "react"; import { Link, withRouter } from "react-router-dom"; import "./GlobalNavigationBar.css"; -import * as arrowImg from "./img/arrow.svg"; +import { ReactComponent as ArrowImg } from "./img/arrow.svg"; const getGnbMenu = ( url: string, title: string, @@ -21,7 +22,7 @@ const getGnbMenu = (
{isSelected ? ( - + ) : null}
@@ -48,6 +49,7 @@ export const GlobalNavigationBar = withRouter(props => { )} {getGnbMenu("rpc", "RPC", faRetweet, pathname === "/rpc")} {getGnbMenu("log", "Log", faHistory, pathname === "/log")} + {getGnbMenu("graph", "Graph", faChartLine, pathname === "/graph")}
); diff --git a/ui/src/components/Graph/Graph.tsx b/ui/src/components/Graph/Graph.tsx new file mode 100644 index 0000000..c94f21e --- /dev/null +++ b/ui/src/components/Graph/Graph.tsx @@ -0,0 +1,15 @@ +import { Component } from "react"; +import * as React from "react"; +import NetworkOutAllAVGGraph from "./NetworkOutAllAVGGraph/NetworkOutAllAVGGraph"; +import NetworkOutAllGraph from "./NetworkOutAllGraph/NetworkOutAllGraph"; + +export default class Graph extends Component { + public render() { + return ( +
+ + +
+ ); + } +} diff --git a/ui/src/components/Graph/NetworkOutAllAVGGraph/NetworkOutAllAVGGraph.scss b/ui/src/components/Graph/NetworkOutAllAVGGraph/NetworkOutAllAVGGraph.scss new file mode 100644 index 0000000..01144c3 --- /dev/null +++ b/ui/src/components/Graph/NetworkOutAllAVGGraph/NetworkOutAllAVGGraph.scss @@ -0,0 +1,27 @@ +.network-out-all-avg-graph { + background-color: white; + width: 1100px; + padding: 10px; + margin: 10px; + + .plot { + display: block; + } +} + +.to-time { + display: inline-block; + margin: 5px; + + label { + margin-right: 5px; + } +} +.from-time { + display: inline-block; + margin: 5px; + + label { + margin-right: 5px; + } +} diff --git a/ui/src/components/Graph/NetworkOutAllAVGGraph/NetworkOutAllAVGGraph.tsx b/ui/src/components/Graph/NetworkOutAllAVGGraph/NetworkOutAllAVGGraph.tsx new file mode 100644 index 0000000..6d7176c --- /dev/null +++ b/ui/src/components/Graph/NetworkOutAllAVGGraph/NetworkOutAllAVGGraph.tsx @@ -0,0 +1,147 @@ +import * as _ from "lodash"; +import moment from "moment"; +import { PlotData } from "plotly.js"; +import { Component } from "react"; +import * as React from "react"; +import DatePicker from "react-datepicker"; +import "react-datepicker/dist/react-datepicker.css"; +import Plot from "react-plotly.js"; +import { connect } from "react-redux"; +import { Label } from "reactstrap"; +import { + changeNetworkOutAllAVGFilters, + fetchNetworkOutAllAVGGraph +} from "../../../actions/graph"; +import { ReducerConfigure } from "../../../reducers"; +import { GraphNetworkOutAllAVGRow } from "../../../requests/types"; +import "./NetworkOutAllAVGGraph.css"; + +interface StateProps { + fromTime: number; + toTime: number; + data: GraphNetworkOutAllAVGRow[]; +} + +interface DispatchProps { + dispatch: any; +} + +type Props = StateProps & DispatchProps; +class NetworkOutAllAVGGraph extends Component { + public constructor(props: any) { + super(props); + } + + public componentDidMount(): void { + this.props.dispatch(fetchNetworkOutAllAVGGraph()); + } + + public render() { + const { fromTime, toTime } = this.props; + const rowsByNodeName = _.groupBy(this.props.data, row => row.nodeName); + return ( +
+
+ + +
+
+ + +
+
+ >( + rowsByNodeName, + (rows, nodeName) => ({ + x: _.map(rows, row => row.time), + y: _.map(rows, row => row.value), + type: "scatter", + mode: "lines+markers", + name: nodeName, + showlegend: true + }) + )} + layout={{ width: 1000, height: 600, title: "Network Out All AVG" }} + /> +
+
+ ); + } + + private handleChangeFromTime = (date: moment.Moment) => { + this.props.dispatch( + changeNetworkOutAllAVGFilters({ + time: { + fromTime: date.unix(), + toTime: this.props.toTime + } + }) + ); + }; + private handleChangeFromTimeRawDate = (event: any) => { + const newDate = moment(event.target.value); + if (newDate.isValid()) { + this.props.dispatch( + changeNetworkOutAllAVGFilters({ + time: { + fromTime: newDate.unix(), + toTime: this.props.toTime + } + }) + ); + } + }; + + private handleChangeToTime = (date: moment.Moment) => { + this.props.dispatch( + changeNetworkOutAllAVGFilters({ + time: { + fromTime: this.props.fromTime, + toTime: date.unix() + } + }) + ); + }; + private handleChangeToTimeRawDate = (event: any) => { + const newDate = moment(event.target.value); + if (newDate.isValid()) { + this.props.dispatch( + changeNetworkOutAllAVGFilters({ + time: { + fromTime: this.props.toTime, + toTime: newDate.unix() + } + }) + ); + } + }; +} + +const mapStateToProps = (state: ReducerConfigure) => ({ + data: state.graphReducer.networkOutAllAVGGraph.data, + fromTime: state.graphReducer.networkOutAllAVGGraph.time.fromTime, + toTime: state.graphReducer.networkOutAllAVGGraph.time.toTime +}); + +export default connect(mapStateToProps)(NetworkOutAllAVGGraph); diff --git a/ui/src/components/Graph/NetworkOutAllGraph/NetworkOutAllGraph.scss b/ui/src/components/Graph/NetworkOutAllGraph/NetworkOutAllGraph.scss new file mode 100644 index 0000000..1570c01 --- /dev/null +++ b/ui/src/components/Graph/NetworkOutAllGraph/NetworkOutAllGraph.scss @@ -0,0 +1,27 @@ +.network-out-all-graph { + background-color: white; + width: 1100px; + padding: 10px; + margin: 10px; + + plot { + display: block; + } +} + +.to-time { + display: inline-block; + margin: 5px; + + label { + margin-right: 5px; + } +} +.from-time { + display: inline-block; + margin: 5px; + + label { + margin-right: 5px; + } +} diff --git a/ui/src/components/Graph/NetworkOutAllGraph/NetworkOutAllGraph.tsx b/ui/src/components/Graph/NetworkOutAllGraph/NetworkOutAllGraph.tsx new file mode 100644 index 0000000..1299b9e --- /dev/null +++ b/ui/src/components/Graph/NetworkOutAllGraph/NetworkOutAllGraph.tsx @@ -0,0 +1,149 @@ +import * as _ from "lodash"; +import moment from "moment"; +import { PlotData } from "plotly.js"; +import { Component } from "react"; +import * as React from "react"; +import DatePicker from "react-datepicker"; +import "react-datepicker/dist/react-datepicker.css"; +import Plot from "react-plotly.js"; +import { connect } from "react-redux"; +import { Label } from "reactstrap"; +import { + changeNetworkOutAllFilters, + fetchNetworkOutAllGraph +} from "../../../actions/graph"; +import { ReducerConfigure } from "../../../reducers"; +import { GraphNetworkOutAllRow } from "../../../requests/types"; +import "./NetworkOutAllGraph.css"; + +interface StateProps { + fromTime: number; + toTime: number; + data: GraphNetworkOutAllRow[]; +} + +interface DispatchProps { + dispatch: any; +} + +type Props = StateProps & DispatchProps; +class NetworkOutAllGraph extends Component { + public constructor(props: any) { + super(props); + } + + public componentDidMount(): void { + this.props.dispatch(fetchNetworkOutAllGraph()); + } + + public render() { + const { fromTime, toTime } = this.props; + const rowsByNodeName = _.groupBy(this.props.data, row => row.nodeName); + return ( +
+
+ + +
+
+ + +
+
+ >( + rowsByNodeName, + (rows, nodeName) => ({ + x: _.map(rows, row => row.time), + y: _.map(rows, row => row.value), + type: "scatter", + mode: "lines+markers", + name: nodeName, + showlegend: true + }) + )} + layout={{ width: 1000, height: 600, title: "Network Out All" }} + /> +
+
+ ); + } + + private handleChangeFromTime = (date: moment.Moment) => { + this.props.dispatch( + changeNetworkOutAllFilters({ + time: { + fromTime: date.unix(), + toTime: this.props.toTime + } + }) + ); + }; + private handleChangeFromTimeRawDate = (event: any) => { + const newDate = moment(event.target.value); + if (newDate.isValid()) { + this.props.dispatch( + changeNetworkOutAllFilters({ + time: { + fromTime: newDate.unix(), + toTime: this.props.toTime + } + }) + ); + } + }; + + private handleChangeToTime = (date: moment.Moment) => { + this.props.dispatch( + changeNetworkOutAllFilters({ + time: { + fromTime: this.props.fromTime, + toTime: date.unix() + } + }) + ); + }; + private handleChangeToTimeRawDate = (event: any) => { + const newDate = moment(event.target.value); + if (newDate.isValid()) { + this.props.dispatch( + changeNetworkOutAllFilters({ + time: { + fromTime: this.props.toTime, + toTime: newDate.unix() + } + }) + ); + } + }; +} + +const mapStateToProps = (state: ReducerConfigure) => { + return { + data: state.graphReducer.networkOutAllGraph.data, + fromTime: state.graphReducer.networkOutAllGraph.time.fromTime, + toTime: state.graphReducer.networkOutAllGraph.time.toTime + }; +}; + +export default connect(mapStateToProps)(NetworkOutAllGraph); diff --git a/ui/src/components/Header/Header.tsx b/ui/src/components/Header/Header.tsx index 33a2975..5ab33ef 100644 --- a/ui/src/components/Header/Header.tsx +++ b/ui/src/components/Header/Header.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import { withRouter } from "react-router-dom"; import "./Header.css"; -import * as Logo from "./img/logo.png"; +import Logo from "./img/logo.png"; const getTitle = (pathName: string) => { if (pathName === "/") { @@ -25,7 +25,7 @@ export const Header = withRouter(props => { return (
- +

diff --git a/ui/src/components/Log/LogViewer/LogItem/LogItem.tsx b/ui/src/components/Log/LogViewer/LogItem/LogItem.tsx index 7c89b25..c50c5de 100644 --- a/ui/src/components/Log/LogViewer/LogItem/LogItem.tsx +++ b/ui/src/components/Log/LogViewer/LogItem/LogItem.tsx @@ -4,9 +4,9 @@ import { faCopy } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import * as moment from "moment"; +import moment from "moment"; import * as React from "react"; -import * as CopyToClipboard from "react-copy-to-clipboard"; +import CopyToClipboard from "react-copy-to-clipboard"; import { connect } from "react-redux"; import { ReducerConfigure } from "../../../../reducers"; import { Log } from "../../../../requests/types"; diff --git a/ui/src/components/Log/TopFilter/TopFilter.tsx b/ui/src/components/Log/TopFilter/TopFilter.tsx index 82ccf15..5896962 100644 --- a/ui/src/components/Log/TopFilter/TopFilter.tsx +++ b/ui/src/components/Log/TopFilter/TopFilter.tsx @@ -1,6 +1,6 @@ import { faSearch } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import * as moment from "moment"; +import moment from "moment"; import * as React from "react"; import DatePicker from "react-datepicker"; import "react-datepicker/dist/react-datepicker.css"; @@ -25,9 +25,6 @@ interface DispatchProps { type Props = StateProps & DispatchProps; class TopFilter extends React.Component { - constructor(props: any) { - super(props); - } public componentWillUnmount() { this.props.dispatch(setAutoRefresh(false)); } diff --git a/ui/src/components/NodeList/NodeDetailContainer/NodeDetail/NodeDetail.tsx b/ui/src/components/NodeList/NodeDetailContainer/NodeDetail/NodeDetail.tsx index 05b4793..fb7896c 100644 --- a/ui/src/components/NodeList/NodeDetailContainer/NodeDetail/NodeDetail.tsx +++ b/ui/src/components/NodeList/NodeDetailContainer/NodeDetail/NodeDetail.tsx @@ -1,6 +1,10 @@ +import { faSpinner } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import * as _ from "lodash"; import * as React from "react"; import { Doughnut, HorizontalBar } from "react-chartjs-2"; +// import { Transaction } from "codechain-sdk/lib/core/classes"; +import { toast } from "react-toastify"; import { JsonRPCError } from "../../../../RequestAgent"; import { Apis } from "../../../../requests"; import { @@ -8,15 +12,11 @@ import { NodeStatus, UpdateCodeChainRequest } from "../../../../requests/types"; +import { getStatusClass } from "../../../../utils/getStatusClass"; +import UpgradeNodeModal from "../../UpgradeNodeModal/UpgradeNodeModal"; import "./NodeDetail.css"; import StartNodeModal from "./StartNodeModal/StartNodeModal"; const { confirmAlert } = require("react-confirm-alert"); -import { faSpinner } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -// import { Transaction } from "codechain-sdk/lib/core/classes"; -import { toast } from "react-toastify"; -import { getStatusClass } from "../../../../utils/getStatusClass"; -import UpgradeNodeModal from "../../UpgradeNodeModal/UpgradeNodeModal"; interface Props { nodeInfo: NodeInfo; @@ -175,6 +175,7 @@ export default class NodeDetail extends React.Component {
diff --git a/ui/src/components/NodeList/NodeDetailContainer/NodeDetail/StartNodeModal/StartNodeModal.tsx b/ui/src/components/NodeList/NodeDetailContainer/NodeDetail/StartNodeModal/StartNodeModal.tsx index 2c6aefb..28edb33 100644 --- a/ui/src/components/NodeList/NodeDetailContainer/NodeDetail/StartNodeModal/StartNodeModal.tsx +++ b/ui/src/components/NodeList/NodeDetailContainer/NodeDetail/StartNodeModal/StartNodeModal.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import * as Modal from "react-modal"; +import Modal from "react-modal"; import { Form, Label } from "reactstrap"; import "./StartNodeModal.css"; const customStyles = { diff --git a/ui/src/components/NodeList/NodeList.tsx b/ui/src/components/NodeList/NodeList.tsx index 47d6579..6b0df3f 100644 --- a/ui/src/components/NodeList/NodeList.tsx +++ b/ui/src/components/NodeList/NodeList.tsx @@ -8,9 +8,6 @@ interface Props { match: any; } export default class NodeList extends Component { - constructor(props: Props) { - super(props); - } public render() { const { match } = this.props; return ( diff --git a/ui/src/components/NodeList/NodeListContainer/NodeItem/NodeItem.tsx b/ui/src/components/NodeList/NodeListContainer/NodeItem/NodeItem.tsx index 077f106..ddfbd89 100644 --- a/ui/src/components/NodeList/NodeListContainer/NodeItem/NodeItem.tsx +++ b/ui/src/components/NodeList/NodeListContainer/NodeItem/NodeItem.tsx @@ -32,6 +32,7 @@ const NodeItem = (props: Props) => {