Adds admin sidebar
This commit is contained in:
190
src/clj/auto_ap/ssr/admin/history.clj
Normal file
190
src/clj/auto_ap/ssr/admin/history.clj
Normal file
@@ -0,0 +1,190 @@
|
||||
(ns auto-ap.ssr.admin.history
|
||||
(:require
|
||||
[auto-ap.datomic :refer [conn]]
|
||||
[auto-ap.logging :as alog]
|
||||
[auto-ap.ssr.ui :refer [base-page]]
|
||||
[auto-ap.ssr.utils :refer [html-response]]
|
||||
[auto-ap.time :as atime]
|
||||
[clj-time.coerce :as coerce]
|
||||
[clojure.string :as str]
|
||||
[datomic.api :as dc]
|
||||
[auto-ap.ssr.components :as com]
|
||||
[auto-ap.ssr-routes :as ssr-routes]
|
||||
[bidi.bidi :as bidi]))
|
||||
|
||||
(defn tx-rows->changes [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
|
||||
(com/link
|
||||
{:href "#"
|
||||
:hx-get (str "/admin/history/" v)
|
||||
:hx-swap "innerHTML"
|
||||
:hx-push-url "true"
|
||||
:hx-select "#history-table"
|
||||
:hx-target "#history-table"}
|
||||
v)
|
||||
" ["
|
||||
(com/link
|
||||
{:href "#"
|
||||
:hx-get (str "/admin/history/inspect/" v)
|
||||
:hx-swap "innerHTML"
|
||||
:hx-target "#inspector"
|
||||
:hx-trigger "click"}
|
||||
"snapshot") "]"]
|
||||
|
||||
:else
|
||||
(pr-str v)))
|
||||
|
||||
|
||||
(defn inspect [{{:keys [entity-id]} :params :as request}]
|
||||
(alog/info ::inspect
|
||||
:request request)
|
||||
(try
|
||||
(let [entity-id (Long/parseLong entity-id)
|
||||
data (dc/pull (dc/db conn)
|
||||
'[*]
|
||||
entity-id)]
|
||||
|
||||
(html-response
|
||||
[:section.py-3.sm:py-5.max-w-lg
|
||||
(com/card {:class "p-2"}
|
||||
[:div {:style {:display "inline-block"}}
|
||||
[:h1.title "Snapshot of "
|
||||
entity-id]
|
||||
[:ul
|
||||
(for [[k v] data]
|
||||
[:li [:strong k] ":" (format-value v)])]])]))
|
||||
(catch NumberFormatException _
|
||||
(html-response
|
||||
[:div.notification.is-danger.is-light
|
||||
"Cannot parse the entity-id " entity-id ". It should be a number."]))))
|
||||
|
||||
(defn result-table [{:keys [entity-id]}]
|
||||
(try
|
||||
(let [history (->>
|
||||
(dc/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]]
|
||||
(dc/db conn)
|
||||
(dc/history (dc/db conn))
|
||||
entity-id)
|
||||
tx-rows->changes
|
||||
(sort-by (comp :db/id first))
|
||||
vec)
|
||||
best-guess-entity (or (->> history
|
||||
(group-by
|
||||
(comp
|
||||
namespace
|
||||
second))
|
||||
(map (fn [[k v]]
|
||||
[k v]))
|
||||
(sort-by second)
|
||||
last
|
||||
first)
|
||||
"?")]
|
||||
(com/data-grid-card {:id "history-table"
|
||||
:title (format "History for %s: %d" (str/capitalize best-guess-entity) entity-id)
|
||||
:route :history-table
|
||||
:paginate? false
|
||||
:total (count history)
|
||||
:subtitle nil
|
||||
:action-buttons nil
|
||||
:rows (for [[tx a c] history]
|
||||
(com/data-grid-row
|
||||
{}
|
||||
(com/data-grid-cell {} [:div [:div (some-> (:db/txInstant tx)
|
||||
coerce/to-date-time
|
||||
atime/localize
|
||||
(atime/unparse atime/standard-time))]
|
||||
[:div.tag (:db/id tx)]])
|
||||
(com/data-grid-cell {} (str (:audit/user tx)))
|
||||
(com/data-grid-cell {} (namespace a) ": " (name a))
|
||||
|
||||
(com/data-grid-cell {}
|
||||
(com/pill {:color :red}
|
||||
(format-value (:removed c))))
|
||||
(com/data-grid-cell {}
|
||||
[:div.tag.is-primary.is-light
|
||||
[:span
|
||||
(format-value (:added c))]])))
|
||||
:headers
|
||||
[(com/data-grid-header {}
|
||||
"Date")
|
||||
(com/data-grid-header {}
|
||||
"User")
|
||||
(com/data-grid-header {}
|
||||
"Field")
|
||||
(com/data-grid-header {}
|
||||
"From")
|
||||
(com/data-grid-header {}
|
||||
"To")]}))
|
||||
(catch NumberFormatException e
|
||||
(throw e))))
|
||||
|
||||
(defn search-box [{:keys [entity-id]}]
|
||||
[:div.mt-4
|
||||
[:form.flex.gap-2 {"hx-target" "#history-table"
|
||||
"hx-get" (bidi/path-for ssr-routes/only-routes
|
||||
:admin-history)
|
||||
"hx-select" "#history-table"
|
||||
"hx-swap" "innerHTML"
|
||||
"hx-ext" "debug"
|
||||
"hx-push-url" "true"}
|
||||
(com/text-input {:name "entity-id" :placeholder "Entity Id" :value entity-id
|
||||
:style {:width "300px"}})
|
||||
(com/button {:color :primary}
|
||||
"DIG")]])
|
||||
|
||||
(defn page [{:keys [matched-route route-params query-params] :as request}]
|
||||
(let [entity-id (or (some-> query-params (get "entity-id") Long/parseLong)
|
||||
(some-> route-params (get :entity-id) Long/parseLong))]
|
||||
(base-page request
|
||||
(com/page {:nav (com/admin-aside-nav)
|
||||
:active-client (:client (:session request))
|
||||
:identity (:identity request)
|
||||
:app-params {:hx-get (bidi/path-for ssr-routes/only-routes
|
||||
:admin-history)
|
||||
:hx-trigger "clientSelected from:body"
|
||||
:hx-select "#app-contents"
|
||||
:hx-swap "outerHTML swap:300ms"}}
|
||||
(com/breadcrumbs {}
|
||||
[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||
:admin)}
|
||||
"Admin"]
|
||||
[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||
:admin-history)}
|
||||
"History"])
|
||||
(search-box {:entity-id entity-id})
|
||||
[:div.flex.gap-4.flex-col.lg:flex-row
|
||||
(if entity-id
|
||||
(result-table {:entity-id entity-id})
|
||||
[:div#history-table])
|
||||
[:div#inspector]
|
||||
])
|
||||
"History")))
|
||||
Reference in New Issue
Block a user