diff options
Diffstat (limited to 'examples/py-flow-dashboard')
-rw-r--r-- | examples/py-flow-dashboard/assets/flow-dash.css | 3 | ||||
-rwxr-xr-x | examples/py-flow-dashboard/flow-dash.py | 300 | ||||
-rw-r--r-- | examples/py-flow-dashboard/plotly_dash.py | 415 | ||||
-rw-r--r-- | examples/py-flow-dashboard/requirements.txt | 3 |
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 |