<commit_before>/**
 * html模板入口
 */
"use strict";

const path = require('path');
const url = require('url');
const glob = require('glob');

const HtmlWebpackPlugin = require('html-webpack-plugin');

const ReBodyClosureTag = /<\/body>/;
const ReScriptTag = /'
    ].join('');
}
function createStyleSheetTag (url) {
    return '<link rel="stylesheet" href="' + url + '">';
}
function createStyleTag (content) {
    return '';
}

function toRelativeOutputDirPath (outputType, result) {
    let self = this;

    if (ReInlinePlaceholder.test(result)) {
        result = result.replace(ReInlinePlaceholder, '');
    } else if (ReUrlPlaceholder.test(result)) {
        result = result.replace(ReUrlPlaceholder, '');
    }

    let outputAssetsDir = self.options.assetsDir;
    let outputTemplateDir = path.join(outputAssetsDir.output, outputAssetsDir.template);
    let rootPath = path.normalize(path.join(outputTemplateDir, result));

    let outputDir = path.join(outputAssetsDir.output, outputAssetsDir[outputType]) + '/';
    rootPath = rootPath.replace(outputDir, '');

    return rootPath;
}
function toRelativeCssDirPath (result) {
    return toRelativeOutputDirPath.call(this, 'css', result);
}
function toRelativeJsDirPath (result) {
    return toRelativeOutputDirPath.call(this, 'js', result);
}

const REG_VIEW_HANDLEBARS_NAME_RULE = /(\.view)$/;
module.exports = function (options, webpackConfig) {
    let htmlDirCwd = path.join(webpackConfig.context, 'src');
    let htmlFiles = glob.sync('**/*.view.{handlebars,hbs}', {
        cwd: htmlDirCwd,
        nodir: true,
        matchBase: true
    });

    let htmlConfig = isObject(webpackConfig.html) ? webpackConfig.html : {};

    let result = [];

    if (htmlFiles.length) {
        htmlFiles.forEach(function (file) {
            if (!isUndefined(htmlConfig.test)) {
                let filterResult = true;
                if (isFunction(htmlConfig.test)) {
                    filterResult = htmlConfig.test(path.join(htmlDirCwd, file));
                } else if (isRegExp(htmlConfig.test)) {
                    filterResult = htmlConfig.test.test(path.join(htmlDirCwd, file));
                }

                if (filterResult === false) {
                    return;
                }
            }

            let info = path.parse(file);
            let renderDataPath = path.join(webpackConfig.context, 'src', 'mock', info.name);
            let renderData = {};
            try {
                renderData = require(renderDataPath);
            } catch (e) {
                renderData = {};
            }

            let filename = path.join(
                webpackConfig.output.path,
                webpackConfig.output.templateDir,
                (info.name.replace(REG_VIEW_HANDLEBARS_NAME_RULE, '')) + '.html'
            );

            let HtmlWebpackPluginConfig = {
                filename: filename,
                template: path.join(htmlDirCwd, file),
                inject: false,
                hash: false,

                data: renderData,
                assetsDir: {
                    output: webpackConfig.output.path,
                    template: webpackConfig.output.templateDir,
                    image: webpackConfig.output.imageDir,
                    font: webpackConfig.output.fontDir,
                    css: webpackConfig.output.cssDir,
                    svg: webpackConfig.output.svgDir,
                    js: webpackConfig.output.jsDir
                }
            };

            if (isFunction(htmlConfig.beforeInitialization)) {
                htmlConfig.beforeInitialization(HtmlWebpackPluginConfig);
            }

            let obj = new HtmlWebpackPlugin(HtmlWebpackPluginConfig);

            if (isFunction(htmlConfig.afterInitialization)) {
                obj = htmlConfig.afterInitialization(obj);
            }

            result.push(obj);
        });
    }

    return result;
};
<commit_msg>feat: template resource reference support absolute paths<commit_after>/**
 * html模板入口
 */
"use strict";

const path = require('path');
const url = require('url');
const glob = require('glob');

const HtmlWebpackPlugin = require('html-webpack-plugin');

const ReBodyClosureTag = /<\/body>/;
const ReScriptTag = /'
    ].join('');
}
function createStyleSheetTag (url) {
    return '<link rel="stylesheet" href="' + url + '">';
}
function createStyleTag (content) {
    return '';
}

function toRelativeOutputDirPath (outputType, result) {
    let self = this;

    if (ReInlinePlaceholder.test(result)) {
        result = result.replace(ReInlinePlaceholder, '');
    } else if (ReUrlPlaceholder.test(result)) {
        result = result.replace(ReUrlPlaceholder, '');
    }

    let outputAssetsDirOpts = self.options.assetsDir;
    let rootPath;
    if (/^\//.test(result)) {
        rootPath = path.join(outputAssetsDirOpts.output, result);
    } else {
        let outputTemplateDir = path.join(outputAssetsDirOpts.output, outputAssetsDirOpts.template);
        rootPath = path.normalize(path.join(outputTemplateDir, result));
    }

    let outputDir = path.join(outputAssetsDirOpts.output, outputAssetsDirOpts[outputType]) + '/';
    rootPath = rootPath.replace(outputDir, '');

    return rootPath;
}
function toRelativeCssDirPath (result) {
    return toRelativeOutputDirPath.call(this, 'css', path.normalize(result));
}
function toRelativeJsDirPath (result) {
    return toRelativeOutputDirPath.call(this, 'js', path.normalize(result));
}

const REG_VIEW_HANDLEBARS_NAME_RULE = /(\.view)$/;
module.exports = function (options, webpackConfig) {
    let htmlDirCwd = path.join(webpackConfig.context, 'src');
    let htmlFiles = glob.sync('**/*.view.{handlebars,hbs}', {
        cwd: htmlDirCwd,
        nodir: true,
        matchBase: true
    });

    let htmlConfig = isObject(webpackConfig.html) ? webpackConfig.html : {};

    let result = [];

    if (htmlFiles.length) {
        htmlFiles.forEach(function (file) {
            if (!isUndefined(htmlConfig.test)) {
                let filterResult = true;
                if (isFunction(htmlConfig.test)) {
                    filterResult = htmlConfig.test(path.join(htmlDirCwd, file));
                } else if (isRegExp(htmlConfig.test)) {
                    filterResult = htmlConfig.test.test(path.join(htmlDirCwd, file));
                }

                if (filterResult === false) {
                    return;
                }
            }

            let info = path.parse(file);
            let renderDataPath = path.join(webpackConfig.context, 'src', 'mock', info.name);
            let renderData = {};
            try {
                renderData = require(renderDataPath);
            } catch (e) {
                renderData = {};
            }

            let filename = path.join(
                webpackConfig.output.path,
                webpackConfig.output.templateDir,
                (info.name.replace(REG_VIEW_HANDLEBARS_NAME_RULE, '')) + '.html'
            );

            let HtmlWebpackPluginConfig = {
                filename: filename,
                template: path.join(htmlDirCwd, file),
                inject: false,
                hash: false,

                data: renderData,
                assetsDir: {
                    output: webpackConfig.output.path,
                    template: webpackConfig.output.templateDir,
                    image: webpackConfig.output.imageDir,
                    font: webpackConfig.output.fontDir,
                    css: webpackConfig.output.cssDir,
                    svg: webpackConfig.output.svgDir,
                    js: webpackConfig.output.jsDir
                }
            };

            if (isFunction(htmlConfig.beforeInitialization)) {
                htmlConfig.beforeInitialization(HtmlWebpackPluginConfig);
            }

            let obj = new HtmlWebpackPlugin(HtmlWebpackPluginConfig);

            if (isFunction(htmlConfig.afterInitialization)) {
                obj = htmlConfig.afterInitialization(obj);
            }

            result.push(obj);
        });
    }

    return result;
};
<|endoftext|>


<commit_before>define(function(require) {
  /*
    DEPENDENCIES
   */

  var Locale = require('utils/locale');
  var Tips = require('utils/tips');
  var CapacityInputs = require('./general/capacity-inputs');
  var WizardFields = require('utils/wizard-fields');

  /*
    TEMPLATES
   */

  var TemplateHTML = require('hbs!./general/html');

  /*
    CONSTANTS
   */

  var WIZARD_TAB_ID = require('./general/wizardTabId');
  var LOGOS = [
    {'path': "images/logos/arch.png",       'title': Locale.tr("Arch Linux")},
    {'path': "images/logos/centos.png",     'title': Locale.tr("CentOS")},
    {'path': "images/logos/debian.png",     'title': Locale.tr("Debian")},
    {'path': "images/logos/fedora.png",     'title': Locale.tr("Fedora")},
    {'path': "images/logos/linux.png",      'title': Locale.tr("Linux")},
    {'path': "images/logos/redhat.png",     'title': Locale.tr("Redhat")},
    {'path': "images/logos/ubuntu.png",     'title': Locale.tr("Ubuntu")},
    {'path': "images/logos/windowsxp.png",  'title': Locale.tr("Windows XP/2003")},
    {'path': "images/logos/windows8.png",   'title': Locale.tr("Windows 8")}
  ]

  /*
    CONSTRUCTOR
   */

  function WizardTab() {
    this.wizardTabId = WIZARD_TAB_ID;
    this.icon = 'fa-laptop';
    this.title = Locale.tr("General");
  }

  WizardTab.prototype.constructor = WizardTab;
  WizardTab.prototype.html = _html;
  WizardTab.prototype.setup = _setup;
  WizardTab.prototype.onShow = _onShow;
  WizardTab.prototype.retrieve = _retrieve;
  WizardTab.prototype.fill = _fill;

  return WizardTab;

  /*
    FUNCTION DEFINITIONS
   */

  function _html() {
    return TemplateHTML({
      'capacityInputsHTML': CapacityInputs.html(),
      'logos': LOGOS
    });
  }

  function _onShow(context, panelForm) {
    if (panelForm.action == 'create') {
      $('#template_name_form', context).show();
      $('#template_hypervisor_form', context).removeClass("left");
      $('#NAME', context).removeAttr('disabled');
      $('#NAME', context).attr("required", "");
    } else if (panelForm.action == 'update') {
      $('#template_name_form', context).hide();
      $('#template_hypervisor_form', context).addClass("left");
      $('#NAME', context).attr("disabled", "disabled");
      $('#NAME', context).removeAttr("required");
    }

    context.foundation('slider', 'reflow');
  }

  function _setup(context) {

    context.on("change", "#LOGO", function() {
      $("#template_create_logo", context).show();
      $("#template_create_logo", context).html('' +
          '' +
        '');
    });

    context.on("change", "input[name='hypervisor']", function() {
      // TODO define context (for example: this.closest('form'))
      $(".hypervisor").hide();
      $(".only_" + this.value).show();
    });

    CapacityInputs.setup(context);
  }

  function _retrieve(context) {
    var templateJSON = WizardFields.retrieve(context);

    if (templateJSON["HYPERVISOR"] == 'vcenter') {
      templateJSON["PUBLIC_CLOUD"] = {
        'TYPE': 'vcenter',
        'VM_TEMPLATE': $("#vcenter_template_uuid", context).val()
      }
    }

    if ($('#sunstone_capacity_select:checked', context).length > 0) {
      templateJSON["SUNSTONE_CAPACITY_SELECT"] = "NO"
    }

    if ($('#sunstone_network_select:checked', context).length > 0) {
      templateJSON["SUNSTONE_NETWORK_SELECT"] = "NO"
    }

    return templateJSON;
  }

  function _fill(context, templateJSON) {
    if (templateJSON["SUNSTONE_CAPACITY_SELECT"] &&
          (templateJSON["SUNSTONE_CAPACITY_SELECT"].toUpperCase() == "NO")) {
      $("#sunstone_capacity_select", context).attr("checked", "checked");
      delete templateJSON["SUNSTONE_CAPACITY_SELECT"]
    }

    if (templateJSON["SUNSTONE_NETWORK_SELECT"] &&
          (templateJSON["SUNSTONE_NETWORK_SELECT"].toUpperCase() == "NO")) {
      $("#sunstone_network_select", context).attr("checked", "checked");
      delete templateJSON["SUNSTONE_NETWORK_SELECT"]
    }

    if (templateJSON["HYPERVISOR"]) {
      $("input[name='hypervisor'][value='"+templateJSON["HYPERVISOR"]+"']", context).trigger("click")
      delete templateJSON["HYPERVISOR"];
    }

    WizardFields.fill(context, templateJSON);
  }
});
<commit_msg>feature #3748: Reflow range slider when the General tab is shown<commit_after>define(function(require) {
  /*
    DEPENDENCIES
   */

  var Locale = require('utils/locale');
  var Tips = require('utils/tips');
  var CapacityInputs = require('./general/capacity-inputs');
  var WizardFields = require('utils/wizard-fields');

  /*
    TEMPLATES
   */

  var TemplateHTML = require('hbs!./general/html');

  /*
    CONSTANTS
   */

  var WIZARD_TAB_ID = require('./general/wizardTabId');
  var LOGOS = [
    {'path': "images/logos/arch.png",       'title': Locale.tr("Arch Linux")},
    {'path': "images/logos/centos.png",     'title': Locale.tr("CentOS")},
    {'path': "images/logos/debian.png",     'title': Locale.tr("Debian")},
    {'path': "images/logos/fedora.png",     'title': Locale.tr("Fedora")},
    {'path': "images/logos/linux.png",      'title': Locale.tr("Linux")},
    {'path': "images/logos/redhat.png",     'title': Locale.tr("Redhat")},
    {'path': "images/logos/ubuntu.png",     'title': Locale.tr("Ubuntu")},
    {'path': "images/logos/windowsxp.png",  'title': Locale.tr("Windows XP/2003")},
    {'path': "images/logos/windows8.png",   'title': Locale.tr("Windows 8")}
  ]

  /*
    CONSTRUCTOR
   */

  function WizardTab() {
    this.wizardTabId = WIZARD_TAB_ID;
    this.icon = 'fa-laptop';
    this.title = Locale.tr("General");
  }

  WizardTab.prototype.constructor = WizardTab;
  WizardTab.prototype.html = _html;
  WizardTab.prototype.setup = _setup;
  WizardTab.prototype.onShow = _onShow;
  WizardTab.prototype.retrieve = _retrieve;
  WizardTab.prototype.fill = _fill;

  return WizardTab;

  /*
    FUNCTION DEFINITIONS
   */

  function _html() {
    return TemplateHTML({
      'capacityInputsHTML': CapacityInputs.html(),
      'logos': LOGOS
    });
  }

  function _onShow(context, panelForm) {
    if (panelForm.action == 'create') {
      $('#template_name_form', context).show();
      $('#template_hypervisor_form', context).removeClass("left");
      $('#NAME', context).removeAttr('disabled');
      $('#NAME', context).attr("required", "");
    } else if (panelForm.action == 'update') {
      $('#template_name_form', context).hide();
      $('#template_hypervisor_form', context).addClass("left");
      $('#NAME', context).attr("disabled", "disabled");
      $('#NAME', context).removeAttr("required");
    }

    context.foundation('slider', 'reflow');
  }

  function _setup(context) {
    $(document).on('click', "[href='#" + this.wizardTabId + "']", function(){
      context.foundation('slider', 'reflow');
    });

    context.on("change", "#LOGO", function() {
      $("#template_create_logo", context).show();
      $("#template_create_logo", context).html('' +
          '' +
        '');
    });

    context.on("change", "input[name='hypervisor']", function() {
      // TODO define context (for example: this.closest('form'))
      $(".hypervisor").hide();
      $(".only_" + this.value).show();
    });

    CapacityInputs.setup(context);
  }

  function _retrieve(context) {
    var templateJSON = WizardFields.retrieve(context);

    if (templateJSON["HYPERVISOR"] == 'vcenter') {
      templateJSON["PUBLIC_CLOUD"] = {
        'TYPE': 'vcenter',
        'VM_TEMPLATE': $("#vcenter_template_uuid", context).val()
      }
    }

    if ($('#sunstone_capacity_select:checked', context).length > 0) {
      templateJSON["SUNSTONE_CAPACITY_SELECT"] = "NO"
    }

    if ($('#sunstone_network_select:checked', context).length > 0) {
      templateJSON["SUNSTONE_NETWORK_SELECT"] = "NO"
    }

    return templateJSON;
  }

  function _fill(context, templateJSON) {
    if (templateJSON["SUNSTONE_CAPACITY_SELECT"] &&
          (templateJSON["SUNSTONE_CAPACITY_SELECT"].toUpperCase() == "NO")) {
      $("#sunstone_capacity_select", context).attr("checked", "checked");
      delete templateJSON["SUNSTONE_CAPACITY_SELECT"]
    }

    if (templateJSON["SUNSTONE_NETWORK_SELECT"] &&
          (templateJSON["SUNSTONE_NETWORK_SELECT"].toUpperCase() == "NO")) {
      $("#sunstone_network_select", context).attr("checked", "checked");
      delete templateJSON["SUNSTONE_NETWORK_SELECT"]
    }

    if (templateJSON["HYPERVISOR"]) {
      $("input[name='hypervisor'][value='"+templateJSON["HYPERVISOR"]+"']", context).trigger("click")
      delete templateJSON["HYPERVISOR"];
    }

    WizardFields.fill(context, templateJSON);
  }
});
<|endoftext|>


<commit_before>var events = require('events');
var Q = require('q');
var util = require('util');
var sockjs = require('sockjs');

var auth = require('./auth.js');
var conf = require('./config.js');
var db = require('./db.js');
var error = require('./error.js');
var Filter = require('./filter.js').Filter;
var keyedset = require('./keyedset.js');
var msgid = require('./msgid.js');

// Thin wrapper over a SockJS's connection to parse things out as JSON
// and abort on error.
function Connection(conn) {
  events.EventEmitter.call(this);

  this.conn_ = conn;
  this.conn_.on('close', function() {
    this.emit('close');
  }.bind(this));
  this.conn_.on('data', function(data) {
    try {
      var message = JSON.parse(data);
    } catch (err) {
      this.conn_.close(4000, 'Bad message format');
      return;
    }
    this.emit('message', message);
  }.bind(this));
};
util.inherits(Connection, events.EventEmitter);
Connection.prototype.sockjs = function() {
  // Bah. Too lazy to expose all the properties.
  return this.conn_;
};
Connection.prototype.close = function(code, reason) {
  this.conn_.close(code, reason);
};
Connection.prototype.send = function(message) {
  this.conn_.write(JSON.stringify(message));
};

// TODO(davidben): Version the socket API.

function ConnectionManager(server, subscriber) {
  this.sockServer_ = sockjs.createServer({
    sockjs_url: '/sockjs.min.js'
  });
  this.activeUsers_ = { };
  this.subscriber_ = subscriber;

  this.sockServer_.installHandlers(server, { prefix: '/v1/socket' });

  this.sockServer_.on('connection', this.onConnection_.bind(this));
  this.subscriber_.on('message', this.onMessage_.bind(this));

  this.subscriber_.on('subscribe', function(user, clientId, subs) {
    this.broadcast(user, clientId, {
      type: 'subscribed',
      subs: subs
    });
  }.bind(this));
  this.subscriber_.on('unsubscribe', function(user, clientId, sub) {
    this.broadcast(user, clientId, {
      type: 'unsubscribed',
      subs: [sub]
    });
  }.bind(this));

  // Debug info to monitor socket state. Every 10 minutes, print out
  // how many sockets davidben has open.
  setInterval(function() {
    var davidben = 1;
    if (!this.activeUsers_[davidben]) {
      console.log('==== davidben is not active');
    } else {
      console.log('==== davidben has %d active sockets and %d active tails',
                  this.activeUsers_[davidben].ref_,
                  this.activeUsers_[davidben].activeTails_.length);
      this.activeUsers_[davidben].sockets_.forEach(function(socket) {
        console.log('      %s:%d', socket.conn_.remoteAddress,
                    socket.conn_.remotePort);
      });
    }
  }.bind(this), 5 * 60 * 1000);
}

ConnectionManager.prototype.broadcast = function(user, clientId, msg) {
  if (!this.activeUsers_[user.id])
    return;
  this.activeUsers_[user.id].broadcast(clientId, msg);
};

ConnectionManager.prototype.onConnection_ = function(conn) {
  conn = new Connection(conn);
  conn.once('message', function(msg) {
    if (msg.type !== 'auth') {
      conn.close(4001, 'Auth message expected');
      return;
    }
    var token = msg.token;
    if (typeof token !== 'string') {
      conn.close(4002, 'Bad auth token');
      return;
    }
    conn.clientId = msg.clientId;
    auth.checkAuthToken(token).then(function(user) {
      if (!this.activeUsers_[user.id])
        this.activeUsers_[user.id] = new ActiveUser(this, user);

      this.activeUsers_[user.id].addSocket(conn);
      conn.send({type: 'ready'});
    }.bind(this), function(err) {
      if (err instanceof error.UserError) {
        conn.close(4003, err.msg);
      } else {
        conn.close(4004, 'Internal error');
      }
    }.bind(this)).done();
  }.bind(this));
};

ConnectionManager.prototype.onMessage_ = function(msg, userIds) {
  // Only compute the sealed id once.
  var sealedId = msgid.seal(msg.id);
  // Deliver the message to anyone who might care.
  userIds.forEach(function(userId) {
    if (!this.activeUsers_[userId])
      return;
    this.activeUsers_[userId].onMessage(msg, sealedId);
  }.bind(this));
};

function ActiveUser(parent, user) {
  this.parent_ = parent;
  this.user_ = user;

  this.ref_ = 0;
  this.activeTails_ = new keyedset.KeyedSet();
  this.sockets_ = new keyedset.KeyedSet();
}

ActiveUser.prototype.addSocket = function(socket) {
  var key = this.sockets_.add(socket);
  this.ref_++;
  if (this.user_.id === 1) {
    console.log('==== davidben opened a socket (num = %d = %d; %s:%d)',
                this.ref_, this.sockets_.length,
                socket.conn_.remoteAddress, socket.conn_.remotePort);
  }

  // This stuff should possibly be enclosed in YET ANOTHER class
  // rather than a closure...
  var tails = {};

  socket.on('close', function() {
    // Shut off all the tails, so we stop sending messages through
    // them.
    for (var key in tails) {
      tails[key].close();
      delete tails[key];
    }

    // Bah.
    if (--this.ref_ <= 0)
      delete this.parent_.activeUsers_[this.user_.id];
    this.sockets_.removeKey(key);
    if (this.user_.id === 1) {
      console.log('==== davidben closed a socket (num = %d = %d; %s:%d)',
                  this.ref_, this.sockets_.length,
                  socket.conn_.remoteAddress, socket.conn_.remotePort);
    }
  }.bind(this));

  socket.on('message', function(msg) {
    if (msg.type === 'ping') {
      socket.send({type: 'pong'});
    } else if (msg.type === 'new-tail') {
      var id = msg.id, start = msg.start, inclusive = msg.inclusive;
      if (typeof id !== 'number' ||
          (start != null && typeof start !== 'string')) {
        socket.close(4005, 'Bad message');
        return;
      }

      if (start != null) {
        start = msgid.unseal(start);
        if (inclusive)
          start--;
      } else {
        start = 0;
      }

      var filter = new Filter(msg);

      if (tails[id]) {
        // Uh, you shouldn't reuse ids, but okay...
        tails[id].close();
      }
      tails[id] = new Tail(this, socket, id, start, filter);
    } else if (msg.type === 'extend-tail') {
      var id = msg.id, count = msg.count;
      if (typeof id !== 'number' || typeof count !== 'number') {
        socket.close(4005, 'Bad message');
        return;
      }

      if (tails[id])
        tails[id].extend(count);
    } else if (msg.type === 'close-tail') {
      var id = msg.id;
      if (typeof id !== 'number') {
        socket.close(4005, 'Bad message');
        return;
      }

      if (tails[id]) {
        tails[id].close();
        delete tails[id];
      }
    }
  }.bind(this));
};

ActiveUser.prototype.broadcast = function(clientId, msg) {
  this.sockets_.forEach(function(socket) {
    // Don't broadcast to self.
    if (clientId != null && socket.clientId == clientId)
      return;
    socket.send(msg);
  });
};

ActiveUser.prototype.onMessage = function(msg, sealedId) {
  // Forward to each tail that is listening.
  this.activeTails_.forEach(function(tail) {
    tail.onMessage(msg, sealedId);
  });
};

function Tail(user, socket, id, lastSent, filter) {
  // Possible states:
  //
  // - FULL-TAIL : |messagesWanted_| = 0, so there's no need to
  //   request new messages. |active_| and |messageBuffer_| should
  //   both be null. When |messagesWanted_| becomes non-zero, we fire
  //   a DB query and go into DB-WAIT.
  //
  // - DB-WAIT : |active_| is not null and |messageBuffer_| is not
  //   null. Whenever we are doing a DB query, we are also listening
  //   for live messages to do the handoff properly. They end up in
  //   |messageBuffer_|. When the DB query returns, we either go to
  //   FULL-TAIL, another instance of DB-WAIT (because the user raced
  //   with us in calling extend or we hit |db.getMessage|'s result
  //   size limit), or LIVE-STREAM
  //
  // - LIVE-STREAM : |active_| is not null and |messageBuffer_| is
  //   null. This means that our most recent DB query was done but
  //   there was still room in |messagesWanted_|. In that case, we
  //   switch to just forwarding messages straight from the
  //   subscriber. We stay in this state until |messagesWanted_| is 0
  //   and go into FULL-TAIL.

  this.user_ = user;
  this.socket_ = socket;
  this.id_ = id;
  this.filter_ = filter;

  this.active_ = null;
  this.messageBuffer_ = null;

  this.lastSent_ = lastSent;
  this.messagesSent_ = 0;
  this.messagesWanted_ = 0;
}

Tail.prototype.close = function() {
  this.socket_ = null;
  this.deactivate_();
};

Tail.prototype.extend = function(count) {
  this.messagesWanted_ = Math.max(count - this.messagesSent_,
                                  this.messagesWanted_);
  this.fireQuery_();
};

Tail.prototype.activate_ = function() {
  if (this.active_ == null) {
    this.active_ = this.user_.activeTails_.add(this);
  }
};

Tail.prototype.deactivate_ = function() {
  if (this.active_) {
    this.user_.activeTails_.removeKey(this.active_);
    this.active_ = null;
  }
};

Tail.prototype.fireQuery_ = function() {
  if (this.socket_ == null)
    return;

  // We're either in LIVE-STREAM or already in DB-WAIT. Do nothing.
  if (this.active_ != null)
    return;

  // We're in FULL-TAIL and should stay that way.
  if (this.messagesWanted_ == 0)
    return;

  // Activate live stream in buffer-messages mode.
  this.activate_();
  this.messageBuffer_ = [];
  // Make the DB query.
  db.getMessages(this.user_.user_, this.lastSent_, this.filter_, {
    limit: this.messagesWanted_,
    reverse: false,
    inclusive: false
  }).then(function(result) {
    if (this.socket_ == null)
      return;

    // First, send the result along.
    if (result.messages.length) {
      var lastId = result.messages[result.messages.length - 1].id;
      result.messages.forEach(function(msg) {
        msg.id = msgid.seal(msg.id);
      });
      this.emitMessages_(result.messages, result.isDone);
      this.lastSent_ = lastId;
    } else {
      this.emitMessages_([], result.isDone);
    }

    // This was (at query time) the end of the database. Now we
    // transition to LIVE-STREAM mode.
    if (result.isDone && this.messagesWanted_) {
      var messageBuffer = this.messageBuffer_;
      this.messageBuffer_ = null;

      // But first, to make the hand-off atomic, we send what messages
      // in the buffer weren't seen yet.
      var start;
      for (start = 0; start < messageBuffer.length; start++) {
        if (messageBuffer[start][0].id > this.lastSent_)
          break;
      }
      messageBuffer = messageBuffer.slice(start);
      if (messageBuffer.length > 0) {
        var sealedMsgs = messageBuffer.map(function(entry) {
          var msg = entry[0], sealedId = entry[1];
          var sealedMsg = { };
          for (var key in msg) {
            sealedMsg[key] = msg[key];
          }
          sealedMsg.id = sealedId;
          return sealedMsg;
        });
        this.emitMessages_(sealedMsgs, true);
        this.lastSent_ = messageBuffer[messageBuffer.length - 1][0].id;
      }
    } else {
      // Otherwise... we deactivate everything and check if we need to
      // fire a query again.
      this.messageBuffer_ = null;
      this.deactivate_();
      this.fireQuery_();
    }
  }.bind(this)).done();
  // TODO(davidben): Error handling!
};

Tail.prototype.onMessage = function(msg, sealedId) {
  if (!this.socket_)
    return;

  if (!this.filter_.matchesMessage(msg))
    return;

  if (this.messageBuffer_) {
    this.messageBuffer_.push([msg, sealedId]);
    return;
  }
  // We're active and not in message buffering mode. Forward them
  // through the socket. isDone is true since the tail is caught up.
  var sealedMsg = { };
  for (var key in msg) {
    sealedMsg[key] = msg[key];
  }
  sealedMsg.id = sealedId;

  this.emitMessages_([sealedMsg], true);
  this.lastSent_ = msg.id;
  // Transition out of LIVE-STREAM mode if needbe.
  if (this.messagesWanted_ <= 0) {
    this.deactivate_();
  }
};

Tail.prototype.emitMessages_ = function(msgs, isDone) {
  this.socket_.send({
    type: 'messages',
    id: this.id_,
    messages: msgs,
    isDone: isDone
  });
  this.messagesSent_ += msgs.length;
  this.messagesWanted_ -= msgs.length;
};

exports.listen = function(server, messageQueue) {
  return new ConnectionManager(server, messageQueue);
};<commit_msg>Saw something funny<commit_after>var events = require('events');
var Q = require('q');
var util = require('util');
var sockjs = require('sockjs');

var auth = require('./auth.js');
var conf = require('./config.js');
var db = require('./db.js');
var error = require('./error.js');
var Filter = require('./filter.js').Filter;
var keyedset = require('./keyedset.js');
var msgid = require('./msgid.js');

// Thin wrapper over a SockJS's connection to parse things out as JSON
// and abort on error.
function Connection(conn) {
  events.EventEmitter.call(this);

  this.conn_ = conn;
  this.conn_.on('close', function() {
    this.emit('close');
  }.bind(this));
  this.conn_.on('data', function(data) {
    try {
      var message = JSON.parse(data);
    } catch (err) {
      this.conn_.close(4000, 'Bad message format');
      return;
    }
    this.emit('message', message);
  }.bind(this));
};
util.inherits(Connection, events.EventEmitter);
Connection.prototype.sockjs = function() {
  // Bah. Too lazy to expose all the properties.
  return this.conn_;
};
Connection.prototype.close = function(code, reason) {
  this.conn_.close(code, reason);
};
Connection.prototype.send = function(message) {
  this.conn_.write(JSON.stringify(message));
};

// TODO(davidben): Version the socket API.

function ConnectionManager(server, subscriber) {
  this.sockServer_ = sockjs.createServer({
    sockjs_url: '/sockjs.min.js'
  });
  this.activeUsers_ = { };
  this.subscriber_ = subscriber;

  this.sockServer_.installHandlers(server, { prefix: '/v1/socket' });

  this.sockServer_.on('connection', this.onConnection_.bind(this));
  this.subscriber_.on('message', this.onMessage_.bind(this));

  this.subscriber_.on('subscribe', function(user, clientId, subs) {
    this.broadcast(user, clientId, {
      type: 'subscribed',
      subs: subs
    });
  }.bind(this));
  this.subscriber_.on('unsubscribe', function(user, clientId, sub) {
    this.broadcast(user, clientId, {
      type: 'unsubscribed',
      subs: [sub]
    });
  }.bind(this));

  // Debug info to monitor socket state. Every 10 minutes, print out
  // how many sockets davidben has open.
  setInterval(function() {
    var davidben = 1;
    if (!this.activeUsers_[davidben]) {
      console.log('==== davidben is not active');
    } else {
      console.log('==== davidben has (%d = %d) active sockets and %d active tails',
                  this.activeUsers_[davidben].ref_,
                  this.activeUsers_[davidben].sockets_.length,
                  this.activeUsers_[davidben].activeTails_.length);
      this.activeUsers_[davidben].sockets_.forEach(function(socket) {
        console.log('      %s:%d', socket.conn_.remoteAddress,
                    socket.conn_.remotePort);
      });
    }
  }.bind(this), 5 * 60 * 1000);
}

ConnectionManager.prototype.broadcast = function(user, clientId, msg) {
  if (!this.activeUsers_[user.id])
    return;
  this.activeUsers_[user.id].broadcast(clientId, msg);
};

ConnectionManager.prototype.onConnection_ = function(conn) {
  conn = new Connection(conn);
  conn.once('message', function(msg) {
    if (msg.type !== 'auth') {
      conn.close(4001, 'Auth message expected');
      return;
    }
    var token = msg.token;
    if (typeof token !== 'string') {
      conn.close(4002, 'Bad auth token');
      return;
    }
    conn.clientId = msg.clientId;
    auth.checkAuthToken(token).then(function(user) {
      if (!this.activeUsers_[user.id])
        this.activeUsers_[user.id] = new ActiveUser(this, user);

      this.activeUsers_[user.id].addSocket(conn);
      conn.send({type: 'ready'});
    }.bind(this), function(err) {
      if (err instanceof error.UserError) {
        conn.close(4003, err.msg);
      } else {
        conn.close(4004, 'Internal error');
      }
    }.bind(this)).done();
  }.bind(this));
};

ConnectionManager.prototype.onMessage_ = function(msg, userIds) {
  // Only compute the sealed id once.
  var sealedId = msgid.seal(msg.id);
  // Deliver the message to anyone who might care.
  userIds.forEach(function(userId) {
    if (!this.activeUsers_[userId])
      return;
    this.activeUsers_[userId].onMessage(msg, sealedId);
  }.bind(this));
};

function ActiveUser(parent, user) {
  this.parent_ = parent;
  this.user_ = user;

  this.ref_ = 0;
  this.activeTails_ = new keyedset.KeyedSet();
  this.sockets_ = new keyedset.KeyedSet();
}

ActiveUser.prototype.addSocket = function(socket) {
  var key = this.sockets_.add(socket);
  this.ref_++;
  if (this.user_.id === 1) {
    console.log('==== davidben opened a socket (num = %d = %d; %s:%d)',
                this.ref_, this.sockets_.length,
                socket.conn_.remoteAddress, socket.conn_.remotePort);
    this.sockets_.forEach(function(socket) {
      console.log('      %s:%d', socket.conn_.remoteAddress,
                  socket.conn_.remotePort);
    });
  }

  // This stuff should possibly be enclosed in YET ANOTHER class
  // rather than a closure...
  var tails = {};

  socket.on('close', function() {
    // Shut off all the tails, so we stop sending messages through
    // them.
    for (var key in tails) {
      tails[key].close();
      delete tails[key];
    }

    // Bah.
    if (--this.ref_ <= 0)
      delete this.parent_.activeUsers_[this.user_.id];
    this.sockets_.removeKey(key);
    if (this.user_.id === 1) {
      console.log('==== davidben closed a socket (num = %d = %d; %s:%d)',
                  this.ref_, this.sockets_.length,
                  socket.conn_.remoteAddress, socket.conn_.remotePort);
      this.sockets_.forEach(function(socket) {
        console.log('      %s:%d', socket.conn_.remoteAddress,
                    socket.conn_.remotePort);
      });
    }
  }.bind(this));

  socket.on('message', function(msg) {
    if (msg.type === 'ping') {
      socket.send({type: 'pong'});
    } else if (msg.type === 'new-tail') {
      var id = msg.id, start = msg.start, inclusive = msg.inclusive;
      if (typeof id !== 'number' ||
          (start != null && typeof start !== 'string')) {
        socket.close(4005, 'Bad message');
        return;
      }

      if (start != null) {
        start = msgid.unseal(start);
        if (inclusive)
          start--;
      } else {
        start = 0;
      }

      var filter = new Filter(msg);

      if (tails[id]) {
        // Uh, you shouldn't reuse ids, but okay...
        tails[id].close();
      }
      tails[id] = new Tail(this, socket, id, start, filter);
    } else if (msg.type === 'extend-tail') {
      var id = msg.id, count = msg.count;
      if (typeof id !== 'number' || typeof count !== 'number') {
        socket.close(4005, 'Bad message');
        return;
      }

      if (tails[id])
        tails[id].extend(count);
    } else if (msg.type === 'close-tail') {
      var id = msg.id;
      if (typeof id !== 'number') {
        socket.close(4005, 'Bad message');
        return;
      }

      if (tails[id]) {
        tails[id].close();
        delete tails[id];
      }
    }
  }.bind(this));
};

ActiveUser.prototype.broadcast = function(clientId, msg) {
  this.sockets_.forEach(function(socket) {
    // Don't broadcast to self.
    if (clientId != null && socket.clientId == clientId)
      return;
    socket.send(msg);
  });
};

ActiveUser.prototype.onMessage = function(msg, sealedId) {
  // Forward to each tail that is listening.
  this.activeTails_.forEach(function(tail) {
    tail.onMessage(msg, sealedId);
  });
};

function Tail(user, socket, id, lastSent, filter) {
  // Possible states:
  //
  // - FULL-TAIL : |messagesWanted_| = 0, so there's no need to
  //   request new messages. |active_| and |messageBuffer_| should
  //   both be null. When |messagesWanted_| becomes non-zero, we fire
  //   a DB query and go into DB-WAIT.
  //
  // - DB-WAIT : |active_| is not null and |messageBuffer_| is not
  //   null. Whenever we are doing a DB query, we are also listening
  //   for live messages to do the handoff properly. They end up in
  //   |messageBuffer_|. When the DB query returns, we either go to
  //   FULL-TAIL, another instance of DB-WAIT (because the user raced
  //   with us in calling extend or we hit |db.getMessage|'s result
  //   size limit), or LIVE-STREAM
  //
  // - LIVE-STREAM : |active_| is not null and |messageBuffer_| is
  //   null. This means that our most recent DB query was done but
  //   there was still room in |messagesWanted_|. In that case, we
  //   switch to just forwarding messages straight from the
  //   subscriber. We stay in this state until |messagesWanted_| is 0
  //   and go into FULL-TAIL.

  this.user_ = user;
  this.socket_ = socket;
  this.id_ = id;
  this.filter_ = filter;

  this.active_ = null;
  this.messageBuffer_ = null;

  this.lastSent_ = lastSent;
  this.messagesSent_ = 0;
  this.messagesWanted_ = 0;
}

Tail.prototype.close = function() {
  this.socket_ = null;
  this.deactivate_();
};

Tail.prototype.extend = function(count) {
  this.messagesWanted_ = Math.max(count - this.messagesSent_,
                                  this.messagesWanted_);
  this.fireQuery_();
};

Tail.prototype.activate_ = function() {
  if (this.active_ == null) {
    this.active_ = this.user_.activeTails_.add(this);
  }
};

Tail.prototype.deactivate_ = function() {
  if (this.active_) {
    this.user_.activeTails_.removeKey(this.active_);
    this.active_ = null;
  }
};

Tail.prototype.fireQuery_ = function() {
  if (this.socket_ == null)
    return;

  // We're either in LIVE-STREAM or already in DB-WAIT. Do nothing.
  if (this.active_ != null)
    return;

  // We're in FULL-TAIL and should stay that way.
  if (this.messagesWanted_ == 0)
    return;

  // Activate live stream in buffer-messages mode.
  this.activate_();
  this.messageBuffer_ = [];
  // Make the DB query.
  db.getMessages(this.user_.user_, this.lastSent_, this.filter_, {
    limit: this.messagesWanted_,
    reverse: false,
    inclusive: false
  }).then(function(result) {
    if (this.socket_ == null)
      return;

    // First, send the result along.
    if (result.messages.length) {
      var lastId = result.messages[result.messages.length - 1].id;
      result.messages.forEach(function(msg) {
        msg.id = msgid.seal(msg.id);
      });
      this.emitMessages_(result.messages, result.isDone);
      this.lastSent_ = lastId;
    } else {
      this.emitMessages_([], result.isDone);
    }

    // This was (at query time) the end of the database. Now we
    // transition to LIVE-STREAM mode.
    if (result.isDone && this.messagesWanted_) {
      var messageBuffer = this.messageBuffer_;
      this.messageBuffer_ = null;

      // But first, to make the hand-off atomic, we send what messages
      // in the buffer weren't seen yet.
      var start;
      for (start = 0; start < messageBuffer.length; start++) {
        if (messageBuffer[start][0].id > this.lastSent_)
          break;
      }
      messageBuffer = messageBuffer.slice(start);
      if (messageBuffer.length > 0) {
        var sealedMsgs = messageBuffer.map(function(entry) {
          var msg = entry[0], sealedId = entry[1];
          var sealedMsg = { };
          for (var key in msg) {
            sealedMsg[key] = msg[key];
          }
          sealedMsg.id = sealedId;
          return sealedMsg;
        });
        this.emitMessages_(sealedMsgs, true);
        this.lastSent_ = messageBuffer[messageBuffer.length - 1][0].id;
      }
    } else {
      // Otherwise... we deactivate everything and check if we need to
      // fire a query again.
      this.messageBuffer_ = null;
      this.deactivate_();
      this.fireQuery_();
    }
  }.bind(this)).done();
  // TODO(davidben): Error handling!
};

Tail.prototype.onMessage = function(msg, sealedId) {
  if (!this.socket_)
    return;

  if (!this.filter_.matchesMessage(msg))
    return;

  if (this.messageBuffer_) {
    this.messageBuffer_.push([msg, sealedId]);
    return;
  }
  // We're active and not in message buffering mode. Forward them
  // through the socket. isDone is true since the tail is caught up.
  var sealedMsg = { };
  for (var key in msg) {
    sealedMsg[key] = msg[key];
  }
  sealedMsg.id = sealedId;

  this.emitMessages_([sealedMsg], true);
  this.lastSent_ = msg.id;
  // Transition out of LIVE-STREAM mode if needbe.
  if (this.messagesWanted_ <= 0) {
    this.deactivate_();
  }
};

Tail.prototype.emitMessages_ = function(msgs, isDone) {
  this.socket_.send({
    type: 'messages',
    id: this.id_,
    messages: msgs,
    isDone: isDone
  });
  this.messagesSent_ += msgs.length;
  this.messagesWanted_ -= msgs.length;
};

exports.listen = function(server, messageQueue) {
  return new ConnectionManager(server, messageQueue);
};<|endoftext|>


<commit_before>/*
    Copyright Sergio García <s3rgio.gr gmail com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

'use strict';

// Private stuff

var SteroidsSocket = require('sip-fake-stack').SteroidsSocket,
    logger = require('../utils/logger'),

    HELP = {
        description: 'Really stupid app layer fuzzer (support:' +
                     'UDP, TCP, TLS, [secure] websockects)',
        options: {
            target: {
                type: 'ip',
                description: 'Host to attack',
                defaultValue: '127.0.0.1'
            },
            port: {
                type: 'port',
                description: 'Port to attack on chosen IPs',
                defaultValue: 1337
            },
            transport: {
                type: 'transports',
                description: 'Underlying protocol',
                defaultValue: 'TCP'
            },
            wsPath: {
                type: 'allValid',
                description: 'Websockets path (only when websockets)',
                defaultValue: 'ws'
            },
            wsProto: {
                type: 'allValid',
                description: 'Websockets protocol (only when websockets)',
                defaultValue: 'sip'
            },
            payload: {
                type: 'allValid',
                description: 'Stuff to send',
                defaultValue: 'A'
            },
            timeout: {
                type: 'positiveInt',
                description: 'Time to wait for a response, in ms.',
                defaultValue: 5000
            }
        }
    },
    fuzzString = '',
    payload, lastSent, megaSocket;

// Simply takes the current string and add another payload string
function mutate(oldPayload) {
    return oldPayload + payload;
}

// Muting and sending the payload,
// we're doing it over the last mutated value
function send() {
    fuzzString = mutate(fuzzString);
    megaSocket.send(fuzzString);

    // The message which is being sent
    lastSent = fuzzString;
    // Omit printint to increase performance
    logger.infoHigh('Packet sent:');
    logger.highlight(fuzzString);
}

// Public stuff

module.exports.help = HELP;

module.exports.run = function (options, callback) {
    var socketCfg = {
            target: options.target,
            port: options.port,
            transport: options.transport,
            timeout: options.timeout
        },
        answering = false;

    payload = options.payload;

    megaSocket = new SteroidsSocket(socketCfg);
    // Sending initial request
    send();

    // We reuse the same socket for now
    megaSocket.on('error', function (err) {
        if (!answering) {
            callback(err);
        } else {
            callback(null, {
                lastSent: lastSent
            });
            logger.info('Boom!, last packet sent:');
            logger.highlight(lastSent);
        }
    });

    //megaSocket.on('message', function (msg) {
    megaSocket.on('message', function () {
        answering = true;

        logger.info('Response received');

        // Server is still answering, so keep muting
        send();
    });
};
<commit_msg>Style changed<commit_after>// Copyright Jesus Perez <jesusprubio gmail com>
//           Sergio Garcia <s3rgio.gr gmail com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

'use strict';

const SteroidsSocket = require('sip-fake-stack').SteroidsSocket;
const logger = require('../utils/logger');

module.exports.help = {
  description: 'Really stupid app layer fuzzer (support:' +
               'UDP, TCP, TLS, [secure] websockects)',
  options: {
    target: {
      type: 'ip',
      description: 'Host to attack',
      defaultValue: '127.0.0.1',
    },
    port: {
      type: 'port',
      description: 'Port to attack on chosen IPs',
      defaultValue: 1337,
    },
    transport: {
      type: 'transports',
      description: 'Underlying protocol',
      defaultValue: 'TCP',
    },
    wsPath: {
      type: 'allValid',
      description: 'Websockets path (only when websockets)',
      defaultValue: 'ws',
    },
    wsProto: {
      type: 'allValid',
      description: 'Websockets protocol (only when websockets)',
      defaultValue: 'sip',
    },
    payload: {
      type: 'allValid',
      description: 'Stuff to send',
      defaultValue: 'A',
    },
    timeout: {
      type: 'positiveInt',
      description: 'Time to wait for a response, in ms.',
      defaultValue: 5000,
    },
  },
};
let fuzzString = '';
let payload;
let lastSent;
let megaSocket;

// Simply takes the current string and add another payload string
function mutate(oldPayload) {
  return oldPayload + payload;
}

// Muting and sending the payload,
// we're doing it over the last mutated value
function send() {
  fuzzString = mutate(fuzzString);
  megaSocket.send(fuzzString);

  // The message which is being sent
  lastSent = fuzzString;
  // Omit printint to increase performance
  logger.infoHigh('Packet sent:');
  logger.highlight(fuzzString);
}

module.exports.run = (options, callback) => {
  const socketCfg = {
    target: options.target,
    port: options.port,
    transport: options.transport,
    timeout: options.timeout,
  };
  let answering = false;

  payload = options.payload;

  megaSocket = new SteroidsSocket(socketCfg);
  // Sending initial request
  send();

  // We reuse the same socket for now
  megaSocket.on('error', err => {
    if (!answering) {
      callback(err);
    } else {
      callback(null, {
        lastSent,
      });
      logger.info('Boom!, last packet sent:');
      logger.highlight(lastSent);
    }
  });

  // megaSocket.on('message', function (msg) {
  megaSocket.on('message', () => {
    answering = true;

    logger.info('Response received');

    // Server is still answering, so keep muting
    send();
  });
};
<|endoftext|>


<commit_before>import React from 'react'
import { storiesOf, action } from '@storybook/react'
import { Button, ButtonToolbar, ButtonGroup,
  DropdownButton, MenuItem } from 'react-bootstrap'

storiesOf('Button', module)
    .add('default', () => (
        <Button onClick={action('onClick')}>
          Unstyled button
        </Button>
    ))
    .add('primary', () => (
        <Button bsStyle='primary' onClick={action('onClick')}>
          Primary button
        </Button>
    ))
    .add('info', () => (
        <Button bsStyle='info' onClick={action('onClick')}>
          Info button
        </Button>
    ))
    .add('warning', () => (
        <Button bsStyle='warning' onClick={action('onClick')}>
          Warning button
        </Button>
    ))
    .add('danger', () => (
        <Button bsStyle='danger' onClick={action('onClick')}>
          Danger button
        </Button>
    ))
    .add('success', () => (
        <Button bsStyle='success' onClick={action('onClick')}>
          Success button
        </Button>
    ))
    .add('sizes', () => (
    <ButtonToolbar>
      <Button bsStyle='primary' bsSize='large' onClick={action('onClick')}>
        Large button</Button>
      <Button bsSize='large' onClick={action('onClick')}>Large button</Button>
      <Button bsStyle='primary' onClick={action('onClick')}>
        Default button</Button>
      <Button onClick={action('onClick')}>Default button</Button>
      <Button bsStyle='primary' bsSize='small' onClick={action('onClick')}>
        Small button</Button>
      <Button bsSize='small' onClick={action('onClick')}>Small button</Button>
      <Button bsStyle='primary' bsSize='xsmall' onClick={action('onClick')}>
        Extra small button</Button>
      <Button bsSize='xsmall' onClick={action('onClick')}>Extra small button</Button>
    </ButtonToolbar>
  ))
    .add('group', () => (
        <ButtonGroup>
          <Button onClick={action('onClick')}>Left</Button>
          <Button onClick={action('onClick')}>Middle</Button>
          <Button onClick={action('onClick')}>Right</Button>
        </ButtonGroup>
   ))
    .add('toolbar', () => (
        <ButtonToolbar>
          <ButtonGroup>
            <Button onClick={action('onClick')}>1</Button>
            <Button onClick={action('onClick')}>2</Button>
            <Button onClick={action('onClick')}>3</Button>
            <Button onClick={action('onClick')}>4</Button>
          </ButtonGroup>
          <ButtonGroup>
            <Button onClick={action('onClick')}>5</Button>
            <Button onClick={action('onClick')}>6</Button>
            <Button onClick={action('onClick')}>7</Button>
          </ButtonGroup>
          <ButtonGroup>
            <Button onClick={action('onClick')}>8</Button>
          </ButtonGroup>
        </ButtonToolbar>
    ))
    .add('nested', () => (
        <ButtonGroup>
          <Button onClick={action('onClick')}>1</Button>
          <Button onClick={action('onClick')}>2</Button>
          <DropdownButton onClick={action('onClick')}
            title='Dropdown' id='bg-nested-dropdown'>
            <MenuItem eventKey='1'>Dropdown link</MenuItem>
            <MenuItem eventKey='2'>Dropdown link</MenuItem>
          </DropdownButton>
        </ButtonGroup>
    ))
<commit_msg>(ZNTA-2308) added details to storybook for Button, ButtonGroup, ButtonToolbar etc<commit_after>import React from 'react'
import { storiesOf, action } from '@storybook/react'
import { Button, ButtonToolbar, ButtonGroup,
  DropdownButton, MenuItem, Table } from 'react-bootstrap'

storiesOf('Button', module)
    .add('default', () => (

Buttons

        <Button onClick={action('onClick')}>
          Unstyled button
        </Button>

Button spacing

Because React doesn't output newlines between elements, buttons on the same line are displayed flush against each other. To preserve the spacing between multiple inline buttons, wrap your button group in ButtonToolbar.

Props

            Name
            Type
            Default
            Description

            active
            boolean
            'false'

            block
            boolean
            'false'

            bsClass
            string
            'btn'
            Base CSS class and prefix for the component. Generally one should only change bsClass to provide new, non-Bootstrap, CSS styles for a component.

            bsSize
            one of: "lg", "large", "sm", "small", "xs", "xsmall"

            Component size variations

            bsStyle
            one of: "success", "warning", "danger", "info", "default", "primary", "link"

            'default'
            Component visual or contextual style variants.

            componentClass
            elementType

            You can use a custom element type for this component.

            disabled
            boolean
            'false'

            href
            string

            type
            one of: 'button', 'reset', 'submit'
            'button'
            Defines HTML button type attribute

    ))
    .add('primary', () => (

Primary button

        <Button bsStyle='primary' onClick={action('onClick')}>
          Primary button
        </Button>

bsStyle: "primary"

    ))
    .add('info', () => (

Info button

        <Button bsStyle='info' onClick={action('onClick')}>
          Info button
        </Button>

bsStyle: "info"

    ))
    .add('warning', () => (

Warning button

        <Button bsStyle='warning' onClick={action('onClick')}>
          Warning button
        </Button>

bsStyle: "warning"

    ))
    .add('danger', () => (

Danger button

        <Button bsStyle='danger' onClick={action('onClick')}>
          Danger button
        </Button>

bsStyle: "danger"

    ))
    .add('success', () => (

Success button

        <Button bsStyle='success' onClick={action('onClick')}>
          Success button
        </Button>

bsStyle: "success"

    ))
    .add('block', () => (

Button block

        <Button block onClick={action('onClick')}>
          Button block
        </Button>

block: true

    ))
    .add('sizes', () => (

Button sizes

          <ButtonGroup>
      <Button bsStyle='primary' bsSize='large' onClick={action('onClick')}>
        Large button</Button>
            <Button bsSize='large' onClick={action('onClick')}>Large button</Button>
          </ButtonGroup>
      <ButtonGroup>
          <Button bsStyle='primary' onClick={action('onClick')}>
        Default button</Button>
      <Button onClick={action('onClick')}>Default button</Button>
      </ButtonGroup>
          <ButtonGroup>
      <Button bsStyle='primary' bsSize='small' onClick={action('onClick')}>
        Small button</Button>
      <Button bsSize='small' onClick={action('onClick')}>Small button</Button>
          </ButtonGroup>
          <ButtonGroup>
      <Button bsStyle='primary' bsSize='xsmall' onClick={action('onClick')}>
        Extra small button</Button>
      <Button bsSize='xsmall' onClick={action('onClick')}>Extra small button</Button>
    </ButtonGroup>

bsSize: "lg", "large", "sm", "small", "xs", "xsmall"

  ))
    .add('group', () => (

ButtonGroup

Guideline: group a series of buttons together on a single line with the button group.

<ButtonGroup>
          <Button onClick={action('onClick')}>Left</Button>
          <Button onClick={action('onClick')}>Middle</Button>
          <Button onClick={action('onClick')}>Right</Button>
        </ButtonGroup>

Props

            Name
            Type
            Default
            Description

            block
            boolean
            'false'

            bsClass
            string
            'btn-group'
            Base CSS class and prefix for the component. Generally one should only change bsClass to provide new, non-Bootstrap, CSS styles for a component.

            justified
            boolean
            'false'

            vertical
            boolean
            'false'

   ))
    .add('toolbar', () => (

Button toolbar

Guideline: Combine sets of ButtonGroups into a ButtonToolbar for more complex components.

        <ButtonToolbar>
          <ButtonGroup>
            <Button onClick={action('onClick')}>1</Button>
            <Button onClick={action('onClick')}>2</Button>
            <Button onClick={action('onClick')}>3</Button>
            <Button onClick={action('onClick')}>4</Button>
          </ButtonGroup>
          <ButtonGroup>
            <Button onClick={action('onClick')}>5</Button>
            <Button onClick={action('onClick')}>6</Button>
            <Button onClick={action('onClick')}>7</Button>
          </ButtonGroup>
          <ButtonGroup>
            <Button onClick={action('onClick')}>8</Button>
          </ButtonGroup>
        </ButtonToolbar>

Props

            Name
            Type
            Default
            Description

            bsClass
            string
            'btn-toolbar'
            Base CSS class and prefix for the component. Generally one should only change bsClass to provide new, non-Bootstrap, CSS styles for a component.

    ))
    .add('nested', () => (

Nested ButtonGroup

Guideline: for adding dropdowns to button groups

        <ButtonGroup>
          <Button onClick={action('onClick')}>1</Button>
          <Button onClick={action('onClick')}>2</Button>
          <DropdownButton onClick={action('onClick')}
            title='Dropdown' id='bg-nested-dropdown'>
            <MenuItem eventKey='1'>Dropdown link</MenuItem>
            <MenuItem eventKey='2'>Dropdown link</MenuItem>
          </DropdownButton>
        </ButtonGroup>

    ))
<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');

/**@ngInject*/
function ProjectController($interval, project, ProjectUsersResource, OrderItemsResource, alerts, products, FlashesService) {

  this.intervalDelay = 30000;
  this.$interval = $interval;

  this.project = project;
  this.reason = null; // The reason this project has been rejected.

  // Filter the alerts to only show them for this project.
  this.alerts = _.filter(alerts, function(alert) {
    return alert.project_id == project.id;
  });
  this.products = products;

  this.ProjectUsersResource = ProjectUsersResource;
  this.OrderItemsResource = OrderItemsResource;

  this.FlashesService = FlashesService;

  /**
   * On creation/transition to scope, start refresh interval if
   * we need to to reload unfinished service data.
   *
   * TODO : Revisit the data refresh
   */
  //$scope.$on('$stateChangeSuccess', _.bind(function () {
  //  this.stopRefreshInterval();
  //}, this));
  //
  //$scope.$on('$stateChangeStart', _.bind(function() {
  //  this.startRefreshInterval();
  //}, this));
}

ProjectController.resolve = {
  /**@ngInject*/
  project: function(ProjectsResource, $stateParams) {
    return ProjectsResource.get({
      id: $stateParams.projectId,
      'includes[]': ['approvals', 'approvers', 'services', 'project_answers', 'staff']
    }).$promise;
  },
  /**@ngInject*/
  products: function(ProductsResource) {
    return ProductsResource.query().$promise;
  }
};

ProjectController.prototype = {

  /**
   * Setup refresh interval if not all services are complete.
   * Will automatically stop polling after all services are complete.
   * Polls Every 30 Seconds.
   */
  startRefreshInterval: function() {
    var self = this;
    if (!self._areAllServicesComplete()) {
      self.interval = this.$interval(function() {
        self.project.$get(function() {
          if (self._areAllServicesComplete()) {
            self.stopRefreshInterval();
          }
        });
      }, this.intervalDelay);
    }
  },

  /**
   * Clear/Stop the polling.
   */
  stopRefreshInterval: function() {
    this.$interval.cancel(this.interval);
    this.interval = undefined;
  },

  /**
   * Project Approval actions
   */
  approve: function() {
    this.project.$approve();
  },
  reject: function() {
    this.project.$reject({reason: this.reason});
  },

  removeUserFromProject: function(index) {
    var self = this;

    this.ProjectUsersResource.delete({id: this.project.id, staff_id: this.project.users[index].id}).$promise.then(
      _.bind(function(data) {
        this.project.users.splice(index, 1);
      }, this),
      function(error) {
        self.FlashesService.add({
          timeout: true,
          type: 'error',
          message: "There was an error removing this user. Please try again later"
        });
      }
    );
  },

  removeServiceFromProject: function(serviceIndex) {
    var self = this,
      service = this.project.services[serviceIndex];

    this.OrderItemsResource.delete({id: service.id, order_id: service.order_id}).$promise.then(
      _.bind(function() {
        // Remove it from the existing array.
        this.project.services.splice(serviceIndex, 1);
      }, this),
      function(error) {
        self.FlashesService.add({
          timeout: true,
          type: 'error',
          message: "There was an error removing this service."
        });
      }
    );
  },

  /**
   * Links the service with the product.
   * @param serviceObject
   * @returns {*}
   */
  getServiceWithProduct: function(serviceObject) {
    var productId = serviceObject.product_id;

    var product = _.find(this.products, function(obj) {
      return obj.id == productId;
    });

    // Hook on the product details we need to use the product box.
    serviceObject.img = product.img;
    serviceObject.name = product.name;
    serviceObject.description = product.description;

    return serviceObject;
  },

  getLeftData: function() {
    var projectBudget = this.project.budget || 0;
    var projectSpent = this.project.spent || 0;
    var monthlySpend = this.project.monthly_spend || 0;

    var leftPercent = 1.0;
    var leftMonths = '>12';
    var leftColor = 'green';

    if (projectBudget > 0 && monthlySpend !== 0 && this._isANumber(monthlySpend)) {

      leftMonths = Math.round((projectBudget - projectSpent) / monthlySpend);
      leftPercent = leftMonths / 12;

      if (leftMonths > 12) {
        leftPercent = 1.0;
      } else if (leftMonths <= 5 && leftMonths > 3) {
        leftColor = '#CCDB23';
      } else if (leftMonths <= 3 && leftMonths > 0) {
        leftColor = 'red';
      } else if (leftMonths <= 0) {
        leftMonths = 0;
        leftPercent = 0.0;
        leftColor = 'red';
      }

      if (leftMonths > 99) {
        leftMonths = '>99';
      }

    }

    return {
      'leftPercent': leftPercent,
      'leftColor': leftColor,
      'leftMonths': leftMonths,
      'monthlySpend': monthlySpend
    };
  },

  getBudgetData: function() {
    var projectBudget = this.project.budget || 0;
    var projectSpent = this.project.spent || 0;
    var usedPercent = 0.0;
    var usedColor = 'green';

    if (projectBudget > 0) {

      usedPercent = projectSpent / projectBudget;
      if (usedPercent > 1.0) {
        usedPercent = 1.0;
      }

      // Set the colors for usedAmount
      if (usedPercent <= 0.85 && usedPercent > 0.65) {
        usedColor = '#CCDB23';
      } else if (usedPercent >= 0.85) {
        usedColor = 'red';
      }

    }

    return {
      'total': projectBudget,
      'used': projectSpent,
      'usedPercent': usedPercent,
      'usedColor': usedColor
    };
  },

  /**
   * Loops through the resolved order history on the project
   * If any of these orders are not completed, we return false.
   * A completed order is currently equivalent to a service.
   *
   * @private
   */
  _areAllServicesComplete: function() {

    // This short circuits on the first non complete item.
    var anyNotComplete = _.some(this.project.services, function(item, key) {
      // @todo Who nows if this will be the final status.
      return item.provision_status !== 'Complete';
    });

    // We return the reverse here.
    // Are all services complete
    return !anyNotComplete;

  },

  /**
   * Check that a variable is a number and defined (avoids NaN and undefined)
   *
   * http://stackoverflow.com/questions/18082/validate-decimal-numbers-in-javascript-isnumeric
   *
   * @private
   */
  _isANumber: function(n) {
    return (!isNaN(parseFloat(n)) && isFinite(n));
  }
};

module.exports = ProjectController;
<commit_msg>added flashes for successfully removing users/services from projects<commit_after>'use strict';

var _ = require('lodash');

/**@ngInject*/
function ProjectController($interval, project, ProjectUsersResource, OrderItemsResource, alerts, products, FlashesService) {

  this.intervalDelay = 30000;
  this.$interval = $interval;

  this.project = project;
  this.reason = null; // The reason this project has been rejected.

  // Filter the alerts to only show them for this project.
  this.alerts = _.filter(alerts, function(alert) {
    return alert.project_id == project.id;
  });
  this.products = products;

  this.ProjectUsersResource = ProjectUsersResource;
  this.OrderItemsResource = OrderItemsResource;

  this.FlashesService = FlashesService;

  /**
   * On creation/transition to scope, start refresh interval if
   * we need to to reload unfinished service data.
   *
   * TODO : Revisit the data refresh
   */
  //$scope.$on('$stateChangeSuccess', _.bind(function () {
  //  this.stopRefreshInterval();
  //}, this));
  //
  //$scope.$on('$stateChangeStart', _.bind(function() {
  //  this.startRefreshInterval();
  //}, this));
}

ProjectController.resolve = {
  /**@ngInject*/
  project: function(ProjectsResource, $stateParams) {
    return ProjectsResource.get({
      id: $stateParams.projectId,
      'includes[]': ['approvals', 'approvers', 'services', 'project_answers', 'staff']
    }).$promise;
  },
  /**@ngInject*/
  products: function(ProductsResource) {
    return ProductsResource.query().$promise;
  }
};

ProjectController.prototype = {

  /**
   * Setup refresh interval if not all services are complete.
   * Will automatically stop polling after all services are complete.
   * Polls Every 30 Seconds.
   */
  startRefreshInterval: function() {
    var self = this;
    if (!self._areAllServicesComplete()) {
      self.interval = this.$interval(function() {
        self.project.$get(function() {
          if (self._areAllServicesComplete()) {
            self.stopRefreshInterval();
          }
        });
      }, this.intervalDelay);
    }
  },

  /**
   * Clear/Stop the polling.
   */
  stopRefreshInterval: function() {
    this.$interval.cancel(this.interval);
    this.interval = undefined;
  },

  /**
   * Project Approval actions
   */
  approve: function() {
    this.project.$approve();
  },
  reject: function() {
    this.project.$reject({reason: this.reason});
  },

  removeUserFromProject: function(index) {
    var self = this;

    this.ProjectUsersResource.delete({id: this.project.id, staff_id: this.project.staff[index].id}).$promise.then(
      _.bind(function(data) {
        this.project.staff.splice(index, 1);
        self.FlashesService.add({
            timeout: true,
            type: 'success',
            message: "The user was successfully removed from this project."
        });
      }, this),
      function(error) {
        self.FlashesService.add({
          timeout: true,
          type: 'error',
          message: "There was an error removing this user."
        });
      }
    );
  },

  removeServiceFromProject: function(serviceIndex) {
    var self = this,
      service = this.project.services[serviceIndex];

    this.OrderItemsResource.delete({id: service.id, order_id: service.order_id}).$promise.then(
      _.bind(function() {
        // Remove it from the existing array.
        this.project.services.splice(serviceIndex, 1);
        self.FlashesService.add({
            timeout: true,
            type: 'success',
            message: "The service was successfully removed from this project."
        });
      }, this),
      function(error) {
        self.FlashesService.add({
          timeout: true,
          type: 'error',
          message: "There was an error removing this service."
        });
      }
    );
  },

  /**
   * Links the service with the product.
   * @param serviceObject
   * @returns {*}
   */
  getServiceWithProduct: function(serviceObject) {
    var productId = serviceObject.product_id;

    var product = _.find(this.products, function(obj) {
      return obj.id == productId;
    });

    // Hook on the product details we need to use the product box.
    serviceObject.img = product.img;
    serviceObject.name = product.name;
    serviceObject.description = product.description;

    return serviceObject;
  },

  getLeftData: function() {
    var projectBudget = this.project.budget || 0;
    var projectSpent = this.project.spent || 0;
    var monthlySpend = this.project.monthly_spend || 0;

    var leftPercent = 1.0;
    var leftMonths = '>12';
    var leftColor = 'green';

    if (projectBudget > 0 && monthlySpend !== 0 && this._isANumber(monthlySpend)) {

      leftMonths = Math.round((projectBudget - projectSpent) / monthlySpend);
      leftPercent = leftMonths / 12;

      if (leftMonths > 12) {
        leftPercent = 1.0;
      } else if (leftMonths <= 5 && leftMonths > 3) {
        leftColor = '#CCDB23';
      } else if (leftMonths <= 3 && leftMonths > 0) {
        leftColor = 'red';
      } else if (leftMonths <= 0) {
        leftMonths = 0;
        leftPercent = 0.0;
        leftColor = 'red';
      }

      if (leftMonths > 99) {
        leftMonths = '>99';
      }

    }

    return {
      'leftPercent': leftPercent,
      'leftColor': leftColor,
      'leftMonths': leftMonths,
      'monthlySpend': monthlySpend
    };
  },

  getBudgetData: function() {
    var projectBudget = this.project.budget || 0;
    var projectSpent = this.project.spent || 0;
    var usedPercent = 0.0;
    var usedColor = 'green';

    if (projectBudget > 0) {

      usedPercent = projectSpent / projectBudget;
      if (usedPercent > 1.0) {
        usedPercent = 1.0;
      }

      // Set the colors for usedAmount
      if (usedPercent <= 0.85 && usedPercent > 0.65) {
        usedColor = '#CCDB23';
      } else if (usedPercent >= 0.85) {
        usedColor = 'red';
      }

    }

    return {
      'total': projectBudget,
      'used': projectSpent,
      'usedPercent': usedPercent,
      'usedColor': usedColor
    };
  },

  /**
   * Loops through the resolved order history on the project
   * If any of these orders are not completed, we return false.
   * A completed order is currently equivalent to a service.
   *
   * @private
   */
  _areAllServicesComplete: function() {

    // This short circuits on the first non complete item.
    var anyNotComplete = _.some(this.project.services, function(item, key) {
      // @todo Who nows if this will be the final status.
      return item.provision_status !== 'Complete';
    });

    // We return the reverse here.
    // Are all services complete
    return !anyNotComplete;

  },

  /**
   * Check that a variable is a number and defined (avoids NaN and undefined)
   *
   * http://stackoverflow.com/questions/18082/validate-decimal-numbers-in-javascript-isnumeric
   *
   * @private
   */
  _isANumber: function(n) {
    return (!isNaN(parseFloat(n)) && isFinite(n));
  }
};

module.exports = ProjectController;
<|endoftext|>