diff --git a/frontend/components/forms/packs/EditPackForm/EditPackForm.jsx b/frontend/components/forms/packs/EditPackForm/EditPackForm.jsx
index 871ad3d3d0..c9ff8e3281 100644
--- a/frontend/components/forms/packs/EditPackForm/EditPackForm.jsx
+++ b/frontend/components/forms/packs/EditPackForm/EditPackForm.jsx
@@ -4,8 +4,9 @@ import Button from 'components/buttons/Button';
import Form from 'components/forms/Form';
import formFieldInterface from 'interfaces/form_field';
import InputField from 'components/forms/fields/InputField';
+import SelectTargetsDropdown from 'components/forms/fields/SelectTargetsDropdown';
-const fieldNames = ['description', 'name'];
+const fieldNames = ['description', 'name', 'targets'];
class EditPackForm extends Component {
static propTypes = {
@@ -13,13 +14,23 @@ class EditPackForm extends Component {
fields: PropTypes.shape({
description: formFieldInterface.isRequired,
name: formFieldInterface.isRequired,
+ targets: formFieldInterface.isRequired,
}).isRequired,
handleSubmit: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired,
+ onFetchTargets: PropTypes.func,
+ targetsCount: PropTypes.number,
};
render () {
- const { className, fields, handleSubmit, onCancel } = this.props;
+ const {
+ className,
+ fields,
+ handleSubmit,
+ onCancel,
+ onFetchTargets,
+ targetsCount,
+ } = this.props;
return (
diff --git a/frontend/components/packs/EditPackFormWrapper/EditPackFormWrapper.jsx b/frontend/components/packs/EditPackFormWrapper/EditPackFormWrapper.jsx
index 8bae9df2a5..f9b3c7ef8b 100644
--- a/frontend/components/packs/EditPackFormWrapper/EditPackFormWrapper.jsx
+++ b/frontend/components/packs/EditPackFormWrapper/EditPackFormWrapper.jsx
@@ -6,6 +6,7 @@ import EditPackForm from 'components/forms/packs/EditPackForm';
import Icon from 'components/icons/Icon';
import packInterface from 'interfaces/pack';
import SelectTargetsDropdown from 'components/forms/fields/SelectTargetsDropdown';
+import targetInterface from 'interfaces/target';
const baseClass = 'edit-pack-form';
@@ -18,6 +19,7 @@ class EditPackFormWrapper extends Component {
onEditPack: PropTypes.func.isRequired,
onFetchTargets: PropTypes.func,
pack: packInterface.isRequired,
+ packTargets: PropTypes.arrayOf(targetInterface),
targetsCount: PropTypes.number,
};
@@ -30,6 +32,7 @@ class EditPackFormWrapper extends Component {
onEditPack,
onFetchTargets,
pack,
+ packTargets,
targetsCount,
} = this.props;
@@ -37,9 +40,11 @@ class EditPackFormWrapper extends Component {
return (
);
}
@@ -63,6 +68,7 @@ class EditPackFormWrapper extends Component {
name="selected-pack-targets"
onFetchTargets={onFetchTargets}
onSelect={noop}
+ selectedTargets={packTargets}
targetsCount={targetsCount}
disabled
className={`${baseClass}__select-targets`}
diff --git a/frontend/interfaces/form_field.js b/frontend/interfaces/form_field.js
index ec07788e82..2149e94f05 100644
--- a/frontend/interfaces/form_field.js
+++ b/frontend/interfaces/form_field.js
@@ -4,6 +4,8 @@ export default PropTypes.shape({
error: PropTypes.string,
name: PropTypes.string,
onChange: PropTypes.func,
- value: PropTypes.oneOfType([PropTypes.bool, PropTypes.number, PropTypes.string]),
+ value: PropTypes.oneOfType(
+ [PropTypes.array, PropTypes.bool, PropTypes.number, PropTypes.string]
+ ),
});
diff --git a/frontend/interfaces/target.js b/frontend/interfaces/target.js
index f6404fd551..993d3a7926 100644
--- a/frontend/interfaces/target.js
+++ b/frontend/interfaces/target.js
@@ -1,8 +1,5 @@
import { PropTypes } from 'react';
+import hostInterface from 'interfaces/host';
+import labelInterface from 'interfaces/label';
-export default PropTypes.shape({
- id: PropTypes.number,
- label: PropTypes.string,
- name: PropTypes.string,
- target_type: PropTypes.string,
-});
+export default PropTypes.oneOfType([hostInterface, labelInterface]);
diff --git a/frontend/kolide/helpers.js b/frontend/kolide/helpers.js
index 69cac9f5c5..15e69564e1 100644
--- a/frontend/kolide/helpers.js
+++ b/frontend/kolide/helpers.js
@@ -47,11 +47,15 @@ export const formatConfigDataForServer = (config) => {
};
};
-export const formatSelectedTargetsForApi = (selectedTargets) => {
+export const formatSelectedTargetsForApi = (selectedTargets, appendID = false) => {
const targets = selectedTargets || [];
const hosts = flatMap(targets, filterTarget('hosts'));
const labels = flatMap(targets, filterTarget('labels'));
+ if (appendID) {
+ return { host_ids: hosts, label_ids: labels };
+ }
+
return { hosts, labels };
};
diff --git a/frontend/kolide/helpers.tests.js b/frontend/kolide/helpers.tests.js
index 663ddb2282..5d24d288ac 100644
--- a/frontend/kolide/helpers.tests.js
+++ b/frontend/kolide/helpers.tests.js
@@ -53,6 +53,15 @@ describe('Kolide API - helpers', () => {
labels: [1, 2],
});
});
+
+ it('appends `_id` when appendID is specified', () => {
+ const targets = [host1, host2, label1, label2];
+
+ expect(formatSelectedTargetsForApi(targets, true)).toEqual({
+ host_ids: [6, 5],
+ label_ids: [1, 2],
+ });
+ });
});
describe('#setupData', () => {
diff --git a/frontend/kolide/index.js b/frontend/kolide/index.js
index ed400d0793..9720f879a7 100644
--- a/frontend/kolide/index.js
+++ b/frontend/kolide/index.js
@@ -32,10 +32,11 @@ class Kolide extends Base {
});
}
- createPack = ({ name, description }) => {
+ createPack = ({ name, description, targets }) => {
const { PACKS } = endpoints;
+ const packTargets = helpers.formatSelectedTargetsForApi(targets, true);
- return this.authenticatedPost(this.endpoint(PACKS), JSON.stringify({ description, name }))
+ return this.authenticatedPost(this.endpoint(PACKS), JSON.stringify({ description, name, ...packTargets }))
.then((response) => { return response.pack; });
}
@@ -121,7 +122,7 @@ class Kolide extends Base {
const { HOSTS } = endpoints;
return this.authenticatedGet(this.endpoint(HOSTS))
- .then((response) => { return response.hosts; });
+ .then(response => response.hosts);
}
getLabelHosts = (labelID) => {
@@ -358,11 +359,12 @@ class Kolide extends Base {
return this.authenticatedPatch(this.endpoint(CONFIG), JSON.stringify(configData));
}
- updatePack = ({ id: packID }, updateParams) => {
+ updatePack = (pack, { description, name, targets }) => {
const { PACKS } = endpoints;
- const updatePackEndpoint = `${this.baseURL}${PACKS}/${packID}`;
+ const updatePackEndpoint = `${this.baseURL}${PACKS}/${pack.id}`;
+ const packTargets = helpers.formatSelectedTargetsForApi(targets, true);
- return this.authenticatedPatch(updatePackEndpoint, JSON.stringify(updateParams))
+ return this.authenticatedPatch(updatePackEndpoint, JSON.stringify({ description, name, ...packTargets }))
.then((response) => { return response.pack; });
}
diff --git a/frontend/kolide/index.tests.js b/frontend/kolide/index.tests.js
index 8906086682..1150b33dff 100644
--- a/frontend/kolide/index.tests.js
+++ b/frontend/kolide/index.tests.js
@@ -4,7 +4,7 @@ import nock from 'nock';
import Kolide from 'kolide';
import helpers from 'kolide/helpers';
import mocks from 'test/mocks';
-import { queryStub, userStub } from 'test/stubs';
+import { hostStub, queryStub, userStub } from 'test/stubs';
const {
invalidForgotPasswordRequest,
@@ -80,7 +80,7 @@ describe('Kolide - API client', () => {
it('#createPack', (done) => {
const { description, name } = pack;
- const params = { description, name };
+ const params = { description, name, host_ids: [], label_ids: [] };
const request = validCreatePackRequest(bearerToken, params);
Kolide.setBearerToken(bearerToken);
@@ -105,7 +105,9 @@ describe('Kolide - API client', () => {
});
it('#updatePack', (done) => {
- const updatePackParams = { name: 'New Pack Name' };
+ const targets = [hostStub];
+ const packTargets = helpers.formatSelectedTargetsForApi(targets, true);
+ const updatePackParams = { name: 'New Pack Name', ...packTargets };
const request = validUpdatePackRequest(bearerToken, pack, updatePackParams);
Kolide.setBearerToken(bearerToken);
diff --git a/frontend/pages/packs/EditPackPage/EditPackPage.jsx b/frontend/pages/packs/EditPackPage/EditPackPage.jsx
index 7ad2522751..1a257502c6 100644
--- a/frontend/pages/packs/EditPackPage/EditPackPage.jsx
+++ b/frontend/pages/packs/EditPackPage/EditPackPage.jsx
@@ -1,9 +1,13 @@
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
-import { noop, size, find } from 'lodash';
+import { filter, includes, isEqual, noop, size, find } from 'lodash';
import { push } from 'react-router-redux';
import EditPackFormWrapper from 'components/packs/EditPackFormWrapper';
+import hostActions from 'redux/nodes/entities/hosts/actions';
+import hostInterface from 'interfaces/host';
+import labelActions from 'redux/nodes/entities/labels/actions';
+import labelInterface from 'interfaces/label';
import packActions from 'redux/nodes/entities/packs/actions';
import ScheduleQuerySidePanel from 'components/side_panels/ScheduleQuerySidePanel';
import packInterface from 'interfaces/pack';
@@ -24,7 +28,9 @@ export class EditPackPage extends Component {
isLoadingPack: PropTypes.bool,
isLoadingScheduledQueries: PropTypes.bool,
pack: packInterface,
+ packHosts: PropTypes.arrayOf(hostInterface),
packID: PropTypes.string,
+ packLabels: PropTypes.arrayOf(labelInterface),
scheduledQueries: PropTypes.arrayOf(queryInterface),
};
@@ -41,7 +47,16 @@ export class EditPackPage extends Component {
}
componentDidMount () {
- const { allQueries, dispatch, isLoadingPack, pack, packID, scheduledQueries } = this.props;
+ const {
+ allQueries,
+ dispatch,
+ isLoadingPack,
+ pack,
+ packHosts,
+ packID,
+ packLabels,
+ scheduledQueries,
+ } = this.props;
const { load } = packActions;
const { loadAll } = queryActions;
@@ -49,6 +64,16 @@ export class EditPackPage extends Component {
dispatch(load(packID));
}
+ if (pack) {
+ if (!packHosts || packHosts.length !== pack.host_ids.length) {
+ dispatch(hostActions.loadAll());
+ }
+
+ if (!packLabels || packLabels.length !== pack.label_ids.length) {
+ dispatch(labelActions.loadAll());
+ }
+ }
+
if (!size(scheduledQueries)) {
dispatch(scheduledQueryActions.loadAll({ id: packID }));
}
@@ -60,6 +85,20 @@ export class EditPackPage extends Component {
return false;
}
+ componentWillReceiveProps ({ dispatch, pack, packHosts, packLabels }) {
+ if (!isEqual(pack, this.props.pack)) {
+ if (!packHosts || packHosts.length !== pack.host_ids.length) {
+ dispatch(hostActions.loadAll());
+ }
+
+ if (!packLabels || packLabels.length !== pack.label_ids.length) {
+ dispatch(labelActions.loadAll());
+ }
+ }
+
+ return false;
+ }
+
onCancelEditPack = () => {
const { dispatch, isEdit, packID } = this.props;
@@ -97,10 +136,10 @@ export class EditPackPage extends Component {
}
handlePackFormSubmit = (formData) => {
- const { dispatch } = this.props;
+ const { dispatch, pack } = this.props;
const { update } = packActions;
- return dispatch(update(formData));
+ return dispatch(update(pack, formData));
}
handleRemoveScheduledQueries = (scheduledQueryIDs) => {
@@ -149,7 +188,9 @@ export class EditPackPage extends Component {
onToggleEdit,
} = this;
const { targetsCount, selectedQuery } = this.state;
- const { allQueries, isEdit, isLoadingScheduledQueries, pack, scheduledQueries } = this.props;
+ const { allQueries, isEdit, isLoadingScheduledQueries, pack, packHosts, packLabels, scheduledQueries } = this.props;
+
+ const packTargets = [...packHosts, ...packLabels];
if (!pack || isLoadingScheduledQueries) {
return false;
@@ -166,6 +207,7 @@ export class EditPackPage extends Component {
onEditPack={onToggleEdit}
onFetchTargets={onFetchTargets}
pack={pack}
+ packTargets={packTargets}
targetsCount={targetsCount}
/>
{
const scheduledQueries = entityGetter.get('scheduled_queries').where({ pack_id: packID });
const isLoadingScheduledQueries = state.entities.scheduled_queries.loading;
const isEdit = route.path === 'edit';
+ const packHosts = pack ? filter(state.entities.hosts.data, (host) => {
+ return includes(pack.host_ids, host.id);
+ }) : [];
+ const packLabels = pack ? filter(state.entities.labels.data, (label) => {
+ return includes(pack.label_ids, label.id);
+ }) : [];
return {
allQueries,
@@ -201,7 +249,9 @@ const mapStateToProps = (state, { params, route }) => {
isLoadingPack,
isLoadingScheduledQueries,
pack,
+ packHosts,
packID,
+ packLabels,
scheduledQueries,
};
};
diff --git a/frontend/pages/packs/EditPackPage/EditPackPage.tests.jsx b/frontend/pages/packs/EditPackPage/EditPackPage.tests.jsx
index 864e07331d..e6d9b2f789 100644
--- a/frontend/pages/packs/EditPackPage/EditPackPage.tests.jsx
+++ b/frontend/pages/packs/EditPackPage/EditPackPage.tests.jsx
@@ -8,6 +8,8 @@ import EditPackPage from './EditPackPage';
describe('EditPackPage - component', () => {
const store = {
entities: {
+ hosts: { data: {} },
+ labels: { data: {} },
packs: {
data: {
[packStub.id]: packStub,
diff --git a/frontend/pages/packs/PackComposerPage/PackComposerPage.jsx b/frontend/pages/packs/PackComposerPage/PackComposerPage.jsx
index 494c67631e..a2a85f58e5 100644
--- a/frontend/pages/packs/PackComposerPage/PackComposerPage.jsx
+++ b/frontend/pages/packs/PackComposerPage/PackComposerPage.jsx
@@ -3,7 +3,6 @@ import { connect } from 'react-redux';
import { noop } from 'lodash';
import { push } from 'react-router-redux';
-import Kolide from 'kolide';
import packActions from 'redux/nodes/entities/packs/actions';
import PackForm from 'components/forms/packs/PackForm';
import PackInfoSidePanel from 'components/side_panels/PackInfoSidePanel';
@@ -47,37 +46,15 @@ export class PackComposerPage extends Component {
}
handleSubmit = (formData) => {
- const { create, load } = packActions;
+ const { create } = packActions;
const { dispatch } = this.props;
const { visitPackPage } = this;
return dispatch(create(formData))
.then((pack) => {
const { id: packID } = pack;
- const { targets } = formData;
- if (!targets) {
- return visitPackPage(packID);
- }
-
- const promises = targets.map((target) => {
- const { id: targetID } = target;
-
- if (target.target_type === 'labels') {
- Kolide.addLabelToPack(packID, targetID);
- }
-
- // TODO: Add host to pack when API is available
- return Promise.resolve();
- });
-
- return Promise.all(promises)
- .then(() => {
- dispatch(load(packID))
- .then(() => {
- return visitPackPage(packID);
- });
- });
+ return visitPackPage(packID);
});
}
diff --git a/frontend/redux/nodes/entities/hosts/config.js b/frontend/redux/nodes/entities/hosts/config.js
index 6cab00a22b..d2dc128939 100644
--- a/frontend/redux/nodes/entities/hosts/config.js
+++ b/frontend/redux/nodes/entities/hosts/config.js
@@ -7,5 +7,8 @@ const { HOSTS: schema } = schemas;
export default reduxConfig({
entityName: 'hosts',
loadAllFunc: Kolide.getHosts,
+ parseEntityFunc: (host) => {
+ return { ...host, target_type: 'hosts' };
+ },
schema,
});
diff --git a/frontend/redux/nodes/entities/labels/config.js b/frontend/redux/nodes/entities/labels/config.js
index b8205cd6cf..82ba351289 100644
--- a/frontend/redux/nodes/entities/labels/config.js
+++ b/frontend/redux/nodes/entities/labels/config.js
@@ -8,5 +8,8 @@ export default reduxConfig({
createFunc: Kolide.createLabel,
entityName: 'labels',
loadAllFunc: Kolide.getLabels,
+ parseEntityFunc: (label) => {
+ return { ...label, target_type: 'labels' };
+ },
schema,
});
diff --git a/frontend/test/stubs.js b/frontend/test/stubs.js
index f849bdc89c..cd182ba1e6 100644
--- a/frontend/test/stubs.js
+++ b/frontend/test/stubs.js
@@ -30,6 +30,60 @@ export const configStub = {
},
};
+export const hostStub = {
+ created_at: '2017-01-10T19:18:55Z',
+ updated_at: '2017-01-10T20:13:52Z',
+ deleted_at: null,
+ deleted: false,
+ id: 1,
+ detail_updated_at: '2017-01-10T20:01:48Z',
+ seen_time: '2017-01-10T20:13:54Z',
+ hostname: '52883a0ba916',
+ uuid: 'FD87130B-09A9-683D-9095-D92CD20728CA',
+ platform: 'ubuntu',
+ osquery_version: '2.1.2',
+ os_version: 'Ubuntu 14.4.',
+ build: '',
+ platform_like: 'debian',
+ code_name: '',
+ uptime: 45469000000000,
+ memory: 2094940160,
+ cpu_type: '6',
+ cpu_subtype: '78',
+ cpu_brand: 'Intel(R) Core(TM) i5-6267U CPU @ 2.90GHz',
+ cpu_physical_cores: 2,
+ cpu_logical_cores: 2,
+ hardware_vendor: ' ',
+ hardware_model: 'BHYVE',
+ hardware_version: '1.0',
+ hardware_serial: 'None',
+ computer_name: '52883a0ba916',
+ primary_ip_id: 1,
+ network_interfaces: [
+ {
+ id: 1,
+ interface: 'eth0',
+ address: '172.19.0.8',
+ mask: '255.255.0.0',
+ broadcast: '172.19.0.8',
+ point_to_point: '',
+ mac: '02:42:ac:13:00:08',
+ type: 1,
+ mtu: 1500,
+ metric: 0,
+ ipackets: 512,
+ opackets: 317,
+ ibytes: 97231,
+ obytes: 43502,
+ ierrors: 0,
+ oerrors: 0,
+ last_change: -1,
+ },
+ ],
+ status: 'online',
+ display_text: '52883a0ba916',
+};
+
export const packStub = {
created_at: '0001-01-01T00:00:00Z',
updated_at: '0001-01-01T00:00:00Z',
@@ -41,6 +95,8 @@ export const packStub = {
platform: '',
created_by: 1,
disabled: false,
+ host_ids: [],
+ label_ids: [],
};
export const queryStub = {
@@ -82,6 +138,7 @@ export const userStub = {
export default {
adminUserStub,
configStub,
+ hostStub,
packStub,
queryStub,
scheduledQueryStub,