← back to Designer Wallcoverings

DW-Agents/dw-agents/agent-dashboard-monitor/dashboard-monitor.ts

372 lines

/**
 * DW-Agents: Dashboard Monitor
 *
 * Single-page dashboard with iframes showing all agents
 * Port: 9893
 */

import express from 'express';
import cookieParser from 'cookie-parser';
import session from 'express-session';
import { requireGlobalAuth } from '../shared-global-auth';

const app = express();
const PORT = process.env.PORT || 9899;  // Changed to 9899 with 30-second refresh

app.use(express.json());
app.use(cookieParser());
app.use(session({
  secret: process.env.SESSION_SECRET || 'dw-dashboard-monitor-secret-2025',
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: false,
    httpOnly: true,
    maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days
  }
}));

// Apply authentication middleware
app.use(requireGlobalAuth);

// Agent list with ports - Updated with currently running agents only
const AGENTS = [
  { name: 'CEO Dashboard', port: 7120, category: 'Executive' },
  { name: 'VP Operations', port: 7125, category: 'Executive' },
  { name: 'Master Hub', port: 9893, category: 'Admin' },
  { name: 'Control Panel', port: 7200, category: 'Admin' },
  { name: 'Digital Samples', port: 9879, category: 'Product' },
  { name: 'Legal Team', port: 9878, category: 'Operations' },
  { name: 'Purchasing Office', port: 9880, category: 'Operations' },
  { name: 'Shopify Store', port: 7238, category: 'Product' },
  { name: 'Trend Research', port: 9883, category: 'Research' },
  { name: 'Server Uptime', port: 9887, category: 'DevOps' },
  { name: 'Log Monitor', port: 7239, category: 'DevOps' },
  { name: 'Needs Attention', port: 9886, category: 'Operations' },
  { name: 'UI Manager', port: 7240, category: 'Design' },
  { name: 'BigQuery Gov', port: 9876, category: 'Data' },
  { name: 'Crash Loop Resolver', port: 9895, category: 'DevOps' }
];

app.get('/', (req, res) => {
  res.send(`
<!DOCTYPE html>
<html>
<head>
  <title>DW-Agents Dashboard Monitor</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body {
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      min-height: 100vh;
      padding: 20px;
    }
    .header {
      background: rgba(255,255,255,0.95);
      padding: 20px 30px;
      border-radius: 15px;
      margin-bottom: 20px;
      box-shadow: 0 10px 30px rgba(0,0,0,0.2);
      display: flex;
      justify-content: space-between;
      align-items: center;
      flex-wrap: wrap;
      gap: 15px;
    }
    .header h1 {
      color: #667eea;
      font-size: 2em;
      display: flex;
      align-items: center;
      gap: 15px;
    }
    .controls {
      display: flex;
      gap: 10px;
      flex-wrap: wrap;
    }
    .btn {
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
      border: none;
      padding: 10px 20px;
      border-radius: 8px;
      cursor: pointer;
      font-weight: 600;
      transition: all 0.3s;
    }
    .btn:hover {
      transform: translateY(-2px);
      box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
    }
    .filter-group {
      display: flex;
      gap: 10px;
      flex-wrap: wrap;
      background: rgba(255,255,255,0.95);
      padding: 15px;
      border-radius: 10px;
      margin-bottom: 20px;
      box-shadow: 0 5px 20px rgba(0,0,0,0.1);
    }
    .filter-btn {
      background: #f0f0f0;
      color: #333;
      border: 2px solid transparent;
      padding: 8px 16px;
      border-radius: 6px;
      cursor: pointer;
      font-weight: 600;
      transition: all 0.2s;
    }
    .filter-btn:hover {
      background: #e0e0e0;
    }
    .filter-btn.active {
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
      border-color: #667eea;
    }
    .grid-container {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(600px, 1fr));
      gap: 20px;
      margin-bottom: 20px;
    }
    .agent-frame {
      background: white;
      border-radius: 15px;
      overflow: hidden;
      box-shadow: 0 10px 30px rgba(0,0,0,0.2);
      transition: all 0.3s;
    }
    .agent-frame:hover {
      transform: translateY(-5px);
      box-shadow: 0 15px 40px rgba(0,0,0,0.3);
    }
    .agent-header {
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
      padding: 15px 20px;
      display: flex;
      justify-content: space-between;
      align-items: center;
    }
    .agent-name {
      font-weight: bold;
      font-size: 1.1em;
    }
    .agent-port {
      background: rgba(255,255,255,0.2);
      padding: 4px 12px;
      border-radius: 12px;
      font-size: 0.9em;
      font-family: monospace;
    }
    .agent-category {
      background: rgba(255,255,255,0.3);
      padding: 4px 12px;
      border-radius: 12px;
      font-size: 0.85em;
      margin-left: 10px;
    }
    iframe {
      width: 100%;
      height: 600px;
      border: none;
      display: block;
    }
    .status-indicator {
      width: 10px;
      height: 10px;
      border-radius: 50%;
      display: inline-block;
      margin-right: 8px;
      animation: pulse 2s infinite;
    }
    .status-online {
      background: #10b981;
      box-shadow: 0 0 10px #10b981;
    }
    .status-offline {
      background: #ef4444;
      box-shadow: 0 0 10px #ef4444;
    }
    @keyframes pulse {
      0%, 100% { opacity: 1; }
      50% { opacity: 0.5; }
    }
    .reload-btn {
      background: rgba(255,255,255,0.2);
      border: 1px solid rgba(255,255,255,0.3);
      color: white;
      padding: 6px 12px;
      border-radius: 6px;
      cursor: pointer;
      font-size: 0.9em;
      transition: all 0.2s;
    }
    .reload-btn:hover {
      background: rgba(255,255,255,0.3);
    }
    @media (max-width: 1400px) {
      .grid-container {
        grid-template-columns: 1fr;
      }
    }
  </style>
</head>
<body>
  <div class="header">
    <h1>
      <span>🎛️</span>
      <span>DW-Agents Dashboard Monitor</span>
    </h1>
    <div class="controls">
      <button class="btn" onclick="reloadAll()">🔄 Reload All</button>
      <button class="btn" onclick="toggleSize()">📏 Toggle Size</button>
      <button class="btn" onclick="checkStatus()">✅ Check Status</button>
    </div>
  </div>

  <div class="filter-group">
    <span style="font-weight: 600; color: #333; margin-right: 10px;">Filter by Category:</span>
    <button class="filter-btn active" onclick="filterCategory('all')">All</button>
    <button class="filter-btn" onclick="filterCategory('Executive')">Executive</button>
    <button class="filter-btn" onclick="filterCategory('Operations')">Operations</button>
    <button class="filter-btn" onclick="filterCategory('Product')">Product</button>
    <button class="filter-btn" onclick="filterCategory('Marketing')">Marketing</button>
    <button class="filter-btn" onclick="filterCategory('Finance')">Finance</button>
    <button class="filter-btn" onclick="filterCategory('DevOps')">DevOps</button>
    <button class="filter-btn" onclick="filterCategory('Data')">Data</button>
    <button class="filter-btn" onclick="filterCategory('Research')">Research</button>
    <button class="filter-btn" onclick="filterCategory('Design')">Design</button>
    <button class="filter-btn" onclick="filterCategory('Support')">Support</button>
    <button class="filter-btn" onclick="filterCategory('Admin')">Admin</button>
  </div>

  <div class="grid-container" id="agentGrid">
    ${AGENTS.map(agent => `
      <div class="agent-frame" data-category="${agent.category}">
        <div class="agent-header">
          <div>
            <span class="status-indicator status-online" id="status-${agent.port}"></span>
            <span class="agent-name">${agent.name}</span>
            <span class="agent-category">${agent.category}</span>
          </div>
          <div>
            <span class="agent-port">:${agent.port}</span>
            <button class="reload-btn" onclick="reloadFrame(${agent.port})">🔄</button>
            <button class="reload-btn" onclick="openInNew(${agent.port})">🔗</button>
          </div>
        </div>
        <iframe
          id="frame-${agent.port}"
          src="http://45.61.58.125:${agent.port}/"
          loading="lazy"
          sandbox="allow-same-origin allow-scripts allow-forms allow-popups"
        ></iframe>
      </div>
    `).join('')}
  </div>

  <script>
    let currentSize = 600;

    function reloadAll() {
      document.querySelectorAll('iframe').forEach(frame => {
        frame.src = frame.src;
      });
    }

    function reloadFrame(port) {
      const frame = document.getElementById('frame-' + port);
      frame.src = frame.src;
    }

    function openInNew(port) {
      window.open('http://45.61.58.125:' + port + '/', '_blank');
    }

    function toggleSize() {
      currentSize = currentSize === 600 ? 400 : currentSize === 400 ? 800 : 600;
      document.querySelectorAll('iframe').forEach(frame => {
        frame.style.height = currentSize + 'px';
      });
    }

    function filterCategory(category) {
      // Update active button
      document.querySelectorAll('.filter-btn').forEach(btn => {
        btn.classList.remove('active');
      });
      event.target.classList.add('active');

      // Filter frames
      document.querySelectorAll('.agent-frame').forEach(frame => {
        if (category === 'all' || frame.dataset.category === category) {
          frame.style.display = 'block';
        } else {
          frame.style.display = 'none';
        }
      });
    }

    async function checkStatus() {
      const ports = ${JSON.stringify(AGENTS.map(a => a.port))};

      for (const port of ports) {
        try {
          const response = await fetch('http://45.61.58.125:' + port + '/', {
            method: 'HEAD',
            mode: 'no-cors'
          });
          const indicator = document.getElementById('status-' + port);
          indicator.className = 'status-indicator status-online';
        } catch (error) {
          const indicator = document.getElementById('status-' + port);
          indicator.className = 'status-indicator status-offline';
        }
      }
    }

    // Check status on load
    setTimeout(checkStatus, 1000);

    // Auto-refresh status every 30 seconds
    setInterval(checkStatus, 30000);
    
    // Auto-refresh the entire page every 30 seconds
    setInterval(() => {
      location.reload();
    }, 30000);
  </script>
</body>
</html>
  `);
});

// Health check endpoint
app.get('/api/health', (req, res) => {
  res.json({
    status: 'online',
    port: PORT,
    agentCount: AGENTS.length,
    uptime: process.uptime()
  });
});

app.listen(PORT, '0.0.0.0', () => {
  console.log('');
  console.log('🎛️  Dashboard Monitor');
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
  console.log('🌍 External: http://45.61.58.125:' + PORT);
  console.log('🏠 Local: http://localhost:' + PORT);
  console.log('');
  console.log('📊 Monitoring ' + AGENTS.length + ' agents');
  console.log('✅ Dashboard ready...');
  console.log('');
});