diff --git a/assets/src/css/styles.css b/assets/src/css/styles.css
index b687c5d7..8ffe5fe3 100644
--- a/assets/src/css/styles.css
+++ b/assets/src/css/styles.css
@@ -84,6 +84,16 @@ nav .settings svg:hover { transform: rotate(45deg); }
.table-row:after { content: ""; background: #88ffc6; position: absolute; height: 34px; top: 0; left: -16px; opacity: .2; border-right: 2px solid #45ce8c; z-index: 0; }
.table-row.header:after { background: none; border: none; }
+.row.pag {
+ margin-top: 16px;
+ grid-template-columns: 1fr 1fr;
+ grid-gap: 4px;
+}
+.row.pag a {
+ color: #98a0a6;
+ font-size: 19px;
+}
+
.modal-wrap { position: fixed; height: 100%; width: 100%; top: 0; left: 0; z-index: 1977; background: rgba(20,20,20,.8); display: grid; grid-template-columns: 1fr; align-items: center; }
.modal { max-width: 480px; width: 100%; margin: 0 auto; text-align: left; background: #fff; z-index: 1978; height: auto; border-radius: 4px; box-shadow: 0 2px 8px 0 rgba(34,34,34,.10); overflow: hidden; }
.modal p { padding: 16px; font-size: 12px; color: #aaa; }
diff --git a/assets/src/css/util.css b/assets/src/css/util.css
index a0dbc7b9..fbd5faab 100644
--- a/assets/src/css/util.css
+++ b/assets/src/css/util.css
@@ -58,5 +58,5 @@
.fadeInDown { animation-name: fadeInDown; }
.loading {
- opacity: 0.8;
+ opacity: 0.6;
}
diff --git a/assets/src/js/components/Table.js b/assets/src/js/components/Table.js
index 7c3fbf3f..82fbaeb4 100644
--- a/assets/src/js/components/Table.js
+++ b/assets/src/js/components/Table.js
@@ -5,6 +5,7 @@ import * as numbers from '../lib/numbers.js';
import Client from '../lib/client.js';
import { bind } from 'decko';
import classNames from 'classnames';
+import { runInNewContext } from 'vm';
const dayInSeconds = 60 * 60 * 24;
@@ -15,6 +16,7 @@ class Table extends Component {
this.state = {
records: [],
+ offset: 0,
limit: 15,
loading: true,
total: 0,
@@ -37,7 +39,7 @@ class Table extends Component {
fetchData(props) {
this.setState({ loading: true });
- Client.request(`/sites/${props.siteId}/stats/${props.endpoint}/agg?before=${props.before}&after=${props.after}&limit=${this.state.limit}`)
+ Client.request(`/sites/${props.siteId}/stats/${props.endpoint}/agg?before=${props.before}&after=${props.after}&offset=${this.state.offset}&limit=${this.state.limit}`)
.then((d) => {
// request finished; check if timestamp range is still the one user wants to see
if( this.paramsChanged(props, this.props) ) {
@@ -57,12 +59,26 @@ class Table extends Component {
total: d
});
});
+ }
+
+ @bind
+ paginateNext() {
+ this.setState({ offset: this.state.offset + this.state.limit })
+ this.fetchData(this.props)
+ }
+
+ @bind
+ paginatePrev() {
+ if(this.state.offset == 0) {
+ return;
+ }
+ this.setState({ offset: Math.max(0, this.state.offset - this.state.limit) })
+ this.fetchData(this.props)
}
render(props, state) {
const tableRows = state.records !== null && state.records.length > 0 ? state.records.map((p, i) => {
-
let href = (p.Hostname + p.Pathname) || p.URL;
let widthClass = "";
if(state.total > 0) {
@@ -86,6 +102,13 @@ class Table extends Component {
)}) :
;
+ // pagination row: only show when total # of results doesn't fit in one table page
+ const pagination = state.total > state.limit ? (
+ ) : '';
+
return (
{tableRows}
+ {pagination}
)
diff --git a/gulpfile.js b/gulpfile.js
index 5c33d5e2..d2a7cfd3 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -69,7 +69,7 @@ gulp.task('default', gulp.series('app-js', 'tracker-js', 'css', 'html', 'img', '
gulp.task('watch', gulp.series('default', function() {
gulp.watch(['./assets/src/js/**/*.js'], gulp.parallel('app-js', 'tracker-js') );
- gulp.watch(['./assets/src/sass/**/**/*.scss'], gulp.parallel( 'css') );
+ gulp.watch(['./assets/src/css/**/*.css'], gulp.parallel( 'css') );
gulp.watch(['./assets/src/**/*.html'], gulp.parallel( 'html') );
gulp.watch(['./assets/src/img/**/*'], gulp.parallel( 'img') );
gulp.watch(['./assets/src/fonts/**/*'], gulp.parallel( 'fonts') );
diff --git a/pkg/api/page_stats.go b/pkg/api/page_stats.go
index 9712b89b..92ddb9f5 100644
--- a/pkg/api/page_stats.go
+++ b/pkg/api/page_stats.go
@@ -7,7 +7,7 @@ import (
// URL: /api/sites/{id:[0-9]+}/stats/pages/agg
func (api *API) GetAggregatedPageStatsHandler(w http.ResponseWriter, r *http.Request) error {
params := GetRequestParams(r)
- result, err := api.database.SelectAggregatedPageStats(params.SiteID, params.StartDate, params.EndDate, params.Limit)
+ result, err := api.database.SelectAggregatedPageStats(params.SiteID, params.StartDate, params.EndDate, params.Offset, params.Limit)
if err != nil {
return err
}
diff --git a/pkg/api/params.go b/pkg/api/params.go
index fd3138e8..8ae07334 100644
--- a/pkg/api/params.go
+++ b/pkg/api/params.go
@@ -12,6 +12,7 @@ import (
// Params defines the commonly used API parameters
type Params struct {
SiteID int64
+ Offset int64
Limit int64
StartDate time.Time
EndDate time.Time
@@ -22,6 +23,7 @@ func GetRequestParams(r *http.Request) *Params {
params := &Params{
SiteID: 0,
Limit: 20,
+ Offset: 0,
StartDate: time.Now(),
EndDate: time.Now().AddDate(0, 0, -7),
}
@@ -52,6 +54,12 @@ func GetRequestParams(r *http.Request) *Params {
}
}
+ if q.Get("offset") != "" {
+ if offset, err := strconv.ParseInt(q.Get("offset"), 10, 64); err == nil && offset > 0 {
+ params.Offset = offset
+ }
+ }
+
return params
}
diff --git a/pkg/api/referrer_stats.go b/pkg/api/referrer_stats.go
index 694ad186..aca3131e 100644
--- a/pkg/api/referrer_stats.go
+++ b/pkg/api/referrer_stats.go
@@ -6,7 +6,7 @@ import (
func (api *API) GetAggregatedReferrerStatsHandler(w http.ResponseWriter, r *http.Request) error {
params := GetRequestParams(r)
- result, err := api.database.SelectAggregatedReferrerStats(params.SiteID, params.StartDate, params.EndDate, params.Limit)
+ result, err := api.database.SelectAggregatedReferrerStats(params.SiteID, params.StartDate, params.EndDate, params.Offset, params.Limit)
if err != nil {
return err
}
diff --git a/pkg/datastore/datastore.go b/pkg/datastore/datastore.go
index 42d50b8c..2353846c 100644
--- a/pkg/datastore/datastore.go
+++ b/pkg/datastore/datastore.go
@@ -42,13 +42,13 @@ type Datastore interface {
// page stats
GetPageStats(int64, time.Time, int64, int64) (*models.PageStats, error)
SavePageStats(*models.PageStats) error
- SelectAggregatedPageStats(int64, time.Time, time.Time, int64) ([]*models.PageStats, error)
+ SelectAggregatedPageStats(int64, time.Time, time.Time, int64, int64) ([]*models.PageStats, error)
GetAggregatedPageStatsPageviews(int64, time.Time, time.Time) (int64, error)
// referrer stats
GetReferrerStats(int64, time.Time, int64, int64) (*models.ReferrerStats, error)
SaveReferrerStats(*models.ReferrerStats) error
- SelectAggregatedReferrerStats(int64, time.Time, time.Time, int64) ([]*models.ReferrerStats, error)
+ SelectAggregatedReferrerStats(int64, time.Time, time.Time, int64, int64) ([]*models.ReferrerStats, error)
GetAggregatedReferrerStatsPageviews(int64, time.Time, time.Time) (int64, error)
// hostnames
diff --git a/pkg/datastore/sqlstore/page_stats.go b/pkg/datastore/sqlstore/page_stats.go
index 277e8852..6415839a 100644
--- a/pkg/datastore/sqlstore/page_stats.go
+++ b/pkg/datastore/sqlstore/page_stats.go
@@ -38,7 +38,7 @@ func (db *sqlstore) updatePageStats(s *models.PageStats) error {
return err
}
-func (db *sqlstore) SelectAggregatedPageStats(siteID int64, startDate time.Time, endDate time.Time, limit int64) ([]*models.PageStats, error) {
+func (db *sqlstore) SelectAggregatedPageStats(siteID int64, startDate time.Time, endDate time.Time, offset int64, limit int64) ([]*models.PageStats, error) {
var result []*models.PageStats
query := db.Rebind(`SELECT
h.name AS hostname,
@@ -53,8 +53,8 @@ func (db *sqlstore) SelectAggregatedPageStats(siteID int64, startDate time.Time,
LEFT JOIN pathnames p ON p.id = s.pathname_id
WHERE site_id = ? AND ts >= ? AND ts <= ?
GROUP BY hostname, pathname
- ORDER BY pageviews DESC LIMIT ?`)
- err := db.Select(&result, query, siteID, startDate.Format(DATE_FORMAT), endDate.Format(DATE_FORMAT), limit)
+ ORDER BY pageviews DESC LIMIT ?, ?`)
+ err := db.Select(&result, query, siteID, startDate.Format(DATE_FORMAT), endDate.Format(DATE_FORMAT), offset, limit)
return result, err
}
diff --git a/pkg/datastore/sqlstore/referrer_stats.go b/pkg/datastore/sqlstore/referrer_stats.go
index 11127ff2..9f20b1c3 100644
--- a/pkg/datastore/sqlstore/referrer_stats.go
+++ b/pkg/datastore/sqlstore/referrer_stats.go
@@ -38,7 +38,7 @@ func (db *sqlstore) updateReferrerStats(s *models.ReferrerStats) error {
return err
}
-func (db *sqlstore) SelectAggregatedReferrerStats(siteID int64, startDate time.Time, endDate time.Time, limit int64) ([]*models.ReferrerStats, error) {
+func (db *sqlstore) SelectAggregatedReferrerStats(siteID int64, startDate time.Time, endDate time.Time, offset int64, limit int64) ([]*models.ReferrerStats, error) {
var result []*models.ReferrerStats
sql := `SELECT
@@ -59,11 +59,11 @@ func (db *sqlstore) SelectAggregatedReferrerStats(siteID int64, startDate time.T
} else {
sql = sql + `GROUP BY COALESCE(NULLIF(groupname, ''), CONCAT(hostname_id, pathname_id) ) `
}
- sql = sql + ` ORDER BY pageviews DESC LIMIT ?`
+ sql = sql + ` ORDER BY pageviews DESC LIMIT ?, ?`
query := db.Rebind(sql)
- err := db.Select(&result, query, siteID, startDate.Format(DATE_FORMAT), endDate.Format(DATE_FORMAT), limit)
+ err := db.Select(&result, query, siteID, startDate.Format(DATE_FORMAT), endDate.Format(DATE_FORMAT), offset, limit)
return result, mapError(err)
}