initial version resource manager
This commit is contained in:
@@ -1,36 +1,158 @@
|
|||||||
const pool = require("../db");
|
const pool = require("../db");
|
||||||
|
|
||||||
exports.getActive = async (req, res) => {
|
/* ACTIVE */
|
||||||
const [rows] = await pool.query(
|
exports.getActive = async (req,res)=>{
|
||||||
"SELECT * FROM resources WHERE status != 'gekündigt'"
|
|
||||||
);
|
const [rows] = await pool.query(`
|
||||||
res.json(rows);
|
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);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getCancelled = async (req, res) => {
|
/* CANCELLED */
|
||||||
const [rows] = await pool.query(
|
|
||||||
"SELECT * FROM resources WHERE status = 'gekündigt'"
|
exports.getCancelled = async (req,res)=>{
|
||||||
);
|
|
||||||
res.json(rows);
|
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);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.create = async (req, res) => {
|
/* CREATE */
|
||||||
await pool.query("INSERT INTO resources SET ?", req.body);
|
|
||||||
res.json({ message: "Inserted" });
|
exports.create = async (req,res)=>{
|
||||||
|
|
||||||
|
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)
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.update = async (req, res) => {
|
await pool.query("INSERT INTO resources SET ?",data);
|
||||||
await pool.query(
|
|
||||||
"UPDATE resources SET ? WHERE id = ?",
|
res.json({message:"created"});
|
||||||
[req.body, req.params.id]
|
|
||||||
);
|
|
||||||
res.json({ message: "Updated" });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.remove = async (req, res) => {
|
|
||||||
await pool.query(
|
|
||||||
"DELETE FROM resources WHERE id = ?",
|
/* UPDATE */
|
||||||
[req.params.id]
|
|
||||||
);
|
exports.update = async (req,res)=>{
|
||||||
res.json({ message: "Deleted" });
|
|
||||||
|
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"});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/* DELETE */
|
||||||
|
|
||||||
|
exports.remove = async (req,res)=>{
|
||||||
|
|
||||||
|
await pool.query(
|
||||||
|
"DELETE FROM resources WHERE id=?",
|
||||||
|
[req.params.id]
|
||||||
|
);
|
||||||
|
|
||||||
|
res.json({message:"deleted"});
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
+419
-212
@@ -1,326 +1,533 @@
|
|||||||
```html
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="de">
|
<html>
|
||||||
<head>
|
<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>
|
<style>
|
||||||
|
|
||||||
body{
|
body{
|
||||||
background:#f5f6fa;
|
font-family:Arial, Helvetica, sans-serif;
|
||||||
|
background:#0f172a;
|
||||||
|
color:#e5e7eb;
|
||||||
|
margin:0;
|
||||||
|
padding:20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1{
|
||||||
|
margin-bottom:20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
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{
|
.card{
|
||||||
margin-bottom:20px;
|
background:#1e293b;
|
||||||
|
padding:15px;
|
||||||
|
border-radius:10px;
|
||||||
|
margin-bottom:15px;
|
||||||
|
border:1px solid #334155;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ip{
|
table{
|
||||||
font-size:0.9em;
|
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>
|
</style>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div class="container mt-4">
|
<h1>Resource Manager</h1>
|
||||||
|
|
||||||
<h2>Resource Manager</h2>
|
<button onclick="openCreate()">➕ New Resource</button>
|
||||||
|
|
||||||
<div class="card">
|
<h2>Active Resources</h2>
|
||||||
<div class="card-header">
|
|
||||||
Neue Resource
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card-body">
|
<table>
|
||||||
|
|
||||||
<div class="row g-2">
|
<thead>
|
||||||
|
|
||||||
<div class="col">
|
<tr>
|
||||||
<input id="name" class="form-control" placeholder="Name">
|
<th>Name</th>
|
||||||
</div>
|
<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">
|
</thead>
|
||||||
<input id="customer" class="form-control" placeholder="Customer">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col">
|
<tbody id="activeResources"></tbody>
|
||||||
<select id="status" class="form-select">
|
|
||||||
|
</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="aktiv">aktiv</option>
|
||||||
<option value="gekündigt">gekündigt</option>
|
<option value="gekündigt">gekündigt</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col">
|
<input id="kuendigungsdatum" type="date">
|
||||||
<button class="btn btn-primary" onclick="createResource()">Create</button>
|
|
||||||
</div>
|
<textarea id="bemerkung" placeholder="Bemerkung"></textarea>
|
||||||
|
|
||||||
|
<button onclick="saveResource()">Save</button>
|
||||||
|
<button onclick="closeModal()">Cancel</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</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>
|
<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">
|
return v
|
||||||
|
|
||||||
<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>
|
|
||||||
|
|
||||||
`;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadResources(){
|
|
||||||
|
|
||||||
const activeDiv=document.getElementById("activeResources");
|
|
||||||
const cancelledDiv=document.getElementById("cancelledResources");
|
|
||||||
|
|
||||||
activeDiv.innerHTML="";
|
function getForm(){
|
||||||
cancelledDiv.innerHTML="";
|
|
||||||
|
|
||||||
try{
|
return{
|
||||||
|
|
||||||
const res=await fetch(`${API}/resources/active`);
|
name:document.getElementById("name").value,
|
||||||
const active=await res.json();
|
|
||||||
|
|
||||||
active.forEach(r=>{
|
produkt:val(document.getElementById("produkt").value),
|
||||||
activeDiv.innerHTML+=renderResource(r);
|
|
||||||
|
|
||||||
setTimeout(()=>loadIPs(r.id),100);
|
provider:val(document.getElementById("provider").value),
|
||||||
});
|
|
||||||
|
|
||||||
}catch(e){
|
art:val(document.getElementById("art").value),
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
try{
|
cpu:val(document.getElementById("cpu").value),
|
||||||
|
|
||||||
const res=await fetch(`${API}/resources/cancelled`);
|
ram:val(document.getElementById("ram").value),
|
||||||
const cancelled=await res.json();
|
|
||||||
|
|
||||||
cancelled.forEach(r=>{
|
disk:val(document.getElementById("disk").value),
|
||||||
cancelledDiv.innerHTML+=renderResource(r);
|
|
||||||
|
|
||||||
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`,{
|
function openCreate(){
|
||||||
method:"POST",
|
|
||||||
headers:{
|
editId=null
|
||||||
"Content-Type":"application/json"
|
|
||||||
},
|
document.getElementById("modalTitle").innerText="New Resource"
|
||||||
body:JSON.stringify({
|
|
||||||
name,
|
document.getElementById("resourceModal").style.display="flex"
|
||||||
customer,
|
|
||||||
status
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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("resourceModal").style.display="flex"
|
||||||
document.getElementById("customer").value="";
|
|
||||||
|
|
||||||
loadResources();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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){
|
async function deleteResource(id){
|
||||||
|
|
||||||
await fetch(`${API}/resources/${id}`,{
|
if(!confirm("Delete resource?")) return
|
||||||
method:"DELETE"
|
|
||||||
});
|
await fetch(API+"/"+id,{method:"DELETE"})
|
||||||
|
|
||||||
|
loadResources()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async function addIP(resourceId){
|
async function addIP(resourceId){
|
||||||
|
|
||||||
const ip=document.getElementById("ip-"+resourceId).value;
|
const ip=prompt("IP")
|
||||||
const type=document.getElementById("type-"+resourceId).value;
|
|
||||||
const comment=document.getElementById("comment-"+resourceId).value;
|
|
||||||
|
|
||||||
if(!ip){
|
if(!ip) return
|
||||||
alert("IP fehlt");
|
|
||||||
return;
|
const type=prompt("Type (public/vlan/ipv6)")
|
||||||
}
|
|
||||||
|
await fetch(API+"/"+resourceId+"/ips",{
|
||||||
|
|
||||||
await fetch("/resman/api/resources/"+resourceId+"/ips",{
|
|
||||||
method:"POST",
|
method:"POST",
|
||||||
headers:{
|
|
||||||
"Content-Type":"application/json"
|
headers:{"Content-Type":"application/json"},
|
||||||
},
|
|
||||||
body:JSON.stringify({
|
body:JSON.stringify({ip,type})
|
||||||
ip:ip,
|
|
||||||
type:type,
|
|
||||||
comment:comment
|
|
||||||
})
|
})
|
||||||
});
|
|
||||||
|
|
||||||
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`);
|
async function deleteIP(ipId){
|
||||||
const data=await res.json();
|
|
||||||
|
|
||||||
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+=`
|
html+=`
|
||||||
|
|
||||||
<div class="d-flex justify-content-between border rounded p-1 mb-1 ip">
|
<div class="ip">
|
||||||
|
|
||||||
<div>
|
${ip.ip} (${ip.type})
|
||||||
<strong>${ip.ip}</strong>
|
|
||||||
<span class="text-muted">(${ip.type})</span>
|
|
||||||
<small>${ip.comment || ""}</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="btn btn-sm btn-danger"
|
<button onclick="editIP(${ip.id})">Edit</button>
|
||||||
onclick="deleteIP(${ip.id},${resourceId})">
|
|
||||||
X
|
<button onclick="deleteIP(${ip.id})">Delete</button>
|
||||||
</button>
|
|
||||||
|
|
||||||
</div>
|
</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}`,{
|
return html
|
||||||
method:"DELETE"
|
|
||||||
});
|
|
||||||
|
|
||||||
loadIPs(resourceId);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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>
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,17 @@ const ipRoutes = require("./routes/ips");
|
|||||||
|
|
||||||
const app = express();
|
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(cors());
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
|
||||||
@@ -18,6 +29,16 @@ app.use("/resman/api/resources", resourceRoutes);
|
|||||||
|
|
||||||
app.use("/resman/api", ipRoutes);
|
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, () => {
|
app.listen(3000, () => {
|
||||||
console.log("ResMan running on port 3000");
|
console.log("ResMan running on port 3000");
|
||||||
|
|||||||
Reference in New Issue
Block a user