App.renderIntegrations = function() {
return api('/api/integrations').then(function(data) {
var platforms = (data && data.platforms) ? data.platforms : [];
var html = '';
html += '<div class="card-title" style="margin-bottom:1rem">Integrations</div>';
html += '<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:1rem">';
platforms.forEach(function(p) {
var name = p.name || 'unknown';
var configured = !!p.configured;
var enabled = !!p.enabled;
var health = p.health || 'disconnected';
var recv = p.messages_received || 0;
var sent = p.messages_sent || 0;
var errCount = p.error_count || 0;
var lastErr = p.last_error || null;
var lastActivity = p.last_activity || null;
var lastSuccess = p.last_successful_at || null;
var healthColor = health === 'connected' ? '#22c55e'
: health === 'degraded' ? '#f59e0b'
: 'var(--muted)';
var healthLabel = health === 'connected' ? 'Connected'
: health === 'degraded' ? 'Degraded'
: 'Disconnected';
var dimmed = !configured ? 'opacity:0.5;' : '';
var icon = _integrationIcon(name);
html += '<div class="card" style="padding:1rem;' + dimmed + '">';
html += '<div style="display:flex;align-items:center;gap:0.6rem;margin-bottom:0.75rem">';
html += '<span style="width:28px;height:28px;display:flex;align-items:center;justify-content:center">' + icon + '</span>';
html += '<span style="font-weight:700;font-size:0.95rem;text-transform:capitalize">' + esc(name) + '</span>';
html += '<span style="margin-left:auto;font-size:0.7rem;padding:0.15rem 0.5rem;border-radius:9999px;'
+ 'background:' + healthColor + '20;color:' + healthColor + ';font-weight:600">'
+ esc(healthLabel) + '</span>';
html += '</div>';
if (!configured) {
html += '<div style="font-size:0.8rem;color:var(--muted);margin-bottom:0.5rem">Not configured</div>';
html += '<div style="font-size:0.75rem;color:var(--muted)">Add <code>[channels.' + esc(name) + ']</code> to roboticus.toml</div>';
} else {
html += '<div style="display:flex;gap:1.5rem;font-size:0.78rem;margin-bottom:0.5rem">';
html += '<div><span style="color:var(--muted)">Received</span> <strong>' + recv + '</strong></div>';
html += '<div><span style="color:var(--muted)">Sent</span> <strong>' + sent + '</strong></div>';
if (errCount > 0) {
html += '<div><span style="color:var(--error, #ef4444)">Errors</span> <strong style="color:var(--error, #ef4444)">' + errCount + '</strong></div>';
}
html += '</div>';
if (lastErr) {
html += '<div style="font-size:0.72rem;color:var(--warning, #f59e0b);margin-bottom:0.4rem;word-break:break-word">'
+ esc(lastErr.substring(0, 120)) + '</div>';
}
html += '<div style="font-size:0.7rem;color:var(--muted);display:flex;flex-direction:column;gap:0.15rem">';
if (lastActivity) {
html += '<div>Last activity: ' + esc(_relTime(lastActivity)) + '</div>';
}
if (lastSuccess) {
html += '<div>Last success: ' + esc(_relTime(lastSuccess)) + '</div>';
}
if (!enabled) {
html += '<div style="color:var(--warning, #f59e0b)">Configured but disabled</div>';
}
html += '</div>';
html += '<button class="btn secondary integration-test-btn" data-test-platform="'
+ esc(name) + '" style="font-size:0.72rem;padding:0.25rem 0.65rem;margin-top:0.6rem">Test</button>';
}
html += '</div>';
});
html += '</div>';
return html;
});
};
function _integrationIcon(name) {
switch (name) {
case 'telegram':
return '<svg viewBox="0 0 16 16" width="20" height="20"><path d="M1.5 7.5l13-5.5-3 12-4.5-3-2.5 2.5v-3.5l-3-2.5z" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/><line x1="14.5" y1="2" x2="5.5" y2="9" stroke="currentColor" stroke-width="1.2"/></svg>';
case 'discord':
return '<svg viewBox="0 0 16 16" width="20" height="20"><path d="M5.5 3C4 3 3 4 3 4s-1 3-.5 5.5c.5 2.5 3 3 3 3l.5-1s-1-.5-1.5-1.5 0-3 0-3h7s.5 2 0 3S10 11.5 10 11.5l.5 1s2.5-.5 3-3S13 4 13 4s-1-1-2.5-1" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/><circle cx="6" cy="8" r="1" fill="currentColor"/><circle cx="10" cy="8" r="1" fill="currentColor"/></svg>';
case 'whatsapp':
return '<svg viewBox="0 0 16 16" width="20" height="20"><circle cx="8" cy="8" r="6.5" fill="none" stroke="currentColor" stroke-width="1.3"/><path d="M5.5 9.5l-.5 2.5 2.5-.5c2 1 4-1 3.5-3S8 5 6 6s-1.5 3-.5 3.5z" fill="none" stroke="currentColor" stroke-width="1.1"/></svg>';
case 'signal':
return '<svg viewBox="0 0 16 16" width="20" height="20"><circle cx="8" cy="8" r="6.5" fill="none" stroke="currentColor" stroke-width="1.3"/><circle cx="8" cy="8" r="3" fill="none" stroke="currentColor" stroke-width="1.1"/><circle cx="8" cy="8" r="0.8" fill="currentColor"/></svg>';
case 'email':
return '<svg viewBox="0 0 16 16" width="20" height="20"><rect x="1.5" y="3" width="13" height="10" rx="1.5" fill="none" stroke="currentColor" stroke-width="1.3"/><polyline points="1.5,3 8,9 14.5,3" fill="none" stroke="currentColor" stroke-width="1.2"/></svg>';
case 'matrix':
return '<svg viewBox="0 0 16 16" width="20" height="20"><rect x="2" y="1.5" width="12" height="13" rx="0" fill="none" stroke="currentColor" stroke-width="1.3"/><line x1="2" y1="4" x2="0.5" y2="4" stroke="currentColor" stroke-width="1.2"/><line x1="2" y1="12" x2="0.5" y2="12" stroke="currentColor" stroke-width="1.2"/><line x1="14" y1="4" x2="15.5" y2="4" stroke="currentColor" stroke-width="1.2"/><line x1="14" y1="12" x2="15.5" y2="12" stroke="currentColor" stroke-width="1.2"/></svg>';
case 'web':
return '<svg viewBox="0 0 16 16" width="20" height="20"><circle cx="8" cy="8" r="6.5" fill="none" stroke="currentColor" stroke-width="1.3"/><ellipse cx="8" cy="8" rx="3" ry="6.5" fill="none" stroke="currentColor" stroke-width="1.1"/><line x1="1.5" y1="8" x2="14.5" y2="8" stroke="currentColor" stroke-width="1.1"/></svg>';
default:
return '<svg viewBox="0 0 16 16" width="20" height="20"><circle cx="8" cy="8" r="6" fill="none" stroke="currentColor" stroke-width="1.3"/><text x="8" y="11" font-size="8" text-anchor="middle" fill="currentColor">?</text></svg>';
}
}
function _relTime(iso) {
if (!iso) return 'never';
var d = new Date(iso);
var now = Date.now();
var diff = Math.floor((now - d.getTime()) / 1000);
if (diff < 60) return diff + 's ago';
if (diff < 3600) return Math.floor(diff / 60) + 'm ago';
if (diff < 86400) return Math.floor(diff / 3600) + 'h ago';
return Math.floor(diff / 86400) + 'd ago';
}