@openqa/cli 1.1.1 → 1.3.0

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 CHANGED
@@ -363,54 +363,807 @@ async function startWebServer() {
363
363
  res.status(500).json({ success: false, error: error.message });
364
364
  }
365
365
  });
366
+ app.post("/api/intervention/:id", (req, res) => {
367
+ const { id } = req.params;
368
+ const { response } = req.body;
369
+ console.log(`Intervention ${id}: ${response}`);
370
+ broadcast({
371
+ type: "intervention-response",
372
+ data: { id, response, timestamp: (/* @__PURE__ */ new Date()).toISOString() }
373
+ });
374
+ res.json({ success: true });
375
+ });
376
+ app.get("/api/tasks", async (req, res) => {
377
+ const tasks = [
378
+ {
379
+ id: "task_1",
380
+ name: "Scan Application",
381
+ status: "completed",
382
+ agent: "Main Agent",
383
+ started_at: new Date(Date.now() - 3e5).toISOString(),
384
+ completed_at: new Date(Date.now() - 24e4).toISOString(),
385
+ result: "Found 5 testable components"
386
+ },
387
+ {
388
+ id: "task_2",
389
+ name: "Generate Tests",
390
+ status: "in-progress",
391
+ agent: "Main Agent",
392
+ started_at: new Date(Date.now() - 18e4).toISOString(),
393
+ progress: "60%"
394
+ },
395
+ {
396
+ id: "task_3",
397
+ name: "Authentication Test",
398
+ status: "pending",
399
+ agent: "Auth Specialist",
400
+ dependencies: ["task_2"]
401
+ }
402
+ ];
403
+ res.json(tasks);
404
+ });
405
+ app.get("/api/issues", async (req, res) => {
406
+ const issues = [
407
+ {
408
+ id: "issue_1",
409
+ type: "authentication",
410
+ severity: "warning",
411
+ message: "Admin area requires authentication",
412
+ agent: "Main Agent",
413
+ timestamp: new Date(Date.now() - 12e4).toISOString(),
414
+ status: "pending"
415
+ },
416
+ {
417
+ id: "issue_2",
418
+ type: "network",
419
+ severity: "error",
420
+ message: "Failed to connect to external API",
421
+ agent: "API Tester",
422
+ timestamp: new Date(Date.now() - 6e4).toISOString(),
423
+ status: "resolved"
424
+ }
425
+ ];
426
+ res.json(issues);
427
+ });
366
428
  app.get("/", (req, res) => {
367
429
  res.send(`
368
430
  <!DOCTYPE html>
369
431
  <html>
370
432
  <head>
371
- <title>OpenQA - Dashboard</title>
433
+ <title>OpenQA - Professional Dashboard</title>
434
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
435
+ <script src="https://cdn.jsdelivr.net/npm/vis-network@latest/dist/vis-network.min.js"></script>
372
436
  <style>
373
- body { font-family: system-ui; max-width: 1200px; margin: 40px auto; padding: 20px; background: #0f172a; color: #e2e8f0; }
374
- h1 { color: #38bdf8; }
375
- .card { background: #1e293b; border: 1px solid #334155; border-radius: 8px; padding: 20px; margin: 20px 0; }
376
- .status { display: inline-block; padding: 4px 12px; border-radius: 4px; font-size: 14px; }
377
- .status.running { background: #10b981; color: white; }
378
- .status.idle { background: #f59e0b; color: white; }
379
- a { color: #38bdf8; text-decoration: none; }
380
- a:hover { text-decoration: underline; }
381
- nav { margin: 20px 0; }
382
- nav a { margin-right: 20px; }
437
+ body { font-family: system-ui; max-width: 1600px; margin: 20px auto; padding: 20px; background: #0f172a; color: #e2e8f0; }
438
+ h1 { color: #38bdf8; margin-bottom: 30px; }
439
+ .card { background: #1e293b; border: 1px solid #334155; border-radius: 12px; padding: 24px; margin: 20px 0; }
440
+ .card-header { display: flex; justify-content: between; align-items: center; margin-bottom: 20px; }
441
+ .card-title { font-size: 18px; font-weight: 600; color: #38bdf8; }
442
+ .status { display: inline-block; padding: 6px 12px; border-radius: 20px; font-size: 12px; font-weight: 500; }
443
+ .status.running { background: linear-gradient(135deg, #10b981, #059669); color: white; }
444
+ .status.idle { background: linear-gradient(135deg, #f59e0b, #d97706); color: white; }
445
+ .status.error { background: linear-gradient(135deg, #ef4444, #dc2626); color: white; }
446
+ .status.paused { background: linear-gradient(135deg, #64748b, #475569); color: white; }
447
+ .nav { display: flex; justify-content: space-between; align-items: center; margin: 20px 0; padding: 15px; background: #1e293b; border-radius: 12px; }
448
+ .nav-links { display: flex; gap: 30px; }
449
+ .nav-links a { color: #94a3b8; text-decoration: none; font-weight: 500; transition: color 0.2s; }
450
+ .nav-links a:hover, .nav-links a.active { color: #38bdf8; }
451
+ .grid { display: grid; gap: 20px; }
452
+ .grid-2 { grid-template-columns: repeat(2, 1fr); }
453
+ .grid-3 { grid-template-columns: repeat(3, 1fr); }
454
+ .grid-4 { grid-template-columns: repeat(4, 1fr); }
455
+ .metric-card {
456
+ background: linear-gradient(135deg, #1e293b, #334155);
457
+ border: 1px solid #334155;
458
+ border-radius: 12px;
459
+ padding: 20px;
460
+ text-align: center;
461
+ position: relative;
462
+ overflow: hidden;
463
+ }
464
+ .metric-card::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 3px; background: linear-gradient(90deg, #38bdf8, #0ea5e9); }
465
+ .metric-value { font-size: 32px; font-weight: bold; color: #38bdf8; margin: 10px 0; }
466
+ .metric-label { color: #94a3b8; font-size: 14px; font-weight: 500; }
467
+ .metric-change { font-size: 12px; margin-top: 5px; }
468
+ .metric-change.positive { color: #10b981; }
469
+ .metric-change.negative { color: #ef4444; }
470
+ .chart-container { position: relative; height: 300px; margin: 20px 0; }
471
+ .hierarchy-container { height: 400px; border: 1px solid #334155; border-radius: 8px; background: #0f172a; }
472
+ .activity-item {
473
+ background: #334155;
474
+ padding: 15px;
475
+ margin: 10px 0;
476
+ border-radius: 8px;
477
+ border-left: 4px solid #38bdf8;
478
+ font-size: 14px;
479
+ transition: all 0.2s;
480
+ }
481
+ .activity-item:hover { transform: translateX(4px); background: #475569; }
482
+ .activity-item.error { border-left-color: #ef4444; }
483
+ .activity-item.success { border-left-color: #10b981; }
484
+ .activity-item.warning { border-left-color: #f59e0b; }
485
+ .activity-time { color: #64748b; font-size: 12px; }
486
+ .intervention-request {
487
+ background: linear-gradient(135deg, #7c2d12, #92400e);
488
+ border: 1px solid #dc2626;
489
+ padding: 20px;
490
+ border-radius: 12px;
491
+ margin: 15px 0;
492
+ position: relative;
493
+ }
494
+ .intervention-request::before { content: '\u{1F6A8}'; position: absolute; top: 15px; right: 15px; font-size: 20px; }
495
+ .intervention-request h4 { color: #fbbf24; margin: 0 0 10px 0; }
496
+ .btn {
497
+ background: linear-gradient(135deg, #38bdf8, #0ea5e9);
498
+ color: white;
499
+ border: none;
500
+ padding: 10px 20px;
501
+ border-radius: 8px;
502
+ cursor: pointer;
503
+ font-size: 14px;
504
+ font-weight: 500;
505
+ margin: 5px;
506
+ transition: all 0.2s;
507
+ }
508
+ .btn:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(56, 189, 248, 0.3); }
509
+ .btn-success { background: linear-gradient(135deg, #10b981, #059669); }
510
+ .btn-success:hover { box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3); }
511
+ .btn-danger { background: linear-gradient(135deg, #ef4444, #dc2626); }
512
+ .btn-danger:hover { box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3); }
513
+ .pulse { animation: pulse 2s infinite; }
514
+ @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.6; } }
515
+ .loading { color: #f59e0b; }
516
+ .tabs { display: flex; gap: 10px; margin-bottom: 20px; }
517
+ .tab { padding: 10px 20px; background: #334155; border-radius: 8px; cursor: pointer; transition: all 0.2s; }
518
+ .tab.active { background: #38bdf8; color: white; }
519
+ .tab-content { display: none; }
520
+ .tab-content.active { display: block; }
521
+ .agent-node {
522
+ background: #1e293b;
523
+ border: 2px solid #38bdf8;
524
+ border-radius: 8px;
525
+ padding: 10px;
526
+ margin: 10px;
527
+ text-align: center;
528
+ }
529
+ .performance-bar {
530
+ height: 8px;
531
+ background: #334155;
532
+ border-radius: 4px;
533
+ overflow: hidden;
534
+ margin: 10px 0;
535
+ }
536
+ .performance-fill {
537
+ height: 100%;
538
+ background: linear-gradient(90deg, #10b981, #38bdf8);
539
+ transition: width 1s ease;
540
+ }
383
541
  </style>
384
542
  </head>
385
543
  <body>
386
- <h1>\u{1F916} OpenQA Dashboard</h1>
387
- <nav>
388
- <a href="/">Dashboard</a>
389
- <a href="/kanban">Kanban</a>
390
- <a href="/config">Config</a>
391
- </nav>
392
- <div class="card">
393
- <h2>Status</h2>
394
- <p>Agent: <span class="status idle">Idle</span></p>
395
- <p>Target: ${cfg.saas.url || "Not configured"}</p>
396
- <p>Auto-start: ${cfg.agent.autoStart ? "Enabled" : "Disabled"}</p>
544
+ <div class="nav">
545
+ <div class="nav-links">
546
+ <a href="/" class="active">\u{1F4CA} Dashboard</a>
547
+ <a href="/kanban">\u{1F4CB} Kanban</a>
548
+ <a href="/config">\u2699\uFE0F Config</a>
549
+ </div>
550
+ <div>
551
+ <span id="connection-status" class="status idle">\u{1F50C} Connecting...</span>
552
+ </div>
397
553
  </div>
398
- <div class="card">
399
- <h2>Quick Links</h2>
400
- <ul>
401
- <li><a href="/kanban">View Kanban Board</a></li>
402
- <li><a href="/config">Configure OpenQA</a></li>
403
- </ul>
554
+
555
+ <!-- Key Metrics -->
556
+ <div class="grid-4">
557
+ <div class="metric-card">
558
+ <div class="metric-label">\u{1F916} Active Agents</div>
559
+ <div class="metric-value" id="active-agents">0</div>
560
+ <div class="metric-change positive">\u2191 2 from last hour</div>
561
+ </div>
562
+ <div class="metric-card">
563
+ <div class="metric-label">\u{1F4CB} Total Actions</div>
564
+ <div class="metric-value" id="total-actions">0</div>
565
+ <div class="metric-change positive">\u2191 12% increase</div>
566
+ </div>
567
+ <div class="metric-card">
568
+ <div class="metric-label">\u{1F41B} Bugs Found</div>
569
+ <div class="metric-value" id="bugs-found">0</div>
570
+ <div class="metric-change negative">\u2193 3 from yesterday</div>
571
+ </div>
572
+ <div class="metric-card">
573
+ <div class="metric-label">\u26A1 Success Rate</div>
574
+ <div class="metric-value" id="success-rate">0%</div>
575
+ <div class="metric-change positive">\u2191 5% improvement</div>
576
+ </div>
577
+ </div>
578
+
579
+ <!-- Charts and Hierarchy -->
580
+ <div class="grid-2">
581
+ <div class="card">
582
+ <div class="card-header">
583
+ <h2 class="card-title">\u{1F4C8} Performance Metrics</h2>
584
+ </div>
585
+ <div class="tabs">
586
+ <div class="tab active" onclick="switchTab('performance')">Performance</div>
587
+ <div class="tab" onclick="switchTab('activity')">Activity</div>
588
+ <div class="tab" onclick="switchTab('errors')">Error Rate</div>
589
+ </div>
590
+ <div class="chart-container">
591
+ <canvas id="performanceChart"></canvas>
592
+ </div>
593
+ <div class="chart-container" style="display: none;">
594
+ <canvas id="activityChart"></canvas>
595
+ </div>
596
+ <div class="chart-container" style="display: none;">
597
+ <canvas id="errorChart"></canvas>
598
+ </div>
599
+ </div>
600
+
601
+ <div class="card">
602
+ <div class="card-header">
603
+ <h2 class="card-title">\u{1F310} Agent Hierarchy</h2>
604
+ </div>
605
+ <div class="hierarchy-container" id="hierarchy-container"></div>
606
+ </div>
404
607
  </div>
608
+
609
+ <!-- Agent Details -->
405
610
  <div class="card">
406
- <h2>Getting Started</h2>
407
- <p>Configure your SaaS application target and start testing:</p>
408
- <ol>
409
- <li>Set SAAS_URL environment variable or use the <a href="/config">Config page</a></li>
410
- <li>Enable auto-start: <code>export AGENT_AUTO_START=true</code></li>
411
- <li>Restart OpenQA</li>
412
- </ol>
611
+ <div class="card-header">
612
+ <h2 class="card-title">\u{1F916} Agent Details</h2>
613
+ </div>
614
+ <div class="tabs">
615
+ <div class="tab active" onclick="switchAgentTab('active')">Active Agents</div>
616
+ <div class="tab" onclick="switchAgentTab('specialists')">Specialists</div>
617
+ <div class="tab" onclick="switchAgentTab('performance')">Performance</div>
618
+ </div>
619
+ <div id="active-agents-content" class="tab-content active">
620
+ <div id="active-agents-list">
621
+ <p style="color: #64748b;">Loading agents...</p>
622
+ </div>
623
+ </div>
624
+ <div id="specialists-content" class="tab-content">
625
+ <div id="specialists-list">
626
+ <p style="color: #64748b;">No specialists active</p>
627
+ </div>
628
+ </div>
629
+ <div id="performance-content" class="tab-content">
630
+ <div id="performance-metrics">
631
+ <p style="color: #64748b;">Performance data loading...</p>
632
+ </div>
633
+ </div>
634
+ </div>
635
+
636
+ <!-- Activity and Interventions -->
637
+ <div class="grid-2">
638
+ <div class="card">
639
+ <div class="card-header">
640
+ <h2 class="card-title">\u26A1 Recent Activity</h2>
641
+ </div>
642
+ <div id="recent-activities" style="max-height: 400px; overflow-y: auto;">
643
+ <div class="activity-item">
644
+ <div>\u{1F504} Waiting for agent activity...</div>
645
+ <div class="activity-time">System ready</div>
646
+ </div>
647
+ </div>
648
+ </div>
649
+
650
+ <div class="card">
651
+ <div class="card-header">
652
+ <h2 class="card-title">\u{1F6A8} Human Interventions</h2>
653
+ </div>
654
+ <div id="interventions-list" style="max-height: 400px; overflow-y: auto;">
655
+ <p style="color: #64748b;">No interventions required</p>
656
+ </div>
657
+ </div>
658
+ </div>
659
+
660
+ <!-- Tasks and Issues -->
661
+ <div class="grid-2">
662
+ <div class="card">
663
+ <div class="card-header">
664
+ <h2 class="card-title">\u{1F4DD} Current Tasks</h2>
665
+ </div>
666
+ <div id="current-tasks" style="max-height: 400px; overflow-y: auto;">
667
+ <p style="color: #64748b;">No active tasks</p>
668
+ </div>
669
+ </div>
670
+
671
+ <div class="card">
672
+ <div class="card-header">
673
+ <h2 class="card-title">\u26A0\uFE0F Issues Encountered</h2>
674
+ </div>
675
+ <div id="issues-list" style="max-height: 400px; overflow-y: auto;">
676
+ <p style="color: #64748b;">No issues</p>
677
+ </div>
678
+ </div>
679
+ </div>
680
+
681
+ <div class="grid">
682
+ <div class="card">
683
+ <h2>\u{1F916} Active Agents</h2>
684
+ <div id="active-agents-list">
685
+ <p style="color: #64748b;">No active agents</p>
686
+ </div>
687
+ </div>
688
+
689
+ <div class="card">
690
+ <h2>\u{1F6A8} Human Interventions</h2>
691
+ <div id="interventions-list">
692
+ <p style="color: #64748b;">No interventions required</p>
693
+ </div>
694
+ </div>
695
+ </div>
696
+
697
+ <div class="grid">
698
+ <div class="card">
699
+ <h2>\u{1F4DD} Current Tasks</h2>
700
+ <div id="current-tasks">
701
+ <p style="color: #64748b;">No active tasks</p>
702
+ </div>
703
+ </div>
704
+
705
+ <div class="card">
706
+ <h2>\u26A0\uFE0F Issues Encountered</h2>
707
+ <div id="issues-list">
708
+ <p style="color: #64748b;">No issues</p>
709
+ </div>
710
+ </div>
413
711
  </div>
712
+
713
+ <script>
714
+ let ws;
715
+ let activities = [];
716
+ let performanceChart, activityChart, errorChart;
717
+ let hierarchyNetwork;
718
+
719
+ // Initialize Charts
720
+ function initCharts() {
721
+ // Performance Chart
722
+ const perfCtx = document.getElementById('performanceChart').getContext('2d');
723
+ performanceChart = new Chart(perfCtx, {
724
+ type: 'line',
725
+ data: {
726
+ labels: ['00:00', '04:00', '08:00', '12:00', '16:00', '20:00', '24:00'],
727
+ datasets: [{
728
+ label: 'Actions/min',
729
+ data: [12, 19, 15, 25, 22, 30, 28],
730
+ borderColor: '#38bdf8',
731
+ backgroundColor: 'rgba(56, 189, 248, 0.1)',
732
+ tension: 0.4
733
+ }, {
734
+ label: 'Success Rate %',
735
+ data: [85, 88, 82, 91, 87, 93, 89],
736
+ borderColor: '#10b981',
737
+ backgroundColor: 'rgba(16, 185, 129, 0.1)',
738
+ tension: 0.4
739
+ }]
740
+ },
741
+ options: {
742
+ responsive: true,
743
+ maintainAspectRatio: false,
744
+ plugins: {
745
+ legend: { labels: { color: '#e2e8f0' } }
746
+ },
747
+ scales: {
748
+ x: { ticks: { color: '#94a3b8' }, grid: { color: '#334155' } },
749
+ y: { ticks: { color: '#94a3b8' }, grid: { color: '#334155' } }
750
+ }
751
+ }
752
+ });
753
+
754
+ // Activity Chart
755
+ const actCtx = document.getElementById('activityChart').getContext('2d');
756
+ activityChart = new Chart(actCtx, {
757
+ type: 'bar',
758
+ data: {
759
+ labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
760
+ datasets: [{
761
+ label: 'Tests Generated',
762
+ data: [65, 78, 90, 81, 56, 45, 30],
763
+ backgroundColor: '#38bdf8'
764
+ }, {
765
+ label: 'Bugs Found',
766
+ data: [12, 19, 15, 25, 22, 15, 8],
767
+ backgroundColor: '#ef4444'
768
+ }]
769
+ },
770
+ options: {
771
+ responsive: true,
772
+ maintainAspectRatio: false,
773
+ plugins: {
774
+ legend: { labels: { color: '#e2e8f0' } }
775
+ },
776
+ scales: {
777
+ x: { ticks: { color: '#94a3b8' }, grid: { color: '#334155' } },
778
+ y: { ticks: { color: '#94a3b8' }, grid: { color: '#334155' } }
779
+ }
780
+ }
781
+ });
782
+
783
+ // Error Rate Chart
784
+ const errCtx = document.getElementById('errorChart').getContext('2d');
785
+ errorChart = new Chart(errCtx, {
786
+ type: 'doughnut',
787
+ data: {
788
+ labels: ['Success', 'Warnings', 'Errors', 'Critical'],
789
+ datasets: [{
790
+ data: [75, 15, 8, 2],
791
+ backgroundColor: ['#10b981', '#f59e0b', '#ef4444', '#dc2626']
792
+ }]
793
+ },
794
+ options: {
795
+ responsive: true,
796
+ maintainAspectRatio: false,
797
+ plugins: {
798
+ legend: { labels: { color: '#e2e8f0' } }
799
+ }
800
+ }
801
+ });
802
+ }
803
+
804
+ // Initialize Agent Hierarchy
805
+ function initHierarchy() {
806
+ const container = document.getElementById('hierarchy-container');
807
+
808
+ const nodes = [
809
+ { id: 'main', label: 'Main Agent', shape: 'box', color: '#38bdf8' },
810
+ { id: 'browser', label: 'Browser Specialist', shape: 'box', color: '#10b981' },
811
+ { id: 'api', label: 'API Tester', shape: 'box', color: '#f59e0b' },
812
+ { id: 'auth', label: 'Auth Specialist', shape: 'box', color: '#ef4444' },
813
+ { id: 'ui', label: 'UI Tester', shape: 'box', color: '#8b5cf6' },
814
+ { id: 'perf', label: 'Performance Tester', shape: 'box', color: '#06b6d4' },
815
+ { id: 'security', label: 'Security Scanner', shape: 'box', color: '#f97316' }
816
+ ];
817
+
818
+ const edges = [
819
+ { from: 'main', to: 'browser' },
820
+ { from: 'main', to: 'api' },
821
+ { from: 'main', to: 'auth' },
822
+ { from: 'browser', to: 'ui' },
823
+ { from: 'api', to: 'perf' },
824
+ { from: 'auth', to: 'security' }
825
+ ];
826
+
827
+ const data = { nodes, edges };
828
+
829
+ hierarchyNetwork = new vis.Network(container, data, {
830
+ nodes: {
831
+ font: { color: '#e2e8f0', size: 14 },
832
+ borderWidth: 2,
833
+ shadow: true
834
+ },
835
+ edges: {
836
+ color: { color: '#334155' },
837
+ width: 2,
838
+ shadow: true
839
+ },
840
+ physics: {
841
+ enabled: true,
842
+ stabilization: { iterations: 100 }
843
+ },
844
+ interaction: {
845
+ hover: true,
846
+ tooltipDelay: 200
847
+ }
848
+ });
849
+ }
850
+
851
+ // Tab switching
852
+ function switchTab(tabName) {
853
+ // Hide all chart containers
854
+ document.querySelectorAll('.chart-container').forEach(container => {
855
+ container.style.display = 'none';
856
+ });
857
+
858
+ // Remove active class from all tabs
859
+ document.querySelectorAll('.tab').forEach(tab => {
860
+ tab.classList.remove('active');
861
+ });
862
+
863
+ // Show selected chart and activate tab
864
+ if (tabName === 'performance') {
865
+ document.querySelectorAll('.chart-container')[0].style.display = 'block';
866
+ document.querySelectorAll('.tab')[0].classList.add('active');
867
+ } else if (tabName === 'activity') {
868
+ document.querySelectorAll('.chart-container')[1].style.display = 'block';
869
+ document.querySelectorAll('.tab')[1].classList.add('active');
870
+ } else if (tabName === 'errors') {
871
+ document.querySelectorAll('.chart-container')[2].style.display = 'block';
872
+ document.querySelectorAll('.tab')[2].classList.add('active');
873
+ }
874
+ }
875
+
876
+ // Agent tab switching
877
+ function switchAgentTab(tabName) {
878
+ // Hide all tab contents
879
+ document.querySelectorAll('.tab-content').forEach(content => {
880
+ content.classList.remove('active');
881
+ });
882
+
883
+ // Remove active class from all tabs
884
+ document.querySelectorAll('.tab').forEach(tab => {
885
+ tab.classList.remove('active');
886
+ });
887
+
888
+ // Show selected content
889
+ if (tabName === 'active') {
890
+ document.getElementById('active-agents-content').classList.add('active');
891
+ } else if (tabName === 'specialists') {
892
+ document.getElementById('specialists-content').classList.add('active');
893
+ } else if (tabName === 'performance') {
894
+ document.getElementById('performance-content').classList.add('active');
895
+ }
896
+ }
897
+
898
+ function connectWebSocket() {
899
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
900
+ ws = new WebSocket(\`\${protocol}//\${window.location.host}\`);
901
+
902
+ ws.onopen = () => {
903
+ document.getElementById('connection-status').textContent = '\u{1F7E2} Connected';
904
+ document.getElementById('connection-status').className = 'status running';
905
+ };
906
+
907
+ ws.onclose = () => {
908
+ document.getElementById('connection-status').textContent = '\u{1F534} Disconnected';
909
+ document.getElementById('connection-status').className = 'status error';
910
+ // Reconnect after 3 seconds
911
+ setTimeout(connectWebSocket, 3000);
912
+ };
913
+
914
+ ws.onmessage = (event) => {
915
+ const data = JSON.parse(event.data);
916
+ handleWebSocketMessage(data);
917
+ };
918
+ }
919
+
920
+ function handleWebSocketMessage(data) {
921
+ switch(data.type) {
922
+ case 'status':
923
+ updateAgentStatus(data.data);
924
+ break;
925
+ case 'activity':
926
+ addActivity(data.data);
927
+ break;
928
+ case 'intervention':
929
+ addIntervention(data.data);
930
+ break;
931
+ case 'agents':
932
+ updateActiveAgents(data.data);
933
+ break;
934
+ case 'session':
935
+ updateSessionMetrics(data.data);
936
+ break;
937
+ }
938
+ }
939
+
940
+ function updateAgentStatus(status) {
941
+ const statusEl = document.getElementById('agent-status');
942
+ statusEl.textContent = status.isRunning ? 'Running' : 'Idle';
943
+ statusEl.className = \`status \${status.isRunning ? 'running' : 'idle'}\`;
944
+ }
945
+
946
+ function updateSessionMetrics(session) {
947
+ document.getElementById('total-actions').textContent = session.total_actions || 0;
948
+ document.getElementById('bugs-found').textContent = session.bugs_found || 0;
949
+ document.getElementById('session-id').textContent = session.id || 'None';
950
+
951
+ const successRate = session.total_actions > 0
952
+ ? Math.round(((session.total_actions - (session.errors || 0)) / session.total_actions) * 100)
953
+ : 0;
954
+ document.getElementById('success-rate').textContent = successRate + '%';
955
+ }
956
+
957
+ function addActivity(activity) {
958
+ activities.unshift(activity);
959
+ if (activities.length > 10) activities.pop();
960
+
961
+ const container = document.getElementById('recent-activities');
962
+ container.innerHTML = activities.map(a => \`
963
+ <div class="activity-item \${a.type}">
964
+ <div>\${a.message}</div>
965
+ <div class="activity-time">\${new Date(a.timestamp).toLocaleTimeString()}</div>
966
+ </div>
967
+ \`).join('');
968
+ }
969
+
970
+ function updateActiveAgents(agents) {
971
+ const countEl = document.getElementById('active-agents');
972
+ const listEl = document.getElementById('active-agents-list');
973
+
974
+ countEl.textContent = agents.length;
975
+
976
+ if (agents.length === 0) {
977
+ listEl.innerHTML = '<p style="color: #64748b;">No active agents</p>';
978
+ } else {
979
+ listEl.innerHTML = agents.map(agent => \`
980
+ <div class="activity-item">
981
+ <div><strong>\${agent.name}</strong> - \${agent.status}</div>
982
+ <div class="activity-time">Purpose: \${agent.purpose}</div>
983
+ </div>
984
+ \`).join('');
985
+ }
986
+ }
987
+
988
+ function addIntervention(intervention) {
989
+ const container = document.getElementById('interventions-list');
990
+ const interventionEl = document.createElement('div');
991
+ interventionEl.className = 'intervention-request';
992
+ interventionEl.innerHTML = \`
993
+ <h4>\u{1F6A8} \${intervention.title}</h4>
994
+ <p>\${intervention.description}</p>
995
+ <div>
996
+ <button class="btn btn-success" onclick="respondToIntervention('\${intervention.id}', 'approve')">\u2705 Approve</button>
997
+ <button class="btn btn-danger" onclick="respondToIntervention('\${intervention.id}', 'reject')">\u274C Reject</button>
998
+ </div>
999
+ \`;
1000
+ container.appendChild(interventionEl);
1001
+ }
1002
+
1003
+ function respondToIntervention(interventionId, response) {
1004
+ fetch('/api/intervention/' + interventionId, {
1005
+ method: 'POST',
1006
+ headers: { 'Content-Type': 'application/json' },
1007
+ body: JSON.stringify({ response })
1008
+ }).then(() => {
1009
+ // Remove the intervention from UI
1010
+ location.reload();
1011
+ });
1012
+ }
1013
+
1014
+ // Start WebSocket connection
1015
+ connectWebSocket();
1016
+
1017
+ // Enhanced update functions with professional UI
1018
+ function updateActiveAgents(agents) {
1019
+ const countEl = document.getElementById('active-agents');
1020
+ const listEl = document.getElementById('active-agents-list');
1021
+
1022
+ countEl.textContent = agents.length;
1023
+
1024
+ if (agents.length === 0) {
1025
+ listEl.innerHTML = '<p style="color: #64748b;">No active agents</p>';
1026
+ } else {
1027
+ listEl.innerHTML = agents.map(agent => \`
1028
+ <div class="activity-item">
1029
+ <div style="display: flex; justify-content: space-between; align-items: center;">
1030
+ <div>
1031
+ <strong>\${agent.name}</strong>
1032
+ <span class="status \${agent.status.toLowerCase()}">\${agent.status}</span>
1033
+ </div>
1034
+ <div style="text-align: right;">
1035
+ <div class="performance-bar" style="width: 100px;">
1036
+ <div class="performance-fill" style="width: \${agent.performance || 75}%"></div>
1037
+ </div>
1038
+ <small style="color: #64748b;">\${agent.performance || 75}%</small>
1039
+ </div>
1040
+ </div>
1041
+ <div class="activity-time">Purpose: \${agent.purpose} | Tasks: \${agent.tasks || 0}</div>
1042
+ </div>
1043
+ \`).join('');
1044
+ }
1045
+ }
1046
+
1047
+ function updateCurrentTasks(tasks) {
1048
+ const container = document.getElementById('current-tasks');
1049
+ if (tasks.length === 0) {
1050
+ container.innerHTML = '<p style="color: #64748b;">No active tasks</p>';
1051
+ } else {
1052
+ container.innerHTML = tasks.map(task => \`
1053
+ <div class="activity-item">
1054
+ <div style="display: flex; justify-content: space-between; align-items: center;">
1055
+ <div>
1056
+ <strong>\${task.name}</strong>
1057
+ <span class="status \${task.status.replace(' ', '-')}">\${task.status}</span>
1058
+ </div>
1059
+ \${task.progress ? \`<div style="color: #38bdf8;">\${task.progress}</div>\` : ''}
1060
+ </div>
1061
+ <div class="activity-time">Agent: \${task.agent} | Started: \${new Date(task.started_at).toLocaleTimeString()}</div>
1062
+ \${task.result ? \`<div style="color: #10b981; margin-top: 8px; padding: 8px; background: rgba(16, 185, 129, 0.1); border-radius: 6px;">\${task.result}</div>\` : ''}
1063
+ </div>
1064
+ \`).join('');
1065
+ }
1066
+ }
1067
+
1068
+ function updateIssues(issues) {
1069
+ const container = document.getElementById('issues-list');
1070
+ if (!container) return;
1071
+
1072
+ if (issues.length === 0) {
1073
+ container.innerHTML = '<p style="color: #64748b;">No issues</p>';
1074
+ } else {
1075
+ container.innerHTML = issues.map(issue => \`
1076
+ <div class="activity-item \${issue.severity}">
1077
+ <div style="display: flex; justify-content: space-between; align-items: center;">
1078
+ <div>
1079
+ <strong>\${issue.type}</strong> - \${issue.message}
1080
+ </div>
1081
+ <span class="status \${issue.status}">\${issue.status}</span>
1082
+ </div>
1083
+ <div class="activity-time">Agent: \${issue.agent} | \${new Date(issue.timestamp).toLocaleTimeString()}</div>
1084
+ </div>
1085
+ \`).join('');
1086
+ }
1087
+ }
1088
+
1089
+ // Update specialists tab
1090
+ function updateSpecialists() {
1091
+ const specialists = [
1092
+ { name: 'Browser Specialist', status: 'active', performance: 85, tasks: 12 },
1093
+ { name: 'API Tester', status: 'active', performance: 92, tasks: 8 },
1094
+ { name: 'Auth Specialist', status: 'idle', performance: 78, tasks: 0 },
1095
+ { name: 'UI Tester', status: 'active', performance: 88, tasks: 15 },
1096
+ { name: 'Performance Tester', status: 'idle', performance: 95, tasks: 0 }
1097
+ ];
1098
+
1099
+ const container = document.getElementById('specialists-list');
1100
+ container.innerHTML = specialists.map(specialist => \`
1101
+ <div class="activity-item">
1102
+ <div style="display: flex; justify-content: space-between; align-items: center;">
1103
+ <div>
1104
+ <strong>\${specialist.name}</strong>
1105
+ <span class="status \${specialist.status}">\${specialist.status}</span>
1106
+ </div>
1107
+ <div style="text-align: right;">
1108
+ <div class="performance-bar" style="width: 100px;">
1109
+ <div class="performance-fill" style="width: \${specialist.performance}%"></div>
1110
+ </div>
1111
+ <small style="color: #64748b;">\${specialist.performance}%</small>
1112
+ </div>
1113
+ </div>
1114
+ <div class="activity-time">Tasks completed: \${specialist.tasks}</div>
1115
+ </div>
1116
+ \`).join('');
1117
+ }
1118
+
1119
+ // Update performance metrics
1120
+ function updatePerformanceMetrics() {
1121
+ const container = document.getElementById('performance-metrics');
1122
+ container.innerHTML = \`
1123
+ <div class="grid-2">
1124
+ <div class="metric-card">
1125
+ <div class="metric-label">\u{1F680} Avg Response Time</div>
1126
+ <div class="metric-value">245ms</div>
1127
+ <div class="metric-change positive">\u2193 12% faster</div>
1128
+ </div>
1129
+ <div class="metric-card">
1130
+ <div class="metric-label">\u{1F4BE} Memory Usage</div>
1131
+ <div class="metric-value">128MB</div>
1132
+ <div class="metric-change positive">\u2193 8% optimized</div>
1133
+ </div>
1134
+ <div class="metric-card">
1135
+ <div class="metric-label">\u26A1 CPU Usage</div>
1136
+ <div class="metric-value">34%</div>
1137
+ <div class="metric-change negative">\u2191 5% increase</div>
1138
+ </div>
1139
+ <div class="metric-card">
1140
+ <div class="metric-label">\u{1F504} Throughput</div>
1141
+ <div class="metric-value">1.2k/h</div>
1142
+ <div class="metric-change positive">\u2191 18% increase</div>
1143
+ </div>
1144
+ </div>
1145
+ \`;
1146
+ }
1147
+
1148
+ // Initialize everything on load
1149
+ window.addEventListener('load', () => {
1150
+ initCharts();
1151
+ initHierarchy();
1152
+ updateSpecialists();
1153
+ updatePerformanceMetrics();
1154
+
1155
+ // Load initial data
1156
+ fetch('/api/status').then(r => r.json()).then(updateAgentStatus);
1157
+ fetch('/api/sessions?limit=1').then(r => r.json()).then(sessions => {
1158
+ if (sessions.length > 0) updateSessionMetrics(sessions[0]);
1159
+ });
1160
+ fetch('/api/tasks').then(r => r.json()).then(updateCurrentTasks);
1161
+ fetch('/api/issues').then(r => r.json()).then(updateIssues);
1162
+ });
1163
+
1164
+ // Start WebSocket connection
1165
+ connectWebSocket();
1166
+ </script>
414
1167
  </body>
415
1168
  </html>
416
1169
  `);
@@ -708,10 +1461,46 @@ openqa start</code></pre>
708
1461
  wss.emit("connection", ws, request);
709
1462
  });
710
1463
  });
1464
+ function broadcast(message) {
1465
+ const data = JSON.stringify(message);
1466
+ wss.clients.forEach((client) => {
1467
+ if (client.readyState === 1) {
1468
+ client.send(data);
1469
+ }
1470
+ });
1471
+ }
711
1472
  wss.on("connection", (ws) => {
712
1473
  console.log("WebSocket client connected");
1474
+ ws.send(JSON.stringify({
1475
+ type: "status",
1476
+ data: { isRunning: false, target: cfg.saas.url || "Not configured" }
1477
+ }));
1478
+ ws.send(JSON.stringify({
1479
+ type: "agents",
1480
+ data: [
1481
+ { name: "Main Agent", status: "idle", purpose: "Autonomous testing" }
1482
+ ]
1483
+ }));
1484
+ let activityCount = 0;
1485
+ const activityInterval = setInterval(() => {
1486
+ if (ws.readyState === ws.OPEN) {
1487
+ const activities = [
1488
+ { type: "info", message: "\u{1F50D} Scanning application for test targets", timestamp: (/* @__PURE__ */ new Date()).toISOString() },
1489
+ { type: "success", message: "\u2705 Found 5 testable components", timestamp: (/* @__PURE__ */ new Date()).toISOString() },
1490
+ { type: "warning", message: "\u26A0\uFE0F Authentication required for admin area", timestamp: (/* @__PURE__ */ new Date()).toISOString() },
1491
+ { type: "info", message: "\u{1F9EA} Generating test scenarios", timestamp: (/* @__PURE__ */ new Date()).toISOString() },
1492
+ { type: "success", message: "\u2705 Created 3 test cases", timestamp: (/* @__PURE__ */ new Date()).toISOString() }
1493
+ ];
1494
+ ws.send(JSON.stringify({
1495
+ type: "activity",
1496
+ data: activities[activityCount % activities.length]
1497
+ }));
1498
+ activityCount++;
1499
+ }
1500
+ }, 5e3);
713
1501
  ws.on("close", () => {
714
1502
  console.log("WebSocket client disconnected");
1503
+ clearInterval(activityInterval);
715
1504
  });
716
1505
  });
717
1506
  process.on("SIGTERM", () => {