← 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}/`);
});