1919 .trace-result : hover { background-color : # 3c3c3c ; }
2020 .trace-result .selected { background-color : # 0d6efd ; }
2121 .span-info { font-size : 0.9em ; color : # 888 ; }
22+
23+ /* Pagination styles */
24+ # pagination-controls button : disabled {
25+ opacity : 0.5 ;
26+ cursor : not-allowed;
27+ }
28+ .sidebar {
29+ min-height : 100vh ;
30+ position : sticky;
31+ top : 0 ;
32+ }
2233 </ style >
2334</ head >
2435< body >
2536< div class ="container-fluid ">
26- < div class ="row ">
37+ < div class ="row flex-nowrap ">
2738 <!-- Include sidebar -->
28- < div th:replace ="~{fragments/sidebar} "> </ div >
39+ < div th:replace ="~{fragments/sidebar} " class =" col-auto sidebar " style =" width: 180px; " > </ div >
2940
3041 <!-- Main content -->
3142 < div class ="col py-3 ">
@@ -43,8 +54,8 @@ <h5 class="card-title mb-0">Query Jaeger Traces</h5>
4354 < div class ="col-md-4 ">
4455 < label for ="service-name " class ="form-label "> Service Name</ label >
4556 < select class ="form-control " id ="service-name " name ="service ">
57+ < option value ="sentrius-api " selected > sentrius-api</ option >
4658 < option value =""> All Services</ option >
47- < option value ="sentrius-api "> sentrius-api</ option >
4859 < option value ="sentrius-dataplane "> sentrius-dataplane</ option >
4960 < option value ="sentrius-agent-proxy "> sentrius-agent-proxy</ option >
5061 </ select >
@@ -79,6 +90,21 @@ <h5 class="card-title mb-0">Query Jaeger Traces</h5>
7990 < input type ="text " class ="form-control " id ="tags " name ="tags " placeholder ="e.g. error=true, user.id=123 ">
8091 </ div >
8192 </ div >
93+ < div class ="row mb-3 ">
94+ < div class ="col-md-6 ">
95+ < label for ="limit " class ="form-label "> Results per page</ label >
96+ < select class ="form-control " id ="limit " name ="limit ">
97+ < option value ="10 "> 10</ option >
98+ < option value ="20 " selected > 20</ option >
99+ < option value ="50 "> 50</ option >
100+ < option value ="100 "> 100</ option >
101+ </ select >
102+ </ div >
103+ < div class ="col-md-6 ">
104+ < label for ="start " class ="form-label "> Start from result</ label >
105+ < input type ="number " class ="form-control " id ="start " name ="start " value ="0 " min ="0 " step ="1 ">
106+ </ div >
107+ </ div >
82108 < button type ="submit " class ="btn btn-primary "> Search Traces</ button >
83109 < button type ="button " class ="btn btn-secondary " onclick ="clearForm() "> Clear</ button >
84110 < button type ="button " class ="btn btn-info " onclick ="loadMockData() "> Load Mock Data</ button >
@@ -101,11 +127,24 @@ <h5 class="card-title mb-0">Query Jaeger Traces</h5>
101127 < div id ="results-section " style ="display: none; ">
102128 <!-- Trace List -->
103129 < div class ="card mb-4 ">
104- < div class ="card-header ">
130+ < div class ="card-header d-flex justify-content-between align-items-center ">
105131 < h5 class ="card-title mb-0 "> Trace Results < span id ="trace-count " class ="badge bg-secondary "> 0</ span > </ h5 >
132+ < div id ="pagination-info " class ="text-muted " style ="display: none; ">
133+ < span id ="result-range "> </ span >
134+ </ div >
106135 </ div >
107136 < div class ="card-body ">
108137 < div id ="trace-list "> </ div >
138+ <!-- Pagination Controls -->
139+ < div id ="pagination-controls " class ="d-flex justify-content-between align-items-center mt-3 " style ="display: none; ">
140+ < button id ="prev-page " class ="btn btn-outline-primary " onclick ="previousPage() ">
141+ < i class ="fas fa-chevron-left "> </ i > Previous
142+ </ button >
143+ < span id ="page-info " class ="text-muted "> </ span >
144+ < button id ="next-page " class ="btn btn-outline-primary " onclick ="nextPage() ">
145+ Next < i class ="fas fa-chevron-right "> </ i >
146+ </ button >
147+ </ div >
109148 </ div >
110149 </ div >
111150
@@ -127,6 +166,10 @@ <h5 class="card-title mb-0">Call Graph Visualization</h5>
127166< script >
128167let cyGraph = null ;
129168let currentTraces = [ ] ;
169+ let currentQueryParams = { } ;
170+ let currentStart = 0 ;
171+ let currentLimit = 20 ;
172+ let hasMoreResults = false ;
130173
131174document . getElementById ( 'trace-query-form' ) . addEventListener ( 'submit' , function ( e ) {
132175 e . preventDefault ( ) ;
@@ -137,6 +180,18 @@ <h5 class="card-title mb-0">Call Graph Visualization</h5>
137180 document . getElementById ( 'trace-query-form' ) . reset ( ) ;
138181 document . getElementById ( 'results-section' ) . style . display = 'none' ;
139182 document . getElementById ( 'error-display' ) . style . display = 'none' ;
183+
184+ // Reset to default values
185+ document . getElementById ( 'service-name' ) . value = 'sentrius-api' ;
186+ document . getElementById ( 'limit' ) . value = '20' ;
187+ document . getElementById ( 'start' ) . value = '0' ;
188+
189+ // Reset pagination state
190+ currentStart = 0 ;
191+ currentLimit = 20 ;
192+ hasMoreResults = false ;
193+ currentQueryParams = { } ;
194+
140195 if ( cyGraph ) {
141196 cyGraph . destroy ( ) ;
142197 cyGraph = null ;
@@ -155,6 +210,15 @@ <h5 class="card-title mb-0">Call Graph Visualization</h5>
155210 }
156211 }
157212
213+ // Store current query params for pagination
214+ currentQueryParams = Object . fromEntries ( params . entries ( ) ) ;
215+ currentStart = parseInt ( currentQueryParams . start || '0' ) ;
216+ currentLimit = parseInt ( currentQueryParams . limit || '20' ) ;
217+
218+ await executeSearch ( params ) ;
219+ }
220+
221+ async function executeSearch ( params ) {
158222 // Show loading
159223 document . getElementById ( 'loading' ) . style . display = 'block' ;
160224 document . getElementById ( 'results-section' ) . style . display = 'none' ;
@@ -168,6 +232,7 @@ <h5 class="card-title mb-0">Call Graph Visualization</h5>
168232
169233 const data = await response . json ( ) ;
170234 currentTraces = data . traces || [ ] ;
235+ hasMoreResults = data . hasMore || false ;
171236 displayResults ( data ) ;
172237 } catch ( error ) {
173238 console . error ( 'Error fetching traces:' , error ) ;
@@ -181,6 +246,9 @@ <h5 class="card-title mb-0">Call Graph Visualization</h5>
181246 const traceCount = data . traces ? data . traces . length : 0 ;
182247 document . getElementById ( 'trace-count' ) . textContent = traceCount ;
183248
249+ // Update pagination info
250+ updatePaginationInfo ( data ) ;
251+
184252 // Display trace list
185253 const traceList = document . getElementById ( 'trace-list' ) ;
186254 if ( traceCount === 0 ) {
@@ -197,6 +265,50 @@ <h5 class="card-title mb-0">Call Graph Visualization</h5>
197265 document . getElementById ( 'results-section' ) . style . display = 'block' ;
198266}
199267
268+ function updatePaginationInfo ( data ) {
269+ const start = data . start || 0 ;
270+ const limit = data . limit || 20 ;
271+ const count = data . count || 0 ;
272+ const hasMore = data . hasMore || false ;
273+
274+ // Update range display
275+ const endRange = start + count ;
276+ document . getElementById ( 'result-range' ) . textContent =
277+ count > 0 ? `Showing ${ start + 1 } -${ endRange } ` : 'No results' ;
278+ document . getElementById ( 'pagination-info' ) . style . display = count > 0 ? 'block' : 'none' ;
279+
280+ // Update pagination controls
281+ const prevBtn = document . getElementById ( 'prev-page' ) ;
282+ const nextBtn = document . getElementById ( 'next-page' ) ;
283+ const pageInfo = document . getElementById ( 'page-info' ) ;
284+
285+ prevBtn . disabled = start === 0 ;
286+ nextBtn . disabled = ! hasMore ;
287+
288+ const currentPage = Math . floor ( start / limit ) + 1 ;
289+ pageInfo . textContent = `Page ${ currentPage } ` ;
290+
291+ document . getElementById ( 'pagination-controls' ) . style . display = count > 0 ? 'flex' : 'none' ;
292+ }
293+
294+ async function previousPage ( ) {
295+ if ( currentStart > 0 ) {
296+ currentStart = Math . max ( 0 , currentStart - currentLimit ) ;
297+ const params = new URLSearchParams ( currentQueryParams ) ;
298+ params . set ( 'start' , currentStart . toString ( ) ) ;
299+ await executeSearch ( params ) ;
300+ }
301+ }
302+
303+ async function nextPage ( ) {
304+ if ( hasMoreResults ) {
305+ currentStart += currentLimit ;
306+ const params = new URLSearchParams ( currentQueryParams ) ;
307+ params . set ( 'start' , currentStart . toString ( ) ) ;
308+ await executeSearch ( params ) ;
309+ }
310+ }
311+
200312function createTraceHtml ( trace ) {
201313 const duration = trace . duration ? ( trace . duration / 1000 ) . toFixed ( 2 ) + ' ms' : 'Unknown' ;
202314 const startTime = trace . startTime ? new Date ( trace . startTime / 1000 ) . toLocaleString ( ) : 'Unknown' ;
@@ -402,21 +514,28 @@ <h5 class="card-title mb-0">Call Graph Visualization</h5>
402514 if ( response . ok ) {
403515 const data = await response . json ( ) ;
404516 const serviceSelect = document . getElementById ( 'service-name' ) ;
517+ const currentValue = serviceSelect . value ; // Preserve current selection
405518
406- // Clear existing options except "All Services"
407- while ( serviceSelect . children . length > 1 ) {
519+ // Clear existing options except defaults
520+ while ( serviceSelect . children . length > 2 ) {
408521 serviceSelect . removeChild ( serviceSelect . lastChild ) ;
409522 }
410523
411524 // Add services from Jaeger
412525 if ( data . data && Array . isArray ( data . data ) ) {
413526 data . data . forEach ( service => {
414- const option = document . createElement ( 'option' ) ;
415- option . value = service ;
416- option . textContent = service ;
417- serviceSelect . appendChild ( option ) ;
527+ // Skip if already exists in defaults
528+ if ( ! [ 'sentrius-api' , '' ] . includes ( service ) ) {
529+ const option = document . createElement ( 'option' ) ;
530+ option . value = service ;
531+ option . textContent = service ;
532+ serviceSelect . appendChild ( option ) ;
533+ }
418534 } ) ;
419535 }
536+
537+ // Restore selection
538+ serviceSelect . value = currentValue ;
420539 }
421540 } catch ( error ) {
422541 console . warn ( 'Could not load services from Jaeger, trying mock data:' , error ) ;
@@ -426,15 +545,22 @@ <h5 class="card-title mb-0">Call Graph Visualization</h5>
426545 if ( mockResponse . ok ) {
427546 const mockData = await mockResponse . json ( ) ;
428547 const serviceSelect = document . getElementById ( 'service-name' ) ;
548+ const currentValue = serviceSelect . value ;
429549
430550 if ( mockData . data && Array . isArray ( mockData . data ) ) {
431551 mockData . data . forEach ( service => {
432- const option = document . createElement ( 'option' ) ;
433- option . value = service ;
434- option . textContent = service + ' (mock)' ;
435- serviceSelect . appendChild ( option ) ;
552+ // Skip if already exists in defaults
553+ if ( ! [ 'sentrius-api' , '' ] . includes ( service ) ) {
554+ const option = document . createElement ( 'option' ) ;
555+ option . value = service ;
556+ option . textContent = service + ' (mock)' ;
557+ serviceSelect . appendChild ( option ) ;
558+ }
436559 } ) ;
437560 }
561+
562+ // Restore selection
563+ serviceSelect . value = currentValue ;
438564 }
439565 } catch ( mockError ) {
440566 console . warn ( 'Could not load mock services either:' , mockError ) ;
0 commit comments