Skip to content
Snippets Groups Projects
network-controllers.js 14.82 KiB

angular.module('cesium.network.controllers', ['cesium.services'])

.config(function($stateProvider) {
  'ngInject';

  $stateProvider

    .state('app.network', {
      url: "/network?type&expert",
      cache: false,
      views: {
        'menuContent': {
          templateUrl: "templates/network/view_network.html",
          controller: 'NetworkLookupCtrl'
        }
      },
      data: {
        silentLocationChange: true
      }
    })

    .state('app.view_peer', {
      url: "/network/peer/:server?ssl&tor",
      cache: false,
      views: {
        'menuContent': {
          templateUrl: "templates/network/view_peer.html",
          controller: 'PeerViewCtrl'
        }
      },
      data: {
        preferHttp: true // avoid HTTPS if config has httpsMode=clever
      }
    });
})

.controller('NetworkLookupCtrl', NetworkLookupController)

.controller('PeerViewCtrl', PeerViewController)

.controller('NetworkLookupModalCtrl', NetworkLookupModalController)

.controller('NetworkLookupPopoverCtrl', NetworkLookupPopoverController)

.controller('PeerInfoPopoverCtrl', PeerInfoPopoverController)

;

function NetworkLookupController($scope,  $state, $location, $ionicPopover, $window,
                                 BMA, UIUtils, csSettings, csCurrency, csNetwork, 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,
    type: undefined,
    results: [],
    endpointFilter: null,
    sort : undefined,
    asc: true
  };
  $scope.eanbleLocationHref = true; // can be overrided by sub-controler (e.g. popup)

  /**
   * 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 = !BMA.node.same(currency.node.host, currency.node.port) ?
            BMA.instance(currency.node.host, currency.node.port) : BMA;
          if (state && state.stateParams) {
            if (state.stateParams.type && ['mirror', 'member', 'offline'].indexOf(state.stateParams.type) != -1) {
              $scope.search.type = state.stateParams.type;
            }
            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;
    csNetwork.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.type && $scope.search.type === 'offline')
      },
      sort: {
        type : $scope.search.sort,
        asc : $scope.search.asc
      },
      expertMode: $scope.expertMode
    };
    return options;
  };

  $scope.load = function() {

    if ($scope.search.loading){
      csNetwork.start($scope.node, $scope.computeOptions());

      // Catch event on new peers
      $scope.refreshing = false;
      csNetwork.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 || csNetwork.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;
    csNetwork.loadPeers();
  };

  $scope.sort = function() {
    $scope.search.loading = true;
    $scope.refreshing = true;
    csNetwork.sort($scope.computeOptions());
    $scope.updateView(csNetwork.data);
  };

  $scope.toggleSearchType = function(type){
    $scope.hideActionsPopover();
    if ($scope.search.type === type || type === 'none') {
      $scope.search.type = undefined;
    }
    else {
      $scope.search.type = type;
    }
    csNetwork.close();
    $scope.search.loading = true;
    $scope.load();

    // Update location href
    if ($scope.eanbleLocationHref) {
      $location.search({type: $scope.search.type}).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) {
    if (!peer.online) return; // nothing to do
    var stateParams = {server: peer.getServer()};
    if (peer.isSsl()) {
      stateParams.ssl = true;
    }
    if (peer.isTor()) {
      stateParams.tor = true;
    }
    $state.go('app.view_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 parts = ep.split(' ');
        if (parts[0] == endpointFilter) {
          return res.concat(parts[1] + (parts[2] != 80 ? (':'+parts[2]) : ''));
        }
        return res;
      }, []);
    if (!endpoints.length) return;

    UIUtils.popover.show($event, {
      templateUrl: 'templates/network/popover_endpoints.html',
      bindings: {
        titleKey: 'NETWORK.VIEW.ENDPOINTS.' + endpointFilter,
        valueKey: 'NETWORK.VIEW.NODE_ADDRESS',
        endpoints: endpoints
      }
    });
    $event.stopPropagation();
  };

  /* -- help tip -- */

  // Show help tip
  $scope.showHelpTip = function() {
    if (!$scope.isLogin()) return;
    index = csSettings.data.helptip.currency;
    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

    return helptipScope.startCurrencyTour(index, false)
      .then(function(endIndex) {
        helptipScope.$destroy();
        csSettings.data.helptip.currency = endIndex;
        csSettings.store();
      });
  };
}


function NetworkLookupModalController($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.eanbleLocationHref = false;

  $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 NetworkLookupPopoverController($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.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 PeerInfoPopoverController($scope, csSettings, csCurrency, BMA) {
  'ngInject';

  $scope.loading = true;
  $scope.formData = {};

  $scope.enter = function() {
    csCurrency.blockchain.current()
      .then(function(block) {
        $scope.formData = angular.copy(block);
        $scope.formData.useSsl = BMA.useSsl;
      })
      .then(function() {
        $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;
    $scope.formData = angular.copy(block);
    $scope.formData.useSsl = BMA.useSsl;
    console.debug("[peer info] Received new block ", block);
    $scope.$broadcast('$$rebind::' + 'rebind'); // force data binding
  });

  // Update UI on settings changed
  csSettings.api.data.on.changed($scope, function(data) {
    if ($scope.loading) return;
    if ($scope.formData.useSsl != BMA.useSsl) {
      console.debug("[peer info] Peer settings changed");
      $scope.formData.useSsl = BMA.useSsl;
      $scope.$broadcast('$$rebind::' + 'rebind'); // force data binding
    }
  });

  // Enter the popover
  $scope.enter();
}

function PeerViewController($scope, $q, UIUtils, csWot, BMA) {
  'ngInject';

  $scope.node = {};
  $scope.loading = true;

  $scope.$on('$ionicView.enter', function(e, state) {
    var isDefaultNode = !state.stateParams || !state.stateParams.server;
    var server = state.stateParams && state.stateParams.server || BMA.server;
    var useSsl = state.stateParams && state.stateParams.ssl == "true" || (isDefaultNode ? BMA.useSsl : false);
    var useTor = state.stateParams.tor == "true" || (isDefaultNode ? BMA.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];
    }

    angular.merge($scope.node,
      useTor ?
        // For TOR, use a web2tor to access the endpoint
        BMA.lightInstance(node.host + ".to", 443, true/*ssl*/, 60000 /*long timeout*/) :
        BMA.lightInstance(node.host, node.port, node.useSsl),
      node);

    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 known peers
      $scope.node.network.peers()
        .then(function(json) {
          var peers = json.peers.map(function (p) {
            var peer = new Peer(p);
            peer.online = p.status == 'UP';
            peer.blockNumber = peer.block.replace(/-.+$/, '');
            peer.dns = peer.getDns();
            return 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.uid ? 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"));
  };
}