<commit_before>import * as React from 'react';
import { isFragment } from 'react-is';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import MuiError from '@material-ui/utils/macros/MuiError.macro';
import { refType } from '@material-ui/utils';
import ownerDocument from '../utils/ownerDocument';
import capitalize from '../utils/capitalize';
import Menu from '../Menu/Menu';
import { isFilled } from '../InputBase/utils';
import useForkRef from '../utils/useForkRef';
import useControlled from '../utils/useControlled';

function areEqualValues(a, b) {
  if (typeof b === 'object' && b !== null) {
    return a === b;
  }

  return String(a) === String(b);
}

function isEmpty(display) {
  return display == null || (typeof display === 'string' && !display.trim());
}

/**
 * @ignore - internal component.
 */
const SelectInput = React.forwardRef(function SelectInput(props, ref) {
  const {
    'aria-label': ariaLabel,
    autoFocus,
    autoWidth,
    children,
    classes,
    className,
    defaultValue,
    disabled,
    displayEmpty,
    IconComponent,
    inputRef: inputRefProp,
    labelId,
    MenuProps = {},
    multiple,
    name,
    onBlur,
    onChange,
    onClose,
    onFocus,
    onOpen,
    open: openProp,
    readOnly,
    renderValue,
    SelectDisplayProps = {},
    tabIndex: tabIndexProp,
    // catching `type` from Input which makes no sense for SelectInput
    type,
    value: valueProp,
    variant = 'standard',
    ...other
  } = props;

  const [value, setValueState] = useControlled({
    controlled: valueProp,
    default: defaultValue,
    name: 'Select',
  });

  const inputRef = React.useRef(null);
  const [displayNode, setDisplayNode] = React.useState(null);
  const { current: isOpenControlled } = React.useRef(openProp != null);
  const [menuMinWidthState, setMenuMinWidthState] = React.useState();
  const [openState, setOpenState] = React.useState(false);
  const handleRef = useForkRef(ref, inputRefProp);

  React.useImperativeHandle(
    handleRef,
    () => ({
      focus: () => {
        displayNode.focus();
      },
      node: inputRef.current,
      value,
    }),
    [displayNode, value],
  );

  React.useEffect(() => {
    if (autoFocus && displayNode) {
      displayNode.focus();
    }
  }, [autoFocus, displayNode]);

  React.useEffect(() => {
    if (displayNode) {
      const label = ownerDocument(displayNode).getElementById(labelId);
      if (label) {
        const handler = () => {
          if (getSelection().isCollapsed) {
            displayNode.focus();
          }
        };
        label.addEventListener('click', handler);
        return () => {
          label.removeEventListener('click', handler);
        };
      }
    }

    return undefined;
  }, [labelId, displayNode]);

  const update = (open, event) => {
    if (open) {
      if (onOpen) {
        onOpen(event);
      }
    } else if (onClose) {
      onClose(event);
    }

    if (!isOpenControlled) {
      setMenuMinWidthState(autoWidth ? null : displayNode.clientWidth);
      setOpenState(open);
    }
  };

  const handleMouseDown = (event) => {
    // Ignore everything but left-click
    if (event.button !== 0) {
      return;
    }
    // Hijack the default focus behavior.
    event.preventDefault();
    displayNode.focus();

    update(true, event);
  };

  const handleClose = (event) => {
    update(false, event);
  };

  const childrenArray = React.Children.toArray(children);

  // Support autofill.
  const handleChange = (event) => {
    const index = childrenArray.map((child) => child.props.value).indexOf(event.target.value);

    if (index === -1) {
      return;
    }

    const child = childrenArray[index];
    setValueState(child.props.value);

    if (onChange) {
      onChange(event, child);
    }
  };

  const handleItemClick = (child) => (event) => {
    let newValue;

    if (multiple) {
      newValue = Array.isArray(value) ? value.slice() : [];
      const itemIndex = value.indexOf(child.props.value);
      if (itemIndex === -1) {
        newValue.push(child.props.value);
      } else {
        newValue.splice(itemIndex, 1);
      }
    } else {
      newValue = child.props.value;
    }

    if (child.props.onClick) {
      child.props.onClick(event);
    }

    if (value !== newValue) {
      setValueState(newValue);

      if (onChange) {
        event.persist();
        // Preact support, target is read only property on a native event.
        Object.defineProperty(event, 'target', {
          writable: true,
          value: { value: newValue, name },
        });
        onChange(event, child);
      }
    }

    if (!multiple) {
      update(false, event);
    }
  };

  const handleKeyDown = (event) => {
    if (!readOnly) {
      const validKeys = [
        ' ',
        'ArrowUp',
        'ArrowDown',
        // The native select doesn't respond to enter on MacOS, but it's recommended by
        // https://www.w3.org/TR/wai-aria-practices/examples/listbox/listbox-collapsible.html
        'Enter',
      ];

      if (validKeys.indexOf(event.key) !== -1) {
        event.preventDefault();
        update(true, event);
      }
    }
  };

  const open = displayNode !== null && (isOpenControlled ? openProp : openState);

  const handleBlur = (event) => {
    // if open event.stopImmediatePropagation
    if (!open && onBlur) {
      event.persist();
      // Preact support, target is read only property on a native event.
      Object.defineProperty(event, 'target', { writable: true, value: { value, name } });
      onBlur(event);
    }
  };

  delete other['aria-invalid'];

  let display;
  let displaySingle;
  const displayMultiple = [];
  let computeDisplay = false;
  let foundMatch = false;

  // No need to display any value if the field is empty.
  if (isFilled({ value }) || displayEmpty) {
    if (renderValue) {
      display = renderValue(value);
    } else {
      computeDisplay = true;
    }
  }

  const items = childrenArray.map((child) => {
    if (!React.isValidElement(child)) {
      return null;
    }

    if (process.env.NODE_ENV !== 'production') {
      if (isFragment(child)) {
        console.error(
          [
            "Material-UI: The Select component doesn't accept a Fragment as a child.",
            'Consider providing an array instead.',
          ].join('\n'),
        );
      }
    }

    let selected;

    if (multiple) {
      if (!Array.isArray(value)) {
        throw new MuiError(
          'Material-UI: The `value` prop must be an array ' +
            'when using the `Select` component with `multiple`.',
        );
      }

      selected = value.some((v) => areEqualValues(v, child.props.value));
      if (selected && computeDisplay) {
        displayMultiple.push(child.props.children);
      }
    } else {
      selected = areEqualValues(value, child.props.value);
      if (selected && computeDisplay) {
        displaySingle = child.props.children;
      }
    }

    if (selected) {
      foundMatch = true;
    }

    return React.cloneElement(child, {
      'aria-selected': selected ? 'true' : undefined,
      onClick: handleItemClick(child),
      onKeyUp: (event) => {
        if (event.key === ' ') {
          // otherwise our MenuItems dispatches a click event
          // it's not behavior of the native <option> and causes
          // the select to close immediately since we open on space keydown
          event.preventDefault();
        }

        if (child.props.onKeyUp) {
          child.props.onKeyUp(event);
        }
      },
      role: 'option',
      selected,
      value: undefined, // The value is most likely not a valid HTML attribute.
      'data-value': child.props.value, // Instead, we provide it as a data attribute.
    });
  });

  if (process.env.NODE_ENV !== 'production') {
    // eslint-disable-next-line react-hooks/rules-of-hooks
    React.useEffect(() => {
      if (!foundMatch && !multiple && value !== '') {
        const values = childrenArray.map((child) => child.props.value);
        console.warn(
          [
            `Material-UI: You have provided an out-of-range value \`${value}\` for the select ${
              name ? `(name="${name}") ` : ''
            }component.`,
            "Consider providing a value that matches one of the available options or ''.",
            `The available values are ${
              values
                .filter((x) => x != null)
                .map((x) => `\`${x}\``)
                .join(', ') || '""'
            }.`,
          ].join('\n'),
        );
      }
    }, [foundMatch, childrenArray, multiple, name, value]);
  }

  if (computeDisplay) {
    display = multiple ? displayMultiple.join(', ') : displaySingle;
  }

  // Avoid performing a layout computation in the render method.
  let menuMinWidth = menuMinWidthState;

  if (!autoWidth && isOpenControlled && displayNode) {
    menuMinWidth = displayNode.clientWidth;
  }

  let tabIndex;
  if (typeof tabIndexProp !== 'undefined') {
    tabIndex = tabIndexProp;
  } else {
    tabIndex = disabled ? null : 0;
  }

  const buttonId = SelectDisplayProps.id || (name ? `mui-component-select-${name}` : undefined);

  return (
    <React.Fragment>
      <div
        className={clsx(
          classes.root, // TODO v5: merge root and select
          classes.select,
          classes.selectMenu,
          classes[variant],
          {
            [classes.disabled]: disabled,
          },
          className,
        )}
        ref={setDisplayNode}
        tabIndex={tabIndex}
        role="button"
        aria-disabled={disabled ? 'true' : undefined}
        aria-expanded={open ? 'true' : undefined}
        aria-haspopup="listbox"
        aria-label={ariaLabel}
        aria-labelledby={[labelId, buttonId].filter(Boolean).join(' ') || undefined}
        onKeyDown={handleKeyDown}
        onMouseDown={disabled || readOnly ? null : handleMouseDown}
        onBlur={handleBlur}
        onFocus={onFocus}
        {...SelectDisplayProps}
        // The id is required for proper a11y
        id={buttonId}
      >
        {/* So the vertical align positioning algorithm kicks in. */}
        {isEmpty(display) ? (
          // eslint-disable-next-line react/no-danger
          <span dangerouslySetInnerHTML={{ __html: '' }} />
        ) : (
          display
        )}
      </div>
      <input
        value={Array.isArray(value) ? value.join(',') : value}
        name={name}
        ref={inputRef}
        aria-hidden
        onChange={handleChange}
        tabIndex={-1}
        className={classes.nativeInput}
        autoFocus={autoFocus}
        {...other}
      />
      <IconComponent
        className={clsx(classes.icon, classes[`icon${capitalize(variant)}`], {
          [classes.iconOpen]: open,
          [classes.disabled]: disabled,
        })}
      />
      <Menu
        id={`menu-${name || ''}`}
        anchorEl={displayNode}
        open={open}
        onClose={handleClose}
        {...MenuProps}
        MenuListProps={{
          'aria-labelledby': labelId,
          role: 'listbox',
          disableListWrap: true,
          ...MenuProps.MenuListProps,
        }}
        PaperProps={{
          ...MenuProps.PaperProps,
          style: {
            minWidth: menuMinWidth,
            ...(MenuProps.PaperProps != null ? MenuProps.PaperProps.style : null),
          },
        }}
      >
        {items}
      </Menu>
    </React.Fragment>
  );
});

SelectInput.propTypes = {
  /**
   * @ignore
   */
  'aria-label': PropTypes.string,
  /**
   * @ignore
   */
  autoFocus: PropTypes.bool,
  /**
   * If `true`, the width of the popover will automatically be set according to the items inside the
   * menu, otherwise it will be at least the width of the select input.
   */
  autoWidth: PropTypes.bool,
  /**
   * The option elements to populate the select with.
   * Can be some `<MenuItem>` elements.
   */
  children: PropTypes.node,
  /**
   * Override or extend the styles applied to the component.
   * See [CSS API](#css) below for more details.
   */
  classes: PropTypes.object.isRequired,
  /**
   * The CSS class name of the select element.
   */
  className: PropTypes.string,
  /**
   * The default element value. Use when the component is not controlled.
   */
  defaultValue: PropTypes.any,
  /**
   * If `true`, the select will be disabled.
   */
  disabled: PropTypes.bool,
  /**
   * If `true`, the selected item is displayed even if its value is empty.
   */
  displayEmpty: PropTypes.bool,
  /**
   * The icon that displays the arrow.
   */
  IconComponent: PropTypes.elementType.isRequired,
  /**
   * Imperative handle implementing `{ value: T, node: HTMLElement, focus(): void }`
   * Equivalent to `ref`
   */
  inputRef: refType,
  /**
   * The ID of an element that acts as an additional label. The Select will
   * be labelled by the additional label and the selected value.
   */
  labelId: PropTypes.string,
  /**
   * Props applied to the [`Menu`](/api/menu/) element.
   */
  MenuProps: PropTypes.object,
  /**
   * If `true`, `value` must be an array and the menu will support multiple selections.
   */
  multiple: PropTypes.bool,
  /**
   * Name attribute of the `select` or hidden `input` element.
   */
  name: PropTypes.string,
  /**
   * @ignore
   */
  onBlur: PropTypes.func,
  /**
   * Callback fired when a menu item is selected.
   *
   * @param {object} event The event source of the callback.
   * You can pull out the new value by accessing `event.target.value` (any).
   * @param {object} [child] The react element that was selected.
   */
  onChange: PropTypes.func,
  /**
   * Callback fired when the component requests to be closed.
   * Use in controlled mode (see open).
   *
   * @param {object} event The event source of the callback.
   */
  onClose: PropTypes.func,
  /**
   * @ignore
   */
  onFocus: PropTypes.func,
  /**
   * Callback fired when the component requests to be opened.
   * Use in controlled mode (see open).
   *
   * @param {object} event The event source of the callback.
   */
  onOpen: PropTypes.func,
  /**
   * Control `select` open state.
   */
  open: PropTypes.bool,
  /**
   * @ignore
   */
  readOnly: PropTypes.bool,
  /**
   * Render the selected value.
   *
   * @param {any} value The `value` provided to the component.
   * @returns {ReactNode}
   */
  renderValue: PropTypes.func,
  /**
   * Props applied to the clickable div element.
   */
  SelectDisplayProps: PropTypes.object,
  /**
   * @ignore
   */
  tabIndex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  /**
   * @ignore
   */
  type: PropTypes.any,
  /**
   * The input value.
   */
  value: PropTypes.any,
  /**
   * The variant to use.
   */
  variant: PropTypes.oneOf(['standard', 'outlined', 'filled']),
};

export default SelectInput;
<commit_msg>[Select] Fix SelectDisplayProps className concat (#23211)<commit_after>import * as React from 'react';
import { isFragment } from 'react-is';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import MuiError from '@material-ui/utils/macros/MuiError.macro';
import { refType } from '@material-ui/utils';
import ownerDocument from '../utils/ownerDocument';
import capitalize from '../utils/capitalize';
import Menu from '../Menu/Menu';
import { isFilled } from '../InputBase/utils';
import useForkRef from '../utils/useForkRef';
import useControlled from '../utils/useControlled';

function areEqualValues(a, b) {
  if (typeof b === 'object' && b !== null) {
    return a === b;
  }

  return String(a) === String(b);
}

function isEmpty(display) {
  return display == null || (typeof display === 'string' && !display.trim());
}

/**
 * @ignore - internal component.
 */
const SelectInput = React.forwardRef(function SelectInput(props, ref) {
  const {
    'aria-label': ariaLabel,
    autoFocus,
    autoWidth,
    children,
    classes,
    className,
    defaultValue,
    disabled,
    displayEmpty,
    IconComponent,
    inputRef: inputRefProp,
    labelId,
    MenuProps = {},
    multiple,
    name,
    onBlur,
    onChange,
    onClose,
    onFocus,
    onOpen,
    open: openProp,
    readOnly,
    renderValue,
    SelectDisplayProps = {},
    tabIndex: tabIndexProp,
    // catching `type` from Input which makes no sense for SelectInput
    type,
    value: valueProp,
    variant = 'standard',
    ...other
  } = props;

  const [value, setValueState] = useControlled({
    controlled: valueProp,
    default: defaultValue,
    name: 'Select',
  });

  const inputRef = React.useRef(null);
  const [displayNode, setDisplayNode] = React.useState(null);
  const { current: isOpenControlled } = React.useRef(openProp != null);
  const [menuMinWidthState, setMenuMinWidthState] = React.useState();
  const [openState, setOpenState] = React.useState(false);
  const handleRef = useForkRef(ref, inputRefProp);

  React.useImperativeHandle(
    handleRef,
    () => ({
      focus: () => {
        displayNode.focus();
      },
      node: inputRef.current,
      value,
    }),
    [displayNode, value],
  );

  React.useEffect(() => {
    if (autoFocus && displayNode) {
      displayNode.focus();
    }
  }, [autoFocus, displayNode]);

  React.useEffect(() => {
    if (displayNode) {
      const label = ownerDocument(displayNode).getElementById(labelId);
      if (label) {
        const handler = () => {
          if (getSelection().isCollapsed) {
            displayNode.focus();
          }
        };
        label.addEventListener('click', handler);
        return () => {
          label.removeEventListener('click', handler);
        };
      }
    }

    return undefined;
  }, [labelId, displayNode]);

  const update = (open, event) => {
    if (open) {
      if (onOpen) {
        onOpen(event);
      }
    } else if (onClose) {
      onClose(event);
    }

    if (!isOpenControlled) {
      setMenuMinWidthState(autoWidth ? null : displayNode.clientWidth);
      setOpenState(open);
    }
  };

  const handleMouseDown = (event) => {
    // Ignore everything but left-click
    if (event.button !== 0) {
      return;
    }
    // Hijack the default focus behavior.
    event.preventDefault();
    displayNode.focus();

    update(true, event);
  };

  const handleClose = (event) => {
    update(false, event);
  };

  const childrenArray = React.Children.toArray(children);

  // Support autofill.
  const handleChange = (event) => {
    const index = childrenArray.map((child) => child.props.value).indexOf(event.target.value);

    if (index === -1) {
      return;
    }

    const child = childrenArray[index];
    setValueState(child.props.value);

    if (onChange) {
      onChange(event, child);
    }
  };

  const handleItemClick = (child) => (event) => {
    let newValue;

    if (multiple) {
      newValue = Array.isArray(value) ? value.slice() : [];
      const itemIndex = value.indexOf(child.props.value);
      if (itemIndex === -1) {
        newValue.push(child.props.value);
      } else {
        newValue.splice(itemIndex, 1);
      }
    } else {
      newValue = child.props.value;
    }

    if (child.props.onClick) {
      child.props.onClick(event);
    }

    if (value !== newValue) {
      setValueState(newValue);

      if (onChange) {
        event.persist();
        // Preact support, target is read only property on a native event.
        Object.defineProperty(event, 'target', {
          writable: true,
          value: { value: newValue, name },
        });
        onChange(event, child);
      }
    }

    if (!multiple) {
      update(false, event);
    }
  };

  const handleKeyDown = (event) => {
    if (!readOnly) {
      const validKeys = [
        ' ',
        'ArrowUp',
        'ArrowDown',
        // The native select doesn't respond to enter on MacOS, but it's recommended by
        // https://www.w3.org/TR/wai-aria-practices/examples/listbox/listbox-collapsible.html
        'Enter',
      ];

      if (validKeys.indexOf(event.key) !== -1) {
        event.preventDefault();
        update(true, event);
      }
    }
  };

  const open = displayNode !== null && (isOpenControlled ? openProp : openState);

  const handleBlur = (event) => {
    // if open event.stopImmediatePropagation
    if (!open && onBlur) {
      event.persist();
      // Preact support, target is read only property on a native event.
      Object.defineProperty(event, 'target', { writable: true, value: { value, name } });
      onBlur(event);
    }
  };

  delete other['aria-invalid'];

  let display;
  let displaySingle;
  const displayMultiple = [];
  let computeDisplay = false;
  let foundMatch = false;

  // No need to display any value if the field is empty.
  if (isFilled({ value }) || displayEmpty) {
    if (renderValue) {
      display = renderValue(value);
    } else {
      computeDisplay = true;
    }
  }

  const items = childrenArray.map((child) => {
    if (!React.isValidElement(child)) {
      return null;
    }

    if (process.env.NODE_ENV !== 'production') {
      if (isFragment(child)) {
        console.error(
          [
            "Material-UI: The Select component doesn't accept a Fragment as a child.",
            'Consider providing an array instead.',
          ].join('\n'),
        );
      }
    }

    let selected;

    if (multiple) {
      if (!Array.isArray(value)) {
        throw new MuiError(
          'Material-UI: The `value` prop must be an array ' +
            'when using the `Select` component with `multiple`.',
        );
      }

      selected = value.some((v) => areEqualValues(v, child.props.value));
      if (selected && computeDisplay) {
        displayMultiple.push(child.props.children);
      }
    } else {
      selected = areEqualValues(value, child.props.value);
      if (selected && computeDisplay) {
        displaySingle = child.props.children;
      }
    }

    if (selected) {
      foundMatch = true;
    }

    return React.cloneElement(child, {
      'aria-selected': selected ? 'true' : undefined,
      onClick: handleItemClick(child),
      onKeyUp: (event) => {
        if (event.key === ' ') {
          // otherwise our MenuItems dispatches a click event
          // it's not behavior of the native <option> and causes
          // the select to close immediately since we open on space keydown
          event.preventDefault();
        }

        if (child.props.onKeyUp) {
          child.props.onKeyUp(event);
        }
      },
      role: 'option',
      selected,
      value: undefined, // The value is most likely not a valid HTML attribute.
      'data-value': child.props.value, // Instead, we provide it as a data attribute.
    });
  });

  if (process.env.NODE_ENV !== 'production') {
    // eslint-disable-next-line react-hooks/rules-of-hooks
    React.useEffect(() => {
      if (!foundMatch && !multiple && value !== '') {
        const values = childrenArray.map((child) => child.props.value);
        console.warn(
          [
            `Material-UI: You have provided an out-of-range value \`${value}\` for the select ${
              name ? `(name="${name}") ` : ''
            }component.`,
            "Consider providing a value that matches one of the available options or ''.",
            `The available values are ${
              values
                .filter((x) => x != null)
                .map((x) => `\`${x}\``)
                .join(', ') || '""'
            }.`,
          ].join('\n'),
        );
      }
    }, [foundMatch, childrenArray, multiple, name, value]);
  }

  if (computeDisplay) {
    display = multiple ? displayMultiple.join(', ') : displaySingle;
  }

  // Avoid performing a layout computation in the render method.
  let menuMinWidth = menuMinWidthState;

  if (!autoWidth && isOpenControlled && displayNode) {
    menuMinWidth = displayNode.clientWidth;
  }

  let tabIndex;
  if (typeof tabIndexProp !== 'undefined') {
    tabIndex = tabIndexProp;
  } else {
    tabIndex = disabled ? null : 0;
  }

  const buttonId = SelectDisplayProps.id || (name ? `mui-component-select-${name}` : undefined);

  return (
    <React.Fragment>
      <div
        ref={setDisplayNode}
        tabIndex={tabIndex}
        role="button"
        aria-disabled={disabled ? 'true' : undefined}
        aria-expanded={open ? 'true' : undefined}
        aria-haspopup="listbox"
        aria-label={ariaLabel}
        aria-labelledby={[labelId, buttonId].filter(Boolean).join(' ') || undefined}
        onKeyDown={handleKeyDown}
        onMouseDown={disabled || readOnly ? null : handleMouseDown}
        onBlur={handleBlur}
        onFocus={onFocus}
        {...SelectDisplayProps}
        className={clsx(
          classes.root, // TODO v5: merge root and select
          classes.select,
          classes.selectMenu,
          classes[variant],
          {
            [classes.disabled]: disabled,
          },
          className,
          SelectDisplayProps.className,
        )}
        // The id is required for proper a11y
        id={buttonId}
      >
        {/* So the vertical align positioning algorithm kicks in. */}
        {isEmpty(display) ? (
          // eslint-disable-next-line react/no-danger
          <span dangerouslySetInnerHTML={{ __html: '' }} />
        ) : (
          display
        )}
      </div>
      <input
        value={Array.isArray(value) ? value.join(',') : value}
        name={name}
        ref={inputRef}
        aria-hidden
        onChange={handleChange}
        tabIndex={-1}
        className={classes.nativeInput}
        autoFocus={autoFocus}
        {...other}
      />
      <IconComponent
        className={clsx(classes.icon, classes[`icon${capitalize(variant)}`], {
          [classes.iconOpen]: open,
          [classes.disabled]: disabled,
        })}
      />
      <Menu
        id={`menu-${name || ''}`}
        anchorEl={displayNode}
        open={open}
        onClose={handleClose}
        {...MenuProps}
        MenuListProps={{
          'aria-labelledby': labelId,
          role: 'listbox',
          disableListWrap: true,
          ...MenuProps.MenuListProps,
        }}
        PaperProps={{
          ...MenuProps.PaperProps,
          style: {
            minWidth: menuMinWidth,
            ...(MenuProps.PaperProps != null ? MenuProps.PaperProps.style : null),
          },
        }}
      >
        {items}
      </Menu>
    </React.Fragment>
  );
});

SelectInput.propTypes = {
  /**
   * @ignore
   */
  'aria-label': PropTypes.string,
  /**
   * @ignore
   */
  autoFocus: PropTypes.bool,
  /**
   * If `true`, the width of the popover will automatically be set according to the items inside the
   * menu, otherwise it will be at least the width of the select input.
   */
  autoWidth: PropTypes.bool,
  /**
   * The option elements to populate the select with.
   * Can be some `<MenuItem>` elements.
   */
  children: PropTypes.node,
  /**
   * Override or extend the styles applied to the component.
   * See [CSS API](#css) below for more details.
   */
  classes: PropTypes.object.isRequired,
  /**
   * The CSS class name of the select element.
   */
  className: PropTypes.string,
  /**
   * The default element value. Use when the component is not controlled.
   */
  defaultValue: PropTypes.any,
  /**
   * If `true`, the select will be disabled.
   */
  disabled: PropTypes.bool,
  /**
   * If `true`, the selected item is displayed even if its value is empty.
   */
  displayEmpty: PropTypes.bool,
  /**
   * The icon that displays the arrow.
   */
  IconComponent: PropTypes.elementType.isRequired,
  /**
   * Imperative handle implementing `{ value: T, node: HTMLElement, focus(): void }`
   * Equivalent to `ref`
   */
  inputRef: refType,
  /**
   * The ID of an element that acts as an additional label. The Select will
   * be labelled by the additional label and the selected value.
   */
  labelId: PropTypes.string,
  /**
   * Props applied to the [`Menu`](/api/menu/) element.
   */
  MenuProps: PropTypes.object,
  /**
   * If `true`, `value` must be an array and the menu will support multiple selections.
   */
  multiple: PropTypes.bool,
  /**
   * Name attribute of the `select` or hidden `input` element.
   */
  name: PropTypes.string,
  /**
   * @ignore
   */
  onBlur: PropTypes.func,
  /**
   * Callback fired when a menu item is selected.
   *
   * @param {object} event The event source of the callback.
   * You can pull out the new value by accessing `event.target.value` (any).
   * @param {object} [child] The react element that was selected.
   */
  onChange: PropTypes.func,
  /**
   * Callback fired when the component requests to be closed.
   * Use in controlled mode (see open).
   *
   * @param {object} event The event source of the callback.
   */
  onClose: PropTypes.func,
  /**
   * @ignore
   */
  onFocus: PropTypes.func,
  /**
   * Callback fired when the component requests to be opened.
   * Use in controlled mode (see open).
   *
   * @param {object} event The event source of the callback.
   */
  onOpen: PropTypes.func,
  /**
   * Control `select` open state.
   */
  open: PropTypes.bool,
  /**
   * @ignore
   */
  readOnly: PropTypes.bool,
  /**
   * Render the selected value.
   *
   * @param {any} value The `value` provided to the component.
   * @returns {ReactNode}
   */
  renderValue: PropTypes.func,
  /**
   * Props applied to the clickable div element.
   */
  SelectDisplayProps: PropTypes.object,
  /**
   * @ignore
   */
  tabIndex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  /**
   * @ignore
   */
  type: PropTypes.any,
  /**
   * The input value.
   */
  value: PropTypes.any,
  /**
   * The variant to use.
   */
  variant: PropTypes.oneOf(['standard', 'outlined', 'filled']),
};

export default SelectInput;
<|endoftext|>


<commit_before>'use strict';

const Router = require('./index');
const log = require('../../logger');
const middleware = require('storj-service-middleware');
const farmerMiddleware = require('../middleware/farmer-auth');
const rawBody = require('../middleware/raw-body');
const errors = require('storj-service-error-types');
const StorageModels = require('storj-service-storage-models');
const points = StorageModels.constants.POINTS;
const inherits = require('util').inherits;
const BucketsRouter = require('./buckets');
const constants = require('../../constants');
const async = require('async');
const storj = require('storj-lib');
const limiter = require('../limiter').DEFAULTS;
const utils = require('../../utils');

/**
 * Handles endpoints for reporting
 * @constructor
 * @extends {Router}
 */
function ReportsRouter(options) {
  if (!(this instanceof ReportsRouter)) {
    return new ReportsRouter(options);
  }

  Router.apply(this, arguments);

  this.getLimiter = middleware.rateLimiter(options.redis);
  this.userAuthMiddlewares = middleware.authenticate(this.storage);
}

inherits(ReportsRouter, Router);

ReportsRouter.prototype.authMiddleware = function(req, res, next) {
  /* jshint sub:true */
  const isUser = (req.headers['authorization'] !== undefined ||
                  req.headers['x-pubkey'] !== undefined);
  const isFarmer = (req.headers['x-node-id'] !== undefined);

  // These rely on rawBody being already handled in the middleware
  if (isUser) {
    this.userAuthMiddlewares[1](req, res, next);
  } else if (isFarmer) {
    farmerMiddleware.authFarmer(req, res, next);
  } else {
    next(new errors.NotAuthorizedError('No authentication strategy detected'));
  }
};

ReportsRouter.prototype.compat = function(req, res, next) {
  // If the exchange report does not have a token, we can
  // assume that it's not in the new SIP9 format, and will be
  // handled temporarily for backwards compatibility.
  if (!req.body.token) {
    this.createExchangeReportCompat(req, res, next);
  } else {
    next();
  }
};

ReportsRouter.prototype.updateReputation = function(nodeID, points) {
  this.storage.models.Contact.findOne({_id: nodeID}, (err, contact) => {
    if (err || !contact) {
      log.warn('updateReputation: Error trying to find contact ' +
               ' %s, reason: %s', nodeID, err ? err.message : 'unknown');
      return;
    }
    contact.recordPoints(points).save((err) => {
      if (err) {
        log.warn('updateReputation: Error saving contact ' +
                 ' %s, reason: %s', nodeID, err.message);
      }
    });
  });
};

ReportsRouter.prototype.validateExchangeReport = function(report) {
  if (!Number.isFinite(report.exchangeStart)) {
    return false;
  }
  if (!Number.isFinite(report.exchangeEnd)) {
    return false;
  }

  if (report.exchangeResultCode !== 1100 &&
      report.exchangeResultCode !== 1000) {
    return false;
  }

  // TODO: Check timestamp is reasonable

  const validMessages = [
    'FAILED_INTEGRITY',
    'SHARD_DOWNLOADED',
    'SHARD_UPLOADED',
    'DOWNLOAD_ERROR',
    'TRANSFER_FAILED',
    'MIRROR_SUCCESS'
  ];

  if (!validMessages.includes(report.exchangeResultMessage)) {
    return false;
  }

  return true;
};

/**
 * Creates an exchange report for compatibility purposes only, this method can
 * be removed and return a bad request error once SIP9 is fully deployed.
 * @param {http.IncomingMessage} req
 * @param {http.ServerResponse} res
 * @param {Function} next
 */
ReportsRouter.prototype.createExchangeReportCompat = function(req, res, next) {
  const self = this;
  var exchangeReport = new this.storage.models.ExchangeReport(req.body);
  var projection = {
    hash: true,
    contracts: true
  };

  this.storage.models.Shard.find({
    hash: exchangeReport.dataHash
  }, projection, function(err, shards) {
    if (err) {
      return next(new errors.InternalError(err.message));
    }

    if (!shards || !shards.length) {
      return next(new errors.NotFoundError('Shard not found for report'));
    }

    // NB: Kick off mirroring if needed
    const dataBytes = shards[0].contracts[0].data_size;
    self._handleExchangeReport(exchangeReport, dataBytes, (err) => {
      /* istanbul ignore next */
      if (err) {
        return log.warn(err.message);
      }
    });
    exchangeReport.save(function(err) {
      if (err) {
        return next(new errors.BadRequestError(err.message));
      }

      res.status(201).send({});
    });
  });
};

/**
 * Creates an exchange report
 * @param {http.IncomingMessage} req
 * @param {http.ServerResponse} res
 * @param {Function} next
 */
ReportsRouter.prototype.createExchangeReport = function(req, res, next) {
  const token = req.body.token;

  /* jshint maxstatements: 30, maxcomplexity: 15 */
  this.storage.models.StorageEvent.findOne({token: token}, (err, event) => {
    if (err) {
      return next(new errors.InternalError(err.message));
    }

    if (!event) {
      return next(new errors.NotFoundError('Storage event not found'));
    }

    // TODO: Check that event isn't closed for reports

    const report = {
      exchangeStart: req.body.exchangeStart,
      exchangeEnd: req.body.exchangeEnd,
      exchangeResultCode: req.body.exchangeResultCode,
      exchangeResultMessage: req.body.exchangeResultMessage
    };

    if (!this.validateExchangeReport(report)) {
      return next(new errors.BadRequestError('Invalid exchange report'));
    }

    const isClientReport = req.user ?
          (event.client === req.user.id) : req.farmerNodeID ?
          (event.client === req.farmerNodeID) : false;
    const isFarmerReport = req.farmerNodeID ?
          (event.farmer === req.farmerNodeID) : false;

    if (!isClientReport && !isFarmerReport) {
      return next(new errors.NotAuthorizedError('Not authorized to report'));
    }

    let modified = false;

    const hasClientReport = event.clientReport ?
          !!event.clientReport.exchangeResultCode : false;
    const hasFarmerReport = event.farmerReport ?
          !!event.farmerReport.exchangeResultCode : false;

    if (isClientReport && !hasClientReport) {
      modified = true;
      event.clientReport = report;
      if (Number(req.body.exchangeResultCode) === 1000) {
        event.success = true;
        this.updateReputation(event.farmer, points.TRANSFER_SUCCESS);
      } else {
        this.updateReputation(event.farmer, points.TRANSFER_FAILURE);
      }
    } else if (isFarmerReport && !hasFarmerReport) {
      modified = true;
      event.farmerReport = report;
    }

    if (modified) {
      event.save((err) => {
        if (err) {
          return next(new errors.InternalError(err.message));
        }

        // Kick off mirroring if needed
        const dataBytes = event.storage;
        this._handleExchangeReport({
          dataHash: req.body.dataHash,
          exchangeResultMessage: req.body.exchangeResultMessage
        }, dataBytes, (err) => {
          /* istanbul ignore next */
          if (err) {
            return log.warn(err.message);
          }
        });

        res.status(201).send({});
      });

    } else {
      res.status(200).send({});
    }

  });
};

/**
 * @private
 */
ReportsRouter.prototype._handleExchangeReport = function(report, dataBytes, callback) {
  const {dataHash, exchangeResultMessage} = report;

  switch (exchangeResultMessage) {
    case 'MIRROR_SUCCESS':
    case 'SHARD_UPLOADED':
    case 'DOWNLOAD_ERROR':
      this._triggerMirrorEstablish(constants.M_REPLICATE, dataHash, dataBytes, callback);
      break;
    default:
      callback(new Error('Exchange result type will not trigger action'));
  }
};

ReportsRouter._sortByTimeoutRate = function(a, b) {
  const a1 = a.contact.timeoutRate >= 0 ? a.contact.timeoutRate : 0;
  const b1 = b.contact.timeoutRate >= 0 ? b.contact.timeoutRate : 0;
  return (a1 === b1) ? 0 : (a1 > b1) ? 1 : -1;
};

ReportsRouter._sortByResponseTime = function(a, b) {
  const aTime = a.contact.responseTime || Infinity;
  const bTime = b.contact.responseTime || Infinity;
  return (aTime === bTime) ? 0 : (aTime > bTime) ? 1 : -1;
};

ReportsRouter.prototype._createStorageEvent = function(token,
                                                       clientNodeID,
                                                       farmerNodeID,
                                                       storage,
                                                       shardHash) {
  const StorageEvent = this.storage.models.StorageEvent;

  const storageEvent = new StorageEvent({
    token: token,
    user: null,
    client: clientNodeID, // farmer that already has the mirror
    farmer: farmerNodeID, // farmer storing the new mirror
    timestamp: Date.now(),
    downloadBandwidth: 0,
    storage: storage,
    shardHash: shardHash,
    success: false
  });

  storageEvent.save((err) => {
    if (err) {
      log.warn('_createStorageEvent: Error saving event, ' +
               'reason: %s', err.message);
    }
  });
};

/**
 * Loads some mirrors for the hash and establishes them
 * @private
 */
ReportsRouter.prototype._triggerMirrorEstablish = function(n,
                                                           hash,
                                                           dataBytes,
                                                           done) {
  const self = this;
  let item = null;

  function _loadShard(callback) {
    self.contracts.load(hash, (err, _item) => {
      if (err) {
        return callback(err);
      }
      item = _item;
      callback();
    });
  }

  function _getMirrors(callback) {
    self.storage.models.Mirror.find({ shardHash: hash })
      .populate('contact')
      .exec(callback);
  }

  function _getMirrorCandidate(mirrors, callback) {
    let established = [], available = [];

    mirrors.forEach((m) => {
      if (!m.contact) {
        log.warn('Mirror %s is missing contact in database', m._id);
      } else if (!m.isEstablished) {
        available.push(m);
      } else {
        established.push(m);
      }
    });

    if (available.length === 0) {
      return callback(new Error('No available mirrors'));
    }

    if (established.length >= n) {
      return callback(new Error('Auto mirroring limit is reached'));
    }

    available.sort(utils.sortByReputation);

    callback(null, available.shift());
  }

  function _getRetrievalTokenFromFarmer(mirror, callback) {
    let farmers = Object.keys(item.contracts);
    let pointer = null;
    let test = () => farmers.length === 0 || pointer !== null;
    let contact = storj.Contact(mirror.contact.toObject());

    async.until(test, (done) => {
      self.getContactById(farmers.pop(), (err, result) => {
        if (err) {
          return done();
        }

        let farmer = storj.Contact(result.toObject());

        self.network.getRetrievalPointer(
          farmer,
          item.getContract(farmer),
          (err, result) => {
            // NB: Make sure that we don't set pointer to undefined
            // instead of null that would trigger the until loop to quit
            if (err) {
              log.warn('Unable to get pointer for mirroring, reason: %s',
                       err.message);
            } else {
              pointer = result;
            }
            done();
          }
        );
      });
    }, () => {
      if (!pointer) {
        return callback(new Error('Failed to get pointer'));
      }

      let farmer = pointer.farmer;
      let token = pointer.token;
      self._createStorageEvent(token,
                               farmer.nodeID,
                               mirror.contact,
                               dataBytes,
                               hash);

      callback(null, pointer, mirror, contact);
    });
  }

  function _establishMirror(source, mirror, contact, callback) {
    self.network.getMirrorNodes(
      [source],
      [contact],
      (err) => {
        if (err) {
          return callback(err);
        }

        mirror.isEstablished = true;
        mirror.save();
        item.addContract(contact, storj.Contract(mirror.contract));
        self.contracts.save(item, callback);
      }
    );
  }

  async.waterfall([
    _loadShard,
    _getMirrors,
    _getMirrorCandidate,
    _getRetrievalTokenFromFarmer,
    _establishMirror
  ], done);
};

/**
 * @private
 */
ReportsRouter.prototype.getContactById = BucketsRouter.prototype.getContactById;

/**
 * @private
 */
ReportsRouter.prototype._definitions = function() {
  return [
    ['POST', '/reports/exchanges', this.getLimiter(limiter(1000)),
     rawBody, this.compat.bind(this), this.authMiddleware.bind(this),
     this.createExchangeReport.bind(this)]
  ];
};

module.exports = ReportsRouter;
<commit_msg>Fix reference<commit_after>'use strict';

const Router = require('./index');
const log = require('../../logger');
const middleware = require('storj-service-middleware');
const farmerMiddleware = require('../middleware/farmer-auth');
const rawBody = require('../middleware/raw-body');
const errors = require('storj-service-error-types');
const StorageModels = require('storj-service-storage-models');
const points = StorageModels.constants.POINTS;
const inherits = require('util').inherits;
const BucketsRouter = require('./buckets');
const constants = require('../../constants');
const async = require('async');
const storj = require('storj-lib');
const limiter = require('../limiter').DEFAULTS;
const utils = require('../../utils');

/**
 * Handles endpoints for reporting
 * @constructor
 * @extends {Router}
 */
function ReportsRouter(options) {
  if (!(this instanceof ReportsRouter)) {
    return new ReportsRouter(options);
  }

  Router.apply(this, arguments);

  this.getLimiter = middleware.rateLimiter(options.redis);
  this.userAuthMiddlewares = middleware.authenticate(this.storage);
}

inherits(ReportsRouter, Router);

ReportsRouter.prototype.authMiddleware = function(req, res, next) {
  /* jshint sub:true */
  const isUser = (req.headers['authorization'] !== undefined ||
                  req.headers['x-pubkey'] !== undefined);
  const isFarmer = (req.headers['x-node-id'] !== undefined);

  // These rely on rawBody being already handled in the middleware
  if (isUser) {
    this.userAuthMiddlewares[1](req, res, next);
  } else if (isFarmer) {
    farmerMiddleware.authFarmer(req, res, next);
  } else {
    next(new errors.NotAuthorizedError('No authentication strategy detected'));
  }
};

ReportsRouter.prototype.compat = function(req, res, next) {
  // If the exchange report does not have a token, we can
  // assume that it's not in the new SIP9 format, and will be
  // handled temporarily for backwards compatibility.
  if (!req.body.token) {
    this.createExchangeReportCompat(req, res, next);
  } else {
    next();
  }
};

ReportsRouter.prototype.updateReputation = function(nodeID, points) {
  this.storage.models.Contact.findOne({_id: nodeID}, (err, contact) => {
    if (err || !contact) {
      log.warn('updateReputation: Error trying to find contact ' +
               ' %s, reason: %s', nodeID, err ? err.message : 'unknown');
      return;
    }
    contact.recordPoints(points).save((err) => {
      if (err) {
        log.warn('updateReputation: Error saving contact ' +
                 ' %s, reason: %s', nodeID, err.message);
      }
    });
  });
};

ReportsRouter.prototype.validateExchangeReport = function(report) {
  if (!Number.isFinite(report.exchangeStart)) {
    return false;
  }
  if (!Number.isFinite(report.exchangeEnd)) {
    return false;
  }

  if (report.exchangeResultCode !== 1100 &&
      report.exchangeResultCode !== 1000) {
    return false;
  }

  // TODO: Check timestamp is reasonable

  const validMessages = [
    'FAILED_INTEGRITY',
    'SHARD_DOWNLOADED',
    'SHARD_UPLOADED',
    'DOWNLOAD_ERROR',
    'TRANSFER_FAILED',
    'MIRROR_SUCCESS'
  ];

  if (!validMessages.includes(report.exchangeResultMessage)) {
    return false;
  }

  return true;
};

/**
 * Creates an exchange report for compatibility purposes only, this method can
 * be removed and return a bad request error once SIP9 is fully deployed.
 * @param {http.IncomingMessage} req
 * @param {http.ServerResponse} res
 * @param {Function} next
 */
ReportsRouter.prototype.createExchangeReportCompat = function(req, res, next) {
  const self = this;
  var exchangeReport = new this.storage.models.ExchangeReport(req.body);
  var projection = {
    hash: true,
    contracts: true
  };

  this.storage.models.Shard.find({
    hash: exchangeReport.dataHash
  }, projection, function(err, shards) {
    if (err) {
      return next(new errors.InternalError(err.message));
    }

    if (!shards || !shards.length) {
      return next(new errors.NotFoundError('Shard not found for report'));
    }

    // NB: Kick off mirroring if needed
    const dataBytes = shards[0].contracts[0].data_size;
    self._handleExchangeReport(exchangeReport, dataBytes, (err) => {
      /* istanbul ignore next */
      if (err) {
        return log.warn(err.message);
      }
    });
    exchangeReport.save(function(err) {
      if (err) {
        return next(new errors.BadRequestError(err.message));
      }

      res.status(201).send({});
    });
  });
};

/**
 * Creates an exchange report
 * @param {http.IncomingMessage} req
 * @param {http.ServerResponse} res
 * @param {Function} next
 */
ReportsRouter.prototype.createExchangeReport = function(req, res, next) {
  const token = req.body.token;

  /* jshint maxstatements: 30, maxcomplexity: 15 */
  this.storage.models.StorageEvent.findOne({token: token}, (err, event) => {
    if (err) {
      return next(new errors.InternalError(err.message));
    }

    if (!event) {
      return next(new errors.NotFoundError('Storage event not found'));
    }

    // TODO: Check that event isn't closed for reports

    const report = {
      exchangeStart: req.body.exchangeStart,
      exchangeEnd: req.body.exchangeEnd,
      exchangeResultCode: req.body.exchangeResultCode,
      exchangeResultMessage: req.body.exchangeResultMessage
    };

    if (!this.validateExchangeReport(report)) {
      return next(new errors.BadRequestError('Invalid exchange report'));
    }

    const isClientReport = req.user ?
          (event.client === req.user.id) : req.farmerNodeID ?
          (event.client === req.farmerNodeID) : false;
    const isFarmerReport = req.farmerNodeID ?
          (event.farmer === req.farmerNodeID) : false;

    if (!isClientReport && !isFarmerReport) {
      return next(new errors.NotAuthorizedError('Not authorized to report'));
    }

    let modified = false;

    const hasClientReport = event.clientReport ?
          !!event.clientReport.exchangeResultCode : false;
    const hasFarmerReport = event.farmerReport ?
          !!event.farmerReport.exchangeResultCode : false;

    if (isClientReport && !hasClientReport) {
      modified = true;
      event.clientReport = report;
      if (Number(req.body.exchangeResultCode) === 1000) {
        event.success = true;
        this.updateReputation(event.farmer, points.TRANSFER_SUCCESS);
      } else {
        this.updateReputation(event.farmer, points.TRANSFER_FAILURE);
      }
    } else if (isFarmerReport && !hasFarmerReport) {
      modified = true;
      event.farmerReport = report;
    }

    if (modified) {
      event.save((err) => {
        if (err) {
          return next(new errors.InternalError(err.message));
        }

        // Kick off mirroring if needed
        const dataBytes = event.storage;
        this._handleExchangeReport({
          dataHash: req.body.dataHash,
          exchangeResultMessage: req.body.exchangeResultMessage
        }, dataBytes, (err) => {
          /* istanbul ignore next */
          if (err) {
            return log.warn(err.message);
          }
        });

        res.status(201).send({});
      });

    } else {
      res.status(200).send({});
    }

  });
};

/**
 * @private
 */
ReportsRouter.prototype._handleExchangeReport = function(report, dataBytes, callback) {
  const {dataHash, exchangeResultMessage} = report;

  switch (exchangeResultMessage) {
    case 'MIRROR_SUCCESS':
    case 'SHARD_UPLOADED':
    case 'DOWNLOAD_ERROR':
      this._triggerMirrorEstablish(constants.M_REPLICATE, dataHash, dataBytes, callback);
      break;
    default:
      callback(new Error('Exchange result type will not trigger action'));
  }
};

ReportsRouter._sortByTimeoutRate = function(a, b) {
  const a1 = a.contact.timeoutRate >= 0 ? a.contact.timeoutRate : 0;
  const b1 = b.contact.timeoutRate >= 0 ? b.contact.timeoutRate : 0;
  return (a1 === b1) ? 0 : (a1 > b1) ? 1 : -1;
};

ReportsRouter._sortByResponseTime = function(a, b) {
  const aTime = a.contact.responseTime || Infinity;
  const bTime = b.contact.responseTime || Infinity;
  return (aTime === bTime) ? 0 : (aTime > bTime) ? 1 : -1;
};

ReportsRouter.prototype._createStorageEvent = function(token,
                                                       clientNodeID,
                                                       farmerNodeID,
                                                       storage,
                                                       shardHash) {
  const StorageEvent = this.storage.models.StorageEvent;

  const storageEvent = new StorageEvent({
    token: token,
    user: null,
    client: clientNodeID, // farmer that already has the mirror
    farmer: farmerNodeID, // farmer storing the new mirror
    timestamp: Date.now(),
    downloadBandwidth: 0,
    storage: storage,
    shardHash: shardHash,
    success: false
  });

  storageEvent.save((err) => {
    if (err) {
      log.warn('_createStorageEvent: Error saving event, ' +
               'reason: %s', err.message);
    }
  });
};

/**
 * Loads some mirrors for the hash and establishes them
 * @private
 */
ReportsRouter.prototype._triggerMirrorEstablish = function(n,
                                                           hash,
                                                           dataBytes,
                                                           done) {
  const self = this;
  let item = null;

  function _loadShard(callback) {
    self.contracts.load(hash, (err, _item) => {
      if (err) {
        return callback(err);
      }
      item = _item;
      callback();
    });
  }

  function _getMirrors(callback) {
    self.storage.models.Mirror.find({ shardHash: hash })
      .populate('contact')
      .exec(callback);
  }

  function _getMirrorCandidate(mirrors, callback) {
    let established = [], available = [];

    mirrors.forEach((m) => {
      if (!m.contact) {
        log.warn('Mirror %s is missing contact in database', m._id);
      } else if (!m.isEstablished) {
        available.push(m);
      } else {
        established.push(m);
      }
    });

    if (available.length === 0) {
      return callback(new Error('No available mirrors'));
    }

    if (established.length >= n) {
      return callback(new Error('Auto mirroring limit is reached'));
    }

    available.sort(utils.sortByReputation);

    callback(null, available.shift());
  }

  function _getRetrievalTokenFromFarmer(mirror, callback) {
    let farmers = Object.keys(item.contracts);
    let pointer = null;
    let test = () => farmers.length === 0 || pointer !== null;
    let contact = storj.Contact(mirror.contact.toObject());

    async.until(test, (done) => {
      self.getContactById(farmers.pop(), (err, result) => {
        if (err) {
          return done();
        }

        let farmer = storj.Contact(result.toObject());

        self.network.getRetrievalPointer(
          farmer,
          item.getContract(farmer),
          (err, result) => {
            // NB: Make sure that we don't set pointer to undefined
            // instead of null that would trigger the until loop to quit
            if (err) {
              log.warn('Unable to get pointer for mirroring, reason: %s',
                       err.message);
            } else {
              pointer = result;
            }
            done();
          }
        );
      });
    }, () => {
      if (!pointer) {
        return callback(new Error('Failed to get pointer'));
      }

      let farmer = pointer.farmer;
      let token = pointer.token;
      self._createStorageEvent(token,
                               farmer.nodeID,
                               contact.nodeID,
                               dataBytes,
                               hash);

      callback(null, pointer, mirror, contact);
    });
  }

  function _establishMirror(source, mirror, contact, callback) {
    self.network.getMirrorNodes(
      [source],
      [contact],
      (err) => {
        if (err) {
          return callback(err);
        }

        mirror.isEstablished = true;
        mirror.save();
        item.addContract(contact, storj.Contract(mirror.contract));
        self.contracts.save(item, callback);
      }
    );
  }

  async.waterfall([
    _loadShard,
    _getMirrors,
    _getMirrorCandidate,
    _getRetrievalTokenFromFarmer,
    _establishMirror
  ], done);
};

/**
 * @private
 */
ReportsRouter.prototype.getContactById = BucketsRouter.prototype.getContactById;

/**
 * @private
 */
ReportsRouter.prototype._definitions = function() {
  return [
    ['POST', '/reports/exchanges', this.getLimiter(limiter(1000)),
     rawBody, this.compat.bind(this), this.authMiddleware.bind(this),
     this.createExchangeReport.bind(this)]
  ];
};

module.exports = ReportsRouter;
<|endoftext|>


<commit_before>var getJSON = function(url, callback) {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);
    xhr.responseType = 'json';
    xhr.onload = function() {
        var status = xhr.status;
        if (status == 200) {
            callback(null, xhr.response);
        } else {
            callback(status);
        }
    };
    xhr.send();
};
function showElementsByClassName(className) {
    var elements = document.getElementsByClassName(className);
    for ( var i = 0; i < elements.length; i++ ) {
        var element = elements[i];
        element.style.display = "block";
    }
}

var ociTrainingTrack = 11;
getJSON('https://grailsplugins.cfapps.io/api/training/'+ ociTrainingTrack, function(err, data) {
    var msg = '';
    if (err != null) {
        msg = 'Something went wrong while retrieving OCI training offerings';

    } else {
        if ( data.length == 0 ) {
            msg = '
Currenlty, we don\'t have any training offerings available
.';

        } else {
            msg += '
';
            msg += '<colgroup>';
            msg += '<col>';
            msg += '<col>';
            msg += '<col>';
            msg += '<col>';
            msg += '</colgroup>';
            msg += '
';
            msg += '
Course	Date(s)	Instructor(s)	Hour(s)
';
            msg += '
';
            msg += '
';
            for ( var i = 0; i < data.length; i++ ) {
                msg += '
'+ data[i].course + '	'+ data[i].dates + '	'+ data[i].instructors + '	'+ data[i].hours + '
';
            }
            msg += '
';
            msg += '
';
        }
    }
    var ociTraining = document.getElementById("ocitraining");
    ociTraining.innerHTML = msg;
    //showElementsByClassName('training')
});<commit_msg>show training<commit_after>var getJSON = function(url, callback) {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);
    xhr.responseType = 'json';
    xhr.onload = function() {
        var status = xhr.status;
        if (status == 200) {
            callback(null, xhr.response);
        } else {
            callback(status);
        }
    };
    xhr.send();
};
function showElementsByClassName(className) {
    var elements = document.getElementsByClassName(className);
    for ( var i = 0; i < elements.length; i++ ) {
        var element = elements[i];
        element.style.display = "block";
    }
}

var ociTrainingTrack = 11;
getJSON('https://grailsplugins.cfapps.io/api/training/'+ ociTrainingTrack, function(err, data) {
    var msg = '';
    if (err != null) {
        msg = 'Something went wrong while retrieving OCI training offerings';

    } else {
        if ( data.length == 0 ) {
            msg = '
Currenlty, we don\'t have any training offerings available
.';

        } else {
            msg += '
';
            msg += '<colgroup>';
            msg += '<col>';
            msg += '<col>';
            msg += '<col>';
            msg += '<col>';
            msg += '</colgroup>';
            msg += '
';
            msg += '
Course	Date(s)	Instructor(s)	Hour(s)
';
            msg += '
';
            msg += '
';
            for ( var i = 0; i < data.length; i++ ) {
                msg += '
'+ data[i].course + '	'+ data[i].dates + '	'+ data[i].instructors + '	'+ data[i].hours + '
';
            }
            msg += '
';
            msg += '
';
        }
    }
    var ociTraining = document.getElementById("ocitraining");
    ociTraining.innerHTML = msg;
    showElementsByClassName('training')
});<|endoftext|>


<commit_before>'use strict';

module.exports = {
  extends: 'octane',

  rules: {
    'no-invalid-interactive': false,
    'link-href-attributes': false
  },

  ignore: [
    'hyperchannel/templates/components/**',
    'app/templates/components/**'
  ],

  pending: [
    {
      "moduleId": "app/templates/settings",
      "only": [
        "no-action"
      ]
    },
    {
      "moduleId": "app/components/channel-container/template",
      "only": [
        "no-action",
        "no-curly-component-invocation",
        "no-implicit-this"
      ]
    },
    {
      "moduleId": "app/components/date-headline/template",
      "only": [
        "no-curly-component-invocation",
        "no-implicit-this"
      ]
    },
    {
      "moduleId": "app/components/link-to-username/template",
      "only": [
        "no-curly-component-invocation",
        "no-implicit-this"
      ]
    },
    {
      "moduleId": "app/components/message-chat/template",
      "only": [
        "no-action",
        "no-curly-component-invocation",
        "no-implicit-this"
      ]
    },
    {
      "moduleId": "app/components/message-chat-me/template",
      "only": [
        "no-curly-component-invocation",
        "no-implicit-this"
      ]
    },
    {
      "moduleId": "app/components/modal-target/template",
      "only": [
        "no-action",
        "no-curly-component-invocation",
        "no-implicit-this"
      ]
    },
    {
      "moduleId": "app/components/notification-topic-change/template",
      "only": [
        "no-curly-component-invocation",
        "no-implicit-this"
      ]
    },
    {
      "moduleId": "app/components/space-setting-modal/template",
      "only": [
        "no-action",
        "no-curly-component-invocation",
        "no-implicit-this",
        "require-button-type"
      ]
    },
    {
      "moduleId": "app/components/user-list/template",
      "only": [
        "no-action",
        "no-curly-component-invocation"
      ]
    },
    {
      "moduleId": "app/components/web-push-subscription/template",
      "only": [
        "no-action",
        "no-curly-component-invocation",
        "no-implicit-this",
        "require-button-type"
      ]
    },
    {
      "moduleId": "app/templates/space/channel",
      "only": [
        "no-action"
      ]
    },
    {
      "moduleId": "app/templates/space/user_channel",
      "only": [
        "no-action"
      ]
    },
    {
      "moduleId": "hyperchannel/components/channel-container/template",
      "only": [
        "no-action",
        "no-curly-component-invocation",
        "no-implicit-this"
      ]
    },
    {
      "moduleId": "hyperchannel/components/date-headline/template",
      "only": [
        "no-curly-component-invocation",
        "no-implicit-this"
      ]
    },
    {
      "moduleId": "hyperchannel/components/link-to-username/template",
      "only": [
        "no-curly-component-invocation",
        "no-implicit-this"
      ]
    },
    {
      "moduleId": "hyperchannel/components/message-chat-me/template",
      "only": [
        "no-curly-component-invocation",
        "no-implicit-this"
      ]
    },
    {
      "moduleId": "hyperchannel/components/message-chat/template",
      "only": [
        "no-action",
        "no-curly-component-invocation",
        "no-implicit-this"
      ]
    },
    {
      "moduleId": "hyperchannel/components/modal-target/template",
      "only": [
        "no-action",
        "no-curly-component-invocation",
        "no-implicit-this"
      ]
    },
    {
      "moduleId": "hyperchannel/components/notification-topic-change/template",
      "only": [
        "no-curly-component-invocation",
        "no-implicit-this"
      ]
    },
    {
      "moduleId": "hyperchannel/components/space-setting-modal/template",
      "only": [
        "no-action",
        "no-curly-component-invocation",
        "no-implicit-this",
        "require-button-type"
      ]
    },
    {
      "moduleId": "hyperchannel/components/user-list/template",
      "only": [
        "no-action",
        "no-curly-component-invocation"
      ]
    },
    {
      "moduleId": "hyperchannel/components/web-push-subscription/template",
      "only": [
        "no-action",
        "no-curly-component-invocation",
        "no-implicit-this",
        "require-button-type"
      ]
    },
    {
      "moduleId": "hyperchannel/templates/settings",
      "only": [
        "no-action"
      ]
    },
    {
      "moduleId": "hyperchannel/templates/space/channel",
      "only": [
        "no-action"
      ]
    },
    {
      "moduleId": "hyperchannel/templates/space/user_channel",
      "only": [
        "no-action"
      ]
    }
  ]
};
<commit_msg>Add an template lint exception<commit_after>'use strict';

module.exports = {
  extends: 'octane',

  rules: {
    'no-invalid-interactive': false,
    'link-href-attributes': false
  },

  ignore: [
    'hyperchannel/templates/components/**',
    'app/templates/components/**'
  ],

  pending: [
    {
      "moduleId": "app/templates/settings",
      "only": [
        "no-action"
      ]
    },
    {
      "moduleId": "app/components/channel-container/template",
      "only": [
        "no-action",
        "no-curly-component-invocation",
        "no-implicit-this",
        "no-outlet-outside-routes"
      ]
    },
    {
      "moduleId": "app/components/date-headline/template",
      "only": [
        "no-curly-component-invocation",
        "no-implicit-this"
      ]
    },
    {
      "moduleId": "app/components/link-to-username/template",
      "only": [
        "no-curly-component-invocation",
        "no-implicit-this"
      ]
    },
    {
      "moduleId": "app/components/message-chat/template",
      "only": [
        "no-action",
        "no-curly-component-invocation",
        "no-implicit-this"
      ]
    },
    {
      "moduleId": "app/components/message-chat-me/template",
      "only": [
        "no-curly-component-invocation",
        "no-implicit-this"
      ]
    },
    {
      "moduleId": "app/components/modal-target/template",
      "only": [
        "no-action",
        "no-curly-component-invocation",
        "no-implicit-this"
      ]
    },
    {
      "moduleId": "app/components/notification-topic-change/template",
      "only": [
        "no-curly-component-invocation",
        "no-implicit-this"
      ]
    },
    {
      "moduleId": "app/components/space-setting-modal/template",
      "only": [
        "no-action",
        "no-curly-component-invocation",
        "no-implicit-this",
        "require-button-type"
      ]
    },
    {
      "moduleId": "app/components/user-list/template",
      "only": [
        "no-action",
        "no-curly-component-invocation"
      ]
    },
    {
      "moduleId": "app/components/web-push-subscription/template",
      "only": [
        "no-action",
        "no-curly-component-invocation",
        "no-implicit-this",
        "require-button-type"
      ]
    },
    {
      "moduleId": "app/templates/space/channel",
      "only": [
        "no-action"
      ]
    },
    {
      "moduleId": "app/templates/space/user_channel",
      "only": [
        "no-action"
      ]
    },
    {
      "moduleId": "hyperchannel/components/channel-container/template",
      "only": [
        "no-action",
        "no-curly-component-invocation",
        "no-implicit-this",
        "no-outlet-outside-routes"
      ]
    },
    {
      "moduleId": "hyperchannel/components/date-headline/template",
      "only": [
        "no-curly-component-invocation",
        "no-implicit-this"
      ]
    },
    {
      "moduleId": "hyperchannel/components/link-to-username/template",
      "only": [
        "no-curly-component-invocation",
        "no-implicit-this"
      ]
    },
    {
      "moduleId": "hyperchannel/components/message-chat-me/template",
      "only": [
        "no-curly-component-invocation",
        "no-implicit-this"
      ]
    },
    {
      "moduleId": "hyperchannel/components/message-chat/template",
      "only": [
        "no-action",
        "no-curly-component-invocation",
        "no-implicit-this"
      ]
    },
    {
      "moduleId": "hyperchannel/components/modal-target/template",
      "only": [
        "no-action",
        "no-curly-component-invocation",
        "no-implicit-this"
      ]
    },
    {
      "moduleId": "hyperchannel/components/notification-topic-change/template",
      "only": [
        "no-curly-component-invocation",
        "no-implicit-this"
      ]
    },
    {
      "moduleId": "hyperchannel/components/space-setting-modal/template",
      "only": [
        "no-action",
        "no-curly-component-invocation",
        "no-implicit-this",
        "require-button-type"
      ]
    },
    {
      "moduleId": "hyperchannel/components/user-list/template",
      "only": [
        "no-action",
        "no-curly-component-invocation"
      ]
    },
    {
      "moduleId": "hyperchannel/components/web-push-subscription/template",
      "only": [
        "no-action",
        "no-curly-component-invocation",
        "no-implicit-this",
        "require-button-type"
      ]
    },
    {
      "moduleId": "hyperchannel/templates/settings",
      "only": [
        "no-action"
      ]
    },
    {
      "moduleId": "hyperchannel/templates/space/channel",
      "only": [
        "no-action"
      ]
    },
    {
      "moduleId": "hyperchannel/templates/space/user_channel",
      "only": [
        "no-action"
      ]
    }
  ]
};
<|endoftext|>


<commit_before>import React from 'react';
import {Link} from 'react-router';

class Navbar extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (

          <button type='button' className='navbar-toggle collapsed' data-toggle='collapse' data-target='#navbar'>
            Toggle navigation

          </button>
          <Link className='navbar-brand' to='/'>Live Dashboard</Link>

    );
  }
}

Navbar.contextTypes = {
  router: React.PropTypes.func.isRequired
};

export default Navbar;<commit_msg>navbar links<commit_after>import React from 'react';
import {Link} from 'react-router';

class Navbar extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (

          <button type='button' className='navbar-toggle collapsed' data-toggle='collapse' data-target='#navbar'>
            Toggle navigation

          </button>
          <Link className='navbar-brand' to='/'>Live Dashboard</Link>

<Link to='/'> New Event</Link>

<Link to='/'>Manage Events</Link>

    );
  }
}

Navbar.contextTypes = {
  router: React.PropTypes.func.isRequired
};

export default Navbar;<|endoftext|>


<commit_before>// Copyright 2015, EMC, Inc.

'use strict';

var di = require('di');
var express = require('express');
var cors = require('cors');
var onFinished = require('on-finished');
var http = require('http');
var https = require('https');
var fs = require('fs');
var proxyMiddleware = require('http-proxy-middleware');
var path = require('path');
var bodyParser = require('body-parser');
var rewriter = require('express-urlrewrite');

/**
 * Get remote address of the client.
 * @private
 * @param {express.Request} req from express
 * @returns {String|Undefined} either the ip of requester or undefined
 *                             if unavailable
 */
function remoteAddress(req) {

    if(req.get("X-Real-IP")) {
        return req.get("X-Real-IP");
    }

    if (req.ip) {
        return req.ip;
    }

    if (req._remoteAddress) {
        return req._remoteAddress;
    }

    if (req.connection) {
        return req.connection.remoteAddress;
    }

    return undefined;
}

module.exports = httpServiceFactory;

di.annotate(httpServiceFactory, new di.Provide('Http.Server'));
di.annotate(httpServiceFactory,
    new di.Inject(
        'Protocol.Events',
        'Services.Configuration',
        'Services.Lookup',
        'swagger',
        'Logger',
        'Promise',
        'Events',
        'Errors',
        'Http.Services.SkuPack',
        '_',
        'Auth.Services',
        di.Injector
    )
);

/**
 * Factory that creates the express http service
 * @private
 * @param {Protocol.Events} eventsProtocol
 * @param {Services.Configuration} configuration
 * @param {Services.Lookup} lokupService
 * @param swagger
 * @param Logger
 * @param Q
 * @param {Events} events
 * @param Errors
 * @param injector
 * @returns {function} HttpService constructor
 */
function httpServiceFactory(
    eventsProtocol,
    configuration,
    lookupService,
    swagger,
    Logger,
    Promise,
    events,
    Errors,
    skuService,
    _,
    authService,
    injector
) {
    var logger = Logger.initialize(httpServiceFactory);

    function HttpService(endpoint) {
        this.app = express();
        this.endpoint = this._parseEndpoint(endpoint);
        this.server = null;
        authService.init();
        this._setup();
    }

    HttpService.prototype.start = function() {
        var self = this;
        // Create Http Proxy
        if (this.endpoint.proxiesEnabled) {
            var httpProxies = configuration.get('httpProxies');
            if (httpProxies && _.isArray(httpProxies)) {
                _.forEach(httpProxies, createHttpProxy.bind(this, self.app));
            }
        }

        if (this.endpoint.httpsEnabled) {
            this.server = https.createServer(this.httpsOptions, this.app);
        } else {
            this.server = http.createServer(this.app);
        }

        this.server.on('close', function() {
            logger.info('Server Closing.');
        });

        return new Promise(function(resolve, reject) {
            return self.server.listen(
                self.endpoint.port,
                self.endpoint.address,
                function(error) {
                    return error ? reject(error) : resolve();
            });
        });
    };

    HttpService.prototype.stop = function() {
        var self = this;
        return new Promise(function(resolve, reject) {
            return self.server.close(
                function(error) {
                    return error ? reject(error) : resolve();
            });
        });
    };

    HttpService.prototype.createSwagger = function() {
        var app = this.app;
        var endpoint = this.endpoint;
        //Swagger support
        var swaggerConfig = {
            appRoot: path.resolve(__dirname, '../../'),
            configDir: path.resolve(__dirname, '../../swagger_config'),
            swagger: path.resolve(__dirname, '../../static/' + endpoint.yamlName),
            authEnabled: this.endpoint.authEnabled
        };

        var redfishConfig = {
            appRoot: path.resolve(__dirname, '../../'),
            configDir: path.resolve(__dirname, '../../swagger_config'),
            swagger: path.resolve(__dirname, '../../static/redfish.yaml'),
            authEnabled: this.endpoint.authEnabled
        };

        //todo: make this configurable vis endpoint
        var sbSwaggerConfig = {
            appRoot: path.resolve(__dirname, '../../'),
            configDir: path.resolve(__dirname, '../../swagger_config'),
            swagger: path.resolve(__dirname, '../../static/monorail-2.0-sb.yaml')
        };

        var swaggerCreateAsync = Promise.promisify(swagger.create);

        var apiV2 = swaggerCreateAsync(swaggerConfig).then(function(swaggerExpress) {
            return swaggerExpress.register(app);
        });

        var sbApiV2 = swaggerCreateAsync(sbSwaggerConfig).then(function(swaggerExpress) {
            return swaggerExpress.register(app);
        });

        var redfish = swaggerCreateAsync(redfishConfig).then(function(swaggerExpress) {
            var swaggerUi = require('swagger-tools/middleware/swagger-ui');
            app.use(
                swaggerUi(swaggerExpress.runner.swagger, {
                    swaggerUi: '/swagger-ui',
                    swaggerUiDir: path.resolve(__dirname, '../../static/swagger-ui')
                })
            );
            return swaggerExpress.register(app);
        });

        return Promise.all([apiV2, sbApiV2, redfish]);
    };

    HttpService.prototype._setup =  function() {
        var app = this.app;
        var endpoint = this.endpoint;

        // CORS Support
        app.use(cors());
        app.options('*', cors());

        // Imaging Event Middleware
        app.use(httpEventMiddleware);

        // Override default static directory with sku specific handlers
        app.use(function(req, res, next) {
            skuService.static(req, res, next);
        });

        // Parse request body. Limit set to 50MB
        app.use(bodyParser.json({limit: '50mb'}));

        // Default Static Directory
        app.use(express.static('./static/http'));
        app.use('/upnp', express.static('./static/upnp'));

        // Additional Static Directory
        app.use(
            express.static(
                configuration.get('httpStaticRoot', '/opt/monorail/static/http')
            )
        );

        //re-route common and current
        var versionPath = configuration.get('versionBase') || '1.1';
        app.use(rewriter('/api/current/*', '/api/' + versionPath + '/$1'));
        app.use(rewriter('/api/common/*', '/api/' + versionPath + '/$1'));

        //enble/disable trusted proxy
        if ((configuration.get('trustedProxy') || false) === true) {
            app.enable('trust proxy');
        }

        // API Docs Directory
        app.use('/docs',
            express.static(
                configuration.get('httpDocsRoot', './build/apidoc')
            )
        );

        // UI Directory
        app.use('/ui', express.static('./static/web-ui'));

        // Task-doc Directory
        app.use('/taskdoc', express.static('./static/taskdoc/'));

        // Initialize authentication always
        app.use(authService.getPassportMidware());

        // Only apply authentication routes if auth is enabled
        if (endpoint.authEnabled) {
            // mount ./login only when authentication is enabled
            app.use(injector.get('Http.Api.Login'));
            app.all('/api/1.1/*', authService.authMiddlewareJwt.bind(authService));
        }

        // Mount API Routers
        _.forEach(endpoint.routers, function(routerName) {
            var router;
            try {
                router = injector.get(routerName);
            } catch (e){
                logger.error('Cound not found router named : ' + routerName, { error: e });
                return;
            }

            router.mount();
            app.use(/\/api\/1\.1/, router);
        });

        if (endpoint.httpsEnabled) {
            if (endpoint.httpsPfx) {
                this.httpsOptions = {
                    pfx: fs.readFileSync(endpoint.httpsPfx)
                };
            } else {
                this.httpsOptions = {
                    cert: fs.readFileSync(endpoint.httpsCert),
                    key: fs.readFileSync(endpoint.httpsKey)
                };
            }
        }
    };

    HttpService.prototype._parseEndpoint = function(endpoint) {
        function parseRouterNames (routers) {
            if (_.isEmpty(routers)) {
                return ['northbound-api-router', 'southbound-api-router'];
            } else {
                if (_.isString(routers)) {
                    return [routers];
                }
                if (_.isArray(routers) && _.all(routers, _.isString)) {
                    return routers;
                }
                return ['northbound-api-router', 'southbound-api-router'];
            }
        }

        return {
            address: endpoint.address || '0.0.0.0',
            port: endpoint.port || (endpoint.httpsEnabled ? 443 : 80),
            httpsEnabled : endpoint.httpsEnabled === true,
            httpsCert: endpoint.httpsCert || 'data/dev-cert.pem',
            httpsKey: endpoint.httpsKey || 'data/dev-key.pem',
            httpsPfx: endpoint.httpsPfx,
            proxiesEnabled: endpoint.proxiesEnabled === true,
            authEnabled: endpoint.authEnabled === true,
            routers: parseRouterNames(endpoint.routers),
            yamlName: endpoint.yamlName || 'monorail-2.0.yaml'
        };
    };

    function httpEventMiddleware(req, res, next) {
        req._startAt = process.hrtime();
        res.locals.ipAddress = remoteAddress(req);
        res.locals.scope = ['global'];

        onFinished(res, function () {
            if (!req._startAt) {
                return '';
            }

            var diff = process.hrtime(req._startAt),
                ms = diff[0] * 1e3 + diff[1] * 1e-6;

            var data = {
                ipAddress: res.locals.ipAddress
            };

            if (res.locals.identifier) {
                data.id = res.locals.identifier;
            }

            logger.debug(
                'http: ' + req.method +
                ' ' + res.statusCode +
                ' ' + ms.toFixed(3) +
                ' - ' + req.originalUrl,
                data
            );

            eventsProtocol.publishHttpResponse(
                res.locals.identifier || 'external',
                {
                    address: res.locals.ipAddress,
                    method: req.method,
                    url: req.originalUrl,
                    statusCode: res.statusCode,
                    time: ms.toFixed(3)
                }
            );
        });

        lookupService.ipAddressToNodeId(res.locals.ipAddress).then(function (nodeId) {
            res.locals.identifier = nodeId;
            return skuService.setupScope(nodeId);
        }).then(function(scope) {
            res.locals.scope = scope;
        }).catch(Errors.NotFoundError, function () {
            // No longer log NotFoundErrors
        }).catch(function (error) {
            events.ignoreError(error);
        }).finally(function () {
            next();
        });
    }

    /**
     * Creates http proxy
     * @private
     * @param {Object} httpProxy - proxy parameters
     * @returns
     */
    function createHttpProxy(app, httpProxy){
        var sep = path.sep; //The platform-specific file separator. '\\' or '/'
        try {
            var localPath = httpProxy.localPath || sep,
                remotePath = httpProxy.remotePath || sep,
                serverAddr = httpProxy.server;

            //localPath & remotePath should always starts with /
            if (!localPath.startsWith(sep)) {
                localPath = sep + localPath;
            }
            if (!remotePath.startsWith(sep)) {
                remotePath = sep + remotePath;
            }

            //localPath & remotePath should both ends with / or not, otherwise the pathRewrite will
            //not work well
            if (remotePath.endsWith(sep) && !localPath.endsWith(sep)) {
                localPath = localPath + sep;
            }
            else if (!remotePath.endsWith(sep) && localPath.endsWith(sep)) {
                remotePath = remotePath + sep;
            }

            var pathRewrite = {};
            pathRewrite['^' + localPath] = remotePath; //^ is a symbol in regex, means beginning

            var proxy = proxyMiddleware(localPath, {
                target: serverAddr,
                changeOrigin: true, //needed for virtual hosted sites. Without this, proxy to
                                    //external sites will fail and are blocked by corp's internal
                                    //firewall
                pathRewrite: pathRewrite,
                secure: false //ignore verify SSL Certs, so proxy works for https
            });
            app.use(proxy);
            logger.info('Create proxy succeeded',
                {
                    localPath: localPath,
                    remotePath: remotePath,
                    serverAddress: serverAddr
                });
        }
        catch(e){
            logger.error('Create proxy failed',
                {
                    message: e.message,
                    parameter: httpProxy
                }
            );
        }
    }

    return HttpService;
}
<commit_msg>Set routes current and common to 2.0 by default<commit_after>// Copyright 2015, EMC, Inc.

'use strict';

var di = require('di');
var express = require('express');
var cors = require('cors');
var onFinished = require('on-finished');
var http = require('http');
var https = require('https');
var fs = require('fs');
var proxyMiddleware = require('http-proxy-middleware');
var path = require('path');
var bodyParser = require('body-parser');
var rewriter = require('express-urlrewrite');

/**
 * Get remote address of the client.
 * @private
 * @param {express.Request} req from express
 * @returns {String|Undefined} either the ip of requester or undefined
 *                             if unavailable
 */
function remoteAddress(req) {

    if(req.get("X-Real-IP")) {
        return req.get("X-Real-IP");
    }

    if (req.ip) {
        return req.ip;
    }

    if (req._remoteAddress) {
        return req._remoteAddress;
    }

    if (req.connection) {
        return req.connection.remoteAddress;
    }

    return undefined;
}

module.exports = httpServiceFactory;

di.annotate(httpServiceFactory, new di.Provide('Http.Server'));
di.annotate(httpServiceFactory,
    new di.Inject(
        'Protocol.Events',
        'Services.Configuration',
        'Services.Lookup',
        'swagger',
        'Logger',
        'Promise',
        'Events',
        'Errors',
        'Http.Services.SkuPack',
        '_',
        'Auth.Services',
        di.Injector
    )
);

/**
 * Factory that creates the express http service
 * @private
 * @param {Protocol.Events} eventsProtocol
 * @param {Services.Configuration} configuration
 * @param {Services.Lookup} lokupService
 * @param swagger
 * @param Logger
 * @param Q
 * @param {Events} events
 * @param Errors
 * @param injector
 * @returns {function} HttpService constructor
 */
function httpServiceFactory(
    eventsProtocol,
    configuration,
    lookupService,
    swagger,
    Logger,
    Promise,
    events,
    Errors,
    skuService,
    _,
    authService,
    injector
) {
    var logger = Logger.initialize(httpServiceFactory);

    function HttpService(endpoint) {
        this.app = express();
        this.endpoint = this._parseEndpoint(endpoint);
        this.server = null;
        authService.init();
        this._setup();
    }

    HttpService.prototype.start = function() {
        var self = this;
        // Create Http Proxy
        if (this.endpoint.proxiesEnabled) {
            var httpProxies = configuration.get('httpProxies');
            if (httpProxies && _.isArray(httpProxies)) {
                _.forEach(httpProxies, createHttpProxy.bind(this, self.app));
            }
        }

        if (this.endpoint.httpsEnabled) {
            this.server = https.createServer(this.httpsOptions, this.app);
        } else {
            this.server = http.createServer(this.app);
        }

        this.server.on('close', function() {
            logger.info('Server Closing.');
        });

        return new Promise(function(resolve, reject) {
            return self.server.listen(
                self.endpoint.port,
                self.endpoint.address,
                function(error) {
                    return error ? reject(error) : resolve();
            });
        });
    };

    HttpService.prototype.stop = function() {
        var self = this;
        return new Promise(function(resolve, reject) {
            return self.server.close(
                function(error) {
                    return error ? reject(error) : resolve();
            });
        });
    };

    HttpService.prototype.createSwagger = function() {
        var app = this.app;
        var endpoint = this.endpoint;
        //Swagger support
        var swaggerConfig = {
            appRoot: path.resolve(__dirname, '../../'),
            configDir: path.resolve(__dirname, '../../swagger_config'),
            swagger: path.resolve(__dirname, '../../static/' + endpoint.yamlName),
            authEnabled: this.endpoint.authEnabled
        };

        var redfishConfig = {
            appRoot: path.resolve(__dirname, '../../'),
            configDir: path.resolve(__dirname, '../../swagger_config'),
            swagger: path.resolve(__dirname, '../../static/redfish.yaml'),
            authEnabled: this.endpoint.authEnabled
        };

        //todo: make this configurable vis endpoint
        var sbSwaggerConfig = {
            appRoot: path.resolve(__dirname, '../../'),
            configDir: path.resolve(__dirname, '../../swagger_config'),
            swagger: path.resolve(__dirname, '../../static/monorail-2.0-sb.yaml')
        };

        var swaggerCreateAsync = Promise.promisify(swagger.create);

        var apiV2 = swaggerCreateAsync(swaggerConfig).then(function(swaggerExpress) {
            return swaggerExpress.register(app);
        });

        var sbApiV2 = swaggerCreateAsync(sbSwaggerConfig).then(function(swaggerExpress) {
            return swaggerExpress.register(app);
        });

        var redfish = swaggerCreateAsync(redfishConfig).then(function(swaggerExpress) {
            var swaggerUi = require('swagger-tools/middleware/swagger-ui');
            app.use(
                swaggerUi(swaggerExpress.runner.swagger, {
                    swaggerUi: '/swagger-ui',
                    swaggerUiDir: path.resolve(__dirname, '../../static/swagger-ui')
                })
            );
            return swaggerExpress.register(app);
        });

        return Promise.all([apiV2, sbApiV2, redfish]);
    };

    HttpService.prototype._setup =  function() {
        var app = this.app;
        var endpoint = this.endpoint;

        // CORS Support
        app.use(cors());
        app.options('*', cors());

        // Imaging Event Middleware
        app.use(httpEventMiddleware);

        // Override default static directory with sku specific handlers
        app.use(function(req, res, next) {
            skuService.static(req, res, next);
        });

        // Parse request body. Limit set to 50MB
        app.use(bodyParser.json({limit: '50mb'}));

        // Default Static Directory
        app.use(express.static('./static/http'));
        app.use('/upnp', express.static('./static/upnp'));

        // Additional Static Directory
        app.use(
            express.static(
                configuration.get('httpStaticRoot', '/opt/monorail/static/http')
            )
        );

        //re-route common and current
        var versionPath = configuration.get('versionBase') || '2.0';
        app.use(rewriter('/api/current/*', '/api/' + versionPath + '/$1'));
        app.use(rewriter('/api/common/*', '/api/' + versionPath + '/$1'));

        //enble/disable trusted proxy
        if ((configuration.get('trustedProxy') || false) === true) {
            app.enable('trust proxy');
        }

        // API Docs Directory
        app.use('/docs',
            express.static(
                configuration.get('httpDocsRoot', './build/apidoc')
            )
        );

        // UI Directory
        app.use('/ui', express.static('./static/web-ui'));

        // Task-doc Directory
        app.use('/taskdoc', express.static('./static/taskdoc/'));

        // Initialize authentication always
        app.use(authService.getPassportMidware());

        // Only apply authentication routes if auth is enabled
        if (endpoint.authEnabled) {
            // mount ./login only when authentication is enabled
            app.use(injector.get('Http.Api.Login'));
            app.all('/api/1.1/*', authService.authMiddlewareJwt.bind(authService));
        }

        // Mount API Routers
        _.forEach(endpoint.routers, function(routerName) {
            var router;
            try {
                router = injector.get(routerName);
            } catch (e){
                logger.error('Cound not found router named : ' + routerName, { error: e });
                return;
            }

            router.mount();
            app.use(/\/api\/1\.1/, router);
        });

        if (endpoint.httpsEnabled) {
            if (endpoint.httpsPfx) {
                this.httpsOptions = {
                    pfx: fs.readFileSync(endpoint.httpsPfx)
                };
            } else {
                this.httpsOptions = {
                    cert: fs.readFileSync(endpoint.httpsCert),
                    key: fs.readFileSync(endpoint.httpsKey)
                };
            }
        }
    };

    HttpService.prototype._parseEndpoint = function(endpoint) {
        function parseRouterNames (routers) {
            if (_.isEmpty(routers)) {
                return ['northbound-api-router', 'southbound-api-router'];
            } else {
                if (_.isString(routers)) {
                    return [routers];
                }
                if (_.isArray(routers) && _.all(routers, _.isString)) {
                    return routers;
                }
                return ['northbound-api-router', 'southbound-api-router'];
            }
        }

        return {
            address: endpoint.address || '0.0.0.0',
            port: endpoint.port || (endpoint.httpsEnabled ? 443 : 80),
            httpsEnabled : endpoint.httpsEnabled === true,
            httpsCert: endpoint.httpsCert || 'data/dev-cert.pem',
            httpsKey: endpoint.httpsKey || 'data/dev-key.pem',
            httpsPfx: endpoint.httpsPfx,
            proxiesEnabled: endpoint.proxiesEnabled === true,
            authEnabled: endpoint.authEnabled === true,
            routers: parseRouterNames(endpoint.routers),
            yamlName: endpoint.yamlName || 'monorail-2.0.yaml'
        };
    };

    function httpEventMiddleware(req, res, next) {
        req._startAt = process.hrtime();
        res.locals.ipAddress = remoteAddress(req);
        res.locals.scope = ['global'];

        onFinished(res, function () {
            if (!req._startAt) {
                return '';
            }

            var diff = process.hrtime(req._startAt),
                ms = diff[0] * 1e3 + diff[1] * 1e-6;

            var data = {
                ipAddress: res.locals.ipAddress
            };

            if (res.locals.identifier) {
                data.id = res.locals.identifier;
            }

            logger.debug(
                'http: ' + req.method +
                ' ' + res.statusCode +
                ' ' + ms.toFixed(3) +
                ' - ' + req.originalUrl,
                data
            );

            eventsProtocol.publishHttpResponse(
                res.locals.identifier || 'external',
                {
                    address: res.locals.ipAddress,
                    method: req.method,
                    url: req.originalUrl,
                    statusCode: res.statusCode,
                    time: ms.toFixed(3)
                }
            );
        });

        lookupService.ipAddressToNodeId(res.locals.ipAddress).then(function (nodeId) {
            res.locals.identifier = nodeId;
            return skuService.setupScope(nodeId);
        }).then(function(scope) {
            res.locals.scope = scope;
        }).catch(Errors.NotFoundError, function () {
            // No longer log NotFoundErrors
        }).catch(function (error) {
            events.ignoreError(error);
        }).finally(function () {
            next();
        });
    }

    /**
     * Creates http proxy
     * @private
     * @param {Object} httpProxy - proxy parameters
     * @returns
     */
    function createHttpProxy(app, httpProxy){
        var sep = path.sep; //The platform-specific file separator. '\\' or '/'
        try {
            var localPath = httpProxy.localPath || sep,
                remotePath = httpProxy.remotePath || sep,
                serverAddr = httpProxy.server;

            //localPath & remotePath should always starts with /
            if (!localPath.startsWith(sep)) {
                localPath = sep + localPath;
            }
            if (!remotePath.startsWith(sep)) {
                remotePath = sep + remotePath;
            }

            //localPath & remotePath should both ends with / or not, otherwise the pathRewrite will
            //not work well
            if (remotePath.endsWith(sep) && !localPath.endsWith(sep)) {
                localPath = localPath + sep;
            }
            else if (!remotePath.endsWith(sep) && localPath.endsWith(sep)) {
                remotePath = remotePath + sep;
            }

            var pathRewrite = {};
            pathRewrite['^' + localPath] = remotePath; //^ is a symbol in regex, means beginning

            var proxy = proxyMiddleware(localPath, {
                target: serverAddr,
                changeOrigin: true, //needed for virtual hosted sites. Without this, proxy to
                                    //external sites will fail and are blocked by corp's internal
                                    //firewall
                pathRewrite: pathRewrite,
                secure: false //ignore verify SSL Certs, so proxy works for https
            });
            app.use(proxy);
            logger.info('Create proxy succeeded',
                {
                    localPath: localPath,
                    remotePath: remotePath,
                    serverAddress: serverAddr
                });
        }
        catch(e){
            logger.error('Create proxy failed',
                {
                    message: e.message,
                    parameter: httpProxy
                }
            );
        }
    }

    return HttpService;
}
<|endoftext|>


<commit_before>'use strict';

var conversions = [{
    // Length
    'in': 96,
    'px': 1,
    'pt': 4 / 3,
    'pc': 16
}, {
    // Time
    's': 1000,
    'ms': 1
}];

function dropLeadingZero (number) {
    var value = number.toString();

    if (value[0] === '0' && number % 1) {
        return value.substring(1);
    }

    return value;
}

module.exports = function (number, unit) {
    var converted,
        value = dropLeadingZero(number) + (unit ? unit : ''),
        conversion,
        base;

    conversion = conversions.filter(function (area) {
        return unit in area;
    })[0];

    if (conversion) {
        base = number / conversion[unit];

        converted = Object.keys(conversion)
            .filter(function (u) {
                return unit !== u;
            })
            .map(function (u) {
                return dropLeadingZero(base / conversion[u]) + u;
            })
            .reduce(function (a, b) {
                return a.length < b.length ? a : b;
            });

        if(converted.length < value.length) {
            value = converted;
        }
    }

    return value;
};
<commit_msg>JSCS.<commit_after>'use strict';

var conversions = [{
    // Length
    'in': 96,
    'px': 1,
    'pt': 4 / 3,
    'pc': 16
}, {
    // Time
    's': 1000,
    'ms': 1
}];

function dropLeadingZero (number) {
    var value = number.toString();

    if (value[0] === '0' && number % 1) {
        return value.substring(1);
    }

    return value;
}

module.exports = function (number, unit) {
    var converted,
        value = dropLeadingZero(number) + (unit ? unit : ''),
        conversion,
        base;

    conversion = conversions.filter(function (area) {
        return unit in area;
    })[0];

    if (conversion) {
        base = number / conversion[unit];

        converted = Object.keys(conversion)
            .filter(function (u) {
                return unit !== u;
            })
            .map(function (u) {
                return dropLeadingZero(base / conversion[u]) + u;
            })
            .reduce(function (a, b) {
                return a.length < b.length ? a : b;
            });

        if (converted.length < value.length) {
            value = converted;
        }
    }

    return value;
};
<|endoftext|>