├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── build ├── icon.icns └── icon.ico ├── doc ├── icon.ai ├── icon.png ├── logo.ai └── screenshot.png ├── electron-builder.yml ├── install_ubuntu.sh ├── logo.svg ├── mgmt.html ├── package.json ├── preload.js ├── project.clj ├── publish.sh ├── resources ├── logo.svg ├── public │ └── css │ │ ├── inspect.css │ │ ├── loader.css │ │ └── updater.css ├── updater.html ├── view-dev.html └── view.html └── src ├── cljs └── inspect │ ├── main │ ├── core.cljs │ ├── download.cljs │ ├── graphviz.cljs │ ├── log.cljs │ ├── menu.cljs │ ├── reader.cljs │ ├── runtime.cljs │ ├── sled.cljs │ ├── startup.cljs │ ├── store.cljs │ └── update.cljs │ ├── specs │ └── specs.cljs │ ├── update │ ├── core.cljs │ ├── log.cljs │ └── ui.cljs │ └── view │ ├── core.cljs │ ├── force.cljs │ ├── force2.cljs │ ├── graphviz.cljs │ ├── log.cljs │ ├── store.cljs │ ├── ui.cljs │ ├── ui │ ├── cmp.cljs │ ├── detail.cljs │ ├── flow.cljs │ ├── matches.cljs │ └── timeline.cljs │ └── util.cljs └── scss ├── _buttons.scss ├── _edn.scss ├── _variables.scss ├── inspect.scss ├── loader.scss └── updater.scss /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | pom.xml 3 | node_modules 4 | dist/ 5 | prod/ 6 | dev/ 7 | out/ 8 | .sass-cache/ 9 | *.log 10 | .DS_Store 11 | *.iml 12 | *.lock 13 | .idea/ 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 2 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 3 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 4 | 5 | 1. DEFINITIONS 6 | 7 | "Contribution" means: 8 | 9 | a) in the case of the initial Contributor, the initial code and 10 | documentation distributed under this Agreement, and 11 | 12 | b) in the case of each subsequent Contributor: 13 | 14 | i) changes to the Program, and 15 | 16 | ii) additions to the Program; 17 | 18 | where such changes and/or additions to the Program originate from and are 19 | distributed by that particular Contributor. A Contribution 'originates' from 20 | a Contributor if it was added to the Program by such Contributor itself or 21 | anyone acting on such Contributor's behalf. Contributions do not include 22 | additions to the Program which: (i) are separate modules of software 23 | distributed in conjunction with the Program under their own license 24 | agreement, and (ii) are not derivative works of the Program. 25 | 26 | "Contributor" means any person or entity that distributes the Program. 27 | 28 | "Licensed Patents" mean patent claims licensable by a Contributor which are 29 | necessarily infringed by the use or sale of its Contribution alone or when 30 | combined with the Program. 31 | 32 | "Program" means the Contributions distributed in accordance with this 33 | Agreement. 34 | 35 | "Recipient" means anyone who receives the Program under this Agreement, 36 | including all Contributors. 37 | 38 | 2. GRANT OF RIGHTS 39 | 40 | a) Subject to the terms of this Agreement, each Contributor hereby grants 41 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 42 | reproduce, prepare derivative works of, publicly display, publicly perform, 43 | distribute and sublicense the Contribution of such Contributor, if any, and 44 | such derivative works, in source code and object code form. 45 | 46 | b) Subject to the terms of this Agreement, each Contributor hereby grants 47 | Recipient a non-exclusive, worldwide, royalty-free patent license under 48 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 49 | transfer the Contribution of such Contributor, if any, in source code and 50 | object code form. This patent license shall apply to the combination of the 51 | Contribution and the Program if, at the time the Contribution is added by the 52 | Contributor, such addition of the Contribution causes such combination to be 53 | covered by the Licensed Patents. The patent license shall not apply to any 54 | other combinations which include the Contribution. No hardware per se is 55 | licensed hereunder. 56 | 57 | c) Recipient understands that although each Contributor grants the licenses 58 | to its Contributions set forth herein, no assurances are provided by any 59 | Contributor that the Program does not infringe the patent or other 60 | intellectual property rights of any other entity. Each Contributor disclaims 61 | any liability to Recipient for claims brought by any other entity based on 62 | infringement of intellectual property rights or otherwise. As a condition to 63 | exercising the rights and licenses granted hereunder, each Recipient hereby 64 | assumes sole responsibility to secure any other intellectual property rights 65 | needed, if any. For example, if a third party patent license is required to 66 | allow Recipient to distribute the Program, it is Recipient's responsibility 67 | to acquire that license before distributing the Program. 68 | 69 | d) Each Contributor represents that to its knowledge it has sufficient 70 | copyright rights in its Contribution, if any, to grant the copyright license 71 | set forth in this Agreement. 72 | 73 | 3. REQUIREMENTS 74 | 75 | A Contributor may choose to distribute the Program in object code form under 76 | its own license agreement, provided that: 77 | 78 | a) it complies with the terms and conditions of this Agreement; and 79 | 80 | b) its license agreement: 81 | 82 | i) effectively disclaims on behalf of all Contributors all warranties and 83 | conditions, express and implied, including warranties or conditions of title 84 | and non-infringement, and implied warranties or conditions of merchantability 85 | and fitness for a particular purpose; 86 | 87 | ii) effectively excludes on behalf of all Contributors all liability for 88 | damages, including direct, indirect, special, incidental and consequential 89 | damages, such as lost profits; 90 | 91 | iii) states that any provisions which differ from this Agreement are offered 92 | by that Contributor alone and not by any other party; and 93 | 94 | iv) states that source code for the Program is available from such 95 | Contributor, and informs licensees how to obtain it in a reasonable manner on 96 | or through a medium customarily used for software exchange. 97 | 98 | When the Program is made available in source code form: 99 | 100 | a) it must be made available under this Agreement; and 101 | 102 | b) a copy of this Agreement must be included with each copy of the Program. 103 | 104 | Contributors may not remove or alter any copyright notices contained within 105 | the Program. 106 | 107 | Each Contributor must identify itself as the originator of its Contribution, 108 | if any, in a manner that reasonably allows subsequent Recipients to identify 109 | the originator of the Contribution. 110 | 111 | 4. COMMERCIAL DISTRIBUTION 112 | 113 | Commercial distributors of software may accept certain responsibilities with 114 | respect to end users, business partners and the like. While this license is 115 | intended to facilitate the commercial use of the Program, the Contributor who 116 | includes the Program in a commercial product offering should do so in a 117 | manner which does not create potential liability for other Contributors. 118 | Therefore, if a Contributor includes the Program in a commercial product 119 | offering, such Contributor ("Commercial Contributor") hereby agrees to defend 120 | and indemnify every other Contributor ("Indemnified Contributor") against any 121 | losses, damages and costs (collectively "Losses") arising from claims, 122 | lawsuits and other legal actions brought by a third party against the 123 | Indemnified Contributor to the extent caused by the acts or omissions of such 124 | Commercial Contributor in connection with its distribution of the Program in 125 | a commercial product offering. The obligations in this section do not apply 126 | to any claims or Losses relating to any actual or alleged intellectual 127 | property infringement. In order to qualify, an Indemnified Contributor must: 128 | a) promptly notify the Commercial Contributor in writing of such claim, and 129 | b) allow the Commercial Contributor tocontrol, and cooperate with the 130 | Commercial Contributor in, the defense and any related settlement 131 | negotiations. The Indemnified Contributor may participate in any such claim 132 | at its own expense. 133 | 134 | For example, a Contributor might include the Program in a commercial product 135 | offering, Product X. That Contributor is then a Commercial Contributor. If 136 | that Commercial Contributor then makes performance claims, or offers 137 | warranties related to Product X, those performance claims and warranties are 138 | such Commercial Contributor's responsibility alone. Under this section, the 139 | Commercial Contributor would have to defend claims against the other 140 | Contributors related to those performance claims and warranties, and if a 141 | court requires any other Contributor to pay any damages as a result, the 142 | Commercial Contributor must pay those damages. 143 | 144 | 5. NO WARRANTY 145 | 146 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON 147 | AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER 148 | EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR 149 | CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A 150 | PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the 151 | appropriateness of using and distributing the Program and assumes all risks 152 | associated with its exercise of rights under this Agreement , including but 153 | not limited to the risks and costs of program errors, compliance with 154 | applicable laws, damage to or loss of data, programs or equipment, and 155 | unavailability or interruption of operations. 156 | 157 | 6. DISCLAIMER OF LIABILITY 158 | 159 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 160 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 161 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 162 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 163 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 164 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 165 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 166 | OF SUCH DAMAGES. 167 | 168 | 7. GENERAL 169 | 170 | If any provision of this Agreement is invalid or unenforceable under 171 | applicable law, it shall not affect the validity or enforceability of the 172 | remainder of the terms of this Agreement, and without further action by the 173 | parties hereto, such provision shall be reformed to the minimum extent 174 | necessary to make such provision valid and enforceable. 175 | 176 | If Recipient institutes patent litigation against any entity (including a 177 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 178 | (excluding combinations of the Program with other software or hardware) 179 | infringes such Recipient's patent(s), then such Recipient's rights granted 180 | under Section 2(b) shall terminate as of the date such litigation is filed. 181 | 182 | All Recipient's rights under this Agreement shall terminate if it fails to 183 | comply with any of the material terms or conditions of this Agreement and 184 | does not cure such failure in a reasonable period of time after becoming 185 | aware of such noncompliance. If all Recipient's rights under this Agreement 186 | terminate, Recipient agrees to cease use and distribution of the Program as 187 | soon as reasonably practicable. However, Recipient's obligations under this 188 | Agreement and any licenses granted by Recipient relating to the Program shall 189 | continue and survive. 190 | 191 | Everyone is permitted to copy and distribute copies of this Agreement, but in 192 | order to avoid inconsistency the Agreement is copyrighted and may only be 193 | modified in the following manner. The Agreement Steward reserves the right to 194 | publish new versions (including revisions) of this Agreement from time to 195 | time. No one other than the Agreement Steward has the right to modify this 196 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 197 | Eclipse Foundation may assign the responsibility to serve as the Agreement 198 | Steward to a suitable separate entity. Each new version of the Agreement will 199 | be given a distinguishing version number. The Program (including 200 | Contributions) may always be distributed subject to the version of the 201 | Agreement under which it was received. In addition, after a new version of 202 | the Agreement is published, Contributor may elect to distribute the Program 203 | (including its Contributions) under the new version. Except as expressly 204 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 205 | licenses to the intellectual property of any Contributor under this 206 | Agreement, whether expressly, by implication, estoppel or otherwise. All 207 | rights in the Program not expressly granted under this Agreement are 208 | reserved. 209 | 210 | This Agreement is governed by the laws of the State of New York and the 211 | intellectual property laws of the United States of America. No party to this 212 | Agreement will bring a legal action under this Agreement more than one year 213 | after the cause of action arose. Each party waives its rights to a jury trial 214 | in any resulting litigation. 215 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # OS detection adapted from: https://gist.github.com/sighingnow/deee806603ec9274fd47 2 | OSFLAG := 3 | LEIN := 4 | YARN := $(shell command -v yarn 2> /dev/null) 5 | JLINK := $(shell command -v jlink 2> /dev/null) 6 | 7 | ifeq ($(OS),Windows_NT) 8 | LEIN := $(shell command -v lein.bat 2> /dev/null) 9 | OSFLAG := -w 10 | else 11 | LEIN := $(shell command -v lein 2> /dev/null) 12 | UNAME_S := $(shell uname -s) 13 | ifeq ($(UNAME_S),Linux) 14 | OSFLAG := -l 15 | endif 16 | ifeq ($(UNAME_S),Darwin) 17 | OSFLAG := -m 18 | endif 19 | ifeq ($(UNAME_S),CYGWIN_NT-10.0) 20 | LEIN := $(shell command -v lein.bat 2> /dev/null) 21 | OSFLAG := -w 22 | endif 23 | endif 24 | 25 | package: install package-only 26 | 27 | build-deps: 28 | ifndef LEIN 29 | $(error "Leiningen not found, please install from https://leiningen.org") 30 | endif 31 | ifndef YARN 32 | $(error "yarn not found, please install from https://yarnpkg.com") 33 | endif 34 | ifndef JLINK 35 | $(error "jlink not found, please install JDK10+") 36 | endif 37 | 38 | clean: build-deps 39 | @echo Cleaning up... 40 | @rm -rf ./bin 41 | @rm -rf ./dist 42 | @eval $(LEIN) clean 43 | @rm -f ./yarn.lock 44 | 45 | deps: clean 46 | @echo Fetching Leiningen dependencies... 47 | @eval $(LEIN) deps 48 | 49 | npm-deps: clean 50 | @echo Fetching NPM dependencies... 51 | @yarn install 52 | 53 | sass: 54 | @echo Building CSS... 55 | @eval $(LEIN) sass4clj once 56 | 57 | cljs: deps npm-deps 58 | @echo Building ClojureScript for main electron process... 59 | @eval $(LEIN) cljsbuild once main 60 | @echo Building ClojureScript for electron renderer process... 61 | @eval $(LEIN) cljsbuild once view 62 | @echo Building ClojureScript for electron updater process... 63 | @eval $(LEIN) cljsbuild once updater 64 | 65 | figwheel: 66 | @lein cljs-figwheel 67 | 68 | install: clean deps sass cljs 69 | 70 | package-only: 71 | @echo Building executable... 72 | ./node_modules/.bin/electron-builder $(OSFLAG) 73 | 74 | publish-github: 75 | @echo Publishing to GitHub Releases - requires GH_TOKEN in ENV... 76 | ./node_modules/.bin/electron-builder -c electron-builder.yml --publish always $(OSFLAG) 77 | 78 | release: install publish-github 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | logo 2 | 3 | This Electron-based application lets you look inside systems built with the **[systems-toolbox](https://github.com/matthiasn/systems-toolbox)**. You need a system that is configured to offload its firehose to a Kafka topic. Once that is set up, you can connect this application to your Kafka host and watch it draw your system from the messages flowing through. Other than that, this application has zero knowledge about the system under observation. This particular screenshot comes from observing my **[meo](https://github.com/matthiasn/meo)** journaling application, where **inspect** has already helped quite a bit in making sense of message flows. 4 | 5 | ![Screenshot](./doc/screenshot.png) 6 | 7 | 8 | ## Download 9 | 10 | If you don't want to build this application yourself, you can download an already packaged application: 11 | 12 | * **[Mac](https://s3.eu-central-1.amazonaws.com/matthiasn-inspect/inspect-0.2.67.dmg)** 13 | 14 | The Mac version will notify you when there's an update. For Linux, that feature is still missing, unfortunately. If you believe that should be different, maybe you can help out with the [electron-builder](https://github.com/electron-userland/electron-builder/issues/1138) project. 15 | 16 | 17 | ## Preparing your machine 18 | 19 | For a fresh Ubuntu 17 VM, you can run the following script: 20 | 21 | $ install_ubuntu.sh 22 | 23 | 24 | ## Building inspect 25 | 26 | $ npm install -g electron-builder 27 | $ npm install -g electron-publisher-s3 28 | $ yarn install 29 | $ lein cljsbuild auto main 30 | $ lein cljsbuild auto view 31 | $ lein cljsbuild auto updater 32 | $ lein sass4clj auto 33 | $ npm run build 34 | $ npm start 35 | 36 | ## Publishing inspect 37 | 38 | $ AWS_ACCESS_KEY_ID=<...> AWS_SECRET_ACCESS_KEY=<...> ./publish.sh -l beta 39 | $ AWS_ACCESS_KEY_ID=<...> AWS_SECRET_ACCESS_KEY=<...> ./publish.sh -m release 40 | 41 | 42 | ## Contributions 43 | 44 | Contributions always welcome. Please help to make this project more useful, prettier, and just generally more awesome. Thanks! 45 | 46 | 47 | ## License 48 | 49 | Copyright © 2016, 2017 Matthias Nehlsen 50 | 51 | Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version. 52 | -------------------------------------------------------------------------------- /build/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthiasn/inspect/963ae5e29fc1c836c620cfed67787f1b99c3fe0c/build/icon.icns -------------------------------------------------------------------------------- /build/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthiasn/inspect/963ae5e29fc1c836c620cfed67787f1b99c3fe0c/build/icon.ico -------------------------------------------------------------------------------- /doc/icon.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthiasn/inspect/963ae5e29fc1c836c620cfed67787f1b99c3fe0c/doc/icon.ai -------------------------------------------------------------------------------- /doc/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthiasn/inspect/963ae5e29fc1c836c620cfed67787f1b99c3fe0c/doc/icon.png -------------------------------------------------------------------------------- /doc/logo.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthiasn/inspect/963ae5e29fc1c836c620cfed67787f1b99c3fe0c/doc/logo.ai -------------------------------------------------------------------------------- /doc/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthiasn/inspect/963ae5e29fc1c836c620cfed67787f1b99c3fe0c/doc/screenshot.png -------------------------------------------------------------------------------- /electron-builder.yml: -------------------------------------------------------------------------------- 1 | appId: "matthiasn.inspect" 2 | asar: false 3 | files: 4 | - "node_modules/**" 5 | - "!node_modules/babel-runtime/**" 6 | - "prod/**" 7 | - "electron/**" 8 | - "src/**" 9 | - "resources/**" 10 | - "doc/**" 11 | publish: 12 | provider: "github" 13 | owner: "matthiasn" 14 | draft: true 15 | nsis: 16 | differentialPackage: false 17 | -------------------------------------------------------------------------------- /install_ubuntu.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | sudo apt-get install openjdk-8-jre-headless 4 | sudo apt-get install openssl 5 | sudo apt-get install libsasl2-dev 6 | sudo apt-get install liblz1 7 | sudo apt-get install icnsutils 8 | sudo apt-get install graphicsmagick 9 | sudo apt install ruby-sass 10 | sudo apt-get install libgconf-2-4 11 | 12 | curl https://sh.rustup.rs -sSf | sh 13 | source $HOME/.profile 14 | 15 | curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.5/install.sh | bash 16 | source $HOME/.bashrc 17 | nvm install 8.7 18 | 19 | npm install -g electron 20 | npm install -g electron-builder 21 | npm install -g electron-cli 22 | npm install -g electron-build-env 23 | npm install -g electron-publisher-s3 24 | npm install -g neon 25 | npm install -g neon-cli 26 | npm install -g node-gyp 27 | 28 | npm install 29 | npm run build 30 | -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mgmt.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | inspect 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 | 16 | 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "inspect", 3 | "version": "0.2.76", 4 | "description": "a tool for inspecting systems-toolbox based systems", 5 | "main": "prod/main/main.js", 6 | "scripts": { 7 | "start": "electron .", 8 | "build": "electron-build-env neon build neon-sled", 9 | "postinstall": "electron-builder install-app-deps", 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/matthiasn/inspect.git" 15 | }, 16 | "author": "Matthias Nehlsen", 17 | "license": "EPL-1.0", 18 | "bugs": { 19 | "url": "https://github.com/matthiasn/inspect/issues" 20 | }, 21 | "homepage": "https://github.com/matthiasn/inspect", 22 | "dependencies": { 23 | "@cljs-oss/module-deps": "^1.1.1", 24 | "@fortawesome/fontawesome-free-webfonts": "^1.0.3", 25 | "always-tail": "^0.2.0", 26 | "asar": "0.14.6", 27 | "cookie-parser": "^1.4.3", 28 | "create-react-class": "^15.6.3", 29 | "d3": "^4.10.2", 30 | "d3-ellipse-force": "^0.1.1", 31 | "d3-force": "^1.0.6", 32 | "decompress": "^4.2.0", 33 | "electron-dl": "^1.10.0", 34 | "electron-fetch": "1.3.0", 35 | "electron-log": "2.2.17", 36 | "electron-updater": "4.0.6", 37 | "i": "^0.3.6", 38 | "moment": "^2.20.1", 39 | "npid": "^0.4.0", 40 | "npm": "^6.1.0", 41 | "randomcolor": "^0.5.3", 42 | "react": "16.8.1", 43 | "react-dom": "16.8.1", 44 | "s": "^0.1.1", 45 | "tcp-port-used": "^0.1.2", 46 | "viz.js": "^1.8.1" 47 | }, 48 | "devDependencies": { 49 | "browser-resolve": "1.11.3", 50 | "electron": "4.0.4", 51 | "electron-build-env": "0.2.0", 52 | "electron-builder": "20.38.5", 53 | "module-deps": "6.2.0", 54 | "resolve": "1.9.0" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /preload.js: -------------------------------------------------------------------------------- 1 | const {SpellCheckHandler, ContextMenuListener, ContextMenuBuilder} = require('electron-spellchecker'); 2 | 3 | window.spellCheckHandler = new SpellCheckHandler(); 4 | window.spellCheckHandler.attachToInput(); 5 | 6 | window.spellCheckHandler.switchLanguage('en-US'); 7 | window.spellCheckHandler.autoUnloadDictionariesOnBlur(); 8 | 9 | window.contextMenuBuilder = new ContextMenuBuilder(window.spellCheckHandler); 10 | window.contextMenuListener = new ContextMenuListener((info) => { 11 | window.contextMenuBuilder.showPopupMenu(info); 12 | }); 13 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject matthiasn/inspect "0.0-SNAPSHOT" 2 | :dependencies [[org.clojure/clojure "1.10.0"] 3 | [org.clojure/clojurescript "1.10.516"] 4 | [re-frame "0.10.6"] 5 | [reagent "0.8.1"] 6 | [com.taoensso/timbre "4.10.0"] 7 | [matthiasn/systems-toolbox "0.6.38"] 8 | [matthiasn/systems-toolbox-electron "0.6.29"] 9 | [timbre-ns-pattern-level "0.1.2"] 10 | [org.clojure/data.avl "0.0.18"] 11 | [frankiesardo/linked "1.3.0"] 12 | [com.cognitect/transit-cljs "0.8.256"]] 13 | 14 | :plugins [[lein-cljsbuild "1.1.7"] 15 | [lein-shell "0.5.0"] 16 | [deraen/lein-sass4clj "0.3.1"] 17 | [lein-ancient "0.6.15"]] 18 | 19 | :sass {:source-paths ["src/scss/"] 20 | :target-path "resources/public/css/"} 21 | 22 | :clean-targets ^{:protect false} ["target/" "prod/" "dev/" "out"] 23 | 24 | :aliases {"dist" ["do" 25 | ["clean"] 26 | ["cljsbuild" "once" "main"] 27 | ["cljsbuild" "once" "view"] 28 | ["cljsbuild" "once" "updater"] 29 | ["sass4clj" "once"]]} 30 | 31 | :cljsbuild {:builds [{:id "main" 32 | :source-paths ["src/cljs"] 33 | :compiler {:main inspect.main.core 34 | :target :nodejs 35 | :output-to "prod/main/main.js" 36 | :output-dir "out/main" 37 | :optimizations :simple 38 | :npm-deps true 39 | :language-in :ecmascript5 40 | :language-out :ecmascript5 41 | :parallel-build true}} 42 | {:id "view" 43 | :source-paths ["src/cljs"] 44 | :compiler {:main inspect.view.core 45 | :output-to "prod/view/core.js" 46 | :target :nodejs 47 | :npm-deps true 48 | :output-dir "out/view" 49 | :optimizations :simple 50 | :parallel-build true}} 51 | {:id "view-dev" 52 | :source-paths ["src/cljs"] 53 | :compiler {:main inspect.view.core 54 | :output-to "dev/view/core.js" 55 | :target :nodejs 56 | :output-dir "dev/view" 57 | :source-map true 58 | :npm-deps true 59 | :optimizations :none 60 | :parallel-build true}} 61 | {:id "updater" 62 | :source-paths ["src/cljs"] 63 | :compiler {:main inspect.update.core 64 | :output-to "prod/updater/update.js" 65 | :target :nodejs 66 | :output-dir "prod/updater" 67 | :optimizations :simple 68 | :npm-deps true 69 | :parallel-build true}}]}) 70 | -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | yarn install 4 | rm -rf ./dist 5 | lein dist 6 | 7 | PLATFORMS=$1 8 | ELECTRON_BUILDER_COMPRESSION_LEVEL=3 9 | 10 | echo "Publishing Release" 11 | ./node_modules/.bin/electron-builder --publish always $1 12 | -------------------------------------------------------------------------------- /resources/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/public/css/inspect.css: -------------------------------------------------------------------------------- 1 | .edn-tree .map:before, .edn-tree .map:after { 2 | color: #eeee22; } 3 | 4 | .edn-tree .vector:before, .edn-tree .vector:after { 5 | color: #22ee22; } 6 | 7 | .edn-tree .seq:before, .edn-tree .seq:after { 8 | color: purple; } 9 | 10 | .edn-tree .string { 11 | color: #33ffff; } 12 | 13 | .edn-tree .number { 14 | color: #cc66ff; } 15 | 16 | .edn-tree .keyword { 17 | color: #ff8888; } 18 | 19 | .edn-tree .nil { 20 | color: red; } 21 | 22 | .edn-tree .boolean { 23 | color: #ff00ff; } 24 | 25 | .edn-tree.light .map:before, .edn-tree.light .map:after { 26 | color: #555500; } 27 | 28 | .edn-tree.light .vector:before, .edn-tree.light .vector:after { 29 | color: #228822; } 30 | 31 | .edn-tree.light .seq:before, .edn-tree.light .seq:after { 32 | color: purple; } 33 | 34 | .edn-tree.light .string { 35 | color: #339999; } 36 | 37 | .edn-tree.light .number { 38 | color: #7700aa; } 39 | 40 | .edn-tree.light .keyword { 41 | color: #770000; } 42 | 43 | .edn-tree.light .nil { 44 | color: red; } 45 | 46 | .edn-tree.light .boolean { 47 | color: #990099; } 48 | 49 | /* Layout */ 50 | .edn-tree { 51 | font-size: 10px; 52 | display: flex; } 53 | .edn-tree .map { 54 | display: block; 55 | float: left; } 56 | .edn-tree .map .key-val { 57 | display: block; 58 | float: left; 59 | /* Map key */ 60 | /* Map value */ } 61 | .edn-tree .map .key-val:not(:first-child) { 62 | display: block; 63 | clear: both; 64 | padding-left: 4px; } 65 | .edn-tree .map .key-val > div:nth-child(1) { 66 | display: block; 67 | float: left; 68 | padding: 0 5px 0 5px; } 69 | .edn-tree .map .key-val > div:nth-child(2) { 70 | display: inline-block; } 71 | .edn-tree .map:before { 72 | content: "{"; } 73 | .edn-tree .map:after { 74 | content: "}"; } 75 | .edn-tree .map:before, .edn-tree .map:after { 76 | display: inline-flex; 77 | float: left; } 78 | .edn-tree .vector > div { 79 | display: inline-flex; 80 | float: left; 81 | clear: both; } 82 | .edn-tree .vector:before { 83 | content: "["; } 84 | .edn-tree .vector:after { 85 | content: "]"; } 86 | .edn-tree .vector:before, .edn-tree .vector:after { 87 | display: inline-flex; 88 | float: left; } 89 | .edn-tree .seq:before { 90 | content: "("; } 91 | .edn-tree .seq:after { 92 | content: ")"; } 93 | .edn-tree .seq:before, .edn-tree .seq:after { 94 | display: inline-flex; 95 | float: left; } 96 | .edn-tree span.string:before, .edn-tree span.string:after { 97 | content: "\""; } 98 | .edn-tree .collapsed { 99 | cursor: pointer; } 100 | .edn-tree .collapsed > .vector:before { 101 | content: "[..."; } 102 | .edn-tree .collapsed > .map:before { 103 | content: "{..."; } 104 | .edn-tree .collapsed > .seq:before { 105 | content: "(..."; } 106 | .edn-tree .collapsed:hover > .vector:before, .edn-tree .collapsed:hover > .vector:after, 107 | .edn-tree .collapsed:hover > .map:before, .edn-tree .collapsed:hover > .map:before, 108 | .edn-tree .collapsed:hover > .seq:before, .edn-tree .collapsed:hover > .seq:before { 109 | text-decoration: underline; } 110 | 111 | body { 112 | color: #576364; 113 | background-color: white; 114 | font-size: 12px; 115 | font-weight: 300; 116 | font-family: "Lato", sans-serif; 117 | display: flex; 118 | width: 3840px; } 119 | 120 | h1, h2, h3, h4, h5, h6 { 121 | font-family: "Oswald", "Lato", sans-serif; } 122 | 123 | .btn { 124 | padding: 1px 5px; 125 | color: white; 126 | font-size: 0.8em; 127 | cursor: pointer; 128 | white-space: nowrap; } 129 | .btn .fas { 130 | padding-right: 0.1em; } 131 | 132 | .save { 133 | height: 15px; } 134 | 135 | .not-saved { 136 | padding: 1px 5px; 137 | color: white; 138 | font-size: 0.8em; 139 | background-color: #ca3c3c; 140 | cursor: pointer; } 141 | 142 | .link-btn { 143 | padding: 0 5px; 144 | color: white; 145 | font-size: 0.8em; 146 | background-color: #4CAF50; 147 | cursor: pointer; 148 | white-space: nowrap; } 149 | 150 | button { 151 | background-color: #4CAF50; 152 | border: none; 153 | color: white; 154 | padding: 5px 12px; 155 | text-align: center; 156 | text-decoration: none; 157 | display: inline-block; 158 | font-size: 16px; 159 | cursor: pointer; 160 | white-space: nowrap; 161 | outline: 0; } 162 | button i { 163 | margin-right: 6px; } 164 | 165 | .menu-new { 166 | background-color: #0078e7; } 167 | 168 | .show-more-btn { 169 | background-color: #1f8dd6; 170 | color: whitesmoke; 171 | font-size: 0.8em; 172 | padding: 1px 5px; } 173 | 174 | .delete-warn { 175 | padding: 1px 5px; 176 | color: white; 177 | font-size: 0.8em; 178 | cursor: pointer; 179 | background-color: #ca3c3c; } 180 | 181 | .show-more { 182 | padding: 20px 0; } 183 | 184 | a, a:visited { 185 | text-decoration: none; 186 | font-weight: 400; 187 | color: #006c91; 188 | outline: none !important; } 189 | 190 | .toggle { 191 | padding: 2px; 192 | margin-left: 4px; 193 | cursor: pointer; } 194 | .toggle.green { 195 | color: #3d8b40; } 196 | 197 | .fa-thumbs-up, .fa-thumbs-down { 198 | color: #006C91; } 199 | 200 | .inactive { 201 | color: #AAA; } 202 | 203 | .hidden-comments { 204 | color: red; } 205 | 206 | a .toggle { 207 | color: #576364; } 208 | 209 | .upvotes { 210 | vertical-align: super; 211 | color: #006C91; 212 | font-size: 0.7em; 213 | line-height: 1.2em; 214 | font-weight: bold; 215 | padding: 0 2px 0 1px; 216 | margin-left: 1px; 217 | margin-bottom: 3px; 218 | border: 1px solid #006C91; 219 | border-radius: 2px; } 220 | 221 | .toggle-cmds { 222 | margin-right: 30px; } 223 | 224 | .linked-tasks .filter { 225 | background-color: #DDD; 226 | color: #AAA; 227 | font-size: 0.8em; 228 | padding: 1px 5px; 229 | margin: 0 2px 0 3px; 230 | cursor: pointer; } 231 | 232 | .linked-tasks .current { 233 | background-color: #1f8dd6; 234 | color: whitesmoke; } 235 | 236 | #logo { 237 | width: 150px; 238 | position: absolute; 239 | top: 1em; 240 | left: 2em; } 241 | 242 | .cmp-table h2 { 243 | margin-bottom: 3px; 244 | font-family: monospace; 245 | font-size: 0.9em; } 246 | 247 | #graphviz1 svg, #graphviz2 svg { 248 | max-width: 1000px; } 249 | 250 | .flex { 251 | display: flex; } 252 | 253 | .menu { 254 | background-color: #EEE; 255 | padding-left: 200px; } 256 | 257 | .observer pre { 258 | font-size: 0.7em; 259 | line-height: 1em; } 260 | 261 | .observer h3 { 262 | margin: 0.6em 0 0 0; 263 | font-size: 1em; 264 | font-family: monospace; } 265 | 266 | .observer .header { 267 | display: flex; 268 | margin-bottom: 10px; } 269 | .observer .header .host-input { 270 | display: flex; } 271 | .observer .header input { 272 | height: 28px; 273 | width: 320px; 274 | border: 0; 275 | outline: none; 276 | padding-left: 6px; 277 | border-bottom-left-radius: 4px; 278 | border-top-left-radius: 4px; } 279 | .observer .header button { 280 | border-bottom-right-radius: 4px; 281 | border-top-right-radius: 4px; } 282 | .observer .header .cnt { 283 | padding: 4px 0; 284 | font-family: monospace; } 285 | 286 | .observer .section { 287 | padding: 12px; 288 | margin-bottom: 10px; 289 | width: calc(100vw - 24px); 290 | min-height: 40px; } 291 | 292 | .observer .timeline svg { 293 | width: 1100px; } 294 | 295 | .observer .msg-flow, .observer .msg-flows { 296 | min-height: 600px; } 297 | .observer .msg-flow .time, .observer .msg-flows .time { 298 | font-weight: bold; 299 | font-family: monospace; } 300 | .observer .msg-flow table, .observer .msg-flows table { 301 | margin-top: 1em; 302 | font-size: 0.8em; 303 | font-family: monospace; 304 | border-spacing: 0; 305 | cursor: pointer; } 306 | .observer .msg-flow table tr:nth-child(even), .observer .msg-flows table tr:nth-child(even) { 307 | background-color: #eff9fc; } 308 | .observer .msg-flow table .max-per-type tr, .observer .msg-flows table .max-per-type tr { 309 | background-color: transparent; } 310 | .observer .msg-flow table td, .observer .msg-flow table th, .observer .msg-flows table td, .observer .msg-flows table th { 311 | padding-right: 8px; 312 | vertical-align: middle; } 313 | .observer .msg-flow table .number, .observer .msg-flows table .number { 314 | text-align: right; 315 | font-weight: bold; } 316 | 317 | .observer .stop { 318 | background-color: #ca3c3c; } 319 | 320 | .observer .freeze { 321 | background-color: #eee; 322 | color: #4CAF50; 323 | margin-top: 10px; } 324 | 325 | .observer .clear { 326 | background-color: #ca3c3c; 327 | color: white; 328 | margin: 10px 0 0 10px; } 329 | 330 | .observer .status { 331 | font-family: monospace; } 332 | .observer .status.error { 333 | color: #ca3c3c; 334 | font-weight: bold; } 335 | 336 | .observer .color { 337 | padding: 0 0.6em; 338 | margin-right: 0.5em; } 339 | 340 | .observer .active, .observer .selected { 341 | background-color: #7FDBFF !important; } 342 | 343 | .observer .active-flow { 344 | background-color: #c7e7c8 !important; } 345 | 346 | .observer .force-wrapper { 347 | align-items: center; 348 | display: flex; } 349 | 350 | .observer ul { 351 | list-style: none; 352 | padding: 0; 353 | margin: 0; } 354 | 355 | .observer .tables { 356 | display: flex; 357 | flex-flow: row; } 358 | .observer .tables table { 359 | background-color: white; 360 | margin: 0 1em 0 0; 361 | min-width: 220px; 362 | line-height: 1em; 363 | font-family: monospace; 364 | font-size: 0.8em; 365 | cursor: pointer; } 366 | .observer .tables table tr:nth-child(even) { 367 | background-color: #eff9fc; } 368 | .observer .tables table td { 369 | min-width: 20px; } 370 | .observer .tables table td ul { 371 | margin: 0; } 372 | .observer .tables table td.dir { 373 | width: 30px; } 374 | .observer .tables table td.cmp-id { 375 | min-width: 170px; } 376 | .observer .tables table .cnt { 377 | text-align: end; 378 | padding-right: 5px; 379 | background-color: white; } 380 | .observer .tables table .changed { 381 | font-weight: bold; 382 | background-color: orange; } 383 | 384 | .grid .wrapper { 385 | display: grid; 386 | height: 100vh; 387 | width: 3840px; 388 | grid-gap: 10px; 389 | grid-template-rows: 80px calc(100vh - 100px); 390 | grid-template-columns: 1000px 800px 600px 1000px 600px 600px 600px; } 391 | 392 | .grid .menu { 393 | grid-column: 1/8; 394 | grid-row: 1; } 395 | 396 | .grid .col-1 { 397 | grid-column: 1; 398 | grid-row: 2; } 399 | 400 | .grid .col-2 { 401 | grid-column: 2; 402 | grid-row: 2; } 403 | 404 | .grid .col-3 { 405 | grid-column: 3; 406 | grid-row: 2; } 407 | 408 | .grid .col-4 { 409 | grid-column: 4; 410 | grid-row: 2; } 411 | 412 | .grid .col-5 { 413 | grid-column: 5; 414 | grid-row: 2; } 415 | 416 | .grid .col-6 { 417 | grid-column: 6; 418 | grid-row: 2; } 419 | 420 | .grid .col-7 { 421 | grid-column: 7; 422 | grid-row: 2; } 423 | 424 | .spec-errors .header { 425 | font-weight: bold; } 426 | .spec-errors .header time { 427 | margin-right: 1em; } 428 | 429 | .spec-errors pre { 430 | font-size: 1.2em; } 431 | 432 | .known-hosts { 433 | line-height: 1.6em; 434 | position: absolute; 435 | background-color: lightblue; 436 | padding: 5px; 437 | font-family: monospace; 438 | width: 300px; } 439 | .known-hosts .known-host { 440 | padding: 5px; 441 | cursor: pointer; } 442 | 443 | .msg-cmp { 444 | display: flex; 445 | flex-direction: row; } 446 | .msg-cmp .msg-types { 447 | padding-right: 30px; 448 | white-space: nowrap; } 449 | .msg-cmp .msg-types input { 450 | margin-top: 20px; 451 | margin-bottom: 2px; 452 | width: 180px; } 453 | .msg-cmp .msg-types table { 454 | background-color: white; 455 | margin: 0 1em 0 0; 456 | min-width: 220px; 457 | line-height: 1em; 458 | font-family: monospace; 459 | font-size: 0.8em; 460 | cursor: pointer; } 461 | .msg-cmp .msg-types table tr:nth-child(even) { 462 | background-color: #eff9fc; } 463 | .msg-cmp .msg-types table td { 464 | min-width: 20px; } 465 | .msg-cmp .msg-types table td ul { 466 | margin: 0; } 467 | .msg-cmp .msg-types table td.dir { 468 | width: 30px; } 469 | .msg-cmp .msg-types table td.cmp-id { 470 | min-width: 170px; } 471 | .msg-cmp .msg-types table .cnt { 472 | text-align: end; 473 | padding-right: 5px; 474 | background-color: white; } 475 | .msg-cmp .msg-types table .changed { 476 | font-weight: bold; 477 | background-color: orange; } 478 | .msg-cmp .msg-types th { 479 | user-select: none; } 480 | .msg-cmp .msg-types .fas { 481 | padding: 5px 10px; 482 | color: #AAA; } 483 | .msg-cmp .msg-types .fas.sort-active { 484 | color: #006c91; } 485 | -------------------------------------------------------------------------------- /resources/public/css/loader.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: #576364; 3 | background-color: #94a4b5; 4 | font-size: 12px; 5 | font-weight: 300; 6 | font-family: "Lato", sans-serif; 7 | overflow: hidden; } 8 | 9 | .loader { 10 | border: 8px solid #f6f6f6; 11 | border-top: 8px solid #4CAF50; 12 | border-radius: 50%; 13 | margin: 20px; 14 | width: 60px; 15 | height: 60px; 16 | animation: spin 2s linear infinite; } 17 | 18 | @keyframes spin { 19 | 0% { 20 | transform: rotate(0deg); } 21 | 100% { 22 | transform: rotate(360deg); } } 23 | -------------------------------------------------------------------------------- /resources/public/css/updater.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: #576364; 3 | background-color: #94a4b5; 4 | font-size: 12px; 5 | font-weight: 300; 6 | font-family: "Lato", sans-serif; 7 | overflow: hidden; } 8 | 9 | h1, h2, h3, h4, h5, h6 { 10 | font-family: "Oswald", "Lato", sans-serif; } 11 | 12 | .flex-container { 13 | display: flex; 14 | flex-flow: column; 15 | height: 100vh; } 16 | 17 | .updater { 18 | padding: 1em; } 19 | .updater .info { 20 | padding: 1em 0 2em 0; } 21 | 22 | .btn { 23 | padding: 1px 5px; 24 | color: white; 25 | font-size: 0.8em; 26 | cursor: pointer; 27 | white-space: nowrap; } 28 | .btn .fas { 29 | padding-right: 0.1em; } 30 | 31 | .save { 32 | height: 15px; } 33 | 34 | .not-saved { 35 | padding: 1px 5px; 36 | color: white; 37 | font-size: 0.8em; 38 | background-color: #ca3c3c; 39 | cursor: pointer; } 40 | 41 | .link-btn { 42 | padding: 0 5px; 43 | color: white; 44 | font-size: 0.8em; 45 | background-color: #4CAF50; 46 | cursor: pointer; 47 | white-space: nowrap; } 48 | 49 | button { 50 | background-color: #4CAF50; 51 | border: none; 52 | color: white; 53 | padding: 5px 12px; 54 | text-align: center; 55 | text-decoration: none; 56 | display: inline-block; 57 | font-size: 16px; 58 | cursor: pointer; 59 | white-space: nowrap; 60 | outline: 0; } 61 | button i { 62 | margin-right: 6px; } 63 | 64 | .menu-new { 65 | background-color: #0078e7; } 66 | 67 | .show-more-btn { 68 | background-color: #1f8dd6; 69 | color: whitesmoke; 70 | font-size: 0.8em; 71 | padding: 1px 5px; } 72 | 73 | .delete-warn { 74 | padding: 1px 5px; 75 | color: white; 76 | font-size: 0.8em; 77 | cursor: pointer; 78 | background-color: #ca3c3c; } 79 | 80 | .show-more { 81 | padding: 20px 0; } 82 | 83 | a, a:visited { 84 | text-decoration: none; 85 | font-weight: 400; 86 | color: #006c91; 87 | outline: none !important; } 88 | 89 | .toggle { 90 | padding: 2px; 91 | margin-left: 4px; 92 | cursor: pointer; } 93 | .toggle.green { 94 | color: #3d8b40; } 95 | 96 | .fa-thumbs-up, .fa-thumbs-down { 97 | color: #006C91; } 98 | 99 | .inactive { 100 | color: #AAA; } 101 | 102 | .hidden-comments { 103 | color: red; } 104 | 105 | a .toggle { 106 | color: #576364; } 107 | 108 | .upvotes { 109 | vertical-align: super; 110 | color: #006C91; 111 | font-size: 0.7em; 112 | line-height: 1.2em; 113 | font-weight: bold; 114 | padding: 0 2px 0 1px; 115 | margin-left: 1px; 116 | margin-bottom: 3px; 117 | border: 1px solid #006C91; 118 | border-radius: 2px; } 119 | 120 | .toggle-cmds { 121 | margin-right: 30px; } 122 | 123 | .linked-tasks .filter { 124 | background-color: #DDD; 125 | color: #AAA; 126 | font-size: 0.8em; 127 | padding: 1px 5px; 128 | margin: 0 2px 0 3px; 129 | cursor: pointer; } 130 | 131 | .linked-tasks .current { 132 | background-color: #1f8dd6; 133 | color: whitesmoke; } 134 | 135 | .meter { 136 | height: 16px; 137 | position: relative; 138 | background: #5d7186; 139 | border-radius: 6px; 140 | padding: 4px; } 141 | 142 | .meter > span { 143 | display: block; 144 | height: 100%; 145 | border-radius: 4px; 146 | background-color: #4CAF50; 147 | position: relative; 148 | overflow: hidden; } 149 | -------------------------------------------------------------------------------- /resources/updater.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | inspect 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 | 16 | 17 | -------------------------------------------------------------------------------- /resources/view-dev.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | inspect 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 | 14 |
15 | 16 | 17 | 21 | 22 | -------------------------------------------------------------------------------- /resources/view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | inspect 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 | 14 |
15 | 16 | 17 | 21 | 22 | -------------------------------------------------------------------------------- /src/cljs/inspect/main/core.cljs: -------------------------------------------------------------------------------- 1 | (ns inspect.main.core 2 | (:require [inspect.specs.specs] 3 | [inspect.main.log] 4 | [taoensso.timbre :as timbre :refer-macros [info error]] 5 | [matthiasn.systems-toolbox-electron.ipc-main :as ipc] 6 | [matthiasn.systems-toolbox-electron.window-manager :as wm] 7 | [matthiasn.systems-toolbox.scheduler :as sched] 8 | [matthiasn.systems-toolbox.switchboard :as sb] 9 | [inspect.main.menu :as menu] 10 | [inspect.main.update :as upd] 11 | [inspect.main.graphviz :as gv] 12 | [inspect.main.reader :as kafka] 13 | [inspect.main.download :as dl] 14 | [inspect.main.store :as st] 15 | [inspect.main.sled :as db] 16 | [inspect.main.startup :as startup] 17 | [electron :refer [app]] 18 | [cljs.nodejs :as nodejs :refer [process]] 19 | [inspect.main.runtime :as rt])) 20 | 21 | (when-not (aget js/goog "global" "setTimeout") 22 | (info "goog.global.setTimeout not defined - let's change that") 23 | (aset js/goog "global" "setTimeout" js/setTimeout)) 24 | 25 | (defonce switchboard (sb/component :electron/switchboard)) 26 | 27 | (def wm-relay #{:exec/js 28 | :import/listen 29 | :reader/status 30 | :reader/files 31 | :subscription/match 32 | :spec/error 33 | :update/status 34 | :sled/res 35 | :svg/overview 36 | :observer/cmps-msgs}) 37 | 38 | (def app-path (:app-path rt/runtime-info)) 39 | 40 | (defn start [] 41 | (info "Starting CORE:" (.-resourcesPath process)) 42 | (sb/send-mult-cmd 43 | switchboard 44 | [[:cmd/init-comp #{(wm/cmp-map :electron/window-manager wm-relay app-path) 45 | (kafka/cmp-map :electron/kafka-cmp) 46 | (st/cmp-map :electron/store-cmp) 47 | (db/cmp-map :electron/db-cmp) 48 | (gv/cmp-map :electron/graphviz) 49 | (dl/cmp-map :electron/download-cmp) 50 | (ipc/cmp-map :electron/ipc-cmp) 51 | (startup/cmp-map :electron/startup-cmp) 52 | (upd/cmp-map :electron/update-cmp) 53 | (sched/cmp-map :electron/scheduler-cmp) 54 | (menu/cmp-map :electron/menu-cmp)}] 55 | 56 | [:cmd/route {:from :electron/menu-cmp 57 | :to #{:electron/window-manager 58 | :electron/kafka-cmp 59 | :electron/download-cmp 60 | :electron/startup-cmp 61 | :electron/update-cmp}}] 62 | 63 | [:cmd/route {:from #{:electron/scheduler-cmp} 64 | :to #{:electron/update-cmp 65 | :electron/store-cmp 66 | :electron/kafka-cmp}}] 67 | 68 | [:cmd/route {:from :electron/update-cmp 69 | :to #{:electron/window-manager 70 | :electron/scheduler-cmp}}] 71 | 72 | [:cmd/route {:from :electron/ipc-cmp 73 | :to #{:electron/store-cmp 74 | :electron/kafka-cmp 75 | :electron/db-cmp 76 | :electron/window-manager 77 | :electron/graphviz 78 | :electron/update-cmp}}] 79 | 80 | [:cmd/route {:from :electron/scheduler-cmp 81 | :to :electron/kafka-cmp}] 82 | 83 | [:cmd/route {:from :electron/graphviz 84 | :to :electron/window-manager}] 85 | 86 | [:cmd/route {:from #{:electron/kafka-cmp 87 | :electron/db-cmp 88 | :electron/scheduler-cmp} 89 | :to #{:electron/store-cmp 90 | :electron/window-manager}}] 91 | 92 | [:cmd/route {:from :electron/store-cmp 93 | :to #{:electron/window-manager 94 | :electron/graphviz 95 | :electron/db-cmp}}] 96 | 97 | [:cmd/send {:to :electron/window-manager 98 | :msg [:window/new {:url (:index-page rt/runtime-info)}]}] 99 | 100 | [:cmd/send {:to :electron/scheduler-cmp 101 | :msg [:cmd/schedule-new {:timeout 2000 102 | :message [:state/publish] 103 | :repeat true}]}] 104 | 105 | [:cmd/send {:to :electron/scheduler-cmp 106 | :msg [:cmd/schedule-new {:timeout (* 24 60 60 1000) 107 | :message [:update/auto-check] 108 | :repeat true 109 | :initial true}]}]])) 110 | 111 | (.on app "ready" start) 112 | (.on app "uncaughtException" #(error "uncaughtException" %)) 113 | -------------------------------------------------------------------------------- /src/cljs/inspect/main/download.cljs: -------------------------------------------------------------------------------- 1 | (ns inspect.main.download 2 | (:require [taoensso.timbre :as timbre :refer-macros [info debug]] 3 | [electron :refer [BrowserWindow]] 4 | [electron-dl :refer [download]] 5 | [decompress] 6 | [inspect.main.runtime :as rt])) 7 | 8 | (def urls 9 | {:kafka "http://apache.mirror.digionline.de/kafka/0.10.1.1/kafka_2.11-0.10.1.1.tgz" 10 | :jdk "http://cdn.azul.com/zulu/bin/zulu8.23.0.3-jdk8.0.144-macosx_x64.zip"}) 11 | 12 | (defn download-bin [{:keys [msg-payload]}] 13 | (info "DOWNLOAD:" msg-payload) 14 | (let [window (BrowserWindow. (clj->js {:width 400 :height 300})) 15 | url (msg-payload urls) 16 | progress (fn [p] (info "DOWNLOAD: progress" p)) 17 | {:keys [downloads]} rt/runtime-info 18 | opts (clj->js {:directory downloads 19 | :onProgress progress 20 | :openFolderWhenDone true}) 21 | jdk-dl (download window url opts) 22 | on-complete (fn [dl-item] 23 | (let [filename (.getFilename dl-item)] 24 | (decompress (str downloads "/" filename) downloads) 25 | (info "DOWNLOAD completed" filename)))] 26 | (.then jdk-dl on-complete) 27 | {})) 28 | 29 | (defn cmp-map [cmp-id] 30 | {:cmp-id cmp-id 31 | :handler-map {:download/bin download-bin}}) 32 | 33 | -------------------------------------------------------------------------------- /src/cljs/inspect/main/graphviz.cljs: -------------------------------------------------------------------------------- 1 | (ns inspect.main.graphviz 2 | (:require [viz.js :as Viz] 3 | [fs :refer [writeFileSync]])) 4 | 5 | (defn sanitize [cmp-id] (str "\"" cmp-id "\"")) 6 | 7 | (defn sub-graph [[label nodes]] 8 | (let [cluster-name (str "cluster_" label)] 9 | (str "subgraph " cluster-name " { label =< " label " >; " 10 | (apply str (map (fn [n] (str n "; ")) nodes)) 11 | "} "))) 12 | 13 | (defn gen-svg [cmp-ids edges active-type] 14 | (let [edge-mapper (fn edge-mapper [{:keys [source target msg-type]}] 15 | (str (sanitize source) " -> " (sanitize target) 16 | (if (= msg-type active-type) 17 | " [color = red penwidth=4]" 18 | (when active-type 19 | " [color = lightgrey]")) 20 | "; ")) 21 | links (apply str (map edge-mapper edges)) 22 | clusters 23 | (reduce 24 | (fn [acc in] 25 | (let [{:keys [source-ns source target-ns target]} in] 26 | (-> acc 27 | (update-in [source-ns] #(set (conj % (sanitize source)))) 28 | (update-in [target-ns] #(set (conj % (sanitize target))))))) 29 | {} 30 | edges) 31 | sub-graphs (map sub-graph clusters) 32 | digraph (str "digraph { " links (apply str sub-graphs) "}") 33 | svg (Viz digraph)] 34 | (writeFileSync "/tmp/inspect.dot" digraph "utf-8") 35 | (writeFileSync "/tmp/inspect.svg" svg "utf-8") 36 | svg)) 37 | 38 | (defn generate-svg [{:keys [current-state msg-payload put-fn]}] 39 | (let [new-state (assoc-in current-state [:prev] msg-payload)] 40 | (when-not (= (:prev current-state) msg-payload) 41 | (let [{:keys [cmp-ids edges]} msg-payload 42 | active-type (:active-type current-state) 43 | svg (gen-svg cmp-ids edges active-type)] 44 | {:new-state new-state 45 | :emit-msg [:svg/overview svg]})))) 46 | 47 | (defn set-active [{:keys [current-state msg-payload]}] 48 | (let [{:keys [cmp-ids edges]} (:prev current-state) 49 | active-type (when-not (= msg-payload (:active-type current-state)) 50 | msg-payload) 51 | new-state (assoc-in current-state [:active-type] active-type) 52 | svg (gen-svg cmp-ids edges active-type)] 53 | {:new-state new-state 54 | :emit-msg [:svg/overview svg]})) 55 | 56 | (defn cmp-map [cmp-id] 57 | {:cmp-id cmp-id 58 | :handler-map {:svg/gen-overview generate-svg 59 | :svg/set-active set-active}}) 60 | -------------------------------------------------------------------------------- /src/cljs/inspect/main/log.cljs: -------------------------------------------------------------------------------- 1 | (ns inspect.main.log 2 | (:require [electron-log :as l] 3 | [cljs.nodejs :as nodejs] 4 | [taoensso.encore :as enc] 5 | [taoensso.timbre :as timbre])) 6 | 7 | (aset l "transports" "console" "level" "info") 8 | (aset l "transports" "console" "format" "{h}:{i}:{s}:{ms} {text}") 9 | (aset l "transports" "file" "level" "info") 10 | (aset l "transports" "file" "format" "{h}:{i}:{s}:{ms} {text}") 11 | (aset l "transports" "file" "file" "/tmp/inspect.log") 12 | 13 | (nodejs/enable-util-print!) 14 | 15 | (defn ns-filter 16 | "From: https://github.com/yonatane/timbre-ns-pattern-level" 17 | [fltr] 18 | (-> fltr enc/compile-ns-filter taoensso.encore/memoize_)) 19 | 20 | (defn middleware 21 | "From: https://github.com/yonatane/timbre-ns-pattern-level" 22 | [ns-patterns] 23 | (fn log-by-ns-pattern [{:keys [?ns-str config level] :as opts}] 24 | (let [namesp (or (some->> ns-patterns 25 | keys 26 | (filter #(and (string? %) 27 | ((ns-filter %) ?ns-str))) 28 | not-empty 29 | (apply max-key count)) 30 | :all) 31 | log-level (get ns-patterns namesp (get config :level))] 32 | (when (and (taoensso.timbre/may-log? log-level namesp) 33 | (taoensso.timbre/level>= level log-level)) 34 | opts)))) 35 | 36 | ; See https://github.com/ptaoussanis/timbre 37 | (def timbre-config 38 | {:ns-whitelist [] #_["my-app.foo-ns"] 39 | :ns-blacklist [] #_["taoensso.*"] 40 | 41 | :middleware [(middleware {"inspect.main.kafka" :info 42 | :all :info})] 43 | 44 | :appenders {:console {:enabled? true 45 | :fn (fn [data] 46 | (let [{:keys [output_]} data 47 | formatted-output-str (force output_)] 48 | (l/info formatted-output-str)))}}}) 49 | 50 | (timbre/merge-config! timbre-config) -------------------------------------------------------------------------------- /src/cljs/inspect/main/menu.cljs: -------------------------------------------------------------------------------- 1 | (ns inspect.main.menu 2 | (:require [taoensso.timbre :as timbre :refer-macros [info debug]] 3 | [electron :refer [app Menu dialog]] 4 | [cljs.nodejs :as nodejs :refer [process]] 5 | [inspect.main.runtime :as rt])) 6 | 7 | (defn open-dialog [put-fn] 8 | (let [options (clj->js {:properties ["openFile"] 9 | :buttonLabel "Open" 10 | :filters []}) 11 | callback (fn [res] 12 | (let [file (first (js->clj res))] 13 | (put-fn [:tail/start file])))] 14 | (.showOpenDialog dialog options callback))) 15 | 16 | (defn state-fn [put-fn] 17 | (let [index-page (:index-page rt/runtime-info) 18 | menu-tpl 19 | [{:label "Application" 20 | :submenu [{:label "About" 21 | :selector "orderFrontStandardAboutPanel:"} 22 | {:label "Check for Updates..." 23 | :click #(put-fn [:window/new {:url "updater.html" 24 | :width 600 25 | :height 300}])} 26 | {:label "Clear Caches" 27 | :submenu [{:label "Clear Electron Cache" 28 | :click #(put-fn [:app/clear-cache])}]} 29 | {:label "Process Management" 30 | :click #(put-fn [:mgmt/open])} 31 | {:label "Download Kafka" 32 | :click #(put-fn [:download/bin :kafka])} 33 | {:label "Download JDK" 34 | :click #(put-fn [:download/bin :jdk])} 35 | {:label "Run Zookeeper" 36 | :click #(put-fn [:run/zookeeper])} 37 | {:label "Run Kafka" 38 | :click #(put-fn [:run/kafka])} 39 | {:label "Close Window" 40 | :accelerator "Cmd+W" 41 | :click #(put-fn [:window/close])} 42 | {:label "Quit" 43 | :accelerator "Cmd+Q" 44 | :click #(put-fn [:app/shutdown])}]} 45 | {:label "File" 46 | :submenu [{:label "Open File..." 47 | :accelerator "CmdOrCtrl+O" 48 | :click #(open-dialog put-fn)}]} 49 | {:label "Edit" 50 | :submenu [{:label "Undo" 51 | :accelerator "CmdOrCtrl+Z" 52 | :selector "undo:"} 53 | {:label "Redo" 54 | :accelerator "Shift+CmdOrCtrl+Z" 55 | :selector "redo:"} 56 | {:label "Cut" 57 | :accelerator "CmdOrCtrl+X" 58 | :selector "cut:"} 59 | {:label "Copy" 60 | :accelerator "CmdOrCtrl+C" 61 | :selector "copy:"} 62 | {:label "Paste" 63 | :accelerator "CmdOrCtrl+V" 64 | :selector "paste:"} 65 | {:label "Select All" 66 | :accelerator "CmdOrCtrl+A" 67 | :selector "selectAll:"}]} 68 | {:label "View" 69 | :submenu [{:label "New Window" 70 | :accelerator "Option+Cmd+N" 71 | :click #(put-fn [:window/new {:url index-page}])} 72 | {:type "separator"} 73 | {:role "zoomin"} 74 | {:role "zoomout"} 75 | {:type "separator"} 76 | {:label "Open Dev Tools" 77 | :click #(put-fn [:window/dev-tools])}]}] 78 | menu (.buildFromTemplate Menu (clj->js menu-tpl)) 79 | activate #(put-fn [:window/activate])] 80 | (info "Starting Menu Component") 81 | (.on app "activate" activate) 82 | (.setApplicationMenu Menu menu)) 83 | {:state (atom {})}) 84 | 85 | (defn cmp-map [cmp-id] 86 | {:cmp-id cmp-id 87 | :state-fn state-fn}) 88 | -------------------------------------------------------------------------------- /src/cljs/inspect/main/reader.cljs: -------------------------------------------------------------------------------- 1 | (ns inspect.main.reader 2 | (:require-macros [cljs.core.async.macros :refer [go]]) 3 | (:require [taoensso.timbre :refer-macros [info debug warn error]] 4 | [inspect.main.runtime :as rt] 5 | [electron :refer [dialog]] 6 | [always-tail :as Tail] 7 | [cognitect.transit :as t] 8 | [cljs.core.async :refer [put! chan ! close!]] 9 | [fs :refer [existsSync readFileSync writeFileSync]] 10 | [cljs.nodejs :refer [process]] 11 | [matthiasn.systems-toolbox.component :as stc] 12 | [clojure.string :as str] 13 | [cljs.reader :as edn])) 14 | 15 | (defn state-fn [put-fn] 16 | (let [state (atom {:count 0 :last-ts (stc/now)}) 17 | err-handler (fn [title content] 18 | (error "showErrorBox" title content) 19 | (when (str/includes? content "ETIMEDOUT") 20 | (put-fn [:reader/status {:status :error 21 | :text "connection timeout"}])))] 22 | (aset dialog "showErrorBox" err-handler) 23 | {:state state})) 24 | 25 | (defn stop [{:keys [put-fn cmp-state current-state]}] 26 | (when-let [tail (:tail current-state)] 27 | (info "stopping consumer") 28 | (swap! cmp-state assoc-in [:count] 0) 29 | (put-fn [:reader/status {:status :stopped :text "stopped"}]) 30 | (put-fn [:observer/stop]) 31 | (.unwatch tail))) 32 | 33 | (def files-history (str (:user-data rt/runtime-info) "/files_history.edn")) 34 | 35 | (defn read-known-files [] 36 | (if (existsSync files-history) 37 | (edn/read-string (readFileSync files-history "utf-8")) 38 | #{})) 39 | 40 | (defn add-file [kafka-host] 41 | (let [known-hosts (read-known-files) 42 | updated (conj known-hosts kafka-host)] 43 | (writeFileSync files-history (pr-str updated) "utf-8"))) 44 | 45 | (defn get-known-files [_] 46 | (let [hosts (read-known-files)] 47 | (info "read known hosts" hosts) 48 | {:emit-msg [:reader/files hosts]})) 49 | 50 | (defn on-line [cmp-state put-chan put-fn] 51 | (fn [data] 52 | (try 53 | (swap! cmp-state update-in [:count] inc) 54 | (let [cnt (:count @cmp-state) 55 | parsed (edn/read-string data) 56 | {:keys [msg-type msg-payload msg-meta]} parsed 57 | msg (with-meta [msg-type msg-payload] msg-meta)] 58 | (go (>! put-chan msg)) 59 | (when (zero? (mod cnt 100)) 60 | (let [last-ts (:last-ts @cmp-state) 61 | now (stc/now) 62 | duration (- now last-ts) 63 | per-sec (Math/floor (/ 100 (/ duration 1000)))] 64 | (swap! cmp-state assoc-in [:last-ts] now) 65 | (put-fn [:reader/status 66 | {:status :connected 67 | :text (str per-sec " msg/s")}]) 68 | (info "Messages received:" cnt 69 | "-" per-sec "msg/s")))) 70 | (catch :default e (error "Something went wrong" e data))))) 71 | 72 | (defn start [{:keys [put-fn cmp-state put-chan current-state msg-payload] 73 | :as msg-map}] 74 | (info "Tailing" msg-payload) 75 | (try 76 | (stop msg-map) 77 | (put-fn [:reader/status {:status :starting 78 | :text (str "attempting to read " msg-payload)}]) 79 | (let [file msg-payload 80 | tail (new Tail file)] 81 | (info "Starting firehose file reader" file) 82 | (.on tail "error" #(error %)) 83 | (.on tail "line" (on-line cmp-state put-chan put-fn)) 84 | (.watch tail) 85 | (add-file file) 86 | {:new-state (assoc-in current-state [:tail] tail)}) 87 | (catch :default e (do (error "start fn" e) 88 | {:emit-msg [:reader/status 89 | {:status :error 90 | :text "Firehose Reader Error"}]})))) 91 | 92 | (defn cmp-map [cmp-id] 93 | {:cmp-id cmp-id 94 | :state-fn state-fn 95 | :handler-map {:tail/start start 96 | :tail/stop stop 97 | :tail/get-files get-known-files}}) 98 | -------------------------------------------------------------------------------- /src/cljs/inspect/main/runtime.cljs: -------------------------------------------------------------------------------- 1 | (ns inspect.main.runtime 2 | (:require [path :refer [normalize join]] 3 | [electron :refer [app]] 4 | [cljs.nodejs :as nodejs :refer [process]] 5 | [clojure.string :as s])) 6 | 7 | (def runtime-info 8 | (let [user-data (.getPath app "userData") 9 | cwd (.cwd process) 10 | rp (.-resourcesPath process) 11 | app-path (if (s/includes? (s/lower-case rp) "electron") 12 | cwd 13 | (str rp "/app")) 14 | repo-dir (s/includes? rp "Electron.app") 15 | platform (.-platform process) 16 | info {:platform (.-platform process) 17 | :downloads (.getPath app "downloads") 18 | :user-data user-data 19 | :bin-path (join user-data "bin") 20 | :cwd cwd 21 | :pid-file (str user-data "/inspect.pid") 22 | :resources-path rp 23 | :app-path app-path}] 24 | (into {:index-page (if repo-dir 25 | "./resources/view-dev.html" 26 | "/resources/view.html")} 27 | (map (fn [[k v]] [k (normalize v)]) info)))) 28 | -------------------------------------------------------------------------------- /src/cljs/inspect/main/sled.cljs: -------------------------------------------------------------------------------- 1 | (ns inspect.main.sled 2 | (:require [taoensso.timbre :refer-macros [info error debug]] 3 | [clojure.pprint :as pp] 4 | ;[neon-sled] 5 | [cognitect.transit :as t] 6 | [fs :refer [existsSync unlinkSync]])) 7 | 8 | (def DB_PATH "/tmp/inspect.db") 9 | 10 | (def r (t/reader :json)) 11 | (def w (t/writer :json)) 12 | 13 | #_(defn state-fn [put-fn] 14 | (when (existsSync DB_PATH) (unlinkSync DB_PATH)) 15 | (let [sled (neon-sled DB_PATH) 16 | state (atom {:db sled})] 17 | ;(.syncAndClose sled) 18 | {:state state})) 19 | 20 | #_(defn get-msg [{:keys [current-state msg-payload]}] 21 | (let [sled (:db current-state) 22 | {:keys [k]} msg-payload 23 | res (t/read r (.get sled (str k)))] 24 | (debug :get-msg msg-payload) 25 | {:emit-msg [:sled/res {:k k :v res}]})) 26 | 27 | #_(defn put-msg [{:keys [current-state msg-payload]}] 28 | (let [sled (:db current-state) 29 | {:keys [k v]} msg-payload] 30 | (.set sled (str k) (t/write w v)) 31 | (debug :put-msg msg-payload) 32 | {:emit-msg [:sled/res {:k k :status :saved}]})) 33 | 34 | (defn state-fn [put-fn] 35 | (let [state (atom {})] 36 | {:state state})) 37 | 38 | (defn get-msg [{:keys [current-state msg-payload]}] 39 | (let [k (str (:k msg-payload)) 40 | res (get current-state k)] 41 | (info "retrieved" k res) 42 | {:emit-msg [:sled/res {:k k :v res}]})) 43 | 44 | (defn put-msg [{:keys [current-state msg-payload]}] 45 | (let [{:keys [k v]} msg-payload 46 | new-state (assoc current-state k v)] 47 | {:new-state new-state 48 | :emit-msg [:sled/res {:k k :status :saved}]})) 49 | 50 | (defn cmp-map [cmp-id] 51 | {:cmp-id cmp-id 52 | :state-fn state-fn 53 | :handler-map {:sled/get get-msg 54 | :sled/put put-msg}}) 55 | -------------------------------------------------------------------------------- /src/cljs/inspect/main/startup.cljs: -------------------------------------------------------------------------------- 1 | (ns inspect.main.startup 2 | (:require [taoensso.timbre :as timbre :refer-macros [info debug error]] 3 | [electron :refer [app session]] 4 | [path :refer [normalize join]] 5 | [inspect.main.runtime :as rt] 6 | [child_process :refer [spawn]] 7 | [clojure.string :as str])) 8 | 9 | (defn shutdown [{:keys []}] 10 | (info "Shutting down") 11 | (.quit app) 12 | {}) 13 | 14 | (defn clear-cache [{:keys []}] 15 | (info "Clearing Electron Cache") 16 | (let [session (.-defaultSession session)] 17 | (.clearCache session #(info "Electron Cache Cleared"))) 18 | {}) 19 | 20 | (defn spawn-process [cmd args opts] 21 | (info "STARTUP: spawning" cmd args opts) 22 | (spawn cmd (clj->js args) (clj->js opts))) 23 | 24 | (defn run-zookeeper [{:keys [current-state]}] 25 | (info "STARTUP: run Zookeeper") 26 | (let [{:keys [user-data app-path cwd repo-dir downloads]} rt/runtime-info 27 | kafka-path (join downloads "kafka_2.11-0.10.1.1") 28 | zk-bin (join kafka-path "bin/zookeeper-server-start.sh") 29 | zk-conf (str kafka-path "/config/zookeeper.properties") 30 | java-home (join downloads "zulu8.23.0.3-jdk8.0.144-macosx_x64") 31 | service (spawn-process zk-bin 32 | [zk-conf] 33 | {:detached false 34 | :cwd kafka-path 35 | :env {:JAVA_HOME java-home}}) 36 | std-out (.-stdout service) 37 | std-err (.-stderr service)] 38 | (.on std-out "data" #(info "ZOOKEEPER " (.toString % "utf8"))) 39 | (.on std-err "data" #(error "ZOOKEEPER " (.toString % "utf8"))) 40 | {:new-state (assoc-in current-state [:zookeeper] service)})) 41 | 42 | (defn run-kafka [{:keys [current-state]}] 43 | (info "STARTUP: run Kafka") 44 | (let [{:keys [user-data app-path bin-path cwd downloads]} rt/runtime-info 45 | kafka-path (join downloads "kafka_2.11-0.10.1.1") 46 | bin (join kafka-path "bin/kafka-server-start.sh") 47 | conf (str kafka-path "/config/server.properties") 48 | java-home (join downloads "zulu8.23.0.3-jdk8.0.144-macosx_x64") 49 | service (spawn-process bin 50 | [conf] 51 | {:detached false 52 | :cwd kafka-path 53 | :env {:JAVA_HOME java-home}}) 54 | std-out (.-stdout service) 55 | std-out-handler #(info "KAFKA " (.toString % "utf8")) 56 | std-err (.-stderr service)] 57 | (.on std-out "data" std-out-handler) 58 | (.on std-err "data" #(error "KAFKA " (.toString % "utf8"))) 59 | {:new-state (assoc-in current-state [:zookeeper] service)})) 60 | 61 | (defn cmp-map [cmp-id] 62 | {:cmp-id cmp-id 63 | :handler-map {:app/shutdown shutdown 64 | :app/clear-cache clear-cache 65 | :run/zookeeper run-zookeeper 66 | :run/kafka run-kafka}}) 67 | 68 | (.on app "window-all-closed" 69 | (fn [ev] 70 | (info "window-all-closed") 71 | (when-not (= (:platform rt/runtime-info) "darwin") 72 | (shutdown {})))) 73 | -------------------------------------------------------------------------------- /src/cljs/inspect/main/store.cljs: -------------------------------------------------------------------------------- 1 | (ns inspect.main.store 2 | (:require [taoensso.timbre :refer-macros [info error debug]] 3 | [cljs.reader :refer [read-string]] 4 | [clojure.set :as set])) 5 | 6 | (defn state-fn [put-fn] 7 | (let [state (atom {:stats {:count 0 8 | :cmp-ids #{} 9 | :components {} 10 | :msg-types {}}})] 11 | {:state state})) 12 | 13 | (defn firehose-msg [{:keys [current-state msg-type msg-payload put-fn]}] 14 | (let [in-out (if (= msg-type :firehose/cmp-recv) :in :out) 15 | {:keys [cmp-id msg msg-meta firehose-id spec-error]} msg-payload 16 | firehose-type msg-type 17 | msg-type (first msg) 18 | add-edge (fn [prev-edges] 19 | (let [cmps (:cmp-seq msg-meta) 20 | [from to] (take-last 2 cmps) 21 | edges (if (and from 22 | to 23 | (or (= firehose-type :firehose/cmp-recv) 24 | (not= (namespace from) 25 | (namespace to)))) 26 | #{{:source (str from) 27 | :target (str to) 28 | :from from 29 | :to to 30 | :source-name (name from) 31 | :source-ns (namespace from) 32 | :target-name (name to) 33 | :target-ns (namespace to) 34 | :msg-type msg-type}} 35 | #{})] 36 | (debug (set/union prev-edges edges)) 37 | (set/union prev-edges edges))) 38 | inc-cnt #(inc (or % 0)) 39 | new-state (-> current-state 40 | (update-in [:stats :cnt] inc) 41 | (update-in [:stats :cmp-ids] conj cmp-id) 42 | (update-in [:stats :components cmp-id in-out msg-type] inc-cnt) 43 | (update-in [:stats :msg-types msg-type] inc-cnt) 44 | (update-in [:stats :edges] add-edge) 45 | (update-in [:stats :edges-by-type msg-type] add-edge)) 46 | subscription (:subscription current-state) 47 | map-stats (fn [m] 48 | (when (map? m) 49 | (into {} (map (fn [[k v]] [k (count (str v))]) m)))) 50 | msg-stats (map-stats (-> msg-payload :msg second)) 51 | type-and-size (-> msg-payload 52 | (update-in [:msg] (fn [[t m]] [t (count (str m))])) 53 | (assoc-in [:msg-stats] msg-stats) 54 | (update-in [:msg-meta :cmp-seq] #(take-last 20 %)) 55 | (assoc-in [:firehose-type] firehose-type)) 56 | match (when (-> type-and-size :msg-meta :tag) 57 | (with-meta [:subscription/match type-and-size] 58 | (:msg-meta subscription)))] 59 | (when match 60 | (debug "Subscription match:" match) (put-fn match)) 61 | (when spec-error 62 | (put-fn [:spec/error msg-payload])) 63 | {:new-state new-state 64 | :emit-msg [[:sled/put {:k (str firehose-id) :v msg-payload}]]})) 65 | 66 | (defn state-publish [{:keys [current-state put-fn]}] 67 | (let [stats (:stats current-state) 68 | {:keys [cnt prev-cnt]} stats 69 | new-state (-> current-state 70 | (assoc-in [:prev-cnt] cnt) 71 | (assoc-in [:prev-stats] stats)) 72 | svg-data (select-keys stats [:cmp-ids :edges])] 73 | (when (not= cnt prev-cnt) 74 | (debug "STORE received:" cnt) 75 | (put-fn (with-meta [:observer/cmps-msgs (:stats new-state)] 76 | {:window-id :broadcast}))) 77 | {:new-state new-state 78 | :emit-msg [:svg/gen-overview svg-data]})) 79 | 80 | (defn subscribe [{:keys [current-state msg-payload msg-meta]}] 81 | (let [subscription (assoc-in msg-payload [:msg-meta] msg-meta) 82 | new-state (assoc-in current-state [:subscription] subscription)] 83 | (info "OBSERVER: subscribe" msg-payload) 84 | {:new-state new-state})) 85 | 86 | (defn stop [{:keys [current-state]}] 87 | (let [new-state (assoc-in current-state [:subscription] nil)] 88 | (info "OBSERVER: subscription stopped") 89 | {:new-state new-state})) 90 | 91 | (defn cmp-map [cmp-id] 92 | {:cmp-id cmp-id 93 | :state-fn state-fn 94 | :handler-map {:firehose/cmp-recv firehose-msg 95 | :firehose/cmp-put firehose-msg 96 | :observer/subscribe subscribe 97 | :observer/stop stop 98 | :state/publish state-publish}}) 99 | -------------------------------------------------------------------------------- /src/cljs/inspect/main/update.cljs: -------------------------------------------------------------------------------- 1 | (ns inspect.main.update 2 | (:require [taoensso.timbre :as timbre :refer-macros [info debug]] 3 | [electron-log :as electron-log] 4 | [electron-updater :refer [autoUpdater]])) 5 | 6 | (defn set-feed [channel] 7 | (.setFeedURL autoUpdater (clj->js 8 | {:url "https://matthiasn-inspect.s3.amazonaws.com" 9 | :provider "s3" 10 | :bucket "matthiasn-inspect" 11 | :acl "public-read" 12 | :channel channel}))) 13 | 14 | (defn state-fn [put-fn] 15 | (let [state (atom {:open-window false}) 16 | put-fn (fn [msg] 17 | (let [msg-meta (merge {:window-id :broadcast} (meta msg))] 18 | (put-fn (with-meta msg msg-meta)))) 19 | no-update-available (fn [_] 20 | (info "Update not available.") 21 | (put-fn [:update/status {:status :update/not-available}])) 22 | update-available (fn [info] 23 | (let [info (js->clj info :keywordize-keys true)] 24 | (info "Update available.") 25 | (if (:open-window @state) 26 | (put-fn [:window/updater]) 27 | (put-fn [:update/status {:status :update/available 28 | :info info}])))) 29 | checking (fn [_] 30 | (info "Checking for update...") 31 | (put-fn [:update/status {:status :update/checking}])) 32 | downloaded (fn [ev] 33 | (info "Update downloaded") 34 | (put-fn [:update/status {:status :update/downloaded}])) 35 | downloading (fn [progress] 36 | (let [info (js->clj progress :keywordize-keys true)] 37 | (info "Update downloading" (str info)) 38 | (put-fn [:update/status {:status :update/downloading 39 | :info info}]))) 40 | error (fn [ev] 41 | (info "Error in auto-updater" ev) 42 | (put-fn [:update/status {:status :update/error}]))] 43 | (info "Starting UPDATE Component") 44 | (aset autoUpdater "autoDownload" false) 45 | (aset autoUpdater "logger" electron-log) 46 | (.on autoUpdater "checking-for-update" checking) 47 | (.on autoUpdater "update-available" update-available) 48 | (.on autoUpdater "update-not-available" no-update-available) 49 | (.on autoUpdater "update-downloaded" downloaded) 50 | (.on autoUpdater "download-progress" downloading) 51 | (.on autoUpdater "error" error) 52 | {:state state})) 53 | 54 | (defn check-updates [open-window] 55 | (fn [{:keys [current-state]}] 56 | (info "UPDATE: check release versions") 57 | (set-feed "release") 58 | (.checkForUpdates autoUpdater) 59 | {:new-state (assoc-in current-state [:open-window] open-window)})) 60 | 61 | (defn check-updates-beta [{:keys []}] 62 | (info "UPDATE: check beta versions") 63 | (set-feed "beta") 64 | (.checkForUpdates autoUpdater) 65 | {}) 66 | 67 | (defn download-updates [{:keys []}] 68 | (info "UPDATE: download") 69 | (.downloadUpdate autoUpdater) 70 | {}) 71 | 72 | (defn install-updates [{:keys []}] 73 | (info "UPDATE: install") 74 | {:emit-msg [[:app/clear-cache] 75 | [:cmd/schedule-new {:timeout 1000 76 | :message [:update/quit-install]}]]}) 77 | 78 | (defn quit-install [{:keys []}] 79 | (info "UPDATE: quit and install") 80 | (.quitAndInstall autoUpdater) 81 | {}) 82 | 83 | (defn cmp-map [cmp-id] 84 | {:cmp-id cmp-id 85 | :state-fn state-fn 86 | :handler-map {:update/check (check-updates false) 87 | :update/auto-check (check-updates true) 88 | :update/check-beta check-updates-beta 89 | :update/download download-updates 90 | :update/install install-updates 91 | :update/quit-install quit-install}}) 92 | 93 | -------------------------------------------------------------------------------- /src/cljs/inspect/specs/specs.cljs: -------------------------------------------------------------------------------- 1 | (ns inspect.specs.specs 2 | (:require [cljs.spec.alpha :as s] 3 | [matthiasn.systems-toolbox.spec])) 4 | 5 | (s/def :tail/start string?) 6 | (s/def :reader/status map?) 7 | 8 | (s/def :observer/cmps-msgs map?) 9 | (s/def :observer/subscribe map?) 10 | 11 | (s/def :subscription/match :firehose/cmp-recv) 12 | 13 | (s/def :inspect.update/status #{:update/available 14 | :update/not-available 15 | :update/checking 16 | :update/downloading 17 | :update/downloaded 18 | :update/error}) 19 | 20 | (s/def :update/status (s/keys :req-un [:inspect.update/status])) 21 | 22 | (s/def :sled/msg (s/keys :req-un [:inspect.sled/k] 23 | :opt-un [:inspect.sled/v 24 | :inspect.sled/status])) 25 | 26 | (s/def :sled/put :sled/msg) 27 | (s/def :sled/get :sled/msg) 28 | (s/def :sled/res :sled/msg) 29 | 30 | (s/def :svg/overview string?) 31 | (s/def :svg/gen-overview map?) 32 | 33 | (s/def :state/publish nil?) -------------------------------------------------------------------------------- /src/cljs/inspect/update/core.cljs: -------------------------------------------------------------------------------- 1 | (ns inspect.update.core 2 | (:require [inspect.specs.specs] 3 | [inspect.update.log :as log] 4 | [taoensso.timbre :as timbre :refer-macros [info]] 5 | [matthiasn.systems-toolbox-electron.ipc-renderer :as ipc] 6 | [matthiasn.systems-toolbox.switchboard :as sb] 7 | [inspect.update.ui :as ui])) 8 | 9 | (defonce switchboard (sb/component :updater/switchboard)) 10 | 11 | (def relay-types #{:update/check :update/check-beta :update/download 12 | :update/install :window/close}) 13 | 14 | (defn start [] 15 | (info "Starting UPDATER") 16 | (sb/send-mult-cmd 17 | switchboard 18 | [[:cmd/init-comp #{(ipc/cmp-map :updater/ipc-cmp relay-types) 19 | (ui/cmp-map :updater/ui-cmp)}] 20 | 21 | [:cmd/route {:from :updater/ipc-cmp 22 | :to #{:updater/ui-cmp}}] 23 | 24 | [:cmd/route {:from :updater/ui-cmp 25 | :to #{:updater/ipc-cmp}}]])) 26 | 27 | (.addEventListener js/window "load" #(start)) 28 | -------------------------------------------------------------------------------- /src/cljs/inspect/update/log.cljs: -------------------------------------------------------------------------------- 1 | (ns inspect.update.log 2 | (:require [taoensso.encore :as enc] 3 | [taoensso.timbre :as timbre])) 4 | 5 | (enable-console-print!) 6 | 7 | (defn ns-filter 8 | "From: https://github.com/yonatane/timbre-ns-pattern-level" 9 | [fltr] 10 | (-> fltr enc/compile-ns-filter taoensso.encore/memoize_)) 11 | 12 | (defn middleware 13 | "From: https://github.com/yonatane/timbre-ns-pattern-level" 14 | [ns-patterns] 15 | (fn log-by-ns-pattern [{:keys [?ns-str config level] :as opts}] 16 | (let [namesp (or (some->> ns-patterns 17 | keys 18 | (filter #(and (string? %) 19 | ((ns-filter %) ?ns-str))) 20 | not-empty 21 | (apply max-key count)) 22 | :all) 23 | log-level (get ns-patterns namesp (get config :level))] 24 | (when (and (taoensso.timbre/may-log? log-level namesp) 25 | (taoensso.timbre/level>= level log-level)) 26 | opts)))) 27 | 28 | ; See https://github.com/ptaoussanis/timbre 29 | (def timbre-config 30 | {:ns-whitelist [] #_["my-app.foo-ns"] 31 | :ns-blacklist [] #_["taoensso.*"] 32 | 33 | :middleware [(middleware {"inspect.view.ipc" :info 34 | "inspect.view.store" :info 35 | :all :info})] 36 | 37 | :appenders {:console {:enabled? true 38 | :fn (fn [data] 39 | (let [{:keys [output_]} data 40 | formatted-output-str (force output_)] 41 | (println formatted-output-str)))}}}) 42 | 43 | (timbre/merge-config! timbre-config) 44 | -------------------------------------------------------------------------------- /src/cljs/inspect/update/ui.cljs: -------------------------------------------------------------------------------- 1 | (ns inspect.update.ui 2 | (:require-macros [reagent.ratom :refer [reaction]]) 3 | (:require [reagent.core :as rc] 4 | [re-frame.core :refer [reg-sub subscribe]] 5 | [re-frame.db :as rdb] 6 | [taoensso.timbre :as timbre :refer-macros [info]])) 7 | 8 | ;; Subscription Handlers 9 | (reg-sub :current-page (fn [db _] (:current-page db))) 10 | 11 | (defn cancel-btn [put-fn] 12 | (let [cancel (fn [_] 13 | (info "Cancel button clicked") 14 | (put-fn [:window/close]))] 15 | [:button {:on-click cancel} "cancel"])) 16 | 17 | (defn checking [put-fn] 18 | [:div.updater 19 | [:h1 "Checking for latest version of inspect..."] 20 | [cancel-btn put-fn]]) 21 | 22 | (defn no-update [put-fn] 23 | (let [check (fn [_] 24 | (info "Check button clicked") 25 | (put-fn [:update/check])) 26 | check-beta (fn [_] 27 | (info "Check beta versions") 28 | (put-fn [:update/check-beta]))] 29 | [:div.updater 30 | [:h1 "You already have the latest version of inspect."] 31 | [cancel-btn put-fn] 32 | " " 33 | [:button {:on-click check} "check"] 34 | " " 35 | [:button {:on-click check-beta} "check for beta version"]])) 36 | 37 | (defn update-available [status-msg put-fn] 38 | (let [download (fn [_] 39 | (info "Download button clicked") 40 | (put-fn [:update/download])) 41 | {:keys [version releaseDate]} (:info status-msg)] 42 | [:div.updater 43 | [:h1 "New version of inspect available."] 44 | [:div.info 45 | [:div [:strong "Version: "] version] 46 | [:div [:strong "Release date: "] (subs releaseDate 0 10)]] 47 | [cancel-btn put-fn] 48 | " " 49 | [:button {:on-click download} "download"]])) 50 | 51 | (defn downloading [status-msg put-fn] 52 | (let [{:keys [total percent bytesPerSecond transferred]} (:info status-msg) 53 | mbs (/ (Math/floor (/ bytesPerSecond 1024 102.4)) 10) 54 | total (Math/floor (/ total 1024 1024)) 55 | transferred (Math/floor (/ transferred 1024 1024)) 56 | percent (Math/floor percent)] 57 | [:div.updater 58 | [:h1 "Downloading new version of inspect."] 59 | [:div.meter 60 | [:span {:style {:width (str percent "%")}}]] 61 | [:div.info 62 | [:div [:strong "Total size: "] total " MB"] 63 | [:div [:strong "Transferred: "] transferred " MB"] 64 | [:div [:strong "Progress: "] percent "%"] 65 | [:div [:strong "Speed: "] mbs " MB/s"]] 66 | [cancel-btn put-fn]])) 67 | 68 | (defn update-downloaded [put-fn] 69 | (let [install (fn [_] 70 | (info "Install button clicked") 71 | (put-fn [:update/install]))] 72 | [:div.updater 73 | [:h1 "New version of inspect ready to install."] 74 | [cancel-btn put-fn] 75 | " " 76 | [:button {:on-click install} "install"]])) 77 | 78 | (defn re-frame-ui [local put-fn] 79 | (let [current-page (subscribe [:current-page])] 80 | (fn [local put-fn] 81 | (let [status-msg (:status-msg @local) 82 | status (:status status-msg)] 83 | [:div.updater 84 | (case status 85 | :update/checking [checking put-fn] 86 | :update/not-available [no-update put-fn] 87 | :update/available [update-available status-msg put-fn] 88 | :update/downloading [downloading status-msg put-fn] 89 | :update/downloaded [update-downloaded put-fn] 90 | [:h1 "inspect updater: " (str status-msg)])])))) 91 | 92 | (defn state-fn [put-fn] 93 | (let [local (rc/atom {})] 94 | (rc/render [re-frame-ui local put-fn] (.getElementById js/document "app")) 95 | (put-fn [:update/check]) 96 | {:observed rdb/app-db 97 | :state local})) 98 | 99 | (defn set-status [{:keys [current-state msg msg-type msg-meta msg-payload]}] 100 | (let [new-state (assoc-in current-state [:status-msg] msg-payload)] 101 | {:new-state new-state})) 102 | 103 | (defn cmp-map [cmp-id] 104 | {:cmp-id cmp-id 105 | :state-fn state-fn 106 | :handler-map {:update/status set-status}}) 107 | -------------------------------------------------------------------------------- /src/cljs/inspect/view/core.cljs: -------------------------------------------------------------------------------- 1 | (ns inspect.view.core 2 | (:require [inspect.specs.specs] 3 | [inspect.view.log] 4 | [matthiasn.systems-toolbox-electron.ipc-renderer :as ipc] 5 | [inspect.view.store :as st] 6 | [inspect.view.ui :as ui] 7 | [matthiasn.systems-toolbox.switchboard :as sb] 8 | [matthiasn.systems-toolbox.scheduler :as sched] 9 | [taoensso.timbre :as timbre :refer-macros [info debug]])) 10 | 11 | (defonce switchboard (sb/component :updater/switchboard)) 12 | 13 | (def relay-types #{:update/check 14 | :update/check-beta 15 | :update/download 16 | :update/install 17 | :sled/get 18 | :sled/put 19 | :sled/bench 20 | :svg/set-active 21 | :tail/start 22 | :tail/stop 23 | :tail/get-files 24 | :observer/subscribe 25 | :observer/stop 26 | :window/close}) 27 | 28 | (defn start [] 29 | (info "Starting OBSERVER") 30 | (sb/send-mult-cmd 31 | switchboard 32 | [[:cmd/init-comp #{(ipc/cmp-map :observer/ipc relay-types) 33 | (st/cmp-map :observer/store) 34 | (sched/cmp-map :observer/scheduler) 35 | (ui/cmp-map :observer/ui)}] 36 | 37 | [:cmd/route {:from :observer/ipc 38 | :to #{:observer/ui 39 | :observer/store}}] 40 | 41 | [:cmd/route {:from :observer/ui 42 | :to #{:observer/ipc 43 | :observer/store}}] 44 | 45 | [:cmd/route {:from :observer/scheduler 46 | :to :observer/ipc}] 47 | 48 | [:cmd/observe-state {:from :observer/store 49 | :to :observer/ui}] 50 | 51 | [:cmd/send {:to :observer/ipc 52 | :msg [:tail/get-files]}]])) 53 | 54 | (.addEventListener js/window "load" #(start)) 55 | -------------------------------------------------------------------------------- /src/cljs/inspect/view/force.cljs: -------------------------------------------------------------------------------- 1 | (ns inspect.view.force 2 | (:require [d3 :as d3] 3 | [d3-force :refer [forceSimulation forceLink forceManyBody 4 | forceCenter forceCollide]] 5 | [re-frame.core :refer [subscribe]] 6 | [taoensso.timbre :as timbre :refer-macros [info debug]] 7 | [randomcolor] 8 | [reagent.core :as r] 9 | [inspect.view.util :as u] 10 | [clojure.set :as set])) 11 | 12 | (defn by-id [id] (.getElementById js/document id)) 13 | 14 | (defn components-force 15 | [canvas nodes links put-fn] 16 | (let [context (.getContext canvas "2d") 17 | width (.-width canvas) 18 | height (.-height canvas) 19 | sim (-> (forceSimulation) 20 | (.force "link" (-> (forceLink) 21 | (.id (fn [d] (.-id d))) 22 | (.strength 0.1))) 23 | (.force "charge" (forceManyBody)) 24 | (.force "collide" (-> (forceCollide) 25 | (.radius (fn [d] 26 | (+ (.-r d) 25) 27 | 35)) 28 | (.iterations 20))) 29 | (.force "center" (forceCenter (/ width 2) (/ height 2)))) 30 | draw-node (fn [d] 31 | (let [cmp-id (.-id d) 32 | color (u/random-color cmp-id)] 33 | (.beginPath context) 34 | (.moveTo context (.-x d) (.-y d)) 35 | (.arc context (.-x d) (.-y d) 15 0 (* 2 3.1415)) 36 | (.stroke context) 37 | (aset context "fillStyle" color) 38 | (.fill context))) 39 | draw-link (fn [d] 40 | (.beginPath context) 41 | (.moveTo context (.-x (.-source d)) (.-y (.-source d))) 42 | (.lineTo context (.-x (.-target d)) (.-y (.-target d))) 43 | (.stroke context)) 44 | ticked (fn [d] 45 | (.clearRect context 0 0 width height) 46 | (doseq [link links] 47 | (draw-link link)) 48 | (doseq [node nodes] 49 | (draw-node node))) 50 | run (fn [nodes links] 51 | (-> sim 52 | (.nodes nodes) 53 | (.on "tick" ticked) 54 | (.force "link") 55 | (.links links)))] 56 | (run nodes links) 57 | {:run run :sim sim})) 58 | 59 | (defn components-force-layout [_ put-fn] 60 | (let [render-fn (fn [] [:canvas#force {:width 600 :height 300}]) 61 | did-mount (fn [this]) 62 | did-update (fn [this] 63 | (let [elem (by-id "force") 64 | [_ nodes links] (r/argv this)] 65 | ;(.redraw (:word-cloud @local) (clj->js words)) 66 | (components-force elem nodes links put-fn)))] 67 | (r/create-class 68 | {:reagent-render render-fn 69 | :component-did-mount did-mount 70 | :component-did-update did-update 71 | :display-name "components-force-layout"}))) 72 | 73 | (defn wiring [put-fn] 74 | (let [cmp-ids (subscribe [:cmp-ids]) 75 | edges (subscribe [:edges])] 76 | (fn [put-fn] 77 | (let [links (clj->js (vec @edges)) 78 | sources (set (mapv :source @edges)) 79 | targets (set (mapv :target @edges)) 80 | nodes-set (set/union sources targets) 81 | nodes (clj->js (mapv (fn [id] {:id (str id)}) nodes-set))] 82 | [:div.force-wrapper 83 | [components-force-layout nodes links put-fn] 84 | [:ul 85 | (for [node nodes-set] 86 | ^{:key (str node)} 87 | [:li 88 | [:span.color {:style {:background-color (u/random-color node)}}] 89 | node])]])))) 90 | -------------------------------------------------------------------------------- /src/cljs/inspect/view/force2.cljs: -------------------------------------------------------------------------------- 1 | (ns inspect.view.force2 2 | (:require [d3 :as d3] 3 | [d3-ellipse-force :refer [ellipseForce]] 4 | [d3-force :refer [forceSimulation forceLink forceCenter]] 5 | [re-frame.core :refer [subscribe]] 6 | [taoensso.timbre :as timbre :refer-macros [info debug]] 7 | [randomcolor] 8 | [reagent.core :as r] 9 | [inspect.view.util :as u] 10 | [clojure.set :as set] 11 | [reagent.core :as rc])) 12 | 13 | (defn components-force 14 | [nodes links put-fn] 15 | (let [svg (d3/select "#force2") 16 | width (js/parseInt (.attr svg "width")) 17 | height (js/parseInt (.attr svg "height")) 18 | color (d3/scaleOrdinal d3/schemeCategory20) 19 | sim (-> (forceSimulation) 20 | (.force "link" (-> (forceLink) 21 | (.id (fn [d] (.-id d))) 22 | (.strength 0.15))) 23 | (.force "charge" (ellipseForce 6 1.5 6)) 24 | (.force "collide" (ellipseForce 6 1.5 6)) 25 | (.force "center" (forceCenter (/ width 2) (/ height 2)))) 26 | link (-> svg 27 | (.append "g") 28 | (.attr "class" "link") 29 | (.selectAll "line") 30 | (.data links) 31 | (.enter) 32 | (.append "line") 33 | (.attr "stroke-width" (fn [d] 2)) 34 | (.attr "stroke" (fn [d] "#999"))) 35 | 36 | drag-started (fn [d] 37 | (let [active (.-active d3/event) 38 | x (.-x d3/event) 39 | y (.-y d3/event)] 40 | ;(when-not) 41 | (-> sim 42 | (.alphaTarget 0.3) 43 | (.restart)) 44 | (info "drag-started" x y active) 45 | (aset d "fx" (.-x d)) 46 | (aset d "fy" (.-y d))) 47 | ) 48 | dragged (fn [d] 49 | (let [active (.-active d3/event) 50 | x (.-x d3/event) 51 | y (.-y d3/event)] 52 | ;(info "drag" x y active) 53 | (aset d "fx" x) 54 | (aset d "x" x) 55 | (aset d "fy" y))) 56 | drag-end (fn [d] 57 | (info "drag-end" d) 58 | (-> sim 59 | (.alphaTarget 0)) 60 | (aset d "fx" nil) 61 | (aset d "fy" nil)) 62 | node (-> svg 63 | (.append "g") 64 | (.attr "class" "node") 65 | (.selectAll "ellipse") 66 | (.data nodes (fn [d] (.-id d))) 67 | (.enter) 68 | (.append "ellipse") 69 | (.attr "rx" (fn [d] (.-rx d))) 70 | (.attr "ry" (fn [d] (.-ry d))) 71 | (.attr "fill" (fn [d] (u/random-color (.-id d)))) 72 | (.call (-> (d3/drag) 73 | (.on "start" drag-started) 74 | (.on "drag" dragged) 75 | (.on "end" drag-end)))) 76 | text (-> svg 77 | (.append "g") 78 | (.attr "class" "labels") 79 | (.selectAll "text") 80 | (.data nodes) 81 | (.enter) 82 | (.append "text") 83 | (.attr "dy" 2) 84 | (.attr "text-anchor" "middle") 85 | (.attr "stroke" (fn [d] "#444")) 86 | (.attr "fill" "white") 87 | (.text (fn [d] (.-id d)))) 88 | ticked (fn [d] 89 | (-> link 90 | (.attr "x1" (fn [d] (aget d "source" "x"))) 91 | (.attr "y1" (fn [d] (aget d "source" "y"))) 92 | (.attr "x2" (fn [d] (aget d "target" "x"))) 93 | (.attr "y2" (fn [d] (aget d "target" "y")))) 94 | (-> node 95 | (.attr "cx" (fn [d] (.-x d))) 96 | (.attr "cy" (fn [d] (.-y d)))) 97 | (-> text 98 | (.attr "x" (fn [d] (.-x d))) 99 | (.attr "y" (fn [d] (.-y d))))) 100 | run (fn [nodes links] 101 | (-> sim 102 | (.nodes nodes) 103 | (.on "tick" ticked) 104 | (.force "link") 105 | (.links links)))] 106 | (run nodes links) 107 | {:run run :sim sim})) 108 | 109 | (defn components-force-layout [_ put-fn] 110 | (let [local (rc/atom {}) 111 | render-fn (fn [] [:svg#force2 {:width 900 :height 500}]) 112 | did-mount (fn [this] 113 | (let [[_ nodes links] (r/argv this) 114 | force-layout (components-force nodes links put-fn)] 115 | (info "did-mount") 116 | (reset! local force-layout))) 117 | did-update (fn [this] 118 | (let [run (:run @local) 119 | [_ nodes links] (r/argv this)] 120 | (info "did-update") 121 | (components-force nodes links put-fn) 122 | (run nodes links)))] 123 | (r/create-class 124 | {:reagent-render render-fn 125 | :component-did-mount did-mount 126 | :component-did-update did-update 127 | :display-name "components-force-layout"}))) 128 | 129 | (defn wiring [put-fn] 130 | (let [cmp-ids (subscribe [:cmp-ids]) 131 | edges (subscribe [:edges])] 132 | (fn [put-fn] 133 | (let [links (clj->js (vec @edges)) 134 | sources (set (mapv :source @edges)) 135 | targets (set (mapv :target @edges)) 136 | nodes-set (set/union sources targets) 137 | nodes (clj->js (mapv (fn [id] 138 | {:id (str id) 139 | :rx (* 5 (count (str id))) 140 | :ry 12 141 | :group (namespace (keyword id))}) 142 | nodes-set))] 143 | [:div ;.force-wrapper 144 | [components-force-layout nodes links put-fn] 145 | [:ul 146 | (for [node nodes-set] 147 | ^{:key (str node)} 148 | [:li 149 | [:span.color {:style {:background-color (u/random-color node)}}] 150 | node])]])))) 151 | -------------------------------------------------------------------------------- /src/cljs/inspect/view/graphviz.cljs: -------------------------------------------------------------------------------- 1 | (ns inspect.view.graphviz 2 | (:require-macros [reagent.ratom :refer [reaction]]) 3 | (:require [viz.js :as Viz] 4 | [re-frame.core :refer [subscribe]] 5 | [taoensso.timbre :as timbre :refer-macros [info debug]] 6 | [randomcolor] 7 | [clojure.string :as str] 8 | [reagent.core :as r] 9 | [cljs.pprint :as pp] 10 | [clojure.set :as set] 11 | [inspect.view.util :as u])) 12 | 13 | (defn by-id [id] (.getElementById js/document id)) 14 | 15 | (defn sanitize [cmp-id] (str "\"" cmp-id "\"")) 16 | 17 | (defn sub-graph [[label nodes]] 18 | (let [cluster-name (str "cluster_" label)] 19 | (str "subgraph " cluster-name " { label =< " label " >; " 20 | (apply str (map (fn [n] (str n "; ")) nodes)) 21 | "} "))) 22 | 23 | (defn inner-wiring-view [_ put-fn] 24 | (info :inner-wiring-view) 25 | (let [render-fn (fn [] (info :reagent-render) [:div#graphviz1]) 26 | did-mount (fn [] (info :component-did-mount)) 27 | did-update (fn [this] 28 | (time 29 | (let [[_ svg] (r/argv this) 30 | svg-elem (by-id "graphviz1")] 31 | (aset svg-elem "innerHTML" svg))))] 32 | (r/create-class 33 | {:reagent-render render-fn 34 | :component-did-mount did-mount 35 | :component-did-update did-update 36 | :display-name "wiring-view"}))) 37 | 38 | (defn wiring-view [_put-fn] 39 | (let [svg-overview (subscribe [:svg-overview])] 40 | (fn [put-fn] 41 | [:div 42 | [inner-wiring-view @svg-overview put-fn]]))) 43 | 44 | (defn inner-wiring-view2 [_ put-fn] 45 | (info :inner-wiring-view2) 46 | (let [render-fn (fn [] (info :reagent-render) [:div#graphviz2]) 47 | did-mount-or-update (fn [this] 48 | (let [[_ digraph] (r/argv this) 49 | svg-elem (by-id "graphviz2") 50 | svg (Viz digraph)] 51 | (aset svg-elem "innerHTML" svg)))] 52 | (r/create-class 53 | {:reagent-render render-fn 54 | :component-did-mount did-mount-or-update 55 | :component-did-update did-mount-or-update 56 | :display-name "wiring-view2"}))) 57 | 58 | (defn flow-graph [put-fn] 59 | (let [msg-flow (subscribe [:show-flow]) 60 | msgs (reaction (vals (:msgs @msg-flow))) 61 | find-edge (fn [{:keys [msg-meta msg firehose-type]}] 62 | (let [cmps (:cmp-seq msg-meta) 63 | [from to] (take-last 2 cmps) 64 | msg-type (first msg)] 65 | (if (and from 66 | to 67 | (or (= firehose-type :firehose/cmp-recv) 68 | (not= (namespace from) (namespace to)))) 69 | #{{:source (str from) 70 | :target (str to) 71 | :from from 72 | :to to 73 | :source-name (name from) 74 | :source-ns (namespace from) 75 | :target-name (name to) 76 | :target-ns (namespace to) 77 | :msg-type msg-type}} 78 | #{}))) 79 | edges (reaction (apply set/union (map find-edge @msgs))) 80 | active-type (subscribe [:active-type]) 81 | edge-mapper (fn edge-mapper [{:keys [source target msg-type]}] 82 | (let [color (u/chf msg-type u/colors)] 83 | (prn color msg-type) 84 | (str (sanitize source) " -> " (sanitize target) 85 | " [color=" color 86 | " penwidth=2 label=" (sanitize msg-type) 87 | " fontcolor=" color 88 | " fontsize=8]" 89 | "; "))) 90 | links (reaction (apply str (map edge-mapper @edges))) 91 | clusters (reaction 92 | (reduce 93 | (fn [acc in] 94 | (let [{:keys [source-ns source target-ns target]} in] 95 | (-> acc 96 | (update-in [source-ns] #(set (conj % (sanitize source)))) 97 | (update-in [target-ns] #(set (conj % (sanitize target))))))) 98 | {} 99 | @edges)) 100 | sub-graphs (reaction (map sub-graph @clusters)) 101 | digraph (reaction (str "digraph { " @links (apply str @sub-graphs) "}"))] 102 | (fn [put-fn] 103 | (let [] 104 | [:div 105 | [inner-wiring-view2 @digraph put-fn] 106 | ;[:pre [:code (with-out-str (pp/pprint @edges))]] 107 | ])))) -------------------------------------------------------------------------------- /src/cljs/inspect/view/log.cljs: -------------------------------------------------------------------------------- 1 | (ns inspect.view.log 2 | (:require [taoensso.encore :as enc] 3 | [taoensso.timbre :as timbre])) 4 | 5 | (enable-console-print!) 6 | 7 | (defn ns-filter 8 | "From: https://github.com/yonatane/timbre-ns-pattern-level" 9 | [fltr] 10 | (-> fltr enc/compile-ns-filter taoensso.encore/memoize_)) 11 | 12 | (defn middleware 13 | "From: https://github.com/yonatane/timbre-ns-pattern-level" 14 | [ns-patterns] 15 | (fn log-by-ns-pattern [{:keys [?ns-str config level] :as opts}] 16 | (let [namesp (or (some->> ns-patterns 17 | keys 18 | (filter #(and (string? %) 19 | ((ns-filter %) ?ns-str))) 20 | not-empty 21 | (apply max-key count)) 22 | :all) 23 | log-level (get ns-patterns namesp (get config :level))] 24 | (when (and (taoensso.timbre/may-log? log-level namesp) 25 | (taoensso.timbre/level>= level log-level)) 26 | opts)))) 27 | 28 | ; See https://github.com/ptaoussanis/timbre 29 | (def timbre-config 30 | {:ns-whitelist [] #_["my-app.foo-ns"] 31 | :ns-blacklist [] #_["taoensso.*"] 32 | 33 | :middleware [(middleware {"inspect.view.ipc" :info 34 | "inspect.view.store" :info 35 | :all :info})] 36 | 37 | :appenders {:console {:enabled? true 38 | :fn (fn [data] 39 | (let [{:keys [output_]} data 40 | formatted-output-str (force output_)] 41 | (println formatted-output-str)))}}}) 42 | 43 | (timbre/merge-config! timbre-config) 44 | -------------------------------------------------------------------------------- /src/cljs/inspect/view/store.cljs: -------------------------------------------------------------------------------- 1 | (ns inspect.view.store 2 | (:require [taoensso.timbre :refer-macros [info debug]] 3 | [linked.core :as linked] 4 | [clojure.data.avl :as avl] 5 | [inspect.view.util :as u])) 6 | 7 | (defn cmps-msgs [{:keys [current-state msg-payload]}] 8 | (let [prev (:cmps-msgs current-state) 9 | new-state (-> current-state 10 | (assoc-in [:cmps-msgs] msg-payload) 11 | (assoc-in [:cmps-msgs-prev] prev))] 12 | (info "Received:" (keys msg-payload)) 13 | {:new-state new-state})) 14 | 15 | (defn freeze [{:keys [current-state msg]}] 16 | (let [prev (:cmps-msgs current-state) 17 | new-state (assoc-in current-state [:frozen] prev)] 18 | (info msg) 19 | {:new-state new-state})) 20 | 21 | (defn clear [{:keys [current-state]}] 22 | (let [new-state (assoc-in current-state [:ordered-msgs] (linked/map))] 23 | {:new-state new-state})) 24 | 25 | (defn cell-active [{:keys [current-state msg-payload]}] 26 | (let [active #(when-not (= % msg-payload) msg-payload) 27 | new-state (update-in current-state [:active-type] active)] 28 | (info "activated" (:active-type new-state)) 29 | {:new-state new-state})) 30 | 31 | (defn active-cmps [{:keys [current-state msg-payload]}] 32 | (let [active #(if (contains? % msg-payload) 33 | (disj % msg-payload) 34 | (conj % msg-payload)) 35 | new-state (update-in current-state [:active-cmps] active)] 36 | (info "active components" (:active-cmps new-state)) 37 | {:new-state new-state})) 38 | 39 | (defn kafka-status [{:keys [current-state msg-payload]}] 40 | (let [new-state (assoc-in current-state [:kafka-status] msg-payload)] 41 | (debug msg-payload) 42 | {:new-state new-state})) 43 | 44 | (defn svg-overview [{:keys [current-state msg-payload]}] 45 | (let [new-state (assoc-in current-state [:svg-overview] msg-payload)] 46 | (debug msg-payload) 47 | {:new-state new-state})) 48 | 49 | (defn show-flow [{:keys [current-state msg-payload]}] 50 | (let [toggle #(when-not (= (:tag msg-payload) (:tag %)) msg-payload) 51 | new-state (update-in current-state [:show-flow] toggle)] 52 | (debug msg-payload) 53 | {:new-state new-state})) 54 | 55 | (defn update-flow [state tag tag-ts] 56 | (let [msgs (get-in state [:ordered-msgs tag]) 57 | first-ts (apply min (map #(-> % second :ts) msgs)) 58 | first-seen (u/format-time first-ts) 59 | last-ts (apply max (map #(-> % second :ts) msgs)) 60 | max-size (apply max (map #(-> % second :msg second) msgs)) 61 | max-per-type (reduce (fn [acc m] 62 | (let [msg-type (-> m second :msg first) 63 | size (-> m second :msg second)] 64 | (update-in acc [msg-type] max size))) 65 | {} 66 | msgs) 67 | processing-time (apply + (map #(-> % second :duration) msgs)) 68 | last-seen (u/format-time last-ts) 69 | duration (- last-ts first-ts)] 70 | {:duration duration 71 | :tag tag 72 | :tag-ts tag-ts 73 | :msgs msgs 74 | :first-seen first-seen 75 | :first-seen-ts first-ts 76 | :last-seen last-seen 77 | :processing-time processing-time 78 | :max-per-type max-per-type 79 | :max-size max-size})) 80 | 81 | (defn match [{:keys [current-state msg-payload]}] 82 | (let [firehose-id (:firehose-id msg-payload) 83 | ; TODO: all messages MUST have a tag -> s-t (see publish state) 84 | tag (-> msg-payload :msg-meta :tag) 85 | tag-ts (-> msg-payload :msg-meta :tag-ts) 86 | new-state (-> current-state 87 | (assoc-in [:ordered-msgs tag firehose-id] msg-payload)) 88 | flow (update-flow new-state tag tag-ts) 89 | new-state (-> new-state 90 | (assoc-in [:flows tag] flow) 91 | (assoc-in [:avl-map tag-ts tag] flow))] 92 | (debug "Match" msg-payload) 93 | {:new-state new-state})) 94 | 95 | (defn msg-res [{:keys [current-state msg-payload]}] 96 | (let [new-state (if-let [v (:v msg-payload)] 97 | (assoc-in current-state [:detailed-msg] (:v msg-payload)) 98 | (update-in current-state [:db-counter] inc))] 99 | (debug :msg-res msg-payload) 100 | {:new-state new-state})) 101 | 102 | (defn spec-error [{:keys [current-state msg-payload]}] 103 | (let [new-state (update-in current-state [:spec-errors] conj msg-payload)] 104 | (info :spec-error msg-payload) 105 | {:new-state new-state})) 106 | 107 | (defn save-hosts [{:keys [current-state msg-payload]}] 108 | (let [new-state (assoc-in current-state [:known-hosts] msg-payload)] 109 | {:new-state new-state})) 110 | 111 | (defn cmp-map [cmp-id] 112 | {:cmp-id cmp-id 113 | :state-fn (fn [_] {:state (atom {:ordered-msgs (linked/map) 114 | :flows (linked/map) 115 | :db-counter 0 116 | :active-cmps #{} 117 | :avl-map (avl/sorted-map)})}) 118 | :handler-map {:observer/cmps-msgs cmps-msgs 119 | :cell/active cell-active 120 | :cmp/active active-cmps 121 | :state/freeze freeze 122 | :state/clear clear 123 | :flow/show show-flow 124 | :subscription/match match 125 | :sled/res msg-res 126 | :spec/error spec-error 127 | :svg/overview svg-overview 128 | :reader/files save-hosts 129 | :reader/status kafka-status}}) 130 | -------------------------------------------------------------------------------- /src/cljs/inspect/view/ui.cljs: -------------------------------------------------------------------------------- 1 | (ns inspect.view.ui 2 | (:require-macros [reagent.ratom :refer [reaction]]) 3 | (:require [reagent.core :as r] 4 | [re-frame.core :refer [reg-sub subscribe]] 5 | [re-frame.db :as rdb] 6 | [taoensso.timbre :refer-macros [info debug]] 7 | [inspect.view.graphviz :as gv] 8 | [inspect.view.ui.matches :as um] 9 | [inspect.view.ui.detail :as ud] 10 | [inspect.view.ui.flow :as uf] 11 | [inspect.view.ui.cmp :as uc] 12 | [inspect.view.util :as u] 13 | [clojure.pprint :as pp] 14 | [clojure.string :as s])) 15 | 16 | ;; Subscription Handlers 17 | (reg-sub :cmps-msgs (fn [db _] (:cmps-msgs db))) 18 | (reg-sub :spec-errors (fn [db _] (:spec-errors db))) 19 | (reg-sub :flows (fn [db _] (:flows db))) 20 | (reg-sub :detailed-msg (fn [db _] (:detailed-msg db))) 21 | (reg-sub :svg-overview (fn [db _] (:svg-overview db))) 22 | (reg-sub :avl-map (fn [db _] (:avl-map db))) 23 | (reg-sub :show-flow (fn [db _] (:show-flow db))) 24 | (reg-sub :ordered-msgs (fn [db _] (:ordered-msgs db))) 25 | (reg-sub :active-type (fn [db _] (:active-type db))) 26 | (reg-sub :active-cmps (fn [db _] (:active-cmps db))) 27 | (reg-sub :kafka-status (fn [db _] (:kafka-status db))) 28 | (reg-sub :known-hosts (fn [db _] (:known-hosts db))) 29 | (reg-sub :components (fn [db _] (:components (:cmps-msgs db)))) 30 | (reg-sub :prev-cmps (fn [db _] (:components (:cmps-msgs-prev db)))) 31 | (reg-sub :frozen (fn [db _] (:components (:frozen db)))) 32 | (reg-sub :cmp-ids (fn [db _] (:cmp-ids (:cmps-msgs db)))) 33 | (reg-sub :msg-types (fn [db _] (:msg-types (:cmps-msgs db)))) 34 | (reg-sub :edges (fn [db _] (:edges (:cmps-msgs db)))) 35 | (reg-sub :cnt (fn [db _] (-> db :cmps-msgs :cnt))) 36 | (reg-sub :db-counter (fn [db _] (-> db :db-counter))) 37 | 38 | (defn msg-types [put-fn] 39 | (let [msg-types (subscribe [:msg-types]) 40 | active-type (subscribe [:active-type]) 41 | sort-fns {:by-name first 42 | :by-cnt last} 43 | local (r/atom {:sort :by-cnt 44 | :search "" 45 | :ascending true}) 46 | input-fn (fn [ev] 47 | (let [s (-> ev .-nativeEvent .-target .-value)] 48 | (swap! local assoc-in [:search] s)))] 49 | (fn msg-types-render [put-fn] 50 | (let [active-type @active-type 51 | sort-fn (get sort-fns (:sort @local)) 52 | filtered (filter (fn [[k _]] (s/includes? (str k) (:search @local ""))) 53 | @msg-types) 54 | sorted (sort-by sort-fn filtered) 55 | sorted (if (:ascending @local) sorted (reverse sorted))] 56 | [:div.msg-types 57 | [:input {:type :text 58 | :on-change input-fn 59 | :value (:search @local)}] 60 | [:table 61 | [:tbody 62 | [:tr 63 | [:th 64 | "message type" 65 | [:i.fas {:class (str (if (:ascending @local) 66 | "fa-sort-alpha-down" 67 | "fa-sort-alpha-up") 68 | (when (= :by-name (:sort @local)) 69 | " sort-active")) 70 | :on-click #(do 71 | (swap! local assoc :sort :by-name) 72 | (swap! local update :ascending not))}]] 73 | [:th 74 | [:i.fas {:class (str (if (:ascending @local) 75 | "fa-sort-amount-down" 76 | "fa-sort-amount-up") 77 | (when (= :by-cnt (:sort @local)) 78 | " sort-active")) 79 | :on-click #(do 80 | (swap! local assoc :sort :by-cnt) 81 | (swap! local update :ascending not))}]]] 82 | (for [[msg-type cnt] sorted] 83 | ^{:key (str msg-type)} 84 | [:tr {:on-click #(let [subscription {:msg-type msg-type}] 85 | (if (= msg-type active-type) 86 | (put-fn [:observer/stop]) 87 | (put-fn [:observer/subscribe subscription])) 88 | (put-fn [:svg/set-active msg-type]) 89 | (put-fn [:cell/active msg-type]))} 90 | [:td.cmp-id {:class (when (= msg-type active-type) "active")} 91 | (str msg-type)] 92 | [:td.cnt cnt]])]]])))) 93 | 94 | (defn component-filter [put-fn] 95 | (let [cmp-ids (subscribe [:cmp-ids]) 96 | active-cmps (subscribe [:active-cmps]) 97 | local (r/atom {:search "" 98 | :ascending true}) 99 | input-fn (fn [ev] 100 | (let [s (-> ev .-nativeEvent .-target .-value)] 101 | (swap! local assoc-in [:search] s)))] 102 | (fn component-filter-render [put-fn] 103 | (let [active-cmps @active-cmps 104 | filtered (filter (fn [k] (s/includes? (str k) (:search @local ""))) 105 | @cmp-ids) 106 | sorted (sort filtered) 107 | sorted (if (:ascending @local) sorted (reverse sorted))] 108 | [:div.msg-types 109 | [:input {:type :text 110 | :on-change input-fn 111 | :value (:search @local)}] 112 | [:table 113 | [:tbody 114 | [:tr 115 | [:th 116 | "Components" 117 | [:i.fas {:class (str (if (:ascending @local) 118 | "fa-sort-alpha-down" 119 | "fa-sort-alpha-up") 120 | (when (= :by-name (:sort @local)) 121 | " sort-active")) 122 | :on-click #(do 123 | (swap! local assoc :sort :by-name) 124 | (swap! local update :ascending not))}]]] 125 | (for [cmp-id sorted] 126 | ^{:key (str cmp-id)} 127 | [:tr {:on-click #(put-fn [:cmp/active cmp-id])} 128 | [:td.cmp-id {:class (when (contains? active-cmps cmp-id) "active")} 129 | (str cmp-id)]])]]])))) 130 | 131 | (defn re-frame-ui [put-fn] 132 | (let [cmp-ids (subscribe [:cmp-ids]) 133 | count (subscribe [:cnt]) 134 | spec-errors (subscribe [:spec-errors]) 135 | kafka-status (subscribe [:kafka-status]) 136 | active-cmps (subscribe [:active-cmps]) 137 | freeze #(put-fn [:state/freeze]) 138 | clear #(put-fn [:state/clear])] 139 | (fn [_] 140 | (let [] 141 | [:div.grid.observer 142 | [:div.wrapper 143 | [:div.menu 144 | [:div.section 145 | [:div.header 146 | (let [cnt @count] 147 | (when (pos? cnt) 148 | [:div.cnt [:strong cnt] " messages analyzed"]))] 149 | [:div.status {:class (when (= :error (:status @kafka-status)) "error")} 150 | (:text @kafka-status)]]] 151 | [:div.col-1 152 | [gv/wiring-view put-fn]] 153 | [:div.col-2 154 | (when @cmp-ids 155 | [:div.section.msg-cmp 156 | [:div.filters 157 | [component-filter put-fn] 158 | [msg-types put-fn]] 159 | (let [filtered (if (not-empty @active-cmps) 160 | @active-cmps 161 | @cmp-ids) 162 | sorted (sort filtered)] 163 | [:div 164 | (for [cmp-id sorted ] 165 | ^{:key (str cmp-id)} 166 | [uc/component-table cmp-id put-fn])])]) 167 | [:div 168 | [:button.freeze {:on-click freeze} [:i.fas.fa-bolt] "freeze"] 169 | [:button.clear {:on-click clear} [:i.fas.fa-trash] "clear"]]] 170 | [:div.col-3 171 | [um/matches put-fn]] 172 | [:div.col-4 173 | [gv/flow-graph put-fn]] 174 | [:div.col-5 175 | [uf/msg-flow put-fn]] 176 | [:div.col-6 177 | [ud/detailed-msg put-fn]] 178 | [:div.col-7.spec-errors 179 | [:h2 "Spec Validation Errors"] 180 | (for [err @spec-errors] 181 | (let [{:keys [firehose-id msg spec-error]} err 182 | click #(put-fn [:sled/get {:k firehose-id}])] 183 | ^{:key firehose-id} 184 | [:div {:on-click click} 185 | [:div.header 186 | [:time (u/format-time (:ts err))] 187 | (str (first msg))] 188 | [:pre [:code spec-error]]]))]]])))) 189 | 190 | (defn state-fn 191 | "Renders main view component and wires the central re-frame app-db as the 192 | observed component state, which will then be updated whenever the store-cmp 193 | changes." 194 | [put-fn] 195 | (r/render [re-frame-ui put-fn] (.getElementById js/document "app")) 196 | {:observed rdb/app-db}) 197 | 198 | (defn cmp-map [cmp-id] 199 | {:cmp-id cmp-id 200 | :state-fn state-fn}) 201 | -------------------------------------------------------------------------------- /src/cljs/inspect/view/ui/cmp.cljs: -------------------------------------------------------------------------------- 1 | (ns inspect.view.ui.cmp 2 | (:require-macros [reagent.ratom :refer [reaction]]) 3 | (:require [re-frame.core :refer [subscribe]] 4 | [taoensso.timbre :refer-macros [info debug]] 5 | [cljs.pprint :as pp])) 6 | 7 | (defn cnt-cell 8 | [cmp-id msg-type dir] 9 | (let [components (subscribe [:components]) 10 | cnt (reaction (-> @components cmp-id dir msg-type)) 11 | prev-cmps (subscribe [:prev-cmps]) 12 | prev-cnt (reaction (-> @prev-cmps cmp-id dir msg-type))] 13 | (fn [cmp-id msg-type dir] 14 | (let [changed (not= @cnt @prev-cnt)] 15 | [:td.cnt {:class (when changed "changed")} 16 | @cnt])))) 17 | 18 | (defn delta-cell 19 | [cmp-id msg-type dir] 20 | (let [components (subscribe [:components]) 21 | cnt (reaction (-> @components cmp-id dir msg-type)) 22 | frozen-cmps (subscribe [:frozen]) 23 | frozen-cnt (reaction (-> @frozen-cmps cmp-id dir msg-type))] 24 | (fn [cmp-id msg-type dir] 25 | (let [delta (- @cnt @frozen-cnt)] 26 | [:td.cnt (if (and @frozen-cnt (pos? delta)) 27 | [:strong (str "+" delta)] 28 | " ")])))) 29 | 30 | (defn msg-table 31 | [cmp-id dir put-fn] 32 | (let [components (subscribe [:components]) 33 | active-type (subscribe [:active-type]) 34 | cmp-map (reaction (cmp-id @components))] 35 | (fn [cmp-id dir put-fn] 36 | (let [active-type @active-type] 37 | [:table 38 | [:tbody 39 | (for [[msg-type cnt] (dir @cmp-map)] 40 | ^{:key (str msg-type)} 41 | [:tr 42 | [:td.dir [:strong (str dir)]] 43 | [:td.cmp-id {:on-click #(let [subscription {:msg-type msg-type 44 | :cmp-id cmp-id 45 | :dir dir}] 46 | (if (= msg-type active-type) 47 | (put-fn [:observer/stop]) 48 | (put-fn [:observer/subscribe subscription])) 49 | (put-fn [:svg/set-active msg-type]) 50 | (put-fn [:cell/active msg-type])) 51 | :class (when (= msg-type active-type) "active")} 52 | (str msg-type)] 53 | [cnt-cell cmp-id msg-type dir] 54 | [delta-cell cmp-id msg-type dir]])]])))) 55 | 56 | (defn component-table [cmp-id put-fn] 57 | (let [components (subscribe [:components])] 58 | (fn [cmp-id put-fn] 59 | [:div.cmp-table 60 | [:h2 (str cmp-id)] 61 | [:div.tables 62 | [msg-table cmp-id :in put-fn] 63 | [msg-table cmp-id :out put-fn]]]))) -------------------------------------------------------------------------------- /src/cljs/inspect/view/ui/detail.cljs: -------------------------------------------------------------------------------- 1 | (ns inspect.view.ui.detail 2 | "Slightly modified from https://github.com/kamituel/systems-toolbox-chrome" 3 | (:require-macros [reagent.ratom :refer [reaction]]) 4 | (:require [re-frame.core :refer [subscribe]] 5 | [taoensso.timbre :as timbre :refer-macros [info debug]] 6 | [inspect.view.util :as u] 7 | [cljs.pprint :as pp] 8 | [reagent.core :as r])) 9 | 10 | (defn data->hiccup 11 | "Converts an arbitrary EDN data structure to the HTML where each element (i.e. map, vector, 12 | sequence, number, string) are wrapped in DOM elements such as DIV's or SPAN's so they are 13 | easy to style using CSS." 14 | ([data expanded-path on-expand-fn] 15 | (data->hiccup data expanded-path on-expand-fn [])) 16 | ([data expanded-path on-expand-fn current-path] 17 | (let [key-to-expand (first expanded-path) 18 | handle-coll (fn [v expand-key] 19 | (if (or (not (coll? v)) (= key-to-expand expand-key)) 20 | [:div (data->hiccup v (rest expanded-path) on-expand-fn (conj current-path expand-key))] 21 | [:div.collapsed 22 | {:on-click (on-expand-fn (conj current-path expand-key))} 23 | (data->hiccup (empty v) expanded-path on-expand-fn [])]))] 24 | (cond 25 | (map? data) 26 | [:div.map (for [[k v] data] 27 | ^{:key (hash (conj current-path k))} 28 | [:div.key-val 29 | [:div (data->hiccup k expanded-path on-expand-fn (conj current-path k))] 30 | (handle-coll v k)])] 31 | 32 | (vector? data) 33 | [:div.vector (for [[idx v] 34 | (map-indexed (fn [idx v] [idx v]) data)] 35 | ^{:key (hash (conj current-path idx))} 36 | [:div (handle-coll v idx)])] 37 | 38 | (seq? data) 39 | [:div.seq (for [[idx v] (map-indexed (fn [idx v] [idx v]) data)] 40 | ^{:key (hash (conj current-path idx))} 41 | [:div (handle-coll v idx)])] 42 | 43 | (string? data) 44 | [:span.string data] 45 | 46 | (number? data) 47 | [:span.number data] 48 | 49 | (keyword? data) 50 | [:span.keyword (str data)] 51 | 52 | (nil? data) 53 | [:span.nil "nil"] 54 | 55 | (or (true? data) (false? data)) 56 | [:span.boolean (str data)] 57 | 58 | :else 59 | (str data))))) 60 | 61 | (defn edn-tree [data local k] 62 | [:div.edn-tree.light 63 | (data->hiccup data (k @local) 64 | (fn [path] 65 | (fn [_] 66 | (swap! local assoc k path))))]) 67 | 68 | (defn detailed-msg [put-fn] 69 | (let [detailed-msg (subscribe [:detailed-msg]) 70 | local (r/atom {})] 71 | (fn [put-fn] 72 | (when-let [detailed-msg @detailed-msg] 73 | (let [{:keys [msg msg-meta system-info]} detailed-msg] 74 | [:div.section 75 | [:div.value.block [:h3 "Message payload"]] 76 | [edn-tree (second msg) local :expanded-body] 77 | [:div.value.block [:h3 "Message meta"]] 78 | [edn-tree msg-meta local :expanded-meta] 79 | [:pre [:code (with-out-str (pp/pprint system-info))]]]))))) 80 | -------------------------------------------------------------------------------- /src/cljs/inspect/view/ui/flow.cljs: -------------------------------------------------------------------------------- 1 | (ns inspect.view.ui.flow 2 | (:require-macros [reagent.ratom :refer [reaction]]) 3 | (:require [re-frame.core :refer [subscribe]] 4 | [taoensso.timbre :as timbre :refer-macros [info debug]] 5 | [inspect.view.util :as u] 6 | [cljs.pprint :as pp] 7 | [inspect.view.graphviz :as gv])) 8 | 9 | (defn msg-mapper [[firehose-id firehose-msg]] 10 | (let [{:keys [cmp-id msg duration msg-meta firehose-type]} firehose-msg 11 | [msg-type msg-size] msg] 12 | (-> firehose-msg 13 | (assoc-in [:msg-type] msg-type) 14 | (assoc-in [:msg-size] msg-size)))) 15 | 16 | (defn msg-compare [x y] 17 | (let [c (compare (:ts x) (:ts y))] 18 | (if (not= c 0) 19 | c 20 | (compare (-> x :msg-meta :cmp-seq count) 21 | (-> y :msg-meta :cmp-seq count))))) 22 | 23 | (defn msg-flow [put-fn] 24 | (let [msg-flow (subscribe [:show-flow]) 25 | detailed-msg (subscribe [:detailed-msg]) 26 | active-detail (reaction (:firehose-id @detailed-msg))] 27 | (fn [put-fn] 28 | (let [{:keys [tag msgs]} @msg-flow 29 | sorted-by-ts (sort msg-compare (map msg-mapper msgs)) 30 | active-detail @active-detail] 31 | (when tag 32 | [:div.msg-flow.section 33 | [:h3 "Tag: " tag] 34 | [:table 35 | [:tbody 36 | [:tr 37 | [:th "Time"] 38 | [:th "Cmp ID"] 39 | [:th "Msg type"] 40 | [:th "Duration"] 41 | [:th "Size"] 42 | [:th "Direction"]] 43 | (for [firehose-msg sorted-by-ts] 44 | (let [{:keys [cmp-id duration msg-type msg-meta firehose-type 45 | firehose-id ts msg-size]} firehose-msg 46 | color (u/chf msg-type u/colors) 47 | click #(put-fn [:sled/get {:k firehose-id}])] 48 | ^{:key firehose-id} 49 | [:tr {:class (when (= firehose-id active-detail) "active") 50 | :on-click click} 51 | [:td (u/format-time ts)] 52 | [:td (str cmp-id)] 53 | [:td 54 | [:span {:style {:background-color color 55 | :padding-right "10px"}}] 56 | (str msg-type)] 57 | [:td.number (when duration (str duration "ms"))] 58 | [:td.number msg-size] 59 | [:td.number (if (= firehose-type :firehose/cmp-recv) "IN" "OUT")]]))]]]))))) 60 | -------------------------------------------------------------------------------- /src/cljs/inspect/view/ui/matches.cljs: -------------------------------------------------------------------------------- 1 | (ns inspect.view.ui.matches 2 | (:require-macros [reagent.ratom :refer [reaction]]) 3 | (:require [re-frame.core :refer [subscribe]] 4 | [taoensso.timbre :as timbre :refer-macros [info debug]] 5 | [inspect.view.util :as u] 6 | [inspect.view.ui.detail :as ud] 7 | [clojure.pprint :as pp] 8 | [reagent.core :as r] 9 | [clojure.data.avl :as avl] 10 | [inspect.view.ui.timeline :as ut])) 11 | 12 | (defn matches [put-fn] 13 | (let [local (r/atom {:ts 9999999999999}) 14 | avl-map (subscribe [:avl-map]) 15 | from-avl (reaction (->> @avl-map 16 | (map second) 17 | (apply concat) 18 | (map second))) 19 | 20 | msg-flow (subscribe [:show-flow]) 21 | flows (subscribe [:flows]) 22 | active-type (subscribe [:active-type]) 23 | older-than (reaction (->> @avl-map 24 | ;(avl/subrange @avl-map < (:ts @local)) 25 | (map second) 26 | (apply concat) 27 | (map second))) 28 | filtered (reaction 29 | (take-last 15 (if @active-type 30 | (filter 31 | #(contains? (:max-per-type %) 32 | @active-type) 33 | @older-than) 34 | @older-than)))] 35 | (fn [put-fn] 36 | (let [msg-flow @msg-flow 37 | active-type @active-type 38 | first-flow-ts (:first-seen-ts (first @from-avl)) 39 | time-span (- (:first-seen-ts (last @from-avl)) first-flow-ts) 40 | slider-val (* (/ (- (:ts @local) 41 | first-flow-ts) 42 | time-span) 43 | 100) 44 | slider-val (if (= (:ts @local) 9999999999999) 45 | 100 46 | slider-val) 47 | slider-input (fn [e] 48 | (let [v (.-target.value e) 49 | new-ts (if (> v 99) 50 | 9999999999999 51 | (.floor js/Math 52 | (+ first-flow-ts 53 | (* v (/ time-span 100)))))] 54 | (swap! local assoc-in [:ts] new-ts)))] 55 | [:div.msg-flows.section 56 | [:h2 "Message Flows"] 57 | [:div 58 | "Recorded message flows starting between " 59 | [:span.time (:first-seen (first @from-avl))] " and " 60 | [:span.time (:first-seen (last @from-avl))]] 61 | [:div "Recorded time span: " [:span.time time-span "ms"]] 62 | (if (= slider-val 100) 63 | [:div "Showing " [:span.time "latest"] " message flows."] 64 | [:div 65 | "Showing message flows started before: " 66 | [:span.time (u/format-time (:ts @local))]]) 67 | (when (> time-span 0) 68 | [:input {:type "range" 69 | :value slider-val 70 | :min min 71 | :max max 72 | :style {:width "580px"} 73 | :on-change slider-input}]) 74 | ;[:div [ut/timeline-view first-flow-ts time-span (:ts @local) put-fn]] 75 | [:table 76 | [:tbody 77 | [:tr 78 | [:th "First seen"] 79 | [:th "Last seen"] 80 | [:th "Duration"] 81 | [:th "Processing time"] 82 | [:th "Msg type"] 83 | [:th "Max size"]] 84 | (for [{:keys [tag first-seen max-per-type duration last-seen 85 | msgs max-size processing-time]} @filtered] 86 | ^{:key (str tag)} 87 | [:tr {:class (when (= tag (:tag msg-flow)) 88 | "active-flow") 89 | :on-click #(put-fn [:flow/show {:tag tag :msgs msgs}])} 90 | [:td first-seen] 91 | [:td last-seen] 92 | [:td.number (if (> duration 10000) 93 | (str (.floor js/Math (/ duration 1000)) "s") 94 | (str duration "ms"))] 95 | [:td.number (str processing-time "ms")] 96 | [:td [:table.max-per-type 97 | [:tbody 98 | (for [[msg-type size] max-per-type] 99 | ^{:key (str tag msg-type)} 100 | [:tr 101 | [:td (str msg-type)] 102 | [:td size]])]]] 103 | [:td.number max-size]])]]])))) 104 | -------------------------------------------------------------------------------- /src/cljs/inspect/view/ui/timeline.cljs: -------------------------------------------------------------------------------- 1 | (ns inspect.view.ui.timeline 2 | (:require [moment] 3 | [re-frame.core :refer [subscribe]] 4 | [taoensso.timbre :as timbre :refer-macros [info debug]] 5 | [reagent.ratom :refer-macros [reaction]] 6 | [reagent.core :as r] 7 | [clojure.data.avl :as avl] 8 | [clojure.pprint :as pp])) 9 | 10 | (defn tick 11 | "Renders individual timeline tick." 12 | [ts pos color w h base-y local] 13 | (let [half-h (/ h 2)] 14 | (when-not (js/isNaN pos) 15 | [:line 16 | {:x1 pos 17 | :y1 (- base-y half-h) 18 | :x2 pos 19 | :y2 (+ base-y half-h) 20 | :on-mouse-enter #(swap! local assoc-in [:mouse-over] ts) 21 | :on-mouse-leave (fn [_ev] (.setTimeout js/window #(swap! local dissoc :mouse-over) 5000)) 22 | :stroke color 23 | :stroke-width w}]))) 24 | 25 | (def ymd-hms "YYYY-MM-DD HH:mm:ss") 26 | (def hm "HH:mm:ss:SSS") 27 | (defn df [ts format] (.format (moment ts) format)) 28 | 29 | (defn timeline-zoom 30 | "Renders timeline zoom slider." 31 | [local _tl-start _ts-range _flows] 32 | (let [mouse-up (fn [_ev] 33 | (swap! local #(-> % 34 | (assoc-in [:in-slider-down] false) 35 | (assoc-in [:out-slider-down] false) 36 | (assoc-in [:rect-down] false)))) 37 | in-slider-down #(swap! local assoc-in [:in-slider-down] true) 38 | out-slider-down #(swap! local assoc-in [:out-slider-down] true) 39 | slider-width 540 40 | calc-x-scale (fn [] 41 | (let [rng (- (:scale-out @local) (:scale-in @local)) 42 | x-scale (/ slider-width rng) 43 | tl-width (* (:elem-width @local) x-scale) 44 | offset (* (/ (- (:scale-in @local) 10) slider-width) tl-width)] 45 | (swap! local assoc-in [:x-scale] x-scale) 46 | (swap! local assoc-in [:x-offset] offset))) 47 | mouse-move (fn [ev] 48 | (let [x (- (.-clientX ev) (:zoom-left @local)) 49 | x (min (+ slider-width 10) (max 10 x))] 50 | (when (:in-slider-down @local) 51 | (swap! local assoc-in [:scale-in] x) 52 | (calc-x-scale)) 53 | (when (:in-slider-down @local) 54 | (swap! local assoc-in [:scale-in] x) 55 | (calc-x-scale)) 56 | (when (:out-slider-down @local) 57 | (swap! local assoc-in [:scale-out] x) 58 | (calc-x-scale)) 59 | (when (:rect-down @local) 60 | (let [offset (- (:rect-down-x @local) x) 61 | scale-in (- (:rect-down-in @local) offset) 62 | scale-out (- (:rect-down-out @local) offset)] 63 | (when (and (>= scale-in 10) (<= scale-out (+ slider-width 10))) 64 | (swap! local assoc-in [:scale-in] scale-in) 65 | (swap! local assoc-in [:scale-out] scale-out) 66 | (calc-x-scale)))))) 67 | rect-down (fn [ev] 68 | (let [x (- (.-clientX ev) (:zoom-left @local)) 69 | x (min (+ slider-width 10) (max 10 x))] 70 | (swap! local #(-> % 71 | (assoc-in [:rect-down] true) 72 | (assoc-in [:rect-down-in] (:scale-in @local)) 73 | (assoc-in [:rect-down-out] (:scale-out @local)) 74 | (assoc-in [:rect-down-x] x))))) 75 | render (fn zoom-render [local tl-start ts-range flows] 76 | (let [calc-pos #(when ts-range 77 | (+ 10 (* slider-width 78 | (/ (- % tl-start) ts-range))))] 79 | [:div.timeline-zoom 80 | [:svg {:viewBox (str "0 0 1000 40") 81 | :on-mouse-up mouse-up 82 | :on-mouse-leave mouse-up 83 | :on-mouse-move mouse-move} 84 | (let [scale-in (:scale-in @local) 85 | scale-out (:scale-out @local) 86 | w (- scale-out scale-in)] 87 | [:g 88 | (for [{:keys [first-seen-ts max-per-type]} @flows] 89 | ^{:key (str first-seen-ts (hash max-per-type))} 90 | [tick nil (calc-pos first-seen-ts) "green" 1 10 10 local]) 91 | [:line {:x1 10 92 | :x2 (+ slider-width 10) 93 | :y1 20 94 | :y2 20 95 | :stroke "black" 96 | :stroke-width 1}] 97 | [:rect {:x scale-in 98 | :y 8 99 | :width w 100 | :height 24 101 | :stroke "#333" 102 | :stroke-width 1 103 | :on-mouse-down rect-down 104 | :fill "#02c9d4" 105 | :opacity 0.3}] 106 | [:path {:transform (str "translate(" scale-in ",0)") 107 | :fill "#555" 108 | :on-mouse-down in-slider-down 109 | :d "M0,5 l0,30 l-4,0 l0,-20 l-7,0 l11,-10 L10,5 Z"}] 110 | [:rect {:fill "white" 111 | :opacity 0.01 112 | :x (- scale-in 10) 113 | :width 20 114 | :height 40 115 | :y 0 116 | :on-mouse-down in-slider-down}] 117 | [:path {:transform (str "translate(" scale-out ",0) scale(-1,1)") 118 | :fill "#555" 119 | :on-mouse-down out-slider-down 120 | :d "M0,5 l0,30 l-4,0 l0,-20 l-7,0 l11,-10 L10,5 Z"}] 121 | [:rect {:fill "white" 122 | :opacity 0.01 123 | :x (- scale-out 10) 124 | :width 20 125 | :height 40 126 | :y 0 127 | :on-mouse-down out-slider-down}]])]]))] 128 | (r/create-class 129 | {:component-did-mount #(swap! local assoc-in [:zoom-node] (r/dom-node %)) 130 | :reagent-render render}))) 131 | 132 | (def default-timeline-state 133 | {:zoom-slider-pos 10 134 | :offset-slider-pos 10 135 | :left 0 136 | :zoom-left 0 137 | :x-scale 1 138 | :x-offset 0 139 | :elem-width 1000 140 | :scale-in 10 141 | :scale-out 550}) 142 | 143 | (defn timeline-view [first-flow-ts time-span ts put-fn] 144 | (let [avl-map (subscribe [:avl-map]) 145 | flows (reaction (->> @avl-map 146 | (map second) 147 | (apply concat) 148 | (map second))) 149 | local (r/atom default-timeline-state) 150 | hr (* 60 60 1000) 151 | timestamps (reaction (map :first-seen-ts @flows)) 152 | hour-before-mn (reaction (let [mn (apply min @timestamps)] (- mn (mod mn hr)))) 153 | hour-after-mx (reaction (let [mx (apply max @timestamps)] (+ (- mx (mod mx hr)) hr hr))) 154 | rng (reaction (- @hour-after-mx @hour-before-mn)) 155 | hour-tick-timestamps (reaction (mapv #(+ @hour-before-mn (* % hr)) (range (/ @rng hr)))) 156 | reset-fn (fn [_ev] 157 | (put-fn [:timeline/set-ts nil]) 158 | (reset! local default-timeline-state)) 159 | hr-label (fn [ts hrs-total] 160 | (let [increment (condp < hrs-total 161 | 168 24 162 | 72 12 163 | 36 6 164 | 3)] 165 | (zero? (mod (.hour (moment ts)) increment)))) 166 | mouse-down #(swap! local assoc-in [:mouse-down] true) 167 | mouse-up #(swap! local assoc-in [:mouse-down] false) 168 | mouse-move (fn [ev] 169 | (let [x (+ (- (.-clientX ev) (:left @local)) (:x-offset @local)) 170 | dom-node (:tl-node @local) 171 | elem-w (if dom-node (.-offsetWidth dom-node) 1000) 172 | x-scale (:x-scale @local) 173 | tl-width (* elem-w x-scale) 174 | ts (+ @hour-before-mn (* (/ x tl-width) @rng))] 175 | (when (:mouse-down @local) 176 | (swap! local assoc-in [:ts] ts) 177 | (put-fn [:timeline/set-ts ts])))) 178 | render (fn timeline-render [first-flow-ts ts-range ts _put-fn] 179 | (let [elem-w 1000 180 | hrs-total (/ ts-range hr) 181 | x-scale (:x-scale @local) 182 | tl-width (* elem-w x-scale) 183 | hrs-shown (/ hrs-total x-scale) 184 | hrs-shown 6 185 | x-offset (:x-offset @local) 186 | tl-start @hour-before-mn 187 | tl-start first-flow-ts 188 | calc-pos #(when ts-range 189 | (* tl-width (/ (- % tl-start) ts-range))) 190 | hr-ticks (condp < hrs-shown 191 | 144 (take-nth 6 @hour-tick-timestamps) 192 | 72 (take-nth 3 @hour-tick-timestamps) 193 | @hour-tick-timestamps)] 194 | 195 | [:div.timeline 196 | 197 | ;[:pre [:code (with-out-str (pp/pprint (second @flows)))]] 198 | 199 | [:div.widget-header 200 | [:h2 "Timeline"] 201 | ;[timeline-zoom local @hour-tick-timestamps] 202 | [timeline-zoom local tl-start ts-range flows] 203 | [:button {:on-click reset-fn 204 | :class (if (:ts @local) "shake active" "inactive")} 205 | [:i.fa.fa-undo] "reset"]] 206 | [:svg { ;:viewBox (str "0 0 " elem-w " 75") 207 | :style (if (:mouse-down @local) {:cursor :hand} {}) 208 | :on-mouse-down mouse-down 209 | :on-mouse-up mouse-up 210 | :on-mouse-move mouse-move} 211 | [:g 212 | {:transform (str "translate(-" x-offset ",0)")} 213 | [:line {:x1 0 214 | :y1 35 215 | :x2 tl-width 216 | :y2 35 217 | :stroke "black" 218 | :stroke-width 1}] 219 | #_(for [ts (rest hr-ticks)] 220 | ^{:key ts} 221 | [tick ts (calc-pos ts) "black" 1 (if (hr-label ts hrs-total) 10 6) 35 local]) 222 | (for [{:keys [first-seen-ts max-per-type]} @flows] 223 | ^{:key (str first-seen-ts (hash max-per-type))} 224 | [tick ts (calc-pos first-seen-ts) "green" 3 10 10 local]) 225 | #_(for [ts @hour-tick-timestamps] 226 | ^{:key ts} 227 | [:text {:x (calc-pos ts) 228 | :y 50 229 | :font-size 7 230 | :text-anchor "middle"} 231 | (when (hr-label ts hrs-total) 232 | (df ts hm))]) 233 | (for [ts @hour-tick-timestamps] 234 | ^{:key ts} 235 | [:text {:x (calc-pos ts) 236 | :y 61 237 | :font-size 7 238 | :text-anchor "middle"} 239 | (when (zero? (mod (+ ts hr) (* hr 24))) 240 | (df ts "DD.MM.YYYY"))]) 241 | (when ts 242 | (let [playhead-pos (calc-pos ts)] 243 | [:g 244 | [:line.playhead 245 | {:x1 playhead-pos 246 | :y1 5 247 | :x2 playhead-pos 248 | :y2 60 249 | :stroke "#02c9d4" 250 | :opacity 0.8 251 | :stroke-width 3}] 252 | [:text {:x playhead-pos 253 | :y 72 254 | :font-size 9 255 | :font-weight "bold" 256 | :text-anchor "middle" 257 | :fill "black"} 258 | (df ts hm)]]))]] 259 | ]))] 260 | (r/create-class 261 | {:component-did-mount #(swap! local assoc-in [:tl-node] (r/dom-node %)) 262 | :reagent-render render}))) 263 | -------------------------------------------------------------------------------- /src/cljs/inspect/view/util.cljs: -------------------------------------------------------------------------------- 1 | (ns inspect.view.util 2 | (:require [randomcolor] 3 | [moment])) 4 | 5 | (defn random-color [seed] 6 | (randomcolor (clj->js {:seed (str (hash (str seed)))}))) 7 | 8 | 9 | ;; from https://github.com/bluemont/con-hash/blob/master/src/con_hash/core.clj 10 | (defn clockwise-node 11 | "Returns the next clockwise node starting from hashed-item. The 12 | term 'clockwise' means 'greater than with wrap-around'." 13 | [hashed-item hashed-nodes] 14 | {:pre [(map? hashed-nodes) (sorted? hashed-nodes)]} 15 | (let [kv (->> hashed-nodes 16 | (filter (fn [[k v]] (< hashed-item k))) 17 | first)] 18 | (second (or kv (first hashed-nodes))))) 19 | 20 | (defn hash-nodes 21 | "Returns a sorted map (where the key is a hash and the value is the 22 | original node) from a list of nodes and node hashing function." 23 | [nodes hash-fn] 24 | (->> nodes 25 | (map (fn [n] [(hash-fn n) n])) 26 | (into (sorted-map)))) 27 | 28 | (defn consistent-hash-fn 29 | "Returns a consistent hash function for a given item hashing 30 | function and node hashing function. The returned function accepts 31 | an item and sequence of nodes and returns one of the nodes." 32 | [item-hash-fn node-hash-fn] 33 | {:pre [(fn? item-hash-fn) (fn? node-hash-fn)]} 34 | (fn [item nodes] 35 | (let [hashed-nodes (hash-nodes nodes node-hash-fn)] 36 | (clockwise-node (item-hash-fn item) hashed-nodes)))) 37 | 38 | (def chf (consistent-hash-fn hash hash)) 39 | 40 | (def colors 41 | #{"aliceblue" "aqua" "aquamarine" "bisque" "black" "blue" 42 | "blueviolet" "brown" "burlywood" "cadetblue" "chocolate" "coral" 43 | "crimson" "cyan" "darkblue" "darkcyan" "darkmagenta" 44 | "darkorange" "darkorchid" "darkred" "darksalmon" 45 | "darkseagreen" "darkslateblue" "darkturquoise" "darkviolet" 46 | "deeppink" "deepskyblue" "dodgerblue" "firebrick" "forestgreen" "fuchsia" 47 | "gold" "goldenrod" "green" "hotpink" "indianred" 48 | "indigo" "khaki" "lavender" "lavenderblush" "lawngreen" "limegreen" 49 | "magenta" "maroon" "mediumaquamarine" "mediumblue" 50 | "mediumorchid" "mediumpurple" "mediumseagreen" "mediumslateblue" 51 | "mediumspringgreen" "mediumturquoise" "mediumvioletred" "midnightblue" 52 | "navy" "orange" "orangered" "orchid" "plum" "powderblue" "purple" 53 | "red" "rosybrown" "royalblue" "salmon" "seagreen" "sienna" "skyblue" 54 | "slateblue" "springgreen" "steelblue" "tomato" "turquoise"}) 55 | 56 | (defn format-time [m] (.format (moment m) "HH:mm:ss:SSS")) 57 | -------------------------------------------------------------------------------- /src/scss/_buttons.scss: -------------------------------------------------------------------------------- 1 | @import "variables.scss"; 2 | 3 | .btn { 4 | padding: 1px 5px; 5 | color: white; 6 | font-size: 0.8em; 7 | cursor: pointer; 8 | white-space: nowrap; 9 | 10 | .fas { 11 | padding-right: 0.1em; 12 | } 13 | } 14 | 15 | .save { 16 | height: 15px; 17 | } 18 | 19 | .not-saved { 20 | padding: 1px 5px; 21 | color: white; 22 | font-size: 0.8em; 23 | background-color: $red; 24 | cursor: pointer; 25 | } 26 | 27 | .link-btn { 28 | padding: 0 5px; 29 | color: white; 30 | font-size: 0.8em; 31 | background-color: $green; 32 | cursor: pointer; 33 | white-space: nowrap; 34 | } 35 | 36 | button { 37 | background-color: $green; 38 | border: none; 39 | color: white; 40 | padding: 5px 12px; 41 | text-align: center; 42 | text-decoration: none; 43 | display: inline-block; 44 | font-size: 16px; 45 | cursor: pointer; 46 | white-space: nowrap; 47 | outline: 0; 48 | 49 | i { 50 | margin-right: 6px; 51 | } 52 | } 53 | 54 | .menu-new { 55 | background-color: $menu-new-btn; 56 | } 57 | 58 | .show-more-btn { 59 | background-color: $show-more; 60 | color: whitesmoke; 61 | font-size: 0.8em; 62 | padding: 1px 5px; 63 | } 64 | 65 | .delete-warn { 66 | padding: 1px 5px; 67 | color: white; 68 | font-size: 0.8em; 69 | cursor: pointer; 70 | background-color: $delete-warning; 71 | } 72 | 73 | .show-more { 74 | padding: 20px 0; 75 | } 76 | 77 | a, a:visited { 78 | text-decoration: none; 79 | font-weight: 400; 80 | color: $link-color; 81 | outline: none!important; 82 | } 83 | 84 | .toggle { 85 | padding: 2px; 86 | margin-left: 4px; 87 | cursor: pointer; 88 | 89 | &.green { 90 | color: darken($green, 10); 91 | } 92 | } 93 | 94 | .fa-thumbs-up, .fa-thumbs-down { 95 | color: #006C91; 96 | } 97 | 98 | .inactive { 99 | color: #AAA; 100 | } 101 | 102 | .hidden-comments { 103 | color: red; 104 | } 105 | 106 | a .toggle { 107 | color: $toggle-btn; 108 | } 109 | 110 | .upvotes { 111 | vertical-align: super; 112 | color: #006C91; 113 | font-size: 0.7em; 114 | line-height: 1.2em; 115 | font-weight: bold; 116 | padding: 0 2px 0 1px; 117 | margin-left: 1px; 118 | margin-bottom: 3px; 119 | border: 1px solid #006C91; 120 | border-radius: 2px; 121 | } 122 | 123 | .toggle-cmds { 124 | margin-right: 30px; 125 | } 126 | 127 | .linked-tasks { 128 | .filter { 129 | background-color: #DDD; 130 | color: #AAA; 131 | font-size: 0.8em; 132 | padding: 1px 5px; 133 | margin: 0 2px 0 3px; 134 | cursor: pointer; 135 | } 136 | 137 | .current { 138 | background-color: $show-more; 139 | color: whitesmoke; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/scss/_edn.scss: -------------------------------------------------------------------------------- 1 | // Slightly modified from https://github.com/kamituel/systems-toolbox-chrome 2 | 3 | .edn-tree { 4 | .map { &:before, &:after { color: #eeee22; }} 5 | .vector { &:before, &:after { color: #22ee22; }} 6 | .seq { &:before, &:after { color: purple; }} 7 | .string { color: #33ffff; } 8 | .number { color: #cc66ff; } 9 | .keyword { color: #ff8888; } 10 | .nil { color: red; } 11 | .boolean { color: #ff00ff; } 12 | } 13 | 14 | .edn-tree.light { 15 | .map { &:before, &:after { color: #555500; }} 16 | .vector { &:before, &:after { color: #228822; }} 17 | .seq { &:before, &:after { color: purple; }} 18 | .string { color: #339999; } 19 | .number { color: #7700aa; } 20 | .keyword { color: #770000; } 21 | .nil { color: red; } 22 | .boolean { color: #990099; } 23 | } 24 | 25 | 26 | /* Layout */ 27 | .edn-tree { 28 | font-size: 10px; 29 | display: flex; 30 | 31 | .map { 32 | display: block; 33 | float: left; 34 | 35 | .key-val { 36 | display: block; 37 | float: left; 38 | 39 | &:not(:first-child) { 40 | display: block; 41 | clear: both; 42 | padding-left: 4px; 43 | } 44 | 45 | /* Map key */ 46 | &> div:nth-child(1) { 47 | display: block; 48 | float: left; 49 | padding: 0 5px 0 5px; 50 | } 51 | 52 | /* Map value */ 53 | &> div:nth-child(2) { 54 | display: inline-block; 55 | } 56 | } 57 | 58 | &:before { content: "{"; } 59 | &:after { content: "}"; } 60 | &:before, &:after { 61 | display: inline-flex; 62 | float: left; 63 | } 64 | } 65 | 66 | .vector { 67 | &> div { 68 | display: inline-flex; 69 | float: left; 70 | clear: both; 71 | } 72 | 73 | &:before { content: "["; } 74 | &:after { content: "]"; } 75 | &:before, &:after { 76 | display: inline-flex; 77 | float: left; 78 | } 79 | } 80 | 81 | .seq { 82 | &:before { content: "("; } 83 | &:after { content: ")"; } 84 | &:before, &:after { 85 | display: inline-flex; 86 | float: left; 87 | } 88 | } 89 | 90 | span { 91 | &.string {&:before, &:after {content: "\"";}} 92 | } 93 | 94 | .collapsed { 95 | cursor: pointer; 96 | 97 | &>.vector:before { content: "[..."; }; 98 | &>.map:before { content: "{..."; }; 99 | &>.seq:before { content: "(..."; }; 100 | } 101 | 102 | .collapsed:hover { 103 | &>.vector:before, &>.vector:after, 104 | &>.map:before, &>.map:before, 105 | &>.seq:before, &>.seq:before { 106 | text-decoration: underline; 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/scss/_variables.scss: -------------------------------------------------------------------------------- 1 | $font-color: #576364; 2 | $font-family: "Lato", sans-serif; 3 | $header-font-family: "Oswald", "Lato", sans-serif; 4 | $green: #4CAF50; 5 | $red: rgb(202, 60, 60); 6 | $hashtag-blue: rgb(66, 184, 221); 7 | $info-border-radius: 0.3em; 8 | $mention-color: rgb(55, 201, 154); 9 | $search-field-color: white; 10 | $delete-warning: rgb(202, 60, 60); 11 | $show-more: #1f8dd6; 12 | $not-tag: rgb(221, 77, 55); 13 | $menu-bg: #2d3e50; 14 | $body-background-color: desaturate(lighten($menu-bg, 20), 10); 15 | $stats-bg-color: $menu-bg; 16 | $toggle-btn: #576364; 17 | $link-color: #006c91; 18 | $menu-new-btn: #0078e7; 19 | $mention-link: #007d4e; 20 | $tab-bg: desaturate(lighten($menu-bg, 40), 10); 21 | -------------------------------------------------------------------------------- /src/scss/inspect.scss: -------------------------------------------------------------------------------- 1 | @import "variables.scss"; 2 | @import "edn.scss"; 3 | 4 | body { 5 | color: $font-color; 6 | background-color: white; 7 | font-size: 12px; 8 | font-weight: 300; 9 | font-family: $font-family; 10 | display: flex; 11 | width: 3840px; 12 | } 13 | 14 | h1, h2, h3, h4, h5, h6 { 15 | font-family: $header-font-family; 16 | } 17 | 18 | @import "buttons.scss"; 19 | 20 | #logo { 21 | width: 150px; 22 | position: absolute; 23 | top: 1em; 24 | left: 2em; 25 | } 26 | 27 | .cmp-table { 28 | h2 { 29 | margin-bottom: 3px; 30 | font-family: monospace; 31 | font-size: 0.9em; 32 | } 33 | } 34 | 35 | #graphviz1, #graphviz2 { 36 | svg { 37 | max-width: 1000px; 38 | } 39 | } 40 | 41 | .flex { 42 | display: flex; 43 | } 44 | 45 | .menu { 46 | background-color: #EEE; 47 | padding-left: 200px; 48 | } 49 | 50 | .observer { 51 | 52 | pre { 53 | font-size: 0.7em; 54 | line-height: 1em; 55 | } 56 | 57 | h3 { 58 | margin: 0.6em 0 0 0; 59 | font-size: 1em; 60 | font-family: monospace; 61 | } 62 | .header { 63 | display: flex; 64 | margin-bottom: 10px; 65 | 66 | .host-input { 67 | display: flex; 68 | } 69 | 70 | input { 71 | height: 28px; 72 | width: 320px; 73 | border: 0; 74 | outline: none; 75 | padding-left: 6px; 76 | border-bottom-left-radius: 4px; 77 | border-top-left-radius: 4px; 78 | } 79 | button { 80 | border-bottom-right-radius: 4px; 81 | border-top-right-radius: 4px; 82 | } 83 | .cnt { 84 | padding: 4px 0; 85 | font-family: monospace; 86 | } 87 | } 88 | 89 | .section { 90 | padding: 12px; 91 | margin-bottom: 10px; 92 | width: calc(100vw - 24px); 93 | min-height: 40px; 94 | } 95 | 96 | .timeline { 97 | svg { 98 | width: 1100px; 99 | } 100 | } 101 | 102 | .msg-flow, .msg-flows { 103 | min-height: 600px; 104 | .time { 105 | font-weight: bold; 106 | font-family: monospace; 107 | } 108 | table { 109 | margin-top: 1em; 110 | font-size: 0.8em; 111 | font-family: monospace; 112 | border-spacing:0; 113 | cursor: pointer; 114 | 115 | tr:nth-child(even) { 116 | background-color: lighten($hashtag-blue, 40%); 117 | } 118 | 119 | .max-per-type { 120 | tr { 121 | background-color: rgba(0,0,0,0); 122 | } 123 | } 124 | 125 | td, th { 126 | padding-right: 8px; 127 | vertical-align: middle; 128 | } 129 | 130 | .number { 131 | text-align: right; 132 | font-weight: bold; 133 | } 134 | } 135 | } 136 | 137 | .stop { 138 | background-color: $red; 139 | } 140 | 141 | .freeze { 142 | background-color: #eee; 143 | color: $green; 144 | margin-top: 10px; 145 | } 146 | 147 | .clear { 148 | background-color: $red; 149 | color: white; 150 | margin: 10px 0 0 10px; 151 | } 152 | 153 | .status { 154 | font-family: monospace; 155 | 156 | &.error { 157 | color: $red; 158 | font-weight: bold; 159 | } 160 | } 161 | 162 | .color { 163 | padding: 0 0.6em; 164 | margin-right: 0.5em; 165 | } 166 | 167 | .active, .selected { 168 | background-color: #7FDBFF!important; 169 | } 170 | 171 | .active-flow { 172 | background-color: lighten($green, 35)!important; 173 | } 174 | 175 | .force-wrapper { 176 | align-items: center; 177 | display: flex; 178 | } 179 | 180 | ul { 181 | list-style: none; 182 | padding: 0; 183 | margin: 0; 184 | } 185 | 186 | .tables { 187 | display: flex; 188 | flex-flow: row; 189 | 190 | table { 191 | background-color: white; 192 | margin: 0 1em 0 0; 193 | min-width: 220px; 194 | line-height: 1em; 195 | font-family: monospace; 196 | font-size: 0.8em; 197 | cursor: pointer; 198 | 199 | tr:nth-child(even) { 200 | background-color: lighten($hashtag-blue, 40%); 201 | } 202 | 203 | td { 204 | min-width: 20px; 205 | 206 | ul { 207 | margin: 0; 208 | } 209 | 210 | &.dir { 211 | width: 30px; 212 | } 213 | &.cmp-id { 214 | min-width: 170px; 215 | } 216 | } 217 | 218 | .cnt { 219 | text-align: end; 220 | padding-right: 5px; 221 | background-color: white; 222 | } 223 | 224 | .changed { 225 | font-weight: bold; 226 | background-color: orange; 227 | //transition: background-color 1s; 228 | } 229 | } 230 | } 231 | } 232 | 233 | .grid { 234 | .wrapper { 235 | display: grid; 236 | height: 100vh; 237 | width: 3840px; 238 | grid-gap: 10px; 239 | grid-template-rows: 80px calc(100vh - 100px); 240 | grid-template-columns: 1000px 800px 600px 1000px 600px 600px 600px; 241 | } 242 | 243 | .menu { 244 | grid-column: 1/8; 245 | grid-row: 1; 246 | } 247 | .col-1 { 248 | grid-column: 1; 249 | grid-row: 2; 250 | } 251 | .col-2 { 252 | grid-column: 2; 253 | grid-row: 2; 254 | } 255 | .col-3 { 256 | grid-column: 3; 257 | grid-row: 2; 258 | } 259 | .col-4 { 260 | grid-column: 4; 261 | grid-row: 2; 262 | } 263 | .col-5 { 264 | grid-column: 5; 265 | grid-row: 2; 266 | } 267 | .col-6 { 268 | grid-column: 6; 269 | grid-row: 2; 270 | } 271 | .col-7 { 272 | grid-column: 7; 273 | grid-row: 2; 274 | } 275 | } 276 | 277 | .spec-errors { 278 | .header { 279 | font-weight: bold; 280 | 281 | time { 282 | margin-right: 1em; 283 | } 284 | } 285 | pre { 286 | font-size: 1.2em; 287 | } 288 | } 289 | 290 | .known-hosts { 291 | line-height: 1.6em; 292 | position: absolute; 293 | background-color: lightblue; 294 | padding: 5px; 295 | font-family: monospace; 296 | width: 300px; 297 | 298 | .known-host { 299 | padding: 5px; 300 | cursor: pointer; 301 | } 302 | } 303 | 304 | .msg-cmp { 305 | display: flex; 306 | flex-direction: row; 307 | 308 | .msg-types { 309 | padding-right: 30px; 310 | white-space: nowrap; 311 | 312 | input { 313 | margin-top: 20px; 314 | margin-bottom: 2px; 315 | width: 180px; 316 | } 317 | 318 | table { 319 | background-color: white; 320 | margin: 0 1em 0 0; 321 | min-width: 220px; 322 | line-height: 1em; 323 | font-family: monospace; 324 | font-size: 0.8em; 325 | cursor: pointer; 326 | 327 | tr:nth-child(even) { 328 | background-color: lighten($hashtag-blue, 40%); 329 | } 330 | 331 | td { 332 | min-width: 20px; 333 | 334 | ul { 335 | margin: 0; 336 | } 337 | 338 | &.dir { 339 | width: 30px; 340 | } 341 | &.cmp-id { 342 | min-width: 170px; 343 | } 344 | } 345 | 346 | .cnt { 347 | text-align: end; 348 | padding-right: 5px; 349 | background-color: white; 350 | } 351 | 352 | .changed { 353 | font-weight: bold; 354 | background-color: orange; 355 | //transition: background-color 1s; 356 | } 357 | } 358 | 359 | th { 360 | user-select: none; 361 | } 362 | 363 | .fas { 364 | padding: 5px 10px; 365 | color: #AAA; 366 | &.sort-active { 367 | color: #006c91; 368 | } 369 | } 370 | } 371 | } -------------------------------------------------------------------------------- /src/scss/loader.scss: -------------------------------------------------------------------------------- 1 | @import "variables.scss"; 2 | 3 | body { 4 | color: $font-color; 5 | background-color: $tab-bg; 6 | font-size: 12px; 7 | font-weight: 300; 8 | font-family: $font-family; 9 | overflow: hidden; 10 | } 11 | 12 | // from: https://www.w3schools.com/howto/howto_css_loader.asp 13 | .loader { 14 | border: 8px solid #f6f6f6; 15 | border-top: 8px solid $green; 16 | border-radius: 50%; 17 | margin: 20px; 18 | width: 60px; 19 | height: 60px; 20 | animation: spin 2s linear infinite; 21 | } 22 | 23 | @keyframes spin { 24 | 0% { transform: rotate(0deg); } 25 | 100% { transform: rotate(360deg); } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /src/scss/updater.scss: -------------------------------------------------------------------------------- 1 | @import "variables.scss"; 2 | 3 | body { 4 | color: $font-color; 5 | background-color: $tab-bg; 6 | font-size: 12px; 7 | font-weight: 300; 8 | font-family: $font-family; 9 | overflow: hidden; 10 | } 11 | 12 | h1, h2, h3, h4, h5, h6 { 13 | font-family: $header-font-family; 14 | } 15 | 16 | .flex-container { 17 | display: flex; 18 | flex-flow: column; 19 | height: 100vh; 20 | } 21 | 22 | .updater { 23 | padding: 1em; 24 | 25 | .info { 26 | padding: 1em 0 2em 0; 27 | } 28 | } 29 | 30 | @import "buttons.scss"; 31 | 32 | // adapted from https://css-tricks.com/css3-progress-bars/ 33 | .meter { 34 | height: 16px; 35 | position: relative; 36 | background: $body-background-color; 37 | border-radius: 6px; 38 | padding: 4px; 39 | } 40 | 41 | .meter > span { 42 | display: block; 43 | height: 100%; 44 | border-radius: 4px; 45 | background-color: $green; 46 | position: relative; 47 | overflow: hidden; 48 | } 49 | --------------------------------------------------------------------------------