← back to Designer Wallcoverings

DW-Agents/dw-agents/agent-needs-attention/needs-attention-agent.ts.backup-auth-1762615744

475 lines

/**
 * DW-Agents: Needs Attention Agent
 *
 * Monitors urgent matters across all DW-Agents requiring immediate action:
 * - Pending approvals and orders
 * - Settlement violations
 * - Failed processes
 * - Customer complaints
 * - Vendor issues
 * - System errors
 *
 * Port: 9886
 */

import Anthropic from '@anthropic-ai/sdk';
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 fetch from 'node-fetch';

const app = express();
const PORT = 9886; // Fixed: back to 9886 as 9891 is used by in-parallel agent

// Shopify configuration
const SHOPIFY_STORE = process.env.SHOPIFY_STORE_DOMAIN || 'designer-laboratory-sandbox.myshopify.com';
const SHOPIFY_TOKEN = process.env.SHOPIFY_ACCESS_TOKEN || 'shpat_REDACTED';
const API_VERSION = '2024-07';

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

declare module 'express-session' {
  interface SessionData {
    authenticated: boolean;
  }
}

app.use(
  session({
    secret: 'dw-needs-attention-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>Needs Attention - 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, #DC143C 0%, #FF6B6B 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: #DC143C; 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, #DC143C 0%, #FF6B6B 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>🚨 Needs Attention</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 === 'admin' && password === '2025') {
    req.session.authenticated = true;

    // 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>Needs Attention - 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, #DC143C 0%, #FF6B6B 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: #DC143C; 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, #DC143C 0%, #FF6B6B 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: #DC143C;
    }
    .summary-card .label {
      color: #666;
      margin-top: 5px;
    }
    .attention-section {
      background: white;
      padding: 25px;
      border-radius: 15px;
      margin-bottom: 20px;
      box-shadow: 0 5px 20px rgba(0,0,0,0.1);
    }
    .attention-section h2 {
      color: #DC143C;
      margin-bottom: 15px;
      font-size: 1.5em;
      display: flex;
      align-items: center;
      gap: 10px;
    }
    .attention-item {
      background: #fff3f3;
      padding: 15px;
      border-radius: 8px;
      margin-bottom: 10px;
      border-left: 4px solid #DC143C;
    }
    .attention-item.critical { border-left-color: #DC143C; background: #ffebee; }
    .attention-item.warning { border-left-color: #FFA500; background: #fff8e1; }
    .attention-item.info { border-left-color: #2196F3; background: #e3f2fd; }
    .attention-item strong { color: #333; }
    .attention-item .priority {
      display: inline-block;
      padding: 3px 10px;
      border-radius: 12px;
      font-size: 0.85em;
      font-weight: 600;
      margin-right: 10px;
    }
    .priority.critical { background: #DC143C; color: white; }
    .priority.warning { background: #FFA500; color: white; }
    .priority.info { background: #2196F3; color: white; }
    .action-link {
      color: #DC143C;
      text-decoration: none;
      font-weight: 600;
      margin-left: 10px;
    }
    .no-items { color: #888; text-align: center; padding: 20px; }
  </style>
</head>
<body>
  <div class="header">
    <h1>🚨 Needs Attention</h1>
    <div class="subtitle">Urgent matters requiring immediate action</div>
  </div>

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

  <div class="summary-cards">
    <div class="summary-card">
      <div class="number" id="criticalCount">0</div>
      <div class="label">Critical</div>
    </div>
    <div class="summary-card">
      <div class="number" id="warningCount">0</div>
      <div class="label">Warnings</div>
    </div>
    <div class="summary-card">
      <div class="number" id="totalCount">0</div>
      <div class="label">Total Items</div>
    </div>
  </div>

  <div class="attention-section">
    <h2>🔴 Critical - Immediate Action Required</h2>
    <div id="criticalItems" class="no-items">Loading...</div>
  </div>

  <div class="attention-section">
    <h2>⚠️ Warnings - Attention Needed</h2>
    <div id="warningItems" class="no-items">Loading...</div>
  </div>

  <div class="attention-section">
    <h2>ℹ️ Information - Review Recommended</h2>
    <div id="infoItems" class="no-items">Loading...</div>
  </div>

  <script>
    async function refreshAttention() {
      try {
        const data = await fetch('/api/attention').then(r => r.json());

        const critical = data.items.filter(i => i.priority === 'critical');
        const warnings = data.items.filter(i => i.priority === 'warning');
        const info = data.items.filter(i => i.priority === 'info');

        document.getElementById('criticalCount').textContent = critical.length;
        document.getElementById('warningCount').textContent = warnings.length;
        document.getElementById('totalCount').textContent = data.items.length;

        // Critical items
        const criticalDiv = document.getElementById('criticalItems');
        if (critical.length > 0) {
          criticalDiv.innerHTML = critical.map(item => \`
            <div class="attention-item critical">
              <span class="priority critical">\${item.priority.toUpperCase()}</span>
              <strong>\${item.title}</strong><br>
              \${item.description}
              \${item.actionUrl ? \`<a href="\${item.actionUrl}" target="_blank" class="action-link">→ Take Action</a>\` : ''}
            </div>
          \`).join('');
        } else {
          criticalDiv.innerHTML = '<p class="no-items">✅ No critical items</p>';
        }

        // Warning items
        const warningDiv = document.getElementById('warningItems');
        if (warnings.length > 0) {
          warningDiv.innerHTML = warnings.map(item => \`
            <div class="attention-item warning">
              <span class="priority warning">\${item.priority.toUpperCase()}</span>
              <strong>\${item.title}</strong><br>
              \${item.description}
              \${item.actionUrl ? \`<a href="\${item.actionUrl}" target="_blank" class="action-link">→ View Details</a>\` : ''}
            </div>
          \`).join('');
        } else {
          warningDiv.innerHTML = '<p class="no-items">✅ No warnings</p>';
        }

        // Info items
        const infoDiv = document.getElementById('infoItems');
        if (info.length > 0) {
          infoDiv.innerHTML = info.map(item => \`
            <div class="attention-item info">
              <span class="priority info">\${item.priority.toUpperCase()}</span>
              <strong>\${item.title}</strong><br>
              \${item.description}
              \${item.actionUrl ? \`<a href="\${item.actionUrl}" target="_blank" class="action-link">→ Review</a>\` : ''}
            </div>
          \`).join('');
        } else {
          infoDiv.innerHTML = '<p class="no-items">✅ No information items</p>';
        }

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

    // Auto-refresh every 30 seconds
    setInterval(refreshAttention, 30000);
    refreshAttention();
  </script>
</body>
</html>
  `);
});

// API endpoint to get items needing attention
app.get('/api/attention', requireAuth, async (req: Request, res: Response) => {
  try {
    const attentionItems: Array<{
      title: string;
      description: string;
      priority: 'critical' | 'warning' | 'info';
      source: string;
      actionUrl?: string;
      timestamp: Date;
    }> = [];

    // Check for unfulfilled orders older than 24 hours
    const yesterday = new Date();
    yesterday.setHours(yesterday.getHours() - 24);

    const ordersUrl = `https://${SHOPIFY_STORE}/admin/api/${API_VERSION}/orders.json?status=open&created_at_max=${yesterday.toISOString()}&fulfillment_status=unfulfilled&limit=250`;
    const ordersResponse = await fetch(ordersUrl, {
      headers: { 'X-Shopify-Access-Token': SHOPIFY_TOKEN },
    });
    const ordersData: any = await ordersResponse.json();
    const unfulfilled = ordersData.orders || [];

    if (unfulfilled.length > 0) {
      attentionItems.push({
        title: `${unfulfilled.length} Unfulfilled Orders (>24hrs)`,
        description: `Orders placed more than 24 hours ago are still unfulfilled`,
        priority: 'critical',
        source: 'Orders',
        actionUrl: `https://admin.shopify.com/store/${SHOPIFY_STORE.replace('.myshopify.com', '')}/orders?status=open&fulfillment_status=unfulfilled`,
        timestamp: new Date()
      });
    }

    // Check for pending digital samples
    attentionItems.push({
      title: 'Pending Digital Sample Approvals',
      description: 'Digital samples waiting for approval and delivery',
      priority: 'warning',
      source: 'Digital Samples',
      actionUrl: 'http://45.61.58.125:9879/',
      timestamp: new Date()
    });

    // Check for settlement compliance
    attentionItems.push({
      title: 'Settlement Compliance Review',
      description: 'Regular review of products for settlement term compliance',
      priority: 'info',
      source: 'Legal Team',
      actionUrl: 'http://45.61.58.125:9878/',
      timestamp: new Date()
    });

    // Check for DOWN websites from Server Uptime
    try {
      const uptimeResponse = await fetch('http://localhost:9888/api/status');
      const uptimeData: any = await uptimeResponse.json();

      if (uptimeData.domains) {
        const downDomains = uptimeData.domains.filter((d: any) => d.status === 'down');

        if (downDomains.length > 0) {
          attentionItems.push({
            title: `🚨 ${downDomains.length} WEBSITES DOWN`,
            description: `Critical: ${downDomains.map((d: any) => d.domain).join(', ')} are not responding`,
            priority: 'critical',
            source: 'Server Uptime',
            actionUrl: 'http://45.61.58.125:9888/',
            timestamp: new Date()
          });
        }
      }
    } catch (error) {
      console.error('Could not fetch domain health:', error);
    }

    // Check for vendor relationships needing attention (placeholder)
    attentionItems.push({
      title: 'Vendor Relationships Need Attention',
      description: 'Budget Walls Inc - Recent quality issues require discussion',
      priority: 'warning',
      source: 'Trend Research',
      actionUrl: 'http://45.61.58.125:9883/',
      timestamp: new Date()
    });

    res.json({
      items: attentionItems,
      summary: {
        critical: attentionItems.filter(i => i.priority === 'critical').length,
        warning: attentionItems.filter(i => i.priority === 'warning').length,
        info: attentionItems.filter(i => i.priority === 'info').length,
        total: attentionItems.length
      },
      generatedAt: new Date()
    });
  } catch (error) {
    console.error('Error fetching attention items:', error);
    res.status(500).json({ error: 'Failed to fetch attention items' });
  }
});

app.listen(PORT, '0.0.0.0', () => {
  console.log(`🚨 Needs Attention Agent running on port ${PORT}`);
  console.log(`Dashboard: http://45.61.58.125:${PORT}`);
});