@openqa/cli 1.3.1 → 1.3.3
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/dist/cli/index.js +1197 -378
- package/dist/cli/server.js +1197 -378
- package/package.json +1 -1
package/dist/cli/server.js
CHANGED
|
@@ -326,15 +326,50 @@ async function startWebServer() {
|
|
|
326
326
|
res.status(500).json({ success: false, error: error.message });
|
|
327
327
|
}
|
|
328
328
|
});
|
|
329
|
-
app.post("/api/intervention/:id", (req, res) => {
|
|
329
|
+
app.post("/api/intervention/:id", async (req, res) => {
|
|
330
330
|
const { id } = req.params;
|
|
331
331
|
const { response } = req.body;
|
|
332
|
-
console.log(`Intervention ${id}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
332
|
+
console.log(`Intervention ${id} ${response} by user`);
|
|
333
|
+
res.json({ success: true, message: `Intervention ${response}d` });
|
|
334
|
+
});
|
|
335
|
+
app.post("/api/test-connection", async (req, res) => {
|
|
336
|
+
try {
|
|
337
|
+
const cfg2 = config.getConfigSync();
|
|
338
|
+
if (cfg2.saas.url) {
|
|
339
|
+
const controller = new AbortController();
|
|
340
|
+
const timeoutId = setTimeout(() => controller.abort(), 1e4);
|
|
341
|
+
try {
|
|
342
|
+
const response = await fetch(cfg2.saas.url, {
|
|
343
|
+
method: "HEAD",
|
|
344
|
+
signal: controller.signal,
|
|
345
|
+
headers: cfg2.saas.authType === "basic" && cfg2.saas.username && cfg2.saas.password ? {
|
|
346
|
+
"Authorization": "Basic " + Buffer.from(`${cfg2.saas.username}:${cfg2.saas.password}`).toString("base64")
|
|
347
|
+
} : {}
|
|
348
|
+
});
|
|
349
|
+
clearTimeout(timeoutId);
|
|
350
|
+
if (response.ok) {
|
|
351
|
+
res.json({ success: true, message: "SaaS connection successful" });
|
|
352
|
+
} else {
|
|
353
|
+
res.json({ success: false, message: "SaaS connection failed" });
|
|
354
|
+
}
|
|
355
|
+
} catch (fetchError) {
|
|
356
|
+
clearTimeout(timeoutId);
|
|
357
|
+
throw fetchError;
|
|
358
|
+
}
|
|
359
|
+
} else {
|
|
360
|
+
res.json({ success: false, message: "No SaaS URL configured" });
|
|
361
|
+
}
|
|
362
|
+
} catch (error) {
|
|
363
|
+
res.json({ success: false, message: "Connection error: " + (error.message || String(error)) });
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
app.post("/api/start", async (req, res) => {
|
|
367
|
+
try {
|
|
368
|
+
console.log("Starting agent session...");
|
|
369
|
+
res.json({ success: true, message: "Session started" });
|
|
370
|
+
} catch (error) {
|
|
371
|
+
res.json({ success: false, message: "Failed to start session: " + (error.message || String(error)) });
|
|
372
|
+
}
|
|
338
373
|
});
|
|
339
374
|
app.get("/api/tasks", async (req, res) => {
|
|
340
375
|
const tasks = [
|
|
@@ -397,158 +432,482 @@ async function startWebServer() {
|
|
|
397
432
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
398
433
|
<script src="https://cdn.jsdelivr.net/npm/@xyflow/react@11/dist/umd/index.js"></script>
|
|
399
434
|
<style>
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
435
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
436
|
+
|
|
437
|
+
body {
|
|
438
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
439
|
+
background: #0a0a0a;
|
|
440
|
+
color: #ffffff;
|
|
441
|
+
min-height: 100vh;
|
|
442
|
+
overflow-x: hidden;
|
|
443
|
+
line-height: 1.6;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
.dashboard-container {
|
|
447
|
+
max-width: 1920px;
|
|
448
|
+
margin: 0 auto;
|
|
449
|
+
padding: 20px;
|
|
450
|
+
min-height: 100vh;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
.dashboard-header {
|
|
454
|
+
display: flex;
|
|
455
|
+
justify-content: space-between;
|
|
456
|
+
align-items: center;
|
|
457
|
+
margin-bottom: 32px;
|
|
458
|
+
padding: 24px 32px;
|
|
459
|
+
background: linear-gradient(135deg, #1a1a1a, #2a2a2a);
|
|
460
|
+
border-radius: 16px;
|
|
461
|
+
border: 1px solid #333333;
|
|
462
|
+
backdrop-filter: blur(10px);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
.dashboard-title {
|
|
466
|
+
font-size: 32px;
|
|
467
|
+
font-weight: 700;
|
|
468
|
+
color: #f97316;
|
|
469
|
+
margin: 0;
|
|
470
|
+
letter-spacing: -0.5px;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
.nav {
|
|
474
|
+
display: flex;
|
|
475
|
+
gap: 8px;
|
|
476
|
+
background: rgba(255, 255, 255, 0.05);
|
|
477
|
+
padding: 4px;
|
|
478
|
+
border-radius: 12px;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.nav-links a {
|
|
482
|
+
color: #9ca3af;
|
|
483
|
+
text-decoration: none;
|
|
484
|
+
font-weight: 500;
|
|
485
|
+
padding: 12px 24px;
|
|
486
|
+
border-radius: 8px;
|
|
487
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
488
|
+
display: flex;
|
|
489
|
+
align-items: center;
|
|
490
|
+
gap: 8px;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
.nav-links a:hover, .nav-links a.active {
|
|
494
|
+
color: #ffffff;
|
|
495
|
+
background: #f97316;
|
|
496
|
+
transform: translateY(-1px);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
.connection-status {
|
|
500
|
+
padding: 8px 16px;
|
|
501
|
+
border-radius: 20px;
|
|
502
|
+
font-size: 12px;
|
|
503
|
+
font-weight: 600;
|
|
504
|
+
text-transform: uppercase;
|
|
505
|
+
letter-spacing: 0.5px;
|
|
506
|
+
}
|
|
507
|
+
|
|
406
508
|
.status.running { background: linear-gradient(135deg, #10b981, #059669); color: white; }
|
|
407
509
|
.status.idle { background: linear-gradient(135deg, #f59e0b, #d97706); color: white; }
|
|
408
510
|
.status.error { background: linear-gradient(135deg, #ef4444, #dc2626); color: white; }
|
|
409
511
|
.status.paused { background: linear-gradient(135deg, #64748b, #475569); color: white; }
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
.
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
border
|
|
422
|
-
|
|
423
|
-
|
|
512
|
+
|
|
513
|
+
/* Metrics Grid */
|
|
514
|
+
.metrics-grid {
|
|
515
|
+
display: grid;
|
|
516
|
+
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
517
|
+
gap: 24px;
|
|
518
|
+
margin-bottom: 32px;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
.metric-card {
|
|
522
|
+
background: linear-gradient(145deg, #1a1a1a, #2d2d2d);
|
|
523
|
+
border: 1px solid #333333;
|
|
524
|
+
border-radius: 16px;
|
|
525
|
+
padding: 28px 24px;
|
|
424
526
|
position: relative;
|
|
425
527
|
overflow: hidden;
|
|
528
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
529
|
+
min-height: 160px;
|
|
530
|
+
display: flex;
|
|
531
|
+
flex-direction: column;
|
|
532
|
+
justify-content: space-between;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
.metric-card::before {
|
|
536
|
+
content: '';
|
|
537
|
+
position: absolute;
|
|
538
|
+
top: 0;
|
|
539
|
+
left: 0;
|
|
540
|
+
right: 0;
|
|
541
|
+
height: 4px;
|
|
542
|
+
background: linear-gradient(90deg, #f97316, #ea580c, #f59e0b);
|
|
543
|
+
background-size: 200% 100%;
|
|
544
|
+
animation: shimmer 3s ease-in-out infinite;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
@keyframes shimmer {
|
|
548
|
+
0%, 100% { background-position: -200% 0; }
|
|
549
|
+
50% { background-position: 200% 0; }
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
.metric-card:hover {
|
|
553
|
+
transform: translateY(-4px);
|
|
554
|
+
border-color: #f97316;
|
|
555
|
+
box-shadow: 0 20px 40px rgba(249, 115, 22, 0.15);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
.metric-header {
|
|
559
|
+
display: flex;
|
|
560
|
+
justify-content: space-between;
|
|
561
|
+
align-items: flex-start;
|
|
562
|
+
margin-bottom: 16px;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
.metric-label {
|
|
566
|
+
color: #9ca3af;
|
|
567
|
+
font-size: 14px;
|
|
568
|
+
font-weight: 500;
|
|
569
|
+
text-transform: uppercase;
|
|
570
|
+
letter-spacing: 0.5px;
|
|
426
571
|
}
|
|
427
|
-
|
|
428
|
-
.metric-
|
|
429
|
-
|
|
430
|
-
|
|
572
|
+
|
|
573
|
+
.metric-icon {
|
|
574
|
+
width: 40px;
|
|
575
|
+
height: 40px;
|
|
576
|
+
background: rgba(249, 115, 22, 0.1);
|
|
577
|
+
border-radius: 12px;
|
|
578
|
+
display: flex;
|
|
579
|
+
align-items: center;
|
|
580
|
+
justify-content: center;
|
|
581
|
+
font-size: 20px;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
.metric-value {
|
|
585
|
+
font-size: 42px;
|
|
586
|
+
font-weight: 700;
|
|
587
|
+
color: #f97316;
|
|
588
|
+
margin: 8px 0;
|
|
589
|
+
line-height: 1;
|
|
590
|
+
background: linear-gradient(135deg, #f97316, #fbbf24);
|
|
591
|
+
-webkit-background-clip: text;
|
|
592
|
+
-webkit-text-fill-color: transparent;
|
|
593
|
+
background-clip: text;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
.metric-change {
|
|
597
|
+
font-size: 13px;
|
|
598
|
+
font-weight: 600;
|
|
599
|
+
display: flex;
|
|
600
|
+
align-items: center;
|
|
601
|
+
gap: 4px;
|
|
602
|
+
}
|
|
603
|
+
|
|
431
604
|
.metric-change.positive { color: #10b981; }
|
|
432
605
|
.metric-change.negative { color: #ef4444; }
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
.
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
606
|
+
|
|
607
|
+
/* Main Content Grid */
|
|
608
|
+
.main-grid {
|
|
609
|
+
display: grid;
|
|
610
|
+
grid-template-columns: 1fr 1fr;
|
|
611
|
+
gap: 24px;
|
|
612
|
+
margin-bottom: 32px;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
.card {
|
|
616
|
+
background: linear-gradient(145deg, #1a1a1a, #2d2d2d);
|
|
617
|
+
border: 1px solid #333333;
|
|
618
|
+
border-radius: 16px;
|
|
619
|
+
padding: 28px;
|
|
620
|
+
position: relative;
|
|
621
|
+
overflow: hidden;
|
|
622
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
.card:hover {
|
|
626
|
+
border-color: #444444;
|
|
627
|
+
transform: translateY(-2px);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
.card-header {
|
|
631
|
+
display: flex;
|
|
632
|
+
justify-content: space-between;
|
|
633
|
+
align-items: center;
|
|
634
|
+
margin-bottom: 24px;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
.card-title {
|
|
638
|
+
font-size: 20px;
|
|
639
|
+
font-weight: 600;
|
|
640
|
+
color: #ffffff;
|
|
641
|
+
margin: 0;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
.tabs {
|
|
645
|
+
display: flex;
|
|
646
|
+
gap: 4px;
|
|
647
|
+
background: rgba(255, 255, 255, 0.05);
|
|
648
|
+
padding: 4px;
|
|
649
|
+
border-radius: 12px;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
.tab {
|
|
653
|
+
padding: 10px 20px;
|
|
654
|
+
background: transparent;
|
|
655
|
+
border: none;
|
|
656
|
+
border-radius: 8px;
|
|
657
|
+
color: #9ca3af;
|
|
658
|
+
font-weight: 500;
|
|
659
|
+
cursor: pointer;
|
|
660
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
.tab.active {
|
|
664
|
+
background: #f97316;
|
|
665
|
+
color: white;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
.tab:hover:not(.active) {
|
|
669
|
+
color: #ffffff;
|
|
670
|
+
background: rgba(255, 255, 255, 0.1);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
.chart-container {
|
|
674
|
+
position: relative;
|
|
675
|
+
height: 320px;
|
|
676
|
+
margin: 20px 0;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
.hierarchy-container {
|
|
680
|
+
height: 420px;
|
|
681
|
+
border: 1px solid #333333;
|
|
682
|
+
border-radius: 12px;
|
|
683
|
+
background: #0a0a0a;
|
|
684
|
+
overflow: hidden;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/* Activity Feed */
|
|
688
|
+
.activity-feed {
|
|
689
|
+
max-height: 400px;
|
|
690
|
+
overflow-y: auto;
|
|
691
|
+
padding-right: 8px;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
.activity-feed::-webkit-scrollbar {
|
|
695
|
+
width: 6px;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
.activity-feed::-webkit-scrollbar-track {
|
|
699
|
+
background: rgba(255, 255, 255, 0.05);
|
|
700
|
+
border-radius: 3px;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
.activity-feed::-webkit-scrollbar-thumb {
|
|
704
|
+
background: #f97316;
|
|
705
|
+
border-radius: 3px;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
.activity-item {
|
|
709
|
+
background: rgba(255, 255, 255, 0.03);
|
|
710
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
440
711
|
border-left: 4px solid #f97316;
|
|
712
|
+
border-radius: 12px;
|
|
713
|
+
padding: 16px 20px;
|
|
714
|
+
margin-bottom: 12px;
|
|
441
715
|
font-size: 14px;
|
|
442
|
-
transition: all 0.
|
|
716
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
717
|
+
position: relative;
|
|
718
|
+
overflow: hidden;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
.activity-item::before {
|
|
722
|
+
content: '';
|
|
723
|
+
position: absolute;
|
|
724
|
+
top: 0;
|
|
725
|
+
left: 0;
|
|
726
|
+
right: 0;
|
|
727
|
+
height: 1px;
|
|
728
|
+
background: linear-gradient(90deg, transparent, rgba(249, 115, 22, 0.3), transparent);
|
|
729
|
+
opacity: 0;
|
|
730
|
+
transition: opacity 0.3s;
|
|
443
731
|
}
|
|
444
|
-
|
|
732
|
+
|
|
733
|
+
.activity-item:hover {
|
|
734
|
+
background: rgba(255, 255, 255, 0.06);
|
|
735
|
+
border-left-color: #fbbf24;
|
|
736
|
+
transform: translateX(8px);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
.activity-item:hover::before {
|
|
740
|
+
opacity: 1;
|
|
741
|
+
}
|
|
742
|
+
|
|
445
743
|
.activity-item.error { border-left-color: #ef4444; }
|
|
446
744
|
.activity-item.success { border-left-color: #10b981; }
|
|
447
745
|
.activity-item.warning { border-left-color: #f59e0b; }
|
|
448
|
-
|
|
449
|
-
.
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
746
|
+
|
|
747
|
+
.activity-time {
|
|
748
|
+
color: #6b7280;
|
|
749
|
+
font-size: 12px;
|
|
750
|
+
margin-top: 8px;
|
|
751
|
+
display: flex;
|
|
752
|
+
align-items: center;
|
|
753
|
+
gap: 4px;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/* Buttons */
|
|
757
|
+
.btn {
|
|
758
|
+
background: linear-gradient(135deg, #f97316, #ea580c);
|
|
759
|
+
color: white;
|
|
760
|
+
border: none;
|
|
761
|
+
padding: 12px 24px;
|
|
762
|
+
border-radius: 10px;
|
|
763
|
+
cursor: pointer;
|
|
764
|
+
font-size: 14px;
|
|
765
|
+
font-weight: 600;
|
|
766
|
+
margin: 4px;
|
|
767
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
455
768
|
position: relative;
|
|
769
|
+
overflow: hidden;
|
|
456
770
|
}
|
|
457
|
-
|
|
458
|
-
.
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
771
|
+
|
|
772
|
+
.btn::before {
|
|
773
|
+
content: '';
|
|
774
|
+
position: absolute;
|
|
775
|
+
top: 0;
|
|
776
|
+
left: -100%;
|
|
777
|
+
width: 100%;
|
|
778
|
+
height: 100%;
|
|
779
|
+
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
|
780
|
+
transition: left 0.5s;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
.btn:hover {
|
|
784
|
+
transform: translateY(-2px);
|
|
785
|
+
box-shadow: 0 8px 24px rgba(249, 115, 22, 0.3);
|
|
470
786
|
}
|
|
471
|
-
|
|
787
|
+
|
|
788
|
+
.btn:hover::before {
|
|
789
|
+
left: 100%;
|
|
790
|
+
}
|
|
791
|
+
|
|
472
792
|
.btn-success { background: linear-gradient(135deg, #10b981, #059669); }
|
|
473
|
-
.btn-success:hover { box-shadow: 0
|
|
793
|
+
.btn-success:hover { box-shadow: 0 8px 24px rgba(16, 185, 129, 0.3); }
|
|
474
794
|
.btn-danger { background: linear-gradient(135deg, #ef4444, #dc2626); }
|
|
475
|
-
.btn-danger:hover { box-shadow: 0
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
.
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
795
|
+
.btn-danger:hover { box-shadow: 0 8px 24px rgba(239, 68, 68, 0.3); }
|
|
796
|
+
|
|
797
|
+
/* Performance bars */
|
|
798
|
+
.performance-bar {
|
|
799
|
+
height: 6px;
|
|
800
|
+
background: rgba(255, 255, 255, 0.1);
|
|
801
|
+
border-radius: 3px;
|
|
802
|
+
overflow: hidden;
|
|
803
|
+
margin: 8px 0;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
.performance-fill {
|
|
807
|
+
height: 100%;
|
|
808
|
+
background: linear-gradient(90deg, #10b981, #f97316);
|
|
809
|
+
transition: width 1s cubic-bezier(0.4, 0, 0.2, 1);
|
|
810
|
+
border-radius: 3px;
|
|
491
811
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
812
|
+
|
|
813
|
+
/* Responsive */
|
|
814
|
+
@media (max-width: 1200px) {
|
|
815
|
+
.main-grid {
|
|
816
|
+
grid-template-columns: 1fr;
|
|
817
|
+
}
|
|
498
818
|
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
819
|
+
|
|
820
|
+
@media (max-width: 768px) {
|
|
821
|
+
.dashboard-container {
|
|
822
|
+
padding: 16px;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
.metrics-grid {
|
|
826
|
+
grid-template-columns: 1fr;
|
|
827
|
+
gap: 16px;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
.dashboard-header {
|
|
831
|
+
flex-direction: column;
|
|
832
|
+
gap: 16px;
|
|
833
|
+
padding: 20px;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
.dashboard-title {
|
|
837
|
+
font-size: 24px;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
.metric-value {
|
|
841
|
+
font-size: 32px;
|
|
842
|
+
}
|
|
503
843
|
}
|
|
844
|
+
|
|
845
|
+
/* Loading animation */
|
|
846
|
+
.pulse { animation: pulse 2s infinite; }
|
|
847
|
+
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.6; } }
|
|
848
|
+
</style>
|
|
504
849
|
</style>
|
|
505
850
|
</head>
|
|
506
851
|
<body>
|
|
507
|
-
<div class="
|
|
508
|
-
<
|
|
509
|
-
<
|
|
510
|
-
<
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
<div class="metric-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
<div class="metric-
|
|
852
|
+
<div class="dashboard-container">
|
|
853
|
+
<header class="dashboard-header">
|
|
854
|
+
<h1 class="dashboard-title">\u{1F916} OpenQA Professional Dashboard</h1>
|
|
855
|
+
<nav class="nav">
|
|
856
|
+
<div class="nav-links">
|
|
857
|
+
<a href="/" class="active">\u{1F4CA} Dashboard</a>
|
|
858
|
+
<a href="/kanban">\u{1F4CB} Kanban</a>
|
|
859
|
+
<a href="/config">\u2699\uFE0F Config</a>
|
|
860
|
+
</div>
|
|
861
|
+
<span id="connection-status" class="connection-status status idle">\u{1F50C} Connecting...</span>
|
|
862
|
+
</nav>
|
|
863
|
+
</header>
|
|
864
|
+
|
|
865
|
+
<!-- Key Metrics -->
|
|
866
|
+
<div class="metrics-grid">
|
|
867
|
+
<div class="metric-card">
|
|
868
|
+
<div class="metric-header">
|
|
869
|
+
<div class="metric-label">\u{1F916} Active Agents</div>
|
|
870
|
+
<div class="metric-icon">\u{1F916}</div>
|
|
871
|
+
</div>
|
|
872
|
+
<div class="metric-value" id="active-agents">0</div>
|
|
873
|
+
<div class="metric-change positive">\u2191 2 from last hour</div>
|
|
874
|
+
</div>
|
|
875
|
+
<div class="metric-card">
|
|
876
|
+
<div class="metric-header">
|
|
877
|
+
<div class="metric-label">\u{1F4CB} Total Actions</div>
|
|
878
|
+
<div class="metric-icon">\u{1F4CB}</div>
|
|
879
|
+
</div>
|
|
880
|
+
<div class="metric-value" id="total-actions">0</div>
|
|
881
|
+
<div class="metric-change positive">\u2191 12% increase</div>
|
|
882
|
+
</div>
|
|
883
|
+
<div class="metric-card">
|
|
884
|
+
<div class="metric-header">
|
|
885
|
+
<div class="metric-label">\u{1F41B} Bugs Found</div>
|
|
886
|
+
<div class="metric-icon">\u{1F41B}</div>
|
|
887
|
+
</div>
|
|
888
|
+
<div class="metric-value" id="bugs-found">0</div>
|
|
889
|
+
<div class="metric-change negative">\u2193 3 from yesterday</div>
|
|
890
|
+
</div>
|
|
891
|
+
<div class="metric-card">
|
|
892
|
+
<div class="metric-header">
|
|
893
|
+
<div class="metric-label">\u26A1 Success Rate</div>
|
|
894
|
+
<div class="metric-icon">\u26A1</div>
|
|
895
|
+
</div>
|
|
896
|
+
<div class="metric-value" id="success-rate">0%</div>
|
|
897
|
+
<div class="metric-change positive">\u2191 5% improvement</div>
|
|
898
|
+
</div>
|
|
539
899
|
</div>
|
|
540
|
-
</div>
|
|
541
900
|
|
|
542
901
|
<!-- Charts and Hierarchy -->
|
|
543
|
-
<div class="grid
|
|
902
|
+
<div class="main-grid">
|
|
544
903
|
<div class="card">
|
|
545
904
|
<div class="card-header">
|
|
546
905
|
<h2 class="card-title">\u{1F4C8} Performance Metrics</h2>
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
906
|
+
<div class="tabs">
|
|
907
|
+
<div class="tab active" onclick="switchTab('performance')">Performance</div>
|
|
908
|
+
<div class="tab" onclick="switchTab('activity')">Activity</div>
|
|
909
|
+
<div class="tab" onclick="switchTab('errors')">Error Rate</div>
|
|
910
|
+
</div>
|
|
552
911
|
</div>
|
|
553
912
|
<div class="chart-container">
|
|
554
913
|
<canvas id="performanceChart"></canvas>
|
|
@@ -573,36 +932,36 @@ async function startWebServer() {
|
|
|
573
932
|
<div class="card">
|
|
574
933
|
<div class="card-header">
|
|
575
934
|
<h2 class="card-title">\u{1F916} Agent Details</h2>
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
935
|
+
<div class="tabs">
|
|
936
|
+
<div class="tab active" onclick="switchAgentTab('active')">Active Agents</div>
|
|
937
|
+
<div class="tab" onclick="switchAgentTab('specialists')">Specialists</div>
|
|
938
|
+
<div class="tab" onclick="switchAgentTab('performance')">Performance</div>
|
|
939
|
+
</div>
|
|
581
940
|
</div>
|
|
582
941
|
<div id="active-agents-content" class="tab-content active">
|
|
583
942
|
<div id="active-agents-list">
|
|
584
|
-
<p style="color: #
|
|
943
|
+
<p style="color: #9ca3af;">Loading agents...</p>
|
|
585
944
|
</div>
|
|
586
945
|
</div>
|
|
587
946
|
<div id="specialists-content" class="tab-content">
|
|
588
947
|
<div id="specialists-list">
|
|
589
|
-
<p style="color: #
|
|
948
|
+
<p style="color: #9ca3af;">No specialists active</p>
|
|
590
949
|
</div>
|
|
591
950
|
</div>
|
|
592
951
|
<div id="performance-content" class="tab-content">
|
|
593
952
|
<div id="performance-metrics">
|
|
594
|
-
<p style="color: #
|
|
953
|
+
<p style="color: #9ca3af;">Performance data loading...</p>
|
|
595
954
|
</div>
|
|
596
955
|
</div>
|
|
597
956
|
</div>
|
|
598
957
|
|
|
599
958
|
<!-- Activity and Interventions -->
|
|
600
|
-
<div class="grid
|
|
959
|
+
<div class="main-grid">
|
|
601
960
|
<div class="card">
|
|
602
961
|
<div class="card-header">
|
|
603
962
|
<h2 class="card-title">\u26A1 Recent Activity</h2>
|
|
604
963
|
</div>
|
|
605
|
-
<div
|
|
964
|
+
<div class="activity-feed" id="recent-activities">
|
|
606
965
|
<div class="activity-item">
|
|
607
966
|
<div>\u{1F504} Waiting for agent activity...</div>
|
|
608
967
|
<div class="activity-time">System ready</div>
|
|
@@ -614,20 +973,20 @@ async function startWebServer() {
|
|
|
614
973
|
<div class="card-header">
|
|
615
974
|
<h2 class="card-title">\u{1F6A8} Human Interventions</h2>
|
|
616
975
|
</div>
|
|
617
|
-
<div
|
|
618
|
-
<p style="color: #
|
|
976
|
+
<div class="activity-feed" id="interventions-list">
|
|
977
|
+
<p style="color: #9ca3af;">No interventions required</p>
|
|
619
978
|
</div>
|
|
620
979
|
</div>
|
|
621
980
|
</div>
|
|
622
981
|
|
|
623
982
|
<!-- Tasks and Issues -->
|
|
624
|
-
<div class="grid
|
|
983
|
+
<div class="main-grid">
|
|
625
984
|
<div class="card">
|
|
626
985
|
<div class="card-header">
|
|
627
986
|
<h2 class="card-title">\u{1F4DD} Current Tasks</h2>
|
|
628
987
|
</div>
|
|
629
|
-
<div
|
|
630
|
-
<p style="color: #
|
|
988
|
+
<div class="activity-feed" id="current-tasks">
|
|
989
|
+
<p style="color: #9ca3af;">No active tasks</p>
|
|
631
990
|
</div>
|
|
632
991
|
</div>
|
|
633
992
|
|
|
@@ -635,40 +994,8 @@ async function startWebServer() {
|
|
|
635
994
|
<div class="card-header">
|
|
636
995
|
<h2 class="card-title">\u26A0\uFE0F Issues Encountered</h2>
|
|
637
996
|
</div>
|
|
638
|
-
<div
|
|
639
|
-
<p style="color: #
|
|
640
|
-
</div>
|
|
641
|
-
</div>
|
|
642
|
-
</div>
|
|
643
|
-
|
|
644
|
-
<div class="grid">
|
|
645
|
-
<div class="card">
|
|
646
|
-
<h2>\u{1F916} Active Agents</h2>
|
|
647
|
-
<div id="active-agents-list">
|
|
648
|
-
<p style="color: #64748b;">No active agents</p>
|
|
649
|
-
</div>
|
|
650
|
-
</div>
|
|
651
|
-
|
|
652
|
-
<div class="card">
|
|
653
|
-
<h2>\u{1F6A8} Human Interventions</h2>
|
|
654
|
-
<div id="interventions-list">
|
|
655
|
-
<p style="color: #64748b;">No interventions required</p>
|
|
656
|
-
</div>
|
|
657
|
-
</div>
|
|
658
|
-
</div>
|
|
659
|
-
|
|
660
|
-
<div class="grid">
|
|
661
|
-
<div class="card">
|
|
662
|
-
<h2>\u{1F4DD} Current Tasks</h2>
|
|
663
|
-
<div id="current-tasks">
|
|
664
|
-
<p style="color: #64748b;">No active tasks</p>
|
|
665
|
-
</div>
|
|
666
|
-
</div>
|
|
667
|
-
|
|
668
|
-
<div class="card">
|
|
669
|
-
<h2>\u26A0\uFE0F Issues Encountered</h2>
|
|
670
|
-
<div id="issues-list">
|
|
671
|
-
<p style="color: #64748b;">No issues</p>
|
|
997
|
+
<div class="activity-feed" id="issues-list">
|
|
998
|
+
<p style="color: #9ca3af;">No issues</p>
|
|
672
999
|
</div>
|
|
673
1000
|
</div>
|
|
674
1001
|
</div>
|
|
@@ -1297,228 +1624,720 @@ async function startWebServer() {
|
|
|
1297
1624
|
`);
|
|
1298
1625
|
});
|
|
1299
1626
|
app.get("/config", (req, res) => {
|
|
1627
|
+
const cfg2 = config.getConfigSync();
|
|
1300
1628
|
res.send(`
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1629
|
+
<!DOCTYPE html>
|
|
1630
|
+
<html lang="en">
|
|
1631
|
+
<head>
|
|
1632
|
+
<meta charset="UTF-8">
|
|
1633
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1634
|
+
<title>OpenQA \u2014 Configuration</title>
|
|
1635
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
1636
|
+
<link href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@300;400;500&family=Syne:wght@400;600;700;800&display=swap" rel="stylesheet">
|
|
1637
|
+
<style>
|
|
1638
|
+
:root {
|
|
1639
|
+
--bg: #080b10;
|
|
1640
|
+
--surface: #0d1117;
|
|
1641
|
+
--panel: #111720;
|
|
1642
|
+
--border: rgba(255,255,255,0.06);
|
|
1643
|
+
--border-hi: rgba(255,255,255,0.12);
|
|
1644
|
+
--accent: #f97316;
|
|
1645
|
+
--accent-lo: rgba(249,115,22,0.08);
|
|
1646
|
+
--accent-md: rgba(249,115,22,0.18);
|
|
1647
|
+
--green: #22c55e;
|
|
1648
|
+
--green-lo: rgba(34,197,94,0.08);
|
|
1649
|
+
--red: #ef4444;
|
|
1650
|
+
--red-lo: rgba(239,68,68,0.08);
|
|
1651
|
+
--amber: #f59e0b;
|
|
1652
|
+
--blue: #38bdf8;
|
|
1653
|
+
--text-1: #f1f5f9;
|
|
1654
|
+
--text-2: #8b98a8;
|
|
1655
|
+
--text-3: #4b5563;
|
|
1656
|
+
--mono: 'DM Mono', monospace;
|
|
1657
|
+
--sans: 'Syne', sans-serif;
|
|
1658
|
+
--radius: 10px;
|
|
1659
|
+
--radius-lg: 16px;
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
1663
|
+
|
|
1664
|
+
body {
|
|
1665
|
+
font-family: var(--sans);
|
|
1666
|
+
background: var(--bg);
|
|
1667
|
+
color: var(--text-1);
|
|
1668
|
+
min-height: 100vh;
|
|
1669
|
+
overflow-x: hidden;
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
/* \u2500\u2500 Layout \u2500\u2500 */
|
|
1673
|
+
.shell {
|
|
1674
|
+
display: grid;
|
|
1675
|
+
grid-template-columns: 220px 1fr;
|
|
1676
|
+
grid-template-rows: 1fr;
|
|
1677
|
+
min-height: 100vh;
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
/* \u2500\u2500 Sidebar \u2500\u2500 */
|
|
1681
|
+
aside {
|
|
1682
|
+
background: var(--surface);
|
|
1683
|
+
border-right: 1px solid var(--border);
|
|
1684
|
+
display: flex;
|
|
1685
|
+
flex-direction: column;
|
|
1686
|
+
padding: 28px 0;
|
|
1687
|
+
position: sticky;
|
|
1688
|
+
top: 0;
|
|
1689
|
+
height: 100vh;
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
.logo {
|
|
1693
|
+
display: flex;
|
|
1694
|
+
align-items: center;
|
|
1695
|
+
gap: 10px;
|
|
1696
|
+
padding: 0 24px 32px;
|
|
1697
|
+
border-bottom: 1px solid var(--border);
|
|
1698
|
+
margin-bottom: 12px;
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
.logo-mark {
|
|
1702
|
+
width: 34px;
|
|
1703
|
+
height: 34px;
|
|
1704
|
+
background: var(--accent);
|
|
1705
|
+
border-radius: 8px;
|
|
1706
|
+
display: grid;
|
|
1707
|
+
place-items: center;
|
|
1708
|
+
font-size: 16px;
|
|
1709
|
+
flex-shrink: 0;
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
.logo-name {
|
|
1713
|
+
font-family: var(--sans);
|
|
1714
|
+
font-weight: 800;
|
|
1715
|
+
font-size: 18px;
|
|
1716
|
+
letter-spacing: -0.5px;
|
|
1717
|
+
color: var(--text-1);
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
.logo-version {
|
|
1721
|
+
font-family: var(--mono);
|
|
1722
|
+
font-size: 10px;
|
|
1723
|
+
color: var(--text-3);
|
|
1724
|
+
letter-spacing: 0.5px;
|
|
1725
|
+
margin-top: 2px;
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
.nav-section {
|
|
1729
|
+
padding: 8px 12px;
|
|
1730
|
+
flex: 1;
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
.nav-label {
|
|
1734
|
+
font-family: var(--mono);
|
|
1735
|
+
font-size: 10px;
|
|
1736
|
+
color: var(--text-3);
|
|
1737
|
+
letter-spacing: 1.5px;
|
|
1738
|
+
text-transform: uppercase;
|
|
1739
|
+
padding: 0 12px;
|
|
1740
|
+
margin: 16px 0 6px;
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
.nav-item {
|
|
1744
|
+
display: flex;
|
|
1745
|
+
align-items: center;
|
|
1746
|
+
gap: 10px;
|
|
1747
|
+
padding: 9px 12px;
|
|
1748
|
+
border-radius: var(--radius);
|
|
1749
|
+
color: var(--text-2);
|
|
1750
|
+
text-decoration: none;
|
|
1751
|
+
font-size: 14px;
|
|
1752
|
+
font-weight: 600;
|
|
1753
|
+
transition: all 0.15s ease;
|
|
1754
|
+
cursor: pointer;
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
.nav-item:hover { color: var(--text-1); background: var(--panel); }
|
|
1758
|
+
.nav-item.active { color: var(--accent); background: var(--accent-lo); }
|
|
1759
|
+
.nav-item .icon { font-size: 15px; width: 20px; text-align: center; }
|
|
1760
|
+
|
|
1761
|
+
.sidebar-footer {
|
|
1762
|
+
padding: 16px 24px;
|
|
1763
|
+
border-top: 1px solid var(--border);
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
.status-pill {
|
|
1767
|
+
display: flex;
|
|
1768
|
+
align-items: center;
|
|
1769
|
+
gap: 8px;
|
|
1770
|
+
font-family: var(--mono);
|
|
1771
|
+
font-size: 11px;
|
|
1772
|
+
color: var(--text-2);
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
.dot {
|
|
1776
|
+
width: 7px;
|
|
1777
|
+
height: 7px;
|
|
1778
|
+
border-radius: 50%;
|
|
1779
|
+
background: var(--green);
|
|
1780
|
+
box-shadow: 0 0 8px var(--green);
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
/* \u2500\u2500 Main \u2500\u2500 */
|
|
1784
|
+
main {
|
|
1785
|
+
display: flex;
|
|
1786
|
+
flex-direction: column;
|
|
1787
|
+
min-height: 100vh;
|
|
1788
|
+
overflow-y: auto;
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
.topbar {
|
|
1792
|
+
display: flex;
|
|
1793
|
+
align-items: center;
|
|
1794
|
+
justify-content: space-between;
|
|
1795
|
+
padding: 20px 32px;
|
|
1796
|
+
border-bottom: 1px solid var(--border);
|
|
1797
|
+
background: var(--surface);
|
|
1798
|
+
position: sticky;
|
|
1799
|
+
top: 0;
|
|
1800
|
+
z-index: 10;
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
.page-title {
|
|
1804
|
+
font-size: 15px;
|
|
1805
|
+
font-weight: 700;
|
|
1806
|
+
color: var(--text-1);
|
|
1807
|
+
letter-spacing: -0.2px;
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
.page-breadcrumb {
|
|
1811
|
+
font-family: var(--mono);
|
|
1812
|
+
font-size: 11px;
|
|
1813
|
+
color: var(--text-3);
|
|
1814
|
+
margin-top: 2px;
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
.topbar-actions {
|
|
1818
|
+
display: flex;
|
|
1819
|
+
align-items: center;
|
|
1820
|
+
gap: 12px;
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
.btn-sm {
|
|
1824
|
+
font-family: var(--sans);
|
|
1825
|
+
font-weight: 700;
|
|
1826
|
+
font-size: 12px;
|
|
1827
|
+
padding: 8px 16px;
|
|
1828
|
+
border-radius: 8px;
|
|
1829
|
+
border: none;
|
|
1830
|
+
cursor: pointer;
|
|
1831
|
+
transition: all 0.15s ease;
|
|
1832
|
+
letter-spacing: 0.2px;
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
.btn-ghost {
|
|
1836
|
+
background: var(--panel);
|
|
1837
|
+
color: var(--text-2);
|
|
1838
|
+
border: 1px solid var(--border);
|
|
1839
|
+
}
|
|
1840
|
+
.btn-ghost:hover { border-color: var(--border-hi); color: var(--text-1); }
|
|
1841
|
+
|
|
1842
|
+
.btn-primary {
|
|
1843
|
+
background: var(--accent);
|
|
1844
|
+
color: #fff;
|
|
1845
|
+
}
|
|
1846
|
+
.btn-primary:hover { background: #ea580c; box-shadow: 0 0 20px rgba(249,115,22,0.35); }
|
|
1847
|
+
|
|
1848
|
+
/* \u2500\u2500 Content \u2500\u2500 */
|
|
1849
|
+
.content {
|
|
1850
|
+
padding: 28px 32px;
|
|
1851
|
+
display: flex;
|
|
1852
|
+
flex-direction: column;
|
|
1853
|
+
gap: 24px;
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
/* \u2500\u2500 Panel \u2500\u2500 */
|
|
1857
|
+
.panel {
|
|
1858
|
+
background: var(--panel);
|
|
1859
|
+
border: 1px solid var(--border);
|
|
1860
|
+
border-radius: var(--radius-lg);
|
|
1861
|
+
overflow: hidden;
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
.panel-head {
|
|
1865
|
+
display: flex;
|
|
1866
|
+
align-items: center;
|
|
1867
|
+
justify-content: space-between;
|
|
1868
|
+
padding: 18px 24px;
|
|
1869
|
+
border-bottom: 1px solid var(--border);
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
.panel-title {
|
|
1873
|
+
font-size: 13px;
|
|
1874
|
+
font-weight: 700;
|
|
1875
|
+
color: var(--text-1);
|
|
1876
|
+
letter-spacing: -0.1px;
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
.panel-body {
|
|
1880
|
+
padding: 24px;
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
/* \u2500\u2500 Form \u2500\u2500 */
|
|
1884
|
+
.form-grid {
|
|
1885
|
+
display: grid;
|
|
1886
|
+
gap: 20px;
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
.form-section {
|
|
1890
|
+
display: flex;
|
|
1891
|
+
flex-direction: column;
|
|
1892
|
+
gap: 16px;
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
.form-section-title {
|
|
1896
|
+
font-size: 12px;
|
|
1897
|
+
font-weight: 700;
|
|
1898
|
+
color: var(--text-2);
|
|
1899
|
+
text-transform: uppercase;
|
|
1900
|
+
letter-spacing: 1px;
|
|
1901
|
+
margin-bottom: 8px;
|
|
1902
|
+
padding-bottom: 8px;
|
|
1903
|
+
border-bottom: 1px solid var(--border);
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
.form-row {
|
|
1907
|
+
display: grid;
|
|
1908
|
+
grid-template-columns: 1fr 1fr;
|
|
1909
|
+
gap: 16px;
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
.form-field {
|
|
1913
|
+
display: flex;
|
|
1914
|
+
flex-direction: column;
|
|
1915
|
+
gap: 6px;
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
.form-field.full {
|
|
1919
|
+
grid-column: 1 / -1;
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
label {
|
|
1923
|
+
font-family: var(--mono);
|
|
1924
|
+
font-size: 11px;
|
|
1925
|
+
color: var(--text-3);
|
|
1926
|
+
text-transform: uppercase;
|
|
1927
|
+
letter-spacing: 0.5px;
|
|
1928
|
+
font-weight: 500;
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
input, select {
|
|
1932
|
+
background: var(--surface);
|
|
1933
|
+
border: 1px solid var(--border);
|
|
1934
|
+
color: var(--text-1);
|
|
1935
|
+
padding: 10px 14px;
|
|
1936
|
+
border-radius: var(--radius);
|
|
1937
|
+
font-family: var(--mono);
|
|
1938
|
+
font-size: 12px;
|
|
1939
|
+
transition: all 0.15s ease;
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
input:focus, select:focus {
|
|
1943
|
+
outline: none;
|
|
1944
|
+
border-color: var(--accent);
|
|
1945
|
+
box-shadow: 0 0 0 1px var(--accent);
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
input[type="checkbox"] {
|
|
1949
|
+
width: 16px;
|
|
1950
|
+
height: 16px;
|
|
1951
|
+
margin: 0;
|
|
1952
|
+
cursor: pointer;
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
.checkbox-label {
|
|
1956
|
+
display: flex;
|
|
1957
|
+
align-items: center;
|
|
1958
|
+
gap: 8px;
|
|
1959
|
+
cursor: pointer;
|
|
1960
|
+
font-size: 12px;
|
|
1961
|
+
color: var(--text-2);
|
|
1962
|
+
text-transform: none;
|
|
1963
|
+
letter-spacing: normal;
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
/* \u2500\u2500 Actions \u2500\u2500 */
|
|
1967
|
+
.actions {
|
|
1968
|
+
display: flex;
|
|
1969
|
+
gap: 12px;
|
|
1970
|
+
padding: 20px 24px;
|
|
1971
|
+
background: var(--surface);
|
|
1972
|
+
border-top: 1px solid var(--border);
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
.message {
|
|
1976
|
+
font-family: var(--mono);
|
|
1977
|
+
font-size: 11px;
|
|
1978
|
+
padding: 8px 12px;
|
|
1979
|
+
border-radius: var(--radius);
|
|
1980
|
+
margin-left: auto;
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
.message.success {
|
|
1984
|
+
background: var(--green-lo);
|
|
1985
|
+
color: var(--green);
|
|
1986
|
+
border: 1px solid rgba(34,197,94,0.2);
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
.message.error {
|
|
1990
|
+
background: var(--red-lo);
|
|
1991
|
+
color: var(--red);
|
|
1992
|
+
border: 1px solid rgba(239,68,68,0.2);
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
/* \u2500\u2500 Code Block \u2500\u2500 */
|
|
1996
|
+
.code-block {
|
|
1997
|
+
background: var(--surface);
|
|
1998
|
+
border: 1px solid var(--border);
|
|
1999
|
+
border-radius: var(--radius);
|
|
2000
|
+
padding: 20px;
|
|
2001
|
+
font-family: var(--mono);
|
|
2002
|
+
font-size: 11px;
|
|
2003
|
+
color: var(--text-2);
|
|
2004
|
+
overflow-x: auto;
|
|
2005
|
+
}
|
|
2006
|
+
|
|
2007
|
+
.code-block pre {
|
|
2008
|
+
margin: 0;
|
|
2009
|
+
line-height: 1.6;
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
/* \u2500\u2500 Responsive \u2500\u2500 */
|
|
2013
|
+
@media (max-width: 900px) {
|
|
2014
|
+
.shell { grid-template-columns: 1fr; }
|
|
2015
|
+
aside { display: none; }
|
|
2016
|
+
.form-row { grid-template-columns: 1fr; }
|
|
2017
|
+
}
|
|
2018
|
+
</style>
|
|
2019
|
+
</head>
|
|
2020
|
+
<body>
|
|
2021
|
+
|
|
2022
|
+
<div class="shell">
|
|
2023
|
+
|
|
2024
|
+
<!-- \u2500\u2500 Sidebar \u2500\u2500 -->
|
|
2025
|
+
<aside>
|
|
2026
|
+
<div class="logo">
|
|
2027
|
+
<div class="logo-mark">\u2699</div>
|
|
2028
|
+
<div>
|
|
2029
|
+
<div class="logo-name">OpenQA</div>
|
|
2030
|
+
<div class="logo-version">v2.1.0 \xB7 OSS</div>
|
|
2031
|
+
</div>
|
|
2032
|
+
</div>
|
|
2033
|
+
|
|
2034
|
+
<div class="nav-section">
|
|
2035
|
+
<div class="nav-label">Overview</div>
|
|
2036
|
+
<a class="nav-item" href="/">
|
|
2037
|
+
<span class="icon">\u25A6</span> Dashboard
|
|
2038
|
+
</a>
|
|
2039
|
+
<a class="nav-item" href="/kanban">
|
|
2040
|
+
<span class="icon">\u229E</span> Kanban
|
|
2041
|
+
</a>
|
|
2042
|
+
|
|
2043
|
+
<div class="nav-label">System</div>
|
|
2044
|
+
<a class="nav-item active" href="/config">
|
|
2045
|
+
<span class="icon">\u2699</span> Configuration
|
|
2046
|
+
</a>
|
|
2047
|
+
</div>
|
|
2048
|
+
|
|
2049
|
+
<div class="sidebar-footer">
|
|
2050
|
+
<div class="status-pill">
|
|
2051
|
+
<div class="dot"></div>
|
|
2052
|
+
<span>System Ready</span>
|
|
2053
|
+
</div>
|
|
2054
|
+
</div>
|
|
2055
|
+
</aside>
|
|
2056
|
+
|
|
2057
|
+
<!-- \u2500\u2500 Main \u2500\u2500 -->
|
|
2058
|
+
<main>
|
|
2059
|
+
|
|
2060
|
+
<!-- Topbar -->
|
|
2061
|
+
<div class="topbar">
|
|
2062
|
+
<div>
|
|
2063
|
+
<div class="page-title">Configuration</div>
|
|
2064
|
+
<div class="page-breadcrumb">openqa / system / settings</div>
|
|
2065
|
+
</div>
|
|
2066
|
+
<div class="topbar-actions">
|
|
2067
|
+
<button class="btn-sm btn-ghost">Export Config</button>
|
|
2068
|
+
<button class="btn-sm btn-ghost">Import Config</button>
|
|
2069
|
+
<button class="btn-sm btn-primary" onclick="saveAllConfig()">Save All</button>
|
|
2070
|
+
</div>
|
|
2071
|
+
</div>
|
|
2072
|
+
|
|
2073
|
+
<!-- Content -->
|
|
2074
|
+
<div class="content">
|
|
2075
|
+
|
|
2076
|
+
<!-- SaaS Configuration -->
|
|
2077
|
+
<div class="panel">
|
|
2078
|
+
<div class="panel-head">
|
|
2079
|
+
<span class="panel-title">\u{1F310} SaaS Target Configuration</span>
|
|
2080
|
+
</div>
|
|
2081
|
+
<div class="panel-body">
|
|
2082
|
+
<form class="form-grid" id="saas-form">
|
|
2083
|
+
<div class="form-section">
|
|
2084
|
+
<div class="form-section-title">Target Application</div>
|
|
2085
|
+
<div class="form-field full">
|
|
2086
|
+
<label>Application URL</label>
|
|
2087
|
+
<input type="url" id="saas_url" name="saas.url" value="${cfg2.saas.url || ""}" placeholder="https://your-app.com">
|
|
1373
2088
|
</div>
|
|
1374
|
-
<div class="
|
|
1375
|
-
<
|
|
1376
|
-
|
|
2089
|
+
<div class="form-row">
|
|
2090
|
+
<div class="form-field">
|
|
2091
|
+
<label>Authentication Type</label>
|
|
2092
|
+
<select id="saas_authType" name="saas.authType">
|
|
2093
|
+
<option value="none" ${cfg2.saas.authType === "none" ? "selected" : ""}>None</option>
|
|
2094
|
+
<option value="basic" ${cfg2.saas.authType === "basic" ? "selected" : ""}>Basic Auth</option>
|
|
2095
|
+
<option value="bearer" ${cfg2.saas.authType === "bearer" ? "selected" : ""}>Bearer Token</option>
|
|
2096
|
+
<option value="session" ${cfg2.saas.authType === "session" ? "selected" : ""}>Session</option>
|
|
2097
|
+
</select>
|
|
2098
|
+
</div>
|
|
2099
|
+
<div class="form-field">
|
|
2100
|
+
<label>Timeout (seconds)</label>
|
|
2101
|
+
<input type="number" id="saas_timeout" name="saas.timeout" value="30" min="5" max="300">
|
|
2102
|
+
</div>
|
|
1377
2103
|
</div>
|
|
1378
|
-
<div class="
|
|
1379
|
-
<
|
|
1380
|
-
|
|
2104
|
+
<div class="form-row">
|
|
2105
|
+
<div class="form-field">
|
|
2106
|
+
<label>Username</label>
|
|
2107
|
+
<input type="text" id="saas_username" name="saas.username" value="${cfg2.saas.username || ""}" placeholder="username">
|
|
2108
|
+
</div>
|
|
2109
|
+
<div class="form-field">
|
|
2110
|
+
<label>Password</label>
|
|
2111
|
+
<input type="password" id="saas_password" name="saas.password" value="${cfg2.saas.password || ""}" placeholder="password">
|
|
2112
|
+
</div>
|
|
1381
2113
|
</div>
|
|
1382
|
-
</
|
|
1383
|
-
</
|
|
2114
|
+
</div>
|
|
2115
|
+
</form>
|
|
2116
|
+
</div>
|
|
2117
|
+
</div>
|
|
1384
2118
|
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
2119
|
+
<!-- LLM Configuration -->
|
|
2120
|
+
<div class="panel">
|
|
2121
|
+
<div class="panel-head">
|
|
2122
|
+
<span class="panel-title">\u{1F916} LLM Configuration</span>
|
|
2123
|
+
</div>
|
|
2124
|
+
<div class="panel-body">
|
|
2125
|
+
<form class="form-grid" id="llm-form">
|
|
2126
|
+
<div class="form-section">
|
|
2127
|
+
<div class="form-section-title">Language Model Provider</div>
|
|
2128
|
+
<div class="form-row">
|
|
2129
|
+
<div class="form-field">
|
|
2130
|
+
<label>Provider</label>
|
|
2131
|
+
<select id="llm_provider" name="llm.provider">
|
|
2132
|
+
<option value="openai" ${cfg2.llm.provider === "openai" ? "selected" : ""}>OpenAI</option>
|
|
2133
|
+
<option value="anthropic" ${cfg2.llm.provider === "anthropic" ? "selected" : ""}>Anthropic</option>
|
|
2134
|
+
<option value="ollama" ${cfg2.llm.provider === "ollama" ? "selected" : ""}>Ollama</option>
|
|
2135
|
+
</select>
|
|
2136
|
+
</div>
|
|
2137
|
+
<div class="form-field">
|
|
2138
|
+
<label>Model</label>
|
|
2139
|
+
<input type="text" id="llm_model" name="llm.model" value="${cfg2.llm.model || ""}" placeholder="gpt-4, claude-3-sonnet, etc.">
|
|
2140
|
+
</div>
|
|
1399
2141
|
</div>
|
|
1400
|
-
<div class="
|
|
2142
|
+
<div class="form-field full">
|
|
1401
2143
|
<label>API Key</label>
|
|
1402
|
-
<input type="password" id="llm_apiKey" name="llm.apiKey" value="${
|
|
2144
|
+
<input type="password" id="llm_apiKey" name="llm.apiKey" value="${cfg2.llm.apiKey || ""}" placeholder="Your API key">
|
|
1403
2145
|
</div>
|
|
1404
|
-
<div class="
|
|
2146
|
+
<div class="form-field full">
|
|
1405
2147
|
<label>Base URL (for Ollama)</label>
|
|
1406
|
-
<input type="url" id="llm_baseUrl" name="llm.baseUrl" value="${
|
|
2148
|
+
<input type="url" id="llm_baseUrl" name="llm.baseUrl" value="${cfg2.llm.baseUrl || ""}" placeholder="http://localhost:11434">
|
|
1407
2149
|
</div>
|
|
1408
|
-
</
|
|
1409
|
-
</
|
|
2150
|
+
</div>
|
|
2151
|
+
</form>
|
|
2152
|
+
</div>
|
|
2153
|
+
</div>
|
|
1410
2154
|
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
2155
|
+
<!-- Agent Configuration -->
|
|
2156
|
+
<div class="panel">
|
|
2157
|
+
<div class="panel-head">
|
|
2158
|
+
<span class="panel-title">\u{1F3AF} Agent Settings</span>
|
|
2159
|
+
</div>
|
|
2160
|
+
<div class="panel-body">
|
|
2161
|
+
<form class="form-grid" id="agent-form">
|
|
2162
|
+
<div class="form-section">
|
|
2163
|
+
<div class="form-section-title">Agent Behavior</div>
|
|
2164
|
+
<div class="form-field">
|
|
2165
|
+
<label class="checkbox-label">
|
|
2166
|
+
<input type="checkbox" id="agent_autoStart" name="agent.autoStart" ${cfg2.agent.autoStart ? "checked" : ""}>
|
|
2167
|
+
Auto-start on launch
|
|
1418
2168
|
</label>
|
|
1419
2169
|
</div>
|
|
1420
|
-
<div class="
|
|
1421
|
-
<
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
<
|
|
1426
|
-
|
|
2170
|
+
<div class="form-row">
|
|
2171
|
+
<div class="form-field">
|
|
2172
|
+
<label>Check Interval (ms)</label>
|
|
2173
|
+
<input type="number" id="agent_intervalMs" name="agent.intervalMs" value="${cfg2.agent.intervalMs}" min="60000" step="60000">
|
|
2174
|
+
</div>
|
|
2175
|
+
<div class="form-field">
|
|
2176
|
+
<label>Max Iterations</label>
|
|
2177
|
+
<input type="number" id="agent_maxIterations" name="agent.maxIterations" value="${cfg2.agent.maxIterations}" min="1" max="100">
|
|
2178
|
+
</div>
|
|
1427
2179
|
</div>
|
|
1428
|
-
</
|
|
1429
|
-
</
|
|
2180
|
+
</div>
|
|
2181
|
+
</form>
|
|
2182
|
+
</div>
|
|
2183
|
+
</div>
|
|
1430
2184
|
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
2185
|
+
<!-- Environment Variables -->
|
|
2186
|
+
<div class="panel">
|
|
2187
|
+
<div class="panel-head">
|
|
2188
|
+
<span class="panel-title">\u{1F4DD} Environment Variables</span>
|
|
2189
|
+
</div>
|
|
2190
|
+
<div class="panel-body">
|
|
2191
|
+
<p style="color: var(--text-3); font-size: 12px; margin-bottom: 16px;">
|
|
2192
|
+
You can also set these environment variables before starting OpenQA:
|
|
2193
|
+
</p>
|
|
2194
|
+
<div class="code-block">
|
|
2195
|
+
<pre>export SAAS_URL="https://your-app.com"
|
|
2196
|
+
export SAAS_AUTH_TYPE="basic"
|
|
2197
|
+
export SAAS_USERNAME="admin"
|
|
2198
|
+
export SAAS_PASSWORD="secret"
|
|
2199
|
+
|
|
2200
|
+
export LLM_PROVIDER="openai"
|
|
2201
|
+
export OPENAI_API_KEY="your-openai-key"
|
|
2202
|
+
export LLM_MODEL="gpt-4"
|
|
1437
2203
|
|
|
1438
|
-
<div class="section">
|
|
1439
|
-
<h2>Environment Variables</h2>
|
|
1440
|
-
<p>You can also set these environment variables before starting OpenQA:</p>
|
|
1441
|
-
<pre style="background: #334155; padding: 15px; border-radius: 6px; overflow-x: auto;"><code>export SAAS_URL="https://your-app.com"
|
|
1442
2204
|
export AGENT_AUTO_START=true
|
|
1443
|
-
export
|
|
1444
|
-
export
|
|
2205
|
+
export AGENT_INTERVAL_MS=3600000
|
|
2206
|
+
export AGENT_MAX_ITERATIONS=20
|
|
1445
2207
|
|
|
1446
|
-
openqa start</
|
|
2208
|
+
openqa start</pre>
|
|
1447
2209
|
</div>
|
|
2210
|
+
</div>
|
|
2211
|
+
</div>
|
|
1448
2212
|
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
2213
|
+
<!-- Actions -->
|
|
2214
|
+
<div class="actions">
|
|
2215
|
+
<button class="btn-sm btn-ghost" onclick="testConnection()">Test Connection</button>
|
|
2216
|
+
<button class="btn-sm btn-ghost" onclick="exportConfig()">Export Config</button>
|
|
2217
|
+
<button class="btn-sm btn-ghost" onclick="resetConfig()">Reset to Defaults</button>
|
|
2218
|
+
<div id="message"></div>
|
|
2219
|
+
</div>
|
|
2220
|
+
|
|
2221
|
+
</div><!-- /content -->
|
|
2222
|
+
</main>
|
|
2223
|
+
</div><!-- /shell -->
|
|
2224
|
+
|
|
2225
|
+
<script>
|
|
2226
|
+
async function saveAllConfig() {
|
|
2227
|
+
const forms = ['saas-form', 'llm-form', 'agent-form'];
|
|
2228
|
+
const config = {};
|
|
2229
|
+
|
|
2230
|
+
for (const formId of forms) {
|
|
2231
|
+
const form = document.getElementById(formId);
|
|
2232
|
+
const formData = new FormData(form);
|
|
2233
|
+
|
|
2234
|
+
for (let [key, value] of formData.entries()) {
|
|
2235
|
+
if (value === '') continue;
|
|
2236
|
+
|
|
2237
|
+
// Handle nested keys like "saas.url"
|
|
2238
|
+
const keys = key.split('.');
|
|
2239
|
+
let obj = config;
|
|
2240
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
2241
|
+
if (!obj[keys[i]]) obj[keys[i]] = {};
|
|
2242
|
+
obj = obj[keys[i]];
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
// Convert checkbox values to boolean
|
|
2246
|
+
if (key.includes('autoStart')) {
|
|
2247
|
+
obj[keys[keys.length - 1]] = value === 'on';
|
|
2248
|
+
} else if (key.includes('intervalMs') || key.includes('maxIterations') || key.includes('timeout')) {
|
|
2249
|
+
obj[keys[keys.length - 1]] = parseInt(value);
|
|
2250
|
+
} else {
|
|
2251
|
+
obj[keys[keys.length - 1]] = value;
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
|
|
2256
|
+
try {
|
|
2257
|
+
const response = await fetch('/api/config', {
|
|
2258
|
+
method: 'POST',
|
|
2259
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2260
|
+
body: JSON.stringify(config)
|
|
2261
|
+
});
|
|
2262
|
+
|
|
2263
|
+
if (response.ok) {
|
|
2264
|
+
showMessage('Configuration saved successfully!', 'success');
|
|
2265
|
+
} else {
|
|
2266
|
+
showMessage('Failed to save configuration', 'error');
|
|
2267
|
+
}
|
|
2268
|
+
} catch (error) {
|
|
2269
|
+
showMessage('Error: ' + error.message, 'error');
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
async function testConnection() {
|
|
2274
|
+
showMessage('Testing connection...', 'success');
|
|
2275
|
+
|
|
2276
|
+
try {
|
|
2277
|
+
const response = await fetch('/api/test-connection', { method: 'POST' });
|
|
2278
|
+
|
|
2279
|
+
if (response.ok) {
|
|
2280
|
+
showMessage('Connection successful!', 'success');
|
|
2281
|
+
} else {
|
|
2282
|
+
showMessage('Connection failed', 'error');
|
|
2283
|
+
}
|
|
2284
|
+
} catch (error) {
|
|
2285
|
+
showMessage('Connection error: ' + error.message, 'error');
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
async function exportConfig() {
|
|
2290
|
+
try {
|
|
2291
|
+
const response = await fetch('/api/config');
|
|
2292
|
+
const config = await response.json();
|
|
2293
|
+
|
|
2294
|
+
const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' });
|
|
2295
|
+
const url = URL.createObjectURL(blob);
|
|
2296
|
+
const a = document.createElement('a');
|
|
2297
|
+
a.href = url;
|
|
2298
|
+
a.download = 'openqa-config.json';
|
|
2299
|
+
a.click();
|
|
2300
|
+
URL.revokeObjectURL(url);
|
|
2301
|
+
|
|
2302
|
+
showMessage('Configuration exported', 'success');
|
|
2303
|
+
} catch (error) {
|
|
2304
|
+
showMessage('Export failed: ' + error.message, 'error');
|
|
2305
|
+
}
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
async function resetConfig() {
|
|
2309
|
+
if (confirm('Are you sure you want to reset all configuration to defaults? This cannot be undone.')) {
|
|
2310
|
+
try {
|
|
2311
|
+
const response = await fetch('/api/config/reset', { method: 'POST' });
|
|
2312
|
+
|
|
2313
|
+
if (response.ok) {
|
|
2314
|
+
location.reload();
|
|
2315
|
+
} else {
|
|
2316
|
+
showMessage('Failed to reset configuration', 'error');
|
|
2317
|
+
}
|
|
2318
|
+
} catch (error) {
|
|
2319
|
+
showMessage('Error: ' + error.message, 'error');
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
function showMessage(msg, type) {
|
|
2325
|
+
const el = document.getElementById('message');
|
|
2326
|
+
el.textContent = msg;
|
|
2327
|
+
el.className = 'message ' + type;
|
|
2328
|
+
setTimeout(() => { el.textContent = ''; el.className = ''; }, 5000);
|
|
2329
|
+
}
|
|
2330
|
+
|
|
2331
|
+
// Auto-save on field change
|
|
2332
|
+
document.querySelectorAll('input, select').forEach(field => {
|
|
2333
|
+
field.addEventListener('change', () => {
|
|
2334
|
+
showMessage('Changes made - click "Save All" to apply', 'success');
|
|
2335
|
+
});
|
|
2336
|
+
});
|
|
2337
|
+
</script>
|
|
2338
|
+
|
|
2339
|
+
</body>
|
|
2340
|
+
</html>
|
|
1522
2341
|
`);
|
|
1523
2342
|
});
|
|
1524
2343
|
const server = app.listen(cfg.web.port, cfg.web.host, () => {
|