add cname files

This commit is contained in:
Ben 2021-07-28 22:03:37 +02:00
parent dbc137366e
commit 711f80b3a2
4 changed files with 207 additions and 0 deletions

120
dyndns/handler/cname.go Normal file
View file

@ -0,0 +1,120 @@
package handler
import (
"fmt"
"net/http"
"strconv"
"github.com/benjaminbear/docker-ddns-server/dyndns/model"
"github.com/benjaminbear/docker-ddns-server/dyndns/nswrapper"
"github.com/jinzhu/gorm"
"github.com/labstack/echo/v4"
)
// ListCNames fetches all cnames from database and lists them on the website.
func (h *Handler) ListCNames(c echo.Context) (err error) {
if !h.AuthAdmin {
return c.JSON(http.StatusUnauthorized, &Error{UNAUTHORIZED})
}
cnames := new([]model.CName)
if err = h.DB.Preload("Target").Find(cnames).Error; err != nil {
return c.JSON(http.StatusBadRequest, &Error{err.Error()})
}
return c.Render(http.StatusOK, "listcnames", echo.Map{
"cnames": cnames,
})
}
// AddCName just renders the "add cname" website.
// Therefore all host entries from the database are being fetched.
func (h *Handler) AddCName(c echo.Context) (err error) {
if !h.AuthAdmin {
return c.JSON(http.StatusUnauthorized, &Error{UNAUTHORIZED})
}
hosts := new([]model.Host)
if err = h.DB.Find(hosts).Error; err != nil {
return c.JSON(http.StatusBadRequest, &Error{err.Error()})
}
return c.Render(http.StatusOK, "addcname", echo.Map{
"config": h.Config,
"hosts": hosts,
})
}
// CreateCName validates the cname data from the "add cname" website,
// adds the cname entry to the database,
// and adds the entry to the DNS server.
func (h *Handler) CreateCName(c echo.Context) (err error) {
if !h.AuthAdmin {
return c.JSON(http.StatusUnauthorized, &Error{UNAUTHORIZED})
}
cname := &model.CName{}
if err = c.Bind(cname); err != nil {
return c.JSON(http.StatusBadRequest, &Error{err.Error()})
}
host := &model.Host{}
if err = h.DB.First(host, c.FormValue("target_id")).Error; err != nil {
return c.JSON(http.StatusBadRequest, &Error{err.Error()})
}
cname.Target = *host
if err = c.Validate(cname); err != nil {
return c.JSON(http.StatusBadRequest, &Error{err.Error()})
}
if err = h.checkUniqueHostname(cname.Hostname, cname.Target.Domain); err != nil {
return c.JSON(http.StatusBadRequest, &Error{err.Error()})
}
if err = h.DB.Create(cname).Error; err != nil {
return c.JSON(http.StatusBadRequest, &Error{err.Error()})
}
if err = nswrapper.UpdateRecord(cname.Hostname, fmt.Sprintf("%s.%s", cname.Target.Hostname, cname.Target.Domain), "CNAME", cname.Target.Domain, cname.Ttl); err != nil {
return c.JSON(http.StatusBadRequest, &Error{err.Error()})
}
return c.JSON(http.StatusOK, cname)
}
// DeleteCName fetches a cname entry from the database by "id"
// and deletes the database and DNS server entry to it.
func (h *Handler) DeleteCName(c echo.Context) (err error) {
if !h.AuthAdmin {
return c.JSON(http.StatusUnauthorized, &Error{UNAUTHORIZED})
}
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
return c.JSON(http.StatusBadRequest, &Error{err.Error()})
}
cname := &model.CName{}
if err = h.DB.First(cname, id).Error; err != nil {
return c.JSON(http.StatusBadRequest, &Error{err.Error()})
}
err = h.DB.Transaction(func(tx *gorm.DB) error {
if err = tx.Unscoped().Delete(cname).Error; err != nil {
return c.JSON(http.StatusBadRequest, &Error{err.Error()})
}
return nil
})
if err != nil {
return c.JSON(http.StatusBadRequest, &Error{err.Error()})
}
if err = nswrapper.DeleteRecord(cname.Hostname, cname.Target.Domain); err != nil {
return c.JSON(http.StatusBadRequest, &Error{err.Error()})
}
return c.JSON(http.StatusOK, id)
}

14
dyndns/model/cname.go Normal file
View file

@ -0,0 +1,14 @@
package model
import (
"github.com/jinzhu/gorm"
)
// CName is a dns cname entry.
type CName struct {
gorm.Model
Hostname string `gorm:"not null" form:"hostname" validate:"required,hostname"`
Target Host `validate:"required,hostname"`
TargetID uint
Ttl int `form:"ttl" validate:"required,min=20,max=86400"`
}

View file

@ -0,0 +1,48 @@
{{define "content"}}
<div class="p-4" style="background-color: #e9ecef">
<h3 class="text-center mb-4">Add CName Entry</h3>
<form id="editHostForm" action="javascript:void(0);">
<div class="row mt-3">
<div class="col-1"></div>
<div class="col-2 text-right">Hostname:</div>
<div class="col-8 input-group">
<input type="text" class="form-control" placeholder="Enter hostname" name="hostname">
<div class="input-group-append">
<input type="text" class="form-control" placeholder="Select target.." id="domain_mirror" readonly>
</div>
</div>
<div class="col-1"></div>
</div>
<div class="row mt-3">
<div class="col-1"></div>
<div class="col-2 text-right">Target:</div>
<div class="col-8">
<select class="custom-select" name="target_id" id="target_id" onchange="newTargetSelected()">
<option selected>Choose...</option>
{{range $host := .hosts}}
<a class="dropdown-item"><option label="{{$host.Hostname}}.{{$host.Domain}}" value="{{$host.ID}}">{{$host.Hostname}}</option></a>
{{end}}
</select>
</div>
<div class="col-1"></div>
</div>
<div class="row mt-3">
<div class="col-1"></div>
<div class="col-2 text-right">TTL:</div>
<div class="col-8">
<select class="form-control" name="ttl">
<option value="20" {{if .cname.Ttl }}{{if eq .cname.Ttl 20 }}selected{{end}}{{end}}>20 s. Super dynamic DNS for frequent updates</option>
<option value="60" {{if .cname.Ttl }}{{if eq .cname.Ttl 60 }}selected{{end}}{{end}}>60 s. Default dynamic DNS value</option>
<option value="3600" {{if .cname.Ttl }}{{if eq .cname.Ttl 3600 }}selected{{end}}{{end}}>1 hr. Rarely updated IP address</option>
<option value="14400" {{if .cname.Ttl }}{{if eq .cname.Ttl 14400 }}selected{{end}}{{end}}>4 hrs. Static record with benefits of DNS caching</option>
</select>
</div>
<div class="col-1"></div>
</div>
<div class="row mt-3">
<div class="col-11 d-flex justify-content-end"><button id="{{.cname.ID}}" class="add cname btn btn-primary">Add CName Entry</button></div>
<div class="col-1"></div>
</div>
</form>
</div>
{{end}}

View file

@ -0,0 +1,25 @@
{{define "content"}}
<div class="container marketing">
<h3 class="text-center mb-4">DNS CName Entries</h3>
<table class="table table-striped text-center">
<thead>
<tr>
<th>Hostname</th>
<th>Target</th>
<th>TTL</th>
<th><button class="addCName btn btn-primary">Add CName Entry</button></th>
</tr>
</thead>
<tbody>
{{range .cnames}}
<tr>
<td>{{.Hostname}}.{{.Target.Domain}}</td>
<td>{{.Target.Hostname}}.{{.Target.Domain}}</td>
<td>{{.Ttl}}</td>
<td><button id="{{.ID}}" class="deleteCName btn btn-outline-secondary btn-sm"><img src="/static/icons/trash.svg" alt="" width="16" height="16" title="Delete"></button></td>
</tr>
{{end}}
</tbody>
</table>
</div>
{{end}}