aboutsummaryrefslogtreecommitdiff
path: root/frontend/src/components/Timeline.jsx
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/components/Timeline.jsx')
-rw-r--r--frontend/src/components/Timeline.jsx405
1 files changed, 0 insertions, 405 deletions
diff --git a/frontend/src/components/Timeline.jsx b/frontend/src/components/Timeline.jsx
deleted file mode 100644
index faaa8de..0000000
--- a/frontend/src/components/Timeline.jsx
+++ /dev/null
@@ -1,405 +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 { withRouter } from "react-router-dom";
-
-import ChartContainer from "react-timeseries-charts/lib/components/ChartContainer";
-import ChartRow from "react-timeseries-charts/lib/components/ChartRow";
-import Charts from "react-timeseries-charts/lib/components/Charts";
-import LineChart from "react-timeseries-charts/lib/components/LineChart";
-import MultiBrush from "react-timeseries-charts/lib/components/MultiBrush";
-import Resizable from "react-timeseries-charts/lib/components/Resizable";
-import YAxis from "react-timeseries-charts/lib/components/YAxis";
-import { TimeRange, TimeSeries } from "pondjs";
-import styler from "react-timeseries-charts/lib/js/styler";
-
-import backend from "../backend";
-import dispatcher from "../dispatcher";
-import log from "../log";
-import ChoiceField from "./fields/ChoiceField";
-import "./Timeline.scss";
-
-const minutes = 60 * 1000;
-const maxTimelineRange = 24 * 60 * minutes;
-import classNames from "classnames";
-
-const leftSelectionPaddingMultiplier = 24;
-const rightSelectionPaddingMultiplier = 8;
-
-class Timeline extends Component {
- state = {
- metric: "connections_per_service",
- };
-
- constructor() {
- super();
-
- this.disableTimeSeriesChanges = false;
- this.selectionTimeout = null;
- }
-
- componentDidMount() {
- const urlParams = new URLSearchParams(this.props.location.search);
- this.setState({
- servicePortFilter: urlParams.get("service_port") || null,
- matchedRulesFilter: urlParams.getAll("matched_rules") || null,
- });
-
- this.loadStatistics(this.state.metric).then(() =>
- log.debug("Statistics loaded after mount")
- );
- dispatcher.register(
- "connections_filters",
- this.handleConnectionsFiltersCallback
- );
- dispatcher.register("connection_updates", this.handleConnectionUpdates);
- dispatcher.register("notifications", this.handleNotifications);
- dispatcher.register("pulse_timeline", this.handlePulseTimeline);
- }
-
- componentWillUnmount() {
- dispatcher.unregister(this.handleConnectionsFiltersCallback);
- dispatcher.unregister(this.handleConnectionUpdates);
- dispatcher.unregister(this.handleNotifications);
- dispatcher.unregister(this.handlePulseTimeline);
- }
-
- loadStatistics = async (metric) => {
- const urlParams = new URLSearchParams();
- urlParams.set("metric", metric);
-
- let columns = [];
- if (metric === "matched_rules") {
- let rules = await this.loadRules();
- if (this.state.matchedRulesFilter.length > 0) {
- this.state.matchedRulesFilter.forEach((id) => {
- urlParams.append("rules_ids", id);
- });
- columns = this.state.matchedRulesFilter;
- } else {
- columns = rules.map((r) => r.id);
- }
- } else {
- let services = await this.loadServices();
- const filteredPort = this.state.servicePortFilter;
- if (filteredPort && services[filteredPort]) {
- const service = services[filteredPort];
- services = {};
- services[filteredPort] = service;
- }
-
- columns = Object.keys(services);
- columns.forEach((port) => urlParams.append("ports", port));
- }
-
- const metrics = (await backend.get("/api/statistics?" + urlParams)).json;
- if (metrics.length === 0) {
- return;
- }
-
- const zeroFilledMetrics = [];
- const toTime = (m) => new Date(m["range_start"]).getTime();
-
- let i;
- let timeStart = toTime(metrics[0]) - minutes;
- for (i = 0; timeStart < 0 && i < metrics.length; i++) {
- // workaround to remove negative timestamps :(
- timeStart = toTime(metrics[i]) - minutes;
- }
-
- let timeEnd = toTime(metrics[metrics.length - 1]) + minutes;
- if (timeEnd - timeStart > maxTimelineRange) {
- timeEnd = timeStart + maxTimelineRange;
-
- const now = new Date().getTime();
- if (
- !this.lastDisplayNotificationTime ||
- this.lastDisplayNotificationTime + minutes < now
- ) {
- this.lastDisplayNotificationTime = now;
- dispatcher.dispatch("notifications", { event: "timeline.range.large" });
- }
- }
-
- for (let interval = timeStart; interval <= timeEnd; interval += minutes) {
- if (i < metrics.length && interval === toTime(metrics[i])) {
- const m = metrics[i++];
- m["range_start"] = new Date(m["range_start"]);
- zeroFilledMetrics.push(m);
- } else {
- const m = {};
- m["range_start"] = new Date(interval);
- m[metric] = {};
- columns.forEach((c) => (m[metric][c] = 0));
- zeroFilledMetrics.push(m);
- }
- }
-
- const series = new TimeSeries({
- name: "statistics",
- columns: ["time"].concat(columns),
- points: zeroFilledMetrics.map((m) =>
- [m["range_start"]].concat(
- columns.map((c) =>
- metric in m && m[metric] != null ? m[metric][c] || 0 : 0
- )
- )
- ),
- });
-
- const start = series.range().begin();
- const end = series.range().end();
-
- this.setState({
- metric,
- series,
- timeRange: new TimeRange(start, end),
- columns,
- start,
- end,
- });
- };
-
- loadServices = async () => {
- const services = (await backend.get("/api/services")).json;
- this.setState({ services });
- return services;
- };
-
- loadRules = async () => {
- const rules = (await backend.get("/api/rules")).json;
- this.setState({ rules });
- return rules;
- };
-
- createStyler = () => {
- if (this.state.metric === "matched_rules") {
- return styler(
- this.state.rules.map((rule) => {
- return { key: rule.id, color: rule.color, width: 2 };
- })
- );
- } else {
- return styler(
- Object.keys(this.state.services).map((port) => {
- return {
- key: port,
- color: this.state.services[port].color,
- width: 2,
- };
- })
- );
- }
- };
-
- handleTimeRangeChange = (timeRange) => {
- if (!this.disableTimeSeriesChanges) {
- this.setState({ timeRange });
- }
- };
-
- handleSelectionChange = (timeRange) => {
- this.disableTimeSeriesChanges = true;
-
- this.setState({ selection: timeRange });
- if (this.selectionTimeout) {
- clearTimeout(this.selectionTimeout);
- }
- this.selectionTimeout = setTimeout(() => {
- dispatcher.dispatch("timeline_updates", {
- from: timeRange.begin(),
- to: timeRange.end(),
- });
- this.selectionTimeout = null;
- this.disableTimeSeriesChanges = false;
- }, 1000);
- };
-
- handleConnectionsFiltersCallback = (payload) => {
- if (
- "service_port" in payload &&
- this.state.servicePortFilter !== payload["service_port"]
- ) {
- this.setState({ servicePortFilter: payload["service_port"] });
- this.loadStatistics(this.state.metric).then(() =>
- log.debug("Statistics reloaded after service port changed")
- );
- }
- if (
- "matched_rules" in payload &&
- this.state.matchedRulesFilter !== payload["matched_rules"]
- ) {
- this.setState({ matchedRulesFilter: payload["matched_rules"] });
- this.loadStatistics(this.state.metric).then(() =>
- log.debug("Statistics reloaded after matched rules changed")
- );
- }
- };
-
- handleConnectionUpdates = (payload) => {
- if (
- payload.from >= this.state.start &&
- payload.from < payload.to &&
- payload.to <= this.state.end
- ) {
- this.setState({
- selection: new TimeRange(payload.from, payload.to),
- });
- this.adjustSelection();
- }
- };
-
- handleNotifications = (payload) => {
- if (
- payload.event === "services.edit" &&
- this.state.metric !== "matched_rules"
- ) {
- this.loadStatistics(this.state.metric).then(() =>
- log.debug("Statistics reloaded after services updates")
- );
- } else if (
- payload.event.startsWith("rules") &&
- this.state.metric === "matched_rules"
- ) {
- this.loadStatistics(this.state.metric).then(() =>
- log.debug("Statistics reloaded after rules updates")
- );
- } else if (payload.event === "pcap.completed") {
- this.loadStatistics(this.state.metric).then(() =>
- log.debug("Statistics reloaded after pcap processed")
- );
- }
- };
-
- handlePulseTimeline = (payload) => {
- this.setState({ pulseTimeline: true });
- setTimeout(() => this.setState({ pulseTimeline: false }), payload.duration);
- };
-
- adjustSelection = () => {
- const seriesRange = this.state.series.range();
- const selection = this.state.selection;
- const delta = selection.end() - selection.begin();
- const start = Math.max(
- selection.begin().getTime() - delta * leftSelectionPaddingMultiplier,
- seriesRange.begin().getTime()
- );
- const end = Math.min(
- selection.end().getTime() + delta * rightSelectionPaddingMultiplier,
- seriesRange.end().getTime()
- );
- this.setState({ timeRange: new TimeRange(start, end) });
- };
-
- aggregateSeries = (func) => {
- const values = this.state.series
- .columns()
- .map((c) => this.state.series[func](c));
- return Math[func](...values);
- };
-
- render() {
- if (!this.state.series) {
- return null;
- }
-
- return (
- <footer className="footer">
- <div
- className={classNames("time-line", {
- "pulse-timeline": this.state.pulseTimeline,
- })}
- >
- <Resizable>
- <ChartContainer
- timeRange={this.state.timeRange}
- enableDragZoom={false}
- paddingTop={5}
- minDuration={60000}
- maxTime={this.state.end}
- minTime={this.state.start}
- paddingLeft={0}
- paddingRight={0}
- paddingBottom={0}
- enablePanZoom={true}
- utc={false}
- onTimeRangeChanged={this.handleTimeRangeChange}
- >
- <ChartRow height={this.props.height - 70}>
- <YAxis
- id="axis1"
- hideAxisLine
- min={this.aggregateSeries("min")}
- max={this.aggregateSeries("max")}
- width="35"
- type="linear"
- transition={300}
- />
- <Charts>
- <LineChart
- axis="axis1"
- series={this.state.series}
- columns={this.state.columns}
- style={this.createStyler()}
- interpolation="curveBasis"
- />
-
- <MultiBrush
- timeRanges={[this.state.selection]}
- allowSelectionClear={false}
- allowFreeDrawing={false}
- onTimeRangeChanged={this.handleSelectionChange}
- />
- </Charts>
- </ChartRow>
- </ChartContainer>
- </Resizable>
-
- <div className="metric-selection">
- <ChoiceField
- inline
- small
- keys={[
- "connections_per_service",
- "client_bytes_per_service",
- "server_bytes_per_service",
- "duration_per_service",
- "matched_rules",
- ]}
- values={[
- "connections_per_service",
- "client_bytes_per_service",
- "server_bytes_per_service",
- "duration_per_service",
- "matched_rules",
- ]}
- onChange={(metric) =>
- this.loadStatistics(metric).then(() =>
- log.debug("Statistics loaded after metric changes")
- )
- }
- value={this.state.metric}
- />
- </div>
- </div>
- </footer>
- );
- }
-}
-
-export default withRouter(Timeline);