aboutsummaryrefslogtreecommitdiff
path: root/frontend/src
diff options
context:
space:
mode:
authorEmiliano Ciavatta2020-12-25 13:48:11 +0000
committerEmiliano Ciavatta2020-12-25 13:48:11 +0000
commitf8f3c91a8722e0cb5cc78f21ad163e8165ac5979 (patch)
treeb6319bc00d886bf0c3a4aeaff19cae378a9cf47a /frontend/src
parentad0c27ae1fde7d24c24a34a7c305edeb0ea9974e (diff)
Add stats page, add /api/statistics/totals
Diffstat (limited to 'frontend/src')
-rw-r--r--frontend/src/components/Header.js4
-rw-r--r--frontend/src/components/pages/MainPage.js4
-rw-r--r--frontend/src/components/panels/ConfigPane.js39
-rw-r--r--frontend/src/components/panels/StatsPane.js274
-rw-r--r--frontend/src/components/panels/StatsPane.scss21
-rw-r--r--frontend/src/utils.js4
6 files changed, 302 insertions, 44 deletions
diff --git a/frontend/src/components/Header.js b/frontend/src/components/Header.js
index 4db05a5..e3fafc4 100644
--- a/frontend/src/components/Header.js
+++ b/frontend/src/components/Header.js
@@ -85,8 +85,8 @@ class Header extends Component {
<Link to={"/services" + this.props.location.search}>
<ButtonField variant="indigo" name="services" bordered/>
</Link>
- <Link to={"/config" + this.props.location.search}>
- <ButtonField variant="blue" name="config" bordered/>
+ <Link to={"/stats" + this.props.location.search}>
+ <ButtonField variant="blue" name="stats" bordered/>
</Link>
</div>
}
diff --git a/frontend/src/components/pages/MainPage.js b/frontend/src/components/pages/MainPage.js
index 6058edc..f005f44 100644
--- a/frontend/src/components/pages/MainPage.js
+++ b/frontend/src/components/pages/MainPage.js
@@ -21,13 +21,13 @@ import "react-reflex/styles.css"
import {Route, Switch} from "react-router-dom";
import Filters from "../dialogs/Filters";
import Header from "../Header";
-import ConfigPane from "../panels/ConfigPane";
import Connections from "../panels/ConnectionsPane";
import MainPane from "../panels/MainPane";
import PcapsPane from "../panels/PcapsPane";
import RulesPane from "../panels/RulesPane";
import SearchPane from "../panels/SearchPane";
import ServicesPane from "../panels/ServicesPane";
+import StatsPane from "../panels/StatsPane";
import StreamsPane from "../panels/StreamsPane";
import Timeline from "../Timeline";
import "./MainPage.scss";
@@ -76,7 +76,7 @@ class MainPage extends Component {
<Route path="/pcaps" children={<PcapsPane/>}/>
<Route path="/rules" children={<RulesPane/>}/>
<Route path="/services" children={<ServicesPane/>}/>
- <Route path="/config" children={<ConfigPane/>}/>
+ <Route path="/stats" children={<StatsPane/>}/>
<Route exact path="/connections/:id"
children={<StreamsPane connection={this.state.selectedConnection}/>}/>
<Route children={<MainPane version={this.props.version}/>}/>
diff --git a/frontend/src/components/panels/ConfigPane.js b/frontend/src/components/panels/ConfigPane.js
deleted file mode 100644
index 9710abd..0000000
--- a/frontend/src/components/panels/ConfigPane.js
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * This file is part of caronte (https://github.com/eciavatta/caronte).
- * Copyright (c) 2020 Emiliano Ciavatta.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-import React, {Component} from "react";
-import "./common.scss";
-
-class ConfigPane extends Component {
-
- state = {};
-
- render() {
- return (
- <div className="pane-container">
- <div className="main-pane">
- <div className="pane-section">
- Not implemented
- </div>
- </div>
- </div>
- );
- }
-
-}
-
-export default ConfigPane;
diff --git a/frontend/src/components/panels/StatsPane.js b/frontend/src/components/panels/StatsPane.js
new file mode 100644
index 0000000..dd774d0
--- /dev/null
+++ b/frontend/src/components/panels/StatsPane.js
@@ -0,0 +1,274 @@
+/*
+ * This file is part of caronte (https://github.com/eciavatta/caronte).
+ * Copyright (c) 2020 Emiliano Ciavatta.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import React, {Component} from "react";
+import Table from "react-bootstrap/Table";
+import backend from "../../backend";
+import dispatcher from "../../dispatcher";
+import {formatSize} from "../../utils";
+import ButtonField from "../fields/ButtonField";
+import CopyLinkPopover from "../objects/CopyLinkPopover";
+import LinkPopover from "../objects/LinkPopover";
+import "./common.scss";
+import "./StatsPane.scss";
+
+class StatsPane extends Component {
+
+ state = {
+ rules: []
+ };
+
+ componentDidMount() {
+ this.loadStats();
+ this.loadResourcesStats();
+ this.loadRules();
+ dispatcher.register("notifications", this.handleNotifications);
+ document.title = "caronte:~/stats$";
+ this.intervalToken = setInterval(() => this.loadResourcesStats(), 3000);
+ }
+
+ componentWillUnmount() {
+ dispatcher.unregister(this.handleNotifications);
+ clearInterval(this.intervalToken);
+ }
+
+ handleNotifications = (payload) => {
+ if (payload.event.startsWith("pcap")) {
+ this.loadStats();
+ } else if (payload.event.startsWith("rules")) {
+ this.loadRules();
+ }
+ };
+
+ loadStats = () => {
+ backend.get("/api/statistics/totals")
+ .then((res) => this.setState({stats: res.json, statsStatusCode: res.status}))
+ .catch((res) => this.setState({
+ stats: res.json, statsStatusCode: res.status,
+ statsResponse: JSON.stringify(res.json)
+ }));
+ };
+
+ loadResourcesStats = () => {
+ backend.get("/api/resources/system")
+ .then((res) => this.setState({resourcesStats: res.json, resourcesStatsStatusCode: res.status}))
+ .catch((res) => this.setState({
+ resourcesStats: res.json, resourcesStatsStatusCode: res.status,
+ resourcesStatsResponse: JSON.stringify(res.json)
+ }));
+ };
+
+ loadRules = () => {
+ backend.get("/api/rules").then((res) => this.setState({rules: res.json}));
+ };
+
+ render() {
+ const s = this.state.stats;
+ const rs = this.state.resourcesStats;
+
+ const ports = s ? Object.keys(s["connections_per_service"]) : [];
+ let connections = 0, clientBytes = 0, serverBytes = 0, totalBytes = 0, duration = 0;
+ let servicesStats = ports.map((port) => {
+ connections += s["connections_per_service"][port];
+ clientBytes += s["client_bytes_per_service"][port];
+ serverBytes += s["server_bytes_per_service"][port];
+ totalBytes += s["total_bytes_per_service"][port];
+ duration += s["duration_per_service"][port];
+
+ return <tr key={port} className="row-small row-clickable">
+ <td>{port}</td>
+ <td>{formatSize(s["connections_per_service"][port])}</td>
+ <td>{formatSize(s["client_bytes_per_service"][port])}B</td>
+ <td>{formatSize(s["server_bytes_per_service"][port])}B</td>
+ <td>{formatSize(s["total_bytes_per_service"][port])}B</td>
+ <td>{formatSize(s["duration_per_service"][port] / 1000)}s</td>
+ </tr>;
+ });
+ servicesStats.push(<tr key="totals" className="row-small row-clickable font-weight-bold">
+ <td>totals</td>
+ <td>{formatSize(connections)}</td>
+ <td>{formatSize(clientBytes)}B</td>
+ <td>{formatSize(serverBytes)}B</td>
+ <td>{formatSize(totalBytes)}B</td>
+ <td>{formatSize(duration / 1000)}s</td>
+ </tr>);
+
+ const rulesStats = this.state.rules.map((r) =>
+ <tr key={r.id} className="row-small row-clickable">
+ <td><CopyLinkPopover text={r["id"].substring(0, 8)} value={r["id"]}/></td>
+ <td>{r["name"]}</td>
+ <td><ButtonField name={r["color"]} color={r["color"]} small/></td>
+ <td>{formatSize(s && s["matched_rules"] && s["matched_rules"][r.id] ? s["matched_rules"][r.id] : 0)}</td>
+ </tr>
+ );
+
+ const cpuStats = (rs ? rs["cpu_times"] : []).map((cpu, index) =>
+ <tr key={cpu["cpu"]} className="row-small row-clickable">
+ <td>{cpu["cpu"]}</td>
+ <td>{cpu["user"]}</td>
+ <td>{cpu["system"]}</td>
+ <td>{cpu["idle"]}</td>
+ <td>{cpu["nice"]}</td>
+ <td>{cpu["iowait"]}</td>
+ <td>{rs["cpu_percents"][index].toFixed(2)} %</td>
+ </tr>
+ );
+
+ return (
+ <div className="pane-container stats-pane">
+ <div className="pane-section stats-list">
+ <div className="section-header">
+ <span className="api-request">GET /api/statistics/totals</span>
+ <span className="api-response"><LinkPopover text={this.state.statsStatusCode}
+ content={this.state.statsResponse}
+ placement="left"/></span>
+ </div>
+
+ <div className="section-content">
+ <div className="section-table">
+ <Table borderless size="sm">
+ <thead>
+ <tr>
+ <th>service</th>
+ <th>connections</th>
+ <th>client_bytes</th>
+ <th>server_bytes</th>
+ <th>total_bytes</th>
+ <th>duration</th>
+ </tr>
+ </thead>
+ <tbody>
+ {servicesStats}
+ </tbody>
+ </Table>
+ </div>
+
+ <div className="section-table">
+ <Table borderless size="sm">
+ <thead>
+ <tr>
+ <th>rule_id</th>
+ <th>rule_name</th>
+ <th>rule_color</th>
+ <th>occurrences</th>
+ </tr>
+ </thead>
+ <tbody>
+ {rulesStats}
+ </tbody>
+ </Table>
+ </div>
+ </div>
+ </div>
+
+ <div className="pane-section stats-list" style={{"paddingTop": "10px"}}>
+ <div className="section-header">
+ <span className="api-request">GET /api/resources/system</span>
+ <span className="api-response"><LinkPopover text={this.state.resourcesStatsStatusCode}
+ content={this.state.resourcesStatsResponse}
+ placement="left"/></span>
+ </div>
+
+ <div className="section-content">
+ <div className="section-table">
+ <Table borderless size="sm">
+ <thead>
+ <tr>
+ <th>type</th>
+ <th>total</th>
+ <th>used</th>
+ <th>free</th>
+ <th>shared</th>
+ <th>buff/cache</th>
+ <th>available</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr className="row-small row-clickable">
+ <td>mem</td>
+ <td>{rs && formatSize(rs["virtual_memory"]["total"])}</td>
+ <td>{rs && formatSize(rs["virtual_memory"]["used"])}</td>
+ <td>{rs && formatSize(rs["virtual_memory"]["free"])}</td>
+ <td>{rs && formatSize(rs["virtual_memory"]["shared"])}</td>
+ <td>{rs && formatSize(rs["virtual_memory"]["cached"])}</td>
+ <td>{rs && formatSize(rs["virtual_memory"]["available"])}</td>
+ </tr>
+ <tr className="row-small row-clickable">
+ <td>swap</td>
+ <td>{rs && formatSize(rs["virtual_memory"]["swaptotal"])}</td>
+ <td>{rs && formatSize(rs["virtual_memory"]["swaptotal"])}</td>
+ <td>{rs && formatSize(rs["virtual_memory"]["swapfree"])}</td>
+ <td>-</td>
+ <td>-</td>
+ <td>-</td>
+ </tr>
+ </tbody>
+ </Table>
+ </div>
+
+ <div className="section-table">
+ <Table borderless size="sm">
+ <thead>
+ <tr>
+ <th>cpu</th>
+ <th>user</th>
+ <th>system</th>
+ <th>idle</th>
+ <th>nice</th>
+ <th>iowait</th>
+ <th>used_percent</th>
+ </tr>
+ </thead>
+ <tbody>
+ {cpuStats}
+ </tbody>
+ </Table>
+ </div>
+
+ <div className="section-table">
+ <Table borderless size="sm">
+ <thead>
+ <tr>
+ <th>disk_path</th>
+ <th>fs_type</th>
+ <th>total</th>
+ <th>free</th>
+ <th>used</th>
+ <th>used_percent</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr className="row-small row-clickable">
+ <td>{rs && rs["disk_usage"]["path"]}</td>
+ <td>{rs && rs["disk_usage"]["fstype"]}</td>
+ <td>{rs && formatSize(rs["disk_usage"]["total"])}</td>
+ <td>{rs && formatSize(rs["disk_usage"]["free"])}</td>
+ <td>{rs && formatSize(rs["disk_usage"]["used"])}</td>
+ <td>{rs && rs["disk_usage"]["usedPercent"].toFixed(2)} %</td>
+ </tr>
+ </tbody>
+ </Table>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+}
+
+export default StatsPane;
diff --git a/frontend/src/components/panels/StatsPane.scss b/frontend/src/components/panels/StatsPane.scss
new file mode 100644
index 0000000..24a0a33
--- /dev/null
+++ b/frontend/src/components/panels/StatsPane.scss
@@ -0,0 +1,21 @@
+@import "../../colors.scss";
+
+.stats-pane {
+ display: flex;
+ flex-direction: column;
+
+ .stats-list {
+ position: relative;
+ flex: 1;
+ height: 50%;
+
+ .section-content {
+ overflow-y: scroll;
+ height: calc(100% - 10px);
+ }
+
+ .section-table {
+ margin-bottom: 10px;
+ }
+ }
+}
diff --git a/frontend/src/utils.js b/frontend/src/utils.js
index 445e576..4849a92 100644
--- a/frontend/src/utils.js
+++ b/frontend/src/utils.js
@@ -120,8 +120,10 @@ export function formatSize(size) {
return `${size}`;
} else if (size < 1000000) {
return `${(size / 1000).toFixed(1)}K`;
- } else {
+ } else if (size < 1000000000) {
return `${(size / 1000000).toFixed(1)}M`;
+ } else {
+ return `${(size / 1000000000).toFixed(1)}Gi`;
}
}