@@ -51,6 +51,7 @@ const emit = defineEmits<{
5151}>()
5252
5353const DEFAULT_PRESET = webhookPresetById (' generic' )
54+ const RUN_PAGE_SIZE = 10
5455const helpText = {
5556 guards: ' Guards reject noisy, duplicate, or stale webhook requests before they create an agent session.' ,
5657 rateLimit: ' Limits how many requests this source accepts within one time window.' ,
@@ -85,17 +86,18 @@ const fullPermissionConfirmed = ref(false)
8586const defaultModelLabel = ref (' Default model from user config' )
8687const pollingTimer = ref <ReturnType <typeof setInterval > | null >(null )
8788const selectedRunId = ref (' ' )
89+ const runPage = ref (1 )
90+ const runPageItems = ref <WebhookRun []>([])
91+ const runPageHasNext = ref (false )
92+ const isRunPageLoading = ref (false )
8893const endpointPanel = ref <HTMLElement | null >(null )
94+ let runPageLoadGeneration = 0
8995
9096const form = ref (defaultForm ())
9197
9298const isEmbedded = computed (() => props .embedded )
9399const selectedSource = computed (() => sources .value .find ((source ) => source .id === selectedSourceId .value ) || null )
94- const selectedRuns = computed (() => {
95- if (! selectedSourceId .value ) return runs .value
96- return runs .value .filter ((run ) => run .sourceID === selectedSourceId .value )
97- })
98- const selectedRun = computed (() => selectedRuns .value .find ((run ) => run .id === selectedRunId .value ) || null )
100+ const selectedRun = computed (() => runPageItems .value .find ((run ) => run .id === selectedRunId .value ) || null )
99101
100102const sortedProjects = computed (() => props .projects .slice ().sort ((a , b ) => b .time .updated - a .time .updated ))
101103const selectedProvider = computed (() => providers .value .find ((provider ) => provider .id === form .value .modelProviderID ))
@@ -250,9 +252,11 @@ async function loadAll() {
250252 }
251253 if (! selectedSourceId .value ) {
252254 resetCreateForm ()
255+ resetRunPagination ()
253256 showCreateForm .value = true
254257 } else {
255258 loadFormFromSource (selectedSource .value )
259+ await refreshRunPage ()
256260 }
257261 } catch (err ) {
258262 error .value = friendlyError (err )
@@ -347,6 +351,7 @@ async function createSource() {
347351 showCreateForm .value = false
348352 revealedSecretSourceId .value = created .source .id
349353 revealedSecret .value = created .secret
354+ resetRunPagination ()
350355 notice .value = ' Webhook source created. Copy the full URL now; the secret is shown only once.'
351356 await refreshRuns ()
352357 await nextTick ()
@@ -400,6 +405,7 @@ async function sendTest() {
400405 try {
401406 const result = await webhookApi .sendTest (source .id , payload )
402407 notice .value = ` Test sent. HTTP ${result .status }. `
408+ resetRunPagination ()
403409 await refreshRuns ()
404410 if (result .body && typeof result .body === ' object' && ' runId' in result .body ) {
405411 selectedRunId .value = String ((result .body as { runId? : unknown }).runId || ' ' )
@@ -448,6 +454,7 @@ async function deleteSource() {
448454 await webhookApi .deleteSource (source .id )
449455 sources .value = await webhookApi .sources ()
450456 selectedSourceId .value = sources .value [0 ]?.id || ' '
457+ resetRunPagination ()
451458 if (! selectedSourceId .value ) {
452459 showCreateForm .value = true
453460 resetCreateForm ()
@@ -462,7 +469,71 @@ async function deleteSource() {
462469}
463470
464471async function refreshRuns() {
465- runs .value = await webhookApi .runs ({ limit: 100 })
472+ const [nextRuns] = await Promise .all ([
473+ webhookApi .runs ({ limit: 100 }),
474+ refreshRunPage (),
475+ ])
476+ runs .value = nextRuns
477+ }
478+
479+ function resetRunPagination() {
480+ runPage .value = 1
481+ runPageItems .value = []
482+ runPageHasNext .value = false
483+ selectedRunId .value = ' '
484+ }
485+
486+ async function refreshRunPage() {
487+ const sourceID = selectedSourceId .value
488+ const page = runPage .value
489+ const generation = ++ runPageLoadGeneration
490+ if (! sourceID ) {
491+ runPageItems .value = []
492+ runPageHasNext .value = false
493+ isRunPageLoading .value = false
494+ return
495+ }
496+
497+ isRunPageLoading .value = true
498+ try {
499+ const pageRuns = await webhookApi .runs ({
500+ sourceID ,
501+ limit: RUN_PAGE_SIZE + 1 ,
502+ offset: (page - 1 ) * RUN_PAGE_SIZE ,
503+ })
504+ if (generation !== runPageLoadGeneration || sourceID !== selectedSourceId .value || page !== runPage .value ) return
505+ runPageItems .value = pageRuns .slice (0 , RUN_PAGE_SIZE )
506+ runPageHasNext .value = pageRuns .length > RUN_PAGE_SIZE
507+ if (selectedRunId .value && ! runPageItems .value .some ((run ) => run .id === selectedRunId .value )) {
508+ selectedRunId .value = ' '
509+ }
510+ } finally {
511+ if (generation === runPageLoadGeneration ) {
512+ isRunPageLoading .value = false
513+ }
514+ }
515+ }
516+
517+ async function previousRunPage() {
518+ if (runPage .value <= 1 || isRunPageLoading .value ) return
519+ runPage .value -= 1
520+ selectedRunId .value = ' '
521+ try {
522+ await refreshRunPage ()
523+ } catch (err ) {
524+ error .value = friendlyError (err )
525+ }
526+ }
527+
528+ async function nextRunPage() {
529+ if (! runPageHasNext .value || isRunPageLoading .value ) return
530+ runPage .value += 1
531+ selectedRunId .value = ' '
532+ try {
533+ await refreshRunPage ()
534+ } catch (err ) {
535+ error .value = friendlyError (err )
536+ }
466537}
467538
468539async function copyText(text : string ) {
@@ -499,7 +570,7 @@ async function openRunSession(run: WebhookRun) {
499570function beginCreate() {
500571 showCreateForm .value = true
501572 selectedSourceId .value = ' '
502- selectedRunId . value = ' '
573+ resetRunPagination ()
503574 revealedSecret .value = ' '
504575 revealedSecretSourceId .value = ' '
505576 resetCreateForm ()
@@ -508,8 +579,11 @@ function beginCreate() {
508579function selectSource(source : WebhookSource ) {
509580 showCreateForm .value = false
510581 selectedSourceId .value = source .id
511- selectedRunId . value = ' '
582+ resetRunPagination ()
512583 showMcpPicker .value = false
584+ void refreshRunPage ().catch ((err ) => {
585+ error .value = friendlyError (err )
586+ })
513587}
514588
515589function selectRun(run : WebhookRun ) {
@@ -1073,7 +1147,7 @@ onUnmounted(() => {
10731147 <h3 >Recent Runs</h3 >
10741148 <div class =" run-list" >
10751149 <div
1076- v-for =" run in selectedRuns "
1150+ v-for =" run in runPageItems "
10771151 :key =" run.id"
10781152 class =" run-row"
10791153 :class =" { active: selectedRunId === run.id }"
@@ -1086,10 +1160,22 @@ onUnmounted(() => {
10861160 <button v-if =" run.sessionID" class =" link-btn" @click.stop =" openRunSession(run)" >Open session</button >
10871161 <span v-else class =" muted-text" >{{ run.guardType || 'No session' }}</span >
10881162 </div >
1089- <div v-if =" selectedRuns.length === 0" class =" empty-note" >
1163+ <div v-if =" isRunPageLoading && runPageItems.length === 0" class =" empty-note" >
1164+ Loading runs...
1165+ </div >
1166+ <div v-else-if =" runPageItems.length === 0" class =" empty-note" >
10901167 No runs for this source yet.
10911168 </div >
10921169 </div >
1170+ <div v-if =" runPageItems.length > 0 || runPage > 1" class =" run-pagination" >
1171+ <button class =" btn" @click =" previousRunPage" :disabled =" runPage <= 1 || isRunPageLoading" >
1172+ Previous
1173+ </button >
1174+ <span >Page {{ runPage }}</span >
1175+ <button class =" btn" @click =" nextRunPage" :disabled =" !runPageHasNext || isRunPageLoading" >
1176+ Next
1177+ </button >
1178+ </div >
10931179 <div v-if =" selectedRun" class =" run-detail" >
10941180 <div class =" run-detail-head" >
10951181 <strong >{{ selectedRun.id }}</strong >
@@ -1854,6 +1940,21 @@ textarea {
18541940 color : var (--text-muted );
18551941}
18561942
1943+ .run-pagination {
1944+ display : flex ;
1945+ align-items : center ;
1946+ justify-content : flex-end ;
1947+ gap : var (--space-sm );
1948+ margin-top : var (--space-md );
1949+ color : var (--text-muted );
1950+ font-size : 12px ;
1951+ }
1952+
1953+ .run-pagination .btn {
1954+ height : 32px ;
1955+ padding : 0 var (--space-sm );
1956+ }
1957+
18571958.link-btn {
18581959 border : 0 ;
18591960 background : transparent ;
0 commit comments