Large updates to webserver

Do not merge yet

Change-Id: I38c56844c1b0e3e8e5493c2705e62e6db7ee2102
diff --git a/static/CMakeLists.txt b/static/CMakeLists.txt
index b9137dd..ff58a84 100644
--- a/static/CMakeLists.txt
+++ b/static/CMakeLists.txt
@@ -1,173 +1,53 @@
-set(JAVASCRIPT_ASSETS
-    js/selController.js
-    js/lodash.core.js
-    js/ui-bootstrap-tpls-2.5.0.js
-    js/angular-cookies.js
-    js/angular-websocket.js
-    js/angular-ui-router.js
-    js/kvmController.js
-    js/loginController.js
-    js/ipmiController.js
-    js/fwupdateController.js
-    js/mainController.js
-    js/versionController.js
-    js/sensorController.js
-    js/angular-sanitize.js
-    js/bmcApp.js
-    js/base64.js
-    js/angular-animate.js
-    js/run_prettify.js
-    js/angular.js
-    js/angular-ui-router-uib-modal.js
-    js/smart-table.js
-    js/systemConfigController.js
+set(TIMESTAMP_FILE "${CMAKE_CURRENT_BINARY_DIR}/webpack.timestamp")
+
+
+# this could be improved.  It basically sets all source files of a certain type as dependencies
+execute_process(
+    COMMAND git -C ${CMAKE_CURRENT_SOURCE_DIR}/phosphor-webui ls-files --full-name 
+    *.js *.css *.html
+    OUTPUT_VARIABLE PHOSPHOR_FILES
+    )
+STRING(REGEX REPLACE "\n" ";" PHOSPHOR_FILES "${PHOSPHOR_FILES}")
     
-    noVNC/core/inflator.js
-    noVNC/core/input/xtscancodes.js
-    noVNC/core/input/util.js
-    noVNC/core/input/devices.js
-    noVNC/core/input/keysym.js
-    noVNC/core/input/keysymdef.js
-    noVNC/core/websock.js
-    noVNC/core/util.js
-    noVNC/core/base64.js
-    noVNC/core/rfb.js
-    noVNC/core/des.js
-    noVNC/core/display.js
-    noVNC/app/ui.js
-    noVNC/app/webutil.js
+SET(PHOSPHOR_FILES_FULLPATH "")
+FOREACH(filename ${PHOSPHOR_FILES})
+    LIST(APPEND PHOSPHOR_FILES_FULLPATH "${CMAKE_CURRENT_SOURCE_DIR}/phosphor-webui/${filename}")
+ENDFOREACH(filename ${PHOSPHOR_FILES})
 
-)
+# if this is a debug build, don't minify
+IF(CMAKE_BUILD_TYPE MATCHES DEBUG)
+    set(DEBUG_ADD --devtool source-map)
+ENDIF(CMAKE_BUILD_TYPE MATCHES DEBUG)
 
-set(CSS_ASSETS
-    css/intel.css
-    css/bootstrap.css
-    css/font-awesome.css
-    css/bootstrap-theme.css
-    css/prettify.css
-    noVNC/app/styles/base.css
-    noVNC/app/styles/auto.css
-)
+add_custom_command(
+    COMMAND
+    cd ${CMAKE_CURRENT_SOURCE_DIR}/phosphor-webui &&
+    npm install yarn --no-progress --loglevel info -g --prefix ${CMAKE_BINARY_DIR}/node_prefix &&
+    ${CMAKE_BINARY_DIR}/node_prefix/bin/yarn install --dev --no-progress --loglevel info --network-concurrency 1 &&
+    ${CMAKE_BINARY_DIR}/node_prefix/bin/yarn run build ${DEBUG_ADD} && 
+    touch ${TIMESTAMP_FILE}
+    OUTPUT ${TIMESTAMP_FILE}
+    DEPENDS ${PHOSPHOR_FILES_FULLPATH}
+    )
 
-set(HTML_ASSETS
-    index.html
-    partial-login.html
-    partial-eventlog.html
-    partial-fruinfo.html
-    partial-home-list.html
-    partial-ipmi.html
-    partial-kvm.html
-    partial-sensor.html
-    partial-systeminfo.html
-    partial-fwupdate.html
-    partial-fwupdateconfirm.html
-    partial-systemconfig.html
-)
+add_custom_target(webpackbuild 
+    DEPENDS ${TIMESTAMP_FILE}
+    SOURCES ${PHOSPHOR_FILES_FULLPATH}
+    )
 
-set(OTHER_ASSETS
-    img/logo.png
-    img/blur-bg.jpg
-    fonts/fontawesome-webfont.woff
-)
-
-set(STATIC_ASSETS_OUT "")
-set(MINIFIED_ASSETS_OUT "")
-
-find_program(UGLIFY_MINIFIER uglifyjs)
-if(NOT UGLIFY_MINIFIER)
-    message("uglifyjs not found")
-else()
-    message("Found ${UGLIFY_MINIFIER}")
-endif()
-foreach(JAVASCRIPT_ASSET ${JAVASCRIPT_ASSETS})
-
-    set(MINIFIED_FILENAME ${CMAKE_CURRENT_BINARY_DIR}/${JAVASCRIPT_ASSET})
-    get_filename_component(FOLDERNAME ${MINIFIED_FILENAME} DIRECTORY)
-    # string(REGEX REPLACE "(\\.[^.]*$)" ".min\\1" OUTPUT_FILENAME ${OUTPUT_FILENAME})
-    file(MAKE_DIRECTORY "${FOLDERNAME}")
-    if(UGLIFY_MINIFIER)
-        add_custom_command(OUTPUT ${MINIFIED_FILENAME} 
-            COMMAND ${UGLIFY_MINIFIER} --compress --mangle  
-            --output "${MINIFIED_FILENAME}"
-            "${CMAKE_CURRENT_SOURCE_DIR}/${JAVASCRIPT_ASSET}"
-            DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${JAVASCRIPT_ASSET}"
-            COMMENT "Minifying ${JAVASCRIPT_ASSET}"
-        )
-        list(APPEND MINIFIED_ASSETS_OUT ${MINIFIED_FILENAME})
-    endif(UGLIFY_MINIFIER)
-    
-    # if it's a debug build, use the unminified version
-    if (CMAKE_BUILD_TYPE STREQUAL "Debug" OR NOT UGLIFY_MINIFIER)
-        list(APPEND STATIC_ASSETS_OUT ${CMAKE_CURRENT_SOURCE_DIR}/${JAVASCRIPT_ASSET})
-    else()
-        list(APPEND STATIC_ASSETS_OUT ${MINIFIED_FILENAME})
-    endif (CMAKE_BUILD_TYPE STREQUAL "Debug" OR NOT UGLIFY_MINIFIER)
-endforeach(JAVASCRIPT_ASSET)
-
-
-find_program(CSS_MINIFIER cssnano)
-if(NOT CSS_MINIFIER)
-    message("cssnano not found")
-else()
-    message("Found ${CSS_MINIFIER}")
-endif()
-# for now CSS is included as is
-foreach(CSS_ASSET ${CSS_ASSETS})
-    set(MINIFIED_FILENAME ${CMAKE_CURRENT_BINARY_DIR}/${CSS_ASSET})
-    get_filename_component(FOLDERNAME ${MINIFIED_FILENAME} DIRECTORY)
-    file(MAKE_DIRECTORY "${FOLDERNAME}")
-    if(CSS_MINIFIER)   
-        add_custom_command(OUTPUT ${MINIFIED_FILENAME} 
-            COMMAND ${CSS_MINIFIER} 
-            "${CMAKE_CURRENT_SOURCE_DIR}/${CSS_ASSET}"
-            "${CMAKE_CURRENT_BINARY_DIR}/${CSS_ASSET}"
-
-            DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${CSS_ASSET}"
-            COMMENT "Minifying ${CSS_ASSET}"
-        )
-        list(APPEND MINIFIED_ASSETS_OUT ${MINIFIED_FILENAME})
-    endif()
-    # if it's a debug build, use the unminified version
-    if (CMAKE_BUILD_TYPE STREQUAL "Debug" OR NOT CSS_MINIFIER)
-        list(APPEND STATIC_ASSETS_OUT ${CMAKE_CURRENT_SOURCE_DIR}/${CSS_ASSET})
-    else()
-        list(APPEND STATIC_ASSETS_OUT ${MINIFIED_FILENAME})
-    endif (CMAKE_BUILD_TYPE STREQUAL "Debug" OR NOT CSS_MINIFIER)
-    
-endforeach(CSS_ASSET)
-
-# for now HTML is included as is
-foreach(HTML_ASSET ${HTML_ASSETS})
-    list(APPEND STATIC_ASSETS_OUT ${CMAKE_CURRENT_SOURCE_DIR}/${HTML_ASSET})
-endforeach(HTML_ASSET)
-
-# for now IMG is included as is
-foreach(OTHER_ASSET ${OTHER_ASSETS})
-    list(APPEND STATIC_ASSETS_OUT ${CMAKE_CURRENT_SOURCE_DIR}/${OTHER_ASSET})
-endforeach(OTHER_ASSET)
-
-# this taret is the dependency that the build script uses to make sure everything is available
-add_custom_target(buildstatic ALL DEPENDS ${STATIC_ASSETS_OUT})
-
-# this target makes sure that assets are minified in debug builds, even if they aren't used
-add_custom_target(minifyassets ALL DEPENDS ${MINIFIED_ASSETS_OUT})
-
-
-file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/generated)
-
-set(CXX_STATIC_ASSETS_OUTPUT_FILE ${CMAKE_BINARY_DIR}/generated/webassets.cpp)
 set(WEBASSET_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/build_web_assets.py")
-
-if (CMAKE_BUILD_TYPE STREQUAL "Debug")
-    set(DEBUG_MODE "-d")
-endif()
-
-add_custom_command(OUTPUT ${CXX_STATIC_ASSETS_OUTPUT_FILE}
-    COMMAND ${WEBASSET_SCRIPT} ${DEBUG_MODE}
-        -o ${CXX_STATIC_ASSETS_OUTPUT_FILE} -i ${STATIC_ASSETS_OUT}
-
-    DEPENDS ${STATIC_ASSETS_OUT} ${WEBASSET_SCRIPT}
-    COMMENT "Building CPP file ${CXX_STATIC_ASSETS_OUTPUT_FILE}"
+add_custom_command(
+    OUTPUT ${CMAKE_BINARY_DIR}/generated/webassets.cpp
+    OUTPUT ${CMAKE_BINARY_DIR}/generated/webassets.hpp
+    COMMAND python3 ${WEBASSET_SCRIPT}
+        -o ${CMAKE_BINARY_DIR}/generated/webassets.cpp 
+        -i ${CMAKE_CURRENT_SOURCE_DIR}/phosphor-webui/dist
+    DEPENDS ${TIMESTAMP_FILE} ${WEBASSET_SCRIPT}
+    COMMENT "Building CPP file webassets.cpp"
 )
 
-add_custom_target(packagestaticcpp ALL DEPENDS ${CXX_STATIC_ASSETS_OUTPUT_FILE})
\ No newline at end of file
+add_custom_target(packagestaticcpp ALL DEPENDS 
+    ${CMAKE_BINARY_DIR}/generated/webassets.cpp
+    ${CMAKE_BINARY_DIR}/generated/webassets.hpp
+)
+add_dependencies(packagestaticcpp webpackbuild)
diff --git a/static/index.html b/static/index.html
index e0a2058..a699ada 100644
--- a/static/index.html
+++ b/static/index.html
@@ -28,6 +28,8 @@
 
     <script type="text/javascript" src="static/js/bmcApp.js" defer></script>
     <script type="text/javascript" src="static/js/base64.js" defer></script>
+    <script type="text/javascript" src="static/js/dbusWebsocketFactory.js" defer></script>
+
     <script type="text/javascript" src="static/js/mainController.js" defer></script>
 
     <script type="text/javascript" src="static/js/versionController.js" defer></script>
@@ -52,7 +54,6 @@
     <script type="text/javascript" src="static/noVNC/core/inflator.js" defer></script>
     <script type="text/javascript" src="static/noVNC/core/rfb.js" defer></script>
     <script type="text/javascript" src="static/noVNC/core/input/keysym.js" defer></script>
-
 </head>
 
 <body ng-controller="MainCtrl" ng-class="(is_logged_in()) ? '' : 'auth-main'">
diff --git a/static/js/bmcApp.js b/static/js/bmcApp.js
index fb641ab..f1e02c7 100644
--- a/static/js/bmcApp.js
+++ b/static/js/bmcApp.js
@@ -71,6 +71,19 @@
   }
 ]);
 
+app.directive('fileInput', ['$parse', function ($parse) {
+    return {
+        restrict: 'A',
+        link: function (scope, element, attributes) {
+            element.bind('change', function () {
+                $parse(attributes.fileInput)
+                .assign(scope,element[0].files)
+                scope.$apply()
+            });
+        }
+    };
+}]);
+
 app.run([
   '$rootScope', '$cookieStore', '$state', 'AuthenticationService', '$http',
   '$templateCache',
diff --git a/static/js/dbusWebsocketFactory.js b/static/js/dbusWebsocketFactory.js
new file mode 100644
index 0000000..716f2df
--- /dev/null
+++ b/static/js/dbusWebsocketFactory.js
@@ -0,0 +1,20 @@
+angular.module('bmcApp').factory('dbusWebsocketService', [
+  '$location',
+  function($location) {
+    return {
+      start: function(dbus_namespace, callback) {
+        var url = '/dbus_monitor?path_namespace=' + dbus_namespace;
+        var host = $location.host();
+        var port = 18080;
+        var protocol = 'wss://';
+        if ($location.protocol() === 'http') {
+          protocol = 'ws://';
+        }
+        var websocket = new WebSocket(protocol + host + ':' + port + url);
+        websocket.onopen = function() {};
+        websocket.onclose = function() {};
+        websocket.onmessage = function(evt) { callback(evt); };
+      }
+    }
+  }
+]);
\ No newline at end of file
diff --git a/static/js/fwupdateController.js b/static/js/fwupdateController.js
index d5eab07..e33a30f 100644
--- a/static/js/fwupdateController.js
+++ b/static/js/fwupdateController.js
@@ -1,86 +1,87 @@
 angular.module('bmcApp').controller('fwupdateController', [
   '$scope', '$http', '$uibModal', '$state',
   function($scope, $http, $uibModal, $state) {
-    $scope.upload = function(files) {
-      r = new FileReader();
-      r.onload = function(e) {
-        get_image_info = function(buffer) {
-          image_info = {'valid' : false}
-          var expected = '*SignedImage*\0\0\0'
+    $scope.files = [];
+    $scope.$watch('files', function(newValue, oldValue) {
+      if (newValue.length > 0) {
+        console.log('Loading firware file ' + $scope.files[0]);
+        r = new FileReader();
+        r.onload = function(e) {
+          get_image_info = function(buffer) {
+            image_info = {'valid' : false};
+            var expected = '*SignedImage*\0\0\0';
 
-          var dv1 = new Int8Array(e.target.result, 0, 16);
+            var dv1 = new Int8Array(e.target.result, 0, 16);
 
-          for (var i = 0; i != expected.length; i++) {
-            if (dv1[i] != expected.charCodeAt(i)) {
+            for (var i = 0; i != expected.length; i++) {
+              if (dv1[i] != expected.charCodeAt(i)) {
+                return image_info;
+              }
+            }
+            image_info['valid'] = true;
+            var generation = new Int8Array(e.target.result, 16, 17)[0];
+            image_info['generation'] = generation;
+            if ((generation < 4) ||
+                (generation > 5)) {  // not VLN generation header
+
               return image_info;
+            } else {
+              var version_minor = new Uint16Array(e.target.result, 20, 22)[0];
+              image_info['major_version'] =
+                  new Uint8Array(e.target.result, 28, 29)[0];
+              image_info['submajor_version'] =
+                  new Uint8Array(e.target.result, 29, 30)[0].toString(16);
+              var version_minor2 = new Uint16Array(e.target.result, 30, 32)[0];
+              image_info['sha1_version'] =
+                  ('0000' + version_minor2.toString(16)).substr(-4) +
+                  ('0000' + version_minor.toString(16)).substr(-4);
             }
-          }
-          image_info['valid'] = true;
-          var generation = new Int8Array(e.target.result, 16, 17)[0];
-          image_info['generation'] = generation;
-          if ((generation < 4) ||
-              (generation > 5)) {  // not VLN generation header
-
             return image_info;
-          } else {
-            var version_minor = new Uint16Array(e.target.result, 20, 22)[0];
-            image_info['major_version'] =
-                new Uint8Array(e.target.result, 28, 29)[0];
-            image_info['submajor_version'] =
-                new Uint8Array(e.target.result, 29, 30)[0].toString(16);
-            var version_minor2 = new Uint16Array(e.target.result, 30, 32)[0];
-            image_info['sha1_version'] =
-                ('0000' + version_minor2.toString(16)).substr(-4) +
-                ('0000' + version_minor.toString(16)).substr(-4);
+          };
+          var image_info = get_image_info(e.target.result);
+          $scope.image_info = image_info;
+
+          var objectSelectionModal = $uibModal.open({
+            templateUrl : 'static/partial-fwupdateconfirm.html',
+            controller : function($scope) {
+              $scope.image_info = image_info;
+              $scope.file_to_load = file_to_load;
+              // The function that is called for modal closing (positive button)
+
+              $scope.okModal = function() {
+                // Closing the model with result
+                objectSelectionModal.close($scope.selection);
+                $http({
+                  method : 'POST',
+                  url : '/intel/firmwareupload',
+                  data : e.target.result,
+                  transformRequest : [],
+                  headers : {'Content-Type' : 'application/octet-stream'}
+                })
+                    .then(
+                        function successCallback(response) {
+                          console.log('Success uploaded. Response: ' +
+                                      response.data)
+                        },
+                        function errorCallback(response) {
+                          console.log('Error status: ' + response.status)
+                        });
+              };
+
+              // The function that is called for modal dismissal(negative
+              // button)
+
+              $scope.dismissModal = function() {
+                objectSelectionModal.dismiss();
+              };
             }
-          return image_info;
+          });
         };
-        var image_info = get_image_info(e.target.result);
-        $scope.image_info = image_info;
+        var file_to_load = $scope.files[0];
+        $scope.file_to_load = $scope.files[0];
+        r.readAsArrayBuffer($scope.files[0]);
+      }
+    });
 
-        var objectSelectionModal = $uibModal.open({
-          templateUrl : 'static/partial-fwupdateconfirm.html',
-          controller : function($scope) {
-            $scope.image_info = image_info;
-            $scope.file_to_load = file_to_load;
-            // The function that is called for modal closing (positive button)
-
-            $scope.okModal = function() {
-              // Closing the model with result
-              objectSelectionModal.close($scope.selection);
-              $http({
-                method : 'POST',
-                url : '/intel/firmwareupload',
-                data : e.target.result,
-                transformRequest : [],
-                headers : {'Content-Type' : 'application/octet-stream'}
-              })
-                  .then(
-                      function successCallback(response) {
-                        console.log('Success uploaded. Response: ' +
-                                    response.data)
-                      },
-                      function errorCallback(response) {
-                        console.log('Error status: ' + response.status)
-                      });
-            };
-
-            // The function that is called for modal dismissal(negative button)
-
-            $scope.dismissModal = function() {
-              objectSelectionModal.dismiss();
-            };
-
-          }
-
-        });
-      };
-      var file_to_load = files[0];
-      $scope.file_to_load = file_to_load;
-      r.readAsArrayBuffer(files[0]);
-
-    };
-
-    $scope.filename = '';
   }
 ]);
\ No newline at end of file
diff --git a/static/js/fwupdateconfirmController.js b/static/js/fwupdateconfirmController.js
deleted file mode 100644
index 845e73d..0000000
--- a/static/js/fwupdateconfirmController.js
+++ /dev/null
@@ -1,5 +0,0 @@
-angular.module('bmcApp').controller('fwupdateconfirmController', [
-  '$scope', '$stateParams',function($scope, $stateParams) {
-    $scope.filename = $stateParams.filename;
-  }
-]);
\ No newline at end of file
diff --git a/static/js/sensorController.js b/static/js/sensorController.js
index 3943192..272dfee 100644
--- a/static/js/sensorController.js
+++ b/static/js/sensorController.js
@@ -1,59 +1,37 @@
-angular.module('bmcApp')
-    .controller(
-        'sensorController',
-        [
-          '$scope', '$http', '$location', 'websocketService',
-          function($scope, $http, $location, websocketService) {
-            $scope.smartTablePageSize = 10;
-            $scope.next_id = 0;
-            websocketService.start('/dbus_monitor', function(evt) {
-              var obj = JSON.parse(evt.data);
+angular.module('bmcApp').controller('sensorController', [
+  '$scope', '$http', '$location', 'dbusWebsocketService',
+  function($scope, $http, $location, dbusWebsocketService) {
+    $scope.smartTablePageSize = 10;
+    $scope.next_id = 0;
+    dbusWebsocketService.start('/xyz/openbmc_project/sensors', function(evt) {
+      var obj = JSON.parse(evt.data);
 
-              $scope.$apply(function() {
-                for (var sensor_name in obj) {
-                  var found = false;
-                  for (var sensor_index in $scope.rowCollection) {
-                    var sensor_object = $scope.rowCollection[sensor_index];
-                    if (sensor_object.name === sensor_name) {
-                      sensor_object.value = obj[sensor_name];
-                      found = true;
-                      break;
-                    }
-                    }
-                  if (!found) {
-                    console.log(sensor_name + ' -> ' + obj[sensor_name]);
-                    $scope.next_id = $scope.next_id + 1;
+      $scope.$apply(function() {
+        for (var sensor_name in obj) {
+          var found = false;
+          for (var sensor_index in $scope.rowCollection) {
+            var sensor_object = $scope.rowCollection[sensor_index];
+            if (sensor_object.name === sensor_name) {
+              sensor_object.value = obj[sensor_name];
+              found = true;
+              break;
+            }
+          }
+          if (!found) {
+            console.log(sensor_name + ' -> ' + obj[sensor_name]);
+            $scope.next_id = $scope.next_id + 1;
 
-                    $scope.rowCollection.push({
-                      id : $scope.next_id,
-                      name : sensor_name,
-                      value : obj[sensor_name],
-                    });
-                  }
-                };
-              });
+            $scope.rowCollection.push({
+              id : $scope.next_id,
+              name : sensor_name,
+              value : obj[sensor_name],
             });
-
-            $scope.rowCollection = [];
-
           }
-        ])
-    .factory('websocketService', [
-      '$location',
-      function($location) {
-        return {
-          start: function(url, callback) {
-            var host = $location.host();
-            var port = 18080;
-            var protocol = 'wss://';
-            if ($location.protocol() === 'http') {
-              protocol = 'ws://';
-              }
-            var websocket = new WebSocket(protocol + host + ':' + port + url);
-            websocket.onopen = function() {};
-            websocket.onclose = function() {};
-            websocket.onmessage = function(evt) { callback(evt); };
-          }
-        }
-      }
-    ]);
\ No newline at end of file
+        };
+      });
+    });
+
+    $scope.rowCollection = [];
+
+  }
+]);
diff --git a/static/noVNC b/static/noVNC
index b69dda9..bf82644 160000
--- a/static/noVNC
+++ b/static/noVNC
@@ -1 +1 @@
-Subproject commit b69dda9b19f538c648cb11faddc50f30b93bac47
+Subproject commit bf82644461d95c8621923ffb472f50c1a65f9c33
diff --git a/static/partial-fwupdate.html b/static/partial-fwupdate.html
index 829aad8..7bcd4ba 100644
--- a/static/partial-fwupdate.html
+++ b/static/partial-fwupdate.html
@@ -8,7 +8,7 @@
                         </h4>
                     </div>
                     <div class="box-body">
-                        <input type="file" name="file" onchange="angular.element(this).scope().upload(this.files)" />
+                        <input type="file" name="file" file-input="files" />
                     </div>
                 </div>
             </div>