← back to Designer Wallcoverings

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

707 lines

import express from 'express';
import path from 'path';
import fs from 'fs';
import session from 'express-session';

import cookieParser from "cookie-parser";

import { chatMiddleware } from '../shared-chat-integration';
const app = express();

// Global Authentication
app.use(cookieParser());
const PORT = 9891;

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

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

// Serve static files
app.use(express.static('public'));

// Add inter-agent chat widget
app.use(chatMiddleware({
  agentId: 'agent-in-parallel',
  agentName: 'In Parallel',
  port: 9891,
  category: 'agent'
}));

app.use(express.json());

// Authentication
const AUTH_USERNAME = 'admin2025';
const AUTH_PASSWORD = 'Otis';

// Authentication middleware
const requireAuth = (req: express.Request, res: express.Response, next: express.NextFunction) => {
  if (req.session.authenticated) {
    return next();
  }
  res.redirect('/login');
};

interface ParallelTask {
  id: string;
  title: string;
  description: string;
  status: 'running' | 'completed' | 'paused' | 'error';
  progress: {
    current: number;
    total: number;
    percentage: number;
  };
  stats: {
    startTime: string;
    estimatedCompletion?: string;
    rate?: string;
    elapsed?: string;
  };
  details: string[];
  monitorUrl?: string;
  category: 'shopify' | 'infrastructure' | 'data' | 'other';
  priority: 'high' | 'medium' | 'low';
}

// Load tasks from JSON file
let parallelTasks: ParallelTask[] = [];

function loadTasks() {
  try {
    const dataPath = path.join(__dirname, 'parallel-tasks.json');
    if (fs.existsSync(dataPath)) {
      parallelTasks = JSON.parse(fs.readFileSync(dataPath, 'utf-8'));
    } else {
      // Initialize with sample DWKK task
      parallelTasks = [
        {
          id: 'dwkk-sample-price-update',
          title: '🚨 DWKK Sample Price Update',
          description: 'Updating all DWKK- SKU sample variants to $4.25',
          status: 'running',
          progress: {
            current: 0,
            total: 27289,
            percentage: 0
          },
          stats: {
            startTime: new Date().toISOString(),
            rate: '~2 variants per second',
            estimatedCompletion: new Date(Date.now() + 3.8 * 60 * 60 * 1000).toISOString()
          },
          details: [
            '✅ Only DWKK- SKUs',
            '✅ Only variants with Size = "Sample"',
            '✅ Only ACTIVE variants',
            '✅ Setting price to $4.25'
          ],
          monitorUrl: 'http://45.61.58.125:6766/',
          category: 'shopify',
          priority: 'high'
        }
      ];
      saveTasks();
    }
  } catch (error) {
    console.error('Error loading tasks:', error);
    parallelTasks = [];
  }
}

function saveTasks() {
  try {
    const dataPath = path.join(__dirname, 'parallel-tasks.json');
    fs.writeFileSync(dataPath, JSON.stringify(parallelTasks, null, 2));
  } catch (error) {
    console.error('Error saving tasks:', error);
  }
}

// Initialize tasks on startup
loadTasks();

// Login page
app.get('/login', (req, res) => {
  res.send(`
<!DOCTYPE html>
<html>
<head>
  <title>Login - In Parallel</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;
      justify-content: center;
      align-items: center;
      min-height: 100vh;
    }
    .login-box {
      background: white;
      padding: 40px;
      border-radius: 15px;
      box-shadow: 0 10px 40px rgba(0,0,0,0.2);
      width: 350px;
    }
    h1 { color: #667eea; margin-bottom: 30px; text-align: center; }
    input {
      width: 100%;
      padding: 12px;
      margin: 10px 0;
      border: 2px solid #e0e0e0;
      border-radius: 8px;
      font-size: 14px;
    }
    button {
      width: 100%;
      padding: 12px;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
      border: none;
      border-radius: 8px;
      font-size: 16px;
      font-weight: 600;
      cursor: pointer;
      margin-top: 15px;
    }
    button:hover { opacity: 0.9; }
    .error {
      color: #dc143c;
      text-align: center;
      margin-bottom: 15px;
    }
  </style>
</head>
<body>
  <div class="login-box">
    <h1>⚡ In Parallel</h1>
    ${req.query.error ? '<div class="error">Invalid credentials</div>' : ''}
    <form method="POST" action="/login">
      <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', express.urlencoded({ extended: true }), (req, res) => {
  const { username, password } = req.body;
  if (username === AUTH_USERNAME && password === AUTH_PASSWORD) {
    req.session.authenticated = true;
    res.redirect('/');
  } else {
    res.redirect('/login?error=1');
  }
});

app.get('/', (req, res) => {
  const html = `
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>In Parallel - Background Tasks Monitor</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            padding: 20px;
        }

        .container {
            max-width: 1400px;
            margin: 0 auto;
        }

        .header {
            background: rgba(255, 255, 255, 0.95);
            border-radius: 20px;
            padding: 30px;
            margin-bottom: 30px;
            box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
        }

        .header h1 {
            font-size: 2.5rem;
            color: #2d3748;
            margin-bottom: 10px;
        }

        .header p {
            color: #718096;
            font-size: 1.1rem;
        }

        .stats-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 20px;
            margin-bottom: 30px;
        }

        .stat-card {
            background: rgba(255, 255, 255, 0.95);
            border-radius: 15px;
            padding: 20px;
            box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1);
        }

        .stat-card h3 {
            color: #718096;
            font-size: 0.9rem;
            font-weight: 500;
            margin-bottom: 10px;
            text-transform: uppercase;
            letter-spacing: 0.5px;
        }

        .stat-card .value {
            font-size: 2rem;
            font-weight: 700;
            color: #2d3748;
        }

        .stat-card.running .value {
            color: #48bb78;
        }

        .stat-card.completed .value {
            color: #4299e1;
        }

        .tasks-grid {
            display: grid;
            gap: 25px;
        }

        .task-card {
            background: rgba(255, 255, 255, 0.95);
            border-radius: 20px;
            padding: 30px;
            box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
            transition: transform 0.2s, box-shadow 0.2s;
        }

        .task-card:hover {
            transform: translateY(-5px);
            box-shadow: 0 15px 50px rgba(0, 0, 0, 0.15);
        }

        .task-header {
            display: flex;
            justify-content: space-between;
            align-items: flex-start;
            margin-bottom: 20px;
        }

        .task-title {
            flex: 1;
        }

        .task-title h2 {
            font-size: 1.5rem;
            color: #2d3748;
            margin-bottom: 8px;
        }

        .task-title p {
            color: #718096;
            font-size: 1rem;
        }

        .status-badge {
            padding: 8px 16px;
            border-radius: 20px;
            font-size: 0.85rem;
            font-weight: 600;
            text-transform: uppercase;
            letter-spacing: 0.5px;
        }

        .status-badge.running {
            background: #c6f6d5;
            color: #22543d;
        }

        .status-badge.completed {
            background: #bee3f8;
            color: #2c5282;
        }

        .status-badge.paused {
            background: #fed7aa;
            color: #7c2d12;
        }

        .status-badge.error {
            background: #fed7d7;
            color: #742a2a;
        }

        .progress-section {
            margin: 25px 0;
        }

        .progress-bar-container {
            background: #e2e8f0;
            border-radius: 10px;
            height: 30px;
            overflow: hidden;
            margin-bottom: 15px;
        }

        .progress-bar {
            height: 100%;
            background: linear-gradient(90deg, #48bb78 0%, #38a169 100%);
            display: flex;
            align-items: center;
            justify-content: center;
            color: white;
            font-weight: 600;
            transition: width 0.5s ease;
        }

        .progress-details {
            display: flex;
            justify-content: space-between;
            color: #4a5568;
            font-size: 0.95rem;
        }

        .stats-row {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 15px;
            margin: 20px 0;
        }

        .stat-item {
            background: #f7fafc;
            padding: 15px;
            border-radius: 10px;
        }

        .stat-item h4 {
            color: #718096;
            font-size: 0.85rem;
            margin-bottom: 5px;
            text-transform: uppercase;
            letter-spacing: 0.5px;
        }

        .stat-item p {
            color: #2d3748;
            font-size: 1.1rem;
            font-weight: 600;
        }

        .details-list {
            list-style: none;
            margin: 20px 0;
        }

        .details-list li {
            padding: 10px 0;
            color: #4a5568;
            border-bottom: 1px solid #e2e8f0;
        }

        .details-list li:last-child {
            border-bottom: none;
        }

        .monitor-button {
            display: inline-block;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 12px 24px;
            border-radius: 10px;
            text-decoration: none;
            font-weight: 600;
            margin-top: 15px;
            transition: transform 0.2s, box-shadow 0.2s;
        }

        .monitor-button:hover {
            transform: translateY(-2px);
            box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4);
        }

        .empty-state {
            background: rgba(255, 255, 255, 0.95);
            border-radius: 20px;
            padding: 60px 30px;
            text-align: center;
            box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
        }

        .empty-state h2 {
            color: #2d3748;
            font-size: 1.8rem;
            margin-bottom: 15px;
        }

        .empty-state p {
            color: #718096;
            font-size: 1.1rem;
        }

        @keyframes pulse {
            0%, 100% { opacity: 1; }
            50% { opacity: 0.5; }
        }

        .pulsing {
            animation: pulse 2s ease-in-out infinite;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>⚡ In Parallel</h1>
            <p>Background Tasks & Long-Running Operations Monitor</p>
        </div>

        <div class="stats-grid" id="statsGrid">
            <!-- Stats will be inserted here -->
        </div>

        <div class="tasks-grid" id="tasksGrid">
            <!-- Tasks will be inserted here -->
        </div>

        <div class="empty-state" id="emptyState" style="display: none;">
            <h2>🎉 No Active Tasks</h2>
            <p>All background operations are complete!</p>
        </div>
    </div>

    <script>
        async function fetchTasks() {
            try {
                const response = await fetch('/api/tasks');
                const tasks = await response.json();
                renderTasks(tasks);
            } catch (error) {
                console.error('Error fetching tasks:', error);
            }
        }

        function formatTime(isoString) {
            if (!isoString) return 'N/A';
            const date = new Date(isoString);
            return date.toLocaleString();
        }

        function formatElapsed(startTime) {
            const start = new Date(startTime);
            const now = new Date();
            const diff = now - start;
            const hours = Math.floor(diff / 3600000);
            const minutes = Math.floor((diff % 3600000) / 60000);
            return hours > 0 ? \`\${hours}h \${minutes}m\` : \`\${minutes}m\`;
        }

        function renderTasks(tasks) {
            const statsGrid = document.getElementById('statsGrid');
            const tasksGrid = document.getElementById('tasksGrid');
            const emptyState = document.getElementById('emptyState');

            if (tasks.length === 0) {
                statsGrid.style.display = 'none';
                tasksGrid.style.display = 'none';
                emptyState.style.display = 'block';
                return;
            }

            statsGrid.style.display = 'grid';
            tasksGrid.style.display = 'grid';
            emptyState.style.display = 'none';

            // Calculate stats
            const runningCount = tasks.filter(t => t.status === 'running').length;
            const completedCount = tasks.filter(t => t.status === 'completed').length;
            const totalProgress = tasks.reduce((sum, t) => sum + t.progress.current, 0);
            const totalItems = tasks.reduce((sum, t) => sum + t.progress.total, 0);

            // Render stats
            statsGrid.innerHTML = \`
                <div class="stat-card running">
                    <h3>Running Tasks</h3>
                    <div class="value">\${runningCount}</div>
                </div>
                <div class="stat-card completed">
                    <h3>Completed</h3>
                    <div class="value">\${completedCount}</div>
                </div>
                <div class="stat-card">
                    <h3>Total Progress</h3>
                    <div class="value">\${totalProgress.toLocaleString()}</div>
                </div>
                <div class="stat-card">
                    <h3>Total Items</h3>
                    <div class="value">\${totalItems.toLocaleString()}</div>
                </div>
            \`;

            // Render tasks
            tasksGrid.innerHTML = tasks.map(task => \`
                <div class="task-card">
                    <div class="task-header">
                        <div class="task-title">
                            <h2>\${task.title}</h2>
                            <p>\${task.description}</p>
                        </div>
                        <span class="status-badge \${task.status} \${task.status === 'running' ? 'pulsing' : ''}">\${task.status}</span>
                    </div>

                    <div class="progress-section">
                        <div class="progress-bar-container">
                            <div class="progress-bar" style="width: \${task.progress.percentage}%">
                                \${task.progress.percentage.toFixed(1)}%
                            </div>
                        </div>
                        <div class="progress-details">
                            <span>\${task.progress.current.toLocaleString()} / \${task.progress.total.toLocaleString()}</span>
                            <span>\${task.progress.percentage.toFixed(2)}% Complete</span>
                        </div>
                    </div>

                    <div class="stats-row">
                        <div class="stat-item">
                            <h4>Started</h4>
                            <p>\${formatTime(task.stats.startTime)}</p>
                        </div>
                        <div class="stat-item">
                            <h4>Elapsed</h4>
                            <p>\${formatElapsed(task.stats.startTime)}</p>
                        </div>
                        \${task.stats.rate ? \`
                        <div class="stat-item">
                            <h4>Rate</h4>
                            <p>\${task.stats.rate}</p>
                        </div>
                        \` : ''}
                        \${task.stats.estimatedCompletion ? \`
                        <div class="stat-item">
                            <h4>Est. Completion</h4>
                            <p>\${formatTime(task.stats.estimatedCompletion)}</p>
                        </div>
                        \` : ''}
                    </div>

                    \${task.details.length > 0 ? \`
                    <ul class="details-list">
                        \${task.details.map(detail => \`<li>\${detail}</li>\`).join('')}
                    </ul>
                    \` : ''}

                    \${task.monitorUrl ? \`
                        <a href="\${task.monitorUrl}" target="_blank" class="monitor-button">
                            📊 Open Detailed Monitor
                        </a>
                    \` : ''}
                </div>
            \`).join('');
        }

        // Initial load
        fetchTasks();

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

  res.send(html);
});

app.get('/api/tasks', (req, res) => {
  res.json(parallelTasks);
});

app.post('/api/tasks', (req, res) => {
  const newTask: ParallelTask = req.body;
  parallelTasks.push(newTask);
  saveTasks();
  res.json({ success: true, task: newTask });
});

app.put('/api/tasks/:id', (req, res) => {
  const { id } = req.params;
  const updates = req.body;

  const taskIndex = parallelTasks.findIndex(t => t.id === id);
  if (taskIndex !== -1) {
    parallelTasks[taskIndex] = { ...parallelTasks[taskIndex], ...updates };
    saveTasks();
    res.json({ success: true, task: parallelTasks[taskIndex] });
  } else {
    res.status(404).json({ error: 'Task not found' });
  }
});

app.delete('/api/tasks/:id', (req, res) => {
  const { id } = req.params;
  const taskIndex = parallelTasks.findIndex(t => t.id === id);

  if (taskIndex !== -1) {
    const deletedTask = parallelTasks.splice(taskIndex, 1)[0];
    saveTasks();
    res.json({ success: true, task: deletedTask });
  } else {
    res.status(404).json({ error: 'Task not found' });
  }
});


// 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, () => {
  console.log(`✅ In Parallel Agent running on port ${PORT}`);
  console.log(`🌐 Access at: http://45.61.58.125:${PORT}/`);
});