summaryrefslogtreecommitdiff
path: root/examples/py-flow-dashboard
diff options
context:
space:
mode:
Diffstat (limited to 'examples/py-flow-dashboard')
-rw-r--r--examples/py-flow-dashboard/assets/flow-dash.css3
-rwxr-xr-xexamples/py-flow-dashboard/flow-dash.py300
-rw-r--r--examples/py-flow-dashboard/plotly_dash.py415
-rw-r--r--examples/py-flow-dashboard/requirements.txt3
4 files changed, 721 insertions, 0 deletions
diff --git a/examples/py-flow-dashboard/assets/flow-dash.css b/examples/py-flow-dashboard/assets/flow-dash.css
new file mode 100644
index 000000000..4d813b5ef
--- /dev/null
+++ b/examples/py-flow-dashboard/assets/flow-dash.css
@@ -0,0 +1,3 @@
+body {
+ background: black;
+}
diff --git a/examples/py-flow-dashboard/flow-dash.py b/examples/py-flow-dashboard/flow-dash.py
new file mode 100755
index 000000000..d396e7e97
--- /dev/null
+++ b/examples/py-flow-dashboard/flow-dash.py
@@ -0,0 +1,300 @@
+#!/usr/bin/env python3
+
+import multiprocessing
+import os
+import sys
+import time
+
+sys.path.append(os.path.dirname(sys.argv[0]) + '/../../dependencies')
+sys.path.append(os.path.dirname(sys.argv[0]) + '/../share/nDPId')
+sys.path.append(os.path.dirname(sys.argv[0]))
+sys.path.append(sys.base_prefix + '/share/nDPId')
+import nDPIsrvd
+from nDPIsrvd import nDPIsrvdSocket
+import plotly_dash
+
+FLOW_RISK_SEVERE = 4
+FLOW_RISK_HIGH = 3
+FLOW_RISK_MEDIUM = 2
+FLOW_RISK_LOW = 1
+
+def nDPIsrvd_worker_onFlowCleanup(instance, current_flow, global_user_data):
+ _, shared_flow_dict = global_user_data
+
+ flow_key = current_flow.flow_key
+
+ shared_flow_dict['current-flows'] -= 1
+
+ if flow_key not in shared_flow_dict:
+ return True
+
+ shared_flow_dict['total-l4-bytes'] += shared_flow_dict[flow_key]['total-l4-bytes']
+
+ if shared_flow_dict[flow_key]['is_detected'] is True:
+ shared_flow_dict['current-detected-flows'] -= 1
+
+ if shared_flow_dict[flow_key]['is_guessed'] is True:
+ shared_flow_dict['current-guessed-flows'] -= 1
+
+ if shared_flow_dict[flow_key]['is_not_detected'] is True:
+ shared_flow_dict['current-not-detected-flows'] -= 1
+
+ if shared_flow_dict[flow_key]['is_midstream'] is True:
+ shared_flow_dict['current-midstream-flows'] -= 1
+
+ if shared_flow_dict[flow_key]['is_risky'] > 0:
+ shared_flow_dict['current-risky-flows'] -= 1
+
+ if shared_flow_dict[flow_key]['is_risky'] == FLOW_RISK_LOW:
+ shared_flow_dict['current-risky-flows-low'] -= 1
+ elif shared_flow_dict[flow_key]['is_risky'] == FLOW_RISK_MEDIUM:
+ shared_flow_dict['current-risky-flows-medium'] -= 1
+ elif shared_flow_dict[flow_key]['is_risky'] == FLOW_RISK_HIGH:
+ shared_flow_dict['current-risky-flows-high'] -= 1
+ elif shared_flow_dict[flow_key]['is_risky'] == FLOW_RISK_SEVERE:
+ shared_flow_dict['current-risky-flows-severe'] -= 1
+
+ del shared_flow_dict[current_flow.flow_key]
+
+ return True
+
+def nDPIsrvd_worker_onJsonLineRecvd(json_dict, instance, current_flow, global_user_data):
+ nsock, shared_flow_dict = global_user_data
+
+ shared_flow_dict['total-events'] += 1
+ shared_flow_dict['total-json-bytes'] = nsock.received_bytes
+
+ if 'error_event_name' in json_dict:
+ shared_flow_dict['total-base-events'] += 1
+
+ if 'daemon_event_name' in json_dict:
+ shared_flow_dict['total-daemon-events'] += 1
+
+ if 'packet_event_name' in json_dict and \
+ (json_dict['packet_event_name'] == 'packet' or \
+ json_dict['packet_event_name'] == 'packet-flow'):
+ shared_flow_dict['total-packet-events'] += 1
+
+ if 'flow_id' not in json_dict:
+ return True
+ else:
+ flow_key = json_dict['alias'] + '-' + json_dict['source'] + '-' + str(json_dict['flow_id'])
+
+ if flow_key not in shared_flow_dict:
+ current_flow.flow_key = flow_key
+ shared_flow_dict[flow_key] = mgr.dict()
+ shared_flow_dict[flow_key]['is_detected'] = False
+ shared_flow_dict[flow_key]['is_guessed'] = False
+ shared_flow_dict[flow_key]['is_not_detected'] = False
+ shared_flow_dict[flow_key]['is_midstream'] = False
+ shared_flow_dict[flow_key]['is_risky'] = 0
+ shared_flow_dict[flow_key]['total-l4-bytes'] = 0
+
+ shared_flow_dict[flow_key]['json'] = mgr.dict()
+
+ shared_flow_dict['total-flows'] += 1
+ shared_flow_dict['current-flows'] += 1
+
+ if current_flow.flow_key != flow_key:
+ return False
+
+ if 'flow_src_tot_l4_payload_len' in json_dict and 'flow_dst_tot_l4_payload_len' in json_dict:
+ shared_flow_dict[flow_key]['total-l4-bytes'] = json_dict['flow_src_tot_l4_payload_len'] + \
+ json_dict['flow_dst_tot_l4_payload_len']
+
+ if 'midstream' in json_dict and json_dict['midstream'] != 0:
+ if shared_flow_dict[flow_key]['is_midstream'] is False:
+ shared_flow_dict['total-midstream-flows'] += 1
+ shared_flow_dict['current-midstream-flows'] += 1
+ shared_flow_dict[flow_key]['is_midstream'] = True
+
+ if 'ndpi' in json_dict:
+ shared_flow_dict[flow_key]['json']['ndpi'] = json_dict['ndpi']
+
+ if 'flow_risk' in json_dict['ndpi']:
+ if shared_flow_dict[flow_key]['is_risky'] == 0:
+ shared_flow_dict['total-risky-flows'] += 1
+ shared_flow_dict['current-risky-flows'] += 1
+
+ severity = shared_flow_dict[flow_key]['is_risky']
+ if severity == FLOW_RISK_LOW:
+ shared_flow_dict['current-risky-flows-low'] -= 1
+ elif severity == FLOW_RISK_MEDIUM:
+ shared_flow_dict['current-risky-flows-medium'] -= 1
+ elif severity == FLOW_RISK_HIGH:
+ shared_flow_dict['current-risky-flows-high'] -= 1
+ elif severity == FLOW_RISK_SEVERE:
+ shared_flow_dict['current-risky-flows-severe'] -= 1
+
+ for key in json_dict['ndpi']['flow_risk']:
+ if json_dict['ndpi']['flow_risk'][key]['severity'] == 'Low':
+ severity = max(severity, FLOW_RISK_LOW)
+ elif json_dict['ndpi']['flow_risk'][key]['severity'] == 'Medium':
+ severity = max(severity, FLOW_RISK_MEDIUM)
+ elif json_dict['ndpi']['flow_risk'][key]['severity'] == 'High':
+ severity = max(severity, FLOW_RISK_HIGH)
+ elif json_dict['ndpi']['flow_risk'][key]['severity'] == 'Severe':
+ severity = max(severity, FLOW_RISK_SEVERE)
+ else:
+ raise RuntimeError('Invalid flow risk severity: {}'.format(
+ json_dict['ndpi']['flow_risk'][key]['severity']))
+
+ shared_flow_dict[flow_key]['is_risky'] = severity
+ if severity == FLOW_RISK_LOW:
+ shared_flow_dict['current-risky-flows-low'] += 1
+ elif severity == FLOW_RISK_MEDIUM:
+ shared_flow_dict['current-risky-flows-medium'] += 1
+ elif severity == FLOW_RISK_HIGH:
+ shared_flow_dict['current-risky-flows-high'] += 1
+ elif severity == FLOW_RISK_SEVERE:
+ shared_flow_dict['current-risky-flows-severe'] += 1
+
+ if 'flow_event_name' not in json_dict:
+ return True
+
+ if json_dict['flow_state'] == 'finished' and \
+ json_dict['ndpi']['proto'] != 'Unknown' and \
+ shared_flow_dict[flow_key]['is_detected'] is False:
+ shared_flow_dict['total-detected-flows'] += 1
+ shared_flow_dict['current-detected-flows'] += 1
+ shared_flow_dict[flow_key]['is_detected'] = True
+
+ if json_dict['flow_event_name'] == 'new':
+
+ shared_flow_dict['total-flow-new-events'] += 1
+
+ elif json_dict['flow_event_name'] == 'update':
+
+ shared_flow_dict['total-flow-update-events'] += 1
+
+ elif json_dict['flow_event_name'] == 'analyse':
+
+ shared_flow_dict['total-flow-analyse-events'] += 1
+
+ elif json_dict['flow_event_name'] == 'end':
+
+ shared_flow_dict['total-flow-end-events'] += 1
+
+ elif json_dict['flow_event_name'] == 'idle':
+
+ shared_flow_dict['total-flow-idle-events'] += 1
+
+ elif json_dict['flow_event_name'] == 'guessed':
+
+ shared_flow_dict['total-flow-guessed-events'] += 1
+
+ if shared_flow_dict[flow_key]['is_guessed'] is False:
+ shared_flow_dict['total-guessed-flows'] += 1
+ shared_flow_dict['current-guessed-flows'] += 1
+ shared_flow_dict[flow_key]['is_guessed'] = True
+
+ elif json_dict['flow_event_name'] == 'not-detected':
+
+ shared_flow_dict['total-flow-not-detected-events'] += 1
+
+ if shared_flow_dict[flow_key]['is_not_detected'] is False:
+ shared_flow_dict['total-not-detected-flows'] += 1
+ shared_flow_dict['current-not-detected-flows'] += 1
+ shared_flow_dict[flow_key]['is_not_detected'] = True
+
+ elif json_dict['flow_event_name'] == 'detected' or \
+ json_dict['flow_event_name'] == 'detection-update':
+
+ if json_dict['flow_event_name'] == 'detection-update':
+ shared_flow_dict['total-flow-detection-update-events'] += 1
+ else:
+ shared_flow_dict['total-flow-detected-events'] += 1
+
+ if shared_flow_dict[flow_key]['is_detected'] is False:
+ shared_flow_dict['total-detected-flows'] += 1
+ shared_flow_dict['current-detected-flows'] += 1
+ shared_flow_dict[flow_key]['is_detected'] = True
+
+ if shared_flow_dict[flow_key]['is_guessed'] is True:
+ shared_flow_dict['total-guessed-flows'] -= 1
+ shared_flow_dict['current-guessed-flows'] -= 1
+ shared_flow_dict[flow_key]['is_guessed'] = False
+
+ return True
+
+
+def nDPIsrvd_worker(address, shared_flow_dict):
+ sys.stderr.write('Recv buffer size: {}\n'
+ .format(nDPIsrvd.NETWORK_BUFFER_MAX_SIZE))
+ sys.stderr.write('Connecting to {} ..\n'
+ .format(address[0]+':'+str(address[1])
+ if type(address) is tuple else address))
+
+ try:
+ while True:
+ try:
+ nsock = nDPIsrvdSocket()
+ nsock.connect(address)
+ nsock.loop(nDPIsrvd_worker_onJsonLineRecvd,
+ nDPIsrvd_worker_onFlowCleanup,
+ (nsock, shared_flow_dict))
+ except nDPIsrvd.SocketConnectionBroken:
+ sys.stderr.write('Lost connection to {} .. reconnecting\n'
+ .format(address[0]+':'+str(address[1])
+ if type(address) is tuple else address))
+ time.sleep(1.0)
+ except KeyboardInterrupt:
+ pass
+
+
+if __name__ == '__main__':
+ argparser = nDPIsrvd.defaultArgumentParser()
+ argparser.add_argument('--listen-address', type=str, default='127.0.0.1', help='Plotly listen address')
+ argparser.add_argument('--listen-port', type=str, default=8050, help='Plotly listen port')
+ args = argparser.parse_args()
+ address = nDPIsrvd.validateAddress(args)
+
+ mgr = multiprocessing.Manager()
+ shared_flow_dict = mgr.dict()
+
+ shared_flow_dict['total-events'] = 0
+ shared_flow_dict['total-flow-new-events'] = 0
+ shared_flow_dict['total-flow-update-events'] = 0
+ shared_flow_dict['total-flow-analyse-events'] = 0
+ shared_flow_dict['total-flow-end-events'] = 0
+ shared_flow_dict['total-flow-idle-events'] = 0
+ shared_flow_dict['total-flow-detected-events'] = 0
+ shared_flow_dict['total-flow-detection-update-events'] = 0
+ shared_flow_dict['total-flow-guessed-events'] = 0
+ shared_flow_dict['total-flow-not-detected-events'] = 0
+ shared_flow_dict['total-packet-events'] = 0
+ shared_flow_dict['total-base-events'] = 0
+ shared_flow_dict['total-daemon-events'] = 0
+
+ shared_flow_dict['total-json-bytes'] = 0
+ shared_flow_dict['total-l4-bytes'] = 0
+ shared_flow_dict['total-flows'] = 0
+ shared_flow_dict['total-detected-flows'] = 0
+ shared_flow_dict['total-risky-flows'] = 0
+ shared_flow_dict['total-midstream-flows'] = 0
+ shared_flow_dict['total-guessed-flows'] = 0
+ shared_flow_dict['total-not-detected-flows'] = 0
+
+ shared_flow_dict['current-flows'] = 0
+ shared_flow_dict['current-detected-flows'] = 0
+ shared_flow_dict['current-midstream-flows'] = 0
+ shared_flow_dict['current-guessed-flows'] = 0
+ shared_flow_dict['current-not-detected-flows'] = 0
+
+ shared_flow_dict['current-risky-flows'] = 0
+ shared_flow_dict['current-risky-flows-severe'] = 0
+ shared_flow_dict['current-risky-flows-high'] = 0
+ shared_flow_dict['current-risky-flows-medium'] = 0
+ shared_flow_dict['current-risky-flows-low'] = 0
+
+ nDPIsrvd_job = multiprocessing.Process(target=nDPIsrvd_worker,
+ args=(address, shared_flow_dict))
+ nDPIsrvd_job.start()
+
+ web_job = multiprocessing.Process(target=plotly_dash.web_worker,
+ args=(shared_flow_dict, args.listen_address, args.listen_port))
+ web_job.start()
+
+ nDPIsrvd_job.join()
+ web_job.terminate()
+ web_job.join()
diff --git a/examples/py-flow-dashboard/plotly_dash.py b/examples/py-flow-dashboard/plotly_dash.py
new file mode 100644
index 000000000..34791d8b5
--- /dev/null
+++ b/examples/py-flow-dashboard/plotly_dash.py
@@ -0,0 +1,415 @@
+import math
+
+import dash
+
+try:
+ from dash import dcc
+except ImportError:
+ import dash_core_components as dcc
+
+try:
+ from dash import html
+except ImportError:
+ import dash_html_components as html
+
+try:
+ from dash import dash_table as dt
+except ImportError:
+ import dash_table as dt
+
+from dash.dependencies import Input, Output, State
+
+import dash_daq as daq
+
+import plotly.graph_objects as go
+
+global shared_flow_dict
+
+app = dash.Dash(__name__)
+
+def generate_box():
+ return {
+ 'display': 'flex', 'flex-direction': 'row',
+ 'background-color': '#082255'
+ }
+
+def generate_led_display(div_id, label_name):
+ return daq.LEDDisplay(
+ id=div_id,
+ label={'label': label_name, 'style': {'color': '#C4CDD5'}},
+ labelPosition='bottom',
+ value='0',
+ backgroundColor='#082255',
+ color='#C4CDD5',
+ )
+
+def generate_gauge(div_id, label_name, max_value=10):
+ return daq.Gauge(
+ id=div_id,
+ value=0,
+ label={'label': label_name, 'style': {'color': '#C4CDD5'}},
+ max=max_value,
+ min=0,
+ )
+
+def build_gauge(key, max_value=100):
+ gauge_max = int(max(max_value,
+ shared_flow_dict[key]))
+ grad_green = [0, int(gauge_max * 1/3)]
+ grad_yellow = [int(gauge_max * 1/3), int(gauge_max * 2/3)]
+ grad_red = [int(gauge_max * 2/3), gauge_max]
+
+ grad_dict = {
+ "gradient":True,
+ "ranges":{
+ "green":grad_green,
+ "yellow":grad_yellow,
+ "red":grad_red
+ }
+ }
+
+ return shared_flow_dict[key], gauge_max, grad_dict
+
+def build_piechart(labels, values, color_map=None):
+ lay = dict(
+ plot_bgcolor = '#082255',
+ paper_bgcolor = '#082255',
+ font={"color": "#fff"},
+ uirevision=True,
+ autosize=True,
+ height=250,
+ margin = {'autoexpand': True, 'b': 0, 'l': 0, 'r': 0, 't': 0, 'pad': 0},
+ width = 500,
+ uniformtext_minsize = 12,
+ uniformtext_mode = 'hide',
+ )
+
+ return go.Figure(layout=lay, data=[go.Pie(labels=labels, values=values, sort=False, marker_colors=color_map, textinfo='percent', textposition='inside')])
+
+COLOR_MAP = {
+ 'piechart-flows': ['rgb(153, 153, 255)', 'rgb(153, 204, 255)', 'rgb(255, 204, 153)', 'rgb(255, 255, 255)'],
+ 'piechart-midstream-flows': ['rgb(255, 255, 153)', 'rgb(153, 153, 255)'],
+ 'piechart-risky-flows': ['rgb(255, 0, 0)', 'rgb(255, 128, 0)', 'rgb(255, 255, 0)', 'rgb(128, 255, 0)', 'rgb(153, 153, 255)'],
+ 'graph-flows': {'Current Active Flows': {'color': 'rgb(153, 153, 255)', 'width': 1},
+ 'Current Risky Flows': {'color': 'rgb(255, 153, 153)', 'width': 3},
+ 'Current Midstream Flows': {'color': 'rgb(255, 255, 153)', 'width': 3},
+ 'Current Guessed Flows': {'color': 'rgb(153, 204, 255)', 'width': 1},
+ 'Current Not-Detected Flows': {'color': 'rgb(255, 204, 153)', 'width': 1},
+ 'Current Unclassified Flows': {'color': 'rgb(255, 255, 255)', 'width': 1},
+ },
+}
+
+def generate_tab_flow():
+ return html.Div([
+ html.Div(children=[
+ dcc.Interval(id="tab-flow-default-interval", interval=1 * 2000, n_intervals=0),
+
+ html.Div(children=[
+
+ dt.DataTable(
+ id='table-info',
+ columns=[{'id': c.lower(), 'name': c, 'editable': False}
+ for c in ['Name', 'Total']],
+ style_header={
+ 'backgroundColor': '#082233',
+ 'color': 'white'
+ },
+ style_data={
+ 'backgroundColor': '#082244',
+ 'color': 'white'
+ },
+ )
+
+ ], style={'display': 'flex', 'flex-direction': 'row'}),
+
+ html.Div(children=[
+ dcc.Graph(
+ id='piechart-flows',
+ config={
+ 'displayModeBar': False,
+ },
+ figure=build_piechart(['Detected', 'Guessed', 'Not-Detected', 'Unclassified'],
+ [0, 0, 0, 0], COLOR_MAP['piechart-flows']),
+ ),
+ ], style={'padding': 10, 'flex': 1}),
+
+ html.Div(children=[
+ dcc.Graph(
+ id='piechart-midstream-flows',
+ config={
+ 'displayModeBar': False,
+ },
+ figure=build_piechart(['Midstream', 'Not Midstream'],
+ [0, 0], COLOR_MAP['piechart-midstream-flows']),
+ ),
+ ], style={'padding': 10, 'flex': 1}),
+
+ html.Div(children=[
+ dcc.Graph(
+ id='piechart-risky-flows',
+ config={
+ 'displayModeBar': False,
+ },
+ figure=build_piechart(['Severy Risk', 'High Risk', 'Medium Risk', 'Low Risk', 'No Risk'],
+ [0, 0], COLOR_MAP['piechart-risky-flows']),
+ ),
+ ], style={'padding': 10, 'flex': 1}),
+ ], style=generate_box()),
+
+ html.Div(children=[
+ dcc.Interval(id="tab-flow-graph-interval", interval=4 * 1000, n_intervals=0),
+ dcc.Store(id="graph-traces"),
+
+ html.Div(children=[
+ dcc.Graph(
+ id="graph-flows",
+ config={
+ 'displayModeBar': True,
+ 'displaylogo': False,
+ },
+ style={'height':'60vh'},
+ ),
+ ], style={'padding': 10, 'flex': 1})
+ ], style=generate_box())
+ ])
+
+def generate_tab_other():
+ return html.Div([
+ html.Div(children=[
+ dcc.Interval(id="tab-other-default-interval", interval=1 * 2000, n_intervals=0),
+
+ html.Div(children=[
+ dcc.Graph(
+ id='piechart-events',
+ config={
+ 'displayModeBar': False,
+ },
+ ),
+ ], style={'padding': 10, 'flex': 1}),
+ ], style=generate_box())
+ ])
+
+TABS_STYLES = {
+ 'height': '34px'
+}
+TAB_STYLE = {
+ 'borderBottom': '1px solid #d6d6d6',
+ 'backgroundColor': '#385285',
+ 'padding': '6px',
+ 'fontWeight': 'bold',
+}
+TAB_SELECTED_STYLE = {
+ 'borderTop': '1px solid #d6d6d6',
+ 'borderBottom': '1px solid #d6d6d6',
+ 'backgroundColor': '#119DFF',
+ 'color': 'white',
+ 'padding': '6px'
+}
+
+app.layout = html.Div([
+ dcc.Tabs(id="tabs-flow-dash", value="tab-flows", children=[
+ dcc.Tab(label="Flow", value="tab-flows", style=TAB_STYLE,
+ selected_style=TAB_SELECTED_STYLE,
+ children=generate_tab_flow()),
+ dcc.Tab(label="Other", value="tab-other", style=TAB_STYLE,
+ selected_style=TAB_SELECTED_STYLE,
+ children=generate_tab_other()),
+ ], style=TABS_STYLES),
+ html.Div(id="tabs-content")
+])
+
+def prettifyBytes(bytes_received):
+ size_names = ['B', 'KB', 'MB', 'GB', 'TB']
+ if bytes_received == 0:
+ i = 0
+ else:
+ i = min(int(math.floor(math.log(bytes_received, 1024))), len(size_names) - 1)
+ p = math.pow(1024, i)
+ s = round(bytes_received / p, 2)
+ return '{:.2f} {}'.format(s, size_names[i])
+
+@app.callback(output=[Output('table-info', 'data'),
+ Output('piechart-flows', 'figure'),
+ Output('piechart-midstream-flows', 'figure'),
+ Output('piechart-risky-flows', 'figure')],
+
+ inputs=[Input('tab-flow-default-interval', 'n_intervals')])
+def tab_flow_update_components(n):
+ return [[{'name': 'JSON Events', 'total': shared_flow_dict['total-events']},
+ {'name': 'JSON Bytes', 'total': prettifyBytes(shared_flow_dict['total-json-bytes'])},
+ {'name': 'Layer4 Bytes', 'total': prettifyBytes(shared_flow_dict['total-l4-bytes'])},
+ {'name': 'Flows', 'total': shared_flow_dict['total-flows']},
+ {'name': 'Risky Flows', 'total': shared_flow_dict['total-risky-flows']},
+ {'name': 'Midstream Flows', 'total': shared_flow_dict['total-midstream-flows']},
+ {'name': 'Guessed Flows', 'total': shared_flow_dict['total-guessed-flows']},
+ {'name': 'Not Detected Flows', 'total': shared_flow_dict['total-not-detected-flows']}],
+ build_piechart(['Detected', 'Guessed', 'Not-Detected', 'Unclassified'],
+ [shared_flow_dict['current-detected-flows'],
+ shared_flow_dict['current-guessed-flows'],
+ shared_flow_dict['current-not-detected-flows'],
+ shared_flow_dict['current-flows']
+ - shared_flow_dict['current-detected-flows']
+ - shared_flow_dict['current-guessed-flows']
+ - shared_flow_dict['current-not-detected-flows']],
+ COLOR_MAP['piechart-flows']),
+ build_piechart(['Midstream', 'Not Midstream'],
+ [shared_flow_dict['current-midstream-flows'],
+ shared_flow_dict['current-flows'] -
+ shared_flow_dict['current-midstream-flows']],
+ COLOR_MAP['piechart-midstream-flows']),
+ build_piechart(['Severe', 'High', 'Medium', 'Low', 'No Risk'],
+ [shared_flow_dict['current-risky-flows-severe'],
+ shared_flow_dict['current-risky-flows-high'],
+ shared_flow_dict['current-risky-flows-medium'],
+ shared_flow_dict['current-risky-flows-low'],
+ shared_flow_dict['current-flows'] -
+ shared_flow_dict['current-risky-flows']],
+ COLOR_MAP['piechart-risky-flows'])]
+
+@app.callback(output=[Output('graph-flows', 'figure'),
+ Output('graph-traces', 'data')],
+ inputs=[Input('tab-flow-graph-interval', 'n_intervals'),
+ Input('tab-flow-graph-interval', 'interval')],
+ state=[State('graph-traces', 'data')])
+def tab_flow_update_graph(n, i, traces):
+ if traces is None:
+ traces = ([], [], [], [], [], [])
+
+ max_bins = 75
+
+ traces[0].append(shared_flow_dict['current-flows'])
+ traces[1].append(shared_flow_dict['current-risky-flows'])
+ traces[2].append(shared_flow_dict['current-midstream-flows'])
+ traces[3].append(shared_flow_dict['current-guessed-flows'])
+ traces[4].append(shared_flow_dict['current-not-detected-flows'])
+ traces[5].append(shared_flow_dict['current-flows']
+ - shared_flow_dict['current-detected-flows']
+ - shared_flow_dict['current-guessed-flows']
+ - shared_flow_dict['current-not-detected-flows'])
+ if len(traces[0]) > max_bins:
+ traces[0] = traces[0][1:]
+ traces[1] = traces[1][1:]
+ traces[2] = traces[2][1:]
+ traces[3] = traces[3][1:]
+ traces[4] = traces[4][1:]
+ traces[5] = traces[5][1:]
+
+ i /= 1000.0
+ x = list(range(max(n - max_bins, 0) * int(i), n * int(i), max(int(i), 0)))
+ if len(x) > 0 and x[0] > 60:
+ x = [round(t / 60, 2) for t in x]
+ x_div = 60
+ x_axis_title = 'Time (min)'
+ else:
+ x_div = 1
+ x_axis_title = 'Time (sec)'
+ min_x = max(0, x[0] if len(x) >= max_bins else 0)
+ max_x = max((max_bins * i) / x_div, x[max_bins - 1] if len(x) >= max_bins else 0)
+
+ lay = dict(
+ plot_bgcolor = '#082255',
+ paper_bgcolor = '#082255',
+ font={"color": "#fff"},
+ xaxis = {
+ 'title': x_axis_title,
+ "showgrid": False,
+ "showline": False,
+ "fixedrange": True,
+ "tickmode": 'linear',
+ "tick0": round(max_bins / x_div, 2),
+ "dtick": round(max_bins / x_div, 2),
+ },
+ yaxis = {
+ 'title': 'Flow Count',
+ "showgrid": False,
+ "showline": False,
+ "zeroline": False,
+ "fixedrange": True,
+ "tickmode": 'linear',
+ "dtick": 10,
+ },
+ uirevision=True,
+ autosize=True,
+ bargap=0.01,
+ bargroupgap=0,
+ hovermode="closest",
+ margin = {'b': 0, 'l': 0, 'r': 0, 't': 30, 'pad': 0},
+ legend = {'borderwidth': 0},
+ )
+
+ fig = go.Figure(layout=lay)
+ fig.update_xaxes(showgrid=True, gridwidth=1, gridcolor='#004D80', zeroline=True, zerolinewidth=1, range=[min_x, max_x])
+ fig.update_yaxes(showgrid=True, gridwidth=1, gridcolor='#004D80', zeroline=True, zerolinewidth=1)
+ fig.add_trace(go.Scatter(
+ x=x,
+ y=traces[0],
+ name='Current Active Flows',
+ mode='lines+markers',
+ line=COLOR_MAP['graph-flows']['Current Active Flows'],
+ ))
+ fig.add_trace(go.Scatter(
+ x=x,
+ y=traces[1],
+ name='Current Risky Flows',
+ mode='lines+markers',
+ line=COLOR_MAP['graph-flows']['Current Risky Flows'],
+ ))
+ fig.add_trace(go.Scatter(
+ x=x,
+ y=traces[2],
+ name='Current Midstream Flows',
+ mode='lines+markers',
+ line=COLOR_MAP['graph-flows']['Current Midstream Flows'],
+ ))
+ fig.add_trace(go.Scatter(
+ x=x,
+ y=traces[3],
+ name='Current Guessed Flows',
+ mode='lines+markers',
+ line=COLOR_MAP['graph-flows']['Current Guessed Flows'],
+ ))
+ fig.add_trace(go.Scatter(
+ x=x,
+ y=traces[4],
+ name='Current Not-Detected Flows',
+ mode='lines+markers',
+ line=COLOR_MAP['graph-flows']['Current Not-Detected Flows'],
+ ))
+ fig.add_trace(go.Scatter(
+ x=x,
+ y=traces[5],
+ name='Current Unclassified Flows',
+ mode='lines+markers',
+ line=COLOR_MAP['graph-flows']['Current Unclassified Flows'],
+ ))
+
+ return [fig, traces]
+
+@app.callback(output=[Output('piechart-events', 'figure')],
+ inputs=[Input('tab-other-default-interval', 'n_intervals')])
+def tab_other_update_components(n):
+ return [build_piechart(['Base', 'Daemon', 'Packet',
+ 'Flow New', 'Flow Update', 'Flow Analyse', 'Flow End', 'Flow Idle',
+ 'Flow Detection', 'Flow Detection-Updates', 'Flow Guessed', 'Flow Not-Detected'],
+ [shared_flow_dict['total-base-events'],
+ shared_flow_dict['total-daemon-events'],
+ shared_flow_dict['total-packet-events'],
+ shared_flow_dict['total-flow-new-events'],
+ shared_flow_dict['total-flow-update-events'],
+ shared_flow_dict['total-flow-analyse-events'],
+ shared_flow_dict['total-flow-end-events'],
+ shared_flow_dict['total-flow-idle-events'],
+ shared_flow_dict['total-flow-detected-events'],
+ shared_flow_dict['total-flow-detection-update-events'],
+ shared_flow_dict['total-flow-guessed-events'],
+ shared_flow_dict['total-flow-not-detected-events']])]
+
+def web_worker(mp_shared_flow_dict, listen_host, listen_port):
+ global shared_flow_dict
+
+ shared_flow_dict = mp_shared_flow_dict
+
+ try:
+ app.run_server(debug=False, host=listen_host, port=listen_port)
+ except KeyboardInterrupt:
+ pass
diff --git a/examples/py-flow-dashboard/requirements.txt b/examples/py-flow-dashboard/requirements.txt
new file mode 100644
index 000000000..1adede5dc
--- /dev/null
+++ b/examples/py-flow-dashboard/requirements.txt
@@ -0,0 +1,3 @@
+dash
+dash_daq
+Werkzeug==3.0.3