diff --git a/frontend/components/side_panels/HostSidePanel/PanelGroupItem/_styles.scss b/frontend/components/side_panels/HostSidePanel/PanelGroupItem/_styles.scss index 69e1dcc5be..8ca146fc64 100644 --- a/frontend/components/side_panels/HostSidePanel/PanelGroupItem/_styles.scss +++ b/frontend/components/side_panels/HostSidePanel/PanelGroupItem/_styles.scss @@ -77,6 +77,15 @@ $base-class: 'panel-group-item'; } } + &--new { + @include selected-hover($warning); + + .#{$base-class}__count, + .#{$base-class}__icon { + color: $warning; + } + } + &--online { @include selected-hover($success); diff --git a/frontend/interfaces/status_labels.js b/frontend/interfaces/status_labels.js index dd6698217f..610383979b 100644 --- a/frontend/interfaces/status_labels.js +++ b/frontend/interfaces/status_labels.js @@ -2,6 +2,7 @@ import { PropTypes } from 'react'; export default PropTypes.shape({ loading_counts: PropTypes.bool, + new_count: PropTypes.number, online_count: PropTypes.number, offline_count: PropTypes.number, mia_count: PropTypes.number, diff --git a/frontend/kolide/index.js b/frontend/kolide/index.js index 72715b8ce2..d4904e52eb 100644 --- a/frontend/kolide/index.js +++ b/frontend/kolide/index.js @@ -291,9 +291,10 @@ class Kolide extends Base { }; }); const stubbedLabels = [ - { id: 'online', display_text: 'ONLINE', slug: 'online', type: 'status', count: 0 }, - { id: 'offline', display_text: 'OFFLINE', slug: 'offline', type: 'status', count: 0 }, - { id: 'mia', display_text: 'MIA', description: '(offline > 30 days)', slug: 'mia', type: 'status', count: 0 }, + { id: 'new', display_text: 'NEW', description: '(added in last 24hrs)', slug: 'recently_added', type: 'status', count: 0, statusLabelKey: 'new_count' }, + { id: 'online', display_text: 'ONLINE', slug: 'online', type: 'status', count: 0, statusLabelKey: 'online_count' }, + { id: 'offline', display_text: 'OFFLINE', slug: 'offline', type: 'status', count: 0, statusLabelKey: 'offline_count' }, + { id: 'mia', display_text: 'MIA', description: '(offline > 30 days)', slug: 'mia', type: 'status', count: 0, statusLabelKey: 'mia_count' }, ]; return labels.concat(stubbedLabels); diff --git a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.jsx b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.jsx index 167cc5d792..179f1bfb96 100644 --- a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.jsx +++ b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.jsx @@ -1,8 +1,9 @@ import React, { Component, PropTypes } from 'react'; import AceEditor from 'react-ace'; import { connect } from 'react-redux'; +import { filter, orderBy, sortBy } from 'lodash'; +import moment from 'moment'; import { push } from 'react-router-redux'; -import { orderBy, sortBy } from 'lodash'; import entityGetter from 'redux/utilities/entityGetter'; import { getStatusLabelCounts, setDisplay } from 'redux/nodes/components/ManageHostsPage/actions'; @@ -258,7 +259,7 @@ export class ManageHostsPage extends Component { return false; } - const { count, description, display_text: displayText, slug, type } = selectedLabel; + const { count, description, display_text: displayText, statusLabelKey, type } = selectedLabel; const { onToggleDisplay } = this; const buttonOptions = { rightIcon: 'grid-select', @@ -267,7 +268,7 @@ export class ManageHostsPage extends Component { leftText: 'List', }; - const hostCount = type === 'status' ? statusLabels[`${slug}_count`] : count; + const hostCount = type === 'status' ? statusLabels[`${statusLabelKey}`] : count; const hostsTotalDisplay = hostCount === 1 ? '1 Host Total' : `${hostCount} Hosts Total`; return ( @@ -452,6 +453,11 @@ const mapStateToProps = (state, { location, params }) => { const { selectedOsqueryTable } = state.components.QueryPages; const labelErrors = state.entities.labels.errors; + // TODO: remove this once the API is updated to return new_count + statusLabels.new_count = filter(hosts, (h) => { + return moment().diff(h.created_at, 'hours') <= 24; + }).length; + return { display, hosts, diff --git a/frontend/pages/hosts/ManageHostsPage/_styles.scss b/frontend/pages/hosts/ManageHostsPage/_styles.scss index 70b29d67e7..5df8e4a468 100644 --- a/frontend/pages/hosts/ManageHostsPage/_styles.scss +++ b/frontend/pages/hosts/ManageHostsPage/_styles.scss @@ -25,6 +25,10 @@ .kolidecon-mia { color: $text-ultradark; } + + .kolidecon-clock { + color: $warning; + } } &__delete-label { diff --git a/frontend/pages/hosts/ManageHostsPage/helpers.js b/frontend/pages/hosts/ManageHostsPage/helpers.js index c8c7ba27b4..4b61c8e606 100644 --- a/frontend/pages/hosts/ManageHostsPage/helpers.js +++ b/frontend/pages/hosts/ManageHostsPage/helpers.js @@ -1,10 +1,15 @@ import { filter, includes } from 'lodash'; +import moment from 'moment'; const filterHosts = (hosts, label) => { if (!label) { return hosts; } + if (label.id === 'new') { + return filter(hosts, h => moment().diff(h.created_at, 'hours') <= 24); + } + const { host_ids: hostIDs, platform, slug, type } = label; switch (type) { diff --git a/frontend/utilities/icon_class_for_label.js b/frontend/utilities/icon_class_for_label.js index 586eb7229a..6324b0571f 100644 --- a/frontend/utilities/icon_class_for_label.js +++ b/frontend/utilities/icon_class_for_label.js @@ -8,6 +8,7 @@ export const iconClassForLabel = (label) => { case 'offline': return 'offline'; case 'online': return 'success-check'; case 'mia': return 'mia'; + case 'new': return 'clock'; case 'unknown': return 'single-host'; default: return 'label'; }