File: //proc/self/root/tmp/ezdns_index_runtime_scripts.js
const apiBase = '';
const scansTbody = document.getElementById('scans');
const scanBtn = document.getElementById('scan');
const domainInput = document.getElementById('domain');
const scanOutcome = document.getElementById('scanOutcome');
let lastScanOutcomeDomain = '';
const API_KEY = 'dev-key';
const SUPPORT_URL = 'https://support.panomity.com/tickets/new/';
const quickRequestForm = document.getElementById('quickRequestForm');
const requestDomain = document.getElementById('requestDomain');
const requestEmail = document.getElementById('requestEmail');
const requestMessage = document.getElementById('requestMessage');
const requestPlan = document.getElementById('requestPlan');
const requestStatus = document.getElementById('requestStatus');
const ORDER_URLS = {
priority_audit: 'https://support.panomity.com/cart/priority-support/DNS-Priority-Audit/&step=0',
quick_fix: 'https://support.panomity.com/cart/priority-support/DNS-Quick-Fix/&step=0',
cleanup: 'https://support.panomity.com/cart/priority-support/DNS-Mail-Security-Cleanup/&step=0',
managed_monitoring: 'https://support.panomity.com/?cmd=cart&action=add&id=856&cycle=m',
pro_monthly: 'https://support.panomity.com/?cmd=cart&action=add&id=854&cycle=m',
pro_yearly: 'https://support.panomity.com/?cmd=cart&action=add&id=854&cycle=a',
business_monthly: 'https://support.panomity.com/?cmd=cart&action=add&id=855&cycle=m',
business_yearly: 'https://support.panomity.com/?cmd=cart&action=add&id=855&cycle=a',
agency_monthly: 'https://support.panomity.com/?cmd=cart&action=add&id=856&cycle=m',
agency_yearly: 'https://support.panomity.com/?cmd=cart&action=add&id=856&cycle=a'
};
const ORDER_POSTS = {
priority_audit: { action: "https://support.panomity.com/cart/priority-support/DNS-Priority-Audit/&step=0", id: "879", cycle: "Once" },
quick_fix: { action: "https://support.panomity.com/cart/priority-support/DNS-Quick-Fix/&step=0", id: "867", cycle: "Once" },
cleanup: { action: "https://support.panomity.com/cart/priority-support/DNS-Mail-Security-Cleanup/&step=0", id: "868", cycle: "Once" }
};
function trackEvent(category, action, name) {
if (window._paq) window._paq.push(['trackEvent', category, action, name || '']);
}
function currentDomainValue() {
const typed = domainInput && domainInput.value ? domainInput.value.trim().toLowerCase() : '';
return typed || lastScanOutcomeDomain;
}
async function recordLead(intent, plan, extra = {}) {
try {
const payload = Object.assign({
source: 'website',
intent,
plan,
domain: currentDomainValue(),
page: window.location.pathname
}, extra || {});
await fetch('/v1/leads', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
} catch (e) { /* lead tracking must never block sales */ }
}
function syncRequestDomain() {
if (requestDomain && !requestDomain.value && currentDomainValue()) {
requestDomain.value = currentDomainValue();
}
}
async function submitQualifiedRequest(event) {
event.preventDefault();
const email = requestEmail ? requestEmail.value.trim() : '';
const message = requestMessage ? requestMessage.value.trim() : '';
const domain = requestDomain && requestDomain.value.trim() ? requestDomain.value.trim().toLowerCase() : currentDomainValue();
const plan = requestPlan && requestPlan.value ? requestPlan.value : 'cleanup';
if (!email || !message) {
if (requestStatus) requestStatus.textContent = 'Bitte E-Mail und Problem ergänzen.';
return;
}
if (requestStatus) requestStatus.textContent = 'Anfrage wird gespeichert…';
trackEvent('Revenue', 'qualified_fix_request', plan);
await recordLead('qualified_fix_request', plan, {
domain,
contact_email: email,
message,
page: window.location.pathname + '#fix-request'
});
if (requestStatus) requestStatus.textContent = 'Gespeichert. Wir haben jetzt Domain, Kontakt und Problem im Revenue-Report.';
toast('Fix-Anfrage gespeichert. Nächster Schritt: passendes Paket buchen.', 'success', {
actionHref: orderUrlFor(plan) || supportTicketUrl('qualified_fix_request', plan),
actionLabel: 'Paket öffnen',
timeoutMs: 9000
});
}
function supportTicketUrl(intent, plan) {
const params = new URLSearchParams();
params.set('subject', `ezdns.support ${intent}: ${plan}`);
if (currentDomainValue()) params.set('domain', currentDomainValue());
params.set('source', 'ezdns.support');
return `${SUPPORT_URL}?${params.toString()}`;
}
function orderUrlFor(plan) {
return ORDER_URLS[plan] || '';
}
function submitHostBillOrder(plan) {
const cfg = ORDER_POSTS[plan];
if (!cfg) return false;
const form = document.createElement("form");
form.method = "post";
form.action = cfg.action;
for (const [name, value] of Object.entries({ action: "add", id: cfg.id, cycle: cfg.cycle })) {
const input = document.createElement("input");
input.type = "hidden";
input.name = name;
input.value = value;
form.appendChild(input);
}
document.body.appendChild(form);
form.submit();
return true;
}
async function requestService(plan, intent = 'service_lead') {
trackEvent('Revenue', intent, plan);
await recordLead(intent, plan);
const orderUrl = orderUrlFor(plan);
toast(orderUrl ? 'Warenkorb wird geöffnet…' : 'Anfrage wird geöffnet…', 'info', { timeoutMs: 1500 });
setTimeout(() => {
if (submitHostBillOrder(plan)) return;
window.location.href = orderUrl || supportTicketUrl(intent, plan);
}, 250);
}
async function fetchJSON(url, opts={}) {
const headers = Object.assign({ 'x-api-key': API_KEY }, opts.headers||{});
const res = await fetch(url, Object.assign({}, opts, { headers }));
if (!res.ok) throw new Error('HTTP '+res.status);
return await res.json();
}
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function toast(message, variant = 'info', opts = {}) {
const hostId = 'toastHost';
let host = document.getElementById(hostId);
if (!host) {
host = document.createElement('div');
host.id = hostId;
host.className = 'fixed top-4 right-4 z-50 flex flex-col gap-2';
document.body.appendChild(host);
}
const t = document.createElement('div');
const cls = variant === 'success'
? 'bg-emerald-900 text-emerald-100 border border-emerald-700'
: variant === 'error'
? 'bg-red-900 text-red-100 border border-red-700'
: 'bg-slate-900 text-slate-100 border border-slate-700';
t.className = `px-3 py-2 rounded shadow max-w-sm ${cls}`;
const row = document.createElement('div');
row.className = 'flex items-center gap-3';
const msg = document.createElement('div');
msg.className = 'text-sm';
msg.textContent = message;
row.appendChild(msg);
if (opts.actionHref && opts.actionLabel) {
const a = document.createElement('a');
a.href = opts.actionHref;
a.className = 'text-sm text-blue-200 hover:underline whitespace-nowrap';
a.textContent = opts.actionLabel;
row.appendChild(a);
}
t.appendChild(row);
host.appendChild(t);
const timeoutMs = Number.isFinite(opts.timeoutMs) ? opts.timeoutMs : 3500;
if (timeoutMs > 0) {
setTimeout(() => {
t.remove();
if (host && host.childElementCount === 0) host.remove();
}, timeoutMs);
}
return t;
}
const scanPollers = new Map();
async function pollScanUntilDone(scanId, { maxMs = 120000, intervalMs = 2000 } = {}) {
const start = Date.now();
while (Date.now() - start < maxMs) {
await sleep(intervalMs);
const scan = await fetchJSON(`${apiBase}/v1/scan/${scanId}`);
if (scan && scan.status === 'done') return scan;
}
throw new Error('timeout');
}
function row(status) {
const cls = status === 'done' ? 'bg-emerald-900 text-emerald-200' : 'bg-amber-900 text-amber-200';
return `<span class="px-2 py-0.5 rounded ${cls}">${status}</span>`;
}
function escapeHTML(value) {
return String(value ?? '').replace(/[&<>"']/g, (ch) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[ch]));
}
function scanFindings(scan) {
const result = scan && scan.result ? scan.result : {};
return Array.isArray(result.findings) ? result.findings : [];
}
function renderScanOutcome(scan) {
if (!scanOutcome || !scan) return;
const result = scan.result || {};
const domain = result.domain || currentDomainValue() || 'deine Domain';
lastScanOutcomeDomain = String(domain || '').trim().toLowerCase();
syncRequestDomain();
const findings = scanFindings(scan);
const critical = findings.filter((f) => f.severity === 'error').length;
const warnings = findings.filter((f) => f.severity === 'warning').length;
const top = findings.find((f) => f.severity === 'error') || findings.find((f) => f.severity === 'warning') || findings[0];
const recommendedPlan = critical > 0 || findings.length > 1 ? 'cleanup' : 'quick_fix';
const recommendedLabel = recommendedPlan === 'cleanup' ? 'Cleanup €499 buchen' : 'Quick Fix €149 buchen';
const scoreLabel = critical > 0 ? 'kritisch' : warnings > 0 ? 'fixbar' : 'stabil';
const summaryText = findings.length
? `${critical} kritisch · ${warnings} Warnungen · ${findings.length} Findings`
: 'Keine dringenden Findings gefunden';
const findingText = top
? `${escapeHTML(top.category || 'dns')} – ${escapeHTML(top.message || 'Empfohlene DNS-Korrektur')}`
: 'Monitoring aktivieren, damit DNS- und Mail-Änderungen nicht unbemerkt bleiben.';
scanOutcome.classList.remove('hidden');
scanOutcome.innerHTML = `
<div class="ez-scan-outcome-head">
<div>
<span>Scan-Ergebnis</span>
<strong>${escapeHTML(domain)}</strong>
</div>
<b>${escapeHTML(scoreLabel)}</b>
</div>
<div class="ez-scan-outcome-body">
<div>
<small>${escapeHTML(summaryText)}</small>
<p>${findingText}</p>
</div>
<div class="ez-scan-outcome-actions">
<a href="/app/detail.html?id=${encodeURIComponent(scan.id)}">Report öffnen</a>
<button data-scan-service-lead="priority_audit">Audit €49</button>
<button data-scan-service-lead="${recommendedPlan}">${recommendedLabel}</button>
${recommendedPlan === 'cleanup' ? '<button data-scan-service-lead="quick_fix">Quick Fix €149</button>' : '<button data-scan-service-lead="cleanup">Cleanup €499</button>'}
</div>
</div>
`;
}
function setActiveTab(tab) {
const buttons = document.querySelectorAll('.tab-btn');
const panels = document.querySelectorAll('.tab-panel');
for (const b of buttons) {
const isActive = b.dataset.tab === tab;
b.className = isActive
? 'tab-btn px-3 py-1.5 rounded-lg bg-slate-800 text-slate-100'
: 'tab-btn px-3 py-1.5 rounded-lg border border-slate-700 text-slate-200';
}
for (const p of panels) {
p.classList.toggle('hidden', p.id !== `tab-${tab}`);
}
}
async function loadScans() {
const data = await fetchJSON(`${apiBase}/v1/scans?limit=5`);
scansTbody.innerHTML = '';
for (const s of data.items) {
const tr = document.createElement('tr');
const domain = s.domain || s.domain_id;
tr.innerHTML = `
<td class="py-2 text-blue-300">${domain}</td>
<td>${row(s.status)}</td>
<td><a href="/app/detail.html?id=${s.id}" class="text-sm text-blue-400 hover:underline">Open</a></td>
`;
scansTbody.appendChild(tr);
}
}
// kein separates Detail-Panel mehr
const listedCheckbox = document.getElementById('listed');
scanBtn.addEventListener('click', async () => {
const d = domainInput.value.trim();
if (!d) return;
try {
scanBtn.disabled = true; scanBtn.textContent = 'Starting…';
const created = await fetchJSON(`${apiBase}/v1/scan`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ domain: d, listed: !!listedCheckbox.checked }) });
const scanId = created && created.id;
toast('Scan läuft… wir sagen Bescheid, sobald er fertig ist.', 'info', { timeoutMs: 4000 });
setActiveTab('scans');
await loadScans();
if (scanId && !scanPollers.has(scanId)) {
const p = pollScanUntilDone(scanId)
.then(async (finishedScan) => {
renderScanOutcome(finishedScan);
toast('Scan fertig. Ergebnis und Fix-Optionen sind sichtbar.', 'success', { actionLabel: 'Report öffnen', actionHref: `/app/detail.html?id=${scanId}`, timeoutMs: 9000 });
await loadScans();
})
.catch(() => {
toast('Scan läuft noch – bitte in „Letzte Scans“ prüfen.', 'info', { timeoutMs: 6000 });
})
.finally(() => {
scanPollers.delete(scanId);
});
scanPollers.set(scanId, p);
}
} catch (e) {
toast('Scan konnte nicht gestartet werden.', 'error', { timeoutMs: 5000 });
} finally {
scanBtn.disabled = false; scanBtn.textContent = 'Scan';
}
});
// Checkout-Funktion für den kostenlosen Scan. Bezahlte CTAs nutzen requestService() und HostBill direkt.
function checkout(plan) {
if (plan === 'free') {
trackEvent('Revenue', 'free_plan_clicked', 'free');
domainInput.focus();
setActiveTab('scans');
return;
}
requestService(plan, 'plan_inquiry');
}
// Tabs + initial state
document.querySelectorAll('.tab-btn').forEach((b) => {
b.addEventListener('click', () => setActiveTab(b.dataset.tab));
});
setActiveTab('why');
const cta = document.getElementById('ctaFocus');
if (cta) {
cta.addEventListener('click', () => {
trackEvent('Acquisition', 'scan_cta_clicked', 'hero');
domainInput.focus();
setActiveTab('scans');
});
}
const serviceCta = document.getElementById('serviceCta');
if (serviceCta) {
serviceCta.addEventListener('click', () => requestService(serviceCta.dataset.serviceLead || 'priority_audit', 'service_lead'));
}
if (quickRequestForm) {
syncRequestDomain();
quickRequestForm.addEventListener('submit', submitQualifiedRequest);
}
document.body.addEventListener('click', (event) => {
const scanServiceBtn = event.target.closest('[data-scan-service-lead]');
if (!scanServiceBtn) return;
event.preventDefault();
requestService(scanServiceBtn.dataset.scanServiceLead, 'scan_result_fix');
});
loadScans();
// Footer year
document.getElementById('year').textContent = new Date().getFullYear();
// Matomo config + tracking
(async () => {
try {
await import('/config.js');
const cfg = window.__APP_CONFIG__ || {};
if (cfg.MATOMO_URL && cfg.MATOMO_SITE_ID) {
var _paq = (window._paq = window._paq || []);
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u = cfg.MATOMO_URL.endsWith('/') ? cfg.MATOMO_URL : cfg.MATOMO_URL + '/';
_paq.push(['setTrackerUrl', u + 'matomo.php']);
_paq.push(['setSiteId', cfg.MATOMO_SITE_ID]);
var d = document, g = d.createElement('script'), s = d.getElementsByTagName('script')[0];
g.async = true; g.src = u + 'matomo.js'; s.parentNode.insertBefore(g, s);
})();
}
} catch (e) { /* ignore */ }
})();