Query Select Target Dropdown (#499)
This commit is contained in:
+1
-1
@@ -69,7 +69,7 @@ rules:
|
||||
no-css-comments: 1
|
||||
no-duplicate-properties: 1
|
||||
no-empty-rulesets: 1
|
||||
no-extends: 1
|
||||
no-extends: 0
|
||||
no-ids: 1
|
||||
no-important: 1
|
||||
no-invalid-hex: 1
|
||||
|
||||
Binary file not shown.
@@ -4,7 +4,7 @@
|
||||
border-radius: 2px;
|
||||
background-color: $white;
|
||||
border: solid 1px $accent-medium;
|
||||
font-size: $base;
|
||||
font-size: 16px;
|
||||
padding: $pad-xsmall $pad-base;
|
||||
color: $text-dark;
|
||||
font-family: 'Oxygen', sans-serif;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { isEqual, noop } from 'lodash';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import Kolide from 'kolide';
|
||||
import targetInterface from 'interfaces/target';
|
||||
@@ -22,6 +23,7 @@ class SelectTargetsDropdown extends Component {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isEmpty: false,
|
||||
isLoadingTargets: false,
|
||||
moreInfoTarget: null,
|
||||
query: '',
|
||||
@@ -57,8 +59,6 @@ class SelectTargetsDropdown extends Component {
|
||||
const currentMoreInfoTarget = this.state.moreInfoTarget || {};
|
||||
|
||||
if (isEqual(moreInfoTarget.display_text, currentMoreInfoTarget.display_text)) {
|
||||
this.setState({ moreInfoTarget: null });
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -75,13 +75,18 @@ class SelectTargetsDropdown extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
this.setState({ moreInfoTarget });
|
||||
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
onBackToResults = () => {
|
||||
this.setState({
|
||||
moreInfoTarget: null,
|
||||
});
|
||||
}
|
||||
|
||||
fetchTargets = (query, selectedTargets = this.props.selectedTargets) => {
|
||||
const { onFetchTargets } = this.props;
|
||||
|
||||
@@ -93,6 +98,14 @@ class SelectTargetsDropdown extends Component {
|
||||
targets,
|
||||
} = response;
|
||||
|
||||
if (targets.length === 0) {
|
||||
// We don't want the lib's default "No Results" so we fake it
|
||||
targets.push({});
|
||||
this.setState({ isEmpty: true });
|
||||
} else {
|
||||
this.setState({ isEmpty: false });
|
||||
}
|
||||
|
||||
onFetchTargets(query, response);
|
||||
|
||||
this.setState({ isLoadingTargets: false, targets });
|
||||
@@ -107,13 +120,19 @@ class SelectTargetsDropdown extends Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { isLoadingTargets, moreInfoTarget, targets } = this.state;
|
||||
const { fetchTargets, onInputClose, onTargetSelectMoreInfo } = this;
|
||||
const { isEmpty, isLoadingTargets, moreInfoTarget, targets } = this.state;
|
||||
const { fetchTargets, onBackToResults, onInputClose, onTargetSelectMoreInfo } = this;
|
||||
const { onSelect, selectedTargets } = this.props;
|
||||
const menuRenderer = Menu(onTargetSelectMoreInfo, moreInfoTarget);
|
||||
const menuRenderer = Menu(onTargetSelectMoreInfo, moreInfoTarget, onBackToResults);
|
||||
|
||||
const inputClasses = classnames({
|
||||
'show-preview': moreInfoTarget,
|
||||
'is-empty': isEmpty,
|
||||
});
|
||||
|
||||
return (
|
||||
<Input
|
||||
className={inputClasses}
|
||||
isLoading={isLoadingTargets}
|
||||
menuRenderer={menuRenderer}
|
||||
onClose={onInputClose}
|
||||
|
||||
+5
-1
@@ -7,6 +7,7 @@ import targetInterface from 'interfaces/target';
|
||||
|
||||
class SelectTargetsInput extends Component {
|
||||
static propTypes = {
|
||||
className: PropTypes.string,
|
||||
isLoading: PropTypes.bool,
|
||||
menuRenderer: PropTypes.func,
|
||||
onClose: PropTypes.func,
|
||||
@@ -24,6 +25,7 @@ class SelectTargetsInput extends Component {
|
||||
|
||||
render () {
|
||||
const {
|
||||
className,
|
||||
isLoading,
|
||||
menuRenderer,
|
||||
onClose,
|
||||
@@ -35,7 +37,7 @@ class SelectTargetsInput extends Component {
|
||||
|
||||
return (
|
||||
<Select
|
||||
className="target-select"
|
||||
className={`${className} target-select`}
|
||||
isLoading={isLoading}
|
||||
filterOptions={this.filterOptions}
|
||||
labelKey="display_text"
|
||||
@@ -48,6 +50,8 @@ class SelectTargetsInput extends Component {
|
||||
onInputChange={onTargetSelectInputChange}
|
||||
placeholder="Label Name, Host Name, IP Address, etc."
|
||||
resetValue={[]}
|
||||
scrollMenuIntoView={false}
|
||||
tabSelectsValue={false}
|
||||
value={selectedTargets}
|
||||
valueKey="display_text"
|
||||
/>
|
||||
|
||||
+13
-7
@@ -6,8 +6,9 @@ import targetInterface from 'interfaces/target';
|
||||
import TargetDetails from '../TargetDetails';
|
||||
import TargetOption from '../TargetOption';
|
||||
|
||||
const baseClass = 'target-list';
|
||||
|
||||
const SelectTargetsMenuWrapper = (onMoreInfoClick, moreInfoTarget) => {
|
||||
const SelectTargetsMenuWrapper = (onMoreInfoClick, moreInfoTarget, handleBackToResults) => {
|
||||
const SelectTargetsMenu = ({
|
||||
focusedOption,
|
||||
instancePrefix,
|
||||
@@ -21,9 +22,14 @@ const SelectTargetsMenuWrapper = (onMoreInfoClick, moreInfoTarget) => {
|
||||
onOptionRef,
|
||||
}) => {
|
||||
const Option = optionComponent;
|
||||
|
||||
const renderTargets = (targetType) => {
|
||||
const targets = filter(options, { target_type: targetType });
|
||||
|
||||
if (targets.length === 0) {
|
||||
return <span className={`${baseClass}__not-found`}>Unable to find any matching {targetType}.</span>;
|
||||
}
|
||||
|
||||
return targets.map((target, index) => {
|
||||
const { disabled: isDisabled } = target;
|
||||
const isSelected = includes(valueArray, target);
|
||||
@@ -61,14 +67,14 @@ const SelectTargetsMenuWrapper = (onMoreInfoClick, moreInfoTarget) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<div>hosts</div>
|
||||
{renderTargets('hosts')}
|
||||
<div>labels</div>
|
||||
<div className={baseClass}>
|
||||
<div className={`${baseClass}__options`}>
|
||||
<p className={`${baseClass}__type`}>labels</p>
|
||||
{renderTargets('labels')}
|
||||
<p className={`${baseClass}__type`}>hosts</p>
|
||||
{renderTargets('hosts')}
|
||||
</div>
|
||||
<TargetDetails target={moreInfoTarget} />
|
||||
<TargetDetails target={moreInfoTarget} className={`${baseClass}__spotlight`} handleBackToResults={handleBackToResults} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
.target-list {
|
||||
&__type {
|
||||
background-color: $bg-medium;
|
||||
line-height: $larger;
|
||||
padding: 0 $base;
|
||||
margin: 0;
|
||||
text-transform: uppercase;
|
||||
font-weight: $light;
|
||||
font-size: $mini;
|
||||
color: $text-dark;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
&__options {
|
||||
width: 57%;
|
||||
float: left;
|
||||
overflow-y: auto;
|
||||
box-shadow: 0 8px 12px 0 rgba(0, 0, 0, 0.15);
|
||||
min-height: 500px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
background-color: $bg-light;
|
||||
}
|
||||
|
||||
&__spotlight {
|
||||
width: 43%;
|
||||
float: right;
|
||||
overflow-y: auto;
|
||||
background-color: $white;
|
||||
min-height: 500px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&__not-found {
|
||||
font-size: $small;
|
||||
font-weight: $bold;
|
||||
line-height: 2.64;
|
||||
text-align: left;
|
||||
color: $text-dark;
|
||||
text-transform: uppercase;
|
||||
display: block;
|
||||
padding: 0 $large;
|
||||
}
|
||||
}
|
||||
+103
-81
@@ -1,10 +1,9 @@
|
||||
import React, { Component } from 'react';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { noop } from 'lodash';
|
||||
import AceEditor from 'react-ace';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import hostHelpers from 'components/hosts/HostDetails/helpers';
|
||||
import ShadowBox from 'components/ShadowBox';
|
||||
import ShadowBoxInput from 'components/forms/fields/ShadowBoxInput';
|
||||
import targetInterface from 'interfaces/target';
|
||||
|
||||
const baseClass = 'target-details';
|
||||
@@ -12,10 +11,28 @@ const baseClass = 'target-details';
|
||||
class TargetDetails extends Component {
|
||||
static propTypes = {
|
||||
target: targetInterface,
|
||||
className: PropTypes.string,
|
||||
handleBackToResults: PropTypes.func,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
handleBackToResults: noop,
|
||||
};
|
||||
|
||||
onlineHosts = (labelBaseClass, online) => {
|
||||
if (online > 0) {
|
||||
return (
|
||||
<span className={`${labelBaseClass}__hosts-online`}> ({online}% ONLINE)</span>
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
renderHost = () => {
|
||||
const { className, handleBackToResults, target } = this.props;
|
||||
const {
|
||||
display_text: displayText,
|
||||
ip,
|
||||
mac,
|
||||
memory,
|
||||
@@ -23,7 +40,7 @@ class TargetDetails extends Component {
|
||||
osVersion,
|
||||
platform,
|
||||
status,
|
||||
} = this.props.target;
|
||||
} = target;
|
||||
const hostBaseClass = 'host-target';
|
||||
const isOnline = status === 'online';
|
||||
const isOffline = status === 'offline';
|
||||
@@ -34,63 +51,104 @@ class TargetDetails extends Component {
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p className={statusClassName}>{status}</p>
|
||||
<ShadowBox>
|
||||
<table className={`${baseClass}__table`}>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>IP Address</th>
|
||||
<td>{ip}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>MAC Address</th>
|
||||
<td>{mac}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Platform</th>
|
||||
<td>
|
||||
<i className={hostHelpers.platformIconClass(platform)} />
|
||||
<span className={`${hostBaseClass}__platform-text`}>{platform}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Operating System</th>
|
||||
<td>{osVersion}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Osquery Version</th>
|
||||
<td>{osqueryVersion}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Memory</th>
|
||||
<td>{hostHelpers.humanMemory(memory)}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ShadowBox>
|
||||
<div className={`${hostBaseClass} ${className}`}>
|
||||
<button className={`button button--unstyled ${hostBaseClass}__back`} onClick={handleBackToResults}>
|
||||
<i className="kolidecon kolidecon-chevronleft" />Back
|
||||
</button>
|
||||
|
||||
<p className={`${hostBaseClass}__display-text`}>
|
||||
<i className={`${hostBaseClass}__icon kolidecon-fw kolidecon-single-host`} />
|
||||
<span>{displayText}</span>
|
||||
</p>
|
||||
<p className={statusClassName}>
|
||||
{isOnline && <i className={`${hostBaseClass}__icon ${hostBaseClass}__icon--online kolidecon-fw kolidecon-success-check`} />}
|
||||
{isOffline && <i className={`${hostBaseClass}__icon ${hostBaseClass}__icon--offline kolidecon-fw kolidecon-offline`} />}
|
||||
<span>{status}</span>
|
||||
</p>
|
||||
<table className={`${baseClass}__table`}>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>IP Address</th>
|
||||
<td>{ip}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>MAC Address</th>
|
||||
<td><span className={`${hostBaseClass}__mac-address`}>{mac}</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Platform</th>
|
||||
<td>
|
||||
<i className={hostHelpers.platformIconClass(platform)} />
|
||||
<span className={`${hostBaseClass}__platform-text`}> {platform}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Operating System</th>
|
||||
<td>{osVersion}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Osquery Version</th>
|
||||
<td>{osqueryVersion}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Memory</th>
|
||||
<td>{hostHelpers.humanMemory(memory)}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div className={`${hostBaseClass}__labels-wrapper`}>
|
||||
<div className={`${hostBaseClass}__labels-wrapper--header`}>
|
||||
<i className="kolidecon-label" />
|
||||
<p className={`${hostBaseClass}__labels-header`}>
|
||||
<i className={`${hostBaseClass}__icon kolidecon-fw kolidecon-label`} />
|
||||
<span>Labels</span>
|
||||
</div>
|
||||
</p>
|
||||
<ul className={`${hostBaseClass}__labels-list`}>
|
||||
<li>Engineering</li>
|
||||
<li>DevOps</li>
|
||||
<li>ElCapDev</li>
|
||||
<li>Workstation</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderLabel = () => {
|
||||
const { hosts, query } = this.props.target;
|
||||
const { onlineHosts } = this;
|
||||
const { handleBackToResults, className, target } = this.props;
|
||||
const {
|
||||
count,
|
||||
description,
|
||||
display_text: displayText,
|
||||
online,
|
||||
query,
|
||||
} = target;
|
||||
const labelBaseClass = 'label-target';
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={`${labelBaseClass}__text-editor-wrapper`}>
|
||||
<div className={`${labelBaseClass} ${className}`}>
|
||||
<button className={`button button--unstyled ${labelBaseClass}__back`} onClick={handleBackToResults}>
|
||||
<i className="kolidecon kolidecon-chevronleft" /> Back
|
||||
</button>
|
||||
|
||||
<p className={`${labelBaseClass}__display-text`}>
|
||||
<i className={`${labelBaseClass}__icon kolidecon-fw kolidecon-label`} />
|
||||
<span>{displayText}</span>
|
||||
</p>
|
||||
|
||||
<p className={`${labelBaseClass}__hosts`}>
|
||||
<span className={`${labelBaseClass}__hosts-count`}><strong>{count}</strong>HOSTS</span>
|
||||
{ onlineHosts(labelBaseClass, online) }
|
||||
</p>
|
||||
|
||||
<p className={`${labelBaseClass}__description`}>{description || 'No Description'}</p>
|
||||
|
||||
<div className={`${labelBaseClass}__editor`}>
|
||||
<AceEditor
|
||||
editorProps={{ $blockScrolling: Infinity }}
|
||||
mode="kolide"
|
||||
minLines={4}
|
||||
maxLines={4}
|
||||
fontSize={13}
|
||||
name="label-query"
|
||||
readOnly
|
||||
setOptions={{ wrap: true }}
|
||||
@@ -101,42 +159,6 @@ class TargetDetails extends Component {
|
||||
width="100%"
|
||||
/>
|
||||
</div>
|
||||
<div className={`${labelBaseClass}__search-section`}>
|
||||
<ShadowBoxInput
|
||||
iconClass="kolidecon-search"
|
||||
name="search-hosts"
|
||||
placeholder="SEARCH HOSTS"
|
||||
/>
|
||||
<div className={`${labelBaseClass}__num-hosts-section`}>
|
||||
<span className="num-hosts">{hosts.length} HOSTS</span>
|
||||
</div>
|
||||
</div>
|
||||
<ShadowBox>
|
||||
<table className={`${baseClass}__table`}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Hostname</th>
|
||||
<th>Status</th>
|
||||
<th>Platform</th>
|
||||
<th>Location</th>
|
||||
<th>MAC</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{hosts.map((host) => {
|
||||
return (
|
||||
<tr className="__label-row" key={`host-${host.id}`}>
|
||||
<td>{host.hostname}</td>
|
||||
<td>{host.status}</td>
|
||||
<td><i className={hostHelpers.platformIconClass(host.platform)} /></td>
|
||||
<td>{host.ip}</td>
|
||||
<td>{host.mac}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</ShadowBox>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
.host-target,
|
||||
.label-target {
|
||||
padding: $pad-large;
|
||||
|
||||
&__back {
|
||||
color: $text-dark;
|
||||
font-size: $xsmall;
|
||||
display: none;
|
||||
|
||||
@include breakpoint(tablet) {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
padding: 10px;
|
||||
|
||||
&:active {
|
||||
top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include breakpoint(tablet) {
|
||||
padding-top: $pad-most;
|
||||
}
|
||||
}
|
||||
|
||||
.host-target {
|
||||
&__display-text {
|
||||
font-size: $medium;
|
||||
font-weight: $bold;
|
||||
letter-spacing: 0.6px;
|
||||
color: $text-ultradark;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
color: $accent-text;
|
||||
margin-right: $pad-small;
|
||||
width: 21px;
|
||||
|
||||
&--online {
|
||||
color: $success;
|
||||
}
|
||||
|
||||
&--offline {
|
||||
color: $alert;
|
||||
}
|
||||
}
|
||||
|
||||
&__status {
|
||||
margin: $pad-xsmall 0;
|
||||
font-size: $small;
|
||||
font-weight: $bold;
|
||||
letter-spacing: 1.3px;
|
||||
text-transform: uppercase;
|
||||
|
||||
&--is-online {
|
||||
color: $success;
|
||||
}
|
||||
|
||||
&--is-offline {
|
||||
color: $alert;
|
||||
}
|
||||
}
|
||||
|
||||
&__labels-wrapper {
|
||||
position: absolute;
|
||||
bottom: $pad-large;
|
||||
}
|
||||
|
||||
&__labels-header {
|
||||
font-size: $small;
|
||||
font-weight: $bold;
|
||||
letter-spacing: 0.9px;
|
||||
color: $text-dark;
|
||||
text-transform: uppercase;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&__labels-list {
|
||||
font-size: $small;
|
||||
font-weight: $normal;
|
||||
line-height: 1.85;
|
||||
letter-spacing: 0.5px;
|
||||
color: $text-medium;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
display: inline-block;
|
||||
|
||||
&::after {
|
||||
content: ',';
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
&::after {
|
||||
content: '';
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__platform-text {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
&__mac-address {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
|
||||
.label-target {
|
||||
&__display-text {
|
||||
font-size: $medium;
|
||||
font-weight: $bold;
|
||||
letter-spacing: 0.6px;
|
||||
color: $text-ultradark;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
color: $accent-text;
|
||||
margin-right: $pad-small;
|
||||
width: 21px;
|
||||
}
|
||||
|
||||
&__hosts {
|
||||
margin: $pad-xsmall 0;
|
||||
letter-spacing: 0.4px;
|
||||
font-size: $small;
|
||||
font-weight: $normal;
|
||||
}
|
||||
|
||||
&__hosts-count {
|
||||
font-weight: $bold;
|
||||
color: $text-dark;
|
||||
|
||||
strong {
|
||||
min-width: 21px;
|
||||
text-align: center;
|
||||
font-weight: $bold;
|
||||
display: inline-block;
|
||||
margin-right: $pad-small;
|
||||
}
|
||||
}
|
||||
|
||||
&__hosts-online {
|
||||
color: $success;
|
||||
}
|
||||
|
||||
&__description {
|
||||
font-size: $pad-medium;
|
||||
font-weight: $normal;
|
||||
line-height: 1.71;
|
||||
color: $text-dark;
|
||||
margin: 0 0 $pad-small;
|
||||
}
|
||||
}
|
||||
|
||||
.target-details {
|
||||
&__table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
text-align: left;
|
||||
font-size: $small;
|
||||
width: 100%;
|
||||
margin-bottom: $pad-medium;
|
||||
|
||||
th {
|
||||
font-weight: $bold;
|
||||
line-height: 2.3;
|
||||
color: $text-dark;
|
||||
padding-right: $pad-half;
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-2
@@ -29,7 +29,12 @@ class TargetOption extends Component {
|
||||
const { count, ip, target_type: targetType } = target;
|
||||
|
||||
if (targetType === 'hosts') {
|
||||
return <span className={`${baseClass}__ip`}>{ip}</span>;
|
||||
return (
|
||||
<span>
|
||||
<span className={`${baseClass}__delimeter`}>•</span>
|
||||
<span className={`${baseClass}__ip`}>{ip}</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return <span className={`${baseClass}__count`}>{count} hosts</span>;
|
||||
@@ -57,7 +62,6 @@ class TargetOption extends Component {
|
||||
{targetType === 'hosts' && <i className={`${baseClass}__icon ${hostPlatformIconClass()}`} />}
|
||||
{targetType === 'labels' && <i className={`${baseClass}__icon kolidecon-label`} />}
|
||||
<span className={`${baseClass}__label-label`}>{displayText}</span>
|
||||
<span className={`${baseClass}__delimeter`}>•</span>
|
||||
{renderTargetDetail()}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -7,13 +7,41 @@
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
&__add-btn {
|
||||
color: $accent-medium;
|
||||
font-size: $medium;
|
||||
padding: 0 $pad-half;
|
||||
}
|
||||
|
||||
&__wrapper {
|
||||
@include display(flex);
|
||||
@include align-items(stretch);
|
||||
@include align-content(stretch);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&__target-content,
|
||||
&__add-btn {
|
||||
line-height: 34px;
|
||||
font-size: $small;
|
||||
font-weight: $normal;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
&__target-content {
|
||||
@include flex-grow(1);
|
||||
text-align: left;
|
||||
padding-right: $pad-half;
|
||||
}
|
||||
|
||||
&__count {
|
||||
background-color: $text-medium;
|
||||
border-radius: $border-radius;
|
||||
color: $white;
|
||||
font-size: $small;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
padding: 5px;
|
||||
margin-left: $pad-half;
|
||||
}
|
||||
|
||||
&__delimeter {
|
||||
@@ -24,25 +52,24 @@
|
||||
|
||||
&__icon {
|
||||
color: $text-medium;
|
||||
font-size: $base;
|
||||
margin: 0 0 0 $pad-base;
|
||||
font-size: $small;
|
||||
}
|
||||
|
||||
&__ip {
|
||||
color: $link;
|
||||
color: $text-dark;
|
||||
font-size: $small;
|
||||
}
|
||||
|
||||
&__label-host {
|
||||
color: $link;
|
||||
font-size: $base;
|
||||
margin-left: $pad-xsmall;
|
||||
margin-left: $pad-half;
|
||||
}
|
||||
|
||||
&__label-label {
|
||||
color: $text-medium;
|
||||
font-size: $base;
|
||||
margin-left: $pad-xsmall;
|
||||
font-size: 14px;
|
||||
margin-left: $pad-half;
|
||||
|
||||
.all-hosts {
|
||||
font-weight: $bold;
|
||||
@@ -65,8 +92,4 @@
|
||||
&__target-modal {
|
||||
width: 1000px;
|
||||
}
|
||||
|
||||
&__wrapper {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,45 @@
|
||||
.target-select {
|
||||
&.Select {
|
||||
.Select-control {
|
||||
@include transition(border 150ms ease-in-out, box-shadow 150ms ease-in-out);
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.Select-arrow {
|
||||
border-width: 8px 8px 2.5px;
|
||||
}
|
||||
|
||||
.Select-input {
|
||||
height: 46px;
|
||||
margin: 0 0 0 $pad-base;
|
||||
|
||||
> input {
|
||||
line-height: 30px;
|
||||
padding: 8px 0;
|
||||
color: $text-ultradark;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.Select-placeholder {
|
||||
line-height: 46px;
|
||||
font-size: 16px;
|
||||
padding: 0 $pad-base;
|
||||
}
|
||||
|
||||
.Select-arrow-zone {
|
||||
padding-right: 19px;
|
||||
}
|
||||
|
||||
&.Select--multi {
|
||||
.Select-option {
|
||||
padding: 0;
|
||||
background-color: $bg-light;
|
||||
|
||||
&.is-disabled {
|
||||
&.is-focused {
|
||||
background-color: $brand-light;
|
||||
|
||||
.target-option__label-label,
|
||||
.target-option__target-icon,
|
||||
.target-option__delimeter,
|
||||
@@ -29,13 +48,163 @@
|
||||
.target-option__icon {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.target-option__add-btn {
|
||||
color: $brand-ultralight;
|
||||
}
|
||||
|
||||
.target-option__count {
|
||||
background-color: $white;
|
||||
color: $brand;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.Select-value {
|
||||
line-height: 1.9;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-focused {
|
||||
.Select-control {
|
||||
border: 1px solid $accent-medium;
|
||||
border-bottom-color: $brand;
|
||||
box-shadow: inset 0 0 8px 0 rgba($black, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.Select-menu-outer {
|
||||
max-height: 500px;
|
||||
height: 500px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 8px 12px 0 rgba(0, 0, 0, 0.15);
|
||||
|
||||
.Select-menu {
|
||||
height: 498px;
|
||||
max-height: 498px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
@extend %kolidecon;
|
||||
content: '\f03c';
|
||||
font-size: 150px;
|
||||
position: absolute;
|
||||
left: 57%;
|
||||
top: 50%;
|
||||
margin-top: -75px;
|
||||
opacity: 0.25;
|
||||
width: 43%;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.target-list {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.Select-value {
|
||||
border-radius: 2px;
|
||||
background-color: $white;
|
||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.12), 0 2px 0 0 $accent-dark;
|
||||
border: solid 1px $accent-dark;
|
||||
|
||||
.Select-value-icon {
|
||||
border: 0;
|
||||
float: right;
|
||||
visibility: hidden;
|
||||
position: relative;
|
||||
padding: 0 $pad-medium;
|
||||
line-height: 34px;
|
||||
|
||||
&::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: 20px;
|
||||
color: $accent-dark;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&::after {
|
||||
color: $accent-text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.Select-value-label {
|
||||
font-size: 16px;
|
||||
font-weight: $normal;
|
||||
color: $text-dark;
|
||||
padding: 0 0 0 $pad-medium;
|
||||
line-height: 34px;
|
||||
}
|
||||
}
|
||||
|
||||
.Select-clear {
|
||||
font-size: 28px;
|
||||
margin-right: 10px;
|
||||
color: $text-dark;
|
||||
|
||||
&:hover {
|
||||
color: $alert;
|
||||
}
|
||||
}
|
||||
|
||||
.Select-loading-zone {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
&.is-empty {
|
||||
.Select-menu-outer {
|
||||
.Select-menu {
|
||||
&::after {
|
||||
content: '\f049';
|
||||
font-size: 180px;
|
||||
margin-top: -90px;
|
||||
color: $accent-medium;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include breakpoint(tablet) {
|
||||
.Select-menu-outer {
|
||||
.Select-menu {
|
||||
min-width: 665px;
|
||||
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.show-preview {
|
||||
.Select-menu-outer {
|
||||
.Select-menu {
|
||||
left: auto;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.Select-menu {
|
||||
left: 0;
|
||||
right: auto;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,9 +91,9 @@ class QueryComposer extends Component {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>
|
||||
<p className={`${baseClass}__target-label`}>
|
||||
<span className={`${baseClass}__select-targets`}>Select Targets</span>
|
||||
<span className={`${baseClass}__targets-count`}> {selectedTargetsCount} unique hosts</span>
|
||||
<span className={`${baseClass}__targets-count`}> {selectedTargetsCount} unique {selectedTargetsCount === 1 ? 'host' : 'hosts' }</span>
|
||||
</p>
|
||||
<SelectTargetsDropdown
|
||||
onFetchTargets={onFetchTargets}
|
||||
@@ -110,6 +110,7 @@ class QueryComposer extends Component {
|
||||
|
||||
return (
|
||||
<div className={`${baseClass}__wrapper`}>
|
||||
<h1>New Query</h1>
|
||||
<div className={`${baseClass}__text-editor-wrapper`}>
|
||||
<AceEditor
|
||||
enableBasicAutocompletion
|
||||
|
||||
@@ -12,13 +12,21 @@
|
||||
font-size: $base;
|
||||
}
|
||||
|
||||
&__target-label {
|
||||
margin: 0;
|
||||
font-size: $base;
|
||||
font-weight: $light;
|
||||
color: $text-dark;
|
||||
margin-bottom: $pad-xsmall;
|
||||
}
|
||||
|
||||
&__targets-count {
|
||||
font-size: $small;
|
||||
margin-left: $xsmall;
|
||||
}
|
||||
|
||||
&__text-editor-wrapper {
|
||||
margin-top: $base;
|
||||
margin: $base 0;
|
||||
}
|
||||
|
||||
&__title {
|
||||
@@ -30,5 +38,13 @@
|
||||
&__wrapper {
|
||||
background-color: $white;
|
||||
padding: $base;
|
||||
|
||||
h1 {
|
||||
font-size: $large;
|
||||
font-weight: $light;
|
||||
letter-spacing: 1.2px;
|
||||
color: $text-medium;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+191
-115
@@ -1,6 +1,4 @@
|
||||
[class^='kolidecon-'],
|
||||
[class*=' kolidecon-'],
|
||||
.kolidecon {
|
||||
%kolidecon {
|
||||
display: inline-block;
|
||||
font: normal normal normal 14px/1 'kolidecons';
|
||||
font-size: inherit;
|
||||
@@ -9,228 +7,306 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
[class^='kolidecon-'],
|
||||
[class*=' kolidecon-'],
|
||||
.kolidecon {
|
||||
@extend %kolidecon;
|
||||
}
|
||||
|
||||
.kolidecon-lg {
|
||||
font-size: 1.33333333em;
|
||||
line-height: 0.75em;
|
||||
vertical-align: -15%;
|
||||
font-size: 1.33333333em;
|
||||
line-height: 0.75em;
|
||||
vertical-align: -15%;
|
||||
}
|
||||
|
||||
.kolidecon-2x {
|
||||
font-size: 2em;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.kolidecon-3x {
|
||||
font-size: 3em;
|
||||
font-size: 3em;
|
||||
}
|
||||
|
||||
.kolidecon-4x {
|
||||
font-size: 4em;
|
||||
font-size: 4em;
|
||||
}
|
||||
|
||||
.kolidecon-5x {
|
||||
font-size: 5em;
|
||||
font-size: 5em;
|
||||
}
|
||||
|
||||
.kolidecon-fw {
|
||||
width: 1.28571429em;
|
||||
text-align: center;
|
||||
.kolidecon-fw {
|
||||
width: 1.28571429em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.kolidecon-kolide-logo-flat:before {
|
||||
content: '\f000';
|
||||
.kolidecon-kolide-logo-flat:before {
|
||||
content: '\f000';
|
||||
}
|
||||
|
||||
.kolidecon-chevrondown:before {
|
||||
content: '\f004';
|
||||
.kolidecon-chevrondown:before {
|
||||
content: '\f004';
|
||||
}
|
||||
|
||||
.kolidecon-chevronleft:before {
|
||||
content: '\f006';
|
||||
.kolidecon-chevronleft:before {
|
||||
content: '\f006';
|
||||
}
|
||||
|
||||
.kolidecon-chevronright:before {
|
||||
content: '\f008';
|
||||
.kolidecon-chevronright:before {
|
||||
content: '\f008';
|
||||
}
|
||||
|
||||
.kolidecon-chevronup:before {
|
||||
content: '\f00a';
|
||||
.kolidecon-chevronup:before {
|
||||
content: '\f00a';
|
||||
}
|
||||
|
||||
.kolidecon-cpu:before {
|
||||
content: '\f00c';
|
||||
.kolidecon-cpu:before {
|
||||
content: '\f00c';
|
||||
}
|
||||
|
||||
.kolidecon-downcarat:before {
|
||||
content: '\f00d';
|
||||
.kolidecon-downcarat:before {
|
||||
content: '\f00d';
|
||||
}
|
||||
|
||||
.kolidecon-filter:before {
|
||||
content: '\f00f';
|
||||
.kolidecon-filter:before {
|
||||
content: '\f00f';
|
||||
}
|
||||
|
||||
.kolidecon-mac:before {
|
||||
content: '\f012';
|
||||
.kolidecon-mac:before {
|
||||
content: '\f012';
|
||||
}
|
||||
|
||||
.kolidecon-memory:before {
|
||||
content: '\f013';
|
||||
.kolidecon-memory:before {
|
||||
content: '\f013';
|
||||
}
|
||||
|
||||
.kolidecon-penciledit:before {
|
||||
content: '\f015';
|
||||
.kolidecon-penciledit:before {
|
||||
content: '\f015';
|
||||
}
|
||||
|
||||
.kolidecon-storage:before {
|
||||
content: '\f019';
|
||||
.kolidecon-storage:before {
|
||||
content: '\f019';
|
||||
}
|
||||
|
||||
.kolidecon-upcarat:before {
|
||||
content: '\f01b';
|
||||
.kolidecon-upcarat:before {
|
||||
content: '\f01b';
|
||||
}
|
||||
|
||||
.kolidecon-uptime:before {
|
||||
content: '\f01c';
|
||||
.kolidecon-uptime:before {
|
||||
content: '\f01c';
|
||||
}
|
||||
|
||||
.kolidecon-world:before {
|
||||
content: '\f01d';
|
||||
.kolidecon-world:before {
|
||||
content: '\f01d';
|
||||
}
|
||||
|
||||
.kolidecon-osquery:before {
|
||||
content: '\f021';
|
||||
.kolidecon-osquery:before {
|
||||
content: '\f021';
|
||||
}
|
||||
|
||||
.kolidecon-join:before {
|
||||
content: '\f022';
|
||||
.kolidecon-join:before {
|
||||
content: '\f022';
|
||||
}
|
||||
|
||||
.kolidecon-add-button:before {
|
||||
content: '\f029';
|
||||
.kolidecon-add-button:before {
|
||||
content: '\f029';
|
||||
}
|
||||
|
||||
.kolidecon-username:before {
|
||||
content: '\f02a';
|
||||
.kolidecon-username:before {
|
||||
content: '\f02a';
|
||||
}
|
||||
|
||||
.kolidecon-password:before {
|
||||
content: '\f02b';
|
||||
.kolidecon-password:before {
|
||||
content: '\f02b';
|
||||
}
|
||||
|
||||
.kolidecon-email:before {
|
||||
content: '\f02c';
|
||||
.kolidecon-email:before {
|
||||
content: '\f02c';
|
||||
}
|
||||
|
||||
.kolidecon-query:before {
|
||||
content: '\f02d';
|
||||
.kolidecon-query:before {
|
||||
content: '\f02d';
|
||||
}
|
||||
|
||||
.kolidecon-hosts:before {
|
||||
content: '\f02e';
|
||||
.kolidecon-hosts:before {
|
||||
content: '\f02e';
|
||||
}
|
||||
|
||||
.kolidecon-packs:before {
|
||||
content: '\f02f';
|
||||
.kolidecon-packs:before {
|
||||
content: '\f02f';
|
||||
}
|
||||
|
||||
.kolidecon-help:before {
|
||||
content: '\f030';
|
||||
.kolidecon-help:before {
|
||||
content: '\f030';
|
||||
}
|
||||
|
||||
.kolidecon-admin:before {
|
||||
content: '\f031';
|
||||
.kolidecon-admin:before {
|
||||
content: '\f031';
|
||||
}
|
||||
|
||||
.kolidecon-config:before {
|
||||
content: '\f032';
|
||||
.kolidecon-config:before {
|
||||
content: '\f032';
|
||||
}
|
||||
|
||||
.kolidecon-mia:before {
|
||||
content: '\f034';
|
||||
.kolidecon-mia:before {
|
||||
content: '\f034';
|
||||
}
|
||||
|
||||
.kolidecon-success-check:before {
|
||||
content: '\f035';
|
||||
.kolidecon-success-check:before {
|
||||
content: '\f035';
|
||||
}
|
||||
|
||||
.kolidecon-offline:before {
|
||||
content: '\f036';
|
||||
.kolidecon-offline:before {
|
||||
content: '\f036';
|
||||
}
|
||||
|
||||
.kolidecon-windows:before {
|
||||
content: '\f037';
|
||||
.kolidecon-windows:before {
|
||||
content: '\f037';
|
||||
}
|
||||
|
||||
.kolidecon-centos:before {
|
||||
content: '\f038';
|
||||
.kolidecon-centos:before {
|
||||
content: '\f038';
|
||||
}
|
||||
|
||||
.kolidecon-ubuntu:before {
|
||||
content: '\f039';
|
||||
.kolidecon-ubuntu:before {
|
||||
content: '\f039';
|
||||
}
|
||||
|
||||
.kolidecon-apple:before {
|
||||
content: '\f03a';
|
||||
.kolidecon-apple:before {
|
||||
content: '\f03a';
|
||||
}
|
||||
|
||||
.kolidecon-search:before {
|
||||
content: '\f03b';
|
||||
.kolidecon-search:before {
|
||||
content: '\f03b';
|
||||
}
|
||||
|
||||
.kolidecon-all-hosts:before {
|
||||
content: '\f03c';
|
||||
.kolidecon-all-hosts:before {
|
||||
content: '\f03c';
|
||||
}
|
||||
|
||||
.kolidecon-single-host:before {
|
||||
content: '\f03d';
|
||||
.kolidecon-alerts:before {
|
||||
content: '\f03e';
|
||||
}
|
||||
|
||||
.kolidecon-alerts:before {
|
||||
content: '\f03e';
|
||||
.kolidecon-logout:before {
|
||||
content: '\f03f';
|
||||
}
|
||||
|
||||
.kolidecon-logout:before {
|
||||
content: '\f03f';
|
||||
.kolidecon-user-settings:before {
|
||||
content: '\f040';
|
||||
}
|
||||
|
||||
.kolidecon-user-settings:before {
|
||||
content: '\f040';
|
||||
.kolidecon-clipboard:before {
|
||||
content: '\f043';
|
||||
}
|
||||
|
||||
.kolidecon-clipboard:before {
|
||||
content: '\f043';
|
||||
.kolidecon-list-select:before {
|
||||
content: '\f044';
|
||||
}
|
||||
|
||||
.kolidecon-list-select:before {
|
||||
content: '\f044';
|
||||
.kolidecon-grid-select:before {
|
||||
content: '\f045';
|
||||
}
|
||||
|
||||
.kolidecon-grid-select:before {
|
||||
content: '\f045';
|
||||
.kolidecon-label:before {
|
||||
content: '\f033';
|
||||
}
|
||||
|
||||
.kolidecon-label:before {
|
||||
content: '\f033';
|
||||
.kolidecon-docker:before {
|
||||
content: '\f046';
|
||||
}
|
||||
|
||||
.kolidecon-cloud:before {
|
||||
content: '\f047';
|
||||
}
|
||||
|
||||
.kolidecon-self-hosted:before {
|
||||
content: '\f048';
|
||||
}
|
||||
|
||||
.kolidecon-help-solid:before {
|
||||
content: '\f049';
|
||||
}
|
||||
|
||||
.kolidecon-help-stroke:before {
|
||||
content: '\f04a';
|
||||
}
|
||||
|
||||
.kolidecon-warning-filled:before {
|
||||
content: '\f04b';
|
||||
}
|
||||
|
||||
.kolidecon-delete-cloud:before {
|
||||
content: '\f04c';
|
||||
}
|
||||
|
||||
.kolidecon-pdf:before {
|
||||
content: '\f04d';
|
||||
}
|
||||
|
||||
.kolidecon-credit-card-small:before {
|
||||
content: '\f04e';
|
||||
}
|
||||
|
||||
.kolidecon-billing-card:before {
|
||||
content: '\f04f';
|
||||
}
|
||||
|
||||
.kolidecon-lock-big:before {
|
||||
content: '\f050';
|
||||
}
|
||||
|
||||
.kolidecon-link-big:before {
|
||||
content: '\f051';
|
||||
}
|
||||
|
||||
.kolidecon-briefcase:before {
|
||||
content: '\f052';
|
||||
}
|
||||
|
||||
.kolidecon-name-card:before {
|
||||
content: '\f053';
|
||||
}
|
||||
|
||||
.kolidecon-kolide-logo:before {
|
||||
content: '\f054';
|
||||
}
|
||||
|
||||
.kolidecon-business:before {
|
||||
content: '\f055';
|
||||
}
|
||||
|
||||
.kolidecon-clock:before {
|
||||
content: '\f056';
|
||||
}
|
||||
|
||||
.kolidecon-host-large:before {
|
||||
content: '\f057';
|
||||
}
|
||||
|
||||
.kolidecon-single-host:before {
|
||||
content: '\f03d';
|
||||
}
|
||||
|
||||
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
border: 0
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
border: 0
|
||||
}
|
||||
|
||||
.sr-only-focusable:active,
|
||||
.sr-only-focusable:focus {
|
||||
position: static;
|
||||
width: auto;
|
||||
height: auto;
|
||||
margin: 0;
|
||||
overflow: visible;
|
||||
clip: auto
|
||||
position: static;
|
||||
width: auto;
|
||||
height: auto;
|
||||
margin: 0;
|
||||
overflow: visible;
|
||||
clip: auto
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
$min-width: 768px;
|
||||
$medium-width: 1280px;
|
||||
$max-width: 2560px;
|
||||
|
||||
@mixin breakpoint($size: desktop) {
|
||||
@if ($size == tablet) {
|
||||
@media (min-width: $min-width) and (max-width: $medium-width - 1) {
|
||||
@content;
|
||||
}
|
||||
} @else {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user