From e905618113309eaba7227ff1328a20f6846e4afd Mon Sep 17 00:00:00 2001 From: Emiliano Ciavatta Date: Mon, 5 Oct 2020 23:46:18 +0200 Subject: Implement timeline --- statistics_controller.go | 71 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 statistics_controller.go (limited to 'statistics_controller.go') diff --git a/statistics_controller.go b/statistics_controller.go new file mode 100644 index 0000000..65c7d58 --- /dev/null +++ b/statistics_controller.go @@ -0,0 +1,71 @@ +package main + +import ( + "context" + "fmt" + log "github.com/sirupsen/logrus" + "time" +) + +type StatisticRecord struct { + RangeStart time.Time `json:"range_start" bson:"_id"` + ConnectionsPerService map[uint16]int `json:"connections_per_service" bson:"connections_per_service"` + ClientBytesPerService map[uint16]int `json:"client_bytes_per_service" bson:"client_bytes_per_service"` + ServerBytesPerService map[uint16]int `json:"server_bytes_per_service" bson:"server_bytes_per_service"` + DurationPerService map[uint16]int64 `json:"duration_per_service" bson:"duration_per_service"` +} + +type StatisticsFilter struct { + RangeFrom time.Time `form:"range_from"` + RangeTo time.Time `form:"range_to"` + Ports []uint16 `form:"ports"` + Metric string `form:"metric"` +} + +type StatisticsController struct { + storage Storage + metrics []string +} + +func NewStatisticsController(storage Storage) StatisticsController { + return StatisticsController{ + storage: storage, + metrics: []string{"connections_per_service", "client_bytes_per_service", + "server_bytes_per_service", "duration_per_service"}, + } +} + +func (sc *StatisticsController) GetStatistics(context context.Context, filter StatisticsFilter) []StatisticRecord { + var statisticRecords []StatisticRecord + query := sc.storage.Find(Statistics).Context(context) + if !filter.RangeFrom.IsZero() { + query = query.Filter(OrderedDocument{{"_id", UnorderedDocument{"$lt": filter.RangeFrom}}}) + } + if !filter.RangeTo.IsZero() { + query = query.Filter(OrderedDocument{{"_id", UnorderedDocument{"$gt": filter.RangeTo}}}) + } + for _, port := range filter.Ports { + for _, metric := range sc.metrics { + if filter.Metric == "" || filter.Metric == metric { + query = query.Projection(OrderedDocument{{fmt.Sprintf("%s.%d", metric, port), 1}}) + } + } + } + if filter.Metric != "" && len(filter.Ports) == 0 { + for _, metric := range sc.metrics { + if filter.Metric == metric { + query = query.Projection(OrderedDocument{{metric, 1}}) + } + } + } + + if err := query.All(&statisticRecords); err != nil { + log.WithError(err).WithField("filter", filter).Error("failed to retrieve statistics") + return []StatisticRecord{} + } + if statisticRecords == nil { + return []StatisticRecord{} + } + + return statisticRecords +} -- cgit v1.2.3-70-g09d2 From a30815021e61023f996b1450ddcd9164a6e18bef Mon Sep 17 00:00:00 2001 From: Emiliano Ciavatta Date: Thu, 8 Oct 2020 17:07:07 +0200 Subject: Add header license to all files --- application_context.go | 17 +++++++++++++++++ application_context_test.go | 17 +++++++++++++++++ application_router.go | 17 +++++++++++++++++ application_router_test.go | 17 +++++++++++++++++ caronte.go | 17 +++++++++++++++++ caronte_test.go | 17 +++++++++++++++++ connection_handler.go | 17 +++++++++++++++++ connection_handler_test.go | 17 +++++++++++++++++ connection_streams_controller.go | 17 +++++++++++++++++ connections_controller.go | 17 +++++++++++++++++ frontend/src/backend.js | 17 +++++++++++++++++ frontend/src/components/Connection.js | 17 +++++++++++++++++ frontend/src/components/ConnectionContent.js | 17 +++++++++++++++++ frontend/src/components/ConnectionMatchedRules.js | 17 +++++++++++++++++ frontend/src/components/MessageAction.js | 17 +++++++++++++++++ frontend/src/components/Notifications.js | 17 +++++++++++++++++ frontend/src/components/fields/ButtonField.js | 17 +++++++++++++++++ frontend/src/components/fields/CheckField.js | 17 +++++++++++++++++ frontend/src/components/fields/ChoiceField.js | 17 +++++++++++++++++ frontend/src/components/fields/InputField.js | 17 +++++++++++++++++ frontend/src/components/fields/TextField.js | 17 +++++++++++++++++ .../src/components/fields/extensions/ColorField.js | 17 +++++++++++++++++ .../components/fields/extensions/NumericField.js | 17 +++++++++++++++++ .../components/filters/BooleanConnectionsFilter.js | 17 +++++++++++++++++ .../src/components/filters/FiltersDefinitions.js | 17 +++++++++++++++++ .../components/filters/RulesConnectionsFilter.js | 17 +++++++++++++++++ .../components/filters/StringConnectionsFilter.js | 17 +++++++++++++++++ frontend/src/components/objects/LinkPopover.js | 17 +++++++++++++++++ frontend/src/components/panels/ConfigurationPane.js | 17 +++++++++++++++++ frontend/src/components/panels/MainPane.js | 17 +++++++++++++++++ frontend/src/components/panels/PcapPane.js | 17 +++++++++++++++++ frontend/src/components/panels/RulePane.js | 17 +++++++++++++++++ frontend/src/components/panels/ServicePane.js | 17 +++++++++++++++++ frontend/src/dispatcher.js | 16 ++++++++++++++++ frontend/src/index.js | 17 +++++++++++++++++ frontend/src/log.js | 17 +++++++++++++++++ frontend/src/notifications.js | 17 +++++++++++++++++ frontend/src/setupProxy.js | 17 +++++++++++++++++ frontend/src/utils.js | 17 +++++++++++++++++ frontend/src/validation.js | 16 ++++++++++++++++ frontend/src/views/App.js | 17 +++++++++++++++++ frontend/src/views/Connections.js | 17 +++++++++++++++++ frontend/src/views/Filters.js | 21 +++++++++++++++++++-- frontend/src/views/Header.js | 17 +++++++++++++++++ frontend/src/views/Timeline.js | 17 +++++++++++++++++ notification_controller.go | 17 +++++++++++++++++ parsers/http_request_parser.go | 17 +++++++++++++++++ parsers/http_response_parser.go | 17 +++++++++++++++++ parsers/parser.go | 17 +++++++++++++++++ parsers/parser_utils.go | 17 +++++++++++++++++ pcap_importer.go | 17 +++++++++++++++++ pcap_importer_test.go | 17 +++++++++++++++++ resources_controller.go | 17 +++++++++++++++++ rules_manager.go | 17 +++++++++++++++++ rules_manager_test.go | 17 +++++++++++++++++ services_controller.go | 17 +++++++++++++++++ statistics_controller.go | 17 +++++++++++++++++ storage.go | 17 +++++++++++++++++ storage_test.go | 17 +++++++++++++++++ stream_handler.go | 17 +++++++++++++++++ stream_handler_test.go | 17 +++++++++++++++++ utils.go | 17 +++++++++++++++++ 62 files changed, 1054 insertions(+), 2 deletions(-) (limited to 'statistics_controller.go') diff --git a/application_context.go b/application_context.go index 9a9c97a..9897bb6 100644 --- a/application_context.go +++ b/application_context.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/application_context_test.go b/application_context_test.go index 28c81a5..a7f1a49 100644 --- a/application_context_test.go +++ b/application_context_test.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/application_router.go b/application_router.go index da71538..9fd7e3d 100644 --- a/application_router.go +++ b/application_router.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/application_router_test.go b/application_router_test.go index f4804e3..9741eed 100644 --- a/application_router_test.go +++ b/application_router_test.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/caronte.go b/caronte.go index d999724..d4265bc 100644 --- a/caronte.go +++ b/caronte.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/caronte_test.go b/caronte_test.go index 12ec50f..8935ea3 100644 --- a/caronte_test.go +++ b/caronte_test.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/connection_handler.go b/connection_handler.go index 3d38531..6b2b411 100644 --- a/connection_handler.go +++ b/connection_handler.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/connection_handler_test.go b/connection_handler_test.go index 0bee0ac..d980041 100644 --- a/connection_handler_test.go +++ b/connection_handler_test.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/connection_streams_controller.go b/connection_streams_controller.go index 98f2aca..9251a3a 100644 --- a/connection_streams_controller.go +++ b/connection_streams_controller.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/connections_controller.go b/connections_controller.go index e872c9f..30a5ee5 100644 --- a/connections_controller.go +++ b/connections_controller.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/frontend/src/backend.js b/frontend/src/backend.js index 1b2d8d2..cc8604a 100644 --- a/frontend/src/backend.js +++ b/frontend/src/backend.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + async function json(method, url, data, json, headers) { const options = { method: method, diff --git a/frontend/src/components/Connection.js b/frontend/src/components/Connection.js index b7e2531..c7b0010 100644 --- a/frontend/src/components/Connection.js +++ b/frontend/src/components/Connection.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + import React, {Component} from 'react'; import './Connection.scss'; import {Form, OverlayTrigger, Popover} from "react-bootstrap"; diff --git a/frontend/src/components/ConnectionContent.js b/frontend/src/components/ConnectionContent.js index b09dcf3..b468277 100644 --- a/frontend/src/components/ConnectionContent.js +++ b/frontend/src/components/ConnectionContent.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + import React, {Component} from 'react'; import './ConnectionContent.scss'; import {Row} from 'react-bootstrap'; diff --git a/frontend/src/components/ConnectionMatchedRules.js b/frontend/src/components/ConnectionMatchedRules.js index 21f2a92..35643c5 100644 --- a/frontend/src/components/ConnectionMatchedRules.js +++ b/frontend/src/components/ConnectionMatchedRules.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + import React, {Component} from 'react'; import './ConnectionMatchedRules.scss'; import ButtonField from "./fields/ButtonField"; diff --git a/frontend/src/components/MessageAction.js b/frontend/src/components/MessageAction.js index 8f4b031..b94cbb9 100644 --- a/frontend/src/components/MessageAction.js +++ b/frontend/src/components/MessageAction.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + import React, {Component} from 'react'; import './MessageAction.scss'; import {Modal} from "react-bootstrap"; diff --git a/frontend/src/components/Notifications.js b/frontend/src/components/Notifications.js index 9ce2b58..1017a42 100644 --- a/frontend/src/components/Notifications.js +++ b/frontend/src/components/Notifications.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + import React, {Component} from 'react'; import './Notifications.scss'; import dispatcher from "../dispatcher"; diff --git a/frontend/src/components/fields/ButtonField.js b/frontend/src/components/fields/ButtonField.js index cc32b0f..ffcceae 100644 --- a/frontend/src/components/fields/ButtonField.js +++ b/frontend/src/components/fields/ButtonField.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + import React, {Component} from 'react'; import './ButtonField.scss'; import './common.scss'; diff --git a/frontend/src/components/fields/CheckField.js b/frontend/src/components/fields/CheckField.js index 33f4f83..dd44970 100644 --- a/frontend/src/components/fields/CheckField.js +++ b/frontend/src/components/fields/CheckField.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + import React, {Component} from 'react'; import './CheckField.scss'; import './common.scss'; diff --git a/frontend/src/components/fields/ChoiceField.js b/frontend/src/components/fields/ChoiceField.js index 73e950d..14071c3 100644 --- a/frontend/src/components/fields/ChoiceField.js +++ b/frontend/src/components/fields/ChoiceField.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + import React, {Component} from 'react'; import './ChoiceField.scss'; import './common.scss'; diff --git a/frontend/src/components/fields/InputField.js b/frontend/src/components/fields/InputField.js index 84c981b..80cce3b 100644 --- a/frontend/src/components/fields/InputField.js +++ b/frontend/src/components/fields/InputField.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + import React, {Component} from 'react'; import './InputField.scss'; import './common.scss'; diff --git a/frontend/src/components/fields/TextField.js b/frontend/src/components/fields/TextField.js index de68c21..9237c0c 100644 --- a/frontend/src/components/fields/TextField.js +++ b/frontend/src/components/fields/TextField.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + import React, {Component} from 'react'; import './TextField.scss'; import './common.scss'; diff --git a/frontend/src/components/fields/extensions/ColorField.js b/frontend/src/components/fields/extensions/ColorField.js index 96ebc49..f1c0caf 100644 --- a/frontend/src/components/fields/extensions/ColorField.js +++ b/frontend/src/components/fields/extensions/ColorField.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + import React, {Component} from 'react'; import {OverlayTrigger, Popover} from "react-bootstrap"; import './ColorField.scss'; diff --git a/frontend/src/components/fields/extensions/NumericField.js b/frontend/src/components/fields/extensions/NumericField.js index 19a9e46..d4d027d 100644 --- a/frontend/src/components/fields/extensions/NumericField.js +++ b/frontend/src/components/fields/extensions/NumericField.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + import React, {Component} from 'react'; import InputField from "../InputField"; diff --git a/frontend/src/components/filters/BooleanConnectionsFilter.js b/frontend/src/components/filters/BooleanConnectionsFilter.js index 4c5a78a..a9a420e 100644 --- a/frontend/src/components/filters/BooleanConnectionsFilter.js +++ b/frontend/src/components/filters/BooleanConnectionsFilter.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + import React, {Component} from 'react'; import {withRouter} from "react-router-dom"; import {Redirect} from "react-router"; diff --git a/frontend/src/components/filters/FiltersDefinitions.js b/frontend/src/components/filters/FiltersDefinitions.js index d4f2912..cde3cfb 100644 --- a/frontend/src/components/filters/FiltersDefinitions.js +++ b/frontend/src/components/filters/FiltersDefinitions.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + import {cleanNumber, validateIpAddress, validateMin, validatePort} from "../../utils"; import StringConnectionsFilter from "./StringConnectionsFilter"; import React from "react"; diff --git a/frontend/src/components/filters/RulesConnectionsFilter.js b/frontend/src/components/filters/RulesConnectionsFilter.js index 8366189..48affb0 100644 --- a/frontend/src/components/filters/RulesConnectionsFilter.js +++ b/frontend/src/components/filters/RulesConnectionsFilter.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + import React, {Component} from 'react'; import {withRouter} from "react-router-dom"; import {Redirect} from "react-router"; diff --git a/frontend/src/components/filters/StringConnectionsFilter.js b/frontend/src/components/filters/StringConnectionsFilter.js index f463593..a3b45dc 100644 --- a/frontend/src/components/filters/StringConnectionsFilter.js +++ b/frontend/src/components/filters/StringConnectionsFilter.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + import React, {Component} from 'react'; import {withRouter} from "react-router-dom"; import {Redirect} from "react-router"; diff --git a/frontend/src/components/objects/LinkPopover.js b/frontend/src/components/objects/LinkPopover.js index 8768caa..3c5bf67 100644 --- a/frontend/src/components/objects/LinkPopover.js +++ b/frontend/src/components/objects/LinkPopover.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + import React, {Component} from 'react'; import {randomClassName} from "../../utils"; import {OverlayTrigger, Popover} from "react-bootstrap"; diff --git a/frontend/src/components/panels/ConfigurationPane.js b/frontend/src/components/panels/ConfigurationPane.js index 10309f6..9ae2cfb 100644 --- a/frontend/src/components/panels/ConfigurationPane.js +++ b/frontend/src/components/panels/ConfigurationPane.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + import React, {Component} from 'react'; import './common.scss'; import './ConfigurationPane.scss'; diff --git a/frontend/src/components/panels/MainPane.js b/frontend/src/components/panels/MainPane.js index bd25e0c..d34d58a 100644 --- a/frontend/src/components/panels/MainPane.js +++ b/frontend/src/components/panels/MainPane.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + import React, {Component} from 'react'; import './common.scss'; import './MainPane.scss'; diff --git a/frontend/src/components/panels/PcapPane.js b/frontend/src/components/panels/PcapPane.js index 13f7cb3..d5c2225 100644 --- a/frontend/src/components/panels/PcapPane.js +++ b/frontend/src/components/panels/PcapPane.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + import React, {Component} from 'react'; import './PcapPane.scss'; import './common.scss'; diff --git a/frontend/src/components/panels/RulePane.js b/frontend/src/components/panels/RulePane.js index 76f3ac0..9913962 100644 --- a/frontend/src/components/panels/RulePane.js +++ b/frontend/src/components/panels/RulePane.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + import React, {Component} from 'react'; import './common.scss'; import './RulePane.scss'; diff --git a/frontend/src/components/panels/ServicePane.js b/frontend/src/components/panels/ServicePane.js index 22c6655..fc7004b 100644 --- a/frontend/src/components/panels/ServicePane.js +++ b/frontend/src/components/panels/ServicePane.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + import React, {Component} from 'react'; import './common.scss'; import './ServicePane.scss'; diff --git a/frontend/src/dispatcher.js b/frontend/src/dispatcher.js index 4b8b5a4..943f7ec 100644 --- a/frontend/src/dispatcher.js +++ b/frontend/src/dispatcher.js @@ -1,3 +1,19 @@ +/* + * 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 . + */ class Dispatcher { diff --git a/frontend/src/index.js b/frontend/src/index.js index beb52ae..e3e48de 100644 --- a/frontend/src/index.js +++ b/frontend/src/index.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + import React from 'react'; import ReactDOM from 'react-dom'; import 'bootstrap/dist/css/bootstrap.css'; diff --git a/frontend/src/log.js b/frontend/src/log.js index 0883962..424e1b4 100644 --- a/frontend/src/log.js +++ b/frontend/src/log.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + const log = { debug: (...obj) => console.info(...obj), info: (...obj) => console.info(...obj), diff --git a/frontend/src/notifications.js b/frontend/src/notifications.js index 2a77ffb..f04036d 100644 --- a/frontend/src/notifications.js +++ b/frontend/src/notifications.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + import log from "./log"; import dispatcher from "./dispatcher"; diff --git a/frontend/src/setupProxy.js b/frontend/src/setupProxy.js index 6f082c8..f2e1c39 100644 --- a/frontend/src/setupProxy.js +++ b/frontend/src/setupProxy.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + const { createProxyMiddleware } = require('http-proxy-middleware'); module.exports = function(app) { diff --git a/frontend/src/utils.js b/frontend/src/utils.js index aacc625..f333f0d 100644 --- a/frontend/src/utils.js +++ b/frontend/src/utils.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + const timeRegex = /^(0[0-9]|1[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$/; export function createCurlCommand(subCommand, method = null, json = null, data = null) { diff --git a/frontend/src/validation.js b/frontend/src/validation.js index 7089d7f..87b08de 100644 --- a/frontend/src/validation.js +++ b/frontend/src/validation.js @@ -1,3 +1,19 @@ +/* + * 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 . + */ const validation = { isValidColor: (color) => /^#(?:[0-9a-fA-F]{3}){1,2}$/.test(color), diff --git a/frontend/src/views/App.js b/frontend/src/views/App.js index 4bb9f57..8105117 100644 --- a/frontend/src/views/App.js +++ b/frontend/src/views/App.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + import React, {Component} from 'react'; import './App.scss'; import Header from "./Header"; diff --git a/frontend/src/views/Connections.js b/frontend/src/views/Connections.js index e835dcb..b2edd3f 100644 --- a/frontend/src/views/Connections.js +++ b/frontend/src/views/Connections.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + import React, {Component} from 'react'; import './Connections.scss'; import Connection from "../components/Connection"; diff --git a/frontend/src/views/Filters.js b/frontend/src/views/Filters.js index ba7d467..3dd8280 100644 --- a/frontend/src/views/Filters.js +++ b/frontend/src/views/Filters.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + import React, {Component} from 'react'; import {Col, Container, Modal, Row, Table} from "react-bootstrap"; import {filtersDefinitions, filtersNames} from "../components/filters/FiltersDefinitions"; @@ -31,7 +48,7 @@ class Filters extends Component { this.checkboxChangesHandler(name, event)} /> + onChange={event => this.checkboxChangesHandler(name, event)}/> {filtersDefinitions[name]} ); @@ -89,7 +106,7 @@ class Filters extends Component { - + ); diff --git a/frontend/src/views/Header.js b/frontend/src/views/Header.js index f5eff17..2cfe9fb 100644 --- a/frontend/src/views/Header.js +++ b/frontend/src/views/Header.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + import React, {Component} from 'react'; import Typed from 'typed.js'; import './Header.scss'; diff --git a/frontend/src/views/Timeline.js b/frontend/src/views/Timeline.js index 3adbf88..ebe3eb9 100644 --- a/frontend/src/views/Timeline.js +++ b/frontend/src/views/Timeline.js @@ -1,3 +1,20 @@ +/* + * 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 . + */ + import React, {Component} from 'react'; import './Timeline.scss'; import { diff --git a/notification_controller.go b/notification_controller.go index 88c9e8c..3fa3c5b 100644 --- a/notification_controller.go +++ b/notification_controller.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/parsers/http_request_parser.go b/parsers/http_request_parser.go index e2224b8..bc98f8f 100644 --- a/parsers/http_request_parser.go +++ b/parsers/http_request_parser.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package parsers import ( diff --git a/parsers/http_response_parser.go b/parsers/http_response_parser.go index 1770116..e5ef1ac 100644 --- a/parsers/http_response_parser.go +++ b/parsers/http_response_parser.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package parsers import ( diff --git a/parsers/parser.go b/parsers/parser.go index 06cc0dc..a29b1ab 100644 --- a/parsers/parser.go +++ b/parsers/parser.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package parsers type Parser interface { diff --git a/parsers/parser_utils.go b/parsers/parser_utils.go index b688262..575b666 100644 --- a/parsers/parser_utils.go +++ b/parsers/parser_utils.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package parsers import ( diff --git a/pcap_importer.go b/pcap_importer.go index 78a5e6c..41ed082 100644 --- a/pcap_importer.go +++ b/pcap_importer.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/pcap_importer_test.go b/pcap_importer_test.go index be09ea9..8940060 100644 --- a/pcap_importer_test.go +++ b/pcap_importer_test.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/resources_controller.go b/resources_controller.go index 050157a..10b31e3 100644 --- a/resources_controller.go +++ b/resources_controller.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/rules_manager.go b/rules_manager.go index a5dc7ce..636fc74 100644 --- a/rules_manager.go +++ b/rules_manager.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/rules_manager_test.go b/rules_manager_test.go index a2ec501..dded096 100644 --- a/rules_manager_test.go +++ b/rules_manager_test.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/services_controller.go b/services_controller.go index 9907b5e..e5fa200 100644 --- a/services_controller.go +++ b/services_controller.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/statistics_controller.go b/statistics_controller.go index 65c7d58..006b230 100644 --- a/statistics_controller.go +++ b/statistics_controller.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/storage.go b/storage.go index 0888ce0..f8b7f9c 100644 --- a/storage.go +++ b/storage.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/storage_test.go b/storage_test.go index 4caa30d..dd91e97 100644 --- a/storage_test.go +++ b/storage_test.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/stream_handler.go b/stream_handler.go index bccdeee..48dba34 100644 --- a/stream_handler.go +++ b/stream_handler.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/stream_handler_test.go b/stream_handler_test.go index 199ae5b..127aa82 100644 --- a/stream_handler_test.go +++ b/stream_handler_test.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( diff --git a/utils.go b/utils.go index ec5a807..639fd94 100644 --- a/utils.go +++ b/utils.go @@ -1,3 +1,20 @@ +/* + * 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 . + */ + package main import ( -- cgit v1.2.3-70-g09d2 From bc989facca1f3381afed2f7c982da7784fad2327 Mon Sep 17 00:00:00 2001 From: Emiliano Ciavatta Date: Sun, 11 Oct 2020 21:49:40 +0200 Subject: [inconsistent] SearchOption validation checkpoint --- application_context.go | 2 + application_router.go | 70 +++++++++++++++ connection_streams_controller.go | 1 + search_controller.go | 189 +++++++++++++++++++++++++++++++++++++++ statistics_controller.go | 8 +- storage.go | 41 ++++++--- stream_handler.go | 2 + 7 files changed, 299 insertions(+), 14 deletions(-) create mode 100644 search_controller.go (limited to 'statistics_controller.go') diff --git a/application_context.go b/application_context.go index 9897bb6..fc76d00 100644 --- a/application_context.go +++ b/application_context.go @@ -37,6 +37,7 @@ type ApplicationContext struct { ConnectionsController ConnectionsController ServicesController *ServicesController ConnectionStreamsController ConnectionStreamsController + SearchController *SearchController StatisticsController StatisticsController IsConfigured bool Version string @@ -113,6 +114,7 @@ func (sm *ApplicationContext) configure() { sm.ServicesController = NewServicesController(sm.Storage) sm.ConnectionsController = NewConnectionsController(sm.Storage, sm.ServicesController) sm.ConnectionStreamsController = NewConnectionStreamsController(sm.Storage) + sm.SearchController = NewSearchController(sm.Storage) sm.StatisticsController = NewStatisticsController(sm.Storage) sm.IsConfigured = true } diff --git a/application_router.go b/application_router.go index 89b471b..dc9f9d4 100644 --- a/application_router.go +++ b/application_router.go @@ -22,6 +22,8 @@ import ( "fmt" "github.com/gin-gonic/contrib/static" "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/go-playground/validator/v10" log "github.com/sirupsen/logrus" "net/http" "os" @@ -294,6 +296,74 @@ func CreateApplicationRouter(applicationContext *ApplicationContext, } }) + api.GET("/searches", func(c *gin.Context) { + success(c, applicationContext.SearchController.PerformedSearches()) + }) + + api.POST("/searches/perform", func(c *gin.Context) { + var options SearchOptions + + + parentIsZero := func (fl validator.FieldLevel) bool { + log.Println(fl.FieldName()) + log.Println("noooooo") + return fl.Parent().IsZero() + } + eitherWith := func (fl validator.FieldLevel) bool { + otherField := fl.Parent().FieldByName(fl.Param()) + log.Println(fl.Param()) + log.Println("bbbbbbbbbb") + return (fl.Field().IsZero() && !otherField.IsZero()) || (!fl.Field().IsZero() && otherField.IsZero()) + } + aaa := func (fl validator.FieldLevel) bool { + + log.Println("awww") + return fl.Field().IsZero() + } + + bbb := func (fl validator.FieldLevel) bool { + + log.Println("iiiii") + return true + } + + if validate, ok := binding.Validator.Engine().(*validator.Validate); ok { + if err := validate.RegisterValidation("parent_is_zero", parentIsZero); err != nil { + log.WithError(err).Panic("cannot register 'topzero' validator") + } + if err := validate.RegisterValidation("either_with", eitherWith); err != nil { + log.WithError(err).Panic("cannot register 'either_with' validator") + } + if err := validate.RegisterValidation("aaa", aaa); err != nil { + log.WithError(err).Panic("cannot register 'either_with' validator") + } + if err := validate.RegisterValidation("bbb", bbb); err != nil { + log.WithError(err).Panic("cannot register 'either_with' validator") + } + } else { + log.Panic("cannot ") + } + + + if err := c.ShouldBindJSON(&options); err != nil { + badRequest(c, err) + return + } + + log.Println(options) + + + success(c, "ok") + + + + + + + + //success(c, applicationContext.SearchController.PerformSearch(c, options)) + }) + api.GET("/streams/:id", func(c *gin.Context) { id, err := RowIDFromHex(c.Param("id")) if err != nil { diff --git a/connection_streams_controller.go b/connection_streams_controller.go index 89e484d..eef1a2a 100644 --- a/connection_streams_controller.go +++ b/connection_streams_controller.go @@ -39,6 +39,7 @@ type ConnectionStream struct { FromClient bool `bson:"from_client"` DocumentIndex int `bson:"document_index"` Payload []byte `bson:"payload"` + PayloadString string `bson:"payload_string"` BlocksIndexes []int `bson:"blocks_indexes"` BlocksTimestamps []time.Time `bson:"blocks_timestamps"` BlocksLoss []bool `bson:"blocks_loss"` diff --git a/search_controller.go b/search_controller.go new file mode 100644 index 0000000..ad47dbc --- /dev/null +++ b/search_controller.go @@ -0,0 +1,189 @@ +/* + * 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 . + */ + +package main + +import ( + "context" + log "github.com/sirupsen/logrus" + "strings" + "sync" + "time" +) + +const ( + secondsToNano = 1000 * 1000 * 1000 + maxSearchTimeout = 60 * secondsToNano +) + +type PerformedSearch struct { + ID RowID `bson:"_id" json:"id"` + SearchOptions SearchOptions `bson:"search_options" json:"search_options"` + AffectedConnections []RowID `bson:"affected_connections" json:"affected_connections,omitempty"` + AffectedConnectionsCount int `bson:"affected_connections_count" json:"affected_connections_count"` + StartedAt time.Time `bson:"started_at" json:"started_at"` + FinishedAt time.Time `bson:"finished_at" json:"finished_at"` + UpdatedAt time.Time `bson:"updated_at" json:"updated_at"` + Timeout time.Duration `bson:"timeout" json:"timeout"` +} + +type SearchOptions struct { + TextSearch TextSearch `bson:"text_search" json:"text_search" validate:"either_with=RegexSearch"` + RegexSearch RegexSearch `bson:"regex_search" json:"regex_search" validate:"either_with=TextSearch"` + Timeout time.Duration `bson:"timeout" json:"timeout" binding:"max=60"` +} + +type TextSearch struct { + Terms []string `bson:"terms" json:"terms" binding:"parent_is_zero|either_with=ExactPhrase,isdefault|min=1,dive,min=3"` + ExcludedTerms []string `bson:"excluded_terms" json:"excluded_terms" binding:"required_with=Terms,dive,isdefault|min=1"` + ExactPhrase string `bson:"exact_phrase" json:"exact_phrase" binding:"isdefault|min=3,parent_is_zero|either_with=Terms"` + CaseSensitive bool `bson:"case_sensitive" json:"case_sensitive"` +} + +type RegexSearch struct { + Pattern string `bson:"pattern" json:"pattern" binding:"parent_is_zero|either_with=NotPattern,isdefault|min=3"` + NotPattern string `bson:"not_pattern" json:"not_pattern" binding:"parent_is_zero|either_with=Pattern,isdefault|min=3"` + CaseInsensitive bool `bson:"case_insensitive" json:"case_insensitive"` + MultiLine bool `bson:"multi_line" json:"multi_line"` + IgnoreWhitespaces bool `bson:"ignore_whitespaces" json:"ignore_whitespaces"` + DotCharacter bool `bson:"dot_character" json:"dot_character"` +} + +type SearchController struct { + storage Storage + performedSearches []PerformedSearch + mutex sync.Mutex +} + +func NewSearchController(storage Storage) *SearchController { + var searches []PerformedSearch + if err := storage.Find(Searches).All(&searches); err != nil { + // log.WithError(err).Panic("failed to retrieve performed searches") + } + + return &SearchController{ + storage: storage, + performedSearches: searches, + } +} + +func (sc *SearchController) PerformedSearches() []PerformedSearch { + sc.mutex.Lock() + defer sc.mutex.Unlock() + + return sc.performedSearches +} + +func (sc *SearchController) PerformSearch(c context.Context, options SearchOptions) PerformedSearch { + findQuery := sc.storage.Find(ConnectionStreams).Projection(OrderedDocument{{"connection_id", 1}}).Context(c) + timeout := options.Timeout * secondsToNano + if timeout <= 0 || timeout > maxSearchTimeout { + timeout = maxSearchTimeout + } + findQuery = findQuery.MaxTime(timeout) + + if !options.TextSearch.isZero() { + var text string + if options.TextSearch.ExactPhrase != "" { + text = "\"" + options.TextSearch.ExactPhrase + "\"" + } else { + text = strings.Join(options.TextSearch.Terms, " ") + if options.TextSearch.ExcludedTerms != nil { + text += " -" + strings.Join(options.TextSearch.ExcludedTerms, " -") + } + } + + findQuery = findQuery.Filter(OrderedDocument{{"$text", UnorderedDocument{ + "$search": text, + "$language": "none", + "$caseSensitive": options.TextSearch.CaseSensitive, + "$diacriticSensitive": false, + }}}) + } else { + var regexOptions string + if options.RegexSearch.CaseInsensitive { + regexOptions += "i" + } + if options.RegexSearch.MultiLine { + regexOptions += "m" + } + if options.RegexSearch.IgnoreWhitespaces { + regexOptions += "x" + } + if options.RegexSearch.DotCharacter { + regexOptions += "s" + } + + var regex UnorderedDocument + if options.RegexSearch.Pattern != "" { + regex = UnorderedDocument{"$regex": options.RegexSearch.Pattern, "$options": regexOptions} + } else { + regex = UnorderedDocument{"$not": + UnorderedDocument{"$regex": options.RegexSearch.NotPattern, "$options": regexOptions}} + } + + findQuery = findQuery.Filter(OrderedDocument{{"payload_string", regex}}) + } + + var connections []ConnectionStream + startedAt := time.Now() + if err := findQuery.All(&connections); err != nil { + log.WithError(err).Error("oh no") + } + affectedConnections := uniqueConnectionIds(connections) + + finishedAt := time.Now() + performedSearch := PerformedSearch{ + ID: NewRowID(), + SearchOptions: options, + AffectedConnections: affectedConnections, + AffectedConnectionsCount: len(affectedConnections), + StartedAt: startedAt, + FinishedAt: finishedAt, + UpdatedAt: finishedAt, + Timeout: options.Timeout, + } + if _, err := sc.storage.Insert(Searches).Context(c).One(performedSearch); err != nil { + log.WithError(err).Panic("failed to insert a new performed search") + } + + sc.mutex.Lock() + sc.performedSearches = append([]PerformedSearch{performedSearch}, sc.performedSearches...) + sc.mutex.Unlock() + + return performedSearch +} + +func (sc TextSearch) isZero() bool { + return sc.Terms == nil && sc.ExcludedTerms == nil && sc.ExactPhrase == "" +} + +func (sc RegexSearch) isZero() bool { + return RegexSearch{} == sc +} + +func uniqueConnectionIds(connections []ConnectionStream) []RowID { + keys := make(map[RowID]bool) + var out []RowID + for _, entry := range connections { + if _, value := keys[entry.ConnectionID]; !value { + keys[entry.ConnectionID] = true + out = append(out, entry.ConnectionID) + } + } + return out +} diff --git a/statistics_controller.go b/statistics_controller.go index 006b230..1714c0b 100644 --- a/statistics_controller.go +++ b/statistics_controller.go @@ -26,10 +26,10 @@ import ( type StatisticRecord struct { RangeStart time.Time `json:"range_start" bson:"_id"` - ConnectionsPerService map[uint16]int `json:"connections_per_service" bson:"connections_per_service"` - ClientBytesPerService map[uint16]int `json:"client_bytes_per_service" bson:"client_bytes_per_service"` - ServerBytesPerService map[uint16]int `json:"server_bytes_per_service" bson:"server_bytes_per_service"` - DurationPerService map[uint16]int64 `json:"duration_per_service" bson:"duration_per_service"` + ConnectionsPerService map[uint16]int `json:"connections_per_service,omitempty" bson:"connections_per_service"` + ClientBytesPerService map[uint16]int `json:"client_bytes_per_service,omitempty" bson:"client_bytes_per_service"` + ServerBytesPerService map[uint16]int `json:"server_bytes_per_service,omitempty" bson:"server_bytes_per_service"` + DurationPerService map[uint16]int64 `json:"duration_per_service,omitempty" bson:"duration_per_service"` } type StatisticsFilter struct { diff --git a/storage.go b/storage.go index f8b7f9c..304e88c 100644 --- a/storage.go +++ b/storage.go @@ -25,16 +25,20 @@ import ( "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" + "time" ) // Collections names -const Connections = "connections" -const ConnectionStreams = "connection_streams" -const ImportingSessions = "importing_sessions" -const Rules = "rules" -const Settings = "settings" -const Services = "services" -const Statistics = "statistics" +const ( + Connections = "connections" + ConnectionStreams = "connection_streams" + ImportingSessions = "importing_sessions" + Rules = "rules" + Searches = "searches" + Settings = "settings" + Services = "services" + Statistics = "statistics" +) var ZeroRowID [12]byte @@ -73,6 +77,7 @@ func NewMongoStorage(uri string, port int, database string) (*MongoStorage, erro ConnectionStreams: db.Collection(ConnectionStreams), ImportingSessions: db.Collection(ImportingSessions), Rules: db.Collection(Rules), + Searches: db.Collection(Services), Settings: db.Collection(Settings), Services: db.Collection(Services), Statistics: db.Collection(Statistics), @@ -85,9 +90,13 @@ func NewMongoStorage(uri string, port int, database string) (*MongoStorage, erro return nil, err } - if _, err := collections[ConnectionStreams].Indexes().CreateOne(ctx, mongo.IndexModel{ - Keys: bson.D{{"connection_id", -1}}, // descending - Options: options.Index(), + if _, err := collections[ConnectionStreams].Indexes().CreateMany(ctx, []mongo.IndexModel{ + { + Keys: bson.D{{"connection_id", -1}}, // descending + }, + { + Keys: bson.D{{"payload_string", "text"}}, + }, }); err != nil { return nil, err } @@ -277,6 +286,8 @@ type FindOperation interface { Projection(filter OrderedDocument) FindOperation Sort(field string, ascending bool) FindOperation Limit(n int64) FindOperation + Skip(n int64) FindOperation + MaxTime(duration time.Duration) FindOperation First(result interface{}) error All(results interface{}) error } @@ -318,6 +329,16 @@ func (fo MongoFindOperation) Limit(n int64) FindOperation { return fo } +func (fo MongoFindOperation) Skip(n int64) FindOperation { + fo.optFind.SetSkip(n) + return fo +} + +func (fo MongoFindOperation) MaxTime(duration time.Duration) FindOperation { + fo.optFind.SetMaxTime(duration) + return fo +} + func (fo MongoFindOperation) Sort(field string, ascending bool) FindOperation { var sort int if ascending { diff --git a/stream_handler.go b/stream_handler.go index 48dba34..f08bd70 100644 --- a/stream_handler.go +++ b/stream_handler.go @@ -22,6 +22,7 @@ import ( "github.com/flier/gohs/hyperscan" "github.com/google/gopacket/tcpassembly" log "github.com/sirupsen/logrus" + "strings" "time" ) @@ -176,6 +177,7 @@ func (sh *StreamHandler) storageCurrentDocument() { ConnectionID: ZeroRowID, DocumentIndex: len(sh.documentsIDs), Payload: sh.buffer.Bytes(), + PayloadString: strings.ToValidUTF8(string(sh.buffer.Bytes()), ""), BlocksIndexes: sh.indexes, BlocksTimestamps: sh.timestamps, BlocksLoss: sh.lossBlocks, -- cgit v1.2.3-70-g09d2 From 09cb0a1518feb2221ccd8c10dced859c010e9991 Mon Sep 17 00:00:00 2001 From: Emiliano Ciavatta Date: Mon, 12 Oct 2020 21:33:40 +0200 Subject: Add rules statistics --- connection_handler.go | 22 ++++++++++++++-------- frontend/src/components/Timeline.js | 32 +++++++++++++++++--------------- rules_manager.go | 28 ++++++++++++++++++---------- statistics_controller.go | 27 +++++++++++++++++++++------ 4 files changed, 70 insertions(+), 39 deletions(-) (limited to 'statistics_controller.go') diff --git a/connection_handler.go b/connection_handler.go index 6b2b411..4e92ccf 100644 --- a/connection_handler.go +++ b/connection_handler.go @@ -256,16 +256,22 @@ func (ch *connectionHandlerImpl) UpdateStatistics(connection Connection) { } servicePort := connection.DestinationPort + updateDocument := UnorderedDocument{ + fmt.Sprintf("connections_per_service.%d", servicePort): 1, + fmt.Sprintf("client_bytes_per_service.%d", servicePort): connection.ClientBytes, + fmt.Sprintf("server_bytes_per_service.%d", servicePort): connection.ServerBytes, + fmt.Sprintf("total_bytes_per_service.%d", servicePort): connection.ClientBytes + connection.ServerBytes, + fmt.Sprintf("duration_per_service.%d", servicePort): duration.Milliseconds(), + } + + for _, ruleID := range connection.MatchedRules { + updateDocument[fmt.Sprintf("matched_rules.%s", ruleID.Hex())] = 1 + } + var results interface{} if _, err := ch.Storage().Update(Statistics).Upsert(&results). - Filter(OrderedDocument{{"_id", time.Unix(rangeStart*60, 0)}}).OneComplex(UnorderedDocument{ - "$inc": UnorderedDocument{ - fmt.Sprintf("connections_per_service.%d", servicePort): 1, - fmt.Sprintf("client_bytes_per_service.%d", servicePort): connection.ClientBytes, - fmt.Sprintf("server_bytes_per_service.%d", servicePort): connection.ServerBytes, - fmt.Sprintf("duration_per_service.%d", servicePort): duration.Milliseconds(), - }, - }); err != nil { + Filter(OrderedDocument{{"_id", time.Unix(rangeStart*60, 0)}}). + OneComplex(UnorderedDocument{"$inc": updateDocument}); err != nil { log.WithError(err).WithField("connection", connection).Error("failed to update connection statistics") } } diff --git a/frontend/src/components/Timeline.js b/frontend/src/components/Timeline.js index 615203f..6b8806f 100644 --- a/frontend/src/components/Timeline.js +++ b/frontend/src/components/Timeline.js @@ -102,23 +102,24 @@ class Timeline extends Component { ports.forEach(s => urlParams.append("ports", s)); 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(); - - if (metrics.length > 0) { - let i = 0; - for (let interval = toTime(metrics[0]); interval <= toTime(metrics[metrics.length - 1]); interval += minutes) { - if (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] = {}; - ports.forEach(p => m[metric][p] = 0); - zeroFilledMetrics.push(m); - } + let i = 0; + for (let interval = toTime(metrics[0]); interval <= toTime(metrics[metrics.length - 1]); interval += minutes) { + if (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] = {}; + ports.forEach(p => m[metric][p] = 0); + zeroFilledMetrics.push(m); } } @@ -127,6 +128,7 @@ class Timeline extends Component { columns: ["time"].concat(ports), points: zeroFilledMetrics.map(m => [m["range_start"]].concat(ports.map(p => m[metric][p] || 0))) }); + const start = series.range().begin(); const end = series.range().end(); start.setTime(start.getTime() - minutes); diff --git a/rules_manager.go b/rules_manager.go index 636fc74..0e6c3d1 100644 --- a/rules_manager.go +++ b/rules_manager.go @@ -121,16 +121,24 @@ func LoadRulesManager(storage Storage, flagRegex string) (RulesManager, error) { // if there are no rules in database (e.g. first run), set flagRegex as first rule if len(rulesManager.rules) == 0 { - if _, err := rulesManager.AddRule(context.Background(), Rule{ - Name: "flag", - Color: "#E53935", - Notes: "Mark connections where the flag is stolen", - Patterns: []Pattern{ - {Regex: flagRegex, Direction: DirectionToClient}, - }, - }); err != nil { - return nil, err - } + go func() { + _, _ = rulesManager.AddRule(context.Background(), Rule{ + Name: "flag_out", + Color: "#e53935", + Notes: "Mark connections where the flags are stolen", + Patterns: []Pattern{ + {Regex: flagRegex, Direction: DirectionToClient, Flags: RegexFlags{Utf8Mode: true}}, + }, + }) + _, _ = rulesManager.AddRule(context.Background(), Rule{ + Name: "flag_in", + Color: "#43A047", + Notes: "Mark connections where the flags are placed", + Patterns: []Pattern{ + {Regex: flagRegex, Direction: DirectionToServer, Flags: RegexFlags{Utf8Mode: true}}, + }, + }) + }() } else { if err := rulesManager.generateDatabase(rules[len(rules)-1].ID); err != nil { return nil, err diff --git a/statistics_controller.go b/statistics_controller.go index 1714c0b..57c7d95 100644 --- a/statistics_controller.go +++ b/statistics_controller.go @@ -29,26 +29,29 @@ type StatisticRecord struct { ConnectionsPerService map[uint16]int `json:"connections_per_service,omitempty" bson:"connections_per_service"` ClientBytesPerService map[uint16]int `json:"client_bytes_per_service,omitempty" bson:"client_bytes_per_service"` ServerBytesPerService map[uint16]int `json:"server_bytes_per_service,omitempty" bson:"server_bytes_per_service"` + TotalBytesPerService map[uint16]int `json:"total_bytes_per_service,omitempty" bson:"total_bytes_per_service"` DurationPerService map[uint16]int64 `json:"duration_per_service,omitempty" bson:"duration_per_service"` + MatchedRules map[RowID]int64 `json:"matched_rules,omitempty" bson:"matched_rules"` } type StatisticsFilter struct { RangeFrom time.Time `form:"range_from"` RangeTo time.Time `form:"range_to"` Ports []uint16 `form:"ports"` + RulesIDs []RowID `form:"rules_ids"` Metric string `form:"metric"` } type StatisticsController struct { - storage Storage - metrics []string + storage Storage + servicesMetrics []string } func NewStatisticsController(storage Storage) StatisticsController { return StatisticsController{ storage: storage, - metrics: []string{"connections_per_service", "client_bytes_per_service", - "server_bytes_per_service", "duration_per_service"}, + servicesMetrics: []string{"connections_per_service", "client_bytes_per_service", + "server_bytes_per_service", "total_bytes_per_service", "duration_per_service"}, } } @@ -62,19 +65,31 @@ func (sc *StatisticsController) GetStatistics(context context.Context, filter St query = query.Filter(OrderedDocument{{"_id", UnorderedDocument{"$gt": filter.RangeTo}}}) } for _, port := range filter.Ports { - for _, metric := range sc.metrics { + for _, metric := range sc.servicesMetrics { if filter.Metric == "" || filter.Metric == metric { query = query.Projection(OrderedDocument{{fmt.Sprintf("%s.%d", metric, port), 1}}) } } + } if filter.Metric != "" && len(filter.Ports) == 0 { - for _, metric := range sc.metrics { + for _, metric := range sc.servicesMetrics { if filter.Metric == metric { query = query.Projection(OrderedDocument{{metric, 1}}) } } } + for _, ruleID := range filter.RulesIDs { + if filter.Metric == "" || filter.Metric == "matched_rules" { + query = query.Projection(OrderedDocument{{fmt.Sprintf("matched_rules.%s", ruleID.Hex()), 1}}) + } + + } + if filter.Metric != "" && len(filter.RulesIDs) == 0 { + if filter.Metric == "matched_rules" { + query = query.Projection(OrderedDocument{{"matched_rules", 1}}) + } + } if err := query.All(&statisticRecords); err != nil { log.WithError(err).WithField("filter", filter).Error("failed to retrieve statistics") -- cgit v1.2.3-70-g09d2 From 08456e7f2e1c1af6fc8fdbf580c0178a25b93f8b Mon Sep 17 00:00:00 2001 From: Emiliano Ciavatta Date: Thu, 15 Oct 2020 08:53:09 +0200 Subject: General improvements --- frontend/public/logo128.png | Bin 0 -> 6498 bytes frontend/public/logo192.png | Bin 6498 -> 0 bytes frontend/public/manifest.json | 8 +- frontend/src/components/Header.js | 2 +- frontend/src/components/Timeline.js | 122 +++++++++++++++------ frontend/src/components/Timeline.scss | 1 + .../components/filters/BooleanConnectionsFilter.js | 64 ++++------- .../src/components/filters/ExitSearchFilter.js | 9 +- .../src/components/filters/FiltersDispatcher.js | 57 ---------- .../components/filters/RulesConnectionsFilter.js | 81 ++++++-------- .../components/filters/StringConnectionsFilter.js | 77 ++++++------- frontend/src/components/objects/Connection.js | 4 +- .../components/objects/ConnectionMatchedRules.js | 21 ++-- frontend/src/components/pages/MainPage.js | 3 - frontend/src/components/panels/ConnectionsPane.js | 122 ++++++++++++--------- frontend/src/components/panels/SearchPane.scss | 1 + frontend/src/components/panels/StreamsPane.js | 2 +- frontend/src/dispatcher.js | 6 + search_controller.go | 4 + statistics_controller.go | 19 ++-- 20 files changed, 299 insertions(+), 304 deletions(-) create mode 100644 frontend/public/logo128.png delete mode 100644 frontend/public/logo192.png delete mode 100644 frontend/src/components/filters/FiltersDispatcher.js (limited to 'statistics_controller.go') diff --git a/frontend/public/logo128.png b/frontend/public/logo128.png new file mode 100644 index 0000000..1969e1d Binary files /dev/null and b/frontend/public/logo128.png differ diff --git a/frontend/public/logo192.png b/frontend/public/logo192.png deleted file mode 100644 index 1969e1d..0000000 Binary files a/frontend/public/logo192.png and /dev/null differ diff --git a/frontend/public/manifest.json b/frontend/public/manifest.json index 0409a59..32674ce 100644 --- a/frontend/public/manifest.json +++ b/frontend/public/manifest.json @@ -1,6 +1,6 @@ { - "short_name": "Caronte", - "name": "Caronte", + "short_name": "caronte", + "name": "caronte", "icons": [ { "src": "favicon.ico", @@ -8,9 +8,9 @@ "type": "image/x-icon" }, { - "src": "logo192.png", + "src": "logo128.png", "type": "image/png", - "sizes": "192x192" + "sizes": "128x128" }, { "src": "logo512.png", diff --git a/frontend/src/components/Header.js b/frontend/src/components/Header.js index b72b532..b4a2177 100644 --- a/frontend/src/components/Header.js +++ b/frontend/src/components/Header.js @@ -89,7 +89,7 @@ class Header extends Component {
- {/**/} + diff --git a/frontend/src/components/Timeline.js b/frontend/src/components/Timeline.js index 6b8806f..bc42a01 100644 --- a/frontend/src/components/Timeline.js +++ b/frontend/src/components/Timeline.js @@ -35,8 +35,12 @@ import log from "../log"; import dispatcher from "../dispatcher"; const minutes = 60 * 1000; +const _ = require('lodash'); const classNames = require('classnames'); +const leftSelectionPaddingMultiplier = 24; +const rightSelectionPaddingMultiplier = 8; + class Timeline extends Component { state = { @@ -50,25 +54,30 @@ class Timeline extends Component { this.selectionTimeout = null; } - filteredPort = () => { + additionalFilters = () => { const urlParams = new URLSearchParams(this.props.location.search); - return urlParams.get("service_port"); + if (this.state.metric === "matched_rules") { + return urlParams.getAll("matched_rules") || []; + } else { + return urlParams.get("service_port"); + } }; componentDidMount() { - const filteredPort = this.filteredPort(); - this.setState({filteredPort}); - this.loadStatistics(this.state.metric, filteredPort).then(() => log.debug("Statistics loaded after mount")); + const additionalFilters = this.additionalFilters(); + this.setState({filters: additionalFilters}); + this.loadStatistics(this.state.metric, additionalFilters).then(() => log.debug("Statistics loaded after mount")); dispatcher.register("connection_updates", payload => { this.setState({ selection: new TimeRange(payload.from, payload.to), }); + this.adjustSelection(); }); dispatcher.register("notifications", payload => { if (payload.event === "services.edit") { - this.loadServices().then(() => log.debug("Services reloaded after notification update")); + this.loadServices().then(() => this.adjustSelection()); } }); @@ -79,27 +88,48 @@ class Timeline extends Component { } componentDidUpdate(prevProps, prevState, snapshot) { - const filteredPort = this.filteredPort(); - if (this.state.filteredPort !== filteredPort) { - this.setState({filteredPort}); - this.loadStatistics(this.state.metric, filteredPort).then(() => - log.debug("Statistics reloaded after filtered port changes")); + const additionalFilters = this.additionalFilters(); + const updateStatistics = () => { + this.setState({filters: additionalFilters}); + this.loadStatistics(this.state.metric, additionalFilters).then(() => + log.debug("Statistics reloaded after filters changes")); + }; + + if (this.state.metric === "matched_rules") { + if (!Array.isArray(this.state.filters) || + !_.isEqual(_.sortBy(additionalFilters), _.sortBy(this.state.filters))) { + updateStatistics(); + } + } else { + if (this.state.filters !== additionalFilters) { + updateStatistics(); + } } } - loadStatistics = async (metric, filteredPort) => { + loadStatistics = async (metric, filters) => { const urlParams = new URLSearchParams(); urlParams.set("metric", metric); - let services = await this.loadServices(); - if (filteredPort && services[filteredPort]) { - const service = services[filteredPort]; - services = {}; - services[filteredPort] = service; - } + let columns = []; + if (metric === "matched_rules") { + let rules = await this.loadRules(); + filters.forEach(id => { + urlParams.append("matched_rules", id); + }); + columns = rules.map(r => r.id); + } else { + let services = await this.loadServices(); + const filteredPort = filters; + if (filteredPort && services[filters]) { + const service = services[filteredPort]; + services = {}; + services[filteredPort] = service; + } - const ports = Object.keys(services); - ports.forEach(s => urlParams.append("ports", s)); + columns = Object.keys(services); + columns.forEach(port => urlParams.append("ports", port)); + } const metrics = (await backend.get("/api/statistics?" + urlParams)).json; if (metrics.length === 0) { @@ -109,8 +139,8 @@ class Timeline extends Component { const zeroFilledMetrics = []; const toTime = m => new Date(m["range_start"]).getTime(); let i = 0; - for (let interval = toTime(metrics[0]); interval <= toTime(metrics[metrics.length - 1]); interval += minutes) { - if (interval === toTime(metrics[i])) { + for (let interval = toTime(metrics[0]) - minutes; interval <= toTime(metrics[metrics.length - 1]) + minutes; 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); @@ -118,30 +148,31 @@ class Timeline extends Component { const m = {}; m["range_start"] = new Date(interval); m[metric] = {}; - ports.forEach(p => m[metric][p] = 0); + columns.forEach(c => m[metric][c] = 0); zeroFilledMetrics.push(m); } } const series = new TimeSeries({ name: "statistics", - columns: ["time"].concat(ports), - points: zeroFilledMetrics.map(m => [m["range_start"]].concat(ports.map(p => m[metric][p] || 0))) + 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(); - start.setTime(start.getTime() - minutes); - end.setTime(end.getTime() + minutes); this.setState({ metric, series, timeRange: new TimeRange(start, end), + columns, start, end }); - log.debug(`Loaded statistics for metric "${metric}" for services [${ports}]`); + log.debug(`Loaded statistics for metric "${metric}"`); }; loadServices = async () => { @@ -150,10 +181,22 @@ class Timeline extends Component { return services; }; + loadRules = async () => { + const rules = (await backend.get("/api/rules")).json; + this.setState({rules}); + return rules; + }; + createStyler = () => { - return styler(Object.keys(this.state.services).map(port => { - return {key: port, color: this.state.services[port].color, width: 2}; - })); + 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) => { @@ -179,6 +222,15 @@ class Timeline extends Component { }, 1000); }; + 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); @@ -207,7 +259,7 @@ class Timeline extends Component { max={this.aggregateSeries("max")} width="35" type="linear" transition={300}/> this.loadStatistics(metric, this.state.filteredPort) + "server_bytes_per_service", "duration_per_service", "matched_rules"]} + onChange={(metric) => this.loadStatistics(metric, this.state.filters) .then(() => log.debug("Statistics loaded after metric changes"))} value={this.state.metric}/>
diff --git a/frontend/src/components/Timeline.scss b/frontend/src/components/Timeline.scss index db8d9c8..262da1e 100644 --- a/frontend/src/components/Timeline.scss +++ b/frontend/src/components/Timeline.scss @@ -12,6 +12,7 @@ position: absolute; top: 5px; right: 10px; + width: 180px; } &.pulse-timeline { diff --git a/frontend/src/components/filters/BooleanConnectionsFilter.js b/frontend/src/components/filters/BooleanConnectionsFilter.js index a9a420e..c611a0d 100644 --- a/frontend/src/components/filters/BooleanConnectionsFilter.js +++ b/frontend/src/components/filters/BooleanConnectionsFilter.js @@ -17,65 +17,49 @@ import React, {Component} from 'react'; import {withRouter} from "react-router-dom"; -import {Redirect} from "react-router"; import CheckField from "../fields/CheckField"; +import dispatcher from "../../dispatcher"; class BooleanConnectionsFilter extends Component { - constructor(props) { - super(props); - this.state = { - filterActive: "false" - }; - - this.filterChanged = this.filterChanged.bind(this); - this.needRedirect = false; - } + state = { + filterActive: "false" + }; componentDidMount() { let params = new URLSearchParams(this.props.location.search); this.setState({filterActive: this.toBoolean(params.get(this.props.filterName)).toString()}); + + this.connectionsFiltersCallback = payload => { + const name = this.props.filterName; + if (name in payload && this.state.filterActive !== payload[name]) { + this.setState({filterActive: payload[name]}); + } + }; + dispatcher.register("connections_filters", this.connectionsFiltersCallback); } - componentDidUpdate(prevProps, prevState, snapshot) { - let urlParams = new URLSearchParams(this.props.location.search); - let externalActive = this.toBoolean(urlParams.get(this.props.filterName)); - let filterActive = this.toBoolean(this.state.filterActive); - // if the filterActive state is changed by another component (and not by filterChanged func) and - // the query string is not equals at the filterActive state, update the state of the component - if (this.toBoolean(prevState.filterActive) === filterActive && filterActive !== externalActive) { - this.setState({filterActive: externalActive.toString()}); - } + componentWillUnmount() { + dispatcher.unregister(this.connectionsFiltersCallback); } - toBoolean(value) { + toBoolean = (value) => { return value !== null && value.toLowerCase() === "true"; - } + }; - filterChanged() { - this.needRedirect = true; - this.setState({filterActive: (!this.toBoolean(this.state.filterActive)).toString()}); - } + filterChanged = () => { + const newValue = (!this.toBoolean(this.state.filterActive)).toString(); + const urlParams = {}; + urlParams[this.props.filterName] = newValue === "true" ? "true" : null; + dispatcher.dispatch("connections_filters", urlParams); + this.setState({filterActive: newValue}); + }; render() { - let redirect = null; - if (this.needRedirect) { - let urlParams = new URLSearchParams(this.props.location.search); - if (this.toBoolean(this.state.filterActive)) { - urlParams.set(this.props.filterName, "true"); - } else { - urlParams.delete(this.props.filterName); - } - redirect = ; - - this.needRedirect = false; - } - return (
- {redirect} + onChange={this.filterChanged}/>
); } diff --git a/frontend/src/components/filters/ExitSearchFilter.js b/frontend/src/components/filters/ExitSearchFilter.js index cfee298..68ca686 100644 --- a/frontend/src/components/filters/ExitSearchFilter.js +++ b/frontend/src/components/filters/ExitSearchFilter.js @@ -28,11 +28,16 @@ class ExitSearchFilter extends Component { let params = new URLSearchParams(this.props.location.search); this.setState({performedSearch: params.get("performed_search")}); - dispatcher.register("connections_filters", payload => { + this.connectionsFiltersCallback = payload => { if (this.state.performedSearch !== payload["performed_search"]) { this.setState({performedSearch: payload["performed_search"]}); } - }); + }; + dispatcher.register("connections_filters", this.connectionsFiltersCallback); + } + + componentWillUnmount() { + dispatcher.unregister(this.connectionsFiltersCallback); } render() { diff --git a/frontend/src/components/filters/FiltersDispatcher.js b/frontend/src/components/filters/FiltersDispatcher.js deleted file mode 100644 index 3769055..0000000 --- a/frontend/src/components/filters/FiltersDispatcher.js +++ /dev/null @@ -1,57 +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 . - */ - -import React, {Component} from 'react'; -import {withRouter} from "react-router-dom"; -import {Redirect} from "react-router"; -import dispatcher from "../../dispatcher"; - -class FiltersDispatcher extends Component { - - state = {}; - - componentDidMount() { - let params = new URLSearchParams(this.props.location.search); - this.setState({params}); - - dispatcher.register("connections_filters", payload => { - const params = this.state.params; - - Object.entries(payload).forEach(([key, value]) => { - if (value == null) { - params.delete(key); - } else { - params.set(key, value); - } - }); - - this.needRedirect = true; - this.setState({params}); - }); - } - - render() { - if (this.needRedirect) { - this.needRedirect = false; - return ; - } - - return null; - } -} - -export default withRouter(FiltersDispatcher); diff --git a/frontend/src/components/filters/RulesConnectionsFilter.js b/frontend/src/components/filters/RulesConnectionsFilter.js index fc0ad4d..4c993dc 100644 --- a/frontend/src/components/filters/RulesConnectionsFilter.js +++ b/frontend/src/components/filters/RulesConnectionsFilter.js @@ -17,87 +17,74 @@ import React, {Component} from 'react'; import {withRouter} from "react-router-dom"; -import {Redirect} from "react-router"; import './RulesConnectionsFilter.scss'; import ReactTags from 'react-tag-autocomplete'; import backend from "../../backend"; +import dispatcher from "../../dispatcher"; const classNames = require('classnames'); +const _ = require('lodash'); class RulesConnectionsFilter extends Component { - constructor(props) { - super(props); - this.state = { - mounted: false, - rules: [], - activeRules: [] - }; - - this.needRedirect = false; - } + state = { + rules: [], + activeRules: [] + }; componentDidMount() { - let params = new URLSearchParams(this.props.location.search); + const params = new URLSearchParams(this.props.location.search); let activeRules = params.getAll("matched_rules") || []; backend.get("/api/rules").then(res => { let rules = res.json.flatMap(rule => rule.enabled ? [{id: rule.id, name: rule.name}] : []); activeRules = rules.filter(rule => activeRules.some(id => rule.id === id)); - this.setState({rules, activeRules, mounted: true}); + this.setState({rules, activeRules}); }); + + this.connectionsFiltersCallback = payload => { + if ("matched_rules" in payload && !_.isEqual(payload["matched_rules"].sort(), this.state.activeRules.sort())) { + const newRules = this.state.rules.filter(r => payload["matched_rules"].includes(r.id)); + this.setState({ + activeRules: newRules.map(r => { + return {id: r.id, name: r.name}; + }) + }); + } + }; + dispatcher.register("connections_filters", this.connectionsFiltersCallback); } - componentDidUpdate(prevProps, prevState, snapshot) { - let urlParams = new URLSearchParams(this.props.location.search); - let externalRules = urlParams.getAll("matched_rules") || []; - let activeRules = this.state.activeRules.map(r => r.id); - let compareRules = (first, second) => first.sort().join(",") === second.sort().join(","); - if (this.state.mounted && - compareRules(prevState.activeRules.map(r => r.id), activeRules) && - !compareRules(externalRules, activeRules)) { - this.setState({activeRules: externalRules.map(id => this.state.rules.find(r => r.id === id))}); - } + componentWillUnmount() { + dispatcher.unregister(this.connectionsFiltersCallback); } - onDelete(i) { - const activeRules = this.state.activeRules.slice(0); + onDelete = (i) => { + const activeRules = _.clone(this.state.activeRules); activeRules.splice(i, 1); - this.needRedirect = true; - this.setState({ activeRules }); - } + this.setState({activeRules}); + dispatcher.dispatch("connections_filters", {"matched_rules": activeRules.map(r => r.id)}); + }; - onAddition(rule) { + onAddition = (rule) => { if (!this.state.activeRules.includes(rule)) { const activeRules = [].concat(this.state.activeRules, rule); - this.needRedirect = true; this.setState({activeRules}); + dispatcher.dispatch("connections_filters", {"matched_rules": activeRules.map(r => r.id)}); } - } + }; render() { - let redirect = null; - - if (this.needRedirect) { - let urlParams = new URLSearchParams(this.props.location.search); - urlParams.delete("matched_rules"); - this.state.activeRules.forEach(rule => urlParams.append("matched_rules", rule.id)); - redirect = ; - - this.needRedirect = false; - } - return ( -
+
- suggestion.name.startsWith(query) && !this.state.activeRules.includes(suggestion)} /> + suggestion.name.startsWith(query) && !this.state.activeRules.includes(suggestion)}/>
- - {redirect}
); } diff --git a/frontend/src/components/filters/StringConnectionsFilter.js b/frontend/src/components/filters/StringConnectionsFilter.js index a3b45dc..c833220 100644 --- a/frontend/src/components/filters/StringConnectionsFilter.js +++ b/frontend/src/components/filters/StringConnectionsFilter.js @@ -17,37 +17,36 @@ import React, {Component} from 'react'; import {withRouter} from "react-router-dom"; -import {Redirect} from "react-router"; import InputField from "../fields/InputField"; +import dispatcher from "../../dispatcher"; class StringConnectionsFilter extends Component { - constructor(props) { - super(props); - this.state = { - fieldValue: "", - filterValue: null, - timeoutHandle: null, - invalidValue: false - }; - this.needRedirect = false; - this.filterChanged = this.filterChanged.bind(this); - } + state = { + fieldValue: "", + filterValue: null, + timeoutHandle: null, + invalidValue: false + }; componentDidMount() { let params = new URLSearchParams(this.props.location.search); this.updateStateFromFilterValue(params.get(this.props.filterName)); + + this.connectionsFiltersCallback = payload => { + const name = this.props.filterName; + if (name in payload && this.state.filterValue !== payload[name]) { + this.updateStateFromFilterValue(payload[name]); + } + }; + dispatcher.register("connections_filters", this.connectionsFiltersCallback); } - componentDidUpdate(prevProps, prevState, snapshot) { - let urlParams = new URLSearchParams(this.props.location.search); - let filterValue = urlParams.get(this.props.filterName); - if (prevState.filterValue === this.state.filterValue && this.state.filterValue !== filterValue) { - this.updateStateFromFilterValue(filterValue); - } + componentWillUnmount() { + dispatcher.unregister(this.connectionsFiltersCallback); } - updateStateFromFilterValue(filterValue) { + updateStateFromFilterValue = (filterValue) => { if (filterValue !== null) { let fieldValue = filterValue; if (typeof this.props.decodeFunc === "function") { @@ -70,15 +69,21 @@ class StringConnectionsFilter extends Component { } else { this.setState({fieldValue: "", filterValue: null}); } - } + }; - isValueValid(value) { + isValueValid = (value) => { return typeof this.props.validateFunc !== "function" || (typeof this.props.validateFunc === "function" && this.props.validateFunc(value)); - } + }; - filterChanged(fieldValue) { - if (this.state.timeoutHandle !== null) { + changeFilterValue = (value) => { + const urlParams = {}; + urlParams[this.props.filterName] = value; + dispatcher.dispatch("connections_filters", urlParams); + }; + + filterChanged = (fieldValue) => { + if (this.state.timeoutHandle) { clearTimeout(this.state.timeoutHandle); } @@ -87,11 +92,12 @@ class StringConnectionsFilter extends Component { } if (fieldValue === "") { - this.needRedirect = true; this.setState({fieldValue: "", filterValue: null, invalidValue: false}); - return; + return this.changeFilterValue(null); } + + if (this.isValueValid(fieldValue)) { let filterValue = fieldValue; if (filterValue !== "" && typeof this.props.encodeFunc === "function") { @@ -101,40 +107,27 @@ class StringConnectionsFilter extends Component { this.setState({ fieldValue: fieldValue, timeoutHandle: setTimeout(() => { - this.needRedirect = true; this.setState({filterValue: filterValue}); + this.changeFilterValue(filterValue); }, 500), invalidValue: false }); } else { - this.needRedirect = true; this.setState({ fieldValue: fieldValue, invalidValue: true }); } - } + }; render() { - let redirect = null; - if (this.needRedirect) { - let urlParams = new URLSearchParams(this.props.location.search); - if (this.state.filterValue !== null) { - urlParams.set(this.props.filterName, this.state.filterValue); - } else { - urlParams.delete(this.props.filterName); - } - redirect = ; - this.needRedirect = false; - } let active = this.state.filterValue !== null; return (
- {redirect} + value={this.state.fieldValue} inline={true} small={true}/>
); } diff --git a/frontend/src/components/objects/Connection.js b/frontend/src/components/objects/Connection.js index e0e942a..96f2235 100644 --- a/frontend/src/components/objects/Connection.js +++ b/frontend/src/components/objects/Connection.js @@ -23,6 +23,7 @@ import {dateTimeToTime, durationBetween, formatSize} from "../../utils"; import ButtonField from "../fields/ButtonField"; import LinkPopover from "./LinkPopover"; import TextField from "../fields/TextField"; +import dispatcher from "../../dispatcher"; const classNames = require('classnames'); @@ -99,7 +100,8 @@ class Connection extends Component { this.props.addServicePortFilter(conn["port_dst"])}/> + onClick={() => dispatcher.dispatch("connections_filters", + {"service_port": conn["port_dst"].toString()})}/> {conn["ip_src"]} diff --git a/frontend/src/components/objects/ConnectionMatchedRules.js b/frontend/src/components/objects/ConnectionMatchedRules.js index 73d5c5d..92bde49 100644 --- a/frontend/src/components/objects/ConnectionMatchedRules.js +++ b/frontend/src/components/objects/ConnectionMatchedRules.js @@ -18,20 +18,25 @@ import React, {Component} from 'react'; import './ConnectionMatchedRules.scss'; import ButtonField from "../fields/ButtonField"; +import dispatcher from "../../dispatcher"; +import {withRouter} from "react-router-dom"; class ConnectionMatchedRules extends Component { - constructor(props) { - super(props); - this.state = { - }; - } + onMatchedRulesSelected = (id) => { + const params = new URLSearchParams(this.props.location.search); + const rules = params.getAll("matched_rules"); + if (!rules.includes(id)) { + rules.push(id); + dispatcher.dispatch("connections_filters",{"matched_rules": rules}); + } + }; render() { const matchedRules = this.props.matchedRules.map(mr => { const rule = this.props.rules.find(r => r.id === mr); - return this.props.addMatchedRulesFilter(rule.id)} name={rule.name} - color={rule.color} small />; + return this.onMatchedRulesSelected(rule.id)} name={rule.name} + color={rule.color} small/>; }); return ( @@ -43,4 +48,4 @@ class ConnectionMatchedRules extends Component { } } -export default ConnectionMatchedRules; +export default withRouter(ConnectionMatchedRules); diff --git a/frontend/src/components/pages/MainPage.js b/frontend/src/components/pages/MainPage.js index da57e1a..0b06f55 100644 --- a/frontend/src/components/pages/MainPage.js +++ b/frontend/src/components/pages/MainPage.js @@ -29,7 +29,6 @@ import Header from "../Header"; import Filters from "../dialogs/Filters"; import MainPane from "../panels/MainPane"; import SearchPane from "../panels/SearchPane"; -import FiltersDispatcher from "../filters/FiltersDispatcher"; class MainPage extends Component { @@ -70,8 +69,6 @@ class MainPage extends Component {
- -
); diff --git a/frontend/src/components/panels/ConnectionsPane.js b/frontend/src/components/panels/ConnectionsPane.js index 1f79ab8..33dd7c1 100644 --- a/frontend/src/components/panels/ConnectionsPane.js +++ b/frontend/src/components/panels/ConnectionsPane.js @@ -19,13 +19,13 @@ import React, {Component} from 'react'; import './ConnectionsPane.scss'; import Connection from "../objects/Connection"; import Table from 'react-bootstrap/Table'; -import {Redirect} from 'react-router'; import {withRouter} from "react-router-dom"; import backend from "../../backend"; import ConnectionMatchedRules from "../objects/ConnectionMatchedRules"; import log from "../../log"; import ButtonField from "../fields/ButtonField"; import dispatcher from "../../dispatcher"; +import {Redirect} from "react-router"; const classNames = require('classnames'); @@ -50,60 +50,91 @@ class ConnectionsPane extends Component { } componentDidMount() { - const initialParams = {limit: this.queryLimit}; + let urlParams = new URLSearchParams(this.props.location.search); + this.setState({urlParams}); + + const additionalParams = {limit: this.queryLimit}; const match = this.props.location.pathname.match(/^\/connections\/([a-f0-9]{24})$/); if (match != null) { const id = match[1]; - initialParams.from = id; + additionalParams.from = id; backend.get(`/api/connections/${id}`) - .then(res => this.connectionSelected(res.json, false)) + .then(res => this.connectionSelected(res.json)) .catch(error => log.error("Error loading initial connection", error)); } - this.loadConnections(initialParams, true).then(() => log.debug("Connections loaded")); + this.loadConnections(additionalParams, urlParams, true).then(() => log.debug("Connections loaded")); + + this.connectionsFiltersCallback = payload => { + const params = this.state.urlParams; + const initialParams = params.toString(); + + Object.entries(payload).forEach(([key, value]) => { + if (value == null) { + params.delete(key); + } else if (Array.isArray(value)) { + params.delete(key); + value.forEach(v => params.append(key, v)); + } else { + params.set(key, value); + } + }); + + if (initialParams === params.toString()) { + return; + } - dispatcher.register("timeline_updates", payload => { + log.debug("Update following url params:", payload); + this.queryStringRedirect = true; + this.setState({urlParams}); + + this.loadConnections({limit: this.queryLimit}, urlParams) + .then(() => log.info("ConnectionsPane reloaded after query string update")); + }; + dispatcher.register("connections_filters", this.connectionsFiltersCallback); + + this.timelineUpdatesCallback = payload => { this.connectionsListRef.current.scrollTop = 0; this.loadConnections({ started_after: Math.round(payload.from.getTime() / 1000), started_before: Math.round(payload.to.getTime() / 1000), limit: this.maxConnections }).then(() => log.info(`Loading connections between ${payload.from} and ${payload.to}`)); - }); + }; + dispatcher.register("timeline_updates", this.timelineUpdatesCallback); - dispatcher.register("notifications", payload => { + this.notificationsCallback = payload => { if (payload.event === "rules.new" || payload.event === "rules.edit") { this.loadRules().then(() => log.debug("Loaded connection rules after notification update")); } - }); - - dispatcher.register("notifications", payload => { if (payload.event === "services.edit") { this.loadServices().then(() => log.debug("Services reloaded after notification update")); } - }); + }; + dispatcher.register("notifications", this.notificationsCallback); - dispatcher.register("pulse_connections_view", payload => { + this.pulseConnectionsViewCallback = payload => { this.setState({pulseConnectionsView: true}); setTimeout(() => this.setState({pulseConnectionsView: false}), payload.duration); - }); + }; + dispatcher.register("pulse_connections_view", this.pulseConnectionsViewCallback); + } + + componentWillUnmount() { + dispatcher.unregister(this.timelineUpdatesCallback); + dispatcher.unregister(this.notificationsCallback); + dispatcher.unregister(this.pulseConnectionsViewCallback); + dispatcher.unregister(this.connectionsFiltersCallback); } - connectionSelected = (c, doRedirect = true) => { - this.doSelectedConnectionRedirect = doRedirect; + connectionSelected = (c) => { + this.connectionSelectedRedirect = true; this.setState({selected: c.id}); this.props.onSelected(c); log.debug(`Connection ${c.id} selected`); }; - componentDidUpdate(prevProps, prevState, snapshot) { - if (prevProps.location.search !== this.props.location.search) { - this.loadConnections({limit: this.queryLimit}) - .then(() => log.info("ConnectionsPane reloaded after query string update")); - } - } - handleScroll = (e) => { if (this.disableScrollHandler) { this.lastScrollPosition = e.currentTarget.scrollTop; @@ -135,27 +166,12 @@ class ConnectionsPane extends Component { this.lastScrollPosition = e.currentTarget.scrollTop; }; - addServicePortFilter = (port) => { - const urlParams = new URLSearchParams(this.props.location.search); - urlParams.set("service_port", port); - this.doQueryStringRedirect = true; - this.setState({queryString: urlParams}); - }; - - addMatchedRulesFilter = (matchedRule) => { - const urlParams = new URLSearchParams(this.props.location.search); - const oldMatchedRules = urlParams.getAll("matched_rules") || []; - - if (!oldMatchedRules.includes(matchedRule)) { - urlParams.append("matched_rules", matchedRule); - this.doQueryStringRedirect = true; - this.setState({queryString: urlParams}); + async loadConnections(additionalParams, initialParams = null, isInitial = false) { + if (!initialParams) { + initialParams = this.state.urlParams; } - }; - - async loadConnections(params, isInitial = false) { - const urlParams = new URLSearchParams(this.props.location.search); - for (const [name, value] of Object.entries(params)) { + const urlParams = new URLSearchParams(initialParams.toString()); + for (const [name, value] of Object.entries(additionalParams)) { urlParams.set(name, value); } @@ -173,7 +189,7 @@ class ConnectionsPane extends Component { let firstConnection = this.state.firstConnection; let lastConnection = this.state.lastConnection; - if (params !== undefined && params.from !== undefined && params.to === undefined) { + if (additionalParams !== undefined && additionalParams.from !== undefined && additionalParams.to === undefined) { if (res.length > 0) { if (!isInitial) { res = res.slice(1); @@ -189,7 +205,7 @@ class ConnectionsPane extends Component { firstConnection = connections[0]; } } - } else if (params !== undefined && params.to !== undefined && params.from === undefined) { + } else if (additionalParams !== undefined && additionalParams.to !== undefined && additionalParams.from === undefined) { if (res.length > 0) { connections = res.slice(0, res.length - 1).concat(this.state.connections); firstConnection = connections[0]; @@ -235,12 +251,12 @@ class ConnectionsPane extends Component { render() { let redirect; - if (this.doSelectedConnectionRedirect) { - redirect = ; - this.doSelectedConnectionRedirect = false; - } else if (this.doQueryStringRedirect) { - redirect = ; - this.doQueryStringRedirect = false; + if (this.connectionSelectedRedirect) { + redirect = ; + this.connectionSelectedRedirect = false; + } else if (this.queryStringRedirect) { + redirect = ; + this.queryStringRedirect = false; } let loading = null; @@ -288,12 +304,10 @@ class ConnectionsPane extends Component { selected={this.state.selected === c.id} onMarked={marked => c.marked = marked} onEnabled={enabled => c.hidden = !enabled} - addServicePortFilter={this.addServicePortFilter} services={this.state.services}/>, c.matched_rules.length > 0 && + rules={this.state.rules}/> ]; }) } diff --git a/frontend/src/components/panels/SearchPane.scss b/frontend/src/components/panels/SearchPane.scss index 15fc7da..63e11fb 100644 --- a/frontend/src/components/panels/SearchPane.scss +++ b/frontend/src/components/panels/SearchPane.scss @@ -4,6 +4,7 @@ .searches-list { overflow: hidden; + flex: 2 1; .section-content { height: 100%; diff --git a/frontend/src/components/panels/StreamsPane.js b/frontend/src/components/panels/StreamsPane.js index bd1964e..1aa5c53 100644 --- a/frontend/src/components/panels/StreamsPane.js +++ b/frontend/src/components/panels/StreamsPane.js @@ -107,7 +107,7 @@ class StreamsPane extends Component { const json = JSON.parse(m.body); body = ; } catch (e) { - console.log(e); + log.error(e); } } diff --git a/frontend/src/dispatcher.js b/frontend/src/dispatcher.js index 943f7ec..fa08d48 100644 --- a/frontend/src/dispatcher.js +++ b/frontend/src/dispatcher.js @@ -15,6 +15,8 @@ * along with this program. If not, see . */ +const _ = require('lodash'); + class Dispatcher { constructor() { @@ -44,6 +46,10 @@ class Dispatcher { } }; + unregister = (callback) => { + this.listeners = _.without(callback); + }; + } const dispatcher = new Dispatcher(); diff --git a/search_controller.go b/search_controller.go index 723cd93..5ed762a 100644 --- a/search_controller.go +++ b/search_controller.go @@ -76,6 +76,10 @@ func NewSearchController(storage Storage) *SearchController { log.WithError(err).Panic("failed to retrieve performed searches") } + if searches == nil { + searches = []PerformedSearch{} + } + return &SearchController{ storage: storage, performedSearches: searches, diff --git a/statistics_controller.go b/statistics_controller.go index 57c7d95..fda7494 100644 --- a/statistics_controller.go +++ b/statistics_controller.go @@ -26,19 +26,19 @@ import ( type StatisticRecord struct { RangeStart time.Time `json:"range_start" bson:"_id"` - ConnectionsPerService map[uint16]int `json:"connections_per_service,omitempty" bson:"connections_per_service"` - ClientBytesPerService map[uint16]int `json:"client_bytes_per_service,omitempty" bson:"client_bytes_per_service"` - ServerBytesPerService map[uint16]int `json:"server_bytes_per_service,omitempty" bson:"server_bytes_per_service"` - TotalBytesPerService map[uint16]int `json:"total_bytes_per_service,omitempty" bson:"total_bytes_per_service"` - DurationPerService map[uint16]int64 `json:"duration_per_service,omitempty" bson:"duration_per_service"` - MatchedRules map[RowID]int64 `json:"matched_rules,omitempty" bson:"matched_rules"` + ConnectionsPerService map[uint16]int `json:"connections_per_service" bson:"connections_per_service"` + ClientBytesPerService map[uint16]int `json:"client_bytes_per_service" bson:"client_bytes_per_service"` + ServerBytesPerService map[uint16]int `json:"server_bytes_per_service" bson:"server_bytes_per_service"` + TotalBytesPerService map[uint16]int `json:"total_bytes_per_service" bson:"total_bytes_per_service"` + DurationPerService map[uint16]int64 `json:"duration_per_service" bson:"duration_per_service"` + MatchedRules map[string]int64 `json:"matched_rules" bson:"matched_rules"` } type StatisticsFilter struct { RangeFrom time.Time `form:"range_from"` RangeTo time.Time `form:"range_to"` Ports []uint16 `form:"ports"` - RulesIDs []RowID `form:"rules_ids"` + RulesIDs []string `form:"rules_ids"` Metric string `form:"metric"` } @@ -57,7 +57,7 @@ func NewStatisticsController(storage Storage) StatisticsController { func (sc *StatisticsController) GetStatistics(context context.Context, filter StatisticsFilter) []StatisticRecord { var statisticRecords []StatisticRecord - query := sc.storage.Find(Statistics).Context(context) + query := sc.storage.Find(Statistics).Context(context).Sort("_id", true) if !filter.RangeFrom.IsZero() { query = query.Filter(OrderedDocument{{"_id", UnorderedDocument{"$lt": filter.RangeFrom}}}) } @@ -81,7 +81,7 @@ func (sc *StatisticsController) GetStatistics(context context.Context, filter St } for _, ruleID := range filter.RulesIDs { if filter.Metric == "" || filter.Metric == "matched_rules" { - query = query.Projection(OrderedDocument{{fmt.Sprintf("matched_rules.%s", ruleID.Hex()), 1}}) + query = query.Projection(OrderedDocument{{fmt.Sprintf("matched_rules.%s", ruleID), 1}}) } } @@ -91,6 +91,7 @@ func (sc *StatisticsController) GetStatistics(context context.Context, filter St } } + log.Println(query) if err := query.All(&statisticRecords); err != nil { log.WithError(err).WithField("filter", filter).Error("failed to retrieve statistics") return []StatisticRecord{} -- cgit v1.2.3-70-g09d2 From c745263e1b28e4cedffa88de764f11d6379d745d Mon Sep 17 00:00:00 2001 From: Emiliano Ciavatta Date: Thu, 15 Oct 2020 13:50:37 +0200 Subject: Update rules filters --- frontend/src/components/Timeline.js | 77 ++++++------- frontend/src/components/dialogs/Filters.js | 2 +- frontend/src/components/fields/TagField.js | 29 +++-- frontend/src/components/fields/TagField.scss | 99 +++++++++++------ frontend/src/components/fields/common.scss | 1 + .../components/filters/RulesConnectionsFilter.js | 23 +--- .../components/filters/RulesConnectionsFilter.scss | 121 --------------------- frontend/src/components/objects/MessageAction.js | 2 +- frontend/src/components/panels/PcapsPane.js | 6 +- frontend/src/components/panels/SearchPane.js | 14 +-- frontend/src/components/panels/StreamsPane.js | 2 +- statistics_controller.go | 5 +- 12 files changed, 139 insertions(+), 242 deletions(-) delete mode 100644 frontend/src/components/filters/RulesConnectionsFilter.scss (limited to 'statistics_controller.go') diff --git a/frontend/src/components/Timeline.js b/frontend/src/components/Timeline.js index bc42a01..1d88bcb 100644 --- a/frontend/src/components/Timeline.js +++ b/frontend/src/components/Timeline.js @@ -35,7 +35,6 @@ import log from "../log"; import dispatcher from "../dispatcher"; const minutes = 60 * 1000; -const _ = require('lodash'); const classNames = require('classnames'); const leftSelectionPaddingMultiplier = 24; @@ -54,19 +53,26 @@ class Timeline extends Component { this.selectionTimeout = null; } - additionalFilters = () => { + componentDidMount() { const urlParams = new URLSearchParams(this.props.location.search); - if (this.state.metric === "matched_rules") { - return urlParams.getAll("matched_rules") || []; - } else { - return urlParams.get("service_port"); - } - }; + this.setState({ + servicePortFilter: urlParams.get("service_port") || null, + matchedRulesFilter: urlParams.getAll("matched_rules") || null + }); - componentDidMount() { - const additionalFilters = this.additionalFilters(); - this.setState({filters: additionalFilters}); - this.loadStatistics(this.state.metric, additionalFilters).then(() => log.debug("Statistics loaded after mount")); + this.loadStatistics(this.state.metric).then(() => log.debug("Statistics loaded after mount")); + + this.connectionsFiltersCallback = 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")); + } + }; + dispatcher.register("connections_filters", this.connectionsFiltersCallback); dispatcher.register("connection_updates", payload => { this.setState({ @@ -76,8 +82,10 @@ class Timeline extends Component { }); dispatcher.register("notifications", payload => { - if (payload.event === "services.edit") { - this.loadServices().then(() => this.adjustSelection()); + if (payload.event === "services.edit" && this.state.metric !== "matched_rules") { + this.loadServices().then(() => log.debug("Statistics reloaded after services updates")); + } else if (payload.event.startsWith("rules") && this.state.metric === "matched_rules") { + this.loadServices().then(() => log.debug("Statistics reloaded after rules updates")); } }); @@ -87,41 +95,29 @@ class Timeline extends Component { }); } - componentDidUpdate(prevProps, prevState, snapshot) { - const additionalFilters = this.additionalFilters(); - const updateStatistics = () => { - this.setState({filters: additionalFilters}); - this.loadStatistics(this.state.metric, additionalFilters).then(() => - log.debug("Statistics reloaded after filters changes")); - }; - - if (this.state.metric === "matched_rules") { - if (!Array.isArray(this.state.filters) || - !_.isEqual(_.sortBy(additionalFilters), _.sortBy(this.state.filters))) { - updateStatistics(); - } - } else { - if (this.state.filters !== additionalFilters) { - updateStatistics(); - } - } + componentWillUnmount() { + dispatcher.unregister(this.connectionsFiltersCallback); } - loadStatistics = async (metric, filters) => { + loadStatistics = async (metric) => { const urlParams = new URLSearchParams(); urlParams.set("metric", metric); let columns = []; if (metric === "matched_rules") { let rules = await this.loadRules(); - filters.forEach(id => { - urlParams.append("matched_rules", id); - }); - columns = rules.map(r => r.id); + 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 = filters; - if (filteredPort && services[filters]) { + const filteredPort = this.state.servicePortFilter; + if (filteredPort && services[filteredPort]) { const service = services[filteredPort]; services = {}; services[filteredPort] = service; @@ -172,7 +168,6 @@ class Timeline extends Component { start, end }); - log.debug(`Loaded statistics for metric "${metric}"`); }; loadServices = async () => { @@ -279,7 +274,7 @@ class Timeline extends Component { "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, this.state.filters) + onChange={(metric) => this.loadStatistics(metric) .then(() => log.debug("Statistics loaded after metric changes"))} value={this.state.metric}/>
diff --git a/frontend/src/components/dialogs/Filters.js b/frontend/src/components/dialogs/Filters.js index d2cce4f..a35ece2 100644 --- a/frontend/src/components/dialogs/Filters.js +++ b/frontend/src/components/dialogs/Filters.js @@ -16,7 +16,7 @@ */ import React, {Component} from 'react'; -import {Col, Container, Modal, Row} from "react-bootstrap"; +import {Modal} from "react-bootstrap"; import ButtonField from "../fields/ButtonField"; import './Filters.scss'; import {cleanNumber, validateIpAddress, validateMin, validatePort} from "../../utils"; diff --git a/frontend/src/components/fields/TagField.js b/frontend/src/components/fields/TagField.js index f1a48bd..89445b6 100644 --- a/frontend/src/components/fields/TagField.js +++ b/frontend/src/components/fields/TagField.js @@ -26,50 +26,47 @@ const _ = require('lodash'); class TagField extends Component { + state = {}; + constructor(props) { super(props); this.id = `field-${this.props.name || "noname"}-${randomClassName()}`; } - state = { - - }; - onAddition = (tag) => { if (typeof this.props.onChange === "function") { - const tags = [].concat(this.wrappedTags(), tag); - this.props.onChange(tags.map(t => t.name), true, tag); // true == addition + this.props.onChange([].concat(this.props.tags, tag), true, tag); // true == addition } }; onDelete = (i) => { if (typeof this.props.onChange === "function") { - const tags = this.wrappedTags(); + const tags = _.clone(this.props.tags); const tag = tags[i]; tags.splice(i, 1); - this.props.onChange(tags.map(t => t.name), true, tag); // false == delete + this.props.onChange(tags, true, tag); // false == delete } }; - wrappedTags = () => this.props.tags.map(t => new Object({"name": t})); render() { const small = this.props.small || false; const name = this.props.name || null; return ( -
- { name && +
+ {name &&
} - +
+ +
); } diff --git a/frontend/src/components/fields/TagField.scss b/frontend/src/components/fields/TagField.scss index e77db97..737f11f 100644 --- a/frontend/src/components/fields/TagField.scss +++ b/frontend/src/components/fields/TagField.scss @@ -1,31 +1,68 @@ @import "../../colors.scss"; .tag-field { + font-size: 0.9em; + margin: 5px 0; + + .field-name { + label { + margin: 0; + } + } + + &.field-small { + font-size: 0.8em; + } + + &.field-inline { + display: flex; + + .field-name { + padding: 6px 0 6px 7px; + border-top-left-radius: 5px; + border-bottom-left-radius: 5px; + background-color: $color-primary-2; + } + + .field-input { + flex: 1; + + .react-tags { + padding-left: 3px; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + } + + &:focus-within .field-name { + background-color: $color-primary-1; + } + } + .react-tags { - font-size: 12px; position: relative; - z-index: 10; - padding: 0 6px; - cursor: text; + display: flex; border-radius: 4px; background-color: $color-primary-2; - } - .react-tags.is-focused { - border-color: #b1b1b1; + &:focus-within, + &:focus-within .react-tags__search-input { + background-color: $color-primary-1; + } } .react-tags__selected { - display: inline; + display: inline-block; + flex: 0 1; + margin: 6px 0; + white-space: nowrap; } .react-tags__selected-tag { - font-size: 11px; - display: inline-block; - margin: 0 6px 6px 0; + font-size: 0.75em; + margin: 0 3px; padding: 2px 4px; color: $color-primary-3; - border: none; border-radius: 2px; background: $color-primary-4; } @@ -39,12 +76,15 @@ .react-tags__selected-tag:hover, .react-tags__selected-tag:focus { border-color: #b1b1b1; + background-color: $color-primary-0; + + &::after { + color: $color-primary-4; + } } .react-tags__search { - display: inline-block; - max-width: 100%; - padding: 7px 10px; + flex: 1 0; } @media screen and (min-width: 30em) { @@ -54,14 +94,7 @@ } .react-tags__search-input { - font-size: inherit; - line-height: inherit; - max-width: 100%; - margin: 0; - padding: 0; color: $color-primary-4; - border: 0; - outline: none; background-color: $color-primary-2; } @@ -71,6 +104,7 @@ .react-tags__suggestions { position: absolute; + z-index: 50; top: 100%; left: 0; width: 100%; @@ -87,30 +121,33 @@ margin: 4px -1px; padding: 0; list-style: none; - color: $color-primary-1; - border-radius: 2px; - background: $color-primary-4; + border-radius: 3px; + background: $color-primary-2; } .react-tags__suggestions li { - padding: 3px 5px; - border-bottom: 1px solid #ddd; + padding: 5px 10px; } .react-tags__suggestions li mark { font-weight: 600; - text-decoration: underline; + padding: 0; + color: $color-primary-4; background: none; } .react-tags__suggestions li:hover { cursor: pointer; - color: $color-primary-4; - background: $color-primary-0; + border-radius: 3px; + background: $color-primary-1; + + mark { + color: $color-primary-4; + } } .react-tags__suggestions li.is-active { - background: #b7cfe0; + background: $color-primary-3; } .react-tags__suggestions li.is-disabled { diff --git a/frontend/src/components/fields/common.scss b/frontend/src/components/fields/common.scss index 8fbef0d..e5dc65c 100644 --- a/frontend/src/components/fields/common.scss +++ b/frontend/src/components/fields/common.scss @@ -3,6 +3,7 @@ .field { input, textarea { + font-family: "Fira Code", monospace; width: 100%; padding: 7px 10px; color: $color-primary-4; diff --git a/frontend/src/components/filters/RulesConnectionsFilter.js b/frontend/src/components/filters/RulesConnectionsFilter.js index 4c993dc..8e40d30 100644 --- a/frontend/src/components/filters/RulesConnectionsFilter.js +++ b/frontend/src/components/filters/RulesConnectionsFilter.js @@ -17,10 +17,9 @@ import React, {Component} from 'react'; import {withRouter} from "react-router-dom"; -import './RulesConnectionsFilter.scss'; -import ReactTags from 'react-tag-autocomplete'; import backend from "../../backend"; import dispatcher from "../../dispatcher"; +import TagField from "../fields/TagField"; const classNames = require('classnames'); const _ = require('lodash'); @@ -59,16 +58,8 @@ class RulesConnectionsFilter extends Component { dispatcher.unregister(this.connectionsFiltersCallback); } - onDelete = (i) => { - const activeRules = _.clone(this.state.activeRules); - activeRules.splice(i, 1); - this.setState({activeRules}); - dispatcher.dispatch("connections_filters", {"matched_rules": activeRules.map(r => r.id)}); - }; - - onAddition = (rule) => { - if (!this.state.activeRules.includes(rule)) { - const activeRules = [].concat(this.state.activeRules, rule); + onChange = (activeRules) => { + if (!_.isEqual(activeRules.sort(), this.state.activeRules.sort())) { this.setState({activeRules}); dispatcher.dispatch("connections_filters", {"matched_rules": activeRules.map(r => r.id)}); } @@ -79,11 +70,9 @@ class RulesConnectionsFilter extends Component {
- - suggestion.name.startsWith(query) && !this.state.activeRules.includes(suggestion)}/> +
); diff --git a/frontend/src/components/filters/RulesConnectionsFilter.scss b/frontend/src/components/filters/RulesConnectionsFilter.scss deleted file mode 100644 index 0bb4952..0000000 --- a/frontend/src/components/filters/RulesConnectionsFilter.scss +++ /dev/null @@ -1,121 +0,0 @@ -@import "../../colors"; - -.filter-rules { - .react-tags { - font-size: 12px; - position: relative; - z-index: 10; - padding: 0 6px; - cursor: text; - border-radius: 4px; - background-color: $color-primary-2; - } - - .react-tags.is-focused { - border-color: #b1b1b1; - } - - .react-tags__selected { - display: inline; - } - - .react-tags__selected-tag { - font-size: 11px; - display: inline-block; - margin: 0 6px 6px 0; - padding: 2px 4px; - color: $color-primary-3; - border: none; - border-radius: 2px; - background: $color-primary-4; - } - - .react-tags__selected-tag::after { - margin-left: 8px; - content: "\2715"; - color: $color-primary-3; - } - - .react-tags__selected-tag:hover, - .react-tags__selected-tag:focus { - border-color: #b1b1b1; - } - - .react-tags__search { - display: inline-block; - max-width: 100%; - padding: 7px 10px; - } - - @media screen and (min-width: 30em) { - .react-tags__search { - position: relative; - } - } - - .react-tags__search-input { - font-size: inherit; - line-height: inherit; - max-width: 100%; - margin: 0; - padding: 0; - color: $color-primary-4; - border: 0; - outline: none; - background-color: $color-primary-2; - } - - .react-tags__search-input::-ms-clear { - display: none; - } - - .react-tags__suggestions { - position: absolute; - top: 100%; - left: 0; - width: 100%; - } - - @media screen and (min-width: 30em) { - .react-tags__suggestions { - width: 240px; - } - } - - .react-tags__suggestions ul { - font-size: 12px; - margin: 4px -1px; - padding: 0; - list-style: none; - color: $color-primary-1; - border-radius: 2px; - background: $color-primary-4; - } - - .react-tags__suggestions li { - padding: 3px 5px; - border-bottom: 1px solid #ddd; - } - - .react-tags__suggestions li mark { - font-weight: 600; - text-decoration: underline; - background: none; - } - - .react-tags__suggestions li:hover { - cursor: pointer; - color: $color-primary-4; - background: $color-primary-0; - } - - .react-tags__suggestions li.is-active { - background: #b7cfe0; - } - - .react-tags__suggestions li.is-disabled { - cursor: auto; - opacity: 0.5; - } - -} diff --git a/frontend/src/components/objects/MessageAction.js b/frontend/src/components/objects/MessageAction.js index 9f199b7..2b46320 100644 --- a/frontend/src/components/objects/MessageAction.js +++ b/frontend/src/components/objects/MessageAction.js @@ -43,7 +43,7 @@ class MessageAction extends Component { return ( - + {s["id"].substring(0, 8)} {dateTimeToTime(s["started_at"])} {durationBetween(s["started_at"], s["completed_at"])} @@ -166,13 +166,13 @@ class PcapsPane extends Component { }); }; - const uploadCurlCommand = createCurlCommand("pcap/upload", "POST", null, { + const uploadCurlCommand = createCurlCommand("/pcap/upload", "POST", null, { file: "@" + ((this.state.uploadSelectedFile != null && this.state.isUploadFileValid) ? this.state.uploadSelectedFile.name : "invalid.pcap"), flush_all: this.state.uploadFlushAll }); - const fileCurlCommand = createCurlCommand("pcap/file", "POST", { + const fileCurlCommand = createCurlCommand("/pcap/file", "POST", { file: this.state.fileValue, flush_all: this.state.processFlushAll, delete_original_file: this.state.deleteOriginalFile diff --git a/frontend/src/components/panels/SearchPane.js b/frontend/src/components/panels/SearchPane.js index 21ba139..1fb48ef 100644 --- a/frontend/src/components/panels/SearchPane.js +++ b/frontend/src/components/panels/SearchPane.js @@ -29,7 +29,6 @@ import dispatcher from "../../dispatcher"; import TagField from "../fields/TagField"; import CheckField from "../fields/CheckField"; -const classNames = require('classnames'); const _ = require('lodash'); class SearchPane extends Component { @@ -161,7 +160,7 @@ class SearchPane extends Component { const options = this.state.currentSearchOptions; let searches = this.state.searches.map(s => - + {s.id.substring(0, 8)} {this.extractPattern(s["search_options"])} {s["affected_connections_count"]} @@ -223,13 +222,14 @@ class SearchPane extends Component {
- {return {name: t};})} + name="terms" min={3} inline allowNew={true} readonly={regexOptionsModified || options.text_search.exact_phrase} - onChange={(tags) => this.updateParam(s => s.text_search.terms = tags)}/> - this.updateParam(s => s.text_search.terms = tags.map(t => t.name))}/> + {return {name: t};})} + name="excluded_terms" min={3} inline allowNew={true} readonly={regexOptionsModified || options.text_search.exact_phrase} - onChange={(tags) => this.updateParam(s => s.text_search.excluded_terms = tags)}/> + onChange={(tags) => this.updateParam(s => s.text_search.excluded_terms = tags.map(t => t.name))}/> or diff --git a/frontend/src/components/panels/StreamsPane.js b/frontend/src/components/panels/StreamsPane.js index 1aa5c53..be39777 100644 --- a/frontend/src/components/panels/StreamsPane.js +++ b/frontend/src/components/panels/StreamsPane.js @@ -65,7 +65,7 @@ class StreamsPane extends Component { } loadStream = (connectionId) => { - this.setState({messages: []}); + this.setState({messages: [], currentId: connectionId}); backend.get(`/api/streams/${connectionId}?format=${this.state.format}`) .then(res => this.setState({messages: res.json})); }; diff --git a/statistics_controller.go b/statistics_controller.go index fda7494..29f3fec 100644 --- a/statistics_controller.go +++ b/statistics_controller.go @@ -31,14 +31,14 @@ type StatisticRecord struct { ServerBytesPerService map[uint16]int `json:"server_bytes_per_service" bson:"server_bytes_per_service"` TotalBytesPerService map[uint16]int `json:"total_bytes_per_service" bson:"total_bytes_per_service"` DurationPerService map[uint16]int64 `json:"duration_per_service" bson:"duration_per_service"` - MatchedRules map[string]int64 `json:"matched_rules" bson:"matched_rules"` + MatchedRules map[string]int64 `json:"matched_rules" bson:"matched_rules"` } type StatisticsFilter struct { RangeFrom time.Time `form:"range_from"` RangeTo time.Time `form:"range_to"` Ports []uint16 `form:"ports"` - RulesIDs []string `form:"rules_ids"` + RulesIDs []string `form:"rules_ids"` Metric string `form:"metric"` } @@ -91,7 +91,6 @@ func (sc *StatisticsController) GetStatistics(context context.Context, filter St } } - log.Println(query) if err := query.All(&statisticRecords); err != nil { log.WithError(err).WithField("filter", filter).Error("failed to retrieve statistics") return []StatisticRecord{} -- cgit v1.2.3-70-g09d2