From 6e0ce47f91e872ad60fe398fb99276a20b160c72 Mon Sep 17 00:00:00 2001 From: Karan Sharma Date: Tue, 2 Mar 2021 18:21:59 +0530 Subject: [PATCH] feat: UI design --- cmd/doggo/api/api.go | 34 +++++- cmd/doggo/api/assets/main.js | 92 ++++++++++++++++ cmd/doggo/api/assets/style.css | 0 cmd/doggo/api/handlers.go | 18 ++- cmd/doggo/api/index.html | 195 +++++++++++++++++++++++++++++++++ pkg/models/models.go | 2 +- 6 files changed, 331 insertions(+), 10 deletions(-) create mode 100644 cmd/doggo/api/assets/main.js create mode 100644 cmd/doggo/api/assets/style.css create mode 100644 cmd/doggo/api/index.html diff --git a/cmd/doggo/api/api.go b/cmd/doggo/api/api.go index e8f184f..43b42d9 100644 --- a/cmd/doggo/api/api.go +++ b/cmd/doggo/api/api.go @@ -1,6 +1,8 @@ package main import ( + "embed" + "io/fs" "net/http" "time" @@ -9,6 +11,7 @@ import ( "github.com/sirupsen/logrus" "github.com/go-chi/chi" + "github.com/go-chi/chi/middleware" "github.com/knadh/koanf" ) @@ -18,6 +21,10 @@ var ( // Version and date of the build. This is injected at build-time. buildVersion = "unknown" buildDate = "unknown" + //go:embed assets/* + assetsDir embed.FS + //go:embed index.html + html []byte ) func main() { @@ -26,11 +33,30 @@ func main() { // Initialize app. app := app.New(logger, buildVersion) - // Register handles. + // Register router instance. r := chi.NewRouter() - r.Get("/", wrap(app, handleIndex)) - r.Get("/ping/", wrap(app, handleHealthCheck)) - r.Post("/lookup/", wrap(app, handleLookup)) + + // Register middlewares + r.Use(middleware.RequestID) + r.Use(middleware.RealIP) + r.Use(middleware.Logger) + r.Use(middleware.Recoverer) + + // Frontend Handlers. + assets, _ := fs.Sub(assetsDir, "assets") + r.Get("/assets/*", func(w http.ResponseWriter, r *http.Request) { + fs := http.StripPrefix("/assets/", http.FileServer(http.FS(assets))) + fs.ServeHTTP(w, r) + }) + r.Get("/", func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("Content-Type", "text/html") + w.Write(html) + }) + + // API Handlers. + r.Get("/api/", wrap(app, handleIndexAPI)) + r.Get("/api/ping/", wrap(app, handleHealthCheck)) + r.Post("/api/lookup/", wrap(app, handleLookup)) // HTTP Server. srv := &http.Server{ diff --git a/cmd/doggo/api/assets/main.js b/cmd/doggo/api/assets/main.js new file mode 100644 index 0000000..802c542 --- /dev/null +++ b/cmd/doggo/api/assets/main.js @@ -0,0 +1,92 @@ +var app = new Vue({ + el: '#app', + data: { + apiURL: "/api/lookup/", + results: [], + noRecordsFound: false, + emptyNameError: false, + apiErrorMessage: "", + queryName: "", + queryType: "A", + nameserverName: "google", + customNSAddr: "", + nsAddrMap: { + "google": "8.8.8.8", + "cloudflare": "1.1.1.1", + "quad9": "9.9.9.9", + } + }, + created: function () { + }, + computed: { + getNSAddrValue() { + return this.nsAddrMap[this.nameserverName] + }, + isCustomNS() { + if (this.nameserverName == "custom") { + return true + } + return false + } + }, + methods: { + prepareNS() { + switch (this.nameserverName) { + case "google": + return "tcp://8.8.8.8:53" + case "cloudflare": + return "tcp://1.1.1.1:53" + case "quad9": + return "tcp://9.9.9.9:53" + case "custom": + return this.customNSAddr + default: + return "" + } + }, + lookupRecords() { + // reset variables. + this.results = [] + this.noRecordsFound = false + this.emptyNameError = false + this.apiErrorMessage = "" + + if (this.queryName == "") { + this.emptyNameError = true + return + } + + // GET request using fetch with error handling + fetch(this.apiURL, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + query: [this.queryName,], + type: [this.queryType,], + nameservers: [this.prepareNS(),], + }), + }).then(async response => { + const res = await response.json(); + + // check for error response + if (!response.ok) { + // get error message from body or default to response statusText + const error = (res && res.message) || response.statusText; + return Promise.reject(error); + } + + if (res.data[0].answers == null) { + this.noRecordsFound = true + } else { + // Set the answers in the results list. + this.results = res.data[0].answers + } + + }).catch(error => { + this.apiErrorMessage = error + }); + } + } +}) diff --git a/cmd/doggo/api/assets/style.css b/cmd/doggo/api/assets/style.css new file mode 100644 index 0000000..e69de29 diff --git a/cmd/doggo/api/handlers.go b/cmd/doggo/api/handlers.go index d7ba361..c9488c9 100644 --- a/cmd/doggo/api/handlers.go +++ b/cmd/doggo/api/handlers.go @@ -19,7 +19,7 @@ type httpResp struct { Data interface{} `json:"data,omitempty"` } -func handleIndex(w http.ResponseWriter, r *http.Request) { +func handleIndexAPI(w http.ResponseWriter, r *http.Request) { sendResponse(w, http.StatusOK, "Welcome to Doggo API.") return } @@ -49,6 +49,7 @@ func handleLookup(w http.ResponseWriter, r *http.Request) { sendErrorResponse(w, fmt.Sprintf("Invalid JSON payload"), http.StatusBadRequest, nil) return } + app.QueryFlags = qFlags // Load fallbacks. app.LoadFallbacks() @@ -56,11 +57,17 @@ func handleLookup(w http.ResponseWriter, r *http.Request) { // Load Questions. app.PrepareQuestions() + if len(app.Questions) == 0 { + sendErrorResponse(w, fmt.Sprintf("Missing field `query`."), http.StatusBadRequest, nil) + return + } + // Load Nameservers. err = app.LoadNameservers() if err != nil { app.Logger.WithError(err).Error("error loading nameservers") - sendErrorResponse(w, fmt.Sprintf("Error lookup up for records"), http.StatusInternalServerError, nil) + sendErrorResponse(w, fmt.Sprintf("Error lookuping up for records."), http.StatusInternalServerError, nil) + return } // Load Resolvers. @@ -75,8 +82,8 @@ func handleLookup(w http.ResponseWriter, r *http.Request) { }) if err != nil { app.Logger.WithError(err).Error("error loading resolver") - sendErrorResponse(w, fmt.Sprintf("Error lookup up for records"), http.StatusInternalServerError, nil) - + sendErrorResponse(w, fmt.Sprintf("Error lookuping up for records."), http.StatusInternalServerError, nil) + return } app.Resolvers = rslvrs @@ -86,7 +93,8 @@ func handleLookup(w http.ResponseWriter, r *http.Request) { resp, err := rslv.Lookup(q) if err != nil { app.Logger.WithError(err).Error("error looking up DNS records") - app.Logger.Exit(2) + sendErrorResponse(w, fmt.Sprintf("Error lookuping up for records."), http.StatusInternalServerError, nil) + return } responses = append(responses, resp) } diff --git a/cmd/doggo/api/index.html b/cmd/doggo/api/index.html new file mode 100644 index 0000000..2929586 --- /dev/null +++ b/cmd/doggo/api/index.html @@ -0,0 +1,195 @@ + + + + + Doggo DNS + + + + + + + + + + + +
+

Doggo DNS

+
+
+
+
+
+
+ + +

Please enter a domain name to + query. +

+
+
+ + +
+
+
+
+ + +
+
+ +
+ +

To use different protocols like DOH, DOT etc. refer + to the + instructions here.

+
+
+ +
+
+
+
+ +
+
+
+ + +
+ + +
    +
  • Name: {{answer.name}}
  • +
  • Address: {{answer.address}}
  • +
  • Type: {{answer.type}}
  • +
  • Nameserver: {{answer.nameserver}}
  • +
  • TTL: {{answer.ttl}}
  • +
  • RTT: {{answer.rtt}}
  • +
+
+ + + + +
+

Oops! Found no records for this query.

+
+ + +
+

{{apiErrorMessage}}

+
+ +
+ + + \ No newline at end of file diff --git a/pkg/models/models.go b/pkg/models/models.go index aa4dc1e..a361b79 100644 --- a/pkg/models/models.go +++ b/pkg/models/models.go @@ -25,8 +25,8 @@ type QueryFlags struct { UseIPv4 bool `koanf:"ipv4" json:"ipv4"` UseIPv6 bool `koanf:"ipv6" json:"ipv6"` Ndots int `koanf:"ndots" json:"ndots"` - Color bool `koanf:"color" json:"color"` Timeout time.Duration `koanf:"timeout" json:"timeout"` + Color bool `koanf:"color" json:"-"` DisplayTimeTaken bool `koanf:"time" json:"-"` ShowJSON bool `koanf:"json" json:"-"` UseSearchList bool `koanf:"search" json:"-"`