update sensor page
diff --git a/boost-dbus/CMakeLists.txt b/boost-dbus/CMakeLists.txt
index 736d030..57bd8bd 100644
--- a/boost-dbus/CMakeLists.txt
+++ b/boost-dbus/CMakeLists.txt
@@ -49,6 +49,6 @@
 target_link_libraries(dbustests ${Boost_LIBRARIES})
 target_link_libraries(dbustests ${DBus_LIBRARIES})
 target_link_libraries(dbustests ${GTEST_BOTH_LIBRARIES} gmock)
-target_link_libraries(dbustests -pthread)
+target_link_libraries(dbustests pthread)
 add_test(dbustests dbustests "--gtest_output=xml:${test_name}.xml")
 
diff --git a/boost-dbus/include/dbus/connection.hpp b/boost-dbus/include/dbus/connection.hpp
index a7185a5..50e0207 100644
--- a/boost-dbus/include/dbus/connection.hpp
+++ b/boost-dbus/include/dbus/connection.hpp
@@ -53,6 +53,33 @@
     this->get_service().open(this->get_implementation(), bus);
   }
 
+
+  /// Request a name on the bus.
+  /**
+ * @param name The name requested on the bus
+ *
+ * @return
+ *
+ * @throws boost::system::system_error When the response timed out or
+ * there was some other error.
+ */
+  void request_name(const string& name) {
+      this->get_implementation().request_name(name);
+  }
+
+  /// Reply to a message.
+  /**
+ * @param m The message from which to create the reply
+ *
+ * @return The new reply message
+ *
+ * @throws boost::system::system_error When the response timed out or
+ * there was some other error.
+ */
+  message reply(message& m) {
+    return this->get_implementation().new_method_return(m);
+  }
+
   /// Send a message.
   /**
  * @param m The message to send.
@@ -125,6 +152,8 @@
   friend class filter;
 };
 
+typedef std::shared_ptr<connection> connection_ptr;
+
 }  // namespace dbus
 
 #endif  // DBUS_CONNECTION_HPP
diff --git a/boost-dbus/include/dbus/endpoint.hpp b/boost-dbus/include/dbus/endpoint.hpp
index a574e8f..2955a34 100644
--- a/boost-dbus/include/dbus/endpoint.hpp
+++ b/boost-dbus/include/dbus/endpoint.hpp
@@ -16,17 +16,32 @@
   string process_name_;
   string path_;
   string interface_;
+  string member_;
 
  public:
   endpoint(const string& process_name, const string& path,
            const string& interface)
       : process_name_(process_name), path_(path), interface_(interface) {}
 
+  endpoint(const string& process_name, const string& path,
+           const string& interface, const string& member)
+      : process_name_(process_name), path_(path),
+        interface_(interface), member_(member) {}
+
   const string& get_path() const { return path_; }
 
   const string& get_interface() const { return interface_; }
 
   const string& get_process_name() const { return process_name_; }
+
+  const string& get_member() const { return member_; }
+
+  const bool operator == (const endpoint &other) const {
+    return (process_name_ == other.process_name_ &&
+            path_ == other.path_ &&
+            interface_ == other.interface_ &&
+            member_ == other.member_);
+  }
 };
 
 }  // namespace dbus
diff --git a/boost-dbus/include/dbus/filter.hpp b/boost-dbus/include/dbus/filter.hpp
index 5d60d33..b0f0db4 100644
--- a/boost-dbus/include/dbus/filter.hpp
+++ b/boost-dbus/include/dbus/filter.hpp
@@ -19,7 +19,7 @@
  * Filters examine incoming messages, demuxing them to multiple queues.
  */
 class filter {
-  connection& connection_;
+  connection_ptr connection_;
   std::function<bool(message&)> predicate_;
   detail::queue<message> queue_;
 
@@ -31,24 +31,25 @@
   }
 
   template <typename MessagePredicate>
-  filter(connection& c, BOOST_ASIO_MOVE_ARG(MessagePredicate) p)
+  filter(connection_ptr c, BOOST_ASIO_MOVE_ARG(MessagePredicate) p)
       : connection_(c),
         predicate_(BOOST_ASIO_MOVE_CAST(MessagePredicate)(p)),
-        queue_(connection_.get_io_service()) {
-    connection_.new_filter(*this);
+        queue_(connection_->get_io_service()) {
+    connection_->new_filter(*this);
   }
 
-  ~filter() { connection_.delete_filter(*this); }
+  ~filter() { connection_->delete_filter(*this); }
 
   template <typename MessageHandler>
   inline BOOST_ASIO_INITFN_RESULT_TYPE(MessageHandler,
                                        void(boost::system::error_code, message))
       async_dispatch(BOOST_ASIO_MOVE_ARG(MessageHandler) handler) {
     // begin asynchronous operation
-    connection_.get_implementation().start(connection_.get_io_service());
+    connection_->get_implementation().start(connection_->get_io_service());
 
     return queue_.async_pop(BOOST_ASIO_MOVE_CAST(MessageHandler)(handler));
   }
+
 };
 }  // namespace dbus
 
diff --git a/boost-dbus/include/dbus/impl/connection.ipp b/boost-dbus/include/dbus/impl/connection.ipp
index 24a257d..c4c0605 100644
--- a/boost-dbus/include/dbus/impl/connection.ipp
+++ b/boost-dbus/include/dbus/impl/connection.ipp
@@ -44,6 +44,13 @@
     detail::set_watch_timeout_dispatch_functions(conn, io);
   }
 
+  void request_name(const string& name) {
+    error e;
+    dbus_bus_request_name(conn, name.c_str(),
+			 DBUS_NAME_FLAG_DO_NOT_QUEUE | DBUS_NAME_FLAG_REPLACE_EXISTING, e);
+    e.throw_if_set();
+  }
+
   ~connection() {
     if (conn != NULL) {
       dbus_connection_close(conn);
@@ -51,6 +58,10 @@
     }
   }
 
+  message new_method_return(message &m) {
+  	return dbus_message_new_method_return(m);
+  }
+
   operator DBusConnection*() { return conn; }
   operator const DBusConnection*() const { return conn; }
 
diff --git a/boost-dbus/include/dbus/match.hpp b/boost-dbus/include/dbus/match.hpp
index 0488aa0..cdcd169 100644
--- a/boost-dbus/include/dbus/match.hpp
+++ b/boost-dbus/include/dbus/match.hpp
@@ -22,16 +22,16 @@
  * dispose of the object.
  */
 class match {
-  connection& connection_;
+  connection_ptr connection_;
   std::string expression_;
 
  public:
-  match(connection& c, BOOST_ASIO_MOVE_ARG(std::string) e)
+  match(connection_ptr c, BOOST_ASIO_MOVE_ARG(std::string) e)
       : connection_(c), expression_(BOOST_ASIO_MOVE_CAST(std::string)(e)) {
-    connection_.new_match(*this);
+    connection_->new_match(*this);
   }
 
-  ~match() { connection_.delete_match(*this); }
+  ~match() { connection_->delete_match(*this); }
 
   const std::string& get_expression() const { return expression_; }
 
diff --git a/boost-dbus/include/dbus/message.hpp b/boost-dbus/include/dbus/message.hpp
index 695e1fc..e4b0255 100644
--- a/boost-dbus/include/dbus/message.hpp
+++ b/boost-dbus/include/dbus/message.hpp
@@ -77,6 +77,10 @@
         dbus_message_type_to_string(dbus_message_get_type(message_.get())));
   }
 
+  string get_signature() const {
+    return sanitize(dbus_message_get_signature(message_.get()));
+  }
+
   string get_sender() const {
     return sanitize(dbus_message_get_sender(message_.get()));
   }
diff --git a/boost-dbus/test/avahi.cpp b/boost-dbus/test/avahi.cpp
index 8cbb82f..5d2deb8 100644
--- a/boost-dbus/test/avahi.cpp
+++ b/boost-dbus/test/avahi.cpp
@@ -19,11 +19,11 @@
   dbus::endpoint test_daemon("org.freedesktop.Avahi", "/",
                              "org.freedesktop.Avahi.Server");
   boost::asio::io_service io;
-  dbus::connection system_bus(io, dbus::bus::system);
+  auto system_bus = std::make_shared<dbus::connection>(io, dbus::bus::system);
 
   dbus::message m = dbus::message::new_call(test_daemon, "GetHostName");
 
-  system_bus.async_send(
+  system_bus->async_send(
       m, [&](const boost::system::error_code ec, dbus::message r) {
 
         std::string avahi_hostname;
@@ -52,7 +52,7 @@
 
 TEST(AvahiTest, ServiceBrowser) {
   boost::asio::io_service io;
-  dbus::connection system_bus(io, dbus::bus::system);
+  auto system_bus = std::make_shared<dbus::connection>(io, dbus::bus::system);
 
   dbus::endpoint test_daemon("org.freedesktop.Avahi", "/",
                              "org.freedesktop.Avahi.Server");
@@ -64,7 +64,7 @@
       .pack<std::string>("local")
       .pack<uint32_t>(0);
 
-  dbus::message r = system_bus.send(m1);
+  dbus::message r = system_bus->send(m1);
   std::string browser_path;
   r.unpack(browser_path);
   testing::Test::RecordProperty("browserPath", browser_path);
@@ -102,13 +102,13 @@
     FAIL() << "Callback was never called\n";
   });
 
-  dbus::connection system_bus(io, dbus::bus::system);
+  auto system_bus = std::make_shared<dbus::connection>(io, dbus::bus::system);
 
   dbus::endpoint test_daemon("org.freedesktop.DBus", "/",
                              "org.freedesktop.DBus");
   // create new service browser
   dbus::message m = dbus::message::new_call(test_daemon, "ListNames");
-  system_bus.async_send(
+  system_bus->async_send(
       m, [&](const boost::system::error_code ec, dbus::message r) {
         io.stop();
         std::vector<std::string> services;
@@ -123,13 +123,13 @@
   io.run();
 }
 
-void query_interfaces(dbus::connection& system_bus, std::string& service_name,
-                      std::string& object_name) {
+void query_interfaces(dbus::connection_ptr system_bus,
+                      std::string& service_name, std::string& object_name) {
   dbus::endpoint service_daemon(service_name, object_name,
                                 "org.freedestop.DBus.Introspectable");
   dbus::message m = dbus::message::new_call(service_daemon, "Introspect");
   try {
-    auto r = system_bus.send(m);
+    auto r = system_bus->send(m);
     std::vector<std::string> names;
     // Todo(ed) figure out why we're occassionally getting access
     // denied errors
@@ -152,10 +152,12 @@
 
 TEST(BOOST_DBUS, SingleSensorChanged) {
   boost::asio::io_service io;
-  dbus::connection system_bus(io, dbus::bus::system);
+
+  auto system_bus = std::make_shared<dbus::connection>(io, dbus::bus::system);
 
   dbus::match ma(system_bus,
                  "type='signal',path_namespace='/xyz/openbmc_project/sensors'");
+
   dbus::filter f(system_bus, [](dbus::message& m) {
     auto member = m.get_member();
     return member == "PropertiesChanged";
@@ -199,15 +201,15 @@
 
   auto removed = std::vector<uint32_t>();
   m.pack(removed);
-  system_bus.async_send(m,
-                        [&](boost::system::error_code ec, dbus::message s) {});
+  system_bus->async_send(m,
+                         [&](boost::system::error_code ec, dbus::message s) {});
 
   io.run();
 }
 
 TEST(BOOST_DBUS, MultipleSensorChanged) {
   boost::asio::io_service io;
-  dbus::connection system_bus(io, dbus::bus::system);
+  auto system_bus = std::make_shared<dbus::connection>(io, dbus::bus::system);
 
   dbus::match ma(system_bus,
                  "type='signal',path_namespace='/xyz/openbmc_project/sensors'");
@@ -217,7 +219,8 @@
   });
 
   int count = 0;
-  f.async_dispatch([&](boost::system::error_code ec, dbus::message s) {
+  std::function<void(boost::system::error_code, dbus::message)> callback = [&](
+      boost::system::error_code ec, dbus::message s) {
     std::string object_name;
     EXPECT_EQ(s.get_path(),
               "/xyz/openbmc_project/sensors/temperature/LR_Brd_Temp");
@@ -233,9 +236,12 @@
     count++;
     if (count == 2) {
       io.stop();
+    } else {
+      f.async_dispatch(callback);
     }
 
-  });
+  };
+  f.async_dispatch(callback);
 
   dbus::endpoint test_endpoint(
       "org.freedesktop.Avahi",
@@ -255,9 +261,70 @@
 
   auto removed = std::vector<uint32_t>();
   m.pack(removed);
-  system_bus.async_send(m,
-                        [&](boost::system::error_code ec, dbus::message s) {});
-  system_bus.async_send(m,
-                        [&](boost::system::error_code ec, dbus::message s) {});
+  system_bus->async_send(m,
+                         [&](boost::system::error_code ec, dbus::message s) {});
+  system_bus->async_send(m,
+                         [&](boost::system::error_code ec, dbus::message s) {});
   io.run();
-}
\ No newline at end of file
+}
+
+TEST(BOOST_DBUS, MethodCall) {
+  boost::asio::io_service io;
+  boost::asio::deadline_timer t(io, boost::posix_time::seconds(30));
+  t.async_wait([&](const boost::system::error_code& /*e*/) {
+    io.stop();
+    FAIL() << "Callback was never called\n";
+  });
+  std::string requested_name = "xyz.openbmc_project.fwupdate1.server";
+  auto system_bus = std::make_shared<dbus::connection>(io, dbus::bus::system);
+  //system_bus->request_name(requested_name);
+
+  /* not sure we even need to add a match for method calls,
+   * but this is how you might do it .... */
+  dbus::match ma(
+      system_bus,
+      "type='method_call',path_namespace='/xyz/openbmc_project/fwupdate1'");
+
+  dbus::filter f(system_bus, [](dbus::message& m) {
+    // std::cerr << "filter called: " << m << std::endl;
+    return (m.get_member() == "Get" &&
+            m.get_interface() == "org.freedesktop.DBus.Properties" &&
+            m.get_signature() == "ss");
+  });
+
+  std::function<void(boost::system::error_code, dbus::message)> method_handler =
+      [&](boost::system::error_code ec, dbus::message s) {
+        if (ec) {
+          FAIL() << ec;
+        } else {
+          std::string intf_name, prop_name;
+          s.unpack(intf_name).unpack(prop_name);
+
+          EXPECT_EQ(intf_name, "xyz.openbmc_project.fwupdate1");
+          EXPECT_EQ(prop_name, "State");
+
+          // send a reply so dbus doesn't get angry?
+          auto r = system_bus->reply(s);
+          r.pack("IDLE");
+          system_bus->async_send(
+              r, [&](boost::system::error_code ec, dbus::message s) {});
+          io.stop();
+        }
+      };
+  f.async_dispatch(method_handler);
+
+  dbus::endpoint test_endpoint(requested_name, "/xyz/openbmc_project/fwupdate1",
+                               "org.freedesktop.DBus.Properties");
+
+  auto method_name = std::string("Get");
+  auto m = dbus::message::new_call(test_endpoint, method_name);
+
+  m.pack("xyz.openbmc_project.fwupdate1");
+  m.pack("State");
+
+  system_bus->async_send(m, [&](boost::system::error_code ec, dbus::message s) {
+    std::cerr << "received s: " << s << std::endl;
+  });
+
+  io.run();
+}
diff --git a/src/webserver_main.cpp b/src/webserver_main.cpp
index ec21f6d..0da73f4 100644
--- a/src/webserver_main.cpp
+++ b/src/webserver_main.cpp
@@ -44,7 +44,6 @@
 static std::shared_ptr<dbus::connection> system_bus;
 static std::shared_ptr<dbus::match> sensor_match;
 static std::shared_ptr<dbus::filter> sensor_filter;
-static std::shared_ptr<dbus::filter> sensor_callback;
 
 std::unordered_set<crow::websocket::connection*> users;
 
@@ -58,8 +57,9 @@
     boost::apply_visitor([&](auto val) { j[s.get_path()] = val; },
                          value.second);
   }
+  auto data_to_send = crow::json::dump(j);
   for (auto conn : users) {
-    conn->send_text(crow::json::dump(j));
+    conn->send_text(data_to_send);
   }
   sensor_filter->async_dispatch(on_sensor_update);
 };
@@ -113,12 +113,12 @@
         }
         if (!sensor_match) {
           sensor_match = std::make_shared<dbus::match>(
-              *system_bus,
+              system_bus,
               "type='signal',path_namespace='/xyz/openbmc_project/sensors'");
         }
         if (!sensor_filter) {
           sensor_filter =
-              std::make_shared<dbus::filter>(*system_bus, [](dbus::message& m) {
+              std::make_shared<dbus::filter>(system_bus, [](dbus::message& m) {
                 auto member = m.get_member();
                 return member == "PropertiesChanged";
               });
@@ -149,26 +149,30 @@
     system_bus.async_send(
         m,
         [&j, &system_bus](const boost::system::error_code ec, dbus::message r) {
-          std::string xml;
-          r.unpack(xml);
-          std::vector<std::string> dbus_objects;
-          dbus::read_dbus_xml_names(xml, dbus_objects);
+          if (ec) {
+            
+          } else {
+            std::string xml;
+            r.unpack(xml);
+            std::vector<std::string> dbus_objects;
+            dbus::read_dbus_xml_names(xml, dbus_objects);
 
-          for (auto& object : dbus_objects) {
-            dbus::endpoint test_daemon("org.openbmc.Sensors",
-                                       "/org/openbmc/sensors/tach/" + object,
-                                       "org.openbmc.SensorValue");
-            dbus::message m2 = dbus::message::new_call(test_daemon, "getValue");
+            for (auto& object : dbus_objects) {
+              dbus::endpoint test_daemon("org.openbmc.Sensors",
+                                         "/org/openbmc/sensors/tach/" + object,
+                                         "org.openbmc.SensorValue");
+              dbus::message m2 =
+                  dbus::message::new_call(test_daemon, "getValue");
 
-            system_bus.async_send(
-                m2, [&](const boost::system::error_code ec, dbus::message r) {
-                  int32_t value;
-                  r.unpack(value);
-                  // TODO(ed) if we ever go multithread, j needs a lock
-                  j[object] = value;
-                });
+              system_bus.async_send(
+                  m2, [&](const boost::system::error_code ec, dbus::message r) {
+                    int32_t value;
+                    r.unpack(value);
+                    // TODO(ed) if we ever go multithread, j needs a lock
+                    j[object] = value;
+                  });
+            }
           }
-
         });
 
   });
diff --git a/static/CMakeLists.txt b/static/CMakeLists.txt
index 4e6831c..6e5bc2b 100644
--- a/static/CMakeLists.txt
+++ b/static/CMakeLists.txt
@@ -19,6 +19,7 @@
     js/run_prettify.js
     js/angular.js
     js/angular-ui-router-uib-modal.js
+    js/smart-table.js
     
     noVNC/core/inflator.js
     noVNC/core/input/xtscancodes.js
@@ -72,6 +73,8 @@
 find_program(UGLIFY_MINIFIER uglifyjs)
 if(NOT UGLIFY_MINIFIER)
     message("uglifyjs not found")
+else()
+    message("Found ${UGLIFY_MINIFIER}")
 endif()
 foreach(JAVASCRIPT_ASSET ${JAVASCRIPT_ASSETS})
 
@@ -81,7 +84,7 @@
     file(MAKE_DIRECTORY "${FOLDERNAME}")
     if(UGLIFY_MINIFIER)
         add_custom_command(OUTPUT ${MINIFIED_FILENAME} 
-            COMMAND uglifyjs --compress --mangle  
+            COMMAND ${UGLIFY_MINIFIER} --compress --mangle  
             --output "${MINIFIED_FILENAME}"
             "${CMAKE_CURRENT_SOURCE_DIR}/${JAVASCRIPT_ASSET}"
             DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${JAVASCRIPT_ASSET}"
@@ -102,6 +105,8 @@
 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})
@@ -110,7 +115,7 @@
     file(MAKE_DIRECTORY "${FOLDERNAME}")
     if(CSS_MINIFIER)   
         add_custom_command(OUTPUT ${MINIFIED_FILENAME} 
-            COMMAND cssnano 
+            COMMAND ${CSS_MINIFIER} 
             "${CMAKE_CURRENT_SOURCE_DIR}/${CSS_ASSET}"
             "${CMAKE_CURRENT_BINARY_DIR}/${CSS_ASSET}"
 
diff --git a/static/index.html b/static/index.html
index 6e5f9bd..61e779b 100644
--- a/static/index.html
+++ b/static/index.html
@@ -22,6 +22,8 @@
     <script type="text/javascript" src="static/js/angular-ui-router-uib-modal.js" defer></script>
     <script type="text/javascript" src="static/js/lodash.core.js" defer></script>
 
+    <script type="text/javascript" src="static/js/smart-table.js" defer></script>
+
     <script type="text/javascript" src="static/js/ui-bootstrap-tpls-2.5.0.js" defer></script>
 
     <script type="text/javascript" src="static/js/bmcApp.js" defer></script>
@@ -54,6 +56,38 @@
 </head>
 
 <body ng-controller="MainCtrl" ng-class="(is_logged_in()) ? '' : 'auth-main'">
+    <main ng-if="$pageFinishedLoading" ng-class="{ 'menu-collapsed': $baSidebarService.isMenuCollapsed() }">
+
+        <ba-sidebar></ba-sidebar>
+        <page-top></page-top>
+
+        <div class="al-main">
+            <div class="al-content">
+                <content-top></content-top>
+                <div ui-view autoscroll="true" autoscroll-body-top></div>
+            </div>
+        </div>
+
+        <footer class="al-footer clearfix">
+            <div class="al-footer-right">Created with <i class="ion-heart"></i></div>
+            <div class="al-footer-main clearfix">
+                <div class="al-copy">Blur Admin 2016</div>
+                <ul class="al-share clearfix">
+                    <li><i class="socicon socicon-facebook"></i></li>
+                    <li><i class="socicon socicon-twitter"></i></li>
+                    <li><i class="socicon socicon-google"></i></li>
+                    <li><i class="socicon socicon-github"></i></li>
+                </ul>
+            </div>
+        </footer>
+
+        <back-top></back-top>
+    </main>
+
+    <div id="preloader" ng-show="!$pageFinishedLoading">
+        <div></div>
+    </div>
+
     <div>
         <nav class="navbar navbar-inverse" ng-if='is_logged_in()'>
             <div class="container-fluid">
diff --git a/static/js/bmcApp.js b/static/js/bmcApp.js
index 55e4a42..1812261 100644
--- a/static/js/bmcApp.js
+++ b/static/js/bmcApp.js
@@ -1,8 +1,15 @@
 'use strict';
 angular.module('Authentication', []);
 var app = angular.module('bmcApp', [
-  'ngCookies', 'ngAnimate', 'ngSanitize', 'ui.bootstrap',
-  'ui.router', 'ngWebSocket', 'Authentication', 'ui.router.modal',
+  'ngCookies',
+  'ngAnimate',
+  'ngSanitize',
+  'ui.bootstrap',
+  'ui.router',
+  'ngWebSocket',
+  'Authentication',
+  'ui.router.modal',
+  'smart-table',
 ]);
 
 app.service('loginInterceptor', [
@@ -128,7 +135,7 @@
         .state('fwupdate.confirm', {
           url : '/confirm',
           templateUrl : 'static/partial-fwupdateconfirm.html',
-          modal: true
+          modal : true
         })
         // ABOUT PAGE AND MULTIPLE NAMED VIEWS =================================
         .state('about',
@@ -216,7 +223,7 @@
     };
 
     service.IsLoggedIn = function() {
-      if ($rootScope.globals['currentUser']){
+      if ($rootScope.globals['currentUser']) {
         return true;
       } else {
         return false;
diff --git a/static/js/sensorController.js b/static/js/sensorController.js
index 67c9d82..0fa9045 100644
--- a/static/js/sensorController.js
+++ b/static/js/sensorController.js
@@ -1,41 +1,54 @@
 angular.module('bmcApp').controller('sensorController', [
   '$scope', '$http', '$location', 'websocketService',
   function($scope, $http, $location, websocketService) {
-    $scope.sensor_values = {};
+    $scope.smartTablePageSize = 10;
+    $scope.next_id = 0;
+    websocketService.start('/sensorws', function(evt) {
+      var obj = JSON.parse(evt.data);
 
-    var host = $location.host();
-    var port = $location.port();
-    var protocol = "ws://";
-    if ($location.protocol() === 'https') {
-      protocol = 'wss://';
-    }
-    websocketService.start(protocol + host + ":" + port + "/sensorws", function (evt) {
-        var obj = JSON.parse(evt.data);
-        $scope.$apply(function () {
-            for (var key in obj) {
-              if (obj.hasOwnProperty(key)) {
-                console.log(key + " -> " + obj[key]);
-                $scope.sensor_values[key] = obj[key];
-              }
+      $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 = [];
+
   }
 ]);
 
-app.factory('websocketService', function () {
-        return {
-            start: function (url, callback) {
-                var websocket = new WebSocket(url);
-                websocket.onopen = function () {
-                };
-                websocket.onclose = function () {
-                };
-                websocket.onmessage = function (evt) {
-                    callback(evt);
-                };
-            }
+app.factory('websocketService', 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
+  }
+});
\ No newline at end of file
diff --git a/static/js/smart-table.js b/static/js/smart-table.js
new file mode 100644
index 0000000..e0246d4
--- /dev/null
+++ b/static/js/smart-table.js
@@ -0,0 +1,534 @@
+/** 
+* @version 2.1.8
+* @license MIT
+*/
+(function (ng, undefined){
+    'use strict';
+
+ng.module('smart-table', []).run(['$templateCache', function ($templateCache) {
+    $templateCache.put('template/smart-table/pagination.html',
+        '<nav ng-if="numPages && pages.length >= 2"><ul class="pagination">' +
+        '<li ng-repeat="page in pages" ng-class="{active: page==currentPage}"><a href="javascript: void(0);" ng-click="selectPage(page)">{{page}}</a></li>' +
+        '</ul></nav>');
+}]);
+
+
+ng.module('smart-table')
+  .constant('stConfig', {
+    pagination: {
+      template: 'template/smart-table/pagination.html',
+      itemsByPage: 10,
+      displayedPages: 5
+    },
+    search: {
+      delay: 400, // ms
+      inputEvent: 'input'
+    },
+    select: {
+      mode: 'single',
+      selectedClass: 'st-selected'
+    },
+    sort: {
+      ascentClass: 'st-sort-ascent',
+      descentClass: 'st-sort-descent',
+      descendingFirst: false,
+      skipNatural: false,
+      delay:300
+    },
+    pipe: {
+      delay: 100 //ms
+    }
+  });
+ng.module('smart-table')
+  .controller('stTableController', ['$scope', '$parse', '$filter', '$attrs', function StTableController ($scope, $parse, $filter, $attrs) {
+    var propertyName = $attrs.stTable;
+    var displayGetter = $parse(propertyName);
+    var displaySetter = displayGetter.assign;
+    var safeGetter;
+    var orderBy = $filter('orderBy');
+    var filter = $filter('filter');
+    var safeCopy = copyRefs(displayGetter($scope));
+    var tableState = {
+      sort: {},
+      search: {},
+      pagination: {
+        start: 0,
+        totalItemCount: 0
+      }
+    };
+    var filtered;
+    var pipeAfterSafeCopy = true;
+    var ctrl = this;
+    var lastSelected;
+
+    function copyRefs (src) {
+      return src ? [].concat(src) : [];
+    }
+
+    function updateSafeCopy () {
+      safeCopy = copyRefs(safeGetter($scope));
+      if (pipeAfterSafeCopy === true) {
+        ctrl.pipe();
+      }
+    }
+
+    function deepDelete (object, path) {
+      if (path.indexOf('.') != -1) {
+        var partials = path.split('.');
+        var key = partials.pop();
+        var parentPath = partials.join('.');
+        var parentObject = $parse(parentPath)(object)
+        delete parentObject[key];
+        if (Object.keys(parentObject).length == 0) {
+          deepDelete(object, parentPath);
+        }
+      } else {
+        delete object[path];
+      }
+    }
+
+    if ($attrs.stSafeSrc) {
+      safeGetter = $parse($attrs.stSafeSrc);
+      $scope.$watch(function () {
+        var safeSrc = safeGetter($scope);
+        return safeSrc && safeSrc.length ? safeSrc[0] : undefined;
+      }, function (newValue, oldValue) {
+        if (newValue !== oldValue) {
+          updateSafeCopy();
+        }
+      });
+      $scope.$watch(function () {
+        var safeSrc = safeGetter($scope);
+        return safeSrc ? safeSrc.length : 0;
+      }, function (newValue, oldValue) {
+        if (newValue !== safeCopy.length) {
+          updateSafeCopy();
+        }
+      });
+      $scope.$watch(function () {
+        return safeGetter($scope);
+      }, function (newValue, oldValue) {
+        if (newValue !== oldValue) {
+          tableState.pagination.start = 0;
+          updateSafeCopy();
+        }
+      });
+    }
+
+    /**
+     * sort the rows
+     * @param {Function | String} predicate - function or string which will be used as predicate for the sorting
+     * @param [reverse] - if you want to reverse the order
+     */
+    this.sortBy = function sortBy (predicate, reverse) {
+      tableState.sort.predicate = predicate;
+      tableState.sort.reverse = reverse === true;
+
+      if (ng.isFunction(predicate)) {
+        tableState.sort.functionName = predicate.name;
+      } else {
+        delete tableState.sort.functionName;
+      }
+
+      tableState.pagination.start = 0;
+      return this.pipe();
+    };
+
+    /**
+     * search matching rows
+     * @param {String} input - the input string
+     * @param {String} [predicate] - the property name against you want to check the match, otherwise it will search on all properties
+     */
+    this.search = function search (input, predicate) {
+      var predicateObject = tableState.search.predicateObject || {};
+      var prop = predicate ? predicate : '$';
+
+      input = ng.isString(input) ? input.trim() : input;
+      $parse(prop).assign(predicateObject, input);
+      // to avoid to filter out null value
+      if (!input) {
+        deepDelete(predicateObject, prop);
+      }
+      tableState.search.predicateObject = predicateObject;
+      tableState.pagination.start = 0;
+      return this.pipe();
+    };
+
+    /**
+     * this will chain the operations of sorting and filtering based on the current table state (sort options, filtering, ect)
+     */
+    this.pipe = function pipe () {
+      var pagination = tableState.pagination;
+      var output;
+      filtered = tableState.search.predicateObject ? filter(safeCopy, tableState.search.predicateObject) : safeCopy;
+      if (tableState.sort.predicate) {
+        filtered = orderBy(filtered, tableState.sort.predicate, tableState.sort.reverse);
+      }
+      pagination.totalItemCount = filtered.length;
+      if (pagination.number !== undefined) {
+        pagination.numberOfPages = filtered.length > 0 ? Math.ceil(filtered.length / pagination.number) : 1;
+        pagination.start = pagination.start >= filtered.length ? (pagination.numberOfPages - 1) * pagination.number : pagination.start;
+        output = filtered.slice(pagination.start, pagination.start + parseInt(pagination.number));
+      }
+      displaySetter($scope, output || filtered);
+    };
+
+    /**
+     * select a dataRow (it will add the attribute isSelected to the row object)
+     * @param {Object} row - the row to select
+     * @param {String} [mode] - "single" or "multiple" (multiple by default)
+     */
+    this.select = function select (row, mode) {
+      var rows = copyRefs(displayGetter($scope));
+      var index = rows.indexOf(row);
+      if (index !== -1) {
+        if (mode === 'single') {
+          row.isSelected = row.isSelected !== true;
+          if (lastSelected) {
+            lastSelected.isSelected = false;
+          }
+          lastSelected = row.isSelected === true ? row : undefined;
+        } else {
+          rows[index].isSelected = !rows[index].isSelected;
+        }
+      }
+    };
+
+    /**
+     * take a slice of the current sorted/filtered collection (pagination)
+     *
+     * @param {Number} start - start index of the slice
+     * @param {Number} number - the number of item in the slice
+     */
+    this.slice = function splice (start, number) {
+      tableState.pagination.start = start;
+      tableState.pagination.number = number;
+      return this.pipe();
+    };
+
+    /**
+     * return the current state of the table
+     * @returns {{sort: {}, search: {}, pagination: {start: number}}}
+     */
+    this.tableState = function getTableState () {
+      return tableState;
+    };
+
+    this.getFilteredCollection = function getFilteredCollection () {
+      return filtered || safeCopy;
+    };
+
+    /**
+     * Use a different filter function than the angular FilterFilter
+     * @param filterName the name under which the custom filter is registered
+     */
+    this.setFilterFunction = function setFilterFunction (filterName) {
+      filter = $filter(filterName);
+    };
+
+    /**
+     * Use a different function than the angular orderBy
+     * @param sortFunctionName the name under which the custom order function is registered
+     */
+    this.setSortFunction = function setSortFunction (sortFunctionName) {
+      orderBy = $filter(sortFunctionName);
+    };
+
+    /**
+     * Usually when the safe copy is updated the pipe function is called.
+     * Calling this method will prevent it, which is something required when using a custom pipe function
+     */
+    this.preventPipeOnWatch = function preventPipe () {
+      pipeAfterSafeCopy = false;
+    };
+  }])
+  .directive('stTable', function () {
+    return {
+      restrict: 'A',
+      controller: 'stTableController',
+      link: function (scope, element, attr, ctrl) {
+
+        if (attr.stSetFilter) {
+          ctrl.setFilterFunction(attr.stSetFilter);
+        }
+
+        if (attr.stSetSort) {
+          ctrl.setSortFunction(attr.stSetSort);
+        }
+      }
+    };
+  });
+
+ng.module('smart-table')
+  .directive('stSearch', ['stConfig', '$timeout','$parse', function (stConfig, $timeout, $parse) {
+    return {
+      require: '^stTable',
+      link: function (scope, element, attr, ctrl) {
+        var tableCtrl = ctrl;
+        var promise = null;
+        var throttle = attr.stDelay || stConfig.search.delay;
+        var event = attr.stInputEvent || stConfig.search.inputEvent;
+
+        attr.$observe('stSearch', function (newValue, oldValue) {
+          var input = element[0].value;
+          if (newValue !== oldValue && input) {
+            ctrl.tableState().search = {};
+            tableCtrl.search(input, newValue);
+          }
+        });
+
+        //table state -> view
+        scope.$watch(function () {
+          return ctrl.tableState().search;
+        }, function (newValue, oldValue) {
+          var predicateExpression = attr.stSearch || '$';
+          if (newValue.predicateObject && $parse(predicateExpression)(newValue.predicateObject) !== element[0].value) {
+            element[0].value = $parse(predicateExpression)(newValue.predicateObject) || '';
+          }
+        }, true);
+
+        // view -> table state
+        element.bind(event, function (evt) {
+          evt = evt.originalEvent || evt;
+          if (promise !== null) {
+            $timeout.cancel(promise);
+          }
+
+          promise = $timeout(function () {
+            tableCtrl.search(evt.target.value, attr.stSearch || '');
+            promise = null;
+          }, throttle);
+        });
+      }
+    };
+  }]);
+
+ng.module('smart-table')
+  .directive('stSelectRow', ['stConfig', function (stConfig) {
+    return {
+      restrict: 'A',
+      require: '^stTable',
+      scope: {
+        row: '=stSelectRow'
+      },
+      link: function (scope, element, attr, ctrl) {
+        var mode = attr.stSelectMode || stConfig.select.mode;
+        element.bind('click', function () {
+          scope.$apply(function () {
+            ctrl.select(scope.row, mode);
+          });
+        });
+
+        scope.$watch('row.isSelected', function (newValue) {
+          if (newValue === true) {
+            element.addClass(stConfig.select.selectedClass);
+          } else {
+            element.removeClass(stConfig.select.selectedClass);
+          }
+        });
+      }
+    };
+  }]);
+
+ng.module('smart-table')
+  .directive('stSort', ['stConfig', '$parse', '$timeout', function (stConfig, $parse, $timeout) {
+    return {
+      restrict: 'A',
+      require: '^stTable',
+      link: function (scope, element, attr, ctrl) {
+
+        var predicate = attr.stSort;
+        var getter = $parse(predicate);
+        var index = 0;
+        var classAscent = attr.stClassAscent || stConfig.sort.ascentClass;
+        var classDescent = attr.stClassDescent || stConfig.sort.descentClass;
+        var stateClasses = [classAscent, classDescent];
+        var sortDefault;
+        var skipNatural = attr.stSkipNatural !== undefined ? attr.stSkipNatural : stConfig.sort.skipNatural;
+        var descendingFirst = attr.stDescendingFirst !== undefined ? attr.stDescendingFirst : stConfig.sort.descendingFirst;
+        var promise = null;
+        var throttle = attr.stDelay || stConfig.sort.delay;
+
+        if (attr.stSortDefault) {
+          sortDefault = scope.$eval(attr.stSortDefault) !== undefined ? scope.$eval(attr.stSortDefault) : attr.stSortDefault;
+        }
+
+        //view --> table state
+        function sort () {
+          if (descendingFirst) {
+            index = index === 0 ? 2 : index - 1;
+          } else {
+            index++;
+          }
+
+          var func;
+          predicate = ng.isFunction(getter(scope)) || ng.isArray(getter(scope)) ? getter(scope) : attr.stSort;
+          if (index % 3 === 0 && !!skipNatural !== true) {
+            //manual reset
+            index = 0;
+            ctrl.tableState().sort = {};
+            ctrl.tableState().pagination.start = 0;
+            func = ctrl.pipe.bind(ctrl);
+          } else {
+            func = ctrl.sortBy.bind(ctrl, predicate, index % 2 === 0);
+          }
+          if (promise !== null) {
+            $timeout.cancel(promise);
+          }
+          if (throttle < 0) {
+            func();
+          } else {
+            promise = $timeout(func, throttle);
+          }
+        }
+
+        element.bind('click', function sortClick () {
+          if (predicate) {
+            scope.$apply(sort);
+          }
+        });
+
+        if (sortDefault) {
+          index = sortDefault === 'reverse' ? 1 : 0;
+          sort();
+        }
+
+        //table state --> view
+        scope.$watch(function () {
+          return ctrl.tableState().sort;
+        }, function (newValue) {
+          if (newValue.predicate !== predicate) {
+            index = 0;
+            element
+              .removeClass(classAscent)
+              .removeClass(classDescent);
+          } else {
+            index = newValue.reverse === true ? 2 : 1;
+            element
+              .removeClass(stateClasses[index % 2])
+              .addClass(stateClasses[index - 1]);
+          }
+        }, true);
+      }
+    };
+  }]);
+
+ng.module('smart-table')
+  .directive('stPagination', ['stConfig', function (stConfig) {
+    return {
+      restrict: 'EA',
+      require: '^stTable',
+      scope: {
+        stItemsByPage: '=?',
+        stDisplayedPages: '=?',
+        stPageChange: '&'
+      },
+      templateUrl: function (element, attrs) {
+        if (attrs.stTemplate) {
+          return attrs.stTemplate;
+        }
+        return stConfig.pagination.template;
+      },
+      link: function (scope, element, attrs, ctrl) {
+
+        scope.stItemsByPage = scope.stItemsByPage ? +(scope.stItemsByPage) : stConfig.pagination.itemsByPage;
+        scope.stDisplayedPages = scope.stDisplayedPages ? +(scope.stDisplayedPages) : stConfig.pagination.displayedPages;
+
+        scope.currentPage = 1;
+        scope.pages = [];
+
+        function redraw () {
+          var paginationState = ctrl.tableState().pagination;
+          var start = 1;
+          var end;
+          var i;
+          var prevPage = scope.currentPage;
+          scope.totalItemCount = paginationState.totalItemCount;
+          scope.currentPage = Math.floor(paginationState.start / paginationState.number) + 1;
+
+          start = Math.max(start, scope.currentPage - Math.abs(Math.floor(scope.stDisplayedPages / 2)));
+          end = start + scope.stDisplayedPages;
+
+          if (end > paginationState.numberOfPages) {
+            end = paginationState.numberOfPages + 1;
+            start = Math.max(1, end - scope.stDisplayedPages);
+          }
+
+          scope.pages = [];
+          scope.numPages = paginationState.numberOfPages;
+
+          for (i = start; i < end; i++) {
+            scope.pages.push(i);
+          }
+
+          if (prevPage !== scope.currentPage) {
+            scope.stPageChange({newPage: scope.currentPage});
+          }
+        }
+
+        //table state --> view
+        scope.$watch(function () {
+          return ctrl.tableState().pagination;
+        }, redraw, true);
+
+        //scope --> table state  (--> view)
+        scope.$watch('stItemsByPage', function (newValue, oldValue) {
+          if (newValue !== oldValue) {
+            scope.selectPage(1);
+          }
+        });
+
+        scope.$watch('stDisplayedPages', redraw);
+
+        //view -> table state
+        scope.selectPage = function (page) {
+          if (page > 0 && page <= scope.numPages) {
+            ctrl.slice((page - 1) * scope.stItemsByPage, scope.stItemsByPage);
+          }
+        };
+
+        if (!ctrl.tableState().pagination.number) {
+          ctrl.slice(0, scope.stItemsByPage);
+        }
+      }
+    };
+  }]);
+
+ng.module('smart-table')
+  .directive('stPipe', ['stConfig', '$timeout', function (config, $timeout) {
+    return {
+      require: 'stTable',
+      scope: {
+        stPipe: '='
+      },
+      link: {
+
+        pre: function (scope, element, attrs, ctrl) {
+
+          var pipePromise = null;
+
+          if (ng.isFunction(scope.stPipe)) {
+            ctrl.preventPipeOnWatch();
+            ctrl.pipe = function () {
+
+              if (pipePromise !== null) {
+                $timeout.cancel(pipePromise)
+              }
+
+              pipePromise = $timeout(function () {
+                scope.stPipe(ctrl.tableState(), ctrl);
+              }, config.pipe.delay);
+
+              return pipePromise;
+            }
+          }
+        },
+
+        post: function (scope, element, attrs, ctrl) {
+          ctrl.pipe();
+        }
+      }
+    };
+  }]);
+
+})(angular);
\ No newline at end of file
diff --git a/static/partial-sensor.html b/static/partial-sensor.html
index 3486a63..4503312 100644
--- a/static/partial-sensor.html
+++ b/static/partial-sensor.html
@@ -1,25 +1,43 @@
 <div class="container" ng-controller="sensorController">
     <div class="row">
-        <div class="col-lg-8">
+        <div class="col-md-12">
             <div class="box box-primary">
                 <div class="box-header with-border">
                     <h4>Summary
-                        <h4>
-                </div>
-                <div class="box-body">
-                    <table class="table table-striped system-status-table">
+                    </h4>
+                    <div class="pull-right">
+                        <select class="form-control show-tick" title="Rows on page" selectpicker ng-model="smartTablePageSize" ng-options="i for i in [5,10,25,50,100,500]">
+                            </select>
+                    </div>
+                    <div class="horizontal-scroll box-body"> </div>
+
+                    <table class="table table-striped system-status-table" st-table="smartTableData" st-safe-src="rowCollection">
                         <thead>
+                            <tr class="sortable ">
+                                <th class="table-id" st-sort="id" st-sort-default="true">#</th>
+                                <th st-sort="name">Name</th>
+                                <th st-sort="value">Value</th>
+                            </tr>
                             <tr>
-                                <th>Sensor</th>
-                                <th>Value</th>
+                                <th></th>
+                                <th><input st-search="name" placeholder="Search Name" class="input-sm form-control search-input" type="search" /></th>
+                                <th><input st-search="value" placeholder="Search Value" class="input-sm form-control search-input" type="search" /></th>
                             </tr>
                         </thead>
                         <tbody>
-                            <tr ng-animate="'animate'" ng-repeat="(sensor_name, value) in sensor_values">
-                                <td class="fit">{{ sensor_name }}</td>
-                                <td class="fit">{{ value }}</td>
+                            <tr ng-repeat="item in smartTableData">
+                                <td class="table-id">{{item.id}}</td>
+                                <td>{{item.name}}</td>
+                                <td>{{item.value}}</td>
                             </tr>
                         </tbody>
+                        <tfoot>
+                            <tr>
+                                <td colspan="6" class="text-center">
+                                    <div st-pagination="" st-items-by-page="smartTablePageSize" st-displayed-pages="7"></div>
+                                </td>
+                            </tr>
+                        </tfoot>
                     </table>
                 </div>
             </div>