diff --git a/backend/public/index.html b/backend/public/index.html
index cf599e8..7416234 100644
--- a/backend/public/index.html
+++ b/backend/public/index.html
@@ -210,27 +210,6 @@ Domains jährlich: 0 €
-
@@ -322,8 +301,10 @@ Domains jährlich:
0 €
-
+
+
+
+
diff --git a/backend/public/js/costs.js b/backend/public/js/costs.js
new file mode 100644
index 0000000..0790424
--- /dev/null
+++ b/backend/public/js/costs.js
@@ -0,0 +1,38 @@
+window.loadCosts = async function(){
+
+ const resources = window.resources || []
+ const domains = await api(API + "/domains")
+
+ let month = 0
+ let year = 0
+ let domainYear = 0
+
+ resources.forEach(r => {
+
+ if(r.kosten_monat){
+ month += parseFloat(r.kosten_monat)
+ }
+
+ if(r.kosten_jahr){
+ year += parseFloat(r.kosten_jahr)
+ }
+
+ })
+
+ domains.forEach(d => {
+
+ if(d.yearly_cost){
+ domainYear += parseFloat(d.yearly_cost)
+ }
+
+ })
+
+ document.getElementById("costMonth").innerText = month.toFixed(2)
+ document.getElementById("costYear").innerText = year.toFixed(2)
+ document.getElementById("costDomain").innerText = domainYear.toFixed(2)
+
+ const total = year + domainYear
+
+ document.getElementById("costTotal").innerText = total.toFixed(2)
+
+}
diff --git a/backend/public/js/domains.js b/backend/public/js/domains.js
index 62ab5a3..9840344 100644
--- a/backend/public/js/domains.js
+++ b/backend/public/js/domains.js
@@ -215,45 +215,3 @@ async function loadMapping(){
}
-
-
-
-function openDomainCreate(){
-
-document.getElementById("domainModalTitle").innerText="Create Domain"
-
-document.getElementById("domain_id").value=""
-
-document.getElementById("domain_name").value=""
-document.getElementById("domain_provider").value=""
-document.getElementById("domain_ip").value=""
-document.getElementById("domain_cost").value=""
-document.getElementById("domain_notes").value=""
-
-document.getElementById("domainModal").style.display="block"
-
-}
-
-function openDomainEdit(d){
-
-document.getElementById("domainModalTitle").innerText="Edit Domain"
-
-document.getElementById("domain_id").value=d.id
-
-document.getElementById("domain_name").value=d.domain_name||""
-document.getElementById("domain_provider").value=d.provider||""
-document.getElementById("domain_ip").value=d.ip_address||""
-document.getElementById("domain_cost").value=d.yearly_cost||""
-document.getElementById("domain_notes").value=d.notes||""
-
-document.getElementById("domainModal").style.display="block"
-
-}
-
-
-
-function closeDomainModal(){
-
-document.getElementById("domainModal").style.display="none"
-
-}
diff --git a/backend/public/js/infra.js b/backend/public/js/infra.js
new file mode 100644
index 0000000..0aa0f6b
--- /dev/null
+++ b/backend/public/js/infra.js
@@ -0,0 +1,52 @@
+window.loadInfrastructure = async function(){
+
+ const resources = window.resources || []
+ const mappings = await api(API + "/domainmap")
+ const subs = await api(API + "/subdomains")
+
+ const container = document.getElementById("infraView")
+ container.innerHTML = ""
+
+ resources.forEach(r => {
+
+ let html = `
${r.name}
`
+
+ if(Array.isArray(r.ips)){
+
+ r.ips.forEach(ip => {
+
+ html += `
└ ${ip.ip}
`
+
+ const domains = mappings.filter(m =>
+ m.resource_id == r.id && m.ip_address == ip.ip
+ )
+
+ domains.forEach(d => {
+
+ html += `
└ 🌐 ${d.domain_name}
`
+
+ const sublist = subs.filter(s =>
+ s.domain_id == d.domain_id || s.domain_name == d.domain_name
+ )
+
+ sublist.forEach(s => {
+
+ if(s.ip_address == ip.ip){
+
+ html += `
└ ${s.subdomain}.${s.domain_name}
`
+
+ }
+
+ })
+
+ })
+
+ })
+
+ }
+
+ container.innerHTML += html
+
+ })
+
+}
diff --git a/backend/public/js/main.js b/backend/public/js/main.js
index 17d9c74..07b72bf 100644
--- a/backend/public/js/main.js
+++ b/backend/public/js/main.js
@@ -1,27 +1,11 @@
-document.addEventListener("DOMContentLoaded",()=>{
+document.addEventListener("DOMContentLoaded", async () => {
-loadResources()
-loadDomains()
-loadMapping()
-loadCosts()
-loadInfrastructure()
-loadCancelled();
-setInterval(()=>{
+ await loadResources()
+ await loadDomains()
+ await loadMapping()
+ await loadCancelled()
-document.querySelectorAll("[id^='status-']").forEach(el=>{
-
-const id = el.id.replace("status-","")
-
-const resource = window.resources.find(r=>r.id==id)
-
-if(resource){
-checkServerStatus(resource)
-}
+ loadInfrastructure()
+ loadCosts()
})
-
-},30000)
-
-
-});
-
diff --git a/backend/public/js/main.saV b/backend/public/js/main.saV
new file mode 100644
index 0000000..67fd18f
--- /dev/null
+++ b/backend/public/js/main.saV
@@ -0,0 +1,27 @@
+document.addEventListener("DOMContentLoaded",()=>{
+
+loadResources()
+loadDomains()
+loadMapping()
+loadCosts()
+loadInfrastructure()
+loadCancelled();
+setInterval(()=>{
+
+document.querySelectorAll("[id^='status-']").forEach(el=>{
+
+const id = el.id.replace("status-","")
+
+const resource = window.resources.find(r=>r.id==id)
+
+if(resource){
+checkServerStatus(resource)
+}
+
+})
+
+},30000)
+
+
+});
+
diff --git a/backend/public/js/modals.js b/backend/public/js/modals.js
new file mode 100644
index 0000000..43d1990
--- /dev/null
+++ b/backend/public/js/modals.js
@@ -0,0 +1,171 @@
+/* =========================
+ RESOURCE MODAL
+========================= */
+
+window.openCreate = function(){
+
+document.getElementById("modalTitle").innerText="Create Resource"
+
+document.getElementById("resource_id").value=""
+
+document.getElementById("name").value=""
+document.getElementById("produkt").value=""
+document.getElementById("provider").value=""
+document.getElementById("art").value=""
+document.getElementById("cpu").value=""
+document.getElementById("ram").value=""
+document.getElementById("disk").value=""
+document.getElementById("os").value=""
+
+document.getElementById("kosten_monat").value=""
+document.getElementById("kosten_jahr").value=""
+
+document.getElementById("providername").value=""
+document.getElementById("ipv6_net").value=""
+
+document.getElementById("bestelldatum").value=""
+document.getElementById("kuendbar_ab").value=""
+document.getElementById("kuendigungsdatum").value=""
+
+document.getElementById("status").value="aktiv"
+document.getElementById("bemerkung").value=""
+
+document.getElementById("resourceModal").style.display="block"
+
+}
+
+
+window.openEdit = function(resource){
+
+document.getElementById("modalTitle").innerText="Edit Resource"
+
+document.getElementById("resource_id").value=resource.id
+
+Object.keys(resource).forEach(k=>{
+const el=document.getElementById(k)
+if(el) el.value=resource[k] || ""
+})
+
+document.getElementById("resourceModal").style.display="block"
+
+}
+
+
+window.closeModal = function(){
+
+document.getElementById("resourceModal").style.display="none"
+
+}
+
+
+window.openServerDetail = async function(resource){
+
+const mappings = await api(API+"/domainmap")
+
+let html = `
${resource.name}`
+
+const domains = mappings.filter(m => m.resource_id == resource.id)
+
+domains.forEach(d=>{
+html += `
🌐 ${d.domain_name}
`
+})
+
+document.getElementById("serverDetailContent").innerHTML = html
+document.getElementById("serverDetailModal").style.display="block"
+
+}
+
+
+window.closeServerDetail = function(){
+
+document.getElementById("serverDetailModal").style.display="none"
+
+}
+
+
+/* =========================
+ DOMAIN MODAL
+========================= */
+
+window.openDomainCreate = function(){
+
+document.getElementById("domainModalTitle").innerText="Create Domain"
+
+document.getElementById("domain_id").value=""
+
+document.getElementById("domain_name").value=""
+document.getElementById("domain_provider").value=""
+document.getElementById("domain_ip").value=""
+document.getElementById("domain_cost").value=""
+document.getElementById("domain_notes").value=""
+
+document.getElementById("domainModal").style.display="block"
+
+}
+
+
+window.openDomainEdit = function(d){
+
+document.getElementById("domainModalTitle").innerText="Edit Domain"
+
+document.getElementById("domain_id").value=d.id
+
+document.getElementById("domain_name").value=d.domain_name || ""
+document.getElementById("domain_provider").value=d.provider || ""
+document.getElementById("domain_ip").value=d.ip_address || ""
+document.getElementById("domain_cost").value=d.yearly_cost || ""
+document.getElementById("domain_notes").value=d.notes || ""
+
+document.getElementById("domainModal").style.display="block"
+
+}
+
+
+window.closeDomainModal = function(){
+
+document.getElementById("domainModal").style.display="none"
+
+}
+
+
+/* =========================
+ SUBDOMAIN MODAL
+========================= */
+
+window.openSubCreate = function(domainId){
+
+document.getElementById("sub_domain_id").value=domainId
+
+document.getElementById("sub_name").value=""
+document.getElementById("sub_ip").value=""
+
+document.getElementById("subdomainModal").style.display="block"
+
+}
+
+
+window.closeSubModal = function(){
+
+document.getElementById("subdomainModal").style.display="none"
+
+}
+
+
+/* =========================
+ IP MODAL
+========================= */
+
+window.openIPManager = function(resourceId){
+
+document.getElementById("ip_resource_id").value=resourceId
+
+document.getElementById("ipModal").style.display="block"
+
+}
+
+
+window.closeIPModal = function(){
+
+document.getElementById("ipModal").style.display="none"
+
+}
diff --git a/backend/public/js/resources.js b/backend/public/js/resources.js
index eb987c7..f7d0cff 100644
--- a/backend/public/js/resources.js
+++ b/backend/public/js/resources.js
@@ -1,41 +1,43 @@
window.resources = []
-async function loadResources(){
+window.loadResources = async function(){
window.resources = await api(API + "/resources/active")
const resources = window.resources
- const mappings = await api(API + "/domainmap");
+ const mappings = await api(API + "/domainmap")
- const table = document.getElementById("resources");
- table.innerHTML = "";
+ const table = document.getElementById("resources")
+ table.innerHTML = ""
resources.forEach(r => {
- let ips = "";
+ let ips = ""
if(Array.isArray(r.ips)){
ips = r.ips.map(ip =>
"
" + (ip.type || "") + " " + ip.ip + ""
- ).join("");
+ ).join("")
}
let domains = mappings
.filter(m => m.resource_id == r.id)
.map(m => "
🌐 " + m.domain_name + "")
- .join("");
+ .join("")
- const tr = document.createElement("tr");
+ const tr = document.createElement("tr")
tr.innerHTML = `
-
- ${r.name}
-
+
+ ${r.name}
+
|
+
${r.produkt || ""} |
${r.provider || ""} |
+
... |
@@ -56,432 +58,108 @@ async function loadResources(){
|
-
-
-
-
-
+
+
|
- `;
+ `
- table.appendChild(tr);
- checkServerStatus(r);
-
- });
+ table.appendChild(tr)
+ checkServerStatus(r)
+ })
}
-async function saveResource(){
+window.saveResource = async function(){
-const id = document.getElementById("resource_id").value
+ const id = document.getElementById("resource_id").value
-let kostenMonat=document.getElementById("kosten_monat").value
-let kostenJahr=document.getElementById("kosten_jahr").value
+ let kostenMonat=document.getElementById("kosten_monat").value
+ let kostenJahr=document.getElementById("kosten_jahr").value
-if(kostenMonat) kostenMonat=kostenMonat.replace(",",".")
-if(kostenJahr) kostenJahr=kostenJahr.replace(",",".")
-
-
-const data={
-
-name:document.getElementById("name").value,
-produkt:document.getElementById("produkt").value,
-provider:document.getElementById("provider").value,
-art:document.getElementById("art").value,
-
-cpu:document.getElementById("cpu").value,
-ram:document.getElementById("ram").value,
-disk:document.getElementById("disk").value,
-os:document.getElementById("os").value,
-
-kosten_monat:kostenMonat || null,
-kosten_jahr:kostenJahr || null,
-
-providername:document.getElementById("providername").value,
-ipv6_net:document.getElementById("ipv6_net").value,
-
-bestelldatum:document.getElementById("bestelldatum").value || null,
-kuendbar_ab:document.getElementById("kuendbar_ab").value || null,
-kuendigungsdatum:document.getElementById("kuendigungsdatum").value || null,
-
-status:document.getElementById("status").value,
-bemerkung:document.getElementById("bemerkung").value
-
-}
-
-
-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)
-})
-
-}
-
-closeModal()
-
-loadResources()
-loadCancelled()
-loadCosts()
-loadInfrastructure()
-
-}
-
-async function deleteResource(id){
-
- if(!confirm("delete resource?")) return;
-
- await api(API + "/resources/" + id,{
- method:"DELETE"
- });
-
- loadResources();
- loadCosts();
-
-}
-
-
-async function checkServerStatus(resource){
-
- if(!Array.isArray(resource.ips)) return;
-
- // zuerst public IP suchen
- let ip = resource.ips.find(i =>
- i.type && i.type.toLowerCase().includes("public")
- );
-
- // wenn keine public IP existiert → erste nehmen
- if(!ip){
- ip = resource.ips[0];
+ if(kostenMonat) kostenMonat=kostenMonat.replace(",",".")
+ if(kostenJahr) kostenJahr=kostenJahr.replace(",",".")
+
+ const data={
+ name:name.value,
+ produkt:produkt.value,
+ provider:provider.value,
+ art:art.value,
+ cpu:cpu.value,
+ ram:ram.value,
+ disk:disk.value,
+ os:os.value,
+ kosten_monat:kostenMonat || null,
+ kosten_jahr:kostenJahr || null,
+ providername:providername.value,
+ ipv6_net:ipv6_net.value,
+ bestelldatum:bestelldatum.value || null,
+ kuendbar_ab:kuendbar_ab.value || null,
+ kuendigungsdatum:kuendigungsdatum.value || null,
+ status:status.value,
+ bemerkung:bemerkung.value
}
- if(!ip) return;
+ 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)})
+ }
+
+ closeModal()
+ loadResources()
+}
+
+window.deleteResource = async function(id){
+ if(!confirm("delete resource?")) return
+ await api(API+"/resources/"+id,{method:"DELETE"})
+ loadResources()
+}
+
+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 res = await fetch(API + "/ping/" + ip.ip);
- const data = await res.json();
-
- let icon = "🔴";
-
- if(data.status === "online"){
- icon = "🟢";
- }
-
- const el = document.getElementById("status-" + resource.id);
+ const el = document.getElementById("status-"+resource.id)
if(el){
- el.innerHTML = icon;
+ el.innerHTML = data.status === "online" ? "🟢" : "🔴"
}
}catch(e){
- console.log("Ping error", e);
+ console.log(e)
}
-
}
+window.loadCancelled = async function(){
-function openCreate(){
+ const data = await api(API + "/resources/cancelled")
-document.getElementById("modalTitle").innerText = "Create Resource"
-document.getElementById("resource_id").value = ""
+ const table = document.getElementById("cancelled")
+ table.innerHTML = ""
-document.querySelectorAll("#resourceModal input, #resourceModal textarea")
-.forEach(e=>e.value="")
+ data.forEach(r => {
-document.getElementById("status").value = "aktiv"
+ const tr = document.createElement("tr")
-document.getElementById("resourceModal").style.display = "block"
+ tr.innerHTML = `
+
${r.name} |
+
${r.produkt || ""} |
+
${r.provider || ""} |
+
${r.cpu || ""} |
+
${r.ram || ""} |
+
${r.disk || ""} |
+
${r.status || ""} |
+ `
+
+ table.appendChild(tr)
+
+ })
}
-
-
-function openEdit(resource){
-
-document.getElementById("modalTitle").innerText = "Edit Resource"
-
-document.getElementById("resource_id").value = resource.id
-
-Object.keys(resource).forEach(k=>{
-
-const el = document.getElementById(k)
-
-if(el) el.value = resource[k] || ""
-
-})
-
-document.getElementById("resourceModal").style.display = "block"
-
-}
-
-
-function closeModal(){
-
-document.getElementById("resourceModal").style.display="none"
-
-}
-
-
-
-async function openServerDetail(resource){
-
-const mappings = await api(API+"/domainmap")
-
-let html = `
-
-
Name: ${resource.name}
-
Produkt: ${resource.produkt || ""}
-
Provider: ${resource.provider || ""}
-
Provider Name: ${resource.providername || ""}
-
-
-
-
System
-CPU: ${resource.cpu || ""}
-RAM: ${resource.ram || ""}
-Disk: ${resource.disk || ""}
-OS: ${resource.os || ""}
-
-
-
-
IPs
-`
-
-if(Array.isArray(resource.ips)){
-
-resource.ips.forEach(ip=>{
-html += `
${ip.type || ""} ${ip.ip}
`
-})
-
-}
-
-html += `
Domains`
-
-const domains = mappings.filter(m => m.resource_id == resource.id)
-
-domains.forEach(d=>{
-html += `
🌐 ${d.domain_name}
`
-})
-
-html += `
-
-
-
-
Bestelldatum: ${resource.bestelldatum || ""}
-
Kündbar ab: ${resource.kuendbar_ab || ""}
-
-
-
-
Bemerkung
-${resource.bemerkung || ""}
-
-`
-const subs = await api(API + "/subdomains")
-
-html += "
Subdomains"
-
-if(Array.isArray(resource.ips)){
-
-subs.forEach(s => {
-
-const match = resource.ips.find(ip => ip.ip === s.ip_address)
-
-if(match){
-html += `
🌐 ${s.subdomain}.${s.domain_name}
`
-}
-
-})
-
-}
-
-/* jetzt erst rendern */
-
-document.getElementById("serverDetailContent").innerHTML = html
-document.getElementById("serverDetailModal").style.display="block"
-
-}
-
-
-function closeServerDetail(){
-
-document.getElementById("serverDetailModal").style.display="none"
-
-}
-
-
-document.addEventListener("click", function(event){
-
- const modal = document.getElementById("serverDetailModal");
-
- if(!modal) return;
-
- if(event.target === modal){
- modal.style.display = "none";
- }
-
-});
-
-
-document.addEventListener("keydown", function(e){
-
- if(e.key === "Escape"){
- const modal = document.getElementById("serverDetailModal");
- if(modal) modal.style.display="none";
- }
-
-});
-
-
-router.delete("/:id", async (req,res)=>{
-
-await db.query("DELETE FROM subdomains WHERE id=?",[req.params.id])
-
-res.json({success:true})
-
-})
-
-
-
-async function loadCancelled(){
-
-const res = await api(API+"/resources/cancelled")
-
-const table = document.getElementById("cancelled")
-
-table.innerHTML=""
-
-res.forEach(r=>{
-
-const tr=document.createElement("tr")
-
-tr.innerHTML=`
-
-
${r.name} |
-
${r.produkt||""} |
-
${r.provider || ""} |
-
${r.cpu||""} |
-
${r.ram||""} |
-
${r.disk||""} |
-
gekündigt |
-
-`
-
-table.appendChild(tr)
-
-})
-
-}
-
-
-async function loadCosts(){
-
-const resources = await api(API+"/resources/active")
-const domains = await api(API+"/domains")
-
-let month = 0
-let year = 0
-let domainYear = 0
-
-resources.forEach(r=>{
-
-let m = Number(r.kosten_monat || 0)
-let y = Number(r.kosten_jahr || 0)
-
-if(m){
-month += m
-}else if(y){
-month += y / 12
-}
-
-if(y){
-year += y
-}else if(m){
-year += m * 12
-}
-
-})
-
-domains.forEach(d=>{
-domainYear += Number(d.yearly_cost || 0)
-})
-
-document.getElementById("costMonth").innerText = month.toFixed(2)
-document.getElementById("costYear").innerText = year.toFixed(2)
-document.getElementById("costDomain").innerText = domainYear.toFixed(2)
-document.getElementById("costTotal").innerText = (year + domainYear).toFixed(2)
-
-}
-
-
-async function loadInfrastructure(){
-
-const resources = await api(API + "/resources/active")
-const mappings = await api(API + "/domainmap")
-
-let html = ""
-
-resources.forEach(r => {
-
-html += `
-
-
-
${r.name}
-Produkt: ${r.produkt || ""}
-CPU: ${r.cpu || ""} | RAM: ${r.ram || ""} | Disk: ${r.disk || ""}
-OS: ${r.os || ""}
-
-
-`
-
-if(Array.isArray(r.ips)){
-
-r.ips.forEach(ip => {
-
-html += `
-
-
-
IP: ${ip.ip} (${ip.type || ""})
-`
-
-const domains = mappings.filter(m => m.ip_address == ip.ip)
-
-domains.forEach(d => {
-
-html += `
-
-🌐 ${d.domain_name}
-
-`
-
-})
-
-html += "
"
-
-})
-
-}
-
-html += "
"
-
-})
-
-document.getElementById("infraView").innerHTML = html
-
-
-
-}
-
diff --git a/backend/public/js/ui.js b/backend/public/js/ui.js
index 8fd64b6..15efe73 100644
--- a/backend/public/js/ui.js
+++ b/backend/public/js/ui.js
@@ -1,114 +1,7 @@
-function openIPManager(resourceId){
-
-document.getElementById("ip_resource_id").value = resourceId
-document.getElementById("ipModal").style.display = "block"
-
-loadIPs(resourceId)
-
-}
-
-function closeIPModal(){
-
-document.getElementById("ipModal").style.display = "none"
-
-}
-
-async function loadIPs(resourceId){
-
-const ips = await api(API + "/resources/" + resourceId + "/ips")
-
-const container = document.getElementById("ipList")
-container.innerHTML = ""
-
-ips.forEach(ip=>{
-
-const div = document.createElement("div")
-
-div.innerHTML = `
-${ip.ip} (${ip.type||""}) ${ip.comment||""}
-
-`
-
-container.appendChild(div)
-
-})
-
-}
-
-async function saveIP(){
-
-const resourceId = document.getElementById("ip_resource_id").value
-
-const ip = document.getElementById("new_ip").value
-const type = document.getElementById("new_type").value
-const comment = document.getElementById("new_comment").value
-
-await api("/resman/api/resources/"+resourceId+"/ips",{
-method:"POST",
-headers:{'Content-Type':'application/json'},
-body:JSON.stringify({ip,type,comment})
-})
-
-loadIPs(resourceId)
-loadResources()
-
-}
-
-
-
-async function deleteIP(id, resourceId){
-
- await api(API + "/ips/" + id,{
- method:"DELETE"
- });
-
- loadIPs(resourceId);
- loadResources();
-
-}
-
-
-
-function openSubCreate(domainId){
-
-document.getElementById("sub_domain_id").value=domainId
-
-document.getElementById("sub_name").value=""
-document.getElementById("sub_ip").value=""
-
-document.getElementById("subdomainModal").style.display="block"
-
-}
-
-function closeSubModal(){
-
-document.getElementById("subdomainModal").style.display="none"
-
-}
-
-async function saveSubdomain(){
-
-const domain_id=document.getElementById("sub_domain_id").value
-
-const subdomain=document.getElementById("sub_name").value
-const ip_address=document.getElementById("sub_ip").value
-
-await api(API+"/subdomains",{
-method:"POST",
-headers:{'Content-Type':'application/json'},
-body:JSON.stringify({
-domain_id,
-subdomain,
-ip_address
-})
-})
-
-closeSubModal()
-
-loadDomains()
-
-}
+window.loadCosts = function(){}
+window.loadCancelled = function(){}
+window.loadInfrastructure = function(){}