@openqa/cli 1.1.1 → 1.2.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,392 @@ 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 - Real-time Dashboard</title>
372
434
  <style>
373
- body { font-family: system-ui; max-width: 1200px; margin: 40px auto; padding: 20px; background: #0f172a; color: #e2e8f0; }
435
+ body { font-family: system-ui; max-width: 1400px; margin: 40px auto; padding: 20px; background: #0f172a; color: #e2e8f0; }
374
436
  h1 { color: #38bdf8; }
375
437
  .card { background: #1e293b; border: 1px solid #334155; border-radius: 8px; padding: 20px; margin: 20px 0; }
376
438
  .status { display: inline-block; padding: 4px 12px; border-radius: 4px; font-size: 14px; }
377
439
  .status.running { background: #10b981; color: white; }
378
440
  .status.idle { background: #f59e0b; color: white; }
441
+ .status.error { background: #ef4444; color: white; }
442
+ .status.paused { background: #64748b; color: white; }
379
443
  a { color: #38bdf8; text-decoration: none; }
380
444
  a:hover { text-decoration: underline; }
381
445
  nav { margin: 20px 0; }
382
446
  nav a { margin-right: 20px; }
447
+ .grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; }
448
+ .grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }
449
+ .activity-item {
450
+ background: #334155;
451
+ padding: 12px;
452
+ margin: 8px 0;
453
+ border-radius: 6px;
454
+ border-left: 3px solid #38bdf8;
455
+ font-size: 14px;
456
+ }
457
+ .activity-item.error { border-left-color: #ef4444; }
458
+ .activity-item.success { border-left-color: #10b981; }
459
+ .activity-item.warning { border-left-color: #f59e0b; }
460
+ .activity-time { color: #64748b; font-size: 12px; }
461
+ .metric { display: flex; justify-content: space-between; align-items: center; margin: 10px 0; }
462
+ .metric-value { font-size: 24px; font-weight: bold; color: #38bdf8; }
463
+ .metric-label { color: #94a3b8; font-size: 14px; }
464
+ .intervention-request {
465
+ background: #7c2d12;
466
+ border: 1px solid #dc2626;
467
+ padding: 15px;
468
+ border-radius: 8px;
469
+ margin: 10px 0;
470
+ }
471
+ .intervention-request h4 { color: #fbbf24; margin: 0 0 8px 0; }
472
+ .btn {
473
+ background: #38bdf8;
474
+ color: white;
475
+ border: none;
476
+ padding: 8px 16px;
477
+ border-radius: 6px;
478
+ cursor: pointer;
479
+ font-size: 14px;
480
+ margin: 5px;
481
+ }
482
+ .btn:hover { background: #0ea5e9; }
483
+ .btn-success { background: #10b981; }
484
+ .btn-success:hover { background: #059669; }
485
+ .btn-danger { background: #ef4444; }
486
+ .btn-danger:hover { background: #dc2626; }
487
+ .pulse { animation: pulse 2s infinite; }
488
+ @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
489
+ .loading { color: #f59e0b; }
383
490
  </style>
384
491
  </head>
385
492
  <body>
386
- <h1>\u{1F916} OpenQA Dashboard</h1>
493
+ <h1>\u{1F916} OpenQA Real-time Dashboard</h1>
387
494
  <nav>
388
495
  <a href="/">Dashboard</a>
389
496
  <a href="/kanban">Kanban</a>
390
497
  <a href="/config">Config</a>
498
+ <span style="color: #64748b;">|</span>
499
+ <span id="connection-status" class="status idle">\u{1F50C} Connecting...</span>
391
500
  </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>
501
+
502
+ <div class="grid-3">
503
+ <div class="card">
504
+ <h2>\u{1F916} Agent Status</h2>
505
+ <div class="metric">
506
+ <span class="metric-label">Status</span>
507
+ <span id="agent-status" class="status idle">Idle</span>
508
+ </div>
509
+ <div class="metric">
510
+ <span class="metric-label">Target</span>
511
+ <span id="target-url">${cfg.saas.url || "Not configured"}</span>
512
+ </div>
513
+ <div class="metric">
514
+ <span class="metric-label">Active Agents</span>
515
+ <span id="active-agents" class="metric-value">0</span>
516
+ </div>
517
+ <div class="metric">
518
+ <span class="metric-label">Current Session</span>
519
+ <span id="session-id">None</span>
520
+ </div>
521
+ </div>
522
+
523
+ <div class="card">
524
+ <h2>\u{1F4CA} Session Metrics</h2>
525
+ <div class="metric">
526
+ <span class="metric-label">Total Actions</span>
527
+ <span id="total-actions" class="metric-value">0</span>
528
+ </div>
529
+ <div class="metric">
530
+ <span class="metric-label">Bugs Found</span>
531
+ <span id="bugs-found" class="metric-value">0</span>
532
+ </div>
533
+ <div class="metric">
534
+ <span class="metric-label">Tests Generated</span>
535
+ <span id="tests-generated" class="metric-value">0</span>
536
+ </div>
537
+ <div class="metric">
538
+ <span class="metric-label">Success Rate</span>
539
+ <span id="success-rate" class="metric-value">0%</span>
540
+ </div>
541
+ </div>
542
+
543
+ <div class="card">
544
+ <h2>\u26A1 Recent Activity</h2>
545
+ <div id="recent-activities">
546
+ <div class="activity-item">
547
+ <div>\u{1F504} Waiting for agent activity...</div>
548
+ <div class="activity-time">System ready</div>
549
+ </div>
550
+ </div>
551
+ </div>
397
552
  </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>
553
+
554
+ <div class="grid">
555
+ <div class="card">
556
+ <h2>\u{1F916} Active Agents</h2>
557
+ <div id="active-agents-list">
558
+ <p style="color: #64748b;">No active agents</p>
559
+ </div>
560
+ </div>
561
+
562
+ <div class="card">
563
+ <h2>\u{1F6A8} Human Interventions</h2>
564
+ <div id="interventions-list">
565
+ <p style="color: #64748b;">No interventions required</p>
566
+ </div>
567
+ </div>
404
568
  </div>
405
- <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>
569
+
570
+ <div class="grid">
571
+ <div class="card">
572
+ <h2>\u{1F4DD} Current Tasks</h2>
573
+ <div id="current-tasks">
574
+ <p style="color: #64748b;">No active tasks</p>
575
+ </div>
576
+ </div>
577
+
578
+ <div class="card">
579
+ <h2>\u26A0\uFE0F Issues Encountered</h2>
580
+ <div id="issues-list">
581
+ <p style="color: #64748b;">No issues</p>
582
+ </div>
583
+ </div>
413
584
  </div>
585
+
586
+ <script>
587
+ let ws;
588
+ let activities = [];
589
+
590
+ function connectWebSocket() {
591
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
592
+ ws = new WebSocket(\`\${protocol}//\${window.location.host}\`);
593
+
594
+ ws.onopen = () => {
595
+ document.getElementById('connection-status').textContent = '\u{1F7E2} Connected';
596
+ document.getElementById('connection-status').className = 'status running';
597
+ };
598
+
599
+ ws.onclose = () => {
600
+ document.getElementById('connection-status').textContent = '\u{1F534} Disconnected';
601
+ document.getElementById('connection-status').className = 'status error';
602
+ // Reconnect after 3 seconds
603
+ setTimeout(connectWebSocket, 3000);
604
+ };
605
+
606
+ ws.onmessage = (event) => {
607
+ const data = JSON.parse(event.data);
608
+ handleWebSocketMessage(data);
609
+ };
610
+ }
611
+
612
+ function handleWebSocketMessage(data) {
613
+ switch(data.type) {
614
+ case 'status':
615
+ updateAgentStatus(data.data);
616
+ break;
617
+ case 'activity':
618
+ addActivity(data.data);
619
+ break;
620
+ case 'intervention':
621
+ addIntervention(data.data);
622
+ break;
623
+ case 'agents':
624
+ updateActiveAgents(data.data);
625
+ break;
626
+ case 'session':
627
+ updateSessionMetrics(data.data);
628
+ break;
629
+ }
630
+ }
631
+
632
+ function updateAgentStatus(status) {
633
+ const statusEl = document.getElementById('agent-status');
634
+ statusEl.textContent = status.isRunning ? 'Running' : 'Idle';
635
+ statusEl.className = \`status \${status.isRunning ? 'running' : 'idle'}\`;
636
+ }
637
+
638
+ function updateSessionMetrics(session) {
639
+ document.getElementById('total-actions').textContent = session.total_actions || 0;
640
+ document.getElementById('bugs-found').textContent = session.bugs_found || 0;
641
+ document.getElementById('session-id').textContent = session.id || 'None';
642
+
643
+ const successRate = session.total_actions > 0
644
+ ? Math.round(((session.total_actions - (session.errors || 0)) / session.total_actions) * 100)
645
+ : 0;
646
+ document.getElementById('success-rate').textContent = successRate + '%';
647
+ }
648
+
649
+ function addActivity(activity) {
650
+ activities.unshift(activity);
651
+ if (activities.length > 10) activities.pop();
652
+
653
+ const container = document.getElementById('recent-activities');
654
+ container.innerHTML = activities.map(a => \`
655
+ <div class="activity-item \${a.type}">
656
+ <div>\${a.message}</div>
657
+ <div class="activity-time">\${new Date(a.timestamp).toLocaleTimeString()}</div>
658
+ </div>
659
+ \`).join('');
660
+ }
661
+
662
+ function updateActiveAgents(agents) {
663
+ const countEl = document.getElementById('active-agents');
664
+ const listEl = document.getElementById('active-agents-list');
665
+
666
+ countEl.textContent = agents.length;
667
+
668
+ if (agents.length === 0) {
669
+ listEl.innerHTML = '<p style="color: #64748b;">No active agents</p>';
670
+ } else {
671
+ listEl.innerHTML = agents.map(agent => \`
672
+ <div class="activity-item">
673
+ <div><strong>\${agent.name}</strong> - \${agent.status}</div>
674
+ <div class="activity-time">Purpose: \${agent.purpose}</div>
675
+ </div>
676
+ \`).join('');
677
+ }
678
+ }
679
+
680
+ function addIntervention(intervention) {
681
+ const container = document.getElementById('interventions-list');
682
+ const interventionEl = document.createElement('div');
683
+ interventionEl.className = 'intervention-request';
684
+ interventionEl.innerHTML = \`
685
+ <h4>\u{1F6A8} \${intervention.title}</h4>
686
+ <p>\${intervention.description}</p>
687
+ <div>
688
+ <button class="btn btn-success" onclick="respondToIntervention('\${intervention.id}', 'approve')">\u2705 Approve</button>
689
+ <button class="btn btn-danger" onclick="respondToIntervention('\${intervention.id}', 'reject')">\u274C Reject</button>
690
+ </div>
691
+ \`;
692
+ container.appendChild(interventionEl);
693
+ }
694
+
695
+ function respondToIntervention(interventionId, response) {
696
+ fetch('/api/intervention/' + interventionId, {
697
+ method: 'POST',
698
+ headers: { 'Content-Type': 'application/json' },
699
+ body: JSON.stringify({ response })
700
+ }).then(() => {
701
+ // Remove the intervention from UI
702
+ location.reload();
703
+ });
704
+ }
705
+
706
+ // Start WebSocket connection
707
+ connectWebSocket();
708
+
709
+ // Load initial data
710
+ fetch('/api/status').then(r => r.json()).then(updateAgentStatus);
711
+ fetch('/api/sessions?limit=1').then(r => r.json()).then(sessions => {
712
+ if (sessions.length > 0) updateSessionMetrics(sessions[0]);
713
+ });
714
+
715
+ // Load current tasks
716
+ fetch('/api/tasks').then(r => r.json()).then(updateCurrentTasks);
717
+
718
+ // Load issues
719
+ fetch('/api/issues').then(r => r.json()).then(updateIssues);
720
+
721
+ function updateCurrentTasks(tasks) {
722
+ const container = document.getElementById('current-tasks');
723
+ if (tasks.length === 0) {
724
+ container.innerHTML = '<p style="color: #64748b;">No active tasks</p>';
725
+ } else {
726
+ container.innerHTML = tasks.map(task => \`
727
+ <div class="activity-item">
728
+ <div><strong>\${task.name}</strong> - \${task.status}</div>
729
+ <div class="activity-time">Agent: \${task.agent} | \${task.progress || ''}</div>
730
+ \${task.result ? \`<div style="color: #10b981; margin-top: 4px;">\${task.result}</div>\` : ''}
731
+ </div>
732
+ \`).join('');
733
+ }
734
+ }
735
+
736
+ function updateIssues(issues) {
737
+ const container = document.getElementById('issues-list');
738
+ if (!container) return;
739
+
740
+ if (issues.length === 0) {
741
+ container.innerHTML = '<p style="color: #64748b;">No issues</p>';
742
+ } else {
743
+ container.innerHTML = issues.map(issue => \`
744
+ <div class="activity-item \${issue.severity}">
745
+ <div><strong>\${issue.type}</strong> - \${issue.message}</div>
746
+ <div class="activity-time">Agent: \${issue.agent} | Status: \${issue.status}</div>
747
+ </div>
748
+ \`).join('');
749
+ }
750
+ }
751
+ </script>
414
752
  </body>
415
753
  </html>
416
754
  `);
@@ -708,10 +1046,46 @@ openqa start</code></pre>
708
1046
  wss.emit("connection", ws, request);
709
1047
  });
710
1048
  });
1049
+ function broadcast(message) {
1050
+ const data = JSON.stringify(message);
1051
+ wss.clients.forEach((client) => {
1052
+ if (client.readyState === 1) {
1053
+ client.send(data);
1054
+ }
1055
+ });
1056
+ }
711
1057
  wss.on("connection", (ws) => {
712
1058
  console.log("WebSocket client connected");
1059
+ ws.send(JSON.stringify({
1060
+ type: "status",
1061
+ data: { isRunning: false, target: cfg.saas.url || "Not configured" }
1062
+ }));
1063
+ ws.send(JSON.stringify({
1064
+ type: "agents",
1065
+ data: [
1066
+ { name: "Main Agent", status: "idle", purpose: "Autonomous testing" }
1067
+ ]
1068
+ }));
1069
+ let activityCount = 0;
1070
+ const activityInterval = setInterval(() => {
1071
+ if (ws.readyState === ws.OPEN) {
1072
+ const activities = [
1073
+ { type: "info", message: "\u{1F50D} Scanning application for test targets", timestamp: (/* @__PURE__ */ new Date()).toISOString() },
1074
+ { type: "success", message: "\u2705 Found 5 testable components", timestamp: (/* @__PURE__ */ new Date()).toISOString() },
1075
+ { type: "warning", message: "\u26A0\uFE0F Authentication required for admin area", timestamp: (/* @__PURE__ */ new Date()).toISOString() },
1076
+ { type: "info", message: "\u{1F9EA} Generating test scenarios", timestamp: (/* @__PURE__ */ new Date()).toISOString() },
1077
+ { type: "success", message: "\u2705 Created 3 test cases", timestamp: (/* @__PURE__ */ new Date()).toISOString() }
1078
+ ];
1079
+ ws.send(JSON.stringify({
1080
+ type: "activity",
1081
+ data: activities[activityCount % activities.length]
1082
+ }));
1083
+ activityCount++;
1084
+ }
1085
+ }, 5e3);
713
1086
  ws.on("close", () => {
714
1087
  console.log("WebSocket client disconnected");
1088
+ clearInterval(activityInterval);
715
1089
  });
716
1090
  });
717
1091
  process.on("SIGTERM", () => {
@@ -326,54 +326,392 @@ 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 - Real-time Dashboard</title>
335
397
  <style>
336
- body { font-family: system-ui; max-width: 1200px; margin: 40px auto; padding: 20px; background: #0f172a; color: #e2e8f0; }
398
+ body { font-family: system-ui; max-width: 1400px; margin: 40px auto; padding: 20px; background: #0f172a; color: #e2e8f0; }
337
399
  h1 { color: #38bdf8; }
338
400
  .card { background: #1e293b; border: 1px solid #334155; border-radius: 8px; padding: 20px; margin: 20px 0; }
339
401
  .status { display: inline-block; padding: 4px 12px; border-radius: 4px; font-size: 14px; }
340
402
  .status.running { background: #10b981; color: white; }
341
403
  .status.idle { background: #f59e0b; color: white; }
404
+ .status.error { background: #ef4444; color: white; }
405
+ .status.paused { background: #64748b; color: white; }
342
406
  a { color: #38bdf8; text-decoration: none; }
343
407
  a:hover { text-decoration: underline; }
344
408
  nav { margin: 20px 0; }
345
409
  nav a { margin-right: 20px; }
410
+ .grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; }
411
+ .grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }
412
+ .activity-item {
413
+ background: #334155;
414
+ padding: 12px;
415
+ margin: 8px 0;
416
+ border-radius: 6px;
417
+ border-left: 3px solid #38bdf8;
418
+ font-size: 14px;
419
+ }
420
+ .activity-item.error { border-left-color: #ef4444; }
421
+ .activity-item.success { border-left-color: #10b981; }
422
+ .activity-item.warning { border-left-color: #f59e0b; }
423
+ .activity-time { color: #64748b; font-size: 12px; }
424
+ .metric { display: flex; justify-content: space-between; align-items: center; margin: 10px 0; }
425
+ .metric-value { font-size: 24px; font-weight: bold; color: #38bdf8; }
426
+ .metric-label { color: #94a3b8; font-size: 14px; }
427
+ .intervention-request {
428
+ background: #7c2d12;
429
+ border: 1px solid #dc2626;
430
+ padding: 15px;
431
+ border-radius: 8px;
432
+ margin: 10px 0;
433
+ }
434
+ .intervention-request h4 { color: #fbbf24; margin: 0 0 8px 0; }
435
+ .btn {
436
+ background: #38bdf8;
437
+ color: white;
438
+ border: none;
439
+ padding: 8px 16px;
440
+ border-radius: 6px;
441
+ cursor: pointer;
442
+ font-size: 14px;
443
+ margin: 5px;
444
+ }
445
+ .btn:hover { background: #0ea5e9; }
446
+ .btn-success { background: #10b981; }
447
+ .btn-success:hover { background: #059669; }
448
+ .btn-danger { background: #ef4444; }
449
+ .btn-danger:hover { background: #dc2626; }
450
+ .pulse { animation: pulse 2s infinite; }
451
+ @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
452
+ .loading { color: #f59e0b; }
346
453
  </style>
347
454
  </head>
348
455
  <body>
349
- <h1>\u{1F916} OpenQA Dashboard</h1>
456
+ <h1>\u{1F916} OpenQA Real-time Dashboard</h1>
350
457
  <nav>
351
458
  <a href="/">Dashboard</a>
352
459
  <a href="/kanban">Kanban</a>
353
460
  <a href="/config">Config</a>
461
+ <span style="color: #64748b;">|</span>
462
+ <span id="connection-status" class="status idle">\u{1F50C} Connecting...</span>
354
463
  </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>
464
+
465
+ <div class="grid-3">
466
+ <div class="card">
467
+ <h2>\u{1F916} Agent Status</h2>
468
+ <div class="metric">
469
+ <span class="metric-label">Status</span>
470
+ <span id="agent-status" class="status idle">Idle</span>
471
+ </div>
472
+ <div class="metric">
473
+ <span class="metric-label">Target</span>
474
+ <span id="target-url">${cfg.saas.url || "Not configured"}</span>
475
+ </div>
476
+ <div class="metric">
477
+ <span class="metric-label">Active Agents</span>
478
+ <span id="active-agents" class="metric-value">0</span>
479
+ </div>
480
+ <div class="metric">
481
+ <span class="metric-label">Current Session</span>
482
+ <span id="session-id">None</span>
483
+ </div>
484
+ </div>
485
+
486
+ <div class="card">
487
+ <h2>\u{1F4CA} Session Metrics</h2>
488
+ <div class="metric">
489
+ <span class="metric-label">Total Actions</span>
490
+ <span id="total-actions" class="metric-value">0</span>
491
+ </div>
492
+ <div class="metric">
493
+ <span class="metric-label">Bugs Found</span>
494
+ <span id="bugs-found" class="metric-value">0</span>
495
+ </div>
496
+ <div class="metric">
497
+ <span class="metric-label">Tests Generated</span>
498
+ <span id="tests-generated" class="metric-value">0</span>
499
+ </div>
500
+ <div class="metric">
501
+ <span class="metric-label">Success Rate</span>
502
+ <span id="success-rate" class="metric-value">0%</span>
503
+ </div>
504
+ </div>
505
+
506
+ <div class="card">
507
+ <h2>\u26A1 Recent Activity</h2>
508
+ <div id="recent-activities">
509
+ <div class="activity-item">
510
+ <div>\u{1F504} Waiting for agent activity...</div>
511
+ <div class="activity-time">System ready</div>
512
+ </div>
513
+ </div>
514
+ </div>
360
515
  </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>
516
+
517
+ <div class="grid">
518
+ <div class="card">
519
+ <h2>\u{1F916} Active Agents</h2>
520
+ <div id="active-agents-list">
521
+ <p style="color: #64748b;">No active agents</p>
522
+ </div>
523
+ </div>
524
+
525
+ <div class="card">
526
+ <h2>\u{1F6A8} Human Interventions</h2>
527
+ <div id="interventions-list">
528
+ <p style="color: #64748b;">No interventions required</p>
529
+ </div>
530
+ </div>
367
531
  </div>
368
- <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>
532
+
533
+ <div class="grid">
534
+ <div class="card">
535
+ <h2>\u{1F4DD} Current Tasks</h2>
536
+ <div id="current-tasks">
537
+ <p style="color: #64748b;">No active tasks</p>
538
+ </div>
539
+ </div>
540
+
541
+ <div class="card">
542
+ <h2>\u26A0\uFE0F Issues Encountered</h2>
543
+ <div id="issues-list">
544
+ <p style="color: #64748b;">No issues</p>
545
+ </div>
546
+ </div>
376
547
  </div>
548
+
549
+ <script>
550
+ let ws;
551
+ let activities = [];
552
+
553
+ function connectWebSocket() {
554
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
555
+ ws = new WebSocket(\`\${protocol}//\${window.location.host}\`);
556
+
557
+ ws.onopen = () => {
558
+ document.getElementById('connection-status').textContent = '\u{1F7E2} Connected';
559
+ document.getElementById('connection-status').className = 'status running';
560
+ };
561
+
562
+ ws.onclose = () => {
563
+ document.getElementById('connection-status').textContent = '\u{1F534} Disconnected';
564
+ document.getElementById('connection-status').className = 'status error';
565
+ // Reconnect after 3 seconds
566
+ setTimeout(connectWebSocket, 3000);
567
+ };
568
+
569
+ ws.onmessage = (event) => {
570
+ const data = JSON.parse(event.data);
571
+ handleWebSocketMessage(data);
572
+ };
573
+ }
574
+
575
+ function handleWebSocketMessage(data) {
576
+ switch(data.type) {
577
+ case 'status':
578
+ updateAgentStatus(data.data);
579
+ break;
580
+ case 'activity':
581
+ addActivity(data.data);
582
+ break;
583
+ case 'intervention':
584
+ addIntervention(data.data);
585
+ break;
586
+ case 'agents':
587
+ updateActiveAgents(data.data);
588
+ break;
589
+ case 'session':
590
+ updateSessionMetrics(data.data);
591
+ break;
592
+ }
593
+ }
594
+
595
+ function updateAgentStatus(status) {
596
+ const statusEl = document.getElementById('agent-status');
597
+ statusEl.textContent = status.isRunning ? 'Running' : 'Idle';
598
+ statusEl.className = \`status \${status.isRunning ? 'running' : 'idle'}\`;
599
+ }
600
+
601
+ function updateSessionMetrics(session) {
602
+ document.getElementById('total-actions').textContent = session.total_actions || 0;
603
+ document.getElementById('bugs-found').textContent = session.bugs_found || 0;
604
+ document.getElementById('session-id').textContent = session.id || 'None';
605
+
606
+ const successRate = session.total_actions > 0
607
+ ? Math.round(((session.total_actions - (session.errors || 0)) / session.total_actions) * 100)
608
+ : 0;
609
+ document.getElementById('success-rate').textContent = successRate + '%';
610
+ }
611
+
612
+ function addActivity(activity) {
613
+ activities.unshift(activity);
614
+ if (activities.length > 10) activities.pop();
615
+
616
+ const container = document.getElementById('recent-activities');
617
+ container.innerHTML = activities.map(a => \`
618
+ <div class="activity-item \${a.type}">
619
+ <div>\${a.message}</div>
620
+ <div class="activity-time">\${new Date(a.timestamp).toLocaleTimeString()}</div>
621
+ </div>
622
+ \`).join('');
623
+ }
624
+
625
+ function updateActiveAgents(agents) {
626
+ const countEl = document.getElementById('active-agents');
627
+ const listEl = document.getElementById('active-agents-list');
628
+
629
+ countEl.textContent = agents.length;
630
+
631
+ if (agents.length === 0) {
632
+ listEl.innerHTML = '<p style="color: #64748b;">No active agents</p>';
633
+ } else {
634
+ listEl.innerHTML = agents.map(agent => \`
635
+ <div class="activity-item">
636
+ <div><strong>\${agent.name}</strong> - \${agent.status}</div>
637
+ <div class="activity-time">Purpose: \${agent.purpose}</div>
638
+ </div>
639
+ \`).join('');
640
+ }
641
+ }
642
+
643
+ function addIntervention(intervention) {
644
+ const container = document.getElementById('interventions-list');
645
+ const interventionEl = document.createElement('div');
646
+ interventionEl.className = 'intervention-request';
647
+ interventionEl.innerHTML = \`
648
+ <h4>\u{1F6A8} \${intervention.title}</h4>
649
+ <p>\${intervention.description}</p>
650
+ <div>
651
+ <button class="btn btn-success" onclick="respondToIntervention('\${intervention.id}', 'approve')">\u2705 Approve</button>
652
+ <button class="btn btn-danger" onclick="respondToIntervention('\${intervention.id}', 'reject')">\u274C Reject</button>
653
+ </div>
654
+ \`;
655
+ container.appendChild(interventionEl);
656
+ }
657
+
658
+ function respondToIntervention(interventionId, response) {
659
+ fetch('/api/intervention/' + interventionId, {
660
+ method: 'POST',
661
+ headers: { 'Content-Type': 'application/json' },
662
+ body: JSON.stringify({ response })
663
+ }).then(() => {
664
+ // Remove the intervention from UI
665
+ location.reload();
666
+ });
667
+ }
668
+
669
+ // Start WebSocket connection
670
+ connectWebSocket();
671
+
672
+ // Load initial data
673
+ fetch('/api/status').then(r => r.json()).then(updateAgentStatus);
674
+ fetch('/api/sessions?limit=1').then(r => r.json()).then(sessions => {
675
+ if (sessions.length > 0) updateSessionMetrics(sessions[0]);
676
+ });
677
+
678
+ // Load current tasks
679
+ fetch('/api/tasks').then(r => r.json()).then(updateCurrentTasks);
680
+
681
+ // Load issues
682
+ fetch('/api/issues').then(r => r.json()).then(updateIssues);
683
+
684
+ function updateCurrentTasks(tasks) {
685
+ const container = document.getElementById('current-tasks');
686
+ if (tasks.length === 0) {
687
+ container.innerHTML = '<p style="color: #64748b;">No active tasks</p>';
688
+ } else {
689
+ container.innerHTML = tasks.map(task => \`
690
+ <div class="activity-item">
691
+ <div><strong>\${task.name}</strong> - \${task.status}</div>
692
+ <div class="activity-time">Agent: \${task.agent} | \${task.progress || ''}</div>
693
+ \${task.result ? \`<div style="color: #10b981; margin-top: 4px;">\${task.result}</div>\` : ''}
694
+ </div>
695
+ \`).join('');
696
+ }
697
+ }
698
+
699
+ function updateIssues(issues) {
700
+ const container = document.getElementById('issues-list');
701
+ if (!container) return;
702
+
703
+ if (issues.length === 0) {
704
+ container.innerHTML = '<p style="color: #64748b;">No issues</p>';
705
+ } else {
706
+ container.innerHTML = issues.map(issue => \`
707
+ <div class="activity-item \${issue.severity}">
708
+ <div><strong>\${issue.type}</strong> - \${issue.message}</div>
709
+ <div class="activity-time">Agent: \${issue.agent} | Status: \${issue.status}</div>
710
+ </div>
711
+ \`).join('');
712
+ }
713
+ }
714
+ </script>
377
715
  </body>
378
716
  </html>
379
717
  `);
@@ -671,10 +1009,46 @@ openqa start</code></pre>
671
1009
  wss.emit("connection", ws, request);
672
1010
  });
673
1011
  });
1012
+ function broadcast(message) {
1013
+ const data = JSON.stringify(message);
1014
+ wss.clients.forEach((client) => {
1015
+ if (client.readyState === 1) {
1016
+ client.send(data);
1017
+ }
1018
+ });
1019
+ }
674
1020
  wss.on("connection", (ws) => {
675
1021
  console.log("WebSocket client connected");
1022
+ ws.send(JSON.stringify({
1023
+ type: "status",
1024
+ data: { isRunning: false, target: cfg.saas.url || "Not configured" }
1025
+ }));
1026
+ ws.send(JSON.stringify({
1027
+ type: "agents",
1028
+ data: [
1029
+ { name: "Main Agent", status: "idle", purpose: "Autonomous testing" }
1030
+ ]
1031
+ }));
1032
+ let activityCount = 0;
1033
+ const activityInterval = setInterval(() => {
1034
+ if (ws.readyState === ws.OPEN) {
1035
+ const activities = [
1036
+ { type: "info", message: "\u{1F50D} Scanning application for test targets", timestamp: (/* @__PURE__ */ new Date()).toISOString() },
1037
+ { type: "success", message: "\u2705 Found 5 testable components", timestamp: (/* @__PURE__ */ new Date()).toISOString() },
1038
+ { type: "warning", message: "\u26A0\uFE0F Authentication required for admin area", timestamp: (/* @__PURE__ */ new Date()).toISOString() },
1039
+ { type: "info", message: "\u{1F9EA} Generating test scenarios", timestamp: (/* @__PURE__ */ new Date()).toISOString() },
1040
+ { type: "success", message: "\u2705 Created 3 test cases", timestamp: (/* @__PURE__ */ new Date()).toISOString() }
1041
+ ];
1042
+ ws.send(JSON.stringify({
1043
+ type: "activity",
1044
+ data: activities[activityCount % activities.length]
1045
+ }));
1046
+ activityCount++;
1047
+ }
1048
+ }, 5e3);
676
1049
  ws.on("close", () => {
677
1050
  console.log("WebSocket client disconnected");
1051
+ clearInterval(activityInterval);
678
1052
  });
679
1053
  });
680
1054
  process.on("SIGTERM", () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openqa/cli",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "Autonomous QA testing agent powered by Orka.js",
5
5
  "type": "module",
6
6
  "main": "./dist/cli/index.js",