@@ -40,50 +40,100 @@ interface JobStats {
40
40
runtime? : number
41
41
error? : string
42
42
payload: any
43
+ priority: number
44
+ tags: string []
45
+ dependencies? : string []
46
+ retry_history? : {
47
+ attempted_at: string
48
+ failed_at: string
49
+ error: string
50
+ }[]
51
+ stack_trace? : string
52
+ }
53
+
54
+ interface RetryAttempt {
55
+ attempted_at: string
56
+ error: string
43
57
}
44
58
45
59
interface Job {
46
60
id: string
47
61
name: string
62
+ status: ' queued' | ' processing' | ' completed' | ' failed'
48
63
queue: string
49
- status : ' queued ' | ' processing ' | ' failed ' | ' completed '
50
- runtime ? : number
64
+ payload : any
65
+ created_at : string
51
66
started_at? : string
67
+ completed_at? : string
68
+ error? : string
69
+ stack_trace? : string
70
+ tags? : string []
71
+ priority? : number
72
+ dependencies? : string []
73
+ retry_history? : RetryAttempt []
74
+ attempts: number
75
+ runtime? : number
76
+ logs? : Array <{
77
+ timestamp: string
78
+ level: ' info' | ' warning' | ' error'
79
+ message: string
80
+ }>
52
81
}
53
82
54
- const recentJobs = ref <JobStats []>([
83
+ const recentJobs = ref <Job []>([
55
84
{
56
85
id: ' 1' ,
57
- name: ' ProcessPayment' ,
58
- queue: ' high' ,
59
- attempts: 1 ,
86
+ name: ' ProcessPodcast' ,
60
87
status: ' completed' ,
61
- started_at: ' 2024-03-14 10:15:23' ,
62
- finished_at: ' 2024-03-14 10:15:25' ,
63
- runtime: 2.3 ,
64
- payload: { order_id: ' 12345' },
88
+ queue: ' default' ,
89
+ payload: { podcast_id: 123 },
90
+ created_at: ' 2024-03-14 10:00:00' ,
91
+ started_at: ' 2024-03-14 10:00:01' ,
92
+ completed_at: ' 2024-03-14 10:00:05' ,
93
+ attempts: 1 ,
94
+ runtime: 4.2 ,
95
+ tags: [' podcast' , ' media' ],
96
+ priority: 1 ,
97
+ logs: [
98
+ { timestamp: ' 2024-03-14 10:00:01' , level: ' info' , message: ' Starting podcast processing' },
99
+ { timestamp: ' 2024-03-14 10:00:03' , level: ' info' , message: ' Transcoding audio file' },
100
+ { timestamp: ' 2024-03-14 10:00:05' , level: ' info' , message: ' Podcast processing completed' }
101
+ ]
65
102
},
66
103
{
67
104
id: ' 2' ,
68
- name: ' SendWelcomeEmail' ,
69
- queue: ' default' ,
70
- attempts: 3 ,
105
+ name: ' SendNewsletter' ,
71
106
status: ' failed' ,
72
- started_at: ' 2024-03-14 10:14:00' ,
73
- finished_at: ' 2024-03-14 10:14:05' ,
74
- runtime: 5.1 ,
107
+ queue: ' high' ,
108
+ payload: { newsletter_id: 456 },
109
+ created_at: ' 2024-03-14 10:01:00' ,
110
+ started_at: ' 2024-03-14 10:01:01' ,
75
111
error: ' SMTP connection failed' ,
76
- payload: { user_id: ' 789' },
77
- },
78
- {
79
- id: ' 3' ,
80
- name: ' GenerateReport' ,
81
- queue: ' low' ,
82
- attempts: 1 ,
83
- status: ' processing' ,
84
- started_at: ' 2024-03-14 10:16:00' ,
85
- payload: { report_type: ' monthly' },
86
- },
112
+ stack_trace: ' Error: SMTP connection failed\n at SMTPClient.connect (/app/vendor/smtp.js:123)\n at Newsletter.send (/app/app/Jobs/SendNewsletter.php:45)' ,
113
+ attempts: 3 ,
114
+ tags: [' email' , ' newsletter' ],
115
+ priority: 2 ,
116
+ retry_history: [
117
+ {
118
+ attempted_at: ' 2024-03-14 10:01:01' ,
119
+ error: ' SMTP connection timeout'
120
+ },
121
+ {
122
+ attempted_at: ' 2024-03-14 10:02:01' ,
123
+ error: ' SMTP authentication failed'
124
+ },
125
+ {
126
+ attempted_at: ' 2024-03-14 10:03:01' ,
127
+ error: ' SMTP connection failed'
128
+ }
129
+ ],
130
+ logs: [
131
+ { timestamp: ' 2024-03-14 10:01:01' , level: ' info' , message: ' Starting newsletter delivery' },
132
+ { timestamp: ' 2024-03-14 10:01:01' , level: ' warning' , message: ' SMTP connection timeout' },
133
+ { timestamp: ' 2024-03-14 10:02:01' , level: ' warning' , message: ' SMTP authentication failed' },
134
+ { timestamp: ' 2024-03-14 10:03:01' , level: ' error' , message: ' SMTP connection failed' }
135
+ ]
136
+ }
87
137
])
88
138
89
139
const timeRange = ref <' hour' | ' day' | ' week' >(' hour' )
@@ -214,9 +264,50 @@ onMounted(async () => {
214
264
isLoading .value = false
215
265
})
216
266
217
- const handleRetry = async (job : Job ) => {
218
- // Implement retry logic
219
- console .log (' Retrying job:' , job .id )
267
+ const retryJob = async (jobId : string ) => {
268
+ isLoading .value = true
269
+ await new Promise (resolve => setTimeout (resolve , 1000 ))
270
+ const job = recentJobs .value .find (j => j .id === jobId )
271
+ if (job ) {
272
+ job .status = ' queued'
273
+ job .attempts += 1
274
+ }
275
+ isLoading .value = false
276
+ }
277
+
278
+ const cancelJob = async (jobId : string ) => {
279
+ isLoading .value = true
280
+ await new Promise (resolve => setTimeout (resolve , 1000 ))
281
+ const job = recentJobs .value .find (j => j .id === jobId )
282
+ if (job ) {
283
+ job .status = ' failed'
284
+ job .error = ' Job cancelled by user'
285
+ }
286
+ isLoading .value = false
287
+ }
288
+
289
+ // Add state for expanded job rows
290
+ const expandedJobs = ref <Set <string >>(new Set ())
291
+
292
+ // Toggle job expansion
293
+ const toggleJobExpansion = (jobId : string ) => {
294
+ if (expandedJobs .value .has (jobId )) {
295
+ expandedJobs .value .delete (jobId )
296
+ } else {
297
+ expandedJobs .value .add (jobId )
298
+ }
299
+ }
300
+
301
+ // Get log level color classes
302
+ const getLogLevelColor = (level : string ): string => {
303
+ switch (level ) {
304
+ case ' error' :
305
+ return ' text-red-600 dark:text-red-400'
306
+ case ' warning' :
307
+ return ' text-yellow-600 dark:text-yellow-400'
308
+ default :
309
+ return ' text-gray-600 dark:text-gray-400'
310
+ }
220
311
}
221
312
</script >
222
313
@@ -371,45 +462,54 @@ const handleRetry = async (job: Job) => {
371
462
<th scope =" col" class =" px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100" >Queue</th >
372
463
<th scope =" col" class =" px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100" >Status</th >
373
464
<th scope =" col" class =" px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100" >Runtime</th >
374
- <th scope =" col" class =" px-3 py-3.5 text-right text-sm font-semibold text-gray-900 dark:text-gray-100" >Started</th >
465
+ <th scope =" col" class =" px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100" >Started</th >
375
466
<th scope =" col" class =" relative py-3.5 pl-3 pr-4 sm:pr-0" >
376
467
<span class =" sr-only" >Actions</span >
377
468
</th >
378
469
</tr >
379
470
</thead >
380
471
<tbody class =" divide-y divide-gray-200 dark:divide-blue-gray-600" >
381
- <tr v-for =" job in recentJobs" :key =" job.id" >
382
- <td class =" whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 dark:text-gray-100 sm:pl-0" >{{ job.name }}</td >
383
- <td class =" whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400" >{{ job.queue }}</td >
384
- <td class =" whitespace-nowrap px-3 py-4 text-sm" >
385
- <span
386
- class =" inline-flex items-center rounded-md px-2 py-1 text-xs font-medium ring-1 ring-inset"
387
- :class =" getJobStatusColor(job.status)"
388
- >
389
- {{ job.status }}
390
- </span >
391
- </td >
392
- <td class =" whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400 font-mono" >{{ job.runtime ? `${job.runtime.toFixed(1)}s` : '-' }}</td >
393
- <td class =" whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400 text-right" >{{ job.started_at }}</td >
394
- <td class =" relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-0" >
395
- <div class =" flex justify-end space-x-2" >
396
- <router-link
397
- :to =" `/jobs/${job.id}`"
398
- class =" text-blue-600 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300"
399
- >
400
- View
401
- </router-link >
402
- <button
403
- v-if =" job.status === 'failed'"
404
- type =" button"
405
- class =" text-blue-600 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300"
406
- @click =" handleRetry(job)"
407
- >
408
- Retry
409
- </button >
410
- </div >
411
- </td >
412
- </tr >
472
+ <template v-for =" job in recentJobs " :key =" job .id " >
473
+ <tr >
474
+ <td class =" whitespace-nowrap py-4 pl-4 pr-3 text-sm sm:pl-0" >
475
+ <div class =" font-medium text-gray-900 dark:text-gray-100" >{{ job.name }}</div >
476
+ <div class =" mt-1 flex items-center gap-1" >
477
+ <span v-for =" tag in job.tags" :key =" tag" class =" inline-flex items-center rounded-md px-2 py-1 text-xs font-medium ring-1 ring-inset bg-blue-50 text-blue-700 ring-blue-600/20 dark:bg-blue-900/50 dark:text-blue-300" >
478
+ {{ tag }}
479
+ </span >
480
+ </div >
481
+ </td >
482
+ <td class =" whitespace-nowrap px-3 py-4 text-sm" >
483
+ <div class =" text-gray-900 dark:text-gray-100" >{{ job.queue }}</div >
484
+ <div class =" mt-1 text-gray-500 dark:text-gray-400" >Priority: {{ job.priority }}</div >
485
+ </td >
486
+ <td class =" whitespace-nowrap px-3 py-4 text-sm" >
487
+ <span class =" inline-flex items-center rounded-md px-2 py-1 text-xs font-medium ring-1 ring-inset" :class =" getJobStatusColor(job.status)" >
488
+ {{ job.status }}
489
+ </span >
490
+ <div v-if =" job.status === 'failed'" class =" mt-1 text-xs text-red-600 dark:text-red-400" >
491
+ Attempts: {{ job.attempts }}
492
+ </div >
493
+ </td >
494
+ <td class =" whitespace-nowrap px-3 py-4 text-sm" >
495
+ <div class =" font-mono text-gray-900 dark:text-gray-100" >{{ job.runtime ? `${job.runtime.toFixed(1)}s` : '-' }}</div >
496
+ </td >
497
+ <td class =" whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400" >
498
+ <div >Started: {{ job.started_at || '-' }}</div >
499
+ <div >Completed: {{ job.completed_at || '-' }}</div >
500
+ </td >
501
+ <td class =" relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-0" >
502
+ <div class =" flex justify-end space-x-2" >
503
+ <router-link
504
+ :to =" `/jobs/${job.id}`"
505
+ class =" text-blue-600 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300"
506
+ >
507
+ View
508
+ </router-link >
509
+ </div >
510
+ </td >
511
+ </tr >
512
+ </template >
413
513
</tbody >
414
514
</table >
415
515
</div >
0 commit comments