404 lines
11 KiB
JavaScript
404 lines
11 KiB
JavaScript
window.resources = []
|
|
|
|
function normalizeCostValue(value){
|
|
if(!value) return ""
|
|
|
|
return String(value).replace(",", ".")
|
|
}
|
|
|
|
function formatCostValue(value){
|
|
if(!Number.isFinite(value)) return ""
|
|
|
|
return value.toFixed(2)
|
|
}
|
|
|
|
function setupResourceCostSync(){
|
|
const monthInput = document.getElementById("kosten_monat")
|
|
const yearInput = document.getElementById("kosten_jahr")
|
|
|
|
if(!monthInput || !yearInput) return
|
|
|
|
monthInput.addEventListener("input", () => {
|
|
const month = parseFloat(normalizeCostValue(monthInput.value))
|
|
|
|
if(Number.isFinite(month)){
|
|
yearInput.value = formatCostValue(month * 12)
|
|
}
|
|
})
|
|
|
|
yearInput.addEventListener("input", () => {
|
|
const year = parseFloat(normalizeCostValue(yearInput.value))
|
|
|
|
if(Number.isFinite(year)){
|
|
monthInput.value = formatCostValue(year / 12)
|
|
}
|
|
})
|
|
}
|
|
|
|
document.addEventListener("DOMContentLoaded", setupResourceCostSync)
|
|
|
|
window.loadResources = async function(){
|
|
|
|
window.resources = await api(API + "/resources/active")
|
|
const resources = window.resources
|
|
|
|
const mappings = await api(API + "/domainmap")
|
|
|
|
const table = document.getElementById("resources")
|
|
table.innerHTML = ""
|
|
|
|
resources.forEach(r => {
|
|
|
|
let ips = ""
|
|
|
|
if(Array.isArray(r.ips)){
|
|
ips = r.ips.map(ip => {
|
|
const cls = (ip.type || "").toLowerCase().includes("public")
|
|
? "ip public"
|
|
: "ip"
|
|
|
|
return "<span class='" + cls + "'>" +
|
|
(ip.type || "") + " " + ip.ip +
|
|
"</span>"
|
|
|
|
}).join("")
|
|
|
|
}
|
|
|
|
let domains = mappings
|
|
.filter(m => m.resource_id == r.id)
|
|
.map(m => "<span class='ip'>🌐 " + m.domain_name + "</span>")
|
|
.join("")
|
|
|
|
const tr = document.createElement("tr")
|
|
|
|
tr.innerHTML = `
|
|
<td>
|
|
<button class="btn-small" onclick="moveResource(${r.id}, 'up')">⬆</button>
|
|
<button class="btn-small" onclick="moveResource(${r.id}, 'down')">⬇</button>
|
|
</td>
|
|
<td>
|
|
<span style="cursor:pointer;color:#1e3a8a;font-weight:bold"
|
|
onclick='openServerDetail(${JSON.stringify(r)})'>
|
|
${r.name}
|
|
</span>
|
|
</td>
|
|
|
|
<td>${r.produkt || ""}</td>
|
|
<td><span class="provider">${r.provider || ""}</span></td>
|
|
|
|
<td id="status-${r.id}">...</td>
|
|
|
|
<td>
|
|
<div class="system">
|
|
${r.cpu ? r.cpu + " CPU" : ""}
|
|
${r.ram ? " • " + r.ram + " RAM" : ""}
|
|
${r.disk ? " • " + r.disk : ""}
|
|
${r.os ? " • " + r.os : ""}
|
|
</div>
|
|
</td>
|
|
|
|
<td>${domains}</td>
|
|
|
|
<td>
|
|
${ips}
|
|
<br>
|
|
<button class="btn-small btn-secondary" type="button" onclick="openIPManager(${r.id})">IPs</button>
|
|
</td>
|
|
|
|
<td>
|
|
<button class="btn-small" onclick='openEdit(${JSON.stringify(r)})'>Bearbeiten</button>
|
|
<button class="btn-small btn-danger" onclick="deleteResource(${r.id})">Loeschen</button>
|
|
</td>
|
|
`
|
|
|
|
table.appendChild(tr)
|
|
|
|
checkServerStatus(r)
|
|
})
|
|
}
|
|
|
|
window.saveResource = async function(){
|
|
|
|
const id = document.getElementById("resource_id").value
|
|
const field = id => document.getElementById(id).value
|
|
|
|
let kostenMonat=normalizeCostValue(document.getElementById("kosten_monat").value)
|
|
let kostenJahr=normalizeCostValue(document.getElementById("kosten_jahr").value)
|
|
|
|
if(kostenMonat && !kostenJahr){
|
|
kostenJahr = formatCostValue(parseFloat(kostenMonat) * 12)
|
|
}
|
|
|
|
if(kostenJahr && !kostenMonat){
|
|
kostenMonat = formatCostValue(parseFloat(kostenJahr) / 12)
|
|
}
|
|
|
|
const data={
|
|
name: field("name"),
|
|
produkt: field("produkt"),
|
|
provider: field("provider"),
|
|
art: field("art"),
|
|
cpu: field("cpu"),
|
|
ram: field("ram"),
|
|
disk: field("disk"),
|
|
os: field("os"),
|
|
kosten_monat:kostenMonat || null,
|
|
kosten_jahr:kostenJahr || null,
|
|
providername: field("providername"),
|
|
ipv6_net: field("ipv6_net"),
|
|
bestelldatum: field("bestelldatum") || null,
|
|
kuendbar_ab: field("kuendbar_ab") || null,
|
|
kuendigungsdatum: field("kuendigungsdatum") || null,
|
|
status: field("status"),
|
|
bemerkung: field("bemerkung")
|
|
}
|
|
|
|
await runAction(
|
|
async () => {
|
|
if(id){
|
|
await api(API+"/resources/"+id,{method:"PUT",headers:{'Content-Type':'application/json'},body:JSON.stringify(data)})
|
|
}else{
|
|
await api(API+"/resources",{method:"POST",headers:{'Content-Type':'application/json'},body:JSON.stringify(data)})
|
|
}
|
|
},
|
|
async () => {
|
|
closeModal()
|
|
await loadResources()
|
|
loadInfrastructure()
|
|
loadCosts()
|
|
}
|
|
)
|
|
}
|
|
|
|
window.deleteResource = async function(id){
|
|
if(!confirm("delete resource?")) return
|
|
await runAction(
|
|
async () => {
|
|
await api(API+"/resources/"+id,{method:"DELETE"})
|
|
},
|
|
async () => {
|
|
await loadResources()
|
|
loadInfrastructure()
|
|
loadCosts()
|
|
}
|
|
)
|
|
}
|
|
|
|
window.checkServerStatus = async function(resource){
|
|
|
|
if(!Array.isArray(resource.ips)) return
|
|
|
|
let ip = resource.ips.find(i => i.type?.includes("public")) || resource.ips[0]
|
|
if(!ip) return
|
|
|
|
try{
|
|
const res = await fetch(API+"/ping/"+ip.ip)
|
|
const data = await res.json()
|
|
|
|
const el = document.getElementById("status-"+resource.id)
|
|
|
|
if(el){
|
|
el.innerHTML = data.status === "online" ? "🟢" : "🔴"
|
|
}
|
|
|
|
}catch(e){
|
|
console.log(e)
|
|
}
|
|
}
|
|
|
|
window.loadCancelled = async function(){
|
|
|
|
const data = await api(API + "/resources/cancelled")
|
|
|
|
const table = document.getElementById("cancelled")
|
|
table.innerHTML = ""
|
|
|
|
data.forEach(r => {
|
|
|
|
const tr = document.createElement("tr")
|
|
|
|
tr.innerHTML = `
|
|
<td>${r.name}</td>
|
|
<td>${r.produkt || ""}</td>
|
|
<td>${r.provider || ""}</td>
|
|
<td>${r.cpu || ""}</td>
|
|
<td>${r.ram || ""}</td>
|
|
<td>${r.disk || ""}</td>
|
|
<td>${r.status || ""}</td>
|
|
`
|
|
|
|
table.appendChild(tr)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
window.saveIP = async function(){
|
|
|
|
const resourceId = document.getElementById("ip_resource_id").value
|
|
|
|
const ipField = document.getElementById("new_ip")
|
|
const id = ipField.dataset.editId
|
|
|
|
const ip = ipField.value
|
|
const type = document.getElementById("new_type").value
|
|
const comment = document.getElementById("new_comment").value
|
|
|
|
await runAction(
|
|
async () => {
|
|
if(id){
|
|
await api(API + "/ips/" + id, {
|
|
method: "PUT",
|
|
headers: {'Content-Type':'application/json'},
|
|
body: JSON.stringify({ip, type, comment})
|
|
})
|
|
|
|
delete ipField.dataset.editId
|
|
|
|
}else{
|
|
try{
|
|
const result = await api(API + "/ipcheck/" + ip, {}, { showError: false })
|
|
|
|
if(result.length){
|
|
if(!confirm("IP existiert bereits. Trotzdem speichern?")){
|
|
const error = new Error("IP save cancelled")
|
|
error.silent = true
|
|
throw error
|
|
}
|
|
}
|
|
}catch(e){
|
|
if(e.silent) throw e
|
|
console.warn("IP check fehlgeschlagen", e)
|
|
}
|
|
|
|
await api(API + "/resources/" + resourceId + "/ips", {
|
|
method: "POST",
|
|
headers: {'Content-Type':'application/json'},
|
|
body: JSON.stringify({ip, type, comment})
|
|
})
|
|
}
|
|
},
|
|
async () => {
|
|
ipField.value = ""
|
|
document.getElementById("new_type").value = ""
|
|
document.getElementById("new_comment").value = ""
|
|
|
|
await loadIPs(resourceId)
|
|
await loadResources()
|
|
loadInfrastructure()
|
|
cancelIPEdit()
|
|
}
|
|
)
|
|
}
|
|
|
|
|
|
window.deleteIP = async function(id, resourceId){
|
|
|
|
if(!confirm("IP löschen?")) return
|
|
|
|
await runAction(
|
|
async () => {
|
|
await api(API + "/ips/" + id, {
|
|
method: "DELETE"
|
|
})
|
|
},
|
|
async () => {
|
|
await loadIPs(resourceId)
|
|
await loadResources()
|
|
loadInfrastructure()
|
|
}
|
|
)
|
|
|
|
}
|
|
|
|
window.loadIPs = async function(resourceId){
|
|
|
|
const ips = await api(API + "/resources/" + resourceId + "/ips")
|
|
|
|
const container = document.getElementById("ipList")
|
|
container.innerHTML = ""
|
|
|
|
ips.forEach(ip => {
|
|
|
|
// ✅ HIER rein!
|
|
const cls = (ip.type || "").toLowerCase().includes("public")
|
|
? "ip public"
|
|
: "ip"
|
|
|
|
const div = document.createElement("div")
|
|
|
|
div.innerHTML = `
|
|
<span class="${cls}">
|
|
${ip.ip} (${ip.type || ""}) ${ip.comment || ""}
|
|
</span>
|
|
|
|
<button onclick="editIP(${ip.id}, '${ip.ip}', '${ip.type || ""}', '${ip.comment || ""}')">
|
|
Bearbeiten
|
|
</button>
|
|
|
|
<button class="btn-danger" onclick="deleteIP(${ip.id}, ${resourceId})">
|
|
Loeschen
|
|
</button>
|
|
`
|
|
|
|
container.appendChild(div)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
window.editIP = function(id, ip, type, comment){
|
|
|
|
document.getElementById("new_ip").value = ip
|
|
document.getElementById("new_type").value = type
|
|
document.getElementById("new_comment").value = comment
|
|
|
|
document.getElementById("new_ip").dataset.editId = id
|
|
|
|
// 👇 UX Highlight + Fokus
|
|
document.getElementById("ipForm").classList.add("edit-mode")
|
|
document.getElementById("ipSaveBtn").innerText = "Update"
|
|
document.getElementById("ipCancelBtn").style.display = "inline-block"
|
|
|
|
document.getElementById("new_ip").focus() // 👈 HIER
|
|
}
|
|
|
|
|
|
|
|
|
|
window.cancelIPEdit = function(){
|
|
|
|
const ipField = document.getElementById("new_ip")
|
|
|
|
delete ipField.dataset.editId
|
|
|
|
ipField.value = ""
|
|
document.getElementById("new_type").value = ""
|
|
document.getElementById("new_comment").value = ""
|
|
|
|
document.getElementById("ipForm").classList.remove("edit-mode")
|
|
document.getElementById("ipSaveBtn").innerText = "Add"
|
|
document.getElementById("ipCancelBtn").style.display = "none"
|
|
|
|
}
|
|
|
|
window.moveResource = async function(id, direction){
|
|
await runAction(
|
|
async () => {
|
|
await api(API + "/resources/" + id + "/move", {
|
|
method: "POST",
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ direction })
|
|
})
|
|
},
|
|
async () => {
|
|
await loadResources()
|
|
loadInfrastructure()
|
|
loadCosts()
|
|
}
|
|
)
|
|
}
|