aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPeter Mikus <pmikus@cisco.com>2022-01-10 13:19:31 +0100
committerTibor Frank <tifrank@cisco.com>2022-01-11 14:09:38 +0100
commita5e8351152d9513811e2d6d9eaaf2180e35e002d (patch)
tree83ee473fc391aff83869706345acf740eba22cd9
parentfd5600f3441d99f4cc9643e3a0da99c850b80a5c (diff)
feat(uti): Dash demo
- displays the table with trending data downloaded from trending pages Signed-off-by: Peter Mikus <pmikus@cisco.com> Change-Id: Ic0d48290105ccd846c7de9ee4d8acb78e0b72f00
-rw-r--r--resources/tools/dash/Dockerfile12
-rw-r--r--resources/tools/dash/app/app.ini17
-rw-r--r--resources/tools/dash/app/config.py35
-rw-r--r--resources/tools/dash/app/pal/__init__.py39
-rw-r--r--resources/tools/dash/app/pal/assets.py38
-rw-r--r--resources/tools/dash/app/pal/routes.py27
-rw-r--r--resources/tools/dash/app/pal/static/dist/css/styles.css1
-rw-r--r--resources/tools/dash/app/pal/static/dist/img/favicon.svg348
-rw-r--r--resources/tools/dash/app/pal/static/img/logo.svg348
-rw-r--r--resources/tools/dash/app/pal/static/less/global.less12
-rw-r--r--resources/tools/dash/app/pal/static/less/header.less33
-rw-r--r--resources/tools/dash/app/pal/static/less/home.less64
-rw-r--r--resources/tools/dash/app/pal/static/less/table.less256
-rw-r--r--resources/tools/dash/app/pal/templates/index.jinja213
-rw-r--r--resources/tools/dash/app/pal/templates/layout.jinja233
-rw-r--r--resources/tools/dash/app/pal/trending/dashboard.py69
-rw-r--r--resources/tools/dash/app/pal/trending/data.py24
-rw-r--r--resources/tools/dash/app/pal/trending/layout.py43
-rw-r--r--resources/tools/dash/app/requirements.txt35
-rw-r--r--resources/tools/dash/app/wsgi.py20
-rw-r--r--resources/tools/dash/docker-compose.yaml19
21 files changed, 1486 insertions, 0 deletions
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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 1000 568.31" style="enable-background:new 0 0 1000 568.31;" xml:space="preserve">
+<style type="text/css">
+ .st0{fill:#1DCAD3;}
+ .st1{fill:#36B0C9;}
+ .st2{fill:#231F20;}
+ .st3{fill:#FFFFFF;}
+ .st4{fill:#9164CC;}
+ .st5{clip-path:url(#SVGID_2_);fill:url(#SVGID_3_);}
+ .st6{fill:#201747;}
+ .st7{fill-rule:evenodd;clip-rule:evenodd;fill:#10CFC9;}
+ .st8{clip-path:url(#SVGID_5_);fill:#231F20;}
+ .st9{fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;}
+ .st10{clip-path:url(#SVGID_7_);fill:#FFFFFF;}
+ .st11{fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;}
+ .st12{fill:#8CCEAF;}
+ .st13{fill:#008476;}
+ .st14{fill:#25BCBD;}
+ .st15{fill:#004D70;}
+ .st16{fill:#20BBBB;}
+ .st17{fill:#024D70;}
+ .st18{fill-rule:evenodd;clip-rule:evenodd;fill:#F58B1F;}
+ .st19{fill-rule:evenodd;clip-rule:evenodd;fill:#18335B;}
+ .st20{clip-path:url(#SVGID_9_);}
+ .st21{clip-path:url(#SVGID_11_);}
+ .st22{fill:#18335B;}
+ .st23{fill:#F58B1F;}
+ .st24{clip-path:url(#SVGID_15_);}
+ .st25{clip-path:url(#SVGID_17_);}
+ .st26{clip-path:url(#SVGID_21_);}
+ .st27{clip-path:url(#SVGID_23_);}
+ .st28{clip-path:url(#SVGID_27_);}
+ .st29{clip-path:url(#SVGID_29_);}
+ .st30{clip-path:url(#SVGID_33_);}
+ .st31{clip-path:url(#SVGID_35_);}
+ .st32{clip-path:url(#SVGID_39_);}
+ .st33{clip-path:url(#SVGID_41_);}
+ .st34{fill:#416BA9;}
+ .st35{fill:#73C3D5;}
+ .st36{opacity:0.8;}
+ .st37{fill:#3A3A3A;}
+ .st38{fill:url(#SVGID_44_);}
+ .st39{fill:none;stroke:#000000;stroke-width:6.3384;}
+ .st40{fill:none;stroke:#000000;stroke-width:3.1692;}
+ .st41{fill:#48494B;}
+ .st42{fill:#C1986C;}
+ .st43{fill:url(#SVGID_63_);}
+ .st44{fill:url(#SVGID_64_);}
+ .st45{fill:url(#SVGID_65_);}
+ .st46{fill:url(#SVGID_66_);}
+ .st47{fill:url(#SVGID_67_);}
+ .st48{fill:#4D4E4E;}
+ .st49{fill:#27B373;}
+ .st50{fill:#5DC4CD;}
+ .st51{fill:#1E8756;}
+ .st52{fill:#3D1152;}
+ .st53{fill:#922C48;}
+ .st54{fill-rule:evenodd;clip-rule:evenodd;fill:#922C48;}
+ .st55{fill:#404041;}
+ .st56{fill:#EC1C24;}
+ .st57{fill:#373A36;}
+ .st58{fill:#808184;}
+ .st59{fill:#262261;}
+ .st60{fill:#6FCBDC;}
+ .st61{fill:#2F3436;}
+ .st62{fill:#5F97D0;}
+ .st63{fill:#132428;}
+ .st64{fill:#85C041;}
+ .st65{fill:#677784;}
+ .st66{fill:url(#SVGID_68_);}
+ .st67{opacity:0.2;clip-path:url(#SVGID_70_);}
+ .st68{fill:#FFFEFA;}
+ .st69{opacity:0.1;}
+ .st70{fill:url(#SVGID_71_);}
+ .st71{opacity:0.3;}
+ .st72{opacity:0.08;}
+ .st73{opacity:0.1;fill:url(#Wordmark_1_);}
+ .st74{fill:url(#SVGID_104_);}
+ .st75{opacity:0.6;fill:url(#SVGID_107_);}
+ .st76{opacity:0.4;}
+ .st77{fill:url(#SVGID_110_);}
+ .st78{opacity:0.6;fill:url(#SVGID_113_);}
+ .st79{fill:url(#SVGID_116_);}
+ .st80{opacity:0.6;fill:url(#SVGID_119_);}
+ .st81{fill:url(#SVGID_122_);}
+ .st82{opacity:0.6;fill:url(#SVGID_125_);}
+ .st83{fill:url(#SVGID_128_);}
+ .st84{opacity:0.6;fill:url(#SVGID_131_);}
+ .st85{fill:#221F1F;}
+ .st86{fill:none;}
+ .st87{fill:#00416B;}
+ .st88{opacity:0.8;fill:url(#XMLID_323_);}
+ .st89{fill:#4197CB;}
+ .st90{fill:#003E52;}
+ .st91{fill:#3F96B4;}
+ .st92{fill:#B9DBE5;}
+ .st93{opacity:0.3;fill:#231F20;}
+ .st94{opacity:0.3;fill:#FFFFFF;}
+ .st95{fill:#050013;}
+ .st96{fill:#E87200;}
+ .st97{fill:#FCB813;}
+ .st98{fill:#3D3935;}
+ .st99{fill:#FFB600;}
+ .st100{fill:#FCB814;}
+ .st101{fill:#F48120;}
+ .st102{fill:#EF4E25;}
+ .st103{fill:#ED3024;}
+ .st104{fill:#E0592A;}
+ .st105{fill:#00ADBB;}
+ .st106{fill:#00829B;}
+ .st107{fill:#93D500;}
+ .st108{fill:#4D5A31;}
+ .st109{fill:#6BA43A;}
+ .st110{fill:#424143;}
+ .st111{fill-rule:evenodd;clip-rule:evenodd;fill:#C7E6B4;}
+ .st112{fill-rule:evenodd;clip-rule:evenodd;fill:#5A9891;}
+ .st113{fill-rule:evenodd;clip-rule:evenodd;fill:#127870;}
+ .st114{fill-rule:evenodd;clip-rule:evenodd;fill:#5CCFD5;}
+ .st115{fill-rule:evenodd;clip-rule:evenodd;fill:#ACD5CD;}
+ .st116{fill-rule:evenodd;clip-rule:evenodd;fill:#B5ECC9;}
+ .st117{fill-rule:evenodd;clip-rule:evenodd;fill:#A1D683;}
+ .st118{fill-rule:evenodd;clip-rule:evenodd;fill:#DEF0D3;}
+ .st119{fill-rule:evenodd;clip-rule:evenodd;fill:#91B9B4;}
+ .st120{fill-rule:evenodd;clip-rule:evenodd;fill:#006860;}
+ .st121{fill-rule:evenodd;clip-rule:evenodd;fill:#00ADBB;}
+ .st122{fill-rule:evenodd;clip-rule:evenodd;fill:#B4E7E9;}
+ .st123{fill-rule:evenodd;clip-rule:evenodd;fill:#007565;}
+ .st124{fill-rule:evenodd;clip-rule:evenodd;fill:#00CE7C;}
+ .st125{fill-rule:evenodd;clip-rule:evenodd;fill:#5FD896;}
+ .st126{fill:#007DA5;}
+ .st127{fill:#313032;}
+ .st128{fill:#24272A;}
+ .st129{fill:#00AFAA;}
+ .st130{fill:#66C9BA;}
+ .st131{fill:#0069A7;}
+ .st132{fill:#002F87;}
+ .st133{fill:#8BC53F;}
+ .st134{fill:#1A1A1A;}
+ .st135{fill:#0095D6;}
+ .st136{fill:#003F5F;}
+ .st137{fill:#2D317C;}
+ .st138{fill:#41BFBF;}
+ .st139{fill:#293C97;}
+ .st140{fill:#52C2BD;}
+ .st141{fill:url(#SVGID_134_);}
+ .st142{fill:url(#SVGID_135_);}
+ .st143{fill:url(#SVGID_136_);}
+ .st144{fill:#0DBEEA;}
+ .st145{fill:#097EC2;}
+ .st146{fill:#133C63;}
+ .st147{fill:#3B91CF;}
+ .st148{fill:#C8DEE8;}
+ .st149{fill:#629BBA;}
+ .st150{fill:#F8BE19;}
+ .st151{fill:url(#SVGID_137_);}
+ .st152{fill:url(#SVGID_138_);}
+ .st153{fill:url(#SVGID_139_);}
+ .st154{fill:#00233B;}
+ .st155{fill:url(#SVGID_140_);}
+ .st156{fill:url(#SVGID_141_);}
+ .st157{fill:url(#SVGID_142_);}
+ .st158{fill:url(#SVGID_143_);}
+ .st159{fill:url(#SVGID_144_);}
+ .st160{fill:url(#SVGID_145_);}
+ .st161{fill:url(#SVGID_146_);}
+ .st162{fill:url(#SVGID_147_);}
+ .st163{fill:url(#SVGID_148_);}
+ .st164{fill:url(#SVGID_149_);}
+ .st165{fill:url(#SVGID_150_);}
+ .st166{fill:url(#SVGID_151_);}
+ .st167{fill:url(#SVGID_152_);}
+ .st168{fill:url(#SVGID_153_);}
+ .st169{fill:url(#SVGID_154_);}
+ .st170{fill:url(#SVGID_155_);}
+ .st171{fill:url(#SVGID_156_);}
+ .st172{fill:url(#SVGID_157_);}
+ .st173{fill:url(#SVGID_158_);}
+ .st174{fill:url(#SVGID_159_);}
+ .st175{fill:url(#SVGID_160_);}
+ .st176{fill:url(#SVGID_161_);}
+ .st177{fill:url(#SVGID_162_);}
+ .st178{fill:url(#SVGID_163_);}
+ .st179{fill:url(#SVGID_164_);}
+ .st180{fill:url(#SVGID_165_);}
+ .st181{fill:url(#SVGID_166_);}
+ .st182{fill:url(#SVGID_167_);}
+ .st183{fill:url(#SVGID_168_);}
+ .st184{fill:url(#SVGID_169_);}
+ .st185{fill:url(#SVGID_170_);}
+ .st186{fill:url(#SVGID_171_);}
+ .st187{fill:url(#SVGID_172_);}
+ .st188{fill:url(#SVGID_173_);}
+ .st189{fill:url(#SVGID_174_);}
+ .st190{fill:url(#SVGID_175_);}
+ .st191{fill:url(#SVGID_176_);}
+ .st192{fill:url(#SVGID_177_);}
+ .st193{fill:url(#SVGID_178_);}
+ .st194{fill:#C31230;}
+ .st195{fill:#807F82;}
+ .st196{fill-rule:evenodd;clip-rule:evenodd;fill:#C31230;}
+ .st197{fill-rule:evenodd;clip-rule:evenodd;fill:#807F82;}
+ .st198{fill:#2D2D2D;}
+ .st199{display:none;fill:#2D2D2D;}
+ .st200{fill:#D11F3C;}
+ .st201{fill:#E42C4C;stroke:#E42C4C;stroke-width:1.0503;stroke-miterlimit:10;}
+ .st202{display:none;fill:#231F20;}
+ .st203{display:none;fill:#FFFFFF;}
+ .st204{fill:#FF7F30;}
+ .st205{opacity:0.3;fill:#FF7F30;}
+ .st206{opacity:0.6;fill:#FF7F30;}
+ .st207{opacity:0.7;fill:#FF7F30;}
+ .st208{fill:#221C35;}
+ .st209{fill:#1B98D5;}
+ .st210{fill:#173963;}
+ .st211{fill:#009ADE;}
+ .st212{fill:#003764;}
+ .st213{fill:#2A7DE1;}
+ .st214{opacity:0.4;clip-path:url(#XMLID_324_);fill:#221F1F;}
+ .st215{fill:#002A3A;}
+ .st216{fill:#0033A1;}
+ .st217{fill:url(#SVGID_179_);}
+ .st218{fill:url(#SVGID_180_);}
+ .st219{fill:url(#SVGID_181_);}
+ .st220{fill:url(#SVGID_182_);}
+ .st221{fill:#007EC4;}
+ .st222{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_183_);}
+ .st223{fill-rule:evenodd;clip-rule:evenodd;fill:#E6E7E8;}
+ .st224{fill:#009345;}
+ .st225{fill:#BBBCB8;}
+ .st226{fill:#72C0EB;}
+ .st227{fill:#939598;}
+ .st228{fill-rule:evenodd;clip-rule:evenodd;fill:#2CB8EB;}
+ .st229{fill:#2CB8EB;}
+ .st230{fill:#81B83A;}
+ .st231{fill-rule:evenodd;clip-rule:evenodd;fill:#81B83A;}
+ .st232{enable-background:new ;}
+ .st233{fill:#FF6F3E;}
+ .st234{fill:#12143D;}
+ .st235{fill:url(#SVGID_184_);}
+ .st236{fill:url(#SVGID_185_);}
+ .st237{fill:url(#SVGID_186_);}
+ .st238{fill:url(#SVGID_187_);}
+ .st239{fill:url(#SVGID_188_);}
+ .st240{fill:url(#SVGID_189_);}
+ .st241{fill:url(#SVGID_190_);}
+ .st242{fill:url(#SVGID_191_);}
+ .st243{fill:url(#SVGID_192_);}
+ .st244{fill:#7C51A0;}
+ .st245{fill:#9F66A9;}
+ .st246{fill:#9F80B9;}
+ .st247{fill:url(#SVGID_193_);}
+ .st248{fill:url(#SVGID_194_);}
+ .st249{fill:url(#SVGID_195_);}
+ .st250{fill:url(#SVGID_196_);}
+ .st251{fill:#2D3136;}
+ .st252{fill:#76777A;}
+ .st253{fill:#A7A8A9;}
+ .st254{fill:#0082CA;}
+ .st255{fill:#FFB259;}
+ .st256{fill:#385CAD;}
+ .st257{fill:#7BA0C4;}
+ .st258{fill:#EBA900;}
+ .st259{fill:#929497;}
+ .st260{opacity:0.7;fill:#FFFFFF;}
+ .st261{fill:#016BAF;}
+ .st262{fill:#343432;}
+ .st263{fill:#6D6E70;}
+ .st264{fill:#F4B01B;}
+ .st265{fill:#293271;}
+ .st266{fill:#A1D33C;}
+ .st267{fill:#212322;}
+ .st268{fill:#0047BA;}
+ .st269{fill:#969CDE;}
+ .st270{fill:#047BC1;}
+ .st271{fill:url(#SVGID_197_);}
+ .st272{fill:url(#SVGID_198_);}
+ .st273{fill:url(#SVGID_199_);}
+ .st274{fill:url(#SVGID_200_);}
+ .st275{fill:url(#SVGID_201_);}
+ .st276{fill:url(#SVGID_202_);}
+ .st277{fill:url(#SVGID_203_);}
+ .st278{fill:#13517C;}
+ .st279{fill:#0077A6;}
+ .st280{fill:none;stroke:#231F20;stroke-width:5.9036;stroke-miterlimit:10;}
+ .st281{fill:#00A94F;}
+ .st282{fill:none;stroke:#231F20;stroke-width:3.2172;stroke-miterlimit:10;}
+ .st283{fill:#59595C;}
+ .st284{opacity:0.349;fill:#F9AE19;}
+ .st285{opacity:0.349;fill:#E99F22;}
+ .st286{opacity:0.349;fill:#E47D25;}
+ .st287{fill:#F9AE19;}
+ .st288{fill:#E99F22;}
+ .st289{fill:#F09B20;}
+ .st290{fill:#E47D25;}
+ .st291{fill:#E89223;}
+ .st292{opacity:0.651;fill:#F9AE19;}
+ .st293{fill:#E68825;}
+ .st294{opacity:0.651;fill:#E99F22;}
+ .st295{fill:#EB8D23;}
+ .st296{opacity:0.7725;fill:#EF9B21;}
+ .st297{opacity:0.651;fill:#E47D25;}
+ .st298{opacity:0.7725;fill:#EA9622;}
+ .st299{fill:url(#SVGID_204_);}
+ .st300{fill:#55575B;}
+ .st301{fill:#EE424E;}
+ .st302{fill:#34424B;}
+</style>
+<g>
+ <g>
+ <path class="st55" d="M772.88,526c9.95,0,15.7,5.53,15.7,15.48c0,10.17-5.75,15.48-15.7,15.48c-9.95,0-15.48-5.31-15.48-15.48
+ C757.4,531.53,762.93,526,772.88,526z"/>
+ <path class="st55" d="M832.94,393.35c8.18,0,13.71,3.32,13.71,12.38c0,9.29-5.53,12.6-13.71,12.6c-8.4,0-14.15-3.32-14.15-12.6
+ C818.79,396.67,824.54,393.35,832.94,393.35z M821.22,438.67h22.99V554.3h-22.99V438.67z"/>
+ <path class="st55" d="M934.56,435.58c36.25,0,61.9,26.09,61.9,61.24c0,34.71-25.65,60.58-61.9,60.58
+ c-35.82,0-61.69-25.87-61.69-60.58C872.88,461.67,898.75,435.58,934.56,435.58z M934.56,536.18c23.66,0,39.79-17.03,39.79-39.36
+ c0-22.77-16.14-40.02-39.79-40.02c-23.44,0-39.36,17.25-39.36,40.02C895.21,519.15,911.13,536.18,934.56,536.18z"/>
+ </g>
+ <g>
+ <path class="st56" d="M724.15,245.36c-0.97-8.4-17.24-16.23-17.24-16.23c-6.81-4.71-8.03-16.23-7.16-20.6
+ c0.87-4.36,15.01-36.66,15.01-36.66c11.52-11.35,31.6-35.44,33.87-48.88c1.07-6.31-3.14-38.93,5.41-49.75
+ c5.63-7.12,22.35-15.36,25.84-18.16c3.49-2.79,6.28-11,4.71-14.84c-1.57-3.84-27.41-4.02-27.41-4.02s-2.16-10.79-17.62-11.8
+ c-6.05-0.2-10.18,1.3-10.18,1.3c1.78-0.89,4.69-2.18,6.78-3.1c-0.54-3.56-1.89-11.45-3.24-11.72c-1.43-0.29-4.73,5.28-6.27,7.21
+ l0.28,3.42l-1.11-2.85l-1.63-4.2l-0.02-0.06h0c-0.64-1.74-1.36-3.37-2.08-3.53c-1.57-0.34-38.06,50.81-42.6,57.44
+ c-4.54,6.63-9.25,17.81-33.87,25.49c-17.37,5.42-53.43,6.32-81.13,15.16l0,0c-14.25,1.43-53.29,33.79-83.15,32.62
+ c-20.05-0.78-35.61-6.28-39.57-15.25c0,0,4.19,41.32,25.6,43.64c0,0,13.5,2.33,26.53-9.31c0,0-3.72,7.36-8.38,9.5
+ c0,0,18.07-4.62,29.06-12.94c3.37-7.06,9.22-17.4,17.46-25.17c0.53-0.52,0.86-0.8,0.86-0.8c-0.29,0.26-0.57,0.53-0.86,0.8
+ c-2.65,2.6-10.68,11.48-11.86,24.73c-0.24,2.64-0.21,5.44,0.2,8.41c0,8.9,9.66,20.78,9.66,26.19c0,7.16-16.76,20.25-17.81,23.74
+ c-0.58,1.95-2.35,16.91,0.69,29.77h-59.09c-56.8,0-78.68,56.8-78.68,56.8l-24.4,68h-10.52c-8.25,0-14.94,6.69-14.94,14.94
+ c0,8.19,6.59,14.81,14.75,14.92l-0.01,0.02h0.2h5.92c8.25,0,14.94,6.69,14.94,14.94c0,8.25-6.69,14.94-14.94,14.94H197.01
+ c-8.25,0-14.94,6.69-14.94,14.94c0,8.25,6.69,14.94,14.94,14.94h154.15c8.25,0,14.94,6.69,14.94,14.94
+ c0,8.25-6.69,14.94-14.94,14.94h-32.39H146.48h-32.39c-8.25,0-14.94,6.69-14.94,14.94c0,8.25,6.69,14.94,14.94,14.94h183.08
+ l-13.71,38.21h113.6l35.67-99.4h1.72c79.83,0,109.91-85.2,109.91-85.2h-81.05l15.29-42.6l88.87,0c56.8,0,83.71-85.2,83.71-85.2
+ H539.99c-2.48-4.72-5.03-9.43-5.03-13.01c0-18.5,49.93-30.55,48.36-60.4c-4.08-16.85,2.51-25.92,2.51-25.92
+ c-4.2,9.35,0.01,19.79,2.11,24c0.03,0,0.06,0,0.1,0c13.97,0,41.9,15.36,59.18,15.36c11.43,0,20.41-2.37,25.19-3.97
+ c0.69-3.96,0.82-14.29-14.36-20.96c0,0,18.37,0.16,18.15,19.52c0,0-2.09,31.42,0.7,54.29c0.55,4.48,1.7,8.11,3.22,11.09h-0.52
+ c-13.7,55.08-55.31,85.2-55.31,85.2h60.7l-51.92,142.02l-0.04-0.01H510.67c-56.8,0-85.2,85.2-85.2,85.2H601.9
+ c56.8,0,111.69,5.33,144.18-85.2l40.77-113.6C810.25,293.33,776.46,249.98,724.15,245.36z"/>
+ <path class="st56" d="M56.25,489.25H18.47c-8.25,0-14.94,6.69-14.94,14.94c0,8.25,6.69,14.94,14.94,14.94h37.78
+ c8.25,0,14.94-6.69,14.94-14.94C71.19,495.94,64.5,489.25,56.25,489.25z"/>
+ <path class="st56" d="M171.38,399.61h120.5c8.25,0,14.94-6.69,14.94-14.94c0-8.25-6.69-14.94-14.94-14.94H102.14
+ c-8.25,0-14.94,6.69-14.94,14.94c0,8.25,6.69,14.94,14.94,14.94H171.38z"/>
+ <path class="st56" d="M180.84,339.85h162.85c8.25,0,14.94-6.69,14.94-14.94c0-8.25-6.69-14.94-14.94-14.94H180.84
+ c-8.25,0-14.94,6.69-14.94,14.94C165.9,333.17,172.59,339.85,180.84,339.85z"/>
+ </g>
+</g>
+</svg>
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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 1000 568.31" style="enable-background:new 0 0 1000 568.31;" xml:space="preserve">
+<style type="text/css">
+ .st0{fill:#1DCAD3;}
+ .st1{fill:#36B0C9;}
+ .st2{fill:#231F20;}
+ .st3{fill:#FFFFFF;}
+ .st4{fill:#9164CC;}
+ .st5{clip-path:url(#SVGID_2_);fill:url(#SVGID_3_);}
+ .st6{fill:#201747;}
+ .st7{fill-rule:evenodd;clip-rule:evenodd;fill:#10CFC9;}
+ .st8{clip-path:url(#SVGID_5_);fill:#231F20;}
+ .st9{fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;}
+ .st10{clip-path:url(#SVGID_7_);fill:#FFFFFF;}
+ .st11{fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;}
+ .st12{fill:#8CCEAF;}
+ .st13{fill:#008476;}
+ .st14{fill:#25BCBD;}
+ .st15{fill:#004D70;}
+ .st16{fill:#20BBBB;}
+ .st17{fill:#024D70;}
+ .st18{fill-rule:evenodd;clip-rule:evenodd;fill:#F58B1F;}
+ .st19{fill-rule:evenodd;clip-rule:evenodd;fill:#18335B;}
+ .st20{clip-path:url(#SVGID_9_);}
+ .st21{clip-path:url(#SVGID_11_);}
+ .st22{fill:#18335B;}
+ .st23{fill:#F58B1F;}
+ .st24{clip-path:url(#SVGID_15_);}
+ .st25{clip-path:url(#SVGID_17_);}
+ .st26{clip-path:url(#SVGID_21_);}
+ .st27{clip-path:url(#SVGID_23_);}
+ .st28{clip-path:url(#SVGID_27_);}
+ .st29{clip-path:url(#SVGID_29_);}
+ .st30{clip-path:url(#SVGID_33_);}
+ .st31{clip-path:url(#SVGID_35_);}
+ .st32{clip-path:url(#SVGID_39_);}
+ .st33{clip-path:url(#SVGID_41_);}
+ .st34{fill:#416BA9;}
+ .st35{fill:#73C3D5;}
+ .st36{opacity:0.8;}
+ .st37{fill:#3A3A3A;}
+ .st38{fill:url(#SVGID_44_);}
+ .st39{fill:none;stroke:#000000;stroke-width:6.3384;}
+ .st40{fill:none;stroke:#000000;stroke-width:3.1692;}
+ .st41{fill:#48494B;}
+ .st42{fill:#C1986C;}
+ .st43{fill:url(#SVGID_63_);}
+ .st44{fill:url(#SVGID_64_);}
+ .st45{fill:url(#SVGID_65_);}
+ .st46{fill:url(#SVGID_66_);}
+ .st47{fill:url(#SVGID_67_);}
+ .st48{fill:#4D4E4E;}
+ .st49{fill:#27B373;}
+ .st50{fill:#5DC4CD;}
+ .st51{fill:#1E8756;}
+ .st52{fill:#3D1152;}
+ .st53{fill:#922C48;}
+ .st54{fill-rule:evenodd;clip-rule:evenodd;fill:#922C48;}
+ .st55{fill:#404041;}
+ .st56{fill:#EC1C24;}
+ .st57{fill:#373A36;}
+ .st58{fill:#808184;}
+ .st59{fill:#262261;}
+ .st60{fill:#6FCBDC;}
+ .st61{fill:#2F3436;}
+ .st62{fill:#5F97D0;}
+ .st63{fill:#132428;}
+ .st64{fill:#85C041;}
+ .st65{fill:#677784;}
+ .st66{fill:url(#SVGID_68_);}
+ .st67{opacity:0.2;clip-path:url(#SVGID_70_);}
+ .st68{fill:#FFFEFA;}
+ .st69{opacity:0.1;}
+ .st70{fill:url(#SVGID_71_);}
+ .st71{opacity:0.3;}
+ .st72{opacity:0.08;}
+ .st73{opacity:0.1;fill:url(#Wordmark_1_);}
+ .st74{fill:url(#SVGID_104_);}
+ .st75{opacity:0.6;fill:url(#SVGID_107_);}
+ .st76{opacity:0.4;}
+ .st77{fill:url(#SVGID_110_);}
+ .st78{opacity:0.6;fill:url(#SVGID_113_);}
+ .st79{fill:url(#SVGID_116_);}
+ .st80{opacity:0.6;fill:url(#SVGID_119_);}
+ .st81{fill:url(#SVGID_122_);}
+ .st82{opacity:0.6;fill:url(#SVGID_125_);}
+ .st83{fill:url(#SVGID_128_);}
+ .st84{opacity:0.6;fill:url(#SVGID_131_);}
+ .st85{fill:#221F1F;}
+ .st86{fill:none;}
+ .st87{fill:#00416B;}
+ .st88{opacity:0.8;fill:url(#XMLID_323_);}
+ .st89{fill:#4197CB;}
+ .st90{fill:#003E52;}
+ .st91{fill:#3F96B4;}
+ .st92{fill:#B9DBE5;}
+ .st93{opacity:0.3;fill:#231F20;}
+ .st94{opacity:0.3;fill:#FFFFFF;}
+ .st95{fill:#050013;}
+ .st96{fill:#E87200;}
+ .st97{fill:#FCB813;}
+ .st98{fill:#3D3935;}
+ .st99{fill:#FFB600;}
+ .st100{fill:#FCB814;}
+ .st101{fill:#F48120;}
+ .st102{fill:#EF4E25;}
+ .st103{fill:#ED3024;}
+ .st104{fill:#E0592A;}
+ .st105{fill:#00ADBB;}
+ .st106{fill:#00829B;}
+ .st107{fill:#93D500;}
+ .st108{fill:#4D5A31;}
+ .st109{fill:#6BA43A;}
+ .st110{fill:#424143;}
+ .st111{fill-rule:evenodd;clip-rule:evenodd;fill:#C7E6B4;}
+ .st112{fill-rule:evenodd;clip-rule:evenodd;fill:#5A9891;}
+ .st113{fill-rule:evenodd;clip-rule:evenodd;fill:#127870;}
+ .st114{fill-rule:evenodd;clip-rule:evenodd;fill:#5CCFD5;}
+ .st115{fill-rule:evenodd;clip-rule:evenodd;fill:#ACD5CD;}
+ .st116{fill-rule:evenodd;clip-rule:evenodd;fill:#B5ECC9;}
+ .st117{fill-rule:evenodd;clip-rule:evenodd;fill:#A1D683;}
+ .st118{fill-rule:evenodd;clip-rule:evenodd;fill:#DEF0D3;}
+ .st119{fill-rule:evenodd;clip-rule:evenodd;fill:#91B9B4;}
+ .st120{fill-rule:evenodd;clip-rule:evenodd;fill:#006860;}
+ .st121{fill-rule:evenodd;clip-rule:evenodd;fill:#00ADBB;}
+ .st122{fill-rule:evenodd;clip-rule:evenodd;fill:#B4E7E9;}
+ .st123{fill-rule:evenodd;clip-rule:evenodd;fill:#007565;}
+ .st124{fill-rule:evenodd;clip-rule:evenodd;fill:#00CE7C;}
+ .st125{fill-rule:evenodd;clip-rule:evenodd;fill:#5FD896;}
+ .st126{fill:#007DA5;}
+ .st127{fill:#313032;}
+ .st128{fill:#24272A;}
+ .st129{fill:#00AFAA;}
+ .st130{fill:#66C9BA;}
+ .st131{fill:#0069A7;}
+ .st132{fill:#002F87;}
+ .st133{fill:#8BC53F;}
+ .st134{fill:#1A1A1A;}
+ .st135{fill:#0095D6;}
+ .st136{fill:#003F5F;}
+ .st137{fill:#2D317C;}
+ .st138{fill:#41BFBF;}
+ .st139{fill:#293C97;}
+ .st140{fill:#52C2BD;}
+ .st141{fill:url(#SVGID_134_);}
+ .st142{fill:url(#SVGID_135_);}
+ .st143{fill:url(#SVGID_136_);}
+ .st144{fill:#0DBEEA;}
+ .st145{fill:#097EC2;}
+ .st146{fill:#133C63;}
+ .st147{fill:#3B91CF;}
+ .st148{fill:#C8DEE8;}
+ .st149{fill:#629BBA;}
+ .st150{fill:#F8BE19;}
+ .st151{fill:url(#SVGID_137_);}
+ .st152{fill:url(#SVGID_138_);}
+ .st153{fill:url(#SVGID_139_);}
+ .st154{fill:#00233B;}
+ .st155{fill:url(#SVGID_140_);}
+ .st156{fill:url(#SVGID_141_);}
+ .st157{fill:url(#SVGID_142_);}
+ .st158{fill:url(#SVGID_143_);}
+ .st159{fill:url(#SVGID_144_);}
+ .st160{fill:url(#SVGID_145_);}
+ .st161{fill:url(#SVGID_146_);}
+ .st162{fill:url(#SVGID_147_);}
+ .st163{fill:url(#SVGID_148_);}
+ .st164{fill:url(#SVGID_149_);}
+ .st165{fill:url(#SVGID_150_);}
+ .st166{fill:url(#SVGID_151_);}
+ .st167{fill:url(#SVGID_152_);}
+ .st168{fill:url(#SVGID_153_);}
+ .st169{fill:url(#SVGID_154_);}
+ .st170{fill:url(#SVGID_155_);}
+ .st171{fill:url(#SVGID_156_);}
+ .st172{fill:url(#SVGID_157_);}
+ .st173{fill:url(#SVGID_158_);}
+ .st174{fill:url(#SVGID_159_);}
+ .st175{fill:url(#SVGID_160_);}
+ .st176{fill:url(#SVGID_161_);}
+ .st177{fill:url(#SVGID_162_);}
+ .st178{fill:url(#SVGID_163_);}
+ .st179{fill:url(#SVGID_164_);}
+ .st180{fill:url(#SVGID_165_);}
+ .st181{fill:url(#SVGID_166_);}
+ .st182{fill:url(#SVGID_167_);}
+ .st183{fill:url(#SVGID_168_);}
+ .st184{fill:url(#SVGID_169_);}
+ .st185{fill:url(#SVGID_170_);}
+ .st186{fill:url(#SVGID_171_);}
+ .st187{fill:url(#SVGID_172_);}
+ .st188{fill:url(#SVGID_173_);}
+ .st189{fill:url(#SVGID_174_);}
+ .st190{fill:url(#SVGID_175_);}
+ .st191{fill:url(#SVGID_176_);}
+ .st192{fill:url(#SVGID_177_);}
+ .st193{fill:url(#SVGID_178_);}
+ .st194{fill:#C31230;}
+ .st195{fill:#807F82;}
+ .st196{fill-rule:evenodd;clip-rule:evenodd;fill:#C31230;}
+ .st197{fill-rule:evenodd;clip-rule:evenodd;fill:#807F82;}
+ .st198{fill:#2D2D2D;}
+ .st199{display:none;fill:#2D2D2D;}
+ .st200{fill:#D11F3C;}
+ .st201{fill:#E42C4C;stroke:#E42C4C;stroke-width:1.0503;stroke-miterlimit:10;}
+ .st202{display:none;fill:#231F20;}
+ .st203{display:none;fill:#FFFFFF;}
+ .st204{fill:#FF7F30;}
+ .st205{opacity:0.3;fill:#FF7F30;}
+ .st206{opacity:0.6;fill:#FF7F30;}
+ .st207{opacity:0.7;fill:#FF7F30;}
+ .st208{fill:#221C35;}
+ .st209{fill:#1B98D5;}
+ .st210{fill:#173963;}
+ .st211{fill:#009ADE;}
+ .st212{fill:#003764;}
+ .st213{fill:#2A7DE1;}
+ .st214{opacity:0.4;clip-path:url(#XMLID_324_);fill:#221F1F;}
+ .st215{fill:#002A3A;}
+ .st216{fill:#0033A1;}
+ .st217{fill:url(#SVGID_179_);}
+ .st218{fill:url(#SVGID_180_);}
+ .st219{fill:url(#SVGID_181_);}
+ .st220{fill:url(#SVGID_182_);}
+ .st221{fill:#007EC4;}
+ .st222{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_183_);}
+ .st223{fill-rule:evenodd;clip-rule:evenodd;fill:#E6E7E8;}
+ .st224{fill:#009345;}
+ .st225{fill:#BBBCB8;}
+ .st226{fill:#72C0EB;}
+ .st227{fill:#939598;}
+ .st228{fill-rule:evenodd;clip-rule:evenodd;fill:#2CB8EB;}
+ .st229{fill:#2CB8EB;}
+ .st230{fill:#81B83A;}
+ .st231{fill-rule:evenodd;clip-rule:evenodd;fill:#81B83A;}
+ .st232{enable-background:new ;}
+ .st233{fill:#FF6F3E;}
+ .st234{fill:#12143D;}
+ .st235{fill:url(#SVGID_184_);}
+ .st236{fill:url(#SVGID_185_);}
+ .st237{fill:url(#SVGID_186_);}
+ .st238{fill:url(#SVGID_187_);}
+ .st239{fill:url(#SVGID_188_);}
+ .st240{fill:url(#SVGID_189_);}
+ .st241{fill:url(#SVGID_190_);}
+ .st242{fill:url(#SVGID_191_);}
+ .st243{fill:url(#SVGID_192_);}
+ .st244{fill:#7C51A0;}
+ .st245{fill:#9F66A9;}
+ .st246{fill:#9F80B9;}
+ .st247{fill:url(#SVGID_193_);}
+ .st248{fill:url(#SVGID_194_);}
+ .st249{fill:url(#SVGID_195_);}
+ .st250{fill:url(#SVGID_196_);}
+ .st251{fill:#2D3136;}
+ .st252{fill:#76777A;}
+ .st253{fill:#A7A8A9;}
+ .st254{fill:#0082CA;}
+ .st255{fill:#FFB259;}
+ .st256{fill:#385CAD;}
+ .st257{fill:#7BA0C4;}
+ .st258{fill:#EBA900;}
+ .st259{fill:#929497;}
+ .st260{opacity:0.7;fill:#FFFFFF;}
+ .st261{fill:#016BAF;}
+ .st262{fill:#343432;}
+ .st263{fill:#6D6E70;}
+ .st264{fill:#F4B01B;}
+ .st265{fill:#293271;}
+ .st266{fill:#A1D33C;}
+ .st267{fill:#212322;}
+ .st268{fill:#0047BA;}
+ .st269{fill:#969CDE;}
+ .st270{fill:#047BC1;}
+ .st271{fill:url(#SVGID_197_);}
+ .st272{fill:url(#SVGID_198_);}
+ .st273{fill:url(#SVGID_199_);}
+ .st274{fill:url(#SVGID_200_);}
+ .st275{fill:url(#SVGID_201_);}
+ .st276{fill:url(#SVGID_202_);}
+ .st277{fill:url(#SVGID_203_);}
+ .st278{fill:#13517C;}
+ .st279{fill:#0077A6;}
+ .st280{fill:none;stroke:#231F20;stroke-width:5.9036;stroke-miterlimit:10;}
+ .st281{fill:#00A94F;}
+ .st282{fill:none;stroke:#231F20;stroke-width:3.2172;stroke-miterlimit:10;}
+ .st283{fill:#59595C;}
+ .st284{opacity:0.349;fill:#F9AE19;}
+ .st285{opacity:0.349;fill:#E99F22;}
+ .st286{opacity:0.349;fill:#E47D25;}
+ .st287{fill:#F9AE19;}
+ .st288{fill:#E99F22;}
+ .st289{fill:#F09B20;}
+ .st290{fill:#E47D25;}
+ .st291{fill:#E89223;}
+ .st292{opacity:0.651;fill:#F9AE19;}
+ .st293{fill:#E68825;}
+ .st294{opacity:0.651;fill:#E99F22;}
+ .st295{fill:#EB8D23;}
+ .st296{opacity:0.7725;fill:#EF9B21;}
+ .st297{opacity:0.651;fill:#E47D25;}
+ .st298{opacity:0.7725;fill:#EA9622;}
+ .st299{fill:url(#SVGID_204_);}
+ .st300{fill:#55575B;}
+ .st301{fill:#EE424E;}
+ .st302{fill:#34424B;}
+</style>
+<g>
+ <g>
+ <path class="st55" d="M772.88,526c9.95,0,15.7,5.53,15.7,15.48c0,10.17-5.75,15.48-15.7,15.48c-9.95,0-15.48-5.31-15.48-15.48
+ C757.4,531.53,762.93,526,772.88,526z"/>
+ <path class="st55" d="M832.94,393.35c8.18,0,13.71,3.32,13.71,12.38c0,9.29-5.53,12.6-13.71,12.6c-8.4,0-14.15-3.32-14.15-12.6
+ C818.79,396.67,824.54,393.35,832.94,393.35z M821.22,438.67h22.99V554.3h-22.99V438.67z"/>
+ <path class="st55" d="M934.56,435.58c36.25,0,61.9,26.09,61.9,61.24c0,34.71-25.65,60.58-61.9,60.58
+ c-35.82,0-61.69-25.87-61.69-60.58C872.88,461.67,898.75,435.58,934.56,435.58z M934.56,536.18c23.66,0,39.79-17.03,39.79-39.36
+ c0-22.77-16.14-40.02-39.79-40.02c-23.44,0-39.36,17.25-39.36,40.02C895.21,519.15,911.13,536.18,934.56,536.18z"/>
+ </g>
+ <g>
+ <path class="st56" d="M724.15,245.36c-0.97-8.4-17.24-16.23-17.24-16.23c-6.81-4.71-8.03-16.23-7.16-20.6
+ c0.87-4.36,15.01-36.66,15.01-36.66c11.52-11.35,31.6-35.44,33.87-48.88c1.07-6.31-3.14-38.93,5.41-49.75
+ c5.63-7.12,22.35-15.36,25.84-18.16c3.49-2.79,6.28-11,4.71-14.84c-1.57-3.84-27.41-4.02-27.41-4.02s-2.16-10.79-17.62-11.8
+ c-6.05-0.2-10.18,1.3-10.18,1.3c1.78-0.89,4.69-2.18,6.78-3.1c-0.54-3.56-1.89-11.45-3.24-11.72c-1.43-0.29-4.73,5.28-6.27,7.21
+ l0.28,3.42l-1.11-2.85l-1.63-4.2l-0.02-0.06h0c-0.64-1.74-1.36-3.37-2.08-3.53c-1.57-0.34-38.06,50.81-42.6,57.44
+ c-4.54,6.63-9.25,17.81-33.87,25.49c-17.37,5.42-53.43,6.32-81.13,15.16l0,0c-14.25,1.43-53.29,33.79-83.15,32.62
+ c-20.05-0.78-35.61-6.28-39.57-15.25c0,0,4.19,41.32,25.6,43.64c0,0,13.5,2.33,26.53-9.31c0,0-3.72,7.36-8.38,9.5
+ c0,0,18.07-4.62,29.06-12.94c3.37-7.06,9.22-17.4,17.46-25.17c0.53-0.52,0.86-0.8,0.86-0.8c-0.29,0.26-0.57,0.53-0.86,0.8
+ c-2.65,2.6-10.68,11.48-11.86,24.73c-0.24,2.64-0.21,5.44,0.2,8.41c0,8.9,9.66,20.78,9.66,26.19c0,7.16-16.76,20.25-17.81,23.74
+ c-0.58,1.95-2.35,16.91,0.69,29.77h-59.09c-56.8,0-78.68,56.8-78.68,56.8l-24.4,68h-10.52c-8.25,0-14.94,6.69-14.94,14.94
+ c0,8.19,6.59,14.81,14.75,14.92l-0.01,0.02h0.2h5.92c8.25,0,14.94,6.69,14.94,14.94c0,8.25-6.69,14.94-14.94,14.94H197.01
+ c-8.25,0-14.94,6.69-14.94,14.94c0,8.25,6.69,14.94,14.94,14.94h154.15c8.25,0,14.94,6.69,14.94,14.94
+ c0,8.25-6.69,14.94-14.94,14.94h-32.39H146.48h-32.39c-8.25,0-14.94,6.69-14.94,14.94c0,8.25,6.69,14.94,14.94,14.94h183.08
+ l-13.71,38.21h113.6l35.67-99.4h1.72c79.83,0,109.91-85.2,109.91-85.2h-81.05l15.29-42.6l88.87,0c56.8,0,83.71-85.2,83.71-85.2
+ H539.99c-2.48-4.72-5.03-9.43-5.03-13.01c0-18.5,49.93-30.55,48.36-60.4c-4.08-16.85,2.51-25.92,2.51-25.92
+ c-4.2,9.35,0.01,19.79,2.11,24c0.03,0,0.06,0,0.1,0c13.97,0,41.9,15.36,59.18,15.36c11.43,0,20.41-2.37,25.19-3.97
+ c0.69-3.96,0.82-14.29-14.36-20.96c0,0,18.37,0.16,18.15,19.52c0,0-2.09,31.42,0.7,54.29c0.55,4.48,1.7,8.11,3.22,11.09h-0.52
+ c-13.7,55.08-55.31,85.2-55.31,85.2h60.7l-51.92,142.02l-0.04-0.01H510.67c-56.8,0-85.2,85.2-85.2,85.2H601.9
+ c56.8,0,111.69,5.33,144.18-85.2l40.77-113.6C810.25,293.33,776.46,249.98,724.15,245.36z"/>
+ <path class="st56" d="M56.25,489.25H18.47c-8.25,0-14.94,6.69-14.94,14.94c0,8.25,6.69,14.94,14.94,14.94h37.78
+ c8.25,0,14.94-6.69,14.94-14.94C71.19,495.94,64.5,489.25,56.25,489.25z"/>
+ <path class="st56" d="M171.38,399.61h120.5c8.25,0,14.94-6.69,14.94-14.94c0-8.25-6.69-14.94-14.94-14.94H102.14
+ c-8.25,0-14.94,6.69-14.94,14.94c0,8.25,6.69,14.94,14.94,14.94H171.38z"/>
+ <path class="st56" d="M180.84,339.85h162.85c8.25,0,14.94-6.69,14.94-14.94c0-8.25-6.69-14.94-14.94-14.94H180.84
+ c-8.25,0-14.94,6.69-14.94,14.94C165.9,333.17,172.59,339.85,180.84,339.85z"/>
+ </g>
+</g>
+</svg>
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 %}
+<div class="card">
+ <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>
+{% 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 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+ <meta charset="utf-8" />
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+ <title>{{ title }}</title>
+ <meta property="og:site_name" content="{{ title }}"/>
+ <meta property="og:type" content="website"/>
+ <meta property="og:title" content="{{ title }}"/>
+ <meta property="og:description" content="{{ description }}"/>
+ <meta property="og:url" content="https://fd.io/"/>
+ <meta name="HandheldFriendly" content="True" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
+ <link href="https://fonts.googleapis.com/css?family=Lato" rel="stylesheet">
+ <link rel="stylesheet" href="{{ url_for('static', filename='dist/css/styles.css') }}" />
+ <link rel="stylesheet" href="https://use.typekit.net/uqq2lcv.css">
+ <link rel="shortcut icon" href="{{ url_for('static', filename='dist/img/favicon.svg') }}" type="image/x-icon" />
+ <link
+ rel="stylesheet"
+ href="https://use.fontawesome.com/releases/v5.8.1/css/all.css"
+ integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf"
+ crossorigin="anonymous"
+ />
+</head>
+
+<body class="{{template}}">
+ <div class="container">
+ {% block content %}{% endblock %}
+ </div>
+</body>
+
+</html>
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 = """
+<!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>
+"""
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"