working resman version

This commit is contained in:
root
2026-03-06 18:30:03 +01:00
parent ae97a3441f
commit 883dc6856d
4 changed files with 424 additions and 334 deletions
+263 -333
View File
@@ -1,379 +1,272 @@
<!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;
}
body{font-family:Arial;margin:40px;background:#f2f2f2}
h1,h2{margin-top:30px}
table{
width:100%;
border-collapse:collapse;
margin-top:10px;
width:100%;
background:white;
margin-bottom:20px
}
th,td{
padding:10px;
border-bottom:1px solid #334155;
border:1px solid #ddd;
padding:8px
}
th{
text-align:left;
background:#333;
color:white
}
tr:hover{
background:#273449;
button{
padding:5px 10px;
margin-right:4px;
cursor:pointer
}
.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;
input{
padding:4px
}
.modal-content{
background:#1e293b;
padding:25px;
border-radius:10px;
max-height:90vh;
overflow:auto;
width:700px;
border:1px solid #334155;
.form{
background:white;
padding:10px;
margin-bottom:20px
}
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>
<h1>ResMan</h1>
<button onclick="openCreate()"> New Resource</button>
<h2>Create Resource</h2>
<div class="form">
<input id="r_name" placeholder="Name">
<input id="r_provider" placeholder="Provider">
<input id="r_cpu" placeholder="CPU">
<input id="r_ram" placeholder="RAM">
<input id="r_disk" placeholder="Disk">
<button onclick="createResource()">Create</button>
</div>
<h2>Active Resources</h2>
<table>
<thead>
<tr>
<th>ID</th>
<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>
<tbody id="resources"></tbody>
</table>
<h2>Cancelled Resources</h2>
<table>
<thead>
<tr>
<th>ID</th>
<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>
<tbody id="cancelled"></tbody>
</table>
<h2>Domains</h2>
<!-- MODAL -->
<div class="form">
<div class="modal" id="resourceModal">
<input id="d_name" placeholder="Domain">
<input id="d_provider" placeholder="Provider">
<input id="d_ip" placeholder="IP">
<input id="d_cost" placeholder="Yearly €">
<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>
<button onclick="createDomain()">Create</button>
</div>
</div>
<table>
<thead>
<tr>
<th>ID</th>
<th>Domain</th>
<th>Provider</th>
<th>IP</th>
<th>Server</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="domains"></tbody>
</table>
<h2>Domain → Server Mapping</h2>
<table>
<thead>
<tr>
<th>Domain</th>
<th>IP</th>
<th>Server</th>
</tr>
</thead>
<tbody id="mapping"></tbody>
</table>
<script>
const API="/resman/api/resources"
let editId=null
const API="/resman/api"
function val(v){
/* LOAD RESOURCES */
if(v==="" || v===undefined) return null
async function loadResources(){
return v
const res=await fetch(API+"/resources/active")
const data=await res.json()
}
const table=document.getElementById("resources")
table.innerHTML=""
data.forEach(r=>{
const ips=r.ips.map(ip=>ip.ip).join("<br>")
function getForm(){
const tr=document.createElement("tr")
return{
tr.innerHTML=`
name:document.getElementById("name").value,
<td>${r.id}</td>
<td>${r.name}</td>
<td>${r.provider||""}</td>
<td>${r.cpu||""}</td>
<td>${r.ram||""}</td>
<td>${r.disk||""}</td>
produkt:val(document.getElementById("produkt").value),
<td>
provider:val(document.getElementById("provider").value),
${ips}
art:val(document.getElementById("art").value),
<br>
cpu:val(document.getElementById("cpu").value),
<input id="ip_${r.id}" placeholder="new ip">
<button onclick="addIP(${r.id})">Add</button>
ram:val(document.getElementById("ram").value),
</td>
disk:val(document.getElementById("disk").value),
<td>
os:val(document.getElementById("os").value),
<button onclick="editResource(${r.id})">Edit</button>
ipv6_net:val(document.getElementById("ipv6_net").value),
<button onclick="deleteResource(${r.id})">Delete</button>
providername:val(document.getElementById("providername").value),
</td>
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] || ""
table.appendChild(tr)
})
document.getElementById("resourceModal").style.display="flex"
}
function closeModal(){
/* CANCELLED */
document.getElementById("resourceModal").style.display="none"
async function loadCancelled(){
}
const res=await fetch(API+"/resources/cancelled")
const data=await res.json()
const table=document.getElementById("cancelled")
table.innerHTML=""
data.forEach(r=>{
async function saveResource(){
const tr=document.createElement("tr")
const data=getForm()
tr.innerHTML=`
<td>${r.id}</td>
<td>${r.name}</td>
<td>${r.provider||""}</td>
`
if(editId){
await fetch(API+"/"+editId,{
method:"PUT",
headers:{"Content-Type":"application/json"},
body:JSON.stringify(data)
table.appendChild(tr)
})
}else{
}
await fetch(API,{
/* CREATE RESOURCE */
async function createResource(){
const body={
name:document.getElementById("r_name").value,
provider:document.getElementById("r_provider").value,
cpu:document.getElementById("r_cpu").value,
ram:document.getElementById("r_ram").value,
disk:document.getElementById("r_disk").value
}
await fetch(API+"/resources",{
method:"POST",
headers:{"Content-Type":"application/json"},
headers:{'Content-Type':'application/json'},
body:JSON.stringify(data)
body:JSON.stringify(body)
})
}
closeModal()
loadResources()
}
/* DELETE RESOURCE */
async function deleteResource(id){
if(!confirm("Delete resource?")) return
if(!confirm("delete resource?")) return
await fetch(API+"/"+id,{method:"DELETE"})
await fetch(API+"/resources/"+id,{method:"DELETE"})
loadResources()
@@ -381,21 +274,20 @@ loadResources()
async function addIP(resourceId){
/* EDIT RESOURCE */
const ip=prompt("IP")
async function editResource(id){
if(!ip) return
const name=prompt("Name")
if(!name) return
const type=prompt("Type (public/vlan/ipv6)")
await fetch(API+"/resources/"+id,{
await fetch(API+"/"+resourceId+"/ips",{
method:"PUT",
method:"POST",
headers:{'Content-Type':'application/json'},
headers:{"Content-Type":"application/json"},
body:JSON.stringify({ip,type})
body:JSON.stringify({name})
})
@@ -405,15 +297,17 @@ loadResources()
async function editIP(ipId){
/* IP MANAGER */
const ip=prompt("New IP")
async function addIP(resource_id){
await fetch("/resman/api/ips/"+ipId,{
const ip=document.getElementById("ip_"+resource_id).value
method:"PUT",
await fetch(API+"/resources/"+resource_id+"/ips",{
headers:{"Content-Type":"application/json"},
method:"POST",
headers:{'Content-Type':'application/json'},
body:JSON.stringify({ip})
@@ -425,108 +319,144 @@ loadResources()
async function deleteIP(ipId){
/* DOMAINS */
await fetch("/resman/api/ips/"+ipId,{method:"DELETE"})
async function loadDomains(){
loadResources()
const res=await fetch(API+"/domains")
}
const data=await res.json()
const table=document.getElementById("domains")
table.innerHTML=""
function renderIPs(r){
data.forEach(d=>{
let html=""
const tr=document.createElement("tr")
if(Array.isArray(r.ips)){
tr.innerHTML=`
r.ips.forEach(ip=>{
<td>${d.id}</td>
html+=`
<td>${d.domain_name}</td>
<div class="ip">
<td>${d.provider||""}</td>
${ip.ip} (${ip.type})
<td>${d.ip_address||""}</td>
<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>${d.resource_name||""}</td>
<td>
<button onclick='editResource(${JSON.stringify(r)})'>Edit</button>
<button onclick="deleteResource(${r.id})">Delete</button>
<button onclick="deleteDomain(${d.id})">Delete</button>
</td>
</tr>
`
table.appendChild(tr)
})
}
/* CREATE DOMAIN */
async function createDomain(){
const body={
domain_name:document.getElementById("d_name").value,
provider:document.getElementById("d_provider").value,
ip_address:document.getElementById("d_ip").value,
yearly_cost:document.getElementById("d_cost").value
}
await fetch(API+"/domains",{
method:"POST",
headers:{'Content-Type':'application/json'},
body:JSON.stringify(body)
})
loadDomains()
loadMapping()
}
/* DELETE DOMAIN */
async function deleteDomain(id){
if(!confirm("delete domain?")) return
await fetch(API+"/domains/"+id,{method:"DELETE"})
loadDomains()
loadMapping()
}
/* DOMAIN MAPPING */
async function loadMapping(){
const res=await fetch(API+"/domainmap")
const data=await res.json()
const table=document.getElementById("mapping")
table.innerHTML=""
data.forEach(m=>{
const tr=document.createElement("tr")
tr.innerHTML=`
<td>${m.domain_name}</td>
<td>${m.ip_address}</td>
<td>${m.server_name||""}</td>
`
}
table.appendChild(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)
})
}
/* INIT */
loadResources()
loadCancelled()
loadDomains()
loadMapping()
</script>
</body>
+33
View File
@@ -0,0 +1,33 @@
const express = require('express')
const router = express.Router()
const db = require('../db')
router.get('/', async (req, res) => {
try {
const [rows] = await db.query(`
SELECT
d.id AS domain_id,
d.domain_name,
d.ip_address,
r.id AS resource_id,
r.name AS server_name
FROM domains d
LEFT JOIN resource_ips ip ON d.ip_address = ip.ip
LEFT JOIN resources r ON ip.resource_id = r.id
ORDER BY d.domain_name
`)
res.json(rows)
} catch(err){
console.error(err)
res.status(500).json({error:"DB error"})
}
})
module.exports = router
+120
View File
@@ -0,0 +1,120 @@
const express = require('express');
const router = express.Router();
const pool = require('../db');
// GET ALL DOMAINS
router.get('/', async (req, res) => {
try {
const [rows] = await pool.query(`
SELECT
d.id,
d.domain_name,
d.provider,
d.ip_address,
d.yearly_cost,
d.notes,
r.name AS resource_name
FROM domains d
LEFT JOIN resource_ips ip ON d.ip_address = ip.ip
LEFT JOIN resources r ON ip.resource_id = r.id
ORDER BY d.domain_name
`);
res.json(rows);
} catch (err) {
console.error("GET domains error:", err);
res.status(500).json({ error: "DB error" });
}
});
// GET SINGLE DOMAIN
router.get('/:id', async (req, res) => {
try {
const [rows] = await pool.query(
`SELECT * FROM domains WHERE id = ?`,
[req.params.id]
);
if (rows.length === 0) {
return res.status(404).json({ error: "Domain not found" });
}
res.json(rows[0]);
} catch (err) {
console.error("GET domain error:", err);
res.status(500).json({ error: "DB error" });
}
});
// CREATE DOMAIN
router.post('/', async (req, res) => {
try {
const { domain_name, provider, ip_address, yearly_cost, notes } = req.body;
const [result] = await pool.query(
`INSERT INTO domains
(domain_name, provider, ip_address, yearly_cost, notes)
VALUES (?, ?, ?, ?, ?)`,
[domain_name, provider, ip_address, yearly_cost, notes]
);
res.json({
message: "Domain created",
id: result.insertId
});
} catch (err) {
console.error("CREATE domain error:", err);
res.status(500).json({ error: "DB error" });
}
});
// UPDATE DOMAIN
router.put('/:id', async (req, res) => {
try {
const { domain_name, provider, ip_address, yearly_cost, notes } = req.body;
await pool.query(
`UPDATE domains SET
domain_name = ?,
provider = ?,
ip_address = ?,
yearly_cost = ?,
notes = ?
WHERE id = ?`,
[domain_name, provider, ip_address, yearly_cost, notes, req.params.id]
);
res.json({ message: "Domain updated" });
} catch (err) {
console.error("UPDATE domain error:", err);
res.status(500).json({ error: "DB error" });
}
});
// DELETE DOMAIN
router.delete('/:id', async (req, res) => {
try {
await pool.query(
`DELETE FROM domains WHERE id = ?`,
[req.params.id]
);
res.json({ message: "Domain deleted" });
} catch (err) {
console.error("DELETE domain error:", err);
res.status(500).json({ error: "DB error" });
}
});
module.exports = router;
+7
View File
@@ -4,6 +4,8 @@ const path = require("path");
const resourceRoutes = require("./routes/resources");
const ipRoutes = require("./routes/ips");
const domainsRoutes = require('./routes/domains');
const domainMapping = require('./routes/domainMapping')
const app = express();
@@ -29,6 +31,11 @@ app.use("/resman/api/resources", resourceRoutes);
app.use("/resman/api", ipRoutes);
app.use('/resman/api/domains', domainsRoutes);
app.use('/resman/api/domainmap', domainMapping)
app.use((err, req, res, next) => {
console.error("EXPRESS ERROR:");