diff --git a/frontend/components/forms/ConfigurePackQueryForm/ConfigurePackQueryForm.jsx b/frontend/components/forms/ConfigurePackQueryForm/ConfigurePackQueryForm.jsx index 9d83416352..3d09119b9c 100644 --- a/frontend/components/forms/ConfigurePackQueryForm/ConfigurePackQueryForm.jsx +++ b/frontend/components/forms/ConfigurePackQueryForm/ConfigurePackQueryForm.jsx @@ -11,7 +11,7 @@ import validate from 'components/forms/ConfigurePackQueryForm/validate'; const baseClass = 'configure-pack-query-form'; const fieldNames = ['query_id', 'interval', 'logging_type', 'platform', 'version']; const platformOptions = [ - { label: 'All', value: '' }, + { label: 'All', value: 'all' }, { label: 'Windows', value: 'windows' }, { label: 'Linux', value: 'linux' }, { label: 'macOS', value: 'darwin' }, @@ -73,6 +73,7 @@ class ConfigurePackQueryForm extends Component { options={platformOptions} placeholder="- - -" label="Platform" + multi wrapperClassName={`${baseClass}__form-field ${baseClass}__form-field--platform`} /> { expect(spy).toHaveBeenCalledWith({ interval: 123, logging_type: 'differential', - platform: '', + platform: 'all', query_id: 1, version: '', }); diff --git a/frontend/components/forms/fields/Dropdown/Dropdown.jsx b/frontend/components/forms/fields/Dropdown/Dropdown.jsx index c2e001d6cd..c7fd9fa938 100644 --- a/frontend/components/forms/fields/Dropdown/Dropdown.jsx +++ b/frontend/components/forms/fields/Dropdown/Dropdown.jsx @@ -16,11 +16,12 @@ class Dropdown extends Component { error: PropTypes.string, label: PropTypes.oneOfType([PropTypes.array, PropTypes.string]), labelClassName: PropTypes.string, + multi: PropTypes.bool, name: PropTypes.string, onChange: PropTypes.func, options: PropTypes.arrayOf(dropdownOptionInterface).isRequired, placeholder: PropTypes.oneOfType([PropTypes.array, PropTypes.string]), - value: PropTypes.string, + value: PropTypes.oneOfType([PropTypes.array, PropTypes.string]), wrapperClassName: PropTypes.string, }; @@ -28,14 +29,19 @@ class Dropdown extends Component { onChange: noop, clearable: false, disabled: false, + multi: false, name: 'targets', placeholder: 'Select One...', }; - handleChange = ({ value }) => { - const { onChange } = this.props; + handleChange = (selected) => { + const { multi, onChange } = this.props; - return onChange(value); + if (multi) { + return onChange(selected.map(obj => obj.value).join(',')); + } + + return onChange(selected.value); }; renderLabel = () => { @@ -62,7 +68,7 @@ class Dropdown extends Component { render () { const { handleChange } = this; - const { error, className, clearable, disabled, name, options, placeholder, value, wrapperClassName } = this.props; + const { error, className, clearable, disabled, multi, name, options, placeholder, value, wrapperClassName } = this.props; const formFieldProps = pick(this.props, ['hint', 'label', 'error', 'name']); const selectClasses = classnames(className, `${baseClass}__select`, { @@ -75,6 +81,7 @@ class Dropdown extends Component { className={selectClasses} clearable={clearable} disabled={disabled} + multi={multi} name={`${name}-select`} onChange={handleChange} options={options} diff --git a/frontend/components/forms/fields/Dropdown/_styles.scss b/frontend/components/forms/fields/Dropdown/_styles.scss index 7398c6c2b6..8c897e43f3 100644 --- a/frontend/components/forms/fields/Dropdown/_styles.scss +++ b/frontend/components/forms/fields/Dropdown/_styles.scss @@ -36,6 +36,47 @@ .Select-value { font-size: $medium; + border-radius: 2px; + background-color: #fff; + box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.12), 0 2px 0 0 #b8c2e3; + border: solid 1px #b8c2e3; + + .Select-value-icon { + border: 0; + float: right; + visibility: hidden; + position: relative; + line-height: 28px; + width: 25px; + padding: 0; + margin: 0 5px; + + &::after { + @extend %kolidecon; + @include transition(color 150ms ease-in-out); + @include transform(translate(-50%, -50%)); + content: '\f036'; + position: absolute; + top: 50%; + left: 50%; + visibility: visible; + font-size: 16px; + color: $accent-dark; + } + + &:hover { + &::after { + color: $accent-text; + } + } + } + + .Select-value-label { + font-size: 16px; + font-weight: $normal; + color: $text-dark; + line-height: 28px; + } } &--single { @@ -124,6 +165,21 @@ } &.Select--multi { + .Select-control { + @include display(flex); + @include align-items(center); + height: auto; + min-height: 40px; + } + + .Select-multi-value-wrapper { + @include flex-grow(1); + } + + .Select-arrow-zone { + display: block; + } + .Select-option { &.is-disabled { color: $text-dark; @@ -135,5 +191,19 @@ } } } + + .Select-value { + margin-top: 3px; + } + + .Select-value-label { + padding: 0 0 0 1rem; + } + + &.has-value { + .Select-multi-value-wrapper { + padding: 5px 0; + } + } } } diff --git a/frontend/components/icons/PlatformIcon/PlatformIcon.jsx b/frontend/components/icons/PlatformIcon/PlatformIcon.jsx index 2c3e91d6bc..53c007138a 100644 --- a/frontend/components/icons/PlatformIcon/PlatformIcon.jsx +++ b/frontend/components/icons/PlatformIcon/PlatformIcon.jsx @@ -19,9 +19,10 @@ export class PlatformIcon extends Component { const { className, name, fw, size, title } = this.props; const iconClasses = classnames(baseClass, className); const iconName = platformIconClass(name); + const properNameCase = name.charAt(0).toUpperCase() + name.slice(1); if (!iconName) { - return {name || 'All'}; + return {properNameCase || 'All'}; } return ( diff --git a/frontend/components/queries/ScheduledQueriesList/ScheduledQueriesListItem/ScheduledQueriesListItem.jsx b/frontend/components/queries/ScheduledQueriesList/ScheduledQueriesListItem/ScheduledQueriesListItem.jsx index 7a6231a165..4803fdb73e 100644 --- a/frontend/components/queries/ScheduledQueriesList/ScheduledQueriesListItem/ScheduledQueriesListItem.jsx +++ b/frontend/components/queries/ScheduledQueriesList/ScheduledQueriesListItem/ScheduledQueriesListItem.jsx @@ -42,10 +42,21 @@ class ScheduledQueriesListItem extends Component { return 'bold-plus'; } + renderPlatformIcon = () => { + const { scheduledQuery: { platform } } = this.props; + const platformArr = platform.split(','); + + if (platformArr.includes('all')) { + return ; + } + + return platformArr.map((pltf, idx) => ); + } + render () { const { checked, disabled, scheduledQuery } = this.props; - const { onCheck } = this; - const { id, name, interval, platform, version } = scheduledQuery; + const { onCheck, renderPlatformIcon } = this; + const { id, name, interval, version } = scheduledQuery; const { loggingTypeString } = this; return ( @@ -60,7 +71,7 @@ class ScheduledQueriesListItem extends Component { {name} {interval} - + {renderPlatformIcon()} {version ? `${version}+` : 'Any'} diff --git a/frontend/components/side_panels/ScheduleQuerySidePanel/SearchPackQuery/SearchPackQuery.jsx b/frontend/components/side_panels/ScheduleQuerySidePanel/SearchPackQuery/SearchPackQuery.jsx index 530ebeef4e..461737247f 100644 --- a/frontend/components/side_panels/ScheduleQuerySidePanel/SearchPackQuery/SearchPackQuery.jsx +++ b/frontend/components/side_panels/ScheduleQuerySidePanel/SearchPackQuery/SearchPackQuery.jsx @@ -93,7 +93,7 @@ class SearchPackQuery extends Component { return (

description

-

{selectedQuery.description}

+

{selectedQuery.description || No description available.}

); } diff --git a/frontend/pages/packs/EditPackPage/EditPackPage.jsx b/frontend/pages/packs/EditPackPage/EditPackPage.jsx index 9fa85551ab..115d67d0ad 100644 --- a/frontend/pages/packs/EditPackPage/EditPackPage.jsx +++ b/frontend/pages/packs/EditPackPage/EditPackPage.jsx @@ -160,6 +160,10 @@ export class EditPackPage extends Component { } handleConfigurePackQuerySubmit = (formData) => { + if (formData.platform.indexOf('all') !== -1) { + formData.platform = ''; + } + const { create } = scheduledQueryActions; const { dispatch, packID } = this.props; const scheduledQueryData = {