feature: DNS cache + subdomain CRUD + domains cleanup

This commit is contained in:
ecki
2026-03-27 11:31:42 +01:00
parent 49ae3a5c29
commit 0329c67904
4 changed files with 372 additions and 228 deletions
+24
View File
@@ -98,3 +98,27 @@ margin-left:15px;
font-size:13px;
color:#555;
}
.subdomain-detail{
margin-left:15px;
font-size:13px;
color:#555;
}
.infra-server{
font-weight:bold;
margin-top:10px;
}
.infra-ip{
margin-left:10px;
}
.infra-domain{
margin-left:20px;
}
.infra-sub{
margin-left:30px;
color:#555;
}
+272 -218
View File
@@ -1,257 +1,283 @@
async function loadDomains(){
/* =========================
DNS CACHE (mit TTL)
========================= */
const domains = await api(API + "/domains")
const subs = await api(API + "/subdomains")
const dnsCache = {}
async function getDNS(domain){
const table = document.getElementById("domains")
table.innerHTML = ""
const now = Date.now()
const TTL = 30000 // 30 Sekunden
for(const d of domains){
if(dnsCache[domain] && (now - dnsCache[domain].time < TTL)){
return dnsCache[domain].ips
}
const tr = document.createElement("tr")
try{
const sublist = subs
.filter(s => s.domain_id === d.id)
.map(s => `
<div class="subdomain">
const res = await api(API + "/dns/" + domain)
const ips = res.ips || []
${s.subdomain}.${s.domain_name}
<span id="subdns-${s.id}">...</span>
<span id="subserver-${s.id}" class="serverlink"></span>
<button onclick='openSubEdit(${JSON.stringify(s)})'>Edit</button>
<button onclick="deleteSub(${s.id})">Delete</button>
dnsCache[domain] = {
ips: ips,
time: now
}
</div>
`)
return ips
.join("");
}catch(e){
dnsCache[domain] = {
ips: [],
time: now
}
tr.innerHTML = `
<td>
${d.domain_name}
${sublist}
</td>
return []
<td><span class="provider">${d.provider || ""}</span></td>
<td>${d.ip_address || ""}</td>
<td>${d.resource_name || ""}</td>
<td id="dns-${d.id}">...</td>
<td>${d.yearly_cost || ""}</td>
<td>
<button onclick='openDomainEdit(${JSON.stringify(d)})'>Edit</button>
<button onclick="deleteDomain(${d.id})">Delete</button>
<button onclick="openSubCreate(${d.id},'${d.domain_name}')">
+ Subdomain
</button>
</td>
`;
table.appendChild(tr)
const resources = window.resources || []
subs.forEach(s => {
const server = resources.find(r =>
Array.isArray(r.ips) &&
r.ips.some(ip => ip.ip === s.ip_address)
)
if(server){
const el = document.getElementById("subserver-"+s.id)
if(el){
el.innerText = " → " + server.name
}
}
})
subs
.filter(s => s.domain_id === d.id)
.forEach(async s => {
const full = s.subdomain + "." + s.domain_name
try{
const dns = await api(API + "/dns/" + full)
let status="❌"
if(dns.ips && dns.ips.includes(s.ip_address)){
status="✅" + dns.ips.join(", ")
}else if(dns.ips.length){
status="⚠" + dns.ips.join(", ")
}
document.getElementById("subdns-"+s.id).innerHTML=status
}catch(e){
document.getElementById("subdns-"+s.id).innerHTML="❌"
}
})
try{
const dnsRes = await fetch(API + "/dns/" + d.domain_name)
const result = await dnsRes.json()
let status = "❌"
if(result.ips && result.ips.length){
const ipList = result.ips.join("<br>")
if(d.ip_address && result.ips.includes(d.ip_address)){
status = "✅ " + ipList
}else{
status = "⚠ " + ipList
}
}
document.getElementById("dns-"+d.id).innerHTML = status
}catch(e){
document.getElementById("dns-"+d.id).innerHTML = "❌"
}
}
}
}
/* =========================
DOMAINS + SUBDOMAINS
========================= */
async function deleteDomain(id){
window.loadDomains = async function(){
if(!confirm("Domain löschen?")) return;
const domains = await api(API + "/domains")
const subs = await api(API + "/subdomains")
const table = document.getElementById("domains")
table.innerHTML = ""
for(const d of domains){
const tr = document.createElement("tr")
// Subdomains Liste
const sublist = subs
.filter(s => s.domain_id === d.id)
.map(s => `
<div class="subdomain">
${s.subdomain}.${s.domain_name}
<span id="subdns-${s.id}">...</span>
<span id="subserver-${s.id}" class="serverlink"></span>
<button onclick='openSubEdit(${JSON.stringify(s)})'>Edit</button>
<button onclick="deleteSub(${s.id})">Delete</button>
</div>
`).join("")
tr.innerHTML = `
<td>
${d.domain_name}
${sublist}
</td>
<td><span class="provider">${d.provider || ""}</span></td>
<td>${d.ip_address || ""}</td>
<td>${d.resource_name || ""}</td>
<td id="dns-${d.id}">...</td>
<td>${d.yearly_cost || ""}</td>
<td>
<button onclick='openDomainEdit(${JSON.stringify(d)})'>Edit</button>
<button onclick="deleteDomain(${d.id})">Delete</button>
<button onclick="openSubCreate(${d.id})">+ Subdomain</button>
</td>
`
table.appendChild(tr)
/* =========================
SERVER ZUORDNUNG SUBS
========================= */
const resources = window.resources || []
subs.forEach(s => {
const server = resources.find(r =>
Array.isArray(r.ips) &&
r.ips.some(ip => ip.ip === s.ip_address)
)
if(server){
const el = document.getElementById("subserver-"+s.id)
if(el){
el.innerText = " → " + server.name
}
}
})
/* =========================
DNS CHECK DOMAIN
========================= */
try{
const ips = await getDNS(d.domain_name)
let status = "❌"
if(ips.length){
if(d.ip_address && ips.includes(d.ip_address)){
status = "✅ " + ips.join(", ")
}else{
status = "⚠ " + ips.join(", ")
}
}
document.getElementById("dns-"+d.id).innerHTML = status
}catch(e){
document.getElementById("dns-"+d.id).innerHTML = "❌"
}
/* =========================
DNS CHECK SUBDOMAINS
========================= */
subs
.filter(s => s.domain_id === d.id)
.forEach(async s => {
const full = s.subdomain + "." + s.domain_name
try{
const ips = await getDNS(full)
let status = "❌"
if(ips.length){
if(s.ip_address && ips.includes(s.ip_address)){
status = "✅ " + ips.join(", ")
}else{
status = "⚠ " + ips.join(", ")
}
}
document.getElementById("subdns-"+s.id).innerHTML = status
}catch(e){
document.getElementById("subdns-"+s.id).innerHTML = "❌"
}
})
}
}
/* =========================
DOMAIN CRUD
========================= */
window.saveDomain = async function(){
const id = document.getElementById("domain_id").value
let cost = document.getElementById("domain_cost").value
if(cost) cost = cost.replace(",", ".")
const data = {
domain_name: domain_name.value,
provider: domain_provider.value,
ip_address: domain_ip.value,
yearly_cost: cost || null,
notes: domain_notes.value
}
if(id){
await api(API+"/domains/"+id,{
method:"PUT",
headers:{'Content-Type':'application/json'},
body:JSON.stringify(data)
})
}else{
await api(API+"/domains",{
method:"POST",
headers:{'Content-Type':'application/json'},
body:JSON.stringify(data)
})
}
closeDomainModal()
loadDomains()
loadMapping()
loadCosts()
}
window.deleteDomain = async function(id){
if(!confirm("Domain löschen?")) return
await api(API + "/domains/" + id, {
method: "DELETE"
});
})
loadDomains();
loadDomains()
}
async function saveDomain(){
const id=document.getElementById("domain_id").value
let cost=document.getElementById("domain_cost").value
if(cost){
cost=cost.replace(",",".")
}
const data={
domain_name:domain_name.value,
provider:domain_provider.value,
ip_address:domain_ip.value,
yearly_cost:cost || null,
notes:domain_notes.value
}
if(id){
await api(API+"/domains/"+id,{
method:"PUT",
headers:{'Content-Type':'application/json'},
body:JSON.stringify(data)
})
}else{
await api(API+"/domains",{
method:"POST",
headers:{'Content-Type':'application/json'},
body:JSON.stringify(data)
})
}
closeDomainModal()
loadDomains()
loadMapping()
loadCosts()
}
async function loadMapping(){
const data = await api(API + "/domainmap");
const table = document.getElementById("mapping");
table.innerHTML = "";
data.forEach(m => {
const tr = document.createElement("tr");
tr.innerHTML = `
<td>${m.domain_name}</td>
<td>${m.ip_address}</td>
<td>${m.server_name || ""}</td>
`;
table.appendChild(tr);
});
}
/* =========================
SUBDOMAIN CRUD
========================= */
window.saveSubdomain = async function(){
const id = document.getElementById("sub_id").value
const domain_id = document.getElementById("sub_domain_id").value
const subdomain = document.getElementById("sub_name").value
const ip_address = document.getElementById("sub_ip").value
const id = document.getElementById("sub_id").value
const domain_id = document.getElementById("sub_domain_id").value
const subdomain = document.getElementById("sub_name").value
const ip_address = document.getElementById("sub_ip").value
const data = {
domain_id,
subdomain,
ip_address
}
const data = { domain_id, subdomain, ip_address }
if(id){
if(id){
await api(API+"/subdomains/"+id,{
method:"PUT",
headers:{'Content-Type':'application/json'},
body:JSON.stringify(data)
})
await api(API+"/subdomains/"+id,{
method:"PUT",
headers:{'Content-Type':'application/json'},
body:JSON.stringify(data)
})
}else{
}else{
await api(API+"/subdomains",{
method:"POST",
headers:{'Content-Type':'application/json'},
body:JSON.stringify(data)
})
await api(API+"/subdomains",{
method:"POST",
headers:{'Content-Type':'application/json'},
body:JSON.stringify(data)
})
}
closeSubModal()
loadDomains()
}
closeSubModal()
loadDomains()
}
window.deleteSub = async function(id){
@@ -264,3 +290,31 @@ window.deleteSub = async function(id){
loadDomains()
}
/* =========================
DOMAIN MAPPING
========================= */
window.loadMapping = async function(){
const data = await api(API + "/domainmap")
const table = document.getElementById("mapping")
table.innerHTML = ""
data.forEach(m => {
const tr = document.createElement("tr")
tr.innerHTML = `
<td>${m.domain_name}</td>
<td>${m.ip_address}</td>
<td>${m.server_name || ""}</td>
`
table.appendChild(tr)
})
}
+9 -1
View File
@@ -1,8 +1,9 @@
document.addEventListener("DOMContentLoaded", async () => {
clearDNSCache()
await refreshAll()
setInterval(refreshAll, 30000)
setInterval(refreshAll, 60000)
})
@@ -31,3 +32,10 @@ async function refreshAll(){
refreshing = false
}
function clearDNSCache(){
for(const key in dnsCache){
delete dnsCache[key]
}
}
+67 -9
View File
@@ -57,25 +57,83 @@ document.getElementById("resourceModal").style.display="none"
}
window.openServerDetail = async function(resource){
const mappings = await api(API+"/domainmap")
const mappings = await api(API+"/domainmap")
const subs = await api(API+"/subdomains")
let html = `<b>${resource.name}</b><br><br>`
let html = `
<b>Name:</b> ${resource.name}<br>
<b>Produkt:</b> ${resource.produkt || ""}<br>
<b>Provider:</b> ${resource.provider || ""}<br>
<b>Provider Name:</b> ${resource.providername || ""}
const domains = mappings.filter(m => m.resource_id == resource.id)
<br><br>
domains.forEach(d=>{
html += `<div class="ip">🌐 ${d.domain_name}</div>`
})
<b>System</b><br>
CPU: ${resource.cpu || ""}<br>
RAM: ${resource.ram || ""}<br>
Disk: ${resource.disk || ""}<br>
OS: ${resource.os || ""}
document.getElementById("serverDetailContent").innerHTML = html
document.getElementById("serverDetailModal").style.display="block"
<br><br>
<b>IPs</b><br>
`
// IPs
if(Array.isArray(resource.ips)){
resource.ips.forEach(ip=>{
html += `<div class="ip">${ip.type || ""} ${ip.ip}</div>`
})
}
// Domains
html += `<br><b>Domains</b><br>`
const domains = mappings.filter(m => m.resource_id == resource.id)
domains.forEach(d=>{
html += `<div class="ip">🌐 ${d.domain_name}</div>`
// Subdomains dazu
subs.forEach(s => {
if(s.domain_name === d.domain_name){
html += `
<div class="subdomain-detail">
${s.subdomain}.${s.domain_name}
</div>
`
}
})
})
// Zusatzinfos
html += `
<br>
<b>Bestelldatum:</b> ${resource.bestelldatum || ""}<br>
<b>Kündbar ab:</b> ${resource.kuendbar_ab || ""}
<br><br>
<b>Bemerkung</b><br>
${resource.bemerkung || ""}
`
document.getElementById("serverDetailContent").innerHTML = html
document.getElementById("serverDetailModal").style.display="block"
}
window.closeServerDetail = function(){
document.getElementById("serverDetailModal").style.display="none"