← back to Designer Wallcoverings
DW-Agents/dw-agents/agent-parallel-processes/parallel-processes-agent.ts.backup-auth-1762615744
722 lines
/**
* 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';
const execAsync = promisify(exec);
const app = express();
const PORT = 9887;
// Anthropic Claude
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
app.use(express.json());
app.use(cookieParser());
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 === 'admin' && password === '2025') {
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' });
}
});
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}`);
});