← back to Designer Wallcoverings

DW-Agents/dw-agents/agent-parallel-processes/parallel-processes-agent.ts.backup-oauth-20251112

752 lines

import cookieParser from "cookie-parser";
/**
 * DW-Agents: Parallel Processes Monitor
 *
 * Real-time monitoring of all parallel tasks running across the system:
 * - PM2 managed processes
 * - Background tasks and jobs
 * - Long-running operations
 * - Import/update processes
 * - Shopify sync operations
 *
 * Port: 9887
 */

import express, { Request, Response } from 'express';
import { requireAuth as ssoAuth, handleLogin, setSSOToken, SSO_TOKEN_NAME, SSO_TOKEN_VALUE } from './shared-auth';
import cookieParser from 'cookie-parser';
import session from 'express-session';
import { exec } from 'child_process';
import { promisify } from 'util';
import Anthropic from '@anthropic-ai/sdk';

import { chatMiddleware } from '../shared-chat-integration';

const execAsync = promisify(exec);

const app = express();

// Global Authentication
const PORT = 9887;

// Global Authentication - MUST come before any other middleware
app.use(cookieParser());

// Add inter-agent chat widget (after auth)
app.use(chatMiddleware({
  agentId: 'agent-parallel-processes',
  agentName: 'Parallel Processes',
  port: 9887,
  category: 'agent'
}));


// Anthropic Claude
const anthropic = new Anthropic({
  apiKey: process.env.ANTHROPIC_API_KEY,
});

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

declare module 'express-session' {
  interface SessionData {
    authenticated: boolean;
    chatHistory: Array<{ role: 'user' | 'assistant', content: string }>;
  }
}

app.use(
  session({
    secret: 'dw-parallel-processes-2025',
    resave: false,
    saveUninitialized: true,
    cookie: {
      secure: false,
      httpOnly: true,
      maxAge: 7 * 24 * 60 * 60 * 1000,
    },
  })
);

const requireAuth = (req: Request, res: Response, next: any) => {
  // Check session first
  if (req.session.authenticated) {
    return next();
  }

  // Check SSO cookie
  if (req.cookies && req.cookies[SSO_TOKEN_NAME] === SSO_TOKEN_VALUE) {
    req.session.authenticated = true;
    return next();
  }

  res.redirect('/login');
};

// Login page
app.get('/login', (req: Request, res: Response) => {
  res.send(`
<!DOCTYPE html>
<html>
<head>
  <title>Parallel Processes - Login</title>
  <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%);
      display: flex;
      align-items: center;
      justify-content: center;
      min-height: 100vh;
    }
    .login-container {
      background: white;
      padding: 40px;
      border-radius: 15px;
      box-shadow: 0 15px 35px rgba(0,0,0,0.2);
      max-width: 400px;
      width: 100%;
    }
    h1 { color: #667eea; text-align: center; margin-bottom: 30px; }
    input {
      width: 100%;
      padding: 12px;
      margin: 10px 0;
      border: 2px solid #e0e0e0;
      border-radius: 8px;
    }
    button {
      width: 100%;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
      border: none;
      padding: 15px;
      border-radius: 8px;
      cursor: pointer;
      font-weight: 600;
      margin-top: 10px;
    }
  </style>
</head>
<body>
  <div class="login-container">
    <h1>⚡ Parallel Processes</h1>
    <form method="POST">
      <input type="text" name="username" placeholder="Username" required>
      <input type="password" name="password" placeholder="Password" required>
      <button type="submit">Login</button>
    </form>
  </div>
</body>
</html>
  `);
});

app.post('/login', (req: Request, res: Response) => {
  const { username, password } = req.body;
  if (username === 'admin2025' && password === 'Otis') {
    req.session.authenticated = true;
    req.session.chatHistory = [];

    // Set SSO cookie for cross-agent authentication
    setSSOToken(res);

    res.redirect('/');
  } else {
    res.redirect('/login');
  }
});

// Main dashboard
app.get('/', requireAuth, (req: Request, res: Response) => {
  res.send(`
<!DOCTYPE html>
<html>
<head>
  <title>Parallel Processes Monitor - DW-Agents</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%);
      padding: 20px;
      min-height: 100vh;
    }
    .header {
      background: white;
      padding: 30px;
      border-radius: 15px;
      margin-bottom: 20px;
      box-shadow: 0 5px 20px rgba(0,0,0,0.1);
    }
    .header h1 { color: #667eea; font-size: 2.5em; margin-bottom: 10px; }
    .header .subtitle { color: #666; font-size: 1.1em; }
    .refresh-bar {
      background: white;
      padding: 15px 30px;
      border-radius: 15px;
      margin-bottom: 20px;
      display: flex;
      justify-content: space-between;
      align-items: center;
      box-shadow: 0 5px 20px rgba(0,0,0,0.1);
    }
    .refresh-bar button {
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
      border: none;
      padding: 10px 20px;
      border-radius: 8px;
      cursor: pointer;
      font-weight: 600;
    }
    .summary-cards {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
      gap: 15px;
      margin-bottom: 20px;
    }
    .summary-card {
      background: white;
      padding: 20px;
      border-radius: 12px;
      box-shadow: 0 5px 20px rgba(0,0,0,0.1);
      text-align: center;
    }
    .summary-card .number {
      font-size: 2.5em;
      font-weight: bold;
      color: #667eea;
    }
    .summary-card .label {
      color: #666;
      margin-top: 5px;
    }
    .processes-section {
      background: white;
      padding: 25px;
      border-radius: 15px;
      margin-bottom: 20px;
      box-shadow: 0 5px 20px rgba(0,0,0,0.1);
    }
    .processes-section h2 {
      color: #667eea;
      margin-bottom: 15px;
      font-size: 1.5em;
    }
    .process-table {
      width: 100%;
      border-collapse: collapse;
      font-size: 0.9em;
    }
    .process-table th {
      background: #f8f9fa;
      padding: 12px;
      text-align: left;
      font-weight: 600;
      border-bottom: 2px solid #e0e0e0;
    }
    .process-table td {
      padding: 12px;
      border-bottom: 1px solid #e0e0e0;
    }
    .status-badge {
      display: inline-block;
      padding: 4px 12px;
      border-radius: 12px;
      font-size: 0.85em;
      font-weight: 600;
    }
    .status-online { background: #d1fae5; color: #065f46; }
    .status-stopped { background: #fee2e2; color: #991b1b; }
    .status-errored { background: #fef3c7; color: #92400e; }
    .cpu-bar {
      width: 100px;
      height: 8px;
      background: #e0e0e0;
      border-radius: 4px;
      overflow: hidden;
      display: inline-block;
      vertical-align: middle;
    }
    .cpu-bar-fill {
      height: 100%;
      background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
    }
    /* Chat Interface */
    .chat-button {
      position: fixed;
      bottom: 30px;
      right: 30px;
      width: 60px;
      height: 60px;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
      border: none;
      border-radius: 50%;
      font-size: 1.5em;
      cursor: pointer;
      box-shadow: 0 5px 20px rgba(0,0,0,0.3);
      z-index: 1000;
    }
    .chat-container {
      position: fixed;
      bottom: 100px;
      right: 30px;
      width: 400px;
      height: 600px;
      background: white;
      border-radius: 15px;
      box-shadow: 0 10px 40px rgba(0,0,0,0.3);
      display: none;
      flex-direction: column;
      z-index: 1000;
    }
    .chat-container.open {
      display: flex;
    }
    .chat-header {
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
      padding: 20px;
      border-radius: 15px 15px 0 0;
      font-weight: 600;
      font-size: 1.1em;
    }
    .chat-messages {
      flex: 1;
      overflow-y: auto;
      padding: 20px;
      background: #f8f9fa;
    }
    .chat-message {
      margin-bottom: 15px;
      padding: 12px 16px;
      border-radius: 12px;
      max-width: 80%;
    }
    .chat-message.user {
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
      margin-left: auto;
      text-align: right;
    }
    .chat-message.assistant {
      background: white;
      color: #333;
      border: 1px solid #e0e0e0;
    }
    .chat-input-container {
      padding: 15px;
      border-top: 1px solid #e0e0e0;
      display: flex;
      gap: 10px;
    }
    .chat-input-container input {
      flex: 1;
      padding: 12px;
      border: 2px solid #e0e0e0;
      border-radius: 8px;
      font-size: 1em;
    }
    .chat-input-container button {
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
      border: none;
      padding: 12px 20px;
      border-radius: 8px;
      cursor: pointer;
      font-weight: 600;
    }
    .chat-input-container button:disabled {
      opacity: 0.5;
      cursor: not-allowed;
    }
  </style>
</head>
<body>
  <div class="header">
    <h1>⚡ Parallel Processes Monitor</h1>
    <div class="subtitle">Real-time monitoring of all running processes and background tasks</div>
  </div>

  <div class="refresh-bar">
    <span id="lastUpdate" style="color: #888;">Last updated: --:--</span>
    <button onclick="refreshProcesses()">🔄 Refresh Now</button>
  </div>

  <div class="summary-cards">
    <div class="summary-card">
      <div class="number" id="totalProcesses">0</div>
      <div class="label">Total Processes</div>
    </div>
    <div class="summary-card">
      <div class="number" id="onlineCount">0</div>
      <div class="label">Online</div>
    </div>
    <div class="summary-card">
      <div class="number" id="backgroundCount">0</div>
      <div class="label">Background Tasks</div>
    </div>
    <div class="summary-card">
      <div class="number" id="totalRestarts">0</div>
      <div class="label">Total Restarts</div>
    </div>
  </div>

  <div class="processes-section">
    <h2>⚙️ Background Tasks & Long-Running Jobs</h2>
    <div id="backgroundTasks">Loading...</div>
  </div>

  <div class="processes-section">
    <h2>🖥️ PM2 Managed Processes</h2>
    <div id="pm2Processes">Loading...</div>
  </div>

  <!-- Chat Interface -->
  <button class="chat-button" onclick="toggleChat()">💬</button>

  <div class="chat-container" id="chatContainer">
    <div class="chat-header">💬 Process Monitor Assistant</div>
    <div class="chat-messages" id="chatMessages"></div>
    <div class="chat-input-container">
      <input type="text" id="chatInput" placeholder="Ask about processes..." onkeypress="if(event.key==='Enter') sendMessage()">
      <button onclick="sendMessage()" id="sendButton">Send</button>
    </div>
  </div>

  <script>
    function toggleChat() {
      document.getElementById('chatContainer').classList.toggle('open');
    }

    async function sendMessage() {
      const input = document.getElementById('chatInput');
      const message = input.value.trim();
      if (!message) return;

      const messagesDiv = document.getElementById('chatMessages');
      const sendButton = document.getElementById('sendButton');

      // Add user message
      messagesDiv.innerHTML += \`<div class="chat-message user">\${message}</div>\`;
      input.value = '';
      messagesDiv.scrollTop = messagesDiv.scrollHeight;

      // Disable send button
      sendButton.disabled = true;
      sendButton.textContent = 'Thinking...';

      try {
        const response = await fetch('/api/chat', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ message })
        });

        const data = await response.json();

        // Add assistant message
        messagesDiv.innerHTML += \`<div class="chat-message assistant">\${data.response}</div>\`;
        messagesDiv.scrollTop = messagesDiv.scrollHeight;
      } catch (error) {
        messagesDiv.innerHTML += \`<div class="chat-message assistant">Sorry, I encountered an error. Please try again.</div>\`;
      } finally {
        sendButton.disabled = false;
        sendButton.textContent = 'Send';
      }
    }

    async function refreshProcesses() {
      try {
        const data = await fetch('/api/processes').then(r => r.json());

        // Update summary
        document.getElementById('totalProcesses').textContent = data.pm2.length;
        document.getElementById('onlineCount').textContent = data.pm2.filter(p => p.status === 'online').length;
        document.getElementById('backgroundCount').textContent = data.background.length;
        document.getElementById('totalRestarts').textContent = data.pm2.reduce((sum, p) => sum + p.restarts, 0);

        // PM2 Processes Table
        const pm2Div = document.getElementById('pm2Processes');
        if (data.pm2.length > 0) {
          pm2Div.innerHTML = \`
            <table class="process-table">
              <thead>
                <tr>
                  <th>ID</th>
                  <th>Name</th>
                  <th>Status</th>
                  <th>CPU</th>
                  <th>Memory</th>
                  <th>Uptime</th>
                  <th>Restarts</th>
                </tr>
              </thead>
              <tbody>
                \${data.pm2.map(p => \`
                  <tr>
                    <td>\${p.id}</td>
                    <td>
                      <strong>\${p.name}</strong>
                      <div style="color: #666; font-size: 0.85em; margin-top: 4px;">\${p.description}</div>
                    </td>
                    <td><span class="status-badge status-\${p.status}">\${p.status.toUpperCase()}</span></td>
                    <td>
                      <div class="cpu-bar">
                        <div class="cpu-bar-fill" style="width: \${Math.min(100, parseFloat(p.cpu))}%"></div>
                      </div>
                      \${p.cpu}
                    </td>
                    <td>\${p.memory}</td>
                    <td>\${p.uptime}</td>
                    <td>\${p.restarts}</td>
                  </tr>
                \`).join('')}
              </tbody>
            </table>
          \`;
        } else {
          pm2Div.innerHTML = '<p style="color: #888;">No PM2 processes found</p>';
        }

        // Background Tasks
        const bgDiv = document.getElementById('backgroundTasks');
        if (data.background.length > 0) {
          bgDiv.innerHTML = data.background.map(task => \`
            <div style="background: #f8f9fa; padding: 15px; border-radius: 8px; margin-bottom: 10px; border-left: 4px solid #667eea;">
              <strong style="font-size: 1.1em;">\${task.name}</strong><br>
              <div style="color: #666; font-size: 0.9em; margin-top: 5px; margin-bottom: 8px;">\${task.description}</div>
              <span style="color: #888; font-size: 0.85em;">Command: <code style="background: #e9ecef; padding: 2px 6px; border-radius: 3px;">\${task.command}</code></span><br>
              <span style="color: #888; font-size: 0.85em;">Status: <span style="color: #28a745; font-weight: 600;">\${task.status}</span> | PID: \${task.pid || 'N/A'}</span>
            </div>
          \`).join('');
        } else {
          bgDiv.innerHTML = '<p style="color: #888;">No background tasks running</p>';
        }

        document.getElementById('lastUpdate').textContent = \`Last updated: \${new Date().toLocaleTimeString()}\`;
      } catch (error) {
        console.error('Error fetching processes:', error);
      }
    }

    // Auto-refresh every 5 seconds
    setInterval(refreshProcesses, 5000);
    refreshProcesses();
  </script>
</body>
</html>
  `);
});

// Get description for each process
function getProcessDescription(name: string): string {
  const descriptions: { [key: string]: string } = {
    'dw-master-hub': 'Master Control Hub - Central dashboard for all DW agents',
    'dw-legal-team': 'Legal Team - Settlement compliance monitoring & product review',
    'dw-digital-samples': 'Digital Samples - DIG-series order processing & file delivery',
    'dw-purchasing-office': 'Purchasing (Office) - Office supplies with Amazon price comparison',
    'dw-marketing': 'Marketing - Blog posts, social media & email campaigns',
    'dw-accounting': 'Accounting - Financial management with P&L reports & expense tracking',
    'dw-zendesk-chat': 'Zendesk Chat - Customer support monitoring with ticket integration',
    'dw-trend-research': 'Trend Research - Market intelligence, forecasting & vendor relations',
    'dw-todays-highlights': 'Today\'s Highlights - Daily notable activities across all agents',
    'dw-needs-attention': 'Needs Attention - Urgent matters requiring immediate action',
    'dw-parallel-processes': 'Parallel Processes - Real-time monitor of all running tasks',
    'ServerUptime-9888': 'Server Uptime - Auto-restart crashed processes every 15 seconds',
    'ColefaxBulkUpdate-9892': 'Colefax Bulk Update - Batch product updates for Colefax & Fowler',
    'WolfGordonUpdater-9872': 'Wolf Gordon Updater - Automated product sync for Wolf Gordon vendor'
  };

  return descriptions[name] || 'Background process';
}

// API endpoint to get all processes
app.get('/api/processes', requireAuth, async (req: Request, res: Response) => {
  try {
    // Get PM2 processes
    const { stdout: pm2Output } = await execAsync('pm2 jlist');
    const pm2Processes = JSON.parse(pm2Output);

    const pm2List = pm2Processes.map((p: any) => ({
      id: p.pm_id,
      name: p.name,
      description: getProcessDescription(p.name),
      status: p.pm2_env.status,
      cpu: p.monit.cpu + '%',
      memory: (p.monit.memory / 1024 / 1024).toFixed(1) + 'mb',
      uptime: p.pm2_env.pm_uptime ? formatUptime(Date.now() - p.pm2_env.pm_uptime) : '0s',
      restarts: p.pm2_env.restart_time,
      pid: p.pid
    }));

    // Get background tasks (bash processes)
    const backgroundTasks: Array<any> = [];

    try {
      const { stdout: psOutput } = await execAsync('ps aux | grep -E "tsx|npx|timeout" | grep -v grep');
      const lines = psOutput.trim().split('\n');

      for (const line of lines) {
        const parts = line.trim().split(/\s+/);
        if (parts.length > 10) {
          const command = parts.slice(10).join(' ');
          if (command.includes('tsx') || command.includes('timeout') || command.includes('npx')) {
            // Parse command to get description
            let description = 'Background task running';
            let taskName = 'Background Task';

            if (command.includes('update-single-dwjc')) {
              taskName = 'DWJC Product Update';
              description = 'Updating single product data for DWJC (Designer Wallcoverings & Jim Thompson)';
            } else if (command.includes('update-dwjc-with-ai')) {
              taskName = 'DWJC Bulk AI Update';
              description = 'AI-powered bulk update for DWJC products - enhancing descriptions and metadata';
            } else if (command.includes('fix-all-ambient-products')) {
              taskName = 'Ambient Products Fix';
              description = 'Fixing and standardizing all Ambient product data across the store';
            } else if (command.includes('WolfGordonUpdater') || command.includes('wolf-gordon')) {
              taskName = 'Wolf Gordon Sync';
              description = 'Syncing products from Wolf Gordon vendor - checking for paint color updates';
            } else if (command.includes('ColefaxBulkUpdate') || command.includes('colefax')) {
              taskName = 'Colefax & Fowler Update';
              description = 'Batch updating products from Colefax & Fowler vendor';
            } else if (command.includes('import') && command.includes('sku')) {
              taskName = 'SKU Import';
              description = 'Importing new products by SKU into Shopify';
            } else if (command.includes('fix-') || command.includes('update-')) {
              taskName = 'Product Maintenance';
              description = 'Running product data maintenance and corrections';
            }

            backgroundTasks.push({
              name: taskName,
              description: description,
              command: command.substring(0, 150) + (command.length > 150 ? '...' : ''),
              status: 'running',
              pid: parts[1]
            });
          }
        }
      }
    } catch (error) {
      // No background tasks found
    }

    res.json({
      pm2: pm2List,
      background: backgroundTasks,
      timestamp: new Date()
    });
  } catch (error) {
    console.error('Error fetching processes:', error);
    res.status(500).json({ error: 'Failed to fetch processes' });
  }
});

function formatUptime(ms: number): string {
  const seconds = Math.floor(ms / 1000);
  const minutes = Math.floor(seconds / 60);
  const hours = Math.floor(minutes / 60);
  const days = Math.floor(hours / 24);

  if (days > 0) return `${days}d`;
  if (hours > 0) return `${hours}h`;
  if (minutes > 0) return `${minutes}m`;
  return `${seconds}s`;
}

// Chat endpoint
app.post('/api/chat', requireAuth, async (req: Request, res: Response) => {
  try {
    const { message } = req.body;

    if (!message) {
      return res.status(400).json({ error: 'Message is required' });
    }

    // Get current process data for context
    const { stdout: pm2Output } = await execAsync('pm2 jlist');
    const pm2Processes = JSON.parse(pm2Output);

    const processContext = pm2Processes.map((p: any) => ({
      id: p.pm_id,
      name: p.name,
      status: p.pm2_env.status,
      cpu: p.monit.cpu,
      memory: (p.monit.memory / 1024 / 1024).toFixed(1),
      uptime: p.pm2_env.pm_uptime ? formatUptime(Date.now() - p.pm2_env.pm_uptime) : '0s',
      restarts: p.pm2_env.restart_time
    }));

    // Initialize chat history if not exists
    if (!req.session.chatHistory) {
      req.session.chatHistory = [];
    }

    // Build system message with process context
    const systemMessage = `You are a helpful assistant monitoring parallel processes and background tasks.

Current PM2 Processes:
${JSON.stringify(processContext, null, 2)}

Answer questions about process status, performance, issues, and recommendations. Be concise and helpful.`;

    // Add user message to history
    req.session.chatHistory.push({
      role: 'user',
      content: message
    });

    // Keep only last 10 messages
    if (req.session.chatHistory.length > 10) {
      req.session.chatHistory = req.session.chatHistory.slice(-10);
    }

    // Call Claude API
    const response = await anthropic.messages.create({
      model: 'claude-3-5-sonnet-20241022',
      max_tokens: 1024,
      system: systemMessage,
      messages: req.session.chatHistory
    });

    const assistantMessage = response.content[0].type === 'text' ? response.content[0].text : 'I apologize, I could not process that request.';

    // Add assistant response to history
    req.session.chatHistory.push({
      role: 'assistant',
      content: assistantMessage
    });

    res.json({ response: assistantMessage });
  } catch (error) {
    console.error('Chat error:', error);
    res.status(500).json({ error: 'Failed to process chat message' });
  }
});


// Metrics endpoint for daily reporting
app.get("/api/metrics", (req: Request, res: Response) => {
  res.json({
    status: "online",
    uptime: process.uptime(),
    responseTime: 0,
    tasksCompleted: 0,
    errorsToday: 0,
    lastActivity: new Date().toISOString(),
    qnaReadiness: 100
  });
});

app.listen(PORT, '0.0.0.0', () => {
  console.log(`⚡ Parallel Processes Monitor running on port ${PORT}`);
  console.log(`Dashboard: http://45.61.58.125:${PORT}`);
});