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 (
+
@@ -637,7 +639,22 @@ export class ManageHostsPage extends Component {
{renderHeader()}
- {renderHosts()}
+
+ {!(hostsLoading || loadingHosts) && }
}
diff --git a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tests.jsx b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tests.jsx
index 840eb9b25..5cc132e77 100644
--- a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tests.jsx
+++ b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tests.jsx
@@ -104,62 +104,7 @@ describe('ManageHostsPage - component', () => {
});
});
- describe('host rendering', () => {
- it('renders Spinner while hosts are loading', () => {
- const loadingProps = { ...props, loadingHosts: true };
- const page = mount(
);
-
- expect(page.find('Spinner').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);
- });
-
- 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('.manage-hosts__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('toggles between displays', () => {
- const ownProps = { location: {}, params: {} };
- const component = connectedComponent(ConnectedManageHostsPage, { props: ownProps, mockStore });
- const page = mount(component);
- const button = page.find('Rocker').find('button');
- const toggleDisplayAction = {
- type: 'SET_DISPLAY',
- payload: {
- display: 'List',
- },
- };
-
- button.simulate('click');
-
- expect(mockStore.getActions()).toInclude(toggleDisplayAction);
- });
-
+ describe('host filtering', () => {
it('filters hosts', () => {
const allHostsLabelPageNode = mount(
{
/>
).node;
- expect(allHostsLabelPageNode.filterHosts()).toEqual([hostStub, offlineHost]);
- expect(offlineHostsLabelPageNode.filterHosts()).toEqual([offlineHost]);
+ expect(allHostsLabelPageNode.filterAllHosts([hostStub, offlineHost], allHostsLabel)).toEqual([hostStub, offlineHost]);
+ expect(offlineHostsLabelPageNode.filterAllHosts([hostStub, offlineHost], offlineHostsLabel)).toEqual([offlineHost]);
});
});
diff --git a/frontend/pages/hosts/ManageHostsPage/_styles.scss b/frontend/pages/hosts/ManageHostsPage/_styles.scss
index b4836a215..772b88221 100644
--- a/frontend/pages/hosts/ManageHostsPage/_styles.scss
+++ b/frontend/pages/hosts/ManageHostsPage/_styles.scss
@@ -93,56 +93,6 @@
}
}
- &__list {
- &--grid {
- @include display(flex);
- @include justify-content(space-around);
- @include flex-wrap(wrap);
- @include align-content(center);
- margin: 0 auto;
- }
- }
-
- &__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;
- }
- }
-
&__invite-modal {
.modal__header {
span {
diff --git a/frontend/pages/hosts/ManageHostsPage/helpers.tests.js b/frontend/pages/hosts/ManageHostsPage/helpers.tests.js
index da815f20f..1a5be1a08 100644
--- a/frontend/pages/hosts/ManageHostsPage/helpers.tests.js
+++ b/frontend/pages/hosts/ManageHostsPage/helpers.tests.js
@@ -1,8 +1,8 @@
import expect from 'expect';
import moment from 'moment';
-import helpers from 'pages/hosts/ManageHostsPage/helpers';
import { hostStub, labelStub } from 'test/stubs';
+import helpers from './helpers';
const macHost = { ...hostStub, id: 1, platform: 'darwin', status: 'mia' };
const ubuntuHost = { ...hostStub, id: 2, platform: 'ubuntu', status: 'offline' };
diff --git a/frontend/utilities/scroll_to_top.js b/frontend/utilities/scroll_to_top.js
new file mode 100644
index 000000000..8428930bb
--- /dev/null
+++ b/frontend/utilities/scroll_to_top.js
@@ -0,0 +1,14 @@
+export const scrollToTop = () => {
+ const { window } = global;
+
+ const scrollStep = -window.scrollY / (500 / 15);
+ const scrollInterval = setInterval(() => {
+ if (window.scrollY !== 0) {
+ window.scrollBy(0, scrollStep);
+ } else {
+ clearInterval(scrollInterval);
+ }
+ }, 15);
+};
+
+export default scrollToTop;
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 000000000..fb820b39b
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,169 @@
+{
+ "name": "Kolide",
+ "version": "0.1.0",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "asap": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
+ "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=",
+ "dev": true
+ },
+ "babel-runtime": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
+ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
+ "dev": true,
+ "requires": {
+ "core-js": "2.5.1",
+ "regenerator-runtime": "0.11.0"
+ }
+ },
+ "core-js": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.1.tgz",
+ "integrity": "sha1-rmh03GaTd4m4B1T/VCjfZoGcpQs=",
+ "dev": true
+ },
+ "encoding": {
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
+ "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
+ "dev": true,
+ "requires": {
+ "iconv-lite": "0.4.19"
+ }
+ },
+ "fbjs": {
+ "version": "0.8.16",
+ "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz",
+ "integrity": "sha1-XmdDL1UNxBtXK/VYR7ispk5TN9s=",
+ "dev": true,
+ "requires": {
+ "core-js": "1.2.7",
+ "isomorphic-fetch": "2.2.1",
+ "loose-envify": "1.3.1",
+ "object-assign": "4.1.1",
+ "promise": "7.3.1",
+ "setimmediate": "1.0.5",
+ "ua-parser-js": "0.7.17"
+ },
+ "dependencies": {
+ "core-js": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
+ "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=",
+ "dev": true
+ },
+ "isomorphic-fetch": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
+ "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
+ "dev": true,
+ "requires": {
+ "node-fetch": "1.7.3",
+ "whatwg-fetch": "2.0.3"
+ }
+ },
+ "whatwg-fetch": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz",
+ "integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ=",
+ "dev": true
+ }
+ }
+ },
+ "iconv-lite": {
+ "version": "0.4.19",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz",
+ "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==",
+ "dev": true
+ },
+ "is-stream": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+ "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
+ "dev": true
+ },
+ "js-tokens": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
+ "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=",
+ "dev": true
+ },
+ "loose-envify": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
+ "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=",
+ "dev": true,
+ "requires": {
+ "js-tokens": "3.0.2"
+ }
+ },
+ "node-fetch": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
+ "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==",
+ "dev": true,
+ "requires": {
+ "encoding": "0.1.12",
+ "is-stream": "1.1.0"
+ }
+ },
+ "object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
+ "dev": true
+ },
+ "promise": {
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
+ "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
+ "dev": true,
+ "requires": {
+ "asap": "2.0.6"
+ }
+ },
+ "prop-types": {
+ "version": "15.6.0",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.0.tgz",
+ "integrity": "sha1-zq8IMCL8RrSjX2nhPvda7Q1jmFY=",
+ "dev": true,
+ "requires": {
+ "fbjs": "0.8.16",
+ "loose-envify": "1.3.1",
+ "object-assign": "4.1.1"
+ }
+ },
+ "rc-pagination": {
+ "version": "1.12.10",
+ "resolved": "https://registry.npmjs.org/rc-pagination/-/rc-pagination-1.12.10.tgz",
+ "integrity": "sha512-Afoxbwf759ZPu3/W8/dr1HSXITvnFT4xm7EWI7DuJ94tvQ4mreBPDqRv6TfBJ6BWYbrwEpqpf7r8Zhz40yvgtA==",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "6.26.0",
+ "prop-types": "15.6.0"
+ }
+ },
+ "regenerator-runtime": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz",
+ "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==",
+ "dev": true
+ },
+ "setimmediate": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+ "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=",
+ "dev": true
+ },
+ "ua-parser-js": {
+ "version": "0.7.17",
+ "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.17.tgz",
+ "integrity": "sha512-uRdSdu1oA1rncCQL7sCj8vSyZkgtL7faaw9Tc9rZ3mGgraQ7+Pdx7w5mnOSF3gw9ZNG6oc+KXfkon3bKuROm0g==",
+ "dev": true
+ }
+ }
+}
diff --git a/package.json b/package.json
index 83eeffccc..ff1b32a97 100644
--- a/package.json
+++ b/package.json
@@ -44,6 +44,7 @@
"node-sass": "^4.5.0",
"normalizr": "^2.2.1",
"proxy-middleware": "^0.15.0",
+ "rc-pagination": "^1.12.10",
"react": "^15.3.2",
"react-ace": "^3.6.0",
"react-addons-css-transition-group": "^15.3.2",
@@ -112,8 +113,8 @@
"typescript-require": "^0.2.9-1",
"url-loader": "0.5.8",
"webpack": "2.2.1",
- "webpack-notifier": "1.5.0",
"webpack-dev-middleware": "1.10.1",
- "webpack-hot-middleware": "2.17.1"
+ "webpack-hot-middleware": "2.17.1",
+ "webpack-notifier": "1.5.0"
}
}
diff --git a/yarn.lock b/yarn.lock
index 7c4ab030e..20513792c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -925,6 +925,13 @@ babel-register@^6.22.0, babel-register@^6.9.0:
mkdirp "^0.5.1"
source-map-support "^0.4.2"
+babel-runtime@6.x:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
+ dependencies:
+ core-js "^2.4.0"
+ regenerator-runtime "^0.11.0"
+
babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.6.1, babel-runtime@^6.9.1:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.22.0.tgz#1cf8b4ac67c77a4ddb0db2ae1f74de52ac4ca611"
@@ -2365,6 +2372,18 @@ fbjs@^0.8.1, fbjs@^0.8.4:
setimmediate "^1.0.5"
ua-parser-js "^0.7.9"
+fbjs@^0.8.16:
+ version "0.8.16"
+ resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db"
+ dependencies:
+ core-js "^1.0.0"
+ isomorphic-fetch "^2.1.1"
+ loose-envify "^1.0.0"
+ object-assign "^4.1.0"
+ promise "^7.1.1"
+ setimmediate "^1.0.5"
+ ua-parser-js "^0.7.9"
+
figures@^1.3.5:
version "1.7.0"
resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e"
@@ -3631,7 +3650,7 @@ longest@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"
-loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0:
+loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
dependencies:
@@ -4087,7 +4106,7 @@ oauth-sign@~0.8.1:
version "0.8.2"
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
-object-assign@^4.0.1, object-assign@^4.1.0:
+object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
@@ -4607,6 +4626,14 @@ promise@^7.1.1:
dependencies:
asap "~2.0.3"
+prop-types@^15.5.7:
+ version "15.6.0"
+ resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856"
+ dependencies:
+ fbjs "^0.8.16"
+ loose-envify "^1.3.1"
+ object-assign "^4.1.1"
+
propagate@0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/propagate/-/propagate-0.4.0.tgz#f3fcca0a6fe06736a7ba572966069617c130b481"
@@ -4710,6 +4737,13 @@ raw-loader@0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-0.5.1.tgz#0c3d0beaed8a01c966d9787bf778281252a979aa"
+rc-pagination@^1.12.10:
+ version "1.12.10"
+ resolved "https://registry.yarnpkg.com/rc-pagination/-/rc-pagination-1.12.10.tgz#212c24107fb0bea0ca86fbd6dcfda0ea709a45a2"
+ dependencies:
+ babel-runtime "6.x"
+ prop-types "^15.5.7"
+
rc@~1.1.6:
version "1.1.6"
resolved "https://registry.yarnpkg.com/rc/-/rc-1.1.6.tgz#43651b76b6ae53b5c802f1151fa3fc3b059969c9"
@@ -4960,6 +4994,10 @@ regenerator-runtime@^0.10.0:
version "0.10.1"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.1.tgz#257f41961ce44558b18f7814af48c17559f9faeb"
+regenerator-runtime@^0.11.0:
+ version "0.11.0"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz#7e54fe5b5ccd5d6624ea6255c3473be090b802e1"
+
regenerator-transform@0.9.8:
version "0.9.8"
resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.9.8.tgz#0f88bb2bc03932ddb7b6b7312e68078f01026d6c"