118 lines
2.9 KiB
Go
118 lines
2.9 KiB
Go
package dashboard
|
||||
|
|
|
|||
|
|
import (
|
|||
|
|
"net/url"
|
|||
|
|
"strconv"
|
|||
|
|
"strings"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// Server-side pagination shared by /ips and /registry: `page` (1-based,
|
|||
|
|
// clamped to the last page) and `per_page` (one of perPageOptions, else
|
|||
|
|
// defaultPerPage).
|
|||
|
|
|
|||
|
|
const defaultPerPage = 50
|
|||
|
|
|
|||
|
|
var perPageOptions = []int{25, 50, 100, 200}
|
|||
|
|
|
|||
|
|
// parsePerPage accepts only the whitelisted page sizes.
|
|||
|
|
func parsePerPage(raw string) int {
|
|||
|
|
n, err := strconv.Atoi(strings.TrimSpace(raw))
|
|||
|
|
if err != nil {
|
|||
|
|
return defaultPerPage
|
|||
|
|
}
|
|||
|
|
for _, o := range perPageOptions {
|
|||
|
|
if n == o {
|
|||
|
|
return n
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return defaultPerPage
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// parsePage returns the 1-based page number; anything invalid is page 1.
|
|||
|
|
func parsePage(raw string) int {
|
|||
|
|
n, err := strconv.Atoi(strings.TrimSpace(raw))
|
|||
|
|
if err != nil || n < 1 {
|
|||
|
|
return 1
|
|||
|
|
}
|
|||
|
|
return n
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func pageCount(total, perPage int) int {
|
|||
|
|
if total <= 0 || perPage <= 0 {
|
|||
|
|
return 1
|
|||
|
|
}
|
|||
|
|
return (total + perPage - 1) / perPage
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// clampPage limits page to the last non-empty page for the given total.
|
|||
|
|
func clampPage(page, total, perPage int) int {
|
|||
|
|
if last := pageCount(total, perPage); page > last {
|
|||
|
|
page = last
|
|||
|
|
}
|
|||
|
|
if page < 1 {
|
|||
|
|
page = 1
|
|||
|
|
}
|
|||
|
|
return page
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// pagerData drives the shared "pager" template partial.
|
|||
|
|
type pagerData struct {
|
|||
|
|
// Wrap is the id of the swap-target element holding the table (the
|
|||
|
|
// ‹ › links replace it via hx-select + hx-target).
|
|||
|
|
Wrap string
|
|||
|
|
Page, PerPage, Total, Pages, From, To int
|
|||
|
|
PrevURL, NextURL string
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// newPager builds the pager for page (already clamped) of total rows. base is
|
|||
|
|
// the page path; params are the filters to preserve in the links (without
|
|||
|
|
// page/per_page, which the pager sets itself).
|
|||
|
|
func newPager(base, wrap string, params url.Values, page, perPage, total int) pagerData {
|
|||
|
|
p := pagerData{Wrap: wrap, Page: page, PerPage: perPage, Total: total, Pages: pageCount(total, perPage)}
|
|||
|
|
if total > 0 {
|
|||
|
|
p.From = (page-1)*perPage + 1
|
|||
|
|
p.To = page * perPage
|
|||
|
|
if p.To > total {
|
|||
|
|
p.To = total
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if page > 1 {
|
|||
|
|
p.PrevURL = pageURL(base, params, page-1, perPage)
|
|||
|
|
}
|
|||
|
|
if page < p.Pages {
|
|||
|
|
p.NextURL = pageURL(base, params, page+1, perPage)
|
|||
|
|
}
|
|||
|
|
return p
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// pageURL builds base?...&page=N&per_page=M with every value escaped by
|
|||
|
|
// url.Values (so a search string like "a&b" can't smuggle in a parameter).
|
|||
|
|
// Page 1 omits `page`.
|
|||
|
|
func pageURL(base string, params url.Values, page, perPage int) string {
|
|||
|
|
v := url.Values{}
|
|||
|
|
for k, vals := range params {
|
|||
|
|
for _, val := range vals {
|
|||
|
|
if val != "" {
|
|||
|
|
v.Add(k, val)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
v.Set("per_page", strconv.Itoa(perPage))
|
|||
|
|
if page > 1 {
|
|||
|
|
v.Set("page", strconv.Itoa(page))
|
|||
|
|
}
|
|||
|
|
return base + "?" + v.Encode()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// chunk splits list into slices of at most size elements.
|
|||
|
|
func chunk(list []string, size int) [][]string {
|
|||
|
|
var out [][]string
|
|||
|
|
for len(list) > size {
|
|||
|
|
out = append(out, list[:size])
|
|||
|
|
list = list[size:]
|
|||
|
|
}
|
|||
|
|
if len(list) > 0 {
|
|||
|
|
out = append(out, list)
|
|||
|
|
}
|
|||
|
|
return out
|
|||
|
|
}
|