← back to Designer Wallcoverings
DW-Agents/dw-agents/agent-all-agents/all-agents-viewer.ts
197 lines
import { requireGlobalAuth } from "../shared-global-auth";
import cookieParser from "cookie-parser";
/**
* All Agents Viewer - Port 9905
* Comprehensive dashboard showing all DW agents, their skills, status, and API endpoints
*/
import express from 'express';
import { exec } from 'child_process';
import { promisify } from 'util';
import fs from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const execAsync = promisify(exec);
const app = express();
app.use(cookieParser());
app.use(requireGlobalAuth);
const PORT = 9905;
let agentRegistry: any = {};
(async () => {
const registryData = await fs.readFile(path.join(__dirname, '..', 'agent-registry.json'), 'utf-8');
agentRegistry = JSON.parse(registryData);
})();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// API: Get full status of all agents
app.get('/api/status', async (req, res) => {
try {
const { stdout } = await execAsync('netstat -tlnp 2>/dev/null | grep LISTEN');
const runningPorts = new Set(
stdout.split('\n')
.filter(line => line.includes(':'))
.map(line => { const m = line.match(/:(\d+)\s/); return m ? m[1] : null; })
.filter(Boolean)
);
const status: any = {};
for (const [port, agent] of Object.entries(agentRegistry)) {
const a = agent as any;
status[port] = {
name: a.name,
icon: a.icon || '🤖',
category: a.category || 'Other',
description: a.description || '',
skills: a.skills || [],
endpoints: a.endpoints || [],
running: runningPorts.has(port),
url: `http://45.61.58.125:${port}/`
};
}
res.json(status);
} catch (error) {
res.status(500).json({ error: 'Failed to get status' });
}
});
app.get('/', (req, res) => {
res.send(`<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>DW Agent Directory</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #0f172a; color: #e2e8f0; min-height: 100vh; }
.top-bar { background: #1e293b; padding: 16px 28px; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #334155; position: sticky; top: 0; z-index: 100; }
.top-bar h1 { font-size: 22px; color: #f1f5f9; display: flex; align-items: center; gap: 10px; }
.top-bar .badge { background: #3b82f6; color: white; padding: 3px 10px; border-radius: 12px; font-size: 12px; }
.container { max-width: 1500px; margin: 0 auto; padding: 24px; }
.stats-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 14px; margin-bottom: 20px; }
.stat { background: #1e293b; border: 1px solid #334155; border-radius: 10px; padding: 14px; text-align: center; }
.stat .val { font-size: 32px; font-weight: 700; color: #3b82f6; }
.stat .lbl { font-size: 11px; color: #94a3b8; text-transform: uppercase; letter-spacing: 1px; margin-top: 2px; }
.filters { display: flex; gap: 8px; margin-bottom: 18px; flex-wrap: wrap; }
.filter-btn { padding: 6px 14px; background: #1e293b; border: 1px solid #334155; border-radius: 20px; color: #94a3b8; cursor: pointer; font-size: 12px; transition: all .2s; }
.filter-btn:hover, .filter-btn.active { background: #3b82f6; color: white; border-color: #3b82f6; }
.search-box { background: #1e293b; border: 1px solid #334155; color: #e2e8f0; padding: 8px 14px; border-radius: 8px; font-size: 13px; width: 220px; }
.agents-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(420px, 1fr)); gap: 16px; }
.agent-card { background: #1e293b; border: 1px solid #334155; border-radius: 12px; padding: 20px; transition: transform .15s, box-shadow .15s; }
.agent-card:hover { transform: translateY(-2px); box-shadow: 0 8px 24px rgba(0,0,0,.3); }
.agent-card.offline { opacity: .6; border-left: 3px solid #ef4444; }
.agent-card.online { border-left: 3px solid #22c55e; }
.card-head { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 10px; }
.card-title { display: flex; align-items: center; gap: 8px; }
.card-title .icon { font-size: 22px; }
.card-title .name { font-size: 16px; font-weight: 700; color: #f1f5f9; }
.card-title .port { font-size: 12px; color: #64748b; font-family: monospace; margin-left: 6px; }
.status-dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; }
.status-dot.on { background: #22c55e; box-shadow: 0 0 6px #22c55e; }
.status-dot.off { background: #ef4444; }
.cat-badge { background: #1e293b; border: 1px solid #475569; padding: 2px 8px; border-radius: 10px; font-size: 10px; color: #94a3b8; text-transform: uppercase; letter-spacing: .5px; }
.cat-executive { border-color: #f59e0b; color: #fbbf24; }
.cat-business { border-color: #3b82f6; color: #60a5fa; }
.cat-system { border-color: #8b5cf6; color: #a78bfa; }
.cat-customer { border-color: #10b981; color: #34d399; }
.cat-communications { border-color: #ec4899; color: #f472b6; }
.cat-data { border-color: #06b6d4; color: #22d3ee; }
.desc { font-size: 13px; color: #94a3b8; line-height: 1.5; margin-bottom: 12px; }
.skills-row { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 10px; }
.skill-tag { background: #0f172a; border: 1px solid #334155; padding: 2px 8px; border-radius: 6px; font-size: 11px; color: #cbd5e1; }
.endpoints-row { display: flex; flex-wrap: wrap; gap: 4px; }
.ep-tag { background: #172554; padding: 2px 6px; border-radius: 4px; font-size: 10px; font-family: monospace; color: #60a5fa; }
.card-footer { display: flex; justify-content: space-between; align-items: center; margin-top: 12px; padding-top: 10px; border-top: 1px solid #334155; }
.open-btn { padding: 6px 14px; background: #3b82f6; color: white; border: none; border-radius: 6px; font-size: 12px; cursor: pointer; text-decoration: none; font-weight: 600; }
.open-btn:hover { background: #2563eb; }
.open-btn.disabled { background: #475569; cursor: not-allowed; }
@media(max-width:768px) { .agents-grid { grid-template-columns: 1fr; } .stats-row { grid-template-columns: repeat(3, 1fr); } }
</style></head><body>
<div class="top-bar">
<h1>🗂️ DW Agent Directory</h1>
<div style="display:flex;gap:10px;align-items:center">
<input class="search-box" id="search" placeholder="Search agents..." oninput="filterAgents()">
<span class="badge" id="countBadge">0 agents</span>
</div>
</div>
<div class="container">
<div class="stats-row" id="stats"></div>
<div class="filters" id="filters"></div>
<div class="agents-grid" id="grid"></div>
</div>
<script>
let allAgents = {};
let activeFilter = 'all';
async function load() {
const r = await fetch('/api/status');
allAgents = await r.json();
renderStats();
renderFilters();
renderGrid();
}
function renderStats() {
const entries = Object.values(allAgents);
const total = entries.length;
const online = entries.filter(a => a.running).length;
const offline = total - online;
const cats = [...new Set(entries.map(a => a.category))].length;
const skills = entries.reduce((s, a) => s + (a.skills?.length || 0), 0);
document.getElementById('stats').innerHTML =
'<div class="stat"><div class="val">' + total + '</div><div class="lbl">Agents</div></div>' +
'<div class="stat"><div class="val" style="color:#22c55e">' + online + '</div><div class="lbl">Online</div></div>' +
'<div class="stat"><div class="val" style="color:#ef4444">' + offline + '</div><div class="lbl">Offline</div></div>' +
'<div class="stat"><div class="val" style="color:#f59e0b">' + cats + '</div><div class="lbl">Categories</div></div>' +
'<div class="stat"><div class="val" style="color:#8b5cf6">' + skills + '</div><div class="lbl">Total Skills</div></div>';
document.getElementById('countBadge').textContent = total + ' agents';
}
function renderFilters() {
const cats = ['all', ...new Set(Object.values(allAgents).map(a => a.category))];
document.getElementById('filters').innerHTML = cats.map(c =>
'<div class="filter-btn' + (activeFilter === c ? ' active' : '') + '" onclick="setFilter(\\'' + c + '\\')">' + (c === 'all' ? 'All' : c) + '</div>'
).join('');
}
function setFilter(cat) { activeFilter = cat; renderFilters(); renderGrid(); }
function filterAgents() { renderGrid(); }
function renderGrid() {
const search = document.getElementById('search').value.toLowerCase();
const grid = document.getElementById('grid');
grid.innerHTML = '';
const sorted = Object.entries(allAgents).sort(([,a],[,b]) => {
if (a.running !== b.running) return a.running ? -1 : 1;
return a.name.localeCompare(b.name);
});
for (const [port, a] of sorted) {
if (activeFilter !== 'all' && a.category !== activeFilter) continue;
if (search && !a.name.toLowerCase().includes(search) && !a.description.toLowerCase().includes(search) && !(a.skills || []).some(s => s.toLowerCase().includes(search))) continue;
const catClass = 'cat-' + (a.category || '').toLowerCase().replace(/\\s+/g, '-').replace('business-operations', 'business').replace('customer-service', 'customer');
grid.innerHTML += '<div class="agent-card ' + (a.running ? 'online' : 'offline') + '">' +
'<div class="card-head"><div class="card-title"><span class="icon">' + (a.icon || '🤖') + '</span><span class="name">' + a.name + '</span><span class="port">:' + port + '</span></div><span class="status-dot ' + (a.running ? 'on' : 'off') + '"></span></div>' +
'<span class="cat-badge ' + catClass + '">' + (a.category || 'Other') + '</span>' +
'<p class="desc">' + (a.description || 'No description') + '</p>' +
'<div class="skills-row">' + (a.skills || []).map(s => '<span class="skill-tag">' + s + '</span>').join('') + '</div>' +
'<div class="endpoints-row">' + (a.endpoints || []).slice(0, 6).map(e => '<span class="ep-tag">' + e + '</span>').join('') + (((a.endpoints || []).length > 6) ? '<span class="ep-tag">+' + ((a.endpoints || []).length - 6) + ' more</span>' : '') + '</div>' +
'<div class="card-footer"><span style="font-size:11px;color:#64748b">Port ' + port + '</span>' +
(a.running ? '<a href="' + a.url + '" target="_blank" class="open-btn">Open Dashboard</a>' : '<span class="open-btn disabled">Offline</span>') +
'</div></div>';
}
}
load();
setInterval(load, 15000);
</script></body></html>`);
});
app.listen(PORT, '0.0.0.0', () => {
console.log('🗂️ DW Agent Directory running on port ' + PORT);
console.log(' http://45.61.58.125:' + PORT);
});