# Copyright 2018 Trimble Inc.
# $Id: webApp.py,v 1.74 2022/04/29 17:42:49 stuart Exp $

import matplotlib
# Allow running headless from the command line
matplotlib.use("agg")

import sys
import os
import glob
import time
import datetime
import signal
import math
import threading
import json
import RXTools
import getTTData
import testThreshold
import logging
import data2sheets
from logging.handlers import RotatingFileHandler
from numpy import *
from pylab import *
from flask import Flask, render_template, render_template_string, Markup, Response, request, jsonify, send_from_directory
from flask_compress import Compress
from slackclient import SlackClient
try:
    from urllib.request import urlopen
except ImportError:
    from urllib2 import urlopen

from requests.utils import quote

from bokeh.embed import components
from bokeh.resources import INLINE
from bokeh.util.string import encode_utf8
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource, OpenURL, TapTool
import getPVTStats
import chatbot

# Port at which the service will run - don't run a 
# second instance of this without changing this!!
webPort = 8080
# When True this will run a thread which parses the
# data from a temperature sensor in the antenna room.
# Only run this if you want to run that test and save
# the data. Note if you have two instances running
# they will both save data to the same file!
runTempThread = True
# Mostly a place holder for now ... eventually
# exception reports will go to slack when this is 
# set to True. For now it only reports when the 
# application starts or is terminated via CTRL-C or 
# a detectable crash.
useSlack = True
# We use this frame work to get SV tracking data
# from mulitple stations and provide as an 
# XML output. If you won't want this feature set
# to False
runGetSVData = True
enableServerLogs = True


dCode = 0
dCarr = 1
dDopp = 2

slackTime = -1

# Use the current time to initialize a few variables
secondsSysRequest = datetime.datetime.now()
secondsSvRequest = datetime.datetime.now()

# Set to the past so it will alert immediately if there's a temperature issue
TemperatureAlertTime = datetime.datetime(2018,1,1,0,0,0)

# Global antenna room temperature
AntTemp = {}

app = Flask(__name__)
Compress(app)

def sendToSlack(message):
  if(useSlack == True):
    # If you don't have the API as an environment variable
    # we can't report to slack
    if("SLACK_API_TOKEN" in os.environ):
      slack_token = os.environ["SLACK_API_TOKEN"]
      sc = SlackClient(slack_token)
      # Get the Channel ID from https://my.slack.com/messages
      sc.api_call( "chat.postMessage",
                   channel="CAC19ABHV",
                   text=message)
  return


def sendTrackExceptionsToChat():
  Thresh = 86000 # Minimum number of epochs per day 
  
  space = 'AAAAte3wnhw'
  if("CHAT_API_TOKEN" in os.environ):
    token = os.environ["CHAT_API_TOKEN"]
    if("CHAT_API_KEY" in os.environ):
      key = os.environ["CHAT_API_KEY"]
    else:
      return
  else:
    return

  # Test room
  #key   ='AIzaSyDdI0hCZtE6vySjMm-WEfRq3CPzqKqqsHI'
  #token ='BkqsF7agVwmha7J0patsuPEGkh14Cm1G568Ue2DQsdQ%3D'
  #space = 'AAAAarQBZ0g'

  TrackStations = [ {'name':'Tokyo Alloy vs NetR9','station':'Tokyo','root':'DataDump/CNoDiff/Alloy-NetR9-Tokyo/','baseRX':'Alloy','roverRX':'NetR9'},
                    {'name':'Terrasat2 Alloy vs NetR9','station':'Terrasat2','root':'DataDump/CNoDiff/Alloy-NetR9-Terrasat2/','baseRX':'Alloy','roverRX':'NetR9'},
                    {'name':'Sunnyvale QA Alloy vs NetR9','station':'SunnyvaleQA-Alloy','root':'DataDump/CNoDiff/Alloy2-NetR9-QA/','baseRX':'Alloy','roverRX':'NetR9'},
                    {'name':'Sunnyvale BD940 vs BD970','station':'Sunnyvale','root':'DataDump/CNoDiff/BD970-Argon/','baseRX':'BD940','roverRX':'BD970'},
                    {'name':'Sunnyvale QA BD992 vs NetR9','station':'SunnyvaleQA-BD992','root':'DataDump/CNoDiff/BD992-NetR9-QA/','baseRX':'BD992','roverRX':'NetR9'},
                    {'name':'Singapore Alloy vs NetR9','station':'SingaporeAlloy','root':'DataDump/CNoDiff/Alloy-NetR9-Singapore/','baseRX':'Alloy','roverRX':'NetR9'},
                    {'name':'Singapore Alloy vs BD935','station':'SingaporeBD935','root':'DataDump/CNoDiff/Alloy-BD935-Singapore/','baseRX':'Alloy','roverRX':'BD935'},
                    {'name':'StingerLab BD940 vs BD935','station':'StingerLabBD940','root':'DataDump/CNoDiff/BD940_1-BD935_1/','baseRX':'BD940','roverRX':'BD935'},
                    {'name':'StingerLab BD992 vs BD935','station':'StingerLabBD992','root':'DataDump/CNoDiff/BD992-BD935_1/','baseRX':'BD992','roverRX':'BD935'} ]

# Running old code, so lots of alarms.
#   {'station':'Beijing','root':'DataDump/CNoDiff/Alloy-NetR9-Beijing/'},
#   {'station':'Terrasat','root':'DataDump/CNoDiff/Alloy-NetR9-Terrasat/'},
#   {'station':'Perth','root':'DataDump/CNoDiff/Alloy-NetR9-Perth/'},

  startDate = datetime.datetime(2020,1,1)
  now = datetime.datetime.utcnow()
  yesterday = now - datetime.timedelta(days=1)
  yesterdayNum = int((yesterday - startDate).total_seconds() / 86400)

  for i in range(len(TrackStations)):
    filename = '/net/quark/mnt/data_drive/' + TrackStations[i]['root'] + 'results.txt'
    webRoot  = 'http://quark.eng.trimble.com/' + TrackStations[i]['root']
    Entries = 0
    for sysIndex in range(len(gnssSysData)):
      svData = []
      sysEntries = 0
      chatStr = ''
      for j in range(gnssSysData[sysIndex]['start'] - 1, gnssSysData[sysIndex]['stop']):
        svData.append(dict({'year':None, 'month':None, 'day':None, 'dayNum':None, 
                            'baseEpochs_All':None, 'roverEpochs_All':None, 
                            'baseEpochs_PLL':None, 'roverEpochs_PLL':None,
                            'baseVersion':None, 'roverVersion':None}))
      if(os.path.isfile(filename)): 
        with open(filename,'r') as fid:
          for line in fid:
            tokens = line.rstrip().split(',')

            thisSys = int(tokens[4])
            if(int(tokens[4]) == gnssSysData[sysIndex]['sys']):
              sv = int(tokens[3]) 
              if( (sv >= gnssSysData[sysIndex]['start']) and (sv <= gnssSysData[sysIndex]['stop'])):
                index = sv - gnssSysData[sysIndex]['start']
                if( (len(tokens) > 17) and (int(tokens[16]) >= Thresh) and (int(tokens[17]) >= Thresh) ):
                  svData[index]['baseEpochs_All'] = int(tokens[12])
                  svData[index]['roverEpochs_All'] = int(tokens[13])
                  svData[index]['baseEpochs_PLL'] = int(tokens[14])
                  svData[index]['roverEpochs_PLL'] = int(tokens[15])
                  svData[index]['year'] = int(tokens[0])
                  svData[index]['month'] = int(tokens[1])
                  svData[index]['day'] = int(tokens[2])
                  if(len(tokens) > 21):
                    svData[index]['baseVersion'] = tokens[20]
                    svData[index]['roverVersion'] = tokens[21]
                  else:
                    svData[index]['baseVersion'] = None
                    svData[index]['roverVersion'] = None

        for svIndex in range(len(svData)):
          if(     (svData[svIndex]['baseEpochs_All'] is not None)
              and ( (svData[svIndex]['baseEpochs_All'] + svData[svIndex]['roverEpochs_All']) > 0) ): 
            if((svData[svIndex]['baseEpochs_PLL']+svData[svIndex]['roverEpochs_PLL']) > 0):
              PLLRatio = 100.0 * (svData[svIndex]['baseEpochs_PLL'] - svData[svIndex]['roverEpochs_PLL']) / (0.5*float(svData[svIndex]['baseEpochs_PLL']+svData[svIndex]['roverEpochs_PLL']))
            else:
              PLLRatio = 0.0

            if(PLLRatio<-5.0):
              latestTime = datetime.datetime(svData[svIndex]['year'], svData[svIndex]['month'], svData[svIndex]['day'])
              dayNum = int((latestTime - startDate).total_seconds() / 86400)
              if(dayNum == yesterdayNum):
                dayStr =  str(svData[svIndex]['year'])  + str(svData[svIndex]['month']).zfill(2) + str(svData[svIndex]['day']).zfill(2)

                if(Entries == 0):
                  chatStr += dayStr
                  # "*String*" results in String shown in bold
                  chatStr += ' - *' + TrackStations[i]['name'] + '* Tracking Epoch Test\n'
                  if(     (svData[index]['baseVersion'] is not None) 
                      and (svData[index]['roverVersion'] is not None) ):
                    chatStr += '*Base FW:* ' + svData[index]['baseVersion'] 
                    chatStr += ' (' + TrackStations[i]['baseRX']  + ') '
                    chatStr += ' *Rover FW:* ' + svData[index]['roverVersion']  
                    chatStr += ' (' + TrackStations[i]['roverRX']  + ')\n'

                if(sysEntries == 0):
                  chatStr += 'Tabulated *' + gnssSysData[sysIndex]['sysName'] + '* http://higgs.eng.trimble.com:8080/TrackDiff.html?station='
                  chatStr += TrackStations[i]['station'] + '&system=' + gnssSysData[sysIndex]['sysName'] + '\n'

                chatStr +=  "Epoch Exception: %s %d: %.2f%%\n" % (gnssSysData[sysIndex]['sysName'], svIndex + gnssSysData[sysIndex]['start'],PLLRatio)
                chatStr += webRoot + dayStr + '/' + dayStr + gnssSysData[sysIndex]['sysName'] + '-' + str(svIndex + gnssSysData[sysIndex]['start']) + '.png\n'
                Entries += 1
                sysEntries += 1
      # If we had data for this station, and an extra line feed to break
      # up the data and then output to the chat bot.
      if(sysEntries != 0):
        print(chatStr,end='')
        chatbot.createMessage(key,token,space,chatStr + '\n',thread=dayStr)


# This intercepts the web requests, it allows us to filter and
# provides a central place for logging
@app.before_request
def limit_remote_addr():
  if(request.remote_addr == '10.1.187.20'):
    # Block Trimble's scanner which messes up the logs
    return "", 403
  else:
    if(request.remote_addr != '10.1.148.238'):
      # Don't log the requests for svData.xml from daffy's mashup
      if(enableServerLogs == True):
        app.logger.info("%s %s" % (request.remote_addr, request.url))

# If we get a 404 (page not found) log an error
@app.errorhandler(404)
def handle404(error):
  if(enableServerLogs == True):
    app.logger.error("%s %s" % (request.remote_addr, request.url))
  return "", 404

@app.route("/")
def main():
  return index()


@app.route('/favicon.ico')
def favicon():
  return send_from_directory(os.path.join(app.root_path, 'static'),
                             'favicon.ico', mimetype='image/vnd.microsoft.icon')

@app.route("/Debug/")
def debug():
    return "Debug!"

@app.route("/index.html")
def index():
  webStr = ("{% extends 'menu.html' %}")
  webStr = webStr + ("{% block main %}")
  webStr = webStr + ("<section id='content'>")
  #webStr = webStr + ("<div class='row'>")
  webStr = webStr + ("<div class='col'>")
  webStr = webStr + "<div class='col-sm-10'>"
  webStr = webStr + ("{% include 'results-body.html'%}")
  webStr = webStr + ("</div></div>")
  webStr = webStr + ("</section>")
  webStr = webStr + ("{% endblock %}")
  webStr = webStr + ("{% block userscript %}")
  webStr = webStr + ("{% include 'results-script.html'%}")
  webStr = webStr + ("<script src='static/js/app.js'></script>")
  webStr = webStr + ("<script>startup();</script>")
  webStr = webStr + ("{% endblock %}")
  return render_template_string(Markup(webStr))


@app.route("/testTrack.html")
def testTrack():
  webStr = ("{% extends 'menu.html' %}")
  webStr = webStr + ("{% block main %}")
  webStr = webStr + ("<section id='content'>")
  #webStr = webStr + ("<div class='row'>")
  webStr = webStr + ("<div class='col'>")
  webStr = webStr + "<div class='col-sm-10'>"
  webStr = webStr + ("{% include 'testTrack-body.html'%}")
  webStr = webStr + ("</div></div>")
  webStr = webStr + ("</section>")
  webStr = webStr + ("{% endblock %}")
  webStr = webStr + ("{% block userscript %}")
  webStr = webStr + ("{% include 'testTrack-script.html'%}")
  webStr = webStr + ("<script src='static/js/app.js'></script>")
  webStr = webStr + ("<script>startup();</script>")
  webStr = webStr + ("{% endblock %}")
  return render_template_string(Markup(webStr))


@app.route("/TeqcSlips.html")
def TeqcSlips():
    return "Place Holder"

@app.route("/SoftGNSSTTFF.html")
def SoftGNSSTTFF():
    return "Place Holder"

@app.route("/SoftGNSSPVT.html")
def SoftGNSSPVT():
    return "Place Holder"

@app.route("/SoftGNSSDDCode.html")
def SoftGNSSDDCode():
    return "Place Holder"

@app.route("/SoftGNSSDDCarr.html")
def SoftGNSSDDCarr():
    return "Place Holder"



def is_number(string):
  try:
    float(string)
    return True
  except:
    return False


@app.route("/RFReplay.html")
def RFReplay():

  webStr = ("{% extends 'menu.html' %}")
  webStr = webStr + ("{% block main %}")
  webStr = webStr + ("<section id='content'>")

  d = {}
  d['rx'] = []
  d['location'] = []
  d['test'] = []
  d['filename'] = []
  d['time'] = []
  d['y2d'] = []
  d['y3d'] = []

  # This code assumes that the data is stored in chronological order.
  # That's occuring today, but as RFRegTrend.txt is generated by python
  # performing a directory listing this is not guaranteed. 
  #
  # ToDo - sort based on the time field
  filename = '/net/higgs/mnt/data_drive/DataDump/DashBoard/RFRegTrend.txt'
  if(os.path.isfile(filename)): 
    webStr = webStr + ("<div class='col'>")
    webStr = webStr + "<div class='col-sm-10'>"
    webStr = webStr + ("<h3>RF Replay (2D 95%)</h3>")
    webStr = webStr + ("<div class='table-responsive'>")
    webStr = webStr + ("<table id='data-table-basic' class='table table-striped'>")
    webStr = webStr + ("<thead>")
    webStr = webStr + ("<tr>")
    with open(filename,'r') as f:
      for line in f:
        data = line.rstrip().split(',')
        d['rx'].append(data[1])
        d['location'].append(data[2])
        d['test'].append(data[3])
        d['filename'].append(data[4])
        # We've split a comma delimited line of data. However, 
        # some of the strings have a comma! Fortunately, the 
        # information we care about is at the end of the line. 
        # Therefore, index back from the end.
        try:
          d['time'].append(float(data[-3]))
          d['y2d'].append(float(data[-2]))
          d['y3d'].append(float(data[-1]))
        except:
          print(line)
          print(data)
    
    # Get a unique list of the tests and receivers
    dataSets  = list(set(d['filename']))
    receivers = list(set(d['rx']))

    webStr = webStr + ("<th data-column-id='id'>Data Set</th>")
    for rx in range(len(receivers)):
      localStr = receivers[rx]
      if(localStr == 'Top CVS Zep'):
        localStr = 'Top CVS Astra (RTK)'
      elif(localStr == 'Titan Zep'):
        localStr = 'Top CVS Titan (RTK)'
      elif(localStr == '532 Zep'):
        localStr = 'V5.32 Astra (RTK)'
      else:
        localStr += ' (SBAS)'
      webStr = webStr + ("<th>" + localStr + "</th>")

    webStr = webStr + ("</tr>")
    webStr = webStr + ("</thead>")
    webStr = webStr + ("<tbody>")

    hyst = 10.0;

    for i in range(len(dataSets)):
      Current2D = [0] * len(receivers)
      Last2D    = [0] * len(receivers)
      for rx in range(len(receivers)):
        for j in range(len(d['filename'])):
          if((d['rx'][j] == receivers[rx]) and (d['filename'][j] == dataSets[i])):
            Last2D[rx]    = Current2D[rx]
            Current2D[rx] = d['y2d'][j]
            
      webStr = webStr + "<tr>"
    
      safeLink = quote(dataSets[i])
      webStr = webStr + (  "<td><a onclick='toggleImg(this)' href='#'>" 
                         + dataSets[i]
                         + "<img class='links' src='http://quark.eng.trimble.com/DataDump/DashBoard/"
                         + safeLink + ".2D.png'"
                         + "</img></a></td>")

      # Find which receiver has the worst and best values
      WorstValue = 0
      WorstIndex = 0
      BestValue = 1e100
      BestIndex = 0
      for rx in range(len(receivers)):
        if(Current2D[rx] > WorstValue):
          WorstValue = Current2D[rx]
          WorstIndex = rx
        if(Current2D[rx] < BestValue):
          BestValue = Current2D[rx]
          BestIndex = rx

      for rx in range(len(receivers)):
        webStr = webStr + "<td>" + dataCompare(Current2D[rx],Last2D[rx],hyst) 
        if(WorstIndex == rx):
          webStr = webStr + "<font color='red'>*</font>"
        elif(BestIndex == rx):
          webStr = webStr + "<font color='green'>+</font>"

        webStr = webStr + "</td>"
      webStr = webStr + "</tr>"

    webStr = webStr + ("</tbody>")
    webStr = webStr + ("</table>")
    webStr = webStr + ("</div>")
    webStr = webStr + ("</div>")
    webStr = webStr + ("</div>")


    webStr = webStr + ("<div class='row'>")
    webStr = webStr + "<div class='col-sm-6'>"
    webStr = webStr + ("<h3>Summary Comparison (2D)</h3>")
    webStr = webStr + ("<div class='table-responsive'>")

    webStr = webStr + ("<table id='data-table-basic' class='table table-striped'>")
    webStr = webStr + ("<thead>")
    webStr = webStr + ("<tr>")
    percentiles = [95,99,100]
    webStr = webStr + ("<th data-column-id='id'>&nbsp;</th>")
    for i in range(len(percentiles)):
      webStr = webStr + ("<th>" + str(percentiles[i]) + "%</th>")
    webStr = webStr + ("</tr>")
    webStr = webStr + ("</thead>")

    PVTType = ['SBAS','DGNSS','RTK']
    NameStr = ['sbas','dgnss','']
    for i in range(3):
      webStr = webStr + ("<tr>")
      webStr = webStr + ("<td>" + PVTType[i] + "</td>")
      for j in range(len(percentiles)):
        webStr = webStr + (  "<td><a onclick='toggleImg(this)' href='#'>" 
                           + 'X'
                           + "<img class='links' src='http://quark.eng.trimble.com/DataDump/DashBoard/"
                           + "RFReplay_2D" + str(percentiles[j]) + NameStr[i] + ".png'"
                           + "</img></a></td>")
      webStr = webStr + ("</tr>")
    webStr = webStr + ("</table>")
    webStr = webStr + ("</div>")
    webStr = webStr + ("</div>")


    webStr = webStr + "<div class='col-sm-6'>"
    webStr = webStr + ("<h3>Summary Comparison (3D)</h3>")
    webStr = webStr + ("<div class='table-responsive'>")

    webStr = webStr + ("<table id='data-table-basic' class='table table-striped'>")
    webStr = webStr + ("<thead>")
    webStr = webStr + ("<tr>")
    webStr = webStr + ("<th data-column-id='id'>&nbsp;</th>")
    for i in range(len(percentiles)):
      webStr = webStr + ("<th>" + str(percentiles[i]) + "%</th>")
    webStr = webStr + ("</tr>")
    webStr = webStr + ("</thead>")

    for i in range(3):
      webStr = webStr + ("<tr>")
      webStr = webStr + ("<td>" + PVTType[i] + "</td>")
      for j in range(len(percentiles)):
        webStr = webStr + (  "<td><a onclick='toggleImg(this)' href='#'>" 
                           + 'X'
                           + "<img class='links' src='http://quark.eng.trimble.com/DataDump/DashBoard/"
                           + "RFReplay_3D" + str(percentiles[j]) + NameStr[i] + ".png'"
                           + "</img></a></td>")
      webStr = webStr + ("</tr>")
    webStr = webStr + ("</table>")
    webStr = webStr + ("</div>")
    webStr = webStr + ("</div>")
    webStr = webStr + ("</div>")
    webStr = webStr + ("<p>Hysteresis = " + str(hyst) + "%</p>")
    webStr = webStr + "<p>Worst Receiver = <font color='red'>*</font></p>"
    webStr = webStr + "<p>Best Receiver = <font color='green'>+</font></p>"


  webStr = webStr + ("</section>")
  webStr = webStr + ("{% endblock %}")
  webStr = webStr + ("{% block userscript %}")
  webStr = webStr + ("<script>")
  webStr = webStr + ("</script>")
  webStr = webStr + ("<script src='static/js/app.js'></script>")
  webStr = webStr + ("{% endblock %}")


  return render_template_string(Markup(webStr))



@app.route("/TrackIssues.html")
def TrackIssues():
  webStr = ("{% extends 'menu.html' %}")
  webStr += ("{% block main %}")
  webStr += ("<section id='content'>")
  webStr += ("<div class='row'>")
  webStr += "<div class='col-sm-10'>"
  webStr += ("<h3>Tracking Issues</h3>")
  webStr += ("<div class='table-responsive'>")
  webStr += ("<table id='data-table-basic' class='table table-striped'>")
  webStr += ("<thead>")
  webStr += ("<tr>")
  webStr += ("<th>Date</th>")
  webStr += ("<th>Baseline</th>")
  webStr += ("<th>Type</th>")
  webStr += ("<th>Issue Events</th>")
  webStr += ("<th>Code</th>")
  webStr += ("<th>C/No</th>")
  webStr += ("<th>Carrier</th>")
  webStr += ("</tr>")
  webStr += ("</thead>")
  webStr += ("<tbody>")

  # Now scan the processed data
  lines = []
  with open("/net/quark/mnt/data_drive/DataDump/DashBoard/IssueTest/obsScanMTB.txt","r") as fid:
    for line in fid:
      lines.append(line)

  lines.reverse() # so the latest entry is first

  for i in range(len(lines)):
    data = lines[i].rstrip().split()
    token = data[0].split('/')

    webStr += "<tr>"
    webStr += "<td>" + token[6][0:4] + "-" + token[6][4:6] + "-" + token[6][6:8] + "</td>"
    webStr += "<td>" + token[6][9:] + "</td>"
    webStr += "<td>GAL AltBOC</td>"

    fileBase = "http://proton.eng.trimble.com/GPSRxEng/GNSSResults/%d-Res/%s/" % (int(token[5]),token[6])
    webLink = fileBase + "NoPiDI_Top.html" 
    webStr += "<td><a href='" + webLink + "' target='_blank'>&nbsp;" + str(data[1]) + "</a></td>"
    
    webLink = fileBase + "plots/time_code_all_11.png"
    webStr += (  "<td style='white-space: nowrap;'><a onclick='toggleImg(this)' href='#'>" 
               + "<img class='click' width='20px' src='http://quark.eng.trimble.com/DataDump/DashBoard/graph.png'></img>" 
               + "<img class='links' src='" + webLink + "'>"
               + "</img>"
               + "</a></td>")

    webLink = fileBase + "plots/time_cno_all_11.png"
    webStr += (  "<td style='white-space: nowrap;'><a onclick='toggleImg(this)' href='#'>" 
               + "<img class='click' width='20px' src='http://quark.eng.trimble.com/DataDump/DashBoard/graph.png'></img>" 
               + "<img class='links' src='" + webLink + "'>"
               + "</img>"
               + "</a></td>")

    webLink = fileBase + "plots/time_carr_all_11.png"
    webStr += (  "<td style='white-space: nowrap;'><a onclick='toggleImg(this)' href='#'>" 
               + "<img class='click' width='20px' src='http://quark.eng.trimble.com/DataDump/DashBoard/graph.png'></img>" 
               + "<img class='links' src='" + webLink + "'>"
               + "</img>"
               + "</a></td>")

    webStr += "</tr>"

  webStr = webStr + ("</tbody>")
  webStr = webStr + ("</table>")
  webStr = webStr + ("</div>")
  webStr = webStr + ("</div>")
  webStr = webStr + ("</div>")
  webStr = webStr + ("</section>")
  webStr = webStr + ("{% endblock %}")
  webStr = webStr + ("{% block userscript %}")
  webStr = webStr + ("<script>")
  webStr = webStr + ("</script>")
  webStr = webStr + ("<script src='static/js/app.js'></script>")
  webStr = webStr + ("{% endblock %}")

  return render_template_string(Markup(webStr))

gnssSysData = [ {"sysName":"GPS", "sys":0, "start":1, "stop":32},
                {"sysName":"SBAS", "sys":1, "start":120 , "stop":158},
                {"sysName":"GLONASS", "sys":2, "start":1 , "stop":24},
                {"sysName":"GALILEO", "sys":3,"start":1 , "stop":36},
                {"sysName":"QZSS", "sys":4, "start":193 , "stop":202},
                {"sysName":"IRNSS", "sys":9, "start":1 , "stop":14},
                {"sysName":"BDS", "sys":10, "start":1 , "stop":63} ]

# Not very pythonic but easy to understand - reverse index into the above
reverse = [0,1,2,3,4,None,None,None,None,5,6]

monthLUT = { 'Jan':1, 'Feb':2, 'Mar':3, 'Apr':4,  'May':5,  'Jun':6,
             'Jul':7, 'Aug':8, 'Sep':9, 'Oct':10, 'Nov':11, 'Dec':12 }

def convertDate(inString):
  # Due to some issues with version decoding (I think due to PIVOT to T02) 
  # the version was reported as a negative number. Detect and address this.
  # We should no longer observe this due to a change in mutils.py to handle
  # this situation.
  if( (len(inString) > 0) and (inString[0] == '-') ):
    inString = inString[1:]

  outString = inString
  tokens = inString.split()

  if(len(tokens) == 4):
    if(tokens[1] in monthLUT.keys()):
      # tokens[0] should be the version, the rest the date
      outString = tokens[0] + ' '  + tokens[3] + str(monthLUT[tokens[1]]).zfill(2) + tokens[2].zfill(2)

  return outString


# Beijing is down, so currently only historical data.
compareStations = [
                   {'station':'Tokyo',
                    'label':'Tokyo',
                    'path':'Alloy-NetR9-Tokyo',
                    'type':'Tokyo: Alloy - NetR9'},
                   {'station':'Terrasat',
                    'label':'Terrasat',
                    'path':'Alloy-NetR9-Terrasat',
                    'type':'Terrasat: Alloy - NetR9'},
                   {'station':'Terrasat2',
                    'label':'Terrasat2',
                    'path':'Alloy-NetR9-Terrasat2',
                    'type':'Terrasat2: Alloy2 - NetR9'},
                   {'station':'Perth',
                    'label':'Perth',
                    'path':'Alloy-NetR9-Perth',
                    'type':'Perth: Alloy - NetR9'},
                   {'station':'Sunnyvale',
                    'label':'Sunnyvale',
                    'path':'BD970-Argon',
                    'type':'Sunnyvale: BD940 - BD970'},
                   {'station':'SunnyvaleQA2-Alloy',
                    'label':'Sunnyvale QA2 Alloy',
                    'path':'Alloy2-NetR9-QA',
                    'type':'Sunnyvale QA2 RS9: Alloy NetR9'},
                   {'station':'SunnyvaleQA-BD992',
                    'label':'Sunnyvale QA BD992',
                    'path':'BD992-NetR9-QA',
                    'type':'Sunnyvale QA RS9: BD992 NetR9'},
                   {'station':'SingaporeAlloy',
                    'label':'Singapore Alloy NetR9',
                    'path':'Alloy-NetR9-Singapore',
                    'type':'Singapore: Alloy NetR9'},
                   {'station':'SingaporeBD935',
                    'label':'Singapore Alloy BD935',
                    'path':'Alloy-BD935-Singapore',
                    'type':'Singapore: Alloy BD935'},
                   {'station':'StingerLabBD940',
                    'label':'StingerLab BD940 BD935',
                    'path':'BD940_1-BD935_1',
                    'type':'StingerLab BD940 BD935'},
                   {'station':'StingerLabBD992',
                    'label':'StingerLab BD992 BD935',
                    'path':'BD992-BD935_1',
                    'type':'StingerLab BD992 BD935'},
                   {'station':'Beijing',
                    'label':'Beijing', 
                    'path':'Alloy-NetR9-Beijing',
                    'type':'Beijing: Alloy - NetR9'} ]

@app.route("/TrackDiff.html")
def TrackDiff():

  station = request.args.get('station')
  if(station is None):
    station = 'Tokyo'

  system = request.args.get('system')
  if(system is None):
    system = 'GLONASS'
   
  # Accept any case system names
  system = system.upper()
  # Accept BDS and BEIDOU  (irrespective of case)
  if(system == 'BEIDOU'):
    system = 'BDS'

  gotSystem = False
  for i in range(len(gnssSysData)):
    # Get the index into the gnssSysData list of dictionaries
    if(system==gnssSysData[i]['sysName']):
      sysIndex = i
      gotSystem = True
      break
    
  # Default to GLONASS if we got an invalid system type
  # (we've had the most problems with GLONASS).
  if(gotSystem == False):
    sysIndex = 2
    system = 'GLONASS'

  print(station,system)

  webStr  = ("{% extends 'menu.html' %}")
  webStr += ("{% block main %}")
  webStr += ("<section id='content'>")

  # Script to handle the pull downs
  webStr += ' <script>'
  webStr += ' function ChangeStationSystem() {'
  webStr += ' var x = document.getElementById("stations").value;'
  webStr += ' var y = document.getElementById("system").value;'
  webStr += ' link = "TrackDiff.html?station=" + x + "&system=" + y;'
  webStr += ' window.open(link, "_self");'
  webStr += ' }'
  webStr += ' </script>'

  webStr += ("<div class='row'>")
  webStr += "<div class='col-sm-14'>"

  gotStation = False
  for i in range(len(compareStations)):
    if(station == compareStations[i]['station']):
      webStr += ("<h3>Tracking Difference - " + gnssSysData[sysIndex]['sysName'] + " (" + compareStations[i]['type'] + ")</h3>")
      gotStation = True
      break

  if(gotStation == False):
    station = 'Tokyo'
    webStr += ("<h3>Tracking Difference - " + gnssSysData[sysIndex]['sysName'] + " (Tokyo: NetR9 - Alloy)</h3>")

  webStr+="</br></br>\n\n"
  webStr += ' <label for="stations">Station:</label>'
  webStr += ' <select name="stations" id="stations" onchange="ChangeStationSystem()">'
  
  for i in range(len(compareStations)):
    if(station == compareStations[i]['station']):
      filename = '/net/quark/mnt/data_drive/DataDump/CNoDiff/' + compareStations[i]['path'] + '/results.txt'
      webRoot = 'http://quark.eng.trimble.com/DataDump/CNoDiff/' + compareStations[i]['path'] + '/'
      webStr += ' <option value="' + station + '" selected="selected">' + compareStations[i]['label'] + '</option>'
    else:
      webStr += ' <option value="' + compareStations[i]['station'] + '">' + compareStations[i]['label'] + '</option>'

  webStr += ' </select> '

  webStr += ' <label for="system">System:</label>'
  webStr += ' <select name="system" id="system" onchange="ChangeStationSystem()">'
  
  for i in range(len(gnssSysData)):
    if(system == gnssSysData[i]['sysName']):
      webStr += ' <option value="' + system + '" selected="selected">' + system + '</option>'
    else:
      webStr += ' <option value="' + gnssSysData[i]['sysName'] + '">' + gnssSysData[i]['sysName'] + '</option>'

  webStr += ' </select> '

  webStr+="</br></br>\n\n"

  webStr += ("<div class='table-responsive'>")
  webStr += ("<table id='data-table-basic' class='table table-striped'>")
  webStr += ("<thead>")
  webStr += ("<tr>")
  webStr += ("<th>SV #</th>")
  webStr += ("<th>Date<sup>1</sup></th>")
  webStr += ("<th>Day Num</th>")
  webStr += ("<th>C/No Diff [dBHz]<sup>2</sup></th>")
  webStr += ("<th>Diff Sigma</th>")
  webStr += ("<th>PLL+FLL Epochs [%]<sup>3</sup></th>")
  webStr += ("<th>PLL Epochs [%]<sup>4,6</sup></th>")
  webStr += ("<th>Base Gaps<sup>7</sup></th>")
  webStr += ("<th>Rover Gaps<sup>7</sup></th>")
  webStr += ("<th>Base FW</th>")
  webStr += ("<th>Rover FW</th>")
  webStr += ("</tr>")
  webStr += ("</thead>")
  webStr += ("<tbody>")

  SlipData = []
  maxCombo = 18
  for combos in range(maxCombo):
    SlipData.append([])

  startTime = datetime.datetime(2020,1,1)  
  Thresh = 86000; # Don't want to deal with partial days

  print(filename)
  print(webRoot)

  svData = []
  for i in range(gnssSysData[sysIndex]['start'] - 1, gnssSysData[sysIndex]['stop']):
    svData.append(dict({'year':None, 'month':None, 'day':None, 'dayNum':None, 
                        'baseEpochs_All':None, 'roverEpochs_All':None, 
                        'baseEpochs_PLL':None, 'roverEpochs_PLL':None, 
                        'baseGaps':None, 'roverGaps':None, 
                        'meanCNo':None, 'sigCNo':None,
                        'baseVersion':None, 'roverVersion':None}))

  if(os.path.isfile(filename)): 
    with open(filename,'r') as fid:
      for line in fid:
        tokens = line.rstrip().split(',')

        thisSys = int(tokens[4])
        if(int(tokens[4]) == gnssSysData[sysIndex]['sys']):
          sv = int(tokens[3]) 
          if( (sv >= gnssSysData[sysIndex]['start']) and (sv <= gnssSysData[sysIndex]['stop'])):
            index = sv - gnssSysData[sysIndex]['start']
            if( (len(tokens) > 17) and (int(tokens[16]) >= Thresh) and (int(tokens[17]) >= Thresh) ):
              svData[index]['baseEpochs_All'] = int(tokens[12])
              svData[index]['roverEpochs_All'] = int(tokens[13])
              svData[index]['baseEpochs_PLL'] = int(tokens[14])
              svData[index]['roverEpochs_PLL'] = int(tokens[15])
              svData[index]['year'] = int(tokens[0])
              svData[index]['month'] = int(tokens[1])
              svData[index]['day'] = int(tokens[2])
              svData[index]['meanCNo'] = float(tokens[8])
              svData[index]['sigCNo'] = float(tokens[9])
              if(len(tokens) > 19):
                svData[index]['baseGaps'] = int(tokens[18])
                svData[index]['roverGaps'] = int(tokens[19])
                if(len(tokens) > 21):
                  svData[index]['baseVersion'] = convertDate(tokens[20])
                  svData[index]['roverVersion'] = convertDate(tokens[21])


    for svIndex in range(len(svData)):
      if(     (svData[svIndex]['baseEpochs_All'] is not None)
          and ( (svData[svIndex]['baseEpochs_All'] + svData[svIndex]['roverEpochs_All']) > 0) ): 
        if((svData[svIndex]['baseEpochs_PLL']+svData[svIndex]['roverEpochs_PLL']) > 0):
          PLLRatio = 100.0 * (svData[svIndex]['baseEpochs_PLL'] - svData[svIndex]['roverEpochs_PLL']) / (0.5*float(svData[svIndex]['baseEpochs_PLL']+svData[svIndex]['roverEpochs_PLL']))
        else:
          PLLRatio = 0.0

        if(PLLRatio>5.0):
          webStr += "<tr style='background-color:lightgreen;'>"
        elif(PLLRatio<-5.0):
          webStr += "<tr style='background-color:orange;'>"
        else:
          webStr += "<tr>"
        webStr += "<td>%d</td>" % (svIndex + gnssSysData[sysIndex]['start'])

        webLink = webRoot + gnssSysData[sysIndex]['sysName'] + '-' + str(svIndex + gnssSysData[sysIndex]['start']) + '.html'
        webStr += (  "<td style='white-space: nowrap;'>"
                   + "<a href='" + webLink + "' target='_blank'>"
                   + "<img class='click' width='20px' src='http://quark.eng.trimble.com/DataDump/DashBoard/DataTable.png'></img>" 
                   + "</a>&nbsp;")
        webStr += str(svData[svIndex]['year']) + '-' + str(svData[svIndex]['month']).zfill(2) + '-' + str(svData[svIndex]['day']).zfill(2) + '</td>'
        
        latestTime = datetime.datetime(svData[svIndex]['year'], svData[svIndex]['month'], svData[svIndex]['day'])
        dayNum = int((latestTime - startTime).total_seconds() / 86400)
        webStr += '<td>%d</td>' % dayNum
        
        dayStr =  str(svData[svIndex]['year'])  + str(svData[svIndex]['month']).zfill(2) + str(svData[svIndex]['day']).zfill(2)
        webLink = webRoot + dayStr + '/' + dayStr + gnssSysData[sysIndex]['sysName'] + '-' + str(svIndex + gnssSysData[sysIndex]['start']) + '.png'

        field = "%.3f" % svData[svIndex]['meanCNo']
        webStr = webStr + (  "<td style='white-space: nowrap;'><a onclick='toggleImg(this)' href='#'>" 
                           + "<img class='click' width='20px' src='http://quark.eng.trimble.com/DataDump/DashBoard/graph.png'></img>" 
                           + "<img class='links' src='" 
                           + webLink + "'>"
                           + "</img>"
                           + "</a>&nbsp;"
                           + field + "</td>")
        
        webStr += "<td>%.3f</td>" % svData[svIndex]['sigCNo']

        webLink = webRoot + gnssSysData[sysIndex]['sysName'] + '-SV-' + str(svIndex + gnssSysData[sysIndex]['start']) + '.png'
        field = "%.3f" % (100.0 * (svData[svIndex]['baseEpochs_All'] - svData[svIndex]['roverEpochs_All']) / (0.5*float(svData[svIndex]['baseEpochs_All']+svData[svIndex]['roverEpochs_All'])))
          
        webStr = webStr + (  "<td style='white-space: nowrap;'><a onclick='toggleImg(this)' href='#'>" 
                           + "<img class='click' width='20px' src='http://quark.eng.trimble.com/DataDump/DashBoard/graph.png'></img>" 
                           + "<img class='links' src='" 
                           + webLink + "'>"
                           + "</img>"
                           + "</a>&nbsp;"
                           + field + "</td>")

        webLink = webRoot + 'PLL_' + gnssSysData[sysIndex]['sysName'] + '-SV-' + str(svIndex + gnssSysData[sysIndex]['start']) + '.png'
        field = "%.3f" % PLLRatio

        webStr = webStr + (  "<td style='white-space: nowrap;'><a onclick='toggleImg(this)' href='#'>" 
                           + "<img class='click' width='20px' src='http://quark.eng.trimble.com/DataDump/DashBoard/graph.png'></img>" 
                           + "<img class='links' src='" 
                           + webLink + "'>"
                           + "</img>"
                           + "</a>&nbsp;"
                           + field + "</td>")

        if(svData[svIndex]['baseGaps'] is not None):
          webStr +=  "<td>" + str(svData[svIndex]['baseGaps']) + "</td>"
        else:
          webStr +=  "<td>-</td>"
        if(svData[svIndex]['roverGaps'] is not None):
          webStr +=  "<td>" + str(svData[svIndex]['roverGaps']) + "</td>"
        else:
          webStr +=  "<td>-</td>"

        if(svData[svIndex]['baseVersion'] is not None):
          webStr +=  "<td>" + str(svData[svIndex]['baseVersion']) + "</td>"
        else:
          webStr +=  "<td>-</td>"

        if(svData[svIndex]['roverVersion'] is not None):
          webStr +=  "<td>" + str(svData[svIndex]['roverVersion']) + "</td>"
        else:
          webStr +=  "<td>-</td>"

        webStr += "</tr>"

  webStr = webStr + ("</tbody>")
  webStr = webStr + ("</table>")

  webStr += "<p><a href='" + webRoot + gnssSysData[sysIndex]['sysName'] + "-Epochs.png'>Absolute Epochs (all data above 7 degrees)</a><br/>"
  webStr += "<a href='" + webRoot + gnssSysData[sysIndex]['sysName'] + "-Relative.png'>Relative Epochs (all data above 7 degrees)</a></p>"

  webStr += "<h4>Notes</h4>"
  webStr += "<p><sup>1</sup>Date of data shown, last day processed when there was approximately a full day of data from both stations</br>"
  webStr += "<sup>2</sup>C/No different and standard deviation from all common epochs<br/>"
  webStr += "<sup>3</sup>Relative<sup>5</sup> percentage of all reported epochs after a filter of 7 degrees</br/>"
  webStr += "<sup>4</sup>Relative<sup>5</sup> percentage of PLL only data after a filter of 7 degrees<br/>"
  webStr += "<sup>5</sup>The relative formula is 100*(baseEpochs-roverEpochs)/mean(BaseEpochs,RoverEpochs) %<br/>"
  webStr += "<sup>6</sup>Background color is green when (PLL ratio) is > 5% ('base' has more epochs), and orange when (PLL Ratio) < -5% ('rover' has more epochs.)<br/>"
  webStr += "<sup>7</sup>Number of data gaps > 20 seconds</p>"

  webStr = webStr + ("</div>")
  webStr = webStr + ("</div>")

  webStr = webStr + ("</div>")
  webStr = webStr + ("</section>")
  webStr = webStr + ("{% endblock %}")
  webStr = webStr + ("{% block userscript %}")
  webStr = webStr + ("<script>")
  webStr = webStr + ("</script>")
  webStr = webStr + ("<script src='static/js/app.js'></script>")
  webStr = webStr + ("{% endblock %}")


  return render_template_string(Markup(webStr))



@app.route("/NoPiSlips.html")
def NoPiSlips():

  webStr = ("{% extends 'menu.html' %}")
  webStr = webStr + ("{% block main %}")
  webStr = webStr + ("<section id='content'>")
  webStr = webStr + ("<div class='row'>")
  webStr = webStr + "<div class='col-sm-10'>"
  webStr = webStr + ("<h3>NoPi Cycle Slips (BD940<->BD940 Short)</h3>")
  webStr = webStr + ("<div class='table-responsive'>")
  webStr = webStr + ("<table id='data-table-basic' class='table table-striped'>")
  webStr = webStr + ("<thead>")
  webStr = webStr + ("<tr>")
  webStr = webStr + ("<th>Combo</th>")
  webStr = webStr + ("<th>Epochs</th>")
  webStr = webStr + ("<th>Slip Events</th>")
  webStr = webStr + ("<th>Slip Epochs</th>")
  webStr = webStr + ("<th>Events [PPM]</th>")
  webStr = webStr + ("<th>Epochs [PPM]</th>")
  webStr = webStr + ("<th>Classic</th>")
  webStr = webStr + ("<th>Drift</th>")
  webStr = webStr + ("<th>Gang Lu</th>")
  webStr = webStr + ("<th>Uncharacterized</th>")
  webStr = webStr + ("</tr>")
  webStr = webStr + ("</thead>")
  webStr = webStr + ("<tbody>")

  SlipData = []
  comboList = []
  maxCombo = 0

  filename = '/net/higgs/mnt/data_drive/DataDump/DashBoard/ScanMTB/NoPiSlips.txt'
  if(os.path.isfile(filename)): 
    with open(filename,'r') as f:
      for line in f:
        data = line.rstrip().split()
        if(data[1] not in comboList):
            comboList.append(data[1])
            SlipData.append([])
            maxCombo += 1
            
        try:
          index = comboList.index(data[1])
          SlipData[index].append(line)
        except:
          print('problem with',data[1])

  for combos in range(maxCombo):
    webStr = webStr + "</tr>"

    if(len(SlipData[combos]) > 0):
      dataCurrent = SlipData[combos][-1].rstrip().split()
      if(int(dataCurrent[3])):
        print(dataCurrent[0].split("WWW")[-1])

        webLink = 'http://quark.eng.trimble.com/DataDump/DashBoard/ScanMTB/DDSlipsCombo-' + comboList[combos] + '.html'
        webStr = webStr + (  "<td style='white-space: nowrap;'>"
                           + "<a href='" + webLink + "' target='_blank'>"
                           + "<img class='click' width='20px' src='http://quark.eng.trimble.com/DataDump/DashBoard/DataTable.png'></img>" 
                           + "</a>")


        webLink = 'http://teller.eng.trimble.com/GPSRxEng' + dataCurrent[0].split("WWW")[-1] + '/NoPiDI_Top.html'
        #webStr = webStr + "<a href='" + webLink + "' target='_blank'>&nbsp;" + combo2str.get(combos) + "</a></td>"
        webStr = webStr + "<a href='" + webLink + "' target='_blank'>&nbsp;" + comboList[combos] + "</a></td>"
        #webStr = webStr + "<a href='" + webLink + "' target='_blank'>&nbsp;" + str(combos) + "</a></td>"

        # Number of epochs
        webStr = webStr + (  "<td style='white-space: nowrap;'><a onclick='toggleImg(this)' href='#'>" 
                           + "<img class='click' width='20px' src='http://quark.eng.trimble.com/DataDump/DashBoard/graph.png'></img>" 
                           + "<img class='links' src='http://quark.eng.trimble.com/DataDump/DashBoard/ScanMTB/Epochs-"
                           + comboList[combos] + ".png'>"
                           + "</img>"
                           + "</a>&nbsp;"
                           + dataCurrent[3] + "</td>")

        # Slip count
        webStr = webStr + (  "<td style='white-space: nowrap;'><a onclick='toggleImg(this)' href='#'>" 
                           + "<img class='click' width='20px' src='http://quark.eng.trimble.com/DataDump/DashBoard/graph.png'></img>" 
                           + "<img class='links' src='http://quark.eng.trimble.com/DataDump/DashBoard/ScanMTB/SlipEvents-"
                           + comboList[combos] + ".png'>"
                           + "</img>"
                           + "</a>&nbsp;"
                           + dataCurrent[10] + "</td>")
        
        # Slip Epochs
        webStr = webStr + (  "<td style='white-space: nowrap;'><a onclick='toggleImg(this)' href='#'>" 
                           + "<img class='click' width='20px' src='http://quark.eng.trimble.com/DataDump/DashBoard/graph.png'></img>" 
                           + "<img class='links' src='http://quark.eng.trimble.com/DataDump/DashBoard/ScanMTB/SlipEpochs-"
                           + comboList[combos] + ".png'>"
                           + "</img>"
                           + "</a>&nbsp;"
                           + dataCurrent[11] + "</td>")
        
        # Slip count as a PPM
        webStr = webStr + (  "<td style='white-space: nowrap;'><a onclick='toggleImg(this)' href='#'>" 
                           + "<img class='click' width='20px' src='http://quark.eng.trimble.com/DataDump/DashBoard/graph.png'></img>" 
                           + "<img class='links' src='http://quark.eng.trimble.com/DataDump/DashBoard/ScanMTB/SlipEventsPPM-"
                           + comboList[combos] + ".png'>"
                           + "</img>"
                           + "</a>&nbsp;"
                           + str.format('{0:.4f}',float(dataCurrent[10])*1e6/float(dataCurrent[3])) + "</td>")
        
        # Slip Epochs as a PPM
        webStr = webStr + (  "<td style='white-space: nowrap;'><a onclick='toggleImg(this)' href='#'>" 
                           + "<img class='click' width='20px' src='http://quark.eng.trimble.com/DataDump/DashBoard/graph.png'></img>" 
                           + "<img class='links' src='http://quark.eng.trimble.com/DataDump/DashBoard/ScanMTB/SlipEpochsPPM-"
                           + comboList[combos] + ".png'>"
                           + "</img>"
                           + "</a>&nbsp;"
                           + str.format('{0:.4f}',float(dataCurrent[11])*1e6/float(dataCurrent[3])) + "</td>")
        
        # Classic Slips
        webStr = webStr + "<td>" + dataCurrent[12] + "</td>"
        
        # Drift
        webStr = webStr + "<td>" + dataCurrent[13] + "</td>"
        
        # Gang Lu Effect
        webStr = webStr + "<td>" + dataCurrent[14] + "</td>"
        
        # Uncharacterized
        webStr = webStr + "<td>" + dataCurrent[15] + "</td>"
      
    webStr = webStr + "</tr>"

  webStr = webStr + ("</tbody>")
  webStr = webStr + ("</table>")
  webStr = webStr + ("</div>")
  webStr = webStr + ("</div>")

  webStr = webStr + ("</div>")
  webStr = webStr + ("</section>")
  webStr = webStr + ("{% endblock %}")
  webStr = webStr + ("{% block userscript %}")
  webStr = webStr + ("<script>")
  webStr = webStr + ("</script>")
  webStr = webStr + ("<script src='static/js/app.js'></script>")
  webStr = webStr + ("{% endblock %}")


  return render_template_string(Markup(webStr))


@app.route("/Reacquire.html")
def Reacquire():

  # Construct a list of receiver and signal type name strings
  filedir = '/net/higgs/mnt/data_drive/DataDump/DashBoard/*-reAcq.txt'
  list_of_files = glob.glob(filedir)
  rxList = []
  sigList = []
  for fname in list_of_files:
    # Drop the path and split at '-' to seperate:
    tmp = fname.split('DashBoard/')
    tmp = tmp[1].split('-')
    # The receiver name, first element after the path
    if not(tmp[0] in rxList):
      rxList.append(tmp[0])
    # The signal name, second element onwards but drop the -reAcq.txt ending
    sig = tmp[1]
    for x in range(2,len(tmp)-1):
      sig += '-'+tmp[x]
    if not(sig in sigList):
      sigList.append(sig)

  rxList.sort()
  sigList.sort()

  webStr = ("{% extends 'menu.html' %}")
  webStr = webStr + ("{% block main %}")
  webStr = webStr + ("<section id='content'>")
  webStr = webStr + ("<div class='row'>")
  webStr = webStr + "<div class='col-sm-6'>"
  webStr = webStr + ("<h3>95% Reacquisition Time [Sec]</h3>")
  webStr = webStr + ("<div class='table-responsive'>")
  webStr = webStr + ("<table id='data-table-basic' class='table table-striped'>")
  webStr = webStr + ("<thead>")
  webStr = webStr + ("<tr>")
  webStr = webStr + ("<th>Type</th>")
  # One column in the table per receiver
  for rx in range(len(rxList)):
    webStr = webStr + ("<th>" + rxList[rx] + "</th>")
  webStr = webStr + ("</tr>")
  webStr = webStr + ("</thead>")
  webStr = webStr + ("<tbody>")

  hyst = 5.0;

  # Loop through all signal types found
  for signal in range(len(sigList)):

    webStr = webStr + "<tr>"
    webStr = webStr + (  "<td><a onclick='toggleImg(this)' href='#'>" 
                       + sigList[signal]
                       + "<img class='links' src='http://quark.eng.trimble.com/DataDump/DashBoard/"
                       + "TTReacquire-" + sigList[signal] + ".png'"
                       + "</img></a></td>")

    # Loop through all receivers found
    for rx in range(len(rxList)):
      # Populate the table for this receiver/signal
      AcqTime = []
      filename = '/net/higgs/mnt/data_drive/DataDump/DashBoard/' + rxList[rx] + '-' + sigList[signal] + '-reAcq.txt'
      if(os.path.isfile(filename)): 
        with open(filename,'r') as f:
          for line in f:
            data = line.rstrip().split()
            AcqTime.append(float(data[1]))

        if(len(AcqTime) >= 2):
          webStr = webStr + "<td>" + dataCompare(AcqTime[-1],AcqTime[-2],hyst) + "</td>"
        else:
          webStr = webStr + "<td>&nbsp;</td>"
      else:
        # No file, this receiver/signal combination doesn't exist
        webStr = webStr + "<td>&nbsp;</td>"

    webStr = webStr + "</tr>"

  webStr = webStr + ("</tbody>")
  webStr = webStr + ("</table>")
  webStr = webStr + ("</div>")
  webStr = webStr + ("</div>")

  webStr = webStr + ("</div>")
  webStr = webStr + ("<p>Hysteresis = " + str(hyst) + "%</p>")
  webStr = webStr + ("</section>")
  webStr = webStr + ("{% endblock %}")
  webStr = webStr + ("{% block userscript %}")
  webStr = webStr + ("<script>")
  webStr = webStr + ("</script>")
  webStr = webStr + ("<script src='static/js/app.js'></script>")
  webStr = webStr + ("{% endblock %}")


  return render_template_string(Markup(webStr))





@app.route("/ReacquireSlips.html")
def ReacquireSlips():

  # Construct a list of receiver and signal type name strings
  filedir = '/net/higgs/mnt/data_drive/DataDump/DashBoard/*-reAcq.txt'
  list_of_files = glob.glob(filedir)
  rxList = []
  sigList = []
  for fname in list_of_files:
    # Drop the path and split at '-' to seperate:
    tmp = fname.split('DashBoard/')
    tmp = tmp[1].split('-')
    # The receiver name, first element after the path
    if not(tmp[0] in rxList):
      rxList.append(tmp[0])
    # The signal name, second element onwards but drop the -reAcq.txt ending
    sig = tmp[1]
    for x in range(2,len(tmp)-1):
      sig += '-'+tmp[x]
    if not(sig in sigList):
      sigList.append(sig)

  rxList.sort()
  sigList.sort()

  webStr = ("{% extends 'menu.html' %}")
  webStr = webStr + ("{% block main %}")
  webStr = webStr + ("<section id='content'>")
  webStr = webStr + ("<div class='row'>")
  webStr = webStr + "<div class='col-sm-6'>"
  webStr = webStr + ("<h3>Reacquisition - Slips per Acquisition</h3>")
  webStr = webStr + ("<div class='table-responsive'>")
  webStr = webStr + ("<table id='data-table-basic' class='table table-striped'>")
  webStr = webStr + ("<thead>")
  webStr = webStr + ("<tr>")
  webStr = webStr + ("<th>Type</th>")
  # One column in the table per receiver
  for rx in range(len(rxList)):
    webStr = webStr + ("<th>" + rxList[rx] + "</th>")
  webStr = webStr + ("</tr>")
  webStr = webStr + ("</thead>")
  webStr = webStr + ("<tbody>")

  hyst = 0.01;

  # Loop through all signal types found
  for signal in range(len(sigList)):

    webStr = webStr + "<tr>"
    webStr = webStr + (  "<td><a onclick='toggleImg(this)' href='#'>" 
                       + sigList[signal]
                       + "<img class='links' src='http://quark.eng.trimble.com/DataDump/DashBoard/"
                       + "TTReacquireSlips-" + sigList[signal] + ".png'"
                       + "</img></a></td>")

    # Loop through all receivers found
    for rx in range(len(rxList)):
      # Populate the table for this receiver/signal
      SlipRate= []
      filename = '/net/higgs/mnt/data_drive/DataDump/DashBoard/' + rxList[rx] + '-' + sigList[signal] + '-reAcq.txt'
      if(os.path.isfile(filename)): 
        with open(filename,'r') as f:
          for line in f:
            data = line.rstrip().split()
            SlipRate.append(float(data[6]))

        if(len(SlipRate) >= 2):
          if(SlipRate[-1] < 0.05):
            webStr = webStr + "<td>" + str.format('{0:.2f}',SlipRate[-1]) + "</td>"
          elif(SlipRate[-1] > 0.2):
            webStr = webStr + "<td><font color='red'>" + str.format('{0:.2f}',SlipRate[-1]) + "</font></td>"
          else:
            webStr = webStr + "<td>" + dataCompare(SlipRate[-1],SlipRate[-2],hyst) + "</td>"
        else:
          webStr = webStr + "<td>&nbsp;</td>"
      else:
        # No file, this receiver/signal combination doesn't exist
        webStr = webStr + "<td>&nbsp;</td>"

    webStr = webStr + "</tr>"

  webStr = webStr + ("</tbody>")
  webStr = webStr + ("</table>")
  webStr = webStr + ("</div>")
  webStr = webStr + ("</div>")

  webStr = webStr + ("</div>")
  webStr = webStr + ("</section>")
  webStr = webStr + ("{% endblock %}")
  webStr = webStr + ("{% block userscript %}")
  webStr = webStr + ("<script>")
  webStr = webStr + ("</script>")
  webStr = webStr + ("<script src='static/js/app.js'></script>")
  webStr = webStr + ("{% endblock %}")


  return render_template_string(Markup(webStr))


environs = [ "Open", "Freeway", "SF", "SJ", "Urban", "Walk" ]
env2Thres= { 'Open'    : 2.0,
             'Freeway' : 2.1, 
             'SF'      : 100.0, 
             'SJ'      : 6.0, 
             'Urban'   : 3.5, 
             'Walk'    : 3.0}

@app.route("/SNPC.html")
def SNPC():
  webStr = ("{% extends 'menu.html' %}")
  webStr = webStr + ("{% block main %}")
  webStr = webStr + ("<section id='content'>")
  webStr = webStr + ("<div class='row'>")
  webStr = webStr + "<div class='col-sm-6'>"
  webStr = webStr + ("<h3>Loci-SBAS</h3>")
  webStr = webStr + ("<div class='table-responsive'>")
  webStr = webStr + ("<table id='data-table-basic' class='table table-striped'>")
  webStr = webStr + ("<thead>")
  webStr = webStr + ("<tr>")
  webStr = webStr + ("<th data-column-id='date'>Run</th>")
  webStr = webStr + ("<th data-column-id='id'>Environment</th>")
  webStr = webStr + ("<th data-column-id='Horz'>Horz 95%[m]</th>")
  webStr = webStr + ("<th data-column-id='Hgt'>Hgt 95%[m]</th>")
  webStr = webStr + ("</tr>")
  webStr = webStr + ("</thead>")
  webStr = webStr + ("<tbody>")

  # ToDo - move the trend plotting to the SNPC regression system so we
  # don't have to generate it here on the web response thread
  for env in range(len(environs)):
    RunNum = []
    Horz95 = []
    Hgt95  = []
    filename = '/net/quark/mnt/data_drive/DataDump/SNPC-Reg/results/SBAS-' + environs[env] + '.summary.txt'
    #print filename
    if(os.path.isfile(filename)): 
      with open(filename,'r') as f:
        for line in f:
          data = line.rstrip().split()
          if(data[-1] == 'Loci'):
            RunNum.append(int(data[0]))
            Horz95.append(float(data[4]))
            Hgt95.append(float(data[16]))
    
      webStr = webStr + "<tr><td>" + str(RunNum[-1]) + "</td>" 
      webStr = webStr + (  "<td><a onclick='toggleImg(this)' href='#'>" 
                         + environs[env] 
                         + "<img class='links' src='http://quark.eng.trimble.com/DataDump/DashBoard/Loci"
                         + environs[env] + ".95.png'"
                         + "</img></a></td>")


      # Output the Horzontal and Vertical 95% colouring based on whether
      # it is better or worse than the last run
      webStr = webStr + "<td>" + dataCompare(Horz95[-1],Horz95[-2],1.0) + "</td>"
      webStr = webStr + "<td>" + dataCompare(Hgt95[-1],Hgt95[-2],1.0) + "</td>"
      webStr = webStr + "</tr>"

  webStr = webStr + ("</tbody>")
  webStr = webStr + ("</table>")
  webStr = webStr + ("</div>")
  webStr = webStr + ("</div>")

  webStr = webStr + ("</div>")
  webStr = webStr + ("</section>")
  webStr = webStr + ("{% endblock %}")
  webStr = webStr + ("{% block userscript %}")
  webStr = webStr + ("<script>")
  webStr = webStr + ("</script>")
  webStr = webStr + ("<script src='static/js/app.js'></script>")
  webStr = webStr + ("{% endblock %}")
  
  return render_template_string(Markup(webStr))

# Teqc threshold
MP2Thresh = { 'MP12-GLN_L1CA_L2CA.png'           : 0.42,
              'MP12-GLN_L1P_L2P.png'             : 0.40,
              'MP12-GPS_L1CA_L2C.png'            : 0.26,
              'MP12-GPS_L1CA_L2E.png'            : 0.26,
              'MP15-GPS_L1CA_L5__GAL_E1_E5A.png' : 0.24,
              'MP16-GAL_E1_E6__BDS_B1_B3.png'    : 0.25,
              'MP17-GAL_E1_E5B__BDS_B1_B2.png'   : 0.26,
              'MP18-GAL_E1_E5AltBOC.png'         : 0.23,
              'MP21-GLN_L2CA_L1CA.png'           : 0.52,
              'MP21-GLN_L2P_L1P.png'             : 0.22,
              'MP21-GPS_L2C_L1CA.png'            : 0.27,
              'MP21-GPS_L2E_L1CA.png'            : 0.31,
              'MP51-GPS_L5_L1CA__GAL_E5A_E1.png' : 0.23,
              'MP61-GAL_E6_E1__BDS_B3_B1.png'    : 0.29,
              'MP71-GAL_E5B_E1__BDS_B2_B1.png'   : 0.25,
              'MP81-GAL_E5AltBOC_E1.png'         : 0.08}

TeqcRXData = [ "BD935_1_ROVER",
               "BD935_2_ROVER",
               "BD935_3_ROVER",
               "BD940_1_ROVER",
               "BD940_2_ROVER",
               "BD940_3_ROVER",
               "BD992_ROVER_A0",
               "BD992_ROVER_A1"]

Antenna = [ "RS3",
            "RS9",
            "RS9",
            "RS3",
            "RS9",
            "RS9",
            "RS3",
            "RS9"]

file2Str = {'-noGLN-L2E-MP12.txt' : 'MP12 GPS (L1 CA / L2E)',
            '-noGLN-L2C-MP12.txt' : 'MP12 GPS (L1 CA / L2C)',
            '-GLN_CA-MP12.txt'    : 'MP12 GLN (L1 CA / L2 CA)',
            '-GLN_P-MP12.txt'     : 'MP12 GLN (L1 P / L2 P)',
            '-noGLN-L2E-MP15.txt' : 'MP15 GPS (L1 CA / L5) | GAL (E1 / E5A)',
            '-noGLN-L2E-MP16.txt' : 'MP16 GAL (E1 / E6) | BDS (B1 / B3)',
            '-noGLN-L2E-MP17.txt' : 'MP17 GAL (E1 / E5B) | BDS (B1 / B2)',
            '-noGLN-L2E-MP18.txt' : 'MP18 GAL E1 / E5AltBOC',

            '-noGLN-L2E-MP21.txt' : 'MP21 GPS (L2E / L1 CA)',
            '-noGLN-L2C-MP21.txt' : 'MP21 GPS (L2C / L1 CA)',
            '-GLN_CA-MP21.txt'    : 'MP21 GLN (L2 CA / L1 CA)',
            '-GLN_P-MP21.txt'     : 'MP21 GLN (L2 P / L1 P)',

            '-noGLN-L2E-MP51.txt' : 'MP51 GPS (L5 / L1 CA) | GAL (E5A / E1)',
            '-noGLN-L2E-MP61.txt' : 'MP61 GAL (E6 / E1) | BDS (B3 / B2)',
            '-noGLN-L2E-MP71.txt' : 'MP71 GAL (E5B / E51) | BDS (B2 / B1)',
            '-noGLN-L2E-MP81.txt' : 'MP81 GAL (E5AltBOC / E1)' }


file2name = {'-noGLN-L2E-MP12.txt' : 'MP12-GPS_L1CA_L2E',
             '-noGLN-L2C-MP12.txt' : 'MP12-GPS_L1CA_L2C',
             '-GLN_CA-MP12.txt'    : 'MP12-GLN_L1CA_L2CA',
             '-GLN_P-MP12.txt'     : 'MP12-GLN_L1P_L2P',
             '-noGLN-L2E-MP15.txt' : 'MP15-GPS_L1CA_L5__GAL_E1_E5A',
             '-noGLN-L2E-MP16.txt' : 'MP16-GAL_E1_E6__BDS_B1_B3',
             '-noGLN-L2E-MP17.txt' : 'MP17-GAL_E1_E5B__BDS_B1_B2',
             '-noGLN-L2E-MP18.txt' : 'MP18-GAL_E1_E5AltBOC',

             '-noGLN-L2E-MP21.txt' : 'MP21-GPS_L2E_L1CA',
             '-noGLN-L2C-MP21.txt' : 'MP21-GPS_L2C_L1CA',
             '-GLN_CA-MP21.txt'    : 'MP21-GLN_L2CA_L1CA',
             '-GLN_P-MP21.txt'     : 'MP21-GLN_L2P_L1P',
             '-noGLN-L2E-MP51.txt' : 'MP51-GPS_L5_L1CA__GAL_E5A_E1',
             '-noGLN-L2E-MP61.txt' : 'MP61-GAL_E6_E1__BDS_B3_B1',
             '-noGLN-L2E-MP71.txt' : 'MP71-GAL_E5B_E1__BDS_B2_B1',
             '-noGLN-L2E-MP81.txt' : 'MP81-GAL_E5AltBOC_E1' }
  

@app.route("/getTestTrack.json")
def getTestTrack():
  gotTestTrack = False
  count = 0
  jsonData = []

  # if we fail to get the testtrack data try a few times if
  # that fails (e.g. when it is down during a backup) 
  # return empty json data.
  while( (gotTestTrack == False) and (count < 5) ):
    try:
      jsonData = jsonify(getTTData.getSPRTable('Emerald'))
      gotTestTrack = True
    except:
      time.sleep(1)
    
    count = count + 1

  return jsonData
  
@app.route("/getTestErrors.json")
def getTestErrors():
  [errData, slackStr, chatStr] = testThreshold.testData()
  return jsonify(errData)

@app.route("/getAntTemp.json")
def getAntTemp():
  return jsonify(AntTemp)


@app.route("/getFileInfo.json")
def getFileInfo():
  fileInfo = []

  now = datetime.datetime.now()

  filedir = '/net/quark/mnt/data_drive/DataDump/TeqcStingerLab/' + str(now.year) +'/*'
  list_of_files = glob.glob(filedir)
  latest_file = max(list_of_files, key=os.path.getmtime)
  filetime = os.path.getmtime(latest_file)
  d = {}
  d['type'] = 'Teqc'
  d['time'] = datetime.datetime.fromtimestamp(filetime).strftime('%Y-%m-%d %H:%M:%S')
  fileInfo.append(d)

  filedir = '/net/quark/mnt/data_drive/DataDump/2017-09-DHS-Spoofing/2017-09-11/*' 
  list_of_files = glob.glob(filedir)
  latest_file = max(list_of_files, key=os.path.getmtime)
  filetime = os.path.getmtime(latest_file)
  d = {}
  d['type'] = 'Anti-Spoofing'
  d['time'] = datetime.datetime.fromtimestamp(filetime).strftime('%Y-%m-%d %H:%M:%S')
  fileInfo.append(d)

  filedir = '/net/daffy/mnt/data_drive/' + str(now.year) + '/*' 
  list_of_files = glob.glob(filedir)
  latest_file = max(list_of_files, key=os.path.getmtime)
  filetime = os.path.getmtime(latest_file)
  d = {}
  d['type'] = 'Double Differences'
  d['time'] = datetime.datetime.fromtimestamp(filetime).strftime('%Y-%m-%d %H:%M:%S')
  fileInfo.append(d)

  filedir = '/net/quark/mnt/data_drive/DataDump/SNPC-Reg/results/*'
  list_of_files = glob.glob(filedir)
  latest_file = max(list_of_files, key=os.path.getmtime)
  filetime = os.path.getmtime(latest_file)
  d = {}
  d['type'] = 'SNPC'
  d['time'] = datetime.datetime.fromtimestamp(filetime).strftime('%Y-%m-%d %H:%M:%S')
  fileInfo.append(d)
  
  return jsonify(fileInfo)


AnubisRXData = [ { "File":"BD935_1_ROVER", "RX":"BD935_1_ROVER",  "Ant":"RS3", "symb":"bx", "flag":"-a0"},
                 { "File":"BD935_2_ROVER", "RX":"BD935_2_ROVER",  "Ant":"RS9", "symb":"rx", "flag":"-a0"}, 
                 { "File":"BD935_3_ROVER", "RX":"BD935_3_ROVER",  "Ant":"RS9", "symb":"cx", "flag":"-a0"}, 
                 { "File":"BD940_1_ROVER", "RX":"BD940_1_ROVER",  "Ant":"RS3", "symb":"gx", "flag":"-a0"},
                 { "File":"BD940_2_ROVER", "RX":"BD940_2_ROVER",  "Ant":"RS9", "symb":"kx", "flag":"-a0"}, 
                 { "File":"BD940_3_ROVER", "RX":"BD940_3_ROVER",  "Ant":"RS9", "symb":"yx", "flag":"-a0"}, 
                 { "File":"BD992_ROVER",   "RX":"BD992_ROVER_A0", "Ant":"RS3", "symb":"b*", "flag":"-a0"},
                 { "File":"BD992_ROVER",   "RX":"BD992_ROVER_A1", "Ant":"RS9", "symb":"r*", "flag":"-a1"}]

AnubisSigs = [ {"sys":"GPS","type":"1C"},
               {"sys":"GPS","type":"2W"},
               {"sys":"GPS","type":"2X"},
               {"sys":"GPS","type":"5X"},
               {"sys":"GAL","type":"1X"},
               {"sys":"GAL","type":"5X"},
               {"sys":"GAL","type":"6X"},
               {"sys":"GAL","type":"7X"},
               {"sys":"GAL","type":"8X"},
               {"sys":"GLO","type":"1C"},
               {"sys":"GLO","type":"1P"},
               {"sys":"GLO","type":"2C"},
               {"sys":"GLO","type":"2P"},
               {"sys":"GLO","type":"3X"},
               {"sys":"BDS","type":"2I"},
               {"sys":"BDS","type":"2X"},
               {"sys":"BDS","type":"6I"},
               {"sys":"BDS","type":"7I"},
               {"sys":"QZS","type":"1C"},
               {"sys":"QZS","type":"1Z"},
               {"sys":"QZS","type":"2X"},
               {"sys":"QZS","type":"5X"}]

@app.route("/Anubis.html")
def Anubis():
  webStr = ("{% extends 'menu.html' %}")
  webStr = webStr + ("{% block main %}")
  webStr = webStr + ("<section id='content'>")
  webStr = webStr + ("<div class='row'>")
  webStr = webStr + "<div class='col-sm-10'>"
  webStr = webStr + ("<h3>Anubis MPXX [m]</h3>")
  webStr = webStr + ("<div class='table-responsive'>")
  webStr = webStr + ("<table id='data-table-basic' class='table table-striped'>")
  webStr = webStr + ("<thead>")
  webStr = webStr + ("<tr>")
  webStr = webStr + ("<th data-column-id='id'>Type</th>")
  for rx in range(len(AnubisRXData)):
    webStr = webStr + ("<th>" + AnubisRXData[rx]['RX'].replace('_ROVER','').replace('_','(') +")<br/>" + AnubisRXData[rx]['Ant'] + "</th>")
  webStr = webStr + ("</tr>")
  webStr = webStr + ("</thead>")
  webStr = webStr + ("<tbody>")

  # loop around for each signal/system type
  for i in range(len(AnubisSigs)):
    string = AnubisSigs[i]['sys'] + '-' + AnubisSigs[i]['type']
    webStr = webStr + (  "<tr><td><a onclick='toggleImg(this)' href='#'>" 
                       + string
                       + "<img class='links' src='http://quark.eng.trimble.com/DataDump/AnubisMPX/"
                       + string + ".png'"
                       + "</img></a></td>")

    # Loop around for each receiver that is monitored.
    for rx in range(len(AnubisRXData)):
      DOY = []
      MPX = []
      now = datetime.datetime.now()
      filename = '/net/quark/mnt/data_drive/DataDump/AnubisMPX/' + AnubisRXData[rx]['RX'] + '-' + string + '.txt'
      if(os.path.isfile(filename)): 
        with open(filename,'r') as f:
          for line in f:
            data = line.rstrip().split()
            DOY.append(str(data[0]) + '-' + str(data[1]) + '-' + str(data[2]))
            MPX.append(float(data[4]))

        webStr = webStr + "<td>" + str.format('{0:.2f}',MPX[-1])
        webStr += (   "&nbsp;<a onclick='toggleImg(this)' href='#'>" 
                    + "<img class='click' width='20px' src='http://quark.eng.trimble.com/DataDump/DashBoard/graph.png'></img>" 
                    + "<img class='links' src='http://quark.eng.trimble.com/DataDump/AnubisMPX/"
                    + AnubisRXData[rx]['RX'] + "-" + string + ".png'"
                    + "</img>")
        webStr += "</td>"
      else:
        webStr = webStr + "<td>-</td>"

    webStr = webStr + "</tr>"

  webStr = webStr + ("</tbody>")
  webStr = webStr + ("</table>")
  webStr = webStr + ("</div>")
  webStr = webStr + ("</div>")

  webStr = webStr + ("</div>")
  webStr = webStr + ("</section>")
  webStr = webStr + ("{% endblock %}")
  webStr = webStr + ("{% block userscript %}")
  webStr = webStr + ("<script>")
  webStr = webStr + ("</script>")
  webStr = webStr + ("<script src='static/js/app.js'></script>")
  webStr = webStr + ("{% endblock %}")
  
  return render_template_string(Markup(webStr))


@app.route("/Teqc.html")
def Teqc():
  #return render_template("Teqc.html")
  webStr = ("{% extends 'menu.html' %}")
  webStr = webStr + ("{% block main %}")
  webStr = webStr + ("<section id='content'>")
  webStr = webStr + ("<div class='row'>")
  webStr = webStr + "<div class='col-sm-10'>"
  webStr = webStr + ("<h3>Teqc MPXX [m]</h3>")
  webStr = webStr + ("<div class='table-responsive'>")
  webStr = webStr + ("<table id='data-table-basic' class='table table-striped'>")
  webStr = webStr + ("<thead>")
  webStr = webStr + ("<tr>")
  #webStr = webStr + ("<th data-column-id='date'>Date</th>")
  webStr = webStr + ("<th data-column-id='id'>Type</th>")
  for rx in range(len(TeqcRXData)):
    webStr = webStr + ("<th>" + TeqcRXData[rx].replace('_ROVER','').replace('_','(') +")<br/>" + Antenna[rx] + "</th>")
  webStr = webStr + ("</tr>")
  webStr = webStr + ("</thead>")
  webStr = webStr + ("<tbody>")

  # There are no duplicates so we can invert the dictionary
  ivd = {v: k for k, v in file2name.items()}
  for i in range(len(file2name)):
    key = ivd.get(list(file2name.values())[i])
    string = file2Str.get(key)
    webStr = webStr + (  "<tr><td><a onclick='toggleImg(this)' href='#'>" 
                       #+ file2name.values()[i]
                       + string
                       + "<img class='links' src='http://quark.eng.trimble.com/DataDump/TeqcStingerLab/"
                       + list(file2name.values())[i] + ".png'"
                       + "</img></a></td>")

    Thresh = MP2Thresh.get(list(file2name.values())[i] + '.png')

    for rx in range(len(TeqcRXData)):

      DOY = []
      MPX = []
      now = datetime.datetime.now()
      filename = '/net/quark/mnt/data_drive/DataDump/TeqcStingerLab/' + str(now.year)+ '/' + TeqcRXData[rx] + list(file2name.keys())[i]
      if(os.path.isfile(filename)): 
        with open(filename,'r') as f:
          for line in f:
            data = line.rstrip().split()
            DOY.append(str(data[0]) + '-' + str(data[1]) + '-' + str(data[2]))
            MPX.append(float(data[3]))
        if(MPX[-1] > Thresh):
          webStr = webStr + "<td><font color='red'>" + str.format('{0:.2f}',MPX[-1]) + "</font>"
        else:
          webStr = webStr + "<td>" + str.format('{0:.2f}',MPX[-1])
        webStr += (   "&nbsp;<a onclick='toggleImg(this)' href='#'>" 
                    + "<img class='click' width='20px' src='http://quark.eng.trimble.com/DataDump/DashBoard/graph.png'></img>" 
                    + "<img class='links' src='http://quark.eng.trimble.com/DataDump/TeqcStingerLab/"
                    + list(file2name.values())[i] + "-" + TeqcRXData[rx]+ ".png'"
                    + "</img>")
        webStr += "</td>"
      else:
        webStr = webStr + "<td>-</td>"

    webStr = webStr + "</tr>"


  webStr = webStr + ("</tbody>")
  webStr = webStr + ("</table>")
  webStr = webStr + ("</div>")
  webStr = webStr + ("</div>")

  webStr = webStr + ("</div>")
  webStr = webStr + ("</section>")
  webStr = webStr + ("{% endblock %}")
  webStr = webStr + ("{% block userscript %}")
  webStr = webStr + ("<script>")
  webStr = webStr + ("</script>")
  webStr = webStr + ("<script src='static/js/app.js'></script>")
  webStr = webStr + ("{% endblock %}")
  
  return render_template_string(Markup(webStr))


SpoofDays = [ "2017-09-11",
              "2017-09-12",
              "2017-09-13",
              "2017-09-14",
              "2017-09-15"]
SpoofRX = [ "BX940",
            "BX992",
            "R10-2"]


def dataCompare(new,old,threshPercent):
  factor1 = (100.0 + float(threshPercent)) / 100.0
  factor2 = (100.0 - float(threshPercent)) / 100.0
  if(new > (old*factor1)):
    string = "<font color='red'>" + str.format('{0:.2f}',new) + "</font>"
  elif(new < (old*factor2)):
    string = "<font color='green'>" + str.format('{0:.2f}',new) + "</font>"
  else:
    string = str.format('{0:.2f}',new)

  return string

@app.route("/antiSpoof.html")
def antiSpoof():
  # Set a hysteresis threshold, if the values change more than this
  # percentage then we will colour the HTML to indicate whether the
  # solution is better of worse
  hyst = 5.0

  webStr = ("{% extends 'menu.html' %}")
  webStr = webStr + ("{% block main %}")
  webStr = webStr + ("<section id='content'>")
  webStr = webStr + ("<div class='row'>")
  webStr = webStr + "<div class='col-sm-10'>"
  webStr = webStr + ("<h3>Anti-Spoofing [m]</h3>")
  webStr = webStr + ("<div class='table-responsive'>")
  webStr = webStr + ("<table id='data-table-basic' class='table table-striped'>")
  webStr = webStr + ("<thead>")
  webStr = webStr + ("<tr>")
  webStr = webStr + ("<th rowspan='2' data-column-id='date'>Data Set</th>")
  for rx in range(len(SpoofRX)):
    webStr = webStr + ("<th colspan='2'>" + SpoofRX[rx] + "</th>")
  webStr = webStr + ("</tr><tr>")
  for rx in range(len(SpoofRX)):
    webStr = webStr + ("<th>2D 95% | 100%</th>")
    webStr = webStr + ("<th>Hgt 95% | 100%</th>")
  webStr = webStr + ("</tr>")
  webStr = webStr + ("</thead>")
  webStr = webStr + ("<tbody>")

  for i in range(len(SpoofDays)):
    filename = '/net/quark/mnt/data_drive/DataDump/2017-09-DHS-Spoofing/' + SpoofDays[i]
    webStr = webStr + "<td>" + SpoofDays[i] + "</td>"
    for rx in range(len(SpoofRX)):
      twoD_95 = []
      twoD_100 = []
      hgt_95 = []
      hgt_100 = []
      currentFile = filename + '/' + SpoofRX[rx] + '/Trend/Trend-All-SNPC-Loci-WAAS-APP.T04.csv' 
      print(currentFile)
      if(os.path.isfile(currentFile)): 
        with open(currentFile,'r') as f:
          for line in f:
            data = line.rstrip().split(',')
            if(len(data) >= 17):
              twoD_95.append(float(data[12]))
              twoD_100.append(float(data[13]))
              hgt_95.append(float(data[17]))
              hgt_100.append(float(data[18]))

        webLink =  (   'http://quark.eng.trimble.com/DataDump/2017-09-DHS-Spoofing/' 
                       + SpoofDays[i] + '/' 
                       + SpoofRX[rx] + '/'
                       + 'SNPC-Loci-WAAS-APP.T04-stats.html')

        webStr = webStr + (  "<td style='white-space: nowrap;'>"
                           + "<a href='" + webLink + "' target='_blank'>"
                           + "<img class='click' width='20px' src='http://quark.eng.trimble.com/DataDump/DashBoard/DataTable.png'></img>" 
                           + "</a>&nbsp;")

        webStr = webStr + (  "<a onclick='toggleImg(this)' href='#'>" 
                           + "<img class='click' width='20px' src='http://quark.eng.trimble.com/DataDump/DashBoard/graph.png'></img>" 
                           #+ "<img src='graph.png'</img>" 
                           + "<img class='links' src='http://quark.eng.trimble.com/DataDump/DashBoard/AntiSpoof" 
                           + SpoofDays[i] + "." + SpoofRX[rx] + ".png'>"
                           + "</img>"
                           + "</a>\n")
        if(len(twoD_100) >= 2):
          webStr = webStr + ( dataCompare(twoD_95[-1],  twoD_95[-2],hyst)  + " | "
                                    + dataCompare(twoD_100[-1], twoD_100[-2],hyst) + "</td>")
          webStr = webStr + ("<td>" + dataCompare(hgt_95[-1],  hgt_95[-2],hyst)  + " | "
                                    + dataCompare(hgt_100[-1], hgt_100[-2],hyst) + "</td>")
        else:
          webStr = webStr + "-</td><td>-</td>"

      else:
        webStr = webStr + "<td>-</td><td>-</td>"

    webStr = webStr + "</tr>"


  webStr = webStr + ("</tbody>")
  webStr = webStr + ("</table>")
  webStr = webStr + ("</div>")
  webStr = webStr + ("</div>")

  webStr = webStr + ("</div>")
  webStr = webStr + ("<p>Hysteresis = " + str(hyst) + "%</p>")
  webStr = webStr + ("</section>")
  webStr = webStr + ("{% endblock %}")
  webStr = webStr + ("{% block userscript %}")
  webStr = webStr + ("<script>")
  webStr = webStr + ("</script>")
  webStr = webStr + ("<script src='static/js/app.js'></script>")
  webStr = webStr + ("{% endblock %}")
  
  return render_template_string(Markup(webStr))

str2ShortCode2Thres= { 
              'GPS_L1CA'    : 0.64,
              'GPS_L2E'     : 1.00,
              'GPS_L2C'     : 0.72,
              'GPS_L5'      : 0.72,
              'GLONASS_G1C' : 2.4,
              'GLONASS_G1P' : 1.7,
              'GLONASS_G2C' : 1.2,
              'GLONASS_G2P' : 0.72,
              'Galileo_E1'  : 0.66,
              'Galileo_E5A' : 0.75,
              'Galileo_E5B' : 0.80,
              'Galileo_E5L' : 0.32,
              'Galileo_E6'  : 0.50, # No data yet
              'Beidou_B1'   : 1.8,
              'Beidou_B2'   : 1.0,
              'Beidou_B3'   : 0.5, # No data yet
              'Beidou_B1C'  : 1.0,
              'Beidou_B2A'  : 1.0,
              'Beidou_B2B'  : 1.0}

str2ShortCarr2Thres= { 
              'GPS_L1CA'    : 55,
              'GPS_L2E'     : 46,
              'GPS_L2C'     : 46,
              'GPS_L5'      : 49,
              'GLONASS_G1C' : 14,
              'GLONASS_G1P' : 14,
              'GLONASS_G2C' : 20,
              'GLONASS_G2P' : 20,
              'Galileo_E1'  : 45,
              'Galileo_E5A' : 50,
              'Galileo_E5B' : 50,
              'Galileo_E5L' : 45,
              'Galileo_E6'  : 45, # No data yet
              'Beidou_B1'   : 60,
              'Beidou_B2'   : 60,
              'Beidou_B3'   : 60, # No data yet
              'Beidou_B1C'  : 60, # No data yet
              'Beidou_B2A'  : 60,
              'Beidou_B2B'  : 60}


str2combo = { 'GPS_L1CA'    : 0,
              'GPS_L2E'     : 1,
              'GPS_L2C'     : 2,
              'GPS_L5'      : 3,
              'GLONASS_G1C' : 4,
              'GLONASS_G1P' : 5,
              'GLONASS_G2C' : 6,
              'GLONASS_G2P' : 7,
              'Galileo_E1'  : 8,
              'Galileo_E5A' : 9,
              'Galileo_E5B' : 10,
              'Galileo_E5L' : 11,
              'Galileo_E6'  : 12,
              'Beidou_B1'   : 13,
              'Beidou_B2'   : 14,
              'Beidou_B3'   : 15,
              'Beidou_B1C'  : 16,
              'Beidou_B2A'  : 17,
              'Beidou_B2B'  : 18}


combo2str = { 0  : 'GPS_L1CA',
              1  : 'GPS_L2E',
              2  : 'GPS_L2C',
              3  : 'GPS_L5',
              4  : 'GLONASS_G1C',
              5  : 'GLONASS_G1P',
              6  : 'GLONASS_G2C',
              7  : 'GLONASS_G2P',
              8  : 'Galileo_E1',
              9  : 'Galileo_E5A',
              10 : 'Galileo_E5B',
              11 : 'Galileo_E5L',
              12 : 'Galileo_E6',
              13 : 'Beidou_B1',
              14 : 'Beidou_B2',
              15 : 'Beidou_B3',
              16 : 'Beidou_B1C',
              17 : 'Beidou_B2A',
              18 : 'Beidou_B2B'}



def DDTable(string,suffix,BaselineType,measType):
  webStr = "<div class='col-sm-6'>"
  webStr = webStr + ("<div class='table-responsive'>")
  webStr = webStr + ("<h3>" + string + "</h3>")
  webStr = webStr + ("<table id='data-table-basic' class='table table-striped'>")
  webStr = webStr + ("<thead>")
  webStr = webStr + ("<tr>")
  webStr = webStr + ("<th data-column-id='date'>Date</th>")
  webStr = webStr + ("<th data-column-id='id'>Combo</th>")
  if(measType == dCode):
    webStr = webStr + ("<th data-column-id='sigma' data-type='numeric'>Sigma[m]</th>")
  elif(measType == dCarr):
    webStr = webStr + ("<th data-column-id='sigma' data-type='numeric'>Sigma[mcycle|mm]</th>")
  else:
    webStr = webStr + ("<th data-column-id='sigma' data-type='numeric'>Sigma[m/s]</th>")
  webStr = webStr + ("</tr>")
  webStr = webStr + ("</thead>")
  webStr = webStr + ("<tbody>")

  for i in range(len(combo2str)):
    gotData = False

    filename = "/net/quark/mnt/data_drive/DataDump/DDTrend/DashBoard/"  + combo2str.get(i) + suffix
    if(os.path.isfile(filename + '.txt')): 
      fid = open(filename + '.txt','r')
      # In older versions of python you could seek backwards from the end of the file
      # with something like this fid.seek(-1024,SEEK_END) 
      # You can't do that any more, now use the following two lines to move to 1024
      # bytes from the end of the file. By seeking to the end and then only processing
      # the last line of data this operation is substantially faster than the older code
      fid.seek(0,os.SEEK_END)
      fid.seek(fid.tell() - 1024,os.SEEK_SET)
      # We know the lines of data are no more than a 100+ characters, we have read
      # the final 1024 characters from the file. Read the remaining lines of data, 
      # we only care about the last line (tmp[-1])
      tmp = fid.readlines()
      fid.close()
      
      # Get the data tokens from the last line
      lastline = tmp[-1].rstrip().split(maxsplit=21)

      if(len(lastline) == 22):
        DOY = lastline[0] + '-' + lastline[1] + '-' + lastline[2]
        DDCodeSigma = float(lastline[15])
        DDCarrSigma = float(lastline[10])
        DDDoppSigma = float(lastline[20])
        gotData = True

    if(measType == dCode):
      data = DDCodeSigma
      thresh = str2ShortCode2Thres.get(combo2str.get(i))
    elif(measType == dCarr):
      data = DDCarrSigma
      thresh = str2ShortCarr2Thres.get(combo2str.get(i))
    else:
      data = DDDoppSigma
      thresh = 0.045

    if(gotData):
      if(measType == dCode):
        fileSegment  = combo2str.get(i) + "-" + BaselineType + ".DDCodeSigma"
        HTMLSegment  = combo2str.get(i) + "-" + string[0:5] + "-" + BaselineType + ".DDCodeSigma"
      elif(measType == dCarr):
        fileSegment  = combo2str.get(i) + "-" + BaselineType + ".DDCarrSigma"
        HTMLSegment  = combo2str.get(i) + "-" + string[0:5] + "-" + BaselineType + ".DDCarrSigma"
      else:
        fileSegment  = combo2str.get(i) + "-" + BaselineType + ".DDDoppSigma"
        HTMLSegment  = combo2str.get(i) + "-" + string[0:5] + "-" + BaselineType + ".DDDoppSigma"
      
      webStr = webStr + "<tr><td>" + DOY + "</td>" 

      # Data Table
      webLink = 'http://quark.eng.trimble.com/DataDump/DDTrend/' + HTMLSegment + '.html'
      webStr = webStr + (  "<td style='white-space: nowrap;'>"
                         + "<a href='" + webLink + "' target='_blank'>"
                         + "<img class='click' width='20px' src='http://quark.eng.trimble.com/DataDump/DashBoard/DataTable.png'></img>" 
                         + "</a>&nbsp;")

      # Trend graph
      webLink = 'http://quark.eng.trimble.com/DataDump/DDTrend/' + fileSegment + '.png'
      webStr =  webStr + ( "<a onclick='toggleImg(this)' href='#'>" 
                          + "<img class='click' width='20px' src='http://quark.eng.trimble.com/DataDump/DashBoard/graph.png'></img>" 
                          + "<img class='links' src='" + webLink + "'/></a>")

      dateString = DOY.split("-")
      webLink = 'http://teller.eng.trimble.com/GPSRxEng/GNSSResults/' + dateString[0] + '-Res/' 
      webLink = webLink + dateString[0] + dateString[1].zfill(2) + dateString[2].zfill(2)
      webDir = '_' + suffix[1:]
      webLink = webLink + webDir + '/NoPiDI_Top.html'

      webStr = webStr + "<a href='" + webLink + "' target='_blank'>&nbsp;" + combo2str.get(i) + "</a></td>"

      if(data > thresh):
        webStr = webStr + "<td><font color='red'>" + str.format('{0:.2f}',data) + "</font></td></tr>"
      else:
        webStr = webStr + "<td>" + str.format('{0:.2f}',data) + "</td></tr>"

  webStr = webStr + ("</tbody>")
  webStr = webStr + ("</table>")
  webStr = webStr + ("</div>")
  webStr = webStr + ("</div>")
  return webStr


@app.route("/Latency.html")
def latency():
  webStr = ("{% extends 'menu.html' %}")
  webStr += ("{% block main %}")
  webStr += ("<section id='content'>")
  webStr += "<div class='col-sm-16'>"
  webStr += ("<h3>Latency Performance [ms]</h3>")
  webStr += ("<div class='table-responsive'>")
  webStr += ("<table id='data-table-basic' class='table table-striped'>")
  webStr += ("<thead>")
  webStr += ("<tr>")
  webStr += ("<th>Receiver</th>")
  webStr += ("<th>Mode</th>")

  label = ["Mean", "Median", "68%", "95%", "99%", "100%"]
  pngName = ["Mean", "50-Percentile", "68-Percentile", "95-Percentile", "99-Percentile", "100-Percentile"]
  for j in range(len(label)):
    webLink = (   'http://quark.eng.trimble.com/DataDump/StingerLabLatency/' + pngName[j] + '.png') 
    webStr += (  "<th>" + label[j] + "&nbsp;" 
               + "<a onclick='toggleImg(this)' href='#'>" 
               + "<img class='click' width='20px' src='http://quark.eng.trimble.com/DataDump/DashBoard/graph.png'></img>" 
               + "<img class='links' src='" + webLink + "'/></a></th>")
  
  webStr += ("</tr>")
  webStr += ("</thead>")
  webStr += ("<tbody>")

  # Load the JSON file 
  with open('/net/higgs/mnt/data_drive/StingerLabLatency/receivers.json', 'r') as f:
    receivers = json.load(f)

  NumRX = len(receivers)

  for i in range(NumRX):
    RXName = receivers[i]['short']
    filename =  '/net/higgs/mnt/data_drive/StingerLabLatency/' + RXName + "Hist.res"
    if(os.path.isfile(filename)): 
      latency = np.loadtxt(filename)
      webStr += "<tr>"
      webStr += "<td>" + RXName + '</td>'
      webStr += "<td>" + receivers[i]['mode'] + '</td>'

      for j in range(len(pngName)):
        webStr += "<td style='white-space: nowrap;'>"

        if(latency[-1,j+6] >= 20):
          # Metric is above the 20ms specification
          webStr += "<font color='red'>"
          webStr += str.format('{0:.2f}',latency[-1,j+6]) + 'ms'
          webStr += "</font>"
        else:
          webStr += str.format('{0:.2f}',latency[-1,j+6]) + 'ms'

        webStr += '</td>'
      webStr = webStr + "</tr>"

  webStr = webStr + ("</tbody>")
  webStr = webStr + ("</table>")
  webStr = webStr + ("</div>")
  webStr = webStr + ("</div>")

  webStr = webStr + ("</section>")
  webStr = webStr + ("{% endblock %}")
  webStr = webStr + ("{% block userscript %}")
  webStr = webStr + ("<script>")
  webStr = webStr + ("</script>")
  webStr = webStr + ("<script src='static/js/app.js'></script>")
  webStr = webStr + ("{% endblock %}")

  return render_template_string(Markup(webStr))




@app.route("/Velocity.html")
def velocity():
  webStr = ("{% extends 'menu.html' %}")
  webStr = webStr + ("{% block main %}")
  webStr = webStr + ("<section id='content'>")
  webStr = webStr + "<div class='col-sm-16'>"
  webStr = webStr + ("<h3>Velocity performance [m/s]</h3>")
  webStr = webStr + ("<div class='table-responsive'>")
  webStr = webStr + ("<table id='data-table-basic' class='table table-striped'>")
  webStr = webStr + ("<thead>")
  webStr = webStr + ("<tr>")
  webStr = webStr + ("<th>History</th>")
  webStr = webStr + ("<th>Receiver</th>")
  webStr = webStr + ("<th>Date</th>")
  webStr = webStr + ("<th>2D</th>")
  webStr = webStr + ("<th>2D 68%</th>")
  webStr = webStr + ("<th>2D 95%</th>")
  webStr = webStr + ("<th>2D 100%</th>")
  webStr = webStr + ("<th>ENU</th>")
  webStr = webStr + ("<th>E std</th>")
  webStr = webStr + ("<th>N std</th>")
  webStr = webStr + ("<th>U std</th>")
  webStr = webStr + ("</tr>")
  webStr = webStr + ("</thead>")
  webStr = webStr + ("<tbody>")

  # Load the JSON file 
  with open('/net/higgs/mnt/data_drive/Velocity/receivers.json', 'r') as f:
    receivers = json.load(f)

  NumRX = len(receivers['receivers'])

  for i in range(NumRX):
    trend = []

    RXDir = receivers['receivers'][i]['RXStr']
    filename =  '/net/higgs/mnt/data_drive/Velocity/' + RXDir + "/VelocityStats.txt"
    if(os.path.isfile(filename)): 
      vel = np.loadtxt(filename)

      webStr = webStr + "<tr>"

      webLink = 'http://quark.eng.trimble.com/DataDump/Velocity/' + RXDir + '/summary.html'
      webStr = webStr + (  "<td style='white-space: nowrap;'>"
                         + "<a href='" + webLink + "' target='_blank'>"
                         + "<img class='click' width='20px' src='http://quark.eng.trimble.com/DataDump/DashBoard/DataTable.png'></img>" 
                         + "</a>&nbsp;")
                         
      webLink = 'http://quark.eng.trimble.com/DataDump/Velocity/' + RXDir + '/velocityTrend.png'
      webStr =  webStr + ( "<a onclick='toggleImg(this)' href='#'>" 
                          + "<img class='click' width='20px' src='http://quark.eng.trimble.com/DataDump/DashBoard/graph.png'></img>" 
                          + "<img class='links' src='" + webLink + "'/></a></td>")

      webStr = webStr + "<td>" + RXDir + '</td>'

      YYYYMMDD =  str(int(vel[-1,0])) + str(int(vel[-1,1])).zfill(2) + str(int(vel[-1,2])).zfill(2) 

      webStr =  webStr + ( "<td>" + YYYYMMDD + "</td>")

      webLink = (   'http://quark.eng.trimble.com/DataDump/Velocity/' + RXDir + '/' + YYYYMMDD + '-velocity2D.png') 
      webStr = webStr + (  "<td style='white-space: nowrap;'>"
                          + "<a onclick='toggleImg(this)' href='#'>" 
                          + "<img class='click' width='20px' src='http://quark.eng.trimble.com/DataDump/DashBoard/graph.png'></img>" 
                          + "<img class='links' src='" + webLink + "'/></a></td>")

      webStr = webStr + "<td>" + str.format('{0:.3f}',vel[-1,22]) + '</td>'
      webStr = webStr + "<td>" + str.format('{0:.3f}',vel[-1,23]) + '</td>'
      webStr = webStr + "<td>" + str.format('{0:.3f}',vel[-1,25]) + '</td>'
      
      
      webLink = (   'http://quark.eng.trimble.com/DataDump/Velocity/' + RXDir + '/' + YYYYMMDD + '-velocity.png') 
      webStr = webStr + (  "<td style='white-space: nowrap;'>"
                          + "<a onclick='toggleImg(this)' href='#'>" 
                          + "<img class='click' width='20px' src='http://quark.eng.trimble.com/DataDump/DashBoard/graph.png'></img>" 
                          + "<img class='links' src='" + webLink + "'/></a></td>")

      webStr = webStr + "<td>" + str.format('{0:.3f}',vel[-1,6]) + '</td>'
      webStr = webStr + "<td>" + str.format('{0:.3f}',vel[-1,11]) + '</td>'
      webStr = webStr + "<td>" + str.format('{0:.3f}',vel[-1,17]) + '</td>'
      webStr = webStr + "</tr>"

  webStr = webStr + ("</tbody>")
  webStr = webStr + ("</table>")
  webStr = webStr + ("</div>")
  webStr = webStr + ("</div>")

  #webStr = webStr + "<img href='http://quark.eng.trimble.com/DataDump/Velocity/velocityTrendSummary.png'>"
  webStr = webStr + "<a href='http://quark.eng.trimble.com/DataDump/Velocity/velocityTrendSummary.png'>Combined Trend</a>"

  webStr = webStr + ("</section>")
  webStr = webStr + ("{% endblock %}")
  webStr = webStr + ("{% block userscript %}")
  webStr = webStr + ("<script>")
  webStr = webStr + ("</script>")
  webStr = webStr + ("<script src='static/js/app.js'></script>")
  webStr = webStr + ("{% endblock %}")

  return render_template_string(Markup(webStr))

@app.route("/Memory.html")
def Memory():

  # Load the JSON file 
  with open('/net/quark/mnt/data_drive/DataDump/Memory/receivers.json', 'r') as f:
    receivers = json.load(f)
  MaxRX = len(receivers)

  group = request.args.get('group')
  if(group is None):
    group = 0
  else:
    group = int(group)


  webStr  = ("{% extends 'menu.html' %}")
  webStr += ("{% block main %}")
  webStr += ("<section id='content'>")

  # Script to handle the pull downs
  webStr += ' <script>'
  webStr += ' function ChangeStationSystem() {'
  webStr += ' var x = document.getElementById("group").value;'
  webStr += ' link = "Memory.html?group=" + x;'
  webStr += ' window.open(link, "_self");'
  webStr += ' }'
  webStr += ' </script>'

  webStr += ("<div class='row'>")
  webStr += "<div class='col-sm-14'>"

  webStr += ("<h3>Memory Usage</h3>")

  webStr+="<br/>\n\n"
  webStr += '<div>&nbsp;'
  webStr += ' <label for="group">Receiver Group:</label>'
  webStr += ' <select name="group" id="group" onchange="ChangeStationSystem()">'

  rangeStr = ['Sunnyvale (DHS Case)',
              'Sunnyvale (Misc)/Stanford/CO',
              'Munich/Moscow/Singapore',
              'India/Melbourne',
              'Stinger Lab']
  for i in range(len(rangeStr)):
    if(i == group):
      webStr += ' <option value="' + str(i) + '" selected="selected">' + rangeStr[i]  + '</option>'
    else:
      webStr += ' <option value="' + str(i) + '">' + rangeStr[i] + '</option>'
  webStr += ' </select> '

  webStr += ("<div class='table-responsive'>")
  webStr += ("<table id='data-table-basic' class='table table-striped'>")
  webStr += ("<thead>")
  webStr += ("<tr>")
  webStr += ("<th>&nbsp;</th>")

  memData = []
  for rx in range(MaxRX):
    if(int(receivers[rx]['group']) != group):
      continue
    rxName = receivers[rx]['long']
    webStr += ("<th>" + rxName + "</th>")

    d = {}
    filename = "/net/quark/mnt/data_drive/DataDump/Memory/" + rxName + "-Mem.txt"
    if(os.path.isfile(filename)): 
      fid = open(filename,'r')
      # In older versions of python you could seek backwards from the end of the file
      # with something like this fid.seek(-1024,SEEK_END) 
      # You can't do that any more, now use the following two lines to move to 1024
      # bytes from the end of the file. By seeking to the end and then only processing
      # the last line of data this operation is substantially faster than a full
      # file read
      fid.seek(0,os.SEEK_END)
      fid.seek(fid.tell() - 1024,os.SEEK_SET)
      # We know the lines of data are no more than a 100+ characters, we have read
      # the final 1024 characters from the file. Read the remaining lines of data, 
      # we only care about the last line (tmp[-1])
      tmp = fid.readlines()
      fid.close()
      # Get the data tokens from the last line
      tokens = tmp[-1].rstrip().split()

      d['dateTimeStr']  = (tokens[0] + '-' +
                           tokens[1].zfill(2) + '-' +
                           tokens[2].zfill(2) + 'T' +
                           tokens[3].zfill(2) + ':' +
                           tokens[4].zfill(2) + ':' +
                           tokens[5].zfill(2) + 'Z')

      d['mode']  = tokens[11] # Astra/Titan
      d['mem']  = str.format('{0:.2f}',float(tokens[7]) / (1024*1024))
      d['pool']  = str.format('{0:.2f}',float(tokens[9]) / 1024)
      mallocs = int(tokens[8])
      d['mallocs'] = f"{mallocs:,d}"
      if(len(tokens) == 16):
        try:
          # Had issues with extra spaces in version numbers. Make 
          # sure it looks like the correct field
          largest = int(tokens[15])
          d['largest'] = str.format('{0:.2f}',float(largest) / (1024*1024))
        except ValueError:
          d['largest'] = '-'
    else:
      d['dateTimeStr']  = "-"
      d['mode']  = "-"
      d['mem']  = "-"
      d['pool'] = "-"
      d['mallocs'] = "-"
      d['largest'] = '-'
 
    # Now get the FW version / date
    filename = "/net/quark/mnt/data_drive/DataDump/Memory/" + rxName + "-FW.txt"
    if(os.path.isfile(filename)): 
      fid = open(filename,'r')
      token = fid.readline().split(',')
      if(len(token) == 3):
        d['version'] = token[0]
        d['date']    = token[1]
        d['rxType']  = token[2]
      else:
        d['version'] = '-'
        d['date']    = '-'
        d['rxType']  = '-'

    memData.append(d)
  
  webStr += ("</tr>")
  webStr += ("</thead>")
  webStr += ("<tbody>")

  # Receiver Type
  webStr += "<tr>"
  webStr += "<td>RX Type</td>"
  index = 0
  for rx in range(MaxRX):
    if(int(receivers[rx]['group']) != group):
      continue

    webStr += "<td>" + memData[index]['rxType'] + "</td>"
    index += 1
  webStr += ("</tr>")

  # Latest Date/Time of data collected
  webStr += "<tr>"
  webStr += "<td>Date/Time</td>"
  index = 0
  for rx in range(MaxRX):
    if(int(receivers[rx]['group']) != group):
      continue

    webStr += "<td><font size='1'>" + memData[index]['dateTimeStr'] + "</font></td>"
    index += 1
  webStr += ("</tr>")

  # Receiver IP / Link
  webStr += "<tr>"
  webStr += "<td>IP Address</td>"
  for rx in range(MaxRX):
    if(int(receivers[rx]['group']) != group):
      continue

    webLink = 'http://' + receivers[rx]['addr']
    webStr +=  (  "<td style='white-space: nowrap;'>"
                + "<a href='" + webLink + "' target='_blank'>"
                + receivers[rx]['addr']
                + "</a>")
  webStr += ("</tr>")

  # Firmware version
  webStr += "<tr>"
  webStr += "<td>FW Version</td>"
  index = 0
  for rx in range(MaxRX):
    if(int(receivers[rx]['group']) != group):
      continue

    webStr += "<td>" + memData[index]['version'] + "</td>"
    index += 1
  webStr += ("</tr>")

  # Firmware Date
  webStr += "<tr>"
  webStr += "<td>FW Date</td>"
  index = 0
  for rx in range(MaxRX):
    if(int(receivers[rx]['group']) != group):
      continue

    webStr += "<td>" + memData[index]['date'] + "</td>"
    index += 1
  webStr += ("</tr>")

  # Receiver PVT Engine
  webStr += "<tr>"
  webStr += "<td>PVT Engine</td>"
  
  index = 0
  for rx in range(MaxRX):
    if(int(receivers[rx]['group']) != group):
      continue

    name = receivers[rx]['long']
    if(memData[index]['mode'] == '1'):
      webStr += "<td>Titan</td>"
    elif(memData[index]['mode'] == '0'):
      webStr += "<td>Astra</td>"
    else:
      webStr += "<td>-</td>"
    index += 1
  webStr += ("</tr>")

  # Receiver memory
  webStr += "<tr>"
  webStr += "<td>Memory Free [MiB]</td>"
  index = 0
  for rx in range(MaxRX):
    if(int(receivers[rx]['group']) != group):
      continue
    name = receivers[rx]['long']
    webLink = ( 'http://quark.eng.trimble.com/DataDump/Memory/' + name + '-Mem.png')
    webStr +=   (   "<td>"
                  + "<a onclick='toggleImg(this)' href='#'>" 
                  + "<img class='click' width='20px' src='http://quark.eng.trimble.com/DataDump/DashBoard/graph.png'></img>" 
                  + "<img class='links' src='" + webLink + "'>"
                  + "</img>"
                  + "</a>&nbsp;"
                  + memData[index]['mem']
                  + "</td>")
    index += 1
  webStr += ("</tr>")
  
  # Receiver memory fragmentation test - largest buffer
  webStr += "<tr>"
  webStr += "<td>Largest Free [MiB]</td>"
  index = 0
  for rx in range(MaxRX):
    if(int(receivers[rx]['group']) != group):
      continue
    name = receivers[rx]['long']
    webLink = ( 'http://quark.eng.trimble.com/DataDump/Memory/' + name + '-Mem-Largest.png')
    webStr +=   (   "<td>"
                  + "<a onclick='toggleImg(this)' href='#'>" 
                  + "<img class='click' width='20px' src='http://quark.eng.trimble.com/DataDump/DashBoard/graph.png'></img>" 
                  + "<img class='links' src='" + webLink + "'>"
                  + "</img>"
                  + "</a>&nbsp;"
                  + memData[index]['largest']
                  + "</td>")
    index += 1
  webStr += ("</tr>")

  # Receiver mallocs
  webStr += "<tr>"
  webStr += "<td> Active Mallocs</td>"
  index = 0
  for rx in range(MaxRX):
    if(int(receivers[rx]['group']) != group):
      continue
    name = receivers[rx]['long']
    webLink = ( 'http://quark.eng.trimble.com/DataDump/Memory/' + name + '-Mallocs.png')
    webStr +=   (   "<td>"
                  + "<a onclick='toggleImg(this)' href='#'>" 
                  + "<img class='click' width='20px' src='http://quark.eng.trimble.com/DataDump/DashBoard/graph.png'></img>" 
                  + "<img class='links' src='" + webLink + "'>"
                  + "</img>"
                  + "</a>&nbsp;"
                  + memData[index]['mallocs']
                  + "</td>")
    index += 1
  webStr += ("</tr>")

  # Receiver network pool
  webStr += "<tr>"
  webStr += "<td>Free Network Pool [KiB]</td>"
  index = 0
  for rx in range(MaxRX):
    if(int(receivers[rx]['group']) != group):
      continue
    name = receivers[rx]['long']
    webLink = ( 'http://quark.eng.trimble.com/DataDump/Memory/' + name + '-Pool.png')
    webStr +=   (   "<td>"
                  + "<a onclick='toggleImg(this)' href='#'>" 
                  + "<img class='click' width='20px' src='http://quark.eng.trimble.com/DataDump/DashBoard/graph.png'></img>" 
                  + "<img class='links' src='" + webLink + "'>"
                  + "</img>"
                  + "</a>&nbsp;"
                  + memData[index]['pool']
                  + "</td>")
    index += 1
  webStr += ("</tr>")

  webStr = webStr + ("</tbody>")
  webStr = webStr + ("</table>")
  webStr = webStr + ("</div>")
  webStr = webStr + ("</div>")
  webStr = webStr + ("</div>")

  webStr = webStr + ("</section>")
  webStr = webStr + ("{% endblock %}")
  webStr = webStr + ("{% block userscript %}")
  webStr = webStr + ("<script>")
  webStr = webStr + ("</script>")
  webStr = webStr + ("<script src='static/js/app.js'></script>")
  webStr = webStr + ("{% endblock %}")

  return render_template_string(Markup(webStr))

@app.route("/ReportedSlips.html")
def ReportedSlips():

  # Load the JSON file 
  with open('/net/higgs/mnt/data_drive/Slips/receivers.json', 'r') as f:
    receivers = json.load(f)
  MaxRX = len(receivers['receivers'])

  group = request.args.get('group')
  if(group is None):
    group = 0
  else:
    group = int(group)

  # Load the signal LUT JSON file 
  with open('/net/higgs/mnt/data_drive/Slips/signals.json', 'r') as f:
    signals = json.load(f)
  MaxSatType = len(signals['types'])

  system = request.args.get('system')
  if(system is None):
    system = 'GPS'
   
  # Accept any case system names
  system = system.upper()
  # Accept BDS and BEIDOU  (irrespective of case)
  if(system == 'BEIDOU'):
    system = 'BDS'

  gotSystem = False
  for i in range(MaxSatType):
    # Get the index into the gnssSysData list of dictionaries
    if(system== signals['types'][i]['satTypeStr']):
      sysIndex = i
      gotSystem = True
      break
  
  # Default to GPS if we got an invalid system type
  if(gotSystem == False):
    sysIndex = 0 
    system = 'GPS'
  elevAngle = request.args.get('elevAngle')
  if(elevAngle is None):
    elevAngle = 0

  if(elevAngle):
    if(int(elevAngle) == 0):
      modifier = ''
    elif(int(elevAngle) == 30):
      modifier = '30'
    else:
      return
  else:
    modifier = ''

  print("Group | Sys | elev ",group,system,elevAngle,modifier)

  webStr  = ("{% extends 'menu.html' %}")
  webStr += ("{% block main %}")
  webStr += ("<section id='content'>")

  # Script to handle the pull downs
  webStr += ' <script>'
  webStr += ' function ChangeStationSystem() {'
  webStr += ' var x = document.getElementById("group").value;'
  webStr += ' var y = document.getElementById("system").value;'
  webStr += ' var url = window.location.search;'
  webStr += ' const urlParams = new URLSearchParams(url);'
  webStr += ' var elev = urlParams.getAll("elevAngle");'
  webStr += ' if(elev && elev.length > 0)'
  webStr += '   link = "ReportedSlips.html?group=" + x + "&system=" + y + "&elevAngle=" + elev[0];'
  webStr += ' else'
  webStr += '   link = "ReportedSlips.html?group=" + x + "&system=" + y;'
  webStr += ' console.log(elev);'
  webStr += ' window.open(link, "_self");'
  webStr += ' }'
  webStr += ' </script>'

  webStr += ("<div class='row'>")
  webStr += "<div class='col-sm-14'>"

  if(modifier == '30'):
    webStr += ("<h3>Reported Slip Rate - Satellites &gt; 30 Degrees Elevation</h3>")
  else:
    webStr += ("<h3>Reported Slip Rate - All Data</h3>")

  webStr+="<br/>\n\n"
  webStr += '<div>&nbsp;'
  webStr += ' <label for="group">Receiver Group:</label>'
  webStr += ' <select name="group" id="group" onchange="ChangeStationSystem()">'

  rangeStr = ['Sunnyvale/Melbourne/CO','Singapore/Tokyo/Munich','Beijing']
  for i in range(3):
    if(i == group):
      webStr += ' <option value="' + str(i) + '" selected="selected">' + rangeStr[i]  + '</option>'
    else:
      webStr += ' <option value="' + str(i) + '">' + rangeStr[i] + '</option>'
  webStr += ' </select> '

  webStr += ' <label for="system">System:</label>'
  webStr += ' <select name="system" id="system" onchange="ChangeStationSystem()">'
  
  for i in range(MaxSatType):
    # Get the index into the gnssSysData list of dictionaries
    if(system== signals['types'][i]['satTypeStr']):
      webStr += ' <option value="' + system + '" selected="selected">' + system + '</option>'
    else:
      webStr += ' <option value="' + signals['types'][i]['satTypeStr'] + '">' + signals['types'][i]['satTypeStr'] + '</option>'
  webStr += ' </select> '
  webStr += '</div>'
  webStr+="<br/>\n\n"

  webStr += ("<div class='table-responsive'>")
  webStr = webStr + ("<table id='data-table-basic' class='table table-striped'>")
  webStr = webStr + ("<thead>")
  webStr = webStr + ("<tr>")
  webStr = webStr + ("<th>Signal</th>")

  for rx in range(MaxRX):
    if(int(receivers['receivers'][rx]['group']) != group):
      continue
    rxNameShort = receivers['receivers'][rx]['rxShortName']
    webStr = webStr + ("<th>" + rxNameShort + "</th>")
  
  webStr = webStr + ("</tr>")
  webStr = webStr + ("</thead>")
  webStr = webStr + ("<tbody>")

  webStr = webStr + "<tr>"
  webStr = webStr + "<td>Date</td>"
  dateDetails = []
  # This loop scans the GPS C/A file for all stations to find the latest processed
  # date
  for rx in range(MaxRX):
    #if(int(receivers['receivers'][rx]['group']) != group):
    #  continue
    d = {}
    gotSignal = False
    RXDir = receivers['receivers'][rx]['RXStr']
    filename = ("/net/higgs/mnt/data_drive/Slips/" + RXDir + "/slips" + modifier + ".0.0.0.0.txt")
    if(os.path.isfile(filename) and (os.path.getsize(filename) > 0)):
      slipData = np.loadtxt(filename,dtype='int')
      rows = size(slipData)/8
      if(rows > 1):
        # Use the same number of epochs filter we use in the
        # plotting
        if(slipData[-1,7] > 3600):
          gotSignal = True
          rate = slipData[-1,6].astype(float) / slipData[-1,7].astype(float)
          if(int(receivers['receivers'][rx]['group']) == group):
            webStr = webStr + "<td>" + str(slipData[-1,0]) + str(slipData[-1,1]).zfill(2) + str(slipData[-1,2]).zfill(2) + "</td>"
          d['year']  = str(slipData[-1,0])
          d['month'] = str(slipData[-1,1]).zfill(2)
          d['day']   = str(slipData[-1,2]).zfill(2)

    if(gotSignal == False):
       if(int(receivers['receivers'][rx]['group']) == group):
         webStr = webStr + "<td></td>"
       d['year']  = ''
       d['month'] = ''
       d['day']   = ''
    dateDetails.append(d)
  webStr = webStr + "</tr>"

  for satType in range(MaxSatType):
    thisSat    = signals['types'][satType]
    satTypeStr = thisSat['satTypeStr']
    if(satTypeStr != system):
      # Only process the requested system
      continue

    print(satTypeStr,system)

    webStr = webStr + ("<tr>")
    webStr = webStr + ("<td>" + satTypeStr +"</td>")
    for rx in range(MaxRX):
      if(int(receivers['receivers'][rx]['group']) != group):
        continue
      RXDir = receivers['receivers'][rx]['RXStr']
      webLink = (   'http://quark.eng.trimble.com/DataDump/Slips/' + RXDir + '/'
                  + satTypeStr + '-' + modifier + 'Summary.png')

      webStr =  webStr + (   "<td><a onclick='toggleImg(this)' href='#'>" 
                           + 'Summary'  
                           + "<img class='links' src='" + webLink + "'/></a></td>")

    webStr = webStr + ("</tr>")
  
    MaxSig = len(thisSat['signals'])
    for sigType in range(MaxSig):
      webStr = webStr + ("<tr>")
      webStr = webStr + ("<td>" + satTypeStr + ' ' + 
                         thisSat['signals'][sigType]['sigStr'] + "</td>")

      # Now load the slip data for the requested satellite type and system.
      for rx in range(MaxRX):
        if(int(receivers['receivers'][rx]['group']) != group):
          continue
        RXDir = receivers['receivers'][rx]['RXStr']
        filename = ("/net/higgs/mnt/data_drive/Slips/" + RXDir + "/slips" + modifier + "." + 
                    thisSat['signals'][sigType]['satType'] + "." + 
                    thisSat['signals'][sigType]['freq'] + "." + 
                    thisSat['signals'][sigType]['sig'] + "." + 
                    thisSat['signals'][sigType]['orbit'] + ".txt")

        gotSignal = False
        if(os.path.isfile(filename) and (os.path.getsize(filename) > 0)):
          slipData = np.loadtxt(filename,dtype='int')
          rows = size(slipData)/8
          if(rows > 1):
            # Use the same number of epochs filter we use in the
            # plotting
            if(     (slipData[-1,7] > 3600) 
                and (len(dateDetails[rx]['year']) > 0)
                and (slipData[-1,0] == int(dateDetails[rx]['year']))
                and (slipData[-1,1] == int(dateDetails[rx]['month']))
                and (slipData[-1,2] == int(dateDetails[rx]['day'])) ):
              gotSignal = True
              rate = slipData[-1,6].astype(float) / slipData[-1,7].astype(float)

              # This is the web link for the history of this signal type combined
              # at the system level
              webLink = (   'http://quark.eng.trimble.com/DataDump/Slips/' + RXDir + '/'
                          + satTypeStr + '-' 
                          + thisSat['signals'][sigType]['plotStr'] 
                          + modifier
                          + '.png')

              if(group != 2):
                rxNameShort = receivers['receivers'][rx]['rxShortName']
                # This is the same data as above, but broken out on a per satellite basis
                webLinkSV = ( 'http://quark.eng.trimble.com/DataDump/Slips/History/' + rxNameShort + '-slips' + modifier + '.' + 
                              thisSat['signals'][sigType]['satType'] + "." + 
                              thisSat['signals'][sigType]['freq'] + "." + 
                              thisSat['signals'][sigType]['sig'] + ".png")

                webStr +=   (   "<td>"
                              + "<a onclick='toggleImg(this)' href='#'>" 
                              + "<img class='click' width='20px' src='http://quark.eng.trimble.com/DataDump/DashBoard/graph.png'></img>" 
                              + "<img class='links' src='" + webLinkSV + "'>"
                              + "</img>"
                              + "</a>&nbsp;"
                              + "<a onclick='toggleImg(this)' href='#'>" 
                              + str.format('{0:.5f}',rate) 
                              + "<img class='links' src='" + webLink + "'/></a>"
                              + "</td>")
              else:
                # Beijing data stopped publishing before we created the per satellite data
                webStr +=  (   "<td>"
                             + "<a onclick='toggleImg(this)' href='#'>" 
                             + str.format('{0:.5f}',rate) 
                             + "<img class='links' src='" + webLink + "'/></a>"
                             + "</td>")

        if(gotSignal == False):
          webStr = webStr + "<td>-</td>"

      webStr = webStr + "</tr>"

  webStr = webStr + "<tr>"
  webStr = webStr + ("<td>Details</td>")
  for rx in range(MaxRX):
    if(int(receivers['receivers'][rx]['group']) != group):
      continue
    dataSys = receivers['receivers'][rx]['sys']
    
    if(dataSys == 'COSync'):
      COStation = receivers['receivers'][rx]['coDir']
      webLink = ("http://dave.trimble.com/testing/MON_" + dateDetails[rx]['year'] + "/" 
                  + COStation + "/" + dateDetails[rx]['year'] 
                  + '/' + dateDetails[rx]['month'] 
                  + '/' + dateDetails[rx]['year'] 
                  + '_' + dateDetails[rx]['month'] 
                  + '_' + dateDetails[rx]['day'] 
                  + '-HTML_SV_Tracking_Report.html')

      webStr = webStr + (  "<td style='white-space: nowrap;'>"
                         + "<a href='" + webLink + "' target='_blank'>X</a></td>")

    else:
      webStr = webStr + "<td>-</td>"

  webStr = webStr + "</tr>"

  webStr = webStr + ("</tbody>")
  webStr = webStr + ("</table>")
  webStr = webStr + ("</div>")
  webStr = webStr + ("</div>")
  webStr = webStr + ("</div>")

  webStr = webStr + ("</section>")
  webStr = webStr + ("{% endblock %}")
  webStr = webStr + ("{% block userscript %}")
  webStr = webStr + ("<script>")
  webStr = webStr + ("</script>")
  webStr = webStr + ("<script src='static/js/app.js'></script>")
  webStr = webStr + ("{% endblock %}")

  return render_template_string(Markup(webStr))



@app.route("/CPULoad.html")
def CPULoad():
  webStr = ("{% extends 'menu.html' %}")
  webStr = webStr + ("{% block main %}")
  webStr = webStr + ("<section id='content'>")
  webStr = webStr + "<div class='col-sm-16'>"
  webStr = webStr + ("<h3>System CPU Load</h3>")
  webStr = webStr + ("<div class='table-responsive'>")
  webStr = webStr + ("<table id='data-table-basic' class='table table-striped'>")
  webStr = webStr + ("<thead>")
  webStr = webStr + ("<tr>")
  webStr = webStr + ("<th>History</th>")
  webStr = webStr + ("<th>Receiver</th>")
  webStr = webStr + ("<th>Date</th>")
  webStr = webStr + ("<th>Tracked SVs</th>")
  webStr = webStr + ("<th>Used SVs</th>")
  webStr = webStr + ("<th>Stinger [%]</th>")
  webStr = webStr + ("<th>App [%]</th>")
  webStr = webStr + ("<th>RTK [%]</th>")
  webStr = webStr + ("<th>Reserve [%]</th>")
  webStr = webStr + ("<th>Details</th>")
  webStr = webStr + ("</tr>")
  webStr = webStr + ("</thead>")
  webStr = webStr + ("<tbody>")

  # Load the JSON file 
  with open('/net/higgs/mnt/data_drive/CPULoad/receivers.json', 'r') as f:
    receivers = json.load(f)

  NumRX = len(receivers['receivers'])

  for i in range(NumRX):
    RXDir = receivers['receivers'][i]['LocalDir']
    RXStr = receivers['receivers'][i]['name']
    trend = []
    ResultsFile = '/net/higgs/mnt/data_drive/CPULoad/' + RXDir + '/loadStats.txt'
    with open(ResultsFile,'r') as f:
      for line in f:
        data = line.rstrip().split()
        trend.append({'Year':data[0],
                      'Month':data[1],
                      'Day':data[2],
                      'DOY': datetime.datetime(int(data[0]),int(data[1]),int(data[2])).timetuple().tm_yday,
                      'Tracked':float(data[3]),
                      'Used':float(data[4]),
                      'Stinger':float(data[5]),
                      'App':float(data[6]),
                      'RTK':float(data[7]),
                      'Reserve':float(data[8])})

    webStr = webStr + "<tr>"

    webLink = 'http://quark.eng.trimble.com/DataDump/CPULoad/' + RXDir + '/loadStats.html'
    webStr = webStr + (  "<td style='white-space: nowrap;'>"
                       + "<a href='" + webLink + "' target='_blank'>"
                       + "<img class='click' width='20px' src='http://quark.eng.trimble.com/DataDump/DashBoard/DataTable.png'></img>" 
                       + "</a>&nbsp;")
                       
    webLink = 'http://quark.eng.trimble.com/DataDump/CPULoad/' + RXDir + '/RxTrend.png'
    webStr =  webStr + ( "<a onclick='toggleImg(this)' href='#'>" 
                        + "<img class='click' width='20px' src='http://quark.eng.trimble.com/DataDump/DashBoard/graph.png'></img>" 
                        + "<img class='links' src='" + webLink + "'/></a></td>")

    webStr = webStr + "<td>" + RXStr + '</td>'

    webLink = (   'http://quark.eng.trimble.com/DataDump/CPULoad/' + RXDir + '/CPU_'
                + trend[-1]['Year'] 
                + trend[-1]['Month'].zfill(2)
                + trend[-1]['Day'].zfill(2) 
                + '.Loading.png')

    webStr =  webStr + ( "<td><a onclick='toggleImg(this)' href='#'>" 
                        + trend[-1]['Year'] 
                        + trend[-1]['Month'].zfill(2)
                        + trend[-1]['Day'].zfill(2) 
                        + "<img class='links' src='" + webLink + "'/></a></td>")
    webStr = webStr + "<td>" + str(trend[-1]['Tracked']).zfill(2) + '</td>'
    webStr = webStr + "<td>" + str(trend[-1]['Used']).zfill(2) + '</td>'
    webStr = webStr + "<td>" + str(trend[-1]['Stinger']).zfill(2) + '</td>'
    webStr = webStr + "<td>" + str(trend[-1]['App']).zfill(2) + '</td>'
    webStr = webStr + "<td>" + str(trend[-1]['RTK']).zfill(2) + '</td>'
    webStr = webStr + "<td>" + str(trend[-1]['Reserve']).zfill(2) + '</td>'

    if(receivers['receivers'][i]['fileSource'] == 'COFTP'):
      prefix = receivers['receivers'][i]['prefix']
      webLink = ("http://dave.trimble.com/testing/MON_" + trend[-1]['Year'] + "/" 
                  + prefix + "/" + trend[-1]['Year'] 
                  + '/' + trend[-1]['Month'].zfill(2)
                  + '/' + trend[-1]['Year'] 
                  + '_' + trend[-1]['Month'].zfill(2)
                  + '_' + trend[-1]['Day'].zfill(2) 
                  + '-HTML_CPU_Report.html')

      webStr = webStr + (  "<td style='white-space: nowrap;'>"
                         + "<a href='" + webLink + "' target='_blank'>X</a></td>")
    else:
      webStr = webStr + "<td>-</td>"


    webStr = webStr + "</tr>"

  webStr = webStr + ("</tbody>")
  webStr = webStr + ("</table>")
  webStr = webStr + ("</div>")
  webStr = webStr + ("</div>")

  ######################################
  # Now the Stinger details
  ######################################

  webStr = webStr + "<div class='col-sm-16'>"
  webStr = webStr + ("<h3>Stinger CPU Load</h3>")
  webStr = webStr + ("<div class='table-responsive'>")
  webStr = webStr + ("<table id='data-table-basic' class='table table-striped'>")
  webStr = webStr + ("<thead>")
  webStr = webStr + ("<tr>")
  webStr = webStr + ("<th>History</th>")
  webStr = webStr + ("<th>Receiver</th>")
  webStr = webStr + ("<th>Date</th>")
  webStr = webStr + ("<th>Tracked SVs</th>")
  webStr = webStr + ("<th>Used SVs</th>")
  webStr = webStr + ("<th>BM [%]</th>")
  webStr = webStr + ("<th>LM [%]</th>")
  webStr = webStr + ("<th>MM [%]</th>")
  webStr = webStr + ("<th>PVT [%]</th>")
  webStr = webStr + ("<th>LDPC [%]</th>")
  webStr = webStr + ("<th>AJ [%]</th>")
  webStr = webStr + ("<th>ACQ [%]</th>")
  webStr = webStr + ("</tr>")
  webStr = webStr + ("</thead>")
  webStr = webStr + ("<tbody>")

  for i in range(NumRX):
    RXDir = receivers['receivers'][i]['LocalDir']
    RXStr = receivers['receivers'][i]['name']
    trend = []
    ResultsFile = '/net/higgs/mnt/data_drive/CPULoad/' + RXDir + '/loadStinger.txt'
    with open(ResultsFile,'r') as f:
      for line in f:
        data = line.rstrip().split()
        trend.append({'Year':data[0],
                      'Month':data[1],
                      'Day':data[2],
                      'DOY': datetime.datetime(int(data[0]),int(data[1]),int(data[2])).timetuple().tm_yday,
                      'Tracked':float(data[3]),
                      'Used':float(data[4]),
                      'BM':float(data[5]),
                      'LM':float(data[6]),
                      'MM':float(data[7]),
                      'PVT':float(data[8]),
                      'LDPC':float(data[9]),
                      'AJ':float(data[10]),
                      'ACQ':float(data[11])})

    webStr = webStr + "<tr>"

    webLink = 'http://quark.eng.trimble.com/DataDump/CPULoad/' + RXDir + '/loadStinger.html'
    webStr = webStr + (  "<td style='white-space: nowrap;'>"
                       + "<a href='" + webLink + "' target='_blank'>"
                       + "<img class='click' width='20px' src='http://quark.eng.trimble.com/DataDump/DashBoard/DataTable.png'></img>" 
                       + "</a>&nbsp;")
                       
    webLink = 'http://quark.eng.trimble.com/DataDump/CPULoad/' + RXDir + '/StingerTrend.png'
    webStr =  webStr + ( "<a onclick='toggleImg(this)' href='#'>" 
                        + "<img class='click' width='20px' src='http://quark.eng.trimble.com/DataDump/DashBoard/graph.png'></img>" 
                        + "<img class='links' src='" + webLink + "'/></a></td>")
    
    webStr = webStr + "<td>" + RXStr + '</td>'

    webLink = (   'http://quark.eng.trimble.com/DataDump/CPULoad/' + RXDir + '/CPU_'
                + trend[-1]['Year'] 
                + trend[-1]['Month'].zfill(2)
                + trend[-1]['Day'].zfill(2) 
                + '.Stinger.png')
    webStr =  webStr + ( "<td><a onclick='toggleImg(this)' href='#'>" 
                        + trend[-1]['Year'] 
                        + trend[-1]['Month'].zfill(2)
                        + trend[-1]['Day'].zfill(2) 
                        + "<img class='links' src='" + webLink + "'/></a></td>")
    webStr = webStr + "<td>" + str(trend[-1]['Tracked']).zfill(2) + '</td>'
    webStr = webStr + "<td>" + str(trend[-1]['Used']).zfill(2) + '</td>'
    webStr = webStr + "<td>" + str(trend[-1]['BM']).zfill(2) + '</td>'
    webStr = webStr + "<td>" + str(trend[-1]['LM']).zfill(2) + '</td>'
    webStr = webStr + "<td>" + str(trend[-1]['MM']).zfill(2) + '</td>'
    webStr = webStr + "<td>" + str(trend[-1]['PVT']).zfill(2) + '</td>'
    webStr = webStr + "<td>" + str(trend[-1]['LDPC']).zfill(2) + '</td>'
    webStr = webStr + "<td>" + str(trend[-1]['AJ']).zfill(2) + '</td>'
    webStr = webStr + "<td>" + str(trend[-1]['ACQ']).zfill(2) + '</td>'
    webStr = webStr + "</tr>"

  webStr = webStr + ("</tbody>")
  webStr = webStr + ("</table>")
  webStr = webStr + ("</div>")
  webStr = webStr + ("</div>")

  webStr = webStr + ("</section>")
  webStr = webStr + ("{% endblock %}")
  webStr = webStr + ("{% block userscript %}")
  webStr = webStr + ("<script>")
  webStr = webStr + ("</script>")
  webStr = webStr + ("<script src='static/js/app.js'></script>")
  webStr = webStr + ("{% endblock %}")

  return render_template_string(Markup(webStr))

def buildTTFFRow(resDir,model,filenameRoot,dailyFile,notes):

  trend = []
  ResultsFile = '/net/higgs/mnt/data_drive/' + resDir + '/' + filenameRoot + '.txt'
  with open(ResultsFile,'r') as f:
    for line in f:
      data = line.rstrip().split()
      trend.append({'Year':int(data[0]),
                    'Month':int(data[1]),
                    'Day':int(data[2]),
                    'DOY': datetime.datetime(int(data[0]),int(data[1]),int(data[2])).timetuple().tm_yday,
                    'Connect95':float(data[5]),
                    'TTFF95':float(data[8])})

  webStr = "<tr>"
  webLink = 'http://quark.eng.trimble.com/DataDump/' + resDir +'/' + filenameRoot + '.html'
  webStr = webStr + (  "<td style='white-space: nowrap;'>"
                     + "<a href='" + webLink + "' target='_blank'>"
                     + "<img class='click' width='20px' src='http://quark.eng.trimble.com/DataDump/DashBoard/DataTable.png'></img>" 
                     + "</a>&nbsp;")
                     
  webLink = 'http://quark.eng.trimble.com/DataDump/' + resDir + '/' + filenameRoot + '.png'
  webStr =  webStr + ( "<a onclick='toggleImg(this)' href='#'>" 
                      + "<img class='click' width='20px' src='http://quark.eng.trimble.com/DataDump/DashBoard/graph.png'></img>" 
                      + "<img class='links' src='" + webLink + "'/></a></td>")

  webLink = (   'http://quark.eng.trimble.com/DataDump/' + resDir + '/plots/'
              + str(trend[-1]['Year']) + '-' 
              + str(trend[-1]['Month']).zfill(2) + '-'
              + str(trend[-1]['Day']).zfill(2)  + '-'
              + dailyFile)
  webStr =  webStr + ( "<td><a onclick='toggleImg(this)' href='#'>" 
                      + str(trend[-1]['Year']) + '-' 
                      + str(trend[-1]['Month']).zfill(2) + '-'
                      + str(trend[-1]['Day']).zfill(2) 
                      + "<img class='links' src='" + webLink + "'/></a></td>")
  webStr = webStr + "<td>" + str(trend[-1]['Connect95']).zfill(2) + '</td>'
  webStr = webStr + "<td>" + str(trend[-1]['TTFF95']).zfill(2) + '</td>'
  webStr = webStr + "<td>" + model + '</td>'
  webStr = webStr + "<td>" + notes + '</td>'
  webStr = webStr + "</tr>"

  return(webStr)

@app.route("/TTFF.html")
def TTFF():
  webStr = ("{% extends 'menu.html' %}")
  webStr = webStr + ("{% block main %}")
  webStr = webStr + ("<section id='content'>")
  webStr = webStr + ("<div class='row'>")
  webStr = webStr + "<div class='col-sm-12'>"
  webStr = webStr + ("<h3>TTFF - Power cycled</h3>")
  webStr = webStr + ("<div class='table-responsive'>")

  webStr = webStr + ("<table id='data-table-basic' class='table table-striped'>")
  webStr = webStr + ("<thead>")
  webStr = webStr + ("<tr>")
  webStr = webStr + ("<th>History</th>")
  webStr = webStr + ("<th>Date</th>")
  webStr = webStr + ("<th>95% Connect<sup>1</sup> [sec]</th>")
  webStr = webStr + ("<th>95% TTFF<sup>2</sup> [sec]</th>")
  webStr = webStr + ("<th>Model</th>")
  webStr = webStr + ("<th>Notes</th>")
  webStr = webStr + ("</tr>")
  webStr = webStr + ("</thead>")
  webStr = webStr + ("<tbody>")

  notes = ( 'Cold Start / Injected Alm/Eph/Time<sup>4</sup>/Pos')
  webStr += buildTTFFRow('TTFF-ColdInject','BD935','BD935-1-TTFF-Trend','BD935-1-PowerUpTest.png',notes)
  
  notes = ( 'Warm Start - no Time')
  webStr += buildTTFFRow('TTFF','BD935','BD935-1-TTFF-Trend','BD935-1-PowerUpTest.png',notes)
  
  notes = ( 'Warm Start - NTP Time')
  webStr += buildTTFFRow('TTFF_BCUDA','Barracuda','Barracuda-1-TTFF-Trend','Barracuda-1-PowerUpTest.png',notes)
  notes = ( 'Warm Start - NTP Time')
  webStr += buildTTFFRow('TTFF_Helium','Helium','Helium-1-TTFF-Trend','Helium-1-PowerUpTest.png',notes)
  notes = ( 'Cold Start / No Seeding')
  webStr += buildTTFFRow('TTFF-970Cold','BD970','BD970-1-TTFF-Trend','BD970-1-PowerUpTest.png',notes)

  webStr = webStr + ("</tbody>")
  webStr = webStr + ("</table>")

  webStr = webStr + ("<h3>TTFF - Old Stations</h3>")
  webStr = webStr + ("<table id='data-table-basic' class='table table-striped'>")
  webStr = webStr + ("<thead>")
  webStr = webStr + ("<tr>")
  webStr = webStr + ("<th>History</th>")
  webStr = webStr + ("<th>Date</th>")
  webStr = webStr + ("<th>95% Connect<sup>1</sup> [sec]</th>")
  webStr = webStr + ("<th>95% TTFF<sup>2</sup> [sec]</th>")
  webStr = webStr + ("<th>Model</th>")
  webStr = webStr + ("<th>Notes</th>")
  webStr = webStr + ("</tr>")
  webStr = webStr + ("</thead>")
  webStr = webStr + ("<tbody>")

  
  notes = ( 'Shipping Start - no Time')
  webStr += buildTTFFRow('TTFF-Shipping','BD935','BD935-1-TTFF-Trend','BD935-1-PowerUpTest.png',notes)
  
  notes = ( 'Cold start + Shipping - no Time')
  webStr += buildTTFFRow('TTFF-940','BD940','BD940-1-TTFF-Trend','BD940-1-PowerUpTest.png',notes)
  
  notes = ( 'Hot(ish) Start - NTP<sup>3</sup> Time')
  webStr += buildTTFFRow('TTFF-Warm','BD935','BD935-1-TTFF-Trend','BD935-1-PowerUpTest.png',notes)
  
  notes = ( 'Shipping start - no Time' )
  webStr += buildTTFFRow('TTFF-982','BD982','BD982-1-TTFF-Trend','BD982-1-PowerUpTest.png',notes)

  notes = ( 'Cold Start - NTP Time')
  webStr += buildTTFFRow('TTFF-ColdwTime','BD935','BD935-1-TTFF-Trend','BD935-1-PowerUpTest.png',notes)
  
  webStr = webStr + ("</tbody>")
  webStr = webStr + ("</table>")



  webStr += (  "<p><sup>1</sup>The Connect time is the time from when power is applied until a "
             + "ping response is received. This includes the boot time, along with the DHCP "
             + "negotiation<br/>"
             + "<sup>2</sup>Includes boot time which is roughly the 'Connect' time. All times "
             + "are from when power is applied<br/>"
             + "<sup>3</sup>The BDxxx do not have a battery backed RTC. They can get time via "
             + "NTP however this takes several seconds<br/>"
             + "<sup>4</sup>Time can be provided to the BDxxx by injecting it via a Trimcomm "
             + "command. This occurs after boot (so not as fast is if we had a battery backed RTC) "
             + "but in our testing is probably faster than using NTP</p>")

  webStr = webStr + ("</div>")


  webStr = webStr + ("<h3>TTFF - Software Reset</h3>")
  webStr = webStr + ("<div class='table-responsive'>")
  webStr = webStr + ("<table id='data-table-basic' class='table table-striped'>")
  webStr = webStr + ("<thead>")
  webStr = webStr + ("<tr>")
  webStr = webStr + ("<th>History</th>")
  webStr = webStr + ("<th>Date</th>")
  webStr = webStr + ("<th>95% Connect<sup>1</sup> [sec]</th>")
  webStr = webStr + ("<th>95% TTFF<sup>2</sup> [sec]</th>")
  webStr = webStr + ("<th>Model</th>")
  webStr = webStr + ("<th>Notes</th>")
  webStr = webStr + ("</tr>")
  webStr = webStr + ("</thead>")
  webStr = webStr + ("<tbody>")

  notes = ( 'Hot(ish) Start - NTP<sup>3</sup> Time')
  webStr += buildTTFFRow('TTFF-SoftReset','BD935','BD935-1-TTFF-Trend','BD935-1-PowerUpTest.png',notes)
  
  webStr = webStr + ("</tbody>")
  webStr = webStr + ("</table>")

  webStr += (  "<p><sup>1</sup>There is a variable delay after a SW reboot command is issued until "
             + "the unit reboots. Once the command is sent the unit is 'pinged' with a 1 second "
             + "timeout. When the unit stops responding we assume the reset is happening and start "
             + "our timer. The Connect time is the time after the ping response stops until it "
             + "starts responding again<br/>"
             + "<sup>2</sup>This is the time from when we believe the unit actually started rebooting (note 1) "
             + "until the first position solution<br/>"
             + "<sup>3</sup>The BDxxx do not have a battery backed RTC. They can get time via "
             + "NTP however this takes several seconds<br/>")

  webStr = webStr + ("</div>")

  webStr = webStr + ("</div>")
  webStr = webStr + ("</div>")
  webStr = webStr + ("</section>")
  webStr = webStr + ("{% endblock %}")
  webStr = webStr + ("{% block userscript %}")
  webStr = webStr + ("<script>")
  webStr = webStr + ("</script>")
  webStr = webStr + ("<script src='static/js/app.js'></script>")
  webStr = webStr + ("{% endblock %}")

  return render_template_string(Markup(webStr))


@app.route("/DDCodeTrend.html")
def DDCodeTrend():

  BaselineType = request.args.get('BaselineType')
  print(BaselineType)

  webStr = ("{% extends 'menu.html' %}")
  webStr = webStr + ("{% block main %}")
  webStr = webStr + ("<section id='content'>")
  webStr = webStr + ("<div class='row'>")

  webStr = webStr + DDTable("BD940-BD940 Code " + BaselineType,"-BD940_BD940_" + BaselineType,BaselineType,dCode)
  webStr = webStr + DDTable("BD935-BD935 Code " + BaselineType,"-BD935_BD935_" + BaselineType,BaselineType,dCode)

  webStr = webStr + ("</div>")
  webStr = webStr + ("</section>")
  webStr = webStr + ("{% endblock %}")
  webStr = webStr + ("{% block userscript %}")
  webStr = webStr + ("<script>")
  webStr = webStr + ("</script>")
  webStr = webStr + ("<script src='static/js/app.js'></script>")
  webStr = webStr + ("{% endblock %}")
  
  return render_template_string(Markup(webStr))

@app.route("/DDCarrTrend.html")
def DDCarrTrend():

  BaselineType = request.args.get('BaselineType')
  print(BaselineType)

  webStr = ("{% extends 'menu.html' %}")
  webStr = webStr + ("{% block main %}")
  webStr = webStr + ("<section id='content'>")
  webStr = webStr + ("<div class='row'>")

  webStr = webStr + DDTable("BD940-BD940 Carrier " + BaselineType,"-BD940_BD940_" + BaselineType,BaselineType,dCarr)
  webStr = webStr + DDTable("BD935-BD935 Carrier " + BaselineType,"-BD935_BD935_" + BaselineType,BaselineType,dCarr)

  webStr = webStr + ("</div>")
  webStr = webStr + ("</section>")
  webStr = webStr + ("{% endblock %}")
  webStr = webStr + ("{% block userscript %}")
  webStr = webStr + ("<script>")
  webStr = webStr + ("</script>")
  webStr = webStr + ("<script src='static/js/app.js'></script>")
  webStr = webStr + ("{% endblock %}")
  
  return render_template_string(Markup(webStr))

@app.route("/DDDoppTrend.html")
def DDDoppTrend():

  BaselineType = request.args.get('BaselineType')
  print(BaselineType)

  webStr = ("{% extends 'menu.html' %}")
  webStr = webStr + ("{% block main %}")
  webStr = webStr + ("<section id='content'>")
  webStr = webStr + ("<div class='row'>")

  webStr = webStr + DDTable("BD940-BD940 Doppler " + BaselineType,"-BD940_BD940_" + BaselineType,BaselineType,dDopp)
  webStr = webStr + DDTable("BD935-BD935 Doppler " + BaselineType,"-BD935_BD935_" + BaselineType,BaselineType,dDopp)

  webStr = webStr + ("</div>")
  webStr = webStr + ("</section>")
  webStr = webStr + ("{% endblock %}")
  webStr = webStr + ("{% block userscript %}")
  webStr = webStr + ("<script>")
  webStr = webStr + ("</script>")
  webStr = webStr + ("<script src='static/js/app.js'></script>")
  webStr = webStr + ("{% endblock %}")
  
  return render_template_string(Markup(webStr))




ThreadsActive = True

def signal_handler(signal, frame):
  global ThreadsActive
  print('You pressed Ctrl+C!')
  sendToSlack("DashBoard App terminating")
  ThreadsActive = False

  if(runGetSVData == True):
    time.sleep(5)
    for i in range(len(threadsSVdata)):
      threadsSVdata[i].join()
    for i in range(len(threadsSYSdata)):
      threadsSYSdata[i].join()
  
  if(runTempThread == True):
    threadAntennaRoomMonitor.join()

  print("Exiting")
  sys.exit(0)

TrackLUT = [ {'sys':'G','sig':'L1CA','longSys':'GPS'},
             {'sys':'G','sig':'L2E','longSys':'GPS'},
             {'sys':'G','sig':'L1C','longSys':'GPS'},
             {'sys':'G','sig':'L2C','longSys':'GPS'},
             {'sys':'G','sig':'L5','longSys':'GPS'},

             {'sys':'E','sig':'E1','longSys':'Galileo'},
             {'sys':'E','sig':'E5A','longSys':'Galileo'},
             {'sys':'E','sig':'E5B','longSys':'Galileo'},
             {'sys':'E','sig':'E5Alt','longSys':'Galileo'},
             {'sys':'E','sig':'E6','longSys':'Galileo'},
       
             {'sys':'J','sig':'L1CA','longSys':'QZSS'},
             {'sys':'J','sig':'L1C','longSys':'QZSS'},
             {'sys':'J','sig':'L2C','longSys':'QZSS'},
             {'sys':'J','sig':'L5','longSys':'QZSS'},
       
             {'sys':'I','sig':'L5CA','longSys':'IRNSS'},
             {'sys':'I','sig':'S1CA','longSys':'IRNSS'},
       
             {'sys':'C','sig':'B1I','longSys':'BeiDou'},
             {'sys':'C','sig':'B1C','longSys':'BeiDou'},
             {'sys':'C','sig':'B2I','longSys':'BeiDou'},
             {'sys':'C','sig':'B2A','longSys':'BeiDou'},
             {'sys':'C','sig':'B2B','longSys':'BeiDou'},
             {'sys':'C','sig':'B3I','longSys':'BeiDou'}
           ]



@app.route("/ErrorTrend.html")
def ErrorTrend():
  webStr  = ("{% extends 'menu.html' %}")

  webStr += "{% block main %}"
  webStr += "<section id='content'>"
  webStr += "<div class='row'>"
  webStr += "<h3>Error Trend</h3>"
  webStr += "<div class='col'>"
  webStr += "<div class='col-sm-10'>"
  webStr += "<div>"

  webStr += '<br/>'
  webStr += "</div>"
  webStr += "</div></div>"
  filename = "ErrorTrend.png"

  webStr += "<img id='DopplerPlot' height='600' src='http://quark.eng.trimble.com/DataDump/DashBoard/TerrasatCycleSlips/" + filename + "';>"
  webStr += "</div>"
  webStr += "</section>"

  webStr += ("{% endblock %}")

  webStr += ("{% block userscript %}")
  webStr += ("<script src='static/js/app.js'></script>")
  #webStr += ("<script>startup();</script>")
  webStr += ("{% endblock %}")


  return render_template_string(Markup(webStr))



@app.route("/NetworkErrors.html")
def NetwworkErrors():
  region   = request.args.get('Region')

  if( (region is None) or ( (region!='US') and (region!='EU') ) ):
    region = "EU"
  
  webStr  = ("{% extends 'menu.html' %}")

  webStr += "{% block main %}"
  webStr += "<section id='content'>"
  webStr += "<div class='row'>"
  webStr += "<h3>Warnings and Errors</h3>"

  webStr += '<script>'
  webStr += 'function ChangeRegion() {'
  webStr += 'var region = document.getElementById("region").value;'
  webStr += 'var filename = region + "-WarningErrors.png";'
  webStr += 'document.getElementById("DopplerPlot").src = "http://quark.eng.trimble.com/DataDump/DashBoard/TerrasatCycleSlips/" + filename;'
  webStr += 'window.history.pushState("Page","Title","NetworkErrors.html?Region=" + region);'
  webStr += ' }'
  webStr += ' </script>'

  webStr += "<div class='col'>"
  webStr += "<div class='col-sm-10'>"
  webStr += "<div>"

  webStr += '<br/>'
  webStr += '<label for="type">Region</label>'
  webStr += '<select name="region" id="region" onchange="ChangeRegion()">'
  if(region == 'US'):
    webStr += '<option value="US" selected="selected">US</option>'
    webStr += '<option value="EU">EU</option>'
  else:
    webStr += '<option value="US"">US</option>'
    webStr += '<option value="EU" selected="selected">EU</option>'
  webStr += ' </select> '
  
  webStr += "</div>"

  webStr += "</div></div>"
  filename = region + "-WarningErrors.png"

  webStr += "<img id='DopplerPlot' height='600' src='http://quark.eng.trimble.com/DataDump/DashBoard/TerrasatCycleSlips/" + filename + "';>"
  webStr += "</div>"
  webStr += "</section>"

  webStr += ("{% endblock %}")

  webStr += ("{% block userscript %}")
  webStr += ("<script src='static/js/app.js'></script>")
  #webStr += ("<script>startup();</script>")
  webStr += ("{% endblock %}")


  return render_template_string(Markup(webStr))




@app.route("/L2EDopplerError.html")
def L2EDopplerError():
  region   = request.args.get('Region')

  if( (region is None) or ( (region!='US') and (region!='EU') ) ):
    region = "EU"
  
  webStr  = ("{% extends 'menu.html' %}")

  webStr += "{% block main %}"
  webStr += "<section id='content'>"
  webStr += "<div class='row'>"
  webStr += "<h3>L2E Doppler Error</h3>"

  webStr += '<script>'
  webStr += 'function ChangeRegion() {'
  webStr += 'var region = document.getElementById("region").value;'
  webStr += 'var filename = region + "-DopplerErrors.png";'
  webStr += 'document.getElementById("DopplerPlot").src = "http://quark.eng.trimble.com/DataDump/DashBoard/TerrasatCycleSlips/Doppler/" + filename;'
  webStr += 'window.history.pushState("Page","Title","L2EDopplerError.html?Region=" + region);'
  webStr += ' }'
  webStr += ' </script>'

  webStr += "<div class='col'>"
  webStr += "<div class='col-sm-10'>"
  webStr += "<div>"

  webStr += '<br/>'
  webStr += '<label for="type">Region</label>'
  webStr += '<select name="region" id="region" onchange="ChangeRegion()">'
  if(region == 'US'):
    webStr += '<option value="US" selected="selected">US</option>'
    webStr += '<option value="EU">EU</option>'
  else:
    webStr += '<option value="US"">US</option>'
    webStr += '<option value="EU" selected="selected">EU</option>'
  webStr += ' </select> '
  
  webStr += "</div>"

  webStr += "</div></div>"
  filename = region + "-DopplerErrors.png"

  webStr += "<img id='DopplerPlot' height='600' src='http://quark.eng.trimble.com/DataDump/DashBoard/TerrasatCycleSlips/Doppler/" + filename + "';>"
  webStr += "</div>"
  webStr += "</section>"

  webStr += ("{% endblock %}")

  webStr += ("{% block userscript %}")
  webStr += ("<script src='static/js/app.js'></script>")
  #webStr += ("<script>startup();</script>")
  webStr += ("{% endblock %}")


  return render_template_string(Markup(webStr))



@app.route("/RTXNetTrackSummary.html")
def RTXNetTrackSummary():
  plotType = request.args.get('Type')
  region   = request.args.get('Region')
  mask     = request.args.get('Mask')

  # Default
  if(plotType is None):
    plotType = "GPS-L1CA"

  sigList = []
  for thisType in TrackLUT:
    if(    ('IRNSS' in thisType['longSys'] )
        or ('QZSS' in thisType['longSys'] ) ):
      continue

    sigType = thisType['longSys'] + '-' + thisType['sig']
    sigList.append(sigType)

  if( (region is None) or ( (region!='US') and (region!='EU') ) ):
    region = "US"
  
  if( (mask is None) or ( (mask!='5') and (mask!='10') ) ):
    mask = '5'

  webStr  = ("{% extends 'menu.html' %}")

  webStr += "{% block main %}"
  webStr += "<section id='content'>"
  webStr += "<div class='row'>"
  webStr += "<h3>Tracking Yield History</h3>"

  webStr += '<script>'
  webStr += 'function ChangeSignal() {'
  webStr += 'var type   = document.getElementById("signal").value;'
  webStr += 'var region = document.getElementById("region").value;'
  webStr += 'var mask   = document.getElementById("mask").value.toString();'
  webStr += 'var filename = region + "-" + type + "-TrackingYield_elev" + mask + ".png";'
  webStr += 'document.getElementById("TrackYieldPlot").src = "http://quark.eng.trimble.com/DataDump/DashBoard/TerrasatCycleSlips/Tracking/" + filename;'
  webStr += 'window.history.pushState("Page","Title","RTXNetTrackSummary.html?Type=" + type + "&Region=" + region + "&Mask=" + mask);'
  webStr += ' }'
  webStr += ' </script>'

  webStr += "<div class='col'>"
  webStr += "<div class='col-sm-10'>"
  webStr += "<div>"

  webStr += '<br/>'
  webStr += '<label for="type">Signals</label>'
  webStr += '<select name="signal" id="signal" onchange="ChangeSignal()">'
  
  for thisSig in sigList:
    if(thisSig == plotType):
      webStr += '<option value="' + thisSig + '" selected="selected">' + thisSig +'</option>'
    else:
      webStr += '<option value="' + thisSig + '">' + thisSig+'</option>'
  webStr += ' </select> '

  webStr += '<label for="type">Region</label>'
  webStr += '<select name="region" id="region" onchange="ChangeSignal()">'
  if(region == 'US'):
    webStr += '<option value="US" selected="selected">US</option>'
    webStr += '<option value="EU">EU</option>'
  else:
    webStr += '<option value="US"">US</option>'
    webStr += '<option value="EU" selected="selected">EU</option>'
  webStr += ' </select> '
  
  webStr += '<label for="type">Elev. Mask</label>'
  webStr += '<select name="mask" id="mask" onchange="ChangeSignal()">'
  if(mask == '5'):
    webStr += '<option value="5" selected="selected">5</option>'
    webStr += '<option value="10">10</option>'
  else:
    webStr += '<option value="5"">5</option>'
    webStr += '<option value="10" selected="selected">10</option>'
  webStr += ' </select> '


  webStr += "</div>"

  webStr += "</div></div>"
  filename = region + "-" + plotType + "-TrackingYield_elev5.png"


  webStr += "<img id='TrackYieldPlot' height='600' src='http://quark.eng.trimble.com/DataDump/DashBoard/TerrasatCycleSlips/Tracking/" + filename + "';>"
  webStr += "</div>"
  webStr += "</section>"

  webStr += ("{% endblock %}")

  webStr += ("{% block userscript %}")
  webStr += ("<script src='static/js/app.js'></script>")
  #webStr += ("<script>startup();</script>")
  webStr += ("{% endblock %}")


  return render_template_string(Markup(webStr))

@app.route("/RTXNetTrackStation.html")
def RTXNetTrackStation():
  plotType = request.args.get('Type')
  mask     = request.args.get('Mask')
  station  = request.args.get('Station')

  dirList = os.listdir('/net/higgs/mnt/data_drive/TerrasatCycleSlips/Tracking/StationPlots/')
  
  stationList = []
  for thisEntry in dirList:
    stationType = thisEntry[0:2]
    stationID   = thisEntry[2:4]
    if(thisEntry.endswith('.png') and stationID.isnumeric() and ( (stationType == 'IL') or (stationType == 'IN') or (stationType == 'TI') )):
      stationList.append(stationType + stationID)

  stationList = list(set(stationList))
  stationList.sort()

  if(station is None):
    station = 'IN02'

  # Default
  if(plotType is None):
    plotType = "GPS-L1CA"

  sigList = []
  for thisType in TrackLUT:
    if(    ('IRNSS' in thisType['longSys'] )
        or ('QZSS' in thisType['longSys'] ) ):
      continue

    sigType = thisType['longSys'] + '-' + thisType['sig']
    sigList.append(sigType)

  if( (mask is None) or ( (mask!='5') and (mask!='10') ) ):
    mask = '5'

  webStr  = ("{% extends 'menu.html' %}")

  webStr += "{% block main %}"
  webStr += "<section id='content'>"
  webStr += "<div class='row'>"
  webStr += "<h3>Tracking Yield History</h3>"

  webStr += '<script>'
  webStr += 'function ChangeSignal() {'
  webStr += 'var type   = document.getElementById("signal").value;'
  webStr += 'var mask   = document.getElementById("mask").value.toString();'
  webStr += 'var station = document.getElementById("station").value;'
  webStr += 'var filename = station + "-" + type + "-TrackingYield_elev" + mask + ".png";'
  webStr += 'document.getElementById("TrackYieldPlot").src = "http://quark.eng.trimble.com/DataDump/DashBoard/TerrasatCycleSlips/Tracking/StationPlots/" + filename;'
  webStr += 'window.history.pushState("Page","Title","RTXNetTrackStation.html?Type=" + type + "&Station=" + station + "&Mask=" + mask);'
  webStr += ' }'
  webStr += ' </script>'

  webStr += "<div class='col'>"
  webStr += "<div class='col-sm-10'>"
  webStr += "<div>"

  webStr += '<br/>'
  webStr += '<label for="type">Signals</label>'
  webStr += '<select name="signal" id="signal" onchange="ChangeSignal()">'
  
  for thisSig in sigList:
    if(thisSig == plotType):
      webStr += '<option value="' + thisSig + '" selected="selected">' + thisSig +'</option>'
    else:
      webStr += '<option value="' + thisSig + '">' + thisSig+'</option>'
  webStr += ' </select> '

  webStr += '<label for="type">Station</label>'
  webStr += '<select name="station" id="station" onchange="ChangeSignal()">'
  for thisStation in stationList:
    if(thisStation == station):
      webStr += '<option value="' + thisStation + '" selected="selected">' + thisStation +'</option>'
    else:
      webStr += '<option value="' + thisStation + '">' + thisStation+'</option>'
  webStr += ' </select> '
  
  webStr += '<label for="type">Elev. Mask</label>'
  webStr += '<select name="mask" id="mask" onchange="ChangeSignal()">'
  if(mask == '5'):
    webStr += '<option value="5" selected="selected">5</option>'
    webStr += '<option value="10">10</option>'
  else:
    webStr += '<option value="5"">5</option>'
    webStr += '<option value="10" selected="selected">10</option>'
  webStr += ' </select> '


  webStr += "</div>"

  webStr += "</div></div>"
  filename = station + "-" + plotType + "-TrackingYield_elev5.png"

  webStr += "<img id='TrackYieldPlot' height='600' src='http://quark.eng.trimble.com/DataDump/DashBoard/TerrasatCycleSlips/Tracking/StationPlots/" + filename + "';>"
  webStr += "</div>"
  webStr += "</section>"

  webStr += ("{% endblock %}")

  webStr += ("{% block userscript %}")
  webStr += ("<script src='static/js/app.js'></script>")
  #webStr += ("<script>startup();</script>")
  webStr += ("{% endblock %}")


  return render_template_string(Markup(webStr))



#
# ---------------------------------------------
#

@app.route("/RTXNetFFTHistory.html")
def RTXNetFFTHistory():
  station  = request.args.get('Station')
  band     = request.args.get('Band')
  plotType = request.args.get('Type')

  if(band is None):
    band = 'L1'

  if(station is None):
    station = 'IN02'

  if(plotType is None):
    plotType = 'Mean'

  webStr  = ("{% extends 'menu.html' %}")

  webStr += "{% block main %}"
  webStr += "<section id='content'>"
  webStr += "<div class='row'>"
  webStr += "<h3>FFT History</h3>"

  webStr += '<script>'
  webStr += 'function ChangeFFT() {'
  webStr += 'var station = document.getElementById("station").value;'
  webStr += 'var band = document.getElementById("band").value;'
  webStr += 'var type = document.getElementById("type").value;'
  webStr += 'var filename = type + "-" + station + "-" + band + ".png";'
  webStr += 'document.getElementById("fftPlot").src = "http://quark.eng.trimble.com/DataDump/DashBoard/TerrasatCycleSlips/FFTs/Trend/" + filename;'
  webStr += 'window.history.pushState("Page","Title","RTXNetFFTHistory.html?Band=" + band + "&Station=" + station + "&Type=" + type);'
  webStr += ' }'
  webStr += ' </script>'


  webStr += "<div class='col'>"
  webStr += "<div class='col-sm-10'>"
  webStr += "<div>"

  webStr += '<br/>'
  webStr += '<label for="type">Station</label>'
  webStr += '<select name="station" id="station" onchange="ChangeFFT()">'
  
  filedir = '/net/higgs/mnt/data_drive/DataDump/DashBoard/TerrasatCycleSlips/FFTs/Trend/Mean-*-L1.png'
  list_of_files = glob.glob(filedir)
  statList = []
  for fname in list_of_files:
    tokens = fname.split('/')
    statList.append(tokens[-1][5:9])
  
  statList = list(set(list(statList)))
  statList.sort()
  print(statList)

  for stationID in statList:
    if(stationID == station):
      webStr += '<option value="' + stationID + '" selected="selected">' + stationID +'</option>'
    else:
      webStr += '<option value="' + stationID + '">' + stationID +'</option>'
  webStr += ' </select> '

  webStr += '<label for="type">Band</label>'
  webStr += '<select name="band" id="band" onchange="ChangeFFT()">'
  if(band == 'L1'):
    webStr += '<option value="L1" selected="selected">L1</option>'
  else:
    webStr += '<option value="L1">L1</option>'

  if(band == 'L2'):
    webStr += '<option value="L2" selected="selected">L2</option>'
  else:
    webStr += '<option value="L2">L2</option>'
  
  if(band == 'L5'):
    webStr += '<option value="L5" selected="selected">L5</option>'
  else:
    webStr += '<option value="L5">L5</option>'
  
  if(band == 'E6'):
    webStr += '<option value="E6" selected="selected">E6</option>'
  else:
    webStr += '<option value="E6">E6</option>'
  
  if(band == 'B1'):
    webStr += '<option value="B1" selected="selected">B1</option>'
  else:
    webStr += '<option value="B1">B1</option>'
  webStr += ' </select> '

  webStr += '<label for="type">Plot Type:</label>'
  webStr += '<select name="type" id="type" onchange="ChangeFFT()">'
  if(plotType == 'Mean'):
    webStr += '<option value="Mean" selected="selected">Mean</option>'
    webStr += '<option value="Sigma">Sigma</option>'
  else:
    webStr += '<option value="Mean">Mean</option>'
    webStr += '<option value="Sigma" selected="selected">Sigma</option>'
  webStr += '</select>'
  webStr += '<br/>'
  webStr += "</div>"

  webStr += "</div></div>"
  webStr += "<img id='fftPlot' height='600' src='http://quark.eng.trimble.com/DataDump/DashBoard/TerrasatCycleSlips/FFTs/Trend/"+ plotType + "-" + station + "-" + band + ".png'></img>"
  webStr += "</div>"
  webStr += "</section>"

  webStr += ("{% endblock %}")

  webStr += ("{% block userscript %}")
  webStr += ("<script src='static/js/app.js'></script>")
  #webStr += ("<script>startup();</script>")
  webStr += ("{% endblock %}")


  return render_template_string(Markup(webStr))

@app.route("/RTXNetFFTCompare.html")
def RTXNetFFTCompare():

  band     = request.args.get('Band')
  plotType = request.args.get('Type')
  Date     = request.args.get('Date')

  if(band is None):
    band = 'L1'

  if(plotType is None):
    plotType = 'Mean'
 
  try:
    if(Date is not None):
      tokens = Date.split('-')
      if(len(tokens) == 3):
        year  = int(tokens[0])
        month = int(tokens[1]) 
        day   = int(tokens[2]) 
        # ToDo Bounds check
      else:
        Date = None
  except:
    Date = None

  if(Date is None):
    # The processing is a day behind. Get the date
    # from a couple of days ago
    defaultDate = datetime.datetime.now()
    defaultDate -= datetime.timedelta(days=2)
    year   = defaultDate.year
    month  = defaultDate.month
    day    = defaultDate.day

  print(year,month,day)

  webStr = 'Placeholder'
  webStr  = ("{% extends 'menu.html' %}")

  webStr += "{% block main %}"
  webStr += "<section id='content'>"
  webStr += "<div class='row'>"
  webStr += "<h3>FFT Station Comparison</h3>"

  webStr += "<script>"
  webStr += 'function FFTMonth() {'
  webStr += 'var year= document.getElementById("year").value;'
  webStr += 'var month= document.getElementById("month").value;'
  webStr += 'var daySelect = document.getElementById("day");'
  webStr += 'var days = new Date(year, month, 0).getDate();'
  webStr += 'var optionStr = "";'
  webStr += 'for (var d = 1; d <= days; d++)'
  webStr += '{'
  webStr += '  optionStr += "<option value=" + d + ">" + d + "</option>";'
  webStr += '}'
  webStr += 'daySelect.innerHTML = optionStr;'
  webStr += 'getFFTData();'
  webStr += '}'

  webStr += 'function getFFTData(){'
  webStr += 'var year= parseInt(document.getElementById("year").value);'
  webStr += 'var month= parseInt(document.getElementById("month").value);'
  webStr += 'var day = parseInt(document.getElementById("day").value);'

  webStr += 'const today = new Date();'
  webStr += 'const yesterday = new Date(today);'

  webStr += 'yesterday.setDate(yesterday.getDate() - 1);'

  # Prevent a day from before we started analyzing the data
  webStr += 'if( (year == 2020) && (month < 8) )' # No data before 2020-08-01
  webStr += '{'
  webStr += '  month = 8;'
  webStr += '  day   = 1;'
  webStr += '}'
  
  webStr += 'var request = new Date(year, month-1, day);'

  webStr += 'if(request > yesterday)'
  webStr += '{'
  webStr += 'day   = yesterday.getDate();'
  webStr += 'month = yesterday.getMonth() +1;'
  webStr += 'year  = yesterday.getYear() + 1900;'
  webStr += '}'

  # Adjust the pull-downs to match what we are requesting
  webStr += 'document.getElementById("year").value = year;'
  webStr += 'document.getElementById("month").value = month;'
  webStr += 'document.getElementById("day").value = day;'

  webStr += 'year  = year.toString();'
  webStr += 'month = month.toString();'
  webStr += 'day   = day.toString();'

  webStr += 'var band = document.getElementById("band").value;'
  webStr += 'var type = document.getElementById("type").value;'
  webStr += 'if(type == "Mean")'
  webStr += '  prefix = "FFT-";'
  webStr += 'else'
  webStr += '  prefix = "FFTSig-";'
  webStr += 'var dateStr = year + "-";'

  webStr += 'if(month.length < 2)'
  webStr += '  dateStr += "0" + month + "-";'
  webStr += 'else'
  webStr += '  dateStr += month + "-";'
  webStr += 'if(day.length < 2)'
  webStr += '  dateStr += "0" + day;'
  webStr += 'else'
  webStr += '  dateStr += day;'

  webStr += 'var filename = prefix + dateStr + "-" + band + ".png";'
  webStr += 'document.getElementById("fftPlot").src = "http://quark.eng.trimble.com/DataDump/DashBoard/TerrasatCycleSlips/FFTs/" + year.toString() + "/" + filename;'
  # Change the URL at the top of the page without re-loading the page. Allows the user
  # to copy / paste the URL from the browser bar.
  webStr += 'window.history.pushState("Page","Title","RTXNetFFTCompare.html?Date=" + dateStr + "&Band=" + band + "&Type=" + type);'
  webStr += '} // Function end'

  webStr += ' </script>'

  webStr += "<div class='col'>"
  webStr += "<div class='col-sm-10'>"
  webStr += "<div>"

  webStr += "</div>"

  webStr += '<label for="type">Band</label>'
  webStr += '<select name="band" id="band" onchange="getFFTData()">'
  if(band == 'L1'):
    webStr += '<option value="L1" selected="selected">L1</option>'
  else:
    webStr += '<option value="L1">L1</option>'

  if(band == 'L2'):
    webStr += '<option value="L2" selected="selected">L2</option>'
  else:
    webStr += '<option value="L2">L2</option>'
  
  if(band == 'L5'):
    webStr += '<option value="L5" selected="selected">L5</option>'
  else:
    webStr += '<option value="L5">L5</option>'
  
  if(band == 'E6'):
    webStr += '<option value="E6" selected="selected">E6</option>'
  else:
    webStr += '<option value="E6">E6</option>'
  
  if(band == 'B1'):
    webStr += '<option value="B1" selected="selected">B1</option>'
  else:
    webStr += '<option value="B1">B1</option>'
  webStr += ' </select> '

  webStr += '<label for="type">Plot Type:</label>'
  webStr += '<select name="type" id="type" onchange="getFFTData()">'
  if(plotType == 'Mean'):
    webStr += '<option value="Mean" selected="selected">Mean</option>'
    webStr += '<option value="Sigma">Sigma</option>'
  else:
    webStr += '<option value="Mean">Mean</option>'
    webStr += '<option value="Sigma" selected="selected">Sigma</option>'
  webStr += '</select>'

  NowYear = datetime.datetime.now().year
  webStr += '<select id="year" onchange="getFFTData()">'
  for thisYear in range(2020,NowYear+1):
    if(thisYear == year):
      webStr += '<option value="' + str(thisYear) + '" selected="selected">' + str(thisYear) + '</option>'
    else:
      webStr += '<option value="' + str(thisYear) + '">' + str(thisYear) + '</option>'
  webStr += '</select>'

  monthNames = ["January", "February", "March", "April", "May", "June",
                "July", "August", "September", "October", "November", "December"]

  webStr += '<select id="month" onchange="FFTMonth()">'
  for i in range(len(monthNames)):
    if( (i+1) == month):
      webStr += '<option value="' + str(i+1) + '" selected="selected">' + monthNames[i] + '</option>'
    else:
      webStr += '<option value="' + str(i+1) + '">' + monthNames[i] + '</option>'
  webStr += '</select>'

  webStr += '<select id="day" onchange="getFFTData()">'
  for dayNum in range(1,32):
    if(day == dayNum):
      webStr += '<option value="' + str(dayNum) + '" selected="selected">' + str(dayNum)+ '</option>'
    else:
      webStr += '<option value="' + str(dayNum) + '">' + str(dayNum)+ '</option>'
  webStr += '</select>'

  webStr += "</div></div>"

  dayStr = str(year) + '-' + str(month).zfill(2) + '-' + str(day).zfill(2)
  if(plotType == 'Mean'):
    prefix = "FFT-"
  else:
    prefix = "FFTSig-"

  webStr += "<img id='fftPlot' height='600' src='http://quark.eng.trimble.com/DataDump/DashBoard/TerrasatCycleSlips/FFTs/" + str(year) + "/" + prefix + dayStr + "-" + band + ".png'></img>"
  webStr += "</div>"
  webStr += "</section>"

  webStr += ("{% endblock %}")

  webStr += ("{% block userscript %}")
  webStr += ("<script src='static/js/app.js'></script>")
  #webStr += ("<script>startup();</script>")
  webStr += ("{% endblock %}")

  return render_template_string(Markup(webStr))


@app.route("/RTXNetFFTDaily.html")
def RTXNetFFTDaily():

  station = request.args.get('Station')
  band    = request.args.get('Band')
  Date    = request.args.get('Date')

  if(band is None):
    band = 'L1'

  if(station is None):
    station = 'IN01'
 
  try:
    if(Date is not None):
      tokens = Date.split('-')
      if(len(tokens) == 3):
        year  = int(tokens[0])
        month = int(tokens[1]) 
        day   = int(tokens[2]) 
        # ToDo Bounds check
  except:
    Date = None

  if(Date is None):
    # The processing is a day behind. Get the date
    # from a couple of days ago
    defaultDate = datetime.datetime.now()
    defaultDate -= datetime.timedelta(days=2)
    year   = defaultDate.year
    month  = defaultDate.month
    day    = defaultDate.day

  webStr  = ("{% extends 'menu.html' %}")

  webStr += "{% block main %}"
  webStr += "<section id='content'>"
  webStr += "<div class='row'>"
  webStr += "<h3>Daily FFT Plot</h3>"

  webStr += "<script>"
  webStr += 'function FFTMonth() {'
  webStr += 'var year= document.getElementById("year").value;'
  webStr += 'var month= document.getElementById("month").value;'
  webStr += 'var daySelect = document.getElementById("day");'
  webStr += 'var days = new Date(year, month, 0).getDate();'
  webStr += 'var optionStr = "";'
  webStr += 'for (var d = 1; d <= days; d++)'
  webStr += '{'
  webStr += '  optionStr += "<option value=" + d + ">" + d + "</option>";'
  webStr += '}'
  webStr += 'daySelect.innerHTML = optionStr;'
  webStr += 'ChangeFFT();'
  webStr += '}'

  webStr += 'function ChangeFFT(){'
  webStr += 'var station = document.getElementById("station").value;'
  webStr += 'var band = document.getElementById("band").value;'
  webStr += 'var year= parseInt(document.getElementById("year").value);'
  webStr += 'var month= parseInt(document.getElementById("month").value);'
  webStr += 'var day = parseInt(document.getElementById("day").value);'

  webStr += 'const today = new Date();'
  webStr += 'const yesterday = new Date(today);'

  webStr += 'yesterday.setDate(yesterday.getDate() - 1);'

  # Prevent a day from before we started analyzing the data
  webStr += 'if( (year == 2020) && (month < 8) )' # No data before 2020-08-01
  webStr += '{'
  webStr += '  month = 8;'
  webStr += '  day   = 1;'
  webStr += '}'
  
  webStr += 'var request = new Date(year, month-1, day);'

  webStr += 'if(request > yesterday)'
  webStr += '{'
  webStr += '  day   = yesterday.getDate();'
  webStr += '  month = yesterday.getMonth() +1;'
  webStr += '  year  = yesterday.getYear() + 1900;'
  webStr += '}'

  # Adjust the pull-downs to match what we are requesting
  webStr += 'document.getElementById("year").value = year;'
  webStr += 'document.getElementById("month").value = month;'
  webStr += 'document.getElementById("day").value = day;'

  webStr += 'year  = year.toString();'
  webStr += 'month = month.toString();'
  webStr += 'day   = day.toString();'

  webStr += 'var dateStr = year + "-";'
  webStr += 'if(month.length < 2)'
  webStr += '  dateStr += "0" + month + "-";'
  webStr += 'else'
  webStr += '  dateStr += month + "-";'
  webStr += 'if(day.length < 2)'
  webStr += '  dateStr += "0" + day;'
  webStr += 'else'
  webStr += '  dateStr += day;'

  webStr += '  var filename = station + "/" + station + "-" + dateStr + "-" + band + ".png";'
  webStr += '  document.getElementById("fftPlot").src = "http://quark.eng.trimble.com/DataDump/DashBoard/TerrasatCycleSlips/FFTs/Daily/" + year.toString() + "/" + filename;'
  # Change the URL at the top of the page without re-loading the page. Allows the user
  # to copy / paste the URL from the browser bar.
  webStr += '  window.history.pushState("Page","Title","RTXNetFFTDaily.html?Date=" + dateStr + "&Band=" + band + "&Station=" + station );'

  webStr += '} // Function end'

  webStr += ' </script>'

  webStr += "<div class='col'>"
  webStr += "<div class='col-sm-10'>"
  webStr += "<div>"

  webStr += '<br/>'
  webStr += '<label for="type">Station</label>'
  webStr += '<select name="station" id="station" onchange="ChangeFFT()">'
  
  filedir = '/net/higgs/mnt/data_drive/DataDump/DashBoard/TerrasatCycleSlips/FFTs/Daily/' + str(year) + '/*/'
  list_of_dir = glob.glob(filedir)
  print(list_of_dir)
  statList = []
  for fname in list_of_dir:
    print(fname)
    tokens = fname.split('/')
    statList.append(tokens[-2][-4:])
  
  statList = list(set(list(statList)))
  statList.sort()
  print(statList)

  for stationID in statList:
    if(stationID == station):
      webStr += '<option value="' + stationID + '" selected="selected">' + stationID +'</option>'
    else:
      webStr += '<option value="' + stationID + '">' + stationID +'</option>'
  webStr += ' </select> '


  webStr += '<label for="type">Band</label>'
  webStr += '<select name="band" id="band" onchange="ChangeFFT()">'
  if(band == 'L1'):
    webStr += '<option value="L1" selected="selected">L1</option>'
  else:
    webStr += '<option value="L1">L1</option>'

  if(band == 'L2'):
    webStr += '<option value="L2" selected="selected">L2</option>'
  else:
    webStr += '<option value="L2">L2</option>'
  
  if(band == 'L5'):
    webStr += '<option value="L5" selected="selected">L5</option>'
  else:
    webStr += '<option value="L5">L5</option>'
  
  if(band == 'E6'):
    webStr += '<option value="E6" selected="selected">E6</option>'
  else:
    webStr += '<option value="E6">E6</option>'
  
  if(band == 'B1'):
    webStr += '<option value="B1" selected="selected">B1</option>'
  else:
    webStr += '<option value="B1">B1</option>'
  webStr += ' </select> '

  NowYear = datetime.datetime.now().year
  webStr += '<select id="year" onchange="ChangeFFT()">'
  for thisYear in range(2020,NowYear+1):
    if(thisYear == year):
      webStr += '<option value="' + str(thisYear) + '" selected="selected">' + str(thisYear) + '</option>'
    else:
      webStr += '<option value="' + str(thisYear) + '">' + str(thisYear) + '</option>'
  webStr += '</select>'

  monthNames = ["January", "February", "March", "April", "May", "June",
                "July", "August", "September", "October", "November", "December"]

  webStr += '<select id="month" onchange="FFTMonth()">'
  for i in range(len(monthNames)):
    if( (i+1) == month):
      webStr += '<option value="' + str(i+1) + '" selected="selected">' + monthNames[i] + '</option>'
    else:
      webStr += '<option value="' + str(i+1) + '">' + monthNames[i] + '</option>'
  webStr += '</select>'

  webStr += '<select id="day" onchange="ChangeFFT()">'
  for dayNum in range(1,32):
    if(day == dayNum):
      webStr += '<option value="' + str(dayNum) + '" selected="selected">' + str(dayNum)+ '</option>'
    else:
      webStr += '<option value="' + str(dayNum) + '">' + str(dayNum)+ '</option>'
  webStr += '</select>'

  webStr += "</div>"

  dayStr = str(year) + '-' + str(month).zfill(2) + '-' + str(day).zfill(2)

  webStr += "</div></div>"
  webStr += "<img id='fftPlot' height='600' src='http://quark.eng.trimble.com/DataDump/DashBoard/TerrasatCycleSlips/FFTs/Daily/" + str(year) + "/" + station + "/" + station + "-" + dayStr + "-" + band + ".png'></img>"
  webStr += "</div>"
  webStr += "</section>"

  webStr += ("{% endblock %}")

  webStr += ("{% block userscript %}")
  webStr += ("<script src='static/js/app.js'></script>")
  webStr += ("{% endblock %}")

  return render_template_string(Markup(webStr))

@app.route("/RTXNetAnubis.html")
def RTXNetAnubis():

  LUT = [ {"sys":"GPS","type":"1C","desc":"L1 C/A"},
          {"sys":"GPS","type":"1X","desc":"L1C"}, # Data + Pilot L1C
          {"sys":"GPS","type":"2W","desc":"L2E"}, 
          {"sys":"GPS","type":"2X","desc":"L2C(CM+CL)"}, 
          {"sys":"GPS","type":"5X","desc":"L5(I+Q)"},
          {"sys":"GAL","type":"1X","desc":"E1"},
          {"sys":"GAL","type":"5X","desc":"E5A"}, # I+Q E5A
          {"sys":"GAL","type":"6X","desc":"E6"}, # B+C E6
          {"sys":"GAL","type":"7X","desc":"E5B"}, # I+Q E5B
          {"sys":"GAL","type":"8X","desc":"E5AltBOC"}, # I+Q AltBOC
          {"sys":"GLO","type":"1C","desc":"L1 C/A"},
          {"sys":"GLO","type":"1P","desc":"L1 P"},
          {"sys":"GLO","type":"2C","desc":"L2 C/A"},
          {"sys":"GLO","type":"2P","desc":"L2 P"},
          {"sys":"GLO","type":"3X","desc":"G3"}, # I+Q
          {"sys":"BDS","type":"1X","desc":"B1C"}, # B1C
          {"sys":"BDS","type":"2I","desc":"BII"}, # B1I
          #{"sys":"BDS","type":"2X","desc":"B1(I+Q)"}, # What is this code?? B1I (I+Q???)
          {"sys":"BDS","type":"5X","desc":"B2A"}, # B2A Data + PilotB2A Data + Pilot
          {"sys":"BDS","type":"6I","desc":"B3I"}, # B3I
          {"sys":"BDS","type":"7I","desc":"B2I"}, # B2I
          {"sys":"BDS","type":"7D","desc":"B2B"}, # B2B (Data)
          {"sys":"QZS","type":"1C","desc":"L1 C/A"},
          {"sys":"QZS","type":"1Z","desc":"L1S"},
          {"sys":"QZS","type":"2X","desc":"L2C(CM+CL)"},
          {"sys":"QZS","type":"5X","desc":"L5(I+Q)"}]


  system   = request.args.get('System')
  dataType = request.args.get('Type')
  region   = request.args.get('Region')
  plotType = request.args.get('PlotType')

  validType = False
  if( (system is None) or (dataType is None) or (region is None) ):
    system = "GPS"
    dataType = "1C"
    region = "US"
    validType = True
  else:
    for i in range(len(LUT)):
      if(system == LUT[i]['sys'] and (dataType == LUT[i]['type'])):
        validType = True
        break
 
  if(validType == False):
    for i in range(len(LUT)):
      if(system == LUT[i]['sys']):
        dataType = LUT[i]['type']
        validType = True
        break
  
  if(validType == False):
    system = "GPS"
    dataType = "1C"
    region = "US"


  if( (plotType != "MPX") and (plotType != "CNo") ):
    plotType = "CNo"


  print(system,dataType)

  webStr  = ("{% extends 'menu.html' %}")

  webStr += "{% block main %}"
  webStr += "<section id='content'>"
  webStr += "<div class='row'>"
  webStr += "<h3>Anubis " + plotType + "</h3>"

  webStr += '<script>'
  webStr += ' function ChangeData() {'
  webStr += ' var sys  = document.getElementById("system").value;'
  webStr += ' var type = document.getElementById("type").value;'
  webStr += ' var region = document.getElementById("region").value;'
  webStr += ' link = "RTXNetAnubis.html?System=" + sys + "&Type=" + type + "&Region=" + region + "&PlotType=' + plotType + '";'
  webStr += ' window.open(link, "_self");'
  webStr += ' }'
  webStr += '</script>'


  webStr += "<div class='col'>"
  webStr += "<div class='col-sm-10'>"
  webStr += "<div>"

  webStr += '<br/>'

  webStr += '<label for="type">System</label>'
  webStr += '<select name="system" id="system" onchange="ChangeData()">'

  sysTypes = [ {"sys":"GPS","name":"GPS"},
               {"sys":"GLO","name":"GLONASS"},
               {"sys":"GAL","name":"Galileo"},
               {"sys":"BDS","name":"BeiDou"} ]
  webStr += '\n'
  for i in range(len(sysTypes)):
    if(sysTypes[i]['sys'] == system):
      selected = True
    else:
      selected = False
     
    if(selected == True):
      webStr += '<option value="' + system + '" selected="selected">' + sysTypes[i]['name'] + '</option>\n'
    else:
      webStr += '<option value="' + sysTypes[i]['sys'] + '">' + sysTypes[i]['name'] + '</option>\n'
  webStr += ' </select> '

  webStr += '<label for="type">Plot Type:</label>'
  webStr += '<select name="type" id="type" onchange="ChangeData()">'
  webStr += '\n'
  for i in range(len(LUT)):
    if(LUT[i]['sys'] == system):
      if(LUT[i]['type'] == dataType):
        selected = True
      else:
        selected = False
      
      if(selected == True):
        webStr += '<option value="' + dataType + '" selected="selected">' + LUT[i]['desc'] + '</option>\n'
      else:
        webStr += '<option value="' + LUT[i]['type']+ '">' + LUT[i]['desc'] + '</option>\n'
  webStr += '</select>'

  webStr += '<label for="type">Region</label>'
  webStr += '<select name="region" id="region" onchange="ChangeData()">'
  if(region == 'US'):
    webStr += '<option value="US" selected="selected">US</option>'
    webStr += '<option value="EU">EU</option>'
  else:
    webStr += '<option value="US"">US</option>'
    webStr += '<option value="EU" selected="selected">EU</option>'
  webStr += ' </select> '

  webStr += '<br/>'
  webStr += "</div>"


  now = datetime.datetime.now()

  filedir = '/net/higgs/mnt/data_drive/DataDump/DashBoard/TerrasatCycleSlips/AnubisPro/' + str(now.year) + '/*.scan'
  list_of_files = glob.glob(filedir)
  statList = []
  for fname in list_of_files:
    tokens = fname.split('/')
    statList.append(tokens[-1][0:4])
  
  statList = list(set(list(statList)))
  statList.sort()
  print(statList)

  MaxNumStations = 8
  webStr += "<table id='data-table-basic' class='table table-striped'>\n"
  webStr += "<thead><tr>"
  for i in range(MaxNumStations):
    if(plotType == "CNo"):
      webStr += "<th>ID</th><th>CNo</th>"
    else:
      webStr += "<th>ID</th><th>MPX</th>"
  webStr += "</tr></thead>"
  webStr += "<tbody>"
  numStationsInRow = 0
  for station in statList:
    statID = int(station[2:4])
    if( (region == 'EU') and (statID >= 50) ):
      continue
    elif( (region == 'US') and (statID < 50) ):
      continue

    if(numStationsInRow == MaxNumStations):
      webStr += "</tr>\n"
      numStationsInRow = 0
    if(numStationsInRow == 0):
      webStr += "<tr>"

    matchList = [s for s in list_of_files if station in s]
    matchList.sort()
    with open(matchList[-1]) as fid:
      for line in fid:
        tokens = line.split()
        if(tokens[0] == system and tokens[1] == dataType and tokens[3] == plotType):
          if(plotType == "CNo"):
            kpi = str.format('{0:.1f}',float(tokens[4])) 
          else:
            kpi = str.format('{0:.2f}',float(tokens[4])) 
          webStr += "<td>" + station + "</td><td>" + kpi + "</td>"
          numStationsInRow += 1
          break
  webStr += "</tr>\n</tbody></table>"
          

  webStr += "<img id='AnubisPlot' height='600' src='http://quark.eng.trimble.com/DataDump/DashBoard/TerrasatCycleSlips/AnubisPro/" + region + "-" + system + "-" + dataType + "-" + plotType + ".png'></img>"
  webStr += "</div></div>"
  webStr += "</div>"
  webStr += "</section>"

  webStr += ("{% endblock %}")

  webStr += ("{% block userscript %}")
  webStr += ("<script src='static/js/app.js'></script>")
  webStr += ("{% endblock %}")


  return render_template_string(Markup(webStr))

@app.route("/RTXNetDD.html")
def RTXNetDD():
  filedir = '/net/higgs/mnt/data_drive/DataDump/DashBoard/TerrasatCycleSlips/NoPiAnalysis'

  dataType = request.args.get('Type')
  plotType = request.args.get('PlotType')
  if( (plotType != "Code") and (plotType != "Carr") and (plotType != "Doppler") and (plotType != "CNo") ):
    plotType = "Code"

  dataset = request.args.get('Baseline')
  if( (dataset != 'IL2IN') and (dataset != 'IL2TI') and (dataset != 'IN2TI') ):
    dataset = 'IL2IN'
  
  with open(filedir + '/NoPiTypes.json', 'r') as f:
    tmpLUT = json.load(f)

  LUT = []
  gotType = False
  for data in tmpLUT:
    if("BeiDou" in data['label']):
      continue
    LUT.append(data)
    if(data['code'] == dataType):
      gotType = True

  if(gotType == False):
    dataType = "GPS_L1CA"

  print(dataType,plotType)

  webStr  = ("{% extends 'menu.html' %}")

  webStr += "{% block main %}"
  webStr += "<section id='content'>"
  webStr += "<div class='row'>"
  if(plotType == 'Carr'):
    webStr += "<h3>NoPi Processing: Carrier</h3>"
  else:
    webStr += "<h3>NoPi Processing: " + plotType + "</h3>"

  webStr += '<script>'
  webStr += ' function ChangeData() {'
  webStr += ' var dataType = document.getElementById("dataType").value;'
  webStr += ' var baseline = document.getElementById("baseline").value;'
  webStr += ' link = "RTXNetDD.html?Baseline=" + baseline + "&Type=" + dataType + "&PlotType=' + plotType + '";'
  webStr += 'console.log(link);'
  webStr += ' window.open(link, "_self");'
  webStr += ' }'
  webStr += '</script>'

  webStr += '<script>'
  webStr += ' function ChangeNoPiLink() {'
  webStr += ' var stationID = document.getElementById("NoPiLink").value;'
  webStr += ' var dayNum = document.getElementById("DayNum").value;'
  webStr += ' var NoPiLink = document.getElementById("TheLink");'
  
  webStr += ' var dayList = ['
  now = datetime.datetime.now()
  for year in range(now.year - 2020 + 1):
    webStr += str(int((datetime.datetime(year + 2020,1,1) - datetime.datetime(2020,1,1)).total_seconds()/86400))
    if((year+2020) != now.year):
      webStr += ','
  webStr += ' ];'
  webStr += ' console.log(dayList);'

  webStr += ' year = 2020;'
  webStr += ' for(i=0;i<dayList.length;i++)'
  webStr += ' {'
  webStr += '   if(dayNum > dayList[i])'
  webStr += '     year = i + 2020;'
  webStr += ' }'

  webStr += ' var DOY = parseInt(dayNum) - dayList[year-2020];'
  webStr += ' if(DOY < 10)'
  webStr += '   DOYString = "00" + DOY.toString();'
  webStr += ' else if(DOY < 100)'
  webStr += '   DOYString = "0" + DOY.toString();'
  webStr += ' else'
  webStr += '   DOYString = DOY.toString();'

  webStr += ' var link = "http://quark.eng.trimble.com/DataDump/DashBoard/TerrasatCycleSlips/NoPiAnalysis/Results/' + dataset + '/" + year.toString() + "/" + stationID + "/" + DOYString + "/NoPiDI_Top.html";'
  webStr += ' console.log(link);'
  webStr += ' NoPiLink.innerHTML = "Station: " + stationID + " Day: " + dayNum + " Year: " + year.toString() + " DOY: " + DOY.toString();'
  webStr += ' NoPiLink.href = link;'
  webStr += ' }'
  webStr += '</script>'

  webStr += "<div>"
  webStr += '<label for="type">Station for NoPi Link</label>'
  webStr += '<select name="NoPiLink" id="NoPiLink" onchange="ChangeNoPiLink()">'
  
  dirList = []
  for year in range(2020,now.year+1):
    root = filedir + '/Results/' + dataset + '/' + str(year)

    if(not dirList):
      dirList = os.listdir(root)
    else:
      tmpList = os.listdir(root)
      dirList.extend(tmpList)

  # Uniquify
  dirList = list(set(dirList))
  dirList.sort()

  for station in dirList:
    webStr += '<option value="' + station + '">' + station + '</option>\n'
  webStr += ' </select> '

  start = datetime.datetime(2020,10,1,0,0,0)
  delta_days = int((datetime.datetime.now() - start).total_seconds() / 86400) - 1
  
  webStr += '<label for="type">Day Number</label>'
  webStr += '<select name="DayNum" id="DayNum" onchange="ChangeNoPiLink()">'
  for i in range(delta_days):
    webStr += '<option value="' + str(i+276) + '">' + str(i+276) + '</option>\n'
  webStr += ' </select> '

  webStr += 'NoPi Link:'
  webStr += '<a href="" id="TheLink" target="_blank"></a>' 
  webStr += '</div>'

  webStr += "<div class='col'>"
  webStr += "<div class='col-sm-10'>"
  webStr += "<div>"

  webStr += '<br/>'

  webStr += '<label for="type">Data Type</label>'
  webStr += '<select name="dataType" id="dataType" onchange="ChangeData()">'

  webStr += '\n'
  for i in range(len(LUT)):
    if(LUT[i]['code'] == dataType):
      selected = True
    else:
      selected = False
     
    if(selected == True):
      webStr += '<option value="' + dataType + '" selected="selected">' + LUT[i]['label'] + '</option>\n'
    else:
      webStr += '<option value="' + LUT[i]['code'] + '">' + LUT[i]['label'] + '</option>\n'
  webStr += ' </select> '

  webStr += '<label for="baseline">Baseline</label>'
  webStr += '<select name="baseline" id="baseline" onchange="ChangeData()">'
  webStr += '\n'
  if(dataset == 'IL2IN'):
    webStr += '<option value="IL2IN" selected="selected">IL<->IN</option>\n'
    webStr += '<option value="IL2TI">IL<->TI</option>\n'
    webStr += '<option value="IN2TI">IN<->TI</option>\n'
  elif(dataset == 'IL2TI'):
    webStr += '<option value="IL2IN">IL<->IN</option>\n'
    webStr += '<option value="IL2TI" selected="selected">IL<->TI</option>\n'
    webStr += '<option value="IN2TI">IN<->TI</option>\n'
  else:
    webStr += '<option value="IL2IN">IL<->IN</option>\n'
    webStr += '<option value="IL2TI">IL<->TI</option>\n'
    webStr += '<option value="IN2TI" selected="selected">IN<->TI</option>\n'
  webStr += ' </select> '

  webStr += '<br/>'
  webStr += "</div>"

  linkName = 'http://quark.eng.trimble.com/DataDump/DashBoard/TerrasatCycleSlips/NoPiAnalysis/Plots/' + dataset + '/' + dataType + '-' + plotType + '.png'
  webStr += "<img id='AnubisPlot' height='600' src='" + linkName + "'></img>"

  webStr += "</div>"
  webStr += "</div>"

  webStr += "</div>"
  webStr += "</section>"

  webStr += ("{% endblock %}")

  webStr += ("{% block userscript %}")
  webStr += ("<script src='static/js/app.js'></script>")
  webStr += ("{% endblock %}")

  return render_template_string(Markup(webStr))


@app.route("/RTXNetSlipRate.html")
def RTXNetSlipRate():

  filedir = '/net/higgs/mnt/data_drive/DataDump/DashBoard/TerrasatCycleSlips/'
  with open(filedir + 'signals.json', 'r') as f:
    LUT = json.load(f)

  system   = request.args.get('System')
  if(system == None):
    system = 'GPS'

  dataType = request.args.get('Type')
  if(dataType == None):
    dataType = 0
  else:
    dataType = int(dataType)

  region   = request.args.get('Region')
  
  elev = request.args.get('Elev')

  if(elev is None):
    elev = 0
  else:
    elev = int(elev)

  if( (elev != 0) and (elev !=30) ):
    elev = 0


  if(region is None or ( (region != "EU") and (region != "US") ) ):
    region  = "US"

  sysTypes = [ {"sys":"GPS","name":"GPS"},
               {"sys":"GLN","name":"GLONASS"},
               {"sys":"GAL","name":"Galileo"},
               {"sys":"BDS","name":"BeiDou"} ]

  # 'NumType' = the number of data types for each satellite type
  # 'LUTIndex' = the index for this satellite type in the LUT
  gotSystem = False
  for i in range(len(sysTypes)):
    for j in range(len(LUT['types'])):
      if(LUT['types'][j]['satTypeStr'] == sysTypes[i]['sys']):
        sysTypes[i]['NumTypes'] = len(LUT['types'][j]['signals'])
        sysTypes[i]['LUTIndex'] = j

    if(sysTypes[i]['sys'] == system):
      gotSystem = True
      sysTypeIndex = i
      print("Got Sys ",sysTypeIndex,system)
      if(dataType >= sysTypes[i]['NumTypes']):
        dataType = 0

  print(gotSystem)

  if(gotSystem == True):
    freq = LUT['types'][sysTypes[sysTypeIndex]['LUTIndex']]['signals'][dataType]['freq']
    sig  = LUT['types'][sysTypes[sysTypeIndex]['LUTIndex']]['signals'][dataType]['sig']
    satType = LUT['types'][sysTypes[sysTypeIndex]['LUTIndex']]['signals'][dataType]['satType']

    print(sysTypeIndex)
    print(sysTypes[sysTypeIndex])
    print(sysTypes)
    print(freq,sig,satType)
  else:
    satType = "0"
    freq    = "0"
    sig     = "0"
    sysTypeIndex = 0

  webStr  = ("{% extends 'menu.html' %}")

  webStr += "{% block main %}"
  webStr += "<section id='content'>"
  webStr += "<div class='row'>"
  webStr += "<h3>Reported Cycle Slips (&ge; " + str(elev) + "&#176;)</h3>"

  webStr += '<script>'
  webStr += ' function ChangeData() {'
  webStr += ' var sys  = document.getElementById("system").value;'
  webStr += ' var type = document.getElementById("type").value;'
  webStr += ' var region = document.getElementById("region").value;'
  # We could just update the image file here ... reload the page so 
  # that we have a URL we can share
  webStr += ' link = "RTXNetSlipRate.html?System=" + sys + "&Region=" + region + "&Type=" + type + "&Elev=' + str(elev) + '";'

  webStr += ' window.open(link, "_self");'
  webStr += ' }'
  webStr += '</script>'

  webStr += "<div class='col'>"
  webStr += "<div class='col-sm-10'>"
  webStr += "<div>"

  webStr += '<br/>'

  webStr += '<label for="type">System</label>'
  webStr += '<select name="system" id="system" onchange="ChangeData()">'

  webStr += '\n'

  for i in range(len(sysTypes)):
    if(sysTypes[i]['sys'] == system):
      selected = True
    else:
      selected = False
     
    if(selected == True):
      webStr += '<option value="' + system + '" selected="selected">' + sysTypes[i]['name'] + '</option>\n'
    else:
      webStr += '<option value="' + sysTypes[i]['sys'] + '">' + sysTypes[i]['name'] + '</option>\n'
  webStr += ' </select> '

  webStr += '<label for="type">Type</label>'
  webStr += '<select name="type" id="type" onchange="ChangeData()">'
  for i in range(sysTypes[sysTypeIndex]['NumTypes']):

    sigStr = LUT['types'][sysTypes[sysTypeIndex]['LUTIndex']]['signals'][i]['sigStr']
    if('GEO' in sigStr):
      continue

    sigStr = sigStr.replace('(MEO/IGSO)','')
    
    if(dataType == i):
      selected = True
    else:
      selected = False
     
    if(selected == True):
      webStr += '<option value="' + str(i) + '" selected="selected">' + sigStr + '</option>\n'
    else:
      webStr += '<option value="' + str(i) + '">' + sigStr + '</option>\n'
  webStr += ' </select> '

  webStr += '<label for="type">Region</label>'
  webStr += '<select name="region" id="region" onchange="ChangeData()">'
  if(region == 'US'):
    webStr += '<option value="US" selected="selected">US</option>'
    webStr += '<option value="EU">EU</option>'
  else:
    webStr += '<option value="US">US</option>'
    webStr += '<option value="EU" selected="selected">EU</option>'
  webStr += ' </select> '

  webStr += '<br/>'
  webStr += "</div>"

  webStr += '<br/>'
  webStr += "</div>"


  if(elev == 0):
    modifier = ''
  else:
    modifier = '30'
  webStr += "<img id='AnubisPlot' height='600' src='http://quark.eng.trimble.com/DataDump/DashBoard/TerrasatCycleSlips/StationSummary/" + region + "-slips" + modifier + "."+ satType + "." + freq + "." + sig + ".0.png'></img>"
  webStr += "</div></div>"
  webStr += "</div>"
  webStr += "</section>"

  webStr += ("{% endblock %}")

  webStr += ("{% block userscript %}")
  webStr += ("<script src='static/js/app.js'></script>")
  webStr += ("{% endblock %}")
  return render_template_string(Markup(webStr))


@app.route("/IonoStations.html")
def IonoStations():
  region   = request.args.get('Region')
  plotType = request.args.get('Type')

  if( (region is None) or ( (region!='Kearl') and (region!='Brazil') ) ):
    region = "Kearl"
  
  if( (plotType is None) or ( (plotType != 'RMS') and (plotType != 'TimeSeries') and (plotType != 'Activity') ) ):
    plotType = "RMS"
  
  webStr  = ("{% extends 'menu.html' %}")

  webStr += "{% block main %}"
  webStr += "<section id='content'>"
  webStr += "<div class='row'>"
  webStr += "<h3>Ionospheric Activity</h3>"

  webStr += '<script>'
  webStr += 'function ChangeRegion() {'
  webStr += 'console.log("In ChangeRegion");'
  webStr += 'var region   = document.getElementById("region").value;'
  webStr += 'var plotType = document.getElementById("plotType").value;'
  webStr += 'var filename = region + "/";'
  webStr += 'if(plotType == "RMS")'
  webStr += '{'
  webStr += '  filename += "2DRMS.png";'
  webStr += '}'
  webStr += 'else if(plotType == "Activity")'
  webStr += '{'
  webStr += '  filename += "GPSIonoActivity.png";'
  webStr += '}'
  webStr += 'else'
  webStr += '{'
  webStr += '  filename += "AllGPS.png";'
  webStr += '}'
  webStr += 'document.getElementById("IonoPlot").src = "http://quark.eng.trimble.com/DataDump/Scintillation/" + filename;'
  webStr += 'window.history.pushState("Page","Title","IonoStations.html?Region=" + region + "&Type=" + plotType);'
  webStr += ' }'
  webStr += ' </script>'

  webStr += "<div class='col'>"
  webStr += "<div class='col-sm-10'>"
  webStr += "<div>"

  webStr += '<br/>'
  webStr += '<label for="type">Region</label>'
  webStr += '<select name="region" id="region" onchange="ChangeRegion()">'
  if(region == 'Brazil'):
    webStr += '<option value="Brazil" selected="selected">Brazil</option>'
    webStr += '<option value="Kearl">Kearl</option>'
  else:
    webStr += '<option value="Brazil">Brazil</option>'
    webStr += '<option value="Kearl" selected="selected">Kearl</option>'
  webStr += ' </select> '

  webStr += '<label for="type">Plot Type</label>'
  webStr += '<select name="plotType" id="plotType" onchange="ChangeRegion()">'
  if(plotType == 'RMS'):
    webStr += '<option value="RMS" selected="selected">RMS Heatmap</option>'
    webStr += '<option value="Activity">Iono Activity Heatmap</option>'
    webStr += '<option value="TimeSeries">Iono Activity Time-Series</option>'
    filename = '2DRMS.png'
  elif(plotType == 'Activity'):
    webStr += '<option value="RMS">RMS Heatmap</option>'
    webStr += '<option value="Activity" selected="selected">Iono Activity Heatmap</option>'
    webStr += '<option value="TimeSeries">Iono Activity Time-Series</option>'
    filename = 'GPSIonoActivity.png'
  else:
    webStr += '<option value="RMS">RMS Heatmap</option>'
    webStr += '<option value="Activity">Iono Activity Heatmap</option>'
    webStr += '<option value="TimeSeries" selected="selected">Iono Activity Time-Series</option>'
    filename = 'AllGPS.png'
    
  webStr += ' </select> '
  
  webStr += "</div>"

  webStr += "</div></div>"

  webStr += "<img id='IonoPlot' height='600' src='http://quark.eng.trimble.com/DataDump/Scintillation/" + region + "/" + filename + "'>"
  webStr += "</div>"
  webStr += "</section>"

  webStr += ("{% endblock %}")

  webStr += ("{% block userscript %}")
  webStr += ("<script src='static/js/app.js'></script>")
  #webStr += ("<script>startup();</script>")
  webStr += ("{% endblock %}")


  return render_template_string(Markup(webStr))











#
# ---------------------------------------------
#

stations = (
[
  #{"dir":"Stanford",   "user":"",      "pw":"",         "short":"STAN",  "addr":"waas-netr9.stanford.edu"},
  {"dir":"Stanford",   "user":"admin",  "pw":"rileyalloy","short":"STAN",  "addr":"waas-alloy.stanford.edu"},
  #{"dir":"Sunnyvale",  "user":"admin", "pw":"password", "short":"SUNY",  "addr":"10.1.149.130"},
  #{"dir":"Colorado",   "user":"",      "pw":"",         "short":"COLO",  "addr":"10.7.184.22"},
  #{"dir":"Illinois",   "user":"admin", "pw":"TrimNetR9","short":"ILLI",  "addr":"128.174.132.4"},
  {"dir":"Dayton",     "user":"",      "pw":"",         "short":"DAYT",  "addr":"10.1.20.81"},
  {"dir":"Santiago",   "user":"",      "pw":"",         "short":"SANT",  "addr":"164.77.199.126"},
  #{"dir":"Brazil",     "user":"admin", "pw":"rileyBD",  "short":"BRAZ",  "addr":"186.250.252.178:8091"},
  {"dir":"Nantes",     "user":"admin", "pw":"nantes",   "short":"NANT",  "addr":"10.20.4.195"},
  {"dir":"Munich",     "user":"admin", "pw":"password", "short":"MUNC",  "addr":"10.2.155.199"},
  #{"dir":"CapeTown",   "user":"Stuart", "pw":"Riley",   "short":"CAPE",  "addr":"10.2.99.252"}, 
  #{"dir":"CapeTown",   "user":"admin", "pw":"password", "short":"CAPE",  "addr":"10.2.99.252"}, 
  {"dir":"Moscow",     "user":"admin", "pw":"ashtech",  "short":"MOSC",  "addr":"10.2.92.69"},  
  {"dir":"Chennai",    "user":"admin", "pw":"rileyr9",  "short":"CHEN",  "addr":"10.40.70.42"},
  {"dir":"Singapore",  "user":"admin", "pw":"password", "short":"SING",  "addr":"10.3.36.124"}, 
  {"dir":"Singapore2", "user":"admin", "pw":"password", "short":"SING2", "addr":"10.3.36.121"}, 
  #{"dir":"Xian",       "user":"admin", "pw":"password", "short":"XIAN",  "addr":"10.3.97.146"},
  {"dir":"Beijing",    "user":"admin", "pw":"BeiDou2",  "short":"BEIJ",  "addr":"10.3.52.41"},
  {"dir":"Tokyo",      "user":"netr9", "pw":"qzss",     "short":"TOKY",  "addr":"203.181.17.89:9999"},  
  #{"dir":"Melbourne2", "user":"admin", "pw":"rileyr9",  "short":"MELB",  "addr":"110.142.162.172:8027"},
  {"dir":"Melbourne4", "user":"admin", "pw":"password", "short":"MELB4", "addr":"110.142.162.172:8030"},
  #{"dir":"Melbourne4", "user":"admin", "pw":"password", "short":"MELB4", "addr":"110.142.162.172:8032"},
  {"dir":"TNZ",        "user":"",      "pw":"",         "short":"TNZ1",  "addr":"10.3.16.250"}
] )


#def svData():
#  webStr = "<data>"
#
#  for i in range(len(xmlSVData)):
#    webStr = webStr + "<station><name>" + stations[i].get("dir") + "</name>"
#    webStr = webStr + "<data>" + xmlSVData[i] + "</data></station>"
#  webStr = webStr + "</data>"
# 
#  return Response(webStr,mimetype='text/xml')

def worker(num):
  while(ThreadsActive == True):
    resp = []
    global secondsSvRequest

    delta = (datetime.datetime.now() - secondsSvRequest).total_seconds()
    #print "%d SV-delta = %f" % (num,delta)
    if( delta < 60 ):
      try:
        resp = RXTools.SendHttpPost( stations[num].get("addr"),
                             '/xml/dynamic/svData.xml',
                             #'/xml/dynamic/sec_merge.xml?svData=&sysData=&powerData=&posData=&power=&pos=',
                             #'/xml/dynamic/merge.xml?svData=',
                             stations[num].get("user"),
                             stations[num].get("pw"))
      except:
        print("Exception(SV) Task %d:%s" % (num, stations[num].get("dir")))

      if(resp):
        xmlSVData[num] = resp
        #print 'Worker-SV[%d]: %s got response Active = %d' % (num,stations[num].get("dir"),ThreadsActive)

    time.sleep(3)
  return

def workerSys(num):
  while(ThreadsActive == True):
    resp = []
    global secondsSysRequest

    delta = (datetime.datetime.now() - secondsSysRequest).total_seconds()
    #print "%d SYS-delta = %f" % (num,delta)
    if( delta < 240 ):
      try:
        resp = RXTools.SendHttpPost( stations[num].get("addr"),
                             '/xml/dynamic/sec_merge.xml?sysData=&powerData=&posData=&power=&pos=',
                             stations[num].get("user"),
                             stations[num].get("pw"))
      except:
        try:
          resp = RXTools.SendHttpPost( stations[num].get("addr"),
                               '/xml/dynamic/merge.xml?sysData=&powerData=&posData=&power=&pos=',
                               stations[num].get("user"),
                               stations[num].get("pw"))
        except:
          print("Exception Task(SYS) %d:%s" % (num, stations[num].get("dir")))

      if(resp):
        xmlSYSData[num] = resp
        #print 'Worker-SYS[%d]: %s got response Active = %d' % (num,stations[num].get("dir"),ThreadsActive)

    time.sleep(30)
  return

@app.route("/svData.xml")
def svData():
  global secondsSvRequest
  webStr = "<data>"

  for i in range(len(xmlSVData)):
    if(len(xmlSVData[i]) > 0):
      webStr = webStr + "<station><name>" + stations[i].get("dir") + "</name>"
      #webStr = webStr + xmlSVData[i] + "</station>"
      webStr = webStr + "<data>" + xmlSVData[i] + "</data></station>"
  webStr = webStr + "</data>"
  
  # Get the time of the request
  secondsSvRequest = datetime.datetime.now()  

  return Response(webStr,mimetype='text/xml')


@app.route("/sysData.xml")
def sysData():
  global secondsSysRequest
  webStr = "<data>"

  for i in range(len(xmlSYSData)):
    if(len(xmlSYSData[i]) > 0):
      webStr = webStr + "<station><name>" + stations[i].get("dir") + "</name>"
      webStr = webStr + xmlSYSData[i] + "</station>"
  webStr = webStr + "</data>"
  
  # Get the time of the request
  secondsSysRequest = datetime.datetime.now()  

  return Response(webStr,mimetype='text/xml')

@app.route('/RTK')
def RTK():

  # Get the stats of the latest RF replay run for all receivers.
  PVTStats = getPVTStats.getStats(getPVTStats.dRTK)

  # Get the Astra data
  testName = PVTStats[3]['test']
  data1 = {'testName' : testName,
          'top'    : PVTStats[3]['y2d_95'],
          'bottom' : [0.0001]*len(testName),
          'left'   : list(0.1 + np.float_(np.array(list(range(len(testName)))))),
          'right'  : list(0.5 + np.float_(np.array(list(range(len(testName)))))),
          'links'  : testName
          }
  source1 = ColumnDataSource(data=data1)

  # Get the Titan data
  data2 = {'testName' : testName,
          'top'    : PVTStats[4]['y2d_95'],
          'bottom' : [0.0001]*len(testName),
          'left'   : list(0.5 + np.float_(np.array(list(range(len(testName)))))),
          'right'  : list(0.9 + np.float_(np.array(list(range(len(testName)))))),
          'links'  : testName
          }
  source2 = ColumnDataSource(data=data2)

  p = figure(plot_height=700, plot_width=1000, #title="RF Replay Test",
             toolbar_location=None,
             tools="tap",
             x_range=data2['testName'],y_range=(0.01,100),
             y_axis_type="log")

  # Plot Astra
  p.quad(top='top', bottom='bottom', left='left', right='right', color="#9999ff",source=source1,legend='Top CVS Astra')
  # Plot Titan
  p.quad(top='top', bottom='bottom', left='left', right='right', color="#ff9999",source=source2,legend='Top CVS Titan')

  # Create a hidden data set (based on the top value) which sets the
  # y-axis labels. 
  data3 = {'testName' : testName,
          'top'    : [0.0002]*len(testName),
          'bottom' : [0.0001]*len(testName) }

  source3 = ColumnDataSource(data=data3)
  p.quad(top='top',
         bottom='bottom',
         left='testName',
         right='testName',
         line_width=20,
         color="#e84d60",source=source3)

  # set the background color to match the Trimble style
  p.background_fill_color = "#f3f3f7"
  p.border_fill_color     = "#f3f3f7"

  p.xaxis.major_label_text_font_size = "12pt"
  p.yaxis.major_label_text_font_size = "12pt"

  p.yaxis.axis_label = '2D 95% Error [m]'
  p.legend.location = "top_left"
  p.legend.orientation = "vertical"
  # Rotate the y-axis text by 90 degrees
  p.xaxis.major_label_orientation = math.pi/2 
  url='http://fermion.eng.trimble.com:5000/rtk_results.html?CDF=95&desc=@links.xml'
  taptool = p.select(type=TapTool)
  taptool.callback = OpenURL(url=url)
  fig = p

  # grab the static resources
  js_resources  = INLINE.render_js()
  css_resources = INLINE.render_css()

  # render template
  script, div = components(fig)
  html = render_template( 'menu.html',
                          plot_script=script,
                          plot_div=div,
                          js_resources=js_resources,
                          css_resources=css_resources)
  return encode_utf8(html)


# This performs once per day processing
def processDaily():
  gotTestTrack = False
  count = 0
  data = []

  # Send any tracking epoch exceptions to chat
  sendTrackExceptionsToChat()

  # if we fail to get the testtrack data try a few times if
  # that fails (e.g. when it is down during a backup)
  # return empty json data.
  while( (gotTestTrack == False) and (count < 5) ):
     try:
       data = getTTData.getSPRTable('Emerald')
       gotTestTrack = True
     except:
       time.sleep(1)

     count = count + 1

  if(gotTestTrack == True):
    print("Pushing daily TT daily data to google sheets")
    # Push the daily TT data into a google sheet
    data2sheets.testtrack2sheets(data)
  else:
    print("Failed to get TT daily data")


def slackMonitor():
  global slackTime
  
  if(os.path.isfile("slackAlert.txt")): 
    with open("slackAlert.txt",'r') as f:
      slackTime = int(f.readline().rstrip().split()[0])

  print(slackTime)
  while(ThreadsActive == True):
    # 7AM on the reference day
    start = datetime.datetime(2018,5,27,7,0,0)
    now   = datetime.datetime.now()  
    delta = now - start
    app.logger.info("Time = %d %d" % (delta.days,delta.seconds))
    # Look for the day roll (about 7AM)
    if(delta.days != slackTime):
      print("Send to Slack %d" % delta.days)
      if(enableServerLogs == True):
        app.logger.info("Send to Slack %d" % delta.days)

      [errData, slackStr, chatStr] = testThreshold.testData()
      if(len(slackStr) > 0):
        slackStr = ':boom:Exception\n' + slackStr
        sendToSlack(slackStr)

        # Now send to chat
        if( ("CHAT_API_TOKEN" in os.environ) and ("CHAT_API_KEY" in os.environ) ):
          token = os.environ["CHAT_API_TOKEN"]
          key = os.environ["CHAT_API_KEY"]
          space = 'AAAAte3wnhw'
          now = datetime.datetime.utcnow()
          yesterday = now - datetime.timedelta(days=1)
          year  = yesterday.year
          month = yesterday.month
          day   = yesterday.day

          dayStr = str(year) + str(month).zfill(2) + str(day).zfill(2)
          chatbot.createMessage(key,token,space,chatStr,thread=dayStr)
        
      fid = open("slackAlert.txt","w")
      fid.write("%d" % delta.days)
      fid.close()

      slackTime = delta.days

      # This needs cleaning up - place here as it is a once per day operation
      processDaily()

   
    # How long until we roll into a new "slack" day and
    # want to report the status
    timeToRoll = 86400 - delta.seconds
    # Unlikely this is ever be zero, but in case make sure we don't
    # get into a tight loop
    timeToRoll += 10
    time.sleep(timeToRoll)
  return


def antTempMonitor():
  global AntTemp
  global TemperatureAlertTime

  while(ThreadsActive == True):
    url_str = "http://10.1.150.1/getData.json"
    try:
      response = urlopen(url_str)
      jsonData = response.read()
    except:
      jsonData = ''

    if(jsonData):
      parsed_json = json.loads(jsonData)
      date  = parsed_json['date']
      month = date[0:2]
      day   = date[3:5]
      year  = '20' + date[6:8]
      tempf = parsed_json['sensor'][0]['tempf']
      print("Antenna Room Temp",day,month,year," = ",tempf,"F")
      try:
        fid = open("/net/quark/mnt/data_drive/DataDump/DashBoard/AntennaRoomTemp/" + year + '-' + month + '-' + day + '.txt' ,'a')
        fid.write("%s %s\n" % (date, tempf))
        fid.close()
      except:
        print("Error opening Antenna Room Temp File")

      AntTemp['time'] = date
      AntTemp['temp'] = tempf
      
      now   = datetime.datetime.now()  
      delta = now - TemperatureAlertTime
      TimeDelta = (delta.days * 86400) + delta.seconds

      # If we detect a high temperature alert once per hour
      #if( (float(tempf) >= 80.0) and (TimeDelta >= 3600)):
      if( (float(tempf) >= 82.0) and (TimeDelta >= 3600)):
        TemperatureAlertTime = now
        message =  '`Antenna Room Temperature Alert`\n' + "```" + 'Temp = ' + tempf + 'F' "```"
        sendToSlack(message)
    
    # repeat in 2 minutes
    time.sleep(120)
  return

os.environ['http_proxy'] = 'proxy.trimble.com:3128'
if __name__ == "__main__":
  sendToSlack("DashBoard App restarted")
  signal.signal(signal.SIGINT, signal_handler)
  print("Setup CTRL-C handler")
  if(runGetSVData == True):
    threadsSVdata = []
    threadsSYSdata = []
    xmlSVData = []
    xmlSYSData = []
    # Kick off the threads
    for i in range(len(stations)):
      xmlSVData.append('')
      xmlSYSData.append('')
      t = threading.Thread(target=worker, args=(i,))
      threadsSVdata.append(t)
      t.start()
      t = threading.Thread(target=workerSys, args=(i,))
      threadsSYSdata.append(t)
      t.start()

  # Start the thread for the temperature monitor of the antenna room
  if(runTempThread == True):
    threadAntennaRoomMonitor = threading.Thread(target=antTempMonitor)
    threadAntennaRoomMonitor.start()

  if(enableServerLogs == True):
    formatter = logging.Formatter(
          "[%(asctime)s] {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s")
    handler = RotatingFileHandler('webApp.log', maxBytes=(1024*128), backupCount=64)
    handler.setLevel(logging.DEBUG)
    handler.setFormatter(formatter)
    app.logger.addHandler(handler)
    app.logger.setLevel(logging.DEBUG)
  
  if(useSlack == True):
    threadSlack = threading.Thread(target=slackMonitor)
    threadSlack.start()

    #data = getTTData.getSPRTable('Emerald')
    #data2sheets.testtrack2sheets(data)

  ###
  ### Temp code
  ###
  #print("Sending TT data")
  #processDaily()
  #sendTrackExceptionsToChat()

  try:
    app.run(host='0.0.0.0',port=webPort,threaded=True)
  except:
    if(ThreadsActive == True):
      sendToSlack(":boom: DashBoard Exception - crashed")
    sys.exit(1)



