Guard unavailable appliance service links

This commit is contained in:
Jino Jose 2026-06-30 16:39:02 +05:30
parent 2b03df0e3a
commit 0a2dc49314

View File

@ -48,6 +48,14 @@
max: 'Max',
};
var SERVICE_PORTS = {
'3001': { name: 'open-webui', label: 'Open WebUI' },
'11434': { name: 'ollama', label: 'Ollama' },
'8000': { name: 'chromadb', label: 'ChromaDB' },
'8888': { name: 'jupyter', label: 'Jupyter' },
'8080': { name: 'cezen-api', label: 'Nexus API' },
};
// ── Upgrade modal (injected once into the DOM) ──────────────────────────────
function ensureModal() {
if (document.getElementById('cezen-upgrade-modal')) return;
@ -80,9 +88,84 @@
document.getElementById('cezen-upgrade-modal').classList.add('cezen-modal-open');
}
function openServiceModal(label) {
ensureModal();
document.getElementById('cezen-upgrade-title').textContent = label + ' is not running';
document.getElementById('cezen-upgrade-body').textContent =
'This service is not currently available on this appliance. Check the home page service status, or ask the administrator to enable and start the service for this tier.';
document.getElementById('cezen-upgrade-cta').style.display = 'none';
document.getElementById('cezen-upgrade-modal').classList.add('cezen-modal-open');
}
function closeModal() {
var m = document.getElementById('cezen-upgrade-modal');
if (m) m.classList.remove('cezen-modal-open');
var cta = document.getElementById('cezen-upgrade-cta');
if (cta) cta.style.display = '';
}
function currentBaseForPort(port) {
var protocol = location.protocol === 'https:' ? 'https:' : 'http:';
var host = location.hostname || 'ai.local';
return protocol + '//' + host + ':' + port;
}
function rewriteAiLocalText(value) {
if (!value || value.indexOf('ai.local') === -1) return value;
return value
.replace(/https?:\/\/ai\.local:3001/g, currentBaseForPort('3001'))
.replace(/https?:\/\/ai\.local:11434/g, currentBaseForPort('11434'))
.replace(/https?:\/\/ai\.local:8000/g, currentBaseForPort('8000'))
.replace(/https?:\/\/ai\.local:8888/g, currentBaseForPort('8888'))
.replace(/https?:\/\/ai\.local:8080/g, currentBaseForPort('8080'));
}
function normalizeApplianceLinks() {
document.querySelectorAll('a[href]').forEach(function (link) {
var href = link.getAttribute('href') || '';
if (href.indexOf('ai.local') !== -1) link.setAttribute('href', rewriteAiLocalText(href));
});
var walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, {
acceptNode: function (node) {
var tag = node.parentNode && node.parentNode.tagName;
if (tag === 'SCRIPT' || tag === 'STYLE') return NodeFilter.FILTER_REJECT;
return node.nodeValue.indexOf('ai.local') !== -1 ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
}
});
var textNodes = [];
while (walker.nextNode()) textNodes.push(walker.currentNode);
textNodes.forEach(function (node) { node.nodeValue = rewriteAiLocalText(node.nodeValue); });
}
function serviceForHref(href) {
try {
var u = new URL(href, location.href);
return SERVICE_PORTS[u.port || (u.protocol === 'https:' ? '443' : '80')];
} catch (e) {
return null;
}
}
function applyServiceAvailability() {
fetch('/api/services', { credentials: 'include' })
.then(function (r) { return r.ok ? r.json() : []; })
.then(function (services) {
var online = {};
services.forEach(function (svc) { online[svc.name] = !!svc.ok; });
document.querySelectorAll('a[href]').forEach(function (link) {
var svc = serviceForHref(link.getAttribute('href') || '');
if (!svc || online[svc.name]) return;
if (link.classList.contains('cezen-service-unavailable')) return;
link.classList.add('cezen-service-unavailable');
link.setAttribute('title', svc.label + ' is not running on this appliance');
link.addEventListener('click', function (e) {
e.preventDefault();
openServiceModal(svc.label);
});
});
})
.catch(function () { /* Public pages or offline backend: leave links unchanged. */ });
}
// ── Apply tier locks to all nav links ──────────────────────────────────────
@ -129,6 +212,8 @@
var tierSlug = b.tier_slug || 'basic';
ensureApplianceNavLink();
normalizeApplianceLinks();
applyServiceAvailability();
// ── Accent color CSS variable ───────────────────────────────────────────
document.documentElement.style.setProperty('--accent', accent);
@ -229,6 +314,19 @@
'}',
'a.cezen-locked:hover { opacity: 0.7; }',
'a.cezen-service-unavailable {',
' opacity: 0.58;',
' cursor: not-allowed !important;',
'}',
'a.cezen-service-unavailable::after {',
' content: " offline";',
' display: inline-block;',
' margin-left: 6px;',
' color: #dc2626;',
' font-size: 0.75em;',
' font-weight: 700;',
'}',
/* Lock badge */
'.cezen-lock-badge {',
' font-size: 0.65em;',