<commit_before>/*
 *	Linux class is responsible for all interaction between website and (linux) server itself
 *	It uses the Revealing Module Pattern
 *	https://addyosmani.com/resources/essentialjsdesignpatterns/book/#revealingmodulepatternjavascript
*/

var linux = (function () {
    /*
	 * 																		PRIVATE area
	 */
    var isCameraAvailable = false;
    var serverProcessPID = -1;
    var mjpegStreamPID = -1;

    /*
     *  start python server
     *  python server is responsible for all communication on the server side
     */
    function startPythonServer() {
        $.get("assets/python/server/run_server.php",
        { resolution: "1280x720" },
        function(data) {
            if (DEBUG) console.log("AJAX connection established");
            data = data.split("\r\n");

            serverProcessPID = data[0];
            mjpegStreamPID = data[1];
            //  if the returned value is numeric (pid of the process) continue with camera
            if ($.isNumeric(serverProcessPID) || $.isNumeric(mjpegStreamPID)) {
                isCameraAvailable = true;
                if (DEBUG) console.log("serverProcessPID: " + serverProcessPID);
                if (DEBUG) console.log("mjpegStreamPID: " + mjpegStreamPID);

                //  start communication with webserver
                setTimeout(function(){
                    amplify.publish("linux->serverCommunication", "start communication on port 8080");
                }, 500);
            }
            //  otherwise display a notice and work with static image
            else {
                if (DEBUG) console.log("couldn't initialize python server");
                isCameraAvailable = false;
                serverProcessPID = -1;
                mjpegStreamPID = -1;
                amplify.publish("all->ui", "notifications.server-connection-error");
            }

            amplify.publish("linux->controller", "communication established");
        });
    };

    /*
     *  stop python server when page is unloaded (controller.js -> closePage)
     */
    function stopPythonServer() {
        $.get("assets/python/server/close_server.php");
    };

    function retIsCameraAvailable() {return isCameraAvailable;};
    function retServerProcessPID() {return serverProcessPID;};

    /*
	 * 																		SUBSCRIBE to all topics
	 */
	amplify.subscribe("controller->linux", controllerMessageCallback);

    /*
	 * 																		CALLBACK functions
	 */
	function controllerMessageCallback(message) {
		if (DEBUG) console.log("controllerMessageCallback: " + message);

		switch(message) {
			case "start python server":
                startPythonServer();
				break;
            case "stop python server":
                stopPythonServer();
                break;
			default:
				console.log("unknown command: " + message);
		}
	};

    /*
	 * 																		PUBLIC area
	 * Reveal public pointers to private functions and properties.
	 */
	return {
        getIsCameraAvailable : retIsCameraAvailable,
        getServerProcessPID : retServerProcessPID
	};
})();<commit_msg>resolve #2 - 4 different image resolutions<commit_after>/*
 *	Linux class is responsible for all interaction between website and (linux) server itself
 *	It uses the Revealing Module Pattern
 *	https://addyosmani.com/resources/essentialjsdesignpatterns/book/#revealingmodulepatternjavascript
*/

var linux = (function () {
    /*
	 * 																		PRIVATE area
	 */
    var isCameraAvailable = false;
    var serverProcessPID = -1;
    var mjpegStreamPID = -1;

    /*
     *  start python server
     *  python server is responsible for all communication on the server side
     */
    function startPythonServer() {
        /*
         *  read screen resolution and choose video size from the following ones:
         *  1280 x 720
         *  1024 x 768
         *  640 x 480
         *  320 x 240
         */
        var size = Math.max(window.screen.height, window.screen.width);
        var res = "0x0";
        if (size > 1366) res = "1280x720";
        else if (size > 1152) res = "1024x768";
        else if (size > 800) res = "640x480";
        else res = "320x240";

        if (DEBUG) console.log("Image resolution: " + res);

        $.get("assets/python/server/run_server.php",
        { resolution: res },
        function(data) {
            if (DEBUG) console.log("AJAX connection established");
            data = data.split("\r\n");

            serverProcessPID = data[0];
            mjpegStreamPID = data[1];
            //  if the returned value is numeric (pid of the process) continue with camera
            if ($.isNumeric(serverProcessPID) || $.isNumeric(mjpegStreamPID)) {
                isCameraAvailable = true;
                if (DEBUG) console.log("serverProcessPID: " + serverProcessPID);
                if (DEBUG) console.log("mjpegStreamPID: " + mjpegStreamPID);

                //  start communication with webserver
                setTimeout(function(){
                    amplify.publish("linux->serverCommunication", "start communication on port 8080");
                }, 500);
            }
            //  otherwise display a notice and work with static image
            else {
                if (DEBUG) console.log("couldn't initialize python server");
                isCameraAvailable = false;
                serverProcessPID = -1;
                mjpegStreamPID = -1;
                amplify.publish("all->ui", "notifications.server-connection-error");
            }

            amplify.publish("linux->controller", "communication established");
        });
    };

    /*
     *  stop python server when page is unloaded (controller.js -> closePage)
     */
    function stopPythonServer() {
        $.get("assets/python/server/close_server.php");
    };

    function retIsCameraAvailable() {return isCameraAvailable;};
    function retServerProcessPID() {return serverProcessPID;};

    /*
	 * 																		SUBSCRIBE to all topics
	 */
	amplify.subscribe("controller->linux", controllerMessageCallback);

    /*
	 * 																		CALLBACK functions
	 */
	function controllerMessageCallback(message) {
		if (DEBUG) console.log("controllerMessageCallback: " + message);

		switch(message) {
			case "start python server":
                startPythonServer();
				break;
            case "stop python server":
                stopPythonServer();
                break;
			default:
				console.log("unknown command: " + message);
		}
	};

    /*
	 * 																		PUBLIC area
	 * Reveal public pointers to private functions and properties.
	 */
	return {
        getIsCameraAvailable : retIsCameraAvailable,
        getServerProcessPID : retServerProcessPID
	};
})();<|endoftext|>


<commit_before>module.exports = {
  plugins: [
    'jsx-a11y',
    'react'
  ],

  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
  },

  rules: {
    // Enforce that anchors have content
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/anchor-has-content.md
    'jsx-a11y/anchor-has-content': ['error', { components: [] }],

    // Require ARIA roles to be valid and non-abstract
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/aria-role.md
    'jsx-a11y/aria-role': ['error', { ignoreNonDom: false }],

    // Enforce all aria-* props are valid.
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/aria-props.md
    'jsx-a11y/aria-props': 'error',

    // Enforce ARIA state and property values are valid.
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/aria-proptypes.md
    'jsx-a11y/aria-proptypes': 'error',

    // Enforce that elements that do not support ARIA roles, states, and
    // properties do not have those attributes.
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/aria-unsupported-elements.md
    'jsx-a11y/aria-unsupported-elements': 'error',

    // Enforce that all elements that require alternative text have meaningful information
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/alt-text.md
    'jsx-a11y/alt-text': ['error', {
      elements: ['img', 'object', 'area', 'input[type="image"]'],
      img: [],
      object: [],
      area: [],
      'input[type="image"]': [],
    }],

    // Prevent img alt text from containing redundant words like "image", "picture", or "photo"
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/img-redundant-alt.md
    'jsx-a11y/img-redundant-alt': 'error',

    // require that JSX labels use "htmlFor"
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/label-has-for.md
    'jsx-a11y/label-has-for': ['error', {
      components: [],
      required: {
        every: ['nesting', 'id'],
      },
      allowChildren: false,
    }],

    // Enforce that a label tag has a text label and an associated control.
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/b800f40a2a69ad48015ae9226fbe879f946757ed/docs/rules/label-has-associated-control.md
    'jsx-a11y/label-has-associated-control': ['error', {
      labelComponents: [],
      labelAttributes: [],
      controlComponents: [],
      assert: 'both',
      depth: 25
    }],

    // Enforce that a control (an interactive element) has a text label.
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/control-has-associated-label.md
    // TODO, semver-major: enable
    'jsx-a11y/control-has-associated-label': ['off', {
      labelAttributes: ['label'],
      controlComponents: [],
      ignoreElements: [
        'audio',
        'canvas',
        'embed',
        'input',
        'textarea',
        'tr',
        'video',
      ],
      ignoreRoles: [
        'grid',
        'listbox',
        'menu',
        'menubar',
        'radiogroup',
        'row',
        'tablist',
        'toolbar',
        'tree',
        'treegrid',
      ],
      depth: 5,
    }],

    // require that mouseover/out come with focus/blur, for keyboard-only users
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/mouse-events-have-key-events.md
    'jsx-a11y/mouse-events-have-key-events': 'error',

    // Prevent use of `accessKey`
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-access-key.md
    'jsx-a11y/no-access-key': 'error',

    // require onBlur instead of onChange
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-onchange.md
    'jsx-a11y/no-onchange': 'off',

    // Elements with an interactive role and interaction handlers must be focusable
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/interactive-supports-focus.md
    'jsx-a11y/interactive-supports-focus': 'error',

    // Enforce that elements with ARIA roles must have all required attributes
    // for that role.
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/role-has-required-aria-props.md
    'jsx-a11y/role-has-required-aria-props': 'error',

    // Enforce that elements with explicit or implicit roles defined contain
    // only aria-* properties supported by that role.
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/role-supports-aria-props.md
    'jsx-a11y/role-supports-aria-props': 'error',

    // Enforce tabIndex value is not greater than zero.
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/tabindex-no-positive.md
    'jsx-a11y/tabindex-no-positive': 'error',

    // ensure <hX> tags have content and are not aria-hidden
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/heading-has-content.md
    'jsx-a11y/heading-has-content': ['error', { components: [''] }],

    // require HTML elements to have a "lang" prop
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/html-has-lang.md
    'jsx-a11y/html-has-lang': 'error',

    // require HTML element's lang prop to be valid
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/lang.md
    'jsx-a11y/lang': 'error',

    // prevent distracting elements, like <marquee> and <blink>
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-distracting-elements.md
    'jsx-a11y/no-distracting-elements': ['error', {
      elements: ['marquee', 'blink'],
    }],

    // only allow <th> to have the "scope" attr
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/scope.md
    'jsx-a11y/scope': 'error',

    // require onClick be accompanied by onKeyUp/onKeyDown/onKeyPress
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/click-events-have-key-events.md
    'jsx-a11y/click-events-have-key-events': 'error',

    // Enforce that DOM elements without semantic behavior not have interaction handlers
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-static-element-interactions.md
    'jsx-a11y/no-static-element-interactions': ['error', {
      handlers: [
        'onClick',
        'onMouseDown',
        'onMouseUp',
        'onKeyPress',
        'onKeyDown',
        'onKeyUp',
      ]
    }],

    // A non-interactive element does not support event handlers (mouse and key handlers)
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-noninteractive-element-interactions.md
    'jsx-a11y/no-noninteractive-element-interactions': ['error', {
      handlers: [
        'onClick',
        'onMouseDown',
        'onMouseUp',
        'onKeyPress',
        'onKeyDown',
        'onKeyUp',
      ]
    }],

    // ensure emoji are accessible
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/accessible-emoji.md
    'jsx-a11y/accessible-emoji': 'error',

    // elements with aria-activedescendant must be tabbable
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/aria-activedescendant-has-tabindex.md
    'jsx-a11y/aria-activedescendant-has-tabindex': 'error',

    // ensure iframe elements have a unique title
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/iframe-has-title.md
    'jsx-a11y/iframe-has-title': 'error',

    // prohibit autoFocus prop
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-autofocus.md
    'jsx-a11y/no-autofocus': ['error', { ignoreNonDOM: true }],

    // ensure HTML elements do not specify redundant ARIA roles
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-redundant-roles.md
    'jsx-a11y/no-redundant-roles': 'error',

    // media elements must have captions
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/media-has-caption.md
    'jsx-a11y/media-has-caption': ['error', {
      audio: [],
      video: [],
      track: [],
    }],

    // WAI-ARIA roles should not be used to convert an interactive element to non-interactive
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-interactive-element-to-noninteractive-role.md
    'jsx-a11y/no-interactive-element-to-noninteractive-role': ['error', {
      tr: ['none', 'presentation'],
    }],

    // WAI-ARIA roles should not be used to convert a non-interactive element to interactive
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-noninteractive-element-to-interactive-role.md
    'jsx-a11y/no-noninteractive-element-to-interactive-role': ['error', {
      ul: ['listbox', 'menu', 'menubar', 'radiogroup', 'tablist', 'tree', 'treegrid'],
      ol: ['listbox', 'menu', 'menubar', 'radiogroup', 'tablist', 'tree', 'treegrid'],
      li: ['menuitem', 'option', 'row', 'tab', 'treeitem'],
      table: ['grid'],
      td: ['gridcell'],
    }],

    // Tab key navigation should be limited to elements on the page that can be interacted with.
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-noninteractive-tabindex.md
    'jsx-a11y/no-noninteractive-tabindex': ['error', {
      tags: [],
      roles: ['tabpanel'],
    }],

    // ensure <a> tags are valid
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/0745af376cdc8686d85a361ce36952b1fb1ccf6e/docs/rules/anchor-is-valid.md
    'jsx-a11y/anchor-is-valid': ['error', {
      components: ['Link'],
      specialLink: ['to'],
      aspects: ['noHref', 'invalidHref', 'preferButton'],
    }],
  },
};
<commit_msg>[eslint config] [breaking] disable `label-has-for`; enable `control-has-associated-label`<commit_after>module.exports = {
  plugins: [
    'jsx-a11y',
    'react'
  ],

  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
  },

  rules: {
    // Enforce that anchors have content
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/anchor-has-content.md
    'jsx-a11y/anchor-has-content': ['error', { components: [] }],

    // Require ARIA roles to be valid and non-abstract
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/aria-role.md
    'jsx-a11y/aria-role': ['error', { ignoreNonDom: false }],

    // Enforce all aria-* props are valid.
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/aria-props.md
    'jsx-a11y/aria-props': 'error',

    // Enforce ARIA state and property values are valid.
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/aria-proptypes.md
    'jsx-a11y/aria-proptypes': 'error',

    // Enforce that elements that do not support ARIA roles, states, and
    // properties do not have those attributes.
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/aria-unsupported-elements.md
    'jsx-a11y/aria-unsupported-elements': 'error',

    // Enforce that all elements that require alternative text have meaningful information
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/alt-text.md
    'jsx-a11y/alt-text': ['error', {
      elements: ['img', 'object', 'area', 'input[type="image"]'],
      img: [],
      object: [],
      area: [],
      'input[type="image"]': [],
    }],

    // Prevent img alt text from containing redundant words like "image", "picture", or "photo"
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/img-redundant-alt.md
    'jsx-a11y/img-redundant-alt': 'error',

    // require that JSX labels use "htmlFor"
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/label-has-for.md
    // deprecated: replaced by `label-has-associated-control` rule
    'jsx-a11y/label-has-for': ['off', {
      components: [],
      required: {
        every: ['nesting', 'id'],
      },
      allowChildren: false,
    }],

    // Enforce that a label tag has a text label and an associated control.
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/b800f40a2a69ad48015ae9226fbe879f946757ed/docs/rules/label-has-associated-control.md
    'jsx-a11y/label-has-associated-control': ['error', {
      labelComponents: [],
      labelAttributes: [],
      controlComponents: [],
      assert: 'both',
      depth: 25
    }],

    // Enforce that a control (an interactive element) has a text label.
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/control-has-associated-label.md
    'jsx-a11y/control-has-associated-label': ['error', {
      labelAttributes: ['label'],
      controlComponents: [],
      ignoreElements: [
        'audio',
        'canvas',
        'embed',
        'input',
        'textarea',
        'tr',
        'video',
      ],
      ignoreRoles: [
        'grid',
        'listbox',
        'menu',
        'menubar',
        'radiogroup',
        'row',
        'tablist',
        'toolbar',
        'tree',
        'treegrid',
      ],
      depth: 5,
    }],

    // require that mouseover/out come with focus/blur, for keyboard-only users
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/mouse-events-have-key-events.md
    'jsx-a11y/mouse-events-have-key-events': 'error',

    // Prevent use of `accessKey`
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-access-key.md
    'jsx-a11y/no-access-key': 'error',

    // require onBlur instead of onChange
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-onchange.md
    'jsx-a11y/no-onchange': 'off',

    // Elements with an interactive role and interaction handlers must be focusable
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/interactive-supports-focus.md
    'jsx-a11y/interactive-supports-focus': 'error',

    // Enforce that elements with ARIA roles must have all required attributes
    // for that role.
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/role-has-required-aria-props.md
    'jsx-a11y/role-has-required-aria-props': 'error',

    // Enforce that elements with explicit or implicit roles defined contain
    // only aria-* properties supported by that role.
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/role-supports-aria-props.md
    'jsx-a11y/role-supports-aria-props': 'error',

    // Enforce tabIndex value is not greater than zero.
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/tabindex-no-positive.md
    'jsx-a11y/tabindex-no-positive': 'error',

    // ensure <hX> tags have content and are not aria-hidden
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/heading-has-content.md
    'jsx-a11y/heading-has-content': ['error', { components: [''] }],

    // require HTML elements to have a "lang" prop
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/html-has-lang.md
    'jsx-a11y/html-has-lang': 'error',

    // require HTML element's lang prop to be valid
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/lang.md
    'jsx-a11y/lang': 'error',

    // prevent distracting elements, like <marquee> and <blink>
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-distracting-elements.md
    'jsx-a11y/no-distracting-elements': ['error', {
      elements: ['marquee', 'blink'],
    }],

    // only allow <th> to have the "scope" attr
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/scope.md
    'jsx-a11y/scope': 'error',

    // require onClick be accompanied by onKeyUp/onKeyDown/onKeyPress
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/click-events-have-key-events.md
    'jsx-a11y/click-events-have-key-events': 'error',

    // Enforce that DOM elements without semantic behavior not have interaction handlers
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-static-element-interactions.md
    'jsx-a11y/no-static-element-interactions': ['error', {
      handlers: [
        'onClick',
        'onMouseDown',
        'onMouseUp',
        'onKeyPress',
        'onKeyDown',
        'onKeyUp',
      ]
    }],

    // A non-interactive element does not support event handlers (mouse and key handlers)
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-noninteractive-element-interactions.md
    'jsx-a11y/no-noninteractive-element-interactions': ['error', {
      handlers: [
        'onClick',
        'onMouseDown',
        'onMouseUp',
        'onKeyPress',
        'onKeyDown',
        'onKeyUp',
      ]
    }],

    // ensure emoji are accessible
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/accessible-emoji.md
    'jsx-a11y/accessible-emoji': 'error',

    // elements with aria-activedescendant must be tabbable
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/aria-activedescendant-has-tabindex.md
    'jsx-a11y/aria-activedescendant-has-tabindex': 'error',

    // ensure iframe elements have a unique title
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/iframe-has-title.md
    'jsx-a11y/iframe-has-title': 'error',

    // prohibit autoFocus prop
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-autofocus.md
    'jsx-a11y/no-autofocus': ['error', { ignoreNonDOM: true }],

    // ensure HTML elements do not specify redundant ARIA roles
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-redundant-roles.md
    'jsx-a11y/no-redundant-roles': 'error',

    // media elements must have captions
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/media-has-caption.md
    'jsx-a11y/media-has-caption': ['error', {
      audio: [],
      video: [],
      track: [],
    }],

    // WAI-ARIA roles should not be used to convert an interactive element to non-interactive
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-interactive-element-to-noninteractive-role.md
    'jsx-a11y/no-interactive-element-to-noninteractive-role': ['error', {
      tr: ['none', 'presentation'],
    }],

    // WAI-ARIA roles should not be used to convert a non-interactive element to interactive
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-noninteractive-element-to-interactive-role.md
    'jsx-a11y/no-noninteractive-element-to-interactive-role': ['error', {
      ul: ['listbox', 'menu', 'menubar', 'radiogroup', 'tablist', 'tree', 'treegrid'],
      ol: ['listbox', 'menu', 'menubar', 'radiogroup', 'tablist', 'tree', 'treegrid'],
      li: ['menuitem', 'option', 'row', 'tab', 'treeitem'],
      table: ['grid'],
      td: ['gridcell'],
    }],

    // Tab key navigation should be limited to elements on the page that can be interacted with.
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-noninteractive-tabindex.md
    'jsx-a11y/no-noninteractive-tabindex': ['error', {
      tags: [],
      roles: ['tabpanel'],
    }],

    // ensure <a> tags are valid
    // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/0745af376cdc8686d85a361ce36952b1fb1ccf6e/docs/rules/anchor-is-valid.md
    'jsx-a11y/anchor-is-valid': ['error', {
      components: ['Link'],
      specialLink: ['to'],
      aspects: ['noHref', 'invalidHref', 'preferButton'],
    }],
  },
};
<|endoftext|>


<commit_before>// TEI table js

function TeiTable() {

    var XSLTProc;
    var hiddenCols = [];

    /** Populate the hide and show menus. */
    function _populateMenus() {
        var headings = []
        $('table th').each(function(i) {
            var h = {'label': $(this).html(),
                     'visible': hiddenCols.indexOf(i) == -1,
                     'index': i}
            headings.push(h);
        });

        function renderPlaceholder(id) {
            var template = $("#table-menu-ph-template").html();
                rendered = Mustache.render(template,
                                           {label: "Nothing to " + id});
            $("#" + id + "-menu").html(rendered);
        }

        function renderMenu(id, cls) {
            var template = $("#table-menu-template").html();
                rendered = Mustache.render(template,
                                           {cls: cls, headings: headings});
            $("#" + id + "-menu").html(rendered);
        }

        function getHideCls() {
            return (this.visible) ? "hide-column" : "hide-column hidden";
        }

        function getShowCls() {
            return (!this.visible) ? "show-column" : "show-column hidden";
        }

        if (hiddenCols.length !== $('table th').length) {
            renderMenu('hide', getHideCls)
        } else {
            renderPlaceholder('hide');
        }

        if (hiddenCols.length > 0) {
            renderMenu('show', getShowCls)
        } else {
            renderPlaceholder('show');
        }
    }

    /** Number table rows. */
    function _numberRows() {
        $('table thead tr th').eq(0).before('#	');
        $('table tbody').find('tr').each(function(i){
            $(this).find('td').eq(0).before('' + (i + 1) + '	');
        });
    }

    /** Populate tooltips. */
    function _populateTooltips() {
        $('name[role]').each(function() {
            $(this).attr('data-toggle', 'tooltip');
            var title = 'Role: ' + $(this).attr('role');
            $(this).attr('title', title);
        });
        $('date[calendar]').each(function() {
            $(this).attr('data-toggle', 'tooltip');
            var title = 'Calendar: ' + $(this).attr('calendar');
            $(this).attr('title', title);
        });
        $("body").tooltip({
            selector: '[data-toggle="tooltip"]'
        });
    }

    /** Get the width of a scroll bar. */
    function _getScrollBarWidth () {
        var $outer = $('
').css({
            visibility: 'hidden',
            width: 100,
            overflow: 'scroll'
        }).appendTo('body');
        var widthWithScroll = $('
').css({
            width: '100%'
        }).appendTo($outer).outerWidth();
        $outer.remove();
        return 100 - widthWithScroll;
    };

    /** Hide a table column. */
    this.hideColumn = function(columnIndex) {
        $('table tr > *:nth-child(' + (columnIndex + 1) + ')').hide();
        hiddenCols.push(columnIndex);
        _populateMenus();
    }

    /** Show a table column. */
    this.showColumn = function(columnIndex) {
        $('table tr > *:nth-child(' + (columnIndex + 1) + ')').show();
        hiddenCols = $.grep(hiddenCols, function(value) {
            return value != columnIndex;
        });
        _populateMenus();
    }

    /** Fixes for frozen table header. */
    this.fixFrozenTable = function() {

        // Resize header cells
        $('#tei-view.fixed tbody tr:first-child td').each(function(i) {
            var colWidth = $(this).width();
            $('table thead tr th:nth-child(' + (i + 1) + ')').width(colWidth);
        });

        // Resize tbody to always show vertical scroll bar
        var offset = $('#tei-view').scrollLeft();
            width  = $('#tei-view').width();
        $('#tei-view.fixed tbody').css('width', offset + width);

        // Add margins
        var headerHeight = $('#tei-view thead').height();
            scrollWidth  = _getScrollBarWidth();
            footerHeight = $('footer').height();
            offset = 100 + scrollWidth + footerHeight;
        $('#tei-view.fixed tbody').css('margin-top', headerHeight);
        $('#tei-view.fixed tbody').css('max-height',
                                       'calc(100vh - ' + offset + 'px)');
    }

    /** Load TEI data into the table view. */
    this.populate = function(xml) {
        teiTable = this;
        html = XSLTProc.transformToFragment(xml, document);
        $('#tei-view').html(html);
        $(hiddenCols).each(function(k, v) {
            teiTable.hideColumn(v);
        });
        _populateMenus();
        _populateTooltips();
        _numberRows();
    }

    /** Update the XSLT processor. */
    this.updateXSLTProc = function(obj) {
        XSLTProc = obj;
    }

    /** Check if the XSLT processor has been loaded. */
    this.XSLTProcLoaded = function() {
        return typeof(XSLTProc) !== 'undefined';
    }

    /** Show table borders. */
    this.showBorders = function() {
        $('table').addClass('table-bordered');
    }

    /** Hide table borders. */
    this.hideBorders = function() {
        $('table').removeClass('table-bordered');
    }

    /** Show tooltips. */
    this.showTooltips = function() {
        _populateTooltips();
    }

    /** Hide tooltips. */
    this.hideTooltips = function() {
        $('[data-toggle="tooltip"]').removeAttr('data-toggle');
    }

    /** Freeze header. */
    this.freezeHeader = function() {
        $('#tei-view').addClass('fixed');
    }

    /** Unfreeze header. */
    this.unfreezeHeader = function() {
        $('#tei-view').removeClass('fixed');
    }
}
<commit_msg>Tidy up table.js<commit_after>// TEI table js

function TeiTable() {

    var XSLTProc;
    var hiddenCols = [];

    /** Populate the hide and show menus. */
    function _populateMenus() {

        // Get heading, index and visibility for each column
        var columns = []
        $('table thead th').each(function(i) {
            var h = {'heading': $(this).html(),
                     'visible': hiddenCols.indexOf(i) == -1,
                     'index': i}
            columns.push(h);
        });

        /** Render menu with placeholder. */
        function renderPlaceholder(id) {
            var template = $("#table-menu-ph-template").html();
                rendered = Mustache.render(template,
                                           {heading: "Nothing to " + id});
            $("#" + id + "-menu").html(rendered);
        }

        /** Render menu. */
        function renderMenu(id, cls) {
            var template = $("#table-menu-template").html();
                rendered = Mustache.render(template,
                                           {cls: cls, columns: columns});
            $("#" + id + "-menu").html(rendered);
        }

        /** Determine whether or not to display a hide menu item. */
        function getHideCls() {
            console.log(this, this.visible);
            return (this.visible) ? "hide-column" : "hide-column hidden";
        }

        /** Determine whether or not to display a show menu item. */
        function getShowCls() {
            return (!this.visible) ? "show-column" : "show-column hidden";
        }

        // Render the hide menu or a placeholder
        if (hiddenCols.length !== $('table th').length) {
            renderMenu('hide', getHideCls)
        } else {
            renderPlaceholder('hide');
        }

        // Render the show menu or a placeholder
        if (hiddenCols.length > 0) {
            renderMenu('show', getShowCls)
        } else {
            renderPlaceholder('show');
        }
    }

    /**
     *  Number the table rows.
     *  @param {number} firstIndex - The index from which to start numbering rows.
     */
    function _numberRows(firstIndex) {
        $('table thead tr th').eq(0).before('#	');
        $('table tbody').find('tr').each(function(i){
            var index = firstIndex + i + 1;
            $(this).find('td').eq(0).before('' + index + '	');
        });
    }

    /** Populate tooltips. */
    function _populateTooltips() {
        var tooltips = [{'tag': 'name', 'attr': 'role'},
                        {'tag': 'date', 'attr': 'calendar'}]

        $(tooltips).each(function(i, tooltip){
            var attr = tooltip.attr;
            $(tooltip.tag + '[' + attr + ']').each(function() {
                var title = attr.capitalise()  + ': ' + $(this).attr(attr);
                $(this).attr('data-toggle', 'tooltip');
                $(this).attr('title', title);
            });
        });

        // Initialise
        $("body").tooltip({selector: '[data-toggle="tooltip"]'});
    }

    /** Hide a table column. */
    this.hideColumn = function(columnIndex) {
        $('table tr > *:nth-child(' + (columnIndex + 1) + ')').hide();
        hiddenCols.push(columnIndex);
        _populateMenus();
    }

    /** Show a table column. */
    this.showColumn = function(columnIndex) {
        $('table tr > *:nth-child(' + (columnIndex + 1) + ')').show();
        hiddenCols = $.grep(hiddenCols, function(value) {
            return value != columnIndex;
        });
        _populateMenus();
    }

    /** Fixes for frozen table header. */
    this.fixFrozenTable = function() {

        // Resize header cells
        $('#tei-view.fixed tbody tr:first-child td').each(function(i) {
            var colWidth = $(this).width();
            $('table thead tr th:nth-child(' + (i + 1) + ')').width(colWidth);
        });

        // Resize tbody to always show vertical scroll bar
        var offset = $('#tei-view').scrollLeft();
            width  = $('#tei-view').width();
        $('#tei-view.fixed tbody').css('width', offset + width);

        // Get the width of a scroll bar.
        var $outer = $('
').css({
            visibility: 'hidden',
            width: 100,
            overflow: 'scroll'
        }).appendTo('body');
        var widthWithScroll = $('
').css({
            width: '100%'
        }).appendTo($outer).outerWidth();
        $outer.remove();
        var scrollBarWidth = 100 - widthWithScroll;

        // Update height and margins of table body
        var headerHeight = $('#tei-view thead').height(),
            footerHeight = $('footer').height(),
            offset       = 100 + scrollBarWidth + footerHeight;
        $('#tei-view.fixed tbody').css('margin-top', headerHeight);
        $('#tei-view.fixed tbody').css('max-height',
                                       'calc(100vh - ' + offset + 'px)');
    }

    /**
     * Load TEI data into the table view.
     * @param {string} xml - The XML files to load.
     * @param {number} firstIndex - The index from which to start numbering rows.
     */
    this.populate = function(xml, firstIndex) {
        teiTable = this;
        html = XSLTProc.transformToFragment(xml, document);
        $('#tei-view').html(html);
        $(hiddenCols).each(function(k, v) {
            teiTable.hideColumn(v);
        });
        _populateMenus();

        // Number rows after show/hide menus populated
        _numberRows(firstIndex);
    }

    /** Update the XSLT processor. */
    this.updateXSLTProc = function(obj) {
        XSLTProc = obj;
    }

    /** Check if the XSLT processor has been loaded. */
    this.XSLTProcLoaded = function() {
        return typeof(XSLTProc) !== 'undefined';
    }

    /** Show table borders. */
    this.showBorders = function() {
        $('table').addClass('table-bordered');
    }

    /** Hide table borders. */
    this.hideBorders = function() {
        $('table').removeClass('table-bordered');
    }

    /** Show tooltips. */
    this.showTooltips = function() {
        _populateTooltips();
    }

    /** Hide tooltips. */
    this.hideTooltips = function() {
        $('[data-toggle="tooltip"]').removeAttr('data-toggle');
    }

    /** Freeze header. */
    this.freezeHeader = function() {
        $('#tei-view').addClass('fixed');
    }

    /** Unfreeze header. */
    this.unfreezeHeader = function() {
        $('#tei-view').removeClass('fixed');
    }

    // String function to capitalise first letter
    String.prototype.capitalise = function() {
        return this.charAt(0).toUpperCase() + this.slice(1);
    }
}
<|endoftext|>


<commit_before>module.exports = function(grunt) {

	// Project configuration.
	grunt.initConfig({
		pkg: grunt.file.readJSON('package.json'),
		clean: ['./public/patterns'],
		copy: {
			main: {
				files: [
					{ expand: true, cwd: './source/js/', src: '*', dest: './public/js/'},
					{ expand: true, cwd: './source/css/', src: 'style.css', dest: './public/css/' },
					{ expand: true, cwd: './source/images/', src: ['*.png', '*.jpg', '*.gif', '*.jpeg'], dest: './public/images/' },
					{ expand: true, cwd: './source/images/sample/', src: ['*.png', '*.jpg', '*.gif', '*.jpeg'], dest: './public/images/sample/'},
					{ expand: true, cwd: './source/fonts/', src: '*', dest: './public/fonts/'}
				]
			}
		},
		jshint: {
			options: {
				"curly": true,
				"eqnull": true,
				"eqeqeq": true,
				"undef": true,
				"forin": true,
				//"unused": true,
				"node": true
			},
			patternlab: ['Gruntfile.js', './builder/lib/patternlab.js']
		},
		watch: {
			// scss: { //scss can be watched if you like
			// 	files: ['source/css/**/*.scss', 'public/styleguide/css/*.scss'],
			// 	tasks: ['default']
			// },
			mustache: {
				files: ['source/_patterns/**/*.mustache'],
				tasks: ['default']
			},
			data: {
				files: ['source/_patterns/**/*.json'],
				tasks: ['default']
			}
		},
		sass: {
			build: {
				options: {
					style: 'expanded',
					precision: 8
				},
				files: {
					'./source/css/style.css': './source/css/style.scss',
					'./public/styleguide/css/static.css': './public/styleguide/css/static.scss',
					'./public/styleguide/css/styleguide.css': './public/styleguide/css/styleguide.scss'
				}
			}
		}
	});

	// load all grunt tasks
 	require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);

	//load the patternlab task
	grunt.task.loadTasks('./builder/');

	//if you choose to use scss, or any preprocessor, you can add it here
	grunt.registerTask('default', ['clean', 'patternlab', /*'sass',*/ 'copy']);
};<commit_msg>watch _data/*.json too<commit_after>module.exports = function(grunt) {

	// Project configuration.
	grunt.initConfig({
		pkg: grunt.file.readJSON('package.json'),
		clean: ['./public/patterns'],
		copy: {
			main: {
				files: [
					{ expand: true, cwd: './source/js/', src: '*', dest: './public/js/'},
					{ expand: true, cwd: './source/css/', src: 'style.css', dest: './public/css/' },
					{ expand: true, cwd: './source/images/', src: ['*.png', '*.jpg', '*.gif', '*.jpeg'], dest: './public/images/' },
					{ expand: true, cwd: './source/images/sample/', src: ['*.png', '*.jpg', '*.gif', '*.jpeg'], dest: './public/images/sample/'},
					{ expand: true, cwd: './source/fonts/', src: '*', dest: './public/fonts/'}
				]
			}
		},
		jshint: {
			options: {
				"curly": true,
				"eqnull": true,
				"eqeqeq": true,
				"undef": true,
				"forin": true,
				//"unused": true,
				"node": true
			},
			patternlab: ['Gruntfile.js', './builder/lib/patternlab.js']
		},
		watch: {
			// scss: { //scss can be watched if you like
			// 	files: ['source/css/**/*.scss', 'public/styleguide/css/*.scss'],
			// 	tasks: ['default']
			// },
			mustache: {
				files: ['source/_patterns/**/*.mustache'],
				tasks: ['default']
			},
			data: {
				files: ['source/_patterns/**/*.json', 'source/_data/*.json'],
				tasks: ['default']
			}
		},
		sass: {
			build: {
				options: {
					style: 'expanded',
					precision: 8
				},
				files: {
					'./source/css/style.css': './source/css/style.scss',
					'./public/styleguide/css/static.css': './public/styleguide/css/static.scss',
					'./public/styleguide/css/styleguide.css': './public/styleguide/css/styleguide.scss'
				}
			}
		}
	});

	// load all grunt tasks
 	require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);

	//load the patternlab task
	grunt.task.loadTasks('./builder/');

	//if you choose to use scss, or any preprocessor, you can add it here
	grunt.registerTask('default', ['clean', 'patternlab', /*'sass',*/ 'copy']);
};<|endoftext|>


<commit_before>const mongooseOptions = {
    useNewUrlParser: true, useFindAndModify: false, useCreateIndex: true, useUnifiedTopology: true
};

module.exports = mongooseOptions;
<commit_msg>Set mongoose read preference to secondary nodes<commit_after>const mongooseOptions = {
    useNewUrlParser: true,
    useFindAndModify: false,
    useCreateIndex: true,
    useUnifiedTopology: true,
    replSet: { readPreference: 'secondary' }
};

module.exports = mongooseOptions;
<|endoftext|>


<commit_before>/**
 * Policies are simply Express middleware functions which run before your controllers.
 * You can apply one or more policies for a given controller or action.
 *
 * Any policy file (e.g. `authenticated.js`) can be dropped into the `/policies` folder,
 * at which point it can be accessed below by its filename, minus the extension, (e.g. `authenticated`)
 *
 * For more information on policies, check out:
 * http://sailsjs.org/#documentation
 */
module.exports.policies = {
    /**
     * By default require authentication always.
     *
     * see api/policies/authenticated.js
     */
    "*": ["flashMessage", "authenticated"],

    // whitelist the auth controller, this is used for login
    "auth": {
        "*": true,

        // Custom actions
        login:          ["flashMessage"],
        logout:         [true],
        authenticate:   [true]
    },

    // Project controller policies
    "Project": {
        // By default do not allow nothing
        "*":        false,

        // Default handling for blueprints
        find:       ["flashMessage", "authenticated", "isAjaxOrSocket"],
        create:     ["flashMessage", "authenticated", "isAjaxOrSocket", "addUserDataCreate"],
        update:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasProjectAdmin", "addUserDataUpdate"],
        destroy:    ["flashMessage", "authenticated", "isAjaxOrSocket", "hasProjectAdmin"],

        // Custom actions
        add:        ["flashMessage", "authenticated", "isAjax"],
        edit:       ["flashMessage", "authenticated", "isAjax", "hasProjectAccess"],
        backlog:    ["flashMessage", "authenticated", "isAjax", "hasProjectAccess"],
        milestones: ["flashMessage", "authenticated", "isAjax", "hasProjectAccess"],
        planning:   ["flashMessage", "authenticated", "isAjax", "hasProjectAccess"],
        statistics: ["flashMessage", "authenticated", "isAjax", "hasProjectAccess"],
        sprints:    ["flashMessage", "authenticated", "isAjax", "hasProjectAccess"]
    },

    // Phase controller policies
    "Phase": {
        // By default do not allow nothing
        "*":        false,

        // Default handling for blueprints
        find:       ["flashMessage", "authenticated", "isAjaxOrSocket"],
        create:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasPhaseAdmin", "addUserDataCreate"],
        update:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasPhaseAdmin", "addUserDataUpdate"],
        destroy:    ["flashMessage", "authenticated", "isAjaxOrSocket", "hasPhaseAdmin"],

        // Custom actions
        edit:       ["flashMessage", "authenticated", "isAjax", "hasProjectAdmin"]
    },

    // Sprint controller policies
    "Sprint": {
        // By default do not allow nothing
        "*":        false,

        // Default handling for blueprints
        find:       ["flashMessage", "authenticated", "isAjaxOrSocket", "hasSprintAccess"],
        create:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasSprintAdmin", "addUserDataCreate"],
        update:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasSprintAdmin", "addUserDataUpdate"],
        destroy:    ["flashMessage", "authenticated", "isAjaxOrSocket", "hasSprintAdmin"],

        // Custom actions
        add:            ["flashMessage", "authenticated", "isAjax", "hasSprintAdmin"],
        edit:           ["flashMessage", "authenticated", "isAjax", "hasSprintAccess"],
        backlog:        ["flashMessage", "authenticated", "isAjax", "hasSprintAccess"],
        charts:         ["flashMessage", "authenticated", "isAjax", "hasSprintAccess"],
        chartDataTasks: ["flashMessage", "authenticated", "isAjax", "hasSprintAccess"]
    },

    // Milestone controller policies
    "Milestone": {
        // By default do not allow nothing
        "*":        false,

        // Default handling for blueprints
        find:       ["flashMessage", "authenticated", "isAjaxOrSocket", "hasMilestoneAccess"],
        create:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasMilestoneAdmin", "addUserDataCreate"],
        update:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasMilestoneAdmin", "addUserDataUpdate"],
        destroy:    ["flashMessage", "authenticated", "isAjaxOrSocket", "hasMilestoneAdmin"],

        // Custom actions
        add:        ["flashMessage", "authenticated", "isAjax", "hasMilestoneAdmin"],
        edit:       ["flashMessage", "authenticated", "isAjax", "hasMilestoneAccess"],
        stories:    ["flashMessage", "authenticated", "isAjax", "hasMilestoneAccess"]
    },

    // Story controller policies
    "Story": {
        // By default do not allow nothing
        "*":        false,

        // Default handling for blueprints
        find:       ["flashMessage", "authenticated", "isAjaxOrSocket", "hasStoryAccess"],
        create:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasStoryAdmin", "addUserDataCreate"],
        update:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasStoryAdmin", "addUserDataUpdate"],
        destroy:    ["flashMessage", "authenticated", "isAjaxOrSocket", "hasStoryAdmin"],

        // Custom actions
        add:        ["flashMessage", "authenticated", "isAjax", "hasStoryAdmin"],
        split:      ["flashMessage", "authenticated", "isAjax", "hasStoryAdmin"],
        edit:       ["flashMessage", "authenticated", "isAjax", "hasStoryAccess"],
        tasks:      ["flashMessage", "authenticated", "isAjax", "hasStoryAccess"]
    },

    // Task controller policies
    "Task": {
        // By default do not allow nothing
        "*":        false,

        // Default handling for blueprints
        find:       ["flashMessage", "authenticated", "isAjaxOrSocket", "hasTaskAccess"],
        create:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasTaskAdmin", "addUserDataCreate"],
        update:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasTaskAdmin", "addUserDataUpdate"],
        destroy:    ["flashMessage", "authenticated", "isAjaxOrSocket", "hasTaskAdmin"],

        // Custom actions
        add:        ["flashMessage", "authenticated", "isAjax", "hasTaskAdmin"],
        edit:       ["flashMessage", "authenticated", "isAjax", "hasTaskAccess"],
        statistics: ["flashMessage", "authenticated", "isAjax", "hasTaskAccess"],
        releaseTask:["flashMessage", "authenticated", "isAjax", "hasTaskAccess"],
        takeTask:   ["flashMessage", "authenticated", "isAjax", "hasTaskAccess"]
    },

    // User controller policies
    "User": {
        // By default do not allow nothing
        "*":        false,

        // Default handling for blueprints
        find:       ["flashMessage", "authenticated", "isAjaxOrSocket"],
        create:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasUserAdmin", "addUserDataCreate"],
        update:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasUserAdminOrItself", "addUserDataUpdate"],
        destroy:    ["flashMessage", "authenticated", "isAjaxOrSocket", "hasUserAdmin"],

        // Custom actions
        list:           ["flashMessage", "authenticated", "isAjax", "hasUserAdmin"],
        add:            ["flashMessage", "authenticated", "isAjax", "hasUserAdmin"],
        edit:           ["flashMessage", "authenticated", "isAjax", "hasUserAdminOrItself"],
        history:        ["flashMessage", "authenticated", "isAjax", "hasUserAdminOrItself"],
        projects:       ["flashMessage", "authenticated", "isAjax", "hasUserAdminOrItself"],
        changePassword: ["flashMessage", "authenticated", "isAjax", "hasUserAdminOrItself"]
    },

    // ProjectUser controller policies
    "ProjectUser": {
        // By default do not allow nothing
        "*":        false,

        // Default handling for blueprints
        find:       ["flashMessage", "authenticated", "isAjaxOrSocket"],
        create:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasProjectAdmin", "addUserDataCreate"],
        update:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasProjectAdmin", "addUserDataUpdate"],
        destroy:    ["flashMessage", "authenticated", "isAjaxOrSocket", "hasProjectAdmin"],

        // Custom actions
        users:          ["flashMessage", "authenticated", "isAjax", "hasProjectAccess"],
        availableUsers: ["flashMessage", "authenticated", "isAjaxOrSocket", "hasProjectAccess"],
        ownProjects:    ["flashMessage", "authenticated", "isAjaxOrSocket"],
        getRole:        ["flashMessage", "authenticated", "isAjaxOrSocket", "hasProjectAccess"]
    },

    // Type controller policies
    "Type": {
        // By default do not allow nothing
        "*":        false,

        // Default handling for blueprints
        find:       ["flashMessage", "authenticated", "isAjaxOrSocket"],
        create:     ["flashMessage", "authenticated", "isAjaxOrSocket", "isAdministrator"],
        update:     ["flashMessage", "authenticated", "isAjaxOrSocket", "isAdministrator", "addUserDataCreate"],
        destroy:    ["flashMessage", "authenticated", "isAjaxOrSocket", "isAdministrator", "addUserDataUpdate"]
    },

    // Story controller policies
    "Comment": {
        // By default do not allow nothing
        "*":        false,

        // Default handling for blueprints
        find:       ["flashMessage", "authenticated", "isAjaxOrSocket", "hasCommentObjectAccess"],
        create:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasCommentObjectCreate", "addUserDataCreate"],
        update:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasCommentObjectAdmin", "addUserDataUpdate"],
        destroy:    ["flashMessage", "authenticated", "isAjaxOrSocket", "hasCommentObjectAdmin"],

        // Custom actions
        index:      ["flashMessage", "authenticated", "isAjax", "hasCommentObjectAccess"]
    },

    // Validator controller policies
    "Validator": {
        // By default do not allow nothing
        "*":            false,

        // Custom actions
        isUnique:       ["flashMessage", "authenticated", "isAjax"],
        passwordCheck:  ["flashMessage", "authenticated", "isAjax", "hasUserAdminOrItself"]
    },

    // ExternalLink controller policies
    "ExternalLink": {
        // By default do not allow nothing
        "*":        false,

        // Default handling for blueprints
        find:       ["flashMessage", "authenticated", "isAjaxOrSocket", "hasProjectAccess"],
        create:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasProjectAdmin", "addUserDataCreate"],
        update:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasProjectAdmin", "addUserDataUpdate"],
        destroy:    ["flashMessage", "authenticated", "isAjaxOrSocket", "hasProjectAdmin"],

        // Custom actions
        list:       ["flashMessage", "authenticated", "isAjax", "hasProjectAccess"],
        add:        ["flashMessage", "authenticated", "isAjax", "hasProjectAdmin"],
        edit:       ["flashMessage", "authenticated", "isAjax", "hasProjectAccess"]
    }
};
<commit_msg>Implemented better policy checks for project external links.<commit_after>/**
 * Policies are simply Express middleware functions which run before your controllers.
 * You can apply one or more policies for a given controller or action.
 *
 * Any policy file (e.g. `authenticated.js`) can be dropped into the `/policies` folder,
 * at which point it can be accessed below by its filename, minus the extension, (e.g. `authenticated`)
 *
 * For more information on policies, check out:
 * http://sailsjs.org/#documentation
 */
module.exports.policies = {
    /**
     * By default require authentication always.
     *
     * see api/policies/authenticated.js
     */
    "*": ["flashMessage", "authenticated"],

    // whitelist the auth controller, this is used for login
    "auth": {
        "*": true,

        // Custom actions
        login:          ["flashMessage"],
        logout:         [true],
        authenticate:   [true]
    },

    // Project controller policies
    "Project": {
        // By default do not allow nothing
        "*":        false,

        // Default handling for blueprints
        find:       ["flashMessage", "authenticated", "isAjaxOrSocket"],
        create:     ["flashMessage", "authenticated", "isAjaxOrSocket", "addUserDataCreate"],
        update:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasProjectAdmin", "addUserDataUpdate"],
        destroy:    ["flashMessage", "authenticated", "isAjaxOrSocket", "hasProjectAdmin"],

        // Custom actions
        add:        ["flashMessage", "authenticated", "isAjax"],
        edit:       ["flashMessage", "authenticated", "isAjax", "hasProjectAccess"],
        backlog:    ["flashMessage", "authenticated", "isAjax", "hasProjectAccess"],
        milestones: ["flashMessage", "authenticated", "isAjax", "hasProjectAccess"],
        planning:   ["flashMessage", "authenticated", "isAjax", "hasProjectAccess"],
        statistics: ["flashMessage", "authenticated", "isAjax", "hasProjectAccess"],
        sprints:    ["flashMessage", "authenticated", "isAjax", "hasProjectAccess"]
    },

    // Phase controller policies
    "Phase": {
        // By default do not allow nothing
        "*":        false,

        // Default handling for blueprints
        find:       ["flashMessage", "authenticated", "isAjaxOrSocket"],
        create:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasPhaseAdmin", "addUserDataCreate"],
        update:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasPhaseAdmin", "addUserDataUpdate"],
        destroy:    ["flashMessage", "authenticated", "isAjaxOrSocket", "hasPhaseAdmin"],

        // Custom actions
        edit:       ["flashMessage", "authenticated", "isAjax", "hasProjectAdmin"]
    },

    // Sprint controller policies
    "Sprint": {
        // By default do not allow nothing
        "*":        false,

        // Default handling for blueprints
        find:       ["flashMessage", "authenticated", "isAjaxOrSocket", "hasSprintAccess"],
        create:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasSprintAdmin", "addUserDataCreate"],
        update:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasSprintAdmin", "addUserDataUpdate"],
        destroy:    ["flashMessage", "authenticated", "isAjaxOrSocket", "hasSprintAdmin"],

        // Custom actions
        add:            ["flashMessage", "authenticated", "isAjax", "hasSprintAdmin"],
        edit:           ["flashMessage", "authenticated", "isAjax", "hasSprintAccess"],
        backlog:        ["flashMessage", "authenticated", "isAjax", "hasSprintAccess"],
        charts:         ["flashMessage", "authenticated", "isAjax", "hasSprintAccess"],
        chartDataTasks: ["flashMessage", "authenticated", "isAjax", "hasSprintAccess"]
    },

    // Milestone controller policies
    "Milestone": {
        // By default do not allow nothing
        "*":        false,

        // Default handling for blueprints
        find:       ["flashMessage", "authenticated", "isAjaxOrSocket", "hasMilestoneAccess"],
        create:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasMilestoneAdmin", "addUserDataCreate"],
        update:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasMilestoneAdmin", "addUserDataUpdate"],
        destroy:    ["flashMessage", "authenticated", "isAjaxOrSocket", "hasMilestoneAdmin"],

        // Custom actions
        add:        ["flashMessage", "authenticated", "isAjax", "hasMilestoneAdmin"],
        edit:       ["flashMessage", "authenticated", "isAjax", "hasMilestoneAccess"],
        stories:    ["flashMessage", "authenticated", "isAjax", "hasMilestoneAccess"]
    },

    // Story controller policies
    "Story": {
        // By default do not allow nothing
        "*":        false,

        // Default handling for blueprints
        find:       ["flashMessage", "authenticated", "isAjaxOrSocket", "hasStoryAccess"],
        create:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasStoryAdmin", "addUserDataCreate"],
        update:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasStoryAdmin", "addUserDataUpdate"],
        destroy:    ["flashMessage", "authenticated", "isAjaxOrSocket", "hasStoryAdmin"],

        // Custom actions
        add:        ["flashMessage", "authenticated", "isAjax", "hasStoryAdmin"],
        split:      ["flashMessage", "authenticated", "isAjax", "hasStoryAdmin"],
        edit:       ["flashMessage", "authenticated", "isAjax", "hasStoryAccess"],
        tasks:      ["flashMessage", "authenticated", "isAjax", "hasStoryAccess"]
    },

    // Task controller policies
    "Task": {
        // By default do not allow nothing
        "*":        false,

        // Default handling for blueprints
        find:       ["flashMessage", "authenticated", "isAjaxOrSocket", "hasTaskAccess"],
        create:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasTaskAdmin", "addUserDataCreate"],
        update:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasTaskAdmin", "addUserDataUpdate"],
        destroy:    ["flashMessage", "authenticated", "isAjaxOrSocket", "hasTaskAdmin"],

        // Custom actions
        add:        ["flashMessage", "authenticated", "isAjax", "hasTaskAdmin"],
        edit:       ["flashMessage", "authenticated", "isAjax", "hasTaskAccess"],
        statistics: ["flashMessage", "authenticated", "isAjax", "hasTaskAccess"],
        releaseTask:["flashMessage", "authenticated", "isAjax", "hasTaskAccess"],
        takeTask:   ["flashMessage", "authenticated", "isAjax", "hasTaskAccess"]
    },

    // User controller policies
    "User": {
        // By default do not allow nothing
        "*":        false,

        // Default handling for blueprints
        find:       ["flashMessage", "authenticated", "isAjaxOrSocket"],
        create:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasUserAdmin", "addUserDataCreate"],
        update:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasUserAdminOrItself", "addUserDataUpdate"],
        destroy:    ["flashMessage", "authenticated", "isAjaxOrSocket", "hasUserAdmin"],

        // Custom actions
        list:           ["flashMessage", "authenticated", "isAjax", "hasUserAdmin"],
        add:            ["flashMessage", "authenticated", "isAjax", "hasUserAdmin"],
        edit:           ["flashMessage", "authenticated", "isAjax", "hasUserAdminOrItself"],
        history:        ["flashMessage", "authenticated", "isAjax", "hasUserAdminOrItself"],
        projects:       ["flashMessage", "authenticated", "isAjax", "hasUserAdminOrItself"],
        changePassword: ["flashMessage", "authenticated", "isAjax", "hasUserAdminOrItself"]
    },

    // ProjectUser controller policies
    "ProjectUser": {
        // By default do not allow nothing
        "*":        false,

        // Default handling for blueprints
        find:       ["flashMessage", "authenticated", "isAjaxOrSocket"],
        create:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasProjectAdmin", "addUserDataCreate"],
        update:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasProjectAdmin", "addUserDataUpdate"],
        destroy:    ["flashMessage", "authenticated", "isAjaxOrSocket", "hasProjectAdmin"],

        // Custom actions
        users:          ["flashMessage", "authenticated", "isAjax", "hasProjectAccess"],
        availableUsers: ["flashMessage", "authenticated", "isAjaxOrSocket", "hasProjectAccess"],
        ownProjects:    ["flashMessage", "authenticated", "isAjaxOrSocket"],
        getRole:        ["flashMessage", "authenticated", "isAjaxOrSocket", "hasProjectAccess"]
    },

    // Type controller policies
    "Type": {
        // By default do not allow nothing
        "*":        false,

        // Default handling for blueprints
        find:       ["flashMessage", "authenticated", "isAjaxOrSocket"],
        create:     ["flashMessage", "authenticated", "isAjaxOrSocket", "isAdministrator"],
        update:     ["flashMessage", "authenticated", "isAjaxOrSocket", "isAdministrator", "addUserDataCreate"],
        destroy:    ["flashMessage", "authenticated", "isAjaxOrSocket", "isAdministrator", "addUserDataUpdate"]
    },

    // Story controller policies
    "Comment": {
        // By default do not allow nothing
        "*":        false,

        // Default handling for blueprints
        find:       ["flashMessage", "authenticated", "isAjaxOrSocket", "hasCommentObjectAccess"],
        create:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasCommentObjectCreate", "addUserDataCreate"],
        update:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasCommentObjectAdmin", "addUserDataUpdate"],
        destroy:    ["flashMessage", "authenticated", "isAjaxOrSocket", "hasCommentObjectAdmin"],

        // Custom actions
        index:      ["flashMessage", "authenticated", "isAjax", "hasCommentObjectAccess"]
    },

    // Validator controller policies
    "Validator": {
        // By default do not allow nothing
        "*":            false,

        // Custom actions
        isUnique:       ["flashMessage", "authenticated", "isAjax"],
        passwordCheck:  ["flashMessage", "authenticated", "isAjax", "hasUserAdminOrItself"]
    },

    // ExternalLink controller policies
    "ExternalLink": {
        // By default do not allow nothing
        "*":        false,

        // Default handling for blueprints
        find:       ["flashMessage", "authenticated", "isAjaxOrSocket", "hasExternalLinkAccess"],
        create:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasExternalLinkAdmin", "addUserDataCreate"],
        update:     ["flashMessage", "authenticated", "isAjaxOrSocket", "hasExternalLinkAdmin", "addUserDataUpdate"],
        destroy:    ["flashMessage", "authenticated", "isAjaxOrSocket", "hasExternalLinkAdmin"],

        // Custom actions
        list:       ["flashMessage", "authenticated", "isAjax", "hasExternalLinkAccess"],
        add:        ["flashMessage", "authenticated", "isAjax", "hasExternalLinkAdmin"],
        edit:       ["flashMessage", "authenticated", "isAjax", "hasExternalLinkAccess"]
    }
};
<|endoftext|>


<commit_before>import WebMiddle, { PropTypes } from 'webmiddle';
import phantom from 'phantom';

// promise-based implementation of http://stackoverflow.com/a/19070446
function waitForFn(config) {
  config._start = config._start || new Date();

  if (config.timeout && new Date - config._start > config.timeout) {
    const errorMessage = 'waitForFn timedout ' + (new Date - config._start) + 'ms';
    if (config.debug) console.log(errorMessage);
    return Promise.reject(errorMessage);
  }

  return config.check().then(result => {
    if (result) {
      if (config.debug) console.log('waitForFn success ' + (new Date - config._start) + 'ms');
      return result;
    }

    return new Promise((resolve, reject) => {
      setTimeout(() => {
        waitForFn(config).then(resolve, reject);
      }, config.interval || 0);
    });
  });
}

const Browser =
({ name, contentType, url, method = 'GET', body = {}, httpHeaders = {}, cookies = {}, waitFor }) => {
  // TODO: cookies
  // todo: body to query string (if is object)
  return new Promise((resolve, reject) => {
    let sitepage = null;
    let phInstance = null;
    const pageResponses = {};
    let finalUrl = url;

    phantom.create()
    .then(instance => {
      phInstance = instance;
      return instance.createPage();
    })
    .then(page => {
      sitepage = page;

      page.customHeaders = httpHeaders;

      // track final response (after redirects)
      // https://github.com/ariya/phantomjs/issues/10185#issuecomment-38856203
      page.on('onResourceReceived', (response) => {
        pageResponses[response.url] = response;
      });
      page.on('onUrlChanged', (targetUrl) => {
        finalUrl = targetUrl;
      });

      return page.open(url, method, body);
    })
    .then(status => {
      const pageResponse = pageResponses[finalUrl];

      if (status !== 'success' || pageResponse.status !== 200) {
        throw pageResponse;
      }

      if (waitFor) {
        return waitForFn({
          debug: true,  // optional
          interval: 0,  // optional
          timeout: 10000,  // optional
          check: () => sitepage.evaluate((waitFor) =>
            document.querySelector(waitFor) !== null
          , waitFor),
        });
      }
    })
    .then(() => sitepage.property('content'))
    .then(content => {
      sitepage.close();
      phInstance.exit();

      resolve({ name, contentType, content });
    })
    .catch(error => {
      phInstance.exit();
      reject(error);
    });
  });
};

Browser.propTypes = {
  name: PropTypes.string.isRequired,
  contentType: PropTypes.string.isRequired,
  url: PropTypes.string.isRequired,
  method: PropTypes.string,
  body: PropTypes.oneOfType([
    PropTypes.object,
    PropTypes.string,
  ]),
  httpHeaders: PropTypes.object,
  cookies: PropTypes.object,
  waitFor: PropTypes.string,
};

export default Browser;
<commit_msg>Fix custom headers passing<commit_after>import WebMiddle, { PropTypes } from 'webmiddle';
import phantom from 'phantom';

// promise-based implementation of http://stackoverflow.com/a/19070446
function waitForFn(config) {
  config._start = config._start || new Date();

  if (config.timeout && new Date - config._start > config.timeout) {
    const errorMessage = 'waitForFn timedout ' + (new Date - config._start) + 'ms';
    if (config.debug) console.log(errorMessage);
    return Promise.reject(errorMessage);
  }

  return config.check().then(result => {
    if (result) {
      if (config.debug) console.log('waitForFn success ' + (new Date - config._start) + 'ms');
      return result;
    }

    return new Promise((resolve, reject) => {
      setTimeout(() => {
        waitForFn(config).then(resolve, reject);
      }, config.interval || 0);
    });
  });
}

const Browser =
({ name, contentType, url, method = 'GET', body = {}, httpHeaders = {}, cookies = {}, waitFor }) => {
  // TODO: cookies
  return new Promise((resolve, reject) => {
    let sitepage = null;
    let phInstance = null;
    const pageResponses = {};
    let finalUrl = url;

    if (typeof body === 'object' && body !== null) {
      // TODO: body to string (form string or stringified json data etc. depending on "Content-Type" header)
    }

    phantom.create()
    .then(instance => {
      phInstance = instance;
      return instance.createPage();
    })
    .then(page => {
      sitepage = page;

      //page.customHeaders = httpHeaders; // TODO: doesn't seem to work

      // track final response (after redirects)
      // https://github.com/ariya/phantomjs/issues/10185#issuecomment-38856203
      page.on('onResourceReceived', (response) => {
        pageResponses[response.url] = response;
      });
      page.on('onUrlChanged', (targetUrl) => {
        finalUrl = targetUrl;
      });

      // TODO: are httpHeaders still sent in case of redirect?
      const settings = {
        operation: method,
        headers: httpHeaders,
        data: body,
      };

      return page.open(url, settings);
    })
    .then(status => {
      const pageResponse = pageResponses[finalUrl];

      if (status !== 'success' || pageResponse.status !== 200) {
        throw pageResponse;
      }

      if (waitFor) {
        return waitForFn({
          debug: true,  // optional
          interval: 0,  // optional
          timeout: 10000,  // optional
          check: () => sitepage.evaluate((waitFor) =>
            document.querySelector(waitFor) !== null
          , waitFor),
        });
      }
    })
    .then(() => sitepage.property('content'))
    .then(content => {
      sitepage.close();
      phInstance.exit();

      resolve({ name, contentType, content });
    })
    .catch(error => {
      phInstance.exit();
      reject(error);
    });
  });
};

Browser.propTypes = {
  name: PropTypes.string.isRequired,
  contentType: PropTypes.string.isRequired,
  url: PropTypes.string.isRequired,
  method: PropTypes.string,
  body: PropTypes.oneOfType([
    PropTypes.object,
    PropTypes.string,
  ]),
  httpHeaders: PropTypes.object,
  cookies: PropTypes.object,
  waitFor: PropTypes.string,
};

export default Browser;
<|endoftext|>


<commit_before>import _, { FormattedDuration } from 'intl'
import addSubscriptions from 'add-subscriptions'
import Icon from 'icon'
import NoObjects from 'no-objects'
import React from 'react'
import SortedTable from 'sorted-table'
import { alert } from 'modal'
import { Card, CardHeader, CardBlock } from 'card'
import { keyBy } from 'lodash'
import { FormattedDate } from 'react-intl'
import { get } from 'xo-defined'
import {
  deleteJobsLogs,
  subscribeBackupNgJobs,
  subscribeBackupNgLogs,
} from 'xo'

import LogAlertBody from './log-alert-body'

const STATUS_LABELS = {
  failure: {
    className: 'danger',
    label: 'jobFailed',
  },
  skipped: {
    className: 'info',
    label: 'jobSkipped',
  },
  success: {
    className: 'success',
    label: 'jobSuccess',
  },
  pending: {
    className: 'warning',
    label: 'jobStarted',
  },
  interrupted: {
    className: 'danger',
    label: 'jobInterrupted',
  },
}

const LOG_COLUMNS = [
  {
    name: _('jobId'),
    itemRenderer: log => log.jobId.slice(4, 8),
    sortCriteria: log => log.jobId,
  },
  {
    name: _('jobMode'),
    itemRenderer: log => get(() => log.data.mode),
    sortCriteria: log => get(() => log.data.mode),
  },
  {
    name: _('jobName'),
    itemRenderer: (log, { jobs }) => get(() => jobs[log.jobId].name),
    sortCriteria: (log, { jobs }) => get(() => jobs[log.jobId].name),
  },
  {
    name: _('jobStart'),
    itemRenderer: log => (
      <FormattedDate
        value={new Date(log.start)}
        month='short'
        day='numeric'
        year='numeric'
        hour='2-digit'
        minute='2-digit'
        second='2-digit'
      />
    ),
    sortCriteria: log => log.start,
    sortOrder: 'desc',
  },
  {
    default: true,
    name: _('jobEnd'),
    itemRenderer: log =>
      log.end !== undefined && (
        <FormattedDate
          value={new Date(log.end)}
          month='short'
          day='numeric'
          year='numeric'
          hour='2-digit'
          minute='2-digit'
          second='2-digit'
        />
      ),
    sortCriteria: log => log.end || log.start,
    sortOrder: 'desc',
  },
  {
    name: _('jobDuration'),
    itemRenderer: log =>
      log.end !== undefined && (
        <FormattedDuration duration={log.end - log.start} />
      ),
    sortCriteria: log => log.end - log.start,
  },
  {
    name: _('jobStatus'),
    itemRenderer: log => {
      const { className, label } = STATUS_LABELS[log.status]
      return <span className={`tag tag-${className}`}>{_(label)}
    },
  },
]

const showTasks = log =>
  alert(

      {_('jobModalTitle', { job: log.jobId.slice(4, 8) })}{' '}

        {log.id}

    ,
    <LogAlertBody id={log.id} />
  )

const LOG_INDIVIDUAL_ACTIONS = [
  {
    handler: showTasks,
    icon: 'preview',
    label: _('logDisplayDetails'),
  },
]

const LOG_ACTIONS = [
  {
    handler: deleteJobsLogs,
    icon: 'delete',
    label: _('remove'),
  },
]

const LOG_FILTERS = {
  jobFailed: 'status: failure',
  jobInterrupted: 'status: interrupted',
  jobSkipped: 'status: skipped',
  jobStarted: 'status: started',
  jobSuccess: 'status: success',
}

export default [
  addSubscriptions({
    logs: subscribeBackupNgLogs,
    jobs: cb => subscribeBackupNgJobs(jobs => cb(keyBy(jobs, 'id'))),
  }),
  ({ logs, jobs }) => (
    <Card>
      <CardHeader>
        <Icon icon='log' /> {_('logTitle')}
      </CardHeader>
      <CardBlock>
        <NoObjects
          actions={LOG_ACTIONS}
          collection={logs}
          columns={LOG_COLUMNS}
          component={SortedTable}
          data-jobs={jobs}
          emptyMessage={_('noLogs')}
          filters={LOG_FILTERS}
          individualActions={LOG_INDIVIDUAL_ACTIONS}
        />
      </CardBlock>
    </Card>
  ),
].reduceRight((value, decorator) => decorator(value))
<commit_msg>feat(xo-web/backup-ng/logs): remove Mode column (#3116)<commit_after>import _, { FormattedDuration } from 'intl'
import addSubscriptions from 'add-subscriptions'
import Icon from 'icon'
import NoObjects from 'no-objects'
import React from 'react'
import SortedTable from 'sorted-table'
import { alert } from 'modal'
import { Card, CardHeader, CardBlock } from 'card'
import { keyBy } from 'lodash'
import { FormattedDate } from 'react-intl'
import { get } from 'xo-defined'
import {
  deleteJobsLogs,
  subscribeBackupNgJobs,
  subscribeBackupNgLogs,
} from 'xo'

import LogAlertBody from './log-alert-body'

const STATUS_LABELS = {
  failure: {
    className: 'danger',
    label: 'jobFailed',
  },
  skipped: {
    className: 'info',
    label: 'jobSkipped',
  },
  success: {
    className: 'success',
    label: 'jobSuccess',
  },
  pending: {
    className: 'warning',
    label: 'jobStarted',
  },
  interrupted: {
    className: 'danger',
    label: 'jobInterrupted',
  },
}

const LOG_COLUMNS = [
  {
    name: _('jobId'),
    itemRenderer: log => log.jobId.slice(4, 8),
    sortCriteria: log => log.jobId,
  },
  {
    name: _('jobName'),
    itemRenderer: (log, { jobs }) => get(() => jobs[log.jobId].name),
    sortCriteria: (log, { jobs }) => get(() => jobs[log.jobId].name),
  },
  {
    name: _('jobStart'),
    itemRenderer: log => (
      <FormattedDate
        value={new Date(log.start)}
        month='short'
        day='numeric'
        year='numeric'
        hour='2-digit'
        minute='2-digit'
        second='2-digit'
      />
    ),
    sortCriteria: log => log.start,
    sortOrder: 'desc',
  },
  {
    default: true,
    name: _('jobEnd'),
    itemRenderer: log =>
      log.end !== undefined && (
        <FormattedDate
          value={new Date(log.end)}
          month='short'
          day='numeric'
          year='numeric'
          hour='2-digit'
          minute='2-digit'
          second='2-digit'
        />
      ),
    sortCriteria: log => log.end || log.start,
    sortOrder: 'desc',
  },
  {
    name: _('jobDuration'),
    itemRenderer: log =>
      log.end !== undefined && (
        <FormattedDuration duration={log.end - log.start} />
      ),
    sortCriteria: log => log.end - log.start,
  },
  {
    name: _('jobStatus'),
    itemRenderer: log => {
      const { className, label } = STATUS_LABELS[log.status]
      return <span className={`tag tag-${className}`}>{_(label)}
    },
  },
]

const showTasks = log =>
  alert(

      {_('jobModalTitle', { job: log.jobId.slice(4, 8) })}{' '}

        {log.id}

    ,
    <LogAlertBody id={log.id} />
  )

const LOG_INDIVIDUAL_ACTIONS = [
  {
    handler: showTasks,
    icon: 'preview',
    label: _('logDisplayDetails'),
  },
]

const LOG_ACTIONS = [
  {
    handler: deleteJobsLogs,
    icon: 'delete',
    label: _('remove'),
  },
]

const LOG_FILTERS = {
  jobFailed: 'status: failure',
  jobInterrupted: 'status: interrupted',
  jobSkipped: 'status: skipped',
  jobStarted: 'status: started',
  jobSuccess: 'status: success',
}

export default [
  addSubscriptions({
    logs: subscribeBackupNgLogs,
    jobs: cb => subscribeBackupNgJobs(jobs => cb(keyBy(jobs, 'id'))),
  }),
  ({ logs, jobs }) => (
    <Card>
      <CardHeader>
        <Icon icon='log' /> {_('logTitle')}
      </CardHeader>
      <CardBlock>
        <NoObjects
          actions={LOG_ACTIONS}
          collection={logs}
          columns={LOG_COLUMNS}
          component={SortedTable}
          data-jobs={jobs}
          emptyMessage={_('noLogs')}
          filters={LOG_FILTERS}
          individualActions={LOG_INDIVIDUAL_ACTIONS}
        />
      </CardBlock>
    </Card>
  ),
].reduceRight((value, decorator) => decorator(value))
<|endoftext|>


<commit_before>'use strict';

var extend = require('extend');
var globule = require('globule');
var path = require('path');
var rump = require('rump');
var webpack = require('webpack');
var CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin;
var DefinePlugin = webpack.DefinePlugin;
var DescPlugin = webpack.ResolverPlugin.DirectoryDescriptionFilePlugin;
var ResolverPlugin = webpack.ResolverPlugin;
var UglifyJsPlugin = webpack.optimize.UglifyJsPlugin;
var OccurrenceOrderPlugin = webpack.optimize.OccurrenceOrderPlugin;

module.exports = function() {
  var commonsChunk = 'common';
  var commonsFile;
  var sourceDir = path.join(rump.configs.main.paths.source.root,
                            rump.configs.main.paths.source.scripts);
  var destination = path.join(rump.configs.main.paths.destination.root,
                              rump.configs.main.paths.destination.scripts);
  var options = {
    entry: entries(),
    output: {
      path: destination,
      filename: '[name].js'
    },
    plugins: [
      new ResolverPlugin(new DescPlugin('bower.json', ['main'])),
      new DefinePlugin(rump.configs.main.scripts.macros)
    ],
    resolve: {
      alias: rump.configs.main.scripts.aliases,
      modulesDirectories: ['node_modules', 'bower_components'],
      root: path.resolve(sourceDir)
    },
    watchDelay: 200
  };

  if(rump.configs.main.environment === 'development') {
    options.debug = true;
    options.devtool = 'inline-source-map';
    options.output.devtoolModuleFilenameTemplate = '[absolute-resource-path]';
  }
  else if(rump.configs.main.environment === 'production') {
    options.plugins = options.plugins || [];
    options.plugins.push(new UglifyJsPlugin(rump.configs.main.scripts.uglify));
    options.plugins.push(new OccurrenceOrderPlugin());
  }

  if(rump.configs.main.scripts.common) {
    if(typeof rump.configs.main.scripts.common === 'string') {
      commonsChunk = rump.configs.main.scripts.common;
    }
    commonsFile = commonsChunk + '.js';
    options.plugins = options.plugins || [];
    options.plugins.push(new CommonsChunkPlugin(commonsChunk, commonsFile));
  }

  if(rump.configs.main.scripts.library) {
    options.output.libraryTarget = 'umd';
  }

  return extend(true, options, rump.configs.main.scripts.webpack);
};

function entries() {
  var source = path.join(rump.configs.main.paths.source.root,
                         rump.configs.main.paths.source.scripts,
                         rump.configs.main.globs.build.scripts);

  return globule
  .find([source].concat(rump.configs.main.globs.global))
  .map(function(filename) {
    return path.basename(filename);
  })
  .reduce(function(obj, filename) {
    obj[path.basename(filename, path.extname(filename))] = filename;
    return obj;
  }, {});
}
<commit_msg>Add loaders option for easy access by plugins<commit_after>'use strict';

var extend = require('extend');
var globule = require('globule');
var path = require('path');
var rump = require('rump');
var webpack = require('webpack');
var CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin;
var DefinePlugin = webpack.DefinePlugin;
var DescPlugin = webpack.ResolverPlugin.DirectoryDescriptionFilePlugin;
var ResolverPlugin = webpack.ResolverPlugin;
var UglifyJsPlugin = webpack.optimize.UglifyJsPlugin;
var OccurrenceOrderPlugin = webpack.optimize.OccurrenceOrderPlugin;

module.exports = function() {
  var commonsChunk = 'common';
  var commonsFile;
  var sourceDir = path.join(rump.configs.main.paths.source.root,
                            rump.configs.main.paths.source.scripts);
  var destination = path.join(rump.configs.main.paths.destination.root,
                              rump.configs.main.paths.destination.scripts);
  var options = {
    entry: entries(),
    module: {
      loaders: []
    },
    output: {
      path: destination,
      filename: '[name].js'
    },
    plugins: [
      new ResolverPlugin(new DescPlugin('bower.json', ['main'])),
      new DefinePlugin(rump.configs.main.scripts.macros)
    ],
    resolve: {
      alias: rump.configs.main.scripts.aliases,
      modulesDirectories: ['node_modules', 'bower_components'],
      root: path.resolve(sourceDir)
    },
    watchDelay: 200
  };

  if(rump.configs.main.environment === 'development') {
    options.debug = true;
    options.devtool = 'inline-source-map';
    options.output.devtoolModuleFilenameTemplate = '[absolute-resource-path]';
  }
  else if(rump.configs.main.environment === 'production') {
    options.plugins = options.plugins || [];
    options.plugins.push(new UglifyJsPlugin(rump.configs.main.scripts.uglify));
    options.plugins.push(new OccurrenceOrderPlugin());
  }

  if(rump.configs.main.scripts.common) {
    if(typeof rump.configs.main.scripts.common === 'string') {
      commonsChunk = rump.configs.main.scripts.common;
    }
    commonsFile = commonsChunk + '.js';
    options.plugins = options.plugins || [];
    options.plugins.push(new CommonsChunkPlugin(commonsChunk, commonsFile));
  }

  if(rump.configs.main.scripts.library) {
    options.output.libraryTarget = 'umd';
  }

  return extend(true, options, rump.configs.main.scripts.webpack);
};

function entries() {
  var source = path.join(rump.configs.main.paths.source.root,
                         rump.configs.main.paths.source.scripts,
                         rump.configs.main.globs.build.scripts);

  return globule
  .find([source].concat(rump.configs.main.globs.global))
  .map(function(filename) {
    return path.basename(filename);
  })
  .reduce(function(obj, filename) {
    obj[path.basename(filename, path.extname(filename))] = filename;
    return obj;
  }, {});
}
<|endoftext|>