<commit_before>/* global angular:false */
'use strict';

/**
 * Define our ndslabs module here. All other files will
 * use the single-argument notation for angular.module()
 */
angular.module('ndslabs', [ 'navbar', 'footer', 'ndslabs-services', 'ndslabs-filters', 'ndslabs-directives',  'ndslabs-api', 'ngWizard', 'ngGrid', 'ngAlert',
    'ngRoute', 'ngResource', 'ngCookies', 'ngAnimate', 'ngMessages', 'ui.bootstrap', 'ngPasswordStrength', 'angular-clipboard', 'ui.pwgen', 'frapontillo.gage', 'chart.js' ])

/**
 * If true, display verbose debug data as JSON
 */
.constant('DEBUG', false)

.constant('Layout', 'container')

.value('InitialRedirect', true)

/**
 *TODO: Whether or not to use mock data (false if talking to live etcd)
 */
//.constant('MOCKDATA', false)

/**
 * Make lodash available for injection into controllers
 */
.constant('_', window._)

/**
 * The route to our "Login" View
 */
.constant('LoginRoute', '/login')

/**
 * The route to our "Request Access" View
 */
.constant('SignUpRoute', '/register')

/**
 * The route to the "AppStore" view
 */
.constant('AppStoreRoute', '/store')

/**
 * The route to the "Add Application Spec" view
 */
.constant('AddSpecRoute', '/store/add')

/**
 * The route to the "Edit Application Spec" view
 */
.constant('EditSpecRoute', '/store/edit/:specKey')

/**
 * The route to our "Dashboard" View
 */
.constant('HomeRoute', '/home:query?')

/**
 * The route to the "Add Application Service" view
 */
.constant('AddServiceRoute', '/home/:stackId/add/:service')

/**
 * The route to the "Edit Application Service" view
 */
.constant('EditServiceRoute', '/home/:stackId/edit/:service')

/**
 * The route to the "Application Service Console" view
 */
.constant('ConsoleRoute', '/home/:stackId/console/:service')

/**
 * The version/revision of this GUI
 */
.constant('BuildVersion', '1.0.1-devel')
.constant('BuildDate', '')

/**
 * Hostname / Port for communicating with etcd
 *
 * This must be the external IP and nodePort (when running in k8)
 *
 * TODO: We assume this is running on the same machine as the apiserver.
 */
.constant('ApiHost', '192.168.99.100')
.constant('ApiPort', '30001')
.constant('ApiPath', '/api')
.constant('ApiSecure', false)

.constant('WebsocketPath', '/console')

/** Store our built ApiUri here */
.value('ApiUri', { api: '', ws: '' })

/**
 * Logic for communicating with etcd (powered by swagger-js-codegen)
 * @param {string} ApiHost - the hostname defined above
 * @param {string} ApiPort - the port defined above
 * @param {string} ApiPath - the path defined above
 * @param {Object} ApiServer - the REST API client generated by swagger; see 'app/shared/NdsLabsRestApi.js'
 */
.factory('NdsLabsApi', [ 'ApiHost', 'ApiPort', 'ApiPath', 'ApiSecure', 'WebsocketPath', 'ApiUri', 'ApiServer',
    function(ApiHost, ApiPort, ApiPath, ApiSecure, WebsocketPath, ApiUri, ApiServer) {
  // TODO: Investigate options / caching
  // XXX: Caching may not be possible due to the unique token sent with every request

  // Start with the protocol
  if (ApiSecure) {
    ApiUri.api = 'https://' + ApiHost
    ApiUri.ws = 'wss://' + ApiHost
  } else {
    ApiUri.api = 'http://' + ApiHost
    ApiUri.ws = 'ws://' + ApiHost
  }

  // Add on the port suffix, if applicable
  if (ApiPort) {
    var portSuffix = ':' + ApiPort

    ApiUri.api += portSuffix
    ApiUri.ws += portSuffix
  }

  // Add on the path suffix, if applicable
   ApiUri.api += ApiPath
   ApiUri.ws += ApiPath + WebsocketPath

  // Instantiate a new client for the ApiServer using our newly built uri
  return new ApiServer(ApiUri.api);
}])

/**
 * A shared store for our AuthInfo, done as a provider so that we
 * can easily inject it into the .config() block below
 */
.provider('AuthInfo', function() {
  this.authInfo = {
    namespace: '',
    password: '',
    saveCookie: false,
    project: null,
    token: null
  };

  this.$get = function() {
    var authInfo = this.authInfo;
    return {
      get: function() { return authInfo; }
    }
  };
})

/**
 * Configure routes / HTTP for our app using the services defined above
 */
.config([ '$routeProvider', '$httpProvider', '$logProvider', 'DEBUG', 'AuthInfoProvider', 'LoginRoute', 'AppStoreRoute', 'HomeRoute', 'ConsoleRoute', 'AddServiceRoute', 'EditServiceRoute', 'AddSpecRoute', 'EditSpecRoute',
    function($routeProvider, $httpProvider, $logProvider, DEBUG, authInfo, LoginRoute, AppStoreRoute, HomeRoute, ConsoleRoute, AddServiceRoute, EditServiceRoute, AddSpecRoute, EditSpecRoute) {
  // Squelch debug-level log messages
  $logProvider.debugEnabled(DEBUG);

  // Setup default behaviors for encountering HTTP errors
  $httpProvider.interceptors.push(['$rootScope', '$cookies', '$q', '$location', '$log', '_', 'DEBUG', 'ApiUri', 'AuthInfo',
      function (scope, $cookies, $q, $location, $log, _, DEBUG, ApiUri, AuthInfo) {
    return {
      // Attach our auth token to each outgoing request (to the api server)
      'request': function(config) {
        // If this is a request for our API server
        if (_.includes(config.url, ApiUri.api)) {
          // If this was *not* an attempt to authenticate
          if (!_.includes(config.url, '/authenticate')) {
            // We need to attach our token to this request
            config.headers['Authorization'] = 'Bearer ' + $cookies.get('token');
          }
        }
        return config;
      },
      'requestError': function(rejection) {
        if (_.includes(rejection.config.url, ApiUri.api)) {
          $log.error("Request error encountered: " + rejection.config.url);
        }
        return $q.reject(rejection);
      },
      'response': function(response) {
        // If this is a response from our API server
        if (_.includes(response.config.url, ApiUri.api)) {
          // If this was in response to an /authenticate or /refresh_token request
          if ((_.includes(response.config.url, '/authenticate') && response.config.method === 'POST')
              || (_.includes(response.config.url, '/refresh_token') && response.config.method === 'GET')) {
            // This response should contain a new token, so save it as a cookie
            $cookies.put('token', response.data.token);
          }
        }

        return response;
      },
      'responseError': function(rejection) {
        // If this is a response from our API server
        if (_.includes(rejection.config.url, ApiUri.api)) {
          $log.error("Response error encountered: " + rejection.config.url);

          // Read out the HTTP error code
          var status = rejection.status;

          // Handle HTTP 401: Not Authorized - User needs to provide credentials
          if (status == 401) {
            // TODO: If we want to intercept the route to redirect them after a successful login
            //window.location = "/account/login?redirectUrl=" + Base64.encode(document.URL);

            // Purge current session data
            authInfo.authInfo.token = null;
            $cookies.remove('token');
            $cookies.remove('namespace');

            // Route to Login Page to prompt for credentials
            if ($location.path() !== LoginRoute) {
              $location.path(LoginRoute);
            }
            return $q.reject(rejection);
          }
        }

        // otherwise
        return $q.reject(rejection);
      }
    };
  }]);

  // Setup routes to our different pages
  $routeProvider
  .when(LoginRoute, {
    title: 'Sign into NDS Labs',
    controller: 'LoginController',
    templateUrl: 'app/login/login.html'
  })
  .when(AppStoreRoute, {
    title: 'NDS Labs Catalog',
    controller: 'CatalogController',
    templateUrl: 'app/appStore/catalog.html'
  })
  .when(AddSpecRoute, {
    title: 'Add Application',
    controller: 'AddOrEditSpecController',
    templateUrl: 'app/appStore/addOrEdit/addOrEditSpec.html'
  })
  .when(EditSpecRoute, {
    title: 'Edit Application',
    controller: 'AddOrEditSpecController',
    templateUrl: 'app/appStore/addOrEdit/addOrEditSpec.html'
  })
  .when(HomeRoute, {
    title: 'NDS Labs Dashboard',
    controller: 'DashboardController',
    templateUrl: 'app/dashboard/dashboard.html'
  })
  .when(AddServiceRoute, {
    title: 'Add Application Service',
    controller: 'EditServiceController',
    templateUrl: 'app/dashboard/service/editService.html'
  })
  .when(EditServiceRoute, {
    title: 'Edit Application Service',
    controller: 'EditServiceController',
    templateUrl: 'app/dashboard/service/editService.html'
  })
  .when(ConsoleRoute, {
    title: 'Application Service Console',
    controller: 'ConsoleController',
    templateUrl: 'app/dashboard/console/console.html'
  })
  .otherwise({ redirectTo: LoginRoute });
}])

/**
 * Once configured, run this section of code to finish bootstrapping our app
 */
.run([ '$rootScope', '$window', '$location', '$log', '$interval', '$cookies', '$uibModalStack', 'Stacks', 'InitialRedirect', '_', 'AuthInfo', 'LoginRoute', 'AppStoreRoute', 'HomeRoute', 'NdsLabsApi', 'AutoRefresh', 'ServerData',
    function($rootScope, $window, $location, $log, $interval, $cookies, $uibModalStack, Stacks, InitialRedirect, _, authInfo, LoginRoute, AppStoreRoute, HomeRoute, NdsLabsApi, AutoRefresh, ServerData) {

  // Grab saved auth data from cookies and attempt to use the leftover session
  var token = $cookies.get('token');
  var namespace = $cookies.get('namespace');
  if (token && namespace) {
    // Pull our token / namespace from cookies
    authInfo.get().token = token;
    authInfo.get().namespace = namespace;
  }

  // Make _ bindable in partial views
  // TODO: Investigate performance concerns here...
  $rootScope._ = window._;

  // Change the tab/window title when we change routes
  $rootScope.$on('$routeChangeSuccess', function (event, current, previous) {
    if (current.$$route) {
      $window.document.title = current.$$route.title;
    } else {
      $log.error('Encountered undefined route...');
    }
  });

  // When user changes routes, check that they are still authed
  $rootScope.$on( "$routeChangeStart", function(event, next, current) {
    // Define the logic for ending a user's session in the browser
    var terminateSession = function() {
      // Purge current session data
      authInfo.get().token = null;
      $cookies.remove('token');
      $cookies.remove('namespace');

      // Close any open modals
      $uibModalStack.dismissAll();

      // Stop any running auto-refresh interval
      AutoRefresh.stop();

      // Purge any server data
      ServerData.purgeAll();

      // Cancel the auth check interval
      $interval.cancel(checkTokenInterval);
      checkTokenInterval = null;

      // user needs to log in, redirect to /login
      if (!_.includes(next.templateUrl, "app/login/login.html")) {
        $location.path(LoginRoute);
      }
    };

    // Check our token every 60s
    var tokenCheckMs = 60000;

    // Every so often, check that our token is still valid
    var checkTokenInterval = null;
    var checkToken = function() {
      NdsLabsApi.getCheck_token().then(function() { $log.debug('Token is still valid.'); }, function() {
        $log.error('Token expired, redirecting to login.');
        terminateSession();
      });
    };

    // Check if the token is still valid on route changes
    var token = $cookies.get('token');
    if (token) {
      authInfo.get().token = token;
      authInfo.get().namespace = $cookies.get('namespace');
      NdsLabsApi.getRefresh_token().then(function() {
        $log.debug('Token refreshed: ' + authInfo.get().token);

        // Populate all displayed data here from etcd
        ServerData.populateAll(authInfo.get().namespace).finally(function() {
          /*if (InitialRedirect) {
            InitialRedirect = false;
            var dest = Stacks.all.length > 0 ? HomeRoute : AppStoreRoute;
            debugger;
            $location.path(dest);
          }*/
        });

        // Restart our token check interval
        if (checkTokenInterval) {
          $interval.cancel(checkTokenInterval);
          checkTokenInterval = null;
        }
        checkTokenInterval = $interval(checkToken, tokenCheckMs);
      }, function() {
        $log.debug('Failed to refresh token!');

        // TODO: Allow login page to reroute user to destination?
        // XXX: This would matter more if there are multiple views
        //authInfo.returnRoute = next.$$route.originalPath;

        terminateSession();
      });
    } else {
      terminateSession();
    }
  });
}]);
<commit_msg>Fixed broken /home route<commit_after>/* global angular:false */
'use strict';

/**
 * Define our ndslabs module here. All other files will
 * use the single-argument notation for angular.module()
 */
angular.module('ndslabs', [ 'navbar', 'footer', 'ndslabs-services', 'ndslabs-filters', 'ndslabs-directives',  'ndslabs-api', 'ngWizard', 'ngGrid', 'ngAlert',
    'ngRoute', 'ngResource', 'ngCookies', 'ngAnimate', 'ngMessages', 'ui.bootstrap', 'ngPasswordStrength', 'angular-clipboard', 'ui.pwgen', 'frapontillo.gage', 'chart.js' ])

/**
 * If true, display verbose debug data as JSON
 */
.constant('DEBUG', false)

.constant('Layout', 'container')

.value('InitialRedirect', true)

/**
 *TODO: Whether or not to use mock data (false if talking to live etcd)
 */
//.constant('MOCKDATA', false)

/**
 * Make lodash available for injection into controllers
 */
.constant('_', window._)

/**
 * The route to our "Login" View
 */
.constant('LoginRoute', '/login')

/**
 * The route to our "Request Access" View
 */
.constant('SignUpRoute', '/register')

/**
 * The route to the "AppStore" view
 */
.constant('AppStoreRoute', '/store')

/**
 * The route to the "Add Application Spec" view
 */
.constant('AddSpecRoute', '/store/add')

/**
 * The route to the "Edit Application Spec" view
 */
.constant('EditSpecRoute', '/store/edit/:specKey')

/**
 * The route to our "Dashboard" View
 */
.constant('HomeRoute', '/home')

/**
 * The route to the "Add Application Service" view
 */
.constant('AddServiceRoute', '/home/:stackId/add/:service')

/**
 * The route to the "Edit Application Service" view
 */
.constant('EditServiceRoute', '/home/:stackId/edit/:service')

/**
 * The route to the "Application Service Console" view
 */
.constant('ConsoleRoute', '/home/:stackId/console/:service')

/**
 * The version/revision of this GUI
 */
.constant('BuildVersion', '1.0.1-devel')
.constant('BuildDate', '')

/**
 * Hostname / Port for communicating with etcd
 *
 * This must be the external IP and nodePort (when running in k8)
 *
 * TODO: We assume this is running on the same machine as the apiserver.
 */
.constant('ApiHost', '192.168.99.100')
.constant('ApiPort', '30001')
.constant('ApiPath', '/api')
.constant('ApiSecure', false)

.constant('WebsocketPath', '/console')

/** Store our built ApiUri here */
.value('ApiUri', { api: '', ws: '' })

/**
 * Logic for communicating with etcd (powered by swagger-js-codegen)
 * @param {string} ApiHost - the hostname defined above
 * @param {string} ApiPort - the port defined above
 * @param {string} ApiPath - the path defined above
 * @param {Object} ApiServer - the REST API client generated by swagger; see 'app/shared/NdsLabsRestApi.js'
 */
.factory('NdsLabsApi', [ 'ApiHost', 'ApiPort', 'ApiPath', 'ApiSecure', 'WebsocketPath', 'ApiUri', 'ApiServer',
    function(ApiHost, ApiPort, ApiPath, ApiSecure, WebsocketPath, ApiUri, ApiServer) {
  // TODO: Investigate options / caching
  // XXX: Caching may not be possible due to the unique token sent with every request

  // Start with the protocol
  if (ApiSecure) {
    ApiUri.api = 'https://' + ApiHost
    ApiUri.ws = 'wss://' + ApiHost
  } else {
    ApiUri.api = 'http://' + ApiHost
    ApiUri.ws = 'ws://' + ApiHost
  }

  // Add on the port suffix, if applicable
  if (ApiPort) {
    var portSuffix = ':' + ApiPort

    ApiUri.api += portSuffix
    ApiUri.ws += portSuffix
  }

  // Add on the path suffix, if applicable
   ApiUri.api += ApiPath
   ApiUri.ws += ApiPath + WebsocketPath

  // Instantiate a new client for the ApiServer using our newly built uri
  return new ApiServer(ApiUri.api);
}])

/**
 * A shared store for our AuthInfo, done as a provider so that we
 * can easily inject it into the .config() block below
 */
.provider('AuthInfo', function() {
  this.authInfo = {
    namespace: '',
    password: '',
    saveCookie: false,
    project: null,
    token: null
  };

  this.$get = function() {
    var authInfo = this.authInfo;
    return {
      get: function() { return authInfo; }
    }
  };
})

/**
 * Configure routes / HTTP for our app using the services defined above
 */
.config([ '$routeProvider', '$httpProvider', '$logProvider', 'DEBUG', 'AuthInfoProvider', 'LoginRoute', 'AppStoreRoute', 'HomeRoute', 'ConsoleRoute', 'AddServiceRoute', 'EditServiceRoute', 'AddSpecRoute', 'EditSpecRoute',
    function($routeProvider, $httpProvider, $logProvider, DEBUG, authInfo, LoginRoute, AppStoreRoute, HomeRoute, ConsoleRoute, AddServiceRoute, EditServiceRoute, AddSpecRoute, EditSpecRoute) {
  // Squelch debug-level log messages
  $logProvider.debugEnabled(DEBUG);

  // Setup default behaviors for encountering HTTP errors
  $httpProvider.interceptors.push(['$rootScope', '$cookies', '$q', '$location', '$log', '_', 'DEBUG', 'ApiUri', 'AuthInfo',
      function (scope, $cookies, $q, $location, $log, _, DEBUG, ApiUri, AuthInfo) {
    return {
      // Attach our auth token to each outgoing request (to the api server)
      'request': function(config) {
        // If this is a request for our API server
        if (_.includes(config.url, ApiUri.api)) {
          // If this was *not* an attempt to authenticate
          if (!_.includes(config.url, '/authenticate')) {
            // We need to attach our token to this request
            config.headers['Authorization'] = 'Bearer ' + $cookies.get('token');
          }
        }
        return config;
      },
      'requestError': function(rejection) {
        if (_.includes(rejection.config.url, ApiUri.api)) {
          $log.error("Request error encountered: " + rejection.config.url);
        }
        return $q.reject(rejection);
      },
      'response': function(response) {
        // If this is a response from our API server
        if (_.includes(response.config.url, ApiUri.api)) {
          // If this was in response to an /authenticate or /refresh_token request
          if ((_.includes(response.config.url, '/authenticate') && response.config.method === 'POST')
              || (_.includes(response.config.url, '/refresh_token') && response.config.method === 'GET')) {
            // This response should contain a new token, so save it as a cookie
            $cookies.put('token', response.data.token);
          }
        }

        return response;
      },
      'responseError': function(rejection) {
        // If this is a response from our API server
        if (_.includes(rejection.config.url, ApiUri.api)) {
          $log.error("Response error encountered: " + rejection.config.url);

          // Read out the HTTP error code
          var status = rejection.status;

          // Handle HTTP 401: Not Authorized - User needs to provide credentials
          if (status == 401) {
            // TODO: If we want to intercept the route to redirect them after a successful login
            //window.location = "/account/login?redirectUrl=" + Base64.encode(document.URL);

            // Purge current session data
            authInfo.authInfo.token = null;
            $cookies.remove('token');
            $cookies.remove('namespace');

            // Route to Login Page to prompt for credentials
            if ($location.path() !== LoginRoute) {
              $location.path(LoginRoute);
            }
            return $q.reject(rejection);
          }
        }

        // otherwise
        return $q.reject(rejection);
      }
    };
  }]);

  // Setup routes to our different pages
  $routeProvider
  .when(LoginRoute, {
    title: 'Sign into NDS Labs',
    controller: 'LoginController',
    templateUrl: 'app/login/login.html'
  })
  .when(AppStoreRoute, {
    title: 'NDS Labs Catalog',
    controller: 'CatalogController',
    templateUrl: 'app/appStore/catalog.html'
  })
  .when(AddSpecRoute, {
    title: 'Add Application',
    controller: 'AddOrEditSpecController',
    templateUrl: 'app/appStore/addOrEdit/addOrEditSpec.html'
  })
  .when(EditSpecRoute, {
    title: 'Edit Application',
    controller: 'AddOrEditSpecController',
    templateUrl: 'app/appStore/addOrEdit/addOrEditSpec.html'
  })
  .when(HomeRoute, {
    title: 'NDS Labs Dashboard',
    controller: 'DashboardController',
    templateUrl: 'app/dashboard/dashboard.html'
  })
  .when(AddServiceRoute, {
    title: 'Add Application Service',
    controller: 'EditServiceController',
    templateUrl: 'app/dashboard/service/editService.html'
  })
  .when(EditServiceRoute, {
    title: 'Edit Application Service',
    controller: 'EditServiceController',
    templateUrl: 'app/dashboard/service/editService.html'
  })
  .when(ConsoleRoute, {
    title: 'Application Service Console',
    controller: 'ConsoleController',
    templateUrl: 'app/dashboard/console/console.html'
  })
  .otherwise({ redirectTo: LoginRoute });
}])

/**
 * Once configured, run this section of code to finish bootstrapping our app
 */
.run([ '$rootScope', '$window', '$location', '$log', '$interval', '$cookies', '$uibModalStack', 'Stacks', 'InitialRedirect', '_', 'AuthInfo', 'LoginRoute', 'AppStoreRoute', 'HomeRoute', 'NdsLabsApi', 'AutoRefresh', 'ServerData',
    function($rootScope, $window, $location, $log, $interval, $cookies, $uibModalStack, Stacks, InitialRedirect, _, authInfo, LoginRoute, AppStoreRoute, HomeRoute, NdsLabsApi, AutoRefresh, ServerData) {

  // Grab saved auth data from cookies and attempt to use the leftover session
  var token = $cookies.get('token');
  var namespace = $cookies.get('namespace');
  if (token && namespace) {
    // Pull our token / namespace from cookies
    authInfo.get().token = token;
    authInfo.get().namespace = namespace;
  }

  // Make _ bindable in partial views
  // TODO: Investigate performance concerns here...
  $rootScope._ = window._;

  // Change the tab/window title when we change routes
  $rootScope.$on('$routeChangeSuccess', function (event, current, previous) {
    if (current.$$route) {
      $window.document.title = current.$$route.title;
    } else {
      $log.error('Encountered undefined route...');
    }
  });

  // When user changes routes, check that they are still authed
  $rootScope.$on( "$routeChangeStart", function(event, next, current) {
    // Define the logic for ending a user's session in the browser
    var terminateSession = function() {
      // Purge current session data
      authInfo.get().token = null;
      $cookies.remove('token');
      $cookies.remove('namespace');

      // Close any open modals
      $uibModalStack.dismissAll();

      // Stop any running auto-refresh interval
      AutoRefresh.stop();

      // Purge any server data
      ServerData.purgeAll();

      // Cancel the auth check interval
      $interval.cancel(checkTokenInterval);
      checkTokenInterval = null;

      // user needs to log in, redirect to /login
      if (!_.includes(next.templateUrl, "app/login/login.html")) {
        $location.path(LoginRoute);
      }
    };

    // Check our token every 60s
    var tokenCheckMs = 60000;

    // Every so often, check that our token is still valid
    var checkTokenInterval = null;
    var checkToken = function() {
      NdsLabsApi.getCheck_token().then(function() { $log.debug('Token is still valid.'); }, function() {
        $log.error('Token expired, redirecting to login.');
        terminateSession();
      });
    };

    // Check if the token is still valid on route changes
    var token = $cookies.get('token');
    if (token) {
      authInfo.get().token = token;
      authInfo.get().namespace = $cookies.get('namespace');
      NdsLabsApi.getRefresh_token().then(function() {
        $log.debug('Token refreshed: ' + authInfo.get().token);

        // Populate all displayed data here from etcd
        ServerData.populateAll(authInfo.get().namespace).finally(function() {
          /*if (InitialRedirect) {
            InitialRedirect = false;
            var dest = Stacks.all.length > 0 ? HomeRoute : AppStoreRoute;
            debugger;
            $location.path(dest);
          }*/
        });

        // Restart our token check interval
        if (checkTokenInterval) {
          $interval.cancel(checkTokenInterval);
          checkTokenInterval = null;
        }
        checkTokenInterval = $interval(checkToken, tokenCheckMs);
      }, function() {
        $log.debug('Failed to refresh token!');

        // TODO: Allow login page to reroute user to destination?
        // XXX: This would matter more if there are multiple views
        //authInfo.returnRoute = next.$$route.originalPath;

        terminateSession();
      });
    } else {
      terminateSession();
    }
  });
}]);
<|endoftext|>


<commit_before>'use strict';
const boom = require('boom');

exports.register = (server, options, next) => {
    const path = '/consortia';
    const db = server.plugins.pouch.consortiaDb;

    server.route({
        method: 'GET',
        path: path,
        handler: (request, reply) => {
            db.allDocs({ include_docs: true }) // jshint ignore:line
            .then((docs) => {
                reply(docs.rows);
            }).catch((err) => {
                reply(boom.wrap(err));
            });
        }
    });

    server.route({
        method: 'GET',
        path: path + '/{id}',
        handler: (request, reply) => {
            db.get(request.params.id)
            .then((doc) => {
                reply(doc);
            }).catch((err) => {
                reply(boom.wrap(err));
            });
        }
    });

    server.route({
        method: 'POST',
        path: path,
        handler: (request, reply) => {
            db.post({
                label: request.payload.label,
                users: request.payload.users,
                description: request.payload.description,
                tags: request.payload.tags
            }).then((response) => {
                // handle response
                reply(response.id);
            }).catch((err) => {
                reply(boom.wrap(err));
            });
        }
    });

    next();
};

exports.register.attributes = {
    name: 'consortia'
};
<commit_msg>add comments to consortia.js<commit_after>'use strict';
const boom = require('boom');

exports.register = (server, options, next) => {
    const path = '/consortia';
    const db = server.plugins.pouch.consortiaDb;

    /**
     * responds with list of all consortia and their details
     */
    server.route({
        method: 'GET',
        path: path,
        handler: (request, reply) => {
            db.allDocs({ include_docs: true }) // jshint ignore:line
            .then((docs) => {
                reply(docs.rows);
            }).catch((err) => {
                reply(boom.wrap(err));
            });
        }
    });

    /**
     * accepts a consortia id
     * responds with details about a specific consortia
     */
    server.route({
        method: 'GET',
        path: path + '/{id}',
        handler: (request, reply) => {
            db.get(request.params.id)
            .then((doc) => {
                reply(doc);
            }).catch((err) => {
                reply(boom.wrap(err));
            });
        }
    });

    /**
     * accepts consortia details
     * adds new consortia to coinstac-consortiadb
     * creates a new consortia db named: coinstac-icdb-<consortiaID>
     * responds with id of newly created consortia
     */
    server.route({
        method: 'POST',
        path: path,
        handler: (request, reply) => {
            db.post({
                label: request.payload.label,
                users: request.payload.users,
                description: request.payload.description,
                tags: request.payload.tags
            }).then((response) => {
                // handle response
                reply(response.id);
            }).catch((err) => {
                reply(boom.wrap(err));
            });
        }
    });

    next();
};

exports.register.attributes = {
    name: 'consortia'
};
<|endoftext|>


<commit_before>/**
* EditableForm utilites
*/
(function ($) {
    //utils
    $.fn.editableutils = {
        /**
        * classic JS inheritance function
        */
        inherit: function (Child, Parent) {
            var F = function() { };
            F.prototype = Parent.prototype;
            Child.prototype = new F();
            Child.prototype.constructor = Child;
            Child.superclass = Parent.prototype;
        },

        /**
        * set caret position in input
        * see http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area
        */
        setCursorPosition: function(elem, pos) {
            if (elem.setSelectionRange) {
                elem.setSelectionRange(pos, pos);
            } else if (elem.createTextRange) {
                var range = elem.createTextRange();
                range.collapse(true);
                range.moveEnd('character', pos);
                range.moveStart('character', pos);
                range.select();
            }
        },

        /**
        * function to parse JSON in *single* quotes. (jquery automatically parse only double quotes)
        * That allows such code as:
        * safe = true --> means no exception will be thrown
        * for details see http://stackoverflow.com/questions/7410348/how-to-set-json-format-to-html5-data-attributes-in-the-jquery
        */
        tryParseJson: function(s, safe) {
            if (typeof s === 'string' && s.length && s.match(/^[\{\[].*[\}\]]$/)) {
                if (safe) {
                    try {
                        /*jslint evil: true*/
                        s = (new Function('return ' + s))();
                        /*jslint evil: false*/
                    } catch (e) {} finally {
                        return s;
                    }
                } else {
                    /*jslint evil: true*/
                    s = (new Function('return ' + s))();
                    /*jslint evil: false*/
                }
            }
            return s;
        },

        /**
        * slice object by specified keys
        */
        sliceObj: function(obj, keys, caseSensitive /* default: false */) {
            var key, keyLower, newObj = {};

            if (!$.isArray(keys) || !keys.length) {
                return newObj;
            }

            for (var i = 0; i < keys.length; i++) {
                key = keys[i];
                if (obj.hasOwnProperty(key)) {
                    newObj[key] = obj[key];
                }

                if(caseSensitive === true) {
                    continue;
                }

                //when getting data-* attributes via $.data() it's converted to lowercase.
                //details: http://stackoverflow.com/questions/7602565/using-data-attributes-with-jquery
                //workaround is code below.
                keyLower = key.toLowerCase();
                if (obj.hasOwnProperty(keyLower)) {
                    newObj[key] = obj[keyLower];
                }
            }

            return newObj;
        },

        /**
        * exclude complex objects from $.data() before pass to config
        */
        getConfigData: function($element) {
            var data = {};
            $.each($element.data(), function(k, v) {
                if(typeof v !== 'object' || (v && typeof v === 'object' && (v.constructor === Object || v.constructor === Array))) {
                    data[k] = v;
                }
            });
            return data;
        },

        objectKeys: function(o) {
            if (Object.keys) {
                return Object.keys(o);
            } else {
                if (o !== Object(o)) {
                    throw new TypeError('Object.keys called on a non-object');
                }
                var k=[], p;
                for (p in o) {
                    if (Object.prototype.hasOwnProperty.call(o,p)) {
                        k.push(p);
                    }
                }
                return k;
            }

        },

       /**
        method to escape html.
       **/
       escape: function(str) {
           return $('
').text(str).html();
       },

       /*
        returns array items from sourceData having value property equal or inArray of 'value'
       */
       itemsByValue: function(value, sourceData) {
           if(!sourceData || value === null) {
               return [];
           }

           //convert to array
           if(!$.isArray(value)) {
               value = [value];
           }

           /*jslint eqeq: true*/
           var result = $.grep(sourceData, function(o){
               return $.grep(value, function(v){ return v == o.value; }).length;
           });
           /*jslint eqeq: false*/

           return result;
       },

       /*
       Returns input by options: type, mode.
       */
       createInput: function(options) {
           var TypeConstructor, typeOptions, input,
           type = options.type;

           //`date` is some kind of virtual type that is transformed to one of exact types
           //depending on mode and core lib
           if(type === 'date') {
               //inline
               if(options.mode === 'inline') {
                   if($.fn.editabletypes.datefield) {
                       type = 'datefield';
                   } else if($.fn.editabletypes.dateuifield) {
                       type = 'dateuifield';
                   }
                   //popup
               } else {
                   if($.fn.editabletypes.date) {
                       type = 'date';
                   } else if($.fn.editabletypes.dateui) {
                       type = 'dateui';
                   }
               }
           }

           //change wysihtml5 to textarea for jquery UI and plain versions
           if(type === 'wysihtml5' && !$.fn.editabletypes[type]) {
               type = 'textarea';
           }

           //create input of specified type. Input will be used for converting value, not in form
           if(typeof $.fn.editabletypes[type] === 'function') {
               TypeConstructor = $.fn.editabletypes[type];
               typeOptions = this.sliceObj(options, this.objectKeys(TypeConstructor.defaults));
               input = new TypeConstructor(typeOptions);
               return input;
           } else {
               $.error('Unknown type: '+ type);
               return false;
           }
       }

    };
}(window.jQuery));
<commit_msg>comments<commit_after>/**
* EditableForm utilites
*/
(function ($) {
    //utils
    $.fn.editableutils = {
        /**
        * classic JS inheritance function
        */
        inherit: function (Child, Parent) {
            var F = function() { };
            F.prototype = Parent.prototype;
            Child.prototype = new F();
            Child.prototype.constructor = Child;
            Child.superclass = Parent.prototype;
        },

        /**
        * set caret position in input
        * see http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area
        */
        setCursorPosition: function(elem, pos) {
            if (elem.setSelectionRange) {
                elem.setSelectionRange(pos, pos);
            } else if (elem.createTextRange) {
                var range = elem.createTextRange();
                range.collapse(true);
                range.moveEnd('character', pos);
                range.moveStart('character', pos);
                range.select();
            }
        },

        /**
        * function to parse JSON in *single* quotes. (jquery automatically parse only double quotes)
        * That allows such code as:
        * safe = true --> means no exception will be thrown
        * for details see http://stackoverflow.com/questions/7410348/how-to-set-json-format-to-html5-data-attributes-in-the-jquery
        */
        tryParseJson: function(s, safe) {
            if (typeof s === 'string' && s.length && s.match(/^[\{\[].*[\}\]]$/)) {
                if (safe) {
                    try {
                        /*jslint evil: true*/
                        s = (new Function('return ' + s))();
                        /*jslint evil: false*/
                    } catch (e) {} finally {
                        return s;
                    }
                } else {
                    /*jslint evil: true*/
                    s = (new Function('return ' + s))();
                    /*jslint evil: false*/
                }
            }
            return s;
        },

        /**
        * slice object by specified keys
        */
        sliceObj: function(obj, keys, caseSensitive /* default: false */) {
            var key, keyLower, newObj = {};

            if (!$.isArray(keys) || !keys.length) {
                return newObj;
            }

            for (var i = 0; i < keys.length; i++) {
                key = keys[i];
                if (obj.hasOwnProperty(key)) {
                    newObj[key] = obj[key];
                }

                if(caseSensitive === true) {
                    continue;
                }

                //when getting data-* attributes via $.data() it's converted to lowercase.
                //details: http://stackoverflow.com/questions/7602565/using-data-attributes-with-jquery
                //workaround is code below.
                keyLower = key.toLowerCase();
                if (obj.hasOwnProperty(keyLower)) {
                    newObj[key] = obj[keyLower];
                }
            }

            return newObj;
        },

        /*
        exclude complex objects from $.data() before pass to config
        */
        getConfigData: function($element) {
            var data = {};
            $.each($element.data(), function(k, v) {
                if(typeof v !== 'object' || (v && typeof v === 'object' && (v.constructor === Object || v.constructor === Array))) {
                    data[k] = v;
                }
            });
            return data;
        },

        /*
         returns keys of object
        */
        objectKeys: function(o) {
            if (Object.keys) {
                return Object.keys(o);
            } else {
                if (o !== Object(o)) {
                    throw new TypeError('Object.keys called on a non-object');
                }
                var k=[], p;
                for (p in o) {
                    if (Object.prototype.hasOwnProperty.call(o,p)) {
                        k.push(p);
                    }
                }
                return k;
            }

        },

       /**
        method to escape html.
       **/
       escape: function(str) {
           return $('
').text(str).html();
       },

       /*
        returns array items from sourceData having value property equal or inArray of 'value'
       */
       itemsByValue: function(value, sourceData) {
           if(!sourceData || value === null) {
               return [];
           }

           //convert to array
           if(!$.isArray(value)) {
               value = [value];
           }

           /*jslint eqeq: true*/
           var result = $.grep(sourceData, function(o){
               return $.grep(value, function(v){ return v == o.value; }).length;
           });
           /*jslint eqeq: false*/

           return result;
       },

       /*
       Returns input by options: type, mode.
       */
       createInput: function(options) {
           var TypeConstructor, typeOptions, input,
           type = options.type;

           //`date` is some kind of virtual type that is transformed to one of exact types
           //depending on mode and core lib
           if(type === 'date') {
               //inline
               if(options.mode === 'inline') {
                   if($.fn.editabletypes.datefield) {
                       type = 'datefield';
                   } else if($.fn.editabletypes.dateuifield) {
                       type = 'dateuifield';
                   }
                   //popup
               } else {
                   if($.fn.editabletypes.date) {
                       type = 'date';
                   } else if($.fn.editabletypes.dateui) {
                       type = 'dateui';
                   }
               }
           }

           //change wysihtml5 to textarea for jquery UI and plain versions
           if(type === 'wysihtml5' && !$.fn.editabletypes[type]) {
               type = 'textarea';
           }

           //create input of specified type. Input will be used for converting value, not in form
           if(typeof $.fn.editabletypes[type] === 'function') {
               TypeConstructor = $.fn.editabletypes[type];
               typeOptions = this.sliceObj(options, this.objectKeys(TypeConstructor.defaults));
               input = new TypeConstructor(typeOptions);
               return input;
           } else {
               $.error('Unknown type: '+ type);
               return false;
           }
       }

    };
}(window.jQuery));
<|endoftext|>


<commit_before>/**
 * Given a resource bridge, load a resource.
 *
 * Params:
 * - resourceBridge: an instance of a webdav.ResourceBridge (REQUIRED)
 * - name: If given, the name of the resource. If this is not specified,
 *   the request path will be used.
 */

var pronto = require('pronto');

function LoadResource() {
}
module.exports = LoadResource;
pronto.inheritsCommand(LoadResource);

LoadResource.prototype.execute = function (cxt, params) {
  this.required(params, ['resourceBridge']);

  var bridge = params.resourceBridge;
  var name = params.name || cxt.getDatasource('request').parsedUrl.pathname;

  var cmd = this;
  bridge.load(name, function (e, resource) {
    // FIXME: What should be done here?
    if (e) {
      console.log(e);
    }
    cmd.done(resource);
  });

}
<commit_msg>Resource can now be placed into the context as a datasource.<commit_after>/**
 * Given a resource bridge, load a resource.
 *
 * Params:
 * - resourceBridge: an instance of a webdav.ResourceBridge (REQUIRED)
 * - name: If given, the name of the resource. If this is not specified,
 *   the request path will be used.
 * - addDatasource: If this is set to `true` then the resource will be
 *   added as a datasource, which makes it usable in `from()` clauses.
 */

var pronto = require('pronto');

function LoadResource() {
}
module.exports = LoadResource;
pronto.inheritsCommand(LoadResource);

LoadResource.prototype.execute = function (cxt, params) {
  this.required(params, ['resourceBridge']);

  var bridge = params.resourceBridge;
  var name = params.name || cxt.getDatasource('request').parsedUrl.pathname;
  var addDatasource= params.addDatasource || false;

  var cmd = this;
  bridge.load(name, function (e, resource) {
    // FIXME: What should be done here?
    if (e) {
      console.log(e);
    }

    // If we should add a datasource, put the resource in as a
    // datasource with the name of the command. This makes
    // `from('resource:stream')` work.
    if (addDatasource) {
      cxt.addDatasource(cmd.name, resource);
    }
    cmd.done(resource);
  });

}
<|endoftext|>


<commit_before>import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import R from 'ramda';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { Field, formValueSelector, reduxForm } from 'redux-form';
import Select from 'react-select';
import Sticky from 'react-sticky-el';

import PLACEHOLDER from '../../../configurations/placeholders.config';
import WARNINGS from '../../../configurations/warning-messages.config';

import block from '../../../helpers/bem-cn';
import checkOnPattern from '../../../helpers/pattern-check';
import parse from '../../../helpers/parse-value';
import optionsTransformer from '../../../helpers/optionsTransformer';
import getValues from '../../../helpers/getValues';

import Section from '../../Layout/Section/Section';
import Title from '../../Layout/Title/Title';
import Row from '../../Layout/Row/Row';
import Input from '../../inputs/Input';
import Radio from '../../../components/Radio/Radio';
import Label from '../../../components/Label/Label';
import Hint from '../../../components/Hint/Hint';
import MultiSelect from '../../selects/MultiSelect/MultiSelect';
import TagSelect from '../../selects/TagSelect/TagSelect';
import Button from '../../../components/Button/Button';
import Icon from '../../../components/Icon/Icon';

import RenderPlugins from '../plugins/RenderPlugins';

import MultiRowField from '../../../components/MultiRowField/MultiRowField';
import WeightTargets from '../../pages/NewApiPage/partials/WeightTargets/WeightTargets';

import './EndpointForm.css';

const b = block('j-api-form');
const col = block('j-col');
const row = block('j-row');

const propTypes = {
    api: PropTypes.object.isRequired,
    apiSchema: PropTypes.object.isRequired,
    disabled: PropTypes.bool.isRequired,
    editing: PropTypes.bool,
    excludePlugin: PropTypes.func.isRequired,
    handleDelete: PropTypes.func.isRequired,
    handleSubmit: PropTypes.func.isRequired,
    initialValues: PropTypes.object,
    selectPlugin: PropTypes.func.isRequired,
    selectedPlugins: PropTypes.arrayOf(PropTypes.string).isRequired,
};

class EndpointForm extends PureComponent {
    state = {
        upstreams: this.props.initialValues.proxy.upstreams || {}, // fallback for old endpoints (they have `upstreams: null`), probably temporary
    };

    createStrategyOptions = list => {
        const extractNames = list => list.map(item => item.balancing);
        const labelCombiner = item => ({
            label: item[1],
            value: item[1],
            options: item[0],
        });
        const combineListOfUnitsAndLabels = list => list.map(labelCombiner);

        return R.compose(
            combineListOfUnitsAndLabels,
            R.zip(list),
            extractNames,
        )(list);
    };

    handleChangeStrategy = value => {
        this.setState(() => ({
            upstreams: {
                balancing: value.value,
                targets: value.options,
            }
        }), () => this.props.change('proxy.upstreams.balancing', value.value));
    };

    renderStrategy = balancing => {
        switch (balancing) {
            case 'roundrobin': {
                return (
                    <MultiRowField
                        name="proxy.upstreams.targets"
                        suffix="target"
                        title="Targets"
                        placeholder="Target"
                        isValidate="url"
                        warningMessage={WARNINGS.URL}
                    />
                );
            }
            case 'weight': {
                return (
                    <WeightTargets
                        name="proxy.upstreams.targets"
                        title="Targets"
                        isValidate="url"
                        warningMessage={WARNINGS.URL}
                    />
                );
            }
            default:
                return null;
        }
    };

    render() {
        const {
            api,
            apiSchema,
            disabled,
            editing,
            excludePlugin,
            initialValues,
            handleSubmit,
            handleDelete,
            plugins,
            response,
            selectPlugin,
            selectedPlugins,
        } = this.props;

        const includePlugin = value => {
            apiSchema.plugins
                .filter((plugin, index) =>
                    plugin.name === value.value && !selectedPlugins.includes(plugin.name)
                )
                .map((plugin, index) => selectPlugin(plugin.name));
        };

        return (
            <form className={b} onSubmit={handleSubmit}>

                    <Sticky stickyClassName={b('sticky')()}>
                        <Row>
                            <Title>
                                { editing ? 'Edit API' : 'Create New API' }
                            </Title>
                            {
                                editing && api.name &&

                                        <Link
                                            to={{
                                                pathname: '/new',
                                                state: {
                                                    clone: api,
                                                },
                                            }}
                                        >
                                            <Button
                                                type="button"
                                                mod="primary"
                                            >
                                                <Icon type="copy-white" />
                                                Copy
                                            </Button>
                                        </Link>
                                        <Button
                                            type="button"
                                            mod="danger"
                                            onClick={handleDelete}
                                        >
                                            <Icon type="delete-white" />
                                            Delete
                                        </Button>

                            }
                        </Row>
                    </Sticky>

1. General

                        <Row className={b('row')()} fullwidth>

                                    API Name

                                <Field
                                    name="name"
                                    type="text"
                                    component={Input}
                                    disabled={disabled}
                                    validate={checkOnPattern('name')}
                                    required
                                />
                                {WARNINGS.NAMES}
                                <Hint>Must be unique.</Hint>

                            <Row col>
                                Is Active?
                                <Row className={b('radio-wrap')()}>
                                    <Row className={b('radio')()}>
                                        <Field
                                            name="active"
                                            component={Radio}
                                            value={'true'}
                                            type="radio"
                                            id="is-active"
                                        />
                                        Yes
                                    </Row>
                                    <Row className={b('radio')()}>
                                        <Field
                                            name="active"
                                            component={Radio}
                                            value={'false'}
                                            type="radio"
                                            id="is-not-active"
                                        />
                                        No
                                    </Row>
                                </Row>
                            </Row>
                        </Row>

2. Proxy

                        <Row className={b('row')()} fullwidth>

                                    Listen Path

                                <Field
                                    name="proxy.listen_path"
                                    type="text"
                                    placeholder={PLACEHOLDER.LISTEN_PATH}
                                    component={Input}
                                    validate={checkOnPattern('/')}
                                    required
                                />
                                {WARNINGS.LISTEN_PATH}
                                <Hint>The public url that is exposed by the Gateway.</Hint>

                                    Load balancing alg.

                                <Select
                                    className="j-select"
                                    name="token_strategy.name"
                                    options={this.createStrategyOptions(apiSchema.proxy.upstreams.options)}
                                    onChange={this.handleChangeStrategy}
                                    value={this.state.upstreams.balancing}
                                    clearable={false}
                                    required
                                />

                                    <Row className={b('row')()} fullwidth>
                                        { this.renderStrategy(this.state.upstreams.balancing) }
                                    </Row>

                        </Row>
                        <Row className={b('row')()} fullwidth>
                            <Row col>
                                Methods
                                <Field
                                    name="proxy.methods"
                                    type="text"
                                    placeholder="Choose one or more methods"
                                    edit
                                    value={editing ? () => getValues(['proxy', 'methods'])(initialValues) : []}
                                    options={optionsTransformer(apiSchema.proxy.methods)}
                                    component={MultiSelect}
                                />
                                <Hint>HTTP methods that are supported for the endpoint.</Hint>
                            </Row>
                            <Row col>
                                Hosts
                                <Field
                                    name="proxy.hosts"
                                    type="text"
                                    edit
                                    value={editing ? () => getValues(['proxy', 'hosts'])(initialValues) : []}
                                    options={optionsTransformer(apiSchema.proxy.hosts)}
                                    component={TagSelect}
                                />
                            </Row>
                        </Row>
                        <Row className={b('row')()} fullwidth>
                            <Row col>
                                Preserve Host?
                                <Row className={b('radio-wrap')()}>
                                    <Row className={b('radio')()}>
                                        <Field
                                            name="proxy.preserve_host"
                                            component={Radio}
                                            value={'true'}
                                            type="radio"
                                            id="preserve-host-true"
                                        />
                                        Yes
                                    </Row>
                                    <Row className={b('radio')()}>
                                        <Field
                                            name="proxy.preserve_host"
                                            component={Radio}
                                            value={'false'}
                                            type="radio"
                                            id="preserve-host-false"
                                        />
                                        No
                                    </Row>
                                </Row>
                                <Hint>Preserve the host header the client used for the incoming request.</Hint>
                            </Row>
                            <Row col>
                                Append Path?
                                <Row className={b('radio-wrap')()}>
                                    <Row className={b('radio')()}>
                                        <Field
                                            name="proxy.append_path"
                                            component={Radio}
                                            value={'true'}
                                            type="radio"
                                            id="append-path-true"
                                        />
                                        Yes
                                    </Row>
                                    <Row className={b('radio')()}>
                                        <Field
                                            name="proxy.append_path"
                                            component={Radio}
                                            value={'false'}
                                            type="radio"
                                            id="append-path-false"
                                        />
                                        No
                                    </Row>
                                </Row>
                                <Hint>Appends the path from the listen_path when forwarding the request to the upstream_url.</Hint>
                            </Row>
                        </Row>
                        <Row className={b('row')()} fullwidth>
                            <Row col>
                                Strips Path?
                                <Row className={b('radio-wrap')()}>
                                    <Row className={b('radio')()}>
                                        <Field
                                            name="proxy.strip_path"
                                            component={Radio}
                                            value={'true'}
                                            type="radio"
                                            id="strip-path-true"
                                        />
                                        Yes
                                    </Row>
                                    <Row className={b('radio')()}>
                                        <Field
                                            name="proxy.strip_path"
                                            component={Radio}
                                            value={'false'}
                                            type="radio"
                                            id="strip-path-false"
                                        />
                                        No
                                    </Row>
                                </Row>
                                <Hint> Strip the path out of the listen_path when forwarding the request to the upstream_url.</Hint>
                            </Row>

                        </Row>

3. Health check

                        <Row className={b('row')()} fullwidth>

                                    Health URL (optional)

                                <Field
                                    name="health_check.url"
                                    type="text"
                                    placeholder={PLACEHOLDER.HEALTH_CHECK_URL}
                                    component={Input}
                                    validate={checkOnPattern('url')}
                                />
                                {WARNINGS.URL}
                                <Hint>The url that the Gateway will use to determine the health of the API.</Hint>

                            <Row col>
                                Timeout (optional)
                                <Field
                                    name="health_check.timeout"
                                    type="number"
                                    parse={parse}
                                    component={Input}
                                />
                                <Hint>The length of time that the Gateway should wait before displaying an error.</Hint>
                            </Row>
                        </Row>

4. Plugins

                        {
                            !!plugins &&
                                <RenderPlugins
                                    className={b()}
                                    apiSchema={apiSchema}
                                    plugins={plugins}
                                    initialValues={initialValues}
                                    selectedPlugins={selectedPlugins}
                                    handlePluginInclude={includePlugin}
                                    handlePluginExclude={excludePlugin}
                                    response={response}
                                    edit={editing}
                                />
                        }

                <Row className={b('row',{ 'button-row': true })()}>
                    <Button
                        type="submit"
                        mod="primary"
                    >
                        Save
                    </Button>
                </Row>
            </form>
        );
    }
};

EndpointForm.propTypes = propTypes;

const selector = formValueSelector('apiForm');

const form = reduxForm({
    form: 'apiForm',
    enableReinitialize: true, // this is needed!!
})(EndpointForm);

export default connect(
    state => {
        const plugins = selector(state, 'plugins');

        return {
            keepDirtyOnReinitialize: false,
            plugins,
        };
    },
    null,
)(form);
<commit_msg>fix rendering of load balancing while cloning endpoint<commit_after>import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import R from 'ramda';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { Field, formValueSelector, reduxForm } from 'redux-form';
import Select from 'react-select';
import Sticky from 'react-sticky-el';

import PLACEHOLDER from '../../../configurations/placeholders.config';
import WARNINGS from '../../../configurations/warning-messages.config';

import block from '../../../helpers/bem-cn';
import checkOnPattern from '../../../helpers/pattern-check';
import parse from '../../../helpers/parse-value';
import optionsTransformer from '../../../helpers/optionsTransformer';
import getValues from '../../../helpers/getValues';

import Section from '../../Layout/Section/Section';
import Title from '../../Layout/Title/Title';
import Row from '../../Layout/Row/Row';
import Input from '../../inputs/Input';
import Radio from '../../../components/Radio/Radio';
import Label from '../../../components/Label/Label';
import Hint from '../../../components/Hint/Hint';
import MultiSelect from '../../selects/MultiSelect/MultiSelect';
import TagSelect from '../../selects/TagSelect/TagSelect';
import Button from '../../../components/Button/Button';
import Icon from '../../../components/Icon/Icon';

import RenderPlugins from '../plugins/RenderPlugins';

import MultiRowField from '../../../components/MultiRowField/MultiRowField';
import WeightTargets from '../../pages/NewApiPage/partials/WeightTargets/WeightTargets';

import './EndpointForm.css';

const b = block('j-api-form');
const col = block('j-col');
const row = block('j-row');

const propTypes = {
    api: PropTypes.object.isRequired,
    apiSchema: PropTypes.object.isRequired,
    disabled: PropTypes.bool.isRequired,
    editing: PropTypes.bool,
    excludePlugin: PropTypes.func.isRequired,
    handleDelete: PropTypes.func.isRequired,
    handleSubmit: PropTypes.func.isRequired,
    initialValues: PropTypes.object,
    selectPlugin: PropTypes.func.isRequired,
    selectedPlugins: PropTypes.arrayOf(PropTypes.string).isRequired,
};

class EndpointForm extends PureComponent {
    state = {
        upstreams: this.props.initialValues.proxy.upstreams || {}, // fallback for old endpoints (they have `upstreams: null`), probably temporary
    };

    componentWillReceiveProps(nextProps) {
        if (this.props.initialValues.proxy.upstreams !== nextProps.initialValues.proxy.upstreams) {
            this.setState({
                upstreams: nextProps.initialValues.proxy.upstreams,
            });
        }
    }

    createStrategyOptions = list => {
        const extractNames = list => list.map(item => item.balancing);
        const labelCombiner = item => ({
            label: item[1],
            value: item[1],
            options: item[0],
        });
        const combineListOfUnitsAndLabels = list => list.map(labelCombiner);

        return R.compose(
            combineListOfUnitsAndLabels,
            R.zip(list),
            extractNames,
        )(list);
    };

    handleChangeStrategy = value => {
        this.setState(() => ({
            upstreams: {
                balancing: value.value,
                targets: value.options,
            }
        }), () => this.props.change('proxy.upstreams.balancing', value.value));
    };

    renderStrategy = balancing => {
        switch (balancing) {
            case 'roundrobin': {
                return (
                    <MultiRowField
                        name="proxy.upstreams.targets"
                        suffix="target"
                        title="Targets"
                        placeholder="Target"
                        isValidate="url"
                        warningMessage={WARNINGS.URL}
                    />
                );
            }
            case 'weight': {
                return (
                    <WeightTargets
                        name="proxy.upstreams.targets"
                        title="Targets"
                        isValidate="url"
                        warningMessage={WARNINGS.URL}
                    />
                );
            }
            default:
                return null;
        }
    };

    render() {
        const {
            api,
            apiSchema,
            disabled,
            editing,
            excludePlugin,
            initialValues,
            handleSubmit,
            handleDelete,
            plugins,
            response,
            selectPlugin,
            selectedPlugins,
        } = this.props;

        const includePlugin = value => {
            apiSchema.plugins
                .filter((plugin, index) =>
                    plugin.name === value.value && !selectedPlugins.includes(plugin.name)
                )
                .map((plugin, index) => selectPlugin(plugin.name));
        };

        return (
            <form className={b} onSubmit={handleSubmit}>

                    <Sticky stickyClassName={b('sticky')()}>
                        <Row>
                            <Title>
                                { editing ? 'Edit API' : 'Create New API' }
                            </Title>
                            {
                                editing && api.name &&

                                        <Link
                                            to={{
                                                pathname: '/new',
                                                state: {
                                                    clone: api,
                                                },
                                            }}
                                        >
                                            <Button
                                                type="button"
                                                mod="primary"
                                            >
                                                <Icon type="copy-white" />
                                                Copy
                                            </Button>
                                        </Link>
                                        <Button
                                            type="button"
                                            mod="danger"
                                            onClick={handleDelete}
                                        >
                                            <Icon type="delete-white" />
                                            Delete
                                        </Button>

                            }
                        </Row>
                    </Sticky>

1. General

                        <Row className={b('row')()} fullwidth>

                                    API Name

                                <Field
                                    name="name"
                                    type="text"
                                    component={Input}
                                    disabled={disabled}
                                    validate={checkOnPattern('name')}
                                    required
                                />
                                {WARNINGS.NAMES}
                                <Hint>Must be unique.</Hint>

                            <Row col>
                                Is Active?
                                <Row className={b('radio-wrap')()}>
                                    <Row className={b('radio')()}>
                                        <Field
                                            name="active"
                                            component={Radio}
                                            value={'true'}
                                            type="radio"
                                            id="is-active"
                                        />
                                        Yes
                                    </Row>
                                    <Row className={b('radio')()}>
                                        <Field
                                            name="active"
                                            component={Radio}
                                            value={'false'}
                                            type="radio"
                                            id="is-not-active"
                                        />
                                        No
                                    </Row>
                                </Row>
                            </Row>
                        </Row>

2. Proxy

                        <Row className={b('row')()} fullwidth>

                                    Listen Path

                                <Field
                                    name="proxy.listen_path"
                                    type="text"
                                    placeholder={PLACEHOLDER.LISTEN_PATH}
                                    component={Input}
                                    validate={checkOnPattern('/')}
                                    required
                                />
                                {WARNINGS.LISTEN_PATH}
                                <Hint>The public url that is exposed by the Gateway.</Hint>

                                    Load balancing alg.

                                <Select
                                    className="j-select"
                                    name="token_strategy.name"
                                    options={this.createStrategyOptions(apiSchema.proxy.upstreams.options)}
                                    onChange={this.handleChangeStrategy}
                                    value={this.state.upstreams.balancing}
                                    clearable={false}
                                    required
                                />

                                    <Row className={b('row')()} fullwidth>
                                        { this.renderStrategy(this.state.upstreams.balancing) }
                                    </Row>

                        </Row>
                        <Row className={b('row')()} fullwidth>
                            <Row col>
                                Methods
                                <Field
                                    name="proxy.methods"
                                    type="text"
                                    placeholder="Choose one or more methods"
                                    edit
                                    value={editing ? () => getValues(['proxy', 'methods'])(initialValues) : []}
                                    options={optionsTransformer(apiSchema.proxy.methods)}
                                    component={MultiSelect}
                                />
                                <Hint>HTTP methods that are supported for the endpoint.</Hint>
                            </Row>
                            <Row col>
                                Hosts
                                <Field
                                    name="proxy.hosts"
                                    type="text"
                                    edit
                                    value={editing ? () => getValues(['proxy', 'hosts'])(initialValues) : []}
                                    options={optionsTransformer(apiSchema.proxy.hosts)}
                                    component={TagSelect}
                                />
                            </Row>
                        </Row>
                        <Row className={b('row')()} fullwidth>
                            <Row col>
                                Preserve Host?
                                <Row className={b('radio-wrap')()}>
                                    <Row className={b('radio')()}>
                                        <Field
                                            name="proxy.preserve_host"
                                            component={Radio}
                                            value={'true'}
                                            type="radio"
                                            id="preserve-host-true"
                                        />
                                        Yes
                                    </Row>
                                    <Row className={b('radio')()}>
                                        <Field
                                            name="proxy.preserve_host"
                                            component={Radio}
                                            value={'false'}
                                            type="radio"
                                            id="preserve-host-false"
                                        />
                                        No
                                    </Row>
                                </Row>
                                <Hint>Preserve the host header the client used for the incoming request.</Hint>
                            </Row>
                            <Row col>
                                Append Path?
                                <Row className={b('radio-wrap')()}>
                                    <Row className={b('radio')()}>
                                        <Field
                                            name="proxy.append_path"
                                            component={Radio}
                                            value={'true'}
                                            type="radio"
                                            id="append-path-true"
                                        />
                                        Yes
                                    </Row>
                                    <Row className={b('radio')()}>
                                        <Field
                                            name="proxy.append_path"
                                            component={Radio}
                                            value={'false'}
                                            type="radio"
                                            id="append-path-false"
                                        />
                                        No
                                    </Row>
                                </Row>
                                <Hint>Appends the path from the listen_path when forwarding the request to the upstream_url.</Hint>
                            </Row>
                        </Row>
                        <Row className={b('row')()} fullwidth>
                            <Row col>
                                Strips Path?
                                <Row className={b('radio-wrap')()}>
                                    <Row className={b('radio')()}>
                                        <Field
                                            name="proxy.strip_path"
                                            component={Radio}
                                            value={'true'}
                                            type="radio"
                                            id="strip-path-true"
                                        />
                                        Yes
                                    </Row>
                                    <Row className={b('radio')()}>
                                        <Field
                                            name="proxy.strip_path"
                                            component={Radio}
                                            value={'false'}
                                            type="radio"
                                            id="strip-path-false"
                                        />
                                        No
                                    </Row>
                                </Row>
                                <Hint> Strip the path out of the listen_path when forwarding the request to the upstream_url.</Hint>
                            </Row>

                        </Row>

3. Health check

                        <Row className={b('row')()} fullwidth>

                                    Health URL (optional)

                                <Field
                                    name="health_check.url"
                                    type="text"
                                    placeholder={PLACEHOLDER.HEALTH_CHECK_URL}
                                    component={Input}
                                    validate={checkOnPattern('url')}
                                />
                                {WARNINGS.URL}
                                <Hint>The url that the Gateway will use to determine the health of the API.</Hint>

                            <Row col>
                                Timeout (optional)
                                <Field
                                    name="health_check.timeout"
                                    type="number"
                                    parse={parse}
                                    component={Input}
                                />
                                <Hint>The length of time that the Gateway should wait before displaying an error.</Hint>
                            </Row>
                        </Row>

4. Plugins

                        {
                            !!plugins &&
                                <RenderPlugins
                                    className={b()}
                                    apiSchema={apiSchema}
                                    plugins={plugins}
                                    initialValues={initialValues}
                                    selectedPlugins={selectedPlugins}
                                    handlePluginInclude={includePlugin}
                                    handlePluginExclude={excludePlugin}
                                    response={response}
                                    edit={editing}
                                />
                        }

                <Row className={b('row',{ 'button-row': true })()}>
                    <Button
                        type="submit"
                        mod="primary"
                    >
                        Save
                    </Button>
                </Row>
            </form>
        );
    }
};

EndpointForm.propTypes = propTypes;

const selector = formValueSelector('apiForm');

const form = reduxForm({
    form: 'apiForm',
    enableReinitialize: true, // this is needed!!
})(EndpointForm);

export default connect(
    state => {
        const plugins = selector(state, 'plugins');

        return {
            keepDirtyOnReinitialize: false,
            plugins,
        };
    },
    null,
)(form);
<|endoftext|>


<commit_before>/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2014 Broad Institute
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

var igv = (function (igv) {

    //
    igv.RulerTrack = function () {

        this.height = 50;
        this.name = "";
        this.id = "ruler";
        this.disableButtons = true;
        this.ignoreTrackMenu = true;
        this.order = -Number.MAX_VALUE;

    };

    igv.RulerTrack.prototype.getFeatures = function (chr, bpStart, bpEnd, success, task) {
        success([]);
    };

    igv.RulerTrack.prototype.draw = function (options) {

        var fontStyle,
            ctx = options.context,
            range,
            ts,
            spacing,
            nTick,
            x;

        //fontStyle = { textAlign: 'center', font: 'bold 12px Arial', fillStyle: "green", strokeStyle: "red"};
        fontStyle = { textAlign: 'center', font: '10px PT Sans', fillStyle: "rgba(64, 64, 64, 1)", strokeStyle: "rgba(64, 64, 64, 1)" };

        range = Math.floor(1100 * options.bpPerPixel);
        ts = findSpacing(range);
        spacing = ts.majorTick;

        // Find starting point closest to the current origin
        nTick = Math.floor(options.bpStart / spacing) - 1;
        x = 0;

        //canvas.setProperties({textAlign: 'center'});
        igv.Canvas.setProperties.call(ctx, fontStyle );
        while (x < options.pixelWidth) {

            var l = Math.floor(nTick * spacing);
            x = Math.round(((l - 1) - options.bpStart + 0.5) / options.bpPerPixel);
            var chrPosition = formatNumber(l / ts.unitMultiplier, 0) + " " + ts.majorUnit;

            if (nTick % 1 == 0) {
                igv.Canvas.fillText.call(ctx, chrPosition, x, this.height - 15);
            }

            igv.Canvas.strokeLine.call(ctx, x, this.height - 10, x, this.height - 2);

            nTick++;
        }
        igv.Canvas.strokeLine.call(ctx, 0, this.height - 1, options.pixelWidth, this.height - 1);

        function formatNumber(anynum, decimal) {
            //decimal  - the number of decimals after the digit from 0 to 3
            //-- Returns the passed number as a string in the xxx,xxx.xx format.
            //anynum = eval(obj.value);
            var divider = 10;
            switch (decimal) {
                case 0:
                    divider = 1;
                    break;
                case 1:
                    divider = 10;
                    break;
                case 2:
                    divider = 100;
                    break;
                default:       //for 3 decimal places
                    divider = 1000;
            }

            var workNum = Math.abs((Math.round(anynum * divider) / divider));

            var workStr = "" + workNum

            if (workStr.indexOf(".") == -1) {
                workStr += "."
            }

            var dStr = workStr.substr(0, workStr.indexOf("."));
            var dNum = dStr - 0
            var pStr = workStr.substr(workStr.indexOf("."))

            while (pStr.length - 1 < decimal) {
                pStr += "0"
            }

            if (pStr == '.') pStr = '';

            //--- Adds a comma in the thousands place.
            if (dNum >= 1000) {
                var dLen = dStr.length
                dStr = parseInt("" + (dNum / 1000)) + "," + dStr.substring(dLen - 3, dLen)
            }

            //-- Adds a comma in the millions place.
            if (dNum >= 1000000) {
                dLen = dStr.length
                dStr = parseInt("" + (dNum / 1000000)) + "," + dStr.substring(dLen - 7, dLen)
            }
            var retval = dStr + pStr
            //-- Put numbers in parentheses if negative.
            if (anynum < 0) {
                retval = "(" + retval + ")";
            }

            //You could include a dollar sign in the return value.
            //retval =  "$"+retval
            return retval;
        }

    };

    function TickSpacing(majorTick, majorUnit, unitMultiplier) {
        this.majorTick = majorTick;
        this.majorUnit = majorUnit;
        this.unitMultiplier = unitMultiplier;
    }

    function findSpacing(maxValue) {

        if (maxValue < 10) {
            return new TickSpacing(1, "", 1);
        }

        // Now man zeroes?
        var nZeroes = Math.floor(log10(maxValue));
        var majorUnit = "";
        var unitMultiplier = 1;
        if (nZeroes > 9) {
            majorUnit = "gb";
            unitMultiplier = 1000000000;
        }
        if (nZeroes > 6) {
            majorUnit = "mb";
            unitMultiplier = 1000000;
        } else if (nZeroes > 3) {
            majorUnit = "kb";
            unitMultiplier = 1000;
        }

        var nMajorTicks = maxValue / Math.pow(10, nZeroes - 1);
        if (nMajorTicks < 25) {
            return new TickSpacing(Math.pow(10, nZeroes - 1), majorUnit, unitMultiplier);
        } else {
            return new TickSpacing(Math.pow(10, nZeroes) / 2, majorUnit, unitMultiplier);
        }

        function log10(x) {
            var dn = Math.log(10);
            return Math.log(x) / dn;
        }
    }

    return igv;
})(igv || {});<commit_msg>Ruler text size tweeks<commit_after>/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2014 Broad Institute
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

var igv = (function (igv) {

    //
    igv.RulerTrack = function () {

        this.height = 50;
        this.name = "";
        this.id = "ruler";
        this.disableButtons = true;
        this.ignoreTrackMenu = true;
        this.order = -Number.MAX_VALUE;

    };

    igv.RulerTrack.prototype.getFeatures = function (chr, bpStart, bpEnd, success, task) {
        success([]);
    };

    igv.RulerTrack.prototype.draw = function (options) {

        var fontStyle,
            ctx = options.context,
            range,
            ts,
            spacing,
            nTick,
            x;

        //fontStyle = { textAlign: 'center', font: 'bold 12px Arial', fillStyle: "green", strokeStyle: "red"};
        fontStyle = { textAlign: 'center', font: '14px PT Sans', fillStyle: "rgba(64, 64, 64, 1)", strokeStyle: "rgba(64, 64, 64, 1)" };

        range = Math.floor(1100 * options.bpPerPixel);
        ts = findSpacing(range);
        spacing = ts.majorTick;

        // Find starting point closest to the current origin
        nTick = Math.floor(options.bpStart / spacing) - 1;
        x = 0;

        //canvas.setProperties({textAlign: 'center'});
        igv.Canvas.setProperties.call(ctx, fontStyle );
        while (x < options.pixelWidth) {

            var l = Math.floor(nTick * spacing);
            x = Math.round(((l - 1) - options.bpStart + 0.5) / options.bpPerPixel);
            var chrPosition = formatNumber(l / ts.unitMultiplier, 0) + " " + ts.majorUnit;

            if (nTick % 1 == 0) {
                igv.Canvas.fillText.call(ctx, chrPosition, x, this.height - 15);
            }

            igv.Canvas.strokeLine.call(ctx, x, this.height - 10, x, this.height - 2);

            nTick++;
        }
        igv.Canvas.strokeLine.call(ctx, 0, this.height - 1, options.pixelWidth, this.height - 1);

        function formatNumber(anynum, decimal) {
            //decimal  - the number of decimals after the digit from 0 to 3
            //-- Returns the passed number as a string in the xxx,xxx.xx format.
            //anynum = eval(obj.value);
            var divider = 10;
            switch (decimal) {
                case 0:
                    divider = 1;
                    break;
                case 1:
                    divider = 10;
                    break;
                case 2:
                    divider = 100;
                    break;
                default:       //for 3 decimal places
                    divider = 1000;
            }

            var workNum = Math.abs((Math.round(anynum * divider) / divider));

            var workStr = "" + workNum

            if (workStr.indexOf(".") == -1) {
                workStr += "."
            }

            var dStr = workStr.substr(0, workStr.indexOf("."));
            var dNum = dStr - 0
            var pStr = workStr.substr(workStr.indexOf("."))

            while (pStr.length - 1 < decimal) {
                pStr += "0"
            }

            if (pStr == '.') pStr = '';

            //--- Adds a comma in the thousands place.
            if (dNum >= 1000) {
                var dLen = dStr.length
                dStr = parseInt("" + (dNum / 1000)) + "," + dStr.substring(dLen - 3, dLen)
            }

            //-- Adds a comma in the millions place.
            if (dNum >= 1000000) {
                dLen = dStr.length
                dStr = parseInt("" + (dNum / 1000000)) + "," + dStr.substring(dLen - 7, dLen)
            }
            var retval = dStr + pStr
            //-- Put numbers in parentheses if negative.
            if (anynum < 0) {
                retval = "(" + retval + ")";
            }

            //You could include a dollar sign in the return value.
            //retval =  "$"+retval
            return retval;
        }

    };

    function TickSpacing(majorTick, majorUnit, unitMultiplier) {
        this.majorTick = majorTick;
        this.majorUnit = majorUnit;
        this.unitMultiplier = unitMultiplier;
    }

    function findSpacing(maxValue) {

        if (maxValue < 10) {
            return new TickSpacing(1, "", 1);
        }

        // Now man zeroes?
        var nZeroes = Math.floor(log10(maxValue));
        var majorUnit = "";
        var unitMultiplier = 1;
        if (nZeroes > 9) {
            majorUnit = "gb";
            unitMultiplier = 1000000000;
        }
        if (nZeroes > 6) {
            majorUnit = "mb";
            unitMultiplier = 1000000;
        } else if (nZeroes > 3) {
            majorUnit = "kb";
            unitMultiplier = 1000;
        }

        var nMajorTicks = maxValue / Math.pow(10, nZeroes - 1);
        if (nMajorTicks < 25) {
            return new TickSpacing(Math.pow(10, nZeroes - 1), majorUnit, unitMultiplier);
        } else {
            return new TickSpacing(Math.pow(10, nZeroes) / 2, majorUnit, unitMultiplier);
        }

        function log10(x) {
            var dn = Math.log(10);
            return Math.log(x) / dn;
        }
    }

    return igv;
})(igv || {});<|endoftext|>