diff --git a/frontend/components/hosts/HostContainer/HostContainer.jsx b/frontend/components/hosts/HostContainer/HostContainer.jsx new file mode 100644 index 000000000..555f513f4 --- /dev/null +++ b/frontend/components/hosts/HostContainer/HostContainer.jsx @@ -0,0 +1,99 @@ +import React, { Component, PropTypes } from 'react'; + +import hostInterface from 'interfaces/host'; +import labelInterface from 'interfaces/label'; +import HostsTable from 'components/hosts/HostsTable'; +import HostDetails from 'components/hosts/HostDetails'; +import LonelyHost from 'components/hosts/LonelyHost'; +import Spinner from 'components/loaders/Spinner'; + +const baseClass = 'host-container'; + +class HostContainer extends Component { + static propTypes = { + hosts: PropTypes.arrayOf(hostInterface), + selectedLabel: labelInterface, + loadingHosts: PropTypes.bool.isRequired, + displayType: PropTypes.oneOf(['Grid', 'List']), + toggleAddHostModal: PropTypes.func, + toggleDeleteHostModal: PropTypes.func, + onQueryHost: PropTypes.func, + }; + + renderNoHosts = () => { + const { selectedLabel } = this.props; + const { type } = selectedLabel || ''; + const isCustom = type === 'custom'; + + return ( +
+

No matching hosts found.

+

Where are the missing hosts?

+ + +
+

Still having trouble? Want to talk to a human?

+

Contact Kolide Support:

+

support@kolide.co

+
+
+ ); + } + + renderHosts = () => { + const { displayType, hosts, toggleDeleteHostModal, onQueryHost } = this.props; + + if (displayType === 'Grid') { + return hosts.map((host) => { + const isLoading = !host.hostname; + + return ( + + ); + }); + } + + return ( + + ); + } + + render () { + const { renderHosts, renderNoHosts } = this; + const { hosts, displayType, loadingHosts, selectedLabel, toggleAddHostModal } = this.props; + + if (loadingHosts) { + return ; + } + + if (hosts.length === 0) { + if (selectedLabel && selectedLabel.type === 'all') { + return ; + } + + return renderNoHosts(); + } + + return ( +
+ {renderHosts()} +
+ ); + } +} + +export default HostContainer; diff --git a/frontend/components/hosts/HostContainer/HostContainer.tests.jsx b/frontend/components/hosts/HostContainer/HostContainer.tests.jsx new file mode 100644 index 000000000..f578a1222 --- /dev/null +++ b/frontend/components/hosts/HostContainer/HostContainer.tests.jsx @@ -0,0 +1,60 @@ +import React from 'react'; +import expect from 'expect'; +import { noop } from 'lodash'; +import { mount } from 'enzyme'; + +import { hostStub } from 'test/stubs'; +import HostContainer from './HostContainer'; + +const allHostsLabel = { id: 1, display_text: 'All Hosts', slug: 'all-hosts', type: 'all', count: 22 }; +const customLabel = { id: 6, display_text: 'Custom Label', slug: 'custom-label', type: 'custom', count: 3 }; + +describe('HostsContainer - component', () => { + const props = { + hosts: [hostStub], + selectedLabel: allHostsLabel, + loadingHosts: false, + displayType: 'Grid', + toggleAddHostModal: noop, + toggleDeleteHostModal: noop, + onQueryHost: noop, + }; + + it('renders Spinner while hosts are loading', () => { + const loadingProps = { ...props, loadingHosts: true }; + const page = mount(); + + expect(page.find('Spinner').length).toEqual(1); + }); + + it('render LonelyHost if no hosts available', () => { + const page = mount(); + + expect(page.find('LonelyHost').length).toEqual(1); + }); + + it('renders message if no hosts available and not on All Hosts', () => { + const page = mount(); + + expect(page.find('.host-container--no-hosts').length).toEqual(1); + }); + + it('renders hosts as HostDetails by default', () => { + const page = mount(); + + expect(page.find('HostDetails').length).toEqual(1); + }); + + it('renders hosts as HostsTable when the display is "List"', () => { + const page = mount(); + + expect(page.find('HostsTable').length).toEqual(1); + }); + + it('does not render sidebar if labels are loading', () => { + const loadingProps = { ...props, loadingLabels: true }; + const page = mount(); + + expect(page.find('HostSidePanel').length).toEqual(0); + }); +}); diff --git a/frontend/components/hosts/HostContainer/_styles.scss b/frontend/components/hosts/HostContainer/_styles.scss new file mode 100644 index 000000000..4d371932d --- /dev/null +++ b/frontend/components/hosts/HostContainer/_styles.scss @@ -0,0 +1,49 @@ +.host-container { + &--no-hosts { + width: 440px; + margin: 35px auto; + font-size: 15px; + font-weight: $normal; + line-height: 2; + letter-spacing: normal; + color: rgba(32, 37, 50, 0.66); + + h1 { + font-size: 32px; + font-weight: $normal; + line-height: normal; + letter-spacing: normal; + color: #48c586; + } + + h2 { + font-size: 16px; + font-weight: $bold; + line-height: 1.5; + letter-spacing: -0.5px; + color: rgba(32, 37, 50, 0.66); + } + + ul { + margin: 0; + padding-left: 20px; + } + } + + &__no-hosts-contact { + text-align: right; + margin-top: 30px; + + p { + margin: 0; + } + } + + &--grid { + @include display(flex); + @include justify-content(space-around); + @include flex-wrap(wrap); + @include align-content(center); + margin: 0 auto; + } +} diff --git a/frontend/components/hosts/HostContainer/index.js b/frontend/components/hosts/HostContainer/index.js new file mode 100644 index 000000000..ec513ea31 --- /dev/null +++ b/frontend/components/hosts/HostContainer/index.js @@ -0,0 +1 @@ +export default from './HostContainer'; diff --git a/frontend/components/hosts/HostPagination/HostPagination.jsx b/frontend/components/hosts/HostPagination/HostPagination.jsx new file mode 100644 index 000000000..a31d69eba --- /dev/null +++ b/frontend/components/hosts/HostPagination/HostPagination.jsx @@ -0,0 +1,70 @@ +import React, { PureComponent, PropTypes } from 'react'; +import Pagination from 'rc-pagination'; +import Select from 'react-select'; +import 'rc-pagination/assets/index.css'; + +import enUs from 'rc-pagination/lib/locale/en_US'; + +const baseClass = 'host-pagination'; + +class HostPagination extends PureComponent { + static propTypes = { + allHostCount: PropTypes.number, + currentPage: PropTypes.number, + hostsPerPage: PropTypes.number, + onPaginationChange: PropTypes.func, + onPerPageChange: PropTypes.func, + }; + + render () { + const { + allHostCount, + currentPage, + hostsPerPage, + onPaginationChange, + onPerPageChange, + } = this.props; + + const paginationSelectOpts = [ + { value: 20, label: '20' }, + { value: 100, label: '100' }, + { value: 500, label: '500' }, + { value: 1000, label: '1,000' }, + ]; + + const humanPage = currentPage + 1; + const startRange = (currentPage * hostsPerPage) + 1; + const endRange = Math.min(humanPage * hostsPerPage, allHostCount); + + if (allHostCount === 0) { + return false; + } + + return ( +
+ +

{`${startRange} - ${endRange} of ${allHostCount} hosts`}

+
+