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 }