<commit_before>//= require <jquery.js>
//= require <jqModal.js>
//= require <jquery.flot.js>
//= require <jquery.ba-bbq.js>
//= require <underscore.js>
//= require <tools.tabs.js>
//= require <jquery.cookie.js>
//= require <misc.js>
//= require <base64.js>
//= require <mkclass.js>
//= require <callbacks.js>
//= require <cells.js>
//= require <hash-fragment-cells.js>
//= require <right-form-observer.js>
//= require <app-misc.js>
//= require <core-data.js>
//= require <analytics.js>
//= require <manage-servers.js>
//= require <settings.js>
//= require <manage-buckets.js>
//= require <monitor-buckets.js>

// TODO: doesn't work due to apparent bug in jqModal. Consider switching to another modal windows implementation
// $(function () {
//   $(window).keydown(function (ev) {
//     if (ev.keyCode != 0x1b) // escape
//       return;
//     console.log("got escape!");
//     // escape is pressed, now check if any jqModal window is active and hide it
//     _.each(_.values($.jqm.hash), function (modal) {
//       if (!modal.a)
//         return;
//       $(modal.w).jqmHide();
//     });
//   });
// });


var LogoutTimer = {
  reset: function () {
    if (this.timeoutId) {
      clearTimeout(this.timeoutId);
    }
    if (!DAO.login)
      return;
    this.timeoutId = setTimeout($m(this, 'onTimeout'), 300000);
  },
  onTimeout: function () {
    $.cookie('inactivity_reload', '1');
    DAO.setAuthCookie(null);
    reloadApp();
  }
};

var OverviewSection = {
  renderStatus: function () {
    var nodes = DAO.cells.currentPoolDetails.value.nodes;
    var buckets = BucketsSection.cells.detailedBuckets.value;

    var totalMem = 0;
    var freeMem = 0;
    _.each(nodes, function (n) {
      totalMem += n.memoryTotal;
      freeMem += n.memoryFree;
    });

    var bucketsSizeTotal = 0;  // The total of the buckets defined
    if(buckets) {
      _.each(buckets, function(b) {
          bucketsSizeTotal += b.basicStats.cacheSize;
      });
    }

    this.clusterMemoryAvailable = totalMem - bucketsSizeTotal*1048576;

    var memoryUtilization = 100-Math.round(freeMem*100/totalMem) << 0;

    var isWarning = memoryUtilization > 90;

    var isCritical = false;
    isCritical = isCritical || _.any(nodes, function (n) {
      return n.status != 'healthy';
    });

    var mcdMemReserved = 0;
    var mcdMemAllocd = 0;
    _.each(nodes, function (n) {
        mcdMemReserved += n.mcdMemoryReserved;
        mcdMemAllocd += n.mcdMemoryAllocated;
      });
    mcdMemReserved *= 1048576;
    var mcdItemUtilization = Math.round(mcdMemReserved*100/totalMem);

    var canJoinCluster = (nodes.length == 1);

    var statusData = {
      isCritical: isCritical,
      isWarning: isWarning,
      canJoinCluster: canJoinCluster,
      nodesCount: nodes.length,
      bucketsCount: buckets && buckets.length,
      bucketsSizeTotal: bucketsSizeTotal,
      memoryUtilization: memoryUtilization,
      memoryFree: freeMem,
      mcdItemUtilization: mcdItemUtilization,
      mcdMemReserved: mcdMemReserved
    };

    renderTemplate('cluster_status', statusData);

    var leaveJoinClass = canJoinCluster ? 'join-possible' : 'leave-possible';
    $('#join_leave_switch').attr('class', leaveJoinClass);
  },
  onFreshNodeList: function () {
    var nodes = DAO.cells.currentPoolDetails.value.nodes;
    var expandedHostnames = [];
    $('#server_list_container .primary .expander.expanded').each(function () {
      var server = $.data(this, 'server')
      if (server)
        expandedHostnames.push(server.hostname);
    });
    renderTemplate('server_list', nodes);
    $('#server_list_container table tr.primary:first-child').addClass('nbrdr');
    $('#server_list_container .primary .expander').each(function () {
      var server = $.data(this, 'server');
      if (!server)
        return;
      if (!_.include(expandedHostnames, server.hostname))
        return;
      $(this).parents('#server_list_container .primary').next().addClass('opened');
      $(this).addClass('expanded');
    });

    this.renderStatus();

    var activeNodeCount = _.select(nodes, function (n) {
      return n.status == 'healthy';
    }).length;

    $('.active_node_count').text(ViewHelpers.count(activeNodeCount, "active node"));
  },
  leaveCluster: function () {
    showDialog("eject_confirmation_dialog", {
      eventBindings: [['.save_button', 'click', function (e) {
        e.preventDefault();
        overlayWithSpinner('#eject_confirmation_dialog');

        var reload = mkReloadWithDelay();
        $.ajax({
          type: 'POST',
          url: DAO.cells.currentPoolDetails.value.controllers.ejectNode.uri,
          data: "otpNode=Self",
          success: reload,
          errors: reload
        });
      }]]
    });
  },
  removeNode: function (otpNode) {
    var details = DAO.cells.currentPoolDetails.value.nodes;
    var node = _.detect(details, function (n) {
      return n.otpNode == otpNode;
    });
    if (!node)
      throw new Error('!node. this is unexpected!');

    showDialog("eject_confirmation_dialog", {
      eventBindings: [['.save_button', 'click', function (e) {
        e.preventDefault();

        overlayWithSpinner('#eject_confirmation_dialog');
        var reload = mkReloadWithDelay();
        $.ajax({
          type: 'POST',
          url: DAO.cells.currentPoolDetails.value.controllers.ejectNode.uri,
          data: {otpNode: node.otpNode},
          error: reload,
          success: reload
        });
      }]]
    });
  },
  init: function () {
    var self = this;
    _.defer(function () {
      BucketsSection.cells.detailedBuckets.subscribe($m(self, 'renderStatus'));
    });
    DAO.cells.currentPoolDetails.subscribe($m(self, 'onFreshNodeList'));
    prepareTemplateForCell('server_list', DAO.cells.currentPoolDetails);
    prepareTemplateForCell('cluster_status', DAO.cells.currentPoolDetails);
    prepareTemplateForCell('pool_list', DAO.cells.poolList);
  },
  onEnter: function () {
    DAO.cells.currentPoolDetailsCell.invalidate();
  }
};

;(function () {
  var weekDays = "Sun Mon Tue Wed Thu Fri Sat".split(' ');
  var monthNames = "Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(' ');
  function _2digits(d) {
    d += 100;
    return String(d).substring(1);
  }

  window.formatAlertTStamp = function formatAlertTStamp(mseconds) {
    var date = new Date(mseconds);
    var rv = [weekDays[date.getDay()],
      ' ',
      monthNames[date.getMonth()],
      ' ',
      date.getDate(),
      ' ',
      _2digits(date.getHours()), ':', _2digits(date.getMinutes()), ':', _2digits(date.getSeconds()),
      ' ',
      date.getFullYear()];

    return rv.join('');
  }

  window.formatLogTStamp = function formatLogTStamp(mseconds) {
    var date = new Date(mseconds);
    var rv = [
      "<strong>",
      _2digits(date.getHours()), ':', _2digits(date.getMinutes()), ':', _2digits(date.getSeconds()),
      "</strong> - ",
      weekDays[date.getDay()],
      ' ',
      monthNames[date.getMonth()],
      ' ',
      date.getDate(),
      ', ',
      date.getFullYear()];

    return rv.join('');
  }
})();

function formatAlertType(type) {
  switch (type) {
  case 'warning':
    return "Warning";
  case 'attention':
    return "Needs Your Attention";
  case 'info':
    return "Informative";
  }
}

var AlertsSection = {
  renderAlertsList: function () {
    var value = this.alerts.value;
    renderTemplate('alert_list', _.clone(value.list).reverse());
  },
  changeEmail: function () {
    SettingsSection.gotoSetupAlerts();
  },
  init: function () {
    this.active = new Cell(function (mode) {
      return (mode == "alerts" || mode == "log") ? true : undefined;
    }).setSources({mode: DAO.cells.mode});

    this.alerts = new Cell(function (active) {
      var value = this.self.value;
      var params = {url: "/alerts"};
      return future.get(params);
    }).setSources({active: this.active});
    this.alerts.keepValueDuringAsync = true;
    prepareTemplateForCell("alert_list", this.alerts);
    this.alerts.subscribe($m(this, 'renderAlertsList'));
    this.alerts.subscribe(function (cell) {
      // refresh every 30 seconds
      cell.recalculateAt((new Date()).valueOf() + 30000);
    });

    this.alertTab = new TabsCell("alertsTab",
                                 "#alerts .tabs",
                                 "#alerts .panes > div",
                                 ["log", "list"]);

    _.defer(function () {
      SettingsSection.advancedSettings.subscribe($m(AlertsSection, 'updateAlertsDestination'));
    });

    this.logs = new Cell(function (active) {
      return future.get({url: "/logs"}, undefined, this.self.value);
    }).setSources({active: this.active});
    this.logs.subscribe(function (cell) {
      cell.recalculateAt((new Date()).valueOf() + 30000);
    });
    this.logs.subscribe($m(this, 'renderLogsList'));
    prepareTemplateForCell('alert_logs', this.logs);
  },
  renderLogsList: function () {
    renderTemplate('alert_logs', _.clone(this.logs.value.list).reverse());
  },
  updateAlertsDestination: function () {
    var cell = SettingsSection.advancedSettings.value;
    var who = ''
    if (cell && ('email' in cell)) {
      who = cell.email || 'nobody'
    }
    $('#alerts_email_setting').text(who);
  },
  onEnter: function () {
  },
  navClick: function () {
    if (DAO.cells.mode.value == 'alerts' ||
        DAO.cells.mode.value == 'log') {
      this.alerts.setValue(undefined);
      this.logs.setValue(undefined);
      this.alerts.recalculate();
      this.logs.recalculate();
    }
  },
  domId: function (sec) {
    return 'alerts';
  }
}
var DummySection = {
  onEnter: function () {}
};

var BreadCrumbs = {
  update: function () {
    var sec = DAO.cells.mode.value;
    var path = [];

    function pushSection(name) {
      var el = $('#switch_' + name);
      path.push([el.text(), el.attr('href')]);
    }

    var container = $('.bread_crumbs > ul');
    container.html('');

    $('.currentNav').removeClass('currentNav');
    $('#switch_' + sec).addClass('currentNav');

    // TODO: Revisit bread-crumbs for server-specific or bucket-specific drill-down screens.
    //
    return;

    if (sec == 'analytics' && DAO.cells.statsBucketURL.value) {
      pushSection('buckets')
      var bucketInfo = DAO.cells.currentStatTargetCell.value;
      if (bucketInfo) {
        path.push([bucketInfo.name, '#visitBucket='+bucketInfo.uri]);
      }
    } else
      pushSection(sec);

    _.each(path.reverse(), function (pair) {
      var name = pair[0];
      var href = pair[1];

      var li = $('<li></li>');
      var a = $('<a></a>');
      a.attr('href', href);
      a.text(name);

      li.prepend(a);

      container.prepend(li);
    });

    container.find(':first-child').addClass('nobg');
  },
  init: function () {
    var cells = DAO.cells;
    var update = $m(this, 'update');

    cells.mode.subscribe(update);
    cells.statsBucketURL.subscribe(update);
    cells.currentStatTargetCell.subscribe(update);
  }
};

var ThePage = {
  sections: {overview: OverviewSection,
             servers: ServersSection,
             analytics: AnalyticsSection,
             buckets: BucketsSection,
             alerts: AlertsSection,
             log: AlertsSection,
             settings: SettingsSection,
             monitor_buckets: MonitorBucketsSection,
             monitor_servers: OverviewSection},

  coming: {monitor_servers:true, settings:true},

  currentSection: null,
  currentSectionName: null,
  signOut: function () {
    $.cookie('auth', null);
    reloadApp();
  },
  ensureSection: function (section) {
    if (this.currentSectionName != section)
      this.gotoSection(section);
  },
  gotoSection: function (section) {
    if (!(this.sections[section])) {
      throw new Error('unknown section:' + section);
    }
    if (this.currentSectionName == section) {
      if ('navClick' in this.currentSection)
        this.currentSection.navClick();
      else
        this.currentSection.onEnter();
    } else
      setHashFragmentParam('sec', section);
  },
  initialize: function () {
    _.each(_.uniq(_.values(this.sections)), function (sec) {
      if (sec.init)
        sec.init();
    });
    BreadCrumbs.init();

    DAO.onReady(function () {
      if (DAO.login) {
        $('.sign-out-link').show();
      }
    });

    var self = this;
    watchHashParamChange('sec', 'servers', function (sec) {
      var oldSection = self.currentSection;
      var currentSection = self.sections[sec];
      if (!currentSection) {
        self.gotoSection('overview');
        return;
      }
      self.currentSectionName = sec;
      self.currentSection = currentSection;

      DAO.switchSection(sec);

      var secId = sec;
      if (currentSection.domId != null) {
        secId = currentSection.domId(sec);
      }<commit_msg>made overview initial landing screen<commit_after>//= require <jquery.js>
//= require <jqModal.js>
//= require <jquery.flot.js>
//= require <jquery.ba-bbq.js>
//= require <underscore.js>
//= require <tools.tabs.js>
//= require <jquery.cookie.js>
//= require <misc.js>
//= require <base64.js>
//= require <mkclass.js>
//= require <callbacks.js>
//= require <cells.js>
//= require <hash-fragment-cells.js>
//= require <right-form-observer.js>
//= require <app-misc.js>
//= require <core-data.js>
//= require <analytics.js>
//= require <manage-servers.js>
//= require <settings.js>
//= require <manage-buckets.js>
//= require <monitor-buckets.js>

// TODO: doesn't work due to apparent bug in jqModal. Consider switching to another modal windows implementation
// $(function () {
//   $(window).keydown(function (ev) {
//     if (ev.keyCode != 0x1b) // escape
//       return;
//     console.log("got escape!");
//     // escape is pressed, now check if any jqModal window is active and hide it
//     _.each(_.values($.jqm.hash), function (modal) {
//       if (!modal.a)
//         return;
//       $(modal.w).jqmHide();
//     });
//   });
// });


var LogoutTimer = {
  reset: function () {
    if (this.timeoutId) {
      clearTimeout(this.timeoutId);
    }
    if (!DAO.login)
      return;
    this.timeoutId = setTimeout($m(this, 'onTimeout'), 300000);
  },
  onTimeout: function () {
    $.cookie('inactivity_reload', '1');
    DAO.setAuthCookie(null);
    reloadApp();
  }
};

var OverviewSection = {
  renderStatus: function () {
    var nodes = DAO.cells.currentPoolDetails.value.nodes;
    var buckets = BucketsSection.cells.detailedBuckets.value;

    var totalMem = 0;
    var freeMem = 0;
    _.each(nodes, function (n) {
      totalMem += n.memoryTotal;
      freeMem += n.memoryFree;
    });

    var bucketsSizeTotal = 0;  // The total of the buckets defined
    if(buckets) {
      _.each(buckets, function(b) {
          bucketsSizeTotal += b.basicStats.cacheSize;
      });
    }

    this.clusterMemoryAvailable = totalMem - bucketsSizeTotal*1048576;

    var memoryUtilization = 100-Math.round(freeMem*100/totalMem) << 0;

    var isWarning = memoryUtilization > 90;

    var isCritical = false;
    isCritical = isCritical || _.any(nodes, function (n) {
      return n.status != 'healthy';
    });

    var mcdMemReserved = 0;
    var mcdMemAllocd = 0;
    _.each(nodes, function (n) {
        mcdMemReserved += n.mcdMemoryReserved;
        mcdMemAllocd += n.mcdMemoryAllocated;
      });
    mcdMemReserved *= 1048576;
    var mcdItemUtilization = Math.round(mcdMemReserved*100/totalMem);

    var canJoinCluster = (nodes.length == 1);

    var statusData = {
      isCritical: isCritical,
      isWarning: isWarning,
      canJoinCluster: canJoinCluster,
      nodesCount: nodes.length,
      bucketsCount: buckets && buckets.length,
      bucketsSizeTotal: bucketsSizeTotal,
      memoryUtilization: memoryUtilization,
      memoryFree: freeMem,
      mcdItemUtilization: mcdItemUtilization,
      mcdMemReserved: mcdMemReserved
    };

    renderTemplate('cluster_status', statusData);

    var leaveJoinClass = canJoinCluster ? 'join-possible' : 'leave-possible';
    $('#join_leave_switch').attr('class', leaveJoinClass);
  },
  onFreshNodeList: function () {
    var nodes = DAO.cells.currentPoolDetails.value.nodes;
    var expandedHostnames = [];
    $('#server_list_container .primary .expander.expanded').each(function () {
      var server = $.data(this, 'server')
      if (server)
        expandedHostnames.push(server.hostname);
    });
    renderTemplate('server_list', nodes);
    $('#server_list_container table tr.primary:first-child').addClass('nbrdr');
    $('#server_list_container .primary .expander').each(function () {
      var server = $.data(this, 'server');
      if (!server)
        return;
      if (!_.include(expandedHostnames, server.hostname))
        return;
      $(this).parents('#server_list_container .primary').next().addClass('opened');
      $(this).addClass('expanded');
    });

    this.renderStatus();

    var activeNodeCount = _.select(nodes, function (n) {
      return n.status == 'healthy';
    }).length;

    $('.active_node_count').text(ViewHelpers.count(activeNodeCount, "active node"));
  },
  leaveCluster: function () {
    showDialog("eject_confirmation_dialog", {
      eventBindings: [['.save_button', 'click', function (e) {
        e.preventDefault();
        overlayWithSpinner('#eject_confirmation_dialog');

        var reload = mkReloadWithDelay();
        $.ajax({
          type: 'POST',
          url: DAO.cells.currentPoolDetails.value.controllers.ejectNode.uri,
          data: "otpNode=Self",
          success: reload,
          errors: reload
        });
      }]]
    });
  },
  removeNode: function (otpNode) {
    var details = DAO.cells.currentPoolDetails.value.nodes;
    var node = _.detect(details, function (n) {
      return n.otpNode == otpNode;
    });
    if (!node)
      throw new Error('!node. this is unexpected!');

    showDialog("eject_confirmation_dialog", {
      eventBindings: [['.save_button', 'click', function (e) {
        e.preventDefault();

        overlayWithSpinner('#eject_confirmation_dialog');
        var reload = mkReloadWithDelay();
        $.ajax({
          type: 'POST',
          url: DAO.cells.currentPoolDetails.value.controllers.ejectNode.uri,
          data: {otpNode: node.otpNode},
          error: reload,
          success: reload
        });
      }]]
    });
  },
  init: function () {
    var self = this;
    _.defer(function () {
      BucketsSection.cells.detailedBuckets.subscribe($m(self, 'renderStatus'));
    });
    DAO.cells.currentPoolDetails.subscribe($m(self, 'onFreshNodeList'));
    prepareTemplateForCell('server_list', DAO.cells.currentPoolDetails);
    prepareTemplateForCell('cluster_status', DAO.cells.currentPoolDetails);
    prepareTemplateForCell('pool_list', DAO.cells.poolList);
  },
  onEnter: function () {
    DAO.cells.currentPoolDetailsCell.invalidate();
  }
};

;(function () {
  var weekDays = "Sun Mon Tue Wed Thu Fri Sat".split(' ');
  var monthNames = "Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(' ');
  function _2digits(d) {
    d += 100;
    return String(d).substring(1);
  }

  window.formatAlertTStamp = function formatAlertTStamp(mseconds) {
    var date = new Date(mseconds);
    var rv = [weekDays[date.getDay()],
      ' ',
      monthNames[date.getMonth()],
      ' ',
      date.getDate(),
      ' ',
      _2digits(date.getHours()), ':', _2digits(date.getMinutes()), ':', _2digits(date.getSeconds()),
      ' ',
      date.getFullYear()];

    return rv.join('');
  }

  window.formatLogTStamp = function formatLogTStamp(mseconds) {
    var date = new Date(mseconds);
    var rv = [
      "<strong>",
      _2digits(date.getHours()), ':', _2digits(date.getMinutes()), ':', _2digits(date.getSeconds()),
      "</strong> - ",
      weekDays[date.getDay()],
      ' ',
      monthNames[date.getMonth()],
      ' ',
      date.getDate(),
      ', ',
      date.getFullYear()];

    return rv.join('');
  }
})();

function formatAlertType(type) {
  switch (type) {
  case 'warning':
    return "Warning";
  case 'attention':
    return "Needs Your Attention";
  case 'info':
    return "Informative";
  }
}

var AlertsSection = {
  renderAlertsList: function () {
    var value = this.alerts.value;
    renderTemplate('alert_list', _.clone(value.list).reverse());
  },
  changeEmail: function () {
    SettingsSection.gotoSetupAlerts();
  },
  init: function () {
    this.active = new Cell(function (mode) {
      return (mode == "alerts" || mode == "log") ? true : undefined;
    }).setSources({mode: DAO.cells.mode});

    this.alerts = new Cell(function (active) {
      var value = this.self.value;
      var params = {url: "/alerts"};
      return future.get(params);
    }).setSources({active: this.active});
    this.alerts.keepValueDuringAsync = true;
    prepareTemplateForCell("alert_list", this.alerts);
    this.alerts.subscribe($m(this, 'renderAlertsList'));
    this.alerts.subscribe(function (cell) {
      // refresh every 30 seconds
      cell.recalculateAt((new Date()).valueOf() + 30000);
    });

    this.alertTab = new TabsCell("alertsTab",
                                 "#alerts .tabs",
                                 "#alerts .panes > div",
                                 ["log", "list"]);

    _.defer(function () {
      SettingsSection.advancedSettings.subscribe($m(AlertsSection, 'updateAlertsDestination'));
    });

    this.logs = new Cell(function (active) {
      return future.get({url: "/logs"}, undefined, this.self.value);
    }).setSources({active: this.active});
    this.logs.subscribe(function (cell) {
      cell.recalculateAt((new Date()).valueOf() + 30000);
    });
    this.logs.subscribe($m(this, 'renderLogsList'));
    prepareTemplateForCell('alert_logs', this.logs);
  },
  renderLogsList: function () {
    renderTemplate('alert_logs', _.clone(this.logs.value.list).reverse());
  },
  updateAlertsDestination: function () {
    var cell = SettingsSection.advancedSettings.value;
    var who = ''
    if (cell && ('email' in cell)) {
      who = cell.email || 'nobody'
    }
    $('#alerts_email_setting').text(who);
  },
  onEnter: function () {
  },
  navClick: function () {
    if (DAO.cells.mode.value == 'alerts' ||
        DAO.cells.mode.value == 'log') {
      this.alerts.setValue(undefined);
      this.logs.setValue(undefined);
      this.alerts.recalculate();
      this.logs.recalculate();
    }
  },
  domId: function (sec) {
    return 'alerts';
  }
}
var DummySection = {
  onEnter: function () {}
};

var BreadCrumbs = {
  update: function () {
    var sec = DAO.cells.mode.value;
    var path = [];

    function pushSection(name) {
      var el = $('#switch_' + name);
      path.push([el.text(), el.attr('href')]);
    }

    var container = $('.bread_crumbs > ul');
    container.html('');

    $('.currentNav').removeClass('currentNav');
    $('#switch_' + sec).addClass('currentNav');

    // TODO: Revisit bread-crumbs for server-specific or bucket-specific drill-down screens.
    //
    return;

    if (sec == 'analytics' && DAO.cells.statsBucketURL.value) {
      pushSection('buckets')
      var bucketInfo = DAO.cells.currentStatTargetCell.value;
      if (bucketInfo) {
        path.push([bucketInfo.name, '#visitBucket='+bucketInfo.uri]);
      }
    } else
      pushSection(sec);

    _.each(path.reverse(), function (pair) {
      var name = pair[0];
      var href = pair[1];

      var li = $('<li></li>');
      var a = $('<a></a>');
      a.attr('href', href);
      a.text(name);

      li.prepend(a);

      container.prepend(li);
    });

    container.find(':first-child').addClass('nobg');
  },
  init: function () {
    var cells = DAO.cells;
    var update = $m(this, 'update');

    cells.mode.subscribe(update);
    cells.statsBucketURL.subscribe(update);
    cells.currentStatTargetCell.subscribe(update);
  }
};

var ThePage = {
  sections: {overview: OverviewSection,
             servers: ServersSection,
             analytics: AnalyticsSection,
             buckets: BucketsSection,
             alerts: AlertsSection,
             log: AlertsSection,
             settings: SettingsSection,
             monitor_buckets: MonitorBucketsSection,
             monitor_servers: OverviewSection},

  coming: {monitor_servers:true, settings:true},

  currentSection: null,
  currentSectionName: null,
  signOut: function () {
    $.cookie('auth', null);
    reloadApp();
  },
  ensureSection: function (section) {
    if (this.currentSectionName != section)
      this.gotoSection(section);
  },
  gotoSection: function (section) {
    if (!(this.sections[section])) {
      throw new Error('unknown section:' + section);
    }
    if (this.currentSectionName == section) {
      if ('navClick' in this.currentSection)
        this.currentSection.navClick();
      else
        this.currentSection.onEnter();
    } else
      setHashFragmentParam('sec', section);
  },
  initialize: function () {
    _.each(_.uniq(_.values(this.sections)), function (sec) {
      if (sec.init)
        sec.init();
    });
    BreadCrumbs.init();

    DAO.onReady(function () {
      if (DAO.login) {
        $('.sign-out-link').show();
      }
    });

    var self = this;
    watchHashParamChange('sec', 'overview', function (sec) {
      var oldSection = self.currentSection;
      var currentSection = self.sections[sec];
      if (!currentSection) {
        self.gotoSection('overview');
        return;
      }
      self.currentSectionName = sec;
      self.currentSection = currentSection;

      DAO.switchSection(sec);

      var secId = sec;
      if (currentSection.domId != null) {
        secId = currentSection.domId(sec);
      }
{'fraction_non_alphanumeric': 0.1571439706914023, 'fraction_numerical': 0.00530049107490841, 'mean_word_length': 2.7895436420026583, 'pattern_counts': {'":': 0, '<': 61, '<?xml version=': 0, '>': 63, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1526239', 'n_tokens_mistral': 8302, 'n_tokens_neox': 7988, 'n_words': 1542}