Samfredoly commited on
Commit
dd094ce
·
verified ·
1 Parent(s): 734a827

Create app.js

Browse files
Files changed (1) hide show
  1. app.js +522 -0
app.js ADDED
@@ -0,0 +1,522 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * HF Uploader Dashboard - JavaScript Application
3
+ */
4
+
5
+ // ============================================================================
6
+ // API Client
7
+ // ============================================================================
8
+
9
+ class APIClient {
10
+ constructor(baseURL = '/api') {
11
+ this.baseURL = baseURL;
12
+ }
13
+
14
+ async request(endpoint, options = {}) {
15
+ const url = `${this.baseURL}${endpoint}`;
16
+ const response = await fetch(url, {
17
+ headers: {
18
+ 'Content-Type': 'application/json',
19
+ ...options.headers,
20
+ },
21
+ ...options,
22
+ });
23
+
24
+ if (!response.ok) {
25
+ const error = await response.json().catch(() => ({ detail: response.statusText }));
26
+ throw new Error(error.detail || `HTTP ${response.status}`);
27
+ }
28
+
29
+ return response.json();
30
+ }
31
+
32
+ // Configuration endpoints
33
+ getConfig() {
34
+ return this.request('/config');
35
+ }
36
+
37
+ updateConfig(data) {
38
+ return this.request('/config', {
39
+ method: 'POST',
40
+ body: JSON.stringify(data),
41
+ });
42
+ }
43
+
44
+ // Queue endpoints
45
+ getQueue(status = null) {
46
+ const url = status ? `/queue?status=${status}` : '/queue';
47
+ return this.request(url);
48
+ }
49
+
50
+ getQueueStats() {
51
+ return this.request('/queue/stats');
52
+ }
53
+
54
+ addToQueue() {
55
+ return this.request('/queue/add', { method: 'POST' });
56
+ }
57
+
58
+ clearQueue() {
59
+ return this.request('/queue/clear', { method: 'POST' });
60
+ }
61
+
62
+ // Processing endpoints
63
+ getProcessingState() {
64
+ return this.request('/processing/state');
65
+ }
66
+
67
+ startProcessing(maxFiles = 0) {
68
+ return this.request('/processing/start', {
69
+ method: 'POST',
70
+ body: JSON.stringify({ max_files: maxFiles }),
71
+ });
72
+ }
73
+
74
+ getProcessedFiles() {
75
+ return this.request('/processing/files');
76
+ }
77
+
78
+ // Upload endpoints
79
+ startUpload(fileIds, batchSize = 10) {
80
+ return this.request('/upload/start', {
81
+ method: 'POST',
82
+ body: JSON.stringify({ file_ids: fileIds, batch_size: batchSize }),
83
+ });
84
+ }
85
+
86
+ getUploadStatus() {
87
+ return this.request('/upload/status');
88
+ }
89
+
90
+ retryUpload(itemId) {
91
+ return this.request(`/upload/retry/${itemId}`, { method: 'POST' });
92
+ }
93
+
94
+ // File endpoints
95
+ previewFile(filename) {
96
+ return this.request('/file/preview', {
97
+ method: 'POST',
98
+ body: JSON.stringify({ filename }),
99
+ });
100
+ }
101
+
102
+ // Error log endpoints
103
+ getErrorLogs(limit = 50) {
104
+ return this.request(`/errors?limit=${limit}`);
105
+ }
106
+
107
+ clearErrorLogs() {
108
+ return this.request('/errors', { method: 'DELETE' });
109
+ }
110
+ }
111
+
112
+ // ============================================================================
113
+ // Notification System
114
+ // ============================================================================
115
+
116
+ class Notifier {
117
+ constructor() {
118
+ this.toastEl = document.getElementById('toast');
119
+ this.timeout = null;
120
+ }
121
+
122
+ show(message, type = 'info', duration = 3000) {
123
+ this.toastEl.textContent = message;
124
+ this.toastEl.className = `toast show ${type}`;
125
+
126
+ clearTimeout(this.timeout);
127
+ this.timeout = setTimeout(() => {
128
+ this.toastEl.classList.remove('show');
129
+ }, duration);
130
+ }
131
+
132
+ success(message) {
133
+ this.show(message, 'success');
134
+ }
135
+
136
+ error(message) {
137
+ this.show(message, 'error', 5000);
138
+ }
139
+
140
+ warning(message) {
141
+ this.show(message, 'warning', 4000);
142
+ }
143
+
144
+ info(message) {
145
+ this.show(message, 'info');
146
+ }
147
+ }
148
+
149
+ // ============================================================================
150
+ // Dashboard Application
151
+ // ============================================================================
152
+
153
+ class Dashboard {
154
+ constructor() {
155
+ this.api = new APIClient();
156
+ this.notifier = new Notifier();
157
+ this.currentSection = 'dashboard';
158
+ this.refreshInterval = null;
159
+
160
+ this.init();
161
+ }
162
+
163
+ init() {
164
+ this.setupEventListeners();
165
+ this.loadInitialData();
166
+ this.startAutoRefresh();
167
+ }
168
+
169
+ setupEventListeners() {
170
+ // Navigation
171
+ document.querySelectorAll('.nav-link').forEach(link => {
172
+ link.addEventListener('click', (e) => {
173
+ e.preventDefault();
174
+ const section = link.dataset.section;
175
+ this.switchSection(section);
176
+ });
177
+ });
178
+
179
+ // Dashboard buttons
180
+ document.getElementById('btn-start-processing').addEventListener('click', () => this.startProcessing());
181
+ document.getElementById('btn-start-upload').addEventListener('click', () => this.startUpload());
182
+
183
+ // Queue buttons
184
+ document.getElementById('btn-add-to-queue').addEventListener('click', () => this.addToQueue());
185
+ document.getElementById('btn-clear-queue').addEventListener('click', () => this.clearQueue());
186
+ document.getElementById('queue-filter').addEventListener('change', (e) => this.filterQueue(e.target.value));
187
+
188
+ // Configuration buttons
189
+ document.getElementById('btn-save-config').addEventListener('click', () => this.saveConfig());
190
+
191
+ // Logs buttons
192
+ document.getElementById('btn-clear-logs').addEventListener('click', () => this.clearLogs());
193
+
194
+ // Modal close
195
+ document.querySelector('.modal-close').addEventListener('click', () => this.closeModal());
196
+ }
197
+
198
+ switchSection(section) {
199
+ // Hide all sections
200
+ document.querySelectorAll('.section').forEach(s => s.classList.remove('active'));
201
+ // Show selected section
202
+ document.getElementById(section).classList.add('active');
203
+
204
+ // Update nav
205
+ document.querySelectorAll('.nav-link').forEach(link => {
206
+ link.classList.toggle('active', link.dataset.section === section);
207
+ });
208
+
209
+ this.currentSection = section;
210
+
211
+ // Load section-specific data
212
+ if (section === 'queue') {
213
+ this.loadQueue();
214
+ } else if (section === 'logs') {
215
+ this.loadErrorLogs();
216
+ }
217
+ }
218
+
219
+ async loadInitialData() {
220
+ try {
221
+ await this.loadConfig();
222
+ await this.loadStats();
223
+ await this.loadProcessingState();
224
+ await this.loadUploadStatus();
225
+ } catch (error) {
226
+ this.notifier.error(`Failed to load data: ${error.message}`);
227
+ }
228
+ }
229
+
230
+ async loadConfig() {
231
+ try {
232
+ const config = await this.api.getConfig();
233
+ document.getElementById('hf-token').value = config.hf_token || '';
234
+ document.getElementById('source-all-repo').value = config.source_all_repo;
235
+ document.getElementById('source-ato-repo').value = config.source_ato_repo;
236
+ document.getElementById('target-repo').value = config.target_repo;
237
+ document.getElementById('batch-size').value = config.upload_batch_size;
238
+ document.getElementById('max-uploads').value = config.max_uploads_per_hour;
239
+ } catch (error) {
240
+ console.error('Failed to load config:', error);
241
+ }
242
+ }
243
+
244
+ async loadStats() {
245
+ try {
246
+ const stats = await this.api.getQueueStats();
247
+ document.getElementById('stat-pending').textContent = stats.pending;
248
+ document.getElementById('stat-uploaded').textContent = stats.completed;
249
+ document.getElementById('stat-failed').textContent = stats.failed;
250
+
251
+ const processed = await this.api.getProcessedFiles();
252
+ document.getElementById('stat-processed').textContent = processed.count;
253
+ } catch (error) {
254
+ console.error('Failed to load stats:', error);
255
+ }
256
+ }
257
+
258
+ async loadProcessingState() {
259
+ try {
260
+ const state = await this.api.getProcessingState();
261
+ document.getElementById('processing-status').textContent = state.status;
262
+ document.getElementById('matched-pairs').textContent = state.matched_pairs;
263
+
264
+ if (state.total_files > 0) {
265
+ const progress = (state.processed_files / state.total_files) * 100;
266
+ document.getElementById('processing-progress').style.width = `${progress}%`;
267
+ }
268
+ } catch (error) {
269
+ console.error('Failed to load processing state:', error);
270
+ }
271
+ }
272
+
273
+ async loadUploadStatus() {
274
+ try {
275
+ const status = await this.api.getUploadStatus();
276
+ const rateLimitEl = document.getElementById('rate-limit-status');
277
+ const remainingEl = document.getElementById('remaining-uploads');
278
+
279
+ if (status.rate_limit.can_upload) {
280
+ rateLimitEl.textContent = 'Ready';
281
+ rateLimitEl.style.color = 'var(--color-success)';
282
+ } else {
283
+ rateLimitEl.textContent = 'Limited';
284
+ rateLimitEl.style.color = 'var(--color-error)';
285
+ }
286
+
287
+ remainingEl.textContent = status.rate_limit.remaining_uploads;
288
+ } catch (error) {
289
+ console.error('Failed to load upload status:', error);
290
+ }
291
+ }
292
+
293
+ async loadQueue(status = null) {
294
+ try {
295
+ const items = await this.api.getQueue(status);
296
+ const tbody = document.getElementById('queue-tbody');
297
+
298
+ if (items.length === 0) {
299
+ tbody.innerHTML = '<tr class="empty-row"><td colspan="5">No files in queue</td></tr>';
300
+ return;
301
+ }
302
+
303
+ tbody.innerHTML = items.map(item => `
304
+ <tr>
305
+ <td>${this.escapeHtml(item.file_name)}</td>
306
+ <td>${this.formatBytes(item.file_size)}</td>
307
+ <td><span class="status-badge status-${item.status}">${item.status}</span></td>
308
+ <td>${item.retry_count}/${item.max_retries}</td>
309
+ <td>
310
+ ${item.status === 'failed' ? `<button class="btn btn-secondary" onclick="dashboard.retryFile(${item.id})">Retry</button>` : ''}
311
+ <button class="btn btn-secondary" onclick="dashboard.previewFile('${this.escapeHtml(item.file_name)}')">Preview</button>
312
+ </td>
313
+ </tr>
314
+ `).join('');
315
+ } catch (error) {
316
+ this.notifier.error(`Failed to load queue: ${error.message}`);
317
+ }
318
+ }
319
+
320
+ async filterQueue(status) {
321
+ await this.loadQueue(status || null);
322
+ }
323
+
324
+ async loadErrorLogs() {
325
+ try {
326
+ const logs = await this.api.getErrorLogs();
327
+ const tbody = document.getElementById('logs-tbody');
328
+
329
+ if (logs.length === 0) {
330
+ tbody.innerHTML = '<tr class="empty-row"><td colspan="5">No errors logged</td></tr>';
331
+ return;
332
+ }
333
+
334
+ tbody.innerHTML = logs.map(log => `
335
+ <tr>
336
+ <td>${this.escapeHtml(log.file_name)}</td>
337
+ <td>${log.error_code || 'N/A'}</td>
338
+ <td>${this.escapeHtml(log.error_message || '')}</td>
339
+ <td>${log.retryable ? 'Yes' : 'No'}</td>
340
+ <td>${new Date(log.created_at).toLocaleString()}</td>
341
+ </tr>
342
+ `).join('');
343
+ } catch (error) {
344
+ this.notifier.error(`Failed to load error logs: ${error.message}`);
345
+ }
346
+ }
347
+
348
+ async startProcessing() {
349
+ try {
350
+ const btn = document.getElementById('btn-start-processing');
351
+ btn.disabled = true;
352
+ this.notifier.info('Starting dataset processing...');
353
+
354
+ await this.api.startProcessing(0);
355
+ this.notifier.success('Processing started');
356
+
357
+ // Refresh state periodically
358
+ this.refreshInterval = setInterval(() => this.loadProcessingState(), 2000);
359
+ } catch (error) {
360
+ this.notifier.error(`Failed to start processing: ${error.message}`);
361
+ document.getElementById('btn-start-processing').disabled = false;
362
+ }
363
+ }
364
+
365
+ async startUpload() {
366
+ try {
367
+ const stats = await this.api.getQueueStats();
368
+ if (stats.pending === 0) {
369
+ this.notifier.warning('No pending files to upload');
370
+ return;
371
+ }
372
+
373
+ const btn = document.getElementById('btn-start-upload');
374
+ btn.disabled = true;
375
+ this.notifier.info('Starting upload...');
376
+
377
+ // Get all pending files
378
+ const queue = await this.api.getQueue('pending');
379
+ const fileIds = queue.map(item => item.id);
380
+
381
+ await this.api.startUpload(fileIds);
382
+ this.notifier.success('Upload started');
383
+
384
+ // Refresh stats periodically
385
+ this.refreshInterval = setInterval(() => this.loadStats(), 2000);
386
+ } catch (error) {
387
+ this.notifier.error(`Failed to start upload: ${error.message}`);
388
+ document.getElementById('btn-start-upload').disabled = false;
389
+ }
390
+ }
391
+
392
+ async addToQueue() {
393
+ try {
394
+ const btn = document.getElementById('btn-add-to-queue');
395
+ btn.disabled = true;
396
+
397
+ const result = await this.api.addToQueue();
398
+ this.notifier.success(`Added ${result.added} files to queue`);
399
+
400
+ await this.loadQueue();
401
+ await this.loadStats();
402
+
403
+ btn.disabled = false;
404
+ } catch (error) {
405
+ this.notifier.error(`Failed to add to queue: ${error.message}`);
406
+ document.getElementById('btn-add-to-queue').disabled = false;
407
+ }
408
+ }
409
+
410
+ async clearQueue() {
411
+ if (!confirm('Are you sure you want to clear the entire queue?')) {
412
+ return;
413
+ }
414
+
415
+ try {
416
+ await this.api.clearQueue();
417
+ this.notifier.success('Queue cleared');
418
+ await this.loadQueue();
419
+ await this.loadStats();
420
+ } catch (error) {
421
+ this.notifier.error(`Failed to clear queue: ${error.message}`);
422
+ }
423
+ }
424
+
425
+ async saveConfig() {
426
+ try {
427
+ const config = {
428
+ hf_token: document.getElementById('hf-token').value,
429
+ source_all_repo: document.getElementById('source-all-repo').value,
430
+ source_ato_repo: document.getElementById('source-ato-repo').value,
431
+ target_repo: document.getElementById('target-repo').value,
432
+ upload_batch_size: parseInt(document.getElementById('batch-size').value),
433
+ max_uploads_per_hour: parseInt(document.getElementById('max-uploads').value),
434
+ };
435
+
436
+ const btn = document.getElementById('btn-save-config');
437
+ btn.disabled = true;
438
+
439
+ await this.api.updateConfig(config);
440
+ this.notifier.success('Configuration saved');
441
+
442
+ btn.disabled = false;
443
+ } catch (error) {
444
+ this.notifier.error(`Failed to save configuration: ${error.message}`);
445
+ document.getElementById('btn-save-config').disabled = false;
446
+ }
447
+ }
448
+
449
+ async retryFile(itemId) {
450
+ try {
451
+ await this.api.retryUpload(itemId);
452
+ this.notifier.success('Retry scheduled');
453
+ await this.loadQueue();
454
+ } catch (error) {
455
+ this.notifier.error(`Failed to retry: ${error.message}`);
456
+ }
457
+ }
458
+
459
+ async previewFile(filename) {
460
+ try {
461
+ const data = await this.api.previewFile(filename);
462
+ const modal = document.getElementById('preview-modal');
463
+ const content = document.getElementById('preview-content');
464
+
465
+ content.textContent = JSON.stringify(data.content, null, 2);
466
+ modal.classList.add('show');
467
+ } catch (error) {
468
+ this.notifier.error(`Failed to preview file: ${error.message}`);
469
+ }
470
+ }
471
+
472
+ closeModal() {
473
+ document.getElementById('preview-modal').classList.remove('show');
474
+ }
475
+
476
+ async clearLogs() {
477
+ if (!confirm('Are you sure you want to clear all error logs?')) {
478
+ return;
479
+ }
480
+
481
+ try {
482
+ await this.api.clearErrorLogs();
483
+ this.notifier.success('Error logs cleared');
484
+ await this.loadErrorLogs();
485
+ } catch (error) {
486
+ this.notifier.error(`Failed to clear logs: ${error.message}`);
487
+ }
488
+ }
489
+
490
+ startAutoRefresh() {
491
+ setInterval(() => {
492
+ if (this.currentSection === 'dashboard') {
493
+ this.loadStats();
494
+ this.loadProcessingState();
495
+ this.loadUploadStatus();
496
+ }
497
+ }, 5000);
498
+ }
499
+
500
+ escapeHtml(text) {
501
+ const div = document.createElement('div');
502
+ div.textContent = text;
503
+ return div.innerHTML;
504
+ }
505
+
506
+ formatBytes(bytes) {
507
+ if (bytes === 0) return '0 B';
508
+ const k = 1024;
509
+ const sizes = ['B', 'KB', 'MB', 'GB'];
510
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
511
+ return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
512
+ }
513
+ }
514
+
515
+ // ============================================================================
516
+ // Initialize Dashboard
517
+ // ============================================================================
518
+
519
+ let dashboard;
520
+ document.addEventListener('DOMContentLoaded', () => {
521
+ dashboard = new Dashboard();
522
+ });