534 lines
7.2 KiB
HTML
534 lines
7.2 KiB
HTML
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
|
||
<meta charset="UTF-8">
|
||
<title>ResMan</title>
|
||
|
||
<style>
|
||
|
||
body{
|
||
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{
|
||
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>
|
||
|
||
<h1>Resource Manager</h1>
|
||
|
||
<button onclick="openCreate()">➕ New Resource</button>
|
||
|
||
<h2>Active 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="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>
|
||
|
||
<input id="kuendigungsdatum" type="date">
|
||
|
||
<textarea id="bemerkung" placeholder="Bemerkung"></textarea>
|
||
|
||
<button onclick="saveResource()">Save</button>
|
||
<button onclick="closeModal()">Cancel</button>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
<script>
|
||
|
||
const API="/resman/api/resources"
|
||
|
||
let editId=null
|
||
|
||
|
||
|
||
function val(v){
|
||
|
||
if(v==="" || v===undefined) return null
|
||
|
||
return v
|
||
|
||
}
|
||
|
||
|
||
|
||
function getForm(){
|
||
|
||
return{
|
||
|
||
name:document.getElementById("name").value,
|
||
|
||
produkt:val(document.getElementById("produkt").value),
|
||
|
||
provider:val(document.getElementById("provider").value),
|
||
|
||
art:val(document.getElementById("art").value),
|
||
|
||
cpu:val(document.getElementById("cpu").value),
|
||
|
||
ram:val(document.getElementById("ram").value),
|
||
|
||
disk:val(document.getElementById("disk").value),
|
||
|
||
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)
|
||
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|
||
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("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){
|
||
|
||
if(!confirm("Delete resource?")) return
|
||
|
||
await fetch(API+"/"+id,{method:"DELETE"})
|
||
|
||
loadResources()
|
||
|
||
}
|
||
|
||
|
||
|
||
async function addIP(resourceId){
|
||
|
||
const ip=prompt("IP")
|
||
|
||
if(!ip) return
|
||
|
||
const type=prompt("Type (public/vlan/ipv6)")
|
||
|
||
await fetch(API+"/"+resourceId+"/ips",{
|
||
|
||
method:"POST",
|
||
|
||
headers:{"Content-Type":"application/json"},
|
||
|
||
body:JSON.stringify({ip,type})
|
||
|
||
})
|
||
|
||
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 deleteIP(ipId){
|
||
|
||
await fetch("/resman/api/ips/"+ipId,{method:"DELETE"})
|
||
|
||
loadResources()
|
||
|
||
}
|
||
|
||
|
||
|
||
function renderIPs(r){
|
||
|
||
let html=""
|
||
|
||
if(Array.isArray(r.ips)){
|
||
|
||
r.ips.forEach(ip=>{
|
||
|
||
html+=`
|
||
|
||
<div class="ip">
|
||
|
||
${ip.ip} (${ip.type})
|
||
|
||
<button onclick="editIP(${ip.id})">Edit</button>
|
||
|
||
<button onclick="deleteIP(${ip.id})">Delete</button>
|
||
|
||
</div>
|
||
|
||
`
|
||
|
||
})
|
||
|
||
}
|
||
|
||
html+=`<button onclick="addIP(${r.id})">Add IP</button>`
|
||
|
||
return html
|
||
|
||
}
|
||
|
||
|
||
|
||
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>
|