@rudderjs/horizon 4.0.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/boost/guidelines.md +10 -0
  2. package/dist/api/routes.d.ts +11 -4
  3. package/dist/api/routes.d.ts.map +1 -1
  4. package/dist/api/routes.js +102 -124
  5. package/dist/api/routes.js.map +1 -1
  6. package/dist/index.d.ts +1 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +8 -3
  9. package/dist/index.js.map +1 -1
  10. package/dist/routes.d.ts +22 -0
  11. package/dist/routes.d.ts.map +1 -0
  12. package/dist/routes.js +47 -0
  13. package/dist/routes.js.map +1 -0
  14. package/dist/views/vanilla/Dashboard.d.ts +10 -0
  15. package/dist/views/vanilla/Dashboard.d.ts.map +1 -0
  16. package/dist/views/vanilla/Dashboard.js +86 -0
  17. package/dist/views/vanilla/Dashboard.js.map +1 -0
  18. package/dist/views/vanilla/FailedJobs.d.ts +6 -0
  19. package/dist/views/vanilla/FailedJobs.d.ts.map +1 -0
  20. package/dist/views/vanilla/FailedJobs.js +17 -0
  21. package/dist/views/vanilla/FailedJobs.js.map +1 -0
  22. package/dist/views/vanilla/Layout.d.ts +20 -0
  23. package/dist/views/vanilla/Layout.d.ts.map +1 -0
  24. package/dist/{ui/layout.js → views/vanilla/Layout.js} +11 -15
  25. package/dist/views/vanilla/Layout.js.map +1 -0
  26. package/dist/views/vanilla/Queues.d.ts +6 -0
  27. package/dist/views/vanilla/Queues.d.ts.map +1 -0
  28. package/dist/views/vanilla/Queues.js +54 -0
  29. package/dist/views/vanilla/Queues.js.map +1 -0
  30. package/dist/views/vanilla/RecentJobs.d.ts +6 -0
  31. package/dist/views/vanilla/RecentJobs.d.ts.map +1 -0
  32. package/dist/views/vanilla/RecentJobs.js +17 -0
  33. package/dist/views/vanilla/RecentJobs.js.map +1 -0
  34. package/dist/views/vanilla/Workers.d.ts +6 -0
  35. package/dist/views/vanilla/Workers.d.ts.map +1 -0
  36. package/dist/views/vanilla/Workers.js +60 -0
  37. package/dist/views/vanilla/Workers.js.map +1 -0
  38. package/dist/views/vanilla/_html.d.ts +28 -0
  39. package/dist/views/vanilla/_html.d.ts.map +1 -0
  40. package/dist/views/vanilla/_html.js +56 -0
  41. package/dist/views/vanilla/_html.js.map +1 -0
  42. package/dist/views/vanilla/_jobTable.d.ts +11 -0
  43. package/dist/views/vanilla/_jobTable.d.ts.map +1 -0
  44. package/dist/views/vanilla/_jobTable.js +89 -0
  45. package/dist/views/vanilla/_jobTable.js.map +1 -0
  46. package/dist/views/vanilla/index.d.ts +8 -0
  47. package/dist/views/vanilla/index.d.ts.map +1 -0
  48. package/dist/views/vanilla/index.js +8 -0
  49. package/dist/views/vanilla/index.js.map +1 -0
  50. package/package.json +3 -3
  51. package/dist/ui/layout.d.ts +0 -6
  52. package/dist/ui/layout.d.ts.map +0 -1
  53. package/dist/ui/layout.js.map +0 -1
  54. package/dist/ui/pages.d.ts +0 -6
  55. package/dist/ui/pages.d.ts.map +0 -1
  56. package/dist/ui/pages.js +0 -295
  57. package/dist/ui/pages.js.map +0 -1
package/dist/ui/pages.js DELETED
@@ -1,295 +0,0 @@
1
- import { layout } from './layout.js';
2
- // ─── Dashboard ─────────────────────────────────────────────
3
- export function dashboardPage(basePath, apiPrefix) {
4
- return layout('Dashboard', `
5
- <div x-data="dashboard()" x-init="load()" x-cloak>
6
- <h2 class="text-xl font-bold mb-6">Dashboard</h2>
7
-
8
- <!-- Stats Cards -->
9
- <div class="grid grid-cols-2 md:grid-cols-5 gap-4 mb-8">
10
- <div class="bg-white rounded-xl border border-gray-200 p-4 shadow-sm">
11
- <div class="text-2xl font-bold" x-text="stats.jobs?.total || 0"></div>
12
- <div class="text-sm text-gray-500">Total Jobs</div>
13
- </div>
14
- <div class="bg-white rounded-xl border border-gray-200 p-4 shadow-sm">
15
- <div class="text-2xl font-bold text-amber-600" x-text="stats.jobs?.pending || 0"></div>
16
- <div class="text-sm text-gray-500">Pending</div>
17
- </div>
18
- <div class="bg-white rounded-xl border border-gray-200 p-4 shadow-sm">
19
- <div class="text-2xl font-bold text-blue-600" x-text="stats.jobs?.processing || 0"></div>
20
- <div class="text-sm text-gray-500">Processing</div>
21
- </div>
22
- <div class="bg-white rounded-xl border border-gray-200 p-4 shadow-sm">
23
- <div class="text-2xl font-bold text-green-600" x-text="stats.jobs?.completed || 0"></div>
24
- <div class="text-sm text-gray-500">Completed</div>
25
- </div>
26
- <div class="bg-white rounded-xl border border-gray-200 p-4 shadow-sm">
27
- <div class="text-2xl font-bold text-red-600" x-text="stats.jobs?.failed || 0"></div>
28
- <div class="text-sm text-gray-500">Failed</div>
29
- </div>
30
- </div>
31
-
32
- <!-- Queue Metrics -->
33
- <div class="bg-white rounded-xl border border-gray-200 shadow-sm">
34
- <div class="px-5 py-4 border-b border-gray-100 flex items-center justify-between">
35
- <h3 class="text-sm font-semibold text-gray-700">Queue Metrics</h3>
36
- <span class="text-xs text-gray-400" x-text="stats.workers + ' worker(s)'"></span>
37
- </div>
38
- <div class="overflow-x-auto">
39
- <table class="w-full text-sm">
40
- <thead class="bg-gray-50 text-gray-500 text-xs uppercase">
41
- <tr>
42
- <th class="px-5 py-3 text-left">Queue</th>
43
- <th class="px-5 py-3 text-right">Throughput</th>
44
- <th class="px-5 py-3 text-right">Wait Time</th>
45
- <th class="px-5 py-3 text-right">Runtime</th>
46
- <th class="px-5 py-3 text-right">Pending</th>
47
- <th class="px-5 py-3 text-right">Failed</th>
48
- </tr>
49
- </thead>
50
- <tbody class="divide-y divide-gray-100">
51
- <template x-for="q in stats.queues || []" :key="q.queue">
52
- <tr class="hover:bg-gray-50">
53
- <td class="px-5 py-3 font-medium" x-text="q.queue"></td>
54
- <td class="px-5 py-3 text-right" x-text="q.throughput + '/min'"></td>
55
- <td class="px-5 py-3 text-right" x-text="q.waitTime + 'ms'"></td>
56
- <td class="px-5 py-3 text-right" x-text="q.runtime + 'ms'"></td>
57
- <td class="px-5 py-3 text-right text-amber-600" x-text="q.pending"></td>
58
- <td class="px-5 py-3 text-right text-red-600" x-text="q.failed"></td>
59
- </tr>
60
- </template>
61
- <tr x-show="!stats.queues?.length">
62
- <td colspan="6" class="px-5 py-8 text-center text-gray-400">No queue data yet.</td>
63
- </tr>
64
- </tbody>
65
- </table>
66
- </div>
67
- </div>
68
-
69
- <div class="mt-4 text-center text-xs text-gray-400">Auto-refreshes every 10 seconds</div>
70
- </div>
71
- <script>
72
- function dashboard() {
73
- return {
74
- stats: {},
75
- async load() { await this.refresh(); setInterval(() => this.refresh(), 10000) },
76
- async refresh() { this.stats = await fetch('${apiPrefix}/stats').then(r => r.json()) }
77
- }
78
- }
79
- </script>`, basePath, '/');
80
- }
81
- // ─── Recent Jobs ───────────────────────────────────────────
82
- export function recentJobsPage(basePath, apiPrefix) {
83
- return layout('Recent Jobs', `
84
- <div x-data="jobList('recent')" x-init="load()" x-cloak>
85
- <div class="flex items-center justify-between mb-6">
86
- <h2 class="text-xl font-bold">Recent Jobs</h2>
87
- <input type="text" x-model="search" @input.debounce.300ms="load()"
88
- placeholder="Search..." class="text-sm border border-gray-300 rounded-lg px-3 py-1.5 focus:ring-2 focus:ring-teal-500 outline-none">
89
- </div>
90
- ${jobTable(apiPrefix)}
91
- </div>
92
- ${jobScript(apiPrefix)}`, basePath, '/jobs/recent');
93
- }
94
- // ─── Failed Jobs ───────────────────────────────────────────
95
- export function failedJobsPage(basePath, apiPrefix) {
96
- return layout('Failed Jobs', `
97
- <div x-data="jobList('failed')" x-init="load()" x-cloak>
98
- <div class="flex items-center justify-between mb-6">
99
- <h2 class="text-xl font-bold">Failed Jobs</h2>
100
- <input type="text" x-model="search" @input.debounce.300ms="load()"
101
- placeholder="Search..." class="text-sm border border-gray-300 rounded-lg px-3 py-1.5 focus:ring-2 focus:ring-teal-500 outline-none">
102
- </div>
103
- ${jobTable(apiPrefix)}
104
- </div>
105
- ${jobScript(apiPrefix)}`, basePath, '/jobs/failed');
106
- }
107
- function jobTable(apiPrefix) {
108
- return `
109
- <div class="bg-white rounded-xl border border-gray-200 shadow-sm overflow-hidden">
110
- <table class="w-full text-sm">
111
- <thead class="bg-gray-50 border-b border-gray-200 text-xs uppercase text-gray-500">
112
- <tr>
113
- <th class="px-4 py-3 text-left">Name</th>
114
- <th class="px-4 py-3 text-left">Queue</th>
115
- <th class="px-4 py-3 text-left">Status</th>
116
- <th class="px-4 py-3 text-right">Duration</th>
117
- <th class="px-4 py-3 text-right">Time</th>
118
- <th class="px-4 py-3 text-right">Actions</th>
119
- </tr>
120
- </thead>
121
- <tbody class="divide-y divide-gray-100">
122
- <template x-for="job in jobs" :key="job.id">
123
- <tr class="hover:bg-gray-50">
124
- <td class="px-4 py-3 font-mono text-xs" x-text="job.name"></td>
125
- <td class="px-4 py-3" x-text="job.queue"></td>
126
- <td class="px-4 py-3">
127
- <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium"
128
- :class="{pending:'bg-amber-100 text-amber-700',processing:'bg-blue-100 text-blue-700',completed:'bg-green-100 text-green-700',failed:'bg-red-100 text-red-700'}[job.status]"
129
- x-text="job.status"></span>
130
- </td>
131
- <td class="px-4 py-3 text-right" x-text="job.duration ? job.duration + 'ms' : '—'"></td>
132
- <td class="px-4 py-3 text-right text-gray-400 text-xs" x-text="ago(job.dispatchedAt)"></td>
133
- <td class="px-4 py-3 text-right">
134
- <button x-show="job.status === 'failed'" @click="retry(job.id)"
135
- class="text-xs text-teal-600 hover:text-teal-800 mr-2">Retry</button>
136
- <button @click="remove(job.id)"
137
- class="text-xs text-red-500 hover:text-red-700">Delete</button>
138
- </td>
139
- </tr>
140
- </template>
141
- <tr x-show="jobs.length === 0">
142
- <td colspan="6" class="px-4 py-12 text-center text-gray-400">No jobs found.</td>
143
- </tr>
144
- </tbody>
145
- </table>
146
- <div class="flex items-center justify-between px-4 py-3 border-t border-gray-200 bg-gray-50 text-sm">
147
- <span class="text-gray-500">Total: <span x-text="meta.total"></span></span>
148
- <div class="flex gap-1">
149
- <button @click="page > 1 && (page--, load())" :disabled="page <= 1" class="px-3 py-1 border rounded text-xs disabled:opacity-30">Prev</button>
150
- <button @click="page++; load()" class="px-3 py-1 border rounded text-xs">Next</button>
151
- </div>
152
- </div>
153
- </div>`;
154
- }
155
- function jobScript(apiPrefix) {
156
- return `
157
- <script>
158
- function jobList(type) {
159
- return {
160
- jobs: [], meta: { total: 0 }, page: 1, search: '',
161
- async load() {
162
- const params = new URLSearchParams({ page: this.page, per_page: 50 })
163
- if (this.search) params.set('search', this.search)
164
- const data = await fetch('${apiPrefix}/jobs/' + type + '?' + params).then(r => r.json())
165
- this.jobs = data.data || []
166
- this.meta = data.meta || { total: 0 }
167
- },
168
- async retry(id) {
169
- await fetch('${apiPrefix}/jobs/' + id + '/retry', { method: 'POST' })
170
- this.load()
171
- },
172
- async remove(id) {
173
- await fetch('${apiPrefix}/jobs/' + id, { method: 'DELETE' })
174
- this.load()
175
- },
176
- ago(dateStr) {
177
- const s = Math.floor((Date.now() - new Date(dateStr).getTime()) / 1000)
178
- if (s < 60) return s + 's ago'
179
- if (s < 3600) return Math.floor(s / 60) + 'm ago'
180
- if (s < 86400) return Math.floor(s / 3600) + 'h ago'
181
- return Math.floor(s / 86400) + 'd ago'
182
- }
183
- }
184
- }
185
- </script>`;
186
- }
187
- // ─── Queues ────────────────────────────────────────────────
188
- export function queuesPage(basePath, apiPrefix) {
189
- return layout('Queues', `
190
- <div x-data="queues()" x-init="load()" x-cloak>
191
- <h2 class="text-xl font-bold mb-6">Queues</h2>
192
- <div class="bg-white rounded-xl border border-gray-200 shadow-sm overflow-hidden">
193
- <table class="w-full text-sm">
194
- <thead class="bg-gray-50 border-b border-gray-200 text-xs uppercase text-gray-500">
195
- <tr>
196
- <th class="px-5 py-3 text-left">Queue</th>
197
- <th class="px-5 py-3 text-right">Throughput/min</th>
198
- <th class="px-5 py-3 text-right">Avg Wait</th>
199
- <th class="px-5 py-3 text-right">Avg Runtime</th>
200
- <th class="px-5 py-3 text-right">Pending</th>
201
- <th class="px-5 py-3 text-right">Active</th>
202
- <th class="px-5 py-3 text-right">Completed</th>
203
- <th class="px-5 py-3 text-right">Failed</th>
204
- </tr>
205
- </thead>
206
- <tbody class="divide-y divide-gray-100">
207
- <template x-for="q in data" :key="q.queue">
208
- <tr class="hover:bg-gray-50">
209
- <td class="px-5 py-3 font-medium" x-text="q.queue"></td>
210
- <td class="px-5 py-3 text-right" x-text="q.throughput"></td>
211
- <td class="px-5 py-3 text-right" x-text="q.waitTime + 'ms'"></td>
212
- <td class="px-5 py-3 text-right" x-text="q.runtime + 'ms'"></td>
213
- <td class="px-5 py-3 text-right text-amber-600" x-text="q.pending"></td>
214
- <td class="px-5 py-3 text-right text-blue-600" x-text="q.active"></td>
215
- <td class="px-5 py-3 text-right text-green-600" x-text="q.completed"></td>
216
- <td class="px-5 py-3 text-right text-red-600" x-text="q.failed"></td>
217
- </tr>
218
- </template>
219
- <tr x-show="data.length === 0">
220
- <td colspan="8" class="px-5 py-12 text-center text-gray-400">No queue data yet.</td>
221
- </tr>
222
- </tbody>
223
- </table>
224
- </div>
225
- </div>
226
- <script>
227
- function queues() {
228
- return {
229
- data: [],
230
- async load() {
231
- const res = await fetch('${apiPrefix}/queues').then(r => r.json())
232
- this.data = res.data || []
233
- }
234
- }
235
- }
236
- </script>`, basePath, '/queues');
237
- }
238
- // ─── Workers ───────────────────────────────────────────────
239
- export function workersPage(basePath, apiPrefix) {
240
- return layout('Workers', `
241
- <div x-data="workers()" x-init="load()" x-cloak>
242
- <h2 class="text-xl font-bold mb-6">Workers</h2>
243
- <div class="bg-white rounded-xl border border-gray-200 shadow-sm overflow-hidden">
244
- <table class="w-full text-sm">
245
- <thead class="bg-gray-50 border-b border-gray-200 text-xs uppercase text-gray-500">
246
- <tr>
247
- <th class="px-5 py-3 text-left">ID</th>
248
- <th class="px-5 py-3 text-left">Queue</th>
249
- <th class="px-5 py-3 text-left">Status</th>
250
- <th class="px-5 py-3 text-right">Jobs Run</th>
251
- <th class="px-5 py-3 text-right">Memory</th>
252
- <th class="px-5 py-3 text-right">Last Job</th>
253
- </tr>
254
- </thead>
255
- <tbody class="divide-y divide-gray-100">
256
- <template x-for="w in data" :key="w.id">
257
- <tr class="hover:bg-gray-50">
258
- <td class="px-5 py-3 font-mono text-xs" x-text="w.id"></td>
259
- <td class="px-5 py-3" x-text="w.queue"></td>
260
- <td class="px-5 py-3">
261
- <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium"
262
- :class="{active:'bg-green-100 text-green-700',idle:'bg-gray-100 text-gray-600',paused:'bg-amber-100 text-amber-700'}[w.status]"
263
- x-text="w.status"></span>
264
- </td>
265
- <td class="px-5 py-3 text-right" x-text="w.jobsRun"></td>
266
- <td class="px-5 py-3 text-right" x-text="w.memoryMb + ' MB'"></td>
267
- <td class="px-5 py-3 text-right text-gray-400 text-xs" x-text="w.lastJobAt ? ago(w.lastJobAt) : '—'"></td>
268
- </tr>
269
- </template>
270
- <tr x-show="data.length === 0">
271
- <td colspan="6" class="px-5 py-12 text-center text-gray-400">No workers registered.</td>
272
- </tr>
273
- </tbody>
274
- </table>
275
- </div>
276
- </div>
277
- <script>
278
- function workers() {
279
- return {
280
- data: [],
281
- async load() {
282
- const res = await fetch('${apiPrefix}/workers').then(r => r.json())
283
- this.data = res.data || []
284
- },
285
- ago(dateStr) {
286
- const s = Math.floor((Date.now() - new Date(dateStr).getTime()) / 1000)
287
- if (s < 60) return s + 's ago'
288
- if (s < 3600) return Math.floor(s / 60) + 'm ago'
289
- return Math.floor(s / 3600) + 'h ago'
290
- }
291
- }
292
- }
293
- </script>`, basePath, '/workers');
294
- }
295
- //# sourceMappingURL=pages.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"pages.js","sourceRoot":"","sources":["../../src/ui/pages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEpC,8DAA8D;AAE9D,MAAM,UAAU,aAAa,CAAC,QAAgB,EAAE,SAAiB;IAC/D,OAAO,MAAM,CAAC,WAAW,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wDAwE2B,SAAS;;;cAGnD,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAA;AAC9B,CAAC;AAED,8DAA8D;AAE9D,MAAM,UAAU,cAAc,CAAC,QAAgB,EAAE,SAAiB;IAChE,OAAO,MAAM,CAAC,aAAa,EAAE;;;;;;;QAOvB,QAAQ,CAAC,SAAS,CAAC;;MAErB,SAAS,CAAC,SAAS,CAAC,EAAE,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAA;AACvD,CAAC;AAED,8DAA8D;AAE9D,MAAM,UAAU,cAAc,CAAC,QAAgB,EAAE,SAAiB;IAChE,OAAO,MAAM,CAAC,aAAa,EAAE;;;;;;;QAOvB,QAAQ,CAAC,SAAS,CAAC;;MAErB,SAAS,CAAC,SAAS,CAAC,EAAE,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAA;AACvD,CAAC;AAED,SAAS,QAAQ,CAAC,SAAiB;IACjC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aA6CI,CAAA;AACb,CAAC;AAED,SAAS,SAAS,CAAC,SAAiB;IAClC,OAAO;;;;;;;;wCAQ+B,SAAS;;;;;2BAKtB,SAAS;;;;2BAIT,SAAS;;;;;;;;;;;;cAYtB,CAAA;AACd,CAAC;AAED,8DAA8D;AAE9D,MAAM,UAAU,UAAU,CAAC,QAAgB,EAAE,SAAiB;IAC5D,OAAO,MAAM,CAAC,QAAQ,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uCA0Ca,SAAS;;;;;cAKlC,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAA;AACpC,CAAC;AAED,8DAA8D;AAE9D,MAAM,UAAU,WAAW,CAAC,QAAgB,EAAE,SAAiB;IAC7D,OAAO,MAAM,CAAC,SAAS,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uCA0CY,SAAS;;;;;;;;;;;cAWlC,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAA;AACrC,CAAC"}