├── .editorconfig ├── .gitignore ├── public ├── lib │ └── jquery-treetable │ │ ├── .npmignore │ │ ├── css │ │ ├── screen.css │ │ ├── jquery.treetable.css │ │ └── jquery.treetable.theme.default.css │ │ ├── composer.json │ │ ├── test.html │ │ ├── bower.json │ │ ├── treetable.jquery.json │ │ ├── MIT-LICENSE.txt │ │ ├── README.md │ │ ├── package.json │ │ ├── CHANGELOG.md │ │ ├── GPL-LICENSE.txt │ │ └── jquery.treetable.js ├── ui │ └── stringify │ │ ├── editors │ │ └── calltree.html │ │ └── types │ │ └── calltree │ │ └── calltree.js ├── components │ ├── traceList │ │ ├── traceList.less │ │ ├── traceList.html │ │ └── traceListController.js │ ├── traceGraph │ │ ├── traceGraph.less │ │ ├── traceGraph.html │ │ └── traceGraphDirective.js │ └── callTree │ │ ├── callTree.html │ │ ├── callTreeDirective.js │ │ └── callTree.less ├── app.less ├── app.js ├── services │ └── elasticsearchService.js └── icon.svg ├── tests ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── src │ └── test │ │ ├── resources │ │ ├── type-mapping.json │ │ └── mock-spans.json │ │ └── groovy │ │ ├── GebConfig.groovy │ │ ├── pages │ │ ├── Discover.groovy │ │ ├── StagemonitorPlugin.groovy │ │ └── IndexManagement.groovy │ │ └── StagemonitorPluginTests.groovy ├── build.gradle ├── gradlew.bat └── gradlew ├── package.json ├── package.json.template ├── index.js ├── README.md ├── .travis.yml └── LICENSE /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{groovy,gradle}] 2 | indent_size = 4 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | 3 | .idea 4 | 5 | tests/.gradle 6 | tests/build 7 | -------------------------------------------------------------------------------- /public/lib/jquery-treetable/.npmignore: -------------------------------------------------------------------------------- 1 | *.swo 2 | *.swp 3 | .DS_Store 4 | .idea 5 | bower_components 6 | experiments 7 | node_modules 8 | -------------------------------------------------------------------------------- /tests/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stagemonitor/stagemonitor-kibana/HEAD/tests/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /public/ui/stringify/editors/calltree.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/components/traceList/traceList.less: -------------------------------------------------------------------------------- 1 | .trace-table > tbody > .trace-row:hover { 2 | 3 | font-weight: bold; 4 | 5 | & > td:hover { 6 | cursor: pointer; 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stagemonitor-kibana", 3 | "version": "5.4.0", 4 | "dependencies": { 5 | "dagre-d3-webpack": "0.4.17", 6 | "flat": "^4.0.0", 7 | "jquery": ">=1.6" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /package.json.template: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stagemonitor-kibana", 3 | "version": "@@VERSION@@", 4 | "dependencies": { 5 | "dagre-d3-webpack": "0.4.17", 6 | "flat": "^4.0.0", 7 | "jquery": ">=1.6" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.2-bin.zip 6 | -------------------------------------------------------------------------------- /tests/src/test/resources/type-mapping.json: -------------------------------------------------------------------------------- 1 | { 2 | "mappings": { 3 | "spans": { 4 | "_all": { 5 | "enabled": false 6 | }, 7 | "dynamic_templates": [ 8 | { 9 | "string_fields": { 10 | "match": "*", 11 | "match_mapping_type": "string", 12 | "mapping": { 13 | "ignore_above": 32766, 14 | "type": "keyword" 15 | } 16 | } 17 | } 18 | ] 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/src/test/groovy/GebConfig.groovy: -------------------------------------------------------------------------------- 1 | import org.openqa.selenium.chrome.ChromeDriver 2 | import org.openqa.selenium.chrome.ChromeOptions 3 | import org.openqa.selenium.remote.DesiredCapabilities 4 | 5 | waiting { 6 | timeout = 10 7 | } 8 | 9 | driver = { 10 | ChromeOptions options = new ChromeOptions() 11 | options.addArguments("--headless", "--no-sandbox", "--disable-gpu") 12 | 13 | DesiredCapabilities capabilities = DesiredCapabilities.chrome() 14 | capabilities.setCapability(ChromeOptions.CAPABILITY, options) 15 | 16 | new ChromeDriver(capabilities) 17 | } 18 | -------------------------------------------------------------------------------- /public/lib/jquery-treetable/css/screen.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #ddd; 3 | color: #000; 4 | font-family: Helvetica, Arial, sans-serif; 5 | line-height: 1.5; 6 | margin: 0; 7 | padding: 0; 8 | } 9 | 10 | #main { 11 | background: #fff; 12 | border-left: 20px solid #eee; 13 | border-right: 20px solid #eee; 14 | margin: 0 auto; 15 | max-width: 800px; 16 | padding: 20px; 17 | } 18 | 19 | pre.listing { 20 | background: #eee; 21 | border: 1px solid #ccc; 22 | margin: .6em 0 .3em 0; 23 | padding: .1em .3em; 24 | } 25 | 26 | pre.listing b { 27 | color: #f00; 28 | } 29 | -------------------------------------------------------------------------------- /public/lib/jquery-treetable/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ludo/jquery-treetable", 3 | "description": "jQuery plugin for displaying a tree structure in a (HTML) table, i.e. a directory structure or a nested list.", 4 | "keywords": [ 5 | "explorer", 6 | "file", 7 | "table", 8 | "tree", 9 | "ui" 10 | ], 11 | "authors": [{ 12 | "name": "Ludo van den Boom", 13 | "homepage": "http://ludovandenboom.com" 14 | }], 15 | "license": [ 16 | "GPLv2", 17 | "MIT" 18 | ], 19 | "homepage": "https://github.com/ludo/jquery-treetable", 20 | "require": { 21 | "jquery": ">=1.6" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/src/test/groovy/pages/Discover.groovy: -------------------------------------------------------------------------------- 1 | package pages 2 | 3 | import geb.Page 4 | 5 | class Discover extends Page { 6 | 7 | static url = "app/kibana#/discover" 8 | 9 | static at = { 10 | waitFor { 11 | timeRangeChooser 12 | } 13 | } 14 | 15 | static content = { 16 | timeRangeChooser { $("data-test-subj": "globalTimepickerRange") } 17 | docTable { $("data-test-subj": "docTable") } 18 | docTableRows { $(".discover-table-open-icon, .discover-table-open-button") } 19 | } 20 | 21 | void setTimeRange(String timeRange) { 22 | timeRangeChooser.click() 23 | $("a", text: timeRange).click() 24 | waitFor { docTable } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /public/lib/jquery-treetable/css/jquery.treetable.css: -------------------------------------------------------------------------------- 1 | table.treetable span.indenter { 2 | display: inline-block; 3 | margin: 0; 4 | padding: 0; 5 | text-align: right; 6 | 7 | /* Disable text selection of nodes (for better D&D UX) */ 8 | user-select: none; 9 | -khtml-user-select: none; 10 | -moz-user-select: none; 11 | -o-user-select: none; 12 | -webkit-user-select: none; 13 | 14 | /* Force content-box box model for indenter (Bootstrap compatibility) */ 15 | -webkit-box-sizing: content-box; 16 | -moz-box-sizing: content-box; 17 | box-sizing: content-box; 18 | 19 | width: 19px; 20 | } 21 | 22 | table.treetable span.indenter a { 23 | background-position: left center; 24 | background-repeat: no-repeat; 25 | display: inline-block; 26 | text-decoration: none; 27 | width: 19px; 28 | } 29 | -------------------------------------------------------------------------------- /tests/src/test/groovy/pages/StagemonitorPlugin.groovy: -------------------------------------------------------------------------------- 1 | package pages 2 | 3 | import geb.Page 4 | 5 | class StagemonitorPlugin extends Page { 6 | 7 | static url = "app/stagemonitor-kibana" 8 | 9 | static at = { 10 | waitFor { 11 | $(".trace-table") 12 | } 13 | } 14 | 15 | static content = { 16 | traceRows { $(".trace-row") } 17 | callTreeTab { $("a", text: "Call tree") } 18 | callTreeRows { $(".callTree tbody tr").findAll {it.displayed} } 19 | callTreeRowExpanders { $(".expander") } 20 | } 21 | 22 | void openTraceRow(int index) { 23 | waitFor { traceRows } 24 | traceRows[index].click() 25 | } 26 | 27 | void openSpan(String name) { 28 | waitFor { $(".node--name") } 29 | $(".node--name", text: name).click() 30 | waitFor { callTreeTab } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/src/test/groovy/pages/IndexManagement.groovy: -------------------------------------------------------------------------------- 1 | package pages 2 | 3 | import geb.Page 4 | 5 | class IndexManagement extends Page { 6 | 7 | static url = "app/kibana#/management/kibana/index?_g=()" 8 | 9 | static at = { 10 | waitFor { 11 | indexPatternInput 12 | } 13 | } 14 | 15 | static content = { 16 | indexPatternInput { $("validate-index-name": "") } 17 | createButton { $("button", text: "Create") } 18 | setDefaultIndexPatternButton { $("button", text: "Set as default index") } 19 | form { $("form", "name": "form") } 20 | } 21 | 22 | void createIndexPattern(indexPattern) { 23 | indexPatternInput.value(indexPattern) 24 | waitFor { createButton } 25 | createButton.click() 26 | waitFor { setDefaultIndexPatternButton } 27 | setDefaultIndexPatternButton.click() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = function (kibana) { 2 | return new kibana.Plugin({ 3 | require: ['kibana', 'elasticsearch'], 4 | uiExports: { 5 | // fieldFormats: ['plugins/stagemonitor-kibana/ui/stringify/types/calltree/calltree'], 6 | app: { 7 | title: 'stagemonitor', 8 | order: -1000, 9 | description: 'Visualize traces from stagemonitor', 10 | icon: 'plugins/stagemonitor-kibana/icon.svg', 11 | main: 'plugins/stagemonitor-kibana/app', 12 | injectVars: function (server) { 13 | const config = server.config(); 14 | return { 15 | kbnIndex: config.get('kibana.index'), 16 | esShardTimeout: config.get('elasticsearch.shardTimeout'), 17 | esApiVersion: config.get('elasticsearch.apiVersion') 18 | }; 19 | } 20 | } 21 | }, 22 | //init: require('./init.js'), 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /public/lib/jquery-treetable/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | jQuery treetable Tests 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /public/lib/jquery-treetable/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-treetable", 3 | "description": "jQuery plugin for displaying a tree structure in a (HTML) table, i.e. a directory structure or a nested list.", 4 | "homepage": "http://ludo.cubicphuse.nl/jquery-treetable", 5 | "authors": [ "Ludo van den Boom " ], 6 | "license": [ "GPL-2.0", "MIT" ], 7 | "keywords": [ "explorer", "file", "table", "tree", "ui" ], 8 | "repository": { 9 | "type": "git", 10 | "url": "git://github.com/ludo/jquery-treetable.git" 11 | }, 12 | "main": [ 13 | "jquery.treetable.js", 14 | "css/jquery.treetable.css", 15 | "css/jquery.treetable.theme.default.css" 16 | ], 17 | "ignore": [ "*.html", "test" ], 18 | "dependencies": { 19 | "jquery": ">=1.6" 20 | }, 21 | "devDependencies": { 22 | "chai": "latest", 23 | "chai-jquery": "latest", 24 | "jquery-ui": "latest", 25 | "mocha": "latest", 26 | "sinonjs": "latest", 27 | "underscore": "latest" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "groovy" 2 | 3 | repositories { 4 | mavenCentral() 5 | } 6 | 7 | dependencies { 8 | testCompile 'org.codehaus.groovy:groovy-all:2.4.10' 9 | 10 | def gebVersion = "2.1" 11 | def seleniumVersion = "3.9.1" 12 | 13 | // If using JUnit, need to depend on geb-junit (3 or 4) 14 | testCompile "org.gebish:geb-junit4:${gebVersion}" 15 | testCompile "junit:junit:4.12" 16 | 17 | // Need a driver implementation 18 | testCompile "org.seleniumhq.selenium:selenium-chrome-driver:${seleniumVersion}" 19 | testRuntime "org.seleniumhq.selenium:selenium-support:${seleniumVersion}" 20 | 21 | testCompile "org.codehaus.groovy.modules.http-builder:http-builder:0.7.1" 22 | } 23 | 24 | test { 25 | testLogging.exceptionFormat = 'full' 26 | 27 | systemProperties "geb.build.reportsDir": "$reportsDir/geb" 28 | systemProperties "geb.build.baseUrl": "http://localhost:5601/" 29 | // override via system properties 30 | systemProperties System.properties 31 | } 32 | -------------------------------------------------------------------------------- /public/ui/stringify/types/calltree/calltree.js: -------------------------------------------------------------------------------- 1 | import fieldFormats from 'ui/registry/field_formats'; 2 | import IndexPatternsFieldFormatProvider from 'ui/index_patterns/_field_format/field_format'; 3 | 4 | export default function HTMLFormatProvider(Private) { 5 | let FieldFormat = Private(IndexPatternsFieldFormatProvider); 6 | 7 | class HTML extends FieldFormat { 8 | constructor(params) { 9 | super(params); 10 | } 11 | } 12 | 13 | HTML.prototype._convert = { 14 | text: value => value + "test", 15 | html: value => '
' + value + '
' 16 | }; 17 | 18 | HTML.id = 'html-formatter'; 19 | HTML.title = 'HTML Field'; 20 | HTML.fieldType = ['string']; 21 | HTML.editor = require('plugins/stagemonitor-kibana/ui/stringify/editors/calltree.html'); 22 | 23 | HTML.sampleInputs = [ 24 | 'A Quick Brown Fox.', 25 | 'STAY CALM!', 26 | 'com.organizations.project.ClassName', 27 | 'hostname.net', 28 | 'SGVsbG8gd29ybGQ=' 29 | ]; 30 | 31 | return HTML; 32 | } 33 | 34 | fieldFormats.register(HTMLFormatProvider); 35 | -------------------------------------------------------------------------------- /public/lib/jquery-treetable/treetable.jquery.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "treetable", 3 | "title": "jQuery treetable", 4 | "description": "jQuery plugin for displaying a tree structure in a (HTML) table, i.e. a directory structure or a nested list.", 5 | "keywords": [ 6 | "explorer", 7 | "file", 8 | "table", 9 | "tree", 10 | "ui" 11 | ], 12 | "version": "3.2.0", 13 | "author": { 14 | "name": "Ludo van den Boom", 15 | "url": "http://ludovandenboom.com" 16 | }, 17 | "maintainers": [ 18 | { 19 | "name": "Ludo van den Boom", 20 | "url": "http://ludovandenboom.com" 21 | } 22 | ], 23 | "licenses": [ 24 | { 25 | "type": "GPLv2", 26 | "url": "https://github.com/ludo/jquery-treetable/blob/3.2.0/GPL-LICENSE.txt" 27 | }, 28 | { 29 | "type": "MIT", 30 | "url": "https://github.com/ludo/jquery-treetable/blob/3.2.0/MIT-LICENSE.txt" 31 | } 32 | ], 33 | "bugs": "https://github.com/ludo/jquery-treetable/issues", 34 | "homepage": "https://github.com/ludo/jquery-treetable", 35 | "docs": "http://ludo.cubicphuse.nl/jquery-treetable/", 36 | "dependencies": { 37 | "jquery": ">=1.6" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /public/lib/jquery-treetable/MIT-LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Ludo van den Boom, http://ludovandenboom.com 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /public/app.less: -------------------------------------------------------------------------------- 1 | @import "components/callTree/callTree.less"; 2 | @import "components/traceGraph/traceGraph.less"; 3 | @import "components/traceList/traceList.less"; 4 | 5 | .stagemonitor-kibana { 6 | 7 | .stagemonitor-trace-tab { 8 | padding: 1em; 9 | } 10 | 11 | .table.span-details { 12 | background-color: #fafafa; 13 | } 14 | 15 | .doc-viewer td { 16 | font-family: "Lucida Console", Monaco, monospace; 17 | } 18 | 19 | .hint { 20 | margin-top: 2em; 21 | } 22 | 23 | .nav-tabs { 24 | border-bottom: 3px solid #d8d8d8; 25 | } 26 | 27 | .nav-tabs > li { 28 | margin-bottom: -3px; 29 | cursor: pointer; 30 | background: none; 31 | } 32 | 33 | .nav-tabs > li > a { 34 | border: none; 35 | border-bottom: 3px solid transparent; 36 | margin-right: 0.6em; 37 | margin-left: 0.2em; 38 | } 39 | 40 | .nav-tabs > li.active > a, 41 | .nav-tabs > li > a:hover { 42 | cursor: default; 43 | color: #000; 44 | background-color: transparent; 45 | border: none; 46 | border-bottom: 3px solid #e8488b; 47 | text-decoration: none; 48 | } 49 | 50 | .nav-tabs > li:not(.active):hover > a { 51 | cursor: pointer; 52 | text-decoration: underline; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /public/lib/jquery-treetable/README.md: -------------------------------------------------------------------------------- 1 | # jQuery treetable 2 | [![CDNJS](https://img.shields.io/cdnjs/v/jquery-treetable.svg)](https://cdnjs.com/libraries/jquery-treetable) 3 | 4 | jQuery treetable is a plugin for jQuery, the 'Write Less, Do More, JavaScript 5 | Library'. With this plugin you can display a tree in an HTML table, e.g. a 6 | directory structure or a nested list. Why not use a list, you say? Because lists 7 | are great for displaying a tree, and tables are not. Oh wait, but this plugin 8 | uses tables, doesn't it? Yes. Why do I use a table to display a list? Because I 9 | need multiple columns to display additional data besides the tree. 10 | 11 | Download the latest release from the jQuery Plugin Registry or grab the source 12 | code from Github. Please report issues through Github issues. This plugin is 13 | released under both the MIT and the GPLv2 license by Ludo van den Boom. 14 | 15 | ## Documentation 16 | 17 | See index.html for technical documentation and examples. The most recent version 18 | of this document is also available online at 19 | http://ludo.cubicphuse.nl/jquery-treetable. 20 | 21 | ## Examples 22 | 23 | * JSFiddle with AJAX example: http://jsfiddle.net/djlerman/bbj8k9pe/ [@djlerman] 24 | * Example Ruby on Rails project with AJAX treetable: https://github.com/ludo/jquery-treetable-ajax-example 25 | -------------------------------------------------------------------------------- /public/app.js: -------------------------------------------------------------------------------- 1 | import 'ui/autoload/all'; 2 | import modules from 'ui/modules'; 3 | import { uiModules } from 'ui/modules'; 4 | import uiRoutes from 'ui/routes'; 5 | 6 | import './app.less'; 7 | import traceListTemplate from './components/traceList/traceList.html'; 8 | import ElasticsearchService from './services/elasticsearchService'; 9 | import traceListController from './components/traceList/traceListController'; 10 | import traceGraph from './components/traceGraph/traceGraphDirective'; 11 | import callTree from './components/callTree/callTreeDirective'; 12 | 13 | uiRoutes.enable(); 14 | uiRoutes 15 | .when('/trace/:traceId', { 16 | template: traceListTemplate, 17 | controller: 'traceListController', 18 | controllerAs: 'ctrl' 19 | }) 20 | .otherwise({ 21 | template: traceListTemplate, 22 | controller: 'traceListController', 23 | controllerAs: 'ctrl' 24 | }); 25 | 26 | let realUiModule; 27 | if (modules) { 28 | // kibana 5.4.x 29 | realUiModule = modules; 30 | } else { 31 | // kibana 5.5.x 32 | realUiModule = uiModules; 33 | } 34 | 35 | realUiModule 36 | .get('app/stagemonitor', ['elasticsearch', 'kibana']) 37 | .service('elasticsearchService', ElasticsearchService) 38 | .controller('traceListController', traceListController) 39 | .directive('traceGraph', traceGraph) 40 | .directive('callTree', callTree) 41 | .run((elasticsearchService) => { 42 | elasticsearchService.updateTracingVisualizationUrlScriptedField(); 43 | }); 44 | -------------------------------------------------------------------------------- /public/components/traceList/traceList.html: -------------------------------------------------------------------------------- 1 |
2 |

Showing detail of single trace, click here to show all recent traces

3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 30 | 31 | 32 |
NameTimestampTrace-IDDurationSpans
17 | 18 | {{trace.inner_hits.spans.hits.hits[0]._source.name}}{{trace.inner_hits.spans.hits.hits[0]._source['@timestamp']}}{{trace._source.trace_id}}{{trace.inner_hits.spans.hits.hits[0]._source.duration_ms | number:2}} ms{{trace.inner_hits.spans.hits.total}}
28 | 29 |
33 | 34 |
35 | -------------------------------------------------------------------------------- /public/components/traceGraph/traceGraph.less: -------------------------------------------------------------------------------- 1 | .stagemonitor-kibana { 2 | 3 | .trace-graph-container { 4 | background-color: #fafafa; 5 | padding: 0 2em; 6 | } 7 | 8 | .trace-visualization { 9 | g.type-TK > rect { 10 | fill: #00ffd0; 11 | } 12 | 13 | g.label { 14 | color: #000; 15 | font-weight: normal; 16 | padding: 0; 17 | font-size: 100%; 18 | text-align: left; 19 | line-height: 1.42857143 20 | } 21 | 22 | text { 23 | font-weight: 300; 24 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serf; 25 | font-size: 14px; 26 | } 27 | 28 | svg { 29 | width: 95%; 30 | height: 0; 31 | overflow: hidden; 32 | border: 1px solid #ccc; 33 | display: block; 34 | margin: 1em auto; 35 | } 36 | 37 | .node rect { 38 | stroke: #ccc; 39 | fill: #fff; 40 | stroke-width: 1.5px; 41 | } 42 | 43 | .node g div { 44 | height: 40px; 45 | min-width: 180px; 46 | cursor: pointer; 47 | } 48 | 49 | .edgePath path { 50 | stroke: #333; 51 | stroke-width: 1.5px; 52 | } 53 | 54 | .node--duration { 55 | display: inline-block; 56 | } 57 | 58 | .node--name { 59 | display: inline-block; 60 | margin-top: 3px; 61 | } 62 | 63 | .node--span { 64 | } 65 | 66 | .node--status { 67 | height: 100%; 68 | width: 15px; 69 | display: inline-block; 70 | float: left; 71 | border-top-left-radius: 5px; 72 | border-bottom-left-radius: 5px; 73 | margin-right: 4px; 74 | background-color: #ACFCD9; 75 | border: 1px solid transparent; 76 | background-clip: content-box; 77 | } 78 | 79 | .node--status__high-impact { 80 | background-color: #FC6471; 81 | } 82 | 83 | .node--status__medium-impact { 84 | background-color: #D3C274; 85 | } 86 | 87 | .node.span-open rect { 88 | stroke: #222; 89 | } 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /public/components/callTree/callTree.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 14 | 15 | 16 | 17 | 18 | 23 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 |
Signature 7 | Total time (ms) 8 | 9 | 11 | Self time (ms) 12 | 13 |
24 | 25 |
26 |           {{row.shortSignature ? row.shortSignature.trim() : row.signature.trim()}}
27 |         
28 | 29 | {{row.shortSignature ? row.shortSignature.trim() : row.signature.trim()}} 30 | 31 |
32 | 33 | 34 | 35 | 36 | 37 |
38 | {{row.queryCount}} QueriesQuery 39 |
40 |
41 |
{{row.executionTimeInMs}}
{{row.selfExecutionTimeInMs}}
51 | -------------------------------------------------------------------------------- /public/services/elasticsearchService.js: -------------------------------------------------------------------------------- 1 | export default class ElasticsearchService { 2 | 3 | constructor($http) { 4 | this.$http = $http; 5 | } 6 | 7 | isElasticsearch54() { 8 | return this.$http.get('../elasticsearch').then(res => { 9 | this.isElasticsearch54 = res.data.version.number.indexOf('5.4') === 0; 10 | return this.isElasticsearch54; 11 | }); 12 | } 13 | 14 | searchAllSpansFor(traceId) { 15 | return this.searchSpans({ 16 | 'stored_fields': ['*'], 17 | 'query': { 18 | 'term': { 19 | 'trace_id': traceId 20 | } 21 | }, 22 | '_source': { 23 | 'excludes': ['call_tree_ascii'] 24 | }, 25 | 'size': 10000 26 | }); 27 | } 28 | 29 | searchSpans(searchObject) { 30 | return this.$http.post('../elasticsearch/stagemonitor-spans-*/_search', searchObject); 31 | } 32 | 33 | updateTracingVisualizationUrlScriptedField() { 34 | this.$http.post('../elasticsearch/.kibana/index-pattern/_search', { 35 | 'query': { 36 | 'match': { 37 | 'title': 'stagemonitor-spans-*' 38 | } 39 | } 40 | }).then(res => { 41 | const hit = res.data.hits.hits[0]; 42 | const index = hit._source; 43 | const fields = JSON.parse(index.fields); 44 | const tracingVisualization = { 45 | aggregatable: true, 46 | analyzed: false, 47 | count: 0, 48 | doc_values: false, 49 | indexed: false, 50 | lang: 'painless', 51 | name: 'trace_visualization', 52 | script: 'doc[\'trace_id\'].value', 53 | scripted: true, 54 | searchable: false, 55 | type: 'string' 56 | }; 57 | 58 | if (_.some(fields, _.matchesProperty('name', 'trace_visualization'))) { 59 | // remove if already exists so that we always get the up-to-date mapping definition 60 | _.remove(fields, _.matchesProperty('name', 'trace_visualization')); 61 | } 62 | fields.push(tracingVisualization); 63 | index.fields = JSON.stringify(fields); 64 | 65 | const fieldFormatMap = JSON.parse(index.fieldFormatMap || '{}'); 66 | fieldFormatMap.trace_visualization = { 67 | id: 'url', 68 | params: { 69 | 'labelTemplate': 'Trace Visualization', 70 | 'urlTemplate': '../app/stagemonitor-kibana#/trace/{{value}}' 71 | } 72 | }; 73 | index.fieldFormatMap = JSON.stringify(fieldFormatMap); 74 | 75 | this.$http.post('../elasticsearch/.kibana/index-pattern/' + hit._id + '/_update', { doc: index }); 76 | }); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /tests/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stagemonitor-kibana [![Build Status](https://travis-ci.org/stagemonitor/stagemonitor-kibana.svg?branch=master)](https://travis-ci.org/stagemonitor/stagemonitor-kibana) 2 | 3 | This incubating plug-in for Kibana allows you to visualize distributed traces from spans collected by stagemonitor. 4 | 5 | It looks like this: 6 | 7 | 1. The initial view after opening the plugin 8 | ![Screenshot stagemonitor kibana plugin](https://user-images.githubusercontent.com/4292951/28964437-0c7c2538-790d-11e7-9bb0-eaf7e106a32b.png) 9 | 10 | 2. After clicking a trace, the basic trace visualization is shown 11 | ![Screenshot stagemonitor kibana plugin](https://user-images.githubusercontent.com/4292951/28964436-0c7be49c-790d-11e7-8c22-12cf6cae57ba.png) 12 | 13 | 3. In the trace visualization you may select a span to view the span details 14 | ![Screenshot stagemonitor kibana plugin](https://user-images.githubusercontent.com/4292951/28964439-0c7ff0dc-790d-11e7-943b-3456b469819b.png) 15 | 16 | 4. In the trace visualization you may select a span to view the span details 17 | ![Screenshot stagemonitor kibana plugin](https://user-images.githubusercontent.com/4292951/28964438-0c7f4f4c-790d-11e7-8f9f-795ec1689e3f.png) 18 | 19 | 5. This plugin also adds a scripted field in Kibana to open the trace visualization from a span in the discover tab 20 | ![Screenshot stagemonitor kibana plugin](https://user-images.githubusercontent.com/4292951/28964440-0c8503c4-790d-11e7-8d5a-a759ece14e71.png) 21 | 22 | 23 | # Installation 24 | 25 | [Have a look at our release page.](https://github.com/stagemonitor/stagemonitor-kibana/releases) Be sure to use the plugin version corresponding to your version of kibana, down to the minor patch level. 26 | 27 | # Caveats 28 | 29 | This plugin currently only works with Chrome and Firefox. Internet Explorer is not supported. 30 | 31 | # Development 32 | 33 | Start elasticsearch on your machine and run the following commands 34 | 35 | git clone https://github.com/elastic/kibana 36 | git checkout tags/v5.5.0 37 | npm install 38 | cd plugins 39 | git clone git@github.com:stagemonitor/stagemonitor-kibana.git 40 | cd stagemonitor-kibana 41 | npm install 42 | cd ../.. 43 | npm start 44 | 45 | Access Kibana under https://localhost:5601 -> the lazy optimizations may take some minutes for the initial pageload. 46 | 47 | # Tests 48 | 49 | In the tests folder, run the following command to run the tests on localhost:5601: 50 | 51 | ./gradlew test 52 | 53 | If you are running the development instance you have to adjust the base url, as it will have a randomly generated suffix: 54 | 55 | ./gradlew test -Dgeb.build.baseUrlTest=https://localhost:5601/znr/ 56 | -------------------------------------------------------------------------------- /public/components/traceList/traceListController.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | 3 | export default function traceListController($routeParams, elasticsearchService, $q) { 4 | const vm = this; 5 | const pageSize = 10; 6 | 7 | function getBaseSearchObject() { 8 | return $q.when({ 9 | 'collapse': { 10 | 'field': 'trace_id', 11 | 'inner_hits': { 12 | 'name': 'spans', 13 | 'size': 1, 14 | 'sort': [{'@timestamp': 'asc'}], 15 | '_source': { 16 | 'includes': ['duration_ms', '@timestamp', 'name'] 17 | } 18 | } 19 | }, 20 | 'sort': {'@timestamp': 'desc'}, 21 | '_source': [ 22 | 'trace_id', 23 | 'id', 24 | '@timestamp' 25 | ] 26 | }); 27 | } 28 | 29 | init(); 30 | 31 | function init() { 32 | vm.page = 0; 33 | vm.timeOffset = new Date().toISOString(); 34 | vm.traces = []; 35 | vm.fetchNextPage = fetchNextPage; 36 | vm.onClickTrace = onClickTrace; 37 | vm.isOpen = isOpen; 38 | vm.openedTraces = []; 39 | vm.loadMoreDisabled = false; 40 | vm.singleTraceRoute = false; 41 | 42 | if ($routeParams.traceId) { 43 | vm.loadMoreDisabled = true; 44 | vm.singleTraceRoute = true; 45 | 46 | getBaseSearchObject().then(baseSearchObject => { 47 | const searchObject = _.defaultsDeep( 48 | { 49 | 'query': { 50 | 'term': { 51 | 'trace_id': $routeParams.traceId 52 | } 53 | }, 54 | 'size': 1 55 | }, baseSearchObject); 56 | 57 | elasticsearchService.searchSpans(searchObject).then((response) => { 58 | vm.traces.push(...response.data.hits.hits); 59 | vm.openedTraces.push(_.head(response.data.hits.hits)); 60 | }); 61 | }); 62 | } else { 63 | fetchPage(pageSize, vm.page, vm.timeOffset); 64 | } 65 | } 66 | 67 | function fetchNextPage() { 68 | vm.page++; 69 | fetchPage(pageSize, vm.page, vm.timeOffset); 70 | } 71 | 72 | function fetchPage(pageSize, pageOffset, date) { 73 | getBaseSearchObject().then(baseSearchObject => { 74 | const searchObject = _.defaultsDeep({ 75 | 'query': { 76 | 'range': { 77 | '@timestamp': { 78 | 'lt': date 79 | } 80 | }, 81 | }, 82 | 'size': pageSize, 83 | 'from': pageOffset * pageSize 84 | }, baseSearchObject); 85 | 86 | return elasticsearchService.searchSpans(searchObject).then((response) => { 87 | vm.traces.push(...response.data.hits.hits); 88 | }); 89 | }); 90 | } 91 | 92 | function onClickTrace(trace) { 93 | if (_.includes(vm.openedTraces, trace)) { 94 | _.pull(vm.openedTraces, trace); 95 | } else { 96 | vm.openedTraces.push(trace); 97 | } 98 | } 99 | 100 | function isOpen(trace) { 101 | return _.includes(vm.openedTraces, trace); 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | dist: trusty 3 | language: groovy 4 | addons: 5 | chrome: stable 6 | jdk: oraclejdk8 7 | env: 8 | global: 9 | - PLUGIN_VERSION=0.3.0 10 | - TERM=dumb 11 | - CHROMEDRIVER_VERSION=2.35 12 | matrix: 13 | - ELASTICSEARCH_VERSION=5.4.0 KIBANA_VERSION=5.4.0 14 | - ELASTICSEARCH_VERSION=5.4.1 KIBANA_VERSION=5.4.1 15 | - ELASTICSEARCH_VERSION=5.4.2 KIBANA_VERSION=5.4.2 16 | - ELASTICSEARCH_VERSION=5.4.3 KIBANA_VERSION=5.4.3 17 | - ELASTICSEARCH_VERSION=5.5.0 KIBANA_VERSION=5.5.0 18 | - ELASTICSEARCH_VERSION=5.5.1 KIBANA_VERSION=5.5.1 19 | - ELASTICSEARCH_VERSION=5.5.2 KIBANA_VERSION=5.5.2 20 | - ELASTICSEARCH_VERSION=5.5.3 KIBANA_VERSION=5.5.3 21 | - ELASTICSEARCH_VERSION=5.6.0 KIBANA_VERSION=5.6.0 22 | - ELASTICSEARCH_VERSION=5.6.1 KIBANA_VERSION=5.6.1 23 | - ELASTICSEARCH_VERSION=5.6.2 KIBANA_VERSION=5.6.2 24 | 25 | install: 26 | # install kibana 27 | - mkdir /tmp/kibana-server 28 | - wget --directory-prefix=/tmp/kibana-server https://artifacts.elastic.co/downloads/kibana/kibana-${KIBANA_VERSION}-linux-x86_64.tar.gz 29 | - tar -xzf /tmp/kibana-server/kibana-${KIBANA_VERSION}-linux-x86_64.tar.gz --directory /tmp/kibana-server 30 | # install elasticsearch 31 | - mkdir /tmp/elasticsearch-server 32 | - wget --directory-prefix=/tmp/elasticsearch-server https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-${ELASTICSEARCH_VERSION}.tar.gz 33 | - tar -xzf /tmp/elasticsearch-server/elasticsearch-${ELASTICSEARCH_VERSION}.tar.gz --directory /tmp/elasticsearch-server 34 | # build project 35 | - npm install 36 | - ./build.sh ${KIBANA_VERSION} ${PLUGIN_VERSION} 37 | # start elasticsearch and kibana 38 | - /tmp/elasticsearch-server/elasticsearch-${ELASTICSEARCH_VERSION}/bin/elasticsearch & 39 | - /tmp/kibana-server/kibana-${KIBANA_VERSION}-linux-x86_64/bin/kibana-plugin install file:///tmp/stagemonitor-kibana-${PLUGIN_VERSION}-${KIBANA_VERSION}.zip 40 | - /tmp/kibana-server/kibana-${KIBANA_VERSION}-linux-x86_64/bin/kibana & 41 | # install chromedriver 42 | - wget --directory-prefix=/tmp https://chromedriver.storage.googleapis.com/${CHROMEDRIVER_VERSION}/chromedriver_linux64.zip 43 | - unzip -d /tmp/chromedriver /tmp/chromedriver_linux64.zip 44 | - export PATH=$PATH:/tmp/chromedriver 45 | 46 | before_script: 47 | - "export DISPLAY=:99.0" 48 | - "sh -e /etc/init.d/xvfb start" 49 | - sleep 10 # wait for elastic / kibana / xvfb 50 | 51 | script: 52 | - echo running tests.... 53 | - pushd tests 54 | - ./gradlew test --info --stacktrace 55 | - popd 56 | 57 | before_deploy: 58 | - cp /tmp/stagemonitor-kibana-* ${TRAVIS_BUILD_DIR} 59 | - ls -lah 60 | 61 | deploy: 62 | provider: releases 63 | api_key: 64 | secure: KjOeKj0DOTGUxwiUSFlECiZrfIyrtL5mcpqilrSBuqKizv4moSaee0ZL/iGFaNoZpzBSVJO172h130rHQYZbgLX0XiZjNQpZzdSuu1HMvInxbf0y/70R8cmjlM52zOL/OaAGYolJsJPSU/XltwrH97O5FjdfeU1M35GGxwIbh/AV6NxBK0ypK0CT3G9dfVAcP+A0/bnbJWCM4CGH0HsIPFHsDajRnx4gNcW+/DBo4m+L7lB3kp33c+JcctRTAtJHTyjrUxqcPSq882P2IuyWj7HVx5ChyTHeTmmunhioZMpHmxtr7UpKYR3sPQweGCDsdNpn3HX2pr8jl67yviEoIdlpHSXk/XoIQFmt4ORPOFBIDrI8STDFpWkiSYhKkWjMcc+mViwnzZ7p1gPihwBp//3hYlD5odN8YWHDWuoA65ryT8Z1UbcTPUVKRvZTOp63rGYIRxF7uVKvY6pmPtjY4iXPo+OcDI37AYGv/fcnEQMVZz0w6RYUzP/cXCJd6Fx91N3EV4ZiJfSRTK2Pw8vLS2bfMQ7J2LNxLJUQICbB3qy0hbYf0Vdys4y/0uXiREXVlEVXbewWUFC9g4M53DXXBHwXEWQPl8b2d3sItQ27ArWYW5LPjeOAab5fmElyQlgYCRTwudYd9UvGIvhMqHY8tPR+YKpVUauYWJl8VRt0aoo= 65 | file: stagemonitor-kibana-* 66 | file_glob: true 67 | skip_cleanup: true 68 | on: 69 | tags: true 70 | repo: stagemonitor/stagemonitor-kibana 71 | -------------------------------------------------------------------------------- /tests/src/test/groovy/StagemonitorPluginTests.groovy: -------------------------------------------------------------------------------- 1 | import geb.junit4.GebReportingTest 2 | import groovy.json.JsonOutput 3 | import groovy.json.JsonSlurper 4 | import groovyx.net.http.HTTPBuilder 5 | import groovyx.net.http.URIBuilder 6 | import org.junit.Before 7 | import org.junit.Test 8 | import pages.Discover 9 | import pages.IndexManagement 10 | import pages.StagemonitorPlugin 11 | 12 | import static groovyx.net.http.ContentType.JSON 13 | import static groovyx.net.http.Method.DELETE 14 | import static groovyx.net.http.Method.POST 15 | import static groovyx.net.http.Method.PUT 16 | 17 | class StagemonitorPluginTests extends GebReportingTest { 18 | 19 | @Test 20 | void testSpans() { 21 | to StagemonitorPlugin 22 | openTraceRow(0) 23 | openSpan("Process Find Form") 24 | 25 | // check we've opened the tag page of the span 26 | waitFor { $("td", text: "http.url") } 27 | 28 | callTreeTab.click() 29 | 30 | waitFor { callTreeRows.size() == 3 } 31 | callTreeRowExpanders.last().click() 32 | waitFor { callTreeRows.size() == 2 } 33 | callTreeRowExpanders.last().click() 34 | waitFor { callTreeRows.size() == 3 } 35 | } 36 | 37 | @Test 38 | void checkDiscoverTabForLinkToPlugin() { 39 | to IndexManagement 40 | createIndexPattern("stagemonitor-spans-*") 41 | 42 | to StagemonitorPlugin 43 | sleep(1500) // wait for creation of scripted fields 44 | 45 | to Discover 46 | setTimeRange("Last 5 years") 47 | docTableRows[0].click() 48 | waitFor { 49 | $("a", text: "Trace Visualization") 50 | } 51 | } 52 | 53 | @Before 54 | void importMockData() { 55 | deleteDataIfExists() 56 | 57 | importTypeMapping() 58 | 59 | def slurper = new JsonSlurper() 60 | def spans = slurper.parse(getClass().getResource("mock-spans.json")) 61 | spans.each { span -> 62 | importSpan(span) 63 | } 64 | 65 | // wait for elasticsearch having indexed the data 66 | sleep(2000) 67 | } 68 | 69 | private deleteDataIfExists() { 70 | def spansHttp = new HTTPBuilder("http://localhost:9200/stagemonitor-spans-2017.08.04") 71 | try { 72 | spansHttp.request(DELETE, JSON) {} 73 | } catch (Exception e) { 74 | // ignore if index does not exist 75 | } 76 | 77 | def kibanaDeleteByQuery = new HTTPBuilder("http://localhost:9200/.kibana/_delete_by_query") 78 | try { 79 | kibanaDeleteByQuery.request(POST, JSON) { req -> 80 | body = '{"query": {"match": { "title": "stagemonitor-spans-*"}}}' 81 | } 82 | } catch (Exception e) { 83 | // ignore if index does not exist 84 | println e 85 | } 86 | } 87 | 88 | private importTypeMapping() { 89 | def slurper = new JsonSlurper() 90 | 91 | def http = new HTTPBuilder("http://localhost:9200/stagemonitor-spans-2017.08.04") 92 | http.request(PUT, JSON) { req -> 93 | body = slurper.parse(getClass().getResource("type-mapping.json")) 94 | } 95 | } 96 | 97 | private void importSpan(span) { 98 | def uriBuilder = new URIBuilder('http://localhost:9200/stagemonitor-spans-2017.08.04/spans/') 99 | uriBuilder.path += span._id 100 | def http = new HTTPBuilder(uriBuilder.toURI()) 101 | def response = http.request(PUT, JSON) { req -> 102 | body = JsonOutput.toJson(span._source) 103 | } 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /public/components/callTree/callTreeDirective.js: -------------------------------------------------------------------------------- 1 | import callTree from './callTree.html'; 2 | import '../../lib/jquery-treetable'; 3 | import $ from 'jquery'; 4 | 5 | export default () => { 6 | return { 7 | template: callTree, 8 | scope: { 9 | 'source': '=' 10 | }, 11 | controller($scope, $element, $timeout) { 12 | const vm = this; 13 | vm.callTreeParsed = JSON.parse(vm.source); 14 | vm.callTree = []; 15 | processCallTree(vm.callTree, [vm.callTreeParsed], null, 1, vm.callTreeParsed.executionTime); 16 | 17 | $timeout(function () { 18 | const $callTreeTable = $element.find('.callTree'); 19 | $callTreeTable.treetable({ 20 | expandable: true, 21 | force: true, 22 | indent: 25, 23 | initialState: 'expanded', 24 | expanderTemplate: ' ' 25 | }); 26 | 27 | $callTreeTable.find('tr[data-tt-expanded="false"]').each(function () { 28 | $callTreeTable.treetable('collapseNode', $(this).attr('data-tt-id')); 29 | }); 30 | 31 | $('[data-display-none=\'true\']').hide(); 32 | 33 | $callTreeTable.find('.branch').click(function () { 34 | const $branch = $(this); 35 | if (!$(event.target).hasClass('expander')) { 36 | const treeTableNodeId = $branch.data('tt-id'); 37 | $callTreeTable.treetable('node', treeTableNodeId).toggle(); 38 | } 39 | const $queryCount = $branch.find('.query-count'); 40 | $branch.hasClass('collapsed') ? $queryCount.show() : $queryCount.hide(); 41 | }); 42 | 43 | }); 44 | }, 45 | controllerAs: 'ctrl', 46 | bindToController: true 47 | }; 48 | }; 49 | 50 | function processCallTree(callTreeRows, callArray, parentId, myId, totalExecutionTimeInNs) { 51 | const thresholdPercent = localStorage.getItem('widget-settings-execution-threshold-percent') || 0.05; 52 | const totalExecutionTimeInMs = totalExecutionTimeInNs / 1000 / 1000; 53 | for (let i = 0; i < callArray.length; i++) { 54 | const callData = callArray[i]; 55 | 56 | const executionTimeInMs = Math.round(callData.executionTime / 1000 / 10) / 100; 57 | const selfExecutionTimeInMs = Math.round(callData.netExecutionTime / 1000 / 10) / 100; 58 | const executionTimePercent = (executionTimeInMs / totalExecutionTimeInMs) * 100; 59 | const selfExecutionTimePercent = (selfExecutionTimeInMs / totalExecutionTimeInMs) * 100; 60 | const anyChildExceedsThreshold = $.grep(callData.children,function (e) { 61 | return (e.executionTime / totalExecutionTimeInNs * 100) > thresholdPercent; 62 | }).length > 0; 63 | 64 | callData.queryCount = callData.ioquery ? 1 : 0; 65 | const thisRow = { 66 | executionTimeExceededThreshold: executionTimePercent > thresholdPercent, 67 | anyChildExceedsThreshold: anyChildExceedsThreshold, 68 | parentId: parentId, 69 | myId: myId, 70 | shortSignature: callData.shortSignature, 71 | signature: callData.signature, 72 | ioQuery: callData.ioquery, 73 | queryCount: callData.queryCount, 74 | executionTimePercent: executionTimePercent, 75 | executionTimeInNs: callData.executionTime, 76 | executionTimeInMs: executionTimeInMs, 77 | selfExecutionTimePercent: selfExecutionTimePercent, 78 | selfExecutionTimeInMs: selfExecutionTimeInMs 79 | }; 80 | callTreeRows.push(thisRow); 81 | 82 | myId = processCallTree(callTreeRows, callData.children, myId, myId + 1, totalExecutionTimeInNs); 83 | 84 | callData.queryCount += _.sum(callData.children, 'queryCount'); 85 | thisRow.queryCount += callData.queryCount; 86 | } 87 | return myId; 88 | } 89 | -------------------------------------------------------------------------------- /public/components/traceGraph/traceGraph.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

4 | Visualization for trace {{ctrl.trace._source.trace_id}} (show all spans for this trace) 5 | Permalink 6 |

7 | 8 |
9 | 10 | 11 | 12 |
13 | 14 |
15 |

16 | Details for span {{ctrl.selectedSpan._source.name}} 17 |

18 | 19 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 40 | 41 | 42 |
Tag nameTag value
{{prop.propName}} 36 | 37 | {{prop.value}} 38 | 39 |
43 |
44 | 45 |
46 | 47 |
48 |
49 | 52 |
53 |

54 |  The call tree might not contain all 55 | executed methods. 56 |

57 |

58 | Showing all methods can result in performance problems when rendering and 59 | navigating the call tree. That's why by default, only methods are shown which took longer than 0.5% of the 60 | total 61 | execution time. To change this, change the configuration option with the key 62 | stagemonitor.profiler.minExecutionTimePercent. 63 |

64 |

65 | There is another configuration option (stagemonitor.profiler.minExecutionTimeNanos) which 66 | excludes very fast method invocations, as these are typically not very interesting. 67 |

68 |
69 | 70 | 71 | 72 | 73 |
74 | Hint: press the icon in the trace graph to open directly the call tree of the clicked span 75 |
76 |
77 |
78 | 79 |
80 | -------------------------------------------------------------------------------- /tests/src/test/resources/mock-spans.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "_index": "stagemonitor-spans-2017.08.04", 4 | "_type": "spans", 5 | "_id": "AV2saE9h0sacwixIDLu4", 6 | "_score": 4.5685062, 7 | "_source": { 8 | "db.type": "HSQL Database Engine", 9 | "trace_id": "30d10566858cd70e", 10 | "instance": "localhost", 11 | "method": "SELECT", 12 | "db.statement": "select distinct owner0_.id as id1_0_0_, pets1_.id as id1_1_1_, owner0_.first_name as first_na2_0_0_, owner0_.last_name as last_nam3_0_0_, owner0_.address as address4_0_0_, owner0_.city as city5_0_0_, owner0_.telephone as telephon6_0_0_, pets1_.name as name2_1_1_, pets1_.birth_date as birth_da3_1_1_, pets1_.owner_id as owner_id4_1_1_, pets1_.type_id as type_id5_1_1_, pets1_.owner_id as owner_id4_0_0__, pets1_.id as id1_1_0__ from owners owner0_ left outer join pets pets1_ on owner0_.id=pets1_.owner_id where owner0_.last_name like 'f%'", 13 | "type": "jdbc", 14 | "error": false, 15 | "duration_ms": 4.880729, 16 | "db.user": "SA", 17 | "@timestamp": "2017-08-04T10:41:42.550+0200", 18 | "application": "Spring-PetClinic", 19 | "span.kind": "client", 20 | "parent_id": "30d10566858cd70e", 21 | "peer.service": "SA@jdbc:hsqldb:mem:petclinic", 22 | "host": "Fabians-MBP.isys-software.de", 23 | "name": "JpaOwnerRepositoryImpl#findByLastName", 24 | "id": "1b2f6c80a557fb16" 25 | } 26 | }, 27 | { 28 | "_index": "stagemonitor-spans-2017.08.04", 29 | "_type": "spans", 30 | "_id": "AV2saE9h0sacwixIDLu5", 31 | "_score": 4.5685062, 32 | "_source": { 33 | "duration_cpu_ms": 404.687, 34 | "external_requests.jdbc.count": 1, 35 | "instance": "localhost", 36 | "http.headers.accept-language": "en-GB,en-US;q=0.8,en;q=0.6", 37 | "tracking.unique_visitor_id": "01b7612cf06f9b65a2ef604c3659a7f9707c9fdd", 38 | "http.url": "/petclinic/owners.html", 39 | "type": "http", 40 | "http.headers.accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", 41 | "error": false, 42 | "http.headers.referer": "http://localhost:9966/petclinic/owners/find.html", 43 | "bytes_written": 2837, 44 | "http.headers.connection": "keep-alive", 45 | "http.headers.host": "localhost:9966", 46 | "span.kind": "server", 47 | "host": "Fabians-MBP.isys-software.de", 48 | "external_requests.jdbc.duration_ms": 4.880729, 49 | "id": "30d10566858cd70e", 50 | "jdbc_get_connection_count": 1, 51 | "trace_id": "30d10566858cd70e", 52 | "method": "GET", 53 | "http.headers.dnt": "1", 54 | "http.headers.user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", 55 | "session_id": "D3EE1EAE25654D56B33E3FDFFA1577FF", 56 | "peer.port": 59732, 57 | "peer.ipv6": "0:0:0:0:0:0:0:1", 58 | "duration_ms": 598.432389, 59 | "http.status_code": 200, 60 | "http.headers.accept-encoding": "gzip, deflate, br", 61 | "@timestamp": "2017-08-04T10:41:41.976+0200", 62 | "application": "Spring-PetClinic", 63 | "gc_time_ms": 50, 64 | "jdbc_connection_wait_time_ms": 0.123325, 65 | "name": "Process Find Form", 66 | "call_tree_json": "{\"signature\":\"GET /\",\"executionTime\":482029490,\"children\":[{\"signature\":\"void org.springframework.web.servlet.FrameworkServlet.service(HttpServletRequest,HttpServletResponse)\",\"executionTime\":215544165,\"children\":[{\"signature\":\"void org.springframework.web.servlet.DispatcherServlet.render(ModelAndView,HttpServletRequest,HttpServletResponse)\",\"executionTime\":196224386,\"children\":[],\"ioquery\":false,\"netExecutionTime\":196224386,\"shortSignature\":\"DispatcherServlet#render\"}],\"ioquery\":false,\"netExecutionTime\":19319779,\"shortSignature\":\"FrameworkServlet#service\"}],\"ioquery\":false,\"netExecutionTime\":266485325,\"shortSignature\":null}", 67 | "http.headers.upgrade-insecure-requests": "1", 68 | "parameters": [ 69 | { 70 | "key": "lastName", 71 | "value": "f" 72 | } 73 | ] 74 | } 75 | } 76 | ] 77 | -------------------------------------------------------------------------------- /public/lib/jquery-treetable/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "_args": [ 3 | [ 4 | { 5 | "raw": "ludo/jquery-treetable", 6 | "scope": null, 7 | "escapedName": null, 8 | "name": null, 9 | "rawSpec": "ludo/jquery-treetable", 10 | "spec": "github:ludo/jquery-treetable", 11 | "type": "hosted", 12 | "hosted": { 13 | "type": "github", 14 | "ssh": "git@github.com:ludo/jquery-treetable.git", 15 | "sshUrl": "git+ssh://git@github.com/ludo/jquery-treetable.git", 16 | "httpsUrl": "git+https://github.com/ludo/jquery-treetable.git", 17 | "gitUrl": "git://github.com/ludo/jquery-treetable.git", 18 | "shortcut": "github:ludo/jquery-treetable", 19 | "directUrl": "https://raw.githubusercontent.com/ludo/jquery-treetable/master/package.json" 20 | } 21 | }, 22 | "/Users/fabiantrampusch/workspace/stagemonitor-kibana/src/plugins/stagemonitor-kibana" 23 | ] 24 | ], 25 | "_from": "ludo/jquery-treetable", 26 | "_id": "jquery-treetable@3.2.0", 27 | "_inCache": true, 28 | "_location": "/jquery-treetable", 29 | "_phantomChildren": {}, 30 | "_requested": { 31 | "raw": "ludo/jquery-treetable", 32 | "scope": null, 33 | "escapedName": null, 34 | "name": null, 35 | "rawSpec": "ludo/jquery-treetable", 36 | "spec": "github:ludo/jquery-treetable", 37 | "type": "hosted", 38 | "hosted": { 39 | "type": "github", 40 | "ssh": "git@github.com:ludo/jquery-treetable.git", 41 | "sshUrl": "git+ssh://git@github.com/ludo/jquery-treetable.git", 42 | "httpsUrl": "git+https://github.com/ludo/jquery-treetable.git", 43 | "gitUrl": "git://github.com/ludo/jquery-treetable.git", 44 | "shortcut": "github:ludo/jquery-treetable", 45 | "directUrl": "https://raw.githubusercontent.com/ludo/jquery-treetable/master/package.json" 46 | } 47 | }, 48 | "_requiredBy": [ 49 | "#USER", 50 | "/" 51 | ], 52 | "_resolved": "git://github.com/ludo/jquery-treetable.git#60c5ddfe45c94106e79694d8797fc54d4c2e807d", 53 | "_shasum": "fa6712ce6399e5fe279b0785d9cc3a571665b4f9", 54 | "_shrinkwrap": null, 55 | "_spec": "ludo/jquery-treetable", 56 | "_where": "/Users/fabiantrampusch/workspace/stagemonitor-kibana/src/plugins/stagemonitor-kibana", 57 | "author": { 58 | "name": "Ludo van den Boom", 59 | "email": "ludo@cubicphuse.nl" 60 | }, 61 | "bugs": { 62 | "url": "https://github.com/ludo/jquery-treetable/issues" 63 | }, 64 | "dependencies": { 65 | "jquery": ">=1.6" 66 | }, 67 | "description": "jQuery plugin for displaying a tree structure in a (HTML) table, i.e. a directory structure or a nested list.", 68 | "devDependencies": {}, 69 | "directories": { 70 | "test": "test" 71 | }, 72 | "gitHead": "60c5ddfe45c94106e79694d8797fc54d4c2e807d", 73 | "homepage": "https://github.com/ludo/jquery-treetable#readme", 74 | "keywords": [ 75 | "explorer", 76 | "file", 77 | "table", 78 | "tree", 79 | "ui", 80 | "jquery" 81 | ], 82 | "license": "(MIT OR GPL-2.0)", 83 | "main": "jquery.treetable.js", 84 | "name": "jquery-treetable", 85 | "optionalDependencies": {}, 86 | "readme": "# jQuery treetable\n[![CDNJS](https://img.shields.io/cdnjs/v/jquery-treetable.svg)](https://cdnjs.com/libraries/jquery-treetable)\n\njQuery treetable is a plugin for jQuery, the 'Write Less, Do More, JavaScript\nLibrary'. With this plugin you can display a tree in an HTML table, e.g. a\ndirectory structure or a nested list. Why not use a list, you say? Because lists\nare great for displaying a tree, and tables are not. Oh wait, but this plugin\nuses tables, doesn't it? Yes. Why do I use a table to display a list? Because I\nneed multiple columns to display additional data besides the tree.\n\nDownload the latest release from the jQuery Plugin Registry or grab the source\ncode from Github. Please report issues through Github issues. This plugin is\nreleased under both the MIT and the GPLv2 license by Ludo van den Boom.\n\n## Documentation\n\nSee index.html for technical documentation and examples. The most recent version\nof this document is also available online at\nhttp://ludo.cubicphuse.nl/jquery-treetable. \n\n## Examples\n\n* JSFiddle with AJAX example: http://jsfiddle.net/djlerman/bbj8k9pe/ [@djlerman]\n* Example Ruby on Rails project with AJAX treetable: https://github.com/ludo/jquery-treetable-ajax-example\n", 87 | "readmeFilename": "README.md", 88 | "repository": { 89 | "type": "git", 90 | "url": "git+https://github.com/ludo/jquery-treetable.git" 91 | }, 92 | "scripts": { 93 | "test": "echo \"Error: no test specified\" && exit 1" 94 | }, 95 | "version": "3.2.0" 96 | } 97 | -------------------------------------------------------------------------------- /tests/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /public/lib/jquery-treetable/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 3.2.0 - June 13, 2014 2 | 3 | * [ENHANCEMENT] Simplified directory structure 4 | * [ENHANCEMENT] Do not show outline around indenter (#141) 5 | * [ENHANCEMENT] Use closure for better compatibility with other jQuery versions/plugins (#138) 6 | * [ENHANCEMENT] Bowerified (#113) 7 | * [BUG] Fix for nodes with undefined parent (#132) 8 | * [BUG] Fix removeNode() not deleting node from parent (#131) 9 | 10 | ## 3.1.0 - October 14, 2013 11 | 12 | * [FEATURE] Added branch sort API (#87) 13 | * [FEATURE] Added removeNode function to completely remove a node and its descendants (#75) 14 | * [ENHANCEMENT] Embed Base64 encoded theme images in CSS (#103) 15 | * [ENHANCEMENT] Do not show child nodes on expand of a hidden node (#97) 16 | * [BUG] Moving nodes around caused order of children to be reversed (#112) 17 | * [BUG] Bootstrap v3 compatibility fix for indenter (#111) 18 | * [BUG] D&D example code should not break when nested inside other table (#108) 19 | * [BUG] Node expanders wouldn't render correctly when nodes added dynamically (#79) 20 | 21 | ## 3.0.2 - July 3, 2013 22 | 23 | * [FEATURE] Add argument to treetable plugin function to force reinitialization (#65) 24 | * [BUG] Prevent error when new rows have leading white space (#77) 25 | * [BUG] Fix loadBranch case when new nodes end up at wrong position when added to a branch with already expanded child nodes (#73) 26 | 27 | ## 3.0.1 - April 6, 2013 28 | 29 | * [FEATURE] Expand/collapse nodes using keyboard Return key (#27, #30) 30 | * [FEATURE] Allow root nodes to be added to the tree with loadBranch (#56) 31 | * [ENHANCEMENT] Prevent potential reinitialization (#58) 32 | * [ENHANCEMENT] Add new nodes at the end of children (#52) 33 | * [ENHANCEMENT] Use on/off instead of bind/unbind for ataching event handlers (as per jQuery preferred guidelines) 34 | * [BUG] Nodes wouldn't initialize properly when using loadBranch (#55) 35 | 36 | ## 3.0.0 - February 14, 2013 37 | 38 | * [ENHANCEMENT] Use 'treetable' everywhere instead of a mix between 'treeTable' and 'treetable' 39 | 40 | ## 3.0.0-rc.2 - February 10, 2013 41 | 42 | NOTE: This is a release candidate. Please test this version and report any 43 | issues found. 44 | 45 | * [FEATURE] Added (optional) tt-branch data attribute for AJAX use case 46 | * [FEATURE] Added loadBranch()/unloadBranch() functions for adding/removing nodes to the tree (AJAX) 47 | * [FEATURE] Added onInitialized event 48 | * [FEATURE] Added onNodeInitialized event (#21) 49 | * [ENHANCEMENT] Split CSS file into two files, one with basic styling required in most cases (jquery.treetable.css) and a 'theme' CSS (jquery.treetable.theme.default.css) that styles the trees like the ones in the documentation (#51) 50 | * [ENHANCEMENT] Made appropriate plugin methods chainable 51 | * [BUG] Having an empty data-tt-parent-id attribute would result in an error 52 | * [BUG] A hardcoded reference to 'ttId' prevented proper use of nodeIdAttr setting 53 | 54 | ## 3.0.0-rc.1 - February 3, 2013 55 | 56 | NOTE: This is a release candidate. Please test this version and report any 57 | issues found. 58 | 59 | The treeTable plugin has been completely rewritten for this release. Please 60 | review the documentation for changes in the way the plugin is used and the 61 | options that are supported. 62 | 63 | * [FEATURE] Added test suite to verify plugin behavior 64 | * [ENHANCEMENT] Better adhere to jQuery plugin standards 65 | * [ENHANCEMENT] Use 'data-' attributes for tree metadata (#48, #7) 66 | * [ENHANCEMENT] Optimized performance of large trees (#41) 67 | * [BUG] Do not pollute the jQuery namespace (#45) 68 | * [REMOVED] Built-in persistence feature has been removed 69 | * [REMOVED] IE6 support 70 | 71 | ## 2.3.1 - 18 January 2013 72 | 73 | * [FEATURE] Keyboard accessibility (#27, #30) 74 | * [FEATURE] Optionally persist expanded node state using $.cookie (requires 75 | jquery-cookie plugin) (#19) 76 | * [ENHANCEMENT] Passing options to $.cookie persistence (#31) 77 | * [ENHANCEMENT] Use addClass('ui-helper-hidden') as suggested by whittet (#15) 78 | * [ENHANCEMENT] Added manifest file for jQuery plugin registry 79 | * [BUG] onNodeShow doesn't pass context (#28) 80 | * [BUG] Fixed problems with multiple treeTables with identical rows on the 81 | same page (#16) 82 | * [BUG] Fixed parentOf() not using options.childPrefix (#16) 83 | * [BUG] Prototype conflict (#14) 84 | * [BUG] Wrong documentation for "initialState" (#11) 85 | * [BUG] failed to reveal a node for a pitfall in parentOf (#8) 86 | 87 | ## 2.3.0 - 16 March 2010 88 | 89 | * Added GPL-LICENSE. The jQuery treeTable plugin is now dual-licensed under both the MIT and GPLv2 license. 90 | * Added reveal function to expand a tree to a given node. 91 | * Verified compatibility with jQuery 1.4.2. 92 | 93 | ## 2.2.3 - 18 August 2009 94 | 95 | * Further optimized performance by eliminating most calls to jQuery's css function 96 | 97 | ## 2.2.2 - 25 July 2009 98 | 99 | * Optimized performance of tree initialization (with initialState is collapsed) 100 | * Added option 'clickableNodeNames' to make entire node name clickable to expand branch 101 | * Updated jQuery to version 1.3.2 102 | * Updated jQuery UI components to version 1.7.2 (Core, Draggable, Droppable) 103 | 104 | ## 2.2.1 - 15 February 2009 105 | 106 | * Updated jQuery to version 1.3.1 107 | * Updated jQuery UI components to 1.6rc6 (Core, Draggable, Droppable) 108 | 109 | ## 2.2 - 18 January 2009 110 | 111 | * Fixed expander icon not showing on lazy-initialized nodes 112 | * Fixed drag and drop example code to work for tables within tables 113 | * Updated jQuery to version 1.3.0 114 | * Updated jQuery UI components to 1.6rc5 (Core, Draggable, Droppable) 115 | 116 | ## 2.1 - 16 November 2008 117 | 118 | * Optimized for faster initial loading (Issue #1) 119 | * Implemented lazy initialization of nodes (Issue #1) 120 | * Added information about the order of the rows in the HTML table to the documentation (Issue 2) 121 | * Added performance.html with an example of a large table with drag and drop 122 | 123 | ## 2.0 - 12 November 2008 124 | 125 | * Renamed plugin from ActsAsTreeTable to treeTable 126 | * Added a minified version of the source code (jquery.treeTable.min.js) 127 | * Added appendBranchTo function for easier manipulation of the tree with drag & drop (see docs for an example) 128 | * Added option 'childPrefix' to allow customisation of the prefix used for node classes (default: 'child-of-') 129 | * Renamed option 'default_state' to 'initialState' 130 | * Changed initialState from 'expanded' to 'collapsed' 131 | * Refactored collapse/expand behavior 132 | * Moved private function bodies to their respective public function 133 | 134 | ## 1.2 - 3 November 2008 135 | 136 | * Added option 'default_state' 137 | * Expose additional functions: collapse, expand and toggleBranch 138 | 139 | ## 1.1 - 21 October 2008 140 | 141 | * Fix JavaScript errors in IE7 due to comma-madness 142 | * Fix collapse/expand behavior in IE7 143 | 144 | ## 1.0 - 2 October 2008 145 | 146 | * Public release 147 | -------------------------------------------------------------------------------- /public/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | image/svg+xml 13 | 14 | 15 | 16 | 17 | Openclipart 18 | 19 | 20 | loudspeaker 21 | 2011-02-14T22:01:30 22 | Lautsprecher is a loudspeaker. 23 | http://openclipart.org/detail/118345/loudspeaker-by-forestgreen 24 | 25 | 26 | forestgreen 27 | 28 | 29 | 30 | 31 | clip art 32 | clipart 33 | loudspeaker 34 | music 35 | noise 36 | phono 37 | speaker 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /public/components/traceGraph/traceGraphDirective.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import d3 from 'd3'; 3 | import dagreD3 from 'dagre-d3-webpack'; 4 | import flat from 'flat'; 5 | import $ from 'jquery'; 6 | 7 | import traceGraph from './traceGraph.html'; 8 | 9 | export default () => { 10 | return { 11 | template: traceGraph, 12 | scope: { 13 | 'trace': '=' 14 | }, 15 | controller($scope, $element, elasticsearchService) { 16 | const vm = this; 17 | vm.spans = []; 18 | vm.selectedSpan = null; 19 | vm.selectedSpanProperties = []; 20 | vm.trace = vm.trace; 21 | vm.showCallTreeWarning = !localStorage.getItem('callTreeWarningDiscarded'); 22 | vm.hideCallTreeWarning = hideCallTreeWarning; 23 | 24 | loadSpansOfTrace(vm.trace); 25 | 26 | function hideCallTreeWarning() { 27 | localStorage.setItem('callTreeWarningDiscarded', true); 28 | vm.showCallTreeWarning = false; 29 | } 30 | 31 | function loadSpansOfTrace(trace) { 32 | elasticsearchService.searchAllSpansFor(trace._source.trace_id) 33 | .then((response) => { 34 | vm.spans = response.data.hits.hits; 35 | render(vm.spans); 36 | }); 37 | } 38 | 39 | function render(spans) { 40 | const graph = new dagreD3.graphlib.Graph() 41 | .setGraph({ 42 | nodesep: 70, 43 | ranksep: 50, 44 | rankdir: 'LR', 45 | marginx: 20, 46 | marginy: 20 47 | }) 48 | .setDefaultEdgeLabel(function () { 49 | return {}; 50 | }); 51 | 52 | const longestSpan = _.max(_.map(spans, '_source.duration_ms')); 53 | const highImpactThreshold = longestSpan / 5; 54 | const mediumImpactThreshold = longestSpan / 10; 55 | 56 | for (const span of spans) { 57 | let additionalClass = ''; 58 | if (span._source.duration_ms > highImpactThreshold) { 59 | additionalClass = 'node--status__high-impact'; 60 | } else if (span._source.duration_ms > mediumImpactThreshold) { 61 | additionalClass = 'node--status__medium-impact'; 62 | } 63 | 64 | if (!span.fields && span._source.call_tree_json) { 65 | span.fields = { 66 | call_tree_json: [span._source.call_tree_json] 67 | }; 68 | } 69 | span.iconClass = iconForSpan(span._source); 70 | span.iconTitle = iconTitleForSpan(span._source); 71 | 72 | const node = { 73 | span: span, 74 | spanDiscoverUrl: `../app/kibana#/doc/stagemonitor-spans-*/${span._index}/spans?id=${span._id}` + 75 | `&_g=(refreshInterval:(display:Off,pause:!f,value:0),time:(from:now-5y,mode:quick,to:now))`, 76 | label: `
77 | 78 | 79 | 80 | ${span._source.name} 81 | ${(span.fields && span.fields.call_tree_json) ? ' ' : ''} 82 |
83 | ${_.round(span._source.duration_ms, 2)} ms, ${span._source.application} 84 |
`, 85 | labelType: 'html' 86 | }; 87 | graph.setNode(span._source.id, node); 88 | } 89 | 90 | graph.nodes().forEach(function (v) { 91 | const node = graph.node(v); 92 | // Round the corners of the nodes 93 | node.rx = node.ry = 5; 94 | node.paddingTop = 0; 95 | node.paddingRight = 5; 96 | node.paddingBottom = 0; 97 | node.paddingLeft = 0; 98 | }); 99 | 100 | // set edges 101 | for (const span of spans) { 102 | if (span._source.parent_id && graph.node(span._source.parent_id) && graph.node(span._source.id)) { 103 | graph.setEdge(span._source.parent_id, span._source.id); 104 | } else if (span._source.parent_id && (!graph.node(span._source.parent_id) || !graph.node(span._source.id))) { 105 | console.log('no edge between ' + span._source.parent_id + ' and ' + span._source.id + ' possible, one of them does not exist.'); 106 | } 107 | } 108 | 109 | // Create the renderer 110 | const render = new dagreD3.render(); 111 | const svgSelector = '#trace-' + vm.trace._source.trace_id; 112 | const svg = d3.select(svgSelector); 113 | const svgGroup = svg.select('g'); 114 | const zoom = initializeZoom(); 115 | render(d3.select(svgSelector + ' g'), graph); 116 | centerAndInitialScale(zoom); 117 | d3.selectAll(svgSelector + ' g.node').on('click', openSpanDetails); 118 | d3.selectAll(svgSelector + ' g.node [data-calltree]').on('click', openCallTree); 119 | 120 | function initializeZoom() { 121 | const zoom = d3.behavior.zoom().on('zoom', function () { 122 | svgGroup.attr('transform', 'translate(' + d3.event.translate + ')' + 123 | 'scale(' + d3.event.scale + ')'); 124 | }); 125 | svg.call(zoom); 126 | return zoom; 127 | } 128 | 129 | function centerAndInitialScale(zoom) { 130 | const graphWidth = graph.graph().width + 80; 131 | const graphHeight = graph.graph().height + 40; 132 | svg.style('height', Math.min(graphHeight, 800) + 'px'); 133 | const width = parseInt(svg.style('width').replace(/px/, '')); 134 | const height = parseInt(svg.style('height').replace(/px/, '')); 135 | const zoomScale = Math.min(Math.min(width / graphWidth, height / graphHeight), 2); 136 | const translate = [(width / 2) - ((graphWidth * zoomScale) / 2), (height / 2) - ((graphHeight * zoomScale) / 2)]; 137 | zoom.translate(translate); 138 | zoom.scale(zoomScale); 139 | zoom.event(d3.select('svg')); 140 | } 141 | 142 | function openSpanDetails(nodeId) { 143 | const node = graph.node(nodeId); 144 | $('.span-open', $element).removeClass('span-open'); 145 | node.elem.classList.add('span-open'); 146 | 147 | const props = flattenSpanSource(node.span._source); 148 | $scope.$apply(() => { 149 | vm.selectedSpanProperties = []; 150 | vm.selectedSpan = node.span; 151 | vm.selectedSpanProperties = _.sortBy(props, 'propName'); 152 | 153 | const hasCallTree = node.span.fields && node.span.fields.call_tree_json; 154 | if (!vm.openedTab || (vm.openedTab === 'calltree' && !hasCallTree)) { 155 | vm.openedTab = 'attributes'; 156 | } 157 | }); 158 | } 159 | 160 | function openCallTree() { 161 | const clickedNodeId = $(this).closest('.node--span').data('id'); 162 | vm.openedTab = 'calltree'; 163 | openSpanDetails(clickedNodeId); 164 | } 165 | 166 | function flattenSpanSource(spanSource) { 167 | const flatSpanSource = flat(spanSource); 168 | const props = []; 169 | 170 | for (const propName in flatSpanSource) { 171 | if (flatSpanSource.hasOwnProperty(propName)) { 172 | props.push({ 173 | propName, 174 | value: flatSpanSource[propName] 175 | }); 176 | } 177 | } 178 | return props; 179 | } 180 | 181 | function iconForSpan(span) { 182 | const spanTypeIcons = { 183 | http: 'fa-server', 184 | jdbc: 'fa-database', 185 | soap: 'fa-plane', 186 | 187 | pageload: 'fa-user', 188 | js_error: 'fa-user', 189 | ajax: 'fa-user' 190 | }; 191 | 192 | const spanKindIcons = { 193 | server: 'fa-server', 194 | client: 'fa-sign-out' 195 | }; 196 | 197 | if (spanTypeIcons[span.type]) { 198 | return spanTypeIcons[span.type]; 199 | } else { 200 | return spanKindIcons[span.kind]; 201 | } 202 | } 203 | 204 | function iconTitleForSpan(span) { 205 | const spanTypeTitle = { 206 | http: 'HTTP', 207 | jdbc: 'JDBC', 208 | soap: 'SOAP', 209 | 210 | pageload: 'Pageload', 211 | js_error: 'JavaScript error', 212 | ajax: 'AJAX request' 213 | }; 214 | 215 | const spanKindTitle = { 216 | server: 'Incoming request', 217 | client: 'External request' 218 | }; 219 | 220 | if (spanTypeTitle[span.type]) { 221 | return spanTypeTitle[span.type]; 222 | } else { 223 | return spanKindTitle[span.kind]; 224 | } 225 | } 226 | } 227 | 228 | }, 229 | controllerAs: 'ctrl', 230 | bindToController: true 231 | }; 232 | }; 233 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2014-2016 iSYS Software GmbH 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /public/lib/jquery-treetable/GPL-LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. -------------------------------------------------------------------------------- /public/lib/jquery-treetable/jquery.treetable.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery treetable Plugin 3.2.0 3 | * http://ludo.cubicphuse.nl/jquery-treetable 4 | * 5 | * Copyright 2013, Ludo van den Boom 6 | * Dual licensed under the MIT or GPL Version 2 licenses. 7 | */ 8 | (function($) { 9 | "use strict"; 10 | 11 | var Node, Tree, methods; 12 | 13 | Node = (function() { 14 | function Node(row, tree, settings) { 15 | var parentId; 16 | 17 | this.row = row; 18 | this.tree = tree; 19 | this.settings = settings; 20 | 21 | // TODO Ensure id/parentId is always a string (not int) 22 | this.id = this.row.data(this.settings.nodeIdAttr); 23 | 24 | // TODO Move this to a setParentId function? 25 | parentId = this.row.data(this.settings.parentIdAttr); 26 | if (parentId != null && parentId !== "") { 27 | this.parentId = parentId; 28 | } 29 | 30 | this.treeCell = $(this.row.children(this.settings.columnElType)[this.settings.column]); 31 | this.expander = $(this.settings.expanderTemplate); 32 | this.indenter = $(this.settings.indenterTemplate); 33 | this.cell = $(this.settings.cellTemplate); 34 | this.children = []; 35 | this.initialized = false; 36 | this.treeCell.prepend(this.indenter); 37 | this.treeCell.wrapInner(this.cell); 38 | } 39 | 40 | Node.prototype.addChild = function(child) { 41 | return this.children.push(child); 42 | }; 43 | 44 | Node.prototype.ancestors = function() { 45 | var ancestors, node; 46 | node = this; 47 | ancestors = []; 48 | while (node = node.parentNode()) { 49 | ancestors.push(node); 50 | } 51 | return ancestors; 52 | }; 53 | 54 | Node.prototype.collapse = function() { 55 | if (this.collapsed()) { 56 | return this; 57 | } 58 | 59 | this.row.removeClass("expanded").addClass("collapsed"); 60 | 61 | this._hideChildren(); 62 | this.expander.attr("title", this.settings.stringExpand); 63 | 64 | if (this.initialized && this.settings.onNodeCollapse != null) { 65 | this.settings.onNodeCollapse.apply(this); 66 | } 67 | 68 | return this; 69 | }; 70 | 71 | Node.prototype.collapsed = function() { 72 | return this.row.hasClass("collapsed"); 73 | }; 74 | 75 | // TODO destroy: remove event handlers, expander, indenter, etc. 76 | 77 | Node.prototype.expand = function() { 78 | if (this.expanded()) { 79 | return this; 80 | } 81 | 82 | this.row.removeClass("collapsed").addClass("expanded"); 83 | 84 | if (this.initialized && this.settings.onNodeExpand != null) { 85 | this.settings.onNodeExpand.apply(this); 86 | } 87 | 88 | if ($(this.row).is(":visible")) { 89 | this._showChildren(); 90 | } 91 | 92 | this.expander.attr("title", this.settings.stringCollapse); 93 | 94 | return this; 95 | }; 96 | 97 | Node.prototype.expanded = function() { 98 | return this.row.hasClass("expanded"); 99 | }; 100 | 101 | Node.prototype.hide = function() { 102 | this._hideChildren(); 103 | this.row.hide(); 104 | return this; 105 | }; 106 | 107 | Node.prototype.isBranchNode = function() { 108 | if(this.children.length > 0 || this.row.data(this.settings.branchAttr) === true) { 109 | return true; 110 | } else { 111 | return false; 112 | } 113 | }; 114 | 115 | Node.prototype.updateBranchLeafClass = function(){ 116 | this.row.removeClass('branch'); 117 | this.row.removeClass('leaf'); 118 | this.row.addClass(this.isBranchNode() ? 'branch' : 'leaf'); 119 | }; 120 | 121 | Node.prototype.level = function() { 122 | return this.ancestors().length; 123 | }; 124 | 125 | Node.prototype.parentNode = function() { 126 | if (this.parentId != null) { 127 | return this.tree[this.parentId]; 128 | } else { 129 | return null; 130 | } 131 | }; 132 | 133 | Node.prototype.removeChild = function(child) { 134 | var i = $.inArray(child, this.children); 135 | return this.children.splice(i, 1) 136 | }; 137 | 138 | Node.prototype.render = function() { 139 | var handler, 140 | settings = this.settings, 141 | target; 142 | 143 | if (settings.expandable === true && this.isBranchNode()) { 144 | handler = function(e) { 145 | $(this).parents("table").treetable("node", $(this).parents("tr").data(settings.nodeIdAttr)).toggle(); 146 | return e.preventDefault(); 147 | }; 148 | 149 | this.indenter.html(this.expander); 150 | target = settings.clickableNodeNames === true ? this.treeCell : this.expander; 151 | 152 | target.off("click.treetable").on("click.treetable", handler); 153 | target.off("keydown.treetable").on("keydown.treetable", function(e) { 154 | if (e.keyCode == 13) { 155 | handler.apply(this, [e]); 156 | } 157 | }); 158 | } 159 | 160 | this.treeCell[0].style.paddingLeft = "" + ((this.level()) * settings.indent + 9) + "px"; 161 | 162 | return this; 163 | }; 164 | 165 | Node.prototype.reveal = function() { 166 | if (this.parentId != null) { 167 | this.parentNode().reveal(); 168 | } 169 | return this.expand(); 170 | }; 171 | 172 | Node.prototype.setParent = function(node) { 173 | if (this.parentId != null) { 174 | this.tree[this.parentId].removeChild(this); 175 | } 176 | this.parentId = node.id; 177 | this.row.data(this.settings.parentIdAttr, node.id); 178 | return node.addChild(this); 179 | }; 180 | 181 | Node.prototype.show = function() { 182 | if (!this.initialized) { 183 | this._initialize(); 184 | } 185 | this.row.show(); 186 | if (this.expanded()) { 187 | this._showChildren(); 188 | } 189 | return this; 190 | }; 191 | 192 | Node.prototype.toggle = function() { 193 | if (this.expanded()) { 194 | this.collapse(); 195 | } else { 196 | this.expand(); 197 | } 198 | return this; 199 | }; 200 | 201 | Node.prototype._hideChildren = function() { 202 | var child, _i, _len, _ref, _results; 203 | _ref = this.children; 204 | _results = []; 205 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 206 | child = _ref[_i]; 207 | _results.push(child.hide()); 208 | } 209 | return _results; 210 | }; 211 | 212 | Node.prototype._initialize = function() { 213 | var settings = this.settings; 214 | 215 | this.render(); 216 | 217 | if (settings.expandable === true && settings.initialState === "collapsed") { 218 | this.collapse(); 219 | } else { 220 | this.expand(); 221 | } 222 | 223 | if (settings.onNodeInitialized != null) { 224 | settings.onNodeInitialized.apply(this); 225 | } 226 | 227 | return this.initialized = true; 228 | }; 229 | 230 | Node.prototype._showChildren = function() { 231 | var child, _i, _len, _ref, _results; 232 | _ref = this.children; 233 | _results = []; 234 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 235 | child = _ref[_i]; 236 | _results.push(child.show()); 237 | } 238 | return _results; 239 | }; 240 | 241 | return Node; 242 | })(); 243 | 244 | Tree = (function() { 245 | function Tree(table, settings) { 246 | this.table = table; 247 | this.settings = settings; 248 | this.tree = {}; 249 | 250 | // Cache the nodes and roots in simple arrays for quick access/iteration 251 | this.nodes = []; 252 | this.roots = []; 253 | } 254 | 255 | Tree.prototype.collapseAll = function() { 256 | var node, _i, _len, _ref, _results; 257 | _ref = this.nodes; 258 | _results = []; 259 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 260 | node = _ref[_i]; 261 | _results.push(node.collapse()); 262 | } 263 | return _results; 264 | }; 265 | 266 | Tree.prototype.expandAll = function() { 267 | var node, _i, _len, _ref, _results; 268 | _ref = this.nodes; 269 | _results = []; 270 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 271 | node = _ref[_i]; 272 | _results.push(node.expand()); 273 | } 274 | return _results; 275 | }; 276 | 277 | Tree.prototype.findLastNode = function (node) { 278 | if (node.children.length > 0) { 279 | return this.findLastNode(node.children[node.children.length - 1]); 280 | } else { 281 | return node; 282 | } 283 | }; 284 | 285 | Tree.prototype.loadRows = function(rows) { 286 | var node, row, i; 287 | 288 | if (rows != null) { 289 | for (i = 0; i < rows.length; i++) { 290 | row = $(rows[i]); 291 | 292 | if (row.data(this.settings.nodeIdAttr) != null) { 293 | node = new Node(row, this.tree, this.settings); 294 | this.nodes.push(node); 295 | this.tree[node.id] = node; 296 | 297 | if (node.parentId != null && this.tree[node.parentId]) { 298 | this.tree[node.parentId].addChild(node); 299 | } else { 300 | this.roots.push(node); 301 | } 302 | } 303 | } 304 | } 305 | 306 | for (i = 0; i < this.nodes.length; i++) { 307 | node = this.nodes[i].updateBranchLeafClass(); 308 | } 309 | 310 | return this; 311 | }; 312 | 313 | Tree.prototype.move = function(node, destination) { 314 | // Conditions: 315 | // 1: +node+ should not be inserted as a child of +node+ itself. 316 | // 2: +destination+ should not be the same as +node+'s current parent (this 317 | // prevents +node+ from being moved to the same location where it already 318 | // is). 319 | // 3: +node+ should not be inserted in a location in a branch if this would 320 | // result in +node+ being an ancestor of itself. 321 | var nodeParent = node.parentNode(); 322 | if (node !== destination && destination.id !== node.parentId && $.inArray(node, destination.ancestors()) === -1) { 323 | node.setParent(destination); 324 | this._moveRows(node, destination); 325 | 326 | // Re-render parentNode if this is its first child node, and therefore 327 | // doesn't have the expander yet. 328 | if (node.parentNode().children.length === 1) { 329 | node.parentNode().render(); 330 | } 331 | } 332 | 333 | if(nodeParent){ 334 | nodeParent.updateBranchLeafClass(); 335 | } 336 | if(node.parentNode()){ 337 | node.parentNode().updateBranchLeafClass(); 338 | } 339 | node.updateBranchLeafClass(); 340 | return this; 341 | }; 342 | 343 | Tree.prototype.removeNode = function(node) { 344 | // Recursively remove all descendants of +node+ 345 | this.unloadBranch(node); 346 | 347 | // Remove node from DOM () 348 | node.row.remove(); 349 | 350 | // Remove node from parent children list 351 | if (node.parentId != null) { 352 | node.parentNode().removeChild(node); 353 | } 354 | 355 | // Clean up Tree object (so Node objects are GC-ed) 356 | delete this.tree[node.id]; 357 | this.nodes.splice($.inArray(node, this.nodes), 1); 358 | 359 | return this; 360 | } 361 | 362 | Tree.prototype.render = function() { 363 | var root, _i, _len, _ref; 364 | _ref = this.roots; 365 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 366 | root = _ref[_i]; 367 | 368 | // Naming is confusing (show/render). I do not call render on node from 369 | // here. 370 | root.show(); 371 | } 372 | return this; 373 | }; 374 | 375 | Tree.prototype.sortBranch = function(node, sortFun) { 376 | // First sort internal array of children 377 | node.children.sort(sortFun); 378 | 379 | // Next render rows in correct order on page 380 | this._sortChildRows(node); 381 | 382 | return this; 383 | }; 384 | 385 | Tree.prototype.unloadBranch = function(node) { 386 | // Use a copy of the children array to not have other functions interfere 387 | // with this function if they manipulate the children array 388 | // (eg removeNode). 389 | var children = node.children.slice(0), 390 | i; 391 | 392 | for (i = 0; i < children.length; i++) { 393 | this.removeNode(children[i]); 394 | } 395 | 396 | // Reset node's collection of children 397 | node.children = []; 398 | 399 | node.updateBranchLeafClass(); 400 | 401 | return this; 402 | }; 403 | 404 | Tree.prototype._moveRows = function(node, destination) { 405 | var children = node.children, i; 406 | 407 | node.row.insertAfter(destination.row); 408 | node.render(); 409 | 410 | // Loop backwards through children to have them end up on UI in correct 411 | // order (see #112) 412 | for (i = children.length - 1; i >= 0; i--) { 413 | this._moveRows(children[i], node); 414 | } 415 | }; 416 | 417 | // Special _moveRows case, move children to itself to force sorting 418 | Tree.prototype._sortChildRows = function(parentNode) { 419 | return this._moveRows(parentNode, parentNode); 420 | }; 421 | 422 | return Tree; 423 | })(); 424 | 425 | // jQuery Plugin 426 | methods = { 427 | init: function(options, force) { 428 | var settings; 429 | 430 | settings = $.extend({ 431 | branchAttr: "ttBranch", 432 | clickableNodeNames: false, 433 | column: 0, 434 | columnElType: "td", // i.e. 'td', 'th' or 'td,th' 435 | expandable: false, 436 | expanderTemplate: " ", 437 | indent: 19, 438 | indenterTemplate: "", 439 | cellTemplate: '', 440 | initialState: "collapsed", 441 | nodeIdAttr: "ttId", // maps to data-tt-id 442 | parentIdAttr: "ttParentId", // maps to data-tt-parent-id 443 | stringExpand: "Expand", 444 | stringCollapse: "Collapse", 445 | 446 | // Events 447 | onInitialized: null, 448 | onNodeCollapse: null, 449 | onNodeExpand: null, 450 | onNodeInitialized: null 451 | }, options); 452 | 453 | return this.each(function() { 454 | var el = $(this), tree; 455 | 456 | if (force || el.data("treetable") === undefined) { 457 | tree = new Tree(this, settings); 458 | tree.loadRows(this.rows).render(); 459 | 460 | el.addClass("treetable").data("treetable", tree); 461 | 462 | if (settings.onInitialized != null) { 463 | settings.onInitialized.apply(tree); 464 | } 465 | } 466 | 467 | return el; 468 | }); 469 | }, 470 | 471 | destroy: function() { 472 | return this.each(function() { 473 | return $(this).removeData("treetable").removeClass("treetable"); 474 | }); 475 | }, 476 | 477 | collapseAll: function() { 478 | this.data("treetable").collapseAll(); 479 | return this; 480 | }, 481 | 482 | collapseNode: function(id) { 483 | var node = this.data("treetable").tree[id]; 484 | 485 | if (node) { 486 | node.collapse(); 487 | } else { 488 | throw new Error("Unknown node '" + id + "'"); 489 | } 490 | 491 | return this; 492 | }, 493 | 494 | expandAll: function() { 495 | this.data("treetable").expandAll(); 496 | return this; 497 | }, 498 | 499 | expandNode: function(id) { 500 | var node = this.data("treetable").tree[id]; 501 | 502 | if (node) { 503 | if (!node.initialized) { 504 | node._initialize(); 505 | } 506 | 507 | node.expand(); 508 | } else { 509 | throw new Error("Unknown node '" + id + "'"); 510 | } 511 | 512 | return this; 513 | }, 514 | 515 | loadBranch: function(node, rows) { 516 | var settings = this.data("treetable").settings, 517 | tree = this.data("treetable").tree; 518 | 519 | // TODO Switch to $.parseHTML 520 | rows = $(rows); 521 | 522 | if (node == null) { // Inserting new root nodes 523 | this.append(rows); 524 | } else { 525 | var lastNode = this.data("treetable").findLastNode(node); 526 | rows.insertAfter(lastNode.row); 527 | } 528 | 529 | this.data("treetable").loadRows(rows); 530 | 531 | // Make sure nodes are properly initialized 532 | rows.filter("tr").each(function() { 533 | tree[$(this).data(settings.nodeIdAttr)].show(); 534 | }); 535 | 536 | if (node != null) { 537 | // Re-render parent to ensure expander icon is shown (#79) 538 | node.render().expand(); 539 | } 540 | 541 | return this; 542 | }, 543 | 544 | move: function(nodeId, destinationId) { 545 | var destination, node; 546 | 547 | node = this.data("treetable").tree[nodeId]; 548 | destination = this.data("treetable").tree[destinationId]; 549 | this.data("treetable").move(node, destination); 550 | 551 | return this; 552 | }, 553 | 554 | node: function(id) { 555 | return this.data("treetable").tree[id]; 556 | }, 557 | 558 | removeNode: function(id) { 559 | var node = this.data("treetable").tree[id]; 560 | 561 | if (node) { 562 | this.data("treetable").removeNode(node); 563 | } else { 564 | throw new Error("Unknown node '" + id + "'"); 565 | } 566 | 567 | return this; 568 | }, 569 | 570 | reveal: function(id) { 571 | var node = this.data("treetable").tree[id]; 572 | 573 | if (node) { 574 | node.reveal(); 575 | } else { 576 | throw new Error("Unknown node '" + id + "'"); 577 | } 578 | 579 | return this; 580 | }, 581 | 582 | sortBranch: function(node, columnOrFunction) { 583 | var settings = this.data("treetable").settings, 584 | prepValue, 585 | sortFun; 586 | 587 | columnOrFunction = columnOrFunction || settings.column; 588 | sortFun = columnOrFunction; 589 | 590 | if ($.isNumeric(columnOrFunction)) { 591 | sortFun = function(a, b) { 592 | var extractValue, valA, valB; 593 | 594 | extractValue = function(node) { 595 | var val = node.row.find("td:eq(" + columnOrFunction + ")").text(); 596 | // Ignore trailing/leading whitespace and use uppercase values for 597 | // case insensitive ordering 598 | return $.trim(val).toUpperCase(); 599 | } 600 | 601 | valA = extractValue(a); 602 | valB = extractValue(b); 603 | 604 | if (valA < valB) return -1; 605 | if (valA > valB) return 1; 606 | return 0; 607 | }; 608 | } 609 | 610 | this.data("treetable").sortBranch(node, sortFun); 611 | return this; 612 | }, 613 | 614 | unloadBranch: function(node) { 615 | this.data("treetable").unloadBranch(node); 616 | return this; 617 | } 618 | }; 619 | 620 | $.fn.treetable = function(method) { 621 | if (methods[method]) { 622 | return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); 623 | } else if (typeof method === 'object' || !method) { 624 | return methods.init.apply(this, arguments); 625 | } else { 626 | return $.error("Method " + method + " does not exist on jQuery.treetable"); 627 | } 628 | }; 629 | 630 | // Expose classes to world 631 | window.TreeTable || (window.TreeTable = {}); 632 | window.TreeTable.Node = Node; 633 | window.TreeTable.Tree = Tree; 634 | })(jQuery); 635 | -------------------------------------------------------------------------------- /public/components/callTree/callTree.less: -------------------------------------------------------------------------------- 1 | @import "../../lib/jquery-treetable/css/jquery.treetable.css"; 2 | 3 | .treetable { 4 | 5 | table& { 6 | width: 100%; 7 | } 8 | 9 | thead tr th, 10 | tbody tr td { 11 | cursor: default; 12 | padding: .3em 1em; 13 | border-bottom: 1px solid #ccc; 14 | } 15 | 16 | tbody tr:last-child td { 17 | border-bottom: none; 18 | } 19 | 20 | span { 21 | background-position: center left; 22 | background-repeat: no-repeat; 23 | padding: 0; 24 | color: #000; 25 | } 26 | 27 | span.file { 28 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAADoSURBVBgZBcExblNBGAbA2ceegTRBuIKOgiihSZNTcC5LUHAihNJR0kGKCDcYJY6D3/77MdOinTvzAgCw8ysThIvn/VojIyMjIyPP+bS1sUQIV2s95pBDDvmbP/mdkft83tpYguZq5Jh/OeaYh+yzy8hTHvNlaxNNczm+la9OTlar1UdA/+C2A4trRCnD3jS8BB1obq2Gk6GU6QbQAS4BUaYSQAf4bhhKKTFdAzrAOwAxEUAH+KEM01SY3gM6wBsEAQB0gJ+maZoC3gI6iPYaAIBJsiRmHU0AALOeFC3aK2cWAACUXe7+AwO0lc9eTHYTAAAAAElFTkSuQmCC); 29 | } 30 | 31 | span.folder { 32 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAGrSURBVDjLxZO7ihRBFIa/6u0ZW7GHBUV0UQQTZzd3QdhMQxOfwMRXEANBMNQX0MzAzFAwEzHwARbNFDdwEd31Mj3X7a6uOr9BtzNjYjKBJ6nicP7v3KqcJFaxhBVtZUAK8OHlld2st7Xl3DJPVONP+zEUV4HqL5UDYHr5xvuQAjgl/Qs7TzvOOVAjxjlC+ePSwe6DfbVegLVuT4r14eTr6zvA8xSAoBLzx6pvj4l+DZIezuVkG9fY2H7YRQIMZIBwycmzH1/s3F8AapfIPNF3kQk7+kw9PWBy+IZOdg5Ug3mkAATy/t0usovzGeCUWTjCz0B+Sj0ekfdvkZ3abBv+U4GaCtJ1iEm6ANQJ6fEzrG/engcKw/wXQvEKxSEKQxRGKE7Izt+DSiwBJMUSm71rguMYhQKrBygOIRStf4TiFFRBvbRGKiQLWP29yRSHKBTtfdBmHs0BUpgvtgF4yRFR+NUKi0XZcYjCeCG2smkzLAHkbRBmP0/Uk26O5YnUActBp1GsAI+S5nRJJJal5K1aAMrq0d6Tm9uI6zjyf75dAe6tx/SsWeD//o2/Ab6IH3/h25pOAAAAAElFTkSuQmCC); 33 | } 34 | 35 | tr.collapsed span.indenter a { 36 | background-image: url(data:image/png;base64,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); 37 | } 38 | 39 | tr.expanded span.indenter a { 40 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAAHFJREFUeNpi/P//PwMlgImBQsA44C6gvhfa29v3MzAwOODRc6CystIRbxi0t7fjDJjKykpGYrwwi1hxnLHQ3t7+jIGBQRJJ6HllZaUUKYEYRYBPOB0gBShKwKGA////48VtbW3/8clTnBIH3gCKkzJgAGvBX0dDm0sCAAAAAElFTkSuQmCC); 41 | } 42 | 43 | tr.leaf { 44 | /* background-color: #f9f9f9; */ 45 | } 46 | 47 | tr.selected { 48 | background-color: #3875d7; 49 | color: #fff; 50 | } 51 | 52 | tr span.indenter a { 53 | outline: none; /* Expander shows outline after upgrading to 3.0 (#141) */ 54 | } 55 | 56 | tr.collapsed.selected span.indenter a { 57 | background-image: url(data:image/png;base64,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); 58 | } 59 | 60 | tr.expanded.selected span.indenter a { 61 | background-image: url(data:image/png;base64,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); 62 | } 63 | 64 | tr.accept { 65 | background-color: #a3bce4; 66 | color: #fff 67 | } 68 | 69 | tr.collapsed.accept td span.indenter a { 70 | background-image: url(data:image/x-png;base64,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); 71 | } 72 | 73 | tr.expanded.accept td span.indenter a { 74 | background-image: url(data:image/x-png;base64,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); 75 | } 76 | 77 | .indenter { 78 | display: inline !important; 79 | } 80 | 81 | .progress { 82 | margin-bottom: 0; 83 | } 84 | 85 | .progress .progress-bar { 86 | line-height: 20px; 87 | } 88 | 89 | pre { 90 | color: #000; 91 | white-space: pre-line; 92 | max-width: 50vw; 93 | } 94 | 95 | .branch td:hover { 96 | cursor: pointer; 97 | } 98 | 99 | .branch:hover { 100 | font-weight: bold; 101 | } 102 | 103 | .query-count, 104 | .query-count span { 105 | color: #fff; 106 | } 107 | 108 | .stagemonitor-calltree-executiontime { 109 | width: 10%; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /public/lib/jquery-treetable/css/jquery.treetable.theme.default.css: -------------------------------------------------------------------------------- 1 | table.treetable { 2 | border: 1px solid #888; 3 | border-collapse: collapse; 4 | font-size: .8em; 5 | line-height: 1; 6 | margin: .6em 0 1.8em 0; 7 | width: 100%; 8 | } 9 | 10 | table.treetable caption { 11 | font-size: .9em; 12 | font-weight: bold; 13 | margin-bottom: .2em; 14 | } 15 | 16 | table.treetable thead { 17 | background: #aaa url(data:image/png;base64,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) repeat-x top left; 18 | font-size: .9em; 19 | } 20 | 21 | table.treetable thead tr th { 22 | border: 1px solid #888; 23 | font-weight: normal; 24 | padding: .3em 1em .1em 1em; 25 | text-align: left; 26 | } 27 | 28 | table.treetable tbody tr td { 29 | cursor: default; 30 | padding: .3em 1em; 31 | } 32 | 33 | table.treetable span { 34 | background-position: center left; 35 | background-repeat: no-repeat; 36 | padding: .2em 0 .2em 1.5em; 37 | } 38 | 39 | table.treetable span.file { 40 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAADoSURBVBgZBcExblNBGAbA2ceegTRBuIKOgiihSZNTcC5LUHAihNJR0kGKCDcYJY6D3/77MdOinTvzAgCw8ysThIvn/VojIyMjIyPP+bS1sUQIV2s95pBDDvmbP/mdkft83tpYguZq5Jh/OeaYh+yzy8hTHvNlaxNNczm+la9OTlar1UdA/+C2A4trRCnD3jS8BB1obq2Gk6GU6QbQAS4BUaYSQAf4bhhKKTFdAzrAOwAxEUAH+KEM01SY3gM6wBsEAQB0gJ+maZoC3gI6iPYaAIBJsiRmHU0AALOeFC3aK2cWAACUXe7+AwO0lc9eTHYTAAAAAElFTkSuQmCC); 41 | } 42 | 43 | table.treetable span.folder { 44 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAGrSURBVDjLxZO7ihRBFIa/6u0ZW7GHBUV0UQQTZzd3QdhMQxOfwMRXEANBMNQX0MzAzFAwEzHwARbNFDdwEd31Mj3X7a6uOr9BtzNjYjKBJ6nicP7v3KqcJFaxhBVtZUAK8OHlld2st7Xl3DJPVONP+zEUV4HqL5UDYHr5xvuQAjgl/Qs7TzvOOVAjxjlC+ePSwe6DfbVegLVuT4r14eTr6zvA8xSAoBLzx6pvj4l+DZIezuVkG9fY2H7YRQIMZIBwycmzH1/s3F8AapfIPNF3kQk7+kw9PWBy+IZOdg5Ug3mkAATy/t0usovzGeCUWTjCz0B+Sj0ekfdvkZ3abBv+U4GaCtJ1iEm6ANQJ6fEzrG/engcKw/wXQvEKxSEKQxRGKE7Izt+DSiwBJMUSm71rguMYhQKrBygOIRStf4TiFFRBvbRGKiQLWP29yRSHKBTtfdBmHs0BUpgvtgF4yRFR+NUKi0XZcYjCeCG2smkzLAHkbRBmP0/Uk26O5YnUActBp1GsAI+S5nRJJJal5K1aAMrq0d6Tm9uI6zjyf75dAe6tx/SsWeD//o2/Ab6IH3/h25pOAAAAAElFTkSuQmCC); 45 | } 46 | 47 | table.treetable tr.collapsed span.indenter a { 48 | background-image: url(data:image/png;base64,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); 49 | } 50 | 51 | table.treetable tr.expanded span.indenter a { 52 | background-image: url(data:image/png;base64,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); 53 | } 54 | 55 | table.treetable tr.branch { 56 | background-color: #f9f9f9; 57 | } 58 | 59 | table.treetable tr.selected { 60 | background-color: #3875d7; 61 | color: #fff; 62 | } 63 | 64 | table.treetable tr span.indenter a { 65 | outline: none; /* Expander shows outline after upgrading to 3.0 (#141) */ 66 | } 67 | 68 | table.treetable tr.collapsed.selected span.indenter a { 69 | background-image: url(data:image/png;base64,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); 70 | } 71 | 72 | table.treetable tr.expanded.selected span.indenter a { 73 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAAFtJREFUeNpi/P//PwMlgImBQsA44C6giQENDAwM//HgBmLCAF/AMBLjBUeixf///48L7/+PCvZjU4fPAAc0AxywqcMXCwegGJ1NckL6jx5wpKYDxqGXEkkCgAEAmrqBIejdgngAAAAASUVORK5CYII=); 74 | } 75 | 76 | table.treetable tr.accept { 77 | background-color: #a3bce4; 78 | color: #fff 79 | } 80 | 81 | table.treetable tr.collapsed.accept td span.indenter a { 82 | background-image: url(data:image/x-png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAAFpJREFUeNpi/P//PwMlgHHADWD4//8/NtyAQxwD45KAAQdKDfj//////fgMIsYAZIMw1DKREFwODAwM/4kNRKq64AADA4MjFDOQ6gKyY4HodMA49PMCxQYABgAVYHsjyZ1x7QAAAABJRU5ErkJggg==); 83 | } 84 | 85 | table.treetable tr.expanded.accept td span.indenter a { 86 | background-image: url(data:image/x-png;base64,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); 87 | } 88 | --------------------------------------------------------------------------------