Files
cloud-ip-validator/internal/dashboard/paging.go
T

118 lines
2.9 KiB
Go
Raw Normal View History

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
}