Skip to content

Commit 40100c7

Browse files
authored
Merge pull request #4 from knktc/feature-supports-filter-by-path
Supports pagination
2 parents 79c416c + 88a0b0e commit 40100c7

File tree

2 files changed

+204
-20
lines changed

2 files changed

+204
-20
lines changed

main.go

Lines changed: 93 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ type PageData struct {
4141
// Version information will be set during build
4242
var Version = "dev"
4343

44+
// Constants for error messages
45+
const (
46+
InternalServerError = "Internal Server Error"
47+
)
48+
4449
// Global store for our requests
4550
// We use global variables to store requests.
4651
var (
@@ -112,21 +117,98 @@ func handler(w http.ResponseWriter, r *http.Request) {
112117
captureRequestHandler(w, r)
113118
}
114119

115-
// apiRequestsHandler handles AJAX requests for getting the list of requests
120+
// PaginatedResponse represents a paginated API response
121+
type PaginatedResponse struct {
122+
Requests []RequestInfo `json:"requests"`
123+
Page int `json:"page"`
124+
Limit int `json:"limit"`
125+
Total int `json:"total"`
126+
TotalPages int `json:"total_pages"`
127+
HasNext bool `json:"has_next"`
128+
HasPrev bool `json:"has_prev"`
129+
}
130+
131+
// parsePaginationParams extracts and validates pagination parameters from request
132+
func parsePaginationParams(r *http.Request) (page, limit int) {
133+
page = 1
134+
limit = 20 // Default limit
135+
136+
if pageStr := r.URL.Query().Get("page"); pageStr != "" {
137+
if p, err := strconv.Atoi(pageStr); err == nil && p > 0 {
138+
page = p
139+
}
140+
}
141+
142+
if limitStr := r.URL.Query().Get("limit"); limitStr != "" {
143+
if l, err := strconv.Atoi(limitStr); err == nil && l > 0 && l <= 100 { // Max limit of 100
144+
limit = l
145+
}
146+
}
147+
148+
return page, limit
149+
}
150+
151+
// getRequestsPage returns a paginated slice of requests
152+
func getRequestsPage(page, limit, total int) []RequestInfo {
153+
if total == 0 {
154+
return make([]RequestInfo, 0)
155+
}
156+
157+
// Calculate pagination
158+
offset := (page - 1) * limit
159+
start := offset
160+
end := offset + limit
161+
if end > total {
162+
end = total
163+
}
164+
165+
requests := make([]RequestInfo, end-start)
166+
for i := start; i < end; i++ {
167+
id := requestIDs[total-1-i] // Reverse order to show newest first
168+
requests[i-start] = requestsStore[id]
169+
}
170+
171+
return requests
172+
}
173+
174+
// apiRequestsHandler handles AJAX requests for getting the list of requests with pagination support
116175
func apiRequestsHandler(w http.ResponseWriter, r *http.Request) {
117176
w.Header().Set("Content-Type", "application/json")
118177

178+
// Parse pagination parameters
179+
page, limit := parsePaginationParams(r)
180+
119181
mutex.RLock()
120-
// Create a list of requests based on the ordered ID list.
121-
// To have the newest requests shown at the top, we traverse the ID list in reverse order.
122-
requests := make([]RequestInfo, len(requestIDs))
123-
for i := 0; i < len(requestIDs); i++ {
124-
id := requestIDs[len(requestIDs)-1-i]
125-
requests[i] = requestsStore[id]
182+
total := len(requestIDs)
183+
184+
// Calculate pagination
185+
totalPages := (total + limit - 1) / limit // Ceiling division
186+
if totalPages == 0 {
187+
totalPages = 1
188+
}
189+
190+
// Validate page number
191+
if page > totalPages {
192+
page = totalPages
126193
}
194+
195+
// Get paginated requests
196+
requests := getRequestsPage(page, limit, total)
197+
127198
mutex.RUnlock()
128199

129-
if err := json.NewEncoder(w).Encode(requests); err != nil {
200+
// Create paginated response
201+
response := PaginatedResponse{
202+
Requests: requests,
203+
Page: page,
204+
Limit: limit,
205+
Total: total,
206+
TotalPages: totalPages,
207+
HasNext: page < totalPages,
208+
HasPrev: page > 1,
209+
}
210+
211+
if err := json.NewEncoder(w).Encode(response); err != nil {
130212
http.Error(w, "Failed to encode requests", http.StatusInternalServerError)
131213
return
132214
}
@@ -230,7 +312,7 @@ func renderTemplate(w http.ResponseWriter, tmplName string, data interface{}) {
230312
tmplContent, err := templateFS.ReadFile("templates/main.tmpl")
231313
if err != nil {
232314
log.Printf("Error reading template file: %v", err)
233-
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
315+
http.Error(w, InternalServerError, http.StatusInternalServerError)
234316
return
235317
}
236318

@@ -255,15 +337,15 @@ func renderTemplate(w http.ResponseWriter, tmplName string, data interface{}) {
255337

256338
if err != nil {
257339
log.Printf("Error parsing template %s: %v", tmplName, err)
258-
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
340+
http.Error(w, InternalServerError, http.StatusInternalServerError)
259341
return
260342
}
261343

262344
buf := &bytes.Buffer{}
263345
err = tmpl.Execute(buf, data)
264346
if err != nil {
265347
log.Printf("Error executing template %s: %v", tmplName, err)
266-
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
348+
http.Error(w, InternalServerError, http.StatusInternalServerError)
267349
return
268350
}
269351

templates/main.tmpl

Lines changed: 111 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,9 @@
130130
</div>
131131
<div class="overflow-y-auto flex-1">
132132
{{if .AllRequests}}
133-
<ul class="divide-y">
133+
<ul class="divide-y" id="requestList">
134134
{{range .AllRequests}}
135-
<a href="/?view_id={{.ID}}" class="block p-4 hover:bg-gray-50 {{if isCurrent .ID $.SelectedRequest}}bg-indigo-50 border-l-4 border-indigo-500{{end}}">
135+
<a href="#" data-request-id="{{.ID}}" class="request-link block p-4 hover:bg-gray-50 {{if isCurrent .ID $.SelectedRequest}}bg-indigo-50 border-l-4 border-indigo-500{{end}}">
136136
<div class="flex justify-between items-center mb-1">
137137
<div class="font-bold truncate {{if isCurrent .ID $.SelectedRequest}}text-indigo-700{{else}}text-gray-800{{end}}">
138138
<span class="px-2 mr-2 text-xs leading-5 font-semibold rounded-full
@@ -156,6 +156,22 @@
156156
</div>
157157
{{end}}
158158
</div>
159+
<!-- Pagination Controls -->
160+
<div id="paginationControls" class="p-4 border-t bg-gray-50">
161+
<div class="flex items-center justify-between text-sm">
162+
<div class="text-gray-600">
163+
<span id="paginationInfo">第 1 页,共 1 页 (总计 {{len .AllRequests}} 个请求)</span>
164+
</div>
165+
<div class="flex space-x-2">
166+
<button id="prevPageBtn" class="px-3 py-1 text-sm bg-gray-200 text-gray-600 rounded hover:bg-gray-300 disabled:opacity-50 disabled:cursor-not-allowed" disabled>
167+
上一页
168+
</button>
169+
<button id="nextPageBtn" class="px-3 py-1 text-sm bg-gray-200 text-gray-600 rounded hover:bg-gray-300 disabled:opacity-50 disabled:cursor-not-allowed">
170+
下一页
171+
</button>
172+
</div>
173+
</div>
174+
</div>
159175
</aside>
160176

161177
<!-- Right Column: Request Details -->
@@ -244,12 +260,23 @@
244260
// Initialize language first
245261
initLanguage();
246262

263+
// --- Pagination variables ---
264+
let currentPage = 1;
265+
let totalPages = 1;
266+
let isUpdating = false;
267+
247268
// --- Auto-refresh logic ---
248269
const checkbox = document.getElementById('autoRefreshCheckbox');
249270
const intervalInput = document.getElementById('refreshIntervalInput');
250271
const manualRefreshButton = document.getElementById('manualRefreshButton');
251272
let refreshInterval;
252273

274+
// --- Pagination controls ---
275+
const prevPageBtn = document.getElementById('prevPageBtn');
276+
const nextPageBtn = document.getElementById('nextPageBtn');
277+
const paginationInfo = document.getElementById('paginationInfo');
278+
const paginationControls = document.getElementById('paginationControls');
279+
253280
function startRefresh() {
254281
if (refreshInterval) clearInterval(refreshInterval);
255282
let interval = parseInt(intervalInput.value, 10);
@@ -273,22 +300,48 @@
273300
}
274301
}
275302

303+
// Function to update pagination info
304+
function updatePaginationInfo(data) {
305+
const { page, total_pages, total } = data;
306+
paginationInfo.textContent = `第 ${page} 页,共 ${total_pages} 页 (总计 ${total} 个请求)`;
307+
308+
prevPageBtn.disabled = !data.has_prev;
309+
nextPageBtn.disabled = !data.has_next;
310+
311+
currentPage = page;
312+
totalPages = total_pages;
313+
314+
// Show/hide pagination controls based on whether there are multiple pages
315+
if (total_pages > 1) {
316+
paginationControls.classList.remove('hidden');
317+
} else {
318+
paginationControls.classList.add('hidden');
319+
}
320+
}
321+
276322
// Function to update only the request list via AJAX
277-
function updateRequestList() {
278-
return fetch('/api/requests')
323+
function updateRequestList(page = currentPage) {
324+
if (isUpdating) return;
325+
isUpdating = true;
326+
327+
return fetch(`/api/requests?page=${page}&limit=20`)
279328
.then(response => response.json())
280-
.then(requests => {
329+
.then(data => {
281330
const requestList = document.querySelector('aside .overflow-y-auto');
282331
const currentViewId = new URLSearchParams(window.location.search).get('view_id');
283332

284-
if (requests && requests.length > 0) {
333+
updatePaginationInfo(data);
334+
335+
if (data.requests && data.requests.length > 0) {
285336
const ul = document.createElement('ul');
286337
ul.className = 'divide-y';
338+
ul.id = 'requestList';
287339

288-
requests.forEach(request => {
340+
data.requests.forEach(request => {
289341
const li = document.createElement('a');
290-
li.href = `/?view_id=${request.ID}`;
291-
li.className = `block p-4 hover:bg-gray-50 ${currentViewId == request.ID ? 'bg-indigo-50 border-l-4 border-indigo-500' : ''}`;
342+
li.href = '#';
343+
li.setAttribute('data-request-id', request.ID);
344+
li.className = `request-link block p-4 hover:bg-gray-50 ${currentViewId == request.ID ? 'bg-indigo-50 border-l-4 border-indigo-500' : ''}`;
292345

293346
const methodColor = getMethodColor(request.Method);
294347
const timestamp = new Date(request.Timestamp).toLocaleString();
@@ -305,6 +358,9 @@
305358
<div class="text-xs text-gray-500">${timestamp}</div>
306359
`;
307360

361+
// Add click event listener
362+
li.addEventListener('click', handleRequestClick);
363+
308364
ul.appendChild(li);
309365
});
310366

@@ -320,6 +376,9 @@
320376
})
321377
.catch(error => {
322378
console.error('Error updating request list:', error);
379+
})
380+
.finally(() => {
381+
isUpdating = false;
323382
});
324383
}
325384

@@ -334,6 +393,19 @@
334393
}
335394
}
336395

396+
// Pagination button event listeners
397+
prevPageBtn.addEventListener('click', () => {
398+
if (currentPage > 1) {
399+
updateRequestList(currentPage - 1);
400+
}
401+
});
402+
403+
nextPageBtn.addEventListener('click', () => {
404+
if (currentPage < totalPages) {
405+
updateRequestList(currentPage + 1);
406+
}
407+
});
408+
337409
checkbox.addEventListener('change', () => {
338410
localStorage.setItem('autoRefreshInterval', intervalInput.value);
339411
if (checkbox.checked) {
@@ -398,6 +470,36 @@
398470
}
399471
toggleManualRefreshButton(); // Initialize manual refresh button visibility
400472

473+
// Initialize pagination and request list
474+
updateRequestList();
475+
476+
// Handle request link clicks
477+
function handleRequestClick(event) {
478+
event.preventDefault();
479+
const requestId = event.currentTarget.getAttribute('data-request-id');
480+
if (requestId) {
481+
// Update URL without page reload
482+
const newUrl = new URL(window.location);
483+
newUrl.searchParams.set('view_id', requestId);
484+
window.history.pushState({}, '', newUrl);
485+
486+
// Update the display
487+
loadRequestDetails(requestId);
488+
}
489+
}
490+
491+
// Add click event listeners to existing request links
492+
document.querySelectorAll('.request-link').forEach(link => {
493+
link.addEventListener('click', handleRequestClick);
494+
});
495+
496+
// Function to load request details via AJAX
497+
function loadRequestDetails(requestId) {
498+
// You can implement this to load request details via AJAX
499+
// For now, just reload the page
500+
window.location.href = `/?view_id=${requestId}`;
501+
}
502+
401503
// --- Collapsible sections logic ---
402504
const sections = ['headers', 'body'];
403505

0 commit comments

Comments
 (0)