diff --git a/client/app/assets/less/ant.less b/client/app/assets/less/ant.less index e95084039..574d420fb 100644 --- a/client/app/assets/less/ant.less +++ b/client/app/assets/less/ant.less @@ -31,6 +31,7 @@ @import "~antd/lib/badge/style/index"; @import "~antd/lib/card/style/index"; @import "~antd/lib/spin/style/index"; +@import "~antd/lib/skeleton/style/index"; @import "~antd/lib/tabs/style/index"; @import "~antd/lib/notification/style/index"; @import "~antd/lib/collapse/style/index"; diff --git a/client/app/assets/less/inc/table.less b/client/app/assets/less/inc/table.less index 7a43a6f9e..9b562f1f0 100755 --- a/client/app/assets/less/inc/table.less +++ b/client/app/assets/less/inc/table.less @@ -1,149 +1,153 @@ .table { - margin-bottom: 0; - - th.sortable-column { - cursor: pointer; + margin-bottom: 0; + + th.sortable-column { + cursor: pointer; + } + + &:not(.table-striped) > thead > tr > th { + background-color: #fafafa; + } + + [class*="bg-"] { + & > tr > th { + color: #fff; + border-bottom: 0; + background: transparent !important; } - - &:not(.table-striped) > thead > tr > th { - background-color: #FAFAFA; + + & + tbody > tr:first-child > td { + border-top: 0; } - - [class*="bg-"] { - & > tr > th { - color: #fff; - border-bottom: 0; - background: transparent !important; - } - - & + tbody > tr:first-child > td { - border-top: 0; - } - } - - & > thead > tr > th { - vertical-align: middle; - font-weight: 500; - color: #333; - border-width: 1px; - text-transform: uppercase; - padding: 15px 10px; - } - - & > thead > tr, - & > tbody > tr, - & > tfoot > tr { - - & > th, & > td { - - &:first-child { - padding-left: 30px; - } - - &:last-child { - padding-right: 30px; - } - - } - } - - tbody > tr:last-child > td { - padding-bottom: 20px; + } + + & > thead > tr > th { + vertical-align: middle; + font-weight: 500; + color: #333; + border-width: 1px; + text-transform: uppercase; + padding: 15px 10px; + } + + & > thead > tr, + & > tbody > tr, + & > tfoot > tr { + & > th, + & > td { + &:first-child { + padding-left: 30px; + } + + &:last-child { + padding-right: 30px; + } } + } + + tbody > tr:last-child > td { + padding-bottom: 20px; + } } .table-bordered { - border: 0; - - & > tbody > tr { - & > td, & > th { - border-bottom: 0; - border-left: 0; - - &:last-child { - border-right: 0; - } - } + border: 0; + + & > tbody > tr { + & > td, + & > th { + border-bottom: 0; + border-left: 0; + + &:last-child { + border-right: 0; + } } - - & > thead > tr > th { - border-left: 0; - - &:last-child { - border-right: 0; - } + } + + & > thead > tr > th { + border-left: 0; + + &:last-child { + border-right: 0; } + } } .table-vmiddle { - td { - vertical-align: middle !important; - } + td { + vertical-align: middle !important; + } } .table-responsive { - border: 0; + border: 0; } -.tile .table { - - & > thead:not([class*="bg-"]) > tr > th { - border-top: 1px solid @table-border-color; - - } +.tile .table { + & > thead:not([class*="bg-"]) > tr > th { + border-top: 1px solid @table-border-color; + } } .table-hover > tbody > tr:hover { - background-color: #f4f4f4; + background-color: #f4f4f4; } .table-data { - tbody > tr > td { - padding-top: 5px !important; - } - - .btn-favourite, .btn-archive { - font-size: 15px; - } + thead > tr > th { + white-space: nowrap; + } + + tbody > tr > td { + padding-top: 5px !important; + } + + .btn-favourite, + .btn-archive { + font-size: 15px; + } } .table-main-title { - font-weight: 500; - line-height: 1.7 !important; + font-weight: 500; + line-height: 1.7 !important; } .btn-favourite { - color: #d4d4d4; - transition: all .25s ease-in-out; - - &:hover, &:focus { - color: @yellow-darker; - cursor: pointer; - } - - .fa-star { - color: @yellow-darker; - } + color: #d4d4d4; + transition: all 0.25s ease-in-out; + + &:hover, + &:focus { + color: @yellow-darker; + cursor: pointer; + } + + .fa-star { + color: @yellow-darker; + } } .btn-archive { - color: #d4d4d4; - transition: all .25s ease-in-out; - - &:hover, &:focus { - color: @gray-light; - } - - .fa-archive { - color: @gray-light; - } + color: #d4d4d4; + transition: all 0.25s ease-in-out; + + &:hover, + &:focus { + color: @gray-light; + } + + .fa-archive { + color: @gray-light; + } } .table > thead > tr > th { - text-transform: none; + text-transform: none; } .table-data .label-tag { - display: inline-block; - max-width: 135px; - } \ No newline at end of file + display: inline-block; + max-width: 135px; +} diff --git a/client/app/components/items-list/ItemsList.jsx b/client/app/components/items-list/ItemsList.jsx index 1d2de3f00..19ba7b1fb 100644 --- a/client/app/components/items-list/ItemsList.jsx +++ b/client/app/components/items-list/ItemsList.jsx @@ -110,9 +110,9 @@ export function wrap(WrappedComponent, createItemsSource, createStateStorage) { isLoaded, isEmpty: !isLoaded || totalCount === 0, - totalItemsCount: isLoaded ? totalCount : 0, + totalItemsCount: totalCount, pageSizeOptions: clientConfig.pageSizeOptions, - pageItems: isLoaded ? pageItems : [], + pageItems: pageItems || [], }; } diff --git a/client/app/components/items-list/classes/ItemsSource.js b/client/app/components/items-list/classes/ItemsSource.js index bc18ca567..e877507a7 100644 --- a/client/app/components/items-list/classes/ItemsSource.js +++ b/client/app/components/items-list/classes/ItemsSource.js @@ -41,18 +41,24 @@ export class ItemsSource { extend(customParams, params); }, }; - return this._beforeUpdate().then(() => - this._fetcher + return this._beforeUpdate().then(() => { + const fetchToken = Math.random() + .toString(36) + .substr(2); + this._currentFetchToken = fetchToken; + return this._fetcher .fetch(changes, state, context) .then(({ results, count, allResults }) => { - this._pageItems = results; - this._allItems = allResults || null; - this._paginator.setTotalCount(count); - this._params = { ...this._params, ...customParams }; - return this._afterUpdate(); + if (this._currentFetchToken === fetchToken) { + this._pageItems = results; + this._allItems = allResults || null; + this._paginator.setTotalCount(count); + this._params = { ...this._params, ...customParams }; + return this._afterUpdate(); + } }) - .catch(error => this.handleError(error)) - ); + .catch(error => this.handleError(error)); + }); } constructor({ getRequest, doRequest, processResults, isPlainList = false, ...defaultState }) { diff --git a/client/app/components/items-list/components/ItemsTable.jsx b/client/app/components/items-list/components/ItemsTable.jsx index ac95a7d65..c5ade7d9e 100644 --- a/client/app/components/items-list/components/ItemsTable.jsx +++ b/client/app/components/items-list/components/ItemsTable.jsx @@ -1,8 +1,9 @@ -import { isFunction, map, filter, extend, omit, identity } from "lodash"; +import { isFunction, map, filter, extend, omit, identity, range, isEmpty } from "lodash"; import React from "react"; import PropTypes from "prop-types"; import classNames from "classnames"; import Table from "antd/lib/table"; +import Skeleton from "antd/lib/skeleton"; import FavoritesControl from "@/components/FavoritesControl"; import TimeAgo from "@/components/TimeAgo"; import { durationHumanize, formatDate, formatDateTime } from "@/lib/utils"; @@ -151,8 +152,10 @@ export default class ItemsTable extends React.Component { } render() { - const columns = this.prepareColumns(); - const rows = map(this.props.items, (item, index) => ({ key: "row" + index, item })); + const tableDataProps = { + columns: this.prepareColumns(), + dataSource: map(this.props.items, (item, index) => ({ key: "row" + index, item })), + }; // Bind events only if `onRowClick` specified const onTableRow = isFunction(this.props.onRowClick) @@ -164,17 +167,27 @@ export default class ItemsTable extends React.Component { : null; const { showHeader } = this.props; + if (this.props.loading) { + if (isEmpty(tableDataProps.dataSource)) { + tableDataProps.columns = tableDataProps.columns.map(column => ({ + ...column, + sorter: false, + render: () => , + })); + tableDataProps.dataSource = range(10).map(key => ({ key: `${key}` })); + } else { + tableDataProps.loading = { indicator: null }; + } + } return ( row.key} pagination={false} onRow={onTableRow} + {...tableDataProps} /> ); } diff --git a/client/app/pages/alerts/AlertsList.jsx b/client/app/pages/alerts/AlertsList.jsx index 4f30994e5..b236362d9 100644 --- a/client/app/pages/alerts/AlertsList.jsx +++ b/client/app/pages/alerts/AlertsList.jsx @@ -9,7 +9,6 @@ import { wrap as itemsList, ControllerType } from "@/components/items-list/Items import { ResourceItemsSource } from "@/components/items-list/classes/ItemsSource"; import { StateStorage } from "@/components/items-list/classes/StateStorage"; -import LoadingState from "@/components/items-list/components/LoadingState"; import ItemsTable, { Columns } from "@/components/items-list/components/ItemsTable"; import Alert from "@/services/alert"; @@ -49,7 +48,7 @@ class AlertsList extends React.Component { field: "name", } ), - Columns.custom((text, item) => item.user.name, { title: "Created By" }), + Columns.custom((text, item) => item.user.name, { title: "Created By", width: "1%" }), Columns.custom.sortable( (text, alert) => (
@@ -60,10 +59,11 @@ class AlertsList extends React.Component { title: "State", field: "state", width: "1%", + className: "text-nowrap", } ), - Columns.timeAgo.sortable({ title: "Last Updated At", field: "updated_at", className: "text-nowrap", width: "1%" }), - Columns.dateTime.sortable({ title: "Created At", field: "created_at", className: "text-nowrap", width: "1%" }), + Columns.timeAgo.sortable({ title: "Last Updated At", field: "updated_at", width: "1%" }), + Columns.dateTime.sortable({ title: "Created At", field: "created_at", width: "1%" }), ]; render() { @@ -84,8 +84,7 @@ class AlertsList extends React.Component { } />
- {!controller.isLoaded && } - {controller.isLoaded && controller.isEmpty && ( + {controller.isLoaded && controller.isEmpty ? ( - )} - {controller.isLoaded && !controller.isEmpty && ( + ) : (
item.user.name, { title: "Created By" }), + Columns.custom((text, item) => item.user.name, { title: "Created By", width: "1%" }), Columns.dateTime.sortable({ title: "Created At", field: "created_at", - className: "text-nowrap", width: "1%", }), ]; @@ -99,37 +97,34 @@ class DashboardList extends React.Component { - {controller.isLoaded ? ( -
- {controller.isEmpty ? ( - + {controller.isLoaded && controller.isEmpty ? ( + + ) : ( +
+ - ) : ( -
- - controller.updatePagination({ itemsPerPage })} - page={controller.page} - onChange={page => controller.updatePagination({ page })} - /> -
- )} -
- ) : ( - - )} + controller.updatePagination({ itemsPerPage })} + page={controller.page} + onChange={page => controller.updatePagination({ page })} + /> +
+ )} +
diff --git a/client/app/pages/queries-list/QueriesList.jsx b/client/app/pages/queries-list/QueriesList.jsx index 8277c3f37..e23094b11 100644 --- a/client/app/pages/queries-list/QueriesList.jsx +++ b/client/app/pages/queries-list/QueriesList.jsx @@ -11,7 +11,6 @@ import { wrap as itemsList, ControllerType } from "@/components/items-list/Items import { ResourceItemsSource } from "@/components/items-list/classes/ItemsSource"; import { UrlStateStorage } from "@/components/items-list/classes/StateStorage"; -import LoadingState from "@/components/items-list/components/LoadingState"; import * as Sidebar from "@/components/items-list/components/Sidebar"; import ItemsTable, { Columns } from "@/components/items-list/components/ItemsTable"; @@ -80,12 +79,18 @@ class QueriesList extends React.Component { width: null, } ), - Columns.custom((text, item) => item.user.name, { title: "Created By" }), - Columns.dateTime.sortable({ title: "Created At", field: "created_at" }), - Columns.dateTime.sortable({ title: "Last Executed At", field: "retrieved_at", orderByField: "executed_at" }), + Columns.custom((text, item) => item.user.name, { title: "Created By", width: "1%" }), + Columns.dateTime.sortable({ title: "Created At", field: "created_at", width: "1%" }), + Columns.dateTime.sortable({ + title: "Last Executed At", + field: "retrieved_at", + orderByField: "executed_at", + width: "1%", + }), Columns.custom.sortable((text, item) => , { title: "Refresh Schedule", field: "schedule", + width: "1%", }), ]; @@ -132,18 +137,17 @@ class QueriesList extends React.Component { - {!controller.isLoaded && } - {controller.isLoaded && controller.isEmpty && ( + {controller.isLoaded && controller.isEmpty ? ( - )} - {controller.isLoaded && !controller.isEmpty && ( + ) : (