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