<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';

import Modal from 'react-bootstrap/lib/Modal';
import ButtonGroup from 'react-bootstrap/lib/ButtonGroup';
import Button from 'react-bootstrap/lib/Button';
import Alert from 'react-bootstrap/lib/Alert';
import Glyphicon from 'react-bootstrap/lib/Glyphicon';
import Slider from 'react-rangeslider';
import 'react-rangeslider/lib/index.css';

import { mapSetTileFormat, mapSetOverlayOpacity } from 'fm3/actions/mapActions';
import { setHomeLocation, setActiveModal, setExpertMode, setSelectingHomeLocation } from 'fm3/actions/mainActions';
import { toastsAdd } from 'fm3/actions/toastsActions';
import { trackViewerSetEleSmoothingFactor } from 'fm3/actions/trackViewerActions';

import FontAwesomeIcon from 'fm3/components/FontAwesomeIcon';
import { formatGpsCoord } from 'fm3/geoutils';
import mapEventEmitter from 'fm3/emitters/mapEventEmitter';
import * as FmPropTypes from 'fm3/propTypes';

class Settings extends React.Component {
  static propTypes = {
    homeLocation: PropTypes.shape({
      lat: PropTypes.number,
      lon: PropTypes.number,
    }),
    tileFormat: FmPropTypes.tileFormat.isRequired,
    onSave: PropTypes.func.isRequired,
    onClose: PropTypes.func.isRequired,
    onHomeLocationSelect: PropTypes.func.isRequired,
    onHomeLocationSelectionFinish: PropTypes.func.isRequired,
    nlcOpacity: PropTypes.number.isRequired,
    touristOverlayOpacity: PropTypes.number.isRequired,
    cycloOverlayOpacity: PropTypes.number.isRequired,
    zoom: PropTypes.number,
    expertMode: PropTypes.bool.isRequired,
    trackViewerEleSmoothingFactor: PropTypes.number.isRequired,
    selectingHomeLocation: PropTypes.bool,
  };

  constructor(props) {
    super(props);
    this.state = {
      homeLocationCssClasses: '',
      tileFormat: props.tileFormat,
      homeLocation: props.homeLocation,
      nlcOpacity: props.nlcOpacity,
      touristOverlayOpacity: props.touristOverlayOpacity,
      cycloOverlayOpacity: props.cycloOverlayOpacity,
      expertMode: props.expertMode,
      trackViewerEleSmoothingFactor: props.trackViewerEleSmoothingFactor,
    };
  }

  componentWillMount() {
    mapEventEmitter.on('mapClick', this.onHomeLocationSelected);
  }

  componentWillUnmount() {
    mapEventEmitter.removeListener('mapClick', this.onHomeLocationSelected);
  }

  onHomeLocationSelected = (lat, lon) => {
    this.setState({ homeLocation: { lat, lon }, homeLocationCssClasses: 'animated flash' }); // via animate.css
    this.props.onHomeLocationSelectionFinish();
  }

  handleSave = () => {
    this.props.onSave(
      this.state.tileFormat,
      this.state.homeLocation,
      this.state.nlcOpacity,
      this.state.touristOverlayOpacity,
      this.state.cycloOverlayOpacity,
      this.state.expertMode,
      this.state.trackViewerEleSmoothingFactor,
    );
  }

  render() {
    const {
      onClose, onHomeLocationSelect, selectingHomeLocation, zoom,
    } = this.props;
    const { homeLocation, homeLocationCssClasses } = this.state;
    const nlcOverlayIsNotVisible = zoom < 14;

    const userMadeChanges = ['tileFormat', 'homeLocation', 'nlcOpacity', 'touristOverlayOpacity', 'cycloOverlayOpacity', 'expertMode', 'trackViewerEleSmoothingFactor']
      .some(prop => this.state[prop] !== this.props[prop]);

    const homeLocationInfo = homeLocation
      ? `${formatGpsCoord(homeLocation.lat, 'SN')} ${formatGpsCoord(homeLocation.lon, 'WE')}`
      : 'neurčená';

    return (
      <Modal show={!selectingHomeLocation} onHide={onClose}>
        <Modal.Header closeButton>
          <Modal.Title>Nastavenia</Modal.Title>
        </Modal.Header>
        <Modal.Body>

Formát dlaždíc:

            <ButtonGroup>
              <Button
                active={this.state.tileFormat === 'png'}
                onClick={() => this.setState({ tileFormat: 'png' })}
              >
                PNG
              </Button>
              <Button
                active={this.state.tileFormat === 'jpeg'}
                onClick={() => this.setState({ tileFormat: 'jpeg' })}
              >
                JPG
              </Button>
            </ButtonGroup>

          <Alert>
            Mapové dlaždice vyzerajú lepšie v PNG formáte, ale sú asi 4x väčšie než JPG dlaždice.
            Pri pomalom internete preto odporúčame zvoliť JPG.
          </Alert>

            {'Domovská poloha: '}
            {homeLocationInfo}

          <Button onClick={() => onHomeLocationSelect()}>
            <FontAwesomeIcon icon="crosshairs" /> Vybrať na mape
          </Button>

Viditeľnosť vrstvy Lesné cesty NLC: {this.state.nlcOpacity.toFixed(1) * 100}%

          <Slider
            value={this.state.nlcOpacity}
            min={0.1}
            max={1.0}
            step={0.1}
            tooltip={false}
            onChange={newOpacity => this.setState({ nlcOpacity: newOpacity })}
          />
          {nlcOverlayIsNotVisible &&
            <Alert>
              NLC vrstva sa zobrazuje až pri detailnejšom priblížení (od zoom úrovne 14).
            </Alert>
          }

Expertný mód:

            <ButtonGroup>
              <Button
                active={!this.state.expertMode}
                onClick={() => this.setState({ expertMode: false })}
              >
                Vypnutý
              </Button>
              <Button
                active={this.state.expertMode}
                onClick={() => this.setState({ expertMode: true })}
              >
                Zapnutý
              </Button>
            </ButtonGroup>

          {!this.state.expertMode &&
            <Alert>
              V expertnom móde sú dostupné nástroje pre pokročilých používateľov.
            </Alert>
          }
          {this.state.expertMode &&

                {'Viditeľnosť vrstvy Turistické trasy: '}
                {this.state.touristOverlayOpacity.toFixed(1) * 100}%

              <Slider
                value={this.state.touristOverlayOpacity}
                min={0.1}
                max={1.0}
                step={0.1}
                tooltip={false}
                onChange={newOpacity => this.setState({ touristOverlayOpacity: newOpacity })}
              />

          }
          {this.state.expertMode &&

                {'Viditeľnosť vrstvy Cyklotrasy: '}
                {this.state.cycloOverlayOpacity.toFixed(1) * 100}%

              <Slider
                value={this.state.cycloOverlayOpacity}
                min={0.1}
                max={1.0}
                step={0.1}
                tooltip={false}
                onChange={newOpacity => this.setState({ cycloOverlayOpacity: newOpacity })}
              />

          }
          {this.state.expertMode &&

Úroveň vyhladzovania pri výpočte celkovej nastúpanej/naklesanej nadmorskej výšky v prehliadači trás: {(this.state.trackViewerEleSmoothingFactor)}

              <Slider
                value={this.state.trackViewerEleSmoothingFactor}
                min={1}
                max={10}
                step={1}
                tooltip={false}
                onChange={newValue => this.setState({ trackViewerEleSmoothingFactor: newValue })}
              />

          }
          {this.state.expertMode &&
            <Alert>
              Pri hodnote 1 sa berú do úvahy všetky nadmorské výšky samostatne. Vyššie hodnoty zodpovedajú šírke plávajúceho okna ktorým sa vyhladzujú nadmorské výšky.
            </Alert>
          }
        </Modal.Body>
        <Modal.Footer>
          <Button bsStyle="info" onClick={this.handleSave} disabled={!userMadeChanges}><Glyphicon glyph="floppy-disk" /> Uložiť</Button>
          <Button onClick={onClose}><Glyphicon glyph="remove" /> Zrušiť</Button>
        </Modal.Footer>
      </Modal>
    );
  }
}

export default connect(
  state => ({
    tileFormat: state.map.tileFormat,
    homeLocation: state.main.homeLocation,
    zoom: state.map.zoom,
    nlcOpacity: state.map.overlayOpacity.N,
    touristOverlayOpacity: state.map.overlayOpacity.t,
    cycloOverlayOpacity: state.map.overlayOpacity.c,
    expertMode: state.main.expertMode,
    trackViewerEleSmoothingFactor: state.trackViewer.eleSmoothingFactor,
    selectingHomeLocation: state.main.selectingHomeLocation,
  }),
  dispatch => ({
    onSave(tileFormat, homeLocation, nlcOpacity, touristOverlayOpacity, cycloOverlayOpacity, expertMode, trackViewerEleSmoothingFactor) {
      dispatch(mapSetTileFormat(tileFormat));
      dispatch(setHomeLocation(homeLocation));
      dispatch(mapSetOverlayOpacity('N', nlcOpacity));
      dispatch(mapSetOverlayOpacity('t', touristOverlayOpacity));
      dispatch(mapSetOverlayOpacity('c', cycloOverlayOpacity));
      dispatch(setActiveModal(null));
      dispatch(setExpertMode(expertMode));
      dispatch(trackViewerSetEleSmoothingFactor(trackViewerEleSmoothingFactor));
      dispatch(toastsAdd({
        collapseKey: 'settings.saved',
        message: 'Zmeny boli uložené.',
        style: 'info',
        timeout: 5000,
      }));
    },
    onClose() {
      dispatch(setActiveModal(null));
    },
    onHomeLocationSelect() {
      dispatch(setSelectingHomeLocation(true));
    },
    onHomeLocationSelectionFinish() {
      dispatch(setSelectingHomeLocation(false));
    },
  }),
)(Settings);
<commit_msg>structured settings to tabs + added preparation for name and email changing<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';

import Modal from 'react-bootstrap/lib/Modal';
import ButtonGroup from 'react-bootstrap/lib/ButtonGroup';
import Button from 'react-bootstrap/lib/Button';
import Alert from 'react-bootstrap/lib/Alert';
import Tabs from 'react-bootstrap/lib/Tabs';
import Tab from 'react-bootstrap/lib/Tab';
import FormGroup from 'react-bootstrap/lib/FormGroup';
import ControlLabel from 'react-bootstrap/lib/ControlLabel';
import FormControl from 'react-bootstrap/lib/FormControl';

import Glyphicon from 'react-bootstrap/lib/Glyphicon';
import Slider from 'react-rangeslider';
import 'react-rangeslider/lib/index.css';

import { mapSetTileFormat, mapSetOverlayOpacity } from 'fm3/actions/mapActions';
import { setHomeLocation, setActiveModal, setExpertMode, setSelectingHomeLocation } from 'fm3/actions/mainActions';
import { toastsAdd } from 'fm3/actions/toastsActions';
import { trackViewerSetEleSmoothingFactor } from 'fm3/actions/trackViewerActions';

import FontAwesomeIcon from 'fm3/components/FontAwesomeIcon';
import { formatGpsCoord } from 'fm3/geoutils';
import mapEventEmitter from 'fm3/emitters/mapEventEmitter';
import * as FmPropTypes from 'fm3/propTypes';

class Settings extends React.Component {
  static propTypes = {
    homeLocation: PropTypes.shape({
      lat: PropTypes.number,
      lon: PropTypes.number,
    }),
    tileFormat: FmPropTypes.tileFormat.isRequired,
    onSave: PropTypes.func.isRequired,
    onClose: PropTypes.func.isRequired,
    onHomeLocationSelect: PropTypes.func.isRequired,
    onHomeLocationSelectionFinish: PropTypes.func.isRequired,
    nlcOpacity: PropTypes.number.isRequired,
    touristOverlayOpacity: PropTypes.number.isRequired,
    cycloOverlayOpacity: PropTypes.number.isRequired,
    zoom: PropTypes.number,
    expertMode: PropTypes.bool.isRequired,
    trackViewerEleSmoothingFactor: PropTypes.number.isRequired,
    selectingHomeLocation: PropTypes.bool,
    user: PropTypes.shape({
      name: PropTypes.string,
      email: PropTypes.string,
    }),
  };

  constructor(props) {
    super(props);
    this.state = {
      homeLocationCssClasses: '',
      tileFormat: props.tileFormat,
      homeLocation: props.homeLocation,
      nlcOpacity: props.nlcOpacity,
      touristOverlayOpacity: props.touristOverlayOpacity,
      cycloOverlayOpacity: props.cycloOverlayOpacity,
      expertMode: props.expertMode,
      trackViewerEleSmoothingFactor: props.trackViewerEleSmoothingFactor,
      name: props.user && props.user.name || '',
      email: props.user && props.user.email || '',
    };
  }

  componentWillMount() {
    mapEventEmitter.on('mapClick', this.onHomeLocationSelected);
  }

  componentWillUnmount() {
    mapEventEmitter.removeListener('mapClick', this.onHomeLocationSelected);
  }

  onHomeLocationSelected = (lat, lon) => {
    this.setState({ homeLocation: { lat, lon }, homeLocationCssClasses: 'animated flash' }); // via animate.css
    this.props.onHomeLocationSelectionFinish();
  }

  handleSave = () => {
    this.props.onSave(
      this.state.tileFormat,
      this.state.homeLocation,
      this.state.nlcOpacity,
      this.state.touristOverlayOpacity,
      this.state.cycloOverlayOpacity,
      this.state.expertMode,
      this.state.trackViewerEleSmoothingFactor,
      this.props.user ? { name: this.state.name, email: this.state.email } : null,
    );
  }

  handleNameChange = (e) => {
    this.setState({ name: e.target.value });
  }

  handleEmailChange = (e) => {
    this.setState({ email: e.target.value });
  }

  render() {
    const { onClose, onHomeLocationSelect, selectingHomeLocation, zoom } = this.props;
    const { homeLocation, homeLocationCssClasses, tileFormat, nlcOpacity, expertMode,
      touristOverlayOpacity, cycloOverlayOpacity, trackViewerEleSmoothingFactor, name, email } = this.state;
    const nlcOverlayIsNotVisible = zoom < 14;

    const userMadeChanges = ['tileFormat', 'homeLocation', 'nlcOpacity', 'touristOverlayOpacity', 'cycloOverlayOpacity', 'expertMode', 'trackViewerEleSmoothingFactor']
      .some(prop => this.state[prop] !== this.props[prop])
      || this.props.user && (name !== (this.props.user.name || '') || email !== (this.props.user.email || ''));

    // TODO name, email

    const homeLocationInfo = homeLocation
      ? `${formatGpsCoord(homeLocation.lat, 'SN')} ${formatGpsCoord(homeLocation.lon, 'WE')}`
      : 'neurčená';

    return (
      <Modal show={!selectingHomeLocation} onHide={onClose}>
        <Modal.Header closeButton>
          <Modal.Title>Nastavenia</Modal.Title>
        </Modal.Header>
        <Modal.Body>
          <Tabs id="setting-tabs">
            <Tab title="Mapa" eventKey={2}>

Formát dlaždíc:

                <ButtonGroup>
                  <Button
                    active={tileFormat === 'png'}
                    onClick={() => this.setState({ tileFormat: 'png' })}
                  >
                    PNG
                  </Button>
                  <Button
                    active={tileFormat === 'jpeg'}
                    onClick={() => this.setState({ tileFormat: 'jpeg' })}
                  >
                    JPG
                  </Button>
                </ButtonGroup>

              <Alert>
                Mapové dlaždice vyzerajú lepšie v PNG formáte, ale sú asi 4x väčšie než JPG dlaždice.
                Pri pomalom internete preto odporúčame zvoliť JPG.
              </Alert>

                {'Domovská poloha: '}
                {homeLocationInfo}

              <Button onClick={() => onHomeLocationSelect()}>
                <FontAwesomeIcon icon="crosshairs" /> Vybrať na mape
              </Button>

Viditeľnosť vrstvy Lesné cesty NLC: {nlcOpacity.toFixed(1) * 100}%

              <Slider
                value={nlcOpacity}
                min={0.1}
                max={1.0}
                step={0.1}
                tooltip={false}
                onChange={newOpacity => this.setState({ nlcOpacity: newOpacity })}
              />
              {nlcOverlayIsNotVisible &&
                <Alert>
                  NLC vrstva sa zobrazuje až pri detailnejšom priblížení (od zoom úrovne 14).
                </Alert>
              }
            </Tab>
            <Tab title="Účet" eventKey={1}>
              {this.props.user ? [
                <FormGroup key="PIj9rh3OVZ">
                  <ControlLabel>Meno</ControlLabel>
                  <FormControl value={name} onChange={this.handleNameChange} />
                </FormGroup>,
                <FormGroup key="4uO0MtU74k">
                  <ControlLabel>E-Mail</ControlLabel>
                  <FormControl type="email" value={email} onChange={this.handleEmailChange} />
                </FormGroup>,
              ] : (
                <Alert>
                    Dostupné iba pre prihásených používateľov.
                </Alert>
              )}
            </Tab>
            <Tab title="Expert" eventKey={3}>

Expertný mód:

                <ButtonGroup>
                  <Button
                    active={!expertMode}
                    onClick={() => this.setState({ expertMode: false })}
                  >
                    Vypnutý
                  </Button>
                  <Button
                    active={expertMode}
                    onClick={() => this.setState({ expertMode: true })}
                  >
                    Zapnutý
                  </Button>
                </ButtonGroup>

              {!expertMode &&
                <Alert>
                  V expertnom móde sú dostupné nástroje pre pokročilých používateľov.
                </Alert>
              }
              {expertMode && [

,

                    {'Viditeľnosť vrstvy Turistické trasy: '}
                    {touristOverlayOpacity.toFixed(1) * 100}%

                  <Slider
                    value={touristOverlayOpacity}
                    min={0.1}
                    max={1.0}
                    step={0.1}
                    tooltip={false}
                    onChange={newOpacity => this.setState({ touristOverlayOpacity: newOpacity })}
                  />

,

                    {'Viditeľnosť vrstvy Cyklotrasy: '}
                    {cycloOverlayOpacity.toFixed(1) * 100}%

                  <Slider
                    value={cycloOverlayOpacity}
                    min={0.1}
                    max={1.0}
                    step={0.1}
                    tooltip={false}
                    onChange={newOpacity => this.setState({ cycloOverlayOpacity: newOpacity })}
                  />

,

Úroveň vyhladzovania pri výpočte celkovej nastúpanej/naklesanej nadmorskej výšky v prehliadači trás: {trackViewerEleSmoothingFactor}

                  <Slider
                    value={trackViewerEleSmoothingFactor}
                    min={1}
                    max={10}
                    step={1}
                    tooltip={false}
                    onChange={newValue => this.setState({ trackViewerEleSmoothingFactor: newValue })}
                  />

,
                <Alert key="QgbhwWNfG6">
                  Pri hodnote 1 sa berú do úvahy všetky nadmorské výšky samostatne. Vyššie hodnoty zodpovedajú šírke plávajúceho okna ktorým sa vyhladzujú nadmorské výšky.
                </Alert>,
              ]}
            </Tab>
          </Tabs>
        </Modal.Body>
        <Modal.Footer>
          <Button bsStyle="info" onClick={this.handleSave} disabled={!userMadeChanges}><Glyphicon glyph="floppy-disk" /> Uložiť</Button>
          <Button onClick={onClose}><Glyphicon glyph="remove" /> Zrušiť</Button>
        </Modal.Footer>
      </Modal>
    );
  }
}

export default connect(
  state => ({
    tileFormat: state.map.tileFormat,
    homeLocation: state.main.homeLocation,
    zoom: state.map.zoom,
    nlcOpacity: state.map.overlayOpacity.N,
    touristOverlayOpacity: state.map.overlayOpacity.t,
    cycloOverlayOpacity: state.map.overlayOpacity.c,
    expertMode: state.main.expertMode,
    trackViewerEleSmoothingFactor: state.trackViewer.eleSmoothingFactor,
    selectingHomeLocation: state.main.selectingHomeLocation,
    user: state.auth.user,
  }),
  dispatch => ({
    onSave(tileFormat, homeLocation, nlcOpacity, touristOverlayOpacity, cycloOverlayOpacity, expertMode, trackViewerEleSmoothingFactor, user) {
      // TODO use this
      dispatch({
        type: 'SAVE_SETTINGS',
        payload: {
          tileFormat, homeLocation, nlcOpacity, touristOverlayOpacity, cycloOverlayOpacity, expertMode, trackViewerEleSmoothingFactor, user,
        },
      });

      // TODO get rid of all this
      dispatch(mapSetTileFormat(tileFormat));
      dispatch(setHomeLocation(homeLocation));
      dispatch(mapSetOverlayOpacity('N', nlcOpacity));
      dispatch(mapSetOverlayOpacity('t', touristOverlayOpacity));
      dispatch(mapSetOverlayOpacity('c', cycloOverlayOpacity));
      dispatch(setActiveModal(null));
      dispatch(setExpertMode(expertMode));
      dispatch(trackViewerSetEleSmoothingFactor(trackViewerEleSmoothingFactor));
      dispatch(toastsAdd({
        collapseKey: 'settings.saved',
        message: 'Zmeny boli uložené.',
        style: 'info',
        timeout: 5000,
      }));
    },
    onClose() {
      dispatch(setActiveModal(null));
    },
    onHomeLocationSelect() {
      dispatch(setSelectingHomeLocation(true));
    },
    onHomeLocationSelectionFinish() {
      dispatch(setSelectingHomeLocation(false));
    },
  }),
)(Settings);
<|endoftext|>


<commit_before>'use strict';

const fs = require('fs');
const vow = require('vow');
const async = require('async');
const mongoose = require('mongoose');

const ModuleBase = require('../core').ModuleBase;
const ServantMessage = require('../message').ServantMessage;

const logger = require('../core').logger;
const db = require('../core').db;

const MODULE_NAME = 'haproxy';
const MODULE_VERSION = '1.0';

const GLOBAL_CONFIG_TYPE = 0;
const DEFAULT_CONFIG_TYPE = 1;
const LISTEN_CONFIG_TYPE = 2;
const FRONTEND_CONFIG_TYPE = 3;
const BACKEND_CONFIG_TYPE = 4;

const KIND_LIST = [
    GLOBAL_CONFIG_TYPE,
    DEFAULT_CONFIG_TYPE,
    LISTEN_CONFIG_TYPE,
    FRONTEND_CONFIG_TYPE,
    BACKEND_CONFIG_TYPE
];

class HAProxyModule extends ModuleBase {

    /**
     *
     * @param {Object} serverDB
     * @param {Object} moduleDB
     * @param {ServantServer} serverInstance
     * @param {Object} server
     * @param {Object} options
     */
    constructor(serverDB, moduleDB, serverInstance, server, options) {
        super(serverDB, moduleDB, server);

        this._serverInstance = serverInstance;
        this._cache = new Map();
        this._options = options;

        this.on('task.create-config', this._onCreateConfigTask.bind(this));
        this.on('task.update-config', this._onUpdateConfigTask.bind(this));
        this.on('task.remove-config', this._onRemoveConfigTask.bind(this));
    }

    /**
     * @return {string}
     */
    static get CreateEvent() {
        return 'Create';
    }

    /**
     * @return {string}
     */
    static get UpdateEvent() {
        return 'Update';
    }

    /**
     * @return {string}
     */
    static get RemoveEvent() {
        return 'Remove';
    }

    get name() {
        return MODULE_NAME;
    }

    get version() {
        return MODULE_VERSION;
    }

    /**
     *
     * @param {ServantMessage} message
     * @param {ServantClient} agent
     * @param {Function} next
     * @public
     */
    handle(message, agent, next) {
        if (message.version != this.version) {
            logger.warn('Receive message with incorrect version number');
            return;
        }

        if (message.event === HAProxyModule.CreateEvent) {
            this._onCommonEventComplete(message, agent);
        } else if (message.event === HAProxyModule.UpdateEvent) {
            this._onCommonEventComplete(message, agent);
        } else if (message.event === HAProxyModule.RemoveEvent) {
            this._onCommonEventComplete(message, agent);
        } else {
            logger.warn(`[${this.name}] Unsupported event "${message.event}". Worker: ${agent.ip}`);
        }
    }

    hasBind(block) {
        return block.kind === LISTEN_CONFIG_TYPE || block.kind === FRONTEND_CONFIG_TYPE;
    }

    /**
     *
     * @param {Object} task
     * @param {Object} params
     * @param {TaskModel} originalTask
     * @private
     */
    _onCreateConfigTask(task, params, originalTask) {
        async.waterfall([
            (cb) => {
                if (this.checkArrayParam(params, 'container')) {
                    cb(new Error('Missing "container" parameter'));
                } else {
                    cb();
                }
            },
            (cb) => {
                this.moduleDB.HAProxyConfigModel.findOne({target_id: task.target_id}, (err, config) => {
                    if (err) {
                        cb(err);
                    } else if (config) {
                        cb(new Error(`HAProxy config for "${task.target_id}" already exist`));
                    } else {
                        cb();
                    }
                });
            },
            (cb) => {
                const model = new this.moduleDB.HAProxyConfigModel({
                    target_id: task.target_id,
                    container: []
                });

                try {
                    let index = 0;
                    let isContainsServer = false;

                    async.whilst(
                        () => index < params.container.length,
                        (next) => {
                            try {
                                const config = params.container[index];

                                if (this.checkStringParam(config, 'name')) {
                                    return next(new Error('Missing "name" parameter'));
                                } else if (this.checkStringParam(config, 'content')) {
                                    return next(new Error('Missing "content" parameter'));
                                } else if (this.checkNumberParam(config, 'kind')) {
                                    return next(new Error('Missing "kind" parameter'));
                                } else if (KIND_LIST.indexOf(config.kind) == -1) {
                                    return next(new Error(`Unsupported config kind for ${config.name}`));
                                }

                                if (this.hasBind(config)) {
                                    isContainsServer = true;
                                }

                                const containerItem = {
                                    kind: config.kind,
                                    name: config.name,
                                    content: config.content,
                                    status: 0,
                                    meta: config.meta || []
                                };

                                model.container.push(containerItem);

                                index++;
                                next();
                            } catch (e) {
                                next(e);
                            }
                        }
                        ,
                        (err) => {
                            if (err) {
                                cb(err);
                            } else {
                                if (isContainsServer) {
                                    this._cache.set(task._id.toString(), {model: model, agentsCount: task.agents.length, task: originalTask});

                                    const message = this.createMessage(HAProxyModule.CreateEvent, null, {
                                        taskKey: task._id.toString(),
                                        config: model.container
                                            .map((item) =>
                                                (item.kind !== GLOBAL_CONFIG_TYPE && item.kind !== DEFAULT_CONFIG_TYPE ? '#' + item.meta.map((item) => `${item.token_name}: ${item.value}`).join(', ') + '\n' : '') + item.content)
                                            .join('\n\n')
                                    });

                                    cb(null, message);
                                } else {
                                    model.save((err) => {
                                        cb(err, null);
                                    });
                                }
                            }
                        }
                    )
                } catch (e) {
                    cb(e);
                }
            }
        ], (err, message) => {
            if (err) {
                logger.error(err.message);
                logger.verbose(err.stack);

                originalTask.internal_error = err.message;
                originalTask.status = this.statuses.error;
                originalTask.save((err) => {
                    if (err) {
                        logger.error(err.message);
                        logger.verbose(err.stack);
                    }
                });
            } else {
                if (message) {
                    try {
                        task.agents.forEach((agent) => {
                            agent.sendMessage(message);
                        });
                    } catch (e) {
                        logger.error(e.message);
                        logger.verbose(e.stack);
                    }
                } else {
                    originalTask.status = this.statuses.success;
                    originalTask.save((err) => {
                        if (err) {
                            logger.error(err.message);
                            logger.verbose(err.stack);
                        }
                    });
                }
            }
        });
    }

    /**
     *
     * @param {Object} task
     * @param {Object} params
     * @param {TaskModel} originalTask
     * @private
     */
    _onUpdateConfigTask(task, params, originalTask) {
        async.waterfall([
            (cb) => {
                if (this.checkStringParam(params, 'id')) {
                    cb(new Error('Missing "id" parameter'));
                } else if (this.checkArrayParam(params, 'container')) {
                    cb(new Error('Missing "container" parameter'));
                } else {
                    try {
                        params.id = mongoose.Types.ObjectId(params.id);

                        cb();
                    } catch (e) {
                        cb(e);
                    }
                }
            },
            (cb) => {
                this.moduleDB.HAProxyConfigModel.findOne({_id: params.id}, (err, config) => {
                    if (err) {
                        cb(err);
                    } else if (!config) {
                        cb(new Error(`HAProxy config "${params.id.toString()}" not found`));
                    } else {
                        cb(null, config);
                    }
                });
            },
            (config, cb) => {
                try {
                    let index = 0;
                    let isContainsServer = false;

                    config.container = [];

                    async.whilst(
                        () => index < params.container.length,
                        (next) => {
                            try {
                                const item = params.container[index];

                                if (this.checkStringParam(item, 'name')) {
                                    return next(new Error('Missing "name" parameter'));
                                } else if (this.checkStringParam(item, 'content')) {
                                    return next(new Error('Missing "content" parameter'));
                                } else if (this.checkNumberParam(item, 'kind')) {
                                    return next(new Error('Missing "kind" parameter'));
                                } else if (this.checkNumberParam(item, 'status')) {
                                    return next(new Error('Missing "status" parameter'));
                                } else if (KIND_LIST.indexOf(item.kind) == -1) {
                                    return next(new Error(`Unsupported config kind for ${item.name}`));
                                }

                                if (item.status == this.statuses.success && this.hasBind(item)) {
                                    isContainsServer = true;
                                }

                                const containerItem = {
                                    kind: item.kind,
                                    name: item.name,
                                    content: item.content,
                                    status: item.status,
                                    meta: item.meta || []
                                };

                                config.container.push(containerItem);

                                index++;
                                next();
                            } catch (e) {
                                next(e);
                            }
                        }
                        ,
                        (err) => {
                            if (err) {
                                cb(err);
                            } else {
                                this._cache.set(task._id.toString(), {model: config, agentsCount: task.agents.length, task: originalTask});

                                const msgData = {taskKey: task._id.toString()};

                                if (isContainsServer) {
                                    msgData.config = config.container
                                        .filter((item) => item.status == 0)
                                        .map(
                                            (item) => (item.kind !== GLOBAL_CONFIG_TYPE && item.kind !== DEFAULT_CONFIG_TYPE ? '#' + item.meta.map((item) => `${item.token_name}: ${item.value}`).join(', ') + '\n' : '') + item.content)
                                        .join('\n\n')
                                } else {
                                    msgData.dispose = true;
                                }

                                const message = this.createMessage(HAProxyModule.UpdateEvent, null, msgData);

                                cb(null, message);
                            }
                        }
                    )
                } catch (e) {
                    cb(e);
                }
            }
        ], (err, message) => {
            if (err) {
                logger.error(err.message);
                logger.verbose(err.stack);

                originalTask.internal_error = err.message;
                originalTask.status = this.statuses.error;
                originalTask.save((err) => {
                    if (err) {
                        logger.error(err.message);
                        logger.verbose(err.stack);
                    }
                });
            } else {
                try {
                    task.agents.forEach((agent) => {
                        agent.sendMessage(message);
                    });
                } catch (e) {
                    logger.error(e.message);
                    logger.verbose(e.stack);
                }
            }
        });
    }

    /**
     *
     * @param {Object} task
     * @param {Object} params
     * @param {TaskModel} originalTask
     * @private
     */
    _onRemoveConfigTask(task, params, originalTask) {
        async.waterfall([
            (cb) => {
                if (this.checkStringParam(params, 'id')) {
                    cb(new Error('Missing "id" parameter'));
                } else {
                    try {
                        if (params.hasOwnProperty('id')) {
                            params.id = mongoose.Types.ObjectId(params.id);
                        }

                        cb();
                    } catch (e) {
                        cb(e);
                    }
                }
            },
            (cb) => {
                this.moduleDB.HAProxyConfigModel.findOne({_id: params.id}, (err, config) => {
                    if (err) {
                        cb(err);
                    } else if (!config) {
                        cb(new Error(`HAProxy config "${params.name || params.id.toString()}" not found`));
                    } else {
                        cb(null, config);
                    }
                });
            },
            (config, cb) => {
                this._cache.set(task._id.toString(), {model: config, agentsCount: task.agents.length, task: originalTask});

                const message = this.createMessage(HAProxyModule.RemoveEvent, null, {
                    taskKey: task._id.toString(),
                    dispose: true
                });

                cb(null, message);
            }
        ], (err, message) => {
            if (err) {
                logger.error(err.message);
                logger.verbose(err.stack);

                originalTask.internal_error = err.message;
                originalTask.status = this.statuses.error;
                originalTask.save((err) => {
                    if (err) {
                        logger.error(err.message);
                        logger.verbose(err.stack);
                    }
                });
            } else {
                try {
                    task.agents.forEach((agent) => {
                        agent.sendMessage(message);
                    });
                } catch (e) {
                    logger.error(e.message);
                    logger.verbose(e.stack);
                }
            }
        });
    }

    /**
     *
     * @param {ServantMessage} message
     * @param {ServantClient} agent
     * @private
     */
    _onCommonEventComplete(message, agent) {
        try {
            if (this._cache.has(message.data.taskKey)) {
                const cacheItem = this._cache.get(message.data.taskKey);
                const task = cacheItem.task;

                if (!task.report) {
                    task.report = [];
                }

                if (!task.error) {
                    task.error = [];
                }

                message.data.report.splice(0, 0, 'Worker: ' + agent.hostname);

                task.report.push({
                    worker_id: agent.worker._id,
                    stack: message.data.report.join('\n')
                });

                if (message.error) {
                    task.status = this.statuses.warning;
                    task.error.push({
                        worker_id: agent.worker._id,
                        stack: 'Worker: ' + agent.hostname + '\n' + message.error
                    });
                }

                if (cacheItem.agentsCount == task.report.length && task.error.length) {
                    task.save((err) => {
                        if (err) {
                            logger.error(err.message);
                            logger.verbose(err.stack);
                        }
                    });

                    return;
                }

                if (cacheItem.agentsCount == task.report.length) {
                    task.status = this.statuses.success;
                    let action = 'save';

                    if (message.event == HAProxyModule.RemoveEvent) {
                        action = 'remove';
                    }

                    cacheItem.model[action]((err) => {
                        if (err) {
                            cb(err);
                        } else {
                            task.save((err) => {
                                if (err) {
                                    logger.error(err.message);
                                    logger.verbose(err.stack);
                                }
                            });
                        }
                    });
                }

            } else {
                logger.error(`Model for task "${message.data.taskKey}" not found in cache`);
            }
        } catch (e) {
            logger.error(e.message);
            logger.verbose(e.stack);
        }
    }
}

exports.MODULE_NAME = MODULE_NAME;
exports.MODULE_VERSION = MODULE_VERSION;
exports.HAProxyModule = HAProxyModule;
<commit_msg>fix comment insert<commit_after>'use strict';

const fs = require('fs');
const vow = require('vow');
const async = require('async');
const mongoose = require('mongoose');

const ModuleBase = require('../core').ModuleBase;
const ServantMessage = require('../message').ServantMessage;

const logger = require('../core').logger;
const db = require('../core').db;

const MODULE_NAME = 'haproxy';
const MODULE_VERSION = '1.0';

const GLOBAL_CONFIG_TYPE = 0;
const DEFAULT_CONFIG_TYPE = 1;
const LISTEN_CONFIG_TYPE = 2;
const FRONTEND_CONFIG_TYPE = 3;
const BACKEND_CONFIG_TYPE = 4;

const KIND_LIST = [
    GLOBAL_CONFIG_TYPE,
    DEFAULT_CONFIG_TYPE,
    LISTEN_CONFIG_TYPE,
    FRONTEND_CONFIG_TYPE,
    BACKEND_CONFIG_TYPE
];

class HAProxyModule extends ModuleBase {

    /**
     *
     * @param {Object} serverDB
     * @param {Object} moduleDB
     * @param {ServantServer} serverInstance
     * @param {Object} server
     * @param {Object} options
     */
    constructor(serverDB, moduleDB, serverInstance, server, options) {
        super(serverDB, moduleDB, server);

        this._serverInstance = serverInstance;
        this._cache = new Map();
        this._options = options;

        this.on('task.create-config', this._onCreateConfigTask.bind(this));
        this.on('task.update-config', this._onUpdateConfigTask.bind(this));
        this.on('task.remove-config', this._onRemoveConfigTask.bind(this));
    }

    /**
     * @return {string}
     */
    static get CreateEvent() {
        return 'Create';
    }

    /**
     * @return {string}
     */
    static get UpdateEvent() {
        return 'Update';
    }

    /**
     * @return {string}
     */
    static get RemoveEvent() {
        return 'Remove';
    }

    get name() {
        return MODULE_NAME;
    }

    get version() {
        return MODULE_VERSION;
    }

    /**
     *
     * @param {ServantMessage} message
     * @param {ServantClient} agent
     * @param {Function} next
     * @public
     */
    handle(message, agent, next) {
        if (message.version != this.version) {
            logger.warn('Receive message with incorrect version number');
            return;
        }

        if (message.event === HAProxyModule.CreateEvent) {
            this._onCommonEventComplete(message, agent);
        } else if (message.event === HAProxyModule.UpdateEvent) {
            this._onCommonEventComplete(message, agent);
        } else if (message.event === HAProxyModule.RemoveEvent) {
            this._onCommonEventComplete(message, agent);
        } else {
            logger.warn(`[${this.name}] Unsupported event "${message.event}". Worker: ${agent.ip}`);
        }
    }

    hasBind(block) {
        return block.kind === LISTEN_CONFIG_TYPE || block.kind === FRONTEND_CONFIG_TYPE;
    }

    /**
     *
     * @param {Object} task
     * @param {Object} params
     * @param {TaskModel} originalTask
     * @private
     */
    _onCreateConfigTask(task, params, originalTask) {
        async.waterfall([
            (cb) => {
                if (this.checkArrayParam(params, 'container')) {
                    cb(new Error('Missing "container" parameter'));
                } else {
                    cb();
                }
            },
            (cb) => {
                this.moduleDB.HAProxyConfigModel.findOne({target_id: task.target_id}, (err, config) => {
                    if (err) {
                        cb(err);
                    } else if (config) {
                        cb(new Error(`HAProxy config for "${task.target_id}" already exist`));
                    } else {
                        cb();
                    }
                });
            },
            (cb) => {
                const model = new this.moduleDB.HAProxyConfigModel({
                    target_id: task.target_id,
                    container: []
                });

                try {
                    let index = 0;
                    let isContainsServer = false;

                    async.whilst(
                        () => index < params.container.length,
                        (next) => {
                            try {
                                const config = params.container[index];

                                if (this.checkStringParam(config, 'name')) {
                                    return next(new Error('Missing "name" parameter'));
                                } else if (this.checkStringParam(config, 'content')) {
                                    return next(new Error('Missing "content" parameter'));
                                } else if (this.checkNumberParam(config, 'kind')) {
                                    return next(new Error('Missing "kind" parameter'));
                                } else if (KIND_LIST.indexOf(config.kind) == -1) {
                                    return next(new Error(`Unsupported config kind for ${config.name}`));
                                }

                                if (this.hasBind(config)) {
                                    isContainsServer = true;
                                }

                                const containerItem = {
                                    kind: config.kind,
                                    name: config.name,
                                    content: config.content,
                                    status: 0,
                                    meta: config.meta || []
                                };

                                model.container.push(containerItem);

                                index++;
                                next();
                            } catch (e) {
                                next(e);
                            }
                        }
                        ,
                        (err) => {
                            if (err) {
                                cb(err);
                            } else {
                                if (isContainsServer) {
                                    this._cache.set(task._id.toString(), {model: model, agentsCount: task.agents.length, task: originalTask});

                                    const message = this.createMessage(HAProxyModule.CreateEvent, null, {
                                        taskKey: task._id.toString(),
                                        config: model.container
                                            .map((item) =>
                                                (item.kind !== GLOBAL_CONFIG_TYPE && item.kind !== DEFAULT_CONFIG_TYPE ? '#' + item.meta.map((item) => `${item.token_name}: ${item.value || ''}`).join(', ') + '\n' : '') + item.content)
                                            .join('\n\n')
                                    });

                                    cb(null, message);
                                } else {
                                    model.save((err) => {
                                        cb(err, null);
                                    });
                                }
                            }
                        }
                    )
                } catch (e) {
                    cb(e);
                }
            }
        ], (err, message) => {
            if (err) {
                logger.error(err.message);
                logger.verbose(err.stack);

                originalTask.internal_error = err.message;
                originalTask.status = this.statuses.error;
                originalTask.save((err) => {
                    if (err) {
                        logger.error(err.message);
                        logger.verbose(err.stack);
                    }
                });
            } else {
                if (message) {
                    try {
                        task.agents.forEach((agent) => {
                            agent.sendMessage(message);
                        });
                    } catch (e) {
                        logger.error(e.message);
                        logger.verbose(e.stack);
                    }
                } else {
                    originalTask.status = this.statuses.success;
                    originalTask.save((err) => {
                        if (err) {
                            logger.error(err.message);
                            logger.verbose(err.stack);
                        }
                    });
                }
            }
        });
    }

    /**
     *
     * @param {Object} task
     * @param {Object} params
     * @param {TaskModel} originalTask
     * @private
     */
    _onUpdateConfigTask(task, params, originalTask) {
        async.waterfall([
            (cb) => {
                if (this.checkStringParam(params, 'id')) {
                    cb(new Error('Missing "id" parameter'));
                } else if (this.checkArrayParam(params, 'container')) {
                    cb(new Error('Missing "container" parameter'));
                } else {
                    try {
                        params.id = mongoose.Types.ObjectId(params.id);

                        cb();
                    } catch (e) {
                        cb(e);
                    }
                }
            },
            (cb) => {
                this.moduleDB.HAProxyConfigModel.findOne({_id: params.id}, (err, config) => {
                    if (err) {
                        cb(err);
                    } else if (!config) {
                        cb(new Error(`HAProxy config "${params.id.toString()}" not found`));
                    } else {
                        cb(null, config);
                    }
                });
            },
            (config, cb) => {
                try {
                    let index = 0;
                    let isContainsServer = false;

                    config.container = [];

                    async.whilst(
                        () => index < params.container.length,
                        (next) => {
                            try {
                                const item = params.container[index];

                                if (this.checkStringParam(item, 'name')) {
                                    return next(new Error('Missing "name" parameter'));
                                } else if (this.checkStringParam(item, 'content')) {
                                    return next(new Error('Missing "content" parameter'));
                                } else if (this.checkNumberParam(item, 'kind')) {
                                    return next(new Error('Missing "kind" parameter'));
                                } else if (this.checkNumberParam(item, 'status')) {
                                    return next(new Error('Missing "status" parameter'));
                                } else if (KIND_LIST.indexOf(item.kind) == -1) {
                                    return next(new Error(`Unsupported config kind for ${item.name}`));
                                }

                                if (item.status == this.statuses.success && this.hasBind(item)) {
                                    isContainsServer = true;
                                }

                                const containerItem = {
                                    kind: item.kind,
                                    name: item.name,
                                    content: item.content,
                                    status: item.status,
                                    meta: item.meta || []
                                };

                                config.container.push(containerItem);

                                index++;
                                next();
                            } catch (e) {
                                next(e);
                            }
                        }
                        ,
                        (err) => {
                            if (err) {
                                cb(err);
                            } else {
                                this._cache.set(task._id.toString(), {model: config, agentsCount: task.agents.length, task: originalTask});

                                const msgData = {taskKey: task._id.toString()};

                                if (isContainsServer) {
                                    msgData.config = config.container
                                        .filter((item) => item.status == 0)
                                        .map(
                                            (item) => (item.kind !== GLOBAL_CONFIG_TYPE && item.kind !== DEFAULT_CONFIG_TYPE ? '#' + item.meta.map((item) => `${item.token_name}: ${item.value || ''}`).join(', ') + '\n' : '') + item.content)
                                        .join('\n\n')
                                } else {
                                    msgData.dispose = true;
                                }

                                const message = this.createMessage(HAProxyModule.UpdateEvent, null, msgData);

                                cb(null, message);
                            }
                        }
                    )
                } catch (e) {
                    cb(e);
                }
            }
        ], (err, message) => {
            if (err) {
                logger.error(err.message);
                logger.verbose(err.stack);

                originalTask.internal_error = err.message;
                originalTask.status = this.statuses.error;
                originalTask.save((err) => {
                    if (err) {
                        logger.error(err.message);
                        logger.verbose(err.stack);
                    }
                });
            } else {
                try {
                    task.agents.forEach((agent) => {
                        agent.sendMessage(message);
                    });
                } catch (e) {
                    logger.error(e.message);
                    logger.verbose(e.stack);
                }
            }
        });
    }

    /**
     *
     * @param {Object} task
     * @param {Object} params
     * @param {TaskModel} originalTask
     * @private
     */
    _onRemoveConfigTask(task, params, originalTask) {
        async.waterfall([
            (cb) => {
                if (this.checkStringParam(params, 'id')) {
                    cb(new Error('Missing "id" parameter'));
                } else {
                    try {
                        if (params.hasOwnProperty('id')) {
                            params.id = mongoose.Types.ObjectId(params.id);
                        }

                        cb();
                    } catch (e) {
                        cb(e);
                    }
                }
            },
            (cb) => {
                this.moduleDB.HAProxyConfigModel.findOne({_id: params.id}, (err, config) => {
                    if (err) {
                        cb(err);
                    } else if (!config) {
                        cb(new Error(`HAProxy config "${params.name || params.id.toString()}" not found`));
                    } else {
                        cb(null, config);
                    }
                });
            },
            (config, cb) => {
                this._cache.set(task._id.toString(), {model: config, agentsCount: task.agents.length, task: originalTask});

                const message = this.createMessage(HAProxyModule.RemoveEvent, null, {
                    taskKey: task._id.toString(),
                    dispose: true
                });

                cb(null, message);
            }
        ], (err, message) => {
            if (err) {
                logger.error(err.message);
                logger.verbose(err.stack);

                originalTask.internal_error = err.message;
                originalTask.status = this.statuses.error;
                originalTask.save((err) => {
                    if (err) {
                        logger.error(err.message);
                        logger.verbose(err.stack);
                    }
                });
            } else {
                try {
                    task.agents.forEach((agent) => {
                        agent.sendMessage(message);
                    });
                } catch (e) {
                    logger.error(e.message);
                    logger.verbose(e.stack);
                }
            }
        });
    }

    /**
     *
     * @param {ServantMessage} message
     * @param {ServantClient} agent
     * @private
     */
    _onCommonEventComplete(message, agent) {
        try {
            if (this._cache.has(message.data.taskKey)) {
                const cacheItem = this._cache.get(message.data.taskKey);
                const task = cacheItem.task;

                if (!task.report) {
                    task.report = [];
                }

                if (!task.error) {
                    task.error = [];
                }

                message.data.report.splice(0, 0, 'Worker: ' + agent.hostname);

                task.report.push({
                    worker_id: agent.worker._id,
                    stack: message.data.report.join('\n')
                });

                if (message.error) {
                    task.status = this.statuses.warning;
                    task.error.push({
                        worker_id: agent.worker._id,
                        stack: 'Worker: ' + agent.hostname + '\n' + message.error
                    });
                }

                if (cacheItem.agentsCount == task.report.length && task.error.length) {
                    task.save((err) => {
                        if (err) {
                            logger.error(err.message);
                            logger.verbose(err.stack);
                        }
                    });

                    return;
                }

                if (cacheItem.agentsCount == task.report.length) {
                    task.status = this.statuses.success;
                    let action = 'save';

                    if (message.event == HAProxyModule.RemoveEvent) {
                        action = 'remove';
                    }

                    cacheItem.model[action]((err) => {
                        if (err) {
                            cb(err);
                        } else {
                            task.save((err) => {
                                if (err) {
                                    logger.error(err.message);
                                    logger.verbose(err.stack);
                                }
                            });
                        }
                    });
                }

            } else {
                logger.error(`Model for task "${message.data.taskKey}" not found in cache`);
            }
        } catch (e) {
            logger.error(e.message);
            logger.verbose(e.stack);
        }
    }
}

exports.MODULE_NAME = MODULE_NAME;
exports.MODULE_VERSION = MODULE_VERSION;
exports.HAProxyModule = HAProxyModule;
<|endoftext|>


<commit_before>/**
 * Bio component that queries for data
 * with Gatsby's StaticQuery component
 *
 * See: https://www.gatsbyjs.org/docs/static-query/
 */

import React from "react";
import Bio from "../components/bio";
import BlogLayout from "./blogLayout";
import SEO from "../components/seo";
import blogPostStyles from "./blogPost.module.scss";
import { Link, graphql } from "gatsby";

import Color from "../utils/colors";

function BlogPost(props) {
  const post = props.data.markdownRemark;
  const siteTitle = props.data.site.siteMetadata.title;
  const { previous, next } = props.pageContext;

  return (
    <BlogLayout location={props.location} title={siteTitle}>
      <SEO
        title={post.frontmatter.title}
        description={post.frontmatter.description || post.excerpt}
      />

            {post.frontmatter.title}

{post.frontmatter.date}

          {post.fields.tags.map(tag => (
            <Link to={`/tags/${tag}`} className={blogPostStyles.tag}>
              {tag}
            </Link>
          ))}

        <ul
          style={{
            display: `flex`,
            flexWrap: `wrap`,
            justifyContent: `space-between`,
            listStyle: `none`,
            padding: 0,
            margin: 0,
            marginBottom: "1em"
          }}
        >

            {previous && (
              <Link to={previous.fields.slug} rel="prev">
                ← {previous.frontmatter.title}
              </Link>
            )}

            {next && (
              <Link to={next.fields.slug} rel="next">
                {next.frontmatter.title} →
              </Link>
            )}

      <Bio />
    </BlogLayout>
  );
}

export default BlogPost;

export const blogPostFragment = graphql`
  fragment BlogPost on MarkdownRemark {
    id
    excerpt(pruneLength: 160)
    html
    fields {
      tags
    }
    frontmatter {
      title
      date(formatString: "M.D.YY")
      color
    }
  }
`;
<commit_msg>:cop:<commit_after>/**
 * Bio component that queries for data
 * with Gatsby's StaticQuery component
 *
 * See: https://www.gatsbyjs.org/docs/static-query/
 */

import React from "react";
import Bio from "../components/bio";
import BlogLayout from "./blogLayout";
import SEO from "../components/seo";
import blogPostStyles from "./blogPost.module.scss";
import { Link, graphql } from "gatsby";

import Color from "../utils/colors";

function BlogPost(props) {
  const post = props.data.markdownRemark;
  const siteTitle = props.data.site.siteMetadata.title;
  const { previous, next } = props.pageContext;

  return (
    <BlogLayout location={props.location} title={siteTitle}>
      <SEO
        title={post.frontmatter.title}
        description={post.frontmatter.description || post.excerpt}
      />

        <section
          className={`${blogPostStyles.titleContainer} ${blogPostStyles.PostTitle}`}
          style={{ color: Color[post.frontmatter.color] }}
        >

            {post.frontmatter.title}

{post.frontmatter.date}

          {post.fields.tags.map(tag => (
            <Link to={`/tags/${tag}`} className={blogPostStyles.tag}>
              {tag}
            </Link>
          ))}

        <ul
          style={{
            display: `flex`,
            flexWrap: `wrap`,
            justifyContent: `space-between`,
            listStyle: `none`,
            padding: 0,
            margin: 0,
            marginBottom: "1em"
          }}
        >

            {previous && (
              <Link to={previous.fields.slug} rel="prev">
                ← {previous.frontmatter.title}
              </Link>
            )}

            {next && (
              <Link to={next.fields.slug} rel="next">
                {next.frontmatter.title} →
              </Link>
            )}

      <Bio />
    </BlogLayout>
  );
}

export default BlogPost;

export const blogPostFragment = graphql`
  fragment BlogPost on MarkdownRemark {
    id
    excerpt(pruneLength: 160)
    html
    fields {
      tags
    }
    frontmatter {
      title
      date(formatString: "M.D.YY")
      color
    }
  }
`;
<|endoftext|>


<commit_before>// @ts-check
const {PLATFORM, getDestDir} = require('./paths');
const reload = require('./reload');
const {createTask} = require('./task');
const {readFile, writeFile} = require('./utils');

const srcDir = 'src';

async function readJSON(path) {
    const file = await readFile(path);
    return JSON.parse(file);
}

async function writeJSON(path, json) {
    const content = JSON.stringify(json, null, 4);
    return await writeFile(path, content);
}

async function patchManifest(platform) {
    const manifest = await readJSON(`${srcDir}/manifest.json`);
    const manifestPatch = platform === PLATFORM.CHROME ? {} : await readJSON(`${srcDir}/manifest-${platform}.json`);
    const pacthed = {...manifest, ...manifestPatch};
    if (platform === PLATFORM.CHROME_MV3) {
        pacthed.browser_action = undefined;
    }
    return pacthed;
}

async function manifests({platforms, debug}) {
    for (const platform of Object.values(PLATFORM).filter((platform) => platforms[platform])) {
        const manifest = await patchManifest(platform);
        const destDir = getDestDir({debug, platform});
        await writeJSON(`${destDir}/manifest.json`, manifest);
    }
}

module.exports = createTask(
    'bundle-manifest',
    manifests,
).addWatcher(
    ['src/manifest*.json'],
    async (changedFiles, _, buildPlatforms) => {
        const chrome = changedFiles.some((file) => file.endsWith('manifest.json'));
        const platforms = {};
        for (const platform of Object.values(PLATFORM)) {
            const changed = chrome || changedFiles.some((file) => file.endsWith(`manifest-${platform}.json`));
            platforms[platform] = changed && buildPlatforms[platform];
        }
        await manifests({platforms, debug: true});
        reload({type: reload.FULL});
    },
);
<commit_msg>Fix typo within patchManifest (#9266)<commit_after>// @ts-check
const {PLATFORM, getDestDir} = require('./paths');
const reload = require('./reload');
const {createTask} = require('./task');
const {readFile, writeFile} = require('./utils');

const srcDir = 'src';

async function readJSON(path) {
    const file = await readFile(path);
    return JSON.parse(file);
}

async function writeJSON(path, json) {
    const content = JSON.stringify(json, null, 4);
    return await writeFile(path, content);
}

async function patchManifest(platform) {
    const manifest = await readJSON(`${srcDir}/manifest.json`);
    const manifestPatch = platform === PLATFORM.CHROME ? {} : await readJSON(`${srcDir}/manifest-${platform}.json`);
    const patched = {...manifest, ...manifestPatch};
    if (platform === PLATFORM.CHROME_MV3) {
        patched.browser_action = undefined;
    }
    return patched;
}

async function manifests({platforms, debug}) {
    for (const platform of Object.values(PLATFORM).filter((platform) => platforms[platform])) {
        const manifest = await patchManifest(platform);
        const destDir = getDestDir({debug, platform});
        await writeJSON(`${destDir}/manifest.json`, manifest);
    }
}

module.exports = createTask(
    'bundle-manifest',
    manifests,
).addWatcher(
    ['src/manifest*.json'],
    async (changedFiles, _, buildPlatforms) => {
        const chrome = changedFiles.some((file) => file.endsWith('manifest.json'));
        const platforms = {};
        for (const platform of Object.values(PLATFORM)) {
            const changed = chrome || changedFiles.some((file) => file.endsWith(`manifest-${platform}.json`));
            platforms[platform] = changed && buildPlatforms[platform];
        }
        await manifests({platforms, debug: true});
        reload({type: reload.FULL});
    },
);
<|endoftext|>


<commit_before>/**
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

const gutil = require('gulp-util');
const path = require('path');
const through = require('through2');
const fs = require('fs');
const mkdirp = require('mkdirp');
const PluginError = gutil.PluginError;
const DocumentParser = require('../lib/DocumentParser');
const ExampleFile = require('../lib/ExampleFile');
const Metadata = require('../lib/Metadata');
const Templates = require('../lib/Templates');
const storyController = fs.readFileSync(__dirname + '/../templates/stories/page-switch.html', 'utf8');
const storyBookend = require('./bookend.json');

const STORY_EMBED_DIR = __dirname + '/../api/dist/';

/**
 * Collects a list of example files, renders them (using templateExample) and
 * creates a list (using templateIndex)
 */
module.exports = function(config, indexPath, updateTimestamp) {
  let sampleTemplates;
  let pageTemplates;

  let latestFile;
  let latestMod;
  let examples;
  let timestamp = new Date().toISOString();

  if (typeof config.templates.root === 'string') {
    pageTemplates = Templates.get(config.templates.root,/* minify */ true);
    sampleTemplates = Templates.get(config.templates.root,/* minify */ false, '<% %>');
  } else {
    throw new PluginError('compile-index-example',
        'Missing template root in template options for compile-example');
  }

  if (typeof config.templates.index != 'string') {
    throw new PluginError('compile-index-example',
        'Missing templateIndex name in template options for compile-example');
  }

  if (typeof config.templates.example != 'string') {
    throw new PluginError('compile-index-example',
        'Missing templateExample name in template options for compile-example');
  }

  if (updateTimestamp == false) {
    timestamp = 0;
  }

  function bufferContents(file, enc, cb) {
    // ignore empty files
    if (file.isNull()) {
      cb();
      return;
    }

    // we don't do streams
    if (file.isStream()) {
      this.emit('error', new PluginError('compile-example',
            'Streaming not supported'));
      cb();
      return;
    }

    // set latest file if not already set,
    // or if the current file was modified more recently.
    if (!latestMod || file.stat && file.stat.mtime > latestMod) {
      latestFile = file;
      latestMod = file.stat && file.stat.mtime;
    }

    // create examples list
    if (!examples) {
      examples = [];
    }

    // parse examples into documents and add metadata
    if (file.isBuffer()) {
      const example = ExampleFile.fromPath(file.path);
      const contents = prerenderTemplates(file.contents.toString(), config);
      latestFile.section = example.section();
      example.document = DocumentParser.parse(contents);
      example.file = file;
      example.contents = contents;
      examples.push(example);
    }

    cb();
  }

  function endStream(cb) {
    // no examples passed in, no file goes out
    if (!latestFile || !examples) {
      cb();
      return;
    }

    const stream = this;
    const sections = createSections(examples);
    compileIndex(stream, sections);
    compileSitemap(stream, sections);
    compileExamples(stream);
    generateBookend(stream, sections);
    cb();
  }

  function compileIndex(stream, sections) {
    sections.forEach(section => {
      const args = require('../data/index.json');

      // clear selection state
      sections.forEach(s => s.selected = false);
      section.selected = true;

      const indexArgs = {
        config: config,
        title: section.title,
        desc: section.desc,
        sections: sections,
        categories: section.categories,
        timestamp: timestamp,
        github: "https://github.com/ampproject/amp-by-example/",
        fileName: '/'
      };
      Object.assign(args, indexArgs);

      Metadata.add(args);
      args.fileName = '';
      const html = pageTemplates.render(config.templates.index, args);
      const indexFile = latestFile.clone({contents: false});
      indexFile.path = path.join(latestFile.base, section.path, "index.html");

      indexFile.contents = new Buffer(html);
      stream.push(indexFile);
    });
  }

  function compileSitemap(stream, sections) {
    const indexFile = latestFile.clone({contents: false});
    indexFile.path = path.join(latestFile.base, "sitemap.json");
    indexFile.contents = new Buffer(JSON.stringify(sections, null, 2));
    stream.push(indexFile);
  }

  function findNextExample(examples, index) {
    const next = examples[index];
    if (!next) {
      return null;
    }
    const metadata = next.document.metadata;
    if (!next.category() || (metadata && metadata.draft)) {
      return findNextExample(examples, index+1);
    }
    return next;
  }

  function compileExamples(stream) {
    examples.forEach(function(example, index) {
      const document = example.document;
      const file = example.file;
      let nextExample = '';
      if (example.category()) {
        nextExample = findNextExample(examples, index + 1);
      }
      const sections = createSections(examples, example);
      const args = require('../data/index.json');
      const sampleArgs = {
        config: config,
        index: index,
        document: document,
        head: document.head,
        title: example.title() + ' - ' + 'AMP by Example',
        desc: document.description(),
        timestamp: timestamp,
        fileName: example.url(),
        host: config.host,
        urlSource: example.urlSource(),
        urlPreview: previewUrl(example),
        github: example.githubUrl(),
        subHeading: example.title(),
        exampleStyles: document.styles,
        component: document.metadata.component,
        bodyTag: document.body,
        elementsAfterBody: document.elementsAfterBody,
        sections: sections,
        headings: document.headings(),
        metadata: document.metadata,
        nextExample: nextExample,
        skipCanonical: document.hasCanonical(),
        includesManifest: document.includesLink('manifest'),
        includesAnalytics: document.importsComponent('amp-analytics'),
        includesLiveList: document.importsComponent('amp-live-list'),
        includesAccordion: document.importsComponent('amp-accordion'),
        includesSelector: document.importsComponent('amp-selector'),
        includesSidebar: document.importsComponent('amp-sidebar'),
        includesServiceWorker: document.importsComponent('amp-install-serviceworker') || document.metadata.skipServiceWorker
      };
      sampleArgs.supportsAmpSelector = !sampleArgs.includesLiveList && !sampleArgs.includesSelector;
      Object.assign(args, sampleArgs);
      Metadata.add(args);

      // compile source file
      const inputFile = example.file;
      inputFile.path = path.join(inputFile.base, example.targetSourcePath());
      inputFile.contents = new Buffer(
        example.contents.replace(/\<\!\-\-\-\{(.|[\n\r])*\}\-\-\-\>/, '').trim());
      inputFile.metadata = document.metadata;
      //gutil.log('Generated ' + inputFile.relative);
      stream.push(inputFile);

      // compile example
      compileTemplate(stream, example, args, {
        template: config.templates.example,
        targetPath: example.targetPath(),
        isEmbed: false,
        postProcessors: [replaceAmpAdRuntime, replaceAmpStoryRuntime]
      });

      // compile embed
      compileTemplate(stream, example, args, {
        template: config.templates.example,
        targetPath: example.targetEmbedPath(),
        postProcessors: [replaceAmpAdRuntime, replaceAmpStoryRuntime],
        isEmbed: true
      });

      // compile example preview
      // the default preview template
      let previewTemplate = config.templates.preview;

      // a4a preview embeds the original sample via iframe
      if (document.isAmpAdSample()) {
        previewTemplate = config.a4a.template;
        // configure a4a preview
        args.width = document.metadata.width || config.a4a.defaultWidth;
        args.height = document.metadata.height || config.a4a.defaultHeight;
        args.layout = document.metadata.layout || config.a4a.defaultLayout;
        args.adContainerHeight = args.height;
        args.force3p = document.metadata.force3p || config.a4a.defaultForce3p;
        args.a4aEmbedUrl = example.urlSource();
      }

      args.title = example.title() + ' (Preview) - ' + 'AMP by Example';
      args.desc = "This is a live preview of the '" + example.title() + "' sample. " + args.desc;
      args.canonical = config.host + example.url() + 'preview/';

      // generate story preview embed
      if (document.isAmpStory) {
        generateStoryPreviewEmbed(stream, example, args, {
          targetPath: example.targetPreviewEmbedPath(),
        })
      } else {
        // generate preview
        compileTemplate(stream, example, args, {
          template: previewTemplate,
          targetPath: example.targetPreviewPath(),
          postProcessors: [replaceAmpAdRuntime, replaceAmpStoryRuntime],
          isEmbed: false
        });
      }

      // generate preview embed
      compileTemplate(stream, example, args, {
        template: previewTemplate,
        targetPath: example.targetPreviewEmbedPath(),
        isEmbed: true
      });
    });
  }

  function prerenderTemplates(string, config) {
    return sampleTemplates.renderString(string, config);
  }

  function createSections(examples, currentExample) {
    const sections = [];
    let currentCategory;
    let currentSection;
    sort(examples)
      .filter(exampleFile => exampleFile.category() && !exampleFile.document.metadata.draft)
      .forEach(function(exampleFile) {
        // add new section
        if (!currentSection || currentSection.path !== exampleFile.section().path) {
          currentSection = exampleFile.section();
          currentSection.categories = [];
          sections.push(currentSection);
          currentSection.selected = false;
        }
        // add example to categories instance
        if (!currentCategory ||
          currentCategory.name !== exampleFile.category().name) {
          currentCategory = exampleFile.category();
          currentCategory.examples = [];
          currentSection.categories.push(currentCategory);
          currentCategory.selected = false;
        }
        const selected = currentExample &&
          exampleFile.title() == currentExample.title();
        if (selected) {
          currentCategory.selected = selected;
          currentSection.selected = selected;
        }

        const experiments = exampleFile.document.metadata.experiments;

        currentCategory.examples.push({
          title: exampleFile.title(),
          name: exampleFile.name(),
          description: exampleFile.document.description(),
          url: exampleFile.url(),
          urlPreview: previewUrl(exampleFile),
          urlPreviewEmbed: exampleFile.urlPreviewEmbed(),
          urlEmbed: exampleFile.urlEmbed(),
          selected: selected,
          metadata: exampleFile.document.metadata,
          experiments: experiments,
          experiment: experiments && experiments.length > 0,
          firstImage: exampleFile.document.firstImage,
          highlight: exampleFile.document.metadata.highlight
        });
      });
    return sections;
  }

  function generateStoryPreviewEmbed(stream, example, args, options) {
    const document = example.document;
    const inputFile = example.file;
    const sampleFile = inputFile.clone({contents: false});
    const sampleHtml = inputFile.contents.toString().replace('', storyController + '');
    const samplePath = path.join(STORY_EMBED_DIR, example.targetPath());
    mkdirp(path.dirname(samplePath), err => {
      if (err) {
        gutil.log(err);
        return;
      }
      fs.writeFile(samplePath, sampleHtml, err => {
        if (err) {
          gutil.log(err);
          return;
        }
      });
    });
  }

  function compileTemplate(stream, example, args, options) {
    const document = example.document;
    const inputFile = example.file;
    args.isEmbed = options.isEmbed;
    let sampleHtml = pageTemplates.render(options.template, args);
    options.postProcessors = options.postProcessors || [];
    options.postProcessors.forEach(p =>{
      sampleHtml = p(document, sampleHtml);
    });
    args.isEmbed = false;
    const sampleFile = inputFile.clone({contents: false});
    sampleFile.path = path.join(inputFile.base, options.targetPath);
    sampleFile.metadata = document.metadata;
    sampleFile.contents = new Buffer(sampleHtml);
    stream.push(sampleFile);
  }

  function previewUrl(exampleFile) {
    const document = exampleFile.document;
    if (document.isAmpStory) {
      return exampleFile.urlPreviewEmbed();
    }
    if (!document.metadata.preview) {
      return null;
    }
    if ((document.isAmpAdSample() && document.metadata.adSlot)
      || document.metadata.preview === 'cache') {
      return cachedUrl(exampleFile.urlPreview());
    } else {
      return exampleFile.urlPreview();
    }
  }

  function generateBookend(stream, sections) {
    const storiesSection = sections.find(s => s.name === 'AMPHTML stories');
    if (!storiesSection) {
      gutil.log('no stories section found');
      return;
    }
    const relatedArticles = storyBookend['related-articles'];

    storiesSection.categories.forEach(c => {
      relatedArticles[c.name] = c.examples.map(e => {
        return {
          title: e.title,
          url: e.url,
          image: e.firstImage
        };
      });
    });

    const bookendFile = latestFile.clone({contents: false});
    bookendFile.path = path.join(latestFile.base, 'json', 'bookend.json');
    bookendFile.contents = new Buffer(JSON.stringify(storyBookend, null, 2));
    stream.push(bookendFile);
  }

  function cachedUrl(url) {
    return 'https://ampbyexample-com.cdn.ampproject.org/c/s/ampbyexample.com' + url + '?exp=a4a:-1';
  }

  function sort(examples) {
    examples.sort(function(a, b) {
      return a.filePath.localeCompare(b.filePath);
    });
    return examples;
  }

  function replaceAmpStoryRuntime(document, string) {
    if (!document.isAmpStory) {
      return string;
    }
    string = string.replace(/


<commit_before>// @flow
import * as React from 'react'
import {Platform, StyleSheet, View} from 'react-native'
import * as c from '@frogpond/colors'
import {Touchable} from '@frogpond/touchable'
import {DisclosureArrow} from './disclosure-arrow'
import type {ViewStyleProp} from 'react-native/Libraries/StyleSheet/StyleSheet'

const styles = StyleSheet.create({
	childWrapper: {
		flex: 1,
	},
	container: {
		flexDirection: 'row',
		paddingLeft: 15,
		backgroundColor: c.white,
		...Platform.select({
			ios: {
				paddingVertical: 8,
				paddingRight: 8,
			},
			android: {
				paddingVertical: 16,
				paddingRight: 15,
			},
		}),
	},
	fullWidth: {
		paddingLeft: 0,
	},
	fullHeight: {
		paddingVertical: 0,
	},
})

type PropsType = {|
	style?: ViewStyleProp,
	contentContainerStyle?: ViewStyleProp,
	arrowPosition?: 'center' | 'top' | 'none',
	fullWidth?: boolean,
	fullHeight?: boolean,
	spacing?: {left?: number, right?: number},
	onPress?: () => any,
	children?: React.Node,
|}

export function ListRow(props: PropsType) {
	const {
		style,
		contentContainerStyle,
		children,
		onPress,
		spacing: {left: leftSpacing = 15, right: rightSpacing = null} = {},
		fullWidth = false,
		fullHeight = false,
	} = props

	const arrowPosition = props.arrowPosition || (onPress ? 'center' : 'none')
	const arrowPositionStyle = {
		alignSelf: arrowPosition === 'center' ? 'center' : 'flex-start',
	}
	const arrow =
		arrowPosition === 'none' || Platform.OS === 'android' ? null : (
			<DisclosureArrow style={arrowPositionStyle} />
		)

	/*  eslint-disable no-eq-null */
	let wrapperStyle = [
		styles.container,
		leftSpacing != null && {paddingLeft: leftSpacing},
		rightSpacing != null && {paddingRight: rightSpacing},
		fullWidth && styles.fullWidth,
		fullHeight && styles.fullHeight,
		contentContainerStyle,
	]
	/*  eslint-enable no-eq-null */

	let content = (
		<React.Fragment>
			<View style={[styles.childWrapper, style]}>{children}</View>
			{arrow}
		</React.Fragment>
	)

	if (onPress) {
		return (
			<Touchable onPress={onPress} style={wrapperStyle}>
				{content}
			</Touchable>
		)
	}

	return <View style={wrapperStyle}>{content}</View>
}
<commit_msg>modules/lists/list-row: Remove unused eslint-disable directive<commit_after>// @flow
import * as React from 'react'
import {Platform, StyleSheet, View} from 'react-native'
import * as c from '@frogpond/colors'
import {Touchable} from '@frogpond/touchable'
import {DisclosureArrow} from './disclosure-arrow'
import type {ViewStyleProp} from 'react-native/Libraries/StyleSheet/StyleSheet'

const styles = StyleSheet.create({
	childWrapper: {
		flex: 1,
	},
	container: {
		flexDirection: 'row',
		paddingLeft: 15,
		backgroundColor: c.white,
		...Platform.select({
			ios: {
				paddingVertical: 8,
				paddingRight: 8,
			},
			android: {
				paddingVertical: 16,
				paddingRight: 15,
			},
		}),
	},
	fullWidth: {
		paddingLeft: 0,
	},
	fullHeight: {
		paddingVertical: 0,
	},
})

type PropsType = {|
	style?: ViewStyleProp,
	contentContainerStyle?: ViewStyleProp,
	arrowPosition?: 'center' | 'top' | 'none',
	fullWidth?: boolean,
	fullHeight?: boolean,
	spacing?: {left?: number, right?: number},
	onPress?: () => any,
	children?: React.Node,
|}

export function ListRow(props: PropsType) {
	const {
		style,
		contentContainerStyle,
		children,
		onPress,
		spacing: {left: leftSpacing = 15, right: rightSpacing = null} = {},
		fullWidth = false,
		fullHeight = false,
	} = props

	const arrowPosition = props.arrowPosition || (onPress ? 'center' : 'none')
	const arrowPositionStyle = {
		alignSelf: arrowPosition === 'center' ? 'center' : 'flex-start',
	}
	const arrow =
		arrowPosition === 'none' || Platform.OS === 'android' ? null : (
			<DisclosureArrow style={arrowPositionStyle} />
		)

	let wrapperStyle = [
		styles.container,
		leftSpacing != null && {paddingLeft: leftSpacing},
		rightSpacing != null && {paddingRight: rightSpacing},
		fullWidth && styles.fullWidth,
		fullHeight && styles.fullHeight,
		contentContainerStyle,
	]

	let content = (
		<React.Fragment>
			<View style={[styles.childWrapper, style]}>{children}</View>
			{arrow}
		</React.Fragment>
	)

	if (onPress) {
		return (
			<Touchable onPress={onPress} style={wrapperStyle}>
				{content}
			</Touchable>
		)
	}

	return <View style={wrapperStyle}>{content}</View>
}
<|endoftext|>