From af7a9ae2bfbefc13e7af5e62329527e17f950bbe Mon Sep 17 00:00:00 2001 From: Claus Ibsen Date: Fri, 25 Sep 2015 15:24:14 +0200 Subject: [PATCH] Rebuild --- defs.d.ts | 18 +- dist/hawtio-jmx.js | 6030 ++++++++++++++++++++++---------------------- 2 files changed, 3024 insertions(+), 3024 deletions(-) diff --git a/defs.d.ts b/defs.d.ts index 1f1afee..8da087d 100644 --- a/defs.d.ts +++ b/defs.d.ts @@ -3,15 +3,6 @@ /// /// /// -/// -/// -/// -/// -/// -/// -/// -/// -/// /// /// /// @@ -26,5 +17,14 @@ /// /// /// +/// +/// +/// +/// +/// +/// +/// +/// +/// /// /// diff --git a/dist/hawtio-jmx.js b/dist/hawtio-jmx.js index 162a5c8..f02f603 100644 --- a/dist/hawtio-jmx.js +++ b/dist/hawtio-jmx.js @@ -1801,1053 +1801,1110 @@ var Core; Core.createServerConnectionUrl = createServerConnectionUrl; })(Core || (Core = {})); -/// -/// /** - * @module JVM - * @main JVM + * @module Jmx */ -var JVM; -(function (JVM) { - JVM.windowJolokia = undefined; - JVM._module = angular.module(JVM.pluginName, []); - JVM._module.config(["$provide", "$routeProvider", function ($provide, $routeProvider) { - /* - $provide.decorator('WelcomePageRegistry', ['$delegate', ($delegate) => { - return { - - } - }]); - */ - $routeProvider.when('/jvm', { redirectTo: '/jvm/connect' }).when('/jvm/welcome', { templateUrl: UrlHelpers.join(JVM.templatePath, 'welcome.html') }).when('/jvm/discover', { templateUrl: UrlHelpers.join(JVM.templatePath, 'discover.html') }).when('/jvm/connect', { templateUrl: UrlHelpers.join(JVM.templatePath, 'connect.html') }).when('/jvm/local', { templateUrl: UrlHelpers.join(JVM.templatePath, 'local.html') }); - }]); - JVM._module.constant('mbeanName', 'hawtio:type=JVMList'); - JVM._module.run(["HawtioNav", "$location", "workspace", "viewRegistry", "layoutFull", "helpRegistry", "preferencesRegistry", "ConnectOptions", "locationChangeStartTasks", "HawtioDashboard", "HawtioExtension", "$templateCache", "$compile", function (nav, $location, workspace, viewRegistry, layoutFull, helpRegistry, preferencesRegistry, ConnectOptions, locationChangeStartTasks, dash, extensions, $templateCache, $compile) { - extensions.add('hawtio-header', function ($scope) { - var template = $templateCache.get(UrlHelpers.join(JVM.templatePath, 'navbarHeaderExtension.html')); - return $compile(template)($scope); +var Jmx; +(function (Jmx) { + function createDashboardLink(widgetType, widget) { + var href = "#" + widgetType.route; + var routeParams = angular.toJson(widget); + var title = widget.title; + var size = angular.toJson({ + size_x: widgetType.size_x, + size_y: widgetType.size_y }); - if (!dash.inDashboard) { - // ensure that if the connection parameter is present, that we keep it - locationChangeStartTasks.addTask('ConParam', function ($event, newUrl, oldUrl) { - // we can't execute until the app is initialized... - if (!HawtioCore.injector) { - return; - } - //log.debug("ConParam task firing, newUrl: ", newUrl, " oldUrl: ", oldUrl, " ConnectOptions: ", ConnectOptions); - if (!ConnectOptions || !ConnectOptions.name || !newUrl) { - return; - } - var newQuery = new URI(newUrl).query(true); - if (!newQuery.con) { - //log.debug("Lost connection parameter (", ConnectOptions.name, ") from query params: ", newQuery, " resetting"); - newQuery['con'] = ConnectOptions.name; - $location.search(newQuery); - } - }); + return "/dashboard/add?tab=dashboard" + "&href=" + encodeURIComponent(href) + "&size=" + encodeURIComponent(size) + "&title=" + encodeURIComponent(title) + "&routeParams=" + encodeURIComponent(routeParams); + } + Jmx.createDashboardLink = createDashboardLink; + function getWidgetType(widget) { + return Jmx.jmxWidgetTypes.find(function (type) { + return type.type === widget.type; + }); + } + Jmx.getWidgetType = getWidgetType; + Jmx.jmxWidgetTypes = [ + { + type: "donut", + icon: "fa fa-pie-chart", + route: "/jmx/widget/donut", + size_x: 2, + size_y: 2, + title: "Add Donut chart to Dashboard" + }, + { + type: "area", + icon: "fa fa-bar-chart", + route: "/jmx/widget/area", + size_x: 4, + size_y: 2, + title: "Add Area chart to Dashboard" } - var builder = nav.builder(); - var remote = builder.id('jvm-remote').href(function () { return '/jvm/connect'; }).title(function () { return 'Remote'; }).tooltip(function () { return 'To connect to a remote JVM'; }).build(); - var local = builder.id('jvm-local').href(function () { return '/jvm/local'; }).title(function () { return 'Local'; }).tooltip(function () { return 'To connect to a locale JVM'; }).show(function () { return JVM.hasLocalMBean(workspace); }).build(); - var discover = builder.id('jvm-discover').href(function () { return '/jvm/discover'; }).title(function () { return 'Discover'; }).tooltip(function () { return 'To discover JVMs in the network that has Jolokia agent running'; }).show(function () { return JVM.hasDiscoveryMBean(workspace); }).build(); - var tab = builder.id('jvm').href(function () { return '/jvm'; }).title(function () { return 'Connect'; }).isValid(function () { return ConnectOptions == null || ConnectOptions.name == null; }).tabs(remote, local, discover).build(); - nav.add(tab); - helpRegistry.addUserDoc('jvm', 'plugins/jvm/doc/help.md'); - preferencesRegistry.addTab("Connect", 'plugins/jvm/html/reset.html'); - preferencesRegistry.addTab("Jolokia", "plugins/jvm/html/jolokiaPreferences.html"); - }]); - hawtioPluginLoader.addModule(JVM.pluginName); -})(JVM || (JVM = {})); + ]; + Jmx.jmxWidgets = [ + { + type: "donut", + title: "Java Heap Memory", + mbean: "java.lang:type=Memory", + attribute: "HeapMemoryUsage", + total: "Max", + terms: "Used", + remaining: "Free" + }, + { + type: "donut", + title: "Java Non Heap Memory", + mbean: "java.lang:type=Memory", + attribute: "NonHeapMemoryUsage", + total: "Max", + terms: "Used", + remaining: "Free" + }, + { + type: "donut", + title: "File Descriptor Usage", + mbean: "java.lang:type=OperatingSystem", + total: "MaxFileDescriptorCount", + terms: "OpenFileDescriptorCount", + remaining: "Free" + }, + { + type: "donut", + title: "Loaded Classes", + mbean: "java.lang:type=ClassLoading", + total: "TotalLoadedClassCount", + terms: "LoadedClassCount,UnloadedClassCount", + remaining: "-" + }, + { + type: "donut", + title: "Swap Size", + mbean: "java.lang:type=OperatingSystem", + total: "TotalSwapSpaceSize", + terms: "FreeSwapSpaceSize", + remaining: "Used Swap" + }, + { + type: "area", + title: "Process CPU Time", + mbean: "java.lang:type=OperatingSystem", + attribute: "ProcessCpuTime" + }, + { + type: "area", + title: "Process CPU Load", + mbean: "java.lang:type=OperatingSystem", + attribute: "ProcessCpuLoad" + }, + { + type: "area", + title: "System CPU Load", + mbean: "java.lang:type=OperatingSystem", + attribute: "SystemCpuLoad" + }, + { + type: "area", + title: "System CPU Time", + mbean: "java.lang:type=OperatingSystem", + attribute: "SystemCpuTime" + } + ]; +})(Jmx || (Jmx = {})); -/// -/// /** - * @module JVM + * @module Jmx + * @main Jmx */ -var JVM; -(function (JVM) { - JVM.ConnectController = JVM._module.controller("JVM.ConnectController", ["$scope", "$location", "localStorage", "workspace", "$http", function ($scope, $location, localStorage, workspace, $http) { - JVM.configureScope($scope, $location, workspace); - function newConfig() { - return Core.createConnectOptions({ - scheme: 'http', - host: 'localhost', - path: 'jolokia', - port: 8181, - userName: '', - password: '', - useProxy: !$scope.disableProxy - }); - } - ; - $scope.forms = {}; - $http.get('proxy').then(function (resp) { - if (resp.status === 200 && Core.isBlank(resp.data)) { - $scope.disableProxy = false; - } - else { - $scope.disableProxy = true; - } - }); - var hasMBeans = false; - workspace.addNamedTreePostProcessor('ConnectTab', function (tree) { - hasMBeans = workspace && workspace.tree && workspace.tree.children && workspace.tree.children.length > 0; - $scope.disableProxy = !hasMBeans || Core.isChromeApp(); - Core.$apply($scope); +/// +/// +/// +/// +/// +var Jmx; +(function (Jmx) { + Jmx._module = angular.module(Jmx.pluginName, []); + Jmx._module.config(['HawtioNavBuilderProvider', "$routeProvider", function (builder, $routeProvider) { + $routeProvider.when('/jmx', { redirectTo: '/jmx/attributes' }).when('/jmx/attributes', { templateUrl: UrlHelpers.join(Jmx.templatePath, 'attributes.html') }).when('/jmx/operations', { templateUrl: UrlHelpers.join(Jmx.templatePath, 'operations.html') }).when('/jmx/charts', { templateUrl: UrlHelpers.join(Jmx.templatePath, 'charts.html') }).when('/jmx/chartEdit', { templateUrl: UrlHelpers.join(Jmx.templatePath, 'chartEdit.html') }).when('/jmx/help/:tabName', { templateUrl: 'app/core/html/help.html' }).when('/jmx/widget/donut', { templateUrl: UrlHelpers.join(Jmx.templatePath, 'donutChart.html') }).when('/jmx/widget/area', { templateUrl: UrlHelpers.join(Jmx.templatePath, 'areaChart.html') }); + }]); + Jmx._module.factory('jmxWidgetTypes', function () { + return Jmx.jmxWidgetTypes; + }); + Jmx._module.factory('jmxWidgets', function () { + return Jmx.jmxWidgets; + }); + // Create the workspace object used in all kinds of places + Jmx._module.factory('workspace', ["$location", "jmxTreeLazyLoadRegistry", "$compile", "$templateCache", "localStorage", "jolokia", "jolokiaStatus", "$rootScope", "userDetails", "HawtioNav", function ($location, jmxTreeLazyLoadRegistry, $compile, $templateCache, localStorage, jolokia, jolokiaStatus, $rootScope, userDetails, HawtioNav) { + var answer = new Workspace(jolokia, jolokiaStatus, jmxTreeLazyLoadRegistry, $location, $compile, $templateCache, localStorage, $rootScope, userDetails, HawtioNav); + answer.loadTree(); + return answer; + }]); + Jmx._module.controller("Jmx.MBeanTreeController", ['$scope', 'workspace', function ($scope, workspace) { + $scope.node = {}; + workspace.addNamedTreePostProcessor('MBeanTree', function (tree) { + angular.copy(tree, $scope.node); + $scope.node.open = true; + Jmx.log.debug("got tree: ", $scope.node); }); - $scope.lastConnection = ''; - // load settings like current tab, last used connection - if (JVM.connectControllerKey in localStorage) { - try { - $scope.lastConnection = angular.fromJson(localStorage[JVM.connectControllerKey]); - } - catch (e) { - // corrupt config - $scope.lastConnection = ''; - delete localStorage[JVM.connectControllerKey]; - } - } - // load connection settings - $scope.connectionConfigs = Core.loadConnectionMap(); - if (!Core.isBlank($scope.lastConnection)) { - $scope.currentConfig = $scope.connectionConfigs[$scope.lastConnection]; - } - else { - $scope.currentConfig = newConfig(); - } - /* - log.debug("Controller settings: ", $scope.settings); - log.debug("Current config: ", $scope.currentConfig); - log.debug("All connection settings: ", $scope.connectionConfigs); - */ - $scope.formConfig = { - properties: { - name: { - type: "java.lang.String", - tooltip: "Name for this connection", - required: true, - "input-attributes": { - "placeholder": "Unnamed..." - } - }, - scheme: { - type: "java.lang.String", - tooltip: "HTTP or HTTPS", - enum: ["http", "https"], - required: true - }, - host: { - type: "java.lang.String", - tooltip: "Target host to connect to", - required: true - }, - port: { - type: "java.lang.Integer", - tooltip: "The HTTP port used to connect to the server", - "input-attributes": { - "min": "0" - }, - required: true - }, - path: { - type: "java.lang.String", - tooltip: "The URL path used to connect to Jolokia on the remote server" - }, - userName: { - type: "java.lang.String", - tooltip: "The user name to be used when connecting to Jolokia" - }, - password: { - type: "password", - tooltip: "The password to be used when connecting to Jolokia" - }, - useProxy: { - type: "java.lang.Boolean", - tooltip: "Whether or not we should use a proxy. See more information in the panel to the left.", - "control-attributes": { - "ng-hide": "disableProxy" - } - } + $scope.select = function (node) { + workspace.updateSelectionNode(node); + }; + }]); + Jmx._module.factory('rbacACLMBean', function () { + return { + then: function () { } }; - $scope.newConnection = function () { - $scope.lastConnection = ''; + }); + Jmx._module.constant('layoutTree', 'plugins/jmx/html/layoutTree.html'); + // holds the status returned from the last jolokia call (?) + Jmx._module.factory('jolokiaStatus', function () { + return { + xhr: null }; - $scope.deleteConnection = function () { - delete $scope.connectionConfigs[$scope.lastConnection]; - Core.saveConnectionMap($scope.connectionConfigs); - var keys = _.keys($scope.connectionConfigs); - if (keys.length === 0) { - $scope.lastConnection = ''; - } - else { - $scope.lastConnection = keys[0]; - } + }); + Jmx.DEFAULT_MAX_DEPTH = 7; + Jmx.DEFAULT_MAX_COLLECTION_SIZE = 500; + Jmx._module.factory('jolokiaParams', ["jolokiaUrl", "localStorage", function (jolokiaUrl, localStorage) { + var answer = { + canonicalNaming: false, + ignoreErrors: true, + mimeType: 'application/json', + maxDepth: Jmx.DEFAULT_MAX_DEPTH, + maxCollectionSize: Jmx.DEFAULT_MAX_COLLECTION_SIZE }; - $scope.$watch('lastConnection', function (newValue, oldValue) { - JVM.log.debug("lastConnection: ", newValue); - if (newValue !== oldValue) { - if (Core.isBlank(newValue)) { - $scope.currentConfig = newConfig(); + if ('jolokiaParams' in localStorage) { + answer = angular.fromJson(localStorage['jolokiaParams']); + } + else { + localStorage['jolokiaParams'] = angular.toJson(answer); + } + answer['url'] = jolokiaUrl; + return answer; + }]); + Jmx._module.factory('jmxTreeLazyLoadRegistry', function () { + return Core.lazyLoaders; + }); + Jmx._module.controller('Jmx.EditChartNav', ['$scope', '$location', function ($scope, $location) { + $scope.valid = function () { + return $location.path().startsWith('/jmx/chart'); + }; + }]); + Jmx._module.run(["HawtioNav", "$location", "workspace", "viewRegistry", "layoutTree", "jolokia", "helpRegistry", "pageTitle", "$templateCache", function (nav, $location, workspace, viewRegistry, layoutTree, jolokia, helpRegistry, pageTitle, $templateCache) { + Jmx.log.debug('loaded'); + viewRegistry['{ "main-tab": "jmx" }'] = layoutTree; + helpRegistry.addUserDoc('jmx', 'app/jmx/doc/help.md'); + pageTitle.addTitleElement(function () { + if (Jmx.currentProcessId === '') { + try { + Jmx.currentProcessId = jolokia.getAttribute('java.lang:type=Runtime', 'Name'); } - else { - $scope.currentConfig = $scope.connectionConfigs[newValue]; + catch (e) { + } + if (Jmx.currentProcessId && Jmx.currentProcessId.has("@")) { + Jmx.currentProcessId = "pid:" + Jmx.currentProcessId.split("@")[0]; } - localStorage[JVM.connectControllerKey] = angular.toJson(newValue); } - }, true); - $scope.save = function () { - $scope.gotoServer($scope.currentConfig, null, true); - }; - $scope.gotoServer = function (connectOptions, form, saveOnly) { - if (!connectOptions) { - connectOptions = Core.getConnectOptions($scope.lastConnection); + return Jmx.currentProcessId; + }); + var myUrl = '/jmx/attributes'; + var builder = nav.builder(); + var tab = builder.id('jmx').title(function () { return 'JMX'; }).defaultPage({ + rank: 10, + isValid: function (yes, no) { + var name = 'JmxDefaultPage'; + workspace.addNamedTreePostProcessor(name, function (tree) { + workspace.removeNamedTreePostProcessor(name); + if (workspace.hasMBeans()) { + yes(); + } + else { + no(); + } + }); } - var name = connectOptions.name; - $scope.connectionConfigs[name] = connectOptions; - $scope.lastConnection = name; - if (saveOnly === true) { - Core.saveConnectionMap($scope.connectionConfigs); - $scope.connectionConfigs = Core.loadConnectionMap(); - angular.extend($scope.currentConfig, $scope.connectionConfigs[$scope.lastConnection]); - Core.$apply($scope); - return; + }).isValid(function () { return workspace.hasMBeans(); }).href(function () { return myUrl; }).build(); + tab.tabs = Jmx.getNavItems(builder, workspace, $templateCache); + nav.add(tab); + }]); + hawtioPluginLoader.addModule(Jmx.pluginName); + hawtioPluginLoader.addModule('dangle'); +})(Jmx || (Jmx = {})); + +/** + * @module Jmx + */ +/// +var Jmx; +(function (Jmx) { + Jmx.AreaChartController = Jmx._module.controller("Jmx.AreaChartController", ["$scope", "$routeParams", "jolokia", "$templateCache", "localStorage", function ($scope, $routeParams, jolokia, $templateCache, localStorage) { + $scope.mbean = $routeParams['mbean']; + $scope.attribute = $routeParams['attribute']; + $scope.duration = localStorage['updateRate']; + $scope.width = 308; + $scope.height = 296; + $scope.template = ""; + $scope.entries = []; + $scope.data = { + entries: $scope.entries + }; + $scope.req = [{ type: 'read', mbean: $scope.mbean, attribute: $scope.attribute }]; + $scope.render = function (response) { + $scope.entries.push({ + time: response.timestamp, + count: response.value + }); + $scope.entries = $scope.entries.last(15); + if ($scope.template === "") { + $scope.template = $templateCache.get("areaChart"); } - Core.connectToServer(localStorage, connectOptions); - $scope.connectionConfigs = Core.loadConnectionMap(); - angular.extend($scope.currentConfig, $scope.connectionConfigs[$scope.lastConnection]); + $scope.data = { + _type: "date_histogram", + entries: $scope.entries + }; Core.$apply($scope); }; + Core.register(jolokia, $scope, $scope.req, Core.onSuccess($scope.render)); }]); -})(JVM || (JVM = {})); +})(Jmx || (Jmx = {})); -/// -/// /** - * @module JVM + * @module Jmx */ -var JVM; -(function (JVM) { - JVM._module.controller("JVM.DiscoveryController", ["$scope", "localStorage", "jolokia", function ($scope, localStorage, jolokia) { - $scope.discovering = true; - $scope.agents = undefined; - $scope.$watch('agents', function (newValue, oldValue) { - if (newValue !== oldValue) { - $scope.selectedAgent = $scope.agents.find(function (a) { return a['selected']; }); +/// +var Jmx; +(function (Jmx) { + Jmx._module.controller("Jmx.AttributeController", ["$scope", "jolokia", function ($scope, jolokia) { + $scope.init = function (mbean, attribute) { + $scope.mbean = mbean; + $scope.attribute = attribute; + if (angular.isDefined($scope.mbean) && angular.isDefined($scope.attribute)) { + Core.register(jolokia, $scope, { + type: 'read', + mbean: $scope.mbean, + attribute: $scope.attribute + }, Core.onSuccess(render)); } - }, true); - $scope.closePopover = function ($event) { - $($event.currentTarget).parents('.popover').prev().popover('hide'); }; - function doConnect(agent) { - if (!agent.url) { - Core.notification('warning', 'No URL available to connect to agent'); - return; + function render(response) { + if (_.isEqual($scope.data, response.value)) { + $scope.data = Core.safeNull(response.value); + Core.$apply($scope); } - var options = Core.createConnectOptions(); - options.name = agent.agent_description; - var urlObject = Core.parseUrl(agent.url); - angular.extend(options, urlObject); - options.userName = agent.username; - options.password = agent.password; - Core.connectToServer(localStorage, options); } - ; - $scope.connectWithCredentials = function ($event, agent) { - $scope.closePopover($event); - doConnect(agent); - }; - $scope.gotoServer = function ($event, agent) { - if (agent.secured) { - $($event.currentTarget).popover('show'); - } - else { - doConnect(agent); + }]); + Jmx._module.controller("Jmx.AttributeChartController", ["$scope", "jolokia", "$document", function ($scope, jolokia, $document) { + $scope.init = function (mbean, attribute) { + $scope.mbean = mbean; + $scope.attribute = attribute; + if (angular.isDefined($scope.mbean) && angular.isDefined($scope.attribute)) { + Core.register(jolokia, $scope, { + type: 'read', + mbean: $scope.mbean, + attribute: $scope.attribute + }, Core.onSuccess(render)); } }; - $scope.getElementId = function (agent) { - return agent.agent_id.dasherize().replace(/\./g, "-"); - }; - $scope.getLogo = function (agent) { - if (agent.server_product) { - return JVM.logoRegistry[agent.server_product]; - } - return JVM.logoRegistry['generic']; - }; - $scope.filterMatches = function (agent) { - if (Core.isBlank($scope.filter)) { - return true; - } - else { - return angular.toJson(agent).toLowerCase().has($scope.filter.toLowerCase()); - } - }; - $scope.getAgentIdClass = function (agent) { - if ($scope.hasName(agent)) { - return ""; - } - return "strong"; - }; - $scope.hasName = function (agent) { - if (agent.server_vendor && agent.server_product && agent.server_version) { - return true; - } - return false; - }; - $scope.render = function (response) { - $scope.discovering = false; - if (response) { - var responseJson = angular.toJson(response, true); - if ($scope.responseJson !== responseJson) { - $scope.responseJson = responseJson; - $scope.agents = response; + function render(response) { + if (!angular.isDefined($scope.chart)) { + $scope.chart = $($document.find("#" + $scope.attribute)[0]); + if ($scope.chart) { + $scope.width = $scope.chart.width(); } } - Core.$apply($scope); - }; - $scope.fetch = function () { - $scope.discovering = true; - // use 10 sec timeout - jolokia.execute('jolokia:type=Discovery', 'lookupAgentsWithTimeout(int)', 10 * 1000, Core.onSuccess($scope.render)); - }; - $scope.fetch(); - }]); -})(JVM || (JVM = {})); - -/// -var JVM; -(function (JVM) { - JVM.HeaderController = JVM._module.controller("JVM.HeaderController", ["$scope", "ConnectOptions", function ($scope, ConnectOptions) { - if (ConnectOptions) { - $scope.containerName = ConnectOptions.name || ""; - if (ConnectOptions.returnTo) { - $scope.goBack = function () { - window.location.href = ConnectOptions.returnTo; - }; + if (!angular.isDefined($scope.context)) { + console.log("Got: ", response); + $scope.context = cubism.context().serverDelay(0).clientDelay(0).step(1000).size($scope.width); + $scope.jcontext = $scope.context.jolokia(jolokia); + $scope.metrics = []; + _.forIn(response.value, function (value, key) { + $scope.metrics.push($scope.jcontext.metric({ + type: 'read', + mbean: $scope.mbean, + attribute: $scope.attribute, + path: key + }, $scope.attribute)); + }); + d3.select("#" + $scope.attribute).call(function (div) { + div.append("div").data($scope.metrics).call($scope.context.horizon()); + }); + // let cubism take over at this point... + Core.unregister(jolokia, $scope); + Core.$apply($scope); } } }]); -})(JVM || (JVM = {})); +})(Jmx || (Jmx = {})); -/// -/// -var JVM; -(function (JVM) { - JVM._module.controller("JVM.JolokiaPreferences", ["$scope", "localStorage", "jolokiaParams", "$window", function ($scope, localStorage, jolokiaParams, $window) { - var config = { +/** + * @module Jmx + */ +/// +var Jmx; +(function (Jmx) { + Jmx.propertiesColumnDefs = [ + { + field: 'name', + displayName: 'Property', + width: "27%", + cellTemplate: '' + }, + { + field: 'value', + displayName: 'Value', + width: "70%", + cellTemplate: '
' + } + ]; + Jmx.foldersColumnDefs = [ + { + displayName: 'Name', + cellTemplate: '' + } + ]; + Jmx.AttributesController = Jmx._module.controller("Jmx.AttributesController", ["$scope", "$element", "$location", "workspace", "jolokia", "jolokiaUrl", "jmxWidgets", "jmxWidgetTypes", "$templateCache", "localStorage", "$browser", "HawtioDashboard", function ($scope, $element, $location, workspace, jolokia, jolokiaUrl, jmxWidgets, jmxWidgetTypes, $templateCache, localStorage, $browser, dash) { + $scope.searchText = ''; + $scope.nid = 'empty'; + $scope.selectedItems = []; + $scope.lastKey = null; + $scope.attributesInfoCache = {}; + $scope.entity = {}; + $scope.attributeSchema = {}; + $scope.gridData = []; + $scope.attributes = ""; + $scope.inDashboard = dash.inDashboard; + $scope.$watch('gridData.length', function (newValue, oldValue) { + if (newValue !== oldValue) { + if (newValue > 0) { + $scope.attributes = $templateCache.get('gridTemplate'); + } + else { + $scope.attributes = ""; + } + } + }); + var attributeSchemaBasic = { + style: 0 /* STANDARD */, + mode: 0 /* VIEW */, + hideLegend: true, properties: { - maxDepth: { - type: 'number', - description: 'The number of levels jolokia will marshal an object to json on the server side before returning' + 'key': { + label: 'Key', + tooltip: 'Attribute key', + type: 'static' }, - maxCollectionSize: { - type: 'number', - description: 'The maximum number of elements in an array that jolokia will marshal in a response' + 'attrDesc': { + label: 'Description', + type: 'static' + }, + 'type': { + label: 'Type', + tooltip: 'Attribute type', + type: 'static' + }, + 'jolokia': { + label: 'Jolokia URL', + tooltip: 'Jolokia REST URL', + type: 'string', + 'input-attributes': { + readonly: true + } } } }; - $scope.entity = $scope; - $scope.config = config; - Core.initPreferenceScope($scope, localStorage, { - 'maxDepth': { - 'value': JVM.DEFAULT_MAX_DEPTH, - 'converter': parseInt, - 'formatter': parseInt, - 'post': function (newValue) { - jolokiaParams.maxDepth = newValue; - localStorage['jolokiaParams'] = angular.toJson(jolokiaParams); - } + $scope.gridOptions = { + scope: $scope, + selectedItems: [], + showFilter: false, + canSelectRows: false, + enableRowSelection: false, + enableRowClickSelection: false, + keepLastSelected: false, + multiSelect: true, + showColumnMenu: true, + displaySelectionCheckbox: false, + filterOptions: { + filterText: '' }, - 'maxCollectionSize': { - 'value': JVM.DEFAULT_MAX_COLLECTION_SIZE, - 'converter': parseInt, - 'formatter': parseInt, - 'post': function (newValue) { - jolokiaParams.maxCollectionSize = newValue; - localStorage['jolokiaParams'] = angular.toJson(jolokiaParams); - } + // TODO disabled for now as it causes https://github.com/hawtio/hawtio/issues/262 + //sortInfo: { field: 'name', direction: 'asc'}, + data: 'gridData', + columnDefs: Jmx.propertiesColumnDefs + }; + $scope.$watch(function ($scope) { + return $scope.gridOptions.selectedItems.map(function (item) { return item; }); + }, function (newValue, oldValue) { + if (newValue !== oldValue) { + Jmx.log.debug("Selected items: ", newValue); + $scope.selectedItems = newValue; } + }, true); + var doUpdateTableContents = _.debounce(updateTableContents, 100, { trailing: true }); + $scope.$on("$routeChangeSuccess", function (event, current, previous) { + // lets do this asynchronously to avoid Error: $digest already in progress + $scope.nid = $location.search()['nid']; + setTimeout(function () { + doUpdateTableContents(); + }, 10); }); - $scope.reboot = function () { - $window.location.reload(); - }; - }]); -})(JVM || (JVM = {})); - -/// -/// -/** - * @module JVM - */ -var JVM; -(function (JVM) { - var urlCandidates = ['/hawtio/jolokia', '/jolokia', 'jolokia']; - var discoveredUrl = null; - hawtioPluginLoader.registerPreBootstrapTask(function (next) { - var uri = new URI(); - var query = uri.query(true); - JVM.log.debug("query: ", query); - var jolokiaUrl = query['jolokiaUrl']; - if (jolokiaUrl) { - delete query['sub-tab']; - delete query['main-tab']; - jolokiaUrl = jolokiaUrl.unescapeURL(); - var jolokiaURI = new URI(jolokiaUrl); - var name = query['title'] || 'Unknown Connection'; - var token = query['token'] || Core.trimLeading(uri.hash(), '#'); - var options = Core.createConnectOptions({ - name: name, - scheme: jolokiaURI.protocol(), - host: jolokiaURI.hostname(), - port: Core.parseIntValue(jolokiaURI.port()), - path: Core.trimLeading(jolokiaURI.pathname(), '/'), - useProxy: false - }); - if (!Core.isBlank(token)) { - options['token'] = token; + $scope.$watch('workspace.selection', function () { + if (workspace.moveIfViewInvalid()) { + Core.unregister(jolokia, $scope); + return; } - _.merge(options, jolokiaURI.query(true)); - _.assign(options, query); - JVM.log.debug("options: ", options); - var connectionMap = Core.loadConnectionMap(); - connectionMap[name] = options; - Core.saveConnectionMap(connectionMap); - uri.hash("").query({ - con: name - }); - window.location.replace(uri.toString()); - } - var connectionName = query['con']; - if (connectionName) { - JVM.log.debug("Not discovering jolokia"); - // a connection name is set, no need to discover a jolokia instance - next(); - return; - } - function maybeCheckNext(candidates) { - if (candidates.length === 0) { - next(); + setTimeout(function () { + doUpdateTableContents(); + }, 10); + }); + doUpdateTableContents(); + $scope.hasWidget = function (row) { + return true; + }; + $scope.onCancelAttribute = function () { + // clear entity + $scope.entity = {}; + }; + $scope.onUpdateAttribute = function () { + var value = $scope.entity["value"]; + var key = $scope.entity["key"]; + // clear entity + $scope.entity = {}; + // TODO: check if value changed + // update the attribute on the mbean + var mbean = workspace.getSelectedMBeanName(); + if (mbean) { + jolokia.setAttribute(mbean, key, value, Core.onSuccess(function (response) { + Core.notification("success", "Updated attribute " + key); + })); + } + }; + $scope.onViewAttribute = function (row) { + if (!row.summary) { + return; + } + var entity = $scope.entity = _.cloneDeep(row); + var schema = $scope.attributeSchema = _.cloneDeep(attributeSchemaBasic); + if (entity.key === "ObjectName") { + // ObjectName is calculated locally + delete schema.properties.jolokia; } else { - checkNext(candidates.pop()); + entity.jolokia = Jmx.getUrlForThing(jolokiaUrl, "read", workspace.getSelectedMBeanName(), entity.key); } - } - function checkNext(url) { - JVM.log.debug("trying URL: ", url); - $.ajax(url).always(function (data, statusText, jqXHR) { - if (jqXHR.status === 200) { - try { - var resp = angular.fromJson(data); - //log.debug("Got response: ", resp); - if ('value' in resp && 'agent' in resp.value) { - discoveredUrl = url; - JVM.log.debug("Found jolokia agent at: ", url, " version: ", resp.value.agent); - next(); - } - else { - maybeCheckNext(urlCandidates); - } - } - catch (e) { - maybeCheckNext(urlCandidates); + schema.properties.value = { + formTemplate: '
' + }; + $scope.showAttributeDialog = true; + }; + $scope.getDashboardWidgets = function (row) { + var mbean = workspace.getSelectedMBeanName(); + if (!mbean) { + return ''; + } + var potentialCandidates = jmxWidgets.filter(function (widget) { + return mbean === widget.mbean; + }); + if (potentialCandidates.isEmpty()) { + return ''; + } + potentialCandidates = potentialCandidates.filter(function (widget) { + return widget.attribute === row.key || widget.total === row.key; + }); + if (potentialCandidates.isEmpty()) { + return ''; + } + row.addChartToDashboard = function (type) { + $scope.addChartToDashboard(row, type); + }; + var rc = []; + potentialCandidates.forEach(function (widget) { + var widgetType = Jmx.getWidgetType(widget); + rc.push(""); + }); + return rc.join() + " "; + }; + $scope.addChartToDashboard = function (row, widgetType) { + var mbean = workspace.getSelectedMBeanName(); + var candidates = jmxWidgets.filter(function (widget) { + return mbean === widget.mbean; + }); + candidates = candidates.filter(function (widget) { + return widget.attribute === row.key || widget.total === row.key; + }); + candidates = candidates.filter(function (widget) { + return widget.type === widgetType; + }); + // hmmm, we really should only have one result... + var widget = candidates.first(); + var type = Jmx.getWidgetType(widget); + //console.log("widgetType: ", type, " widget: ", widget); + $location.url(Jmx.createDashboardLink(type, widget)); + }; + /* + * Returns the toolBar template HTML to use for the current selection + */ + $scope.toolBarTemplate = function () { + // lets lookup the list of helpers by domain + var answer = Jmx.getAttributeToolBar(workspace.selection); + // TODO - maybe there's a better way to determine when to enable selections + /* + if (answer.startsWith("app/camel") && workspace.selection.children.length > 0) { + $scope.selectToggle.setSelect(true); + } else { + $scope.selectToggle.setSelect(false); + } + */ + return answer; + }; + $scope.invokeSelectedMBeans = function (operationName, completeFunction) { + if (completeFunction === void 0) { completeFunction = null; } + var queries = []; + angular.forEach($scope.selectedItems || [], function (item) { + var mbean = item["_id"]; + if (mbean) { + var opName = operationName; + if (angular.isFunction(operationName)) { + opName = operationName(item); } - } - else if (jqXHR.status === 401 || jqXHR.status === 403) { - // I guess this could be it... - discoveredUrl = url; - JVM.log.debug("Using URL: ", url, " assuming it could be an agent but got return code: ", jqXHR.status); - next(); - } - else { - maybeCheckNext(urlCandidates); + //console.log("Invoking operation " + opName + " on " + mbean); + queries.push({ type: "exec", operation: opName, mbean: mbean }); } }); - } - checkNext(urlCandidates.pop()); - }); - JVM._module.service('ConnectionName', ['$location', function ($location) { - var answer = null; - return function (reset) { - if (reset === void 0) { reset = false; } - if (!Core.isBlank(answer) && !reset) { - return answer; + if (queries.length) { + var callback = function () { + if (completeFunction) { + completeFunction(); + } + else { + operationComplete(); + } + }; + jolokia.request(queries, Core.onSuccess(callback, { error: callback })); } - answer = ''; - var search = $location.search(); - if ('con' in window) { - answer = window['con']; - JVM.log.debug("Using connection name from window: ", answer); + }; + $scope.folderHref = function (row) { + if (!row.getProperty) { + return ""; } - else if ('con' in search) { - answer = search['con']; - JVM.log.debug("Using connection name from URL: ", answer); + var key = row.getProperty("key"); + if (key) { + return Core.createHref($location, "#" + $location.path() + "?nid=" + key, ["nid"]); } else { - JVM.log.debug("No connection name found, using direct connection to JVM"); + return ""; } - return answer; }; - }]); - JVM._module.service('ConnectOptions', ['ConnectionName', function (ConnectionName) { - var name = ConnectionName(); - if (Core.isBlank(name)) { - // this will fail any if (ConnectOptions) check - return false; - } - var answer = Core.getConnectOptions(name); - try { - if (window.opener && "passUserDetails" in window.opener) { - answer.userName = window.opener["passUserDetails"].username; - answer.password = window.opener["passUserDetails"].password; + $scope.folderIconClass = function (row) { + // TODO lets ignore the classes property for now + // as we don't have an easy way to know if there is an icon defined for an icon or not + // and we want to make sure there always is an icon shown + /* + var classes = (row.getProperty("addClass") || "").trim(); + if (classes) { + return classes; + } + */ + if (!row.getProperty) { + return ""; } + return row.getProperty("objectName") ? "fa fa-cog" : "fa fa-folder-close"; + }; + function operationComplete() { + updateTableContents(); } - catch (securityException) { - } - return answer; - }]); - // the jolokia URL we're connected to - JVM._module.factory('jolokiaUrl', ['ConnectOptions', 'documentBase', function (ConnectOptions, documentBase) { - var answer = undefined; - if (!ConnectOptions || !ConnectOptions.name) { - JVM.log.debug("Using discovered URL"); - answer = discoveredUrl; - } - else { - answer = Core.createServerConnectionUrl(ConnectOptions); - JVM.log.debug("Using configured URL"); - } - if (!answer) { - // this will force a dummy jolokia instance - return false; - } - // build full URL - var windowURI = new URI(); - var jolokiaURI = undefined; - if (_.startsWith(answer, '/') || _.startsWith(answer, 'http')) { - jolokiaURI = new URI(answer); - } - else { - jolokiaURI = new URI(UrlHelpers.join(documentBase, answer)); - } - if (!jolokiaURI.protocol()) { - jolokiaURI.protocol(windowURI.protocol()); - } - if (!jolokiaURI.hostname()) { - jolokiaURI.host(windowURI.hostname()); - } - if (!jolokiaURI.port()) { - jolokiaURI.port(windowURI.port()); - } - answer = jolokiaURI.toString(); - JVM.log.debug("Complete jolokia URL: ", answer); - return answer; - }]); - // holds the status returned from the last jolokia call (?) - JVM._module.factory('jolokiaStatus', function () { - return { - xhr: null - }; - }); - JVM.DEFAULT_MAX_DEPTH = 7; - JVM.DEFAULT_MAX_COLLECTION_SIZE = 500; - JVM._module.factory('jolokiaParams', ["jolokiaUrl", "localStorage", function (jolokiaUrl, localStorage) { - var answer = { - canonicalNaming: false, - ignoreErrors: true, - mimeType: 'application/json', - maxDepth: JVM.DEFAULT_MAX_DEPTH, - maxCollectionSize: JVM.DEFAULT_MAX_COLLECTION_SIZE - }; - if ('jolokiaParams' in localStorage) { - answer = angular.fromJson(localStorage['jolokiaParams']); - } - else { - localStorage['jolokiaParams'] = angular.toJson(answer); - } - answer['url'] = jolokiaUrl; - return answer; - }]); - JVM._module.factory('jolokia', ["$location", "localStorage", "jolokiaStatus", "$rootScope", "userDetails", "jolokiaParams", "jolokiaUrl", "ConnectOptions", "HawtioDashboard", "$modal", function ($location, localStorage, jolokiaStatus, $rootScope, userDetails, jolokiaParams, jolokiaUrl, connectionOptions, dash, $modal) { - if (dash.inDashboard && JVM.windowJolokia) { - return JVM.windowJolokia; - } - if (jolokiaUrl) { - // pass basic auth credentials down to jolokia if set - var username = null; - var password = null; - if (connectionOptions.userName && connectionOptions.password) { - username = connectionOptions.userName; - password = connectionOptions.password; - } - else if (angular.isDefined(userDetails) && angular.isDefined(userDetails.username) && angular.isDefined(userDetails.password)) { - username = userDetails.username; - password = userDetails.password; - } - else { - // lets see if they are passed in via request parameter... - var search = $location.search(); - username = search["_user"]; - password = search["_pwd"]; - if (angular.isArray(username)) - username = username[0]; - if (angular.isArray(password)) - password = password[0]; - } - // Also set an X-Authorization header as well - var headers = ['Authorization', 'X-Authorization']; - if (username && password && !connectionOptions.token) { - userDetails.username = username; - userDetails.password = password; - JVM.log.debug("Setting authorization header to username/password"); - $.ajaxSetup({ - beforeSend: function (xhr) { - headers.forEach(function (header) { - xhr.setRequestHeader(header, Core.getBasicAuthHeader(username, password)); - }); - } - }); - } - else if (connectionOptions.token) { - JVM.log.debug("Setting authorization header to token"); - $.ajaxSetup({ - beforeSend: function (xhr) { - headers.forEach(function (header) { - xhr.setRequestHeader(header, 'Bearer ' + connectionOptions.token); - }); + function updateTableContents() { + // lets clear any previous queries just in case! + Core.unregister(jolokia, $scope); + $scope.gridData = []; + $scope.mbeanIndex = null; + var mbean = workspace.getSelectedMBeanName(); + var request = null; + var node = workspace.getSelectedMBean(); + if (node === null || angular.isUndefined(node) || node.key !== $scope.lastKey) { + // cache attributes info, so we know if the attribute is read-only or read-write, and also the attribute description + $scope.attributesInfoCache = null; + if (mbean == null) { + // in case of refresh + var _key = $location.search()['nid']; + var _node = workspace.keyToNodeMap[_key]; + if (_node) { + mbean = _node.objectName; } - }); + } + if (mbean) { + var asQuery = function (node) { + var path = Core.escapeMBeanPath(node); + var query = { + type: "LIST", + method: "post", + path: path, + ignoreErrors: true + }; + return query; + }; + var infoQuery = asQuery(mbean); + jolokia.request(infoQuery, Core.onSuccess(function (response) { + $scope.attributesInfoCache = response.value; + Jmx.log.debug("Updated attributes info cache for mbean " + mbean); + })); + } } - else { - JVM.log.debug("Not setting any authorization header"); + if (mbean) { + request = { type: 'read', mbean: mbean }; + if (node === null || angular.isUndefined(node) || node.key !== $scope.lastKey) { + $scope.gridOptions.columnDefs = Jmx.propertiesColumnDefs; + $scope.gridOptions.enableRowClickSelection = false; + } } - var modal = null; - jolokiaParams['ajaxError'] = function (xhr, textStatus, error) { - if (xhr.status === 401 || xhr.status === 403) { - userDetails.username = null; - userDetails.password = null; - delete userDetails.loginDetails; - delete window.opener["passUserDetails"]; + else if (node) { + if (node.key !== $scope.lastKey) { + $scope.gridOptions.columnDefs = []; + $scope.gridOptions.enableRowClickSelection = true; } - else { - jolokiaStatus.xhr = xhr; - if (!xhr.responseText && error) { - xhr.responseText = error.stack; + // lets query each child's details + var children = node.children; + if (children) { + var childNodes = children.map(function (child) { return child.objectName; }); + var mbeans = childNodes.filter(function (mbean) { return mbean !== undefined; }); + if (mbeans) { + var typeNames = Jmx.getUniqueTypeNames(children); + if (typeNames.length <= 1) { + var query = mbeans.map(function (mbean) { + return { type: "READ", mbean: mbean, ignoreErrors: true }; + }); + if (query.length > 0) { + request = query; + // deal with multiple results + $scope.mbeanIndex = {}; + $scope.mbeanRowCounter = 0; + $scope.mbeanCount = mbeans.length; + } + } + else { + console.log("Too many type names " + typeNames); + } } } - if (!modal) { - modal = $modal.open({ - templateUrl: UrlHelpers.join(JVM.templatePath, 'jolokiaError.html'), - controller: ['$scope', '$modalInstance', 'ConnectOptions', 'jolokia', function ($scope, instance, ConnectOptions, jolokia) { - jolokia.stop(); - $scope.responseText = xhr.responseText; - $scope.ConnectOptions = ConnectOptions; - $scope.retry = function () { - modal = null; - instance.close(); - jolokia.start(); - }; - $scope.goBack = function () { - if (ConnectOptions.returnTo) { - window.location.href = ConnectOptions.returnTo; - } - }; - }] - }); - Core.$apply($rootScope); - } - }; - var jolokia = new Jolokia(jolokiaParams); - jolokia.stop(); - // TODO this should really go away, need to track down any remaining spots where this is used - //localStorage['url'] = jolokiaUrl; - if ('updateRate' in localStorage) { - if (localStorage['updateRate'] > 0) { - jolokia.start(localStorage['updateRate']); + } + //var callback = Core.onSuccess(render, { error: render }); + var callback = Core.onSuccess(render); + if (request) { + $scope.request = request; + Core.register(jolokia, $scope, request, callback); + } + else if (node) { + if (node.key !== $scope.lastKey) { + $scope.gridOptions.columnDefs = Jmx.foldersColumnDefs; + $scope.gridOptions.enableRowClickSelection = true; } + $scope.gridData = node.children; + addHandlerFunctions($scope.gridData); + Core.$apply($scope); + } + if (node) { + $scope.lastKey = node.key; } - JVM.windowJolokia = jolokia; - return jolokia; } - else { - var answer = { - isDummy: true, - running: false, - request: function (req, opts) { return null; }, - register: function (req, opts) { return null; }, - list: function (path, opts) { return null; }, - search: function (mBeanPatter, opts) { return null; }, - getAttribute: function (mbean, attribute, path, opts) { return null; }, - setAttribute: function (mbean, attribute, value, path, opts) { - }, - version: function (opts) { return null; }, - execute: function (mbean, operation) { - var args = []; - for (var _i = 2; _i < arguments.length; _i++) { - args[_i - 2] = arguments[_i]; + function render(response) { + var data = response.value; + var mbeanIndex = $scope.mbeanIndex; + var mbean = response.request['mbean']; + if (mbean) { + // lets store the mbean in the row for later + data["_id"] = mbean; + } + if (mbeanIndex) { + if (mbean) { + var idx = mbeanIndex[mbean]; + if (!angular.isDefined(idx)) { + idx = $scope.mbeanRowCounter; + mbeanIndex[mbean] = idx; + $scope.mbeanRowCounter += 1; } - return null; - }, - start: function (period) { - answer.running = true; - }, - stop: function () { - answer.running = false; - }, - isRunning: function () { return answer.running; }, - jobs: function () { return []; } - }; - JVM.windowJolokia = answer; - // empty jolokia that returns nothing - return answer; - } - }]); -})(JVM || (JVM = {})); - -/// -/** - * @module JVM - */ -var JVM; -(function (JVM) { - JVM._module.controller("JVM.JVMsController", ["$scope", "$window", "$location", "localStorage", "workspace", "jolokia", "mbeanName", function ($scope, $window, $location, localStorage, workspace, jolokia, mbeanName) { - JVM.configureScope($scope, $location, workspace); - $scope.data = []; - $scope.deploying = false; - $scope.status = ''; - $scope.initDone = false; - $scope.filter = ''; - $scope.filterMatches = function (jvm) { - if (Core.isBlank($scope.filter)) { - return true; + if (idx === 0) { + // this is to force the table to repaint + $scope.selectedIndices = $scope.selectedItems.map(function (item) { return $scope.gridData.indexOf(item); }); + $scope.gridData = []; + if (!$scope.gridOptions.columnDefs.length) { + // lets update the column definitions based on any configured defaults + var key = workspace.selectionConfigKey(); + var defaultDefs = workspace.attributeColumnDefs[key] || []; + var defaultSize = defaultDefs.length; + var map = {}; + angular.forEach(defaultDefs, function (value, key) { + var field = value.field; + if (field) { + map[field] = value; + } + }); + var extraDefs = []; + angular.forEach(data, function (value, key) { + if (includePropertyValue(key, value)) { + if (!map[key]) { + extraDefs.push({ + field: key, + displayName: key === '_id' ? 'Object name' : Core.humanizeValue(key), + visible: defaultSize === 0 + }); + } + } + }); + // the additional columns (which are not pre-configured), should be sorted + // so the column menu has a nice sorted list instead of random ordering + extraDefs = extraDefs.sort(function (def, def2) { + // make sure _id is last + if (def.field.startsWith('_')) { + return 1; + } + else if (def2.field.startsWith('_')) { + return -1; + } + return def.field.localeCompare(def2.field); + }); + extraDefs.forEach(function (e) { + defaultDefs.push(e); + }); + // remove all non visible + defaultDefs = defaultDefs.remove(function (value) { + if (angular.isDefined(value.visible) && value.visible != null) { + return !value.visible; + } + return false; + }); + $scope.gridOptions.columnDefs = defaultDefs; + $scope.gridOptions.enableRowClickSelection = true; + } + } + // assume 1 row of data per mbean + $scope.gridData[idx] = data; + addHandlerFunctions($scope.gridData); + var count = $scope.mbeanCount; + if (!count || idx + 1 >= count) { + // only cause a refresh on the last row + var newSelections = $scope.selectedIndices.map(function (idx) { return $scope.gridData[idx]; }).filter(function (row) { return row; }); + $scope.selectedItems.splice(0, $scope.selectedItems.length); + $scope.selectedItems.push.apply($scope.selectedItems, newSelections); + } + } + else { + console.log("No mbean name in request " + JSON.stringify(response.request)); + } } else { - return jvm.alias.toLowerCase().has($scope.filter.toLowerCase()); - } - }; - $scope.fetch = function () { - jolokia.request({ - type: 'exec', - mbean: mbeanName, - operation: 'listLocalJVMs()', - arguments: [] - }, { - success: render, - error: function (response) { - $scope.data = []; - $scope.initDone = true; - $scope.status = 'Could not discover local JVM processes: ' + response.error; - Core.$apply($scope); + $scope.gridOptions.columnDefs = Jmx.propertiesColumnDefs; + $scope.gridOptions.enableRowClickSelection = false; + var showAllAttributes = true; + if (angular.isObject(data)) { + var properties = Array(); + angular.forEach(data, function (value, key) { + if (showAllAttributes || includePropertyValue(key, value)) { + // always skip keys which start with _ + if (!key.startsWith("_")) { + // lets format the ObjectName nicely dealing with objects with + // nested object names or arrays of object names + if (key === "ObjectName") { + value = unwrapObjectName(value); + } + // lets unwrap any arrays of object names + if (angular.isArray(value)) { + value = value.map(function (v) { + return unwrapObjectName(v); + }); + } + // the value must be string as the sorting/filtering of the table relies on that + var type = lookupAttributeType(key); + var data = { key: key, name: Core.humanizeValue(key), value: Core.safeNullAsString(value, type) }; + generateSummaryAndDetail(key, data); + properties.push(data); + } + } + }); + if (!properties.any(function (p) { + return p['key'] === 'ObjectName'; + })) { + var objectName = { + key: "ObjectName", + name: "Object Name", + value: mbean + }; + generateSummaryAndDetail(objectName.key, objectName); + properties.push(objectName); + } + properties = properties.sortBy("name"); + $scope.selectedItems = [data]; + data = properties; } - }); - }; - $scope.stopAgent = function (pid) { - jolokia.request({ - type: 'exec', - mbean: mbeanName, - operation: 'stopAgent(java.lang.String)', - arguments: [pid] - }, Core.onSuccess(function () { - $scope.fetch(); - })); - }; - $scope.startAgent = function (pid) { - jolokia.request({ - type: 'exec', - mbean: mbeanName, - operation: 'startAgent(java.lang.String)', - arguments: [pid] - }, Core.onSuccess(function () { - $scope.fetch(); - })); - }; - $scope.connectTo = function (url, scheme, host, port, path) { - // we only need the port and path from the url, as we got the rest - var options = {}; - options["scheme"] = scheme; - options["host"] = host; - options["port"] = port; - options["path"] = path; - // add empty username as we dont need login - options["userName"] = ""; - options["password"] = ""; - var con = Core.createConnectToServerOptions(options); - con.name = "local"; - JVM.log.debug("Connecting to local JVM agent: " + url); - Core.connectToServer(localStorage, con); - Core.$apply($scope); - }; - function render(response) { - $scope.initDone = true; - $scope.data = response.value; - if ($scope.data.length === 0) { - $scope.status = 'Could not discover local JVM processes'; + $scope.gridData = data; + addHandlerFunctions($scope.gridData); } Core.$apply($scope); } - $scope.fetch(); - }]); -})(JVM || (JVM = {})); - -/// -/// -/** - * @module JVM - */ -var JVM; -(function (JVM) { - JVM._module.controller("JVM.NavController", ["$scope", "$location", "workspace", function ($scope, $location, workspace) { - JVM.configureScope($scope, $location, workspace); - }]); -})(JVM || (JVM = {})); - -/// -/// -/** - * @module JVM - */ -var JVM; -(function (JVM) { - JVM._module.controller("JVM.ResetController", ["$scope", "localStorage", function ($scope, localStorage) { - $scope.doClearConnectSettings = function () { - var doReset = function () { - delete localStorage[JVM.connectControllerKey]; - delete localStorage[JVM.connectionSettingsKey]; - setTimeout(function () { - window.location.reload(); - }, 10); - }; - doReset(); - }; - }]); -})(JVM || (JVM = {})); - -/** - * @module Jmx - */ -var Jmx; -(function (Jmx) { - function createDashboardLink(widgetType, widget) { - var href = "#" + widgetType.route; - var routeParams = angular.toJson(widget); - var title = widget.title; - var size = angular.toJson({ - size_x: widgetType.size_x, - size_y: widgetType.size_y - }); - return "/dashboard/add?tab=dashboard" + "&href=" + encodeURIComponent(href) + "&size=" + encodeURIComponent(size) + "&title=" + encodeURIComponent(title) + "&routeParams=" + encodeURIComponent(routeParams); - } - Jmx.createDashboardLink = createDashboardLink; - function getWidgetType(widget) { - return Jmx.jmxWidgetTypes.find(function (type) { - return type.type === widget.type; - }); - } - Jmx.getWidgetType = getWidgetType; - Jmx.jmxWidgetTypes = [ - { - type: "donut", - icon: "fa fa-pie-chart", - route: "/jmx/widget/donut", - size_x: 2, - size_y: 2, - title: "Add Donut chart to Dashboard" - }, - { - type: "area", - icon: "fa fa-bar-chart", - route: "/jmx/widget/area", - size_x: 4, - size_y: 2, - title: "Add Area chart to Dashboard" + function addHandlerFunctions(data) { + data.forEach(function (item) { + item['inDashboard'] = $scope.inDashboard; + item['getDashboardWidgets'] = function () { + return $scope.getDashboardWidgets(item); + }; + item['onViewAttribute'] = function () { + $scope.onViewAttribute(item); + }; + item['folderIconClass'] = function (row) { + return $scope.folderIconClass(row); + }; + item['folderHref'] = function (row) { + return $scope.folderHref(row); + }; + }); } - ]; - Jmx.jmxWidgets = [ - { - type: "donut", - title: "Java Heap Memory", - mbean: "java.lang:type=Memory", - attribute: "HeapMemoryUsage", - total: "Max", - terms: "Used", - remaining: "Free" - }, - { - type: "donut", - title: "Java Non Heap Memory", - mbean: "java.lang:type=Memory", - attribute: "NonHeapMemoryUsage", - total: "Max", - terms: "Used", - remaining: "Free" - }, - { - type: "donut", - title: "File Descriptor Usage", - mbean: "java.lang:type=OperatingSystem", - total: "MaxFileDescriptorCount", - terms: "OpenFileDescriptorCount", - remaining: "Free" - }, - { - type: "donut", - title: "Loaded Classes", - mbean: "java.lang:type=ClassLoading", - total: "TotalLoadedClassCount", - terms: "LoadedClassCount,UnloadedClassCount", - remaining: "-" - }, - { - type: "donut", - title: "Swap Size", - mbean: "java.lang:type=OperatingSystem", - total: "TotalSwapSpaceSize", - terms: "FreeSwapSpaceSize", - remaining: "Used Swap" - }, - { - type: "area", - title: "Process CPU Time", - mbean: "java.lang:type=OperatingSystem", - attribute: "ProcessCpuTime" - }, - { - type: "area", - title: "Process CPU Load", - mbean: "java.lang:type=OperatingSystem", - attribute: "ProcessCpuLoad" - }, - { - type: "area", - title: "System CPU Load", - mbean: "java.lang:type=OperatingSystem", - attribute: "SystemCpuLoad" - }, - { - type: "area", - title: "System CPU Time", - mbean: "java.lang:type=OperatingSystem", - attribute: "SystemCpuTime" + function unwrapObjectName(value) { + if (!angular.isObject(value)) { + return value; + } + var keys = Object.keys(value); + if (keys.length === 1 && keys[0] === "objectName") { + return value["objectName"]; + } + return value; } - ]; + function generateSummaryAndDetail(key, data) { + var value = data.value; + if (!angular.isArray(value) && angular.isObject(value)) { + var detailHtml = ""; + var summary = ""; + var object = value; + var keys = Object.keys(value).sort(); + angular.forEach(keys, function (key) { + var value = object[key]; + detailHtml += ""; + summary += "" + Core.humanizeValue(key) + ": " + value + " "; + }); + detailHtml += "
" + Core.humanizeValue(key) + "" + value + "
"; + data.summary = summary; + data.detailHtml = detailHtml; + data.tooltip = summary; + } + else { + var text = value; + // if the text is empty then use a no-break-space so the table allows us to click on the row, + // otherwise if the text is empty, then you cannot click on the row + if (text === '') { + text = ' '; + data.tooltip = ""; + } + else { + data.tooltip = text; + } + data.summary = "" + text + ""; + data.detailHtml = "
" + text + "
"; + if (angular.isArray(value)) { + var html = "
    "; + angular.forEach(value, function (item) { + html += "
  • " + item + "
  • "; + }); + html += "
"; + data.detailHtml = html; + } + } + // enrich the data with information if the attribute is read-only/read-write, and the JMX attribute description (if any) + data.rw = false; + data.attrDesc = data.name; + data.type = "string"; + if ($scope.attributesInfoCache != null && 'attr' in $scope.attributesInfoCache) { + var info = $scope.attributesInfoCache.attr[key]; + if (angular.isDefined(info)) { + data.rw = info.rw; + data.attrDesc = info.desc; + data.type = info.type; + } + } + } + function lookupAttributeType(key) { + if ($scope.attributesInfoCache != null && 'attr' in $scope.attributesInfoCache) { + var info = $scope.attributesInfoCache.attr[key]; + if (angular.isDefined(info)) { + return info.type; + } + } + return null; + } + function includePropertyValue(key, value) { + return !angular.isObject(value); + } + function asJsonSchemaType(typeName, id) { + if (typeName) { + var lower = typeName.toLowerCase(); + if (lower.startsWith("int") || lower === "long" || lower === "short" || lower === "byte" || lower.endsWith("int")) { + return "integer"; + } + if (lower === "double" || lower === "float" || lower === "bigdecimal") { + return "number"; + } + if (lower === "boolean" || lower === "java.lang.boolean") { + return "boolean"; + } + if (lower === "string" || lower === "java.lang.String") { + return "string"; + } + } + // fallback as string + return "string"; + } + }]); })(Jmx || (Jmx = {})); +/// /** * @module Jmx - * @main Jmx */ -/// -/// -/// -/// -/// var Jmx; (function (Jmx) { - Jmx._module = angular.module(Jmx.pluginName, []); - Jmx._module.config(['HawtioNavBuilderProvider', "$routeProvider", function (builder, $routeProvider) { - $routeProvider.when('/jmx', { redirectTo: '/jmx/attributes' }).when('/jmx/attributes', { templateUrl: UrlHelpers.join(Jmx.templatePath, 'attributes.html') }).when('/jmx/operations', { templateUrl: UrlHelpers.join(Jmx.templatePath, 'operations.html') }).when('/jmx/charts', { templateUrl: UrlHelpers.join(Jmx.templatePath, 'charts.html') }).when('/jmx/chartEdit', { templateUrl: UrlHelpers.join(Jmx.templatePath, 'chartEdit.html') }).when('/jmx/help/:tabName', { templateUrl: 'app/core/html/help.html' }).when('/jmx/widget/donut', { templateUrl: UrlHelpers.join(Jmx.templatePath, 'donutChart.html') }).when('/jmx/widget/area', { templateUrl: UrlHelpers.join(Jmx.templatePath, 'areaChart.html') }); - }]); - Jmx._module.factory('jmxWidgetTypes', function () { - return Jmx.jmxWidgetTypes; - }); - Jmx._module.factory('jmxWidgets', function () { - return Jmx.jmxWidgets; - }); - // Create the workspace object used in all kinds of places - Jmx._module.factory('workspace', ["$location", "jmxTreeLazyLoadRegistry", "$compile", "$templateCache", "localStorage", "jolokia", "jolokiaStatus", "$rootScope", "userDetails", "HawtioNav", function ($location, jmxTreeLazyLoadRegistry, $compile, $templateCache, localStorage, jolokia, jolokiaStatus, $rootScope, userDetails, HawtioNav) { - var answer = new Workspace(jolokia, jolokiaStatus, jmxTreeLazyLoadRegistry, $location, $compile, $templateCache, localStorage, $rootScope, userDetails, HawtioNav); - answer.loadTree(); - return answer; - }]); - Jmx._module.controller("Jmx.MBeanTreeController", ['$scope', 'workspace', function ($scope, workspace) { - $scope.node = {}; - workspace.addNamedTreePostProcessor('MBeanTree', function (tree) { - angular.copy(tree, $scope.node); - $scope.node.open = true; - Jmx.log.debug("got tree: ", $scope.node); - }); - $scope.select = function (node) { - workspace.updateSelectionNode(node); - }; - }]); - Jmx._module.factory('rbacACLMBean', function () { - return { - then: function () { + Jmx._module.controller("Jmx.ChartEditController", ["$scope", "$location", "workspace", "jolokia", function ($scope, $location, workspace, jolokia) { + $scope.selectedAttributes = []; + $scope.selectedMBeans = []; + $scope.metrics = {}; + $scope.mbeans = {}; + // TODO move this function to $routeScope + $scope.size = function (value) { + if (angular.isObject(value)) { + return _.keys(value).length; + } + else if (angular.isArray(value)) { + return value.length; } + else + return 1; }; - }); - Jmx._module.constant('layoutTree', 'plugins/jmx/html/layoutTree.html'); - // holds the status returned from the last jolokia call (?) - Jmx._module.factory('jolokiaStatus', function () { - return { - xhr: null + $scope.canViewChart = function () { + return $scope.selectedAttributes.length && $scope.selectedMBeans.length && $scope.size($scope.mbeans) > 0 && $scope.size($scope.metrics) > 0; }; - }); - Jmx.DEFAULT_MAX_DEPTH = 7; - Jmx.DEFAULT_MAX_COLLECTION_SIZE = 500; - Jmx._module.factory('jolokiaParams', ["jolokiaUrl", "localStorage", function (jolokiaUrl, localStorage) { - var answer = { - canonicalNaming: false, - ignoreErrors: true, - mimeType: 'application/json', - maxDepth: Jmx.DEFAULT_MAX_DEPTH, - maxCollectionSize: Jmx.DEFAULT_MAX_COLLECTION_SIZE + $scope.showAttributes = function () { + return $scope.canViewChart() && $scope.size($scope.metrics) > 1; }; - if ('jolokiaParams' in localStorage) { - answer = angular.fromJson(localStorage['jolokiaParams']); - } - else { - localStorage['jolokiaParams'] = angular.toJson(answer); - } - answer['url'] = jolokiaUrl; - return answer; - }]); - Jmx._module.factory('jmxTreeLazyLoadRegistry', function () { - return Core.lazyLoaders; - }); - Jmx._module.controller('Jmx.EditChartNav', ['$scope', '$location', function ($scope, $location) { - $scope.valid = function () { - return $location.path().startsWith('/jmx/chart'); + $scope.showElements = function () { + return $scope.canViewChart() && $scope.size($scope.mbeans) > 1; }; - }]); - Jmx._module.run(["HawtioNav", "$location", "workspace", "viewRegistry", "layoutTree", "jolokia", "helpRegistry", "pageTitle", "$templateCache", function (nav, $location, workspace, viewRegistry, layoutTree, jolokia, helpRegistry, pageTitle, $templateCache) { - Jmx.log.debug('loaded'); - viewRegistry['{ "main-tab": "jmx" }'] = layoutTree; - helpRegistry.addUserDoc('jmx', 'app/jmx/doc/help.md'); - pageTitle.addTitleElement(function () { - if (Jmx.currentProcessId === '') { - try { - Jmx.currentProcessId = jolokia.getAttribute('java.lang:type=Runtime', 'Name'); - } - catch (e) { - } - if (Jmx.currentProcessId && Jmx.currentProcessId.has("@")) { - Jmx.currentProcessId = "pid:" + Jmx.currentProcessId.split("@")[0]; - } + $scope.viewChart = function () { + // lets add the attributes and mbeans into the URL so we can navigate back to the charts view + var search = $location.search(); + // if we have selected all attributes, then lets just remove the attribute + if ($scope.selectedAttributes.length === $scope.size($scope.metrics)) { + delete search["att"]; } - return Jmx.currentProcessId; + else { + search["att"] = $scope.selectedAttributes; + } + // if we are on an mbean with no children lets discard an unnecessary parameter + if ($scope.selectedMBeans.length === $scope.size($scope.mbeans) && $scope.size($scope.mbeans) === 1) { + delete search["el"]; + } + else { + search["el"] = $scope.selectedMBeans; + } + $location.search(search); + $location.path("jmx/charts"); + }; + $scope.$watch('workspace.selection', render); + $scope.$on("$routeChangeSuccess", function (event, current, previous) { + // lets do this asynchronously to avoid Error: $digest already in progress + setTimeout(render, 50); }); - var myUrl = '/jmx/attributes'; - var builder = nav.builder(); - var tab = builder.id('jmx').title(function () { return 'JMX'; }).defaultPage({ - rank: 10, - isValid: function (yes, no) { - var name = 'JmxDefaultPage'; - workspace.addNamedTreePostProcessor(name, function (tree) { - workspace.removeNamedTreePostProcessor(name); - if (workspace.hasMBeans()) { - yes(); - } - else { - no(); + function render() { + var node = workspace.selection; + if (!angular.isDefined(node)) { + return; + } + $scope.selectedAttributes = []; + $scope.selectedMBeans = []; + $scope.metrics = {}; + $scope.mbeans = {}; + var mbeanCounter = 0; + var resultCounter = 0; + // lets iterate through all the children if the current node is not an mbean + var children = node.children; + if (!children || !children.length || node.objectName) { + children = [node]; + } + if (children) { + children.forEach(function (mbeanNode) { + var mbean = mbeanNode.objectName; + var name = mbeanNode.title; + if (name && mbean) { + mbeanCounter++; + $scope.mbeans[name] = name; + // use same logic as the JMX attributes page which works better than jolokia.list which has problems with + // mbeans with special characters such as ? and query parameters such as Camel endpoint mbeans + var asQuery = function (node) { + var path = Core.escapeMBeanPath(node); + var query = { + type: "list", + path: path, + ignoreErrors: true + }; + return query; + }; + var infoQuery = asQuery(mbean); + // must use post, so see further below where we pass in {method: "post"} + jolokia.request(infoQuery, Core.onSuccess(function (meta) { + var attributes = meta.value.attr; + if (attributes) { + for (var key in attributes) { + var value = attributes[key]; + if (value) { + var typeName = value['type']; + if (Core.isNumberTypeName(typeName)) { + if (!$scope.metrics[key]) { + //console.log("Number attribute " + key + " for " + mbean); + $scope.metrics[key] = key; + } + } + } + } + if (++resultCounter >= mbeanCounter) { + // TODO do we need to sort just in case? + // lets look in the search URI to default the selections + var search = $location.search(); + var attributeNames = Core.toSearchArgumentArray(search["att"]); + var elementNames = Core.toSearchArgumentArray(search["el"]); + if (attributeNames && attributeNames.length) { + attributeNames.forEach(function (name) { + if ($scope.metrics[name]) { + $scope.selectedAttributes.push(name); + } + }); + } + if (elementNames && elementNames.length) { + elementNames.forEach(function (name) { + if ($scope.mbeans[name]) { + $scope.selectedMBeans.push(name); + } + }); + } + // default selections if there are none + if ($scope.selectedMBeans.length < 1) { + $scope.selectedMBeans = Object.keys($scope.mbeans); + } + if ($scope.selectedAttributes.length < 1) { + var attrKeys = Object.keys($scope.metrics).sort(); + if ($scope.selectedMBeans.length > 1) { + $scope.selectedAttributes = [attrKeys.first()]; + } + else { + $scope.selectedAttributes = attrKeys; + } + } + // lets update the sizes using jquery as it seems AngularJS doesn't support it + $("#attributes").attr("size", _.keys($scope.metrics).length); + $("#mbeans").attr("size", _.keys($scope.mbeans).length); + Core.$apply($scope); + } + } + // update the website + Core.$apply($scope); + }, { method: "post" })); } }); } - }).isValid(function () { return workspace.hasMBeans(); }).href(function () { return myUrl; }).build(); - tab.tabs = Jmx.getNavItems(builder, workspace, $templateCache); - nav.add(tab); + } }]); - hawtioPluginLoader.addModule(Jmx.pluginName); - hawtioPluginLoader.addModule('dangle'); })(Jmx || (Jmx = {})); /** @@ -2856,2235 +2913,2178 @@ var Jmx; /// var Jmx; (function (Jmx) { - Jmx.AreaChartController = Jmx._module.controller("Jmx.AreaChartController", ["$scope", "$routeParams", "jolokia", "$templateCache", "localStorage", function ($scope, $routeParams, jolokia, $templateCache, localStorage) { - $scope.mbean = $routeParams['mbean']; - $scope.attribute = $routeParams['attribute']; - $scope.duration = localStorage['updateRate']; - $scope.width = 308; - $scope.height = 296; - $scope.template = ""; - $scope.entries = []; - $scope.data = { - entries: $scope.entries - }; - $scope.req = [{ type: 'read', mbean: $scope.mbean, attribute: $scope.attribute }]; - $scope.render = function (response) { - $scope.entries.push({ - time: response.timestamp, - count: response.value - }); - $scope.entries = $scope.entries.last(15); - if ($scope.template === "") { - $scope.template = $templateCache.get("areaChart"); + Jmx._module.controller("Jmx.ChartController", ["$scope", "$element", "$location", "workspace", "localStorage", "jolokiaUrl", "jolokiaParams", function ($scope, $element, $location, workspace, localStorage, jolokiaUrl, jolokiaParams) { + var log = Logger.get("JMX"); + $scope.metrics = []; + $scope.updateRate = 1000; //parseInt(localStorage['updateRate']); + $scope.context = null; + $scope.jolokia = null; + $scope.charts = null; + $scope.reset = function () { + if ($scope.context) { + $scope.context.stop(); + $scope.context = null; } - $scope.data = { - _type: "date_histogram", - entries: $scope.entries - }; - Core.$apply($scope); - }; - Core.register(jolokia, $scope, $scope.req, Core.onSuccess($scope.render)); - }]); -})(Jmx || (Jmx = {})); - -/** - * @module Jmx - */ -/// -var Jmx; -(function (Jmx) { - Jmx._module.controller("Jmx.AttributeController", ["$scope", "jolokia", function ($scope, jolokia) { - $scope.init = function (mbean, attribute) { - $scope.mbean = mbean; - $scope.attribute = attribute; - if (angular.isDefined($scope.mbean) && angular.isDefined($scope.attribute)) { - Core.register(jolokia, $scope, { - type: 'read', - mbean: $scope.mbean, - attribute: $scope.attribute - }, Core.onSuccess(render)); + if ($scope.jolokia) { + $scope.jolokia.stop(); + $scope.jolokia = null; + } + if ($scope.charts) { + $scope.charts.empty(); + $scope.charts = null; } }; - function render(response) { - if (_.isEqual($scope.data, response.value)) { - $scope.data = Core.safeNull(response.value); - Core.$apply($scope); + $scope.$on('$destroy', function () { + try { + $scope.deregRouteChange(); } - } - }]); - Jmx._module.controller("Jmx.AttributeChartController", ["$scope", "jolokia", "$document", function ($scope, jolokia, $document) { - $scope.init = function (mbean, attribute) { - $scope.mbean = mbean; - $scope.attribute = attribute; - if (angular.isDefined($scope.mbean) && angular.isDefined($scope.attribute)) { - Core.register(jolokia, $scope, { - type: 'read', - mbean: $scope.mbean, - attribute: $scope.attribute - }, Core.onSuccess(render)); + catch (error) { } - }; - function render(response) { - if (!angular.isDefined($scope.chart)) { - $scope.chart = $($document.find("#" + $scope.attribute)[0]); - if ($scope.chart) { - $scope.width = $scope.chart.width(); - } - } - if (!angular.isDefined($scope.context)) { - console.log("Got: ", response); - $scope.context = cubism.context().serverDelay(0).clientDelay(0).step(1000).size($scope.width); - $scope.jcontext = $scope.context.jolokia(jolokia); - $scope.metrics = []; - _.forIn(response.value, function (value, key) { - $scope.metrics.push($scope.jcontext.metric({ - type: 'read', - mbean: $scope.mbean, - attribute: $scope.attribute, - path: key - }, $scope.attribute)); - }); - d3.select("#" + $scope.attribute).call(function (div) { - div.append("div").data($scope.metrics).call($scope.context.horizon()); - }); - // let cubism take over at this point... - Core.unregister(jolokia, $scope); - Core.$apply($scope); + try { + $scope.dereg(); } - } - }]); -})(Jmx || (Jmx = {})); - -/** - * @module Jmx - */ -/// -var Jmx; -(function (Jmx) { - Jmx.propertiesColumnDefs = [ - { - field: 'name', - displayName: 'Property', - width: "27%", - cellTemplate: '' - }, - { - field: 'value', - displayName: 'Value', - width: "70%", - cellTemplate: '
' - } - ]; - Jmx.foldersColumnDefs = [ - { - displayName: 'Name', - cellTemplate: '' - } - ]; - Jmx.AttributesController = Jmx._module.controller("Jmx.AttributesController", ["$scope", "$element", "$location", "workspace", "jolokia", "jolokiaUrl", "jmxWidgets", "jmxWidgetTypes", "$templateCache", "localStorage", "$browser", "HawtioDashboard", function ($scope, $element, $location, workspace, jolokia, jolokiaUrl, jmxWidgets, jmxWidgetTypes, $templateCache, localStorage, $browser, dash) { - $scope.searchText = ''; - $scope.nid = 'empty'; - $scope.selectedItems = []; - $scope.lastKey = null; - $scope.attributesInfoCache = {}; - $scope.entity = {}; - $scope.attributeSchema = {}; - $scope.gridData = []; - $scope.attributes = ""; - $scope.inDashboard = dash.inDashboard; - $scope.$watch('gridData.length', function (newValue, oldValue) { - if (newValue !== oldValue) { - if (newValue > 0) { - $scope.attributes = $templateCache.get('gridTemplate'); - } - else { - $scope.attributes = ""; - } + catch (error) { } + $scope.reset(); }); - var attributeSchemaBasic = { - style: 0 /* STANDARD */, - mode: 0 /* VIEW */, - hideLegend: true, - properties: { - 'key': { - label: 'Key', - tooltip: 'Attribute key', - type: 'static' - }, - 'attrDesc': { - label: 'Description', - type: 'static' - }, - 'type': { - label: 'Type', - tooltip: 'Attribute type', - type: 'static' - }, - 'jolokia': { - label: 'Jolokia URL', - tooltip: 'Jolokia REST URL', - type: 'string', - 'input-attributes': { - readonly: true - } - } + $scope.errorMessage = function () { + if ($scope.updateRate === 0) { + return "updateRate"; } - }; - $scope.gridOptions = { - scope: $scope, - selectedItems: [], - showFilter: false, - canSelectRows: false, - enableRowSelection: false, - enableRowClickSelection: false, - keepLastSelected: false, - multiSelect: true, - showColumnMenu: true, - displaySelectionCheckbox: false, - filterOptions: { - filterText: '' - }, - // TODO disabled for now as it causes https://github.com/hawtio/hawtio/issues/262 - //sortInfo: { field: 'name', direction: 'asc'}, - data: 'gridData', - columnDefs: Jmx.propertiesColumnDefs - }; - $scope.$watch(function ($scope) { - return $scope.gridOptions.selectedItems.map(function (item) { return item; }); - }, function (newValue, oldValue) { - if (newValue !== oldValue) { - Jmx.log.debug("Selected items: ", newValue); - $scope.selectedItems = newValue; + if ($scope.metrics.length === 0) { + return "metrics"; } - }, true); - var doUpdateTableContents = _.debounce(updateTableContents, 100, { trailing: true }); - $scope.$on("$routeChangeSuccess", function (event, current, previous) { + }; + var doRender = _.debounce(render, 200, { trailing: true }); + $scope.deregRouteChange = $scope.$on("$routeChangeSuccess", function (event, current, previous) { // lets do this asynchronously to avoid Error: $digest already in progress - $scope.nid = $location.search()['nid']; - setTimeout(function () { - doUpdateTableContents(); - }, 10); + doRender(); }); - $scope.$watch('workspace.selection', function () { - if (workspace.moveIfViewInvalid()) { - Core.unregister(jolokia, $scope); + $scope.dereg = $scope.$watch('workspace.selection', function () { + if (workspace.moveIfViewInvalid()) return; - } - setTimeout(function () { - doUpdateTableContents(); - }, 10); + doRender(); }); - doUpdateTableContents(); - $scope.hasWidget = function (row) { - return true; - }; - $scope.onCancelAttribute = function () { - // clear entity - $scope.entity = {}; - }; - $scope.onUpdateAttribute = function () { - var value = $scope.entity["value"]; - var key = $scope.entity["key"]; - // clear entity - $scope.entity = {}; - // TODO: check if value changed - // update the attribute on the mbean - var mbean = workspace.getSelectedMBeanName(); - if (mbean) { - jolokia.setAttribute(mbean, key, value, Core.onSuccess(function (response) { - Core.notification("success", "Updated attribute " + key); - })); + doRender(); + function render() { + var node = workspace.selection || workspace.getSelectedMBean(); + if (node == null) { + return; } - }; - $scope.onViewAttribute = function (row) { - if (!row.summary) { + if (!angular.isDefined(node) || !angular.isDefined($scope.updateRate) || $scope.updateRate === 0) { + // Called render too early, let's retry + setTimeout(doRender, 500); + Core.$apply($scope); return; } - var entity = $scope.entity = _.cloneDeep(row); - var schema = $scope.attributeSchema = _.cloneDeep(attributeSchemaBasic); - if (entity.key === "ObjectName") { - // ObjectName is calculated locally - delete schema.properties.jolokia; + var width = 594; + var charts = $element.find('#charts'); + if (charts) { + width = charts.width(); } else { - entity.jolokia = Jmx.getUrlForThing(jolokiaUrl, "read", workspace.getSelectedMBeanName(), entity.key); - } - schema.properties.value = { - formTemplate: '
' - }; - $scope.showAttributeDialog = true; - }; - $scope.getDashboardWidgets = function (row) { - var mbean = workspace.getSelectedMBeanName(); - if (!mbean) { - return ''; - } - var potentialCandidates = jmxWidgets.filter(function (widget) { - return mbean === widget.mbean; - }); - if (potentialCandidates.isEmpty()) { - return ''; - } - potentialCandidates = potentialCandidates.filter(function (widget) { - return widget.attribute === row.key || widget.total === row.key; - }); - if (potentialCandidates.isEmpty()) { - return ''; - } - row.addChartToDashboard = function (type) { - $scope.addChartToDashboard(row, type); - }; - var rc = []; - potentialCandidates.forEach(function (widget) { - var widgetType = Jmx.getWidgetType(widget); - rc.push(""); - }); - return rc.join() + " "; - }; - $scope.addChartToDashboard = function (row, widgetType) { - var mbean = workspace.getSelectedMBeanName(); - var candidates = jmxWidgets.filter(function (widget) { - return mbean === widget.mbean; - }); - candidates = candidates.filter(function (widget) { - return widget.attribute === row.key || widget.total === row.key; - }); - candidates = candidates.filter(function (widget) { - return widget.type === widgetType; - }); - // hmmm, we really should only have one result... - var widget = candidates.first(); - var type = Jmx.getWidgetType(widget); - //console.log("widgetType: ", type, " widget: ", widget); - $location.url(Jmx.createDashboardLink(type, widget)); - }; - /* - * Returns the toolBar template HTML to use for the current selection - */ - $scope.toolBarTemplate = function () { - // lets lookup the list of helpers by domain - var answer = Jmx.getAttributeToolBar(workspace.selection); - // TODO - maybe there's a better way to determine when to enable selections - /* - if (answer.startsWith("app/camel") && workspace.selection.children.length > 0) { - $scope.selectToggle.setSelect(true); - } else { - $scope.selectToggle.setSelect(false); - } - */ - return answer; - }; - $scope.invokeSelectedMBeans = function (operationName, completeFunction) { - if (completeFunction === void 0) { completeFunction = null; } - var queries = []; - angular.forEach($scope.selectedItems || [], function (item) { - var mbean = item["_id"]; - if (mbean) { - var opName = operationName; - if (angular.isFunction(operationName)) { - opName = operationName(item); - } - //console.log("Invoking operation " + opName + " on " + mbean); - queries.push({ type: "exec", operation: opName, mbean: mbean }); - } - }); - if (queries.length) { - var callback = function () { - if (completeFunction) { - completeFunction(); - } - else { - operationComplete(); - } - }; - jolokia.request(queries, Core.onSuccess(callback, { error: callback })); - } - }; - $scope.folderHref = function (row) { - if (!row.getProperty) { - return ""; - } - var key = row.getProperty("key"); - if (key) { - return Core.createHref($location, "#" + $location.path() + "?nid=" + key, ["nid"]); - } - else { - return ""; - } - }; - $scope.folderIconClass = function (row) { - // TODO lets ignore the classes property for now - // as we don't have an easy way to know if there is an icon defined for an icon or not - // and we want to make sure there always is an icon shown - /* - var classes = (row.getProperty("addClass") || "").trim(); - if (classes) { - return classes; - } - */ - if (!row.getProperty) { - return ""; - } - return row.getProperty("objectName") ? "fa fa-cog" : "fa fa-folder-close"; - }; - function operationComplete() { - updateTableContents(); - } - function updateTableContents() { - // lets clear any previous queries just in case! - Core.unregister(jolokia, $scope); - $scope.gridData = []; - $scope.mbeanIndex = null; - var mbean = workspace.getSelectedMBeanName(); - var request = null; - var node = workspace.getSelectedMBean(); - if (node === null || angular.isUndefined(node) || node.key !== $scope.lastKey) { - // cache attributes info, so we know if the attribute is read-only or read-write, and also the attribute description - $scope.attributesInfoCache = null; - if (mbean == null) { - // in case of refresh - var _key = $location.search()['nid']; - var _node = workspace.keyToNodeMap[_key]; - if (_node) { - mbean = _node.objectName; - } - } - if (mbean) { - var asQuery = function (node) { - var path = Core.escapeMBeanPath(node); - var query = { - type: "LIST", - method: "post", - path: path, - ignoreErrors: true - }; - return query; - }; - var infoQuery = asQuery(mbean); - jolokia.request(infoQuery, Core.onSuccess(function (response) { - $scope.attributesInfoCache = response.value; - Jmx.log.debug("Updated attributes info cache for mbean " + mbean); - })); - } + // Called render too early, let's retry + setTimeout(doRender, 500); + Core.$apply($scope); + return; } + // clear out any existing context + $scope.reset(); + $scope.charts = charts; + $scope.jolokia = new Jolokia(jolokiaParams); + $scope.jolokia.start($scope.updateRate); + var mbean = node.objectName; + $scope.metrics = []; + var context = cubism.context().serverDelay($scope.updateRate).clientDelay($scope.updateRate).step($scope.updateRate).size(width); + $scope.context = context; + $scope.jolokiaContext = context.jolokia($scope.jolokia); + var search = $location.search(); + var attributeNames = Core.toSearchArgumentArray(search["att"]); if (mbean) { - request = { type: 'read', mbean: mbean }; - if (node === null || angular.isUndefined(node) || node.key !== $scope.lastKey) { - $scope.gridOptions.columnDefs = Jmx.propertiesColumnDefs; - $scope.gridOptions.enableRowClickSelection = false; - } - } - else if (node) { - if (node.key !== $scope.lastKey) { - $scope.gridOptions.columnDefs = []; - $scope.gridOptions.enableRowClickSelection = true; - } - // lets query each child's details - var children = node.children; - if (children) { - var childNodes = children.map(function (child) { return child.objectName; }); - var mbeans = childNodes.filter(function (mbean) { return mbean !== undefined; }); - if (mbeans) { - var typeNames = Jmx.getUniqueTypeNames(children); - if (typeNames.length <= 1) { - var query = mbeans.map(function (mbean) { - return { type: "READ", mbean: mbean, ignoreErrors: true }; - }); - if (query.length > 0) { - request = query; - // deal with multiple results - $scope.mbeanIndex = {}; - $scope.mbeanRowCounter = 0; - $scope.mbeanCount = mbeans.length; + // TODO make generic as we can cache them; they rarely ever change + // lets get the attributes for this mbean + // use same logic as the JMX attributes page which works better than jolokia.list which has problems with + // mbeans with special charachters such as ? and query parameters such as Camel endpoint mbeans + var asQuery = function (node) { + // we need to escape the mbean path for list + var path = Core.escapeMBeanPath(node); + var query = { + type: "list", + path: path, + ignoreErrors: true + }; + return query; + }; + var infoQuery = asQuery(mbean); + var meta = $scope.jolokia.request(infoQuery, { method: "post" }); + if (meta) { + Core.defaultJolokiaErrorHandler(meta, {}); + var attributes = meta.value ? meta.value.attr : null; + if (attributes) { + var foundNames = []; + for (var key in attributes) { + var value = attributes[key]; + if (value) { + var typeName = value['type']; + if (Core.isNumberTypeName(typeName)) { + foundNames.push(key); + } } } - else { - console.log("Too many type names " + typeNames); + // lets filter the attributes + // if we find none then the att search attribute is invalid + // so lets discard the filter - as it must be for some other mbean + if (attributeNames.length) { + var filtered = foundNames.filter(function (key) { return attributeNames.indexOf(key) >= 0; }); + if (filtered.length) { + foundNames = filtered; + } } + // sort the names + foundNames = foundNames.sort(); + angular.forEach(foundNames, function (key) { + var metric = $scope.jolokiaContext.metric({ + type: 'read', + mbean: mbean, + attribute: key + }, Core.humanizeValue(key)); + if (metric) { + $scope.metrics.push(metric); + } + }); } } } - //var callback = Core.onSuccess(render, { error: render }); - var callback = Core.onSuccess(render); - if (request) { - $scope.request = request; - Core.register(jolokia, $scope, request, callback); - } - else if (node) { - if (node.key !== $scope.lastKey) { - $scope.gridOptions.columnDefs = Jmx.foldersColumnDefs; - $scope.gridOptions.enableRowClickSelection = true; + else { + // lets try pull out the attributes and elements from the URI and use those to chart + var elementNames = Core.toSearchArgumentArray(search["el"]); + if (attributeNames && attributeNames.length && elementNames && elementNames.length) { + // first lets map the element names to mbean names to keep the URI small + var mbeans = {}; + elementNames.forEach(function (elementName) { + var child = node.get(elementName); + if (!child && node.children) { + child = node.children.find(function (n) { return elementName === n["title"]; }); + } + if (child) { + var mbean = child.objectName; + if (mbean) { + mbeans[elementName] = mbean; + } + } + }); + // sort the names + attributeNames = attributeNames.sort(); + // lets create the metrics + attributeNames.forEach(function (key) { + angular.forEach(mbeans, function (mbean, name) { + var attributeTitle = Core.humanizeValue(key); + // for now lets always be verbose + var title = name + ": " + attributeTitle; + var metric = $scope.jolokiaContext.metric({ + type: 'read', + mbean: mbean, + attribute: key + }, title); + if (metric) { + $scope.metrics.push(metric); + } + }); + }); + } + // if we've children and none of the query arguments matched any metrics + // lets redirect back to the edit view + if (node.children.length && !$scope.metrics.length) { + // lets forward to the chart selection UI if we have some children; they may have + // chartable attributes + $location.path("jmx/chartEdit"); } - $scope.gridData = node.children; - addHandlerFunctions($scope.gridData); - Core.$apply($scope); - } - if (node) { - $scope.lastKey = node.key; } - } - function render(response) { - var data = response.value; - var mbeanIndex = $scope.mbeanIndex; - var mbean = response.request['mbean']; - if (mbean) { - // lets store the mbean in the row for later - data["_id"] = mbean; - } - if (mbeanIndex) { - if (mbean) { - var idx = mbeanIndex[mbean]; - if (!angular.isDefined(idx)) { - idx = $scope.mbeanRowCounter; - mbeanIndex[mbean] = idx; - $scope.mbeanRowCounter += 1; + if ($scope.metrics.length > 0) { + var d3Selection = d3.select(charts.get(0)); + var axisEl = d3Selection.selectAll(".axis"); + var bail = false; + axisEl.data(["top", "bottom"]).enter().append("div").attr("class", function (d) { + return d + " axis"; + }).each(function (d) { + if (bail) { + return; } - if (idx === 0) { - // this is to force the table to repaint - $scope.selectedIndices = $scope.selectedItems.map(function (item) { return $scope.gridData.indexOf(item); }); - $scope.gridData = []; - if (!$scope.gridOptions.columnDefs.length) { - // lets update the column definitions based on any configured defaults - var key = workspace.selectionConfigKey(); - var defaultDefs = workspace.attributeColumnDefs[key] || []; - var defaultSize = defaultDefs.length; - var map = {}; - angular.forEach(defaultDefs, function (value, key) { - var field = value.field; - if (field) { - map[field] = value; - } - }); - var extraDefs = []; - angular.forEach(data, function (value, key) { - if (includePropertyValue(key, value)) { - if (!map[key]) { - extraDefs.push({ - field: key, - displayName: key === '_id' ? 'Object name' : Core.humanizeValue(key), - visible: defaultSize === 0 - }); - } - } - }); - // the additional columns (which are not pre-configured), should be sorted - // so the column menu has a nice sorted list instead of random ordering - extraDefs = extraDefs.sort(function (def, def2) { - // make sure _id is last - if (def.field.startsWith('_')) { - return 1; - } - else if (def2.field.startsWith('_')) { - return -1; - } - return def.field.localeCompare(def2.field); - }); - extraDefs.forEach(function (e) { - defaultDefs.push(e); - }); - // remove all non visible - defaultDefs = defaultDefs.remove(function (value) { - if (angular.isDefined(value.visible) && value.visible != null) { - return !value.visible; - } - return false; - }); - $scope.gridOptions.columnDefs = defaultDefs; - $scope.gridOptions.enableRowClickSelection = true; - } + try { + d3.select(this).call(context.axis().ticks(12).orient(d)); } - // assume 1 row of data per mbean - $scope.gridData[idx] = data; - addHandlerFunctions($scope.gridData); - var count = $scope.mbeanCount; - if (!count || idx + 1 >= count) { - // only cause a refresh on the last row - var newSelections = $scope.selectedIndices.map(function (idx) { return $scope.gridData[idx]; }).filter(function (row) { return row; }); - $scope.selectedItems.splice(0, $scope.selectedItems.length); - $scope.selectedItems.push.apply($scope.selectedItems, newSelections); + catch (error) { + // still rendering at not the right time... + // log.debug("error: ", error); + if (!bail) { + bail = true; + } } + }); + if (bail) { + $scope.reset(); + setTimeout(doRender, 500); + Core.$apply($scope); + return; } - else { - console.log("No mbean name in request " + JSON.stringify(response.request)); - } + d3Selection.append("div").attr("class", "rule").call(context.rule()); + context.on("focus", function (i) { + try { + d3Selection.selectAll(".value").style("right", i === null ? null : context.size() - i + "px"); + } + catch (error) { + log.info("error: ", error); + } + }); + $scope.metrics.forEach(function (metric) { + d3Selection.call(function (div) { + div.append("div").data([metric]).attr("class", "horizon").call(context.horizon()); + }); + }); } else { - $scope.gridOptions.columnDefs = Jmx.propertiesColumnDefs; - $scope.gridOptions.enableRowClickSelection = false; - var showAllAttributes = true; - if (angular.isObject(data)) { - var properties = Array(); - angular.forEach(data, function (value, key) { - if (showAllAttributes || includePropertyValue(key, value)) { - // always skip keys which start with _ - if (!key.startsWith("_")) { - // lets format the ObjectName nicely dealing with objects with - // nested object names or arrays of object names - if (key === "ObjectName") { - value = unwrapObjectName(value); - } - // lets unwrap any arrays of object names - if (angular.isArray(value)) { - value = value.map(function (v) { - return unwrapObjectName(v); - }); - } - // the value must be string as the sorting/filtering of the table relies on that - var type = lookupAttributeType(key); - var data = { key: key, name: Core.humanizeValue(key), value: Core.safeNullAsString(value, type) }; - generateSummaryAndDetail(key, data); - properties.push(data); - } - } - }); - if (!properties.any(function (p) { - return p['key'] === 'ObjectName'; - })) { - var objectName = { - key: "ObjectName", - name: "Object Name", - value: mbean - }; - generateSummaryAndDetail(objectName.key, objectName); - properties.push(objectName); - } - properties = properties.sortBy("name"); - $scope.selectedItems = [data]; - data = properties; - } - $scope.gridData = data; - addHandlerFunctions($scope.gridData); + $scope.reset(); } Core.$apply($scope); } - function addHandlerFunctions(data) { - data.forEach(function (item) { - item['inDashboard'] = $scope.inDashboard; - item['getDashboardWidgets'] = function () { - return $scope.getDashboardWidgets(item); - }; - item['onViewAttribute'] = function () { - $scope.onViewAttribute(item); - }; - item['folderIconClass'] = function (row) { - return $scope.folderIconClass(row); - }; - item['folderHref'] = function (row) { - return $scope.folderHref(row); - }; + ; + }]); +})(Jmx || (Jmx = {})); + +/// +/** + * @module Jmx + */ +var Jmx; +(function (Jmx) { + Jmx.DonutChartController = Jmx._module.controller("Jmx.DonutChartController", ["$scope", "$routeParams", "jolokia", "$templateCache", function ($scope, $routeParams, jolokia, $templateCache) { + /* + console.log("routeParams: ", $routeParams); + + + // using multiple attributes + $scope.mbean = "java.lang:type=OperatingSystem"; + $scope.total = "MaxFileDescriptorCount"; + $scope.terms = "OpenFileDescriptorCount"; + */ + // using a single attribute with multiple paths + /* + $scope.mbean = "java.lang:type=Memory"; + $scope.total = "Max"; + $scope.attribute = "HeapMemoryUsage"; + $scope.terms = "Used"; + */ + $scope.mbean = $routeParams['mbean']; + $scope.total = $routeParams['total']; + $scope.attribute = $routeParams['attribute']; + $scope.terms = $routeParams['terms']; + $scope.remainder = $routeParams['remaining']; + $scope.template = ""; + $scope.termsArray = $scope.terms.split(","); + $scope.data = { + total: 0, + terms: [] + }; + if (!$scope.attribute) { + $scope.reqs = [{ type: 'read', mbean: $scope.mbean, attribute: $scope.total }]; + $scope.termsArray.forEach(function (term) { + $scope.reqs.push({ type: 'read', mbean: $scope.mbean, attribute: term }); + $scope.data.terms.push({ + term: term, + count: 0 + }); }); } - function unwrapObjectName(value) { - if (!angular.isObject(value)) { - return value; - } - var keys = Object.keys(value); - if (keys.length === 1 && keys[0] === "objectName") { - return value["objectName"]; - } - return value; - } - function generateSummaryAndDetail(key, data) { - var value = data.value; - if (!angular.isArray(value) && angular.isObject(value)) { - var detailHtml = ""; - var summary = ""; - var object = value; - var keys = Object.keys(value).sort(); - angular.forEach(keys, function (key) { - var value = object[key]; - detailHtml += ""; - summary += "" + Core.humanizeValue(key) + ": " + value + " "; + else { + var terms = $scope.termsArray.include($scope.total); + $scope.reqs = [{ type: 'read', mbean: $scope.mbean, attribute: $scope.attribute, paths: terms.join(",") }]; + $scope.termsArray.forEach(function (term) { + $scope.data.terms.push({ + term: term, + count: 0 + }); + }); + } + if ($scope.remainder && $scope.remainder !== "-") { + $scope.data.terms.push({ + term: $scope.remainder, + count: 0 + }); + } + /* + $scope.data = { + total: 100, + terms: [{ + term: "One", + count: 25 + }, { + term: "Two", + count: 75 + }] + }; + */ + $scope.render = function (response) { + //console.log("got: ", response); + var freeTerm = null; + if ($scope.remainder && $scope.remainder !== "-") { + freeTerm = $scope.data.terms.find(function (term) { + return term.term === $scope.remainder; }); - detailHtml += "
" + Core.humanizeValue(key) + "" + value + "
"; - data.summary = summary; - data.detailHtml = detailHtml; - data.tooltip = summary; } - else { - var text = value; - // if the text is empty then use a no-break-space so the table allows us to click on the row, - // otherwise if the text is empty, then you cannot click on the row - if (text === '') { - text = ' '; - data.tooltip = ""; + if (!$scope.attribute) { + if (response.request.attribute === $scope.total) { + $scope.data.total = response.value; } else { - data.tooltip = text; - } - data.summary = "" + text + ""; - data.detailHtml = "
" + text + "
"; - if (angular.isArray(value)) { - var html = "
    "; - angular.forEach(value, function (item) { - html += "
  • " + item + "
  • "; + var term = $scope.data.terms.find(function (term) { + return term.term === response.request.attribute; }); - html += "
"; - data.detailHtml = html; + if (term) { + term.count = response.value; + } + if (freeTerm) { + freeTerm.count = $scope.data.total; + $scope.data.terms.forEach(function (term) { + if (term.term !== $scope.remainder) { + freeTerm.count = freeTerm.count - term.count; + } + }); + } } } - // enrich the data with information if the attribute is read-only/read-write, and the JMX attribute description (if any) - data.rw = false; - data.attrDesc = data.name; - data.type = "string"; - if ($scope.attributesInfoCache != null && 'attr' in $scope.attributesInfoCache) { - var info = $scope.attributesInfoCache.attr[key]; - if (angular.isDefined(info)) { - data.rw = info.rw; - data.attrDesc = info.desc; - data.type = info.type; + else { + if (response.request.attribute === $scope.attribute) { + $scope.data.total = response.value[$scope.total.toLowerCase()]; + $scope.data.terms.forEach(function (term) { + if (term.term !== $scope.remainder) { + term.count = response.value[term.term.toLowerCase()]; + } + }); + if (freeTerm) { + freeTerm.count = $scope.data.total; + $scope.data.terms.forEach(function (term) { + if (term.term !== $scope.remainder) { + freeTerm.count = freeTerm.count - term.count; + } + }); + } } } - } - function lookupAttributeType(key) { - if ($scope.attributesInfoCache != null && 'attr' in $scope.attributesInfoCache) { - var info = $scope.attributesInfoCache.attr[key]; - if (angular.isDefined(info)) { - return info.type; - } + if ($scope.template === "") { + $scope.template = $templateCache.get("donut"); } - return null; + // console.log("Data: ", $scope.data); + $scope.data = Object.clone($scope.data); + Core.$apply($scope); + }; + Core.register(jolokia, $scope, $scope.reqs, Core.onSuccess($scope.render)); + }]); +})(Jmx || (Jmx = {})); + +/// +var Core; +(function (Core) { + function d3ForceGraph(scope, nodes, links, canvasElement) { + // lets remove the old graph first + if (scope.graphForce) { + scope.graphForce.stop(); } - function includePropertyValue(key, value) { - return !angular.isObject(value); + if (!canvasElement) { + canvasElement = $("#canvas")[0]; } - function asJsonSchemaType(typeName, id) { - if (typeName) { - var lower = typeName.toLowerCase(); - if (lower.startsWith("int") || lower === "long" || lower === "short" || lower === "byte" || lower.endsWith("int")) { - return "integer"; - } - if (lower === "double" || lower === "float" || lower === "bigdecimal") { - return "number"; - } - if (lower === "boolean" || lower === "java.lang.boolean") { - return "boolean"; - } - if (lower === "string" || lower === "java.lang.String") { - return "string"; + var canvasDiv = $(canvasElement); + canvasDiv.children("svg").remove(); + if (nodes.length) { + var width = canvasDiv.parent().width(); + var height = canvasDiv.parent().height(); + if (height < 100) { + //console.log("browse thinks the height is only " + height + " so calculating offset from doc height"); + var offset = canvasDiv.offset(); + height = $(document).height() - 5; + if (offset) { + height -= offset['top']; } } - // fallback as string - return "string"; + //console.log("Using width " + width + " and height " + height); + var svg = d3.select(canvasDiv[0]).append("svg").attr("width", width).attr("height", height); + var force = d3.layout.force().distance(100).charge(-120 * 10).linkDistance(50).size([width, height]); + scope.graphForce = force; + /* + var force = d3.layout.force() + .gravity(.05) + .distance(100) + .charge(-100) + .size([width, height]); + */ + // prepare the arrows + svg.append("svg:defs").selectAll("marker").data(["from"]).enter().append("svg:marker").attr("id", String).attr("viewBox", "0 -5 10 10").attr("refX", 25).attr("refY", -1.5).attr("markerWidth", 6).attr("markerHeight", 6).attr("orient", "auto").append("svg:path").attr("d", "M0,-5L10,0L0,5"); + force.nodes(nodes).links(links).start(); + var link = svg.selectAll(".link").data(links).enter().append("line").attr("class", "link"); + // draw the arrow + link.attr("class", "link from"); + // end marker + link.attr("marker-end", "url(#from)"); + var node = svg.selectAll(".node").data(nodes).enter().append("g").attr("class", "node").call(force.drag); + node.append("image").attr("xlink:href", function (d) { + return d.imageUrl; + }).attr("x", -15).attr("y", -15).attr("width", 30).attr("height", 30); + node.append("text").attr("dx", 20).attr("dy", ".35em").text(function (d) { + return d.label; + }); + force.on("tick", function () { + link.attr("x1", function (d) { + return d.source.x; + }).attr("y1", function (d) { + return d.source.y; + }).attr("x2", function (d) { + return d.target.x; + }).attr("y2", function (d) { + return d.target.y; + }); + node.attr("transform", function (d) { + return "translate(" + d.x + "," + d.y + ")"; + }); + }); } - }]); -})(Jmx || (Jmx = {})); - -/// -/** - * @module Jmx - */ -var Jmx; -(function (Jmx) { - Jmx._module.controller("Jmx.ChartEditController", ["$scope", "$location", "workspace", "jolokia", function ($scope, $location, workspace, jolokia) { - $scope.selectedAttributes = []; - $scope.selectedMBeans = []; - $scope.metrics = {}; - $scope.mbeans = {}; - // TODO move this function to $routeScope - $scope.size = function (value) { - if (angular.isObject(value)) { - return _.keys(value).length; - } - else if (angular.isArray(value)) { - return value.length; - } - else - return 1; - }; - $scope.canViewChart = function () { - return $scope.selectedAttributes.length && $scope.selectedMBeans.length && $scope.size($scope.mbeans) > 0 && $scope.size($scope.metrics) > 0; - }; - $scope.showAttributes = function () { - return $scope.canViewChart() && $scope.size($scope.metrics) > 1; - }; - $scope.showElements = function () { - return $scope.canViewChart() && $scope.size($scope.mbeans) > 1; - }; - $scope.viewChart = function () { - // lets add the attributes and mbeans into the URL so we can navigate back to the charts view - var search = $location.search(); - // if we have selected all attributes, then lets just remove the attribute - if ($scope.selectedAttributes.length === $scope.size($scope.metrics)) { - delete search["att"]; + } + Core.d3ForceGraph = d3ForceGraph; + function createGraphStates(nodes, links, transitions) { + var stateKeys = {}; + nodes.forEach(function (node) { + var idx = node.id; + if (idx === undefined) { + console.log("No node found for node " + JSON.stringify(node)); } else { - search["att"] = $scope.selectedAttributes; + if (node.edges === undefined) + node.edges = []; + if (!node.label) + node.label = "node " + idx; + stateKeys[idx] = node; } - // if we are on an mbean with no children lets discard an unnecessary parameter - if ($scope.selectedMBeans.length === $scope.size($scope.mbeans) && $scope.size($scope.mbeans) === 1) { - delete search["el"]; + }); + var states = d3.values(stateKeys); + links.forEach(function (d) { + var source = stateKeys[d.source]; + var target = stateKeys[d.target]; + if (source === undefined || target === undefined) { + console.log("Bad link! " + source + " target " + target + " for " + d); } else { - search["el"] = $scope.selectedMBeans; + var edge = { source: source, target: target }; + transitions.push(edge); + source.edges.push(edge); + target.edges.push(edge); } - $location.search(search); - $location.path("jmx/charts"); - }; - $scope.$watch('workspace.selection', render); - $scope.$on("$routeChangeSuccess", function (event, current, previous) { - // lets do this asynchronously to avoid Error: $digest already in progress - setTimeout(render, 50); }); - function render() { - var node = workspace.selection; - if (!angular.isDefined(node)) { - return; - } - $scope.selectedAttributes = []; - $scope.selectedMBeans = []; - $scope.metrics = {}; - $scope.mbeans = {}; - var mbeanCounter = 0; - var resultCounter = 0; - // lets iterate through all the children if the current node is not an mbean - var children = node.children; - if (!children || !children.length || node.objectName) { - children = [node]; - } - if (children) { - children.forEach(function (mbeanNode) { - var mbean = mbeanNode.objectName; - var name = mbeanNode.title; - if (name && mbean) { - mbeanCounter++; - $scope.mbeans[name] = name; - // use same logic as the JMX attributes page which works better than jolokia.list which has problems with - // mbeans with special characters such as ? and query parameters such as Camel endpoint mbeans - var asQuery = function (node) { - var path = Core.escapeMBeanPath(node); - var query = { - type: "list", - path: path, - ignoreErrors: true - }; - return query; - }; - var infoQuery = asQuery(mbean); - // must use post, so see further below where we pass in {method: "post"} - jolokia.request(infoQuery, Core.onSuccess(function (meta) { - var attributes = meta.value.attr; - if (attributes) { - for (var key in attributes) { - var value = attributes[key]; - if (value) { - var typeName = value['type']; - if (Core.isNumberTypeName(typeName)) { - if (!$scope.metrics[key]) { - //console.log("Number attribute " + key + " for " + mbean); - $scope.metrics[key] = key; - } - } - } - } - if (++resultCounter >= mbeanCounter) { - // TODO do we need to sort just in case? - // lets look in the search URI to default the selections - var search = $location.search(); - var attributeNames = Core.toSearchArgumentArray(search["att"]); - var elementNames = Core.toSearchArgumentArray(search["el"]); - if (attributeNames && attributeNames.length) { - attributeNames.forEach(function (name) { - if ($scope.metrics[name]) { - $scope.selectedAttributes.push(name); - } - }); - } - if (elementNames && elementNames.length) { - elementNames.forEach(function (name) { - if ($scope.mbeans[name]) { - $scope.selectedMBeans.push(name); - } - }); - } - // default selections if there are none - if ($scope.selectedMBeans.length < 1) { - $scope.selectedMBeans = Object.keys($scope.mbeans); - } - if ($scope.selectedAttributes.length < 1) { - var attrKeys = Object.keys($scope.metrics).sort(); - if ($scope.selectedMBeans.length > 1) { - $scope.selectedAttributes = [attrKeys.first()]; - } - else { - $scope.selectedAttributes = attrKeys; - } - } - // lets update the sizes using jquery as it seems AngularJS doesn't support it - $("#attributes").attr("size", _.keys($scope.metrics).length); - $("#mbeans").attr("size", _.keys($scope.mbeans).length); - Core.$apply($scope); - } - } - // update the website - Core.$apply($scope); - }, { method: "post" })); - } - }); - } + return states; + } + Core.createGraphStates = createGraphStates; + // TODO Export as a service + function dagreLayoutGraph(nodes, links, width, height, svgElement, allowDrag, onClick) { + if (allowDrag === void 0) { allowDrag = false; } + if (onClick === void 0) { onClick = null; } + var nodePadding = 10; + var transitions = []; + var states = Core.createGraphStates(nodes, links, transitions); + function spline(e) { + var points = e.dagre.points.slice(0); + var source = dagre.util.intersectRect(e.source.dagre, points.length > 0 ? points[0] : e.source.dagre); + var target = dagre.util.intersectRect(e.target.dagre, points.length > 0 ? points[points.length - 1] : e.source.dagre); + points.unshift(source); + points.push(target); + return d3.svg.line().x(function (d) { + return d.x; + }).y(function (d) { + return d.y; + }).interpolate("linear")(points); } - }]); -})(Jmx || (Jmx = {})); - -/** - * @module Jmx - */ -/// -var Jmx; -(function (Jmx) { - Jmx._module.controller("Jmx.ChartController", ["$scope", "$element", "$location", "workspace", "localStorage", "jolokiaUrl", "jolokiaParams", function ($scope, $element, $location, workspace, localStorage, jolokiaUrl, jolokiaParams) { - var log = Logger.get("JMX"); - $scope.metrics = []; - $scope.updateRate = 1000; //parseInt(localStorage['updateRate']); - $scope.context = null; - $scope.jolokia = null; - $scope.charts = null; - $scope.reset = function () { - if ($scope.context) { - $scope.context.stop(); - $scope.context = null; - } - if ($scope.jolokia) { - $scope.jolokia.stop(); - $scope.jolokia = null; - } - if ($scope.charts) { - $scope.charts.empty(); - $scope.charts = null; - } - }; - $scope.$on('$destroy', function () { - try { - $scope.deregRouteChange(); - } - catch (error) { - } - try { - $scope.dereg(); - } - catch (error) { - } - $scope.reset(); + // Translates all points in the edge using `dx` and `dy`. + function translateEdge(e, dx, dy) { + e.dagre.points.forEach(function (p) { + p.x = Math.max(0, Math.min(svgBBox.width, p.x + dx)); + p.y = Math.max(0, Math.min(svgBBox.height, p.y + dy)); + }); + } + // Now start laying things out + var svg = svgElement ? d3.select(svgElement) : d3.select("svg"); + // lets remove all the old g elements + if (svgElement) { + $(svgElement).children("g").remove(); + } + $(svg).children("g").remove(); + var svgGroup = svg.append("g").attr("transform", "translate(5, 5)"); + // `nodes` is center positioned for easy layout later + var nodes = svgGroup.selectAll("g .node").data(states).enter().append("g").attr("class", "node").attr("data-cid", function (d) { + return d.cid; + }).attr("id", function (d) { + return "node-" + d.label; }); - $scope.errorMessage = function () { - if ($scope.updateRate === 0) { - return "updateRate"; - } - if ($scope.metrics.length === 0) { - return "metrics"; - } - }; - var doRender = _.debounce(render, 200, { trailing: true }); - $scope.deregRouteChange = $scope.$on("$routeChangeSuccess", function (event, current, previous) { - // lets do this asynchronously to avoid Error: $digest already in progress - doRender(); + // lets add a tooltip + nodes.append("title").text(function (d) { + return d.tooltip || ""; }); - $scope.dereg = $scope.$watch('workspace.selection', function () { - if (workspace.moveIfViewInvalid()) - return; - doRender(); + var edges = svgGroup.selectAll("path .edge").data(transitions).enter().append("path").attr("class", "edge").attr("marker-end", "url(#arrowhead)"); + // Append rectangles to the nodes. We do this before laying out the text + // because we want the text above the rectangle. + var rects = nodes.append("rect").attr("rx", "4").attr("ry", "4").attr("class", function (d) { + return d.type; }); - doRender(); - function render() { - var node = workspace.selection || workspace.getSelectedMBean(); - if (node == null) { - return; - } - if (!angular.isDefined(node) || !angular.isDefined($scope.updateRate) || $scope.updateRate === 0) { - // Called render too early, let's retry - setTimeout(doRender, 500); - Core.$apply($scope); - return; - } - var width = 594; - var charts = $element.find('#charts'); - if (charts) { - width = charts.width(); - } - else { - // Called render too early, let's retry - setTimeout(doRender, 500); - Core.$apply($scope); - return; + var images = nodes.append("image").attr("xlink:href", function (d) { + return d.imageUrl; + }).attr("x", -12).attr("y", -20).attr("height", 24).attr("width", 24); + var counters = nodes.append("text").attr("text-anchor", "end").attr("class", "counter").attr("x", 0).attr("dy", 0).text(_counterFunction); + var inflights = nodes.append("text").attr("text-anchor", "middle").attr("class", "inflight").attr("x", 10).attr("dy", -32).text(_inflightFunction); + // Append text + var labels = nodes.append("text").attr("text-anchor", "middle").attr("x", 0); + labels.append("tspan").attr("x", 0).attr("dy", 28).text(function (d) { + return d.label; + }); + var labelPadding = 12; + var minLabelwidth = 80; + labels.each(function (d) { + var bbox = this.getBBox(); + d.bbox = bbox; + if (bbox.width < minLabelwidth) { + bbox.width = minLabelwidth; } - // clear out any existing context - $scope.reset(); - $scope.charts = charts; - $scope.jolokia = new Jolokia(jolokiaParams); - $scope.jolokia.start($scope.updateRate); - var mbean = node.objectName; - $scope.metrics = []; - var context = cubism.context().serverDelay($scope.updateRate).clientDelay($scope.updateRate).step($scope.updateRate).size(width); - $scope.context = context; - $scope.jolokiaContext = context.jolokia($scope.jolokia); - var search = $location.search(); - var attributeNames = Core.toSearchArgumentArray(search["att"]); - if (mbean) { - // TODO make generic as we can cache them; they rarely ever change - // lets get the attributes for this mbean - // use same logic as the JMX attributes page which works better than jolokia.list which has problems with - // mbeans with special charachters such as ? and query parameters such as Camel endpoint mbeans - var asQuery = function (node) { - // we need to escape the mbean path for list - var path = Core.escapeMBeanPath(node); - var query = { - type: "list", - path: path, - ignoreErrors: true - }; - return query; - }; - var infoQuery = asQuery(mbean); - var meta = $scope.jolokia.request(infoQuery, { method: "post" }); - if (meta) { - Core.defaultJolokiaErrorHandler(meta, {}); - var attributes = meta.value ? meta.value.attr : null; - if (attributes) { - var foundNames = []; - for (var key in attributes) { - var value = attributes[key]; - if (value) { - var typeName = value['type']; - if (Core.isNumberTypeName(typeName)) { - foundNames.push(key); - } - } - } - // lets filter the attributes - // if we find none then the att search attribute is invalid - // so lets discard the filter - as it must be for some other mbean - if (attributeNames.length) { - var filtered = foundNames.filter(function (key) { return attributeNames.indexOf(key) >= 0; }); - if (filtered.length) { - foundNames = filtered; - } - } - // sort the names - foundNames = foundNames.sort(); - angular.forEach(foundNames, function (key) { - var metric = $scope.jolokiaContext.metric({ - type: 'read', - mbean: mbean, - attribute: key - }, Core.humanizeValue(key)); - if (metric) { - $scope.metrics.push(metric); - } - }); - } - } - } - else { - // lets try pull out the attributes and elements from the URI and use those to chart - var elementNames = Core.toSearchArgumentArray(search["el"]); - if (attributeNames && attributeNames.length && elementNames && elementNames.length) { - // first lets map the element names to mbean names to keep the URI small - var mbeans = {}; - elementNames.forEach(function (elementName) { - var child = node.get(elementName); - if (!child && node.children) { - child = node.children.find(function (n) { return elementName === n["title"]; }); - } - if (child) { - var mbean = child.objectName; - if (mbean) { - mbeans[elementName] = mbean; - } - } - }); - // sort the names - attributeNames = attributeNames.sort(); - // lets create the metrics - attributeNames.forEach(function (key) { - angular.forEach(mbeans, function (mbean, name) { - var attributeTitle = Core.humanizeValue(key); - // for now lets always be verbose - var title = name + ": " + attributeTitle; - var metric = $scope.jolokiaContext.metric({ - type: 'read', - mbean: mbean, - attribute: key - }, title); - if (metric) { - $scope.metrics.push(metric); - } - }); - }); - } - // if we've children and none of the query arguments matched any metrics - // lets redirect back to the edit view - if (node.children.length && !$scope.metrics.length) { - // lets forward to the chart selection UI if we have some children; they may have - // chartable attributes - $location.path("jmx/chartEdit"); - } + d.width = bbox.width + 2 * nodePadding; + d.height = bbox.height + 2 * nodePadding + labelPadding; + }); + rects.attr("x", function (d) { + return -(d.bbox.width / 2 + nodePadding); + }).attr("y", function (d) { + return -(d.bbox.height / 2 + nodePadding + (labelPadding / 2)); + }).attr("width", function (d) { + return d.width; + }).attr("height", function (d) { + return d.height; + }); + if (onClick != null) { + rects.on("click", onClick); + } + images.attr("x", function (d) { + return -(d.bbox.width) / 2; + }); + labels.attr("x", function (d) { + return -d.bbox.width / 2; + }).attr("y", function (d) { + return -d.bbox.height / 2; + }); + counters.attr("x", function (d) { + var w = d.bbox.width; + return w / 2; + }); + // Create the layout and get the graph + dagre.layout().nodeSep(50).edgeSep(10).rankSep(50).nodes(states).edges(transitions).debugLevel(1).run(); + nodes.attr("transform", function (d) { + return 'translate(' + d.dagre.x + ',' + d.dagre.y + ')'; + }); + edges.attr('id', function (e) { + return e.dagre.id; + }).attr("d", function (e) { + return spline(e); + }); + // Resize the SVG element + var svgNode = svg.node(); + if (svgNode) { + var svgBBox = svgNode.getBBox(); + if (svgBBox) { + svg.attr("width", svgBBox.width + 10); + svg.attr("height", svgBBox.height + 10); } - if ($scope.metrics.length > 0) { - var d3Selection = d3.select(charts.get(0)); - var axisEl = d3Selection.selectAll(".axis"); - var bail = false; - axisEl.data(["top", "bottom"]).enter().append("div").attr("class", function (d) { - return d + " axis"; - }).each(function (d) { - if (bail) { - return; - } - try { - d3.select(this).call(context.axis().ticks(12).orient(d)); - } - catch (error) { - // still rendering at not the right time... - // log.debug("error: ", error); - if (!bail) { - bail = true; - } - } - }); - if (bail) { - $scope.reset(); - setTimeout(doRender, 500); - Core.$apply($scope); - return; - } - d3Selection.append("div").attr("class", "rule").call(context.rule()); - context.on("focus", function (i) { - try { - d3Selection.selectAll(".value").style("right", i === null ? null : context.size() - i + "px"); - } - catch (error) { - log.info("error: ", error); - } - }); - $scope.metrics.forEach(function (metric) { - d3Selection.call(function (div) { - div.append("div").data([metric]).attr("class", "horizon").call(context.horizon()); - }); + } + // configure dragging if enabled + if (allowDrag) { + // Drag handlers + var nodeDrag = d3.behavior.drag().origin(function (d) { + return d.pos ? { x: d.pos.x, y: d.pos.y } : { x: d.dagre.x, y: d.dagre.y }; + }).on('drag', function (d, i) { + var prevX = d.dagre.x, prevY = d.dagre.y; + // The node must be inside the SVG area + d.dagre.x = Math.max(d.width / 2, Math.min(svgBBox.width - d.width / 2, d3.event.x)); + d.dagre.y = Math.max(d.height / 2, Math.min(svgBBox.height - d.height / 2, d3.event.y)); + d3.select(this).attr('transform', 'translate(' + d.dagre.x + ',' + d.dagre.y + ')'); + var dx = d.dagre.x - prevX, dy = d.dagre.y - prevY; + // Edges position (inside SVG area) + d.edges.forEach(function (e) { + translateEdge(e, dx, dy); + d3.select('#' + e.dagre.id).attr('d', spline(e)); }); - } - else { - $scope.reset(); - } - Core.$apply($scope); + }); + var edgeDrag = d3.behavior.drag().on('drag', function (d, i) { + translateEdge(d, d3.event.dx, d3.event.dy); + d3.select(this).attr('d', spline(d)); + }); + nodes.call(nodeDrag); + edges.call(edgeDrag); } - ; - }]); -})(Jmx || (Jmx = {})); - -/// -/** - * @module Jmx - */ -var Jmx; -(function (Jmx) { - Jmx.DonutChartController = Jmx._module.controller("Jmx.DonutChartController", ["$scope", "$routeParams", "jolokia", "$templateCache", function ($scope, $routeParams, jolokia, $templateCache) { + return states; + } + Core.dagreLayoutGraph = dagreLayoutGraph; + // TODO Export as a service + function dagreUpdateGraphData(data) { + var svg = d3.select("svg"); + svg.selectAll("text.counter").text(_counterFunction); + svg.selectAll("text.inflight").text(_inflightFunction); + // add tooltip + svg.selectAll("g .node title").text(function (d) { + return d.tooltip || ""; + }); /* - console.log("routeParams: ", $routeParams); - + TODO can we reuse twitter bootstrap on an svg title? + .each(function (d) { + $(d).tooltip({ + 'placement': "bottom" + }); + }); - // using multiple attributes - $scope.mbean = "java.lang:type=OperatingSystem"; - $scope.total = "MaxFileDescriptorCount"; - $scope.terms = "OpenFileDescriptorCount"; - */ - // using a single attribute with multiple paths - /* - $scope.mbean = "java.lang:type=Memory"; - $scope.total = "Max"; - $scope.attribute = "HeapMemoryUsage"; - $scope.terms = "Used"; */ - $scope.mbean = $routeParams['mbean']; - $scope.total = $routeParams['total']; - $scope.attribute = $routeParams['attribute']; - $scope.terms = $routeParams['terms']; - $scope.remainder = $routeParams['remaining']; - $scope.template = ""; - $scope.termsArray = $scope.terms.split(","); - $scope.data = { - total: 0, - terms: [] - }; - if (!$scope.attribute) { - $scope.reqs = [{ type: 'read', mbean: $scope.mbean, attribute: $scope.total }]; - $scope.termsArray.forEach(function (term) { - $scope.reqs.push({ type: 'read', mbean: $scope.mbean, attribute: term }); - $scope.data.terms.push({ - term: term, - count: 0 - }); - }); - } - else { - var terms = $scope.termsArray.include($scope.total); - $scope.reqs = [{ type: 'read', mbean: $scope.mbean, attribute: $scope.attribute, paths: terms.join(",") }]; - $scope.termsArray.forEach(function (term) { - $scope.data.terms.push({ - term: term, - count: 0 - }); - }); + } + Core.dagreUpdateGraphData = dagreUpdateGraphData; + function _counterFunction(d) { + return d.counter || ""; + } + function _inflightFunction(d) { + return d.inflight || ""; + } +})(Core || (Core = {})); + +/// +/** + * @module Tree + * @main Tree + */ +var Tree; +(function (Tree) { + Tree.pluginName = 'tree'; + Tree.log = Logger.get("Tree"); + function expandAll(el) { + treeAction(el, true); + } + Tree.expandAll = expandAll; + function contractAll(el) { + treeAction(el, false); + } + Tree.contractAll = contractAll; + function treeAction(el, expand) { + $(el).dynatree("getRoot").visit(function (node) { + node.expand(expand); + }); + } + /** + * @function sanitize + * @param tree + * + * Use to HTML escape all entries in a tree before passing it + * over to the dynatree plugin to avoid cross site scripting + * issues. + * + */ + function sanitize(tree) { + if (!tree) { + return; } - if ($scope.remainder && $scope.remainder !== "-") { - $scope.data.terms.push({ - term: $scope.remainder, - count: 0 + if (angular.isArray(tree)) { + tree.forEach(function (folder) { + Tree.sanitize(folder); }); } - /* - $scope.data = { - total: 100, - terms: [{ - term: "One", - count: 25 - }, { - term: "Two", - count: 75 - }] - }; - */ - $scope.render = function (response) { - //console.log("got: ", response); - var freeTerm = null; - if ($scope.remainder && $scope.remainder !== "-") { - freeTerm = $scope.data.terms.find(function (term) { - return term.term === $scope.remainder; - }); - } - if (!$scope.attribute) { - if (response.request.attribute === $scope.total) { - $scope.data.total = response.value; - } - else { - var term = $scope.data.terms.find(function (term) { - return term.term === response.request.attribute; - }); - if (term) { - term.count = response.value; - } - if (freeTerm) { - freeTerm.count = $scope.data.total; - $scope.data.terms.forEach(function (term) { - if (term.term !== $scope.remainder) { - freeTerm.count = freeTerm.count - term.count; - } - }); - } - } - } - else { - if (response.request.attribute === $scope.attribute) { - $scope.data.total = response.value[$scope.total.toLowerCase()]; - $scope.data.terms.forEach(function (term) { - if (term.term !== $scope.remainder) { - term.count = response.value[term.term.toLowerCase()]; - } - }); - if (freeTerm) { - freeTerm.count = $scope.data.total; - $scope.data.terms.forEach(function (term) { - if (term.term !== $scope.remainder) { - freeTerm.count = freeTerm.count - term.count; - } - }); - } - } - } - if ($scope.template === "") { - $scope.template = $templateCache.get("donut"); - } - // console.log("Data: ", $scope.data); - $scope.data = Object.clone($scope.data); - Core.$apply($scope); - }; - Core.register(jolokia, $scope, $scope.reqs, Core.onSuccess($scope.render)); - }]); -})(Jmx || (Jmx = {})); - -/// -var Core; -(function (Core) { - function d3ForceGraph(scope, nodes, links, canvasElement) { - // lets remove the old graph first - if (scope.graphForce) { - scope.graphForce.stop(); + var title = tree['title']; + if (title) { + tree['title'] = title.unescapeHTML(true).escapeHTML(); } - if (!canvasElement) { - canvasElement = $("#canvas")[0]; + if (tree.children) { + Tree.sanitize(tree.children); } - var canvasDiv = $(canvasElement); - canvasDiv.children("svg").remove(); - if (nodes.length) { - var width = canvasDiv.parent().width(); - var height = canvasDiv.parent().height(); - if (height < 100) { - //console.log("browse thinks the height is only " + height + " so calculating offset from doc height"); - var offset = canvasDiv.offset(); - height = $(document).height() - 5; - if (offset) { - height -= offset['top']; + } + Tree.sanitize = sanitize; + Tree._module = angular.module(Tree.pluginName, []); + Tree._module.directive('hawtioTree', ["workspace", "$timeout", "$location", function (workspace, $timeout, $location) { + // return the directive link function. (compile function not needed) + return function (scope, element, attrs) { + var tree = null; + var data = null; + var widget = null; + var timeoutId = null; + var onSelectFn = lookupFunction("onselect"); + var onDragStartFn = lookupFunction("ondragstart"); + var onDragEnterFn = lookupFunction("ondragenter"); + var onDropFn = lookupFunction("ondrop"); + function lookupFunction(attrName) { + var answer = null; + var fnName = attrs[attrName]; + if (fnName) { + answer = Core.pathGet(scope, fnName); + if (!angular.isFunction(answer)) { + answer = null; + } } + return answer; } - //console.log("Using width " + width + " and height " + height); - var svg = d3.select(canvasDiv[0]).append("svg").attr("width", width).attr("height", height); - var force = d3.layout.force().distance(100).charge(-120 * 10).linkDistance(50).size([width, height]); - scope.graphForce = force; - /* - var force = d3.layout.force() - .gravity(.05) - .distance(100) - .charge(-100) - .size([width, height]); - */ - // prepare the arrows - svg.append("svg:defs").selectAll("marker").data(["from"]).enter().append("svg:marker").attr("id", String).attr("viewBox", "0 -5 10 10").attr("refX", 25).attr("refY", -1.5).attr("markerWidth", 6).attr("markerHeight", 6).attr("orient", "auto").append("svg:path").attr("d", "M0,-5L10,0L0,5"); - force.nodes(nodes).links(links).start(); - var link = svg.selectAll(".link").data(links).enter().append("line").attr("class", "link"); - // draw the arrow - link.attr("class", "link from"); - // end marker - link.attr("marker-end", "url(#from)"); - var node = svg.selectAll(".node").data(nodes).enter().append("g").attr("class", "node").call(force.drag); - node.append("image").attr("xlink:href", function (d) { - return d.imageUrl; - }).attr("x", -15).attr("y", -15).attr("width", 30).attr("height", 30); - node.append("text").attr("dx", 20).attr("dy", ".35em").text(function (d) { - return d.label; + // watch the expression, and update the UI on change. + var data = attrs.hawtioTree; + var queryParam = data; + scope.$watch(data, onWidgetDataChange); + // lets add a separate event so we can force updates + // if we find cases where the delta logic doesn't work + scope.$on("hawtio.tree." + data, function (args) { + var value = Core.pathGet(scope, data); + onWidgetDataChange(value); }); - force.on("tick", function () { - link.attr("x1", function (d) { - return d.source.x; - }).attr("y1", function (d) { - return d.source.y; - }).attr("x2", function (d) { - return d.target.x; - }).attr("y2", function (d) { - return d.target.y; - }); - node.attr("transform", function (d) { - return "translate(" + d.x + "," + d.y + ")"; - }); + // listen on DOM destroy (removal) event, and cancel the next UI update + // to prevent updating ofter the DOM element was removed. + element.bind('$destroy', function () { + $timeout.cancel(timeoutId); }); - } - } - Core.d3ForceGraph = d3ForceGraph; - function createGraphStates(nodes, links, transitions) { - var stateKeys = {}; - nodes.forEach(function (node) { - var idx = node.id; - if (idx === undefined) { - console.log("No node found for node " + JSON.stringify(node)); + updateLater(); // kick off the UI update process. + // used to update the UI + function updateWidget() { + // console.log("updating the grid!!"); + Core.$applyNowOrLater(scope); } - else { - if (node.edges === undefined) - node.edges = []; - if (!node.label) - node.label = "node " + idx; - stateKeys[idx] = node; + function onWidgetDataChange(value) { + tree = value; + if (tree) { + Tree.sanitize(tree); + } + if (tree && !widget) { + // lets find a child table element + // or lets add one if there's not one already + var treeElement = $(element); + var children = Core.asArray(tree); + var hideRoot = attrs["hideroot"]; + if ("true" === hideRoot) { + children = tree['children']; + } + var config = { + clickFolderMode: 3, + /* + * The event handler called when a different node in the tree is selected + */ + onActivate: function (node) { + var data = node.data; + if (onSelectFn) { + onSelectFn(data, node); + } + else { + workspace.updateSelectionNode(data); + } + Core.$apply(scope); + }, + /* + onLazyRead: function(treeNode) { + var folder = treeNode.data; + var plugin = null; + if (folder) { + plugin = Jmx.findLazyLoadingFunction(workspace, folder); + } + if (plugin) { + console.log("Lazy loading folder " + folder.title); + var oldChildren = folder.childen; + plugin(workspace, folder, () => { + treeNode.setLazyNodeStatus(DTNodeStatus_Ok); + var newChildren = folder.children; + if (newChildren !== oldChildren) { + treeNode.removeChildren(); + angular.forEach(newChildren, newChild => { + treeNode.addChild(newChild); + }); + } + }); + } else { + treeNode.setLazyNodeStatus(DTNodeStatus_Ok); + } + }, + */ + onClick: function (node, event) { + if (event["metaKey"]) { + event.preventDefault(); + var url = $location.absUrl(); + if (node && node.data) { + var key = node.data["key"]; + if (key) { + var hash = $location.search(); + hash[queryParam] = key; + // TODO this could maybe be a generic helper function? + // lets trim after the ? + var idx = url.indexOf('?'); + if (idx <= 0) { + url += "?"; + } + else { + url = url.substring(0, idx + 1); + } + url += $.param(hash); + } + } + window.open(url, '_blank'); + window.focus(); + return false; + } + return true; + }, + persist: false, + debugLevel: 0, + children: children, + dnd: { + onDragStart: onDragStartFn ? onDragStartFn : function (node) { + /* This function MUST be defined to enable dragging for the tree. + * Return false to cancel dragging of node. + */ + console.log("onDragStart!"); + return true; + }, + onDragEnter: onDragEnterFn ? onDragEnterFn : function (node, sourceNode) { + console.log("onDragEnter!"); + return true; + }, + onDrop: onDropFn ? onDropFn : function (node, sourceNode, hitMode) { + console.log("onDrop!"); + /* This function MUST be defined to enable dropping of items on + * the tree. + */ + sourceNode.move(node, hitMode); + return true; + } + } + }; + if (!onDropFn && !onDragEnterFn && !onDragStartFn) { + delete config["dnd"]; + } + widget = treeElement.dynatree(config); + var activatedNode = false; + var activateNodeName = attrs["activatenodes"]; + if (activateNodeName) { + var values = scope[activateNodeName]; + var tree = treeElement.dynatree("getTree"); + if (values && tree) { + angular.forEach(Core.asArray(values), function (value) { + //tree.selectKey(value, true); + tree.activateKey(value); + activatedNode = true; + }); + } + } + var root = treeElement.dynatree("getRoot"); + if (root) { + var onRootName = attrs["onroot"]; + if (onRootName) { + var fn = scope[onRootName]; + if (fn) { + fn(root); + } + } + // select and activate first child if we have not activated any others + if (!activatedNode) { + var children = root['getChildren'](); + if (children && children.length) { + var child = children[0]; + child.expand(true); + child.activate(true); + } + } + } + } + updateWidget(); + } + // schedule update in one second + function updateLater() { + // save the timeoutId for canceling + timeoutId = $timeout(function () { + updateWidget(); // update DOM + }, 300); } + }; + }]); + Tree._module.run(["helpRegistry", function (helpRegistry) { + helpRegistry.addDevDoc(Tree.pluginName, 'app/tree/doc/developer.md'); + }]); + hawtioPluginLoader.addModule(Tree.pluginName); +})(Tree || (Tree = {})); + +/// +/// +/** + * @module Jmx + */ +var Jmx; +(function (Jmx) { + Jmx._module.controller("Jmx.TreeHeaderController", ["$scope", function ($scope) { + $scope.expandAll = function () { + Tree.expandAll("#jmxtree"); + }; + $scope.contractAll = function () { + Tree.contractAll("#jmxtree"); + }; + }]); + Jmx._module.controller("Jmx.MBeansController", ["$scope", "$location", "workspace", function ($scope, $location, workspace) { + $scope.num = 1; + $scope.$on("$routeChangeSuccess", function (event, current, previous) { + // lets do this asynchronously to avoid Error: $digest already in progress + setTimeout(updateSelectionFromURL, 50); }); - var states = d3.values(stateKeys); - links.forEach(function (d) { - var source = stateKeys[d.source]; - var target = stateKeys[d.target]; - if (source === undefined || target === undefined) { - console.log("Bad link! " + source + " target " + target + " for " + d); + $scope.select = function (node) { + $scope.workspace.updateSelectionNode(node); + Core.$apply($scope); + }; + function updateSelectionFromURL() { + Jmx.updateTreeSelectionFromURL($location, $("#jmxtree")); + } + $scope.populateTree = function () { + var treeElement = $("#jmxtree"); + $scope.tree = workspace.tree; + Jmx.enableTree($scope, $location, workspace, treeElement, $scope.tree.children, true); + setTimeout(updateSelectionFromURL, 50); + }; + $scope.$on('jmxTreeUpdated', $scope.populateTree); + $scope.populateTree(); + }]); +})(Jmx || (Jmx = {})); + +/// +var Jmx; +(function (Jmx) { + Jmx.NavBarController = Jmx._module.controller("Jmx.NavBarController", ["$scope", "$location", "workspace", "$route", "jolokia", "localStorage", function ($scope, $location, workspace, $route, jolokia, localStorage) { + $scope.hash = workspace.hash(); + $scope.topLevelTabs = []; + $scope.subLevelTabs = workspace.subLevelTabs; + $scope.currentPerspective = null; + $scope.localStorage = localStorage; + $scope.recentConnections = []; + $scope.goTo = function (destination) { + //Logger.debug("going to: " + destination); + $location.url(destination); + }; + $scope.$watch('hash', function (newValue, oldValue) { + if (newValue !== oldValue) { + Jmx.log.debug("hash changed from ", oldValue, " to ", newValue); + } + }); + // when we change the view/selection lets update the hash so links have the latest stuff + $scope.$on('$routeChangeSuccess', function () { + $scope.hash = workspace.hash(); + }); + $scope.isValid = function (nav) { + if ('isValid' in nav) { + return nav.isValid(workspace); + } + return true; + }; + // use includePerspective = false as default as that was the previous behavior + $scope.link = function (nav, includePerspective) { + if (includePerspective === void 0) { includePerspective = false; } + var href; + if (angular.isString(nav)) { + href = nav; } else { - var edge = { source: source, target: target }; - transitions.push(edge); - source.edges.push(edge); - target.edges.push(edge); + href = angular.isObject(nav) ? nav.href() : null; } - }); - return states; - } - Core.createGraphStates = createGraphStates; - // TODO Export as a service - function dagreLayoutGraph(nodes, links, width, height, svgElement, allowDrag, onClick) { - if (allowDrag === void 0) { allowDrag = false; } - if (onClick === void 0) { onClick = null; } - var nodePadding = 10; - var transitions = []; - var states = Core.createGraphStates(nodes, links, transitions); - function spline(e) { - var points = e.dagre.points.slice(0); - var source = dagre.util.intersectRect(e.source.dagre, points.length > 0 ? points[0] : e.source.dagre); - var target = dagre.util.intersectRect(e.target.dagre, points.length > 0 ? points[points.length - 1] : e.source.dagre); - points.unshift(source); - points.push(target); - return d3.svg.line().x(function (d) { - return d.x; - }).y(function (d) { - return d.y; - }).interpolate("linear")(points); - } - // Translates all points in the edge using `dx` and `dy`. - function translateEdge(e, dx, dy) { - e.dagre.points.forEach(function (p) { - p.x = Math.max(0, Math.min(svgBBox.width, p.x + dx)); - p.y = Math.max(0, Math.min(svgBBox.height, p.y + dy)); - }); - } - // Now start laying things out - var svg = svgElement ? d3.select(svgElement) : d3.select("svg"); - // lets remove all the old g elements - if (svgElement) { - $(svgElement).children("g").remove(); - } - $(svg).children("g").remove(); - var svgGroup = svg.append("g").attr("transform", "translate(5, 5)"); - // `nodes` is center positioned for easy layout later - var nodes = svgGroup.selectAll("g .node").data(states).enter().append("g").attr("class", "node").attr("data-cid", function (d) { - return d.cid; - }).attr("id", function (d) { - return "node-" + d.label; - }); - // lets add a tooltip - nodes.append("title").text(function (d) { - return d.tooltip || ""; - }); - var edges = svgGroup.selectAll("path .edge").data(transitions).enter().append("path").attr("class", "edge").attr("marker-end", "url(#arrowhead)"); - // Append rectangles to the nodes. We do this before laying out the text - // because we want the text above the rectangle. - var rects = nodes.append("rect").attr("rx", "4").attr("ry", "4").attr("class", function (d) { - return d.type; - }); - var images = nodes.append("image").attr("xlink:href", function (d) { - return d.imageUrl; - }).attr("x", -12).attr("y", -20).attr("height", 24).attr("width", 24); - var counters = nodes.append("text").attr("text-anchor", "end").attr("class", "counter").attr("x", 0).attr("dy", 0).text(_counterFunction); - var inflights = nodes.append("text").attr("text-anchor", "middle").attr("class", "inflight").attr("x", 10).attr("dy", -32).text(_inflightFunction); - // Append text - var labels = nodes.append("text").attr("text-anchor", "middle").attr("x", 0); - labels.append("tspan").attr("x", 0).attr("dy", 28).text(function (d) { - return d.label; - }); - var labelPadding = 12; - var minLabelwidth = 80; - labels.each(function (d) { - var bbox = this.getBBox(); - d.bbox = bbox; - if (bbox.width < minLabelwidth) { - bbox.width = minLabelwidth; + href = href || ""; + var removeParams = ['tab', 'nid', 'chapter', 'pref', 'q']; + if (!includePerspective && href) { + if (href.indexOf("?p=") >= 0 || href.indexOf("&p=") >= 0) { + removeParams.push("p"); + } } - d.width = bbox.width + 2 * nodePadding; - d.height = bbox.height + 2 * nodePadding + labelPadding; - }); - rects.attr("x", function (d) { - return -(d.bbox.width / 2 + nodePadding); - }).attr("y", function (d) { - return -(d.bbox.height / 2 + nodePadding + (labelPadding / 2)); - }).attr("width", function (d) { - return d.width; - }).attr("height", function (d) { - return d.height; - }); - if (onClick != null) { - rects.on("click", onClick); - } - images.attr("x", function (d) { - return -(d.bbox.width) / 2; - }); - labels.attr("x", function (d) { - return -d.bbox.width / 2; - }).attr("y", function (d) { - return -d.bbox.height / 2; - }); - counters.attr("x", function (d) { - var w = d.bbox.width; - return w / 2; - }); - // Create the layout and get the graph - dagre.layout().nodeSep(50).edgeSep(10).rankSep(50).nodes(states).edges(transitions).debugLevel(1).run(); - nodes.attr("transform", function (d) { - return 'translate(' + d.dagre.x + ',' + d.dagre.y + ')'; - }); - edges.attr('id', function (e) { - return e.dagre.id; - }).attr("d", function (e) { - return spline(e); - }); - // Resize the SVG element - var svgNode = svg.node(); - if (svgNode) { - var svgBBox = svgNode.getBBox(); - if (svgBBox) { - svg.attr("width", svgBBox.width + 10); - svg.attr("height", svgBBox.height + 10); + return Core.createHref($location, href, removeParams); + }; + $scope.fullScreenLink = function () { + var href = "#" + $location.path() + "?tab=notree"; + return Core.createHref($location, href, ['tab']); + }; + $scope.addToDashboardLink = function () { + var href = "#" + $location.path() + workspace.hash(); + var answer = "#/dashboard/add?tab=dashboard&href=" + encodeURIComponent(href); + if ($location.url().has("/jmx/charts")) { + var size = { + size_x: 4, + size_y: 3 + }; + answer += "&size=" + encodeURIComponent(angular.toJson(size)); } - } - // configure dragging if enabled - if (allowDrag) { - // Drag handlers - var nodeDrag = d3.behavior.drag().origin(function (d) { - return d.pos ? { x: d.pos.x, y: d.pos.y } : { x: d.dagre.x, y: d.dagre.y }; - }).on('drag', function (d, i) { - var prevX = d.dagre.x, prevY = d.dagre.y; - // The node must be inside the SVG area - d.dagre.x = Math.max(d.width / 2, Math.min(svgBBox.width - d.width / 2, d3.event.x)); - d.dagre.y = Math.max(d.height / 2, Math.min(svgBBox.height - d.height / 2, d3.event.y)); - d3.select(this).attr('transform', 'translate(' + d.dagre.x + ',' + d.dagre.y + ')'); - var dx = d.dagre.x - prevX, dy = d.dagre.y - prevY; - // Edges position (inside SVG area) - d.edges.forEach(function (e) { - translateEdge(e, dx, dy); - d3.select('#' + e.dagre.id).attr('d', spline(e)); - }); - }); - var edgeDrag = d3.behavior.drag().on('drag', function (d, i) { - translateEdge(d, d3.event.dx, d3.event.dy); - d3.select(this).attr('d', spline(d)); + return answer; + }; + $scope.isActive = function (nav) { + if (angular.isString(nav)) + return workspace.isLinkActive(nav); + var fn = nav.isActive; + if (fn) { + return fn(workspace); + } + return workspace.isLinkActive(nav.href()); + }; + $scope.isTopTabActive = function (nav) { + if (angular.isString(nav)) + return workspace.isTopTabActive(nav); + var fn = nav.isActive; + if (fn) { + return fn(workspace); + } + return workspace.isTopTabActive(nav.href()); + }; + $scope.activeLink = function () { + var tabs = $scope.topLevelTabs(); + if (!tabs) { + return "Loading..."; + } + var tab = tabs.find(function (nav) { + return $scope.isActive(nav); }); - nodes.call(nodeDrag); - edges.call(edgeDrag); - } - return states; - } - Core.dagreLayoutGraph = dagreLayoutGraph; - // TODO Export as a service - function dagreUpdateGraphData(data) { - var svg = d3.select("svg"); - svg.selectAll("text.counter").text(_counterFunction); - svg.selectAll("text.inflight").text(_inflightFunction); - // add tooltip - svg.selectAll("g .node title").text(function (d) { - return d.tooltip || ""; - }); - /* - TODO can we reuse twitter bootstrap on an svg title? - .each(function (d) { - $(d).tooltip({ - 'placement': "bottom" - }); - }); - - */ - } - Core.dagreUpdateGraphData = dagreUpdateGraphData; - function _counterFunction(d) { - return d.counter || ""; - } - function _inflightFunction(d) { - return d.inflight || ""; - } -})(Core || (Core = {})); + return tab ? tab['content'] : ""; + }; + }]); +})(Jmx || (Jmx = {})); -/// +/// /** - * @module Tree - * @main Tree - */ -var Tree; -(function (Tree) { - Tree.pluginName = 'tree'; - Tree.log = Logger.get("Tree"); - function expandAll(el) { - treeAction(el, true); - } - Tree.expandAll = expandAll; - function contractAll(el) { - treeAction(el, false); - } - Tree.contractAll = contractAll; - function treeAction(el, expand) { - $(el).dynatree("getRoot").visit(function (node) { - node.expand(expand); +* @module Jmx +*/ +var Jmx; +(function (Jmx) { + // IOperationControllerScope + Jmx._module.controller("Jmx.OperationController", ["$scope", "workspace", "jolokia", "jolokiaUrl", "$timeout", "$location", "localStorage", "$browser", function ($scope, workspace, jolokia, jolokiaUrl, $timeout, $location, localStorage, $browser) { + $scope.item = $scope.selectedOperation; + $scope.title = $scope.item.humanReadable; + $scope.desc = $scope.item.desc; + $scope.operationResult = ''; + $scope.executeIcon = "fa fa-ok"; + $scope.mode = "text"; + $scope.entity = {}; + $scope.formConfig = { + hideLegend: true, + properties: {} + }; + $scope.jolokiaUrl = Jmx.getUrlForThing(jolokiaUrl, 'exec', workspace.getSelectedMBeanName(), $scope.item.name); + $scope.item.args.forEach(function (arg) { + var property = { + type: arg.type, + tooltip: arg.desc, + description: "Type: " + arg.type + }; + if (arg.type.toLowerCase() === 'java.util.list' || arg.type.toLowerCase() === '[j') { + property.type = 'array'; + property.items = { type: 'string' }; + } + if (arg.type.toLowerCase() === 'java.util.map') { + property.type = 'map'; + property.items = { + key: { type: 'string' }, + value: { type: 'string' } + }; + } + $scope.formConfig.properties[arg.name] = property; }); - } - /** - * @function sanitize - * @param tree - * - * Use to HTML escape all entries in a tree before passing it - * over to the dynatree plugin to avoid cross site scripting - * issues. - * - */ - function sanitize(tree) { - if (!tree) { - return; - } - if (angular.isArray(tree)) { - tree.forEach(function (folder) { - Tree.sanitize(folder); + Jmx.log.debug("Form config: ", $scope.formConfig); + $timeout(function () { + $("html, body").animate({ scrollTop: 0 }, "medium"); + }, 250); + $scope.dump = function (data) { + console.log(data); + }; + $scope.ok = function () { + $scope.operationResult = ''; + }; + $scope.reset = function () { + $scope.entity = {}; + }; + $scope.close = function () { + $scope.$parent.showInvoke = false; + }; + $scope.handleResponse = function (response) { + $scope.executeIcon = "fa fa-ok"; + $scope.operationStatus = "success"; + if (response === null || 'null' === response) { + $scope.operationResult = "Operation Succeeded!"; + } + else if (typeof response === 'string') { + $scope.operationResult = response; + } + else { + $scope.operationResult = angular.toJson(response, true); + } + $scope.mode = CodeEditor.detectTextFormat($scope.operationResult); + Core.$apply($scope); + }; + $scope.onSubmit = function () { + var json = $scope.entity; + Jmx.log.debug("onSubmit: json:", json); + Jmx.log.debug("$scope.item.args: ", $scope.item.args); + angular.forEach(json, function (value, key) { + $scope.item.args.find(function (arg) { + return arg['name'] === key; + }).value = value; }); + $scope.execute(); + }; + $scope.execute = function () { + var node = workspace.selection; + if (!node) { + return; + } + var objectName = node.objectName; + if (!objectName) { + return; + } + var args = [objectName, $scope.item.name]; + if ($scope.item.args) { + $scope.item.args.forEach(function (arg) { + args.push(arg.value); + }); + } + args.push(Core.onSuccess($scope.handleResponse, { + error: function (response) { + $scope.executeIcon = "fa fa-ok"; + $scope.operationStatus = "error"; + var error = response.error; + $scope.operationResult = error; + var stacktrace = response.stacktrace; + if (stacktrace) { + $scope.operationResult = stacktrace; + } + Core.$apply($scope); + } + })); + $scope.executeIcon = "fa fa-spinner fa fa-spin"; + var fn = jolokia.execute; + fn.apply(jolokia, args); + }; + }]); + Jmx._module.controller("Jmx.OperationsController", ["$scope", "workspace", "jolokia", "rbacACLMBean", "$templateCache", function ($scope, workspace, jolokia, rbacACLMBean, $templateCache) { + $scope.fetched = false; + $scope.operations = {}; + $scope.objectName = ''; + $scope.methodFilter = ''; + $scope.workspace = workspace; + $scope.selectedOperation = null; + $scope.showInvoke = false; + $scope.template = ""; + $scope.invokeOp = function (operation) { + if (!$scope.canInvoke(operation)) { + return; + } + $scope.selectedOperation = operation; + $scope.showInvoke = true; + }; + $scope.getJson = function (operation) { + return angular.toJson(operation, true); + }; + $scope.cancel = function () { + $scope.selectedOperation = null; + $scope.showInvoke = false; + }; + $scope.$watch('showInvoke', function (newValue, oldValue) { + if (newValue !== oldValue) { + if (newValue) { + $scope.template = $templateCache.get("operationTemplate"); + } + else { + $scope.template = ""; + } + } + }); + var fetch = _.debounce(function () { + var node = workspace.selection || workspace.getSelectedMBean(); + if (!node) { + return; + } + $scope.objectName = node.objectName; + if (!$scope.objectName) { + return; + } + jolokia.request({ + type: 'list', + path: Core.escapeMBeanPath($scope.objectName) + }, Core.onSuccess(render)); + }, 100, { trailing: true }); + function getArgs(args) { + return "(" + args.map(function (arg) { + return arg.type; + }).join() + ")"; } - var title = tree['title']; - if (title) { - tree['title'] = title.unescapeHTML(true).escapeHTML(); - } - if (tree.children) { - Tree.sanitize(tree.children); + function sanitize(value) { + for (var item in value) { + item = "" + item; + value[item].name = item; + value[item].humanReadable = Core.humanizeValue(item); + } + return value; } - } - Tree.sanitize = sanitize; - Tree._module = angular.module(Tree.pluginName, []); - Tree._module.directive('hawtioTree', ["workspace", "$timeout", "$location", function (workspace, $timeout, $location) { - // return the directive link function. (compile function not needed) - return function (scope, element, attrs) { - var tree = null; - var data = null; - var widget = null; - var timeoutId = null; - var onSelectFn = lookupFunction("onselect"); - var onDragStartFn = lookupFunction("ondragstart"); - var onDragEnterFn = lookupFunction("ondragenter"); - var onDropFn = lookupFunction("ondrop"); - function lookupFunction(attrName) { - var answer = null; - var fnName = attrs[attrName]; - if (fnName) { - answer = Core.pathGet(scope, fnName); - if (!angular.isFunction(answer)) { - answer = null; - } - } - return answer; + $scope.isOperationsEmpty = function () { + return $.isEmptyObject($scope.operations); + }; + $scope.doFilter = function (item) { + if (Core.isBlank($scope.methodFilter)) { + return true; } - // watch the expression, and update the UI on change. - var data = attrs.hawtioTree; - var queryParam = data; - scope.$watch(data, onWidgetDataChange); - // lets add a separate event so we can force updates - // if we find cases where the delta logic doesn't work - scope.$on("hawtio.tree." + data, function (args) { - var value = Core.pathGet(scope, data); - onWidgetDataChange(value); + if (item.name.toLowerCase().has($scope.methodFilter.toLowerCase()) || item.humanReadable.toLowerCase().has($scope.methodFilter.toLowerCase())) { + return true; + } + return false; + }; + $scope.canInvoke = function (operation) { + if (!('canInvoke' in operation)) { + return true; + } + else { + return operation['canInvoke']; + } + }; + $scope.getClass = function (operation) { + if ($scope.canInvoke(operation)) { + return 'can-invoke'; + } + else { + return 'cant-invoke'; + } + }; + $scope.$watch('workspace.selection', function (newValue, oldValue) { + if (workspace.moveIfViewInvalid()) { + return; + } + fetch(); + }); + function fetchPermissions(objectName, operations) { + var map = {}; + map[objectName] = []; + angular.forEach(operations, function (value, key) { + map[objectName].push(value.name); }); - // listen on DOM destroy (removal) event, and cancel the next UI update - // to prevent updating ofter the DOM element was removed. - element.bind('$destroy', function () { - $timeout.cancel(timeoutId); + rbacACLMBean.then(function (rbacACLMBean) { + jolokia.request({ + type: 'exec', + mbean: rbacACLMBean, + operation: 'canInvoke(java.util.Map)', + arguments: [map] + }, Core.onSuccess(function (response) { + var map = response.value; + angular.forEach(map[objectName], function (value, key) { + operations[key]['canInvoke'] = value['CanInvoke']; + }); + Jmx.log.debug("Got operations: ", $scope.operations); + Core.$apply($scope); + }, { + error: function (response) { + // silently ignore + Jmx.log.debug("Failed to fetch ACL for operations: ", response); + Core.$apply($scope); + } + })); }); - updateLater(); // kick off the UI update process. - // used to update the UI - function updateWidget() { - // console.log("updating the grid!!"); - Core.$applyNowOrLater(scope); - } - function onWidgetDataChange(value) { - tree = value; - if (tree) { - Tree.sanitize(tree); + } + function render(response) { + $scope.fetched = true; + var ops = response.value.op; + var answer = {}; + angular.forEach(ops, function (value, key) { + if (angular.isArray(value)) { + angular.forEach(value, function (value, index) { + answer[key + getArgs(value.args)] = value; + }); } - if (tree && !widget) { - // lets find a child table element - // or lets add one if there's not one already - var treeElement = $(element); - var children = Core.asArray(tree); - var hideRoot = attrs["hideroot"]; - if ("true" === hideRoot) { - children = tree['children']; - } - var config = { - clickFolderMode: 3, - /* - * The event handler called when a different node in the tree is selected - */ - onActivate: function (node) { - var data = node.data; - if (onSelectFn) { - onSelectFn(data, node); - } - else { - workspace.updateSelectionNode(data); - } - Core.$apply(scope); - }, - /* - onLazyRead: function(treeNode) { - var folder = treeNode.data; - var plugin = null; - if (folder) { - plugin = Jmx.findLazyLoadingFunction(workspace, folder); - } - if (plugin) { - console.log("Lazy loading folder " + folder.title); - var oldChildren = folder.childen; - plugin(workspace, folder, () => { - treeNode.setLazyNodeStatus(DTNodeStatus_Ok); - var newChildren = folder.children; - if (newChildren !== oldChildren) { - treeNode.removeChildren(); - angular.forEach(newChildren, newChild => { - treeNode.addChild(newChild); - }); - } - }); - } else { - treeNode.setLazyNodeStatus(DTNodeStatus_Ok); - } - }, - */ - onClick: function (node, event) { - if (event["metaKey"]) { - event.preventDefault(); - var url = $location.absUrl(); - if (node && node.data) { - var key = node.data["key"]; - if (key) { - var hash = $location.search(); - hash[queryParam] = key; - // TODO this could maybe be a generic helper function? - // lets trim after the ? - var idx = url.indexOf('?'); - if (idx <= 0) { - url += "?"; - } - else { - url = url.substring(0, idx + 1); - } - url += $.param(hash); - } - } - window.open(url, '_blank'); - window.focus(); - return false; - } - return true; - }, - persist: false, - debugLevel: 0, - children: children, - dnd: { - onDragStart: onDragStartFn ? onDragStartFn : function (node) { - /* This function MUST be defined to enable dragging for the tree. - * Return false to cancel dragging of node. - */ - console.log("onDragStart!"); - return true; - }, - onDragEnter: onDragEnterFn ? onDragEnterFn : function (node, sourceNode) { - console.log("onDragEnter!"); - return true; - }, - onDrop: onDropFn ? onDropFn : function (node, sourceNode, hitMode) { - console.log("onDrop!"); - /* This function MUST be defined to enable dropping of items on - * the tree. - */ - sourceNode.move(node, hitMode); - return true; + else { + answer[key + getArgs(value.args)] = value; + } + }); + $scope.operations = sanitize(answer); + if ($scope.isOperationsEmpty()) { + Core.$apply($scope); + } + else { + fetchPermissions($scope.objectName, $scope.operations); + Core.$apply($scope); + } + } + }]); +})(Jmx || (Jmx = {})); + +/** + * @module Core + */ +/// +var Jmx; +(function (Jmx) { + // NOTE - $route is brought in here to ensure the factory for that service + // has been called, otherwise the ng-include directive doesn't show the partial + // after a refresh until you click a top-level link. + Jmx.ViewController = Jmx._module.controller("Jmx.ViewController", ["$scope", "$route", "$location", "layoutTree", "layoutFull", "viewRegistry", function ($scope, $route, $location, layoutTree, layoutFull, viewRegistry) { + findViewPartial(); + $scope.$on("$routeChangeSuccess", function (event, current, previous) { + findViewPartial(); + }); + function searchRegistry(path) { + var answer = undefined; + _.forIn(viewRegistry, function (value, key) { + if (!answer) { + if (key.startsWith("/") && key.endsWith("/")) { + // assume its a regex + var text = key.substring(1, key.length - 1); + try { + var reg = new RegExp(text, ""); + if (reg.exec(path)) { + answer = value; } } - }; - if (!onDropFn && !onDragEnterFn && !onDragStartFn) { - delete config["dnd"]; - } - widget = treeElement.dynatree(config); - var activatedNode = false; - var activateNodeName = attrs["activatenodes"]; - if (activateNodeName) { - var values = scope[activateNodeName]; - var tree = treeElement.dynatree("getTree"); - if (values && tree) { - angular.forEach(Core.asArray(values), function (value) { - //tree.selectKey(value, true); - tree.activateKey(value); - activatedNode = true; - }); + catch (e) { + Jmx.log.debug("Invalid RegExp " + text + " for viewRegistry value: " + value); } } - var root = treeElement.dynatree("getRoot"); - if (root) { - var onRootName = attrs["onroot"]; - if (onRootName) { - var fn = scope[onRootName]; - if (fn) { - fn(root); - } - } - // select and activate first child if we have not activated any others - if (!activatedNode) { - var children = root['getChildren'](); - if (children && children.length) { - var child = children[0]; - child.expand(true); - child.activate(true); - } + else { + if (path.startsWith(key)) { + answer = value; } } } - updateWidget(); + }); + //log.debug("Searching for: " + path + " returning: ", answer); + return answer; + } + function findViewPartial() { + var answer = null; + var hash = $location.search(); + var tab = hash['tab']; + if (angular.isString(tab)) { + answer = searchRegistry(tab); + } + if (!answer) { + var path = $location.path(); + if (path) { + if (path.startsWith("/")) { + path = path.substring(1); + } + answer = searchRegistry(path); + } + } + if (!answer) { + answer = layoutTree; + } + $scope.viewPartial = answer; + Jmx.log.debug("Using view partial: " + answer); + return answer; + } + }]); +})(Jmx || (Jmx = {})); + +/// +/// +/** + * @module JVM + * @main JVM + */ +var JVM; +(function (JVM) { + JVM.windowJolokia = undefined; + JVM._module = angular.module(JVM.pluginName, []); + JVM._module.config(["$provide", "$routeProvider", function ($provide, $routeProvider) { + /* + $provide.decorator('WelcomePageRegistry', ['$delegate', ($delegate) => { + return { + + } + }]); + */ + $routeProvider.when('/jvm', { redirectTo: '/jvm/connect' }).when('/jvm/welcome', { templateUrl: UrlHelpers.join(JVM.templatePath, 'welcome.html') }).when('/jvm/discover', { templateUrl: UrlHelpers.join(JVM.templatePath, 'discover.html') }).when('/jvm/connect', { templateUrl: UrlHelpers.join(JVM.templatePath, 'connect.html') }).when('/jvm/local', { templateUrl: UrlHelpers.join(JVM.templatePath, 'local.html') }); + }]); + JVM._module.constant('mbeanName', 'hawtio:type=JVMList'); + JVM._module.run(["HawtioNav", "$location", "workspace", "viewRegistry", "layoutFull", "helpRegistry", "preferencesRegistry", "ConnectOptions", "locationChangeStartTasks", "HawtioDashboard", "HawtioExtension", "$templateCache", "$compile", function (nav, $location, workspace, viewRegistry, layoutFull, helpRegistry, preferencesRegistry, ConnectOptions, locationChangeStartTasks, dash, extensions, $templateCache, $compile) { + extensions.add('hawtio-header', function ($scope) { + var template = $templateCache.get(UrlHelpers.join(JVM.templatePath, 'navbarHeaderExtension.html')); + return $compile(template)($scope); + }); + if (!dash.inDashboard) { + // ensure that if the connection parameter is present, that we keep it + locationChangeStartTasks.addTask('ConParam', function ($event, newUrl, oldUrl) { + // we can't execute until the app is initialized... + if (!HawtioCore.injector) { + return; + } + //log.debug("ConParam task firing, newUrl: ", newUrl, " oldUrl: ", oldUrl, " ConnectOptions: ", ConnectOptions); + if (!ConnectOptions || !ConnectOptions.name || !newUrl) { + return; + } + var newQuery = new URI(newUrl).query(true); + if (!newQuery.con) { + //log.debug("Lost connection parameter (", ConnectOptions.name, ") from query params: ", newQuery, " resetting"); + newQuery['con'] = ConnectOptions.name; + $location.search(newQuery); + } + }); + } + var builder = nav.builder(); + var remote = builder.id('jvm-remote').href(function () { return '/jvm/connect'; }).title(function () { return 'Remote'; }).tooltip(function () { return 'To connect to a remote JVM'; }).build(); + var local = builder.id('jvm-local').href(function () { return '/jvm/local'; }).title(function () { return 'Local'; }).tooltip(function () { return 'To connect to a locale JVM'; }).show(function () { return JVM.hasLocalMBean(workspace); }).build(); + var discover = builder.id('jvm-discover').href(function () { return '/jvm/discover'; }).title(function () { return 'Discover'; }).tooltip(function () { return 'To discover JVMs in the network that has Jolokia agent running'; }).show(function () { return JVM.hasDiscoveryMBean(workspace); }).build(); + var tab = builder.id('jvm').href(function () { return '/jvm'; }).title(function () { return 'Connect'; }).isValid(function () { return ConnectOptions == null || ConnectOptions.name == null; }).tabs(remote, local, discover).build(); + nav.add(tab); + helpRegistry.addUserDoc('jvm', 'plugins/jvm/doc/help.md'); + preferencesRegistry.addTab("Connect", 'plugins/jvm/html/reset.html'); + preferencesRegistry.addTab("Jolokia", "plugins/jvm/html/jolokiaPreferences.html"); + }]); + hawtioPluginLoader.addModule(JVM.pluginName); +})(JVM || (JVM = {})); + +/// +/// +/** + * @module JVM + */ +var JVM; +(function (JVM) { + JVM.ConnectController = JVM._module.controller("JVM.ConnectController", ["$scope", "$location", "localStorage", "workspace", "$http", function ($scope, $location, localStorage, workspace, $http) { + JVM.configureScope($scope, $location, workspace); + function newConfig() { + return Core.createConnectOptions({ + scheme: 'http', + host: 'localhost', + path: 'jolokia', + port: 8181, + userName: '', + password: '', + useProxy: !$scope.disableProxy + }); + } + ; + $scope.forms = {}; + $http.get('proxy').then(function (resp) { + if (resp.status === 200 && Core.isBlank(resp.data)) { + $scope.disableProxy = false; + } + else { + $scope.disableProxy = true; + } + }); + var hasMBeans = false; + workspace.addNamedTreePostProcessor('ConnectTab', function (tree) { + hasMBeans = workspace && workspace.tree && workspace.tree.children && workspace.tree.children.length > 0; + $scope.disableProxy = !hasMBeans || Core.isChromeApp(); + Core.$apply($scope); + }); + $scope.lastConnection = ''; + // load settings like current tab, last used connection + if (JVM.connectControllerKey in localStorage) { + try { + $scope.lastConnection = angular.fromJson(localStorage[JVM.connectControllerKey]); + } + catch (e) { + // corrupt config + $scope.lastConnection = ''; + delete localStorage[JVM.connectControllerKey]; + } + } + // load connection settings + $scope.connectionConfigs = Core.loadConnectionMap(); + if (!Core.isBlank($scope.lastConnection)) { + $scope.currentConfig = $scope.connectionConfigs[$scope.lastConnection]; + } + else { + $scope.currentConfig = newConfig(); + } + /* + log.debug("Controller settings: ", $scope.settings); + log.debug("Current config: ", $scope.currentConfig); + log.debug("All connection settings: ", $scope.connectionConfigs); + */ + $scope.formConfig = { + properties: { + name: { + type: "java.lang.String", + tooltip: "Name for this connection", + required: true, + "input-attributes": { + "placeholder": "Unnamed..." + } + }, + scheme: { + type: "java.lang.String", + tooltip: "HTTP or HTTPS", + enum: ["http", "https"], + required: true + }, + host: { + type: "java.lang.String", + tooltip: "Target host to connect to", + required: true + }, + port: { + type: "java.lang.Integer", + tooltip: "The HTTP port used to connect to the server", + "input-attributes": { + "min": "0" + }, + required: true + }, + path: { + type: "java.lang.String", + tooltip: "The URL path used to connect to Jolokia on the remote server" + }, + userName: { + type: "java.lang.String", + tooltip: "The user name to be used when connecting to Jolokia" + }, + password: { + type: "password", + tooltip: "The password to be used when connecting to Jolokia" + }, + useProxy: { + type: "java.lang.Boolean", + tooltip: "Whether or not we should use a proxy. See more information in the panel to the left.", + "control-attributes": { + "ng-hide": "disableProxy" + } + } } - // schedule update in one second - function updateLater() { - // save the timeoutId for canceling - timeoutId = $timeout(function () { - updateWidget(); // update DOM - }, 300); + }; + $scope.newConnection = function () { + $scope.lastConnection = ''; + }; + $scope.deleteConnection = function () { + delete $scope.connectionConfigs[$scope.lastConnection]; + Core.saveConnectionMap($scope.connectionConfigs); + var keys = _.keys($scope.connectionConfigs); + if (keys.length === 0) { + $scope.lastConnection = ''; + } + else { + $scope.lastConnection = keys[0]; } }; + $scope.$watch('lastConnection', function (newValue, oldValue) { + JVM.log.debug("lastConnection: ", newValue); + if (newValue !== oldValue) { + if (Core.isBlank(newValue)) { + $scope.currentConfig = newConfig(); + } + else { + $scope.currentConfig = $scope.connectionConfigs[newValue]; + } + localStorage[JVM.connectControllerKey] = angular.toJson(newValue); + } + }, true); + $scope.save = function () { + $scope.gotoServer($scope.currentConfig, null, true); + }; + $scope.gotoServer = function (connectOptions, form, saveOnly) { + if (!connectOptions) { + connectOptions = Core.getConnectOptions($scope.lastConnection); + } + var name = connectOptions.name; + $scope.connectionConfigs[name] = connectOptions; + $scope.lastConnection = name; + if (saveOnly === true) { + Core.saveConnectionMap($scope.connectionConfigs); + $scope.connectionConfigs = Core.loadConnectionMap(); + angular.extend($scope.currentConfig, $scope.connectionConfigs[$scope.lastConnection]); + Core.$apply($scope); + return; + } + Core.connectToServer(localStorage, connectOptions); + $scope.connectionConfigs = Core.loadConnectionMap(); + angular.extend($scope.currentConfig, $scope.connectionConfigs[$scope.lastConnection]); + Core.$apply($scope); + }; }]); - Tree._module.run(["helpRegistry", function (helpRegistry) { - helpRegistry.addDevDoc(Tree.pluginName, 'app/tree/doc/developer.md'); - }]); - hawtioPluginLoader.addModule(Tree.pluginName); -})(Tree || (Tree = {})); +})(JVM || (JVM = {})); -/// -/// +/// +/// /** - * @module Jmx + * @module JVM */ -var Jmx; -(function (Jmx) { - Jmx._module.controller("Jmx.TreeHeaderController", ["$scope", function ($scope) { - $scope.expandAll = function () { - Tree.expandAll("#jmxtree"); +var JVM; +(function (JVM) { + JVM._module.controller("JVM.DiscoveryController", ["$scope", "localStorage", "jolokia", function ($scope, localStorage, jolokia) { + $scope.discovering = true; + $scope.agents = undefined; + $scope.$watch('agents', function (newValue, oldValue) { + if (newValue !== oldValue) { + $scope.selectedAgent = $scope.agents.find(function (a) { return a['selected']; }); + } + }, true); + $scope.closePopover = function ($event) { + $($event.currentTarget).parents('.popover').prev().popover('hide'); }; - $scope.contractAll = function () { - Tree.contractAll("#jmxtree"); + function doConnect(agent) { + if (!agent.url) { + Core.notification('warning', 'No URL available to connect to agent'); + return; + } + var options = Core.createConnectOptions(); + options.name = agent.agent_description; + var urlObject = Core.parseUrl(agent.url); + angular.extend(options, urlObject); + options.userName = agent.username; + options.password = agent.password; + Core.connectToServer(localStorage, options); + } + ; + $scope.connectWithCredentials = function ($event, agent) { + $scope.closePopover($event); + doConnect(agent); }; - }]); - Jmx._module.controller("Jmx.MBeansController", ["$scope", "$location", "workspace", function ($scope, $location, workspace) { - $scope.num = 1; - $scope.$on("$routeChangeSuccess", function (event, current, previous) { - // lets do this asynchronously to avoid Error: $digest already in progress - setTimeout(updateSelectionFromURL, 50); - }); - $scope.select = function (node) { - $scope.workspace.updateSelectionNode(node); + $scope.gotoServer = function ($event, agent) { + if (agent.secured) { + $($event.currentTarget).popover('show'); + } + else { + doConnect(agent); + } + }; + $scope.getElementId = function (agent) { + return agent.agent_id.dasherize().replace(/\./g, "-"); + }; + $scope.getLogo = function (agent) { + if (agent.server_product) { + return JVM.logoRegistry[agent.server_product]; + } + return JVM.logoRegistry['generic']; + }; + $scope.filterMatches = function (agent) { + if (Core.isBlank($scope.filter)) { + return true; + } + else { + return angular.toJson(agent).toLowerCase().has($scope.filter.toLowerCase()); + } + }; + $scope.getAgentIdClass = function (agent) { + if ($scope.hasName(agent)) { + return ""; + } + return "strong"; + }; + $scope.hasName = function (agent) { + if (agent.server_vendor && agent.server_product && agent.server_version) { + return true; + } + return false; + }; + $scope.render = function (response) { + $scope.discovering = false; + if (response) { + var responseJson = angular.toJson(response, true); + if ($scope.responseJson !== responseJson) { + $scope.responseJson = responseJson; + $scope.agents = response; + } + } Core.$apply($scope); }; - function updateSelectionFromURL() { - Jmx.updateTreeSelectionFromURL($location, $("#jmxtree")); - } - $scope.populateTree = function () { - var treeElement = $("#jmxtree"); - $scope.tree = workspace.tree; - Jmx.enableTree($scope, $location, workspace, treeElement, $scope.tree.children, true); - setTimeout(updateSelectionFromURL, 50); + $scope.fetch = function () { + $scope.discovering = true; + // use 10 sec timeout + jolokia.execute('jolokia:type=Discovery', 'lookupAgentsWithTimeout(int)', 10 * 1000, Core.onSuccess($scope.render)); }; - $scope.$on('jmxTreeUpdated', $scope.populateTree); - $scope.populateTree(); + $scope.fetch(); }]); -})(Jmx || (Jmx = {})); +})(JVM || (JVM = {})); -/// -var Jmx; -(function (Jmx) { - Jmx.NavBarController = Jmx._module.controller("Jmx.NavBarController", ["$scope", "$location", "workspace", "$route", "jolokia", "localStorage", function ($scope, $location, workspace, $route, jolokia, localStorage) { - $scope.hash = workspace.hash(); - $scope.topLevelTabs = []; - $scope.subLevelTabs = workspace.subLevelTabs; - $scope.currentPerspective = null; - $scope.localStorage = localStorage; - $scope.recentConnections = []; - $scope.goTo = function (destination) { - //Logger.debug("going to: " + destination); - $location.url(destination); - }; - $scope.$watch('hash', function (newValue, oldValue) { - if (newValue !== oldValue) { - Jmx.log.debug("hash changed from ", oldValue, " to ", newValue); +/// +var JVM; +(function (JVM) { + JVM.HeaderController = JVM._module.controller("JVM.HeaderController", ["$scope", "ConnectOptions", function ($scope, ConnectOptions) { + if (ConnectOptions) { + $scope.containerName = ConnectOptions.name || ""; + if (ConnectOptions.returnTo) { + $scope.goBack = function () { + window.location.href = ConnectOptions.returnTo; + }; } - }); - // when we change the view/selection lets update the hash so links have the latest stuff - $scope.$on('$routeChangeSuccess', function () { - $scope.hash = workspace.hash(); - }); - $scope.isValid = function (nav) { - if ('isValid' in nav) { - return nav.isValid(workspace); + } + }]); +})(JVM || (JVM = {})); + +/// +/// +var JVM; +(function (JVM) { + JVM._module.controller("JVM.JolokiaPreferences", ["$scope", "localStorage", "jolokiaParams", "$window", function ($scope, localStorage, jolokiaParams, $window) { + var config = { + properties: { + maxDepth: { + type: 'number', + description: 'The number of levels jolokia will marshal an object to json on the server side before returning' + }, + maxCollectionSize: { + type: 'number', + description: 'The maximum number of elements in an array that jolokia will marshal in a response' + } + } + }; + $scope.entity = $scope; + $scope.config = config; + Core.initPreferenceScope($scope, localStorage, { + 'maxDepth': { + 'value': JVM.DEFAULT_MAX_DEPTH, + 'converter': parseInt, + 'formatter': parseInt, + 'post': function (newValue) { + jolokiaParams.maxDepth = newValue; + localStorage['jolokiaParams'] = angular.toJson(jolokiaParams); + } + }, + 'maxCollectionSize': { + 'value': JVM.DEFAULT_MAX_COLLECTION_SIZE, + 'converter': parseInt, + 'formatter': parseInt, + 'post': function (newValue) { + jolokiaParams.maxCollectionSize = newValue; + localStorage['jolokiaParams'] = angular.toJson(jolokiaParams); + } } - return true; + }); + $scope.reboot = function () { + $window.location.reload(); }; - // use includePerspective = false as default as that was the previous behavior - $scope.link = function (nav, includePerspective) { - if (includePerspective === void 0) { includePerspective = false; } - var href; - if (angular.isString(nav)) { - href = nav; + }]); +})(JVM || (JVM = {})); + +/// +/// +/** + * @module JVM + */ +var JVM; +(function (JVM) { + var urlCandidates = ['/hawtio/jolokia', '/jolokia', 'jolokia']; + var discoveredUrl = null; + hawtioPluginLoader.registerPreBootstrapTask(function (next) { + var uri = new URI(); + var query = uri.query(true); + JVM.log.debug("query: ", query); + var jolokiaUrl = query['jolokiaUrl']; + if (jolokiaUrl) { + delete query['sub-tab']; + delete query['main-tab']; + jolokiaUrl = jolokiaUrl.unescapeURL(); + var jolokiaURI = new URI(jolokiaUrl); + var name = query['title'] || 'Unknown Connection'; + var token = query['token'] || Core.trimLeading(uri.hash(), '#'); + var options = Core.createConnectOptions({ + name: name, + scheme: jolokiaURI.protocol(), + host: jolokiaURI.hostname(), + port: Core.parseIntValue(jolokiaURI.port()), + path: Core.trimLeading(jolokiaURI.pathname(), '/'), + useProxy: false + }); + if (!Core.isBlank(token)) { + options['token'] = token; + } + _.merge(options, jolokiaURI.query(true)); + _.assign(options, query); + JVM.log.debug("options: ", options); + var connectionMap = Core.loadConnectionMap(); + connectionMap[name] = options; + Core.saveConnectionMap(connectionMap); + uri.hash("").query({ + con: name + }); + window.location.replace(uri.toString()); + } + var connectionName = query['con']; + if (connectionName) { + JVM.log.debug("Not discovering jolokia"); + // a connection name is set, no need to discover a jolokia instance + next(); + return; + } + function maybeCheckNext(candidates) { + if (candidates.length === 0) { + next(); } else { - href = angular.isObject(nav) ? nav.href() : null; + checkNext(candidates.pop()); } - href = href || ""; - var removeParams = ['tab', 'nid', 'chapter', 'pref', 'q']; - if (!includePerspective && href) { - if (href.indexOf("?p=") >= 0 || href.indexOf("&p=") >= 0) { - removeParams.push("p"); + } + function checkNext(url) { + JVM.log.debug("trying URL: ", url); + $.ajax(url).always(function (data, statusText, jqXHR) { + if (jqXHR.status === 200) { + try { + var resp = angular.fromJson(data); + //log.debug("Got response: ", resp); + if ('value' in resp && 'agent' in resp.value) { + discoveredUrl = url; + JVM.log.debug("Found jolokia agent at: ", url, " version: ", resp.value.agent); + next(); + } + else { + maybeCheckNext(urlCandidates); + } + } + catch (e) { + maybeCheckNext(urlCandidates); + } } + else if (jqXHR.status === 401 || jqXHR.status === 403) { + // I guess this could be it... + discoveredUrl = url; + JVM.log.debug("Using URL: ", url, " assuming it could be an agent but got return code: ", jqXHR.status); + next(); + } + else { + maybeCheckNext(urlCandidates); + } + }); + } + checkNext(urlCandidates.pop()); + }); + JVM._module.service('ConnectionName', ['$location', function ($location) { + var answer = null; + return function (reset) { + if (reset === void 0) { reset = false; } + if (!Core.isBlank(answer) && !reset) { + return answer; } - return Core.createHref($location, href, removeParams); - }; - $scope.fullScreenLink = function () { - var href = "#" + $location.path() + "?tab=notree"; - return Core.createHref($location, href, ['tab']); - }; - $scope.addToDashboardLink = function () { - var href = "#" + $location.path() + workspace.hash(); - var answer = "#/dashboard/add?tab=dashboard&href=" + encodeURIComponent(href); - if ($location.url().has("/jmx/charts")) { - var size = { - size_x: 4, - size_y: 3 - }; - answer += "&size=" + encodeURIComponent(angular.toJson(size)); - } - return answer; - }; - $scope.isActive = function (nav) { - if (angular.isString(nav)) - return workspace.isLinkActive(nav); - var fn = nav.isActive; - if (fn) { - return fn(workspace); + answer = ''; + var search = $location.search(); + if ('con' in window) { + answer = window['con']; + JVM.log.debug("Using connection name from window: ", answer); } - return workspace.isLinkActive(nav.href()); - }; - $scope.isTopTabActive = function (nav) { - if (angular.isString(nav)) - return workspace.isTopTabActive(nav); - var fn = nav.isActive; - if (fn) { - return fn(workspace); + else if ('con' in search) { + answer = search['con']; + JVM.log.debug("Using connection name from URL: ", answer); } - return workspace.isTopTabActive(nav.href()); - }; - $scope.activeLink = function () { - var tabs = $scope.topLevelTabs(); - if (!tabs) { - return "Loading..."; + else { + JVM.log.debug("No connection name found, using direct connection to JVM"); } - var tab = tabs.find(function (nav) { - return $scope.isActive(nav); - }); - return tab ? tab['content'] : ""; + return answer; }; }]); -})(Jmx || (Jmx = {})); - -/// -/** -* @module Jmx -*/ -var Jmx; -(function (Jmx) { - // IOperationControllerScope - Jmx._module.controller("Jmx.OperationController", ["$scope", "workspace", "jolokia", "jolokiaUrl", "$timeout", "$location", "localStorage", "$browser", function ($scope, workspace, jolokia, jolokiaUrl, $timeout, $location, localStorage, $browser) { - $scope.item = $scope.selectedOperation; - $scope.title = $scope.item.humanReadable; - $scope.desc = $scope.item.desc; - $scope.operationResult = ''; - $scope.executeIcon = "fa fa-ok"; - $scope.mode = "text"; - $scope.entity = {}; - $scope.formConfig = { - hideLegend: true, - properties: {} - }; - $scope.jolokiaUrl = Jmx.getUrlForThing(jolokiaUrl, 'exec', workspace.getSelectedMBeanName(), $scope.item.name); - $scope.item.args.forEach(function (arg) { - var property = { - type: arg.type, - tooltip: arg.desc, - description: "Type: " + arg.type - }; - if (arg.type.toLowerCase() === 'java.util.list' || arg.type.toLowerCase() === '[j') { - property.type = 'array'; - property.items = { type: 'string' }; - } - if (arg.type.toLowerCase() === 'java.util.map') { - property.type = 'map'; - property.items = { - key: { type: 'string' }, - value: { type: 'string' } - }; + JVM._module.service('ConnectOptions', ['ConnectionName', function (ConnectionName) { + var name = ConnectionName(); + if (Core.isBlank(name)) { + // this will fail any if (ConnectOptions) check + return false; + } + var answer = Core.getConnectOptions(name); + try { + if (window.opener && "passUserDetails" in window.opener) { + answer.userName = window.opener["passUserDetails"].username; + answer.password = window.opener["passUserDetails"].password; } - $scope.formConfig.properties[arg.name] = property; - }); - Jmx.log.debug("Form config: ", $scope.formConfig); - $timeout(function () { - $("html, body").animate({ scrollTop: 0 }, "medium"); - }, 250); - $scope.dump = function (data) { - console.log(data); - }; - $scope.ok = function () { - $scope.operationResult = ''; - }; - $scope.reset = function () { - $scope.entity = {}; + } + catch (securityException) { + } + return answer; + }]); + // the jolokia URL we're connected to + JVM._module.factory('jolokiaUrl', ['ConnectOptions', 'documentBase', function (ConnectOptions, documentBase) { + var answer = undefined; + if (!ConnectOptions || !ConnectOptions.name) { + JVM.log.debug("Using discovered URL"); + answer = discoveredUrl; + } + else { + answer = Core.createServerConnectionUrl(ConnectOptions); + JVM.log.debug("Using configured URL"); + } + if (!answer) { + // this will force a dummy jolokia instance + return false; + } + // build full URL + var windowURI = new URI(); + var jolokiaURI = undefined; + if (_.startsWith(answer, '/') || _.startsWith(answer, 'http')) { + jolokiaURI = new URI(answer); + } + else { + jolokiaURI = new URI(UrlHelpers.join(documentBase, answer)); + } + if (!jolokiaURI.protocol()) { + jolokiaURI.protocol(windowURI.protocol()); + } + if (!jolokiaURI.hostname()) { + jolokiaURI.host(windowURI.hostname()); + } + if (!jolokiaURI.port()) { + jolokiaURI.port(windowURI.port()); + } + answer = jolokiaURI.toString(); + JVM.log.debug("Complete jolokia URL: ", answer); + return answer; + }]); + // holds the status returned from the last jolokia call (?) + JVM._module.factory('jolokiaStatus', function () { + return { + xhr: null }; - $scope.close = function () { - $scope.$parent.showInvoke = false; + }); + JVM.DEFAULT_MAX_DEPTH = 7; + JVM.DEFAULT_MAX_COLLECTION_SIZE = 500; + JVM._module.factory('jolokiaParams', ["jolokiaUrl", "localStorage", function (jolokiaUrl, localStorage) { + var answer = { + canonicalNaming: false, + ignoreErrors: true, + mimeType: 'application/json', + maxDepth: JVM.DEFAULT_MAX_DEPTH, + maxCollectionSize: JVM.DEFAULT_MAX_COLLECTION_SIZE }; - $scope.handleResponse = function (response) { - $scope.executeIcon = "fa fa-ok"; - $scope.operationStatus = "success"; - if (response === null || 'null' === response) { - $scope.operationResult = "Operation Succeeded!"; + if ('jolokiaParams' in localStorage) { + answer = angular.fromJson(localStorage['jolokiaParams']); + } + else { + localStorage['jolokiaParams'] = angular.toJson(answer); + } + answer['url'] = jolokiaUrl; + return answer; + }]); + JVM._module.factory('jolokia', ["$location", "localStorage", "jolokiaStatus", "$rootScope", "userDetails", "jolokiaParams", "jolokiaUrl", "ConnectOptions", "HawtioDashboard", "$modal", function ($location, localStorage, jolokiaStatus, $rootScope, userDetails, jolokiaParams, jolokiaUrl, connectionOptions, dash, $modal) { + if (dash.inDashboard && JVM.windowJolokia) { + return JVM.windowJolokia; + } + if (jolokiaUrl) { + // pass basic auth credentials down to jolokia if set + var username = null; + var password = null; + if (connectionOptions.userName && connectionOptions.password) { + username = connectionOptions.userName; + password = connectionOptions.password; } - else if (typeof response === 'string') { - $scope.operationResult = response; + else if (angular.isDefined(userDetails) && angular.isDefined(userDetails.username) && angular.isDefined(userDetails.password)) { + username = userDetails.username; + password = userDetails.password; } else { - $scope.operationResult = angular.toJson(response, true); - } - $scope.mode = CodeEditor.detectTextFormat($scope.operationResult); - Core.$apply($scope); - }; - $scope.onSubmit = function () { - var json = $scope.entity; - Jmx.log.debug("onSubmit: json:", json); - Jmx.log.debug("$scope.item.args: ", $scope.item.args); - angular.forEach(json, function (value, key) { - $scope.item.args.find(function (arg) { - return arg['name'] === key; - }).value = value; - }); - $scope.execute(); - }; - $scope.execute = function () { - var node = workspace.selection; - if (!node) { - return; - } - var objectName = node.objectName; - if (!objectName) { - return; + // lets see if they are passed in via request parameter... + var search = $location.search(); + username = search["_user"]; + password = search["_pwd"]; + if (angular.isArray(username)) + username = username[0]; + if (angular.isArray(password)) + password = password[0]; } - var args = [objectName, $scope.item.name]; - if ($scope.item.args) { - $scope.item.args.forEach(function (arg) { - args.push(arg.value); + // Also set an X-Authorization header as well + var headers = ['Authorization', 'X-Authorization']; + if (username && password && !connectionOptions.token) { + userDetails.username = username; + userDetails.password = password; + JVM.log.debug("Setting authorization header to username/password"); + $.ajaxSetup({ + beforeSend: function (xhr) { + headers.forEach(function (header) { + xhr.setRequestHeader(header, Core.getBasicAuthHeader(username, password)); + }); + } }); } - args.push(Core.onSuccess($scope.handleResponse, { - error: function (response) { - $scope.executeIcon = "fa fa-ok"; - $scope.operationStatus = "error"; - var error = response.error; - $scope.operationResult = error; - var stacktrace = response.stacktrace; - if (stacktrace) { - $scope.operationResult = stacktrace; + else if (connectionOptions.token) { + JVM.log.debug("Setting authorization header to token"); + $.ajaxSetup({ + beforeSend: function (xhr) { + headers.forEach(function (header) { + xhr.setRequestHeader(header, 'Bearer ' + connectionOptions.token); + }); } - Core.$apply($scope); - } - })); - $scope.executeIcon = "fa fa-spinner fa fa-spin"; - var fn = jolokia.execute; - fn.apply(jolokia, args); - }; - }]); - Jmx._module.controller("Jmx.OperationsController", ["$scope", "workspace", "jolokia", "rbacACLMBean", "$templateCache", function ($scope, workspace, jolokia, rbacACLMBean, $templateCache) { - $scope.fetched = false; - $scope.operations = {}; - $scope.objectName = ''; - $scope.methodFilter = ''; - $scope.workspace = workspace; - $scope.selectedOperation = null; - $scope.showInvoke = false; - $scope.template = ""; - $scope.invokeOp = function (operation) { - if (!$scope.canInvoke(operation)) { - return; + }); } - $scope.selectedOperation = operation; - $scope.showInvoke = true; - }; - $scope.getJson = function (operation) { - return angular.toJson(operation, true); - }; - $scope.cancel = function () { - $scope.selectedOperation = null; - $scope.showInvoke = false; - }; - $scope.$watch('showInvoke', function (newValue, oldValue) { - if (newValue !== oldValue) { - if (newValue) { - $scope.template = $templateCache.get("operationTemplate"); + else { + JVM.log.debug("Not setting any authorization header"); + } + var modal = null; + jolokiaParams['ajaxError'] = function (xhr, textStatus, error) { + if (xhr.status === 401 || xhr.status === 403) { + userDetails.username = null; + userDetails.password = null; + delete userDetails.loginDetails; + delete window.opener["passUserDetails"]; } else { - $scope.template = ""; + jolokiaStatus.xhr = xhr; + if (!xhr.responseText && error) { + xhr.responseText = error.stack; + } + } + if (!modal) { + modal = $modal.open({ + templateUrl: UrlHelpers.join(JVM.templatePath, 'jolokiaError.html'), + controller: ['$scope', '$modalInstance', 'ConnectOptions', 'jolokia', function ($scope, instance, ConnectOptions, jolokia) { + jolokia.stop(); + $scope.responseText = xhr.responseText; + $scope.ConnectOptions = ConnectOptions; + $scope.retry = function () { + modal = null; + instance.close(); + jolokia.start(); + }; + $scope.goBack = function () { + if (ConnectOptions.returnTo) { + window.location.href = ConnectOptions.returnTo; + } + }; + }] + }); + Core.$apply($rootScope); + } + }; + var jolokia = new Jolokia(jolokiaParams); + jolokia.stop(); + // TODO this should really go away, need to track down any remaining spots where this is used + //localStorage['url'] = jolokiaUrl; + if ('updateRate' in localStorage) { + if (localStorage['updateRate'] > 0) { + jolokia.start(localStorage['updateRate']); } } - }); - var fetch = _.debounce(function () { - var node = workspace.selection || workspace.getSelectedMBean(); - if (!node) { - return; - } - $scope.objectName = node.objectName; - if (!$scope.objectName) { - return; - } - jolokia.request({ - type: 'list', - path: Core.escapeMBeanPath($scope.objectName) - }, Core.onSuccess(render)); - }, 100, { trailing: true }); - function getArgs(args) { - return "(" + args.map(function (arg) { - return arg.type; - }).join() + ")"; + JVM.windowJolokia = jolokia; + return jolokia; } - function sanitize(value) { - for (var item in value) { - item = "" + item; - value[item].name = item; - value[item].humanReadable = Core.humanizeValue(item); - } - return value; + else { + var answer = { + isDummy: true, + running: false, + request: function (req, opts) { return null; }, + register: function (req, opts) { return null; }, + list: function (path, opts) { return null; }, + search: function (mBeanPatter, opts) { return null; }, + getAttribute: function (mbean, attribute, path, opts) { return null; }, + setAttribute: function (mbean, attribute, value, path, opts) { + }, + version: function (opts) { return null; }, + execute: function (mbean, operation) { + var args = []; + for (var _i = 2; _i < arguments.length; _i++) { + args[_i - 2] = arguments[_i]; + } + return null; + }, + start: function (period) { + answer.running = true; + }, + stop: function () { + answer.running = false; + }, + isRunning: function () { return answer.running; }, + jobs: function () { return []; } + }; + JVM.windowJolokia = answer; + // empty jolokia that returns nothing + return answer; } - $scope.isOperationsEmpty = function () { - return $.isEmptyObject($scope.operations); - }; - $scope.doFilter = function (item) { - if (Core.isBlank($scope.methodFilter)) { - return true; - } - if (item.name.toLowerCase().has($scope.methodFilter.toLowerCase()) || item.humanReadable.toLowerCase().has($scope.methodFilter.toLowerCase())) { - return true; - } - return false; - }; - $scope.canInvoke = function (operation) { - if (!('canInvoke' in operation)) { + }]); +})(JVM || (JVM = {})); + +/// +/** + * @module JVM + */ +var JVM; +(function (JVM) { + JVM._module.controller("JVM.JVMsController", ["$scope", "$window", "$location", "localStorage", "workspace", "jolokia", "mbeanName", function ($scope, $window, $location, localStorage, workspace, jolokia, mbeanName) { + JVM.configureScope($scope, $location, workspace); + $scope.data = []; + $scope.deploying = false; + $scope.status = ''; + $scope.initDone = false; + $scope.filter = ''; + $scope.filterMatches = function (jvm) { + if (Core.isBlank($scope.filter)) { return true; } else { - return operation['canInvoke']; - } - }; - $scope.getClass = function (operation) { - if ($scope.canInvoke(operation)) { - return 'can-invoke'; - } - else { - return 'cant-invoke'; + return jvm.alias.toLowerCase().has($scope.filter.toLowerCase()); } }; - $scope.$watch('workspace.selection', function (newValue, oldValue) { - if (workspace.moveIfViewInvalid()) { - return; - } - fetch(); - }); - function fetchPermissions(objectName, operations) { - var map = {}; - map[objectName] = []; - angular.forEach(operations, function (value, key) { - map[objectName].push(value.name); - }); - rbacACLMBean.then(function (rbacACLMBean) { - jolokia.request({ - type: 'exec', - mbean: rbacACLMBean, - operation: 'canInvoke(java.util.Map)', - arguments: [map] - }, Core.onSuccess(function (response) { - var map = response.value; - angular.forEach(map[objectName], function (value, key) { - operations[key]['canInvoke'] = value['CanInvoke']; - }); - Jmx.log.debug("Got operations: ", $scope.operations); + $scope.fetch = function () { + jolokia.request({ + type: 'exec', + mbean: mbeanName, + operation: 'listLocalJVMs()', + arguments: [] + }, { + success: render, + error: function (response) { + $scope.data = []; + $scope.initDone = true; + $scope.status = 'Could not discover local JVM processes: ' + response.error; Core.$apply($scope); - }, { - error: function (response) { - // silently ignore - Jmx.log.debug("Failed to fetch ACL for operations: ", response); - Core.$apply($scope); - } - })); - }); - } - function render(response) { - $scope.fetched = true; - var ops = response.value.op; - var answer = {}; - angular.forEach(ops, function (value, key) { - if (angular.isArray(value)) { - angular.forEach(value, function (value, index) { - answer[key + getArgs(value.args)] = value; - }); - } - else { - answer[key + getArgs(value.args)] = value; } }); - $scope.operations = sanitize(answer); - if ($scope.isOperationsEmpty()) { - Core.$apply($scope); - } - else { - fetchPermissions($scope.objectName, $scope.operations); - Core.$apply($scope); + }; + $scope.stopAgent = function (pid) { + jolokia.request({ + type: 'exec', + mbean: mbeanName, + operation: 'stopAgent(java.lang.String)', + arguments: [pid] + }, Core.onSuccess(function () { + $scope.fetch(); + })); + }; + $scope.startAgent = function (pid) { + jolokia.request({ + type: 'exec', + mbean: mbeanName, + operation: 'startAgent(java.lang.String)', + arguments: [pid] + }, Core.onSuccess(function () { + $scope.fetch(); + })); + }; + $scope.connectTo = function (url, scheme, host, port, path) { + // we only need the port and path from the url, as we got the rest + var options = {}; + options["scheme"] = scheme; + options["host"] = host; + options["port"] = port; + options["path"] = path; + // add empty username as we dont need login + options["userName"] = ""; + options["password"] = ""; + var con = Core.createConnectToServerOptions(options); + con.name = "local"; + JVM.log.debug("Connecting to local JVM agent: " + url); + Core.connectToServer(localStorage, con); + Core.$apply($scope); + }; + function render(response) { + $scope.initDone = true; + $scope.data = response.value; + if ($scope.data.length === 0) { + $scope.status = 'Could not discover local JVM processes'; } + Core.$apply($scope); } + $scope.fetch(); }]); -})(Jmx || (Jmx = {})); +})(JVM || (JVM = {})); +/// +/// /** - * @module Core + * @module JVM */ -/// -var Jmx; -(function (Jmx) { - // NOTE - $route is brought in here to ensure the factory for that service - // has been called, otherwise the ng-include directive doesn't show the partial - // after a refresh until you click a top-level link. - Jmx.ViewController = Jmx._module.controller("Jmx.ViewController", ["$scope", "$route", "$location", "layoutTree", "layoutFull", "viewRegistry", function ($scope, $route, $location, layoutTree, layoutFull, viewRegistry) { - findViewPartial(); - $scope.$on("$routeChangeSuccess", function (event, current, previous) { - findViewPartial(); - }); - function searchRegistry(path) { - var answer = undefined; - _.forIn(viewRegistry, function (value, key) { - if (!answer) { - if (key.startsWith("/") && key.endsWith("/")) { - // assume its a regex - var text = key.substring(1, key.length - 1); - try { - var reg = new RegExp(text, ""); - if (reg.exec(path)) { - answer = value; - } - } - catch (e) { - Jmx.log.debug("Invalid RegExp " + text + " for viewRegistry value: " + value); - } - } - else { - if (path.startsWith(key)) { - answer = value; - } - } - } - }); - //log.debug("Searching for: " + path + " returning: ", answer); - return answer; - } - function findViewPartial() { - var answer = null; - var hash = $location.search(); - var tab = hash['tab']; - if (angular.isString(tab)) { - answer = searchRegistry(tab); - } - if (!answer) { - var path = $location.path(); - if (path) { - if (path.startsWith("/")) { - path = path.substring(1); - } - answer = searchRegistry(path); - } - } - if (!answer) { - answer = layoutTree; - } - $scope.viewPartial = answer; - Jmx.log.debug("Using view partial: " + answer); - return answer; - } +var JVM; +(function (JVM) { + JVM._module.controller("JVM.NavController", ["$scope", "$location", "workspace", function ($scope, $location, workspace) { + JVM.configureScope($scope, $location, workspace); }]); -})(Jmx || (Jmx = {})); +})(JVM || (JVM = {})); + +/// +/// +/** + * @module JVM + */ +var JVM; +(function (JVM) { + JVM._module.controller("JVM.ResetController", ["$scope", "localStorage", function ($scope, localStorage) { + $scope.doClearConnectSettings = function () { + var doReset = function () { + delete localStorage[JVM.connectControllerKey]; + delete localStorage[JVM.connectionSettingsKey]; + setTimeout(function () { + window.location.reload(); + }, 10); + }; + doReset(); + }; + }]); +})(JVM || (JVM = {})); /// ///