|
|
@@ -93,15 +93,15 @@
|
|
|
</div>
|
|
|
<div class="flex gap-3 opacity-0 group-hover:opacity-100 transition items-center">
|
|
|
{% if session.get('role') == 'admin' %}
|
|
|
- <button onclick="openAssignModal('{{ zone_name }}', '{{ owners.get(zone_name, 'admin') }}')" class="text-blue-400 text-[10px] font-black uppercase tracking-widest hover:text-blue-300">Assign</button>
|
|
|
+ <button type="button" onclick="openAssignModal('{{ zone_name|e }}', '{{ owners.get(zone_name, 'admin')|e }}')" class="text-blue-400 text-[10px] font-black uppercase tracking-widest hover:text-blue-300">Assign</button>
|
|
|
{% endif %}
|
|
|
- <a href="/zones/delete/{{ url_param }}" onclick="handleAjaxLink(event, this, 'dashboard-dynamic-content', restoreDashboardState, 'Delete this zone permanently?')" class="text-red-500 text-[10px] font-black uppercase tracking-widest hover:text-red-400 transition">Delete</a>
|
|
|
+ <a href="/zones/delete/{{ url_param|urlencode }}" onclick="handleAjaxLink(event, this, 'dashboard-dynamic-content', restoreDashboardState, 'Delete this zone permanently?')" class="text-red-500 text-[10px] font-black uppercase tracking-widest hover:text-red-400 transition">Delete</a>
|
|
|
</div>
|
|
|
</div>
|
|
|
<h3 class="text-xl font-bold text-white mb-6 font-mono truncate" title="{{ zone_name }}">
|
|
|
{% if zone_name == '.' %}. (Root Zone){% else %}{{ zone_name }}{% endif %}
|
|
|
</h3>
|
|
|
- <a href="/zones/manage/{{ url_param }}" class="block w-full text-center bg-gray-800 hover:bg-blue-600 py-3 rounded-xl text-gray-300 hover:text-white font-black text-xs transition uppercase tracking-widest">Manage</a>
|
|
|
+ <a href="/zones/manage/{{ url_param|urlencode }}" class="block w-full text-center bg-gray-800 hover:bg-blue-600 py-3 rounded-xl text-gray-300 hover:text-white font-black text-xs transition uppercase tracking-widest">Manage</a>
|
|
|
</div>
|
|
|
{% endfor %}
|
|
|
</div>
|
|
|
@@ -124,9 +124,11 @@
|
|
|
<td class="p-6 text-gray-400 text-xs uppercase">{{ owners.get(zone_name, 'admin') }}</td>
|
|
|
<td class="p-6 text-right">
|
|
|
<div class="flex justify-end gap-4 items-center">
|
|
|
- {% if session.get('role') == 'admin' %}<button onclick="openAssignModal('{{ zone_name }}', '{{ owners.get(zone_name, 'admin') }}')" class="opacity-0 group-hover:opacity-100 text-purple-400 text-[10px] font-black uppercase transition">Assign</button>{% endif %}
|
|
|
- <a href="/zones/manage/{{ url_param }}" class="text-green-400 font-black text-[10px] uppercase">Manage</a>
|
|
|
- <a href="/zones/delete/{{ url_param }}" onclick="handleAjaxLink(event, this, 'dashboard-dynamic-content', restoreDashboardState, 'Delete this zone permanently?')" class="opacity-0 group-hover:opacity-100 text-red-500 text-[10px] font-black uppercase transition">Delete</a>
|
|
|
+ {% if session.get('role') == 'admin' %}
|
|
|
+ <button type="button" onclick="openAssignModal('{{ zone_name|e }}', '{{ owners.get(zone_name, 'admin')|e }}')" class="opacity-0 group-hover:opacity-100 text-purple-400 text-[10px] font-black uppercase transition">Assign</button>
|
|
|
+ {% endif %}
|
|
|
+ <a href="/zones/manage/{{ url_param|urlencode }}" class="text-green-400 font-black text-[10px] uppercase">Manage</a>
|
|
|
+ <a href="/zones/delete/{{ url_param|urlencode }}" onclick="handleAjaxLink(event, this, 'dashboard-dynamic-content', restoreDashboardState, 'Delete this zone permanently?')" class="opacity-0 group-hover:opacity-100 text-red-500 text-[10px] font-black uppercase transition">Delete</a>
|
|
|
</div>
|
|
|
</td>
|
|
|
</tr>
|
|
|
@@ -142,9 +144,18 @@
|
|
|
<div class="bg-[#12161f] border border-gray-800 w-full max-w-sm rounded-3xl p-8 shadow-2xl">
|
|
|
<h3 class="text-xl font-black text-white mb-6 uppercase text-center">Assign Ownership</h3>
|
|
|
<form action="/zones/assign" method="POST" class="space-y-6" onsubmit="handleAjaxForm(event, this, 'dashboard-dynamic-content', restoreDashboardState)">
|
|
|
- <input type="hidden" name="domain" id="assignDomainInput"><div class="text-center mb-4"><span id="assignDomainDisplay" class="text-lg font-mono text-blue-400 font-bold"></span></div>
|
|
|
- <select name="owner" id="assignOwnerSelect" class="w-full bg-[#0b0e14] border border-gray-800 rounded-xl p-4 text-white font-bold outline-none cursor-pointer"><option value="admin">admin (Unassign)</option>{% for u in all_usernames %}{% if u != 'admin' %}<option value="{{ u }}">{{ u }}</option>{% endif %}{% endfor %}</select>
|
|
|
- <div class="flex gap-4 pt-6"><button type="button" onclick="closeAssignModal()" class="flex-1 bg-gray-800 text-gray-400 font-bold py-4 rounded-xl uppercase text-xs">Cancel</button><button type="submit" class="flex-1 bg-blue-600 text-white font-black py-4 rounded-xl uppercase text-xs shadow-xl">Assign</button></div>
|
|
|
+ <input type="hidden" name="domain" id="assignDomainInput">
|
|
|
+ <div class="text-center mb-4"><span id="assignDomainDisplay" class="text-lg font-mono text-blue-400 font-bold"></span></div>
|
|
|
+ <select name="owner" id="assignOwnerSelect" class="w-full bg-[#0b0e14] border border-gray-800 rounded-xl p-4 text-white font-bold outline-none cursor-pointer">
|
|
|
+ <option value="admin">admin (Unassign)</option>
|
|
|
+ {% for u in all_usernames %}
|
|
|
+ {% if u != 'admin' %}<option value="{{ u }}">{{ u }}</option>{% endif %}
|
|
|
+ {% endfor %}
|
|
|
+ </select>
|
|
|
+ <div class="flex gap-4 pt-6">
|
|
|
+ <button type="button" onclick="closeAssignModal()" class="flex-1 bg-gray-800 text-gray-400 font-bold py-4 rounded-xl uppercase text-xs">Cancel</button>
|
|
|
+ <button type="submit" class="flex-1 bg-blue-600 text-white font-black py-4 rounded-xl uppercase text-xs shadow-xl">Assign</button>
|
|
|
+ </div>
|
|
|
</form>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -154,25 +165,66 @@
|
|
|
<h3 class="text-2xl font-black text-white mb-8 uppercase border-b border-gray-800 pb-4 text-center">Create New Zone</h3>
|
|
|
<form action="/zones/add" method="POST" id="addZoneForm" class="space-y-6" onsubmit="handleAjaxForm(event, this, 'dashboard-dynamic-content', restoreDashboardState)">
|
|
|
<div class="grid grid-cols-2 gap-4">
|
|
|
- <div><label class="block text-[10px] font-bold text-gray-500 uppercase mb-2 ml-1">Direction</label><select name="direction" id="zoneDirection" onchange="updatePlaceholder()" class="w-full bg-[#0b0e14] border border-gray-800 rounded-xl p-4 text-white font-bold outline-none text-sm cursor-pointer"><option value="forward">Forward</option><option value="reverse">Reverse</option></select></div>
|
|
|
- <div><label class="block text-[10px] font-bold text-gray-500 uppercase mb-2 ml-1">Type</label><select name="type" id="zoneType" onchange="toggleZoneFields()" class="w-full bg-[#0b0e14] border border-gray-800 rounded-xl p-4 text-white font-bold outline-none text-sm cursor-pointer"><option value="master">Master</option><option value="slave">Slave</option><option value="forward">Forwarder</option><option value="hint">Hint (Root Zone)</option></select></div>
|
|
|
+ <div>
|
|
|
+ <label class="block text-[10px] font-bold text-gray-500 uppercase mb-2 ml-1">Direction</label>
|
|
|
+ <select name="direction" id="zoneDirection" onchange="updatePlaceholder()" class="w-full bg-[#0b0e14] border border-gray-800 rounded-xl p-4 text-white font-bold outline-none text-sm cursor-pointer">
|
|
|
+ <option value="forward">Forward</option>
|
|
|
+ <option value="reverse">Reverse</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <label class="block text-[10px] font-bold text-gray-500 uppercase mb-2 ml-1">Type</label>
|
|
|
+ <select name="type" id="zoneType" onchange="toggleZoneFields()" class="w-full bg-[#0b0e14] border border-gray-800 rounded-xl p-4 text-white font-bold outline-none text-sm cursor-pointer">
|
|
|
+ <option value="master">Master</option>
|
|
|
+ <option value="slave">Slave</option>
|
|
|
+ <option value="forward">Forwarder</option>
|
|
|
+ <option value="hint">Hint (Root Zone)</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- <div id="domainContainer"><label id="domainLabel" class="block text-[10px] font-bold text-gray-500 uppercase mb-2 ml-1">Domain Name</label><input type="text" name="domain" id="domainInput" placeholder="example.com" required class="w-full bg-[#0b0e14] border border-gray-800 rounded-xl p-4 text-white font-mono outline-none focus:border-blue-500 transition-colors"></div>
|
|
|
+
|
|
|
+ <div id="domainContainer">
|
|
|
+ <label id="domainLabel" class="block text-[10px] font-bold text-gray-500 uppercase mb-2 ml-1">Domain Name</label>
|
|
|
+ <input type="text" name="domain" id="domainInput" placeholder="example.com" required class="w-full bg-[#0b0e14] border border-gray-800 rounded-xl p-4 text-white font-mono outline-none focus:border-blue-500 transition-colors">
|
|
|
+ </div>
|
|
|
+
|
|
|
<div id="masterFields" class="space-y-4">
|
|
|
- <label class="block text-[10px] font-bold text-gray-500 uppercase mb-2 ml-1">Template (Required)</label>
|
|
|
- <select name="template_name" class="w-full bg-[#0b0e14] border border-gray-800 rounded-xl p-4 text-white font-bold outline-none cursor-pointer" required>
|
|
|
- {% if not templates %}
|
|
|
- <option value="" disabled selected>No templates assigned to your account!</option>
|
|
|
- {% else %}
|
|
|
- {% for tname in templates.keys() %}
|
|
|
- <option value="{{ tname }}">{{ tname }}</option>
|
|
|
- {% endfor %}
|
|
|
- {% endif %}
|
|
|
- </select>
|
|
|
- </div>
|
|
|
- <div id="slaveFields" class="hidden space-y-4"><div class="flex justify-between items-center"><label class="text-[10px] font-bold text-gray-500 uppercase">Master IPs</label><button type="button" onclick="addDynamicIp('slaveIpCont', 'master_ips[]')" class="text-[9px] bg-purple-600/20 text-purple-400 px-3 py-1 rounded font-black uppercase">+ Add</button></div><div id="slaveIpCont" class="space-y-2"><input type="text" name="master_ips[]" placeholder="1.2.3.4" class="w-full bg-[#0b0e14] border border-gray-800 rounded-xl p-3 text-white font-mono"></div></div>
|
|
|
- <div id="forwardFields" class="hidden space-y-4"><div class="flex justify-between items-center"><label class="text-[10px] font-bold text-gray-500 uppercase">Forwarders</label><button type="button" onclick="addDynamicIp('forwardIpCont', 'forwarders[]')" class="text-[9px] bg-blue-600/20 text-blue-400 px-3 py-1 rounded font-black uppercase">+ Add</button></div><div id="forwardIpCont" class="space-y-2"><input type="text" name="forwarders[]" placeholder="8.8.8.8" class="w-full bg-[#0b0e14] border border-gray-800 rounded-xl p-3 text-white font-mono"></div></div>
|
|
|
- <div class="flex gap-4 pt-6 border-t border-gray-800"><button type="button" onclick="closeAddModal()" class="flex-1 bg-gray-800 text-gray-400 font-bold py-4 rounded-xl hover:text-white transition uppercase text-xs tracking-widest">Cancel</button><button type="submit" class="flex-1 bg-blue-600 text-white font-black py-4 rounded-xl shadow-xl transition uppercase text-xs tracking-widest hover:bg-blue-500">Create</button></div>
|
|
|
+ <label class="block text-[10px] font-bold text-gray-500 uppercase mb-2 ml-1">Template (Required)</label>
|
|
|
+ <select name="template_name" id="templateSelect" class="w-full bg-[#0b0e14] border border-gray-800 rounded-xl p-4 text-white font-bold outline-none cursor-pointer" required>
|
|
|
+ {% if not templates %}
|
|
|
+ <option value="" disabled selected>No templates assigned to your account!</option>
|
|
|
+ {% else %}
|
|
|
+ {% for tname in templates.keys() %}
|
|
|
+ <option value="{{ tname }}">{{ tname }}</option>
|
|
|
+ {% endfor %}
|
|
|
+ {% endif %}
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div id="slaveFields" class="hidden space-y-4">
|
|
|
+ <div class="flex justify-between items-center">
|
|
|
+ <label class="text-[10px] font-bold text-gray-500 uppercase">Master IPs</label>
|
|
|
+ <button type="button" onclick="addDynamicIp('slaveIpCont', 'master_ips[]')" class="text-[9px] bg-purple-600/20 text-purple-400 px-3 py-1 rounded font-black uppercase">+ Add</button>
|
|
|
+ </div>
|
|
|
+ <div id="slaveIpCont" class="space-y-2">
|
|
|
+ <input type="text" name="master_ips[]" placeholder="1.2.3.4" class="w-full bg-[#0b0e14] border border-gray-800 rounded-xl p-3 text-white font-mono">
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div id="forwardFields" class="hidden space-y-4">
|
|
|
+ <div class="flex justify-between items-center">
|
|
|
+ <label class="text-[10px] font-bold text-gray-500 uppercase">Forwarders</label>
|
|
|
+ <button type="button" onclick="addDynamicIp('forwardIpCont', 'forwarders[]')" class="text-[9px] bg-blue-600/20 text-blue-400 px-3 py-1 rounded font-black uppercase">+ Add</button>
|
|
|
+ </div>
|
|
|
+ <div id="forwardIpCont" class="space-y-2">
|
|
|
+ <input type="text" name="forwarders[]" placeholder="8.8.8.8" class="w-full bg-[#0b0e14] border border-gray-800 rounded-xl p-3 text-white font-mono">
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="flex gap-4 pt-6 border-t border-gray-800">
|
|
|
+ <button type="button" onclick="closeAddModal()" class="flex-1 bg-gray-800 text-gray-400 font-bold py-4 rounded-xl hover:text-white transition uppercase text-xs tracking-widest">Cancel</button>
|
|
|
+ <button type="submit" class="flex-1 bg-blue-600 text-white font-black py-4 rounded-xl shadow-xl transition uppercase text-xs tracking-widest hover:bg-blue-500">Create</button>
|
|
|
+ </div>
|
|
|
</form>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -181,8 +233,17 @@
|
|
|
<div class="bg-[#12161f] border border-gray-800 w-full max-w-md rounded-3xl p-8 shadow-2xl">
|
|
|
<h3 class="text-2xl font-black text-white mb-8 uppercase text-center italic">System Backup & Restore</h3>
|
|
|
<div class="space-y-6">
|
|
|
- <div class="bg-gray-900/50 p-6 rounded-2xl border border-gray-800"><h4 class="text-xs font-black text-green-500 uppercase mb-2">Export Data</h4><a href="/backup/export" class="block w-full text-center bg-green-600/20 text-green-400 font-black py-3 rounded-xl border border-green-500/30 uppercase text-xs">Download ZIP</a></div>
|
|
|
- <div class="bg-gray-900/50 p-6 rounded-2xl border border-gray-800"><h4 class="text-xs font-black text-blue-500 uppercase mb-2">Restore Data</h4><form action="/backup/import" method="POST" enctype="multipart/form-data" class="space-y-4"><input type="file" name="backup_file" required class="block w-full text-xs text-gray-400 file:bg-gray-800 file:text-white file:border-0 file:py-2 file:px-4 file:rounded-lg"><button type="submit" class="w-full bg-blue-600 text-white font-black py-3 rounded-xl uppercase text-xs shadow-xl">Restore ZIP</button></form></div>
|
|
|
+ <div class="bg-gray-900/50 p-6 rounded-2xl border border-gray-800">
|
|
|
+ <h4 class="text-xs font-black text-green-500 uppercase mb-2">Export Data</h4>
|
|
|
+ <a href="/backup/export" class="block w-full text-center bg-green-600/20 text-green-400 font-black py-3 rounded-xl border border-green-500/30 uppercase text-xs">Download ZIP</a>
|
|
|
+ </div>
|
|
|
+ <div class="bg-gray-900/50 p-6 rounded-2xl border border-gray-800">
|
|
|
+ <h4 class="text-xs font-black text-blue-500 uppercase mb-2">Restore Data</h4>
|
|
|
+ <form action="/backup/import" method="POST" enctype="multipart/form-data" class="space-y-4">
|
|
|
+ <input type="file" name="backup_file" required class="block w-full text-xs text-gray-400 file:bg-gray-800 file:text-white file:border-0 file:py-2 file:px-4 file:rounded-lg">
|
|
|
+ <button type="submit" class="w-full bg-blue-600 text-white font-black py-3 rounded-xl uppercase text-xs shadow-xl">Restore ZIP</button>
|
|
|
+ </form>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
<button onclick="closeBackupModal()" class="w-full mt-6 bg-gray-800 text-gray-400 py-3 rounded-xl uppercase text-xs font-bold">Close</button>
|
|
|
</div>
|
|
|
@@ -200,17 +261,21 @@ function renderLiveCharts() {
|
|
|
const ipsData = times.map(t => todayData.timeline[t].unique_ips);
|
|
|
const tldLabels = Object.keys(todayData.tlds).sort((a,b) => todayData.tlds[b] - todayData.tlds[a]).slice(0, 5);
|
|
|
const tldData = tldLabels.map(t => todayData.tlds[t]);
|
|
|
+
|
|
|
const chartOptions = { animation: false, interaction: { mode: 'index', intersect: false }, responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { x: { display: false }, y: { grid: { color: '#1f2937' }, beginAtZero: true } } };
|
|
|
+
|
|
|
if(!rpsChartInst) {
|
|
|
rpsChartInst = new Chart(document.getElementById('rpsChart'), { type: 'line', data: { labels: times, datasets: [{ data: rpsData, borderColor: '#3b82f6', fill: true, tension: 0.4 }] }, options: chartOptions });
|
|
|
} else {
|
|
|
rpsChartInst.data.labels = times; rpsChartInst.data.datasets[0].data = rpsData; rpsChartInst.update('none');
|
|
|
}
|
|
|
+
|
|
|
if(!ipsChartInst) {
|
|
|
ipsChartInst = new Chart(document.getElementById('ipsChart'), { type: 'bar', data: { labels: times, datasets: [{ data: ipsData, backgroundColor: '#8b5cf6' }] }, options: chartOptions });
|
|
|
} else {
|
|
|
ipsChartInst.data.labels = times; ipsChartInst.data.datasets[0].data = ipsData; ipsChartInst.update('none');
|
|
|
}
|
|
|
+
|
|
|
if(!tldChartInst) {
|
|
|
tldChartInst = new Chart(document.getElementById('tldChart'), { type: 'doughnut', data: { labels: tldLabels, datasets: [{ data: tldData, backgroundColor: ['#3b82f6', '#10b981', '#f59e0b', '#8b5cf6', '#6366f1'], borderWidth: 0 }] }, options: { animation: false, responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'right', labels: { color: '#9ca3af', font: { size: 10 } } } }, cutout: '70%' } });
|
|
|
} else {
|
|
|
@@ -238,7 +303,8 @@ async function handleAjaxForm(e, form, containerId, callback) {
|
|
|
}
|
|
|
|
|
|
async function handleAjaxLink(e, link, containerId, callback, confirmMsg) {
|
|
|
- e.preventDefault(); if(confirmMsg && !confirm(confirmMsg)) return;
|
|
|
+ e.preventDefault();
|
|
|
+ if(confirmMsg && !confirm(confirmMsg)) return;
|
|
|
try {
|
|
|
const res = await fetch(link.href);
|
|
|
const html = await res.text();
|
|
|
@@ -247,7 +313,7 @@ async function handleAjaxLink(e, link, containerId, callback, confirmMsg) {
|
|
|
if(newCont) {
|
|
|
document.getElementById(containerId).innerHTML = newCont.innerHTML;
|
|
|
if(callback) callback();
|
|
|
- AppToast.fire({ icon: 'success', title: 'Deleted' });
|
|
|
+ AppToast.fire({ icon: 'success', title: 'Action Completed' });
|
|
|
} else window.location.reload();
|
|
|
} catch(err) { window.location.reload(); }
|
|
|
}
|
|
|
@@ -255,16 +321,36 @@ async function handleAjaxLink(e, link, containerId, callback, confirmMsg) {
|
|
|
// DASHBOARD LOGIC
|
|
|
function openBackupModal() { document.getElementById('backupModal').classList.remove('hidden'); }
|
|
|
function closeBackupModal() { document.getElementById('backupModal').classList.add('hidden'); }
|
|
|
-function openAddModal() { document.getElementById('addZoneModal').classList.remove('hidden'); }
|
|
|
-function closeAddModal() { document.getElementById('addZoneModal').classList.add('hidden'); }
|
|
|
-function openAssignModal(d, o) { document.getElementById('assignDomainInput').value=d; document.getElementById('assignDomainDisplay').innerText=d; document.getElementById('assignOwnerSelect').value=o; document.getElementById('assignZoneModal').classList.remove('hidden'); }
|
|
|
-function closeAssignModal() { document.getElementById('assignZoneModal').classList.add('hidden'); }
|
|
|
+
|
|
|
+function openAddModal() {
|
|
|
+ document.getElementById('addZoneForm').reset();
|
|
|
+ document.getElementById('addZoneModal').classList.remove('hidden');
|
|
|
+ toggleZoneFields();
|
|
|
+}
|
|
|
+
|
|
|
+function closeAddModal() {
|
|
|
+ // Καθαρισμός όλων των δυναμικών IPs κατά το κλείσιμο
|
|
|
+ document.getElementById('slaveIpCont').innerHTML = '<input type="text" name="master_ips[]" placeholder="1.2.3.4" class="w-full bg-[#0b0e14] border border-gray-800 rounded-xl p-3 text-white font-mono">';
|
|
|
+ document.getElementById('forwardIpCont').innerHTML = '<input type="text" name="forwarders[]" placeholder="8.8.8.8" class="w-full bg-[#0b0e14] border border-gray-800 rounded-xl p-3 text-white font-mono">';
|
|
|
+ document.getElementById('addZoneModal').classList.add('hidden');
|
|
|
+}
|
|
|
+
|
|
|
+function openAssignModal(d, o) {
|
|
|
+ document.getElementById('assignDomainInput').value=d;
|
|
|
+ document.getElementById('assignDomainDisplay').innerText=d;
|
|
|
+ document.getElementById('assignOwnerSelect').value=o;
|
|
|
+ document.getElementById('assignZoneModal').classList.remove('hidden');
|
|
|
+}
|
|
|
+
|
|
|
+function closeAssignModal() {
|
|
|
+ document.getElementById('assignZoneModal').classList.add('hidden');
|
|
|
+}
|
|
|
|
|
|
function toggleZoneFields() {
|
|
|
const type = document.getElementById('zoneType').value;
|
|
|
const domCont = document.getElementById('domainContainer');
|
|
|
const domInp = document.getElementById('domainInput');
|
|
|
- const tmplSelect = document.querySelector('select[name="template_name"]');
|
|
|
+ const tmplSelect = document.getElementById('templateSelect');
|
|
|
|
|
|
if(type === 'hint') {
|
|
|
domCont.classList.add('hidden');
|
|
|
@@ -274,7 +360,7 @@ function toggleZoneFields() {
|
|
|
if(domInp.value === '.') domInp.value = '';
|
|
|
}
|
|
|
|
|
|
- // Εμφάνιση/Απόκρυψη και προσθήκη/αφαίρεση του Required!
|
|
|
+ // Ασφαλής έλεγχος με το νέο ID
|
|
|
if (type === 'master') {
|
|
|
document.getElementById('masterFields').style.display = 'block';
|
|
|
if (tmplSelect) tmplSelect.required = true;
|
|
|
@@ -295,7 +381,7 @@ function updatePlaceholder() {
|
|
|
|
|
|
function addDynamicIp(contId, name) {
|
|
|
const div = document.createElement('div'); div.className = 'flex gap-2 mt-2';
|
|
|
- div.innerHTML = `<input type="text" name="${name}" placeholder="0.0.0.0" class="w-full bg-[#0b0e14] border border-gray-800 rounded-xl p-3 text-white font-mono"><button type="button" onclick="this.parentElement.remove()" class="text-red-500 font-black px-4">×</button>`;
|
|
|
+ div.innerHTML = `<input type="text" name="${name}" placeholder="0.0.0.0" class="w-full bg-[#0b0e14] border border-gray-800 rounded-xl p-3 text-white font-mono"><button type="button" onclick="this.parentElement.remove()" class="text-red-500 font-black px-4 hover:text-red-400 transition">×</button>`;
|
|
|
document.getElementById(contId).appendChild(div);
|
|
|
}
|
|
|
|
|
|
@@ -318,35 +404,52 @@ function switchTab(tab) {
|
|
|
}
|
|
|
|
|
|
function filterZones() {
|
|
|
- let input = document.getElementById('zoneSearch').value.toLowerCase(), items = document.querySelectorAll('.zone-item'), count = 0;
|
|
|
+ let input = document.getElementById('zoneSearch').value.toLowerCase();
|
|
|
+ let items = document.querySelectorAll('.zone-item');
|
|
|
+ let count = 0;
|
|
|
items.forEach(item => {
|
|
|
- let domain = item.getAttribute('data-domain'), owner = item.getAttribute('data-owner') || '', category = item.getAttribute('data-category'), match = (domain.includes(input) || owner.includes(input)) && category === activeTab;
|
|
|
- item.classList.toggle('hidden', !match); if(match) count++;
|
|
|
+ let domain = item.getAttribute('data-domain') || '';
|
|
|
+ let owner = item.getAttribute('data-owner') || '';
|
|
|
+ let category = item.getAttribute('data-category');
|
|
|
+ let match = (domain.includes(input) || owner.includes(input)) && category === activeTab;
|
|
|
+ item.classList.toggle('hidden', !match);
|
|
|
+ if(match) count++;
|
|
|
});
|
|
|
- const msg = document.getElementById('noZonesMsg'); if(msg) msg.classList.toggle('hidden', count > 0);
|
|
|
+ const msg = document.getElementById('noZonesMsg');
|
|
|
+ if(msg) msg.classList.toggle('hidden', count > 0);
|
|
|
}
|
|
|
|
|
|
function updateLiveStatus() {
|
|
|
fetch('/api/service/status').then(r => r.json()).then(data => {
|
|
|
const dot = document.getElementById('status-dot'), text = document.getElementById('status-text');
|
|
|
- if(data.status === 'active') {
|
|
|
- dot.className = "w-2.5 h-2.5 rounded-full bg-green-500 shadow-[0_0_10px_rgba(34,197,94,0.8)] animate-pulse";
|
|
|
- text.innerHTML = `Service: Active <span class="text-gray-600 ml-2 font-normal italic lowercase">(since ${data.uptime})</span>`;
|
|
|
- text.classList.replace('text-gray-500', 'text-green-400');
|
|
|
- } else {
|
|
|
- dot.className = "w-2.5 h-2.5 rounded-full bg-red-500 shadow-[0_0_10px_rgba(239,68,68,0.8)]";
|
|
|
- text.innerText = "Service: Offline";
|
|
|
- text.classList.replace('text-green-400', 'text-red-500');
|
|
|
+ if(dot && text) {
|
|
|
+ if(data.status === 'active') {
|
|
|
+ dot.className = "w-2.5 h-2.5 rounded-full bg-green-500 shadow-[0_0_10px_rgba(34,197,94,0.8)] animate-pulse";
|
|
|
+ text.innerHTML = `Service: Active <span class="text-gray-600 ml-2 font-normal italic lowercase">(since ${data.uptime})</span>`;
|
|
|
+ text.classList.replace('text-gray-500', 'text-green-400');
|
|
|
+ text.classList.replace('text-red-500', 'text-green-400'); // Ασφαλής αλλαγή
|
|
|
+ } else {
|
|
|
+ dot.className = "w-2.5 h-2.5 rounded-full bg-red-500 shadow-[0_0_10px_rgba(239,68,68,0.8)]";
|
|
|
+ text.innerText = "Service: Offline";
|
|
|
+ text.classList.replace('text-green-400', 'text-red-500');
|
|
|
+ text.classList.replace('text-gray-500', 'text-red-500');
|
|
|
+ }
|
|
|
}
|
|
|
- });
|
|
|
+ }).catch(e => console.log('Status check error:', e)); // Προστέθηκε catch
|
|
|
}
|
|
|
|
|
|
function restoreDashboardState() { switchTab(activeTab); updateLiveStatus(); }
|
|
|
|
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
|
- renderLiveCharts();
|
|
|
- setInterval(renderLiveCharts, 10000);
|
|
|
- setInterval(updateLiveStatus, 5000);
|
|
|
+ // Μόνο οι Admin βλέπουν τα charts
|
|
|
+ if(document.getElementById('rpsChart')) {
|
|
|
+ renderLiveCharts();
|
|
|
+ setInterval(renderLiveCharts, 10000);
|
|
|
+ }
|
|
|
+ // Ο έλεγχος του service τρέχει μόνο αν υπάρχει το dot
|
|
|
+ if(document.getElementById('status-dot')) {
|
|
|
+ setInterval(updateLiveStatus, 5000);
|
|
|
+ }
|
|
|
restoreDashboardState();
|
|
|
});
|
|
|
</script>
|