A web panel for Joker's Go DNS server, written using Vue.js
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

247 lines
5.2 KiB

package main
import (
"encoding/json"
"flag"
"github.com/asaskevich/govalidator"
"github.com/hoisie/redis"
"github.com/julienschmidt/httprouter"
"github.com/weppos/publicsuffix-go/publicsuffix"
"log"
"net/http"
"os"
"strings"
)
var c *redis.Client
type infoResponse struct {
RedisServer string `json:"redis"`
}
var (
flagKey = flag.String("key", "godns:hosts", "Redis key for hash set")
flagListen = flag.String("listen", ":8080", "listen address")
flagServer = flag.String("server", "localhost:6379", "redis host")
filePath = flag.String("filepath", "/var/lib/joker/dist", "file path")
)
var (
key string
server string
)
func main() {
flag.Parse()
if key = os.Getenv("REDIS_KEY"); key == "" {
key = *flagKey
}
if server = os.Getenv("REDIS_ADDR"); server == "" {
server = *flagServer
}
c = &redis.Client{
Addr: server,
}
router := httprouter.New()
fs := http.FileServer(http.Dir(*filePath))
serveFiles := func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
fs.ServeHTTP(w, r)
}
router.GET("/", serveFiles)
router.GET("/js/*filepath", serveFiles)
router.GET("/css/*filepath", serveFiles)
router.GET("/info", getInfo)
router.GET("/records", getRecords)
router.GET("/records/:zone", getRecords)
router.POST("/update", updateRecord)
router.POST("/remove", removeRecord)
log.Println("Starting server on " + *flagListen)
http.ListenAndServe(*flagListen, router)
}
func getInfo(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
json.NewEncoder(w).Encode(&infoResponse{
RedisServer: server,
})
}
type tableRow struct {
Name string `json:"name"`
IP string `json:"ip"`
ItemCount int `json:"itemCount"`
}
type response struct {
Success bool `json:"success"`
Message string `json:"message"`
}
func getRecords(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
zone := p.ByName("zone")
hosts := make(map[string]string)
if err := c.Hgetall(key, hosts); err != nil {
errorResponse(w, http.StatusInternalServerError, "Unable to retrieve records: " + err.Error())
return
}
groups := make(map[string]map[string]string)
for host, addr := range hosts {
domain, err := publicsuffix.Domain(host)
if err != nil {
domain = host
}
if zone != "" && !strings.HasSuffix(host, zone) {
continue
}
if group, ok := groups[domain]; ok {
group[host] = addr
} else {
groups[domain] = map[string]string{host:addr}
}
}
out := make([]*tableRow, 0)
if zone == "" {
for domain, children := range groups {
ip := children[domain]
if len(children) > 1 {
ip = "-"
} else {
domain = mapFirst(children)
ip = children[domain]
}
out = append(out, &tableRow{
Name: domain,
IP: ip,
ItemCount: len(children),
})
}
} else {
for domain, ip := range groups[zone] {
out = append(out, &tableRow{
Name: domain,
IP: ip,
ItemCount: 1,
})
}
}
json.NewEncoder(w).Encode(out)
}
func mapFirst(m map[string]string) string {
for k := range m {
return k
}
return ""
}
type domainRequest struct {
Name string `json:"name"`
IP string `json:"ip"`
Group bool `json:"group"`
}
func updateRecord(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
if !strings.HasPrefix(r.Header.Get("Content-Type"), "application/json") {
errorResponse(w, http.StatusBadRequest, "Invalid body type.")
return
}
var req domainRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
errorResponse(w, http.StatusBadRequest, "Unable to decode body: " + err.Error())
return
}
req.Name = strings.ToLower(req.Name)
// Replace wildcards with an 'a' to test the domain
testDomain := req.Name
testDomain = strings.Replace(testDomain, "*", "a", -1)
if !govalidator.IsDNSName(testDomain) || !govalidator.IsIP(req.IP) {
errorResponse(w, http.StatusBadRequest, "Invalid domain or IP")
return
}
if _, err := c.Hset(key, req.Name, []byte(req.IP)); err != nil {
errorResponse(w, http.StatusInternalServerError, "Unable to save record: " + err.Error())
return
}
c.Publish("godns:update_record", []byte(req.Name))
json.NewEncoder(w).Encode(&response{Success: true})
}
func removeRecord(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
var req domainRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return
}
req.Name = strings.ToLower(req.Name)
if !govalidator.IsDNSName(strings.Replace(req.Name, "*", "a", -1)) {
w.WriteHeader(http.StatusBadRequest)
return
}
if req.Group {
hosts := make(map[string]string)
if err := c.Hgetall(key, hosts); err != nil {
errorResponse(w, http.StatusInternalServerError, "Unable to fetch records: " + err.Error())
return
}
for host, _ := range hosts {
host = strings.ToLower(host)
if !strings.HasSuffix(host, req.Name) {
continue
}
c.Hdel(key, host)
c.Publish("godns:remove_record", []byte(host))
}
} else {
if _, err := c.Hdel(key, req.Name); err != nil {
errorResponse(w, http.StatusInternalServerError, "Unable to delete record: " + err.Error())
return
}
c.Publish("godns:remove_record", []byte(req.Name))
}
}
func errorResponse(w http.ResponseWriter, status int, message string) {
w.WriteHeader(status)
json.NewEncoder(w).Encode(&response{Success: false, Message: message})
}