From a5e8351152d9513811e2d6d9eaaf2180e35e002d Mon Sep 17 00:00:00 2001 From: Peter Mikus Date: Mon, 10 Jan 2022 13:19:31 +0100 Subject: [PATCH] feat(uti): Dash demo - displays the table with trending data downloaded from trending pages Signed-off-by: Peter Mikus Change-Id: Ic0d48290105ccd846c7de9ee4d8acb78e0b72f00 --- resources/tools/dash/Dockerfile | 12 + resources/tools/dash/app/app.ini | 17 + resources/tools/dash/app/config.py | 35 +++ resources/tools/dash/app/pal/__init__.py | 39 +++ resources/tools/dash/app/pal/assets.py | 38 +++ resources/tools/dash/app/pal/routes.py | 27 ++ .../tools/dash/app/pal/static/dist/css/styles.css | 1 + .../tools/dash/app/pal/static/dist/img/favicon.svg | 348 +++++++++++++++++++++ resources/tools/dash/app/pal/static/img/logo.svg | 348 +++++++++++++++++++++ .../tools/dash/app/pal/static/less/global.less | 12 + .../tools/dash/app/pal/static/less/header.less | 33 ++ resources/tools/dash/app/pal/static/less/home.less | 64 ++++ .../tools/dash/app/pal/static/less/table.less | 256 +++++++++++++++ .../tools/dash/app/pal/templates/index.jinja2 | 13 + .../tools/dash/app/pal/templates/layout.jinja2 | 33 ++ resources/tools/dash/app/pal/trending/dashboard.py | 69 ++++ resources/tools/dash/app/pal/trending/data.py | 24 ++ resources/tools/dash/app/pal/trending/layout.py | 43 +++ resources/tools/dash/app/requirements.txt | 35 +++ resources/tools/dash/app/wsgi.py | 20 ++ resources/tools/dash/docker-compose.yaml | 19 ++ 21 files changed, 1486 insertions(+) create mode 100644 resources/tools/dash/Dockerfile create mode 100644 resources/tools/dash/app/app.ini create mode 100644 resources/tools/dash/app/config.py create mode 100644 resources/tools/dash/app/pal/__init__.py create mode 100644 resources/tools/dash/app/pal/assets.py create mode 100644 resources/tools/dash/app/pal/routes.py create mode 100644 resources/tools/dash/app/pal/static/dist/css/styles.css create mode 100644 resources/tools/dash/app/pal/static/dist/img/favicon.svg create mode 100644 resources/tools/dash/app/pal/static/img/logo.svg create mode 100644 resources/tools/dash/app/pal/static/less/global.less create mode 100644 resources/tools/dash/app/pal/static/less/header.less create mode 100644 resources/tools/dash/app/pal/static/less/home.less create mode 100644 resources/tools/dash/app/pal/static/less/table.less create mode 100644 resources/tools/dash/app/pal/templates/index.jinja2 create mode 100644 resources/tools/dash/app/pal/templates/layout.jinja2 create mode 100644 resources/tools/dash/app/pal/trending/dashboard.py create mode 100644 resources/tools/dash/app/pal/trending/data.py create mode 100644 resources/tools/dash/app/pal/trending/layout.py create mode 100644 resources/tools/dash/app/requirements.txt create mode 100644 resources/tools/dash/app/wsgi.py create mode 100644 resources/tools/dash/docker-compose.yaml diff --git a/resources/tools/dash/Dockerfile b/resources/tools/dash/Dockerfile new file mode 100644 index 0000000000..cd7eab7772 --- /dev/null +++ b/resources/tools/dash/Dockerfile @@ -0,0 +1,12 @@ +ARG PYTHON_VERSION=3.10 +FROM python:${PYTHON_VERSION}-buster + +WORKDIR /app + +COPY ./app/requirements.txt . + +RUN pip3 install -r requirements.txt + +EXPOSE 5000 + +CMD [ "uwsgi", "app.ini" ] \ No newline at end of file diff --git a/resources/tools/dash/app/app.ini b/resources/tools/dash/app/app.ini new file mode 100644 index 0000000000..bbf2943193 --- /dev/null +++ b/resources/tools/dash/app/app.ini @@ -0,0 +1,17 @@ +[uwsgi] +ini = :pal + +[pal] +module = wsgi:app + +processes = 2 +threads = 2 +plugin = python3 + +master = true +http-socket = :5000 +socket = /tmp/app.sock +chmod-socket = 666 +vacuum = true + +die-on-term = true \ No newline at end of file diff --git a/resources/tools/dash/app/config.py b/resources/tools/dash/app/config.py new file mode 100644 index 0000000000..279317b781 --- /dev/null +++ b/resources/tools/dash/app/config.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 + +# 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. + +from os import environ + + +class Config: + """Flask configuration variables.""" + + # General Config + FLASK_APP = environ.get("FLASK_APP") + FLASK_ENV = environ.get("FLASK_ENV") + SECRET_KEY = environ.get("SECRET_KEY") + + # Assets + LESS_BIN = environ.get("LESS_BIN") + ASSETS_DEBUG = environ.get("ASSETS_DEBUG") + LESS_RUN_IN_DEBUG = environ.get("LESS_RUN_IN_DEBUG") + + # Static Assets + STATIC_FOLDER = "static" + TEMPLATES_FOLDER = "templates" + COMPRESSOR_DEBUG = environ.get("COMPRESSOR_DEBUG") diff --git a/resources/tools/dash/app/pal/__init__.py b/resources/tools/dash/app/pal/__init__.py new file mode 100644 index 0000000000..9950defa44 --- /dev/null +++ b/resources/tools/dash/app/pal/__init__.py @@ -0,0 +1,39 @@ +# 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. + +"""Initialize Flask app.""" +from flask import Flask +from flask_assets import Environment + + +def init_app(): + """Construct core Flask application with embedded Dash app.""" + app = Flask(__name__, instance_relative_config=False) + app.config.from_object("config.Config") + assets = Environment() + assets.init_app(app) + + 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) + + # Compile static assets. + compile_static_assets(assets) + + return app diff --git a/resources/tools/dash/app/pal/assets.py b/resources/tools/dash/app/pal/assets.py new file mode 100644 index 0000000000..4237707734 --- /dev/null +++ b/resources/tools/dash/app/pal/assets.py @@ -0,0 +1,38 @@ +# 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. + +"""Compile static assets.""" +from flask import current_app as app +from flask_assets import Bundle + + +def compile_static_assets(assets): + """Compile stylesheets if in development mode. + + :param assets: Flask-Assets Environment. + :type assets: Environment + :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"}, + ) + assets.register("less_all", less_bundle) + if app.config["FLASK_ENV"] == "development": + less_bundle.build() + return assets diff --git a/resources/tools/dash/app/pal/routes.py b/resources/tools/dash/app/pal/routes.py new file mode 100644 index 0000000000..16680c4436 --- /dev/null +++ b/resources/tools/dash/app/pal/routes.py @@ -0,0 +1,27 @@ +# 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. + +"""Routes for parent Flask app.""" +from flask import current_app as app +from flask import render_template + + +@app.route("/") +def home(): + """Landing page.""" + return render_template( + "index.jinja2", + title="FD.io CSIT", + description="Performance Dashboard", + template="home-template" + ) diff --git a/resources/tools/dash/app/pal/static/dist/css/styles.css b/resources/tools/dash/app/pal/static/dist/css/styles.css new file mode 100644 index 0000000000..d77f95a58f --- /dev/null +++ b/resources/tools/dash/app/pal/static/dist/css/styles.css @@ -0,0 +1 @@ +body,html{height:100%!important;margin:0;padding:0;background:#e7ecf7!important;font-family:'Lato',sans-serif}.row .col{padding:0!important}header{position:relative;width:100%;padding:30px 0!important;background:white!important;box-shadow:0 0 5px #bec6cf;font-family:'Lato',sans-serif}header .nav-wrapper{display:flex;align-items:center;justify-content:space-between;width:1000px;max-width:90%;margin:auto}header .nav-wrapper nav{display:flex}header .nav-wrapper .logo{display:inline-block;width:40px}header .nav-wrapper a{color:#70829d;font-size:1em;text-decoration:none;transition:all .3s ease-out}.home-template .container{margin:0!important}.home-template .card{width:800px;max-width:93%;height:fit-content;margin:50px auto;padding:60px;background:white;box-shadow:0 0 5px rgba(65,67,144,0.15);text-align:center}@media(max-width:800px){.home-template .card{max-width:78%;width:unset;padding:40px}}.home-template .card .logo{width:50px;margin:auto}.home-template .card .site-title{margin:10px 0 3px;color:#5f6988;font-family:proxima-nova,sans-serif;font-size:2.3em;line-height:1;text-transform:uppercase}.home-template .card p{margin:0;color:#7f92af;font-size:1.08em}.home-template .card a{color:#79aec8;font-weight:500;text-decoration:none;transition:all .2s ease}.home-template .card .dash-link{display:block;margin-top:30px;font-size:1.1em;font-weight:600}.home-template .card .dash-link:hover{opacity:.7}.home-template .card .dash-link i{margin-left:6px;font-size:.9em}body,html{height:100%!important;padding:0;background:#e7ecf7!important;font-family:'Lato',sans-serif;margin:0}.dash-template h1{display:inline-block;margin:0;font-size:1.5em}.dash-template .logo{width:40px;margin-right:20px}.dash-template .logo:hover{opacity:.7}.dash-template .nav-wrapper a{display:flex;align-items:center}#dash-container{width:1200px;max-width:95%;margin:50px auto 0}.container{margin:90px 30px;color:#182635}#_dash-app-content{max-width:95%!important;margin:90px auto!important;overflow:hidden}nav a{display:flex;align-items:center;justify-content:space-between;color:#59657b;line-height:1;text-decoration:none;transition:all .3s ease-out}nav a:hover{cursor:pointer;opacity:.7}nav a i{margin-right:5px}.dash-spreadsheet-container{max-width:100%;margin:0 auto 20px!important;overflow:hidden;border:0;border-radius:4px;box-shadow:0 0 4px #cdd3e2;font-family:'Lato',sans-serif}.dash-spreadsheet-container *{box-shadow:none!important;font-family:Lato,sans-serif}.dash-spreadsheet-container th,.dash-spreadsheet-container tr{box-shadow:none!important}.dash-spreadsheet-container td:first-of-type,.dash-spreadsheet-container tr:first-of-type{width:55px}.dash-spreadsheet-container td:last-of-type,.dash-spreadsheet-container tr:last-of-type{width:100px}.dash-spreadsheet-container th{padding:25px 12px!important;border-top:0!important;border-right:0!important;border-bottom:1px solid #e5e7eb!important;border-left:0!important;background:white!important;color:#404552}.dash-spreadsheet-container th .column-header--sort{margin-right:7px;transition:all .2s ease-out}.dash-spreadsheet-container th .column-header--sort:hover{color:#9bd2eb!important}.dash-spreadsheet-container th .column-header--sort svg{width:.5em}.dash-spreadsheet-container th .sort{order:2;color:#aeaeae!important;transition:all .3s ease-out}.dash-spreadsheet-container th .sort:hover{text-decoration:none;cursor:pointer;opacity:.7}.dash-spreadsheet-container th>div{display:flex;align-items:center;width:fit-content}.dash-spreadsheet-container th>div span:last-of-type{font-size:.8em;font-weight:600;text-transform:uppercase}.dash-spreadsheet-container td{padding:12px!important;font-size:.95em;text-align:left!important}.dash-spreadsheet-container td:first-of-type,.dash-spreadsheet-container td:last-of-type{max-width:50px!important}.dash-spreadsheet-container td .dash-cell-value{display:block!important;max-width:500px;overflow:hidden!important;font-size:1.2em;font-weight:300;text-align:left;text-overflow:ellipsis;white-space:nowrap;opacity:.8}.dash-spreadsheet-container td[data-dash-column="command"],.dash-spreadsheet-container .column-1{max-width:200px!important}.dash-spreadsheet-container td[data-dash-column="index"]{text-align:center!important}.dash-spreadsheet-container td[data-dash-column="index"] div{text-align:center!important}.dash-spreadsheet-container td[data-dash-column="index"]{color:#939da4;font-size:1.1em;font-weight:500}.dash-spreadsheet-container tr:nth-child(even){background:#f5f8fc!important}table tbody{box-shadow:0 0 7px #bdbdd2!important}table tbody tr{border:0!important}table tbody tr:nth-child(even){background:#e7eefa!important}table tbody tr td{padding:12px 10px!important;overflow:hidden!important;border:0!important;font-size:.65em!important;line-height:1.25!important;text-align:center!important}.dash-spreadsheet-container .dash-spreadsheet-inner td.focused{background-color:rgba(154,212,255,0.2)!important;box-shadow:0 0 4px #cdd3e2}.dash-spreadsheet-container .dash-spreadsheet-inner td .input-cell-value-shadow{width:100%}.dash-spreadsheet-container .dash-spreadsheet-inner td input.dash-cell-value.unfocused{caret-color:#317ed1!important}.dash-spreadsheet-inner .input-active{overflow:visible!important;color:#829ab2;text-align:left!important}.dash-spreadsheet-inner input::placeholder{color:grey}#histogram-graph{margin-bottom:30px;padding-bottom:176px!important;overflow:hidden;border-radius:4px;background:white;box-shadow:0 0 4px #cdd3e2}.main-svg{overflow:visible!important}header .nav-wrapper{width:1200px!important} \ No newline at end of file diff --git a/resources/tools/dash/app/pal/static/dist/img/favicon.svg b/resources/tools/dash/app/pal/static/dist/img/favicon.svg new file mode 100644 index 0000000000..689757e3fd --- /dev/null +++ b/resources/tools/dash/app/pal/static/dist/img/favicon.svg @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + diff --git a/resources/tools/dash/app/pal/static/img/logo.svg b/resources/tools/dash/app/pal/static/img/logo.svg new file mode 100644 index 0000000000..689757e3fd --- /dev/null +++ b/resources/tools/dash/app/pal/static/img/logo.svg @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + diff --git a/resources/tools/dash/app/pal/static/less/global.less b/resources/tools/dash/app/pal/static/less/global.less new file mode 100644 index 0000000000..78b7ae4314 --- /dev/null +++ b/resources/tools/dash/app/pal/static/less/global.less @@ -0,0 +1,12 @@ +body, +html { + height: 100% !important; + margin: 0; + padding: 0; + background: #e7ecf7 !important; + font-family: 'Lato', sans-serif; +} + +.row .col { + padding: 0 !important; +} diff --git a/resources/tools/dash/app/pal/static/less/header.less b/resources/tools/dash/app/pal/static/less/header.less new file mode 100644 index 0000000000..49f6769df2 --- /dev/null +++ b/resources/tools/dash/app/pal/static/less/header.less @@ -0,0 +1,33 @@ +header { + position: relative; + width: 100%; + padding: 30px 0 !important; + background: white !important; + box-shadow: 0 0 5px #bec6cf; + font-family: 'Lato', sans-serif; + + .nav-wrapper { + display: flex; + align-items: center; + justify-content: space-between; + width: 1000px; + max-width: 90%; + margin: auto; + + nav { + display: flex; + } + + .logo { + display: inline-block; + width: 40px; + } + + a { + color: #70829d; + font-size: 1em; + text-decoration: none; + transition: all 0.3s ease-out; + } + } +} diff --git a/resources/tools/dash/app/pal/static/less/home.less b/resources/tools/dash/app/pal/static/less/home.less new file mode 100644 index 0000000000..ebd66a2ceb --- /dev/null +++ b/resources/tools/dash/app/pal/static/less/home.less @@ -0,0 +1,64 @@ +.home-template { + .container { + margin: 0 !important; + } + + .card { + width: 800px; + max-width: 93%; + height: fit-content; + margin: 50px auto; + padding: 60px; + background: white; + box-shadow: 0 0 5px rgba(65, 67, 144, 0.15); + text-align: center; + @media (max-width: 800px) { + max-width: 78%; + width: unset; + padding: 40px; + } + + .logo { + width: 50px; + margin: auto; + } + + .site-title { + margin: 10px 0 3px; + color: #5f6988; + font-family: proxima-nova, sans-serif; + font-size: 2.3em; + line-height: 1; + text-transform: uppercase; + } + + p { + margin: 0; + color: #7f92af; + font-size: 1.08em; + } + + a { + color: #79aec8; + font-weight: 500; + text-decoration: none; + transition: all .2s ease; + } + + .dash-link { + display: block; + margin-top: 30px; + font-size: 1.1em; + font-weight: 600; + + &:hover { + opacity: .7; + } + + i { + margin-left: 6px; + font-size: .9em; + } + } + } +} diff --git a/resources/tools/dash/app/pal/static/less/table.less b/resources/tools/dash/app/pal/static/less/table.less new file mode 100644 index 0000000000..40e18e0e64 --- /dev/null +++ b/resources/tools/dash/app/pal/static/less/table.less @@ -0,0 +1,256 @@ +body, +html { + height: 100% !important; + padding: 0; + background: #e7ecf7 !important; + font-family: 'Lato', sans-serif; + margin: 0; +} + +.dash-template { + h1 { + display: inline-block; + margin: 0; + font-size: 1.5em; + } + + .logo { + width: 40px; + margin-right: 20px; + + &:hover { + opacity: .7; + } + } + + .nav-wrapper a { + display: flex; + align-items: center; + } +} + +#dash-container { + width: 1200px; + max-width: 95%; + margin: 50px auto 0; +} + +.container { + margin: 90px 30px; + color: #182635; +} + +#_dash-app-content { + max-width: 95% !important; + margin: 90px auto !important; + overflow: hidden; +} + +nav { + a { + display: flex; + align-items: center; + justify-content: space-between; + color: #59657b; + line-height: 1; + text-decoration: none; + transition: all 0.3s ease-out; + + &:hover { + cursor: pointer; + opacity: 0.7; + } + + i { + margin-right: 5px; + } + } +} + +.dash-spreadsheet-container { + max-width: 100%; + margin: 0 auto 20px !important; + overflow: hidden; + border: 0; + border-radius: 4px; + box-shadow: 0 0 4px #cdd3e2; + font-family: 'Lato', sans-serif; + + * { + box-shadow: none !important; + font-family: Lato, sans-serif; + } + + th, + tr { + box-shadow: none !important; + } + + td:first-of-type, + tr:first-of-type { + width: 55px; + } + + td:last-of-type, + tr:last-of-type { + width: 100px; + } + + th { + padding: 25px 12px !important; + border-top: 0 !important; + border-right: 0 !important; + border-bottom: 1px solid #e5e7eb !important; + border-left: 0 !important; + background: white !important; + color: #404552; + + .column-header--sort { + margin-right: 7px; + transition: all 0.2s ease-out; + + &:hover { + color: #9bd2eb !important; + } + + svg { + width: 0.5em; + } + } + + .sort { + order: 2; + color: #aeaeae !important; + transition: all 0.3s ease-out; + + &:hover { + text-decoration: none; + cursor: pointer; + opacity: 0.7; + } + } + + & > div { + display: flex; + align-items: center; + width: fit-content; + + span:last-of-type { + font-size: 0.8em; + font-weight: 600; + text-transform: uppercase; + } + } + } + + td { + padding: 12px !important; + font-size: .95em; + text-align: left !important; + + &:first-of-type, + &:last-of-type { + max-width: 50px !important; + } + + .dash-cell-value { + display: block !important; + max-width: 500px; + overflow: hidden !important; + font-size: 1.2em; + font-weight: 300; + text-align: left; + text-overflow: ellipsis; + white-space: nowrap; + opacity: .8; + } + } + + td[data-dash-column="command"], + .column-1 { + max-width: 200px !important; + } + + td[data-dash-column="index"] { + text-align: center !important; + + div { + text-align: center !important; + } + } + + td[data-dash-column="index"] { + color: #939da4; + font-size: 1.1em; + font-weight: 500; + } + + tr:nth-child(even) { + background: #f5f8fc !important; + } +} + +table { + tbody { + box-shadow: 0 0 7px #bdbdd2 !important; + + tr { + border: 0 !important; + + &:nth-child(even) { + background: #e7eefa !important; + } + + td { + padding: 12px 10px !important; + overflow: hidden !important; + border: 0 !important; + font-size: 0.65em !important; + line-height: 1.25 !important; + text-align: center !important; + } + } + } +} + +.dash-spreadsheet-container .dash-spreadsheet-inner td.focused { + background-color: rgba(154, 212, 255, 0.2) !important; + box-shadow: 0 0 4px #cdd3e2; +} + +.dash-spreadsheet-container .dash-spreadsheet-inner td .input-cell-value-shadow { + width: 100%; +} + +.dash-spreadsheet-container .dash-spreadsheet-inner td input.dash-cell-value.unfocused { + caret-color: #317ed1 !important; +} + +.dash-spreadsheet-inner .input-active { + overflow: visible !important; + color: #829ab2; + text-align: left !important; +} + +.dash-spreadsheet-inner input { + &::placeholder { + color: grey; + } +} + +#histogram-graph { + margin-bottom: 30px; + padding-bottom: 176px !important; + overflow: hidden; + border-radius: 4px; + background: white; + box-shadow: 0 0 4px #cdd3e2; +} + +.main-svg { + overflow: visible !important; +} + +header .nav-wrapper { + width: 1200px !important; +} diff --git a/resources/tools/dash/app/pal/templates/index.jinja2 b/resources/tools/dash/app/pal/templates/index.jinja2 new file mode 100644 index 0000000000..8ab5c84356 --- /dev/null +++ b/resources/tools/dash/app/pal/templates/index.jinja2 @@ -0,0 +1,13 @@ +{% extends "layout.jinja2" %} + +{% block content %} +
+ +

{{ title }}

+

{{ description }}

+ + trending + + +
+{% endblock %} diff --git a/resources/tools/dash/app/pal/templates/layout.jinja2 b/resources/tools/dash/app/pal/templates/layout.jinja2 new file mode 100644 index 0000000000..59abba36e3 --- /dev/null +++ b/resources/tools/dash/app/pal/templates/layout.jinja2 @@ -0,0 +1,33 @@ + + + + + + + {{ title }} + + + + + + + + + + + + + + + +
+ {% block content %}{% endblock %} +
+ + + diff --git a/resources/tools/dash/app/pal/trending/dashboard.py b/resources/tools/dash/app/pal/trending/dashboard.py new file mode 100644 index 0000000000..ee5ea5123f --- /dev/null +++ b/resources/tools/dash/app/pal/trending/dashboard.py @@ -0,0 +1,69 @@ +# 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 a Dash app.""" +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 html_layout + + +def init_dashboard(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="/trending/", + external_stylesheets=[ + "/static/dist/css/styles.css", + "https://fonts.googleapis.com/css?family=Lato", + ], + ) + + # Load DataFrame + df = create_dataframe() + + # Custom HTML layout + dash_app.index_string = html_layout + + # Create Layout + dash_app.layout = html.Div( + children=[ + create_data_table(df), + ], + id="dash-container", + ) + return dash_app.server + + +def create_data_table(df): + """Create Dash datatable from Pandas DataFrame.""" + 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, + ) + return table diff --git a/resources/tools/dash/app/pal/trending/data.py b/resources/tools/dash/app/pal/trending/data.py new file mode 100644 index 0000000000..06466a9175 --- /dev/null +++ b/resources/tools/dash/app/pal/trending/data.py @@ -0,0 +1,24 @@ +# 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( + "https://s3-docs.fd.io/csit/master/trending/_static/vpp/" + "csit-vpp-perf-mrr-daily-master-2n-zn2-trending.csv" + ) diff --git a/resources/tools/dash/app/pal/trending/layout.py b/resources/tools/dash/app/pal/trending/layout.py new file mode 100644 index 0000000000..1f60aecd83 --- /dev/null +++ b/resources/tools/dash/app/pal/trending/layout.py @@ -0,0 +1,43 @@ +# 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 = """ + + + + {%metas%} + {%title%} + {%favicon%} + {%css%} + + +
+ +
+ {%app_entry%} + + + +""" diff --git a/resources/tools/dash/app/requirements.txt b/resources/tools/dash/app/requirements.txt new file mode 100644 index 0000000000..90e595e81c --- /dev/null +++ b/resources/tools/dash/app/requirements.txt @@ -0,0 +1,35 @@ +attrs==21.2.0 +Brotli==1.0.9 +click==8.0.3 +dash==2.0.0 +dash-core-components==2.0.0 +dash-html-components==2.0.0 +dash-renderer==1.9.1 +dash-table==5.0.0 +Flask==2.0.2 +Flask-Assets==2.0 +Flask-Compress==1.10.1 +future==0.18.2 +intervaltree==3.1.0 +itsdangerous==2.0.1 +Jinja2==3.0.3 +MarkupSafe==2.0.1 +numpy==1.21.4 +packaging==21.3 +pandas==1.3.5 +pip==21.2.4 +plotly==5.4.0 +protobuf==3.19.1 +pyparsing==3.0.6 +python-dateutil==2.8.2 +python-dotenv==0.19.2 +pytz==2022.3 +retrying==1.3.3 +setuptools==57.5.0 +six==1.16.0 +sortedcontainers==2.4.0 +tenacity==8.0.1 +uWSGI==2.0.20 +webassets==2.0 +Werkzeug==2.0.2 +wheel==0.37.0 \ No newline at end of file diff --git a/resources/tools/dash/app/wsgi.py b/resources/tools/dash/app/wsgi.py new file mode 100644 index 0000000000..c2832e29eb --- /dev/null +++ b/resources/tools/dash/app/wsgi.py @@ -0,0 +1,20 @@ +# 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. + +from pal import init_app + +app = init_app() + +if __name__ == "__main__": + # Main entry point. + app.run(host="0.0.0.0") diff --git a/resources/tools/dash/docker-compose.yaml b/resources/tools/dash/docker-compose.yaml new file mode 100644 index 0000000000..335bcdaf94 --- /dev/null +++ b/resources/tools/dash/docker-compose.yaml @@ -0,0 +1,19 @@ +version: "3" +services: + dash: + build: "." + command: "uwsgi app.ini" + environment: + AWS_ACCESS_KEY_ID: "" + AWS_SECRET_ACCESS_KEY: "" + FLASK_APP: "wsgi.py" + FLASK_ENV: "production" + LESS_BIN: "/usr/local/bin/lessc" + ASSETS_DEBUG: "False" + LESS_RUN_IN_DEBUG: "False" + COMPRESSOR_DEBUG: "True" + ports: + - "5000:5000" + volumes: + - "./app/:/app" + working_dir: "/app" -- 2.16.6