<commit_before>/*    Copyright 2016 Firewalla LLC
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    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 Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
'use strict';

const log = require('../net2/logger.js')(__filename);

const fc = require('../net2/config.js')

const Sensor = require('./Sensor.js').Sensor;

const sem = require('../sensor/SensorEventManager.js').getInstance();

const bone = require('../lib/Bone.js');

const CategoryUpdater = require('../control/CategoryUpdater.js');
const categoryUpdater = new CategoryUpdater();

const CountryUpdater = require('../control/CountryUpdater.js');
const countryUpdater = new CountryUpdater();

const categoryHashsetMapping = {
  "games": "app.gaming",
  "social": "app.social",
  "av": "app.video",
  "porn": "app.porn",  // dnsmasq redirect to blue hole if porn
  "gamble": "app.gamble",
  "p2p": "app.p2p"
}

const securityHashMapping = {
  "default_c": "blockset:default:consumer"
}

class CategoryUpdateSensor extends Sensor {

  async regularJob() {
    const categories = Object.keys(categoryHashsetMapping)
    for (const category of categories) {
      await this.updateCategory(category);
    }
  }

  async securityJob() {
    const securityCategories = Object.keys(securityHashMapping)
    for (const category of securityCategories) {
      await this.updateSecurityCategory(category)
    }
  }

  async countryJob() {
    const countryList = this.loadCategoryFromBone('country:list');
    rc.saddAsync('', countryList);

    const activeCountries = countryUpdater.getActiveCountries();
    log.info('Active countries', activeCountries);
    for (const country of activeCountries) {
      await this.updateCountryAllocation(country)
      const category = countryUpdater.getCategory(country)
      await countryUpdater.refreshCategoryRecord(category)
      await countryUpdater.recycleIPSet(category)
    }
  }

  async updateCategory(category) {
    log.info(`Loading domains for ${category} from cloud`);

    const hashset = this.getCategoryHashset(category)
    const domains = await this.loadCategoryFromBone(hashset);
    log.info(`category ${category} has ${domains.length} domains`)

    await categoryUpdater.flushDefaultDomains(category);
    return categoryUpdater.addDefaultDomains(category,domains);
  }

  async updateSecurityCategory(category) {
    log.info(`Loading security info for ${category} from cloud`);

    const hashset = securityHashMapping[category]
    const info = await this.loadCategoryFromBone(hashset);

    const domains = info.domain
    const ip4List = info["ip4"]
    const ip6List = info["ip6"]

    log.info(`category ${category} has ${(ip4List || []).length} ipv4,`
      + ` ${(ip6List || []).length} ipv6, ${(domains || []).length} domains`)

    // if (domains) {
    //   await categoryUpdater.flushDefaultDomains(category);
    //   await categoryUpdater.addDefaultDomains(category,domains);
    // }

    if (ip4List) {
      await categoryUpdater.flushIPv4Addresses(category)
      await categoryUpdater.addIPv4Addresses(category, ip4List)
    }

    if (ip6List) {
      await categoryUpdater.flushIPv6Addresses(category)
      await categoryUpdater.addIPv6Addresses(category, ip6List)
    }
  }

  async updateCountryAllocation(country) {
    const category = countryUpdater.getCategory(country);
    log.info(`Loading country ip allocation list for ${country} from cloud`);

    const ip4List = await this.loadCategoryFromBone(category + ':ip4');

    if (ip4List) {
      await countryUpdater.addAddresses(country, false, ip4List)
    }

    log.info(`Country ${country} has ${(ip4List || []).length} ipv4 entries`);

    if (fc.isFeatureOn('ipv6')) {
      const ip6List = await this.loadCategoryFromBone(category + ':ip6');

      if (ip6List) {
        await countryUpdater.addAddresses(country, true, ip6List)
      }

      log.info(`Country ${country} has ${(ip6List || []).length} ipv6 entries`)
    }
  }

  run() {
    sem.once('IPTABLES_READY', async() => {
      await this.regularJob()
      await this.securityJob()

      // initial round of country list update is triggered by this event
      // also triggers dynamic list and ipset update here
      // to make sure blocking takes effect immediately
      sem.on('Policy:CountryActivated', async (event) => {
        await this.updateCountryAllocation(event.country)
        const category = countryUpdater.getCategory(event.country)
        await countryUpdater.refreshCategoryRecord(category)
        await countryUpdater.recycleIPSet(category, false)
      })

      setInterval(this.regularJob, this.config.regularInterval * 1000)

      setInterval(this.securityJob, this.config.securityInterval * 1000)

      setInterval(this.countryJob, this.config.countryInterval * 1000)
    })
  }

  async loadCategoryFromBone(hashset) {
    if (hashset) {
      try {
        const data = await bone.hashsetAsync(hashset)
        const list = JSON.parse(data)
        return list
      } catch(err) {
        log.error("Failed to get hashset", hashset, err);
        return []
      }
    } else {
      return []
    }
  }

  getCategoryHashset(category) {
    return categoryHashsetMapping[category]
  }
}

module.exports = CategoryUpdateSensor;
<commit_msg>start listening to CountryActivated immediately after iptables are ready<commit_after>/*    Copyright 2016 Firewalla LLC
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    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 Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
'use strict';

const log = require('../net2/logger.js')(__filename);

const fc = require('../net2/config.js')

const Sensor = require('./Sensor.js').Sensor;

const sem = require('../sensor/SensorEventManager.js').getInstance();

const bone = require('../lib/Bone.js');

const CategoryUpdater = require('../control/CategoryUpdater.js');
const categoryUpdater = new CategoryUpdater();

const CountryUpdater = require('../control/CountryUpdater.js');
const countryUpdater = new CountryUpdater();

const categoryHashsetMapping = {
  "games": "app.gaming",
  "social": "app.social",
  "av": "app.video",
  "porn": "app.porn",  // dnsmasq redirect to blue hole if porn
  "gamble": "app.gamble",
  "p2p": "app.p2p"
}

const securityHashMapping = {
  "default_c": "blockset:default:consumer"
}

class CategoryUpdateSensor extends Sensor {

  async regularJob() {
    const categories = Object.keys(categoryHashsetMapping)
    for (const category of categories) {
      await this.updateCategory(category);
    }
  }

  async securityJob() {
    const securityCategories = Object.keys(securityHashMapping)
    for (const category of securityCategories) {
      await this.updateSecurityCategory(category)
    }
  }

  async countryJob() {
    const countryList = this.loadCategoryFromBone('country:list');
    rc.saddAsync('', countryList);

    const activeCountries = countryUpdater.getActiveCountries();
    log.info('Active countries', activeCountries);
    for (const country of activeCountries) {
      await this.updateCountryAllocation(country)
      const category = countryUpdater.getCategory(country)
      await countryUpdater.refreshCategoryRecord(category)
      await countryUpdater.recycleIPSet(category)
    }
  }

  async updateCategory(category) {
    log.info(`Loading domains for ${category} from cloud`);

    const hashset = this.getCategoryHashset(category)
    const domains = await this.loadCategoryFromBone(hashset);
    log.info(`category ${category} has ${domains.length} domains`)

    await categoryUpdater.flushDefaultDomains(category);
    return categoryUpdater.addDefaultDomains(category,domains);
  }

  async updateSecurityCategory(category) {
    log.info(`Loading security info for ${category} from cloud`);

    const hashset = securityHashMapping[category]
    const info = await this.loadCategoryFromBone(hashset);

    const domains = info.domain
    const ip4List = info["ip4"]
    const ip6List = info["ip6"]

    log.info(`category ${category} has ${(ip4List || []).length} ipv4,`
      + ` ${(ip6List || []).length} ipv6, ${(domains || []).length} domains`)

    // if (domains) {
    //   await categoryUpdater.flushDefaultDomains(category);
    //   await categoryUpdater.addDefaultDomains(category,domains);
    // }

    if (ip4List) {
      await categoryUpdater.flushIPv4Addresses(category)
      await categoryUpdater.addIPv4Addresses(category, ip4List)
    }

    if (ip6List) {
      await categoryUpdater.flushIPv6Addresses(category)
      await categoryUpdater.addIPv6Addresses(category, ip6List)
    }
  }

  async updateCountryAllocation(country) {
    const category = countryUpdater.getCategory(country);
    log.info(`Loading country ip allocation list for ${country} from cloud`);

    const ip4List = await this.loadCategoryFromBone(category + ':ip4');

    if (ip4List) {
      await countryUpdater.addAddresses(country, false, ip4List)
    }

    log.info(`Country ${country} has ${(ip4List || []).length} ipv4 entries`);

    if (fc.isFeatureOn('ipv6')) {
      const ip6List = await this.loadCategoryFromBone(category + ':ip6');

      if (ip6List) {
        await countryUpdater.addAddresses(country, true, ip6List)
      }

      log.info(`Country ${country} has ${(ip6List || []).length} ipv6 entries`)
    }
  }

  run() {
    sem.once('IPTABLES_READY', async() => {
      // initial round of country list update is triggered by this event
      // also triggers dynamic list and ipset update here
      // to make sure blocking takes effect immediately
      sem.on('Policy:CountryActivated', async (event) => {
        await this.updateCountryAllocation(event.country)
        const category = countryUpdater.getCategory(event.country)
        await countryUpdater.refreshCategoryRecord(category)
        await countryUpdater.recycleIPSet(category, false)
      })

      await this.regularJob()
      await this.securityJob()

      setInterval(this.regularJob, this.config.regularInterval * 1000)

      setInterval(this.securityJob, this.config.securityInterval * 1000)

      setInterval(this.countryJob, this.config.countryInterval * 1000)
    })
  }

  async loadCategoryFromBone(hashset) {
    if (hashset) {
      try {
        const data = await bone.hashsetAsync(hashset)
        const list = JSON.parse(data)
        return list
      } catch(err) {
        log.error("Failed to get hashset", hashset, err);
        return []
      }
    } else {
      return []
    }
  }

  getCategoryHashset(category) {
    return categoryHashsetMapping[category]
  }
}

module.exports = CategoryUpdateSensor;
<|endoftext|>


<commit_before>describe('/ftp', () => {
  describe('challenge "confidentialDocument"', () => {
    it('should be able to access file /ftp/acquisitions.md', () => {
      browser.driver.get(browser.baseUrl + '/ftp/acquisitions.md')
    })

    protractor.expect.challengeSolved({challenge: 'Confidential Document'})
  })

  describe('challenge "errorHandling"', () => {
    it('should leak information through error message accessing /ftp/easter.egg due to wrong file suffix', () => {
      browser.driver.get(browser.baseUrl + '/ftp/easter.egg')

      browser.driver.isElementPresent(by.id('stacktrace')).then(present => {
        expect(present).toBe(true)
      })

      protractor.expect.challengeSolved({challenge: 'Error Handling'})
    })
  })

  describe('challenge "forgottenBackup"', () => {
    it('should be able to access file /ftp/coupons_2013.md.bak abusing md_debug parameter', () => {
      browser.driver.get(browser.baseUrl + '/ftp/coupons_2013.md.bak?md_debug=.md')
    })

    protractor.expect.challengeSolved({challenge: 'Forgotten Sales Backup'})
  })

  describe('challenge "forgottenDevBackup"', () => {
    it('should be able to access file /ftp/package.json.bak with poison null byte attack', () => {
      browser.driver.get(browser.baseUrl + '/ftp/package.json.bak%2500.md')
    })

    protractor.expect.challengeSolved({challenge: 'Forgotten Developer Backup'})
  })

  describe('challenge "easterEgg1"', () => {
    it('should be able to access file /ftp/easter.egg with poison null byte attack', () => {
      browser.driver.get(browser.baseUrl + '/ftp/eastere.gg%2500.md')
    })

    protractor.expect.challengeSolved({challenge: 'Easter Egg Tier 1'})
  })
})
<commit_msg>Disable broken non-Angular element locator<commit_after>describe('/ftp', () => {
  describe('challenge "confidentialDocument"', () => {
    it('should be able to access file /ftp/acquisitions.md', () => {
      browser.driver.get(browser.baseUrl + '/ftp/acquisitions.md')
    })

    protractor.expect.challengeSolved({challenge: 'Confidential Document'})
  })

  describe('challenge "errorHandling"', () => {
    it('should leak information through error message accessing /ftp/easter.egg due to wrong file suffix', () => {
      browser.driver.get(browser.baseUrl + '/ftp/easter.egg')

      /* browser.driver.isElementPresent(by.id('stacktrace')).then(present => {
        expect(present).toBe(true)
      }) */

      protractor.expect.challengeSolved({challenge: 'Error Handling'})
    })
  })

  describe('challenge "forgottenBackup"', () => {
    it('should be able to access file /ftp/coupons_2013.md.bak abusing md_debug parameter', () => {
      browser.driver.get(browser.baseUrl + '/ftp/coupons_2013.md.bak?md_debug=.md')
    })

    protractor.expect.challengeSolved({challenge: 'Forgotten Sales Backup'})
  })

  describe('challenge "forgottenDevBackup"', () => {
    it('should be able to access file /ftp/package.json.bak with poison null byte attack', () => {
      browser.driver.get(browser.baseUrl + '/ftp/package.json.bak%2500.md')
    })

    protractor.expect.challengeSolved({challenge: 'Forgotten Developer Backup'})
  })

  describe('challenge "easterEgg1"', () => {
    it('should be able to access file /ftp/easter.egg with poison null byte attack', () => {
      browser.driver.get(browser.baseUrl + '/ftp/eastere.gg%2500.md')
    })

    protractor.expect.challengeSolved({challenge: 'Easter Egg Tier 1'})
  })
})
<|endoftext|>


<commit_before>/**
 * Displays an editable view of an output's details.
 * We support output-specific metadata and ad-hoc view if the output
 * plugin defines it.
 *
 * (c) 2014 Edouard Lafargue, ed@lafargue.name
 * All rights reserved.
 */

define(function(require) {

    "use strict";

    var $       = require('jquery'),
        _       = require('underscore'),
        Backbone = require('backbone'),
        utils   = require('app/utils'),
        tableTemplate = require('tpl/OutputDetailsFieldTable'),
        outputMappingsTemplate = require('tpl/OutputDetailsMappingTable'),
        template = require('tpl/OutputDetailsView');

    return Backbone.View.extend({

        id: "output-details",
        gotdata: false,

        initialize: function() {

            // Mapping is "outputfield" : "datafield"
            this.devicefields = this.model.get('mappings');

            // We need to query our output plugin for the fields it needs, so that we can map them.
            //
            // Some plugins can accept a variable number of fiels, named "Field1" ... "FieldN", and
            // this.outputfields will be "variable" in that case.
            this.generateOutputFields();

        },

        generateOutputFields: function() {
            this.outputfields = outputManager.getOutputFields(this.model.get('type'));
            if (this.outputfields == "variable") {
                var num = parseInt(this.model.get('metadata')['numfields']) || 1;
                this.outputfields = {};
                for (var i=1; i < num+1 ; i++) {
                    this.outputfields['field' + i] = { "name": "Field " + i,
                                                      "required": (i==1)? true: false
                                                     };
                }
            }
        },

        render: function () {

            console.log("Render output details");

            $(this.el).html(template(_.extend(this.model.toJSON(), {outtypes: outputManager.getOutputsForCurrentInstrument(),
                                                                   outputfields: this.outputfields
                                                                   })));

            // If the instrument type has got its own extra settings, then render those here:
            var outSettingsView = outputManager.supportedOutputs[this.model.get('type')].settings;
            if ( outSettingsView != null) {
                var settingsView = new outSettingsView({model: this.model});
                $('#metadata',this.el).html(settingsView.el);
                settingsView.render();
            }

            this.renderMappingsTable();

            // Now, we want to listen to the instrument to display a nice table of all fields
            // that the instrument outputs, so that the user can select those he wants to send to the
            // output plugin.

            this.wasStreaming = linkManager.isStreaming();
            linkManager.startLiveStream();
            this.listenTo(linkManager, 'input', this.showInput);

            return this;
        },

        renderMappingsTable: function() {
            var self = this;
            // Populate the field mappings: we are going to enrich the output plugin
            // config with the name of the data field it is mapped to

            // Gotta refresh the default outputfields each time
            this.generateOutputFields();
            _.each(this.outputfields, function(field, fieldname) {
                var mapped = self.devicefields[fieldname];
                if (mapped != undefined)
                    self.outputfields[fieldname].mappedto = mapped;
            });

            $("#mappings", this.el).html(outputMappingsTemplate({mappings: this.outputfields}));

        },

        onClose: function() {
            if (! this.wasStreaming )
                linkManager.stopLiveStream();
        },

        events: {
            "change"        : "change",
            "click .save"   : "beforeSave",
            "click .delete" : "deleteOutput",
            "click #do-delete": "doDeleteOutput",
            "click .fieldcheckbox" : "toggleEnabled",
        },

        change: function (event) {
            console.log("Output settings change");
            // Remove any existing alert message
            utils.hideAlert();

            // Apply the change to the model
            var target = event.target;
            var change = {};

            // A change for a field mapping is a bit more complex and needs
            // to be handled separately
            if (target.name == "fieldmapping") {
                var devicefield = $(event.target).data("field");
                if (target.value == "_unused") {
                  // We removed a mapping: we need to go through all device fields
                  // and look where this field was used, to remove it
                  for (var fieldname in this.devicefields) {
                      if (this.devicefields[fieldname] == devicefield)
                          delete this.devicefields[fieldname];
                    };
                } else {
                    this.devicefields[target.value] = devicefield;
                }
                this.model.set('mappings', this.devicefields);
                this.renderMappingsTable();
            } else if (target.name == "staticvalue") {
                var field = $(target).data('field');
                if (target.value == '') {
                    delete this.devicefields[field];
                } else {
                    this.devicefields[field] = "__" + target.value;
                }
                this.model.set('mappings', this.devicefields);
                this.renderMappingsTable();
            }  else if (target.name.indexOf("alarm") == 0) {
                // Update our alarms:
                var keys = target.name.split(".");
                var alrm = this.model.get(keys[0]);
                alrm[keys[1]] = target.value;
                this.model.set(keys[0],alrm);
            } else if (target.name == 'numfields') {
                // this is a change that bubbled up from the bespoke settings view,
                // we just need to re-render for the updated number of fields
                this.gotdata = false;
                this.generateOutputFields();
                this.render();
                // And switch back to the correct tab so that the user is not surprised:
                $('#settingsTabs a[href="#props"]').tab('show')

            } else {

                // Our Spinedit control returns values as strings even
                // when they are numbers (uses .val() in the setvalue method),
                // so we have to attempt to convert it back to a number if it makes
                // sense:
                var numval = parseFloat(target.value);
                change[target.name] = isNaN(numval) ? target.value : numval;
                this.model.set(change);

                // If we changed the plugin type, we need to reset the view:
                if (target.name == "type") {
                    this.gotdata = false;
                    this.generateOutputFields();
                    this.render();
                }

            }

            // TODO: is this right ?
            // This view is embedded into another view, so change events
            // are going to bubble up to the upper view and change attributes
            // with the same name, so we stop event propagation here:
            event.stopPropagation();
        },

        beforeSave: function () {
            console.log('Output: before save for output ' + this.model.id);

            this.saveOutput();
            return false;
        },

        saveOutput: function () {
            var self = this;
            this.model.save(null, {
                success: function (model) {
                    utils.showAlert('Success', 'Configuration saved', 'alert-success');
                    linkManager.setOutputs(instrumentManager.getInstrument().id);
                },
                error: function () {
                    console.log('Output: error saving');
                    utils.showAlert('Error', 'An error occurred while trying to save output config', 'alert-error');
                }
            });
        },

        deleteOutput: function() {
            $('#deleteConfirm',self.el).modal('show');
        },

        doDeleteOutput: function () {
            self = this;
            console.log("Delete output " + this.model.id);
            this.model.destroy({
                success: function () {
                    //alert('Controller deleted successfully');
                    self.remove();
                    //this.render();
                    return false;
                }
            });
            return false;
        },

        toggleEnabled: function(event) {
            var field = event.currentTarget.name;
            var idx = this.devicefields.indexOf(field);
            if ( idx > -1 ) {
                this.devicefields.splice(idx);
            } else {
                this.devicefields.push(field);
            }
            this.model.set('datafields', this.devicefields);
        },

        showInput: function(data) {
            if (this.gotdata)
                return;
            var flat = {};
            if (typeof(data) === "object") {
                // We sometimes have embedded JSON structures: flatten them
                flat = utils.JSONflatten(data);
                this.gotdata = true;

            } else if (typeof(data) === "string") {
                // If the instrument is sending a string, not much we can do except
                // send that string:
                flat = { "string" : data };
                this.gotdata = true;
            }
            $(".fieldselect", this.el).html(tableTemplate({fields: flat, outputfields: this.outputfields, selected: this.devicefields}));

        },

    });
});<commit_msg>Bugfix: when creating and output and deleting right away, app did not like it.<commit_after>/**
 * Displays an editable view of an output's details.
 * We support output-specific metadata and ad-hoc view if the output
 * plugin defines it.
 *
 * (c) 2014 Edouard Lafargue, ed@lafargue.name
 * All rights reserved.
 */

define(function(require) {

    "use strict";

    var $       = require('jquery'),
        _       = require('underscore'),
        Backbone = require('backbone'),
        utils   = require('app/utils'),
        tableTemplate = require('tpl/OutputDetailsFieldTable'),
        outputMappingsTemplate = require('tpl/OutputDetailsMappingTable'),
        template = require('tpl/OutputDetailsView');

    return Backbone.View.extend({

        id: "output-details",
        gotdata: false,

        initialize: function() {

            // Mapping is "outputfield" : "datafield"
            this.devicefields = this.model.get('mappings');

            // We need to query our output plugin for the fields it needs, so that we can map them.
            //
            // Some plugins can accept a variable number of fiels, named "Field1" ... "FieldN", and
            // this.outputfields will be "variable" in that case.
            this.generateOutputFields();

        },

        generateOutputFields: function() {
            this.outputfields = outputManager.getOutputFields(this.model.get('type'));
            if (this.outputfields == "variable") {
                var num = parseInt(this.model.get('metadata')['numfields']) || 1;
                this.outputfields = {};
                for (var i=1; i < num+1 ; i++) {
                    this.outputfields['field' + i] = { "name": "Field " + i,
                                                      "required": (i==1)? true: false
                                                     };
                }
            }
        },

        render: function () {

            console.log("Render output details");

            $(this.el).html(template(_.extend(this.model.toJSON(), {outtypes: outputManager.getOutputsForCurrentInstrument(),
                                                                   outputfields: this.outputfields
                                                                   })));

            // If the instrument type has got its own extra settings, then render those here:
            var outSettingsView = outputManager.supportedOutputs[this.model.get('type')].settings;
            if ( outSettingsView != null) {
                var settingsView = new outSettingsView({model: this.model});
                $('#metadata',this.el).html(settingsView.el);
                settingsView.render();
            }

            this.renderMappingsTable();

            // Now, we want to listen to the instrument to display a nice table of all fields
            // that the instrument outputs, so that the user can select those he wants to send to the
            // output plugin.

            this.wasStreaming = linkManager.isStreaming();
            linkManager.startLiveStream();
            this.listenTo(linkManager, 'input', this.showInput);

            return this;
        },

        renderMappingsTable: function() {
            var self = this;
            // Populate the field mappings: we are going to enrich the output plugin
            // config with the name of the data field it is mapped to

            // Gotta refresh the default outputfields each time
            this.generateOutputFields();
            _.each(this.outputfields, function(field, fieldname) {
                var mapped = self.devicefields[fieldname];
                if (mapped != undefined)
                    self.outputfields[fieldname].mappedto = mapped;
            });

            $("#mappings", this.el).html(outputMappingsTemplate({mappings: this.outputfields}));

        },

        onClose: function() {
            if (! this.wasStreaming )
                linkManager.stopLiveStream();
        },

        events: {
            "change"        : "change",
            "click .save"   : "beforeSave",
            "click .delete" : "deleteOutput",
            "click #do-delete": "doDeleteOutput",
            "click .fieldcheckbox" : "toggleEnabled",
        },

        change: function (event) {
            console.log("Output settings change");
            // Remove any existing alert message
            utils.hideAlert();

            // Apply the change to the model
            var target = event.target;
            var change = {};

            // A change for a field mapping is a bit more complex and needs
            // to be handled separately
            if (target.name == "fieldmapping") {
                var devicefield = $(event.target).data("field");
                if (target.value == "_unused") {
                  // We removed a mapping: we need to go through all device fields
                  // and look where this field was used, to remove it
                  for (var fieldname in this.devicefields) {
                      if (this.devicefields[fieldname] == devicefield)
                          delete this.devicefields[fieldname];
                    };
                } else {
                    this.devicefields[target.value] = devicefield;
                }
                this.model.set('mappings', this.devicefields);
                this.renderMappingsTable();
            } else if (target.name == "staticvalue") {
                var field = $(target).data('field');
                if (target.value == '') {
                    delete this.devicefields[field];
                } else {
                    this.devicefields[field] = "__" + target.value;
                }
                this.model.set('mappings', this.devicefields);
                this.renderMappingsTable();
            }  else if (target.name.indexOf("alarm") == 0) {
                // Update our alarms:
                var keys = target.name.split(".");
                var alrm = this.model.get(keys[0]);
                alrm[keys[1]] = target.value;
                this.model.set(keys[0],alrm);
            } else if (target.name == 'numfields') {
                // this is a change that bubbled up from the bespoke settings view,
                // we just need to re-render for the updated number of fields
                this.gotdata = false;
                this.generateOutputFields();
                this.render();
                // And switch back to the correct tab so that the user is not surprised:
                $('#settingsTabs a[href="#props"]').tab('show')

            } else {

                // Our Spinedit control returns values as strings even
                // when they are numbers (uses .val() in the setvalue method),
                // so we have to attempt to convert it back to a number if it makes
                // sense:
                var numval = parseFloat(target.value);
                change[target.name] = isNaN(numval) ? target.value : numval;
                this.model.set(change);

                // If we changed the plugin type, we need to reset the view:
                if (target.name == "type") {
                    this.gotdata = false;
                    this.generateOutputFields();
                    this.render();
                }

            }

            // TODO: is this right ?
            // This view is embedded into another view, so change events
            // are going to bubble up to the upper view and change attributes
            // with the same name, so we stop event propagation here:
            event.stopPropagation();
        },

        beforeSave: function () {
            console.log('Output: before save for output ' + this.model.id);

            this.saveOutput();
            return false;
        },

        saveOutput: function () {
            var self = this;
            this.model.save(null, {
                success: function (model) {
                    utils.showAlert('Success', 'Configuration saved', 'alert-success');
                    linkManager.setOutputs(instrumentManager.getInstrument().id);
                },
                error: function () {
                    console.log('Output: error saving');
                    utils.showAlert('Error', 'An error occurred while trying to save output config', 'alert-error');
                }
            });
        },

        deleteOutput: function() {
            if (this.model.id == undefined) {
                console.log("User wants to delete an output that was not created yet");
                router.navigate('outputs', {trigger: true});
                return;
            }

            $('#deleteConfirm',self.el).modal('show');
        },

        doDeleteOutput: function () {
            self = this;
            console.log("Delete output " + this.model.id);
            this.model.destroy({
                success: function () {
                    //alert('Controller deleted successfully');
                    self.remove();
                    //this.render();
                    return false;
                }
            });
            return false;
        },

        toggleEnabled: function(event) {
            var field = event.currentTarget.name;
            var idx = this.devicefields.indexOf(field);
            if ( idx > -1 ) {
                this.devicefields.splice(idx);
            } else {
                this.devicefields.push(field);
            }
            this.model.set('datafields', this.devicefields);
        },

        showInput: function(data) {
            if (this.gotdata)
                return;
            var flat = {};
            if (typeof(data) === "object") {
                // We sometimes have embedded JSON structures: flatten them
                flat = utils.JSONflatten(data);
                this.gotdata = true;

            } else if (typeof(data) === "string") {
                // If the instrument is sending a string, not much we can do except
                // send that string:
                flat = { "string" : data };
                this.gotdata = true;
            }
            $(".fieldselect", this.el).html(tableTemplate({fields: flat, outputfields: this.outputfields, selected: this.devicefields}));

        },

    });
});<|endoftext|>


<commit_before>// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

(function($, cloudStack) {
    cloudStack.uiCustom.autoscaler = function(args) {
        // Place outer args here as local variables
        // i.e, -- var dataProvider = args.dataProvider
        var forms = $.extend(true, {}, args.forms);
        var topfields = forms.topFields;
        var bottomfields = forms.bottomFields;
        var scaleuppolicy = forms.scaleUpPolicy;
        var scaledownpolicy = forms.scaleDownPolicy;
        var dataProvider = cloudStack.autoscaler.dataProvider;
        var actions = cloudStack.autoscaler.autoscaleActions;
        var actionFilter = cloudStack.autoscaler.actionFilter;

        return function(args) {
            var context = args.data ?
                $.extend(true, {}, args.context, {
                    lbRules: [args.data]
                }) : args.context;
            var formData = args.formData;
            var $autoscalerDialog = $('
').addClass('autoscaler');
            var $topFields = $('
').addClass('field-group top-fields');
            var $bottomFields = $('
').addClass('field-group bottom-fields');
            var $scaleUpPolicy = $('
').addClass('scale-up-policy');
            var $slideScaleUp = $('

').addClass('expand');
            var $hideScaleUp = $('

').addClass('hide');
            var $scaleUpLabel = $('
Show
').addClass('slide-label');
            var $scaleUpHideLabel = $('
Hide
').addClass('slide-label');
            var $scaleDownHideLabel = $('
Hide
').addClass('slide-label');
            var $scaleDownLabel = $('
Show
').addClass('slide-label');
            var $slideScaleDown = $('

').addClass('expand');
            var $hideScaleDown = $('

').addClass('hide');
            var $scaleUpDivider = $('

').addClass('policy-divider');
            var $scaleDownDivider = $('

').addClass('policy-divider');
            var $bottomFieldDivider = $('

').addClass('policy-divider');
            var $scaleDownPolicy = $('
').addClass('scale-down-policy');
            var $scaleUpPolicyTitle = $('
').addClass('scale-up-policy-title')
                .html("Scale Up Policy");
            var $scaleDownPolicyTitle = $('
').addClass('scale-down-policy-title')
                .html("Scale Down Policy");
            var topFieldForm, $topFieldForm,
                bottomFieldForm, $bottomFieldForm,
                scaleUpPolicyTitleForm, $scaleUpPolicyTitleForm,
                scaleDownPolicyTitleForm, $scaleDownPolicyTitleForm,
                scaleUpPolicyForm, scaleDownPolicyForm;

            var renderActions = function(args) {
                var targetActionFilter = args.actionFilter ? args.actionFilter : actionFilter;
                var data = args.data;
                var context = args.context;
                var $actions = $('
').addClass('detail-group');
                var $actionsTable = $('
').append('
');
                var $detailActions = $('').addClass('detail-actions');
                var $buttons = $('
').addClass('buttons');
                var visibleActions = targetActionFilter ?
                    targetActionFilter({
                        context: $.extend(true, {}, context, {
                            originalAutoscaleData: data ? [data] : null
                        })
                    }) :
                    $.map(actions, function(value, key) {
                        return key;
                    });

                $detailActions.append($buttons);
                $actionsTable.find('tr').append($detailActions);
                $actions.append($actionsTable);

                $(visibleActions).map(function(index, actionID) {
                    var action = actions[actionID];
                    var label = _l(action.label);
                    var $action = $('
').addClass('action').addClass(actionID);
                    var $icon = $('')
                        .attr({
                            href: '#',
                            title: label
                        })
                        .append($('').addClass('icon'));

                    if (visibleActions.length == 1) $action.addClass('single');
                    else if (!index) $action.addClass('first');
                    else if (index == visibleActions.length - 1) $action.addClass('last');

                    // Perform action event
                    $action.click(function() {
                        var $loading = $('
').addClass('loading-overlay').appendTo($autoscalerDialog);
                        var success = function(args) {
                            $loading.remove();
                            cloudStack.dialog.notice({
                                message: _l('label.task.completed') + ': ' + label
                            });

                            // Reload actions
                            if (data != null) { //data is originalAutoscaleData in \ui\scripts\autoscaler.js
                                data['afterActionIsComplete'] = args.data;
                            }

                            var $newActions = renderActions({
                                data: data ? $.extend(data, args.data) : args.data,
                                actionFilter: args.actionFilter,
                                context: context
                            });

                            $actions.after($newActions);
                            $actions.remove();
                        };
                        var error = function(message) {
                            $loading.remove();
                            cloudStack.dialog.notice({
                                message: message
                            });
                        };

                        action.action({
                            context: {
                                originalAutoscaleData: args.data
                            },
                            response: {
                                success: function(args) {
                                    var notification = $.extend(args.notification, {
                                        _custom: args._custom,
                                        desc: label
                                    });

                                    cloudStack.ui.notifications.add(
                                        notification,
                                        success, {},
                                        error, {}
                                    );
                                },
                                error: error
                            }
                        });
                    });

                    $action.append($icon);
                    $action.appendTo($buttons);
                });

                if (!visibleActions || !visibleActions.length) $actions.hide();

                return $actions;
            };

            var renderDialogContent = function(args) {
                var data = args.data ? args.data : {};

                // Setup default values, in case where existing data is present
                var setDefaultFields = function(fieldID, field) {
                    var fieldData = data[fieldID];

                    if (fieldData && !field.isBoolean) {
                        field.defaultValue = fieldData;
                    } else {
                        field.isChecked = fieldData;
                    }
                };
                $.each(topfields, setDefaultFields);
                $.each(bottomfields, setDefaultFields);

                $.extend(context, {
                    originalAutoscaleData: args.data
                });

                // Create and append top fields
                // -- uses create form to generate fields
                topFieldForm = cloudStack.dialog.createForm({
                    context: context,
                    noDialog: true, // Don't render a dialog, just return $formContainer
                    form: {
                        title: '',
                        fields: topfields
                    }
                });
                $topFieldForm = topFieldForm.$formContainer;
                $topFieldForm.appendTo($topFields);

                scaleUpPolicyTitleForm = cloudStack.dialog.createForm({
                    context: context,
                    noDialog: true,
                    form: {
                        title: '',
                        fields: {
                            scaleUpDuration: {
                                label: 'Duration(in sec)',
                                validation: {
                                    required: true
                                }
                            }
                        }
                    }
                });
                $scaleUpPolicyTitleForm = scaleUpPolicyTitleForm.$formContainer;
                $scaleUpPolicyTitleForm.appendTo($scaleUpPolicyTitle);

                scaleDownPolicyTitleForm = cloudStack.dialog.createForm({
                    context: context,
                    noDialog: true,
                    form: {
                        title: '',
                        fields: {
                            scaleDownDuration: {
                                label: 'Duration(in sec)',
                                validation: {
                                    required: true
                                }
                            }
                        }
                    }
                });
                $scaleDownPolicyTitleForm = scaleDownPolicyTitleForm.$formContainer;
                $scaleDownPolicyTitleForm.appendTo($scaleDownPolicyTitle);

                // Make multi-edits
                //
                // Scale up policy
                if (data.scaleUpPolicy && $.isArray(data.scaleUpPolicy.conditions)) {
                    $autoscalerDialog.data('autoscaler-scale-up-data',
                        data.scaleUpPolicy.conditions);
                }

                if (data.scaleUpPolicy && data.scaleUpPolicy.duration) {
                    $scaleUpPolicyTitleForm.find('input[name=scaleUpDuration]').val(
                        data.scaleUpPolicy.duration
                    );
                }

                scaleuppolicy.context = context;
                scaleUpPolicyForm = $scaleUpPolicy.multiEdit(scaleuppolicy);

                // Scale down policy
                if (data.scaleDownPolicy && $.isArray(data.scaleDownPolicy.conditions)) {
                    $autoscalerDialog.data('autoscaler-scale-down-data',
                        data.scaleDownPolicy.conditions);
                }

                if (data.scaleDownPolicy && data.scaleDownPolicy.duration) {
                    $scaleDownPolicyTitleForm.find('input[name=scaleDownDuration]').val(
                        data.scaleDownPolicy.duration
                    );
                }

                scaledownpolicy.context = context;
                scaleDownPolicyForm = $scaleDownPolicy.multiEdit(scaledownpolicy);

                // Create and append bottom fields
                bottomFieldForm = cloudStack.dialog.createForm({
                    context: context,
                    noDialog: true, // Don't render a dialog, just return $formContainer
                    form: {
                        title: '',
                        fields: bottomfields
                    }
                });
                $bottomFieldForm = bottomFieldForm.$formContainer;
                $bottomFieldForm.appendTo($bottomFields);

                // Append main div elements
                $autoscalerDialog.append(
                    $topFields,
                    $scaleUpPolicyTitle,
                    $scaleUpPolicy,
                    $scaleDownPolicyTitle,
                    $scaleDownPolicy,
                    $bottomFields
                );

                // Render dialog
                //$autoscalerDialog.find('.form-item[rel=templateNames] label').hide();
                /* Duration Fields*/
                //$('div.ui-dialog div.autoscaler').find('div.scale-up-policy-title').append("

").append($inputLabel = $('').html('Duration').attr({left:'200'})).append($('<input>').attr({ name: 'username' }));
                //$('div.ui-dialog div.autoscaler').find('div.scale-down-policy-title').append("

").append($inputLabel = $('').html('Duration').attr({left:'200'})).append($('<input>').attr({ name: 'username' }));

                /*Dividers*/
                $autoscalerDialog.find('div.scale-up-policy-title').prepend($scaleUpDivider);
                $autoscalerDialog.find('div.scale-down-policy-title').prepend($scaleDownDivider);
                $autoscalerDialog.find('div.field-group.bottom-fields').prepend($bottomFieldDivider);

                /* Hide effects for multi-edit table*/
                $autoscalerDialog.find('div.scale-up-policy').prepend($hideScaleUp);
                $autoscalerDialog.find('div.scale-down-policy ').prepend($hideScaleDown);
                $autoscalerDialog.find('div.scale-up-policy').prepend($scaleUpHideLabel);
                $autoscalerDialog.find('div.scale-down-policy').prepend($scaleDownHideLabel);

                /*Toggling the labels and data-item table - SCALE UP POLICY*/
                $autoscalerDialog.find('div.scale-up-policy div.hide').click(function() {
                    $autoscalerDialog.find('div.scale-up-policy div.multi-edit div.data-item').slideToggle();
                    $scaleUpLabel = $autoscalerDialog.find('div.scale-up-policy div.slide-label').replaceWith($scaleUpLabel);
                });

                /*Toggling the images */
                $('div.ui-dialog div.autoscaler div.scale-up-policy div.hide').click(function() {
                    $(this).toggleClass('expand hide');
                });

                $('div.ui-dialog div.autoscaler div.scale-down-policy div.hide').click(function() {
                    $(this).toggleClass('expand hide');
                });

                /*Toggling the labels and data-item table - SCALE DOWN POLICY*/
                $('div.ui-dialog div.autoscaler div.scale-down-policy div.hide').click(function() {
                    $('div.ui-dialog div.autoscaler div.scale-down-policy div.multi-edit div.data div.data-item').slideToggle();
                    $scaleDownLabel = $('div.ui-dialog div.autoscaler div.scale-down-policy div.slide-label').replaceWith($scaleDownLabel);
                });

                $('div.ui-dialog div.autoscaler div.scale-down-policy div.multi-edit div.data div.expand').click(function() {
                    $('div.ui-dialog div.autoscaler div.scale-down-policy div.multi-edit div.data div.data-item').slideToggle();
                });

                $autoscalerDialog.dialog('option', 'position', 'center');
                $autoscalerDialog.dialog('option', 'height', 'auto');

                // Setup actions
                renderActions(args).prependTo($autoscalerDialog);
            };

            var $loading = $('
').addClass('loading-overlay').appendTo($autoscalerDialog);
            $autoscalerDialog.dialog({
                title: 'AutoScale Configuration Wizard',
                width: 825,
                height: 600,
                draggable: true,
                closeonEscape: false,
                overflow: 'auto',
                open: function() {
                    $("button").each(function() {
                        $(this).attr("style", "left: 600px; position: relative; margin-right: 5px; ");
                    });
                },
                buttons: [{
                    text: _l('label.cancel'),
                    'class': 'cancel',
                    click: function() {
                        $autoscalerDialog.dialog('destroy');
                        $('.overlay').remove();
                    }
                }, {
                    text: _l('Apply'),
                    'class': 'ok',
                    click: function() {
                        var data = cloudStack.serializeForm($('.ui-dialog .autoscaler form'));

                        // Pass VPC data
                        if (formData.tier) {
                            data.tier = formData.tier;
                        }

                        $loading.appendTo($autoscalerDialog);
                        cloudStack.autoscaler.actions.apply({
                            formData: formData,
                            context: context,
                            data: data,
                            response: {
                                success: function() {
                                    $loading.remove();
                                    $autoscalerDialog.dialog('destroy');
                                    $autoscalerDialog.closest(':ui-dialog').remove();
                                    $('.overlay').remove();
                                    cloudStack.dialog.notice({
                                        message: 'Autoscale configured successfully.'
                                    });
                                },
                                error: function(message) {
                                    cloudStack.dialog.notice({
                                        message: message
                                    });
                                    $loading.remove();
                                }
                            }
                        });
                    }
                }]
            }).closest('.ui-dialog').overlay();

            dataProvider({
                context: context,
                response: {
                    success: function(args) {
                        $loading.remove();
                        renderDialogContent(args);

                        if (args.data == null) { //from a new LB rule
                            $autoscalerDialog.find('select[name=serviceOfferingId]').removeAttr('disabled');
                            $autoscalerDialog.find('select[name=securityGroups]').removeAttr('disabled');
                            $autoscalerDialog.find('select[name=diskOfferingId]').removeAttr('disabled');
                        } else { //from an existing LB rule
                            $autoscalerDialog.find('select[name=serviceOfferingId]').attr('disabled', true);
                            $autoscalerDialog.find('select[name=securityGroups]').attr('disabled', true);
                            $autoscalerDialog.find('select[name=diskOfferingId]').attr('disabled', true);

                            if (args.data.isAdvanced != null) {
                                $autoscalerDialog.find('input[type=checkbox]').trigger('click');
                                $autoscalerDialog.find('input[type=checkbox]').attr('checked', 'checked');
                            }
                        }
                    }
                }
            });
        };
    };
}(jQuery, cloudStack));
<commit_msg>CLOUDSTACK-4219: Autoscale UI: Fix for missing form data<commit_after>// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

(function($, cloudStack) {
    cloudStack.uiCustom.autoscaler = function(args) {
        // Place outer args here as local variables
        // i.e, -- var dataProvider = args.dataProvider
        var forms = $.extend(true, {}, args.forms);
        var topfields = forms.topFields;
        var bottomfields = forms.bottomFields;
        var scaleuppolicy = forms.scaleUpPolicy;
        var scaledownpolicy = forms.scaleDownPolicy;
        var dataProvider = cloudStack.autoscaler.dataProvider;
        var actions = cloudStack.autoscaler.autoscaleActions;
        var actionFilter = cloudStack.autoscaler.actionFilter;

        return function(args) {
            var context = args.data ?
                $.extend(true, {}, args.context, {
                    lbRules: [args.data]
                }) : args.context;
            var formData = args.formData;
            var $autoscalerDialog = $('
').addClass('autoscaler');
            var $topFields = $('
').addClass('field-group top-fields');
            var $bottomFields = $('
').addClass('field-group bottom-fields');
            var $scaleUpPolicy = $('
').addClass('scale-up-policy');
            var $slideScaleUp = $('

').addClass('expand');
            var $hideScaleUp = $('

').addClass('hide');
            var $scaleUpLabel = $('
Show
').addClass('slide-label');
            var $scaleUpHideLabel = $('
Hide
').addClass('slide-label');
            var $scaleDownHideLabel = $('
Hide
').addClass('slide-label');
            var $scaleDownLabel = $('
Show
').addClass('slide-label');
            var $slideScaleDown = $('

').addClass('expand');
            var $hideScaleDown = $('

').addClass('hide');
            var $scaleUpDivider = $('

').addClass('policy-divider');
            var $scaleDownDivider = $('

').addClass('policy-divider');
            var $bottomFieldDivider = $('

').addClass('policy-divider');
            var $scaleDownPolicy = $('
').addClass('scale-down-policy');
            var $scaleUpPolicyTitle = $('
').addClass('scale-up-policy-title')
                .html("Scale Up Policy");
            var $scaleDownPolicyTitle = $('
').addClass('scale-down-policy-title')
                .html("Scale Down Policy");
            var topFieldForm, $topFieldForm,
                bottomFieldForm, $bottomFieldForm,
                scaleUpPolicyTitleForm, $scaleUpPolicyTitleForm,
                scaleDownPolicyTitleForm, $scaleDownPolicyTitleForm,
                scaleUpPolicyForm, scaleDownPolicyForm;

            var renderActions = function(args) {
                var targetActionFilter = args.actionFilter ? args.actionFilter : actionFilter;
                var data = args.data;
                var context = args.context;
                var $actions = $('
').addClass('detail-group');
                var $actionsTable = $('
').append('
');
                var $detailActions = $('').addClass('detail-actions');
                var $buttons = $('
').addClass('buttons');
                var visibleActions = targetActionFilter ?
                    targetActionFilter({
                        context: $.extend(true, {}, context, {
                            originalAutoscaleData: data ? [data] : null
                        })
                    }) :
                    $.map(actions, function(value, key) {
                        return key;
                    });

                $detailActions.append($buttons);
                $actionsTable.find('tr').append($detailActions);
                $actions.append($actionsTable);

                $(visibleActions).map(function(index, actionID) {
                    var action = actions[actionID];
                    var label = _l(action.label);
                    var $action = $('
').addClass('action').addClass(actionID);
                    var $icon = $('')
                        .attr({
                            href: '#',
                            title: label
                        })
                        .append($('').addClass('icon'));

                    if (visibleActions.length == 1) $action.addClass('single');
                    else if (!index) $action.addClass('first');
                    else if (index == visibleActions.length - 1) $action.addClass('last');

                    // Perform action event
                    $action.click(function() {
                        var $loading = $('
').addClass('loading-overlay').appendTo($autoscalerDialog);
                        var success = function(args) {
                            $loading.remove();
                            cloudStack.dialog.notice({
                                message: _l('label.task.completed') + ': ' + label
                            });

                            // Reload actions
                            if (data != null) { //data is originalAutoscaleData in \ui\scripts\autoscaler.js
                                data['afterActionIsComplete'] = args.data;
                            }

                            var $newActions = renderActions({
                                data: data ? $.extend(data, args.data) : args.data,
                                actionFilter: args.actionFilter,
                                context: context
                            });

                            $actions.after($newActions);
                            $actions.remove();
                        };
                        var error = function(message) {
                            $loading.remove();
                            cloudStack.dialog.notice({
                                message: message
                            });
                        };

                        action.action({
                            context: {
                                originalAutoscaleData: args.data
                            },
                            response: {
                                success: function(args) {
                                    var notification = $.extend(args.notification, {
                                        _custom: args._custom,
                                        desc: label
                                    });

                                    cloudStack.ui.notifications.add(
                                        notification,
                                        success, {},
                                        error, {}
                                    );
                                },
                                error: error
                            }
                        });
                    });

                    $action.append($icon);
                    $action.appendTo($buttons);
                });

                if (!visibleActions || !visibleActions.length) $actions.hide();

                return $actions;
            };

            var renderDialogContent = function(args) {
                var data = args.data ? args.data : {};

                // Setup default values, in case where existing data is present
                var setDefaultFields = function(fieldID, field) {
                    var fieldData = data[fieldID];

                    if (fieldData && !field.isBoolean) {
                        field.defaultValue = fieldData;
                    } else {
                        field.isChecked = fieldData;
                    }
                };
                $.each(topfields, setDefaultFields);
                $.each(bottomfields, setDefaultFields);

                $.extend(context, {
                    originalAutoscaleData: args.data
                });

                // Create and append top fields
                // -- uses create form to generate fields
                topFieldForm = cloudStack.dialog.createForm({
                    context: context,
                    noDialog: true, // Don't render a dialog, just return $formContainer
                    form: {
                        title: '',
                        fields: topfields
                    }
                });
                $topFieldForm = topFieldForm.$formContainer;
                $topFieldForm.appendTo($topFields);

                scaleUpPolicyTitleForm = cloudStack.dialog.createForm({
                    context: context,
                    noDialog: true,
                    form: {
                        title: '',
                        fields: {
                            scaleUpDuration: {
                                label: 'Duration(in sec)',
                                validation: {
                                    required: true
                                }
                            }
                        }
                    }
                });
                $scaleUpPolicyTitleForm = scaleUpPolicyTitleForm.$formContainer;
                $scaleUpPolicyTitleForm.appendTo($scaleUpPolicyTitle);

                scaleDownPolicyTitleForm = cloudStack.dialog.createForm({
                    context: context,
                    noDialog: true,
                    form: {
                        title: '',
                        fields: {
                            scaleDownDuration: {
                                label: 'Duration(in sec)',
                                validation: {
                                    required: true
                                }
                            }
                        }
                    }
                });
                $scaleDownPolicyTitleForm = scaleDownPolicyTitleForm.$formContainer;
                $scaleDownPolicyTitleForm.appendTo($scaleDownPolicyTitle);

                // Make multi-edits
                //
                // Scale up policy
                if (data.scaleUpPolicy && $.isArray(data.scaleUpPolicy.conditions)) {
                    $autoscalerDialog.data('autoscaler-scale-up-data',
                        data.scaleUpPolicy.conditions);
                }

                if (data.scaleUpPolicy && data.scaleUpPolicy.duration) {
                    $scaleUpPolicyTitleForm.find('input[name=scaleUpDuration]').val(
                        data.scaleUpPolicy.duration
                    );
                }

                scaleuppolicy.context = context;
                scaleUpPolicyForm = $scaleUpPolicy.multiEdit(scaleuppolicy);

                // Scale down policy
                if (data.scaleDownPolicy && $.isArray(data.scaleDownPolicy.conditions)) {
                    $autoscalerDialog.data('autoscaler-scale-down-data',
                        data.scaleDownPolicy.conditions);
                }

                if (data.scaleDownPolicy && data.scaleDownPolicy.duration) {
                    $scaleDownPolicyTitleForm.find('input[name=scaleDownDuration]').val(
                        data.scaleDownPolicy.duration
                    );
                }

                scaledownpolicy.context = context;
                scaleDownPolicyForm = $scaleDownPolicy.multiEdit(scaledownpolicy);

                // Create and append bottom fields
                bottomFieldForm = cloudStack.dialog.createForm({
                    context: context,
                    noDialog: true, // Don't render a dialog, just return $formContainer
                    form: {
                        title: '',
                        fields: bottomfields
                    }
                });
                $bottomFieldForm = bottomFieldForm.$formContainer;
                $bottomFieldForm.appendTo($bottomFields);

                // Append main div elements
                $autoscalerDialog.append(
                    $topFields,
                    $scaleUpPolicyTitle,
                    $scaleUpPolicy,
                    $scaleDownPolicyTitle,
                    $scaleDownPolicy,
                    $bottomFields
                );

                // Render dialog
                //$autoscalerDialog.find('.form-item[rel=templateNames] label').hide();
                /* Duration Fields*/
                //$('div.ui-dialog div.autoscaler').find('div.scale-up-policy-title').append("

").append($inputLabel = $('').html('Duration').attr({left:'200'})).append($('<input>').attr({ name: 'username' }));
                //$('div.ui-dialog div.autoscaler').find('div.scale-down-policy-title').append("

").append($inputLabel = $('').html('Duration').attr({left:'200'})).append($('<input>').attr({ name: 'username' }));

                /*Dividers*/
                $autoscalerDialog.find('div.scale-up-policy-title').prepend($scaleUpDivider);
                $autoscalerDialog.find('div.scale-down-policy-title').prepend($scaleDownDivider);
                $autoscalerDialog.find('div.field-group.bottom-fields').prepend($bottomFieldDivider);

                /* Hide effects for multi-edit table*/
                $autoscalerDialog.find('div.scale-up-policy').prepend($hideScaleUp);
                $autoscalerDialog.find('div.scale-down-policy ').prepend($hideScaleDown);
                $autoscalerDialog.find('div.scale-up-policy').prepend($scaleUpHideLabel);
                $autoscalerDialog.find('div.scale-down-policy').prepend($scaleDownHideLabel);

                /*Toggling the labels and data-item table - SCALE UP POLICY*/
                $autoscalerDialog.find('div.scale-up-policy div.hide').click(function() {
                    $autoscalerDialog.find('div.scale-up-policy div.multi-edit div.data-item').slideToggle();
                    $scaleUpLabel = $autoscalerDialog.find('div.scale-up-policy div.slide-label').replaceWith($scaleUpLabel);
                });

                /*Toggling the images */
                $('div.ui-dialog div.autoscaler div.scale-up-policy div.hide').click(function() {
                    $(this).toggleClass('expand hide');
                });

                $('div.ui-dialog div.autoscaler div.scale-down-policy div.hide').click(function() {
                    $(this).toggleClass('expand hide');
                });

                /*Toggling the labels and data-item table - SCALE DOWN POLICY*/
                $('div.ui-dialog div.autoscaler div.scale-down-policy div.hide').click(function() {
                    $('div.ui-dialog div.autoscaler div.scale-down-policy div.multi-edit div.data div.data-item').slideToggle();
                    $scaleDownLabel = $('div.ui-dialog div.autoscaler div.scale-down-policy div.slide-label').replaceWith($scaleDownLabel);
                });

                $('div.ui-dialog div.autoscaler div.scale-down-policy div.multi-edit div.data div.expand').click(function() {
                    $('div.ui-dialog div.autoscaler div.scale-down-policy div.multi-edit div.data div.data-item').slideToggle();
                });

                $autoscalerDialog.dialog('option', 'position', 'center');
                $autoscalerDialog.dialog('option', 'height', 'auto');

                // Setup actions
                renderActions(args).prependTo($autoscalerDialog);
            };

            var $loading = $('
').addClass('loading-overlay').appendTo($autoscalerDialog);
            $autoscalerDialog.dialog({
                title: 'AutoScale Configuration Wizard',
                width: 825,
                height: 600,
                draggable: true,
                closeonEscape: false,
                overflow: 'auto',
                open: function() {
                    $("button").each(function() {
                        $(this).attr("style", "left: 600px; position: relative; margin-right: 5px; ");
                    });
                },
                buttons: [{
                    text: _l('label.cancel'),
                    'class': 'cancel',
                    click: function() {
                        $autoscalerDialog.dialog('destroy');
                        $('.overlay').remove();
                    }
                }, {
                    text: _l('Apply'),
                    'class': 'ok',
                    click: function() {
                        var data = cloudStack.serializeForm($('.ui-dialog .autoscaler form'));

                        // Fix for missing formData, when editing existing rules;
                        if (!formData) formData = data;

                        // Pass VPC data
                        if (formData.tier) {
                            data.tier = formData.tier;
                        }

                        $loading.appendTo($autoscalerDialog);
                        cloudStack.autoscaler.actions.apply({
                            formData: formData,
                            context: context,
                            data: data,
                            response: {
                                success: function() {
                                    $loading.remove();
                                    $autoscalerDialog.dialog('destroy');
                                    $autoscalerDialog.closest(':ui-dialog').remove();
                                    $('.overlay').remove();
                                    cloudStack.dialog.notice({
                                        message: 'Autoscale configured successfully.'
                                    });
                                },
                                error: function(message) {
                                    cloudStack.dialog.notice({
                                        message: message
                                    });
                                    $loading.remove();
                                }
                            }
                        });
                    }
                }]
            }).closest('.ui-dialog').overlay();

            dataProvider({
                context: context,
                response: {
                    success: function(args) {
                        $loading.remove();
                        renderDialogContent(args);

                        if (args.data == null) { //from a new LB rule
                            $autoscalerDialog.find('select[name=serviceOfferingId]').removeAttr('disabled');
                            $autoscalerDialog.find('select[name=securityGroups]').removeAttr('disabled');
                            $autoscalerDialog.find('select[name=diskOfferingId]').removeAttr('disabled');
                        } else { //from an existing LB rule
                            $autoscalerDialog.find('select[name=serviceOfferingId]').attr('disabled', true);
                            $autoscalerDialog.find('select[name=securityGroups]').attr('disabled', true);
                            $autoscalerDialog.find('select[name=diskOfferingId]').attr('disabled', true);

                            if (args.data.isAdvanced != null) {
                                $autoscalerDialog.find('input[type=checkbox]').trigger('click');
                                $autoscalerDialog.find('input[type=checkbox]').attr('checked', 'checked');
                            }
                        }
                    }
                }
            });
        };
    };
}(jQuery, cloudStack));
<|endoftext|>


<commit_before>/**
 * @fileOverview Allows to work with deferred values that will be resolved in the future.
 */

export("Deferred", "PromiseList");

var NEW = 0;
var FULFILLED = 1;
var FAILED = 2;

/**
 * Creates an object representing a deferred value.
 * The deferred object has two properties: a [promise](#Promise)
 * object and a [resolve()](#Deferred.prototype.resolve) function.
 *
 * The promise object can be used to [register a callback](#Promise.prototype.then)
 * to be invoked when the promise is eventually resolved.
 *
 * The [resolve](#Deferred.prototype.resolve) function is used to resolve the
 * promise as either fulfilled or failed.
 *
 * @constructor
 * @example
 * // Example for an asynchronous JSGI response.
 * // The response is generated after a one second delay.
 * exports.asyncAction = function(request) {
 *   var response = new Deferred();
 *   setTimeout(function() {
 *       response.resolve({
 *           status: 200, headers: {}, body: ["Delayed"]
 *       });
 *   }, 1000);
 *   return response.promise;
 * }
 */
function Deferred() {
    var value;
    var listeners = [];
    var state = NEW;
    var lock = new java.lang.Object();

    /**
     * Resolve the promise.
     * @name Deferred.prototype.resolve
     * @param {Object} result the result or error value
     * @param {Boolean} isError if true the promise is resolved as failed
     * @type Function
     */
    var resolve = sync(function(result, isError) {
        if (state !== NEW) {
            throw new Error("Promise has already been resolved.");
        }
        value = result;
        state = isError ? FAILED : FULFILLED;
        listeners.forEach(notify);
        listeners = [];
        lock.notifyAll();
    }, lock);

    var notify = function(listener) {
        var isError = (state === FAILED);
        var callback = isError ? listener.errback : listener.callback;
        var result = value;

        if (callback) {
            try {
                result = callback(value);
            } catch (error) {
                result = error;
                isError = true;
            }
        }
        // If result is a promise, chain its result to our existing promise. Else resolve directly.
        if (result != null && typeof result.then === 'function') {
            result.then(function(val) {
                listener.tail.resolve(val, isError);
            }, function(err) {
                listener.tail.resolve(err, true);
            });
        } else {
            listener.tail.resolve(result, isError);
        }
    };

    /**
     * The promise object can be used to [register a callback](#Promise.prototype.then)
     * to be invoked when the promise is eventually resolved.
     * @name Deferred.prototype.promise
     */
    var promise = {
        /**
         * Register callback and errback functions to be invoked when
         * the promise is resolved.
         * @name Promise.prototype.then
         * @param {Function} callback called if the promise is resolved as fulfilled
         * @param {Function} errback called if the promise is resolved as failed
         * @return {Object} a new promise that resolves to the return value of the
         *     callback or errback when it is called.
         */
        then: sync(function(callback, errback) {
            if (typeof callback !== "function") {
                throw new Error("First argument to then() must be a function.");
            }
            var tail = new Deferred();
            var listener = {
                tail: tail,
                callback: callback,
                errback: errback
            };
            if (state === NEW) {
                listeners.push(listener);
            } else {
                notify(listener);
            }
            return tail.promise;
        }, lock),

        /**
         * Wait for the promise to be resolved.
         * @name Promise.prototype.wait
         * @param {Number} timeout optional time in milliseconds to wait for.
         *                 If timeout is undefined wait() blocks forever.
         * @return {Object} the value if the promise is resolved as fulfilled
         * @throws Object the error value if the promise is resolved as failed
         */
        wait: sync(function(timeout) {
            if (state === NEW) {
                if (typeof timeout === "undefined") {
                    lock.wait();
                } else {
                    lock.wait(timeout);
                }
            }
            if (state === FAILED) {
                throw value;
            } else {
                return value;
            }
        }, lock)
    };

    return {
        resolve: resolve,
        promise: promise
    };
}

/**
 * The PromiseList class allows to combine several promises into one.
 * It represents itself a promise that resolves to an array of objects,
 * each containing a `value` or `error` property with the value
 * or error of the corresponding promise argument.
 *
 * A PromiseList resolves successfully even if some or all of the partial
 * promises resolve to an error. It is the responsibility of the handler
 * function to check each individual promise result.
 *
 * @param {Promise...} promise... any number of promise arguments.
 * @constructor
 */
function PromiseList(args) {
    var promises = Array.isArray(args) ? args : Array.prototype.slice.call(arguments);
    var count = new java.util.concurrent.atomic.AtomicInteger(promises.length);
    var results = [];
    var i = 0;
    var deferred = new Deferred();

    promises.forEach(function(promise) {
        if (typeof promise.then !== "function" && promise.promise) {
            promise = promise.promise;
        }
        var index = i++;
        promise.then(
            sync(function(value) {
                results[index] = {value: value};
                if (count.decrementAndGet() == 0) {
                    deferred.resolve(results);
                }
            }, count),
            sync(function(error) {
                results[index] = {error: error};
                if (count.decrementAndGet() == 0) {
                    deferred.resolve(results);
                }
            }, count)
        );
    });
    return deferred.promise;
}

/**
 * A promise object. This class is not exported, create a
 * [deferred object](#Deferred) to create a promise.
 * @name Promise
 */<commit_msg>Removes outdated promise exampe<commit_after>/**
 * @fileOverview Allows to work with deferred values that will be resolved in the future.
 */

export("Deferred", "PromiseList");

var NEW = 0;
var FULFILLED = 1;
var FAILED = 2;

/**
 * Creates an object representing a deferred value.
 * The deferred object has two properties: a [promise](#Promise)
 * object and a [resolve()](#Deferred.prototype.resolve) function.
 *
 * The promise object can be used to [register a callback](#Promise.prototype.then)
 * to be invoked when the promise is eventually resolved.
 *
 * The [resolve](#Deferred.prototype.resolve) function is used to resolve the
 * promise as either fulfilled or failed.
 *
 * @constructor
 * @example
 * // --- sample output ---
 * // getDeferredValue() ...
 * // getDeferredValue() finished after 396 ms
 * // Resolved promise, got foo
 * // Total time:  2400 ms
 *
 * const getDeferredValue = function () {
 *   const deferred = new Deferred();
 *   setTimeout(function() {
 *     deferred.resolve("foo");
 *   }, 2000);
 *
 *   return deferred;
 * };
 *
 * const start = Date.now();
 * console.log("getDeferredValue() ... ");
 *
 * const def = getDeferredValue();
 * console.log("getDeferredValue() finished after",
 *   Date.now() - start, "ms");
 *
 * def.promise.then(function(value) {
 *   console.log("Resolved promise, got", value);
 *   console.log("Total time: ", Date.now() - start, "ms");
 * });
 */
function Deferred() {
    var value;
    var listeners = [];
    var state = NEW;
    var lock = new java.lang.Object();

    /**
     * Resolve the promise.
     * @name Deferred.prototype.resolve
     * @param {Object} result the result or error value
     * @param {Boolean} isError if true the promise is resolved as failed
     * @type Function
     */
    var resolve = sync(function(result, isError) {
        if (state !== NEW) {
            throw new Error("Promise has already been resolved.");
        }
        value = result;
        state = isError ? FAILED : FULFILLED;
        listeners.forEach(notify);
        listeners = [];
        lock.notifyAll();
    }, lock);

    var notify = function(listener) {
        var isError = (state === FAILED);
        var callback = isError ? listener.errback : listener.callback;
        var result = value;

        if (callback) {
            try {
                result = callback(value);
            } catch (error) {
                result = error;
                isError = true;
            }
        }
        // If result is a promise, chain its result to our existing promise. Else resolve directly.
        if (result != null && typeof result.then === 'function') {
            result.then(function(val) {
                listener.tail.resolve(val, isError);
            }, function(err) {
                listener.tail.resolve(err, true);
            });
        } else {
            listener.tail.resolve(result, isError);
        }
    };

    /**
     * The promise object can be used to [register a callback](#Promise.prototype.then)
     * to be invoked when the promise is eventually resolved.
     * @name Deferred.prototype.promise
     */
    var promise = {
        /**
         * Register callback and errback functions to be invoked when
         * the promise is resolved.
         * @name Promise.prototype.then
         * @param {Function} callback called if the promise is resolved as fulfilled
         * @param {Function} errback called if the promise is resolved as failed
         * @return {Object} a new promise that resolves to the return value of the
         *     callback or errback when it is called.
         */
        then: sync(function(callback, errback) {
            if (typeof callback !== "function") {
                throw new Error("First argument to then() must be a function.");
            }
            var tail = new Deferred();
            var listener = {
                tail: tail,
                callback: callback,
                errback: errback
            };
            if (state === NEW) {
                listeners.push(listener);
            } else {
                notify(listener);
            }
            return tail.promise;
        }, lock),

        /**
         * Wait for the promise to be resolved.
         * @name Promise.prototype.wait
         * @param {Number} timeout optional time in milliseconds to wait for.
         *                 If timeout is undefined wait() blocks forever.
         * @return {Object} the value if the promise is resolved as fulfilled
         * @throws Object the error value if the promise is resolved as failed
         */
        wait: sync(function(timeout) {
            if (state === NEW) {
                if (typeof timeout === "undefined") {
                    lock.wait();
                } else {
                    lock.wait(timeout);
                }
            }
            if (state === FAILED) {
                throw value;
            } else {
                return value;
            }
        }, lock)
    };

    return {
        resolve: resolve,
        promise: promise
    };
}

/**
 * The PromiseList class allows to combine several promises into one.
 * It represents itself a promise that resolves to an array of objects,
 * each containing a `value` or `error` property with the value
 * or error of the corresponding promise argument.
 *
 * A PromiseList resolves successfully even if some or all of the partial
 * promises resolve to an error. It is the responsibility of the handler
 * function to check each individual promise result.
 *
 * @param {Promise...} promise... any number of promise arguments.
 * @constructor
 */
function PromiseList(args) {
    var promises = Array.isArray(args) ? args : Array.prototype.slice.call(arguments);
    var count = new java.util.concurrent.atomic.AtomicInteger(promises.length);
    var results = [];
    var i = 0;
    var deferred = new Deferred();

    promises.forEach(function(promise) {
        if (typeof promise.then !== "function" && promise.promise) {
            promise = promise.promise;
        }
        var index = i++;
        promise.then(
            sync(function(value) {
                results[index] = {value: value};
                if (count.decrementAndGet() == 0) {
                    deferred.resolve(results);
                }
            }, count),
            sync(function(error) {
                results[index] = {error: error};
                if (count.decrementAndGet() == 0) {
                    deferred.resolve(results);
                }
            }, count)
        );
    });
    return deferred.promise;
}

/**
 * A promise object. This class is not exported, create a
 * [deferred object](#Deferred) to create a promise.
 * @name Promise
 */<|endoftext|>


<commit_before>import actionTypes from '../action-types';
import { getGemFromChallengeErrors } from './helpers/gems-helper';

const initialState = [];

/**
 * gems is the list of all gems the user has been awarded.
 */
export default function gems(state = initialState, challengeErrors, routeSpec, action) {
  switch(action.type) {
    case actionTypes.CHALLENGE_COMPLETED:
    case actionTypes.CHALLENGE_RETRIED: {
        let { level, mission, challenge } = routeSpec;
        if (!state[level]) {
          state = state.setIn([level], []);
        }
        if (!state[level][mission]) {
          state = state.setIn([level, mission], []);
        }

        let gem = getGemFromChallengeErrors(challengeErrors);
        state = state.setIn([level, mission, challenge], gem);

        return state;
      }
    case actionTypes.LOAD_SAVED_STATE: {
      if (action.gems && action.gems.length > 0) {
        return action.gems;
      } else {
        return state;
      }
    }
    default:
      return state;
  }
}
<commit_msg>Fixes challenge-won't-end issue<commit_after>import actionTypes from '../action-types';
import { getGemFromChallengeErrors } from './helpers/gems-helper';

const initialState = [];

/**
 * gems is the list of all gems the user has been awarded.
 */
export default function gems(state = initialState, challengeErrors, routeSpec, action) {
  switch(action.type) {
    case actionTypes.CHALLENGE_COMPLETED:
    case actionTypes.CHALLENGE_RETRIED: {
        let { level: currLevel, mission: currMission, challenge: currChallenge } = routeSpec;
        for (let level = 0; level <= currLevel; level++) {
          if (!state[level]) {
            state = state.setIn([level], []);
          }
        }
        for (let mission = 0; mission <= currMission; mission++) {
          if (!state[currLevel][mission]) {
            state = state.setIn([currLevel, mission], []);
          }
        }

        let gem = getGemFromChallengeErrors(challengeErrors);
        state = state.setIn([currLevel, currMission, currChallenge], gem);

        return state;
      }
    case actionTypes.LOAD_SAVED_STATE: {
      if (action.gems && action.gems.length > 0) {
        return action.gems;
      } else {
        return state;
      }
    }
    default:
      return state;
  }
}
<|endoftext|>


<commit_before>define(["Tone/effect/AutoPanner", "helper/Basic", "helper/EffectTests"], function (AutoPanner, Basic, EffectTests) {

	describe("AutoPanner", function(){
		Basic(AutoPanner);
		EffectTests(AutoPanner);

		context("API", function(){

			it ("can pass in options in the constructor", function(){
				var autoPanner = new AutoPanner({
					"type" : "sawtooth",
					"depth" : 0.2
				});
				expect(autoPanner.depth.value).to.be.closeTo(0.2, 0.01);
				expect(autoPanner.type).to.equal("sawtooth");
				autoPanner.dispose();
			});

			it ("can be started and stopped", function(){
				var autoPanner = new AutoPanner();
				autoPanner.start().stop("+0.2");
				autoPanner.dispose();
			});

			it ("can get/set the options", function(){
				var autoPanner = new AutoPanner();
				autoPanner.set({
					"frequency" : 2.4,
					"type" : "triangle"
				});
				expect(autoPanner.get().frequency).to.be.closeTo(2.4, 0.01);
				expect(autoPanner.get().type).to.equal("triangle");
				autoPanner.dispose();
			});

			it ("can set the frequency and depth", function(){
				var autoPanner = new AutoPanner();
				autoPanner.depth.value = 0.4;
				autoPanner.frequency.value = 0.4;
				expect(autoPanner.depth.value).to.be.closeTo(0.4, 0.01);
				expect(autoPanner.frequency.value).to.be.closeTo(0.4, 0.01);
				autoPanner.dispose();
			});
		});
	});
});<commit_msg>testing AutoPanner syncing/unsyncing<commit_after>define(["Tone/effect/AutoPanner", "helper/Basic", "helper/EffectTests", "helper/Offline"],
function (AutoPanner, Basic, EffectTests, Offline) {

	describe("AutoPanner", function(){
		Basic(AutoPanner);
		EffectTests(AutoPanner);

		context("API", function(){

			it ("can pass in options in the constructor", function(){
				var autoPanner = new AutoPanner({
					"type" : "sawtooth",
					"depth" : 0.2
				});
				expect(autoPanner.depth.value).to.be.closeTo(0.2, 0.01);
				expect(autoPanner.type).to.equal("sawtooth");
				autoPanner.dispose();
			});

			it ("can be started and stopped", function(){
				var autoPanner = new AutoPanner();
				autoPanner.start().stop("+0.2");
				autoPanner.dispose();
			});

			it ("can get/set the options", function(){
				var autoPanner = new AutoPanner();
				autoPanner.set({
					"frequency" : 2.4,
					"type" : "triangle"
				});
				expect(autoPanner.get().frequency).to.be.closeTo(2.4, 0.01);
				expect(autoPanner.get().type).to.equal("triangle");
				autoPanner.dispose();
			});

			it ("can set the frequency and depth", function(){
				var autoPanner = new AutoPanner();
				autoPanner.depth.value = 0.4;
				autoPanner.frequency.value = 0.4;
				expect(autoPanner.depth.value).to.be.closeTo(0.4, 0.01);
				expect(autoPanner.frequency.value).to.be.closeTo(0.4, 0.01);
				autoPanner.dispose();
			});

			it ("can sync the frequency to the Transport", function(){
				return Offline(function(Transport){
					var panner = new AutoPanner(2);
					panner.sync();
					panner.frequency.toMaster();
					Transport.bpm.setValueAtTime(Transport.bpm.value * 2, 0.05);
					// Transport.start(0)
				}, 0.1).then(function(buffer){
					expect(buffer.getValueAtTime(0)).to.be.closeTo(2, 0.1);
					expect(buffer.getValueAtTime(0.05)).to.be.closeTo(4, 0.1);
				});
			});

			it ("can unsync the frequency to the Transport", function(){
				return Offline(function(Transport){
					var panner = new AutoPanner(2);
					panner.sync();
					panner.frequency.toMaster();
					Transport.bpm.setValueAtTime(Transport.bpm.value * 2, 0.05);
					panner.unsync();
					// Transport.start(0)
				}, 0.1).then(function(buffer){
					expect(buffer.getValueAtTime(0)).to.be.closeTo(2, 0.1);
					expect(buffer.getValueAtTime(0.05)).to.be.closeTo(2, 0.1);
				});
			});
		});
	});
});
<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');

var Field = require('./Field');

var STATE_HEALTHY = 0;
var STATE_WOUNDED = 1;
var STATE_SUNK = 2;

var ORIENTATION_UNKNOWN = 'unknown';
var ORIENTATION_HORIZONTAL = 'horizontal';
var ORIENTATION_VERTICAL = 'vertical';

class Ship {
    constructor(fields) {
        fields = fields || [];
        this._fields = fields;
    }

    getLength () {
        return this._fields.length;
    }

    getOrientation () {
        if (this._fields.length <= 1) {
            // we do not know anything about the orientation if there is only 1 or less known fields
            return ORIENTATION_UNKNOWN;
        }

        var values = {
            horizontal: [],
            vertical: []
        };

        this._fields.forEach(field => {
            values.vertical.push(field.getY());
            values.horizontal.push(field.getX());
        });

        if (_.uniq(values.vertical).length === 1) {
            return ORIENTATION_VERTICAL;
        } else if (_.uniq(values.horizontal).length === 1) {
            return ORIENTATION_HORIZONTAL;
        }

        throw new Error('invalid ship position, ship has multiple horizontal AND vertical values');
    }

    /**
     * @param {Field} field
     * @returns {Ship}
     */
    addField(field) {
        this._fields.push(field);

        return this;
    }

    getState () {
        // if we do not know about the fields, the ship is healthy
        if (!this._fields.length) {
            return STATE_HEALTHY;
        }

        var fieldsByState = {
            hit : [],
            healthy : []
        };

        this._fields.forEach(field => {
            if (field.getState() > Field.STATE.UNKNOWN) {
                fieldsByState.hit.push(field);
            } else {
                fieldsByState.healthy.push(field);
            }
        });

        if (!fieldsByState.healthy.length) {
            // no healthy fields means the ship is sunk
            return STATE_SUNK;
        } else if (!fieldsByState.hit.length) {
            // no hit fields means the ship is healthy
            return STATE_HEALTHY;
        }

        // anything between these states means the ship is wounded
        return STATE_WOUNDED;
    }
}

Ship.STATE = {
    HEALTHY: STATE_HEALTHY,
    WOUNDED: STATE_WOUNDED,
    SUNK: STATE_SUNK
};

Ship.ORIENTATION = {
    VERTICAL : ORIENTATION_VERTICAL,
    HORIZONTAL : ORIENTATION_HORIZONTAL,
    UNKNOWN : ORIENTATION_UNKNOWN
};

module.exports = Ship;<commit_msg>add method getFields<commit_after>'use strict';

var _ = require('lodash');

var Field = require('./Field');

var STATE_HEALTHY = 0;
var STATE_WOUNDED = 1;
var STATE_SUNK = 2;

var ORIENTATION_UNKNOWN = 'unknown';
var ORIENTATION_HORIZONTAL = 'horizontal';
var ORIENTATION_VERTICAL = 'vertical';

class Ship {
    constructor(fields) {
        fields = fields || [];
        this._fields = fields;
    }

    getLength () {
        return this._fields.length;
    }

    getOrientation () {
        if (this._fields.length <= 1) {
            // we do not know anything about the orientation if there is only 1 or less known fields
            return ORIENTATION_UNKNOWN;
        }

        var values = {
            horizontal: [],
            vertical: []
        };

        this._fields.forEach(field => {
            values.vertical.push(field.getY());
            values.horizontal.push(field.getX());
        });

        if (_.uniq(values.vertical).length === 1) {
            return ORIENTATION_VERTICAL;
        } else if (_.uniq(values.horizontal).length === 1) {
            return ORIENTATION_HORIZONTAL;
        }

        throw new Error('invalid ship position, ship has multiple horizontal AND vertical values');
    }

    getFields () {
        return this._fields;
    }

    /**
     * @param {Field} field
     * @returns {Ship}
     */
    addField(field) {
        this._fields.push(field);

        return this;
    }

    getState () {
        // if we do not know about the fields, the ship is healthy
        if (!this._fields.length) {
            return STATE_HEALTHY;
        }

        var fieldsByState = {
            hit : [],
            healthy : []
        };

        this._fields.forEach(field => {
            if (field.getState() > Field.STATE.UNKNOWN) {
                fieldsByState.hit.push(field);
            } else {
                fieldsByState.healthy.push(field);
            }
        });

        if (!fieldsByState.healthy.length) {
            // no healthy fields means the ship is sunk
            return STATE_SUNK;
        } else if (!fieldsByState.hit.length) {
            // no hit fields means the ship is healthy
            return STATE_HEALTHY;
        }

        // anything between these states means the ship is wounded
        return STATE_WOUNDED;
    }
}

Ship.STATE = {
    HEALTHY: STATE_HEALTHY,
    WOUNDED: STATE_WOUNDED,
    SUNK: STATE_SUNK
};

Ship.ORIENTATION = {
    VERTICAL : ORIENTATION_VERTICAL,
    HORIZONTAL : ORIENTATION_HORIZONTAL,
    UNKNOWN : ORIENTATION_UNKNOWN
};

module.exports = Ship;<|endoftext|>