(ns dnsadmin.core (:require-macros [hiccups.core :as hiccups :refer [html]]) (:require [ajax.core :refer [GET POST]] [dommy.core :as dommy] [hiccups.runtime :as hiccupsrt])) ;; declarations (enable-console-print!) (declare null-or-empty-p) (declare reduce-checkboxes) (declare template-message) (declare maybe-errormsg) (declare maybe-message) (declare notifications) (declare auth-notifications) (declare template-generic-form) (declare template-dns) (declare handler-dns) (declare render-dns) (declare navigate-to) (declare handler-location) (declare goto-location) (declare reset-app) (declare template-dns-login-get) (declare handler-dns-login-get) (declare render-dns-login-get) (declare on-dns-login-get-clicked) (declare handler-dns-login-post) (declare render-dns-login-post) (declare template-dns-headers) (declare handler-dns-headers) (declare render-dns-headers) (declare on-dns-select-recordtype-changed) (declare template-dns-api-search-get) (declare handler-dns-api-search-get) (declare render-dns-api-search-get) (declare on-dns-api-search-get-clicked) (declare template-dns-api-search-post-loading) (declare template-dns-api-search-post) (declare handler-dns-api-search-post) (declare render-dns-api-search-post) (declare on-dns-api-add-clicked) (declare template-dns-api-add-get) (declare handler-dns-api-add-get) (declare render-dns-api-add-get) (declare on-dns-add-get-clicked) (declare handler-dns-api-add-post) (declare render-dns-api-add-post) (declare on-dns-modify-api-clicked) (declare template-dns-api-modify-get) (declare handler-dns-api-modify-get) (declare render-dns-api-modify-get) (declare on-dns-api-modify-post-clicked) (declare handler-dns-api-modify-post) (declare render-dns-api-modify-post) (declare on-dns-api-delete-clicked) (declare handler-dns-api-delete-post) (declare render-dns-api-delete-post) (declare on-dns-api-ttl-clicked) (declare template-dns-api-ttl-get) (declare handler-dns-api-ttl-get) (declare render-dns-api-ttl-get) (declare on-dns-api-ttl-post-clicked) (declare handler-dns-api-ttl-post) (declare render-dns-api-ttl-post) (def jquery (js* "$")) (defn null-or-empty-p [arg] (or (not arg) (= arg ""))) (defn reduce-checkboxes [selector] "Reduces the names of all checked checkboxes to a pipe-separated list. Assumes that the checkboxes are named via the convention `chk_something-more'. The important thing is the underscore separating the throwaway prefix and the remaining useful bit. `selector' will likely be something like: [id^='chk_']" (reduce (fn [x y] (cond (and x y) (str x "|" y) (and x (not y)) x (and (not x) y) y :else "")) (map (fn [elem] (let [this (jquery (str "#" (dommy/attr elem :id)))] (when (-> this (.prop "checked")) (second (clojure.string/split (-> this (.prop "id")) "_"))))) (.toArray (jquery selector))))) ;; notifications (hiccups/defhtml template-authmsg [authmsg] [:div {:class "alert alert-danger"} authmsg]) (hiccups/defhtml template-errormsg [errormsg] [:div {:class "alert alert-danger"} errormsg]) (hiccups/defhtml template-message [message] [:div {:class "alert alert-success"} message]) (defn maybe-authmsg [jsonobj] (let [authmsg (get jsonobj "authmsg")] (cond (null-or-empty-p authmsg) (do (dommy/set-style! (dommy/sel1 :#authmsg) :display "none") (dommy/set-html! (dommy/sel1 :#authmsg) "") true) :else (do (dommy/set-style! (dommy/sel1 :#authmsg) :display "block") (dommy/set-html! (dommy/sel1 :#authmsg) (template-authmsg authmsg)) false)))) (defn maybe-errormsg [jsonobj] (let [errormsg (get jsonobj "errormsg")] (cond (null-or-empty-p errormsg) (do (dommy/set-style! (dommy/sel1 :#errormsg) :display "none") (dommy/set-html! (dommy/sel1 :#errormsg) "") true) :else (do (dommy/set-style! (dommy/sel1 :#errormsg) :display "block") (dommy/set-html! (dommy/sel1 :#errormsg) (template-errormsg errormsg)) false)))) (defn maybe-message [jsonobj] (let [message (get jsonobj "message")] (cond (null-or-empty-p message) (do (dommy/set-style! (dommy/sel1 :#message) :display "none") (dommy/set-html! (dommy/sel1 :#message) "")) :else (do (dommy/set-style! (dommy/sel1 :#message) :display "block") (dommy/set-html! (dommy/sel1 :#message) (template-message message)))) true)) (defn notifications [jsonobj] (let [authmsg-result (maybe-authmsg jsonobj) errormsg-result (maybe-errormsg jsonobj) message-result (maybe-message jsonobj)] (and authmsg-result errormsg-result message-result))) (defn auth-notifications [jsonobj] (let [result (notifications jsonobj)] (cond (get jsonobj "authmsg") (render-dns-login-get)) result)) ;; forms (hiccups/defhtml template-generic-form [jsonobj] [:div {:id "form-errormsg"}] [:form {:name (get jsonobj "name") :id (get jsonobj "name") :method (get jsonobj "httpMethod") :action (when (get jsonobj "action") (str "javascript:" (namespace ::x) "." (get jsonobj "action")))} (for [form-field (get jsonobj "formFields")] (cond (= (get form-field "fieldType") "button") [:div {:class "form-group row"} [:div {:class "offset-sm-2 col-sm-10"} [:button {:class "btn btn-primary" :data-dismiss (get jsonobj "dismiss") :type (cond (get form-field "onclick") "button" :else "submit") :onclick (when (get form-field "onclick") (str (namespace ::x) "." (get form-field "onclick")))} (get form-field "label")]]] (= (get form-field "fieldType") "hidden") [:input {:name (get form-field "name") :id (get form-field "name") :value (get form-field "value") :type (get form-field "fieldType")}] (= (get form-field "fieldType") "checkbox") [:div {:class "form-check"} [:input {:type "checkbox" :class "form-check-input" :name (get form-field "name") :id (get form-field "name") :value (get form-field "value") :checked (get form-field "checked") :required (get form-field "required")}] [:label {:class "form-check-label" :for (get form-field "name")} (get form-field "label")]] (= (get form-field "fieldType") "radio") [:div {:class "form-check"} [:input {:type "radio" :class "form-check-input" :name (get form-field "name") :id (get form-field "name") :value (get form-field "value") :checked (get form-field "checked") :required (get form-field "required")}] [:label {:class "form-check-label" :for (get form-field "name")} (get form-field "label")]] (= (get form-field "fieldType") "select") [:div {:class "form-group"} [:label {:for (get form-field "name")} (get form-field "label")] [:select {:class "form-control" :name (get form-field "name") :id (get form-field "name") :required (get form-field "required") :onchange (when (get form-field "onchange") (str (namespace ::x) "." (get form-field "onchange")))} (for [option (get form-field "options")] [:option {:value (get option "value")} (get option "label")])]] :else [:div {:class "form-group row"} [:label {:for (get form-field "name") :class "col-form-label col-sm-2" :style "text-align: right"} (get form-field "label")] [:div {:class "col-sm-10"} [:input {:name (get form-field "name") :id (get form-field "name") :value (get form-field "value") :type (get form-field "fieldType") :class "form-control" :required (get form-field "required")}]]]))] (when (get jsonobj "requiredP") [:div "* Required"])) ;; dns (hiccups/defhtml template-dns [jsonobj] [:div {:class "container-fluid"} [:div {:class "row"} [:div {:class "col-2"} [:h4 (get jsonobj "title")] (template-generic-form (get jsonobj "form")) [:div {:id "search"}]] [:div {:class "col"} [:div {:style "text-align: right"} [:button {:class "btn btn-primary" :type "button" :onclick (str (namespace ::x) ".on_dns_api_add_clicked()")} "Add New Record"]] [:div {:id "results"}]]]] [:div {:id "add" :class "modal fade" :role "dialog"} [:div {:class "modal-dialog modal-lg"} [:div {:class "modal-content"} [:div {:class "modal-header"} [:h5 {:class "modal-title"} "DNS - Add"] [:button {:type "button" :class "close" :data-dismiss "modal"} "×"]] [:div {:id "add-body" :class "modal-body" :style "height: 460px;"}] [:div {:class "modal-footer"} [:button {:type "submit" :class "btn btn-danger btn-default" :data-dismiss "modal"} [:span {:class "glyphicon glyphicon-remove"}] "Cancel"]]]]] [:div {:id "modify" :class "modal fade" :role "dialog"} [:div {:class "modal-dialog modal-lg"} [:div {:class "modal-content"} [:div {:class "modal-header"} [:h5 {:class "modal-title"} "DNS - Modify"] [:button {:type "button" :class "close" :data-dismiss "modal"} "×"]] [:div {:id "modify-body" :class "modal-body" :style "height: 460px;"}] [:div {:class "modal-footer"} [:button {:type "submit" :class "btn btn-danger btn-default" :data-dismiss "modal"} [:span {:class "glyphicon glyphicon-remove"}] "Cancel"]]]]] [:div {:id "ttl" :class "modal fade" :role "dialog"} [:div {:class "modal-dialog modal-lg"} [:div {:class "modal-content"} [:div {:class "modal-header"} [:h5 {:class "modal-title"} "DNS - TTL"] [:button {:type "button" :class "close" :data-dismiss "modal"} "×"]] [:div {:id "ttl-body" :class "modal-body" :style "height: 200px;"}] [:div {:class "modal-footer"} [:button {:type "submit" :class "btn btn-danger btn-default" :data-dismiss "modal"} [:span {:class "glyphicon glyphicon-remove"}] "Cancel"]]]]] [:div {:id "login" :class "modal fade" :role "dialog"} [:div {:class "modal-dialog modal-lg"} [:div {:class "modal-content"} [:div {:class "modal-header"} [:h5 {:class "modal-title"} "DNS - Authentication"] [:button {:type "button" :class "close" :data-dismiss "modal"} "×"]] [:div {:id "login-body" :class "modal-body" :style "height: 460px;"}] [:div {:class "modal-footer"} [:button {:type "submit" :class "btn btn-danger btn-default" :data-dismiss "modal"} [:span {:class "glyphicon glyphicon-remove"}] "Cancel"]]]]]) (defn handler-dns [response] (let [jsonobj (js->clj (js/JSON.parse response))] (when (auth-notifications jsonobj) (dommy/set-html! (dommy/sel1 :#body) (template-dns jsonobj)) (render-dns-api-search-get) (render-dns-headers)))) (defn render-dns [] (GET "/dns" {:handler handler-dns})) ;; location (defn navigate-to [handler] (dommy/set-html! (dommy/sel1 :#location) handler) (cond (= handler "/dns") (render-dns))) (defn handler-location [response] (let [jsonobj (js->clj (js/JSON.parse response))] (navigate-to (get jsonobj "location")) (notifications jsonobj))) (defn goto-location [location] (POST "/location" {:format :raw :params {:location location} :handler handler-location})) (defn reset-app [] (set! (.-location js/document) "/")) ;; dns-login-get (hiccups/defhtml template-dns-login-get [jsonobj] [:h5 (get jsonobj "title")] (template-generic-form (get jsonobj "form"))) (defn handler-dns-login-get [response] (let [jsonobj (js->clj (js/JSON.parse response))] (dommy/set-html! (dommy/sel1 :#login-body) (template-dns-login-get jsonobj)) (.modal (jquery "#login")))) (defn render-dns-login-get [] (GET "/dns/login" {:handler handler-dns-login-get})) (defn on-dns-login-get-clicked [] (when (-> (jquery "#dns-login-get-form") (.get "0") (.checkValidity)) (.modal (jquery "#login") "hide") (apply render-dns-login-post (map (fn [field] (when (jquery (str "#dns-login-get-form input[id=" field "]")) (.val (jquery (str "#dns-login-get-form input[id=" field "]"))))) ["username" "pwd" "keypath"])))) ;; dns-login-post (defn handler-dns-login-post [response] (let [jsonobj (js->clj (js/JSON.parse response))] (when (auth-notifications jsonobj) (render-dns-api-search-post)))) (defn render-dns-login-post [username pwd keypath] (POST "/dns/login/post" {:format :raw :params {:username username :pwd pwd :keypath keypath} :handler handler-dns-login-post})) ;; dns-headers (hiccups/defhtml template-dns-headers [jsonobj] [:table {:class "table table-hover"} [:thead [:tr (for [header (remove (fn [x] (= x "ttl")) (get jsonobj "headers"))] [:th header]) [:th "TTL"] [:th "Del"]]]]) (defn handler-dns-headers [response] (let [jsonobj (js->clj (js/JSON.parse response))] (when (auth-notifications jsonobj) (dommy/set-html! (dommy/sel1 :#results) (template-dns-headers jsonobj))))) (defn render-dns-headers [] (POST "/dns/headers" {:format :raw :params {:recordtype (dommy/value (dommy/sel1 :#recordtype))} :handler handler-dns-headers})) ;; dns-api-search-get (defn on-dns-select-recordtype-changed [] (render-dns-api-search-get) (render-dns-headers)) (hiccups/defhtml template-dns-api-search-get [jsonobj] [:h4 (get jsonobj "title")] (template-generic-form (get jsonobj "form"))) (defn handler-dns-api-search-get [response] (let [jsonobj (js->clj (js/JSON.parse response))] (when (auth-notifications jsonobj) (dommy/set-html! (dommy/sel1 :#search) (template-dns-api-search-get jsonobj))))) (defn render-dns-api-search-get [] (POST "/dns/api/search" {:format :raw :params {:recordtype (dommy/value (dommy/sel1 :#recordtype))} :handler handler-dns-api-search-get})) (defn on-dns-api-search-get-clicked [] (when (-> (jquery "#dns-api-search-get-form") (.get "0") (.checkValidity)) (dommy/set-html! (dommy/sel1 :#results) (template-dns-api-search-post-loading)) (render-dns-api-search-post))) ;; dns-api-search-post (hiccups/defhtml template-dns-api-search-post-loading [] [:h5 "Loading..."]) (hiccups/defhtml template-dns-api-search-post [jsonobj] (let [results (get jsonobj "results") keys (remove (fn [x] (or (= x "Recordtype") (= x "ttl"))) (keys (first results)))] [:table {:class "table table-hover"} [:thead [:tr (for [key keys] [:th key]) [:th "TTL"] [:th "Del"]]] [:tbody (for [rec results] (let [onclick (str (namespace ::x) ".on_dns_api_modify_clicked('" (get rec "_ref") "','" (get rec "name") "','" (get rec "ipv4Addr") "','" (get rec "ipv6Addr") "','" (get rec "canonical") "','" (get rec "ptrdname") "')")] [:tr (for [key keys] [:td {:onclick onclick} (get rec key)]) [:td [:img {:src "/static/images/clock.png" :onclick (str (namespace ::x) ".on_dns_api_ttl_clicked('" (get rec "_ref") "','" (get rec "name") "')")}]] [:td [:img {:src "/static/images/delete.png" :onclick (str (namespace ::x) ".on_dns_api_delete_clicked('" (get rec "_ref") "','" (get rec "name") "')")}]]]))]])) (defn handler-dns-api-search-post [response] (let [jsonobj (js->clj (js/JSON.parse response))] (when (auth-notifications jsonobj) (dommy/set-html! (dommy/sel1 :#results) (template-dns-api-search-post jsonobj))))) (defn render-dns-api-search-post [] (let [fields ["ref" "name" "ipv4addr" "ipv6addr" "canonical" "ptrdname"] params (merge {:recordtype (dommy/value (dommy/sel1 :#recordtype))} (into {} (map vector (map (fn [field] (keyword field)) fields) (map (fn [field] (when (jquery (str "#dns-api-search-get-form input[id=" field "]")) (let [val (.val (jquery (str "#dns-api-search-get-form input[id=" field "]")))] (cond (null-or-empty-p val) nil :else val)))) fields))))] (POST "/dns/api/search/post" {:format :raw :params params :handler handler-dns-api-search-post}))) ;; dns-api-add-get (defn on-dns-api-add-clicked [] (render-dns-api-add-get)) (hiccups/defhtml template-dns-api-add-get [jsonobj] [:h5 (get jsonobj "title")] (template-generic-form (get jsonobj "form"))) (defn handler-dns-api-add-get [response] (let [jsonobj (js->clj (js/JSON.parse response))] (when (auth-notifications jsonobj) (dommy/set-html! (dommy/sel1 :#add-body) (template-dns-api-add-get jsonobj)) (.modal (jquery "#add"))))) (defn render-dns-api-add-get [] (POST "/dns/api/add" {:format :raw :params {:recordtype (dommy/value (dommy/sel1 :#recordtype))} :handler handler-dns-api-add-get})) (defn on-dns-api-add-get-clicked [] (when (-> (jquery "#dns-api-add-get-form") (.get "0") (.checkValidity)) (cond (and (= (dommy/value (dommy/sel1 :#recordtype)) "a") (let [ipv4addr (when (jquery "#dns-api-add-get-form input[id=ipv4addr]") (.val (jquery "#dns-api-add-get-form input[id=ipv4addr]"))) ipv6addr (when (jquery "#dns-api-add-get-form input[id=ipv6addr]") (.val (jquery "#dns-api-add-get-form input[id=ipv6addr]")))] (and (null-or-empty-p ipv4addr) (null-or-empty-p ipv6addr)))) (js/alert "One or both of the following fields must be populated: IPv4 Address, IPv6 Address") :else (do (.modal (jquery "#add") "hide") (dommy/set-html! (dommy/sel1 :#results) (template-dns-api-search-post-loading)) (apply render-dns-api-add-post (map (fn [field] (when (jquery (str "#dns-api-add-get-form input[id=" field "]")) (.val (jquery (str "#dns-api-add-get-form input[id=" field "]"))))) ["name" "ipv4addr" "ipv6addr" "canonical" "ptrdname"])))))) ;; dns-api-add-post (defn handler-dns-api-add-post [response] (let [jsonobj (js->clj (js/JSON.parse response))] (when (auth-notifications jsonobj) (render-dns-api-search-post)))) (defn render-dns-api-add-post [name ipv4addr ipv6addr canonical ptrdname] (POST "/dns/api/add/post" {:format :raw :params {:recordtype (dommy/value (dommy/sel1 :#recordtype)) :name name :ipv4addr ipv4addr :ipv6addr ipv6addr :canonical canonical :ptrdname ptrdname} :handler handler-dns-api-add-post})) ;; dns-api-modify-get (defn on-dns-api-modify-clicked [ref name ipv4addr ipv6addr canonical ptrdname] (render-dns-api-modify-get ref name ipv4addr ipv6addr canonical ptrdname)) (hiccups/defhtml template-dns-api-modify-get [jsonobj] [:h5 (get jsonobj "title")] (template-generic-form (get jsonobj "form"))) (defn handler-dns-api-modify-get [response] (let [jsonobj (js->clj (js/JSON.parse response))] (when (auth-notifications jsonobj) (dommy/set-html! (dommy/sel1 :#modify-body) (template-dns-api-modify-get jsonobj)) (.modal (jquery "#modify"))))) (defn render-dns-api-modify-get [ref name ipv4addr ipv6addr canonical ptrdname] (POST "/dns/api/modify" {:format :raw :params {:recordtype (dommy/value (dommy/sel1 :#recordtype)) :ref ref :name name :ipv4addr ipv4addr :ipv6addr ipv6addr :canonical canonical :ptrdname ptrdname} :handler handler-dns-api-modify-get})) (defn on-dns-api-modify-get-clicked [ref name ipv4addr ipv6addr canonical ptrdname] (when (-> (jquery "#dns-api-modify-get-form") (.get "0") (.checkValidity)) (.modal (jquery "#modify") "hide") (dommy/set-html! (dommy/sel1 :#results) (template-dns-api-search-post-loading)) (apply render-dns-api-modify-post (map (fn [field] (when (jquery (str "#dns-api-modify-get-form input[id=" field "]")) (.val (jquery (str "#dns-api-modify-get-form input[id=" field "]"))))) ["ref" "name" "ipv4addr" "ipv6addr" "canonical" "ptrdname"])))) ;; dns-api-modify-post (defn handler-dns-api-modify-post [response] (let [jsonobj (js->clj (js/JSON.parse response))] (when (auth-notifications jsonobj) (render-dns-api-search-post)))) (defn render-dns-api-modify-post [ref name ipv4addr ipv6addr canonical ptrdname] (POST "/dns/api/modify/post" {:format :raw :params {:recordtype (dommy/value (dommy/sel1 :#recordtype)) :ref ref :name name :ipv4addr ipv4addr :ipv6addr ipv6addr :canonical canonical :ptrdname ptrdname} :handler handler-dns-api-modify-post})) ;; dns-api-delete-post (defn on-dns-api-delete-clicked [ref name] (when (js/confirm (str "Are you sure that you want to delete the record for " name "?")) (dommy/set-html! (dommy/sel1 :#results) (template-dns-api-search-post-loading)) (render-dns-api-delete-post ref))) (defn handler-dns-api-delete-post [response] (let [jsonobj (js->clj (js/JSON.parse response))] (when (auth-notifications jsonobj) (render-dns-api-search-post)))) (defn render-dns-api-delete-post [ref] (POST "/dns/api/delete/post" {:format :raw :params {:recordtype (dommy/value (dommy/sel1 :#recordtype)) :ref ref} :handler handler-dns-api-delete-post})) ;; dns-api-ttl-get (defn on-dns-api-ttl-clicked [ref name] (render-dns-api-ttl-get ref name)) (hiccups/defhtml template-dns-api-ttl-get [jsonobj] [:h5 (get jsonobj "title")] (template-generic-form (get jsonobj "form"))) (defn handler-dns-api-ttl-get [response] (let [jsonobj (js->clj (js/JSON.parse response))] (when (auth-notifications jsonobj) (dommy/set-html! (dommy/sel1 :#ttl-body) (template-dns-api-ttl-get jsonobj)) (.modal (jquery "#ttl"))))) (defn render-dns-api-ttl-get [ref name] (POST "/dns/api/ttl" {:format :raw :params {:recordtype (dommy/value (dommy/sel1 :#recordtype)) :ref ref :name name} :handler handler-dns-api-ttl-get})) (defn on-dns-api-ttl-get-clicked [ref ttl] (when (-> (jquery "#dns-api-ttl-get-form") (.get "0") (.checkValidity)) (.modal (jquery "#ttl") "hide") (dommy/set-html! (dommy/sel1 :#results) (template-dns-api-search-post-loading)) (apply render-dns-api-ttl-post (map (fn [field] (when (jquery (str "#dns-api-ttl-get-form input[id=" field "]")) (.val (jquery (str "#dns-api-ttl-get-form input[id=" field "]"))))) ["ref" "ttl"])))) ;; dns-api-ttl-post (defn handler-dns-api-ttl-post [response] (let [jsonobj (js->clj (js/JSON.parse response))] (when (auth-notifications jsonobj) (render-dns-api-search-post)))) (defn render-dns-api-ttl-post [ref ttl] (POST "/dns/api/ttl/post" {:format :raw :params {:recordtype (dommy/value (dommy/sel1 :#recordtype)) :ref ref :ttl ttl} :handler handler-dns-api-ttl-post}))