@rudderjs/horizon 0.0.1

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.
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Shared HTML layout for Horizon UI pages.
3
+ * Sidebar navigation + content area. Alpine.js + Tailwind CDN.
4
+ */
5
+ export declare function layout(title: string, body: string, basePath: string, activePath: string): string;
6
+ //# sourceMappingURL=layout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../src/ui/layout.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAmDhG"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Shared HTML layout for Horizon UI pages.
3
+ * Sidebar navigation + content area. Alpine.js + Tailwind CDN.
4
+ */
5
+ export function layout(title, body, basePath, activePath) {
6
+ const nav = [
7
+ { label: 'Dashboard', path: '', icon: 'M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6' },
8
+ { label: 'Recent Jobs', path: '/jobs/recent', icon: 'M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2' },
9
+ { label: 'Failed Jobs', path: '/jobs/failed', icon: 'M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z' },
10
+ { label: 'Queues', path: '/queues', icon: 'M4 6h16M4 10h16M4 14h16M4 18h16' },
11
+ { label: 'Workers', path: '/workers', icon: 'M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.573c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.573 1.066c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.066-2.573c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z' },
12
+ ];
13
+ const navHtml = nav.map(n => {
14
+ const href = `${basePath}${n.path}`;
15
+ const active = activePath === n.path || (n.path === '' && activePath === '/');
16
+ return `<a href="${href}" class="flex items-center gap-3 px-3 py-2 text-sm rounded-lg transition ${active ? 'bg-teal-50 text-teal-700 font-medium' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'}">
17
+ <svg class="w-4 h-4 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="${n.icon}"/></svg>
18
+ ${n.label}
19
+ </a>`;
20
+ }).join('\n ');
21
+ return `<!DOCTYPE html>
22
+ <html lang="en">
23
+ <head>
24
+ <meta charset="UTF-8">
25
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
26
+ <title>${title} — Horizon</title>
27
+ <script src="https://cdn.tailwindcss.com"></script>
28
+ <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
29
+ <style>[x-cloak] { display: none !important; }</style>
30
+ </head>
31
+ <body class="bg-gray-50 text-gray-900 font-sans antialiased">
32
+ <div class="flex min-h-screen">
33
+ <aside class="w-56 bg-white border-r border-gray-200 flex flex-col">
34
+ <div class="px-4 py-5 flex items-center gap-2 border-b border-gray-100">
35
+ <div class="w-7 h-7 bg-teal-600 rounded-lg flex items-center justify-center">
36
+ <svg class="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
37
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16"/>
38
+ </svg>
39
+ </div>
40
+ <span class="font-semibold text-sm">Horizon</span>
41
+ </div>
42
+ <nav class="flex-1 px-3 py-4 space-y-0.5 overflow-y-auto">
43
+ ${navHtml}
44
+ </nav>
45
+ </aside>
46
+ <main class="flex-1 overflow-auto">
47
+ <div class="max-w-6xl mx-auto px-6 py-8">
48
+ ${body}
49
+ </div>
50
+ </main>
51
+ </div>
52
+ </body>
53
+ </html>`;
54
+ }
55
+ //# sourceMappingURL=layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout.js","sourceRoot":"","sources":["../../src/ui/layout.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,UAAU,MAAM,CAAC,KAAa,EAAE,IAAY,EAAE,QAAgB,EAAE,UAAkB;IACtF,MAAM,GAAG,GAAG;QACV,EAAE,KAAK,EAAE,WAAW,EAAK,IAAI,EAAE,EAAE,EAAe,IAAI,EAAE,kJAAkJ,EAAE;QAC1M,EAAE,KAAK,EAAE,aAAa,EAAG,IAAI,EAAE,cAAc,EAAG,IAAI,EAAE,iIAAiI,EAAE;QACzL,EAAE,KAAK,EAAE,aAAa,EAAG,IAAI,EAAE,cAAc,EAAG,IAAI,EAAE,sIAAsI,EAAE;QAC9L,EAAE,KAAK,EAAE,QAAQ,EAAQ,IAAI,EAAE,SAAS,EAAQ,IAAI,EAAE,iCAAiC,EAAE;QACzF,EAAE,KAAK,EAAE,SAAS,EAAO,IAAI,EAAE,UAAU,EAAO,IAAI,EAAE,qeAAqe,EAAE;KAC9hB,CAAA;IAED,MAAM,OAAO,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE;QAC1B,MAAM,IAAI,GAAK,GAAG,QAAQ,GAAG,CAAC,CAAC,IAAI,EAAE,CAAA;QACrC,MAAM,MAAM,GAAG,UAAU,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,IAAI,UAAU,KAAK,GAAG,CAAC,CAAA;QAC7E,OAAO,YAAY,IAAI,4EAA4E,MAAM,CAAC,CAAC,CAAC,sCAAsC,CAAC,CAAC,CAAC,oDAAoD;0KACnC,CAAC,CAAC,IAAI;QACxK,CAAC,CAAC,KAAK;SACN,CAAA;IACP,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;IAErB,OAAO;;;;;WAKE,KAAK;;;;;;;;;;;;;;;;;UAiBN,OAAO;;;;;UAKP,IAAI;;;;;QAKN,CAAA;AACR,CAAC"}
@@ -0,0 +1,6 @@
1
+ export declare function dashboardPage(basePath: string, apiPrefix: string): string;
2
+ export declare function recentJobsPage(basePath: string, apiPrefix: string): string;
3
+ export declare function failedJobsPage(basePath: string, apiPrefix: string): string;
4
+ export declare function queuesPage(basePath: string, apiPrefix: string): string;
5
+ export declare function workersPage(basePath: string, apiPrefix: string): string;
6
+ //# sourceMappingURL=pages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pages.d.ts","sourceRoot":"","sources":["../../src/ui/pages.ts"],"names":[],"mappings":"AAIA,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CA6EzE;AAID,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAW1E;AAID,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAW1E;AAsFD,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAiDtE;AAID,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAuDvE"}
@@ -0,0 +1,295 @@
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
@@ -0,0 +1 @@
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"}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@rudderjs/horizon",
3
+ "version": "0.0.1",
4
+ "license": "MIT",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/rudderjs/rudder",
8
+ "directory": "packages/horizon"
9
+ },
10
+ "type": "module",
11
+ "files": [
12
+ "dist",
13
+ "boost"
14
+ ],
15
+ "main": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "import": "./dist/index.js",
20
+ "types": "./dist/index.d.ts"
21
+ }
22
+ },
23
+ "dependencies": {
24
+ "@rudderjs/core": "0.0.8",
25
+ "@rudderjs/contracts": "0.0.3",
26
+ "@rudderjs/queue": "0.0.5"
27
+ },
28
+ "peerDependencies": {
29
+ "@rudderjs/router": "0.0.3",
30
+ "@rudderjs/middleware": "0.0.7"
31
+ },
32
+ "optionalDependencies": {
33
+ "better-sqlite3": "^11.0.0"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^20.0.0",
37
+ "@types/better-sqlite3": "^7.6.0",
38
+ "typescript": "^5.4.0"
39
+ },
40
+ "author": "Suleiman Shahbari",
41
+ "scripts": {
42
+ "build": "tsc -p tsconfig.build.json",
43
+ "dev": "tsc -p tsconfig.build.json --watch",
44
+ "typecheck": "tsc --noEmit",
45
+ "lint": "eslint src",
46
+ "clean": "rm -rf dist",
47
+ "test": "tsc -p tsconfig.test.json && node --test dist-test/index.test.js; EXIT=$?; rm -rf dist-test; exit $EXIT"
48
+ }
49
+ }