summaryrefslogtreecommitdiff
path: root/_www/zone.d/index.html
diff options
context:
space:
mode:
Diffstat (limited to '_www/zone.d/index.html')
-rw-r--r--_www/zone.d/index.html513
1 files changed, 513 insertions, 0 deletions
diff --git a/_www/zone.d/index.html b/_www/zone.d/index.html
new file mode 100644
index 0000000..77bfb8c
--- /dev/null
+++ b/_www/zone.d/index.html
@@ -0,0 +1,513 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
+ <link rel="icon" type="image/png" href="/favicon.png" />
+ <link rel="stylesheet" href="/index.css" />
+ <title>rescached | zone.d</title>
+
+ <style>
+ h4 {
+ border-bottom: 1px solid silver;
+ }
+ .nav-left {
+ padding: 0;
+ width: 13em;
+ float: left;
+ }
+ .nav-left .item {
+ margin: 1em 0;
+ cursor: pointer;
+ color: rgb(0, 100, 200);
+ }
+ .nav-left > input {
+ width: 12em;
+ }
+ .content {
+ float: left;
+ width: calc(100% - 14em);
+ }
+ .action-delete {
+ margin-left: 1em;
+ }
+ .actions {
+ padding: 1em;
+ }
+ .actions button {
+ width: 100%;
+ }
+
+ .rr_form {
+ margin: 1em 0px;
+ padding: 10px 10px 0px 10px;
+ border: 1px solid silver;
+ }
+ .rr_form > * > .input > input.name {
+ width: auto;
+ }
+
+ .rr {
+ font-family: monospace;
+ width: 100%;
+ padding: 1em 0px;
+ }
+ .rr.header {
+ font-weight: bold;
+ }
+ .rr > .name {
+ width: 20em;
+ display: inline-block;
+ word-wrap: break-word;
+ }
+ .rr > .type {
+ width: 4em;
+ display: inline-block;
+ }
+ .rr > .ttl {
+ width: 6em;
+ display: inline-block;
+ }
+ .rr > .value {
+ display: inline-block;
+ word-wrap: break-word;
+ }
+ </style>
+ </head>
+ <body onload="main()">
+ <nav class="menu">
+ <a href="/"> rescached </a>
+ /
+ <a href="/environment/"> Environment </a>
+ /
+ <a href="/hosts_blocks/"> Hosts blocks </a>
+ /
+ <a href="/hosts.d/"> hosts.d </a>
+ /
+ <a href="/zone.d/" class="active"> zone.d </a>
+ </nav>
+ <div id="notif"></div>
+
+ <div class="nav-left">
+ <h3>Zone files</h3>
+ <div id="ZoneFiles"></div>
+
+ <label for="newZoneFile"> New zone file: </label>
+ <input id="newZoneFile" />
+ <button onclick="createZoneFile()">Create</button>
+ </div>
+
+ <div class="content">
+ <div id="activeZone"></div>
+
+ <div id="activeZone_soa" style="display: none">
+ <h4>SOA record</h4>
+
+ <div class="input">
+ <label for="soa_mname"> Name server </label>
+ <input id="soa_mname" oninput="updateSOA('MName', this.value)" />
+ <span class="input-info-toggler" onclick="toggleInfo('soa_mname_info')">?</span>
+ <div id="soa_mname_info" class="input-info" style="display: none">
+ The domain-name of the name server that was the original or primary source
+ of data for this zone. It should be domain-name where the rescached run.
+ </div>
+ </div>
+
+ <div class="input">
+ <label for="soa_rname">Admin email</label>
+ <input id="soa_rname" oninput="updateSOA('RName', this.value)" />
+ <span class="input-info-toggler" onclick="toggleInfo('soa_rname_info')">?</span>
+ <div id="soa_rname_info" class="input-info" style="display: none">
+ Email address of the administrator responsible for this zone. The "@" on
+ email address is replaced with dot, and if there is a dot before "@" it
+ should be escaped with "\". For example, "dns.admin@domain.tld" would be
+ written as "dns\.admin.domain.tld".'
+ </div>
+ </div>
+
+ <div class="input">
+ <label for="soa_serial">Serial</label>
+ <input
+ id="soa_serial"
+ type="number"
+ min="0"
+ oninput="updateSOA('Serial', parseInt(this.value))"
+ />
+ <span class="input-info-toggler" onclick="toggleInfo('soa_serial_info')">?</span>
+ <div id="soa_serial_info" class="input-info" style="display: none">
+ Serial number for this zone. If a secondary name server observes an increase
+ in this number, the server will assume that the zone has been updated and
+ initiate a zone transfer.
+ </div>
+ </div>
+
+ <div class="input">
+ <label for="soa_refresh">Refresh</label>
+ <input
+ id="soa_refresh"
+ type="number"
+ min="0"
+ oninput="updateSOA('Refresh', parseInt(this.value))"
+ />
+ <span class="input-info-toggler" onclick="toggleInfo('soa_refresh_info')">?</span>
+ <div id="soa_refresh_info" class="input-info" style="display: none">
+ Number of seconds after which secondary name servers should query the zone
+ for the SOA record, to detect zone changes. Recommendation for small and
+ stable zones is 86400 seconds (24 hours).
+ </div>
+ </div>
+
+ <div class="input">
+ <label for="soa_retry">Retry</label>
+ <input
+ id="soa_retry"
+ type="number"
+ min="0"
+ oninput="updateSOA('Retry', parseInt(this.value))"
+ />
+ <span class="input-info-toggler" onclick="toggleInfo('soa_retry_info')">?</span>
+ <div id="soa_retry_info" class="input-info" style="display: none">
+ Number of seconds after which secondary name servers should retry to request
+ the serial number from the zone if the zone does not respond. It must be
+ less than Refresh. Recommendation for small and stable zones is 7200 seconds
+ (2 hours).
+ </div>
+ </div>
+
+ <div class="input">
+ <label for="soa_expire">Expire</label>
+ <input
+ id="soa_expire"
+ type="number"
+ min="0"
+ oninput="updateSOA('Expire', parseInt(this.value))"
+ />
+ <span class="input-info-toggler" onclick="toggleInfo('soa_expire_info')">?</span>
+ <div class="soa_expire_info" class="input-info" style="display: none">
+ Number of seconds after which secondary name servers should stop answering
+ request for this zone if the zone does not respond. This value must be
+ bigger than the sum of Refresh and Retry. Recommendation for small and
+ stable zones is 3600000 seconds (1000 hours).
+ </div>
+ </div>
+
+ <div class="input">
+ <label for="soa_minimum"> Minimum </label>
+ <input
+ id="soa_minimum"
+ type="number"
+ min="0"
+ oninput="updateSOA('Minimum', parseInt(this.value))"
+ />
+ <span class="input-info-toggler" onclick="toggleInfo('soa_expire_info')">?</span>
+ <div id="soa_minimum_info" class="input-info" style="display: none">
+ Time to live for purposes of negative caching. Recommendation for small and
+ stable zones is 1800 seconds (30 min).
+ </div>
+ </div>
+
+ <div class="actions">
+ <button onclick="saveSOA()">Save</button>
+ </div>
+ </div>
+
+ <div id="activeZone_records" style="display: none">
+ <h4>List records</h4>
+ <div class="rr header">
+ <span class="name"> Name </span>
+ <span class="type"> Type </span>
+ <span class="value"> Value </span>
+ </div>
+ <div id="list_records"></div>
+ </div>
+
+ <div id="activeZone_form" class="rr_form" style="display: none">
+ <div class="input">
+ <label for="rr_type"> Type: </label>
+ <select id="rr_type" oninput="onSelectRRType(this.value)">
+ <option value="1">A</option>
+ <option value="2">NS</option>
+ <option value="5">CNAME</option>
+ <option value="12">PTR</option>
+ <option value="15">MX</option>
+ <option value="16">TXT</option>
+ <option value="28">AAAA</option>
+ </select>
+ </div>
+
+ <div id="activeZone_form_default">
+ <div class="input">
+ <label> Name: </label>
+ <input class="name" oninput="updateNewRR('Name', this.value)" />
+ <span></span>
+ </div>
+ <div class="input">
+ <label> Value: </label>
+ <input oninput="updateNewRR('Value', this.value)" />
+ </div>
+ </div>
+
+ <div id="activeZone_form_ptr" style="display: none">
+ <div class="input">
+ <label> Name: </label>
+ <input oninput="updateNewRR('Name', this.value)" />
+ </div>
+ <div class="input">
+ <label> Value: </label>
+ <input class="name" oninput="updateNewRR('Value', this.value)" />
+ <span></span>
+ </div>
+ </div>
+
+ <div id="activeZone_form_mx" style="display: none">
+ <div class="input">
+ <label> Name: </label>
+ <input class="name" oninput="updateNewRR('Name', this.value)" />
+ <span></span>
+ </div>
+ <div class="input">
+ <label> Preference: </label>
+ <input
+ type="number"
+ min="1"
+ max="65535"
+ oninput="updateNewRR('Value.Preference', this.value)"
+ />
+ </div>
+ <div class="input">
+ <label> Exchange: </label>
+ <input oninput="updateNewRR('Value.Exchange', this.value)" />
+ </div>
+ </div>
+
+ <div class="actions">
+ <button class="create" onclick="createRR()">Create</button>
+ </div>
+ </div>
+ </div>
+
+ <script src="/index.js"></script>
+ <script src="/rescached.js"></script>
+ <script>
+ let resc = null
+ let activeZone = null
+ let newRR = {
+ Name: "",
+ Value: "",
+ }
+
+ async function main() {
+ resc = new Rescached("")
+
+ let res = await resc.getEnvironment()
+ if (res.code != 200) {
+ notifError(res.message)
+ return
+ }
+ renderZoneFiles(resc.env.ZoneFiles)
+ resetActiveZone()
+ }
+
+ async function createZoneFile() {
+ let name = document.getElementById("newZoneFile").value
+ if (name === "") {
+ notifError("The zone file name must not be empty")
+ return
+ }
+ let res = await resc.ZoneFileCreate(name)
+ if (res.code != 200) {
+ notifError(res.message)
+ return
+ }
+ renderZoneFiles(resc.env.ZoneFiles)
+ }
+
+ async function deleteZoneFile() {
+ let res = await resc.ZoneFileDelete(activeZone.Name)
+ if (res.code != 200) {
+ notifError(res.message)
+ return
+ }
+ renderZoneFiles(resc.env.ZoneFiles)
+ resetActiveZone()
+ notifInfo(res.message)
+ }
+
+ async function createRR() {
+ let res = await resc.ZoneFileRecordCreate(activeZone.Name, newRR)
+ if (res.code != 200) {
+ notifError(res.message)
+ return
+ }
+ activeZone.Records = res.data
+ renderActiveZoneRecords()
+ notifInfo(res.message)
+ }
+
+ async function deleteRR(name, idx) {
+ let rr = activeZone.Records[name][idx]
+ console.log("deleteRR: ", rr)
+ let res = await resc.ZoneFileRecordDelete(activeZone.Name, rr)
+ if (res.code != 200) {
+ notifError(res.message)
+ return
+ }
+ activeZone.Records = res.data
+ renderActiveZoneRecords()
+ notifInfo(res.message)
+ }
+
+ function onSelectRRType(v) {
+ let formDefault = document.getElementById("activeZone_form_default")
+ let formPTR = document.getElementById("activeZone_form_ptr")
+ let formMX = document.getElementById("activeZone_form_mx")
+
+ newRR.Type = v
+ newRR.Value = ""
+
+ if (v == 12) {
+ // PTR
+ formDefault.style.display = "none"
+ formPTR.style.display = "block"
+ formPTR.children[1].children[2].innerText = "." + activeZone.Name
+ formMX.style.display = "none"
+ } else if (v == 15) {
+ formDefault.style.display = "none"
+ formPTR.style.display = "none"
+ formMX.style.display = "block"
+ formMX.children[0].children[2].innerText = "." + activeZone.Name
+ newRR.Value = {
+ Name: "",
+ Exchange: "",
+ Preference: 0,
+ }
+ } else {
+ formDefault.style.display = "block"
+ formDefault.children[0].children[2].innerText = "." + activeZone.Name
+ formPTR.style.display = "none"
+ formMX.style.display = "none"
+ }
+ }
+
+ function renderZoneFiles(zoneFiles) {
+ let wrapper = document.getElementById("ZoneFiles")
+ out = ""
+ for (let name in zoneFiles) {
+ if (!zoneFiles.hasOwnProperty(name)) {
+ continue
+ }
+ let zoneFile = zoneFiles[name]
+ out += `
+ <div class="item">
+ <span onclick="setActiveZone('${zoneFile.Name}')">
+ ${zoneFile.Name}
+ </span>
+ </div>`
+ }
+ wrapper.innerHTML = out
+ }
+
+ function renderActiveZone() {
+ let w = document.getElementById("activeZone")
+ w.innerHTML = `
+ <h3>
+ ${activeZone.Name}
+ <button
+ class="action-delete"
+ onclick="deleteZoneFile()"
+ >
+ Delete
+ </button>
+ </h3>
+ `
+ }
+
+ function renderActiveZoneSOA() {
+ const w = document.getElementById("activeZone_soa")
+ w.style.display = "block"
+ const soa = activeZone.SOA.Value
+ document.getElementById("soa_mname").value = soa.MName
+ document.getElementById("soa_rname").value = soa.RName
+ document.getElementById("soa_serial").value = soa.Serial
+ document.getElementById("soa_refresh").value = soa.Refresh
+ document.getElementById("soa_retry").value = soa.Retry
+ document.getElementById("soa_expire").value = soa.Expire
+ document.getElementById("soa_minimum").value = soa.Minimum
+ }
+
+ function renderActiveZoneRecords() {
+ let el = document.getElementById("activeZone_records")
+ el.style.display = "block"
+ let w = document.getElementById("list_records")
+ out = ""
+ for (const [name, listRR] of Object.entries(activeZone.Records)) {
+ listRR.forEach((rr, idx) => {
+ out += `
+ <div class="rr">
+ <span class="name">
+ ${rr.Name}
+ </span>
+ <span class="type">
+ ${resc.GetRRTypeName(rr.Type)}
+ </span>
+ <span class="value">
+ ${rr.Value}
+ </span>
+ <button onclick="deleteRR('${rr.Name}', ${idx})">
+ X
+ </button>
+ </div>
+ `
+ })
+ }
+ w.innerHTML = out
+ }
+
+ function renderActiveZoneForm() {
+ let form = document.getElementById("activeZone_form")
+ form.style.display = "block"
+ document.getElementById("rr_type").value = 1
+ onSelectRRType(1)
+ }
+
+ function resetActiveZone() {
+ document.getElementById("activeZone").innerHTML = `
+ <p>Select one of the zone file to manage.</p>
+ `
+ document.getElementById("activeZone_soa").style.display = "none"
+ document.getElementById("activeZone_records").style.display = "none"
+ document.getElementById("activeZone_form").style.display = "none"
+ activeZone = null
+ }
+
+ async function saveSOA() {
+ console.log("saveSOA: ", activeZone.SOA.Value)
+ let rr = activeZone.SOA
+ rr.Type = 6
+ let res = await resc.ZoneFileRecordCreate(activeZone.Name, rr)
+ if (res.code != 200) {
+ notifError(res.message)
+ return
+ }
+ notifInfo(res.message)
+ }
+
+ function setActiveZone(name) {
+ activeZone = resc.env.ZoneFiles[name]
+ console.log("setActiveZone: ", activeZone)
+ renderActiveZone()
+ renderActiveZoneSOA()
+ renderActiveZoneRecords()
+ renderActiveZoneForm()
+ }
+
+ function updateNewRR(k, v) {
+ newRR[k] = v
+ }
+
+ function updateSOA(k, v) {
+ activeZone.SOA.Value[k] = v
+ }
+ </script>
+ </body>
+</html>