From 2facb0c64fa43d877fe85cef84e840316841334e Mon Sep 17 00:00:00 2001 From: Bryce Covert Date: Wed, 11 Jan 2023 11:01:48 -0800 Subject: [PATCH] test --- src/clj/auto_ap/ssr/admin.clj | 260 ++++++++++++++++++ .../auto_ap/views/pages/admin/history.cljs | 40 +++ 2 files changed, 300 insertions(+) create mode 100644 src/clj/auto_ap/ssr/admin.clj create mode 100644 src/cljs/auto_ap/views/pages/admin/history.cljs diff --git a/src/clj/auto_ap/ssr/admin.clj b/src/clj/auto_ap/ssr/admin.clj new file mode 100644 index 00000000..8e6e10f0 --- /dev/null +++ b/src/clj/auto_ap/ssr/admin.clj @@ -0,0 +1,260 @@ +(ns auto-ap.ssr.admin + (:require + [auto-ap.logging :as alog] + [clojure.string :as str] + [clojure.tools.logging :as log] + #_{:clj-kondo/ignore [:refer-all]} + [compojure.core :refer [defroutes GET POST context ANY wrap-routes]] + [compojure.route :as route] + [config.core :refer [env]] + [mount.core :as mount] + [ring.middleware.edn :refer [wrap-edn-params]] + [ring.middleware.multipart-params :as mp] + [ring.middleware.params :refer [wrap-params]] + [ring.middleware.reload :refer [wrap-reload]] + [ring.util.response :as response] + [datomic.api :as d] + [auto-ap.datomic :refer [conn]] + [clj-time.coerce :as coerce] + [auto-ap.time :as atime] + [hiccup.core :refer [html]] + [hiccup.page :refer [html5]] + [unilog.context :as lc])) + +(defn base-page [contents] + [:html.has-navbar-fixed-top + [:head + [:meta {:charset "utf-8"}] + [:meta {:http-equiv "X-UA-Compatible", :content "IE=edge"}] + [:meta {:name "viewport", :content "width=device-width, initial-scale=1"}] + [:title "Integreat"] + [:link {:rel "stylesheet", :href "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css", :integrity "sha256-eZrrJcwDc/3uDhsdt61sL2oOBY362qM3lon1gyExkL0=", :crossorigin "anonymous"}] + [:link {:href "/css/font.min.css", :rel "stylesheet"}] + [:link {:rel "stylesheet", :href "/css/bulma.min.css"}] + [:link {:rel "stylesheet", :href "/css/bulma-calendar.min.css"}] + [:link {:rel "stylesheet", :href "/css/bulma-badge.min.css"}] + [:link {:rel "stylesheet", :href "/css/react-datepicker.min.inc.css"}] + [:link {:rel "stylesheet", :href "/css/animate.css"}] + [:link {:rel "stylesheet", :href "/finance-font/style.css"}] + [:link {:rel "stylesheet", :href "/css/main.css"}] + [:link {:rel "stylesheet", :href "https://unpkg.com/placeholder-loading/dist/css/placeholder-loading.min.css"}] + [:script {:src "https://unpkg.com/hyperscript.org@0.9.7"}] + [:script {:src "https://unpkg.com/htmx.org@1.8.4" + :integrity "sha384-wg5Y/JwF7VxGk4zLsJEcAojRtlVp1FKKdGy1qN+OMtdq72WRvX/EdRdqg/LOhYeV" + :crossorigin="anonymous"}] + [:script {:type "text/javascript", :src "https://cdn.yodlee.com/fastlink/v4/initialize.js", :async "async" }]] + [:body + [:div {:id "app"} + [:div + [:nav {:class "navbar has-shadow is-fixed-top"} + [:div {:class "container"} + [:div {:class "navbar-brand"} + [:a {:class "navbar-item", :href "../"} + [:img {:src "/img/logo.png"}]]] + [:div {:class "navbar-menu"} + [:div {:class "navbar-burger burger", :data-target "navMenu"} + [:span] + [:span] + [:span]]]]] + [:div {:class "columns has-shadow", :id "mail-app", :style "margin-bottom: 0px; height: calc(100vh - 46px);"} + [:aside {:class "column aside menu is-2 "} + [:div {:class "main left-nav"} + [:div]]] + [:div {:class "column messages hero ", :id "message-feed", :style "overflow: auto;"} + (into [:div {:class "inbox-messages"}] + contents)]] + [:div] + [:div {:id "dz-hidden"}]]]]]) + +(defn wrap-html [handler] + (fn [request] + (doto + {:status 200 + :headers {"Content-Type" "text/html"} + :body (handler request) + } + println))) + + +(defn inline-add-deletes [history] + (->> history + (group-by (fn [[a _ t]] + [a t])) + (map (fn [[[a t] changes]] + (let [changes (-> (reduce + (fn [acc [_ v _ added]] + (if added + (assoc acc :added v) + (assoc acc :removed v))) + {} + changes))] + [t a changes]))))) + +(defn format-value [v] + (cond (inst? v) + (-> v + coerce/to-date-time + atime/localize + (atime/unparse atime/normal-date)) + + (nil? v) + [:em "(none)"] + + (and (integer? v) + (> v 1000000)) + [:span + [:a {:hx-get (str "/admin/history/" v) + :hx-swap "innerHTML" + :hx-push-url "true" + :hx-target "#history-table"} + v] + " [" [:a + {:hx-get (str "/admin/history/inspect/" v) + :hx-swap "innerHTML" + :hx-target "#inspector" + :hx-trigger "click"} + "snapshot"] "]" + ] + + + :else + (pr-str v))) + +(comment "_" ) +(defn page-template [& {:keys [table entity-id]}] + [:div + [:div.columns + [:div.column.is-4 + [:form.hello {"hx-target" "#history-table" + "hx-post" "/admin/history/search" + "hx-swap" "innerHTML" + "_" (hiccup2.core/raw "on submit toggle @disabled on me then toggle .is-loading on <#dig/> end + on htmx:afterRequest toggle @disabled on me then toggle .is-loading on <#dig /> end") + } + [:div.field.is-grouped + [:p.control {} + [:input.input {:type "text" :name "entity-id" :placeholder "Entity id" }]] + [:p.control + [:button#dig.button.is-primary {} + "Dig"]]]]]] + [:div#history-table + table]]) + +(defn history-search [{:keys [form-params params] :as request}] + (log/info ::request + request) + (try + (let [entity-id (Long/parseLong (or (some-> (:entity-id form-params) not-empty) + (:entity-id params))) + history (->> + (d/q '[:find ?a2 ?v (pull ?tx [:db/txInstant :audit/user :db/id]) ?ad + :in $ $$ ?i + :where + [$$ ?i ?a ?v ?tx ?ad] + [$ ?a :db/ident ?a2]] + (d/db conn) + (d/history (d/db conn)) + entity-id ) + inline-add-deletes + (sort-by (comp :db/txInstant :tx)) + vec) + best-guess-entity (->> history + (group-by + (comp + namespace + second) + ) + (map (fn [[k v]] + [k v])) + (sort-by second) + last + first) + table [:div [:h1.title "History for " + (str/capitalize best-guess-entity) + " " + entity-id] + [:div.columns + [:div.column.is-9 + [:table.table.compact.grid {:style "width: 100%"} + [:thead + [:tr + [:td {:style "width: 12em"} "Transaction" ] + [:td {:style "width: 12em"} "Date"] + [:td {:style "width: 12em"} "User"] + [:td {:style "width: 15em"} "Field"] + [:td "Change"]]] + [:tbody + (for [[tx a c] history] + [:tr + [:td (:db/id tx)] + [:td (-> (:db/txInstant tx) + coerce/to-date-time + atime/localize + (atime/unparse atime/standard-time))] + [:td (str (:audit/user tx))] + [:td (namespace a) ": " (name a)] + [:td (format-value (:removed c)) + " => " + (format-value (:added c))]])] + ]] + [:div.column.is-3 + [:div#inspector.card]]]]] + + (alog/info ::trace + :bge best-guess-entity + :headers (:headers request)) + (html (if (= "history-table" + (get (:headers request) "hx-target")) + table + (do + (println "WHOLE PAGOE") + (page-template :table table + :entity-id entity-id))))) + (catch NumberFormatException e + (html [:div.notification.is-danger.is-light + "Cannot parse the entity-id " (or (:entity-id form-params) + (:entity-id params)) + + ". It should be a number."])))) + +(defn inspect [{{:keys [entity-id]} :params :as request}] + (alog/info ::inspect + :request request) + (try + (let [entity-id (Long/parseLong entity-id) + data (d/pull (d/db conn) + '[*] + entity-id + ) ] + + + (html [:div.box [:h1.title "Snapshot of " + entity-id] + [:ul + (for [[k v] data] + [:li [:strong k] ":" v] + )]])) + (catch NumberFormatException e + (html [:div.notification.is-danger.is-light + "Cannot parse the entity-id " entity-id ". It should be a number."])))) + +(defn history [request] + (str "" + (html + (base-page (page-template ))))) + +(defn- with-ignore-trailing-slash [handler] + (fn [request] + (let [uri (request :uri) + clean-uri (str/replace uri #"^(.+?)/+$" "$1")] + (handler (assoc request :uri clean-uri))))) + +(defroutes admin-routes + (wrap-routes + (context "/admin" [] + (GET "/history" [] history) + (GET "/history/" [] history) + (POST "/history/search" [] history-search) + #_(GET "/history/:entity-id" [entity-id] history-search) + #_(GET "/history/inspect/:entity-id" [entity-id] inspect)) + with-ignore-trailing-slash)) diff --git a/src/cljs/auto_ap/views/pages/admin/history.cljs b/src/cljs/auto_ap/views/pages/admin/history.cljs new file mode 100644 index 00000000..156c5ed3 --- /dev/null +++ b/src/cljs/auto_ap/views/pages/admin/history.cljs @@ -0,0 +1,40 @@ +(ns auto-ap.views.pages.admin.history + (:require + [auto-ap.status :as status] + [auto-ap.subs :as subs] + [auto-ap.views.components.admin.side-bar :refer [admin-side-bar]] + [auto-ap.views.components.layouts :refer [side-bar-layout]] + [auto-ap.views.pages.admin.jobs.table :as table] + [auto-ap.views.components.modal :as modal] + [auto-ap.views.components :as com] + [auto-ap.views.pages.data-page :as data-page] + [auto-ap.views.utils :refer [dispatch-event with-user]] + [clojure.set :as set] + [re-frame.core :as re-frame] + [vimsical.re-frame.fx.track :as track] + [auto-ap.forms.builder :as form-builder] + [vimsical.re-frame.cofx.inject :as inject] + [auto-ap.forms :as forms] + [clojure.string :as str] + ["htmx.org" :as htmx] + )) + +;; VIEWS +(def history-content + (let [c (atom nil)] + (with-meta + (fn [] + (println "bothered?") + [:div {"hx-get" (.-pathname (.-location js/document)) + "hx-trigger" "load" + "hx-swap" "outerHTML" + :ref (fn [i] (reset! c i))} + "test"]) + {:should-component-update (fn [] false) + :component-did-mount (fn [] + (.process htmx @c))}))) + +(defn history-page [] + [side-bar-layout {:side-bar [admin-side-bar {}] + :main [history-content]}]) +