diff --git a/.gitignore b/.gitignore index cdd1de3..1b949e8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -/resources/public/js/compiled/** +/resources/public/js/** figwheel_server.log pom.xml *jar @@ -11,3 +11,6 @@ pom.xml .lein-plugins/ .repl .nrepl-port +node_modules/ +package-lock.json +.shadow-cljs/ diff --git a/README.md b/README.md index 1c14f9d..39ed9c5 100644 --- a/README.md +++ b/README.md @@ -8,23 +8,46 @@ The new (as of 2020) CLJS frontend dashboard for Yetibot We chose `shadow-cljs` as our main build tool for the project. `shadow-cljs` is easy to use and configure and comes with a very good integration with the other tools that we already use on the other sub-projects under `Yetibot`, for e.g `Leiningen`. -See the `shadow-cljs` _user-guide_ on how to install it. +See the [`shadow-cljs` _user-guide_](https://shadow-cljs.github.io/docs/UsersGuide.html "Shadow-cljs user guide") on how to install it. -## Leiningen +# Development workflow using the shadow-cljs CLI -`Leiningen` is the Clojure build tool that `Yetibot` uses. For `dashboard`, `shadow-cljs` delegates the _dependency management_ to `leiningen`. +There are two (2) main steps to get into the development workflow : -See the `Leiningen` Installation docs to install it. +* Starting the `shadow-cljs` server which will be (re)used by any process to interact with the app. +* Starting a `watch` process which will, as its name suggests, watch and push any changes in the project code to the runtime (_hot reload_). -# Development workflow using the shadow-cljs CLI +> Starting a `watch` process actually starts a server process _automatically_ (step 2). +> +> When the _server_ is launched via the _watch_ process, the _server_ will have to be killed if the _watch_ has to be restarted. +> When launched separately, the `watch` process can be killed and restarted without restarting the _server_ and the `watch` will just reuse the existing server process. -## Running a watch process +## Starting the `shadow-cljs` server -To start a `watch` process which will monitor changes and automatically recompile the `dashboard` code base, run the following from your terminal: +To start the server, just type in : -`$ shadow-cljs watch :dashboard` +`$ shadow-cljs start` -a `watch` will implicitly start a _server process_ which will be re-used by all commands sent by the CLI, instead of starting a new JVM. +It will then spin up a new jvm process for the server and the following output should be displayed on the console. + +```shell script +shadow-cljs - config: /home/kaffein/Projects/dashboard/shadow-cljs.edn +shadow-cljs - updating dependencies +shadow-cljs - dependencies updated +shadow-cljs - server starting ........................................................................ +ready! +``` + +> As part of the spinning-up process, the server process will also expose an _nrepl_ server via TCP. +> It can be used to manage the `shadow-cljs` process or interact with the application in its runtime. + +## Running a `watch` process + +To start a `watch` process which will monitor changes and automatically recompile the `dashboard` code base, run the following incantation from your terminal: + +`$ shadow-cljs watch dashboard` + +the output on the console should give something like this : ```shell script + react-dom@16.13.0 @@ -40,7 +63,7 @@ a `watch` will implicitly start a _server process_ which will be re-used by all [:dashboard] Build completed. (157 files, 156 compiled, 0 warnings, 35.04s) ``` -To stop the `watch` process, just type in CTRL-C from the CLI : +To stop the `watch` process, just `CTRL-C` at the command line : ```shell script ... @@ -51,109 +74,32 @@ To stop the `watch` process, just type in CTRL-C from the CLI : Ctrl^C ``` -## The REPL - -To start the `REPL`, run : - -`$ shadow-cljs browser-repl :dashboard` +## Checking the _dev_ setup -It will launch a browser providing the `runtime` used to evaluate the code entered in the `REPL` - -``` -+ react-dom@16.13.0 -+ react@16.13.0 -updated 2 packages and audited 36 packages in 1.479s -found 0 vulnerabilities - -[:browser-repl] Configuring build. -[:browser-repl] Compiling ... -[:browser-repl] Build completed. (157 files, 156 compiled, 0 warnings, 21.91s) -cljs.user=> -``` +At this point, to check whether everything works as expected : -# Development workflow using the provided `dev.clj` namespace +* open the application at `http://localhost:3000/` in the browser +* `jack-in`/`connect` your editor to the nrepl process -We also provide a `dev.clj` namespace in `dev/dev.clj` which allows for a more Clojure-_friendly_ workflow during development. -This namespace contains a few utility functions for _programmatically_ interacting with `shadow-cljs` through its Clojure API. -The first step is to `eval` those functions in your REPL. +Once at the `repl` prompt, type in : -```clojure +```shell script +To quit, type: :cljs/quit ... -(defn- compile-build - "Compiles a build `build-id` where `build-id` is a - keyword identifying a build. - This is equivalent to invoking : - $ shadow-cljs compile :build-id" - [build-id] - (shadow/compile build-id)) -=> nil -=> #'dev/start-server -=> #'dev/stop-server -=> #'dev/re-init-server -=> #'dev/watch-build -=> #'dev/compile-build - -``` - -You can then `watch` or `compile` a build, `start`, `stop` or `re-init` the `shadow-cljs` server by just calling those functions from within the REPL. - -## Starting the shadow-cljs server - -```clojure -;; starts a shadow-cljs server -(start-server) -avr. 13, 2020 1:08:25 PM org.xnio.Xnio -INFO: XNIO version 3.7.3.Final -avr. 13, 2020 1:08:25 PM org.xnio.nio.NioXnio -INFO: XNIO NIO Implementation Version 3.7.3.Final -avr. 13, 2020 1:08:25 PM org.jboss.threads.Version -INFO: JBoss Threads version 2.3.2.Final -shadow-cljs - server version: 2.8.94 running at http://localhost:9630 -shadow-cljs - nREPL server started on port 34619 -=> :shadow.cljs.devtools.server/started - -;; since a shadow-cljs server is already running -(start-server) -=> :shadow.cljs.devtools.server/already-running +cljs.user> (js/alert "test") ``` +a popup should appear from inside the application. -## Shutting down the shadow-cljs server +# Compile the project -```clojure -;; stops the running shadow-cljs server -(stop-server) -shutting down ... -=> nil -``` - -## Re-initializing the shadow-cljs server +To compile the project once and exit : -```clojure -;; re-initializes the running shadow-cljs server -(re-init-server) -shadow-cljs - server version: 2.8.94 running at http://localhost:9630 -shadow-cljs - nREPL server started on port 42915 -=> :shadow.cljs.devtools.server/started -``` +`$ shadow-cljs compile :dashboard` -## Watching a build +If everything goes right, you should have the following output on the console. -```clojure -;; watches a build (e.g here :dashboard) -(watch-build :dashboard) -[:dashboard] Configuring build. -[:dashboard] Compiling ... -=> :watching -[:dashboard] Build completed. (157 files, 1 compiled, 0 warnings, 2,18s) -... -``` - -## Compiling a build - -```clojure -;; compiles a build (e.g here :dashboard) -(compile-build :dashboard) -[:dashboard] Compiling ... -[:dashboard] Build completed. (59 files, 0 compiled, 0 warnings, 0,58s) -=> :done -``` +```shell script + ... + [:dashboard] Compiling ... + [:dashboard] Build completed. (567 files, 1 compiled, 3 warnings, 1,96s) +``` \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..b834935 --- /dev/null +++ b/package.json @@ -0,0 +1,34 @@ +{ + "name": "dashboard", + "version": "1.0.0", + "description": "The new (as of 2020) CLJS frontend dashboard for Yetibot", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/kaffein/dashboard.git" + }, + "keywords": [], + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/kaffein/dashboard/issues" + }, + "homepage": "https://github.com/kaffein/dashboard#readme", + "dependencies": { + "bloomer": "^0.6.3", + "bulma": "^0.8.2", + "bulma-checkradio": "^1.1.1", + "create-react-class": "^15.6.3", + "highlight.js": "^11.6.0", + "react": "^16.13.1", + "react-dom": "^16.13.1", + "react-highlight.js": "^1.0.0", + "react-router-dom": "^5.1.2" + }, + "devDependencies": { + "shadow-cljs": "^2.20.6" + } +} diff --git a/project.clj b/project.clj deleted file mode 100644 index 97e7741..0000000 --- a/project.clj +++ /dev/null @@ -1,22 +0,0 @@ -(defproject dashboard "0.1.0-SNAPSHOT" - :description "FIXME: write this!" - :url "http://example.com/FIXME" - :license {:name "Eclipse Public License" - :url "http://www.eclipse.org/legal/epl-v10.html"} - - :min-lein-version "2.9.1" - - :dependencies [[org.clojure/clojure "1.10.0"] - [org.clojure/clojurescript "1.10.520"] - [binaryage/devtools "1.0.0"] - [thheller/shadow-cljs "2.8.94"] - [reagent "0.10.0"] - ;; These has to be explicitly specified as lein does not - ;; properly manage dependency version conflicts : - ;; https://github.com/thheller/shadow-cljs/issues/488#issuecomment-486732296 - [com.google.javascript/closure-compiler-unshaded "v20190325"] - [org.clojure/google-closure-library "0.0-20190213-2033d5d9"]] - - :source-paths ["src"] - - :profiles {:dev {}}) diff --git a/resources/public/index.html b/resources/public/index.html index 12081cd..38705fb 100644 --- a/resources/public/index.html +++ b/resources/public/index.html @@ -5,12 +5,10 @@ + -
-

Figwheel template

-

Checkout your developer console.

-
- +
If you see this then there must be an issue with the react rendering in dashboard.core/init
+ diff --git a/shadow-cljs.edn b/shadow-cljs.edn index 9d48028..95cfdcf 100644 --- a/shadow-cljs.edn +++ b/shadow-cljs.edn @@ -1,8 +1,46 @@ -;; shadow-cljs configuration -{:lein true +{:source-paths ["dev" "src"] + + :dependencies [;; react + [reagent "1.1.1"] + [re-frame "1.3.0"] + + ;; graphql + [re-graph/re-graph "0.2.0" :exclusions [re-graph.hato]] + [district0x/graphql-query "1.0.6"] + + ;; utils + [camel-snake-kebab/camel-snake-kebab "0.4.2"] + [com.cognitect/transit-cljs "0.8.264"] + + ;; logging + [com.taoensso/timbre "5.2.0"] + + ;; Tooling + [day8.re-frame/re-frame-10x "1.5.0"] + [day8.re-frame/tracing "0.6.2"] + [binaryage/devtools "1.0.6"]] + + :nrepl {:port 8777} :builds - {:dashboard {:target :browser - :output-dir "public/assets/app/js" - :asset-path "/assets/app/js" - :modules {:main {:entries [dashboard.core]}}}}} \ No newline at end of file + {:dashboard + {:target :browser + + ;; compiled assets output location configuration + :output-dir "resources/public/js" + :output-to "resources/public/js/dashboard.js" + :asset-path "public/js" + + ;; module entry point configuration + :modules {:dashboard {:init-fn dashboard.core/init}} + + ;; Trace-enabling for re-frame-10x tooling + :dev {:closure-defines {"re_frame.trace.trace_enabled_QMARK_" true + "day8.re_frame.tracing.trace_enabled_QMARK_" true}} + + :compiler-options {:shadow-keywords true} + + :devtools {:http-root "resources/public" + :http-port 3000 + :preloads [devtools.preload + day8.re-frame-10x.preload]}}}} diff --git a/src/dashboard/components/dashboard.cljs b/src/dashboard/components/dashboard.cljs new file mode 100644 index 0000000..3604bd9 --- /dev/null +++ b/src/dashboard/components/dashboard.cljs @@ -0,0 +1,59 @@ +(ns dashboard.components.dashboard + (:require [reagent.core :as r] + [re-frame.core :as rf] + [reagent.dom :as rdom] + [taoensso.timbre :as log + :refer-macros [log trace debug info warn error fatal report + logf tracef debugf infof warnf errorf fatalf reportf + spy get-env]] + ["bloomer" :refer (Tile Hero HeroBody Title Subtitle Notification)] + ["react-router-dom" :refer (NavLink)] + [dashboard.components.search :refer [search]] + [dashboard.subs.dashboard])) + +(defn display + [label value] + [:> Tile {:is-child "true" :class "box"} + [:> Title @value] + [:> Subtitle label]]) + +(defn expandable-stat-display + [label value path] + [:> NavLink {:class "tile is-parent is-4" :to path} + [display label value]]) + +(defn stat-display + [label value] + [:> Tile {:is-size 4 :is-parent "true"} + [display label value]]) + +(defn dashboard + [] + (let [_ (rf/dispatch [:dashboard.stats/fetch 120]) + adapter-count (rf/subscribe [:dashboard.stats/adapter-count]) + command-count (rf/subscribe [:dashboard.stats/command-count]) + command-count-today (rf/subscribe [:dashboard.stats/command-count-today]) + user-count (rf/subscribe [:dashboard.stats/user-count]) + history-count (rf/subscribe [:dashboard.stats/history-count]) + history-count-today (rf/subscribe [:dashboard.stats/history-count-today]) + alias-count (rf/subscribe [:dashboard.stats/alias-count]) + observer-count (rf/subscribe [:dashboard.stats/observer-count]) + cront-count (rf/subscribe [:dashboard.stats/cron-count]) + uptime (rf/subscribe [:dashboard.stats/uptime])] + (fn [] + [:div + [:> Hero {:is-bold "true" :is-color "info" :is-size "small"} + [:> HeroBody + [:> Title "Dashboard"] + [:> Subtitle (str "Uptime " @uptime)]]] + [:div.tiles + [:> Tile {:is-ancestor "true" :has-text-align "centered"} + [expandable-stat-display "Adapters" adapter-count "/adapters"] + [expandable-stat-display "Commands" command-count "/history?co=1"] + [stat-display "Commands today" command-count-today] + [expandable-stat-display "User" user-count "/users"] + [expandable-stat-display "History items" history-count "/history"] + [stat-display "History items today" history-count-today] + [expandable-stat-display "Aliases" alias-count "/aliases"] + [expandable-stat-display "Observers" observer-count "/observers"] + [expandable-stat-display "Cron tasks" cront-count "/cron"]]]]))) diff --git a/src/dashboard/components/history.cljs b/src/dashboard/components/history.cljs new file mode 100644 index 0000000..ba344de --- /dev/null +++ b/src/dashboard/components/history.cljs @@ -0,0 +1,19 @@ +(ns dashboard.components.history + (:require [reagent.core :as r] + [re-frame.core :as rf] + ["bloomer" :refer (Tile Hero HeroBody Title Subtitle Notification)] + ["react-router-dom" :refer (NavLink)])) + +(defn history + [] + (let [_ (rf/dispatch [:dashboard.history/fetch 480])] + (fn [] + [:div + [:> Hero {:is-bold "true" :is-color "info" :is-size "small"} + [:> HeroBody + [:> Title "History"] + [:> Subtitle (str "Total items")]]] + [:div.tiles + [:> Tile {:is-ancestor "true" :has-text-align "centered"} + ]]]))) + diff --git a/src/dashboard/components/search.cljs b/src/dashboard/components/search.cljs new file mode 100644 index 0000000..6f4b9a4 --- /dev/null +++ b/src/dashboard/components/search.cljs @@ -0,0 +1,21 @@ +(ns dashboard.components.search + (:require [taoensso.timbre :as log + :refer-macros [log trace debug info warn error fatal report + logf tracef debugf infof warnf errorf fatalf reportf + spy get-env]] + ["bloomer" :as bloomer])) + +(defn search + "Search component skeleton" + [] + [:> bloomer/Control {:is-expanded true + :has-icons "left"} + [:input {:type :text + :value "" + :placeholder "Search history" + :iscolor "light" + :on-change (fn [e] + (info "changed this to stuff"))}] + [:> bloomer/Icon {:is-size "small" + :is-align "left" + :class "fa fa-search"}]]) \ No newline at end of file diff --git a/src/dashboard/core.cljs b/src/dashboard/core.cljs index e13e23f..3698ef0 100644 --- a/src/dashboard/core.cljs +++ b/src/dashboard/core.cljs @@ -1,5 +1,18 @@ (ns dashboard.core - (:require )) + (:require [reagent.core :as r] + [reagent.dom :as rdom] + [re-frame.core :as rf] + [taoensso.timbre :as log + :refer-macros [log trace debug info warn error fatal report + logf tracef debugf infof warnf errorf fatalf reportf + spy get-env]] + ["bloomer" :refer (Navbar Container NavbarStart NavbarBrand NavbarItem + Icon MenuList MenuLabel Menu Field NavbarEnd)] + ["react-router-dom" :refer (Route NavLink) :rename {BrowserRouter Router}] + [dashboard.components.search :refer [search]] + [dashboard.components.dashboard :refer [dashboard]] + [dashboard.components.history :refer [history]] + [dashboard.events.init :as init])) (enable-console-print!) @@ -9,6 +22,125 @@ (defonce app-state (atom {:text "Hello world!"})) +;; Imported components +(def Dashboard (r/reactify-component dashboard)) +(def History (r/reactify-component history)) + +(defn nav-bar + "Top nav bar" + [] + [:> Navbar {:class "is-white is-fixed-top"} + [:> Container + [:> NavbarStart + [:> NavbarBrand + [:> NavbarItem + [:> NavLink {:to "/"} + [:img {:style {:width 120 + :height 28} + :class "yetibot-logo" + :alt "Yetibot" + :src "https://yetibot.com/img/yetibot_lambda_blue_with_grey.svg"}]]]]] + [:> NavbarEnd + [:> NavbarItem + [:> Field + [search]]]]]]) + +(defn menu + [] + [:div.column + [:> Menu + ;; yetibot + [:> MenuLabel "Yetibot"] + [:> MenuList + [:li + [:> NavLink {:exact true + :to "/"} "Dashboard"]] + [:li + [:> NavLink {:exact true + :to "/history"} "History"]] + [:li + [:> NavLink {:to "/users"} "Users"]] + [:li + [:> NavLink {:to "/adapters"} "Adapters"]] + [:li + [:> NavLink {:to "/aliases"} "Aliases"]] + [:li + [:> NavLink {:to "/observers"} "Observers"]] + [:li + [:> NavLink {:to "/cron"} "Cron tasks"]] + [:li + [:> NavLink {:to "/repl"} "REPL"]]] + + ;; links + [:> MenuLabel "Links"] + [:> MenuList + [:li + [:a {:href "https://yetibot.com"} + [:> Icon {:is-size "small" + :is-align "left" + :class "fa fa-external-link-alt"}] + "Yetibot.com"]] + [:li + [:a {:href "https://github.com/yetibot/yetibot"} + [:> Icon {:is-size "small" + :is-align "left" + :class "fa fa-external-link-alt"}] + "Github"]] + [:li + [:a {:href "https://yetibot.com/archives"} + [:> Icon {:is-size "small" + :is-align "left" + :class "fa fa-external-link-alt"}] + "Blog"]] + [:li + [:a {:href "https://yetibot.com/user-guide"} + [:> Icon {:is-size "small" + :is-align "left" + :class "fa fa-external-link-alt"}] + "Docs"]]]]]) + +(defn routes + [] + [:<> + [:> Route {:path "/" :exact true :component Dashboard}] + [:> Route {:path "/adapters"}] + [:> Route {:path "/history" :exact true :component History}] + [:> Route {:path "/users"}] + [:> Route {:path "/user/:id"}] + [:> Route {:path "/aliases"}] + [:> Route {:path "/observers"}] + [:> Route {:path "/cron"}] + [:> Route {:path "/repl"}]]) + +(defn content-body + "Content body" + [] + [:> Container {:id "content-body"} + [:div.columns + [:div.column.is-2 + [menu]] + [:div#content-container.column + [routes]]]]) + +(defn dashboard-app + "The dashboard component" + [] + [:> Router + [:div + [nav-bar] + [content-body]]]) + +(defn ^:dev/after-load start + "Mounts the application root component in the DOM." + [] + (rdom/render [dashboard-app] (js/document.getElementById "app"))) + +(defn ^:export init + "Dashboard entrypoint which is called only once when `index.html` loads. + It must be exported so it is available even in :advanced release builds." + [] + (rf/dispatch-sync [::init/init]) + (start)) (defn on-js-reload [] ;; optionally touch your app-state to force rerendering depending on diff --git a/src/dashboard/events/init.cljs b/src/dashboard/events/init.cljs new file mode 100644 index 0000000..9efe630 --- /dev/null +++ b/src/dashboard/events/init.cljs @@ -0,0 +1,94 @@ +(ns dashboard.events.init + (:require [re-frame.core :as rf] + [re-graph.core :as re-graph] + [camel-snake-kebab.core :as csk] + [camel-snake-kebab.extras :as cske] + [dashboard.graphql.queries :as queries] + [taoensso.timbre :as log] + [cognitect.transit :as t] + [clojure.walk] + [day8.re-frame.tracing :refer-macros [fn-traced]])) + +#_(def ^:const graphql-endpoint "https://public.yetibot.com/graphql") +(def ^:const graphql-endpoint "http://localhost:3003/graphql") + +(def json-reader (t/reader :json)) + +(def kebab-case-keywords (partial cske/transform-keys csk/->kebab-case-keyword)) + +;-------------------------------------------------------------- +; Initialization +;-------------------------------------------------------------- +(rf/reg-event-fx + ::init + (fn [{:keys [db]} _] + {:dispatch [::init-re-graph]})) + +;-------------------------------------------------------------- +; Re-graph initialization +;-------------------------------------------------------------- +(rf/reg-event-fx + ::init-re-graph + (fn [_ _] + {:dispatch [::re-graph/init {:http + {:url graphql-endpoint + :impl {:with-credentials? false}} + :ws + {:url nil}}]})) + +;-------------------------------------------------------------- +; Dashboard +;-------------------------------------------------------------- +(rf/reg-event-fx + :dashboard.stats/fetch + (fn [_ [_ timezone-offset-hours]] + {:dispatch [::re-graph/query + queries/stats + {:timezone_offset_hours timezone-offset-hours} + [:dashboard.stats/store]]})) + +(rf/reg-event-fx + :dashboard.stats/store + (fn [{:keys [db]} [_ payload]] + (let [data (-> (t/read json-reader payload) + (clojure.walk/keywordize-keys) + kebab-case-keywords + (get-in [:data :stats]))] + (if (nil? data) + {:dispatch [::on-error :dashboard/error (str "An error occured while fetching statistics data")]} + {:db (assoc db :dashboard/stats data)})))) + +;-------------------------------------------------------------- +; History +;-------------------------------------------------------------- +(rf/reg-event-fx + :dashboard.history/fetch + (fn [_ [_ timezone-offset-hours]] + {:dispatch [::re-graph/query + queries/history + {:commands_only true + :yetibot_only true + :search_query "" + :timezone_offset_hours timezone-offset-hours} + [:dashboard.history/store]]})) + +(rf/reg-event-fx + :dashboard.history/store + (fn [{:keys [db]} [_ payload]] + (println payload) + (let [data (-> (t/read json-reader payload) + (clojure.walk/keywordize-keys) + kebab-case-keywords + (get-in [:data]))] + (if (nil? data) + {:dispatch [::on-error :dashboard/error (str "An error occured while fetching statistics data")]} + {:db (assoc db :dashboard/stats data)})))) + +;-------------------------------------------------------------- +; Generic error-handling +;-------------------------------------------------------------- +(rf/reg-event-db + ::on-error + (fn [db [{:keys [event-id & parameters]}]] + {:db (assoc db :error/event-id event-id + :error/parameters parameters)})) diff --git a/src/dashboard/graphql/queries.cljs b/src/dashboard/graphql/queries.cljs new file mode 100644 index 0000000..df62c6c --- /dev/null +++ b/src/dashboard/graphql/queries.cljs @@ -0,0 +1,50 @@ +(ns dashboard.graphql.queries) + +(def stats + "query stats($timezone_offset_hours: Int!) { + stats(timezone_offset_hours: $timezone_offset_hours) { + uptime + adapter_count + user_count + command_count_today + command_count + history_count + history_count_today + alias_count + observer_count + cron_count + } + }") + +(def history + "query history($timezone_offset_hours: Int!, $yetibot_only: Boolean!, $commands_only: Boolean!, $search_query: String) { + stats(timezone_offset_hours: $timezone_offset_hours) { + history_count + } + + history(limit: 30, offset: 0, + commands_only: $commands_only, + yetibot_only: $yetibot_only, + search_query: $search_query + ) { + id + chat_source_adapter + chat_source_room + command + correlation_id + created_at + user_name + is_command + is_yetibot + body + user_id + user_name + } + }") + +(def history-item + "query history_item($history_id: String!) { + history_item(id: $history_id) { + id + } + }") diff --git a/src/dashboard/subs/dashboard.cljs b/src/dashboard/subs/dashboard.cljs new file mode 100644 index 0000000..67753d8 --- /dev/null +++ b/src/dashboard/subs/dashboard.cljs @@ -0,0 +1,52 @@ +(ns dashboard.subs.dashboard + (:require [re-frame.core :as rf])) + +(rf/reg-sub + :dashboard.stats/uptime + (fn [db _] + (get-in db [:dashboard/stats :uptime]))) + +(rf/reg-sub + :dashboard.stats/adapter-count + (fn [db _] + (get-in db [:dashboard/stats :adapter-count]))) + +(rf/reg-sub + :dashboard.stats/command-count + (fn [db _] + (get-in db [:dashboard/stats :command-count]))) + +(rf/reg-sub + :dashboard.stats/command-count-today + (fn [db _] + (get-in db [:dashboard/stats :command-count-today]))) + +(rf/reg-sub + :dashboard.stats/user-count + (fn [db _] + (get-in db [:dashboard/stats :user-count]))) + +(rf/reg-sub + :dashboard.stats/history-count + (fn [db _] + (get-in db [:dashboard/stats :history-count]))) + +(rf/reg-sub + :dashboard.stats/history-count-today + (fn [db _] + (get-in db [:dashboard/stats :history-count-today]))) + +(rf/reg-sub + :dashboard.stats/alias-count + (fn [db _] + (get-in db [:dashboard/stats :alias-count]))) + +(rf/reg-sub + :dashboard.stats/observer-count + (fn [db _] + (get-in db [:dashboard/stats :observer-count]))) + +(rf/reg-sub + :dashboard.stats/cron-count + (fn [db _] + (get-in db [:dashboard/stats :cron-count])))