mirror of
https://github.com/fleetdm/fleet
synced 2026-05-24 09:28:54 +00:00
199 lines
5.8 KiB
React
199 lines
5.8 KiB
React
|
|
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 (
|
||
|
|
<div className={`body-wrap ${baseClass}`}>
|
||
|
|
<div className={`${baseClass}__header-wrapper`}>
|
||
|
|
<div>
|
||
|
|
<h1>Manage Additional Osquery Options</h1>
|
||
|
|
<p>
|
||
|
|
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.
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
<div className={`${baseClass}__btn-wrapper`}>
|
||
|
|
<Button block className={`${baseClass}__reset-btn`} onClick={onResetConfigOptions} variant="inverse">
|
||
|
|
RESET TO DEFAULT
|
||
|
|
</Button>
|
||
|
|
<Button block className={`${baseClass}__save-btn`} onClick={onSave} variant="brand">
|
||
|
|
SAVE OPTIONS
|
||
|
|
</Button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<ConfigOptionsForm
|
||
|
|
configNameOptions={helpers.configOptionDropdownOptions(configOptions)}
|
||
|
|
completedOptions={availableOptions}
|
||
|
|
errors={configOptionErrors}
|
||
|
|
onFormUpdate={onOptionUpdate}
|
||
|
|
onRemoveOption={onRemoveOption}
|
||
|
|
/>
|
||
|
|
<Button onClick={onAddNewOption} variant="unstyled">Add New Option</Button>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const mapStateToProps = (state) => {
|
||
|
|
const { entities: configOptions } = entityGetter(state).get('config_options');
|
||
|
|
|
||
|
|
return { configOptions };
|
||
|
|
};
|
||
|
|
|
||
|
|
export default connect(mapStateToProps)(ConfigOptionsPage);
|