import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; import { differenceWith, find, filter, isEqual, noop } from 'lodash'; import Button from 'components/buttons/Button'; import configOptionActions from 'redux/nodes/entities/config_options/actions'; import ConfigOptionsForm from 'components/forms/ConfigOptionsForm'; import configOptionInterface from 'interfaces/config_option'; import debounce from 'utilities/debounce'; import defaultConfigOptions from 'pages/config/ConfigOptionsPage/default_config_options'; import entityGetter from 'redux/utilities/entityGetter'; import helpers from 'pages/config/ConfigOptionsPage/helpers'; import { renderFlash } from 'redux/nodes/notifications/actions'; const baseClass = 'config-options-page'; const DEFAULT_CONFIG_OPTION = { name: '', value: '' }; export class ConfigOptionsPage extends Component { static propTypes = { configOptions: PropTypes.arrayOf(configOptionInterface), dispatch: PropTypes.func.isRequired, }; static defaultProps = { configOptions: [], dispatch: noop, }; constructor (props) { super(props); this.state = { configOptions: [], configOptionErrors: {}, }; } componentWillMount () { const { configOptions, dispatch } = this.props; this.setState({ configOptions }); dispatch(configOptionActions.loadAll()); return false; } componentWillReceiveProps ({ configOptions }) { if (!isEqual(configOptions, this.state.configOptions)) { this.setState({ configOptions }); } return false; } onAddNewOption = (evt) => { evt.preventDefault(); const { configOptions } = this.state; if (find(configOptions, DEFAULT_CONFIG_OPTION)) { return false; } this.setState({ configOptions: [ ...configOptions, DEFAULT_CONFIG_OPTION, ], }); return false; } onOptionUpdate = (oldOption, newOption) => { const { configOptions } = this.state; const newConfigOptions = helpers.updatedConfigOptions({ oldOption, newOption, configOptions }); this.setState({ configOptions: newConfigOptions }); return false; } onRemoveOption = (option) => { const { configOptions } = this.state; const configOptionsWithoutRemovedOption = filter(configOptions, o => !isEqual(o, option)); if (isEqual(option, DEFAULT_CONFIG_OPTION)) { this.setState({ configOptions: configOptionsWithoutRemovedOption }); } else { this.setState({ configOptions: [ ...configOptionsWithoutRemovedOption, { ...option, value: null }, ], }); } return false; } onResetConfigOptions = () => { this.setState({ configOptions: defaultConfigOptions }); return false; } onSave = debounce(() => { const { dispatch } = this.props; const changedOptions = this.calculateChangedOptions(); const { errors, valid } = this.validate(); if (!changedOptions.length) { return false; } if (!valid) { this.setState({ configOptionErrors: errors }); return false; } const formattedChangedOptions = helpers.formatOptionsForServer(changedOptions); dispatch(configOptionActions.update(formattedChangedOptions)) .then(() => { dispatch(renderFlash('success', 'Options updated!')); return false; }) .catch(() => { dispatch(renderFlash('error', 'We were unable to update your config options')); return false; }); return false; }) calculateChangedOptions = () => { const { configOptions: stateConfigOptions } = this.state; const { configOptions: propConfigOptions } = this.props; const presentStateConfigOptions = filter(stateConfigOptions, o => o.name); return differenceWith(presentStateConfigOptions, propConfigOptions, isEqual); } validate = () => { const { configOptions: allConfigOptions } = this.state; const changedConfigOptions = this.calculateChangedOptions(); return helpers.configErrorsFor(changedConfigOptions, allConfigOptions); } render () { const { configOptionErrors, configOptions } = this.state; const { onAddNewOption, onOptionUpdate, onRemoveOption, onResetConfigOptions, onSave } = this; const availableOptions = filter(configOptions, option => option.value !== null); return (
Osquery allows you to set a number of configuration options (Osquery Documentation). Since Kolide manages your Osquery configuration, you can set these additional desired options on this screen. Some options that Kolide needs to function correctly will be ignored.