Distributed fiber optic sensors: panel multi app with flask

Hi all,

In this post I want to show the app I am building for my research work. Before continue talking about the webapp, I am going to give some context. We are a photonics research group working in custom fiber optic distributed sensors (https://en.wikipedia.org/wiki/Fiber-optic_sensor). These sensors measure the vibration and temperature signal along 100 km of oil pipeline in real time with a resolution less than one meter. Analyzing the vibration signals and its spectral response with some classical probabilities and convolutional networks with LSTM we can predict if there is some risk for the pipeline and where it is happenning in real time, for example we can detect a big excavator digging next to the pipeline or some intrusor trying to thief the oil. In these cases, the people controlling the pipeline can send security personal to avoid the risk.

After one year playing with panel holoviz I am going to show the app I am building. It is noteworthy to mention that one and a half year ago I only used labview. The webapp is a multipage app with panel and flask. The login is done with ldap and active directory (ldap3 library), the users and the routing is managed by flask while the connection with the measurement equipments, the database and the data is controlled by panel.

The layout consist in an icon bar in the left (https://www.w3schools.com/howto/howto_css_icon_bar.asp) for the routing of the 6 different panel app served with pn.serve(dict_apps). The apps use some of the panel templates and are iframed due to not being able to embed the components of panel in custom templates in flask as is requested in this bokeh feature request (https://github.com/bokeh/bokeh/issues/8499). The webapp allows the monitoring of the pipeline vibration and temperature signal, the configuration of zones to configure different settings and allowed events, the
inquiry to a sql database (pyodbc) with the detected events in the past, and the monitoring of all the vibration activity during the day.

Here you have some recording of the first draft of the app, as you can see the panel apps are simple as compared to the ones shown in the discourse forum.

I want to thanks to all the panel people and mainly to @philippjfr and @Marc by their work.

Best regards,
Néstor.

4 Likes

Thanks so much for sharing! Looks great. We should definitely work on integrating multi-page navigation somehow.

2 Likes

Congratulations @nghenzi2019.

Looks Great. Thanks for sharing.

Which table widget are you using? I’m asking because it has pagination.

What things would you be adding in the future?

1 Like

Hello,
I think it is simply Datatable. An example is shown here. Correct me if you already knew it.
https://panel.holoviz.org/gallery/external/DataTable.html#external-gallery-datatable

I saw this type of navigation in grafana, but it is in Visual studio code, youtube in the smart tv and so on. It is really comfortable to be able to switch across the different apps. I use iframes as can be seen below. It would be nice to have the same functionality that Visual Studio code, i.e. when you press the icon corresponding to the active app, the sidebar of the main app is shown or hidden, and when you press the icon corresponding to an inactive app you go to that app as it is shown in the example below.

For someone trying to dome the same stuff, here is a not so short example but fully functional. In each app function (app1,app2,app3,app4,app5) you can replace by the desired functionality

from functools import partial

import numpy as np
import panel as pn

from bokeh.models import ColumnDataSource
from bokeh.plotting import figure

template = """
{% extends base %}

{% block postamble %}
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">

<style>
    html, body {margin: 0; 
            height: 100%; 
            overflow-y: auto;
            background-color: white;}	

    .icon-bar { width: 80px;
            background-color: #555;
            height: 100%;}

    .icon-bar .icon {display: block;
            text-align: center;
            padding: 8px;
            transition: all 0.3s ease;
            color: white;
            font-size: 28px;
            text-size-adjust: none;
            height: 54px; }

    .icon-bar .icon:hover {background-color: #000;
            width: 120 px;}

    .left{width:80px;
            float:left;
            height:100%;}

    .right{margin-left:80px;
    height:100%;}
</style>

{% endblock %}

{% block contents %}

<div class="main_div" style="height:100%">

  <div class="left">
    <div class="icon-bar" style="height:100%">
        <div class= 'icon' ><span></span></div>
        <div class= 'icon' ><span></span></div>
        <div class='icon' onclick="Redirect('app1')">  <i class="fa fa-trash"></i>  </div>
        <div class='icon' onclick="Redirect('app2')">  <i class="fa fa-globe"></i>  </div>
        <div class='icon' onclick="Redirect('app3')">  <i class="fa fa-minus-circle"></i>  </div>
        <div class='icon' onclick="Redirect('app4')">  <i class="fa fa-envelope"></i>  </div>
        <div class='icon' onclick="Redirect('app5')">  <i class="fa fa-search"></i>  </div>
    </div>
  </div>

  <div class="right">
      <iframe id="iframe" style="border: None;  
          width: 100%; height: 100%; overflow: hidden;"  
          src="http://localhost:5006/app1" title="W3Schools">
      </iframe>  
  </div>

  <script>
    function Redirect(app){
        document.getElementById('iframe').src = 'http://localhost:5006/'+app;
    }
  </script>

</div>
{% endblock %}
"""

def navigator():
    return pn.Template(template)


def update(source):
    data = np.random.randint(0, 2 ** 31, 10)
    source.data.update({"y": data})


def app1():
    source = ColumnDataSource({"x": range(10), "y": range(10)})
    p = figure()
    p.line(x="x", y="y", source=source)
    vanilla = pn.template.VanillaTemplate(title='app1: Vanilla')
    vanilla.main.append(pn.pane.Bokeh(p,sizing_mode='stretch_both'))
    cb = pn.state.add_periodic_callback(partial(update, source), 200, timeout=5000)
    return vanilla

def app2():
    source = ColumnDataSource({"x": range(10), "y": range(10)})
    p = figure()
    p.line(x="x", y="y", source=source)
    golden = pn.template.GoldenTemplate(title='app2: Golden')
    golden.main.append(pn.pane.Bokeh(p,sizing_mode='stretch_both'))
    cb = pn.state.add_periodic_callback(partial(update, source), 200, timeout=5000)
    return golden

def app3():
    source = ColumnDataSource({"x": range(10), "y": range(10)})
    p = figure()
    p.line(x="x", y="y", source=source)
    material = pn.template.MaterialTemplate(title='app3: Material')
    material.main.append(pn.pane.Bokeh(p,sizing_mode='stretch_both'))
    cb = pn.state.add_periodic_callback(partial(update, source), 200, timeout=5000)
    return material

def app4():
    source = ColumnDataSource({"x": range(10), "y": range(10)})
    p = figure()
    p.line(x="x", y="y", source=source)
    react = pn.template.ReactTemplate(title='app4: React')
    react.main[:5,:12] = pn.pane.Bokeh(p,sizing_mode='stretch_both')
    cb = pn.state.add_periodic_callback(partial(update, source), 200, timeout=5000)
    return react

def app5():
    source = ColumnDataSource({"x": range(10), "y": range(10)})
    p = figure()
    p.line(x="x", y="y", source=source)
    bootstrap = pn.template.BootstrapTemplate(title='app5: Bootstrap')
    bootstrap.main.append(pn.pane.Bokeh(p,sizing_mode='stretch_both'))
    cb = pn.state.add_periodic_callback(partial(update, source), 200, timeout=5000)
    return bootstrap

dict_apps = {'navigator':navigator,
            'app1':app1,
            'app2':app2,
            'app3':app3,
            'app4':app4,
            'app5':app5}

port_bokeh = 5006
pn.serve(dict_apps, port=port_bokeh)

and it works like this. The main problem with this approach is you do not have the real location of the app.

Yes, it is the datatable example slightly modified as shown below. As can be seen below, the table is completely responsive in width, hiding or showing the columns according to the width of the div container.

import panel as pn
from bokeh.sampledata.autompg import autompg

raw_css = """ div.dataTables_wrapper {
                    width: 100%;
                    height: 100%;
                    margin: 0 auto;
                } 


     """ 

css = ['https://cdn.datatables.net/1.10.22/css/jquery.dataTables.min.css',
        'https://cdn.datatables.net/fixedheader/3.1.7/css/fixedHeader.dataTables.min.css',
      'https://cdn.datatables.net/responsive/2.2.6/css/responsive.dataTables.min.css']

js = {
    '$': 'https://code.jquery.com/jquery-3.5.1.js',
    'DataTable': 'https://cdn.datatables.net/1.10.22/js/jquery.dataTables.min.js',
    'respo1': 'https://cdn.datatables.net/fixedheader/3.1.7/js/dataTables.fixedHeader.min.js',
    'respo2': 'https://cdn.datatables.net/responsive/2.2.6/js/dataTables.responsive.min.js'
}

pn.extension(css_files=css, js_files=js, raw_css=[raw_css])

pn.config.sizing_mode = 'stretch_width'


script = """
<script>
if (document.readyState === "complete") {
var table = $('#example').DataTable( {
            "responsive": true,
            "scrollCollapse": true,
            "paging":         true,
            "columnDefs": [{"className": "dt-left", "targets": "_all"}]
        });


} """

html = autompg.to_html().replace("""class="dataframe\"""",
            """ id="example" class="display nowrap" style="width:100%" """)

d = pn.pane.HTML(html+script, sizing_mode='stretch_width')
d.show()

and it works like this

1 Like

Hi @nghenzi2019

I’m updating the awesome-list and I would like to add a link to this app. What would be the best link to add?

Hi @Marc

The app is in a private network, so no access is possible. We are still improving it, but when it is finished I will put some copy of the functionality in heroku or in our public server.

Thanks for the consideration !
Best regards,
N

1 Like

Neat demo! I was curious whether the username/password page was part of panel or flask?

it is part of flask, i am using flask login. Today I see this package FastAPI Users for fast api.

I do not know how to achieve the same functionality of flask login with panel, due to that I use flask.

1 Like