From 74322dafcd484e9eeae36a39cdafccc7439b67af Mon Sep 17 00:00:00 2001
From: Benoit Lavenier <benoit.lavenier@e-is.pro>
Date: Fri, 3 Jan 2020 19:00:21 +0100
Subject: [PATCH] [enh] ES network: add a view to monitor Cesium+ pod network

---
 .gitignore                                    |   8 +-
 doc/development_guide.md                      |  15 +-
 package.json                                  |  14 +-
 scss/ionic.app.scss                           |  74 +-
 www/index.html                                |   8 +-
 www/js/config.js                              |   2 +-
 www/js/controllers/app-controllers.js         |   4 +-
 www/js/services/http-services.js              |   4 +-
 www/plugins/es/css/style.css                  |  42 +-
 www/plugins/es/i18n/locale-en-GB.json         |  10 +
 www/plugins/es/i18n/locale-en.json            |  10 +
 www/plugins/es/i18n/locale-fr-FR.json         |  12 +-
 .../es/js/controllers/document-controllers.js | 237 +++++-
 .../es/js/controllers/network-controllers.js  | 690 +++++++++++++++++-
 .../controllers/subscription-controllers.js   |  22 +-
 www/plugins/es/js/entities/peer.js            | 163 +++++
 www/plugins/es/js/services.js                 |   3 +-
 .../es/js/services/comment-services.js        |  15 +-
 .../es/js/services/document-services.js       | 115 +--
 www/plugins/es/js/services/http-services.js   | 211 ++++--
 www/plugins/es/js/services/modal-services.js  | 127 ++--
 .../es/js/services/network-services.js        | 645 ++++++++++++++++
 .../document/item_document_comment.html       |  44 ++
 .../document/item_document_profile.html       |  41 ++
 .../templates/document/items_documents.html   |  13 +-
 .../es/templates/document/list_documents.html |  13 +
 .../templates/network/item_content_peer.html  |  69 ++
 .../es/templates/network/items_peers.html     |  36 +
 .../network/lookup_popover_actions.html       |  31 +
 .../es/templates/network/modal_network.html   |  36 +
 .../templates/network/popover_endpoints.html  |  15 +
 .../es/templates/network/popover_network.html |  38 +
 .../templates/network/popover_peer_info.html  |  86 +++
 .../es/templates/network/view_network.html    |  79 ++
 .../es/templates/network/view_peer.html       | 137 ++++
 .../js/controllers/docstats-controllers.js    |   4 +-
 .../js/controllers/network-controllers.js     |   9 +
 .../network/view_es_network_extend.html       |   8 +
 .../network/view_network_extend.html          |   2 +-
 www/templates/menu.html                       |  10 +-
 yarn.lock                                     |   8 +-
 41 files changed, 2805 insertions(+), 305 deletions(-)
 create mode 100644 www/plugins/es/js/entities/peer.js
 create mode 100644 www/plugins/es/js/services/network-services.js
 create mode 100644 www/plugins/es/templates/document/item_document_comment.html
 create mode 100644 www/plugins/es/templates/document/item_document_profile.html
 create mode 100644 www/plugins/es/templates/document/list_documents.html
 create mode 100644 www/plugins/es/templates/network/item_content_peer.html
 create mode 100644 www/plugins/es/templates/network/items_peers.html
 create mode 100644 www/plugins/es/templates/network/lookup_popover_actions.html
 create mode 100644 www/plugins/es/templates/network/modal_network.html
 create mode 100644 www/plugins/es/templates/network/popover_endpoints.html
 create mode 100644 www/plugins/es/templates/network/popover_network.html
 create mode 100644 www/plugins/es/templates/network/popover_peer_info.html
 create mode 100644 www/plugins/es/templates/network/view_network.html
 create mode 100644 www/plugins/es/templates/network/view_peer.html
 create mode 100644 www/plugins/graph/templates/network/view_es_network_extend.html

diff --git a/.gitignore b/.gitignore
index ec5565e0..ab49affc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,14 +17,10 @@
 /package-lock.json
 /yarn-error.log
 
+/dist/web
+/dist/android
 
-/dist/*
-!/dist/desktop
-
-/hooks/playstore-config.json
-/hooks/minify-conf.json
 /hooks/uglify-config.json
-/hooks/after_prepare/ionic-minify.js
 /hooks/after_prepare/uglify.js
 
 /www/js/config.js
diff --git a/doc/development_guide.md b/doc/development_guide.md
index c4df1378..6f53040b 100644
--- a/doc/development_guide.md
+++ b/doc/development_guide.md
@@ -6,7 +6,7 @@ To build Cesium, you will have to:
  
   - Installing build tools:
 ```
- sudo apt-get install build-essential
+ sudo apt-get install git wget curl unzip build-essential software-properties-common ruby ruby-dev ruby-ffi gcc make
 ```
 
   - Installing [nvm](https://github.com/nvm-sh/nvm)
@@ -18,14 +18,14 @@ If you are using fish shell, there is a [dedicated plugin](https://github.com/jo
 
 > Then reload your terminal, for instance by executing the commande `bash`
 
-  - Configure NodeJS to use a version 6: (**WARNING**: upper version will NOT work !) 
+  - Configure NodeJS to use a version 10: (**WARNING**: upper version will NOT work !) 
 ```
-  nvm install 6
+  nvm install 10
 ```
       
-  - Installing node.js build tools:
+  - Installing node.js build tools, as global dependencies:
 ```
-   npm install -g yarn gulp cordova@9.0.0 ionic@1.7.16
+   npm install -g yarn gulp cordova ionic
 ```
    
 ## Get the source code and dependencies
@@ -40,7 +40,6 @@ If you are using fish shell, there is a [dedicated plugin](https://github.com/jo
   - Installing Cordova plugins (need for platforms specific builds)   
 ```
   ionic state restore
-  ionic browser add crosswalk@12.41.296.5
 ```
 
 - This should create a new directory `platforms/android`
@@ -66,10 +65,10 @@ If you are using fish shell, there is a [dedicated plugin](https://github.com/jo
  
   - Compiling and running Cesium:
 ```
-  ionic serve
+  npm start
 ```
  
-> or alternative: `ionic serve` 
+> or alternative: `yarn run start` or `ionic serve` 
 
   - Open a web browser at address: [localhost:8100](http://localhost:8100). The application should be running.
   
diff --git a/package.json b/package.json
index fd56f632..61ff9d11 100644
--- a/package.json
+++ b/package.json
@@ -9,8 +9,8 @@
     "url": "git@git.duniter.org:clients/cesium/cesium.git"
   },
   "scripts": {
-    "clean": "trash www/dist/** platforms/web platforms/**/*.apk  platforms/desktop/**/*.deb",
-    "postinstall": "node -e \"try { require('fs').symlinkSync(require('path').resolve('node_modules/@bower_components'), 'www/lib', 'junction') } catch (e) { }\" && rm -f hooks/minify-conf.json hooks/uglify-config.json hooks/after_prepare/ionic-minify.js  hooks/after_prepare/uglify.js",
+    "clean": "trash www/dist/** dist/web/* dist/desktop/**/*.deb platforms/android/**/*.apk",
+    "postinstall": "node -e \"try { require('fs').symlinkSync(require('path').resolve('node_modules/@bower_components'), 'www/lib', 'junction') } catch (e) { }\" && rm -f hooks/uglify-config.json hooks/after_prepare/uglify.js",
     "install-platforms": "ionic cordova prepare",
     "start": "ionic serve",
     "docker:build": "sudo docker build . -t cesium/release",
@@ -182,7 +182,9 @@
   ],
   "cordova": {
     "plugins": {
-      "cordova-plugin-camera": {},
+      "cordova-plugin-camera": {
+        "CAMERA_USAGE_DESCRIPTION": "Add picture to the user profile",
+        "PHOTOLIBRARY_USAGE_DESCRIPTION": "Take a picture for the user profile"},
       "cordova-plugin-console": {},
       "cordova-plugin-device": {},
       "cordova-plugin-dialogs": {},
@@ -200,7 +202,9 @@
       },
       "ionic-plugin-keyboard": {},
       "cordova-clipboard": {},
-      "cordova-plugin-ionic-webview": {}
+      "cordova-plugin-ionic-webview": {
+        "ANDROID_SUPPORT_ANNOTATIONS_VERSION": "27.+"
+      }
     },
     "platforms": [
       "ios",
@@ -211,4 +215,4 @@
   "engines": {
     "yarn": ">= 1.0.0"
   }
-}
\ No newline at end of file
+}
diff --git a/scss/ionic.app.scss b/scss/ionic.app.scss
index 015adfca..2b4b46d1 100644
--- a/scss/ionic.app.scss
+++ b/scss/ionic.app.scss
@@ -138,10 +138,10 @@ $screen-lg:                       1200px;
     padding: 16px !important;
   }
   .no-padding-xs {
-    padding: 0px !important;
+    padding: 0 !important;
   }
   .no-margin-xs {
-    margin: 0px !important;
+    margin: 0 !important;
   }
 }
 
@@ -169,10 +169,10 @@ $screen-lg:                       1200px;
     padding: 16px !important;
   }
   .no-padding-sm {
-    padding: 0px !important;
+    padding: 0 !important;
   }
   .no-margin-sm {
-    margin: 0px !important;
+    margin: 0 !important;
   }
 }
 
@@ -345,7 +345,7 @@ $screen-lg:                       1200px;
 
 @media screen and (max-width: $screen-xs-max) {
   .no-margin-xs {
-    margin: 0px !important;
+    margin: 0 !important;
   }
 }
 
@@ -482,7 +482,7 @@ html, body {
       height: 20px;
       line-height: 19px;
       max-width: 260px;
-      margin: 0px 5px;
+      margin: 0 5px;
       text-align: left;
     }
 
@@ -493,7 +493,7 @@ html, body {
       position: absolute;
       right: 0;
       top: 0;
-      margin: 0px 5px;
+      margin: 0 5px;
       display: block;
     }
   }
@@ -559,7 +559,7 @@ html, body {
     .title {
       color: black;
       text-align: left;
-      left: 0px !important;
+      left: 0 !important;
       font-size: 14px;
       line-height: 30px;
     }
@@ -620,7 +620,7 @@ html, body {
   .button.button-small {
     height: 30px;
     font-size: 12px;
-    padding: 0px 5px !important;
+    padding: 0 5px !important;
     line-height: 30px;
   }
 }
@@ -701,7 +701,7 @@ html, body {
 
 // Avoid to have not align button on bar-header
 .bar .buttons.pull-right, .bar .title + .button:last-child, .bar .title + .buttons, .bar > .button + .button:last-child, .bar > .button.pull-right {
-  top: 0px !important;
+  top: 0 !important;
 }
 
 // Avoid different between home view and other view (space between last button and left border)
@@ -709,7 +709,7 @@ html, body {
   padding-right: 5px !important;
 
   .buttons-right span {
-    margin-left: 0px !important;
+    margin-left: 0 !important;
   }
 }
 
@@ -861,7 +861,7 @@ html, body {
 }
 
 .no-padding {
-  padding: 0px !important;
+  padding: 0 !important;
 }
 
 .avatar-member {
@@ -1266,7 +1266,7 @@ body {
 
 .card .card-header {
   padding-top: 5px !important;
-  padding-bottom: 0px !important;
+  padding-bottom: 0 !important;
   min-height: 25px;
 }
 
@@ -1277,7 +1277,7 @@ body {
 .card .card-avatar,
 .card.card-avatar {
   .avatar {
-    box-shadow: 0px 3px 4px 0 rgba(0, 0, 0, 0.26);
+    box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.26);
     top: 7px;
     background-color: #D9D9D9;
   }
@@ -1340,7 +1340,7 @@ a.underline:hover,
 
   .avatar,
   .item-avatar .avatar {
-    box-shadow: 0px 2px 2px 0 rgba(0, 0, 0, 0.26);
+    box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.26);
     height: 30px !important;
     width: 30px !important;
     left: 5px !important;
@@ -1352,7 +1352,7 @@ a.underline:hover,
   }
 
   .card-footer {
-    padding-top: 0px;
+    padding-top: 0;
     padding-left: 42px !important;
   }
 }
@@ -1557,7 +1557,7 @@ a.underline:hover,
   .list.item-border-large {
     .item {
       border-bottom: solid 1px #ccc !important;
-      margin: 0px 0px 1px;
+      margin: 0 0px 1px;
     }
 
     .item-divider {
@@ -1573,11 +1573,11 @@ a.underline:hover,
 
 .list .item.item-small-height {
   padding-top: 2px;
-  padding-bottom: 0px;
+  padding-bottom: 0;
   min-height: 24px;
 
   .badge {
-    padding-top: 0px !important;
+    padding-top: 0 !important;
     top: inherit;
   }
 
@@ -1606,12 +1606,12 @@ a.underline:hover,
 }
 
 .form-error {
-  padding: 0px 16px;
+  padding: 0 16px;
   font-size: 12px;
   color: red;
   vertical-align: middle;
   text-align: end;
-  top: 0px;
+  top: 0;
   position: relative;
 }
 
@@ -1747,7 +1747,7 @@ $ionicon-var-badge-editable: $ionicon-var-edit + "\00a0";
 **********/
 
 .modal.modal-full-height {
-  bottom: 0px;
+  bottom: 0;
 }
 
 // Force modal as fullscreen, on xs screen
@@ -1755,7 +1755,7 @@ $ionicon-var-badge-editable: $ionicon-var-edit + "\00a0";
   .modal {
     top: 0 !important;
     left: 0 !important;
-    bottom: 0px;
+    bottom: 0;
     min-height: 100% !important;
     width: 100% !important;
   }
@@ -1763,7 +1763,7 @@ $ionicon-var-badge-editable: $ionicon-var-edit + "\00a0";
 
 @media screen and (max-width: $screen-sm-max) {
   .modal {
-    bottom: 0px;
+    bottom: 0;
 
     // Hide swiper on tablet
     .swiper-pagination {
@@ -1789,7 +1789,7 @@ $ionicon-var-badge-editable: $ionicon-var-edit + "\00a0";
 
 
 .modal.about .bar.bar-header .button + .title {
-  left: 0px !important; /* avoid title offset on large screens, if button are 'visible-xs')'*/
+  left: 0 !important; /* avoid title offset on large screens, if button are 'visible-xs')'*/
 }
 
 /**********
@@ -1934,8 +1934,8 @@ $ionicon-var-badge-editable: $ionicon-var-edit + "\00a0";
     .item.item-divider {
       min-height: 2px;
       height: 2px;
-      padding-top: 0px;
-      padding-bottom: 0px;
+      padding-top: 0;
+      padding-bottom: 0;
     }
 
     .item,
@@ -1953,7 +1953,7 @@ $ionicon-var-badge-editable: $ionicon-var-edit + "\00a0";
     }
 
     .item.item-complex {
-      padding-top: 0px;
+      padding-top: 0;
     }
 
     .item.item-button-right .button {
@@ -1965,14 +1965,14 @@ $ionicon-var-badge-editable: $ionicon-var-edit + "\00a0";
 
     // Hide footer on small screen
     ion-content.has-footer {
-      bottom: 0px !important; /*ignore footer*/
+      bottom: 0 !important; /*ignore footer*/
     }
 
     .bar-header {
       background-color: $positive-900-bg;
       color: #fff;
       height: 150px;
-      padding-right: 0px !important;
+      padding-right: 0 !important;
 
       .platform-ios.platform-cordova & {
         height: calc(constant(safe-area-inset-top) + 150px);
@@ -1984,7 +1984,7 @@ $ionicon-var-badge-editable: $ionicon-var-edit + "\00a0";
         box-shadow: none; // not need (define in bar-header)
 
         .content {
-          bottom: 0px;
+          bottom: 0;
         }
       }
     }
@@ -2169,7 +2169,7 @@ $ionicon-var-badge-editable: $ionicon-var-edit + "\00a0";
     text-align: center;
     position: relative;
     top: 8px;
-    height: 0px;
+    height: 0;
   }
 
   .icon.icon-bottom-right {
@@ -2195,7 +2195,7 @@ $ionicon-var-badge-editable: $ionicon-var-edit + "\00a0";
     width: 100%;
     bottom: 8px;
     text-align: center;
-    height: 0px;
+    height: 0;
   }
 }
 
@@ -2223,7 +2223,7 @@ $ionicon-var-badge-editable: $ionicon-var-edit + "\00a0";
     .col .button {
       max-width: inherit;
       width: 100%;
-      padding: 5px 0px;
+      padding: 5px 0;
       margin: 0;
     }
   }
@@ -2242,7 +2242,7 @@ $ionicon-var-badge-editable: $ionicon-var-edit + "\00a0";
     height: 35px;
     width: 35px;
     position: relative;
-    left: 0px;
+    left: 0;
     top: 4px;
     border: solid 1px #D9D9D9;
   }
@@ -2255,7 +2255,7 @@ $ionicon-var-badge-editable: $ionicon-var-edit + "\00a0";
     height: 31px;
     width: 31px;
     position: relative;
-    left: 0px;
+    left: 0;
     top: 6px;
   }
 }
@@ -2321,7 +2321,7 @@ $ionicon-var-badge-editable: $ionicon-var-edit + "\00a0";
 
 .row-header {
   border-bottom: solid 1px #ccc !important;
-  margin: 0px;
+  margin: 0;
   min-height: 28px !important;
 }
 
diff --git a/www/index.html b/www/index.html
index 7f34f0bb..d742b7bd 100644
--- a/www/index.html
+++ b/www/index.html
@@ -26,7 +26,6 @@
     <link rel="stylesheet" type="text/css" href="lib/chart.js/dist/Chart.min.css">
     <link rel="stylesheet" type="text/css" href="dist/dist_css/plugins/es/css/style.css">
     <link rel="stylesheet" type="text/css" href="dist/dist_css/plugins/graph/css/style.css">
-    <link rel="stylesheet" type="text/css" href="dist/dist_css/plugins/graph/css/style.css">
     <link rel="stylesheet" type="text/css" href="dist/dist_css/plugins/map/css/style.css">
     <!--endRemoveIf(no-plugin)-->
     <!-- endbuild -->
@@ -48,6 +47,7 @@
     <meta property="og:locale:alternate" content="fr_FR" />
     <meta property="og:locale:alternate" content="it_IT" />
     <meta property="og:locale:alternate" content="nl_NL" />
+    <meta property="og:locale:alternate" content="eo_EO" />
 
     <!--endRemoveIf(device)-->
 
@@ -72,11 +72,9 @@
     <script src="lib/numeral/languages/es.js"></script>
     <script src="lib/numeral/languages/it.js"></script>
     <script src="js/vendor/numeral.eo.js"></script>
-
     <script src="lib/socket.io-client/dist/socket.io.min.js"></script>
     <script src="lib/underscore/underscore-min.js"></script>
     <script src="lib/qrcode.js/qrcode.js"></script>
-
     <script src="lib/aes-js/index.js"></script>
     <script src="lib/chart.js/dist/Chart.min.js"></script>
 
@@ -191,6 +189,7 @@
     <script src="dist/dist_js/plugins/es/js/entities/notification.js"></script>
     <script src="dist/dist_js/plugins/es/js/entities/comment.js"></script>
     <script src="dist/dist_js/plugins/es/js/entities/invitation.js"></script>
+    <script src="dist/dist_js/plugins/es/js/entities/peer.js"></script>
     <script src="dist/dist_js/plugins/es/js/services.js"></script>
     <script src="dist/dist_js/plugins/es/js/services/comment-services.js"></script>
     <script src="dist/dist_js/plugins/es/js/services/http-services.js"></script>
@@ -211,6 +210,7 @@
     <script src="dist/dist_js/plugins/es/js/services/tx-services.js"></script>
     <script src="dist/dist_js/plugins/es/js/services/geo-services.js"></script>
     <script src="dist/dist_js/plugins/es/js/services/document-services.js"></script>
+    <script src="dist/dist_js/plugins/es/js/services/network-services.js"></script>
     <script src="dist/dist_js/plugins/es/js/controllers/common-controllers.js"></script>
     <script src="dist/dist_js/plugins/es/js/controllers/app-controllers.js"></script>
     <script src="dist/dist_js/plugins/es/js/controllers/settings-controllers.js"></script>
@@ -229,7 +229,6 @@
     <script src="dist/dist_js/plugins/es/js/controllers/document-controllers.js"></script>
 
     <!-- Graph plugin -->
-    <!--removeIf(ubuntu)--> <!-- FIXME: issue #463 -->
     <script src="dist/dist_js/plugins/graph/js/plugin.js"></script>
     <script src="dist/dist_js/plugins/graph/js/services.js"></script>
     <script src="dist/dist_js/plugins/graph/js/services/data-services.js"></script>
@@ -241,7 +240,6 @@
     <script src="dist/dist_js/plugins/graph/js/controllers/account-controllers.js"></script>
     <script src="dist/dist_js/plugins/graph/js/controllers/docstats-controllers.js"></script>
     <script src="dist/dist_js/plugins/graph/js/controllers/synchro-controllers.js"></script>
-    <!--endRemoveIf(ubuntu)-->
 
     <!-- Map plugin -->
     <script src="dist/dist_js/plugins/map/js/plugin.js"></script>
diff --git a/www/js/config.js b/www/js/config.js
index c7197691..ca638509 100644
--- a/www/js/config.js
+++ b/www/js/config.js
@@ -94,7 +94,7 @@ angular.module("cesium.config", [])
 		}
 	},
 	"version": "1.4.18",
-	"build": "2019-12-28T12:21:59.344Z",
+	"build": "2019-12-31T11:21:28.655Z",
 	"newIssueUrl": "https://git.duniter.org/clients/cesium-grp/cesium/issues/new"
 })
 
diff --git a/www/js/controllers/app-controllers.js b/www/js/controllers/app-controllers.js
index c2e765ce..90963f2b 100644
--- a/www/js/controllers/app-controllers.js
+++ b/www/js/controllers/app-controllers.js
@@ -559,8 +559,8 @@ function HomeController($scope, $state, $timeout, $ionicHistory, $translate, UIU
    * Catch click for quick fix
    * @param event
    */
-  $scope.doQuickFix = function(event) {
-    if (event == 'settings') {
+  $scope.doQuickFix = function(action) {
+    if (action === 'settings') {
       $ionicHistory.nextViewOptions({
         historyRoot: true
       });
diff --git a/www/js/services/http-services.js b/www/js/services/http-services.js
index 94e57bfc..b332d635 100644
--- a/www/js/services/http-services.js
+++ b/www/js/services/http-services.js
@@ -158,7 +158,9 @@ angular.module('cesium.http.services', ['cesium.cache.services'])
     timeout = timeout || csSettings.data.timeout;
 
     function _waitOpen(self) {
-      if (!self.delegate) throw new Error('Websocket not opened');
+      if (!self.delegate) {
+        throw new Error('Websocket {0} was closed!'.format(uri));
+      }
       if (self.delegate.readyState == 1) {
         return $q.when(self.delegate);
       }
diff --git a/www/plugins/es/css/style.css b/www/plugins/es/css/style.css
index a1279102..2ab89803 100644
--- a/www/plugins/es/css/style.css
+++ b/www/plugins/es/css/style.css
@@ -81,22 +81,22 @@
 }
 
 .row-record .col-text-wrap {
-    padding: 0;
-    margin: 0;
+  padding: 0;
+  margin: 0;
 }
 .row-record .col .text-wrap {
-    height: 70px;
-    white-space: normal;
-    position: relative;
-    word-wrap: break-word !important;
-    overflow: hidden !important;
-    text-overflow: ellipsis;
-    -o-text-overflow: ellipsis;
-    -webkit-hyphens: auto;
-    -moz-hyphens: auto;
-    -ms-hyphens: auto;
-    -o-hyphens: auto;
-    hyphens: auto;
+  height: 70px;
+  white-space: normal;
+  position: relative;
+  word-wrap: break-word !important;
+  overflow: hidden !important;
+  text-overflow: ellipsis;
+  -o-text-overflow: ellipsis;
+  -webkit-hyphens: auto;
+  -moz-hyphens: auto;
+  -ms-hyphens: auto;
+  -o-hyphens: auto;
+  hyphens: auto;
 }
 
 /* Source: See doc: http://stackoverflow.com/questions/15814346 */
@@ -243,6 +243,20 @@
 }
 
 
+/**********
+   Document
+**********/
+
+.item-document.compacted {
+  min-height: 16px !important;
+  max-height: 16px !important;
+  border-bottom: 0 !important;
+}
+
+.item-document.compacted .col{
+  padding-top: 1px;
+}
+
 /**********
    Add specific Icons
 **********/
diff --git a/www/plugins/es/i18n/locale-en-GB.json b/www/plugins/es/i18n/locale-en-GB.json
index 4ab28fa5..e45f63b4 100644
--- a/www/plugins/es/i18n/locale-en-GB.json
+++ b/www/plugins/es/i18n/locale-en-GB.json
@@ -413,6 +413,7 @@
       "TITLE": "Document search",
       "BTN_ACTIONS": "Actions",
       "SEARCH_HELP": "issuer:AAA*, time:1508406169",
+      "LAST_DOCUMENTS_DOTS": "Last documents:",
       "LAST_DOCUMENTS": "Last documents",
       "SHOW_QUERY": "Show query",
       "HIDE_QUERY": "Hide query",
@@ -421,6 +422,8 @@
       "HEADER_RECIPIENT": "Recipient",
       "READ": "Read",
       "BTN_REMOVE": "Delete this document",
+      "BTN_COMPACT": "Compact",
+      "HAS_REGISTERED": "create or edit his profile",
       "POPOVER_ACTIONS": {
         "TITLE": "Actions",
         "REMOVE_ALL": "Delete these documents..."
@@ -470,6 +473,13 @@
   "ES_WALLET": {
     "ERROR": {
       "RECIPIENT_IS_MANDATORY": "A recipient is required for encryption."
+    },
+    "ES_PEER": {
+      "NAME": "Name",
+      "DOCUMENTS": "Documents",
+      "SOFTWARE": "Software",
+      "DOCUMENT_COUNT": "Number of documents",
+      "EMAIL_SUBSCRIPTION_COUNT": "{{emailSubscription}} subscribers to email notification"
     }
   },
   "EVENT": {
diff --git a/www/plugins/es/i18n/locale-en.json b/www/plugins/es/i18n/locale-en.json
index 4ab28fa5..e45f63b4 100644
--- a/www/plugins/es/i18n/locale-en.json
+++ b/www/plugins/es/i18n/locale-en.json
@@ -413,6 +413,7 @@
       "TITLE": "Document search",
       "BTN_ACTIONS": "Actions",
       "SEARCH_HELP": "issuer:AAA*, time:1508406169",
+      "LAST_DOCUMENTS_DOTS": "Last documents:",
       "LAST_DOCUMENTS": "Last documents",
       "SHOW_QUERY": "Show query",
       "HIDE_QUERY": "Hide query",
@@ -421,6 +422,8 @@
       "HEADER_RECIPIENT": "Recipient",
       "READ": "Read",
       "BTN_REMOVE": "Delete this document",
+      "BTN_COMPACT": "Compact",
+      "HAS_REGISTERED": "create or edit his profile",
       "POPOVER_ACTIONS": {
         "TITLE": "Actions",
         "REMOVE_ALL": "Delete these documents..."
@@ -470,6 +473,13 @@
   "ES_WALLET": {
     "ERROR": {
       "RECIPIENT_IS_MANDATORY": "A recipient is required for encryption."
+    },
+    "ES_PEER": {
+      "NAME": "Name",
+      "DOCUMENTS": "Documents",
+      "SOFTWARE": "Software",
+      "DOCUMENT_COUNT": "Number of documents",
+      "EMAIL_SUBSCRIPTION_COUNT": "{{emailSubscription}} subscribers to email notification"
     }
   },
   "EVENT": {
diff --git a/www/plugins/es/i18n/locale-fr-FR.json b/www/plugins/es/i18n/locale-fr-FR.json
index c5c84bf3..cdf39e9c 100644
--- a/www/plugins/es/i18n/locale-fr-FR.json
+++ b/www/plugins/es/i18n/locale-fr-FR.json
@@ -463,7 +463,8 @@
     "LOOKUP": {
       "TITLE": "Recherche de documents",
       "BTN_ACTIONS": "Actions",
-      "SEARCH_HELP": "Emetteur:AAA*, temps:1508406169",
+      "SEARCH_HELP": "issuer:AAA*, time:1508406169",
+      "LAST_DOCUMENTS_DOTS": "Derniers documents :",
       "LAST_DOCUMENTS": "Derniers documents",
       "SHOW_QUERY": "Voir la requête",
       "HIDE_QUERY": "Masquer la requête",
@@ -472,6 +473,8 @@
       "HEADER_RECIPIENT": "Destinataire",
       "READ": "Lu",
       "BTN_REMOVE": "Supprimer ce document",
+      "BTN_COMPACT": "Compacter",
+      "HAS_REGISTERED": "a créé ou modifié son profil",
       "POPOVER_ACTIONS": {
         "TITLE": "Actions",
         "REMOVE_ALL": "Supprimer ces documents..."
@@ -523,6 +526,13 @@
       "RECIPIENT_IS_MANDATORY": "Un destinataire est obligatoire pour le chiffrement."
     }
   },
+  "ES_PEER": {
+    "NAME": "Nom",
+    "DOCUMENTS": "Documents",
+    "SOFTWARE": "Logiciel",
+    "DOCUMENT_COUNT": "Nombre de documents",
+    "EMAIL_SUBSCRIPTION_COUNT": "{{emailSubscription}} abonnés aux notifications par email"
+  },
   "EVENT": {
     "NODE_STARTED": "Votre noeud ES API <b>{{params[0]}}</b> est démarré",
     "NODE_BMA_DOWN": "Le noeud <b>{{params[0]}}:{{params[1]}}</b> (utilisé par votre noeud ES API) est <b>injoignable</b>.",
diff --git a/www/plugins/es/js/controllers/document-controllers.js b/www/plugins/es/js/controllers/document-controllers.js
index 3e9f5212..7d5d335c 100644
--- a/www/plugins/es/js/controllers/document-controllers.js
+++ b/www/plugins/es/js/controllers/document-controllers.js
@@ -22,45 +22,52 @@ angular.module('cesium.es.document.controllers', ['cesium.es.services'])
 
   .controller('ESDocumentLookupCtrl', ESDocumentLookupController)
 
+  .controller('ESLastDocumentsCtrl', ESLastDocumentsController)
 ;
 
 function ESDocumentLookupController($scope, $ionicPopover, $location, $timeout,
                                     csSettings, csWallet, UIUtils, esHttp, esDocument) {
   'ngInject';
 
-  $scope.search = {
+  $scope.search = $scope.search || {
     loading: true,
-    hasMore: true,
+    hasMore: false,
     text: undefined,
     index: 'invitation',
     type: 'certification',
-    results: undefined,
+    results: [],
     sort: 'time',
-    asc: false
+    asc: false,
+    loadingMore: false
   };
   $scope.entered = false;
   $scope.searchTextId = 'documentSearchText';
   $scope.ionItemClass = 'item-border-large';
-  $scope.defaultSizeLimit = UIUtils.screen.isSmall() ? 50 : 100;
+  $scope.defaultSizeLimit = $scope.defaultSizeLimit || (UIUtils.screen.isSmall() ? 50 : 100);
   $scope.helptipPrefix = 'helptip-document';
-
-  $scope.$on('$ionicView.enter', function(e, state) {
-
+  $scope.compactMode = angular.isDefined($scope.compactMode) ? $scope.compactMode : true;
+  $scope._source = $scope._source || ["issuer", "hash", "time", "creationTime", "title", "message"];
+  $scope.showHeaders = angular.isDefined($scope.showHeaders) ? $scope.showHeaders : true;
+
+  /**
+   * Enter into the view
+   * @param e
+   * @param state
+   */
+  $scope.enter = function(e, state) {
     if (!$scope.entered) {
       $scope.entered = true;
       $scope.search.index = state.stateParams && state.stateParams.index || $scope.search.index;
       $scope.search.type = state.stateParams && state.stateParams.type || $scope.search.type;
       $scope.search.text = state.stateParams && state.stateParams.q || $scope.search.text;
-
       $scope.search.last = !$scope.search.text;
       $scope.load();
     }
     $scope.expertMode = angular.isDefined($scope.expertMode) ? $scope.expertMode : !UIUtils.screen.isSmall() && csSettings.data.expertMode;
-  });
-
-  $scope.load = function(size, offset) {
-    if ($scope.search.error) return;
+  };
+  $scope.$on('$ionicView.enter', $scope.enter);
 
+  $scope.computeOptions = function(offset, size) {
     var options  = {
       index: $scope.search.index,
       type: $scope.search.type,
@@ -77,34 +84,44 @@ function ESDocumentLookupController($scope, $ionicPopover, $location, $timeout,
       options.sort = {time:'desc'};
     }
 
-    $scope.search.loading = true;
+    // Included fields
+    options._source = options._source || $scope._source;
+
+    return options;
+  };
+
+  $scope.load = function(offset, size, silent) {
+    if ($scope.search.error) return;
+
+    var options = $scope.computeOptions(offset, size);
+
+    $scope.search.loading = !silent;
 
     var searchFn =  $scope.search.last ?
       esDocument.search(options) :
       esDocument.searchText($scope.search.text||'', options);
     return searchFn
       .then(function(res) {
-        $scope.search.results = res.hits;
+        if (!offset) {
+          $scope.search.results = res.hits;
+          $scope.search.took = res.took;
+        }
+        else {
+          $scope.search.results = $scope.search.results.concat(res.hits);
+        }
         $scope.search.total = res.total;
-        $scope.search.took = res.took;
 
         UIUtils.loading.hide();
         $scope.search.loading = false;
+        $scope.search.hasMore = res.hits && res.hits.length > 0 && res.total > $scope.search.results.length;
 
-        if (res.hits && res.hits.length > 0) {
-          $scope.motion.show({selector: '.list .item.item-document'});
-          $scope.search.hasMore = res.total > $scope.search.results.length;
-        }
-        else {
-          $scope.search.hasMore = false;
-        }
-
-        $scope.$broadcast('$$rebind::rebind'); // notify binder
+        $scope.updateView();
       })
       .catch(function(err) {
         $scope.search.results = [];
         $scope.search.loading = false;
         $scope.search.error = true;
+        $scope.search.hasMore = false;
         UIUtils.onError('DOCUMENT.ERROR.LOAD_DOCUMENTS_FAILED')(err)
           .then(function() {
             $scope.search.error = false;
@@ -112,6 +129,13 @@ function ESDocumentLookupController($scope, $ionicPopover, $location, $timeout,
       });
   };
 
+  $scope.updateView = function() {
+    if ($scope.motion && $scope.search.results && $scope.search.results.length) {
+      $scope.motion.show({selector: '.list .item.item-document'});
+    }
+    $scope.$broadcast('$$rebind::rebind'); // notify binder
+  };
+
   $scope.doSearchText = function() {
     $scope.search.last = $scope.search.text ? false : true;
     return $scope.load()
@@ -147,9 +171,10 @@ function ESDocumentLookupController($scope, $ionicPopover, $location, $timeout,
       });
   };
 
-  $scope.remove = function(index, options) {
+  $scope.remove = function($event, index) {
     var doc = $scope.search.results[index];
-    if (!doc) return;
+    if (!doc || $event.defaultPrevented) return;
+    $event.stopPropagation();
 
     UIUtils.alert.confirm('DOCUMENT.CONFIRM.REMOVE')
       .then(function(confirm) {
@@ -171,6 +196,18 @@ function ESDocumentLookupController($scope, $ionicPopover, $location, $timeout,
     return $scope.openLink(event, url);
   };
 
+  $scope.toggleCompactMode = function() {
+    $scope.compactMode = !$scope.compactMode;
+    $scope.updateView();
+
+    // Workaround to re-initialized the <ion-infinite-loop>
+    if (!$scope.search.hasMore && $scope.search.results.length && $scope.search.type == 'last') {
+      $timeout(function() {
+        $scope.search.hasMore = true;
+      }, 500);
+    }
+  };
+
   $scope.toggleSort = function(sort){
     if ($scope.search.sort === sort && !$scope.search.asc) {
       $scope.search.asc = undefined;
@@ -183,6 +220,71 @@ function ESDocumentLookupController($scope, $ionicPopover, $location, $timeout,
     $scope.load();
   };
 
+  $scope.showMore = function() {
+    if ($scope.search.loading) return;
+    $scope.search.loadingMore = true;
+    $scope.load(
+      $scope.search.results.length, // from
+      $scope.defaultSizeLimit,
+      true/*silent*/)
+      .then(function() {
+        $scope.search.loadingMore = false;
+        $scope.$broadcast('scroll.infiniteScrollComplete');
+      });
+  };
+
+  $scope.startListenChanges = function() {
+    var now = Date.now();
+    var source = $scope.search.index + '/' + $scope.search.type;
+    var wsChanges = esHttp.websocket.changes(source);
+    return wsChanges.open()
+      .then(function(){
+        console.debug("[ES] [document] Websocket opened in {0} ms".format(Date.now()- now));
+        wsChanges.on(function(change) {
+          if (!$scope.search.last || !change) return; // ignore
+          esDocument.fromHit(change)
+            .then(function(doc) {
+              if (change._operation === 'DELETE') {
+                $scope.onDeleteDocument(doc);
+              }
+              else {
+                $scope.onNewDocument(doc);
+              }
+            });
+        });
+      });
+  };
+
+  $scope.onNewDocument = function(document) {
+    if (!$scope.search.last || $scope.search.loading) return; // skip
+    console.debug("[ES] [document] Detected new document: ", document);
+    var index = _.findIndex($scope.search.results, {id: document.id, index: document.index, type: document.type});
+    if (index < 0) {
+      $scope.search.total++;
+      $scope.search.results.splice(0, 0, document);
+    }
+    else {
+      document.updated = true;
+      $timeout(function() {
+        document.updated = false;
+      }, 2000);
+      $scope.search.results.splice(index, 1, document);
+    }
+    $scope.updateView();
+  };
+
+  $scope.onDeleteDocument = function(document) {
+    if (!$scope.search.last || $scope.search.loading) return; // skip
+    $timeout(function() {
+      var index = _.findIndex($scope.search.results, {id: document.id, index: document.index, type: document.type});
+      if (index < 0) return; // skip if not found
+      console.debug("[ES] [document] Detected document deletion: ", document);
+      $scope.search.results.splice(index, 1);
+      $scope.search.total--;
+      $scope.updateView();
+    }, 750);
+  };
+
   /* -- Modals -- */
 
   /* -- Popover -- */
@@ -207,14 +309,19 @@ function ESDocumentLookupController($scope, $ionicPopover, $location, $timeout,
 
   /* -- watch events -- */
 
-  // Watch unauth
-  $scope.onUnauth = function() {
+  $scope.resetData = function() {
+    if ($scope.search.loading) return;
+    console.debug("[ES] [document] Resetting data (settings or account may have changed)");
     // Reset all data
-    $scope.search.results = undefined;
+    $scope.search.results = [];
     $scope.search.loading = false;
+    $scope.search.total = undefined;
+    $scope.search.loadingMore = false;
     $scope.entered = false;
+    delete $scope.search.limit;
   };
-  csWallet.api.data.on.unauth($scope, $scope.onUnauth);
+
+  csWallet.api.data.on.unauth($scope, $scope.resetData);
 
   // for DEV only
   /*$timeout(function() {
@@ -222,3 +329,71 @@ function ESDocumentLookupController($scope, $ionicPopover, $location, $timeout,
    }, 900);
    */
 }
+
+
+function ESLastDocumentsController($scope, $controller, $timeout, $state) {
+  'ngInject';
+
+  $scope.search = {
+    loading: true,
+    hasMore: true,
+    text: undefined,
+    index: 'user,page,group', type: 'profile,record,comment',
+    results: undefined,
+    sort: 'time',
+    asc: false
+  };
+  $scope.expertMode = false;
+  $scope.defaultSizeLimit = 20;
+  $scope._source = ["issuer", "hash", "time", "creationTime", "title", "avatar._content_type", "city", "message", "record"];
+  $scope.showHeaders = false;
+
+  // Initialize the super class and extend it.
+  angular.extend(this, $controller('ESDocumentLookupCtrl', {$scope: $scope}));
+  $scope.$on('$ionicParentView.enter', $scope.enter);
+
+  $scope.selectDocument = function(event, doc) {
+    if (!doc || !event || event.defaultPrevented) return;
+    event.stopPropagation();
+
+    if (doc.index === "user" && doc.type === "profile") {
+      $state.go('app.wot_identity', {pubkey: doc.pubkey, uid: doc.name});
+    }
+    else if (doc.index === "page" && doc.type === "record") {
+      $state.go('app.view_page', {title: doc.title, id: doc.id});
+      return
+    }
+    else if (doc.index === "group" && doc.type === "record") {
+      $state.go('app.view_group', {title: doc.title, id: doc.id});
+      return
+    }
+    console.warn("Click on this kind of document not implement yet!", doc)
+  };
+
+  // Override parent function computeOptions
+  var inheritedComputeOptions = $scope.computeOptions;
+  $scope.computeOptions = function(offset, size){
+    // Cal inherited function
+    var options = inheritedComputeOptions(offset, size);
+
+    if (!options.sort || options.sort.time) {
+      var side = options.sort && options.sort.time || side;
+      options.sort = [
+        //{'creationTime': side},
+        {'time': side}
+      ];
+    }
+
+    options._source = options._source || $scope._source;
+    options.getTimeFunction = function(doc) {
+      doc.time = doc.creationTime || doc.time;
+      return doc.time;
+    };
+    return options;
+  };
+
+  // Listen for changes
+  $timeout(function() {
+    $scope.startListenChanges();
+  }, 1000);
+}
diff --git a/www/plugins/es/js/controllers/network-controllers.js b/www/plugins/es/js/controllers/network-controllers.js
index cbf23462..ebaf126b 100644
--- a/www/plugins/es/js/controllers/network-controllers.js
+++ b/www/plugins/es/js/controllers/network-controllers.js
@@ -1,20 +1,688 @@
 
 angular.module('cesium.es.network.controllers', ['cesium.es.services'])
 
-  .config(function(PluginServiceProvider, csConfig) {
-    'ngInject';
-
-    var enable = csConfig.plugins && csConfig.plugins.es;
-    if (enable) {
-      PluginServiceProvider.extendState('app.network', {
-        points: {
-          'buttons': {
-            templateUrl: "plugins/es/templates/network/view_network_extend.html",
-            controller: 'ESExtensionCtrl'
+.config(function(PluginServiceProvider, csConfig) {
+  'ngInject';
+
+  var enable = csConfig.plugins && csConfig.plugins.es;
+  if (enable) {
+    PluginServiceProvider.extendState('app.network', {
+      points: {
+        'buttons': {
+          templateUrl: "plugins/es/templates/network/view_network_extend.html",
+          controller: 'ESExtensionCtrl'
+        }
+      }
+    })
+    ;
+  }
+})
+
+.config(function($stateProvider) {
+  'ngInject';
+
+  $stateProvider
+
+    .state('app.es_network', {
+      url: "/network/data?online&expert",
+      cache: false, // fix #766
+      views: {
+        'menuContent': {
+          templateUrl: "plugins/es/templates/network/view_network.html",
+          controller: 'ESNetworkLookupCtrl'
+        }
+      },
+      data: {
+        silentLocationChange: true
+      }
+    })
+
+    .state('app.view_es_peer', {
+      url: "/data/network/peer/:server?ssl&tor",
+      cache: false,
+      views: {
+        'menuContent': {
+          templateUrl: "plugins/es/templates/network/view_peer.html",
+          controller: 'ESPeerViewCtrl'
+        }
+      },
+      data: {
+        preferHttp: true // avoid HTTPS if config has httpsMode=clever
+      }
+    });
+})
+
+  .controller('ESNetworkLookupCtrl', ESNetworkLookupController)
+
+  .controller('ESPeerViewCtrl', ESPeerViewController)
+
+  .controller('ESNetworkLookupModalCtrl', ESNetworkLookupModalController)
+
+  .controller('ESNetworkLookupPopoverCtrl', ESNetworkLookupPopoverController)
+
+  .controller('ESPeerInfoPopoverCtrl', ESPeerInfoPopoverController)
+
+;
+
+function ESNetworkLookupController($scope,  $state, $location, $ionicPopover, $window, $translate,
+                                 esHttp, UIUtils, csConfig, csSettings, csCurrency, esNetwork, csWot) {
+  'ngInject';
+
+  $scope.networkStarted = false;
+  $scope.ionItemClass = '';
+  $scope.expertMode = csSettings.data.expertMode && !UIUtils.screen.isSmall();
+  $scope.isHttps = ($window.location.protocol === 'https:');
+  $scope.search = {
+    text: '',
+    loading: true,
+    online: true,
+    results: [],
+    endpointFilter: esHttp.constants.ES_USER_API,
+    sort : undefined,
+    asc: true
+  };
+  $scope.listeners = [];
+  $scope.helptipPrefix = 'helptip-network';
+  $scope.enableLocationHref = true; // can be overrided by sub-controller (e.g. popup)
+
+  $scope.removeListeners = function() {
+    if ($scope.listeners.length) {
+      console.debug("[ES] [network] Closing listeners");
+      _.forEach($scope.listeners, function(remove){
+        remove();
+      });
+      $scope.listeners = [];
+    }
+  };
+
+  /**
+   * Enter in view
+   */
+  $scope.enter = function(e, state) {
+    if ($scope.networkStarted) return;
+    $scope.networkStarted = true;
+    $scope.search.loading = true;
+    csCurrency.get()
+      .then(function (currency) {
+        if (currency) {
+          $scope.node = !esHttp.node.same(currency.node.host, currency.node.port) ?
+            esHttp.instance(currency.node.host, currency.node.port) : esHttp;
+          if (state && state.stateParams) {
+            if (state.stateParams.online == 'true') {
+              $scope.search.online = true;
+            }
+            if (state.stateParams.expert) {
+              $scope.expertMode = (state.stateParams.expert == 'true');
+            }
           }
+          $scope.load();
         }
       })
-      ;
+      .catch(function(err) {
+        UIUtils.onError('ERROR.GET_CURRENCY_FAILED')(err);
+        $scope.networkStarted = false;
+      });
+  };
+  $scope.$on('$ionicParentView.enter', $scope.enter);
+
+  /**
+   * Leave the view
+   */
+  $scope.leave = function() {
+    if (!$scope.networkStarted) return;
+    $scope.removeListeners();
+    esNetwork.close();
+    $scope.networkStarted = false;
+    $scope.search.loading = true;
+  };
+  $scope.$on('$ionicView.beforeLeave', $scope.leave);
+  $scope.$on('$ionicParentView.beforeLeave', $scope.leave);
+  $scope.$on('$destroy', $scope.leave);
+
+
+  $scope.computeOptions = function() {
+    var options = {
+      filter: {
+        member: (!$scope.search.type || $scope.search.type === 'member'),
+        mirror: (!$scope.search.type || $scope.search.type === 'mirror'),
+        endpointFilter : (angular.isDefined($scope.search.endpointFilter) ? $scope.search.endpointFilter : null),
+        online: $scope.search.online && true
+      },
+      sort: {
+        type : $scope.search.sort,
+        asc : $scope.search.asc
+      },
+      expertMode: $scope.expertMode,
+      // larger timeout when on expert mode
+      timeout: csConfig.timeout && ($scope.expertMode ? (csConfig.timeout / 10) : (csConfig.timeout / 100))
+    };
+    return options;
+  };
+
+  $scope.load = function() {
+
+    if ($scope.search.loading){
+      esNetwork.start($scope.node, $scope.computeOptions());
+
+      // Catch event on new peers
+      $scope.refreshing = false;
+      $scope.listeners.push(
+        esNetwork.api.data.on.changed($scope, function(data){
+          if (!$scope.refreshing) {
+            $scope.refreshing = true;
+            csWot.extendAll(data.peers)
+              .then(function() {
+                // Avoid to refresh if view has been leaving
+                if ($scope.networkStarted) {
+                  $scope.updateView(data);
+                }
+                $scope.refreshing = false;
+              });
+          }
+        }));
+    }
+
+    // Show help tip
+    $scope.showHelpTip();
+  };
+
+  $scope.updateView = function(data) {
+    console.debug("[peers] Updating UI");
+    $scope.$broadcast('$$rebind::' + 'rebind'); // force data binding
+    $scope.search.results = data.peers;
+    $scope.search.memberPeersCount = data.memberPeersCount;
+    // Always tru if network not started (e.g. after leave+renter the view)
+    $scope.search.loading = !$scope.networkStarted || esNetwork.isBusy();
+    if ($scope.motion && $scope.search.results && $scope.search.results.length > 0) {
+      $scope.motion.show({selector: '.item-peer'});
+    }
+    if (!$scope.loading) {
+      $scope.$broadcast('$$rebind::' + 'rebind'); // force data binding
+    }
+  };
+
+  $scope.refresh = function() {
+    // Network
+    $scope.search.loading = true;
+    esNetwork.loadPeers();
+  };
+
+  $scope.sort = function() {
+    $scope.search.loading = true;
+    $scope.refreshing = true;
+    esNetwork.sort($scope.computeOptions());
+    $scope.updateView(esNetwork.data);
+  };
+
+  $scope.toggleOnline = function(online){
+    $scope.hideActionsPopover();
+    $scope.search.online = (online !== false);
+    esNetwork.close();
+    $scope.search.loading = true;
+    $scope.load();
+
+    // Update location href
+    if ($scope.enableLocationHref) {
+      $location.search({online: $scope.search.online}).replace();
+    }
+  };
+
+  $scope.toggleSearchEndpoint = function(endpoint){
+    $scope.hideActionsPopover();
+    if ($scope.search.endpointFilter === endpoint || endpoint === null) {
+      $scope.search.endpointFilter = null;
+    }
+    else {
+      $scope.search.endpointFilter = endpoint;
+    }
+    $scope.sort();
+  };
+
+  $scope.toggleSort = function(sort){
+    if ($scope.search.sort === sort && !$scope.search.asc) {
+      $scope.search.asc = undefined;
+      $scope.search.sort = undefined;
+    }
+    else {
+      $scope.search.asc = ($scope.search.sort === sort) ? !$scope.search.asc : true;
+      $scope.search.sort = sort;
+    }
+    $scope.sort();
+  };
+
+  $scope.selectPeer = function(peer) {
+    // Skip offline
+    if (!peer.online ) return;
+
+    var stateParams = {server: peer.getServer()};
+    if (peer.isSsl()) {
+      stateParams.ssl = true;
+    }
+    if (peer.isTor()) {
+      stateParams.tor = true;
+    }
+    $state.go('app.view_es_peer', stateParams);
+  };
+
+  $scope.$on('csView.action.refresh', function(event, context) {
+    if (context === 'peers') {
+      $scope.refresh();
+    }
+  });
+
+  $scope.$on('csView.action.showActionsPopover', function(event, clickEvent) {
+    $scope.showActionsPopover(clickEvent);
+  });
+
+  /* -- popover -- */
+
+  $scope.showActionsPopover = function(event) {
+    if (!$scope.actionsPopover) {
+      $ionicPopover.fromTemplateUrl('templates/network/lookup_popover_actions.html', {
+        scope: $scope
+      }).then(function(popover) {
+        $scope.actionsPopover = popover;
+        //Cleanup the popover when we're done with it!
+        $scope.$on('$destroy', function() {
+          $scope.actionsPopover.remove();
+        });
+        $scope.actionsPopover.show(event);
+      });
+    }
+    else {
+      $scope.actionsPopover.show(event);
+    }
+  };
+
+  $scope.hideActionsPopover = function() {
+    if ($scope.actionsPopover) {
+      $scope.actionsPopover.hide();
+    }
+  };
+
+  $scope.showEndpointsPopover = function($event, peer, endpointFilter) {
+    var endpoints = peer.getEndpoints(endpointFilter);
+    endpoints = (endpoints||[]).reduce(function(res, ep) {
+      var bma = esHttp.node.parseEndPoint(ep);
+      return res.concat({
+        label: 'NETWORK.VIEW.NODE_ADDRESS',
+        value: peer.getServer() + (bma.path||'')
+      });
+    }, []);
+    if (!endpoints.length) return;
+
+    UIUtils.popover.show($event, {
+      templateUrl: 'templates/network/popover_endpoints.html',
+      bindings: {
+        titleKey: 'NETWORK.VIEW.ENDPOINTS.' + endpointFilter,
+        items: endpoints
+      }
+    });
+    $event.stopPropagation();
+  };
+
+  $scope.showWs2pPopover = function($event, peer) {
+    $event.stopPropagation();
+
+    return $translate('NETWORK.VIEW.PRIVATE_ACCESS')
+      .then(function(privateAccessMessage) {
+        UIUtils.popover.show($event, {
+          templateUrl: 'templates/network/popover_endpoints.html',
+          bindings: {
+            titleKey: 'NETWORK.VIEW.ENDPOINTS.WS2P',
+            valueKey: 'NETWORK.VIEW.NODE_ADDRESS',
+            items: [
+              {
+                label: 'NETWORK.VIEW.NODE_ADDRESS',
+                value: !peer.bma.private ? (peer.getServer() + (peer.bma.path||'')) : privateAccessMessage
+              },
+              {
+                label: 'NETWORK.VIEW.WS2PID',
+                value: peer.bma.ws2pid
+              },
+              {
+                label: 'NETWORK.VIEW.POW_PREFIX',
+                value: peer.powPrefix
+              }]
+          }
+        });
+      });
+  };
+
+
+
+  /* -- help tip -- */
+
+  // Show help tip
+  $scope.showHelpTip = function(index, isTour) {
+    index = angular.isDefined(index) ? index : csSettings.data.helptip.network;
+    isTour = angular.isDefined(isTour) ? isTour : false;
+    if (index < 0) return;
+
+    // Create a new scope for the tour controller
+    var helptipScope = $scope.createHelptipScope();
+    if (!helptipScope) return; // could be undefined, if a global tour already is already started
+    helptipScope.tour = isTour;
+
+    return helptipScope.startNetworkTour(index, false)
+      .then(function(endIndex) {
+        helptipScope.$destroy();
+        if (!isTour) {
+          csSettings.data.helptip.network = endIndex;
+          csSettings.store();
+        }
+      });
+  };
+}
+
+
+function ESNetworkLookupModalController($scope, $controller, parameters) {
+  'ngInject';
+
+  // Initialize the super class and extend it.
+  angular.extend(this, $controller('NetworkLookupCtrl', {$scope: $scope}));
+
+  // Read parameters
+  parameters = parameters || {};
+  $scope.enableFilter = angular.isDefined(parameters.enableFilter) ? parameters.enableFilter : true;
+  $scope.search.type = angular.isDefined(parameters.type) ? parameters.type : $scope.search.type;
+  $scope.search.endpointFilter = angular.isDefined(parameters.endpointFilter) ? parameters.endpointFilter : $scope.search.endpointFilter;
+  $scope.expertMode = angular.isDefined(parameters.expertMode) ? parameters.expertMode : $scope.expertMode;
+  $scope.ionItemClass = parameters.ionItemClass || 'item-border-large';
+  $scope.enableLocationHref = false;
+  $scope.helptipPrefix = '';
+
+  $scope.selectPeer = function(peer) {
+    $scope.closeModal(peer);
+  };
+
+  $scope.$on('modal.hidden', function(){
+    $scope.leave();
+  });
+
+  // Disable this unsed method - called by load()
+  $scope.showHelpTip = function() {};
+
+  // Enter the modal
+  $scope.enter();
+}
+
+
+function ESNetworkLookupPopoverController($scope, $controller) {
+  'ngInject';
+
+  // Initialize the super class and extend it.
+  angular.extend(this, $controller('NetworkLookupCtrl', {$scope: $scope}));
+
+  // Read parameters
+  var parameters = parameters || {};
+  $scope.enableFilter = angular.isDefined(parameters.enableFilter) ? parameters.enableFilter : true;
+  $scope.search.type = angular.isDefined(parameters.type) ? parameters.type : $scope.search.type;
+  $scope.search.endpointFilter = angular.isDefined(parameters.endpointFilter) ? parameters.endpointFilter : $scope.search.endpointFilter;
+  $scope.expertMode = angular.isDefined(parameters.expertMode) ? parameters.expertMode : $scope.expertMode;
+  $scope.ionItemClass = parameters.ionItemClass || 'item-border-large';
+  $scope.helptipPrefix = '';
+
+  $scope.selectPeer = function(peer) {
+    $scope.closePopover(peer);
+  };
+
+  $scope.$on('popover.hidden', function(){
+    $scope.leave();
+  });
+
+  // Disable this unsed method - called by load()
+  $scope.showHelpTip = function() {};
+
+  // Enter the popover
+  $scope.enter();
+}
+
+function ESPeerInfoPopoverController($scope, $q, csSettings, csCurrency, csHttp, esHttp) {
+  'ngInject';
+
+  $scope.loading = true;
+  $scope.formData = {};
+
+  $scope.load = function() {
+
+    $scope.loading = true;
+    $scope.formData = {};
+
+    return $q.all([
+      // get current block
+      csCurrency.blockchain.current()
+        .then(function(block) {
+          $scope.formData.number = block.number;
+          $scope.formData.medianTime = block.medianTime;
+          $scope.formData.powMin = block.powMin;
+          $scope.formData.useSsl = esHttp.useSsl;
+        })
+        .catch(function() {
+          delete $scope.formData.number;
+          delete $scope.formData.medianTime;
+          delete $scope.formData.powMin;
+          delete $scope.formData.useSsl;
+          // continue
+        }),
+
+      // Get node current version
+      esHttp.node.summary()
+        .then(function(res){
+          $scope.formData.version = res && res.duniter && res.duniter.version;
+          $scope.formData.software = res && res.duniter && res.duniter.software;
+        })
+        .catch(function() {
+          delete $scope.formData.version;
+          delete $scope.formData.software;
+          // continue
+        }),
+
+      // Get duniter latest version
+      esHttp.version.latest()
+        .then(function(latestRelease){
+          $scope.formData.latestRelease = latestRelease;
+        })
+        .catch(function() {
+          delete $scope.formData.latestRelease;
+          // continue
+        })
+    ])
+      .then(function() {
+        // Compare, to check if newer
+        if ($scope.formData.latestRelease && $scope.formData.software == 'duniter') {
+          var compare = csHttp.version.compare($scope.formData.version, $scope.formData.latestRelease.version);
+          $scope.formData.isPreRelease = compare > 0;
+          $scope.formData.hasNewRelease = compare < 0;
+        }
+        else {
+          $scope.formData.isPreRelease = false;
+          $scope.formData.hasNewRelease = false;
+        }
+        $scope.loading = false;
+        $scope.$broadcast('$$rebind::' + 'rebind'); // force data binding
+      });
+  };
+
+  // Update UI on new block
+  csCurrency.api.data.on.newBlock($scope, function(block) {
+    if ($scope.loading) return;
+    console.debug("[peer info] Received new block. Reload content");
+    $scope.load();
+  });
+
+  // Update UI on settings changed
+  csSettings.api.data.on.changed($scope, function(data) {
+    if ($scope.loading) return;
+    console.debug("[peer info] Peer settings changed. Reload content");
+    $scope.load();
+  });
+
+  // Load data when enter
+  $scope.load();
+}
+
+function ESPeerViewController($scope, $q, $window, $state, UIUtils, csWot, esHttp, csHttp, csSettings) {
+  'ngInject';
+
+  $scope.node = {};
+  $scope.loading = true;
+  $scope.isHttps = ($window.location.protocol === 'https:');
+  $scope.isReachable = true;
+  $scope.options = {
+    document: {
+      index : csSettings.data.plugins.es && csSettings.data.plugins.es.document && csSettings.data.plugins.es.document.index || 'user',
+      type: csSettings.data.plugins.es && csSettings.data.plugins.es.document && csSettings.data.plugins.es.document.type || 'profile'
     }
+  };
+
+  $scope.$on('$ionicView.beforeEnter', function (event, viewData) {
+    // Enable back button (workaround need for navigation outside tabs - https://stackoverflow.com/a/35064602)
+    viewData.enableBack = UIUtils.screen.isSmall() ? true : viewData.enableBack;
   });
 
+  $scope.$on('$ionicView.enter', function(e, state) {
+    var isDefaultNode = !state.stateParams || !state.stateParams.server;
+    var server = state.stateParams && state.stateParams.server || esHttp.server;
+    var useSsl = state.stateParams && state.stateParams.ssl == "true" || (isDefaultNode ? esHttp.useSsl : false);
+    var useTor = state.stateParams.tor == "true" || (isDefaultNode ? esHttp.useTor : false);
+
+    return $scope.load(server, useSsl, useTor)
+      .then(function() {
+        return $scope.$broadcast('$csExtension.enter', e, state);
+      })
+      .then(function(){
+        $scope.loading = false;
+      });
+  });
+
+  $scope.load = function(server, useSsl, useTor) {
+    var node = {
+      server: server,
+      host: server,
+      useSsl: useSsl,
+      useTor: useTor
+    };
+    var serverParts = server.split(':');
+    if (serverParts.length == 2) {
+      node.host = serverParts[0];
+      node.port = serverParts[1];
+    }
+    node.url = csHttp.getUrl(node.host, node.port, undefined/*path*/, node.useSsl);
+
+    angular.merge($scope.node,
+      useTor ?
+        // For TOR, use a web2tor to access the endpoint
+        esHttp.lightInstance(node.host + ".to", 443, 443, true/*ssl*/, 60000 /*long timeout*/) :
+        esHttp.lightInstance(node.host, node.port, node.useSsl),
+      node);
+
+    $scope.isReachable = !$scope.isHttps || useSsl;
+    if (!$scope.isReachable) {
+      // Get node from the default node
+      return esHttp.network.peers()
+        .then(function(res) {
+          // find the current peer
+          var peers = (res && res.peers || []).reduce(function(res, json) {
+            var peer = new EsPeer(json);
+            if (!peer.hasEndpoint('GCHANGE_API')) return res;
+            var ep = esHttp.node.parseEndPoint(peer.getEndpoints('GCHANGE_API')[0]);
+            if((ep.dns == node.host || ep.ipv4 == node.host || ep.ipv6 == node.host) && (
+              ep.port == node.port)) {
+              peer.ep = ep;
+              return res.concat(peer);
+            }
+            return res;
+          }, []);
+          var peer = peers.length && peers[0];
+
+          // Current node found
+          if (peer) {
+            $scope.node.pubkey = peer.pubkey;
+            $scope.node.currency = peer.currency;
+            return csWot.extend($scope.node);
+          }
+          else {
+            console.warn('Could not get peer from /network/peers');
+          }
+        });
+    }
+
+    return $q.all([
+
+      // Get node peer info
+      $scope.node.network.peering.self()
+        .then(function(json) {
+          $scope.node.pubkey = json.pubkey;
+          $scope.node.currency = json.currency;
+        }),
+
+      // Get node doc count
+      $scope.node.record.count($scope.options.document.index, $scope.options.document.type)
+        .then(function(count) {
+          $scope.node.docCount = count;
+        }),
+
+      // Get known peers
+      $scope.node.network.peers()
+        .then(function(json) {
+          var peers = json.peers.reduce(function (res, p) {
+            var peer = new EsPeer(p);
+            if (!peer.hasEndpoint('GCHANGE_API')) return res;
+            peer.online = p.status === 'UP';
+            peer.blockNumber = peer.block.replace(/-.+$/, '');
+            peer.ep = esHttp.node.parseEndPoint(peer.getEndpoints('GCHANGE_API')[0]);
+            peer.dns = peer.getDns();
+            peer.id = peer.keyID();
+            peer.server = peer.getServer();
+            return res.concat(peer);
+          }, []);
+
+          // Extend (add uid+name+avatar)
+          return csWot.extendAll([$scope.node].concat(peers))
+            .then(function() {
+              // Final sort
+              $scope.peers = _.sortBy(peers, function(p) {
+                var score = 1;
+                score += 10000 * (p.online ? 1 : 0);
+                score += 1000  * (p.hasMainConsensusBlock ? 1 : 0);
+                score += 100   * (p.name ? 1 : 0);
+                return -score;
+              });
+              $scope.motion.show({selector: '.item-peer'});
+            });
+        }),
+
+      // Get current block
+      $scope.node.blockchain.current()
+        .then(function(json) {
+          $scope.current = json;
+        })
+    ])
+      .catch(UIUtils.onError(useTor ? "PEER.VIEW.ERROR.LOADING_TOR_NODE_ERROR" : "PEER.VIEW.ERROR.LOADING_NODE_ERROR"));
+  };
+
+  $scope.selectPeer = function(peer) {
+    // Skip offline
+    if (!peer.online ) return;
+
+    var stateParams = {server: peer.getServer()};
+    if (peer.isSsl()) {
+      stateParams.ssl = true;
+    }
+    if (peer.isTor()) {
+      stateParams.tor = true;
+    }
+    $state.go('app.view_es_peer', stateParams);
+  };
+
+  /* -- manage link to raw document -- */
+
+  $scope.openRawPeering = function(event) {
+    return $scope.openLink(event, $scope.node.url + '/network/peering?pretty');
+  };
+
+  $scope.openRawCurrentBlock = function(event) {
+    return $scope.openLink(event, $scope.node.url + '/network/peering?pretty');
+  };
+}
diff --git a/www/plugins/es/js/controllers/subscription-controllers.js b/www/plugins/es/js/controllers/subscription-controllers.js
index d73f7736..8ad1106d 100644
--- a/www/plugins/es/js/controllers/subscription-controllers.js
+++ b/www/plugins/es/js/controllers/subscription-controllers.js
@@ -35,9 +35,9 @@ angular.module('cesium.es.subscription.controllers', ['cesium.es.services'])
 
   })
 
- .controller('ViewSubscriptionsCtrl', ViewSubscriptionsController)
+  .controller('ViewSubscriptionsCtrl', ViewSubscriptionsController)
 
- .controller('ModalEmailSubscriptionsCtrl', ModalEmailSubscriptionsController)
+  .controller('ModalEmailSubscriptionsCtrl', ModalEmailSubscriptionsController)
 
 ;
 
@@ -260,7 +260,7 @@ function ViewSubscriptionsController($scope, $q, $ionicHistory, csWot, csWallet,
       })
       .then(function(cat){
         if (cat && cat.parent) {
-           return cat;
+          return cat;
         }
       });
   };
@@ -272,7 +272,7 @@ function ViewSubscriptionsController($scope, $q, $ionicHistory, csWot, csWallet,
 }
 
 
-function ModalEmailSubscriptionsController($scope, Modals, csSettings, esHttp, csWot, parameters) {
+function ModalEmailSubscriptionsController($scope, Modals, csSettings, esHttp, csWot, esModals, parameters) {
   'ngInject';
 
   $scope.frequencies = [
@@ -290,6 +290,15 @@ function ModalEmailSubscriptionsController($scope, Modals, csSettings, esHttp, c
       $scope.recipient = {pubkey: $scope.formData.recipient};
       return csWot.extendAll([$scope.recipient]);
     }
+    else {
+      return esHttp.network.peering.self()
+        .then(function(res){
+          if (!res) return;
+          $scope.formData.recipient = res.pubkey;
+          $scope.recipient = {pubkey: $scope.formData.recipient};
+          return csWot.extendAll([$scope.recipient]);
+        });
+    }
   });
 
   // Submit
@@ -318,10 +327,9 @@ function ModalEmailSubscriptionsController($scope, Modals, csSettings, esHttp, c
   }
 
   $scope.showNetworkLookup = function() {
-    return Modals.showNetworkLookup({
+    return esModals.showNetworkLookup({
       enableFilter: true,
-      endpoint: esHttp.constants.ES_USER_API_ENDPOINT,
-      bma: false
+      endpointFilter: esHttp.constants.ES_SUBSCRIPTION_API
     })
       .then(function (peer) {
         if (peer) {
diff --git a/www/plugins/es/js/entities/peer.js b/www/plugins/es/js/entities/peer.js
new file mode 100644
index 00000000..0c0af2bc
--- /dev/null
+++ b/www/plugins/es/js/entities/peer.js
@@ -0,0 +1,163 @@
+
+
+function EsPeer(json) {
+
+  var that = this;
+
+  Object.keys(json).forEach(function (key) {
+    that[key] = json[key];
+  });
+
+  that.endpoints = that.endpoints || [];
+}
+
+
+EsPeer.prototype.regexp = {
+  API_REGEXP: /^([A-Z_]+)(?:[ ]+([a-z_][a-z0-9-_.ÄŸÄž]*))?(?:[ ]+([0-9.]+))?(?:[ ]+([0-9a-f:]+))?(?:[ ]+([0-9]+))(?:\/[^\/]+)?$/,
+  LOCAL_IP_ADDRESS: /^127[.]0[.]0.|192[.]168[.]|10[.]0[.]0[.]|172[.]16[.]/
+};
+
+EsPeer.prototype.keyID = function () {
+  var ep = this.ep || this.getEP();
+  if (ep.useBma) {
+    return [this.pubkey || "Unknown", ep.dns, ep.ipv4, ep.ipv6, ep.port, ep.useSsl, ep.path].join('-');
+  }
+  return [this.pubkey || "Unknown", ep.ws2pid, ep.path].join('-');
+};
+
+EsPeer.prototype.copyValues = function(to) {
+  var obj = this;
+  ["version", "currency", "pub", "endpoints", "hash", "status", "block", "signature"].forEach(function (key) {
+    to[key] = obj[key];
+  });
+};
+
+EsPeer.prototype.copyValuesFrom = function(from) {
+  var obj = this;
+  ["version", "currency", "pub", "endpoints", "block", "signature"].forEach(function (key) {
+    obj[key] = from[key];
+  });
+};
+
+EsPeer.prototype.json = function() {
+  var obj = this;
+  var json = {};
+  ["version", "currency", "endpoints", "status", "block", "signature"].forEach(function (key) {
+    json[key] = obj[key];
+  });
+  json.raw = this.raw && this.getRaw();
+  json.pubkey = this.pubkey;
+  return json;
+};
+
+EsPeer.prototype.getEP = function() {
+  if (this.ep) return this.ep;
+  var ep = null;
+  var epRegex = this.regexp.API_REGEXP;
+  this.endpoints.forEach(function(epStr){
+    var matches = !ep && epRegex.exec(epStr);
+    if (matches) {
+      ep = {
+        "api": matches[1] || '',
+        "dns": matches[2] || '',
+        "ipv4": matches[3] || '',
+        "ipv6": matches[4] || '',
+        "port": matches[5] || 80,
+        "path": matches[6] || '',
+        "useSsl": matches[5] == 443
+      };
+    }
+  });
+  return ep || {};
+};
+
+EsPeer.prototype.getEndpoints = function(regexp) {
+  if (!regexp) return this.endpoints;
+  if (typeof regexp === 'string') regexp = new RegExp('^' + regexp);
+    return this.endpoints.reduce(function(res, ep){
+      return ep.match(regexp) ?  res.concat(ep) : res;
+    }, []);
+};
+
+EsPeer.prototype.hasEndpoint = function(endpoint){
+  var regExp = this.regexp[endpoint] || new RegExp('^' + endpoint);
+  var endpoints = this.getEndpoints(regExp);
+  return endpoints && endpoints.length > 0;
+};
+
+EsPeer.prototype.hasEsEndpoint = function() {
+  var endpoints = this.getEsEndpoints();
+  return endpoints && endpoints.length > 0;
+};
+
+EsPeer.prototype.getEsEndpoints = function() {
+  return this.getEndpoints(/^(ES_CORE_API|ES_USER_API|ES_SUBSCRIPTION_API|GCHANGE_API)/);
+};
+
+EsPeer.prototype.getDns = function() {
+  var ep = this.ep || this.getEP();
+  return ep.dns ? ep.dns : null;
+};
+
+EsPeer.prototype.getIPv4 = function() {
+  var ep = this.ep || this.getEP();
+  return ep.ipv4 ? ep.ipv4 : null;
+};
+
+EsPeer.prototype.getIPv6 = function() {
+  var ep = this.ep || this.getEP();
+  return ep.ipv6 ? ep.ipv6 : null;
+};
+
+EsPeer.prototype.getPort = function() {
+  var ep = this.ep || this.getEP();
+  return ep.port ? ep.port : null;
+};
+
+EsPeer.prototype.getHost = function() {
+  var ep = this.ep || this.getEP();
+  return ((ep.port == 443 || ep.useSsl) && ep.dns) ? ep.dns :
+    (this.hasValid4(ep) ? ep.ipv4 :
+        (ep.dns ? ep.dns :
+          (ep.ipv6 ? '[' + ep.ipv6 + ']' :'')));
+};
+
+EsPeer.prototype.getURL = function() {
+  var ep = this.ep || this.getEP();
+  var host = this.getHost();
+  var protocol = (ep.port == 443 || ep.useSsl) ? 'https' : 'http';
+  return protocol + '://' + host + (ep.port ? (':' + ep.port) : '');
+};
+
+EsPeer.prototype.getServer = function() {
+  var ep = this.ep || this.getEP();
+  var host = this.getHost();
+  return host + (host && ep.port ? (':' + ep.port) : '');
+};
+
+EsPeer.prototype.hasValid4 = function(ep) {
+  return ep.ipv4 &&
+    /* exclude private address - see https://fr.wikipedia.org/wiki/Adresse_IP */
+    !ep.ipv4.match(this.regexp.LOCAL_IP_ADDRESS) ?
+    true : false;
+};
+
+EsPeer.prototype.isReachable = function () {
+  return !!this.getServer();
+};
+
+EsPeer.prototype.isSsl = function() {
+  var ep = this.ep || this.getEP();
+  return ep.useSsl;
+};
+
+EsPeer.prototype.isTor = function() {
+  var ep = this.ep || this.getEP();
+  return ep.useTor;
+};
+
+EsPeer.prototype.isHttp = function() {
+  var ep = this.ep || this.getEP();
+  return !bma.useTor;
+};
+
diff --git a/www/plugins/es/js/services.js b/www/plugins/es/js/services.js
index a4a8f888..0bb32bb2 100644
--- a/www/plugins/es/js/services.js
+++ b/www/plugins/es/js/services.js
@@ -19,6 +19,7 @@ angular.module('cesium.es.services', [
     'cesium.es.wot.services',
     'cesium.es.tx.services',
     'cesium.es.geo.services',
-    'cesium.es.document.services'
+    'cesium.es.document.services',
+    'cesium.es.network.services'
   ])
 ;
diff --git a/www/plugins/es/js/services/comment-services.js b/www/plugins/es/js/services/comment-services.js
index 374752bb..5044924b 100644
--- a/www/plugins/es/js/services/comment-services.js
+++ b/www/plugins/es/js/services/comment-services.js
@@ -101,7 +101,7 @@ angular.module('cesium.es.comment.services', ['ngResource', 'cesium.services',
         options.size = options.size || DEFAULT_SIZE;
         options.loadAvatar = angular.isDefined(options.loadAvatar) ? options.loadAvatar : true;
         options.loadAvatarAllParent = angular.isDefined(options.loadAvatarAllParent) ? (options.loadAvatar && options.loadAvatarAllParent) : false;
-        if (options.size < 0) options.size = DEFAULT_SIZE;
+        if (options.size < 0) options.size = 1000; // all comments
 
         var request = {
           query : {
@@ -199,19 +199,14 @@ angular.module('cesium.es.comment.services', ['ngResource', 'cesium.services',
         });
 
         // Open websocket
-        var time = Date.now();
+        var now = Date.now();
         console.info("[ES] [comment] Starting websocket to listen comments on [{0}/record/{1}]".format(index, recordId.substr(0,8)));
-        var wsChanges = exports.raw.wsChanges();
+        var wsChanges = esHttp.websocket.changes(index + '/comment');
         return wsChanges.open()
 
-        // Define source filter
-          .then(function(sock) {
-            return sock.send(index + '/comment');
-          })
-
           // Listen changes
           .then(function(){
-            console.debug("[ES] [comment] Websocket opened in {0} ms".format(Date.now() - time));
+            console.debug("[ES] [comment] Websocket opened in {0} ms".format(Date.now() - now));
             wsChanges.on(function(change) {
               if (!change) return;
               scope.$applyAsync(function() {
@@ -237,7 +232,7 @@ angular.module('cesium.es.comment.services', ['ngResource', 'cesium.services',
                     // fill map by id
                     data.mapById[change._id] = comment;
                     exports.raw.refreshTreeLinks(data)
-                    // fill avatars (and uid)
+                      // fill avatars (and uid)
                       .then(function() {
                         return csWot.extend(comment, 'issuer');
                       })
diff --git a/www/plugins/es/js/services/document-services.js b/www/plugins/es/js/services/document-services.js
index 41567a05..7e987962 100644
--- a/www/plugins/es/js/services/document-services.js
+++ b/www/plugins/es/js/services/document-services.js
@@ -11,7 +11,7 @@ angular.module('cesium.es.document.services', ['ngResource', 'cesium.platform',
   })
 
   .factory('esDocument', function($q, $rootScope, $timeout, UIUtils, Api, CryptoUtils,
-                                 csPlatform, csConfig, csSettings, csWot, csWallet, esHttp) {
+                                  csPlatform, csConfig, csSettings, csWot, csWallet, esHttp) {
     'ngInject';
 
     var
@@ -25,9 +25,44 @@ angular.module('cesium.es.document.services', ['ngResource', 'cesium.platform',
       },
       raw = {
         search: esHttp.post('/:index/:type/_search'),
-        searchText: esHttp.get('/:index/:type/_search?q=:text')
+        searchText: esHttp.get('/:index/:type/_search?q=:text&_source=:source')
       };
 
+    function _initOptions(options) {
+      if (!options || !options.index || !options.type) throw new Error('Missing mandatory options [index, type]');
+
+      var side = 'desc';
+      if (options.type == 'peer') {
+        if (!options.sort || options.sort.time) {
+          side = options.sort && options.sort.time || side;
+          options.sort = {
+            'stats.medianTime': {
+              nested_path: 'stats',
+              order: side
+            }
+          };
+        }
+        options._source = fields.peer;
+        options.getTimeFunction = function(doc) {
+          doc.time = doc.stats && doc.stats.medianTime;
+          return doc.time;
+        };
+      }
+      else if (options.type == 'movement') {
+        if (!options.sort || options.sort.time) {
+          side = options.sort && options.sort.time || side;
+          options.sort = {'medianTime': side};
+        }
+        options._source = options._source || fields.movement;
+        options.getTimeFunction = function(doc) {
+          doc.time = doc.medianTime;
+          return doc.time;
+        };
+      }
+
+      return options;
+    }
+
     function _readSearchHits(res, options) {
       options.issuerField = options.issuerField || 'pubkey';
 
@@ -36,8 +71,9 @@ angular.module('cesium.es.document.services', ['ngResource', 'cesium.platform',
         doc.index = hit._index;
         doc.type = hit._type;
         doc.id = hit._id;
-        doc.pubkey = doc.issuer || options.issuerField && doc[options.issuerField]; // need to call csWot.extendAll()
-        doc.time = doc.time || options.getTimeFunction && options.getTimeFunction(doc);
+        doc.pubkey = doc.issuer || options.issuerField && doc[options.issuerField] || doc.pubkey; // need to call csWot.extendAll()
+        doc.time = options.getTimeFunction && options.getTimeFunction(doc) || doc.time;
+        doc.thumbnail = esHttp.image.fromHit(hit, 'thumbnail');
         return res.concat(doc);
       }, []);
 
@@ -62,54 +98,25 @@ angular.module('cesium.es.document.services', ['ngResource', 'cesium.platform',
         });
     }
 
-    function search(options) {
-      options = options || {};
+    function readSearchHit(hit) {
+      var options = _initOptions({
+        index: hit._index,
+        type: hit._type
+      });
 
-      var sortParts, side;
-      if (options.type == 'movement') {
-        if (!options.sort) {
-          options.sort = 'stats.medianTime:desc';
-        }
-        else {
-          sortParts = options.sort.split(':');
-          side = sortParts.length > 1 ? sortParts[1] : 'desc';
-
-          options.sort = [
-            {'stats.medianTime': {
-              nested_path : 'stats',
-              order: side
-            }}
-          ];
-          options._source = fields.peer;
-          options.getTimeFunction = function(doc) {
-            doc.time = doc.stats && doc.stats.medianTime;
-            return doc.time;
-          };
+      return _readSearchHits({
+        hits: {
+          hits: [hit]
         }
-      }
-      else if (options.type == 'movement') {
-        if (!options.sort) {
-          options.sort = 'medianTime:desc';
-        }
-        else {
-          sortParts = options.sort.split(':');
-          side = sortParts.length > 1 ? sortParts[1] : 'desc';
-
-          options.sort = [
-            {'medianTime': {
-              order: side
-            }}
-          ];
-          options._source = fields.movement;
-          options.getTimeFunction = function(doc) {
-            doc.time = doc.medianTime;
-            return doc.time;
-          };
-        }
-      }
+      }, options)
+        .then(function(res) {
+          return res.hits[0];
+        });
+    }
 
+    function search(options) {
+      options = _initOptions(options);
 
-      if (!options || !options.index || !options.type) throw new Error('Missing mandatory options [index, type]');
       var request = {
         from: options.from || 0,
         size: options.size || constants.DEFAULT_LOAD_SIZE,
@@ -121,9 +128,9 @@ angular.module('cesium.es.document.services', ['ngResource', 'cesium.platform',
       }
 
       return raw.search(request, {
-          index: options.index,
-          type: options.type
-        })
+        index: options.index,
+        type: options.type
+      })
         .then(function(res) {
           return _readSearchHits(res, options);
         });
@@ -140,7 +147,7 @@ angular.module('cesium.es.document.services', ['ngResource', 'cesium.platform',
         from: options.from || 0,
         size: options.size || constants.DEFAULT_LOAD_SIZE,
         sort: options.sort || 'time:desc',
-        _source: options._source || fields.commons.join(',')
+        source: options._source && options._source.join(',') || fields.commons.join(',')
       };
 
       console.debug('[ES] [wallet] [document] [{0}/{1}] Loading documents...'.format(
@@ -166,6 +173,7 @@ angular.module('cesium.es.document.services', ['ngResource', 'cesium.platform',
 
     function remove(document, options) {
       if (!document || !document.index || !document.type || !document.id) return $q.reject('Could not remove document: missing mandatory fields');
+
       return esHttp.record.remove(document.index, document.type)(document.id, options);
     }
 
@@ -190,7 +198,8 @@ angular.module('cesium.es.document.services', ['ngResource', 'cesium.platform',
       removeAll: removeAll,
       fields: {
         commons: fields.commons
-      }
+      },
+      fromHit: readSearchHit
     };
   })
 ;
diff --git a/www/plugins/es/js/services/http-services.js b/www/plugins/es/js/services/http-services.js
index bc1b0707..1aa5d2df 100644
--- a/www/plugins/es/js/services/http-services.js
+++ b/www/plugins/es/js/services/http-services.js
@@ -20,15 +20,19 @@ angular.module('cesium.es.http.services', ['ngResource', 'ngApi', 'cesium.servic
       that = this,
       cachePrefix = 'esHttp-',
       constants = {
+        ES_USER_API: 'ES_USER_API',
+        ES_SUBSCRIPTION_API: 'ES_SUBSCRIPTION_API',
         ES_USER_API_ENDPOINT: 'ES_USER_API( ([a-z_][a-z0-9-_.]*))?( ([0-9.]+))?( ([0-9a-f:]+))?( ([0-9]+))',
+        ANY_API_ENDPOINT: '([A-Z_]+)(?:[ ]+([a-z_][a-z0-9-_.ÄŸÄž]*))?(?:[ ]+([0-9.]+))?(?:[ ]+([0-9a-f:]+))?(?:[ ]+([0-9]+))(?:\\/[^\\/]+)?',
         MAX_UPLOAD_BODY_SIZE: csConfig.plugins && csConfig.plugins.es && csConfig.plugins.es.maxUploadBodySize || 2097152 /*=2M*/
       },
       regexp = {
         IMAGE_SRC: exact('data:([A-Za-z//]+);base64,(.+)'),
         URL: match('(www\\.|https?:\/\/(www\\.)?)[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)'),
-        HASH_TAG: match('(?:^|[\t\n\r\s ])#([\\wḡĞǦğàáâãäåçèéêëìíîïðòóôõöùúûüýÿ]+)'),
+        HASH_TAG: match('(?:^|[\t\n\r\s ])#([0-9_-\\wḡĞǦğàáâãäåçèéêëìíîïðòóôõöùúûüýÿ]+)'),
         USER_TAG: match('(?:^|[\t\n\r\s ])@('+BMA.constants.regexp.USER_ID+')'),
-        ES_USER_API_ENDPOINT: exact(constants.ES_USER_API_ENDPOINT)
+        ES_USER_API_ENDPOINT: exact(constants.ES_USER_API_ENDPOINT),
+        API_ENDPOINT: exact(constants.ANY_API_ENDPOINT),
       },
       fallbackNodeIndex = 0,
       listeners,
@@ -74,8 +78,8 @@ angular.module('cesium.es.http.services', ['ngResource', 'ngApi', 'cesium.servic
     }
 
     function isSameNode(host, port, useSsl) {
-      return (that.host == host) &&
-        (that.port == port) &&
+      return (that.host === host) &&
+        (that.port === port) &&
         (angular.isUndefined(useSsl) || useSsl == that.useSsl);
     }
 
@@ -140,6 +144,9 @@ angular.module('cesium.es.http.services', ['ngResource', 'ngApi', 'cesium.servic
       return that.start(true /*skipInit*/);
     };
 
+    // Get node time (UTC) FIXME: get it from the node
+    that.date = { now : csHttp.date.now };
+
     that.byteCount = function (s) {
       s = (typeof s == 'string') ? s : JSON.stringify(s);
       return encodeURI(s).split(/%(?:u[0-9A-F]{2})?[0-9A-F]{2}|./).length - 1;
@@ -157,7 +164,7 @@ angular.module('cesium.es.http.services', ['ngResource', 'ngApi', 'cesium.servic
       var getRequestFn = function(params) {
         if (!that.started) {
           if (!that._startPromise) {
-            console.error('[ES] [http] Trying to get [{0}] before start(). Waiting...'.format(path));
+            console.warn('[ES] [http] Trying to get [{0}] before start(). Waiting...'.format(path));
           }
           return that.ready().then(function(start) {
             if (!start) return $q.reject('ERROR.ES_CONNECTION_ERROR');
@@ -214,6 +221,23 @@ angular.module('cesium.es.http.services', ['ngResource', 'ngApi', 'cesium.servic
       };
     };
 
+    that.wsChanges = function(source) {
+      var wsChanges = that.ws('/ws/_changes')();
+      if (!source) return wsChanges;
+      // var oldOpen = wsChanges.open;
+      // wsChanges.open = function() {
+      //   return oldOpen.call(wsChanges).then(function(sock) {
+      //     if(sock) {
+      //       sock.send(source);
+      //     }
+      //     else {
+      //       console.warn('Trying to access ws changes, but no sock anymore... already open ?');
+      //     }
+      //   });
+      // };
+      return wsChanges;
+    };
+
     that.isAlive = function() {
       return csHttp.get(that.host, that.port, '/node/summary', that.useSsl)()
         .then(function(json) {
@@ -378,6 +402,28 @@ angular.module('cesium.es.http.services', ['ngResource', 'ngApi', 'cesium.servic
       return urls;
     }
 
+    function parseMarkdownTitlesFromText(value, prefix, suffix) {
+      prefix = prefix || '##';
+      var reg = match('(?:^|[\\r\\s])('+prefix+'([^#></]+)' + (suffix||'') + ')');
+      var matches = value && reg.exec(value);
+      var lines = matches && [];
+      var res = matches && [];
+      while(matches) {
+        var line = matches[1];
+        if (!_.contains(lines, line)) {
+          lines.push(line);
+          res.push({
+            line: line,
+            title: matches[2]
+          });
+        }
+        value = value.substr(matches.index + matches[1].length + 1);
+        matches = value.length > 0 && reg.exec(value);
+      }
+      return res;
+    }
+
+
     function escape(text) {
       if (!text) return text;
       return text.replace(/</g, '&lt;').replace(/>/g, '&gt;');
@@ -417,6 +463,13 @@ angular.module('cesium.es.http.services', ['ngResource', 'ngApi', 'cesium.servic
           var link = '<a ui-sref=\"{0}({uid: \'{1}\'})\">@{2}</a>'.format(options.uidState, tag, tag);
           content = content.replace('@'+tag, link);
         });
+
+        // Replace markdown titles
+        var titles = parseMarkdownTitlesFromText(content, '#+[ ]*', '<br>');
+        _.forEach(titles, function(matches){
+          var size = matches.line.lastIndexOf('#', 5)+1;
+          content = content.replace(matches.line, '<h{0}>{1}</h{2}>'.format(size, matches.title, size));
+        });
       }
       return content;
     }
@@ -492,36 +545,43 @@ angular.module('cesium.es.http.services', ['ngResource', 'ngApi', 'cesium.servic
               fillRecordTags(obj, options.tagFields);
             }
 
-        var str = JSON.stringify(obj);
-
-          return CryptoUtils.util.hash(str)
-            .then(function(hash) {
-              return CryptoUtils.sign(hash, keypair)
-                .then(function(signature) {
-                  // Prepend hash+signature
-                  str = '{"hash":"{0}","signature":"{1}",'.format(hash, signature) + str.substring(1);
-                  // Send data
-                  return postRequest(str, params)
-                    .then(function (id){
-
-                      // Clear cache
-                      csCache.clear(cachePrefix);
-
-                      return id;
-                    })
-                    .catch(function(err) {
-                      var bodyLength = that.byteCount(obj);
-                      if (bodyLength > constants.MAX_UPLOAD_BODY_SIZE) {
-                        throw {message: 'ERROR.ES_MAX_UPLOAD_BODY_SIZE', length: bodyLength};
-                      }
-                      throw err;
-                    });
-                });
-            });
-         });
+            var str = JSON.stringify(obj);
+
+            return CryptoUtils.util.hash(str)
+              .then(function(hash) {
+                return CryptoUtils.sign(hash, keypair)
+                  .then(function(signature) {
+                    // Prepend hash+signature
+                    str = '{"hash":"{0}","signature":"{1}",'.format(hash, signature) + str.substring(1);
+                    // Send data
+                    return postRequest(str, params)
+                      .then(function (id){
+
+                        // Clear cache
+                        csCache.clear(cachePrefix);
+
+                        return id;
+                      })
+                      .catch(function(err) {
+                        var bodyLength = that.byteCount(obj);
+                        if (bodyLength > constants.MAX_UPLOAD_BODY_SIZE) {
+                          throw {message: 'ERROR.ES_MAX_UPLOAD_BODY_SIZE', length: bodyLength};
+                        }
+                        throw err;
+                      });
+                  });
+              });
+          });
       };
     }
 
+    function countRecord(index, type) {
+      return that.get("/{0}/{1}/_search?size=0".format(index, type))
+        .then(function(res) {
+          return res && res.hits && res.hits.total;
+        });
+    }
+
     function removeRecord(index, type) {
       return function(id, options) {
         options = options || {};
@@ -624,25 +684,26 @@ angular.module('cesium.es.http.services', ['ngResource', 'ngApi', 'cesium.servic
     };
 
     function parseEndPoint(endpoint) {
-      var matches = regexp.ES_USER_API_ENDPOINT.exec(endpoint);
+      var matches = regexp.API_ENDPOINT.exec(endpoint);
       if (!matches) return;
-      var port = matches[8] || 80;
       return {
+        "api": matches[1] || '',
         "dns": matches[2] || '',
-        "ipv4": matches[4] || '',
-        "ipv6": matches[6] || '',
-        "port": port,
-        "useSsl": port == 80 ? false : (port == 443)
+        "ipv4": matches[3] || '',
+        "ipv6": matches[4] || '',
+        "port": matches[5] || 80,
+        "path": matches[6] || '',
+        "useSsl": matches[5] == 443
       };
     }
 
     function emptyHit() {
       return {
-         _id: null,
-         _index: null,
-         _type: null,
-         _version: null,
-         _source: {}
+        _id: null,
+        _index: null,
+        _type: null,
+        _version: null,
+        _source: {}
       };
     }
 
@@ -673,13 +734,26 @@ angular.module('cesium.es.http.services', ['ngResource', 'ngApi', 'cesium.servic
         sameAsSettings: isSameNodeAsSettings,
         isFallback: isFallbackNode
       },
+      websocket: {
+        changes: that.wsChanges,
+        block: that.ws('/ws/block'),
+        peer: that.ws('/ws/peer')
+      },
+      wot: {
+        member: {
+          uids : that.get('/wot/members')
+        }
+      },
       network: {
-        peering: that.get('/network/peering'),
+        peering: {
+          self: that.get('/network/peering')
+        },
         peers: that.get('/network/peers')
       },
       record: {
         post: postRecord,
-        remove: removeRecord
+        remove: removeRecord,
+        count : countRecord
       },
       image: {
         fromAttachment: imageFromAttachment,
@@ -707,6 +781,53 @@ angular.module('cesium.es.http.services', ['ngResource', 'ngApi', 'cesium.servic
     return new EsHttp(host, port, useSsl, useCache);
   };
 
+  service.lightInstance = function(host, port, useSsl, timeout) {
+    port = port || 80;
+    useSsl = angular.isDefined(useSsl) ? useSsl : (port == 443);
+
+    function countHits(path, params) {
+      return csHttp.get(host, port, path)(params)
+        .then(function(res) {
+          return res && res.hits && res.hits.total;
+        });
+    }
+
+    function countRecords(index, type) {
+      return countHits("/{0}/{1}/_search?size=0".format(index, type));
+    }
+
+    function countSubscriptions(params) {
+      var queryString = _.keys(params||{}).reduce(function(res, key) {
+        return (res && (res + " AND ") || "") + key + ":" + params[key];
+      }, '');
+      return countHits("/subscription/record/_search?size=0&q=" + queryString);
+    }
+
+    return {
+      host: host,
+      port: port,
+      useSsl: useSsl,
+      node: {
+        summary: csHttp.getWithCache(host, port, '/node/summary', useSsl, csHttp.cache.LONG, false, timeout)
+      },
+      network: {
+        peering: {
+          self: csHttp.get(host, port, '/network/peering', useSsl, timeout)
+        },
+        peers: csHttp.get(host, port, '/network/peers', useSsl, timeout)
+      },
+      blockchain: {
+        current: csHttp.get(host, port, '/blockchain/current?_source=number,hash,medianTime', useSsl, timeout)
+      },
+      record: {
+        count: countRecords
+      },
+      subscription: {
+        count: countSubscriptions
+      }
+    };
+  };
+
   return service;
 })
 ;
diff --git a/www/plugins/es/js/services/modal-services.js b/www/plugins/es/js/services/modal-services.js
index 06a11add..b1186405 100644
--- a/www/plugins/es/js/services/modal-services.js
+++ b/www/plugins/es/js/services/modal-services.js
@@ -1,65 +1,84 @@
 angular.module('cesium.es.modal.services', ['cesium.modal.services', 'cesium.es.message.services'])
 
-.factory('esModals', function($state, ModalUtils, UIUtils, csWallet) {
-  'ngInject';
+  .factory('esModals', function($state, ModalUtils, UIUtils, csWallet) {
+    'ngInject';
 
-  function showMessageCompose(parameters) {
-    return ModalUtils.show('plugins/es/templates/message/modal_compose.html','ESMessageComposeModalCtrl',
-      parameters, {focusFirstInput: true});
-  }
+    function showMessageCompose(parameters) {
+      return ModalUtils.show('plugins/es/templates/message/modal_compose.html','ESMessageComposeModalCtrl',
+        parameters, {focusFirstInput: true});
+    }
 
-  function showNotificationsPopover(scope, event) {
-    return UIUtils.popover.show(event, {
-      templateUrl :'plugins/es/templates/common/popover_notification.html',
-      scope: scope,
-      autoremove: false, // reuse popover
-      afterHidden: scope.resetUnreadCount
-    })
-      .then(function(notification) {
-        if (!notification) return; // no selection
-        if (notification.onRead && typeof notification.onRead == 'function') notification.onRead();
-        if (notification.state) {
-          $state.go(notification.state, notification.stateParams);
+    function updateNotificationCountAndReadTime() {
+      csWallet.data.notifications.unreadCount = 0;
+      if (csWallet.data.notifications && csWallet.data.notifications.history.length) {
+        var lastNotification = csWallet.data.notifications.history[0];
+        var readTime = lastNotification ? lastNotification.time : 0;
+        csSettings.data.wallet = csSettings.data.wallet || {};
+        if (readTime && csSettings.data.wallet.notificationReadTime != readTime) {
+          csSettings.data.wallet.notificationReadTime = readTime;
+          csSettings.store();
         }
-      });
-  }
+      }
+    }
 
-  function showNewInvitation(parameters) {
-    return csWallet.auth({minData: true})
-      .then(function(walletData) {
-        UIUtils.loading.hide();
+    function showNotificationsPopover(scope, event) {
+      return UIUtils.popover.show(event, {
+        templateUrl :'plugins/es/templates/common/popover_notification.html',
+        scope: scope,
+        autoremove: false, // reuse popover
+        afterHidden: updateNotificationCountAndReadTime
+      })
+        .then(function(notification) {
+          if (!notification) return; // no selection
+          if (notification.onRead && typeof notification.onRead == 'function') notification.onRead();
+          if (notification.state) {
+            $state.go(notification.state, notification.stateParams);
+          }
+        });
+    }
 
-        // Not allow for non-member - issue #561
-        if (!walletData.isMember) {
-          return UIUtils.alert.error('ERROR.ONLY_MEMBER_CAN_EXECUTE_THIS_ACTION');
-        }
-        return ModalUtils.show('plugins/es/templates/invitation/modal_new_invitation.html', 'ESNewInvitationModalCtrl',
-          parameters);
-      });
-  }
+    function showNewInvitation(parameters) {
+      return csWallet.auth({minData: true})
+        .then(function(walletData) {
+          UIUtils.loading.hide();
+
+          // Not allow for non-member - issue #561
+          if (!walletData.isMember) {
+            return UIUtils.alert.error('ERROR.ONLY_MEMBER_CAN_EXECUTE_THIS_ACTION');
+          }
+          return ModalUtils.show('plugins/es/templates/invitation/modal_new_invitation.html', 'ESNewInvitationModalCtrl',
+            parameters);
+        });
+    }
+
+    function showNewPage(options) {
+      var wallet = options && options.wallet || csWallet;
+      return wallet.auth({minData: true})
+        .then(function() {
+          UIUtils.loading.hide();
 
-  function showNewPage(options) {
-    var wallet = options && options.wallet || csWallet;
-    return wallet.auth({minData: true})
-      .then(function() {
-        UIUtils.loading.hide();
+          return ModalUtils.show('plugins/es/templates/registry/modal_record_type.html', undefined, {
+            title: 'REGISTRY.EDIT.TITLE_NEW'
+          })
+            .then(function(type){
+              if (type) {
+                $state.go('app.registry_add_record', {type: type, wallet: wallet.id});
+              }
+            });
+        });
+    }
 
-        return ModalUtils.show('plugins/es/templates/registry/modal_record_type.html', undefined, {
-          title: 'REGISTRY.EDIT.TITLE_NEW'
-        })
-          .then(function(type){
-            if (type) {
-              $state.go('app.registry_add_record', {type: type, wallet: wallet.id});
-            }
-          });
-      });
-  }
+    function showNetworkLookup(parameters) {
+      return ModalUtils.show('plugins/es/templates/network/modal_network.html', 'NetworkLookupModalCtrl',
+        parameters, {focusFirstInput: true});
+    }
 
-  return {
-    showMessageCompose: showMessageCompose,
-    showNotifications: showNotificationsPopover,
-    showNewInvitation: showNewInvitation,
-    showNewPage: showNewPage
-  };
+    return {
+      showMessageCompose: showMessageCompose,
+      showNotifications: showNotificationsPopover,
+      showNewInvitation: showNewInvitation,
+      showNewPage: showNewPage,
+      showNetworkLookup: showNetworkLookup
+    };
 
-});
+  });
diff --git a/www/plugins/es/js/services/network-services.js b/www/plugins/es/js/services/network-services.js
new file mode 100644
index 00000000..d9589f54
--- /dev/null
+++ b/www/plugins/es/js/services/network-services.js
@@ -0,0 +1,645 @@
+
+angular.module('cesium.es.network.services', ['ngApi', 'cesium.es.http.services'])
+
+.factory('esNetwork', function($rootScope, $q, $interval, $timeout, $window, csSettings, csConfig, esHttp, Api, BMA) {
+  'ngInject';
+
+  factory = function(id) {
+
+    var
+      interval,
+      constants = {
+        UNKNOWN_BUID: -1
+      },
+      isHttpsMode = $window.location.protocol === 'https:',
+      api = new Api(this, "csNetwork-" + id),
+
+      data = {
+        pod: null,
+        listeners: [],
+        loading: true,
+        peers: [],
+        filter: {
+          endpointFilter: null,
+          online: true,
+          ssl: undefined,
+          tor: undefined
+        },
+        sort:{
+          type: null,
+          asc: true
+        },
+        expertMode: false,
+        knownBlocks: [],
+        mainBlock: null,
+        searchingPeersOnNetwork: false,
+        timeout: csConfig.timeout
+      },
+
+      // Return the block uid
+      buid = function(block) {
+        return block && [block.number, block.hash].join('-');
+      },
+
+      resetData = function() {
+        data.pod = null;
+        data.listeners = [];
+        data.peers.splice(0);
+        data.filter = {
+          endpointFilter: null,
+          online: true
+        };
+        data.sort = {
+          type: null,
+          asc: true
+        };
+        data.expertMode = false;
+        data.knownBlocks = [];
+        data.mainBlock = null;
+        data.loading = true;
+        data.searchingPeersOnNetwork = false;
+        data.timeout = csConfig.timeout;
+
+        data.document = {
+          index : csSettings.data.plugins.es && csSettings.data.plugins.es.document && csSettings.data.plugins.es.document.index || 'user',
+          type: csSettings.data.plugins.es && csSettings.data.plugins.es.document && csSettings.data.plugins.es.document.type || 'profile'
+        };
+      },
+
+      hasPeers = function() {
+        return data.peers && data.peers.length > 0;
+      },
+
+      getPeers = function() {
+        return data.peers;
+      },
+
+      isBusy = function() {
+        return data.loading;
+      },
+
+      getKnownBlocks = function() {
+        return data.knownBlocks;
+      },
+
+      loadPeers = function() {
+        data.peers = [];
+        data.searchingPeersOnNetwork = true;
+        data.loading = true;
+        data.pod = data.pod || esHttp;
+        var newPeers = [];
+
+        if (interval) {
+          $interval.cancel(interval);
+        }
+
+        interval = $interval(function() {
+          // not same job instance
+          if (newPeers.length) {
+            flushNewPeersAndSort(newPeers);
+          }
+          else if (data.loading && !data.searchingPeersOnNetwork) {
+            data.loading = false;
+            $interval.cancel(interval);
+            // The peer lookup end, we can make a clean final report
+            sortPeers(true/*update main buid*/);
+
+            console.debug('[network] Finish: {0} peers found.'.format(data.peers.length));
+          }
+        }, 1000);
+
+        return $q.when()
+          .then(function(){
+            // online nodes
+            if (data.filter.online) {
+              return data.pod.network.peers()
+                .then(function(res){
+                  var jobs = [];
+                  _.forEach(res.peers, function(json) {
+                    if (json.status == 'UP') {
+                      jobs.push(addOrRefreshPeerFromJson(json, newPeers));
+                    }
+                  });
+
+                  if (jobs.length) return $q.all(jobs);
+                })
+                .catch(function(err) {
+                  // Log and continue
+                  console.error(err);
+                });
+            }
+
+            // offline nodes
+            return data.pod.network.peers()
+              .then(function(res){
+                var jobs = [];
+                _.forEach(res.peers, function(json) {
+                  if (json.status !== 'UP') {
+                    jobs.push(addOrRefreshPeerFromJson(json, newPeers));
+                  }
+                });
+                if (jobs.length) return $q.all(jobs);
+              });
+          })
+
+          .then(function(){
+            data.searchingPeersOnNetwork = false;
+          })
+          .catch(function(err){
+            console.error(err);
+            data.searchingPeersOnNetwork = false;
+          });
+      },
+
+      /**
+       * Apply filter on a peer. (peer uid should have been filled BEFORE)
+       */
+      applyPeerFilter = function(peer) {
+        // no filter
+        if (!data.filter) return true;
+
+        // Filter on endpoints
+        if (data.filter.endpointFilter &&
+          (peer.ep && peer.ep.api && peer.ep.api !== data.filter.endpointFilter || !peer.hasEndpoint(data.filter.endpointFilter))) {
+          return false;
+        }
+
+        // Filter on status
+        if (!data.filter.online && peer.status == 'UP') {
+          return false;
+        }
+
+        // Filter on ssl
+        if (angular.isDefined(data.filter.ssl) && peer.isSsl() != data.filter.ssl) {
+          return false;
+        }
+
+        // Filter on tor
+        if (angular.isDefined(data.filter.tor) && peer.isTor() != data.filter.tor) {
+          return false;
+        }
+
+        return true;
+      },
+
+      addOrRefreshPeerFromJson = function(json, list) {
+        list = list || data.newPeers;
+
+        var peers = createPeerEntities(json);
+        var hasUpdates = false;
+
+        var jobs = peers.reduce(function(jobs, peer) {
+            var existingPeer = _.findWhere(data.peers, {id: peer.id});
+            var existingMainBuid = existingPeer ? existingPeer.buid : null;
+            var existingOnline = existingPeer ? existingPeer.online : false;
+
+            return jobs.concat(
+              refreshPeer(peer)
+                .then(function (refreshedPeer) {
+                  if (existingPeer) {
+                    // remove existing peers, when reject or offline
+                    if (!refreshedPeer || (refreshedPeer.online !== data.filter.online && data.filter.online !== 'all')) {
+                      console.debug('[network] Peer [{0}] removed (cause: {1})'.format(peer.server, !refreshedPeer ? 'filtered' : (refreshedPeer.online ? 'UP': 'DOWN')));
+                      data.peers.splice(data.peers.indexOf(existingPeer), 1);
+                      hasUpdates = true;
+                    }
+                    else if (refreshedPeer.buid !== existingMainBuid){
+                      console.debug('[network] {0} endpoint [{1}] new current block'.format(
+                        refreshedPeer.ep && (refreshedPeer.ep.useBma ? 'BMA' : 'WS2P') || 'null',
+                        refreshedPeer.server));
+                      hasUpdates = true;
+                    }
+                    else if (existingOnline !== refreshedPeer.online){
+                      console.debug('[network] {0} endpoint [{1}] is now {2}'.format(
+                        refreshedPeer.ep && (refreshedPeer.ep.useBma ? 'BMA' : 'WS2P') || 'null',
+                        refreshedPeer.server,
+                        refreshedPeer.online ? 'UP' : 'DOWN'));
+                      hasUpdates = true;
+                    }
+                    else {
+                      console.debug("[network] {0} endpoint [{1}] unchanged".format(
+                        refreshedPeer.ep && (refreshedPeer.ep.useBma ? 'BMA' : 'WS2P') || 'null',
+                        refreshedPeer.server));
+                    }
+                  }
+                  else if (refreshedPeer && (refreshedPeer.online === data.filter.online || data.filter.online === 'all')) {
+                    console.debug("[network] {0} endpoint [{1}] is {2}".format(
+                      refreshedPeer.ep && refreshedPeer.ep.api || '',
+                      refreshedPeer.server,
+                      refreshedPeer.online ? 'UP' : 'DOWN'
+                    ));
+                    list.push(refreshedPeer);
+                    hasUpdates = true;
+                  }
+                })
+           );
+        }, []);
+        return (jobs.length === 1 ? jobs[0] : $q.all(jobs))
+          .then(function() {
+            return hasUpdates;
+          });
+      },
+
+      createPeerEntities = function(json, ep) {
+        if (!json) return [];
+        var peer = new EsPeer(json);
+
+        // Read endpoints
+        if (!ep) {
+          var endpointsAsString = peer.getEndpoints();
+          if (!endpointsAsString) return []; // no BMA
+
+          var endpoints = endpointsAsString.reduce(function (res, epStr) {
+            var ep = esHttp.node.parseEndPoint(epStr);
+            return ep ? res.concat(ep) : res;
+          }, []);
+
+          // recursive call, on each endpoint
+          if (endpoints.length > 1) {
+            return endpoints.reduce(function (res, ep) {
+              return res.concat(createPeerEntities(json, ep));
+            }, []);
+          }
+          else {
+            // if only one endpoint: use it and continue
+            ep = endpoints[0];
+          }
+        }
+        peer.ep = ep;
+        peer.server = peer.getServer();
+        peer.dns = peer.getDns();
+        peer.blockNumber = peer.block && peer.block.replace(/-.+$/, '');
+        peer.id = peer.keyID();
+        return [peer];
+      },
+
+      refreshPeer = function(peer) {
+
+        // Apply filter
+        if (!applyPeerFilter(peer)) return $q.when();
+
+        if (!data.filter.online || (data.filter.online === 'all' && peer.status === 'DOWN') || !peer.getHost() /*fix #537*/) {
+          peer.online = false;
+          return $q.when(peer);
+        }
+
+        // App running in SSL: Do not try to access not SSL node,
+        if (isHttpsMode && !peer.isSsl()) {
+          peer.online = (peer.status === 'UP');
+          peer.buid = constants.UNKNOWN_BUID;
+          delete peer.version;
+
+          return $q.when(peer);
+        }
+
+        // Do not try to access TOR or WS2P endpoints
+        if (peer.ep.useTor) {
+          peer.online = (peer.status == 'UP');
+          peer.buid = constants.UNKNOWN_BUID;
+          delete peer.software;
+          delete peer.version;
+          return $q.when(peer);
+        }
+
+        peer.api = peer.api ||  esHttp.lightInstance(peer.getHost(), peer.getPort(), peer.isSsl(), data.timeout);
+
+        // Get current block
+        return peer.api.blockchain.current()
+          .then(function(block) {
+            peer.currentNumber = block.number;
+            peer.online = true;
+            peer.buid = buid(block);
+            peer.medianTime = block.medianTime;
+            if (data.knownBlocks.indexOf(peer.buid) === -1) {
+              data.knownBlocks.push(peer.buid);
+            }
+            return peer;
+          })
+          .catch(function(err) {
+            // Special case for currency init (root block not exists): use fixed values
+            if (err && err.ucode == BMA.errorCodes.NO_CURRENT_BLOCK) {
+              peer.online = true;
+              peer.buid = buid({number:0, hash: BMA.constants.ROOT_BLOCK_HASH});
+              peer.difficulty  = 0;
+              return peer;
+            }
+            if (!peer.secondTry) {
+              var ep = peer.ep || peer.getEP();
+              if (ep.dns && peer.server.indexOf(ep.dns) == -1) {
+                // try again, using DNS instead of IPv4 / IPV6
+                peer.secondTry = true;
+                peer.api = esHttp.lightInstance(ep.dns, ep.port, ep.useSsl);
+                return refreshPeer(peer); // recursive call
+              }
+            }
+
+            peer.online=false;
+            peer.currentNumber = null;
+            peer.buid = null;
+            return peer;
+          })
+          .then(function(peer) {
+            // Exit if offline
+            if (!data.filter.online || !peer || !peer.online) return peer;
+
+            peer.docCount = {};
+
+            return $q.all([
+              // Get summary (software and version) - expert mode only
+              !data.expertMode ? $q.when() : peer.api.node.summary()
+                .then(function(res){
+                  peer.software = res && res.duniter && res.duniter.software || undefined;
+                  peer.version = res && res.duniter && res.duniter.version || '?';
+                })
+                .catch(function() {
+                  peer.software = undefined;
+                  peer.version = '?';
+                }),
+
+              // Count documents
+              peer.api.record.count(data.document.index,data.document.type)
+                .then(function(count){
+                  peer.docCount.record = count;
+                })
+                .catch(function() {
+                  peer.docCount.record = undefined;
+                }),
+
+              // Count email subscription
+              peer.api.subscription.count({recipient: peer.pubkey, type: 'email'})
+                .then(function(res){
+                  peer.docCount.emailSubscription = res;
+                })
+                .catch(function() {
+                  peer.docCount.emailSubscription = undefined; // continue
+                })
+            ]);
+
+        })
+        .then(function() {
+          // Clean the instance
+          delete peer.api;
+          return peer;
+        });
+      },
+
+      flushNewPeersAndSort = function(newPeers, updateMainBuid) {
+        newPeers = newPeers || data.newPeers;
+        if (!newPeers.length) return;
+        var ids = _.map(data.peers, function(peer){
+          return peer.id;
+        });
+        var hasUpdates = false;
+        var newPeersAdded = 0;
+        _.forEach(newPeers.splice(0), function(peer) {
+          if (!ids[peer.id]) {
+            data.peers.push(peer);
+            ids[peer.id] = peer;
+            hasUpdates = true;
+            newPeersAdded++;
+          }
+        });
+        if (hasUpdates) {
+          console.debug('[network] Flushing {0} new peers...'.format(newPeersAdded));
+          sortPeers(updateMainBuid);
+        }
+      },
+
+      computeScoreAlphaValue = function(value, nbChars, asc) {
+        if (!value) return 0;
+        var score = 0;
+        value = value.toLowerCase();
+        if (nbChars > value.length) {
+          nbChars = value.length;
+        }
+        score += value.charCodeAt(0);
+        for (var i=1; i < nbChars; i++) {
+          score += Math.pow(0.001, i) * value.charCodeAt(i);
+        }
+        return asc ? (1000 - score) : score;
+      },
+
+      sortPeers = function(updateMainBuid) {
+        // Construct a map of buid, with peer count and medianTime
+        var buids = {};
+        _.forEach(data.peers, function(peer){
+          if (peer.buid) {
+            var buid = buids[peer.buid];
+            if (!buid || !buid.medianTime) {
+              buid = {
+                buid: peer.buid,
+                count: 0,
+                medianTime: peer.medianTime
+              };
+              buids[peer.buid] = buid;
+            }
+            // If not already done, try to fill medianTime (need to compute consensusBlockDelta)
+            else if (!buid.medianTime && peer.medianTime) {
+              buid.medianTime = peer.medianTime;
+            }
+            if (buid.buid != constants.UNKNOWN_BUID) {
+              buid.count++;
+            }
+          }
+        });
+        // Compute pct of use, per buid
+        _.forEach(_.values(buids), function(buid) {
+          buid.pct = buid.count * 100 / data.peers.length;
+        });
+        var mainBlock = _.max(buids, function(obj) {
+          return obj.count;
+        });
+        _.forEach(data.peers, function(peer){
+          peer.hasMainConsensusBlock = peer.buid == mainBlock.buid;
+          peer.hasConsensusBlock = peer.buid && !peer.hasMainConsensusBlock && buids[peer.buid].count > 1;
+          if (peer.hasConsensusBlock) {
+            peer.consensusBlockDelta = buids[peer.buid].medianTime - mainBlock.medianTime;
+          }
+        });
+        data.peers = _.uniq(data.peers, false, function(peer) {
+          return peer.id;
+        });
+        data.peers = _.sortBy(data.peers, function(peer) {
+          var score = 0;
+          if (data.sort.type) {
+            var sortScore = 0;
+            sortScore += (data.sort.type == 'name' ? computeScoreAlphaValue(peer.name, 10, data.sort.asc) : 0);
+            sortScore += (data.sort.type == 'software' ? computeScoreAlphaValue(peer.software, 10, data.sort.asc) : 0);
+            sortScore += (data.sort.type == 'api') &&
+              ((peer.hasEndpoint('ES_SUBSCRIPTION_API') && (data.sort.asc ? 1 : -1) || 0) +
+              (peer.hasEndpoint('ES_USER_API') && (data.sort.asc ? 0.01 : -0.01) || 0) +
+              (peer.isSsl() && (data.sort.asc ? 0.75 : -0.75) || 0)) || 0;
+            sortScore += (data.sort.type == 'doc_count' ? (peer.docCount ? (data.sort.asc ? (1000000000 - peer.docCount) : peer.docCount) : 0) : 0);
+            score += (10000000000 * sortScore);
+          }
+          score += (1000000000 * (peer.online ? 1 : 0));
+          score += (100000000  * (peer.hasMainConsensusBlock ? 1 : 0));
+          score += (1000000    * (peer.hasConsensusBlock ? buids[peer.buid].pct : 0));
+          if (data.expertMode) {
+            score += (100     * (peer.difficulty ? (10000-peer.difficulty) : 0));
+            score += (1       * (peer.uid ? computeScoreAlphaValue(peer.uid, 2, true) : 0));
+          }
+          else {
+            score += (100     * (peer.uid ? computeScoreAlphaValue(peer.uid, 2, true) : 0));
+            score += (1       * (!peer.uid ? computeScoreAlphaValue(peer.pubkey, 2, true) : 0));
+          }
+          return -score;
+        });
+
+        // Raise event on new main block
+        if (updateMainBuid && mainBlock.buid && (!data.mainBlock || data.mainBlock.buid !== mainBlock.buid)) {
+          data.mainBlock = mainBlock;
+          api.data.raise.mainBlockChanged(mainBlock);
+        }
+
+        // Raise event when changed
+        api.data.raise.changed(data); // raise event
+      },
+
+      removeListeners = function() {
+        _.forEach(data.listeners, function(remove){
+          remove();
+        });
+        data.listeners = [];
+      },
+
+      addListeners = function() {
+        data.listeners = [
+
+          // Listen for new block
+          data.pod.websocket.block().onListener(function(block) {
+            if (!block || data.loading) return;
+            var buid = [block.number, block.hash].join('-');
+            if (data.knownBlocks.indexOf(buid) === -1) {
+              console.debug('[network] Receiving block: ' + buid.substring(0, 20));
+              data.knownBlocks.push(buid);
+              // If first block: do NOT refresh peers (will be done in start() method)
+              var skipRefreshPeers = data.knownBlocks.length === 1;
+              if (!skipRefreshPeers) {
+                data.loading = true;
+                // We wait 2s when a new block is received, just to wait for network propagation
+                $timeout(function() {
+                  console.debug('[network] new block received by WS: will refresh peers');
+                  loadPeers();
+                }, 2000, false /*invokeApply*/);
+              }
+            }
+          }),
+
+          // Listen for new peer
+          data.pod.websocket.peer().onListener(function(json) {
+            if (!json || data.loading) return;
+            var newPeers = [];
+            addOrRefreshPeerFromJson(json, newPeers)
+              .then(function(hasUpdates) {
+                if (!hasUpdates) return;
+                if (newPeers.length>0) {
+                  flushNewPeersAndSort(newPeers, true);
+                }
+                else {
+                  console.debug('[network] [ws] Peers updated received');
+                  sortPeers(true);
+                }
+              });
+          })
+        ];
+      },
+
+      sort = function(options) {
+        options = options || {};
+        data.filter = options.filter ? angular.merge(data.filter, options.filter) : data.filter;
+        data.sort = options.sort ? angular.merge(data.sort, options.sort) : data.sort;
+        sortPeers(false);
+      },
+
+      start = function(pod, options) {
+        options = options || {};
+        return esHttp.ready()
+          .then(function() {
+            close();
+            data.pod = pod || esHttp;
+            data.filter = options.filter ? angular.merge(data.filter, options.filter) : data.filter;
+            data.sort = options.sort ? angular.merge(data.sort, options.sort) : data.sort;
+            data.expertMode = angular.isDefined(options.expertMode) ? options.expertMode : data.expertMode;
+            data.timeout = angular.isDefined(options.timeout) ? options.timeout : csConfig.timeout;
+            console.info('[network] Starting network from [{0}]'.format(data.pod.server));
+            var now = Date.now();
+
+            addListeners();
+
+            return loadPeers()
+              .then(function(peers){
+                console.debug('[network] Started in '+(Date.now() - now)+'ms');
+                return peers;
+              });
+          });
+      },
+
+      close = function() {
+        if (data.pod) {
+          console.info('[network-service] Stopping...');
+          removeListeners();
+        }
+        resetData();
+      },
+
+      isStarted = function() {
+        return !data.pod;
+      },
+
+      $q_started = function(callback) {
+        if (!isStarted()) { // start first
+          return start()
+            .then(function() {
+              return $q(callback);
+            });
+        }
+        else {
+          return $q(callback);
+        }
+      },
+
+      getMainBlockUid = function() {
+        return $q_started(function(resolve, reject){
+          resolve (data.mainBuid);
+        });
+      },
+
+      // Get peers on the main consensus blocks
+      getTrustedPeers = function() {
+        return $q_started(function(resolve, reject){
+          resolve(data.peers.reduce(function(res, peer){
+            return (peer.hasMainConsensusBlock && peer.uid) ? res.concat(peer) : res;
+          }, []));
+        });
+      }
+      ;
+
+    // Register extension points
+    api.registerEvent('data', 'changed');
+    api.registerEvent('data', 'mainBlockChanged');
+    api.registerEvent('data', 'rollback');
+
+    return {
+      id: id,
+      data: data,
+      start: start,
+      close: close,
+      hasPeers: hasPeers,
+      getPeers: getPeers,
+      sort: sort,
+      getTrustedPeers: getTrustedPeers,
+      getKnownBlocks: getKnownBlocks,
+      getMainBlockUid: getMainBlockUid,
+      loadPeers: loadPeers,
+      isBusy: isBusy,
+      // api extension
+      api: api
+    };
+  };
+
+  var service = factory('default');
+
+  service.instance = factory;
+  return service;
+});
diff --git a/www/plugins/es/templates/document/item_document_comment.html b/www/plugins/es/templates/document/item_document_comment.html
new file mode 100644
index 00000000..48a46ae3
--- /dev/null
+++ b/www/plugins/es/templates/document/item_document_comment.html
@@ -0,0 +1,44 @@
+<ion-item id="doc-{{::doc.id}}"
+          class="item item-document item-document-comment item-icon-left ink {{::ionItemClass}} no-padding-top no-padding-bottom"
+          ng-class="{'compacted': compactMode}"
+          ng-click="selectDocument($event, doc)">
+
+  <i ng-show=":rebind:!compactMode" ng-if=":rebind:!doc.avatar" class="icon ion-ios-chatbubble-outline stable" ></i>
+  <i ng-show=":rebind:!compactMode" ng-if=":rebind:doc.avatar" class="avatar" style="background-image: url('{{:rebind:doc.avatar.src}}')"></i>
+
+  <div class="row no-padding">
+    <div class="col">
+      <h4 >
+        <i class="ion-ios-chatbubble-outline dark"></i>
+        <span class="gray" ng-if=":rebind:doc.name">
+          <i class="ion-person" ng-show=":rebind:!compactMode"></i>
+          {{:rebind:doc.name}}:
+        </span>
+        <span class="dark">
+          <i class="ion-quote" ng-if=":rebind:!compactMode"></i>
+          {{:rebind:doc.message|truncText:50}}
+        </span>
+      </h4>
+      <h4 class="gray"> <i class="ion-clock"></i> {{:rebind:doc.time|formatDate}}</h4>
+    </div>
+
+    <div class="col">
+      <h3>
+        <a ui-sref="app.wot_identity({pubkey: doc.pubkey, uid: doc.name})">
+
+        </a>
+      </h3>
+    </div>
+
+    <div class="col" ng-if=":rebind:!compactMode">
+      <a
+        ng-if=":rebind:login && doc.pubkey==walletData.pubkey"
+        ng-click="remove($event, $index)"
+        class="gray pull-right hidden-xs hidden-sm"
+         title="{{'DOCUMENT.LOOKUP.BTN_REMOVE'|translate}}">
+        <i class="ion-trash-a"></i>
+      </a>
+    </div>
+
+  </div>
+</ion-item>
diff --git a/www/plugins/es/templates/document/item_document_profile.html b/www/plugins/es/templates/document/item_document_profile.html
new file mode 100644
index 00000000..94168814
--- /dev/null
+++ b/www/plugins/es/templates/document/item_document_profile.html
@@ -0,0 +1,41 @@
+<ion-item id="doc-{{::doc.id}}"
+          class="item item-document item-icon-left ink {{::ionItemClass}} no-padding-top no-padding-bottom"
+          ng-class="{'compacted': compactMode}"
+          ng-click="selectDocument($event, doc)">
+
+  <i ng-show=":rebind:!compactMode" ng-if=":rebind:doc.avatar" class="avatar" style="background-image: url({{:rebind:doc.avatar.src}})"></i>
+  <i ng-show=":rebind:!compactMode" ng-if=":rebind:!doc.avatar" class="icon ion-person stable"></i>
+
+  <div class="row no-padding">
+    <div class="col">
+      <h4 ng-if=":rebind:doc.title">
+        <i class="ion-person gray"></i>
+        <span class="dark">
+         {{:rebind:doc.title}}
+        </span>
+        <span class="gray" >
+          {{:rebind:'DOCUMENT.LOOKUP.HAS_REGISTERED'|translate}}
+        </span>
+      </h4>
+      <h4>
+        <span class="dark" ng-if=":rebind:doc.city">
+          <i class="ion-location"></i> {{:rebind:doc.city}}
+        </span>
+        <span class="gray">
+          <i class="ion-clock"></i> {{:rebind:doc.time|formatDate}}
+        </span>
+      </h4>
+    </div>
+
+    <div class="col" ng-if=":rebind:!compactMode">
+      <a
+        ng-if=":rebind:login && doc.pubkey==walletData.pubkey"
+        ng-click="remove($event, $index)"
+        class="gray pull-right"
+         title="{{'DOCUMENT.LOOKUP.BTN_REMOVE'|translate}}">
+        <i class="ion-trash-a"></i>
+      </a>
+    </div>
+
+  </div>
+</ion-item>
diff --git a/www/plugins/es/templates/document/items_documents.html b/www/plugins/es/templates/document/items_documents.html
index 6306e779..b72f39ca 100644
--- a/www/plugins/es/templates/document/items_documents.html
+++ b/www/plugins/es/templates/document/items_documents.html
@@ -1,5 +1,5 @@
 
-<div class="item row row-header done in hidden-xs hidden-sm">
+<div class="item row row-header done in hidden-xs hidden-sm" ng-if="showHeaders">
 
   <a class="no-padding dark col col-header"
      ng-if=":rebind:expertMode"
@@ -27,5 +27,14 @@
 
 <!-- for each doc -->
 <ng-repeat ng-repeat="doc in :rebind:search.results track by doc.id"
-           ng-include="'plugins/es/templates/document/item_document.html'">
+           ng-switch on="doc.type">
+  <div ng-switch-when="comment">
+    <ng-include src="::'plugins/es/templates/document/item_document_comment.html'"></ng-include>
+  </div>
+  <div ng-switch-when="profile">
+    <ng-include src="::'plugins/es/templates/document/item_document_profile.html'"></ng-include>
+  </div>
+  <div ng-switch-default>
+    <ng-include src="::'plugins/es/templates/document/item_document.html'"></ng-include>
+  </div>
 </ng-repeat>
diff --git a/www/plugins/es/templates/document/list_documents.html b/www/plugins/es/templates/document/list_documents.html
new file mode 100644
index 00000000..a1617984
--- /dev/null
+++ b/www/plugins/es/templates/document/list_documents.html
@@ -0,0 +1,13 @@
+
+<ion-list class="list" ng-class="::motion.ionListClass">
+
+  <ng-include src="'plugins/es/templates/document/items_documents.html'"></ng-include>
+
+</ion-list>
+
+<ion-infinite-scroll
+        ng-if="!search.loading && search.hasMore"
+        spinner="android"
+        on-infinite="showMore()"
+        distance="1%">
+</ion-infinite-scroll>
diff --git a/www/plugins/es/templates/network/item_content_peer.html b/www/plugins/es/templates/network/item_content_peer.html
new file mode 100644
index 00000000..efbad7b3
--- /dev/null
+++ b/www/plugins/es/templates/network/item_content_peer.html
@@ -0,0 +1,69 @@
+
+    <i class="icon ion-android-desktop"
+       ng-class=":rebind:{'balanced': peer.online && peer.hasMainConsensusBlock, 'energized': peer.online && peer.hasConsensusBlock, 'gray': peer.online && !peer.hasConsensusBlock && !peer.hasMainConsensusBlock, 'stable': !peer.online}"
+       ng-if=":rebind:!peer.avatar"></i>
+    <b class="icon-secondary ion-person" ng-if=":rebind:!peer.avatar"
+       ng-class=":rebind:{'balanced': peer.online && peer.hasMainConsensusBlock, 'energized': peer.online && peer.hasConsensusBlock, 'gray': peer.online && !peer.hasConsensusBlock && !peer.hasMainConsensusBlock, 'stable': !peer.online}"
+       style="left: 26px; top: -3px;"></b>
+    <i class="avatar" ng-if=":rebind:peer.avatar" style="background-image: url('{{:rebind:peer.avatar.src}}')"></i>
+    <b class="icon-secondary assertive ion-close-circled" ng-if=":rebind:!peer.online" style="left: 37px; top: -10px;"></b>
+
+    <div class="row no-padding">
+      <div class="col no-padding">
+        <h3 class="dark">{{:rebind:peer.dns || peer.server}}</h3>
+        <h4>
+          <span class="gray" ng-if=":rebind:!peer.name">
+            <i class="ion-key"></i> {{:rebind:peer.pubkey|formatPubkey}}
+          </span>
+          <span class="positive" ng-if=":rebind:peer.name">
+            <i class="ion-person"></i> {{:rebind:peer.name}}
+          </span>
+          <span class="gray">{{:rebind:peer.dns && (' | ' + peer.server) + (peer.ep.path||'') }}</span>
+        </h4>
+      </div>
+      <div class="col col-15 no-padding text-center hidden-xs hidden-sm" ng-if="::expertMode">
+        <div style="min-width: 50px; padding-top: 5px;" >
+          <span ng-if=":rebind:peer.isSsl()" title="SSL">
+            <i class="ion-locked"></i><small class="hidden-md"> SSL</small>
+          </span>
+          <span ng-if=":rebind:peer.hasEndpoint('ES_SUBSCRIPTION_API')"
+               title="{{'ES_PEER.EMAIL_SUBSCRIPTION_COUNT'|translate: peer.docCount }}">
+            <i class="ion-email"></i> {{:rebind:peer.docCount.emailSubscription || '?'}}
+          </span>
+        </div>
+        <div ng-if=":rebind:peer.isTor()">
+          <i class="ion-bma-tor-api"></i>
+        </div>
+        <div ng-if=":rebind:peer.isWs2p()&&peer.isTor()" ng-click="showWs2pPopover($event, peer)">
+          <i class="ion-bma-tor-api"></i>
+        </div>
+      </div>
+      <div class="col col-20 no-padding text-center" ng-if="::!expertMode && search.type != 'offline'">
+        <div style="min-width: 50px; padding-top: 5px;" ng-if=":rebind:peer.docCount.emailSubscription!==undefined">
+          <span ng-if=":rebind:peer.hasEndpoint('ES_SUBSCRIPTION_API')"
+              title="{{'ES_PEER.EMAIL_SUBSCRIPTION_COUNT'|translate: peer.docCount }}">
+            <i class="ion-email"></i> {{:rebind:peer.docCount.emailSubscription || '?'}}
+          </span>
+        </div>
+      </div>
+      <div class="col col-20 no-padding text-center" ng-if="::expertMode && search.type != 'offline'">
+        <h4 class="hidden-sm hidden-xs gray">
+          {{:rebind:peer.software||'?'}}
+        </h4>
+        <h4 class="hidden-sm hidden-xs gray">{{:rebind: peer.version ? ('v'+peer.version) : ''}}</h4>
+      </div>
+      <div class="col col-20 no-padding text-center" id="{{$index === 0 ? helptipPrefix + '-peer-0-block' : ''}}">
+        <span class="badge badge-stable">
+          {{:rebind:peer.docCount.record !== undefined ? (peer.docCount.record|formatInteger) : '?'}}
+          <span ng-if=":rebind:!expertMode && peer.docCount.record!==undefined">
+            {{::'ES_PEER.DOCUMENTS'|translate|lowercase }}
+          </span>
+        </span>
+        <span class="badge badge-secondary"
+              ng-class=":rebind:{'balanced': peer.hasMainConsensusBlock, 'energized': peer.hasConsensusBlock, 'ng-hide': !peer.currentNumber }"
+              ng-if="::expertMode">
+          {{:rebind:'BLOCKCHAIN.VIEW.TITLE'|translate: {number:peer.currentNumber} }}
+        </span>
+
+      </div>
+    </div>
diff --git a/www/plugins/es/templates/network/items_peers.html b/www/plugins/es/templates/network/items_peers.html
new file mode 100644
index 00000000..d2c6b622
--- /dev/null
+++ b/www/plugins/es/templates/network/items_peers.html
@@ -0,0 +1,36 @@
+<div ng-class="::motion.ionListClass" class="no-padding">
+
+  <div class="item item-text-wrap no-border done in gray no-padding-top no-padding-bottom inline text-italic" ng-if="::isHttps && expertMode">
+    <small><i class="icon ion-alert-circled"></i> {{::'NETWORK.INFO.ONLY_SSL_PEERS'|translate}}</small>
+  </div>
+
+  <div class="item row row-header hidden-xs hidden-sm done in" ng-if="::expertMode">
+    <a class="col col-header no-padding dark" ng-click="toggleSort('name')">
+      <cs-sort-icon asc="search.asc" sort="search.sort" toggle="'name'"></cs-sort-icon>
+      {{::'ES_PEER.NAME' | translate}} / {{::'COMMON.PUBKEY' | translate}}
+    </a>
+    <a class="no-padding dark hidden-md col col-15 col-header"
+       ng-click="toggleSort('api')">
+      <cs-sort-icon asc="search.asc" sort="search.sort" toggle="'api'"></cs-sort-icon>
+      {{::'PEER.API' | translate}}
+    </a>
+    <a class="no-padding dark col col-20 col-header"
+       ng-click="toggleSort('difficulty')">
+      <cs-sort-icon asc="search.asc" sort="search.sort" toggle="'software'"></cs-sort-icon>
+      {{::'ES_PEER.SOFTWARE' | translate}}
+    </a>
+    <a class="col col-20 col-header no-padding dark" ng-click="toggleSort('doc_count')">
+      <cs-sort-icon asc="search.asc" sort="search.sort" toggle="'doc_count'"></cs-sort-icon>
+      {{::'ES_PEER.DOCUMENTS' | translate}}
+    </a>
+  </div>
+
+  <div ng-repeat="peer in :rebind:search.results track by peer.id"
+       class="item item-peer item-icon-left ink"
+       ng-class="::ionItemClass"
+       id="{{helptipPrefix}}-peer-{{$index}}"
+       ng-click="selectPeer(peer)"
+       ng-include="'plugins/es/templates/network/item_content_peer.html'">
+  </div>
+
+</div>
diff --git a/www/plugins/es/templates/network/lookup_popover_actions.html b/www/plugins/es/templates/network/lookup_popover_actions.html
new file mode 100644
index 00000000..ac30de8e
--- /dev/null
+++ b/www/plugins/es/templates/network/lookup_popover_actions.html
@@ -0,0 +1,31 @@
+<ion-popover-view class="fit has-header">
+  <ion-header-bar>
+    <h1 class="title" translate>PEER.POPOVER_FILTER_TITLE</h1>
+  </ion-header-bar>
+  <ion-content scroll="false">
+    <div class="list item-text-wrap">
+
+      <a class="item item-icon-left item-icon-right ink"
+         ng-click="toggleSearchType('member')">
+        <i class="icon ion-person"></i>
+        {{'PEER.MEMBERS' | translate}}
+        <i class="icon ion-ios-checkmark-empty" ng-show="search.type=='member'"></i>
+      </a>
+
+      <a class="item item-icon-left item-icon-right ink"
+         ng-click="toggleSearchType('mirror')">
+        <i class="icon ion-radio-waves"></i>
+        {{'PEER.MIRRORS' | translate}}
+        <i class="icon ion-ios-checkmark-empty" ng-show="search.type=='mirror'"></i>
+      </a>
+
+      <a class="item item-icon-left item-icon-right ink"
+         ng-click="toggleSearchType('offline')">
+        <i class="icon ion-eye-disabled"></i>
+        {{'PEER.OFFLINE' | translate}}
+        <i class="icon ion-ios-checkmark-empty" ng-show="search.type=='offline'"></i>
+      </a>
+
+    </div>
+  </ion-content>
+</ion-popover-view>
diff --git a/www/plugins/es/templates/network/modal_network.html b/www/plugins/es/templates/network/modal_network.html
new file mode 100644
index 00000000..efe1c1dd
--- /dev/null
+++ b/www/plugins/es/templates/network/modal_network.html
@@ -0,0 +1,36 @@
+<ion-modal-view id="nodes" class="modal-full-height" cache-view="false">
+  <ion-header-bar class="bar-positive">
+    <button class="button button-clear" ng-click="closeModal()" translate>COMMON.BTN_CANCEL</button>
+    <h1 class="title" translate>PEER.PEER_LIST</h1>
+    <div class="buttons buttons-right header-item">
+      <span class="secondary">
+        <button class="button button-clear icon ion-loop button-clear" ng-click="refresh()">
+
+        </button>
+        <button class="button button-icon button-clear icon ion-android-more-vertical visible-xs visible-sm"
+                ng-click="showActionsPopover($event)">
+        </button>
+      </span>
+    </div>
+  </ion-header-bar>
+
+  <ion-content>
+    <div class="list">
+      <div class="padding padding-xs" style="display: block; height: 60px;">
+
+        <div class="pull-left">
+          <h4 ng-if="!enableFilter || !search.type">
+            {{'PEER.ALL_PEERS' | translate}} <span ng-if="!search.loading">({{search.results.length}})</span>
+          </h4>
+        </div>
+
+        <div class="pull-right">
+          <ion-spinner class="icon" icon="android" ng-if="search.loading"></ion-spinner>&nbsp;
+        </div>
+      </div>
+
+      <ng-include src="'plugins/es/templates/network/items_peers.html'"></ng-include>
+
+	  </div>
+  </ion-content>
+</ion-modal-view>
diff --git a/www/plugins/es/templates/network/popover_endpoints.html b/www/plugins/es/templates/network/popover_endpoints.html
new file mode 100644
index 00000000..75338af0
--- /dev/null
+++ b/www/plugins/es/templates/network/popover_endpoints.html
@@ -0,0 +1,15 @@
+<ion-popover-view class="popover-endpoints popover-light" style="height: {{(titleKey?30:0)+((!items || items.length &lt;= 1) ? 55 : 3+items.length*52)}}px">
+  <ion-header-bar class="bar bar-header stable-bg" ng-if="titleKey">
+    <div class="title">
+      {{titleKey | translate:titleValues }}
+    </div>
+  </ion-header-bar>
+  <ion-content scroll="false">
+    <div class="list" ng-class="{'has-header': titleKey}">
+      <div class="item item-text-wrap" ng-repeat="item in items">
+        <div class="item-label" ng-if="item.label">{{item.label | translate}}</div>
+        <div id="endpoint_{{$index}}" class="badge item-note dark">{{item.value}}</span>
+      </div>
+    </div>
+  </ion-content>
+</ion-popover-view>
diff --git a/www/plugins/es/templates/network/popover_network.html b/www/plugins/es/templates/network/popover_network.html
new file mode 100644
index 00000000..ce40b917
--- /dev/null
+++ b/www/plugins/es/templates/network/popover_network.html
@@ -0,0 +1,38 @@
+<ion-popover-view class="fit hidden-xs hidden-sm popover-notification popover-network"
+                  ng-controller="NetworkLookupPopoverCtrl">
+  <ion-header-bar class="stable-bg block">
+    <div class="title">
+      {{'MENU.NETWORK'|translate}}
+      <ion-spinner class="ion-spinner-small" icon="android" ng-if="search.loading"></ion-spinner>
+    </div>
+
+    <div class="pull-right">
+      <a ng-class="{'positive': search.type=='member', 'dark': search.type!=='member'}"
+         ng-click="toggleSearchType('member')"
+         translate>PEER.MEMBERS</a>
+    </div>
+  </ion-header-bar>
+  <ion-content  scroll="true">
+    <div class="list no-padding">
+      <ng-include src="'plugins/es/templates/network/items_peers.html'"></ng-include>
+    </div>
+  </ion-content>
+
+  <ion-footer-bar class="stable-bg block">
+    <!-- settings -->
+    <div class="pull-left">
+      <a class="positive"
+         ui-sref="app.settings"
+         ng-click="closePopover()"
+         translate>COMMON.NOTIFICATIONS.SETTINGS</a>
+    </div>
+
+    <!-- show all -->
+    <div class="pull-right">
+      <a class="positive"
+         ui-sref="app.es_network"
+         ng-click="closePopover()"
+         translate>COMMON.NOTIFICATIONS.SHOW_ALL</a>
+    </div>
+  </ion-footer-bar>
+</ion-popover-view>
diff --git a/www/plugins/es/templates/network/popover_peer_info.html b/www/plugins/es/templates/network/popover_peer_info.html
new file mode 100644
index 00000000..a01e83fd
--- /dev/null
+++ b/www/plugins/es/templates/network/popover_peer_info.html
@@ -0,0 +1,86 @@
+<ion-popover-view class="fit hidden-xs hidden-sm popover-notification popover-peer-info"
+                  ng-controller="PeerInfoPopoverCtrl">
+  <ion-header-bar class="stable-bg block">
+    <div class="title">
+      {{'PEER.VIEW.TITLE'|translate}}
+    </div>
+  </ion-header-bar>
+  <ion-content  scroll="true" >
+    <div class="center padding" ng-if="loading">
+      <ion-spinner icon="android"></ion-spinner>
+    </div>
+
+    <div class="list no-padding" ng-if="!loading">
+
+      <div class="item" ng-if=":rebind:formData.software">
+        <i class="ion-outlet"></i>
+        {{'NETWORK.VIEW.SOFTWARE'|translate}}
+        <div class="badge"
+             ng-class=":rebind:{'badge-energized': formData.isPreRelease, 'badge-assertive': formData.hasNewRelease }">
+          {{formData.software}} v{{:rebind:formData.version}}
+        </div>
+        <div class="gray badge badge-secondary" ng-if="formData.isPreRelease">
+          <i class="ion-alert-circled"></i>
+          <span ng-bind-html="'NETWORK.VIEW.WARN_PRE_RELEASE'|translate: formData.latestRelease"></span>
+        </div>
+        <div class="gray badge badge-secondary" ng-if="formData.hasNewRelease">
+          <i class="ion-alert-circled"></i>
+          <span ng-bind-html="'NETWORK.VIEW.WARN_NEW_RELEASE'|translate: formData.latestRelease"></span>
+        </div>
+      </div>
+
+      <div class="item">
+        <i class="ion-locked"></i>
+        {{'NETWORK.VIEW.ENDPOINTS.BMAS'|translate}}
+        <div class="badge badge-balanced" ng-if=":rebind:formData.useSsl" translate>COMMON.BTN_YES</div>
+        <div class="badge badge-assertive" ng-if=":rebind:!formData.useSsl" translate>COMMON.BTN_NO</div>
+      </div>
+
+      <div class="item">
+        <i class="ion-cube"></i>
+        {{'BLOCKCHAIN.VIEW.TITLE_CURRENT'|translate}}
+        <div class="badge badge-balanced">
+          {{:rebind:formData.number | formatInteger}}
+        </div>
+      </div>
+
+      <div class="item">
+          <i class="ion-clock"></i>
+          {{'CURRENCY.VIEW.MEDIAN_TIME'|translate}}
+        <div class="badge dark">
+          {{:rebind:formData.medianTime | medianDate}}
+        </div>
+      </div>
+
+      <div class="item">
+        <i class="ion-lock-combination"></i>
+        {{'CURRENCY.VIEW.POW_MIN'|translate}}
+        <div class="badge dark">
+          {{:rebind:formData.powMin | formatInteger}}
+        </div>
+      </div>
+
+      <!-- Allow extension here -->
+      <cs-extension-point name="default"></cs-extension-point>
+
+    </div>
+  </ion-content>
+
+  <ion-footer-bar class="stable-bg block">
+    <!-- settings -->
+    <div class="pull-left">
+      <a class="positive"
+         ui-sref="app.settings"
+         ng-click="closePopover()"
+         translate>MENU.SETTINGS</a>
+    </div>
+
+    <!-- show all -->
+    <div class="pull-right">
+      <a class="positive"
+         ui-sref="app.view_es_peer"
+         ng-click="closePopover()"
+         translate>PEER.BTN_SHOW_PEER</a>
+    </div>
+  </ion-footer-bar>
+</ion-popover-view>
diff --git a/www/plugins/es/templates/network/view_network.html b/www/plugins/es/templates/network/view_network.html
new file mode 100644
index 00000000..d2c29d8c
--- /dev/null
+++ b/www/plugins/es/templates/network/view_network.html
@@ -0,0 +1,79 @@
+<ion-view>
+  <ion-nav-title>
+    <span translate>MENU.NETWORK</span>
+  </ion-nav-title>
+
+  <ion-nav-buttons side="secondary">
+    <button class="button button-icon button-clear icon ion-loop visible-xs visible-sm" ng-click="refresh()">
+    </button>
+  </ion-nav-buttons>
+
+
+  <ion-content scroll="true" ng-init="enableFilter=true; ionItemClass='item-border-large';">
+
+    <div class="row responsive-sm responsive-md responsive-lg">
+      <div class="col list col-border-right">
+        <div class="padding padding-xs" style="display: block; height: 60px;">
+          <div class="pull-left">
+            <h4>
+              <span ng-if="enableFilter && !search.online" translate>PEER.OFFLINE_PEERS</span>
+              <span ng-if="!enableFilter || search.online" translate>PEER.ALL_PEERS</span>
+              <span ng-if="search.results.length">({{search.results.length}})</span>
+              <ion-spinner ng-if="search.loading" class="icon ion-spinner-small" icon="android"></ion-spinner>
+            </h4>
+          </div>
+
+          <div class="pull-right">
+
+            <div class="pull-right" ng-if="enableFilter">
+
+              <a class="button button-text button-small hidden-xs hidden-sm ink"
+                 ng-class="{'button-text-positive': !search.online, 'button-text-stable': search.online}"
+                 ng-click="toggleOnline(!search.online)" >
+                <i class="icon ion-close-circled light-gray"></i>
+                <span>{{'PEER.OFFLINE'|translate}}</span>
+              </a>
+
+              <!-- Allow extension here -->
+              <cs-extension-point name="filter-buttons"></cs-extension-point>
+            </div>
+          </div>
+        </div>
+
+        <div id="helptip-network-peers" style="display: block"></div>
+
+        <ng-include src="'plugins/es/templates/network/items_peers.html'"></ng-include>
+      </div>
+
+      <div class="col col-33 " ng-controller="ESLastDocumentsCtrl">
+        <div class="padding padding-xs" style="display: block;">
+          <h4 translate>DOCUMENT.LOOKUP.LAST_DOCUMENTS_DOTS</h4>
+
+          <div class="pull-right hidden-xs hidden-sm">
+            <a class="button button-text button-small ink"
+               ng-class="{'button-text-positive': compactMode, 'button-text-stable': !compactMode}"
+               ng-click="toggleCompactMode()" >
+              <i class="icon ion-navicon"></i>
+              <b class="icon-secondary ion-arrow-down-b" style="top: -8px; left: 5px; font-size: 8px;"></b>
+              <b class="icon-secondary ion-arrow-up-b" style="top: 4px; left: 5px; font-size: 8px;"></b>
+              <span>{{'DOCUMENT.LOOKUP.BTN_COMPACT'|translate}}</span>
+            </a>
+
+            <!-- Allow extension here -->
+            <cs-extension-point name="buttons"></cs-extension-point>
+
+            <a class="button button-text button-small ink"
+               ui-sref="app.document_search({index: search.index, type: search.type})" >
+              <i class="icon ion-android-search"></i>
+              <span>{{'COMMON.BTN_SEARCH'|translate}}</span>
+            </a>
+
+          </div>
+        </div>
+
+        <ng-include src="'plugins/es/templates/document/list_documents.html'"></ng-include>
+
+      </div>
+    </div>
+  </ion-content>
+</ion-view>
diff --git a/www/plugins/es/templates/network/view_peer.html b/www/plugins/es/templates/network/view_peer.html
new file mode 100644
index 00000000..8d102bf9
--- /dev/null
+++ b/www/plugins/es/templates/network/view_peer.html
@@ -0,0 +1,137 @@
+<ion-view>
+  <ion-nav-title>
+    <span translate>PEER.VIEW.TITLE</span>
+  </ion-nav-title>
+
+  <ion-content class="has-header" scroll="true">
+
+    <div class="row no-padding">
+      <div class="col col-20 hidden-xs hidden-sm">&nbsp;
+      </div>
+
+      <div class="col list">
+
+        <ion-item>
+          <h1>
+            <span translate>PEER.VIEW.TITLE</span>
+            <span class="gray">
+              {{node.host}}
+            </span>
+          </h1>
+          <h2 class="gray">
+            <i class="gray icon ion-android-globe"></i>
+            {{node.ep.dns || node.server}}
+            <span class="gray" ng-if="!loading && node.useSsl">
+              <i class="gray ion-locked"></i> <small>SSL</small>
+            </span>
+            <span class="gray" ng-if="!loading && node.useTor">
+              <i class="gray ion-bma-tor-api"></i>
+            </span>
+          </h2>
+
+          <!-- node owner -->
+          <h3>
+            <span class="dark">
+              <i class="icon ion-android-desktop"></i>
+              {{'PEER.VIEW.OWNER'|translate}}
+            </span>
+            <a class="positive"
+                  ng-if="node.name"
+                  ui-sref="app.wot_identity({pubkey: node.pubkey, uid: node.name})">
+              <i class="ion-person"></i> {{node.name}}
+            </a>
+            <span ng-if="!loading && !node.name">
+              <a class="gray"
+                 ui-sref="app.wot_identity({pubkey: node.pubkey})">
+                <i class="ion-key"></i>
+                {{node.pubkey|formatPubkey}}
+              </a>
+            </span>
+          </h3>
+
+          <h3>
+            <a ng-click="openRawPeering($event)">
+              <i class="icon ion-share"></i> {{'PEER.VIEW.SHOW_RAW_PEERING'|translate}}
+            </a>
+
+            <span class="gray" ng-if="!isReachable"> | </span>
+            <a ng-if="!isReachable"
+               ng-click="openRawCurrentBlock($event)">
+              <i class="icon ion-share"></i> <span translate>PEER.VIEW.SHOW_RAW_CURRENT_BLOCK</span>
+            </a>
+          </h3>
+        </ion-item>
+
+
+        <div class="item item-divider" translate>
+          PEER.VIEW.GENERAL_DIVIDER
+        </div>
+
+        <ion-item class="item-icon-left item-text-wrap ink"
+                  copy-on-click="{{node.pubkey}}">
+          <i class="icon ion-key"></i>
+          <span translate>COMMON.PUBKEY</span>
+          <h4 class="dark text-left">{{node.pubkey}}</h4>
+        </ion-item>
+
+        <ion-item class="item item-icon-left item-text-wrap"
+                  ng-if="isReachable">
+          <i class="icon ion-document"></i>
+          <span translate>ES_PEER.DOCUMENT_COUNT</span>
+          <div class="badge badge-stable" ng-if="!loading">
+            {{node.docCount|formatInteger}}
+          </div>
+        </ion-item>
+
+        <!--<a class="item item-icon-left item-icon-right item-text-wrap ink"-->
+           <!--ng-if="isReachable"-->
+           <!--ui-sref="app.document_search(options.document)">-->
+          <!--<i class="icon ion-document" style="font-size: 25px;"></i>-->
+          <!--<i class="icon-secondary ion-clock" style="font-size: 18px; left: 33px; top: -12px;"></i>-->
+          <!--<span translate>DOCUMENT.LOOKUP.LAST_DOCUMENTS</span>-->
+          <!--<i class="gray icon ion-ios-arrow-right"></i>-->
+        <!--</a>-->
+
+        <ion-item class="item item-icon-left item-text-wrap ink"
+                  ng-if="isReachable">
+          <i class="icon ion-cube"></i>
+          <span translate>BLOCKCHAIN.VIEW.TITLE_CURRENT</span>
+          <div class="badge badge-calm" ng-if="!loading">
+            {{current.number|formatInteger}}
+          </div>
+        </ion-item>
+
+        <!-- Allow extension here -->
+        <cs-extension-point name="general"></cs-extension-point>
+
+        <div class="item item-divider" ng-hide="loading || !isReachable" translate>
+          PEER.VIEW.KNOWN_PEERS
+        </div>
+
+        <ion-item class="item item-text-wrap no-border done in gray no-padding-top no-padding-bottom inline text-italic"
+                  ng-show="!loading && !isReachable">
+          <small><i class="icon ion-alert-circled"></i> {{'NETWORK.INFO.ONLY_SSL_PEERS'|translate}}</small>
+        </ion-item>
+
+        <div class="item center" ng-if="loading">
+            <ion-spinner class="icon" icon="android"></ion-spinner>
+        </div>
+
+        <div class="list no-padding {{::motion.ionListClass}}" ng-if="isReachable">
+
+          <div ng-repeat="peer in :rebind:peers track by peer.id"
+               class="item item-peer item-icon-left ink"
+               ng-class="::ionItemClass"
+               ng-click="selectPeer(peer)"
+               ng-include="'plugins/es/templates/network/item_content_peer.html'">
+          </div>
+
+        </div>
+      </div>
+
+      <div class="col col-20 hidden-xs hidden-sm">&nbsp;
+      </div>
+    </div>
+
+  </ion-content>
+</ion-view>
diff --git a/www/plugins/graph/js/controllers/docstats-controllers.js b/www/plugins/graph/js/controllers/docstats-controllers.js
index 2052e4b3..055e2e86 100644
--- a/www/plugins/graph/js/controllers/docstats-controllers.js
+++ b/www/plugins/graph/js/controllers/docstats-controllers.js
@@ -31,6 +31,8 @@ function GpDocStatsController($scope, $state, $controller, $q, $translate, gpCol
   // Initialize the super class and extend it.
   angular.extend(this, $controller('GpCurrencyAbstractCtrl', {$scope: $scope}));
 
+  $scope.formData.rangeDuration = 'month';
+
   $scope.displayRightAxis = true;
   $scope.hiddenDatasets = [];
 
@@ -353,7 +355,7 @@ function GpDocStatsController($scope, $state, $controller, $q, $translate, gpCol
     if (!values) return undefined;
     var previousValue;
     return _.map(values, function(value) {
-      var newValue = (value !== undefined) && (value - (previousValue || value)) || undefined;
+      var newValue = (value !== undefined && previousValue !== undefined) ? (value - (previousValue || value)) : undefined;
       previousValue = value;
       return newValue;
     });
diff --git a/www/plugins/graph/js/controllers/network-controllers.js b/www/plugins/graph/js/controllers/network-controllers.js
index 603a22a3..5ba4e008 100644
--- a/www/plugins/graph/js/controllers/network-controllers.js
+++ b/www/plugins/graph/js/controllers/network-controllers.js
@@ -25,6 +25,15 @@ angular.module('cesium.graph.network.controllers', ['chart.js', 'cesium.graph.se
             }
           }
         })
+
+        .extendState('app.es_network', {
+          points: {
+            'buttons': {
+              templateUrl: "plugins/graph/templates/network/view_es_network_extend.html",
+              controller: 'GpNetworkViewExtendCtrl'
+            }
+          }
+        })
       ;
 
       $stateProvider
diff --git a/www/plugins/graph/templates/network/view_es_network_extend.html b/www/plugins/graph/templates/network/view_es_network_extend.html
new file mode 100644
index 00000000..e2e0544a
--- /dev/null
+++ b/www/plugins/graph/templates/network/view_es_network_extend.html
@@ -0,0 +1,8 @@
+<!-- Buttons section -->
+<ng-if ng-if=":state:enable && extensionPoint === 'buttons'">
+  <a class="button button-text button-small ink"
+     ui-sref="app.doc_stats_lg" >
+    <i class="icon ion-stats-bars"></i>
+    <span>{{'NETWORK.VIEW.BTN_GRAPH'|translate}}</span>
+  </a>
+</ng-if>
diff --git a/www/plugins/graph/templates/network/view_network_extend.html b/www/plugins/graph/templates/network/view_network_extend.html
index 60ecb297..8b387b41 100644
--- a/www/plugins/graph/templates/network/view_network_extend.html
+++ b/www/plugins/graph/templates/network/view_network_extend.html
@@ -1,5 +1,5 @@
 <!-- Buttons section -->
-<ng-if ng-if="enable && extensionPoint === 'buttons'">
+<ng-if ng-if=":state:enable && extensionPoint === 'buttons'">
   <a class="button button-text button-small ink"
      ui-sref="app.blockchain_stats" >
     <i class="icon ion-stats-bars"></i>
diff --git a/www/templates/menu.html b/www/templates/menu.html
index 1be68ccd..65e5e325 100644
--- a/www/templates/menu.html
+++ b/www/templates/menu.html
@@ -82,8 +82,8 @@
           <i class="icon light ion-android-exit"></i>
         </a>
 
-        <!-- Fullscreen button -->
         <!-- removeIf(device) -->
+        <!-- Fullscreen button -->
         <a ng-if="::$root.device.isWeb()"
            ng-click="toggleFullscreen()"
            class="button-icon"
@@ -144,7 +144,7 @@
           <i class="icon-secondary ion-card" style="top: 22px; left: 19px; font-size: 20px; background-color: white; width:17px; height: 14px;"></i>
           {{:locale:'MENU.WALLETS'|translate}}
         </a>
-		  
+
         <!-- MAIN Section -->
         <div class="item item-divider"></div>
 
@@ -162,7 +162,7 @@
 
         <!-- POWER USER Section -->
         <div class="item item-divider"></div>
-		  
+
         <a menu-close
            class="item item-icon-left"
            active-link="active"
@@ -185,8 +185,8 @@
 
         <!-- Allow extension here -->
         <cs-extension-point name="menu-discover"></cs-extension-point>
-		  
-		  
+
+
 
 
         <div class="item item-divider visible-xs visible-sm"></div>
diff --git a/yarn.lock b/yarn.lock
index 3f409800..c122feb9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -12,6 +12,10 @@
   version "0.1.2"
   resolved "https://codeload.github.com/VivekKhandre/Leaflet.FeatureGroup.SubGroup/tar.gz/2ec699f11e1b6a8fa2596a1bb2b7a144d162c6d6"
 
+"@bower_components/Leaflet.awesome-markers@lvoogdt/Leaflet.awesome-markers#2.0.2":
+  version "0.0.0"
+  resolved "https://codeload.github.com/lvoogdt/Leaflet.awesome-markers/tar.gz/bfac1eb6f7896072d690bde57c1fb5002961a99a"
+
 "@bower_components/aes-js@ricmoo/aes-js#3.1.2":
   version "3.1.2"
   resolved "https://codeload.github.com/ricmoo/aes-js/tar.gz/7c9fad4add4b349dcb89a4e2125f37defaef3bc8"
@@ -151,10 +155,6 @@
   dependencies:
     leaflet "*"
 
-"@bower_components/leaflet.awesome-markers@lvoogdt/Leaflet.awesome-markers#2.0.2":
-  version "0.0.0"
-  resolved "https://codeload.github.com/lvoogdt/Leaflet.awesome-markers/tar.gz/bfac1eb6f7896072d690bde57c1fb5002961a99a"
-
 "@bower_components/leaflet.loading@ebrelsford/Leaflet.loading#^0.1.24":
   version "0.1.24"
   resolved "https://codeload.github.com/ebrelsford/Leaflet.loading/tar.gz/a1329ac02d709e9b63416b5cb09cea861a37fb4a"
-- 
GitLab