├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── LICENSE
├── Makefile
├── crux-console-conf.sample.edn
├── dev
├── build-ebs.sh
├── build-report--app-8f3b217f.html
├── dep-tree.txt
├── dev.clj
├── log_dev_app.properties
├── reqs.clj
├── requests.sh
├── shadow-dev.sh
├── shadow-prod.sh
└── update-jar-app-js.sh
├── doc
├── README.adoc
├── get_started.adoc
└── resources
│ ├── doc
│ ├── asciidoctor
│ │ └── attributes.edn
│ └── docinfo
│ │ ├── docinfo-footer.html
│ │ └── docinfo.html
│ └── img
│ └── console1-runqempty.gif
├── ebs
├── .ebextensions
│ ├── https-instance-securitygroup.config
│ └── nginx
│ │ ├── conf.d
│ │ └── console.conf
│ │ └── nginx.conf
├── generate-dh.sh
├── readme.md
├── sample-console-install.config
└── stack.yaml
├── package.json
├── project.clj
├── readme.md
├── resources
├── screenshot-1.png
└── static
│ ├── img
│ ├── console-logo.svg
│ ├── crux-labs.png
│ ├── crux-labs.svg
│ ├── crux-logo.svg
│ ├── cube-for-icon.svg
│ ├── cube-on-white-120.png
│ ├── cube-on-white-192.png
│ ├── cube-on-white-512.png
│ ├── cube.svg
│ └── juxt-logo.svg
│ ├── plots-data.edn
│ └── styles
│ ├── codemirror.css
│ ├── eclipse.css
│ ├── main.css
│ ├── monokai.css
│ ├── normalize.css
│ ├── react-input-range.css
│ ├── react-ui-tree.css
│ └── reset.css
├── shadow-cljs.edn
├── src
├── crux_ui
│ ├── better_printer.cljs
│ ├── config.cljs
│ ├── cookies.cljs
│ ├── events
│ │ ├── default_db.cljs
│ │ ├── effects.cljs
│ │ └── facade.cljs
│ ├── functions.cljs
│ ├── io
│ │ └── query.cljs
│ ├── logging.cljs
│ ├── logic
│ │ ├── example_queries.cljs
│ │ ├── example_txes_amzn.cljs
│ │ ├── history_perversions.cljs
│ │ ├── plotting_data_calc.cljs
│ │ ├── query_analysis.cljs
│ │ ├── readme.md
│ │ └── time.cljs
│ ├── main.cljs
│ ├── main_perf.cljs
│ ├── routes.cljs
│ ├── subs.cljs
│ ├── svg_icons.cljs
│ ├── ua-regexp.js
│ └── views
│ │ ├── attr_stats.cljs
│ │ ├── charts
│ │ ├── custom-plotly--console.js
│ │ ├── custom-plotly--perf.js
│ │ ├── query_perf_plots.cljs
│ │ ├── wrapper_3d.cljs
│ │ └── wrapper_basic.cljs
│ │ ├── cluster_health.cljs
│ │ ├── codemirror.cljs
│ │ ├── commons
│ │ ├── contenteditable.cljs
│ │ ├── css_cube.cljs
│ │ ├── css_logo.cljs
│ │ ├── cube_svg.cljs
│ │ ├── datepicker_native.cljs
│ │ ├── dom.cljs
│ │ ├── form_line.cljs
│ │ ├── input.cljs
│ │ ├── keycodes.cljs
│ │ ├── tabs.cljs
│ │ ├── tiny_components.cljs
│ │ └── user_intents.cljs
│ │ ├── facade.cljs
│ │ ├── facade_perf.cljs
│ │ ├── functions.cljs
│ │ ├── header.cljs
│ │ ├── node_status.cljs
│ │ ├── output
│ │ ├── attr_history.cljs
│ │ ├── edn.cljs
│ │ ├── error.cljs
│ │ ├── react_tree.cljs
│ │ ├── table.cljs
│ │ ├── tree.cljs
│ │ └── tx_history.cljs
│ │ ├── overview.cljs
│ │ ├── query
│ │ ├── datepicker_slider.cljs
│ │ ├── editor.cljs
│ │ ├── examples.cljs
│ │ ├── form.cljs
│ │ ├── output.cljs
│ │ └── time_controls.cljs
│ │ ├── query_perf.cljs
│ │ ├── query_ui.cljs
│ │ ├── second_layer.cljs
│ │ ├── settings.cljs
│ │ ├── sidebar.cljs
│ │ └── style.cljs
├── crux_ui_lib
│ ├── async_http_client.cljc
│ ├── functions.cljc
│ └── http_functions.cljs
├── crux_ui_server
│ ├── config.clj
│ ├── crux_auto_start.clj
│ ├── generate_web_manifest.clj
│ ├── main.clj
│ ├── pages.clj
│ └── preloader.clj
├── dev.clj
└── public
│ ├── logo.css
│ ├── main.css
│ └── normalize.css
├── test
└── crux_console
│ ├── query_analysis_test.cljs
│ └── test_runner.cljs
└── yarn.lock
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: build
2 |
3 | on: push
4 |
5 | jobs:
6 | #lint:
7 | # runs-on: ubuntu-latest
8 | # steps:
9 | # - uses: actions/checkout@v1
10 | # - name: Run linter
11 | # uses: DeLaGuardo/clojure-lint-action@v1
12 | # with:
13 | # clj-kondo-args: --lint src
14 | # github_token: ${{ secrets.GITHUB_TOKEN }}
15 |
16 | jars:
17 | # needs: lint
18 | runs-on: ubuntu-latest
19 | steps:
20 | - name: Git checkout
21 | uses: actions/checkout@v1
22 |
23 | - name: Setup java
24 | uses: actions/setup-java@v1
25 | with:
26 | java-version: '13'
27 |
28 | - name: Setup clojure
29 | uses: DeLaGuardo/setup-clojure@2.0
30 | with:
31 | tools-deps: latest
32 |
33 | - name: Cache clojure deps
34 | uses: actions/cache@v1
35 | with:
36 | path: ~/.m2/repository
37 | key: clojure-${{ hashFiles('**/project.clj') }}
38 | restore-keys: |
39 | clojure-
40 |
41 | - name: Run clean
42 | run: make clean
43 |
44 | - name: install node modules
45 | run: make install
46 |
47 | - name: Get yarn cache
48 | id: yarn-cache
49 | run: echo "::set-output name=dir::$(yarn cache dir)"
50 |
51 | - uses: actions/cache@v1
52 | with:
53 | path: ${{ steps.yarn-cache.outputs.dir }}
54 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
55 | restore-keys: |
56 | ${{ runner.os }}-yarn-
57 |
58 | - name: build web
59 | run: make build
60 |
61 | - name: build uberjar
62 | run: make jar
63 |
64 | - name: upload skimmed jar
65 | uses: actions/upload-artifact@v1
66 | with:
67 | name: crux-console-skimmed.jar
68 | path: target/crux-console-skimmed.jar
69 |
70 | - name: upload the jar with crux
71 | uses: actions/upload-artifact@v1
72 | with:
73 | name: crux-console.jar
74 | path: target/crux-console.jar
75 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | **/.cpcache
2 | **/.rebel_readline_history
3 | **/.nrepl-port
4 | **/target/*/**
5 | target
6 | **/log/*
7 | !**/target/*/.gitkeep
8 | data
9 | crux-console-conf.edn
10 | resources/static/crux-ui/compiled
11 | resources/static/crux-ui/test
12 | node_modules
13 | .shadow-cljs
14 | ebs/ebs-uber.jar
15 | ebs/ebs-upload.zip
16 |
17 | # pack'd output
18 | /*.jar
19 | **/*.jar
20 |
21 | # Elastic Beanstalk Files
22 | .elasticbeanstalk/*
23 | !.elasticbeanstalk/*.cfg.yml
24 | !.elasticbeanstalk/*.global.yml
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright © 2016-2019 JUXT LTD.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | all: clean install build jar
2 |
3 | clean:
4 | rm -rf target
5 |
6 | install:
7 | yarn install
8 |
9 | build:
10 | node_modules/.bin/shadow-cljs release app
11 |
12 | build-perf:
13 | node_modules/.bin/shadow-cljs release app-perf
14 |
15 | jar:
16 | lein with-profile base:crux-jars uberjar
17 |
--------------------------------------------------------------------------------
/crux-console-conf.sample.edn:
--------------------------------------------------------------------------------
1 | {:console/frontend-port 5000
2 |
3 | :console/embed-crux false
4 | ; set to true, if you want the console to start a Crux node for you
5 | ; meaning you should use the crux-console.jar
6 |
7 | :console/routes-prefix "/console"
8 | :console/hide-features #{:features/attribute-history}
9 | :console/crux-node-url-base "localhost:8080/crux"
10 | :console/crux-http-port 8080}
--------------------------------------------------------------------------------
/dev/build-ebs.sh:
--------------------------------------------------------------------------------
1 | #/usr/bin/env bash
2 |
3 | cp target/crux-console.jar ebs/ebs-uber.jar
4 |
5 | # java -jar target/crux-console.jar
6 |
7 | (
8 | cd ebs
9 | zip ebs-upload.zip ebs-uber.jar
10 | zip -r ebs-upload.zip .ebextensions
11 | )
12 |
13 | # ./bin/capsule -m edge.main -A:prod ebs/ebs-uber.jar && cd ebs && zip ebs-upload.zip ebs-uber.jar && zip -r ebs-upload.zip .ebextensions
14 |
--------------------------------------------------------------------------------
/dev/dev.clj:
--------------------------------------------------------------------------------
1 | (ns dev
2 | (:require
3 | [dev-extras :refer :all]))
4 |
5 | ;; Add your helpers here
6 |
--------------------------------------------------------------------------------
/dev/log_dev_app.properties:
--------------------------------------------------------------------------------
1 | app_root_logger=juxt.crux-ui
2 |
--------------------------------------------------------------------------------
/dev/reqs.clj:
--------------------------------------------------------------------------------
1 | (require '[crux.api :as crux])
2 |
3 | (def n
4 | (crux/start-node
5 | {:crux.node/topology :crux.standalone/topology
6 | :crux.node/kv-store "crux.kv.rocksdb/kv"
7 | :crux.standalone/event-log-dir "data/eventlog-2"
8 | :crux.kv/db-dir "data/db-dir-2"}))
9 |
10 | (crux/submit-tx n
11 | [[:crux.tx/put
12 | {:crux.db/id :ids/leno}]
13 | [:crux.tx/put
14 | {:crux.db/id :ids/deni}]
15 | [:crux.tx/put
16 | {:crux.db/id :ids/reni}]])
17 |
18 | (crux/q (crux/db n)
19 | '[:find e
20 | :where
21 | [e :crux.db/id]
22 | [(contains? ids e)]
23 | :args {ids #{:ids/leno :ids/reni}}
24 | :limit 10])
25 |
26 | (crux/q (crux/db n)
27 | '{:find [e]
28 | :args [{id [in #{:ids/leno}]}]
29 | :in [id]
30 | :where [[e :crux.db/id id]]
31 | :limit 1})
32 |
--------------------------------------------------------------------------------
/dev/requests.sh:
--------------------------------------------------------------------------------
1 |
2 | curl -v localhost:8080/query \
3 | -H 'Content-Type: application/edn' \
4 | -H "Origin: http://example.com" \
5 | --data-binary \
6 | "{:query
7 | [:find e
8 | :where
9 | [e :crux.db/id]
10 | [(contains? ids e)]
11 | :args {}
12 | :limit 10]}"
13 |
14 | # breaks, to test exception reporting
15 | curl -v localhost:8080/query \
16 | -H 'Content-Type: application/edn' \
17 | -H "Origin: http://example.com" \
18 | --data-binary \
19 | "{:query
20 | [:find e
21 | :where
22 | [e :crux.db/id]
23 | [(contains? ids e)]
24 | :args {}
25 | :limit 10]}"
26 |
27 |
28 | curl localhost:8080/query \
29 | -H 'Content-Type: application/edn' \
30 | --data-binary \
31 | '{:query
32 | {:find [e]
33 | :where
34 | [[e :crux.db/id]
35 | [(contains? ids e)]]
36 | :args [{ids #{:ids/tech-ticker-2}}]
37 | :limit 2}}'
38 |
39 |
--------------------------------------------------------------------------------
/dev/shadow-dev.sh:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env sh
2 | node_modules/.bin/shadow-cljs watch dev
3 |
4 | # or
5 | # shadow-cljs watch dev-perf
6 |
--------------------------------------------------------------------------------
/dev/shadow-prod.sh:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env sh
2 |
3 | rm -r resources/static/crux-ui/compiled
4 |
5 | node_modules/.bin/shadow-cljs release app
6 |
7 | node_modules/.bin/shadow-cljs release app-perf
8 |
9 | gzip -9k resources/static/crux-ui/compiled/*
10 |
11 | ls -lah resources/static/crux-ui/compiled
12 |
--------------------------------------------------------------------------------
/dev/update-jar-app-js.sh:
--------------------------------------------------------------------------------
1 | shadow release app
2 |
3 | # jar tf target/crux-console.jar | grep main.js
4 | # zip -d target/crux-console.jar static/crux-ui/compiled/main.js
5 |
6 | jar uf target/crux-console.jar -C resources static/crux-ui/compiled/main.js
7 | # says:
8 | # jar update file name.jar -Cd to resources and put path/main.js into it
9 |
--------------------------------------------------------------------------------
/doc/README.adoc:
--------------------------------------------------------------------------------
1 | = Crux Console docs
2 |
3 | This directory represents the Crux Console documentation module.
4 |
5 | It is explicitly referenced by `app`.
6 |
7 | Asciidoc sources can be found in link:resources/doc/sources[].
8 |
--------------------------------------------------------------------------------
/doc/resources/doc/asciidoctor/attributes.edn:
--------------------------------------------------------------------------------
1 | {"docinfo" "shared"
2 | "docinfodir" "doc"
3 | "experimental" true
4 | "figure-caption" false
5 | "highlightjs-theme" "tomorrow-night"
6 | "highlightjsdir" "/highlight"
7 | "icons" "font"
8 | "iconfont-cdn" "/font-awesome/css/font-awesome.min.css"
9 | "icontype" "svg"
10 | "imagesdir" "/img"
11 | "nofooter" true
12 | "sectnums" true
13 | "sectanchors" true
14 | "source-highlighter" "highlightjs"
15 | "stylesdir" "/doc/css"
16 | "stylesheet" "juxt.css"
17 | "linkcss" true
18 | "toc" "left"
19 | "webfonts" false
20 | "xrefstyle" "short"}
21 |
--------------------------------------------------------------------------------
/doc/resources/doc/docinfo/docinfo-footer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
Copyright © 2019, JUXT LTD. All Rights Reserved.
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/doc/resources/doc/docinfo/docinfo.html:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/doc/resources/img/console1-runqempty.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xtdb-labs/crux-console/6bdc59339dd2513f60b61188c1c2f331006511fd/doc/resources/img/console1-runqempty.gif
--------------------------------------------------------------------------------
/ebs/.ebextensions/https-instance-securitygroup.config:
--------------------------------------------------------------------------------
1 | Resources:
2 | sslSecurityGroupIngress:
3 | Type: AWS::EC2::SecurityGroupIngress
4 | Properties:
5 | GroupId: {"Fn::GetAtt" : ["AWSEBSecurityGroup", "GroupId"]}
6 | IpProtocol: tcp
7 | ToPort: 443
8 | FromPort: 443
9 | CidrIp: 0.0.0.0/0
10 |
--------------------------------------------------------------------------------
/ebs/.ebextensions/nginx/conf.d/console.conf:
--------------------------------------------------------------------------------
1 | # See guide
2 | # https://geekflare.com/nginx-webserver-security-hardening-guide/
3 |
4 | # redirect all http traffic to https
5 | server {
6 | listen 80 default_server;
7 | listen [::]:80 default_server;
8 | server_name .console.crux.cloud;
9 | return 301 https://$host$request_uri;
10 | }
11 |
12 | server {
13 | listen 443 ssl http2;
14 | listen [::]:443 ssl http2;
15 | server_name .console.crux.cloud;
16 |
17 | ssl_certificate /etc/letsencrypt/live/console.crux.cloud/fullchain.pem; # managed by Certbot
18 | ssl_certificate_key /etc/letsencrypt/live/console.crux.cloud/privkey.pem; # managed by Certbot
19 |
20 | # enable session resumption to improve https performance
21 | # http://vincent.bernat.im/en/blog/2011-ssl-session-reuse-rfc5077.html
22 | ssl_session_cache shared:SSL:50m;
23 | ssl_session_timeout 1d;
24 | ssl_session_tickets off;
25 |
26 | # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
27 | # ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
28 | # not worth the time to produce it on EBS instances
29 |
30 | # enable ocsp stapling (mechanism by which a site can convey certificate revocation information to visitors in a privacy-preserving, scalable manner)
31 | # http://blog.mozilla.org/security/2013/07/29/ocsp-stapling-in-firefox/
32 | resolver 8.8.8.8 8.8.4.4;
33 | ssl_stapling on;
34 | ssl_stapling_verify on;
35 |
36 |
37 | # config to don't allow the browser to render the page inside an frame or iframe
38 | # and avoid clickjacking http://en.wikipedia.org/wiki/Clickjacking
39 | # if you need to allow [i]frames, you can use SAMEORIGIN or even set an uri with ALLOW-FROM uri
40 | # https://developer.mozilla.org/en-US/docs/HTTP/X-Frame-Options
41 | # add_header X-Frame-Options SAMEORIGIN;
42 | # comes with aleph
43 |
44 | # when serving user-supplied content, include a X-Content-Type-Options: nosniff header along with the Content-Type: header,
45 | # to disable content-type sniffing on some browsers.
46 | # https://www.owasp.org/index.php/List_of_useful_HTTP_headers
47 | # currently suppoorted in IE > 8 http://blogs.msdn.com/b/ie/archive/2008/09/02/ie8-security-part-vi-beta-2-update.aspx
48 | # http://msdn.microsoft.com/en-us/library/ie/gg622941(v=vs.85).aspx
49 | # 'soon' on Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=471020
50 | # add_header X-Content-Type-Options nosniff;
51 | # comes with aleph
52 |
53 | # This header enables the Cross-site scripting (XSS) filter built into most recent web browsers.
54 | # It's usually enabled by default anyway, so the role of this header is to re-enable the filter for
55 | # this particular website if it was disabled by the user.
56 | # https://www.owasp.org/index.php/List_of_useful_HTTP_headers
57 | # add_header X-XSS-Protection "1; mode=block";
58 | # comes with aleph
59 |
60 | # with Content Security Policy (CSP) enabled(and a browser that supports it(http://caniuse.com/#feat=contentsecuritypolicy),
61 | # you can tell the browser that it can only download content from the domains you explicitly allow
62 | # http://www.html5rocks.com/en/tutorials/security/content-security-policy/
63 | # https://www.owasp.org/index.php/Content_Security_Policy
64 | # I need to change our application code so we can increase security by disabling 'unsafe-inline' 'unsafe-eval'
65 | # directives for css and js(if you have inline css or js, you will need to keep it too).
66 | # more: http://www.html5rocks.com/en/tutorials/security/content-security-policy/#inline-code-considered-harmful
67 | add_header Content-Security-Policy
68 | "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://storage.googleapis.com; img-src 'self' https://juxt.pro; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://themes.googleusercontent.com; frame-src 'none'; object-src 'none'; connect-src 'self' https://gist.githubusercontent.com/";
69 |
70 |
71 | # config to enable HSTS(HTTP Strict Transport Security) https://developer.mozilla.org/en-US/docs/Security/HTTP_Strict_Transport_Security
72 | # to avoid ssl stripping https://en.wikipedia.org/wiki/SSL_stripping#SSL_stripping
73 | # also https://hstspreload.org/
74 | add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload";
75 |
76 | client_max_body_size 2M;
77 | # ... the rest of your configuration
78 |
79 | root /srv/crux/actual/public;
80 |
81 | if ($request_method !~ ^(GET|HEAD|POST|OPTIONS)$ )
82 | {
83 | return 405;
84 | }
85 |
86 | location /crux/ {
87 | proxy_pass http://127.0.0.1:8080/;
88 | # proxy_set_header Connection $connection_upgrade;
89 | # proxy_set_header Upgrade $http_upgrade;
90 | # proxy_set_header Host $host;
91 | # proxy_set_header X-Real-IP $remote_addr;
92 | # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
93 | }
94 |
95 | location / {
96 | proxy_pass http://localhost:5000/; # important for bidi
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/ebs/.ebextensions/nginx/nginx.conf:
--------------------------------------------------------------------------------
1 | #au BufWritePost * !service nginx reload
2 | #au BufWritePost * !service nginx restart
3 | user nginx;
4 | worker_processes auto;
5 | pid /var/run/nginx.pid;
6 | worker_rlimit_nofile 32794;
7 | error_log /var/log/nginx/error.log warn;
8 |
9 | events {
10 | worker_connections 1024;
11 | # multi_accept on;
12 | }
13 |
14 | http {
15 |
16 | ##
17 | # Basic Settings
18 | ##
19 |
20 | sendfile on;
21 | tcp_nopush on;
22 | tcp_nodelay on;
23 | keepalive_timeout 65;
24 | types_hash_max_size 2048;
25 |
26 | # server_names_hash_bucket_size 64;
27 | # server_name_in_redirect off;
28 |
29 | include /etc/nginx/mime.types;
30 | default_type application/octet-stream;
31 |
32 | ##
33 | # Logging Settings
34 | ##
35 |
36 | log_format main '$remote_addr - $remote_user [$time_local] "$request" '
37 | '$status $body_bytes_sent "$http_referer" '
38 | '"$http_user_agent" "$http_x_forwarded_for"';
39 |
40 | ##
41 | # SSL settings
42 | ##
43 |
44 | # ciphers chosen for forward secrecy and compatibility
45 | # http://blog.ivanristic.com/2013/08/configuring-apache-nginx-and-openssl-for-forward-secrecy.html
46 | ssl_ciphers "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA HIGH !RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS";
47 |
48 | # enables server-side protection from BEAST attacks
49 | # http://blog.ivanristic.com/2013/09/is-beast-still-a-threat.html
50 | ssl_prefer_server_ciphers on;
51 |
52 | # disable SSLv3(enabled by default since nginx 0.8.19) since it's less secure then TLS http://en.wikipedia.org/wiki/Secure_Sockets_Layer#SSL_3.0
53 | ssl_protocols TLSv1.2 TLSv1.3;
54 |
55 |
56 | # read more here http://tautt.com/best-nginx-configuration-for-security/
57 | # don't send the nginx version number in error pages and Server header
58 | server_tokens off;
59 |
60 | access_log /var/log/nginx/access.log main;
61 |
62 | # Gzip Settings
63 | gzip on;
64 | gzip_static on;
65 | gzip_proxied any;
66 | gzip_comp_level 9;
67 | gzip_types text/plain text/css application/json application/edn application/javascript text/xml application/xml application/xml+rss text/javascript;
68 |
69 | # Virtual Host Configs
70 | include conf.d/console.conf;
71 | #include conf.d/elasticbeanstalk/*;
72 | }
73 |
74 |
75 |
--------------------------------------------------------------------------------
/ebs/generate-dh.sh:
--------------------------------------------------------------------------------
1 | # to generate your dhparam.pem file, run in the terminal
2 | openssl dhparam -out /etc/nginx/ssl/dhparam.pem 4096
3 |
--------------------------------------------------------------------------------
/ebs/readme.md:
--------------------------------------------------------------------------------
1 | https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/java-se-nginx.html
2 |
--------------------------------------------------------------------------------
/ebs/sample-console-install.config:
--------------------------------------------------------------------------------
1 | # If you need custom software on your EBS instance
2 | # read this
3 | # https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/customize-containers-ec2.html
4 | # then drop this config into .ebextensions dir
5 |
6 | #commands:
7 | # 10_installcert:
8 | # command: "wget https://dl.eff.org/certbot-auto;chmod a+x certbot-auto"
9 | # 13_getcert:
10 | # command: "sudo ./certbot-auto certonly --debug --non-interactive --email jdt@juxt.pro --agree-tos --standalone --domains console.crux.cloud --keep-until-expiring --pre-hook \"service nginx stop\" --staging"
11 | # 31-hey:
12 | # command: echo "kitkattoken" && pwd
13 | # 32-hey:
14 | # command: wget -q https://download.java.net/java/GA/jdk13/5b8a42f3905b406298b72d750b6919f6/33/GPL/openjdk-13_linux-x64_bin.tar.gz
15 | # 33-hey:
16 | # command: tar -xzf openjdk-13_linux-x64_bin.tar.gz
17 | # 34-hey:
18 | # command: export JAVA_HOME=$(pwd)/jdk-13
19 | # 35-hey:
20 | # command: export PATH=$(pwd)/jdk-13/bin:$PATH
21 |
22 | container_commands:
23 | 31-hey:
24 | command: echo "containertoken" && pwd
25 |
--------------------------------------------------------------------------------
/ebs/stack.yaml:
--------------------------------------------------------------------------------
1 | AWSTemplateFormatVersion: '2010-09-09'
2 | Parameters:
3 | Description:
4 | Type: String
5 | Description: Description of your application for Elastic Beanstalk Resources
6 | Default: "Crux Console"
7 | CNAMEPrefix:
8 | Type: String
9 | Description: Prefix to be used in the generated CNAME
10 | Resources:
11 | sslSecurityGroupIngress:
12 | Type: AWS::EC2::SecurityGroupIngress
13 | Properties:
14 | GroupId: {"Fn::GetAtt" : ["AWSEBSecurityGroup", "GroupId"]}
15 | IpProtocol: tcp
16 | ToPort: 443
17 | FromPort: 443
18 | CidrIp: 0.0.0.0/0
19 | BeanstalkInstanceRole:
20 | Type: "AWS::IAM::Role"
21 | Properties:
22 | AssumeRolePolicyDocument:
23 | Version: 2012-10-17
24 | Statement:
25 | -
26 | Effect: Allow
27 | Principal:
28 | Service:
29 | - ec2.amazonaws.com
30 | Action:
31 | - sts:AssumeRole
32 | ManagedPolicyArns:
33 | - arn:aws:iam::aws:policy/AWSElasticBeanstalkWebTier
34 | - arn:aws:iam::aws:policy/AWSElasticBeanstalkMulticontainerDocker
35 | BeanstalkInstanceProfile:
36 | Type: AWS::IAM::InstanceProfile
37 | Properties:
38 | Roles:
39 | - !Ref BeanstalkInstanceRole
40 | Application:
41 | Type: AWS::ElasticBeanstalk::Application
42 | Properties:
43 | Description: !Ref Description
44 | ConfigurationTemplate:
45 | Type: AWS::ElasticBeanstalk::ConfigurationTemplate
46 | Properties:
47 | ApplicationName: !Ref Application
48 | Description: !Join [" ", [!Ref Description, "Configuration"]]
49 | OptionSettings:
50 | - Namespace: aws:autoscaling:launchconfiguration
51 | OptionName: IamInstanceProfile
52 | Value: !Ref BeanstalkInstanceProfile
53 | - Namespace: aws:autoscaling:asg
54 | OptionName: MinSize
55 | Value: '1'
56 | - Namespace: aws:autoscaling:asg
57 | OptionName: MaxSize
58 | Value: '1'
59 | - Namespace: aws:elasticbeanstalk:environment
60 | OptionName: EnvironmentType
61 | Value: LoadBalanced
62 | - Namespace: aws:elasticbeanstalk:cloudwatch:logs
63 | OptionName: StreamLogs
64 | Value: true
65 | SolutionStackName: 64bit Amazon Linux 2018.03 v2.8.2 running Java 8
66 | Environment:
67 | Type: AWS::ElasticBeanstalk::Environment
68 | Properties:
69 | ApplicationName: !Ref Application
70 | Description: !Join [" ", [!Ref Description, "Environment"]]
71 | CNAMEPrefix: !Ref CNAMEPrefix
72 | TemplateName: !Ref ConfigurationTemplate
73 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "crux-ui",
3 | "version": "1.0.0",
4 | "authors": [
5 | "Jeremy Taylor [refset]",
6 | "Ivan Fedorov [spacegangster]"
7 | ],
8 | "private": true,
9 | "dependencies": {
10 | "codemirror": "5.47.0",
11 | "create-react-class": "^15.6.3",
12 | "highlight.js": "9.15.8",
13 | "plotly.js": "^1.49.3",
14 | "plotly.js-gl3d-dist": "^1.49.3",
15 | "process": "^0.11.10",
16 | "quill": ">=1.3.6",
17 | "quill-auto-links": ">=0.1.3",
18 | "quill-delta": ">=4.1.0",
19 | "quill-plain-text-paste": ">=1.0.1",
20 | "react": "^16.8.6",
21 | "react-dom": "^16.7.0",
22 | "react-flip-move": "^3.0.3",
23 | "react-highlight.js": "^1.0.7",
24 | "react-input-range": "^1.3.0",
25 | "react-ui-tree": "^4.0.0"
26 | },
27 | "resolutions": {
28 | "esm": "^3.1.0",
29 | "static-eval": "^2.0.0"
30 | },
31 | "devDependencies": {
32 | "shadow-cljs": "^2.6.24"
33 | },
34 | "peerDependencies": {
35 | "gl-surface3d": "^1.4.6",
36 | "graceful-fs": "^4.2.2"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/project.clj:
--------------------------------------------------------------------------------
1 | (defproject crux-console "derived-from-git"
2 | :dependencies
3 | [[org.clojure/clojure "1.10.1"]
4 | ; 1.10.1 doesn't work with this aleph
5 | [aleph "0.4.6"]
6 | [bidi "2.1.6"]
7 | [hiccup "1.0.5"]
8 | [page-renderer "0.4.4"]]
9 |
10 | :min-lein-version "2.9.1"
11 | :main crux-ui-server.main
12 | :aot [crux-ui-server.main]
13 | :uberjar-name "crux-console-skimmed.jar"
14 | :omit-sources true
15 | :resource-paths ["resources"]
16 |
17 | :profiles
18 | {:dev
19 | {:main dev :repl-options {:init-ns dev}
20 | :source-paths ["src" "test" #_"node_modules"]
21 | :dependencies [[nrepl/nrepl "0.6.0"]]}
22 | :crux-jars
23 | {:uberjar-name "crux-console.jar"
24 | :auto-clean false
25 | :dependencies
26 | [[juxt/crux-core "20.01-1.6.2-alpha"]
27 | [juxt/crux-rocksdb "20.01-1.6.2-alpha"]
28 | [juxt/crux-http-server "20.01-1.6.2-alpha"]]}
29 | :shadow-cljs ; also see package.json deps
30 | {:dependencies
31 | [[org.clojure/clojurescript "1.10.520"]
32 | [reagent "0.8.1"]
33 | [re-frame "0.10.8"]
34 | [garden "1.3.9"]
35 | [medley "1.2.0"]
36 | [funcool/promesa "2.0.1"]
37 | [org.clojure/tools.logging "0.5.0"]
38 | [com.andrewmcveigh/cljs-time "0.5.2"]
39 | [binaryage/oops "0.6.4"]
40 | [day8.re-frame/test "0.1.5"]
41 | [day8.re-frame/re-frame-10x "0.3.3"]
42 | [thheller/shadow-cljs "2.8.52"]
43 | [com.google.javascript/closure-compiler-unshaded "v20190819"]
44 | [org.clojure/google-closure-library "0.0-20190213-2033d5d9"]]}}
45 |
46 | ; AWS is using Java 8
47 | :javac-options ["-source" "8" "-target" "8"
48 | "-XDignore.symbol.file"
49 | "-Xlint:all,-options,-path"
50 | "-Werror"
51 | "-proc:none"]
52 |
53 | :repositories [["clojars" "https://repo.clojars.org"]]
54 | :plugins [;[lein-shadow "0.1.5"] ; nasty guy, deletes original shadow-cljs config if you run it
55 | [lein-shell "0.5.0"]] ; https://github.com/hypirion/lein-shell
56 |
57 | :clean-targets ^{:protect false} ["target" #_"resources/static/crux-ui/compiled"]
58 |
59 | :aliases
60 | {"yarn"
61 | ["do" ["shell" "yarn" "install"]]
62 |
63 | "shadow"
64 | ["shell" "node_modules/.bin/shadow-cljs"]
65 |
66 | "build"
67 | ["do"
68 | ["clean"]
69 | ["yarn"]
70 | ["shadow" "release" "app"] ; compile
71 | #_["shadow" "release" "app-perf"]] ; compile production ready performance charts app
72 |
73 | "ebs"
74 | ["do" ["shell" "sh" "./dev/build-ebs.sh"]]
75 |
76 | "build-ebs"
77 | ["do"
78 | ["build"]
79 | ["uberjar"]
80 | ["ebs"]]
81 |
82 | "cljs-dev"
83 | ["do"
84 | ["yarn"]
85 | ["shadow" "watch" "app"]]
86 |
87 | "dep-tree"
88 | ["do"
89 | ["yarn"]
90 | ["shadow" "pom"]
91 | ["shell" "mvn" "dependency:tree"]]
92 |
93 | "build-report"
94 | [["yarn"]
95 | ["shadow" "run" "shadow.cljs.build-report" "app" "report.html"]]})
96 |
97 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Crux Console
2 |
3 | Console is a web app that allows you to query Crux and see data from different perpectives.
4 | 
5 |
6 | It can be used as a standalone analytics tool or as an educational tool.
7 |
8 | ## Educational use
9 | Console comes with a set of example queries but you can make it load your own
10 | set by providing a url param `examples-gist=gist-raw-url`, like
11 | [here](http://console.crux.cloud/console?examples-gist=https://gist.githubusercontent.com/spacegangster/b68f72e3c81524a71af1f3033ea7507e/raw/5fb55fe8e766245a3338f6e5c508ffbbe824900f/examples.edn)
12 | This way Console will import this file.
13 | Important note : link must point to raw gist content.
14 | Preset queries example:
15 | ```clojure
16 | [{:title "Space Stocks"
17 | :query {:find [e]
18 | :where [[e :crux.db/id _]]}}
19 | {:title "Add space stocks"
20 | :query [[:crux.tx/put {:crux.db/id 33 :title "Company 3"}]]}]
21 | ```
22 |
23 | ## Deployment scenarios
24 | Can be used as an analytics tool
25 | For nginx – the following redirects may be used
26 |
27 | ```
28 | # in a server block
29 | location /console {
30 | # don't forget about at least basic auth
31 | proxy_pass http://localhost:5000;
32 | }
33 | location /crux {
34 | rewrite ^(/crux)(/.*)$ $2 break;
35 | proxy_pass http://localhost:4889/;
36 | }
37 | ```
38 |
39 | ### Command line flags
40 | ```
41 | --frontend-port 5000
42 | --embed-crux false
43 | --conf-file crux-console-conf.edn
44 | --crux-http-port 8080
45 | ```
46 |
47 | ## How to build or develop
48 |
49 | ### Prerequisites
50 |
51 | All builds require `node` and `yarn` to be installed.
52 |
53 | `yarn` is, at the moment, a much better alternative to npm.
54 | Yarn ROI is that in 2-3 uses it will save more time than you spent
55 | installing and learning it, compared to 2-3 runs of npm install.
56 |
57 |
58 | ### Release flow
59 | 1. `lein build` will produce you production ready assets
60 | 2. `lein build-ebs` does the build above and packs it into
61 | Elastic Beanstalk package which we upload to AWS to see console.crux.cloud
62 |
63 |
64 | ### Dev flow
65 | To launch development REPL
66 |
67 | #### Preferred
68 | ```sh
69 | lein cljs-dev # will install all the node modules, launch cljs compiling guard with code hotswapping
70 | lein repl # will launch edge rebel repl
71 | ```
72 |
73 |
74 | #### Alternative
75 | ```sh
76 | # once
77 | yarn install
78 |
79 | dev/shadow-dev &
80 | # will launch shadow-cljs watch build
81 | # it runs a local version
82 |
83 | lein repl
84 | # will launch the server
85 | ```
86 |
87 |
88 |
89 | ## Sidenotes
90 |
91 | #### Plotly
92 | Note that Plotly has its own packaging model (due to heavy weight beneath),
93 | so search for different packages on npm if you need more fine-grained control.
94 | e.g.
95 |
96 | - All-in-one https://www.npmjs.com/package/plotly.js-dist
97 |
98 | ### Datepickers
99 | There are a few things we can look at
100 | - Pikaday
101 | - looking nice and simple
102 | - no deps
103 | - url https://github.com/timgilbert/cljs-pikaday
104 | - no timepicker, but that's an easier fix
105 |
106 | - React datetime picker
107 | - demo url http://projects.wojtekmaj.pl/react-datetime-picker/
108 | - gh url: https://github.com/wojtekmaj/react-datetime-picker
109 | - good UX, has timepicker
110 | - claimed test cov 92%
111 | - seemingly lots of deps :(
112 |
113 | - Hacker one datepicker
114 | - demo link: https://reactdatepicker.com
115 | - gh link: https://github.com/Hacker0x01/react-datepicker/
116 | - Lots of options, arguable best datepicker-timepicker combination
117 | - quite heavy :(
118 |
119 | Things to be aware of
120 | - YouCanBookMe/react-datetime
121 | - drags moment.js as a dep
122 | - UX is questionable
123 |
124 | ## Potential extension points
125 |
126 | ### Build
127 | Can be improved with skipping with uberjar build and just updating the jar with
128 |
129 | ```
130 | jar uf jar-file input-file(s)
131 | ```
132 |
--------------------------------------------------------------------------------
/resources/screenshot-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xtdb-labs/crux-console/6bdc59339dd2513f60b61188c1c2f331006511fd/resources/screenshot-1.png
--------------------------------------------------------------------------------
/resources/static/img/console-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/resources/static/img/crux-labs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xtdb-labs/crux-console/6bdc59339dd2513f60b61188c1c2f331006511fd/resources/static/img/crux-labs.png
--------------------------------------------------------------------------------
/resources/static/img/crux-labs.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/resources/static/img/crux-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
40 |
--------------------------------------------------------------------------------
/resources/static/img/cube-for-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/resources/static/img/cube-on-white-120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xtdb-labs/crux-console/6bdc59339dd2513f60b61188c1c2f331006511fd/resources/static/img/cube-on-white-120.png
--------------------------------------------------------------------------------
/resources/static/img/cube-on-white-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xtdb-labs/crux-console/6bdc59339dd2513f60b61188c1c2f331006511fd/resources/static/img/cube-on-white-192.png
--------------------------------------------------------------------------------
/resources/static/img/cube-on-white-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xtdb-labs/crux-console/6bdc59339dd2513f60b61188c1c2f331006511fd/resources/static/img/cube-on-white-512.png
--------------------------------------------------------------------------------
/resources/static/img/cube.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/resources/static/img/juxt-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/resources/static/styles/eclipse.css:
--------------------------------------------------------------------------------
1 | .cm-s-eclipse span.cm-meta { color: #FF1717; }
2 | .cm-s-eclipse span.cm-keyword { line-height: 1em; font-weight: bold; color: #7F0055; }
3 | .cm-s-eclipse span.cm-atom { color: #219; }
4 | .cm-s-eclipse span.cm-number { color: #164; }
5 | .cm-s-eclipse span.cm-def { color: #00f; }
6 | .cm-s-eclipse span.cm-variable { color: black; }
7 | .cm-s-eclipse span.cm-variable-2 { color: #0000C0; }
8 | .cm-s-eclipse span.cm-variable-3, .cm-s-eclipse span.cm-type { color: #0000C0; }
9 | .cm-s-eclipse span.cm-property { color: black; }
10 | .cm-s-eclipse span.cm-operator { color: black; }
11 | .cm-s-eclipse span.cm-comment { color: #3F7F5F; }
12 | .cm-s-eclipse span.cm-string { color: #2A00FF; }
13 | .cm-s-eclipse span.cm-string-2 { color: #f50; }
14 | .cm-s-eclipse span.cm-qualifier { color: #555; }
15 | .cm-s-eclipse span.cm-builtin { color: #30a; }
16 | .cm-s-eclipse span.cm-bracket { color: #cc7; }
17 | .cm-s-eclipse span.cm-tag { color: #170; }
18 | .cm-s-eclipse span.cm-attribute { color: #00c; }
19 | .cm-s-eclipse span.cm-link { color: #219; }
20 | .cm-s-eclipse span.cm-error { color: #f00; }
21 |
22 | .cm-s-eclipse .CodeMirror-activeline-background { background: #e8f2ff; }
23 | .cm-s-eclipse .CodeMirror-matchingbracket { outline:1px solid grey; color:black !important; }
24 |
--------------------------------------------------------------------------------
/resources/static/styles/monokai.css:
--------------------------------------------------------------------------------
1 | /* Based on Sublime Text's Monokai theme */
2 |
3 | .cm-s-monokai.CodeMirror { background: #272822; color: #f8f8f2; }
4 | .cm-s-monokai div.CodeMirror-selected { background: #49483E; }
5 | .cm-s-monokai .CodeMirror-line::selection, .cm-s-monokai .CodeMirror-line > span::selection, .cm-s-monokai .CodeMirror-line > span > span::selection { background: rgba(73, 72, 62, .99); }
6 | .cm-s-monokai .CodeMirror-line::-moz-selection, .cm-s-monokai .CodeMirror-line > span::-moz-selection, .cm-s-monokai .CodeMirror-line > span > span::-moz-selection { background: rgba(73, 72, 62, .99); }
7 | .cm-s-monokai .CodeMirror-gutters { background: #272822; border-right: 0px; }
8 | .cm-s-monokai .CodeMirror-guttermarker { color: white; }
9 | .cm-s-monokai .CodeMirror-guttermarker-subtle { color: #d0d0d0; }
10 | .cm-s-monokai .CodeMirror-linenumber { color: #d0d0d0; }
11 | .cm-s-monokai .CodeMirror-cursor { border-left: 1px solid #f8f8f0; }
12 |
13 | .cm-s-monokai span.cm-comment { color: #75715e; }
14 | .cm-s-monokai span.cm-atom { color: #ae81ff; }
15 | .cm-s-monokai span.cm-number { color: #ae81ff; }
16 |
17 | .cm-s-monokai span.cm-comment.cm-attribute { color: #97b757; }
18 | .cm-s-monokai span.cm-comment.cm-def { color: #bc9262; }
19 | .cm-s-monokai span.cm-comment.cm-tag { color: #bc6283; }
20 | .cm-s-monokai span.cm-comment.cm-type { color: #5998a6; }
21 |
22 | .cm-s-monokai span.cm-property, .cm-s-monokai span.cm-attribute { color: #a6e22e; }
23 | .cm-s-monokai span.cm-keyword { color: #f92672; }
24 | .cm-s-monokai span.cm-builtin { color: #66d9ef; }
25 | .cm-s-monokai span.cm-string { color: #e6db74; }
26 |
27 | .cm-s-monokai span.cm-variable { color: #f8f8f2; }
28 | .cm-s-monokai span.cm-variable-2 { color: #9effff; }
29 | .cm-s-monokai span.cm-variable-3, .cm-s-monokai span.cm-type { color: #66d9ef; }
30 | .cm-s-monokai span.cm-def { color: #fd971f; }
31 | .cm-s-monokai span.cm-bracket { color: #f8f8f2; }
32 | .cm-s-monokai span.cm-tag { color: #f92672; }
33 | .cm-s-monokai span.cm-header { color: #ae81ff; }
34 | .cm-s-monokai span.cm-link { color: #ae81ff; }
35 | .cm-s-monokai span.cm-error { background: #f92672; color: #f8f8f0; }
36 |
37 | .cm-s-monokai .CodeMirror-activeline-background { background: #373831; }
38 | .cm-s-monokai .CodeMirror-matchingbracket {
39 | text-decoration: underline;
40 | color: white !important;
41 | }
42 |
--------------------------------------------------------------------------------
/resources/static/styles/react-input-range.css:
--------------------------------------------------------------------------------
1 | .input-range__slider {
2 | -webkit-appearance: none;
3 | -moz-appearance: none;
4 | appearance: none;
5 | background: #3f51b5;
6 | border: 1px solid #3f51b5;
7 | border-radius: 100%;
8 | cursor: pointer;
9 | display: block;
10 | height: 1rem;
11 | margin-left: -0.5rem;
12 | margin-top: -0.65rem;
13 | outline: none;
14 | position: absolute;
15 | top: 0%;
16 | transition: transform 0.1s ease-out, box-shadow 0.1s ease-out;
17 | will-change: transform, box-shadow;
18 | width: 1rem; }
19 | .input-range__slider:active {
20 | transform: scale(1.3); }
21 | .input-range__slider:focus {
22 | box-shadow: 0 0 0 5px rgba(63, 81, 181, 0.2); }
23 | .input-range--disabled .input-range__slider {
24 | background: #cccccc;
25 | border: 1px solid #cccccc;
26 | box-shadow: none;
27 | transform: none; }
28 |
29 | .input-range__slider-container {
30 | transition: left 0.3s ease-out; }
31 |
32 |
33 | .input-range__label {
34 | color: #aaaaaa;
35 | font-size: 0.8rem;
36 | transform: translateZ(0);
37 | white-space: nowrap; }
38 |
39 | .input-range__label--min,
40 | .input-range__label--max {
41 | display: none
42 | }
43 |
44 | .input-range__label--min {
45 | grid-area: l;
46 | left: 0; }
47 |
48 | .input-range__label--max {
49 | grid-area: r;
50 | right: 0; }
51 |
52 | .input-range__label--value,
53 | .input-range__label--value-year {
54 | top: -0.92em;
55 | position: absolute;
56 | left: 1em;
57 | text-align: right;
58 | width: 1.8em;
59 | color: black;
60 | font-size: 1.0em;
61 | font-weight: 400;
62 | font-variant-numeric: tabular-nums;}
63 |
64 | .input-range__label--value-year {
65 | left: 1.7em
66 | }
67 |
68 | .input-range__label-container {
69 | left: -50%;
70 | position: relative; }
71 | .input-range__label--max .input-range__label-container {
72 | left: 50%; }
73 |
74 | .input-range__track {
75 | background: #eeeeee;
76 | border-radius: 0.3rem;
77 | cursor: pointer;
78 | display: block;
79 | grid-area: c;
80 | height: 0.3rem;
81 | position: relative;
82 | will-change: left, width;
83 | transition: left 0.1s ease-out, width 0.1s ease-out; }
84 | .input-range--disabled .input-range__track {
85 | background: #eeeeee; }
86 |
87 | .input-range__track--background {
88 | left: 0;
89 | margin-top: -0.15rem;
90 | position: absolute;
91 | right: 0;
92 | top: 50%; }
93 |
94 | .input-range__track--active {
95 | background: #3f51b5; }
96 |
97 | .input-range {
98 | height: 1rem;
99 | position: relative;
100 | display: grid;
101 | grid-template: 'l c r' / 0px 1fr 30px;
102 | width: 100%; }
103 |
--------------------------------------------------------------------------------
/resources/static/styles/react-ui-tree.css:
--------------------------------------------------------------------------------
1 | .f-no-select {
2 | -webkit-user-select: none;
3 | -khtml-user-select: none;
4 | -moz-user-select: none;
5 | -ms-user-select: none;
6 | user-select: none;
7 | }
8 | .m-tree {
9 | position: relative;
10 | overflow: hidden;
11 | -webkit-user-select: none;
12 | -khtml-user-select: none;
13 | -moz-user-select: none;
14 | -ms-user-select: none;
15 | user-select: none;
16 | }
17 | .m-draggable {
18 | position: absolute;
19 | opacity: 0.8;
20 | -webkit-user-select: none;
21 | -khtml-user-select: none;
22 | -moz-user-select: none;
23 | -ms-user-select: none;
24 | user-select: none;
25 | }
26 | .m-node.placeholder > * {
27 | visibility: hidden;
28 | }
29 | .m-node.placeholder {
30 | border: 1px dashed #ccc;
31 | }
32 | .m-node .inner {
33 | position: relative;
34 | cursor: pointer;
35 | padding-left: 10px;
36 | }
37 | .m-node .collapse {
38 | position: absolute;
39 | left: 0;
40 | cursor: pointer;
41 | }
42 | .m-node .caret-right:before {
43 | content: '\25B8';
44 | }
45 | .m-node .caret-down:before {
46 | content: '\25BE';
47 | }
48 |
--------------------------------------------------------------------------------
/resources/static/styles/reset.css:
--------------------------------------------------------------------------------
1 | html, body, div, span, applet, object, iframe,
2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
3 | a, abbr, acronym, address, big, cite, code,
4 | del, dfn, em, img, ins, kbd, q, s, samp,
5 | small, strike, strong, sub, sup, tt, var,
6 | b, u, i, center, button,
7 | dl, dt, dd, ol, ul, li,
8 | fieldset, form, label, legend,
9 | table, caption, tbody, tfoot, thead, tr, th, td,
10 | article, aside, canvas, details, embed,
11 | figure, figcaption, footer, header, hgroup,
12 | menu, nav, output, ruby, section, summary,
13 | time, mark, audio, video {
14 | margin: 0;
15 | padding: 0;
16 | border: 0;
17 | font-size: 100%;
18 | font: inherit;
19 | vertical-align: baseline;
20 | box-sizing: border-box;
21 | }
22 |
23 |
24 | h1 {
25 | font-size: 36px
26 | }
27 | h2 {
28 | font-size: 30px
29 | }
30 | h3 {
31 | font-size: 24px
32 | }
33 | h4 {
34 | font-size: 18px
35 | }
36 | h5 {
37 | font-size: 14px
38 | }
39 | h6 {
40 | font-size: 12px
41 | }
42 |
43 | body {
44 | line-height: 1.5;
45 | font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;
46 | }
47 |
48 | table {
49 | border-collapse: collapse;
50 | border-spacing: 0;
51 | }
52 |
--------------------------------------------------------------------------------
/shadow-cljs.edn:
--------------------------------------------------------------------------------
1 | ;; shadow-cljs configuration
2 | {:source-paths ["src" "test" "node_modules"]
3 |
4 | :dependencies
5 | [[reagent "0.8.1"]
6 | [re-frame "0.10.8"]
7 | [garden "1.3.9"]
8 | [bidi "2.1.6"]
9 | [stylefy "1.13.3"]
10 | [medley "1.2.0"]
11 | [day8.re-frame/re-frame-10x "0.4.1"]
12 | [clj-commons/secretary "1.2.4"]
13 | [funcool/promesa "2.0.1"]
14 | [com.andrewmcveigh/cljs-time "0.5.2"]
15 | [binaryage/oops "0.6.4"]]
16 |
17 | :builds
18 | {:app
19 | {:target :browser
20 | :modules {:main {:init-fn crux-ui.main/init}}
21 | :output-dir "resources/static/crux-ui/compiled"
22 | :compiler-options {:optimizations :advanced}
23 | :asset-path "/console/static/crux-ui/compiled"}
24 |
25 | :app-perf
26 | {:target :browser
27 | :modules {:main-perf {:init-fn crux-ui.main-perf/init}}
28 | :output-dir "resources/static/crux-ui/compiled"
29 | :compiler-options {:optimizations :advanced}
30 | :asset-path "/console/static/crux-ui/compiled"}
31 |
32 |
33 | :test
34 | {:target :browser-test
35 | :test-dir "resources/static/crux-ui/test"
36 | :ns-regexp "-test$"
37 | :runner-ns crux-console.test-runner
38 | :devtools
39 | {:http-port 4001
40 | :http-root "resources/static/crux-ui/test"}}
41 |
42 | :dev
43 | {:target :browser
44 | :modules {:main {:init-fn crux-ui.main/init}}
45 | :output-dir "resources/static/crux-ui/compiled"
46 | :asset-path "/console/static/crux-ui/compiled"
47 | :compiler-options {:closure-warnings {:global-this :off}
48 | :closure-defines {re-frame.trace.trace-enabled? true}
49 | :optimizations :none}
50 | :devtools
51 | {:after-load crux-ui.main/reload!
52 | :preloads [day8.re-frame-10x.preload]}}
53 |
54 | :dev-perf
55 | {:target :browser
56 | :modules
57 | {:main-perf {:init-fn crux-ui.main-perf/init}}
58 | :output-dir "resources/static/crux-ui/compiled"
59 | :asset-path "/static/crux-ui/compiled"
60 | :compiler-options {:closure-warnings {:global-this :off}
61 | :closure-defines {re-frame.trace.trace-enabled? true}
62 | :optimizations :none}
63 | :devtools
64 | {:after-load crux-ui.main-perf/reload!
65 | :preloads [day8.re-frame-10x.preload]}}}
66 |
67 | :nrepl {:port 55300
68 | :init-ns crux-ui.main}}
69 |
--------------------------------------------------------------------------------
/src/crux_ui/better_printer.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.better-printer
2 | (:require [cljs.pprint :as pp]
3 | [clojure.string :as s]))
4 |
5 |
6 | (defmulti better-printer
7 | (fn [edn]
8 | (cond
9 | (string? edn) ::string
10 | (map? edn) ::map
11 | (vector? edn) ::vec
12 | :else ::default)))
13 |
14 | (defn simple-print [edn]
15 | (with-out-str (pp/pprint edn)))
16 |
17 | (defn- print-entry [i [k v]]
18 | (if (= 0 i)
19 | (str (pr-str k) "\t" (pr-str v))
20 | (str " " (pr-str k) "\t" (pr-str v))))
21 |
22 | (defmethod better-printer ::map [edn]
23 | (str "{" (s/join "\n" (map-indexed print-entry edn)) "}"))
24 |
25 | (defmethod better-printer ::string [edn-str]
26 | (pr-str edn-str))
27 |
28 | (defmethod better-printer ::vec [edn]
29 | (with-out-str (pp/pprint edn)))
30 |
31 | (defmethod better-printer ::default [edn]
32 | (with-out-str (pp/pprint edn)))
33 |
34 | (comment
35 | (println
36 | (better-printer '{:find [e p], :where [[e :crux.db/id _] [e :ticker/price p]]}))
37 | (println
38 | (better-printer '[:find [e p], :where [[e :crux.db/id _] [e :ticker/price p]]])))
39 |
--------------------------------------------------------------------------------
/src/crux_ui/config.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.config
2 | (:require ["./ua-regexp.js" :as ua-rgx]))
3 |
4 | (def url-docs "https://juxt.pro/crux/docs/index.html")
5 | (def url-chat "https://juxt-oss.zulipchat.com/#narrow/stream/194466-crux")
6 | (def url-mail "crux@juxt.pro")
7 | (def url-examples-gist "https://gist.githubusercontent.com/spacegangster/b68f72e3c81524a71af1f3033ea7507e/raw/572396dec0791500c965fea443b2f26a60f500d4/examples.edn")
8 |
9 | (def ^:const ua-regex ua-rgx)
10 |
11 | (def ^:const user-agent js/navigator.userAgent)
12 |
13 | (def ^:const ua-info
14 | (re-find ua-regex user-agent))
15 |
16 | (def ^:const browser-vendor-string (second ua-info))
17 | (def ^:const browser-version-string (nth ua-info 2))
18 |
19 | (def ^:const browser-vendor
20 | (case browser-vendor-string
21 | "Chrome" :browser/chrome
22 | "Firefox" :browser/firefox
23 | "Opera" :browser/opera
24 | "Safari" :browser/safari
25 | "Edge" :browser/edge
26 | :browser/unknown))
27 |
28 | (def ^:const supports-input-datetime?
29 | (not (#{:browser/firefox :browser/ie :browser/safari :browser/unknown} browser-vendor)))
30 |
--------------------------------------------------------------------------------
/src/crux_ui/cookies.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.cookies
2 | (:refer-clojure :exclude [count get keys vals empty? reset!])
3 | (:require [goog.net.cookies :as cks]
4 | [cljs.reader :as reader]))
5 |
6 | (defn set!
7 | "sets a cookie, the max-age for session cookie
8 | following optional parameters may be passed in as a map:
9 | :max-age - defaults to -1
10 | :path - path of the cookie, defaults to the full request path
11 | :domain - domain of the cookie, when null the browser will use the full request host name
12 | :secure? - boolean specifying whether the cookie should only be sent over a secure channel
13 | :raw? - boolean specifying whether content should be stored raw, or as EDN
14 | "
15 | [k content & [{:keys [max-age path domain secure? raw?]} :as opts]]
16 | (let [k (name k)
17 | content (if raw?
18 | (str content)
19 | (pr-str content))]
20 | (if-not opts
21 | (.set goog.net.cookies k content)
22 | (.set goog.net.cookies k content (or max-age -1) path domain (boolean secure?)))))
23 |
24 | (defn- get-value [k]
25 | (.get goog.net.cookies k))
26 |
27 | (defn get
28 | "gets the value at the key (as edn), optional default when value is not found"
29 | [k & [default]]
30 | (or (some-> k name get-value reader/read-string) default))
31 |
32 | (defn contains-key?
33 | "is the key present in the cookies"
34 | [k]
35 | (.containsKey goog.net.cookies (name k)))
36 |
37 | (defn empty?
38 | "true if no cookies are set"
39 | []
40 | (.isEmpty goog.net.cookies))
41 |
42 | (defn remove!
43 | "removes a cookie, optionally for a specific path and/or domain"
44 | ([k]
45 | (.remove goog.net.cookies (name k)))
46 | ([k path domain]
47 | (.remove goog.net.cookies (name k) path domain)))
48 |
--------------------------------------------------------------------------------
/src/crux_ui/events/default_db.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.events.default-db
2 | (:require [clojure.string :as s]
3 | [crux-ui.cookies :as c]
4 | [crux-ui.views.commons.dom :as dom]))
5 |
6 | (def example-query-str
7 | (s/join "\n"
8 | ["{:find [e]"
9 | " :where"
10 | " [[e :crux.db/id _]]"
11 | "; options"
12 | " :full-results? true}"]))
13 |
14 | (def ^:private now (js/Date.))
15 |
16 | (def default-db
17 | {:db.query/input example-query-str
18 | :db.query/time {:time/vt nil :time/tt nil}
19 | :db.query/input-committed example-query-str
20 | :db.query/limit 10000
21 | :db.query.attr-history/docs-limit 10000
22 | :db.sys/host (dom/calc-initial-host)
23 | :db.sys/initialized? false
24 | :db.sys/route {:r/handler :rd/query-ui :r/query-params {:r/output-tab :db.ui.output-tab/table}}
25 | :db.ui/root-tab :db.ui.root-tab/query-ui
26 | :db.ui/output-side-tab nil ;:db.ui.output-tab/table
27 | :db.ui/editor-key 0
28 | :db.ui/show-form? true
29 | :db.ui/second-layer false
30 | :db.ui.second-layer/main-pane nil
31 | :db.ui.attr-history/hint? true
32 | :db.ui/display-mode :ui.display-mode/query
33 | :db.ui/screen-size {:ui.screen/inner-width js/window.innerWidth
34 | :ui.screen/inner-height js/window.innerHeight}
35 | :db.ui.examples/closed? (c/get :db.ui.examples/closed? false)
36 | :db.query/key 0
37 | :db.query/error nil
38 | :db.query/result nil})
39 |
--------------------------------------------------------------------------------
/src/crux_ui/events/effects.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.events.effects
2 | (:require [re-frame.core :as rf]
3 | [crux-ui.io.query :as q]
4 | [crux-ui.routes :as routes]
5 | [crux-ui-lib.http-functions :as hf]
6 | [promesa.core :as p]
7 | [crux-ui.cookies :as c]
8 | [crux-ui.logging :as log]))
9 |
10 | (rf/reg-fx
11 | :fx/query-exec
12 | (fn [{:keys [raw-input query-analysis] :as query}]
13 | (if query
14 | (q/exec query))))
15 |
16 | (defn qs [_]
17 | (q/fetch-stats))
18 |
19 | (rf/reg-fx
20 | :fx/query-stats
21 | qs)
22 |
23 | (rf/reg-fx
24 | :fx/set-node
25 | (fn [node-addr]
26 | (when node-addr
27 | (q/set-node! (str "//" node-addr))
28 | (q/ping-status)
29 | (q/fetch-stats))))
30 |
31 | (rf/reg-fx
32 | :fx.query/history
33 | (fn [history-query]
34 | (q/fetch-histories history-query)))
35 |
36 | (rf/reg-fx
37 | :fx.query/histories-docs
38 | (fn [eids->histories]
39 | (q/fetch-histories-docs eids->histories)))
40 |
41 | (rf/reg-fx
42 | :fx/push-query-into-url
43 | (fn [^js/String query]
44 | (prn ::push-query query)
45 | (if (and query (< (.-length query) 2000))
46 | (try
47 | (routes/push-query query)
48 | (catch js/Error e
49 | (log/warn "failed to push query"))))))
50 |
51 | (rf/reg-fx
52 | :fx.sys/set-cookie
53 | (fn [[cookie-name value]]
54 | (c/set! cookie-name value {:max-age 86400})))
55 |
56 | (rf/reg-fx
57 | :fx.ui/alert
58 | (fn [message]
59 | (js/alert message)))
60 |
61 | (rf/reg-fx
62 | :fx.ui/prompt
63 | (fn [[message event-id]]
64 | (rf/dispatch [event-id (js/prompt message)])))
65 |
66 | (rf/reg-fx
67 | :fx.ui/toggle-fullscreen
68 | (fn [_]
69 | (if js/document.fullscreen
70 | (js/document.exitFullscreen)
71 | (^js .requestFullscreen js/document.documentElement))))
72 |
73 | (rf/reg-fx
74 | :fx/set-polling
75 | (fn [poll-interval-in-seconds]
76 | (some-> js/window.__console_polling_id js/clearInterval)
77 | (when poll-interval-in-seconds
78 | (let [ms (* 1000 poll-interval-in-seconds)
79 | iid (js/setInterval #(rf/dispatch [:evt.ui.query/poll-tick]) ms)]
80 | (set! js/window.__console_polling_id iid)))))
81 |
82 | (defn grab-gh-gist [gh-link]
83 | (-> (hf/fetch gh-link)
84 | (p/catch #(rf/dispatch [:evt.io/gist-err %]))
85 | (p/then #(rf/dispatch [:evt.io/gist-success (:body %)]))))
86 |
87 | (rf/reg-fx
88 | :fx/get-github-gist
89 | grab-gh-gist)
90 |
--------------------------------------------------------------------------------
/src/crux_ui/functions.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.functions
2 | (:require [cljs.core.async :as async]
3 | [clojure.string :as str]
4 | [cljs.pprint :as pprint])
5 | (:import [goog.object]
6 | [goog.async Debouncer]))
7 |
8 | (def jsget goog.object/getValueByKeys)
9 |
10 | (defn pprint-str [edn]
11 | (with-out-str (pprint/pprint edn)))
12 |
13 | (defn merge-with-keep [a]
14 | (apply merge-with (fn [v1 v2] ((if (vector? v1) conj vector) v1 v2)) a))
15 |
16 | (defn debounce [f interval]
17 | (let [dbnc (Debouncer. f interval)]
18 | ;; We use apply here to support functions of various arities
19 | (fn [& args] (.apply (.-fire dbnc) dbnc (to-array args)))))
20 |
21 | (defn lines [& lines] (str/join "\n" lines))
22 |
23 | (defn index-by
24 | "indexes a finite collection"
25 | [key-fn coll]
26 | (loop [remaining-items (seq coll)
27 | index-map (transient {})]
28 | (if-let [x (first remaining-items)]
29 | (recur (next remaining-items)
30 | (assoc! index-map (key-fn x) x))
31 | (persistent! index-map))))
32 |
33 |
34 | (defn map-map-values-vec [f m]
35 | (into {} (for [[k vs] m] [k (mapv f vs)])))
36 |
37 | (defn map-keys [f m]
38 | (into {} (for [[k v] m] [(f k) v])))
39 |
40 | (defn map-values [f m]
41 | (into {} (for [[k v] m] [k (f v)])))
42 |
43 | (defn prefix-keys [prefix map]
44 | (map-keys #(keyword (str (name prefix) "/" (name %))) map))
45 |
46 | (defn ks-vs-to-map [ks vs]
47 | (merge-with-keep (map #(zipmap ks %) vs)))
48 |
49 | (defn positions
50 | [pred coll]
51 | (keep-indexed (fn [idx x]
52 | (when (pred x)
53 | idx))
54 | coll))
55 |
56 | (defn qsort [f sb]
57 | (apply juxt (map (fn [s] (fn [x] (nth x (first (positions #{s} f))))) sb)))
58 |
59 |
60 | (defn format-date [d]
61 | (.toISOString (new js/Date d)))
62 |
63 | (defn decode-html [html]
64 | (let [txt (.createElement js/document "textarea")]
65 | (set! (.-innerHTML txt) html)
66 | (.. txt -value)))
67 |
68 | (defn strreplace [s]
69 | (clojure.string/replace s #"(?:\\r\\n|\\r|\\n)" "
"))
70 | ;/(?:\r\n|\r|\n)/g
71 |
72 | (defn e0? [v]
73 | (= 'e (first v)))
74 |
75 |
--------------------------------------------------------------------------------
/src/crux_ui/io/query.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.io.query
2 | (:require [re-frame.core :as rf]
3 | [medley.core :as m]
4 | [crux-ui-lib.async-http-client :as crux-api]
5 | [promesa.core :as p]
6 | [cljs.reader :as reader]
7 | [crux-ui.logging :as log]))
8 |
9 |
10 | (defn post-opts [body]
11 | #js {:method "POST"
12 | :body body
13 | :headers #js {:Content-Type "application/edn"}})
14 |
15 |
16 | (def debug? ^boolean goog.DEBUG)
17 |
18 | (def ^:private node-client (atom nil))
19 |
20 | (defn set-node!
21 | "node-address crux node "
22 | [^js/String node-address]
23 | (reset! node-client (crux-api/new-api-client node-address)))
24 |
25 | (defn- on-exec-success [resp]
26 | (rf/dispatch [:evt.io/query-success resp]))
27 |
28 | (defn- on-stats-success [resp]
29 | (rf/dispatch [:evt.io/stats-success resp]))
30 |
31 | (defn- on-status-success [resp]
32 | (rf/dispatch [:evt.sys/node-connect-success resp]))
33 |
34 | (defn- on-tx-success [resp]
35 | (rf/dispatch [:evt.io/tx-success resp]))
36 |
37 | (defn- on-histories-success [eid->history]
38 | (rf/dispatch [:evt.io/histories-fetch-success eid->history]))
39 |
40 | (defn- on-histories-docs-success [eid->history]
41 | (rf/dispatch [:evt.io/histories-with-docs-fetch-success eid->history]))
42 |
43 | (defn- on-error [query-type err]
44 | (rf/dispatch [:evt.io/query-error
45 | {:evt/query-type query-type
46 | :evt/error (.-data err)}]))
47 |
48 | (defn- on-error--query [err]
49 | (on-error :crux.ui.query-type/query err))
50 |
51 | (defn- on-error--tx [err]
52 | (on-error :crux.ui.query-type/tx err))
53 |
54 |
55 | (defn exec-q [query-edn vt tt]
56 | (-> @node-client
57 | (crux-api/db vt tt)
58 | (crux-api/q query-edn)
59 | (p/then on-exec-success)
60 | (p/catch on-error--query)))
61 |
62 | (defn exec-tx [query-text]
63 | (-> @node-client
64 | (crux-api/submitTx query-text)
65 | (p/then on-tx-success)
66 | (p/catch on-error--tx)))
67 |
68 | (defn exec [{:keys [query-vt query-tt raw-input query-analysis] :as query}]
69 | (let [qtype (:crux.ui/query-type query-analysis)]
70 | (if-not query-analysis
71 | (println "err") ; TODO feedback to UI, or rather, UI shouldn't let it get this far
72 | (case qtype
73 | :crux.ui.query-type/query (exec-q raw-input query-vt query-tt)
74 | :crux.ui.query-type/tx (exec-tx raw-input)))))
75 |
76 |
77 | (defn fetch-stats []
78 | (-> @node-client
79 | (crux-api/attributeStats)
80 | (p/then on-stats-success)))
81 |
82 | (defn ping-status []
83 | (-> @node-client
84 | (crux-api/status)
85 | (p/then on-status-success)))
86 |
87 | (defn fetch-history [eid time-bounds docs-limit]
88 | (let [{:time/keys [tt-upper vt-upper]} time-bounds]
89 | (-> @node-client
90 | (crux-api/historyRange eid nil nil vt-upper tt-upper)
91 | (p/then #(take docs-limit %)))))
92 | ; (fetch-history :ids/fashion-ticker-2)
93 |
94 | (defn fetch-histories
95 | "Fetches histories, without docs"
96 | [{:q/keys [entity-ids time-bounds documents-per-entity-limit]
97 | :as history-query}]
98 | (-> (p/all (map #(fetch-history % time-bounds documents-per-entity-limit) entity-ids))
99 | (p/then #(zipmap entity-ids %))
100 | (p/then on-histories-success)))
101 | ; (fetch-histories [:ids/fashion-ticker-2])
102 |
103 | (defn fetch-docs [hashes]
104 | (crux-api/documents @node-client hashes))
105 |
106 | (defn- merge-docs-into-histories [eids->histories hash->doc]
107 | (let [with-doc
108 | (fn [{ch :crux.db/content-hash :as history-entry}]
109 | (assoc history-entry :crux.query/doc (hash->doc ch)))
110 | merge-docs-into-history
111 | (fn [eid history]
112 | (map with-doc history))]
113 | (m/map-kv-vals merge-docs-into-history eids->histories)))
114 |
115 | #_(merge-docs-into-histories
116 | {:ids/fashion-ticker-2 [{:crux.db/content-hash "49556fb568926e9f4d1d053f65fe86969baed9ab"}
117 | {:crux.db/content-hash "41f5d2050100fa23c0084042bda7a59d8d41d07b"}]}
118 | {"49556fb568926e9f4d1d053f65fe86969baed9ab" {:crux.db/id :ids/fashion-ticker-2,
119 | :ticker/price 77,
120 | :ticker/currency :currency/rub},
121 | "41f5d2050100fa23c0084042bda7a59d8d41d07b" {:crux.db/id :ids/fashion-ticker-2,
122 | :ticker/price 75,
123 | :ticker/currency :currency/rub}})
124 |
125 |
126 | (defn on-histories-docs-error [err]
127 | (println :on-histories-docs-error err))
128 |
129 | (defn fetch-histories-docs
130 | "Fetches histories, without docs"
131 | [eids->histories]
132 | (let [doc-hashes (->> eids->histories vals flatten (map :crux.db/content-hash))]
133 | (-> (fetch-docs (set doc-hashes))
134 | (p/then (fn [hash->doc] (merge-docs-into-histories eids->histories hash->doc)))
135 | (p/then on-histories-docs-success)
136 | (p/catch on-histories-docs-error))))
137 | #_(fetch-histories-docs
138 | {:ids/fashion-ticker-2
139 | [{:crux.db/content-hash "49556fb568926e9f4d1d053f65fe86969baed9ab"}
140 | {:crux.db/content-hash "41f5d2050100fa23c0084042bda7a59d8d41d07b"}]})
141 |
142 | (comment
143 |
144 | terr
145 |
146 | (-> '{:find [e]
147 | :args [{}]
148 | :where [[e :crux.db/id :ids/tech-ticker-2]]}
149 | pr-str
150 | exec-q))
151 |
--------------------------------------------------------------------------------
/src/crux_ui/logging.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.logging)
2 |
3 | (defn debug [& values]
4 | (apply (.-debug js/console) (map clj->js values)))
5 |
6 | (defn log [& values]
7 | (apply (.-log js/console) (map clj->js values)))
8 |
9 | (defn info [& values]
10 | (apply (.-info js/console) (map clj->js values)))
11 |
12 | (defn warn [& values]
13 | (apply (.-warn js/console) (map clj->js values)))
14 |
15 | (defn error [& values]
16 | (apply (.-error js/console) (map clj->js values)))
--------------------------------------------------------------------------------
/src/crux_ui/logic/history_perversions.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.logic.history-perversions
2 | (:require [medley.core :as m]))
3 |
4 | (defn- simplify-history-entry
5 | [{:keys [crux.query/doc
6 | crux.db/content-hash
7 | crux.tx/tx-time
8 | crux.tx/tx-id
9 | crux.db/valid-time]
10 | :as entry}]
11 | (assoc doc :crux.db/valid-time valid-time
12 | :crux.tx/tx-time tx-time
13 | :crux.tx/tx-id tx-id
14 | :crux.db/content-hash content-hash))
15 |
16 | (defn- simplify-entity-history [eid history]
17 | (map simplify-history-entry history))
18 |
19 | (defn calc-entity-time-series [numeric-attrs eid->history-range]
20 | (m/map-kv-vals simplify-entity-history eid->history-range))
21 |
--------------------------------------------------------------------------------
/src/crux_ui/logic/plotting_data_calc.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.logic.plotting-data-calc)
2 |
3 | ; See https://plot.ly/javascript/line-and-scatter/ for docs
4 |
5 | (defn auto-name [v]
6 | (cond-> v (keyword? v) name))
7 |
8 | (defn- attr-info-str
9 | [attr-key
10 | {:keys
11 | [crux.db/valid-time
12 | crux.db/content-hash
13 | crux.tx/tx-time
14 | crux.tx/tx-id]
15 | :as simple-history-entry}]
16 | (str (auto-name attr-key) ": " (get simple-history-entry attr-key) "
"
17 | "vt: " valid-time "
"
18 | "tt: " tx-time "
"
19 | "tx-id: " tx-id "
"
20 | "hash: " content-hash))
21 |
22 | (defn calc-plotly-trace--attr [attr-key eid simple-history]
23 | {:name (auto-name eid)
24 | :type "scatter"
25 | :text (map (partial attr-info-str attr-key) simple-history)
26 | :x (map :crux.db/valid-time simple-history)
27 | :y (map attr-key simple-history)})
28 |
29 |
30 | (defn- tx-info-str
31 | [{:keys [crux.db/valid-time crux.tx/tx-time crux.tx/tx-id]}]
32 | (str "vt: " valid-time "
"
33 | "tt: " tx-time "
"
34 | "tx-id" tx-id))
35 |
36 | (defn calc-plotly-trace--tx-scatter [eid txes]
37 | {:name (auto-name eid)
38 | :mode "markers"
39 | :type "scatter"
40 | :text (map tx-info-str txes)
41 | :x (map :crux.db/valid-time txes)
42 | :y (map :crux.tx/tx-time txes)})
43 |
--------------------------------------------------------------------------------
/src/crux_ui/logic/query_analysis.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.logic.query-analysis
2 | (:require [medley.core :as m]
3 | [clojure.set :as cset]
4 | [cljs.reader :as reader]
5 | [crux-ui-lib.functions :as fns]))
6 |
7 |
8 | (defn calc-vector-headers [query-vector]
9 | (->> query-vector
10 | (drop-while #(not= :find %))
11 | (rest)
12 | (take-while #(not= (keyword? %)))))
13 |
14 |
15 | (defn analyse-full-results-headers [query-results-seq]
16 | (let [res-count (count query-results-seq)
17 | sample (if (> res-count 50)
18 | (random-sample (/ 50 res-count) query-results-seq)
19 | query-results-seq)]
20 | (set (flatten (map (comp keys first) sample)))))
21 |
22 | (defn single-tx-vec->map [[type doc-id doc vt tt]]
23 | {:crux.ui/query-type :crux.ui.query-type/tx
24 | :crux.tx/type type
25 | :crux.db/id doc-id
26 | :crux.db/doc doc
27 | :crux.ui/vt vt
28 | :crux.ui/tt tt})
29 |
30 | (defn multi-tx-vec->map [txes-vector]
31 | (let [tx-infos (map single-tx-vec->map txes-vector)]
32 | {:crux.ui/query-type :crux.ui.query-type/tx
33 | :tx-count (count tx-infos)
34 | :tx-infos tx-infos}))
35 |
36 | (defn try-parse-edn-string-or-nil [^js/String str]
37 | (try
38 | (reader/read-string str)
39 | (catch js/Error e
40 | nil)))
41 |
42 | (defn try-parse-edn-string [^js/String str]
43 | (try
44 | (reader/read-string str)
45 | (catch js/Error e
46 | {:error e})))
47 |
48 | (defn query-vector? [edn]
49 | (and (vector? edn) (= :find (first edn))))
50 |
51 | (def crux-tx-types-set
52 | #{:crux.tx/put :crux.tx/cas :crux.tx/delete :crux.tx/evict})
53 |
54 | (defn single-tx-vector? [edn]
55 | (and (vector? edn) (crux-tx-types-set (first edn))))
56 |
57 | (defn multi-tx-vector? [edn]
58 | (and (vector? edn) (not-empty edn) (every? single-tx-vector? edn)))
59 |
60 | (defn query-map? [edn]
61 | (and (map? edn) (every? edn [:find :where])))
62 |
63 | (defn third [coll]
64 | (nth coll 2 nil))
65 |
66 | (defn infer-symbol-attr-map
67 | "Given a simple datalog query map returns a map symbol -> attribute"
68 | [qmap]
69 | (let [symbols-queried (set (:find qmap))
70 | where-vec (:where qmap)
71 | attr-triplets (filter (comp keyword? second) where-vec)
72 | eid-triplets (filter (comp symbol? first) where-vec)
73 | retained-triplets (filter #(symbols-queried (third %)) attr-triplets)
74 | eid-symbols (set (map first eid-triplets))
75 | retained-eids (set (filter symbols-queried eid-symbols))
76 | lookup-symbol-attr (fn [symbol]
77 | (second (m/find-first #(= (third %) symbol) retained-triplets)))
78 | symbol-attr-pair (fn [symbol]
79 | [symbol
80 | (if (retained-eids symbol)
81 | :crux.db/id
82 | (lookup-symbol-attr symbol))])
83 | symbol->attr (into {} (map symbol-attr-pair symbols-queried))]
84 | symbol->attr))
85 |
86 |
87 | (defn analyse-query [input-edn]
88 | (let [qmap (fns/normalize-query input-edn)
89 | s->a (infer-symbol-attr-map qmap)
90 | a->s (cset/map-invert s->a)
91 | attr-seq (vals s->a)
92 | attr-set (set attr-seq)
93 | find-vec (:find qmap)
94 | symbol-positions (into {} (map vector find-vec (range)))
95 | attr-positions (zipmap attr-seq (map (comp symbol-positions a->s) attr-seq))]
96 | (assoc qmap :crux.ui/query-type :crux.ui.query-type/query
97 | :query/style (if (map? input-edn) :query.style/map :query.style/vector)
98 | :query/ids-queried? (attr-set :crux.db/id)
99 | :query/symbol-positions symbol-positions
100 | :query/original-edn input-edn
101 | :query/normalized-edn qmap
102 | :query/attr-set attr-set
103 | :query/attr-vec (mapv s->a find-vec)
104 | :query/attr-positions attr-positions
105 | :query/pos->attr (cset/map-invert attr-positions)
106 | :query/attributes s->a)))
107 |
108 | (defn analyse-any-query [input-edn]
109 | (try
110 | (cond
111 | (query-vector? input-edn) (analyse-query input-edn)
112 | (multi-tx-vector? input-edn) (multi-tx-vec->map input-edn)
113 | (query-map? input-edn) (analyse-query input-edn)
114 | :else false)
115 | (catch js/Error e
116 | false)))
117 |
118 | (defn- calc-numeric-keys [result-map]
119 | (map first (filter (comp number? second) result-map)))
120 |
121 | (defn analyse-results
122 | [{:query/keys
123 | [attr-set
124 | attr-vec]
125 | :as query-info}
126 | results]
127 | (if (and (= (:crux.ui/query-type query-info) :crux.ui.query-type/query)
128 | (not-empty results))
129 | (let [r-count (count results)
130 | ids-received? (or (attr-set :crux.db/id) (:full-results? query-info))
131 | full-results? (:full-results? query-info)
132 | first-res (if full-results?
133 | (-> results first first)
134 | (-> results first))
135 | first-res-map (if full-results?
136 | first-res
137 | (zipmap attr-vec first-res))
138 | ids-pluck
139 | (cond
140 | full-results? (comp :crux.db/id first)
141 | ids-received? first
142 | :else identity)
143 | numeric-attrs (disj (set (calc-numeric-keys first-res-map)) :crux.db/id)
144 | discrete-attrs (cset/difference (disj attr-set :crux.db/id) numeric-attrs)
145 | ids (if ids-received? (map ids-pluck results))]
146 | {:ra/single-entity? (= 1 r-count)
147 | :ra/results-count r-count
148 | :ra/has-results? (> r-count 0)
149 | :ra/numeric-attrs numeric-attrs
150 | :ra/has-numeric-attrs? (> (count numeric-attrs) 0)
151 | :ra/discrete-attrs discrete-attrs
152 | :ra/entity-ids ids
153 | :ra/entity-id (first ids)
154 | :ra/first-res-map first-res-map
155 | :ra/first-res first-res})
156 | (println :bailing-out query-info results)))
157 |
--------------------------------------------------------------------------------
/src/crux_ui/logic/readme.md:
--------------------------------------------------------------------------------
1 | This package is for pure functions of different domains
--------------------------------------------------------------------------------
/src/crux_ui/logic/time.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.logic.time
2 | (:require [goog.string :as gs]))
3 |
4 | (defn format-iso-8601--utc [^js/Date d]
5 | (str "" (.getUTCFullYear d)
6 | "-" (gs/padNumber (inc (.getUTCMonth d)) 2)
7 | "-" (gs/padNumber (.getUTCDate d) 2)
8 | "T" (gs/padNumber (.getUTCHours d) 2)
9 | ":" (gs/padNumber (.getUTCMinutes d) 2)
10 | ":" (gs/padNumber (.getUTCSeconds d) 2)
11 | "." (gs/padNumber (.getUTCMilliseconds d) 3)
12 | "Z"))
13 |
14 | (defn format-for-dt-local [^js/Date d]
15 | (str "" (.getFullYear d)
16 | "-" (gs/padNumber (inc (.getMonth d)) 2)
17 | "-" (gs/padNumber (.getDate d) 2)
18 | "T" (gs/padNumber (.getHours d) 2)
19 | ":" (gs/padNumber (.getMinutes d) 2)))
20 |
21 | (def user-time-zone
22 | (.. js/Intl (^js DateTimeFormat) (^js resolvedOptions) ^js -timeZone))
23 |
24 |
25 | (defn date->comps [^js/Date t]
26 | {:time/year (.getFullYear t)
27 | :time/month (inc (.getMonth t))
28 | :time/date (.getDate t)
29 | :time/hour (.getHours t)
30 | :time/minute (.getMinutes t)})
31 |
32 | (defn comps->date [comps]
33 | (let [date-str (str (:time/year comps) "-"
34 | (gs/padNumber (:time/month comps) 2) "-"
35 | (gs/padNumber (:time/date comps) 2) " "
36 | (gs/padNumber (:time/hour comps) 2) ":"
37 | (gs/padNumber (:time/minute comps) 2))
38 | ts (js/Date.parse date-str)]
39 | (if-not (js/isNaN ts)
40 | (js/Date. ts))))
41 |
42 | (comment
43 | (let [d (js/Date.)]
44 | (date->comps d)
45 | (comps->date (date->comps d))))
46 |
--------------------------------------------------------------------------------
/src/crux_ui/main.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.main
2 | (:require [reagent.core :as r]
3 | [crux-ui.subs]
4 | [re-frame.core :as rf]
5 | [crux-ui.views.facade :as views]
6 | [crux-ui.events.facade]
7 | [crux-ui.routes :as routes]
8 | [crux-ui.events.default-db :as d]
9 | [clojure.string :as s]
10 | [crux-ui.logging :as log]))
11 |
12 |
13 |
14 | (set! js/window.tojs clj->js)
15 |
16 | (defn mount-root []
17 | (r/render [views/root] (js/document.getElementById "app")))
18 |
19 | (defn dispatch-screen-measurements []
20 | (rf/dispatch
21 | [:evt.ui/screen-resize
22 | {:ui.screen/inner-height js/window.innerHeight
23 | :ui.screen/inner-width js/window.innerWidth}]))
24 |
25 | (defn- listen-keyboard-shortcuts [evt]
26 | (when (and (.-ctrlKey evt) (= 69 (.-keyCode evt)))
27 | (rf/dispatch [:evt.keyboard/ctrl-e]))
28 | (when (and (.-ctrlKey evt) (= 13 (.-keyCode evt)))
29 | (rf/dispatch [:evt.keyboard/ctrl-enter])))
30 |
31 | (defn lookup-gist []
32 | (let [search js/location.search
33 | re-expls #"\?examples-gist="]
34 | (when (re-find re-expls search)
35 | (println :examples-gist-link-found (s/replace search re-expls ""))
36 | (rf/dispatch [:evt.ui/github-examples-request (s/replace search re-expls "")]))))
37 |
38 | ; (lookup-gist)
39 |
40 | (defn init []
41 | (js/window.addEventListener "keydown" listen-keyboard-shortcuts true)
42 | (js/window.addEventListener "resize" dispatch-screen-measurements)
43 | (rf/dispatch-sync [:evt.db/init d/default-db])
44 | (routes/init)
45 | (lookup-gist)
46 | (js/setTimeout dispatch-screen-measurements 50)
47 | (mount-root))
48 |
49 | ;; This is called every time you make a code change
50 | (defn reload! []
51 | (mount-root))
52 |
--------------------------------------------------------------------------------
/src/crux_ui/main_perf.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.main-perf
2 | (:require [reagent.core :as r]
3 | [crux-ui.subs]
4 | [re-frame.core :as rf]
5 | [crux-ui.views.facade-perf :as views]
6 | [crux-ui.events.facade :as events]
7 | [clojure.string :as s]
8 | [crux-ui.events.default-db :as d]
9 | [crux-ui.cookies :as c]))
10 |
11 |
12 | (set! js/window.tojs clj->js)
13 |
14 | (defn mount-root []
15 | (r/render [views/root] (js/document.getElementById "app")))
16 |
17 | (defn- listen-keyboard-shortcuts [evt]
18 | (when (and (.-ctrlKey evt) (= 13 (.-keyCode evt)))
19 | (rf/dispatch [:evt.keyboard/ctrl-enter])))
20 |
21 | (defn init []
22 | (js/window.addEventListener "keydown" listen-keyboard-shortcuts)
23 | (rf/dispatch-sync [:evt.db/init d/default-db])
24 | (mount-root))
25 |
26 | ;; This is called every time you make a code change
27 | (defn reload! []
28 | (mount-root))
29 |
--------------------------------------------------------------------------------
/src/crux_ui/routes.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.routes
2 | (:require [bidi.bidi :as bidi]
3 | [re-frame.core :as rf]
4 | [crux-ui.logging :as log]
5 | [crux-ui.views.commons.dom :as dom]
6 | [crux-ui.functions :as f]
7 | [medley.core :as m]))
8 |
9 | (def prefix
10 | (str (or (dom/get-routes-prefix) "/console")))
11 |
12 | (def app-routes-base (str prefix "/app"))
13 |
14 | (def ^:private routes
15 | [app-routes-base
16 | {"" :rd/query-ui
17 |
18 | "/output"
19 | {"" :rd/query-ui
20 | ["/" :r/output-tab] :rd/query-ui-output-tab}
21 |
22 | "/settings"
23 | {"" :rd/settings
24 | ["/" :r/tab] :rd/settings}
25 |
26 | ["/example/" :rd/example-id] :rd/query-ui}])
27 |
28 | (defn- prefix-keys [route]
29 | (f/map-keys #(keyword "r" (name %)) route))
30 |
31 | (defn- match-route [path]
32 | (if-let [route (prefix-keys (bidi/match-route routes path))]
33 | (cond-> route
34 | (get-in route [:r/route-params :r/output-tab])
35 | (update-in [:r/route-params :r/output-tab]
36 | #(keyword "db.ui.output-tab" %)))))
37 |
38 | (def ^:private path-for (partial bidi/path-for routes))
39 |
40 | (defn path-for-tab [tab-name {:r/keys [search]}]
41 | (str (path-for :rd/query-ui-output-tab
42 | :r/output-tab
43 | (name tab-name))
44 | search))
45 |
46 | (defn query-str->map [^js/String query]
47 | (let [raw-map (into {} (js->clj (js/Array.from (js/URLSearchParams. query))))]
48 | (f/map-keys #(if (string? %) (keyword "rd" %) %) raw-map)))
49 |
50 | (defn ^js/String query-map->str [m]
51 | (.toString (js/URLSearchParams. (clj->js m))))
52 |
53 | (defn- url-for [route-vector query-map]
54 | (let [path (apply path-for route-vector)
55 | query-str (query-map->str query-map)]
56 | (str path "?" query-str)))
57 |
58 |
59 | (defn- calc-route-data-from-location []
60 | (let [route-data (match-route js/location.pathname)
61 | query-map (query-str->map js/location.search)]
62 | (assoc route-data :r/query-params query-map)))
63 |
64 | (defn- on-pop-state [evt]
65 | (rf/dispatch [:evt.sys/set-route (calc-route-data-from-location)]))
66 |
67 | (defn- calc-query-url [route-data crux-query-str]
68 | (let [handler-id (route-data :r/handler)
69 | route-params (-> (:r/route-params route-data)
70 | (m/update-existing :r/output-tab name))
71 | route-params (vec (flatten (into [handler-id] route-params)))]
72 | (prn ::route-params route-params)
73 | (url-for route-params {:rd/query crux-query-str})))
74 |
75 | (comment
76 | (calc-query-url (calc-route-data-from-location) "{:find [e]}"))
77 |
78 | (defn- push-query [crux-query-str]
79 | (js/history.pushState nil "Crux Console : user query" (calc-query-url (calc-route-data-from-location) crux-query-str)))
80 |
81 | (defn- on-link-clicks [click-evt]
82 | (let [target (dom/jsget click-evt "target")
83 | tagname (dom/jsget target "tagName")
84 | href (dom/jsget target "href")
85 | is-anchor? (= "A" tagname)
86 | url (if is-anchor?
87 | (try
88 | (js/URL. href)
89 | (catch js/Error e
90 | (log/error "can't parse link url, will ignore it" href)
91 | nil)))
92 | pathname (and url (dom/jsget url "pathname"))
93 | route-data (and pathname (match-route pathname))]
94 | (when (and url is-anchor? route-data)
95 | (.preventDefault click-evt)
96 | (js/history.pushState nil "Crux Console" href)
97 | (rf/dispatch [:evt.sys/set-route route-data]))))
98 |
99 | (defn init []
100 | (js/window.addEventListener "popstate" on-pop-state false)
101 | (js/window.addEventListener "click" on-link-clicks true)
102 | (let [route-data (calc-route-data-from-location)]
103 | (rf/dispatch [:evt.sys/set-route route-data])))
104 |
--------------------------------------------------------------------------------
/src/crux_ui/svg_icons.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.svg-icons)
2 |
3 | (defn- icon [icon-name svg]
4 | [:div.svg-icon {:class (str "svg-icon--" (name icon-name))}
5 | svg])
6 |
7 | (def external
8 | (icon :external
9 | [:svg#Capa_1 {:xmlns "http://www.w3.org/2000/svg" :version "1.1" :x "0px" :y "0px" :viewBox "0 0 511.626 511.627"
10 | :style {:enable-background "new 0 0 511.626 511.627"} :xml:space "preserve"}
11 | [:path {:d "M392.857,292.354h-18.274c-2.669,0-4.859,0.855-6.563,2.573c-1.718,1.708-2.573,3.897-2.573,6.563v91.361 c0,12.563-4.47,23.315-13.415,32.262c-8.945,8.945-19.701,13.414-32.264,13.414H82.224c-12.562,0-23.317-4.469-32.264-13.414 c-8.945-8.946-13.417-19.698-13.417-32.262V155.31c0-12.562,4.471-23.313,13.417-32.259c8.947-8.947,19.702-13.418,32.264-13.418 h200.994c2.669,0,4.859-0.859,6.57-2.57c1.711-1.713,2.566-3.9,2.566-6.567V82.221c0-2.662-0.855-4.853-2.566-6.563 c-1.711-1.713-3.901-2.568-6.57-2.568H82.224c-22.648,0-42.016,8.042-58.102,24.125C8.042,113.297,0,132.665,0,155.313v237.542 c0,22.647,8.042,42.018,24.123,58.095c16.086,16.084,35.454,24.13,58.102,24.13h237.543c22.647,0,42.017-8.046,58.101-24.13 c16.085-16.077,24.127-35.447,24.127-58.095v-91.358c0-2.669-0.856-4.859-2.574-6.57 C397.709,293.209,395.519,292.354,392.857,292.354z"}]
12 | [:path {:d "M506.199,41.971c-3.617-3.617-7.905-5.424-12.85-5.424H347.171c-4.948,0-9.233,1.807-12.847,5.424 c-3.617,3.615-5.428,7.898-5.428,12.847s1.811,9.233,5.428,12.85l50.247,50.248L198.424,304.067 c-1.906,1.903-2.856,4.093-2.856,6.563c0,2.479,0.953,4.668,2.856,6.571l32.548,32.544c1.903,1.903,4.093,2.852,6.567,2.852 s4.665-0.948,6.567-2.852l186.148-186.148l50.251,50.248c3.614,3.617,7.898,5.426,12.847,5.426s9.233-1.809,12.851-5.426 c3.617-3.616,5.424-7.898,5.424-12.847V54.818C511.626,49.866,509.813,45.586,506.199,41.971z"}]]))
13 |
14 | (def close
15 | (icon :close
16 | [:svg.svg-icon__content {:viewBox "0 0 1000 1000" :xmlns "http://www.w3.org/2000/svg"}
17 | [:path {:d "M1008 898.915l-45.085 45.085-450.915-450.915-450.915 450.915-45.085-45.085 450.915-450.915-450.915-450.915 45.085-45.085 450.915 450.915 450.915-450.915 45.085 45.085-450.915 450.915z"}]]))
18 |
19 |
20 | (def styles
21 | [[:.svg-icon
22 | {:width :12px
23 | :height :12px
24 | :color :grey}
25 | [:&__content
26 | {:width :100%
27 | :height :100%}]]])
28 |
--------------------------------------------------------------------------------
/src/crux_ui/ua-regexp.js:
--------------------------------------------------------------------------------
1 | module.exports = /(MSIE|Trident|(?!Gecko.+)Firefox|(?!AppleWebKit.+Chrome.+)Safari(?!.+Edge)|(?!AppleWebKit.+)Chrome(?!.+Edge)|(?!AppleWebKit.+Chrome.+Safari.+)Edge|AppleWebKit(?!.+Chrome|.+Safari)|Gecko(?!.+Firefox))(?: |\/)([\d\.apre]+)/
--------------------------------------------------------------------------------
/src/crux_ui/views/attr_stats.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.attr-stats
2 | (:require [garden.core :as garden]
3 | [re-frame.core :as rf]
4 | [crux-ui.views.output.table :as q-results-table]
5 | [crux-ui.views.style :as s]))
6 |
7 | (def ^:private -sub-attr-stats
8 | (rf/subscribe [:subs.query/attr-stats-table]))
9 |
10 | (def ^:private style
11 | [:style
12 | (garden/css
13 | [:.attr-stats
14 | {:height :100%}
15 | [:&__footer
16 | {:text-align :center
17 | :padding :16px}]])])
18 |
19 | (defn root []
20 | [:div.attr-stats
21 | style
22 | [q-results-table/root @-sub-attr-stats]
23 | [:footer.attr-stats__footer
24 | [:strong "Indexed attributes frequencies"]]])
25 |
--------------------------------------------------------------------------------
/src/crux_ui/views/charts/custom-plotly--console.js:
--------------------------------------------------------------------------------
1 | // in custom-plotly--perf.js
2 | var Plotly = require('plotly.js/lib/core');
3 |
4 | // Load in the trace types for pie, and choropleth
5 | Plotly.register([
6 | require('plotly.js/lib/scatter')
7 | ]);
8 |
9 | module.exports = Plotly;
--------------------------------------------------------------------------------
/src/crux_ui/views/charts/custom-plotly--perf.js:
--------------------------------------------------------------------------------
1 | // in custom-plotly--perf.js
2 | require('graceful-fs/graceful-fs');
3 | require('gl-surface3d/lib/shaders');
4 | require('gl-surface3d/surface');
5 | require('gl-surface3d');
6 | var Plotly = require('plotly.js/lib/core');
7 |
8 | // Load in the trace types for pie, and choropleth
9 | Plotly.register([
10 | require('plotly.js/lib/surface')
11 | ]);
12 |
13 | module.exports = Plotly;
--------------------------------------------------------------------------------
/src/crux_ui/views/charts/query_perf_plots.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.charts.query-perf-plots
2 | (:require ["plotly.js-gl3d-dist" :as Plotly]
3 | [reagent.core :as r]
4 | [garden.core :as css]
5 | [garden.core :as garden]
6 | [cljs.tools.reader.edn :as edn]
7 | [crux-ui.logging :as log]))
8 |
9 |
10 | (def ^:private plot-styling
11 | [:style
12 | (garden/css
13 | [:.plotly-container
14 | {:height :100%}])])
15 |
16 | (def colors
17 | #{"YIGnBu" "Portland" "Picnic"})
18 |
19 | (defn z-data [{:keys [plain with-cache] :as query-data}]
20 | (clj->js
21 | [{:z (:data plain)
22 | :name "Cache off"
23 | :colorscale "Picnic"
24 | :type "surface"}
25 | {:z (:data with-cache)
26 | :name "Cache on"
27 | :colorscale "Viridis"
28 | :type "surface"}]))
29 |
30 |
31 | (defn axis [{:keys [title ticks] :as axis}]
32 | {:title (name (:title axis))
33 | :nticks (count ticks)})
34 |
35 | (defn opts [{:keys [title with-cache plain] :as query-data}]
36 | (clj->js
37 | {:title title
38 | :autosize true
39 | :showlegend true
40 | :height 900
41 | :width 1200
42 | :scene
43 | {:xaxis (axis (:x with-cache)) ; x is history days
44 | :yaxis (axis (:y with-cache)) ; y is stocks count
45 | :zaxis {:title "ms"}}
46 | :margin
47 | {:l 65,
48 | :r 50,
49 | :b 65,
50 | :t 90}}))
51 |
52 | (defn do-plot [container query-data]
53 | (.newPlot Plotly container (z-data query-data) (opts query-data)))
54 |
55 | (defn root
56 | [data]
57 | (let [-inst (atom nil)]
58 | (r/create-class
59 | {:component-did-mount
60 | (fn [this]
61 | (let [el (r/dom-node this)
62 | inst (do-plot el data)]
63 | (reset! -inst inst)))
64 |
65 | :reagent-render
66 | (fn [_ _ _]
67 | [:div.plotly-container
68 | [:style plot-styling]])})))
69 |
--------------------------------------------------------------------------------
/src/crux_ui/views/charts/wrapper_3d.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.charts.wrapper-3d
2 | "For responsiveness ensure that parent element is responsive and takes exactly 100%
3 | of the desired viewport."
4 | (:require [reagent.core :as r]
5 | ["plotly.js-gl3d-dist" :as Plotly]
6 | [garden.core :as garden]))
7 |
8 |
9 | (def ^:private plot-styling
10 | [:style
11 | (garden/css
12 | [:.plotly-container
13 | {:height :100%}])])
14 |
15 | (defn plotly-wrapper
16 | [data layout]
17 | (let [-inst (atom nil)]
18 | (r/create-class
19 | {:component-did-mount
20 | (fn [this]
21 | (reset! -inst (.newPlot Plotly
22 | (r/dom-node this)
23 | (clj->js data)
24 | (clj->js layout)
25 | #js {:responsive true})))
26 |
27 | :reagent-render
28 | (fn [_ _ _]
29 | [:div.plotly-container
30 | [:style plot-styling]])})))
--------------------------------------------------------------------------------
/src/crux_ui/views/charts/wrapper_basic.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.charts.wrapper-basic
2 | "For responsiveness ensure that parent element is responsive and takes exactly 100%
3 | of the desired viewport."
4 | (:require [reagent.core :as r]
5 | ; ["plotly.js-basic-dist" :as Plotly]
6 | ["./custom-plotly--console" :as Plotly]
7 | [garden.core :as garden]))
8 |
9 |
10 | (def ^:private plot-styling
11 | [:style
12 | (garden/css
13 | [:.plotly-container
14 | {:height :100%}])])
15 |
16 | (defn plotly-wrapper
17 | [data layout]
18 | (let [-inst (atom nil)]
19 | (r/create-class
20 | {:component-did-mount
21 | (fn [this]
22 | (reset! -inst (.newPlot Plotly
23 | (r/dom-node this)
24 | (clj->js data)
25 | (clj->js layout)
26 | #js {:responsive true})))
27 |
28 | :reagent-render
29 | (fn [_ _ _]
30 | [:div.plotly-container
31 | [:style plot-styling]])})))
--------------------------------------------------------------------------------
/src/crux_ui/views/cluster_health.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.cluster-health
2 | (:require [cljs.pprint]))
3 |
4 |
5 |
6 | (def cluster-mock
7 | (let [protonode {:crux.version/version "19.04-1.0.1-alpha",
8 | :crux.kv/size 792875,
9 | :crux.index/index-version 4,
10 | :crux.tx-log/consumer-state
11 | {:crux.tx/event-log
12 | {:lag 0, :next-offset 1592957991111682,
13 | :time #inst "2019-04-18T21:30:38.195-00:00"}}}
14 | protonode2 {:crux.version/version "19.04-1.0.0-alpha",
15 | :crux.kv/size 792875,
16 | :crux.index/index-version 4,
17 | :crux.tx-log/consumer-state
18 | {:crux.tx/event-log
19 | {:lag 0, :next-offset 1592957988161759,
20 | :time #inst "2019-04-18T21:15:12.561-00:00"}}}]
21 | [["http://node-1.crux.cloud:8080" [:span "Status: OK " [:span {:style {:font-weight "bold" :color "green"}} "✓"]] protonode]
22 | ["http://node-2.crux.cloud:8080" [:span "Status: OK " [:span {:style {:font-weight "bold" :color "green"}} "✓"]] protonode]
23 | ["http://node-3.crux.cloud:8080" [:span "Error: " [:span {:style {:font-weight "normal" :color "red"}} "Connection Timeout ✘"]] protonode2]]))
24 |
25 | (defn root []
26 | [:div.cluster-health
27 | [:h2 "Cluster Health"]
28 | [:div.cg {:style {:display "grid"
29 | :margin-right "auto"
30 | :margin-left "auto"
31 | :grid-template-columns "2em auto auto auto"
32 | :grid-row-gap "1em"}}
33 |
34 | (mapcat identity
35 | (for [n cluster-mock]
36 | [
37 | [:div {:key (str n 0)} [:a {:style {:text-decoration "none" :font-weight "bold" :font-size "1.3em"}} "↻"]]
38 | [:div {:key (str n 1)}[:a (nth n 0)]]
39 | [:div {:key (str n 2)}(nth n 1)]
40 | [:div {:key (str n 3)}[:pre (with-out-str (cljs.pprint/pprint (nth n 2)))]]]))
41 |
42 |
43 |
44 | [:div {:style {:height "1em"}}]
45 | [:a "Refresh All"]]])
46 |
--------------------------------------------------------------------------------
/src/crux_ui/views/codemirror.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.codemirror
2 | (:require [reagent.core :as r]
3 | [garden.core :as garden]
4 | [goog.object :as gobj]
5 | ["/codemirror/lib/codemirror.js" :as codemirror]
6 | ["/codemirror/mode/clojure/clojure.js"]
7 | ["/codemirror/addon/edit/closebrackets.js"]
8 | ["/codemirror/addon/edit/matchbrackets.js"]
9 | ["/codemirror/addon/hint/show-hint"]
10 | ["/codemirror/addon/hint/anyword-hint"]))
11 |
12 |
13 | (def code-mirror-styling
14 | (garden/css
15 | [[:.code-mirror-container
16 | {:font-size :17px
17 | :padding "0px 0px"
18 | :overflow :hidden
19 | :height :100%}]
20 | [:.CodeMirror
21 | {:border-radius :2px
22 | :height :100%}]]))
23 |
24 | (defn escape-re [input]
25 | (let [re (js/RegExp. "([.*+?^=!:${}()|[\\]\\/\\\\])" "g")]
26 | (-> input str (.replace re "\\$1"))))
27 |
28 | (defn fuzzy-re [input]
29 | (-> (reduce (fn [s c] (str s (escape-re c) ".*")) "" input)
30 | (js/RegExp "i")))
31 |
32 | (def ^{:private true :const true} crux-builtin-keywords
33 | [:find :where :args :rules :offset :limit :order-by
34 | :timeout :full-results? :not :not-join :or :or-join
35 | :range :unify :rule :pred :ui/poll-interval-seconds?])
36 |
37 |
38 | (defn- autocomplete [index cm options]
39 | (let [cur (.getCursor cm)
40 | line (.-line cur)
41 | ch (.-ch cur)
42 | token (.getTokenAt cm cur)
43 | reg (subs (.-string token) 0 (- ch (.-start token)))
44 | blank? (#{"[" "{" " " "("} reg)
45 | start (if blank? cur (.Pos codemirror line (gobj/get token "start")))
46 | end (if blank? cur (.Pos codemirror line (gobj/get token "end")))
47 | words (concat crux-builtin-keywords index)
48 | fuzzy (if blank? #".*" (fuzzy-re reg))
49 | words (->> words
50 | (map str)
51 | (filter #(re-find fuzzy %)))]
52 | (clj->js {:list words
53 | :from start
54 | :to end})))
55 |
56 |
57 | (defn code-mirror
58 | [initial-value {:keys [read-only? stats on-change on-cm-init]}]
59 |
60 | (let [value-atom (atom (or initial-value ""))
61 | on-change (or on-change (constantly nil))
62 | cm-inst (atom nil)
63 | indexes (when (map? stats) (keys stats))]
64 | (r/create-class
65 |
66 | {:component-did-mount
67 | (fn [this]
68 | (let [el (r/dom-node this)
69 | opts #js {:lineNumbers false
70 | :undoDepth 100000000
71 | :historyEventDelay 1
72 | :viewportMargin js/Infinity
73 | :autofocus true
74 | :readOnly read-only?
75 | :value @value-atom
76 | :theme "eclipse" ; or "monokai"
77 | :autoCloseBrackets true
78 | :hintOptions #js {:hint (partial autocomplete indexes)
79 | :completeSingle false}
80 | :extraKeys {"Ctrl-Space" "autocomplete"} ;need to leave this in for `:` to work, there's probably a better way!
81 | :matchBrackets true
82 | :mode "clojure"}
83 | inst (codemirror. el opts)]
84 | (.on inst "keyup"
85 | (fn [cm e] (when (and (not (gobj/getValueByKeys cm #js ["state" "completionActive"]))
86 | (= 1 (-> (gobj/get e "key") (count)))
87 | (= (gobj/get e "key") ":"))
88 | (.showHint inst))))
89 | (reset! cm-inst inst)
90 | (.on inst "change"
91 | (fn []
92 | (let [value (.getValue inst)]
93 | (when-not (= value @value-atom)
94 | (on-change value)
95 | (reset! value-atom value)))))
96 | (when on-cm-init
97 | (on-cm-init inst))))
98 |
99 | :component-did-update
100 | (fn [this old-argv]
101 | (when-not (= @value-atom (.getValue @cm-inst))
102 | (.setValue @cm-inst @value-atom)
103 | ;; reset the cursor to the end of the text, if the text was changed externally
104 | (let [last-line (.lastLine @cm-inst)
105 | last-ch (count (.getLine @cm-inst last-line))]
106 | (.setCursor @cm-inst last-line last-ch))))
107 |
108 | :reagent-render
109 | (fn [_ _ _]
110 | [:div.code-mirror-container
111 | [:style code-mirror-styling]])})))
112 |
--------------------------------------------------------------------------------
/src/crux_ui/views/commons/contenteditable.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.commons.contenteditable
2 | (:require [reagent.core :as r]
3 | [crux-ui.views.commons.user-intents :as user-intents]))
4 |
5 |
6 | (defn- -data-attrs-mapper [[k v]]
7 | (vector (str "data-" (name k)) v))
8 |
9 | (defn- render-data-attrs [hmap]
10 | (into {} (map -data-attrs-mapper hmap)))
11 |
12 |
13 | (defn div
14 | [id
15 | {:keys
16 | [on-change
17 | on-intent
18 | on-key-down
19 | on-blur
20 | on-change-complete
21 | text-mode?
22 | process-paste]
23 | :as opts}]
24 | ;;
25 | (let [node (r/atom nil)
26 | state-value (atom nil)
27 |
28 | get-cur-value
29 | (fn []
30 | (when-let [n @node]
31 | (if text-mode?
32 | (.-textContent n)
33 | (.-innerHTML n))))
34 |
35 | on-key-down (cond
36 | on-intent #(some-> % user-intents/key-down-evt->intent-evt on-intent)
37 | on-key-down on-key-down
38 | :else identity)
39 |
40 | on-key-up-internal
41 | (if on-change
42 | (fn [evt]
43 | (let [cur-val (get-cur-value)]
44 | (when (not= cur-val @state-value)
45 | (on-change {:value cur-val
46 | :target @node})))))
47 |
48 | on-blur-internal
49 | (fn [evt]
50 | (if on-blur (on-blur))
51 | (if on-change-complete
52 | (on-change-complete
53 | {:value (get-cur-value)
54 | :target @node})))
55 |
56 | on-paste-internal
57 | (if on-change
58 | (fn [evt]
59 | (let [cur-val (get-cur-value)]
60 | (when (not= cur-val @state-value)
61 | (let [paste-processed (if process-paste
62 | (process-paste cur-val)
63 | cur-val)]
64 | (on-change {:value paste-processed
65 | :target @node}))))))]
66 | (r/create-class
67 | {:display-name "ContentEditable"
68 |
69 | :component-did-mount
70 | (fn [this]
71 | (reset! node (r/dom-node this)))
72 |
73 | :should-component-update
74 | (fn [this cur-argv [f id next-props :as next-argv]]
75 | (let [active-element js/document.activeElement
76 | not-active? (not= @node active-element)]
77 | (and not-active? (not= (get-cur-value) (:value next-props)))))
78 |
79 | :reagent-render
80 | (fn [id {:keys [data ; @param {map} with data attributes
81 | on-blur on-focus
82 | placeholder css-class value] :as opts}] ;; remember to repeat parameters
83 | (reset! state-value value)
84 | (let [id-str (if (keyword? id) (name id) (str id))
85 | data-attrs (render-data-attrs data)]
86 | [:div.crux-ui-input.contenteditable
87 | (cond->
88 | {:id id-str,
89 | :suppressContentEditableWarning true
90 | :dangerouslySetInnerHTML {:__html value}
91 | :placeholder placeholder
92 | :class (if css-class (name css-class))
93 | :autoFocus (:autofocus opts)
94 | :content-editable true
95 | :spellCheck "false"
96 | :on-key-up on-key-up-internal
97 | :on-paste on-paste-internal
98 | :on-key-down on-key-down
99 | :on-focus on-focus
100 | :on-blur on-blur-internal}
101 | data-attrs (merge data-attrs))]))})))
102 |
--------------------------------------------------------------------------------
/src/crux_ui/views/commons/css_cube.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.commons.css-cube
2 | (:require [crux-ui.views.functions :as vf]
3 | [garden.core :as garden]))
4 |
5 | (def ^:private cube-styles
6 | [:style
7 | (garden/css
8 | [:.cube
9 | {:display :block
10 | ;:outline "1px solid blue"
11 | :height :50px
12 | :position :relative
13 | :width :50px
14 | :transform-origin "50% 50% -25px"
15 | :transform-style "preserve-3d"
16 | :transform "rotateX(-45deg) rotateY(55deg)"}
17 | (let [rib-length "100%"
18 | rib-thickness "1px"
19 | transform-back "translateZ(-50px)"
20 | transform-left "rotateY(90deg)"
21 | transform-right "rotateY(-90deg)"]
22 | [:&__rib
23 | {:width rib-length
24 | :height rib-thickness
25 | :background :black
26 | :position :absolute}
27 | [:&--center
28 | {:height rib-length
29 | :width rib-thickness
30 | :bottom 0}]
31 | [:&--bottom
32 | {:bottom 0}]
33 | [:&--left
34 | {:right 0
35 | :transform-origin "0%"
36 | :transform transform-left}]
37 | [:&--right
38 | {:right 0
39 | :transform-origin "100%"
40 | :transform transform-right}]
41 | [:&--top&--right
42 | {:background :orange}]
43 | [:&--center&--right
44 | :&--center&--left
45 | {:transform :none}]
46 | [:&--back
47 | :&--back&--center
48 | {:transform transform-back}]
49 | [:&--center&--back
50 | {}]])])])
51 |
52 | (defn rib [& modifiers]
53 | [:div (apply vf/bem (cons :cube__rib modifiers))])
54 |
55 | (defn crux-cube []
56 | [:div.cube
57 | cube-styles
58 | [rib :top :front]
59 | [rib :top :left]
60 | [rib :top :back]
61 | [rib :top :right]
62 | ;
63 | [rib :center :left]
64 | [rib :center :right]
65 | [rib :center :back :left]
66 | [rib :center :back :right]
67 | ;
68 | [rib :bottom :front]
69 | [rib :bottom :left]
70 | [rib :bottom :back]
71 | [rib :bottom :right]])
72 |
--------------------------------------------------------------------------------
/src/crux_ui/views/commons/css_logo.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.commons.css-logo
2 | (:require [garden.core :as garden]
3 | [garden.stylesheet :as gs]
4 | [clojure.string :as s]
5 | [crux-ui.views.commons.cube-svg :as cube]
6 | [crux-ui.views.functions :as vf]))
7 |
8 |
9 | (def ^:private css-logo-styles
10 | [:style
11 | (garden/css
12 | [:.css-logo
13 | {:display :flex
14 | :justify-content :space-between
15 | :align-items :center
16 | :width "100%"}
17 | [:&__cube
18 | {:width :60px
19 | :height :60px
20 | :flex "0 0 60px"
21 | :margin-right :8px}
22 | [:>.svg-cube
23 | {:height :100%
24 | :width :83.3%}]]
25 | [:&__text
26 | {:display "flex"
27 | :justify-content "space-between"
28 | :letter-spacing "0.15em"
29 | :font-weight "400"
30 | :flex "0 0 250px"
31 | :align-items "center"}]]
32 | (gs/at-media {:max-width :500px}
33 | [:.css-logo__text
34 | {:display :none}])
35 | (gs/at-media {:max-width :375px}
36 | [:.css-logo__cube
37 | {:width :40px
38 | :height :40px
39 | :flex "0 0 40px"}]))])
40 |
41 |
42 | (defn root []
43 | [:div.css-logo
44 | css-logo-styles
45 | [:div.css-logo__cube (cube/cube {:animating? false})]
46 | [:div.css-logo__text "[?console]"]])
47 |
--------------------------------------------------------------------------------
/src/crux_ui/views/commons/cube_svg.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.commons.cube-svg
2 | (:require [garden.stylesheet :as gs]
3 | [garden.core :as garden]
4 | [clojure.string :as s]
5 | [crux-ui.views.functions :as vu]))
6 |
7 |
8 | (def color-gray "rgb(201, 201, 201)")
9 | (def color-black "rgb(1, 1, 1)")
10 |
11 |
12 | (def kf-pulse-gray-orange
13 | (let [frame1
14 | {:stroke color-gray
15 | :fill color-gray}]
16 | (gs/at-keyframes :pulse-gray-orange
17 | [:0% frame1]
18 | [:33% {:stroke :orange
19 | :fill :orange}]
20 | [:66% frame1])))
21 |
22 | (def kf-pulse-black-orange
23 | (let [frame1
24 | {:stroke color-black
25 | :fill color-black}]
26 | (gs/at-keyframes :pulse-black-orange
27 | [:0% frame1]
28 | [:33% {:stroke :orange
29 | :fill :orange}]
30 | [:66% frame1]
31 | [:100% frame1])))
32 |
33 | (def cube-animation
34 | (let [anim-for-gray "pulse-gray-orange"
35 | anim-for-black "pulse-black-orange"
36 | duration-ms 1000
37 |
38 | ms #(str % "ms")
39 |
40 | ae
41 | (fn [anim-name animation-order]
42 | (let [delay-ms (* (/ duration-ms 3) animation-order)]
43 | (s/join " "
44 | [anim-name
45 | (ms duration-ms)
46 | (ms delay-ms)
47 | "infinite"])))
48 |
49 | animated
50 | (list
51 | [:.rib--grey.rib--bottom-left
52 | {:animation (ae anim-for-gray 0)}]
53 | [:.rib--bottom-front
54 | {:animation (ae anim-for-black 0)}]
55 | [:.rib--bottom-right
56 | {:animation (ae anim-for-black 0)}]
57 | [:.rib--grey.rib--bottom-back
58 | {:animation (ae anim-for-gray 0)}]
59 |
60 |
61 | [:.rib--grey.rib--center-back-left
62 | {:animation (ae anim-for-gray 1)}]
63 | [:.rib--black.rib--center-right
64 | {:animation (ae anim-for-black 1)}]
65 |
66 |
67 | [:.rib--top-front
68 | :.rib--top-back
69 | :.rib--top-left
70 | :.rib--orange.rib--top-right
71 | {:animation (ae anim-for-black 2)}])]
72 | animated))
73 |
74 | (def style
75 | [:style
76 | (let [stroke-width--desktop1 "15px"
77 | stroke-width--desktop2 "6px"
78 | ;
79 | stroke-width--mobile1 "21px"
80 | stroke-width--mobile2 "15px"
81 | ;
82 | stroke-color--grey "rgb(201, 201, 201)"
83 | stroke-color--orange "rgb(248, 150, 29)"
84 | stroke-color--black "#000"]
85 |
86 | (garden/css
87 | kf-pulse-black-orange
88 | kf-pulse-gray-orange
89 |
90 | [:.svg-cube
91 | {:width :83.3%
92 | :height :100%}
93 | [:&--animating
94 | cube-animation]]
95 | [:.rib
96 | {}]
97 | [:.rib--grey
98 | {:stroke stroke-color--grey
99 | :stroke-width stroke-width--desktop1
100 | :stroke-miterlimit "10px"}]
101 | [:.rib--black
102 | {:stroke stroke-color--black
103 | :stroke-width stroke-width--desktop2}]
104 | [:.rib6
105 | {:stroke-width :12px}]
106 | [:.rib--orange
107 | {:stroke stroke-color--orange
108 | :stroke-width stroke-width--desktop2
109 | :fill stroke-color--orange}]
110 | ;
111 | (gs/at-media {:max-width :768px}
112 | [:.rib
113 | {}]
114 | [:.rib--grey
115 | {:stroke stroke-color--grey
116 | :stroke-width stroke-width--mobile1
117 | :stroke-miterlimit "10px"}]
118 | [:.rib--black
119 | {:stroke-width stroke-width--mobile2
120 | :stroke stroke-color--black}]
121 | [:.rib6
122 | {:stroke-width :22px}]
123 | [:.rib--orange
124 | {:stroke-width stroke-width--mobile2
125 | :stroke stroke-color--orange
126 | :fill stroke-color--orange}])))])
127 |
128 |
129 |
130 | (defn cube [{:keys [animating?]}]
131 | [:svg
132 | {:class (vu/bem-str :svg-cube {:animating animating?})
133 | :version "1.1" :x "0px" :y "0px" :viewBox "0 0 500 598.837"
134 | :xmlns "http://www.w3.org/2000/svg"}
135 | style
136 | [:line.rib.rib--grey.rib--bottom-left
137 | {:x1 "250.911" :y1 "249.156" :x2 "19.097" :y2 "421.962"}]
138 | [:line#line14.rib.rib--grey.rib--center-back-left
139 | {:x1 "249.528" :y1 "252.244" :x2 "250.555" :y2 "4.264"}]
140 | [:line.rib.rib--grey.rib--bottom-back
141 | {:x1 "250.649" :y1 "250.835" :x2 "483.864" :y2 "419.57"}]
142 | [:line.rib.rib--black.rib--center-right.rib6
143 | {:x1 "483.578" :y1 "419.286" :x2 "483.578" :y2 "174.755"}]
144 | [:polygon.rib.rib--black.rib--top-front
145 | {:points "16.193 181.85 247.778 351.14 249.908 352.694 249.956 352.646 249.956 343.685 249.908 343.685 16.193 172.844"}]
146 | [:polygon.rib.rib--black.rib--bottom-front
147 | {:points "16.199 427.682 246.538 591.662 248.655 593.164 248.704 593.118 248.704 584.438 248.655 584.438 16.199 418.959"}]
148 | [:polygon.rib.rib--black.rib--bottom-right
149 | {:points "248.299 583.672 484.859 415.128 487.034 413.583 487.084 413.631 487.084 422.553 487.034 422.553 248.299 592.638"
150 | :transform "matrix(-1, 0, 0, -1, 735.383011, 1006.221008)"}]
151 | [:polygon.rib.rib--black.rib--top-back
152 | {:points "251.976 11.002 485.873 178.546 488.041 180.087 488.091 180.039 488.091 171.148 488.041 171.148 252.95 3.762"
153 | :transform "matrix(-1, 0, 0, -1, 738.143997, 181.72001)"}]
154 | [:polygon.rib.rib--black.rib--top-left
155 | {:points "16.268 171.283 247.892 2.993 250.021 1.45 250.07 1.498 250.07 10.405 250.021 10.405 16.268 180.236"}]
156 | [:polygon.rib.rib--orange.rib--top-right
157 | {:points "249.956 343.562 250.005 343.562 250.005 352.646 486.649 180.05 486.535 170.226"}]])
158 |
--------------------------------------------------------------------------------
/src/crux_ui/views/commons/datepicker_native.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.commons.datepicker-native
2 | (:require [garden.core :as garden]
3 | [garden.stylesheet :as gs]
4 | [crux-ui.logging :as log]
5 | [crux-ui.functions :as f]
6 | [crux-ui.logic.time :as time]
7 | [crux-ui.config :as cfg]
8 | [crux-ui.views.commons.input :as input]
9 | [crux-ui.views.commons.keycodes :as kc]
10 | [reagent.core :as r]
11 | [crux-ui.views.commons.dom :as dom]
12 | [crux-ui.views.functions :as vu]))
13 |
14 |
15 | (defn- on-time-change--native [on-change-external evt]
16 | (try
17 | (let [v (f/jsget evt "target" "value")
18 | ts (js/Date.parse v)]
19 | (if (js/isNaN ts)
20 | (on-change-external nil)
21 | (on-change-external (js/Date. ts))))
22 | (catch js/Error err
23 | (on-change-external nil)
24 | (log/error err))))
25 |
26 |
27 | (def style
28 | [:style
29 | (garden/css
30 | [:.native-date-time-picker
31 | {:display :flex
32 | :font-size :14px
33 | :align-items :center}
34 | [:&__label
35 | {:width :136px
36 | :display :block
37 | :letter-spacing :.04em}]
38 | [:&__input
39 | input/styles-src
40 | {:padding "4px 0"
41 | :width :auto}]]
42 | (gs/at-media {:min-width :1000px}
43 | [:.native-date-time-picker--column
44 | {:flex-direction :column
45 | :align-items :flex-start}
46 | ["> .native-date-time-picker__label"
47 | {:font-size :16px
48 | :width :auto
49 | :letter-spacing :0.09em
50 | :color :gray}]
51 | ["> .native-date-time-picker__input"
52 | {:line-height :32px}]]))])
53 |
54 |
55 |
56 | (defn picker
57 | [{:keys
58 | [label
59 | ui/layout
60 | ^js/Date value
61 | on-change]
62 | :as prms}]
63 | (let [state (r/atom {:value value})
64 | on-commit-internal (r/partial on-time-change--native on-change)]
65 | (fn []
66 | [:div (vu/bem :native-date-time-picker layout)
67 | (if label
68 | [:label.native-date-time-picker__label label])
69 | [:input.native-date-time-picker__input
70 | (let [v (:value @state)]
71 | {:type "datetime-local"
72 | :placeholder (if-not cfg/supports-input-datetime? "dd/mm/yyyy, --:--")
73 | :defaultValue (if v (time/format-for-dt-local v))
74 | :on-key-down
75 | (dom/dispatch-on-keycode
76 | {::kc/enter on-commit-internal})
77 | :on-blur on-commit-internal})]])))
78 |
--------------------------------------------------------------------------------
/src/crux_ui/views/commons/form_line.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.commons.form-line
2 | (:require [clojure.string :as s]))
3 |
4 | (defn line [{:keys [on-reset css-class label control hint]}]
5 | [:div.line {:class css-class :title label}
6 | (if label
7 | [:div.line__label label])
8 | [:div.line__control control
9 | (if on-reset
10 | [:button.line__reset
11 | {:title (if-not label
12 | "reset"
13 | (s/join " " (cons "reset" (filter string? label))))
14 | :on-click on-reset} "x"])]
15 | (if hint
16 | [:div.line__hint hint])])
17 |
--------------------------------------------------------------------------------
/src/crux_ui/views/commons/input.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.commons.input
2 | (:require [reagent.core :as r]
3 | [crux-ui.views.commons.user-intents :as user-intents]
4 | [garden.core :as garden]
5 | [crux-ui.views.style :as s]))
6 |
7 |
8 | (defn- -data-attrs-mapper [[k v]]
9 | (vector (str "data-" (name k)) v))
10 |
11 | (defn render-data-attrs [hmap]
12 | (into {} (map -data-attrs-mapper hmap)))
13 |
14 |
15 | (def styles-src
16 | (list
17 | {:border :none}
18 | {:border-bottom s/q-ui-border}
19 | {:padding "6px 0px"
20 | :border-radius :2px
21 | :width :100%
22 | :letter-spacing :.09em
23 | :outline :none
24 | :font-size :inherit
25 | :font-family :inherit
26 | :display :inline-block}
27 | ["&:empty"
28 | {:min-width :220px}]
29 | ["&:empty::before"
30 | {:content "attr(placeholder)"}]))
31 |
32 | (def styles
33 | [:.crux-ui-input styles-src])
34 |
35 |
36 | (defn text
37 | [id {:keys [on-change on-change-complete
38 | on-intent on-key-down
39 | parse-fn
40 | value
41 | process-paste]
42 | :as opts}]
43 | (let [node (r/atom nil)
44 | cur-val (atom value)
45 | parse-fn (or parse-fn identity)
46 | get-cur-value #(some-> @node (.-value) parse-fn)
47 | on-key-down (cond
48 | on-intent #(some-> % user-intents/key-down-evt->intent-evt on-intent)
49 | on-key-down on-key-down
50 | :else identity)
51 |
52 | on-blur-internal
53 | (fn [evt]
54 | (if on-change-complete
55 | (on-change-complete {:value (get-cur-value)})))
56 |
57 | on-key-up-internal
58 | (if on-change
59 | (fn [evt]
60 | (let [cur-value (get-cur-value)]
61 | (when (not= cur-value @cur-val)
62 | (on-change {:value cur-value
63 | :target @node})))))
64 |
65 | on-paste-internal
66 | (if on-change
67 | (fn [evt]
68 | (let [cur-html (get-cur-value)]
69 | (when (not= cur-html @cur-val)
70 | (let [paste-processed (if process-paste
71 | (process-paste cur-html)
72 | cur-html)]
73 | (on-change {:value (parse-fn paste-processed)
74 | :target @node}))))))]
75 | (r/create-class
76 | {:display-name "SpaceInput"
77 |
78 | :component-did-mount
79 | (fn [this]
80 | (reset! node (r/dom-node this)))
81 |
82 | :component-did-update
83 | (fn [this old-argv]
84 | (let [nv (:value (r/props this))]
85 | (reset! cur-val nv)))
86 |
87 | :reagent-render
88 | (fn [id {:keys [data ; @param {map} with data attributes
89 | on-blur on-focus
90 | placeholder css-class value] :as opts}] ;; remember to repeat parameters
91 | (let [id-str (if (keyword? id) (name id) (str id))
92 | attrs
93 | (cond-> {:id id-str
94 | :placeholder placeholder
95 | :class (if css-class (name css-class))
96 | :defaultValue value
97 | :autoFocus (:autofocus opts)
98 | :spellCheck "false"
99 | :on-key-up on-key-up-internal
100 | :on-paste on-paste-internal
101 | :on-key-down on-key-down
102 | :on-focus on-focus
103 | :on-blur on-blur-internal}
104 | (seq data) (merge (render-data-attrs data)))]
105 |
106 | ^{:key value}
107 | [:input.crux-ui-input attrs]))})))
108 |
109 |
--------------------------------------------------------------------------------
/src/crux_ui/views/commons/keycodes.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.commons.keycodes)
2 |
3 | (def kc->kw
4 | {3 ::break
5 | 8 ::backspace-or-delete
6 | 9 ::tab
7 | 12 ::clear
8 | 13 ::enter
9 | 16 ::shift
10 | 17 ::ctrl
11 | 18 ::alt
12 | 19 ::pause-break
13 | 20 ::caps-lock
14 | 21 ::hangul
15 | 25 ::hanja
16 | 27 ::escape
17 | 28 ::conversion
18 | 29 ::non-conversion
19 | 32 ::spacebar
20 | 33 ::page-up
21 | 34 ::page-down
22 | 35 ::end
23 | 36 ::home
24 | 37 ::left-arrow
25 | 38 ::up-arrow
26 | 39 ::right-arrow
27 | 40 ::down-arrow
28 | 41 ::select
29 | 42 ::print
30 | 45 ::insert
31 | 46 ::delete
32 | 47 ::help
33 | 48 ::0
34 | 49 ::1
35 | 50 ::2
36 | 51 ::3
37 | 52 ::4
38 | 53 ::5
39 | 54 ::6
40 | 55 ::7
41 | 56 ::8
42 | 57 ::9
43 | 65 ::a
44 | 66 ::b
45 | 67 ::c
46 | 68 ::d
47 | 69 ::e
48 | 70 ::f
49 | 71 ::g
50 | 72 ::h
51 | 73 ::i
52 | 74 ::j
53 | 75 ::k
54 | 76 ::l
55 | 77 ::m
56 | 78 ::n
57 | 79 ::o
58 | 80 ::p
59 | 81 ::q
60 | 82 ::r
61 | 83 ::s
62 | 84 ::t
63 | 85 ::u
64 | 86 ::v
65 | 87 ::w
66 | 88 ::x
67 | 89 ::y
68 | 90 ::z
69 |
70 | 91 ::meta--left
71 | 92 ::right-window-key
72 | 93 ::meta--right
73 | 96 ::numpad-0
74 | 97 ::numpad-1
75 | 98 ::numpad-2
76 | 99 ::numpad-3
77 | 100 ::numpad-4
78 | 101 ::numpad-5
79 | 102 ::numpad-6
80 | 103 ::numpad-7
81 | 104 ::numpad-8
82 | 105 ::numpad-9
83 | 106 ::multiply
84 | 107 ::add
85 | 109 ::subtract
86 | 110 ::decimal-point
87 | 111 ::divide
88 | 112 ::f1
89 | 113 ::f2
90 | 114 ::f3
91 | 115 ::f4
92 | 116 ::f5
93 | 117 ::f6
94 | 118 ::f7
95 | 119 ::f8
96 | 120 ::f9
97 | 121 ::f10
98 | 122 ::f11
99 | 123 ::f12
100 | 124 ::f13
101 | 125 ::f14
102 | 126 ::f15
103 | 127 ::f16
104 | 128 ::f17
105 |
106 | 129 ::f18
107 | 130 ::f19
108 | 131 ::f20
109 | 132 ::f21
110 | 133 ::f22
111 | 134 ::f23
112 | 135 ::f24
113 | 144 ::num-lock
114 | 145 ::scroll-lock
115 | 166 ::page-backward
116 | 167 ::page-forward
117 | 168 ::refresh
118 | 172 ::home-key
119 | 187 ::equals
120 | 189 ::minus
121 | 174 ::decrease-volume-level
122 | 175 ::increase-volume-level
123 | 225 ::altgr})
124 |
--------------------------------------------------------------------------------
/src/crux_ui/views/commons/tabs.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.commons.tabs
2 | (:require [garden.core :as garden]
3 | [reagent.core :as r]))
4 |
5 | (def tabs-styles
6 | [:style
7 | (garden/css
8 | [:.tabs
9 | {:display "flex"
10 | :justify-content "space-between"
11 | :letter-spacing ".08em"
12 | :text-transform :uppercase
13 | :font-size :13px
14 | :align-items "center"}
15 | [:&__item
16 | {:cursor :pointer}
17 | [:&--active
18 | {:font-weight 600}]]
19 | [:&__sep
20 | {:padding "0 8px"}]
21 | [:&--header
22 | {:font-size "20px"}
23 | [:>.tabs__sep
24 | {:padding "16px"}]]])])
25 |
26 |
27 | (defn root [{:tabs/keys [tabs on-tab-activate active-id]}]
28 | [:div.tabs
29 | tabs-styles
30 | (butlast
31 | (interleave
32 | (for [{:tabs/keys [id title href] :as tab} tabs]
33 | ^{:key id}
34 | [:a.tabs__item.g-nolink
35 | (cond->
36 | {:class (if (= id active-id) "tabs__item--active")}
37 | href (assoc :href href)
38 | (not href) (assoc :on-click (r/partial on-tab-activate id)))
39 | title])
40 | (map (fn [i] ^{:key i} [:div.tabs__sep "/"]) (range))))])
41 |
--------------------------------------------------------------------------------
/src/crux_ui/views/commons/tiny_components.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.commons.tiny-components
2 | (:require [re-frame.core :as rf]
3 | [crux-ui.views.functions :as vu]
4 | [garden.core :as garden]
5 | [crux-ui.svg-icons :as icon]
6 | [crux-ui.views.style :as s]))
7 |
8 |
9 | (defn icon' [icon-name]
10 | [:i.icon {:key icon-name, :class (str "icon-" (name icon-name))}])
11 |
12 | (defn link-mailto [email]
13 | [:a {:href (str "mailto:" email)} email])
14 |
15 | (defn link-outer [href title]
16 | [:a {:target "_blank" :href href :title (str "Open " title " in a new tab")} title #_icon/external])
17 |
18 | (defn link-box [{:keys [main? luminous? active? svg? round? icon icon-alt on-click class href label attrs] :as params}]
19 | (let [css-class
20 | (-> :link
21 | (vu/bem-str {:main main? :round round? :active active? :luminous luminous?})
22 | (str class))]
23 | [:a.link.link--box.g-nolink (merge attrs {:href href :title icon-alt :on-click on-click :class css-class})
24 | #_(if icon
25 | (if svg?
26 | [svg/icon icon {:active? active?}]
27 | [:div.link__icon {:title icon-alt} [icon' icon icon-alt]]))
28 | (if label
29 | [:div.link__label label])]))
30 |
31 | (def col-base {:h 197 :s 80 :l 65 :a 0.8})
32 |
33 | ; cta – call to action
34 | (def btn-color--cta (s/hsl col-base))
35 | (def btn-color--inactive (s/hsl (assoc col-base :a 0.5)))
36 | (def btn-color--cta-hover (s/hsl (assoc col-base :a 0.9)))
37 | (def btn-color--cta-active (s/hsl (assoc col-base :a 1)))
38 |
39 |
40 | (def button-styles
41 | [:.button
42 | {:cursor :pointer
43 | :background :none
44 | :padding "6px 8px"
45 | :border-radius :2px
46 | :user-select :none
47 | :border "1px solid hsl(0, 0%, 85%)"}
48 |
49 | ; bordered cta button
50 | [:&--bright
51 | {:border (str "1px solid " btn-color--cta-active)
52 | :color btn-color--cta-active}
53 | [:&:hover
54 | {:border (str "1px solid " btn-color--cta-hover)}]]
55 | [:&--bright.button--inactive
56 | {:border (str "1px solid " btn-color--inactive)
57 | :color btn-color--inactive}]
58 |
59 | [:&--textual
60 | {:border :none
61 | :background :none}]
62 | [:&--active
63 | {:font-weight 400}]
64 |
65 | [:&--cta
66 | {:background btn-color--cta
67 | :border-radius :2px
68 | :color "white"
69 | :cursor :pointer
70 | :border 0
71 | :letter-spacing "0.03em"
72 | :padding "8px 14px"}
73 | [:&:hover
74 | {:background btn-color--cta-hover}]
75 | [:&:active
76 | {:background btn-color--cta-active}]]
77 | [:&--cta.button--inactive
78 | {:background btn-color--inactive}]])
79 |
80 | (defn- button [main-mod {:keys [on-click active? text css-mods] :as params}]
81 | [:button
82 | {:type :button
83 | :on-click on-click
84 | :class (vu/bem-str :button main-mod css-mods {:active active?})}
85 | (if (:icon params)
86 | [:div.button__icon [icon' (:icon params)]])
87 | [:span.button__text text]])
88 |
89 | (def button-textual (partial button :textual))
90 | (def button-cta (partial button :cta))
91 | (def button-bordered (partial button :bordered))
92 |
--------------------------------------------------------------------------------
/src/crux_ui/views/commons/user_intents.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.commons.user-intents
2 | (:require [crux-ui.views.commons.keycodes :as kc]
3 | [crux-ui.views.commons.dom :as dom]))
4 |
5 | (defn interpret-user-intent [target-value key-code]
6 | (if (empty? target-value)
7 | (case (kc/kc->kw key-code)
8 | ::kc/enter ::create
9 | ::kc/backspace-or-delete ::delete
10 | nil)
11 | (case (kc/kc->kw key-code)
12 | ::kc/enter ::create
13 | ::kc/backspace-or-delete nil
14 | nil)))
15 |
16 | (defn key-down-evt->intent-evt
17 | "boolean altKey
18 | number charCode
19 | boolean ctrlKey
20 | boolean getModifierState(key)
21 | string key
22 | number keyCode
23 | string locale
24 | number location
25 | boolean metaKey
26 | boolean repeat
27 | boolean shiftKey
28 | number which"
29 | [react-evt]
30 | (let [t (.-target react-evt)
31 | v (or (not-empty (.-value t)) (.-innerHTML t))
32 | window-selection (.getSelection js/window)
33 | kc (.-keyCode react-evt)
34 | intent (interpret-user-intent v kc)]
35 | (when intent
36 | (.preventDefault react-evt)
37 | {:intent intent
38 | :value v
39 | :target t
40 | :data (some-> t (dom/jsget "dataset") dom/dataset->clj-raw)})))
41 |
--------------------------------------------------------------------------------
/src/crux_ui/views/facade.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.facade
2 | (:require [garden.core :as garden]
3 | [garden.stylesheet :as gs]
4 | [re-frame.core :as rf]
5 | [crux-ui.views.query-ui :as q]
6 | [crux-ui.views.header :as header]
7 | [crux-ui.views.commons.tiny-components :as comps]
8 | [crux-ui.views.settings :as settings]
9 | [crux-ui.svg-icons :as icon]
10 | [crux-ui.views.second-layer :as second-layer]
11 | [crux-ui.views.commons.input :as input]
12 | [crux-ui.functions :as f]))
13 |
14 |
15 | (def color-link "hsl(32, 61%, 64%)")
16 | (def color-link--hover "hsl(32, 91%, 54%)")
17 |
18 | (defn on-sidebar-bg-click [evt]
19 | (if (= "root__sidebar" (some-> evt (f/jsget "target" "id")))
20 | (rf/dispatch [:evt.ui.second-layer/toggle])))
21 |
22 | (def ^:private root-styles
23 | [:style
24 | (garden/css
25 | [[:a
26 | {:color color-link}
27 | [:&:visited {:color color-link}]
28 | [:&:hover {:color color-link--hover}]]
29 | [:h1 :h2
30 | {:font-weight 300
31 | :letter-spacing :0.19em}]
32 | [:h3 :h4
33 | {:font-weight 300
34 | :letter-spacing :0.05em}]
35 | [:.g-nolink
36 | :.g-nolink:active
37 | :.g-nolink:visited
38 | {:text-decoration :inherit
39 | :color :inherit}]
40 | [:button
41 | {:font-size :1rem}]
42 | input/styles
43 | icon/styles
44 | comps/button-styles
45 | [:html :body :#app
46 | {:font-family "Helvetica Neue, Helvetica, BlinkMacSystemFont, -apple-system, Roboto, 'Segoe UI', sans-serif"
47 | :height "100%"
48 | :font-weight 400}]
49 |
50 | [:.root
51 | {:display :grid
52 | :place-items :stretch
53 | :height :100%
54 | :grid-template
55 | "'header' 84px
56 | 'body' calc(100% - 84px)"}
57 | [:&--page
58 | {:grid-template
59 | "'header' 84px
60 | 'body' 1fr"}]
61 | [:&__header
62 | {:grid-area :header}]
63 | [:&__second-layer
64 | {:position :fixed
65 | :top :0px
66 | :bottom :0px
67 | :border-radius :2px
68 | :left :0px
69 | :right 0
70 | :z-index 100
71 | :background "hsla(0, 0%, 0%, .3)"}]
72 | [:&__body
73 | {:grid-area :body
74 | :display :flex
75 | :overflow :hidden
76 | :align-items :center
77 | :justify-content :center}]]
78 |
79 | (gs/at-media {:max-width :375px}
80 | [:h1
81 | {:font-size :30px
82 | :line-height 2.0
83 | :letter-spacing :0.08em
84 | :font-weight 400}]
85 | [:h2
86 | {:font-size :24px}])
87 |
88 | (gs/at-media {:max-width :375px}
89 | [:.root
90 | {:grid-template
91 | "'header' 64px
92 | 'body' calc(100% - 64px)"}])])])
93 |
94 |
95 | (defn root []
96 | (let [-sub-root-tab (rf/subscribe [:subs.ui/root-tab])
97 | -sub-second-layer (rf/subscribe [:subs.db.ui/second-layer])]
98 | (fn []
99 | [:div#root.root
100 | root-styles
101 | [:div.root__header
102 | [header/root]]
103 | (if @-sub-second-layer
104 | [:div#root__second-layer.root__second-layer {:on-click on-sidebar-bg-click}
105 | [second-layer/root]])
106 | [:div.root__body
107 | (case @-sub-root-tab
108 | :db.ui.root-tab/query-ui [q/query-ui]
109 | :db.ui.root-tab/settings [settings/root]
110 | [q/query-ui])]])))
111 |
--------------------------------------------------------------------------------
/src/crux_ui/views/facade_perf.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.facade-perf
2 | (:require [garden.core :as garden]
3 | [crux-ui.views.query-perf :as query-perf]
4 | [crux-ui.views.header :as header]
5 | [crux-ui.views.commons.tiny-components :as comps]
6 | [crux-ui.svg-icons :as icon]))
7 |
8 | (def ^:private root-styles
9 | [:style
10 | (garden/css
11 | [[:a
12 | {:color "hsl(32, 91%, 54%)"}
13 | [:&:visited {:color "hsl(32, 91%, 54%)"}]]
14 | [:button
15 | {:font-size :1rem}]
16 | icon/styles
17 | comps/button-styles
18 | [:html :body :#app
19 | {:font-family "Helvetica Neue, Helvetica, BlinkMacSystemFont, -apple-system, Roboto, 'Segoe UI', sans-serif"
20 | :height "100%"
21 | :font-weight 300}]
22 |
23 | [:.root
24 | {:display :grid
25 | :place-items :stretch
26 | :height "100%"
27 | :grid-template
28 | "'header' 100px
29 | 'body' calc(100% - 100px)"}
30 | [:&--page
31 | {:grid-template
32 | "'header' 100px
33 | 'body' 1fr"}]
34 | [:&__header
35 | {:grid-area :header}]
36 | [:&__body
37 | {:grid-area :body
38 | :display :flex
39 | :align-items :center
40 | :justify-content :center}]]])])
41 |
42 |
43 | (defn root []
44 | [:div#root.root.root--page
45 | root-styles
46 | [:div.root__header
47 | [header/root]]
48 | [:div.root__body.root__body--page
49 | [query-perf/root]]])
50 |
51 |
--------------------------------------------------------------------------------
/src/crux_ui/views/functions.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.functions)
2 |
3 |
4 | ; bem
5 | (defn unfold-modifier-maps [possible-modifier-map]
6 | (if-not (map? possible-modifier-map)
7 | possible-modifier-map
8 | (for [[k v] possible-modifier-map :when v] k)))
9 |
10 | (defn flatten-modifiers [modifiers]
11 | (flatten (map unfold-modifier-maps modifiers)))
12 |
13 | (defn- bem-str-strict [css-class-name modifiers]
14 | (let [base-class-name (name css-class-name)
15 | modifiers (flatten-modifiers modifiers)
16 | -append
17 | (fn [final-class-name modifier-name]
18 | (if modifier-name
19 | (str final-class-name " " base-class-name"--" (.replace (name modifier-name) "?" ""))
20 | final-class-name))]
21 | (reduce -append base-class-name modifiers)))
22 |
23 | (defn bem-str [css-class-name & modifiers]
24 | (bem-str-strict css-class-name modifiers))
25 |
26 | (defn bem [css-class-name & modifiers]
27 | {:class (bem-str-strict css-class-name modifiers)})
28 |
29 |
--------------------------------------------------------------------------------
/src/crux_ui/views/header.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.header
2 | (:require [garden.core :as garden]
3 | [garden.stylesheet :as gs]
4 | [re-frame.core :as rf]
5 | [crux-ui.routes :as routes]
6 | [crux-ui.views.commons.tabs :as tabs]
7 | [crux-ui.views.commons.css-logo :as css-logo]
8 | [crux-ui.views.node-status :as node-status]
9 | [crux-ui.views.commons.input :as input]
10 | [crux-ui.views.commons.tiny-components :as comps]
11 | [crux-ui.config :as cfg]))
12 |
13 |
14 | (def ^:private -sub-display-mode (rf/subscribe [:subs.ui/display-mode]))
15 |
16 | (defn disable-toggle-display-mode []
17 | (rf/dispatch [:evt.ui.display-mode/toggle]))
18 |
19 | (defn dispatch-second-layer-toggle []
20 | (rf/dispatch [:evt.ui.second-layer/toggle]))
21 |
22 |
23 | (def ^:private header-styles
24 | [:style
25 | (garden/css
26 | [:.header
27 | {:display :grid
28 | :grid-template "'logo status spacer links' / 184px 100px 1fr 260px"
29 | :place-items "center start"
30 | :padding "12px 1rem"
31 | :width "100%"}
32 | [:&__logo
33 | {:display :flex
34 | :grid-area :logo
35 | :justify-content :space-between
36 | :align-items :center}]
37 | [:&__status
38 | {:grid-area :status}]
39 | [:&__display-mode-toggle
40 | {:display :none}]
41 | [:&__links
42 | {:display "flex"
43 | :grid-area :links
44 | :place-self "center stretch"
45 | :justify-content "space-between"
46 | :flex "0 0 250px"
47 | :align-items "center"}]]
48 | (gs/at-media {:max-width :1000px}
49 | [:.header
50 | {:padding "16px"
51 | :grid-gap :8px
52 | :grid-template "'logo status toggle' / 1fr 1fr 1fr"}
53 | [:&__display-mode-toggle
54 | {:display :block
55 | :justify-self :end
56 | :grid-area :toggle}]
57 | [:&__links
58 | {:display :none}]])
59 | (gs/at-media {:max-width :600px}
60 | [:.header
61 | {:padding "16px"
62 | :grid-template "'logo status toggle' / auto auto auto"}])
63 | (gs/at-media {:max-width :400px}
64 | [:.header
65 | {:padding "16px 4px 16px 16px"
66 | :grid-gap :4px
67 | :grid-template "'logo status toggle' / auto minmax(auto, 120px) auto"}
68 | [:&__status
69 | {:justify-self :stretch
70 | :max-width :100%}]]))])
71 |
72 | (defn root []
73 | [:header.header
74 | header-styles
75 | [:div.header__logo {:on-click dispatch-second-layer-toggle}
76 | [css-logo/root]]
77 | [:div.header__status
78 | [node-status/node-status]]
79 |
80 | [:div.header__display-mode-toggle
81 | [comps/button-textual
82 | {:on-click disable-toggle-display-mode
83 | :text
84 | (if (= @-sub-display-mode :ui.display-mode/query)
85 | "To output >"
86 | "< To query")}]]
87 |
88 | [:div.header__links
89 | [:div.header__links__item
90 | [comps/link-outer cfg/url-docs "Docs"]]
91 | [:div.header__links__item
92 | [comps/link-outer cfg/url-chat "Crux Chat"]]
93 | [:div.header__links__item
94 | [comps/link-mailto cfg/url-mail]]]])
95 |
96 |
--------------------------------------------------------------------------------
/src/crux_ui/views/node_status.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.node-status
2 | (:require [re-frame.core :as rf]
3 | [garden.core :as garden]
4 | [garden.stylesheet :as gs]
5 | [crux-ui.views.commons.contenteditable :as editable]
6 | [crux-ui.functions :as f]
7 | [crux-ui.views.commons.keycodes :as kc]
8 | [crux-ui.views.functions :as vf]
9 | [crux-ui.views.style :as s]
10 | [crux-ui.views.commons.dom :as dom]))
11 |
12 |
13 |
14 | (def ^:private -sub-node-addr (rf/subscribe [:subs.sys/host]))
15 | (def ^:private -sub-node-status (rf/subscribe [:subs.sys.host/status]))
16 |
17 | (defn- on-host-change [{v :value :as change-complete-evt}]
18 | (rf/dispatch [:evt.db/host-change v]))
19 |
20 | (def ^:private node-styles
21 | [:style
22 | (garden/css
23 | [:.led
24 | (let [size :8px]
25 | {:height size
26 | :width size
27 | :background :grey
28 | :margin-right :8px
29 | :border-radius size})
30 | [:&--on
31 | {:background :green}]]
32 | [:.node
33 | {:display :flex
34 | :position :relative
35 | :justify-content :space-between
36 | :align-items :center
37 | :width "100%"}
38 | [:>.node__status
39 | {:display :none}]
40 | [:&:hover
41 | [:>.node__status
42 | {:position :absolute
43 | :background :white
44 | :padding :8px
45 | ;:max-width :500px
46 | :font-family "monospace"
47 | :display :block
48 | :z-index 1000
49 | :font-size :1.2rem
50 | :border-radius :2px
51 | :border s/q-ui-border
52 | :top :32px}]]
53 | [:&__addr
54 | {:display "flex"
55 | :justify-content "space-between"
56 | :overflow :hidden
57 | :white-space :nowrap
58 | :text-overflow :ellipsis
59 | :flex "1 1 120px"
60 | :align-items "center"}]]
61 | (gs/at-media {:max-width :1000px}
62 | [".node>.node__status"
63 | {:display :none}]))])
64 |
65 |
66 | (defn led [on?]
67 | [:div (vf/bem :led {:on on? :off (not on?)})])
68 |
69 | (defn node-status []
70 | [:div.node
71 | node-styles
72 | [:div.node__led [led (boolean @-sub-node-status)]]
73 | [:div.node__status
74 | (if-let [node-info @-sub-node-status]
75 | [:pre (f/pprint-str node-info)] ; alternatively [edn/root node-info]
76 | [:pre "Cannot connect to the specified node address"])]
77 | [:div.node__addr
78 | [editable/div ::host-input
79 | {:on-change-complete on-host-change
80 | :on-key-down
81 | (dom/dispatch-on-keycode
82 | {::kc/enter #(some-> % (.-target) (.blur))})
83 | :placeholder "Node hostname/and-path"
84 | :value @-sub-node-addr}]]])
85 |
--------------------------------------------------------------------------------
/src/crux_ui/views/output/attr_history.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.output.attr-history
2 | (:require [re-frame.core :as rf]
3 | [garden.core :as garden]
4 | [crux-ui.views.charts.wrapper-basic :as charts]
5 | [crux-ui.functions :as f]
6 | [crux-ui.views.commons.tiny-components :as comps]))
7 |
8 | (def ^:private -sub-plotly-data (rf/subscribe [:subs.query/attr-history-plot-data]))
9 |
10 |
11 | (def ^:private root-styles
12 | [:style
13 | (garden/css
14 | [:.attr-history
15 | {:height :100%
16 | :padding "6px 8px"
17 | :position :relative}
18 | [:&__chart
19 | {:height :100%}]
20 | [:&__hint
21 | {:position :absolute
22 | :background "hsla(0,0%, 100%, 0.9)"
23 | :top 0
24 | :left 0
25 | :bottom 0
26 | :right 0
27 | :overflow :hidden
28 | :display :grid
29 | :grid-template
30 | (f/lines
31 | "'h h h' 1fr"
32 | "'l hint r' auto"
33 | "'l btn r' auto"
34 | "'f f f' 1fr"
35 | "/ 1fr auto 1fr")
36 | :grid-gap :32px
37 | :place-items "center start"
38 | :width :100%
39 | :height :100%}
40 | ["> .q-output-empty"
41 | {:height :auto
42 | :grid-area :hint}]
43 | ["> .button"
44 | {:height :auto
45 | :grid-area :btn}]]])])
46 |
47 | (def ^:private hint
48 | (str "This view composes `history-range` and `documents` endpoints to\n"
49 | "give you a some understanding of how an entity attribute has changed over valid time.\n"
50 | "This view is limited to display up to 7 entities.\n"
51 | "You can use \"put with valid time\" example to transact in\n"
52 | "some historical data for. Use that entity id later in your query.\n"
53 | "\n"
54 | "Try to run a query that will include a numeric attribute like:\n"
55 | "{:find [e p]\n"
56 | " :args [{ids #{:ids/tech-ticker-2 :ids/pharma-ticker-5}}]\n"
57 | " :where\n"
58 | " [[e :crux.db/id]\n"
59 | " [e :ticker/price p]\n"
60 | " [(contains? ids e)]]}\n"))
61 |
62 | (defn- attr-layout [attr]
63 | {:title (str (pr-str attr) " over time")
64 | :xaxis {:title "Valid Time"}
65 | :yaxis {:title (pr-str attr)}})
66 |
67 | (defn- dispatch-disable-hint [_]
68 | (rf/dispatch [:evt.ui.attr-history/disable-hint]))
69 |
70 | (defn root []
71 | [:div.attr-history
72 | root-styles
73 | (let [{:keys [traces attribute hint?] :as p-data} @-sub-plotly-data]
74 | (if p-data
75 | [:<>
76 | [:div.attr-history__chart
77 | [charts/plotly-wrapper traces (attr-layout attribute)]]
78 | (if hint?
79 | [:div.attr-history__hint
80 | [:pre.q-output-empty hint]
81 | [comps/button-bordered
82 | {:on-click dispatch-disable-hint
83 | :text "Got it"}]])]
84 | [:pre.q-output-empty hint]))])
85 |
86 |
87 |
--------------------------------------------------------------------------------
/src/crux_ui/views/output/edn.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.output.edn
2 | (:require [cljs.pprint :as pp]
3 | [crux-ui.better-printer :as bp]
4 | [crux-ui.views.codemirror :as cm]
5 | [clojure.string :as s]))
6 |
7 | (defn root [edn]
8 | [cm/code-mirror (bp/better-printer edn) {:read-only? true}])
9 |
10 | (defn simple-print [edn]
11 | [cm/code-mirror (bp/simple-print edn) {:read-only? true}])
12 |
--------------------------------------------------------------------------------
/src/crux_ui/views/output/error.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.output.error
2 | (:require [cljs.pprint :refer [pprint]]
3 | [crux-ui.views.codemirror :as cm]
4 | [garden.core :as garden]))
5 |
6 |
7 | (def ^:private style
8 | [:style
9 | (garden/css
10 | [:.err-info
11 | {:padding "8px 16px"
12 | :height :100%
13 | :overflow :scroll}
14 | [:&__header
15 | {:line-height 1
16 | :padding "24px 8px 24px"}]
17 | [:&__title
18 | {:letter-spacing :0.09em
19 | :font-size :30px}]
20 | [:&__subtitle
21 | {:font-size :24px
22 | :margin-top :8px}]])])
23 |
24 |
25 |
26 | (defn root [{:keys [query-type err/http-status err/type err/exception] :as err-data}]
27 | (let [fmt (with-out-str (pprint err-data))]
28 | [:div.err-info
29 | style
30 | [:div.err-info__header
31 | [:h1.err-info__title http-status " Error"]
32 | [:h2.err-info__subtitle
33 | (case type
34 | :err.type/client "Server considered the query malformed"
35 | :err.type/server "HTTP Server produced an error while executing the query")]]
36 | [:div.err-info__raw
37 | [cm/code-mirror fmt {:read-only? true}]]]))
38 |
--------------------------------------------------------------------------------
/src/crux_ui/views/output/react_tree.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.output.react-tree
2 | (:require ["react-ui-tree" :as ReactTree]
3 | [garden.core :as garden]))
4 |
5 | (defn on-tree-change [evt]
6 | (println :on-tree-change evt))
7 |
8 | (def style
9 | [:style
10 | (garden/css
11 | [:.react-tree
12 | {:min-width :100px
13 | :min-height :100px
14 | :height :100%
15 | :overflow :auto
16 | :padding "40px 32px"}])])
17 |
18 | (defn root [tree-struct]
19 | [:div.react-tree
20 | style
21 | [:> ReactTree
22 | {:paddingLeft 20
23 | :onChange on-tree-change
24 | :renderNode
25 | (fn [node]
26 | (.-title node))
27 |
28 | :tree (clj->js tree-struct)}]])
29 |
30 |
--------------------------------------------------------------------------------
/src/crux_ui/views/output/table.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.output.table
2 | (:require [re-frame.core :as rf]
3 | [crux-ui.views.style :as s]
4 | [garden.core :as garden]
5 | [reagent.core :as r]
6 | [crux-ui.functions :as f]
7 | [crux-ui.logging :as log]
8 | [crux-ui.views.commons.dom :as dom]))
9 |
10 |
11 | (def ^:private table-style
12 | [:style
13 | (garden/css
14 |
15 | [:.g-cell-oversize-arrest
16 | [:&__content
17 | {:max-width :300px
18 | :max-height :76px
19 | :overflow :hidden}
20 | [:&--expanded
21 | {:max-width :none
22 | :max-height :none}]]
23 | [:&__expand
24 | {:text-align :center
25 | :cursor :pointer}]]
26 |
27 | [:.q-grid-wrapper
28 | {:overflow :scroll
29 | :height :100%
30 | :padding-bottom :15rem}
31 | ["> ::-webkit-scrollbar"
32 | "> ::-moz-scrollbar"
33 | "> ::scrollbar"
34 | {:display :none}]]
35 | [:.q-grid
36 | {:border-collapse :separate
37 | :border-radius :2px
38 | :width :100%
39 | :overflow :visible
40 | :position :relative}
41 | [:&__head
42 | {}
43 | [:&-row
44 | {}]]
45 |
46 | ["&__body-cell"
47 | "&__head-cell"
48 | {:border-left s/q-ui-border
49 | :border-bottom s/q-ui-border
50 | :padding "10px 12px"}]
51 | ["&__head-cell"
52 | {:border-top :none
53 | :background :white
54 | :position :sticky
55 | :top 0
56 | :text-align :center
57 | :font-weight 400
58 | :letter-spacing :.10em}
59 | [:&:first-child
60 | {:border-left :none}]]
61 | [:&__body
62 | [:&-row
63 | {:border-top s/q-ui-border}
64 | [:&:first-child
65 | {:border-top :none}]]
66 | [:&-cell
67 | {:letter-spacing :.04em
68 | :border-bottom "1px solid hsl(200,20%, 90%)"}
69 | [:&:first-child
70 | {:border-left :none}]
71 | [:&--queryable
72 | {:cursor :pointer
73 | :box-shadow "inset 5px 0 0px -3px hsla(220, 90%, 70%, 0.5)"}
74 | [:&:hover
75 | {:box-shadow "inset 6px 0 0px -3px hsla(220, 90%, 70%, 1)"}]]]]])])
76 |
77 | (defn- expand-sibling [click-evt]
78 | (let [cl (f/jsget click-evt "target" "previousSibling" "classList")]
79 | (^js .toggle cl "g-cell-oversize-arrest__content--expanded")))
80 |
81 | (defn- on-queryable-cell-click [click-evt]
82 | (let [dataset (f/prefix-keys :data (dom/event->target-data click-evt))]
83 | (rf/dispatch [:evt.ui.query/submit--cell dataset])))
84 |
85 | (defn- plain-value? [v]
86 | (or (boolean? v)
87 | (number? v)
88 | (keyword? v)
89 | (and (string? v) (< (.-length v) 100))))
90 |
91 | ; (pr-str (type true))
92 |
93 | (defn table-row [attr-headers value-is-the-attribute? i row-item-map]
94 | ^{:key i}
95 | [:tr.q-grid__body-row
96 | (for [attr attr-headers
97 | :let [raw-value (get row-item-map attr)
98 | cell-content ^js/String (or (some-> raw-value pr-str) "")
99 | cont-length (.-length cell-content)
100 | queryable? (and raw-value (plain-value? raw-value))
101 | attr-str (pr-str (if value-is-the-attribute? raw-value attr))]]
102 | ^{:key attr}
103 | [:td.q-grid__body-cell
104 |
105 | (if queryable?
106 | (cond->
107 | {:class (if queryable? "q-grid__body-cell--queryable")
108 | :on-click (if queryable? on-queryable-cell-click)
109 | :title attr-str
110 | :data-idx i
111 | :data-attr attr-str}
112 | ;
113 | (not value-is-the-attribute?)
114 | (assoc :data-value (pr-str raw-value))))
115 |
116 | (if (> cont-length 100)
117 | [:div.g-cell-oversize-arrest
118 | [:div.g-cell-oversize-arrest__content cell-content]
119 | [:div.g-cell-oversize-arrest__expand
120 | {:on-click expand-sibling} "..."]]
121 | cell-content)])])
122 |
123 | (defn root [table-data]
124 | (let [instance-state
125 | (r/atom {:i/node nil
126 | :i/ctrl-c? false})
127 |
128 | on-ku-internal
129 | (fn [evt]
130 | (when-let [meta-released? (#{"Meta" "Control"} (.-key evt))]
131 | (swap! instance-state assoc :i/ctrl-c? false)
132 | (.remove (.-classList (:i/node @instance-state)) "q-grid--table")))
133 |
134 | on-kd-internal
135 | (fn [kb-evt]
136 | (let [ctrl-or-cmd? (or (.-metaKey kb-evt) (.-ctrlKey kb-evt))
137 | c? (= "c" (.-key kb-evt))
138 | ctrl-c? (and ctrl-or-cmd? c?)]
139 | (when ctrl-c?
140 | (swap! instance-state assoc :i/ctrl-c? true)
141 | (.add (.-classList (:i/node @instance-state)) "q-grid--table"))))]
142 |
143 | (r/create-class
144 | {:component-will-unmount
145 | (fn [this]
146 | (js/window.removeEventListener "keydown" on-kd-internal true)
147 | (js/window.removeEventListener "keyup" on-ku-internal true))
148 | :component-did-mount
149 | (fn [this]
150 | (let [node (r/dom-node this)]
151 | (js/window.addEventListener "keydown" on-kd-internal true)
152 | (js/window.addEventListener "keyup" on-ku-internal true)
153 | (swap! instance-state assoc :i/node node)))
154 |
155 | :reagent-render
156 | (fn [{:keys [headers attr-headers rows value-is-the-attribute?] :as table-data}]
157 | [:div.q-grid-wrapper
158 | [:table.q-grid
159 | table-style
160 | [:thead.q-grid__head
161 | [:tr.q-grid__head-row
162 | (for [[h ha] (map vector headers attr-headers)]
163 | ^{:key h}
164 | [:th.q-grid__head-cell {:title ha} (pr-str h)])]]
165 | [:tbody.q-grid__body
166 | (map-indexed (partial table-row attr-headers value-is-the-attribute?) rows)]]])})))
167 |
168 |
169 |
--------------------------------------------------------------------------------
/src/crux_ui/views/output/tx_history.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.output.tx-history
2 | (:require [re-frame.core :as rf]
3 | [garden.core :as garden]
4 | [crux-ui.views.charts.wrapper-basic :as charts]))
5 |
6 | (def ^:private -sub-tx-history (rf/subscribe [:subs.output/tx-history-plot-data]))
7 |
8 | (def ^:private root-styles
9 | [:style
10 | (garden/css
11 | [:.tx-history
12 | {:height :100%
13 | :padding "6px 8px"}])])
14 |
15 | (def tx-layout
16 | {:title "Queried entities transactions"
17 | :xaxis {:title "Valid Time"}
18 | :yaxis {:title "Transaction time"}})
19 |
20 | (defn root []
21 | [:div.tx-history
22 | root-styles
23 | (if-let [tx-history @-sub-tx-history]
24 | [charts/plotly-wrapper tx-history tx-layout]
25 | [:div.q-output-empty "No data to display, try to run a query"])])
26 |
--------------------------------------------------------------------------------
/src/crux_ui/views/overview.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.overview
2 | (:require [crux-ui.views.settings :as settings]
3 | [crux-ui.views.sidebar :as sidebar]
4 | [crux-ui.views.commons.tiny-components :as comps]
5 | [garden.core :as garden]
6 | [garden.stylesheet :as gs]
7 | [re-frame.core :as rf]
8 | [crux-ui.config :as cfg]))
9 |
10 | (def ^:private root-styles
11 | [:style
12 | (garden/css
13 | [:.overview
14 | {:padding "16px 32px"
15 | :width :100%}
16 | [:label
17 | {:display :inline-block
18 | :width :200px}]
19 | [:.g-mt-2
20 | {:margin-top :16px}]
21 | [:kbd
22 | {:padding "4px 6px"
23 | :background :white
24 | :border-radius :2px
25 | :box-shadow "0 0px 0px 1px hsl(0, 0%, 75%)"}]
26 | [:&__section
27 | {:margin-top :32px}]
28 | [:&__footer
29 | {:margin-top :48px}]]
30 | (gs/at-media {:max-width :1000px}
31 | [:.overview
32 | {}
33 | [:&__section--shortcuts
34 | {:display :none}]]))])
35 |
36 | (defn close []
37 | (rf/dispatch [:evt.ui.second-layer.main-pane/cancel]))
38 |
39 | (defn root []
40 | [:div.overview
41 | root-styles
42 | [:h1.overview__title "Console Overview"]
43 | [:section.overview__section.overview__section--shortcuts
44 | [:h2.overview__header "Features"]
45 | [:section.overview__section
46 | [:h3.overview__header "Attribute history"]]
47 | [:section.overview__section
48 | [:h3.overview__header "Examples import"]
49 | [:a {:href cfg/url-examples-gist :target :_blank} "Import example"]]]
50 | [:section.overview__section
51 | [:h2.overview__header "Limitations"]
52 | [:div.g-mt-2 "When you execute a normal query (not tx) not longer than 2000
53 | characters it will be placed into your address bar so you can share it."]]
54 | [:section.overview__section.overview__section--shortcuts
55 | [:h2.overview__header "Shortcuts"]
56 | [:div.g-mt-2 [:label "Query submit"] [:kbd "ctrl + enter"]]
57 | [:div.g-mt-2 [:label "Toggle editor"] [:kbd "ctrl + e"]]
58 | [:div.g-mt-2 [:label "Toggle fullscreen"] [:kbd "ctrl + cmd + f"]]]
59 | [:footer.overview__footer
60 | [comps/button-textual
61 | {:text "Cancel"
62 | :on-click close}]]])
63 |
--------------------------------------------------------------------------------
/src/crux_ui/views/query/datepicker_slider.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.query.datepicker-slider
2 | (:require [re-frame.core :as rf]
3 | [crux-ui.logging :as log]
4 | [crux-ui.logic.time :as time]
5 | [crux-ui.views.commons.datepicker-native :as dpn]
6 | ["react-input-range" :as ir]
7 | [reagent.core :as r]
8 | [garden.core :as garden]))
9 |
10 |
11 | (def style
12 | [:style
13 | (garden/css
14 | [:.slider-date-time-picker
15 | {:width :100%}
16 | [:&__header
17 | {:display :flex
18 | :align-items :center
19 | :justify-content :space-between}
20 | [:&-label
21 | {:font-size :18px
22 | :font-weight 400
23 | :letter-spacing :0.09em}]
24 | [:&-native
25 | {:width :250px
26 | :flex "0 0 100px"}]]
27 | [:&__row
28 | {:display :flex
29 | :align-items :center
30 | :height :28px}
31 | [:&-label
32 | {:flex "0 0 60px"
33 | :text-transform :lowercase
34 | :font-size :13px}]
35 | [:&-slider
36 | {:flex "1 1 auto"}]]])])
37 |
38 |
39 | (defn root-simple
40 | [{:keys [value ; components
41 | ^js/String label
42 | on-change
43 | on-change-complete]
44 | :as prms}]
45 | (let [on-change-internal
46 | (fn [time-component nv & [complete?]]
47 | (let [new-val (assoc value time-component nv)]
48 | (when (and complete? on-change-complete)
49 | (println :on-change (time/comps->date new-val))
50 | (on-change-complete (time/comps->date new-val)))
51 | (when on-change
52 | (println :on-change (time/comps->date new-val))
53 | (on-change (time/comps->date new-val)))))]
54 | [:div.slider-date-time-picker
55 | [:div.slider-date-time-picker__header
56 | [:label.slider-date-time-picker__header-label label]
57 | [:div.slider-date-time-picker__header-native
58 | [dpn/picker
59 | {:value (time/comps->date value)
60 | :on-change on-change}]]]
61 | [:div.slider-date-time-picker__row
62 | [:div.slider-date-time-picker__row-label "Year"]
63 | [:div.slider-date-time-picker__row-slider
64 | [:> ir {:value (get value :time/year)
65 | :step 1
66 | :classNames #js {:activeTrack "input-range__track input-range__track--active",
67 | :disabledInputRange "input-range input-range--disabled",
68 | :inputRange "input-range",
69 | :labelContainer "input-range__label-container",
70 | :maxLabel "input-range__label input-range__label--max",
71 | :minLabel "input-range__label input-range__label--min",
72 | :slider "input-range__slider",
73 | :sliderContainer "input-range__slider-container",
74 | :track "input-range__track input-range__track--background",
75 | :valueLabel "input-range__label input-range__label--value-year",}
76 |
77 | :minValue 1970
78 | :maxValue 2020
79 | :onChangeComplete #(on-change-internal :time/year % true)
80 | :onChange #(on-change-internal :time/year %)}]]]
81 | [:div.slider-date-time-picker__row
82 | [:div.slider-date-time-picker__row-label "Month"]
83 | [:div.slider-date-time-picker__row-slider
84 | [:> ir {:value (get value :time/month)
85 | :step 1
86 | :minValue 1
87 | :maxValue 12
88 | :onChangeComplete #(on-change-internal :time/month % true)
89 | :onChange #(on-change-internal :time/month %)}]]]
90 | [:div.slider-date-time-picker__row
91 | [:div.slider-date-time-picker__row-label "Day"]
92 | [:div.slider-date-time-picker__row-slider
93 | [:> ir {:value (get value :time/date)
94 | :step 1
95 | :minValue 1
96 | :maxValue 31
97 | :onChangeComplete #(on-change-internal :time/date % true)
98 | :onChange #(on-change-internal :time/date %)}]]]
99 | [:div.slider-date-time-picker__row
100 | [:div.slider-date-time-picker__row-label "Hour"]
101 | [:div.slider-date-time-picker__row-slider
102 | [:> ir {:value (get value :time/hour)
103 | :step 1
104 | :minValue 0
105 | :maxValue 23
106 | :onChangeComplete #(on-change-internal :time/hour % true)
107 | :onChange #(on-change-internal :time/hour %)}]]]
108 | [:div.slider-date-time-picker__row
109 | [:div.slider-date-time-picker__row-label "Minute"]
110 | [:div.slider-date-time-picker__row-slider
111 | [:> ir {:value (get value :time/minute)
112 | :step 1
113 | :minValue 0
114 | :maxValue 59
115 | :onChangeComplete #(on-change-internal :time/minute % true)
116 | :onChange #(on-change-internal :time/minute %)}]]]]))
117 |
--------------------------------------------------------------------------------
/src/crux_ui/views/query/editor.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.query.editor
2 | (:require [crux-ui.views.codemirror :as cm]
3 | [garden.core :as garden]
4 | [re-frame.core :as rf]
5 | [crux-ui.views.query.examples :as query-examples]
6 | [crux-ui.views.style :as s]
7 | [garden.stylesheet :as gs]))
8 |
9 | (def ^:private -sub-query-input (rf/subscribe [:subs.query/input]))
10 | (def ^:private -sub-query-analysis (rf/subscribe [:subs.query/analysis]))
11 | (def ^:private -stats (rf/subscribe [:subs.query/stats]))
12 | (def ^:private -sub-query-input-malformed (rf/subscribe [:subs.query/input-malformed?]))
13 |
14 | (defn- on-qe-change [v]
15 | (rf/dispatch [:evt.ui/query-change v]))
16 |
17 | (def query-ui-styles
18 | [:style
19 | (garden/css
20 | [:.q-editor
21 | {:padding "8px"
22 | :position :relative
23 | :max-height :100%
24 | ;:overflow :scroll
25 | :height :100%}
26 |
27 | [:&__query-type
28 | {:position :absolute
29 | :right :8px
30 | :color s/color-font-secondary
31 | :top :8px
32 | :z-index 10}]
33 |
34 | [:&__error
35 | {:position :absolute
36 | :color :grey
37 | :background :white
38 | :bottom :8px
39 | :z-index 10
40 | :left :24px}]])])
41 |
42 |
43 |
44 |
45 | (defn root []
46 | [:div.q-editor
47 | query-ui-styles
48 | [:div.q-editor__query-type (name (:crux.ui/query-type @-sub-query-analysis ""))]
49 | (if-let [e @-sub-query-input-malformed]
50 | [:div.q-editor__error
51 | "Query input appears to be malformed: " (.-message e)])
52 |
53 | ^{:key @-stats}
54 | [cm/code-mirror
55 | @-sub-query-input
56 | {:on-change on-qe-change
57 | :stats @-stats}]])
58 |
59 |
--------------------------------------------------------------------------------
/src/crux_ui/views/query/examples.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.query.examples
2 | (:require [re-frame.core :as rf]
3 | [crux-ui.views.commons.tiny-components :as comps]
4 | [crux-ui.svg-icons :as icon]
5 | [garden.core :as garden]
6 | [garden.stylesheet :as gs]
7 | [reagent.core :as r]))
8 |
9 |
10 | (def ^:private -sub-examples (rf/subscribe [:subs.query/examples]))
11 |
12 | (defn dispatch-examples-close []
13 | (rf/dispatch [:evt.ui.examples/close]))
14 |
15 | (defn- dispatch-example [ex-title]
16 | (rf/dispatch [:evt.ui.editor/set-example ex-title]))
17 |
18 | (defn is-gist-link? [s]
19 | (let [url (js/URL. s)]
20 | (= "gist.githubusercontent.com" (.-hostname url))))
21 |
22 | (defn on-examples-add []
23 | (let [gh-link (js/prompt "Paste a GitHub gist raw content link")]
24 | (if (is-gist-link? gh-link)
25 | (rf/dispatch [:evt.ui/github-examples-request gh-link])
26 | (js/alert "Please ensure it's a raw gist link"))))
27 |
28 | (def ^:private q-form-styles
29 | [:style
30 | (garden/css
31 | [:.examples
32 | {:display :flex
33 | :width :max-content
34 | :align-items :center
35 | :font-size :13px}
36 | [:&__item
37 | :&__close
38 | {:padding :8px
39 | :cursor :pointer}
40 | [:&:hover
41 | {:color :black}]]
42 | [:&__close
43 | {:line-height 0
44 | :padding "0 8px"
45 | :display :inline-flex
46 | :align-items :center}]
47 | [:&__title
48 | {:padding :8px}]
49 | [:&__import
50 | {:margin-left :8px
51 | :padding :8px}]]
52 | (gs/at-media {:max-width :1000px}
53 | [:.examples
54 | {;:display :none
55 | }]))])
56 |
57 | (def ^{:private true :const true} examples-close-text
58 | "Tap to close examples. You can always get them back by resetting the cookies on this host.")
59 |
60 | (defn root []
61 | (if-let [examples @-sub-examples]
62 | [:div.examples
63 | q-form-styles
64 | [:div.examples__close
65 | {:title examples-close-text
66 | :on-click dispatch-examples-close} icon/close]
67 | [:div.examples__title "Examples: "]
68 | (for [{ex-title :title} examples]
69 | ^{:key ex-title}
70 | [:div.examples__item
71 | [comps/button-textual
72 | {:on-click (r/partial dispatch-example ex-title)
73 | :text ex-title}]])
74 | [:div.examples__import
75 | {:on-click on-examples-add}
76 | "Set my examples"]]))
77 |
78 |
--------------------------------------------------------------------------------
/src/crux_ui/views/query/form.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.query.form
2 | (:require [re-frame.core :as rf]
3 | [garden.core :as garden]
4 | [garden.stylesheet :as gs]
5 | [crux-ui.views.style :as s]
6 | [crux-ui.views.query.editor :as q-editor]
7 | [crux-ui.views.query.time-controls :as time-controls]
8 | [crux-ui.views.commons.tiny-components :as comps]
9 | [crux-ui.views.query.examples :as query-examples]
10 | [crux-ui.views.functions :as vu]))
11 |
12 |
13 | (def ^:private -sub-editor-key (rf/subscribe [:subs.ui/editor-key]))
14 | (def ^:private -sub-query-analysis (rf/subscribe [:subs.query/analysis]))
15 |
16 | (defn- on-submit [e]
17 | (rf/dispatch [:evt.ui.query/submit {:evt/push-url? true}]))
18 |
19 |
20 | (def ^:private q-form-styles
21 | [:style
22 | (garden/css
23 | [:.examples-wrapper
24 | {:overflow :auto}]
25 | [:.q-form
26 | {:position :relative
27 | :display :grid
28 | :overflow :hidden
29 | :grid-template
30 | "'editor editor' 1fr
31 | 'time-controls submit' auto
32 | / 1fr 128px"
33 | :height :100%}
34 | [:&--examples
35 | {:grid-template
36 | "'editor editor' 1fr
37 | 'time-controls submit' auto
38 | 'examples examples' auto
39 | / 1fr 128px"}]
40 |
41 | [:&__time-controls
42 | {:grid-area "time-controls"
43 | :padding "16px 8px 16px 16px"}]
44 |
45 | [:&__editor
46 | {:grid-area "editor"
47 | :overflow :hidden
48 | :height :100%}]
49 |
50 | [:&__submit
51 | {:grid-area :submit
52 | :text-align :center
53 | :padding "24px 8px"
54 | :place-self "end"}
55 | [:>small
56 | {:font-size :0.8em
57 | :color s/color-font-secondary}]]
58 |
59 |
60 |
61 | [:&__examples
62 | {:grid-area :examples
63 | :overflow :hidden
64 | :width "calc(100% - 32px)"
65 | :border-radius :2px
66 | :background :white
67 | :padding "0px 0px"
68 | :position :relative
69 | :height :47px
70 | :left :16px
71 | :color s/color-font-secondary
72 | :z-index 10}
73 | ["> .examples-wrapper"
74 | {:margin-left "-8px"
75 | :position :absolute
76 | :width :100%
77 | :top 0
78 | :padding-bottom :17px
79 | :overflow :scroll
80 | :bottom "-17px"}
81 | {:overflow-x :scroll
82 | :overflow-y :hidden}]
83 | ["> ::scrollbar"
84 | "> ::-moz-scrollbar"
85 | "> ::-webkit-scrollbar"
86 | {:display :none}]]]
87 |
88 | (gs/at-media {:max-width :1000px}
89 | [:.q-form
90 | {:padding-bottom :32px
91 | :grid-template
92 | "'editor' 1fr
93 | 'time-controls' auto
94 | 'submit' auto
95 | / 100%"}
96 | [:&--examples
97 | {:grid-template
98 | "'editor' 1fr
99 | 'time-controls' auto
100 | 'submit' auto
101 | 'examples' auto
102 | / 100%"}]
103 | [:&__submit
104 | {:padding "0 16px"
105 | :justify-self :start}]]))])
106 |
107 | (defn root []
108 | (let [ex (rf/subscribe [:subs.query/examples])]
109 | (fn []
110 | [:div (vu/bem :q-form (if @ex :examples))
111 | q-form-styles
112 | [:div.q-form__editor
113 | ^{:key @-sub-editor-key}
114 | [q-editor/root]]
115 | [:div.q-form__time-controls
116 | [time-controls/root {:ui/layout :ui.layout/column}]]
117 | (if @ex
118 | [:div.q-form__examples
119 | [:div.examples-wrapper
120 | [query-examples/root]]])
121 | [:div.q-form__submit
122 | (let [qa @-sub-query-analysis]
123 | [comps/button-bordered
124 | {:on-click on-submit
125 | :css-mods ["bright" (if-not qa "inactive")]
126 | :text "Run Query"}])]])))
127 |
--------------------------------------------------------------------------------
/src/crux_ui/views/query/time_controls.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.query.time-controls
2 | (:require [garden.core :as garden]
3 | [garden.stylesheet :as gs]
4 | [crux-ui.views.commons.datepicker-native :as ndt]
5 | [crux-ui.views.query.datepicker-slider :as sdt]
6 | [re-frame.core :as rf]
7 | [reagent.core :as r]
8 | [crux-ui.functions :as f]
9 | [crux-ui.views.functions :as vu]))
10 |
11 | (def -sub-time (rf/subscribe [:subs.query/time]))
12 | (def -sub-vt (rf/subscribe [:subs.query.time/vt]))
13 | (def -sub-tt (rf/subscribe [:subs.query.time/tt]))
14 | (def -sub-vtc (rf/subscribe [:subs.query.time/vtc]))
15 | (def -sub-ttc (rf/subscribe [:subs.query.time/ttc]))
16 |
17 | (defn on-time-commit [^keyword time-type ^js/Date time]
18 | (rf/dispatch [:evt.ui.query/time-commit time-type time]))
19 |
20 | (def on-time-commit-debounced
21 | (f/debounce on-time-commit 1000))
22 |
23 |
24 | (defn on-vt-change [d]
25 | (println :on-vt-change d)
26 | #_(on-time-commit-debounced :time/vt d)
27 | (rf/dispatch [:evt.ui.query/time-change :time/vt d]))
28 |
29 | (defn on-vt-commit [d]
30 | (println :on-vt-commit d)
31 | (on-time-commit :time/vt d))
32 |
33 | (defn on-tt-commit [d]
34 | (on-time-commit :time/tt d))
35 |
36 | (defn on-tt-change [d]
37 | #_(on-time-commit-debounced :time/tt d)
38 | (rf/dispatch [:evt.ui.query/time-change :time/tt d]))
39 |
40 |
41 | (def ^:private time-controls-styles
42 | [:style
43 | (garden/css
44 | [:.time-controls
45 | {:display :flex
46 | :flex-direction :column
47 | :justify-content :space-between}
48 | [:&__item
49 | {:margin "8px 0"}]
50 | [:&__switcher
51 | {:margin-top :16px}]]
52 | (gs/at-media {:min-width :1000px}
53 | [:.time-controls--column
54 | {:flex-direction :row}]))])
55 |
56 | (def ^:private -sub-bp-375 (rf/subscribe [:subs.ui.responsive-breakpoints/width-lt-375]))
57 |
58 | (defn native-pickers [layout]
59 | [:<>
60 | [:div.time-controls__item
61 | [ndt/picker
62 | {:label "Valid time"
63 | :ui/layout layout
64 | :value @-sub-vt
65 | :on-change on-vt-change}]]
66 | [:div.time-controls__item
67 | [ndt/picker
68 | {:label (if @-sub-bp-375 "Tx time" "Transaction time")
69 | :ui/layout layout
70 | :value @-sub-tt
71 | :on-change on-tt-change}]]])
72 |
73 |
74 | (defn range-pickers []
75 | (let [active-picker (r/atom ::pickers-vt)
76 | toggle {::pickers-vt ::pickers-tt
77 | ::pickers-tt ::pickers-vt}
78 | toggle-time #(swap! active-picker toggle)]
79 | (fn []
80 | (if (= ::pickers-vt @active-picker)
81 | [:<>
82 | [sdt/root-simple
83 | {:label "Valid time"
84 | :value @-sub-vtc
85 | :on-change-complete on-vt-commit
86 | :on-change on-vt-change}]
87 | [:label.time-controls__switcher
88 | {:on-click toggle-time} "Tx time: " (str @-sub-tt)]]
89 | [:<>
90 | [sdt/root-simple
91 | {:label "Tx time"
92 | :value @-sub-ttc
93 | :on-change-complete on-tt-commit
94 | :on-change on-tt-change}]
95 | [:label.time-controls__switcher
96 | {:on-click toggle-time} "Valid time: " (str @-sub-vt)]]))))
97 |
98 |
99 | (defn root [{:ui/keys [layout]}]
100 | [:div {:class (vu/bem-str :time-controls layout)}
101 |
102 | #_sdt/style
103 | ndt/style
104 | time-controls-styles
105 | [native-pickers layout]
106 | #_[range-pickers]])
107 |
108 |
--------------------------------------------------------------------------------
/src/crux_ui/views/query_perf.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.query-perf
2 | (:require [garden.core :as garden]
3 | [crux-ui.views.charts.query-perf-plots :as perf-plots]
4 | [cljs.reader :as edn]))
5 |
6 |
7 | (def plots-data
8 | (if-let [script (js/document.getElementById "plots-data")]
9 | (edn/read-string (.-textContent script))
10 | (println :plots-data-not-found)))
11 |
12 | (def q1-data
13 | {:title "Simple query execution time"
14 | :plain (:q1 plots-data)
15 | :with-cache (:q1-with-cache plots-data)})
16 |
17 | (def q3-data
18 | {:title "Query with joins execution time"
19 | :plain (:q3 plots-data)
20 | :with-cache (:q3-with-cache plots-data)})
21 |
22 |
23 | (def ^:private root-styles
24 | [:style
25 | (garden/css
26 | [[:a]])])
27 |
28 | (defn root []
29 | [:div.query-perf
30 | [:div.query-perf__plot1
31 | [perf-plots/root q1-data]]
32 | [:div.query-perf__plot1
33 | [perf-plots/root q3-data]]])
34 |
--------------------------------------------------------------------------------
/src/crux_ui/views/query_ui.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.query-ui
2 | (:require [re-frame.core :as rf]
3 | [garden.core :as garden]
4 | [garden.stylesheet :as gs]
5 | [crux-ui.views.query.form :as q-form]
6 | [crux-ui.views.query.output :as q-output]
7 | [crux-ui.views.style :as s]
8 | [crux-ui.views.functions :as vu]))
9 |
10 |
11 | (def ^:private query-ui-styles
12 | [:style
13 | (garden/css
14 | [:.query-ui
15 | {:font-size :16px
16 | :border-radius :2px
17 | :margin "0 1rem 8px"
18 | :border s/q-ui-border
19 | :width :100%
20 | :overflow :hidden
21 | :height "calc(100% - 8px)"
22 | :display :grid
23 | :place-items :stretch
24 | :grid-template
25 | "'form output' 100% / minmax(512px, 38%) 62%"}
26 | [:&--query
27 | {:grid-template
28 | "'form output' 100% / 1fr 0px"}
29 | [">.query-ui__output"
30 | {:display :none}]]
31 |
32 | [:&--output
33 | {:grid-template
34 | "'form output' 100% / 0 100%"}
35 | [">.query-ui__form"
36 | {:display :none}]]
37 |
38 | [:&--horizontal
39 | {:grid-template
40 | "'output' calc(100% - 330px)
41 | 'form' 330px"}]
42 |
43 | [:&__form
44 | {:padding "0px 0"
45 | :overflow :hidden
46 | :grid-area :form}]
47 |
48 | [:&--form-minimised
49 | {:grid-template
50 | "'form output' 100% / minmax(0px, 0%) 100%"}]
51 |
52 | [:&__output
53 | {:padding "0px 0"
54 | :grid-area :output
55 | :border-left s/q-ui-border}]]
56 |
57 | (gs/at-media {:max-width :1000px}
58 | [:.query-ui
59 | {:margin 0}]))])
60 |
61 | (def ^:private mode->class-mod
62 | {:ui.display-mode/query "query"
63 | :ui.display-mode/output "output"})
64 |
65 | (def -sub-display-mode (rf/subscribe [:subs.ui/display-mode]))
66 | (def -sub-show-form (rf/subscribe [:subs.db.ui/show-form?]))
67 |
68 | (defn query-ui []
69 | [:div
70 | (vu/bem :query-ui
71 | (if-not @-sub-show-form :form-minimised)
72 | (mode->class-mod @-sub-display-mode))
73 | query-ui-styles
74 | [:div.query-ui__form
75 | [q-form/root]]
76 | [:div.query-ui__output
77 | [q-output/root]]])
78 |
--------------------------------------------------------------------------------
/src/crux_ui/views/second_layer.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.second-layer
2 | (:require [crux-ui.views.settings :as settings]
3 | [crux-ui.views.sidebar :as sidebar]
4 | [crux-ui.views.overview :as overview]
5 | [garden.core :as garden]
6 | [garden.stylesheet :as gs]
7 | [re-frame.core :as rf]))
8 |
9 | (def ^:private main-pane-views
10 | {:db.ui.second-layer.main-pane/overview overview/root
11 | :db.ui.second-layer.main-pane/settings settings/root})
12 |
13 | (def ^:private root-styles
14 | [:style
15 | (garden/css
16 | [:.second-layer
17 | {:width :100%
18 | :height :100%
19 | :display :grid
20 | :grid-gap :16px
21 | :grid-template "'side main' 1fr / 264px 1fr"}
22 | [:&__side
23 | {:grid-area :side
24 | :overflow :scroll}]
25 | [:&__main
26 | {:grid-area :main
27 | :background :white
28 | :border-radius :2px
29 | :height :100%
30 | :overflow :scroll}]]
31 | (gs/at-media {:max-width :1000px}
32 | [:.second-layer
33 | {:grid-template "'main' 1fr / 1fr"}
34 | [:&__side
35 | {:grid-area :main}]
36 | [:&__main
37 | {:grid-area :main}]]))])
38 |
39 | (defn autoclose-on-fader-click [evt]
40 | (let [target (.-target evt)]
41 | (if (= "second-layer" (.-id target))
42 | (rf/dispatch [:evt.ui.second-layer/toggle]))))
43 |
44 | (defn root []
45 | (let [-sub-second-layer-main-pane (rf/subscribe [:subs.db.ui.second-layer/main-pane])]
46 | (fn []
47 | [:div#second-layer.second-layer {:on-click autoclose-on-fader-click}
48 | root-styles
49 | [:div.second-layer__side [sidebar/root]]
50 | (let [mpv (main-pane-views @-sub-second-layer-main-pane)]
51 | (if mpv
52 | [:div.second-layer__main [mpv]]))])))
53 |
54 |
--------------------------------------------------------------------------------
/src/crux_ui/views/settings.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.settings
2 | (:require [garden.core :as garden]
3 | [garden.stylesheet :as gs]
4 | [reagent.core :as r]
5 | [re-frame.core :as rf]
6 | [crux-ui.views.commons.input :as input]
7 | [crux-ui.views.commons.form-line :as fl]
8 | [crux-ui.subs]
9 | [crux-ui.functions :as f]
10 | [crux-ui.views.output.edn :as output-edn]
11 | [crux-ui.views.commons.tiny-components :as comps]
12 | [crux-ui.views.commons.dom :as dom]))
13 |
14 |
15 | (def ^:private root-styles
16 | [:style
17 | (garden/css
18 | [:.g-spacer-w-40
19 | {:height :16px
20 | :display :inline-block
21 | :width :40px}]
22 | [:.g-container
23 | {:height :100%
24 | :overflow :hidden}
25 | [:&__content
26 | {:overflow :auto
27 | :height :100%}]]
28 | [:.g-sticky
29 | {:position :sticky
30 | :top :0px}]
31 | [:.line
32 | {}
33 | [:&__label
34 | {:margin-bottom :4px}]
35 | [:&__control
36 | {:max-width :300px}]]
37 | [:.settings
38 | {:padding "0px 32px 32px"}
39 | [:&__title
40 | {:padding-top :16px
41 | :background :white}]
42 | [:&__line
43 | {:margin-top "40px"}
44 | [:&--submit
45 | {}]]
46 | [:&__status
47 | {:margin-top "24px"}
48 | [:>h3
49 | {:font-weight 400
50 | :font-size :20px
51 | :margin-bottom :8px}]]])])
52 |
53 | (defn- on-prop-change [prop-name {v :value :as change-complete-evt}]
54 | (rf/dispatch
55 | [:evt.db/prop-change
56 | {:evt/prop-name prop-name
57 | :evt/value v}]))
58 |
59 | (defn- on-host-change [{v :value :as change-complete-evt}]
60 | (rf/dispatch [:evt.db/host-change v]))
61 |
62 | (defn- on-commit [arg]
63 | (println "changes commit" (pr-str arg))
64 | (rf/dispatch [:evt.db/props-change arg]))
65 |
66 | (defn close []
67 | (rf/dispatch [:evt.ui.second-layer.main-pane/cancel]))
68 |
69 | (defn root []
70 | (let [-sub-settings (rf/subscribe [:subs.sys/settings])
71 | -sub-host-status (rf/subscribe [:subs.sys.host/status])
72 | -local-atom (r/atom @-sub-settings)
73 | commit-changes #(on-commit (dissoc @-local-atom :db.sys.host/status))
74 |
75 | on-prop-change
76 | (fn on-prop-change [prop-name {v :value :as change-complete-evt}]
77 | (swap! -local-atom assoc prop-name v))
78 |
79 | on-host-change-internal
80 | (fn [{v :value :as evt}]
81 | (on-host-change evt)
82 | (on-prop-change :db.sys/host evt))]
83 |
84 | (fn []
85 | [:div.settings
86 | root-styles
87 | [:h1.settings__title.g-sticky "Settings"]
88 |
89 | [:div.settings__line
90 | [fl/line
91 | {:label "Attribute history data points limit per entity"
92 | :control
93 | [input/text :ui.settings/attr-qlimit
94 | {:on-change-complete (r/partial on-prop-change :db.query.attr-history/docs-limit)
95 | :parse-fn dom/parse-int-or-nil
96 | :value (:db.query.attr-history/docs-limit @-local-atom)}]}]]
97 |
98 | [:div.settings__line
99 | [fl/line
100 | {:label "Query results limit"
101 | :control
102 | [input/text :ui.settings/qlimit
103 | {:on-change-complete (r/partial on-prop-change :db.query/limit)
104 | :parse-fn dom/parse-int-or-nil
105 | :value (:db.query/limit @-local-atom)}]}]]
106 |
107 | [:div.settings__line
108 | [fl/line
109 | {:label "Crux HTTP-Server Host and port"
110 | :control
111 | [input/text :ui.settings/host
112 | {:on-change-complete on-host-change-internal
113 | :value (:db.sys/host @-local-atom)}]}]
114 | ^{:key @-sub-host-status}
115 | [:pre.settings__status
116 | (let [s @-sub-host-status]
117 | (if s
118 | [:<>
119 | [:h3 "connection successful"]
120 | [output-edn/simple-print s]]
121 | [:h3 "connection failed"]))]]
122 |
123 | [:div.settings__line.settings__line--submit
124 | [comps/button-cta
125 | {:text "Apply"
126 | :on-click commit-changes}]
127 | [:div.g-spacer-w-40]
128 | [comps/button-textual
129 | {:text "Cancel"
130 | :on-click close}]]])))
131 |
132 |
133 |
134 |
135 |
--------------------------------------------------------------------------------
/src/crux_ui/views/sidebar.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.sidebar
2 | (:require [garden.core :as garden]
3 | [garden.stylesheet :as gs]
4 | [clojure.string :as s]
5 | [crux-ui.views.commons.cube-svg :as cube]
6 | [crux-ui.views.functions :as vf]
7 | [crux-ui.routes :as routes]
8 | [crux-ui.views.commons.css-logo :as css-logo]
9 | [re-frame.core :as rf]
10 | [crux-ui.views.commons.tiny-components :as comps]
11 | [crux-ui.config :as cfg]
12 | [reagent.core :as r]))
13 |
14 |
15 | (def ^:private sidebar-styles
16 | [:style
17 | (garden/css
18 | [:.sidebar
19 | {:width :100%
20 | :min-height :100%
21 | :background :white
22 | :border-radius :2px
23 | :padding "0px 0px 48px"}
24 | [:&__item
25 | {:cursor :pointer
26 | :padding "16px 24px"}
27 | [:&:hover
28 | {:background "hsla(0, 0%, 0%, 0.1)"}]
29 | [:&--logo
30 | {:padding "12px 16px"}]]]
31 | (gs/at-media {:max-width :500px}
32 | [:.css-logo__text
33 | {:display :none}])
34 | (gs/at-media {:max-width :375px}
35 | [:.css-logo__cube
36 | {:width :40px
37 | :height :40px
38 | :flex "0 0 40px"}]))])
39 |
40 |
41 | (defn -item [{:keys [dispatch] :as attrs} contents]
42 | [:div.sidebar__item
43 | (cond-> attrs
44 | dispatch (-> (assoc :on-click (r/partial rf/dispatch dispatch))
45 | (dissoc :dispatch)))
46 | contents])
47 |
48 |
49 | (defn root []
50 | (let [-sub-examples (rf/subscribe [:subs.query/examples])
51 | -sub-qmap? (rf/subscribe [:subs.query/is-query-map?])]
52 | (fn []
53 | [:div.sidebar
54 | sidebar-styles
55 | [-item {:class "sidebar__item--logo"
56 | :dispatch [:evt.ui.second-layer/toggle]} [css-logo/root]]
57 | [-item {:dispatch [:evt.ui/fullscreen]} "Fullscreen mode"]
58 | (if @-sub-qmap?
59 | [-item {:dispatch [:evt.ui/toggle-polling]} "Toggle polling"])
60 | [-item {:dispatch [:evt.ui.sidebar/show-settings]} "Settings"]
61 | [-item {:dispatch [:evt.ui.sidebar/show-overview]} "Console Overview"]
62 | (if-not @-sub-examples
63 | [-item {:dispatch [:evt.ui/restore-examples]} "Restore Examples"])
64 | [-item {:dispatch [:evt.ui/import-examples]} "Import Examples"]
65 | [-item {} [comps/link-outer cfg/url-docs "Crux Docs"]]
66 | [-item {} [comps/link-outer cfg/url-chat "Crux Chat"]]
67 | [-item {} [comps/link-mailto cfg/url-mail]]])))
68 |
69 | (comment
70 | (routes/path-for :rd/query-ui)
71 | (routes/path-for :rd/settings))
72 |
--------------------------------------------------------------------------------
/src/crux_ui/views/style.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui.views.style)
2 |
3 | (def color-placeholder "grey")
4 | (def color-font-secondary "hsl(0,0%,55%)")
5 | (def q-ui-border "1px solid hsl(0,0%,85%)")
6 |
7 | (defn hsl
8 | ([{:keys [h s l a] :as conf}]
9 | (if a
10 | (str "hsla(" h "," s "%," l "%," a ")")
11 | (str "hsl(" h "," s "%," l "%)")))
12 | ([h s l]
13 | (str "hsl(" h "," s "%," l "%)"))
14 | ([h s l a]
15 | (str "hsl(" h "," s "%," l "%," a ")")))
16 |
--------------------------------------------------------------------------------
/src/crux_ui_lib/functions.cljc:
--------------------------------------------------------------------------------
1 | (ns crux-ui-lib.functions
2 | #?(:cljs (:require [cljs.reader :as edn]))
3 | #?(:clj (:require [clojure.tools.reader.edn :as edn])))
4 |
5 | (defn- single-int-or-bool? [v]
6 | (let [v1 (first v)]
7 | (and (= 1 (count v))
8 | (or (nat-int? v1)
9 | (boolean? v1)))))
10 |
11 | (defn- normalize-query-part [v]
12 | "this strips out limit/offset/full-results into singular variable"
13 | (if (single-int-or-bool? v)
14 | (first v)
15 | (vec v)))
16 |
17 | ; todo critical review target
18 |
19 | (defn normalize-query [q]
20 | (cond
21 | (vector? q) (into {} (for [[[k] v] (->> (partition-by keyword? q)
22 | (partition-all 2))]
23 | [k (normalize-query-part v)]))
24 | (string? q) (if-let [q (try
25 | (edn/read-string q)
26 | #?(:cljs (catch :default e e)
27 | :clj (catch Exception e)))]
28 | (normalize-query q)
29 | q)
30 | :else
31 | q))
32 | ; adapted from https://github.com/juxt/crux/blob/3368595d7fcaec726b1a602a9ec75e325b49ecd6/src/crux/query.clj#L1013
33 |
--------------------------------------------------------------------------------
/src/crux_ui_lib/http_functions.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-ui-lib.http-functions
2 | (:require [promesa.core :as p :include-macros true]
3 | [clojure.edn :as edn]))
4 |
5 | (defmulti fetch type)
6 |
7 | (defmethod fetch :default [{:keys [method url] :as opts}]
8 | (if method (assert (#{:post :get} method) (str "Unsupported HTTP method: " (:method opts))))
9 | (let [opts (-> opts
10 | (update :method (fnil name :get))
11 | (assoc :referrerPolicy "origin"))]
12 | (p/alet [fp (js/fetch url (clj->js opts))
13 | resp (p/await fp)
14 | headers (.-headers resp)
15 | content-type (.get headers "Content-Type")
16 | text (p/await (.text resp))]
17 | {:body text
18 | :status (.-status resp)
19 | :headers {:content-type content-type}})))
20 |
21 | (defmethod fetch js/String [url]
22 | (fetch {:url url :method :get}))
23 |
24 | (defn fetch-edn [prms]
25 | (p/map #(update % :body edn/read-string) (fetch prms)))
26 |
27 |
--------------------------------------------------------------------------------
/src/crux_ui_server/config.clj:
--------------------------------------------------------------------------------
1 | (ns crux-ui-server.config
2 | (:require [clojure.java.io :as io]
3 | [clojure.edn :as edn])
4 | (:import (java.io FileNotFoundException PushbackReader IOException File)))
5 |
6 | (def ^:private defaults
7 | {:console/frontend-port 5000
8 | :console/embed-crux false
9 | :console/routes-prefix "/console"
10 | :console/crux-node-url-base "localhost:8080/crux"
11 | :console/hide-features #{:features/attribute-history}
12 | :console/crux-http-port 8080})
13 |
14 | (defn load-edn
15 | "Load edn from an io/reader source (filename or io/resource)."
16 | [source]
17 | (try
18 | (with-open [r (io/reader source)]
19 | (edn/read (PushbackReader. r)))
20 | (catch IOException e
21 | (printf "Couldn't open '%s': %s\n" source (.getMessage e)))
22 | (catch RuntimeException e
23 | (printf "Error parsing edn file '%s': %s\n" source (.getMessage e)))))
24 |
25 | (def ^:private default-conf-name "crux-console-conf.edn")
26 |
27 | (defn- try-load-conf [^String user-conf-filename]
28 | (if (and user-conf-filename
29 | (not (.exists (io/as-file user-conf-filename))))
30 | (throw (FileNotFoundException. (str "file " user-conf-filename " not found"))))
31 | (let [conf-file-name (or user-conf-filename default-conf-name)
32 | file ^File (io/as-file conf-file-name)]
33 | (when (.exists file)
34 | (println "loading conf from " user-conf-filename)
35 | (load-edn file))))
36 |
37 | (defn- fix-key [[k v]]
38 | [(cond-> k
39 | (and (string? k) (.startsWith k "--"))
40 | (subs 2))
41 | v])
42 |
43 | (defn- parse-args [args-map]
44 | (let [w-norm-keys (into {} (map fix-key args-map))
45 | w-kw-keys (into {} (map (fn [[k v]] [(keyword "console" k) v]) w-norm-keys))
46 | w-parsed (into {} (map (fn [[k v]] [k (cond-> v (string? v) read-string)]) w-kw-keys))]
47 | w-parsed))
48 |
49 | (assert
50 | (= {:console/frontend-port 5000,
51 | :console/embed-crux false,
52 | :console/crux-http-port 8080}
53 | (parse-args
54 | {"--frontend-port" "5000"
55 | "--embed-crux" "false"
56 | "--crux-http-port" "8080"})))
57 |
58 | (defn calc-conf [args]
59 | (let [conf-filename (get args "--conf-file")
60 | args (dissoc args "--conf-file")]
61 | (merge defaults
62 | (try-load-conf conf-filename)
63 | (parse-args args))))
64 |
65 |
--------------------------------------------------------------------------------
/src/crux_ui_server/crux_auto_start.clj:
--------------------------------------------------------------------------------
1 | (ns crux-ui-server.crux-auto-start)
2 |
3 | (def ^:private node-opts
4 | {:crux.node/topology :crux.standalone/topology
5 | :crux.standalone/event-log-dir "data/eventlog-1"
6 | :crux.kv/kv-backend :crux.kv.rocksdb/kv
7 | :crux.kv/db-dir "data/db-dir-1"})
8 |
9 | (def ^:private http-opts
10 | {:server-port 8080
11 | :cors-access-control
12 | [:access-control-allow-origin [#".*"]
13 | :access-control-allow-headers
14 | ["X-Requested-With" "X-Custom-Header" "Content-Type" "Cache-Control"
15 | "Origin" "Accept" "Authorization"]
16 | :access-control-allow-methods [:get :options :head :post]]})
17 |
18 | (defn try-start-servers [{:keys [console/embed-crux console/crux-http-port]}]
19 | (try
20 | (let [start-node (requiring-resolve 'crux.api/start-node)
21 | start-http-server (requiring-resolve 'crux.http-server/start-http-server)
22 | node (start-node node-opts)
23 | http-opts (assoc http-opts :server-port crux-http-port)
24 | crux-http-server (start-http-server node http-opts)]
25 | {:crux-node node
26 | :crux-http-server crux-http-server})
27 | (catch ClassNotFoundException e
28 | (println "failed to load crux.http-server or crux.api, are they on the classpath?")
29 | {})))
30 |
--------------------------------------------------------------------------------
/src/crux_ui_server/generate_web_manifest.clj:
--------------------------------------------------------------------------------
1 | (ns crux-ui-server.generate-web-manifest)
2 |
3 | (defn generate-str [{:console/keys [routes-prefix] :as conf}]
4 | (str "{
5 | \"name\" : \"Crux Console\",
6 | \"short_name\" : \"Crux Console\",
7 | \"start_url\" : \"" routes-prefix "/app\",
8 | \"display\" : \"standalone\",
9 | \"theme_color\" : \"hsl(32, 91%, 54%)\",
10 | \"background_color\" : \"rgb(167, 191, 232)\",
11 | \"description\" : \"Console for Crux to see data in perspectives\",
12 | \"icons\" : [{
13 | \"src\" : \"" routes-prefix "/static/img/cube-on-white-120.png\",
14 | \"sizes\" : \"120x120\",
15 | \"type\" : \"image/png\"
16 | }, {
17 | \"src\" : \"" routes-prefix "/static/img/cube-on-white-192.png\",
18 | \"sizes\" : \"192x192\",
19 | \"type\" : \"image/png\"
20 | }, {
21 | \"src\" : \"" routes-prefix "/static/img/cube-on-white-512.png\",
22 | \"sizes\" : \"512x512\",
23 | \"type\" : \"image/png\"
24 | }]
25 | }"))
26 |
--------------------------------------------------------------------------------
/src/crux_ui_server/main.clj:
--------------------------------------------------------------------------------
1 | (ns crux-ui-server.main
2 | "Routing and bootstrapping"
3 | (:require [aleph.http :as http]
4 | [bidi.bidi :as bidi]
5 | [crux-ui-server.crux-auto-start :as crux-auto-start]
6 | [crux-ui-server.config :as config]
7 | [crux-ui-server.generate-web-manifest :as manifest]
8 | [clojure.tools.logging :as log]
9 | [crux-ui-server.pages :as pages]
10 | [clojure.java.io :as io]
11 | [clojure.string :as s]
12 | [clojure.pprint :as pprint])
13 | (:gen-class)
14 | (:import (java.io Closeable)))
15 |
16 | (defonce closables (atom {}))
17 |
18 | (defonce config (atom nil))
19 | (defonce routes (atom nil))
20 |
21 | (defn calc-routes [routes-prefix]
22 | [routes-prefix
23 | [["" ::home]
24 | ["/" ::home]
25 | ["/app"
26 | {"" ::console
27 | "/output" ::console
28 | ["/output/" :rd/out-tab] ::console
29 | ["/" :rd/tab] ::console}]
30 | ["/query-perf" ::query-perf]
31 | ["/service-worker-for-console.js" ::service-worker-for-console]
32 | ["/manifest.json" ::web-app-manifest]
33 | ["/static/"
34 | {true ::static}]
35 | [true ::not-found]]])
36 |
37 |
38 | (comment
39 | (bidi/match-route @routes "/console")
40 | (bidi/match-route @routes "/console/app")
41 | (reset! routes (calc-routes "/console")))
42 |
43 |
44 | (defmulti handler
45 | (fn [{:keys [uri] :as req}]
46 | (println ::uri uri)
47 | (some-> (bidi/match-route @routes uri) :handler)))
48 |
49 | (defmethod handler ::home [req]
50 | {:status 301
51 | :headers {"location" (bidi/path-for @routes ::console)}})
52 |
53 | (defmethod handler ::console [req]
54 | {:status 200
55 | :headers {"content-type" "text/html"}
56 | :body (pages/gen-console-page req @config)})
57 |
58 | (defmethod handler ::service-worker-for-console [req]
59 | {:status 200
60 | :headers {"content-type" "text/javascript"}
61 | :body (pages/gen-service-worker req @config)})
62 |
63 | (defmethod handler ::web-app-manifest [req]
64 | {:status 200
65 | :headers {"content-type" "application/json"}
66 | :body (manifest/generate-str @config)})
67 |
68 | (defn uri->mime-type [uri]
69 | (cond
70 | (re-find #".css$" uri) "text/css"
71 | (re-find #".js$" uri) "text/javascript"
72 | (re-find #".json$" uri) "application/json"
73 | (re-find #".png$" uri) "image/png"
74 | (re-find #".jpe?g$" uri) "image/jpeg"
75 | (re-find #".svg$" uri) "image/svg"
76 | :else "text/plain"))
77 |
78 | (defmethod handler ::static [{:keys [uri] :as req}]
79 | (let [relative-uri (s/replace uri (re-pattern (str "^" @pages/routes-prefix)) "")
80 | relative-uri (s/replace relative-uri #"^/" "")
81 | resource (io/resource relative-uri)]
82 | (if resource
83 | (let [stream (io/input-stream resource)
84 | mime-type (uri->mime-type uri)]
85 | {:status 200
86 | :headers {"content-type" mime-type}
87 | :body stream})
88 | (log/warn "No resource for" uri))))
89 |
90 | (defmethod handler ::not-found [req]
91 | {:status 200
92 | :headers {"content-type" "text/plain"}
93 | :body "Not found"})
94 |
95 | (defmethod handler :default [req]
96 | {:status 200
97 | :headers {"content-type" "text/plain"}
98 | :body "Default handler : Not implemented"})
99 |
100 | (defn stop-servers []
101 | (when-let [closables' (not-empty @closables)]
102 | (println "stopping console server")
103 | (doseq [^Closeable closable (vals closables')]
104 | (.close closable))
105 | (reset! closables {})))
106 |
107 | (defn- start-servers [{:keys [console/frontend-port console/embed-crux] :as conf}]
108 | (swap! closables assoc :frontend (http/start-server handler {:port frontend-port}))
109 | (if embed-crux
110 | (swap! closables merge (crux-auto-start/try-start-servers conf))))
111 |
112 |
113 | (defn -main
114 | "Accepted args
115 | --frontend-port 5000
116 | --conf-file crux-console-conf.edn
117 | --embed-crux false
118 | --crux-http-server-port 8080"
119 | [& {:as args}]
120 | (.addShutdownHook (Runtime/getRuntime) (Thread. #'stop-servers))
121 | (let [conf (config/calc-conf args)]
122 | (reset! routes (calc-routes (:console/routes-prefix conf)))
123 | (reset! config conf)
124 | (reset! pages/routes-prefix (:console/routes-prefix conf))
125 | (println
126 | (str "starting console server w conf: \n"
127 | (with-out-str (pprint/pprint conf))))
128 | (start-servers conf)))
129 |
--------------------------------------------------------------------------------
/src/crux_ui_server/pages.clj:
--------------------------------------------------------------------------------
1 | (ns crux-ui-server.pages
2 | (:require [crux-ui-server.preloader :as preloader]
3 | [page-renderer.api :as pr]
4 | [clojure.java.io :as io])
5 | (:import (clojure.lang IPersistentMap)))
6 |
7 | (def routes-prefix (atom ""))
8 |
9 | (defn console-assets-frame ^IPersistentMap
10 | [^IPersistentMap config]
11 | (let [routes-prefix (:console/routes-prefix config)]
12 | {:title "Crux Console"
13 | :lang "en"
14 | :theme-color "hsl(32, 91%, 54%)"
15 | :og-image (str routes-prefix "/static/img/crux-logo.svg")
16 | :link-apple-icon (str routes-prefix "/static/img/cube-on-white-192.png")
17 | :link-apple-startup-image (str routes-prefix "/static/img/cube-on-white-512.png")
18 | :link-image-src (str routes-prefix "/static/img/cube-on-white-512.png")
19 | :service-worker (str routes-prefix "/service-worker-for-console.js")
20 | :favicon (str routes-prefix "/static/img/cube-on-white-120.png")
21 |
22 | :doc-attrs
23 | {:data-routes-prefix routes-prefix
24 | :data-hide-features (pr-str (:console/hide-features config))
25 | :data-crux-base-url (:console/crux-node-url-base config)
26 | :data-crux-http-port (str (:console/crux-http-port config))}
27 |
28 | :sw-default-url (str routes-prefix "/app")
29 | :stylesheet-async
30 | [(str routes-prefix "/static/styles/reset.css")
31 | (str routes-prefix "/static/styles/react-input-range.css")
32 | (str routes-prefix "/static/styles/react-ui-tree.css")
33 | (str routes-prefix "/static/styles/codemirror.css")
34 | (str routes-prefix "/static/styles/monokai.css")
35 | (str routes-prefix "/static/styles/eclipse.css")]
36 | :script (str routes-prefix "/static/crux-ui/compiled/main.js")
37 | :manifest (str routes-prefix "/manifest.json")
38 | :head-tags
39 | [[:style#_stylefy-constant-styles_]
40 | [:style#_stylefy-styles_]
41 | [:meta {:name "google" :content "notranslate"}]]
42 | :body [:body [:div#app preloader/root]]}))
43 |
44 | (defn gen-console-page [req config]
45 | (pr/render-page (console-assets-frame config)))
46 |
47 | (defn gen-service-worker [req config]
48 | (pr/generate-service-worker (console-assets-frame config)))
49 |
50 | (defn q-perf-page [req]
51 | (pr/render-page
52 | {:title "Crux Console"
53 | :lang "en"
54 | :doc-attrs {:data-scenario :perf-plot}
55 | :stylesheet-async (str @routes-prefix "/static/styles/reset.css")
56 | :og-image (str @routes-prefix "/static/img/crux-logo.svg")
57 | :script (str @routes-prefix "/static/crux-ui/compiled/main-perf.js")
58 | :head-tags [[:script {:id "plots-data" :type "text/edn"}
59 | (slurp (io/resource "static/plots-data.edn"))]]
60 | :body [:body [:div#app preloader/root]]}))
61 |
62 | (defn gen-home-page [req]
63 | (pr/render-page
64 | {:title "Crux Standalone Demo with HTTP"
65 | :lang "en"
66 | :og-image (str @routes-prefix "/static/img/crux-logo.svg")
67 | :body
68 | [:body
69 | [:header
70 | [:div.nav
71 | [:div.logo {:style {:opacity "0"}}
72 | [:a {:href (str @routes-prefix "/")}
73 | [:img.logo-img {:src (str @routes-prefix "/static/img/crux-logo.svg")}]]]
74 | [:div.n0
75 | [:a {:href "https://juxt.pro/crux/docs/index.html"} [:span.n "Documentation"]]
76 | [:a {:href "https://juxt-oss.zulipchat.com/#narrow/stream/194466-crux"} [:span.n "Community Chat"]]
77 | [:a {:href "mailto:crux@juxt.pro"} [:span.n "crux@juxt.pro"]]]]]
78 | [:div {:style {:text-align "center" :width "100%" :margin-top "6em"}}
79 | [:div.splash {:style {:max-width "25vw" :margin-left "auto" :margin-right "auto"}}
80 | [:a {:href (str @routes-prefix "/")}
81 | [:img.splash-img {:src (str @routes-prefix "/static/img/crux-logo.svg")}]]]
82 |
83 | [:div {:style {:height "4em"}}]
84 | [:h3 "You should now be able to access this Crux standalone demo node using the HTTP API via localhost:8080"]]
85 |
86 | [:div#app]]}))
87 |
--------------------------------------------------------------------------------
/src/crux_ui_server/preloader.clj:
--------------------------------------------------------------------------------
1 | (ns crux-ui-server.preloader)
2 |
3 | (def ^:private style
4 | [:style
5 | "html, body, #app, .preloader {
6 | width: 100%;
7 | height: 100%;
8 | }
9 |
10 | .preloader {
11 | display: flex;
12 | align-items: center;
13 | justify-content: center;
14 | }
15 |
16 | .scene {
17 | width: 200px;
18 | height: 200px;
19 | perspective: 600px;
20 | }
21 |
22 | .cube {
23 | width: 100%;
24 | height: 100%;
25 | position: relative;
26 | transform-style: preserve-3d;
27 | transform: translateZ(-100px) rotateX(-45deg) rotateY(-45deg);
28 | }
29 |
30 | .cube__face {
31 | position: absolute;
32 | width: 200px;
33 | height: 200px;
34 | border: 1px solid orange;
35 | }
36 |
37 | .cube__face--front { transform: rotateY( 0deg); }
38 | .cube__face--right { transform: rotateY( 90deg); }
39 | .cube__face--back { transform: rotateY(180deg); }
40 | .cube__face--left { transform: rotateY(-90deg); }
41 | .cube__face--top { transform: rotateX( 90deg); }
42 | .cube__face--bottom { transform: rotateX(-90deg); }
43 |
44 |
45 | .cube__face--front { transform: rotateY( 0deg) translateZ(100px); }
46 | .cube__face--right { transform: rotateY( 90deg) translateZ(100px); }
47 | .cube__face--back { transform: rotateY(180deg) translateZ(100px); }
48 | .cube__face--left { transform: rotateY(-90deg) translateZ(100px); }
49 | .cube__face--top { transform: rotateX( 90deg) translateZ(100px); }
50 | .cube__face--bottom { transform: rotateX(-90deg) translateZ(100px); }
51 |
52 | .cube__face.cube__face--back {}
53 |
54 | .cube__face {
55 | border-color: gray !important;
56 | }
57 |
58 |
59 | @keyframes pulse-bottom-left {
60 | 0% {
61 | border-bottom: 1px solid gray;
62 | border-left: 1px solid gray;
63 | }
64 | 25% {
65 | border-bottom: 1px solid orange;
66 | border-left: 1px solid orange;
67 | }
68 | 50% {
69 | border-left: 1px solid gray;
70 | border-top: 1px solid gray;
71 | }
72 | }
73 | @keyframes pulse-bottom-right {
74 | 0% {
75 | border-bottom: 1px solid gray;
76 | border-right: 1px solid gray;
77 | }
78 | 25% {
79 | border-bottom: 1px solid orange;
80 | border-right: 1px solid orange;
81 | }
82 | 50% {
83 | border-left: 1px solid gray;
84 | border-top: 1px solid gray;
85 | }
86 | }
87 | @keyframes pulse-top-right {
88 | 0% {
89 | border-top: 1px solid gray;
90 | border-right: 1px solid gray;
91 | }
92 | 25% {
93 | border-top: 1px solid orange;
94 | border-right: 1px solid orange;
95 | }
96 | 50% {
97 | border-left: 1px solid gray;
98 | border-top: 1px solid gray;
99 | }
100 | }
101 | @keyframes pulse-top-left {
102 | 0% {
103 | border-left: 1px solid gray;
104 | border-top: 1px solid gray;
105 | }
106 | 25% {
107 | border-left: 1px solid orange;
108 | border-top: 1px solid orange;
109 | }
110 | 50% {
111 | border-left: 1px solid gray;
112 | border-top: 1px solid gray;
113 | }
114 | }
115 |
116 |
117 | .cube__face--front {
118 | animation: pulse-top-right 8s 0s infinite, pulse-bottom-right 8s 2s infinite;
119 | }
120 | .cube__face--right {
121 | animation: pulse-top-left 8s 0s infinite, pulse-bottom-left 8s 2s infinite;
122 | }
123 | .cube__face--top {
124 | animation: pulse-bottom-right 8s 0s infinite, pulse-top-left 8s 6s infinite;
125 | }
126 | .cube__face--bottom {
127 | animation: pulse-top-right 8s 2s infinite, pulse-bottom-left 8s 4s infinite;
128 | }
129 | .cube__face--back {
130 | animation: pulse-bottom-right 8s 4s infinite, pulse-top-right 8s 6s infinite;
131 | }
132 | .cube__face--left {
133 | animation: pulse-bottom-left 8s 4s infinite, pulse-top-left 8s 6s infinite;
134 | }"])
135 |
136 | (def root
137 | [:div.preloader
138 | style
139 | [:div.scene
140 | (let [titles? false]
141 | [:div.cube
142 | [:div.cube__face.cube__face--front (if titles? "front")]
143 | [:div.cube__face.cube__face--back (if titles? "back")]
144 | [:div.cube__face.cube__face--right (if titles? "right")]
145 | [:div.cube__face.cube__face--left (if titles? "left")]
146 | [:div.cube__face.cube__face--top (if titles? "top")]
147 | [:div.cube__face.cube__face--bottom (if titles? "bottom")]])]])
148 |
--------------------------------------------------------------------------------
/src/dev.clj:
--------------------------------------------------------------------------------
1 | (ns dev
2 | (:require [clojure.java.io :as io]
3 | [crux-ui-server.main :as main]))
4 |
5 | (main/stop-servers)
6 | (main/-main)
7 |
8 | (comment
9 | (io/resource "/static/crux-ui/compiled/main.js")
10 | (main/handler {:uri "/static/crux-ui/compiled/main.js"}))
11 |
--------------------------------------------------------------------------------
/src/public/logo.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | background-color: #eee;
3 | height: 100%;
4 |
5 | font-family: sans-serif;
6 | }
7 |
8 | *, *:after, *:before {
9 | box-sizing: border-box;
10 | }
11 |
12 | body {
13 | display: flex;
14 | justify-content: center;
15 | align-items: center;
16 |
17 | margin: 0;
18 | padding: 1em;
19 | }
20 |
21 | .content {
22 | background-color: white;
23 | padding: 2rem;
24 |
25 | max-width: 960px;
26 | width: 50vw;
27 |
28 | margin: auto;
29 |
30 | box-shadow: 0 0 2em rgba(0, 0, 0, 0.4);
31 | border-radius: 5px;
32 |
33 | text-align: center;
34 | }
35 |
36 | #app {
37 | box-sizing: border-box;
38 | min-height: 50px;
39 | width: 100%;
40 | background-color: #efefef;
41 | box-shadow: 0 0 5px rgba(0, 0, 0, 0.4);
42 | padding: 1em;
43 | }
44 |
--------------------------------------------------------------------------------
/test/crux_console/query_analysis_test.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-console.query-analysis-test
2 | (:require [cljs.test :refer :all]
3 | [crux-ui.logic.query-analysis :as qa]))
4 |
5 | (def q
6 | '{:find [e p]
7 | :where [[e :crux.db/id]
8 | [e :d/my-attr-one p]
9 | [e :d/my-attr-two 3]]})
10 |
11 |
12 | (deftest test-symbol-inference
13 | (let [res (qa/infer-symbol-attr-map q)]
14 | (is (= res {'e :crux.db/id, 'p :d/my-attr-one}))))
15 |
16 |
17 | ; (run-tests 'crux-console.query-analysis-test)
18 |
--------------------------------------------------------------------------------
/test/crux_console/test_runner.cljs:
--------------------------------------------------------------------------------
1 | (ns crux-console.test-runner
2 | (:require [cljs.test :refer :all]
3 | [crux-console.query-analysis-test]))
4 |
5 |
6 | (defn init []
7 | (run-tests))
--------------------------------------------------------------------------------