vor subdomain UX anpassung
This commit is contained in:
parent
e4e42b0472
commit
39f5f262fe
25
README.md
25
README.md
@ -66,14 +66,29 @@ infra.js
|
||||
```bash
|
||||
git clone <repo>
|
||||
cd resman
|
||||
cd backend
|
||||
npm install
|
||||
npm run migrate
|
||||
npm start
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
### 2. Migrationen
|
||||
|
||||
Beim Start des Backends werden ausstehende Migrationen automatisch ausgefuehrt.
|
||||
Manuell kannst du sie jederzeit mit `npm run migrate` im `backend`-Ordner starten.
|
||||
|
||||
Manuell kannst du sie im Container jederzeit starten:
|
||||
|
||||
```bash
|
||||
docker compose exec backend npm run migrate
|
||||
```
|
||||
|
||||
### 3. Backend neu bauen / neu starten
|
||||
|
||||
```bash
|
||||
docker compose up -d --build backend
|
||||
```
|
||||
|
||||
### 4. Erreichbarkeit
|
||||
|
||||
- Frontend/API intern: `http://127.0.0.1:3000/resman/`
|
||||
- Fuer produktive Nutzung ist ein Reverse Proxy davor vorgesehen
|
||||
|
||||
|
||||
🗄️ Datenbank Schema
|
||||
|
||||
@ -123,6 +123,12 @@ input, textarea {
|
||||
background: #f4f4f4;
|
||||
}
|
||||
|
||||
.modal-meta{
|
||||
font-size:13px;
|
||||
color:#64748b;
|
||||
margin:-6px 0 12px 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.subdomain{
|
||||
@ -225,3 +231,7 @@ body.dark .ip.public{
|
||||
background:#16a34a;
|
||||
color:white;
|
||||
}
|
||||
|
||||
body.dark .modal-meta{
|
||||
color:#94a3b8;
|
||||
}
|
||||
|
||||
@ -257,6 +257,7 @@ Domains jährlich: <span id="costDomain">0</span> €<br>
|
||||
<div id="domainModal" class="modal">
|
||||
|
||||
<h3 id="domainModalTitle">Domain</h3>
|
||||
<div id="domainModalMeta" class="modal-meta"></div>
|
||||
|
||||
<input type="hidden" id="domain_id">
|
||||
|
||||
@ -277,7 +278,7 @@ Domains jährlich: <span id="costDomain">0</span> €<br>
|
||||
|
||||
<br><br>
|
||||
|
||||
<button onclick="saveDomain()">Save</button>
|
||||
<button id="domainSaveBtn" onclick="saveDomain()">Save</button>
|
||||
<button onclick="closeDomainModal()">Cancel</button>
|
||||
|
||||
</div>
|
||||
@ -302,6 +303,7 @@ Domains jährlich: <span id="costDomain">0</span> €<br>
|
||||
<div id="subdomainModal" class="modal">
|
||||
|
||||
<h3>Subdomain erstellen</h3>
|
||||
<div id="subdomainModalMeta" class="modal-meta"></div>
|
||||
|
||||
<input type="hidden" id="sub_domain_id">
|
||||
<input type="hidden" id="sub_id">
|
||||
@ -314,7 +316,7 @@ Domains jährlich: <span id="costDomain">0</span> €<br>
|
||||
|
||||
<br><br>
|
||||
|
||||
<button onclick="saveSubdomain()">Save</button>
|
||||
<button id="subdomainSaveBtn" onclick="saveSubdomain()">Save</button>
|
||||
<button onclick="closeSubModal()">Cancel</button>
|
||||
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
const API = "/resman/api"
|
||||
|
||||
async function api(url, options = {}) {
|
||||
async function api(url, options = {}, config = {}) {
|
||||
const showToast = config.showError !== false
|
||||
const res = await fetch(url, options)
|
||||
|
||||
let data
|
||||
@ -14,10 +15,35 @@ async function api(url, options = {}) {
|
||||
if (!res.ok) {
|
||||
const msg = data.error || "Unbekannter Fehler"
|
||||
|
||||
showError(msg)
|
||||
if (showToast) {
|
||||
showError(msg)
|
||||
}
|
||||
|
||||
throw new Error(msg)
|
||||
const error = new Error(msg)
|
||||
error.status = res.status
|
||||
error.data = data
|
||||
|
||||
throw error
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
window.runAction = async function(action, onSuccess) {
|
||||
try {
|
||||
await action()
|
||||
|
||||
if (onSuccess) {
|
||||
await onSuccess()
|
||||
}
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
if (error && error.silent) {
|
||||
return false
|
||||
}
|
||||
|
||||
console.error(error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@ async function getDNS(domain){
|
||||
|
||||
try{
|
||||
|
||||
const res = await api(API + "/dns/" + domain)
|
||||
const res = await api(API + "/dns/" + domain, {}, { showError: false })
|
||||
const ips = res.ips || []
|
||||
|
||||
dnsCache[domain] = {
|
||||
@ -47,6 +47,7 @@ window.loadDomains = async function(){
|
||||
|
||||
const domains = await api(API + "/domains")
|
||||
const subs = await api(API + "/subdomains")
|
||||
window.domainList = domains
|
||||
|
||||
const table = document.getElementById("domains")
|
||||
table.innerHTML = ""
|
||||
@ -208,28 +209,34 @@ window.saveDomain = async function(){
|
||||
notes: domain_notes.value
|
||||
}
|
||||
|
||||
if(id){
|
||||
await runAction(
|
||||
async () => {
|
||||
if(id){
|
||||
|
||||
await api(API+"/domains/"+id,{
|
||||
method:"PUT",
|
||||
headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify(data)
|
||||
})
|
||||
await api(API+"/domains/"+id,{
|
||||
method:"PUT",
|
||||
headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify(data)
|
||||
})
|
||||
|
||||
}else{
|
||||
}else{
|
||||
|
||||
await api(API+"/domains",{
|
||||
method:"POST",
|
||||
headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify(data)
|
||||
})
|
||||
await api(API+"/domains",{
|
||||
method:"POST",
|
||||
headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify(data)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
closeDomainModal()
|
||||
loadDomains()
|
||||
loadMapping()
|
||||
loadCosts()
|
||||
}
|
||||
},
|
||||
async () => {
|
||||
closeDomainModal()
|
||||
await loadDomains()
|
||||
await loadMapping()
|
||||
loadCosts()
|
||||
loadInfrastructure()
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@ -238,29 +245,36 @@ window.deleteDomain = async function(id){
|
||||
|
||||
if(!confirm("Domain löschen?")) return
|
||||
|
||||
await api(API + "/domains/" + id, {
|
||||
method: "DELETE"
|
||||
})
|
||||
|
||||
loadDomains()
|
||||
await runAction(
|
||||
async () => {
|
||||
await api(API + "/domains/" + id, {
|
||||
method: "DELETE"
|
||||
})
|
||||
},
|
||||
async () => {
|
||||
await loadDomains()
|
||||
await loadMapping()
|
||||
loadCosts()
|
||||
loadInfrastructure()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
window.moveDomain = async function(id, direction){
|
||||
|
||||
try{
|
||||
|
||||
await api(API + "/domains/" + id + "/move", {
|
||||
method: "POST",
|
||||
headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ direction })
|
||||
})
|
||||
|
||||
loadDomains()
|
||||
loadMapping()
|
||||
|
||||
}catch(e){
|
||||
console.log(e)
|
||||
}
|
||||
await runAction(
|
||||
async () => {
|
||||
await api(API + "/domains/" + id + "/move", {
|
||||
method: "POST",
|
||||
headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ direction })
|
||||
})
|
||||
},
|
||||
async () => {
|
||||
await loadDomains()
|
||||
await loadMapping()
|
||||
loadInfrastructure()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -277,26 +291,33 @@ window.saveSubdomain = async function(){
|
||||
|
||||
const data = { domain_id, subdomain, ip_address }
|
||||
|
||||
if(id){
|
||||
await runAction(
|
||||
async () => {
|
||||
if(id){
|
||||
|
||||
await api(API+"/subdomains/"+id,{
|
||||
method:"PUT",
|
||||
headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify(data)
|
||||
})
|
||||
await api(API+"/subdomains/"+id,{
|
||||
method:"PUT",
|
||||
headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify(data)
|
||||
})
|
||||
|
||||
}else{
|
||||
}else{
|
||||
|
||||
await api(API+"/subdomains",{
|
||||
method:"POST",
|
||||
headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify(data)
|
||||
})
|
||||
await api(API+"/subdomains",{
|
||||
method:"POST",
|
||||
headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify(data)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
closeSubModal()
|
||||
loadDomains()
|
||||
}
|
||||
},
|
||||
async () => {
|
||||
closeSubModal()
|
||||
await loadDomains()
|
||||
await loadMapping()
|
||||
loadInfrastructure()
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@ -305,11 +326,18 @@ window.deleteSub = async function(id){
|
||||
|
||||
if(!confirm("Subdomain löschen?")) return
|
||||
|
||||
await api(API + "/subdomains/" + id, {
|
||||
method: "DELETE"
|
||||
})
|
||||
|
||||
loadDomains()
|
||||
await runAction(
|
||||
async () => {
|
||||
await api(API + "/subdomains/" + id, {
|
||||
method: "DELETE"
|
||||
})
|
||||
},
|
||||
async () => {
|
||||
await loadDomains()
|
||||
await loadMapping()
|
||||
loadInfrastructure()
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -126,6 +126,7 @@ document.getElementById("serverDetailModal").style.display="none"
|
||||
window.openDomainCreate = function(){
|
||||
|
||||
document.getElementById("domainModalTitle").innerText = "Create Domain"
|
||||
document.getElementById("domainModalMeta").innerText = "Neue Domain anlegen"
|
||||
|
||||
document.getElementById("domain_id").value = ""
|
||||
document.getElementById("domain_name").value = ""
|
||||
@ -133,8 +134,10 @@ window.openDomainCreate = function(){
|
||||
document.getElementById("domain_ip").value = ""
|
||||
document.getElementById("domain_cost").value = ""
|
||||
document.getElementById("domain_notes").value = ""
|
||||
document.getElementById("domainSaveBtn").innerText = "Create"
|
||||
|
||||
openModal("domainModal")
|
||||
document.getElementById("domain_name").focus()
|
||||
}
|
||||
|
||||
|
||||
@ -142,6 +145,7 @@ window.openDomainCreate = function(){
|
||||
window.openDomainEdit = function(d){
|
||||
|
||||
document.getElementById("domainModalTitle").innerText="Edit Domain"
|
||||
document.getElementById("domainModalMeta").innerText=d.domain_name || ""
|
||||
|
||||
document.getElementById("domain_id").value=d.id
|
||||
|
||||
@ -150,8 +154,10 @@ 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("domainSaveBtn").innerText = "Update"
|
||||
|
||||
document.getElementById("domainModal").style.display="block"
|
||||
document.getElementById("domain_name").focus()
|
||||
|
||||
}
|
||||
|
||||
@ -166,17 +172,23 @@ window.closeDomainModal = function(){
|
||||
========================= */
|
||||
|
||||
window.openSubCreate = function(domainId){
|
||||
const domains = window.domainList || []
|
||||
const domain = domains.find(d => d.id == domainId)
|
||||
const domainName = domain?.domain_name || "Unbekannte Domain"
|
||||
|
||||
document.getElementById("sub_id").value = "" // RESET !!
|
||||
|
||||
document.getElementById("sub_domain_id").value = domainId
|
||||
document.getElementById("sub_name").value = ""
|
||||
document.getElementById("sub_ip").value = ""
|
||||
document.getElementById("sub_ip").value = domain?.ip_address || ""
|
||||
|
||||
document.querySelector("#subdomainModal h3").innerText = "Subdomain erstellen"
|
||||
document.getElementById("subdomainModalMeta").innerText = "Fuer " + domainName
|
||||
document.getElementById("subdomainSaveBtn").innerText = "Create"
|
||||
|
||||
|
||||
document.getElementById("subdomainModal").style.display="block"
|
||||
document.getElementById("sub_name").focus()
|
||||
|
||||
}
|
||||
|
||||
@ -187,6 +199,7 @@ window.closeSubModal = function(){
|
||||
|
||||
|
||||
window.openSubEdit = function(s){
|
||||
const fullName = s.subdomain + "." + s.domain_name
|
||||
|
||||
document.getElementById("sub_id").value = s.id
|
||||
document.getElementById("sub_domain_id").value = s.domain_id
|
||||
@ -195,8 +208,11 @@ document.getElementById("sub_name").value = s.subdomain
|
||||
document.getElementById("sub_ip").value = s.ip_address
|
||||
|
||||
document.querySelector("#subdomainModal h3").innerText = "Subdomain bearbeiten"
|
||||
document.getElementById("subdomainModalMeta").innerText = fullName
|
||||
document.getElementById("subdomainSaveBtn").innerText = "Update"
|
||||
|
||||
document.getElementById("subdomainModal").style.display="block"
|
||||
document.getElementById("sub_name").focus()
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -111,20 +111,35 @@ window.saveResource = async function(){
|
||||
bemerkung: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()
|
||||
await runAction(
|
||||
async () => {
|
||||
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)})
|
||||
}
|
||||
},
|
||||
async () => {
|
||||
closeModal()
|
||||
await loadResources()
|
||||
loadInfrastructure()
|
||||
loadCosts()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
window.deleteResource = async function(id){
|
||||
if(!confirm("delete resource?")) return
|
||||
await api(API+"/resources/"+id,{method:"DELETE"})
|
||||
loadResources()
|
||||
await runAction(
|
||||
async () => {
|
||||
await api(API+"/resources/"+id,{method:"DELETE"})
|
||||
},
|
||||
async () => {
|
||||
await loadResources()
|
||||
loadInfrastructure()
|
||||
loadCosts()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
window.checkServerStatus = async function(resource){
|
||||
@ -187,46 +202,51 @@ window.saveIP = async function(){
|
||||
const type = document.getElementById("new_type").value
|
||||
const comment = document.getElementById("new_comment").value
|
||||
|
||||
if(id){
|
||||
// 🔥 UPDATE
|
||||
await api(API + "/ips/" + id, {
|
||||
method: "PUT",
|
||||
headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ip, type, comment})
|
||||
})
|
||||
await runAction(
|
||||
async () => {
|
||||
if(id){
|
||||
await api(API + "/ips/" + id, {
|
||||
method: "PUT",
|
||||
headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ip, type, comment})
|
||||
})
|
||||
|
||||
delete ipField.dataset.editId
|
||||
delete ipField.dataset.editId
|
||||
|
||||
}else{
|
||||
// 🔥 CREATE
|
||||
}else{
|
||||
try{
|
||||
const result = await api(API + "/ipcheck/" + ip, {}, { showError: false })
|
||||
|
||||
try{
|
||||
const result = await api(API + "/ipcheck/" + ip)
|
||||
|
||||
if(result.length){
|
||||
if(!confirm("IP existiert bereits. Trotzdem speichern?")){
|
||||
return
|
||||
if(result.length){
|
||||
if(!confirm("IP existiert bereits. Trotzdem speichern?")){
|
||||
const error = new Error("IP save cancelled")
|
||||
error.silent = true
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}catch(e){
|
||||
if(e.silent) throw e
|
||||
console.warn("IP check fehlgeschlagen", e)
|
||||
}
|
||||
|
||||
await api(API + "/resources/" + resourceId + "/ips", {
|
||||
method: "POST",
|
||||
headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ip, type, comment})
|
||||
})
|
||||
}
|
||||
}catch(e){
|
||||
console.log("Save IP Fehler:", e)
|
||||
},
|
||||
async () => {
|
||||
ipField.value = ""
|
||||
document.getElementById("new_type").value = ""
|
||||
document.getElementById("new_comment").value = ""
|
||||
|
||||
await loadIPs(resourceId)
|
||||
await loadResources()
|
||||
loadInfrastructure()
|
||||
cancelIPEdit()
|
||||
}
|
||||
|
||||
await api(API + "/resources/" + resourceId + "/ips", {
|
||||
method: "POST",
|
||||
headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ip, type, comment})
|
||||
})
|
||||
}
|
||||
|
||||
// Felder reset
|
||||
ipField.value = ""
|
||||
document.getElementById("new_type").value = ""
|
||||
document.getElementById("new_comment").value = ""
|
||||
|
||||
loadIPs(resourceId)
|
||||
loadResources()
|
||||
cancelIPEdit()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -234,12 +254,18 @@ window.deleteIP = async function(id, resourceId){
|
||||
|
||||
if(!confirm("IP löschen?")) return
|
||||
|
||||
await api(API + "/ips/" + id, {
|
||||
method: "DELETE"
|
||||
})
|
||||
|
||||
loadIPs(resourceId) // Modal neu laden
|
||||
loadResources() // Tabelle aktualisieren
|
||||
await runAction(
|
||||
async () => {
|
||||
await api(API + "/ips/" + id, {
|
||||
method: "DELETE"
|
||||
})
|
||||
},
|
||||
async () => {
|
||||
await loadIPs(resourceId)
|
||||
await loadResources()
|
||||
loadInfrastructure()
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@ -317,19 +343,18 @@ window.cancelIPEdit = function(){
|
||||
}
|
||||
|
||||
window.moveResource = async function(id, direction){
|
||||
|
||||
try {
|
||||
|
||||
await api(API + "/resources/" + id + "/move", {
|
||||
method: "POST",
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ direction })
|
||||
})
|
||||
|
||||
loadResources()
|
||||
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
|
||||
}
|
||||
await runAction(
|
||||
async () => {
|
||||
await api(API + "/resources/" + id + "/move", {
|
||||
method: "POST",
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ direction })
|
||||
})
|
||||
},
|
||||
async () => {
|
||||
await loadResources()
|
||||
loadInfrastructure()
|
||||
loadCosts()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user