aboutsummaryrefslogtreecommitdiff
path: root/frontend/src/components
diff options
context:
space:
mode:
authorEmiliano Ciavatta2020-10-07 12:58:48 +0000
committerEmiliano Ciavatta2020-10-07 12:58:48 +0000
commitd5f94b76986615b255b77b2a7b7ed336e5ad4838 (patch)
treec813c55845be273efccf60995f43a77fdee68ac8 /frontend/src/components
parente905618113309eaba7227ff1328a20f6846e4afd (diff)
Implement notifications
Diffstat (limited to 'frontend/src/components')
-rw-r--r--frontend/src/components/Connection.js11
-rw-r--r--frontend/src/components/ConnectionContent.scss9
-rw-r--r--frontend/src/components/Notifications.js60
-rw-r--r--frontend/src/components/Notifications.scss48
-rw-r--r--frontend/src/components/panels/PcapPane.js33
-rw-r--r--frontend/src/components/panels/RulePane.js116
-rw-r--r--frontend/src/components/panels/ServicePane.js35
-rw-r--r--frontend/src/components/panels/common.scss4
8 files changed, 219 insertions, 97 deletions
diff --git a/frontend/src/components/Connection.js b/frontend/src/components/Connection.js
index 44f9f18..46a0cab 100644
--- a/frontend/src/components/Connection.js
+++ b/frontend/src/components/Connection.js
@@ -48,10 +48,11 @@ class Connection extends Component {
render() {
let conn = this.props.data;
let serviceName = "/dev/null";
- let serviceColor = "#0F192E";
- if (conn.service.port !== 0) {
- serviceName = conn.service.name;
- serviceColor = conn.service.color;
+ let serviceColor = "#0f192e";
+ if (this.props.services[conn["port_dst"]]) {
+ const service = this.props.services[conn["port_dst"]];
+ serviceName = service.name;
+ serviceColor = service.color;
}
let startedAt = new Date(conn.started_at);
let closedAt = new Date(conn.closed_at);
@@ -87,7 +88,7 @@ class Connection extends Component {
<td>
<span className="connection-service">
<ButtonField small fullSpan color={serviceColor} name={serviceName}
- onClick={() => this.props.addServicePortFilter(conn.port_dst)} />
+ onClick={() => this.props.addServicePortFilter(conn.port_dst)}/>
</span>
</td>
<td className="clickable" onClick={this.props.onSelected}>{conn.ip_src}</td>
diff --git a/frontend/src/components/ConnectionContent.scss b/frontend/src/components/ConnectionContent.scss
index de4d699..f4edec9 100644
--- a/frontend/src/components/ConnectionContent.scss
+++ b/frontend/src/components/ConnectionContent.scss
@@ -2,7 +2,6 @@
.connection-content {
height: 100%;
- padding: 10px 10px 0;
background-color: $color-primary-0;
pre {
@@ -91,12 +90,12 @@
.connection-content-header {
height: 33px;
padding: 0;
- background-color: $color-primary-2;
+ background-color: $color-primary-3;
.header-info {
font-size: 12px;
padding-top: 7px;
- padding-left: 20px;
+ padding-left: 25px;
}
.header-actions {
@@ -104,6 +103,10 @@
.choice-field {
margin-top: -5px;
+
+ .field-value {
+ background-color: $color-primary-3;
+ }
}
}
}
diff --git a/frontend/src/components/Notifications.js b/frontend/src/components/Notifications.js
new file mode 100644
index 0000000..4d6dcd4
--- /dev/null
+++ b/frontend/src/components/Notifications.js
@@ -0,0 +1,60 @@
+import React, {Component} from 'react';
+import './Notifications.scss';
+import dispatcher from "../dispatcher";
+
+const _ = require('lodash');
+const classNames = require('classnames');
+
+class Notifications extends Component {
+
+ state = {
+ notifications: [],
+ closedNotifications: [],
+ };
+
+ componentDidMount() {
+ dispatcher.register("notifications", notification => {
+ const notifications = this.state.notifications;
+ notifications.push(notification);
+ this.setState({notifications});
+
+ setTimeout(() => {
+ const notifications = this.state.notifications;
+ notification.open = true;
+ this.setState({notifications});
+ }, 100);
+
+ setTimeout(() => {
+ const notifications = _.without(this.state.notifications, notification);
+ const closedNotifications = this.state.closedNotifications.concat([notification]);
+ notification.closed = true;
+ this.setState({notifications, closedNotifications});
+ }, 5000);
+
+ setTimeout(() => {
+ const closedNotifications = _.without(this.state.closedNotifications, notification);
+ this.setState({closedNotifications});
+ }, 6000);
+ });
+ }
+
+ render() {
+ return (
+ <div className="notifications">
+ <div className="notifications-list">
+ {
+ this.state.closedNotifications.concat(this.state.notifications).map(n =>
+ <div className={classNames("notification", {"notification-closed": n.closed},
+ {"notification-open": n.open})}>
+ <h3 className="notification-title">{n.event}</h3>
+ <span className="notification-description">{JSON.stringify(n.message)}</span>
+ </div>
+ )
+ }
+ </div>
+ </div>
+ );
+ }
+}
+
+export default Notifications;
diff --git a/frontend/src/components/Notifications.scss b/frontend/src/components/Notifications.scss
new file mode 100644
index 0000000..b0c334b
--- /dev/null
+++ b/frontend/src/components/Notifications.scss
@@ -0,0 +1,48 @@
+@import "../colors.scss";
+
+.notifications {
+ position: absolute;
+
+ left: 30px;
+ bottom: 50px;
+ z-index: 50;
+
+ .notifications-list {
+
+ }
+
+ .notification {
+ background-color: $color-green;
+ border-left: 5px solid $color-green-dark;
+ padding: 10px;
+ margin: 10px 0;
+ width: 250px;
+ color: $color-green-light;
+ transform: translateX(-300px);
+ transition: all 1s ease;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+
+ .notification-title {
+ font-size: 0.9em;
+ margin: 0;
+ }
+
+ .notification-description {
+ font-size: 0.8em;
+ }
+
+ &.notification-open {
+ transform: translateX(0px);
+ }
+
+ &.notification-closed {
+ transform: translateY(-50px);
+ opacity: 0;
+ }
+
+ }
+
+
+} \ No newline at end of file
diff --git a/frontend/src/components/panels/PcapPane.js b/frontend/src/components/panels/PcapPane.js
index 31d8815..13f7cb3 100644
--- a/frontend/src/components/panels/PcapPane.js
+++ b/frontend/src/components/panels/PcapPane.js
@@ -9,28 +9,31 @@ import CheckField from "../fields/CheckField";
import TextField from "../fields/TextField";
import ButtonField from "../fields/ButtonField";
import LinkPopover from "../objects/LinkPopover";
+import dispatcher from "../../dispatcher";
class PcapPane extends Component {
- constructor(props) {
- super(props);
-
- this.state = {
- sessions: [],
- isUploadFileValid: true,
- isUploadFileFocused: false,
- uploadFlushAll: false,
- isFileValid: true,
- isFileFocused: false,
- fileValue: "",
- processFlushAll: false,
- deleteOriginalFile: false
- };
- }
+ state = {
+ sessions: [],
+ isUploadFileValid: true,
+ isUploadFileFocused: false,
+ uploadFlushAll: false,
+ isFileValid: true,
+ isFileFocused: false,
+ fileValue: "",
+ processFlushAll: false,
+ deleteOriginalFile: false
+ };
componentDidMount() {
this.loadSessions();
+ dispatcher.register("notifications", payload => {
+ if (payload.event === "pcap.upload" || payload.event === "pcap.file") {
+ this.loadSessions();
+ }
+ });
+
document.title = "caronte:~/pcaps$";
}
diff --git a/frontend/src/components/panels/RulePane.js b/frontend/src/components/panels/RulePane.js
index 4641378..76f3ac0 100644
--- a/frontend/src/components/panels/RulePane.js
+++ b/frontend/src/components/panels/RulePane.js
@@ -14,35 +14,13 @@ import ButtonField from "../fields/ButtonField";
import validation from "../../validation";
import LinkPopover from "../objects/LinkPopover";
import {randomClassName} from "../../utils";
+import dispatcher from "../../dispatcher";
const classNames = require('classnames');
const _ = require('lodash');
class RulePane extends Component {
- constructor(props) {
- super(props);
-
- this.state = {
- rules: [],
- newRule: this.emptyRule,
- newPattern: this.emptyPattern
- };
-
- this.directions = {
- 0: "both",
- 1: "c->s",
- 2: "s->c"
- };
- }
-
- componentDidMount() {
- this.reset();
- this.loadRules();
-
- document.title = "caronte:~/rules$";
- }
-
emptyRule = {
"name": "",
"color": "",
@@ -60,7 +38,6 @@ class RulePane extends Component {
},
"version": 0
};
-
emptyPattern = {
"regex": "",
"flags": {
@@ -74,6 +51,34 @@ class RulePane extends Component {
"max_occurrences": 0,
"direction": 0
};
+ state = {
+ rules: [],
+ newRule: this.emptyRule,
+ newPattern: this.emptyPattern
+ };
+
+ constructor(props) {
+ super(props);
+
+ this.directions = {
+ 0: "both",
+ 1: "c->s",
+ 2: "s->c"
+ };
+ }
+
+ componentDidMount() {
+ this.reset();
+ this.loadRules();
+
+ dispatcher.register("notifications", payload => {
+ if (payload.event === "rules.new" || payload.event === "rules.edit") {
+ this.loadRules();
+ }
+ });
+
+ document.title = "caronte:~/rules$";
+ }
loadRules = () => {
backend.get("/api/rules").then(res => this.setState({rules: res.json, rulesStatusCode: res.status}))
@@ -226,17 +231,17 @@ class RulePane extends Component {
<tr key={r.id} onClick={() => {
this.reset();
this.setState({selectedRule: _.cloneDeep(r)});
- }} className={classNames("row-small", "row-clickable", {"row-selected": rule.id === r.id })}>
+ }} className={classNames("row-small", "row-clickable", {"row-selected": rule.id === r.id})}>
<td>{r["id"].substring(0, 8)}</td>
<td>{r["name"]}</td>
- <td><ButtonField name={r["color"]} color={r["color"]} small /></td>
+ <td><ButtonField name={r["color"]} color={r["color"]} small/></td>
<td>{r["notes"]}</td>
</tr>
);
let patterns = (this.state.selectedPattern == null && !isUpdate ?
- rule.patterns.concat(this.state.newPattern) :
- rule.patterns
+ rule.patterns.concat(this.state.newPattern) :
+ rule.patterns
).map(p => p === pattern ?
<tr key={randomClassName()}>
<td style={{"width": "500px"}}>
@@ -244,7 +249,7 @@ class RulePane extends Component {
onChange={(v) => {
this.updateParam(() => pattern.regex = v);
this.setState({patternRegexFocused: pattern.regex === ""});
- }} />
+ }}/>
</td>
<td><CheckField small checked={pattern.flags.caseless}
onChange={(v) => this.updateParam(() => pattern.flags.caseless = v)}/></td>
@@ -259,34 +264,35 @@ class RulePane extends Component {
<td style={{"width": "70px"}}>
<NumericField small value={pattern.min_occurrences}
active={this.state.patternOccurrencesFocused}
- onChange={(v) => this.updateParam(() => pattern.min_occurrences = v)} />
+ onChange={(v) => this.updateParam(() => pattern.min_occurrences = v)}/>
</td>
<td style={{"width": "70px"}}>
<NumericField small value={pattern.max_occurrences}
active={this.state.patternOccurrencesFocused}
- onChange={(v) => this.updateParam(() => pattern.max_occurrences = v)} />
+ onChange={(v) => this.updateParam(() => pattern.max_occurrences = v)}/>
</td>
<td><ChoiceField inline small keys={[0, 1, 2]} values={["both", "c->s", "s->c"]}
value={this.directions[pattern.direction]}
- onChange={(v) => this.updateParam(() => pattern.direction = v)} /></td>
+ onChange={(v) => this.updateParam(() => pattern.direction = v)}/></td>
<td>{this.state.selectedPattern == null ?
<ButtonField variant="green" small name="add" inline rounded onClick={() => this.addPattern(p)}/> :
- <ButtonField variant="green" small name="save" inline rounded onClick={() => this.updatePattern(p)}/>}
+ <ButtonField variant="green" small name="save" inline rounded
+ onClick={() => this.updatePattern(p)}/>}
</td>
</tr>
:
<tr key={"new_pattern"} className="row-small">
<td>{p.regex}</td>
- <td>{p.flags.caseless ? "yes": "no"}</td>
- <td>{p.flags.dot_all ? "yes": "no"}</td>
- <td>{p.flags.multi_line ? "yes": "no"}</td>
- <td>{p.flags.utf_8_mode ? "yes": "no"}</td>
- <td>{p.flags.unicode_property ? "yes": "no"}</td>
+ <td>{p.flags.caseless ? "yes" : "no"}</td>
+ <td>{p.flags.dot_all ? "yes" : "no"}</td>
+ <td>{p.flags.multi_line ? "yes" : "no"}</td>
+ <td>{p.flags.utf_8_mode ? "yes" : "no"}</td>
+ <td>{p.flags.unicode_property ? "yes" : "no"}</td>
<td>{p.min_occurrences}</td>
<td>{p.max_occurrences}</td>
<td>{this.directions[p.direction]}</td>
{!isUpdate && <td><ButtonField variant="blue" small rounded name="edit"
- onClick={() => this.editPattern(p) }/></td>}
+ onClick={() => this.editPattern(p)}/></td>}
</tr>
);
@@ -296,9 +302,9 @@ class RulePane extends Component {
<div className="section-header">
<span className="api-request">GET /api/rules</span>
{this.state.rulesStatusCode &&
- <span className="api-response"><LinkPopover text={this.state.rulesStatusCode}
- content={this.state.rulesResponse}
- placement="left" /></span>}
+ <span className="api-response"><LinkPopover text={this.state.rulesStatusCode}
+ content={this.state.rulesResponse}
+ placement="left"/></span>}
</div>
<div className="section-content">
@@ -327,7 +333,7 @@ class RulePane extends Component {
</span>
<span className="api-response"><LinkPopover text={this.state.ruleStatusCode}
content={this.state.ruleResponse}
- placement="left" /></span>
+ placement="left"/></span>
</div>
<div className="section-content">
@@ -336,11 +342,11 @@ class RulePane extends Component {
<Col>
<InputField name="name" inline value={rule.name}
onChange={(v) => this.updateParam((r) => r.name = v)}
- error={this.state.ruleNameError} />
+ error={this.state.ruleNameError}/>
<ColorField inline value={rule.color} error={this.state.ruleColorError}
- onChange={(v) => this.updateParam((r) => r.color = v)} />
+ onChange={(v) => this.updateParam((r) => r.color = v)}/>
<TextField name="notes" rows={2} value={rule.notes}
- onChange={(v) => this.updateParam((r) => r.notes = v)} />
+ onChange={(v) => this.updateParam((r) => r.notes = v)}/>
</Col>
<Col style={{"paddingTop": "6px"}}>
@@ -348,29 +354,29 @@ class RulePane extends Component {
<NumericField name="service_port" inline value={rule.filter.service_port}
onChange={(v) => this.updateParam((r) => r.filter.service_port = v)}
min={0} max={65565} error={this.state.ruleServicePortError}
- readonly={isUpdate} />
+ readonly={isUpdate}/>
<NumericField name="client_port" inline value={rule.filter.client_port}
onChange={(v) => this.updateParam((r) => r.filter.client_port = v)}
min={0} max={65565} error={this.state.ruleClientPortError}
- readonly={isUpdate} />
+ readonly={isUpdate}/>
<InputField name="client_address" value={rule.filter.client_address}
error={this.state.ruleClientAddressError} readonly={isUpdate}
- onChange={(v) => this.updateParam((r) => r.filter.client_address = v)} />
+ onChange={(v) => this.updateParam((r) => r.filter.client_address = v)}/>
</Col>
<Col style={{"paddingTop": "11px"}}>
<NumericField name="min_duration" inline value={rule.filter.min_duration}
error={this.state.ruleDurationError} readonly={isUpdate}
- onChange={(v) => this.updateParam((r) => r.filter.min_duration = v)} />
+ onChange={(v) => this.updateParam((r) => r.filter.min_duration = v)}/>
<NumericField name="max_duration" inline value={rule.filter.max_duration}
error={this.state.ruleDurationError} readonly={isUpdate}
- onChange={(v) => this.updateParam((r) => r.filter.max_duration = v)} />
+ onChange={(v) => this.updateParam((r) => r.filter.max_duration = v)}/>
<NumericField name="min_bytes" inline value={rule.filter.min_bytes}
error={this.state.ruleBytesError} readonly={isUpdate}
- onChange={(v) => this.updateParam((r) => r.filter.min_bytes = v)} />
+ onChange={(v) => this.updateParam((r) => r.filter.min_bytes = v)}/>
<NumericField name="max_bytes" inline value={rule.filter.max_bytes}
error={this.state.ruleBytesError} readonly={isUpdate}
- onChange={(v) => this.updateParam((r) => r.filter.max_bytes = v)} />
+ onChange={(v) => this.updateParam((r) => r.filter.max_bytes = v)}/>
</Col>
</Row>
</Container>
@@ -388,7 +394,7 @@ class RulePane extends Component {
<th>min</th>
<th>max</th>
<th>direction</th>
- {!isUpdate && <th>actions</th> }
+ {!isUpdate && <th>actions</th>}
</tr>
</thead>
<tbody>
@@ -403,7 +409,7 @@ class RulePane extends Component {
<div className="section-footer">
{<ButtonField variant="red" name="cancel" bordered onClick={this.reset}/>}
<ButtonField variant={isUpdate ? "blue" : "green"} name={isUpdate ? "update_rule" : "add_rule"}
- bordered onClick={isUpdate ? this.updateRule : this.addRule} />
+ bordered onClick={isUpdate ? this.updateRule : this.addRule}/>
</div>
</div>
</div>
diff --git a/frontend/src/components/panels/ServicePane.js b/frontend/src/components/panels/ServicePane.js
index 0e99652..22c6655 100644
--- a/frontend/src/components/panels/ServicePane.js
+++ b/frontend/src/components/panels/ServicePane.js
@@ -12,28 +12,13 @@ import ButtonField from "../fields/ButtonField";
import validation from "../../validation";
import LinkPopover from "../objects/LinkPopover";
import {createCurlCommand} from "../../utils";
+import dispatcher from "../../dispatcher";
const classNames = require('classnames');
const _ = require('lodash');
class ServicePane extends Component {
- constructor(props) {
- super(props);
-
- this.state = {
- services: [],
- currentService: this.emptyService,
- };
-
- document.title = "caronte:~/services$";
- }
-
- componentDidMount() {
- this.reset();
- this.loadServices();
- }
-
emptyService = {
"port": 0,
"name": "",
@@ -41,6 +26,24 @@ class ServicePane extends Component {
"notes": ""
};
+ state = {
+ services: [],
+ currentService: this.emptyService,
+ };
+
+ componentDidMount() {
+ this.reset();
+ this.loadServices();
+
+ dispatcher.register("notifications", payload => {
+ if (payload.event === "services.edit") {
+ this.loadServices();
+ }
+ });
+
+ document.title = "caronte:~/services$";
+ }
+
loadServices = () => {
backend.get("/api/services")
.then(res => this.setState({services: Object.values(res.json), servicesStatusCode: res.status}))
diff --git a/frontend/src/components/panels/common.scss b/frontend/src/components/panels/common.scss
index 121a917..1468f35 100644
--- a/frontend/src/components/panels/common.scss
+++ b/frontend/src/components/panels/common.scss
@@ -2,11 +2,9 @@
.pane-container {
height: 100%;
- padding: 10px 10px 0;
background-color: $color-primary-3;
.pane-section {
- margin-bottom: 10px;
background-color: $color-primary-0;
.section-header {
@@ -14,7 +12,7 @@
font-weight: 500;
display: flex;
padding: 5px 10px;
- background-color: $color-primary-2;
+ background-color: $color-primary-3;
.api-request {
flex: 1;