summaryrefslogtreecommitdiff
path: root/lisp/webapps/ldapadmin
diff options
context:
space:
mode:
authorckonstanski <carlos.konstanski@olo.com>2021-11-27 10:20:12 -0700
committerckonstanski <carlos.konstanski@olo.com>2021-11-27 10:20:12 -0700
commit31657f6854edc116946a099be889440fb3e92e53 (patch)
treebe32747cfaf2f19f59857349438d62fb4a4a25b6 /lisp/webapps/ldapadmin
parent41ca4dcf8a18a76c48734a5fca4c56afb735a66e (diff)
moved everything under lisp/
Diffstat (limited to 'lisp/webapps/ldapadmin')
-rw-r--r--lisp/webapps/ldapadmin/clojurescript/ldapadmin/.gitignore15
-rw-r--r--lisp/webapps/ldapadmin/clojurescript/ldapadmin/README.md14
-rw-r--r--lisp/webapps/ldapadmin/clojurescript/ldapadmin/project.clj13
-rw-r--r--lisp/webapps/ldapadmin/clojurescript/ldapadmin/src/core.cljs447
-rw-r--r--lisp/webapps/ldapadmin/conf/.gitignore1
-rw-r--r--lisp/webapps/ldapadmin/conf/options.lisp.example11
-rw-r--r--lisp/webapps/ldapadmin/site.lisp101
-rw-r--r--lisp/webapps/ldapadmin/static/images/edit-delete.pngbin0 -> 1121 bytes
l---------lisp/webapps/ldapadmin/static/js/cljs1
9 files changed, 603 insertions, 0 deletions
diff --git a/lisp/webapps/ldapadmin/clojurescript/ldapadmin/.gitignore b/lisp/webapps/ldapadmin/clojurescript/ldapadmin/.gitignore
new file mode 100644
index 0000000..a1cd79c
--- /dev/null
+++ b/lisp/webapps/ldapadmin/clojurescript/ldapadmin/.gitignore
@@ -0,0 +1,15 @@
+target
+classes
+resources
+checkouts
+pom.xml
+pom.xml.asc
+*.jar
+*.class
+.lein-*
+.nrepl-port
+.hgignore
+.hg
+profiles.clj
+figwheel_server.log
+.rebel_readline_history
diff --git a/lisp/webapps/ldapadmin/clojurescript/ldapadmin/README.md b/lisp/webapps/ldapadmin/clojurescript/ldapadmin/README.md
new file mode 100644
index 0000000..53ca866
--- /dev/null
+++ b/lisp/webapps/ldapadmin/clojurescript/ldapadmin/README.md
@@ -0,0 +1,14 @@
+# ldapadmin
+
+A Clojure library designed to ... well, that part is up to you.
+
+## Usage
+
+FIXME
+
+## License
+
+Copyright © 2017 FIXME
+
+Distributed under the Eclipse Public License either version 1.0 or (at
+your option) any later version.
diff --git a/lisp/webapps/ldapadmin/clojurescript/ldapadmin/project.clj b/lisp/webapps/ldapadmin/clojurescript/ldapadmin/project.clj
new file mode 100644
index 0000000..001af8b
--- /dev/null
+++ b/lisp/webapps/ldapadmin/clojurescript/ldapadmin/project.clj
@@ -0,0 +1,13 @@
+(defproject ldapadmin "0.1.0-SNAPSHOT"
+ :description "An LDAP adminitration utility written in SBCL on the
+ server-side and ClojureScript on the client-side. This is the
+ client-side component."
+ :url "FIXME"
+ :license "public domain"
+ :dependencies [[org.clojure/clojure "LATEST"]
+ [org.clojure/clojurescript "LATEST"]
+ [cljs-ajax "LATEST"]
+ [prismatic/dommy "LATEST"]
+ [hiccups "LATEST"]]
+ :plugins [[lein-cljsbuild "LATEST"]]
+ :clean-targets ^{:protect false} [:target-path "out" "resources/public/cljs"])
diff --git a/lisp/webapps/ldapadmin/clojurescript/ldapadmin/src/core.cljs b/lisp/webapps/ldapadmin/clojurescript/ldapadmin/src/core.cljs
new file mode 100644
index 0000000..d3f0374
--- /dev/null
+++ b/lisp/webapps/ldapadmin/clojurescript/ldapadmin/src/core.cljs
@@ -0,0 +1,447 @@
+(ns ldapadmin.core
+ (:require-macros [hiccups.core :as hiccups :refer [html]])
+ (:require [ajax.core :refer [GET POST]]
+ [dommy.core :as dommy]
+ [hiccups.runtime :as hiccupsrt]
+ [clojure.string :as str]
+ [org-ckons-cljs.notifications.core :as ck-notifications]
+ [org-ckons-cljs.form.core :as ck-form]))
+
+;; declarations
+
+(enable-console-print!)
+(def jquery (js* "$"))
+
+(declare notifications)
+(declare auth-notifications)
+(declare template-menu)
+(declare handler-menu)
+(declare render-menu)
+(declare template-home)
+(declare handler-home)
+(declare render-home)
+(declare template-login)
+(declare handler-login)
+(declare render-login)
+(declare on-login-submit-clicked)
+(declare handler-login-authenticate)
+(declare render-login-authenticate)
+(declare handler-logout)
+(declare render-logout)
+(declare template-inetorg-view)
+(declare handler-inetorg-view)
+(declare render-inetorg-view)
+(declare on-inetorg-view-search-clicked)
+(declare template-inetorg-view-search)
+(declare handler-inetorg-view-search)
+(declare render-inetorg-view-search)
+(declare template-inetorg-view-results)
+(declare handler-inetorg-view-results)
+(declare render-inetorg-view-results)
+(declare on-inetorg-modify-clicked)
+(declare template-inetorg-modify)
+(declare handler-inetorg-modify)
+(declare render-inetorg-modify)
+(declare on-inetorg-modify-submit-clicked)
+(declare handler-inetorg-modify-submit)
+(declare render-inetorg-modify-submit)
+(declare on-inetorg-delete-clicked)
+(declare template-inetorg-delete)
+(declare handler-inetorg-delete)
+(declare render-inetorg-delete)
+(declare on-inetorg-delete-submit-clicked)
+(declare handler-inetorg-delete-submit)
+(declare render-inetorg-delete-submit)
+(declare template-inetorg-add)
+(declare handler-inetorg-add)
+(declare render-inetorg-add)
+(declare on-inetorg-add-submit-clicked)
+(declare handler-inetorg-add-submit)
+(declare render-inetorg-add-submit)
+(declare on-menu-clicked)
+(declare handler-location)
+(declare goto-location)
+
+;; notifications
+
+(defn notifications [jsonobj]
+ (ck-notifications/maybe-message jsonobj)
+ (ck-notifications/maybe-error jsonobj))
+
+(defn auth-notifications [jsonobj]
+ (cond (empty? (get jsonobj "errormsg"))
+ (notifications jsonobj)
+ :else
+ (do
+ (render-home "" (get jsonobj "errormsg"))
+ (render-menu))))
+
+;; menu
+
+(hiccups/defhtml template-menu [menuitems]
+ [:ul {:class "nav nav-pills"}
+ (for [menuitem menuitems]
+ [:li {:class "nav-item"}
+ [:a {:class (cond (= (str/upper-case (get menuitem "handler"))
+ (str/upper-case (dommy/html (dommy/sel1 :#location))))
+ "nav-link active"
+ :else
+ "nav-link")
+ :id (get menuitem "id")
+ :onclick (str (namespace ::x) ".on_menu_clicked('" (get menuitem "handler") "')")}
+ (get menuitem "label")]])])
+
+(defn handler-menu [response]
+ (let [jsonobj (js->clj (js/JSON.parse response))]
+ (dommy/set-html! (dommy/sel1 :#menu) (template-menu (get jsonobj "menuitems")))))
+
+(defn render-menu []
+ (GET "/menu" {:handler handler-menu}))
+
+;; home
+
+(hiccups/defhtml template-home [jsonobj]
+ [:h3 {:style "text-align: center"} (get jsonobj "content")])
+
+(defn handler-home [response]
+ (let [jsonobj (js->clj (js/JSON.parse response))]
+ (notifications jsonobj)
+ (dommy/set-html! (dommy/sel1 :#body) (template-home jsonobj))))
+
+(defn render-home
+ ([]
+ (GET "/home" {:handler handler-home}))
+ ([message errormsg]
+ (POST "/home" {:format :raw
+ :params {:message message
+ :errormsg errormsg}
+ :handler handler-home})))
+
+;; login
+
+(hiccups/defhtml template-login [jsonobj]
+ [:h3 {:style "text-align: center"} (get jsonobj "title")]
+ (ck-form/template-generic-form (get jsonobj "form") (namespace ::x))
+ [:div {:style "text-align: center"}])
+
+(defn handler-login [response]
+ (let [jsonobj (js->clj (js/JSON.parse response))]
+ (notifications jsonobj)
+ (dommy/set-html! (dommy/sel1 :#body) (template-login jsonobj))))
+
+(defn render-login []
+ (GET "/login" {:handler handler-login}))
+
+;; login-authenticate
+
+(defn on-login-submit-clicked []
+ (when (-> (jquery "#login-form")
+ (.get "0")
+ (.checkValidity))
+ (render-login-authenticate)))
+
+(defn handler-login-authenticate [response]
+ (let [jsonobj (js->clj (js/JSON.parse response))]
+ (cond (get jsonobj "errormsg")
+ (goto-location "/login")
+ (get jsonobj "message")
+ (do
+ (dommy/set-html! (dommy/sel1 :#location) "/inetorg/view")
+ (render-inetorg-view)
+ (render-menu)))))
+
+(defn render-login-authenticate []
+ (POST "/login/authenticate" {:format :raw
+ :params {:dn (dommy/value (dommy/sel1 :#dn))
+ :password (dommy/value (dommy/sel1 :#password))}
+ :handler handler-login-authenticate}))
+
+;; logout
+
+(defn handler-logout [response]
+ (let [jsonobj (js->clj (js/JSON.parse response))]
+ (render-home (get jsonobj "message") "")
+ (dommy/set-html! (dommy/sel1 :#location) "/home")
+ (render-menu)))
+
+(defn render-logout []
+ (GET "/logout" {:handler handler-logout}))
+
+;; inetorg-view
+
+(hiccups/defhtml template-inetorg-view [jsonobj]
+ [:h3 {:style "text-align: center"} (get jsonobj "title")]
+ [:div {:id "search"}]
+ [:div {:id "results"}]
+ [:div {:id "modify"
+ :class "modal fade"
+ :role "dialog"}
+ [:div {:class "modal-dialog modal-lg"}
+ [:div {:class "modal-content"}
+ [:div {:class "modal-header"}
+ [:button {:type "button"
+ :class "close"
+ :data-dismiss "modal"}
+ "&times;"]
+ [:h4 "Modify InetOrg Entry"]]
+ [:div {:id "modify-body"
+ :class "modal-body"
+ :style "height: 510px;"}]
+ [:div {:class "modal-footer"}
+ [:button {:type "submit"
+ :class "btn btn-danger btn-default"
+ :data-dismiss "modal"}
+ [:span {:class "glyphicon glyphicon-remove"}]
+ "Cancel"]]]]]
+ [:div {:id "delete"
+ :class "modal fade"
+ :role "dialog"}
+ [:div {:class "modal-dialog"}
+ [:div {:class "modal-content"}
+ [:div {:class "modal-header"}
+ [:button {:type "button"
+ :class "close"
+ :data-dismiss "modal"}
+ "&times;"]
+ [:h4 "Delete InetOrg Entry"]]
+ [:div {:id "delete-body"
+ :class "modal-body"}]
+ [:div {:class "modal-footer"}
+ [:button {:type "submit"
+ :class "btn btn-danger btn-default"
+ :data-dismiss "modal"}
+ [:span {:class "glyphicon glyphicon-remove"}]
+ "Cancel"]]]]])
+
+(defn handler-inetorg-view [response]
+ (let [jsonobj (js->clj (js/JSON.parse response))]
+ (auth-notifications jsonobj)
+ (dommy/set-html! (dommy/sel1 :#body) (template-inetorg-view jsonobj))
+ (render-inetorg-view-search)))
+
+(defn render-inetorg-view []
+ (GET "/inetorg/view" {:handler handler-inetorg-view}))
+
+;; inetorg-view-search
+
+(defn on-inetorg-view-search-clicked []
+ (when (-> (jquery "#inetorg-view-search-form")
+ (.get "0")
+ (.checkValidity))
+ (render-inetorg-view-results)))
+
+(hiccups/defhtml template-inetorg-view-search [jsonobj]
+ (ck-form/template-generic-form (get jsonobj "form") (namespace ::x)))
+
+(defn handler-inetorg-view-search [response]
+ (let [jsonobj (js->clj (js/JSON.parse response))]
+ (auth-notifications jsonobj)
+ (dommy/set-html! (dommy/sel1 :#search) (template-inetorg-view-search jsonobj))
+ (render-inetorg-view-results)))
+
+(defn render-inetorg-view-search []
+ (GET "/inetorg/view/search" {:handler handler-inetorg-view-search}))
+
+;; inetorg-view-results
+
+(hiccups/defhtml template-inetorg-view-results [jsonobj]
+ [:table {:class "table table-hover"}
+ [:thead
+ [:tr
+ [:th "givenName"]
+ [:th "sn"]
+ [:th "mail"]
+ [:th "postalAddress"]
+ [:th "postalCode"]
+ [:th "st"]
+ [:th "l"]
+ [:th "telephoneNumber"]
+ [:th "mobile"]
+ [:th "businessCategory"]
+ [:th "Del"]]]
+ [:tbody
+ (for [ldap-user (get jsonobj "results")]
+ (let* [cn (str (get ldap-user "givenname") " " (get ldap-user "sn"))
+ onclick (str (namespace ::x) ".on_inetorg_modify_clicked('" cn "')")]
+ [:tr
+ [:td {:onclick onclick} (get ldap-user "givenname")]
+ [:td {:onclick onclick} (get ldap-user "sn")]
+ [:td {:onclick onclick} (get ldap-user "mail")]
+ [:td {:onclick onclick} (get ldap-user "postaladdress")]
+ [:td {:onclick onclick} (get ldap-user "postalcode")]
+ [:td {:onclick onclick} (get ldap-user "st")]
+ [:td {:onclick onclick} (get ldap-user "l")]
+ [:td {:onclick onclick} (get ldap-user "telephonenumber")]
+ [:td {:onclick onclick} (get ldap-user "mobile")]
+ [:td {:onclick onclick} (get ldap-user "businesscategory")]
+ [:td [:img {:src "/static/images/edit-delete.png"
+ :onclick (str (namespace ::x) ".on_inetorg_delete_clicked('" cn "')")}]]]))]])
+
+(defn handler-inetorg-view-results [response]
+ (let [jsonobj (js->clj (js/JSON.parse response))]
+ (auth-notifications jsonobj)
+ (dommy/set-html! (dommy/sel1 :#results) (template-inetorg-view-results jsonobj))))
+
+(defn render-inetorg-view-results []
+ (POST "/inetorg/view/results" {:format :raw
+ :params {:givenname (dommy/value (dommy/sel1 :#view-givenname))
+ :sn (dommy/value (dommy/sel1 :#view-sn))
+ :mail (dommy/value (dommy/sel1 :#view-mail))
+ :postaladdress (dommy/value (dommy/sel1 :#view-postaladdress))
+ :postalcode (dommy/value (dommy/sel1 :#view-postalcode))
+ :st (dommy/value (dommy/sel1 :#view-st))
+ :l (dommy/value (dommy/sel1 :#view-l))
+ :telephonenumber (dommy/value (dommy/sel1 :#view-telephonenumber))
+ :mobile (dommy/value (dommy/sel1 :#view-mobile))
+ :businesscategory (dommy/value (dommy/sel1 :#view-businesscategory))}
+ :handler handler-inetorg-view-results}))
+
+;; inetorg-modify
+
+(defn on-inetorg-modify-clicked [cn]
+ (render-inetorg-modify cn))
+
+(hiccups/defhtml template-inetorg-modify [jsonobj]
+ (ck-form/template-generic-form (get jsonobj "form") (namespace ::x)))
+
+(defn handler-inetorg-modify [response]
+ (let [jsonobj (js->clj (js/JSON.parse response))
+ jquery (js* "$")]
+ (auth-notifications jsonobj)
+ (dommy/set-html! (dommy/sel1 :#modify-body) (template-inetorg-modify jsonobj))
+ (doseq [[name value] (get jsonobj "ldapUserValues")]
+ (dommy/set-value! (dommy/sel1 (keyword (str "#modify-" name))) value))
+ (.modal (jquery "#modify"))))
+
+(defn render-inetorg-modify [cn]
+ (POST "/inetorg/modify" {:format :raw
+ :params {:cn cn}
+ :handler handler-inetorg-modify}))
+
+;; inetorg-modify-submit
+
+(defn on-inetorg-modify-submit-clicked []
+ (.modal (jquery "#modify") "hide")
+ (render-inetorg-modify-submit))
+
+(defn handler-inetorg-modify-submit [response]
+ (let [jsonobj (js->clj (js/JSON.parse response))]
+ (auth-notifications jsonobj)
+ (on-menu-clicked "/inetorg/view")))
+
+(defn render-inetorg-modify-submit []
+ (POST "/inetorg/modify/submit" {:format :raw
+ :params {:givenname (dommy/value (dommy/sel1 :#modify-givenname))
+ :sn (dommy/value (dommy/sel1 :#modify-sn))
+ :mail (dommy/value (dommy/sel1 :#modify-mail))
+ :postaladdress (dommy/value (dommy/sel1 :#modify-postaladdress))
+ :postalcode (dommy/value (dommy/sel1 :#modify-postalcode))
+ :st (dommy/value (dommy/sel1 :#modify-st))
+ :l (dommy/value (dommy/sel1 :#modify-l))
+ :telephonenumber (dommy/value (dommy/sel1 :#modify-telephonenumber))
+ :mobile (dommy/value (dommy/sel1 :#modify-mobile))
+ :businesscategory (dommy/value (dommy/sel1 :#modify-businesscategory))}
+ :handler handler-inetorg-modify-submit}))
+
+;; inetrog-delete
+
+(defn on-inetorg-delete-clicked [cn]
+ (render-inetorg-delete cn))
+
+(hiccups/defhtml template-inetorg-delete [jsonobj]
+ [:p (str "Are you sure you want to delete the InetOrg entry: " (get jsonobj "cn"))]
+ [:p "This action cannot be undone."]
+ [:button {:type "button"
+ :data-dismiss "modal"
+ :onclick (str (namespace ::x) ".on_inetorg_delete_submit_clicked('" (get jsonobj "cn") "')")}
+ "Delete InetOrg Entry"])
+
+(defn handler-inetorg-delete [response]
+ (let [jsonobj (js->clj (js/JSON.parse response))
+ jquery (js* "$")]
+ (auth-notifications jsonobj)
+ (dommy/set-html! (dommy/sel1 :#delete-body) (template-inetorg-delete jsonobj))
+ (.modal (jquery "#delete"))))
+
+(defn render-inetorg-delete [cn]
+ (POST "/inetorg/delete" {:format :raw
+ :params {:cn cn}
+ :handler handler-inetorg-delete}))
+
+;; inetrog-delete-submit
+
+(defn on-inetorg-delete-submit-clicked [cn]
+ (render-inetorg-delete-submit cn))
+
+(defn handler-inetorg-delete-submit [response]
+ (let [jsonobj (js->clj (js/JSON.parse response))]
+ (auth-notifications jsonobj)
+ (on-menu-clicked "/inetorg/view")))
+
+(defn render-inetorg-delete-submit [cn]
+ (POST "/inetorg/delete/submit" {:format :raw
+ :params {:cn cn}
+ :handler handler-inetorg-delete-submit}))
+
+;; inetrog-add
+
+(hiccups/defhtml template-inetorg-add [jsonobj]
+ (ck-form/template-generic-form (get jsonobj "form") (namespace ::x)))
+
+(defn handler-inetorg-add [response]
+ (let [jsonobj (js->clj (js/JSON.parse response))]
+ (auth-notifications jsonobj)
+ (dommy/set-html! (dommy/sel1 :#body) (template-inetorg-add jsonobj))))
+
+(defn render-inetorg-add []
+ (GET "/inetorg/add" {:handler handler-inetorg-add}))
+
+;; inetorg-add-submit
+
+(defn on-inetorg-add-submit-clicked []
+ (when (-> (jquery "#inetorg-add-form")
+ (.get "0")
+ (.checkValidity))
+ (render-inetorg-add-submit)))
+
+(defn handler-inetorg-add-submit [response]
+ (let [jsonobj (js->clj (js/JSON.parse response))]
+ (auth-notifications jsonobj)
+ (notifications jsonobj)
+ (on-menu-clicked "/inetorg/view")))
+
+(defn render-inetorg-add-submit []
+ (POST "/inetorg/add/submit" {:format :raw
+ :params {:givenname (dommy/value (dommy/sel1 :#add-givenname))
+ :sn (dommy/value (dommy/sel1 :#add-sn))
+ :mail (dommy/value (dommy/sel1 :#add-mail))
+ :postaladdress (dommy/value (dommy/sel1 :#add-postaladdress))
+ :postalcode (dommy/value (dommy/sel1 :#add-postalcode))
+ :st (dommy/value (dommy/sel1 :#add-st))
+ :l (dommy/value (dommy/sel1 :#add-l))
+ :telephonenumber (dommy/value (dommy/sel1 :#add-telephonenumber))
+ :mobile (dommy/value (dommy/sel1 :#add-mobile))
+ :businesscategory (dommy/value (dommy/sel1 :#add-businesscategory))}
+ :handler handler-inetorg-add-submit}))
+
+;; location
+
+(defn on-menu-clicked [handler]
+ (dommy/set-html! (dommy/sel1 :#location) handler)
+ (render-menu)
+ (cond (= handler "/home") (render-home)
+ (= handler "/login") (render-login)
+ (= handler "/login/authenticate") (render-login-authenticate)
+ (= handler "/logout") (render-logout)
+ (= handler "/inetorg/view") (render-inetorg-view)
+ (= handler "/inetorg/add") (render-inetorg-add)))
+
+(defn handler-location [response]
+ (let [jsonobj (js->clj (js/JSON.parse response))]
+ (on-menu-clicked (get jsonobj "location"))
+ (notifications jsonobj)))
+
+(defn goto-location [location]
+ (POST "/location" {:format :raw
+ :params {:location location}
+ :handler handler-location}))
diff --git a/lisp/webapps/ldapadmin/conf/.gitignore b/lisp/webapps/ldapadmin/conf/.gitignore
new file mode 100644
index 0000000..14fa7a6
--- /dev/null
+++ b/lisp/webapps/ldapadmin/conf/.gitignore
@@ -0,0 +1 @@
+options.lisp
diff --git a/lisp/webapps/ldapadmin/conf/options.lisp.example b/lisp/webapps/ldapadmin/conf/options.lisp.example
new file mode 100644
index 0000000..89f0a37
--- /dev/null
+++ b/lisp/webapps/ldapadmin/conf/options.lisp.example
@@ -0,0 +1,11 @@
+((:name "ldapadmin"
+ :url "ldapadmin.tld"
+ :document-root "ldapadmin"
+ :title "LDAP Administration Tool"
+ :meta-description "LDAP Administration Tool"
+ :ldap (:ldap-host "ldap.tld"
+ :sslflag nil
+ :username "cn=Manager,dc=tld"
+ :password "Welcome1"
+ :base-dn "dc=tld"
+ :debug-mode t)))
diff --git a/lisp/webapps/ldapadmin/site.lisp b/lisp/webapps/ldapadmin/site.lisp
new file mode 100644
index 0000000..5b5163a
--- /dev/null
+++ b/lisp/webapps/ldapadmin/site.lisp
@@ -0,0 +1,101 @@
+;;; -*- Mode: Lisp; Syntax: ANSI-Common-Lisp; Base: 10 -*-
+(declaim (optimize (speed 0) (safety 3) (debug 3)))
+
+(in-package :ldapadmin)
+
+(defmacro .base (&optional (onload-fn "goto_location('/home')"))
+ `(org-ckons-http::html5
+ `(html
+ (head
+ ((meta :name "viewport" :content "width=device-width, initial-scale=1"))
+ ((meta :charset "utf-8"))
+ ((title) ,(title *webapp*))
+ ,@(mapcar (lambda (css)
+ `((link :rel "stylesheet" :href ,(getf css :href) :integrity ,(getf css :integrity) :crossorigin ,(getf css :crossorigin))))
+ '((:href "https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" :integrity "sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" :crossorigin "anonymous")
+ (:href "https://stackpath.bootstrapcdn.com/bootswatch/4.5.2/darkly/bootstrap.min.css" :integrity "sha384-nNK9n28pDUDDgIiIqZ/MiyO3F4/9vsMtReZK39klb/MtkZI3/LtjSjlmyVPS3KdN" :crossorigin "anonymous")))
+ ,@(mapcar (lambda (js)
+ `((script :type "text/javascript" :src ,(getf js :src) :integrity ,(getf js :integrity) :crossorigin ,(getf js :crossorigin))))
+ '((:src "https://code.jquery.com/jquery-3.2.1.slim.min.js" :integrity "sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" :crossorigin "anonymous")
+ (:src "https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" :integrity "sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" :crossorigin "anonymous")
+ (:src "https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" :integrity "sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" :crossorigin "anonymous")))
+ ((script :type "text/javascript" :src "/static/js/cljs/main.js")))
+ ((body :onload ,(format nil "ldapadmin.core.~a" ,onload-fn))
+ ((div :class "container-fluid")
+ ((div :class "row")
+ ((div :class "col") "&nbsp;")
+ ((div :class "col")
+ ((div :class "page-header")
+ ((h2 :align "center") ,(title *webapp*))))
+ ((div :class "col") "&nbsp;"))
+ ((div :id "menu" :class "well"))
+ ((div :id "location" :style "display: none"))
+ ((div :id "errormsg"))
+ ((div :id "message"))
+ ((div :id "body")))))))
+
+(defmacro .location ()
+ `(location-json location))
+
+(defmacro .home-get ()
+ `(home-json))
+
+(defmacro .home-post ()
+ `(home-json message errormsg))
+
+(defmacro .menu ()
+ `(menu-json))
+
+(defmacro .login ()
+ `(login-json))
+
+(defmacro .login-authenticate ()
+ `(login-authenticate-json dn password))
+
+(defmacro .logout ()
+ `(logout-json))
+
+(defmacro .inetorg-view ()
+ `(inetorg-view-json))
+
+(defmacro .inetorg-view-search ()
+ `(inetorg-view-search-json))
+
+(defmacro .inetorg-view-results ()
+ `(inetorg-view-results-json givenname sn mail postaladdress postalcode st l telephonenumber mobile businesscategory))
+
+(defmacro .inetorg-modify ()
+ `(inetorg-modify-json cn))
+
+(defmacro .inetorg-modify-submit ()
+ `(inetorg-modify-submit-json givenname sn mail postaladdress postalcode st l telephonenumber mobile businesscategory))
+
+(defmacro .inetorg-delete ()
+ `(inetorg-delete-json cn))
+
+(defmacro .inetorg-delete-submit ()
+ `(inetorg-delete-submit-json cn))
+
+(defmacro .inetorg-add ()
+ `(inetorg-add-json))
+
+(defmacro .inetorg-add-submit ()
+ `(inetorg-add-submit-json givenname sn mail postaladdress postalcode st l telephonenumber mobile businesscategory))
+
+(define-endpoint :get "/" () .base)
+(define-endpoint :post "/location" ((location :parameter-type 'string)) .location)
+(define-endpoint :get "/home" () .home-get)
+(define-endpoint :post "/home" ((message :parameter-type 'string) (errormsg :parameter-type 'string)) .home-post)
+(define-endpoint :get "/menu" () .menu)
+(define-endpoint :get "/login" () .login)
+(define-endpoint :post "/login/authenticate" ((dn :parameter-type 'string) (password :parameter-type 'string)) .login-authenticate)
+(define-endpoint :get "/logout" () .logout)
+(define-endpoint :get "/inetorg/view" () .inetorg-view)
+(define-endpoint :get "/inetorg/view/search" () .inetorg-view-search)
+(define-endpoint :post "/inetorg/view/results" ((givenname :parameter-type 'string) (sn :parameter-type 'string) (mail :parameter-type 'string) (postaladdress :parameter-type 'string) (postalcode :parameter-type 'string) (st :parameter-type 'string) (l :parameter-type 'string) (telephonenumber :parameter-type 'string) (mobile :parameter-type 'string) (businesscategory :parameter-type 'string)) .inetorg-view-results)
+(define-endpoint :post "/inetorg/modify" ((cn :parameter-type 'string)) .inetorg-modify)
+(define-endpoint :post "/inetorg/modify/submit" ((givenname :parameter-type 'string) (sn :parameter-type 'string) (mail :parameter-type 'string) (postaladdress :parameter-type 'string) (postalcode :parameter-type 'string) (st :parameter-type 'string) (l :parameter-type 'string) (telephonenumber :parameter-type 'string) (mobile :parameter-type 'string) (businesscategory :parameter-type 'string)) .inetorg-modify-submit)
+(define-endpoint :post "/inetorg/delete" ((cn :parameter-type 'string)) .inetorg-delete)
+(define-endpoint :post "/inetorg/delete/submit" ((cn :parameter-type 'string)) .inetorg-delete-submit)
+(define-endpoint :get "/inetorg/add" () .inetorg-add)
+(define-endpoint :post "/inetorg/add/submit" ((givenname :parameter-type 'string) (sn :parameter-type 'string) (mail :parameter-type 'string) (postaladdress :parameter-type 'string) (postalcode :parameter-type 'string) (st :parameter-type 'string) (l :parameter-type 'string) (telephonenumber :parameter-type 'string) (mobile :parameter-type 'string) (businesscategory :parameter-type 'string)) .inetorg-add-submit)
diff --git a/lisp/webapps/ldapadmin/static/images/edit-delete.png b/lisp/webapps/ldapadmin/static/images/edit-delete.png
new file mode 100644
index 0000000..b0de61d
--- /dev/null
+++ b/lisp/webapps/ldapadmin/static/images/edit-delete.png
Binary files differ
diff --git a/lisp/webapps/ldapadmin/static/js/cljs b/lisp/webapps/ldapadmin/static/js/cljs
new file mode 120000
index 0000000..349848d
--- /dev/null
+++ b/lisp/webapps/ldapadmin/static/js/cljs
@@ -0,0 +1 @@
+../../clojurescript/ldapadmin/resources/public/cljs \ No newline at end of file