Implements the ability to dig into history
This commit is contained in:
@@ -10,7 +10,6 @@
|
|||||||
"@popperjs/core": "^2.11.5",
|
"@popperjs/core": "^2.11.5",
|
||||||
"downshift": "^6.1.3",
|
"downshift": "^6.1.3",
|
||||||
"dropzone": "^4.3.0",
|
"dropzone": "^4.3.0",
|
||||||
"htmx.org": "^1.8.4",
|
|
||||||
"minisearch": "^3.0.2",
|
"minisearch": "^3.0.2",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
"react": "^17.0.1",
|
"react": "^17.0.1",
|
||||||
|
|||||||
@@ -1,27 +1,17 @@
|
|||||||
(ns auto-ap.ssr.admin
|
(ns auto-ap.ssr.admin
|
||||||
(:require
|
(:require
|
||||||
|
[auto-ap.datomic :refer [conn]]
|
||||||
|
[auto-ap.graphql.utils :refer [assert-admin]]
|
||||||
[auto-ap.logging :as alog]
|
[auto-ap.logging :as alog]
|
||||||
|
[auto-ap.time :as atime]
|
||||||
|
[clj-time.coerce :as coerce]
|
||||||
[clojure.string :as str]
|
[clojure.string :as str]
|
||||||
[clojure.tools.logging :as log]
|
[clojure.tools.logging :as log]
|
||||||
#_{:clj-kondo/ignore [:refer-all]}
|
[compojure.core :refer [context defroutes GET POST routes]]
|
||||||
[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]
|
[datomic.api :as d]
|
||||||
[auto-ap.datomic :refer [conn]]
|
[hiccup2.core :as hiccup]))
|
||||||
[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]
|
(defn base-page [request contents]
|
||||||
[:html.has-navbar-fixed-top
|
[:html.has-navbar-fixed-top
|
||||||
[:head
|
[:head
|
||||||
[:meta {:charset "utf-8"}]
|
[:meta {:charset "utf-8"}]
|
||||||
@@ -43,37 +33,57 @@
|
|||||||
:integrity "sha384-wg5Y/JwF7VxGk4zLsJEcAojRtlVp1FKKdGy1qN+OMtdq72WRvX/EdRdqg/LOhYeV"
|
:integrity "sha384-wg5Y/JwF7VxGk4zLsJEcAojRtlVp1FKKdGy1qN+OMtdq72WRvX/EdRdqg/LOhYeV"
|
||||||
:crossorigin="anonymous"}]
|
:crossorigin="anonymous"}]
|
||||||
[:script {:type "text/javascript", :src "https://cdn.yodlee.com/fastlink/v4/initialize.js", :async "async" }]]
|
[:script {:type "text/javascript", :src "https://cdn.yodlee.com/fastlink/v4/initialize.js", :async "async" }]]
|
||||||
|
[:script {:type "text/javascript"}
|
||||||
|
(hiccup/raw
|
||||||
|
(str
|
||||||
|
"
|
||||||
|
window.onload = function () {
|
||||||
|
document.body.addEventListener(\"htmx:configRequest\", function(event) {
|
||||||
|
event.detail.headers[\"Authorization\"] = \"Token " (get (:query-params request) "jwt")" \" ;
|
||||||
|
});
|
||||||
|
}"))]
|
||||||
|
|
||||||
[:body
|
[:body
|
||||||
[:div {:id "app"}
|
[:div {:id "app"}
|
||||||
[:div
|
[:div
|
||||||
[:nav {:class "navbar has-shadow is-fixed-top"}
|
[:nav {:class "navbar has-shadow is-fixed-top is-grey"}
|
||||||
|
|
||||||
[:div {:class "container"}
|
[:div {:class "container"}
|
||||||
[:div {:class "navbar-brand"}
|
[:div {:class "navbar-brand"}
|
||||||
[:a {:class "navbar-item", :href "../"}
|
[:a {:class "navbar-item", :href "../"}
|
||||||
[:img {:src "/img/logo.png"}]]]
|
[:img {:src "/img/logo.png"}]]]
|
||||||
[:div {:class "navbar-menu"}
|
[:div.navbar-menu {:id "navMenu"}
|
||||||
[:div {:class "navbar-burger burger", :data-target "navMenu"}
|
[:div.navbar-start
|
||||||
[:span]
|
[:a.navbar-item {:href "/"}
|
||||||
[:span]
|
"Home" ]
|
||||||
[:span]]]]]
|
[:a.navbar-item {:href "/invoices/"}
|
||||||
|
"Invoices" ]
|
||||||
|
[:a.navbar-item {:href "/payments/"}
|
||||||
|
"Payments" ]
|
||||||
|
[:a.navbar-item {:href "/pos/sales-orders/"}
|
||||||
|
"POS" ]
|
||||||
|
[:a.navbar-item {:href "/transactions/"}
|
||||||
|
"Transactions" ]
|
||||||
|
|
||||||
|
[:a.navbar-item {:href "/ledger/"}
|
||||||
|
"Ledger" ]]]]]
|
||||||
[:div {:class "columns has-shadow", :id "mail-app", :style "margin-bottom: 0px; height: calc(100vh - 46px);"}
|
[:div {:class "columns has-shadow", :id "mail-app", :style "margin-bottom: 0px; height: calc(100vh - 46px);"}
|
||||||
[:aside {:class "column aside menu is-2 "}
|
[:aside {:class "column aside menu is-2 "}
|
||||||
[:div {:class "main left-nav"}
|
[:div {:class "main left-nav"}
|
||||||
[:div]]]
|
[:div]]]
|
||||||
[:div {:class "column messages hero ", :id "message-feed", :style "overflow: auto;"}
|
[:div {:class "column messages hero ", :id "message-feed", :style "overflow: auto;"}
|
||||||
(into [:div {:class "inbox-messages"}]
|
[:div {:class "inbox-messages"}
|
||||||
contents)]]
|
contents]]]
|
||||||
[:div]
|
[:div]
|
||||||
[:div {:id "dz-hidden"}]]]]])
|
[:div {:id "dz-hidden"}]]]]])
|
||||||
|
|
||||||
(defn wrap-html [handler]
|
(defn html-response [hiccup]
|
||||||
(fn [request]
|
|
||||||
(doto
|
|
||||||
{:status 200
|
{:status 200
|
||||||
:headers {"Content-Type" "text/html"}
|
:headers {"Content-Type" "text/html"}
|
||||||
:body (handler request)
|
:body (str
|
||||||
}
|
(hiccup/html
|
||||||
println)))
|
{}
|
||||||
|
hiccup))})
|
||||||
|
|
||||||
|
|
||||||
(defn inline-add-deletes [history]
|
(defn inline-add-deletes [history]
|
||||||
@@ -121,6 +131,7 @@
|
|||||||
(pr-str v)))
|
(pr-str v)))
|
||||||
|
|
||||||
(comment "_" )
|
(comment "_" )
|
||||||
|
|
||||||
(defn page-template [& {:keys [table entity-id]}]
|
(defn page-template [& {:keys [table entity-id]}]
|
||||||
[:div
|
[:div
|
||||||
[:div.columns
|
[:div.columns
|
||||||
@@ -128,19 +139,21 @@
|
|||||||
[:form.hello {"hx-target" "#history-table"
|
[:form.hello {"hx-target" "#history-table"
|
||||||
"hx-post" "/admin/history/search"
|
"hx-post" "/admin/history/search"
|
||||||
"hx-swap" "innerHTML"
|
"hx-swap" "innerHTML"
|
||||||
"_" (hiccup2.core/raw "on submit toggle @disabled on me then toggle .is-loading on <#dig/> end
|
"_" (hiccup/raw "on htmx:beforeRequest 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")
|
on htmx:afterRequest toggle @disabled on me then toggle .is-loading on <#dig /> end")
|
||||||
}
|
}
|
||||||
[:div.field.is-grouped
|
[:div.field.is-grouped
|
||||||
[:p.control {}
|
[:p.control {}
|
||||||
[:input.input {:type "text" :name "entity-id" :placeholder "Entity id" }]]
|
[:input.input {:type "text" :name "entity-id" :placeholder "Entity id" :value entity-id}]]
|
||||||
[:p.control
|
[:p.control
|
||||||
[:button#dig.button.is-primary {}
|
[:button#dig.button.is-primary {}
|
||||||
"Dig"]]]]]]
|
"Dig"]]]]]]
|
||||||
[:div#history-table
|
[:div#history-table
|
||||||
table]])
|
table]])
|
||||||
|
|
||||||
(defn history-search [{:keys [form-params params] :as request}]
|
(defn history-search [{:keys [form-params params] identity :identity :as request}]
|
||||||
|
|
||||||
|
(assert-admin identity)
|
||||||
(log/info ::request
|
(log/info ::request
|
||||||
request)
|
request)
|
||||||
(try
|
(try
|
||||||
@@ -158,7 +171,7 @@
|
|||||||
inline-add-deletes
|
inline-add-deletes
|
||||||
(sort-by (comp :db/txInstant :tx))
|
(sort-by (comp :db/txInstant :tx))
|
||||||
vec)
|
vec)
|
||||||
best-guess-entity (->> history
|
best-guess-entity (or (->> history
|
||||||
(group-by
|
(group-by
|
||||||
(comp
|
(comp
|
||||||
namespace
|
namespace
|
||||||
@@ -169,6 +182,7 @@
|
|||||||
(sort-by second)
|
(sort-by second)
|
||||||
last
|
last
|
||||||
first)
|
first)
|
||||||
|
"?")
|
||||||
table [:div [:h1.title "History for "
|
table [:div [:h1.title "History for "
|
||||||
(str/capitalize best-guess-entity)
|
(str/capitalize best-guess-entity)
|
||||||
" "
|
" "
|
||||||
@@ -181,7 +195,7 @@
|
|||||||
[:td {:style "width: 12em"} "Transaction" ]
|
[:td {:style "width: 12em"} "Transaction" ]
|
||||||
[:td {:style "width: 12em"} "Date"]
|
[:td {:style "width: 12em"} "Date"]
|
||||||
[:td {:style "width: 12em"} "User"]
|
[:td {:style "width: 12em"} "User"]
|
||||||
[:td {:style "width: 15em"} "Field"]
|
[:td {:style "width: 10em"} "Field"]
|
||||||
[:td "Change"]]]
|
[:td "Change"]]]
|
||||||
[:tbody
|
[:tbody
|
||||||
(for [[tx a c] history]
|
(for [[tx a c] history]
|
||||||
@@ -193,9 +207,16 @@
|
|||||||
(atime/unparse atime/standard-time))]
|
(atime/unparse atime/standard-time))]
|
||||||
[:td (str (:audit/user tx))]
|
[:td (str (:audit/user tx))]
|
||||||
[:td (namespace a) ": " (name a)]
|
[:td (namespace a) ": " (name a)]
|
||||||
[:td (format-value (:removed c))
|
[:td
|
||||||
" => "
|
[:div.tags
|
||||||
(format-value (:added c))]])]
|
[:div.tag.is-danger.is-light
|
||||||
|
[:span
|
||||||
|
"FROM "
|
||||||
|
(format-value (:removed c))]]
|
||||||
|
[:div.tag.is-primary.is-light
|
||||||
|
[:span
|
||||||
|
"TO "
|
||||||
|
(format-value (:added c))]]]]])]
|
||||||
]]
|
]]
|
||||||
[:div.column.is-3
|
[:div.column.is-3
|
||||||
[:div#inspector.card]]]]]
|
[:div#inspector.card]]]]]
|
||||||
@@ -203,23 +224,24 @@
|
|||||||
(alog/info ::trace
|
(alog/info ::trace
|
||||||
:bge best-guess-entity
|
:bge best-guess-entity
|
||||||
:headers (:headers request))
|
:headers (:headers request))
|
||||||
(html (if (= "history-table"
|
(html-response
|
||||||
(get (:headers request) "hx-target"))
|
(if (get (:headers request) "hx-request")
|
||||||
table
|
table
|
||||||
(do
|
(base-page request
|
||||||
(println "WHOLE PAGOE")
|
|
||||||
(page-template :table table
|
(page-template :table table
|
||||||
:entity-id entity-id)))))
|
:entity-id entity-id)))))
|
||||||
(catch NumberFormatException e
|
(catch NumberFormatException e
|
||||||
(html [:div.notification.is-danger.is-light
|
(html-response
|
||||||
|
(str [:div.notification.is-danger.is-light
|
||||||
"Cannot parse the entity-id " (or (:entity-id form-params)
|
"Cannot parse the entity-id " (or (:entity-id form-params)
|
||||||
(:entity-id params))
|
(:entity-id params))
|
||||||
|
|
||||||
". It should be a number."]))))
|
". It should be a number."])))))
|
||||||
|
|
||||||
(defn inspect [{{:keys [entity-id]} :params :as request}]
|
(defn inspect [{{:keys [entity-id]} :params identity :identity :as request}]
|
||||||
(alog/info ::inspect
|
(alog/info ::inspect
|
||||||
:request request)
|
:request request)
|
||||||
|
(assert-admin identity)
|
||||||
(try
|
(try
|
||||||
(let [entity-id (Long/parseLong entity-id)
|
(let [entity-id (Long/parseLong entity-id)
|
||||||
data (d/pull (d/db conn)
|
data (d/pull (d/db conn)
|
||||||
@@ -227,34 +249,27 @@
|
|||||||
entity-id
|
entity-id
|
||||||
) ]
|
) ]
|
||||||
|
|
||||||
|
(html-response
|
||||||
(html [:div.box [:h1.title "Snapshot of "
|
[:div.box [:h1.title "Snapshot of "
|
||||||
entity-id]
|
entity-id]
|
||||||
[:ul
|
[:ul
|
||||||
(for [[k v] data]
|
(for [[k v] data]
|
||||||
[:li [:strong k] ":" v]
|
[:li [:strong k] ":" v]
|
||||||
)]]))
|
)]]))
|
||||||
(catch NumberFormatException e
|
(catch NumberFormatException e
|
||||||
(html [:div.notification.is-danger.is-light
|
(html-response
|
||||||
|
[:div.notification.is-danger.is-light
|
||||||
"Cannot parse the entity-id " entity-id ". It should be a number."]))))
|
"Cannot parse the entity-id " entity-id ". It should be a number."]))))
|
||||||
|
|
||||||
(defn history [request]
|
(defn history [{:keys [identity] :as request}]
|
||||||
(str "<!DOCTYPE html>"
|
(html-response
|
||||||
(html
|
(base-page request (page-template ))))
|
||||||
(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
|
(defroutes admin-routes
|
||||||
(wrap-routes
|
(routes
|
||||||
(context "/admin" []
|
(context "/admin" []
|
||||||
(GET "/history" [] history)
|
(GET "/history" [] history)
|
||||||
(GET "/history/" [] history)
|
(GET "/history/" [] history)
|
||||||
(POST "/history/search" [] history-search)
|
(POST "/history/search" [] history-search)
|
||||||
#_(GET "/history/:entity-id" [entity-id] history-search)
|
(GET "/history/:entity-id" [entity-id] history-search)
|
||||||
#_(GET "/history/inspect/:entity-id" [entity-id] inspect))
|
(GET "/history/inspect/:entity-id" [entity-id] inspect))))
|
||||||
with-ignore-trailing-slash))
|
|
||||||
|
|||||||
@@ -381,7 +381,7 @@
|
|||||||
|
|
||||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||||
(defn tx-detail [i]
|
(defn tx-detail [i]
|
||||||
(map (juxt :e #(d/ident (d/db (d/connect uri)) (:a %)) :v)
|
(map (juxt :e #(d/ident (d/db (d/connect uri)) (:a %)) :v :added?)
|
||||||
(:data (first
|
(:data (first
|
||||||
(d/tx-range (d/log (d/connect uri))
|
(d/tx-range (d/log (d/connect uri))
|
||||||
i
|
i
|
||||||
|
|||||||
@@ -27,13 +27,7 @@
|
|||||||
(defn ^:export init []
|
(defn ^:export init []
|
||||||
(dev-setup)
|
(dev-setup)
|
||||||
|
|
||||||
(.addEventListener (.-body js/document)
|
|
||||||
"htmx:configRequest"
|
|
||||||
(fn [event]
|
|
||||||
(aset (.-headers (.-detail event))
|
|
||||||
"Authorization"
|
|
||||||
(some->> (.getItem js/localStorage "jwt")
|
|
||||||
(str "Token ")))))
|
|
||||||
|
|
||||||
|
|
||||||
;; document.body.addEventListener('htmx:configRequest', function(evt) {
|
;; document.body.addEventListener('htmx:configRequest', function(evt) {
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
[:span {:class "icon icon-cog-play-1" :style {:font-size "25px"}}]
|
[:span {:class "icon icon-cog-play-1" :style {:font-size "25px"}}]
|
||||||
[:span {:class "name"} "Rules"]]]
|
[:span {:class "name"} "Rules"]]]
|
||||||
[:li.menu-item
|
[:li.menu-item
|
||||||
[:a {:href "/admin/history", :class (str "item" (active-when ap = :admin-history))}
|
[:a {:href (str "/admin/history?jwt=" (.getItem js/localStorage "jwt")) :class (str "item" (active-when ap = :admin-history))}
|
||||||
[:span {:class "icon icon-cog-play-1" :style {:font-size "25px"}}]
|
[:span {:class "icon icon-cog-play-1" :style {:font-size "25px"}}]
|
||||||
[:span {:class "name"} "History"]]]
|
[:span {:class "name"} "History"]]]
|
||||||
[:ul ]]
|
[:ul ]]
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
[auto-ap.views.pages.error :refer [error-page]]
|
[auto-ap.views.pages.error :refer [error-page]]
|
||||||
[auto-ap.views.pages.ledger.balance-sheet :refer [balance-sheet-page]]
|
[auto-ap.views.pages.ledger.balance-sheet :refer [balance-sheet-page]]
|
||||||
[auto-ap.views.pages.admin.jobs :refer [jobs-page]]
|
[auto-ap.views.pages.admin.jobs :refer [jobs-page]]
|
||||||
[auto-ap.views.pages.admin.history :refer [history-page]]
|
|
||||||
[auto-ap.views.pages.ledger.external-import :refer [external-import-page]]
|
[auto-ap.views.pages.ledger.external-import :refer [external-import-page]]
|
||||||
[auto-ap.views.pages.ledger.external-ledger :refer [external-ledger-page]]
|
[auto-ap.views.pages.ledger.external-ledger :refer [external-ledger-page]]
|
||||||
[auto-ap.views.pages.ledger.profit-and-loss :refer [profit-and-loss-page]]
|
[auto-ap.views.pages.ledger.profit-and-loss :refer [profit-and-loss-page]]
|
||||||
@@ -155,9 +154,6 @@
|
|||||||
[admin-excel-import-page])
|
[admin-excel-import-page])
|
||||||
|
|
||||||
|
|
||||||
(defmethod page :admin-history [_]
|
|
||||||
[history-page])
|
|
||||||
|
|
||||||
(defmethod page :initial-error [_]
|
(defmethod page :initial-error [_]
|
||||||
[error-page])
|
[error-page])
|
||||||
|
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
(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]}])
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user