diff options
author | Tibor Frank <tifrank@cisco.com> | 2022-01-26 09:31:43 +0100 |
---|---|---|
committer | Tibor Frank <tifrank@cisco.com> | 2022-02-24 08:11:41 +0100 |
commit | b2cb835b34c7404b2aaee3ec30700c67537da66d (patch) | |
tree | d0a6ce077b6d927565294fcd7301fb727708cd06 /resources | |
parent | 9eebc3e59028014a8fc151575b99347d2871c097 (diff) |
UTI: PoC - Dash application for Trending
- delete old dash demo
Change-Id: I3e75c1dc18ee85c1826838af0343a4b779f71754
Signed-off-by: Tibor Frank <tifrank@cisco.com>
Diffstat (limited to 'resources')
-rw-r--r-- | resources/tools/dash/app/config.py | 3 | ||||
-rw-r--r-- | resources/tools/dash/app/pal/__init__.py | 39 | ||||
-rw-r--r-- | resources/tools/dash/app/pal/assets.py | 18 | ||||
-rw-r--r-- | resources/tools/dash/app/pal/report/data.py | 26 | ||||
-rw-r--r-- | resources/tools/dash/app/pal/report/layout.py | 46 | ||||
-rw-r--r-- | resources/tools/dash/app/pal/report/report.py (renamed from resources/tools/dash/app/pal/trending/dashboard.py) | 33 | ||||
-rw-r--r-- | resources/tools/dash/app/pal/routes.py | 18 | ||||
-rw-r--r-- | resources/tools/dash/app/pal/templates/index.jinja2 | 14 | ||||
-rw-r--r-- | resources/tools/dash/app/pal/trending/data.py | 13 | ||||
-rw-r--r-- | resources/tools/dash/app/pal/trending/html_layout.txt | 29 | ||||
-rw-r--r-- | resources/tools/dash/app/pal/trending/layout.py | 364 | ||||
-rw-r--r-- | resources/tools/dash/app/pal/trending/spec_test_selection.yaml | 106 | ||||
-rw-r--r-- | resources/tools/dash/app/pal/trending/trending.py | 74 | ||||
-rw-r--r-- | resources/tools/dash/app/requirements.txt | 3 | ||||
-rw-r--r-- | resources/tools/dash/app/wsgi.py | 4 |
15 files changed, 712 insertions, 78 deletions
diff --git a/resources/tools/dash/app/config.py b/resources/tools/dash/app/config.py index 279317b781..55f92cc36b 100644 --- a/resources/tools/dash/app/config.py +++ b/resources/tools/dash/app/config.py @@ -17,7 +17,8 @@ from os import environ class Config: - """Flask configuration variables.""" + """Flask configuration variables. + """ # General Config FLASK_APP = environ.get("FLASK_APP") diff --git a/resources/tools/dash/app/pal/__init__.py b/resources/tools/dash/app/pal/__init__.py index 9950defa44..863fd081e7 100644 --- a/resources/tools/dash/app/pal/__init__.py +++ b/resources/tools/dash/app/pal/__init__.py @@ -11,29 +11,48 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Initialize Flask app.""" +"""Initialize Flask app. +""" + +import logging + from flask import Flask from flask_assets import Environment def init_app(): - """Construct core Flask application with embedded Dash app.""" + """Construct core Flask application with embedded Dash app. + """ + + logging.basicConfig( + format=u"%(asctime)s: %(levelname)s: %(message)s", + datefmt=u"%Y/%m/%d %H:%M:%S", + level=logging.INFO + ) + + logging.info("Application started.") + app = Flask(__name__, instance_relative_config=False) - app.config.from_object("config.Config") - assets = Environment() - assets.init_app(app) + app.config.from_object(u"config.Config") with app.app_context(): # Import parts of our core Flask app. from . import routes from .assets import compile_static_assets - # Import Trending Dash application. - from .trending.dashboard import init_dashboard - - app = init_dashboard(app) + assets = Environment() + assets.init_app(app) # Compile static assets. compile_static_assets(assets) - return app + # Import Dash applications. + from .trending.trending import init_trending + app = init_trending(app) + + from .report.report import init_report + app = init_report(app) + + return app + +app = init_app() diff --git a/resources/tools/dash/app/pal/assets.py b/resources/tools/dash/app/pal/assets.py index 4237707734..63fac16333 100644 --- a/resources/tools/dash/app/pal/assets.py +++ b/resources/tools/dash/app/pal/assets.py @@ -11,7 +11,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Compile static assets.""" +"""Compile static assets. +""" + from flask import current_app as app from flask_assets import Bundle @@ -24,15 +26,17 @@ def compile_static_assets(assets): :returns: Compiled stylesheets. :rtype: Environment """ + assets.auto_build = True assets.debug = False less_bundle = Bundle( - "less/*.less", - filters="less,cssmin", - output="dist/css/styles.css", - extra={"rel": "stylesheet/less"}, + u"less/*.less", + filters=u"less,cssmin", + output=u"dist/css/styles.css", + extra={u"rel": u"stylesheet/less"}, ) - assets.register("less_all", less_bundle) - if app.config["FLASK_ENV"] == "development": + assets.register(u"less_all", less_bundle) + if app.config[u"FLASK_ENV"] == u"development": less_bundle.build() + return assets diff --git a/resources/tools/dash/app/pal/report/data.py b/resources/tools/dash/app/pal/report/data.py new file mode 100644 index 0000000000..43e0239228 --- /dev/null +++ b/resources/tools/dash/app/pal/report/data.py @@ -0,0 +1,26 @@ +# Copyright (c) 2022 Cisco and/or its affiliates. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Prepare data for Plotly Dash.""" + +import pandas as pd + + +def create_dataframe(): + """Create Pandas DataFrame from local CSV. + """ + + return pd.read_csv( + u"https://s3-docs.fd.io/csit/master/trending/_static/vpp/" + u"csit-vpp-perf-mrr-daily-master-2n-skx-trending.csv" + ) diff --git a/resources/tools/dash/app/pal/report/layout.py b/resources/tools/dash/app/pal/report/layout.py new file mode 100644 index 0000000000..70fe727efc --- /dev/null +++ b/resources/tools/dash/app/pal/report/layout.py @@ -0,0 +1,46 @@ +# Copyright (c) 2022 Cisco and/or its affiliates. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Plotly Dash HTML layout override.""" + +html_layout = u""" +<!DOCTYPE html> + <html> + <head> + {%metas%} + <title>{%title%}</title> + {%favicon%} + {%css%} + </head> + <body class="dash-template"> + <header> + <div class="nav-wrapper"> + <a href="/"> + <h1>FD.io CSIT</h1> + </a> + <a href=""> + <h1>Report</h1> + </a> + <nav> + </nav> + </div> + </header> + {%app_entry%} + <footer> + {%config%} + {%scripts%} + {%renderer%} + </footer> + </body> + </html> +""" diff --git a/resources/tools/dash/app/pal/trending/dashboard.py b/resources/tools/dash/app/pal/report/report.py index ee5ea5123f..d22a0b6705 100644 --- a/resources/tools/dash/app/pal/trending/dashboard.py +++ b/resources/tools/dash/app/pal/report/report.py @@ -11,7 +11,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Instantiate a Dash app.""" +"""Instantiate the Report Dash application. +""" + import dash from dash import dcc from dash import html @@ -23,7 +25,7 @@ from .data import create_dataframe from .layout import html_layout -def init_dashboard(server): +def init_report(server): """Create a Plotly Dash dashboard. :param server: Flask server. @@ -31,12 +33,13 @@ def init_dashboard(server): :returns: Dash app server. :rtype: Dash """ + dash_app = dash.Dash( server=server, - routes_pathname_prefix="/trending/", + routes_pathname_prefix=u"/report/", external_stylesheets=[ - "/static/dist/css/styles.css", - "https://fonts.googleapis.com/css?family=Lato", + u"/static/dist/css/styles.css", + u"https://fonts.googleapis.com/css?family=Lato", ], ) @@ -51,19 +54,23 @@ def init_dashboard(server): children=[ create_data_table(df), ], - id="dash-container", + id=u"dash-container", ) return dash_app.server def create_data_table(df): - """Create Dash datatable from Pandas DataFrame.""" + """Create Dash datatable from Pandas DataFrame. + + DEMO + """ + table = dash_table.DataTable( - id="database-table", - columns=[{"name": i, "id": i} for i in df.columns], - data=df.to_dict("records"), - sort_action="native", - sort_mode="native", - page_size=300, + id=u"database-table", + columns=[{u"name": i, u"id": i} for i in df.columns], + data=df.to_dict(u"records"), + sort_action=u"native", + sort_mode=u"native", + page_size=5, ) return table diff --git a/resources/tools/dash/app/pal/routes.py b/resources/tools/dash/app/pal/routes.py index 16680c4436..200d7afeba 100644 --- a/resources/tools/dash/app/pal/routes.py +++ b/resources/tools/dash/app/pal/routes.py @@ -11,17 +11,21 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Routes for parent Flask app.""" +"""Routes for parent Flask app. +""" + from flask import current_app as app from flask import render_template -@app.route("/") +@app.route(u"/") def home(): - """Landing page.""" + """Landing page. + """ + return render_template( - "index.jinja2", - title="FD.io CSIT", - description="Performance Dashboard", - template="home-template" + u"index.jinja2", + title=u"FD.io CSIT", + description=u"Performance Dashboard", + template=u"home-template" ) diff --git a/resources/tools/dash/app/pal/templates/index.jinja2 b/resources/tools/dash/app/pal/templates/index.jinja2 index 8ab5c84356..bd53346065 100644 --- a/resources/tools/dash/app/pal/templates/index.jinja2 +++ b/resources/tools/dash/app/pal/templates/index.jinja2 @@ -5,9 +5,15 @@ <img src="{{ url_for('static', filename='img/logo.svg') }}" class="logo" /> <h1 class="site-title">{{ title }}</h1> <p class="subtitle">{{ description }}</p> - <a href="/trending/" class="dash-link"> - <span>trending</span> - <i class="fas fa-arrow-right"></i> - </a> + <div> + <a href="/trending/" class="dash-link"> + <span>Trending</span> + <i class="fas fa-arrow-right"></i> + </a> + <a href="/report/" class="dash-link"> + <span>Report</span> + <i class="fas fa-arrow-right"></i> + </a> + </div> </div> {% endblock %} diff --git a/resources/tools/dash/app/pal/trending/data.py b/resources/tools/dash/app/pal/trending/data.py index 06466a9175..298a40542e 100644 --- a/resources/tools/dash/app/pal/trending/data.py +++ b/resources/tools/dash/app/pal/trending/data.py @@ -11,14 +11,19 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Prepare data for Plotly Dash.""" +"""Prepare data for Plotly Dash. +""" import pandas as pd def create_dataframe(): - """Create Pandas DataFrame from local CSV.""" + """Create Pandas DataFrame from local CSV. + + DEMO + """ + return pd.read_csv( - "https://s3-docs.fd.io/csit/master/trending/_static/vpp/" - "csit-vpp-perf-mrr-daily-master-2n-zn2-trending.csv" + u"https://s3-docs.fd.io/csit/master/trending/_static/vpp/" + u"csit-vpp-perf-mrr-daily-master-2n-zn2-trending.csv" ) diff --git a/resources/tools/dash/app/pal/trending/html_layout.txt b/resources/tools/dash/app/pal/trending/html_layout.txt new file mode 100644 index 0000000000..3f0aa0cb8d --- /dev/null +++ b/resources/tools/dash/app/pal/trending/html_layout.txt @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html> + <head> + {%metas%} + <title>Continuous Performance Trending</title> + {%favicon%} + {%css%} + </head> + <body class="dash-template"> + <header> + <div class="nav-wrapper"> + <a href="/"> + <h1>FD.io CSIT</h1> + </a> + <a href=""> + <h1>Continuous Performance Trending</h1> + </a> + <nav> + </nav> + </div> + </header> + {%app_entry%} + <footer> + {%config%} + {%scripts%} + {%renderer%} + </footer> + </body> +</html> diff --git a/resources/tools/dash/app/pal/trending/layout.py b/resources/tools/dash/app/pal/trending/layout.py index 1f60aecd83..69ce0c416d 100644 --- a/resources/tools/dash/app/pal/trending/layout.py +++ b/resources/tools/dash/app/pal/trending/layout.py @@ -11,33 +11,339 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Plotly Dash HTML layout override.""" - -html_layout = """ -<!DOCTYPE html> - <html> - <head> - {%metas%} - <title>{%title%}</title> - {%favicon%} - {%css%} - </head> - <body class="dash-template"> - <header> - <div class="nav-wrapper"> - <a href="/"> - <h1>Continuous Performance Trending</h1> - </a> - <nav> - </nav> - </div> - </header> - {%app_entry%} - <footer> - {%config%} - {%scripts%} - {%renderer%} - </footer> - </body> - </html> +"""Plotly Dash HTML layout override. """ + +import logging + +from dash import dcc +from dash import html +from dash import Input, Output, callback +from dash.exceptions import PreventUpdate +from yaml import load, FullLoader, YAMLError + + +class Layout: + """ + """ + + def __init__(self, app, html_layout_file, spec_file): + """ + """ + + # Inputs + self._app = app + self._html_layout_file = html_layout_file + self._spec_file = spec_file + + # Read from files: + self._html_layout = "" + self._spec_test = None + + try: + with open(self._html_layout_file, "r") as layout_file: + self._html_layout = layout_file.read() + except IOError as err: + logging.error(f"Not possible to open the file {layout_file}\n{err}") + + try: + with open(self._spec_file, "r") as file_read: + self._spec_test = load(file_read, Loader=FullLoader) + except IOError as err: + logging.error(f"Not possible to open the file {spec_file}\n{err}") + except YAMLError as err: + logging.error( + f"An error occurred while parsing the specification file " + f"{spec_file}\n" + f"{err}" + ) + + # Callbacks: + if self._app is not None and hasattr(self, 'callbacks'): + self.callbacks(self._app) + + # User choice (one test): + self._test_selection = { + "phy": "", + "area": "", + "test": "", + "core": "", + "frame-size": "", + "test-type": "" + } + + @property + def html_layout(self): + return self._html_layout + + @property + def spec_test(self): + return self._spec_test + + def _reset_test_selection(self): + self._test_selection = { + "phy": "", + "area": "", + "test": "", + "core": "", + "frame-size": "", + "test-type": "" + } + + def add_content(self): + """ + """ + if self._html_layout and self._spec_test: + return html.Div( + id="div-main", + children=[ + self._add_ctrl_div(), + self._add_plotting_div() + ] + ) + else: + return html.Div( + id="div-main-error", + children="An Error Occured." + ) + + def _add_ctrl_div(self): + """Add div with controls. It is placed on the left side. + """ + return html.Div( + id="div-controls", + children=[ + html.Div( + id="div-controls-tabs", + children=[ + self._add_ctrl_select(), + self._add_ctrl_shown() + ] + ) + ], + style={ + "display": "inline-block", + "width": "18%", + "padding": "5px" + } + ) + + def _add_plotting_div(self): + """Add div with plots and tables. It is placed on the right side. + """ + return html.Div( + id="div-plotting-area", + children=[ + # Only a visible note. + # TODO: Add content. + html.H3( + "Graphs and Tables", + style={ + "vertical-align": "middle", + "text-align": "center" + } + ) + ], + style={ + "vertical-align": "middle", + "display": "inline-block", + "width": "80%", + "padding": "5px" + } + ) + + def _add_ctrl_shown(self): + """ + """ + return html.Div( + id="div-ctrl-shown", + children="List of selected tests" + ) + + def _add_ctrl_select(self): + """ + """ + return html.Div( + id="div-ctrl-select", + children=[ + html.Br(), + html.Div( + children="Physical Test Bed Topology, NIC and Driver" + ), + dcc.Dropdown( + id="dd-ctrl-phy", + placeholder="Select a Physical Test Bed Topology...", + multi=False, + clearable=False, + options=[ + {"label": k, "value": k} for k in self._spec_test.keys() + ], + ), + html.Br(), + html.Div( + children="Area" + ), + dcc.Dropdown( + id="dd-ctrl-area", + placeholder="Select an Area...", + disabled=True, + multi=False, + clearable=False, + ), + html.Br(), + html.Div( + children="Test" + ), + dcc.Dropdown( + id="dd-ctrl-test", + placeholder="Select a Test...", + disabled=True, + multi=False, + clearable=False, + ), + + # Change to radio buttons: + html.Br(), + html.Div( + children="Number of Cores" + ), + dcc.Dropdown( + id="dd-ctrl-core", + placeholder="Select a Number of Cores...", + disabled=True, + multi=False, + clearable=False, + ), + html.Br(), + html.Div( + children="Frame Size" + ), + dcc.Dropdown( + id="dd-ctrl-framesize", + placeholder="Select a Frame Size...", + disabled=True, + multi=False, + clearable=False, + ), + html.Br(), + html.Div( + children="Test Type" + ), + dcc.Dropdown( + id="dd-ctrl-testtype", + placeholder="Select a Test Type...", + disabled=True, + multi=False, + clearable=False, + ), + html.Br(), + html.Button( + id="btn-ctrl-add", + children="Add", + disabled=True + ), + html.Br(), + html.Div( + id="div-ctrl-info" + ) + ] + ) + + def callbacks(self, app): + + @app.callback( + Output("dd-ctrl-area", "options"), + Output("dd-ctrl-area", "disabled"), + Input("dd-ctrl-phy", "value"), + ) + def _update_dd_area(phy): + """ + """ + + if phy is None: + raise PreventUpdate + + try: + options = [ + {"label": self._spec_test[phy][v]["label"], "value": v} + for v in [v for v in self._spec_test[phy].keys()] + ] + except KeyError: + options = list() + + return options, False + + @app.callback( + Output("dd-ctrl-test", "options"), + Output("dd-ctrl-test", "disabled"), + Input("dd-ctrl-phy", "value"), + Input("dd-ctrl-area", "value"), + ) + def _update_dd_test(phy, area): + """ + """ + + if not all((phy, area, )): + raise PreventUpdate + + try: + options = [ + {"label": v, "value": v} + for v in self._spec_test[phy][area]["test"] + ] + except KeyError: + options = list() + + return options, False + + @app.callback( + Output("btn-ctrl-add", "disabled"), + Input("dd-ctrl-phy", "value"), + Input("dd-ctrl-area", "value"), + Input("dd-ctrl-test", "value"), + ) + def _update_btn_add(phy, area, test): + """ + """ + + if all((phy, area, test, )): + self._test_selection["phy"] = phy + self._test_selection["area"] = area + self._test_selection["test"] = test + return False + else: + return True + + @app.callback( + Output("div-ctrl-info", "children"), + Output("dd-ctrl-phy", "value"), + Output("dd-ctrl-area", "value"), + Output("dd-ctrl-test", "value"), + Output("btn-ctrl-add", "n_clicks"), + Input("btn-ctrl-add", "n_clicks") + ) + def _print_user_selection(n_clicks): + """ + """ + + logging.info(f"\n\n{n_clicks}\n\n") + + if not n_clicks: + raise PreventUpdate + + selected = ( + f"{self._test_selection['phy']} # " + f"{self._test_selection['area']} # " + f"{self._test_selection['test']} # " + f"{n_clicks}\n" + ) + + self._reset_test_selection() + + return ( + selected, + None, + None, + None, + 0, + ) diff --git a/resources/tools/dash/app/pal/trending/spec_test_selection.yaml b/resources/tools/dash/app/pal/trending/spec_test_selection.yaml new file mode 100644 index 0000000000..dae902203a --- /dev/null +++ b/resources/tools/dash/app/pal/trending/spec_test_selection.yaml @@ -0,0 +1,106 @@ +2n-aws-nitro-50g-ena: + ip4-base: + label: IPv4 Routing Base + test: + - ethip4-ip4base + core: [1C, 2C] + frame-size: [64B, 1518B] + test-type: [MRR, NDR, PDR] + ip4-scale: + label: IPv4 Routing Scale + test: + - ethip4-ip4scale20k + - ethip4-ip4scale20k-rnd + core: [1C, 2C] + frame-size: [64B, 1518B] + test-type: [MRR, NDR, PDR] + ip6-base: + label: IPv6 Routing Base + test: + - ethip6-ip4base + core: [1C, 2C] + frame-size: [78B, 1518B] + test-type: [MRR, NDR, PDR] + ip6-scale: + label: IPv6 Routing Scale + test: + - ethip6-ip4scale20k + - ethip6-ip4scale20k-rnd + core: [1C, 2C] + frame-size: [78B, 1518B] + test-type: [MRR, NDR, PDR] +# 2n-clx-cx556a-rdma: +# 2n-clx-x710-avf: +# 2n-clx-x710-dpdk: +# 2n-clx-xxv710-af-xdp: +# 2n-clx-xxv710-avf: +# l2-base: +# l2-scale: +# ip4-base: +# ip4-scale: +# ip4-features: +# ip6-base: +# ip6-scale: +# ethip4-ethip4udpgeneve: +# nat44det-ip4-stl-bidir: +# nat44ed-ip4-stl-unidir: +# nat44ed-ip4-udp-stf-cps: +# nat44ed-ip4-tcp-stf-cps: +# nat44ed-ip4-udp-stf-pps: +# nat44ed-ip4-tcp-stf-pps: +# nat44ed-ip4-udp-tput: +# nat44ed-ip4-tcp-tput: +# vhost-base: +# memif-base: +# vnf-service-chains-routing: +# cnf-service-chains-routing: +# cnf-service-pipelines-routing: +# vnf-service-chains-tunnels: +# 2n-clx-xxv710-dpdk: +# 2n-dnv-x553-ixgbe: +# 2n-icx-xxv710-avf: +# 2n-icx-xxv710-dpdk: +# 2n-skx-x710-avf: +# 2n-skx-x710-dpdk: +# 2n-skx-xxv710-avf: +# 2n-skx-xxv710-dpdk: +# 2n-tx2-xl710-af-xdp: +# 2n-tx2-xl710-avf: +# 2n-tx2-xl710-dpdk: +# 2n-zn2-x710-avf: +# 2n-zn2-x710-dpdk: +# 2n-zn2-cx556a-rdma: +# 2n-zn2-xxv710-avf: +# 2n-zn2-xxv710-dpdk: +3n-aws-nitro-50g-ena: + ip4-base: + label: IPv4 Routing Base + test: + - ethip4-ip4base + core: [1C, 2C] + frame-size: [64B, 1518B] + test-type: [MRR, NDR, PDR] + + ip4-scale: + label: IPv4 Routing Scale + test: + - ethip4-ip4scale20k + - ethip4-ip4scale20k-rnd + core: [1C, 2C] + frame-size: [64B, 1518B] + test-type: [MRR, NDR, PDR] + + ipsec-base: + label: IPSec IPv4 Routing Base + test: + - ethip4ipsec40tnlsw-ip4base-int-aes256gcm + core: [1C, 2C] + frame-size: [IMIX, 1518B] + test-type: [MRR, NDR, PDR] +# 3n-dnv-x553-ixgbe: +# 3n-icx-xxv710-avf: +# 3n-icx-xxv710-dpdk: +# 3n-skx-x710-avf: +# 3n-skx-xxv710-avf: +# 3n-skx-xxv710-dpdk: +# 3n-tsh-x520-ixgbe: diff --git a/resources/tools/dash/app/pal/trending/trending.py b/resources/tools/dash/app/pal/trending/trending.py new file mode 100644 index 0000000000..4edf831045 --- /dev/null +++ b/resources/tools/dash/app/pal/trending/trending.py @@ -0,0 +1,74 @@ +# Copyright (c) 2022 Cisco and/or its affiliates. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Instantiate the Trending Dash applocation. +""" +import dash +from dash import dcc +from dash import html +from dash import dash_table +import numpy as np +import pandas as pd + +from .data import create_dataframe +from .layout import Layout + + +def init_trending(server): + """Create a Plotly Dash dashboard. + + :param server: Flask server. + :type server: Flask + :returns: Dash app server. + :rtype: Dash + """ + + dash_app = dash.Dash( + server=server, + routes_pathname_prefix=u"/trending/", + external_stylesheets=[ + u"/static/dist/css/styles.css", + u"https://fonts.googleapis.com/css?family=Lato", + ], + ) + + # Load DataFrame + df = create_dataframe() + + # Custom HTML layout + layout = Layout( + app=dash_app, + html_layout_file="pal/trending/html_layout.txt", + spec_file="pal/trending/spec_test_selection.yaml" + ) + dash_app.index_string = layout.html_layout + dash_app.layout = layout.add_content() + + return dash_app.server + + +def create_data_table(df): + """Create Dash datatable from Pandas DataFrame. + + DEMO + """ + + table = dash_table.DataTable( + id=u"database-table", + columns=[{u"name": i, u"id": i} for i in df.columns], + data=df.to_dict(u"records"), + sort_action=u"native", + sort_mode=u"native", + page_size=10, + ) + return table diff --git a/resources/tools/dash/app/requirements.txt b/resources/tools/dash/app/requirements.txt index 90e595e81c..befdc8a83b 100644 --- a/resources/tools/dash/app/requirements.txt +++ b/resources/tools/dash/app/requirements.txt @@ -23,7 +23,8 @@ protobuf==3.19.1 pyparsing==3.0.6 python-dateutil==2.8.2 python-dotenv==0.19.2 -pytz==2022.3 +pytz==2021.3 +PyYAML==5.1 retrying==1.3.3 setuptools==57.5.0 six==1.16.0 diff --git a/resources/tools/dash/app/wsgi.py b/resources/tools/dash/app/wsgi.py index c2832e29eb..316901c707 100644 --- a/resources/tools/dash/app/wsgi.py +++ b/resources/tools/dash/app/wsgi.py @@ -11,9 +11,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -from pal import init_app -app = init_app() +from pal import app + if __name__ == "__main__": # Main entry point. |