<commit_before>import './Modes.js';
import './TabStrip.js';
import { merge } from './updates.js';
import * as symbols from './symbols.js';
import ContentItemsMixin from './ContentItemsMixin.js';
import ElementBase from './ElementBase.js';
import SingleSelectionMixin from './SingleSelectionMixin.js';
import SlotContentMixin from './SlotContentMixin.js';
import TabButton from './TabButton.js';

const generatedIdKey = Symbol('generatedId');
const previousItemsKey = Symbol('previousItems');
const tabButtonsKey = Symbol('tabButtons');

const Base =
  ContentItemsMixin(
  SingleSelectionMixin(
  SlotContentMixin(
    ElementBase
  )));

/**
 * A set of pages with a tab strip governing which page is shown.
 *
 * Use tabs when you want to provide a large set of options or elements than
 * can comfortably fit inline, the options can be coherently grouped into pages,
 * and you want to avoid making the user navigate to a separate page. Tabs work
 * best if you only have a small handful of pages, say 2–7.
 *
 * This stock combination applies the [TabStripWrapper](TabStripWrapper) to a
 * [Modes](Modes) element. The former takes care of the relative positioning
 * of the tab buttons and tab panels; the latter takes care of displaying only
 * the currently-selected tab panel. If you'd like to create something more
 * complex than this arrangement, you can use either of those elements on its
 * own.
 *
 * You will need to provide `Tabs` with the buttons that will select the
 * corresponding tab panels. Do this by slotting the buttons into the slot named
 * "tabButtons". If you don't require custom tab buttons, you can use the more
 * specialized [LabeledTabs](LabeledTabs) component, which will generate text
 * tab buttons for you.
 *
 * @inherits ElementBase
 * @mixes ContentItemsMixin
 * @mixes SingleSelectionMixin
 * @mixes SlotContentMixin
 */
class Tabs extends Base {

  componentDidMount() {
    if (super.componentDidMount) { super.componentDidMount(); }
    this.$.tabStrip.addEventListener('selected-index-changed', () => {
      /** @type {any} */
      const tabStrip = this.$.tabStrip;
      this.selectedIndex = tabStrip.selectedIndex;
    });
    this.$.tabButtonsSlot.addEventListener('slotchange', () => {
      updateDefaultTabButtons(this);
    });
  }

  /* eslint-disable no-unused-vars */
  componentDidUpdate(previousState) {
    if (super.componentDidMount) { super.componentDidMount(); }
    updateDefaultTabButtons(this);
  }

  get defaultState() {
    return Object.assign({}, super.defaultState, {
      selectionRequired: true,
      tabAlign: 'start',
      tabButtons: [],
      TabButtonClass: TabButton,
      tabPanelsTag: 'elix-modes',
      tabPosition: 'top'
    });
  }

  itemUpdates(item, calcs, original) {
    const base = super.itemUpdates ? super.itemUpdates(item, calcs, original) : {};

    // See notes in AriaListMixin for similar handling of ID.
    let id = item[generatedIdKey] ||
    original.attributes.id ||
    base.attributes && base.attributes.id;
    if (!id) {
      id = getIdForPanel(this, item, calcs.index);
      // Remember that we generated an ID for this item.
      item[generatedIdKey] = id;
    }

    // Look up corresponding tab button.
    const tabButtons = this.tabButtons;
    const tabButton = tabButtons && calcs.index < tabButtons.length ?
      tabButtons[calcs.index] :
      null;
    const tabButtonId = tabButton && tabButton.id;

    return {
      attributes: {
        id,
        'aria-labelledby': tabButtonId
      }
    };
  }

  [symbols.render]() {
    if (super[symbols.render]) { super[symbols.render](); }

    // Physically reorder the tabs and panels to reflect the desired arrangement.
    // We could change the visual appearance by reversing the order of the flex
    // box, but then the visual order wouldn't reflect the document order, which
    // determines focus order. That would surprise a user trying to tab through
    // the controls.
    const tabPosition = this.state.tabPosition;
    const topOrLeftPosition = (tabPosition === 'top' || tabPosition === 'left');
    const firstElement = topOrLeftPosition ?
      this.$.tabStrip :
      this.$.tabPanels;
    const lastElement = topOrLeftPosition ?
      this.$.tabPanels :
      this.$.tabStrip;
    if (!this.shadowRoot) {
      /* eslint-disable no-console */
      console.warn(`Tabs expects ${this.constructor.name} to define a shadowRoot.\nThis can be done with ShadowTemplateMixin: https://elix.org/documentation/ShadowTemplateMixin.`);
    } else if (firstElement.nextSibling !== lastElement) {
      this.shadowRoot.insertBefore(firstElement, lastElement);
    }
  }

  /**
   * The alignment of the tabs within the tab strip.
   *
   * @type {('start'|'center'|'end'|'stretch')}
   * @default 'start'
   */
  get tabAlign() {
    return this.state.tabAlign;
  }
  set tabAlign(tabAlign) {
    this.setState({ tabAlign });
  }

  /**
   * Returns the set of tab buttons.
   *
   * By default, this looks in the slot named `tabButtons` for items. If nothing
   * is assigned to that slot, this generates an array of [TabButton](TabButton)
   * elements for use as tab buttons, one for each tab panel.
   *
   * You can override this if you want the component to treat a different
   * collection of elements as the tab buttons.
   *
   * @returns {Element[]}
   */
  get tabButtons() {
    /** @type {any} */
    const tabButtonsSlot = this.$.tabButtonsSlot;
    const assignedButtons = tabButtonsSlot.assignedNodes({ flatten: true });
    return assignedButtons.length > 0 ?
      assignedButtons :
      defaultTabButtons(this);
  }

  /**
   * The position of the tab strip with respect to the associated tab panels.
   *
   * @type {('bottom'|'left'|'right'|'top')}
   * @default 'top'
   */
  get tabPosition() {
    return this.state.tabPosition;
  }
  set tabPosition(tabPosition) {
    this.setState({ tabPosition });
  }

  get [symbols.template]() {
    const tabPanelsTag = this.state.tabPanelsTag;
    return `
      <style>
        :host {
          display: inline-flex;
          position: relative;
        }
      </style>
      <elix-tab-strip id="tabStrip"><slot id="tabButtonsSlot" name="tabButtons"></slot></elix-tab-strip>
      <${tabPanelsTag} id="tabPanels" style="display: flex; flex: 1;">
        <slot></slot>
      </${tabPanelsTag}>
    `;
  }

  get updates() {
    const tabPosition = this.state.tabPosition;
    const lateralPosition = tabPosition === 'left' || tabPosition === 'right';

    // Generate tab buttons if no nodes are assigned to the tabButtons slot.
    // It'd be a lot cleaner if we could just stick the default tab buttons
    // directly inside the slot as default content. However, both Safari and the
    // polyfill have bugs that crop up when working with default slot content.
    // Instead, if we need default tab buttons, we'll append them to the tab
    // strip after the slot. We still need to leave the slot there so that, if
    // nodes are later assigned to the slot, we can detect that and drop our
    // default tab buttons.
    /** @type {any} */
    const tabButtons = this.state.tabButtons || [];
    let tabStripChildNodes = [this.$.tabButtonsSlot, ...tabButtons];

    // Some of the styling applied to the tabStrip and tabPanels is static, and
    // properly should be done through static CSS in the template. However,
    // Edge's flex box implementation seems to struggle with dynamic changes to
    // properties like flex-direction, and works better if we reapply the
    // styling each time.
    return merge(super.updates, {
      style: {
        'flex-direction': lateralPosition ? 'row' : 'column'
      },
      $: {
        tabStrip: {
          attributes: {
            'selected-index': this.state.selectedIndex,
            'tab-align': this.state.tabAlign,
            'tab-position': this.state.tabPosition
          },
          childNodes: tabStripChildNodes,
          style: {
            'z-index': 1
          }
        },
        tabPanels: {
          attributes: {
            'selected-index': this.state.selectedIndex
          },
          style: {
            'background': 'white',
            'border': '1px solid #ccc',
            'box-sizing': 'border-box',
            'flex': 1
          }
        }
      }
    });
  }

}

// Return true if arrays a and b have the same elements.
function arrayEquals(a, b) {
  if ((a && !b) || (!a && b) || (!a && !b) || (a.length !== b.length)) {
    return false;
  }
  return !a.some((element, index) => element !== b[index]);
}

// Return the set of buttons generated for the given Tabs element.
function defaultTabButtons(element) {
  if (element.items !== element[previousItemsKey]) {
    if (!element.items) {
      // No items yet.
      element[tabButtonsKey] = [];
    } else {
      // Items have changed; create new buttons set.
      element[tabButtonsKey] = element.items.map((panel, index) => {
        const label = panel.getAttribute('aria-label');
        const panelId = getIdForPanel(element, panel, index);
        const TabButtonClass = element.state.TabButtonClass;
        const tabButton = new TabButtonClass();
        const id = getIdForTabButton(element, index);
        tabButton.setAttribute('id', id);
        tabButton.setAttribute('aria-controls', panelId);
        tabButton.textContent = label;
        return tabButton;
      });
      // Make the array immutable.
      Object.freeze(element[tabButtonsKey]);
    }
    element[previousItemsKey] = element.items;
  }
  return element[tabButtonsKey];
}

function getIdForTabButton(element, index) {
  const hostId = element.id ?
    "_" + element.id + "Button" :
    "_button";
  const id = `${hostId}${index}`;
  return id;
}

function getIdForPanel(element, panel, index) {
  let id = panel.id;
  if (!id) {
    const hostId = element.id ?
      "_" + element.id + "Panel" :
      "_panel";
    id = `${hostId}${index}`;
  }
  return id;
}

// See if any nodes are assigned to the tabButtonsSlot and, if so, generate a
// set of default tab buttons. Alternatively, if we previously needed default
// tab buttons, but nodes have now been assigned to the slot, remove the default
// buttons. If anything changed, set state and trigger a render.
function updateDefaultTabButtons(element) {
  /** @type {any} */
  const tabButtonsSlot = element.$.tabButtonsSlot;
  const assignedButtons = tabButtonsSlot.assignedNodes({ flatten: true });
  const tabButtons = assignedButtons.length > 0 ?
    [] :
    defaultTabButtons(element);
  const changed = !arrayEquals(tabButtons, element.state.tabButtons);
  if (changed) {
    element.setState({ tabButtons });
  }
}

customElements.define('elix-tabs', Tabs);
export default Tabs;
<commit_msg>Fix copy/paste error and ensure componentDidUpdate calls super method correctly. Raise change event if inner tab strip raised an event.<commit_after>import './Modes.js';
import './TabStrip.js';
import { merge } from './updates.js';
import * as symbols from './symbols.js';
import ContentItemsMixin from './ContentItemsMixin.js';
import ElementBase from './ElementBase.js';
import SingleSelectionMixin from './SingleSelectionMixin.js';
import SlotContentMixin from './SlotContentMixin.js';
import TabButton from './TabButton.js';

const generatedIdKey = Symbol('generatedId');
const previousItemsKey = Symbol('previousItems');
const tabButtonsKey = Symbol('tabButtons');

const Base =
  ContentItemsMixin(
  SingleSelectionMixin(
  SlotContentMixin(
    ElementBase
  )));

/**
 * A set of pages with a tab strip governing which page is shown.
 *
 * Use tabs when you want to provide a large set of options or elements than
 * can comfortably fit inline, the options can be coherently grouped into pages,
 * and you want to avoid making the user navigate to a separate page. Tabs work
 * best if you only have a small handful of pages, say 2–7.
 *
 * This stock combination applies the [TabStripWrapper](TabStripWrapper) to a
 * [Modes](Modes) element. The former takes care of the relative positioning
 * of the tab buttons and tab panels; the latter takes care of displaying only
 * the currently-selected tab panel. If you'd like to create something more
 * complex than this arrangement, you can use either of those elements on its
 * own.
 *
 * You will need to provide `Tabs` with the buttons that will select the
 * corresponding tab panels. Do this by slotting the buttons into the slot named
 * "tabButtons". If you don't require custom tab buttons, you can use the more
 * specialized [LabeledTabs](LabeledTabs) component, which will generate text
 * tab buttons for you.
 *
 * @inherits ElementBase
 * @mixes ContentItemsMixin
 * @mixes SingleSelectionMixin
 * @mixes SlotContentMixin
 */
class Tabs extends Base {

  componentDidMount() {
    if (super.componentDidMount) { super.componentDidMount(); }
    this.$.tabStrip.addEventListener('selected-index-changed', () => {
      this[symbols.raiseChangeEvents] = true;
      /** @type {any} */
      const tabStrip = this.$.tabStrip;
      this.selectedIndex = tabStrip.selectedIndex;
      this[symbols.raiseChangeEvents] = false;
    });
    this.$.tabButtonsSlot.addEventListener('slotchange', () => {
      updateDefaultTabButtons(this);
    });
  }

  /* eslint-disable no-unused-vars */
  componentDidUpdate(previousState) {
    if (super.componentDidUpdate) { super.componentDidUpdate(previousState); }
    updateDefaultTabButtons(this);
  }

  get defaultState() {
    return Object.assign({}, super.defaultState, {
      selectionRequired: true,
      tabAlign: 'start',
      tabButtons: [],
      TabButtonClass: TabButton,
      tabPanelsTag: 'elix-modes',
      tabPosition: 'top'
    });
  }

  itemUpdates(item, calcs, original) {
    const base = super.itemUpdates ? super.itemUpdates(item, calcs, original) : {};

    // See notes in AriaListMixin for similar handling of ID.
    let id = item[generatedIdKey] ||
    original.attributes.id ||
    base.attributes && base.attributes.id;
    if (!id) {
      id = getIdForPanel(this, item, calcs.index);
      // Remember that we generated an ID for this item.
      item[generatedIdKey] = id;
    }

    // Look up corresponding tab button.
    const tabButtons = this.tabButtons;
    const tabButton = tabButtons && calcs.index < tabButtons.length ?
      tabButtons[calcs.index] :
      null;
    const tabButtonId = tabButton && tabButton.id;

    return {
      attributes: {
        id,
        'aria-labelledby': tabButtonId
      }
    };
  }

  [symbols.render]() {
    if (super[symbols.render]) { super[symbols.render](); }

    // Physically reorder the tabs and panels to reflect the desired arrangement.
    // We could change the visual appearance by reversing the order of the flex
    // box, but then the visual order wouldn't reflect the document order, which
    // determines focus order. That would surprise a user trying to tab through
    // the controls.
    const tabPosition = this.state.tabPosition;
    const topOrLeftPosition = (tabPosition === 'top' || tabPosition === 'left');
    const firstElement = topOrLeftPosition ?
      this.$.tabStrip :
      this.$.tabPanels;
    const lastElement = topOrLeftPosition ?
      this.$.tabPanels :
      this.$.tabStrip;
    if (!this.shadowRoot) {
      /* eslint-disable no-console */
      console.warn(`Tabs expects ${this.constructor.name} to define a shadowRoot.\nThis can be done with ShadowTemplateMixin: https://elix.org/documentation/ShadowTemplateMixin.`);
    } else if (firstElement.nextSibling !== lastElement) {
      this.shadowRoot.insertBefore(firstElement, lastElement);
    }
  }

  /**
   * The alignment of the tabs within the tab strip.
   *
   * @type {('start'|'center'|'end'|'stretch')}
   * @default 'start'
   */
  get tabAlign() {
    return this.state.tabAlign;
  }
  set tabAlign(tabAlign) {
    this.setState({ tabAlign });
  }

  /**
   * Returns the set of tab buttons.
   *
   * By default, this looks in the slot named `tabButtons` for items. If nothing
   * is assigned to that slot, this generates an array of [TabButton](TabButton)
   * elements for use as tab buttons, one for each tab panel.
   *
   * You can override this if you want the component to treat a different
   * collection of elements as the tab buttons.
   *
   * @returns {Element[]}
   */
  get tabButtons() {
    /** @type {any} */
    const tabButtonsSlot = this.$.tabButtonsSlot;
    const assignedButtons = tabButtonsSlot.assignedNodes({ flatten: true });
    return assignedButtons.length > 0 ?
      assignedButtons :
      defaultTabButtons(this);
  }

  /**
   * The position of the tab strip with respect to the associated tab panels.
   *
   * @type {('bottom'|'left'|'right'|'top')}
   * @default 'top'
   */
  get tabPosition() {
    return this.state.tabPosition;
  }
  set tabPosition(tabPosition) {
    this.setState({ tabPosition });
  }

  get [symbols.template]() {
    const tabPanelsTag = this.state.tabPanelsTag;
    return `
      <style>
        :host {
          display: inline-flex;
          position: relative;
        }
      </style>
      <elix-tab-strip id="tabStrip"><slot id="tabButtonsSlot" name="tabButtons"></slot></elix-tab-strip>
      <${tabPanelsTag} id="tabPanels" style="display: flex; flex: 1;">
        <slot></slot>
      </${tabPanelsTag}>
    `;
  }

  get updates() {
    const tabPosition = this.state.tabPosition;
    const lateralPosition = tabPosition === 'left' || tabPosition === 'right';

    // Generate tab buttons if no nodes are assigned to the tabButtons slot.
    // It'd be a lot cleaner if we could just stick the default tab buttons
    // directly inside the slot as default content. However, both Safari and the
    // polyfill have bugs that crop up when working with default slot content.
    // Instead, if we need default tab buttons, we'll append them to the tab
    // strip after the slot. We still need to leave the slot there so that, if
    // nodes are later assigned to the slot, we can detect that and drop our
    // default tab buttons.
    /** @type {any} */
    const tabButtons = this.state.tabButtons || [];
    let tabStripChildNodes = [this.$.tabButtonsSlot, ...tabButtons];

    // Some of the styling applied to the tabStrip and tabPanels is static, and
    // properly should be done through static CSS in the template. However,
    // Edge's flex box implementation seems to struggle with dynamic changes to
    // properties like flex-direction, and works better if we reapply the
    // styling each time.
    return merge(super.updates, {
      style: {
        'flex-direction': lateralPosition ? 'row' : 'column'
      },
      $: {
        tabStrip: {
          attributes: {
            'selected-index': this.state.selectedIndex,
            'tab-align': this.state.tabAlign,
            'tab-position': this.state.tabPosition
          },
          childNodes: tabStripChildNodes,
          style: {
            'z-index': 1
          }
        },
        tabPanels: {
          attributes: {
            'selected-index': this.state.selectedIndex
          },
          style: {
            'background': 'white',
            'border': '1px solid #ccc',
            'box-sizing': 'border-box',
            'flex': 1
          }
        }
      }
    });
  }

}

// Return true if arrays a and b have the same elements.
function arrayEquals(a, b) {
  if ((a && !b) || (!a && b) || (!a && !b) || (a.length !== b.length)) {
    return false;
  }
  return !a.some((element, index) => element !== b[index]);
}

// Return the set of buttons generated for the given Tabs element.
function defaultTabButtons(element) {
  if (element.items !== element[previousItemsKey]) {
    if (!element.items) {
      // No items yet.
      element[tabButtonsKey] = [];
    } else {
      // Items have changed; create new buttons set.
      element[tabButtonsKey] = element.items.map((panel, index) => {
        const label = panel.getAttribute('aria-label');
        const panelId = getIdForPanel(element, panel, index);
        const TabButtonClass = element.state.TabButtonClass;
        const tabButton = new TabButtonClass();
        const id = getIdForTabButton(element, index);
        tabButton.setAttribute('id', id);
        tabButton.setAttribute('aria-controls', panelId);
        tabButton.textContent = label;
        return tabButton;
      });
      // Make the array immutable.
      Object.freeze(element[tabButtonsKey]);
    }
    element[previousItemsKey] = element.items;
  }
  return element[tabButtonsKey];
}

function getIdForTabButton(element, index) {
  const hostId = element.id ?
    "_" + element.id + "Button" :
    "_button";
  const id = `${hostId}${index}`;
  return id;
}

function getIdForPanel(element, panel, index) {
  let id = panel.id;
  if (!id) {
    const hostId = element.id ?
      "_" + element.id + "Panel" :
      "_panel";
    id = `${hostId}${index}`;
  }
  return id;
}

// See if any nodes are assigned to the tabButtonsSlot and, if so, generate a
// set of default tab buttons. Alternatively, if we previously needed default
// tab buttons, but nodes have now been assigned to the slot, remove the default
// buttons. If anything changed, set state and trigger a render.
function updateDefaultTabButtons(element) {
  /** @type {any} */
  const tabButtonsSlot = element.$.tabButtonsSlot;
  const assignedButtons = tabButtonsSlot.assignedNodes({ flatten: true });
  const tabButtons = assignedButtons.length > 0 ?
    [] :
    defaultTabButtons(element);
  const changed = !arrayEquals(tabButtons, element.state.tabButtons);
  if (changed) {
    element.setState({ tabButtons });
  }
}

customElements.define('elix-tabs', Tabs);
export default Tabs;
<|endoftext|>


<commit_before>Groups = new Mongo.Collection("groups");

Meteor.startup(function () {
	Meteor.publish('userScores', function () {
		return Meteor.users.find({}, {profile: {displayName: 1, subs: 1, rejs: 1, accs: 1, wds: 1, score: 1, groupId: 1}});
	});

	/// Email Verification nonsense cribbed from gentlenode.com
	Accounts.emailTemplates.from = 'Sink or Submit <no-reply@meteor.com>';

	Accounts.emailTemplates.siteName = 'Sink or Submit';

	Accounts.emailTemplates.verifyEmail.subject = function (user) {
		return 'Confirm Your Email Address';
	};

	Accounts.emailTemplates.verifyEmail.text = function (user, url) {
		return 'Click the following link to verify your email address for Sink or Submit: ' + url;
	};

/*	Meteor.publish('groups', function () {
		return Groups.find(Meteor.user().groupId, {fields: {groupId: 1, groupDescription: 1}});
	});*/
});

Accounts.onCreateUser(function(options, user) {
	user.displayName = user.username;
	user.subs = 0;
	user.rejs = 0;
	user.accs = 0;
	user.wds = 0;
	user.score = 0;
	user.prevScore = 0;
	user.groupId = null;
  	user.groupAdmin = false;

	if (options.profile)
		user.profile = options.profile;

	Meteor.setTimeout(function () {
		Accounts.sendVerificationEmail(user._id);
	}, 2 * 1000);

	return user;
});

Accounts.validateLoginAttempt(function (attempt) {
	if (attempt.user && attempt.user.email && !attempt.user.emaills[0].verified ) {
		console.log('email not verified');

		return false;
	}

	return true;
});

Meteor.publish('userData', function() {
	if (!this.userId) return null;
	return Meteor.users.find(this.userId, {fields: {
		displayName: 1,
		subs: 1,
		rejs: 1,
		accs: 1,
		wds: 1,
		score: 1,
	  	groupId: 1,
	  	groupAdmin: 1}
	});
});

Meteor.publish("groups", function () {
	return Groups.find({}, {fields: {
		groupId: 1,
		groupDescription: 1,
		members: 1,
		admins: 1}
	});
});

Meteor.methods({
	setDisplayName: function (name) {
		Meteor.users.update(Meteor.userId(), {$set: {displayName: name}});
	},

	joinGroup: function (group, admin, secret) {
		// confirms that secret provided matches with group secret
		if (secret === Groups.findOne({groupId: group}, {fields: {secret: 1}}).secret) {
			// takes a group's shortname and converts it to group's _id
			var id = Groups.findOne({groupId: group}, {fields: {_id: 1}})._id;
			Meteor.users.update(Meteor.userId(), {$set: {groupId: id}});
			Meteor.call("memberNum", id);
			if (admin)
				Meteor.call("makeAdmin", Meteor.userId(), admin);
		}
	},

	memberNum: function (gid) {
		var number = Meteor.users.find({groupId: gid}).fetch().length;
		console.log("This group now has " + number + " members.");
		Groups.update(gid, {$set: {members: number}});
	},

	removeGroup: function (gid) {
		if (Groups.findOne(gid).members === 0) {
			console.log("Removing group " + gid);
			Groups.remove(gid);
		}
	},

	changeGroupName: function (name) {
		Groups.update(Meteor.user().groupId, {$set: {groupDescription: name}});
	},

	changeGroupSecret: function (oldSecret, newSecret) {
		if (Groups.findOne(Meteor.user().groupId).secret === oldSecret)
			Groups.update(Meteor.user().groupId, {$set: {secret: newSecret}});
	},

	makeAdmin: function (id, admin) {
		// Below comment no longer true, but I'm keeping it there so that this stupid function will
		// remember what it's done.
		//
		// THIS PIECE OF SHIT STILL WON'T GIVE ACCURATE NUMBERS ON GROUP ADMINS ON THE FIRST TRY
		// AND I HATE ITS GUTS
		var gid = Meteor.users.findOne(id).groupId;
		console.log("Group id: " + gid);
		if (admin) {
			console.log("Adding admin");
			Meteor.users.update(id, {$set: {groupAdmin: admin}});
			var num = Meteor.users.find({groupId: gid, groupAdmin: true}).fetch().length;
			console.log("Number of admins in group: " + num);
			Groups.update(gid, {$set: {admins: num}});
			console.log("Group.find.admins: " + Groups.findOne(Meteor.users.findOne(id).groupId).admins);
		} else {
			console.log("Setting admin status to " + admin);
			Meteor.users.update(id, {$set: {groupAdmin: admin}});
			console.log("Calculating number of admins...");
			var num = Meteor.users.find({groupId: gid, groupAdmin: true}).fetch().length;
			console.log("Number of admins in group, according to users.find.length method: " + num);
			Groups.update(gid, {$set: {admins: num}});
			console.log("Group.find.admins: " + Groups.findOne(Meteor.users.findOne(id).groupId).admins);
		}
		console.log("Final number of admins: " + num);
	},

	adminNum: function (gid, number) {
		Groups.update(gid, {$set: {admins: number}});
	},

	leaveGroup: function (id) {
		// Set user id's group to null and strip admin privileges
		var gid = Meteor.users.findOne(id).groupId;
		console.log("User with id " + id + " leaving group");
		Meteor.call("makeAdmin", id, false);
		Meteor.users.update(id, {$set: {groupId: null}});
		Meteor.call("memberNum", gid);
	},

	createGroup: function (groupId, groupDescription, secret) {
		if (! Meteor.userId())
			throw new Meteor.Error("not-authorized");

		Groups.insert({
			groupId: groupId,
			groupDescription: groupDescription,
			secret: secret
		});
	},

  	incSubs: function () {
  		Meteor.users.update(Meteor.userId(), {$inc: {subs: 1}});
		Meteor.users.update(Meteor.userId(), {$inc: {score: 1}});
  	},

  	decSubs: function () {
  		Meteor.users.update(Meteor.userId(), {$inc: {subs: -1}});
		Meteor.users.update(Meteor.userId(), {$inc: {score: -1}});
  	},

  	incRejs: function () {
  		Meteor.users.update(Meteor.userId(), {$inc: {rejs: 1}});
		Meteor.users.update(Meteor.userId(), {$inc: {score: 1}});
  	},

  	decRejs: function () {
  		Meteor.users.update(Meteor.userId(), {$inc: {rejs: -1}});
		Meteor.users.update(Meteor.userId(), {$inc: {score: -1}});
  	},

  	incAccs: function () {
		// Buffers user's previous score in case of accidental acceptance log
		var score = Meteor.user().score;
  		Meteor.users.update(Meteor.userId(), {$inc: {accs: 1}});
		Meteor.users.update(Meteor.userId(), {$set: {prevScore: score}});
		Meteor.users.update(Meteor.userId(), {$set: {score: 0}});
  	},

  	decAccs: function () {
		// Restores user's score in case of accidental acceptance log
		var prevScore = Meteor.user().prevScore;
  		Meteor.users.update(Meteor.userId(), {$inc: {accs: -1}});
		Meteor.users.update(Meteor.userId(), {$set: {score: prevScore}});
  	},

  	incWds: function () {
  		Meteor.users.update(Meteor.userId(), {$inc: {wds: 1}});
  	},

  	decWds: function () {
  		Meteor.users.update(Meteor.userId(), {$inc: {wds: -1}});
  	},

	setGroup: function (group) {
		Meteor.users.update(Meteor.user(), {$set: {groupId: group}});
	}

});
<commit_msg>Email verification working. Need final styling for login buttons, but we have a product!<commit_after>Groups = new Mongo.Collection("groups");

Meteor.startup(function () {
	Meteor.publish('userScores', function () {
		return Meteor.users.find({}, {profile: {displayName: 1, subs: 1, rejs: 1, accs: 1, wds: 1, score: 1, groupId: 1}});
	});

	/// Email Verification nonsense cribbed from gentlenode.com
	Accounts.emailTemplates.from = 'Sink or Submit <no-reply@meteor.com>';

	Accounts.emailTemplates.siteName = 'Sink or Submit';

	Accounts.emailTemplates.verifyEmail.subject = function (user) {
		return 'Confirm Your Email Address';
	};

	Accounts.emailTemplates.verifyEmail.text = function (user, url) {
		return 'Click the following link to verify your email address for Sink or Submit: ' + url;
	};

/*	Meteor.publish('groups', function () {
		return Groups.find(Meteor.user().groupId, {fields: {groupId: 1, groupDescription: 1}});
	});*/
});

Accounts.onCreateUser(function(options, user) {
	user.displayName = user.username;
	user.subs = 0;
	user.rejs = 0;
	user.accs = 0;
	user.wds = 0;
	user.score = 0;
	user.prevScore = 0;
	user.groupId = null;
  	user.groupAdmin = false;

	if (options.profile)
		user.profile = options.profile;

	Meteor.setTimeout(function () {
		Accounts.sendVerificationEmail(user._id);
	}, 2 * 1000);

	return user;
});

Accounts.validateLoginAttempt(function (attempt) {
	if (attempt.user && attempt.user.emails && !attempt.user.emails[0].verified ) {
		console.log('email not verified');

		return false;
	}

	return true;
});

Meteor.publish('userData', function() {
	if (!this.userId) return null;
	return Meteor.users.find(this.userId, {fields: {
		displayName: 1,
		subs: 1,
		rejs: 1,
		accs: 1,
		wds: 1,
		score: 1,
	  	groupId: 1,
	  	groupAdmin: 1}
	});
});

Meteor.publish("groups", function () {
	return Groups.find({}, {fields: {
		groupId: 1,
		groupDescription: 1,
		members: 1,
		admins: 1}
	});
});

Meteor.methods({
	setDisplayName: function (name) {
		Meteor.users.update(Meteor.userId(), {$set: {displayName: name}});
	},

	joinGroup: function (group, admin, secret) {
		// confirms that secret provided matches with group secret
		if (secret === Groups.findOne({groupId: group}, {fields: {secret: 1}}).secret) {
			// takes a group's shortname and converts it to group's _id
			var id = Groups.findOne({groupId: group}, {fields: {_id: 1}})._id;
			Meteor.users.update(Meteor.userId(), {$set: {groupId: id}});
			Meteor.call("memberNum", id);
			if (admin)
				Meteor.call("makeAdmin", Meteor.userId(), admin);
		}
	},

	memberNum: function (gid) {
		var number = Meteor.users.find({groupId: gid}).fetch().length;
		console.log("This group now has " + number + " members.");
		Groups.update(gid, {$set: {members: number}});
	},

	removeGroup: function (gid) {
		if (Groups.findOne(gid).members === 0) {
			console.log("Removing group " + gid);
			Groups.remove(gid);
		}
	},

	changeGroupName: function (name) {
		Groups.update(Meteor.user().groupId, {$set: {groupDescription: name}});
	},

	changeGroupSecret: function (oldSecret, newSecret) {
		if (Groups.findOne(Meteor.user().groupId).secret === oldSecret)
			Groups.update(Meteor.user().groupId, {$set: {secret: newSecret}});
	},

	makeAdmin: function (id, admin) {
		// Below comment no longer true, but I'm keeping it there so that this stupid function will
		// remember what it's done.
		//
		// THIS PIECE OF SHIT STILL WON'T GIVE ACCURATE NUMBERS ON GROUP ADMINS ON THE FIRST TRY
		// AND I HATE ITS GUTS
		var gid = Meteor.users.findOne(id).groupId;
		console.log("Group id: " + gid);
		if (admin) {
			console.log("Adding admin");
			Meteor.users.update(id, {$set: {groupAdmin: admin}});
			var num = Meteor.users.find({groupId: gid, groupAdmin: true}).fetch().length;
			console.log("Number of admins in group: " + num);
			Groups.update(gid, {$set: {admins: num}});
			console.log("Group.find.admins: " + Groups.findOne(Meteor.users.findOne(id).groupId).admins);
		} else {
			console.log("Setting admin status to " + admin);
			Meteor.users.update(id, {$set: {groupAdmin: admin}});
			console.log("Calculating number of admins...");
			var num = Meteor.users.find({groupId: gid, groupAdmin: true}).fetch().length;
			console.log("Number of admins in group, according to users.find.length method: " + num);
			Groups.update(gid, {$set: {admins: num}});
			console.log("Group.find.admins: " + Groups.findOne(Meteor.users.findOne(id).groupId).admins);
		}
		console.log("Final number of admins: " + num);
	},

	adminNum: function (gid, number) {
		Groups.update(gid, {$set: {admins: number}});
	},

	leaveGroup: function (id) {
		// Set user id's group to null and strip admin privileges
		var gid = Meteor.users.findOne(id).groupId;
		console.log("User with id " + id + " leaving group");
		Meteor.call("makeAdmin", id, false);
		Meteor.users.update(id, {$set: {groupId: null}});
		Meteor.call("memberNum", gid);
	},

	createGroup: function (groupId, groupDescription, secret) {
		if (! Meteor.userId())
			throw new Meteor.Error("not-authorized");

		Groups.insert({
			groupId: groupId,
			groupDescription: groupDescription,
			secret: secret
		});
	},

  	incSubs: function () {
  		Meteor.users.update(Meteor.userId(), {$inc: {subs: 1}});
		Meteor.users.update(Meteor.userId(), {$inc: {score: 1}});
  	},

  	decSubs: function () {
  		Meteor.users.update(Meteor.userId(), {$inc: {subs: -1}});
		Meteor.users.update(Meteor.userId(), {$inc: {score: -1}});
  	},

  	incRejs: function () {
  		Meteor.users.update(Meteor.userId(), {$inc: {rejs: 1}});
		Meteor.users.update(Meteor.userId(), {$inc: {score: 1}});
  	},

  	decRejs: function () {
  		Meteor.users.update(Meteor.userId(), {$inc: {rejs: -1}});
		Meteor.users.update(Meteor.userId(), {$inc: {score: -1}});
  	},

  	incAccs: function () {
		// Buffers user's previous score in case of accidental acceptance log
		var score = Meteor.user().score;
  		Meteor.users.update(Meteor.userId(), {$inc: {accs: 1}});
		Meteor.users.update(Meteor.userId(), {$set: {prevScore: score}});
		Meteor.users.update(Meteor.userId(), {$set: {score: 0}});
  	},

  	decAccs: function () {
		// Restores user's score in case of accidental acceptance log
		var prevScore = Meteor.user().prevScore;
  		Meteor.users.update(Meteor.userId(), {$inc: {accs: -1}});
		Meteor.users.update(Meteor.userId(), {$set: {score: prevScore}});
  	},

  	incWds: function () {
  		Meteor.users.update(Meteor.userId(), {$inc: {wds: 1}});
  	},

  	decWds: function () {
  		Meteor.users.update(Meteor.userId(), {$inc: {wds: -1}});
  	},

	setGroup: function (group) {
		Meteor.users.update(Meteor.user(), {$set: {groupId: group}});
	}

});
<|endoftext|>


<commit_before>process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';

var fs = require('fs');
var deepExtend = require('extend');
var stringify = require('json-stable-stringify');
var winston = require('winston');
var moment = require('moment');
var restClient = require('node-rest-client').Client;

var options = {
	netmonUrl: "https://netmon.freifunk-franken.de/api/rest/routerlist/?limit=2000",
	communitysFile: 'communitys_franken.json',
	outputDir: '../freifunkfranken-community/',
	outputPrefix: '',
	cacheRouterlistFile: './cache.routerlist.json',
	cacheMaxAge: 30, //in minutes
};

var client = new restClient();
var routerlist = null;
var routers = null;

// gets loaded from file
var metacommunity = null;
var communities = null;

function extend(destination, source) {
	for ( var property in source) {
		if (!destination[property]) {
			destination[property] = source[property];
		} else if (destination[property] instanceof Array && source[property] instanceof Array) {
			Array.prototype.push.apply(destination[property], source[property]);
		} else {
			if (typeof destination[property] == 'object') {
				deepExtend(true, destination[property], source[property]);
			}
		}
	}
	return destination;
}

function saveCommunityData(data, output_filename) {
	fs.writeFile(output_filename, stringify(data, {space: 4}), function(err) {
		if (err) {
			winston.error(err);
		} else {
			winston.info("Updated " + output_filename);
		}
	});
}

function updateCommunity(community, onlineCount, offlineCount) {
	var output_filename = options.outputDir + options.outputPrefix + community.id + ".json";
	if (!community.data.state) {
		community.data.state = {};
	}
	community.data.state.nodes = onlineCount;
	community.data.state.lastchange = new Date().toJSON();

	winston.info("==============================================================");
	winston.info("Online nodes in community '" + community.data.name + "': " + onlineCount + " (offine: " + offlineCount + ") \n");
	saveCommunityData(community.data, output_filename);
}

function getDistance(p1, p2) {
	function toRad(n) {
		return n * (Math.PI / 180);
	}
	var dLat = toRad(p2.lat - p1.lat);
	var dLon = toRad(p2.lon - p1.lon);
	var lat1 = toRad(p1.lat);
	var lat2 = toRad(p2.lat);
	var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.sin(dLon / 2) * Math.sin(dLon / 2) * Math.cos(p1.lat) * Math.cos(p2.lat);
	var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
	var R = 6371; // km
	var d = R * c;
	return d;
}

function updateCommunities(data) {
	var onlineCount = 0, offlineCount = 0;
	routerlist = data.netmon_response.routerlist;
	routers = data.netmon_response.routerlist[0].router;

	// write community files
	var length = communities.length;
	var community = {};
	for (var i = 0; i < length; i = i + 1) {
		onlineCount = offlineCount = 0;
		winston.info("Routers in " + communities[i].data.name + " (radius: " + communities[i].radius + " km): ");
		winston.info("-------------------------------------------------------------");
		for (var j = 0; j < routers.length; j = j + 1) {
			var distance = getDistance({
				"lat" : parseFloat(routers[j].latitude),
				"lon" : parseFloat(routers[j].longitude)
			}, communities[i].data.location).toFixed(2);

			if (distance < communities[i].radius) {
				if (routers[j].statusdata[0].status[0] === "online") {
					winston.info("Node ON  " + routers[j].hostname[0] + ", distance: " + distance + " km");
					onlineCount = onlineCount + 1;
				} else {
					winston.info("Node OFF " + routers[j].hostname[0] + ", distance: " + distance + " km");
					offlineCount = offlineCount + 1;
				}
			}
		}
		community.id = communities[i].id;
		community.data = extend(communities[i].data, metacommunity.data);
		//community.data.contact = extend(communities[i].data.contact, metacommunity.data.contact);
		updateCommunity(community, onlineCount, offlineCount);
	}
}

function exec(instructions) {
	var terminal = require('child_process').spawn('bash');
	terminal.stdout.on('data', function (data) {
		console.log("" + data);
	});
	terminal.on('exit', function (code) {
		console.log("-------------------------------------------------------");
		console.log("DONE");
	});

	for (var i=0; i<instructions.length; i=i+1) {
		console.log('\n' + instructions[i]);
		console.log("-------------------------------------------------------");
		terminal.stdin.write(instructions[i] + "\n");
	}
	terminal.stdin.end();
}

function run() {
	fs.readFile(options.communitysFile, 'utf8', function read(err, data) {
		if (err) {
			throw err;
		}
		data = JSON.parse(data);
		metacommunity = data.metacommunity;
		communities = data.communities;

		fs.stat(options.cacheRouterlistFile, function(err, stats) {
			var minutesDiff = -1;
			if (!err) {
				var mtime = stats.mtime;
				var fileDate = moment(mtime);
				var now = moment();
				minutesDiff = now.diff(fileDate, 'minutes');
				winston.info("Age of local routerlist: " + minutesDiff + " minutes\n");
			}
			if (err || (minutesDiff > options.cacheMaxAge)) {
				winston.info("Loading routerlist from netmon...");
				//TODO split into multiple requests
				client.get(options.netmonUrl,
					function(data, response) {
						winston.info("saving data to file " + options.cacheRouterlistFile + "\n");
						fs.writeFile(options.cacheRouterlistFile, stringify(data, {space: 4}), function(err) {
							if (err) {
								winston.error(err);
							}
						});
						updateCommunities(data);
					}
				);
			} else  {
				winston.info("Loading local routerlist...");
				fs.readFile(options.cacheRouterlistFile, 'utf8', function read(err, data) {
					if (err) {
						throw err;
					}
					updateCommunities(JSON.parse(data));
				});
			}
		});
	});
}

run();
<commit_msg>removed unused code<commit_after>process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';

var fs = require('fs');
var deepExtend = require('extend');
var stringify = require('json-stable-stringify');
var winston = require('winston');
var moment = require('moment');
var restClient = require('node-rest-client').Client;

var options = {
	netmonUrl: "https://netmon.freifunk-franken.de/api/rest/routerlist/?limit=2000",
	communitysFile: 'communitys_franken.json',
	outputDir: '../freifunkfranken-community/',
	outputPrefix: '',
	cacheRouterlistFile: './cache.routerlist.json',
	cacheMaxAge: 30, //in minutes
};

var client = new restClient();
var routerlist = null;
var routers = null;

// gets loaded from file
var metacommunity = null;
var communities = null;

function extend(destination, source) {
	for ( var property in source) {
		if (!destination[property]) {
			destination[property] = source[property];
		} else if (destination[property] instanceof Array && source[property] instanceof Array) {
			Array.prototype.push.apply(destination[property], source[property]);
		} else {
			if (typeof destination[property] == 'object') {
				deepExtend(true, destination[property], source[property]);
			}
		}
	}
	return destination;
}

function saveCommunityData(data, output_filename) {
	fs.writeFile(output_filename, stringify(data, {space: 4}), function(err) {
		if (err) {
			winston.error(err);
		} else {
			winston.info("Updated " + output_filename);
		}
	});
}

function updateCommunity(community, onlineCount, offlineCount) {
	var output_filename = options.outputDir + options.outputPrefix + community.id + ".json";
	if (!community.data.state) {
		community.data.state = {};
	}
	community.data.state.nodes = onlineCount;
	community.data.state.lastchange = new Date().toJSON();

	winston.info("==============================================================");
	winston.info("Online nodes in community '" + community.data.name + "': " + onlineCount + " (offine: " + offlineCount + ") \n");
	saveCommunityData(community.data, output_filename);
}

function getDistance(p1, p2) {
	function toRad(n) {
		return n * (Math.PI / 180);
	}
	var dLat = toRad(p2.lat - p1.lat);
	var dLon = toRad(p2.lon - p1.lon);
	var lat1 = toRad(p1.lat);
	var lat2 = toRad(p2.lat);
	var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.sin(dLon / 2) * Math.sin(dLon / 2) * Math.cos(p1.lat) * Math.cos(p2.lat);
	var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
	var R = 6371; // km
	var d = R * c;
	return d;
}

function updateCommunities(data) {
	var onlineCount = 0, offlineCount = 0;
	routerlist = data.netmon_response.routerlist;
	routers = data.netmon_response.routerlist[0].router;

	// write community files
	var length = communities.length;
	var community = {};
	for (var i = 0; i < length; i = i + 1) {
		onlineCount = offlineCount = 0;
		winston.info("Routers in " + communities[i].data.name + " (radius: " + communities[i].radius + " km): ");
		winston.info("-------------------------------------------------------------");
		for (var j = 0; j < routers.length; j = j + 1) {
			var distance = getDistance({
				"lat" : parseFloat(routers[j].latitude),
				"lon" : parseFloat(routers[j].longitude)
			}, communities[i].data.location).toFixed(2);

			if (distance < communities[i].radius) {
				if (routers[j].statusdata[0].status[0] === "online") {
					winston.info("Node ON  " + routers[j].hostname[0] + ", distance: " + distance + " km");
					onlineCount = onlineCount + 1;
				} else {
					winston.info("Node OFF " + routers[j].hostname[0] + ", distance: " + distance + " km");
					offlineCount = offlineCount + 1;
				}
			}
		}
		community.id = communities[i].id;
		community.data = extend(communities[i].data, metacommunity.data);

		updateCommunity(community, onlineCount, offlineCount);
	}
}

function exec(instructions) {
	var terminal = require('child_process').spawn('bash');
	terminal.stdout.on('data', function (data) {
		console.log("" + data);
	});
	terminal.on('exit', function (code) {
		console.log("-------------------------------------------------------");
		console.log("DONE");
	});

	for (var i=0; i<instructions.length; i=i+1) {
		console.log('\n' + instructions[i]);
		console.log("-------------------------------------------------------");
		terminal.stdin.write(instructions[i] + "\n");
	}
	terminal.stdin.end();
}

function run() {
	fs.readFile(options.communitysFile, 'utf8', function read(err, data) {
		if (err) {
			throw err;
		}
		data = JSON.parse(data);
		metacommunity = data.metacommunity;
		communities = data.communities;

		fs.stat(options.cacheRouterlistFile, function(err, stats) {
			var minutesDiff = -1;
			if (!err) {
				var mtime = stats.mtime;
				var fileDate = moment(mtime);
				var now = moment();
				minutesDiff = now.diff(fileDate, 'minutes');
				winston.info("Age of local routerlist: " + minutesDiff + " minutes\n");
			}
			if (err || (minutesDiff > options.cacheMaxAge)) {
				winston.info("Loading routerlist from netmon...");
				//TODO split into multiple requests
				client.get(options.netmonUrl,
					function(data, response) {
						winston.info("saving data to file " + options.cacheRouterlistFile + "\n");
						fs.writeFile(options.cacheRouterlistFile, stringify(data, {space: 4}), function(err) {
							if (err) {
								winston.error(err);
							}
						});
						updateCommunities(data);
					}
				);
			} else  {
				winston.info("Loading local routerlist...");
				fs.readFile(options.cacheRouterlistFile, 'utf8', function read(err, data) {
					if (err) {
						throw err;
					}
					updateCommunities(JSON.parse(data));
				});
			}
		});
	});
}

run();
<|endoftext|>


<commit_before>'use strict';

require('../test_helper');

var _ = require('lodash'),
    apiUmbrellaConfig = require('api-umbrella-config'),
    async = require('async'),
    execFile = require('child_process').execFile,
    Factory = require('factory-lady'),
    fs = require('fs'),
    path = require('path'),
    processEnv = require('../../lib/process_env'),
    request = require('request');

var config = apiUmbrellaConfig.load(path.resolve(__dirname, '../config/test.yml'));

describe('processes', function() {
  shared.runServer();

  describe('nginx', function() {
    it('does not leak file descriptors across reloads', function(done) {
      this.timeout(40000);

      var execOpts = { env: processEnv.env() };
      execFile('pgrep', ['-f', 'nginx: master'], function(error, stdout, stderr) {
        if(error) {
          return done('Error fetching nginx pid: ' + error + ' (STDOUT: ' + stdout + ', STDERR: ' + stderr + ')');
        }

        if(!stdout || !/^\d+$/.test(stdout.trim())) {
          return done('No PID returned for nginx (STDOUT: ' + stdout + ', STDERR: ' + stderr + ')');
        }

        var parentPid = parseInt(stdout, 10);
        var descriptorCounts = [];
        var urandomDescriptorCounts = [];

        async.timesSeries(15, function(index, next) {
          execFile('pkill', ['-HUP', '-f', 'nginx: master'], function(error, stdout, stderr) {
            if(error) {
              return next('Error reloading nginx: ' + error + ' (STDOUT: ' + stdout + ', STDERR: ' + stderr + ')');
            }

            setTimeout(function() {
              execFile('lsof', ['-R', '-c', 'nginx'], execOpts, function(error, stdout, stderr) {
                if(error) {
                  return next('Error gathering lsof details: ' + error.message + '\n\nSTDOUT: ' + stdout + '\n\nSTDERR:' + stderr);
                }

                var lines = stdout.split('\n');
                var descriptorCount = 0;
                var urandomDescriptorCount = 0;
                lines.forEach(function(line) {
                  var columns = line.split(/\s+/);
                  if(parseInt(columns[1], 10) === parentPid || parseInt(columns[2], 10) === parentPid) {
                    descriptorCount++;

                    if(_.contains(line, 'urandom')) {
                      urandomDescriptorCount++;
                    }
                  }
                });

                descriptorCounts.push(descriptorCount);
                urandomDescriptorCounts.push(urandomDescriptorCount);

                setTimeout(function() {
                  next(null, lines.length);
                }, 500);
              });
            }, 500);
          });
        }, function(error) {
          if(error) {
            return done(error);
          }

          // Test to ensure ngx_txid isn't leaving open file descriptors around
          // on reloads test for this patch:
          // https://github.com/streadway/ngx_txid/pull/6
          urandomDescriptorCounts.length.should.eql(15);
          urandomDescriptorCounts[0].should.be.greaterThan(0);
          _.max(urandomDescriptorCounts).should.eql(_.min(urandomDescriptorCounts));

          // A more general test to ensure that we don't see other unexpected
          // file descriptor growth. We'll allow some growth for this test,
          // though, just to account for small fluctuations in sockets due to
          // other things nginx may be doing.
          descriptorCounts.length.should.eql(15);
          _.min(descriptorCounts).should.be.greaterThan(0);
          var range = _.max(descriptorCounts) - _.min(descriptorCounts);
          range.should.be.lessThan(10);

          done();
        });
      });
    });
  });

  describe('reload', function() {
    before(function publishDbConfig(done) {
      this.timeout(10000);

      // Be sure that these tests interact with a backend published via Mongo,
      // so we can also catch errors for when the mongo-based configuration
      // data experiences failures.
      shared.setRuntimeConfigOverrides({
        apis: [
          {
            _id: 'db-config',
            frontend_host: 'localhost',
            backend_host: 'localhost',
            servers: [
              {
                host: '127.0.0.1',
                port: 9444,
              }
            ],
            url_matches: [
              {
                frontend_prefix: '/db-config/hello',
                backend_prefix: '/hello',
              }
            ],
          },
        ],
      }, function(error) {
        should.not.exist(error);
        shared.waitForConfig(done);
      });
    });

    beforeEach(function setOptionDefaults(done) {
      Factory.create('api_user', { settings: { rate_limit_mode: 'unlimited' } }, function(user) {
        this.user = user;
        this.apiKey = user.api_key;
        this.options = {
          followRedirect: false,
          headers: {
            'X-Api-Key': this.apiKey,
            'X-Disable-Router-Connection-Limits': 'yes',
            'X-Disable-Router-Rate-Limits': 'yes',
          },
          agentOptions: {
            maxSockets: 500,
          },
        };

        done();
      }.bind(this));
    });

    after(function removeDbConfig(done) {
      this.timeout(6000);

      // Remove DB-based config after these tests, so the rest of the tests go
      // back to the file-based configs.
      shared.revertRuntimeConfigOverrides(function(error) {
        should.not.exist(error);
        shared.waitForConfig(done);
      });
    });

    it('updates templates with file-based config file changes', function(done) {
      this.timeout(90000);

      var nginxFile = path.join(config.get('etc_dir'), 'nginx/router.conf');
      var nginxConfig = fs.readFileSync(nginxFile).toString();
      nginxConfig.should.contain('worker_processes 4;');

      shared.setConfigOverrides({
        nginx: {
          workers: 1,
        },
      }, function(error) {
        should.not.exist(error);
        shared.waitForConfig(function(error) {
          should.not.exist(error);

          nginxConfig = fs.readFileSync(nginxFile).toString();
          nginxConfig.should.contain('worker_processes 1;');

          shared.setConfigOverrides({}, function(error) {
            should.not.exist(error);
            shared.waitForConfig(function(error) {
              should.not.exist(error);

              nginxConfig = fs.readFileSync(nginxFile).toString();
              nginxConfig.should.contain('worker_processes 4;');

              done();
            });
          });
        });
      });
    });

    it('does not drop connections during reloads', function(done) {
      this.timeout(60000);

      execFile('pgrep', ['-f', 'nginx: worker'], function(error, stdout, stderr) {
        if(error) {
          return done('Error fetching nginx pid: ' + error + ' (STDOUT: ' + stdout + ', STDERR: ' + stderr + ')');
        }

        var originalPids = stdout.split('\n').sort();

        var runTests = true;
        setTimeout(function() {
          execFile('pkill', ['-HUP', '-f', 'nginx: master'], function(error, stdout, stderr) {
            if(error) {
              return done('Error reloading nginx: ' + error + ' (STDOUT: ' + stdout + ', STDERR: ' + stderr + ')');
            }

            setTimeout(function() { runTests = false; }, 5000);
          });
        }.bind(this), 100);

        async.whilst(function() { return runTests; }, function(whilstCallback) {
          request.get('http://localhost:9080/db-config/hello', this.options, function(error, response, body) {
            should.not.exist(error);
            response.statusCode.should.eql(200);
            body.should.eql('Hello World');

            whilstCallback(error);
          });
        }.bind(this), function(error) {
          should.not.exist(error);

          execFile('pgrep', ['-f', 'nginx: worker'], function(error, stdout, stderr) {
            if(error) {
              return done('Error fetching nginx pid: ' + error + ' (STDOUT: ' + stdout + ', STDERR: ' + stderr + ')');
            }

            var finalPids = stdout.split('\n').sort();
            finalPids.should.not.eql(originalPids);
            done();
          });
        });
      }.bind(this));
    });
  });
});
<commit_msg>Fix nginx reload file descriptor tests in lua branch.<commit_after>'use strict';

require('../test_helper');

var _ = require('lodash'),
    apiUmbrellaConfig = require('api-umbrella-config'),
    async = require('async'),
    execFile = require('child_process').execFile,
    Factory = require('factory-lady'),
    fs = require('fs'),
    path = require('path'),
    processEnv = require('../../lib/process_env'),
    request = require('request');

var config = apiUmbrellaConfig.load(path.resolve(__dirname, '../config/test.yml'));

describe('processes', function() {
  shared.runServer();

  describe('nginx', function() {
    it('does not leak file descriptors across reloads', function(done) {
      this.timeout(50000);

      var execOpts = { env: processEnv.env() };
      var parentPid;
      var descriptorCounts = [];
      var urandomDescriptorCounts = [];

      async.series([
        function(callback) {
          // First make a number of concurrent requests to ensure that each
          // nginx worker process is warmed up. This ensures that each worker
          // should at least have initialized its usage of its urandom
          // descriptors. Since we want to test that these descriptors don't
          // grow, we first need to ensure each worker process is first fully
          // initialized (so they don't grow due to be initialized later on in
          // the tests).
          async.times(50, function(index, next) {
            request.get('http://localhost:9080/delay/500', this.options, function(error, response) {
              response.statusCode.should.eql(200);
              next(error);
            });
          }.bind(this), callback);
        }.bind(this),
        function(callback) {
          execFile('pgrep', ['-f', 'nginx: master'], function(error, stdout, stderr) {
            if(error) {
              return done('Error fetching nginx pid: ' + error + ' (STDOUT: ' + stdout + ', STDERR: ' + stderr + ')');
            }

            if(!stdout || !/^\d+$/.test(stdout.trim())) {
              return done('No PID returned for nginx (STDOUT: ' + stdout + ', STDERR: ' + stderr + ')');
            }

            parentPid = parseInt(stdout, 10);
            callback();
          });
        },
        function(callback) {
          async.timesSeries(15, function(index, next) {
            execFile('pkill', ['-HUP', '-f', 'nginx: master'], function(error, stdout, stderr) {
              if(error) {
                return next('Error reloading nginx: ' + error + ' (STDOUT: ' + stdout + ', STDERR: ' + stderr + ')');
              }

              setTimeout(function() {
                execFile('lsof', ['-R', '-c', 'nginx'], execOpts, function(error, stdout, stderr) {
                  if(error) {
                    return next('Error gathering lsof details: ' + error.message + '\n\nSTDOUT: ' + stdout + '\n\nSTDERR:' + stderr);
                  }

                  var lines = stdout.split('\n');
                  var descriptorCount = 0;
                  var urandomDescriptorCount = 0;
                  lines.forEach(function(line) {
                    var columns = line.split(/\s+/);
                    if(parseInt(columns[1], 10) === parentPid || parseInt(columns[2], 10) === parentPid) {
                      descriptorCount++;

                      if(_.contains(line, 'urandom')) {
                        urandomDescriptorCount++;
                      }
                    }
                  });

                  descriptorCounts.push(descriptorCount);
                  urandomDescriptorCounts.push(urandomDescriptorCount);

                  setTimeout(function() {
                    next(null, lines.length);
                  }, 500);
                });
              }, 1000);
            });
          }, callback);
        },
      ], function(error) {
        if(error) {
          return done(error);
        }

        // Test to ensure ngx_txid isn't leaving open file descriptors around
        // on reloads test for this patch:
        // https://github.com/streadway/ngx_txid/pull/6
        urandomDescriptorCounts.length.should.eql(15);
        urandomDescriptorCounts[0].should.be.greaterThan(0);
        _.max(urandomDescriptorCounts).should.eql(_.min(urandomDescriptorCounts));

        // A more general test to ensure that we don't see other unexpected
        // file descriptor growth. We'll allow some growth for this test,
        // though, just to account for small fluctuations in sockets due to
        // other things nginx may be doing.
        descriptorCounts.length.should.eql(15);
        _.min(descriptorCounts).should.be.greaterThan(0);
        var range = _.max(descriptorCounts) - _.min(descriptorCounts);
        range.should.be.lessThan(10);

        done();
      });
    });
  });

  describe('reload', function() {
    before(function publishDbConfig(done) {
      this.timeout(10000);

      // Be sure that these tests interact with a backend published via Mongo,
      // so we can also catch errors for when the mongo-based configuration
      // data experiences failures.
      shared.setRuntimeConfigOverrides({
        apis: [
          {
            _id: 'db-config',
            frontend_host: 'localhost',
            backend_host: 'localhost',
            servers: [
              {
                host: '127.0.0.1',
                port: 9444,
              }
            ],
            url_matches: [
              {
                frontend_prefix: '/db-config/hello',
                backend_prefix: '/hello',
              }
            ],
          },
        ],
      }, function(error) {
        should.not.exist(error);
        shared.waitForConfig(done);
      });
    });

    beforeEach(function setOptionDefaults(done) {
      Factory.create('api_user', { settings: { rate_limit_mode: 'unlimited' } }, function(user) {
        this.user = user;
        this.apiKey = user.api_key;
        this.options = {
          followRedirect: false,
          headers: {
            'X-Api-Key': this.apiKey,
            'X-Disable-Router-Connection-Limits': 'yes',
            'X-Disable-Router-Rate-Limits': 'yes',
          },
          agentOptions: {
            maxSockets: 500,
          },
        };

        done();
      }.bind(this));
    });

    after(function removeDbConfig(done) {
      this.timeout(6000);

      // Remove DB-based config after these tests, so the rest of the tests go
      // back to the file-based configs.
      shared.revertRuntimeConfigOverrides(function(error) {
        should.not.exist(error);
        shared.waitForConfig(done);
      });
    });

    it('updates templates with file-based config file changes', function(done) {
      this.timeout(90000);

      var nginxFile = path.join(config.get('etc_dir'), 'nginx/router.conf');
      var nginxConfig = fs.readFileSync(nginxFile).toString();
      nginxConfig.should.contain('worker_processes 4;');

      shared.setConfigOverrides({
        nginx: {
          workers: 1,
        },
      }, function(error) {
        should.not.exist(error);
        shared.waitForConfig(function(error) {
          should.not.exist(error);

          nginxConfig = fs.readFileSync(nginxFile).toString();
          nginxConfig.should.contain('worker_processes 1;');

          shared.setConfigOverrides({}, function(error) {
            should.not.exist(error);
            shared.waitForConfig(function(error) {
              should.not.exist(error);

              nginxConfig = fs.readFileSync(nginxFile).toString();
              nginxConfig.should.contain('worker_processes 4;');

              done();
            });
          });
        });
      });
    });

    it('does not drop connections during reloads', function(done) {
      this.timeout(60000);

      execFile('pgrep', ['-f', 'nginx: worker'], function(error, stdout, stderr) {
        if(error) {
          return done('Error fetching nginx pid: ' + error + ' (STDOUT: ' + stdout + ', STDERR: ' + stderr + ')');
        }

        var originalPids = stdout.split('\n').sort();

        var runTests = true;
        setTimeout(function() {
          execFile('pkill', ['-HUP', '-f', 'nginx: master'], function(error, stdout, stderr) {
            if(error) {
              return done('Error reloading nginx: ' + error + ' (STDOUT: ' + stdout + ', STDERR: ' + stderr + ')');
            }

            setTimeout(function() { runTests = false; }, 5000);
          });
        }.bind(this), 100);

        async.whilst(function() { return runTests; }, function(whilstCallback) {
          request.get('http://localhost:9080/db-config/hello', this.options, function(error, response, body) {
            should.not.exist(error);
            response.statusCode.should.eql(200);
            body.should.eql('Hello World');

            whilstCallback(error);
          });
        }.bind(this), function(error) {
          should.not.exist(error);

          execFile('pgrep', ['-f', 'nginx: worker'], function(error, stdout, stderr) {
            if(error) {
              return done('Error fetching nginx pid: ' + error + ' (STDOUT: ' + stdout + ', STDERR: ' + stderr + ')');
            }

            var finalPids = stdout.split('\n').sort();
            finalPids.should.not.eql(originalPids);
            done();
          });
        });
      }.bind(this));
    });
  });
});
<|endoftext|>


<commit_before>window.GobiertoAdmin.GobiertoCommonCustomFieldRecordsProgressPluginController = (function() {

  function GobiertoCommonCustomFieldRecordsProgressPluginController() {}

  var grid;

  GobiertoCommonCustomFieldRecordsProgressPluginController.prototype.form = function(opts = {}) {
    _handlePluginData(opts.uid);
  };

  function _applyPluginStyles(element, plugin_name_hint) {
    element.wrap("

");
  }

  function _handlePluginData(uid) {
    let element = $(`[data-uid=${uid}]`)
    let id = element.attr('id')
    let data = JSON.parse($(`#${id}`).find("input[name$='[value]'").val())

    _applyPluginStyles(element, "progress")
    _insertProgressPlugin(id, data)
  }

  function _insertProgressPlugin(id, data) {
    let element = $(`#${id}`)
    let formatted_data = data
      ? data.toLocaleString(I18n.locale, { style: 'percent', maximumFractionDigits: 1 })
      : "-"

    element.append(formatted_data);
    // element.append($('<input>', { disabled: true, type: 'text', val: formatted_data }))
  }

  return GobiertoCommonCustomFieldRecordsProgressPluginController;
})();

window.GobiertoAdmin.gobierto_common_custom_field_records_progress_plugin_controller = new GobiertoAdmin.GobiertoCommonCustomFieldRecordsProgressPluginController;
<commit_msg>Change progress plugin javascript to replace progress with custom field value<commit_after>window.GobiertoAdmin.GobiertoCommonCustomFieldRecordsProgressPluginController = (function() {

  function GobiertoCommonCustomFieldRecordsProgressPluginController() {}

  GobiertoCommonCustomFieldRecordsProgressPluginController.prototype.form = function(opts = {}) {
    _handlePluginData(opts.uid);
  };

  function _handlePluginData(uid) {
    let element = $(`[data-uid=${uid}]`)
    let id = element.attr('id')
    let data = JSON.parse($(`#${id}`).find("input[name$='[value]'").val())

    _insertProgressPlugin(id, data)
  }

  function _insertProgressPlugin(id, data) {
    let element = $(`#${id}`)
    element.remove();
    let progress_select = $("select[id$='_progress'")
    if(progress_select.length) {
      progress_select.replaceWith(
        $('<input type="text" disabled="disabled">').val(
          data.toLocaleString(I18n.locale, { style: 'percent', maximumFractionDigits: 1 })
        )
      );
    }
  }

  return GobiertoCommonCustomFieldRecordsProgressPluginController;
})();

window.GobiertoAdmin.gobierto_common_custom_field_records_progress_plugin_controller = new GobiertoAdmin.GobiertoCommonCustomFieldRecordsProgressPluginController;
<|endoftext|>