├── .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 |
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 | 
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 |
--------------------------------------------------------------------------------