diff options
author | Emiliano Ciavatta | 2020-10-09 15:07:24 +0000 |
---|---|---|
committer | Emiliano Ciavatta | 2020-10-09 15:07:24 +0000 |
commit | c21541a31fe45ba3a0bafca46415247f3837713e (patch) | |
tree | 9581c6a2801556d602099a64840909a451e61ffd /frontend/src | |
parent | d203f3c7e3bcaa20895c0f32f348cd1513ae9876 (diff) |
Add MainPane
Diffstat (limited to 'frontend/src')
23 files changed, 278 insertions, 126 deletions
diff --git a/frontend/src/components/App.js b/frontend/src/components/App.js index bf959c5..0f700db 100644 --- a/frontend/src/components/App.js +++ b/frontend/src/components/App.js @@ -31,7 +31,8 @@ class App extends Component { if (payload.event === "connected") { this.setState({ connected: true, - configured: payload.message["is_configured"] + configured: payload.message["is_configured"], + version: payload.message["version"] }); } }); @@ -50,7 +51,7 @@ class App extends Component { <> <Notifications/> {this.state.connected ? - (this.state.configured ? <MainPage/> : + (this.state.configured ? <MainPage version={this.state.version}/> : <ConfigurationPage onConfigured={() => this.setState({configured: true})}/>) : <ServiceUnavailablePage/> } diff --git a/frontend/src/components/Notifications.js b/frontend/src/components/Notifications.js index 1017a42..ad681a2 100644 --- a/frontend/src/components/Notifications.js +++ b/frontend/src/components/Notifications.js @@ -30,49 +30,84 @@ class Notifications extends Component { }; componentDidMount() { - dispatcher.register("notifications", notification => { + dispatcher.register("notifications", n => this.notificationHandler(n)); + } + + notificationHandler = (n) => { + switch (n.event) { + case "connected": + n.title = "connected"; + n.description = `number of active clients: ${n.message["connected_clients"]}`; + return this.pushNotification(n); + case "services.edit": + n.title = "services updated"; + n.description = `updated "${n.message["name"]}" on port ${n.message["port"]}`; + n.variant = "blue"; + return this.pushNotification(n); + case "rules.new": + n.title = "rules updated"; + n.description = `new rule added: ${n.message["name"]}`; + n.variant = "green"; + return this.pushNotification(n); + case "rules.edit": + n.title = "rules updated"; + n.description = `existing rule updated: ${n.message["name"]}`; + n.variant = "blue"; + return this.pushNotification(n); + default: + return; + } + }; + + pushNotification = (notification) => { + const notifications = this.state.notifications; + notifications.push(notification); + this.setState({notifications}); + setTimeout(() => { const notifications = this.state.notifications; - notifications.push(notification); + notification.open = true; this.setState({notifications}); - setTimeout(() => { - const notifications = this.state.notifications; - notification.open = true; - this.setState({notifications}); - }, 100); + }, 100); - const hideHandle = setTimeout(() => { - const notifications = _.without(this.state.notifications, notification); - const closedNotifications = this.state.closedNotifications.concat([notification]); - notification.closed = true; - this.setState({notifications, closedNotifications}); - }, 5000); + const hideHandle = setTimeout(() => { + const notifications = _.without(this.state.notifications, notification); + const closedNotifications = this.state.closedNotifications.concat([notification]); + notification.closed = true; + this.setState({notifications, closedNotifications}); + }, 5000); - const removeHandle = setTimeout(() => { - const closedNotifications = _.without(this.state.closedNotifications, notification); - this.setState({closedNotifications}); - }, 6000); + const removeHandle = setTimeout(() => { + const closedNotifications = _.without(this.state.closedNotifications, notification); + this.setState({closedNotifications}); + }, 6000); - notification.onClick = () => { - clearTimeout(hideHandle); - clearTimeout(removeHandle); - const notifications = _.without(this.state.notifications, notification); - this.setState({notifications}); - }; - }); - } + notification.onClick = () => { + clearTimeout(hideHandle); + clearTimeout(removeHandle); + const notifications = _.without(this.state.notifications, notification); + this.setState({notifications}); + }; + }; 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})} onClick={n.onClick}> - <h3 className="notification-title">{n.event}</h3> - <span className="notification-description">{JSON.stringify(n.message)}</span> - </div> - ) + this.state.closedNotifications.concat(this.state.notifications).map(n => { + const notificationClassnames = { + "notification": true, + "notification-closed": n.closed, + "notification-open": n.open + }; + if (n.variant) { + notificationClassnames[`notification-${n.variant}`] = true; + } + return <div className={classNames(notificationClassnames)} onClick={n.onClick}> + <h3 className="notification-title">{n.title}</h3> + <pre className="notification-description">{n.description}</pre> + </div>; + }) } </div> </div> diff --git a/frontend/src/components/Notifications.scss b/frontend/src/components/Notifications.scss index 324d0bb..98d228e 100644 --- a/frontend/src/components/Notifications.scss +++ b/frontend/src/components/Notifications.scss @@ -7,18 +7,15 @@ left: 30px; .notification { - overflow: hidden; width: 250px; margin: 10px 0; padding: 10px; + cursor: pointer; transition: all 1s ease; transform: translateX(-300px); - white-space: nowrap; - text-overflow: ellipsis; color: $color-green-light; border-left: 5px solid $color-green-dark; background-color: $color-green; - cursor: pointer; .notification-title { font-size: 0.9em; @@ -27,6 +24,11 @@ .notification-description { font-size: 0.8em; + overflow: hidden; + margin: 10px 0; + white-space: nowrap; + text-overflow: ellipsis; + color: $color-primary-4; } &.notification-open { @@ -37,5 +39,11 @@ transform: translateY(-50px); opacity: 0; } + + &.notification-blue { + color: $color-blue-light; + border-left: 5px solid $color-blue-dark; + background-color: $color-blue; + } } } diff --git a/frontend/src/components/Timeline.js b/frontend/src/components/Timeline.js index 7be42e0..615203f 100644 --- a/frontend/src/components/Timeline.js +++ b/frontend/src/components/Timeline.js @@ -35,6 +35,7 @@ import log from "../log"; import dispatcher from "../dispatcher"; const minutes = 60 * 1000; +const classNames = require('classnames'); class Timeline extends Component { @@ -70,6 +71,11 @@ class Timeline extends Component { this.loadServices().then(() => log.debug("Services reloaded after notification update")); } }); + + dispatcher.register("pulse_timeline", payload => { + this.setState({pulseTimeline: true}); + setTimeout(() => this.setState({pulseTimeline: false}), payload.duration); + }); } componentDidUpdate(prevProps, prevState, snapshot) { @@ -183,7 +189,7 @@ class Timeline extends Component { return ( <footer className="footer"> - <div className="time-line"> + <div className={classNames("time-line", {"pulse-timeline": this.state.pulseTimeline})}> <Resizable> <ChartContainer timeRange={this.state.timeRange} enableDragZoom={false} paddingTop={5} minDuration={60000} diff --git a/frontend/src/components/Timeline.scss b/frontend/src/components/Timeline.scss index eeb9d50..db8d9c8 100644 --- a/frontend/src/components/Timeline.scss +++ b/frontend/src/components/Timeline.scss @@ -13,6 +13,10 @@ top: 5px; right: 10px; } + + &.pulse-timeline { + animation: pulse 2s infinite; + } } svg text { diff --git a/frontend/src/components/dialogs/Filters.js b/frontend/src/components/dialogs/Filters.js index 35c11df..dfd554b 100644 --- a/frontend/src/components/dialogs/Filters.js +++ b/frontend/src/components/dialogs/Filters.js @@ -81,7 +81,7 @@ class Filters extends Component { </thead> <tbody> {this.generateRows(["service_port", "client_address", "min_duration", - "min_bytes", "started_after", "closed_after", "marked"])} + "min_bytes"])} </tbody> </Table> </Col> @@ -95,14 +95,11 @@ class Filters extends Component { </thead> <tbody> {this.generateRows(["matched_rules", "client_port", "max_duration", - "max_bytes", "started_before", "closed_before", "hidden"])} + "max_bytes", "marked"])} </tbody> </Table> </Col> - </Row> - - </Container> </Modal.Body> <Modal.Footer className="dialog-footer"> diff --git a/frontend/src/components/fields/ButtonField.js b/frontend/src/components/fields/ButtonField.js index ffcceae..193339c 100644 --- a/frontend/src/components/fields/ButtonField.js +++ b/frontend/src/components/fields/ButtonField.js @@ -55,8 +55,8 @@ class ButtonField extends Component { } return ( - <div className={classNames( "field", "button-field", {"field-small": this.props.small})}> - <button type="button" className={classNames(classNames(buttonClassnames))} + <div className={classNames("field", "button-field", {"field-small": this.props.small})}> + <button type="button" className={classNames(buttonClassnames)} onClick={handler} style={buttonStyle}>{this.props.name}</button> </div> ); diff --git a/frontend/src/components/fields/TextField.scss b/frontend/src/components/fields/TextField.scss index c2d6ef5..5fde9e6 100644 --- a/frontend/src/components/fields/TextField.scss +++ b/frontend/src/components/fields/TextField.scss @@ -51,4 +51,8 @@ padding: 5px 10px; color: $color-secondary-0; } + + &:hover::-webkit-scrollbar-thumb { + background: $color-secondary-2 !important; + } } diff --git a/frontend/src/components/filters/FiltersDefinitions.js b/frontend/src/components/filters/FiltersDefinitions.js index cde3cfb..9fb3b18 100644 --- a/frontend/src/components/filters/FiltersDefinitions.js +++ b/frontend/src/components/filters/FiltersDefinitions.js @@ -22,8 +22,7 @@ import RulesConnectionsFilter from "./RulesConnectionsFilter"; import BooleanConnectionsFilter from "./BooleanConnectionsFilter"; export const filtersNames = ["service_port", "matched_rules", "client_address", "client_port", - "min_duration", "max_duration", "min_bytes", "max_bytes", "started_after", - "started_before", "closed_after", "closed_before", "marked", "hidden"]; + "min_duration", "max_duration", "min_bytes", "max_bytes", "marked"]; export const filtersDefinitions = { service_port: <StringConnectionsFilter filterName="service_port" @@ -66,34 +65,9 @@ export const filtersDefinitions = { replaceFunc={cleanNumber} key="max_bytes_filter" width={200}/>, - // started_after: <StringConnectionsFilter filterName="started_after" - // defaultFilterValue="00:00:00" - // validateFunc={validate24HourTime} - // encodeFunc={timeToTimestamp} - // decodeFunc={timestampToTime} - // key="started_after_filter" - // width={230} />, - // started_before: <StringConnectionsFilter filterName="started_before" - // defaultFilterValue="00:00:00" - // validateFunc={validate24HourTime} - // encodeFunc={timeToTimestamp} - // decodeFunc={timestampToTime} - // key="started_before_filter" - // width={230} />, - // closed_after: <StringConnectionsFilter filterName="closed_after" - // defaultFilterValue="00:00:00" - // validateFunc={validate24HourTime} - // encodeFunc={timeToTimestamp} - // decodeFunc={timestampToTime} - // key="closed_after_filter" - // width={230} />, - // closed_before: <StringConnectionsFilter filterName="closed_before" - // defaultFilterValue="00:00:00" - // validateFunc={validate24HourTime} - // encodeFunc={timeToTimestamp} - // decodeFunc={timestampToTime} - // key="closed_before_filter" - // width={230} />, - marked: <BooleanConnectionsFilter filterName={"marked"}/>, - // hidden: <BooleanConnectionsFilter filterName={"hidden"} /> + contains_string: <StringConnectionsFilter filterName="contains_string" + defaultFilterValue="" + key="contains_string_filter" + width={320}/>, + marked: <BooleanConnectionsFilter filterName={"marked"}/> }; diff --git a/frontend/src/components/objects/Connection.js b/frontend/src/components/objects/Connection.js index 5e2beba..e0e942a 100644 --- a/frontend/src/components/objects/Connection.js +++ b/frontend/src/components/objects/Connection.js @@ -17,11 +17,12 @@ import React, {Component} from 'react'; import './Connection.scss'; -import {Form, OverlayTrigger, Popover} from "react-bootstrap"; +import {Form} from "react-bootstrap"; import backend from "../../backend"; import {dateTimeToTime, durationBetween, formatSize} from "../../utils"; import ButtonField from "../fields/ButtonField"; import LinkPopover from "./LinkPopover"; +import TextField from "../fields/TextField"; const classNames = require('classnames'); @@ -81,14 +82,6 @@ class Connection extends Component { <span>Closed at {closedAt.toLocaleDateString() + " " + closedAt.toLocaleTimeString()}</span> </div>; - const popoverFor = function (name, content) { - return <Popover id={`popover-${name}-${conn.id}`} className="connection-popover"> - <Popover.Content> - {content} - </Popover.Content> - </Popover>; - }; - const commentPopoverContent = <div> <span>Click to <strong>{conn.comment.length > 0 ? "edit" : "add"}</strong> comment</span> {conn.comment && <Form.Control as="textarea" readOnly={true} rows={2} defaultValue={conn.comment}/>} @@ -97,7 +90,7 @@ class Connection extends Component { const copyPopoverContent = <div> {this.state.copiedMessage ? <span><strong>Copied!</strong></span> : <span>Click to <strong>copy</strong> the connection id</span>} - <Form.Control as="textarea" readOnly={true} rows={1} defaultValue={conn.id} ref={this.copyTextarea}/> + <TextField readonly rows={1} value={conn.id} textRef={this.copyTextarea}/> </div>; return ( @@ -119,22 +112,16 @@ class Connection extends Component { <td className="clickable" onClick={this.props.onSelected}>{durationBetween(startedAt, closedAt)}</td> <td className="clickable" onClick={this.props.onSelected}>{formatSize(conn["client_bytes"])}</td> <td className="clickable" onClick={this.props.onSelected}>{formatSize(conn["server_bytes"])}</td> - <td> - <OverlayTrigger trigger={["focus", "hover"]} placement="right" - overlay={popoverFor("hide", <span>Mark this connection</span>)}> - <span className={"connection-icon" + (conn.marked ? " icon-enabled" : "")} - onClick={() => this.handleAction("mark")}>!!</span> - </OverlayTrigger> - <OverlayTrigger trigger={["focus", "hover"]} placement="right" - overlay={popoverFor("comment", commentPopoverContent)}> - <span className={"connection-icon" + (conn.comment ? " icon-enabled" : "")} - onClick={() => this.handleAction("comment")}>@</span> - </OverlayTrigger> - <OverlayTrigger trigger={["focus", "hover"]} placement="right" - overlay={popoverFor("copy", copyPopoverContent)}> - <span className="connection-icon" - onClick={() => this.handleAction("copy")}>#</span> - </OverlayTrigger> + <td className="connection-actions"> + <LinkPopover text={<span className={classNames("connection-icon", {"icon-enabled": conn.marked})} + onClick={() => this.handleAction("mark")}>!!</span>} + content={<span>Mark this connection</span>} placement="right"/> + <LinkPopover text={<span className={classNames("connection-icon", {"icon-enabled": conn.comment})} + onClick={() => this.handleAction("comment")}>@</span>} + content={commentPopoverContent} placement="right"/> + <LinkPopover text={<span className={classNames("connection-icon", {"icon-enabled": conn.hidden})} + onClick={() => this.handleAction("copy")}>#</span>} + content={copyPopoverContent} placement="right"/> </td> </tr> ); diff --git a/frontend/src/components/objects/Connection.scss b/frontend/src/components/objects/Connection.scss index 3b9f479..bf66272 100644 --- a/frontend/src/components/objects/Connection.scss +++ b/frontend/src/components/objects/Connection.scss @@ -46,6 +46,10 @@ .link-popover { font-weight: 400; } + + .connection-actions .link-popover { + text-decoration: none; + } } .connection-popover { diff --git a/frontend/src/components/objects/LinkPopover.scss b/frontend/src/components/objects/LinkPopover.scss index 725224c..c81f8bb 100644 --- a/frontend/src/components/objects/LinkPopover.scss +++ b/frontend/src/components/objects/LinkPopover.scss @@ -5,3 +5,8 @@ cursor: pointer; text-decoration: underline; } + +.popover { + font-family: "Fira Code", monospace; + font-size: 0.75em; +} diff --git a/frontend/src/components/pages/MainPage.js b/frontend/src/components/pages/MainPage.js index 7376091..4632bbd 100644 --- a/frontend/src/components/pages/MainPage.js +++ b/frontend/src/components/pages/MainPage.js @@ -57,7 +57,7 @@ class MainPage extends Component { <Route path="/services" children={<ServicesPane/>}/> <Route exact path="/connections/:id" children={<StreamsPane connection={this.state.selectedConnection}/>}/> - <Route children={<MainPane/>}/> + <Route children={<MainPane version={this.props.version}/>}/> </Switch> </div> diff --git a/frontend/src/components/pages/MainPage.scss b/frontend/src/components/pages/MainPage.scss index 3b1a689..4ca54c0 100644 --- a/frontend/src/components/pages/MainPage.scss +++ b/frontend/src/components/pages/MainPage.scss @@ -13,6 +13,7 @@ } .details-pane { + position: relative; flex: 1 1; margin-left: 7.5px; } diff --git a/frontend/src/components/panels/ConnectionsPane.js b/frontend/src/components/panels/ConnectionsPane.js index 038ef8f..1f79ab8 100644 --- a/frontend/src/components/panels/ConnectionsPane.js +++ b/frontend/src/components/panels/ConnectionsPane.js @@ -27,6 +27,8 @@ import log from "../../log"; import ButtonField from "../fields/ButtonField"; import dispatcher from "../../dispatcher"; +const classNames = require('classnames'); + class ConnectionsPane extends Component { state = { @@ -81,6 +83,11 @@ class ConnectionsPane extends Component { this.loadServices().then(() => log.debug("Services reloaded after notification update")); } }); + + dispatcher.register("pulse_connections_view", payload => { + this.setState({pulseConnectionsView: true}); + setTimeout(() => this.setState({pulseConnectionsView: false}), payload.duration); + }); } connectionSelected = (c, doRedirect = true) => { @@ -246,7 +253,7 @@ class ConnectionsPane extends Component { return ( <div className="connections-container"> {this.state.showMoreRecentButton && <div className="most-recent-button"> - <ButtonField name="most_recent" variant="green" onClick={() => { + <ButtonField name="most_recent" variant="green" bordered onClick={() => { this.disableScrollHandler = true; this.connectionsListRef.current.scrollTop = 0; this.loadConnections({limit: this.queryLimit}) @@ -257,7 +264,8 @@ class ConnectionsPane extends Component { }}/> </div>} - <div className="connections" onScroll={this.handleScroll} ref={this.connectionsListRef}> + <div className={classNames("connections", {"connections-pulse": this.state.pulseConnectionsView})} + onScroll={this.handleScroll} ref={this.connectionsListRef}> <Table borderless size="sm"> <thead> <tr> diff --git a/frontend/src/components/panels/ConnectionsPane.scss b/frontend/src/components/panels/ConnectionsPane.scss index 06f5827..59fe372 100644 --- a/frontend/src/components/panels/ConnectionsPane.scss +++ b/frontend/src/components/panels/ConnectionsPane.scss @@ -33,6 +33,9 @@ z-index: 20; top: 45px; left: calc(50% - 50px); - background-color: red; + } + + .connections-pulse { + animation: pulse 2s infinite; } } diff --git a/frontend/src/components/panels/MainPane.js b/frontend/src/components/panels/MainPane.js index 74c859c..8aa8ad8 100644 --- a/frontend/src/components/panels/MainPane.js +++ b/frontend/src/components/panels/MainPane.js @@ -17,17 +17,91 @@ import React, {Component} from 'react'; import './common.scss'; -import './ServicesPane.scss'; +import './MainPane.scss'; +import Typed from "typed.js"; +import dispatcher from "../../dispatcher"; +import RulesPane from "./RulesPane"; +import StreamsPane from "./StreamsPane"; +import PcapsPane from "./PcapsPane"; +import ServicesPane from "./ServicesPane"; class MainPane extends Component { state = {}; + componentDidMount() { + const nl = "^600\n^400"; + const options = { + strings: [ + `welcome to caronte!^1000 the current version is ${this.props.version}` + nl + + "caronte is a network analyzer,^300 it is able to read pcaps and extract connections", // 0 + "the left panel lists all connections that have already been closed" + nl + + "scrolling up the list will load the most recent connections,^300 downward the oldest ones", // 1 + "by selecting a connection you can view its content,^300 which will be shown in the right panel" + nl + + "you can choose the display format,^300 or decide to download the connection content", // 2 + "below there is the timeline,^300 which shows the number of connections per minute per service" + nl + + "you can use the sliding window to move the time range of the connections to be displayed", // 3 + "there are also additional metrics,^300 selectable from the drop-down menu", // 4 + "at the top are the filters,^300 which can be used to select only certain types of connections" + nl + + "you can choose which filters to display in the top bar from the filters window", // 5 + "in the pcaps panel it is possible to analyze new pcaps,^300 or to see the pcaps already analyzed" + nl + + "you can load pcaps from your browser,^300 or process pcaps already present on the filesystem", // 6 + "in the rules panel you can see the rules already created,^300 or create new ones" + nl + + "the rules inserted will be used only to label new connections, not those already analyzed" + nl + + "a connection is tagged if it meets all the requirements specified by the rule", // 7 + "in the services panel you can assign new services or edit existing ones" + nl + + "each service is associated with a port number,^300 and will be shown in the connection list", // 8 + "from the configuration panel you can change the settings of the frontend application", // 9 + "that's all! and have fun!" + nl + "created by @eciavatta" // 10 + ], + typeSpeed: 40, + cursorChar: "_", + backSpeed: 5, + smartBackspace: false, + backDelay: 1500, + preStringTyped: (arrayPos) => { + switch (arrayPos) { + case 1: + return dispatcher.dispatch("pulse_connections_view", {duration: 12000}); + case 2: + return this.setState({backgroundPane: <StreamsPane/>}); + case 3: + this.setState({backgroundPane: null}); + return dispatcher.dispatch("pulse_timeline", {duration: 12000}); + case 6: + return this.setState({backgroundPane: <PcapsPane/>}); + case 7: + return this.setState({backgroundPane: <RulesPane/>}); + case 8: + return this.setState({backgroundPane: <ServicesPane/>}); + case 10: + return this.setState({backgroundPane: null}); + default: + return; + } + }, + }; + this.typed = new Typed(this.el, options); + } + + componentWillUnmount() { + this.typed.destroy(); + } + render() { return ( - <div className="pane-container main-pane"> - <div className="pane-section"> - MainPane + <div className="pane-container"> + <div className="main-pane"> + <div className="pane-section"> + <div className="tutorial"> + <span style={{whiteSpace: 'pre'}} ref={(el) => { + this.el = el; + }}/> + </div> + </div> + </div> + <div className="background-pane"> + {this.state.backgroundPane} </div> </div> ); diff --git a/frontend/src/components/panels/MainPane.scss b/frontend/src/components/panels/MainPane.scss index c8460f2..8f99b3c 100644 --- a/frontend/src/components/panels/MainPane.scss +++ b/frontend/src/components/panels/MainPane.scss @@ -1,5 +1,30 @@ @import "../../colors"; -.main-pane { +.pane-container { + background-color: $color-primary-0; + .main-pane { + position: absolute; + z-index: 50; + top: 0; + left: 0; + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + background-color: transparent; + + .tutorial { + flex-basis: 100%; + padding: 5px 10px; + text-align: center; + background-color: $color-primary-2; + } + } + + .background-pane { + height: 100%; + opacity: 0.4; + } } diff --git a/frontend/src/components/panels/StreamsPane.js b/frontend/src/components/panels/StreamsPane.js index c8bd121..bd1964e 100644 --- a/frontend/src/components/panels/StreamsPane.js +++ b/frontend/src/components/panels/StreamsPane.js @@ -208,8 +208,8 @@ class StreamsPane extends Component { ); return ( - <div className="connection-content"> - <div className="connection-content-header container-fluid"> + <div className="pane-container stream-pane"> + <div className="stream-pane-header container-fluid"> <Row> <div className="header-info col"> <span><strong>flow</strong>: {conn["ip_src"]}:{conn["port_src"]} -> {conn["ip_dst"]}:{conn["port_dst"]}</span> @@ -235,7 +235,6 @@ class StreamsPane extends Component { </div> ); } - } diff --git a/frontend/src/components/panels/StreamsPane.scss b/frontend/src/components/panels/StreamsPane.scss index d5510cf..1f641f3 100644 --- a/frontend/src/components/panels/StreamsPane.scss +++ b/frontend/src/components/panels/StreamsPane.scss @@ -1,7 +1,6 @@ @import "../../colors"; -.connection-content { - height: 100%; +.stream-pane { background-color: $color-primary-0; pre { @@ -15,6 +14,10 @@ margin: 0; padding: 0; } + + &:hover::-webkit-scrollbar-thumb { + background: $color-secondary-2; + } } .connection-message { @@ -87,7 +90,7 @@ } } - .connection-content-header { + .stream-pane-header { height: 33px; padding: 0; background-color: $color-primary-3; diff --git a/frontend/src/components/panels/common.scss b/frontend/src/components/panels/common.scss index 1468f35..335e65b 100644 --- a/frontend/src/components/panels/common.scss +++ b/frontend/src/components/panels/common.scss @@ -32,6 +32,10 @@ margin-left: 10px; color: $color-secondary-0; } + + &:hover::-webkit-scrollbar-thumb { + background: $color-secondary-2; + } } table { @@ -96,4 +100,8 @@ margin-left: 5px; } } + + &:hover::-webkit-scrollbar-thumb { + background: $color-secondary-2; + } } diff --git a/frontend/src/index.scss b/frontend/src/index.scss index 9d6afc4..ea360be 100644 --- a/frontend/src/index.scss +++ b/frontend/src/index.scss @@ -77,3 +77,15 @@ a { .popover-header { color: $color-primary-1; } + +@keyframes pulse { + 0% { + opacity: 1; + } + 50% { + opacity: 0.3; + } + 100% { + opacity: 1; + } +} diff --git a/frontend/src/logo.svg b/frontend/src/logo.svg index 6b60c10..cb825f3 100644 --- a/frontend/src/logo.svg +++ b/frontend/src/logo.svg @@ -1,7 +1 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"> - <g fill="#61DAFB"> - <path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/> - <circle cx="420.9" cy="296.5" r="45.7"/> - <path d="M520.5 78.1z"/> - </g> -</svg> +<svg height="512pt" viewBox="0 0 512.0001 512" width="512pt" xmlns="http://www.w3.org/2000/svg"><path d="m99 163h140v209h-140zm0 0" fill="#5aa096"/><path d="m10 372v-40h90l40 40h292l70-30-46.113281 75.878906c-14.972657 21.382813-39.433594 34.121094-65.539063 34.121094h-300.347656c-44.183594 0-80-35.816406-80-80zm0 0" fill="#96786e"/><path d="m320.246094 466.644531-35.355469 35.355469-45.960937-45.960938c-9.765626-9.765624-9.765626-25.59375 0-35.355468 9.761718-9.761719 25.589843-9.761719 35.355468 0zm0 0" fill="#5aa096"/><path d="m232.734375 132.625c11.335937-21.507812 10.265625-41.625 10.265625-41.625 0-46.945312-28.339844-81-67-81s-70 38.054688-70 85v37.144531c-14.839844 1.582031-41.0625 10.261719-41.0625 51.855469v107s35-49.5 53.5-49.5 30.5 21.5 30.5 21.5-6-50.5 23.5-50.5c42.5625 0 14.5625 103.5 50.5 103.5 31.0625 0 25-73 25-73l23 13v-75s4.351562-42.707031-38.203125-48.375zm0 0" fill="#96786e"/><path d="m201 85c0-19.328125-11.191406-35-25-35s-25 15.671875-25 35 11.191406 35 25 35 25-15.671875 25-35zm0 0" fill="#f0e6d2"/><path d="m509.554688 335.445312c-2.851563-3.285156-7.496094-4.347656-11.492188-2.636718l-68.117188 29.191406h-180.945312v-53.214844c7.375-13.921875 9.21875-33.839844 9.4375-48.363281l7.578125 4.28125c3.097656 1.75 6.890625 1.726563 9.960937-.066406 3.070313-1.792969 4.960938-5.082031 4.960938-8.636719v-74.570312c.21875-2.851563.703125-13.9375-3.773438-25.976563-3.972656-10.691406-12.425781-23.59375-30.378906-29.789063 6.328125-17.105468 6.316406-31.453124 6.222656-34.597656-.003906-.101562-.003906-.191406-.007812-.269531-.042969-24.925781-7.605469-47.75-21.304688-64.28125-14.167968-17.097656-33.949218-26.515625-55.695312-26.515625-44.113281 0-80 42.617188-80 95v28.789062c-7.277344 1.835938-15.53125 5.320313-22.785156 11.847657-12.128906 10.910156-18.277344 27.183593-18.277344 48.363281v15.785156l-37.480469-41.941406c-3.679687-4.117188-10.003906-4.476562-14.117187-.792969-4.121094 3.679688-4.476563 10.003907-.792969 14.121094l52.390625 58.628906v61.199219c0 4.355469 2.820312 8.210938 6.972656 9.53125s8.679688-.203125 11.195313-3.757812c.074219-.105469 6.878906-9.703126 15.894531-20.199219v45.425781h-79c-5.523438 0-10 4.476562-10 10v40c0 49.625 40.375 90 90 90h140.804688c.34375.375 47.015624 47.070312 47.015624 47.070312 1.953126 1.953126 4.511719 2.929688 7.070313 2.929688s5.117187-.976562 7.070313-2.929688l35.355468-35.355468c3.171875-3.167969 3.765625-7.941406 1.789063-11.714844h61.242187c29.339844 0 56.90625-14.351562 73.730469-38.390625.121094-.175781.238281-.355469.351563-.539063l46.117187-75.878906c2.257813-3.714844 1.855469-8.460937-.992187-11.746094zm-365.410157 26.554688-35.144531-35.140625v-36.5625l64.074219 71.703125zm-35.144531-101.71875v-3.59375c6.601562-5.085938 9.394531-5.1875 9.4375-5.1875 10.222656 0 19.191406 11.84375 21.769531 16.375 2.324219 4.164062 7.265625 6.097656 11.804688 4.636719 4.535156-1.464844 7.410156-5.941407 6.859375-10.675781-1.167969-10.050782-.550782-28.765626 6.070312-36.195313 1.933594-2.171875 4.246094-3.140625 7.496094-3.140625 12.25 0 15.25 19.105469 18.148438 46.097656 1.410156 13.171875 2.746093 25.613282 6.238281 35.710938 6.195312 17.929687 17.621093 21.691406 26.113281 21.691406 2.082031 0 4.109375-.226562 6.0625-.660156v36.660156h-29.101562zm-31.703125-.695312c-.800781.894531-1.585937 1.785156-2.359375 2.671874v-78.257812c0-20.65625 7.078125-33.828125 21.0625-39.3125v18.3125c0 5.511719 4.492188 10 10 10 5.511719 0 10-4.488281 10-10v-68c0-41.355469 26.917969-75 60-75 33.027344 0 57 29.859375 57 71 0 .148438.007812.339844.011719.492188.003906.015624.007812.207031.011719.546874 0 .097657.003906.203126 0 .328126v.121093c0 .15625 0 .328125-.003907.523438v.0625c-.121093 6.808593-2.050781 34.238281-24.488281 54.847656-4.066406 3.734375-4.335938 10.0625-.597656 14.128906 1.96875 2.144531 4.664062 3.234375 7.367187 3.234375 2.414063 0 4.839844-.871094 6.761719-2.636718 6.515625-5.988282 11.652344-12.378907 15.722656-18.792969 25.035156 7.121093 23.488282 33.03125 23.203125 36.128906-.03125.335937-.050781 58.875-.050781 58.875l-8.078125-4.566406c-3.226563-1.820313-7.1875-1.710938-10.308594.285156-3.121093 1.996094-4.882812 5.554687-4.578125 9.242187 1.4375 17.550782.191406 49.21875-9.273437 59.492188-1.734375 1.882812-3.457031 2.6875-5.761719 2.6875-1.632812 0-4.367188 0-7.210938-8.226562-2.75-7.953126-3.96875-19.296876-5.257812-31.3125-1.503906-13.996094-3.058594-28.472657-7.386719-40.046876-4.296875-11.488281-10.609375-17.691406-17.082031-20.882812v-45.53125c0-5.523438-4.476562-10-10-10s-10 4.476562-10 10v43.125c-6.253906 1.257812-11.714844 4.390625-16.023438 9.242188-6.230468 7.015624-9.226562 16.765624-10.574218 25.941406-5.785156-3.898438-12.832032-6.808594-20.964844-6.808594-6.882812 0-18.636719 2.910156-41.140625 28.085938zm12.703125 182.414062c-38.597656 0-70-31.402344-70-70v-30h75.855469l20 20h-25.855469c-5.511719 0-10 4.488281-10 10s4.488281 10 10 10h100.949219l17.871093 20h-9.820312c-5.523438 0-10 4.476562-10 10s4.476562 10 10 10h26.664062c-3.273437 6.199219-4.554687 13.183594-3.847656 20zm194.890625 45.859375-38.890625-38.890625c-5.847656-5.851562-5.847656-15.367188-.003906-21.214844 5.558594-5.558594 15.46875-5.75 21.214844 0l38.890624 38.890625zm162.625-75.460937c-13.105469 18.539062-34.453125 29.601562-57.167969 29.601562h-80.605468l-20-20h29.257812c5.523438 0 10-4.476562 10-10s-4.476562-10-10-10h-83.355469l-17.875-20h214.230469c1.355469 0 2.695312-.273438 3.9375-.808594l41.296875-17.699218zm0 0"/><path d="m50 362c-5.511719 0-10 4.488281-10 10s4.488281 10 10 10 10-4.488281 10-10-4.488281-10-10-10zm0 0"/><path d="m387 402h-28c-5.523438 0-10 4.476562-10 10s4.476562 10 10 10h28c5.523438 0 10-4.476562 10-10s-4.476562-10-10-10zm0 0"/><path d="m159 402h-30c-5.523438 0-10 4.476562-10 10s4.476562 10 10 10h30c5.523438 0 10-4.476562 10-10s-4.476562-10-10-10zm0 0"/><path d="m176 130c19.625 0 35-19.765625 35-45s-15.375-45-35-45-35 19.765625-35 45 15.375 45 35 45zm0-70c7.09375 0 15 10.265625 15 25s-7.90625 25-15 25-15-10.265625-15-25 7.90625-25 15-25zm0 0"/><path d="m106 193c-5.511719 0-10 4.488281-10 10s4.488281 10 10 10 10-4.488281 10-10-4.488281-10-10-10zm0 0"/></svg>
\ No newline at end of file |