initial version resource manager
This commit is contained in:
@@ -1,36 +1,158 @@
|
||||
const pool = require("../db");
|
||||
|
||||
/* ACTIVE */
|
||||
exports.getActive = async (req,res)=>{
|
||||
const [rows] = await pool.query(
|
||||
"SELECT * FROM resources WHERE status != 'gekündigt'"
|
||||
);
|
||||
|
||||
const [rows] = await pool.query(`
|
||||
SELECT
|
||||
r.*,
|
||||
JSON_ARRAYAGG(
|
||||
JSON_OBJECT(
|
||||
'id', ip.id,
|
||||
'ip', ip.ip,
|
||||
'type', ip.type,
|
||||
'comment', ip.comment
|
||||
)
|
||||
) AS ips
|
||||
FROM resources r
|
||||
LEFT JOIN resource_ips ip ON r.id = ip.resource_id
|
||||
WHERE r.status != 'gekündigt'
|
||||
GROUP BY r.id
|
||||
`);
|
||||
|
||||
rows.forEach(r=>{
|
||||
|
||||
/* JSON string → array */
|
||||
if(typeof r.ips === "string"){
|
||||
try{
|
||||
r.ips = JSON.parse(r.ips);
|
||||
}catch{
|
||||
r.ips=[];
|
||||
}
|
||||
}
|
||||
|
||||
/* remove null entries */
|
||||
if(r.ips && r.ips[0] && r.ips[0].id === null){
|
||||
r.ips=[];
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
res.json(rows);
|
||||
|
||||
};
|
||||
|
||||
/* CANCELLED */
|
||||
|
||||
exports.getCancelled = async (req,res)=>{
|
||||
const [rows] = await pool.query(
|
||||
"SELECT * FROM resources WHERE status = 'gekündigt'"
|
||||
);
|
||||
|
||||
const [rows] = await pool.query(`
|
||||
SELECT
|
||||
r.*,
|
||||
JSON_ARRAYAGG(
|
||||
JSON_OBJECT(
|
||||
'id', ip.id,
|
||||
'ip', ip.ip,
|
||||
'type', ip.type,
|
||||
'comment', ip.comment
|
||||
)
|
||||
) AS ips
|
||||
FROM resources r
|
||||
LEFT JOIN resource_ips ip ON r.id = ip.resource_id
|
||||
WHERE r.status = 'gekündigt'
|
||||
GROUP BY r.id
|
||||
`);
|
||||
|
||||
rows.forEach(r=>{
|
||||
|
||||
if(typeof r.ips === "string"){
|
||||
try{
|
||||
r.ips = JSON.parse(r.ips);
|
||||
}catch{
|
||||
r.ips=[];
|
||||
}
|
||||
}
|
||||
|
||||
if(r.ips && r.ips[0] && r.ips[0].id === null){
|
||||
r.ips=[];
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
res.json(rows);
|
||||
|
||||
};
|
||||
|
||||
/* CREATE */
|
||||
|
||||
exports.create = async (req,res)=>{
|
||||
await pool.query("INSERT INTO resources SET ?", req.body);
|
||||
res.json({ message: "Inserted" });
|
||||
|
||||
const clean = v => v === "" ? null : v;
|
||||
|
||||
const data = {
|
||||
name: req.body.name,
|
||||
produkt: clean(req.body.produkt),
|
||||
provider: clean(req.body.provider),
|
||||
art: clean(req.body.art),
|
||||
|
||||
cpu: clean(req.body.cpu),
|
||||
ram: clean(req.body.ram),
|
||||
disk: clean(req.body.disk),
|
||||
os: clean(req.body.os),
|
||||
|
||||
ipv6_net: clean(req.body.ipv6_net),
|
||||
providername: clean(req.body.providername),
|
||||
|
||||
kosten_monat: clean(req.body.kosten_monat),
|
||||
kosten_jahr: clean(req.body.kosten_jahr),
|
||||
laufzeit_monate: clean(req.body.laufzeit_monate),
|
||||
|
||||
bestelldatum: clean(req.body.bestelldatum),
|
||||
kuendbar_ab: clean(req.body.kuendbar_ab),
|
||||
|
||||
status: clean(req.body.status),
|
||||
kuendigungsdatum: clean(req.body.kuendigungsdatum),
|
||||
|
||||
bemerkung: clean(req.body.bemerkung)
|
||||
};
|
||||
|
||||
await pool.query("INSERT INTO resources SET ?",data);
|
||||
|
||||
res.json({message:"created"});
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* UPDATE */
|
||||
|
||||
exports.update = async (req,res)=>{
|
||||
|
||||
const clean = v => v === "" ? null : v;
|
||||
|
||||
Object.keys(req.body).forEach(k=>{
|
||||
req.body[k]=clean(req.body[k]);
|
||||
});
|
||||
|
||||
await pool.query(
|
||||
"UPDATE resources SET ? WHERE id=?",
|
||||
[req.body,req.params.id]
|
||||
);
|
||||
res.json({ message: "Updated" });
|
||||
|
||||
res.json({message:"updated"});
|
||||
|
||||
};
|
||||
|
||||
|
||||
/* DELETE */
|
||||
|
||||
exports.remove = async (req,res)=>{
|
||||
|
||||
await pool.query(
|
||||
"DELETE FROM resources WHERE id=?",
|
||||
[req.params.id]
|
||||
);
|
||||
res.json({ message: "Deleted" });
|
||||
|
||||
res.json({message:"deleted"});
|
||||
|
||||
};
|
||||
|
||||
+419
-212
@@ -1,326 +1,533 @@
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Resource Manager</title>
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<meta charset="UTF-8">
|
||||
<title>ResMan</title>
|
||||
|
||||
<style>
|
||||
|
||||
body{
|
||||
background:#f5f6fa;
|
||||
font-family:Arial, Helvetica, sans-serif;
|
||||
background:#0f172a;
|
||||
color:#e5e7eb;
|
||||
margin:0;
|
||||
padding:20px;
|
||||
}
|
||||
|
||||
.card{
|
||||
h1{
|
||||
margin-bottom:20px;
|
||||
}
|
||||
|
||||
.ip{
|
||||
font-size:0.9em;
|
||||
button{
|
||||
background:#3b82f6;
|
||||
border:none;
|
||||
padding:8px 14px;
|
||||
color:white;
|
||||
border-radius:6px;
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
button:hover{
|
||||
background:#2563eb;
|
||||
}
|
||||
|
||||
button.delete{
|
||||
background:#ef4444;
|
||||
}
|
||||
|
||||
button.delete:hover{
|
||||
background:#dc2626;
|
||||
}
|
||||
|
||||
.card{
|
||||
background:#1e293b;
|
||||
padding:15px;
|
||||
border-radius:10px;
|
||||
margin-bottom:15px;
|
||||
border:1px solid #334155;
|
||||
}
|
||||
|
||||
table{
|
||||
width:100%;
|
||||
border-collapse:collapse;
|
||||
margin-top:10px;
|
||||
}
|
||||
|
||||
th,td{
|
||||
padding:10px;
|
||||
border-bottom:1px solid #334155;
|
||||
}
|
||||
|
||||
th{
|
||||
text-align:left;
|
||||
}
|
||||
|
||||
tr:hover{
|
||||
background:#273449;
|
||||
}
|
||||
|
||||
.modal{
|
||||
position:fixed;
|
||||
top:0;
|
||||
left:0;
|
||||
width:100%;
|
||||
height:100%;
|
||||
background:rgba(0,0,0,0.6);
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
}
|
||||
|
||||
.modal-content{
|
||||
background:#1e293b;
|
||||
padding:25px;
|
||||
border-radius:10px;
|
||||
max-height:90vh;
|
||||
overflow:auto;
|
||||
width:700px;
|
||||
border:1px solid #334155;
|
||||
}
|
||||
|
||||
input,select,textarea{
|
||||
width:100%;
|
||||
padding:8px;
|
||||
margin-bottom:10px;
|
||||
border-radius:6px;
|
||||
border:1px solid #334155;
|
||||
background:#0f172a;
|
||||
color:#e5e7eb;
|
||||
}
|
||||
|
||||
label{
|
||||
font-size:14px;
|
||||
}
|
||||
|
||||
.form-grid{
|
||||
display:grid;
|
||||
grid-template-columns:1fr 1fr;
|
||||
gap:10px;
|
||||
}
|
||||
|
||||
.ip-list{
|
||||
margin-top:10px;
|
||||
}
|
||||
|
||||
.ip-item{
|
||||
display:flex;
|
||||
justify-content:space-between;
|
||||
padding:6px;
|
||||
border-bottom:1px solid #334155;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="container mt-4">
|
||||
<h1>Resource Manager</h1>
|
||||
|
||||
<h2>Resource Manager</h2>
|
||||
<button onclick="openCreate()">➕ New Resource</button>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
Neue Resource
|
||||
</div>
|
||||
<h2>Active Resources</h2>
|
||||
|
||||
<div class="card-body">
|
||||
<table>
|
||||
|
||||
<div class="row g-2">
|
||||
<thead>
|
||||
|
||||
<div class="col">
|
||||
<input id="name" class="form-control" placeholder="Name">
|
||||
</div>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Provider</th>
|
||||
<th>Produkt</th>
|
||||
<th>CPU</th>
|
||||
<th>RAM</th>
|
||||
<th>Disk</th>
|
||||
<th>IPs</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
|
||||
<div class="col">
|
||||
<input id="customer" class="form-control" placeholder="Customer">
|
||||
</div>
|
||||
</thead>
|
||||
|
||||
<div class="col">
|
||||
<select id="status" class="form-select">
|
||||
<tbody id="activeResources"></tbody>
|
||||
|
||||
</table>
|
||||
|
||||
|
||||
<h2>Cancelled Resources</h2>
|
||||
|
||||
<table>
|
||||
|
||||
<thead>
|
||||
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Provider</th>
|
||||
<th>Produkt</th>
|
||||
<th>CPU</th>
|
||||
<th>RAM</th>
|
||||
<th>Disk</th>
|
||||
<th>IPs</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
|
||||
</thead>
|
||||
|
||||
<tbody id="cancelledResources"></tbody>
|
||||
|
||||
</table>
|
||||
|
||||
|
||||
<!-- MODAL -->
|
||||
|
||||
<div class="modal" id="resourceModal">
|
||||
|
||||
<div class="modal-content">
|
||||
|
||||
<h2 id="modalTitle"></h2>
|
||||
|
||||
<input id="name" placeholder="Name">
|
||||
<input id="produkt" placeholder="Produkt">
|
||||
<input id="provider" placeholder="Provider">
|
||||
<input id="art" placeholder="Art">
|
||||
|
||||
<input id="cpu" placeholder="CPU">
|
||||
<input id="ram" placeholder="RAM">
|
||||
<input id="disk" placeholder="Disk">
|
||||
|
||||
<input id="os" placeholder="OS">
|
||||
|
||||
<input id="ipv6_net" placeholder="IPv6 Netz">
|
||||
|
||||
<input id="providername" placeholder="Provider Name">
|
||||
|
||||
<input id="kosten_monat" placeholder="Kosten Monat">
|
||||
<input id="kosten_jahr" placeholder="Kosten Jahr">
|
||||
|
||||
<input id="laufzeit_monate" placeholder="Laufzeit Monate">
|
||||
|
||||
<input id="bestelldatum" type="date">
|
||||
<input id="kuendbar_ab" type="date">
|
||||
|
||||
<select id="status">
|
||||
<option value="aktiv">aktiv</option>
|
||||
<option value="gekündigt">gekündigt</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<button class="btn btn-primary" onclick="createResource()">Create</button>
|
||||
</div>
|
||||
<input id="kuendigungsdatum" type="date">
|
||||
|
||||
<textarea id="bemerkung" placeholder="Bemerkung"></textarea>
|
||||
|
||||
<button onclick="saveResource()">Save</button>
|
||||
<button onclick="closeModal()">Cancel</button>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4>Aktive Ressourcen</h4>
|
||||
<div id="activeResources"></div>
|
||||
|
||||
<h4 class="mt-4">Gekündigte Ressourcen</h4>
|
||||
<div id="cancelledResources"></div>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
const API="/resman/api";
|
||||
const API="/resman/api/resources"
|
||||
|
||||
function statusBadge(status){
|
||||
let editId=null
|
||||
|
||||
if(status==="gekündigt"){
|
||||
return `<span class="badge bg-dark">Gekündigt</span>`;
|
||||
}
|
||||
|
||||
return `<span class="badge bg-success">Aktiv</span>`;
|
||||
}
|
||||
|
||||
function renderResource(r){
|
||||
function val(v){
|
||||
|
||||
return `
|
||||
if(v==="" || v===undefined) return null
|
||||
|
||||
<div class="card">
|
||||
|
||||
<div class="card-body">
|
||||
|
||||
<div class="d-flex justify-content-between">
|
||||
|
||||
<div>
|
||||
<h5>${r.name || ""}</h5>
|
||||
${statusBadge(r.status)}
|
||||
<br>
|
||||
<small>${r.customer || ""}</small>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button class="btn btn-sm btn-danger" onclick="deleteResource(${r.id})">
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<strong>IPs</strong>
|
||||
|
||||
<div id="ips-${r.id}" class="mb-2"></div>
|
||||
|
||||
<div class="input-group input-group-sm">
|
||||
|
||||
<input class="form-control" placeholder="IP" id="ip-${r.id}">
|
||||
|
||||
<select class="form-select" id="type-${r.id}">
|
||||
<option value="public">public</option>
|
||||
<option value="private">private</option>
|
||||
</select>
|
||||
|
||||
<input class="form-control" placeholder="comment" id="comment-${r.id}">
|
||||
|
||||
<button class="btn btn-primary" onclick="addIP(${r.id})">
|
||||
Add
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
`;
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
async function loadResources(){
|
||||
|
||||
const activeDiv=document.getElementById("activeResources");
|
||||
const cancelledDiv=document.getElementById("cancelledResources");
|
||||
|
||||
activeDiv.innerHTML="";
|
||||
cancelledDiv.innerHTML="";
|
||||
function getForm(){
|
||||
|
||||
try{
|
||||
return{
|
||||
|
||||
const res=await fetch(`${API}/resources/active`);
|
||||
const active=await res.json();
|
||||
name:document.getElementById("name").value,
|
||||
|
||||
active.forEach(r=>{
|
||||
activeDiv.innerHTML+=renderResource(r);
|
||||
produkt:val(document.getElementById("produkt").value),
|
||||
|
||||
setTimeout(()=>loadIPs(r.id),100);
|
||||
});
|
||||
provider:val(document.getElementById("provider").value),
|
||||
|
||||
}catch(e){
|
||||
console.error(e);
|
||||
}
|
||||
art:val(document.getElementById("art").value),
|
||||
|
||||
try{
|
||||
cpu:val(document.getElementById("cpu").value),
|
||||
|
||||
const res=await fetch(`${API}/resources/cancelled`);
|
||||
const cancelled=await res.json();
|
||||
ram:val(document.getElementById("ram").value),
|
||||
|
||||
cancelled.forEach(r=>{
|
||||
cancelledDiv.innerHTML+=renderResource(r);
|
||||
disk:val(document.getElementById("disk").value),
|
||||
|
||||
setTimeout(()=>loadIPs(r.id),100);
|
||||
});
|
||||
os:val(document.getElementById("os").value),
|
||||
|
||||
ipv6_net:val(document.getElementById("ipv6_net").value),
|
||||
|
||||
providername:val(document.getElementById("providername").value),
|
||||
|
||||
kosten_monat:val(document.getElementById("kosten_monat").value),
|
||||
|
||||
kosten_jahr:val(document.getElementById("kosten_jahr").value),
|
||||
|
||||
laufzeit_monate:val(document.getElementById("laufzeit_monate").value),
|
||||
|
||||
bestelldatum:val(document.getElementById("bestelldatum").value),
|
||||
|
||||
kuendbar_ab:val(document.getElementById("kuendbar_ab").value),
|
||||
|
||||
status:document.getElementById("status").value,
|
||||
|
||||
kuendigungsdatum:val(document.getElementById("kuendigungsdatum").value),
|
||||
|
||||
bemerkung:val(document.getElementById("bemerkung").value)
|
||||
|
||||
}catch(e){
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function createResource(){
|
||||
|
||||
const name=document.getElementById("name").value;
|
||||
const customer=document.getElementById("customer").value;
|
||||
const status=document.getElementById("status").value;
|
||||
|
||||
await fetch(`${API}/resources`,{
|
||||
method:"POST",
|
||||
headers:{
|
||||
"Content-Type":"application/json"
|
||||
},
|
||||
body:JSON.stringify({
|
||||
name,
|
||||
customer,
|
||||
status
|
||||
function openCreate(){
|
||||
|
||||
editId=null
|
||||
|
||||
document.getElementById("modalTitle").innerText="New Resource"
|
||||
|
||||
document.getElementById("resourceModal").style.display="flex"
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function editResource(r){
|
||||
|
||||
editId=r.id
|
||||
|
||||
document.getElementById("modalTitle").innerText="Edit Resource"
|
||||
|
||||
Object.keys(r).forEach(k=>{
|
||||
|
||||
const el=document.getElementById(k)
|
||||
|
||||
if(el) el.value=r[k] || ""
|
||||
|
||||
})
|
||||
});
|
||||
|
||||
document.getElementById("name").value="";
|
||||
document.getElementById("customer").value="";
|
||||
|
||||
loadResources();
|
||||
document.getElementById("resourceModal").style.display="flex"
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function closeModal(){
|
||||
|
||||
document.getElementById("resourceModal").style.display="none"
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function saveResource(){
|
||||
|
||||
const data=getForm()
|
||||
|
||||
if(editId){
|
||||
|
||||
await fetch(API+"/"+editId,{
|
||||
|
||||
method:"PUT",
|
||||
|
||||
headers:{"Content-Type":"application/json"},
|
||||
|
||||
body:JSON.stringify(data)
|
||||
|
||||
})
|
||||
|
||||
}else{
|
||||
|
||||
await fetch(API,{
|
||||
|
||||
method:"POST",
|
||||
|
||||
headers:{"Content-Type":"application/json"},
|
||||
|
||||
body:JSON.stringify(data)
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
closeModal()
|
||||
|
||||
loadResources()
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function deleteResource(id){
|
||||
|
||||
await fetch(`${API}/resources/${id}`,{
|
||||
method:"DELETE"
|
||||
});
|
||||
if(!confirm("Delete resource?")) return
|
||||
|
||||
await fetch(API+"/"+id,{method:"DELETE"})
|
||||
|
||||
loadResources()
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function addIP(resourceId){
|
||||
|
||||
const ip=document.getElementById("ip-"+resourceId).value;
|
||||
const type=document.getElementById("type-"+resourceId).value;
|
||||
const comment=document.getElementById("comment-"+resourceId).value;
|
||||
const ip=prompt("IP")
|
||||
|
||||
if(!ip){
|
||||
alert("IP fehlt");
|
||||
return;
|
||||
}
|
||||
if(!ip) return
|
||||
|
||||
const type=prompt("Type (public/vlan/ipv6)")
|
||||
|
||||
await fetch(API+"/"+resourceId+"/ips",{
|
||||
|
||||
await fetch("/resman/api/resources/"+resourceId+"/ips",{
|
||||
method:"POST",
|
||||
headers:{
|
||||
"Content-Type":"application/json"
|
||||
},
|
||||
body:JSON.stringify({
|
||||
ip:ip,
|
||||
type:type,
|
||||
comment:comment
|
||||
|
||||
headers:{"Content-Type":"application/json"},
|
||||
|
||||
body:JSON.stringify({ip,type})
|
||||
|
||||
})
|
||||
});
|
||||
|
||||
loadResources();
|
||||
loadResources()
|
||||
|
||||
}
|
||||
|
||||
|
||||
loadResources();
|
||||
|
||||
async function editIP(ipId){
|
||||
|
||||
const ip=prompt("New IP")
|
||||
|
||||
await fetch("/resman/api/ips/"+ipId,{
|
||||
|
||||
method:"PUT",
|
||||
|
||||
headers:{"Content-Type":"application/json"},
|
||||
|
||||
body:JSON.stringify({ip})
|
||||
|
||||
})
|
||||
|
||||
loadResources()
|
||||
|
||||
}
|
||||
|
||||
async function loadIPs(resourceId){
|
||||
|
||||
const container=document.getElementById("ips-"+resourceId);
|
||||
|
||||
const res=await fetch(`${API}/resources/${resourceId}/ips`);
|
||||
const data=await res.json();
|
||||
async function deleteIP(ipId){
|
||||
|
||||
let html="";
|
||||
await fetch("/resman/api/ips/"+ipId,{method:"DELETE"})
|
||||
|
||||
data.forEach(ip=>{
|
||||
loadResources()
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function renderIPs(r){
|
||||
|
||||
let html=""
|
||||
|
||||
if(Array.isArray(r.ips)){
|
||||
|
||||
r.ips.forEach(ip=>{
|
||||
|
||||
html+=`
|
||||
|
||||
<div class="d-flex justify-content-between border rounded p-1 mb-1 ip">
|
||||
<div class="ip">
|
||||
|
||||
<div>
|
||||
<strong>${ip.ip}</strong>
|
||||
<span class="text-muted">(${ip.type})</span>
|
||||
<small>${ip.comment || ""}</small>
|
||||
</div>
|
||||
${ip.ip} (${ip.type})
|
||||
|
||||
<button class="btn btn-sm btn-danger"
|
||||
onclick="deleteIP(${ip.id},${resourceId})">
|
||||
X
|
||||
</button>
|
||||
<button onclick="editIP(${ip.id})">Edit</button>
|
||||
|
||||
<button onclick="deleteIP(${ip.id})">Delete</button>
|
||||
|
||||
</div>
|
||||
|
||||
`;
|
||||
`
|
||||
|
||||
});
|
||||
|
||||
container.innerHTML=html;
|
||||
|
||||
}
|
||||
|
||||
async function addIP(resourceId){
|
||||
|
||||
const ip=document.getElementById("ip-"+resourceId).value;
|
||||
const type=document.getElementById("type-"+resourceId).value;
|
||||
const comment=document.getElementById("comment-"+resourceId).value;
|
||||
|
||||
await fetch(`${API}/resources/${resourceId}/ips`,{
|
||||
method:"POST",
|
||||
headers:{
|
||||
"Content-Type":"application/json"
|
||||
},
|
||||
body:JSON.stringify({
|
||||
ip,
|
||||
type,
|
||||
comment
|
||||
})
|
||||
});
|
||||
|
||||
document.getElementById("ip-"+resourceId).value="";
|
||||
document.getElementById("comment-"+resourceId).value="";
|
||||
|
||||
loadIPs(resourceId);
|
||||
|
||||
}
|
||||
|
||||
async function deleteIP(ipId,resourceId){
|
||||
html+=`<button onclick="addIP(${r.id})">Add IP</button>`
|
||||
|
||||
await fetch(`${API}/ips/${ipId}`,{
|
||||
method:"DELETE"
|
||||
});
|
||||
|
||||
loadIPs(resourceId);
|
||||
return html
|
||||
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
loadResources();
|
||||
});
|
||||
|
||||
|
||||
function renderRow(r){
|
||||
|
||||
return `
|
||||
|
||||
<tr>
|
||||
|
||||
<td>${r.name}</td>
|
||||
<td>${r.provider||""}</td>
|
||||
<td>${r.produkt||""}</td>
|
||||
<td>${r.cpu||""}</td>
|
||||
<td>${r.ram||""}</td>
|
||||
<td>${r.disk||""}</td>
|
||||
|
||||
<td>${renderIPs(r)}</td>
|
||||
|
||||
<td>
|
||||
|
||||
<button onclick='editResource(${JSON.stringify(r)})'>Edit</button>
|
||||
|
||||
<button onclick="deleteResource(${r.id})">Delete</button>
|
||||
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
`
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function loadResources(){
|
||||
|
||||
const active=await fetch(API+"/active").then(r=>r.json())
|
||||
|
||||
const cancelled=await fetch(API+"/cancelled").then(r=>r.json())
|
||||
|
||||
const activeTable=document.getElementById("activeResources")
|
||||
|
||||
const cancelledTable=document.getElementById("cancelledResources")
|
||||
|
||||
activeTable.innerHTML=""
|
||||
cancelledTable.innerHTML=""
|
||||
|
||||
active.forEach(r=>{
|
||||
activeTable.innerHTML+=renderRow(r)
|
||||
})
|
||||
|
||||
cancelled.forEach(r=>{
|
||||
cancelledTable.innerHTML+=renderRow(r)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
loadResources()
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
@@ -7,6 +7,17 @@ const ipRoutes = require("./routes/ips");
|
||||
|
||||
const app = express();
|
||||
|
||||
process.on("uncaughtException", (err) => {
|
||||
console.error("UNCAUGHT EXCEPTION");
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
process.on("unhandledRejection", (err) => {
|
||||
console.error("UNHANDLED PROMISE REJECTION");
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
@@ -18,6 +29,16 @@ app.use("/resman/api/resources", resourceRoutes);
|
||||
|
||||
app.use("/resman/api", ipRoutes);
|
||||
|
||||
app.use((err, req, res, next) => {
|
||||
|
||||
console.error("EXPRESS ERROR:");
|
||||
console.error(err);
|
||||
|
||||
res.status(500).json({
|
||||
error: "Internal Server Error"
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
app.listen(3000, () => {
|
||||
console.log("ResMan running on port 3000");
|
||||
|
||||
Reference in New Issue
Block a user