import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { push } from 'react-router-redux';
import { first, isEqual, values } from 'lodash';
import classnames from 'classnames';
import Kolide from 'kolide';
import campaignActions from 'redux/nodes/entities/campaigns/actions';
import campaignInterface from 'interfaces/campaign';
import debounce from 'utilities/debounce';
import entityGetter from 'redux/utilities/entityGetter';
import { formatSelectedTargetsForApi } from 'kolide/helpers';
import QueryForm from 'components/forms/queries/QueryForm';
import osqueryTableInterface from 'interfaces/osquery_table';
import queryActions from 'redux/nodes/entities/queries/actions';
import queryInterface from 'interfaces/query';
import QueryResultsTable from 'components/queries/QueryResultsTable';
import QuerySidePanel from 'components/side_panels/QuerySidePanel';
import { renderFlash } from 'redux/nodes/notifications/actions';
import { selectOsqueryTable, setSelectedTargets, setSelectedTargetsQuery } from 'redux/nodes/components/QueryPages/actions';
import targetInterface from 'interfaces/target';
import validateQuery from 'components/forms/validators/validate_query';
import Spinner from 'components/loaders/Spinner';
const baseClass = 'query-page';
class QueryPage extends Component {
static propTypes = {
campaign: campaignInterface,
dispatch: PropTypes.func,
errors: PropTypes.shape({
base: PropTypes.string,
}),
query: queryInterface,
selectedOsqueryTable: osqueryTableInterface,
selectedTargets: PropTypes.arrayOf(targetInterface),
};
constructor (props) {
super(props);
this.state = {
queryIsRunning: false,
targetsCount: 0,
};
}
componentWillUnmount () {
const { destroyCampaign, removeSocket } = this;
removeSocket();
destroyCampaign();
return false;
}
onFetchTargets = (query, targetResponse) => {
const { dispatch } = this.props;
const {
targets_count: targetsCount,
} = targetResponse;
dispatch(setSelectedTargetsQuery(query));
this.setState({ targetsCount });
return false;
}
onOsqueryTableSelect = (tableName) => {
const { dispatch } = this.props;
dispatch(selectOsqueryTable(tableName));
return false;
}
onRunQuery = debounce((queryText) => {
const { dispatch, selectedTargets } = this.props;
const { error } = validateQuery(queryText);
if (error) {
dispatch(renderFlash('error', error));
return false;
}
const { create, update } = campaignActions;
const { destroyCampaign, removeSocket } = this;
const selected = formatSelectedTargetsForApi(selectedTargets);
removeSocket();
destroyCampaign();
dispatch(create({ query: queryText, selected }))
.then((campaignResponse) => {
return Kolide.runQueryWebsocket(campaignResponse.id)
.then((socket) => {
this.campaign = campaignResponse;
this.socket = socket;
this.setState({ queryIsRunning: true });
this.socket.onmessage = ({ data }) => {
const socketData = JSON.parse(data);
const { previousSocketData } = this;
if (previousSocketData && isEqual(socketData, previousSocketData)) {
this.previousSocketData = socketData;
return false;
}
return dispatch(update(this.campaign, socketData))
.then((updatedCampaign) => {
this.previousSocketData = socketData;
this.campaign = updatedCampaign;
});
};
});
})
.catch((campaignError) => {
if (campaignError === 'resource already created') {
dispatch(renderFlash('error', 'A campaign with the provided query text has already been created'));
return false;
}
dispatch(renderFlash('error', campaignError));
return false;
});
return false;
})
onSaveQueryFormSubmit = debounce((formData) => {
const { dispatch } = this.props;
const { error } = validateQuery(formData.query);
if (error) {
dispatch(renderFlash('error', error));
return false;
}
return dispatch(queryActions.create(formData))
.then((query) => {
dispatch(push(`/queries/${query.id}`));
dispatch(renderFlash('success', 'Query created'));
})
.catch(() => false);
})
onStopQuery = (evt) => {
evt.preventDefault();
const { removeSocket } = this;
this.setState({ queryIsRunning: false });
return removeSocket();
}
onTargetSelect = (selectedTargets) => {
const { dispatch } = this.props;
dispatch(setSelectedTargets(selectedTargets));
return false;
}
onUpdateQuery = (formData) => {
const { dispatch, query } = this.props;
dispatch(queryActions.update(query, formData))
.then(() => {
dispatch(renderFlash('success', 'Query updated!'));
});
return false;
};
destroyCampaign = () => {
const { campaign, dispatch } = this.props;
const { destroy } = campaignActions;
if (campaign) {
this.campaign = null;
dispatch(destroy(campaign));
}
return false;
}
removeSocket = () => {
if (this.socket) {
this.socket.close();
this.socket = null;
this.previousSocketData = null;
}
return false;
}
renderResultsTable = () => {
const { campaign } = this.props;
const resultsClasses = classnames(`${baseClass}__results`, 'body-wrap', {
[`${baseClass}__results--loading`]: this.socket && !campaign.query_results,
});
let resultBody = '';
if (!campaign || (!this.socket && !campaign.query_results)) {
return false;
}
if ((!campaign.query_results || campaign.query_results.length < 1) && this.socket) {
resultBody =