/**
* Header Tagger — client-side logic.
* Uses Zoraxy's $.cjax() for CSRF-safe AJAX.
*/
function showToast(message, type) {
var toast = document.getElementById('toast');
toast.textContent = message;
toast.className = 'toast ' + (type || '');
toast.classList.remove('hidden');
setTimeout(function () { toast.classList.add('hidden'); }, 4000);
}
function esc(s) {
if (!s) return '';
return s.replace(/&/g, '&').replace(//g, '>');
}
function escAttr(s) {
if (!s) return '';
return s.replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, ''');
}
function renderGroups(data) {
var container = document.getElementById('groups');
var groups = data.groups || [];
if (groups.length === 0) {
container.innerHTML = '
No tagged domains found. Tag a domain in Zoraxy (e.g. "tracking") and refresh.
';
return;
}
var html = groups.map(function (g) {
var domainsHtml = g.domains.map(function (d) {
var isSource = (d === g.source);
if (isSource) {
return '' + esc(d) + ' SOURCE';
}
return '';
}).join('');
var noSource = !g.hasSource
? 'No domain in this tag has headers yet. Set headers on one domain (via Zoraxy UI) to use it as the source.
'
: '';
return (
'' +
'
' + esc(g.tag) + '
' +
noSource +
'
' +
'
' +
'' +
'' +
'
' +
'
' +
'
'
);
}).join('');
container.innerHTML = html;
}
function loadGroups() {
$.get('./api/groups', function (data) {
renderGroups(data);
}).fail(function () {
showToast('Failed to load tags', 'error');
});
}
function getSelectedTargets(group) {
var targets = [];
group.querySelectorAll('.target-cb:checked').forEach(function (cb) {
targets.push(cb.dataset.domain);
});
return targets;
}
function applyGroup(group) {
var tag = group.dataset.tag;
var source = group.dataset.source;
var targets = getSelectedTargets(group);
var resultEl = group.querySelector('.result');
if (!source) { showToast('No source domain with headers', 'error'); return; }
if (targets.length === 0) { showToast('No target domains selected', 'error'); return; }
resultEl.textContent = 'Applying...';
$.cjax({
url: './api/apply',
type: 'POST',
dataType: 'json',
data: { tag: tag, source: source, targets: targets },
success: function (data) {
var ok = (data.results || []).filter(function (r) { return r.success; }).length;
var fail = (data.results || []).filter(function (r) { return !r.success; }).length;
resultEl.innerHTML = '' + ok + ' applied' + (fail ? ', ' + fail + ' failed' : '');
loadGroups();
},
error: function (xhr) {
var msg = 'Apply failed';
try { var r = JSON.parse(xhr.responseText); msg = r.error || r.message || msg; } catch(e) {}
resultEl.innerHTML = '' + esc(msg) + '';
}
});
}
function removeGroup(group) {
var source = group.dataset.source;
var targets = getSelectedTargets(group);
var resultEl = group.querySelector('.result');
if (!source) { showToast('No source domain', 'error'); return; }
if (targets.length === 0) { showToast('No target domains selected', 'error'); return; }
resultEl.textContent = 'Removing...';
$.cjax({
url: './api/remove',
type: 'POST',
dataType: 'json',
data: { source: source, targets: targets },
success: function (data) {
var ok = (data.results || []).filter(function (r) { return r.success; }).length;
var fail = (data.results || []).filter(function (r) { return !r.success; }).length;
resultEl.innerHTML = '' + ok + ' removed' + (fail ? ', ' + fail + ' failed' : '');
loadGroups();
},
error: function (xhr) {
var msg = 'Remove failed';
try { var r = JSON.parse(xhr.responseText); msg = r.error || r.message || msg; } catch(e) {}
resultEl.innerHTML = '' + esc(msg) + '';
}
});
}
document.getElementById('groups').addEventListener('click', function (e) {
var group = e.target.closest('.group');
if (!group) return;
if (e.target.closest('.btn-apply')) applyGroup(group);
if (e.target.closest('.btn-remove')) removeGroup(group);
});
loadGroups();