@zintrust/workers 0.1.28 → 0.1.30
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.
- package/README.md +16 -1
- package/dist/AnomalyDetection.d.ts +4 -0
- package/dist/AnomalyDetection.js +8 -0
- package/dist/BroadcastWorker.d.ts +2 -0
- package/dist/CanaryController.js +49 -5
- package/dist/ChaosEngineering.js +13 -0
- package/dist/ClusterLock.js +21 -10
- package/dist/DeadLetterQueue.js +12 -8
- package/dist/MultiQueueWorker.d.ts +1 -1
- package/dist/MultiQueueWorker.js +12 -7
- package/dist/NotificationWorker.d.ts +2 -0
- package/dist/PriorityQueue.d.ts +2 -2
- package/dist/PriorityQueue.js +20 -21
- package/dist/ResourceMonitor.js +65 -38
- package/dist/WorkerFactory.d.ts +23 -3
- package/dist/WorkerFactory.js +420 -40
- package/dist/WorkerInit.js +8 -3
- package/dist/WorkerMetrics.d.ts +2 -1
- package/dist/WorkerMetrics.js +152 -93
- package/dist/WorkerRegistry.d.ts +6 -0
- package/dist/WorkerRegistry.js +70 -1
- package/dist/WorkerShutdown.d.ts +21 -0
- package/dist/WorkerShutdown.js +82 -9
- package/dist/WorkerShutdownDurableObject.d.ts +12 -0
- package/dist/WorkerShutdownDurableObject.js +41 -0
- package/dist/build-manifest.json +171 -99
- package/dist/createQueueWorker.d.ts +2 -0
- package/dist/createQueueWorker.js +42 -27
- package/dist/dashboard/types.d.ts +5 -0
- package/dist/dashboard/workers-api.js +136 -43
- package/dist/http/WorkerApiController.js +1 -0
- package/dist/http/WorkerController.js +133 -85
- package/dist/http/WorkerMonitoringService.d.ts +11 -0
- package/dist/http/WorkerMonitoringService.js +62 -0
- package/dist/http/middleware/CustomValidation.js +1 -1
- package/dist/http/middleware/EditWorkerValidation.d.ts +1 -1
- package/dist/http/middleware/EditWorkerValidation.js +7 -6
- package/dist/http/middleware/ProcessorPathSanitizer.js +101 -35
- package/dist/http/middleware/WorkerValidationChain.js +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/routes/workers.js +48 -6
- package/dist/storage/WorkerStore.d.ts +4 -1
- package/dist/storage/WorkerStore.js +55 -7
- package/dist/telemetry/api/TelemetryAPI.d.ts +46 -0
- package/dist/telemetry/api/TelemetryAPI.js +219 -0
- package/dist/telemetry/api/TelemetryMonitoringService.d.ts +17 -0
- package/dist/telemetry/api/TelemetryMonitoringService.js +113 -0
- package/dist/telemetry/components/AlertPanel.d.ts +1 -0
- package/dist/telemetry/components/AlertPanel.js +13 -0
- package/dist/telemetry/components/CostTracking.d.ts +1 -0
- package/dist/telemetry/components/CostTracking.js +14 -0
- package/dist/telemetry/components/ResourceUsageChart.d.ts +1 -0
- package/dist/telemetry/components/ResourceUsageChart.js +11 -0
- package/dist/telemetry/components/WorkerHealthChart.d.ts +1 -0
- package/dist/telemetry/components/WorkerHealthChart.js +11 -0
- package/dist/telemetry/index.d.ts +15 -0
- package/dist/telemetry/index.js +60 -0
- package/dist/telemetry/routes/dashboard.d.ts +6 -0
- package/dist/telemetry/routes/dashboard.js +608 -0
- package/dist/ui/router/EmbeddedAssets.d.ts +4 -0
- package/dist/ui/router/EmbeddedAssets.js +13 -0
- package/dist/ui/router/ui.js +100 -4
- package/package.json +9 -5
- package/src/AnomalyDetection.ts +9 -0
- package/src/CanaryController.ts +41 -5
- package/src/ChaosEngineering.ts +14 -0
- package/src/ClusterLock.ts +22 -9
- package/src/DeadLetterQueue.ts +13 -8
- package/src/MultiQueueWorker.ts +15 -8
- package/src/PriorityQueue.ts +21 -22
- package/src/ResourceMonitor.ts +72 -40
- package/src/WorkerFactory.ts +545 -49
- package/src/WorkerInit.ts +8 -3
- package/src/WorkerMetrics.ts +183 -105
- package/src/WorkerRegistry.ts +80 -1
- package/src/WorkerShutdown.ts +115 -9
- package/src/WorkerShutdownDurableObject.ts +64 -0
- package/src/createQueueWorker.ts +73 -30
- package/src/dashboard/types.ts +5 -0
- package/src/dashboard/workers-api.ts +165 -52
- package/src/http/WorkerApiController.ts +1 -0
- package/src/http/WorkerController.ts +167 -90
- package/src/http/WorkerMonitoringService.ts +77 -0
- package/src/http/middleware/CustomValidation.ts +1 -1
- package/src/http/middleware/EditWorkerValidation.ts +7 -6
- package/src/http/middleware/ProcessorPathSanitizer.ts +123 -36
- package/src/http/middleware/WorkerValidationChain.ts +1 -0
- package/src/index.ts +6 -1
- package/src/routes/workers.ts +66 -9
- package/src/storage/WorkerStore.ts +59 -9
- package/src/telemetry/api/TelemetryAPI.ts +292 -0
- package/src/telemetry/api/TelemetryMonitoringService.ts +149 -0
- package/src/telemetry/components/AlertPanel.ts +13 -0
- package/src/telemetry/components/CostTracking.ts +14 -0
- package/src/telemetry/components/ResourceUsageChart.ts +11 -0
- package/src/telemetry/components/WorkerHealthChart.ts +11 -0
- package/src/telemetry/index.ts +121 -0
- package/src/telemetry/public/assets/zintrust-logo.svg +15 -0
- package/src/telemetry/routes/dashboard.ts +638 -0
- package/src/telemetry/styles/tailwind.css +1 -0
- package/src/telemetry/styles/zintrust-theme.css +8 -0
- package/src/ui/router/EmbeddedAssets.ts +13 -0
- package/src/ui/router/ui.ts +112 -5
- package/src/ui/workers/index.html +2 -2
- package/src/ui/workers/main.js +232 -61
- package/src/ui/workers/zintrust.svg +30 -0
- package/dist/dashboard/workers-dashboard-ui.d.ts +0 -3
- package/dist/dashboard/workers-dashboard-ui.js +0 -1026
- package/dist/dashboard/workers-dashboard.d.ts +0 -4
- package/dist/dashboard/workers-dashboard.js +0 -904
|
@@ -1,904 +0,0 @@
|
|
|
1
|
-
import { getWorkersDashboardStyles } from './workers-dashboard-ui';
|
|
2
|
-
const getHeaderTopSection = () => `
|
|
3
|
-
<div class="header-top">
|
|
4
|
-
<div style="display: flex; align-items: center; gap: 16px">
|
|
5
|
-
<div class="logo-frame">
|
|
6
|
-
<img
|
|
7
|
-
src="/zintrust.svg"
|
|
8
|
-
alt="ZinTrust"
|
|
9
|
-
class="logo logo-img"
|
|
10
|
-
/>
|
|
11
|
-
</div>
|
|
12
|
-
<h1>Workers Dashboard</h1>
|
|
13
|
-
</div>
|
|
14
|
-
<div class="header-actions">
|
|
15
|
-
<button id="theme-toggle" class="theme-toggle">
|
|
16
|
-
<svg class="icon" viewBox="0 0 24 24">
|
|
17
|
-
<circle cx="12" cy="12" r="5" />
|
|
18
|
-
<path
|
|
19
|
-
d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"
|
|
20
|
-
/>
|
|
21
|
-
</svg>
|
|
22
|
-
Theme
|
|
23
|
-
</button>
|
|
24
|
-
<button id="auto-refresh-toggle" class="btn" onclick="toggleAutoRefresh()">
|
|
25
|
-
<svg id="auto-refresh-icon" class="icon" viewBox="0 0 24 24">
|
|
26
|
-
<polygon points="5 3 19 12 5 21 5 3" />
|
|
27
|
-
</svg>
|
|
28
|
-
<span id="auto-refresh-label">Auto Refresh</span>
|
|
29
|
-
</button>
|
|
30
|
-
<button class="btn" onclick="fetchData()">
|
|
31
|
-
<svg class="icon" viewBox="0 0 24 24">
|
|
32
|
-
<path d="M23 4v6h-6" />
|
|
33
|
-
<path d="M1 20v-6h6" />
|
|
34
|
-
<path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15" />
|
|
35
|
-
</svg>
|
|
36
|
-
Refresh
|
|
37
|
-
</button>
|
|
38
|
-
<button class="btn btn-primary" onclick="showAddWorkerModal()">
|
|
39
|
-
<svg class="icon" viewBox="0 0 24 24">
|
|
40
|
-
<line x1="12" y1="5" x2="12" y2="19" />
|
|
41
|
-
<line x1="5" y1="12" x2="19" y2="12" />
|
|
42
|
-
</svg>
|
|
43
|
-
Add Worker
|
|
44
|
-
</button>
|
|
45
|
-
</div>
|
|
46
|
-
</div>
|
|
47
|
-
`;
|
|
48
|
-
const getNavigationBar = () => `
|
|
49
|
-
<div class="nav-bar">
|
|
50
|
-
<nav class="nav-links">
|
|
51
|
-
<a href="/queue-monitor" class="nav-link">Queue Monitor</a>
|
|
52
|
-
<a href="/telemetry" class="nav-link">Telemetry</a>
|
|
53
|
-
<a href="/metrics" class="nav-link">Metrics</a>
|
|
54
|
-
</nav>
|
|
55
|
-
</div>
|
|
56
|
-
`;
|
|
57
|
-
const getFilterBar = () => `
|
|
58
|
-
<div class="filters-bar">
|
|
59
|
-
<div class="filter-group">
|
|
60
|
-
<label>Status:</label>
|
|
61
|
-
<select id="status-filter">
|
|
62
|
-
<option value="">All Status</option>
|
|
63
|
-
<option value="running">Running</option>
|
|
64
|
-
<option value="stopped">Stopped</option>
|
|
65
|
-
<option value="error">Error</option>
|
|
66
|
-
<option value="paused">Paused</option>
|
|
67
|
-
</select>
|
|
68
|
-
</div>
|
|
69
|
-
<div class="filter-group">
|
|
70
|
-
<label>Driver:</label>
|
|
71
|
-
<select id="driver-filter">
|
|
72
|
-
<option value="">All Drivers</option>
|
|
73
|
-
</select>
|
|
74
|
-
</div>
|
|
75
|
-
<div class="filter-group">
|
|
76
|
-
<label>Sort:</label>
|
|
77
|
-
<select id="sort-select">
|
|
78
|
-
<option value="name">Sort by Name</option>
|
|
79
|
-
<option value="status">Sort by Status</option>
|
|
80
|
-
<option value="driver">Sort by Driver</option>
|
|
81
|
-
<option value="health">Sort by Health</option>
|
|
82
|
-
<option value="version">Sort by Version</option>
|
|
83
|
-
<option value="processed">Sort by Performance</option>
|
|
84
|
-
</select>
|
|
85
|
-
</div>
|
|
86
|
-
<div style="flex-grow: 1"></div>
|
|
87
|
-
<div class="search-box">
|
|
88
|
-
<svg class="search-icon" viewBox="0 0 24 24">
|
|
89
|
-
<circle cx="11" cy="11" r="8"></circle>
|
|
90
|
-
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
|
91
|
-
</svg>
|
|
92
|
-
<input type="text" id="search-input" placeholder="Search workers...">
|
|
93
|
-
</div>
|
|
94
|
-
</div>
|
|
95
|
-
`;
|
|
96
|
-
const getDashboardHeader = () => `
|
|
97
|
-
<div class="header">
|
|
98
|
-
${getHeaderTopSection()}
|
|
99
|
-
${getNavigationBar()}
|
|
100
|
-
${getFilterBar()}
|
|
101
|
-
</div>
|
|
102
|
-
`;
|
|
103
|
-
const getDashboardLoadingStates = () => `
|
|
104
|
-
<div id="loading" style="text-align: center; padding: 40px; color: var(--muted);">
|
|
105
|
-
<div>Loading workers...</div>
|
|
106
|
-
</div>
|
|
107
|
-
|
|
108
|
-
<div id="error" style="display: none; text-align: center; padding: 40px; color: var(--danger);">
|
|
109
|
-
<div>Failed to load workers data</div>
|
|
110
|
-
<button class="btn" onclick="fetchData()" style="margin-top: 16px;">Retry</button>
|
|
111
|
-
</div>
|
|
112
|
-
|
|
113
|
-
<div id="workers-content" style="display: none;">
|
|
114
|
-
<div class="summary-bar" id="queue-summary">
|
|
115
|
-
<div class="summary-item">
|
|
116
|
-
<span class="summary-label">Queue Driver</span>
|
|
117
|
-
<span class="summary-value" id="queue-driver">-</span>
|
|
118
|
-
</div>
|
|
119
|
-
<div class="summary-item">
|
|
120
|
-
<span class="summary-label">Queues</span>
|
|
121
|
-
<span class="summary-value" id="queue-total">0</span>
|
|
122
|
-
</div>
|
|
123
|
-
<div class="summary-item">
|
|
124
|
-
<span class="summary-label">Jobs</span>
|
|
125
|
-
<span class="summary-value" id="queue-jobs">0</span>
|
|
126
|
-
</div>
|
|
127
|
-
<div class="summary-item">
|
|
128
|
-
<span class="summary-label">Processing</span>
|
|
129
|
-
<span class="summary-value" id="queue-processing">0</span>
|
|
130
|
-
</div>
|
|
131
|
-
<div class="summary-item">
|
|
132
|
-
<span class="summary-label">Failed</span>
|
|
133
|
-
<span class="summary-value" id="queue-failed">0</span>
|
|
134
|
-
</div>
|
|
135
|
-
<div class="summary-item">
|
|
136
|
-
<span class="summary-label">Drivers</span>
|
|
137
|
-
<div class="drivers-list" id="drivers-list"></div>
|
|
138
|
-
</div>
|
|
139
|
-
</div>
|
|
140
|
-
<div class="table-container">
|
|
141
|
-
<div class="table-wrapper">
|
|
142
|
-
<table>
|
|
143
|
-
<thead>
|
|
144
|
-
<tr>
|
|
145
|
-
<th style="width: 250px">Worker</th>
|
|
146
|
-
<th style="width: 120px">Status</th>
|
|
147
|
-
<th style="width: 120px">Health</th>
|
|
148
|
-
<th style="width: 100px">Driver</th>
|
|
149
|
-
<th style="width: 100px">Version</th>
|
|
150
|
-
<th style="width: 320px">Performance</th>
|
|
151
|
-
<th style="width: 180px">Actions</th>
|
|
152
|
-
</tr>
|
|
153
|
-
</thead>
|
|
154
|
-
<tbody id="workers-tbody">
|
|
155
|
-
<!-- Workers will be populated here -->
|
|
156
|
-
</tbody>
|
|
157
|
-
</table>
|
|
158
|
-
</div>
|
|
159
|
-
|
|
160
|
-
<div class="pagination">
|
|
161
|
-
<div class="pagination-info" id="pagination-info">
|
|
162
|
-
Showing 0-0 of 0 workers
|
|
163
|
-
</div>
|
|
164
|
-
<div class="pagination-controls">
|
|
165
|
-
<button class="page-btn" id="prev-btn" onclick="loadPage('prev')" disabled>
|
|
166
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg>
|
|
167
|
-
</button>
|
|
168
|
-
<div id="page-numbers" style="display:flex; gap:8px;"></div>
|
|
169
|
-
<button class="page-btn" id="next-btn" onclick="loadPage('next')" disabled>
|
|
170
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg>
|
|
171
|
-
</button>
|
|
172
|
-
|
|
173
|
-
<div class="page-size-selector">
|
|
174
|
-
<label>Show:</label>
|
|
175
|
-
<select id="limit-select" onchange="changeLimit(this.value)">
|
|
176
|
-
<option value="10">10</option>
|
|
177
|
-
<option value="25">25</option>
|
|
178
|
-
<option value="50">50</option>
|
|
179
|
-
<option value="100">100</option>
|
|
180
|
-
</select>
|
|
181
|
-
</div>
|
|
182
|
-
</div>
|
|
183
|
-
</div>
|
|
184
|
-
</div>
|
|
185
|
-
</div>
|
|
186
|
-
`;
|
|
187
|
-
const getThemeManagementScripts = () => `
|
|
188
|
-
// Theme management
|
|
189
|
-
function getPreferredTheme() {
|
|
190
|
-
const stored = localStorage.getItem(THEME_KEY);
|
|
191
|
-
if (stored === 'light' || stored === 'dark') {
|
|
192
|
-
return stored;
|
|
193
|
-
}
|
|
194
|
-
const prefersLight = window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches;
|
|
195
|
-
return prefersLight ? 'light' : 'dark';
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function applyTheme(nextTheme) {
|
|
199
|
-
currentTheme = nextTheme;
|
|
200
|
-
document.documentElement.setAttribute('data-theme', nextTheme);
|
|
201
|
-
localStorage.setItem(THEME_KEY, nextTheme);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function toggleTheme() {
|
|
205
|
-
applyTheme(currentTheme === 'dark' ? 'light' : 'dark');
|
|
206
|
-
}
|
|
207
|
-
`;
|
|
208
|
-
const getDataFetchingScripts = (options) => `
|
|
209
|
-
// Data fetching
|
|
210
|
-
async function fetchData() {
|
|
211
|
-
const loading = document.getElementById('loading');
|
|
212
|
-
const error = document.getElementById('error');
|
|
213
|
-
const content = document.getElementById('workers-content');
|
|
214
|
-
const searchBtn = document.getElementById('search-btn');
|
|
215
|
-
|
|
216
|
-
// Only show full loading state if we have no content yet
|
|
217
|
-
if (content.style.display === 'none') {
|
|
218
|
-
loading.style.display = 'block';
|
|
219
|
-
} else {
|
|
220
|
-
content.style.opacity = '0.5';
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
error.style.display = 'none';
|
|
224
|
-
if (searchBtn) searchBtn.disabled = true;
|
|
225
|
-
|
|
226
|
-
try {
|
|
227
|
-
const limit = localStorage.getItem(PAGE_SIZE_KEY) || '${options.pageSize}';
|
|
228
|
-
// Update limit select if needed
|
|
229
|
-
const limitSelect = document.getElementById('limit-select');
|
|
230
|
-
if (limitSelect && limitSelect.value !== limit) limitSelect.value = limit;
|
|
231
|
-
|
|
232
|
-
const statusFilter = document.getElementById('status-filter');
|
|
233
|
-
const driverFilter = document.getElementById('driver-filter');
|
|
234
|
-
const sortSelect = document.getElementById('sort-select');
|
|
235
|
-
const searchInput = document.getElementById('search-input');
|
|
236
|
-
|
|
237
|
-
const params = new URLSearchParams({
|
|
238
|
-
page: currentPage.toString(),
|
|
239
|
-
limit: limit,
|
|
240
|
-
status: statusFilter ? statusFilter.value : '',
|
|
241
|
-
driver: driverFilter ? driverFilter.value : '',
|
|
242
|
-
sortBy: sortSelect ? sortSelect.value : 'name',
|
|
243
|
-
sortOrder: 'asc',
|
|
244
|
-
search: searchInput ? searchInput.value : ''
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
const response = await fetch(API_BASE + '/api/workers?' + params.toString());
|
|
248
|
-
if (!response.ok) {
|
|
249
|
-
console.error('Failed to fetch workers:', response.statusText);
|
|
250
|
-
loading.style.display = 'none';
|
|
251
|
-
error.style.display = 'block';
|
|
252
|
-
content.style.opacity = '1';
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const data = await response.json();
|
|
257
|
-
renderWorkers(data);
|
|
258
|
-
|
|
259
|
-
loading.style.display = 'none';
|
|
260
|
-
content.style.display = 'block';
|
|
261
|
-
content.style.opacity = '1';
|
|
262
|
-
} catch (err) {
|
|
263
|
-
console.error('Error fetching workers:', err);
|
|
264
|
-
loading.style.display = 'none';
|
|
265
|
-
error.style.display = 'block';
|
|
266
|
-
content.style.opacity = '1';
|
|
267
|
-
} finally {
|
|
268
|
-
if (searchBtn) searchBtn.disabled = false;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
function changeLimit(newLimit) {
|
|
273
|
-
localStorage.setItem(PAGE_SIZE_KEY, newLimit);
|
|
274
|
-
currentPage = 1;
|
|
275
|
-
fetchData();
|
|
276
|
-
}
|
|
277
|
-
`;
|
|
278
|
-
const getWorkerRowTemplate = () => `
|
|
279
|
-
<td>
|
|
280
|
-
<div class="worker-name">\${worker.name}</div>
|
|
281
|
-
<div class="worker-queue">\${worker.queueName}</div>
|
|
282
|
-
</td>
|
|
283
|
-
<td>
|
|
284
|
-
<span class="status-badge status-\${worker.status}">
|
|
285
|
-
<span class="status-dot"></span>
|
|
286
|
-
\${worker.status.charAt(0).toUpperCase() + worker.status.slice(1)}
|
|
287
|
-
</span>
|
|
288
|
-
</td>
|
|
289
|
-
<td>
|
|
290
|
-
<div class="health-indicator">
|
|
291
|
-
<span class="health-dot health-\${worker.health.status}"></span>
|
|
292
|
-
\${worker.health.status.charAt(0).toUpperCase() + worker.health.status.slice(1)}
|
|
293
|
-
</div>
|
|
294
|
-
</td>
|
|
295
|
-
<td><span class="driver-badge">\${worker.driver}</span></td>
|
|
296
|
-
<td><span class="version-badge">v\${worker.version}</span></td>
|
|
297
|
-
<td>
|
|
298
|
-
<div class="performance-icons">
|
|
299
|
-
<div class="perf-icon processed" title="Processed Jobs">
|
|
300
|
-
<svg class="icon" viewBox="0 0 24 24">
|
|
301
|
-
<line x1="12" y1="20" x2="12" y2="10" />
|
|
302
|
-
<line x1="18" y1="20" x2="18" y2="4" />
|
|
303
|
-
<line x1="6" y1="20" x2="6" y2="16" />
|
|
304
|
-
</svg>
|
|
305
|
-
<span>\${worker.processed.toLocaleString()}</span>
|
|
306
|
-
</div>
|
|
307
|
-
<div class="perf-icon time" title="Avg Time">
|
|
308
|
-
<svg class="icon" viewBox="0 0 24 24">
|
|
309
|
-
<circle cx="12" cy="12" r="10" />
|
|
310
|
-
<polyline points="12 6 12 12 16 14" />
|
|
311
|
-
</svg>
|
|
312
|
-
<span>\${worker.avgTime}ms</span>
|
|
313
|
-
</div>
|
|
314
|
-
<div class="perf-icon memory" title="Memory Usage">
|
|
315
|
-
<svg class="icon" viewBox="0 0 24 24">
|
|
316
|
-
<rect x="4" y="4" width="16" height="16" rx="2" ry="2" />
|
|
317
|
-
<rect x="9" y="9" width="6" height="6" />
|
|
318
|
-
<line x1="9" y1="1" x2="9" y2="4" />
|
|
319
|
-
<line x1="15" y1="1" x2="15" y2="4" />
|
|
320
|
-
<line x1="9" y1="20" x2="9" y2="23" />
|
|
321
|
-
<line x1="15" y1="20" x2="15" y2="23" />
|
|
322
|
-
<line x1="20" y1="9" x2="23" y2="9" />
|
|
323
|
-
<line x1="20" y1="14" x2="23" y2="14" />
|
|
324
|
-
<line x1="1" y1="9" x2="4" y2="9" />
|
|
325
|
-
<line x1="1" y1="14" x2="4" y2="14" />
|
|
326
|
-
</svg>
|
|
327
|
-
<span>\${worker.memory}MB</span>
|
|
328
|
-
</div>
|
|
329
|
-
</div>
|
|
330
|
-
</td>
|
|
331
|
-
<td>
|
|
332
|
-
<div class="actions-cell">
|
|
333
|
-
<button class="action-btn start" title="Start" onclick="event.stopPropagation(); startWorker('\${worker.name}', '\${worker.driver}')">
|
|
334
|
-
<svg viewBox="0 0 24 24"><polygon points="5 3 19 12 5 21 5 3" /></svg>
|
|
335
|
-
</button>
|
|
336
|
-
<button class="action-btn stop" title="Stop" onclick="event.stopPropagation(); stopWorker('\${worker.name}', '\${worker.driver}')">
|
|
337
|
-
<svg viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2" ry="2" /></svg>
|
|
338
|
-
</button>
|
|
339
|
-
<button class="action-btn restart" title="Restart" onclick="event.stopPropagation(); restartWorker('\${worker.name}', '\${worker.driver}')">
|
|
340
|
-
<svg viewBox="0 0 24 24">
|
|
341
|
-
<polyline points="23 4 23 10 17 10" />
|
|
342
|
-
<polyline points="1 20 1 14 7 14" />
|
|
343
|
-
<path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15" />
|
|
344
|
-
</svg>
|
|
345
|
-
</button>
|
|
346
|
-
<button class="action-btn delete" title="Delete" onclick="event.stopPropagation(); deleteWorker('\${worker.name}', '\${worker.driver}')">
|
|
347
|
-
<svg viewBox="0 0 24 24">
|
|
348
|
-
<polyline points="3 6 5 6 21 6" />
|
|
349
|
-
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
|
|
350
|
-
<line x1="10" y1="11" x2="10" y2="17" />
|
|
351
|
-
<line x1="14" y1="11" x2="14" y2="17" />
|
|
352
|
-
</svg>
|
|
353
|
-
</button>
|
|
354
|
-
</div>
|
|
355
|
-
</td>
|
|
356
|
-
`;
|
|
357
|
-
const getConfigSection = () => `
|
|
358
|
-
<div class="detail-section">
|
|
359
|
-
<h4>Configuration</h4>
|
|
360
|
-
<div class="detail-item">
|
|
361
|
-
<span>Queue Name</span>
|
|
362
|
-
<span data-key="configuration.queueName">\${worker.queueName}</span>
|
|
363
|
-
</div>
|
|
364
|
-
<div class="detail-item">
|
|
365
|
-
<span>Concurrency</span>
|
|
366
|
-
<span data-key="configuration.concurrency">-</span>
|
|
367
|
-
</div>
|
|
368
|
-
<div class="detail-item">
|
|
369
|
-
<span>Auto Start</span>
|
|
370
|
-
<label class="auto-start-toggle" onclick="event.stopPropagation()">
|
|
371
|
-
<input type="checkbox" \${worker.autoStart ? 'checked' : ''} onchange="toggleAutoStart('\${worker.name}', '\${worker.driver}', this.checked)">
|
|
372
|
-
<span class="toggle-slider"></span>
|
|
373
|
-
</label>
|
|
374
|
-
</div>
|
|
375
|
-
<div class="detail-item">
|
|
376
|
-
<span>Processor Path</span>
|
|
377
|
-
<span data-key="configuration.processorPath">-</span>
|
|
378
|
-
</div>
|
|
379
|
-
</div>
|
|
380
|
-
`;
|
|
381
|
-
const getMetricsSection = () => `
|
|
382
|
-
<div class="detail-section">
|
|
383
|
-
<h4>Performance Metrics</h4>
|
|
384
|
-
<div class="detail-item">
|
|
385
|
-
<span>Processed Jobs</span>
|
|
386
|
-
<span data-key="metrics.processed">\${worker.processed != null ? worker.processed.toLocaleString() : '-'}</span>
|
|
387
|
-
</div>
|
|
388
|
-
<div class="detail-item">
|
|
389
|
-
<span>Failed Jobs</span>
|
|
390
|
-
<span data-key="metrics.failed">-</span>
|
|
391
|
-
</div>
|
|
392
|
-
<div class="detail-item">
|
|
393
|
-
<span>Success Rate</span>
|
|
394
|
-
<span data-key="metrics.successRate">-</span>
|
|
395
|
-
</div>
|
|
396
|
-
<div class="detail-item">
|
|
397
|
-
<span>Avg Processing Time</span>
|
|
398
|
-
<span data-key="metrics.avgTime">\${worker.avgTime != null ? worker.avgTime + 'ms' : '-'}</span>
|
|
399
|
-
</div>
|
|
400
|
-
</div>
|
|
401
|
-
`;
|
|
402
|
-
const getHealthSection = () => `
|
|
403
|
-
<div class="detail-section">
|
|
404
|
-
<h4>Health & Status</h4>
|
|
405
|
-
<div class="detail-item">
|
|
406
|
-
<span>Last Health Check</span>
|
|
407
|
-
<span data-key="health.lastCheck">-</span>
|
|
408
|
-
</div>
|
|
409
|
-
<div class="detail-item">
|
|
410
|
-
<span>Response Time</span>
|
|
411
|
-
<span data-key="health.responseTime">-</span>
|
|
412
|
-
</div>
|
|
413
|
-
<div class="detail-item">
|
|
414
|
-
<span>Memory Usage</span>
|
|
415
|
-
<span data-key="metrics.memory">\${worker.memory != null ? worker.memory + 'MB' : '-'}</span>
|
|
416
|
-
</div>
|
|
417
|
-
<div class="detail-item">
|
|
418
|
-
<span>Uptime</span>
|
|
419
|
-
<span data-key="metrics.uptime">-</span>
|
|
420
|
-
</div>
|
|
421
|
-
</div>
|
|
422
|
-
`;
|
|
423
|
-
const getLogsSection = () => `
|
|
424
|
-
<div class="detail-section">
|
|
425
|
-
<h4>Recent Logs</h4>
|
|
426
|
-
<div class="recent-logs-container" style="
|
|
427
|
-
font-family: monospace;
|
|
428
|
-
font-size: 11px;
|
|
429
|
-
line-height: 1.6;
|
|
430
|
-
color: var(--text);
|
|
431
|
-
background: var(--input-bg);
|
|
432
|
-
padding: 12px;
|
|
433
|
-
border-radius: 8px;
|
|
434
|
-
border: 1px solid var(--border);
|
|
435
|
-
max-height: 200px;
|
|
436
|
-
overflow-y: auto;
|
|
437
|
-
">
|
|
438
|
-
<div class="logs-content">Loading logs...</div>
|
|
439
|
-
</div>
|
|
440
|
-
</div>
|
|
441
|
-
`;
|
|
442
|
-
const getDetailRowTemplate = () => `
|
|
443
|
-
<td colspan="7" class="details-cell">
|
|
444
|
-
<div class="details-content">
|
|
445
|
-
<div class="details-grid">
|
|
446
|
-
${getConfigSection()}
|
|
447
|
-
${getMetricsSection()}
|
|
448
|
-
${getHealthSection()}
|
|
449
|
-
${getLogsSection()}
|
|
450
|
-
</div>
|
|
451
|
-
</div>
|
|
452
|
-
</td>
|
|
453
|
-
`;
|
|
454
|
-
const getDetailFormattingScripts = () => String.raw `
|
|
455
|
-
function updateDetailViews(detailRow, details) {
|
|
456
|
-
if (!details) return;
|
|
457
|
-
|
|
458
|
-
// Helper to safe access nested properties
|
|
459
|
-
const get = (obj, path) => path.split('.').reduce((o, i) => o ? o[i] : null, obj);
|
|
460
|
-
|
|
461
|
-
// Update simple data attributes
|
|
462
|
-
detailRow.querySelectorAll('[data-key]').forEach(el => {
|
|
463
|
-
const key = el.getAttribute('data-key');
|
|
464
|
-
let value = get(details, key);
|
|
465
|
-
|
|
466
|
-
// Format specific fields
|
|
467
|
-
if (key === 'metrics.processed' && value != null) value = Number(value).toLocaleString();
|
|
468
|
-
if (key === 'metrics.avgTime' && value != null) value = value + 'ms';
|
|
469
|
-
if (key === 'metrics.memory' && value != null) value = value + 'MB';
|
|
470
|
-
|
|
471
|
-
if (value !== null && value !== undefined) {
|
|
472
|
-
el.textContent = value;
|
|
473
|
-
}
|
|
474
|
-
});
|
|
475
|
-
|
|
476
|
-
// Handle logs if present
|
|
477
|
-
const logsContainer = detailRow.querySelector('.logs-content');
|
|
478
|
-
if (logsContainer && details.recentLogs && Array.isArray(details.recentLogs)) {
|
|
479
|
-
if (details.recentLogs.length === 0) {
|
|
480
|
-
logsContainer.innerHTML = '<div style="color: var(--muted)">No recent logs</div>';
|
|
481
|
-
} else {
|
|
482
|
-
logsContainer.innerHTML = details.recentLogs.map(log => {
|
|
483
|
-
let color = 'var(--text)';
|
|
484
|
-
if (log.toLowerCase().includes('failed') || log.toLowerCase().includes('error')) color = 'var(--danger)';
|
|
485
|
-
else if (log.toLowerCase().includes('success')) color = 'var(--success)';
|
|
486
|
-
else if (log.toLowerCase().includes('processing')) color = 'var(--info)';
|
|
487
|
-
|
|
488
|
-
return '<div style="color: ' + color + '">' + log + '</div>';
|
|
489
|
-
}).join('');
|
|
490
|
-
}
|
|
491
|
-
} else if (logsContainer) {
|
|
492
|
-
logsContainer.innerHTML = '<div style="color: var(--muted)">No logs available</div>';
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
`;
|
|
496
|
-
const getDetailRenderingScripts = () => String.raw `
|
|
497
|
-
async function ensureWorkerDetails(workerName, detailRow, driver) {
|
|
498
|
-
if (!workerName || !detailRow) return;
|
|
499
|
-
if (!detailsCache.has(workerName)) {
|
|
500
|
-
try {
|
|
501
|
-
// Validate driver before making request
|
|
502
|
-
if (driver && !['db', 'redis', 'memory'].includes(driver)) {
|
|
503
|
-
console.error('Invalid driver specified');
|
|
504
|
-
return;
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
const response = await fetch(API_BASE + '/api/workers/' + workerName + '/details?driver=' + driver);
|
|
508
|
-
if (!response.ok) {
|
|
509
|
-
console.error('Failed to load worker details:', response.statusText);
|
|
510
|
-
return;
|
|
511
|
-
}
|
|
512
|
-
const data = await response.json();
|
|
513
|
-
if (detailsCache.size >= MAX_CACHE_SIZE) {
|
|
514
|
-
const firstKey = detailsCache.keys().next().value;
|
|
515
|
-
detailsCache.delete(firstKey);
|
|
516
|
-
}
|
|
517
|
-
detailsCache.set(workerName, data);
|
|
518
|
-
} catch (err) {
|
|
519
|
-
console.error('Failed to load worker details:', err);
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
const cached = detailsCache.get(workerName);
|
|
524
|
-
const detailsData = cached?.details ?? cached;
|
|
525
|
-
updateDetailViews(detailRow, detailsData);
|
|
526
|
-
}
|
|
527
|
-
`;
|
|
528
|
-
const getSummaryRenderingScripts = () => `
|
|
529
|
-
function updateQueueSummary(queueData) {
|
|
530
|
-
if (!queueData) return;
|
|
531
|
-
const driverEl = document.getElementById('queue-driver');
|
|
532
|
-
const totalEl = document.getElementById('queue-total');
|
|
533
|
-
const jobsEl = document.getElementById('queue-jobs');
|
|
534
|
-
const processingEl = document.getElementById('queue-processing');
|
|
535
|
-
const failedEl = document.getElementById('queue-failed');
|
|
536
|
-
|
|
537
|
-
if (driverEl) driverEl.textContent = queueData.driver || '-';
|
|
538
|
-
if (totalEl) totalEl.textContent = String(queueData.totalQueues ?? 0);
|
|
539
|
-
if (jobsEl) jobsEl.textContent = String(queueData.totalJobs ?? 0);
|
|
540
|
-
if (processingEl) processingEl.textContent = String(queueData.processingJobs ?? 0);
|
|
541
|
-
if (failedEl) failedEl.textContent = String(queueData.failedJobs ?? 0);
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
function updateDriverFilter(drivers) {
|
|
545
|
-
const select = document.getElementById('driver-filter');
|
|
546
|
-
if (!select || !Array.isArray(drivers)) return;
|
|
547
|
-
const currentValue = select.value;
|
|
548
|
-
select.innerHTML = '<option value="">All Drivers</option>';
|
|
549
|
-
drivers.forEach((driver) => {
|
|
550
|
-
const option = document.createElement('option');
|
|
551
|
-
option.value = driver;
|
|
552
|
-
option.textContent = driver.charAt(0).toUpperCase() + driver.slice(1);
|
|
553
|
-
select.appendChild(option);
|
|
554
|
-
});
|
|
555
|
-
if (drivers.includes(currentValue)) {
|
|
556
|
-
select.value = currentValue;
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
function updateDriversList(drivers) {
|
|
561
|
-
const list = document.getElementById('drivers-list');
|
|
562
|
-
if (!list) return;
|
|
563
|
-
list.innerHTML = '';
|
|
564
|
-
if (!Array.isArray(drivers) || drivers.length === 0) {
|
|
565
|
-
return;
|
|
566
|
-
}
|
|
567
|
-
drivers.forEach((driver) => {
|
|
568
|
-
const chip = document.createElement('span');
|
|
569
|
-
chip.className = 'driver-chip';
|
|
570
|
-
chip.textContent = driver;
|
|
571
|
-
list.appendChild(chip);
|
|
572
|
-
});
|
|
573
|
-
}
|
|
574
|
-
`;
|
|
575
|
-
const getTableRenderingScripts = () => `
|
|
576
|
-
function createWorkerRow(worker) {
|
|
577
|
-
const detailsId = \`details-\${worker.name.replace(/[^a-z0-9]/gi, '-')}\`;
|
|
578
|
-
|
|
579
|
-
const row = document.createElement('tr');
|
|
580
|
-
row.className = 'expander';
|
|
581
|
-
row.setAttribute('onclick', \`toggleDetails('\${detailsId}')\`);
|
|
582
|
-
row.setAttribute('data-worker-name', worker.name);
|
|
583
|
-
row.setAttribute('data-worker-driver', worker.driver);
|
|
584
|
-
|
|
585
|
-
row.innerHTML = \`${getWorkerRowTemplate()}\`;
|
|
586
|
-
return { row, detailsId };
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
function createDetailRow(worker, detailsId) {
|
|
590
|
-
const detailRow = document.createElement('tr');
|
|
591
|
-
detailRow.className = 'expandable-row';
|
|
592
|
-
detailRow.id = detailsId;
|
|
593
|
-
detailRow.setAttribute('data-worker-name', worker.name);
|
|
594
|
-
detailRow.setAttribute('data-worker-driver', worker.driver);
|
|
595
|
-
detailRow.innerHTML = \`${getDetailRowTemplate()}\`;
|
|
596
|
-
return detailRow;
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
function renderWorkers(data) {
|
|
600
|
-
const tbody = document.getElementById('workers-tbody');
|
|
601
|
-
if (!tbody) return;
|
|
602
|
-
|
|
603
|
-
const expandedWorkers = new Set(
|
|
604
|
-
Array.from(tbody.querySelectorAll('.expandable-row.open'))
|
|
605
|
-
.map(row => row.getAttribute('id')?.replace('details-', ''))
|
|
606
|
-
.filter(Boolean)
|
|
607
|
-
);
|
|
608
|
-
|
|
609
|
-
tbody.innerHTML = '';
|
|
610
|
-
|
|
611
|
-
if (!data.workers || data.workers.length === 0) {
|
|
612
|
-
tbody.innerHTML = '<tr><td colspan="7" class="text-center p-4">No workers found</td></tr>';
|
|
613
|
-
updateQueueSummary(data.queueData);
|
|
614
|
-
updateDriverFilter(data.drivers);
|
|
615
|
-
updateDriversList(data.drivers);
|
|
616
|
-
updatePagination(data.pagination);
|
|
617
|
-
return;
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
data.workers.forEach(worker => {
|
|
621
|
-
const { row, detailsId } = createWorkerRow(worker);
|
|
622
|
-
const detailRow = createDetailRow(worker, detailsId);
|
|
623
|
-
|
|
624
|
-
const normalizedName = worker.name.replace(/[^a-z0-9]/gi, '-');
|
|
625
|
-
if (expandedWorkers.has(normalizedName)) {
|
|
626
|
-
detailRow.classList.add('open');
|
|
627
|
-
ensureWorkerDetails(worker.name, detailRow, worker.driver);
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
tbody.appendChild(row);
|
|
631
|
-
tbody.appendChild(detailRow);
|
|
632
|
-
});
|
|
633
|
-
|
|
634
|
-
updateQueueSummary(data.queueData);
|
|
635
|
-
updateDriverFilter(data.drivers);
|
|
636
|
-
updateDriversList(data.drivers);
|
|
637
|
-
updatePagination(data.pagination);
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
function toggleDetails(rowId) {
|
|
641
|
-
const row = document.getElementById(rowId);
|
|
642
|
-
if (row) {
|
|
643
|
-
const isOpen = row.classList.toggle('open');
|
|
644
|
-
if (isOpen) {
|
|
645
|
-
const workerName = row.getAttribute('data-worker-name') || rowId.replace('details-', '');
|
|
646
|
-
const workerDriver = row.getAttribute('data-worker-driver');
|
|
647
|
-
ensureWorkerDetails(workerName, row, workerDriver);
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
`;
|
|
652
|
-
const getRenderingScripts = () => `
|
|
653
|
-
${getDetailRenderingScripts()}
|
|
654
|
-
${getDetailFormattingScripts()}
|
|
655
|
-
${getSummaryRenderingScripts()}
|
|
656
|
-
${getTableRenderingScripts()}
|
|
657
|
-
`;
|
|
658
|
-
const getPaginationScripts = () => `
|
|
659
|
-
function updatePagination(pagination) {
|
|
660
|
-
currentPage = pagination.page;
|
|
661
|
-
totalPages = pagination.totalPages;
|
|
662
|
-
totalWorkers = pagination.total;
|
|
663
|
-
|
|
664
|
-
const start = (pagination.page - 1) * pagination.limit + 1;
|
|
665
|
-
const end = Math.min(pagination.page * pagination.limit, pagination.total);
|
|
666
|
-
|
|
667
|
-
document.getElementById('pagination-info').textContent =
|
|
668
|
-
\`Showing \${pagination.total === 0 ? 0 : start}-\${end} of \${pagination.total} workers\`;
|
|
669
|
-
|
|
670
|
-
document.getElementById('prev-btn').disabled = !pagination.hasPrev;
|
|
671
|
-
document.getElementById('next-btn').disabled = !pagination.hasNext;
|
|
672
|
-
|
|
673
|
-
// Update page numbers
|
|
674
|
-
const pageNumbers = document.getElementById('page-numbers');
|
|
675
|
-
pageNumbers.innerHTML = '';
|
|
676
|
-
|
|
677
|
-
const startPage = Math.max(1, currentPage - 2);
|
|
678
|
-
const endPage = Math.min(totalPages, currentPage + 2);
|
|
679
|
-
|
|
680
|
-
for (let i = startPage; i <= endPage; i++) {
|
|
681
|
-
const btn = document.createElement('button');
|
|
682
|
-
btn.className = \`page-btn \${i === currentPage ? 'active' : ''}\`;
|
|
683
|
-
btn.textContent = i.toString();
|
|
684
|
-
btn.onclick = () => goToPage(i);
|
|
685
|
-
pageNumbers.appendChild(btn);
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
function loadPage(direction) {
|
|
690
|
-
if (direction === 'prev' && currentPage > 1) {
|
|
691
|
-
currentPage--;
|
|
692
|
-
} else if (direction === 'next' && currentPage < totalPages) {
|
|
693
|
-
currentPage++;
|
|
694
|
-
}
|
|
695
|
-
fetchData();
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
function goToPage(page) {
|
|
699
|
-
currentPage = page;
|
|
700
|
-
fetchData();
|
|
701
|
-
}
|
|
702
|
-
`;
|
|
703
|
-
const getWorkerActionScripts = () => `
|
|
704
|
-
${getWorkerLifecycleScripts()}
|
|
705
|
-
${getWorkerAutoStartScripts()}
|
|
706
|
-
${getWorkerModalScripts()}
|
|
707
|
-
`;
|
|
708
|
-
const getWorkerLifecycleScripts = () => `
|
|
709
|
-
// Worker actions
|
|
710
|
-
async function startWorker(name, driver) {
|
|
711
|
-
try {
|
|
712
|
-
await fetch(\`\${API_BASE}/api/workers/\${name}/start?driver=\${driver}\`, { method: 'POST' });
|
|
713
|
-
fetchData();
|
|
714
|
-
} catch (err) {
|
|
715
|
-
console.error('Failed to start worker:', err);
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
async function stopWorker(name, driver) {
|
|
720
|
-
try {
|
|
721
|
-
await fetch(\`\${API_BASE}/api/workers/\${name}/stop?driver=\${driver}\`, { method: 'POST' });
|
|
722
|
-
fetchData();
|
|
723
|
-
} catch (err) {
|
|
724
|
-
console.error('Failed to stop worker:', err);
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
async function restartWorker(name, driver) {
|
|
729
|
-
try {
|
|
730
|
-
await fetch(\`\${API_BASE}/api/workers/\${name}/restart?driver=\${driver}\`, { method: 'POST' });
|
|
731
|
-
fetchData();
|
|
732
|
-
} catch (err) {
|
|
733
|
-
console.error('Failed to restart worker:', err);
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
async function deleteWorker(name, driver) {
|
|
738
|
-
if (!confirm(\`Are you sure you want to delete worker "\${name}"? This action cannot be undone.\`)) {
|
|
739
|
-
return;
|
|
740
|
-
}
|
|
741
|
-
try {
|
|
742
|
-
const response = await fetch(\`\${API_BASE}/api/workers/\${name}?driver=\${driver}\`, { method: 'DELETE' });
|
|
743
|
-
if (!response.ok) throw new Error('Failed to delete worker');
|
|
744
|
-
fetchData();
|
|
745
|
-
} catch (err) {
|
|
746
|
-
console.error('Failed to delete worker:', err);
|
|
747
|
-
alert('Failed to delete worker: ' + err.message);
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
`;
|
|
751
|
-
const getWorkerAutoStartScripts = () => `
|
|
752
|
-
async function toggleAutoStart(name, driver, enabled) {
|
|
753
|
-
try {
|
|
754
|
-
await fetch(\`\${API_BASE}/api/workers/\${name}/auto-start?driver=\${driver}\`, {
|
|
755
|
-
method: 'POST',
|
|
756
|
-
headers: { 'Content-Type': 'application/json' },
|
|
757
|
-
body: JSON.stringify({ enabled })
|
|
758
|
-
});
|
|
759
|
-
} catch (err) {
|
|
760
|
-
console.error('Failed to toggle auto-start:', err);
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
`;
|
|
764
|
-
const getWorkerModalScripts = () => `
|
|
765
|
-
function showAddWorkerModal() {
|
|
766
|
-
// TODO: Implement add worker modal
|
|
767
|
-
alert('Add Worker functionality coming soon!');
|
|
768
|
-
}
|
|
769
|
-
`;
|
|
770
|
-
const getAutoRefreshScripts = (options) => `
|
|
771
|
-
function toggleAutoRefresh() {
|
|
772
|
-
setAutoRefresh(!autoRefreshEnabled);
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
// Auto-refresh
|
|
776
|
-
function setAutoRefresh(enabled) {
|
|
777
|
-
autoRefreshEnabled = enabled;
|
|
778
|
-
localStorage.setItem(AUTO_REFRESH_KEY, enabled.toString());
|
|
779
|
-
|
|
780
|
-
if (refreshTimer) {
|
|
781
|
-
clearInterval(refreshTimer);
|
|
782
|
-
refreshTimer = null;
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
if (enabled) {
|
|
786
|
-
refreshTimer = setInterval(fetchData, ${options.refreshIntervalMs});
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
const btn = document.getElementById('auto-refresh-toggle');
|
|
790
|
-
const icon = document.getElementById('auto-refresh-icon');
|
|
791
|
-
const label = document.getElementById('auto-refresh-label');
|
|
792
|
-
|
|
793
|
-
if (btn && icon && label) {
|
|
794
|
-
if (enabled) {
|
|
795
|
-
label.textContent = 'Pause Refresh';
|
|
796
|
-
icon.innerHTML = '<rect x="6" y="4" width="4" height="16"></rect><rect x="14" y="4" width="4" height="16"></rect>';
|
|
797
|
-
} else {
|
|
798
|
-
label.textContent = 'Auto Refresh';
|
|
799
|
-
icon.innerHTML = '<polygon points="5 3 19 12 5 21 5 3"></polygon>';
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
`;
|
|
804
|
-
const getEventListenersScripts = (options) => `
|
|
805
|
-
// Event listeners
|
|
806
|
-
document.addEventListener('DOMContentLoaded', function() {
|
|
807
|
-
// Initialize theme
|
|
808
|
-
currentTheme = getPreferredTheme();
|
|
809
|
-
applyTheme(currentTheme);
|
|
810
|
-
document.getElementById('theme-toggle').addEventListener('click', toggleTheme);
|
|
811
|
-
|
|
812
|
-
// Set up event listeners
|
|
813
|
-
document.getElementById('status-filter').addEventListener('change', fetchData);
|
|
814
|
-
document.getElementById('driver-filter').addEventListener('change', fetchData);
|
|
815
|
-
document.getElementById('sort-select').addEventListener('change', fetchData);
|
|
816
|
-
|
|
817
|
-
const searchBtn = document.getElementById('search-btn');
|
|
818
|
-
if (searchBtn) {
|
|
819
|
-
searchBtn.addEventListener('click', () => {
|
|
820
|
-
currentPage = 1;
|
|
821
|
-
fetchData();
|
|
822
|
-
});
|
|
823
|
-
}
|
|
824
|
-
document.getElementById('search-input').addEventListener('keypress', (e) => {
|
|
825
|
-
if (e.key === 'Enter') {
|
|
826
|
-
currentPage = 1;
|
|
827
|
-
fetchData();
|
|
828
|
-
}
|
|
829
|
-
});
|
|
830
|
-
|
|
831
|
-
// Initialize auto-refresh
|
|
832
|
-
const storedAutoRefresh = localStorage.getItem(AUTO_REFRESH_KEY);
|
|
833
|
-
if (storedAutoRefresh === null) {
|
|
834
|
-
// Only use default if no value is stored
|
|
835
|
-
setAutoRefresh(${options.autoRefresh});
|
|
836
|
-
} else {
|
|
837
|
-
// Use stored value
|
|
838
|
-
setAutoRefresh(storedAutoRefresh === 'true');
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
const storedBulkAutoStart = localStorage.getItem(BULK_AUTO_START_KEY);
|
|
842
|
-
if (storedBulkAutoStart === 'true') {
|
|
843
|
-
bulkAutoStartEnabled = true;
|
|
844
|
-
updateBulkAutoStartButton('Auto Start: On');
|
|
845
|
-
} else if (storedBulkAutoStart === 'false') {
|
|
846
|
-
bulkAutoStartEnabled = false;
|
|
847
|
-
updateBulkAutoStartButton('Auto Start: Off');
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
// Load initial data
|
|
851
|
-
fetchData();
|
|
852
|
-
});
|
|
853
|
-
`;
|
|
854
|
-
const getDashboardScripts = (options) => `
|
|
855
|
-
<script>
|
|
856
|
-
// Configuration
|
|
857
|
-
const API_BASE = '';
|
|
858
|
-
|
|
859
|
-
const THEME_KEY = 'zintrust-workers-dashboard-theme';
|
|
860
|
-
const AUTO_REFRESH_KEY = 'zintrust-workers-dashboard-auto-refresh';
|
|
861
|
-
const PAGE_SIZE_KEY = 'zintrust-workers-dashboard-page-size';
|
|
862
|
-
const BULK_AUTO_START_KEY = 'zintrust-workers-dashboard-bulk-auto-start';
|
|
863
|
-
|
|
864
|
-
let currentPage = 1;
|
|
865
|
-
let totalPages = 1;
|
|
866
|
-
let totalWorkers = 0;
|
|
867
|
-
let autoRefreshEnabled = ${options.autoRefresh};
|
|
868
|
-
let refreshTimer = null;
|
|
869
|
-
let currentTheme = null;
|
|
870
|
-
let bulkAutoStartEnabled = false;
|
|
871
|
-
let lastWorkers = [];
|
|
872
|
-
const detailsCache = new Map();
|
|
873
|
-
const MAX_CACHE_SIZE = 50;
|
|
874
|
-
|
|
875
|
-
${getThemeManagementScripts()}
|
|
876
|
-
${getDataFetchingScripts(options)}
|
|
877
|
-
${getRenderingScripts()}
|
|
878
|
-
${getPaginationScripts()}
|
|
879
|
-
${getWorkerActionScripts()}
|
|
880
|
-
${getAutoRefreshScripts(options)}
|
|
881
|
-
${getEventListenersScripts(options)}
|
|
882
|
-
</script>
|
|
883
|
-
`;
|
|
884
|
-
const getWorkersDashboardHTML = (options) => `
|
|
885
|
-
<!DOCTYPE html>
|
|
886
|
-
<html lang="en">
|
|
887
|
-
<head>
|
|
888
|
-
<meta charset="UTF-8">
|
|
889
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
890
|
-
<title>Workers Dashboard</title>
|
|
891
|
-
<style>
|
|
892
|
-
${getWorkersDashboardStyles()}
|
|
893
|
-
</style>
|
|
894
|
-
</head>
|
|
895
|
-
<body>
|
|
896
|
-
<div class="container">
|
|
897
|
-
${getDashboardHeader()}
|
|
898
|
-
${getDashboardLoadingStates()}
|
|
899
|
-
</div>
|
|
900
|
-
${getDashboardScripts(options)}
|
|
901
|
-
</body>
|
|
902
|
-
</html>
|
|
903
|
-
`;
|
|
904
|
-
export { getWorkersDashboardHTML };
|