'use strict';
'require view';
'require dom';
'require poll';
'require ui';
'require service-registry/api as api';
// Category icons
var catIcons = {
'proxy': '๐', 'privacy': '๐ง
', 'system': 'โ๏ธ', 'app': '๐ฑ',
'media': '๐ต', 'security': '๐', 'container': '๐ฆ', 'services': '๐ฅ๏ธ',
'monitoring': '๐', 'other': '๐'
};
// Generate QR code using QR Server API (free, reliable)
function generateQRCodeImg(data, size) {
var url = 'https://api.qrserver.com/v1/create-qr-code/?size=' + size + 'x' + size + '&data=' + encodeURIComponent(data);
return '';
}
return view.extend({
title: _('Service Registry'),
pollInterval: 30,
load: function() {
return api.getDashboardData();
},
render: function(data) {
var self = this;
var services = data.services || [];
var providers = data.providers || {};
// Load CSS
var style = document.createElement('style');
style.textContent = this.getStyles();
document.head.appendChild(style);
var published = services.filter(function(s) { return s.published; });
var unpublished = services.filter(function(s) { return !s.published; });
return E('div', { 'class': 'sr-compact' }, [
this.renderHeader(services, providers, data.haproxy, data.tor),
this.renderSection('๐ก Published Services', published, true),
this.renderSection('๐ Discovered Services', unpublished, false),
this.renderLandingLink(data.landing)
]);
},
renderHeader: function(services, providers, haproxy, tor) {
var published = services.filter(function(s) { return s.published; }).length;
var running = services.filter(function(s) { return s.status === 'running'; }).length;
var haproxyCount = providers.haproxy ? providers.haproxy.count : 0;
var torCount = providers.tor ? providers.tor.count : 0;
var haproxyStatus = haproxy && haproxy.container_running ? '๐ข' : '๐ด';
var torStatus = tor && tor.running ? '๐ข' : '๐ด';
return E('div', { 'class': 'sr-header' }, [
E('div', { 'class': 'sr-title' }, [
E('h2', {}, '๐๏ธ Service Registry'),
E('span', { 'class': 'sr-subtitle' },
published + ' published ยท ' + running + ' running ยท ' +
haproxyCount + ' domains ยท ' + torCount + ' onion')
]),
E('div', { 'class': 'sr-providers-bar' }, [
E('span', { 'class': 'sr-provider-badge' }, haproxyStatus + ' HAProxy'),
E('span', { 'class': 'sr-provider-badge' }, torStatus + ' Tor'),
E('span', { 'class': 'sr-provider-badge' }, '๐ ' + (providers.direct ? providers.direct.count : 0) + ' ports'),
E('span', { 'class': 'sr-provider-badge' }, '๐ฆ ' + (providers.lxc ? providers.lxc.count : 0) + ' LXC')
])
]);
},
renderSection: function(title, services, isPublished) {
var self = this;
if (services.length === 0) {
return E('div', { 'class': 'sr-section' }, [
E('h3', { 'class': 'sr-section-title' }, title),
E('div', { 'class': 'sr-empty-msg' }, isPublished ?
'No published services yet' : 'No discovered services')
]);
}
// Group by category
var grouped = {};
services.forEach(function(svc) {
var cat = svc.category || 'other';
if (!grouped[cat]) grouped[cat] = [];
grouped[cat].push(svc);
});
var lists = [];
Object.keys(grouped).sort().forEach(function(cat) {
var catIcon = catIcons[cat] || '๐';
lists.push(E('div', { 'class': 'sr-category' }, [
E('div', { 'class': 'sr-cat-header' }, catIcon + ' ' + cat.charAt(0).toUpperCase() + cat.slice(1)),
E('div', { 'class': 'sr-list' },
grouped[cat].map(function(svc) {
return self.renderServiceRow(svc, isPublished);
})
)
]));
});
return E('div', { 'class': 'sr-section' }, [
E('h3', { 'class': 'sr-section-title' }, title + ' (' + services.length + ')'),
E('div', { 'class': 'sr-categories' }, lists)
]);
},
renderServiceRow: function(service, isPublished) {
var self = this;
var urls = service.urls || {};
// Status indicators
var healthIcon = service.status === 'running' ? '๐ข' :
service.status === 'stopped' ? '๐ด' : '๐ก';
var publishIcon = service.published ? 'โ
' : 'โฌ';
// Build URL display
var urlDisplay = '';
if (urls.clearnet) {
urlDisplay = urls.clearnet;
} else if (urls.onion) {
urlDisplay = urls.onion.substring(0, 25) + '...';
} else if (urls.local) {
urlDisplay = urls.local;
}
// Port display
var portDisplay = service.local_port ? ':' + service.local_port : '';
if (service.haproxy && service.haproxy.backend_port) {
portDisplay = ':' + service.haproxy.backend_port;
}
// SSL/Cert badge
var sslBadge = null;
if (service.haproxy) {
if (service.haproxy.acme) {
sslBadge = E('span', { 'class': 'sr-badge sr-badge-acme', 'title': 'ACME Certificate' }, '๐');
} else if (service.haproxy.ssl) {
sslBadge = E('span', { 'class': 'sr-badge sr-badge-ssl', 'title': 'SSL Enabled' }, '๐');
}
}
// Tor badge
var torBadge = null;
if (service.tor && service.tor.enabled) {
torBadge = E('span', { 'class': 'sr-badge sr-badge-tor', 'title': 'Tor Hidden Service' }, '๐ง
');
}
// QR button for published services with URLs
var qrBtn = null;
if (service.published && (urls.clearnet || urls.onion)) {
qrBtn = E('button', {
'class': 'sr-btn sr-btn-qr',
'title': 'Show QR Code',
'click': ui.createHandlerFn(this, 'handleShowQR', service)
}, '๐ฑ');
}
// Action button
var actionBtn;
if (isPublished) {
actionBtn = E('button', {
'class': 'sr-btn sr-btn-unpublish',
'title': 'Unpublish',
'click': ui.createHandlerFn(this, 'handleUnpublish', service.id)
}, 'โ');
} else {
actionBtn = E('button', {
'class': 'sr-btn sr-btn-publish',
'title': 'Quick Publish',
'click': ui.createHandlerFn(this, 'handleQuickPublish', service)
}, '๐ค');
}
return E('div', { 'class': 'sr-row' }, [
E('span', { 'class': 'sr-col-health', 'title': service.status || 'unknown' }, healthIcon),
E('span', { 'class': 'sr-col-publish' }, publishIcon),
E('span', { 'class': 'sr-col-name' }, [
E('strong', {}, service.name || service.id),
E('span', { 'class': 'sr-port' }, portDisplay)
]),
E('span', { 'class': 'sr-col-url' },
urlDisplay ? E('a', { 'href': urlDisplay.startsWith('http') ? urlDisplay : 'http://' + urlDisplay, 'target': '_blank' }, urlDisplay) : '-'
),
E('span', { 'class': 'sr-col-badges' }, [sslBadge, torBadge].filter(Boolean)),
E('span', { 'class': 'sr-col-qr' }, qrBtn),
E('span', { 'class': 'sr-col-action' }, actionBtn)
]);
},
handleShowQR: function(service) {
var urls = service.urls || {};
var qrBoxes = [];
if (urls.clearnet) {
var qrDiv = E('div', { 'class': 'sr-qr-box' });
qrDiv.innerHTML = '