<commit_before>// @flow

import passport from "passport";
import { Strategy as SamlStrategy } from "passport-saml";
import fs from "fs";
import path from "path";
import User from "../../api/user/user.model";

const uid = "urn:oid:0.9.2342.19200300.100.1.1";
const groupMembership = "urn:oid:2.16.840.1.113719.1.1.4.1.25";
const firstName = "urn:oid:2.5.4.42";
const lastName = "urn:oid:2.5.4.4";
const displayName = "urn:oid:2.16.840.1.113730.3.1.241";
const studentNumber = "urn:mace:dir:attribute-def:ubcEduStudentNumber";
const employeeNumber = "urn:oid:2.16.840.1.113730.3.1.3";

const memberOfInstructors =
  "cn=instructors,ou=comped.cs.ubc.ca,ou=applications,ou=cpsc-ubcv,ou=clients,dc=id,dc=ubc,dc=ca";
const memberOfTeachingAssistants =
  "cn=teaching-assistants,ou=comped.cs.ubc.ca,ou=applications,ou=cpsc-ubcv,ou=clients,dc=id,dc=ubc,dc=ca";

// ***** Change these next two variables as needed for the current term *****
const currentAPSC160Sections = ["101", "102", "VE1", "VE2"];
const currentAPSC160Session = "2018W";

const memberOfAPSC160StudentsRegexPatterns = currentAPSC160Sections.map(section => {
  const regexpString = "cn=apsc_160_" + section + "_" + currentAPSC160Session + ",ou=apsc,ou=ubc,ou=academic,dc=id,dc=ubc,dc=ca";
  return new RegExp(regexpString, "i");
});
const memberOfAPSC160StudentsRegex = /cn=apsc_160_[A-KM-SU-Za-km-su-z\d][A-KM-SU-Za-km-su-z\d]\w_\w+,ou=apsc,ou=ubc,ou=academic,dc=id,dc=ubc,dc=ca/i;

passport.serializeUser((user, done) => {
  //console.log("serialize user", user);
  return done(null, user._id);
});

passport.deserializeUser(async (userId, done) => {
  //console.log("deserialize user", userId);
  try {
    const user = await User.findById(userId);
    if (!user) {
      return done(null, false);
    } else {
      //console.log("deserialized user", user);
      return done(null, user);
    }
  } catch (err) {
    return done(err);
  }
});

const keyContents = fs.readFileSync(
  path.join(__dirname, "../../cert/key.pem"),
  "utf8"
);

/* Checks if `str` appears in array `arr`, ignoring case.
Useful for doing case-insensitive LDAP comparisons.
*/
function caseInsensitiveIncludes(arr, str) {
  return arr.some(s => s.toUpperCase() === str.toUpperCase());
}

const samlStrategy = new SamlStrategy(
  {
    // URL that goes from the Identity Provider -> Service Provider
    callbackUrl: "https://comped.cs.ubc.ca/auth/cwl/login/callback",
    // URL that goes from the Service Provider -> Identity Provider
    entryPoint: "https://authentication.ubc.ca/idp/profile/SAML2/Redirect/SSO",
    // Usually specified as `/shibboleth` from site root
    issuer: "https://comped.cs.ubc.ca",
    identifierFormat: null,
    // Service Provider private key
    decryptionPvk: keyContents,
    // Service Provider Certificate
    privateCert: keyContents,
    // Identity Provider's public key
    cert: fs.readFileSync(
      path.join(__dirname, "../../cert/idp_cert.pem"),
      "utf8"
    ),
    validateInResponseTo: false,
    disableRequestedAuthnContext: true
  },
  async (profile, done) => {
    let role;
    let section, termInt, session, yearInt;
    // `groupMembership` should be an array of LDAP strings
    if (profile.hasOwnProperty(groupMembership)) {
      if (caseInsensitiveIncludes(profile[groupMembership], memberOfInstructors)) {
        role = "instructor";
      } else if (caseInsensitiveIncludes(profile[groupMembership], memberOfTeachingAssistants)) {
        role = "teaching-assistant";
      } else {
        // Check if `groupMembership` contains an APSC 160 student LDAP string
        let studentLDAPstring = null;
        for (let pattern of memberOfAPSC160StudentsRegexPatterns) {
          let studentLDAPstring = profile[groupMembership].find(s => pattern.test(s));
          if (studentLDAPstring) {
            break;
          }
        }
        if (!studentLDAPstring) {
          // Unauthorized to access app
          return done(null, false, {
            message: "You are not registered in APSC 160"
          });
        } else {
          role = "student";

          try {
            // Parse `studentLDAPstring` for section / term / session / year.
            // Putting this in a try block because it's hard to test, so this is in case it fails.
            const cnString = studentLDAPstring.split(",")[0];  // e.g. cn=APSC_110T_601_2015W
            const cnStringWithoutCN = cnString.split("=")[1];  // e.g. APSC_110T_601_2015W
            const courseSectionGroupArray = cnStringWithoutCN.split("_");  // e.g. ["APSC", "110T", "601", "2015W"]
            section = courseSectionGroupArray[2];  // e.g. "601"
            session = courseSectionGroupArray[3];  // e.g. "2015W" or "2015W2"
            const yearStr = session.match(/\d+/i)[0];  // e.g. "2015"
            const termStr = session.split(/[WS]/i)[1] || null;  // e.g. null or "2"
            yearInt = Number(yearStr);
            termInt = Number(termStr) || null;
          } catch (err) {
            console.error("Error determining section / term / session / year: ", err);
          }

          // Temp: Restrict access to students.
          // return done(null, false, {
          //   message: "Students are currently not allowed to access BitFit."
          // });
        }
      }
    } else {
      // Unauthorized to access app
      return done(null, false, {
        message: "You are not registered in APSC 160"
      });
    }

    User.findOne(
      {
        // Check CWL login name
        uid: profile[uid]
      },
      (err, user) => {
        if (err) return done(err);

        if (!user) {
          // create new user account
          user = new User({ uid: profile[uid] });
        }
        user.firstName = profile[firstName];
        user.lastName = profile[lastName];
        user.displayName = profile[displayName];
        user.role = role;
        user.studentNumber = profile[studentNumber];
        user.employeeNumber = profile[employeeNumber];
        user.section = section;
        user.term = termInt;
        user.session = session;
        user.year = yearInt;

        user.save((err, user) => {
          if (err) return done(err);
          return done(null, user);
        });
      }
    );
  }
);

passport.use(samlStrategy);

export default samlStrategy;
<commit_msg>Remove current APSC 160 sections so that no students can access BitFit<commit_after>// @flow

import passport from "passport";
import { Strategy as SamlStrategy } from "passport-saml";
import fs from "fs";
import path from "path";
import User from "../../api/user/user.model";

const uid = "urn:oid:0.9.2342.19200300.100.1.1";
const groupMembership = "urn:oid:2.16.840.1.113719.1.1.4.1.25";
const firstName = "urn:oid:2.5.4.42";
const lastName = "urn:oid:2.5.4.4";
const displayName = "urn:oid:2.16.840.1.113730.3.1.241";
const studentNumber = "urn:mace:dir:attribute-def:ubcEduStudentNumber";
const employeeNumber = "urn:oid:2.16.840.1.113730.3.1.3";

const memberOfInstructors =
  "cn=instructors,ou=comped.cs.ubc.ca,ou=applications,ou=cpsc-ubcv,ou=clients,dc=id,dc=ubc,dc=ca";
const memberOfTeachingAssistants =
  "cn=teaching-assistants,ou=comped.cs.ubc.ca,ou=applications,ou=cpsc-ubcv,ou=clients,dc=id,dc=ubc,dc=ca";

// ***** Change these next two variables as needed for the current term *****
const currentAPSC160Sections = [];
const currentAPSC160Session = "2018W";

const memberOfAPSC160StudentsRegexPatterns = currentAPSC160Sections.map(section => {
  const regexpString = "cn=apsc_160_" + section + "_" + currentAPSC160Session + ",ou=apsc,ou=ubc,ou=academic,dc=id,dc=ubc,dc=ca";
  return new RegExp(regexpString, "i");
});
const memberOfAPSC160StudentsRegex = /cn=apsc_160_[A-KM-SU-Za-km-su-z\d][A-KM-SU-Za-km-su-z\d]\w_\w+,ou=apsc,ou=ubc,ou=academic,dc=id,dc=ubc,dc=ca/i;

passport.serializeUser((user, done) => {
  //console.log("serialize user", user);
  return done(null, user._id);
});

passport.deserializeUser(async (userId, done) => {
  //console.log("deserialize user", userId);
  try {
    const user = await User.findById(userId);
    if (!user) {
      return done(null, false);
    } else {
      //console.log("deserialized user", user);
      return done(null, user);
    }
  } catch (err) {
    return done(err);
  }
});

const keyContents = fs.readFileSync(
  path.join(__dirname, "../../cert/key.pem"),
  "utf8"
);

/* Checks if `str` appears in array `arr`, ignoring case.
Useful for doing case-insensitive LDAP comparisons.
*/
function caseInsensitiveIncludes(arr, str) {
  return arr.some(s => s.toUpperCase() === str.toUpperCase());
}

const samlStrategy = new SamlStrategy(
  {
    // URL that goes from the Identity Provider -> Service Provider
    callbackUrl: "https://comped.cs.ubc.ca/auth/cwl/login/callback",
    // URL that goes from the Service Provider -> Identity Provider
    entryPoint: "https://authentication.ubc.ca/idp/profile/SAML2/Redirect/SSO",
    // Usually specified as `/shibboleth` from site root
    issuer: "https://comped.cs.ubc.ca",
    identifierFormat: null,
    // Service Provider private key
    decryptionPvk: keyContents,
    // Service Provider Certificate
    privateCert: keyContents,
    // Identity Provider's public key
    cert: fs.readFileSync(
      path.join(__dirname, "../../cert/idp_cert.pem"),
      "utf8"
    ),
    validateInResponseTo: false,
    disableRequestedAuthnContext: true
  },
  async (profile, done) => {
    let role;
    let section, termInt, session, yearInt;
    // `groupMembership` should be an array of LDAP strings
    if (profile.hasOwnProperty(groupMembership)) {
      if (caseInsensitiveIncludes(profile[groupMembership], memberOfInstructors)) {
        role = "instructor";
      } else if (caseInsensitiveIncludes(profile[groupMembership], memberOfTeachingAssistants)) {
        role = "teaching-assistant";
      } else {
        // Check if `groupMembership` contains an APSC 160 student LDAP string
        let studentLDAPstring = null;
        for (let pattern of memberOfAPSC160StudentsRegexPatterns) {
          let studentLDAPstring = profile[groupMembership].find(s => pattern.test(s));
          if (studentLDAPstring) {
            break;
          }
        }
        if (!studentLDAPstring) {
          // Unauthorized to access app
          return done(null, false, {
            message: "You are not registered in APSC 160"
          });
        } else {
          role = "student";

          try {
            // Parse `studentLDAPstring` for section / term / session / year.
            // Putting this in a try block because it's hard to test, so this is in case it fails.
            const cnString = studentLDAPstring.split(",")[0];  // e.g. cn=APSC_110T_601_2015W
            const cnStringWithoutCN = cnString.split("=")[1];  // e.g. APSC_110T_601_2015W
            const courseSectionGroupArray = cnStringWithoutCN.split("_");  // e.g. ["APSC", "110T", "601", "2015W"]
            section = courseSectionGroupArray[2];  // e.g. "601"
            session = courseSectionGroupArray[3];  // e.g. "2015W" or "2015W2"
            const yearStr = session.match(/\d+/i)[0];  // e.g. "2015"
            const termStr = session.split(/[WS]/i)[1] || null;  // e.g. null or "2"
            yearInt = Number(yearStr);
            termInt = Number(termStr) || null;
          } catch (err) {
            console.error("Error determining section / term / session / year: ", err);
          }

          // Temp: Restrict access to students.
          // return done(null, false, {
          //   message: "Students are currently not allowed to access BitFit."
          // });
        }
      }
    } else {
      // Unauthorized to access app
      return done(null, false, {
        message: "You are not registered in APSC 160"
      });
    }

    User.findOne(
      {
        // Check CWL login name
        uid: profile[uid]
      },
      (err, user) => {
        if (err) return done(err);

        if (!user) {
          // create new user account
          user = new User({ uid: profile[uid] });
        }
        user.firstName = profile[firstName];
        user.lastName = profile[lastName];
        user.displayName = profile[displayName];
        user.role = role;
        user.studentNumber = profile[studentNumber];
        user.employeeNumber = profile[employeeNumber];
        user.section = section;
        user.term = termInt;
        user.session = session;
        user.year = yearInt;

        user.save((err, user) => {
          if (err) return done(err);
          return done(null, user);
        });
      }
    );
  }
);

passport.use(samlStrategy);

export default samlStrategy;
<|endoftext|>


<commit_before>/**
 * js/com/Element.js
 * Author: H.Alper Tuna <halpertuna@gmail.com>
 * Date: 12.04.2016
 */

'use strict';

define(['../core/EventHandler', '../core/Utils', './TextElement'], function(EventHandler, Utils, TextElement){
    return EventHandler.extend({
        'init': function(tag){
            if(!tag) tag = 'div';

            this.set('dom', Utils.isString(tag) ? document.createElement(tag) : tag);
            this.set('children', []);
            this.handle('show');
            this.handle('hide');
            this.on('show', function(){this.removeClass('jb-hidden')});
            this.on('hide', function(){this.addClass('jb-hidden')});
        },

        /*
         * Attribute Managing
         *===========================================================*/
        'getAttr': function(attr){
            return this.getDom().getAttribute(attr);
        },
        'setAttr':  function(attr, value){
            this.getDom().setAttribute(attr, value);
            return this.ref;
        },
        'removeAttr': function(attr){
            this.getDom().removeAttribute(attr);
            return this.ref;
        },
        'getStyle': function(key){
            var styles = this.getAttr('style');
            if(styles == null) return;

            styles = styles.split(';');
            for(var i in styles){
                if(styles[i] == '') continue;
                var style = styles[i].split(':');
                if(style[0] == key) return style[1];
            }
        },
        'setStyle': function(key, value){
            var newStyles;
            if(Utils.isSet(value)){
                newStyles = {}
                newStyles[key] = value;
            }else if(Utils.isObject(key))
                newStyles = key;

            var oldStyles = this.getAttr('style');
            var styles = {}
            if(oldStyles != null){
                oldStyles = oldStyles.split(';');
                for(var i in oldStyles){
                    if(oldStyles[i] == '') continue;
                    var style = oldStyles[i].split(':');
                    styles[style[0]] = style[1];
                }
            }

            Utils.extend(styles, newStyles);

            var stylesAttr = '';
            for(var i in styles){
                if(Utils.isNumber(styles[i])) styles[i] += 'px';
                stylesAttr += i + ':' + styles[i] + ';';
            }

            this.setAttr('style', stylesAttr);

            return this.ref;
        },
        'addClass': function(newClasses){
            var classList = this.getDom().classList;
            newClasses = newClasses.split(' ');
            for(var i in newClasses)
                classList.add(newClasses[i]);

            return this.ref;
        },
        'removeClass': function(oldClasses){
            var classList = this.getDom().classList;
            if(classList.length == 0)
                return this.ref;

            oldClasses = oldClasses.split(' ');
            for(var i in oldClasses)
                classList.remove(oldClasses[i]);

            return this.ref;
        },
        'hasClass': function(className){
            return this.getDom().classList.contains(className);
        },
        'toggleClass': function(className){
            if(this.hasClass(className))
                return this.removeClass(className);
            return this.addClass(className);
        },

        /*
         * Content Managing
         *===========================================================*/
        'clear': function(){
            this.getDom().innerHTML = '';
            return this.ref;
        },
        'add': function(){
            Utils.each(arguments, function(element){
                if(Utils.isUnset(element)) return;
                if(Utils.isNumber(element)) element = Utils.toString(element);
                if(!Utils.isString(element) && (!element || !element.getDom))
                    //TODO Error
                    throw 'Child has to be an Element, string or number. (' + element + ')';

                if(Utils.isString(element))
                    element = TextElement.new(element);

                var dom = this.get('dom');
                dom.appendChild(element.getDom());

                element.set('parent', this.ref);
                this.get('children').push(element);
            }, this);

            return this.ref;
        },
        'prepend': function(){
            Utils.each(arguments, function(element){
                if(Utils.isUnset(element)) return;
                if(Utils.isNumber(element)) element = Utils.toString(element);
                if(!Utils.isString(element) && (!element || !element.getDom))
                    //TODO Error
                    throw 'Child has to be an Element, string or number. (' + element + ')';

                if(Utils.isString(element))
                    element = TextElement.new(element);

                var dom = this.getDom();
                dom.insertBefore(element.getDom(), dom.firstChild);

                element.set('parent', this.ref);
                this.get('children').unshift(element);
            }, this);

            return this.ref;
        },
        'addAt': function(element, index){
            var children = this.get('children');
            if(index == children.length) return this.add(element);

            if(Utils.isUnset(element)) return;
            if(Utils.isNumber(element)) element = Utils.toString(element);
            if(!Utils.isString(element) && (!element || !element.getDom))
                //TODO Error
                throw 'Child has to be an Element, string or number. (' + element + ')';

            if(Utils.isString(element))
                element = TextElement.new(element);

            var dom = this.getDom();
            dom.insertBefore(element.getDom(), children[index]);

            element.set('parent', this.ref);
            children.splice(index, 0, element);

            return this.ref;
        },
        'addAfter': function(element, targetElement){
            if(Utils.isUnset(element)) return;
            if(Utils.isNumber(element)) element = Utils.toString(element);
            if(!Utils.isString(element) && (!element || !element.getDom))
                //TODO Error
                throw 'Child has to be an Element, string or number. (' + element + ')';

            if(Utils.isString(element))
                element = TextElement.new(element);

            var dom = this.getDom();
            dom.insertBefore(element.getDom(), targetElement.getDom().nextSibling);

            element.set('parent', this.ref);
            this.get('children').unshift(element);

            return this.ref;
        },
        'remove': function(element){
            //Remove element
            if(element){
                this.getDom().removeChild(element.getDom());

                element.unset('parent');
                var children = this.get('children');
                children.splice(children.indexOf(element), -1);
                return this.ref;
            }

            //Remove this from parent
            this.get('parent').remove(this);

            return this.ref;
        },

        'getParent': function(){
            return this.get('parent');
        },

        /*
         * Event Handling
         *===========================================================*/
        'on': function(action, func){
            try{
                this.super.on(action, func);
            }catch(e){
                if(e[0] == 'NO_ACTION')
                    this.getDom().addEventListener(action, func);
                else
                    throw e;
            }

            return this.ref;
        },
        'off': function(action, func){
            try{
                this.super.off(action, func);
            }catch(e){
                if(e[0] == 'NO_ACTION')
                    this.getDom().removeEventListener(action, func);
                else
                    throw e;
            }
            return this.ref;
        },
        'trigger': function(action, event){
            try{
                this.super.trigger(action, event);
            }catch(e){
                if(e[0] == 'NO_ACTION')
                    this.getDom().dispatchEvent(new Event(action));
                else
                    throw e;
            }
            return this.ref;
        },

        /*
         * Ready-to-use handled events
         *===========================================================*/
        'hide': function(){
            if(this.hasClass('jb-hidden'))
                return this.ref;

            return this.trigger('hide');
        },
        'show': function(){
            if(!this.hasClass('jb-hidden'))
                return this.ref;

            return this.trigger('show');
        },
        'isShown': function(){
            return !this.hasClass('jb-hidden');
        },

        /*'animate': function(property, value, unit, duration){
            var clock = this.get('animationClock');
            if(Utils.isSet(clock)) clearInterval(clock);

            if(!unit) unit = 'px';
            if(!duration) duration = 200;
            var FRAME_RATE = 50;
            var frameNumber = Math.round(FRAME_RATE/1000*duration);
            var frameDuration = Math.round(1000/FRAME_RATE);
            var currentFrame = 0;

            [>var rect = this.getDom().getBoundingClientRect();
              var diff = value - rect[property];<]
            var from = Utils.toFloat(this.style(property));
            var diff = value - from;
            var clock = setInterval((function(){
                currentFrame++;
                var rate = currentFrame / frameNumber;
                this.style(property, (from - diff * rate * (rate - 2)) + unit);
                if(currentFrame == frameNumber){
                    clearInterval(clock);
                    this.set('animationClock', null);
                }
            }).bind(this), frameDuration);
            this.set('animationClock', clock);

            return this.ref;
        },*/

        /*
         * Dom returner
         *===========================================================*/
        'getDom': function(){
            return this.get('dom');
        }
    });
});
<commit_msg>Updated remove method.<commit_after>/**
 * js/com/Element.js
 * Author: H.Alper Tuna <halpertuna@gmail.com>
 * Date: 12.04.2016
 */

'use strict';

define(['../core/EventHandler', '../core/Utils', './TextElement'], function(EventHandler, Utils, TextElement){
    return EventHandler.extend({
        'init': function(tag){
            if(!tag) tag = 'div';

            this.set('dom', Utils.isString(tag) ? document.createElement(tag) : tag);
            this.set('children', []);
            this.handle('show');
            this.handle('hide');
            this.on('show', function(){this.removeClass('jb-hidden')});
            this.on('hide', function(){this.addClass('jb-hidden')});
        },

        /*
         * Attribute Managing
         *===========================================================*/
        'getAttr': function(attr){
            return this.getDom().getAttribute(attr);
        },
        'setAttr':  function(attr, value){
            this.getDom().setAttribute(attr, value);
            return this.ref;
        },
        'removeAttr': function(attr){
            this.getDom().removeAttribute(attr);
            return this.ref;
        },
        'getStyle': function(key){
            var styles = this.getAttr('style');
            if(styles == null) return;

            styles = styles.split(';');
            for(var i in styles){
                if(styles[i] == '') continue;
                var style = styles[i].split(':');
                if(style[0] == key) return style[1];
            }
        },
        'setStyle': function(key, value){
            var newStyles;
            if(Utils.isSet(value)){
                newStyles = {}
                newStyles[key] = value;
            }else if(Utils.isObject(key))
                newStyles = key;

            var oldStyles = this.getAttr('style');
            var styles = {}
            if(oldStyles != null){
                oldStyles = oldStyles.split(';');
                for(var i in oldStyles){
                    if(oldStyles[i] == '') continue;
                    var style = oldStyles[i].split(':');
                    styles[style[0]] = style[1];
                }
            }

            Utils.extend(styles, newStyles);

            var stylesAttr = '';
            for(var i in styles){
                if(Utils.isNumber(styles[i])) styles[i] += 'px';
                stylesAttr += i + ':' + styles[i] + ';';
            }

            this.setAttr('style', stylesAttr);

            return this.ref;
        },
        'addClass': function(newClasses){
            var classList = this.getDom().classList;
            newClasses = newClasses.split(' ');
            for(var i in newClasses)
                classList.add(newClasses[i]);

            return this.ref;
        },
        'removeClass': function(oldClasses){
            var classList = this.getDom().classList;
            if(classList.length == 0)
                return this.ref;

            oldClasses = oldClasses.split(' ');
            for(var i in oldClasses)
                classList.remove(oldClasses[i]);

            return this.ref;
        },
        'hasClass': function(className){
            return this.getDom().classList.contains(className);
        },
        'toggleClass': function(className){
            if(this.hasClass(className))
                return this.removeClass(className);
            return this.addClass(className);
        },

        /*
         * Content Managing
         *===========================================================*/
        'clear': function(){
            this.getDom().innerHTML = '';
            return this.ref;
        },
        'add': function(){
            Utils.each(arguments, function(element){
                if(Utils.isUnset(element)) return;
                if(Utils.isNumber(element)) element = Utils.toString(element);
                if(!Utils.isString(element) && (!element || !element.getDom))
                    //TODO Error
                    throw 'Child has to be an Element, string or number. (' + element + ')';

                if(Utils.isString(element))
                    element = TextElement.new(element);

                var dom = this.get('dom');
                dom.appendChild(element.getDom());

                element.set('parent', this.ref);
                this.get('children').push(element);
            }, this);

            return this.ref;
        },
        'prepend': function(){
            Utils.each(arguments, function(element){
                if(Utils.isUnset(element)) return;
                if(Utils.isNumber(element)) element = Utils.toString(element);
                if(!Utils.isString(element) && (!element || !element.getDom))
                    //TODO Error
                    throw 'Child has to be an Element, string or number. (' + element + ')';

                if(Utils.isString(element))
                    element = TextElement.new(element);

                var dom = this.getDom();
                dom.insertBefore(element.getDom(), dom.firstChild);

                element.set('parent', this.ref);
                this.get('children').unshift(element);
            }, this);

            return this.ref;
        },
        'addAt': function(element, index){
            var children = this.get('children');
            if(index == children.length) return this.add(element);

            if(Utils.isUnset(element)) return;
            if(Utils.isNumber(element)) element = Utils.toString(element);
            if(!Utils.isString(element) && (!element || !element.getDom))
                //TODO Error
                throw 'Child has to be an Element, string or number. (' + element + ')';

            if(Utils.isString(element))
                element = TextElement.new(element);

            var dom = this.getDom();
            dom.insertBefore(element.getDom(), children[index]);

            element.set('parent', this.ref);
            children.splice(index, 0, element);

            return this.ref;
        },
        'addAfter': function(element, targetElement){
            if(Utils.isUnset(element)) return;
            if(Utils.isNumber(element)) element = Utils.toString(element);
            if(!Utils.isString(element) && (!element || !element.getDom))
                //TODO Error
                throw 'Child has to be an Element, string or number. (' + element + ')';

            if(Utils.isString(element))
                element = TextElement.new(element);

            var dom = this.getDom();
            dom.insertBefore(element.getDom(), targetElement.getDom().nextSibling);

            element.set('parent', this.ref);
            this.get('children').unshift(element);

            return this.ref;
        },
        'remove': function(element){
            //Remove element
            if(element){
                this.getDom().removeChild(element.getDom());

                element.unset('parent');
                var children = this.get('children');
                children.splice(children.indexOf(element), -1);
                return this.ref;
            }

            //Remove this from parent
            var parent = this.get('parent');
            if(parent) parent.remove(this);

            return this.ref;
        },

        'getParent': function(){
            return this.get('parent');
        },

        /*
         * Event Handling
         *===========================================================*/
        'on': function(action, func){
            try{
                this.super.on(action, func);
            }catch(e){
                if(e[0] == 'NO_ACTION')
                    this.getDom().addEventListener(action, func);
                else
                    throw e;
            }

            return this.ref;
        },
        'off': function(action, func){
            try{
                this.super.off(action, func);
            }catch(e){
                if(e[0] == 'NO_ACTION')
                    this.getDom().removeEventListener(action, func);
                else
                    throw e;
            }
            return this.ref;
        },
        'trigger': function(action, event){
            try{
                this.super.trigger(action, event);
            }catch(e){
                if(e[0] == 'NO_ACTION')
                    this.getDom().dispatchEvent(new Event(action));
                else
                    throw e;
            }
            return this.ref;
        },

        /*
         * Ready-to-use handled events
         *===========================================================*/
        'hide': function(){
            if(this.hasClass('jb-hidden'))
                return this.ref;

            return this.trigger('hide');
        },
        'show': function(){
            if(!this.hasClass('jb-hidden'))
                return this.ref;

            return this.trigger('show');
        },
        'isShown': function(){
            return !this.hasClass('jb-hidden');
        },

        /*'animate': function(property, value, unit, duration){
            var clock = this.get('animationClock');
            if(Utils.isSet(clock)) clearInterval(clock);

            if(!unit) unit = 'px';
            if(!duration) duration = 200;
            var FRAME_RATE = 50;
            var frameNumber = Math.round(FRAME_RATE/1000*duration);
            var frameDuration = Math.round(1000/FRAME_RATE);
            var currentFrame = 0;

            [>var rect = this.getDom().getBoundingClientRect();
              var diff = value - rect[property];<]
            var from = Utils.toFloat(this.style(property));
            var diff = value - from;
            var clock = setInterval((function(){
                currentFrame++;
                var rate = currentFrame / frameNumber;
                this.style(property, (from - diff * rate * (rate - 2)) + unit);
                if(currentFrame == frameNumber){
                    clearInterval(clock);
                    this.set('animationClock', null);
                }
            }).bind(this), frameDuration);
            this.set('animationClock', clock);

            return this.ref;
        },*/

        /*
         * Dom returner
         *===========================================================*/
        'getDom': function(){
            return this.get('dom');
        }
    });
});
<|endoftext|>


<commit_before>LedPanel =
	function (containerId, options) {
		this.options = {layout: 'vertical', showLabels: true, color: '#00FF00', labelRotation: 45, data:[]};
		this.id = containerId;
		this.leds = [];

		this.mergeOptions = function(options, overrideOptions) {
			if (overrideOptions != null) {
				for ( var key in overrideOptions) {
					if (overrideOptions.hasOwnProperty(key)) {
					options[key] = overrideOptions[key];
					}
				}
			}
			return options;
		}

		if(options) {
			this.options = this.mergeOptions(this.options, options);
		}

		this.options.layout = this.options.layout.toLowerCase();

		var container = document.getElementById(this.id);
	    var paper = new Raphael(container);
	    paper.setViewBox(0, 0, container.offsetWidth, container.offsetHeight, true);

	    var panelWidth = 0;
		var panelHeight = 0;
		if(this.options.layout == 'horizontal') {
			panelHeight = 10 + this.options.data.length*50;
			panelWidth = 60;
		} else {
			panelHeight = 60;
		  	panelWidth = 10 + this.options.data.length*50;
		}

		var panel = paper.rect(0, 0, panelWidth, panelHeight);
	    panel.glow({
	        color: '#444',
	        offsety: 3,
	        offsetx: 3
	    });
	    panel.attr({stroke: '#000', 'stroke-width': 4, gradient: '90-#292929-#4D4D4D'});

		for(var i=0; i < this.options.data.length; i++) {
	    	var ledGlow = paper.set();

	    	var led = {};
	    	var label;
	    	if(this.options.layout == 'horizontal') {
	    		led = paper.circle(30, 30+(i*50), 20);
	    		if(this.options.showLabels) {
	    			label = paper.text(75, 30 + (i*50), options.data[i].title);

	    		}
	    	} else {
	    		led = paper.circle(30+(i*50), 30, 20);
	    		if(this.options.showLabels) {
	    			label = paper.text(30 + (i*50), 75, options.data[i].title);

	    		}

	    	}
	    	if(this.options.showLabels) {
	    		label.attr({"font-size":16, "font-family": "Segoe UI, Arial, Helvetica, sans-serif", "font-weight": "bold", "text-anchor": "start"});
	    		var bbox = label.getBBox();
	    		var labelBox = paper.rect(bbox.x, bbox.y+bbox.height/2-1,bbox.width,2).attr('stroke','none');

    			label.rotate(this.options.labelRotation,bbox.x,bbox.y);
    			labelBox.rotate(this.options.labelRotation,bbox.x,bbox.y);

    			ledGlow.push(labelBox.glow({color: this.options.color, opacity: 0.3, width: 10}));
	    	}
	    	ledGlow.push(led.glow({opacity: 0.7, color: this.options.color, width: 20}));

	    	if(this.options.data[i].enabled) {
	    		led.attr({fill: this.options.color, stroke: '#292828', 'stroke-width': 2, opacity: 1.0, title: this.options.data[i].title});
	    	} else {
	    		led.attr({fill: this.options.color, stroke: '#292828', 'stroke-width': 2, opacity: 0.2, title: this.options.data[i].title});
	    		ledGlow.hide();
	    	}

			this.leds.push({graphic: led, glow: ledGlow, enabled: this.options.data[i].enabled, title: this.options.data[i].title});
	    }

		this.enableLed = function (index, enabled) {
			var led = this.leds[index];
			if(led) {
				if(enabled && !led.enabled) {
					for(var i=0; i < 8; i++) {
						var alpha = 0.2+(i*0.1);
						led.graphic.animate({opacity: alpha}, 100*i, 'bounce');
					}
					var glow = led.glow;
					setTimeout(function(){glow.show()},800);
					led.enabled = true;
				} else if(!enabled && led.enabled) {
					led.glow.hide();
					for(var i=0; i < 8; i++) {
						var alpha = 1.0-(i*0.1);
						led.graphic.animate({opacity: alpha}, 100*i, 'bounce');

					}
					led.enabled=false;
				}
			}
		}

		this.isEnabled = function(index) {
			var led = this.leds[index];
			if(led) {
				return led.endabled;
			}
			return false;
		}

}<commit_msg>KA_JMX-21: Added offset X and Y values to introduce padding, fixed javascript namespace.<commit_after>if(typeof org=='undefined') {
	org = {};
}
if(!org.hasOwnProperty("kasource")) {
	org.kasource = {};
}
if(!(org.kasource.hasOwnProperty("jmx"))) {
	org.kasource.jmx = {};
}
if(!(org.kasource.jmx.hasOwnProperty("widget"))) {
	org.kasource.jmx.widget = {};
}

org.kasource.jmx.widget.LedPanel =
	function (containerId, options) {
		this.options = {layout: 'vertical', showLabels: true, color: '#00FF00', labelRotation: 45, data:[]};
		this.id = containerId;
		this.leds = [];

		this.mergeOptions = function(options, overrideOptions) {
			if (overrideOptions != null) {
				for ( var key in overrideOptions) {
					if (overrideOptions.hasOwnProperty(key)) {
					options[key] = overrideOptions[key];
					}
				}
			}
			return options;
		}

		if(options) {
			this.options = this.mergeOptions(this.options, options);
		}

		this.options.layout = this.options.layout.toLowerCase();

		var container = document.getElementById(this.id);
	    var paper = new Raphael(this.id);
	    var set = paper.set();
	    var panelWidth = 0;
		var panelHeight = 0;
		if(this.options.layout == 'vertical') {
			panelHeight = 10 + this.options.data.length*50;
			panelWidth = 60;
		} else {
			panelHeight = 60;
		  	panelWidth = 10 + this.options.data.length*50;
		}
		var offsetX = 5;
		var offsetY = 5;
		var panel = paper.rect(offsetX, offsetY, panelWidth, panelHeight);
	    panel.glow({
	        color: '#444',
	        offsety: 3,
	        offsetx: 3
	    });
	    panel.attr({stroke: '#000', 'stroke-width': 4, gradient: '90-#292929-#4D4D4D'});
		set.push(panel);
		for(var i=0; i < this.options.data.length; i++) {
	    	var ledGlow = paper.set();

	    	var led = {};
	    	var label;
	    	if(this.options.layout == 'vertical') {
	    		led = paper.circle(30 + offsetX, 30+(i*50) + offsetY, 20);
	    		set.push(led);
	    		if(this.options.showLabels) {
	    			label = paper.text(75 + offsetX, 30 + (i*50) + offsetY, options.data[i].title);

	    		}
	    	} else {
	    		led = paper.circle(30+(i*50)+offsetX, 30+offsetY, 20);
	    		set.push(led);
	    		if(this.options.showLabels) {
	    			label = paper.text(30 + (i*50) + offsetX, 75 + offsetY, options.data[i].title);

	    		}

	    	}
	    	if(this.options.showLabels) {
	    		label.attr({"font-size":16, "font-family": "Segoe UI, Arial, Helvetica, sans-serif", "font-weight": "bold", "text-anchor": "start"});
	    		var bbox = label.getBBox();
	    		var labelBox = paper.rect(bbox.x, bbox.y+bbox.height/2-1,bbox.width,2).attr('stroke','none');

    			label.rotate(this.options.labelRotation,bbox.x,bbox.y);
    			labelBox.rotate(this.options.labelRotation,bbox.x,bbox.y);
    			set.push(label);
    			set.push(labelBox);
    			ledGlow.push(labelBox.glow({color: this.options.color, opacity: 0.3, width: 10}));
	    	}
	    	ledGlow.push(led.glow({opacity: 0.7, color: this.options.color, width: 20}));

	    	if(this.options.data[i].enabled) {
	    		led.attr({fill: this.options.color, stroke: '#292828', 'stroke-width': 2, opacity: 1.0, title: this.options.data[i].title});
	    	} else {
	    		led.attr({fill: this.options.color, stroke: '#292828', 'stroke-width': 2, opacity: 0.2, title: this.options.data[i].title});
	    		ledGlow.hide();
	    	}

			this.leds.push({graphic: led, glow: ledGlow, enabled: this.options.data[i].enabled, title: this.options.data[i].title});
	    }

		 var viewBox = set.getBBox();
		 paper.setViewBox(0, 0, viewBox.width + offsetX, viewBox.height + offsetY, true);
		// paper.canvas.setAttribute('preserveAspectRatio', 'none');

		this.enableLed = function (index, enabled) {
			var led = this.leds[index];
			if(led) {
				if(enabled && !led.enabled) {
					led.graphic.animate({opacity: 1}, 800, 'bounce');

					var glow = led.glow;
					setTimeout(function(){glow.show()},800);
					led.enabled = true;
				} else if(!enabled && led.enabled) {
					led.glow.hide();
					led.graphic.animate({opacity: 0.2}, 800, 'bounce');

					led.enabled=false;
				}
			}
		}

		this.isEnabled = function(index) {
			var led = this.leds[index];
			if(led) {
				return led.endabled;
			}
			return false;
		}

}<|endoftext|>


<commit_before>import React from 'react';
import {mount, shallow} from 'enzyme';
import {Provider} from 'react-redux';
import STATUS from '../../src/constants';
import {genNotification, mockStore} from '../fixtures';
import {types} from '../../src/store/notifications';
import css from '../../src/components/Notification/Notification.scss';
import ConnectNotification, {Notification} from '../../src/components/Notification/Notification';

describe('Notification', () => {
  // a default notification
  const notification = {
    id: 123123123,
    title: 'title',
    message: 'message',
    dismissible: false,
    dismissAfter: 3333,
    status: STATUS.info,
    onAdd: function() {
    },
    onRemove: function() {
    }
  };

  // default props for Notification component
  const defaultProps = {
    removeNotification: function() {
    }
  };

  // default className for Notification component
  const className = {
    main: css['notification'],
    status: function(status) {
      return css[`notification-${status}`];
    },
    // `fa` corresponds to font-awesome's class name
    icon: `fa ${css['notification-icon']}`,
    title: css['notification-title'],
    message: ''
  };

  /**
   * Return expected JSX of Notification component
   * @param {Object} props
   * @returns {XML}
   */
  /* eslint-disable "react/prop-types" */
  function renderExpectedNotification(notification) {
    const {title, message, status, dismissible} = notification;
    let titleDiv = null;
    if (title) {
      titleDiv =
{title}
;
    }
    return (
      <div className={`${className.main} ${className.status(status)}`}
           onClick={dismissible ? this._remove : ''}>

        {titleDiv}

          {message}

    );
  }

  /* eslint-enable "react/prop-types" */

  it('should mount with passed props', () => {
    const wrapper = mount(<Notification key={notification.id}
      {...notification} {...defaultProps}/>);
    expect(wrapper.props().className.main).toEqual(className.main);
    expect(wrapper.props().className.icon).toEqual(className.icon);
    expect(wrapper.props().className.title).toEqual(className.title);
    expect(wrapper.props().className.message).toEqual(className.message);
    expect(wrapper.props().className.status()).toEqual(className.status());
    expect(wrapper.props().removeNotification).toEqual(defaultProps.removeNotification);
    expect(wrapper.props().id).toEqual(notification.id);
    expect(wrapper.props().title).toEqual(notification.title);
    expect(wrapper.props().message).toEqual(notification.message);
    expect(wrapper.props().status).toEqual(notification.status);
    expect(wrapper.props().dismissible).toEqual(notification.dismissible);
    expect(wrapper.props().dismissAfter).toEqual(notification.dismissAfter);
    expect(wrapper.props().onAdd()).toEqual(notification.onAdd());
    expect(wrapper.props().onRemove()).toEqual(notification.onRemove());
  });

  it('should render JSX and HTML correctly (with title)', () => {
    const wrapper = shallow(<Notification key={notification.id} {...notification}
      {...defaultProps}/>);
    const expectedComponent = shallow(renderExpectedNotification(notification));
    expect(wrapper.html()).toEqual(expectedComponent.html());
    expect(wrapper.debug()).toEqual(expectedComponent.debug());
  });

  it('should render JSX and HTML correctly (without title)', () => {
    const _notification = Object.assign({}, notification);
    // remove title
    delete _notification.title;
    const wrapper = shallow(<Notification key={_notification.id} {..._notification}
      {...defaultProps}/>);
    const expectedComponent = shallow(renderExpectedNotification(_notification));
    expect(wrapper.html()).toEqual(expectedComponent.html());
    expect(wrapper.debug()).toEqual(expectedComponent.debug());
  });

  it('should create an action to remove the notification when it is clicked', () => {
    const notification = genNotification({dismissible: true});
    const store = mockStore({notification: [notification]});
    const wrapper = mount(
      <Provider store={store}>
        <ConnectNotification key={notification.id} {...notification}/>
      </Provider>
    );
    wrapper.find(ConnectNotification).simulate('click');
    const expectedAction = {
      type: types.REMOVE_NOTIFICATION,
      payload: notification.id
    };
    expect(store.getActions()).toEqual([expectedAction]);
  });

  it('should run onAdd() callback at componentDidMount() lifecycle', () => {
    const errorMessage = 'onAdd() callback';
    const _notification = Object.assign({}, notification, {
      // here we throw an error to capture where
      // the code has been executed before error was thrown
      onAdd: function() {
        throw new Error(errorMessage);
      }
    });
    try {
      mount(<Notification key={_notification.id} {..._notification} {...defaultProps}/>);
    }
    catch (error) {
      expect(error.stack).toMatch(/onAdd\ncomponentDidMount/);
      expect(error.message).toEqual(errorMessage);
    }
  });

  it('should run onRemove() callback at componentWillUnmount() lifecycle', () => {
    const errorMessage = 'onRemove() callback';
    const _notification = Object.assign({}, notification, {
      // here we throw an error to capture where
      // the code has been executed before error was thrown
      onRemove: function() {
        throw new Error(errorMessage);
      }
    });
    const wrapper = mount(<Notification key={_notification.id} {..._notification}
      {...defaultProps}/>);
    try {
      wrapper.unmount();
    }
    catch (error) {
      expect(error.stack).toMatch(/onRemove\ncomponentWillUnmount/);
      expect(error.message).toEqual(errorMessage);
    }
  });
});
<commit_msg>improve Notification test suite<commit_after>import React from 'react';
import {mount} from 'enzyme';
import {Provider} from 'react-redux';
import {genNotification, mockStore} from '../fixtures';
import {types, removeNotification} from '../../src/store/notifications';
import css from '../../src/components/Notification/Notification.scss';
import ConnectNotification, {Notification} from '../../src/components/Notification/Notification';

describe('Notification', () => {
  let notification = null;
  let store = null;

  // default className for Notification component
  const className = {
    main: css['notification'],
    status: function(status) {
      return css[`notification-${status}`];
    },
    icon: `fa ${css['notification-icon']}`,
    title: css['notification-title'],
    message: ''
  };

  /**
   * Return expected JSX of Notification component
   * @param {Object} props
   * @returns {XML}
   */
  /* eslint-disable "react/prop-types" */
  function renderExpectedNotification(notification) {
    const {title, message, status, dismissible} = notification;
    let titleDiv = null;
    if (title) {
      titleDiv =
{title}
;
    }
    return (
      <div className={`${className.main} ${className.status(status)}`}
           onClick={dismissible ? this._remove : ''}>

        {titleDiv}

          {message}

    );
  }

  /* eslint-enable "react/prop-types" */

  beforeEach('generate a new notification and init store', () => {
    notification = genNotification();
    store = mockStore({notifications: []});
  });

  it('should mount with default props', () => {
    delete notification.onAdd;
    delete notification.onRemove;
    const wrapper = mount(
      <Notification key={notification.id} {...notification}
                    removeNotification={removeNotification}/>
    );
    expect(wrapper.props().className.main).toEqual(className.main);
    expect(wrapper.props().className.icon).toEqual(className.icon);
    expect(wrapper.props().className.title).toEqual(className.title);
    expect(wrapper.props().className.message).toEqual(className.message);
    expect(wrapper.props().className.status()).toEqual(className.status());
    expect(wrapper.props().removeNotification).toEqual(removeNotification);
    expect(wrapper.props().onAdd()).toEqual((() => {
    })());
    expect(wrapper.props().onRemove()).toEqual((() => {
    })());
  });

  it('should render JSX and HTML correctly (with title)', () => {
    notification.dismissible = false;
    const wrapper = mount(
      <Provider store={store}>
        <ConnectNotification key={notification.id} {...notification}/>
      </Provider>
    );
    const expectedComponent = mount(renderExpectedNotification(notification));
    expect(wrapper.html()).toEqual(expectedComponent.html());
  });

  it('should render JSX and HTML correctly (without title)', () => {
    notification.dismissible = false;
    notification.title = null;
    const wrapper = mount(
      <Provider store={store}>
        <ConnectNotification key={notification.id} {...notification}/>
      </Provider>
    );

    const expectedComponent = mount(renderExpectedNotification(notification));
    expect(wrapper.html()).toEqual(expectedComponent.html());
  });

  it('should run onAdd() callback at componentDidMount() lifecycle', () => {
    const errorMessage = 'onAdd() callback';
    // we throw an error to capture where
    // the code has been executed before error was thrown
    notification.onAdd = () => {
      throw new Error(errorMessage);
    };
    try {
      mount(
        <Provider store={store}>
          <ConnectNotification key={notification.id} {...notification}/>
        </Provider>
      );
    }
    catch (error) {
      expect(error.stack).toMatch(/onAdd\ncomponentDidMount/);
      expect(error.message).toEqual(errorMessage);
    }
  });

  it('should run onRemove() callback at componentWillUnmount() lifecycle', () => {
    const errorMessage = 'onRemove() callback';
    // we throw an error to capture where
    // the code has been executed before error was thrown
    notification.onRemove = () => {
      throw new Error(errorMessage);
    };
    const wrapper = mount(
      <Provider store={store}>
        <ConnectNotification key={notification.id} {...notification}/>
      </Provider>
    );
    try {
      wrapper.unmount();
    }
    catch (error) {
      expect(error.stack).toMatch(/onRemove\ncomponentWillUnmount/);
      expect(error.message).toEqual(errorMessage);
    }
  });

  it('should create an action to remove the notification when it is clicked', () => {
    notification.dismissible = true;
    const wrapper = mount(
      <Provider store={store}>
        <ConnectNotification key={notification.id} {...notification}/>
      </Provider>
    );
    wrapper.find(ConnectNotification).simulate('click');
    const expectedAction = {
      type: types.REMOVE_NOTIFICATION,
      payload: notification.id
    };
    expect(store.getActions()).toEqual([expectedAction]);
  });
});
<|endoftext|>


<commit_before>/*
 * Paper.js
 *
 * This file is part of Paper.js, a JavaScript Vector Graphics Library,
 * based on Scriptographer.org and designed to be largely API compatible.
 * http://paperjs.org/
 * http://scriptographer.org/
 *
 * Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
 * http://lehni.org/ & http://jonathanpuckey.com/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * All rights reserved.
 */

var Palette = this.Palette = Base.extend(Callback, /** @lends Palette# */{
	_events: [ 'onChange' ],

	initialize: function(title, components, values) {
		var parent = DomElement.find('.paperjs-palettes')
			|| DomElement.find('body').appendChild(
				DomElement.create('div', { 'class': 'paperjs-palettes' })),
			table = parent.appendChild(
				DomElement.create('table', { 'class': 'paperjs-palette' })),
			that = this;
		this._title = title;
		if (!values)
			values = {};
		this._components = Base.each(components, function(component, name) {
			if (!(component instanceof Component)) {
				if (component.value == null)
					component.value = values[name];
				component.name = name;
				component = components[name] = new Component(component);
			}
			component.palette = that;
			// Make sure each component has an entry in values, so observers get
			// installed further down.
			if (values[name] === undefined)
				values[name] = null;
			var row = table.appendChild(
				DomElement.create('tr', [
					'td', { text: (component.label || name) + ':' },
					'td', component.element
				])
			);
		});
		this._values = Base.each(values, function(value, name) {
			// Replace each entry with an getter / setters so we can observe
			// change.
			Base.define(values, name, {
				enumerable: true,
				configurable: true,
				writable: true,
				get: function() {
					return value;
				},
				set: function(val) {
					value = val;
					components[name].setValue(val);
				}
			});
		});
	},

	reset: function() {
		for (var i in this._components)
			this._components[i].reset();
	}
});
<commit_msg>Rename Palette classes.<commit_after>/*
 * Paper.js
 *
 * This file is part of Paper.js, a JavaScript Vector Graphics Library,
 * based on Scriptographer.org and designed to be largely API compatible.
 * http://paperjs.org/
 * http://scriptographer.org/
 *
 * Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
 * http://lehni.org/ & http://jonathanpuckey.com/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * All rights reserved.
 */

var Palette = this.Palette = Base.extend(Callback, /** @lends Palette# */{
	_events: [ 'onChange' ],

	initialize: function(title, components, values) {
		var parent = DomElement.find('.palettejs-panel')
			|| DomElement.find('body').appendChild(
				DomElement.create('div', { 'class': 'palettejs-panel' })),
			table = parent.appendChild(
				DomElement.create('table', { 'class': 'palettejs-pane' })),
			that = this;
		this._title = title;
		if (!values)
			values = {};
		this._components = Base.each(components, function(component, name) {
			if (!(component instanceof Component)) {
				if (component.value == null)
					component.value = values[name];
				component.name = name;
				component = components[name] = new Component(component);
			}
			component.palette = that;
			// Make sure each component has an entry in values, so observers get
			// installed further down.
			if (values[name] === undefined)
				values[name] = null;
			var row = table.appendChild(
				DomElement.create('tr', [
					'td', { text: (component.label || name) + ':' },
					'td', component.element
				])
			);
		});
		this._values = Base.each(values, function(value, name) {
			// Replace each entry with an getter / setters so we can observe
			// change.
			Base.define(values, name, {
				enumerable: true,
				configurable: true,
				writable: true,
				get: function() {
					return value;
				},
				set: function(val) {
					value = val;
					components[name].setValue(val);
				}
			});
		});
	},

	reset: function() {
		for (var i in this._components)
			this._components[i].reset();
	}
});
<|endoftext|>


<commit_before>import uuid from "uuid";
import Team from "./teams";
import { DamageStep } from "./generic";

class RemoteAccess {
  constructor(params = {}) {
    this.id = params.id || uuid.v4();
    this.code = params.code || "";
    this.state = params.state || "sent";
    this.station = params.station || "";
    this.timestamp = params.timestamp || new Date().toISOString();
  }
}
// A separate object for vestigial parts of the ship
class Ship {
  constructor(params = {}) {
    this.clamps = params.clamps || false; // Detached
    this.ramps = params.ramps || false; // Retracted
    this.airlock = params.airlock || false; // Closed
    this.bridgeCrew = params.bridgeCrew || 14;
    this.radiation = params.radiation || 0.1;
    this.speed = params.speed || 0;
    this.selfDestructTime = params.selfDestructTime || null;
    this.selfDestructCode = params.selfDestructCode || null;
    this.selfDestructAuto = params.selfDestructAuto || false; // Automatically black out stations when self destructed
    this.remoteAccessCodes = [];
    this.extraSystems = [];
    const codes = params.remoteAccessCodes || [];
    codes.forEach(c => this.remoteAccessCodes.push(new RemoteAccess(c)));
  }
}

export default class Simulator {
  constructor(params) {
    this.id = params.id || uuid.v4();
    this.name = params.name || "Simulator";
    this.layout = params.layout || "LayoutDefault";
    this.alertlevel = params.alertlevel || "5";
    this.template = params.template || false;
    this.templateId = params.templateId || null;
    this.class = "Simulator";
    this.stationSet = params.stationSet || null;
    this.stations = params.stations || [];
    this.exocomps = params.exocomps || 0;
    this.mission = params.mission || null;
    this.currentTimelineStep = params.currentTimelineStep || 0;
    this.teams = [];
    this.training = params.training || false;
    this.ship = new Ship(params.ship);
    this.panels = params.panels || [];
    // Set up the teams
    if (params.teams) {
      params.teams.forEach(t => this.teams.push(new Team(t)));
    }

    // Damage reports
    this.stepDamage = params.stepDamage || true;
    this.verifyStep = params.verifyStep || false;
    this.requiredDamageSteps = [];
    this.optionalDamageSteps = [];
    params.requiredDamageSteps &&
      params.requiredDamageSteps.forEach(s =>
        this.requiredDamageSteps.push(new DamageStep(s))
      );
    params.optionalDamageSteps &&
      params.optionalDamageSteps.forEach(s =>
        this.optionalDamageSteps.push(new DamageStep(s))
      );
  }
  trainingMode(tf) {
    this.training = tf;
  }
  rename(name) {
    this.name = name;
  }
  setAlertLevel(alertlevel) {
    if (["5", "4", "3", "2", "1", "p"].indexOf(alertlevel) === -1) {
      throw new Error(
        "Invalid Alert Level. Must be one of '5','4','3','2','1','p'"
      );
    }
    this.alertlevel = alertlevel;
  }
  setLayout(layout) {
    this.layout = layout;
  }
  setTimelineStep(step) {
    this.currentTimelineStep = step;
  }

  // Ship
  clamps(tf) {
    this.ship.clamps = tf;
  }
  ramps(tf) {
    this.ship.ramps = tf;
  }
  airlock(tf) {
    this.ship.airlock = tf;
  }
  bridgeCrew(num) {
    this.ship.bridgeCrew = num;
  }
  radiation(num) {
    this.ship.radiation = num;
  }
  speed(num) {
    this.ship.speed = num;
  }
  sendCode(code, station) {
    this.ship.remoteAccessCodes.push(new RemoteAccess({ code, station }));
  }
  updateCode(codeId, state) {
    this.ship.remoteAccessCodes.find(c => c.id === codeId).state = state;
  }
  setSelfDestructTime(time) {
    this.ship.selfDestructTime = time;
  }
  setSelfDestructCode(code) {
    this.ship.selfDestructCode = code;
  }
  setSelfDestructAuto(tf) {
    this.ship.selfDestructAuto = tf;
  }
  updatePanels(panels) {
    this.panels = panels || [];
  }
  // Damage Steps
  addDamageStep({ name, args, type }) {
    this[`${type}DamageSteps`].push(new DamageStep({ name, args }));
  }
  updateDamageStep({ id, name, args }) {
    const step =
      this.requiredDamageSteps.find(s => s.id === id) ||
      this.optionalDamageSteps.find(s => s.id === id);
    step.update({ name, args });
  }
  removeDamageStep(stepId) {
    // Check both required and optional
    this.requiredDamageSteps = this.requiredDamageSteps.filter(
      s => s.id !== stepId
    );
    this.optionalDamageSteps = this.optionalDamageSteps.filter(
      s => s.id !== stepId
    );
  }
}
<commit_msg>Fix an issue with damage steps on the simulator<commit_after>import uuid from "uuid";
import Team from "./teams";
import DamageStep from "./generic/damageStep";

class RemoteAccess {
  constructor(params = {}) {
    this.id = params.id || uuid.v4();
    this.code = params.code || "";
    this.state = params.state || "sent";
    this.station = params.station || "";
    this.timestamp = params.timestamp || new Date().toISOString();
  }
}
// A separate object for vestigial parts of the ship
class Ship {
  constructor(params = {}) {
    this.clamps = params.clamps || false; // Detached
    this.ramps = params.ramps || false; // Retracted
    this.airlock = params.airlock || false; // Closed
    this.bridgeCrew = params.bridgeCrew || 14;
    this.radiation = params.radiation || 0.1;
    this.speed = params.speed || 0;
    this.selfDestructTime = params.selfDestructTime || null;
    this.selfDestructCode = params.selfDestructCode || null;
    this.selfDestructAuto = params.selfDestructAuto || false; // Automatically black out stations when self destructed
    this.remoteAccessCodes = [];
    this.extraSystems = [];
    const codes = params.remoteAccessCodes || [];
    codes.forEach(c => this.remoteAccessCodes.push(new RemoteAccess(c)));
  }
}

export default class Simulator {
  constructor(params) {
    this.id = params.id || uuid.v4();
    this.name = params.name || "Simulator";
    this.layout = params.layout || "LayoutDefault";
    this.alertlevel = params.alertlevel || "5";
    this.template = params.template || false;
    this.templateId = params.templateId || null;
    this.class = "Simulator";
    this.stationSet = params.stationSet || null;
    this.stations = params.stations || [];
    this.exocomps = params.exocomps || 0;
    this.mission = params.mission || null;
    this.currentTimelineStep = params.currentTimelineStep || 0;
    this.teams = [];
    this.training = params.training || false;
    this.ship = new Ship(params.ship);
    this.panels = params.panels || [];
    // Set up the teams
    if (params.teams) {
      params.teams.forEach(t => this.teams.push(new Team(t)));
    }

    // Damage reports
    this.stepDamage = params.stepDamage || true;
    this.verifyStep = params.verifyStep || false;
    this.requiredDamageSteps = [];
    this.optionalDamageSteps = [];
    params.requiredDamageSteps &&
      params.requiredDamageSteps.forEach(s =>
        this.requiredDamageSteps.push(new DamageStep(s))
      );
    params.optionalDamageSteps &&
      params.optionalDamageSteps.forEach(s =>
        this.optionalDamageSteps.push(new DamageStep(s))
      );
  }
  trainingMode(tf) {
    this.training = tf;
  }
  rename(name) {
    this.name = name;
  }
  setAlertLevel(alertlevel) {
    if (["5", "4", "3", "2", "1", "p"].indexOf(alertlevel) === -1) {
      throw new Error(
        "Invalid Alert Level. Must be one of '5','4','3','2','1','p'"
      );
    }
    this.alertlevel = alertlevel;
  }
  setLayout(layout) {
    this.layout = layout;
  }
  setTimelineStep(step) {
    this.currentTimelineStep = step;
  }

  // Ship
  clamps(tf) {
    this.ship.clamps = tf;
  }
  ramps(tf) {
    this.ship.ramps = tf;
  }
  airlock(tf) {
    this.ship.airlock = tf;
  }
  bridgeCrew(num) {
    this.ship.bridgeCrew = num;
  }
  radiation(num) {
    this.ship.radiation = num;
  }
  speed(num) {
    this.ship.speed = num;
  }
  sendCode(code, station) {
    this.ship.remoteAccessCodes.push(new RemoteAccess({ code, station }));
  }
  updateCode(codeId, state) {
    this.ship.remoteAccessCodes.find(c => c.id === codeId).state = state;
  }
  setSelfDestructTime(time) {
    this.ship.selfDestructTime = time;
  }
  setSelfDestructCode(code) {
    this.ship.selfDestructCode = code;
  }
  setSelfDestructAuto(tf) {
    this.ship.selfDestructAuto = tf;
  }
  updatePanels(panels) {
    this.panels = panels || [];
  }
  // Damage Steps
  addDamageStep({ name, args, type }) {
    this[`${type}DamageSteps`].push(new DamageStep({ name, args }));
  }
  updateDamageStep({ id, name, args }) {
    const step =
      this.requiredDamageSteps.find(s => s.id === id) ||
      this.optionalDamageSteps.find(s => s.id === id);
    step.update({ name, args });
  }
  removeDamageStep(stepId) {
    // Check both required and optional
    this.requiredDamageSteps = this.requiredDamageSteps.filter(
      s => s.id !== stepId
    );
    this.optionalDamageSteps = this.optionalDamageSteps.filter(
      s => s.id !== stepId
    );
  }
}
<|endoftext|>


<commit_before>var config = require('./config');
var zlib = require('zlib');
var compute = require('./compute');
var computePlayerMatchData = compute.computePlayerMatchData;
var aggregator = require('./aggregator');
var async = require('async');
var constants = require('./constants');
var utility = require('./utility');
var serialize = utility.serialize;
var filter = require('./filter');
var util = require('util');
var reduceAggregable = utility.reduceAggregable;
var enabled = config.ENABLE_PLAYER_CACHE;
var cEnabled = config.ENABLE_CASSANDRA_PLAYER_CACHE;
var redis;
var cassandra;
if (enabled)
{
    redis = require('./redis');
    if (cEnabled)
    {
        cassandra = require('./cassandra');
    }
}

function readCache(account_id, options, cb)
{
    if (enabled)
    {
        if (cEnabled)
        {
            //TODO currently aggregator does live significance check.  Persist it to store so we can project fewer fields?
            var proj = ['account_id', 'match_id', 'player_slot', 'version', 'start_time', 'duration', 'game_mode', 'lobby_type', 'radiant_win'];
            var table = ['hero_id', 'player_win', 'game_mode', 'skill', 'duration', 'kills', 'deaths', 'assists', 'last_hits', 'gold_per_min', 'parse_status'];
            var filters = ['pgroup', 'hero_id', 'isRadiant', 'player_win', 'lane_role', 'game_mode', 'lobby_type', 'region', 'patch', 'start_time', 'purchase'];
            var query = util.format('SELECT %s FROM player_caches WHERE account_id = ?', Object.keys(options.js_agg).concat(proj).concat(table).concat(options.filter_count > 1 ? filters : []).join(','));
            var aggData = aggregator([], options.js_agg);
            return cassandra.stream(query, [account_id],
            {
                prepare: true,
                fetchSize: 1000,
                autoPage: true,
            }).on('readable', function()
            {
                //readable is emitted as soon a row is received and parsed
                var m;
                while (m = this.read())
                {
                    m.keys().forEach(function(key)
                    {
                        m[key] = JSON.parse(m[key]);
                    });
                    if (filter([m], options.js_select).length)
                    {
                        aggData = aggregator([m], options.js_agg, aggData);
                    }
                }
            }).on('end', function()
            {
                //stream ended, there aren't any more rows
                return cb(null,
                {
                    aggData: aggData
                });
            }).on('error', function(err)
            {
                //Something went wrong: err is a response error from Cassandra
                throw err;
            });
        }
        else
        {
            redis.get(new Buffer("player:" + account_id), function(err, result)
            {
                var cache = result ? JSON.parse(zlib.inflateSync(result)) : null;
                //console.log(result ? result.length : 0, JSON.stringify(cache).length);
                return cb(err, cache);
            });
        }
    }
    else
    {
        return cb();
    }
}

function writeCache(account_id, cache, cb)
{
    if (enabled)
    {
        if (cEnabled)
        {
            //console.log("saving player cache to cassandra %s", account_id);
            //upsert matches into store
            return async.each(cache.raw, function(m, cb)
            {
                m = serialize(reduceAggregable(m));
                var query = util.format('INSERT INTO player_caches (%s) VALUES (%s)', Object.keys(m).join(','), Object.keys(m).map(function(k)
                {
                    return '?';
                }).join(','));
                cassandra.execute(query, Object.keys(m).map(function(k)
                {
                    return m[k];
                }),
                {
                    prepare: true
                }, cb);
            }, function(err)
            {
                if (err)
                {
                    console.error(err.stack);
                }
                return cb(err);
            });
        }
        else
        {
            console.log("saving player cache to redis %s", account_id);
            redis.ttl("player:" + account_id, function(err, ttl)
            {
                if (err)
                {
                    return cb(err);
                }
                cache = {
                    aggData: cache.aggData
                };
                redis.setex(new Buffer("player:" + account_id), Number(ttl) > 0 ? Number(ttl) : 24 * 60 * 60 * config.UNTRACK_DAYS, zlib.deflateSync(JSON.stringify(cache)), function(err)
                {
                    cb(err);
                });
            });
        }
    }
    else
    {
        return cb();
    }
}

function updateCache(match, cb)
{
    if (enabled)
    {
        var players = match.players;
        if (match.pgroup && players)
        {
            players.forEach(function(p)
            {
                //add account id to each player so we know what caches to update
                p.account_id = match.pgroup[p.player_slot].account_id;
                //add hero_id to each player so we update records with hero played
                p.hero_id = match.pgroup[p.player_slot].hero_id;
            });
        }
        async.eachSeries(players, function(player_match, cb)
        {
            if (player_match.account_id && player_match.account_id !== constants.anonymous_account_id)
            {
                //join player with match to form player_match
                for (var key in match)
                {
                    player_match[key] = match[key];
                }
                computePlayerMatchData(player_match);
                if (cEnabled)
                {
                    writeCache(player_match.account_id,
                    {
                        raw: [player_match]
                    }, cb);
                }
                else
                {
                    readCache(player_match.account_id,
                    {}, function(err, cache)
                    {
                        if (err)
                        {
                            return cb(err);
                        }
                        //if player cache doesn't exist, skip
                        if (cache)
                        {
                            cache.aggData = aggregator([player_match], null, cache.aggData);
                            writeCache(player_match.account_id, cache, cb);
                        }
                        else
                        {
                            return cb();
                        }
                    });
                }
            }
            else
            {
                return cb();
            }
        }, cb);
    }
    else
    {
        return cb();
    }
}

function validateCache(db, account_id, cache, cb)
{
    if (!cache)
    {
        return cb();
    }
    if (!Number.isNaN(account_id))
    {
        db('player_matches').count().where(
        {
            account_id: Number(account_id)
        }).asCallback(function(err, count)
        {
            if (err)
            {
                return cb(err);
            }
            count = Number(count[0].count);
            //console.log(cache);
            //console.log(Object.keys(cache.aggData.matches).length, count);
            var cacheValid = cache && cache.aggData && cache.aggData.matches && Object.keys(cache.aggData.matches).length && Object.keys(cache.aggData.matches).length === count;
            return cb(err, cacheValid);
        });
    }
    else
    {
        //non-integer account_id (all/professional), skip validation (always valid)
        cb(null, true);
    }
}

function countPlayerCaches(cb)
{
    if (enabled)
    {
        redis.keys("player:*", function(err, result)
        {
            cb(err, result.length);
        });
    }
    else
    {
        return cb(null, 0);
    }
}
module.exports = {
    readCache: readCache,
    writeCache: writeCache,
    updateCache: updateCache,
    validateCache: validateCache,
    countPlayerCaches: countPlayerCaches,
};<commit_msg>randomly validate cache instead of doing on every load<commit_after>var config = require('./config');
var zlib = require('zlib');
var compute = require('./compute');
var computePlayerMatchData = compute.computePlayerMatchData;
var aggregator = require('./aggregator');
var async = require('async');
var constants = require('./constants');
var utility = require('./utility');
var serialize = utility.serialize;
var filter = require('./filter');
var util = require('util');
var reduceAggregable = utility.reduceAggregable;
var enabled = config.ENABLE_PLAYER_CACHE;
var cEnabled = config.ENABLE_CASSANDRA_PLAYER_CACHE;
var redis;
var cassandra;
if (enabled)
{
    redis = require('./redis');
    if (cEnabled)
    {
        cassandra = require('./cassandra');
    }
}

function readCache(account_id, options, cb)
{
    if (enabled)
    {
        if (cEnabled)
        {
            //TODO currently aggregator does live significance check.  Persist it to store so we can project fewer fields?
            var proj = ['account_id', 'match_id', 'player_slot', 'version', 'start_time', 'duration', 'game_mode', 'lobby_type', 'radiant_win'];
            var table = ['hero_id', 'player_win', 'game_mode', 'skill', 'duration', 'kills', 'deaths', 'assists', 'last_hits', 'gold_per_min', 'parse_status'];
            var filters = ['pgroup', 'hero_id', 'isRadiant', 'player_win', 'lane_role', 'game_mode', 'lobby_type', 'region', 'patch', 'start_time', 'purchase'];
            var query = util.format('SELECT %s FROM player_caches WHERE account_id = ?', Object.keys(options.js_agg).concat(proj).concat(table).concat(options.filter_count > 1 ? filters : []).join(','));
            var aggData = aggregator([], options.js_agg);
            return cassandra.stream(query, [account_id],
            {
                prepare: true,
                fetchSize: 1000,
                autoPage: true,
            }).on('readable', function()
            {
                //readable is emitted as soon a row is received and parsed
                var m;
                while (m = this.read())
                {
                    m.keys().forEach(function(key)
                    {
                        m[key] = JSON.parse(m[key]);
                    });
                    if (filter([m], options.js_select).length)
                    {
                        aggData = aggregator([m], options.js_agg, aggData);
                    }
                }
            }).on('end', function()
            {
                //stream ended, there aren't any more rows
                return cb(null,
                {
                    aggData: aggData
                });
            }).on('error', function(err)
            {
                //Something went wrong: err is a response error from Cassandra
                throw err;
            });
        }
        else
        {
            redis.get(new Buffer("player:" + account_id), function(err, result)
            {
                var cache = result ? JSON.parse(zlib.inflateSync(result)) : null;
                //console.log(result ? result.length : 0, JSON.stringify(cache).length);
                return cb(err, cache);
            });
        }
    }
    else
    {
        return cb();
    }
}

function writeCache(account_id, cache, cb)
{
    if (enabled)
    {
        if (cEnabled)
        {
            //console.log("saving player cache to cassandra %s", account_id);
            //upsert matches into store
            return async.each(cache.raw, function(m, cb)
            {
                m = serialize(reduceAggregable(m));
                var query = util.format('INSERT INTO player_caches (%s) VALUES (%s)', Object.keys(m).join(','), Object.keys(m).map(function(k)
                {
                    return '?';
                }).join(','));
                cassandra.execute(query, Object.keys(m).map(function(k)
                {
                    return m[k];
                }),
                {
                    prepare: true
                }, cb);
            }, function(err)
            {
                if (err)
                {
                    console.error(err.stack);
                }
                return cb(err);
            });
        }
        else
        {
            console.log("saving player cache to redis %s", account_id);
            redis.ttl("player:" + account_id, function(err, ttl)
            {
                if (err)
                {
                    return cb(err);
                }
                cache = {
                    aggData: cache.aggData
                };
                redis.setex(new Buffer("player:" + account_id), Number(ttl) > 0 ? Number(ttl) : 24 * 60 * 60 * config.UNTRACK_DAYS, zlib.deflateSync(JSON.stringify(cache)), function(err)
                {
                    cb(err);
                });
            });
        }
    }
    else
    {
        return cb();
    }
}

function updateCache(match, cb)
{
    if (enabled)
    {
        var players = match.players;
        if (match.pgroup && players)
        {
            players.forEach(function(p)
            {
                //add account id to each player so we know what caches to update
                p.account_id = match.pgroup[p.player_slot].account_id;
                //add hero_id to each player so we update records with hero played
                p.hero_id = match.pgroup[p.player_slot].hero_id;
            });
        }
        async.eachSeries(players, function(player_match, cb)
        {
            if (player_match.account_id && player_match.account_id !== constants.anonymous_account_id)
            {
                //join player with match to form player_match
                for (var key in match)
                {
                    player_match[key] = match[key];
                }
                computePlayerMatchData(player_match);
                if (cEnabled)
                {
                    writeCache(player_match.account_id,
                    {
                        raw: [player_match]
                    }, cb);
                }
                else
                {
                    readCache(player_match.account_id,
                    {}, function(err, cache)
                    {
                        if (err)
                        {
                            return cb(err);
                        }
                        //if player cache doesn't exist, skip
                        if (cache)
                        {
                            cache.aggData = aggregator([player_match], null, cache.aggData);
                            writeCache(player_match.account_id, cache, cb);
                        }
                        else
                        {
                            return cb();
                        }
                    });
                }
            }
            else
            {
                return cb();
            }
        }, cb);
    }
    else
    {
        return cb();
    }
}

function validateCache(db, account_id, cache, cb)
{
    if (!cache)
    {
        return cb();
    }
    //random auditing of the cache
    if (!Number.isNaN(account_id) && Math.random() > 0.99)
    {
        db('player_matches').count().where(
        {
            account_id: Number(account_id)
        }).asCallback(function(err, count)
        {
            if (err)
            {
                return cb(err);
            }
            count = Number(count[0].count);
            //console.log(cache);
            //console.log(Object.keys(cache.aggData.matches).length, count);
            var cacheValid = cache && cache.aggData && cache.aggData.matches && Object.keys(cache.aggData.matches).length && Object.keys(cache.aggData.matches).length === count;
            return cb(err, cacheValid);
        });
    }
    else
    {
        //non-integer account_id (all/professional), skip validation (always valid)
        cb(null, true);
    }
}

function countPlayerCaches(cb)
{
    if (enabled)
    {
        redis.keys("player:*", function(err, result)
        {
            cb(err, result.length);
        });
    }
    else
    {
        return cb(null, 0);
    }
}
module.exports = {
    readCache: readCache,
    writeCache: writeCache,
    updateCache: updateCache,
    validateCache: validateCache,
    countPlayerCaches: countPlayerCaches,
};<|endoftext|>