from genericpath import isfile
import webbrowser
import os, sys
import threading
import pathlib
import logging
import shutil
from platform import uname

from pyawscp.pymxgraph.HttpServer import HttpServer
sys.path.append('..')
from pyawscp.Utils import Utils, Style
from pyawscp.Emoticons import Emoticons

LOG = logging.getLogger("app." + __name__)
LOG.setLevel(logging.INFO)

class PymxGraphAwsNavigator:

    def __init__(self, pathToResources, images, htmlSnippets, verbose=False):
        self.pathToResources = pathToResources
        self.images          = images
        self.htmlSnippets    = htmlSnippets
        self.verbose         = verbose
        self.httpServer      = None

    def startHttpServer(self):
        self.httpServer = HttpServer(self.verbose)
        self.httpServer.run()

    def stopHttpServer(self):
        self.httpServer.stop()

    def openHTML(self,html):
        directory = os.path.dirname(os.path.realpath(__file__))
        fileName  = os.path.join(str(directory),"aws-navigator.html")
        
        try:
            os.remove(fileName)
        except OSError:
            pass

        with open(fileName, "w") as file:
            file.write(html)
        
        if os.name == "nt":
           # Windows
           # Use the user's default browser
           webbrowser.open('file://' + os.path.realpath(fileName))
        else:
           # Linux
           # Am I a WSL?  
           if 'MICROSOFT' in uname().release.upper():
               # WSL - A linux running inside Windows

               # Identifying WSL Version
               wsl_path_to_win_browser = "file://wsl%24/Ubuntu-18.04/"
               wsl_version             = None
               if os.path.isfile('/etc/os-release'):
                   with open('/etc/os-release','r') as f:
                       for line in f.readlines():
                           if "VERSION_ID" in line.upper():
                               wsl_version = line.split("=")[1].replace("\"","")
               if wsl_version:
                  wsl_path_to_win_browser = f"file://wsl%24/Ubuntu-{wsl_version}"
               
               # Check browser to use (from Windows)
               chrome  = '/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe'
               firefox = '/mnt/c/Program Files/Mozilla Firefox/firefox.exe'
               edge    = '/mnt/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe'
               browser = None
               if os.path.isfile(f'{chrome}'):
                  webbrowser.register('chrome',None,webbrowser.BackgroundBrowser(chrome))    
                  browser = "chrome"
               elif os.path.isfile(f'{edge}'):
                  webbrowser.register('edge',None,webbrowser.BackgroundBrowser(edge))    
                  browser = "edge" 
               elif os.path.isfile(f'{firefox}'):
                  webbrowser.register('firefox',None,webbrowser.BackgroundBrowser(firefox))    
                  browser = "firefox"
               if not browser:
                  print("")
                  print(Style.GREEN + "==================================================")
                  print(Emoticons.error() + Style.BYELLOW + " Sorry! " + Style.BLUE)
                  print("  No browser found on this system to open it!" + Style.GREEN)
                  print("==================================================")
                  print(Style.RESET)
                  return
               webbrowser.get(browser).open(f"{wsl_path_to_win_browser}{fileName}")
           else:
               # Native Linux
               webbrowser.open('file://' + os.path.realpath(fileName))

    def drawAwsNavigator(self):
        html = """
        <html>
          <head>
             """ + self.htmlSnippets.htmlTitle('Hi There!') + """
             """ + self.htmlSnippets.jsLibraries()          + """
             <style type="text/css">
                .modalInfo {
                    position: absolute;
                    display: block;
                    top: 200px;
                    left: 200px;
                    background: rgb(245, 245, 245);
                    //width: 350px;
                    //height: 250px;
                    padding: 5px;
                    font-size: 0.875rem;
                    box-shadow: 0 0 2.5rem rgba(0, 0, 0, 0.5);
                    border-radius: 0.3125rem;
                    font-family: 'Source Sans Pro', sans-serif
                }
                .content {
                    font-size: 0.8rem;
                    font-family: 'Consolas', sans-serif
                }
                .tag {
                    border: 1.4px solid darkcyan;
                    padding: 3px;
                    border-radius: 8px;
                    background-color:rgb(0,220,220);
                    color:black;
                    display:inline;
                    font-size: 12px;
                }
                .tableInfo {
                    font-family: Consolas;
                    font-size: 13px;
                    border: 2px solid darkcyan;
                    padding: 0;
                    border-radius: 8px;
                }
                .cellHeader {
                    border-bottom: 1.5px darkcyan solid;
                    font-size: 15px;
                    font-weight: bold;
                    background-color: darkcyan;
                }
                .cellInfo {
                    padding: 10px 8px 10px 8px;
                }
                .cellTags {
                    border-top: 1.5px darkcyan solid;
                    padding: 8px 8px 15px 8px;
                }
                .buttonCopyClipboard {
                    margin-bottom: -3px;
                }
             </style>
             <script type="text/javascript">

                 """ + self.htmlSnippets.jsDataURIForImage()    + """
                 
                 var requestId   = 0;

                 const Http             = new XMLHttpRequest();
                 const url              = '"""+ self.htmlSnippets.serverURL +"""';
                 var   previousGraphXml = [];
                 var   previousCoord    = {"x":0, "y":0};

                 function moveMe(graph, cellId, valueX, valueY, addToIt) {
                    if ( graph.view.getScale() == 1 ) { 
                        try
                        {
                            var v1 = graph.getModel().getCell(cellId); 
                            graph.clearSelection();
                            graph.getModel().beginUpdate(); 
                            var geo = graph.getCellGeometry(v1);
                            geo  = geo.clone();

                            previousCoord.x = geo.x;
                            previousCoord.y = geo.y;
                            
                            if ( addToIt ) {
                                geo.x += valueX;
                                geo.y += valueY;
                            } else {
                                geo.x = valueX;
                                geo.y = valueY;
                            }
                            graph.getModel().setGeometry(v1, geo);
                        }
                        finally
                        {
                            var morph = new mxMorphing(graph, 0, 0, 0);
                            morph.addListener(mxEvent.DONE, function()
                            {
                                graph.getModel().endUpdate();
                            });
                            morph.startAnimation();
                            return previousCoord;
                        }
                    }
                 }

                 function historyGraphGoBack(graph) {
                    // Clean any ModalInfo Timer started before go back 
                    if ( timerShowModalSet != undefined ) {
                            clearTimeout(timerShowModalSet)
                    }
                    level           -= 1
                    var parentCellId = '';
                    try {
                        graph.getModel().beginUpdate();
                        graph.removeCells(graph.getChildVertices(graph.getDefaultParent()));
                        var xml = previousGraphXml.pop();
                        var doc = mxUtils.parseXml(xml);
                        var codec = new mxCodec(doc);
                        var model = codec.decode(doc.documentElement);

                        // Find out the parent of the previous level to highlight it with some animation. 
                        // Example: if we go back to List_VPC level, we need to find the first VPC Cell and use its ParecenCellId
                        for (var key in model.cells)
                        {
                            var tmp = model.getCell(key);
                            if ( tmp.data != undefined && tmp.data.parentCellId != undefined ) {
                                if ( LEVELS[level] == LIST_VPC && tmp.data.awsResourceType == "vpc" ) {
                                    parentCellId = tmp.data.parentCellId;
                                    break;
                                } else
                                if ( LEVELS[level] == LIST_SUBNET && tmp.data.awsResourceType == "subnet" ) {
                                    parentCellId = tmp.data.parentCellId;
                                    break;
                                }
                            }
                        }
                        graph.getModel().mergeChildren(model.getRoot().getChildAt(0), graph.getDefaultParent());
                    } finally {
                        graph.getModel().endUpdate();
                    }
                    // Animation
                    var xs = [10,-10,-5,5,20,25];
                    var ys = [10,-10,-5,5,20,25];
                    var x  = xs[Math.floor(Math.random() * (5 - 0) + 0)];
                    var y  = ys[Math.floor(Math.random() * (5 - 0) + 0)];
                    var previousCoord;
                    setTimeout(function(){
                        previousCoord = moveMe(graph,parentCellId,x,y,true);
                    },70);
                    setTimeout(function(){
                        moveMe(graph,parentCellId,previousCoord.x,previousCoord.y, false);
                    },450);
                 }

                 function showModalInfo(currentState) {
                    // Check if the Mouse is over the same Object/Icon for a specific amount of time (set it on the calling of this throuhg setTimeOut, down below) 
                    // If the mouse has moved away after some time, the ModalInfo should not be shown, otherwise should be shown
                    var sameRegionYet = (globalCurrentState == currentState) && (globalCurrentState != undefined) && (globalCurrentState.cell != undefined);
                    if (!sameRegionYet)
                        return;

                    var divContent = document.getElementById(divContentId);
                    divContent.innerHTML  = currentState.cell.modalInfo.html;

                    if ( !showModalRightBtnFixed )
                         document.querySelector("#imgBtnClose").style.display = "none";

                    x = currentState.cell.getGeometry().x + (currentState.cell.getGeometry().width);
                    y = currentState.cell.getGeometry().y - 50;
                    var div = document.getElementById(divModalInfoId);
                    
                    var win = window,
                        doc = document,
                        docElem = doc.documentElement,
                        body    = doc.getElementsByTagName('body')[0],
                        width   = win.innerWidth || docElem.clientWidth || body.clientWidth,
                        height  = win.innerHeight|| docElem.clientHeight|| body.clientHeight;

                    div.style.display = "block";    
                        
                    // Adjust the Y position of the Div accordingly with the screen height    		 
                    divModalInfoHeight = div.offsetHeight;
                    if ( (y + divModalInfoHeight) > height ) {
                        y = y - ((y + divModalInfoHeight) - height) - (currentState.cell.getGeometry().height * 1.8); 
                    }
                    if ( y < 0 ) {
                        y = currentState.cell.getGeometry().y + 30;
                    }

                    // Adjust the X position of the Div accordingly with the screen width and the cell width
                    divModalInfoWidth = div.offsetWidth;
                    if ( (x + divModalInfoWidth) > width ) {
                        x = x - (divModalInfoWidth + currentState.cell.getGeometry().width + 5); 
                    }
                    
                    div.style.display = "block";
                    div.style.left    = x;
                    div.style.top     = y;
                 }

                 var delayTimeShowModalInfo = 500; // one second
                 var timerShowModalSet      = undefined;
                 var divModalInfoWidth      = 350;
                 var divModalInfoHeight     = 250;
                 var globalCurrentState     = null;
                 var showModalRightBtnFixed = false;
                 var divModalInfo           = document.createElement("div");   
                 var divModalInfoId         = "divModalInfo"
                 divModalInfo.style.display = "none";
                 divModalInfo.className     = "modalInfo";
                 //divModalInfo.innerHTML     = "Header";
                 divModalInfo.id            = divModalInfoId;
                 var divContentId           = "divContentId";
                 var divContent             = document.createElement("div");
                 divContent.innerHTML       = "Here, you can put any additional information";
                 divContent.id              = divContentId;
                 divModalInfo.appendChild(divContent);    

                 function main(container)
                 {
                     document.body.appendChild(divModalInfo);   

                     """ + self.htmlSnippets.jsBrowserNotSupported() + """
                     else
                     {
                         // Speedup the animation
                         mxText.prototype.enableBoundingBox = false
                         mxEvent.disableContextMenu(document.body);

                         // Creates the graph inside the given container
                         var graph = new mxGraph(container);
                         
                         // Disables all built-in interactions
                         graph.setEnabled(false);
                         graph.setPanning(true);
                         graph.panningHandler.useLeftButtonForPanning = true;
                         graph.setHtmlLabels(true);
         
                         // Handles clicks on cells
                         graph.addListener(mxEvent.CLICK, function(sender, evt)
                         {
                             var rightButton;
                             if ( evt.properties != undefined && evt.properties.event != undefined && evt.properties.event.button != undefined ) {
                                 rightButton = evt.properties.event.button == 2 ? true : false;
                             }
                             if ( rightButton != undefined && rightButton == false ) {
                                  var cell = evt.getProperty('cell');
                                  if (cell != null)
                                  {
                                      // If the Cell clicked is a VPC Resource and we are at the level of LIST_SUBNET, so...
                                      // we are clicking at the center of the graph, that is, the root parent (VPC) of all Subnets 
                                      // Then, we must go back to the previous Level: LIST_VPC(1) ---> AWS (0)
                                      if ( cell.data != undefined && cell.data.awsResourceType == "vpc" && LEVELS[level] == LIST_SUBNET ) {
                                          historyGraphGoBack(graph);
                                      } else
                                      if ( cell.data != undefined && cell.data.awsResourceType == "subnet" && LEVELS[level] == LIST_EC2 ) {
                                          historyGraphGoBack(graph);
                                      } else {
                                          // Before going foward, clean any ModalInfo TimeOut Set
                                          if ( timerShowModalSet != undefined ) {
                                              clearTimeout(timerShowModalSet)
                                          }
                                          // Save snapshot of the previous graph to go back when requested
                                          var enc          = new mxCodec();
                                          var node         = enc.encode(graph.getModel());
                                          var tmpXml       = mxUtils.getXml(node);
                                          if ( callAWSGraphNavigation(graph, cell) ) {
                                              // Only save when any call was implemented
                                              previousGraphXml.push(tmpXml);    
                                          }
                                      }
                                  }
                             } else {
                                 // Right Mouse Button Pressed
                                 showModalRightBtnFixed = true;
                                 showModalInfo(globalCurrentState);
                             }
                         });

                         // Handles mouseOver, mouseOut - ModalInfo
                         graph.addMouseListener({
                            currentState: null,
                            mouseMove: function(sender, evt) {
                                 if ( !showModalRightBtnFixed ) {
                                    if (this.currentState != null && (evt.getState() == this.currentState || evt.getState() == null) ) {
                                        globalCurrentState = this.currentState;
                                        if ( timerShowModalSet != undefined ) {
                                            clearTimeout(timerShowModalSet)
                                        }
                                        timerShowModalSet = setTimeout(showModalInfo,delayTimeShowModalInfo,this.currentState);
                                        if ( evt.getState() ) {
                                            evt.getState().setCursor('pointer');
                                        }     
                                    }

                                    var tmp = graph.view.getState(evt.getCell()); 
                                    
                                    if ( tmp != this.currentState ) {
                                        var div = document.getElementById(divModalInfoId);
                                        div.style.display = "none";
                                        if ( timerShowModalSet != undefined ) {
                                            clearTimeout(timerShowModalSet)
                                        }
                                        if ( tmp != null && tmp.cell.isVertex() ) {
                                            this.currentState  = tmp;
                                            globalCurrentState = tmp;
                                        }
                                    }
                                 }
                            },
                            mouseDown: function(sender, evt) { },
                            mouseUp:   function(sender, evt) { },
                            dragEnter: function(evt, state) { },
                            dragLeave: function(evt, state) { }
                         });

                         // Mouse Wheel Zooming
                         mxEvent.addMouseWheelListener((evt, up) => {
                            if (mxEvent.isConsumed(evt)) {
                                return;
                            }
                            let gridEnabled = graph.gridEnabled;
                            // disable snapping
                            graph.gridEnabled = false;
                            let p1 = graph.getPointForEvent(evt, false);
                            if (up) {
                                graph.zoomIn();
                            } else {
                                graph.zoomOut();
                            }
                            let p2 = graph.getPointForEvent(evt, false);
                            let deltaX = p2.x - p1.x;
                            let deltaY = p2.y - p1.y;
                            let view = graph.view;
                            view.setTranslate(view.translate.x + deltaX, view.translate.y + deltaY);
                            graph.gridEnabled = gridEnabled;
                            mxEvent.consume(evt);
                         }, container);
                          
                         // Changes the default vertex style in-place
                         var style = graph.getStylesheet().getDefaultVertexStyle();
                         // Rectangle (rounded)
                         style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_RECTANGLE;
                         style[mxConstants.STYLE_ROUNDED] = true;
                         style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter;
                         style[mxConstants.STYLE_GRADIENTCOLOR] = 'white';
                                         
                         // Gets the default parent for inserting new cells. This
                         // is normally the first child of the root (ie. layer 0).
                         var parent = graph.getDefaultParent();
         
                         var cx = graph.container.clientWidth / 2;
                         var cy = graph.container.clientHeight / 2;
                         
                         // Root of All
                         var awsImg = dataURIFor("aws-4.png").replace(";base64,",",");
                         var prefix = 'shape=image;image=' + awsImg + ";";

                         var cell       = graph.insertVertex(parent, 'AWS', 'AWS', cx - 20, cy - 15, 60, 40,prefix+'verticalLabelPosition=bottom;verticalAlign=top');
                         cell.data      = new CustomData('aws', 'root');
                         cell.modalInfo = new ModalInfoHTML("root",{"aws":"aws"});
                         
                         // Animates the changes in the graph model
                         graph.getModel().addListener(mxEvent.CHANGE, function(sender, evt)
                         {
                             var changes = evt.getProperty('edit').changes;
                             mxEffects.animateChanges(graph, changes);
                         });

                         """ +  self.htmlSnippets.jsMxToolBox("90","15") + """

                         var parameters = undefined;
                         postRequest(LIST_VPC,parameters,function(response) {
                             drillDown(graph, cell, response);
                         });
                     }
                 };

                 function closeModalInfo() {
                    var div = document.getElementById(divModalInfoId);
                    div.style.display      = "none";
                    showModalRightBtnFixed = false;
                 }

                 var LIST_VPC            = "listVpc";
                 var LIST_SUBNET         = "listSubnet";
                 var LIST_EC2            = "listEc2";
                 var LIST_SECURITY_GROUP = "listSecurityGroup";
                 var level               = 0;
                 var LEVELS              = ["ROOT", LIST_VPC, LIST_SUBNET, LIST_EC2];

                 function showLoading(show) {
                     if (show) {
                         document.querySelector("#loadingBackground").style.display = "";
                         document.querySelector("#loadingMessage").style.display = "";
                     } else {
                         document.querySelector("#loadingBackground").style.display = "none";
                         document.querySelector("#loadingMessage").style.display    = "none";
                     }
                 }

                 function callAWS(request, callback, event) {
                    showLoading(true);
                    postRequest(request.call,request.parameters,function(response, event) {
                        showLoading(false);
                        callback(response, event);
                    }, event);
                 }
                 function callListSecurityGroups(groupId, callback, event) {
                    var parameters  = [];
                    parameters.push({"name":"groupId","value":groupId});
                    var request = {
                        "call":LIST_SECURITY_GROUP,
                        "parameters": parameters
                    };
                    callAWS(request, callback, event);
                 }

                 function callAWSGraphNavigation(graph, cell) {
                     var callImplemented = false;
                     var awsResourceType = cell.data.awsResourceType;
                     if ( awsResourceType == "subnet" ) {
                         // Drill Down Subnets --> EC2s
                         showLoading(true);
                         callImplemented = true;
                         var parameters  = [];
                         parameters.push({"name":"vpcId","value":cell.data.awsResourceId},
                                         {"name":"subnetId","value":cell.data.awsResourceId});
                         postRequest(LIST_EC2,parameters,function(response) {
                             drillDown(graph, cell, response);
                             level = 3;
                         });
                     } else
                     if ( awsResourceType == "vpc" ) {
                         // Drill Down VPC --> Subnets
                         showLoading(true);
                         callImplemented = true;
                         var parameters  = [];
                         parameters.push({"name":"vpcId","value":cell.data.awsResourceId});
                         postRequest(LIST_SUBNET,parameters,function(response) {
                             drillDown(graph, cell, response);
                             level = 2;
                         });
                     } else
                     if ( awsResourceType == "aws" ) {
                         // Drill Down AWS --> VPCs
                         showLoading(true);
                         callImplemented = true;
                         var parameters  = undefined;
                         postRequest(LIST_VPC,parameters,function(response) {
                             drillDown(graph, cell, response);
                             level = 1;
                         });
                     }
                     return callImplemented;
                 }

                 function postRequest(action, parameters, callback, event) {
                     Http.open("POST", url, true);
                     Http.setRequestHeader('Content-type','application/json');
                     request = createRequest(action,parameters);
                     Http.onreadystatechange = (e) => {
                        if ( Http.readyState == 4 && Http.status == 200 ) {
                            callback(JSON.parse(Http.responseText), event)
                        } else {
                            //console.log("onreadystatechange = " + Http.status);
                        }
                     }
                     Http.onerror = (e) => {
                         showLoading(false);
                         alert("No connection available for " + url);
                     }
                     //Http.send(JSON.stringify(request));
                     Http.send(request);
                 }

                 function createRequest(action, params) {
                    var request        = {};
                    var parameters     = [];
                    request.action     = action;
                    request.parameters = parameters;

                    if ( params && params != undefined && params.length > 0 ) {
                        for (index = 0; index < params.length; ++index) {
                            request.parameters.push({
                                "name": params[index]["name"],
                                "value": params[index]["value"]
                            });
                        }
                    }
                    return JSON.stringify(request);
                }

                 // Loads the links for the given cell into the given graph
                 // by requesting the respective data in the server-side
                 // (implemented for this demo using the server-function)
                 function drillDown(graph, cell, json)
                 {
                     if (graph.getModel().isVertex(cell))
                     {
                         centerCell = cell;
                         var cx = graph.container.clientWidth / 2;
                         var cy = graph.container.clientHeight / 2;
                         
                         // Gets the default parent for inserting new cells. This
                         // is normally the first child of the root (ie. layer 0).
                         var parent = graph.getDefaultParent();
         
                         // Adds cells to the model in a single step
                         graph.getModel().beginUpdate();
                         try
                         {
                             var xml = jsonToGraphModel(cell.id, json);
                             var doc = mxUtils.parseXml(xml);
                             var dec = new mxCodec(doc);
                             var model = dec.decode(doc.documentElement);
         
                             // Removes all cells which are not in the response
                             for (var key in graph.getModel().cells)
                             {
                                 var tmp = graph.getModel().getCell(key);
                                 
                                 if (tmp != cell &&
                                     graph.getModel().isVertex(tmp))
                                 {
                                     graph.removeCells([tmp]);
                                 }
                             }
         
                             // Merges the response model with the client model
                             graph.getModel().mergeChildren(model.getRoot().getChildAt(0), parent);
         
                             // Moves the given cell to the center
                             var geo = graph.getModel().getGeometry(cell);
         
                             if (geo != null)
                             {
                                 geo = geo.clone();
                                 geo.x = cx - geo.width / 2;
                                 geo.y = cy - geo.height / 2;
         
                                 graph.getModel().setGeometry(cell, geo);
                             }
         
                             // Creates a list of the new vertices, if there is more
                             // than the center vertex which might have existed
                             // previously, then this needs to be changed to analyze
                             // the target model before calling mergeChildren above
                             var vertices = [];
                             
                             for (var key in graph.getModel().cells)
                             {
                                 var tmp = graph.getModel().getCell(key);
                                 
                                 if (tmp != cell && model.isVertex(tmp))
                                 {
                                     vertices.push(tmp);
         
                                     // Changes the initial location "in-place"
                                     // to get a nice animation effect from the
                                     // center to the radius of the circle
                                     var geo = model.getGeometry(tmp);
         
                                     if (geo != null)
                                     {
                                         geo.x = cx - geo.width / 2;
                                         geo.y = cy - geo.height / 2;
                                     }
                                 }
                             }
                             
                             // Arranges the response in a circle
                             var cellCount = vertices.length;
                             var phi = 2 * Math.PI / cellCount;
                             var r = Math.min(graph.container.clientWidth / 2.3,
                                              graph.container.clientHeight / 2.3);
                             
                             for (var i = 0; i < cellCount; i++)
                             {
                                 var geo = graph.getModel().getGeometry(vertices[i]);
                                 
                                 if (geo != null)
                                 {
                                     geo = geo.clone();
                                     geo.x += r * Math.sin(i * phi);
                                     geo.y += r * Math.cos(i * phi);
         
                                     graph.getModel().setGeometry(vertices[i], geo);
                                 }
                             }
                         }
                         finally
                         {
                             // Updates the display
                             graph.getModel().endUpdate();
                         }
                     }
                     showLoading(false);
                 };

                 
                 var colors      = ["rgb(0,0,255)","rgb(183,151,43)","rgb(110,105,89)","rgb(91,154,176)","rgb(255,0,9)","rgb(204,204,0)","rgb(128,128,128)","rgb(246,0,0)","rgb(246,178,103)","rgb(8,180,151)"];
                 var labelColors = {};
                 function colorForLabel(label) {
                    var color; 
                    if (labelColors[label] && labelColors[label] != undefined && labelColors[label] != "") {
                        color = labelColors[label];
                    }
                    if ( !color || color == undefined || color == "" ) {
                        var rnd = Math.floor(Math.random() * colors.length-1);
                        if ( rnd < 0 ) {
                            rnd = 3;
                        }
                        color = colors[rnd];
                        labelColors[label] = color;
                    }
                    return color;
                 }
                 function componentToHex(c) {
                    var hex = c.toString(16);
                    return hex.length == 1 ? "0" + hex : hex;
                 }
                 function rgbToHex(r, g, b) {
                    return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
                 }
                 function rgbToHexStr(str) {
                    var cleanRGB = (str.replace("rgb(","").replace(")","")).split(","); 
                    return rgbToHex(parseInt(cleanRGB[0]), parseInt(cleanRGB[1]), parseInt(cleanRGB[2]));
                 }

                 function jsonToGraphModel(cellId, json)
                 {
                     // Creates a local graph with no display
                     var graph = new mxGraph();
                     
                     // Gets the default parent for inserting new cells. This
                     // is normally the first child of the root (ie. layer 0).
                     var parent = graph.getDefaultParent();
         
                     // Adds cells to the model in a single step
                     graph.getModel().beginUpdate();
                     try
                     {
                        if ( json != undefined ) {
                            action = json["aws"]["action"]

                            width  = 130;
                            height = 50;
                            
                            if (action == LIST_VPC) {
                                var v0  = graph.insertVertex(parent, cellId, 'AWS-Dummy', 0, 0, width, height);
                                v0.data    = new CustomData('root', undefined, json, '');
                                var prefix = 'shape=image;image=' + dataURIFor(AWS_VPC_IMG) + ";";
                                for( var key in json["aws"]["vpc"] ) {
                                    var vpc     = json["aws"]["vpc"][key];
                                    var id      = vpc.vpcId;
                                    var label   = "<b>" + vpc.vpcId + "</b><br>" + vpc.cidrBlock; 
                                    var v       = graph.insertVertex(parent, id, label, 0, 0, width, height, prefix+'verticalLabelPosition=bottom;verticalAlign=top;whiteSpace=wrap;');
                                    v.data      = new CustomData('vpc', vpc.vpcId, json, cellId);
                                    v.modalInfo = new ModalInfoHTML('vpc',vpc);
                                    var e       = graph.insertEdge(parent, null, '', v0, v);
                                }
                            } else
                            if (action == LIST_SUBNET) {
                                 var v0      = graph.insertVertex(parent, cellId, 'Vpc', 0, 0, width, height);
                                 v0.data     = new CustomData('vpc', undefined, json);
                                 var prefix1 = 'shape=image;image=' + dataURIFor(AWS_SUBNET_PRIVATE_IMG) + ";";
                                 var prefix2 = 'shape=image;image=' + dataURIFor(AWS_SUBNET_PUBLIC_IMG) + ";";
                                 var prefix  = prefix1;
                                 for( var key in json["aws"]["vpc"]["subnets"] ) {
                                    var subnet = json["aws"]["vpc"]["subnets"][key];
                                    var id           = subnet.subnetId;
                                    var defaultForAz = subnet.defaultForAz ? "<br>DEFAULT" : "";
                                    if (subnet.scope == "public") {
                                        prefix = prefix2
                                    } else {
                                        prefix = prefix1
                                    }
                                    var colorAz = colorForLabel(subnet.availabilitZone);
                                    var label   = "<b>" + subnet.subnetId + "</b><br>" + subnet.cidrBlock + "<br><span style='color:" + colorAz + ";'>" + subnet.availabilitZone + "</span>" + defaultForAz;
                                    var v       = graph.insertVertex(parent, id, label, 0, 0, width, height, prefix+'verticalLabelPosition=bottom;verticalAlign=top;whiteSpace=wrap;');
                                    v.data      = new CustomData('subnet', subnet.subnetId, json, cellId);
                                    v.modalInfo = new ModalInfoHTML('subnet',subnet);
                                    var e       = graph.insertEdge(parent, null,subnet.availabilitZone, v0, v,'strokeColor=' + rgbToHexStr(colorAz) + ';');
                                }
                            } else
                            if (action == LIST_EC2) {
                                 var v0     = graph.insertVertex(parent, cellId, 'Subnet', 0, 0, width, height);
                                 v0.data    = new CustomData('ec2', undefined, json);
                                 for( var key in json["aws"]["ec2"]["instances"] ) {
                                    var ec2instance = json["aws"]["ec2"]["instances"][key];

                                    var prefix = 'shape=image;image=' + dataURIFor(AWS_EC2_IMG) + ";";
                                    if ( ec2instance.instanceType.startsWith("t2") ) {
                                        prefix = 'shape=image;image=' + dataURIFor(AWS_EC2_T2_IMG) + ";";
                                    } else 
                                    if ( ec2instance.instanceType.startsWith("t3a") ) {
                                        prefix = 'shape=image;image=' + dataURIFor(AWS_EC2_T3A_IMG) + ";";
                                    } else
                                    if ( ec2instance.instanceType.startsWith("t3") ) {
                                        prefix = 'shape=image;image=' + dataURIFor(AWS_EC2_T3_IMG) + ";";
                                    } else
                                    if ( ec2instance.instanceType.startsWith("a1") ) {
                                        prefix = 'shape=image;image=' + dataURIFor(AWS_EC2_A1_IMG) + ";";
                                    } else
                                    if ( ec2instance.instanceType.startsWith("c4") ) {
                                        prefix = 'shape=image;image=' + dataURIFor(AWS_EC2_C4_IMG) + ";";
                                    } else
                                    if ( ec2instance.instanceType.startsWith("c5") ) {
                                        prefix = 'shape=image;image=' + dataURIFor(AWS_EC2_C5_IMG) + ";";
                                    } else
                                    if ( ec2instance.instanceType.startsWith("m4") ) {
                                        prefix = 'shape=image;image=' + dataURIFor(AWS_EC2_M4_IMG) + ";";
                                    } else
                                    if ( ec2instance.instanceType.startsWith("m5a") ) {
                                        prefix = 'shape=image;image=' + dataURIFor(AWS_EC2_M5A_IMG) + ";";
                                    } else
                                    if ( ec2instance.instanceType.startsWith("m5") ) {
                                        prefix = 'shape=image;image=' + dataURIFor(AWS_EC2_M5_IMG) + ";";
                                    } else
                                    if ( ec2instance.instanceType.startsWith("p2") ) {
                                        prefix = 'shape=image;image=' + dataURIFor(AWS_EC2_P2_IMG) + ";";
                                    } else
                                    if ( ec2instance.instanceType.startsWith("p3") ) {
                                        prefix = 'shape=image;image=' + dataURIFor(AWS_EC2_P3_IMG) + ";";
                                    } else
                                    if ( ec2instance.instanceType.startsWith("r4") ) {
                                        prefix = 'shape=image;image=' + dataURIFor(AWS_EC2_R4_IMG) + ";";
                                    } else
                                    if ( ec2instance.instanceType.startsWith("r5a") ) {
                                        prefix = 'shape=image;image=' + dataURIFor(AWS_EC2_R5A_IMG) + ";";
                                    } else
                                    if ( ec2instance.instanceType.startsWith("r5") ) {
                                        prefix = 'shape=image;image=' + dataURIFor(AWS_EC2_R5_IMG) + ";";
                                    }
                                    
                                    var id           = ec2instance.instanceId;
                                    var colorInsType = colorForLabel(ec2instance.instanceType);
                                    var colorState   = colorForLabel(ec2instance.state.name.toUpperCase());
                                    var label        = "<b>" + ec2instance.instanceId + "</b><br>" + ec2instance.privateIpAddress + "<br><span style='color:" + colorInsType + ";'>" + ec2instance.instanceType + "</span>";
                                    var v            = graph.insertVertex(parent, id, label, 0, 0, width, height, prefix+'verticalLabelPosition=bottom;verticalAlign=top;whiteSpace=wrap;');
                                    v.data           = new CustomData('ec2', ec2instance.instanceId, json, v0.cellId);
                                    v.modalInfo      = new ModalInfoHTML('ec2',ec2instance);
                                    var e            = graph.insertEdge(parent,null,ec2instance.state.name.toUpperCase(), v0, v, 'strokeColor=' + rgbToHexStr(colorState) + ';');
                                }
                            }
                        } else {
                            console.log("json is not defined");
                        }
                     }
                     finally
                     {
                         // Updates the display
                         graph.getModel().endUpdate();
                     }
         
                     var enc = new mxCodec();
                     var node = enc.encode(graph.getModel());
                     
                     return mxUtils.getXml(node);
                 };

                 function CustomData(awsResourceType, awsResourceId, sourceData, parentCellId) {
                    this.awsResourceType = awsResourceType;
                    this.awsResourceId   = awsResourceId;
                    this.sourceData      = sourceData;
                    this.parentCellId    = parentCellId;
                 }

                 function ModalInfoHTML(resourceId, data) {
                    if ( resourceId == 'root' ) {
                       this.html = buildHtmlAwsModalInfo(data); 
                    } else
                    if ( resourceId == 'vpc' ) {
                       this.html = buildHtmlVpcModalInfo(data);
                    } else
                    if ( resourceId == 'subnet' ) {
                       this.html = buildHtmlSubnetModalInfo(data);   
                    } else
                    if ( resourceId == 'ec2' ) {
                       this.html = buildHtmlEc2ModalInfo(data);
                    } else {
                       this.html = "<h5>" + JSON.stringify(data) + "</h5>"; 
                    }
                 }


                 var tagBehavior = 'onmouseover="javascript:this.style.backgroundColor=' + "'" + 'rgb(0,175,175)' + "'" + ';this.style.cursor=' + "'" + 'pointer' + "'" + ';"onmouseout="javascript:this.style.backgroundColor=' + "'" + 'rgb(0,220,220)' + "';" + '"';
                 function buildHtmlVpcModalInfo(data) {
                     var html =
                         '<div id="imgBtnClose" style="position:absolute;top:7%;left:93%"><img  onMouseOver="this.style.cursor=' + "'" + 'pointer' + "'" + '" src=""" + self.images.close +""" onClick="javascript:closeModalInfo();"></div>'
                       + '<table class="tableInfo" width="100%" height="100%" border="0" cellpadding="0" cellspacing="0">'
                       + ' <tr><td align="center" height="35px" colspan="2" class="cellHeader">VPC</td></tr>'  
                       + ' <tr><td class="cellInfo">'
                       + '     <input type="image" class="buttonCopyClipboard" onclick="javascript:copyToClipboard(' + "'" + data.vpcId + "'" + ');" src=""" + self.images.copyClipboardPath +""" />'
                       + '     Vpc Id.......:&nbsp;<font style="color:blue;">' + data.vpcId + '</font><br>'
                       + '     <input type="image" class="buttonCopyClipboard" onclick="javascript:copyToClipboard(' + "'" + data.cidrBlock + "'"+ ');" src=""" + self.images.copyClipboardPath +""" />'
                       + '     CIDR Block...:&nbsp;<font style="color:blue;">' + data.cidrBlock + '</font>'
                       + ' <tr>'
                       + ' <tr><td colspan="2" class="cellTags">'
                       + '     Tags:<br><br>'
                       var tagsPerLine = 0;
                       for(var index = 0; index < data.tags.length; index++) {
                           var tag = data.tags[index];
                           html = html + '<div class="tag"' 
                                       + 'onclick="javascript:copyToClipboard(' + "'" + tag.key + "=" + tag.value + "'" + ');" '
                                       + tagBehavior + '>' + tag.key + '=' + tag.value + '</div>';
                           tagsPerLine += 1 
                           if (tagsPerLine >= 2) {
                               tagsPerLine = 0;
                               html = html + '<br><br>'
                           }
                       }
                       + ' </td>'
                       + '</table>'
                    return html;  
                 }

                 function buildHtmlSubnetModalInfo(data) {
                     var labelScope = data.scope == "public" ? "PUBLIC" : "PRIVATE";
                     var html =  
                         '<div id="imgBtnClose" style="position:absolute;top:5%;left:93%"><img  onMouseOver="this.style.cursor=' + "'" + 'pointer' + "'" + '" src=""" + self.images.close +""" onClick="javascript:closeModalInfo();"></div>'
                       + '<table class="tableInfo" width="100%" height="100%" border="0" cellpadding="0" cellspacing="0">'
                       + ' <tr><td align="center" height="35px" colspan="2" class="cellHeader">' + labelScope + ' SUBNET</td></tr>'  
                       + ' <tr><td class="cellInfo">'
                       + '     <input type="image" class="buttonCopyClipboard" onclick="javascript:copyToClipboard(' + "'" + data.subnetId + "'" + ');" src=""" + self.images.copyClipboardPath +""" />'
                       + '     Subnet Id.......:&nbsp;<font style="color:blue;">' + data.subnetId + '</font><br>'
                       + '     <input type="image" class="buttonCopyClipboard" onclick="javascript:copyToClipboard(' + "'" + data.availabilitZone + "'" + ');" src=""" + self.images.copyClipboardPath +""" />'
                       + '     AZ..............:&nbsp;<font style="color:blue;">' + data.availabilitZone + ' (' + data.availabilitZoneId  + ')' + '</font><br>'
                       + '     <input type="image" class="buttonCopyClipboard" onclick="javascript:copyToClipboard(' + "'" + data.cidrBlock + "'"+ ');" src=""" + self.images.copyClipboardPath +""" />'
                       + '     CIDR Block......:&nbsp;<font style="color:blue;">' + data.cidrBlock + '</font><br>'
                       + '     <input type="image" class="buttonCopyClipboard" onclick="javascript:copyToClipboard(' + "'" + data.availableIpAddressCount + "'"+ ');" src=""" + self.images.copyClipboardPath +""" />'
                       + '     Available IpV4..:&nbsp;<font style="color:blue;">' + data.availableIpAddressCount + '</font><br>'
                       + '     <input type="image" class="buttonCopyClipboard" onclick="javascript:copyToClipboard(' + "'" + data.vpcId + "'"+ ');" src=""" + self.images.copyClipboardPath +""" />'
                       + '     VPC Id..........:&nbsp;<font style="color:blue;">' + data.vpcId + '</font><br>'
                       + '     <input type="image" class="buttonCopyClipboard" onclick="javascript:copyToClipboard(' + "'" + data.defaultForAz + "'"+ ');" src=""" + self.images.copyClipboardPath +""" />'
                       + '     Default for AZ..:&nbsp;<font style="color:blue;">' + data.defaultForAz + '</font><br>'
                       + '     <input type="image" class="buttonCopyClipboard" onclick="javascript:copyToClipboard(' + "'" + data.subnetArn + "'"+ ');" src=""" + self.images.copyClipboardPath +""" />'
                       + '     Subnet ARN......:&nbsp;<font style="color:blue;font-size:9px;">' + data.subnetArn + '</font>'
                       + ' <tr>'
                       + ' <tr><td colspan="2" class="cellTags">'
                       + '     Tags:<br><br>'
                       var tagsPerLine = 0;
                       for(var index = 0; index < data.tags.length; index++) {
                           var tag = data.tags[index];
                           html = html + '<div class="tag"' 
                                       + 'onclick="javascript:copyToClipboard(' + "'" + tag.key + "=" + tag.value + "'" + ');" '
                                       + tagBehavior + '>' + tag.key + '=' + tag.value + '</div>';
                           tagsPerLine += 1 
                           if (tagsPerLine >= 2) {
                               tagsPerLine = 0;
                               html = html + '<br><br>'
                           }
                       }
                       + ' </td>'
                       + '</table>'
                    return html;  
                 }

                 function thousandSeparator(x) {
                    return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".");
                 }

                 function callBackShowHtmlSecurityGroup(response, event) {
                     //TODO: Build the HTML Popup for Security Group
                     console.log(event);
                     console.log(response);
                 }

                 function buildHtmlEc2ModalInfo(data) {
                     var colorState;
                     if ( data.state.name.toUpperCase() == "RUNNING" ) {
                         colorState = "green";
                     } else
                     if ( data.state.name.toUpperCase() == "STOPPED" ) {
                         colorState = "red";
                     } else {
                         colorState = "black";
                     }
                     var dtToday      = new Date();
                     var dt           = data.launchTime.split("-")[1] + "-" + data.launchTime.split("-")[0] + "-" + data.launchTime.split("-")[2];
                     var dtLaunched   = new Date(dt);
                     var timeLaucnhed = milliseconds = Math.abs(dtLaunched - dtToday);
                     var hours        = "&nbsp;(<font color=gray;>" + thousandSeparator(parseInt((milliseconds / 36e5))) + " hours)</font>";
                     var publicIpAddressEntry = "";
                     if ( data.publicIpAddress && data.publicIpAddress != "" ){
                           publicIpAddressEntry = '     <input type="image" class="buttonCopyClipboard" onclick="javascript:copyToClipboard(' + "'" + data.publicIpAddress + "'" + ');" src=""" + self.images.copyClipboardPath +""" />'
                                                + '     Public IP...........:&nbsp;<font style="color:blue;">' + data.publicIpAddress + '</font><br>';
                     }
                     var blockDevices = "";
                     if ( data.blockDeviceMappings ) {
                          var totalDevices = data.blockDeviceMappings.length
                          for( var i = 0; i < data.blockDeviceMappings.length; i++ ) {
                              var blockDevice = data.blockDeviceMappings[0];
                              var labelTotal  = '<font style="color:gray;">[' + (i+1) + ' of ' + totalDevices + ']</font>';
                              blockDevices =  blockDevices
                              + '     <input type="image" class="buttonCopyClipboard" onclick="javascript:copyToClipboard(' + "'" + blockDevice.ebs.volumeId + "'" + ');" src=""" + self.images.copyClipboardPath +""" />'
                              + '     EBS...............' + labelTotal + '..:&nbsp;<font style="color:blue;">' + blockDevice.deviceName + ' (' + blockDevice.ebs.volumeId +')&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</font><br>'
                              + '&nbsp;&nbsp;&nbsp;&nbsp;|Status....................:&nbsp;<font style="color:blue;">' + blockDevice.ebs.status + '</font><br>'
                              + '&nbsp;&nbsp;&nbsp;&nbsp;|Attach Time...............:&nbsp;<font style="color:blue;">' + blockDevice.ebs.attachTime + '</font><br>'
                              + '&nbsp;&nbsp;&nbsp;&nbsp;|Delete on Terminatation...:&nbsp;<font style="color:blue;">' + blockDevice.ebs.deleteOnTermination + '</font><br>'  
                          }
                     }
                     var securityGroups = "";
                     if ( data.securityGroups ) {
                          var totalSecurityGroups = data.securityGroups.length
                          for( var i = 0; i < data.securityGroups.length; i++ ) {
                              var securityGroup = data.securityGroups[0];
                              var labelTotal  = '<font style="color:gray;">[' + (i+1) + ' of ' + totalSecurityGroups + ']</font>';
                              securityGroups =  securityGroups
                              + '     <input type="image" class="buttonCopyClipboard" onclick="javascript:copyToClipboard(' + "'" + securityGroup.groupId + "'" + ');" src=""" + self.images.copyClipboardPath +""" />'
                              + '     Security Group....' + labelTotal + '..:&nbsp;<font style="color:darkorange;text-decoration-line:underline;">'
                              + '<span onMouseOver="this.style.cursor=' + "'" + 'pointer' + "'" + '" onClick="javascript:callListSecurityGroups(' + "'" + securityGroup.groupId + "'" + ',callBackShowHtmlSecurityGroup, event);">' + securityGroup.groupId + '</span></font><br>'
                              + '&nbsp;&nbsp;&nbsp;&nbsp;|Group Name................:&nbsp;<font style="color:blue;">' + securityGroup.groupName + '</font><br>'
                          }
                     }
                     var html =  
                         '<div id="imgBtnClose" style="position:absolute;top:4%;left:93%"><img  onMouseOver="this.style.cursor=' + "'" + 'pointer' + "'" + '" src=""" + self.images.close +""" onClick="javascript:closeModalInfo();"></div>'
                       + '<table class="tableInfo" width="100%" height="100%" border="0" cellpadding="0" cellspacing="0">'
                       + ' <tr><td align="center" height="35px" colspan="2" class="cellHeader">EC2 ' + data.instanceType.toUpperCase() + '</td></tr>'  
                       + ' <tr><td class="cellInfo">'
                       + '     <input type="image" class="buttonCopyClipboard" onclick="javascript:copyToClipboard(' + "'" + data.instanceId + "'" + ');" src=""" + self.images.copyClipboardPath +""" />'
                       + '     Instance Id.................:&nbsp;<font style="color:blue;">' + data.instanceId + '</font><br>'
                       + '     <input type="image" class="buttonCopyClipboard" onclick="javascript:copyToClipboard(' + "'" + data.privateIpAddress + "'" + ');" src=""" + self.images.copyClipboardPath +""" />'
                       + '     Private IP..................:&nbsp;<font style="color:blue;">' + data.privateIpAddress + '</font><br>'
                       + '     <input type="image" class="buttonCopyClipboard" onclick="javascript:copyToClipboard(' + "'" + data.launchTime + "'" + ');" src=""" + self.images.copyClipboardPath +""" />'
                       + '     Launch Time.................:&nbsp;<font style="color:blue;">' + data.launchTime + '</font>' + hours + '<br>'
                       + publicIpAddressEntry
                       + '     <input type="image" class="buttonCopyClipboard" onclick="javascript:copyToClipboard(' + "'" + data.state.name.toUpperCase() + "'" + ');" src=""" + self.images.copyClipboardPath +""" />'
                       + '     State.......................:&nbsp;<font style="color:' + colorState +';">' + data.state.name.toUpperCase() + '</font><br>'

                       + '     <input type="image" class="buttonCopyClipboard" onclick="javascript:copyToClipboard(' + "'" + data.cpu.coreCount + "'" + ');" src=""" + self.images.copyClipboardPath +""" />'
                       + '     CPU Core Count..............:&nbsp;<font style="color:blue;">' + data.cpu.coreCount + '</font><br>'
                       + '     <input type="image" class="buttonCopyClipboard" onclick="javascript:copyToClipboard(' + "'" + data.cpu.coreCount + "'" + ');" src=""" + self.images.copyClipboardPath +""" />'
                       + '     CPU Threads Per Core........:&nbsp;<font style="color:blue;">' + data.cpu.threadsPerCore + '</font><br>'
                       + securityGroups
                       + blockDevices
                       + '     <input type="image" class="buttonCopyClipboard" onclick="javascript:copyToClipboard(' + "'" + data.subnetId + "'" + ');" src=""" + self.images.copyClipboardPath +""" />'
                       + '     Subnet Id...................:&nbsp;<font style="color:blue;">' + data.subnetId + '</font><br>'
                       + '     <input type="image" class="buttonCopyClipboard" onclick="javascript:copyToClipboard(' + "'" + data.vpcId + "'"+ ');" src=""" + self.images.copyClipboardPath +""" />'
                       + '     VPC Id......................:&nbsp;<font style="color:blue;">' + data.vpcId + '</font><br>'
                       + ' <tr>'
                       + ' <tr><td colspan="2" class="cellTags">'
                       + '     Tags:<br><br>'
                       var tagsPerLine = 0;
                       for(var index = 0; index < data.tags.length; index++) {
                           var tag = data.tags[index];
                           html = html + '<div class="tag"' 
                                       + 'onclick="javascript:copyToClipboard(' + "'" + tag.key + "=" + tag.value + "'" + ');" '
                                       + tagBehavior + '>' + tag.key + '=' + tag.value + '</div>';
                           tagsPerLine += 1 
                           if (tagsPerLine >= 2) {
                               tagsPerLine = 0;
                               html = html + '<br><br>'
                           }
                       }
                       + ' </td>'
                       + '</table>'
                    return html;  
                 }

                 function buildHtmlAwsModalInfo(data) {
                     var html =  
                         '<table class="tableInfo" width="100%" height="100%" border="0" cellpadding="0" cellspacing="0">'
                       + ' <tr><td align="center" height="35px" colspan="2" class="cellHeader">AWS</td></tr>'  
                       + ' <tr><td class="cellInfo">'
                       + '    <font style="color:blue;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;AWS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</font><br>'
                       + ' <tr>'
                       + '</table>'
                    return html;  
                 }

                 function copyToClipboard(str) {
                    const el = document.createElement('textarea');
                    el.value = str;
                    document.body.appendChild(el);
                    el.select();
                    document.execCommand('copy');
                    document.body.removeChild(el);
                 }

                 """ + self.htmlSnippets.jsFormatXml() + """
                 function pad2(n) { return n < 10 ? '0' + n : n }

                 function timestamp() {
                    var date = new Date();
                    return  date.getFullYear().toString() + "/" + pad2(date.getMonth() + 1) + "/" +  pad2( date.getDate()) 
                    +  " " + pad2( date.getHours() ) + ":" + pad2( date.getMinutes() ) + ":" + pad2( date.getSeconds() ) + ":" + pad2( date.getMilliseconds() ) + " ";
                 }
             </script>
          </head>   
          """ + self.htmlSnippets.htmlCommonBody() + """
        </html>
        """
        self.openHTML(html)
        self.startHttpServer()
        print(Emoticons.ok() + " \033[93mNavigator exited!\033[0m")    
