diff --git a/internal/server/handlers.go b/internal/server/handlers.go index 0fe6492..13cc5bc 100644 --- a/internal/server/handlers.go +++ b/internal/server/handlers.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "net/http" + "sort" "strconv" "memodroid/internal/app" @@ -378,26 +379,62 @@ func (h *handler) searchFilter(w http.ResponseWriter, r *http.Request) { writeJSON(w, map[string]any{"candidates": sess.CandidateCount()}) } -func (h *handler) searchCandidates(w http.ResponseWriter, _ *http.Request) { +func (h *handler) searchCandidates(w http.ResponseWriter, r *http.Request) { sess := h.state.GetSession() vt := h.state.GetValueType() if sess == nil { - writeJSON(w, []any{}) + writeJSON(w, map[string]any{"total": 0, "page": 0, "page_size": 100, "items": []any{}}) return } + + pageSize := 100 + page := 0 + if s := r.URL.Query().Get("page_size"); s != "" { + if v, err := strconv.Atoi(s); err == nil && v > 0 { + pageSize = v + } + } + if s := r.URL.Query().Get("page"); s != "" { + if v, err := strconv.Atoi(s); err == nil && v >= 0 { + page = v + } + } + snap := sess.Snapshot() + addrs := make([]uintptr, 0, len(snap)) + for addr := range snap { + addrs = append(addrs, addr) + } + sort.Slice(addrs, func(i, j int) bool { return addrs[i] < addrs[j] }) + + total := len(addrs) + start := page * pageSize + if start > total { + start = total + } + end := start + pageSize + if end > total { + end = total + } + type entry struct { Addr string `json:"addr"` Value string `json:"value"` } - out := make([]entry, 0, len(snap)) - for addr, val := range snap { - out = append(out, entry{ + items := make([]entry, 0, end-start) + for _, addr := range addrs[start:end] { + items = append(items, entry{ Addr: fmt.Sprintf("0x%x", addr), - Value: search.FormatValue(val, vt), + Value: search.FormatValue(snap[addr], vt), }) } - writeJSON(w, out) + + writeJSON(w, map[string]any{ + "total": total, + "page": page, + "page_size": pageSize, + "items": items, + }) } func (h *handler) searchReset(w http.ResponseWriter, _ *http.Request) { diff --git a/internal/server/static/index.html b/internal/server/static/index.html index 0e10775..cbae66a 100644 --- a/internal/server/static/index.html +++ b/internal/server/static/index.html @@ -359,12 +359,22 @@
+
AddressValue
No candidates
+ @@ -699,12 +709,21 @@ } // ── Candidates ─────────────────────────────────────── -async function loadCandidates() { - const list = await api('/search/candidates'); - document.getElementById('cand-count').textContent = list.length; +let candCurrentPage = 0; +async function loadCandidates(page) { + if (page !== undefined) candCurrentPage = page; + const ps = document.getElementById('cand-pagesize').value; + const res = await api(`/search/candidates?page=${candCurrentPage}&page_size=${ps}`); + if (res.error) { log(res.error, false); return; } + + document.getElementById('cand-count').textContent = res.total; const tb = document.getElementById('cand-table'); - if (!list.length) { tb.innerHTML = 'No candidates'; return; } - tb.innerHTML = list.map(c => + if (!res.items || !res.items.length) { + tb.innerHTML = 'No candidates'; + document.getElementById('cand-pagination').style.display = 'none'; + return; + } + tb.innerHTML = res.items.map(c => ` ${escHtml(c.addr)} ${escHtml(c.value)} @@ -715,6 +734,20 @@ ` ).join(''); + + const totalPages = Math.ceil(res.total / parseInt(ps)); + document.getElementById('cand-pageinfo').textContent = `Page ${candCurrentPage + 1} / ${totalPages} (${res.total} total)`; + document.getElementById('cand-pagination').style.display = res.total > parseInt(ps) ? '' : 'none'; + document.getElementById('cand-prev').disabled = candCurrentPage === 0; + document.getElementById('cand-next').disabled = candCurrentPage >= totalPages - 1; +} +function candPage(delta) { + const ps = parseInt(document.getElementById('cand-pagesize').value); + const total = parseInt(document.getElementById('cand-count').textContent); + const totalPages = Math.ceil(total / ps); + const next = candCurrentPage + delta; + if (next < 0 || next >= totalPages) return; + loadCandidates(next); } function prefillModify(addr, val) { document.querySelector('[data-panel="memory"]').click();