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