mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 08:55:24 +00:00
Hosts Pagination (#1594)
This commit is contained in:
parent
0ad4caa95c
commit
78b831a6d2
99
frontend/components/hosts/HostContainer/HostContainer.jsx
Normal file
99
frontend/components/hosts/HostContainer/HostContainer.jsx
Normal file
@ -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 (
|
||||
<div className={`${baseClass} ${baseClass}--no-hosts`}>
|
||||
<h1>No matching hosts found.</h1>
|
||||
<h2>Where are the missing hosts?</h2>
|
||||
<ul>
|
||||
{isCustom && <li>Check your SQL query above to confirm there are no mistakes.</li>}
|
||||
<li>Check to confirm that your hosts are online.</li>
|
||||
<li>Confirm that your expected hosts have osqueryd installed and configured.</li>
|
||||
</ul>
|
||||
|
||||
<div className={`${baseClass}__no-hosts-contact`}>
|
||||
<p>Still having trouble? Want to talk to a human?</p>
|
||||
<p>Contact Kolide Support:</p>
|
||||
<p><a href="mailto:support@kolide.co">support@kolide.co</a></p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderHosts = () => {
|
||||
const { displayType, hosts, toggleDeleteHostModal, onQueryHost } = this.props;
|
||||
|
||||
if (displayType === 'Grid') {
|
||||
return hosts.map((host) => {
|
||||
const isLoading = !host.hostname;
|
||||
|
||||
return (
|
||||
<HostDetails
|
||||
host={host}
|
||||
key={`host-${host.id}-details`}
|
||||
onDestroyHost={toggleDeleteHostModal}
|
||||
onQueryHost={onQueryHost}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<HostsTable
|
||||
hosts={hosts}
|
||||
onDestroyHost={toggleDeleteHostModal}
|
||||
onQueryHost={onQueryHost}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { renderHosts, renderNoHosts } = this;
|
||||
const { hosts, displayType, loadingHosts, selectedLabel, toggleAddHostModal } = this.props;
|
||||
|
||||
if (loadingHosts) {
|
||||
return <Spinner />;
|
||||
}
|
||||
|
||||
if (hosts.length === 0) {
|
||||
if (selectedLabel && selectedLabel.type === 'all') {
|
||||
return <LonelyHost onClick={toggleAddHostModal} />;
|
||||
}
|
||||
|
||||
return renderNoHosts();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`${baseClass} ${baseClass}--${displayType.toLowerCase()}`}>
|
||||
{renderHosts()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default HostContainer;
|
@ -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(<HostContainer {...loadingProps} hosts={[]} selectedLabel={allHostsLabel} />);
|
||||
|
||||
expect(page.find('Spinner').length).toEqual(1);
|
||||
});
|
||||
|
||||
it('render LonelyHost if no hosts available', () => {
|
||||
const page = mount(<HostContainer {...props} hosts={[]} selectedLabel={allHostsLabel} />);
|
||||
|
||||
expect(page.find('LonelyHost').length).toEqual(1);
|
||||
});
|
||||
|
||||
it('renders message if no hosts available and not on All Hosts', () => {
|
||||
const page = mount(<HostContainer {...props} hosts={[]} selectedLabel={customLabel} />);
|
||||
|
||||
expect(page.find('.host-container--no-hosts').length).toEqual(1);
|
||||
});
|
||||
|
||||
it('renders hosts as HostDetails by default', () => {
|
||||
const page = mount(<HostContainer {...props} />);
|
||||
|
||||
expect(page.find('HostDetails').length).toEqual(1);
|
||||
});
|
||||
|
||||
it('renders hosts as HostsTable when the display is "List"', () => {
|
||||
const page = mount(<HostContainer {...props} displayType="List" />);
|
||||
|
||||
expect(page.find('HostsTable').length).toEqual(1);
|
||||
});
|
||||
|
||||
it('does not render sidebar if labels are loading', () => {
|
||||
const loadingProps = { ...props, loadingLabels: true };
|
||||
const page = mount(<HostContainer {...loadingProps} hosts={[]} selectedLabel={allHostsLabel} />);
|
||||
|
||||
expect(page.find('HostSidePanel').length).toEqual(0);
|
||||
});
|
||||
});
|
49
frontend/components/hosts/HostContainer/_styles.scss
Normal file
49
frontend/components/hosts/HostContainer/_styles.scss
Normal file
@ -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;
|
||||
}
|
||||
}
|
1
frontend/components/hosts/HostContainer/index.js
Normal file
1
frontend/components/hosts/HostContainer/index.js
Normal file
@ -0,0 +1 @@
|
||||
export default from './HostContainer';
|
70
frontend/components/hosts/HostPagination/HostPagination.jsx
Normal file
70
frontend/components/hosts/HostPagination/HostPagination.jsx
Normal file
@ -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 (
|
||||
<div className={`${baseClass}__pager-wrap`}>
|
||||
<Pagination
|
||||
onChange={onPaginationChange}
|
||||
current={humanPage}
|
||||
total={allHostCount}
|
||||
pageSize={hostsPerPage}
|
||||
className={`${baseClass}__pagination`}
|
||||
locale={enUs}
|
||||
showLessItems
|
||||
/>
|
||||
<p className={`${baseClass}__pager-range`}>{`${startRange} - ${endRange} of ${allHostCount} hosts`}</p>
|
||||
<div className={`${baseClass}__pager-count`}>
|
||||
<Select
|
||||
name="pager-host-count"
|
||||
value={hostsPerPage}
|
||||
options={paginationSelectOpts}
|
||||
onChange={onPerPageChange}
|
||||
className={`${baseClass}__count-select`}
|
||||
clearable={false}
|
||||
/> <span>Hosts per page</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default HostPagination;
|
132
frontend/components/hosts/HostPagination/_style.scss
Normal file
132
frontend/components/hosts/HostPagination/_style.scss
Normal file
@ -0,0 +1,132 @@
|
||||
.host-pagination {
|
||||
&__pager-wrap {
|
||||
flex-basis: 100%;
|
||||
margin: 50px 0 115px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
&__pagination {
|
||||
margin: 0 25px 0 0;
|
||||
padding: 0;
|
||||
font-family: 'Oxygen', 'Helvetica Neue', 'Helvetica', 'Roboto', 'Arial', sans-serif;
|
||||
|
||||
.rc-pagination-prev,
|
||||
.rc-pagination-next,
|
||||
.rc-pagination-item {
|
||||
border: 1px solid $success;
|
||||
font-size: 15px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 3px;
|
||||
|
||||
a {
|
||||
color: $success;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.rc-pagination-prev {
|
||||
a {
|
||||
&::after {
|
||||
@extend %kolidecon;
|
||||
content: '\f006';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rc-pagination-next {
|
||||
a {
|
||||
&::after {
|
||||
@extend %kolidecon;
|
||||
content: '\f008';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rc-pagination-item {
|
||||
border: 0;
|
||||
|
||||
&:hover {
|
||||
background-color: $success-light;
|
||||
|
||||
a {
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rc-pagination-jump-prev,
|
||||
.rc-pagination-jump-next {
|
||||
&::after {
|
||||
color: $success;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&::after {
|
||||
font-size: 22px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rc-pagination-disabled {
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
.rc-pagination-item-active {
|
||||
background-color: $success;
|
||||
|
||||
a {
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__pager-count {
|
||||
flex-basis: 100%;
|
||||
margin: 15px 0 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&__count-select {
|
||||
width: 90px;
|
||||
margin-right: 15px;
|
||||
|
||||
.Select-control {
|
||||
border-color: $success;
|
||||
border-radius: 3px;
|
||||
height: 30px;
|
||||
|
||||
@at-root .Select.is-focused#{&} {
|
||||
border-color: $success;
|
||||
}
|
||||
}
|
||||
|
||||
.Select-value {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&.Select--single {
|
||||
> .Select-control {
|
||||
.Select-value {
|
||||
line-height: 30px;
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.Select-input {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.Select-option {
|
||||
&.is-focused {
|
||||
background-color: $success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__pager-range {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
1
frontend/components/hosts/HostPagination/index.js
Normal file
1
frontend/components/hosts/HostPagination/index.js
Normal file
@ -0,0 +1 @@
|
||||
export default from './HostPagination';
|
@ -4,8 +4,6 @@
|
||||
border-radius: 3px;
|
||||
box-shadow: inset 0 0 8px 0 rgba(0, 0, 0, 0.12);
|
||||
margin-top: $pad-base;
|
||||
max-height: 85vh;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
&__table {
|
||||
|
@ -2,38 +2,37 @@ import React, { Component, PropTypes } from 'react';
|
||||
import AceEditor from 'react-ace';
|
||||
import { connect } from 'react-redux';
|
||||
import FileSaver from 'file-saver';
|
||||
import { orderBy, sortBy } from 'lodash';
|
||||
import { push } from 'react-router-redux';
|
||||
import { isEqual, orderBy, slice, sortBy } from 'lodash';
|
||||
|
||||
import deepDifference from 'utilities/deep_difference';
|
||||
import entityGetter from 'redux/utilities/entityGetter';
|
||||
import Kolide from 'kolide';
|
||||
import AddHostModal from 'components/hosts/AddHostModal';
|
||||
import Button from 'components/buttons/Button';
|
||||
import HostContainer from 'components/hosts/HostContainer';
|
||||
import HostPagination from 'components/hosts/HostPagination';
|
||||
import HostSidePanel from 'components/side_panels/HostSidePanel';
|
||||
import Icon from 'components/icons/Icon';
|
||||
import LabelForm from 'components/forms/LabelForm';
|
||||
import Modal from 'components/modals/Modal';
|
||||
import PlatformIcon from 'components/icons/PlatformIcon';
|
||||
import QuerySidePanel from 'components/side_panels/QuerySidePanel';
|
||||
import Rocker from 'components/buttons/Rocker';
|
||||
import labelInterface from 'interfaces/label';
|
||||
import hostInterface from 'interfaces/host';
|
||||
import osqueryTableInterface from 'interfaces/osquery_table';
|
||||
import statusLabelsInterface from 'interfaces/status_labels';
|
||||
import { selectOsqueryTable } from 'redux/nodes/components/QueryPages/actions';
|
||||
import { getStatusLabelCounts, setDisplay, silentGetStatusLabelCounts } from 'redux/nodes/components/ManageHostsPage/actions';
|
||||
import helpers from 'pages/hosts/ManageHostsPage/helpers';
|
||||
import hostActions from 'redux/nodes/entities/hosts/actions';
|
||||
import labelActions from 'redux/nodes/entities/labels/actions';
|
||||
import labelInterface from 'interfaces/label';
|
||||
import HostDetails from 'components/hosts/HostDetails';
|
||||
import hostInterface from 'interfaces/host';
|
||||
import HostSidePanel from 'components/side_panels/HostSidePanel';
|
||||
import HostsTable from 'components/hosts/HostsTable';
|
||||
import LabelForm from 'components/forms/LabelForm';
|
||||
import LonelyHost from 'components/hosts/LonelyHost';
|
||||
import AddHostModal from 'components/hosts/AddHostModal';
|
||||
import Icon from 'components/icons/Icon';
|
||||
import Spinner from 'components/loaders/Spinner';
|
||||
import Kolide from 'kolide';
|
||||
import PlatformIcon from 'components/icons/PlatformIcon';
|
||||
import osqueryTableInterface from 'interfaces/osquery_table';
|
||||
import paths from 'router/paths';
|
||||
import QuerySidePanel from 'components/side_panels/QuerySidePanel';
|
||||
import { renderFlash } from 'redux/nodes/notifications/actions';
|
||||
import Rocker from 'components/buttons/Rocker';
|
||||
import Button from 'components/buttons/Button';
|
||||
import Modal from 'components/modals/Modal';
|
||||
import { selectOsqueryTable } from 'redux/nodes/components/QueryPages/actions';
|
||||
import statusLabelsInterface from 'interfaces/status_labels';
|
||||
import entityGetter from 'redux/utilities/entityGetter';
|
||||
import paths from 'router/paths';
|
||||
import deepDifference from 'utilities/deep_difference';
|
||||
import iconClassForLabel from 'utilities/icon_class_for_label';
|
||||
import platformIconClass from 'utilities/platform_icon_class';
|
||||
import scrollToTop from 'utilities/scroll_to_top';
|
||||
import helpers from './helpers';
|
||||
|
||||
const NEW_LABEL_HASH = '#new_label';
|
||||
const baseClass = 'manage-hosts';
|
||||
@ -66,12 +65,18 @@ export class ManageHostsPage extends Component {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
allHostCount: 0,
|
||||
currentPaginationPage: 0,
|
||||
hostsLoading: false,
|
||||
hostsPerPage: 20,
|
||||
isEditLabel: false,
|
||||
labelQueryText: '',
|
||||
pagedHosts: [],
|
||||
showDeleteHostModal: false,
|
||||
showAddHostModal: false,
|
||||
selectedHost: null,
|
||||
showDeleteLabelModal: false,
|
||||
showHostContainerSpinner: false,
|
||||
};
|
||||
}
|
||||
|
||||
@ -79,6 +84,7 @@ export class ManageHostsPage extends Component {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(hostActions.loadAll());
|
||||
this.buildSortedHosts();
|
||||
|
||||
return this.getEntities();
|
||||
}
|
||||
@ -91,6 +97,15 @@ export class ManageHostsPage extends Component {
|
||||
return false;
|
||||
}
|
||||
|
||||
shouldComponentUpdate (nextProps, nextState) {
|
||||
if (isEqual(nextProps, this.props) && isEqual(nextState, this.state)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.buildSortedHosts(nextProps, nextState);
|
||||
return true;
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
if (this.interval) {
|
||||
global.window.clearInterval(this.interval);
|
||||
@ -181,6 +196,11 @@ export class ManageHostsPage extends Component {
|
||||
|
||||
dispatch(push(nextLocation));
|
||||
|
||||
this.setState({
|
||||
currentPaginationPage: 0,
|
||||
hostsLoading: true,
|
||||
});
|
||||
|
||||
return false;
|
||||
};
|
||||
}
|
||||
@ -193,6 +213,30 @@ export class ManageHostsPage extends Component {
|
||||
return false;
|
||||
}
|
||||
|
||||
onPaginationChange = (page) => {
|
||||
this.setState({
|
||||
currentPaginationPage: page - 1,
|
||||
hostsLoading: true,
|
||||
});
|
||||
|
||||
scrollToTop();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
onPerPageChange = (option) => {
|
||||
this.setState({
|
||||
currentPaginationPage: 0,
|
||||
hostsPerPage: Number(option.value),
|
||||
hostsLoading: true,
|
||||
showHostContainerSpinner: true,
|
||||
});
|
||||
|
||||
scrollToTop();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
onSaveAddLabel = (formData) => {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
@ -256,6 +300,38 @@ export class ManageHostsPage extends Component {
|
||||
return false;
|
||||
}
|
||||
|
||||
buildSortedHosts = (nextProps, nextState) => {
|
||||
const { filterAllHosts, sortHosts } = this;
|
||||
const { currentPaginationPage, hostsPerPage } = nextState || this.state;
|
||||
const { hosts, selectedLabel } = this.props;
|
||||
|
||||
const sortedHosts = sortHosts(filterAllHosts(hosts, selectedLabel));
|
||||
|
||||
const fromIndex = currentPaginationPage * hostsPerPage;
|
||||
const toIndex = fromIndex + hostsPerPage;
|
||||
|
||||
const pagedHosts = slice(sortedHosts, fromIndex, toIndex);
|
||||
|
||||
this.setState({
|
||||
allHostCount: sortedHosts.length,
|
||||
hostsLoading: false,
|
||||
pagedHosts,
|
||||
});
|
||||
}
|
||||
|
||||
filterAllHosts = (hosts, selectedLabel) => {
|
||||
const { filterHosts } = helpers;
|
||||
|
||||
return filterHosts(hosts, selectedLabel);
|
||||
}
|
||||
|
||||
sortHosts = (hosts) => {
|
||||
const alphaHosts = sortBy(hosts, (h) => { return h.hostname; });
|
||||
const orderedHosts = orderBy(alphaHosts, 'status', 'desc');
|
||||
|
||||
return orderedHosts;
|
||||
}
|
||||
|
||||
toggleAddHostModal = () => {
|
||||
const { showAddHostModal } = this.state;
|
||||
this.setState({ showAddHostModal: !showAddHostModal });
|
||||
@ -290,19 +366,6 @@ export class ManageHostsPage extends Component {
|
||||
return false;
|
||||
}
|
||||
|
||||
filterHosts = () => {
|
||||
const { hosts, selectedLabel } = this.props;
|
||||
|
||||
return helpers.filterHosts(hosts, selectedLabel);
|
||||
}
|
||||
|
||||
sortHosts = (hosts) => {
|
||||
const alphaHosts = sortBy(hosts, (h) => { return h.hostname; });
|
||||
const orderedHosts = orderBy(alphaHosts, 'status', 'desc');
|
||||
|
||||
return orderedHosts;
|
||||
}
|
||||
|
||||
renderAddHostModal = () => {
|
||||
const { onFetchCertificate, toggleAddHostModal } = this;
|
||||
const { showAddHostModal } = this.state;
|
||||
@ -473,79 +536,6 @@ export class ManageHostsPage extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
renderNoHosts = () => {
|
||||
const { selectedLabel } = this.props;
|
||||
const { type } = selectedLabel || '';
|
||||
const isCustom = type === 'custom';
|
||||
|
||||
return (
|
||||
<div className={`${baseClass}__no-hosts`}>
|
||||
<h1>No matching hosts found.</h1>
|
||||
<h2>Where are the missing hosts?</h2>
|
||||
<ul>
|
||||
{isCustom && <li>Check your SQL query above to confirm there are no mistakes.</li>}
|
||||
<li>Check to confirm that your hosts are online.</li>
|
||||
<li>Confirm that your expected hosts have osqueryd installed and configured.</li>
|
||||
</ul>
|
||||
|
||||
<div className={`${baseClass}__no-hosts-contact`}>
|
||||
<p>Still having trouble? Want to talk to a human?</p>
|
||||
<p>Contact Kolide Support:</p>
|
||||
<p><a href="mailto:support@kolide.co">support@kolide.co</a></p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderHosts = () => {
|
||||
const { display, isAddLabel, selectedLabel, loadingHosts } = this.props;
|
||||
const { toggleDeleteHostModal, filterHosts, onQueryHost, sortHosts, renderNoHosts, toggleAddHostModal } = this;
|
||||
|
||||
if (isAddLabel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const filteredHosts = filterHosts();
|
||||
const sortedHosts = sortHosts(filteredHosts);
|
||||
|
||||
if (loadingHosts) {
|
||||
return <Spinner />;
|
||||
}
|
||||
|
||||
if (sortedHosts.length === 0 && !loadingHosts) {
|
||||
if (selectedLabel && selectedLabel.type === 'all') {
|
||||
return <LonelyHost onClick={toggleAddHostModal} />;
|
||||
}
|
||||
|
||||
return renderNoHosts();
|
||||
}
|
||||
|
||||
if (display === 'Grid') {
|
||||
return sortedHosts.map((host) => {
|
||||
const isLoading = !host.hostname;
|
||||
|
||||
return (
|
||||
<HostDetails
|
||||
host={host}
|
||||
key={`host-${host.id}-details`}
|
||||
onDestroyHost={toggleDeleteHostModal}
|
||||
onQueryHost={onQueryHost}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<HostsTable
|
||||
hosts={sortedHosts}
|
||||
onDestroyHost={toggleDeleteHostModal}
|
||||
onQueryHost={onQueryHost}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
renderForm = () => {
|
||||
const { isAddLabel, labelErrors, selectedLabel } = this.props;
|
||||
const { isEditLabel } = this.state;
|
||||
@ -625,9 +615,21 @@ export class ManageHostsPage extends Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { renderForm, renderHeader, renderHosts, renderSidePanel, renderAddHostModal, renderDeleteHostModal, renderDeleteLabelModal } = this;
|
||||
const { display, isAddLabel, loadingLabels } = this.props;
|
||||
const { isEditLabel } = this.state;
|
||||
const {
|
||||
onQueryHost,
|
||||
onPerPageChange,
|
||||
onPaginationChange,
|
||||
renderForm,
|
||||
renderHeader,
|
||||
renderSidePanel,
|
||||
renderAddHostModal,
|
||||
renderDeleteHostModal,
|
||||
renderDeleteLabelModal,
|
||||
toggleAddHostModal,
|
||||
toggleDeleteHostModal,
|
||||
} = this;
|
||||
const { display, isAddLabel, loadingLabels, loadingHosts, selectedLabel } = this.props;
|
||||
const { allHostCount, currentPaginationPage, hostsPerPage, hostsLoading, isEditLabel, pagedHosts } = this.state;
|
||||
|
||||
return (
|
||||
<div className="has-sidebar">
|
||||
@ -637,7 +639,22 @@ export class ManageHostsPage extends Component {
|
||||
<div className={`${baseClass} body-wrap`}>
|
||||
{renderHeader()}
|
||||
<div className={`${baseClass}__list ${baseClass}__list--${display.toLowerCase()}`}>
|
||||
{renderHosts()}
|
||||
<HostContainer
|
||||
hosts={pagedHosts}
|
||||
selectedLabel={selectedLabel}
|
||||
displayType={display}
|
||||
loadingHosts={hostsLoading || loadingHosts}
|
||||
toggleAddHostModal={toggleAddHostModal}
|
||||
toggleDeleteHostModal={toggleDeleteHostModal}
|
||||
onQueryHost={onQueryHost}
|
||||
/>
|
||||
{!(hostsLoading || loadingHosts) && <HostPagination
|
||||
allHostCount={allHostCount}
|
||||
currentPage={currentPaginationPage}
|
||||
hostsPerPage={hostsPerPage}
|
||||
onPaginationChange={onPaginationChange}
|
||||
onPerPageChange={onPerPageChange}
|
||||
/>}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
@ -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(<ManageHostsPage {...loadingProps} hosts={[]} selectedLabel={allHostsLabel} />);
|
||||
|
||||
expect(page.find('Spinner').length).toEqual(1);
|
||||
});
|
||||
|
||||
it('does not render sidebar if labels are loading', () => {
|
||||
const loadingProps = { ...props, loadingLabels: true };
|
||||
const page = mount(<ManageHostsPage {...loadingProps} hosts={[]} selectedLabel={allHostsLabel} />);
|
||||
|
||||
expect(page.find('HostSidePanel').length).toEqual(0);
|
||||
});
|
||||
|
||||
it('render LonelyHost if no hosts available', () => {
|
||||
const page = mount(<ManageHostsPage {...props} hosts={[]} selectedLabel={allHostsLabel} />);
|
||||
|
||||
expect(page.find('LonelyHost').length).toEqual(1);
|
||||
});
|
||||
|
||||
it('renders message if no hosts available and not on All Hosts', () => {
|
||||
const page = mount(<ManageHostsPage {...props} hosts={[]} selectedLabel={customLabel} />);
|
||||
|
||||
expect(page.find('.manage-hosts__no-hosts').length).toEqual(1);
|
||||
});
|
||||
|
||||
it('renders hosts as HostDetails by default', () => {
|
||||
const page = mount(<ManageHostsPage {...props} hosts={[hostStub]} />);
|
||||
|
||||
expect(page.find('HostDetails').length).toEqual(1);
|
||||
});
|
||||
|
||||
it('renders hosts as HostsTable when the display is "List"', () => {
|
||||
const page = mount(<ManageHostsPage {...props} display="List" hosts={[hostStub]} />);
|
||||
|
||||
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(
|
||||
<ManageHostsPage
|
||||
@ -176,8 +121,8 @@ describe('ManageHostsPage - component', () => {
|
||||
/>
|
||||
).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]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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' };
|
||||
|
14
frontend/utilities/scroll_to_top.js
Normal file
14
frontend/utilities/scroll_to_top.js
Normal file
@ -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;
|
169
package-lock.json
generated
Normal file
169
package-lock.json
generated
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
42
yarn.lock
42
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"
|
||||
|
Loading…
Reference in New Issue
Block a user