worker-que 1.0.1 → 1.1.2

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.
@@ -1,7 +1,257 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getLoginHTML = getLoginHTML;
3
4
  exports.getDashboardHTML = getDashboardHTML;
4
- function getDashboardHTML(options) {
5
+ function getLoginHTML(options, error) {
6
+ return `
7
+ <!DOCTYPE html>
8
+ <html lang="en">
9
+ <head>
10
+ <meta charset="UTF-8">
11
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
12
+ <title>Sign in - ${options.title}</title>
13
+ <style>
14
+ * {
15
+ margin: 0;
16
+ padding: 0;
17
+ box-sizing: border-box;
18
+ }
19
+
20
+ body {
21
+ font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, Roboto, 'Helvetica Neue', sans-serif;
22
+ background: linear-gradient(135deg, #0078d4 0%, #005a9e 100%);
23
+ min-height: 100vh;
24
+ display: flex;
25
+ align-items: center;
26
+ justify-content: center;
27
+ padding: 2rem;
28
+ }
29
+
30
+ .login-container {
31
+ background: white;
32
+ border-radius: 8px;
33
+ box-shadow: 0 2px 40px rgba(0,0,0,0.2);
34
+ width: 100%;
35
+ max-width: 440px;
36
+ padding: 44px;
37
+ }
38
+
39
+ .logo-container {
40
+ text-align: center;
41
+ margin-bottom: 24px;
42
+ }
43
+
44
+ .logo {
45
+ width: 60px;
46
+ height: 60px;
47
+ background: linear-gradient(135deg, #0078d4, #106ebe);
48
+ border-radius: 12px;
49
+ display: inline-flex;
50
+ align-items: center;
51
+ justify-content: center;
52
+ margin-bottom: 16px;
53
+ }
54
+
55
+ .logo-icon {
56
+ font-size: 32px;
57
+ color: white;
58
+ }
59
+
60
+ h1 {
61
+ font-size: 24px;
62
+ font-weight: 600;
63
+ color: #1f1f1f;
64
+ margin-bottom: 8px;
65
+ text-align: center;
66
+ }
67
+
68
+ .subtitle {
69
+ font-size: 15px;
70
+ color: #605e5c;
71
+ text-align: center;
72
+ margin-bottom: 32px;
73
+ }
74
+
75
+ .form-group {
76
+ margin-bottom: 24px;
77
+ }
78
+
79
+ label {
80
+ display: block;
81
+ font-size: 14px;
82
+ font-weight: 600;
83
+ color: #323130;
84
+ margin-bottom: 8px;
85
+ }
86
+
87
+ input[type="email"],
88
+ input[type="password"] {
89
+ width: 100%;
90
+ padding: 10px 12px;
91
+ font-size: 14px;
92
+ border: 1px solid #8a8886;
93
+ border-radius: 2px;
94
+ transition: all 0.15s ease;
95
+ font-family: 'Segoe UI', sans-serif;
96
+ }
97
+
98
+ input:focus {
99
+ outline: none;
100
+ border-color: #0078d4;
101
+ box-shadow: 0 0 0 1px #0078d4;
102
+ }
103
+
104
+ input:hover {
105
+ border-color: #323130;
106
+ }
107
+
108
+ .error-message {
109
+ background: #fde7e9;
110
+ border-left: 3px solid #a4262c;
111
+ color: #a4262c;
112
+ padding: 12px 16px;
113
+ font-size: 13px;
114
+ margin-bottom: 20px;
115
+ border-radius: 2px;
116
+ display: flex;
117
+ align-items: center;
118
+ gap: 8px;
119
+ }
120
+
121
+ .error-icon {
122
+ font-weight: bold;
123
+ }
124
+
125
+ .submit-btn {
126
+ width: 100%;
127
+ background: #0078d4;
128
+ color: white;
129
+ border: none;
130
+ padding: 11px 24px;
131
+ font-size: 14px;
132
+ font-weight: 600;
133
+ border-radius: 2px;
134
+ cursor: pointer;
135
+ transition: all 0.15s ease;
136
+ font-family: 'Segoe UI', sans-serif;
137
+ }
138
+
139
+ .submit-btn:hover {
140
+ background: #106ebe;
141
+ }
142
+
143
+ .submit-btn:active {
144
+ background: #005a9e;
145
+ }
146
+
147
+ .submit-btn:disabled {
148
+ background: #f3f2f1;
149
+ color: #a19f9d;
150
+ cursor: not-allowed;
151
+ }
152
+
153
+ .remember-me {
154
+ display: flex;
155
+ align-items: center;
156
+ margin-bottom: 24px;
157
+ font-size: 13px;
158
+ color: #323130;
159
+ }
160
+
161
+ .remember-me input[type="checkbox"] {
162
+ margin-right: 8px;
163
+ width: 16px;
164
+ height: 16px;
165
+ cursor: pointer;
166
+ }
167
+
168
+ .footer-text {
169
+ margin-top: 24px;
170
+ text-align: center;
171
+ font-size: 12px;
172
+ color: #605e5c;
173
+ }
174
+
175
+ .security-badge {
176
+ display: inline-flex;
177
+ align-items: center;
178
+ gap: 6px;
179
+ padding: 8px 12px;
180
+ background: #f3f2f1;
181
+ border-radius: 4px;
182
+ font-size: 12px;
183
+ color: #605e5c;
184
+ margin-top: 16px;
185
+ }
186
+
187
+ .lock-icon {
188
+ color: #107c10;
189
+ }
190
+ </style>
191
+ </head>
192
+ <body>
193
+ <div class="login-container">
194
+ <div class="logo-container">
195
+ <div class="logo">
196
+ <span class="logo-icon">📊</span>
197
+ </div>
198
+ <h1>${options.title}</h1>
199
+ <p class="subtitle">Sign in to continue to dashboard</p>
200
+ </div>
201
+
202
+ ${error ? `
203
+ <div class="error-message">
204
+ <span class="error-icon">⚠</span>
205
+ <span>${error}</span>
206
+ </div>
207
+ ` : ''}
208
+
209
+ <form method="POST" action="${options.basePath}/login">
210
+ <div class="form-group">
211
+ <label for="email">Email</label>
212
+ <input
213
+ type="email"
214
+ id="email"
215
+ name="email"
216
+ required
217
+ autocomplete="email"
218
+ placeholder="Enter your email"
219
+ autofocus
220
+ >
221
+ </div>
222
+
223
+ <div class="form-group">
224
+ <label for="password">Password</label>
225
+ <input
226
+ type="password"
227
+ id="password"
228
+ name="password"
229
+ required
230
+ autocomplete="current-password"
231
+ placeholder="Enter your password"
232
+ >
233
+ </div>
234
+
235
+ <div class="remember-me">
236
+ <input type="checkbox" id="remember" name="remember" value="yes">
237
+ <label for="remember" style="margin-bottom: 0; font-weight: 400;">Keep me signed in</label>
238
+ </div>
239
+
240
+ <button type="submit" class="submit-btn">Sign in</button>
241
+
242
+ <div class="footer-text">
243
+ <div class="security-badge">
244
+ <span class="lock-icon">🔒</span>
245
+ <span>Secure connection</span>
246
+ </div>
247
+ </div>
248
+ </form>
249
+ </div>
250
+ </body>
251
+ </html>
252
+ `.trim();
253
+ }
254
+ function getDashboardHTML(options, userEmail) {
5
255
  return `
6
256
  <!DOCTYPE html>
7
257
  <html lang="en">
@@ -17,164 +267,296 @@ function getDashboardHTML(options) {
17
267
  }
18
268
 
19
269
  body {
20
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
21
- background: #f5f7fa;
22
- color: #2c3e50;
270
+ font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, Roboto, 'Helvetica Neue', sans-serif;
271
+ background: #faf9f8;
272
+ color: #201f1e;
23
273
  line-height: 1.6;
24
274
  }
25
275
 
276
+ /* Microsoft Fluent Design Header */
26
277
  .header {
27
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
28
- color: white;
29
- padding: 2rem;
30
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
278
+ background: #ffffff;
279
+ border-bottom: 1px solid #edebe9;
280
+ box-shadow: 0 0.3px 0.9px rgba(0,0,0,0.108), 0 1.6px 3.6px rgba(0,0,0,0.132);
281
+ position: sticky;
282
+ top: 0;
283
+ z-index: 100;
284
+ }
285
+
286
+ .header-content {
287
+ max-width: 1600px;
288
+ margin: 0 auto;
289
+ padding: 0 24px;
290
+ display: flex;
291
+ align-items: center;
292
+ justify-content: space-between;
293
+ height: 48px;
294
+ }
295
+
296
+ .header-left {
297
+ display: flex;
298
+ align-items: center;
299
+ gap: 16px;
300
+ }
301
+
302
+ .logo {
303
+ width: 32px;
304
+ height: 32px;
305
+ background: linear-gradient(135deg, #0078d4, #106ebe);
306
+ border-radius: 6px;
307
+ display: flex;
308
+ align-items: center;
309
+ justify-content: center;
310
+ font-size: 18px;
31
311
  }
32
312
 
33
313
  .header h1 {
34
- font-size: 2rem;
314
+ font-size: 16px;
35
315
  font-weight: 600;
36
- margin-bottom: 0.5rem;
316
+ color: #201f1e;
317
+ }
318
+
319
+ .header-right {
320
+ display: flex;
321
+ align-items: center;
322
+ gap: 12px;
323
+ }
324
+
325
+ .user-info {
326
+ display: flex;
327
+ align-items: center;
328
+ gap: 8px;
329
+ padding: 4px 12px 4px 4px;
330
+ background: #f3f2f1;
331
+ border-radius: 4px;
332
+ font-size: 13px;
333
+ color: #323130;
37
334
  }
38
335
 
39
- .header p {
40
- opacity: 0.9;
41
- font-size: 0.95rem;
336
+ .user-avatar {
337
+ width: 24px;
338
+ height: 24px;
339
+ background: #0078d4;
340
+ color: white;
341
+ border-radius: 50%;
342
+ display: flex;
343
+ align-items: center;
344
+ justify-content: center;
345
+ font-size: 11px;
346
+ font-weight: 600;
347
+ }
348
+
349
+ .logout-btn {
350
+ background: transparent;
351
+ border: 1px solid #8a8886;
352
+ color: #323130;
353
+ padding: 6px 12px;
354
+ font-size: 13px;
355
+ border-radius: 2px;
356
+ cursor: pointer;
357
+ transition: all 0.1s ease;
358
+ font-family: 'Segoe UI', sans-serif;
359
+ font-weight: 600;
360
+ }
361
+
362
+ .logout-btn:hover {
363
+ background: #f3f2f1;
364
+ border-color: #323130;
365
+ }
366
+
367
+ .logout-btn:active {
368
+ background: #edebe9;
42
369
  }
43
370
 
44
371
  .container {
45
- max-width: 1400px;
372
+ max-width: 1600px;
46
373
  margin: 0 auto;
47
- padding: 2rem;
374
+ padding: 24px;
48
375
  }
49
376
 
377
+ /* Fluent Cards */
50
378
  .stats-grid {
51
379
  display: grid;
52
- grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
53
- gap: 1.5rem;
54
- margin-bottom: 2rem;
380
+ grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
381
+ gap: 16px;
382
+ margin-bottom: 24px;
55
383
  }
56
384
 
57
385
  .stat-card {
58
- background: white;
59
- border-radius: 12px;
60
- padding: 1.5rem;
61
- box-shadow: 0 2px 8px rgba(0,0,0,0.08);
62
- transition: transform 0.2s, box-shadow 0.2s;
386
+ background: #ffffff;
387
+ border: 1px solid #edebe9;
388
+ border-radius: 4px;
389
+ padding: 20px;
390
+ box-shadow: 0 0.3px 0.9px rgba(0,0,0,0.108), 0 1.6px 3.6px rgba(0,0,0,0.132);
391
+ transition: all 0.2s cubic-bezier(0.4, 0.0, 0.23, 1);
392
+ position: relative;
393
+ overflow: hidden;
63
394
  }
64
395
 
65
396
  .stat-card:hover {
66
397
  transform: translateY(-2px);
67
- box-shadow: 0 4px 12px rgba(0,0,0,0.12);
398
+ box-shadow: 0 3.2px 7.2px rgba(0,0,0,0.132), 0 0.6px 1.8px rgba(0,0,0,0.108);
399
+ }
400
+
401
+ .stat-card::before {
402
+ content: '';
403
+ position: absolute;
404
+ top: 0;
405
+ left: 0;
406
+ right: 0;
407
+ height: 3px;
408
+ background: #8a8886;
68
409
  }
69
410
 
411
+ .stat-card.success::before { background: #107c10; }
412
+ .stat-card.warning::before { background: #faa900; }
413
+ .stat-card.danger::before { background: #d13438; }
414
+ .stat-card.info::before { background: #0078d4; }
415
+
70
416
  .stat-label {
71
- font-size: 0.875rem;
72
- color: #7f8c8d;
417
+ font-size: 12px;
418
+ color: #605e5c;
73
419
  text-transform: uppercase;
74
420
  letter-spacing: 0.5px;
75
- margin-bottom: 0.5rem;
421
+ margin-bottom: 8px;
422
+ font-weight: 600;
76
423
  }
77
424
 
78
425
  .stat-value {
79
- font-size: 2.5rem;
80
- font-weight: 700;
81
- color: #2c3e50;
426
+ font-size: 32px;
427
+ font-weight: 600;
428
+ color: #323130;
429
+ line-height: 1.2;
82
430
  }
83
431
 
84
- .stat-card.success .stat-value { color: #27ae60; }
85
- .stat-card.warning .stat-value { color: #f39c12; }
86
- .stat-card.danger .stat-value { color: #e74c3c; }
87
- .stat-card.info .stat-value { color: #3498db; }
432
+ .stat-card.success .stat-value { color: #107c10; }
433
+ .stat-card.warning .stat-value { color: #f7630c; }
434
+ .stat-card.danger .stat-value { color: #d13438; }
435
+ .stat-card.info .stat-value { color: #0078d4; }
88
436
 
89
437
  .section {
90
- background: white;
91
- border-radius: 12px;
92
- padding: 1.5rem;
93
- margin-bottom: 2rem;
94
- box-shadow: 0 2px 8px rgba(0,0,0,0.08);
438
+ background: #ffffff;
439
+ border: 1px solid #edebe9;
440
+ border-radius: 4px;
441
+ padding: 24px;
442
+ margin-bottom: 24px;
443
+ box-shadow: 0 0.3px 0.9px rgba(0,0,0,0.108), 0 1.6px 3.6px rgba(0,0,0,0.132);
95
444
  }
96
445
 
97
446
  .section-header {
98
447
  display: flex;
99
448
  justify-content: space-between;
100
449
  align-items: center;
101
- margin-bottom: 1.5rem;
102
- padding-bottom: 1rem;
103
- border-bottom: 2px solid #ecf0f1;
450
+ margin-bottom: 20px;
451
+ padding-bottom: 16px;
452
+ border-bottom: 1px solid #edebe9;
104
453
  }
105
454
 
106
455
  .section-title {
107
- font-size: 1.5rem;
456
+ font-size: 20px;
108
457
  font-weight: 600;
109
- color: #2c3e50;
458
+ color: #323130;
110
459
  }
111
460
 
461
+ /* Fluent Inputs */
112
462
  .filters {
113
463
  display: flex;
114
- gap: 1rem;
464
+ gap: 12px;
115
465
  flex-wrap: wrap;
116
- margin-bottom: 1.5rem;
466
+ margin-bottom: 20px;
117
467
  }
118
468
 
119
469
  select, input {
120
- padding: 0.5rem 1rem;
121
- border: 1px solid #ddd;
122
- border-radius: 6px;
123
- font-size: 0.95rem;
470
+ padding: 7px 8px;
471
+ border: 1px solid #8a8886;
472
+ border-radius: 2px;
473
+ font-size: 14px;
124
474
  background: white;
475
+ color: #323130;
125
476
  cursor: pointer;
126
- transition: border-color 0.2s;
477
+ transition: all 0.15s ease;
478
+ font-family: 'Segoe UI', sans-serif;
479
+ }
480
+
481
+ select:hover, input:hover {
482
+ border-color: #323130;
127
483
  }
128
484
 
129
- select:hover, select:focus, input:hover, input:focus {
130
- border-color: #667eea;
485
+ select:focus, input:focus {
131
486
  outline: none;
487
+ border-color: #0078d4;
488
+ box-shadow: 0 0 0 1px #0078d4;
132
489
  }
133
490
 
491
+ /* Fluent Buttons */
134
492
  .btn {
135
- padding: 0.5rem 1.25rem;
136
- border: none;
137
- border-radius: 6px;
138
- font-size: 0.95rem;
493
+ padding: 7px 16px;
494
+ border: 1px solid #8a8886;
495
+ border-radius: 2px;
496
+ font-size: 14px;
497
+ font-weight: 600;
139
498
  cursor: pointer;
140
- transition: all 0.2s;
141
- font-weight: 500;
499
+ transition: all 0.1s ease;
500
+ font-family: 'Segoe UI', sans-serif;
501
+ background: white;
502
+ color: #323130;
503
+ }
504
+
505
+ .btn:hover {
506
+ background: #f3f2f1;
507
+ border-color: #323130;
508
+ }
509
+
510
+ .btn:active {
511
+ background: #edebe9;
142
512
  }
143
513
 
144
514
  .btn-primary {
145
- background: #667eea;
515
+ background: #0078d4;
516
+ border-color: #0078d4;
146
517
  color: white;
147
518
  }
148
519
 
149
520
  .btn-primary:hover {
150
- background: #5568d3;
521
+ background: #106ebe;
522
+ border-color: #106ebe;
523
+ }
524
+
525
+ .btn-primary:active {
526
+ background: #005a9e;
151
527
  }
152
528
 
153
529
  .btn-danger {
154
- background: #e74c3c;
530
+ background: #d13438;
531
+ border-color: #d13438;
155
532
  color: white;
156
533
  }
157
534
 
158
535
  .btn-danger:hover {
159
- background: #c0392b;
536
+ background: #a4262c;
537
+ border-color: #a4262c;
160
538
  }
161
539
 
162
540
  .btn-success {
163
- background: #27ae60;
541
+ background: #107c10;
542
+ border-color: #107c10;
164
543
  color: white;
165
544
  }
166
545
 
167
546
  .btn-success:hover {
168
- background: #229954;
547
+ background: #0b6a0b;
548
+ border-color: #0b6a0b;
169
549
  }
170
550
 
171
551
  .btn-small {
172
- padding: 0.375rem 0.75rem;
173
- font-size: 0.875rem;
552
+ padding: 5px 12px;
553
+ font-size: 13px;
174
554
  }
175
555
 
556
+ /* Modern Table */
176
557
  .table-container {
177
558
  overflow-x: auto;
559
+ border-radius: 4px;
178
560
  }
179
561
 
180
562
  table {
@@ -183,108 +565,117 @@ function getDashboardHTML(options) {
183
565
  }
184
566
 
185
567
  th {
186
- background: #f8f9fa;
187
- padding: 1rem;
568
+ background: #f3f2f1;
569
+ padding: 12px 16px;
188
570
  text-align: left;
189
571
  font-weight: 600;
190
- color: #495057;
191
- border-bottom: 2px solid #dee2e6;
192
- font-size: 0.875rem;
193
- text-transform: uppercase;
194
- letter-spacing: 0.5px;
572
+ color: #323130;
573
+ border-bottom: 1px solid #edebe9;
574
+ font-size: 13px;
575
+ text-transform: none;
576
+ letter-spacing: 0;
195
577
  }
196
578
 
197
579
  td {
198
- padding: 1rem;
199
- border-bottom: 1px solid #ecf0f1;
580
+ padding: 12px 16px;
581
+ border-bottom: 1px solid #edebe9;
582
+ font-size: 13px;
200
583
  }
201
584
 
202
585
  tr:hover {
203
- background: #f8f9fa;
586
+ background: #faf9f8;
204
587
  }
205
588
 
589
+ tr:last-child td {
590
+ border-bottom: none;
591
+ }
592
+
593
+ /* Fluent Badges */
206
594
  .badge {
207
595
  display: inline-block;
208
- padding: 0.25rem 0.75rem;
209
- border-radius: 12px;
210
- font-size: 0.75rem;
596
+ padding: 4px 8px;
597
+ border-radius: 2px;
598
+ font-size: 12px;
211
599
  font-weight: 600;
212
- text-transform: uppercase;
213
- letter-spacing: 0.5px;
600
+ text-transform: capitalize;
214
601
  }
215
602
 
216
- .badge-success { background: #d4edda; color: #155724; }
217
- .badge-warning { background: #fff3cd; color: #856404; }
218
- .badge-danger { background: #f8d7da; color: #721c24; }
219
- .badge-info { background: #d1ecf1; color: #0c5460; }
603
+ .badge-success { background: #dff6dd; color: #107c10; }
604
+ .badge-warning { background: #fff4ce; color: #8a5500; }
605
+ .badge-danger { background: #fde7e9; color: #a4262c; }
606
+ .badge-info { background: #deecf9; color: #004e8c; }
220
607
 
608
+ /* Charts */
221
609
  .chart-container {
222
610
  display: grid;
223
- grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
224
- gap: 2rem;
225
- margin-top: 1.5rem;
611
+ grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
612
+ gap: 24px;
613
+ margin-top: 20px;
226
614
  }
227
615
 
228
616
  .chart {
229
- background: #f8f9fa;
230
- padding: 1.5rem;
231
- border-radius: 8px;
617
+ background: #faf9f8;
618
+ padding: 20px;
619
+ border-radius: 4px;
620
+ border: 1px solid #edebe9;
232
621
  }
233
622
 
234
623
  .chart-title {
235
624
  font-weight: 600;
236
- margin-bottom: 1rem;
237
- color: #495057;
625
+ margin-bottom: 16px;
626
+ color: #323130;
627
+ font-size: 14px;
238
628
  }
239
629
 
240
630
  .chart-bar {
241
631
  display: flex;
242
632
  align-items: center;
243
- margin-bottom: 0.75rem;
633
+ margin-bottom: 12px;
244
634
  }
245
635
 
246
636
  .chart-label {
247
637
  min-width: 120px;
248
- font-size: 0.875rem;
249
- color: #6c757d;
638
+ font-size: 13px;
639
+ color: #605e5c;
250
640
  }
251
641
 
252
642
  .chart-bar-bg {
253
643
  flex: 1;
254
- height: 24px;
255
- background: #e9ecef;
256
- border-radius: 4px;
644
+ height: 28px;
645
+ background: #edebe9;
646
+ border-radius: 2px;
257
647
  overflow: hidden;
258
648
  position: relative;
259
649
  }
260
650
 
261
651
  .chart-bar-fill {
262
652
  height: 100%;
263
- background: linear-gradient(90deg, #667eea, #764ba2);
653
+ background: linear-gradient(90deg, #0078d4, #106ebe);
264
654
  transition: width 0.3s ease;
265
655
  }
266
656
 
267
657
  .chart-value {
268
- margin-left: 0.75rem;
658
+ margin-left: 12px;
269
659
  font-weight: 600;
270
- font-size: 0.875rem;
660
+ font-size: 13px;
271
661
  min-width: 40px;
662
+ color: #323130;
272
663
  }
273
664
 
274
665
  .loading {
275
666
  text-align: center;
276
- padding: 3rem;
277
- color: #7f8c8d;
667
+ padding: 48px;
668
+ color: #605e5c;
278
669
  }
279
670
 
280
671
  .spinner {
281
- border: 3px solid #f3f3f3;
282
- border-top: 3px solid #667eea;
672
+ border: 3px solid #edebe9;
673
+ border-top: 3px solid #0078d4;
283
674
  border-radius: 50%;
284
675
  width: 40px;
285
676
  height: 40px;
286
677
  animation: spin 1s linear infinite;
287
- margin: 0 auto 1rem;
678
+ margin: 0 auto 16px;
288
679
  }
289
680
 
290
681
  @keyframes spin {
@@ -293,49 +684,49 @@ function getDashboardHTML(options) {
293
684
  }
294
685
 
295
686
  .error {
296
- background: #f8d7da;
297
- color: #721c24;
298
- padding: 1rem;
299
- border-radius: 8px;
300
- border-left: 4px solid #e74c3c;
687
+ background: #fde7e9;
688
+ color: #a4262c;
689
+ padding: 16px;
690
+ border-radius: 4px;
691
+ border-left: 3px solid #d13438;
301
692
  }
302
693
 
303
694
  .empty-state {
304
695
  text-align: center;
305
- padding: 3rem;
306
- color: #7f8c8d;
696
+ padding: 48px;
697
+ color: #605e5c;
307
698
  }
308
699
 
309
700
  .empty-state-icon {
310
- font-size: 4rem;
311
- margin-bottom: 1rem;
312
- opacity: 0.3;
701
+ font-size: 48px;
702
+ margin-bottom: 16px;
703
+ opacity: 0.5;
313
704
  }
314
705
 
315
706
  .pagination {
316
707
  display: flex;
317
708
  justify-content: center;
318
709
  align-items: center;
319
- gap: 0.5rem;
320
- margin-top: 1.5rem;
710
+ gap: 12px;
711
+ margin-top: 24px;
321
712
  }
322
713
 
323
714
  .pagination button {
324
- padding: 0.5rem 1rem;
715
+ padding: 7px 16px;
325
716
  }
326
717
 
327
718
  .pagination span {
328
- color: #6c757d;
329
- font-size: 0.95rem;
719
+ color: #605e5c;
720
+ font-size: 13px;
330
721
  }
331
722
 
332
723
  code {
333
- background: #f8f9fa;
334
- padding: 0.125rem 0.375rem;
335
- border-radius: 3px;
336
- font-family: 'Monaco', 'Courier New', monospace;
337
- font-size: 0.875rem;
338
- color: #e83e8c;
724
+ background: #f3f2f1;
725
+ padding: 2px 6px;
726
+ border-radius: 2px;
727
+ font-family: 'Consolas', 'Monaco', monospace;
728
+ font-size: 12px;
729
+ color: #d13438;
339
730
  }
340
731
 
341
732
  .job-args {
@@ -343,8 +734,143 @@ function getDashboardHTML(options) {
343
734
  overflow: hidden;
344
735
  text-overflow: ellipsis;
345
736
  white-space: nowrap;
346
- font-family: 'Monaco', 'Courier New', monospace;
347
- font-size: 0.875rem;
737
+ font-family: 'Consolas', 'Monaco', monospace;
738
+ font-size: 12px;
739
+ cursor: pointer;
740
+ padding: 4px 8px;
741
+ border-radius: 2px;
742
+ transition: background 0.1s ease;
743
+ }
744
+
745
+ .job-args:hover {
746
+ background: #deecf9;
747
+ color: #004e8c;
748
+ }
749
+
750
+ /* Args Modal */
751
+ .modal-overlay {
752
+ display: none;
753
+ position: fixed;
754
+ inset: 0;
755
+ background: rgba(0,0,0,0.4);
756
+ z-index: 200;
757
+ align-items: center;
758
+ justify-content: center;
759
+ }
760
+
761
+ .modal-overlay.active {
762
+ display: flex;
763
+ }
764
+
765
+ .modal {
766
+ background: #ffffff;
767
+ border-radius: 4px;
768
+ box-shadow: 0 25.6px 57.6px rgba(0,0,0,0.22), 0 4.8px 14.4px rgba(0,0,0,0.18);
769
+ width: 90%;
770
+ max-width: 640px;
771
+ max-height: 80vh;
772
+ display: flex;
773
+ flex-direction: column;
774
+ animation: modalIn 0.15s ease;
775
+ }
776
+
777
+ @keyframes modalIn {
778
+ from { opacity: 0; transform: scale(0.95); }
779
+ to { opacity: 1; transform: scale(1); }
780
+ }
781
+
782
+ .modal-header {
783
+ display: flex;
784
+ justify-content: space-between;
785
+ align-items: center;
786
+ padding: 16px 24px;
787
+ border-bottom: 1px solid #edebe9;
788
+ }
789
+
790
+ .modal-title {
791
+ font-size: 18px;
792
+ font-weight: 600;
793
+ color: #323130;
794
+ }
795
+
796
+ .modal-close {
797
+ background: none;
798
+ border: none;
799
+ font-size: 20px;
800
+ cursor: pointer;
801
+ color: #605e5c;
802
+ padding: 4px 8px;
803
+ border-radius: 2px;
804
+ }
805
+
806
+ .modal-close:hover {
807
+ background: #f3f2f1;
808
+ color: #201f1e;
809
+ }
810
+
811
+ .modal-body {
812
+ padding: 24px;
813
+ overflow-y: auto;
814
+ flex: 1;
815
+ }
816
+
817
+ .modal-footer {
818
+ display: flex;
819
+ justify-content: flex-end;
820
+ gap: 8px;
821
+ padding: 16px 24px;
822
+ border-top: 1px solid #edebe9;
823
+ }
824
+
825
+ .args-viewer {
826
+ background: #1e1e1e;
827
+ color: #d4d4d4;
828
+ padding: 16px;
829
+ border-radius: 4px;
830
+ font-family: 'Consolas', 'Monaco', monospace;
831
+ font-size: 13px;
832
+ white-space: pre-wrap;
833
+ word-break: break-word;
834
+ line-height: 1.5;
835
+ max-height: 300px;
836
+ overflow-y: auto;
837
+ }
838
+
839
+ .args-editor {
840
+ width: 100%;
841
+ min-height: 200px;
842
+ background: #1e1e1e;
843
+ color: #d4d4d4;
844
+ padding: 16px;
845
+ border-radius: 4px;
846
+ border: 2px solid #0078d4;
847
+ font-family: 'Consolas', 'Monaco', monospace;
848
+ font-size: 13px;
849
+ line-height: 1.5;
850
+ resize: vertical;
851
+ }
852
+
853
+ .args-editor:focus {
854
+ outline: none;
855
+ border-color: #106ebe;
856
+ }
857
+
858
+ .args-error {
859
+ color: #d13438;
860
+ font-size: 12px;
861
+ margin-top: 8px;
862
+ }
863
+
864
+ .copy-feedback {
865
+ color: #107c10;
866
+ font-size: 12px;
867
+ margin-left: 8px;
868
+ opacity: 0;
869
+ transition: opacity 0.2s ease;
870
+ }
871
+
872
+ .copy-feedback.show {
873
+ opacity: 1;
348
874
  }
349
875
 
350
876
  .error-message {
@@ -352,8 +878,8 @@ function getDashboardHTML(options) {
352
878
  overflow: hidden;
353
879
  text-overflow: ellipsis;
354
880
  white-space: nowrap;
355
- color: #e74c3c;
356
- font-size: 0.875rem;
881
+ color: #d13438;
882
+ font-size: 12px;
357
883
  }
358
884
 
359
885
  .refresh-indicator {
@@ -361,8 +887,7 @@ function getDashboardHTML(options) {
361
887
  width: 8px;
362
888
  height: 8px;
363
889
  border-radius: 50%;
364
- background: #27ae60;
365
- margin-left: 0.5rem;
890
+ background: #107c10;
366
891
  animation: pulse 2s infinite;
367
892
  }
368
893
 
@@ -373,14 +898,29 @@ function getDashboardHTML(options) {
373
898
 
374
899
  .actions {
375
900
  display: flex;
376
- gap: 0.5rem;
901
+ gap: 8px;
377
902
  }
378
903
  </style>
379
904
  </head>
380
905
  <body>
381
906
  <div class="header">
382
- <h1>${options.title}</h1>
383
- <p>Real-time job queue monitoring and management <span class="refresh-indicator"></span></p>
907
+ <div class="header-content">
908
+ <div class="header-left">
909
+ <div class="logo">📊</div>
910
+ <h1>${options.title}</h1>
911
+ </div>
912
+ <div class="header-right">
913
+ ${userEmail ? `
914
+ <div class="user-info">
915
+ <div class="user-avatar">${userEmail.charAt(0).toUpperCase()}</div>
916
+ <span>${userEmail}</span>
917
+ </div>
918
+ ` : ''}
919
+ <form method="POST" action="${options.basePath}/logout" style="display: inline;">
920
+ <button type="submit" class="logout-btn">Sign out</button>
921
+ </form>
922
+ </div>
923
+ </div>
384
924
  </div>
385
925
 
386
926
  <div class="container">
@@ -408,6 +948,7 @@ function getDashboardHTML(options) {
408
948
  <div class="section">
409
949
  <div class="section-header">
410
950
  <h2 class="section-title">Analytics</h2>
951
+ <span class="refresh-indicator"></span>
411
952
  </div>
412
953
  <div class="chart-container">
413
954
  <div class="chart">
@@ -465,10 +1006,38 @@ function getDashboardHTML(options) {
465
1006
  </div>
466
1007
  </div>
467
1008
 
1009
+ <!-- Args Modal -->
1010
+ <div class="modal-overlay" id="args-modal">
1011
+ <div class="modal">
1012
+ <div class="modal-header">
1013
+ <span class="modal-title" id="args-modal-title">Job Arguments</span>
1014
+ <button class="modal-close" onclick="closeArgsModal()">&times;</button>
1015
+ </div>
1016
+ <div class="modal-body">
1017
+ <div id="args-view-mode">
1018
+ <pre class="args-viewer" id="args-content"></pre>
1019
+ </div>
1020
+ <div id="args-edit-mode" style="display:none">
1021
+ <textarea class="args-editor" id="args-editor"></textarea>
1022
+ <div class="args-error" id="args-error"></div>
1023
+ </div>
1024
+ </div>
1025
+ <div class="modal-footer">
1026
+ <span class="copy-feedback" id="copy-feedback">Copied!</span>
1027
+ <button class="btn" id="btn-copy" onclick="copyArgs()">Copy</button>
1028
+ <button class="btn" id="btn-edit" onclick="enterEditMode()">Edit</button>
1029
+ <button class="btn btn-primary" id="btn-save" style="display:none" onclick="saveArgs()">Save</button>
1030
+ <button class="btn" id="btn-cancel-edit" style="display:none" onclick="cancelEdit()">Cancel</button>
1031
+ </div>
1032
+ </div>
1033
+ </div>
1034
+
468
1035
  <script>
469
1036
  const REFRESH_INTERVAL = ${options.refreshInterval};
470
1037
  let currentPage = 0;
471
1038
  const pageSize = 50;
1039
+ let currentModalJobId = null;
1040
+ let currentModalArgs = null;
472
1041
 
473
1042
  // Load initial data
474
1043
  loadStats();
@@ -629,7 +1198,7 @@ function getDashboardHTML(options) {
629
1198
  <td><span class="badge badge-\${statusBadge}">\${status}</span></td>
630
1199
  <td>\${formatDate(runAt)}</td>
631
1200
  <td>\${job.errorCount > 0 ? '<span class="badge badge-danger">' + job.errorCount + '</span>' : '-'}</td>
632
- <td class="job-args">\${escapeHtml(JSON.stringify(job.args))}</td>
1201
+ <td class="job-args" data-job-id="\${job.id}" data-args="\${escapeAttr(JSON.stringify(job.args))}" onclick="openArgsFromEl(this)" title="Click to view/edit">\${escapeHtml(JSON.stringify(job.args))}</td>
633
1202
  <td>
634
1203
  <div class="actions">
635
1204
  \${job.errorCount > 0 ?
@@ -645,9 +1214,9 @@ function getDashboardHTML(options) {
645
1214
  </table>
646
1215
  </div>
647
1216
  <div class="pagination">
648
- <button class="btn btn-primary" onclick="previousPage()" \${currentPage === 0 ? 'disabled' : ''}>Previous</button>
1217
+ <button class="btn" onclick="previousPage()" \${currentPage === 0 ? 'disabled' : ''}>Previous</button>
649
1218
  <span>Page \${currentPage + 1} of \${Math.ceil(total / pageSize)}</span>
650
- <button class="btn btn-primary" onclick="nextPage()" \${(currentPage + 1) * pageSize >= total ? 'disabled' : ''}>Next</button>
1219
+ <button class="btn" onclick="nextPage()" \${(currentPage + 1) * pageSize >= total ? 'disabled' : ''}>Next</button>
651
1220
  </div>
652
1221
  \`;
653
1222
 
@@ -778,6 +1347,115 @@ function getDashboardHTML(options) {
778
1347
  div.textContent = text;
779
1348
  return div.innerHTML;
780
1349
  }
1350
+
1351
+ function escapeAttr(text) {
1352
+ return text.replace(/&/g, '&amp;').replace(/'/g, '&#39;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
1353
+ }
1354
+
1355
+ function openArgsFromEl(el) {
1356
+ var jobId = parseInt(el.getAttribute('data-job-id'));
1357
+ var args = JSON.parse(el.getAttribute('data-args'));
1358
+ openArgsModal(jobId, args);
1359
+ }
1360
+
1361
+ function openArgsModal(jobId, args) {
1362
+ currentModalJobId = jobId;
1363
+ currentModalArgs = args;
1364
+ document.getElementById('args-modal-title').textContent = 'Job #' + jobId + ' Arguments';
1365
+ document.getElementById('args-content').textContent = JSON.stringify(args, null, 2);
1366
+ document.getElementById('args-view-mode').style.display = '';
1367
+ document.getElementById('args-edit-mode').style.display = 'none';
1368
+ document.getElementById('btn-copy').style.display = '';
1369
+ document.getElementById('btn-edit').style.display = '';
1370
+ document.getElementById('btn-save').style.display = 'none';
1371
+ document.getElementById('btn-cancel-edit').style.display = 'none';
1372
+ document.getElementById('args-error').textContent = '';
1373
+ document.getElementById('args-modal').classList.add('active');
1374
+ }
1375
+
1376
+ function closeArgsModal() {
1377
+ document.getElementById('args-modal').classList.remove('active');
1378
+ currentModalJobId = null;
1379
+ currentModalArgs = null;
1380
+ }
1381
+
1382
+ function copyArgs() {
1383
+ const text = JSON.stringify(currentModalArgs, null, 2);
1384
+ navigator.clipboard.writeText(text).then(function() {
1385
+ var fb = document.getElementById('copy-feedback');
1386
+ fb.classList.add('show');
1387
+ setTimeout(function() { fb.classList.remove('show'); }, 1500);
1388
+ });
1389
+ }
1390
+
1391
+ function enterEditMode() {
1392
+ document.getElementById('args-view-mode').style.display = 'none';
1393
+ document.getElementById('args-edit-mode').style.display = '';
1394
+ document.getElementById('args-editor').value = JSON.stringify(currentModalArgs, null, 2);
1395
+ document.getElementById('btn-copy').style.display = 'none';
1396
+ document.getElementById('btn-edit').style.display = 'none';
1397
+ document.getElementById('btn-save').style.display = '';
1398
+ document.getElementById('btn-cancel-edit').style.display = '';
1399
+ document.getElementById('args-error').textContent = '';
1400
+ document.getElementById('args-editor').focus();
1401
+ }
1402
+
1403
+ function cancelEdit() {
1404
+ document.getElementById('args-view-mode').style.display = '';
1405
+ document.getElementById('args-edit-mode').style.display = 'none';
1406
+ document.getElementById('btn-copy').style.display = '';
1407
+ document.getElementById('btn-edit').style.display = '';
1408
+ document.getElementById('btn-save').style.display = 'none';
1409
+ document.getElementById('btn-cancel-edit').style.display = 'none';
1410
+ document.getElementById('args-error').textContent = '';
1411
+ }
1412
+
1413
+ async function saveArgs() {
1414
+ var raw = document.getElementById('args-editor').value;
1415
+ var parsed;
1416
+ try {
1417
+ parsed = JSON.parse(raw);
1418
+ } catch (e) {
1419
+ document.getElementById('args-error').textContent = 'Invalid JSON: ' + e.message;
1420
+ return;
1421
+ }
1422
+
1423
+ if (!Array.isArray(parsed)) {
1424
+ document.getElementById('args-error').textContent = 'Arguments must be a JSON array.';
1425
+ return;
1426
+ }
1427
+
1428
+ try {
1429
+ var response = await fetch(\`${options.basePath}/api/jobs/\${currentModalJobId}/args\`, {
1430
+ method: 'PUT',
1431
+ headers: { 'Content-Type': 'application/json' },
1432
+ body: JSON.stringify({ args: parsed })
1433
+ });
1434
+
1435
+ if (response.ok) {
1436
+ currentModalArgs = parsed;
1437
+ document.getElementById('args-content').textContent = JSON.stringify(parsed, null, 2);
1438
+ cancelEdit();
1439
+ loadJobs();
1440
+ loadStats();
1441
+ } else {
1442
+ var data = await response.json();
1443
+ document.getElementById('args-error').textContent = data.error || 'Failed to update arguments.';
1444
+ }
1445
+ } catch (error) {
1446
+ document.getElementById('args-error').textContent = 'Network error. Please try again.';
1447
+ }
1448
+ }
1449
+
1450
+ // Close modal on overlay click
1451
+ document.getElementById('args-modal').addEventListener('click', function(e) {
1452
+ if (e.target === this) closeArgsModal();
1453
+ });
1454
+
1455
+ // Close modal on Escape key
1456
+ document.addEventListener('keydown', function(e) {
1457
+ if (e.key === 'Escape') closeArgsModal();
1458
+ });
781
1459
  </script>
782
1460
  </body>
783
1461
  </html>